总感觉之前的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的存在?
于是新板子上我尝试了以下方法:
- 双线双工,和XPT2046共用SPI,软件管理CS
- 双线双工,单独使用一个SPI,硬件管理CS
- 双线双工,单独使用一个SPI,软件管理CS
- 单线半双工,单独使用一个SPI,硬件管理CS
基于几个基本逻辑:节约CPU资源:只要不是共用SPI,就用硬件CS;节约引脚:只要没有用到数据输入,就不使用双线双工。因此这里面合理的选项只有1和4。另外,只要和触摸IC共用SPI,就不得不用到MISO,且“一主多从”只支持软件CS。
1在之前的小板上就已经实现过了,但是觉得在IO资源足够的情况下,还是应当以节约CPU资源为主,所以舍弃了软件CS,想试试方法4:在LCD不与XPT2046共用SPI的时候,仅使用 SCLK, MOSI, CS(硬件控制) 3根线来驱动LCD。
然后理所当然的踩了一堆坑。。。调的头都要秃了,在此记录一下。
关于AT32F435开启单线半双工模式
刚开始我以为只要在 spi_init_struct 中配置下面这一项:
spi_init_struct.transmission_mode = SPI_TRANSMIT_HALF_DUPLEX_TX;
然后 在spi_init() 函数里传进去就够了,结果代码都运行了,没有任何现象,检查走信号的GPIO,初始化后只能看到有一点点毛刺。这种情况通常可能是
- GPIO时钟没使能
- SPI时钟没使能
- SPI没使能
然而负责这三行代码都执行过了,怎么出的问题?
检查寄存器的时候才发现,每当我初始化SPI设置后、执行 spi_enable() 去使能SPI时,STS寄存器蹦出一个MMERR(主模式错误),同时把我的模式改成了从模式,还关掉了我的SPI使能。
查了AT的RM,上面也没说什么情况下会触发MMERR,仔细检查RM提供的信息,总结了下面几点:
- 单线半双工模式下ctrl1_bit.slben将使能,并且ctrl1_bit.slbtd决定是发还是收;
- 主模式下 ctrl1_bit.swcsil 必须置位;
- ctrl2_bit.hwcsoe 在做主设备时,如果置位,测CS输出低电平;如果清零,必须保证CS电平为高电平。
事实上对于第一项,如果设置了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呢...
关于硬件管理CS
在单线半双工下通过 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:
经过优化后的刷屏代码:
- ST7789.c
#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 }
- ST7789.h
/* 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
- lv_port_disp.c
/** * @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 <stdbool.h> #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
- main.c
#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; } }