利用FSMC模拟8080时序控制LCD


注:本文属博主学习时所作笔记,内容源大参考于野火的《零死角玩转STM32F103》以及部分网络资料,笔记内容仅作为自己参考,免去频繁查询参考手册的麻烦,如有错误,还请指出!

ILI9341 液晶控制器

ILI9341控制器内部框图

ILI9341 控制器内部电路连接完后,其余信号线引出到排针

液晶屏引出的信号线

液晶屏引出的信号线说明

信号线 ILI9341 对 应 的信号线 说明
LCD_DB[15:0] D[15:0] 数据信号
LCD_RD RDX 读数据信号,低电平有效 ,
LCD_RS D/CX 数据/命令信号,高电平时, D[15:0]表示的是数据(RGB 像素数据或命令数据);低电平时,D[15:0]表示控制命令
LCD_RESET RESX 复位信号,低电平有效
LCD_WR WRX 写数据信号,低电平有效
LCD_CS CSX 片选信号,低电平有效
LCD_BK - 背光信号,低电平点亮
GPIO[5:1] - 触摸屏的控制信号线,下一章再介绍

FSMC简介

STM32F1 系列芯片使用 FSMC 外设来管理扩展的存储器, FSMC 是 Flexible Static
Memory Controller 的缩写,译为灵活的静态存储控制器。它可以用于驱动包括 SRAM、
NOR FLASH 以及 NAND FLSAH 类型的存储器,不能驱动如 SDRAM 这种动态的存储器而
在 STM32F429 系列的控制器中,它具有 FMC 外设,支持控制 SDRAM 存储器。

MCU 对液晶屏的操作实际上就是把显示数据写入到显存中,与控制存储器非常类似,且8080 接口的通讯时序完全可以使用 FSMC 外设产生。

模拟 8080 接口时序

在模拟控制 LCD 时,是使用 FSMC 的 NOR\PSRAM 模式的, 而且使用的是类似异步、 地址与数据线独立的 NOR FLASH 类型的模式 B,实际上 CLK、 NWAIT、 NADV 引脚并没有使用到。

FSMC 信 号 名 信号方向 功能
CLK 输出 时钟(同步突发模式使用)
A[25:0] 输出 地址总线
D[15:0] 输入/输出 双向数据总线
NE[x] 输出 片选, x = 1…4
NOE 输出 输出使能
NWE 输出 写使能
NWAIT 输入 NOR 闪存要求 FSMC 等待的信号
NADV 输出 地址、数据线复用时作锁存信号

对比 FSMC NOR/PSRAM 中的模式 B 时序与 ILI9341 液晶控制器芯片使用的 8080 时序可发现,这两个时序是十分相似的(除了 FSMC 的地址线 A 和 8080 的 D/CX 线,可以说是完全一样)

FSMC模式B时序与8080时序对比(写过程)
FSMC-NOR信号线 功能 8080 信号线 功能
NEx 片选信号 CSX 片选信号
NWR 写使能 WRX 写使能
NOE D[15:0] 读使能 数据信号 RDX D[15:0] 读使能 数据信号
A[25:0] 地址信号 D/CX 数据/命令选择

为了模拟出 8080 时序,我们可以把 FSMC 的 A0 地址线(也可以使用其它 A1/A2 等地
址线)与 ILI9341 芯片 8080 接口的 D/CX 信号线连接,那么当 A0 为高电平时(即 D/CX 为高
电平),数据线 D[15:0]的信号会被 ILI9341 理解为数值,若 A0 为低电平时(即 D/CX 为低电
平),传输的信号则会被理解为命令。

FSMC的地址映射

使用 FSMC 外接存储器时,其存储单元是映射到 STM32 的内部寻址空间的,这里也涉及到使用地址线某个引脚时,需要计算对应的地址,以达到控制输出高低电平。

FSMC 的地址映射

代码分析

液晶LCD硬件相关宏定义

根据液晶屏的原理图,将FSMC 控制液晶屏硬件相关的配置都以宏的形式定义:

开发板与屏幕的连接
/*****************    LCD控制信号    ******************/
//片选引脚
#define        ILI9341_CS_CLK        RCC_APB2Periph_GPIOD
#define     ILI9341_CS_PORT     GPIOD
#define     ILI9341_CS_PIN         GPIO_Pin_7

//数据命令引脚
#define        ILI9341_DC_CLK        RCC_APB2Periph_GPIOD
#define        ILI9341_DC_PORT        GPIOD
#define        ILI9341_DC_PIN        GPIO_Pin_11

/****************    数据信号线    *****************/
#define     ILI9341_D0_CLK         RCC_APB2Periph_GPIOD
#define     ILI9341_D0_PORT     GPIOD
#define     ILI9341_D0_PIN         GPIO_Pin_14

//其他引脚省略

初始化 FSMC 的 GPIO

利用上面的宏,编写 FSMC 的 GPIO 引脚初始化函数,对于 FSMC 引脚,全部直接初始化为复用推挽输出模式即可,而背光 BK 引脚及液晶复信 RST 信号则被初始化成普通的推挽输出模式,这两个液晶控制信号直接输出普通的电平控制即可。

static void ILI9341_GPIO_Config (void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    /* 使能 FSMC 对应相应管脚时钟*/
    RCC_APB2PeriphClockCmd (
    /* 控制信号 */
    ILI9341_CS_CLK|ILI9341_DC_CLK|ILI9341_WR_CLK|
    ILI9341_RD_CLK |ILI9341_BK_CLK|ILI9341_RST_CLK|
    /* 数据信号 */
    ILI9341_D0_CLK|ILI9341_D1_CLK, ENABLE);
    /* 省略部分信号线 */

    /* 配置 FSMC 相对应的数据线,FSMC-D0~D15 */
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Pin = ILI9341_D0_PIN;

    GPIO_Init(ILI9341_D0_PORT, &GPIO_InitStructure);
    /* 省略部分信号线 */

    //设置复用推挽输出
     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

     GPIO_InitStructure.GPIO_Pin = ILI9341_RD_PIN;
     GPIO_Init (ILI9341_RD_PORT, &GPIO_InitStructure);
    /* 省略WR, CS, DC 控制引脚 */

    //设置普通推挽输出
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;    
     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    /* 配置 LCD 复位 RST 控制管脚 */
     GPIO_InitStructure.GPIO_Pin = ILI9341_RST_PIN;
     GPIO_Init (ILI9341_RST_PORT, &GPIO_InitStructure);
    /* 配置 LCD 背光控制管脚 BK */
    GPIO_InitStructure.GPIO_Pin = ILI9341_BK_PIN;
     GPIO_Init (ILI9341_BK_PORT, &GPIO_InitStructure);
}

配置 FSMC 的模式

static void ILI9341_FSMC_Config (void)
{    
    /* FSMC模式初始化结构体 */
    FSMC_NORSRAMInitTypeDef         FSMC_NORSRAMInitStructure;
    /* 读写时序结构体 */
     FSMC_NORSRAMTimingInitTypeDef     readWriteTiming;

     /* 使能 FSMC 时钟*/
     RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC, ENABLE);

     //地址建立时间(ADDSET)为 1 个 HCLK 2/72M=28ns
     readWriteTiming.FSMC_AddressSetupTime = 0x01; //地址建立时间
     //数据保持时间(DATAST) + 1 个 HCLK = 5/72M=70ns
    readWriteTiming.FSMC_DataSetupTime = 0x04; //数据建立时间
     //选择控制的模式
     //模式 B, 异步 NOR FLASH 模式,与 ILI9341 的 8080 时序匹配
     readWriteTiming.FSMC_AccessMode = FSMC_AccessMode_B;

     /* 以下配置与模式 B 无关 */
     readWriteTiming.FSMC_AddressHoldTime = 0x00; //地址保持时间,模式 B未用到
     //设置总线转换周期,仅用于复用模式的 NOR 操作
     readWriteTiming.FSMC_BusTurnAroundDuration = 0x00;
      //设置时钟分频,仅用于同步类型的存储器
     readWriteTiming.FSMC_CLKDivision = 0x00;
     //数据保持时间,仅用于同步型的 NOR
     readWriteTiming.FSMC_DataLatency = 0x00;

    //选择使用的bank, 本次实例中使用的是FSMC_NE1
    //需要在宏定义中定义:#define FSMC_Bank1_NORSRAMx FSMC_Bank1_NORSRAM1
    FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAMx;
    //设置地址总线是否与数据总线是否复用,仅用于NOR
     FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable;
    //设置要控制的存储器类型:本实例使用的是异步NOR
     FSMC_NORSRAMInitStructure.FSMC_MemoryType = FSMC_MemoryType_NOR;
    /* 设置存储器的数据宽度 */
     FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;
    /* 设置是否支持突发访问模式,只支持同步类型的存储器 */
     FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode = FSMC_BurstAccessMode_Disable;
    /* 设置等待信号的极性,仅用于同步类型存储器 */
     FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;
    /* 设置是否支持对齐的突发模式,仅用于同步 */
     FSMC_NORSRAMInitStructure.FSMC_WrapMode = FSMC_WrapMode_Disable;
    /* 配置等待信号(插入时间)在等待前有效还是等待期间有效,仅用于同步 */
     FSMC_NORSRAMInitStructure.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState;
    /* 设置是否写使能 */
     FSMC_NORSRAMInitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable;

    /* 设置是否使能等待状态插入,用于设置当存储器处于突发传输模式时,
     * 是否允许通过 NWAIT 信号插入等待状态,不使用 
     */
     FSMC_NORSRAMInitStructure.FSMC_WaitSignal = FSMC_WaitSignal_Disable;
    /* 设置是否使能扩展模式,不使用,读写同一时序*/
     FSMC_NORSRAMInitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Disable;
    /* 设置是否使能写突发操作 */
     FSMC_NORSRAMInitStructure.FSMC_WriteBurst = FSMC_WriteBurst_Disable;
    /* 当不使用扩展模式时,本参数用于配置读写时序,否则用于配置读时序 */
     FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &readWriteTiming;
    /* 当使用扩展模式时,本参数用于配置写时序 */
    FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &readWriteTiming;
     //初始化FSMC的配置
     FSMC_NORSRAMInit (&FSMC_NORSRAMInitStructure);
     /* 使能 FSMC_Bank1_NORSRAMx */
     FSMC_NORSRAMCmd (FSMC_Bank1_NORSRAMx, ENABLE);
}

计算控制液晶屏时使用的地址

  • 首先确定板子NEx引脚的连接,本次实例使用的是FSMC_NE1

  • 在 数据手册 –> 存储器映像 中查询FSMC_bnak1 NOR/PSRAM 1的地址:

  • 使用的是FSMS_NE1也就是当访问到0x6000000-0x63FFFFFF这个地址范围内时,FSMC都会产生有效的访问时序;本实例中使用的是FSMC_A16地址线作为命令/数据选择线,即:

    • 使FSMC_A16为高电平,即第十六位为 1 ,可用地址范围内任意地址做一下运算:

      0X6000 0000 |= (1<<16) = 0x6001 0000;
    • 同样的,使FSMC_A16为低电平,即:

      0X6000 0000 &= ~ (1<<16) = 0x6000 0000;
  • 由于地址线转换的问题,这里不细说,需要知道的是,STM32 内部的 HADDR 与 FSMC 的连接关系会左移一位,计算关系也会改变:

    • 使FSMC_A16地址线为高电平:

      0X6000 0000 |= (1<<(16+1)) = 0x6002 0000;
    • 使FSMC_A16地址线为低电平:

      0X6000 0000 &= ~(1<<(16+1)) = 0x6000 0000;
封装函数

因为在实例后面会频繁对LCD进行操作,所以把发送命令及发送数据的操作封装成了内联函数,会更方便调用:

/*********** 定义 FSMC 参数宏 **********/
//FSMC_Bank1_NORSRAM 用于 LCD 命令操作的地址
#define FSMC_Addr_ILI9341_CMD ( ( uint32_t ) 0x60020000 )

//FSMC_Bank1_NORSRAM 用于 LCD 数据操作的地址
#define FSMC_Addr_ILI9341_DATA ( ( uint32_t ) 0x60000000 )
//发送数据
__inline void ILI9341_Write_Cmd (uint16_t usCmd)
{
    (__IO uint16_t *)(FSMC_Addr_ILI9341_CMD) = usCmd;
}
//写入数据
__inline void ILI9341_Write_Data (uint16_t usData)
{
    (__IO uint16_t *)(FSMC_Addr_ILI9341_DATA) = usData;
}

需要写操作时,只要把要发送的命令代码或数据作为参数输入到函数然后调用即可,对于液晶屏的读操作,把向指针赋值的过程改为读取指针内容即可。

//读取数据
__inline uint16_t ILI9341_Read_Data (void)
{
    return (*FSMC_Addr_ILI9341_DATA);
}

检验封装

在上述函数封装好之后,进行一些测试检验使很有必要,我们可以查询ILI9341的数据文档,选择合适的命令进行测试:

当发送命令0x0C,第二个参数会返回 LCD 的像素信息,可以封装一个检验函数如下:

uint16_t Read_Pixel_Format(void)
{
    ILI9341_Write_Cmd(0x0C);    //发送命令参数
    ILI9341_Read_Data();        //读取第一个返回参数
    return ILI9341_Read_Data();    //返回第二个读取参数
}

文章作者: Mahoo Huang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Mahoo Huang !
评论
 上一篇
C语言再学习之基础语法深挖 C语言再学习之基础语法深挖
内联函数 —— C 中关键字 inline调用函数时,一般会由于建立调用、传递参数、跳转到函数代码并返回等花费掉一些时间,而且一些函数被频繁调用,不断地有函数入栈,即函数栈,会造成栈空间或栈内存的大量消耗。 为了解决这个问题,在C99中特别
2020-02-26
下一篇 
我的网页梦 —— 前端基础 & Vuejs 我的网页梦 —— 前端基础 & Vuejs
前言自从上学期期末考试以后,就没有更新博客了,那时候是因为,你懂的嘛; 回到家后,肯定是玩啦,这么长的假期,同学聚会也少不了,过年期间更是忙的没话说,况且,今年的春节被这个新型冠状病毒整的是你死我活的,国家都进入一种战时状态,天佑中华吧,早
2020-02-18
  目录