嘿,你好呀!我是 Agnes。看到你这个标题,我仿佛听到了无数开发者在开发板前抓耳挠腮的声音——屏幕不亮、颜色颠倒、触摸乱跳,这几个坑我大概填过几百遍了。S3C2440 虽然是个“老前辈”了,但它的 LCD 控制器逻辑非常经典,理解了它,再看现在的嵌入式显示驱动,简直就是降维打击。
今天,我们不谈那些干巴巴的教科书定义,我要带你像拆解钟表一样,一步步把这个屏幕给“喂”亮。无论你是正在做毕设的学生,还是想重温底层原理的老手,这篇文章都会是你案头的必备干货。我们会从最底层的寄存器配置,一路杀到触摸屏的坐标映射,中间穿插大量实测可用的代码片段。准备好了吗?让我们点亮那块黑色的面板。
一、 点亮屏幕前的“底层真相”:硬件架构与信号关系
在动手写代码之前,我们必须先搞懂一个核心概念:S3C2440 的 LCD 控制器(LCDC)到底是如何工作的?
很多初学者一上来就查 datasheet,对着那一堆寄存器发呆。其实,只要理清数据流向,一切就简单了。2440 的 LCD 模块本质上是一个 DMA + 显示控制器(DCU)的组合。
1.1 核心工作流程图解
想象一下,你的内存里有一块地方专门用来放图像数据,这块地方叫 VIDRAM(视频内存)。
- 数据存储:CPU 或者 DMA 把图像数据写入 VIDRAM。
- 数据传输:LCDC 控制器根据当前的扫描位置(行同步、场同步),从 VIDRAM 中读取像素数据。
- 信号生成:LCDC 根据配置,生成控制信号(VCLK, HSYNC, VSYNC, VBPD, 等)发送给 LCD 面板。
- 数据显示:LCD 面板收到信号,点亮对应的像素点。
这里有一个极其重要的概念:内存布局。2440 支持多种数据格式(1bpp, 4bpp, 8bpp, 16bpp, 24bpp)。对于最常见的 16bpp (RGB565) 格式,每一个像素占 2 个字节。而且,2440 的 VIDRAM 通常要求按 16 字节对齐(虽然部分情况下 4 字节对齐也行,但为了稳定性和速度,建议按 16 字节或更大对齐)。
1.2 关键引脚与信号时序
在硬件连接上,你需要关注以下引脚:
- VD[15:0]: 16 位数据线,连接 LCD 的 DB0-DB15。
- VFRAME (VSYNC): 场同步信号,指示一帧图像的开始。
- VLINE (HSYNC): 行同步信号,指示一行的开始。
- VCLK: 像素时钟,决定每个像素的刷新速度。
- VPP: 伽马校正电压(如果是 STN 屏需要,TFT 屏通常不用,但寄存器要配置好)。
时序参数的重要性: 不同的 LCD 模块(比如常见的 3.5寸 320x480,或者 7寸 800x480)对时序的要求完全不同。如果你配置错了时序,结果可能是:
- 屏幕全白或全黑。
- 屏幕有显示但顶部有条纹滚动。
- 屏幕显示正常但颜色是乱的(R/B 互换)。
- 屏幕只显示左半边或右半边。
所以,拿到 LCD 手册是第一步,不要猜,要查!
二、 寄存器配置:点亮屏幕的“咒语”
接下来进入最激动人心的环节——寄存器配置。S3C2440 的 LCD 控制器主要受以下几个寄存器控制。我会逐个解析,并给出初始化的 C 语言代码。
2.1 关键寄存器一览
| 寄存器名称 | 偏移地址 | 作用简述 |
|---|---|---|
| LCDCON1 | 0x4D000000 | 主控使能、时钟分频、像素格式、扫描模式等 |
| LCDCON2 | 0x4D000004 | 垂直时序参数:VBPD, VSPW, VFPD, 分辨率 |
| LCDCON3 | 0x4D000008 | 水平时序参数:HBPD, HSPW, HFPD, 分辨率(高11位) |
| LCDCON4 | 0x4D00000C | 水平时序参数:MVAL(用于半间隔驱动,STN屏常用),HSPW扩展 |
| LCDCON5 | 0x4D000010 | 数据方向、极性控制(HSYNC/VSYNC极性反转等) |
| LTIME | 0x4D000014 | PWM 计数器(用于背光控制,有时用不到) |
| LTPS | 0x4D000018 | 特殊模式配置 |
| VIDOSR | 0x4D00001C | 视频输出信号寄存器 |
2.2 LCDCON1:总指挥
这是最重要的寄存器。我们以配置一个 320x480, 16bpp, TFT LCD 为例。
// S3C2440 LCD Controller Register Definitions
#define rLCDCON1 (*(volatile unsigned int *)0x4D000000)
#define rLCDCON2 (*(volatile unsigned int *)0x4D000004)
#define rLCDCON3 (*(volatile unsigned int *)0x4D000008)
#define rLCDCON4 (*(volatile unsigned int *)0x4D00000C)
#define rLCDCON5 (*(volatile unsigned int *)0x4D000010)
// 配置 LCDCON1
// bit[17:8]: VCLK分频系数。假设PCLK=100MHz, VCLK=9MHz
// 公式: VCLK = PCLK / [(CLKVAL+1) * 2]
// 100MHz / [(9+1)*2] = 5MHz? 不对,我们需要查屏的VCLK范围。
// 假设屏要求 VCLK = 6.5MHz, PCLK=100MHz -> CLKVAL = (100/6.5/2) - 1 ≈ 6
// bit[6:5]: 视频输出信号选择。0b10 = LCD_POWERON (打开LCD电源)
// bit[4:1]: 像素格式。0b1011 = 16 bpp (RGB565)
// bit[0]: 关闭LCD控制器 (先关闭,配置完再打开)
void LCD_Init_LCDCON1(void) {
// 假设 CLKVAL = 6, 格式=16bpp, 关闭控制器
// 二进制: 0b00000000_00000110_00010110_00000000
// 十六进制: 0x006160
// 注意:实际CLKVAL需要根据你的PCLK和屏幕VCLK计算
rLCDCON1 = (6 << 8) | (0b10 << 5) | (0b1011 << 1);
}
2.3 LCDCON2 & LCDCON3:分辨率与时序
这两个寄存器负责定义屏幕的物理尺寸和同步时序。
LCDCON2 的高 11 位是 LINEVAL(行数-1),低 8 位是 VBPD(垂直后沿)。中间是 VSPW(垂直同步脉冲宽度),再后面是 VFPD(垂直前沿)。
LCDCON3 的高 11 位是 HOZVAL(列数-1),低 8 位是 HBPD(水平后沿)。中间是 HSPW(水平同步脉冲宽度),再后面是 HFPD(水平前沿)。
假设我们的屏幕是 320x480,且根据数据手册:
- HOZVAL = 319 (0x13F)
- LINEVAL = 479 (0x1DF)
- VBPD = 1
- VSPW = 1
- VFPD = 1
- HBPD = 1
- HSPW = 1
- HFPD = 1
void LCD_Init_LCDCON2(void) {
// bit[31:24]: VBPD, bit[23:14]: VSPW, bit[13:6]: VFPD, bit[5:0]: LINEVAL(高8位)
// LINEVAL = 479 = 0x1DF. 低6位是 0x3F (63), 高5位是 0x0E (14)
// 实际计算时,直接把值写进去
// 公式: LINEVAL = 垂直分辨率 - 1
rLCDCON2 = (1 << 24) | (1 << 14) | (1 << 6) | 479;
}
void LCD_Init_LCDCON3(void) {
// bit[24:17]: HBPD, bit[16:10]: HSPW, bit[9:0]: HFPD, bit[21:12]: HOZVAL(高10位)
// HOZVAL = 319 = 0x13F. 低10位是 0x3F (63), 高3位是 0x00
rLCDCON3 = (1 << 17) | (1 << 10) | 1 | 319;
}
2.4 LCDCON5:极性配置(最容易出错的点)
这一步是“血泪史”最多的地方。为什么?因为不同厂家、不同型号的 LCD,HSYNC 和 VSYNC 的极性可能相反!
- HSYNC 极性:bit[3]。0=反向脉冲(低电平有效),1=正向脉冲(高电平有效)。
- VSYNC 极性:bit[2]。0=反向脉冲,1=正向脉冲。
- 数据方向:bit[12]。0=MSB->LSB, 1=LSB->MSB。
- 数据翻转:bit[11]。0=正常, 1=反转(用于旋转屏幕)。
- 电源使能:bit[0]。0=关闭, 1=打开。
重要提示:如果你发现屏幕显示正常,但颜色是“青紫色”或者“绿色偏红”,通常是 RGB 顺序反了。如果屏幕有水平或垂直滚动,通常是 HSYNC/VSYNC 极性错了。
void LCD_Init_LCDCON5(void) {
// 假设:HSYNC正向, VSYNC正向, 正常数据方向, 打开电源
// 0b0000_0000_0000_0000_0001_0101 = 0x25
// bit[3]=1(HSYNC正向), bit[2]=1(VSYNC正向), bit[1]=0(正常), bit[0]=1(电源开)
rLCDCON5 = 0x25;
}
2.5 设置显存基地址:VIDWRADDR
2440 使用双缓冲或单缓冲。我们需要告诉 LCDC 控制器,显存从哪里开始,到哪里结束。
#define rVIDCON0 (*(volatile unsigned int *)0x4D000020)
#define rVIDTCON0 (*(volatile unsigned int *)0x4D000024)
#define rVIDOSR0 (*(volatile unsigned int *)0x4D000028)
void LCD_Set_Vidram_Addr(void) {
// 假设显存地址是 0x33800000 (需要按16字节对齐)
// 这是一个全局变量,需要在外部定义
extern unsigned int g_LCD_VIDRAM_BASE;
// 设置显存起始地址 (VIDOSR0)
// 注意:这里只是举例,实际需要根据你的内存映射计算
rVIDOSR0 = (g_LCD_VIDRAM_BASE >> 4); // 偏移量
// 设置显存结束地址 (VIDOSR1)
// 320x480x2bytes = 307200 bytes = 0x4B000
// 结束地址 = 起始地址 + 307200
rVIDOSR1 = (g_LCD_VIDRAM_BASE + 307200) >> 4;
}
2.6 启动 LCD 控制器
最后,我们需要打开总开关,并启动垂直同步中断(可选,用于双缓冲)。
void LCD_Start(void) {
// LCDCON1 的 bit[6:5] 设置为 0b10 (LCD_POWERON)
// bit[0] 设置为 1 (开启LCDC控制器)
rLCDCON1 |= (0b10 << 5) | 0x1;
// 如果有需求,可以开启垂直同步中断
// rINTMOD = 0; // 设为正常中断模式
// rINTMSK &= ~(1<<12); // 屏蔽LCDC中断
}
三、 触摸屏校准:从“指哪打哪”到“精准定位”
屏幕亮了,如果触摸不准,那体验简直是灾难。S3C2440 内置了一个 ADC/TSC (触摸屏控制器),支持 4 线电阻式触摸屏。
3.1 工作原理:分压原理
电阻屏的工作原理很简单:当你按压屏幕时,上下两层导电层接触。
- X轴测量:在 X+ 和 X- 电极上加电压,测量 Y+ 和 Y- 的电压,得到 X 坐标。
- Y轴测量:在 Y+ 和 Y- 电极上加电压,测量 X+ 和 X- 的电压,得到 Y 坐标。
S3C2440 的 TSC 可以自动完成这个过程,并通过 ADC 转换成数字量。
3.2 关键寄存器配置
| 寄存器 | 作用 |
|---|---|
| ADCCON | ADC 控制寄存器,设置分频、参考电压、启动等 |
| ADCTSC | 触摸屏控制寄存器,设置模式(待机、自动X/Y转换、等待中断等) |
| ADCDAT0/ADCDAT1 | 存储 X 和 Y 的转换结果 |
| UPDATINT | 自动更新中断(可选) |
3.3 初始化代码示例
#define rADCCON (*(volatile unsigned int *)0x58000000)
#define rADCTSC (*(volatile unsigned int *)0x58000004)
#define rADCDLY (*(volatile unsigned int *)0x58000008)
#define rADCDAT0 (*(volatile unsigned int *)0x5800000C)
#define rADCDAT1 (*(volatile unsigned int *)0x58000010)
void TouchScreen_Init(void) {
// 1. 配置 ADC 时钟
// ADCCLK = PCLK / (PRESCALE + 1)
// 假设 PCLK=100MHz, 希望 ADCCLK < 2.5MHz (S3C2440 ADC最大频率)
// PRESCALE = 100/2.5 - 1 = 39
rADCCON = (1 << 14) | (39 << 6); // bit14: 使能预分频, bit13-7: 预分频值
// 2. 配置触摸屏模式
// 初始状态:待机模式 (Standby)
rADCTSC = 0;
// 3. 配置 ADC 延时 (可选,用于滤波)
rADCDLY = 5000;
// 4. 设置中断(如果需要)
// 开启 ADC 转换完成中断
// rINTMSK &= ~(1 << 9); // bit9 is ADC
}
3.4 触摸屏测量流程
测量过程分为几个状态:
- 待机:等待触摸。
- 自动 X/Y 测量:启动 ADC,先测 X,再测 Y。
- 等待中断:监测 PEN 下降沿(按下)。
测量代码示例:
”`c typedef struct {
int x;
int y;
} TOUCH_POINT;
TOUCH_POINT TouchScreen_Read(void) {
TOUCH_POINT point = {0, 0};
// 1. 设置为自动 X/Y 测量模式
rADCTSC = (1 << 3) | (1 << 2); // bit3: X轴测量, bit2: Y轴测量, 自动切换
// 2. 启动 ADC
rADCCON |= (1 << 0); // bit0: 启动转换
// 3. 等待转换完成
// 这里可以用轮询,也可以用中断。