LV10-01-LCD驱动-03-LCD控制器

接下来了解一下imx6ull中的lcd控制器——eLCDIF?若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
PC端开发环境 Windows Windows11
Ubuntu Ubuntu20.04.2的64位版本
VMware® Workstation 17 Pro 17.6.0 build-24238078
终端软件 MobaXterm(Professional Edition v23.0 Build 5042 (license))
Win32DiskImager Win32DiskImager v1.0
Linux开发板环境 Linux开发板 正点原子 i.MX6ULL Linux 阿尔法开发板
uboot NXP官方提供的uboot,使用的uboot版本为U-Boot 2019.04
linux内核 linux-4.19.71(NXP官方提供)
点击查看本文参考资料
分类 网址 说明
官方网站 https://www.arm.com/ ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档
https://www.nxp.com.cn/ NXP官方网站
https://www.nxpic.org.cn/NXP 官方社区
https://u-boot.readthedocs.io/en/latest/u-boot官网
https://www.kernel.org/linux内核官网
点击查看相关文件下载
分类 网址 说明
NXP https://github.com/nxp-imx NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库
nxp-imx/linux-imx/releases/tag/v4.19.71 NXP linux内核仓库tags中的v4.19.71
nxp-imx/uboot-imx/releases/tag/rel_imx_4.19.35_1.1.0 NXP u-boot仓库tags中的rel_imx_4.19.35_1.1.0
I.MX6ULL i.MX 6ULL Applications Processors for Industrial Products I.MX6ULL 芯片手册(datasheet,可以在线查看)
i.MX 6ULL Applications ProcessorReference Manual I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网)
Source Code https://elixir.bootlin.com/linux/latest/source linux kernel源码
kernel/git/stable/linux.git - Linux kernel stable tree linux kernel源码(官网,tag 4.19.71)
https://elixir.bootlin.com/u-boot/latest/source uboot源码

一、eLCDIF——液晶控制器

1. eLCDIF简介

IMX6U系列芯片内部自带一个增强型液晶接口外设eLCDIF(Enhanced LCD Interface),配合使用DDR作为显存, 可直接控制液晶面板,无需额外增加液晶控制器芯片。

IMX6U的eLCDIF液晶控制器最高支持1366x768分辨率的屏幕; 可支持多种颜色格式,包括RGB888、RGB565、ARGB8888等(其中的“A”是指透明像素)。 还可配合像素渲染流水线PXP(Pixel Pipeline)进行复杂的图像处理,如格式转换、缩放、翻转以及图层混合等操作, 使IMX6U有非常出色的图形显示性能。特性如下:

①、支持DOTCLK模式,也就是 RGB LCD 的 DE 模式。

②、支持 VSYNC 模式以实现高速数据传输,跟MPU模式类似,多了VSYNC信号。针对高速数据传输(行场信号)。

③、支持MPU模式:有些显示屏自带显存,只需要把命令、数据发送给显示屏即可;就是前面讲的8080接口

④、支持 ITU-R BT.656 格式的 4:2:2 的 YCbCr 数字视频,并且将其转换为模拟 TV 信号。

⑤、支持 8/16/18/24/32 位 LCD,取决于IO的复用设置及寄存器配置。

2. 支持多种接口?

eLCDIF 支持三种接口: MPU 接口、 VSYNC 接口和 DOTCLK 接口。

2.1 MPU 接口

MPU 接口用于在 I.MX6U 和 LCD 屏幕直接传输数据和命令,这个接口用于 6080/8080 接口的 LCD 屏幕,比如我们学习 STM32 的时候常用到的 MCU 屏幕。如果寄存器 LCDIF_CTRL的位 DOTCLK_MODE、 DVI_MODE 和 VSYNC_MODE 都为 0 的话就表示 LCDIF 工作在 MPU接口模式。

关于 MPU 接口的详细信息以及时序参考《I.MX6ULL 参考手册》第 2150 页的“34.4.6 MPU Interface”小节.

2.2 VSYNC 接口

VSYNC 接口时序和 MPU 接口时序基本一样,只是多了 VSYNC 信号来作为帧同步,当LCDIF_CTRL 的位 VSYNC_MODE 为 1 的时候此接口使能。

关于 VSYNC 接口的详细信息可以参考《I.MX6ULL 参考手册》第 2152 页的“34.4.7 VSYNC Interface”小节 。

2.3 DOTCLK 接口

DOTCLK 接口就是用来连接 RGB LCD 接口屏幕的, 它包括 VSYNC、 HSYNC、 DOTCLK和 ENABLE(可选的)这四个信号,这样的接口通常被称为 RGB 接口。

可以看《I.MX6ULL 参考手册》——34.4.8 DOTCLK Interface

3. 框图解析

eLCDIF控制器的结构框图

3.1 通讯引脚

上图的标号 1 处表示eLCDIF的通讯引脚,eLCDIF的通讯引脚与液晶显示面板控制信号一一对应, 包含有HSYNC、VSYNC、DE、CLK以及RGB数据线各8根。设计硬件时把液晶面板与IMX6对应的这些引脚连接起来即可, 查阅《I.MX6ULL 参考手册》——Table 34-5. Pin use in DOTCLK Mode ,可获知eLCDIF信号线对应的引脚,具体见下表。

3.2 总线接口

eLCDIF的液晶接口有两个总线接口,System Bus总线,用于向eLCDIF液晶接口的FIFO中写入数据。 而Control Bus用于设置eLCDIF用于读、写控制寄存器以及DMA、数据寄存器等等。

3.3 液晶接口(LCD Interface)

上图的标号处表示eLCDIF的液晶接口(LCD Interface),它是eLCDIF外设的主要功能部件,受控制总线(Control Bus)的寄存器控制, 从系统总线(System Bus)获得输入像素数据,经过一系列转换后通过eLCDIF的通讯引脚发送至外接的液晶面板。

其中控制总线的寄存器可以配置显存地址、输入像素数据的格式、输出的数据信号线宽度、 各个控制信号的有效极性以及控制时序中的VSW、VBP等参数,还能配置使用DMA传输。

使用寄存器初始化好eLCDIF的后,它会从“LFIFO”和“TXFIFO”中获取数据进行转换处理(格式转换、移位等操作)并传输出去。 当FIFO中的数据量低于一定程度时,它会向系统总线(SystemBus)发起请求,系统总线会把显存地址的数据搬运至FIFO中。 FIFO还可以配置阈值,低于该阈值时系统总线会提高获取数据的优先级。

eLCDIF正常运行后,数据从显存到液晶屏全程不需要内核的干预,程序控制时我们只要把像素数据写入到显存即可。

3.4 驱动时钟

elcdf模块包含两个时钟信号,分别是BUS CLOCK(apb_clk)和DISPLAY CLOCK(pix_clk)。

3.4.1 BUS CLOCK(apb_clk)

这个BUS CLOCK(apb_clk)就是指eLCDIF外设的 根时钟LCDIF_CLK_ROOT,它给eLCDIF提供驱动的时钟源,在时钟树中的结构具体如下图所示。

根时钟

LCDIF_CLK_ROOT根时钟可以选择多种输入时钟源,首先是时钟源预选择器(Pre-multiplexer)支持使用如下时钟:

  • PLL2:System PLL,该时钟频率通常为528MHz。
  • PLL2 PFD0:该时钟常规配置为352MHz。
  • PLL2 PFD1:该时钟常规配置为594MHz。
  • PLL3 PFD3:该时钟常规配置为454.74MHz。
  • PLL3 PFD1:该时钟常规配置为664.62MHz。
  • PLL5:Video PLL,该时钟常规配置为649.52MHz。

预选择器得到的时钟,可根据需要进行分频配置,分频后输入到时钟源选择器(multiplexer)作为LCDIF_CLK_ROOT默认的时钟源, 除此之外,图中的时钟的选择器还包含其它可选的输入时钟:ipp_di0_clk、ipp_di1_clk、ldb_di0_clk、ldb_di1_clk, 不过关于这些时钟在参考手册中并没有介绍,而且在寄存器中并没有这些时钟源的分频、选择的配置, 也许该选择器是兼容其它设备而保留的内容,所以使用时我们直接选择预选择器得到的时钟作为LCDIF_CLK_ROOT的输入时钟源即可。

3.4.2 DISPLAY CLOCK(pix_clk)

这个DISPLAY CLOCK(pix_clk)是指eLCDIF与液晶面板接口的 像素时钟LCDIF_pix_clk,它的时钟频率与根时钟LCDIF_CLK_ROOT一致,不过它们 的时钟开关是分开的,其中 LCDIF_CLK_ROOT 使用寄存器位CCM_CCGR2[CG14] 控制,而LCDIF_pix_clk使用寄存器位CCM_CCGR3[CG5]控制。

4. 数据传输与处理

这里就简单了解下吧,看看手册中的框图。

  • 《I.MX6ULL 参考手册》——34.4.2 Write Data Path
image-20250410173212689
  • 《I.MX6ULL 参考手册》——34.4.3 Read Data Path
image-20250410173145926

二、eLCDIF寄存器

查看任何芯片的LCD控制器寄存器时,记住几个要点:

① 怎么把LCD的信息告诉LCD控制器:即分辨率、行列时序、像素时钟等;

② 怎么把显存地址、像素格式告诉LCD控制器。

对于i.MX6ULL来说,我们可以看《I.MX6ULL参考手册》——34.6 eLCDIF Memory Map/Register Definition

image-20250411093613402

1. LCDIF_CTRLn

eLCDIF General Control Register (LCDIF_CTRLn):

image-20250411094541467
位域 名称 读写 描述
[31] SFTRST R/W 软件复位,正常工作时应设为0;如果设为1,它会复位整个LCD控制器
[30] CLKGATE R/W 时钟开关, 0:正常工作时要设置为0; 1:关闭LCD控制器时钟
[29] YCBCR422_INPUT R/W 使用RGB接口时,设置为0;其他接口我们暂时不关心
[28] READ_WRITEB R/W 使用RGB接口时,设置为0;其他接口我们暂时不关心
[27] WAIT_FOR_VSYNC_EDGE R/W 在VSYNC模式时,设置为1;我们不关心
[26] DATA_SHIFT_DIR R/W 在DVI模式下才需要设置,我们不关心
[25:21] SHIFT_NUM_BITS R/W 在DVI模式下才需要设置,我们不关心
[20] DVI_MODE R/W 设置为1时,使用DVI模式,就是ITU-R BT.656数字接口
[19] BYPASS_COUNT R/W DOTCLK和DVI模式下需要设置为1;MPU、VSYNC模式时设为0
[18] VSYNC_MODE R/W 使用VSYNC模式时,设置为1
[17] DOTCLK_MODE R/W 使用DOTCLK模式时,设置为1;本实验用的就是这个模式
[16] DATA_SELECT R/W MPU模式下才用到,我们不关心
[15:14] INPUT_DATA_SWIZZLE R/W 显存中像素颜色的数据转给LCD控制器时,字节位置是否交换:
0x0:NO_SWAP,不交换;
0x0:LITTLE_ENDIAN,小字节序,跟NO_SWAP一样;
0x1:BIG_ENDIAN_SWAP,字节0、3交换;字节1、2交换;
0x1:SWAP_ALL_BYTES,字节0、3交换;字节1、2交换;
0x2:HWD_SWAP,半字交换,即0x12345678转为0x56781234
0x3:HWD_BYTE_SWAP,在每个半字内部放换字节, 即0x12345678转换为0x34127856
[13:12] CSC_DATA_SWIZZLE R/W 显存中的数据被传入LCD控制器内部并被转换为24BPP后,在它被转给LCD接口之前,字节位置是否交换: 0x0:NO_SWAP,不交换; 0x0:LITTLE_ENDIAN,小字节序,跟NO_SWAP一样; 0x1:BIG_ENDIAN_SWAP,字节0、3交换;字节1、2交换; 0x1:SWAP_ALL_BYTES,字节0、3交换;字节1、2交换; 0x2:HWD_SWAP,半字交换,即0x12345678转为0x56781234 0x3:HWD_BYTE_SWAP,在每个半字内部放换字节, 即0x12345678转换为0x34127856
[11:10] LCD_DATABUS_WIDTH R/W LCD数据总线宽度,就是对外输出的LCD数据的位宽, 0x0:16位; 0x1:8位; 0x2:18位; 0x3:24位
[9:8] WORD_LENGTH R/W 输入的数据格式,即显存中每个像素占多少位, 0x0:16位; 0x1:8位; 0x2:18位; 0x3:24位
[7] RGB_TO_YCBCR422_CSC R/W 设置为1时,使能颜色空间转换:RGB转为YCbCr
[6] ENABLE_PXP_HANDSHAKE R/W 当LCDIF_MASTER设置为1时,再设置这位, 则LCD控制器跟PXP之间的握手机制被关闭(我们不关心)
[5] MASTER R/W 设置为1时,LCD控制器成为bus master
[4] RSRVD0 R/W 保留
[3] DATA_FORMAT_16_BIT R/W WORD_LENGTH为0时,表示一个像素用16位,此位作用如下: 0:数据格式为ARGB555; 1:数据格式为RGB565
[2] DATA_FORMAT_18_BIT R/W WORD_LENGTH为2时,表示一个像素用18位,RGB数据还是保存在32位数据里,此位作用如下: 0:低18位用来表示RGB666,高14位无效 1:高18位用来表示RGB666,低14位无效
[1] DATA_FORMAT_24_BIT R/W WORD_LENGTH为3时,表示一个像素用24位,此位作用如下: 0:所有的24位数据都有效,格式为RGB888 1:转给LCD控制器的数据是24位的,但只用到其中的18位, 每个字节用来表示一个原色,每字节中高2位无效
[0] RUN R/W 使能LCD控制器,开始传输数据

2. LCDIF_CTRL1n

eLCDIF General Control1 Register (LCDIF_CTRL1n):

image-20250411094948349

我是使用TFT LCD,LCD控制器使用DOTCLK模式。本寄存器会用到BYTE_PACKING_FORMAT 。

位域 名称 读写 描述
[19:16] BYTE_PACKING_FORMAT R/W 用来表示一个32位的word中,哪些字节是有效的,即哪些字节是用来表示颜色的。 bit16、17、18、19分别对应byte0、1、2、3;某位为1,就表示对应的字节有效。 默认值是0xf,表示32位的word中,所有字节都有效。 对于8bpp,可以忽略本设置,所有的字节都是有效的; 对于16bpp,bit[1:0]、bit[3:2]分别对应一个字节,组合中的2位都为1时,对应的字节才有效; 对于24bpp,0x7表示32位数据中只用到3个字节,这称为“24 bit unpacked format”,即ARGB,其中的A字节被丢弃
[0] RESET R/W 用来复位了接的LCD, 0:LCD_RESET引脚输出低电平; 1:LCD_RESET引脚输出高电平

3. LCDIF_TRANSFER_COUNT

eLCDIF Horizontal and Vertical Valid Data Count Register(LCDIF_TRANSFER_COUNT):

image-20250411095536632                                                                                                                        
位域 名称 读写 描述
[31:16]V_COUNTR/W一帧中,有多少行有效数据,高16位是V_COUNT,是 LCD 的垂直分辨率。
[15:0] H_COUNTR/W一行中,有多少个像素,低 16 位是 H_COUNT,是 LCD 的水平分辨率。

如果 LCD 分辨率为1024*600 的话,那么 V_COUNT 就是 600, H_COUNT 就是 1024。

4. LCDIF_VDCTRL0n

eLCDIF VSYNC Mode and Dotclk Mode Control Register0(LCDIF_VDCTRL0n):

image-20250411095514380

本寄存器用来设置Vsync信号相关的时序,及极性。

位域 名称 读写 描述
[29] VSYNC_OEB R/W 用来控制VSYNC信号,对于DOTCLK模式,设为0, 0:VSYNC是输出引脚,用LCD控制器产生; 1:VSYNC是输入引脚
[28] ENABLE_PRESENT R/W 在DOTCLK模式下,硬件是否会产生数据使能信号ENALBE: 0:不产生; 1:产生
[27] VSYNC_POL R/W 用来决定VSYNC脉冲的极性, 0:低脉冲; 1:高脉冲
[26] HSYNC_POL R/W 用来决定HSYNC脉冲的极性, 0:低脉冲; 1:高脉冲
[25] DOTCLK_POL R/W 用来决定DOTCLK的极性, 0:LCD控制器在DOTCLK下降沿发送数据,LCD在上升沿捕获数据; 1:反过来
[24] ENABLE_POL R/W 用来决定ENABLE信号的极性, 0:数据有效期间,ENABLE信号为低; 1:反过来
[21] VSYNC_PERIOD_UNIT R/W 用来决定VSYNC_PERIOD的单位, 0:单位是像素时钟(pix_clk),这在VSYNC模式下使用; 1:单位是“整行”,这在DOTCLK模式下使用
[20] VSYNC_PULSE_WIDTH_UNIT R/W 用来决定VSYNC_PULSE_WIDTH的单位, 0:单位是像素时钟(pix_clk); 1:单位是“整行”
[19] HALF_LINE R/W VSYNC周期是否周加上半行的时间, 0:VSYNC周期=VSYNC_PERIOD; 1:VSYNC周期=VSYNC_PERIOD+HORIZONTAL_PERIOD/2
[18] HALF_LINE_MODE R/W 0:第1帧将在一行的中间结束,第2帧在一行的中间开始; 1:所有帧结束前都加上半行时间,这样所有帧都会起始于“行的开头”
[17:0] VSYNC_PULSE_WIDTH R/W VSYNC脉冲的宽度

5. LCDIF_VDCTRL1

eLCDIF VSYNC Mode and Dotclk Mode Control Register1(LCDIF_VDCTRL1):

image-20250411102445910
位域 名称 读写 描述
[29] VSYNC_PERIOD R/W 两个垂直同步信号之间的间隔,即垂直方向同步信号的总周期; 单位由VSYNC_PERIOD_UNIT决定

这个寄存器是 VSYNC 和 DOTCLK 模式控制寄存器 1,此寄存器只有一个功能,用来设置 VSYNC 总周期,就是:屏幕高度+VSPW+VBP+VFP。

6. LCDIF_VDCTRL2

LCDIF VSYNC Mode and Dotclk Mode Control Register2(LCDIF_VDCTRL2):

image-20250411102513858

HSYNC_PULSE_WIDTH:水平同步信号脉冲宽度;

HSYNC_PERIOD:两个水平同步信号之间的总数,即水平方向同步信号的总周期

位域 名称 读写 描述
[31:18] HSYNC_PULSE_WIDTH R/W HSYNC脉冲的宽度(单位:pix_clk)
[17:0] HSYNC_PERIOD R/W 整行的宽度,即两个HYSNC信号之间的宽度(单位:pix_clk)

这个寄存器分为高 16 位和低 16 位两部分,高 16位是 HSYNC_PULSE_WIDTH,用来设置 HSYNC 信号宽度,也就是 HSPW。低 16 位是HSYNC_PERIOD,设置 HSYNC 总周期,就是:屏幕宽度+HSPW+HBP+HFP。

7. LCDIF_VDCTRL3

eLCDIF VSYNC Mode and Dotclk Mode Control Register3(LCDIF_VDCTRL3):

image-20250411102600731
位域 名称 读写 描述
[29] MUX_SYNC_SIGNALS R/W 用不着
[28] VSYNC_ONLY R/W 0:DOTCLK模式时必须设置为0; 1:VSYNC模式时必须设置为1
[27:16] HORIZONTAL_WAIT_CNT R/W 水平方向上的等待像素个数,等于thp+thb
[15:0] VERTICAL_WAIT_CNT R/W 垂直方向上的等待行数,等于tvp+tvb

HORIZONTAL_WAIT_CNT(bit27:16):此位用于 DOTCLK 模式,用于设置 HSYNC 信号产生到有效数据产生之间的时间,也就是 HSPW+HBP。

VERTICAL_WAIR_CNT(bit15:0):和 HORIZONTAL_WAIT_CNT 一样,只是此位用于VSYNC 信号,也就是 VSPW+VBP。

8. LCDIF_VDCTRL4

eLCDIF VSYNC Mode and Dotclk Mode Control Register4(LCDIF_VDCTRL4):

image-20250411102630180
位域 名称 读写 描述
[31:29] DOTCLK_DLY_SEL R/W 在LCD控制器内部的DOTCLK输出到LCD_DOTCK引脚时,延时多久: 0:2ns; 1:4ns; 2:6ns; 3:8ns; 其他值保留
[18] SYNC_SIGNALS_ON R/W 同步信号使能位,设置为 1 的话使能 VSYNC、 HSYNC、DOTCLK 这些信号。DOTCLK模式下必须设为1
[17:0] DOTCLK_H_VALID_DATA_CNT R/W 水平方向上的有效像素个数(pix_clk),即分辨率的y

9. LCDIF_CUR_BUF

LCD Interface Current Buffer Address Register(LCDIF_CUR_BUF):

image-20250411102727298
位域 名称 读写 描述
[31:0] ADDR R/W LCD控制器正在传输的当前帧在显存中的地址

10. LCDIF_NEXT_BUF

LCD Interface Next Buffer Address Register(LCDIF_NEXT_BUF):

image-20250411102749667
位域 名称 读写 描述
[31:0] ADDR R/W 下一帧在显存中的地址

LCD控制器传输完当前帧后,会把LCDIF_NEXT_BUF寄存器的值复制到LCDIF_CUR_BUF寄存器。

三、裸机demo

1. 硬件原理图

1.1 电路原理图

image-20250411104720096

三个 SGM3157 的目的是在未使用 RGBLCD 的时候将 LCD_DATA7、LCD_DATA15 和 LCD_DATA23 这三个线隔离开来,因为 ALIENTEK 的屏幕的 LCD_R7/G7/B7这几个线用来设置 LCD 的 ID,所以这几根线上有上拉/下拉电阻。但是 I.MX6U 的 BOOT 设置也用到了 LCD_DATA7、 LCD_DATA15 和 LCD_DATA23 这三个引脚,所以接上屏幕以后屏幕上的 ID 电阻就会影响到 BOOT 设置,会导致代码无法运行,所以先将其隔离开来,如果要使用 RGB LCD 屏幕的时候再通过 LCD_DE 将其“连接”起来。

1.2 连接方式

image-20250411104820855

1.3 参数计算

我使用的屏幕是ATK-MD0430R-800480,读出来的ID会是0x4384。这款屏幕对应的一些时间参数如下:

ATK-MD0430R-800480
ID=4384
HOZVAL(水平显示区域) thd 800 tCLK
HSPW(horizontal sync width) thp 48 tCLK
HBP(horizontal back porch) thb 88 tCLK
HFP(horizontal front porth) thf 40 tCLK
LINE(垂直显示区域) tvd 480 th
VSPW(vertical sync width) tvp 3 th
VBP(vertical back porch) tvb 32 th
VFP(vertical front porch) tvf 13 th
像素时钟 - 31 MHz
1
2
3
4
5
6
7
8
9
10
11
显示一行的时间: HSPW + HBP + HOZVAL + HFP
显示一帧的时间: (VSPW+VBP+LINE+VFP) * (HSPW + HBP + HOZVAL + HFP)
显示一帧的时钟: (VSPW+VBP+LINE+VFP) * (HSPW + HBP + HOZVAL + HFP)
=(3+32+480+13) * (48+88+800+40)
=528*976
=515328
显示60帧的时钟:(VSPW+VBP+LINE+VFP) * (HSPW + HBP + HOZVAL + HFP) * 60
=515328*60
=30919680
≈30.92M
≈31M

2. 生成字模

想要显示英文字符的话需要用软件生成字模,这里可以用PCtoLCD2002 。字模选项设置界面。设置界面中点阵格式和取模方式等参数配置如图

image-20250411115351151

上图设置的取模方式,在右上角的取模说明里面有,即:从第一列开始向下每取 8 个点作为一个字节,如果最后不足 8 个点就补满 8 位。取模顺序是从高到低,即第一个点作为最高位。如*——-取为 10000000。其实就是按下图的方式:

image-20250411115435787

从上到下,从左到右,高位在前。我们按这样的取模方式,然后把 ASCII 字符集按 12*6 大小、 16*8、 24*12 和 32*16 大小取模出来(对应汉字大小为 12*12、 16*16、 24*24 和 32*32,字符的只有汉字的一半大!)。将取出的点阵数组保存在 font.h 里面,每个 12*6 的字符占用 12 个字节,每个 16*8 的字符占用 16 个字节,每个 24*12 的字符占用 36 个字节,每个 32*16 的字符占用 64 个字节。

3. demo源码

看这里:21_lcd/01_lcd_demo · 苏木/imx6ull-bare-demo - 码云 - 开源中国

3.1 出现的报错问题

这里有个坑,就是关于内联函数的。接下来看一下:

原本在 21_lcd/01_lcd_demo/bsp/lcd/bsp_lcd.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
/*
* @description : 画点函数
* @param - x : x轴坐标
* @param - y : y轴坐标
* @param - color : 颜色值
* @return : 无
*/
inline void lcd_drawpoint(unsigned short x,unsigned short y,unsigned int color)
{
*(unsigned int*)((unsigned int)tftlcd_dev.framebuffer +
tftlcd_dev.pixsize * (tftlcd_dev.width * y+x))=color;
}

/*
* @description : 读取指定点的颜色值
* @param - x : x轴坐标
* @param - y : y轴坐标
* @return : 读取到的指定点的颜色值
*/
inline unsigned int lcd_readpoint(unsigned short x,unsigned short y)
{
return *(unsigned int*)((unsigned int)tftlcd_dev.framebuffer +
tftlcd_dev.pixsize * (tftlcd_dev.width * y + x));
}

然后在21_lcd/01_lcd_demo/bsp/lcd/bsp_lcd.h中做如下声明:

1
2
inline void lcd_drawpoint(unsigned short x,unsigned short y,unsigned int color);
inline unsigned int lcd_readpoint(unsigned short x,unsigned short y);

这样在编译的时候会警告,说有找不到这两个函数定义。这个,一般来说,内联函数最好是定义在头文件中,并且加上static关键字保证一定可以展开。

3.2 解决办法

这里若是想要在.h文件中声明,在.c文件中实现,需要在 21_lcd/01_lcd_demo/bsp/lcd/bsp_lcd.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
/*
* @description : 画点函数
* @param - x : x轴坐标
* @param - y : y轴坐标
* @param - color : 颜色值
* @return : 无
*/
inline void lcd_drawpoint(unsigned short x,unsigned short y,unsigned int color)
{
*(unsigned int*)((unsigned int)tftlcd_dev.framebuffer +
tftlcd_dev.pixsize * (tftlcd_dev.width * y+x))=color;
}

/*
* @description : 读取指定点的颜色值
* @param - x : x轴坐标
* @param - y : y轴坐标
* @return : 读取到的指定点的颜色值
*/
inline unsigned int lcd_readpoint(unsigned short x,unsigned short y)
{
return *(unsigned int*)((unsigned int)tftlcd_dev.framebuffer +
tftlcd_dev.pixsize * (tftlcd_dev.width * y + x));
}

然后在21_lcd/01_lcd_demo/bsp/lcd/bsp_lcd.h中做如下声明:

1
2
void lcd_drawpoint(unsigned short x,unsigned short y,unsigned int color);
unsigned int lcd_readpoint(unsigned short x,unsigned short y);

我一般还是直接定义到头文件去。