我们是STM32的朋友,在I2C总线上带有LCD显示屏1604(HAL库)

哈Ha!

在本文中,我想谈谈我使用I2C总线上的HAL库将LCD显示器连接到STM32微控制器的经验。

图片

我将连接显示器1602和2004。它们都具有基于PCF8574T芯片的焊接I2C适配器。调试板为Nucleo767ZI,开发环境为STM32CubeIDE 1.3.0。

我不会详细讨论I2C总线的工作原理,建议您在这里这里查看

我们创建项目,选择调试板:我们

图片

指示我们将使用I2C1。我还将连接UART5以与开发板通信,这对于从开发板接收有关显示地址的信息是必需的。

图片

图片

在同一窗口中,您可以看到与显示器连接的支脚的编号,在我的情况下,结果是这样的:

图片

要启动,仅连接一个显示器,我将从1602开始。此外,我将连接经验丰富的arduin驱动程序已知的USB-UART CH340适配器,以从板上接收数据。

图片

请注意,适配器将RX连接到TX,将TX连接到RX,适配器上的跳线为3.3V

图片

让我们仔细研究一下PCF8574T芯片和显示器。下面是带有显示屏的模块的示意图:

图片

PCF8574T芯片的功能类似于移位寄存器74hc595-它通过I2C接口接收字节并将相应位的值分配给其输出(P0-P7)。

考虑一下微电路的哪些引脚连接到显示器以及它们负责什么:

  • 微电路的端子P0连接到显示器的RS端子,该RS端子负责接收显示数据(1)或显示操作指令(0);
  • 如果1-将数据写入显示器,0-读取,则引脚P1连接到R \ W。
  • 端子P2连接到CS-端子,正在读取状态变化;
  • 结论P3-背光控制;
  • 结论P4-P7用于将数据传输到显示器。

多个设备可以同时连接到一条I2C总线。为了能够访问特定的设备,每个设备都有其自己的地址,首先我们将发现它们。如果转接板上的触点A1,A2和A3没有密封,则地址很可能为0x27,但最好检查一下。为此,我们将编写一个小的函数,该函数将显示连接到I2C总线的所有设备的地址:

void I2C_Scan ()
{
	//  ,  
        HAL_StatusTypeDef res;                          
	//    
        char info[] = "Scanning I2C bus...\r\n";      
        //    UART	
        HAL_UART_Transmit(&huart5, (uint8_t*)info, strlen(info), HAL_MAX_DELAY);  
	/* &huart5 -   UART
	 * (uint8_t*)info -     
	 * strlen(info) -   
	 * HAL_MAX_DELAY - 
	 */
        //    
	for(uint16_t i = 0; i < 128; i++)              
	{
            // ,      i  
            res = HAL_I2C_IsDeviceReady(&hi2c1, i << 1, 1, HAL_MAX_DELAY);                   
	    //  , 
            if(res == HAL_OK)                              
	    {
	    	char msg[64];
	    	//   i,   ,     
                // 16 :
	    	snprintf(msg, sizeof(msg), "0x%02X", i);
	    	//    
	    	HAL_UART_Transmit(&huart5, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY);
	    	//    
	    	HAL_UART_Transmit(&huart5, (uint8_t*)"\r\n", 2, HAL_MAX_DELAY);
	    }
	    else HAL_UART_Transmit(&huart5, (uint8_t*)".", 1, HAL_MAX_DELAY);
	}
	HAL_UART_Transmit(&huart5, (uint8_t*)"\r\n", 2, HAL_MAX_DELAY);
}

该函数轮询从0到127的所有地址,如果从该地址接收到响应,它将以十六进制形式将地址号发送到UART。

为了与董事会沟通,我使用了白蚁程序。默认情况下,微控制器的UART速度设置为115200,必须在白蚁中设置相同的速度。我们在程序的主体中调用该函数,使电路板闪烁,并以白蚁方式连接到我们的微控制器:

图片

点显示所有未收到答案的地址。由于我焊接了跳线A0,因此显示器上的地址为0x26。现在,将第二个显示器与第一个显示器并行连接,并查看程序将提供什么:

图片

我们有两个地址:0x26(显示1602)和0x27(显示2004)。现在介绍如何使用显示器。微控制器发送一个地址字节,连接到总线的所有设备都用自己的地址字节进行检查。如果匹配,则模块开始与微控制器通信。首先,您需要配置显示:字符计数的来源和方向,光标的行为等。之后,已经可以将用于显示的信息传输到显示器。唯一的特点是我们只能使用4位来传输信息,即数据必须分为两部分。数据存储在高位(4-7)中,低位用于指示背光是否将打开(3位),数据是要输出还是显示设置(RS输出,0位)和2位,通过阅读发生的变化,即e要发送1个字节的数据,您需要发送4个字节-第一个字节将包含4位信息,第二个字节为状态1,第二个字节是第1个字节的重复,仅第二个字节为状态0。第3个和第4个字节相似,仅包含数据的后半部分。这听起来有点不可理解,我将向您展示一个示例:

void I2C_send(uint8_t data, uint8_t flags)
{
	HAL_StatusTypeDef res;
	 //     
        for(;;) {
                // ,      lcd_addr                                                                                        
	        res = HAL_I2C_IsDeviceReady(&hi2c1, LCD_ADDR, 1, HAL_MAX_DELAY);               
	         //  ,     
                if(res == HAL_OK) break;                                                  
	    }
        //    1111 0000      0  3,    4  7
	uint8_t up = data & 0xF0;   
        //   ,  data   4                	
        uint8_t lo = (data << 4) & 0xF0;          
                                          
	uint8_t data_arr[4];
         // 4-7   ,  0-3   
	data_arr[0] = up|flags|BACKLIGHT|PIN_EN; 
         //  ,       0  
	data_arr[1] = up|flags|BACKLIGHT;         
	data_arr[2] = lo|flags|BACKLIGHT|PIN_EN;
	data_arr[3] = lo|flags|BACKLIGHT;

	HAL_I2C_Master_Transmit(&hi2c1, LCD_ADDR, data_arr, sizeof(data_arr), HAL_MAX_DELAY);
	HAL_Delay(LCD_DELAY_MS);
}

让我们按顺序进行。开头有存储显示地址的变量,以及每次必须与数据一起发送的设置位。在发送功能中,我们首先检查记录的地址是否有模块。在接收到HAL_OK消息的情况下,我们开始形成用于发送的字节。在我们将要发送的字节的开头,有必要将其分为两部分,并将它们都写入高位。假设我们要让显示器显示字符“ s”,在二进制系统中为1110011(计算器)使用逻辑运算&我们将= 01110000写入变量,即只写最高有效位。低位开始时向左移动4个字符,然后写入变量lo =00110000。接下来,我们形成一个4字节的数组,其中包含有关要显示的字符的信息。现在,我们将配置位(0-3位)分配给现有字节。之后,我们使用HAL_I2C_Master_Transmit()函数将地址字节和4个字节的信息发送到显示器;

但不要急于下载程序,因为一开始您需要设置显示设置。该站点具有出色的翻译表,其中包含用于自定义显示的命令。与文档一起检查后,我得出了最适合自己的以下设置:

  I2C_send(0b00110000,0);   // 8  
  I2C_send(0b00000010,0);   //     
  I2C_send(0b00001100,0);   //   ,  
  I2C_send(0b00000001,0);   //  

我们将这些命令放在无限循环的开始之前,以便在开始工作之前将设置发送一次(如arduinki中的void设置)。除字节外,I2C_send函数还要求您指定是否发送显示设置或数据。如果函数的第二个参数为0,则为设置,如果为1,则为数据。

最后一点-您需要一个函数,该函数将按字符发送截止日期。这里的一切都很简单:

void LCD_SendString(char *str)
{
    // *char    
    //    
	while(*str) 
        {                                   
                //    
		I2C_send((uint8_t)(*str), 1); 
                //     1               
                str++;                                     
        }
}

收集了所有这些功能后,您可以编写:

  LCD_SendString("  Hello");
  I2C_send(0b11000000,0);   //  
  LCD_SendString("    Habr");

图片

好了,我们找到了现在是2004年的1602显示器。它们之间的差异很小,即使该代码也能正常工作。所有的不同归结于组织显示器上单元格的地址。在两个显示屏中,内存均包含80个单元,在1602显示屏中,前16个单元负责第一行,而40至56个单元负责第二行,其余的存储单元均不显示,因此,如果您向显示屏发送17个字符,则最后一个将不被传输在第二行上,并将被记录在没有输出到显示器的存储单元中。更清楚一点,存储器的组织方式如下:

图片

要使用换行,我使用了I2C_send命令(0b11000000,0);,它只剩下40个单元格。 2004年的展览更加有趣。

第一行是单元格1到20。
第二行是单元格40到60
第三行-从21到40
的单元格第四行-从60到80的单元格,
如果您发送命令

LCD_SendString("___________________1___________________2___________________3___________________4");

我们得到以下信息:

图片

要组织各行之间的过渡,必须将光标手动移至所需的存储单元,或者可以通过编程方式补充该功能。到目前为止,我已经决定使用手动版本:

  I2C_send(0b10000000,0);   //   1 
  LCD_SendString("  Hello Habr");
  I2C_send(0b11000000,0);   //   2 
  LCD_SendString(" STM32 + LCD 1602");
  I2C_send(0b10010100,0);   //   3 
  LCD_SendString(" +LCD 2004A");
  I2C_send(0b11010100,0);   //   4 
  LCD_SendString(" library HAL");

结果:

图片

这些显示可能就是所有这些,有用的链接使我得以弄清一切:

  1. 代码在这里看起来很多
  2. 显示配置表在这里
  3. 程序在这里看了

程序和数据表

PS:不要忘了预先调整显示屏的亮度。

All Articles