ST7789旋转屏幕适配不同摆放角度
本文详解ST7789显示屏的旋转原理与实现方法,通过MADCTL寄存器配置实现0°、90°、180°、270°方向切换,并提供C语言代码示例及坐标映射处理方案,帮助开发者解决嵌入式项目中因安装角度导致的显示异常问题。
ST7789旋转屏幕适配不同摆放角度
你有没有遇到过这样的情况:辛辛苦苦把代码写好,UI也调得漂漂亮亮,结果一上电——文字横着走、图标倒着显示?😅
别急,大概率不是你画图的问题,而是你的 ST7789 屏幕方向没对齐 !
这在嵌入式开发里太常见了。尤其是做智能手表、手持设备或者DIY小屏项目时,物理安装位置五花八门:竖着装、横着放、甚至 upside down 🙃……而 ST7789 这颗“明星”驱动芯片,默认可不管你这些,它只认一个方向:左上角为原点,从左到右、从上到下。
所以,要想让 UI 始终正对着用户,就得靠我们手动“掰”过来 —— 也就是实现 屏幕旋转适配 。好消息是,ST7789 硬件本身就支持这个功能,只需要动几个寄存器位,再配合一点坐标映射逻辑,就能轻松搞定 0°、90°、180°、270° 四个方向自由切换!
那它是怎么“转”的?
先别急着写代码,咱们得搞清楚背后的工作原理 💡
ST7789 内部有个关键寄存器叫 MADCTL(Memory Access Control) ,地址是 0x36 ,它的作用就是控制帧内存(GRAM)如何映射到实际的屏幕像素点上。换句话说,它决定了“我往内存里写的数据,到底出现在屏幕哪个位置”。
这个寄存器有几位特别重要:
| Bit | 名称 | 含义 |
|---|---|---|
| 7 | MY | 行方向:0=顶→底,1=底→顶 |
| 6 | MX | 列方向:0=左→右,1=右→左 |
| 5 | MV | 是否交换 X/Y 轴:0=不换,1=互换 |
| 3 | RGB | 颜色顺序:0=BGR,1=RGB |
看到没?光靠这三个标志位(MX/MY/MV),就可以组合出四种常见的旋转模式 👇
四种旋转模式配置一览
| 角度 | 描述 | MX | MY | MV | MADCTL 值(Hex) | 实际分辨率(W×H) |
|---|---|---|---|---|---|---|
| 0° | 默认方向 | 0 | 0 | 0 | 0x00 |
240×320 |
| 90° | 左旋90° | 1 | 0 | 1 | 0x60 |
320×240 |
| 180° | 倒置 | 1 | 1 | 0 | 0xC0 |
240×320 |
| 270° | 右旋90° | 0 | 1 | 1 | 0xA0 |
320×240 |
⚠️ 注意:有些模组出厂就设定了特定方向(比如某些圆形表盘默认是 90°),所以在初始化时一定要重新设置 MADCTL,避免“我以为是对的,其实早就偏了”。
最关键的是 MV 位 ——它负责是否交换 X 和 Y 轴。比如你要从竖屏变横屏,就必须启用 MV=1 ,否则只是镜像翻转,根本达不到旋转效果。
而且这一切都是 硬件级操作 !不需要你去搬动 framebuffer 里的每一个像素,CPU 几乎零开销,效率拉满 ✅
上代码!让屏幕听话转动
下面这段 C 代码适用于 ESP32、STM32、RP2040 等主流 MCU 平台,使用 SPI 接口通信,结构清晰、易于移植。
#include <stdint.h>
#include "st7789.h"
// MADCTL 位定义
#define MADCTL_MY 0x80 // 行地址顺序反转(Bottom to Top)
#define MADCTL_MX 0x40 // 列地址顺序反转(Right to Left)
#define MADCTL_MV 0x20 // XY轴交换(Vertical ↔ Horizontal)
#define MADCTL_RGB 0x08 // 使用RGB颜色顺序(否则为BGR)
// 全局状态变量(假设已定义 st7789 结构体)
extern struct {
uint16_t width;
uint16_t height;
uint8_t rotation;
} st7789;
static void st7789_write_command(uint8_t cmd);
static void st7789_write_data(const uint8_t *data, size_t len);
/**
* @brief 设置屏幕旋转角度
* @param rotation 0=0°, 1=90°, 2=180°, 3=270°
*/
void st7789_set_rotation(uint8_t rotation) {
uint8_t madctl = 0;
// 取模 4,防止非法输入
rotation &= 3;
switch (rotation) {
case 0:
madctl = 0x00; // 正常方向
st7789.width = 240;
st7789.height = 320;
break;
case 1:
madctl = MADCTL_MX | MADCTL_MV; // 90° 左旋
st7789.width = 320;
st7789.height = 240;
break;
case 2:
madctl = MADCTL_MY | MADCTL_MX; // 180° 倒置
st7789.width = 240;
st7789.height = 320;
break;
case 3:
madctl = MADCTL_MY | MADCTL_MV; // 270° 右旋
st7789.width = 320;
st7789.height = 240;
break;
}
// 设置颜色顺序(多数模组用RGB)
madctl |= MADCTL_RGB;
// 发送命令并写入值
st7789_write_command(0x36); // MADCTL register
st7789_write_data(&madctl, 1);
// 保存当前旋转状态
st7789.rotation = rotation;
}
📌 小贴士:
- 每次旋转后记得更新 width 和 height ,不然绘图画出界都不知道 😅
- 如果发现颜色发紫或偏红,可能是 RGB/BGR 搞反了,试试把 MADCTL_RGB 改成 0
图形绘制前的坐标魔法
光改寄存器还不够!如果你在程序中调用了 draw_line(x1,y1,x2,y2) ,传进去的坐标还是“逻辑坐标”,必须先转换成当前方向下的“物理坐标”。
所以我们需要一个坐标变换函数:
/**
* @brief 将逻辑坐标转换为物理屏幕坐标(根据当前旋转)
* @param x IN/OUT: 输入原始X,返回映射后的X
* @param y IN/OUT: 输入原始Y,返回映射后的Y
*/
void st7789_transform_point(int16_t *x, int16_t *y) {
int16_t orig_x = *x;
int16_t orig_y = *y;
switch (st7789.rotation) {
case 1: // 90° CCW (逆时针)
*x = orig_y;
*y = st7789.width - 1 - orig_x;
break;
case 2: // 180°
*x = st7789.width - 1 - orig_x;
*y = st7789.height - 1 - orig_y;
break;
case 3: // 270° CCW (等价于 90° CW)
*x = st7789.height - 1 - orig_y;
*y = orig_x;
break;
default: // 0°
// 不做变换
break;
}
}
🎯 使用场景示例:
void draw_pixel(int16_t x, int16_t y, uint16_t color) {
st7789_transform_point(&x, &y); // 先转坐标!
// 然后再写GRAM...
set_window(x, y, x, y);
send_pixel(color);
}
💡 提醒:如果你用的是 LVGL、TFT_eSPI 或者 Adafruit GFX 这类图形库,它们大多已经内置了旋转支持,只需调用 setRotation() 即可,底层会自动处理坐标映射。
实战中的那些坑 🕳️,我都替你踩过了
别以为改个寄存器就万事大吉,实际调试中一堆诡异问题等着你……
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 显示倒置或镜像 | MADCTL 配置错误 | 对照表格检查 MX/MY/MV 组合 |
| 文字歪斜但非整90度 | 忘了改 width/height | 旋转时务必同步更新尺寸变量 |
| 只能画半屏或边缘截断 | 坐标未经过 transform | 所有绘图接口前加坐标转换 |
| 屏幕偏红/蓝(色彩异常) | RGB vs BGR 混淆 | 修改 MADCTL 的 RGB 位 |
| 初始化后黑屏无反应 | 复位时序不对 or 缺少延时 | 严格按照 datasheet 加 delay |
| 触摸坐标和显示不匹配 | 触摸IC未同步旋转 | XPT2046/FT6236也要做坐标校准 |
🔧 特别提醒:某些便宜模组使用的 ST7789 兼容芯片(如 ILI9341 改头换面版)行为可能略有差异,建议以实际测试为准,必要时抓 SPI 波形分析。
设计建议与最佳实践 ✅
想写出健壮又灵活的驱动代码?收下这几条经验之谈:
1. 封装统一 API
提供类似 st7789_set_rotation(angle) 的接口,对外隐藏底层细节,方便后期维护和移植。
2. 避免运行时频繁切换方向
每次旋转都要重设 CASET/PASET(列/页地址),影响性能。除非是电子相框这类需求,否则建议在初始化时定死方向。
3. 兼容多种分辨率模组
别只盯着 240×320!现在还有 135×240 圆角屏、160×80 条形屏……只要用 ST7789 驱动,同一套旋转机制照样适用。
4. 触摸屏联动校准
如果搭配 XPT2046 或 FT6236,记得触摸数据也要做同样的坐标变换,可以用矩阵仿射变换统一处理:
// 示例:90° 旋转下触摸校准
touch_x = raw_y;
touch_y = SCREEN_WIDTH - 1 - raw_x;
5. 启用双缓冲提升体验
尤其是在播放动画或视频时,单缓冲容易撕裂。虽然 ST7789 本身不带显存,但可以用 MCU 外部 RAM 实现双 buffer + DMA 刷屏,丝滑多了~
最后说点掏心窝的话 💬
很多人觉得“驱动个屏幕而已,有啥难的?”
可真当你面对一块歪七扭八的显示屏时,才知道什么叫“细节决定成败”。
ST7789 的旋转机制看似简单,实则融合了硬件控制与软件抽象的巧妙设计。掌握它,不只是为了修一个显示方向,更是理解嵌入式系统中 “逻辑与物理分离” 的经典范例。
你在代码里画的是 (10, 20) ,但它最终出现在哪里,取决于 MADCTL 怎么解释这段数据。这种“间接映射”的思想,在 GPU、GUI 框架、甚至操作系统图形子系统中都随处可见。
所以啊,别小看这块小小的 TFT 屏幕 🖼️,它可是通往更广阔嵌入式世界的窗口之一。
下次你看到 TTGO Watch 上那块精致的小表盘,或是 Pico Display Pack 上流畅滚动的信息流,背后很可能就有 ST7789 默默地转了个身,只为让你看得更舒服一点 ❤️
“优秀的工程师,总能让硬件乖乖听话。” —— 而你现在,已经离这个目标更近了一步 😉
更多推荐



所有评论(0)