单片机1622/1621驱动程序编写(较为通用版)

最近在搞LCD,刚好用到1622/1621驱动芯片,网上看了很多,但是实际中还是有很多问题,比如不够说得有点模棱两可,或者比较专用,而且遇到的一些坑也没有详细记录,所以写个笔记记录一下。

一、硬件部分

1、含LCD驱动的单片机

LCD可以直接用单片机或者ic来驱动,但是首先要看的是单片机是否有LCD驱动这个模块,如果没有的话是不能直接把端口连在LCD上,通过端口拉高和拉低来操作LCD,因为LCD的引脚拉高或拉低不是立刻的,而是一个上升和下降的过程。比如复旦微的一款FM33LG048就支持LCD驱动。

2、不含LCD驱动的单片机

还有单片机没有这一个模块,这个时候就需要对每个引脚进行处理或者使用驱动ic,每个引脚单独处理这方面未涉及,所以选的是驱动ic的方式。

3、驱动ic选型

找厂商定制LCD或者是从外面买的现成的LCD,在硬件层面,有几个参数需要注意,一个是供电电压还有一个是偏压和占空比。这里也是容易忽略的地方

1)两种LCD对比

下面是两种LCD

1、LCD1

2、LCD2

常见的LCD驱动有1622和1621,先对比一下相关的手册:

2)两种驱动芯片对比

1)1621

2)1622

3)驱动芯片选型

根据上述LCD的偏置和占空比,可以迅速进行选型:

  • LCD1 选择 1621
  • LCD2 选择 1622
  • 注意: 选型错误可能导致亮度过低和可视角偏移。例如,正常情况下可视角范围为 -45° 到 45°,但使用错误的芯片可能会变为 0° 到 90°,这时需要将LCD抬高或倾斜才能清晰查看。

    4)LCD表格理解

    以8COM口的LCD为例,描述采用tb购买的随机LCD,下面是对应的表格

    这里要结合LCD引脚定义来看

    一般COM口的位置是定好的,1-24可以理解为SEG0-SEG23。

    很多的LCD厂商都是从SEG1开始的,而驱动芯片是从SEG0开始的,这里需要注意一点。

    里面有很多的7F,11C这种,这个要结合下面的图。

    这样就很明显,比如要让第6个8显示4,则6F,6G,6B,6C要点亮。或者要显示电池两个电量,此时就要求电池的外壳S1点亮,以及点亮内部的S2、S3表示电量的块。那么具体怎么驱动就要看后面的驱动部分了。

    4、驱动ic外设电路

    这里以1621为例,下图为推荐电路

    这里只需考虑两个方面:首先,除了将CS、RD和WR三根线连接到单片机外,还需要关注VLCD的上拉电阻VR,这与对比度有关。当VR为0V时,对比度达到最大;增大电阻会导致对比度降低。

    如果对比度最大,即使不驱动,LCD屏幕上可能会显示所有内容;而对比度过低时,即使驱动LCD,屏幕也可能没有任何显示。

    因此,VR部分需要进行调节。例如,在单片机未运行程序的情况下,给整板上电,先将VR短接,观察显示效果(某些LCD在VR短接时,即使对比度最大,也可能不会全显示)。如果屏幕全显示,则可以使用变阻箱逐渐增大VR,直到屏幕的显示刚好消失为止。下图为电路示例。

    二、软件部分

    1、驱动理解

    因为绝大部分情况我们都是考虑写LCD,这里以写模式为例,读模式其实也是同样的理解。

    这里截的图是1622,1621和1622的这一块基本上一样。

    连续写就是在一个地址上写很多数据,我理解是不断变化的情况会用到,最简单的就是“8”那里要不停地变化,比如闹钟或者显示温度。而单次写则是用来表示某种状态,比如打开蓝牙,那么蓝牙图标就变亮,关闭蓝牙就灭,这种情况很适合单次写入。

    几个注意点:

    1、DATA传输命令码是101,后面紧跟的地址码是A5-A0低六位,所以写驱动的时候要先去除掉最高位。

    2、DATA传输1bit数据的时候,WR线刚好是先拉低后拉高,这里也是经常出问题的地方,很容易写成WR_LOW+sendbit+WR_HIGH,这种形式的代码。个人理解是WR一低一高是ic识别协议的方式。

    3、指令形式101 + 8位地址(去除高2位),数据是4位

    4、地址是从高位到低位,而数据却是从低位到高位,这点非常重要。。。。。。。。。。

    根据上面的理解就可以写出相关的驱动伪代码了

    /*
    *   @brief  LCD向指定地址写一次
    *
    *   @param  8位地址地址
    *   @param  8位数据
    *
    *   @retval 无返回值
    */
    void Write_Once(uint16_t address, uint8_t data)
    {
        // 拉低CS端
        CS_LOW;
    
        write_one_bit(address, data);    
    
        // 拉高CS端
        CS_HIGH;       
    }

    但是这样写有几个问题,第一是驱动分为三个部分,头码+地址+数据。如果全部由write_one_bit函数实现,这个函数就会不够通用(其他的操作头码不是101)。解决办法是可以再写一个函数,传递地址和传送的十六进制位数,这样就可以规避这个问题。同时,数据也可以同样的操作。这样函数就变成了:

    // 数据ID码:101*****
    #define LCD_MODE_WRITE 0xa0    
    
    /*
    *   @brief  LCD向指定地址写一次
    *
    *   @param  8位地址地址
    *   @param  4位数据
    *
    *   @retval 无返回值
    */
    void Write_Once(uint16_t address, uint8_t data)
    {
        // 拉低CS端
        CS_LOW;
        
        // 发送数据码ID
        // 截止到3位
        Send_1622_H_To_L(LCD_MODE_WRITE, 3);     // 高到低位传输
        
        // 发送地址
        Send_1622_Address(address, data);        // 高到低位传输
    
        // 发送数据
        // 截止到4位,D0-D3
        Send_1622_L_To_H(data, 4);               // 低到高位传输
    
        // 拉高CS端
        CS_HIGH;       
    }

    地址和数据码分成两个函数就是因为地址是从A5-A0的六位,而头码可以认为是从A7-A5三位。

    /*
    *   @brief  1622从高到低发送
    *
    *   @param  address:地址
    *   @param  num:发送几位
    *
    *   @retval 无返回值
    */
    void Send_1622_H_To_L(uint16_t address, uint8_t num)
    {
        unsigned char i;
        
        // 从高位发送num位
        for (i = 0; i < num; i++)
        {
            if (address & 0x80)
            {
                DATA_HIGH;             // 数据为高
            }
            else
            {
                DATA_LOW;              // 数据为低
            }
            
            WR_LOW;                    // WR拉低
            delay_loop;                // 延迟一段时间
            WR_HIGH;                   // WR拉高
            delay_loop;                // 延迟一段时间
            address = address << 1;    // 去除高位
        }
    }

    从低到高的函数则为

    /*
    *   @brief  1622从低到高发送
    *
    *   @param  address:地址
    *   @param  num:发送几位
    *
    *   @retval 无返回值
    */
    void Send_1622_L_To_H(uint16_t address, uint8_t num)
    {
        unsigned char i;
        
        // 从低位发送num位
        for (i = 0; i < num; i++)
        {
            if (address & 0x01)  // 检查最低位
            {
                DATA_HIGH;       // 数据为高
            }
            else
            {
                DATA_LOW;        // 数据为低
            }
            
            WR_LOW;              // WR拉低
            delay_loop;          // 延迟一段时间
            WR_HIGH;             // WR拉高
            delay_loop;          // 延迟一段时间
            address = address >> 1; // 去除最低位
        }
    }

    2、地址确认

    1)任务

    驱动部分写好的话,接下来就是在地址上面写数据。比如现在要在1号数码管上显示1。

     显示“1”对应的表格为:

    1 2
    COM1 0 0
    COM2 0 1
    COM3 0 0
    COM4 0 1

    2)两种驱动的地址

    HT1621 HT1622

    可以看出对于1621,addr=SEG

    而1622低四位和高四位是分别处理的。COM0-COM3,addr=SEG*2;COM4-COM7,addr=SEG*2+1。

    本次项目以HT1621为例。

    3、写数据

    数据就是对应COM0-COMx的值,回到之前的表格,按照手册的定义,那我们可以知道让第一个数码管显示1,也就是SEG1=0x05(注意图纸是从COM1开始,而驱动ic是从COM0开始。SEG也是同样的道理)。

    1 2
    COM1 0 0
    COM2 0 1
    COM3 0 0
    COM4 0 1

    这样的话就可以直接调用函数Write_Once(1, 0x05)。

    4、手册的其他特殊地址进一步分析

    下面是主要的会用到的,并未截全,其他可在手册上进一步查找。

    有几个必须要开启点是需要注意的。

     

    三、程序总结

    使用HT1621为例

    HT1621.h

    /************************************************************ 
     Copyright (C), 2024, Co., Ltd. 
     FileName:     Ht1621.c 
     Author:       Zhao
     Version :     1.0.0
     Date:         24.12.9
     Description:  HT1621头文件,包含端口初始化及端口定义
        
     Function List: // 主要函数及其功能
     1. ------- 
     History: // 历史修改记录
     Zhao 24/12/9 创建模块 V1.0.0
    ***********************************************************/
    
    #ifndef __HT1621_H__
    #define __HT1621_H__
    
    
    /********************端口定义********************
    // 不同单片机不同
    // 端口定义
    #define PORT_HT1621_DATA    *
    #define PORT_HT1621_CS      *
    #define PORT_HT1621_WR      *
    
    // 引脚定义
    #define PIN_HT1621_DATA    *
    #define PIN_HT1621_CS      *
    #define PIN_HT1621_WR      *
    /***************************************************
    
    /********************端口处理函数********************
    // 端口处理函数
    // 设置指定位置的位为1
    #define setb(p, x) (p |= (1 << (x)))
    
    // 清除指定位置的位(设置为0)
    #define clrb(p, x) (p &= ~(1 << (x)))
    
    // 获取指定位置的位值(返回1或0)
    #define getb(p, x) (p & (1 << (x)))
    
    // 切换指定位置的位(0变1,1变0)
    #define toggleb(p, x) ((p) ^= (1 << (x)))
    /***************************************************
    
    /***********************指令码***********************
    #define HT_1621_Write        0xa0        // LCD写地址
    #define HT_1621_SYS_EN       0x01        // 开启系统晶振
    #define HT_1621_LCD_ON       0x03        // 开启LCD
    #define HT_1621_LCD_OFF      0X02        // 关闭LCD 
    #define HT_1621_BIAS_1/2     0x28        // 4个COM口
    #define HT_1621_BIAS_1/3     0x29        // 4个COM口
    /***************************************************
    
    /***********************HT1621端口*******************
    #define HT1621_DATA_H  setb(PORT_HT1621_DATA, PIN_HT1621_DATA) 
    #define HT1621_DATA_L  clrb(PORT_HT1621_DATA, PIN_HT1621_DATA) 
    #define HT1621_WR_H    setb(PORT_HT1621_WR, PIN_HT1621_WR)  
    #define HT1621_WR_L    clrb(PORT_HT1621_WR, PIN_HT1621_WR)   
    #define HT1621_CS_H    setb(PORT_HT1621_CS, PIN_HT1621_CS)    
    #define HT1621_CS_L    clrb(PORT_HT1621_CS, PIN_HT1621_CS)   
    /***************************************************
    
    /*************************函数表*********************
    extern void HT1621_Send_H_To_L(uint16_t address, uint8_t num);
    extern void HT1621_Send_L_To_H(uint16_t address, uint8_t num);
    extern void HT1621_Send_Address(uint16_t address);
    extern void HT1621_Write_Address(uint16_t address, uint8_t data);
    extern void HT1621_LCD_Init(void);
    /***************************************************
    #endif
    

    HT1621.c

    /************************************************************ 
     Copyright (C), 2024, Co., Ltd. 
     FileName:     Ht1621.c 
     Author:       Zhao
     Version :     1.0.0
     Date:         24.12.9
     Description:  HT1621函数
        
     Function List:
     1. HT1621_Port_Init     // 端口配置函数
     2. HT1621_Send_H_To_L   // HT1621从高到低发送
     3. HT1621_Send_L_To_H   // HT1621从低到高发送
     4. HT1621_Send_Address  // 写入LCD A5-A0六位地址
     5. HT1621_Write_Address // 向指定地址写入
     6. HT1621_LCD_Init      // LCD初始化
     History: 
     Zhao 24/12/9 创建模块 V1.0.0
    ***********************************************************/
    #include "HT1621.h"
    
    /*
    *   @brief  HT1621端口配置
    *   @param  None
    *   @retval 无返回值
    */
    void HT1621_Port_Init(void)
    {
        // 不同单片机不同
        // WR
    
        // CS
    
        // DATA
    }
    
    /*
    *   @brief  HT1621从高到低发送
    *
    *   @param  address:地址
    *   @param  num:发送几位
    *
    *   @retval 无返回值
    */
    void HT1621_Send_H_To_L(uint16_t address, uint8_t num)
    {
        unsigned char i;
        
        // 从高位发送num位
        for (i = 0; i < num; i++)
        {
            if (address & 0x80)
            {
                HT1621_DATA_H;         // 数据为高
            }
            else
            {
                HT1621_DATA_L;         // 数据为低
            }
            
            HT1621_WR_L; ;             // WR拉低
            delay_loop;                // 延迟一段时间
            HT1621_WR_H;               // WR拉高
            delay_loop;                // 延迟一段时间
            address = address << 1;    // 去除高位
        }
    }
    
    /*
    *   @brief  HT1621从低到高发送
    *
    *   @param  address:地址
    *   @param  num:发送几位
    *
    *   @retval 无返回值
    */
    void HT1621_Send_L_To_H(uint16_t address, uint8_t num)
    {
        unsigned char i;
        
        // 从低位发送num位
        for (i = 0; i < num; i++)
        {
            if (address & 0x01)      // 检查最低位
            {
                HT1621_DATA_H;       // 数据为高
            }
            else
            {
                HT1621_DATA_L;       // 数据为低
            }
            
            HT1621_WR_L;             // WR拉低
            delay_loop;              // 延迟一段时间
            HT1621_WR_H;             // WR拉高
            delay_loop;              // 延迟一段时间
            address = address >> 1;  // 去除最低位
        }
    }
    
    
    
    /*
    *   @brief  LCD写入
    *   @param  16位地址
    *   @retval 无返回值
    */
    void HT1621_Send_Address(uint16_t address)
    {
        unsigned char i;
    
        // 地址数据只有低6位,所以要先移掉高两位
        ucAddrValue = ucAddrValue << 2; 
    
        for (i = 0; i < 6; i++)
        {
            if (ucAddrValue & 0x80)
            {
                HT1621_DATA_H;             // 数据为高
            }
            else
            {
                HT1621_DATA_L;             // 数据为低
            }
    
            HT1621_WR_L;                    // WR拉低
            delay_loop;                     // 延时一段时间
            HT1621_WR_H;                    // WR拉高
            delay_loop;                     // 延时一段时间
    
            ucAddrValue = ucAddrValue << 1;
        }
    }
    
    
    /*
    *   @brief  LCD向指定地址写入
    *
    *   @param  16位地址
    *   @param  8位数据
    *
    *   @retval 无返回值
    */
    void HT1621_Write_Address(uint16_t address, uint8_t data)
    {
        // 拉低CS端
        HT1621_CS_L;
        
        // 发送数据码ID
        // 截止到3位
        HT1621_Send_H_To_L(LCD_MODE_WRITE, 3);   // 高到低位传输
        
        // 发送地址
        Send_1622_Address(address, data);        // 高到低位传输
    
        // 发送数据
        // 截止到4位,D0-D3
        HT1621_Send_L_To_H(data, 4);             // 低到高位传输
    
        // 拉高CS端
        HT1621_CS_H;       
    }
    
    
    /*
    *   @brief  LCD初始化
    *   @param  None
    *   @retval 无返回值
    */
    void HT1621_LCD_Init(void)
    {
        // 拉低CS端
        HT1621_CS_L;
    
        SendNBitToRAM(HT_1621_BIAS_1, 3);       // 4个COM口
        SendNBitToRAM(HT_1621_SYS_EN, 9);       // 开启系统晶振
        SendNBitToRAM(HT_1621_LCD_OFF, 9);      // 关闭LCD
    
        // 拉高CS端
        HT1621_CS_H;       
    }

    作者:颖风船

    物联沃分享整理
    物联沃-IOTWORD物联网 » 单片机1622/1621驱动程序编写(较为通用版)

    发表回复