【普中STM32F1xx开发攻略--标准库版】-- 第 40 章 FSMC-TFTLCD 显示实验

(1)实验平台:

普中STM32F103 朱雀、玄武开发板https://item.taobao.com/item.htm?id=620302685024(2)资料下载:普中科技-各型号产品资料下载链接


以前我们用 51 单片机控制 TFTLCD(TFT 液晶屏) 时, 使用的是 IO 口模拟80 接口时序。 这一章我们来学习如何使用 STM32F1 的 FSMC(灵活的静态存储控制器) 接口控制 TFTLCD 显示。 TFTLCD 可以显示 16 位色真彩图片。 本章要实现的功能是: 在 TFTLCD 上显示 ASCII 字符和汉字, 同时 D1 指示灯闪烁, 提示系统正常运行。 学习本章可以参考《STM32F10x 中文参考手册》 -19 灵活的静态存储控制器(FSMC) 章节, 特别是寄存器介绍部分。 若结合视频学习效果更佳。 本章分为如下几部分内容:

[40.1 TFTLCD 和 FSMC 介绍](#40.1 TFTLCD 和 FSMC 介绍)

[40.1.1 TFTLCD 简介](#40.1.1 TFTLCD 简介)

[40.1.2 FSMC 简介](#40.1.2 FSMC 简介)

[40.2 FSMC 配置步骤](#40.2 FSMC 配置步骤)

[40.3 硬件设计](#40.3 硬件设计)

[40.4 软件设计](#40.4 软件设计)

[40.4.1 TFTLCD 的 GPIO 初始化函数](#40.4.1 TFTLCD 的 GPIO 初始化函数)

[40.4.2 TFTLCD 的 FSMC 初始化函数](#40.4.2 TFTLCD 的 FSMC 初始化函数)

[40.4.3 TFTLCD 初始化函数](#40.4.3 TFTLCD 初始化函数)

[40.4.4 TFTLCD 显示函数](#40.4.4 TFTLCD 显示函数)

[40.4.5 字符汉字图片提取软件使用](#40.4.5 字符汉字图片提取软件使用)

[40.4.6 主函数](#40.4.6 主函数)

[40.5 实验现象](#40.5 实验现象)

课后作业


40.1 TFTLCD 和 FSMC 介绍

40.1.1 TFTLCD 简介

TFT-LCD 是薄膜晶体管液晶显示器英文 thin film transistor-liquid crystal display 字头的缩写。TFT 液晶为每个像素都设有一个薄膜晶体管(TFT),每个像素都可以通过点脉冲直接控制, 因而每个节点都相对独立, 并可以连续控制, 不仅提高了显示屏的反应速度, 同时可以精确控制显示色阶, 所以 TFT 液晶的色彩更真, 因此 TFT-LCD 也被叫做真彩液晶显示器。

常用的 TFT 液晶屏接口有很多种, 8 位、 9 位、 16 位、 18 位都有, 这里的位数表示的是彩屏数据线的数量。 常用的通信模式主要有 6800 模式和 8080 模式, 对于 TFT 彩屏通常都使用 8080 并口(简称 80 并口) 模式。

如果大家接触过 LCD1602 或者 LCD12864 等, 那么就会发现 8080 模式的读写时序其实跟 LCD1602 或者 LCD12864 的读写时序是差不多的。 8080 接口有5 条基本的控制线和多条数据线, 数据线的数量主要看液晶屏使用的是几位模式, 有 8 根、 9 根、 16 根、 18 根四种类型。 它们的功能如下:

接下来我们来看一下 8080 接口模式的时序, 如下图:

从上图我们就可以很清晰的看得出液晶屏的读写时序:

①: 在 WR 跳变为低电平之后, 液晶屏开始读取总线上面的数据。 如果使用IO 口模拟写入的时候, 可以先在总线上面写入数据, 然后在跳变 WR, 以保证当读取的时候, 总线上面的数据是稳定的。

②: 在 RD 跳变为低电平之后, 液晶屏放置数据到总线上面。

像以前我们使用 51 单片机就是通过单片机的 IO 口模拟 8080 时序进行 TFT彩屏控制, 但是对于我们 STM32F767 就不需要这样模拟了, 我们 STM32F767 自带了 FMC 接口, 这个后面我们会介绍到。

下面我们来介绍下 TFTLCD 模块, 我们公司推出的 TFTLCD 模块有很多种, 按照屏幕大小的不同可分为 2.0、 2.4、 2.8、 3.0、 3.2、 3.5、 3.6、 4.3、 4.5、 7寸等, 不同尺寸的彩屏对应的分辨率可能不同, 比如说 3.5 寸的彩屏分辨率为320*480(宽*高) , 4.5 寸的为 480*854, 当然这个具体要看对应彩屏的数据手册,彩屏数据手册在光盘的"\2--开发板原理图\彩屏原理图" 。 按照 TFT 彩屏驱动芯片的不同可分为海信 HX83xx、 ILI93xx、 R615xx、 LG45xx、 NT355xx 等等,你手上的彩屏驱动芯片具体是哪一种, 需要看下彩屏正面左上角或背面型号, 通常我们都会将彩屏的驱动芯片型号放在 TFTLCD 模块的左上角或背面。 我们的TFTLCD 模块都自带触摸功能, 可用来做输入控制。

本章我们就以 3.5 寸的 TFTLCD 模块为例来介绍(其他尺寸的彩屏和驱动芯片使用方法类似) , 该模块驱动芯片型号是 HX8357D, 分辨率为 320*480, 接口为 16 位的 80 并口, 自带触摸功能。 该模块的外观图如下图所示:

该模块原理图如下图所示:

TFTLCD 模块采用 2*17 的 2.54 公排针与外部连接, 从上图可以看出, 此TFTLCD 模块采用 16 位的并口方式与外部连接, 之所以不采用 8 位的方式, 是因为彩屏的数据量比较大, 尤其在显示图片的时候, 如果用 8 位数据线, 就会比 16 位方式慢一倍以上, 我们当然希望速度越快越好, 所以选择 16 位的接口,当然有的 TFTLCD 本身就是 8 位数据线。 图中还列出了触摸屏芯片的接口, 关于触摸屏本章我们不多介绍, 在后面的章节会有详细的介绍。 该模块的 80 并口有如下一些信号线:

CS: TFTLCD 片选信号。

WR: 向 TFTLCD 写入数据控制。

RD: 从 TFTLCD 读取数据控制。

RS: 命令/数据选择( 0, 读写命令; 1, 读写数据) 。

DB15: 0: 16 位双向数据线。

RST: TFTLCD 复位。

80 并口的通信时序前面已经介绍, 这里需要说明的是, TFTLCD 模块的 RST 信号线是直接接到 STM32F1 的复位脚上, 并不由软件控制, 这样可以节省一个 IO口。 所以要控制 TFTLCD 模块显示, 总共需要 20 个 IO 口(除触摸功能管脚) 。

知道了模块的管脚功能及通信时序, 接下来我们就来介绍下如何让液晶模块显示。 通常按照以下几步即可实现 TFT 液晶显示:

(1) 设置 STM32F1 与 TFTLCD 模块相连接的 IO

要让 TFTLCD 模块显示, 首先得初始化 TFTLCD 模块与 STM32F1 相连的 IO口, 以便控制 TFTLCD。 这里我们用使用的是 STM32F1 的 FSMC, FSMC 将在后面小节向大家详细介绍。

(2) 初始化 TFTLCD 模块(写入一系列设置值)

初始化 IO 口, 接着就是对 TFTLCD 进行配置, 首先就是要复位下 LCD, 由于模块的复位引脚是接在 STM32F1 复位上的, 所以直接按下开发板复位键即可, 然后就是初始化序列, 即向 LCD 控制器写入一系列的设置值(比如 RGB 格式、 LCD显示方向、 伽马校准等) , 这部分代码一般 LCD 厂商会提供, 我们直接使用这些初始化序列即可, 无需深入研究。 关于这些设置值可以在你所使用的彩屏模块驱动芯片数据手册内查找到, 只不过这些数据手册全是英文的, 其实也不是很难,我们用到的只是几个设置值而已, 不认识的可以百度翻译下。 初始化完成之后, LCD 就可以正常使用了。

(3) 将要显示的内容写到 TFTLCD 模块内

这一步需要按照: 设置坐标→写 GRAM 指令→写 GRAM 来实现, 但是这个步骤, 只是一个点的处理, 如果我们想要显示字符或数字, 就必须要多次使用这个步骤, 从而达到显示字符或数字的目的, 一般我们会设计一个函数来封装这些过程(实现字符或数字的显示) , 之后只需调用该函数, 就可以实现字符或数字的显示了。

这一部分内容等到我们后面编写程序的时候大家就可以看到, 其实还是比较简单的。 接下来我们就来揭开 STM32F1 的 FSMC 的神秘面纱。

40.1.2 FSMC 简介

STM32F103 100 引脚以上系列芯片都带有 FSMC 接口, 我们开发板上使用的是 STM32F103ZET6, 因此也具有 FSMC 接口。

FSMC(Flexible Static Memory Controller, 灵活的静态存储控制器)是STM32 系列采用一种新型的存储器扩展技术, 能够连接同步、 异步存储器和 16位 PC 存储卡。STM32 通过 FSMC 可以与 SRAM、ROM、PSRAM、NOR Flash 和 NANDFlash等存储器的引脚直接相连。

STM32F1 的 FSMC 内部框图如下图所示: (大家也可以查看《STM32F10x 中文参考手册》 -19 灵活的静态存储控制器(FSMC) -19.2 章节内容)

我们把 FMC 结构框图分成 3 个子模块, 按照顺序依次进行简单介绍。

(1) 标号 1: 时钟输入

FSMC 的时钟来至时钟控制器 HCLK, 在前面我们讲解"存储器与寄存器" 时,我们知道, AHB 区域内包含 FSMC 模块, 所以如果要使用 FSMC, 必须使能 AHB 总线时钟。

(2) 标号 2: AHB 接口

CPU 和其它 AHB 总线主设备可通过该 AHB 从设备接口访问外部静态存储器。 FSMC 可通过一个寄存器组进行配置。 有关 NOR Flash/PSRAM 控制寄存器的详细说明, 请参《STM32F10x 中文参考手册》 -19 灵活的静态存储控制器(FSMC) -19.5 章节内容。

(3) FSMC 外部设备

STM32F1 的 FSMC 将外部设备分为 2 类: NOR/PSRAM 设备、 NAND/PC 卡设备。 他们共用地址数据总线等信号, 但具有不同的 CS 以区分不同的设备。

本章实验我们使用的是 FSMC 的 NOR/PSRAM 存储器控制器部分, 即把 TFTLCD当成 SRAM 设备使用。 为什么可以把 TFTLCD 当成 SRAM 设备用, 这个首先要了解 NOR/PSRAM 存储器控制器的接口信号, 其接口信号功能如下:

从上图中可以看出外部 SRAM 的控制一般有: 地址线(如 A0~A25) 、 双向数据线(如 D0~D15) 、 写信号( NWE) 、 读信号( NOE) 、 片选信号(NEx) ,如果 SRAM 支持字节控制, 那么还有 UB/LB 信号。 而 TFTLCD 的信号我们在上一节有介绍, 包括: RS、 DB0-DB15、 WR、 RD、 CS、 RST 等, 其中真正在操作 LCD 的时候需要用到的就只有: RS、 DB0-DB15、 WR、 RD、 CS。 这样一来它们的操作接口信号完全类似, 唯一不同就是 TFTLCD 有 RS 信号, 但是没有地址信号。

TFTLCD 通过 RS 信号来决定传送的数据是数据还是命令, 本质上可以理解为一个地址信号, 比如我们把 RS 接在 A0 上面, 那么当 FSMC 控制器写地址 0的时候, 会使得 A0 变为 0, 对 TFTLCD 来说, 就是写命令。 而 FSMC 写地址 1 的时候, A0 将会变为 1, 对 TFTLCD 来说, 就是写数据了。 这样, 就把数据和命令区分开了, 他们其实就是对应 SRAM 操作的两个连续地址。 当然 RS 也可以接在其他地址线上, 我们 STM32F1 开发板是把 RS 连接在 A10 上面的。

知道了可以将 TFTLCD 当做 SRAM 设备用, 下面我们就来看下 FSMC 的外部设备地址映射, 从 FSMC 的角度, 外部存储器被划分为 4 个固定大小的存储区域(Bank) , 每个存储区域的大小为 256 MB, 共 1GB 空间。 如下图所示:

本章实验使用到的是 Bank1, 所以我们只讲解这块存储区域, 其他的区域大家可参考《STM32F10x 中文参考手册》 -19 灵活的静态存储控制器(FSMC) 章节内容。

存储区域 1 可连接多达 4 个 NOR Flash 或 PSRAM 存储器器件。 此存储区域被划分为 4 个 NOR/PSRAM 区域, 带 4 个专用片选信号。 存储区域 2 和 3 用于连接 NAND Flash 器件(每个存储区域一个器件) 。 存储区域 4 用于连接 PC卡设备。 对于每个存储区域, 所要使用的存储器类型由用户在配置寄存器中定义。STM32F1 的 FSMC 各 Bank 配置寄存器如下图所示:

STM32F1 的 FSMC 存储块 1( Bank1)又被分为 4 个区, 每个区管理 64M 字节空间, 每个区都有独立的寄存器对所连接的存储器进行配置。 Bank1 的 256M字节空间由 28 根地址线(HADDR27:0)寻址。 这里 HADDR 是内部 AHB 地址 总线, 其 中 HADDR25:0 来自外部存储器地 址 FSMC_A25:0, 而 HADDR26:27对 4 个区进行寻址。 如下图所示:

本章实验我们使用的是 Bank1 的第 4 区, 即起始地址为 0X6C000000。 这里要特别注意 HADDR25:0, HADDR25:0包含外部存储器地址。 由于 HADDR 为字节地址, 而存储器按字寻址, 所以根据存储器数据宽度不同, 实际向存储器发送的地址也将有所不同, 如下图所示:

如果外部存储器的宽度为 8 位, FSMC 将使用内部的 HADDR25:0 地址来作为对外部存储器的寻址地址 FSMC_A25:0

这里请大家特别留意, 如果外部存储器的宽度为 16 位, FSMC 将使用内部的 HADDR25:1 地址来作为对外部存储器的寻址地址 FSMC_A24:0, 相当于右移了一位, 在后面我们设置 A10 地址的时候就要使用到。 无论外部存储器的宽度为 16 位还是 8 位, FSMC_A0 都应连接到外部存储器地址 A0

另外, HADDR27:26的设置, 是不需要我们干预的, 比如: 当你选择使用Bank1 的第三个区, 即使用 FSMC_NE3 来连接外部设备的时候, 即对应了HADDR27:26=10, 我们要做的就是配置对应第 3 区的寄存器组, 来适应外部设备即可。 FSMC 的各 Bank 配置寄存器在上图已列出。

对于 NOR FLASH 控制器, 主要是通过 FSMC_BCRx、 FSMC_BTRx 和FSMC_BWTRx 寄存器设置(其中 x=1~4, 对应 4 个区) 。 通过这 3 个寄存器,可以设置 FSMC 访问外部存储器的时序参数, 拓宽了可选用的外部存储器的速度范围。

FSMC 的 NOR FLASH 控制器支持同步和异步突发两种访问方式。 选用同步突发访问方式时, FSMC 将 HCLK(系统时钟)分频后, 发送给外部存储器作为同步时钟信号 FSMC_CLK。 此时需要的设置的时间参数有 2 个:

①HCLK 与 FSMC_CLK 的分频系数(CLKDIV), 可以为 2~16 分频;

②同步突发访问中获得第 1 个数据所需要的等待延迟(DATLAT)。

对于异步突发访问方式, FSMC 主要设置 3 个时间参数: 地址建立时间(ADDSET)、 数据建立时间(DATAST)和地址保持时间(ADDHLD)。 FSMC 综合了 SRAM/ ROM、 PSRAM 和 NORFlash 产品的信号特点, 定义了 4 种不同的异步时序模型。 选用不同的时序模型时, 需要设置不同的时序参数, 如下图所示:

在实际扩展时, 根据选用存储器的特征确定时序模型, 从而确定各时间参数与存储器读/ 写周期参数指标之间的计算关系; 利用该计算关系和存储芯片数据手册中给定的参数指标, 可计算出 FSMC 所需要的各时间参数, 从而对时间参数寄存器进行合理的配置。

本章实验我们使用异步模式 A(ModeA) 方式来控制 TFTLCD, 模式 A 的读操作时序如下图所示:

模式 A 支持独立的读写时序控制, 这个对我们驱动 TFTLCD 来说非常有用,因为 TFTLCD 在读的时候, 一般比较慢, 而在写的时候可以比较快, 如果读写用一样的时序, 那么只能以读的时序为基准, 从而导致写的速度变慢, 或者在读数据的时候, 重新配置 FSMC 的延时, 在读操作完成的时候, 再配置回写的时序,这样虽然也不会降低写的速度, 但是频繁配置, 比较麻烦。 而如果有独立的读写时序控制, 那么我们只要初始化的时候配置好, 之后就不用再配置, 既可以满足速度要求, 又不需要频繁改配置。

模式 A 的写操作时序如下图所示:

模式 A 读写时序中的 ADDSET 与 DATAST, 是通过不同的寄存器设置的。 由于篇幅限制, 本章并没有对 FSMC 相关寄存器进行介绍, 大家可以参考《STM32F7xx中文参考手册》-13 可变存储控制器 (FMC)章节寄存器内容, 里面有详细的讲解。不过, 这里还要给大家做下科普, 在 KEIL 的寄存器定义里面, 并没有定义FMC_BCRx、 FMC_BTRx、 FMC_BWTRx 等这个单独的寄存器, 而是将他们进行了一些组合。

FSMC_BCRx 和 FSMC_BTRx, 组合成 BTCR8寄存器组, 他们的对应关系如下:

BTCR0对应 FSMC_BCR1, BTCR1对应 FSMC_BTR1

BTCR2对应 FSMC_BCR2, BTCR3对应 FSMC_BTR2

BTCR4对应 FSMC_BCR3, BTCR5对应 FSMC_BTR3

BTCR6对应 FSMC_BCR4, BTCR7对应 FSMC_BTR4

FSMC_BWTRx 则组合成 BWTR7, 他们的对应关系如下:

BWTR0对应 FSMC_BWTR1, BWTR2对应 FSMC_BWTR2,

BWTR4对应 FSMC_BWTR3, BWTR6对应 FSMC_BWTR4,

BWTR1、 BWTR3和 BWTR5保留, 没有用到。

FSMC 内部还是比较复杂的, 如果看不懂的可以暂时放下, 因为我们使用的是库函数开发, 只需简单配置下即可使用。

40.2 FSMC 配置步骤

接下来我们介绍下如何使用库函数对 FSMC 进行配置。 这个也是在编写程序中必须要了解的。 具体步骤如下: (FSMC 相关库函数在 stm32f10x_fsmc.c 和stm32f10x_fsmc.h 文件中)

(1) FSMC 初始化

FSMC 的初始化主要是配置 FSMC_BCRx, FSMC_BTRx, FSMC_BWTRx 这三个寄存器, 固件库内提供了 3 个初始化函数对这些寄存器配置。 FSMC 初始化库函数如下:

cpp 复制代码
FSMC_NORSRAMInit();
FSMC_NANDInit();
FSMC_PCCARDInit();

这三个函数分别用来初始化 4 种类型存储器。 这里根据名字就很好判断对应关系。 用来初始化 NOR 和 SRAM 使用同一个函数 FSMC_NORSRAMInit()。 所以我们之后使用的 FSMC 初始化函数为 FSMC_NORSRAMInit()。 该初始化函数原型是

cpp 复制代码
void FSMC_NORSRAMInit(FSMC_NORSRAMInitTypeDef* FSMC_NORSRAMInitStruct);

这个函数只有一个参数, 是一个结构体指针变量, 结构体类型是FSMC_NORSRAMInitTypeDef, 其内成员变量非常多, 因为 FSMC 相关的配置项非常多。 下面我们简单介绍下它的成员:

cpp 复制代码
/** 
  * @brief  FSMC NOR/SRAM Init structure definition
  */

typedef struct
{
  uint32_t FSMC_Bank;                /*!< Specifies the NOR/SRAM memory bank that will be used.
                                          This parameter can be a value of @ref FSMC_NORSRAM_Bank */

  uint32_t FSMC_DataAddressMux;      /*!< Specifies whether the address and data values are
                                          multiplexed on the databus or not. 
                                          This parameter can be a value of @ref FSMC_Data_Address_Bus_Multiplexing */

  uint32_t FSMC_MemoryType;          /*!< Specifies the type of external memory attached to
                                          the corresponding memory bank.
                                          This parameter can be a value of @ref FSMC_Memory_Type */

  uint32_t FSMC_MemoryDataWidth;     /*!< Specifies the external memory device width.
                                          This parameter can be a value of @ref FSMC_Data_Width */

  uint32_t FSMC_BurstAccessMode;     /*!< Enables or disables the burst access mode for Flash memory,
                                          valid only with synchronous burst Flash memories.
                                          This parameter can be a value of @ref FSMC_Burst_Access_Mode */
                                       
  uint32_t FSMC_AsynchronousWait;     /*!< Enables or disables wait signal during asynchronous transfers,
                                          valid only with asynchronous Flash memories.
                                          This parameter can be a value of @ref FSMC_AsynchronousWait */

  uint32_t FSMC_WaitSignalPolarity;  /*!< Specifies the wait signal polarity, valid only when accessing
                                          the Flash memory in burst mode.
                                          This parameter can be a value of @ref FSMC_Wait_Signal_Polarity */

  uint32_t FSMC_WrapMode;            /*!< Enables or disables the Wrapped burst access mode for Flash
                                          memory, valid only when accessing Flash memories in burst mode.
                                          This parameter can be a value of @ref FSMC_Wrap_Mode */

  uint32_t FSMC_WaitSignalActive;    /*!< Specifies if the wait signal is asserted by the memory one
                                          clock cycle before the wait state or during the wait state,
                                          valid only when accessing memories in burst mode. 
                                          This parameter can be a value of @ref FSMC_Wait_Timing */

  uint32_t FSMC_WriteOperation;      /*!< Enables or disables the write operation in the selected bank by the FSMC. 
                                          This parameter can be a value of @ref FSMC_Write_Operation */

  uint32_t FSMC_WaitSignal;          /*!< Enables or disables the wait-state insertion via wait
                                          signal, valid for Flash memory access in burst mode. 
                                          This parameter can be a value of @ref FSMC_Wait_Signal */

  uint32_t FSMC_ExtendedMode;        /*!< Enables or disables the extended mode.
                                          This parameter can be a value of @ref FSMC_Extended_Mode */

  uint32_t FSMC_WriteBurst;          /*!< Enables or disables the write burst operation.
                                          This parameter can be a value of @ref FSMC_Write_Burst */ 

  FSMC_NORSRAMTimingInitTypeDef* FSMC_ReadWriteTimingStruct; /*!< Timing Parameters for write and read access if the  ExtendedMode is not used*/  

  FSMC_NORSRAMTimingInitTypeDef* FSMC_WriteTimingStruct;     /*!< Timing Parameters for write access if the  ExtendedMode is used*/      
}FSMC_NORSRAMInitTypeDef;

从这个结构体我们可以看出, 前面有 13 个基本类型(unit32_t) 的成员变量, 这 13 个参数是用来配置片选控制寄存器 FSMC_BCRx。 最后面还有两个SMC_NORSRAMTimingInitTypeDef 指针类型的成员变量。 前面我们讲到, FSMC 有读时序和写时序之分, 所以这里就是用来设置读时序和写时序的参数了, 也就是说, 这两个参数是用来配置寄存器 FSMC_BTRx 和 FSMC_BWTRx, 后面我们会讲解到。 下面我们就来看看这些成员:

FSMC_Bank: 用来设置使用到的存储块标号和区号, 本章实验我们是使用的存储块 1 区号 4, 所以选择值为 FSMC_Bank1_NORSRAM4。

FSMC_DataAddressMux: 用于配置 FSMC 的数据线与地址线是否复用。 FSMC支持数据与地址线复用或非复用两种模式。 在非复用模式下 16 位数据线及 26位地址线分开始用; 复用模式则低 16 位数据/地址线复用, 仅对 NOR 和 PSRAM有效。 在复用模式下, 推荐使用地址锁存器以区分数据与地址。 本实验使用 FSMC模拟 8080 时序, 仅使用一根地址线 A10 提供 8080 的 RS 信号, 所以不需要复用,即设置为 FSMC_DataAddressMux_Disable。

**FSMC_MemoryType:**用来设置FSMC外接的存储器类型, 可选类型为 NOR FLASH模式、 PSARM 模式及 SRAM 模式。 我们这里把 TFTLCD 当做 SRAM 使用, 所以选择值为 FSMC_MemoryType_SRAM。

FSMC_MemoryDataWidth: 用来设置 FSMC 接口的数据宽度, 可选择 8 位还是16 位, 这里我们是 16 位数据宽度, 所以选择值为 FSMC_MemoryDataWidth_16b。

FSMC_WriteOperation: 用于配置写操作使能, 如果禁止了写操作, FSMC 不会产生写时序, 但仍可从存储器中读出数据。 本实验需要向 TFTLCD 内写数据,所以要写使能, 配置为 FSMC_WriteOperation_Enable(写使能)。

FSMC_ExtendedMode: 用于配置是否使用扩展模式, 在扩展模式下, 读时序和写时序可以使用独立时序模式。 如读时序使用模式 A, 写时序使用模式 B, 这些 A、 B、 C、 D 模式实际上差别不大, 主要是在使用数据/地址线复用的情况下, FSMC 信号产生的时序不一样。

FSMC_BurstAccessMode: 用于配置访问模式。 FSMC 对存储器的访问分为异步模式和突发模式(同步模式)。 在异步模式下, 每次传送数据都需要产生一个确定的地址, 而突发模式可以在开始时提供一个地址之后, 把数据成组地连续写入。本实验使用异步模式 FSMC_BurstAccessMode_Disable。

FSMC_WaitSignalPolarity (配置等待信号极性)、FSMC_WrapMode (配置是否使用非对齐方式)、 FSMC_WaitSignalActive (配置等待信号什么时期产生)、FSMC_WaitSignal (配置是否使用等待信号) 、 FSMC_WriteBurst(配置是否允许突发写操作), 这些成员均需要在突发模式开启后配置才有效。 本实验使用的是异步模式, 所以这些成员的参数没有意义。

FSMC_ReadWriteTimingStructFSMC_WriteTimingStruct: 用于设置读写时序。 这两个变量都是 FSMC_NORSRAMTimingInitTypeDef 结构体指针类型。这两个参数在初始化的时候分别用来初始化片选控制寄存器 FSMC_BTRx 和写操作时序控制寄存器 FSMC_BWTRx。 FSMC_NORSRAMTimingInitTypeDef 结构体如下:

cpp 复制代码
typedef struct
{
    uint32_t FSMC_AddressSetupTime;//地址建立时间
    uint32_t FSMC_AddressHoldTime;//地址保持时间
    uint32_t FSMC_DataSetupTime;//数据建立时间
    uint32_t FSMC_BusTurnAroundDuration;//总线恢复时间
    uint32_t FSMC_CLKDivision;//时钟分频
    uint32_t FSMC_DataLatency;//数据保持时间
    uint32_t FSMC_AccessMode;//访问模式
}FSMC_NORSRAMTimingInitTypeDef;

这些成员主要用于设计地址建立保持时间, 数据建立时间等配置, 这些时间是由 HCLK 经过成员时钟分频得来的, 该分频值在成员FSMC_CLKDivision (时钟分频)中设置, 其中 FSMC_AccessMode (访问模式)成员的设置只在开启了扩展模式才有效, 而且开启了扩展模式后, 读时序和写时序的设置可以是独立的。 本实验中我们需要读写速度不一样, 所以开启了扩展模式并且对于参数FSMC_DataSetupTime 设置了不同的值。 此结构体其实就是对FSMC_BTRxFSMC_BWTRx寄存器操作, 大家可以查看中文参考手册寄存器说明。

本实验中的时序设置是根据 ILI9481 的数据手册设置的, 调试的时候可以先把这些值设置得大一些, 然后慢慢靠近数据手册要求的最小值, 这样会取得比较好的效果。 时序的参数设置对 LCD 的显示效果有一定的影响。

了解结构体成员功能后, 就可以进行配置, 本章实验配置代码如下:

cpp 复制代码
	FSMC_NORSRAMInitTypeDef  FSMC_NORSRAMInitStructure;
  	FSMC_NORSRAMTimingInitTypeDef  FSMC_ReadTimingInitStructure; 
	FSMC_NORSRAMTimingInitTypeDef  FSMC_WriteTimingInitStructure;
	
  	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC,ENABLE);	//使能FSMC时钟
	
	FSMC_ReadTimingInitStructure.FSMC_AddressSetupTime = 0x01;	 //地址建立时间(ADDSET)为2个HCLK 1/36M=27ns
  	FSMC_ReadTimingInitStructure.FSMC_AddressHoldTime = 0x00;	 //地址保持时间(ADDHLD)模式A未用到	
  	FSMC_ReadTimingInitStructure.FSMC_DataSetupTime = 0x0f;		 // 数据保存时间为16个HCLK,因为液晶驱动IC的读数据的时候,速度不能太快,尤其对1289这个IC。
  	FSMC_ReadTimingInitStructure.FSMC_BusTurnAroundDuration = 0x00;
  	FSMC_ReadTimingInitStructure.FSMC_CLKDivision = 0x00;
  	FSMC_ReadTimingInitStructure.FSMC_DataLatency = 0x00;
  	FSMC_ReadTimingInitStructure.FSMC_AccessMode = FSMC_AccessMode_A;	 //模式A 

	FSMC_WriteTimingInitStructure.FSMC_AddressSetupTime = 0x15;	 //地址建立时间(ADDSET)为16个HCLK  
  	FSMC_WriteTimingInitStructure.FSMC_AddressHoldTime = 0x15;	 //地址保持时间		
  	FSMC_WriteTimingInitStructure.FSMC_DataSetupTime = 0x05;		 //数据保存时间为6个HCLK	
  	FSMC_WriteTimingInitStructure.FSMC_BusTurnAroundDuration = 0x00;
  	FSMC_WriteTimingInitStructure.FSMC_CLKDivision = 0x00;
  	FSMC_WriteTimingInitStructure.FSMC_DataLatency = 0x00;
  	FSMC_WriteTimingInitStructure.FSMC_AccessMode = FSMC_AccessMode_A;	 //模式A  

 
  	FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM4;//  这里我们使用NE4 ,也就对应BTCR[6],[7]。
  	FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable; // 不复用数据地址
  	FSMC_NORSRAMInitStructure.FSMC_MemoryType =FSMC_MemoryType_SRAM;// FSMC_MemoryType_SRAM;  //SRAM   
  	FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;//存储器数据宽度为16bit   
  	FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode =FSMC_BurstAccessMode_Disable;// FSMC_BurstAccessMode_Disable; 
  	FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;
	FSMC_NORSRAMInitStructure.FSMC_AsynchronousWait=FSMC_AsynchronousWait_Disable; 
  	FSMC_NORSRAMInitStructure.FSMC_WrapMode = FSMC_WrapMode_Disable;   
  	FSMC_NORSRAMInitStructure.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState;  
  	FSMC_NORSRAMInitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable;	//  存储器写使能
  	FSMC_NORSRAMInitStructure.FSMC_WaitSignal = FSMC_WaitSignal_Disable;   
  	FSMC_NORSRAMInitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Enable; // 读写使用不同的时序
  	FSMC_NORSRAMInitStructure.FSMC_WriteBurst = FSMC_WriteBurst_Disable; 
  	FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &FSMC_ReadTimingInitStructure; //读写时序
  	FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &FSMC_WriteTimingInitStructure;  //写时序

  	FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure);  //初始化FSMC配置

(3) 使能(开启) FSMC

固件库提供了不同的库函数来初始化各种存储器, 同样也提供了不同类型的存储器使能函数, 如下:

cpp 复制代码
void FSMC_NORSRAMCmd(uint32_t FSMC_Bank, FunctionalState NewState);
void FSMC_NANDCmd(uint32_t FSMC_Bank, FunctionalState NewState);
void FSMC_PCCARDCmd(FunctionalState NewState);

这 3 个函数支持不同种类的存储器, 从函数名来看也非常好理解。 我们把TFTLCD 当作 SRAM 使用, 即使用第一个函数。 该函数第一个参数用来选择存储器的区域, 第二个参数用来使能或者失能。

将以上几步全部配置好后, 我们就可以使用 STM32F1 的 FSMC 了。

40.3 硬件设计

本实验使用到硬件资源如下:

(1) DS0 指示灯

(2) 串口 1

(3) FSMC

(4) TFTLCD 模块

DS0 指示灯、 串口 1 电路在前面章节都介绍过, 这里就不多说, 至于 FSMC它属于 STM32F1 芯片内部的资源, 只要通过软件配置好即可使用。 下面我们看看STM32F1 与 TFTLCD 接口的连接关系如图所示:

从电路图中可以看到, TFTLCD 接口 LCD1 连接在 STM32F1 的 FSMC 功能引脚上, TFTLCD 的数据口对应 FSMC_D0-FSMC_D15, TFTLCD 的 CS 对应 PG12 即 FMC_NE4, TFTLCD 的 RS 对应 PG0 即 FMC_A10, TFTLCD 的 WR 对应 PD5 即 FMC_NWE, TFTLCD的 RD 对应 PD4 即 FMC_NOE, TFTLCD 的 LCD_BL 背光控制引脚对应 PB0(目前彩屏没有引出背光控制脚, 预留) 。 TFTLCD 接口上的 T_SCK、 T_MOSI 等引脚是用于控制触摸的, 这些在后面触摸实验章节会介绍到。

因此我们只需要将 TFTLCD 模块插入开发板上 TFTLCD 接口即可, 开发板上TFTLCD 接口如下图所示:

DS0 指示灯用来提示系统运行状态, TFTLCD 的 ID 可以通过串口 1 打印输出。

40.4 软件设计

本章所要实现的功能是: 在 TFTLCD 上显示 ASCII 字符、 汉字和图片, 同时DS0 指示灯闪烁, 提示系统正常运行。 本实验我们使用的是 FSMC 的 Bank1 的第 4 区来控制 TFTLCD, 程序框架如下:

(1) 初始化 TFTLCD 对应的 GPIO, 初始化 FSMC

(2) TFTLCD 初始化, 包括初始化序列

(3) 编写 TFTLCD 的显示函数

(4) 编写主函数

前面我们介绍了常用的 TFTLCD 初始化步骤, FSMC 配置步骤前面也已介绍,下面我们打开"\4--实验程序\1--基础实验\32-FSMC-TFTLCD 显示实验" 工程,在 APP 工程组中可以看到添加了 tftlcd.c 文件(里面包含了多种 TFTLCD 驱动程序) , 在 StdPeriph_Driver 工程组中添加了 stm32f10x_fsmc.c 库文件。 FSMC操作的库函数都放在 stm32f10x_fsmc.c 和 stm32f10x_fsmc.h 文件中, 所以使用到 FSMC 就必须加入 stm32f10x_fsmc.c 文件, 同时还要包含对应的头文件路径。

这里我们分析几个重要函数, 其他部分程序大家可以打开工程查看。

40.4.1 TFTLCD 的 GPIO 初始化函数

按照前面介绍的 TFTLCD 初始化流程, 我们首先要初始化 TFTLCD 对应连接的IO, 其对应的 IO 前面硬件电路已介绍, 代码如下:

cpp 复制代码
void TFTLCD_GPIO_Init(void)
{
	GPIO_InitTypeDef  GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOE|RCC_APB2Periph_GPIOG,ENABLE);//使能PORTD,E,G时钟
	
 	//PORTD复用推挽输出  
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_14|GPIO_Pin_15;				 //	//PORTD复用推挽输出  
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 		 //复用推挽输出   
 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 	GPIO_Init(GPIOD, &GPIO_InitStructure); 
  	 
	//PORTE复用推挽输出  
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;				 //	//PORTD复用推挽输出  
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 		 //复用推挽输出   
 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 	GPIO_Init(GPIOE, &GPIO_InitStructure);    	    	 											 

   	//	//PORTG12复用推挽输出 A10	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_12;	 //	//PORTG复用推挽输出  
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 		 //复用推挽输出   
 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 	GPIO_Init(GPIOG, &GPIO_InitStructure);  
	
	//背光控制管脚初始化
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;				 //PB0 推挽输出 背光
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 	GPIO_Init(GPIOB, &GPIO_InitStructure);
	LCD_LED=1;				//点亮背光
	
}

该函数非常简单, 将对应的 IO 复用映射为 FSMC 功能, 并配置 GPIO 为复用功能模式, 最后调用 GPIO_Init 初始化。

40.4.2 TFTLCD 的 FSMC 初始化函数

本实验使用的是 FSMC 模拟 8080 时序, 所以要对它配置, FSMC 初始化步骤前面已介绍, 代码如下:

cpp 复制代码
void TFTLCD_FSMC_Init(void)
{
	FSMC_NORSRAMInitTypeDef  FSMC_NORSRAMInitStructure;
  	FSMC_NORSRAMTimingInitTypeDef  FSMC_ReadTimingInitStructure; 
	FSMC_NORSRAMTimingInitTypeDef  FSMC_WriteTimingInitStructure;
	
  	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC,ENABLE);	//使能FSMC时钟
	
	FSMC_ReadTimingInitStructure.FSMC_AddressSetupTime = 0x01;	 //地址建立时间(ADDSET)为2个HCLK 1/36M=27ns
  	FSMC_ReadTimingInitStructure.FSMC_AddressHoldTime = 0x00;	 //地址保持时间(ADDHLD)模式A未用到	
  	FSMC_ReadTimingInitStructure.FSMC_DataSetupTime = 0x0f;		 // 数据保存时间为16个HCLK,因为液晶驱动IC的读数据的时候,速度不能太快,尤其对1289这个IC。
  	FSMC_ReadTimingInitStructure.FSMC_BusTurnAroundDuration = 0x00;
  	FSMC_ReadTimingInitStructure.FSMC_CLKDivision = 0x00;
  	FSMC_ReadTimingInitStructure.FSMC_DataLatency = 0x00;
  	FSMC_ReadTimingInitStructure.FSMC_AccessMode = FSMC_AccessMode_A;	 //模式A 

	FSMC_WriteTimingInitStructure.FSMC_AddressSetupTime = 0x15;	 //地址建立时间(ADDSET)为16个HCLK  
  	FSMC_WriteTimingInitStructure.FSMC_AddressHoldTime = 0x15;	 //地址保持时间		
  	FSMC_WriteTimingInitStructure.FSMC_DataSetupTime = 0x05;		 //数据保存时间为6个HCLK	
  	FSMC_WriteTimingInitStructure.FSMC_BusTurnAroundDuration = 0x00;
  	FSMC_WriteTimingInitStructure.FSMC_CLKDivision = 0x00;
  	FSMC_WriteTimingInitStructure.FSMC_DataLatency = 0x00;
  	FSMC_WriteTimingInitStructure.FSMC_AccessMode = FSMC_AccessMode_A;	 //模式A  

 
  	FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM4;//  这里我们使用NE4 ,也就对应BTCR[6],[7]。
  	FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable; // 不复用数据地址
  	FSMC_NORSRAMInitStructure.FSMC_MemoryType =FSMC_MemoryType_SRAM;// FSMC_MemoryType_SRAM;  //SRAM   
  	FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;//存储器数据宽度为16bit   
  	FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode =FSMC_BurstAccessMode_Disable;// FSMC_BurstAccessMode_Disable; 
  	FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;
	FSMC_NORSRAMInitStructure.FSMC_AsynchronousWait=FSMC_AsynchronousWait_Disable; 
  	FSMC_NORSRAMInitStructure.FSMC_WrapMode = FSMC_WrapMode_Disable;   
  	FSMC_NORSRAMInitStructure.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState;  
  	FSMC_NORSRAMInitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable;	//  存储器写使能
  	FSMC_NORSRAMInitStructure.FSMC_WaitSignal = FSMC_WaitSignal_Disable;   
  	FSMC_NORSRAMInitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Enable; // 读写使用不同的时序
  	FSMC_NORSRAMInitStructure.FSMC_WriteBurst = FSMC_WriteBurst_Disable; 
  	FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &FSMC_ReadTimingInitStructure; //读写时序
  	FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &FSMC_WriteTimingInitStructure;  //写时序

  	FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure);  //初始化FSMC配置

 	FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM4, ENABLE);  // 使能BANK1 
	
}

该函数的功能很简单, 按照前面讲解的 FSMC 结构体功能进行配置, 由于TFTLCD 的读写速度不一样, 所以单独对 FSMC_NORSRAMTimingInitTypeDe 结构体进行读写时序的配置。 可参考代码备注。

40.4.3 TFTLCD 初始化函数

要让 TFTLCD 显示, 还需要初始化序列, 即 TFT 彩屏厂家提供的 TFTLCD 寄存器设置值。 将这些数值写入到 TFTLCD 内对应的命令寄存器中即可, 所以还需要编写 TFTLCD 写命令和写数据等函数。 TFTLCD 初始化代码如下:

cpp 复制代码
void TFTLCD_Init(void)
{
	u16 i;

	TFTLCD_GPIO_Init();
	TFTLCD_FSMC_Init();
	
	delay_ms(50); 
	
#ifdef TFTLCD_HX8357D	
	...(省略)    
#endif 

#ifdef TFTLCD_HX8357DN	
	...(省略)    
#endif 

#ifdef TFTLCD_R61509V	
	...(省略)    
#endif

#ifdef TFTLCD_R61509VN	
	...(省略)    
#endif

#ifdef TFTLCD_R61509V3	
	...(省略)    
#endif
	
#ifdef TFTLCD_HX8352C
	...(省略)
#endif

#ifdef TFTLCD_ILI9341
    ...(省略)
#endif

#ifdef TFTLCD_ILI9327
    ...(省略)
#endif

#ifdef TFTLCD_ILI9325
    ...(省略)		
#endif

#ifdef TFTLCD_ILI9486
    ...(省略)
#endif

#ifdef TFTLCD_ST7793	
    ...(省略)
#endif

#ifdef TFTLCD_SSD1963
    ...(省略)		
#endif
	
#ifdef TFTLCD_NT35510	
    ...(省略)
#endif

#ifdef TFTLCD_ILI9481
	LCD_WriteCmd(0Xd3);	
	tftlcd_data.id=TFTLCD->LCD_DATA;	 
	tftlcd_data.id=TFTLCD->LCD_DATA;  
	tftlcd_data.id=TFTLCD->LCD_DATA;  
	tftlcd_data.id<<=8;
	tftlcd_data.id|=TFTLCD->LCD_DATA;  
#endif

#ifdef TFTLCD_R61509VE	
    ...(省略)  
#endif

#ifdef TFTLCD_ILI9488
    ...(省略)
	
#endif

#ifdef TFTLCD_ILI9806
    ...(省略)
#endif
	
 	printf(" LCD ID:%x\r\n",tftlcd_data.id); //打印LCD ID
	
#ifdef TFTLCD_HX8357D	
    ...(省略)
#endif

#ifdef TFTLCD_HX8357DN
    ...(省略)
#endif

#ifdef TFTLCD_R61509V
    ...(省略)
#endif

#ifdef TFTLCD_R61509VN
    ...(省略)
#endif

#ifdef TFTLCD_R61509V3
    ...(省略)
#endif


#ifdef TFTLCD_HX8352C
    ...(省略)
#endif

#ifdef TFTLCD_ILI9341
    ...(省略)
#endif

#ifdef TFTLCD_ILI9327
    ...(省略)
#endif


#ifdef TFTLCD_ILI9325
    ...(省略)
#endif

#ifdef TFTLCD_ILI9486
    ...(省略)
#endif

#ifdef TFTLCD_ST7793	
    ...(省略)
#endif

#ifdef TFTLCD_SSD1963
    ...(省略)
#endif

#ifdef TFTLCD_NT35510
    ...(省略)
#endif

#ifdef TFTLCD_ILI9481
	LCD_WriteCmd(0xFF);
	LCD_WriteCmd(0xFF);
	delay_ms(5);

	LCD_WriteCmd(0xFF);
	LCD_WriteCmd(0xFF);
	LCD_WriteCmd(0xFF);
	LCD_WriteCmd(0xFF);
	delay_ms(10);
	
	LCD_WriteCmd(0xB0);
	LCD_WriteData(0x00);
	
	LCD_WriteCmd(0xB3);
	LCD_WriteData(0x02);
	LCD_WriteData(0x00);
	LCD_WriteData(0x00);
	LCD_WriteData(0x00);
	
	LCD_WriteCmd(0xC0);
	LCD_WriteData(0x13);
	LCD_WriteData(0x3B);//480
	LCD_WriteData(0x00);
	LCD_WriteData(0x00);
	LCD_WriteData(0x00);
	LCD_WriteData(0x01);
	LCD_WriteData(0x00);//NW
	LCD_WriteData(0x43);
	
	LCD_WriteCmd(0xC1);
	LCD_WriteData(0x08);
	LCD_WriteData(0x1B);//CLOCK
	LCD_WriteData(0x08);
	LCD_WriteData(0x08);
	
	LCD_WriteCmd(0xC4);
	LCD_WriteData(0x11);
	LCD_WriteData(0x01);
	LCD_WriteData(0x73);
	LCD_WriteData(0x01);
	
	LCD_WriteCmd(0xC6);
	LCD_WriteData(0x00);
	
	LCD_WriteCmd(0xC8);
	LCD_WriteData(0x0F);
	LCD_WriteData(0x05);
	LCD_WriteData(0x14);
	LCD_WriteData(0x5C);
	LCD_WriteData(0x03);
	LCD_WriteData(0x07);
	LCD_WriteData(0x07);
	LCD_WriteData(0x10);
	LCD_WriteData(0x00);
	LCD_WriteData(0x23);
	
	LCD_WriteData(0x10);
	LCD_WriteData(0x07);
	LCD_WriteData(0x07);
	LCD_WriteData(0x53);
	LCD_WriteData(0x0C);
	LCD_WriteData(0x14);
	LCD_WriteData(0x05);
	LCD_WriteData(0x0F);
	LCD_WriteData(0x23);
	LCD_WriteData(0x00);
	
	LCD_WriteCmd(0x35);
	LCD_WriteData(0x00);
	
	LCD_WriteCmd(0x44);
	LCD_WriteData(0x00);
	LCD_WriteData(0x01);
	
	LCD_WriteCmd(0xD0);
	LCD_WriteData(0x07);
	LCD_WriteData(0x07);//VCI1
	LCD_WriteData(0x1D);//VRH
	LCD_WriteData(0x03);//BT
	
	LCD_WriteCmd(0xD1);
	LCD_WriteData(0x03);
	LCD_WriteData(0x5B);//VCM
	LCD_WriteData(0x10);//VDV
	
	LCD_WriteCmd(0xD2);
	LCD_WriteData(0x03);
	LCD_WriteData(0x24);
	LCD_WriteData(0x04);

	LCD_WriteCmd(0x2A);
	LCD_WriteData(0x00);
	LCD_WriteData(0x00);
	LCD_WriteData(0x01);
	LCD_WriteData(0x3F);//320
	
	LCD_WriteCmd(0x2B);
	LCD_WriteData(0x00);
	LCD_WriteData(0x00);
	LCD_WriteData(0x01);
	LCD_WriteData(0xDF);//480

	LCD_WriteCmd(0x36);
	LCD_WriteData(0x00);

	LCD_WriteCmd(0xC0);
	LCD_WriteData(0x13);

	LCD_WriteCmd(0x3A);
	LCD_WriteData(0x55);

	LCD_WriteCmd(0x11);
	delay_ms(150);
	
	LCD_WriteCmd(0x29);
	delay_ms(30);
		
	LCD_WriteCmd(0x2C);
#endif

#ifdef TFTLCD_R61509VE
    ...(省略)
#endif
	
#ifdef TFTLCD_SSD1963N
    ...(省略)
#endif
		
#ifdef TFTLCD_ILI9488
    ...(省略)
#endif

#ifdef TFTLCD_ILI9806
    ...(省略)
#endif

	LCD_Display_Dir(TFTLCD_DIR);		//0:竖屏  1:横屏  默认竖屏
	LCD_Clear(WHITE);
}

该函数中的 LCD_WriteCmd、 LCD_WriteData 和 LCD_WriteData_Color 为写命令、 写数据函数, 其实现过程很简单, 代码如下:

cpp 复制代码
//写寄存器函数
//cmd:寄存器值
void LCD_WriteCmd(u16 cmd)
{
#ifdef TFTLCD_HX8357D	
	TFTLCD->LCD_CMD=cmd;//写入要写的寄存器序号
#endif
	
#ifdef TFTLCD_HX8357DN	
	TFTLCD->LCD_CMD=cmd;
#endif

#ifdef TFTLCD_R61509V	
	TFTLCD->LCD_CMD=cmd;//写入要写的寄存器序号
#endif
	
#ifdef TFTLCD_R61509VN	
	TFTLCD->LCD_CMD=(cmd>>8)<<1;
	TFTLCD->LCD_CMD=(cmd&0xff)<<1;
#endif

#ifdef TFTLCD_R61509V3	
	TFTLCD->LCD_CMD=cmd;//写入要写的寄存器序号
#endif
	
#ifdef TFTLCD_HX8352C
	TFTLCD->LCD_CMD=cmd<<8;
#endif

#ifdef TFTLCD_ILI9341
	TFTLCD->LCD_CMD=cmd<<8;
#endif
	
#ifdef TFTLCD_ILI9327
	TFTLCD->LCD_CMD=cmd;
#endif

#ifdef TFTLCD_ILI9325
	TFTLCD->LCD_CMD=cmd;
#endif

#ifdef TFTLCD_ILI9486
	TFTLCD->LCD_CMD=cmd;
#endif

#ifdef TFTLCD_ST7793		
	TFTLCD->LCD_CMD=cmd>>8;
	TFTLCD->LCD_CMD=cmd&0xff;
#endif

#ifdef TFTLCD_SSD1963		
	TFTLCD->LCD_CMD=cmd;
#endif
	
#ifdef TFTLCD_NT35510		
	TFTLCD->LCD_CMD=cmd;	
#endif

#ifdef TFTLCD_ILI9481
	TFTLCD->LCD_CMD=cmd;
#endif

#ifdef TFTLCD_R61509VE
	TFTLCD->LCD_CMD=cmd;
#endif

#ifdef TFTLCD_SSD1963N		
	TFTLCD->LCD_CMD=cmd;
#endif

#ifdef TFTLCD_ILI9488
	TFTLCD->LCD_CMD=cmd;
#endif

#ifdef TFTLCD_ILI9806
	TFTLCD->LCD_CMD=cmd;
#endif
	
}

//写数据
//data:要写入的值
void LCD_WriteData(u16 data)
{
#ifdef TFTLCD_HX8357D	
	TFTLCD->LCD_DATA=data;//写入要写的寄存器序号
#endif

#ifdef TFTLCD_HX8357DN	
	TFTLCD->LCD_DATA=data;
#endif
	
#ifdef TFTLCD_R61509V	
	TFTLCD->LCD_DATA=data;//写入要写的寄存器序号
#endif
	
#ifdef TFTLCD_R61509VN	
	TFTLCD->LCD_DATA=(data>>8)<<1;
	TFTLCD->LCD_DATA=(data&0xff)<<1;	
#endif

#ifdef TFTLCD_R61509V3	
	TFTLCD->LCD_DATA=data;//写入要写的寄存器序号
#endif
	
#ifdef TFTLCD_HX8352C
	TFTLCD->LCD_DATA=data<<8;
#endif

#ifdef TFTLCD_ILI9341
	TFTLCD->LCD_DATA=data<<8;
#endif

#ifdef TFTLCD_ILI9327
	TFTLCD->LCD_DATA=data;
#endif

#ifdef TFTLCD_ILI9325
	TFTLCD->LCD_DATA=data;
#endif
	
#ifdef TFTLCD_ILI9486
	TFTLCD->LCD_DATA=data;
#endif
	
#ifdef TFTLCD_ST7793	
	TFTLCD->LCD_DATA=data>>8;	
	TFTLCD->LCD_DATA=data&0xff;	
#endif

#ifdef TFTLCD_SSD1963
	TFTLCD->LCD_DATA=data;
#endif
	
#ifdef TFTLCD_NT35510		
	TFTLCD->LCD_DATA=data;	
#endif

#ifdef TFTLCD_ILI9481
	TFTLCD->LCD_DATA=data;
#endif

#ifdef TFTLCD_R61509VE
	TFTLCD->LCD_DATA=data;
#endif

#ifdef TFTLCD_SSD1963N
	TFTLCD->LCD_DATA=data;
#endif

#ifdef TFTLCD_ILI9488
	TFTLCD->LCD_DATA=data;
#endif

#ifdef TFTLCD_ILI9806
	TFTLCD->LCD_DATA=data;
#endif
}

void LCD_WriteData_Color(u16 color)
{
#ifdef TFTLCD_HX8357D
	TFTLCD->LCD_DATA=color;
#endif

#ifdef TFTLCD_HX8357DN
	TFTLCD->LCD_DATA=color>>8;
	TFTLCD->LCD_DATA=color&0xff;
#endif

#ifdef TFTLCD_R61509V
	TFTLCD->LCD_DATA=color;
#endif
	
#ifdef TFTLCD_R61509VN
	u32 recolor=0;
	recolor=LCD_RGBColor_Change(color);
	TFTLCD->LCD_DATA=(recolor>>9);
	TFTLCD->LCD_DATA=recolor;
#endif
	
#ifdef TFTLCD_R61509V3
	TFTLCD->LCD_DATA=color;
#endif

#ifdef TFTLCD_HX8352C
	TFTLCD->LCD_DATA=color&0xff00;
	TFTLCD->LCD_DATA=color<<8;
#endif	

#ifdef TFTLCD_ILI9341
	TFTLCD->LCD_DATA=color&0xff00;
	TFTLCD->LCD_DATA=color<<8;
#endif
	
#ifdef TFTLCD_ILI9327
	TFTLCD->LCD_DATA=color>>8;
	TFTLCD->LCD_DATA=color&0xff;
#endif

#ifdef TFTLCD_ILI9325
	TFTLCD->LCD_DATA=color;
#endif

#ifdef TFTLCD_ILI9486
	TFTLCD->LCD_DATA=color;
#endif

#ifdef TFTLCD_ST7793
	TFTLCD->LCD_DATA=color>>8;
	TFTLCD->LCD_DATA=color&0xff;
#endif

#ifdef TFTLCD_SSD1963
	TFTLCD->LCD_DATA=color;
#endif
	
#ifdef TFTLCD_NT35510
	TFTLCD->LCD_DATA=color;	
#endif

#ifdef TFTLCD_ILI9481
	TFTLCD->LCD_DATA=color;
#endif

#ifdef TFTLCD_R61509VE
	TFTLCD->LCD_DATA=color;
#endif

#ifdef TFTLCD_SSD1963N
	TFTLCD->LCD_DATA=color;
#endif

#ifdef TFTLCD_ILI9488
	TFTLCD->LCD_DATA=color>>8;
	TFTLCD->LCD_DATA=color&0xff;
#endif

#ifdef TFTLCD_ILI9806
	TFTLCD->LCD_DATA=color;
#endif
}

函数中的 TFTLCD 是一个结构体类型指针宏定义, 因为我们使用的 FSMC 是Bank1 的第 4 区, 并且把 FSMC_A10 作为数据命令选择。 具体的定义在 tftlcd.h文件开始处, 代码如下:

cpp 复制代码
//TFTLCD地址结构体
typedef struct
{
	u16 LCD_CMD;
	u16 LCD_DATA;
}TFTLCD_TypeDef;


//使用NOR/SRAM的 Bank1.sector4,地址位HADDR[27,26]=11 A10作为数据命令区分线 
//注意设置16位总线时STM32内部会右移一位对齐!			    
#define TFTLCD_BASE        ((u32)(0x6C000000 | 0x000007FE))
#define TFTLCD             ((TFTLCD_TypeDef *) TFTLCD_BASE)

其中 TFTLCD_BASE, 必须根据我们外部电路的连接来确定, 我们使用Bank1.sector4 就是从地址 0X6C000000 开始, 而 0X000007FE, 则是 A10 的偏移量, 很多朋友不理解这个偏移量的概念, 我们简单说明下: 以 A10 为例, 7FE转换成二进制就是: 111 1111 1110, 而 16 位数据时, 地址右移一位对齐(介绍框图时已介绍) , 那么实际对应到地址引脚的时候, 就是: A10:A0=011 1111 1111, 此时 A10 是 0, 但是如果 16 位地址再加 1(注意: 对应到 8 位地址是加 2, 即 7FE+0X02) , 那么: A10:A0=100 0000 0000, 此时 A10 就是 1 了,即实现了对 RS 的 0 和 1 的控制。

我们将这个地址强制转换为 TFTLCD_TypeDef 结构体地址, 那么可以得到TFTLCD->LCD_CMD 的地址就是 0X6C00,07FE, 对应 A10 的状态为 0(即 RS=0),而 TFTLCD->LCD_DATA 的地址就是 0X6C00,0800(结构体地址自增) , 对应 A10的状态为 1(即 RS=1) 。

所以, 有了这个定义, 当我们要往 LCD 写命令/数据的时候, 可以这样写:

cpp 复制代码
TFTLCD->LCD_CMD=cmd;//写命令
TFTLCD->LCD_DATA=data;//写数据

而读的时候反过来操作就可以了, 如下所示:

cpp 复制代码
cmd=TFTLCD->LCD_CMD;
data=TFTLCD->LCD_DATA;

这其中, CS、 WR、 RD 和 IO 口方向都是由 FSMC 控制, 不需要我们手动设置。 下面我们再来介绍下 tftlcd.h 中另一个重要的结构体:

cpp 复制代码
//LCD 重要参数集
typedef struct
{
    u16 width; //LCD 宽度
    u16 height; //LCD 高度
    u16 id; //LCD ID
    u8 dir; //横屏还是竖屏控制: 0, 竖屏; 1, 横屏。
}_tftlcd_data;
extern _tftlcd_data tftlcd_data; //管理 TFTLCD 重要参数

该结构体用于保存一些 TFTLCD 重要参数信息, 比如 LCD 的尺寸、 LCD ID (驱动 IC 型号) 、 LCD 显示方向, 当然你也可以在我们这个结构体内添加其他LCD 相关参数变量。 通过对此结构体的管理可以让我们的驱动函数支持不同尺寸的 LCD, 同时可以实现 LCD 横竖屏切换等重要功能。 所以大家今后要多学习使用结构体管理不同属性。

初始化函数内我们还使用了条件编译语句来兼容多种彩屏驱动程序。 比如:

cpp 复制代码
#ifdef TFTLCD_HX8357D
    ....对应 HX8357D 驱动的操作代码
#endif
#ifdef TFTLCD_HX8352C
    ....对应 HX8352C 驱动的操作代码
#endif
#ifdef TFTLCD_NT5510
    ....对应 NT5510 驱动的操作代码
#endif
#ifdef TFTLCD_ILI9481
    ....对应 ILI9481 驱动的操作代码
#endif

本章所介绍的 TFTLCD 驱动 IC 是 ILI9481, 所以要对这部分代码操作, 只需在 tftlcd.h 文件开始处开启这个宏定义, 其他彩屏的宏定义就关闭它。 如果您手上使用的是其他驱动类型屏, 对应型号打开即可, 如下: (后面如果又增加了屏, 使用同样方法来兼容)

cpp 复制代码
//定义LCD彩屏的驱动类型  可根据自己手上的彩屏背面型号来选择打开哪种驱动
//#define TFTLCD_HX8357D 

//#define TFTLCD_HX8352C

//#define TFTLCD_ILI9341

//#define TFTLCD_ILI9327

//#define TFTLCD_ILI9486

//#define TFTLCD_R61509V

//#define TFTLCD_R61509VN

//#define TFTLCD_R61509V3

//#define TFTLCD_ST7793

//#define TFTLCD_NT35510

//#define TFTLCD_HX8357DN

//#define TFTLCD_ILI9325

//#define TFTLCD_SSD1963

#define TFTLCD_ILI9481

在初始化函数最后部分还调用了 LCD_Display_Dir 和 LCD_Clear 函数,LCD_Display_Dir 函数用于切换 TFTLCD 显示方向(横屏和竖屏) , 默认我们设置为竖屏, 代码如下:

cpp 复制代码
//设置LCD显示方向
//dir:0,竖屏;1,横屏
void LCD_Display_Dir(u8 dir)
{
	tftlcd_data.dir=dir;         //横屏/竖屏
	if(dir==0)  //默认竖屏方向
	{		
#ifdef TFTLCD_HX8357D		
		...(省略)
#endif

#ifdef TFTLCD_HX8357DN		
		...(省略)
#endif

#ifdef TFTLCD_R61509V		
	...(省略)
#endif

#ifdef TFTLCD_R61509VN		
		...(省略)
#endif
		
#ifdef TFTLCD_R61509V3		
		...(省略)
#endif

#ifdef TFTLCD_HX8352C
		...(省略)		
#endif

#ifdef TFTLCD_ILI9341
		...(省略)		
#endif

#ifdef TFTLCD_ILI9327
	    ...(省略)		
#endif

#ifdef TFTLCD_ILI9325
		...(省略)		
#endif

#ifdef TFTLCD_ILI9486
		...(省略)		
#endif

#ifdef TFTLCD_ST7793
		...(省略)	
#endif

#ifdef TFTLCD_SSD1963
		...(省略)		
#endif
		
#ifdef TFTLCD_NT35510	
		...(省略)
#endif	

#ifdef TFTLCD_ILI9481
		LCD_WriteCmd(0x36);   //设置彩屏显示方向的寄存器
		LCD_WriteData(0x00);  
		tftlcd_data.height=480;
		tftlcd_data.width=320;			
#endif

#ifdef TFTLCD_R61509VE		
		...(省略)
#endif

#ifdef TFTLCD_SSD1963N
		...(省略)			
#endif

#ifdef TFTLCD_ILI9488		
		...(省略)
#endif

#ifdef TFTLCD_ILI9806		
		...(省略)
#endif
	}
	else
	{	
#ifdef TFTLCD_HX8357D
		...(省略)
#endif
		
#ifdef TFTLCD_HX8357DN
		...(省略)
#endif

#ifdef TFTLCD_R61509V		
		...(省略)
#endif

#ifdef TFTLCD_R61509VN		
		...(省略)
#endif

#ifdef TFTLCD_R61509V3		
		...(省略)
#endif
		
#ifdef TFTLCD_HX8352C
		...(省略)			
#endif

#ifdef TFTLCD_ILI9341
		...(省略)	
#endif

#ifdef TFTLCD_ILI9327
		...(省略)	
#endif

#ifdef TFTLCD_ILI9325
		...(省略)		
#endif

#ifdef TFTLCD_ILI9486
		...(省略)		
#endif

#ifdef TFTLCD_ST7793
		...(省略)		
#endif

#ifdef TFTLCD_SSD1963
		...(省略)		
#endif

#ifdef TFTLCD_NT35510	
		...(省略)
#endif

#ifdef TFTLCD_ILI9481
		LCD_WriteCmd(0x36);   //设置彩屏显示方向的寄存器
		LCD_WriteData(0x60);  
		tftlcd_data.height=320;
		tftlcd_data.width=480;			
#endif

#ifdef TFTLCD_R61509VE		
		...(省略)
#endif

#ifdef TFTLCD_SSD1963N
		...(省略)		
#endif

#ifdef TFTLCD_ILI9488		
		...(省略)
#endif

#ifdef TFTLCD_ILI9806		
		...(省略)
#endif
	}	
}

横屏和竖屏的控制只需要对 TFTLCD 显示方向寄存器进行操作(寄存器可通过彩屏数据手册查找) , 然后再把他们的 X 和 Y 轴像素调换即可。

为了方便大家设置横竖屏, 我们直接在初始化函数内调用LCD_Display_Dir()函数时传递的是一个宏定义 TFTLCD_DIR, 而该宏定义在tftlcd.h 头文件开始处也定义了, 其实就是数字 0 或者 1, 如果要横屏显示的话该宏值为 1, 如果要竖屏显示就为 0。 如下所示:

cpp 复制代码
#define TFTLCD_DIR 0 //0: 竖屏 1: 横屏 默认竖屏

LCD_Clear 函数用于清屏, 函数入口参数用于选择清屏的颜色, 常用的颜色都在 tftlcd.h 文件内进行了宏定义, 直接调用宏即可。 清屏函数实现过程很简单, 其实就是设置 TFTLCD 显示窗口, 然后写入tftlcd_data.width*tftlcd_data.height 个颜色数据即可。 设置窗口函数代码如下:

cpp 复制代码
//设置窗口,并自动设置画点坐标到窗口左上角(sx,sy).
//sx,sy:窗口起始坐标(左上角)
//width,height:窗口宽度和高度,必须大于0!!
//窗体大小:width*height. 
void LCD_Set_Window(u16 sx,u16 sy,u16 width,u16 height)
{    
#ifdef TFTLCD_HX8357D	
	...(省略)
#endif

#ifdef TFTLCD_HX8357DN	
	...(省略)
#endif

#ifdef TFTLCD_R61509V	
	...(省略)
#endif

#ifdef TFTLCD_R61509VN	
	...(省略)
#endif	

#ifdef TFTLCD_R61509V3	
	...(省略)
#endif
	
#ifdef TFTLCD_HX8352C
	...(省略)
#endif

#ifdef TFTLCD_ILI9341
	...(省略)

#endif

#ifdef TFTLCD_ILI9327
	...(省略)

#endif

#ifdef TFTLCD_ILI9325
	...(省略)

#ifdef TFTLCD_ILI9486
	...(省略)

#endif

#ifdef TFTLCD_ST7793	
	...(省略)		
#endif

#ifdef TFTLCD_SSD1963
	...(省略)
#endif

#ifdef TFTLCD_NT35510	
	...(省略)
#endif

#ifdef TFTLCD_ILI9481
	LCD_WriteCmd(0x2A);
	LCD_WriteData(sx/256);   
	LCD_WriteData(sx%256); 	 
	LCD_WriteData(width/256); 
	LCD_WriteData(width%256);
	
	LCD_WriteCmd(0x2B);
	LCD_WriteData(sy/256);  
	LCD_WriteData(sy%256);
	LCD_WriteData(height/256); 
	LCD_WriteData(height%256); 	

	LCD_WriteCmd(0x2C);
#endif

#ifdef TFTLCD_R61509VE	
	...(省略)
#endif

#ifdef TFTLCD_SSD1963N
	...(省略)
#endif

#ifdef TFTLCD_ILI9488
	...(省略)

#endif

#ifdef TFTLCD_ILI9806
	...(省略)

#endif
}

其实就是在 TFTLCD 的 X 方向和 Y 方向寄存器内写入窗口值。 我们讲解的ILI9481 的 X 方向和 Y 方向命令(寄存器) 是 0x2A 和 0x2B, 写完窗口值还需要

将这些值写入到 TFTLCD 的 GRAM 内, 命令是 0X2C。 这些命令也是通过彩屏数据手册查找。

在初始化函数内使用了 printf 函数打印 TFTLCD 的 ID, 所以在主函数内需要对串口初始化, 否则将导致程序死在 printf 里面, 如果不想用 printf, 那么请注释掉它。

40.4.4 TFTLCD 显示函数

上述几个函数完成后, 我们就可以使用 TFTLCD 了, 要在 TFTLCD 上显示内容,我们需要编写对应的显示函数, 比如要显示一个点, 那么就编写一个点的函数。TFTLCD 显示函数如下:

cpp 复制代码
//清屏函数
//color:要清屏的填充色
void LCD_Clear(u16 color)
{
	uint16_t i, j ;

	LCD_Set_Window(0, 0, tftlcd_data.width-1, tftlcd_data.height-1);	 //作用区域
  	for(i=0; i<tftlcd_data.width; i++)
	{
		for (j=0; j<tftlcd_data.height; j++)
		{
			LCD_WriteData_Color(color);
		}
	} 
}


//在指定区域内填充单个颜色
//(sx,sy),(ex,ey):填充矩形对角坐标,区域大小为:(ex-sx+1)*(ey-sy+1)   
//color:要填充的颜色
void LCD_Fill(u16 xState,u16 yState,u16 xEnd,u16 yEnd,u16 color)
{          
	uint16_t temp;

    if((xState > xEnd) || (yState > yEnd))
    {
        return;
    }   
	LCD_Set_Window(xState, yState, xEnd, yEnd); 
    xState = xEnd - xState + 1;
	yState = yEnd - yState + 1;

	while(xState--)
	{
	 	temp = yState;
		while (temp--)
	 	{			
			LCD_WriteData_Color(color);	
		}
	}	
} 

//在指定区域内填充指定颜色块			 
//(sx,sy),(ex,ey):填充矩形对角坐标,区域大小为:(ex-sx+1)*(ey-sy+1)   
//color:要填充的颜色
void LCD_Color_Fill(u16 sx,u16 sy,u16 ex,u16 ey,u16 *color)
{  
	u16 height,width;
	u16 i,j;
	width=ex-sx+1; 			//得到填充的宽度
	height=ey-sy+1;			//高度
	
	for(i=0;i<height;i++)
	{
		for(j=0;j<width;j++)
		{
			LCD_Set_Window(sx+j, sy+i,ex, ey);
			LCD_WriteData_Color(color[i*width+j]);
		}
	}		  
}
//画点
//x,y:坐标
//FRONT_COLOR:此点的颜色
void LCD_DrawPoint(u16 x,u16 y)
{
	LCD_Set_Window(x, y, x, y);  //设置点的位置
	LCD_WriteData_Color(FRONT_COLOR);	
}

//快速画点
//x,y:坐标
//color:颜色
void LCD_DrawFRONT_COLOR(u16 x,u16 y,u16 color)
{	   
	LCD_Set_Window(x, y, x, y);
	LCD_WriteData_Color(color);	
} 

//画线
//x1,y1:起点坐标
//x2,y2:终点坐标  
void LCD_DrawLine(u16 x1, u16 y1, u16 x2, u16 y2)
{
	u16 t; 
	int xerr=0,yerr=0,delta_x,delta_y,distance; 
	int incx,incy,uRow,uCol; 
	delta_x=x2-x1; //计算坐标增量 
	delta_y=y2-y1; 
	uRow=x1; 
	uCol=y1; 
	if(delta_x>0)incx=1; //设置单步方向 
	else if(delta_x==0)incx=0;//垂直线 
	else {incx=-1;delta_x=-delta_x;} 
	if(delta_y>0)incy=1; 
	else if(delta_y==0)incy=0;//水平线 
	else{incy=-1;delta_y=-delta_y;} 
	if( delta_x>delta_y)distance=delta_x; //选取基本增量坐标轴 
	else distance=delta_y; 
	for(t=0;t<=distance+1;t++ )//画线输出 
	{  
		LCD_DrawPoint(uRow,uCol);//画点 
		xerr+=delta_x ; 
		yerr+=delta_y ; 
		if(xerr>distance) 
		{ 
			xerr-=distance; 
			uRow+=incx; 
		} 
		if(yerr>distance) 
		{ 
			yerr-=distance; 
			uCol+=incy; 
		} 
	}  
} 

void LCD_DrawLine_Color(u16 x1, u16 y1, u16 x2, u16 y2,u16 color)
{
	u16 t; 
	int xerr=0,yerr=0,delta_x,delta_y,distance; 
	int incx,incy,uRow,uCol; 
	delta_x=x2-x1; //计算坐标增量 
	delta_y=y2-y1; 
	uRow=x1; 
	uCol=y1; 
	if(delta_x>0)incx=1; //设置单步方向 
	else if(delta_x==0)incx=0;//垂直线 
	else {incx=-1;delta_x=-delta_x;} 
	if(delta_y>0)incy=1; 
	else if(delta_y==0)incy=0;//水平线 
	else{incy=-1;delta_y=-delta_y;} 
	if( delta_x>delta_y)distance=delta_x; //选取基本增量坐标轴 
	else distance=delta_y; 
	for(t=0;t<=distance+1;t++ )//画线输出 
	{  
		LCD_DrawFRONT_COLOR(uRow,uCol,color);//画点 
		xerr+=delta_x ; 
		yerr+=delta_y ; 
		if(xerr>distance) 
		{ 
			xerr-=distance; 
			uRow+=incx; 
		} 
		if(yerr>distance) 
		{ 
			yerr-=distance; 
			uCol+=incy; 
		} 
	}  
} 


// 画一个十字的标记
// x:标记的X坐标
// y:标记的Y坐标
// color:标记的颜色
void LCD_DrowSign(uint16_t x, uint16_t y, uint16_t color)
{
    uint8_t i;

    /* 画点 */
    LCD_Set_Window(x-1, y-1, x+1, y+1);
    for(i=0; i<9; i++)
    {
		LCD_WriteData_Color(color);   
    }

    /* 画竖 */
    LCD_Set_Window(x-4, y, x+4, y);
    for(i=0; i<9; i++)
    {
		LCD_WriteData_Color(color); 
    }

    /* 画横 */
    LCD_Set_Window(x, y-4, x, y+4);
    for(i=0; i<9; i++)
    {
		LCD_WriteData_Color(color); 
    }
}

//画矩形	  
//(x1,y1),(x2,y2):矩形的对角坐标
void LCD_DrawRectangle(u16 x1, u16 y1, u16 x2, u16 y2)
{
	LCD_DrawLine(x1,y1,x2,y1);
	LCD_DrawLine(x1,y1,x1,y2);
	LCD_DrawLine(x1,y2,x2,y2);
	LCD_DrawLine(x2,y1,x2,y2);
}
//在指定位置画一个指定大小的圆
//(x,y):中心点
//r    :半径
void LCD_Draw_Circle(u16 x0,u16 y0,u8 r)
{
	int a,b;
	int di;
	a=0;b=r;	  
	di=3-(r<<1);             //判断下个点位置的标志
	while(a<=b)
	{
		LCD_DrawPoint(x0+a,y0-b);             //5
 		LCD_DrawPoint(x0+b,y0-a);             //0           
		LCD_DrawPoint(x0+b,y0+a);             //4               
		LCD_DrawPoint(x0+a,y0+b);             //6 
		LCD_DrawPoint(x0-a,y0+b);             //1       
 		LCD_DrawPoint(x0-b,y0+a);             
		LCD_DrawPoint(x0-a,y0-b);             //2             
  		LCD_DrawPoint(x0-b,y0-a);             //7     	         
		a++;
		//使用Bresenham算法画圆     
		if(di<0)di +=4*a+6;	  
		else
		{
			di+=10+4*(a-b);   
			b--;
		} 						    
	}
} 



//在指定位置显示一个字符
//x,y:起始坐标
//num:要显示的字符:" "--->"~"
//size:字体大小 12/16/24
//mode:叠加方式(1)还是非叠加方式(0)
void LCD_ShowChar(u16 x,u16 y,u8 num,u8 size,u8 mode)
{  							  
    u8 temp,t1,t;
	u16 y0=y;
	u8 csize=(size/8+((size%8)?1:0))*(size/2);		//得到字体一个字符对应点阵集所占的字节数	
 	num=num-' ';//得到偏移后的值(ASCII字库是从空格开始取模,所以-' '就是对应字符的字库)
	for(t=0;t<csize;t++)
	{   
		if(size==12)temp=ascii_1206[num][t]; 	 	//调用1206字体
		else if(size==16)temp=ascii_1608[num][t];	//调用1608字体
		else if(size==24)temp=ascii_2412[num][t];	//调用2412字体
		else return;								//没有的字库
		for(t1=0;t1<8;t1++)
		{			    
			if(temp&0x80)LCD_DrawFRONT_COLOR(x,y,FRONT_COLOR);
			else if(mode==0)LCD_DrawFRONT_COLOR(x,y,BACK_COLOR);
			temp<<=1;
			y++;
			if(y>=tftlcd_data.height)return;		//超区域了
			if((y-y0)==size)
			{
				y=y0;
				x++;
				if(x>=tftlcd_data.width)return;	//超区域了
				break;
			}
		}  	 
	}  	    	   	 	  
}   
//m^n函数
//返回值:m^n次方.
u32 LCD_Pow(u8 m,u8 n)
{
	u32 result=1;	 
	while(n--)result*=m;    
	return result;
}			 
//显示数字,高位为0,则不显示
//x,y :起点坐标	 
//len :数字的位数
//size:字体大小
//color:颜色 
//num:数值(0~4294967295);	 
void LCD_ShowNum(u16 x,u16 y,u32 num,u8 len,u8 size)
{         	
	u8 t,temp;
	u8 enshow=0;						   
	for(t=0;t<len;t++)
	{
		temp=(num/LCD_Pow(10,len-t-1))%10;
		if(enshow==0&&t<(len-1))
		{
			if(temp==0)
			{
				LCD_ShowChar(x+(size/2)*t,y,' ',size,0);
				continue;
			}else enshow=1; 
		 	 
		}
	 	LCD_ShowChar(x+(size/2)*t,y,temp+'0',size,0); 
	}
} 

//显示数字,高位为0,还是显示
//x,y:起点坐标
//num:数值(0~999999999);	 
//len:长度(即要显示的位数)
//size:字体大小
//mode:
//[7]:0,不填充;1,填充0.
//[6:1]:保留
//[0]:0,非叠加显示;1,叠加显示.
void LCD_ShowxNum(u16 x,u16 y,u32 num,u8 len,u8 size,u8 mode)
{  
	u8 t,temp;
	u8 enshow=0;						   
	for(t=0;t<len;t++)
	{
		temp=(num/LCD_Pow(10,len-t-1))%10;
		if(enshow==0&&t<(len-1))
		{
			if(temp==0)
			{
				if(mode&0X80)LCD_ShowChar(x+(size/2)*t,y,'0',size,mode&0X01);  
				else LCD_ShowChar(x+(size/2)*t,y,' ',size,mode&0X01);  
 				continue;
			}else enshow=1; 
		 	 
		}
	 	LCD_ShowChar(x+(size/2)*t,y,temp+'0',size,mode&0X01); 
	}
} 
//显示字符串
//x,y:起点坐标
//width,height:区域大小  
//size:字体大小
//*p:字符串起始地址		  
void LCD_ShowString(u16 x,u16 y,u16 width,u16 height,u8 size,u8 *p)
{         
	u8 x0=x;
	width+=x;
	height+=y;
    while((*p<='~')&&(*p>=' '))//判断是不是非法字符!
    {       
        if(x>=width){x=x0;y+=size;}
        if(y>=height)break;//退出
        LCD_ShowChar(x,y,*p,size,0);
        x+=size/2;
        p++;
    }  
}

/****************************************************************************
*函数名:LCD_ShowFontHZ
*输  入:x:汉字显示的X坐标
*      * y:汉字显示的Y坐标
*      * cn:要显示的汉字
*      * wordColor:文字的颜色
*      * backColor:背景颜色
*输  出:
*功  能:写二号楷体汉字
****************************************************************************/
#if 0
void LCD_ShowFontHZ(u16 x, u16 y, u8 *cn)	 
{  
	u8 i, j, wordNum;
	u16 color;
	while (*cn != '\0')
	{
		LCD_Set_Window(x, y, x+31, y+28);
		for (wordNum=0; wordNum<20; wordNum++)
		{	//wordNum扫描字库的字数
			if ((CnChar32x29[wordNum].Index[0]==*cn)
			     &&(CnChar32x29[wordNum].Index[1]==*(cn+1)))
			{
				for(i=0; i<116; i++) 
				{	//MSK的位数
					color=CnChar32x29[wordNum].Msk[i];
					for(j=0;j<8;j++) 
					{
						if((color&0x80)==0x80)
						{
							LCD_WriteData_Color(FRONT_COLOR); 						
						} 						
						else
						{
							LCD_WriteData_Color(BACK_COLOR); 
						} 
						color<<=1;
					}//for(j=0;j<8;j++)结束
				}    
			}
		} //for (wordNum=0; wordNum<20; wordNum++)结束 	
		cn += 2;
		x += 32;
	}
}
#endif


#if 1
void LCD_ShowFontHZ(u16 x, u16 y, u8 *cn)
{
	u8 i, j, wordNum;
	u16 color;
	u16 x0=x; 
	u16 y0=y; 
	while (*cn != '\0')
	{
		for (wordNum=0; wordNum<20; wordNum++)
		{	//wordNum扫描字库的字数
			if ((CnChar32x29[wordNum].Index[0]==*cn)
			     &&(CnChar32x29[wordNum].Index[1]==*(cn+1)))
			{
				for(i=0; i<116; i++) 
				{	//MSK的位数
					color=CnChar32x29[wordNum].Msk[i];
					for(j=0;j<8;j++) 
					{
						if((color&0x80)==0x80)
						{
							LCD_DrawFRONT_COLOR(x,y,FRONT_COLOR);
						} 						
						else
						{
							LCD_DrawFRONT_COLOR(x,y,BACK_COLOR);
						} 
						color<<=1;
						x++;
						if((x-x0)==32)
						{
							x=x0;
							y++;
							if((y-y0)==29)
							{
								y=y0;
							}
						}
					}//for(j=0;j<8;j++)结束
				}	
			}
			
		} //for (wordNum=0; wordNum<20; wordNum++)结束 	
		cn += 2;
		x += 32;
		x0=x;
	}
}	
#endif

void LCD_ShowPicture(u16 x, u16 y, u16 wide, u16 high,u8 *pic)
{
	u16 i,j;
	u16 temp = 0;
	long tmp=0,num=0;
	LCD_Set_Window(x, y, x+wide-1, y+high-1);
	num = wide * high*2 ;
	//方法1:提高显示速度,太快可能导致图像显示不全
//	do
//	{  
//		temp = pic[tmp + 1];
//		temp = temp << 8;
//		temp = temp | pic[tmp];
//		LCD_WriteData_Color(temp);//逐点显示
//		tmp += 2;
//	}
//	while(tmp < num);
	
	//方法2:可有效消除LCD显示速度过快导致显示不全问题
	for(i=0;i<high;i++)
	{
		for(j=0;j<wide;j++)
		{
			temp = pic[tmp + 1];
			temp = temp << 8;
			temp = temp | pic[tmp];
			LCD_DrawFRONT_COLOR(x+j,y+i,temp);
			tmp += 2;
		}
	}	
}

我们给大家提供很多 TFTLCD 操作的 API 函数, 如下:

cpp 复制代码
void TFTLCD_Init(void); //初始化
void LCD_Set_Window(u16 sx,u16 sy,u16 width,u16 height);//设置窗口
void LCD_Display_Dir(u8 dir);//设置屏幕显示方向
void LCD_Clear(u16 Color);//清屏
void LCD_Fill(u16 xState,u16 yState,u16 xEnd,u16 yEnd,u16 color);//填充单色
void LCD_Color_Fill(u16 sx,u16 sy,u16 ex,u16 ey,u16 *color);//在指定区域内填充指定颜色块
void LCD_DrawPoint(u16 x,u16 y);//画点
void LCD_DrawFRONT_COLOR(u16 x,u16 y,u16 color);//指定颜色画点
u16 LCD_ReadPoint(u16 x,u16 y);//读点
void LCD_DrawLine(u16 x1, u16 y1, u16 x2, u16 y2);//画线
void LCD_DrawLine_Color(u16 x1, u16 y1, u16 x2, u16 y2,u16 color);//指定颜色画线
void LCD_DrowSign(uint16_t x, uint16_t y, uint16_t color);//画十字标记
void LCD_DrawRectangle(u16 x1, u16 y1, u16 x2, u16 y2);//画矩形
void LCD_Draw_Circle(u16 x0,u16 y0,u8 r);//画圆
void LCD_ShowChar(u16 x,u16 y,u8 num,u8 size,u8 mode);//显示一个字符
void LCD_ShowNum(u16 x,u16 y,u32 num,u8 len,u8 size);//显示一个数字
void LCD_ShowxNum(u16 x,u16 y,u32 num,u8 len,u8 size,u8 mode);//显示数字
void LCD_ShowString(u16 x,u16 y,u16 width,u16 height,u8 size,u8 *p);//显示字符串
void LCD_ShowFontHZ(u16 x, u16 y, u8 *cn);//显示汉字					   						   																			
void LCD_ShowPicture(u16 x, u16 y, u16 wide, u16 high,u8 *pic);//显示图片

我们提供的这么多函数, 其实很多函数都是调用画点函数 LCD_DrawPoint 完成的, 这些函数的使用方法也很简单, 我们就以显示字符函数 LCD_ShowChar 为例进行介绍(其他函数大家可参考对应的源代码) :

cpp 复制代码
//在指定位置显示一个字符
//x,y:起始坐标
//num:要显示的字符:" "--->"~"
//size:字体大小 12/16/24
//mode:叠加方式(1)还是非叠加方式(0)
void LCD_ShowChar(u16 x,u16 y,u8 num,u8 size,u8 mode)
{  							  
    u8 temp,t1,t;
	u16 y0=y;
	u8 csize=(size/8+((size%8)?1:0))*(size/2);		//得到字体一个字符对应点阵集所占的字节数	
 	num=num-' ';//得到偏移后的值(ASCII字库是从空格开始取模,所以-' '就是对应字符的字库)
	for(t=0;t<csize;t++)
	{   
		if(size==12)temp=ascii_1206[num][t]; 	 	//调用1206字体
		else if(size==16)temp=ascii_1608[num][t];	//调用1608字体
		else if(size==24)temp=ascii_2412[num][t];	//调用2412字体
		else return;								//没有的字库
		for(t1=0;t1<8;t1++)
		{			    
			if(temp&0x80)LCD_DrawFRONT_COLOR(x,y,FRONT_COLOR);
			else if(mode==0)LCD_DrawFRONT_COLOR(x,y,BACK_COLOR);
			temp<<=1;
			y++;
			if(y>=tftlcd_data.height)return;		//超区域了
			if((y-y0)==size)
			{
				y=y0;
				x++;
				if(x>=tftlcd_data.width)return;	//超区域了
				break;
			}
		}  	 
	}  	    	   	 	  
}   

函数入口参数 x 和 y 用来设置显示的起始位置, num 是要显示的字符, size用来选择显示字体大小, 本实验支持 12/16/24 号的 ASCII 字符显示。 mode 用来设置是否支持叠加显示, 为 0 表示不使用叠加, 为 1 表示使用叠加显示。 叠加方式显示多用于在显示的图片上显示字符, 非叠加方式一般用于普通的显示。 函数内我们用到了三个字符集点阵数据数组 asc2_1206、 asc2_1608、 asc2_2412。

40.4.5 字符汉字图片提取软件使用

下面我们重点介绍下如何让字符显示在 TFTLCD 模块上。 要显示字符, 我们先要有字符的点阵数据, ASCII 常用的字符集总共有 95 个, 从空格符开始, 分为: !"#$%&'()*+,-0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\\^_`abc defghijklmnopqrstuvwxyz{|}~.

我们先要得到这个字符集的点阵数据, 这里我们介绍一个款很好的字符提取软件: PCtoLCD2002 完美版, 在我们光盘的"\5--开发工具\4-常用辅助开发软件\PCtoLCD2002 完美版" 位置。 该软件可以提供各种字符, 包括汉字(字体和大小都可以自己设置) 阵提取, 且取模方式可以设置好几种, 常用的取模方式,该软件都支持。 该软件还支持图形模式, 也就是用户可以自己定义图片的大小,然后画图, 根据所画的图形再生成点阵数据, 这功能在制作图标或图片的时候很有用。 双击这个软件, 弹出界面如下:

这里我们介绍下如何取汉字和字符字模数据, 点击"模式" , 选择"字符模式" , 操作如下:

然后点击"选项" , 会弹出一个字模选项对话框, 将其设置为"阴码+逐列式+顺向+C51 格式" , 操作如下:

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

从上到下, 从左到右, 高位在前。 我们按这样的取模方式, 然后把 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 个字节。 具体见 font.h 部分代码。

(1) 汉字显示

本实验我们还给大家提供了一个汉字显示函数, 这里再给大家介绍另一款取模软件, 放在光盘"\5--开发工具\4-常用辅助开发软件\文字取模软件" 目录内,此软件用于汉字取模极其方便, 下面打开这个软件, 界面如下:

首先我们选择"参数设置" 设定好字体类型及大小, 本实验所取汉字大小为宋体 22 号, 具体操作步骤如下:

设置好字体大小后, 我们再来设置下取模的方式, 点击"其他选项" , 选择"横向取模" , 操作步骤如下:

设置好取模方式后, 接下来就是取汉字字模数据了, 我们直接在"文字输入区" 输入所要显示的汉字, 比如输入"普" , 然后按下"Ctrl+Enter 组合键"就会在窗口显示输入的汉字, 点击"取模方式" 选择"C51 格式" , 就会在"点阵生成区" 显示汉字"普" 的字模数据, 操作步骤如下:

这样我们直接将其保存在 font.h 的 CnChar32x29 数组内, 我们在定义显示汉字时还加了汉字内锁码, 所以在保存这些汉字字模数据时, 前面加上我们所输入的汉字"普" , 如下:

如果要显示其他的汉字, 取模方法和上述一样, 这些显示函数的具体实现过程, 大家可以参考工程源代码, 这里就不多介绍。

注意: 有的朋友在使用取模软件取汉字时会出现这样一种情况, 明明是按照教程来设置取模软件参数, 但是最后取的汉字数据不是 16*16 大小的, 这种情况是因为你电脑系统内缺少某种字库, 你可以换台电脑测试, 如果可以就把系统内字库拷贝到你电脑中去, 怎么查找系统字库, 这个就需要自己百度了。

(2) 图片显示

在我们的 tftlcd.c 文件内还提供了一个图片显示函数, 那么怎么来显示图片呢? 这里再给大家介绍另一款取模软件, 放在光盘"\5--开发工具\4-常用辅助开发软件\Image2Lcd 2.9(破解版)" 目录内, 此软件用于图片取模极其方便,打开后界面如下:

1, 点击"打开" 菜单按钮, 选择你想要显示的图片, 注意图片格式要是.BMP,如下所示:

2, 设置相关参数, 包括输出数据类型、 扫描模式、 输出灰度、 最大宽度和高度等, 注意: 最大宽度和高度要按照你所选择的图片尺寸设置。 图片的实际尺寸大小可点击图片属性查看, 如下:

具体设置如下: 其他未标注的保持默认即可。

4, 点击"保存" 菜单按钮, 将输出的图片数据保存为 xxx.h 格式, 存储到tftlcd 文件夹内, 这里我们就命名为 picture.h, 如下:

保存好后就会弹出图片取模后的数据, 如下:

上述 gImage_picture 数组即为该图片的数据。

然后在 main.c 文件内将 picture.h 头文件包含进来并且主函数内调用显示图片函数即可, 如下:

cpp 复制代码
LCD_ShowPicture(10,60,56,56,gImage_picture);

函数中前面两个参数 10,60 为图片显示的起始位置, 后面两个参数 56,56 分别是图片的宽度和高度, 这两个值一定要保证和前面取模时图片大小一致, 否则显示出错。 最后一个参数 gImage_picture 即为存储图片数据的数组。

40.4.6 主函数

编写好 TFTLCD 初始化和显示函数后, 接下来就可以编写主函数了, 代码如下

cpp 复制代码
#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "usart.h"
#include "tftlcd.h"
#include "picture.h"


/*******************************************************************************
* 函 数 名         : main
* 函数功能		   : 主函数
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/
int main()
{
	u8 i=0;
	u16 color=0;
	
	SysTick_Init(72);
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  //中断优先级分组 分2组
	LED_Init();
	USART1_Init(115200);
	TFTLCD_Init();			//LCD初始化
	
	FRONT_COLOR=BLACK;
	LCD_ShowString(10,10,tftlcd_data.width,tftlcd_data.height,12,"Hello World!");
	LCD_ShowString(10,30,tftlcd_data.width,tftlcd_data.height,16,"Hello World!");
	LCD_ShowString(10,50,tftlcd_data.width,tftlcd_data.height,24,"Hello World!");
	LCD_ShowFontHZ(10, 80,"普中科技");
	LCD_ShowString(10,120,tftlcd_data.width,tftlcd_data.height,24,"www.prechin.cn");
	
	LCD_Fill(10,150,60,180,GRAY);
	color=LCD_ReadPoint(20,160);
	LCD_Fill(100,150,150,180,color);
	printf("color=%x\r\n",color);
	
	LCD_ShowPicture(20,220,200,112,(u8 *)gImage_picture);

	while(1)
	{
		i++;
		if(i%20==0)
		{
			LED1=!LED1;
		}
		
		delay_ms(10);		
	}
}

主函数实现的功能很简单, 首先调用之前编写好的硬件初始化函数, 包括SysTick 系统时钟, LED 初始化等。 然后调用我们前面编写的 TFTLCD_Init 函数,用来初始化 TFTLCD, 默认设置为竖屏显示, 背景颜色为白色。 设置显示颜色为黑色后, 调用字符串及汉字显示函数进行显示, 最后进入 while 循环, 控制 D1指示灯会间隔 200ms 闪烁, 提示系统正常运行。

40.5 实验现象

将工程程序编译后下载到开发板内, 可以看到 D1 指示灯不断闪烁, 表示程序正常运行。 将 TFTLCD 模块插上开发板上的彩屏接口, 按下复位键后, TFTLCD即可显示字符及汉字信息。 如下图所示:

实验说明: 下载有关 TFTLCD 显示实验时, 请确保您所使用的 TFTLCD 彩屏驱动型号与程序内选择的一致, 如何查看呢? 首先看下您 TFTLCD 彩屏板左上角对应的驱动型号, 比如 ILI9481。 然后打开实验程序, 查看 tftlcd.h 头文件开始处对应的 TFTLCD 驱动型号选择的是哪个? 如下所示:

课后作业

(1) 在本实验基础上实现其他大小的 ASCII 或汉字显示。 (温馨提示: 按照前面所介绍的取模软件使用方法取模, 然后在驱动程序内修改即可)

(2) 可以将前面章节的实验, 采用 TFTLCD 彩屏显示, 就不需要 printf 打印输出了。

相关推荐
woohuwan2 小时前
功率线与信号线共模电感的核心区别
嵌入式硬件
LCG元3 小时前
STM32实战:基于STM32F103的智能衣柜(除湿+防霉+照明)
stm32·单片机·嵌入式硬件
0南城逆流03 小时前
【STM32】RTT-Studio中HAL库开发教程十三:MSH串口组件
stm32·单片机·嵌入式硬件
子朔不言3 小时前
MH2030B 一个输入IO失效故障分析(stm32F030系列有类似问题)
单片机·嵌入式硬件·mh2030b
LCG元4 小时前
STM32实战:基于STM32F103的智能饮水机(温度控制+流量计费)
stm32·单片机·嵌入式硬件
m0_377108144 小时前
stm32-DMA
stm32·单片机·嵌入式硬件
嵌入式小站4 小时前
STM32 零基础可移植教程 11:PWM 输出,让 LED 呼吸起来
stm32·单片机·嵌入式硬件
sramdram4 小时前
Cascadeteq国产替代psram芯片,国产psram芯片CSS1604S系列
单片机·嵌入式硬件·psram·cascadeteq·国产替代psram·国产psram芯片
peixiuhui4 小时前
ARM工控机与边缘计算网关:工业现场的算力革命与选型实践
网关·边缘计算·数据采集·开发板·工控机·rk3506·工控板