Somos amigos STM32 com display LCD 1604 no barramento I2C (biblioteca HAL)

Olá Habr!

Neste artigo, gostaria de falar sobre minha experiência em conectar monitores LCD ao microcontrolador STM32 usando a biblioteca HAL no barramento I2C.

imagem

Vou conectar o monitor 1602 e 2004. Ambos têm um adaptador I2C soldado, baseado no chip PCF8574T. A placa de depuração será Nucleo767ZI e o ambiente de desenvolvimento será o STM32CubeIDE 1.3.0.

Não vou falar detalhadamente sobre o princípio de operação do barramento I2C, aconselho você a procurar aqui e aqui .

Criamos o projeto, selecionamos o quadro de depuração:

imagem

Indicamos que usaremos I2C1. Também conectarei o UART5 para se comunicar com o quadro, isso é necessário para receber informações do quadro sobre o endereço de exibição.

imagem

imagem

Na mesma janela, você pode ver o número de pernas às quais o monitor está conectado, no meu caso ficou assim:

imagem

Para iniciar, conecte apenas um monitor, inicio com 1602. Além disso, conectarei o adaptador USB-UART CH340, conhecido por drivers de arduino experientes, para receber dados da placa.

imagem

Observe que o adaptador conecta RX a TX e TX a RX, o jumper no adaptador é de 3,3V

imagem

Vamos dar uma olhada em como trabalhar com o chip e a tela PCF8574T. Abaixo está um diagrama esquemático de um módulo com um display:

imagem

O chip PCF8574T é semelhante em função ao registrador de deslocamento 74hc595 - recebe bytes via interface I2C e atribui os valores do bit correspondente às suas saídas (P0-P7).

Considere quais pinos do microcircuito estão conectados ao monitor e o que eles são responsáveis:

  • O terminal P0 do microcircuito é conectado ao terminal RS do display, responsável por receber os dados do display (1) ou instruções de operação do display (0);
  • O pino P1 está conectado a R \ W, se 1 - escreve dados no display, 0 - lê;
  • O terminal P2 está conectado ao CS - o terminal, quando a mudança de estado está sendo lida;
  • Conclusão P3 - controle da luz de fundo;
  • Conclusões P4 - P7 são usadas para transmitir dados para o display.

Vários dispositivos podem ser conectados a um barramento I2C ao mesmo tempo. Para poder acessar um dispositivo específico, cada um deles tem seu próprio endereço, primeiro descobriremos. Se os contatos A1, A2 e A3 na placa adaptadora não estiverem lacrados, o endereço provavelmente será 0x27, mas é melhor verificar. Para fazer isso, escreveremos uma pequena função que mostrará os endereços de todos os dispositivos conectados ao barramento 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);
}

Essa função pesquisa todos os endereços de 0 a 127 e, se uma resposta for recebida desse endereço, envia o número do endereço em formato hexadecimal para o UART.

Para me comunicar com o conselho, uso o programa Termite. Por padrão, a velocidade UART do microcontrolador é definida como 115200, você deve definir o mesmo em cupins. Chamamos a função no corpo principal do programa, exibimos a placa e conectamos os cupins ao microcontrolador:

imagem

Pontos mostram todos os endereços dos quais a resposta não foi recebida. O endereço no meu visor é 0x26, desde que soldei o jumper A0. Agora conecte o segundo monitor paralelo ao primeiro e veja o que o programa dará:

imagem

Temos dois endereços: 0x26 (exibição 1602) e 0x27 (exibição 2004). Agora, sobre como trabalhar com a tela. O microcontrolador envia um byte de endereço e todos os dispositivos conectados ao barramento o verificam por conta própria. Se corresponder, o módulo inicia a comunicação com o microcontrolador. Primeiro de tudo, você precisa configurar a exibição: de onde irá a contagem de caracteres e em qual direção, como o cursor se comportará, etc. Depois disso, já será possível transmitir informações para exibição no display. A peculiaridade é que podemos usar apenas 4 bits para transmitir informações, ou seja, os dados devem ser divididos em duas partes. Os dados são armazenados nos bits altos (4-7) e os bits baixos são usados ​​para indicar se a luz de fundo será ligada (3 bits), se os dados estão chegando para a saída ou as configurações de exibição (saída RS, 0 bits) e 2 bits, pela mudança em que a leitura ocorre, ou seja,e para enviar 1 byte de dados, você precisa enviar 4 bytes - o primeiro byte conterá 4 bits de informação, o segundo bit para o estado 1, o segundo byte é uma repetição do primeiro, apenas o segundo bit para o estado 0. O terceiro e o quarto bytes são semelhantes, apenas eles contêm segunda metade dos dados. Parece um pouco incompreensível, vou mostrar um exemplo:

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);
}

Vamos tomá-lo em ordem. No início, existem variáveis ​​que armazenam o endereço de exibição e bits de configurações que devem ser enviadas a cada vez junto com os dados. Na função de envio, verificamos primeiro se existe um módulo no endereço gravado. No caso de receber a mensagem HAL_OK, começamos a formar bytes para envio. No início do byte que enviaremos, é necessário dividir em duas partes, escrever as duas nos bits mais altos. Suponha que queremos que o display exiba o caractere 's', no sistema binário é 1110011 ( calculadora) Usando a operação lógica e escrevemos = 01110000 na variável, ou seja, escreva apenas os bits mais significativos. Os bits de ordem inferior são deslocados para a esquerda por 4 caracteres no início e, em seguida, são gravados na variável lo = 00110000. Em seguida, formamos uma matriz de 4 bytes que contém informações sobre o caractere a ser exibido. Agora, atribuímos bits de configuração (0-3 bits) aos bytes existentes. Depois disso, enviamos o byte do endereço e 4 bytes de informações para o display usando a função HAL_I2C_Master_Transmit ();

Mas não se apresse para baixar o programa, porque no início você precisa definir as configurações de exibição. O site possui uma excelente tabela traduzida com comandos para personalizar a exibição. Depois de verificar com a documentação, cheguei às seguintes configurações ideais para mim:

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

Colocamos esses comandos antes do início de um loop infinito, para que as configurações sejam enviadas uma vez antes do início do trabalho (como a configuração nula no arduinki). A função I2C_send, além do byte, requer que você especifique se as configurações ou dados da tela serão enviados. Se o segundo argumento da função for 0, as configurações e, se 1, os dados.

E o último toque - você precisa de uma função que envie um prazo de caractere por caractere. Tudo é bem simples aqui:

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

Depois de reunir todas essas funções, você pode escrever:

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

imagem

Bem, descobrimos a tela 1602, agora em 2004. A diferença entre eles é mínima, mesmo esse código funcionará bem. Toda a diferença se resume em organizar os endereços das células no visor. Em ambos os monitores, a memória contém 80 células, no 1602, as 16 primeiras são responsáveis ​​pela primeira linha e as células 40 a 56 são responsáveis ​​pela segunda linha. As células restantes não são exibidas, portanto, se você enviar 17 caracteres para a tela, a última não será transferida. na segunda linha e será gravada em uma célula de memória que não possui saída para a tela. Um pouco mais claramente, a memória está organizada da seguinte forma:

imagem

Para usar o feed de linha, usei o comando I2C_send (0b11000000,0); , apenas vai para 40 células. A exibição de 2004 é mais interessante.

A primeira linha são as células 1 a 20
A segunda linha são as células 40 a 60
A terceira linha - células de 21 a 40
A quarta linha - células de 60 a 80,
isto é , se você enviar um comando

LCD_SendString("___________________1___________________2___________________3___________________4");

Temos o seguinte:

imagem

Para organizar transições entre linhas, você deve mover manualmente o cursor para a célula de memória desejada ou pode complementar programaticamente a função. Até agora, resolvi a versão manual:

  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");

Resultado:

imagem

Provavelmente é tudo com essas telas, links úteis, graças aos quais eu consegui descobrir tudo:

  1. O código parecia muito aqui
  2. Tabelas para configuração de exibição aqui
  3. O procedimento foi analisado aqui

Programa e folha de dados

PS: não se esqueça de ajustar o brilho da tela com antecedência.

All Articles