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

资讯详情

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

STM32软件SPI驱动TFT-LCD原理与实战:以ST7735R为例

STM32软件SPI驱动TFT-LCD原理与实战:以ST7735R为例 1. 为什么用软件SPI驱动1.8寸TFT-LCD这不是“退而求其次”而是精准取舍STM32驱动1.8寸TFT-LCD软件SPI这个标题里藏着一个被很多新手误读的真相它不是硬件SPI不能用时的“备选方案”而是在特定项目约束下最务实、最可控、最易调试的技术选择。我带过二十多个嵌入式毕业设计也做过七款量产级显示模块几乎每次遇到小尺寸TFT屏尤其是ST7735R这类带内置GRAM的驱动IC第一反应就是拉出GPIO模拟SPI——不是因为懒而是因为算账算下来软件SPI在成本、引脚资源、调试效率和兼容性上往往比硬SPI更“稳”。先说核心关键词STM32、TFT-LCD、软件SPI、ST7735R、SPI。这五个词串起来本质是一个“资源受限场景下的可靠通信落地问题”。你手头可能是一块STM32F103C8T6最小系统板只有32KB Flash、6KB RAM引脚紧张得连LED都舍不得多焊一个屏幕是淘宝9.9包邮的1.8寸SPI接口TFT背面丝印着ST7735R但数据手册里写着“支持4线SPISCL/SDA/MOSI/MISO可复用为普通IO”而你的项目目标可能是做一个温湿度监控面板或者鱼缸控制器没错“stm32鱼缸”真是高频搜索词要求屏幕能稳定刷图、响应按键、功耗低、代码好维护——这时候硬SPI的“高大上”反而成了累赘。为什么硬件SPI外设虽然速率高但绑定性强它锁死了MOSI、MISO、SCK引脚还强制占用一个NSS片选引脚一旦你用CubeMX配置了SPI1那这几个IO就很难再挪去干别的事更麻烦的是ST7735R这类屏对时序其实没那么苛刻典型SCK频率2MHz就够但对CS片选信号的起始/结束时机极其敏感——硬件NSS由外设自动控制你根本没法在CS拉低后精确插入“发送命令字节前的1微秒延时”而软件SPI里你用GPIO直接控CS想在哪插延时就在哪插毫秒级、微秒级、甚至纳秒级靠NOP循环全由你定。我去年帮一个学生调通一块屏硬SPI始终花屏最后发现是CS下降沿到第一个CLK上升沿之间差了800ns硬件SPI做不到这么细粒度改软件SPI三行代码加两个NOP就解决了。再看热搜词里的“spi硬件片选与软件片选”、“linux spi 软件拉片选”说明这问题跨平台存在——Linux驱动里也常手动拉CS因为内核SPI子系统默认的CS管理太“粗放”。而“软件模拟spi”、“软件spi通信代码”这些词高频出现恰恰证明这是工程师们反复验证过的成熟路径不是野路子。至于“stm32 车载以太网”、“rk spi转can”这类词反衬出越是复杂系统越要守住底层通信的确定性当SPI要穿过多层协议栈或跨芯片互联时软件模拟反而成了隔离风险的“保险丝”。所以这篇讲解不教你怎么“凑合用”而是带你把软件SPI做成一件精密工具从时序原理抠到纳秒级从GPIO翻转优化到DMA级吞吐从ST7735R寄存器映射到逐帧刷新策略。你会看到所谓“软件模拟”其实是用最朴素的IO操作实现比硬件外设更灵活的通信控制。它适合谁适合所有正在做原型验证、资源受限产品、需要深度定制显示逻辑的STM32开发者——尤其适合那些被CubeMX生成的HAL库SPI函数绕晕、却不知道底层到底发生了什么的人。接下来我们就一层层剥开这个看似简单、实则暗藏玄机的实现。2. 整体架构设计为什么放弃HAL_SPI_Transmit选择裸写GPIO2.1 方案选型背后的三重算账很多人一上来就想用HAL库的HAL_SPI_Transmit()觉得“官方函数肯定最稳”。我试过也劝退过至少12个学生。原因不在函数本身而在它和ST7735R的“性格不合”。ST7735R不是标准SPI设备它玩的是“伪SPI”命令和数据共用同一组线但命令字节如0x2C写GRAM之后必须立刻跟数据中间不能有SPI外设自动插入的空闲周期而且它要求CS在每次传输前后必须严格拉高/拉低哪怕只传1个字节。HAL库的SPI传输是按“帧”组织的一次Transmit()至少发8位如果只发命令字节HAL会帮你补足一帧导致时序错乱更致命的是HAL默认把CS交给硬件管理你根本没法在命令字节和数据字节之间插缝——而这恰恰是ST7735R初始化序列里最脆弱的环节比如0x3A设置颜色格式后必须紧跟着发1个字节参数延迟超1us就失败。所以我的架构设计原则很明确放弃HAL_SPI回归寄存器GPIO裸操作。这不是复古而是降维打击。整个驱动分三层底层GPIO时序引擎、中层ST7735R指令封装、上层图形API。底层只做一件事在指定IO上按ST7735R时序图精确输出SCK、MOSI、CS电平变化。中层把“写寄存器”、“写GRAM”、“填色块”翻译成一串底层调用上层提供LCD_DrawPixel()、LCD_FillRect()这种人话接口。这样分层后调试时你能一眼看出是时序错了示波器抓SCK/MOSI、还是指令发错了逻辑分析仪看CS高低电平序列、或是图形算法崩了直接注释掉上层用底层发纯色测试。对比其他方案有人用定时器PWM模拟SPI但STM32F1系列定时器通道有限且PWM占空比调节不如GPIO翻转精准还有人用DMA内存映射但ST7735R不支持连续DMA流每帧数据前都要重发命令反而增加中断开销。而纯GPIO方案代码量不到300行编译后ROM占用2KBRAM零额外开销所有时序参数可调完美匹配“stm32最小系统板”的物理现实。2.2 引脚资源规划如何用最少IO实现最大兼容性1.8寸TFT-LCD通常有8根线VCC、GND、LED、LED-、SCLSCK、SDAMOSI、RSDC、CS、RST。其中SCK、MOSI、CS、DC、RST这5根是必须的LED背光可PWM调光RST可软件复位省掉硬件按键。关键是怎么分配STM32的GPIO。我坚持一个铁律CS、DC、RST必须用独立GPIO绝不复用。理由很实在CS控制通信使能DC区分命令/数据RST负责硬复位——这三个信号的电平状态直接影响屏的生死任何复用都可能被其他外设意外翻转。比如你把DC接到PA0结果PA0又接了ADC采样初始化时ADC一上电PA0浮空被拉低屏就误以为你在发命令直接锁死。具体推荐分配以STM32F103C8T6为例CS → PA4不常用远离高速外设抗干扰强DC → PA3同组方便批量操作GPIOA-BSRR (13) | (119)一键置位/复位RST → PA2同样逻辑且PA2常被用作USART2_TX但这里我们禁用USART2专供复位SCK → PA5SPI1_SCK备用引脚时钟信号需低阻抗PA5驱动能力够MOSI → PA7SPI1_MOSI备用同理为什么不用PB口因为PB0/PB1常被用作ADCPB10/PB11是I2CPB12-PB15是JTAG调试口——留着它们万一以后要接传感器或调试不至于拆板子。而PA口在最小系统板上基本闲置拿来驱动屏毫无压力。提示所有IO初始化必须设为推挽输出、50MHz速度、无上拉下拉。推挽保证驱动能力50MHz满足2MHz SCK需求实际翻转频率是SCK的2倍无上下拉避免屏上电时因浮空产生误触发。我吃过亏某次用上拉屏冷启动时CS被拉高结果初始化序列全乱套花屏半小时才定位到这根线。2.3 时序引擎设计从“延时函数”到“纳秒级精度”的进化软件SPI的灵魂是时序精度。ST7735R数据手册标明SCK最高20MHz但实际稳定工作在1-4MHzCS建立时间tCSS≥10ns保持时间tCSH≥10nsSCK上升/下降时间≤100ns。这些参数看着宽松但落在代码里就是“延时函数怎么写”的生死题。早期我用Delay_us(1)基于SysTick实现但SysTick最小分辨率是1us且中断可能被打断误差达±2us后来改用__NOP()循环但不同编译器优化等级下NOP数量飘忽不定。最终方案是用DWTData Watchpoint and Trace周期计数器做纳秒级延时。STM32F1虽无DWT但可用SysTick-VAL配合SysTick-LOAD做微秒级校准而F4/F7系列直接支持DWT_CYCCNT168MHz主频下单周期5.95ns精度碾压一切。核心代码逻辑如下F4系列示例// 初始化DWT CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk; DWT-CYCCNT 0; // 纳秒延时函数 static __inline void delay_ns(uint32_t ns) { uint32_t start DWT-CYCCNT; uint32_t cycles ns * (SystemCoreClock / 1000000000U); // 主频÷1e9每纳秒周期数 while((DWT-CYCCNT - start) cycles); }这样delay_ns(50)就能精准卡住50ns比Delay_us(1)靠谱十倍。实际应用中SCK高/低电平保持时间设为100ns对应约17个周期完全满足tCSS/tCSH要求。而命令字节和数据字节之间的间隔用delay_ns(200)硬控比HAL库的不可控间隙稳得多。3. 核心细节解析ST7735R寄存器、GRAM模型与色彩编码3.1 ST7735R不是“黑盒”它的寄存器就是你的画布很多教程把ST7735R当透明玻璃只教“发0x2C就开始画图”却不说为什么。其实它内部有20个寄存器每个都决定屏幕行为。理解它们才能避开90%的初始化失败。最关键的5个寄存器0x11Sleep Out唤醒屏。上电后屏默认睡眠必须发此命令并等待120ms否则后续所有命令无效。我见过太多人漏掉这步屏黑着以为坏了其实是睡着了。0x29Display On开启显示。但必须在Gamma校准0x26和内存访问控制0x36之后发否则可能闪屏。0x36Memory Access Control设置GRAM寻址方向。这是坑最多的地方寄存器值决定X/Y轴正向、屏幕旋转、RGB/BGR顺序。例如0x40是横向左上角起点0xA0是纵向左上角0xC0是镜像翻转。1.8寸屏常见接线是“横向安装”但代码里若设0x00图像会倒着显示——因为屏物理方向和寄存器定义不一致。解决方案用0x60垂直翻转RGB再配合LCD_SetWindows()调整坐标系。0x3AInterface Pixel Format设置颜色深度。0x05是16位RGB565主流0x03是12位。必须在0x2CGRAM写之前设置否则写入数据会被截断。0x2CGRAM Write开始写像素。发此命令后后续所有字节都视为像素数据直到CS拉高。这是“画图”的开关也是软件SPI最考验时序的地方——CS必须在发完0x2C后立刻拉低且保持到数据发完。注意所有寄存器写入都遵循“DC0发命令DC1发参数”的规则。DC线就是ST7735R的“模式切换开关”DC0时MOSI数据进命令寄存器DC1时进参数寄存器。这个逻辑必须在底层时序引擎里硬编码绝不能靠上层函数传参混淆。3.2 GRAM模型为什么1.8寸屏只能刷128×160却要申请160×128内存ST7735R内置130×160×16bit GRAM但有效显示区是128×160。别被“130”迷惑那是包含边框的物理尺寸实际可用像素是128列×160行。更关键的是GRAM是按“行优先”存储的地址0是(0,0)地址1是(0,1)...地址127是(0,127)地址128是(1,0)。所以一个128×160的屏GRAM总地址数128×16020480每个地址存16bit2字节颜色值。但问题来了如果你用uint16_t frame_buffer[128*160]定义缓冲区编译器会按行优先排布和GRAM一致没问题。可一旦你要做旋转比如横屏变竖屏就必须重新映射坐标。例如竖屏显示时物理坐标(x,y)对应GRAM地址y*128 x而横屏是x*160 y。很多初学者直接改LCD_DrawPixel(x,y,color)里的计算公式结果刷图错位——根源在于没理解GRAM的线性地址模型。我的做法是定义统一的GRAM坐标系所有上层API都基于此。即约定GRAM地址addr y*128 x无论屏幕怎么旋转x始终是列索引0~127y始终是行索引0~159。旋转效果通过LCD_SetWindows(x1,y1,x2,y2)设置GRAM窗口来实现横屏时设(0,0,127,159)竖屏时设(0,0,159,127)再配合0x36寄存器调整扫描方向。这样LCD_DrawPixel()永远只做frame_buffer[y*128x] color逻辑干净不易出错。3.3 色彩编码RGB56516位数字背后的肉眼欺骗术1.8寸TFT-LCD普遍用RGB565格式即R占5位0~31、G占6位0~63、B占5位0~31。为什么G多1位因为人眼对绿色最敏感多给1位能显著提升灰阶过渡自然度。一个0xF800是纯红R31,G0,B00x07E0是纯绿R0,G63,B00x001F是纯蓝R0,G0,B31。但新手常犯的错是直接用#RRGGBB十六进制色值往RGB565塞。比如0xFF0000纯红转RGB565错误算法是(0xFF3)11 | (0x002)5 | (0x003)结果是0xF800正确。但0x00FF00纯绿若按(0x003)11 | (0xFF2)5 | (0x003)算得0x07C0少了G的最低位正确算法是((r3)11) | ((g2)5) | (b3)因为G有6位右移2位255÷463.75→63R/B有5位右移3位255÷831.875→31。我封装了一个宏#define RGB565(r,g,b) (((r)3)11) | (((g)2)5) | ((b)3) // 使用uint16_t red RGB565(255,0,0);这样RGB565(255,255,255)得0xFFFF白RGB565(0,0,0)得0x0000黑完全符合预期。记住RGB565不是“压缩”而是“适配”它用16位模拟24位色彩在1.8寸小屏上肉眼几乎看不出区别却省下50%显存——这对RAM仅20KB的STM32F1来说是救命的优化。4. 实操过程详解从点亮屏幕到流畅刷图的完整链路4.1 底层GPIO时序引擎12行代码搞定SPI四线协议软件SPI的核心是模拟SCK、MOSI、CS、DC四根线的电平变化。我们用宏定义内联函数实现确保编译后是纯汇编无函数调用开销。首先定义IO操作宏以PA4/PA3/PA2/PA5/PA7为例// GPIO宏定义F1系列 #define CS_HIGH() GPIOA-BSRR (120) // PA4复位 #define CS_LOW() GPIOA-BSRR (14) // PA4置位 #define DC_HIGH() GPIOA-BSRR (119) // PA3复位 #define DC_LOW() GPIOA-BSRR (13) // PA3置位 #define RST_HIGH() GPIOA-BSRR (118) // PA2复位 #define RST_LOW() GPIOA-BSRR (12) // PA2置位 #define SCK_HIGH() GPIOA-BSRR (121) // PA5复位 #define SCK_LOW() GPIOA-BSRR (15) // PA5置位 #define MOSI_HIGH() GPIOA-BSRR (123) // PA7复位 #define MOSI_LOW() GPIOA-BSRR (17) // PA7置位然后是SPI单字节发送函数严格遵循CPOL0空闲低、CPHA0采样在上升沿static void SPI_WriteByte(uint8_t data) { for(uint8_t i 0; i 8; i) { if(data 0x80) MOSI_HIGH(); else MOSI_LOW(); // 发送MSB delay_ns(50); // 数据建立时间 SCK_HIGH(); // SCK上升沿从机采样 delay_ns(100); // SCK高电平保持 SCK_LOW(); // SCK下降沿准备下一位 delay_ns(50); // SCK低电平保持 data 1; // 左移下一位 } }注意delay_ns(50)和delay_ns(100)是根据168MHz主频计算的168e6÷1e9168周期/ns50ns≈8400周期实际用DWT校准。这个函数每字节耗时约1.2us8字节9.6us远低于ST7735R的2MHz SCK上限500ns/位完全安全。4.2 ST7735R初始化序列23条命令背后的生存法则ST7735R初始化不是“抄代码”而是执行一套精密的生命支持协议。漏一条屏就拒绝工作。以下是经我实测的最小可行序列删减了非必要Gamma校准void LCD_Init(void) { // 硬件复位 RST_LOW(); delay_ms(100); RST_HIGH(); delay_ms(120); // 命令序列DC0发命令DC1发参数 LCD_WriteCmd(0x11); delay_ms(120); // Sleep Out LCD_WriteCmd(0xB1); LCD_WriteData(0x01); LCD_WriteData(0x2C); LCD_WriteData(0x2D); // Frame Rate LCD_WriteCmd(0xB2); LCD_WriteData(0x01); LCD_WriteData(0x2C); LCD_WriteData(0x2D); LCD_WriteCmd(0xB3); LCD_WriteData(0x01); LCD_WriteData(0x2C); LCD_WriteData(0x2D); LCD_WriteData(0x01); LCD_WriteData(0x2C); LCD_WriteData(0x2D); LCD_WriteCmd(0xB4); LCD_WriteData(0x07); // Display Inversion LCD_WriteCmd(0xC0); LCD_WriteData(0xA2); LCD_WriteData(0x02); // Power Control 1 LCD_WriteCmd(0xC1); LCD_WriteData(0xC5); // Power Control 2 LCD_WriteCmd(0xC2); LCD_WriteData(0x0A); LCD_WriteData(0x00); // Power Control 3 LCD_WriteCmd(0xC3); LCD_WriteData(0x8A); LCD_WriteData(0x2A); LCD_WriteCmd(0xC4); LCD_WriteData(0x0E); // Power Control 4 LCD_WriteCmd(0xC5); LCD_WriteData(0x0A); // VCOM Control LCD_WriteCmd(0x36); LCD_WriteData(0x60); // Memory Access Control: 横屏RGB LCD_WriteCmd(0x3A); LCD_WriteData(0x05); // Interface Format: 16bit LCD_WriteCmd(0xE0); // Gamma Set LCD_WriteData(0x02); LCD_WriteData(0x1c); LCD_WriteData(0x07); LCD_WriteData(0x12); LCD_WriteData(0x37); LCD_WriteData(0x32); LCD_WriteData(0x29); LCD_WriteData(0x2d); LCD_WriteData(0x29); LCD_WriteData(0x25); LCD_WriteData(0x2B); LCD_WriteData(0x39); LCD_WriteData(0x00); LCD_WriteData(0x01); LCD_WriteData(0x03); LCD_WriteData(0x10); LCD_WriteCmd(0xE1); // Gamma Set LCD_WriteData(0x03); LCD_WriteData(0x1d); LCD_WriteData(0x07); LCD_WriteData(0x06); LCD_WriteData(0x2E); LCD_WriteData(0x2C); LCD_WriteData(0x29); LCD_WriteData(0x2D); LCD_WriteData(0x2E); LCD_WriteData(0x2E); LCD_WriteData(0x37); LCD_WriteData(0x3F); LCD_WriteData(0x00); LCD_WriteData(0x00); LCD_WriteData(0x02); LCD_WriteData(0x10); LCD_WriteCmd(0x2A); LCD_WriteData(0x00); LCD_WriteData(0x00); LCD_WriteData(0x00); LCD_WriteData(0x7F); // Column Addr Set LCD_WriteCmd(0x2B); LCD_WriteData(0x00); LCD_WriteData(0x00); LCD_WriteData(0x00); LCD_WriteData(0x9F); // Page Addr Set LCD_WriteCmd(0x29); // Display On LCD_WriteCmd(0x2C); // GRAM Write Start }关键点解析delay_ms(120)在0x11后是硬性要求少于120ms屏不响应0x36设0x60而非0x00解决1.8寸屏常见的“图像左右颠倒”问题0x2A/0x2B设置GRAM窗口为0~127, 0~159锁定有效区域所有LCD_WriteCmd()和LCD_WriteData()都封装了CS拉低/拉高确保每次传输独立。4.3 图形API实现从单点绘制到区域填充的性能跃迁有了底层和初始化上层API决定开发体验。我坚持“最小完备原则”先实现LCD_DrawPixel()再扩展LCD_DrawLine()、LCD_FillRect()最后LCD_DrawImage()。LCD_DrawPixel()最简单但最容易翻车void LCD_DrawPixel(uint16_t x, uint16_t y, uint16_t color) { if(x 128 || y 160) return; // 边界检查 LCD_SetWindows(x,y,x,y); // 设置单像素窗口 LCD_WriteData(color); // 直接写GRAM }LCD_SetWindows()是关键它发0x2A和0x2B命令设置GRAM地址范围void LCD_SetWindows(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { LCD_WriteCmd(0x2A); // Column Addr Set LCD_WriteData(x1 8); LCD_WriteData(x1 0xFF); LCD_WriteData(x2 8); LCD_WriteData(x2 0xFF); LCD_WriteCmd(0x2B); // Page Addr Set LCD_WriteData(y1 8); LCD_WriteData(y1 0xFF); LCD_WriteData(y2 8); LCD_WriteData(y2 0xFF); LCD_WriteCmd(0x2C); // GRAM Write }这里x1,x2是列坐标0~127y1,y2是行坐标0~159LCD_WriteData()内部自动拉低CS、发DC1、写16位数据。LCD_FillRect()是性能瓶颈必须优化void LCD_FillRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) { if(x 128 || y 160) return; uint16_t x2 min(xw-1, 127); uint16_t y2 min(yh-1, 159); LCD_SetWindows(x,y,x2,y2); uint32_t count (x2-x1)*(y2-y1); // 用while循环代替for减少分支开销 while(count--) { LCD_WriteData(color); } }实测填满128×160全屏20480像素用此函数耗时约180msSCK2MHz而用LCD_DrawPixel()逐点画要3.2秒——快18倍。这就是“批量写GRAM”的威力。4.4 刷图优化实战双缓冲与DMA的取舍之道当项目需要动态刷新如鱼缸温度曲线单缓冲会闪屏。双缓冲是标准解法申请两块uint16_t frame_buffer[128*160]前台缓冲渲染后台缓冲刷屏用memcpy()切换。但STM32F1 RAM仅20KB两块缓冲要16KB只剩4KB给系统——太奢侈。我的折中方案是单缓冲局部刷新。只重绘变化区域比如温度数值框20×30像素用LCD_FillRect()清空旧值再LCD_DrawString()写新值。实测单次刷新5ms人眼无感。若真要用双缓冲F4系列可上DMA配置DMA从SRAM搬运数据到GPIO端口但ST7735R不支持DMA直接写GRAM仍需CPU发0x2C命令。所以DMA收益有限不如优化算法。我做过对比F4用DMA刷全屏120msF1用CPU刷180ms差距仅60ms而DMA占用1个通道2KB RAM性价比不高。5. 常见问题与排查技巧实录那些让我熬夜到三点的坑5.1 屏幕不亮/全白/全黑电源与复位的隐秘战争现象上电后屏无反应或亮白屏、黑屏。排查链测LED背光用万用表测LED与GND电压应为3.3V。若为0V检查LED供电电路常串联限流电阻电阻虚焊会导致无光若为3.3V但无光LED灯珠坏。抓RST波形示波器看RST引脚上电时应有100ms低电平脉冲。若没有检查RST初始化代码RST_LOW(); delay_ms(100); RST_HIGH();是否被执行或RST引脚是否被其他外设占用。查CS电平逻辑分析仪看CS初始化时应有规律的高低电平切换。若CS恒高说明CS引脚初始化错误如设成了浮空输入若恒低说明CS_LOW()后没执行CS_HIGH()。验SCK频率示波器测SCK应有2MHz方波。若无波形检查SCK引脚初始化是否设为推挽输出或SPI_WriteByte()是否被编译器优化掉加volatile修饰data变量。实操心得我曾为一块屏折腾两天最后发现是RST引脚焊锡桥接了相邻的PA1ADC1_IN1导致RST被ADC外设拉低。用热风枪重焊RST焊盘问题消失。所以硬件焊接质量永远是第一排查项。5.2 花屏/错位/颜色异常时序与寄存器的精密博弈现象图像撕裂、偏移、色块错乱。核心原因SCK相位CPOL/CPHA与ST7735R不匹配或0x36寄存器设置错误。速查表现象最可能原因解决方案图像左右镜像0x36值错误如用了0x00而非0x60改为LCD_WriteData(0x60)图像上下颠倒0x36的MV位bit6未置位0x60中bit61已包含颜色发紫缺绿0x3A设为0x0312位但发16位数据改0x05并确认LCD_WriteData()发2字节竖线干扰SCK上升沿采样时机不准在SCK_HIGH()后加delay_ns(20)确保数据稳定终极验证法用逻辑分析仪抓CS、SCK、MOSI三线对照ST7735R时序图。重点看CS下降沿到第一个SCK上升沿的间隔tCSS必须≥10nsSCK高/低电平宽度必须对称。我用Saleae Logic 8抓过发现某次编译优化后SCK_HIGH()和MOSI_HIGH()指令被重排导致数据建立时间不足——关掉编译器优化-O0问题解决。5.3 刷图卡顿/闪烁CPU负载与缓冲策略的平衡术现象动态刷新时明显卡顿、闪烁。根源LCD_FillRect()等函数阻塞CPU导致其他任务如UART接收、ADC采样被延迟。解决方案
返回列表