STM32F1与STM32CubeIDE快速入门-I2C驱动LCD1602显示屏(基于PCF8574)

I2C驱动LCD1602显示屏(基于PCF8574)

LCD1602液晶显示器是广泛使用的一种字符型液晶显示模块。它是由字符型液晶显示屏(LCD)、控制驱动主电路HD44780及其扩展驱动电路HD44100,以及少量电阻、电容元件和结构件等装配在PCB板上而组成。

PCF8574/74A 通过两线双向 I2C 总线(串行时钟 (SCL)、串行数据 (SDA))提供通用远程 I/O 扩展。

PCF8574/74A包括八个准双向端口、100 kHz I2C 总线接口、三个硬件地址输入和中断输出在 2.5 V 和 6 V 之间运行。准双向端口可以独立指定为输入以监控中断状态或键盘,或作为输出以激活 LED 等指示设备。 系统主机可以通过单个寄存器从输入端口读取或写入输出端口。
在这里插入图片描述

关于PCF8574 I2C驱动LCD1602相关文章,请参考:

本次实例将实现PCF8574驱动LCD1602,以节省宝贵的IO。

1、LCD1602-I2C配置

STM32CubeIDE创建工程、系统配置、调试配置,在这里不再做介绍,请参考:

PCF8574配置如下:

在这里插入图片描述

保存配置,并生成代码。

2、LCD1602-I2C驱动实现

2.1 基本定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
#ifndef STM32\_LCD\_I2C\_H
#define STM32\_LCD\_I2C\_H

/\* C++ detection \*/
#ifdef \_\_cplusplus
extern "C" {
#endif

#include "stm32f1xx\_hal.h"
#define DELAY\_MS(x) DWT\_Delay\_ms(x)

// commands
#define LCD\_CLEARDISPLAY 0x01
#define LCD\_RETURNHOME 0x02
#define LCD\_ENTRYMODESET 0x04
#define LCD\_DISPLAYCONTROL 0x08
#define LCD\_CURSORSHIFT 0x10
#define LCD\_FUNCTIONSET 0x20
#define LCD\_SETCGRAMADDR 0x40
#define LCD\_SETDDRAMADDR 0x80

// flags for display entry mode
#define LCD\_ENTRYRIGHT 0x00
#define LCD\_ENTRYLEFT 0x02
#define LCD\_ENTRYSHIFTINCREMENT 0x01
#define LCD\_ENTRYSHIFTDECREMENT 0x00

// flags for display on/off control
#define LCD\_DISPLAYON 0x04
#define LCD\_DISPLAYOFF 0x00
#define LCD\_CURSORON 0x02
#define LCD\_CURSOROFF 0x00
#define LCD\_BLINKON 0x01
#define LCD\_BLINKOFF 0x00

// flags for display/cursor shift
#define LCD\_DISPLAYMOVE 0x08
#define LCD\_CURSORMOVE 0x00
#define LCD\_MOVERIGHT 0x04
#define LCD\_MOVELEFT 0x00

// flags for function set
#define LCD\_8BITMODE 0x10
#define LCD\_4BITMODE 0x00
#define LCD\_2LINE 0x08
#define LCD\_1LINE 0x00
#define LCD\_5x10DOTS 0x04
#define LCD\_5x8DOTS 0x00

// flags for backlight control
#define LCD\_BACKLIGHT 0x08
#define LCD\_NOBACKLIGHT 0x00

#define BIT\_EN 0b00000100 // Enable bit
#define BIT\_RW 0b00000010 // Read/Write bit
#define BIT\_RS 0b00000001 // Register select bit

typedef struct {
I2C_HandleTypeDef \*hi2c;
uint8\_t Addr;
uint8\_t displaycontrol;
uint8\_t displaymode;
uint8\_t backlight;
uint8\_t numlines;
uint8\_t cols;
uint8\_t rows;
} LCD_I2C_t;


/\*\*
\* @brief Display init
\* @par \*lcd\_i2c - Pointer to @ref LCD\_I2C\_t working lcd\_i2c struct
\* @par addr - address of the display. It could be get in I2C\_Scan
\* @par cols - width of the display
\* @par rows - height of the display
\*/
void LCD\_Init(LCD_I2C_t \*lcd_i2c, I2C_HandleTypeDef \*, uint8\_t addr, uint8\_t cols, uint8\_t rows);

/\*\*
\* Set cursor to the left top corner.
\* @par \*lcd\_i2c - Pointer to @ref LCD\_I2C\_t working lcd\_i2c struct
\*/
void LCD\_Home(LCD_I2C_t \*lcd_i2c);

/\*\*
\* Clear the display and set cursor to the left top corner.
\* @par \*lcd\_i2c - Pointer to @ref LCD\_I2C\_t working lcd\_i2c struct
\*/
void LCD\_Clear(LCD_I2C_t \*lcd_i2c);

/\*\*
\* Turn the backlight on and off
\* @par \*lcd\_i2c - Pointer to @ref LCD\_I2C\_t working lcd\_i2c struct
\* @par on - 1 - turn on, 0 - turn off
\*/
void LCD\_Backlight(LCD_I2C_t \*lcd_i2c, uint8\_t on);

/\*\*
\* Turn blinking cursor on and off
\* @par \*lcd\_i2c - Pointer to @ref LCD\_I2C\_t working lcd\_i2c struct
\* @par on - 1 - turn on, 0 - turn off
\*/
void LCD\_Blink(LCD_I2C_t \*lcd_i2c, uint8\_t on);

/\*\*
\* Turn underline cursor on and off
\* @par \*lcd\_i2c - Pointer to @ref LCD\_I2C\_t working lcd\_i2c struct
\* @par on - 1 - turn on, 0 - turn off
\*/
void LCD\_Cursor(LCD_I2C_t \*lcd_i2c, uint8\_t on);

/\*\*
\* Set cursor position
\* @par \*lcd\_i2c - Pointer to @ref LCD\_I2C\_t working lcd\_i2c struct
\* @par col - column
\* @par row - row
\*/
void LCD\_SetCursor(LCD_I2C_t \*lcd_i2c, uint8\_t col, uint8\_t row);

/\*\*
\* Display string
\* @par \*lcd\_i2c - Pointer to @ref LCD\_I2C\_t working lcd\_i2c struct
\* @par str - pointer to string
\*/
void LCD\_SendString(LCD_I2C_t \*lcd_i2c, const char \*str);

#ifdef \_\_cplusplus
}
#endif

#endif

2.2 驱动实现

1)LCD1602命令、数据发送

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include "LCD\_i2c.h"
//Prototypes
uint8\_t LCD\_command(LCD_I2C_t \*lcd_i2c, uint8\_t command);
uint8\_t LCD\_send(LCD_I2C_t \*lcd_i2c, uint8\_t data, uint8\_t flags);
/\*\*
\* 发送命令
\* @par \*lcd\_i2c - 指向 LCD\_I2C\_t的结构的指针
\* @par command - 命令
\* @retval 1 - 成功, 0 - 错误,设备无响应
\*/
uint8\_t LCD\_command(LCD_I2C_t \*lcd_i2c, uint8\_t command)
{
return LCD\_send(lcd_i2c, command, 0);
}

/\*\*
\* 发送数据
\* @par cd\_i2c - 指向 LCD\_I2C\_t的结构的指针
\* @par data - 数据
\* @par flags - 1 - 表示发送数据; 0 - 表示发送命令
\* @return 1 - success, 0 - error divice not responding
\*/
uint8\_t LCD\_send(LCD_I2C_t \*lcd_i2c, uint8\_t data, uint8\_t flags)
{
if (lcd_i2c->hi2c == NULL) return 0;

HAL_StatusTypeDef res;

// 检查设备是否准备好
res = HAL\_I2C\_IsDeviceReady(lcd_i2c->hi2c, lcd_i2c->Addr, 10, HAL_MAX_DELAY);
// 失败
if(res != HAL_OK) return 0;

// 将字节拆分为上下部分
uint8\_t up = data & 0xF0; //上部分
uint8\_t lo = (data << 4) & 0xF0; // 下部分

uint8\_t data_arr[4];
// 4-7 位包含信息,0-3 位包含配置
data_arr[0] = up|flags|lcd_i2c->backlight|BIT_EN;
// 信号重复,此时在引脚 E 处为 0
// 再次发送,这次 IT 为零
data_arr[1] = up|flags|lcd_i2c->backlight;

data_arr[2] = lo|flags|lcd_i2c->backlight|BIT_EN;
data_arr[3] = lo|flags|lcd_i2c->backlight;

// I2C主机发送数据
res = HAL\_I2C\_Master\_Transmit(lcd_i2c->hi2c, lcd_i2c->Addr, data_arr, sizeof(data_arr), HAL_MAX_DELAY);

DELAY\_MS(5);

if (res == HAL_OK) return 1;
else return 0;
}

2)LCD1602初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void LCD\_Init(LCD_I2C_t \*lcd_i2c, I2C_HandleTypeDef \*handle, uint8\_t addr, uint8\_t cols, uint8\_t rows)
{
lcd_i2c->hi2c = handle;

lcd_i2c->Addr = (addr << 1); //correct address
lcd_i2c->cols = cols;
lcd_i2c->rows = rows;
lcd_i2c->numlines = rows;
lcd_i2c->backlight = LCD_NOBACKLIGHT;
lcd_i2c->displaycontrol = LCD_CURSOROFF|LCD_BLINKOFF;

DELAY\_MS(50); // 等待 >40ms

LCD\_command(lcd_i2c, LCD_FUNCTIONSET|LCD_8BITMODE); // 8位接口
DELAY\_MS(5);

LCD\_command(lcd_i2c, LCD_FUNCTIONSET|LCD_8BITMODE); // 8位接口
DELAY\_MS(120);

LCD\_command(lcd_i2c, LCD_FUNCTIONSET|LCD_4BITMODE); // 4位接口

LCD\_command(lcd_i2c, LCD_FUNCTIONSET|LCD_4BITMODE|LCD_5x8DOTS|LCD_2LINE);

LCD\_command(lcd_i2c, LCD_DISPLAYCONTROL|LCD_DISPLAYOFF|LCD_CURSOROFF|LCD_BLINKOFF);

LCD\_command(lcd_i2c, LCD_CLEARDISPLAY); // 清屏
DELAY\_MS(2);

LCD\_command(lcd_i2c, LCD_ENTRYMODESET|LCD_ENTRYLEFT|LCD_ENTRYSHIFTDECREMENT);

LCD\_command(lcd_i2c, LCD_DISPLAYCONTROL|LCD_DISPLAYON|lcd_i2c->displaycontrol);
}

2.3 功能实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
void LCD\_Home(LCD_I2C_t \*lcd_i2c)
{
LCD\_command(lcd_i2c, LCD_RETURNHOME); // 将光标定位在行首
DELAY\_MS(2);
}

void LCD\_Clear(LCD_I2C_t \*lcd_i2c)
{
// 发送清屏命令
LCD\_command(lcd_i2c, LCD_CLEARDISPLAY);
DELAY\_MS(2);
}

void LCD\_Backlight(LCD_I2C_t \*lcd_i2c, uint8\_t backlight)
{
if (backlight) lcd_i2c->backlight = LCD_BACKLIGHT;
else lcd_i2c->backlight = 0;
LCD\_command(lcd_i2c, 0);
}

void LCD\_Blink(LCD_I2C_t \*lcd_i2c, uint8\_t on)
{
if (on) {
lcd_i2c->displaycontrol |= LCD_BLINKON;
} else {
lcd_i2c->displaycontrol &= ~LCD_BLINKON;
}
LCD\_command(lcd_i2c, LCD_DISPLAYCONTROL|LCD_DISPLAYON|lcd_i2c->displaycontrol);
}

void LCD\_Cursor(LCD_I2C_t \*lcd_i2c, uint8\_t cur)
{
if (cur) {
lcd_i2c->displaycontrol |= LCD_CURSORON;
} else {
lcd_i2c->displaycontrol &= ~LCD_CURSORON;
}
LCD\_command(lcd_i2c, LCD_DISPLAYCONTROL|LCD_DISPLAYON|lcd_i2c->displaycontrol);
}

void LCD\_SetCursor(LCD_I2C_t \*lcd_i2c, uint8\_t col, uint8\_t row)
{
if ((row+1)>lcd_i2c->rows) return;

int row_offsets[] = { 0x00, 0x40, 0x14, 0x54 };
if ( row > (4-1) ) {
row = 4 - 1; // 从 w/0 开始计算行数
}
LCD\_command(lcd_i2c, LCD_SETDDRAMADDR | (col + row_offsets[row]));
}

void LCD\_SendString(LCD_I2C_t \*lcd_i2c, const char \*str)
{
while(\*str && LCD\_send(lcd_i2c, (uint8\_t)(\*str), 1)) {
str++;
}
}

2.4 基于DWT延时实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#ifndef DWT\_DELAY\_H\_
#define DWT\_DELAY\_H\_

#include "stm32f1xx\_hal.h"


uint32\_t DWT\_Delay\_Init(void);


// 此函数使用 DWT 提供以微秒为单位的延迟
__STATIC_INLINE void DWT\_Delay\_us(volatile uint32\_t au32_microseconds)
{
uint32\_t au32_initial_ticks = DWT->CYCCNT;
uint32\_t au32_ticks = (HAL\_RCC\_GetHCLKFreq() / 1000000);
au32_microseconds \*= au32_ticks;
while ((DWT->CYCCNT - au32_initial_ticks) < au32_microseconds-au32_ticks);
}

// 此函数使用 DWT 提供以毫秒为单位的延迟
__STATIC_INLINE void DWT\_Delay\_ms(volatile uint32\_t au32_milliseconds)
{
uint32\_t au32_initial_ticks = DWT->CYCCNT;
uint32\_t au32_ticks = (HAL\_RCC\_GetHCLKFreq() / 1000);
au32_milliseconds \*= au32_ticks;
while ((DWT->CYCCNT - au32_initial_ticks) < au32_milliseconds);
}

#endif /\* DWT\_DELAY\_H\_ \*/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
uint32\_t DWT\_Delay\_Init(void)
{
/\* Disable TRC \*/
CoreDebug->DEMCR &= ~CoreDebug_DEMCR_TRCENA_Msk; // ~0x01000000;
/\* Enable TRC \*/
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; // 0x01000000;

/\* Disable clock cycle counter \*/
DWT->CTRL &= ~DWT_CTRL_CYCCNTENA_Msk; //~0x00000001;
/\* Enable clock cycle counter \*/
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; //0x00000001;

/\* Reset the clock cycle counter value \*/
DWT->CYCCNT = 0;

/\* 3 NO OPERATION instructions \*/
__ASM volatile ("NOP");
__ASM volatile ("NOP");
__ASM volatile ("NOP");

/\* Check if clock cycle counter has started \*/
if(DWT->CYCCNT)
{
return 0; /\*clock cycle counter started\*/
}
else
{
return 1; /\*clock cycle counter not started\*/
}
}

3、主程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/\* Includes ------------------------------------------------------------------\*/
#include "main.h"
#include "i2c.h"
#include "usart.h"
#include "gpio.h"

/\* Private includes ----------------------------------------------------------\*/
/\* USER CODE BEGIN Includes \*/
#include <LCD1602\_I2C/LCD\_i2c.h>
#include <stdio.h>
/\* USER CODE END Includes \*/

/\* Private user code ---------------------------------------------------------\*/
/\* USER CODE BEGIN 0 \*/
LCD_I2C_t lcd;
/\* USER CODE END 0 \*/

int main(void) {
/\* USER CODE BEGIN 1 \*/

/\* USER CODE END 1 \*/

/\* MCU Configuration--------------------------------------------------------\*/

/\* Reset of all peripherals, Initializes the Flash interface and the Systick. \*/
HAL\_Init();

/\* USER CODE BEGIN Init \*/

/\* USER CODE END Init \*/

/\* Configure the system clock \*/
SystemClock\_Config();

/\* USER CODE BEGIN SysInit \*/

/\* USER CODE END SysInit \*/

/\* Initialize all configured peripherals \*/
MX\_GPIO\_Init();
MX\_I2C1\_Init();
MX\_USART1\_UART\_Init();
/\* USER CODE BEGIN 2 \*/
// 延时初始化
DWT\_Delay\_Init();
// LCD1602初始化
LCD\_Init(&lcd, &hi2c1, 0x27, 16, 2);
// 打开背光
LCD\_Backlight(&lcd, 1);
//LCD\_Clear(&lcd);
// 显示字符串
LCD\_SendString(&lcd, "Hello World!");

char msg[10] = { 0 };
uint16\_t counter = 0;
/\* USER CODE END 2 \*/

/\* Infinite loop \*/
/\* USER CODE BEGIN WHILE \*/
while (1) {
/\* USER CODE END WHILE \*/

/\* USER CODE BEGIN 3 \*/
LCD\_SetCursor(&lcd, 0, 1);
sprintf(msg, "%d ", counter++);
LCD\_SendString(&lcd, msg); // 显示字符串
DWT\_Delay\_ms(300);
}
/\* USER CODE END 3 \*/
}

4、运行结果

在这里插入图片描述

文章来源: https://iotsmart.blog.csdn.net/article/details/123607110