Às vezes, o código de outra pessoa realmente ajuda a conectar o ferro periférico ao microcontrolador. Infelizmente, adaptar o código de outra pessoa ao seu projeto pode ser mais difícil do que reescrevê-lo, especialmente quando se trata de megaestruturas como arduino ou mbed. Querendo conectar um LCD chinês baseado em ILI9341 à placa STM32L476G DISCOVERY, o autor decidiu usar o driver escrito para mbed no projeto de demonstração da ST sem alterar uma única linha em seu código. Como resultado, foi possível ao mesmo tempo fazer overclock da tela para velocidades de atualização sem precedentes de 27 fps.
Introdução ao problema
A ST Microelectronics produz microcontroladores muito interessantes, tanto em termos de capacidade e preço, como também lança placas para desenvolvimento rápido. Um deles será discutido - STM32L476G DESCOBERTA . Os recursos de computação desta placa são muito encorajadores - um ARM de 32 bits com uma freqüência de clock máxima de 80 MHz pode executar operações de ponto flutuante. Ao mesmo tempo, é capaz de reduzir ao mínimo o consumo de energia e trabalhar com bateria, aguardando a oportunidade de fazer algo útil. Decidi conectar um LCD colorido chinês barato com uma resolução de 320 por 240 trabalhando na interface SPI para este dispositivo. Como usá-lo com o mbed é descrito em detalhes aqui . Mbed- este é um ambiente de programação on-line onde você pode compilar seu firmware sem ter um compilador no seu computador, após o qual você pode baixá-lo e atualizá-lo simplesmente copiando-o na placa compatível com mbed, que parece um disco removível quando conectado ao USB. Tudo isso é ótimo, mas existem alguns problemas. Em primeiro lugar, nem todas as placas são compatíveis com mbed. Em segundo lugar, existem muitos projetos existentes que não são compatíveis com o mbed, incluindo o software fornecido pela ST. E, finalmente, nem todos os desenvolvedores são compatíveis com o mbed, alguns (por exemplo, o autor dessas linhas) encontram mais desvantagens do que vantagens nesta ferramenta maravilhosa. Quais são essas desvantagens, discutiremos abaixo, por enquanto é suficiente mencionar que, depois de conectar o driver de vídeo para o projeto de demonstração da ST e algumas otimizações simples, ele começou a funcionar cerca de 10 vezes mais rápido.Aprendendo o código do driver
Está na hora de baixar e estudar o código fonte do driver de vídeo. O trabalho com portas no mbed é organizado por meio de chamadas para métodos de classe que representam portas de E / S. Por exemplo, a classe DigitalOut implementa o acesso à porta de saída. Atribuir uma instância desse objeto zero ou um inicia a gravação do bit correspondente na porta de saída. A classe DigitalOut é inicializada pelo tipo enumerado PinName, cujo único objetivo é identificar a perna do processador. Uma das principais desvantagens da implementação do DigitalOut e de outras classes que implementam a E / S é que a porta é inicializada no construtor da instância da classe. Isso é ótimo para piscar um LED se uma instância da classe DigitalOut for criada na pilha na função principal. Mas imagine que temos muito ferro diferente, cuja inicialização está espalhada por vários módulos.Se tornarmos instâncias de nossas classes de E / S estáticas, perderemos todo o controle sobre a inicialização, pois ocorrerá antes da função principal e em uma ordem arbitrária. As bibliotecas ST (chamadas HAL - nível de abstração de hardware) usam um paradigma diferente e mais flexível. Cada porta de entrada / saída possui seu próprio contexto e um conjunto de funções que funcionam com ela, mas elas podem ser chamadas exatamente quando necessário. Os contextos de porta geralmente são criados como variáveis estáticas, mas nenhuma inicialização automática descontrolada ocorre (as bibliotecas ST são escritas em C). Um utilitário extremamente conveniente também merece ser mencionado.As bibliotecas ST (chamadas HAL - nível de abstração de hardware) usam um paradigma diferente e mais flexível. Cada porta de entrada / saída possui seu próprio contexto e um conjunto de funções que funcionam com ela, mas elas podem ser chamadas exatamente quando necessário. Os contextos de porta geralmente são criados como variáveis estáticas, mas nenhuma inicialização automática descontrolada ocorre (as bibliotecas ST são escritas em C). Um utilitário extremamente conveniente também merece ser mencionado.As bibliotecas ST (chamadas HAL - nível de abstração de hardware) usam um paradigma diferente e mais flexível. Cada porta de entrada / saída possui seu próprio contexto e um conjunto de funções que funcionam com ela, mas elas podem ser chamadas exatamente quando necessário. Os contextos de porta geralmente são criados como variáveis estáticas, mas nenhuma inicialização automática descontrolada ocorre (as bibliotecas ST são escritas em C). Um utilitário extremamente conveniente também merece ser mencionado.mas nenhuma inicialização automática descontrolada ocorre neste caso (as bibliotecas ST são escritas em C). Um utilitário extremamente conveniente também merece ser mencionado.mas nenhuma inicialização automática descontrolada ocorre neste caso (as bibliotecas ST são escritas em C). Um utilitário extremamente conveniente também merece ser mencionado.CubeMX , que pode gerar todo o código de inicialização necessário para o conjunto de portas que você precisa e até permite que você faça alterações subseqüentes nesse conjunto de portas sem afetar seu próprio código. Sua única desvantagem é a incapacidade de usar com projetos existentes; você deve iniciar o projeto com o uso deste utilitário.A biblioteca mbed usa as mesmas funções HAL da biblioteca ST para inicializar os recursos do microcontrolador, mas isso a torna incrivelmente impensada em alguns lugares. Para garantir isso, basta olhar para o código de inicialização da porta SPI (que precisamos trabalhar com a exibição) no arquivo spi_api.c. A função spi_init procura primeiro uma porta SPI adequada pelas pernas que ela usará e depois chama a função init_spi, que realmente inicializa a porta. Nesse caso, todas as três portas SPI possíveis usam uma estrutura de contexto estáticastatic SPI_HandleTypeDef SpiHandle;
Este é essencialmente um exemplo clássico de uso de variáveis globais em vez de variáveis locais. Mesmo levando em conta o fato de termos um núcleo de computação, o contexto global não está protegido contra o uso simultâneo em diferentes locais do código, ainda há interrupções e o multitarefa.Conecte a biblioteca ao seu projeto
Então, eu não quero escrever todo o código no mbed. Gosto muito dos exemplos de ST que acompanham o CubeMX . Não encontrei o driver pronto para o meu LCD para as bibliotecas ST, não queria escrever sozinho. Permaneceu uma maneira alternativa de se divertir de alguma forma - para conectar o driver escrito para o mbed, e para que ele não exija que nada seja alterado. Tudo o que você precisa fazer é implementar as bibliotecas mbed de uma maneira alternativa. De fato, a tarefa é mais simples do que parece, devido a todas as bibliotecas mbed, o driver do LCD usa apenas a porta de saída e o SPI. Além disso, ele precisa das funções de formação de atrasos e classes de arquivos e fluxos. Com o último, tudo é simples - não precisamos deles e somos substituídos por plugues que não fazem nada. As funções de geração de atraso são fáceis de escrever - elas estão no arquivowait_api.h . A implementação de classes de E / S requer uma abordagem um pouco mais criativa. Vamos corrigir a falta de bibliotecas mbed e não fazer a inicialização do hardware no construtor. O construtor receberá um link para o contexto da porta localizado em outro local, seu código de inicialização será completamente independente de nossas classes de interface. Existe a única maneira de transferir essas informações para o construtor sem alterar o código do driver - através do PinName, que em vez de simplesmente listar as pernas agora armazenará um ponteiro para a porta, o número da perna e, opcionalmente, um ponteiro para o recurso (como SPI) ao qual essa perna está conectada.class PinName {
public:
PinName() : m_port(0), m_pin(0), m_obj(0) {}
PinName(GPIO_TypeDef* port, unsigned pin, void* obj = 0)
: m_port(port), m_pin(pin), m_obj(obj)
{
assert_param(m_port != 0);
}
GPIO_TypeDef* m_port;
unsigned m_pin;
void* m_obj;
static PinName not_connected;
};
A implementação da porta de saída é bastante trivial. Para melhorar o desempenho, tentaremos usar menos as funções HAL e trabalhar diretamente com os registradores de portas o máximo possível, além de escrever código embutido, o que permitirá ao compilador evitar chamadas de função.class DigitalOut {
public:
DigitalOut(GPIO_TypeDef* port, unsigned pin)
: m_port(port), m_pin(pin)
{
assert_param(m_port != 0);
}
DigitalOut(PinName const& N)
: m_port(N.m_port), m_pin(N.m_pin)
{
assert_param(m_port != 0);
}
void operator =(int bit) {
if (bit) m_port->BSRR = m_pin;
else m_port->BRR = m_pin;
}
private:
GPIO_TypeDef* m_port;
unsigned m_pin;
};
O código de implementação da porta SPI não é muito mais complicado, você pode vê-lo aqui . Como separamos a inicialização da porta do código da interface, ignoramos os pedidos de alterações na configuração. A profundidade dos bits da palavra é simplesmente lembrada. Se o usuário deseja transmitir uma palavra de 16 bits e a porta estiver configurada como 8 bits, basta reorganizar os bytes e transferi-los um a um - até 4 bytes ainda são colocados no buffer da porta. Todos os arquivos necessários para compilar o driver são coletados no diretório compat . Agora você pode conectar os arquivos originais do driver ao projeto e compilá-los. Também precisaremos de um código que inicialize as portas, crie uma instância do driver e desenhe algo significativo na tela.Overclock
Se o LCD for usado para produzir algo dinâmico, existe um desejo natural de tornar a comunicação com ele mais rápida. A primeira coisa que vem à mente é aumentar a freqüência do relógio SPI, que o motorista define em 10MHz, mas ignoramos seus desejos e podemos definir qualquer coisa. Aconteceu que a tela funciona bem e com uma frequência de 40MHz - esta é a frequência máxima que nosso processador com uma frequência de clock de 80MHz é capaz. Para avaliar o desempenho, foi escrito um código simples que exibe um bitmap de 100x100 pixels em um loop. O resultado foi extrapolado para a tela inteira (um bitmap que ocupa a tela inteira simplesmente não cabe na memória). O resultado - 11fps está muito longe do limite teórico de 32fps, obtido se você transferir 16 bits para cada pixel sem parar. O motivo fica claro se você olhar paracódigo fonte do driver . Se ele precisa transmitir uma sequência de bytes, ele simplesmente os transfere um a um, na melhor das hipóteses, com palavras de 16 bits. A razão para esse design ineficiente está na API mbed. A classe SPI possui um método para transmitir uma matriz de dados, mas só pode ser usada de forma assíncrona, chamando a função de notificação após a conclusão e no contexto de um manipulador de interrupção. Não é de surpreender que poucas pessoas usem esse método. Eu completei minha implementação da classe SPI com uma função que envia um buffer e aguarda o final da transferência. Depois de adicionar uma chamada a essa função no código de transferência de bitmap, o desempenho aumentou para 27 fps, o que já está muito próximo do limite teórico.Código fonte
Está aqui . Para compilação, foi utilizado o IAR Embedded Workbench for ARM 7.50.2. Com base no firmware de demonstração de código da ST. Descrição do pino, que está ligado ao LCD pode ser encontrada no arquivo lcd.h .