裸机MCU驱动OLED进阶:用DMA刷新画面,CPU占用率直降80%
裸机跑SSD1306最典型的写法是在主循环里逐字节I2C/SPI发送,1024字节全屏刷一次CPU全程被外设阻塞住,主业务全卡着。要降占用,核心不是“刷得快”,是把“数据搬运”交给DMA,CPU只改显存、只在传输完成中断里换缓冲。
一、先留一份本地显存
SSD1306内部有GDDRAM,但MCU侧必须维护一份镜像缓冲,所有画点/字符/图形先改这份缓冲,再统一下发。128×64屏显存=8页×128=1024字节。
uint8_t oled_gram[8][128]; // 当前显示内容
uint8_t oled_dma_buf[1024+1]; // 0x40控制字节 + 数据
页寻址模式是默认模式,按页设B0~B7+列地址;全屏刷建议切水平寻址(0x20 0x00),一次连续写1024字节最省命令开销。
二、SPI DMA搬运整帧
SPI屏最适合DMA:CLK一拉,DMA从内存搬字节到SPI DR,CPU中途完全脱手。I2C屏也能用DMA,但要处理控制字节拆分、I2C事件中断、和别的I2C传感器互斥,新手建议先SPI。
刷整帧流程:
填控制字节0x40到dma_buf[0]
把oled_gram按页拼进dma_buf[1..1024]
发“写GRAM”命令0x2C(I2C)或直接拉DC=1(SPI)
启动DMA
传输完成中断里置ready
void oled_start_dma(void){
oled_dma_buf[0] = 0x40;
int p=1;
for(int pg=0;pg<8;pg++)
memcpy(&oled_dma_buf[p], oled_gram[pg], 128), p+=128;
HAL_SPI_Transmit_DMA(&hspi1, oled_dma_buf, 1024+1);
}
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef*hspi){
if(hspi->Instance==SPI1) oled_busy=0; // 标记可准备下一帧
}
CPU占用从“搬1024字节期间全阻塞”变成“只调一次启动函数”,传输期去做按键扫描、传感器解算都行。
三、双缓冲避免花屏
单缓冲最大坑:DMA还没发完,主循环又开始改oled_gram,屏幕会花。正解开两块显存,一块给DMA发,一块给GUI写:
uint8_t gram_front[8][128]; // DMA正在发
uint8_t gram_back[8][128]; // CPU正在画
volatile uint8_t *tx_ptr;
void oled_swap(void){
if(oled_busy) return; // DMA没完不换
uint8_t (*tmp)[128]=gram_front;
gram_front=gram_back; gram_back=tmp;
tx_ptr=(uint8_t*)gram_front;
oled_start_dma();
}
画图函数永远写gram_back,主循环周期到点调oled_swap()。这样“画下一帧”和“发上一帧”并行,不撕屏。
四、局部刷新比全屏更省
不是每帧都要1024字节。状态栏、菜单、传感器数值往往只动某几页:
void oled_refresh_page(uint8_t page){
oled_set_pos(0,page);
oled_dc_high();
HAL_SPI_Transmit_DMA(&hspi1, oled_gram[page], 128);
}
只刷第2页(标题)、第6~7页(底部状态),一帧数据量从1KB掉到两三百字节,DMA启动次数和总线占用都降下来。
五、裸机落坑清单
DMA没完就改缓冲:花屏根源,换缓冲前必须看完成标志
局部数组当DMA缓冲:函数返回内存没了,DMA还在读→硬错
I2C屏和传感器共线:HAL_I2C_Transmit_DMA和传感器读抢总线冲突,OLED独占总线更稳
Cache没清:M7带Cache时DMA读前Invalidate,写后Clean
DC脚时序错:SPI发数据前DC必须提前拉高,DMA启动后别再动DC
阻塞等DMA:裸机别while等状态,真要等就退化成轮询,失去意义
SPI波特率过高:OLED模块一般撑8~10M,F1/F4别直接上36M
没开完成中断:只启动不处理TxCplt,双缓冲切换永远不触发
六、实测收益
F103+SPL写法下,阻塞SPI刷1024字节约2~3ms CPU全占;改DMA后这2~3ms主循环完全空闲,只付出一次启动+一次中断,CPU占用按主循环业务占比算能降一大截,标称“直降80%”在工程里是合理的。
裸机OLED用DMA的本质就三句话:本地显存分离绘图与发送、DMA搬字节CPU脱手、双缓冲在TxCplt里换指针。全屏刷保底,局部刷省带宽,中断里别干重活——这样哪怕主循环还跑按键、ADC、CAN解析,OLED也不卡顿。





