- UID
- 1
- 精华
- 积分
- 76361
- 威望
- 点
- 宅币
- 个
- 贡献
- 次
- 宅之契约
- 份
- 最后登录
- 1970-1-1
- 在线时间
- 小时
|
原文网址: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
我买到的这个它是使用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控制器它就开始传输了——你只需要按一下“煮饭”,你的电饭煲就开始煮饭了。等到它完成了传输,它就会产生一个特定的中断信号,中断处理程序就会启动下一个传输请求——电饭煲煮好饭的时候会发出声音通知你,然后你就可以把米饭拿出来,再把煮粥的材料倒进电饭煲里,按下“煮粥”按钮了……
虽然传输的结束和开始依然需要处理器的干预,但传输的过程是不需要干预的了。这样就达到了我的目的——使用一个电饭煲煮各种食物的同时,不影响我干别的事。- #ifndef _DMA_BUFFERED_
- #define _DMA_BUFFERED_
- #include<inttypes.h>
- #include<stm32f10x_dma.h>
- #include"dma_queue_cfg.h"
- #include"delay.h"
- typedef struct dma_ctrl_struct
- {
- volatile data_t data; // 数据,或者数据的地址
- volatile uint16_t count; // 传输的数量
- volatile uint8_t repeat_count; // 重复的次数
- volatile uint8_t flags; // 控制属性
- }dma_ctrl_t, *dma_ctrl_p;
- // DCF开头的宏就是控制属性了
- // 传输单元位数
- #define DCF_BITS_8 0x0000 /* default */
- #define DCF_BITS_16 0x0001
- #define DCF_BITS_32 0x0002
- #define DCF_BITS_FLAGS (DCF_BITS_8 | DCF_BITS_16 | DCF_BITS_32)
- // 指针行为(增大,或者不变)
- #define DCF_PTR_INC 0x0000 /* default */
- #define DCF_PTR_STAY 0x0004
- #define DCF_PTR_FLAGS (DCF_PTR_INC | DCF_PTR_STAY)
- // 数据的存储方式(指针,或者直接装填)
- #define DCF_DATA_PTR 0x0000 /* default */
- #define DCF_DATA_INLINED 0x0008
- #define DCF_DATA_FLAGS (DCF_DATA_PTR | DCF_DATA_INLINED)
- // 收,还是发
- #define DCF_DIR_TX 0x0000 /* default */
- #define DCF_DIR_RX 0x0010
- #define DCF_DIR_FLAGS (DCF_DIR_TX | DCF_DIR_RX)
- // 给调用者预留的Flag,供调用者在on_start和on_finish里面判断。
- #define DCF_CUSTOM_FLAG 0x0080
- typedef struct dma_queue_struct dma_queue_t, *dma_queue_p;
- // 回调函数
- typedef void(*on_start_f)(dma_queue_p pq, uint32_t index);
- typedef void(*on_finish_f)(dma_queue_p pq, uint32_t index);
- // DMA队列对象
- struct dma_queue_struct
- {
- DMA_Channel_TypeDef *channel; // DMA控制器寄存器地址
- volatile void *periph_addr; // 要操作的外设的地址
-
- int dma_controller; // DMA控制器号,1或2
- int channel_num; // DMA通道号,1,2,3,4,5,6,7
- uint32_t flags; // 状态控制
-
- on_start_f on_start; // 开始传输时被调用的回调函数
- on_finish_f on_finish; // 传输结束后被调用的回调函数
-
- volatile uint32_t dma_ctrl_current; // 正在传输的DMA请求
- volatile uint32_t dma_ctrl_count; // 队列里的请求个数
- volatile uint32_t dma_ctrl_inprogress; // 是否正在传输
- dma_ctrl_t dma_ctrl[DMA_QUEUE_LENGTH]; // 队列
- };
- // 按要求定义全局变量
- #if DMA1_1_QUEUE
- extern dma_queue_t g_dma1_1_queue;
- #endif
- #if DMA1_2_QUEUE
- extern dma_queue_t g_dma1_2_queue;
- #endif
- #if DMA1_3_QUEUE
- extern dma_queue_t g_dma1_3_queue;
- #endif
- #if DMA1_4_QUEUE
- extern dma_queue_t g_dma1_4_queue;
- #endif
- #if DMA1_5_QUEUE
- extern dma_queue_t g_dma1_5_queue;
- #endif
- #if DMA1_6_QUEUE
- extern dma_queue_t g_dma1_6_queue;
- #endif
- #if DMA1_7_QUEUE
- extern dma_queue_t g_dma1_7_queue;
- #endif
- // 初始化一个DMA队列对象,返回上述的全局变量中的一个(根据前两个参数匹配)
- dma_queue_p dma_queue_init
- (
- int dma_controller, // DMA控制器号,1或2
- int channel_num, // DMA通道号,1,2,3,4,5,6,7
- volatile void *periph_addr, // 要操作的外设的地址
- on_start_f on_start, // 开始传输时被调用的回调函数
- on_finish_f on_finish, // 传输结束后被调用的回调函数
- uint32_t DMA_Priority // DMA传输优先级
- );
- // 压入一个DMA传输请求到队列里,返回它在队列中的位置
- uint32_t dma_queue_push
- (
- dma_queue_p dma_queue, // DMA队列对象
- data_t data, // 数据,或者数据的地址
- uint16_t count, // 传输的数量
- uint8_t repeat_count, // 重复的次数
- uint8_t flags // 控制属性
- );
- // 等待特定的传输请求完成
- void dma_queue_wait_transfer(dma_queue_p dma_queue, uint32_t index);
- // 等待全部传输请求完成
- void dma_queue_wait(dma_queue_p dma_queue);
- #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_dmax _y _queue;就是这个结构体的实例化变量了。因为中断处理函数的名字是钦定的,所以我要给每一个需要用到的DMA通道都定义一个专属的全局变量。然后每个变量的体积都不小,所以用宏来配置它,只定义用得到的。免得占用RAM过多。
dma_queue_push()用来提交你的DMA传输请求。它的第一个参数,就是传递的上述结构体的地址。说到地址,这里就要提到数据的有效性。这个和我们在PC上进行多线程开发的情况很接近。在没有电饭煲的时候,我们煮饭用的是柴,我们把柴砍下来后,像打桩一样插在地上,钻木取火,引燃枯树叶,然后点燃木桩。等到木桩烧起来了,把锅子架上去。大功告成!玩泥巴去了。然后回来的时候,柴早就被别人拿走了,釜底抽薪。这是因为我们的DMA队列它的设计结构是队列方式,你提交的传输请求并不会被立即处理——它要等到前面的所有请求都处理了之后才会轮到你现在提交的请求,而这个时候你早就玩泥巴去了,栈上的数据都“物是人非”了。所以我们不能用栈来存储不会被立即引用的数据。
我的设计是:对于少量的数据(比如一条用于控制显示器的命令等),直接把它存储在每个DMA请求的data字段里,然后用DCF_DATA_INLINED来标识。而对于较大的数据的话,则另想办法。至少,我们不至于为了那几个字节的控制命令而浪费有限的RAM。用柴火煮饭的话好歹刨个坑,然后把柴火放坑里。- #include"dma_queue.h"
- #define CCR_CLEAR_Mask ((uint32_t)0xFFFF800F)
- // 按要求定义全局变量
- #if DMA1_1_QUEUE
- dma_queue_t g_dma1_1_queue;
- #endif
- #if DMA1_2_QUEUE
- dma_queue_t g_dma1_2_queue;
- #endif
- #if DMA1_3_QUEUE
- dma_queue_t g_dma1_3_queue;
- #endif
- #if DMA1_4_QUEUE
- dma_queue_t g_dma1_4_queue;
- #endif
- #if DMA1_5_QUEUE
- dma_queue_t g_dma1_5_queue;
- #endif
- #if DMA1_6_QUEUE
- dma_queue_t g_dma1_6_queue;
- #endif
- #if DMA1_7_QUEUE
- dma_queue_t g_dma1_7_queue;
- #endif
- // 开始传输。
- static void _start_transfer(dma_queue_p dma_queue)
- {
- uint32_t tmpreg;
- uint32_t addr = 0;
- dma_ctrl_p pdc;
- DMA_Channel_TypeDef *channel = dma_queue->channel;
-
- pdc = &dma_queue->dma_ctrl[dma_queue->dma_ctrl_current];
- tmpreg = channel->CCR & CCR_CLEAR_Mask;
-
- // 设置状态
- dma_queue->dma_ctrl_inprogress = 1;
-
- // 调用回调
- dma_queue->on_start(dma_queue, dma_queue->dma_ctrl_current);
-
- // 分析位数
- if((pdc->flags & DCF_BITS_FLAGS) == DCF_BITS_8)
- {
- tmpreg |= DMA_MemoryDataSize_Byte | DMA_PeripheralDataSize_Byte;
- }
-
- if((pdc->flags & DCF_BITS_FLAGS) == DCF_BITS_16)
- {
- tmpreg |= DMA_MemoryDataSize_HalfWord | DMA_PeripheralDataSize_HalfWord;
- }
-
- if((pdc->flags & DCF_BITS_FLAGS) == DCF_BITS_32)
- {
- tmpreg |= DMA_MemoryDataSize_Word | DMA_PeripheralDataSize_Word;
- }
-
- // 分析指针行为
- if((pdc->flags & DCF_PTR_FLAGS) == DCF_PTR_INC)
- {
- tmpreg |= DMA_MemoryInc_Enable;
- }
-
- if((pdc->flags & DCF_PTR_FLAGS) == DCF_PTR_STAY)
- {
- tmpreg |= DMA_MemoryInc_Disable;
- }
-
- // 数据是指针
- if((pdc->flags & DCF_DATA_FLAGS) == DCF_DATA_PTR)
- {
- addr = pdc->data;
- }
-
- // 数据是数据
- if((pdc->flags & DCF_DATA_FLAGS) == DCF_DATA_INLINED)
- {
- addr = (uint32_t)&(pdc->data); // 直到传输结束前,这个地址上的数据都不会变
- }
-
- // 收发行为
- if((pdc->flags & DCF_DIR_FLAGS) == DCF_DIR_TX)
- {
- tmpreg |= DMA_DIR_PeripheralDST; // 内存到外设
- }
-
- if((pdc->flags & DCF_DIR_FLAGS) == DCF_DIR_RX)
- {
- tmpreg |= DMA_DIR_PeripheralSRC; // 外设到内存
- }
-
- // 其它控制位
- tmpreg |= dma_queue->flags;
-
- channel->CCR = tmpreg; // 设置DMA通道寄存器(并且暂停传输,如果旧的传输在进行的话)
- channel->CNDTR = pdc->count; // 个数
- channel->CPAR = (uint32_t)dma_queue->periph_addr; // 外设地址
- channel->CMAR = addr; // 数据地址
-
- channel->CCR |= DMA_CCR1_EN; // 开启传输
- }
- // 中断回调
- static void _on_irq(dma_queue_p dma_queue, const int dma_controller, const int channel_num)
- {
- dma_ctrl_p pdc;
- DMA_TypeDef *dma;
- uint32_t TC_Flag;
-
- //if(dma_controller != dma_queue->dma_controller || channel_num != dma_queue->channel_num)
- // return;
-
- if(dma_controller == 1)
- {
- dma = DMA1;
- TC_Flag = 0x00000000;
- }
- else if(dma_controller == 2)
- {
- dma = DMA2;
- TC_Flag = 0x10000000;
- }
- //else
- //{
- // return;
- //}
-
- TC_Flag |= DMA_IT_TC << ((channel_num - 1) * 4);
-
- // 判断是否自己对应的中断
- if((dma->ISR & TC_Flag) == TC_Flag)
- {
- // 停止传输
- dma_queue->channel->CCR &= (uint16_t)~DMA_CCR1_EN;
-
- // 清除中断位
- dma->IFCR = TC_Flag;
- }
- else
- {
- return;
- }
-
- // 这是DMA传输完成时的中断,因此我们要开始下一轮传输
- __disable_irq();
- pdc = &dma_queue->dma_ctrl[dma_queue->dma_ctrl_current];
-
- if(pdc->repeat_count) // 如果它有重复次数,那就尽量以最快速度重复这次传输
- {
- pdc->repeat_count --;
- dma_queue->channel->CNDTR = pdc->count; // 必须设置计数器
- dma_queue->channel->CCR |= DMA_CCR1_EN; // 别的寄存器不用动。开始传输
- }
- else
- {
- dma_queue->dma_ctrl_inprogress = 0; // 完成了传输
- dma_queue->on_finish(dma_queue, dma_queue->dma_ctrl_current); // 完成传输的回调
- dma_queue->dma_ctrl_count --; // 队列请求计数-1
- dma_queue->dma_ctrl_current = (dma_queue->dma_ctrl_current + 1) % DMA_QUEUE_LENGTH; // 下一个请求
-
- if(dma_queue->dma_ctrl_count) // 依然有剩余请求,则开始传输
- _start_transfer(dma_queue);
- }
- __enable_irq();
- }
- // 初始化一个DMA队列对象,返回上述的全局变量中的一个(根据前两个参数匹配)
- dma_queue_p dma_queue_init
- (
- int dma_controller, // DMA控制器号,1或2
- int channel_num, // DMA通道号,1,2,3,4,5,6,7
- volatile void *periph_addr, // 要操作的外设的地址
- on_start_f on_start, // 开始传输时被调用的回调函数
- on_finish_f on_finish, // 传输结束后被调用的回调函数
- uint32_t DMA_Priority // DMA传输优先级
- )
- {
- dma_queue_p dma_queue;
- if(dma_controller == 1)
- {
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
- switch(channel_num)
- {
- #if DMA1_1_QUEUE
- case 1:
- dma_queue = &g_dma1_1_queue;
- dma_queue->channel = DMA1_Channel1;
- NVIC_EnableIRQ(DMA1_Channel1_IRQn);
- break;
- #endif
- #if DMA1_2_QUEUE
- case 2:
- dma_queue = &g_dma1_2_queue;
- dma_queue->channel = DMA1_Channel2;
- NVIC_EnableIRQ(DMA1_Channel2_IRQn);
- break;
- #endif
- #if DMA1_3_QUEUE
- case 3:
- dma_queue = &g_dma1_3_queue;
- dma_queue->channel = DMA1_Channel3;
- NVIC_EnableIRQ(DMA1_Channel3_IRQn);
- break;
- #endif
- #if DMA1_4_QUEUE
- case 4:
- dma_queue = &g_dma1_4_queue;
- dma_queue->channel = DMA1_Channel4;
- NVIC_EnableIRQ(DMA1_Channel4_IRQn);
- break;
- #endif
- #if DMA1_5_QUEUE
- case 5:
- dma_queue = &g_dma1_5_queue;
- dma_queue->channel = DMA1_Channel5;
- NVIC_EnableIRQ(DMA1_Channel5_IRQn);
- break;
- #endif
- #if DMA1_6_QUEUE
- case 6:
- dma_queue = &g_dma1_6_queue;
- dma_queue->channel = DMA1_Channel6;
- NVIC_EnableIRQ(DMA1_Channel6_IRQn);
- break;
- #endif
- #if DMA1_7_QUEUE
- case 7:
- dma_queue = &g_dma1_7_queue;
- dma_queue->channel = DMA1_Channel7;
- NVIC_EnableIRQ(DMA1_Channel7_IRQn);
- break;
- #endif
- default:
- DMA_QUEUE_BKPT();
- return 0;
- }
- }
- else
- {
- // 暂时懒得处理DMA2
- DMA_QUEUE_BKPT();
- return 0;
- }
-
- dma_queue->dma_controller = dma_controller;
- dma_queue->channel_num = channel_num;
- dma_queue->periph_addr = periph_addr;
- dma_queue->flags = DMA_Priority | // 优先级是参数
- // 下面的才是宏
- DMA_Mode_Normal | DMA_PeripheralInc_Disable | DMA_M2M_Disable | DMA_IT_TC;
- dma_queue->on_start = on_start;
- dma_queue->on_finish = on_finish;
- dma_queue->dma_ctrl_current = 0;
- dma_queue->dma_ctrl_count = 0;
- dma_queue->dma_ctrl_inprogress = 0;
- return dma_queue;
- }
- // 压入一个DMA传输请求到队列里,返回它在队列中的位置
- uint32_t dma_queue_push
- (
- dma_queue_p dma_queue, // DMA队列对象
- data_t data, // 数据,或者数据的地址
- uint16_t count, // 传输的数量
- uint8_t repeat_count, // 重复的次数
- uint8_t flags // 控制属性
- )
- {
- uint32_t cur;
-
- while(1)
- {
- __disable_irq();
- // 防止队列溢出
- if(dma_queue->dma_ctrl_count >= DMA_QUEUE_LENGTH)
- {
- // 如果队列是满的,但并没有传输的话,开启传输
- if(!dma_queue->dma_ctrl_inprogress)
- _start_transfer(dma_queue);
- }
- else
- {
- // 插入传输请求到队列里
- cur = (dma_queue->dma_ctrl_current + dma_queue->dma_ctrl_count) % DMA_QUEUE_LENGTH;
- dma_queue->dma_ctrl[cur].data = data;
- dma_queue->dma_ctrl[cur].count = count;
- dma_queue->dma_ctrl[cur].repeat_count = repeat_count;
- dma_queue->dma_ctrl[cur].flags = flags;
- dma_queue->dma_ctrl_count ++;
-
- // 如果之前队列是空的,现在插入了传输请求,那就开启传输
- if(dma_queue->dma_ctrl_count == 1)
- _start_transfer(dma_queue);
- __enable_irq();
- break; // 并且退出
- }
- __enable_irq();
-
- // 这个循环是为了等待队列空出空位
- }
-
- return cur;
- }
- // 等待特定的传输请求完成
- void dma_queue_wait_transfer(dma_queue_p dma_queue, uint32_t index)
- {
- index %= DMA_QUEUE_LENGTH;
- while(dma_queue->dma_ctrl_current != index)
- {
- if(!dma_queue->dma_ctrl_inprogress) break;
- }
- }
- // 等待全部传输请求完成
- void dma_queue_wait(dma_queue_p dma_queue)
- {
- while(dma_queue->dma_ctrl_count);
- }
- // 用宏来生成函数名
- #define DMAx_Channely_IRQHandler(x,y) \
- void DMA ## x ## _Channel ## y ## _IRQHandler(void) \
- { \
- _on_irq(&g_dma ## x ## _ ## y ## _queue, x, y); \
- }
- #if DMA1_1_QUEUE
- DMAx_Channely_IRQHandler(1,1)
- #endif
- #if DMA1_2_QUEUE
- DMAx_Channely_IRQHandler(1,2)
- #endif
- #if DMA1_3_QUEUE
- DMAx_Channely_IRQHandler(1,3)
- #endif
- #if DMA1_4_QUEUE
- DMAx_Channely_IRQHandler(1,4)
- #endif
- #if DMA1_5_QUEUE
- DMAx_Channely_IRQHandler(1,5)
- #endif
- #if DMA1_6_QUEUE
- DMAx_Channely_IRQHandler(1,6)
- #endif
- #if DMA1_7_QUEUE
- DMAx_Channely_IRQHandler(1,7)
- #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个像素——光是像素的个数,就超过了STM32F103C8T6 的内存字节数(20480字节)。所以在单片机的内存里存储屏幕图像的做法并不现实,因为你存不下。
所以绘图都是靠的直接写屏。也就是设置屏幕的寻址区域,然后输出数据到显存里。就像当年x86电脑的内存也很小的时候,在Windo ws上通过调用GDI的绘图函数来绘制窗体。而不是像现在这样可以随便加载jpg、png、bmp、gif 等各种五花八门的图像存储格式到内存里(jpg、png、gif 等这些格式最终都要解压成bmp才能使用),然后用各种操作像素的方式来绘图。
理解了这个,你就更容易能理解GDI的设计初衷了。- #ifndef _ILI9341_LCD_
- #define _ILI9341_LCD_
- #include<inttypes.h>
- #include"config.h"
- typedef enum lcd_rgb_format_enum
- {
- lcdf_rgb565 = 16,
- lcdf_rgb666 = 18
- }lcd_rgb_format_t, *lcd_rgb_format_p;
- // 屏幕分辨率
- extern int g_lcd_width;
- extern int g_lcd_height;
- extern const uint32_t g_lcd_pixels;
- extern lcd_rgb_format_t g_lcd_rgb_format; // 传输的颜色格式
- #define RGB565(r,g,b) (((((uint32_t)(b) >> 3) & 0x001F) | \
- (((uint32_t)(g) << 3) & 0x07E0) | \
- (((uint32_t)(r) << 8) & 0xf800)) & 0x0FFFF)
- #define RGB666(r,g,b) ((((uint32_t)(r) << 0) & 0x0000FC) | \
- (((uint32_t)(g) << 8) & 0x00FC00) | \
- (((uint32_t)(b) << 16) & 0xFC0000))
-
- #define RGB(r,g,b) (g_lcd_rgb_format == lcdf_rgb666 ? RGB666(r, g, b) : RGB565(r, g, b))
- // 初始化、重置
- void lcd_init();
- void lcd_reset();
- // 设置方向
- void lcd_set_landscape(const int mirror);
- void lcd_set_portrait(const int mirror);
- // 设置传输的颜色格式
- void lcd_set_rgb16();
- void lcd_set_rgb18();
- // 设置绘图区域
- void lcd_set_draw_area(const int x1, const int y1, const int x2, const int y2);
- // 从绘图区域里面取出像素数据
- void lcd_get_pixels(void *pixels, const uint32_t count);
- // 写入像素数据到绘图区域里
- void lcd_put_pixels(const void *pixels, const uint32_t count);
- // 写入纯色到绘图区域里
- void lcd_put_pure_color(const uint32_t color, const uint32_t count);
- // 用一个颜色填充全屏
- void lcd_fill_screen(const uint32_t color);
- // 填充区域(根据左、上、右、下确定范围)
- void lcd_fill_area(int x1, int y1, int x2, int y2, const uint32_t color);
- // 填充矩形(根据左、上、宽、高确定范围)
- void lcd_fill_rect(int x, int y, int w, int h, const uint32_t color);
- // 等待绘图完成
- void lcd_flush();
- #endif
复制代码 这是最基本的接口。在不考虑其它功能比如休眠模式(实际应用是必须考虑的,因为显示屏很耗电)、伽玛度调节、亮度对比度、关屏序列以外,与像素操作关系最密切的就是上面的函数了。
其它的任何绘图方式,比如画线、画圆、显示文字、画各种几何图形等,都是用上面这些函数实现的。
底层上,这些函数就是靠调用DMA把命令和命令参数都传输过去。当你实现了自己的画线的函数以后,假设你要画一条斜线,它其实是靠画多个横线或者竖线来完成的。而画横线和竖线则是靠填充区域完成的。
想象一下,如果你要画一条斜线,你需要一边计算每个小的直线的线段位置和长度,一边把绘图的请求发给屏幕。这个过程如果能并行处理的话,你可以在很短的时间里就画好一条直线。- #include"lcd.h"
- #include"delay.h"
- #include"debug.h"
- #include"dma_queue.h"
- #include<stm32f10x_dma.h>
- #include<stm32f10x_spi.h>
- #include<system_stm32f10x.h>
- /* Level 1 Commands */
- #define LCD_SWRESET 0x01 /* Software Reset */
- #define LCD_READ_DISPLAY_ID 0x04 /* Read display identification information */
- #define LCD_RDDST 0x09 /* Read Display Status */
- #define LCD_RDDPM 0x0A /* Read Display Power Mode */
- #define LCD_RDDMADCTL 0x0B /* Read Display MADCTL */
- #define LCD_RDDCOLMOD 0x0C /* Read Display Pixel Format */
- #define LCD_RDDIM 0x0D /* Read Display Image Format */
- #define LCD_RDDSM 0x0E /* Read Display Signal Mode */
- #define LCD_RDDSDR 0x0F /* Read Display Self-Diagnostic Result */
- #define LCD_SPLIN 0x10 /* Enter Sleep Mode */
- #define LCD_SLEEP_OUT 0x11 /* Sleep out register */
- #define LCD_PTLON 0x12 /* Partial Mode ON */
- #define LCD_NORMAL_MODE_ON 0x13 /* Normal Display Mode ON */
- #define LCD_DINVOFF 0x20 /* Display Inversion OFF */
- #define LCD_DINVON 0x21 /* Display Inversion ON */
- #define LCD_GAMMA 0x26 /* Gamma register */
- #define LCD_DISPLAY_OFF 0x28 /* Display off register */
- #define LCD_DISPLAY_ON 0x29 /* Display on register */
- #define LCD_COLUMN_ADDR 0x2A /* Colomn address register */
- #define LCD_PAGE_ADDR 0x2B /* Page address register */
- #define LCD_GRAM 0x2C /* GRAM register */
- #define LCD_RGBSET 0x2D /* Color SET */
- #define LCD_RAMRD 0x2E /* Memory Read */
- #define LCD_PLTAR 0x30 /* Partial Area */
- #define LCD_VSCRDEF 0x33 /* Vertical Scrolling Definition */
- #define LCD_TEOFF 0x34 /* Tearing Effect Line OFF */
- #define LCD_TEON 0x35 /* Tearing Effect Line ON */
- #define LCD_MAC 0x36 /* Memory Access Control register*/
- #define LCD_VSCRSADD 0x37 /* Vertical Scrolling Start Address */
- #define LCD_IDMOFF 0x38 /* Idle Mode OFF */
- #define LCD_IDMON 0x39 /* Idle Mode ON */
- #define LCD_PIXEL_FORMAT 0x3A /* Pixel Format register */
- #define LCD_WRITE_MEM_CONTINUE 0x3C /* Write Memory Continue */
- #define LCD_READ_MEM_CONTINUE 0x3E /* Read Memory Continue */
- #define LCD_SET_TEAR_SCANLINE 0x44 /* Set Tear Scanline */
- #define LCD_GET_SCANLINE 0x45 /* Get Scanline */
- #define LCD_WDB 0x51 /* Write Brightness Display register */
- #define LCD_RDDISBV 0x52 /* Read Display Brightness */
- #define LCD_WCD 0x53 /* Write Control Display register*/
- #define LCD_RDCTRLD 0x54 /* Read CTRL Display */
- #define LCD_WRCABC 0x55 /* Write Content Adaptive Brightness Control */
- #define LCD_RDCABC 0x56 /* Read Content Adaptive Brightness Control */
- #define LCD_WRITE_CABC 0x5E /* Write CABC Minimum Brightness */
- #define LCD_READ_CABC 0x5F /* Read CABC Minimum Brightness */
- #define LCD_READ_ID1 0xDA /* Read ID1 */
- #define LCD_READ_ID2 0xDB /* Read ID2 */
- #define LCD_READ_ID3 0xDC /* Read ID3 */
- /* Level 2 Commands */
- #define LCD_RGB_INTERFACE 0xB0 /* RGB Interface Signal Control */
- #define LCD_FRMCTR1 0xB1 /* Frame Rate Control (In Normal Mode) */
- #define LCD_FRMCTR2 0xB2 /* Frame Rate Control (In Idle Mode) */
- #define LCD_FRMCTR3 0xB3 /* Frame Rate Control (In Partial Mode) */
- #define LCD_INVTR 0xB4 /* Display Inversion Control */
- #define LCD_BPC 0xB5 /* Blanking Porch Control register */
- #define LCD_DFC 0xB6 /* Display Function Control register */
- #define LCD_ETMOD 0xB7 /* Entry Mode Set */
- #define LCD_BACKLIGHT1 0xB8 /* Backlight Control 1 */
- #define LCD_BACKLIGHT2 0xB9 /* Backlight Control 2 */
- #define LCD_BACKLIGHT3 0xBA /* Backlight Control 3 */
- #define LCD_BACKLIGHT4 0xBB /* Backlight Control 4 */
- #define LCD_BACKLIGHT5 0xBC /* Backlight Control 5 */
- #define LCD_BACKLIGHT7 0xBE /* Backlight Control 7 */
- #define LCD_BACKLIGHT8 0xBF /* Backlight Control 8 */
- #define LCD_POWER1 0xC0 /* Power Control 1 register */
- #define LCD_POWER2 0xC1 /* Power Control 2 register */
- #define LCD_VCOM1 0xC5 /* VCOM Control 1 register */
- #define LCD_VCOM2 0xC7 /* VCOM Control 2 register */
- #define LCD_NVMWR 0xD0 /* NV Memory Write */
- #define LCD_NVMPKEY 0xD1 /* NV Memory Protection Key */
- #define LCD_RDNVM 0xD2 /* NV Memory Status Read */
- #define LCD_READ_ID4 0xD3 /* Read ID4 */
- #define LCD_PGAMMA 0xE0 /* Positive Gamma Correction register */
- #define LCD_NGAMMA 0xE1 /* Negative Gamma Correction register */
- #define LCD_DGAMCTRL1 0xE2 /* Digital Gamma Control 1 */
- #define LCD_DGAMCTRL2 0xE3 /* Digital Gamma Control 2 */
- #define LCD_INTERFACE 0xF6 /* Interface control register */
- /* Extend register commands */
- #define LCD_POWERA 0xCB /* Power control A register */
- #define LCD_POWERB 0xCF /* Power control B register */
- #define LCD_DTCA 0xE8 /* Driver timing control A */
- #define LCD_DTCB 0xEA /* Driver timing control B */
- #define LCD_POWER_SEQ 0xED /* Power on sequence register */
- #define LCD_3GAMMA_EN 0xF2 /* 3 Gamma enable register */
- #define LCD_PRC 0xF7 /* Pump ratio control register */
- #define ORIENTATION_PORTRAIT 0x48
- #define ORIENTATION_LANDSCAPE 0x28
- #define ORIENTATION_PORTRAIT_MIRROR 0x88
- #define ORIENTATION_LANDSCAPE_MIRROR 0xE8
- #define LCD_PIXEL_WIDTH 240
- #define LCD_PIXEL_HEIGHT 320
- #define LCD_PIXEL_COUNT LCD_PIXEL_WIDTH * LCD_PIXEL_HEIGHT
- #define MAKE_16BIT(l,h) ((uint16_t)(l) | ((uint16_t)(h) << 8))
- #define MAKE_PARAM(l,h) (((uint32_t)(l) & 0x0000FFFF) | ((uint32_t)(h) << 16))
- // 屏幕分辨率
- int g_lcd_width;
- int g_lcd_height;
- const uint32_t g_lcd_pixels = LCD_PIXEL_COUNT;
- lcd_rgb_format_t g_lcd_rgb_format;
- // 初始化命令
- static const uint8_t _init_commands[] =
- {
- // Power control A
- 6, LCD_POWERA, 0x39, 0x2C, 0x00, 0x34, 0x02,
- // Power control B
- 4, LCD_POWERB, 0x00, 0xC1, 0x30,
- // Driver timing control A
- 4, LCD_DTCA, 0x85, 0x00, 0x78,
- // Driver timing control B
- 3, LCD_DTCB, 0x00, 0x00,
- // Power on sequence control
- 5, LCD_POWER_SEQ, 0x64, 0x03, 0x12, 0x81,
- // Pump ratio control
- 2, LCD_PRC, 0x20,
- // Power control 1
- 2, LCD_POWER1, 0x23,
- // Power control 2
- 2, LCD_POWER2, 0x10,
- // VCOM control 1
- 3, LCD_VCOM1, 0x3E, 0x28,
- // VCOM cotnrol 2
- 2, LCD_VCOM2, 0x86,
- // Memory access control
- 2, LCD_MAC, 0x48,
- // Pixel format set
- 2, LCD_PIXEL_FORMAT, 0x55,
- // Frame rate control
- 3, LCD_FRMCTR1, 0x00, 0x18,
- // Display function control
- 4, LCD_DFC, 0x08, 0x82, 0x27,
- // 3Gamma function disable
- 2, LCD_3GAMMA_EN, 0x00,
- // Gamma curve selected
- 2, LCD_GAMMA, 0x01,
- // Set positive gamma
- 16, LCD_PGAMMA, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00,
- 16, LCD_NGAMMA, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F,
- 0
- };
- // 开始传输的回调
- static void _on_start(dma_queue_p pq, uint32_t index)
- {
- TFT_GPIO->BRR = TFT_CS_PIN;
- if((pq->dma_ctrl[index].flags & DCF_CUSTOM_FLAG) == DCF_CUSTOM_FLAG)
- {
- TFT_GPIO->BSRR = TFT_DC_PIN;
- }
- if((pq->dma_ctrl[index].flags & DCF_BITS_FLAGS) == DCF_BITS_16)
- {
- SPI_MASTER->CR1 &= ~SPI_CR1_SPE; // DISABLE SPI
- SPI_MASTER->CR1 |= SPI_CR1_DFF; // SPI 16
- SPI_MASTER->CR1 |= SPI_CR1_SPE; // ENABLE SPI
- }
- else
- {
- SPI_MASTER->CR1 &= ~SPI_CR1_SPE; // DISABLE SPI
- SPI_MASTER->CR1 &= ~SPI_CR1_DFF; // SPI 8
- SPI_MASTER->CR1 |= SPI_CR1_SPE; // ENABLE SPI
- }
- #if LCD_CMDBUF_LED // 调试:传输时亮灯
- LCD_CMDBUF_LED_GPIO->BRR = LCD_CMDBUF_LED_PIN;
- #endif
- }
- // 传输结束的回调
- static void _on_finish(dma_queue_p pq, uint32_t index)
- {
- if((pq->dma_ctrl[index].flags & DCF_CUSTOM_FLAG) == DCF_CUSTOM_FLAG)
- {
- TFT_GPIO->BRR = TFT_DC_PIN;
- }
- TFT_GPIO->BSRR = TFT_CS_PIN;
- #if LCD_CMDBUF_LED // 调试:传输结束时熄灯
- LCD_CMDBUF_LED_GPIO->BSRR = LCD_CMDBUF_LED_PIN;
- #endif
- }
- // 配置SPI与DMA协作
- static void _spi_dma_config()
- {
- SPI_I2S_DMACmd(SPI_MASTER, SPI_I2S_DMAReq_Tx, ENABLE);
- SPI_I2S_DMACmd(SPI_MASTER, SPI_I2S_DMAReq_Rx, ENABLE);
-
- // 初始化DMA传输队列
- dma_queue_init(1, 2, &(SPI_MASTER->DR), _on_start, _on_finish, DMA_Priority_Medium);
- dma_queue_init(1, 3, &(SPI_MASTER->DR), _on_start, _on_finish, DMA_Priority_Medium);
- }
- // 配置SPI
- static void _spi_config()
- {
- SPI_InitTypeDef spi_conf;
-
- RCC_APB2PeriphClockCmd(SPI_MASTER_CLK, ENABLE);
-
- SPI_StructInit(&spi_conf);
- spi_conf.SPI_Mode = SPI_Mode_Master;
- spi_conf.SPI_NSS = SPI_NSS_Soft;
- spi_conf.SPI_CPOL = SPI_CPOL_High;
- spi_conf.SPI_CPHA = SPI_CPHA_2Edge;
- spi_conf.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
- SPI_Init(SPI_MASTER, &spi_conf);
- SPI_Cmd(SPI_MASTER, ENABLE);
- }
- // 配置GPIO
- static void _spi_gpio_config()
- {
- GPIO_InitTypeDef gpio_conf;
- RCC_PCLK2Config(RCC_HCLK_Div2);
- RCC_APB2PeriphClockCmd(SPI_MASTER_GPIO_CLK, ENABLE);
- RCC_APB2PeriphClockCmd(RCC_APB2ENR_AFIOEN, ENABLE);
- // GPIO speed by default
- gpio_conf.GPIO_Speed = GPIO_Speed_50MHz;
- // GPIO for CS/DC/LED/RESET
- gpio_conf.GPIO_Pin = TFT_CS_PIN | TFT_DC_PIN | TFT_RESET_PIN | TFT_LED_PIN;
- gpio_conf.GPIO_Mode = GPIO_Mode_Out_PP;
- GPIO_Init(GPIOA, &gpio_conf);
- // GPIO for SPI
- gpio_conf.GPIO_Pin = SPI_MASTER_PIN_SCK | SPI_MASTER_PIN_MOSI;
- gpio_conf.GPIO_Mode = GPIO_Mode_AF_PP;
- GPIO_Init(SPI_MASTER_GPIO, &gpio_conf);
- // GPIO for SPI
- gpio_conf.GPIO_Pin = SPI_MASTER_PIN_MISO;
- gpio_conf.GPIO_Mode = GPIO_Mode_IPD;
- GPIO_Init(SPI_MASTER_GPIO, &gpio_conf);
- }
- // 等待SPI传输
- // static void _spi_transfer_wait()
- // {
- // while(SPI_I2S_GetFlagStatus(SPI_MASTER, SPI_I2S_FLAG_BSY) == SET);
- // }
- // 等待所有DMA队列完成传输
- static void _spi_lcd_flush(void)
- {
- dma_queue_wait(&g_dma1_2_queue);
- dma_queue_wait(&g_dma1_3_queue);
- }
- // 发送命令
- static uint32_t _spi_lcd_send_cmd(const uint8_t cmd)
- {
- return dma_queue_push(&g_dma1_3_queue, cmd, 1, 0, DCF_BITS_8 | DCF_PTR_INC | DCF_DIR_TX | DCF_DATA_INLINED);
- }
- ///////////////////////////////////////////////////////////////////////////////
- //
- // 以下的函数都是发送数据的
- //
- // 所谓数据,就是传输的时候DC针要设为高电平
- // param的意思是“发送的是参数”,数据是4字节以内的
- // num是DMA传输的单位数量
- // repeat_count是DMA传输的自动重启的次数
- //
- ///////////////////////////////////////////////////////////////////////////////
- // 以8bit为单位发送指针指向的数据
- static uint32_t _spi_lcd_send_data8(const void *data, const uint32_t num, const uint8_t repeat_count)
- {
- return dma_queue_push(&g_dma1_3_queue, (data_t)data, num, repeat_count,
- DCF_BITS_8 | DCF_PTR_INC | DCF_DIR_TX | DCF_CUSTOM_FLAG);
- }
- // 以8bit为单位发送4个字节以内的数据
- static uint32_t _spi_lcd_send_param8(const data_t param, const uint32_t num, const uint8_t repeat_count)
- {
- return dma_queue_push(&g_dma1_3_queue, param, num, repeat_count,
- DCF_BITS_8 | DCF_PTR_INC | DCF_DIR_TX | DCF_DATA_INLINED | DCF_CUSTOM_FLAG);
- }
- // 以16bit为单位发送指针指向的数据
- static uint32_t _spi_lcd_send_data16(const void *data, const uint32_t num, const uint8_t repeat_count)
- {
- return dma_queue_push(&g_dma1_3_queue, (data_t)data, num, repeat_count,
- DCF_BITS_16 | DCF_PTR_INC | DCF_DIR_TX | DCF_CUSTOM_FLAG);
- }
- // 以16bit为单位发送4个字节以内的数据
- static uint32_t _spi_lcd_send_param16(const data_t data, const uint32_t num, const uint8_t repeat_count)
- {
- return dma_queue_push(&g_dma1_3_queue, data, num, repeat_count,
- DCF_BITS_16 | DCF_PTR_INC | DCF_DIR_TX | DCF_DATA_INLINED | DCF_CUSTOM_FLAG);
- }
- // 以8bit为单位发送同一个uint8_t的数据,num次
- // static uint32_t _spi_lcd_send_param8_repeat(const data_t param, const uint32_t num, const uint8_t repeat_count)
- // {
- // return dma_queue_push(&g_dma1_3_queue, param, num, repeat_count,
- // DCF_BITS_8 | DCF_PTR_STAY | DCF_DIR_TX | DCF_DATA_INLINED | DCF_CUSTOM_FLAG);
- // }
- // 以16bit为单位发送同一个uint16_t的数据,num次
- static uint32_t _spi_lcd_send_param16_repeat(const data_t data, const uint32_t num, const uint8_t repeat_count)
- {
- return dma_queue_push(&g_dma1_3_queue, data, num, repeat_count,
- DCF_BITS_16 | DCF_PTR_STAY | DCF_DIR_TX | DCF_DATA_INLINED | DCF_CUSTOM_FLAG);
- }
- // 以8bit为单位,接收数据到一个缓冲区
- static uint32_t _spi_lcd_recv_data8(const void *data, const uint32_t num)
- {
- return dma_queue_push(&g_dma1_2_queue, (data_t)data, num, 1,
- DCF_BITS_8 | DCF_PTR_INC | DCF_DIR_RX | DCF_CUSTOM_FLAG);
- }
- // 以16bit为单位,接收数据到一个缓冲区
- static uint32_t _spi_lcd_recv_data16(const void *data, const uint32_t num)
- {
- return dma_queue_push(&g_dma1_2_queue, (data_t)data, num, 1,
- DCF_BITS_16 | DCF_PTR_INC | DCF_DIR_RX | DCF_CUSTOM_FLAG);
- }
- ///////////////////////////////////////////////////////////////////////////////
- //
- // 以下的函数都是传输特定格式的颜色数据用的
- //
- // RGB565的格式是:
- // 红色在高位,绿色在中间,蓝色在低位,组成一个uint16_t,红色和蓝色是5bit,
- // 绿色是6bit。
- // rrrrrGGGGGGbbbbb
- //
- // RGB666的格式是:
- // 红色在低位,绿色在中间,蓝色在高位,红绿蓝各占一个字节,共3个字节,24位。
- // 其中红绿蓝每个通道的低2位被丢弃,传输的时候不起作用。只有高6bit起作用。
- // bbbbbb00gggggg00rrrrrr00
- //
- ///////////////////////////////////////////////////////////////////////////////
- static void _get_pixels_rgb565(void *rgb565, const uint32_t count)
- {
- uint32_t last_index;
- _spi_lcd_send_cmd(LCD_RAMRD);
- // 接收数据前,需要先发个0xFF
- last_index = _spi_lcd_send_param8(0xFF, 1, 0);
- dma_queue_wait_transfer(&g_dma1_3_queue, last_index); // 要等这个0xFF发出去,再接收
- _spi_lcd_recv_data16(rgb565, count);
- }
- static void _get_pixels_rgb666(void *rgb666, const uint32_t count)
- {
- uint32_t last_index;
- _spi_lcd_send_cmd(LCD_RAMRD);
- // 接收数据前,需要先发个0xFF
- last_index = _spi_lcd_send_param8(0xFF, 1, 0);
- dma_queue_wait_transfer(&g_dma1_3_queue, last_index); // 要等这个0xFF发出去,再接收
- _spi_lcd_recv_data8(rgb666, count * 3);
- }
- static void _put_pixels_rgb565(const void *rgb565, const uint32_t count)
- {
- _spi_lcd_send_cmd(LCD_GRAM);
- _spi_lcd_send_data16(rgb565, count, 0);
- }
- static void _put_pixels_rgb666(const void *rgb666, const uint32_t count)
- {
- _spi_lcd_send_cmd(LCD_GRAM);
- _spi_lcd_send_data8(rgb666, count * 3, 0);
- }
- static void _put_pure_color_rgb565(const uint32_t rgb565, const uint32_t count)
- {
- uint32_t rest = count;
- _spi_lcd_send_cmd(LCD_GRAM);
- // 一次不能发送超过65535个数据
- if(rest > 0x0FFFF)
- {
- _spi_lcd_send_param16_repeat(rgb565, 0x0FFFF, rest / 0x0FFFF - 1); // 重复发送
- rest %= 0x0FFFF;
- }
- _spi_lcd_send_param16_repeat(rgb565, rest, 0); // 发送剩下的
- }
- static void _put_pure_color_rgb666(const uint32_t rgb666, const uint32_t count)
- {
- // 发送3个字节编码的纯色,这里其实非常尴尬。
- // DMA无法以3字节为单位发送数据
- // 在栈上建立缓冲区来实现功能
-
- const uint32_t buffer_pixels = 64; // 缓冲区大小,像素数
- uint8_t color_buf[buffer_pixels * 3];
- int i;
- uint32_t last_index;
- uint32_t rest = count;
-
- // 填充纯色到缓冲区里
- for(i = 0; i < buffer_pixels * 3 && i < count * 3; i++)
- {
- color_buf[i] = rgb666 >> ((i % 3) * 8);
- }
-
- last_index = _spi_lcd_send_cmd(LCD_GRAM);
-
- // 数据量大,循环重复
- while(rest > buffer_pixels * 256)
- {
- last_index = _spi_lcd_send_data8(color_buf, buffer_pixels * 3, 255);
- rest -= buffer_pixels * 256;
- }
- // 发送剩下的循环重复的部分
- if(rest > buffer_pixels)
- {
- last_index = _spi_lcd_send_data8(color_buf, buffer_pixels * 3, rest / buffer_pixels - 1);
- rest %= buffer_pixels;
- }
- // 发送最后剩下的
- if(rest)
- last_index = _spi_lcd_send_data8(color_buf, rest * 3, 0);
-
- // 数据在栈上,所以只能等传输完了才能返回。
- dma_queue_wait_transfer(&g_dma1_3_queue, last_index);
-
- /*
- // 另一个实现,直接重复发送指定个数的3字节,非常慢。
- // 每发3个字节会产生一次中断。
- uint32_t rest = count;
- _spi_lcd_send_cmd(LCD_GRAM);
- while(rest > 256)
- {
- _spi_lcd_send_param8(rgb666, 3, 255);
- rest -= 256;
- }
- _spi_lcd_send_param8(rgb666, 3, rest - 1);*/
-
- }
- // 函数指针,用于对外提供不针对格式的接口。
- static void(*_get_pixels)(void *pixels, const uint32_t count);
- static void(*_put_pixels)(const void *pixels, const uint32_t count);
- static void(*_put_pure_color)(const uint32_t color, const uint32_t count);
- void lcd_init()
- {
- _spi_gpio_config();
- _spi_config();
- _spi_dma_config();
-
- #if LCD_CMDBUF_LED // 调试:传输时的灯
- RCC_APB2PeriphClockCmd(LCD_CMDBUF_LED_GPIO_CLK, ENABLE);
- #endif
-
- lcd_reset();
- }
- void lcd_reset()
- {
- const uint8_t *ptr = _init_commands;
- TFT_GPIO->BRR = TFT_RESET_PIN;
- delay_ms(10);
- TFT_GPIO->BSRR = TFT_RESET_PIN;
- delay_ms(50);
- _spi_lcd_send_cmd(LCD_SLEEP_OUT);
- delay_ms(150);
- _spi_lcd_send_cmd(LCD_DISPLAY_ON);
-
- // 发送初始化序列
- while(1)
- {
- uint8_t count = *ptr;
- if(!count) break; // 参数字节数
- ptr ++;
- _spi_lcd_send_cmd(*ptr); // 第一个是命令
- ptr ++;
- _spi_lcd_send_data8(ptr, count - 1, 0); // 剩下的是参数
- ptr += count - 1;
- }
-
- // 初始化后的传输格式是RGB565
- g_lcd_rgb_format = lcdf_rgb565;
-
- // 设置函数指针
- _get_pixels = _get_pixels_rgb565;
- _put_pixels = _put_pixels_rgb565;
- _put_pure_color = _put_pure_color_rgb565;
-
- // 此处设置 g_lcd_width 和 g_lcd_height的数值
- lcd_set_portrait(0);
-
- // 开启背光
- TFT_GPIO->BSRR = TFT_LED_PIN;
- }
- // 切换格式为16bit颜色
- void lcd_set_rgb16()
- {
- _spi_lcd_send_cmd(LCD_PIXEL_FORMAT);
- _spi_lcd_send_param8(0x55, 1, 0);
- g_lcd_rgb_format = lcdf_rgb565;
-
- _get_pixels = _get_pixels_rgb565;
- _put_pixels = _put_pixels_rgb565;
- _put_pure_color = _put_pure_color_rgb565;
- }
- // 切换格式为18bit颜色
- void lcd_set_rgb18()
- {
- _spi_lcd_send_cmd(LCD_PIXEL_FORMAT);
- _spi_lcd_send_param8(0x66, 1, 0);
- g_lcd_rgb_format = lcdf_rgb666;
-
- _get_pixels = _get_pixels_rgb666;
- _put_pixels = _put_pixels_rgb666;
- _put_pure_color = _put_pure_color_rgb666;
- }
- // 设置横向显示
- void lcd_set_landscape(const int mirror)
- {
- uint8_t cmd = mirror ? ORIENTATION_LANDSCAPE_MIRROR : ORIENTATION_LANDSCAPE;
-
- g_lcd_height = LCD_PIXEL_WIDTH;
- g_lcd_width = LCD_PIXEL_HEIGHT;
-
- _spi_lcd_send_cmd(LCD_MAC);
- _spi_lcd_send_param8(cmd, 1, 0);
- }
- // 设置竖向显示
- void lcd_set_portrait(const int mirror)
- {
- uint8_t cmd = mirror ? ORIENTATION_PORTRAIT_MIRROR : ORIENTATION_PORTRAIT;
-
- g_lcd_height = LCD_PIXEL_HEIGHT;
- g_lcd_width = LCD_PIXEL_WIDTH;
-
- _spi_lcd_send_cmd(LCD_MAC);
- _spi_lcd_send_param8(cmd, 1, 0);
- }
- // 设置绘图区域
- void lcd_set_draw_area(const int x1, const int y1, const int x2, const int y2)
- {
- _spi_lcd_send_cmd(LCD_COLUMN_ADDR);
- _spi_lcd_send_param16(MAKE_PARAM(x1, x2), 2, 0);
-
- _spi_lcd_send_cmd(LCD_PAGE_ADDR);
- _spi_lcd_send_param16(MAKE_PARAM(y1, y2), 2, 0);
- }
- // 从绘图区域里面取出像素数据
- void lcd_get_pixels(void *color, const uint32_t count)
- {
- _get_pixels(color, count);
- }
- // 写入像素数据到绘图区域里
- void lcd_put_pixels(const void *color, const uint32_t count)
- {
- _put_pixels(color, count);
- }
- // 写入纯色到绘图区域里
- void lcd_put_pure_color(const uint32_t color, const uint32_t count)
- {
- _put_pure_color(color, count);
- }
- // 用一个颜色填充全屏
- void lcd_fill_screen(const uint32_t color)
- {
- lcd_set_draw_area(0, 0, g_lcd_width - 1, g_lcd_height - 1);
- lcd_put_pure_color(color, g_lcd_pixels);
- }
- // 填充区域(根据左、上、右、下确定范围)
- void lcd_fill_area(int x1, int y1, int x2, int y2, const uint32_t color)
- {
- if(x1 > x2)
- {
- int t = x1;
- x1 = x2;
- x2 = t;
- }
- if(y1 > y2)
- {
- int t = y1;
- y1 = y2;
- y2 = t;
- }
- if(x1 >= g_lcd_width || y1 >= g_lcd_height || x2 < 0 || y2 < 0) return;
-
- if(x1 < 0) x1 = 0;
- if(y1 < 0) y1 = 0;
- if(x2 > g_lcd_width - 1) x2 = g_lcd_width - 1;
- if(y2 > g_lcd_height - 1) y2 = g_lcd_height - 1;
-
- lcd_set_draw_area(x1, y1, x2, y2);
- lcd_put_pure_color(color, (x2 - x1 + 1) * (y2 - y1 + 1));
- }
- // 填充矩形(根据左、上、宽、高确定范围)
- void lcd_fill_rect(int x, int y, int w, int h, const uint32_t color)
- {
- if(x >= g_lcd_width || y >= g_lcd_height) return;
-
- if(x < 0)
- {
- w += x;
- x = 0;
- }
-
- if(y < 0)
- {
- h += y;
- y = 0;
- }
-
- if(x + w > g_lcd_width)
- {
- w = g_lcd_width - x;
- }
-
- if(y + h > g_lcd_height)
- {
- h = g_lcd_height - y;
- }
-
- if(w <= 0 || h <= 0) return;
-
- lcd_set_draw_area(x, y, x + w - 1, y + h - 1);
- lcd_put_pure_color(color, w * h);
- }
- // 等待绘图完成
- void lcd_flush()
- {
- _spi_lcd_flush();
- }
复制代码 这是LCD控制部分的源码。经过测试,它可以在屏幕上绘制出图形。
然后这是测试代码。- #include<inttypes.h>
- #include<stdlib.h>
- #include<stm32f10x.h>
- #include<system_stm32f10x.h>
- #include"lcd.h"
- #include"delay.h"
- #include"debug.h"
- void main()
- {
- SystemInit();
-
- lcd_init();
- // lcd_set_landscape(0);
-
- lcd_fill_screen(RGB565(0, 127, 255));
-
- int i = 0, f = 0;
-
- while(1)
- {
- int x, y;
-
- i = f;
-
- // 画全屏棋盘格
- for(y = 0; y < g_lcd_height; y += 20)
- {
- for(x = 0; x < g_lcd_width; x += 20)
- {
- int r = 0, g = 0, b = 0;
- int v = i % 1536;
- // 根据i的值生成平滑过渡的颜色(有六个过渡阶段)
- if(v < 256)
- {
- r = 255;
- g = 0;
- b = 255 - v;
- }
- else if(v < 512)
- {
- r = 255;
- g = v - 256;
- b = 0;
- }
- else if(v < 768)
- {
- r = 255 - (v - 512);
- g = 255;
- b = 0;
- }
- else if(v < 1024)
- {
- r = 0;
- g = 255;
- b = v - 768;
- }
- else if(v < 1280)
- {
- r = 0;
- g = 255 - (v - 1024);
- b = 255;
- }
- else
- {
- r = v - 1280;
- g = 0;
- b = 255;
- }
-
- // 奇偶块反色
- lcd_fill_rect(x, y, 20, 20, ((x / 20) ^ (y / 20)) & 1 ? ~RGB(r, g, b) : RGB(r, g, b));
-
- i ++;
- }
- }
-
- f += 12;
- }
- }
复制代码 效果如下(拍的角度不太好,正面看的效果是很好的)
放大看它的像素点
可以用ST-Util调试看到DMA请求队列的变化过程。
接下来,我应该可以用这个去做个ADC示波器了。 |
stm32, 单片机, dma, 并行, 嵌入式, stm32, dma, stm32, dma, stm32, dma
|