【单片机】给STM32F103实现一个靠谱的并行DMA库
原文网址: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://camo.githubusercontent.com/598be785690c99f7bd22c349e9abe7d2baa1240e/687474703a2f2f696d672e796f75747562652e636f6d2f76692f356e4c512d56714d762d672f302e6a7067
毛子视频: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回帖后可见。**** Hidden Message *****最后我决定以毛子代码为基准来重新造轮子了——我要让它把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; // 队列
};
// 按要求定义全局变量
#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;
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;
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.data = data;
dma_queue->dma_ctrl.count = count;
dma_queue->dma_ctrl.repeat_count = repeat_count;
dma_queue->dma_ctrl.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电脑的内存也很小的时候,在Windows上通过调用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_CONTINUE0x3C /* 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.flags & DCF_CUSTOM_FLAG) == DCF_CUSTOM_FLAG)
{
TFT_GPIO->BSRR = TFT_DC_PIN;
}
if((pq->dma_ctrl.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.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;
int i;
uint32_t last_index;
uint32_t rest = count;
// 填充纯色到缓冲区里
for(i = 0; i < buffer_pixels * 3 && i < count * 3; i++)
{
color_buf = 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示波器了。 学习一下。
页:
[1]