尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32F103驱动ST7789 IPS屏:SPI时序、DMA传输与局部刷新实战

STM32F103驱动ST7789 IPS屏:SPI时序、DMA传输与局部刷新实战 简介一份面向嵌入式开发者与电子爱好者的ST7789液晶驱动源码包基于STM32F103微控制器适用于小尺寸彩色TFT屏幕的快速接入与显示控制可移植到各类物联网终端、手持设备或学习项目中。压缩包共4个文件以ST7789.c与ST7789.h源文件为核心涵盖SPI接口配置、初始化命令序列、显示区域设置、颜色写入等基础驱动功能同时包括对应的备份文件便于开发时对照与恢复整体仅1KB代码量精简、结构清晰适合直接阅读与二次开发。已有365人学习浏览尤其适合正在学习STM32外设驱动、或希望低成本为项目增加屏幕显示的开发者参考。通过这份源码可以快速理解驱动芯片与MCU之间的通信流程减少翻阅数据手册和反复调试底层寄存器的时间并可直接将关键函数移植到自身工程中。1. 一块 240x240 IPS 屏和 STM32F103 之间隔着哪些东西拿一块 240x240 的 IPS 彩屏配一块 STM32F103 最小系统板很多人第一周的时间都耗在让屏幕亮起来这件事上。ST7789 是小尺寸 TFT 模组里出现频率最高的驱动 IC1.3 寸、1.54 寸、2.0 寸屏幕大多用它四根 SPI 线加 DC、RST 就能跑起来。st7789.zip 里放的就是一套 ST7789.c / ST7789.h 驱动源码还带着两个 .bak 备份文件说明是有人在真机上反复调过的版本比直接啃英文参考手册省事得多。源码已经把初始化序列、画点、区域填充、清屏这类底层操作封装好了你要补的只是 STM32F103 这一侧的 SPI 外设配置以及想清楚大块像素数据是走 DMA 还是靠 CPU 逐字节阻塞发送。按 9MHz SPI 时钟估算刷新一帧 240x240 RGB565 约 115KB 数据纯阻塞方式耗时接近 130ms做静态菜单勉强够用做动画会明显卡顿。所以这篇文章不满足于把屏幕点亮而是把 SPI 时序参数、初始化命令含义、DMA 传输和局部刷新这些最终决定显示效果的环节逐个拆开。2. STM32F103 SPI1 与 ST7789 的接线和时序控制2.1 引脚映射DC 和 RST 选 GPIO 而不是 SPI 复用先看一张最小系统下最常用的接线表。ST7789 模组引脚命名各厂略有出入但功能对得上信号模组引脚STM32F103 建议引脚方向说明SCKSCL / SCLKPA5SPI1_SCK输出SPI1 外设控制MOSISDAPA7SPI1_MOSI输出三线制无 MISO 回读CSCSPB12任意 GPIO输出低电平有效DC / A0DCPB13任意 GPIO输出0 发命令1 发数据RSTRES / RSTPB14任意 GPIO输出低脉冲复位BLKBLK / LED3.3V 或定时器 PWM 脚输出背光控制三个细节先说在前面。第一SPI1 在 APB2 总线上时钟源是 72MHzSPI2 挂在 APB1 上只有 36MHz所以追求刷屏速度优先选 SPI1。第二ST7789 只用了 SPI 的单向写通道MISO 没人接PA6SPI1_MISO可以省下来做触摸或其他功能。第三CS 不要图省事直接接地虽然单设备场景下能工作但后续做 DMA 批量传输、中途插命令时没有 CS 控制会很难看。DC 必须接普通 GPIO 而不是 SPI 复用脚因为它要在每个字节之间切换高低电平发命令字节时拉低发数据字节时拉高。常见错误是把 DC 接到某个串口或 I2C 的复用引脚上初始化序列里第一个 0x36 就被误判成数据屏幕整个花掉。用宏把引脚操作集中起来后续换板子只需要改这一处#define ST7789_CS_LOW() GPIO_ResetBits(GPIOB, GPIO_Pin_12) #define ST7789_CS_HIGH() GPIO_SetBits(GPIOB, GPIO_Pin_12) #define ST7789_DC_LOW() GPIO_ResetBits(GPIOB, GPIO_Pin_13) #define ST7789_DC_HIGH() GPIO_SetBits(GPIOB, GPIO_Pin_13) #define ST7789_RST_LOW() GPIO_ResetBits(GPIOB, GPIO_Pin_14) #define ST7789_RST_HIGH() GPIO_SetBits(GPIOB, GPIO_Pin_14)宏定义的好处是编译器在调用点直接展开没有函数调用开销。注意这些引脚在进入驱动初始化之前必须配置成推挽输出我在 stm32f10x_gpio.c 里写了一组GPIO_Mode_Out_PP的初始化函数时钟由 RCC_APB2PeriphClockCmd 使能 GPIOB。别漏了 RCC 时钟GPIOB 没开时钟时所有写操作都静默失效这是仿真器看不出来的问题。2.2 SPI 模式 0 / 模式 3CPOL 和 CPHA 怎么选ST7789 的 SPI 接口从参考手册上的时序图看兼容模式 0 和模式 3。大多数现成驱动包括 Adafruit 系和很多国产模组资料默认用模式 0也就是SPI_CPOL_Low配SPI_CPHA_1Edge数据在 SCK 上升沿被采样。如果参考代码跑不通先检查是不是平台间的SPI_MODE宏定义差异——同一套 ST7789.c 从 Arduino 移植到 F103 标准外设库最容易栽在这一处。基于 stm32f10x 标准外设库 v3.5.0 的配置代码void SPI1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); // SCK 和 MOSI 复用推挽速度拉到 50MHz 保证翻转率 GPIO_InitStructure.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_Init(GPIOA, GPIO_InitStructure); SPI_InitStructure.SPI_Direction SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode SPI_Mode_Master; SPI_InitStructure.SPI_DataSize SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL SPI_CPOL_Low; SPI_InitStructure.SPI_CPHA SPI_CPHA_1Edge; SPI_InitStructure.SPI_NSS SPI_NSS_Soft; // 软件管理片选 SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_8; SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; SPI_Init(SPI1, SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); }逐个参数说清楚。SPI_Direction_2Lines_FullDuplex是主模式最通用的配置虽然 ST7789 不接 MISO但 F103 的 SPI 硬件必须选双线全双工否则发送路径会被 RX 逻辑拖慢。SPI_NSS_Soft表示 NSS 引脚不用硬件管理由 GPIO 手动拉 CS这样多屏共用一条 SPI 总线时更灵活。SPI_BaudRatePrescaler_8在 APB2 72MHz 下得到 9MHz 的 SCK 频率模组走线 5cm 以内都能稳定工作把分频改成SPI_BaudRatePrescaler_4能上到 18MHz接近 ST7789 的极限但长排线和高容性负载会把波形弄成圆顶反而容易丢位。这里有个常见误用把 GPIO_Speed_50MHz 理解成 SPI 时钟的倍频关系。GPIO 速度指的是 IO 驱动强度与 SPI 波特率分频是两个独立配置项。GPIO_Speed_2MHz 配 SPI 9MHz 也能跑但边沿变缓信号完整性差。稳妥的做法是一律配 50MHz。2.3 逻辑分析仪验证时序再写应用层代码写完先别急着刷动画用逻辑分析仪挂 SCK、MOSI、DC、CS 四个通道把初始化过程抓下来看三件事复位低脉冲是否大于 1msSLPOUT 之后的延时是否足够DC 翻转是否发生在 CS 低电平期间而不是字节发送中间。如果 DC 在 SCK 边沿附近抖动ST7789 会把数据误判为命令。我一般会写一个只发0x00的测试循环把逻辑分析仪触发条件设成 CS 下降沿抓 16 个字节确认协议骨架。没有逻辑分析仪也可以用一个 GPIO 翻转 示波器两通道替代但四个信号同时观察效率高得多。这一步做好后面排错大概率不会再回到硬件层。3. ST7789 初始化序列拆解寄存器与显存窗口3.1 初始化命令流程与 .bak 备份的对照意义打开 st7789.zip 的目录顶层ST7789.c是主驱动ST7789.h是头文件另外还有ST7789.c.bak和ST7789.h.bak。这两个 .bak 本质上是修改前的还原点也就是作者在某一次调参失败之后留的备份。如果你拿到的 .c 编译不过或者屏幕点不亮用 diff 工具对比主文件和 .bak能直接看到寄存器参数改动过哪些地方这些改动往往是针对某款具体模组的兼容性修正。初始化序列是驱动里最敏感的部分。以常见 ST7789V 模组为例完整的初始化流程包含退出睡眠、设置扫描方向、设置颜色格式、调节电压和打开显示。下面这段是 st7789.zip 里最常见结构的简化版void ST7789_Init(void) { ST7789_RST_LOW(); delay_ms(20); ST7789_RST_HIGH(); delay_ms(120); // 上电后到 SLPOUT 之间至少等 5ms ST7789_WriteCommand(0x11); // SLPOUT退出睡眠 delay_ms(120); ST7789_WriteCommand(0x36); // MADCTL扫描方向和颜色序 ST7789_WriteData(0x00); ST7789_WriteCommand(0x3A); // COLMOD像素格式 ST7789_WriteData(0x05); // 16bit RGB565 ST7789_WriteCommand(0xB2); // PORCTRL帧率相关 ST7789_WriteData(0x0C); ST7789_WriteData(0x0C); ST7789_WriteData(0x00); ST7789_WriteData(0x33); ST7789_WriteData(0x33); ST7789_WriteCommand(0xC5); // VCOM 电压微调 ST7789_WriteData(0x35); ST7789_WriteCommand(0x21); // INVON打开反色 ST7789_WriteCommand(0x29); // DISPON点亮显示 }这里的坑几乎全在延时上。RST 低电平保持时间至少要 10us上电后到 SLPOUT 必须留出稳定时间太多移植失败不是命令写错而是延时被裁得太狠芯片还没完成内部振荡器起振就被发了后续命令。delay_ms(120)在这套代码里是一个基于 SysTick 的阻塞延时如果你改用了实际项目里的非阻塞延时框架要防止初始化期间的 DMA 或定时器中断把流程打断。命令与数据的发送落到底层就是 SPI 字节写操作。DC 的翻转时机必须是先置低再拉低 CS最后写字节void ST7789_WriteCommand(uint8_t cmd) { while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); // 等发送缓冲空 ST7789_DC_LOW(); ST7789_CS_LOW(); SPI_I2S_SendData(SPI1, cmd); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) SET); // 等发送完成 ST7789_CS_HIGH(); ST7789_DC_HIGH(); }SPI_I2S_FLAG_TXE表示发送数据寄存器已空SPI_I2S_FLAG_BSY表示 SPI 外设忙。顺序上先查 TXE 再拉低 DC 和 CS能保证进入写入时总线已空闲。如果 DC 信号放在 CS 之后翻转第一个字节的起始位会被 ST7789 错当命令处理初始化直接乱掉。3.2 MADCTL 和 COLMOD方向、镜像与颜色格式MADCTL0x36是显示方向控制寄存器一个字节里同时管了行扫描方向、列扫描方向和 RGB/BGR 颜色顺序。位定义在 ST7789 中文参考手册的表里写得很直白但实际调的时候最容易翻车的是主控坐标系与 GRAM 坐标系的映射。常用值的意义MADCTL 值效果适用场景0x00竖屏原点左上RGB 顺序默认竖屏0x60旋转 90 度横屏横向显示0xC0旋转 180 度倒屏模组安装方向反过来0xA0旋转 270 度另一侧横屏0x08RGB 与 BGR 互换红色和蓝色对调时镜像问题的判断方法如果初始化后文字左右反说明 MY 位反了如果颜色通道对调则是 RGB/BGR 位反了。很多人喜欢把 MADCTL 写死在代码里但如果你的产品有横竖屏切换需求这个寄存器必须做成可配置项切换时还要连带修改显存窗口的起止坐标。COLMOD0x3A决定像素格式。三个常见值COLMOD 值格式每像素字节说明0x05RGB5652默认选择SPI 负载最小0x06RGB6663色彩过渡细腻传输量多 50%0x07RGB8884极少用SPI 带宽完全不够RGB565 里每个像素 16 位高 5 位红、中间 6 位绿、低 5 位蓝。如果你是直接从 PC 端图片转数组常见的转码工具默认输出大端格式也就是说数组里先放高位字节再放低位字节。ST7789 的 RAMWR 写入顺序也是高字节在前这一点要和后面ST7789_WriteData(color 8)的写法对应否则颜色错位成噪点。3.3 CASET/RASET把写入窗口精确到像素ST7789 把显存划分成一个 240x320 的 GRAM 矩阵CASET 和 RASET 两个命令划出你真正要写入的矩形区域之后 RAMWR 命令后面跟着的数据就按这个窗口自动换行填充。代码实现void ST7789_SetWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { ST7789_WriteCommand(0x2A); // CASET列地址 ST7789_WriteData(x0 8); ST7789_WriteData(x0 0xFF); ST7789_WriteData(x1 8); ST7789_WriteData(x1 0xFF); ST7789_WriteCommand(0x2B); // RASET行地址 ST7789_WriteData(y0 8); ST7789_WriteData(y0 0xFF); ST7789_WriteData(y1 8); ST7789_WriteData(y1 0xFF); ST7789_WriteCommand(0x2C); // RAMWR开始写像素 }CASET 的参数是列的起止地址RASET 是行的起止地址都是 16 位参数高字节在前。240x240 的模组内部 GRAM 虽然是 240x320但出厂时可见区域已经把多余的部分遮掉所以 CASET 从 0 到 239、RASET 从 0 到 239 就可以。如果你买的是 135x240 这类异形屏就必须按模组资料在 CASET/RASET 上加偏移量否则完整画面会被裁掉一块错位症状通常是图像底部跑到顶部去。基于窗口的填充函数可以复用上面的设置void ST7789_FillRect(uint16_t x0, uint16_t y0, uint16_t w, uint16_t h, uint16_t color) { uint32_t npixels (uint32_t)w * h; ST7789_SetWindow(x0, y0, x0 w - 1, y0 h - 1); while (npixels--) { ST7789_WriteData(color 8); ST7789_WriteData(color 0xFF); } }npixels用 uint32_t 是因为 240x240 的整屏填充是 57600 次迭代uint16_t 虽然够用但乘法表达式里 w 和 h 都被提升为 int转发给局部变量容易在优化级别开高后产生符号位告警直接写 uint32_t 省心。填充颜色分两次发是高字节先行的时序约束不能合并成一个 16 位一次性发送除非你写的是SPI_DataSize_16b的模式。4. 显示数据通路DMA 传输与局部刷新方案4.1 F103 的内存约束下怎么设计缓冲STM32F103C8T6 只有 20KB SRAM一块 240x240 全屏 RGB565 帧缓冲就要 115200 字节想放全帧根本不可能。所以在 F103 上做图形成像核心问题不是画得快而是缓冲怎么分。缓冲方案内存开销传输方式适用场景全帧缓冲115.2KB一次 DMA 传到底需要 32KB RAM 的型号F103 放不下行缓冲480B每行开一次窗口内存极紧但每行都要切换窗口慢块缓冲8KB~16KB按矩形块 DMAF103 上的折中方案我一般在 F103C8 上做一个 64x64 的块缓冲占 8KB SRAM剩余内存留给协议栈和 UI 状态。这个尺寸的块做按钮、弹窗和滚动列表时足够覆盖高频率变化的区域静态背景走一次全屏 DMA 之后就不用再动。块缓冲的定义放在栈外用 static 声明避免栈溢出。代码结构上这样组织static uint8_t lcd_block_buf[64 * 64 * 2]; void LCD_DrawBlock(uint16_t x0, uint16_t y0, uint16_t w, uint16_t h) { // 从图形引擎生成像素到 lcd_block_buf RenderBlock(x0, y0, w, h, (uint16_t *)lcd_block_buf, 64); // 再把块写到屏幕 ST7789_SetWindow(x0, y0, x0 w - 1, y0 h - 1); ST7789_DMA_Send(lcd_block_buf, w * h * 2); }注意lcd_block_buf的宽度固定为 64而不是用动态的 w。固定宽度能让编译器把地址计算简化成移位和加法省掉乘法指令。块宽小于缓冲宽度时只有前面一部分被 DMA 发送这没有关系DMA 的len参数精确控制发送字节数。清屏函数不需要单独做全屏循环直接设一次大窗口不断发同样的颜色数据即可。4.2 SPI DMA 发送实现与性能实测STM32F103 的 SPI1_TX 请求映射在 DMA1 通道 3。用标准外设库初始化 DMA 的完整代码void ST7789_DMA_Send(uint8_t *buf, uint32_t len) { DMA_InitTypeDef DMA_InitStructure; // 等上一次 DMA 传输完成标志位 while (DMA_GetFlagStatus(DMA1_FLAG_TC1) RESET) {} DMA_ClearFlag(DMA1_FLAG_TC1); DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)((SPI1-DR)); DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)buf; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralDST; DMA_InitStructure.DMA_BufferSize len; DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_Mode DMA_Mode_Normal; DMA_InitStructure.DMA_Priority DMA_Priority_VeryHigh; DMA_InitStructure.DMA_M2M DMA_M2M_Disable; DMA_Init(DMA1_Channel3, DMA_InitStructure); SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE); DMA_Cmd(DMA1_Channel3, ENABLE); }先说标志位的处理。DMA1_FLAG_TC1 是通道 3 传输完成标志发送前必须等它置位并清除否则上一次传输还没结束就重新配置 DMA会让这次的数据覆盖掉上一个 buffer 的残余状态。DMA_Mode_Normal是单次模式待传输字节数等于DMA_BufferSize别用DMA_Mode_Circular那会无限循环发送同一块内存。性能上做个实测估算。SPI 时钟 9MHz 时每秒传 1.125MB115200 字节的一帧理论耗时 102ms加上 CASET/RASET 窗口切换和 DMA 启动开销实际约 110ms。不开 DMA 的阻塞发送会再慢 20ms 到 30ms因为每发一个字节都要轮询TXE标志CPU 白白等待。DMA 另一个价值是释放 CPU传输期间主循环可以继续画下一帧的矢量图形或处理串口命令。你如果开了SPI_BaudRatePrescaler_4到 18MHz理论帧时间降到 51ms但要注意排线长度对波形的劣化降不下来的话就维持 9MHz。DMA 传输还有一个生命周期问题buf在 DMA 启动后不能被立即改写函数返回时 DMA 可能还在传。常见的做法是传输前等待 TC 标志或者用 DMA 传输完成中断来通知外部void DMA1_Channel3_IRQHandler(void) { if (DMA_GetITStatus(DMA1_IT_TC3)) { DMA_ClearITPendingBit(DMA1_IT_TC3); lcd_dma_busy 0; // 允许外部改写缓冲 } }lcd_dma_busy做成全局标志或者用信号量UI 层在往lcd_block_buf写数据前先检查它。这比盲目延时可靠性能也好。4.3 局部刷新与 TE 同步避免画面撕裂ST7789 在连续大窗口刷新时如果恰好写到一半GRAM 正在从顶部扫描显示就会产生撕裂感——上一帧的下半屏和这一帧的上半屏叠在一起。支持 TE 引脚的模组把帧同步信号拉出来这个信号在每个显示扫描周期开始前产生一个脉冲。配合它做局部更新可以保证窗口数据只在帧间隙写入。void ST7789_UpdateRegion(uint16_t x0, uint16_t y0, uint16_t w, uint16_t h, uint16_t *pixels) { // PA3 接 TE 脚等一个高电平脉冲再写窗口 while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3) RESET); ST7789_SetWindow(x0, y0, x0 w - 1, y0 h - 1); ST7789_DMA_Send((uint8_t *)pixels, w * h * 2); }TE 引脚的采样电路要注意如果模组的 TE 输出是开漏还要在主控侧加外部上拉电阻如果是推挽输出就不需要。全屏刷新时把 x0/y0 设成 0、w/h 设成屏幕分辨率代码同样成立。如果模组没引出 TE 引脚退而求其次的做法是用一个定时器中断模拟固定帧周期但效果不如硬件同步精确。5. 硬件滚动、PWM 调光和 FreeRTOS 集成时的三个移植点5.1 用 VSCRDEF 实现文本日志的无像素滚动ST7789 内置垂直滚动功能不需要重写 GRAM 就能让一块显示区域向上滚动。控制它的命令是 0x33VSCRDEF和 0x37VSCRSAD。VSCRDEF 一次传入 6 个字节分别是顶部固定区 TFA、滚动区 VSA、底部固定区 BFA 三个 12 位参数的高字节和低字节。假设屏幕 240x240想让从第 40 行开始的 160 行区域滚动顶部固定 40 行、底部固定 40 行参数就是 0x00 0x28、0x00 0xA0、0x00 0x28。void ST7789_EnableScroll(uint16_t bfa, uint16_t vsa, uint16_t tfa) { ST7789_WriteCommand(0x33); ST7789_WriteData(bfa 8); ST7789_WriteData(bfa 0xFF); ST7789_WriteData(vsa 8); ST7789_WriteData(vsa 0xFF); ST7789_WriteData(tfa 8); ST7789_WriteData(tfa 0xFF); ST7789_WriteCommand(0x37); // VSCRSAD滚动起始地址 ST7789_WriteData(0x00); ST7789_WriteData(0x00); }这条路径省了 CPU 也省了 SPI 带宽做传感器日志或者跑马灯提示信息时很实用。要恢复静态显示再写一次 VSCRSAD 把起始地址设为 0然后补一条 0x13NORON正常显示命令。注意滚动只改变显示读出的起点不改 GRAM 内容。5.2 PWM 背光调光占比与刷新率BLK 引脚接一个定时器通道而不是固定高电平亮度就能软件可调。用 TIM2 输出 PWM 的初始化参数TIM2_PWM_Init(999, 71); // 72MHz / (72 * 1000) 1kHz TIM_SetCompare1(TIM2, 800); // 占空比 80%PWM 频率至少要 1kHz低于这个值人眼能察觉频闪暗光环境更明显。占空比从 0 到 999 线性变化800 就是 80% 亮度。不要从 0 直接跳到 100%加一点渐变逻辑视觉体验差别很大。5.3 FreeRTOS 下给 SPI 外设加互斥锁工程里同时有多个任务往屏幕写内容时SPI 总线必须变成关键资源。最简单有效的做法是在 st7789 驱动里加一把互斥信号量每个写屏幕的入口函数先拿锁再操作static SemaphoreHandle_t spi_mutex; void ST7789_Lock(void) { xSemaphoreTake(spi_mutex, portMAX_DELAY); } void ST7789_Unlock(void) { xSemaphoreGive(spi_mutex); }初始化驱动前用spi_mutex xSemaphoreCreateMutex()建好信号量。调度策略上锁的粒度要小只在单条命令或一次 DMA 发送期间持锁不要让刷新全屏这种长操作一直占着锁否则其他任务会被长期阻塞。更细的做法是给 DMA 传输加一个 xSemaphoreGive 放在传输完成中断里任务侧用带超时的 take这样既保证互斥又避免死锁。最后一招如果两个任务同时写同一块区域与其锁来锁去不如在逻辑层就定好分区各画各的窗口省掉一半调度开销。本文还有配套的精品资源点击获取
返回列表