如何点亮一块屏幕(超详细)
基于极海G32A1465的1.77寸SPI屏幕点亮实战
一、概述
- 背景介绍
ST7789屏幕特性:1.77寸TFTLCD,分辨率160×128,SPI接口,支持RGB565/16位色。
极海G32A1465优势:带有 FPU 的 32 位 Arm® Cortex®-M4F 内核,最高 112MHz 工作频率,支持硬件SPI,低功耗设计,适用于嵌入式显示场景。
目标:通过SPI接口驱动ST7789屏幕,实现基础图形显示功能。
2. 硬件准备
开发板:极海G32A1465开发板
屏幕模块:1.77寸ST7789驱动LCD(带SPI接口)
连接方式:SPI模式,需额外控制DC
(数据/命令选择)、RESET
、CS
(片选)引脚。
二、SPI介绍
- SPI协议核心
四线制:SCK(时钟)、MOSI(主出从入)、MISO(主入从出)、CS(片选)。
通信模式:CPOL(时钟极性)与CPHA(时钟相位)需与屏幕规格匹配(ST7789通常为CPOL=0, CPHA=0)。
2. 极海SPI外设配置:
支持主模式,最大速率可达系统时钟的1/2。
可配置8/16位数据帧,支持DMA传输优化性能。
关键参数计算
SPI速率:屏幕支持最高80MHz,但需根据PCB走线长度调整(实测建议20-40MHz)。
数据格式:16位RGB565色彩模式,需通过DC
引脚区分命令与数据。
3. SPI通信数据读取时机解析
SPI如何传递信息呢?我们通过逻辑分析仪来仔细观察一下。
1. 时钟信号(CLK)的作用
时钟周期是什么?
图中红圈标出的部分是一个完整的时钟周期,就像音乐的节奏一样——CLK线会循环执行“拉高→保持→拉低→保持”的动作(类似不断开关的脉冲信号)。
数据读取的时机由时钟边沿决定
上升沿触发(图2、图3模式):
当CLK从低电平(0)突然变成高电平(1)的瞬间(类似“↑”箭头),SPI设备开始读取或发送数据。(图中红线位置)
下降沿触发(另一种模式):
如果设置为此模式,则会在CLK从高电平(1)变成低电平(0)的瞬间(类似“↓”箭头)触发数据读取。
2. 片选信号(CS)的作用
片选就像开关
高电平选中(本测试设置):
当CS引脚变为高电平(1)时,表示“选中设备开始通信”(相当于打开开关)。图中每次数据传输结束后,CS会被拉低(0)表示“关闭通信”。
低电平选中的情况:
有些设备设计相反——CS为低电平(0)时选中,需根据具体硬件调整。
3. 电平状态的本质
数字信号的0和1
所有提到的“0”代表低电平(通常0V或接近GND电压),“1”代表高电平(如3.3V或5V)。实际电压值需参考硬件规格。
通俗总结
SPI通信就像两人配合搬箱子:
CLK信号:像喊口号的人,每喊一次“起!”(上升沿)或“放!”(下降沿),其他人就搬一个箱子(传输1bit数据)。
CS信号:像仓库大门,只有门开了(CS有效时),才能开始搬箱子。
如果关于SPI信号传输大家有什么问题,欢迎在评论区讨论,或者私信讨论,以上为本人的一点浅显认识,如有问题欢迎批评指正。
参考链接:https://baike.baidu.com/item/SPI%E7%90%86%E8%AE%BA/10660678
https://www.bilibili.com/video/BV1F54y1M7e7/?spm_id_from=333.337.search-card.all.click这个链接动画非常好,可以帮助大家理解SPI的流程.
https://blog.csdn.net/2301_78622258/article/details/143465507
三、屏幕点亮流程
1. 硬件连接
屏幕引脚 | G32A1465引脚 | 功能 |
---|---|---|
VCC | 3.3V | 电源 |
GND | GND | 地 |
SCL | PB14 | SPI_SCK |
SDA | PD2 | SPI_MOSI |
RES | PD17 | 复位控制 |
DC | PC9 | 数据/命令选择 |
CS | PD3 | 片选 |
BLK | PC8 | 背光控制 |
屏幕点亮与显示原理详解
1. 屏幕与单片机的通信
屏幕的角色:屏幕就像一个“听话的画板”,它只需要按照单片机的指令,在指定的位置显示指定的颜色。
SPI引脚的作用:
MOSI:单片机通过MOSI(主机输出,从机输入)向屏幕发送数据。
MISO:屏幕不需要向单片机发送数据,因此MISO引脚用不到。
DC引脚(也叫RS):用来区分发送的是“命令”还是“数据”。
2. 屏幕点亮步骤
复位屏幕
将RES(复位)引脚拉低(0),等待100ms后再拉高(1)。
复位完成后,RES引脚就不再使用了。
操作RES引脚的方法和控制LED灯开关类似,都是对GPIO的操作。
SPI初始化屏幕
命令与数据的区别:
命令:告诉屏幕接下来要做什么(比如设置参数或准备刷屏)。
数据:具体的内容(比如颜色值或区域大小)。
DC引脚的作用:
当DC为低电平(0)时,SPI发送的是命令。
当DC为高电平(1)时,SPI发送的是数据。
示例:
设置刷屏区域为120x160:
发送命令:“我要设置刷屏区域”。
发送数据:宽度120。
发送数据:高度160。
刷屏:
发送命令:“我要刷屏”。
发送120x160个颜色数据(每个数据对应一个像素点)。
初始化序列
每块屏幕都有自己的“初始化序列”,相当于对屏幕进行个性化设置。
如果找不到初始化序列,可以百度或咨询商家。
注意:部分屏幕(如手机屏)的初始化序列不公开,个人开发者可能无法点亮。
BLK引脚的作用
BLK引脚控制屏幕背光,就像控制一个LED灯:
拉高(1):背光亮(屏幕发光)。
拉低(0):背光灭(屏幕变黑)。
3. 屏幕显示原理
屏幕可以简单理解为四层结构:
背光层
由BLK引脚控制,发出白光或熄灭。
三原色偏光片层
屏幕上有三原色(红、绿、蓝)的偏光片,每个偏光片就像一块“有色玻璃”:
偏光片完全打开:白光直接通过。
偏光片完全关闭:白光被过滤,变成对应颜色。
偏光片半开:控制颜色的深浅。
像素点控制
屏幕由许多像素点组成(如160x128)。
每个像素点包含红、绿、蓝三个偏光片。
通过SPI发送的16位数据(565格式)控制每个偏光片的开合程度:
前5位控制红色。
中间6位控制绿色。
后5位控制蓝色。
显示任意图形
通过控制每个像素点的三原色偏光片,屏幕就能显示出任意颜色和图形。
通俗总结
屏幕就像一块智能画布:
复位:按下“重启键”,让屏幕恢复初始状态。
初始化:告诉屏幕“你该怎么工作”。
刷屏:在指定区域涂上指定颜色。
背光:打开“灯光”,让画面亮起来。
显示原理:通过控制红、绿、蓝三原色的“有色玻璃”,混合出各种颜色。
2. 软件流程
初始化引脚:
初始化RES、DC、LCD引脚,这几个引脚的初始化参考GPIO的初始化
初始化SPI(包括CLK、MOSI、MISO、CS)
硬件复位:
拉低RES
引脚至少10μs,再拉高并延时120ms。
发送初始化命令序列:
通过DC
引脚切换命令/数据模式,依次发送睡眠退出、颜色模式设置、显示开启等指令(参考ST7789数据手册)。参考初始化序列如下:
Lcd_WriteIndex(0x11);//Sleep exit
//delay_ms (120);
Delay_ms(200);
//ST7735R Frame Rate
Lcd_WriteIndex(0xB1);
Lcd_WriteData(0x01);
Lcd_WriteData(0x2C);
Lcd_WriteData(0x2D);
Lcd_WriteIndex(0xB2);
Lcd_WriteData(0x01);
Lcd_WriteData(0x2C);
Lcd_WriteData(0x2D);
Lcd_WriteIndex(0xB3);
Lcd_WriteData(0x01);
Lcd_WriteData(0x2C);
Lcd_WriteData(0x2D);
Lcd_WriteData(0x01);
Lcd_WriteData(0x2C);
Lcd_WriteData(0x2D);
Lcd_WriteIndex(0xB4); //Column inversion
Lcd_WriteData(0x07);
//ST7735R Power Sequence
Lcd_WriteIndex(0xC0);
Lcd_WriteData(0xA2);
Lcd_WriteData(0x02);
Lcd_WriteData(0x84);
Lcd_WriteIndex(0xC1);
Lcd_WriteData(0xC5);
Lcd_WriteIndex(0xC2);
Lcd_WriteData(0x0A);
Lcd_WriteData(0x00);
Lcd_WriteIndex(0xC3);
Lcd_WriteData(0x8A);
Lcd_WriteData(0x2A);
Lcd_WriteIndex(0xC4);
Lcd_WriteData(0x8A);
Lcd_WriteData(0xEE);
Lcd_WriteIndex(0xC5); //VCOM
Lcd_WriteData(0x0E);
Lcd_WriteIndex(0x36); //MX, MY, RGB mode
Lcd_WriteData(0xC0);
//ST7735R Gamma Sequence
Lcd_WriteIndex(0xe0);
Lcd_WriteData(0x0f);
Lcd_WriteData(0x1a);
Lcd_WriteData(0x0f);
Lcd_WriteData(0x18);
Lcd_WriteData(0x2f);
Lcd_WriteData(0x28);
Lcd_WriteData(0x20);
Lcd_WriteData(0x22);
Lcd_WriteData(0x1f);
Lcd_WriteData(0x1b);
Lcd_WriteData(0x23);
Lcd_WriteData(0x37);
Lcd_WriteData(0x00);
Lcd_WriteData(0x07);
Lcd_WriteData(0x02);
Lcd_WriteData(0x10);
Lcd_WriteIndex(0xe1);
Lcd_WriteData(0x0f);
Lcd_WriteData(0x1b);
Lcd_WriteData(0x0f);
Lcd_WriteData(0x17);
Lcd_WriteData(0x33);
Lcd_WriteData(0x2c);
Lcd_WriteData(0x29);
Lcd_WriteData(0x2e);
Lcd_WriteData(0x30);
Lcd_WriteData(0x30);
Lcd_WriteData(0x39);
Lcd_WriteData(0x3f);
Lcd_WriteData(0x00);
Lcd_WriteData(0x07);
Lcd_WriteData(0x03);
Lcd_WriteData(0x10);
Lcd_WriteIndex(0x2a);
Lcd_WriteData(0x00);
Lcd_WriteData(0x00);
Lcd_WriteData(0x00);
Lcd_WriteData(0x7f);
Lcd_WriteIndex(0x2b);
Lcd_WriteData(0x00);
Lcd_WriteData(0x00);
Lcd_WriteData(0x00);
Lcd_WriteData(0x9f);
Lcd_WriteIndex(0xF0); //Enable test command
Lcd_WriteData(0x01);
Lcd_WriteIndex(0xF6); //Disable ram power save mode
Lcd_WriteData(0x00);
Lcd_WriteIndex(0x3A); //65k mode
Lcd_WriteData(0x05);
Lcd_WriteIndex(0x29);//Display on
这里Lcd_WriteIndex()
是发送命令,Lcd_WriteData()
是发送数据.
填充显存测试图案
四、极海代码讲解
极海主要代码如下(完整代码见附加,欢迎下载讨论):
对GPIO初始化(LCD,DC,RES):
/* Enable Clock to Port D */
CLOCK_SYS_ConfigModuleClock(PMD_CLK, NULL); //引脚D时钟使能
/* Port Initialization */
PINS_SetMuxModeSel(PMD, 17U, PM_MUX_AS_GPIO); //设置PD17引脚的模式
PINS_SetPinIntSel(PMD, 17U, PM_DMA_INT_DISABLED); //设置PD17引脚是否使用DMA
/* GPIO Initialization */
PINS_SetPins(GPIOD, 1U << 17U); //设置PD17引脚
PINS_SetPinDir(GPIOD, 17U, 1U); //设置PD17传输方向(输入/输出)
/* Enable Clock to Port C */
CLOCK_SYS_ConfigModuleClock(PMC_CLK, NULL); //引脚C时钟使能
/* Port Initialization */
PINS_SetMuxModeSel(PMC, 8U, PM_MUX_AS_GPIO); //设置PC8引脚的模式
PINS_SetPinIntSel(PMC, 8U, PM_DMA_INT_DISABLED); //设置PC8引脚是否使用DMA
/* GPIO Initialization */
PINS_SetPins(GPIOC, 1U << 8U); //设置PC8引脚
PINS_SetPinDir(GPIOC, 8U, 1U); //设置PC8传输方向(输入/输出)
/* Enable Clock to Port D */
CLOCK_SYS_ConfigModuleClock(PMC_CLK, NULL); //引脚C时钟使能
/* Port Initialization */
PINS_SetMuxModeSel(PMC, 9U, PM_MUX_AS_GPIO); //设置PC9引脚的模式
PINS_SetPinIntSel(PMC, 9U, PM_DMA_INT_DISABLED); //设置PC9引脚是否使用DMA
/* GPIO Initialization */
PINS_SetPins(GPIOC, 1U << 9U); //设置PC9引脚
PINS_SetPinDir(GPIOC, 9U, 1U); //设置PC9传输方向(输入/输出)
对SPI初始化:
PINS_Init(NUM_OF_CONFIGURED_PINS0, g_spiPinsConfig); //初始化SPI对应引脚
DMA_Init(&g_dmaState, &g_dmaInitConfig, g_dmaChannelStateArray,
g_dmaChannelConfigArray, DMA_CHANNELS_COUNT); //初始化DMA
/* initialize SPI to master mode based on the configuration set */
LPSPI_MasterInit(LPSPI_1, &g_spiState, &g_spiMasterCfg); //初始化SPI
这里很明显代码并不完全,因为从这里我们并不能看出使用的是哪些引脚,以及如何修改SPI的参数,因此进一步展开有:
展开PINS_Init函数有:
STATUS_T PINS_Init(uint32_t pinCnt, const PIN_SETTINGS_CFG_T cfg[])
{
uint32_t k;
for (k = 0U; k < pinCnt; k++)
{
PINS_HW_Cfg(&cfg[k]);
}
return STATUS_SUCCESS;
}
这是一个循环配置IO引脚,配置哪些引脚呢?
展开g_spiPinsConfig
有:
PIN_SETTINGS_CFG_T g_spiPinsConfig[NUM_OF_CONFIGURED_PINS0] = {
{
.base = PMB,
.pinPmIdx = 0U,
.pullCfg = PM_INTERNAL_PULL_NOT_ENABLED,
.drvSel = PM_LOW_DRIVE_STRENGTH,
.passiveFilt = false,
.mux = PM_MUX_ALT3,
.pinLk = false,
.intCfg = PM_DMA_INT_DISABLED,
.clrIntFlg = false,
.gpioBase = NULL,
.digitFilt = false,
},
{
.base = PMB,
.pinPmIdx = 14U,
.pullCfg = PM_INTERNAL_PULL_NOT_ENABLED,
.drvSel = PM_LOW_DRIVE_STRENGTH,
.passiveFilt = false,
.mux = PM_MUX_ALT3,
.pinLk = false,
.intCfg = PM_DMA_INT_DISABLED,
.clrIntFlg = false,
.gpioBase = NULL,
.digitFilt = false,
},
{
.base = PMB,
.pinPmIdx = 2U,
.pullCfg = PM_INTERNAL_PULL_NOT_ENABLED,
.drvSel = PM_LOW_DRIVE_STRENGTH,
.passiveFilt = false,
.mux = PM_MUX_ALT3,
.pinLk = false,
.intCfg = PM_DMA_INT_DISABLED,
.clrIntFlg = false,
.gpioBase = NULL,
.digitFilt = false,
},
{
.base = PMB,
.pinPmIdx = 4U,
.pullCfg = PM_INTERNAL_PULL_NOT_ENABLED,
.drvSel = PM_LOW_DRIVE_STRENGTH,
.passiveFilt = false,
.mux = PM_MUX_ALT3,
.pinLk = false,
.intCfg = PM_DMA_INT_DISABLED,
.clrIntFlg = false,
.gpioBase = NULL,
.digitFilt = false,
},
{
.base = PMD,
.pinPmIdx = 1U,
.pullCfg = PM_INTERNAL_PULL_NOT_ENABLED,
.drvSel = PM_LOW_DRIVE_STRENGTH,
.passiveFilt = false,
.mux = PM_MUX_ALT3,
.pinLk = false,
.intCfg = PM_DMA_INT_DISABLED,
.clrIntFlg = false,
.gpioBase = NULL,
.digitFilt = false,
},
{
.base = PMD,
.pinPmIdx = 2U,
.pullCfg = PM_INTERNAL_PULL_NOT_ENABLED,
.drvSel = PM_LOW_DRIVE_STRENGTH,
.passiveFilt = false,
.mux = PM_MUX_ALT3,
.pinLk = false,
.intCfg = PM_DMA_INT_DISABLED,
.clrIntFlg = false,
.gpioBase = NULL,
.digitFilt = false,
},
{
.base = PMD,
.pinPmIdx = 3U,
.pullCfg = PM_INTERNAL_PULL_NOT_ENABLED,
.drvSel = PM_LOW_DRIVE_STRENGTH,
.passiveFilt = false,
.mux = PM_MUX_ALT3,
.pinLk = false,
.intCfg = PM_DMA_INT_DISABLED,
.clrIntFlg = false,
.gpioBase = NULL,
.digitFilt = false,
},
{
.base = PME,
.pinPmIdx = 1U,
.pullCfg = PM_INTERNAL_PULL_NOT_ENABLED,
.drvSel = PM_LOW_DRIVE_STRENGTH,
.passiveFilt = false,
.mux = PM_MUX_ALT2,
.pinLk = false,
.intCfg = PM_DMA_INT_DISABLED,
.clrIntFlg = false,
.gpioBase = NULL,
.digitFilt = false,
},
};
通过查询芯片的数据手册可以知道:

很明显初始化了SPI0和SPI1两套SPI,具体是那一套呢?
从LPSPI_MasterInit(LPSPI_1, &g_spiState, &g_spiMasterCfg);
函数可以看出了使用的是SPI1,我们改动第一个参数可以调整为SPI0.
展开g_spiMasterCfg
有:
LPSPI_MASTER_CFG_T g_spiMasterCfg = {
.bitNumber = 8U, //位数
.baudBitsPer = 8000000UL,
.lpspiModSrcClk = 8000000UL, //时钟频率
.selectPcs = LPSPI_PER_PCS0, //片选引脚,这里是CS0,对应的就是PD3引脚
.selectPcsPolar = LPSPI_SIGNAL_ACTIVE_LOW, //引脚不使用的时候置于高电位还是低电位
.selectClkPhase = LPSPI_CLOCK_PHASE_CFG_1ST, //从这里选择上升沿或者下降沿
.clkPolar = LPSPI_CLK_SIGNAL_HIGH, //CS是高电平选中还是低电平选中,我们的屏幕应该是低电平
.callback = NULL, //回调函数,NULL就是没有
.callbackParam = NULL,
.pcsKeeps = false, //CS保持,就是数据发送完了,CS仍然是选中状态
.sendLsbFirst = false, //高位先发送,就是一个16位数据,是否先发送高位
.transferType = LPSPI_TRANSFER_DMA, //使用DMA加速
.rxDMAChan = 0U, //DMA通道
.txDMAChan = 1U
};
五、实验结果
成功点亮屏幕,如图:


由于时间原因,就不进行更深层次的图像显示了,因为只要能画图,图像显示就是改变一下每个位置的像素点的颜色,如果有需求可以找我拿画图的代码,当然后续的测评会有提到.
六、后期计划
下一篇将更换一个可以触屏的屏幕,测试显示和触屏,然后尝试使用LVGL.
七、一点碎碎念
由于过年的原因一直拖着没测试,很多东西做的都比较糙,欢迎大家多批评指正.如果对上面的内容有疑问也欢迎大家提问.本文部分文段是使用了DeepSeek润色的,感谢免费的人工智能.大家有问题也可以尝试使用.
最后一篇测评应该会在元宵节前后发出来,本文的源码会附在最后,欢迎大家下载参考.
全部评论