嵌入式显示旋转编程:无需修改画面数据实现90/180/270度画面切换
“旋转显示”常被理解成把帧缓冲整帧重排一遍,其实90/180/270这种直角旋转,大多数情况不用动原始画面数据——只改“控制器怎么读显存”或“flush时往哪写”。驱动IC支持MADCTL就零拷贝,LTDC不支持就放flush里做块旋转。
一、旋转的两层做法
控制器侧(首选):带GRAM的屏驱(ST7789/ILI9341/RM67162)改MADCTL,行列扫描方向变了,主控仍按逻辑坐标绘图,像素数据原样发。
主控侧软件旋转:LTDC/RGB屏没有硬件旋转,只能在GUI flush回调里把脏矩形转到物理帧缓冲对应位置。
选错路线要么白吃CPU,要么坐标全歪。
二、MADCTL:最干净的方案
MADCTL(0x36)三位决定方向:
MY(0x80):行方向翻转
MX(0x40):列方向翻转
MV(0x20):行列交换(90°关键)
ILI9341常见组合:
void lcd_set_rotation(int rot){
uint8_t v;
switch(rot){
case 0: v = 0x48; break; // 竖屏
case 90: v = 0x28; break; // 横屏 MV=1
case 180: v = 0x88; break;
case 270: v = 0xE8; break;
}
lcd_write_cmd(0x36);
lcd_write_data(v);
}
设完之后:
逻辑分辨率宽高互换(90/270时)
绘图API坐标不变
显存里的RGB数据一个字节都不用重排
地址窗口照常用0x2A/0x2B,驱动IC自己映射
注意MADCTL还管RGB/BGR位,换方向时别把颜色序弄反。
三、LTDC/RGB屏:flush里转
STM32 LTDC按行扫帧缓冲,硬件不旋转。 LVGL里正确姿势:
lv_display_set_rotation(disp, LV_DISPLAY_ROTATION_90);
flush回调里用LVGL自带旋转把渲染块搬进物理fb:
void disp_flush(lv_display_t*d, const lv_area_t*a, uint8_t*px){
lv_color_format_t cf = lv_display_get_color_format(d);
lv_area_t ra = *a;
lv_display_rotate_area(d, &ra); // 算物理目标区
uint32_t src_stride = lv_draw_buf_width_to_stride(
lv_area_get_width(a), cf);
uint32_t fb_stride = lv_draw_buf_width_to_stride(PHYS_W, cf);
uint8_t*fb = fb_base
+ ra.y1*fb_stride + ra.x1*lv_color_format_get_size(cf);
lv_draw_sw_rotate(px, fb,
lv_area_get_width(a), lv_area_get_height(a),
src_stride, fb_stride,
lv_display_get_rotation(d), cf);
lv_display_flush_ready(d);
}
要点:
用PARTIAL渲染模式,别用DIRECT——DIRECT直接写进fb,后面转不动
旋转开起来DMA2D flush失效,CPU做块拷贝
只转脏矩形,不是整屏重排,开销可控
Cache在外部SDRAM时要Clean/Invalidate
四、裸机无GUI:改DrawPixel映射
没LVGL时别重排显存,改写像素地址:
// 90°: (x,y) -> (H-1-y, x)
#define PXOFF(x,y) ((W*(H-1-y) + x)*2)
void put_px(int x,int y,uint16_t c){
*(uint16_t*)(FB + PXOFF(x,y)) = c;
}
所有上层画图函数调用put_px即可,业务层坐标仍是逻辑坐标。代价是每次写像素多一次运算,静态界面无所谓,动画密集场景还是走MADCTL或硬件叠加层。
五、触控坐标必须跟着转
最容易被忘:屏转了,触摸没转,点按钮全偏。
void touch_remap(int*rx,int*ry){
int tx=*rx, ty=*ry;
switch(cur_rot){
case 90: *rx = ty; *ry = PHYS_W-1-tx; break;
case 180: *rx = PHYS_W-1-tx; *ry = PHYS_H-1-ty; break;
case 270: *rx = PHYS_H-1-ty; *ry = tx; break;
}
}
规则跟MADCTL映射对称,校准时按当前方向标一次点就行。
六、落坑清单
LTDC屏硬等“硬件旋转”→没有,只能软件转
只改MADCTL不换逻辑宽高→GUI布局算错
DIRECT模式开旋转→块写进fb后再也转不了
触控坐标不映射→点击偏移
MADCTL把BGR位清了→颜色反
90°后地址窗口还按原W/H设→写串
整屏memcpy旋转→完全没必要,块旋转就行
SPI屏CS没拉住就改MADCTL→命令被当数据
七、选型结论
SPI TFT(ST7789/ILI9341):MADCTL优先,零拷贝零重排
RGB/LTDC屏:LVGL rotation + flush块旋转
纯裸机RGB:改DrawPixel地址映射或DMA步长
视频层旋转(DSS类):改ROW_INC/PIXEL_INC让DMA按列取数
直角旋转的本质不是“重画一帧”,是“换寻址”。原始画面数据保持不变,控制器或DMA按新方向读显存,CPU只付出一次寄存器配置或脏矩形块拷贝。真要任意角度才需要像素重采样,那是另一回事;90/180/270在工程里都不该动源帧缓冲。





