【深入浅出】单片机驱动8080LCD

1、前言

最近项目上用到了几款2-4寸的屏幕用于做数据显示,考虑到成本,没有采用arm+linux的配置。采用的是gd32单片机作为主控。屏幕没有采用rgb或mipi接口,因为单片机内存有限,这两种方式屏幕不带sram,需要依靠主控频繁刷新,对内存的资源要求比较高。为了保证刷新率,最后采用的8080 16位单片机接口屏幕。

之前使用过4线spi 的屏幕,这种方式配置相对较为简单,8080接口的一般是采用单片机的外部sram接口,sram接口的配置有点复杂,一直没有仔细研究过,本次一次把弄清楚接口配置。

2、单片机的FSMC控制器

2.1FSMC简介

基于stm32f4中文参考手册中的介绍。FSMC全称为灵活额静态存储控制器,其主要的特性如下:

这里就直接截图了f4中文参考手册中的说明,从上面的详细的描述中,可以得到如下几点重要信息:

1、控制器的作用是连接外部存储器
2、多个外部存储器公用相同的数据总线,地址总线。对外部存储器的读写操作采用的是类似于spi的操作方式,通过片选cs信号来访问不同的存储器。
3、外部存储器的数据总线宽度可以是8bit或者16bit

2.2FSMC控制器框图

根据f4中文参考手册的描述。FSMC框图如下:

主要包含四个模块:

  • AHB接口,主要功能是CPU和其他AHB总线设备可以通过该AHB从设备来访问外部存储器。具体原理这里就不深究了。

  • NOR flash/PSRAM控制器,主要是驱动一步SRAM和ROM,PSRAM,NORFlash等外部存储器。fsmc会为每一个存储器分配一个固定的片选信号NE[4:1]。

  • NAND Flash/PC卡控制器,主要是用来配置对应的外部存储器的参数。

  • 外部器件接口

  • 2.3 地址映射

    由于FSMC是通过地址和数据总线与外部存储器连接,所以外部存储器地址实际上是映射到了mcu的地址总线上。相当于给内部存储器扩充了一块空间。

    FSMC将外部存储器划分为4个固定大小的存储区域,每个存储区域的大小为256M。

    存储区1最多可连接4个NOR Flash或者PSRAM存储器,此区域呗划分为4个区域,通过4个专用的片选信号进行区分。

    存储区2和3用于连接NAND Flash 器件,每个区域一个器件

    存储区4用于连接PC卡设备


    这里重点关注下存储区1,因为lcd 8080接口对接的就是在存储区1。

    存储区1地址是从0x6000 0000开始的,分成了4块,每一块的大小为64Mbit

    存储区的片选通过HADDR寄存器进行设置。

    这里只需要了解即可,后续在代码中配置时,官方已经采用宏定义定义好了这个配置,代码中分为BLOCK0 ~ BLOCK3

    HADDR寄出去你的25:0为包含外部存储器地址,由于HADDR为字节地址,而存储器是按照字来寻址的,所有根据存储器数据的宽度不同,实际向存储器发送的地址也将有所不同。具体如下图。


    这张图极为关键,因为后面配置LCD寄存器地址和数据地址的时候,就要用到这里的寻址方式,一开始笔者没有了解清楚,导致LCD的数据地址配置错误,最终导致无法向LCD寄存器写入数据。

    这里来具体分析下上面的这张图。当存储器是8bit的数据宽度的时候,此时地址是按照字节来寻址的。
    比如,如果外部存储器接到了区域1的block0,那么该存储器的地址是:

    地址 存储器空间数据 向存储器发送出地址
    0x600 0000 存储器的第0个字节 0x600 0000
    0x600 0001 存储器的第1个字节 0x600 0001
    0x600 0002 存储器的第2个字节 0x600 0002
    0x600 0003 存储器的第3个字节 0x600 0003
    这比较好理解地址每增加1,访问空间的字节+1.要访问存储器的第一个字节,只用发出0x600 0001即可访问到,改字节的值为*(uint8_t*)(0x600 0001)

    再来看看,如果存储器地址是16位,对应的情况是如何。对于16bit访问,是按照字来访问,一次访问的是两个字节。

    继续上面的例子,如果存储器接到区域1的block0,那么存储器的地址是:

    地址 存储器空间数据 向存储器发送出地址
    0x600 0000 存储器的第0个字,对应字节0-1 0x600 0000 |(0>>1)
    0x600 0002 存储器的第1个字,对应字节2-3 0x600 0000 |(2>>1)
    0x600 0004 存储器的第2个字,对应字节4-5 0x600 0000 |(4>>1)
    0x600 0006 存储器的第3个字,对应字节6-7 0x600 0000 |(6>>1)

    为什么会这样?这应该是mcu内部硬件设计的原因,这么看确实不好理解,为什么要偏移地址右移一位再按位或上基地址?说到根本,还是因为是字访问,地址是跳跃的,0,2,4,6,这样每次地址是+2。这种不连续在硬件设计上就有困难,最容易实现是是采用加法器,每次自加1,为了实现cpu访问外设是地址的连续,所以要要将访问的偏移地址右移一位。还是不好理解是不是,这样我们看下偏移地址右移一位后的结果。

    偏移地址 右移1位的结果
    0 0
    2 1
    4 2
    6 3
    8 4

    通过这样右移实际上就实现了cpu访问地址连续性。这里花了比较多的篇幅来讲这个地址偏移的问题。是为了给后文计算LCD寄存器地址和数据地址打下基础。这里没搞懂。后面配置LCD地址时,就会很慌,不知道怎么配置才是正确的。

    2.4 NOR/PSRAM可编程访问参数

    具体先看图:


    这张图里有几个信息需要注意,配置建立时间,保持时间之类的参数时,单位是时钟周期,比如地址建立时间如果设置为1,代表是配置为1个时钟周期,对于stm32f4,时间就是1000/168=6ns。对于GD32F4,时间就是1000/200=5ns。这个也比较重要,就是这个建立时间要根据LCD内部得驱动芯片的参考手册来设置。

    2.5外部存储器接口

    针对不同类型的外部存储器用到,用到的不同接口及定义如下:
    NOR Flash


    PSRAM/SRAM



    对比NOR Flash和SRAM二者使用的接口较为相似,SRAM比NOR Flash多了NBL高低自己使能信号。

    2.6NOR FLASH/PSRAM控制器时序图

    FSMC控制器根据模式不同,共有6中模式,分为2种标准模式,4种拓展模式。

    标准模式通过将FSMC_BCRx 寄存器中的EXTMOD 位设置为0进行选择。

  • 当存储器为SRAM/CRAM类型是,模式1为默认模式,FSMC_BCRx 寄存器中MTYP = 0x0 或 0x01
    -当存储器为NOR存储器时,模式2为默认模式,FSMC_BCRx 寄存器中MTYP = 0x10
  • 拓展模式,通过将通过将FSMC_BCRx 寄存器中的EXTMOD 位设置为1进行选择。拓展模式供分为A,B,C,D四种模式。

    各个模式时序图如下:

    模式1

    模式A


    模式2和模式B



    模式C

    模式D

    目前看到所有的教程上面在使用LCD8080接口时用的都是模式A。

    3、LCD屏幕

    3.1屏幕接口选择

    常见的小尺寸屏幕出厂一般内部都集成了驱动芯片,常见的驱动芯片有ST77xx ,ILI93xx,ILI94xx,不同的芯片型号测差别主要是驱动的屏幕的分辨率大小不同,像ILI9488 和ST7796能驱动的最大分辨率是480*320.

    一般驱动芯片都支持多种屏幕接口,像3线SPI,4线SPI,8080,RGB,mipi等接口。具体都是通过驱动芯片上的IM0~IM2这三个引脚来确定,由于驱动芯片是封装在裸屏内部的,所以你买到屏幕实际上是看不到驱动芯片,至今也没见过屏幕驱动芯片长啥样的。

    大部分买到的屏幕,在出场时就确定了接口形式8080或者spi接口,根本没有看到IM0,IM1,IM2引脚

    这里列出ST7796和ILI9488两款驱动芯片接口选择表。
    ST7796

    ILI488


    可以看到,不同商场驱动芯片接口基本一致了,IM2~IM0对应的8080 16位接口均为010。

    如果买的裸屏上面有IM引脚,记得设计pcb的时候一定要将IM引脚的高低电平设计的与接口要的一致。

    3.2单片机8080接口

    单片机8080接口采用的并行的通信方式,常用于屏幕驱动IC,最早由因特尔提出。其读写的时序如下:
    8080写时序

    8080读时序

    其中CS为片选信号,低电平有效。其中RS=0时为写命令(写寄存器地址)。RS=1时为数据(读写寄存器里面的数据),写的时候,要先将CS拉低,然后WR从高变低,RS根据需要设置,最后就是一次传输0:15的数据,也就是一个周期,传输了2个字节的数据。

    上面说到了各个教程上都是用的FSMC控制器的模式A,这里对比下8080和模式A的读写时序对比
    模式A和8080写模式

    这里看不出来有什么关联,但如果按照下面对应起来:

    8080引脚 FSMC引脚
    CS NEx
    WR NWE
    RS Ax
    RD NOE

    上面的接口就是屏幕与单片机引脚接口,RS接单片机上的某个FSMC地址引脚。
    这样一对应起来,发现二者的时序是不是基本一致,这也就可以理解了为什么要用模式A了。其他模式暂时没有深究。

    3.3 LCD硬件接口及读写地址计算

    通过上面的分析,基本可以明白了,8080单片机接口与FSMC模式A通信时序基本一致。8080RS引脚作用是,控制当前的数据是命令还是数据。这么干说不太好理解,一般我们用I2C或者SPI操作外部芯片时,对芯片额操作方式一般是。

    1、将要操作的寄存器的地址发送给外部芯片
    2、将要设置的寄存器的值发送给外部芯片
    3、外部芯片的寄存器的值读写成功

    I2C中是通过地址来确定当前是要读寄存器还是写寄存器。在8080接口中,对LCD驱动芯片的操作方式还是读写其内部得寄存器,但其是通过设置RS引脚的电平来告知LCD驱动芯片,当前是要写寄存还要读取寄存器。
    RS=0时,此时总线D0:15传输的数据是寄存器的地址。
    RS=1时,此时总线D0:15传输的数据是寄存器的值。

    那么怎么告知LCD驱动IC我是要读还是写寄存器呢。这里驱动IC将寄存器同一个寄存器的读写采用了不同的地址。

    比如:MADCTL寄存器,该寄存器的写地址是0x36。读地址是0x0b。也就是说,当要向该寄存器写入数据时。

    1、RS=0
    2、D[15:0] = 0x36
    3、RS=1
    4、D[15:0] = data

    先让RS=0,告诉LCD驱动器,下面我要发给你的数据是寄存器地址。然后让RS=1,告诉LCD驱动器,下面我要发给你数据是上一次发给你寄存器里需要配置的值。

    同理,读取数据时也是如此:

    1、RS=0
    2、D[15:0] = 0x0B
    3、RS=1
    4、data = D[15:0]

    读数据的时候,当发送完寄存器地址后,将RS=1,此时LCD驱动器会将该寄存器的值写入总线上,此时我们只需要读取总线的值即可。

    到这里基本明白,LCD驱动器的访问过程,但关键是RS引脚怎么让它自动置1或者0呢?上面我们说到RS引脚一般是接到了FSMC控制器的某个地址引脚。这种接法真的是特别巧妙。

    还是先看一个例子。在正点原子官方开发板中,LCD的接线基本是一样的。如下:

    上图中可以看到,LCD的RS接的是地址总线的A6,为了让A6引脚能改变0或者1,我们只需要设置两个地址,一个是A6=0,此时对应LCD写命令(寄存器地址),A6=1,此时对应LCD写数据。
    这样的组合有很多。
    比如,0x600 0000 这个地址A6=0,向该地址写入的值就应该是LCD控制器的寄存器地址。
    0x600 0080 这个地址A6=1,向该地址写入的值就应该是LCD控制器的寄存器值。
    0x80的二进制为1000 0000。A6=1的二进制为 100 0000。因为上面说到了当存储器为16位的时候,实际cpu访问的时候会将偏移地址右移一位,所有最终A6等于0时,对应的偏移地址为0x80。

    基于上面的例子,通过向这两个地址写入数据即可完成对LCD寄存器的操作。此时写入0x36寄存的命令为

    1、(uint16_t)0x600 0000 = 0x36;
    2、(uint16_t)0x600 0080 = data;

    通过以上两句话,即可完成对LCD驱动器的0x36寄存器写入data值的设置。

    继续来看一个例子,由于项目上将RS引脚接到了A16上。此时写命令的地址可以设置为:0x6000 000,写数据的地址设置为0x602 0000。计算方法与上面的例子一致,就不展开了,感兴趣的可以自己再计算一下,注意让A16=1并且左移一位。

    在最终代码中,可以这样定义

    #define LCD_DATA				((uint32_t)0x60020000)	
    #define LCD_CMD				((uint32_t)0x60000000)
    

    正点原子的例程中,提供一个更STM32方式的定义方式。

    typedef struct
    {
    	__IO uint16_t LCD_REG;
    	__IO uint16_t LCD_RAM;
    } LCD_TypeDef;
    
    #define LCD_BASE        ((uint32_t)(0x60000000 | 0x0001FFFE))
    #define LCD             ((LCD_TypeDef *) LCD_BASE)
    

    这种方式是将LCD的写命令和数据采用结构体进行抽象。
    在写0x36寄存器时,只需要:

    LCD->LCD_REG = 0x36;
    LCD->LCD_RAM = data;
    

    这两种方式没有对错之分,看个人使用习惯。正点原子的方式是采用了两个连续的字地址。

    3.4LCD8080接FSMC控制器的优势。

    lcd8080接口可以使用模拟的,采用通用IO口模拟其时序即可访问。具体参见正点原子。
    https://www.bilibili.com/read/cv16541143?spm_id_from=333.999.0.0。
    这种方式就是完全模拟其读写时序,但是比较麻烦的是设置和调整上文说到的地址建立时间,数据保持时间等等参数。而且读写函数较为复杂。

    而采用FSMC控制的好处是,可以精确配置这些时序参数,并且访问读写非常简单。
    只用向内存地址写入数据即可,不用去管CS,RW,RD这些时序操作,因为这些是清FSMC控制器都帮你做了。

    这样的对LCD的寄存器的控制方式与stm32内部寄存器的控制方式是一致的,直接往某个地址写数据即可。如果不明白,具体去看下stm32单片机,gpio寄存器底层配置逻辑。

    3.5 LCD565颜色

    一般lcd颜色分为24位彩色和16位彩色,在24位色彩模式时,RGB三原色分别占8bit,一共24位。16为色彩是对24位色彩的裁剪,一般是RGB三原色分别占5bit,6bit,5bit简称565模式也就是常说的65K色彩,合起来一共是16位。

    当采用16位8080接口时,最好是采用16为色彩模式,这样一次传输可以传输一个像素点的颜色。
    ST7796中对16位565颜色时序做了说明。

    bit15-bit11对应 Rbit4-bit0
    bit10-bit5对应 Gbit5-bit0
    bit4-bit0对应 Bbit4-bit0

    3.6FSMC控制器的配置

    上文主要是对FSMC及LCD相关知识进行了讲解,这里具体来看下FSMC控制到底怎么配置。
    笔者目前使用的GD32F4系列,所以基于此代码进行讲解。
    接线是,RS接的是A16,CS接的是NE1对应于BLOCK0

    void lcd_sram_init(void)
    {
        exmc_norsram_parameter_struct nor_init_struct;
        exmc_norsram_timing_parameter_struct nor_timing_init_struct,nor_timing_write_struct;
    
        /* EXMC clock enable */
        rcu_periph_clock_enable(RCU_EXMC);
    
        /* EXMC enable */
        rcu_periph_clock_enable(RCU_GPIOA);
        rcu_periph_clock_enable(RCU_GPIOB);
        rcu_periph_clock_enable(RCU_GPIOD);
        rcu_periph_clock_enable(RCU_GPIOE);
    
        /* configure EXMC_D[0~15]*/
        /* PD14(EXMC_D0), PD15(EXMC_D1),PD0(EXMC_D2), PD1(EXMC_D3), PD8(EXMC_D13), PD9(EXMC_D14), PD10(EXMC_D15) */
        gpio_af_set(GPIOD, GPIO_AF_12, GPIO_PIN_0 | GPIO_PIN_1| GPIO_PIN_8 | GPIO_PIN_9 |
                                                             GPIO_PIN_10 | GPIO_PIN_14 | GPIO_PIN_15);
        gpio_mode_set(GPIOD, GPIO_MODE_AF, GPIO_PUPD_PULLUP,GPIO_PIN_0 | GPIO_PIN_1| GPIO_PIN_8 | GPIO_PIN_9 |
                                                             GPIO_PIN_10 | GPIO_PIN_14 | GPIO_PIN_15 );
        gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_0 | GPIO_PIN_1| GPIO_PIN_8 | GPIO_PIN_9 |
                                                             GPIO_PIN_10 | GPIO_PIN_14 | GPIO_PIN_15);
    
        /* PE7(EXMC_D4), PE8(EXMC_D5), PE9(EXMC_D6), PE10(EXMC_D7), PE11(EXMC_D8), PE12(EXMC_D9), 
           PE13(EXMC_D10), PE14(EXMC_D11), PE15(EXMC_D12) */
        /* NBL0(PE0),D4(PE7),D5(PE8),D6(PE9),D7(PE10),D8(PE11),D9(PE12),D10(PE13),D11(PE14),D12(PE15) pin configuration */
        gpio_af_set(GPIOE, GPIO_AF_12,   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);
        gpio_mode_set(GPIOE, GPIO_MODE_AF, GPIO_PUPD_PULLUP,    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);
        gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,   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);
    
        
        /* configure NOE(PD4),NWE(PD5) and NE0(PD7) */
        //cs pd7 ne1 ;rd pd4 noe ; wr pd5 nwe; rs pd11 A16
        /* D2(PD0),D3(PD1),D13(PD8),D14(PD9),D15(PD10),D0(PD14),D1(PD15) pin configuration */
        gpio_af_set(GPIOD, GPIO_AF_12, GPIO_PIN_4  | GPIO_PIN_5  | GPIO_PIN_7 | GPIO_PIN_11 );
        gpio_mode_set(GPIOD, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_4  | GPIO_PIN_5  | GPIO_PIN_7 | GPIO_PIN_11 );
        gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_4  | GPIO_PIN_5  | GPIO_PIN_7 | GPIO_PIN_11 );
    
    
        
    
    
        /* configure timing parameter */
        nor_timing_init_struct.asyn_access_mode = EXMC_ACCESS_MODE_A;
        nor_timing_init_struct.syn_data_latency = 0;
        nor_timing_init_struct.syn_clk_division = 0;
        nor_timing_init_struct.bus_latency = 0;
        nor_timing_init_struct.asyn_data_setuptime = 72;
        nor_timing_init_struct.asyn_address_holdtime = 0;
        nor_timing_init_struct.asyn_address_setuptime = 18;
    
        nor_timing_write_struct.asyn_access_mode = EXMC_ACCESS_MODE_A;
        nor_timing_write_struct.syn_data_latency = 0;
        nor_timing_write_struct.syn_clk_division = 0;
        nor_timing_write_struct.bus_latency = 0;
        nor_timing_write_struct.asyn_data_setuptime = 3;
        nor_timing_write_struct.asyn_address_holdtime = 0;
        nor_timing_write_struct.asyn_address_setuptime = 3;
        /* configure EXMC bus parameters */
        nor_init_struct.norsram_region = EXMC_BANK0_NORSRAM_REGION0;
        nor_init_struct.write_mode = EXMC_ASYN_WRITE;
        nor_init_struct.extended_mode = DISABLE;
        nor_init_struct.asyn_wait = DISABLE;
        nor_init_struct.nwait_signal = DISABLE;
        nor_init_struct.memory_write = ENABLE;
        nor_init_struct.nwait_config = EXMC_NWAIT_CONFIG_BEFORE;
        nor_init_struct.wrap_burst_mode = DISABLE;
        nor_init_struct.nwait_polarity = EXMC_NWAIT_POLARITY_LOW;
        nor_init_struct.burst_mode = DISABLE;
        nor_init_struct.databus_width = EXMC_NOR_DATABUS_WIDTH_16B;
        nor_init_struct.memory_type = EXMC_MEMORY_TYPE_SRAM;
        nor_init_struct.address_data_mux = DISABLE;
        nor_init_struct.read_write_timing = &nor_timing_init_struct;
        nor_init_struct.write_timing = &nor_timing_write_struct;
        exmc_norsram_init(&nor_init_struct);
    
        /* enable the EXMC bank0 NORSRAM */
        exmc_norsram_enable(EXMC_BANK0_NORSRAM_REGION0);
    }
    

    重点需要配置的是两组与屏幕本身相关的参数:

    asyn_data_setuptime = 9;
    asyn_address_holdtime = 2;
    asyn_address_setuptime = 72;

    在上面FSMC读写时序中提到了地址及数据的建立和保持时间,这个值的单位是时钟周期,因为GD32F4工作主频是200M所以当设置为1时,对应的时间为5ns。

    屏幕的这几个值可以在驱动芯片的手册中找到。在ST7796中,对这几个参数的说明如下图:

    根据FSMC模式A读写时序图上标注可以得出。

    其中读模式,RD接的是NOE,数据建立时间为RD为低电平的时间;地址建立时间为RD为高电平的时间。

    写模式,WR接的是NEW,数据建立时间为WR为低电平的时间;地址建立时间为RD高电平时间。

    由于A模式没有地址保持时间,所以地址保持时间设置为0

    由此从上图可以得到

    Adress hold time = 0ns
    READ:address setuptime = 90ns 对应时钟周期为18
    READ:data setuptime = 355ns 对应时钟周期为71
    WRITE:address setuptime = 15ns 对应时钟周期为3
    WRTIE:data setuptime = 15ns 对应时钟周期为3

    需要实现的底层读写函数为:

    #define LCD_BASE        ((uint32_t)(0x60000000 | 0x0001FFFE))
    #define LCD             ((LCD_TypeDef *) LCD_BASE)
    #define LCD_WRITE_REG(value)  (LCD->LCD_REG = value)
    #define LCD_WRITE_DATA(value)  (LCD->LCD_RAM = value)
    #define LCD_READ_DATA()  (LCD->LCD_RAM )
    

    3.7 LCD寄存器配置

    对于LCD控制器,需要重点关注的寄存器如下:

    寄存器地址 名称 作用
    0x3A 像素格式 设置颜色是16 18 还是24bit
    0x36 显示控制 设置GRAM的读写方向,以及屏幕的显示方向
    0x2A 列地址 设置要显示的列地址
    0x2B 行地址 设置要显示的行地址
    0x2C 写GRAM 开始往LCD写像素数据的命令

    对于LCD的初始化命令,一般厂商会给到,但初始化过程中需要修改的主要有0x3A和0x36寄存器。
    具体看下这两个寄存器的内容

    0x3A寄存器

    当我们用到的是565的16位颜色时,0x3A需要设置为0x55。这里被坑过,厂商给的是24为颜色,发现显示出的比较杂乱。排查好久才发现是这里配置错误了。

    0x36寄存器


    这个寄存器主要控制屏幕的显示方向,主要是切换横屏和竖屏。以及调整颜色顺序是RGB还是BGR,一般设置都是BGR。

    当发现屏幕显示方向不对的时候,调整这个寄存器就对了。横竖屏切换只要设置MVbit。这里多试验一下就知道了。

    接下来的0x2A,0x2B,0x2C寄存器就是向屏幕像素点显示颜色的需要操作的寄存器。

    先看下0x2A和0x2B寄存器。



    从上面的图可以看出,0x2A和0x4B寄存器分别需要设置4个参数,起始地址的高8为,起始地址的低8位。结束地址的高8为,结束地址的低8位。

    通过设置这两个寄存器,共同构成了一个矩形坐标区域,就是确定了要在LCD屏幕上显示的区域。

    接下来只需要往0x2C寄存器中写入颜色数值,即可在上述区域内显示,写入的第一个颜色值,显示在(xs,ys)坐标处,第2个颜色值显示在(xs+,ys)坐标处。具体看下0x2c寄存器。

    0x2C寄存器

    该寄存器可以写多个数据,写数据之前要先指定数据要显示在屏幕上的位置。写入的数据为16位颜色。

    基于上面的描述,可以得到,往屏幕上像素点显示颜色的步骤。

    1、设置列(X)方向的起始xs和结束坐标ye
    2、设置行(Y)方向的起始ys和结束坐标ye
    3、向上述的形成的坐标区域内的每个像素点写入颜色值

    对应的代码如下:

    void lcd_color_fill1(uint16_t sx,uint16_t sy,uint16_t ex,uint16_t ey,uint16_t color)
    {  
    	uint16_t height,width;
    	uint32_t i,j;
    	width=ex-sx+1; 			//得到填充的宽度
    	height=ey-sy+1;			//高度
    	LCD_WRITE_REG(0x2A);
    	LCD_WRITE_DATA(sx>>8);
    	LCD_WRITE_DATA(sx&0xFF);
    	LCD_WRITE_DATA(ex>>8);
    	LCD_WRITE_DATA(ex&0xFF);
    	
    	LCD_WRITE_REG(0x2B);
    	LCD_WRITE_DATA(sy>>8);
    	LCD_WRITE_DATA(sy&0xff);
    	LCD_WRITE_DATA(ey>>8);
    	LCD_WRITE_DATA(ey&0xff);
    	
    	LCD_WRITE_REG(0x2c);     //开始写入GRAM
    	for(j=0;j<width*height;j++)
    	LCD_WRITE_DATA(color);//写入数据 	  
    } 
    

    要向某一个点显示颜色,只需要让xs=xe,ys=ye。基于此函数,上层可以继续封装出,划线,显示文字等函数。

    到这里基本上完成了对FSMC控制器以及LCD驱动芯片的原理及代码实现环节。以后碰到了新的mcu或者新的lcd控制器,原理和步骤基本与此一致。

    物联沃分享整理
    物联沃-IOTWORD物联网 » 【深入浅出】单片机驱动8080LCD

    发表评论