Inicie a exibição no STM32 via LTDC ... nos registros

Saudações! Recentemente, um projeto precisava iniciar uma exibição que tivesse uma interface LVDS. Para implementar a tarefa, o controlador STM32F746 foi selecionado, porque Já trabalhei bastante com ele e ele possui o módulo LTDC, que permite trabalhar diretamente com a tela sem um controlador. Nesse caso, o controlador já está implementado dentro do microcontrolador. Além disso, o último argumento foi que havia a depuração do STM32F746-Disco nessa pedra, que eu tinha em mãos, o que significa que eu poderia começar a trabalhar no projeto sem esperar a placa, os componentes etc. chegarem até mim.

Hoje vou explicar como executar o módulo LTDC, trabalhando com registradores (CMSIS). O HAL e outras bibliotecas não gostam e não usam por motivos religiosos, mas isso também é interessante. Você verá que o aumento de periféricos complexos nos registros é tão simples quanto o SPI comum. Interessante? Então vamos lá!



1. Um pouco sobre LTDC


Este módulo periférico é essencialmente um controlador, que geralmente fica do lado da tela, por exemplo, SSD1963 e similares. Se observarmos a estrutura do LTDC, veremos que fisicamente este é um barramento paralelo com 24 bits + um acelerador gráfico de hardware + uma matriz de dados na RAM, que na verdade é um buffer de exibição (buffer de quadro).



Na saída, temos um barramento paralelo comum, que contém 24 bits de cor (8 bits por cor do modelo RGB), linhas de sincronização, uma linha on / off de exibição e relógio de pixel. Este último, de fato, é um sinal de relógio pelo qual os pixels são carregados no visor, ou seja, se tivermos uma frequência de 9,5 MHz, em 1 segundo podemos carregar 9,5 milhões de pixels. Na teoria, é claro, na prática, os números são um pouco mais modestos devido a horários e outras coisas.

Para uma introdução mais detalhada ao LTDC, aconselho a ler alguns documentos:

  1. Uma visão geral dos recursos do LTDC em F4, em nosso F7 tudo é igual
  2. Nota de aplicação 4861. "Controlador de display LCD-TFT (LTDC) em MCUs STM32"

2. O que precisamos fazer?


Os microcontroladores ST ganharam popularidade por um bom motivo, o requisito mais importante para qualquer componente eletrônico é a documentação e tudo está bem com isso. O site é certamente terrível, mas vou deixar links para toda a documentação. O fabricante evita o tormento e a invenção da bicicleta, portanto, na página 520 do manual de referência RM0385, são fornecidas etapas em preto e branco, o que precisamos fazer:



Na verdade, você não precisa executar metade do descrito: não é necessário iniciar ou já está configurado por padrão. Para o início mínimo, que permite desenhar pixels, exibir figuras, gráficos, texto etc., basta fazer o seguinte:

  • Ativar relógio LTDC
  • Defina o sistema de relógio e a frequência da saída de dados (pixel clock)
  • Configurar portas de E / S (GPIO) para trabalhar com LTDC
  • Configurar horários para o nosso modelo de exibição
  • Ajuste a polaridade dos sinais. Já feito por padrão
  • Especifique a cor de fundo da tela. Ainda não o vemos, você pode deixá-lo "em zeros"
  • Defina o tamanho real da área visível da tela para uma camada específica
  • Selecione o formato de cor: ARGB8888, RGB 888, RGB565 etc.
  • Especifique o endereço da matriz que atuará como um buffer de quadro
  • Indique a quantidade de dados em uma linha (comprimento em largura)
  • Indique o número de linhas (altura da tela)
  • Inclua a camada com a qual estamos trabalhando
  • Ativar módulo LTDC

Assustador E fiquei com medo, mas acabou funcionando por 20 minutos com todos os procedimentos. Há uma tarefa, o plano é planejado e resta apenas cumpri-lo.

3. Configurando o sistema de relógio


O primeiro item que precisamos enviar um sinal de relógio para o módulo LTDC, é feito escrevendo no registro RCC:

RCC->APB2ENR |= RCC_APB2ENR_LTDCEN; 

Em seguida, você precisa configurar a frequência do relógio do quartzo externo (HSE) para uma frequência de 216 MHz, ou seja, ao máximo. O primeiro passo é ativar a fonte do relógio do ressonador de quartzo e aguardar o sinalizador pronto:

 RCC->CR |= RCC_CR_HSEON; while (!(RCC->CR & RCC_CR_HSERDY)); 

Agora defina o atraso para a memória flash do controlador, como ela não sabe trabalhar na frequência central. Seu valor, como o restante dos dados, é obtido no manual de referência:

 FLASH->ACR |= FLASH_ACR_LATENCY_5WS; 

Agora, para obter a frequência desejada, vou dividir 25 MHz da entrada para 25 e obter 1 MHz. Em seguida, apenas em PLL eu multiplico por 432, porque no futuro, existe um divisor de frequência com um valor mínimo de / 2 e você precisa aplicar o dobro da frequência a ele. Depois disso, conectamos a entrada PLL ao nosso ressonador de quartzo (HSE):

 RCC->PLLCFGR |= RCC_PLLCFGR_PLLM_0 | RCC_PLLCFGR_PLLM_3 | RCC_PLLCFGR_PLLM_4; RCC->PLLCFGR |= RCC_PLLCFGR_PLLN_4 | RCC_PLLCFGR_PLLN_5 | RCC_PLLCFGR_PLLN_7 | RCC_PLLCFGR_PLLN_8; RCC->PLLCFGR |= RCC_PLLCFGR_PLLSRC; 

Agora ative o PLL e aguarde o sinalizador pronto:

 RCC->CR |= RCC_CR_PLLON; while((RCC->CR & RCC_CR_PLLRDY) == 0){} 

Atribuímos a saída do nosso PLL como a fonte da frequência do sistema e aguardamos o sinalizador pronto:

 RCC->CFGR |= RCC_CFGR_SW_PLL; while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_1) {} 

Isso encerra a configuração geral do relógio e passamos a definir a frequência do relógio (PLLSAI) para nossa exibição (pixel clock). O sinal para PLLSAI de acordo com a folha de dados é obtido após o divisor / 25, ou seja, na entrada, temos 1 MHz. Precisamos obter uma frequência de cerca de 9,5 MHz, para isso multiplicamos a frequência de 1 MHz por 192 e, usando dois divisores por 5 e 4, obtemos o valor desejado PLLSAI = 1 MHz * 192/5/4 = 9,6 MHz:

 RCC->PLLSAICFGR |= RCC_PLLSAICFGR_PLLSAIN_6 | RCC_PLLSAICFGR_PLLSAIN_7; RCC->PLLSAICFGR |= RCC_PLLSAICFGR_PLLSAIR_0 | RCC_PLLSAICFGR_PLLSAIR_2; RCC->DCKCFGR1 |= RCC_DCKCFGR1_PLLSAIDIVR_0; RCC->DCKCFGR1 &= ~RCC_DCKCFGR1_PLLSAIDIVR_1; 

Como etapa final, habilitamos o PLLSAI para a exibição e aguardamos o sinalizador pronto para o trabalho:

 RCC->CR |= RCC_CR_PLLSAION; while ((RCC->CR & RCC_CR_PLLSAIRDY) == 0) {} 

Isso completa a configuração básica do sistema de clock, para não esquecer e depois não sofrer, vamos ativar o clock em todas as portas de entrada / saída (GPIO). Não temos energia da bateria, pelo menos para depuração, portanto não economizamos:

 RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN; RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN; RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN; RCC->AHB1ENR |= RCC_AHB1ENR_GPIOEEN; RCC->AHB1ENR |= RCC_AHB1ENR_GPIOFEN; RCC->AHB1ENR |= RCC_AHB1ENR_GPIOGEN; RCC->AHB1ENR |= RCC_AHB1ENR_GPIOHEN; RCC->AHB1ENR |= RCC_AHB1ENR_GPIOJEN; RCC->AHB1ENR |= RCC_AHB1ENR_GPIOKEN; 

4. Configurando portas de E / S (GPIO)


A configuração do gpio é muito simples - temos todas as pernas do barramento LTDC para serem configuradas como uma saída alternativa e em alta frequência. Para fazer isso, no manual de referência na página 201, temos esta dica:



A tabela indica quais bits nos registradores você precisa definir para obter a configuração necessária. Vale a pena notar que todos os aparelhos estão desabilitados. Onde procurar que função alternativa incluir? E para isso, vá para a página 76 na folha de dados do nosso controlador e veja a tabela a seguir:



Como você pode ver, a lógica da tabela é simples: encontramos a função de que precisamos, no nosso caso LTDC B0, depois olhamos para qual GPIO ela está (PE4, por exemplo) e, na parte superior, vemos o número da função alternativa que usaremos para configurar (AF14 conosco). Para configurar nossa saída como uma saída push-pull com uma função alternativa, LTDC B0, precisamos escrever o seguinte código:

 GPIOE->MODER &= ~GPIO_MODER_MODER4; GPIOE->MODER |= GPIO_MODER_MODER4_1; GPIOE->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR4_1; GPIOE->AFR[0] &= ~GPIO_AFRL_AFRL4_0; GPIOE->AFR[0] |= GPIO_AFRL_AFRL4_1 | GPIO_AFRL_AFRL4_2 | GPIO_AFRL_AFRL4_3; 

Dei um exemplo para o pino PE4, que corresponde ao pino B0 no barramento LTDC, ou seja, é um bit zero da cor azul. Para todas as outras conclusões, a configuração é idêntica, apenas duas conclusões merecem atenção especial, uma das prontas inclui uma tela e a outra a luz de fundo. Eles são configurados como uma saída push-pull normal, que todos usam para piscar um LED. A configuração é assim:

 GPIOK->MODER &= ~GPIO_MODER_MODER3; GPIOK->MODER |= GPIO_MODER_MODER3_0; 

Essa configuração é para saída PK3, que liga e desliga nossa luz de fundo. By the way, você também pode empurrá-lo para ajustar suavemente o brilho. Para o PI12, que inclui uma tela (DISP), tudo é igual. A velocidade nesses 2 pinos é baixa por padrão, porque algumas ações de alta frequência não são necessárias a elas.

Você pode observar todas as outras portas de E / S na placa de circuito da placa de depuração ou no diagrama de circuito do seu próprio dispositivo.

5. Tempos e suas configurações


Os tempos do ponto de vista físico são atrasos comuns. Eu acho que você observou repetidamente várias perversões do tipo delay (1) quando analisou exemplos de código em displays com controladores SPI / I2C semelhantes ao ILI9341. Lá, é necessário um atraso para que o controlador, por exemplo, tenha tempo para aceitar o comando, executá-lo e, em seguida, fazer alguma coisa com os dados. No caso do LTDC, tudo é o mesmo, apenas não faremos muletas e por que não - nosso próprio microcontrolador é capaz de configurar os tempos necessários no hardware. Por que eles são necessários em uma tela onde não há controlador? Sim, é fundamental que, depois de preencher a primeira linha de pixels, vá para a próxima linha e retorne ao seu início. Isso se deve à tecnologia de produção de displays e, portanto, cada modelo de display específico possui seus próprios tempos.

Para descobrir quais valores precisamos, acesse o site da ST e consulte o diagrama da placa de depuração STM32F746-Disco . Aí podemos ver que o display é RK043FN48H-CT672B e a documentação para ele está disponível, por exemplo, aqui . Estamos mais interessados ​​na tabela na página 13 na seção 7.3.1:



Esses são nossos valores que precisaremos ao configurar. Também na documentação, há muito mais interessante, por exemplo, diagramas de sinal no barramento e assim por diante, que você pode precisar se, por exemplo, desejar aumentar a exibição para FPGA ou CPLD.

Vá para as configurações. Antes de tudo, para não manter esses valores em minha cabeça, eu os organizarei na forma de define:

 #define DISPLAY_HSYNC ((uint16_t)30) #define DISPLAY_HBP ((uint16_t)13) #define DISPLAY_HFP ((uint16_t)32) #define DISPLAY_VSYNC ((uint16_t)10) #define DISPLAY_VBP ((uint16_t)2) #define DISPLAY_VFP ((uint16_t)2) 

Há uma característica interessante. A largura do pulso de temporização, chamada DISPLAY_HSYNC , possui um valor na tabela apenas para a frequência do clock de pixel de 5 MHz, mas para 9 e 12 MHz não é. Esse tempo precisa ser selecionado para sua exibição, obtive esse valor de 30, quando nos exemplos do ST era diferente. Na primeira partida, se houver um erro na configuração, a imagem será deslocada para a esquerda ou para a direita. Se for para a direita, diminuímos o tempo; se para a esquerda, aumentamos. De fato, afeta a origem da zona visível, que veremos mais adiante. Lembre-se de que a seguinte figura da página 24 do nosso AN4861 ajudará a compreender todo o parágrafo:



Uma pequena abstração é conveniente aqui. Temos 2 zonas de exibição: visível e geral. A zona visível tem dimensões com uma resolução declarada de 480 por 272 pixels, e a zona total é a visível + nossos tempos, dos quais existem 3 em cada lado. Também vale a pena entender (isso não é mais uma abstração) que uma marca do sistema é de 1 pixel, portanto a área total é de 480 pixels + HSYNC + HBP + HFP.

Também vale a pena perceber que quanto menos temporizações, melhor - a tela será atualizada mais rapidamente e a taxa de quadros aumentará um pouco. Portanto, após a primeira execução, experimente os tempos e reduza-os o máximo possível, mantendo a estabilidade.

Para definir os horários, criei uma pequena "folha de dicas" para o futuro dentro do projeto, também ajudará você a entender qual figura específica e onde escrevê-la:

 /* *************************** Timings for TFT display********************************** * * HSW = (DISPLAY_HSYNC - 1) * VSH = (DISPLAY_VSYNC - 1) * AHBP = (DISPLAY_HSYNC + DISPLAY_HBP - 1) * AVBP = (DISPLAY_VSYNC + DISPLAY_VBP - 1) * AAW = (DISPLAY_HEIGHT + DISPLAY_VSYNC + DISPLAY_VBP - 1) * AAH = (DISPLAY_WIDTH + DISPLAY_HSYNC + DISPLAY_HBP - 1) * TOTALW = (DISPLAY_HEIGHT + DISPLAY_VSYNC + DISPLAY_VBP + DISPLAY_VFP - 1) * TOTALH = (DISPLAY_WIDTH + DISPLAY_HSYNC + DISPLAY_HBP + DISPLAY_HFP - 1) * */ 

De onde veio essa "folha de dicas" ... Primeiro, você viu uma "fórmula" semelhante alguns parágrafos antes. Em segundo lugar, vá para a página 56 do nosso AN4861:



É verdade que espero que você entenda o significado físico dos horários antes do aparecimento desta folha de dicas e tenho certeza de que você mesmo poderia compilá-la. Não há nada complicado, e as imagens da RM e da AN ajudam a entender visualmente o efeito dos tempos no processo de formação da imagem.

Agora é hora de escrever um código que configure esses tempos. Na "folha de dicas" são indicados os bits do registro no qual escrever, por exemplo, TOTALH, e após o sinal ser igual à fórmula que fornece à saída um determinado número. Ta bom Então escrevemos:

 LTDC->SSCR |= ((DISPLAY_HSYNC - 1) << 16 | (DISPLAY_VSYNC - 1)); LTDC->BPCR |= ((DISPLAY_HSYNC+DISPLAY_HBP-1) << 16 | (DISPLAY_VSYNC+DISPLAY_VBP-1)); LTDC->AWCR |= ((DISPLAY_WIDTH + DISPLAY_HSYNC + DISPLAY_HBP - 1) << 16 | (DISPLAY_HEIGHT + DISPLAY_VSYNC + DISPLAY_VBP - 1)); LTDC->TWCR |= ((DISPLAY_WIDTH + DISPLAY_HSYNC + DISPLAY_HBP + DISPLAY_HFP -1)<< 16 |(DISPLAY_HEIGHT + DISPLAY_VSYNC + DISPLAY_VBP + DISPLAY_VFP - 1)); 

E isso é tudo com horários! Nesta seção, você pode configurar apenas a cor de fundo. Eu o tenho preto por padrão, então está escrito em zero. Se você deseja alterar a cor da camada de plano de fundo (plano de fundo), também pode escrever qualquer valor, por exemplo, 0xFFFFFFFF e preencher tudo com branco:

 LTDC->BCCR = 0; 

Há uma ilustração maravilhosa no manual de referência que demonstra claramente que na verdade temos 3 camadas: fundo, camada 1 e camada 2. A camada de fundo é "castrada" e só pode ser preenchida com uma cor específica, mas também pode ser incrivelmente útil design futuro da GUI. Além disso, esta ilustração demonstra claramente a prioridade das camadas, o que significa que veremos a cor de preenchimento no plano de fundo somente quando as camadas restantes estiverem vazias ou transparentes.

Como exemplo, mostrarei uma das páginas do projeto, onde durante a implementação do modelo o fundo foi preenchido com uma cor e o controlador não redesenhou a página inteira, mas apenas setores individuais, que permitiram receber de 50 a 60 fps para muitas outras tarefas:



6. A parte final da configuração do LTDC


As configurações do LTDC são divididas em duas seções: a primeira é comum para todo o módulo LTDC e está localizada no grupo de registros LTDC , e a segunda é configurada em uma das duas camadas e está no grupo LTDC_Layer1 e LTDC_Layer2 .

Fizemos as configurações gerais no parágrafo anterior, incluindo a definição dos tempos, a camada de fundo. Agora passamos a definir as camadas e nossa lista requer o tamanho real da zona visível da camada, descrita na forma de 4 coordenadas (x0, y0, x1, y2), que nos permitem obter as dimensões do retângulo. O tamanho da camada visível pode ser menor que a resolução da tela, ninguém se incomoda em fazer o tamanho da camada 100 por 100 pixels. Para ajustar o tamanho da zona visível, escreva o seguinte código:

 LTDC_Layer2->WHPCR |= (((DISPLAY_WIDTH + DISPLAY_HBP + DISPLAY_HSYNC - 1) << 16) | (DISPLAY_HBP + DISPLAY_HSYNC)); LTDC_Layer2->WVPCR |= (((DISPLAY_HEIGHT + DISPLAY_VSYNC + DISPLAY_VBP - 1) << 16) |(DISPLAY_VSYNC + DISPLAY_VBP)); 

Como você pode ver, tudo é como nos horários. Os pontos de partida (x0, y0) da zona visível consistem na soma de dois tempos: HSYNC + HBP e VSYNC + VBP. Para calcular as coordenadas do ponto final (x1, y1), a largura e a altura em pixels são simplesmente adicionadas aos dados do valor.

Agora você precisa configurar o formato dos dados recebidos. A qualidade máxima é obtida ao usar o formato ARGB8888, mas ao mesmo tempo obtemos a quantidade máxima de memória ocupada. Um pixel ocupa 32 bits ou 4 bytes, o que significa que a tela inteira ocupa 4 * 480 * 272 = 522.240 bytes, ou seja, metade da memória flash do nosso controlador não mais fraco. Não tenha medo - conectar a SDRAM externa e a memória Flash via QSPI resolve problemas de memória e não há restrições nesse formato, nós nos alegramos em boa qualidade. Se você deseja economizar espaço ou seu monitor não suporta o formato de 24 bits, modelos mais adequados são usados ​​para isso, por exemplo, RGB565. Um formato muito popular para monitores e câmeras, e mais importante ao usá-lo, 1 pixel leva apenas 5 + 6 + 5 = 16 bits ou 2 bytes. Consequentemente, a quantidade de memória ocupada pela camada será 2 vezes menor. Por padrão, o controlador já possui o formato ARGB8888 configurado e fica assim:

 LTDC_Layer2->PFCR = 0; 

Se você precisar de um formato diferente do ARGB8888, vá para as páginas 533 e 534 no manual de referência e selecione o formato desejado na lista abaixo:



Agora crie uma matriz e passe seu endereço para o LTDC, ele se transformará em um buffer de quadro e será um "reflexo" da nossa camada. Por exemplo, você precisa preencher o 1º pixel na 1ª linha com a cor branca; para isso, basta escrever o valor da cor (0xFFFFFFFF) no primeiro elemento dessa matriz. Precisa preencher o 1º pixel na 2ª linha? Em seguida, também escrevemos o valor da cor no elemento com o número (480 + 1). 480 - faça uma quebra de linha e adicione o número na linha que precisamos.

Essa configuração é assim:

 #define DISPLAY_WIDTH ((uint16_t)480) #define DISPLAY_HEIGHT ((uint16_t)272) const uint32_t imageLayer2[DISPLAY_WIDTH * DISPLAY_HEIGHT]; LTDC_Layer2->CFBAR = (uint32_t)imageLayer2; 

De uma maneira boa, após configurar o LTDC, você também precisará configurar o SDRAM para remover o modificador const e obter o buffer de quadro na RAM, porque A própria RAM do MK não é suficiente, mesmo para uma camada com 4 bytes. Embora isso não faça mal para testar a configuração correta dos periféricos.

Em seguida, é necessário especificar o valor da camada alfa, ou seja, a transparência da camada 2, para isso escrevemos um valor de 0 a 255, onde 0 é uma camada completamente transparente, 255 é totalmente opaco e 100% visível:

 LTDC_Layer2->CACR = 255; 

De acordo com o nosso plano, agora é necessário registrar o tamanho da nossa área de exibição visível em bytes, para isso escrevemos os valores correspondentes nos registros:

 LTDC_Layer2->CFBLR |= (((PIXEL_SIZE * DISPLAY_WIDTH) << 16) | (PIXEL_SIZE * DISPLAY_WIDTH + 3)); LTDC_Layer2->CFBLNR |= DISPLAY_HEIGHT; 

As duas últimas etapas permanecem, a saber, a inclusão da camada 2 e do próprio módulo periférico LTDC. Para fazer isso, escreva os bits correspondentes:

 LTDC_Layer2->CR |= LTDC_LxCR_LEN; LTDC->GCR |= LTDC_GCR_LTDCEN; 

Isso completa a configuração do nosso módulo e você pode trabalhar com a nossa tela!

7. Um pouco sobre como trabalhar com o LTDC


Todo o trabalho com a tela agora se resume apenas à gravação de dados na matriz imageLayer2 , ela tem um tamanho de 480 por 272 elementos, o que corresponde totalmente à nossa resolução e sugere uma verdade simples - 1 elemento da matriz = 1 pixel na tela .

Como exemplo, escrevi uma imagem em uma matriz para a qual converti no LCD Image Converter , mas, na realidade, é improvável que suas tarefas sejam limitadas a isso. Existem duas maneiras: usar uma GUI pronta e escrever você mesmo. Para tarefas relativamente simples, como saída de texto, gráficos e similares, aconselho que você escreva sua própria GUI, isso levará um tempo e dará a você um entendimento completo de sua operação. Quando a tarefa é grande e difícil, e não há tempo para desenvolver sua própria GUI, aconselho que você preste atenção nas soluções prontas, por exemplo, uGFX e similares.

Símbolos de texto, linhas e outros elementos são inerentemente matrizes de pixels; portanto, para implementá-los, é necessário implementar a lógica, mas você deve começar com a função mais básica - "saída de pixel". Deve levar três argumentos: a coordenada ao longo de X, a coordenada ao longo de Y e, consequentemente, a cor na qual o pixel fornecido é pintado. Pode ser assim:

 typedef enum ColorDisplay { RED = 0xFFFF0000, GREEN = 0xFF00FF00, BLUE = 0xFF0000FF, BLACK = 0xFF000000, WHITE = 0xFFFFFFFF } Color; void SetPixel (uint16_t setX, uint16_t setY, Color Color) { uint32_t numBuffer = ((setY - 1) * DISPLAY_WIDTH) + setX; imageLayer2[numBuffer] = Color; } 

Depois que colocamos as coordenadas em uma função, recalculamos-as no número da matriz que corresponde à coordenada especificada e, em seguida, escrevemos a cor recebida no elemento recebido. Com base nessa função, você já pode implementar funções para exibir geometria, texto e outras "vantagens" da GUI. Eu acho que a ideia é compreensível, mas como trazê-la à vida fica a seu critério.

Sumário


Como você pode ver, a implementação de até periféricos complexos em registros (CMSIS) não é uma tarefa difícil, você só precisa entender como ele funciona por dentro. Claro, agora está na moda desenvolver firmware sem entender o que está acontecendo, mas esse é um beco sem saída se você planeja se tornar um engenheiro, e não ...

Se você comparar o código resultante com uma solução no HAL ou SPL, notará que o código escrito nos registros é mais compacto. Adicionando alguns comentários onde você precisar e agrupando-o em funções, obtemos legibilidade pelo menos tão boa quanto a do HAL / SPL, e se você se lembrar de que o manual de referência registra documentos, usar o CMSIS é mais conveniente.

1) O projeto com fontes no TrueSTUDIO pode ser baixado aqui

2) Para quem está mais à vontade olhando o GitHub

3) Baixe o utilitário para converter imagens em código LCD Image Converter aqui

Source: https://habr.com/ru/post/pt412753/


All Articles