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 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 默默地转了个身,只为让你看得更舒服一点 ❤️

“优秀的工程师,总能让硬件乖乖听话。” —— 而你现在,已经离这个目标更近了一步 😉

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐