普冉PY32系列(六) 通过I2C接口驱动PCF8574扩展的1602LCD

目录

1602 LCD

1602LCD 是工业上常用的模块, 在工厂交通运输设备上经常能见到. 驱动芯片为 HD44780, 1602LCD 的字符显示为两行, 每行16个字符, 字符基于5×8的像素矩阵

PIN脚功能

PinNameFunction
1Ground地 (0V)
2Vcc供电 5V (4.7V – 5.3V), 注意不能用3.3V供电
3Vo / VEE对比度调节. 连接一个可变电阻, 过高无法分辨显示的字符, 过低字符太淡无显示
4RS寄存器选择(Register Select), 低电平为命令寄存器, 高电平为数据寄存器
5Read/write读写选择, 低电平写入, 高电平读取
6EnableEN 闲时处于低电平, 当需要执行指令前几个毫秒将EN拉高, 执行完再拉低
7~14DB0~DB78-bit 数据pin
15Led+LED 背光电源
16Led-LED 背光接地

RS (Register Select)

1602LCD有两组寄存器, 命令寄存器和数据寄存器, RS用于数据和命令寄存器的切换

实际使用时, 需要配合 EN 和 R/W

  • 在 EN = 1, R/W = 0, RS = 1 时, 往数据寄存器写入的字符会用于展示.
  • EN = 1, R/W = 0, RS = 0 时, 往命令寄存器写入, 用于发送指令, 例如: 初始化, 清空屏幕, 设置光标位置, 控制显示等

指令编码

以下是各指令位的说明

/*-------------------------------------------------------------
*   Instruction     D7  D6  D5  D4  D3  D2  D1  D0             
*   ==============================================             
*   Display clear   0   0   0   0   0   0   0   1              
*   Cursor home     0   0   0   0   0   0   1   *              
*   Entry Mode Set  0   0   0   0   0   1  I/D  S              
*   Display On/Off  0   0   0   0   1   D   C   B              
*   Curs/Disp shift 0   0   0   1  S/C R/L  *   *              
*   Function Set    0   0   1   DL  N   F   *   *              
*   CG RAM addr set 0   1   ---------Acg---------              
*   DD RAM addr set 1   -------------Add---------              
*                                                              
*   Meaning:                                                   
*   *     - nonvalid bit                                       
*   Acg   - CG RAM address (CHARACTER GENERATOR)               
*   Add   - DD RAM address (DATA DISPLAY)                      
*   AC    - adress counter                                     
*                                                              
*   I/D   - 1-increment, 0-decrement                           
*   S     - 1-display shift, 0-no display shift                
*   D     - 1-display ON, 0-display OFF                        
*   C     - 1-cursor ON, 0-cursor OFF                          
*   B     - 1-blink ON, 0-blink OFF                            
*   S/C   - 1-display shift, 0-cursor movement                 
*   R/L   - 1-right shift, 0-left shift                        
*   DL    - 1-8 bits data transfer, 0-4 bits data transfer     
*   N     - 1-1/16 duty, 0-1/8 or 1/11 duty                    
*   F     - 1-5x10 dot matrix, 0-5x7 dot matrix                
*   BF    - 1-internal operation in progress, 0-display ready  
*                                                              
\**************************************************************/

配合 EN = 1, R/W = 0, RS = 0 时, 往命令寄存器写入, 可以执行以下指令

No.HexBinary命令说明
1010000 0001清除显示
2020000 001x光标回原位
3040000 0100向左移动光标(两个bit分别控制方向左右, 光标还是屏幕)
4060000 0110向右移动光标
5050000 0101向右移显示
6070000 0111向左移动显示
7080000 1000显示关闭, 光标关闭(三个bit分别控制屏幕显示, 光标显示, 光标闪烁)
80A0000 1010显示关闭, 光标打开
90C0000 1100显示打开, 光标关闭
100E0000 1110显示打开, 光标闪烁
110F0000 1111显示打开, 光标闪烁
12100001 00xx将光标位置向左移动(两个bit分别控制光标还是屏幕, 左移还是右移)
13140001 01xx将光标位置向右移动
14180001 10xx将整个显示屏向左移动
151C0001 11xx将整个显示屏向右移动
16280010 10xx4位, 2行, 5x8矩阵(三个bit分别控制4位还是8位,一行还是两行,5x10还是5x8)
17200010 00xx4位, 1行, 5x8矩阵
18380011 10xx8位, 2行, 5×8
194x01xx xxxx设置 CGRAM 地址, 后面6个bit是地址, 在这个指令之后发送或接收数据
208x1xxx xxxx设置 DDRAM 地址, 后面7个bit是地址, 在这个指令之后发送或接收数据
21801000 0000将光标强制移动到开头(第一行)
22C01100 0000将光标强制移动到开头(第二行)

显示自定义字符

自定义字符要在 CG-RAM 中设置. CG-RAM地址从 0x40 开始到 0x7F 共 64 byte, 可以创建8个字符, 每个字符8个byte. 在这些地址创建字符后, 就可以在LCD中显示.

CG-RAM 地址和命令

No.Addr.Command
00x400
10x481
20x502
30x583
40x604
50x685
60x706
70x787

上面的表中可以看到每个字符的起始地址及其打印命令, 例如第一个字符的地址是 [0x40, 0x47], 使用命令0可以输出这个字符, 第二个字符的地址[0x48, 0x55], 使用命令1输出.

自定义字符时, 每个字符是5x8的点阵, 5是列数, 8是行数. 对应字母b的点阵可以表示为

char b[7] = {0x10,0x10,0x16,0x19,0x11,0x11,0x1E};

将其发送到对应的地址就可以创建字符.

I2C 接口 PCF8574 扩展板

因为1602LCD本身刷新率不高, 为节省IO, 可以通过 PCF8574 扩展模块, 将I2C协议转为并口输出. 1602屏直连MCU, 需要至少7个IO(RS, R/W, EN, 4位对应4根数据线)才能驱动起来, 使用 PCF8574 模块只需要2个IO口.

PCF8574 是一个IIC协议的IO口扩展芯片, 包含一个8位准双向口, 一个总线接口, 还有三条地址线. 每个IO口可以单独的分配为输入或者输出. 作为输入时, 可以用于监控中断或者键盘. 作为输出时, 可以用于驱动LED. 可以通过单独的寄存器读取输入端口状态或者配置输出端口状态. PCF8574 的三个地址管脚, 可以分配8个地址, 也就是同一个系统中可以最多存在8个这样的模块.

PCF8574 电流消耗很低, 并且输出锁存, 具有大电流驱动能力, 可直接驱动LED. 还带一个中断接线, 可以作为MCU的外部中断. 通过中断通知MCU 是否有数据输入, 因此 PCF8574 也可以作为一个单被控器.

通过 PCF8574 控制 1602LCD, 可以将命令发送到 I2C 接口, PCF8574 的输出与 1602LCD 的连接为

[tu]

VSS =  Ground
VDD =  Connects to VCC on the I2C header
VO  =  Display contrast.  Connects to the potentiometer on the module
RS  =  P0 on PCF8574
RW  =  P1 on PCF8574
E   =  P2 on PCF8574
D0 – D3 no connects
D4  =  P4 on PCF8574
D5  =  P5 on PCF8574
D6  =  P6 on PCF8574
D7  =  P7 on PCF8574
A   =  Backlight Anode.  Typically connects to 5V.
K   =  Backlight Cathode.  Connects to ground

基于 PY32F0 的演示

硬件

  • 已焊接 PCF8574 扩展模块的 1602LCD
  • 基于 PY32F0 系列的开发板
  • USB2TTL 用于观察地址输出

接线

大多数 PY32F0 都有 PF1/PF0, 可以通过复用将I2C接口换成其他的pin脚.

注意 PCF8574+1602LCD 供电电压为5V, 3.3V供电无法驱动字符显示. 如果 PY32F0 使用 3.3V 供电, 则需要另接5V电源, 与 PY32F0 共地即可.

PY32          PCF8574 1602 LCD       USB2TTL
 PF1/PA9       SCL
 PF0/PA10      SDA
               VCC    ->  5V
 GND           GND    ->  GND        GND
 PA2                                 RX
 PA3                                 TX

软件

以下的实现基于LL库, 完整的示例代码位于

https://github.com/IOsetting/py32f0-template/tree/main/Examples/LL/I2C/PCF8574_1602LCD

指令和地址的宏定义

/* I2C address
 * - 7 bit slave address, left aligned, bits 7:1 are used, LSB bit is not used
 * - 0x4E or 0x7E
*/
#define LCD1602_I2C_ADDR  0x7E
/* Delay in millisecond */
#define LCD1602_DELAY 5
/* Register selection */
#define PIN_RS    (1 << 0)
/* Read/Write */
#define PIN_RW    (1 << 1)
/* Chip enable */
#define PIN_EN    (1 << 2)
/* Back light - might not be available on some PCF8574 modules */
#define BACKLIGHT (1 << 3)

/* Clear display */
#define LCD1602_CMD_CLEAR_DISPLAY               0b00000001
/* Move cursor home */
#define LCD1602_CMD_HOME                        0b00000010

// Entry Mode, Set cursor/display moving direction
#define LCD1602_CMD_DIRECTION_RIGHT             0b00000110
#define LCD1602_CMD_DIRECTION_LEFT              0b00000100
#define LCD1602_CMD_DIRECTION_RIGHT_SHIFT       0b00000111
#define LCD1602_CMD_DIRECTION_LEFT_SHIFT        0b00000101
// Display mode
#define LCD1602_CMD_MODE_OFF                    0b00001000
#define LCD1602_CMD_MODE_ON_CURSOR_OFF          0b00001100
#define LCD1602_CMD_MODE_ON_CURSOR_ON           0b00001110
#define LCD1602_CMD_MODE_ON_CURSOR_BLNK         0b00001111
// Cursor/Display Shift
#define LCD1602_CMD_CURSOR_MOVE_LEFT            0b00010000
#define LCD1602_CMD_CURSOR_MOVE_RIGHT           0b00010100
#define LCD1602_CMD_DISPLAY_SHIFT_LEFT          0b00011000
#define LCD1602_CMD_DISPLAY_SHIFT_RIGHT         0b00011100

/* Function set: 4-bit, 1 row, 5X8 matrix */
#define LCD1602_CMD_FUNC_4B_1L_5X8              0b00100000
/* Function set: 4-bit, 2 row, 5X8 matrix */
#define LCD1602_CMD_FUNC_4B_2L_5X8              0b00101000
/* Function set: 8-bit, 1 row, 5X8 matrix */
#define LCD1602_CMD_FUNC_8B_1L_5X8              0b00110000
/* Function set: 8-bit, 2 row, 5X8 matrix */
#define LCD1602_CMD_FUNC_8B_2L_5X8              0b00111000
/* Set/Read CGRAM address */
#define LCD1602_CMD_CGRAM_ADDR                  0b01000000
/* Set/Read DDRAM address */
#define LCD1602_CMD_DDRAM_ADDR                  0b10000000

/* First row address */
#define LCD1602_DDRAM_ROW0                      0b10000000
/* Second row address */
#define LCD1602_DDRAM_ROW1                      0b11000000

基础方法

发送指令, 数据和文本

ErrorStatus LCD_SendInternal(uint8_t lcd_addr, uint8_t data, uint8_t flags)
{
  ErrorStatus status;
  for(;;)
  {
    status = BSP_I2C_IsDeviceReady(lcd_addr, 5000);
    if(status == SUCCESS)
    {
      break;
    }
  }

  uint8_t up = data & 0xF0;
  uint8_t lo = (data << 4) & 0xF0;

  uint8_t data_arr[4];
  data_arr[0] = up|flags|BACKLIGHT|PIN_EN;
  data_arr[1] = up|flags|BACKLIGHT;
  data_arr[2] = lo|flags|BACKLIGHT|PIN_EN;
  data_arr[3] = lo|flags|BACKLIGHT;

  status = BSP_I2C_MasterTransmit(lcd_addr, data_arr, sizeof(data_arr), 5000);
  LL_mDelay(LCD1602_DELAY);
  return status;
}

void LCD_SendCommand(uint8_t lcd_addr, uint8_t cmd)
{
  LCD_SendInternal(lcd_addr, cmd, 0);
}

void LCD_SendData(uint8_t lcd_addr, uint8_t data)
{
  LCD_SendInternal(lcd_addr, data, PIN_RS);
}

void LCD_SendString(uint8_t lcd_addr, char *str)
{
  while (*str)
  {
    LCD_SendData(lcd_addr, (uint8_t)(*str));
    str++;
  }
}

初始化设置

void LCD_Init(uint8_t lcd_addr)
{
  // need at least 40ms after power rises above 2.7V
  LL_mDelay(50);
  // start in 8-bit mode, 3 commands
  LCD_SendCommand(lcd_addr, LCD1602_CMD_FUNC_8B_1L_5X8);
  LCD_SendCommand(lcd_addr, LCD1602_CMD_FUNC_8B_1L_5X8);
  LCD_SendCommand(lcd_addr, LCD1602_CMD_FUNC_8B_1L_5X8);
  // set it to 4-bit mode, interface is still 8-bit
  LCD_SendCommand(lcd_addr, LCD1602_CMD_FUNC_4B_1L_5X8);

  // now interface is 4-bit, set it to 2 lines and 5x8 font
  LCD_SendCommand(lcd_addr, LCD1602_CMD_FUNC_4B_2L_5X8);
  // display & cursor home
  LCD_SendCommand(lcd_addr, LCD1602_CMD_HOME);
  // display on, right shift, underline off, blink off
  LCD_SendCommand(lcd_addr, LCD1602_CMD_MODE_ON_CURSOR_BLNK);
  // move direction right
  LCD_SendCommand(lcd_addr, LCD1602_CMD_DIRECTION_RIGHT);
  // clear display (optional here)
  LCD_SendCommand(lcd_addr, LCD1602_CMD_CLEAR_DISPLAY);
}

功能操作

清除屏幕

LCD_SendCommand(LCD1602_I2C_ADDR, LCD1602_CMD_CLEAR_DISPLAY);

移动光标, 输出文字

// move cursor to 0,0
LCD_SendCommand(LCD1602_I2C_ADDR, LCD1602_DDRAM_ROW0|0);
LCD_SendString(LCD1602_I2C_ADDR, " Using 1602 LCD");
// move cursor to 1,0
LCD_SendCommand(LCD1602_I2C_ADDR, LCD1602_DDRAM_ROW1|0);
LCD_SendString(LCD1602_I2C_ADDR, "  over I2C bus");

通过 CGRAM 设置 自定义字符

// CGRAM test
for (i = 0; i < 8; i++)
{
  LCD_SetCGRAM(LCD1602_I2C_ADDR, i, &cgrom[i * 8]);
}

展示自定义字符

for (i = 0; i < 8; i++)
{
  LCD_SendData(LCD1602_I2C_ADDR, i);
  LL_mDelay(200);
}

显示整体左右平移

// Shift display test
LCD_SendCommand(LCD1602_I2C_ADDR, LCD1602_CMD_CLEAR_DISPLAY);
LL_mDelay(500);
LCD_SendCommand(LCD1602_I2C_ADDR, LCD1602_DDRAM_ROW0|9);
LCD_SendString(LCD1602_I2C_ADDR, "Shift");
LCD_SendCommand(LCD1602_I2C_ADDR, LCD1602_DDRAM_ROW1|8);
LCD_SendString(LCD1602_I2C_ADDR, "<<<->>>");
LL_mDelay(500);
for (i = 0; i < 8; i++)
{
  LCD_SendCommand(LCD1602_I2C_ADDR, LCD1602_CMD_DISPLAY_SHIFT_LEFT);
  LL_mDelay(200);
}
LL_mDelay(500);
for (i = 0; i < 8; i++)
{
  LCD_SendCommand(LCD1602_I2C_ADDR, LCD1602_CMD_DISPLAY_SHIFT_RIGHT);
  LL_mDelay(200);
}

左右移动光标

// Move cursor test
for (i = 0; i < 11; i++)
{
  LCD_SendCommand(LCD1602_I2C_ADDR, LCD1602_CMD_CURSOR_MOVE_LEFT);
  LL_mDelay(200);
}
LL_mDelay(500);
for (i = 0; i < 12; i++)
{
  LCD_SendCommand(LCD1602_I2C_ADDR, LCD1602_CMD_CURSOR_MOVE_RIGHT);
  LL_mDelay(200);
}

常见问题

1. 屏幕不显示

不显示的原因有很多, 如果确认代码和接线无误, 可能的原因有

  1. 检查1602LCD的供电电压是不是5V, 在3.3V下无法驱动, 只有背光没有字符
  2. 检查I2C地址是否正确. 查看串口扫描到的实际的设备I2C地址, 是否和程序中的地址一致, 通常情况下, PCF8574T 的地址是 0x4E, PCF8574AT 的地址是 0x7E

2. 字符显示乱码

HD44780对启动的指令顺序和延时是有要求的, 可以参考其数据手册的P45, 如果延时不够或指令顺序不正确, 会导致屏幕未进入4-bit模式而导致显示错乱. 对于部分屏幕, 启动时需要增大延时, 如果等待时间不足, 会导致输出乱码.

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值