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.
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:
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.
Na mesma janela, você pode ver o número de pernas às quais o monitor está conectado, no meu caso ficou assim:
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.
Observe que o adaptador conecta RX a TX e TX a RX, o jumper no adaptador é de 3,3V
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:
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";
HAL_UART_Transmit(&huart5, (uint8_t*)info, strlen(info), HAL_MAX_DELAY);
for(uint16_t i = 0; i < 128; i++)
{
res = HAL_I2C_IsDeviceReady(&hi2c1, i << 1, 1, HAL_MAX_DELAY);
if(res == HAL_OK)
{
char msg[64];
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:
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á:
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(;;) {
res = HAL_I2C_IsDeviceReady(&hi2c1, LCD_ADDR, 1, HAL_MAX_DELAY);
if(res == HAL_OK) 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;
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);
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)
{
while(*str)
{
I2C_send((uint8_t)(*str), 1);
str++;
}
}
Depois de reunir todas essas funções, você pode escrever: LCD_SendString(" Hello");
I2C_send(0b11000000,0);
LCD_SendString(" Habr");
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:
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 20A segunda linha são as células 40 a 60A terceira linha - células de 21 a 40A quarta linha - células de 60 a 80,isto é , se você enviar um comandoLCD_SendString("___________________1___________________2___________________3___________________4");
Temos o seguinte:
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);
LCD_SendString(" Hello Habr");
I2C_send(0b11000000,0);
LCD_SendString(" STM32 + LCD 1602");
I2C_send(0b10010100,0);
LCD_SendString(" +LCD 2004A");
I2C_send(0b11010100,0);
LCD_SendString(" library HAL");
Resultado:
Provavelmente é tudo com essas telas, links úteis, graças aos quais eu consegui descobrir tudo:- O código parecia muito aqui
- Tabelas para configuração de exibição aqui
- O procedimento foi analisado aqui
Programa e folha de dadosPS: não se esqueça de ajustar o brilho da tela com antecedência.