你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

#双节活动#STM32F429-DISCO板载LCD绘制

[复制链接]
shyi 发布时间:2026-11-4 21:12

用 STM32F429 的 LTDC 在屏上放一张中秋图

板子是 STM32F429I-DISCOVERY(MB1075-C01),屏是板上自带的 ILI9341,240×320。

要做的事很简单:让一张图出现在屏上,再让几个字动起来。下面按实际动手的顺序写。


一、先把屏点亮

F429 的 LTDC 是直接驱屏的外设,不走 SPI。它需要四样东西:

时钟。 LTDC 的像素时钟来自 PLLSAI,不是主 PLL。这跟 SYSCLK 是两套独立的时钟,改 HCLK 不影响屏的刷新率。算的时候注意是三级分频:VCO / PLLSAIR / PLLSAIDivR,最后一级容易漏。

时序。 横向 240 有效 + 前后沿和同步脉冲,纵向 320 一样。这些参数照屏的手册抄,凑错一个就会出现画面偏移或者边缘滚动。

引脚。 18 根数据线(R2-R7、G2-G7、B2-B7)加 4 根控制线(HSYNC、VSYNC、CLK、DE)。RGB565 只用到 16 位,低位那两根可以不接。

帧缓冲。 一块存像素的内存,LTDC 每帧从这儿读。这块必须放 SDRAM——240×320×2 是 150KB,片内 SRAM 放不下。

**SDRAM 要先初始化好。F429 片上只有 256KB,这个屏一张全屏图就 150KB,所以外挂的 IS42S16400J(8MB,映射到 **0xD0000000)是必需的。

到这一步屏上应该是一片纯色。如果连纯色都刷不出来,后面的不用看。


二、图怎么变成数据

屏要的是 RGB565:每个像素 16 位。计算机里的图片是 24 位(RGB888),要压。

压的时候丢了什么

R 5 位、G 6 位、B 5 位。绿色多给一位,因为人眼对绿色最敏感。

def rgb888_to_rgb565(r, g, b):
    return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3)

& 0xF8 砍掉低三位,>> 3 是蓝通道右移三位。就这么简单。

有个坑叫字节序

我一开始刷上去,整屏颜色像反相了——深的变浅,红的变青。

**原因是 CubeMX 生成的 **ltdc.c 里配的是:

hltdc.Init.PixelFormat = LTDC_PIXEL_FORMAT_RGB565;

注意不是 _SWAPPED。 这两者差在 LTDC 内部要不要做字节组装:

  • RGB565:半字直接读,数组里存 0x00A4,FLASH 里是 A4 00,读回来还是 0x00A4,对。
  • _SWAPPED:按 (byte0<<8) | byte1 组装,读成 0xA400,红蓝就换了。

所以生成数组时****不要做字节交换。

不确定的话做个测试图:画红、绿、蓝三条竖杠刷上去。红变蓝就是反了。

抖动

直接砍位会产生色带——大片渐变的地方出现一条条断层。中秋图的夜空正好是大片深蓝,问题特别明显。

解决办法是抖动:在量化前给每个像素加一点随机偏移,让误差被打散成"噪声",眼睛就看不出来了。

常用的 Floyd-Steinberg 在这里不好使。它要把误差传给右、左下、下、右下四个像素,在大片纯色区域会织出规则的螺旋纹(业内叫虫子纹)。夜空就是大片纯色,正好中招。

改用 8×8 Bayer:位置固定,同一个坐标永远得到同一个值,没有状态。

def bayer8(x, y):
    """8x8 Bayer 矩阵,返回 0..63"""

三条通道共用一个抖动值。如果各抖各的,抖动就变成彩色噪点了。


三、中文怎么画

为什么不用现成的字库

LVGL 自带字库,但中文要么没有,要么得自己裁。而且我只用十几个字,不想为了它把整套字库塞进 FLASH。

所以自己取模。用 Windows 带的楷体 STKAITI.TTF,只取需要的那几个字。

A8 而不是 1bpp

1bpp 每像素只有黑白两档,28px 的楷体撇捺全是锯齿。A8 用一字节存"覆盖度"(0~255),笔画边缘就平滑了。

代价是体积涨 8 倍:一个字 1024 字节,12 个字 12KB。2MB FLASH 里占 0.6%,无所谓。

抗锯齿有个反直觉的地方

用 Python 的 PIL 画字,fill=255,看着是纯白。但实际存下来的点阵里,笔画中心的像素也到不了 255。

因为 FreeType 算的是"这个像素被字形覆盖了多少"。笔画宽度不是整数像素,中心那个像素可能只被覆盖了 70%。

实测 28px 的「中」字,1024 个像素里:

满格(255)的像素 平均覆盖度
不描边 15 156
加一圈描边 147 205

后果是:上屏的字色永远是"掺了背景色的暗金",到不了想要的正金色。

解决办法是加同色描边。这不是加粗,是把丢掉的满格像素补回来。


四、透明怎么处理

RGB565 没有 alpha 通道。要把字叠到图上,只能约定"某个颜色代表透明"。

**行业惯例是用品红 **(255, 0, 255),因为自然界几乎没有这个颜色,不会和图里的内容撞。

生成的时候把要透明的部分涂成品红,刷上屏时让硬件跳过这些像素。F429 的 DMA2D 外设正好能干这事,叫 Color Mode 抠色。

**顺序是:**先把背景图铺满全屏,再用抠色模式把带品红底的字叠上去。

有个细节容易忽略:抠色的时候 DMA2D 要读"屏幕上已有的内容"当背景再次计算,所以背景地址就填目标地址本身。填错的话会出现随机位置的鬼影。


五、让字动起来

需求是"一直显示中秋画面",所以是死循环,不是播完就退。

每帧做什么

  1. 把背景图重新铺一遍(150KB,约 0.9ms)
  2. 画几颗闪烁的星
  3. 画一行字
  4. 等到下一帧

为什么要整屏重铺,而不是只擦掉字?因为字在淡入时位置每帧都在变(往上浮 10 像素),擦除要记住上一帧每块矩形,漏一块就留残影。0.9ms 换"绝不会有残影",这笔不亏。

一个常见的错误写法

/* ❌ 这样写,动画会越跑越慢 */
render();
HAL_Delay(33);

HAL_Delay(33) 的意思是"从现在起等 33 毫秒",不是"等到下一帧该出现的时刻"。所以帧周期 = 渲染时间 + 33ms。某帧超时了,后面就一直慢下去,越跑越慢。

正确做法是对齐到绝对时刻:

line_frame++;
{
    uint32_t next = t0 + line_frame * ANIM_FRAME_MS;
    uint32_t now  = HAL_GetTick();

    /* 有符号比较,避免 tick 回绕时死等 49 天 */
    if ((int32_t)(next - now) > 0)
    {
        HAL_Delay(next - now);
    }
    elapsed = HAL_GetTick() - t0;
}

超时的那帧不再额外等,后面自动追上,平均帧率就锁死了。

**那句 **(int32_t) 别省。uwTick 是 32 位,约 49.7 天回绕一次,无符号相减会得到一个巨大的数,HAL_Delay 就睡死了。

逐字淡入

「中秋快乐」四个字,第 0/1/2/3 个字分别在 0/120/240/360 毫秒开始淡入,每个用 600 毫秒,到 960 毫秒全部完成。

亮度和位移用同一个时间参数控制,视觉上就是"一边往上浮一边变亮",比分成两段动画自然。---

六、最后占了多大

RAM FLASH
加图前 85,472 B 386,660 B
加图后 85,472 B 546,452 B

RAM 一个字节没涨。因为图和字库都声明成 const,编译器把它们放进只读段,只读段在 FLASH 里,运行时不需要搬到 RAM。

F429 有 2MB FLASH、256KB SRAM,常量放 FLASH 不占 RAM,所以 150KB 的图直接塞进去完全没问题。

编完可以看一眼符号表确认:

08052280 00025800 R g_lcd_img_moon     ← 153,600 B = 240×320×2
0807aa80 00000090 R g_font_kai28       ←     144 B = 12 × 12

数字对得上就说明没搞错。

**还有一处:图的数组要加 **__attribute__((aligned(32)))。LTDC 和 DMA2D 都要求源地址 32 字节对齐,不加的话会走慢路径,严重时直接报错。


七、换一张图

  1. **图丢进 **assets/images/
  2. **跑 **python tools/img2rgb565.py 图片名.png --name 名字
  3. CMakeLists.txt 里加一行 App/Src/lcd_img_名字.c
  4. App/src/app_lcd_img.c 的 s_imgs[] 里注册一行
  5. 重新跑 cmake --preset,只点编译是不认新文件的

**字库同理,改 **app_lcd_anim.c 里的文案,字库外的字画不出来,要重跑 font2bitmap.py --chars ...。


八、几件记一下

  • 屏不亮先查三样:像素时钟对不对、时序参数对不对、帧缓冲地址在哪里
  • 屏亮但花屏,先看形态:整屏反色查字节序,边缘毛刺查透明色,斜切查偏移
  • **不用 **HAL_Delay 做帧同步
  • 常量放 FLASH,不占 RAM
赞 收藏 评论0 发布时间:2026-11-4 21:12

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版