第一次接触N32G43X的硬件SPI驱动LCD时,我也被各种寄存器配置搞得晕头转向。但经过几个项目的实战,我发现只要掌握几个关键点,就能轻松点亮屏幕。N32G43X是国民技术推出的一款性价比极高的ARM Cortex-M4内核MCU,内置硬件SPI接口,特别适合驱动中小尺寸LCD屏。
硬件SPI相比软件模拟SPI最大的优势就是速度快、占用CPU资源少。实测在72MHz主频下,硬件SPI的传输速率能达到9Mbps,而软件SPI通常连1Mbps都难以稳定维持。这对于需要频繁刷新屏幕的应用场景尤为重要。
这个教程适合以下几类开发者:
我们将从最基础的寄存器配置开始,逐步实现屏幕点亮、图形显示等完整功能。不用担心基础薄弱,我会把每个步骤都拆解得明明白白。
时钟配置是SPI工作的基础,就像人体的心跳一样重要。N32G43X的SPI时钟来源于APB2总线,我们需要先确保APB2时钟正常。这里有个小技巧:APB2时钟最好不要超过72MHz,否则可能导致SPI工作不稳定。
c复制void SPI1_RCC_Configuration(void)
{
RCC_ConfigPclk2(RCC_HCLK_DIV2); // 设置APB2时钟为HCLK的1/2
RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOA | RCC_APB2_PERIPH_AFIO, ENABLE);
RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_SPI1, ENABLE);
}
这段代码做了三件事:
实际项目中我遇到过SPI无法工作的情况,最后发现是忘记使能AFIO时钟。AFIO(Alternate Function I/O)负责管脚复用功能,SPI使用的GPIO必须开启这个时钟。
SPI需要三个关键信号线:
对于驱动LCD,通常只需要SCK和MOSI,因为LCD大多是只写设备。N32G43X的SPI1默认使用PA5(SCK)、PA6(MISO)、PA7(MOSI)。
c复制void SPI1_GPIO_Configuration(void)
{
GPIO_InitType GPIO_InitStructure;
GPIO_InitStruct(&GPIO_InitStructure);
// 配置SPI1管脚:SCK(PA5)、MOSI(PA7)
GPIO_InitStructure.Pin = GPIO_PIN_5 | GPIO_PIN_7;
GPIO_InitStructure.GPIO_Current = GPIO_DC_4mA; // 4mA驱动能力
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
GPIO_InitStructure.GPIO_Alternate = GPIO_AF0_SPI1; // 复用功能选择
GPIO_InitPeripheral(GPIOA, &GPIO_InitStructure);
}
这里有几个容易出错的地方:
我曾经因为模式设置错误导致信号幅度不足,LCD显示出现雪花点。后来用逻辑分析仪抓取波形才发现问题所在。
SPI初始化是整个配置的核心,需要根据LCD规格书设置合适的参数。以下是一个典型配置:
c复制void LCD_SPI1_Init(void)
{
SPI_InitType SPI_InitStructure;
SPI1_RCC_Configuration();
SPI1_GPIO_Configuration();
SPI_InitStructure.DataDirection = SPI_DIR_DOUBLELINE_FULLDUPLEX;
SPI_InitStructure.SpiMode = SPI_MODE_MASTER;
SPI_InitStructure.DataLen = SPI_DATA_SIZE_8BITS;
SPI_InitStructure.CLKPOL = SPI_CLKPOL_HIGH;
SPI_InitStructure.CLKPHA = SPI_CLKPHA_SECOND_EDGE;
SPI_InitStructure.NSS = SPI_NSS_SOFT;
SPI_InitStructure.BaudRatePres = SPI_BR_PRESCALER_8;
SPI_InitStructure.FirstBit = SPI_FB_MSB;
SPI_Init(SPI1, &SPI_InitStructure);
SPI_Enable(SPI1, ENABLE);
}
关键参数解析:
| 参数 | 说明 | 常见值 |
|---|---|---|
| CLKPOL | 时钟极性 | HIGH/LOW |
| CLKPHA | 时钟相位 | FIRST_EDGE/SECOND_EDGE |
| BaudRatePres | 波特率预分频 | 2/4/8/16/32/64/128/256 |
| FirstBit | 数据传输顺序 | MSB/LSB |
CLKPOL和CLKPHA需要严格匹配LCD的要求,否则数据无法正确传输。我曾经因为这两个参数设置错误,导致屏幕显示乱码,调试了整整一天。
除了SPI接口,LCD通常还需要几个控制信号:
c复制// LCD端口定义
#define LCD_CS_L GPIOA->PBC = GPIO_PIN_6
#define LCD_CS_H GPIOA->PBSC = GPIO_PIN_6
#define LCD_RS_L GPIOA->PBC = GPIO_PIN_4
#define LCD_RS_H GPIOA->PBSC = GPIO_PIN_4
#define LCD_Light_L GPIOC->PBC = GPIO_PIN_5
#define LCD_Light_H GPIOC->PBSC = GPIO_PIN_5
#define LCD_REST_L GPIOC->PBC = GPIO_PIN_4
#define LCD_REST_H GPIOC->PBSC = GPIO_PIN_4
这些宏定义使用了N32G43X的位操作功能,相比传统的GPIO写操作效率更高。在需要快速刷屏的场景下,这种优化能明显提升性能。
LCD操作分为命令和数据两种,通过RS信号区分:
c复制// 写命令
void Write_com(u8 com)
{
LCD_RS_L; // RS=0表示写命令
LCD_CS_L; // 片选使能
SPI1_ReadWriteByte(com);
LCD_CS_H; // 片选禁止
LCD_RS_H; // 恢复RS状态
}
// 写数据
void Write_data(u8 dat)
{
LCD_RS_H; // RS=1表示写数据
LCD_CS_L; // 片选使能
SPI1_ReadWriteByte(dat);
LCD_CS_H; // 片选禁止
LCD_RS_H; // 保持RS状态
}
这里有个细节需要注意:CS信号应该在每个命令/数据发送前后进行拉低和拉高操作。有些LCD控制器要求每个命令都必须有独立的CS脉冲。
LCD初始化是驱动成功的关键,不同型号的LCD初始化序列可能差异很大。以下是一个典型序列:
c复制void LCD_Init(void)
{
LCD_SPI1_Init(); // 初始化硬件SPI
LCD_GPIO_Init(); // 初始化GPIO
// 硬件复位
LCD_REST_L;
delay_ms(200);
LCD_REST_H;
// 发送初始化命令
Write_com(0xe2); // 系统复位
Write_com(0xa1); // 段驱动方向选择
Write_com(0xc8); // 公共输出模式选择
Write_com(0xa2); // 偏置设置
Write_com(0x2f); // 电源控制
Write_com(0x26); // 内部电阻比率
Write_com(0x81); // 电子音量模式设置
Write_com(0x20); // 电子音量值
Write_com(0xaf); // 显示开启
// 测试代码
Write_com(0xA5); // 全屏点亮测试
LCD_Light_H; // 打开背光
}
初始化过程中有几个注意事项:
我曾经遇到初始化失败的问题,最后发现是命令间隔时间太短,LCD控制器来不及处理。建议在每个Write_com后加1-5ms延时。
基础显示功能实现后,可以进一步优化显示效果。一个实用的技巧是使用显存机制:
c复制#define LCD_WIDTH 128
#define LCD_HEIGHT 64
uint8_t frameBuffer[LCD_HEIGHT/8][LCD_WIDTH];
void LCD_Update(void)
{
for(uint8_t page=0; page<LCD_HEIGHT/8; page++){
Write_com(0xB0 | page); // 设置页地址
Write_com(0x10); // 设置列地址高4位
Write_com(0x00); // 设置列地址低4位
for(uint8_t col=0; col<LCD_WIDTH; col++){
Write_data(frameBuffer[page][col]);
}
}
}
这种机制将修改操作集中在内存中,最后一次性刷新到LCD,可以避免屏幕闪烁。实测在显示复杂图形时,流畅度能提升3-5倍。
很多LCD模块还集成了触摸功能,通常使用I2C或SPI接口。以SPI接口为例:
c复制uint8_t Read_TouchData(void)
{
uint8_t data = 0;
TOUCH_CS_L;
delay_us(10);
data = SPI1_ReadWriteByte(0x00);
TOUCH_CS_H;
return data;
}
触摸数据处理需要注意消抖和校准。建议采集多次数据取平均,并使用三点校准法提高精度。
对于电池供电设备,功耗优化很重要:
c复制void LCD_EnterSleep(void)
{
Write_com(0xAE); // 关闭显示
Write_com(0xAD); // 进入睡眠模式
LCD_Light_L; // 关闭背光
}
void LCD_WakeUp(void)
{
Write_com(0xAC); // 退出睡眠
Write_com(0xAF); // 开启显示
LCD_Light_H; // 开启背光
}
实测在睡眠模式下,LCD模块功耗可以从5mA降至50μA以下。配合MCU的低功耗模式,整体系统功耗可以做到非常低。