前面几篇文章已经通过配置DTS的方式完成了多个驱动的移植,接下来我们解决TQ335x的触摸驱动问题。由于种种原因,TQ335x的触摸屏驱动是以模块方式提供的,且Linux官方内核中也没有带该触摸屏的驱动源码,单纯的配置DTS是无法完成TQ335x的触摸驱动移植工作的,因此,本文参考内核中原有的pixcir_i2c_ts驱动编写TQ335x的触摸屏(TN92)驱动。

在之前移植TQ210时,我已经编写过TQ210的触摸屏驱动,我的TQ335x还是使用的TQ210的屏,因此,难度不是很大。这里需要说明一点,在TQ210驱动移植时对多点触摸协议的理解还不够深入,当时编写的驱动单点触摸是可以正常使用的,但是多点触摸不对(这次编写TQ335x的触摸驱动是才意识到的)。但是编写的TQ210驱动多点触摸实际上使用的多点触摸的A协议,但是用错了一些地方,本文基于TQ335x的重新编写的触摸驱动是按照多点触摸B协议编写,使用tslib测试正常,文章末尾有效果图。

TN92触摸屏使用的触控芯片是GT811,下面我们来分析下触摸屏驱动的编写。

(1) 查看原理图

从触摸屏原理图中可以看到,GT811与开发板相连的引脚有四条,分别是SDA、SDL、INT和RESET。其中,I2C_SDA和I2C_SDL是连接到AM335x的I2C1端口上的,用来与SoC通信;INT引脚是连接在GPIO1的27号引脚上的,在检测到触摸时GT811通过该引脚向SoC发起中断请求;RESET引脚接到SoC的GPIO1的26号引脚上的,是用来接收SoC复位操作,对于本文,SoC就是AM335x。

其中,GT811与SoC的管脚连接信息可以从底板原理图中找到,SDA和SCL的我就不往外贴了,INT和RESET的连接关系如下:

YP连接到了GT811的RESET脚上,然后通过短路帽与GPIO1_26链接,YM连接到GT811的INT脚上,通过短路帽与GPIO1_27链接,因此,需要将GPIO1_27配置为终端输入引脚,GPIO1_26配置为输出引脚。此外,查看GT811的芯片手册可获得INT和RESET的操作信息:

  1. 1. GT811检测到触摸时会拉低中断引脚,因此,GPIO1_27需要配置为下降沿触发。
  2. 2. GT811的RESET脚为低电平有效,因此,上电时需要拉低RESET引脚。
  3. 3. GT811的RESET引脚自带上拉,因此,使用GPIO1_26将GT811的RESET拉低复位后切换为悬浮输入太即可。

了解这些信息后我们就可以开始分析驱动结构了。

(2) DTS配置Platform信息

通过前面的分析,我们知道需要配置AM335x的四条引脚才能使GT811正常工作。其中,GT811通过I2C接口连接到AM335x的I2C1上,因此,需要配置AM335x的I2C1的两条引脚为I2C功能;GPIO1_27需要配置为中断输入、下降沿触发,中断号也可以确定下来了,就是GPIO1的27号角(内核能将引脚转换为中断号);最后就是RESET脚,驱动初始化GT811时需要操作RESET引脚,且GT811为拉低复位,故可将GPIO1的26角设为输出电平状态。通过前面几篇文章的学习,我们知道DTS可以配置pinmux,然后分析内核自带的DTS文件可以,DTS也可以将连接信息传递给内核。经过分析及参考,我最TQ335x.dts文件做了如下修改:

Step1. 检查I2C引脚的pinmux配置

查找i2c1可以找到i2c1节点,该节点的pinctrl-0只想的phandler对i2c1的两个引脚进行了相关的配置,因此,不需任何修改。

Step2. 配置INT和RESET引脚

在am33xx_pinmux节点内添加引脚配置信息,具体内容如下:

  1. gt811_ts_pins: gt811_ts_pins {
  2. pinctrl-single,pins = <
  3. 0x68 (PIN_INPUT_PULLUP | MUX_MODE7)
  4. 0x6c (PIN_INPUT_PULLUP | MUX_MODE7)
  5. >;
  6. };

Step3. 在i2c1节点内添加GT811设备信息

GT811的设备节点内需要提供以下信息,i2c设备地址、pinmux配置、中断信息、屏幕大小等,具体如下(这里就不细说了,有什么不清楚的可以留言讨论)。

  1. gt811_ts@5d {
  2. compatible = "gt811,gt811_ts";
  3. pinctrl-names = "default";
  4. pinctrl-0 = <&gt811_ts_pins>;
  5. reg = <0x5d>;
  6. interrupt-parent = <&gpio1>;
  7. interrupts = <27 2>;
  8. gpios = <&gpio1 26 0>;
  9. touchscreen-size-x = <800>;
  10. touchscreen-size-y = <480>;
  11. touchscreen-swap = <1>;
  12. touchscreen-revert-x = <1>;
  13. touchscreen-revert-y = <1>;
  14. };

至此,DTS的配置工作就完成了,下面就是GT811的驱动编写。
(3)驱动编写

驱动编写根之前TQ210相比,没有多少变化,都是采用的新式I2C设备驱动架构,重要的区别在于支持了多点触摸,驱动的详细分析我就不多说了,具体可以参考韦东山老师的视频教程(绝对物有所值)。下面是GT811的多点触摸驱动源码。从GT811设备节点中获取坐标信息的部分我直接贴出来了,完整的代码还是请到资源里下载,还是有点贵哈,不过这个驱动可以直接拿去使用了,不需要任何修改。下面是不完整的代码,可以打印出触摸点坐标,该代码已经把本驱动的核心的部分都写出来了。其实很多朋友不需要下载源码就可以补充完善这个驱动了,而且我真心的希望阅读本文的朋友不需要下载源码就能自己写出触摸驱动。

  1. #include <linux/module.h>
  2. #include <linux/i2c.h>
  3. #include <linux/platform_device.h>
  4. #include <linux/gpio.h>
  5. #include <linux/of.h>
  6. #include <linux/of_platform.h>
  7. #include <linux/of_gpio.h>
  8. #include <linux/input.h>
  9. #include <linux/input/mt.h>
  10. #include <linux/interrupt.h>
  11. #include <linux/delay.h>
  12. struct gt811_ts_platdata
  13. {
  14. u32 size_x;
  15. u32 size_y;
  16. u32 size_p;
  17. u32 swap;
  18. u32 revert_x;
  19. u32 revert_y;
  20. u32 reset_pin;
  21. u32 interrupt_pin;
  22. u32 ponits_max;
  23. struct i2c_client *client;
  24. struct input_dev *input;
  25. struct work_struct work;
  26. };
  27. static const struct of_device_id gt811_ts_of_match[] = {
  28. { .compatible = "gt811,gt811_ts", .data = NULL },
  29. { }
  30. };
  31. static int i2c_write_bytes(struct i2c_client *client, uint8_t *data, int len){
  32. struct i2c_msg msg;
  33. msg.flags=!I2C_M_RD;
  34. msg.addr=client->addr;
  35. msg.len=len;
  36. msg.buf=data;
  37. return i2c_transfer(client->adapter,&msg, 1);
  38. }
  39. static int i2c_read_bytes(struct i2c_client *client, uint8_t *buf, int len){
  40. struct i2c_msg msgs[2];
  41. msgs[0].flags=!I2C_M_RD;
  42. msgs[0].addr=client->addr;
  43. msgs[0].len=2;
  44. msgs[0].buf=&buf[0];
  45. msgs[1].flags=I2C_M_RD;
  46. msgs[1].addr=client->addr;
  47. msgs[1].len=len-2;
  48. msgs[1].buf=&buf[2];
  49. return i2c_transfer(client->adapter,msgs, 2);
  50. }
  51. static void gt811_ts_handler(struct work_struct *work)
  52. {
  53. struct gt811_ts_platdata *pdata = container_of(work, struct gt811_ts_platdata, work);
  54. struct device *dev = &pdata->client->dev;
  55. uint8_t buffer[36] = {0x07, 0x21, 0};
  56. uint8_t count, index, flags, position;
  57. int x, y;
  58. buffer[0] = 0x0f;
  59. buffer[1] = 0xff;
  60. if (i2c_write_bytes(pdata->client,buffer,2) < 0) {
  61. dev_err(dev, "Failed to write wakeup message.\n");
  62. goto reenable_irq;
  63. }
  64. buffer[0] = 0x07;
  65. buffer[1] = 0x21;
  66. if (i2c_read_bytes(pdata->client, buffer, sizeof(buffer)) < 0) {
  67. dev_err(dev, "Failed to read touch message.\n");
  68. goto reenable_irq;
  69. }
  70. buffer[0] = 0x80;
  71. buffer[1] = 0x00;
  72. if (i2c_write_bytes(pdata->client, buffer, 2) < 0) {
  73. dev_err(dev, "Failed to write sleep message.\n");
  74. goto reenable_irq;
  75. }
  76. buffer[25] = buffer[19];
  77. buffer[19] = 0;
  78. flags = buffer[2]&0x1f;
  79. while (flags) {
  80. if (!(flags&0x1)) {
  81. continue;
  82. }
  83. if (index < 3) {
  84. position = 4 + index * 5;
  85. }
  86. else{
  87. position = 25 + (index - 3) * 5;
  88. }
  89. x = (buffer[position] << 8) | buffer[position + 1];
  90. y = (buffer[position + 2] << 8) | buffer[position + 3];
  91. if(pdata->swap) {
  92. swap(x, y);
  93. }
  94. if(pdata->revert_x){
  95. x = pdata->size_x - x;
  96. }
  97. if(pdata->revert_y){
  98. y = pdata->size_y - y;
  99. }
  100. printk("point:(x:%03d, y:%03d)\n", x, y);
  101. }
  102. // 组织检测出来的触摸点信息上报到输入子系统节点即可
  103. reenable_irq:
  104. enable_irq(pdata->client->irq);
  105. }
  106. static irqreturn_t gt811_ts_isr(int irq, void *dev_id)
  107. {
  108. struct gt811_ts_platdata* pdata = (struct gt811_ts_platdata*)dev_id;
  109. disable_irq_nosync(pdata->client->irq);
  110. schedule_work(&pdata->work);
  111. return IRQ_HANDLED;
  112. }
  113. static int gt811_ts_initilize(struct i2c_client *client)
  114. {
  115. struct device *dev = &client->dev;
  116. struct gt811_ts_platdata *pdata = (struct gt811_ts_platdata*)i2c_get_clientdata(client);
  117. int status = 0, count = 0;
  118. uint8_t version[4] = {0x7, 0x17, 0};
  119. uint8_t config[] = {
  120. 0x06,0xA2,
  121. 0x12,0x10,0x0E,0x0C,0x0A,0x08,0x06,0x04,0x02,0x00,0xE2,0x53,0xD2,0x53,0xC2,0x53,
  122. 0xB2,0x53,0xA2,0x53,0x92,0x53,0x82,0x53,0x72,0x53,0x62,0x53,0x52,0x53,0x42,0x53,
  123. 0x32,0x53,0x22,0x53,0x12,0x53,0x02,0x53,0xF2,0x53,0x0F,0x13,0x40,0x40,0x40,0x10,
  124. 0x10,0x10,0x0F,0x0F,0x0A,0x35,0x25,0x0C,0x03,0x00,0x05,0x20,0x03,0xE0,0x01,0x00,
  125. 0x00,0x34,0x2C,0x36,0x2E,0x00,0x00,0x03,0x19,0x03,0x08,0x00,0x00,0x00,0x00,0x00,
  126. 0x14,0x10,0xEC,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0D,0x40,
  127. 0x30,0x3C,0x28,0x00,0x00,0x00,0x00,0xC0,0x12,0x01
  128. };
  129. config[62] = 480 >> 8;
  130. config[61] = 480 & 0xff;
  131. config[64] = 800 >> 8;
  132. config[63] = 800 & 0xff;
  133. if (!gpio_is_valid(pdata->reset_pin)) {
  134. dev_err(dev, "The reset pin number is invalid.\n");
  135. return -EINVAL;
  136. }
  137. count = 3;
  138. while (count--) {
  139. gpio_direction_output(pdata->reset_pin, 0);
  140. msleep(10);
  141. gpio_direction_output(pdata->reset_pin, 1);
  142. msleep(100);
  143. if (i2c_read_bytes(client, version, sizeof(version)) < 0) {
  144. dev_err(dev, "Failed to get the version of GT811, try again...\n");
  145. status = -ENODEV;
  146. }
  147. else {
  148. dev_info(dev, "Gt811 detected, version(%04x)...\n", (version[2]<<8)|version[3]);
  149. status = 0;
  150. break;
  151. }
  152. }
  153. if (status) {
  154. return status;
  155. }
  156. count = 3;
  157. while (count--) {
  158. if (i2c_write_bytes(client, config, sizeof(config)) < 0) {
  159. dev_err(dev, "Failed to configure the GT811, try again...\n");
  160. status = -EINVAL;
  161. }
  162. else {
  163. dev_info(dev, "Gt811 configue succeed\n");
  164. status = 0;
  165. break;
  166. }
  167. }
  168. return status;
  169. }
  170. static struct gt811_ts_platdata *gt811_ts_parse_devtree(struct i2c_client *client)
  171. {
  172. struct device *dev = &client->dev;
  173. struct device_node *node;
  174. struct gt811_ts_platdata *pdata;
  175. enum of_gpio_flags flags;
  176. node = dev->of_node;
  177. if (!node) {
  178. dev_err(dev, "The of_node is NULL.\n");
  179. return ERR_PTR(-ENODEV);
  180. }
  181. pdata = devm_kzalloc(dev, sizeof(struct device_node), GFP_KERNEL);
  182. if (!pdata) {
  183. dev_err(dev, "No enough memory left.\n");
  184. return ERR_PTR(-ENOMEM);
  185. }
  186. pdata->reset_pin = of_get_gpio_flags(node, 0, &flags);
  187. if (pdata->reset_pin < 0) {
  188. dev_err(dev, "Get RST pin failed!\n");
  189. return ERR_PTR(-EINVAL);
  190. }
  191. if (of_property_read_u32(node, "touchscreen-size-x", &pdata->size_x )) {
  192. dev_err(dev, "Failed to get the touch screen x size.\n");
  193. return ERR_PTR(-EINVAL);
  194. }
  195. if (of_property_read_u32(node, "touchscreen-size-y", &pdata->size_y)) {
  196. dev_err(dev, "Failed to get the touch screen y size.\n");
  197. return ERR_PTR(-EINVAL);
  198. }
  199. if (of_property_read_u32(node, "touchscreen-size-p", &pdata->size_p)) {
  200. pdata->size_p = 255;
  201. }
  202. if (of_property_read_u32(node, "touchscreen-swap", &pdata->swap)) {
  203. pdata->swap = 1;
  204. }
  205. if (of_property_read_u32(node, "touchscreen-revert-x", &pdata->revert_x)) {
  206. pdata->revert_x = 1;
  207. }
  208. if (of_property_read_u32(node, "touchscreen-revert-x", &pdata->revert_y)) {
  209. pdata->revert_y = 1;
  210. }
  211. return pdata;
  212. }
  213. static int gt811_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
  214. {
  215. struct device *dev = &client->dev;
  216. struct gt811_ts_platdata *pdata = dev_get_platdata(dev);
  217. struct input_dev *input;
  218. int error = 0;
  219. if (!of_match_device(of_match_ptr(gt811_ts_of_match), dev)) {
  220. dev_err(dev, "Failed to match.\n");
  221. return -EINVAL;
  222. }
  223. if (!pdata) {
  224. pdata = gt811_ts_parse_devtree(client);
  225. if (IS_ERR(pdata)) {
  226. dev_err(dev, "Get device data from device tree failed!\n");
  227. error = -EINVAL;
  228. goto failed_exit;
  229. }
  230. }
  231. pdata->client = client;
  232. i2c_set_clientdata(client, pdata);
  233. input = devm_input_allocate_device(dev);
  234. if (!input) {
  235. dev_err(dev, "Failed to allocate input device\n");
  236. error = -ENOMEM;
  237. goto pdata_free;
  238. }
  239. pdata->input = input;
  240. input->name = client->name;
  241. input->id.bustype = BUS_I2C;
  242. input->id.product = 0xBEEF;
  243. input->id.vendor  =0xDEAD;
  244. input->dev.parent = &client->dev;
  245. __set_bit(EV_KEY, input->evbit);
  246. __set_bit(EV_ABS, input->evbit);
  247. __set_bit(BTN_TOUCH, input->keybit);
  248. input_set_abs_params(input, ABS_X, 0, pdata->size_x, 0, 0);
  249. input_set_abs_params(input, ABS_Y, 0, pdata->size_y, 0, 0);
  250. input_set_abs_params(input, ABS_MT_POSITION_X, 0, pdata->size_x, 0, 0);
  251. input_set_abs_params(input, ABS_MT_POSITION_Y, 0, pdata->size_y, 0, 0);
  252. error = input_mt_init_slots(input, 5, INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
  253. if (error) {
  254. dev_err(dev, "Failed to initialize the multi-touch slots.\n");
  255. goto input_free;
  256. }
  257. input_set_drvdata(input, pdata);
  258. error = input_register_device(input);
  259. if (error) {
  260. dev_err(dev, "Register input device failed!\n");
  261. goto input_free;
  262. }
  263. if (gt811_ts_initilize(client)) {
  264. dev_err(dev, "Failed to initialize GT811.\n");
  265. }
  266. INIT_WORK(&pdata->work, gt811_ts_handler);
  267. error = devm_request_any_context_irq(dev, client->irq, gt811_ts_isr,
  268. IRQF_TRIGGER_FALLING, client->name, pdata);
  269. if (error) {
  270. dev_err(dev, "Failed to request irq(number:%d)\n", client->irq);
  271. goto input_free;
  272. }
  273. return 0;
  274. input_free:
  275. devm_kfree(dev, input);
  276. pdata_free:
  277. devm_kfree(dev, pdata);
  278. failed_exit:
  279. return error;
  280. }
  281. static int gt811_ts_remove(struct i2c_client *client)
  282. {
  283. struct gt811_ts_platdata *pdata = (struct gt811_ts_platdata*)i2c_get_clientdata(client);
  284. devm_free_irq(&client->dev, client->irq, i2c_get_clientdata(client));
  285. input_unregister_device(pdata->input);
  286. devm_kfree(&client->dev, pdata);
  287. return 0;
  288. }
  289. static const struct i2c_device_id gt811_ts_id[] = {
  290. { "gt811_ts", 0 },
  291. { }
  292. };
  293. static struct i2c_driver gt811_ts_driver = {
  294. .driver = {
  295. .owner  = THIS_MODULE,
  296. .name   = "gt811_ts",
  297. .of_match_table = of_match_ptr(gt811_ts_of_match),
  298. },
  299. .probe      = gt811_ts_probe,
  300. .remove     = gt811_ts_remove,
  301. .id_table   = gt811_ts_id,
  302. };
  303. module_i2c_driver(gt811_ts_driver);
  304. MODULE_AUTHOR("girlkoo <nightmeng@gmail.com>");
  305. MODULE_DESCRIPTION("Gt811 I2C Touchscreen Driver");
  306. MODULE_LICENSE("GPL");

(4) 使用tslib工具测试

tslib的编译方法请参考本博客的另一片文章,链接如下:

S5PV210(TQ210)学习笔记——触摸屏驱动编写

本文就不再重复tslib的配置方法。

(5)效果展示

(6) 完整驱动代码

请到本人的资源中下载TQ335x的触摸屏驱动源码,链接如下:

http://download.csdn.net/download/girlkoo/8202177

AM335x(TQ335x)学习笔记——触摸屏驱动编写的更多相关文章

  1. AM335x(TQ335x)学习笔记——LCD驱动移植

    TI的LCD控制器驱动是非常完善的,共通的地方已经由驱动封装好了,与按键一样,我们可以通过DTS配置完成LCD的显示.下面,我们来讨论下使用DTS方式配置内核完成LCD驱动的思路. (1)初步分析 由 ...

  2. AM335x(TQ335x)学习笔记——USB驱动移植

    对于AM335x来讲,TI维护的USB驱动已经非常完善了,本文称之为移植,实际上仅仅是配置内核选项使能USB HOST/OTG功能.废话少说,直接动手开启AM335x的USB驱动配置项. Step1. ...

  3. AM335x(TQ335x)学习笔记——挂载Ramdisk

    上篇文章中我们已经能够通过u-boot启动内核了,但是没有能够启动成功,从内核的log中可以看出,内核启动失败的原因是没有挂载到root文件系统,本文将使用busybox制作根文件系统并打包成ramd ...

  4. AM335x(TQ335x)学习笔记——GPIO关键驱动移植

    或按照S5PV210学习秩序.我们首先解决的关键问题.TQ335x有六个用户按钮,每个上.下.剩下.对.Enter和ESC. 我想开始学习S5PV210当同一,写输入子系统驱动器的关键问题要解决,但浏 ...

  5. AM335x(TQ335x)学习笔记——GPIO按键驱动移植

    还是按照S5PV210的学习顺序来,我们首先解决按键问题.TQ335x有六个用户按键,分别是上.下.左.右.Enter和ESC.开始我想到的是跟学习S5PV210时一样,编写输入子系统驱动解决按键问题 ...

  6. AM335x(TQ335x)学习笔记——WM8960声卡驱动移植

    经过一段时间的调试,终于调好了TQ335x的声卡驱动.TQ335x采用的Codec是WM8960,本文来总结下WM8960驱动在AM335x平台上的移植方法.Linux声卡驱动架构有OSS和ALSA两 ...

  7. AM335x(TQ335x)学习笔记——Nand&&网卡驱动移植

    移植完成声卡驱动之后本想再接再励,移植网卡驱动,但没想到的是TI维护的内核太健壮,移植网卡驱动跟之前移植按键驱动一样简单,Nand驱动也是如此,于是,本人将Nand和网卡放在同一篇文章中介绍.介绍之前 ...

  8. AM335x(TQ335x)学习笔记——Nand&amp;&amp;网卡驱动移植

    移植完毕声卡驱动之后本想再接再励,移植网卡驱动,但没想到的是TI维护的内核太健壮,移植网卡驱动跟之前移植按键驱动一样简单,Nand驱动也是如此,于是,本人将Nand和网卡放在同一篇文章中介绍.介绍之前 ...

  9. AM335x(TQ335x)学习笔记——u-boot-2014.10移植

    根据最近移植u-boot-2014.10至TQ335x,基于这样的假设am335x evm移植.不是很多地方需要改变. 因为TI的am335x evm开发了使用eeprom船上保存配置信息.它使用不同 ...

随机推荐

  1. 一道python面试题引发的血案

    这里说的是一道阿里校招的面试题:一行代码实现对列表a中的偶数位置的元素进行加3后求和? 今天去面试同样遇到了这个题目,这道题考察的是对python高阶函数map/filter的灵活运用(具体的使用方法 ...

  2. [eslint-plugin-vue] [vue/no-unused-vars] 'scope' is defined but never used.

    前言 今天在做项目的时候Visual Studio Code报了一个错 这个错的意思是声明了scope却没有使用它,这是vue的eslink插件检测的. 我想这个scope的属性不是自己的吗,咋是我声 ...

  3. mybatis-generator 根据表生成对应文件

    1 创建maven工程 2.编辑.pom文件 <?xml version="1.0" encoding="UTF-8"?> <project ...

  4. Jquery DataTable控制显示列,导出EXCEL

    1.初始化 var table = $('#table').DataTable({ "data": data[0].DATA, "columns": data[ ...

  5. Vim【学习笔记】

    [2017-02-04] 先放几个比较好的资料,看这些资料就好了: 1.Vim入门基础 http://www.jianshu.com/p/bcbe916f97e1 2.vim配置 http://blo ...

  6. 51Nod 欢乐手速场1 B 序列变换[容斥原理 莫比乌斯函数]

    序列变换 alpq654321 (命题人)   基准时间限制:1 秒 空间限制:131072 KB 分值: 40 lyk有两序列a和b. lyk想知道存在多少对x,y,满足以下两个条件. 1:gcd( ...

  7. 在Arrays.asList()引发的问题中进一步学习集合与泛型等内容

    前言 最近在网上看到一个问题,情况类似如下(记为问题1): public class Demo { public static void main(String[] args) { System.ou ...

  8. openvpn服务器一键脚本生成客户端文件

    #!/bin/bash #获取参数 while getopts "n:" opt; do case $opt in n) client_name=$OPTARG ;; \?) ;; ...

  9. 广告中的AdNetwork、AdExchange、DSP、SSP、RTB和DMP是什么?

    [https://www.douban.com/note/557732418/?type=rec] AdNetwork.AdExchange.DSP.SSP.RTB.DMP这些模式之间存在着内在的关系 ...

  10. PHP两个日期之间的所有日期

    我想得到两个日期之间的所有日期, 例如:输入两个日期,把这两个日期之间的所有日期取出来     如果是:2005-02-01至2005-02-05(同为一个月)     则为:2005-02-01,2 ...