1. 准备工作
1.1 物料清单
- 支持8080接口的并口屏
- 主控使用STM32F103ZET6
1.2 开发环境及工具
- 开发工具选择VSCode+Keil的方式
2. 环境搭建及工程创建
STM32F1这里采用标准库来进行开发
从这里下载对应的标准库驱动
工程结构如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29├─Bsp
│ ├─Inc
│ └─Src
├─Core
│ ├─Inc
│ └─Src
├─Doc
├─Drivers
│ ├─CMSIS
│ │ ├─CM3
│ │ │ ├─CoreSupport
│ │ │ └─DeviceSupport
│ │ │ └─ST
│ │ │ └─STM32F10x
│ │ │ └─startup
│ │ │ ├─arm
│ │ │ ├─gcc_ride7
│ │ │ ├─iar
│ │ │ └─TrueSTUDIO
│ │ └─Documentation
│ └─STM32F10x_StdPeriph_Driver
│ ├─inc
│ └─src
├─KeilPrj
│ ├─.vscode
│ ├─DebugConfig
│ ├─Listings
│ └─Objects
└─Libraries
3.FSMC及8080时序
3.1 FSMC简介
FSMC是Flexible Static Memory Controller的缩写,译为灵活的静态存储控制器。 它可以用于驱动包括SRAM、NOR FLASH以及NANDFLSAH类型的存储器, 不能驱动如SDRAM这种动态的存储器而在STM32F429系列的控制器中, 它具有FMC外设,支持控制SDRAM存储器。
- FSMC功能框图如下,其中划分了四组信号分别是
- 公共信号(Shared signals):
- FSMC_A[25:0]:地址锁存线
- FSMC_D[15:0]:数据锁存线
- FSMC_NOE:片选线
- FSMC_NOE:输出使能
- FSMC_NWE:写使能
- FSMC_NWAIT:等待
- NOR/PSRAM信号、NAND信号和PC Card信号
- 公共信号(Shared signals):
FSMC将外设的地址映射到CPU中通过指针直接操作的方式独写外设中的数据。在STM32中的External Memory(FSMC映射)被划分成为如下四个Bank。每个Bank的大小为256Mbytes如下图所示
- Bank1又划分为四个子Bank(支持四个设备),通过片选可以选择如下子Bank
- Bank 1 - NOR/PSRAM 1
- Bank 1 - NOR/PSRAM 2
- Bank 1 - NOR/PSRAM 3
- Bank 1 - NOR/PSRAM 4
- Bank2和Bank3用于NAND Flash设备,每个Bank只支持一个设备
- Bank4用于PC Card的扩展
- 对应的地址映射区间如下图所示
- Bank1又划分为四个子Bank(支持四个设备),通过片选可以选择如下子Bank
对应的FSMC管脚可从Datasheet中获取,以下仅部分截图
学过微机原理的应该都知道8086芯片,其实FSMC跟8086的操作类似,都是写地址、数据和使能。不过8086地址线和数据线采用分时复用的机制通过锁存将地址锁存后再读写数据。如图所示
74273是锁存芯片,输入是啥输出就是啥只有在CLK上升沿的时候对其写入数据
74154是4-16译码器芯片,将二进制译码成对应的电平信号
3.1.1 FSMC时序
- FSMC时序分不同的模式使用不同的时序进行通信,以下是NOR Flash Mode2/B的读写时序。其他模式的时序可以到官方的参考手册了解。这里选择Mode B的写时序模拟8080,原因是其时序和8080时序相似度极高。
- 读时序
- 写时序
- 可以看到写入数据那一部中心虚线右边,当数据准备好后只要拉高NWE刚好模拟8080的WDX上升沿写入数据。而地址线有26位取最低位(或其他)当作D/CX那么将最低位接入到8080的D/CX当发送数据的时候如果地址线最低位是0则为命令,1则为数据。
3.1.2 标准库结构体
- 以下皆为AI生成,建议参考《STM32 Reference Manual》21.5.6节「NOR/PSRAM control registers」
FSMC_NORSRAMInitTypeDef
结构体- FSMC_Bank
- 可选值:FSMC_Bank1_NORSRAM1/2/3/4
- 作用:选择使用哪一路 FSMC NOR/SRAM bank(对应硬件的 NE1..NE4 引脚和地址映射)。
- 注意:要与硬件片选(外设芯片选线)和后续的 FSMC_NORSRAMCmd 一致。
- FSMC_DataAddressMux
- 可选值:FSMC_DataAddressMux_Disable / FSMC_DataAddressMux_Enable
- 作用:是否启用地址/数据复用(AD 总线)。Enable 时地址与数据复用在同一组引脚(节省引脚),Disable 时地址与数据独立。
- 建议:并口 LCD(ILI9341)通常使用非复用并行,总线独立 -> Disable。
- FSMC_MemoryType
- 可选值:FSMC_MemoryType_SRAM / PSRAM / NOR
- 作用:告诉 FSMC 外设类型以使用不同的控制时序/信号。
- 建议:并口 LCD 一般选 NOR 或 SRAM(两者差别主要在驱动策略),常用 FSMC_MemoryType_NOR。
- FSMC_MemoryDataWidth
- 可选值:FSMC_MemoryDataWidth_8b / FSMC_MemoryDataWidth_16b
- 作用:外设数据总线宽度(FSMC 传输位宽)。
- 建议:ILI9341 并口若为 16-bit 数据模式用 16b;若为 8-bit 串行并口则 8b。
- FSMC_BurstAccessMode
- 可选值:FSMC_BurstAccessMode_Disable / Enable
- 作用:允许/禁止突发访问(仅对支持同步突发的 Flash 有意义)。
- 建议:异步并口 LCD 不使用 -> Disable。
- FSMC_AsynchronousWait
- 可选值:FSMC_AsynchronousWait_Disable / Enable
- 作用:在异步传输中是否允许等待信号(NWAIT)用于插入等待周期。
- 建议:外设提供 NWAIT 时可以 Enable;ILI9341 不提供 -> Disable。
- FSMC_WaitSignalPolarity
- 可选值:FSMC_WaitSignalPolarity_Low / High
- 作用:NWAIT 的有效电平(低或高)。
- 仅在启用等待信号/突发模式相关时有意义。
- FSMC_WrapMode
- 可选值:FSMC_WrapMode_Disable / Enable
- 作用:包裹式突发(Wrapped burst)模式,只在同步突发访问时有意义。
- 建议:通常 Disable(对异步 LCD 无用)。
- FSMC_WaitSignalActive
- 可选值:FSMC_WaitSignalActive_BeforeWaitState / DuringWaitState
- 作用:FSMC 何时检测等待信号(是在等待状态之前还是在等待状态中)。
- 仅对启用了等待信号/突发模式的情形有意义。
- FSMC_WriteOperation
- 可选值:FSMC_WriteOperation_Disable / Enable
- 作用:是否允许向该 bank 写操作。
- 建议:若外设需要写(LCD 需要写数据/命令)则 Enable;只读存储可 Disable。
- FSMC_WaitSignal
- 可选值:FSMC_WaitSignal_Disable / Enable
- 作用:是否启用通过 NWAIT 插入等待态(主要针对 Flash 突发访问)。
- 建议:ILI9341 不使用 -> Disable。
- FSMC_ExtendedMode
- 可选值:FSMC_ExtendedMode_Disable / Enable
- 作用:Enable 时可为写操作使用单独的时序结构(FSMC_WriteTimingStruct),Disable 时读写共用 FSMC_ReadWriteTimingStruct。
- 建议:如果写和读时序差别较大(需要单独优化)可 Enable,否则 Disable 即可。对频繁写入的 LCD,若需要更快的读写分别调优,可开启扩展模式并提供单独写时序。
- FSMC_WriteBurst
- 可选值:FSMC_WriteBurst_Disable / Enable
- 作用:允许写突发(仅对支持同步突发的设备有意义)。
- 建议:异步 LCD -> Disable。
- FSMC_ReadWriteTimingStruct
- 类型:FSMC_NORSRAMTimingInitTypeDef*
- 作用:指向读/写共用的时序结构(当 FSMC_ExtendedMode = Disable 时使用)。包含 AddressSetup、DataSetup 等具体周期配置。
- 重要性:对异步设备稳定性影响大,需根据外设数据手册调节(AddressSetupTime、DataSetupTime 是关键)。
- FSMC_WriteTimingStruct
- 类型:FSMC_NORSRAMTimingInitTypeDef*
- 作用:当 FSMC_ExtendedMode = Enable 时,用于单独配置写操作的时序。读操作仍用 FSMC_ReadWriteTimingStruct。
FSMC_NORSRAMTimingInitTypeDef
结构体- FSMC_AddressSetupTime
- 取值范围:0 ~ 0xF(HCLK 周期数)
- 作用:地址总线有效到第一个数据相位开始之间的建立时间(以 HCLK 周期计)。
- 建议(并口 LCD):通常 1 ~ 4(0 表示最短但可兼容性差)。
- FSMC_AddressHoldTime
- 取值范围:0 ~ 0xF(HCLK 周期数)
- 作用:地址保持时间(地址在数据相位后的保持周期数),仅在地址/数据复用时有意义。
- 建议(非复用并口 LCD):设 0。若复用则按器件时序设置(通常 0~1)。
- FSMC_DataSetupTime
- 取值范围:1 ~ 0xFF(注意 header 的 IS 宏要求 >0)
- 作用:数据建立/保持时间(HCLK 周期数),是影响读写稳定性与速度的关键参数。
- 建议(并口 LCD):写操作常需较大值,典型 3~20,视 HCLK 与 LCD 允许的传输时间而定。
- FSMC_BusTurnAroundDuration
- 取值范围:0 ~ 0xF(HCLK 周期数)
- 作用:总线切换(bus turnaround)延迟,仅在地址/数据复用或读写切换时有用。
- 建议(非复用并口 LCD):设 0。
- FSMC_CLKDivision
- 取值范围:1 ~ 0xF(以 header 所述,但在异步访问中不使用)
- 作用:输出给外设的 CLK 分频(用于同步访问,如 CRAM/同步 NOR)。
- 建议(异步并口 LCD):设 0 或不使用(你一般设 0)。
- FSMC_DataLatency
- 取值范围:0 ~ 0xF(对同步 burst NOR 有效;异步访问通常无意义)
- 作用:在同步模式下,表示在第一数据到来前的额外内存时钟延迟。
- 建议(异步 LCD):设 0。
- FSMC_AccessMode
- 可选值:FSMC_AccessMode_A / _B / _C / _D
- 作用:不同访问模式改变总线相位(地址/数据相对 HCLK 的时序)。
- 建议:常用 FSMC_AccessMode_B(你当前使用的),若遇时序问题可尝试切换 A/B/C/D 以微调相位。
3.2 8080接口
8080又叫做MCU接口是一种用于并行数据通信的接口时序标准,源于 Intel 8080 微处理器。Intel 公司在 1974 年推出了这款 8 位微处理器, 8080 微处理器的接口通信规则逐渐演变成了被广泛应用的 8080 时序标准。
8080时序的信号(X表示低电平)组成如下:
- CSX:片选线,低电平有效
- RESX:复位线,低电平复位
- D/CX:命令/数据选择,高电平时数据,低电平是命令
- RDX:允许设备将数据输出到数据总线上,供主机读取。上升沿读取
- WRX:主机可将数据写入到设备中,上升沿写入
- D[17:0]:用于传输数据或命令,数据总线宽度也有 8 位和 16 位等多种
- 下图为8086的时序图总共有两个时钟周期,分别是写入指令和写入数据
以下是关于8080接口详细时序电气参数
3.3 FSMC时序和8080时序对比
- 以下是两个时序的对比
- 8080时序
- FSMC时序
- 信号对比
FSMC信号 功能 8080信号 功能 A[25:0] 地址线 D/CX 指令选择 NEx 片选 CSX 片选 NOE 读使能 RDX 读数据 NWE 写使能 WDX 写数据 D[15:0] 数据总线 D[17:0] 数据总线
4.BSP程序
4.1 初始化及参数计算
- 初始化GPIO口,若是开发板那么需要查看对应的原理图。若不是可以自己根据FSMC(NOR/PSRAM/SRAM)的管脚图连接。
- 需要注意的是地址线有A0-A25但我们只需要其中的Ax,由于我使用的是野火的霸道开发板其原理图如下
- 它使用A23作为D/CX,NE4作为片选也就是说我们需要选择
FSMC_Bank1_NORSRAM4
- 根据上面8080时序的电气参数可知
- 写周期(
twc
)最小是66ns - 数据建立时间(
tdst
)最小10ns
- 写周期(
- 根据FSMC Mode B可知每次发送数据分为两个周期
- 发送地址周期:ADDSET(Address Setup)+1
- 发送数据周期:DATAST(Data Setup)+1
- 发送一次数据的总周期是:发送地址周期+发送数据周期,这个结果不能小于8080的写时序周期(
twc
) - 1个HCLK等于1/72MHz ≈ 13.8ns
- 66ns / 13.8ns ≈ 4.78 ≈ 5个HCLK
- 若DATAST = 3,实际3 + 1 = 4
- 则ADDSET = 2,实际2 + 1 = 3
- 4 * 13.8ns = 55.2ns >
tdst
- 3 * 13.8ns = 41.4ns
- 55.2ns + 41.4ns = 96.8ns >> 66ns
- 实际可根据示波器或者逻辑分析仪进行分析,优化参数可以使其通信速率更快
实际写入地址计算:
根据《STM32F10x Reference Manual》21.4.1节「NOR/PSRAM address mapping」
实际上我们写的地址是AHB的地址,随后映射到外部内存地址
根据上图可知起始地址为6000 0000h,由于使用的是Bank 1 - NOR/PSRAM 4
将其27和26位置1,得到结果6C00 0000
根据如下表格,可知如果我们使用的是16bit的数据位宽地址还需要右移1位
则对应的地址如下
- 写命令的地址
0x6C000000
- 其中A23作为D/CX,又因为使用16bit的位宽最终左移为(23+1),得到最终写数据的地址
0x6C000000 |= (1 << (23 + 1)) =
- 写命令的地址
ili9341.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
void ilI9341Init(void);
void ilI9341FillRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color);
void ilI934Clear(void);
ili9341.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297/**
* @file ili9341.c
* @author Jamiexu(Jamie793/Jamies19) (doxm@foxmail.com)
* @brief
* @version 0.1
* @date 2025-08-16
*
* @copyright Copyright (c) 2025
*
*/
static inline void ili_write_cmd(uint16_t cmd) {
// 发送命令到 ILI9341
*(__IO uint16_t *)ILI9341_CMD_ADDRESS = cmd;
}
static inline void ili_write_data(uint16_t data) {
// 发送数据到 ILI9341
*(__IO uint16_t *)ILI9341_DATA_ADDRESS = data;
}
static inline uint16_t ili_write(uint16_t cmd, uint16_t data) {
// 发送命令和数据到 ILI9341
ili_write_cmd(cmd);
ili_write_data(data);
}
static inline void ili_delay(uint32_t ms) {
delayMs(ms);
}
/* 设置绘图窗口(含发出 Memory Write 命令) */
static void ili_set_window(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
{
/* 边界裁剪 */
if (x1 >= ILI9341_WIDTH) x1 = ILI9341_WIDTH - 1;
if (x2 >= ILI9341_WIDTH) x2 = ILI9341_WIDTH - 1;
if (y1 >= ILI9341_HEIGHT) y1 = ILI9341_HEIGHT - 1;
if (y2 >= ILI9341_HEIGHT) y2 = ILI9341_HEIGHT - 1;
/* 列地址 */
ili_write_cmd(0x2A);
ili_write_data((uint8_t)(x1 >> 8));
ili_write_data((uint8_t)(x1 & 0xFF));
ili_write_data((uint8_t)(x2 >> 8));
ili_write_data((uint8_t)(x2 & 0xFF));
/* 行地址 */
ili_write_cmd(0x2B);
ili_write_data((uint8_t)(y1 >> 8));
ili_write_data((uint8_t)(y1 & 0xFF));
ili_write_data((uint8_t)(y2 >> 8));
ili_write_data((uint8_t)(y2 & 0xFF));
/* 准备写入像素数据 */
ili_write_cmd(0x2C);
}
/* 用 16-bit 颜色填充矩形区域(color: RGB565) */
void ilI9341FillRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color)
{
if (w == 0 || h == 0) return;
uint32_t x1 = x + w - 1;
uint32_t y1 = y + h - 1;
if (x >= ILI9341_WIDTH || y >= ILI9341_HEIGHT) return;
if (x1 >= ILI9341_WIDTH) x1 = ILI9341_WIDTH - 1;
if (y1 >= ILI9341_HEIGHT) y1 = ILI9341_HEIGHT - 1;
ili_set_window(x, y, (uint16_t)x1, (uint16_t)y1);
uint32_t total = (uint32_t)(x1 - x + 1) * (uint32_t)(y1 - y + 1);
for (uint32_t i = 0; i < total; i++) {
ili_write_data(color);
}
}
/* 填充整个屏幕 */
void ilI934Clear(void)
{
ilI9341FillRect(0, 0, ILI9341_WIDTH, ILI9341_HEIGHT, ILI9341_BACKGROUND_COLOR);
}
void ilI9341Init(void) {
/* 硬件复位 */
GPIO_ILI9341_RST_PORT->ODR &= ~GPIO_ILI9341_RST_PIN; // 复位引脚低电平
ili_delay(120); // 等待复位
GPIO_ILI9341_RST_PORT->ODR |= GPIO_ILI9341_RST_PIN;
/* 退出睡眠 */
ili_write_cmd(0x11);
ili_delay(120);
/* 电源与驱动设置(常见序列,按模块手册可调整) */
ili_write_cmd(0xCB);
ili_write_data(0x39); ili_write_data(0x2C); ili_write_data(0x00); ili_write_data(0x34); ili_write_data(0x02);
ili_write_cmd(0xCF);
ili_write_data(0x00); ili_write_data(0xC1); ili_write_data(0x30);
ili_write_cmd(0xE8);
ili_write_data(0x85); ili_write_data(0x00); ili_write_data(0x78);
ili_write_cmd(0xEA);
ili_write_data(0x00); ili_write_data(0x00);
ili_write_cmd(0xED);
ili_write_data(0x64); ili_write_data(0x03); ili_write_data(0x12); ili_write_data(0x81);
/* 单字节命令可直接用 ili_write */
ili_write(0xF7, 0x20); // Pump ratio
ili_write(0xC0, 0x23); // Power control 1
ili_write(0xC1, 0x10); // Power control 2
ili_write_cmd(0xC5);
ili_write_data(0x3E); ili_write_data(0x28); // VCOM control
ili_write_cmd(0x36); ili_write_data(0x48); // MADCTL (方向/顺序,按需调整)
ili_write(0x3A, 0x55); // Pixel Format = 16-bit
/* 帧率与显示功能 */
ili_write_cmd(0xB1);
ili_write_data(0x00); ili_write_data(0x18);
ili_write_cmd(0xB6);
ili_write_data(0x0A); ili_write_data(0x82); ili_write_data(0x27);
/* Gamma 校正(示例值) */
ili_write_cmd(0xE0);
ili_write_data(0x0F); ili_write_data(0x1A); ili_write_data(0x0F); ili_write_data(0x18);
ili_write_data(0x2F); ili_write_data(0x28); ili_write_data(0x20); ili_write_data(0x22);
ili_write_data(0x1F); ili_write_data(0x1B); ili_write_data(0x23); ili_write_data(0x37);
ili_write_data(0x00); ili_write_data(0x07); ili_write_data(0x02); ili_write_data(0x10);
ili_write_cmd(0xE1);
ili_write_data(0x0F); ili_write_data(0x1B); ili_write_data(0x0F); ili_write_data(0x17);
ili_write_data(0x33); ili_write_data(0x2C); ili_write_data(0x29); ili_write_data(0x2E);
ili_write_data(0x30); ili_write_data(0x30); ili_write_data(0x39); ili_write_data(0x3F);
ili_write_data(0x00); ili_write_data(0x07); ili_write_data(0x03); ili_write_data(0x10);
/* 再次确保退出睡眠并打开显示 */
ili_write_cmd(0x11);
ili_delay(120);
ili_write_cmd(0x29); // Display ON
ili_delay(20);
ilI934Clear(); // 清屏
ili_delay(50);
GPIO_ILI9341_BL_PORT->ODR &= ~GPIO_ILI9341_BL_PIN; // 打开背光
}
5. 移植LVGL
5.1 移植屏幕设备
首先看一下LVGL(v8.3)所需的配置信息
来到工程下
Libraries
目录执行命令将lvgl库克隆到本地若我们的工程已有git版本管理,可以通过添加子模块的方式下载lvgl
git submodule init
git submodule add https://github.com/lvgl/lvgl
将lvgl目录下
lv_conf_template.h
复制一份命名为lv_conf.h
移动到Core/Inc
目录修改文件将
#if 0
修改成#if 1
启用头文件的配置LV_MEM_SIZE
用于LVGL分配内存使用的内存池大小,可以将前面的系数修改成合适的大小,这里修改成了12根据不同的板子RAM大小配置,越大越好但是越大板子使用的RAM越多后面可能会内存不足。还需要留给其他模块一些内存其他的配置信息可根据官方文档
复制porting目录下的文件
lv_port_disp_template.c/.h
放置到Core中对应的文件夹中并添加到Keil工程,可以删除后缀template
修改
lv_port_disp.h
文件,将#if 0
修改成#if 1
启用头文件的配置,注释掉多余宏命令或者将宏添加到工程中。修改
lv_port_disp.c
文件,配置lvgl缓存区用来存放用于渲染的像素数据。可根据自己的配置选择三种不同的缓冲区,这里选择第二种修改
disp_flush
函数添加自己的打点函数或者是填充函数,不推荐打点效率太慢了。这里使用的是区域填充- 可选:可将屏幕初始化函数放到
disp_init
中
- 可选:可将屏幕初始化函数放到
添加lvgl/src目录下所有文件到工程中,部分文件可根据自己的需求选择。可建立分组存放不同目录下的文件,这里图省事直接全部放在一起并且所有文件都添加了
将lvgl目录添加到头文件列表中
添加LVGL宏定到工程中,否则会出现找不到
lv_conf.h
文件创建一个TIM定时器用来给LVGL提供心跳,终端周期是1ms
在启动文件将堆栈改大一点否则可能出现添加控件后进入
HardFault
1 | /** |
- 最后的效果图如下
5.2 移植触摸设备
- 复制porting目录下的文件
lv_port_indev_template.c/.h
放置到Core中对应的文件夹中并添加到Keil工程,可以删除后缀`template - 修改
lv_port_indev.h/.c
文件,将#if 0
修改成#if 1
启用头文件的配置 - 修改
lv_port_indev.c
只保留touchpad相关的函数其他的可以注释掉,修改图中的两个函数使用自己的API判断是否有触摸及其触摸的位置 - 以下是完整代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839/**
* @file lv_port_indev_templ.c
*
*/
/*Copy this file as "lv_port_indev.c" and set this value to "1" to enable content*/
/*********************
* INCLUDES
*********************/
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static void touchpad_init(void);
static void touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
static bool touchpad_is_pressed(void);
static void touchpad_get_xy(lv_coord_t * x, lv_coord_t * y);
// static void mouse_init(void);
// static void mouse_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
// static bool mouse_is_pressed(void);
// static void mouse_get_xy(lv_coord_t * x, lv_coord_t * y);
// static void keypad_init(void);
// static void keypad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
// static uint32_t keypad_get_key(void);
// static void encoder_init(void);
// static void encoder_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
// static void encoder_handler(void);
// static void button_init(void);
// static void button_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
// static int8_t button_get_pressed_id(void);
// static bool button_is_pressed(uint8_t id);
/**********************
* STATIC VARIABLES
**********************/
lv_indev_t * indev_touchpad;
lv_indev_t * indev_mouse;
lv_indev_t * indev_keypad;
lv_indev_t * indev_encoder;
lv_indev_t * indev_button;
static int32_t encoder_diff;
static lv_indev_state_t encoder_state;
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
void lv_port_indev_init(void)
{
/**
* Here you will find example implementation of input devices supported by LittelvGL:
* - Touchpad
* - Mouse (with cursor support)
* - Keypad (supports GUI usage only with key)
* - Encoder (supports GUI usage only with: left, right, push)
* - Button (external buttons to press points on the screen)
*
* The `..._read()` function are only examples.
* You should shape them according to your hardware
*/
static lv_indev_drv_t indev_drv;
/*------------------
* Touchpad
* -----------------*/
/*Initialize your touchpad if you have*/
touchpad_init();
/*Register a touchpad input device*/
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = touchpad_read;
indev_touchpad = lv_indev_drv_register(&indev_drv);
/*------------------
* Mouse
* -----------------*/
// /*Initialize your mouse if you have*/
// mouse_init();
// /*Register a mouse input device*/
// lv_indev_drv_init(&indev_drv);
// indev_drv.type = LV_INDEV_TYPE_POINTER;
// indev_drv.read_cb = mouse_read;
// indev_mouse = lv_indev_drv_register(&indev_drv);
// /*Set cursor. For simplicity set a HOME symbol now.*/
// lv_obj_t * mouse_cursor = lv_img_create(lv_scr_act());
// lv_img_set_src(mouse_cursor, LV_SYMBOL_HOME);
// lv_indev_set_cursor(indev_mouse, mouse_cursor);
/*------------------
* Keypad
* -----------------*/
// /*Initialize your keypad or keyboard if you have*/
// keypad_init();
// /*Register a keypad input device*/
// lv_indev_drv_init(&indev_drv);
// indev_drv.type = LV_INDEV_TYPE_KEYPAD;
// indev_drv.read_cb = keypad_read;
// indev_keypad = lv_indev_drv_register(&indev_drv);
/*Later you should create group(s) with `lv_group_t * group = lv_group_create()`,
*add objects to the group with `lv_group_add_obj(group, obj)`
*and assign this input device to group to navigate in it:
*`lv_indev_set_group(indev_keypad, group);`*/
/*------------------
* Encoder
* -----------------*/
// /*Initialize your encoder if you have*/
// encoder_init();
// /*Register a encoder input device*/
// lv_indev_drv_init(&indev_drv);
// indev_drv.type = LV_INDEV_TYPE_ENCODER;
// indev_drv.read_cb = encoder_read;
// indev_encoder = lv_indev_drv_register(&indev_drv);
/*Later you should create group(s) with `lv_group_t * group = lv_group_create()`,
*add objects to the group with `lv_group_add_obj(group, obj)`
*and assign this input device to group to navigate in it:
*`lv_indev_set_group(indev_encoder, group);`*/
/*------------------
* Button
* -----------------*/
/*Initialize your button if you have*/
// button_init();
// /*Register a button input device*/
// lv_indev_drv_init(&indev_drv);
// indev_drv.type = LV_INDEV_TYPE_BUTTON;
// indev_drv.read_cb = button_read;
// indev_button = lv_indev_drv_register(&indev_drv);
// /*Assign buttons to points on the screen*/
// static const lv_point_t btn_points[2] = {
// {10, 10}, /*Button 0 -> x:10; y:10*/
// {40, 100}, /*Button 1 -> x:40; y:100*/
// };
// lv_indev_set_button_points(indev_button, btn_points);
}
/**********************
* STATIC FUNCTIONS
**********************/
/*------------------
* Touchpad
* -----------------*/
/*Initialize your touchpad*/
static void touchpad_init(void)
{
/*Your code comes here*/
}
/*Will be called by the library to read the touchpad*/
static void touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
static lv_coord_t last_x = 0;
static lv_coord_t last_y = 0;
/*Save the pressed coordinates and the state*/
if(touchpad_is_pressed()) {
touchpad_get_xy(&last_x, &last_y);
data->state = LV_INDEV_STATE_PR;
}
else {
data->state = LV_INDEV_STATE_REL;
}
/*Set the last pressed coordinates*/
data->point.x = last_x;
data->point.y = last_y;
}
/*Return true is the touchpad is pressed*/
static bool touchpad_is_pressed(void)
{
/*Your code comes here*/
return xpt2046IsTouched();
}
/*Get the x and y coordinates if the touchpad is pressed*/
static void touchpad_get_xy(lv_coord_t * x, lv_coord_t * y)
{
/*Your code comes here*/
xpt2046_point_t point;
if (xpt2046GetXY(&point) == 0) { // Get touch coordinates
(*x) = point.x;
(*y) = point.y;
} else {
(*x) = 0; // 如果获取失败,返回默认值
(*y) = 0;
}
}
/*------------------
// * Mouse
// * -----------------*/
// /*Initialize your mouse*/
// static void mouse_init(void)
// {
// /*Your code comes here*/
// }
// /*Will be called by the library to read the mouse*/
// static void mouse_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
// {
// /*Get the current x and y coordinates*/
// mouse_get_xy(&data->point.x, &data->point.y);
// /*Get whether the mouse button is pressed or released*/
// if(mouse_is_pressed()) {
// data->state = LV_INDEV_STATE_PR;
// }
// else {
// data->state = LV_INDEV_STATE_REL;
// }
// }
// /*Return true is the mouse button is pressed*/
// static bool mouse_is_pressed(void)
// {
// /*Your code comes here*/
// return false;
// }
// /*Get the x and y coordinates if the mouse is pressed*/
// static void mouse_get_xy(lv_coord_t * x, lv_coord_t * y)
// {
// /*Your code comes here*/
// (*x) = 0;
// (*y) = 0;
// }
// /*------------------
// * Keypad
// * -----------------*/
// /*Initialize your keypad*/
// static void keypad_init(void)
// {
// /*Your code comes here*/
// }
// /*Will be called by the library to read the mouse*/
// static void keypad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
// {
// static uint32_t last_key = 0;
// /*Get the current x and y coordinates*/
// mouse_get_xy(&data->point.x, &data->point.y);
// /*Get whether the a key is pressed and save the pressed key*/
// uint32_t act_key = keypad_get_key();
// if(act_key != 0) {
// data->state = LV_INDEV_STATE_PR;
// /*Translate the keys to LVGL control characters according to your key definitions*/
// switch(act_key) {
// case 1:
// act_key = LV_KEY_NEXT;
// break;
// case 2:
// act_key = LV_KEY_PREV;
// break;
// case 3:
// act_key = LV_KEY_LEFT;
// break;
// case 4:
// act_key = LV_KEY_RIGHT;
// break;
// case 5:
// act_key = LV_KEY_ENTER;
// break;
// }
// last_key = act_key;
// }
// else {
// data->state = LV_INDEV_STATE_REL;
// }
// data->key = last_key;
// }
// /*Get the currently being pressed key. 0 if no key is pressed*/
// static uint32_t keypad_get_key(void)
// {
// /*Your code comes here*/
// return 0;
// }
// /*------------------
// * Encoder
// * -----------------*/
// /*Initialize your keypad*/
// static void encoder_init(void)
// {
// /*Your code comes here*/
// }
// /*Will be called by the library to read the encoder*/
// static void encoder_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
// {
// data->enc_diff = encoder_diff;
// data->state = encoder_state;
// }
// /*Call this function in an interrupt to process encoder events (turn, press)*/
// static void encoder_handler(void)
// {
// /*Your code comes here*/
// encoder_diff += 0;
// encoder_state = LV_INDEV_STATE_REL;
// }
/*------------------
* Button
* -----------------*/
/*Initialize your buttons*/
// static void button_init(void)
// {
// /*Your code comes here*/
// }
// /*Will be called by the library to read the button*/
// static void button_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
// {
// static uint8_t last_btn = 0;
// /*Get the pressed button's ID*/
// int8_t btn_act = button_get_pressed_id();
// if(btn_act >= 0) {
// data->state = LV_INDEV_STATE_PR;
// last_btn = btn_act;
// }
// else {
// data->state = LV_INDEV_STATE_REL;
// }
// /*Save the last pressed button's ID*/
// data->btn_id = last_btn;
// }
// /*Get ID (0, 1, 2 ..) of the pressed button*/
// static int8_t button_get_pressed_id(void)
// {
// uint8_t i;
// /*Check to buttons see which is being pressed (assume there are 2 buttons)*/
// for(i = 0; i < 2; i++) {
// /*Return the pressed button's ID*/
// if(button_is_pressed(i)) {
// return i;
// }
// }
// /*No button pressed*/
// return -1;
// }
// /*Test if `id` button is pressed or not*/
// static bool button_is_pressed(uint8_t id)
// {
// /*Your code comes here*/
// return false;
// }
/*This dummy typedef exists purely to silence -Wpedantic.*/
typedef int keep_pedantic_happy;
1 | /** |
- 将程序烧录后显示如下图
- 点击按钮后变成如下图
- 至此所有的工作完结
6. 开源说明
- 本文章所有程序均由本人编写,部分借助AI工具快速生成,如屏幕的初始化序列等。
- 所有程序已开源: https://github.com/Jamie793/8080-lvgl-porting
7. 参考文献
- 嵌入式火. FSMC— 扩展外部 SRAM [EB/OL]]. https://doc.embedfire.com/mcu/stm32/f103badao/std/zh/latest/book/FSMC.html.
- STMicroelectronics. RM0008 STM32F101xx, STM32F102xx, STM32F103xx, STM32F105xx and STM32F107xx advanced ARM®-based 32-bit MCUs[Z]. 2024. https://www.st.com/resource/en/reference_manual/rm0008-stm32f101xx-stm32f102xx-stm32f103xx-stm32f105xx-and-stm32f107xx-advanced-armbased-32bit-mcus-stmicroelectronics.pdf.
- STMicroelectronics. STM32F103xE Datasheet[Z]. 2024. https://www.st.com/resource/en/datasheet/stm32f103rc.pdf.
- 【Proteus/8086】微机与接口技术实验:计时器 [EB/OL]. 2023. https://blog.csdn.net/qq_61814350/article/details/135140964.
- Adafruit Industries. ILI9341 Datasheet [Z]. https://cdn-shop.adafruit.com/datasheets/ILI9341.pdf.
- LVGL. LVGL Documentation 8.3 [EB/OL]. https://docs.lvgl.io/8.3/.