总感觉之前的AT32F421板子/片子 有点小毛病,出各种莫名其妙的BUG(实在找不出软件的问题,只能怀疑是硬件 QAQ)。
于是之后咕了很久,最近终于想继续折腾,拿AT32F435画了一块LCD驱动板,准备入坑LVGL。板上资源就一块某园的2.8存240x320带电阻膜的LCD屏、触摸IC用XPT2046,另外还画了一片W25Q64和CH340在上面,有空试试QSPI和ISP功能。
画板子的时候就在思考这个问题:
XPT2046 和 LCD(ST7789) 到底要不要共用1个SPI接口?
之前画过一个小的实验板参照LCD厂家提供的手册上的画法,LCD和XPT2046共用一个SPI。其中有一个我不理解的地方,那就是LCD明明不会发送数据(难道不是这样?),但是却有一个数据引脚SDD,在没有触摸IC的应用中,该引脚悬空;在有XPT2046的应用中,厂家的手册上推荐把该引脚同XPT2046的MISO(同时也是单片机的SPI输入引脚)相连。
这真的是必要的吗?为什么需要用4根线来驱动一个LCD,即使它不可能知道外部有没有触摸IC的存在?
于是新板子上我尝试了以下方法:
基于几个基本逻辑:节约CPU资源:只要不是共用SPI,就用硬件CS;节约引脚:只要没有用到数据输入,就不使用双线双工。因此这里面合理的选项只有1和4。另外,只要和触摸IC共用SPI,就不得不用到MISO,且“一主多从”只支持软件CS。
1在之前的小板上就已经实现过了,但是觉得在IO资源足够的情况下,还是应当以节约CPU资源为主,所以舍弃了软件CS,想试试方法4:在LCD不与XPT2046共用SPI的时候,仅使用 SCLK, MOSI, CS(硬件控制) 3根线来驱动LCD。
然后理所当然的踩了一堆坑。。。调的头都要秃了,在此记录一下。
刚开始我以为只要在 spi_init_struct 中配置下面这一项:
spi_init_struct.transmission_mode = SPI_TRANSMIT_HALF_DUPLEX_TX;
然后 在spi_init() 函数里传进去就够了,结果代码都运行了,没有任何现象,检查走信号的GPIO,初始化后只能看到有一点点毛刺。这种情况通常可能是
然而负责这三行代码都执行过了,怎么出的问题?
检查寄存器的时候才发现,每当我初始化SPI设置后、执行 spi_enable() 去使能SPI时,STS寄存器蹦出一个MMERR(主模式错误),同时把我的模式改成了从模式,还关掉了我的SPI使能。
查了AT的RM,上面也没说什么情况下会触发MMERR,仔细检查RM提供的信息,总结了下面几点:
事实上对于第一项,如果设置了SPI_TRANSMIT_HALF_DUPLEX_TX,则在spi_init()中这两个bit会被配置完成。第二项只有在使用软件CS时才有用。所以最后证明是 hwcsoe 背大锅。。。
RM表示 ctrl2_bit.hwcsoe 是一个 rw 位,它的名字虽然叫硬件CS使能,但描述的功能不像是使能,却像是在用软件控制CS啊?!
所以我只好在spi_enable()前加上把这一位置1的命令。结果就没有MMERR了。
遂舍弃方法4,为了继续追求硬件CS,只能尝试方法2
浅浅吐槽一下,写库函数spi_init()的时候为什么不能多一个判断,如果是硬件CS自动加上这个置位啊。真的好坑,手册里也不说MMERR怎么来的,spi_init()明明已经有一堆判断逻辑了,为什么不加几行避免在设置的时候就出ERR呢...
在单线半双工下通过 ctrl2_bit.hwcsoe=1 开启硬件CS输出使能,导致的结果就是 spi使能后 全过程 CS一直为低电平。
查看RM:
当 SPI 作主机,硬件 CS 输出时,HWCSOE 位置 1,SWCSEN 置 0,开启硬件 CS 控制,SPI 在使能之后会在 CS 管脚上输出低电平,在 SPI 关闭并且发送完成后,释放 CS 信号。
RM表示:一直开着CS有啥不好?反正你都只有这一个从设备了。你要是想发完一个frame就关CS,那你直接把SPI关了不就o了?
属于又颠覆我对SPI的认知了。这个 ctrl2_bit.hwcsoe()主要是为 MCU做从设备,拿来检测是否被片选用的;如果是做主机,那它就会一直把CS拉低。
然而有的SPI从设备会在CS的上下沿完成锁存等操作,甚至在时序要求里对CS的拉低、释放时间有具体的要求,比如下图
举个栗子,AD9833的SPI时序要求
如上图,AD9833对CS的时序有一个最小setup time (t7)和最小hold time (t8),甚至t8还有一个最大值要求:不能超过t4-5ns。总之需要只在每一个数据帧传输的时候拉低CS,而之后要能迅速释放。
不死心的我仍决定尝试在每发送完一个帧后关掉SPI,实现SPI通信的代码变成这个样子:
结果令人失望,没能成功点屏。其中很令人在意的一点就是尽管硬件CS拉低很迅速,但释放(通过失能SPI实现)的速度非常慢,过程持续了百微妙级别:关掉SPI后CS信号的样子是典型RC充电曲线的形状。这就导致了在连续传输数据帧的时候,上一帧的CS根本没来得及完全释放,就马上被下一帧拉低了。
总之,硬件管理CS的尝试算是彻底失败了——因为硬件CS做不到只在一个数据帧传输的时候拉低CS。
如果干脆始终拉低CS,我也做了尝试,结果不行。怀疑是过早拉低CS导致多识别了一个时钟沿导致,我尝试了两种SPI时钟相位和两种SPI时钟空闲电平的4种组合。。,都不行。但是作为对照组,仅仅把CS改成软件管理,其余设置不变,就能在时钟POL=HIGH, PHA=2EDGE的组合下成功点屏。
所以排除时钟相位问题后,结论只能是LCD的驱动芯片需要每帧数据结束后CS的上升沿完成某些操作。
所以底层硬件就决定了即便该LCD单独使用一个SPI驱动,也不可能是单线半双工模式实现——因为它只支持硬件管理CS,而硬件管理CS又不能满足CS的时序要求。
那么答案只有一个,变成软件管理CS,MCU直接控制GPIO在每一帧数据结束后拉高CS。即上文的方法2同样不可行,只能改成方法3。那反正都软件管理CS了,干脆和触摸IC共用一个SPI得了,所以最后还是老老实实回到方法1。。
碎碎念:仔细想想,其实LCD的另一些控制信号如RST, DC 后者几乎快和CS一样常用,都只能靠软件实现。所以也许我真的不应该追求硬件CS?
我当前的GCC编译优化为O0,CPU主频288M,SPI通信用8分频(APB的144M / 8 = 18M),在这个速度下,实测方法2中一个8bit数据帧传输<500ns,最后一个时钟结束后约100ns 近300ns(用上图中的代码)CS被拉高,CS高电平又持续了近300ns(这个过程完成了DC的拉高,以及MCU数据搬运),然后CS进入下一帧的拉低,拉低后又是约300ns,才开始下一帧的数据传输。
数据帧之间的间隔接近1us,接近数据传输时间本身的两倍
// =========================> 2022-11-19更新 <=========================
后续优化了代码,所有图像输出均由DMA完成。
使用DMA可以实现数据帧连续传输
并尝试运行了LVGL demo:
经过优化后的刷屏代码:
#include "ST7789.h"
void LCD_GPIO_init(void)
{
#if LCD_USE_HARDWARE_SPI == 1
gpio_init_type gpio_init_struct;
// =========================> 初始化 SPI数据总线 GPIO <=========================
crm_periph_clock_enable(LCD_SPIx_GPIO_CRM, TRUE);
gpio_default_para_init(&gpio_init_struct);
gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;
gpio_init_struct.gpio_mode = GPIO_MODE_MUX;
gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
// SCLK
gpio_init_struct.gpio_pull = GPIO_PULL_DOWN;
gpio_init_struct.gpio_pins = LCD_SPIx_SCLK_GPIO_PIN;
gpio_init(LCD_SPIx_GPIO, &gpio_init_struct);
gpio_pin_mux_config(LCD_SPIx_GPIO, LCD_SPIx_SCLK_SOURCE, LCD_SPIx_MUX_SEL);
// MOSI
gpio_init_struct.gpio_pull = GPIO_PULL_UP;
gpio_init_struct.gpio_pins = LCD_SPIx_MOSI_GPIO_PIN;
;
gpio_init(LCD_SPIx_GPIO, &gpio_init_struct);
gpio_pin_mux_config(LCD_SPIx_GPIO, LCD_SPIx_MOSI_SOURCE, LCD_SPIx_MUX_SEL);
#if LCD_SHARE_SPI_WITH_TOUCH_IC == 1
// LCD和触摸IC共用SPI,CS由软件管理,此处不作配置
// 由于要接收触摸IC的数据,需要配置MISO,注意XPT2046的数据输出引脚外部不能上拉或下拉,只能配置成浮空输入
gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
gpio_init_struct.gpio_pins = LCD_SPIx_MISO_GPIO_PIN;
gpio_init(LCD_SPIx_GPIO, &gpio_init_struct);
gpio_pin_mux_config(LCD_SPIx_GPIO, LCD_SPIx_MISO_SOURCE, LCD_SPIx_MUX_SEL);
#else
// LCD单独使用SPI,无需配置MISO,并且为硬件管理CS,
gpio_init_struct.gpio_pull = GPIO_PULL_UP;
gpio_init_struct.gpio_pins = LCD_SPIx_CS_GPIO_PIN;
gpio_init(LCD_SPIx_GPIO, &gpio_init_struct);
gpio_pin_mux_config(LCD_SPIx_GPIO, LCD_SPIx_CS_SOURCE, LCD_SPIx_MUX_SEL);
#endif
// =========================> 初始化其余信号线的GPIO <=========================
crm_periph_clock_enable(LCD_GPIOPORT_CRM, TRUE);
gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;
gpio_init_struct.gpio_mode = GPIO_MODE_OUTPUT;
gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
#if LCD_SHARE_SPI_WITH_TOUCH_IC == 1
// LCD和触摸IC共用SPI,CS由MCU直接控制GPIO实现
gpio_init_struct.gpio_pins = LCD_PINS_RST | LCD_PINS_DC | LCD_PINS_BLK | LCD_PINS_CS;
gpio_init(LCD_GPIOPORT, &gpio_init_struct);
gpio_bits_set(LCD_GPIOPORT, LCD_PINS_RST | LCD_PINS_DC | LCD_PINS_BLK | LCD_PINS_CS);
#else
// LCD单独使用SPI,CS已经由硬件实现,此处不作配置
#error "Hardware CS is is a piece of shit, DO NOT try!"
gpio_init_struct.gpio_pins = LCD_PINS_RST | LCD_PINS_DC | LCD_PINS_BLK;
gpio_init(LCD_GPIOPORT, &gpio_init_struct);
gpio_bits_set(LCD_GPIOPORT, LCD_PINS_RST | LCD_PINS_DC | LCD_PINS_BLK);
#endif
#else
// 使用软件SPI 且必定共用一个SPI
crm_periph_clock_enable(LCD_GPIOPORT_CRM, TRUE);
gpio_init_type gpio_init_struct;
gpio_default_para_init(&gpio_init_struct);
gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;
gpio_init_struct.gpio_mode = GPIO_MODE_OUTPUT;
gpio_init_struct.gpio_pins = LCD_PINS_SCLK | LCD_PINS_MOSI | LCD_PINS_RST | LCD_PINS_DC | LCD_PINS_BLK | LCD_PINS_CS;
gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
gpio_init(LCD_GPIOPORT, &gpio_init_struct);
gpio_bits_set(LCD_GPIOPORT, LCD_PINS_SCLK | LCD_PINS_MOSI | LCD_PINS_RST | LCD_PINS_DC | LCD_PINS_BLK | LCD_PINS_CS);
#endif
}
#if LCD_USE_HARDWARE_SPI == 1 && LCD_HARDWARE_SPI_USE_DMA == 0
/**
* @brief spi configuration.
* @param none
* @retval none
*/
void LCD_SPIx_init(void)
{
spi_init_type spi_init_struct;
spi_i2s_reset(LCD_SPIx);
crm_periph_clock_enable(LCD_SPIx_CRM, TRUE);
spi_default_para_init(&spi_init_struct);
spi_init_struct.master_slave_mode = SPI_MODE_MASTER; // 设置SPI工作模式:主机模式
spi_init_struct.mclk_freq_division = LCD_SPI_SPEED; // 288M / 8 = 36M
spi_init_struct.first_bit_transmission = SPI_FIRST_BIT_MSB; // 数据传输高位先行
spi_init_struct.frame_bit_num = SPI_FRAME_8BIT; // 设置SPI数据大小:8位帧结构
spi_init_struct.clock_polarity = SPI_CLOCK_POLARITY_HIGH; // 串行同步时钟空闲时SCLK位高电平
#if LCD_SHARE_SPI_WITH_TOUCH_IC == 1
spi_init_struct.clock_phase = SPI_CLOCK_PHASE_2EDGE; // 串行同步时钟空第二个时钟沿捕获
spi_init_struct.transmission_mode = SPI_TRANSMIT_FULL_DUPLEX; // 要接收触模信息所以是全双工
spi_init_struct.cs_mode_selection = SPI_CS_SOFTWARE_MODE; // 片选信号由软件管理
#else
spi_init_struct.clock_phase = SPI_CLOCK_PHASE_2EDGE; // 串行同步时钟空第二个时钟沿捕获
spi_init_struct.transmission_mode = SPI_TRANSMIT_HALF_DUPLEX_TX; // LCD只接收信息所以是单工
spi_init_struct.cs_mode_selection = SPI_CS_HARDWARE_MODE; // 片选信号由硬件管理
#error "Hardware CS is is a piece of shit, DO NOT try!"
spi_hardware_cs_output_enable(LCD_SPIx, TRUE);
#endif
spi_init(LCD_SPIx, &spi_init_struct);
spi_enable(LCD_SPIx, TRUE);
}
#elif LCD_HARDWARE_SPI_USE_DMA == 1
// uint16_t LCD_SPIx_TX_buffer[LCD_SPI_TX_BUFFER_SIZE];
// #if (LCD_SHARE_SPI_WITH_TOUCH_IC == 1)
// uint16_t LCD_SPIx_RX_buffer[LCD_SPI_RX_BUFFER_SIZE];
// #endif
void LCD_SPIx_init(void)
{
dma_init_type dma_init_struct;
spi_init_type spi_init_struct;
/************** DMA 配置 *****************/
/* LCD_SPI_TX_DMAx_CHy configuration */
// 时钟配置
crm_periph_clock_enable(LCD_SPI_TX_DMAx_CRM_CLOCK, TRUE);
dma_reset(LCD_SPI_TX_DMAx_CHy);
// DMA MUX 通道配置
dmamux_enable(LCD_SPI_TX_DMAx, TRUE);
dmamux_init(LCD_SPI_TX_DMAxMUX_CHy, LCD_SPI_TX_DMAMUX_REQ_ID);
// DMA 通道配置
dma_default_para_init(&dma_init_struct);
dma_init_struct.buffer_size = LCD_SPI_TX_BUFFER_SIZE;
dma_init_struct.direction = DMA_DIR_MEMORY_TO_PERIPHERAL;
// dma_init_struct.memory_base_addr = (uint32_t)LCD_SPIx_TX_buffer;
dma_init_struct.memory_data_width = DMA_MEMORY_DATA_WIDTH_HALFWORD;
dma_init_struct.memory_inc_enable = TRUE;
dma_init_struct.peripheral_base_addr = (uint32_t)&(LCD_SPIx->dt);
// dma_init_struct.peripheral_data_width = DMA_PERIPHERAL_DATA_WIDTH_BYTE; // 如果 MWIDTH 是16bit,但 PWIDTH 是8bit,方向是M2P,那么DMA只会搬运存MEM中每个半字的低8位,也即低地址处的字节。
dma_init_struct.peripheral_data_width = DMA_PERIPHERAL_DATA_WIDTH_HALFWORD;
dma_init_struct.peripheral_inc_enable = FALSE;
dma_init_struct.priority = DMA_PRIORITY_MEDIUM;
dma_init_struct.loop_mode_enable = FALSE; //是否循环模式
dma_init(LCD_SPI_TX_DMAx_CHy, &dma_init_struct);
#if (LCD_SHARE_SPI_WITH_TOUCH_IC == 1)
/* LCD_SPI_TX_DMAx_CHy configuration */
// DMA MUX 通道配置
dmamux_init(LCD_SPI_RX_DMAxMUX_CHy, LCD_SPI_RX_DMAMUX_REQ_ID);
// DMA 通道配置
dma_default_para_init(&dma_init_struct);
dma_init_struct.buffer_size = LCD_SPI_RX_BUFFER_SIZE;
dma_init_struct.direction = DMA_DIR_PERIPHERAL_TO_MEMORY;
// dma_init_struct.memory_base_addr = (uint32_t)LCD_SPIx_RX_buffer;
dma_init_struct.memory_data_width = DMA_MEMORY_DATA_WIDTH_BYTE;
dma_init_struct.memory_inc_enable = TRUE;
dma_init_struct.peripheral_base_addr = (uint32_t)&(LCD_SPIx->dt);
dma_init_struct.peripheral_data_width = DMA_PERIPHERAL_DATA_WIDTH_BYTE;
dma_init_struct.peripheral_inc_enable = FALSE;
dma_init_struct.priority = DMA_PRIORITY_MEDIUM;
dma_init_struct.loop_mode_enable = FALSE; //是否循环模式
dma_init(LCD_SPI_RX_DMAx_CHy, &dma_init_struct);
#else
// 不需要 RX buffer 和 RX DMA
#endif
/************** SPI 配置 *****************/
crm_periph_clock_enable(LCD_SPIx_CRM, TRUE);
spi_default_para_init(&spi_init_struct);
#if (LCD_SHARE_SPI_WITH_TOUCH_IC == 1)
spi_init_struct.transmission_mode = SPI_TRANSMIT_FULL_DUPLEX;
#else
spi_init_struct.transmission_mode = SPI_TRANSMIT_HALF_DUPLEX_TX;
#endif
spi_init_struct.master_slave_mode = SPI_MODE_MASTER;
spi_init_struct.mclk_freq_division = LCD_SPI_SPEED;
spi_init_struct.first_bit_transmission = SPI_FIRST_BIT_MSB;
spi_init_struct.frame_bit_num = SPI_FRAME_16BIT;
spi_init_struct.clock_polarity = SPI_CLOCK_POLARITY_HIGH;
spi_init_struct.clock_phase = SPI_CLOCK_PHASE_2EDGE;
spi_init_struct.cs_mode_selection = SPI_CS_SOFTWARE_MODE;
spi_init(LCD_SPIx, &spi_init_struct);
spi_i2s_dma_transmitter_enable(LCD_SPIx, TRUE);
// spi_i2s_dma_receiver_enable(LCD_SPIx,TRUE);
spi_enable(LCD_SPIx, TRUE);
#if (LCD_HARDWARE_SPI_TX_DMA_USE_INT == 1)
//中断配置
nvic_priority_group_config(NVIC_PRIORITY_GROUP_4);
/* enable transfer full data intterrupt 当数据全部发送完毕后产生中断*/
dma_interrupt_enable(LCD_SPI_TX_DMAx_CHy, DMA_FDT_INT, TRUE);
/* LCD_SPI_TX_DMAx_CHy interrupt nvic init */
nvic_irq_enable(LCD_SPI_TX_DMAx_CHy_IRQn, 5, 0);
/* config flexible dma for LCD_SPI_TX_DMAxMUX_CHy 配置弹性映射 */
dma_flexible_config(LCD_SPI_TX_DMAx, LCD_SPI_TX_DMAxMUX_CHy, LCD_SPI_TX_DMAMUX_REQ_ID);
#if (LCD_SHARE_SPI_WITH_TOUCH_IC == 1)
/* enable transfer full data intterrupt 当数据全部发送完毕后产生中断*/
dma_interrupt_enable(LCD_SPI_RX_DMAx_CHy, DMA_FDT_INT, TRUE);
/* LCD_SPI_RX_DMAx_CHy interrupt nvic init */
nvic_irq_enable(LCD_SPI_RX_DMAx_CHy_IRQn, 3, 0);
/* config flexible dma for LCD_SPI_RX_DMAxMUX_CHy 配置弹性映射 */
dma_flexible_config(LCD_SPI_TX_DMAx, LCD_SPI_RX_DMAxMUX_CHy, LCD_SPI_RX_DMAMUX_REQ_ID);
#endif
#endif
}
/**
* @brief data transfer to LCD_SPIx using DMA
* @param buf : buffer address
* @param size : this uint32_t value should be less than 65535. Or otherwise size%0xFFFF is what finally takes effect.
* @retval none
*/
void LCD_SPI_data_send_use_DMA(const void *buf, uint32_t size)
{
#if (LCD_HARDWARE_SPI_TX_DMA_USE_INT == 1)
// 在使用 SPI TX DMA 中断的情况下,如果此时DMA还在搬运,那么应该等DMA中断服务程序结束后再向DMA发送新的buf和size,否则DMA传输中断,屏幕收到数据不完整
if (LCD_SPI_TX_DMAx_CHy->ctrl_bit.chen == 1)
{
__WFI();
}
#endif
// 确保 LCD_SPIx 为16bit传输模式 且 频率为初始设定频率
if(LCD_SPIx->ctrl1_bit.fbn == SPI_FRAME_8BIT || LCD_SPIx->ctrl1_bit.mdiv_l != LCD_SPI_SPEED)
{
// 需要把SPI改为16bit模式 或 把频率改为设定频率
// DMA搬运完成,但此时SPI还在发送,等待SPI通信忙结束
while (spi_i2s_flag_get(LCD_SPIx, SPI_I2S_TDBE_FLAG) == RESET && spi_i2s_flag_get(LCD_SPIx, SPI_I2S_BF_FLAG) == SET) // Tx dt buf空 清零,说明还有数据在传输
{
// 等待主机数据发送完毕
}
while (spi_i2s_flag_get(LCD_SPIx, SPI_I2S_RDBF_FLAG) == RESET && spi_i2s_flag_get(LCD_SPIx, SPI_I2S_BF_FLAG) == SET) // Rx dt buf满 清零,说明还有数据未发送
{
// 等待主机数据接收完毕
}
spi_i2s_data_receive(LCD_SPIx);
// SPI改为16bit模式
spi_frame_bit_num_set(LCD_SPIx, SPI_FRAME_16BIT);
// SPI时钟分频改为初始设定值
LCD_SPIx->ctrl1_bit.mdiv_l = LCD_SPI_SPEED;
}
// 确保对应通道的 CHEN 位为 0,否则无法写入
dma_channel_enable(LCD_SPI_TX_DMAx_CHy, FALSE);
LCD_SPI_TX_DMAx_CHy->maddr = (uint32_t)buf;
LCD_SPI_TX_DMAx_CHy->dtcnt_bit.cnt = size; // 注意 dtcnt 是一个32位寄存器,但只有低16位是可用的
// 开启DMA搬运前拉低CS
LCD_CS_CLR();
// DMA开始搬运,随即SPI开始传输
dma_channel_enable(LCD_SPI_TX_DMAx_CHy, TRUE);
// =============================> 接收通道的DMA配置 <=============================
#if (LCD_SHARE_SPI_WITH_TOUCH_IC == 1)
dma_channel_enable(LCD_SPI_RX_DMAx_CHy, FALSE);
// 确保对应通道的 CHEN 位为 0,否则无法写入
// LCD_SPI_RX_DMAx_CHy->maddr = (uint32_t)LCD_SPIx_RX_buffer;
LCD_SPI_RX_DMAx_CHy->dtcnt_bit.cnt = size; // 注意 dtcnt 是一个32位寄存器,但只有低16位是可用的
dma_channel_enable(LCD_SPI_RX_DMAx_CHy, TRUE); // (在轮询结束或在中断服务程序的最后会关掉)
#endif
#if LCD_HARDWARE_SPI_TX_DMA_USE_INT == 1
// 将在中断服务程序中完成 标志位清除、等待SPI发送结束并读一次数据(如果是全双工)、关闭DMA通道、拉高CS 的工作
#else
/*** 若不开DMA完成中断,则轮询DMAx_FDTy标志位 ***/
// while(spi_i2s_flag_get(LCD_SPIx,SPI_I2S_BF_FLAG) == SET){};
// while(dma_flag_get(DMA2_FDT1_FLAG) == RESET)
// {
// };
// dma_flag_clear(DMA2_FDT1_FLAG);
// LCD_CS_SET();
// // spi_i2s_dma_transmitter_enable(LCD_SPIx, FALSE);
// dma_channel_enable(LCD_SPI_TX_DMAx_CHy, FALSE);
// spi_i2s_data_receive(LCD_SPIx);
// // lv_disp_flush_complete();
#endif
}
#if LCD_HARDWARE_SPI_TX_DMA_USE_INT == 1
// 将在中断服务程序中完成 标志位清除、等待SPI发送结束并读一次数据(如果是全双工)、关闭DMA通道、拉高CS 的工作
void LCD_SPI_TX_DMAx_CHy_IRQHandler(void)
{
/*DMA发送完成中断*/
if (dma_flag_get(LCD_SPI_TX_DMAx_FDTy_FLAG) == SET)
{
dma_channel_enable(LCD_SPI_TX_DMAx_CHy, FALSE);
// DMA搬运完成,但此时SPI还在发送
while (spi_i2s_flag_get(LCD_SPIx, SPI_I2S_TDBE_FLAG) == RESET || spi_i2s_flag_get(LCD_SPIx, SPI_I2S_RDBF_FLAG) == SET || spi_i2s_flag_get(LCD_SPIx, SPI_I2S_BF_FLAG) == SET)// Tx dt buf非空 或者 Rx dt buf满 或者 通信忙
{
spi_i2s_data_receive(LCD_SPIx);
}
while (spi_i2s_flag_get(LCD_SPIx, SPI_I2S_ROERR_FLAG) == SET || spi_i2s_flag_get(LCD_SPIx, SPI_I2S_BF_FLAG) == SET)// Tx dt buf非空 或者 Rx dt buf满 或者 通信忙
{
spi_i2s_data_receive(LCD_SPIx);
}
spi_i2s_data_receive(LCD_SPIx); // !!!血泪教训!!全双工下DMA发完SPI后一定要读一次!!!
LCD_CS_SET();
// // DMA改回默认设置:MEM地址递增 (刷纯色的时候会改成FALSE)
// LCD_SPI_TX_DMAx_CHy->ctrl_bit.mincm = TRUE;
dma_flag_clear(LCD_SPI_TX_DMAx_FDTy_FLAG);
}
}
#endif
void LCD_SPI_RX_DMAx_CHy_IRQHandler(void)
{
/*DMA发送完成中断*/
if (dma_flag_get(LCD_SPI_RX_DMAx_FDTy_FLAG) == SET)
{
dma_flag_clear(LCD_SPI_RX_DMAx_FDTy_FLAG);
// LCD_SPI_RX_DMAx_CHy 将在发送数据的时候被再次使能
dma_channel_enable(LCD_SPI_RX_DMAx_CHy, FALSE);
}
}
#endif
/******************************************************************************
函数说明:LCD串行数据写入函数
入口数据:data 要写入的串行数据
返回值: 无
******************************************************************************/
static void LCD_SPI_write_bus(uint16_t data)
{
#if (LCD_USE_HARDWARE_SPI == 1)
// #if (LCD_HARDWARE_SPI_USE_DMA == 1)
// LCD_SPI_data_send_use_DMA(&data, 1);
// #else
LCD_CS_CLR();
while (spi_i2s_flag_get(LCD_SPIx, SPI_I2S_TDBE_FLAG) == RESET && spi_i2s_flag_get(LCD_SPIx, SPI_I2S_BF_FLAG) == SET) // Tx dt buf空 清零,说明还有数据在传输
{
// 等待主机数据发送完毕
}
spi_i2s_data_transmit(LCD_SPIx, data);
while (spi_i2s_flag_get(LCD_SPIx, SPI_I2S_RDBF_FLAG) == RESET && spi_i2s_flag_get(LCD_SPIx, SPI_I2S_BF_FLAG) == SET) // Rx dt buf满 清零,说明还有数据未发送
{
// 等待主机数据接收完毕
}
spi_i2s_data_receive(LCD_SPIx);// 即使实际上没有用到 MISO,也需要读一次DT寄存器
// while (spi_i2s_flag_get(LCD_SPIx, SPI_I2S_TDBE_FLAG) == RESET && spi_i2s_flag_get(LCD_SPIx, SPI_I2S_BF_FLAG) == SET) // Tx dt buf空 清零,说明还有数据在传输
// {
// // 等待主机数据发送完毕
// // 他说他自己发完了,其实他发完个屁
// }
LCD_CS_SET();
// #endif
#else
uint8_t i;
LCD_CS_CLR();
for (i = 0; i < 8; i++) {
LCD_SCLK_CLR();
if (data & 0x80) {
LCD_MOSI_SET();
} else {
LCD_MOSI_CLR();
}
LCD_SCLK_SET();
data <<= 1;
}
LCD_CS_SET();
#endif
}
/******************************************************************************
函数说明:LCD写入数据
入口数据:data 写入的数据
返回值: 无
******************************************************************************/
void LCD_write_byte(uint8_t data_byte)
{
// 确保 LCD_SPIx 为8bit传输模式
if(LCD_SPIx->ctrl1_bit.fbn == SPI_FRAME_16BIT)
{
// 需要把SPI改为16bit模式
// 等待SPI通信忙结束
while (spi_i2s_flag_get(LCD_SPIx, SPI_I2S_TDBE_FLAG) == RESET && spi_i2s_flag_get(LCD_SPIx, SPI_I2S_BF_FLAG) == SET) // Tx dt buf空 清零,说明还有数据在传输
{
// 等待主机数据发送完毕
}
while (spi_i2s_flag_get(LCD_SPIx, SPI_I2S_RDBF_FLAG) == RESET && spi_i2s_flag_get(LCD_SPIx, SPI_I2S_BF_FLAG) == SET) // Rx dt buf满 清零,说明还有数据未发送
{
// 等待主机数据接收完毕
}
spi_i2s_data_receive(LCD_SPIx);
// SPI改为8bit模式
spi_frame_bit_num_set(LCD_SPIx, SPI_FRAME_8BIT);
}
LCD_SPI_write_bus(data_byte);
}
/******************************************************************************
函数说明:LCD写入数据
入口数据:data 写入的数据
返回值: 无
******************************************************************************/
void LCD_write_half(uint16_t data_half)
{
// 确保 LCD_SPIx 为16bit传输模式
if(LCD_SPIx->ctrl1_bit.fbn == SPI_FRAME_8BIT)
{
// 需要把SPI改为16bit模式
// 等待SPI通信忙结束
while (spi_i2s_flag_get(LCD_SPIx, SPI_I2S_TDBE_FLAG) == RESET && spi_i2s_flag_get(LCD_SPIx, SPI_I2S_BF_FLAG) == SET) // Tx dt buf空 清零,说明还有数据在传输
{
// 等待主机数据发送完毕
}
while (spi_i2s_flag_get(LCD_SPIx, SPI_I2S_RDBF_FLAG) == RESET && spi_i2s_flag_get(LCD_SPIx, SPI_I2S_BF_FLAG) == SET) // Rx dt buf满 清零,说明还有数据未发送
{
// 等待主机数据接收完毕
}
spi_i2s_data_receive(LCD_SPIx);
// SPI改为16bit模式
spi_frame_bit_num_set(LCD_SPIx, SPI_FRAME_16BIT);
}
LCD_SPI_write_bus(data_half);
}
/******************************************************************************
函数说明:LCD写入命令
入口数据:data 写入的命令
返回值: 无
******************************************************************************/
void LCD_write_cmd(uint8_t data_byte)
{
#if (LCD_HARDWARE_SPI_TX_DMA_USE_INT == 1)
// 在使用 SPI TX DMA 中断的情况下,如果此时DMA还在搬运,那么应该等DMA中断服务程序结束,并且SPI当前帧发送结束后再把DC拉低,否则将导致cmd数据和data数据错位,屏幕卡死
if (LCD_SPI_TX_DMAx_CHy->ctrl_bit.chen == 1)
{
__WFI();
}
// DMA搬运完成,但此时SPI还在发送
while (spi_i2s_flag_get(LCD_SPIx, SPI_I2S_TDBE_FLAG) == RESET && spi_i2s_flag_get(LCD_SPIx, SPI_I2S_BF_FLAG) == SET) // Tx dt buf空 清零,说明还有数据在传输
{
// 等待主机数据发送完毕
}
while (spi_i2s_flag_get(LCD_SPIx, SPI_I2S_RDBF_FLAG) == RESET && spi_i2s_flag_get(LCD_SPIx, SPI_I2S_BF_FLAG) == SET) // Rx dt buf满 清零,说明还有数据未发送
{
// 等待主机数据接收完毕
}
spi_i2s_data_receive(LCD_SPIx);
#endif
LCD_DC_CLR(); //写命令
LCD_write_byte(data_byte);
LCD_DC_SET(); //写数据
}
/**
* @brief 设置区域的左上角(起始)和右下角(结束)
* @param x1 : 列起始坐标
* @param y1 : 行起始坐标
* @param x2 : 列结束坐标
* @param y2 : 行结束坐标
* @retval none
*/
void LCD_addr_set(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
{
// 列地址设置
LCD_write_cmd(0x2a);
LCD_write_half(x1);
LCD_write_half(x2);
// 行地址设置
LCD_write_cmd(0x2b);
LCD_write_half(y1);
LCD_write_half(y2);
// 准备接收数据
LCD_write_cmd(0x2c);
}
/******************************************************************************
函数说明:LCD初始化函数
入口数据:无
返回值: 无
******************************************************************************/
void LCD_init(void)
{
#if CHIP_USE_ST7789 == 0 //初始化ILI9341
{
LCD_RES_CLR(); //复位
delay_ms(100);
LCD_RES_SET();
delay_ms(100);
LCD_BLK_SET(); //打开背光
delay_ms(100);
//************* Start Initial Sequence **********//
LCD_write_cmd(0x11); // Sleep out
delay_ms(120); // Delay 120ms
//************* Start Initial Sequence **********//
LCD_write_cmd(0xCF);
LCD_write_byte(0x00);
LCD_write_byte(0xC1);
LCD_write_byte(0X30);
LCD_write_cmd(0xED);
LCD_write_byte(0x64);
LCD_write_byte(0x03);
LCD_write_byte(0X12);
LCD_write_byte(0X81);
LCD_write_cmd(0xE8);
LCD_write_byte(0x85);
LCD_write_byte(0x00);
LCD_write_byte(0x79);
LCD_write_cmd(0xCB);
LCD_write_byte(0x39);
LCD_write_byte(0x2C);
LCD_write_byte(0x00);
LCD_write_byte(0x34);
LCD_write_byte(0x02);
LCD_write_cmd(0xF7);
LCD_write_byte(0x20);
LCD_write_cmd(0xEA);
LCD_write_byte(0x00);
LCD_write_byte(0x00);
LCD_write_cmd(0xC0); // Power control
LCD_write_byte(0x1D); // VRH[5:0]
LCD_write_cmd(0xC1); // Power control
LCD_write_byte(0x12); // SAP[2:0];BT[3:0]
LCD_write_cmd(0xC5); // VCM control
LCD_write_byte(0x33);
LCD_write_byte(0x3F);
LCD_write_cmd(0xC7); // VCM control
LCD_write_byte(0x92);
LCD_write_cmd(0x3A); // Memory Access Control
LCD_write_byte(0x55);
LCD_write_cmd(0x36); // Memory Access Control
if (USE_HORIZONTAL == 0)
LCD_write_byte(0x08);
else if (USE_HORIZONTAL == 1)
LCD_write_byte(0xC8);
else if (USE_HORIZONTAL == 2)
LCD_write_byte(0x78);
else
LCD_write_byte(0xA8);
LCD_write_cmd(0xB1);
LCD_write_byte(0x00);
LCD_write_byte(0x12);
LCD_write_cmd(0xB6); // Display Function Control
LCD_write_byte(0x0A);
LCD_write_byte(0xA2);
LCD_write_cmd(0x44);
LCD_write_byte(0x02);
LCD_write_cmd(0xF2); // 3Gamma Function Disable
LCD_write_byte(0x00);
LCD_write_cmd(0x26); // Gamma curve selected
LCD_write_byte(0x01);
LCD_write_cmd(0xE0); // Set Gamma
LCD_write_byte(0x0F);
LCD_write_byte(0x22);
LCD_write_byte(0x1C);
LCD_write_byte(0x1B);
LCD_write_byte(0x08);
LCD_write_byte(0x0F);
LCD_write_byte(0x48);
LCD_write_byte(0xB8);
LCD_write_byte(0x34);
LCD_write_byte(0x05);
LCD_write_byte(0x0C);
LCD_write_byte(0x09);
LCD_write_byte(0x0F);
LCD_write_byte(0x07);
LCD_write_byte(0x00);
LCD_write_cmd(0XE1); // Set Gamma
LCD_write_byte(0x00);
LCD_write_byte(0x23);
LCD_write_byte(0x24);
LCD_write_byte(0x07);
LCD_write_byte(0x10);
LCD_write_byte(0x07);
LCD_write_byte(0x38);
LCD_write_byte(0x47);
LCD_write_byte(0x4B);
LCD_write_byte(0x0A);
LCD_write_byte(0x13);
LCD_write_byte(0x06);
LCD_write_byte(0x30);
LCD_write_byte(0x38);
LCD_write_byte(0x0F);
LCD_write_cmd(0x29); // Display on
}
#else //初始化ST7789
{
LCD_RES_CLR(); //复位
delay_ms(100);
LCD_RES_SET();
delay_ms(100);
LCD_BLK_SET(); //打开背光
delay_ms(500);
LCD_write_cmd(0x11);
delay_ms(120); // Delay 120ms
//************* Start Initial Sequence **********//
//------------------------------display and color format setting--------------------------------//
LCD_write_cmd(0X36); // Memory Access Control
if (USE_HORIZONTAL == 0)
LCD_write_byte(0x00);
else if (USE_HORIZONTAL == 1)
LCD_write_byte(0xC0);
else if (USE_HORIZONTAL == 2)
LCD_write_byte(0x70);
else
LCD_write_byte(0xA0);
LCD_write_cmd(0X3A);
LCD_write_byte(0X05);
//--------------------------------ST7789S Frame rate setting-------------------------
LCD_write_cmd(0xb2);
LCD_write_byte(0x0c);
LCD_write_byte(0x0c);
LCD_write_byte(0x00);
LCD_write_byte(0x33);
LCD_write_byte(0x33);
LCD_write_cmd(0xb7);
LCD_write_byte(0x35);
//---------------------------------ST7789S Power setting-----------------------------
LCD_write_cmd(0xbb);
LCD_write_byte(0x35);
LCD_write_cmd(0xc0);
LCD_write_byte(0x2c);
LCD_write_cmd(0xc2);
LCD_write_byte(0x01);
LCD_write_cmd(0xc3);
LCD_write_byte(0x13);
LCD_write_cmd(0xc4);
LCD_write_byte(0x20);
LCD_write_cmd(0xc6);
LCD_write_byte(0x0f);
LCD_write_cmd(0xca);
LCD_write_byte(0x0f);
LCD_write_cmd(0xc8);
LCD_write_byte(0x08);
LCD_write_cmd(0x55);
LCD_write_byte(0x90);
LCD_write_cmd(0xd0);
LCD_write_byte(0xa4);
LCD_write_byte(0xa1);
//--------------------------------ST7789S gamma setting------------------------------
LCD_write_cmd(0xe0);
LCD_write_byte(0xd0);
LCD_write_byte(0x00);
LCD_write_byte(0x06);
LCD_write_byte(0x09);
LCD_write_byte(0x0b);
LCD_write_byte(0x2a);
LCD_write_byte(0x3c);
LCD_write_byte(0x55);
LCD_write_byte(0x4b);
LCD_write_byte(0x08);
LCD_write_byte(0x16);
LCD_write_byte(0x14);
LCD_write_byte(0x19);
LCD_write_byte(0x20);
LCD_write_cmd(0xe1);
LCD_write_byte(0xd0);
LCD_write_byte(0x00);
LCD_write_byte(0x06);
LCD_write_byte(0x09);
LCD_write_byte(0x0b);
LCD_write_byte(0x29);
LCD_write_byte(0x36);
LCD_write_byte(0x54);
LCD_write_byte(0x4b);
LCD_write_byte(0x0d);
LCD_write_byte(0x16);
LCD_write_byte(0x14);
LCD_write_byte(0x21);
LCD_write_byte(0x20);
LCD_write_cmd(0x29);
}
#endif
}
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __ST7789_H
#define __ST7789_H
#ifdef __cplusplus
extern "C" {
#endif
/* includes ------------------------------------------------------------------*/
#include "at32f435_437.h"
#include "systick.h"
/* micro and typedef ---------------------------------------------------------*/
#define USE_HORIZONTAL 0 //设置横屏或者竖屏显示 0或1为竖屏 2或3为横屏
#define CHIP_USE_ST7789 1 //设置芯片初始化 0为ILI9341 1为ST7789
#if USE_HORIZONTAL == 0 || USE_HORIZONTAL == 1
#define LCD_W 240
#define LCD_H 320
#else
#define LCD_W 320
#define LCD_H 240
#endif
//-----------------LCD端口定义----------------
/* 请按照以下逻辑修改宏定义:
1,使用硬件SPI?否则使用软件模拟时序。
若是,下一步;否则将配置为和触摸IC共用一个软件SPI。
2,判断触摸IC是否使用硬件SPI?
若是,下一步;否则LCD使用硬件SPI(本文件负责配置)而触摸IC使用软件SPI(不在本文件中配置)。
3,和触摸IC共用同一个SPI?否则使用两个硬件SPI。
若是,请指定SPIx,SPIx将设置成全双工模式,软件管理CS,触摸IC也使用此SPI,并下一步;
否则,请指定两个完全独立的SPI端口分别驱动LCD和触摸IC,硬件管理CS,下一步。
4,使用DMA?
*/
#define LCD_USE_HARDWARE_SPI 1
#define XPT2046_USE_HARDWARE_SPI 1
#if LCD_USE_HARDWARE_SPI == 1
#include "at32f435_437_spi.h"
#if LCD_USE_HARDWARE_SPI == 1 && XPT2046_USE_HARDWARE_SPI == 1
#define LCD_SHARE_SPI_WITH_TOUCH_IC 1 // 见 https://blog.csdn.net/m0_46882426/article/details/127568586?spm=1001.2014.3001.5501 ,共用SPI并软件管理CS是最优解,所以不要改动此宏
#endif
#define LCD_HARDWARE_SPI_USE_DMA 1
#define LCD_HARDWARE_SPI_TX_DMA_USE_INT 1
// SPI 通信速度选择
#define LCD_SPI_USE_SPEED_LEVEL 1
// SPI 端口选择
#if LCD_SHARE_SPI_WITH_TOUCH_IC == 1
// 共用 LCD_SHARE_SPIx_NUM (该宏仅表示SPI序号),且使用软件管理CS
#define LCD_SHARE_SPIx_NUM 2
#define LCD_USE_SPIx_NUM LCD_SHARE_SPIx_NUM
#define XPT2046_USE_SPIx_NUM LCD_SHARE_SPIx_NUM
#else
// 使用独立的两个SPI,且使用硬件管理CS
#define LCD_USE_SPIx_NUM 1
#define XPT2046_USE_SPIx_NUM 2
#endif
// 非SPI控制 GPIO定义
#define LCD_GPIOPORT GPIOA
#define LCD_GPIOPORT_CRM CRM_GPIOA_PERIPH_CLOCK
#define LCD_PINS_RST GPIO_PINS_0
#define LCD_PINS_DC GPIO_PINS_1
#define LCD_PINS_BLK GPIO_PINS_8
#define LCD_PINS_CS GPIO_PINS_4
// DMA 控制器定义
#if LCD_HARDWARE_SPI_USE_DMA == 1
#include "at32f435_437_dma.h"
#define LCD_SPI_TX_DMAx DMA2
#define LCD_SPI_TX_DMAx_CRM_CLOCK CRM_DMA2_PERIPH_CLOCK
#define LCD_SPI_TX_DMAx_CHy DMA2_CHANNEL1
#define LCD_SPI_TX_DMAxMUX_CHy DMA2MUX_CHANNEL1
#define LCD_SPI_TX_DMAMUX_REQ_ID DMAMUX_DMAREQ_ID_SPI2_TX
#define LCD_SPI_TX_DMAx_CHy_IRQn DMA2_Channel1_IRQn
#define LCD_SPI_TX_DMAx_CHy_IRQHandler DMA2_Channel1_IRQHandler
#define LCD_SPI_TX_DMAx_FDTy_FLAG DMA2_FDT1_FLAG
#define LCD_SPI_RX_DMAx_CHy DMA2_CHANNEL2
#define LCD_SPI_RX_DMAxMUX_CHy DMA2MUX_CHANNEL2
#define LCD_SPI_RX_DMAMUX_REQ_ID DMAMUX_DMAREQ_ID_SPI2_RX
#define LCD_SPI_RX_DMAx_CHy_IRQn DMA2_Channel2_IRQn
#define LCD_SPI_RX_DMAx_CHy_IRQHandler DMA2_Channel2_IRQHandler
#define LCD_SPI_RX_DMAx_FDTy_FLAG DMA2_FDT2_FLAG
// 定义 SPI 数据发送缓冲区长度。LCD_SPI_TX_DMAx_CHy 负责将该缓冲区内的数据搬运至 LCD_SPIx
#define LCD_SPI_TX_BUFFER_SIZE 65535 // 设置宽度为半字后,DMA一次最多连续搬运65545个半字
#define LCD_SPI_RX_BUFFER_SIZE 1024/2
#endif
// SPI 控制端口自动定义
#if LCD_USE_SPIx_NUM == 1 // DO NOT MODIFY!
#define LCD_SPIx SPI1
// SPI1 位于 GPIOA 的 MUX5
#define LCD_SPIx_CRM CRM_SPI1_PERIPH_CLOCK
#define LCD_SPIx_GPIO GPIOA
#define LCD_SPIx_GPIO_CRM CRM_GPIOA_PERIPH_CLOCK
#define LCD_SPIx_MUX_SEL GPIO_MUX_5
#define LCD_SPIx_CS_GPIO_PIN GPIO_PINS_4
#define LCD_SPIx_SCLK_GPIO_PIN GPIO_PINS_5
#define LCD_SPIx_MISO_GPIO_PIN GPIO_PINS_6
#define LCD_SPIx_MOSI_GPIO_PIN GPIO_PINS_7
#define LCD_SPIx_CS_SOURCE GPIO_PINS_SOURCE4
#define LCD_SPIx_SCLK_SOURCE GPIO_PINS_SOURCE5
#define LCD_SPIx_MISO_SOURCE GPIO_PINS_SOURCE6
#define LCD_SPIx_MOSI_SOURCE GPIO_PINS_SOURCE7
#define LCD_SPIx_EXT_IQRn SPI1_IRQn
#define LCD_SPIx_EXT_IQRHandler SPI1_IRQHandler
#elif LCD_USE_SPIx_NUM == 2 // DO NOT MODIFY!
#define LCD_SPIx SPI2
// SPI2 位于 GPIOA 的 MUX5
#define LCD_SPIx_CRM CRM_SPI2_PERIPH_CLOCK
#define LCD_SPIx_GPIO GPIOA
#define LCD_SPIx_GPIO_CRM CRM_GPIOA_PERIPH_CLOCK
#define LCD_SPIx_MUX_SEL GPIO_MUX_5
#define LCD_SPIx_SCLK_GPIO_PIN GPIO_PINS_9
#define LCD_SPIx_MOSI_GPIO_PIN GPIO_PINS_10
#define LCD_SPIx_CS_GPIO_PIN GPIO_PINS_11
#define LCD_SPIx_MISO_GPIO_PIN GPIO_PINS_12
#define LCD_SPIx_SCLK_SOURCE GPIO_PINS_SOURCE9
#define LCD_SPIx_MOSI_SOURCE GPIO_PINS_SOURCE10
#define LCD_SPIx_CS_SOURCE GPIO_PINS_SOURCE11
#define LCD_SPIx_MISO_SOURCE GPIO_PINS_SOURCE12
#define LCD_SPIx_EXT_IQRn SPI2_I2S2EXT_IRQn
#else
#error the specified SPI (Macro "LCD_SPIx") is unknown!
#endif
#if LCD_SHARE_SPI_WITH_TOUCH_IC == 1
#define XPT2046_SPIx LCD_SPIx
#endif
#else
#define LCD_GPIOPORT GPIOA
#define LCD_GPIOPORT_CRM CRM_GPIOA_PERIPH_CLOCK
#define LCD_PINS_SCLK GPIO_PINS_5
#define LCD_PINS_MOSI GPIO_PINS_7
#define LCD_PINS_RST GPIO_PINS_0
#define LCD_PINS_DC GPIO_PINS_1
#define LCD_PINS_BLK GPIO_PINS_8
#define LCD_PINS_CS GPIO_PINS_4
#define LCD_SCLK_CLR() gpio_bits_reset(LCD_GPIOPORT, LCD_PINS_SCLK)
#define LCD_MOSI_CLR() gpio_bits_reset(LCD_GPIOPORT, LCD_PINS_MOSI)
#define LCD_CS_CLR() gpio_bits_reset(LCD_GPIOPORT, LCD_PINS_CS)
#define LCD_SCLK_SET() gpio_bits_set(LCD_GPIOPORT, LCD_PINS_SCLK)
#define LCD_MOSI_SET() gpio_bits_set(LCD_GPIOPORT, LCD_PINS_MOSI)
#define LCD_CS_SET() gpio_bits_set(LCD_GPIOPORT, LCD_PINS_CS)
#endif
#define LCD_RES_CLR() gpio_bits_reset(LCD_GPIOPORT, LCD_PINS_RST)
#define LCD_DC_CLR() gpio_bits_reset(LCD_GPIOPORT, LCD_PINS_DC)
#define LCD_BLK_CLR() gpio_bits_reset(LCD_GPIOPORT, LCD_PINS_BLK)
#define LCD_RES_SET() gpio_bits_set(LCD_GPIOPORT, LCD_PINS_RST)
#define LCD_DC_SET() gpio_bits_set(LCD_GPIOPORT, LCD_PINS_DC)
#define LCD_BLK_SET() gpio_bits_set(LCD_GPIOPORT, LCD_PINS_BLK)
#if LCD_SHARE_SPI_WITH_TOUCH_IC == 1
#define LCD_CS_CLR() gpio_bits_reset(LCD_GPIOPORT, LCD_PINS_CS)
#define LCD_CS_SET() gpio_bits_set(LCD_GPIOPORT, LCD_PINS_CS)
#else
// LCD_SPIx 的硬件管理CS将在 ST7789.c 中配置
// XPT2046_SPIx 的硬件管理CS将在 XPT2046.c 中配置
#endif
#if LCD_SPI_USE_SPEED_LEVEL == 1
#define LCD_SPI_SPEED SPI_MCLK_DIV_8 // 288M / 8 = 36M
#elif LCD_SPI_USE_SPEED_LEVEL == 2
#define LCD_SPI_SPEED SPI_MCLK_DIV_16 // 288M / 16 = 18M
#elif LCD_SPI_USE_SPEED_LEVEL == 3
#define LCD_SPI_SPEED SPI_MCLK_DIV_32 // 288M / 32 = 9M
#endif
/* function ------------------------------------------------------------------*/
void LCD_GPIO_init(void); // GPIO初始化
void LCD_SPIx_init(void); // SPI初始化
#if (LCD_HARDWARE_SPI_USE_DMA == 1)
void LCD_SPI_data_send_use_DMA(const void *buf, uint32_t size);
#endif
void LCD_write_half(uint16_t data);
void LCD_addr_set(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2);
void LCD_init(void);
void LCD_fill_color(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color);
#ifdef __cplusplus
}
#endif
#endif
/**
* @file lv_port_disp_templ.c
*
*/
/*Copy this file as "lv_port_disp.c" and set this value to "1" to enable content*/
#if 1
/*********************
* INCLUDES
*********************/
#include "lv_port_disp.h"
#include
#include "LCD/ST7789.h"
/*********************
* DEFINES
*********************/
#ifndef MY_DISP_HOR_RES
#define MY_DISP_HOR_RES 240
#endif
#ifndef MY_DISP_VER_RES
#define MY_DISP_VER_RES 320
#endif
/*-----------------------------
* Create a buffer for drawing
*----------------------------*/
/**
* LVGL requires a buffer where it internally draws the widgets.
* Later this buffer will passed to your display driver's `flush_cb` to copy its content to your display.
* The buffer has to be greater than 1 display row
*
* There are 3 buffering configurations:
* 1. Create ONE buffer:
* LVGL will draw the display's content here and writes it to your display
*
* 2. Create TWO buffer:
* LVGL will draw the display's content to a buffer and writes it your display.
* You should use DMA to write the buffer's content to the display.
* It will enable LVGL to draw the next part of the screen to the other buffer while
* the data is being sent form the first buffer. It makes rendering and flushing parallel.
*
* 3. Double buffering
* Set 2 screens sized buffers and set disp_drv.full_refresh = 1.
* This way LVGL will always provide the whole rendered screen in `flush_cb`
* and you only need to change the frame buffer's address.
*/
#define BUFFER_METHOD 2
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static void disp_init(void);
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p);
//static void gpu_fill(lv_disp_drv_t * disp_drv, lv_color_t * dest_buf, lv_coord_t dest_width,
// const lv_area_t * fill_area, lv_color_t color);
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
void lv_port_disp_init(void)
{
/*-------------------------
* Initialize your display
* -----------------------*/
disp_init();
/*-----------------------------
* Create a buffer for drawing
*----------------------------*/
#if (BUFFER_METHOD == 1)
/* Example for 1) */
static lv_disp_draw_buf_t draw_buf_dsc_1;
static lv_color_t buf_1[MY_DISP_HOR_RES * 10]; /*A buffer for 10 rows*/
lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, MY_DISP_HOR_RES * 10); /*Initialize the display buffer*/
#elif (BUFFER_METHOD == 2)
/* Example for 2) */
static lv_disp_draw_buf_t draw_buf_dsc_2;
static lv_color_t buf_2_1[MY_DISP_HOR_RES * 10]; /*A buffer for 10 rows*/
static lv_color_t buf_2_2[MY_DISP_HOR_RES * 10]; /*An other buffer for 10 rows*/
lv_disp_draw_buf_init(&draw_buf_dsc_2, buf_2_1, buf_2_2, MY_DISP_HOR_RES * 10); /*Initialize the display buffer*/
#elif (BUFFER_METHOD == 3)
/* Example for 3) also set disp_drv.full_refresh = 1 below*/
static lv_disp_draw_buf_t draw_buf_dsc_3;
static lv_color_t buf_3_1[MY_DISP_HOR_RES * MY_DISP_VER_RES]; /*A screen sized buffer*/
static lv_color_t buf_3_2[MY_DISP_HOR_RES * MY_DISP_VER_RES]; /*Another screen sized buffer*/
lv_disp_draw_buf_init(&draw_buf_dsc_3, buf_3_1, buf_3_2,
MY_DISP_VER_RES * MY_DISP_HOR_RES); /*Initialize the display buffer*/
#else
#error "the specified macro 'BUFFER_METHOD' is unknown!"
#endif
/*-----------------------------------
* Register the display in LVGL
*----------------------------------*/
static lv_disp_drv_t disp_drv; /*Descriptor of a display driver*/
lv_disp_drv_init(&disp_drv); /*Basic initialization*/
/*Set up the functions to access to your display*/
/*Set the resolution of the display*/
disp_drv.hor_res = MY_DISP_HOR_RES;
disp_drv.ver_res = MY_DISP_VER_RES;
/*Used to copy the buffer's content to the display*/
disp_drv.flush_cb = disp_flush;
/*Set a display buffer*/
#if (BUFFER_METHOD == 1)
disp_drv.draw_buf = &draw_buf_dsc_1;
#elif (BUFFER_METHOD == 2)
disp_drv.draw_buf = &draw_buf_dsc_2;
#else
disp_drv.draw_buf = &draw_buf_dsc_3;
#endif
#if (BUFFER_METHOD == 3)
/*Required for Example 3)*/
disp_drv.full_refresh = 1;
#endif
/* Fill a memory array with a color if you have GPU.
* Note that, in lv_conf.h you can enable GPUs that has built-in support in LVGL.
* But if you have a different GPU you can use with this callback.*/
//disp_drv.gpu_fill_cb = gpu_fill;
/*Finally register the driver*/
lv_disp_drv_register(&disp_drv);
}
/**********************
* STATIC FUNCTIONS
**********************/
/*Initialize your display and the required peripherals.*/
static void disp_init(void)
{
LCD_GPIO_init();
#if LCD_USE_HARDWARE_SPI == 1
LCD_SPIx_init();
#endif
// 注意在此之前确保已经初始化 GPIO 和 SPI
LCD_init();
}
volatile bool disp_flush_enabled = true;
/* Enable updating the screen (the flushing process) when disp_flush() is called by LVGL
*/
void disp_enable_update(void)
{
disp_flush_enabled = true;
}
/* Disable updating the screen (the flushing process) when disp_flush() is called by LVGL
*/
void disp_disable_update(void)
{
disp_flush_enabled = false;
}
/*Flush the content of the internal buffer the specific area on the display
*You can use DMA or any hardware acceleration to do this operation in the background but
*'lv_disp_flush_ready()' has to be called when finished.*/
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
if(disp_flush_enabled)
{
/*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/
// int32_t x;
// int32_t y;
// for(y = area->y1; y <= area->y2; y++) {
// for(x = area->x1; x <= area->x2; x++) {
// /*Put a pixel to the display. For example:*/
// /*put_px(x, y, *color_p)*/
// color_p++;
// }
// }
// 如果采用写点函数来做,刷屏会很慢,所以推荐的方式如下:
uint16_t x1, y1, x2, y2;
uint16_t total_half;
x1 = area->x1;
y1 = area->y1;
x2 = area->x2;
y2 = area->y2;
total_half = (x2 - x1 + 1) * (y2 - y1 + 1);
LCD_addr_set(x1, y1, x2, y2);
// st7789_cfg_dcx_set();
// st7789_cfg_spi_write((uint8_t*)color_p, total_half );
LCD_SPI_data_send_use_DMA((uint16_t *) color_p, total_half);
}
/*IMPORTANT!!!
*Inform the graphics library that you are ready with the flushing*/
lv_disp_flush_ready(disp_drv);
}
/*OPTIONAL: GPU INTERFACE*/
/*If your MCU has hardware accelerator (GPU) then you can use it to fill a memory with a color*/
//static void gpu_fill(lv_disp_drv_t * disp_drv, lv_color_t * dest_buf, lv_coord_t dest_width,
// const lv_area_t * fill_area, lv_color_t color)
//{
// /*It's an example code which should be done by your GPU*/
// int32_t x, y;
// dest_buf += dest_width * fill_area->y1; /*Go to the first line*/
//
// for(y = fill_area->y1; y <= fill_area->y2; y++) {
// for(x = fill_area->x1; x <= fill_area->x2; x++) {
// dest_buf[x] = color;
// }
// dest_buf+=dest_width; /*Go to the next line*/
// }
//}
#else /*Enable this file at the top*/
/*This dummy typedef exists purely to silence -Wpedantic.*/
typedef int keep_pedantic_happy;
#endif
#include "at32f435_437_clock.h"
#include "systick.h"
#include "usart.h"
#include "LCD/XPT2046.h"
#include "lvgl.h"
#include "examples/porting/lv_port_disp.h"
#include "examples/porting/lv_port_indev.h"
#include "examples/lv_examples.h"
#define LVGL_TICK 1
static void lvgl_init( void )
{
lv_init();
lv_port_disp_init(); // 显示器初始化
lv_port_indev_init(); // 输入设备初始化
// lv_port_fs_init(); // 文件系统设备初始化
}
/**
* @brief main function.
* @param none
* @retval none
*/
int main()
{
system_clock_config();
delay_init();
// LED init
gpio_init_type gpio_init_struct;
crm_periph_clock_enable(CRM_GPIOD_PERIPH_CLOCK, TRUE);
gpio_default_para_init(&gpio_init_struct);
gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;
gpio_init_struct.gpio_mode = GPIO_MODE_OUTPUT;
gpio_init_struct.gpio_pins = GPIO_PINS_2;
gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
gpio_init(GPIOD, &gpio_init_struct);
// //USART init
uart_print_init(115200);
printf("UART initialized successfully!\r\n");
// =============================> LCD app <=============================
lvgl_init();
lv_example_obj_2();
while(1) {
// 先调用 lv_tick_inc 再调用 lv_timer_handler
lv_tick_inc(LVGL_TICK);
lv_timer_handler();
delay_ms(LVGL_TICK);
GPIOD->odt ^= GPIO_PINS_2;
}
}