找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 4325|回复: 1

【单片机】给STM32F103实现一个靠谱的并行DMA库

[复制链接]

1111

主题

1651

回帖

7万

积分

用户组: 管理员

一只技术宅

UID
1
精华
244
威望
743 点
宅币
24241 个
贡献
46222 次
宅之契约
0 份
在线时间
2297 小时
注册时间
2014-1-26
发表于 2018-3-28 12:25:09 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有账号?立即注册→加入我们

×
原文网址:https://www.0xaa55.com/thread-16797-1-1.html
作者:0xAA55
可以转载。转载请注明出处。

所谓靠谱的并行DMA库,就是让单片机在设置完要发送的数据后,它就可以去干别的去了——想象一下你的电饭煲是DMA的话,那么你只需要把米淘好,放进电饭锅,然后插上电,按一下“煮饭”。然后你就可以去吃鸡了,不需要站在电饭煲面前干等。

STM32F10x系列单片机它有很多的外设(Peripheral),能给你提供各种各样的功能——模数转换、各种串口协议传输(UART、I2C、SPI、CAN等)、各种定时器等。这些外设你可以理解为你的“家具”,比如刚才所说的电饭煲等。

之前看了个毛子写的开源的ILI9341显示屏驱动,给STM32F10x写的,非常不清真——它使用DMA的方法就是按下“煮饭”以后就在电饭煲面前转圈圈了。当然这样的设计非常直观易懂,能让你一眼就能看明白这个显示器是怎么驱动的。至少,毛子的这个代码,比Adafruit的官方开源库好看多了。他还在这个的基础上写了个字体库和示波器实例。

毛子驱动:https://github.com/
fagcinsk
/stm-ILI9341-spi


毛子视频:https://www.youtube.com/watch?v=
5nLQ-VqMv-g


虽然很不错,但是实际的应用中我们不是更应该把STM32的性能发挥出来嘛?使用DMA还干等,那还不如直接写GPIO了。

我买到的ILI9341显示屏大概是下面网址上的这样的(类似的多得很,淘宝上肯定也有)

https://www.ebay.com/itm/2-8-TFT-LCD-Display-Touch-Panel-SPI-Serial-240-320-ILI9341-5V-3-3V-STM32-/
201950756171



s-l1600.png s-l1600-(1).png

我买到的这个它是使用SPI接口来控制的。SPI接口我就不详细介绍了。一言概括就是:带时钟的串行接口,数据的输入和输出是分开的,用一个单独的针来选片(低电平选片)。

一开始我买到这个屏幕后,我就到处找它的PDF文档,然后,我找到了——这文档与其说是文档不如说是“对那个官方的C艹库提供的补充说明”
PDF回帖后可见。
游客,如果您要查看本帖隐藏内容请回复
最后我决定以毛子代码为基准来重新造轮子了——我要让它把LCD绘图的工作都交给DMA控制器,然后处理器可以腾出手去干别的。

LCD绘图的工作,除了SPI接口交互的MOSI、MISO、SCK、CS四个针以外,ILI9341还有个“是数据还是命令”(DC,Data/Command)的针,有个背光灯的针(高电平亮灯),以及重置信号(RESET,低电平重置)的针。在发送命令和数据的时候,SPI控制器和DMA控制器只能负责把MOSI、MISO、SCK这几个针的信号给你并行处理好,而选片CS、区分数据命令的DC等,则需要手动干预

这意味着如果你只是简单地把你要发送的数据排个FIFO的队是行不通的。它没办法区分你发送的字节是数据还是命令。我的解决办法是:把每个DMA传输的请求封装为一个类似于“消息队列”一样的东西,它也是个FIFO,但我可以在开始传输请求的时候调用一个回调来完成CS、DC等pin的操作,然后在传输完毕中断里面,调用另一个回调函数来改回CS和DC等针的电平,并且从传输队列里面取出下一个传输请求。

也就是说,开始传输的时候,你只需要设置一下DMA控制器它就开始传输了——你只需要按一下“煮饭”,你的电饭煲就开始煮饭了。等到它完成了传输,它就会产生一个特定的中断信号,中断处理程序就会启动下一个传输请求——电饭煲煮好饭的时候会发出声音通知你,然后你就可以把米饭拿出来,再把煮粥的材料倒进电饭煲里,按下“煮粥”按钮了……

虽然传输的结束和开始依然需要处理器的干预,但传输的过程是不需要干预的了。这样就达到了我的目的——使用一个电饭煲煮各种食物的同时,不影响我干别的事。
  1. #ifndef        _DMA_BUFFERED_
  2. #define _DMA_BUFFERED_

  3. #include<inttypes.h>
  4. #include<stm32f10x_dma.h>

  5. #include"dma_queue_cfg.h"
  6. #include"delay.h"

  7. typedef struct dma_ctrl_struct
  8. {
  9.         volatile data_t data; // 数据,或者数据的地址
  10.         volatile uint16_t count; // 传输的数量
  11.         volatile uint8_t repeat_count; // 重复的次数
  12.         volatile uint8_t flags; // 控制属性
  13. }dma_ctrl_t, *dma_ctrl_p;

  14. // DCF开头的宏就是控制属性了

  15. // 传输单元位数
  16. #define DCF_BITS_8          0x0000 /* default */
  17. #define DCF_BITS_16         0x0001
  18. #define DCF_BITS_32         0x0002

  19. #define DCF_BITS_FLAGS      (DCF_BITS_8 | DCF_BITS_16 | DCF_BITS_32)

  20. // 指针行为(增大,或者不变)
  21. #define DCF_PTR_INC         0x0000 /* default */
  22. #define DCF_PTR_STAY        0x0004

  23. #define DCF_PTR_FLAGS       (DCF_PTR_INC | DCF_PTR_STAY)

  24. // 数据的存储方式(指针,或者直接装填)
  25. #define DCF_DATA_PTR        0x0000 /* default */
  26. #define DCF_DATA_INLINED    0x0008

  27. #define DCF_DATA_FLAGS      (DCF_DATA_PTR | DCF_DATA_INLINED)

  28. // 收,还是发
  29. #define DCF_DIR_TX          0x0000 /* default */
  30. #define DCF_DIR_RX          0x0010

  31. #define DCF_DIR_FLAGS       (DCF_DIR_TX | DCF_DIR_RX)

  32. // 给调用者预留的Flag,供调用者在on_start和on_finish里面判断。
  33. #define DCF_CUSTOM_FLAG     0x0080

  34. typedef struct dma_queue_struct dma_queue_t, *dma_queue_p;

  35. // 回调函数
  36. typedef void(*on_start_f)(dma_queue_p pq, uint32_t index);
  37. typedef void(*on_finish_f)(dma_queue_p pq, uint32_t index);

  38. // DMA队列对象
  39. struct dma_queue_struct
  40. {
  41.         DMA_Channel_TypeDef *channel; // DMA控制器寄存器地址
  42.         volatile void *periph_addr; // 要操作的外设的地址
  43.        
  44.         int dma_controller; // DMA控制器号,1或2
  45.         int channel_num; // DMA通道号,1,2,3,4,5,6,7
  46.         uint32_t flags; // 状态控制
  47.        
  48.         on_start_f on_start; // 开始传输时被调用的回调函数
  49.         on_finish_f on_finish; // 传输结束后被调用的回调函数
  50.        
  51.         volatile uint32_t dma_ctrl_current; // 正在传输的DMA请求
  52.         volatile uint32_t dma_ctrl_count; // 队列里的请求个数
  53.         volatile uint32_t dma_ctrl_inprogress; // 是否正在传输
  54.         dma_ctrl_t dma_ctrl[DMA_QUEUE_LENGTH]; // 队列
  55. };

  56. // 按要求定义全局变量
  57. #if DMA1_1_QUEUE
  58. extern dma_queue_t g_dma1_1_queue;
  59. #endif
  60. #if DMA1_2_QUEUE
  61. extern dma_queue_t g_dma1_2_queue;
  62. #endif
  63. #if DMA1_3_QUEUE
  64. extern dma_queue_t g_dma1_3_queue;
  65. #endif
  66. #if DMA1_4_QUEUE
  67. extern dma_queue_t g_dma1_4_queue;
  68. #endif
  69. #if DMA1_5_QUEUE
  70. extern dma_queue_t g_dma1_5_queue;
  71. #endif
  72. #if DMA1_6_QUEUE
  73. extern dma_queue_t g_dma1_6_queue;
  74. #endif
  75. #if DMA1_7_QUEUE
  76. extern dma_queue_t g_dma1_7_queue;
  77. #endif

  78. // 初始化一个DMA队列对象,返回上述的全局变量中的一个(根据前两个参数匹配)
  79. dma_queue_p dma_queue_init
  80. (
  81.         int dma_controller, // DMA控制器号,1或2
  82.         int channel_num, // DMA通道号,1,2,3,4,5,6,7
  83.         volatile void *periph_addr, // 要操作的外设的地址
  84.         on_start_f on_start, // 开始传输时被调用的回调函数
  85.         on_finish_f on_finish, // 传输结束后被调用的回调函数
  86.         uint32_t DMA_Priority // DMA传输优先级
  87. );

  88. // 压入一个DMA传输请求到队列里,返回它在队列中的位置
  89. uint32_t dma_queue_push
  90. (
  91.         dma_queue_p dma_queue, // DMA队列对象
  92.         data_t data, // 数据,或者数据的地址
  93.         uint16_t count, // 传输的数量
  94.         uint8_t repeat_count, // 重复的次数
  95.         uint8_t flags // 控制属性
  96. );

  97. // 等待特定的传输请求完成
  98. void dma_queue_wait_transfer(dma_queue_p dma_queue, uint32_t index);

  99. // 等待全部传输请求完成
  100. void dma_queue_wait(dma_queue_p dma_queue);

  101. #endif
复制代码
设计接口挺麻烦的。尤其是在单片机上,RAM非常有限,稍有不慎就爆了。没有内存管理,虽然你可以自己造一个,但动态管理内存的意义并不大。

当你的代码里出现数据段的时候,数据段里面的数据是跟着你的.text走的,而.text是存储在ROM里面的,只读。如果你对这部分区域的内存执行了写入操作,你的单片机就会Halt停机。你可以在ST-LINK Utility里面看到它停机时的PC计数器位置。

任何全局变量,或者静态的局部变量,写的时候你要是顺手加了个初始化,那就
Boom
了。你可以初始化非静态的局部变量,因为它在栈上。然后,任何没有被初始化过的全局变量和静态局部变量,最终都会进到.bss段内。

但是你的单片机程序在启动的时候,你得手动把.bss段的内存清零。

而且还有很多坑爹的玩意儿需要你去体验——它给你带的那些头文件和驱动库,里面有各种好玩的。五花八门的命名规则,钦定结构体,钦定地址,钦定外设寄存器,钦定只读中断表——中断表的每个表项的名字都是钦定的。

还有各种意义不明的宏——有的你只能靠翻PDF找出它的用途,有的你只能靠看头文件来理解它的用途,还有的你只能谷歌。

很多我们在PC开发上讨论出的各种“优良习惯”在单片机上都不适用。

dma_queue_struct这个结构体,就是用来维护一个DMA传输队列用的结构体了。它的dma_ctrl数组,就是DMA请求队列的FIFO了。我在dma_queue_cfg.h里面定义了宏用于配置它。extern dma_queue_t g_dma
x
_
y
_queue;就是这个结构体的实例化变量了。因为中断处理函数的名字是钦定的,所以我要给每一个需要用到的DMA通道都定义一个专属的全局变量。然后每个变量的体积都不小,所以用宏来配置它,只定义用得到的。免得占用RAM过多。

dma_queue_push()用来提交你的DMA传输请求。它的第一个参数,就是传递的上述结构体的地址。说到地址,这里就要提到数据的有效性。这个和我们在PC上进行多线程开发的情况很接近。在没有电饭煲的时候,我们煮饭用的是柴,我们把柴砍下来后,像打桩一样插在地上,钻木取火,引燃枯树叶,然后点燃木桩。等到木桩烧起来了,把锅子架上去。大功告成!玩泥巴去了。然后回来的时候,柴早就被别人拿走了,釜底抽薪。这是因为我们的DMA队列它的设计结构是队列方式,你提交的传输请求并不会被立即处理——它要等到前面的所有请求都处理了之后才会轮到你现在提交的请求,而这个时候你早就玩泥巴去了,栈上的数据都“物是人非”了。所以我们不能用栈来存储不会被立即引用的数据。

我的设计是:对于少量的数据(比如一条用于控制显示器的命令等),直接把它存储在每个DMA请求的data字段里,然后用DCF_DATA_INLINED来标识。而对于较大的数据的话,则另想办法。至少,我们不至于为了那几个字节的控制命令而浪费有限的RAM。用柴火煮饭的话好歹刨个坑,然后把柴火放坑里。
  1. #include"dma_queue.h"

  2. #define CCR_CLEAR_Mask ((uint32_t)0xFFFF800F)

  3. // 按要求定义全局变量
  4. #if DMA1_1_QUEUE
  5. dma_queue_t g_dma1_1_queue;
  6. #endif
  7. #if DMA1_2_QUEUE
  8. dma_queue_t g_dma1_2_queue;
  9. #endif
  10. #if DMA1_3_QUEUE
  11. dma_queue_t g_dma1_3_queue;
  12. #endif
  13. #if DMA1_4_QUEUE
  14. dma_queue_t g_dma1_4_queue;
  15. #endif
  16. #if DMA1_5_QUEUE
  17. dma_queue_t g_dma1_5_queue;
  18. #endif
  19. #if DMA1_6_QUEUE
  20. dma_queue_t g_dma1_6_queue;
  21. #endif
  22. #if DMA1_7_QUEUE
  23. dma_queue_t g_dma1_7_queue;
  24. #endif

  25. // 开始传输。
  26. static void _start_transfer(dma_queue_p dma_queue)
  27. {
  28.         uint32_t tmpreg;
  29.         uint32_t addr = 0;
  30.         dma_ctrl_p pdc;
  31.         DMA_Channel_TypeDef *channel = dma_queue->channel;
  32.        
  33.         pdc = &dma_queue->dma_ctrl[dma_queue->dma_ctrl_current];
  34.         tmpreg = channel->CCR & CCR_CLEAR_Mask;
  35.        
  36.         // 设置状态
  37.         dma_queue->dma_ctrl_inprogress = 1;
  38.        
  39.         // 调用回调
  40.         dma_queue->on_start(dma_queue, dma_queue->dma_ctrl_current);
  41.        
  42.         // 分析位数
  43.         if((pdc->flags & DCF_BITS_FLAGS) == DCF_BITS_8)
  44.         {
  45.                 tmpreg |= DMA_MemoryDataSize_Byte | DMA_PeripheralDataSize_Byte;
  46.         }
  47.        
  48.         if((pdc->flags & DCF_BITS_FLAGS) == DCF_BITS_16)
  49.         {
  50.                 tmpreg |= DMA_MemoryDataSize_HalfWord | DMA_PeripheralDataSize_HalfWord;
  51.         }
  52.        
  53.         if((pdc->flags & DCF_BITS_FLAGS) == DCF_BITS_32)
  54.         {
  55.                 tmpreg |= DMA_MemoryDataSize_Word | DMA_PeripheralDataSize_Word;
  56.         }
  57.        
  58.         // 分析指针行为
  59.         if((pdc->flags & DCF_PTR_FLAGS) == DCF_PTR_INC)
  60.         {
  61.                 tmpreg |= DMA_MemoryInc_Enable;
  62.         }
  63.        
  64.         if((pdc->flags & DCF_PTR_FLAGS) == DCF_PTR_STAY)
  65.         {
  66.                 tmpreg |= DMA_MemoryInc_Disable;
  67.         }
  68.        
  69.         // 数据是指针
  70.         if((pdc->flags & DCF_DATA_FLAGS) == DCF_DATA_PTR)
  71.         {
  72.                 addr = pdc->data;
  73.         }
  74.        
  75.         // 数据是数据
  76.         if((pdc->flags & DCF_DATA_FLAGS) == DCF_DATA_INLINED)
  77.         {
  78.                 addr = (uint32_t)&(pdc->data); // 直到传输结束前,这个地址上的数据都不会变
  79.         }
  80.        
  81.         // 收发行为
  82.         if((pdc->flags & DCF_DIR_FLAGS) == DCF_DIR_TX)
  83.         {
  84.                 tmpreg |= DMA_DIR_PeripheralDST; // 内存到外设
  85.         }
  86.        
  87.         if((pdc->flags & DCF_DIR_FLAGS) == DCF_DIR_RX)
  88.         {
  89.                 tmpreg |= DMA_DIR_PeripheralSRC; // 外设到内存
  90.         }
  91.        
  92.         // 其它控制位
  93.         tmpreg |= dma_queue->flags;
  94.        
  95.         channel->CCR = tmpreg; // 设置DMA通道寄存器(并且暂停传输,如果旧的传输在进行的话)
  96.         channel->CNDTR = pdc->count; // 个数
  97.         channel->CPAR = (uint32_t)dma_queue->periph_addr; // 外设地址
  98.         channel->CMAR = addr; // 数据地址
  99.        
  100.         channel->CCR |= DMA_CCR1_EN; // 开启传输
  101. }

  102. // 中断回调
  103. static void _on_irq(dma_queue_p dma_queue, const int dma_controller, const int channel_num)
  104. {
  105.         dma_ctrl_p pdc;
  106.         DMA_TypeDef *dma;
  107.         uint32_t TC_Flag;
  108.        
  109.         //if(dma_controller != dma_queue->dma_controller || channel_num != dma_queue->channel_num)
  110.         //        return;
  111.        
  112.         if(dma_controller == 1)
  113.         {
  114.                 dma = DMA1;
  115.                 TC_Flag = 0x00000000;
  116.         }
  117.         else if(dma_controller == 2)
  118.         {
  119.                 dma = DMA2;
  120.                 TC_Flag = 0x10000000;
  121.         }
  122.         //else
  123.         //{
  124.         //        return;
  125.         //}
  126.        
  127.         TC_Flag |= DMA_IT_TC << ((channel_num - 1) * 4);
  128.        
  129.         // 判断是否自己对应的中断
  130.         if((dma->ISR & TC_Flag) == TC_Flag)
  131.         {
  132.                 // 停止传输
  133.                 dma_queue->channel->CCR &= (uint16_t)~DMA_CCR1_EN;
  134.                
  135.                 // 清除中断位
  136.                 dma->IFCR = TC_Flag;
  137.         }
  138.         else
  139.         {
  140.                 return;
  141.         }
  142.        
  143.         // 这是DMA传输完成时的中断,因此我们要开始下一轮传输
  144.         __disable_irq();
  145.         pdc = &dma_queue->dma_ctrl[dma_queue->dma_ctrl_current];
  146.        
  147.         if(pdc->repeat_count) // 如果它有重复次数,那就尽量以最快速度重复这次传输
  148.         {
  149.                 pdc->repeat_count --;
  150.                 dma_queue->channel->CNDTR = pdc->count; // 必须设置计数器
  151.                 dma_queue->channel->CCR |= DMA_CCR1_EN; // 别的寄存器不用动。开始传输
  152.         }
  153.         else
  154.         {
  155.                 dma_queue->dma_ctrl_inprogress = 0; // 完成了传输
  156.                 dma_queue->on_finish(dma_queue, dma_queue->dma_ctrl_current); // 完成传输的回调
  157.                 dma_queue->dma_ctrl_count --; // 队列请求计数-1
  158.                 dma_queue->dma_ctrl_current = (dma_queue->dma_ctrl_current + 1) % DMA_QUEUE_LENGTH; // 下一个请求
  159.                
  160.                 if(dma_queue->dma_ctrl_count) // 依然有剩余请求,则开始传输
  161.                         _start_transfer(dma_queue);
  162.         }
  163.         __enable_irq();
  164. }

  165. // 初始化一个DMA队列对象,返回上述的全局变量中的一个(根据前两个参数匹配)
  166. dma_queue_p dma_queue_init
  167. (
  168.         int dma_controller, // DMA控制器号,1或2
  169.         int channel_num, // DMA通道号,1,2,3,4,5,6,7
  170.         volatile void *periph_addr, // 要操作的外设的地址
  171.         on_start_f on_start, // 开始传输时被调用的回调函数
  172.         on_finish_f on_finish, // 传输结束后被调用的回调函数
  173.         uint32_t DMA_Priority // DMA传输优先级
  174. )
  175. {
  176.         dma_queue_p dma_queue;
  177.         if(dma_controller == 1)
  178.         {
  179.                 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
  180.                 switch(channel_num)
  181.                 {
  182. #if DMA1_1_QUEUE
  183.                 case 1:
  184.                         dma_queue = &g_dma1_1_queue;
  185.                         dma_queue->channel = DMA1_Channel1;
  186.                         NVIC_EnableIRQ(DMA1_Channel1_IRQn);
  187.                         break;
  188. #endif
  189. #if DMA1_2_QUEUE
  190.                 case 2:
  191.                         dma_queue = &g_dma1_2_queue;
  192.                         dma_queue->channel = DMA1_Channel2;
  193.                         NVIC_EnableIRQ(DMA1_Channel2_IRQn);
  194.                         break;
  195. #endif
  196. #if DMA1_3_QUEUE
  197.                 case 3:
  198.                         dma_queue = &g_dma1_3_queue;
  199.                         dma_queue->channel = DMA1_Channel3;
  200.                         NVIC_EnableIRQ(DMA1_Channel3_IRQn);
  201.                         break;
  202. #endif
  203. #if DMA1_4_QUEUE
  204.                 case 4:
  205.                         dma_queue = &g_dma1_4_queue;
  206.                         dma_queue->channel = DMA1_Channel4;
  207.                         NVIC_EnableIRQ(DMA1_Channel4_IRQn);
  208.                         break;
  209. #endif
  210. #if DMA1_5_QUEUE
  211.                 case 5:
  212.                         dma_queue = &g_dma1_5_queue;
  213.                         dma_queue->channel = DMA1_Channel5;
  214.                         NVIC_EnableIRQ(DMA1_Channel5_IRQn);
  215.                         break;
  216. #endif
  217. #if DMA1_6_QUEUE
  218.                 case 6:
  219.                         dma_queue = &g_dma1_6_queue;
  220.                         dma_queue->channel = DMA1_Channel6;
  221.                         NVIC_EnableIRQ(DMA1_Channel6_IRQn);
  222.                         break;
  223. #endif
  224. #if DMA1_7_QUEUE
  225.                 case 7:
  226.                         dma_queue = &g_dma1_7_queue;
  227.                         dma_queue->channel = DMA1_Channel7;
  228.                         NVIC_EnableIRQ(DMA1_Channel7_IRQn);
  229.                         break;
  230. #endif
  231.                 default:
  232.                         DMA_QUEUE_BKPT();
  233.                         return 0;
  234.                 }
  235.         }
  236.         else
  237.         {
  238.                 // 暂时懒得处理DMA2
  239.                 DMA_QUEUE_BKPT();
  240.                 return 0;
  241.         }
  242.        
  243.         dma_queue->dma_controller = dma_controller;
  244.         dma_queue->channel_num = channel_num;
  245.         dma_queue->periph_addr = periph_addr;
  246.         dma_queue->flags = DMA_Priority | // 优先级是参数
  247.                 // 下面的才是宏
  248.                 DMA_Mode_Normal | DMA_PeripheralInc_Disable | DMA_M2M_Disable | DMA_IT_TC;
  249.         dma_queue->on_start = on_start;
  250.         dma_queue->on_finish = on_finish;
  251.         dma_queue->dma_ctrl_current = 0;
  252.         dma_queue->dma_ctrl_count = 0;
  253.         dma_queue->dma_ctrl_inprogress = 0;
  254.         return dma_queue;
  255. }

  256. // 压入一个DMA传输请求到队列里,返回它在队列中的位置
  257. uint32_t dma_queue_push
  258. (
  259.         dma_queue_p dma_queue, // DMA队列对象
  260.         data_t data, // 数据,或者数据的地址
  261.         uint16_t count, // 传输的数量
  262.         uint8_t repeat_count, // 重复的次数
  263.         uint8_t flags // 控制属性
  264. )
  265. {
  266.         uint32_t cur;
  267.        
  268.         while(1)
  269.         {
  270.                 __disable_irq();
  271.                 // 防止队列溢出
  272.                 if(dma_queue->dma_ctrl_count >= DMA_QUEUE_LENGTH)
  273.                 {
  274.                         // 如果队列是满的,但并没有传输的话,开启传输
  275.                         if(!dma_queue->dma_ctrl_inprogress)
  276.                                 _start_transfer(dma_queue);
  277.                 }
  278.                 else
  279.                 {
  280.                         // 插入传输请求到队列里
  281.                         cur = (dma_queue->dma_ctrl_current + dma_queue->dma_ctrl_count) % DMA_QUEUE_LENGTH;
  282.                         dma_queue->dma_ctrl[cur].data = data;
  283.                         dma_queue->dma_ctrl[cur].count = count;
  284.                         dma_queue->dma_ctrl[cur].repeat_count = repeat_count;
  285.                         dma_queue->dma_ctrl[cur].flags = flags;
  286.                         dma_queue->dma_ctrl_count ++;
  287.                        
  288.                         // 如果之前队列是空的,现在插入了传输请求,那就开启传输
  289.                         if(dma_queue->dma_ctrl_count == 1)
  290.                                 _start_transfer(dma_queue);
  291.                         __enable_irq();
  292.                         break; // 并且退出
  293.                 }
  294.                 __enable_irq();
  295.                
  296.                 // 这个循环是为了等待队列空出空位
  297.         }
  298.        
  299.         return cur;
  300. }

  301. // 等待特定的传输请求完成
  302. void dma_queue_wait_transfer(dma_queue_p dma_queue, uint32_t index)
  303. {
  304.         index %= DMA_QUEUE_LENGTH;
  305.         while(dma_queue->dma_ctrl_current != index)
  306.         {
  307.                 if(!dma_queue->dma_ctrl_inprogress) break;
  308.         }
  309. }

  310. // 等待全部传输请求完成
  311. void dma_queue_wait(dma_queue_p dma_queue)
  312. {
  313.         while(dma_queue->dma_ctrl_count);
  314. }

  315. // 用宏来生成函数名
  316. #define DMAx_Channely_IRQHandler(x,y) \
  317.         void DMA ## x ## _Channel ## y ## _IRQHandler(void) \
  318.         { \
  319.                 _on_irq(&g_dma ## x ## _ ## y ## _queue, x, y); \
  320.         }

  321. #if DMA1_1_QUEUE
  322. DMAx_Channely_IRQHandler(1,1)
  323. #endif

  324. #if DMA1_2_QUEUE
  325. DMAx_Channely_IRQHandler(1,2)
  326. #endif

  327. #if DMA1_3_QUEUE
  328. DMAx_Channely_IRQHandler(1,3)
  329. #endif

  330. #if DMA1_4_QUEUE
  331. DMAx_Channely_IRQHandler(1,4)
  332. #endif

  333. #if DMA1_5_QUEUE
  334. DMAx_Channely_IRQHandler(1,5)
  335. #endif

  336. #if DMA1_6_QUEUE
  337. DMAx_Channely_IRQHandler(1,6)
  338. #endif

  339. #if DMA1_7_QUEUE
  340. DMAx_Channely_IRQHandler(1,7)
  341. #endif
复制代码
其实刚开始写的时候并没有什么合适的方法调试。除了走ST-Util跑单步以外(遇到delay_ms的时候蛋疼死),我并没有示波器(我正打算自己做一个呢),SWO调试貌似需要拆开我的ST-LINK然后飞个线。

在PC上模拟运行的话,并不靠谱。单片机情况本来就比PC复杂。其实最简单(简陋)的调试方法就是配合断点+LED灯,它板子上有个LED,控制它的亮灭来判断代码的执行状况。

我其实写了两个DMA的库,接口一样。用来调试屏幕交互的部分。只不过另一个是“干等”系列。使用干等可以保证屏幕交互这块儿的协议没有问题,但队列方式则需要考虑数据的位置和保留方式等,依然需要单独测试。

单片机控制LCD绘图的方式很像远古时代8086处理器控制显示卡进行光栅操作的绘图。你向ILI9341传输的东西,可以是命令,也可以是数据。传输命令的过程相当于写它的寄存器,然后传输数据则相当于给上一个命令提供参数。绘图是怎么完成的呢?核心就两个命令:设置寻址区域,写显存。

ILI9341的像素格式是RGB各6bit的18bit颜色,但你可以让它进入“16bit颜色传输模式”,此时你就可以传输RGB565这种16bit格式的颜色过去了。它会把你传入的16bit颜色数据扩成18bit颜色数据,然后再显示。

它的分辨率是240x320(也说320x240,看方向。默认是竖着的方向),总共有76800个像素——光是像素的个数,就超过了STM32F103
C8T6
的内存字节数(20480字节)。所以在单片机的内存里存储屏幕图像的做法并不现实,因为你存不下。

所以绘图都是靠的直接写屏。也就是设置屏幕的寻址区域,然后输出数据到显存里。就像当年x86电脑的内存也很小的时候,在Win
do
ws上通过调用GDI的绘图函数来绘制窗体。而不是像现在这样可以随便加载jpg、png、bmp、
gif
等各种五花八门的图像存储格式到内存里(jpg、png、
gif
等这些格式最终都要解压成bmp才能使用),然后用各种操作像素的方式来绘图。

理解了这个,你就更容易能理解GDI的设计初衷了。
  1. #ifndef        _ILI9341_LCD_
  2. #define _ILI9341_LCD_

  3. #include<inttypes.h>
  4. #include"config.h"

  5. typedef enum lcd_rgb_format_enum
  6. {
  7.         lcdf_rgb565 = 16,
  8.         lcdf_rgb666 = 18
  9. }lcd_rgb_format_t, *lcd_rgb_format_p;

  10. // 屏幕分辨率
  11. extern int g_lcd_width;
  12. extern int g_lcd_height;
  13. extern const uint32_t g_lcd_pixels;
  14. extern lcd_rgb_format_t g_lcd_rgb_format; // 传输的颜色格式

  15. #define RGB565(r,g,b) (((((uint32_t)(b) >> 3) & 0x001F) | \
  16.                                             (((uint32_t)(g) << 3) & 0x07E0) | \
  17.                                             (((uint32_t)(r) << 8) & 0xf800)) & 0x0FFFF)

  18. #define RGB666(r,g,b) ((((uint32_t)(r) <<  0) & 0x0000FC) | \
  19.                                            (((uint32_t)(g) <<  8) & 0x00FC00) | \
  20.                                            (((uint32_t)(b) << 16) & 0xFC0000))
  21.                                           
  22. #define RGB(r,g,b) (g_lcd_rgb_format == lcdf_rgb666 ? RGB666(r, g, b) : RGB565(r, g, b))

  23. // 初始化、重置
  24. void lcd_init();
  25. void lcd_reset();

  26. // 设置方向
  27. void lcd_set_landscape(const int mirror);
  28. void lcd_set_portrait(const int mirror);

  29. // 设置传输的颜色格式
  30. void lcd_set_rgb16();
  31. void lcd_set_rgb18();

  32. // 设置绘图区域
  33. void lcd_set_draw_area(const int x1, const int y1, const int x2, const int y2);

  34. // 从绘图区域里面取出像素数据
  35. void lcd_get_pixels(void *pixels, const uint32_t count);

  36. // 写入像素数据到绘图区域里
  37. void lcd_put_pixels(const void *pixels, const uint32_t count);

  38. // 写入纯色到绘图区域里
  39. void lcd_put_pure_color(const uint32_t color, const uint32_t count);

  40. // 用一个颜色填充全屏
  41. void lcd_fill_screen(const uint32_t color);

  42. // 填充区域(根据左、上、右、下确定范围)
  43. void lcd_fill_area(int x1, int y1, int x2, int y2, const uint32_t color);

  44. // 填充矩形(根据左、上、宽、高确定范围)
  45. void lcd_fill_rect(int x, int y, int w, int h, const uint32_t color);

  46. // 等待绘图完成
  47. void lcd_flush();

  48. #endif
复制代码
这是最基本的接口。在不考虑其它功能比如休眠模式(实际应用是必须考虑的,因为显示屏很耗电)、伽玛度调节、亮度对比度、关屏序列以外,与像素操作关系最密切的就是上面的函数了。

其它的任何绘图方式,比如画线、画圆、显示文字、画各种几何图形等,都是用上面这些函数实现的。

底层上,这些函数就是靠调用DMA把命令和命令参数都传输过去。当你实现了自己的画线的函数以后,假设你要画一条斜线,它其实是靠画多个横线或者竖线来完成的。而画横线和竖线则是靠填充区域完成的。

tri.png
bigtri.png

想象一下,如果你要画一条斜线,你需要一边计算每个小的直线的线段位置和长度,一边把绘图的请求发给屏幕。这个过程如果能并行处理的话,你可以在很短的时间里就画好一条直线。
  1. #include"lcd.h"
  2. #include"delay.h"
  3. #include"debug.h"
  4. #include"dma_queue.h"

  5. #include<stm32f10x_dma.h>
  6. #include<stm32f10x_spi.h>
  7. #include<system_stm32f10x.h>

  8. /* Level 1 Commands */
  9. #define LCD_SWRESET             0x01   /* Software Reset */
  10. #define LCD_READ_DISPLAY_ID     0x04   /* Read display identification information */
  11. #define LCD_RDDST               0x09   /* Read Display Status */
  12. #define LCD_RDDPM               0x0A   /* Read Display Power Mode */
  13. #define LCD_RDDMADCTL           0x0B   /* Read Display MADCTL */
  14. #define LCD_RDDCOLMOD           0x0C   /* Read Display Pixel Format */
  15. #define LCD_RDDIM               0x0D   /* Read Display Image Format */
  16. #define LCD_RDDSM               0x0E   /* Read Display Signal Mode */
  17. #define LCD_RDDSDR              0x0F   /* Read Display Self-Diagnostic Result */
  18. #define LCD_SPLIN               0x10   /* Enter Sleep Mode */
  19. #define LCD_SLEEP_OUT           0x11   /* Sleep out register */
  20. #define LCD_PTLON               0x12   /* Partial Mode ON */
  21. #define LCD_NORMAL_MODE_ON      0x13   /* Normal Display Mode ON */
  22. #define LCD_DINVOFF             0x20   /* Display Inversion OFF */
  23. #define LCD_DINVON              0x21   /* Display Inversion ON */
  24. #define LCD_GAMMA               0x26   /* Gamma register */
  25. #define LCD_DISPLAY_OFF         0x28   /* Display off register */
  26. #define LCD_DISPLAY_ON          0x29   /* Display on register */
  27. #define LCD_COLUMN_ADDR         0x2A   /* Colomn address register */
  28. #define LCD_PAGE_ADDR           0x2B   /* Page address register */
  29. #define LCD_GRAM                0x2C   /* GRAM register */
  30. #define LCD_RGBSET              0x2D   /* Color SET */
  31. #define LCD_RAMRD               0x2E   /* Memory Read */
  32. #define LCD_PLTAR               0x30   /* Partial Area */
  33. #define LCD_VSCRDEF             0x33   /* Vertical Scrolling Definition */
  34. #define LCD_TEOFF               0x34   /* Tearing Effect Line OFF */
  35. #define LCD_TEON                0x35   /* Tearing Effect Line ON */
  36. #define LCD_MAC                 0x36   /* Memory Access Control register*/
  37. #define LCD_VSCRSADD            0x37   /* Vertical Scrolling Start Address */
  38. #define LCD_IDMOFF              0x38   /* Idle Mode OFF */
  39. #define LCD_IDMON               0x39   /* Idle Mode ON */
  40. #define LCD_PIXEL_FORMAT        0x3A   /* Pixel Format register */
  41. #define LCD_WRITE_MEM_CONTINUE  0x3C   /* Write Memory Continue */
  42. #define LCD_READ_MEM_CONTINUE   0x3E   /* Read Memory Continue */
  43. #define LCD_SET_TEAR_SCANLINE   0x44   /* Set Tear Scanline */
  44. #define LCD_GET_SCANLINE        0x45   /* Get Scanline */
  45. #define LCD_WDB                 0x51   /* Write Brightness Display register */
  46. #define LCD_RDDISBV             0x52   /* Read Display Brightness */
  47. #define LCD_WCD                 0x53   /* Write Control Display register*/
  48. #define LCD_RDCTRLD             0x54   /* Read CTRL Display */
  49. #define LCD_WRCABC              0x55   /* Write Content Adaptive Brightness Control */
  50. #define LCD_RDCABC              0x56   /* Read Content Adaptive Brightness Control */
  51. #define LCD_WRITE_CABC          0x5E   /* Write CABC Minimum Brightness */
  52. #define LCD_READ_CABC           0x5F   /* Read CABC Minimum Brightness */
  53. #define LCD_READ_ID1            0xDA   /* Read ID1 */
  54. #define LCD_READ_ID2            0xDB   /* Read ID2 */
  55. #define LCD_READ_ID3            0xDC   /* Read ID3 */

  56. /* Level 2 Commands */
  57. #define LCD_RGB_INTERFACE       0xB0   /* RGB Interface Signal Control */
  58. #define LCD_FRMCTR1             0xB1   /* Frame Rate Control (In Normal Mode) */
  59. #define LCD_FRMCTR2             0xB2   /* Frame Rate Control (In Idle Mode) */
  60. #define LCD_FRMCTR3             0xB3   /* Frame Rate Control (In Partial Mode) */
  61. #define LCD_INVTR               0xB4   /* Display Inversion Control */
  62. #define LCD_BPC                 0xB5   /* Blanking Porch Control register */
  63. #define LCD_DFC                 0xB6   /* Display Function Control register */
  64. #define LCD_ETMOD               0xB7   /* Entry Mode Set */
  65. #define LCD_BACKLIGHT1          0xB8   /* Backlight Control 1 */
  66. #define LCD_BACKLIGHT2          0xB9   /* Backlight Control 2 */
  67. #define LCD_BACKLIGHT3          0xBA   /* Backlight Control 3 */
  68. #define LCD_BACKLIGHT4          0xBB   /* Backlight Control 4 */
  69. #define LCD_BACKLIGHT5          0xBC   /* Backlight Control 5 */
  70. #define LCD_BACKLIGHT7          0xBE   /* Backlight Control 7 */
  71. #define LCD_BACKLIGHT8          0xBF   /* Backlight Control 8 */
  72. #define LCD_POWER1              0xC0   /* Power Control 1 register */
  73. #define LCD_POWER2              0xC1   /* Power Control 2 register */
  74. #define LCD_VCOM1               0xC5   /* VCOM Control 1 register */
  75. #define LCD_VCOM2               0xC7   /* VCOM Control 2 register */
  76. #define LCD_NVMWR               0xD0   /* NV Memory Write */
  77. #define LCD_NVMPKEY             0xD1   /* NV Memory Protection Key */
  78. #define LCD_RDNVM               0xD2   /* NV Memory Status Read */
  79. #define LCD_READ_ID4            0xD3   /* Read ID4 */
  80. #define LCD_PGAMMA              0xE0   /* Positive Gamma Correction register */
  81. #define LCD_NGAMMA              0xE1   /* Negative Gamma Correction register */
  82. #define LCD_DGAMCTRL1           0xE2   /* Digital Gamma Control 1 */
  83. #define LCD_DGAMCTRL2           0xE3   /* Digital Gamma Control 2 */
  84. #define LCD_INTERFACE           0xF6   /* Interface control register */

  85. /* Extend register commands */
  86. #define LCD_POWERA              0xCB   /* Power control A register */
  87. #define LCD_POWERB              0xCF   /* Power control B register */
  88. #define LCD_DTCA                0xE8   /* Driver timing control A */
  89. #define LCD_DTCB                0xEA   /* Driver timing control B */
  90. #define LCD_POWER_SEQ           0xED   /* Power on sequence register */
  91. #define LCD_3GAMMA_EN           0xF2   /* 3 Gamma enable register */
  92. #define LCD_PRC                 0xF7   /* Pump ratio control register */

  93. #define ORIENTATION_PORTRAIT 0x48
  94. #define ORIENTATION_LANDSCAPE 0x28
  95. #define ORIENTATION_PORTRAIT_MIRROR 0x88
  96. #define ORIENTATION_LANDSCAPE_MIRROR 0xE8

  97. #define LCD_PIXEL_WIDTH                240
  98. #define LCD_PIXEL_HEIGHT        320
  99. #define LCD_PIXEL_COUNT                LCD_PIXEL_WIDTH * LCD_PIXEL_HEIGHT

  100. #define MAKE_16BIT(l,h) ((uint16_t)(l) | ((uint16_t)(h) << 8))
  101. #define MAKE_PARAM(l,h) (((uint32_t)(l) & 0x0000FFFF) | ((uint32_t)(h) << 16))

  102. // 屏幕分辨率
  103. int g_lcd_width;
  104. int g_lcd_height;
  105. const uint32_t g_lcd_pixels = LCD_PIXEL_COUNT;
  106. lcd_rgb_format_t g_lcd_rgb_format;

  107. // 初始化命令
  108. static const uint8_t _init_commands[] =
  109. {
  110.         // Power control A
  111.         6, LCD_POWERA, 0x39, 0x2C, 0x00, 0x34, 0x02,
  112.         // Power control B
  113.         4, LCD_POWERB, 0x00, 0xC1, 0x30,
  114.         // Driver timing control A
  115.         4, LCD_DTCA, 0x85, 0x00, 0x78,
  116.         // Driver timing control B
  117.         3, LCD_DTCB, 0x00, 0x00,
  118.         // Power on sequence control
  119.         5, LCD_POWER_SEQ, 0x64, 0x03, 0x12, 0x81,
  120.         // Pump ratio control
  121.         2, LCD_PRC, 0x20,
  122.         // Power control 1
  123.         2, LCD_POWER1, 0x23,
  124.         // Power control 2
  125.         2, LCD_POWER2, 0x10,
  126.         // VCOM control 1
  127.         3, LCD_VCOM1, 0x3E, 0x28,
  128.         // VCOM cotnrol 2
  129.         2, LCD_VCOM2, 0x86,
  130.         // Memory access control
  131.         2, LCD_MAC, 0x48,
  132.         // Pixel format set
  133.         2, LCD_PIXEL_FORMAT, 0x55,
  134.         // Frame rate control
  135.         3, LCD_FRMCTR1, 0x00, 0x18,
  136.         // Display function control
  137.         4, LCD_DFC, 0x08, 0x82, 0x27,
  138.         // 3Gamma function disable
  139.         2, LCD_3GAMMA_EN, 0x00,
  140.         // Gamma curve selected
  141.         2, LCD_GAMMA, 0x01,
  142.         // Set positive gamma
  143.         16, LCD_PGAMMA, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00,
  144.         16, LCD_NGAMMA, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F,
  145.         0
  146. };

  147. // 开始传输的回调
  148. static void _on_start(dma_queue_p pq, uint32_t index)
  149. {
  150.         TFT_GPIO->BRR = TFT_CS_PIN;
  151.         if((pq->dma_ctrl[index].flags & DCF_CUSTOM_FLAG) == DCF_CUSTOM_FLAG)
  152.         {
  153.                 TFT_GPIO->BSRR = TFT_DC_PIN;
  154.         }
  155.         if((pq->dma_ctrl[index].flags & DCF_BITS_FLAGS) == DCF_BITS_16)
  156.         {
  157.                 SPI_MASTER->CR1 &= ~SPI_CR1_SPE; // DISABLE SPI
  158.                 SPI_MASTER->CR1 |= SPI_CR1_DFF;  // SPI 16
  159.                 SPI_MASTER->CR1 |= SPI_CR1_SPE;  // ENABLE SPI
  160.         }
  161.         else
  162.         {
  163.                 SPI_MASTER->CR1 &= ~SPI_CR1_SPE; // DISABLE SPI
  164.                 SPI_MASTER->CR1 &= ~SPI_CR1_DFF; // SPI 8
  165.                 SPI_MASTER->CR1 |= SPI_CR1_SPE;  // ENABLE SPI
  166.         }
  167. #if LCD_CMDBUF_LED // 调试:传输时亮灯
  168.         LCD_CMDBUF_LED_GPIO->BRR = LCD_CMDBUF_LED_PIN;
  169. #endif
  170. }

  171. // 传输结束的回调
  172. static void _on_finish(dma_queue_p pq, uint32_t index)
  173. {
  174.         if((pq->dma_ctrl[index].flags & DCF_CUSTOM_FLAG) == DCF_CUSTOM_FLAG)
  175.         {
  176.                 TFT_GPIO->BRR = TFT_DC_PIN;
  177.         }
  178.         TFT_GPIO->BSRR = TFT_CS_PIN;
  179. #if LCD_CMDBUF_LED // 调试:传输结束时熄灯
  180.         LCD_CMDBUF_LED_GPIO->BSRR = LCD_CMDBUF_LED_PIN;
  181. #endif
  182. }

  183. // 配置SPI与DMA协作
  184. static void _spi_dma_config()
  185. {
  186.     SPI_I2S_DMACmd(SPI_MASTER, SPI_I2S_DMAReq_Tx, ENABLE);
  187.     SPI_I2S_DMACmd(SPI_MASTER, SPI_I2S_DMAReq_Rx, ENABLE);
  188.        
  189.         // 初始化DMA传输队列
  190.         dma_queue_init(1, 2, &(SPI_MASTER->DR), _on_start, _on_finish, DMA_Priority_Medium);
  191.         dma_queue_init(1, 3, &(SPI_MASTER->DR), _on_start, _on_finish, DMA_Priority_Medium);
  192. }

  193. // 配置SPI
  194. static void _spi_config()
  195. {
  196.         SPI_InitTypeDef spi_conf;
  197.        
  198.     RCC_APB2PeriphClockCmd(SPI_MASTER_CLK, ENABLE);
  199.        
  200.     SPI_StructInit(&spi_conf);
  201.     spi_conf.SPI_Mode              = SPI_Mode_Master;
  202.     spi_conf.SPI_NSS               = SPI_NSS_Soft;
  203.     spi_conf.SPI_CPOL              = SPI_CPOL_High;
  204.     spi_conf.SPI_CPHA              = SPI_CPHA_2Edge;
  205.     spi_conf.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
  206.     SPI_Init(SPI_MASTER, &spi_conf);

  207.     SPI_Cmd(SPI_MASTER, ENABLE);
  208. }

  209. // 配置GPIO
  210. static void _spi_gpio_config()
  211. {
  212.         GPIO_InitTypeDef gpio_conf;

  213.     RCC_PCLK2Config(RCC_HCLK_Div2);
  214.     RCC_APB2PeriphClockCmd(SPI_MASTER_GPIO_CLK, ENABLE);
  215.     RCC_APB2PeriphClockCmd(RCC_APB2ENR_AFIOEN, ENABLE);

  216.     // GPIO speed by default
  217.     gpio_conf.GPIO_Speed = GPIO_Speed_50MHz;

  218.     // GPIO for CS/DC/LED/RESET
  219.     gpio_conf.GPIO_Pin  = TFT_CS_PIN | TFT_DC_PIN | TFT_RESET_PIN | TFT_LED_PIN;
  220.     gpio_conf.GPIO_Mode = GPIO_Mode_Out_PP;
  221.     GPIO_Init(GPIOA, &gpio_conf);

  222.     // GPIO for SPI
  223.     gpio_conf.GPIO_Pin  = SPI_MASTER_PIN_SCK | SPI_MASTER_PIN_MOSI;
  224.     gpio_conf.GPIO_Mode = GPIO_Mode_AF_PP;
  225.     GPIO_Init(SPI_MASTER_GPIO, &gpio_conf);

  226.     // GPIO for SPI
  227.     gpio_conf.GPIO_Pin  = SPI_MASTER_PIN_MISO;
  228.     gpio_conf.GPIO_Mode = GPIO_Mode_IPD;
  229.     GPIO_Init(SPI_MASTER_GPIO, &gpio_conf);
  230. }

  231. // 等待SPI传输
  232. // static void _spi_transfer_wait()
  233. // {
  234. //         while(SPI_I2S_GetFlagStatus(SPI_MASTER, SPI_I2S_FLAG_BSY) == SET);
  235. // }

  236. // 等待所有DMA队列完成传输
  237. static void _spi_lcd_flush(void)
  238. {
  239.         dma_queue_wait(&g_dma1_2_queue);
  240.         dma_queue_wait(&g_dma1_3_queue);
  241. }

  242. // 发送命令
  243. static uint32_t _spi_lcd_send_cmd(const uint8_t cmd)
  244. {
  245.         return dma_queue_push(&g_dma1_3_queue, cmd, 1, 0, DCF_BITS_8 | DCF_PTR_INC | DCF_DIR_TX | DCF_DATA_INLINED);
  246. }

  247. ///////////////////////////////////////////////////////////////////////////////
  248. //
  249. // 以下的函数都是发送数据的
  250. //
  251. // 所谓数据,就是传输的时候DC针要设为高电平
  252. // param的意思是“发送的是参数”,数据是4字节以内的
  253. // num是DMA传输的单位数量
  254. // repeat_count是DMA传输的自动重启的次数
  255. //
  256. ///////////////////////////////////////////////////////////////////////////////

  257. // 以8bit为单位发送指针指向的数据
  258. static uint32_t _spi_lcd_send_data8(const void *data, const uint32_t num, const uint8_t repeat_count)
  259. {
  260.         return dma_queue_push(&g_dma1_3_queue, (data_t)data, num, repeat_count,
  261.                 DCF_BITS_8 | DCF_PTR_INC | DCF_DIR_TX | DCF_CUSTOM_FLAG);
  262. }

  263. // 以8bit为单位发送4个字节以内的数据
  264. static uint32_t _spi_lcd_send_param8(const data_t param, const uint32_t num, const uint8_t repeat_count)
  265. {
  266.         return dma_queue_push(&g_dma1_3_queue, param, num, repeat_count,
  267.                 DCF_BITS_8 | DCF_PTR_INC | DCF_DIR_TX | DCF_DATA_INLINED | DCF_CUSTOM_FLAG);
  268. }

  269. // 以16bit为单位发送指针指向的数据
  270. static uint32_t _spi_lcd_send_data16(const void *data, const uint32_t num, const uint8_t repeat_count)
  271. {
  272.         return dma_queue_push(&g_dma1_3_queue, (data_t)data, num, repeat_count,
  273.                 DCF_BITS_16 | DCF_PTR_INC | DCF_DIR_TX | DCF_CUSTOM_FLAG);
  274. }

  275. // 以16bit为单位发送4个字节以内的数据
  276. static uint32_t _spi_lcd_send_param16(const data_t data, const uint32_t num, const uint8_t repeat_count)
  277. {
  278.         return dma_queue_push(&g_dma1_3_queue, data, num, repeat_count,
  279.                 DCF_BITS_16 | DCF_PTR_INC | DCF_DIR_TX | DCF_DATA_INLINED | DCF_CUSTOM_FLAG);
  280. }

  281. // 以8bit为单位发送同一个uint8_t的数据,num次
  282. // static uint32_t _spi_lcd_send_param8_repeat(const data_t param, const uint32_t num, const uint8_t repeat_count)
  283. // {
  284. //         return dma_queue_push(&g_dma1_3_queue, param, num, repeat_count,
  285. //                 DCF_BITS_8 | DCF_PTR_STAY | DCF_DIR_TX | DCF_DATA_INLINED | DCF_CUSTOM_FLAG);
  286. // }

  287. // 以16bit为单位发送同一个uint16_t的数据,num次
  288. static uint32_t _spi_lcd_send_param16_repeat(const data_t data, const uint32_t num, const uint8_t repeat_count)
  289. {
  290.         return dma_queue_push(&g_dma1_3_queue, data, num, repeat_count,
  291.                 DCF_BITS_16 | DCF_PTR_STAY | DCF_DIR_TX | DCF_DATA_INLINED | DCF_CUSTOM_FLAG);
  292. }

  293. // 以8bit为单位,接收数据到一个缓冲区
  294. static uint32_t _spi_lcd_recv_data8(const void *data, const uint32_t num)
  295. {
  296.         return dma_queue_push(&g_dma1_2_queue, (data_t)data, num, 1,
  297.                 DCF_BITS_8 | DCF_PTR_INC | DCF_DIR_RX | DCF_CUSTOM_FLAG);
  298. }

  299. // 以16bit为单位,接收数据到一个缓冲区
  300. static uint32_t _spi_lcd_recv_data16(const void *data, const uint32_t num)
  301. {
  302.         return dma_queue_push(&g_dma1_2_queue, (data_t)data, num, 1,
  303.                 DCF_BITS_16 | DCF_PTR_INC | DCF_DIR_RX | DCF_CUSTOM_FLAG);
  304. }

  305. ///////////////////////////////////////////////////////////////////////////////
  306. //
  307. // 以下的函数都是传输特定格式的颜色数据用的
  308. //
  309. // RGB565的格式是:
  310. //   红色在高位,绿色在中间,蓝色在低位,组成一个uint16_t,红色和蓝色是5bit,
  311. //   绿色是6bit。
  312. //   rrrrrGGGGGGbbbbb
  313. //
  314. // RGB666的格式是:
  315. //   红色在低位,绿色在中间,蓝色在高位,红绿蓝各占一个字节,共3个字节,24位。
  316. //   其中红绿蓝每个通道的低2位被丢弃,传输的时候不起作用。只有高6bit起作用。
  317. //   bbbbbb00gggggg00rrrrrr00
  318. //
  319. ///////////////////////////////////////////////////////////////////////////////

  320. static void _get_pixels_rgb565(void *rgb565, const uint32_t count)
  321. {
  322.         uint32_t last_index;
  323.         _spi_lcd_send_cmd(LCD_RAMRD);
  324.         // 接收数据前,需要先发个0xFF
  325.         last_index = _spi_lcd_send_param8(0xFF, 1, 0);
  326.         dma_queue_wait_transfer(&g_dma1_3_queue, last_index); // 要等这个0xFF发出去,再接收
  327.         _spi_lcd_recv_data16(rgb565, count);
  328. }

  329. static void _get_pixels_rgb666(void *rgb666, const uint32_t count)
  330. {
  331.         uint32_t last_index;
  332.         _spi_lcd_send_cmd(LCD_RAMRD);
  333.         // 接收数据前,需要先发个0xFF
  334.         last_index = _spi_lcd_send_param8(0xFF, 1, 0);
  335.         dma_queue_wait_transfer(&g_dma1_3_queue, last_index); // 要等这个0xFF发出去,再接收
  336.         _spi_lcd_recv_data8(rgb666, count * 3);
  337. }

  338. static void _put_pixels_rgb565(const void *rgb565, const uint32_t count)
  339. {
  340.         _spi_lcd_send_cmd(LCD_GRAM);
  341.         _spi_lcd_send_data16(rgb565, count, 0);
  342. }

  343. static void _put_pixels_rgb666(const void *rgb666, const uint32_t count)
  344. {
  345.         _spi_lcd_send_cmd(LCD_GRAM);
  346.         _spi_lcd_send_data8(rgb666, count * 3, 0);
  347. }

  348. static void _put_pure_color_rgb565(const uint32_t rgb565, const uint32_t count)
  349. {
  350.         uint32_t rest = count;

  351.         _spi_lcd_send_cmd(LCD_GRAM);
  352.         // 一次不能发送超过65535个数据
  353.         if(rest > 0x0FFFF)
  354.         {
  355.                 _spi_lcd_send_param16_repeat(rgb565, 0x0FFFF, rest / 0x0FFFF - 1); // 重复发送
  356.                 rest %= 0x0FFFF;
  357.         }
  358.         _spi_lcd_send_param16_repeat(rgb565, rest, 0); // 发送剩下的
  359. }

  360. static void _put_pure_color_rgb666(const uint32_t rgb666, const uint32_t count)
  361. {
  362.         // 发送3个字节编码的纯色,这里其实非常尴尬。
  363.         // DMA无法以3字节为单位发送数据
  364.         // 在栈上建立缓冲区来实现功能
  365.        
  366.         const uint32_t buffer_pixels = 64; // 缓冲区大小,像素数
  367.         uint8_t color_buf[buffer_pixels * 3];
  368.         int i;
  369.         uint32_t last_index;
  370.         uint32_t rest = count;
  371.        
  372.         // 填充纯色到缓冲区里
  373.         for(i = 0; i < buffer_pixels * 3 && i < count * 3; i++)
  374.         {
  375.                 color_buf[i] = rgb666 >> ((i % 3) * 8);
  376.         }
  377.        
  378.         last_index = _spi_lcd_send_cmd(LCD_GRAM);
  379.        
  380.         // 数据量大,循环重复
  381.         while(rest > buffer_pixels * 256)
  382.         {
  383.                 last_index = _spi_lcd_send_data8(color_buf, buffer_pixels * 3, 255);
  384.                 rest -= buffer_pixels * 256;
  385.         }
  386.         // 发送剩下的循环重复的部分
  387.         if(rest > buffer_pixels)
  388.         {
  389.                 last_index = _spi_lcd_send_data8(color_buf, buffer_pixels * 3, rest / buffer_pixels - 1);
  390.                 rest %= buffer_pixels;
  391.         }
  392.         // 发送最后剩下的
  393.         if(rest)
  394.                 last_index = _spi_lcd_send_data8(color_buf, rest * 3, 0);
  395.        
  396.         // 数据在栈上,所以只能等传输完了才能返回。
  397.         dma_queue_wait_transfer(&g_dma1_3_queue, last_index);
  398.        
  399.         /*
  400.         // 另一个实现,直接重复发送指定个数的3字节,非常慢。
  401.         // 每发3个字节会产生一次中断。
  402.         uint32_t rest = count;

  403.         _spi_lcd_send_cmd(LCD_GRAM);
  404.         while(rest > 256)
  405.         {
  406.                 _spi_lcd_send_param8(rgb666, 3, 255);
  407.                 rest -= 256;
  408.         }
  409.         _spi_lcd_send_param8(rgb666, 3, rest - 1);*/
  410.        
  411. }

  412. // 函数指针,用于对外提供不针对格式的接口。
  413. static void(*_get_pixels)(void *pixels, const uint32_t count);
  414. static void(*_put_pixels)(const void *pixels, const uint32_t count);
  415. static void(*_put_pure_color)(const uint32_t color, const uint32_t count);

  416. void lcd_init()
  417. {
  418.         _spi_gpio_config();
  419.         _spi_config();
  420.         _spi_dma_config();
  421.        
  422. #if LCD_CMDBUF_LED // 调试:传输时的灯
  423.         RCC_APB2PeriphClockCmd(LCD_CMDBUF_LED_GPIO_CLK, ENABLE);
  424. #endif
  425.        
  426.         lcd_reset();
  427. }

  428. void lcd_reset()
  429. {
  430.     const uint8_t *ptr = _init_commands;

  431.         TFT_GPIO->BRR = TFT_RESET_PIN;
  432.     delay_ms(10);
  433.         TFT_GPIO->BSRR = TFT_RESET_PIN;
  434.     delay_ms(50);
  435.     _spi_lcd_send_cmd(LCD_SLEEP_OUT);
  436.     delay_ms(150);
  437.     _spi_lcd_send_cmd(LCD_DISPLAY_ON);
  438.        
  439.         // 发送初始化序列
  440.     while(1)
  441.         {
  442.         uint8_t count = *ptr;
  443.         if(!count) break; // 参数字节数
  444.                 ptr ++;
  445.         _spi_lcd_send_cmd(*ptr); // 第一个是命令
  446.                 ptr ++;
  447.         _spi_lcd_send_data8(ptr, count - 1, 0); // 剩下的是参数
  448.         ptr += count - 1;
  449.     }
  450.        
  451.         // 初始化后的传输格式是RGB565
  452.         g_lcd_rgb_format = lcdf_rgb565;
  453.        
  454.         // 设置函数指针
  455.         _get_pixels = _get_pixels_rgb565;
  456.         _put_pixels = _put_pixels_rgb565;
  457.         _put_pure_color = _put_pure_color_rgb565;
  458.        
  459.         // 此处设置 g_lcd_width 和 g_lcd_height的数值
  460.         lcd_set_portrait(0);
  461.        
  462.         // 开启背光
  463.         TFT_GPIO->BSRR = TFT_LED_PIN;
  464. }

  465. // 切换格式为16bit颜色
  466. void lcd_set_rgb16()
  467. {
  468.         _spi_lcd_send_cmd(LCD_PIXEL_FORMAT);
  469.         _spi_lcd_send_param8(0x55, 1, 0);
  470.         g_lcd_rgb_format = lcdf_rgb565;
  471.        
  472.         _get_pixels = _get_pixels_rgb565;
  473.         _put_pixels = _put_pixels_rgb565;
  474.         _put_pure_color = _put_pure_color_rgb565;
  475. }

  476. // 切换格式为18bit颜色
  477. void lcd_set_rgb18()
  478. {
  479.         _spi_lcd_send_cmd(LCD_PIXEL_FORMAT);
  480.         _spi_lcd_send_param8(0x66, 1, 0);
  481.         g_lcd_rgb_format = lcdf_rgb666;
  482.        
  483.         _get_pixels = _get_pixels_rgb666;
  484.         _put_pixels = _put_pixels_rgb666;
  485.         _put_pure_color = _put_pure_color_rgb666;
  486. }

  487. // 设置横向显示
  488. void lcd_set_landscape(const int mirror)
  489. {
  490.         uint8_t cmd = mirror ? ORIENTATION_LANDSCAPE_MIRROR : ORIENTATION_LANDSCAPE;
  491.        
  492.         g_lcd_height = LCD_PIXEL_WIDTH;
  493.         g_lcd_width  = LCD_PIXEL_HEIGHT;
  494.        
  495.     _spi_lcd_send_cmd(LCD_MAC);
  496.     _spi_lcd_send_param8(cmd, 1, 0);
  497. }

  498. // 设置竖向显示
  499. void lcd_set_portrait(const int mirror)
  500. {
  501.         uint8_t cmd = mirror ? ORIENTATION_PORTRAIT_MIRROR : ORIENTATION_PORTRAIT;
  502.        
  503.         g_lcd_height = LCD_PIXEL_HEIGHT;
  504.         g_lcd_width  = LCD_PIXEL_WIDTH;
  505.        
  506.     _spi_lcd_send_cmd(LCD_MAC);
  507.     _spi_lcd_send_param8(cmd, 1, 0);
  508. }

  509. // 设置绘图区域
  510. void lcd_set_draw_area(const int x1, const int y1, const int x2, const int y2)
  511. {
  512.         _spi_lcd_send_cmd(LCD_COLUMN_ADDR);
  513.         _spi_lcd_send_param16(MAKE_PARAM(x1, x2), 2, 0);
  514.        
  515.         _spi_lcd_send_cmd(LCD_PAGE_ADDR);
  516.         _spi_lcd_send_param16(MAKE_PARAM(y1, y2), 2, 0);
  517. }

  518. // 从绘图区域里面取出像素数据
  519. void lcd_get_pixels(void *color, const uint32_t count)
  520. {
  521.         _get_pixels(color, count);
  522. }

  523. // 写入像素数据到绘图区域里
  524. void lcd_put_pixels(const void *color, const uint32_t count)
  525. {
  526.         _put_pixels(color, count);
  527. }

  528. // 写入纯色到绘图区域里
  529. void lcd_put_pure_color(const uint32_t color, const uint32_t count)
  530. {
  531.         _put_pure_color(color, count);
  532. }

  533. // 用一个颜色填充全屏
  534. void lcd_fill_screen(const uint32_t color)
  535. {
  536.         lcd_set_draw_area(0, 0, g_lcd_width - 1, g_lcd_height - 1);
  537.         lcd_put_pure_color(color, g_lcd_pixels);
  538. }

  539. // 填充区域(根据左、上、右、下确定范围)
  540. void lcd_fill_area(int x1, int y1, int x2, int y2, const uint32_t color)
  541. {
  542.         if(x1 > x2)
  543.         {
  544.                 int t = x1;
  545.                 x1 = x2;
  546.                 x2 = t;
  547.         }
  548.         if(y1 > y2)
  549.         {
  550.                 int t = y1;
  551.                 y1 = y2;
  552.                 y2 = t;
  553.         }
  554.         if(x1 >= g_lcd_width || y1 >= g_lcd_height || x2 < 0 || y2 < 0) return;
  555.        
  556.         if(x1 < 0) x1 = 0;
  557.         if(y1 < 0) y1 = 0;
  558.         if(x2 > g_lcd_width - 1) x2 = g_lcd_width - 1;
  559.         if(y2 > g_lcd_height - 1) y2 = g_lcd_height - 1;
  560.        
  561.         lcd_set_draw_area(x1, y1, x2, y2);
  562.         lcd_put_pure_color(color, (x2 - x1 + 1) * (y2 - y1 + 1));
  563. }

  564. // 填充矩形(根据左、上、宽、高确定范围)
  565. void lcd_fill_rect(int x, int y, int w, int h, const uint32_t color)
  566. {
  567.         if(x >= g_lcd_width || y >= g_lcd_height) return;
  568.        
  569.         if(x < 0)
  570.         {
  571.                 w += x;
  572.                 x = 0;
  573.         }
  574.        
  575.         if(y < 0)
  576.         {
  577.                 h += y;
  578.                 y = 0;
  579.         }
  580.        
  581.         if(x + w > g_lcd_width)
  582.         {
  583.                 w = g_lcd_width - x;
  584.         }
  585.        
  586.         if(y + h > g_lcd_height)
  587.         {
  588.                 h = g_lcd_height - y;
  589.         }
  590.        
  591.         if(w <= 0 || h <= 0) return;
  592.        
  593.         lcd_set_draw_area(x, y, x + w - 1, y + h - 1);
  594.         lcd_put_pure_color(color, w * h);
  595. }

  596. // 等待绘图完成
  597. void lcd_flush()
  598. {
  599.         _spi_lcd_flush();
  600. }
复制代码
这是LCD控制部分的源码。经过测试,它可以在屏幕上绘制出图形。

然后这是测试代码。
  1. #include<inttypes.h>
  2. #include<stdlib.h>

  3. #include<stm32f10x.h>
  4. #include<system_stm32f10x.h>

  5. #include"lcd.h"
  6. #include"delay.h"
  7. #include"debug.h"

  8. void main()
  9. {
  10.         SystemInit();
  11.        
  12.         lcd_init();
  13.         // lcd_set_landscape(0);
  14.        
  15.         lcd_fill_screen(RGB565(0, 127, 255));
  16.        
  17.         int i = 0, f = 0;
  18.        
  19.         while(1)
  20.         {
  21.                 int x, y;
  22.                
  23.                 i = f;
  24.                
  25.                 // 画全屏棋盘格
  26.                 for(y = 0; y < g_lcd_height; y += 20)
  27.                 {
  28.                         for(x = 0; x < g_lcd_width; x += 20)
  29.                         {
  30.                                 int r = 0, g = 0, b = 0;
  31.                                 int v = i % 1536;
  32.                                 // 根据i的值生成平滑过渡的颜色(有六个过渡阶段)
  33.                                 if(v < 256)
  34.                                 {
  35.                                         r = 255;
  36.                                         g = 0;
  37.                                         b = 255 - v;
  38.                                 }
  39.                                 else if(v < 512)
  40.                                 {
  41.                                         r = 255;
  42.                                         g = v - 256;
  43.                                         b = 0;
  44.                                 }
  45.                                 else if(v < 768)
  46.                                 {
  47.                                         r = 255 - (v - 512);
  48.                                         g = 255;
  49.                                         b = 0;
  50.                                 }
  51.                                 else if(v < 1024)
  52.                                 {
  53.                                         r = 0;
  54.                                         g = 255;
  55.                                         b = v - 768;
  56.                                 }
  57.                                 else if(v < 1280)
  58.                                 {
  59.                                         r = 0;
  60.                                         g = 255 - (v - 1024);
  61.                                         b = 255;
  62.                                 }
  63.                                 else
  64.                                 {
  65.                                         r = v - 1280;
  66.                                         g = 0;
  67.                                         b = 255;
  68.                                 }
  69.                                
  70.                                 // 奇偶块反色
  71.                                 lcd_fill_rect(x, y, 20, 20, ((x / 20) ^ (y / 20)) & 1 ? ~RGB(r, g, b) : RGB(r, g, b));
  72.                                
  73.                                 i ++;
  74.                         }
  75.                 }
  76.                
  77.                 f += 12;
  78.         }
  79. }
复制代码
效果如下(拍的角度不太好,正面看的效果是很好的)
ili9341_.gif

放大看它的像素点
lcd.jpg

可以用ST-Util调试看到DMA请求队列的变化过程。
st-util.gif

接下来,我应该可以用这个去做个ADC示波器了。
回复

使用道具 举报

0

主题

1

回帖

15

积分

用户组: 初·技术宅

UID
5512
精华
0
威望
2 点
宅币
10 个
贡献
0 次
宅之契约
0 份
在线时间
0 小时
注册时间
2020-1-4
发表于 2020-1-4 14:54:32 | 显示全部楼层
学习一下。
回复

使用道具 举报

QQ|Archiver|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图

GMT+8, 2024-4-25 07:54 , Processed in 0.051025 second(s), 34 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表