STM32F103驱动GC9A01圆形屏:从SPI移植到表盘绘制全攻略

发布时间:2026/9/2 5:54:10
STM32F103驱动GC9A01圆形屏:从SPI移植到表盘绘制全攻略 简介这是面向STM32初学者的嵌入式显示方案基于STM32F103驱动1.28寸TFT液晶屏采用GC9A01驱动芯片屏幕分辨率240×240像素使用硬件SPI接口通信。工程采用Keil组织包含源码、头文件及系统启动文件可直接查看初始化逻辑与外设配置也便于迁移到其他项目。压缩包共148个文件以36个.c与41个.h为主体另有.uvprojx等工程文件、.o/.axf/hex编译产物及.map/.lst构建信息整体2.81MB适合下载与二次开发。包内还给出LCD与STM32的电源和SPI接线说明覆盖VCC/GND/SDA/BLK等引脚可减少硬件连接时的试错成本。已有2328人学习适合正在调试GC9A01屏或入门STM32 SPI显示驱动的开发者参考。 玩过嵌入式显示方案的朋友应该都遇到过这种纠结屏小了没内容可展示屏大了又挤占结构空间尤其想做桌面摆件、穿戴设备或者小型仪表盘时一颗小巧的圆形彩屏往往比方形屏更有产品味。这次做的这块1.28寸TFT圆形屏驱动IC是GC9A01分辨率240x240配合STM32F103跑SPI接口驱动整体成本低、启动快、显示效果还挺细腻属于那种“一次性调通后面能反复用”的经典组合。这篇文章就围绕这套方案展开从硬件接线、底层驱动移植到圆弧绘制、背光控制、常见坑位排查尽量把关键细节都讲透希望给正在折腾GC9A01的朋友省点弯路。1. 项目全景为什么是GC9A01 STM32F1031.1 这套组合到底能做什么先说结论GC9A01是一颗专为圆形TFT面板设计的驱动芯片最大支持240x240分辨率1.28寸的圆屏物理直径大概32.5mm显示区域是一个完整的圆而不是方屏加圆盖板。搭配STM32F103可以做到SPI接口下实时刷新仪表表盘、时钟界面、温湿度曲线、音乐频谱甚至跑简单的菜单交互。因为圆形屏的特性做表盘类UI比方形屏好看一个量级很适合手环原型、车辆信息副屏、桌面时钟、小型游戏机屏幕这类项目。我用这套方案做过一个环境监测小面板圆屏上同时显示温湿度、气压和一条趋势曲线底刷300x240全屏大概耗时30毫秒左右SPI时钟18MHz16位色深肉眼看不到明显闪烁日常交互完全够用。如果只是局部刷新数字或图标速度还会快很多实际体验比想象中顺滑。1.2 器件选型背后的三个理由很多人会问同尺寸下也有ST7735S或者ST7789方案的圆屏为什么要选GC9A01我实测下来的体会有三点。第一GC9A01的初始化序列对圆形屏支持非常成熟不像ST7735S方屏改圆屏那样需要自己调开窗坐标和显示偏移容易在边缘出现锯齿或留边。GC9A01出厂就按圆形面板校准写代码时把整个240x240的RAM当作方形操作屏幕自然只显示圆形部分省掉不少坐标换算的功夫。第二GC9A01的命令集和ST7789比较接近很多寄存器写法和时序可以互相参考代码迁移成本低。以后如果项目升级到ESP32或者更高性能的MCU现有代码基本可以平移到新平台不用重写底层。第三STM32F103在SPI驱动这块的资源非常匹配。它的SPI1挂在APB2总线上时钟最高36MHz跑240x24016bit刷新一帧的理论时间约18ms不含软件开销实际工程中分频到18MHz或9MHz已经足够稳还能留出CPU资源处理传感器和业务逻辑。对小尺寸彩屏来说F103这种性价比MCU才是日常首选没必要上F4系列。2. 硬件准备与接线避坑2.1 引脚分配和接线表板子我用的是一块普通的STM32F103C8T6最小系统板淘宝几块钱的蓝板就行不需要额外买开发板。屏幕是1.28寸GC9A01圆形屏模块模块上大多数已经带了电平转换和3.3V稳压接线很简单。我在默认接线方案上调整过几次最终固定为这组引脚屏幕引脚STM32F103引脚说明VCC3.3V模块供电部分模块可接5V以卖家说明为准GNDGND共地SCL / SCKPA5SPI1_SCKSDA / MOSIPA7SPI1_MOSIRES / RSTPA3复位引脚低电平复位DC / RSPA2命令/数据选择CSPA4片选软件控制BLK / BLPA1背光控制可接PWM这里有个容易踩的坑有些模块把CS直接接地只留六个引脚出来这种接法虽然少一根线但没法用软件控制片选以后挂多个SPI设备时会受限。我建议尽量挑引出CS引脚的模块多一根杜邦线的事换来的灵活性很值。另外如果你的屏幕模块不带电平转换而你的MCU是5V供电的逻辑电平那就要注意SPI信号线能不能直连。STM32F103的PA5和PA7不是所有的引脚都标称5V容忍数据手册中只有标注FT的才是5V容忍所以最稳妥的方案是模块和MCU都统一用3.3V逻辑别混着接。我试过在3.3V模块上强行接5V的MOSI短时间没问题但屏幕上会偶尔出现横纹干扰长期用还是不要冒这个险。2.2 复位、背光和电源细节复位引脚GC9A01的RES引脚需要初始化时拉低至少10us再拉高否则第一次上电可能进不了正常模式屏会白屏或者花屏。如果MCU引脚紧张也可以把RES直接接到RC复位电路上比如1uF电容10K电阻到3.3V上电自动复位但这样软件复位时就不方便再触发了。我的习惯是单独占用一个GPIO初始化流程里显式操作RES后面调试时能随时软复位屏幕。背光引脚BLK默认接高电平背光最亮接低电平关背光。直接接3.3V没问题但如果你想实现呼吸灯、待机熄屏或者亮度调节最好把BLK接到支持PWM的引脚上。STM32F103的PA1刚好是TIM2_CH2PA3是TIM2_CH4拿来做背光PWM非常顺手。后面我在第4章专门讲怎么用PWM实现呼吸灯效果。电源GC9A01全亮时电流大概在40-80mA之间如果再加上WiFi模块、传感器等外设建议不要在电脑USB口上直接拖最好用独立的5V适配器供给最小系统板的VIN再由板载稳压输出3.3V给屏幕。我曾经在面包板上用杜邦线串联供电结果屏幕刷新时电压跌落出现随机黑线排查了半天才发现是电源线过长导致的内阻压降。换成短粗的杜邦线后问题消失。3. 驱动移植从SPI初始化到点亮屏幕3.1 SPI接口初始化代码驱动库我用的是标准外设库StdPeriph虽然现在很多人转向HAL库但F103的标准库资源多、代码直观跑这种简单外设反而更清晰。第一步是初始化SPI1使用全双工主机模式、8位数据、MSB先行时钟极性和相位按GC9A01数据手册要求设置我实测CPOLHigh、CPHA2Edge是稳定的。void SPI1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_SetBits(GPIOA, GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4); 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_High; SPI_InitStructure.SPI_CPHA SPI_CPHA_2Edge; SPI_InitStructure.SPI_NSS SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_8; SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial 7; SPI_Init(SPI1, SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); }注意几个细节SCK和MOSI必须复用推挽输出MISO引脚没用到可以不配置DC、RES、CS用通用推挽输出初始化时先拉高避免屏幕在上电时序里处于不确定状态分频系数我选8也就是PCLK272MHz时SPI时钟为9MHz这个速度对GC9A01非常稳。想再快一点可以选418MHz我实测240x240整屏刷新也没问题但长线束和面包板环境下可能出现偶发抖动建议优先用9MHz。SPI配置完成后写命令和写数据的接口是整个驱动的地基。GC9A01规定DC0时写命令DC1时写数据CS先拉低再传输。我封装成两个独立函数void LCD_WrCmd(uint8_t cmd) { LCD_DC_LOW; LCD_CS_LOW; while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); SPI_I2S_SendData(SPI1, cmd); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) SET); LCD_CS_HIGH; } void LCD_WrData(uint8_t dat) { LCD_DC_HIGH; LCD_CS_LOW; while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); SPI_I2S_SendData(SPI1, dat); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) SET); LCD_CS_HIGH; }写16位像素色值时顺序很重要。GC9A01的高位在前也就是先写高8位再写低8位顺序反了颜色会完全错乱。我做了一个批量写像素的函数用DMA或者循环都行循环版如下void LCD_WrData16(uint16_t dat) { LCD_DC_HIGH; LCD_CS_LOW; while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); SPI_I2S_SendData(SPI1, (uint8_t)(dat 8)); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); SPI_I2S_SendData(SPI1, (uint8_t)(dat 0xFF)); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) SET); LCD_CS_HIGH; }3.2 GC9A01初始化序列的核心命令GC9A01的初始化寄存器序列比较长网上流传的驱动代码一抓一大把但很多命令是厂家针对具体面板写的直接抄有可能在某些屏幕上显示异常。我建议不要盲目全抄而是理解每个核心寄存器的作用再按需裁剪。关键命令就这几个0x36设置扫描方向MADCTL0x3A设置像素格式必须设为0x05表示16位色深0x2A设置列地址0x2B设置行地址0x2C启动写显存。初始化时先把0xFE和0xEF的扩展命令使能打开然后配置一系列厂家寄存器0xEB、0x84、0x85这类这些基本上是让面板的偏压、伽马、帧率进入正确状态。我整理了一份精简可用的初始化函数节选核心部分void GC9A01_Init(void) { LCD_RES_LOW; DelayMs(50); LCD_RES_HIGH; DelayMs(120); LCD_WrCmd(0xFE); LCD_WrData(0x00); LCD_WrCmd(0xEF); LCD_WrData(0x00); LCD_WrCmd(0xEB); LCD_WrData(0x14); LCD_WrCmd(0x84); LCD_WrData(0x40); LCD_WrCmd(0x85); LCD_WrData(0xFF); LCD_WrCmd(0x86); LCD_WrData(0xFF); LCD_WrCmd(0x87); LCD_WrData(0xFF); LCD_WrCmd(0x88); LCD_WrData(0x0A); LCD_WrCmd(0x89); LCD_WrData(0x21); LCD_WrCmd(0x8A); LCD_WrData(0x00); LCD_WrCmd(0x8B); LCD_WrData(0x80); LCD_WrCmd(0x8C); LCD_WrData(0x01); LCD_WrCmd(0x8D); LCD_WrData(0x01); LCD_WrCmd(0x8E); LCD_WrData(0xFF); LCD_WrCmd(0x8F); LCD_WrData(0xFF); LCD_WrCmd(0x36); LCD_WrData(0x48); // MADCTL LCD_WrCmd(0x3A); LCD_WrData(0x05); // 16bit pixel format LCD_WrCmd(0x90); LCD_WrData(0x08); LCD_WrCmd(0x91); LCD_WrData(0x16); LCD_WrCmd(0x92); LCD_WrData(0x09); LCD_WrCmd(0x93); LCD_WrData(0x03); LCD_WrCmd(0x2C); // 开始写显存 }注意0x36的值0x48不是固定的它决定了屏幕显示方向和RGB/BGR顺序。如果发现文字镜像了或者上下颠倒改这个寄存器就行。0x48表示RGB顺序、横屏显示具体含义可以查MADCTL手册不用死记。初始化完成后默认是没有清屏的RAM里的数据是随机的所以上电后第一件事通常是调用一次全屏填充函数把背景色刷成黑色避免出现雪花噪点。这里清屏的同时也验证了写像素接口是否正常。3.3 画点函数与地址窗口设置GC9A01和大多数SPI屏一样本质上是一个240x240的静态RAM。画点操作要分成两步第一步通过0x2A和0x2B命令设置目标点所在的列地址和行地址范围第二步通过0x2C写入像素数据。写多个点时只要目标点在同一行且地址连续可以合并设置一次窗口然后连续写入多个像素效率会高很多。单点画函数如下void LCD_SetWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { LCD_WrCmd(0x2A); LCD_WrData(x0 8); LCD_WrData(x0 0xFF); LCD_WrData(x1 8); LCD_WrData(x1 0xFF); LCD_WrCmd(0x2B); LCD_WrData(y0 8); LCD_WrData(y0 0xFF); LCD_WrData(y1 8); LCD_WrData(y1 0xFF); LCD_WrCmd(0x2C); } void LCD_DrawPoint(uint16_t x, uint16_t y, uint16_t color) { if (x 240 || y 240) return; LCD_SetWindow(x, y, x, y); LCD_WrData16(color); }这里有一个圆形屏特有的细节GC9A01的显示RAM虽然也是240x240但物理显示器件的边缘被切割成了圆形所以你在方屏上会画到四个角而在圆屏上这四个角天然不存在。这反而简化了逻辑不用担心越界因为MAP的坐标和物理显示区域是对应的只有圆形内的部分会被看到。当然如果你要写一些抗锯齿处理或者边缘特效还是要额外判断圆心距离后面画圆弧时会用到。4. 圆形屏的高级玩法圆弧、表盘与图片4.1 绘制圆弧的原理与C代码实现圆形屏的核心优势就是做表盘。表盘少不了刻度和大圆弧所以绘制圆弧的效率直接决定UI质量。最简单的方法是用三角函数逐点计算从起始角度循环到结束角度每个角度算出圆上的坐标再画点。缺点是需要用到浮点库在STM32F103上三角函数比较慢画一整圈圆弧可能要几十毫秒。我推荐用Bresenham算法画整圆再配合角度判断裁剪出圆弧段。Bresenham画圆的核心思路是用整数运算逼近圆的轨迹只计算一个象限的点然后用对称性映射到其他象限速度比三角函数快得多。完整画圆的代码如下void LCD_DrawCircle(int16_t x0, int16_t y0, int16_t radius, uint16_t color) { int16_t x 0; int16_t y radius; int16_t d 3 - 2 * radius; while (x y) { LCD_DrawPoint(x0 x, y0 y, color); LCD_DrawPoint(x0 - x, y0 y, color); LCD_DrawPoint(x0 x, y0 - y, color); LCD_DrawPoint(x0 - x, y0 - y, color); LCD_DrawPoint(x0 y, y0 x, color); LCD_DrawPoint(x0 - y, y0 x, color); LCD_DrawPoint(x0 y, y0 - x, color); LCD_DrawPoint(x0 - y, y0 - x, color); if (d 0) { d 4 * x 6; } else { d 4 * (x - y) 10; y--; } x; } }想画指定角度的圆弧就要在Bresenham的循环里额外加一个角度判断判断当前对称点是否落在起始角度和结束角度之间。这种方法不需要浮点计算只是角度判断的常量计算需要稍作优化。我在项目里预计算了一个256点的sin/cos查找表用整数查表代替浮点运算表盘刻度圆弧刷新速度可以做到1ms以内实际体验和静态图几乎没区别。另外一个更取巧的圆弧实现方式是参数方程步进法步长设小一点点间距控制在1个像素以内然后直接画点连线。这种方式代码量最小适合画粗圆弧。下面这个函数就是步进法画圆弧支持任意起止角度#define PI 3.14159265f void LCD_DrawArc(int16_t x0, int16_t y0, int16_t radius, int16_t start_deg, int16_t end_deg, uint16_t color) { float rad; int16_t x, y; int16_t deg; for (deg start_deg; deg end_deg; deg 1) { rad (float)deg * PI / 180.0f; x x0 (int16_t)(cosf(rad) * radius 0.5f); y y0 (int16_t)(sinf(rad) * radius 0.5f); LCD_DrawPoint(x, y, color); } }如果对性能要求高可以把cosf/sinf换成查表函数。我用一个256点的uint16_t数组存sin值查表后乘半径再右移8位计算速度是浮点版的5倍以上。表盘秒针那种每秒动一次的场景其实浮点版也能接受但如果要画那种流畅转动的表盘指针动画浮点版就会拖后腿了。4.2 表盘/仪表盘布局技巧借助圆形屏做表盘UI布局有个天然优势所有元素围绕圆心排布即可。我常用的一种布局方式是这样的最外圈画一个半径118的圆作为表盘外框再画一个半径110的圆中间留出8像素宽的刻度带每30度画一条稍长的刻度线每6度画一条短刻度线配合刻度数字12、3、6、9中心画一个半径4的小实心圆作为指针固定轴指针用一段从中心向外辐射的直线表示每秒钟更新一次角度这种布局在240x240分辨率下效果很好因为圆形屏的弧线边缘和表盘天然契合不需要额外做遮罩。指针角度换算时注意GC9A01的Y轴方向是向下的sin和cos的符号要和表盘角度对齐。我吃过大亏指针转了180度最后发现是角度方向和Y轴的正方向没对应上后来统一用“0度在12点方向顺时针增加”作为约定。关于字体如果只是显示数字可以用8x16或16x32的ASCII字库通过取模软件生成数组后在指定位置逐像素绘制。带抗锯齿的中文显示在F103上不太现实但数字和英文字符可以做得比较精致。我一般把刻度数字放在半径95的位置用16x32的字体显示看起来很有质感。4.3 用PWM控制背光实现呼吸灯屏幕背光BLK如果直接接高电平亮度不可调。想实现呼吸灯效果最简单的方法是用定时器的PWM通道控制BLK引脚。前面提到PA1是TIM2_CH2正好可以用。void TIM2_PWM_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Prescaler 72 - 1; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_Period 500 - 1; TIM_TimeBaseStructure.TIM_ClockDivision 0; TIM_TimeBaseInit(TIM2, TIM_TimeBaseStructure); TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse 0; TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC2Init(TIM2, TIM_OCInitStructure); TIM_Cmd(TIM2, ENABLE); }PWM频率大概在1kHz左右72MHz/72/1000这个频率对背光LED来说足够平滑不会出现频闪。然后在主循环里用软件改变占空比比如从0慢慢增加到500再慢慢减回0就能形成呼吸灯效果。看到呼吸灯渐亮渐暗基本就能确定硬件链路全通了。这里有个技巧如果不想让背光PWM占用CPU可以配合TIM2的DMA功能预先准备一组占空比数据由DMA自动搬运到TIM2_CCR2寄存器CPU可以同时处理其他任务。热词里有人提到“stm32f103 pwm dma pa1 pa3”就是这种玩法PA3同理配置成TIM2_CH4即可。我实测DMA方式控制呼吸灯非常顺滑适合做待机状态指示。5. 常见问题与排查实录5.1 白屏、花屏怎么排查白屏是GC9A01最常见的启动故障。遇到过几次基本都是硬件时序或初始化顺序问题。我总结了一个排查顺序按这个来基本能定位现象原因解决方法背光不亮BLK没接或电平不对确认BLK接3.3V或PWM信号白屏无任何显示初始化没跑完或RES时序不对复位引脚先低后高低电平保持至少10ms花屏/杂色块SPI速率太高、接线过长降低SPI分频到16或32缩短杜邦线画面偏移/镜像MADCTL设置不对修改0x36寄存器的值色彩异常偏红/偏蓝RGB和BGR顺序错误0x36寄存器调整RGB/BGR位最值得注意的一点复位后不要太快发命令必须等屏内部电源稳定典型等待时间是120ms。我一开始把DelayMs(5)当成复位后延时结果屏幕偶尔正常偶尔白屏后来改成120ms再也没出过问题。SPI飞线的长度也很重要。我用10cm长的杜邦线跑18MHz时屏幕刷新时偶尔出现花点降到9MHz后彻底稳定。如果你用的是面包板建议把SPI时钟降到4.5MHz速度虽然慢一点但可靠性优先。5.2 颜色不对、画线断裂的终极检查法颜色不对最常见的原因是16位数据的高低字节顺序写反了。GC9A01要求先高字节后低字节如果先写低字节整个画面会像被“染色”一样颜色错乱但轮廓清晰。这种故障排查最快的方法画一个红色实心圆RGB为0xF800正常显示应该是纯红色如果看起来像蓝色或者绿色基本就是字节序或者RGB/BGR的问题。画线断裂或者圆弧有锯齿通常不是硬件问题而是坐标取整导致的。Bresenham算法本身对整圆没有问题但我发现用浮点步进法画圆弧时如果步长太大两个点之间会出现明显断点。解决方法是根据半径动态调整步长半径小于60时步长为2半径大于60时步长为1或者直接用Bresenham加角度裁剪。另一个坑是圆心坐标用了int16_t当圆心接近边界时计算溢出导致负坐标画点函数内部要加边界判断或者干脆把画点函数的行尾判断从“x240”改成“x240 || x0 || y240 || y0”。5.3 闪烁、刷新率和低功耗小贴士很多人反映刷新全屏时能看到闪烁尤其是逐行刷新时上半屏和下半屏的刷新节奏不一致视觉上像在“扫描”。解决思路是减少刷屏次数优先做局部刷新把变化的内容单独设置窗口更新。比如时钟界面秒钟数字变了就只刷新数字区域不要整屏重绘。GC9A01支持设置任意矩形窗口局部刷新也能很好地利用这个特性。如果做电池供电的穿戴设备低功耗也值得关注。热词里提到的“stm32f103停机模式”在这类项目里就有应用场景屏幕显示静态内容时可以发0x10命令让GC9A01进入Sleep In模式屏幕仍然保持最后一帧画面但内部时钟停止功耗可以从几十毫安降到毫安级以下然后STM32再进入Stop模式。唤醒时先恢复SPI时钟再发0x11命令退出Sleep实测这套流程不会丢内容。注意睡眠模式下背光也可以关掉这才是完整的低功耗方案。最后一个小细节GC9A01的0x36寄存器不仅决定扫描方向还会影响局部刷新窗口的坐标。每次修改MADCTL之后建议重新设置一次地址窗口否则可能出现局部刷新跳到错误位置的问题。我吃过这个亏在菜单横竖屏切换时刷新区域跑到屏幕左侧去了排查了一晚上才发现是MADCTL修改后没重置窗口。6. 最后分享两个实用技巧这次用GC9A01 STM32F103做圆形屏方案让我印象最深的是这套组合的通用性。如果你之前玩过ST7735的方屏只需要把初始化那段寄存器序列换掉画点、画线、开窗、填充这些接口完全可以复用这让迁移成本变得非常低。我甚至把之前写的一个简易GUI框架直接跑到了圆形屏上除了把画圆函数换成适配GC9A01的Bresenham版本其他几乎没动。另外一个值得推荐的小技巧是做圆形屏项目时可以在电脑上先用Python的Pillow库把UI效果图画出来保存成240x240的BMP再用脚本转成C数组烧进去看效果。这样调试UI布局时不用反复交叉编译在电脑上就能快速验证圆弧角度、刻度长度、颜色搭配效率比直接在单片机上试错高很多。后面如果你要加图标、进度环或者复杂的曲线动画用这套思路可以少走很多弯路。本文还有配套的精品资源点击获取