Take the blue pill.
Recentemente me envolvi em um projeto onde preciso utilizar um microcontrolador para efetuar leituras de alguns sensores e acabei optando por utilizar a linha STM32 da ST Electronics.
Para construir a prova de conceito utilizei uma placa conhecida por Blue Pill.
As vantagens são o preço, a facilidade de uso com o IDE Arduino e as especificações:
-Microcontrolador STM32F103C8T6 Cortex-M3 de 32 Bits rodando a 72Mhz;
-20Kb de RAM;
-64Kb de flash;
-4 Timers
-2 SPI’s
-2 I2C’s
-2 USART’s
-até 40 GPIO’s.
Se você está habituado ao IDE do Arduino a maneira mais fácil de começar com o Blue Pill é o projeto stm32duino. Depois de configurado, podemos simplesmente abrir o exemplo Blink e enviar para a memória do dispositivo.
O problema de verdade só começa a aparecer quando adicionamos um pouco mais de código e ultrapassamos a barreira dos 64Kb da memória flash. Este simples exemplo Blink gera um binário de 12Kb, o que para projetos maiores é um desperdício.
Vou utilizar este exemplo para descrever como cheguei a 3Kb com a mesma funcionalidade e durante o processo descrever um pouco este microcontrolador.
1 - ligar o led
2 - aguardar um segundo
3 - desligar o led
4 - aguardar um segundo
5 - repetir o processo
Colocando em termos do microcontrolador, podemos dizer:
1 - ativar o GPIO PC13
2 - "aguardar" por um segundo
3 - desativar o GPIO PC13
4 - "aguardar" por um segundo
5 - repetir o processo
Ligar o led tornou-se ativar GPIO PC13 porque é neste "pino" que o led presente na placa está ligado.
A palavra "aguardar"está entre aspas porque, embora pareça algo trivial, existem diversas formas de efetuar em um microcontrolador.
Como o IDE do Arduino é focado na simplicidade para os iniciantes de eletrônica digital, praticamente inexiste opção de otimização do código.
Lendo o livro Discovering the STM32 MicroController fui apresentado a lib CMSIS, fornecida pelo fabricante do processador. Esta biblioteca tem a intenção de criar uma API de controle comum entre os vários modelos de processadores para reduzir a curva de aprendizado.
podem ser utilizados para qualquer fim desde que se respeite os limites de tensão e corrente do mesmo.
No caso do STM32 a maioria dos pinos suporta nominalmente 3.3v mas alguns são tolerantes a 5v.
Para ativar um GPIO é necessário ativar o barramento ao qual o mesmo está ligado, configurar a velocidade de operação e o modo de operação.
Por modo de operação entendemos entrada, saída ou modo de função alternativa. A função alternativa é o que possibilita que o mesmo pino que é utilizado como GPIO possa ser usado para uma conexão USART ou SPI por exemplo. Nestes modos alternativos o controlador faz uso do pino atendendo a um destes protocolos sem que você precise implementar em código o mesmo.
Um simples loop grande o bastante pode causar um delay de tempo e sabendo quantas instruções o mesmo toma e a velocidade do clock interno podemos calcular quanto tempo se passou. No entanto esta é uma forma ineficiente de delay já que o processador fica ocupado apenas para passar o tempo.
No STM32 temos diversos timers e eles são distribuídos entre os barramentos do microcontrolador.
No manual do mesmo conseguimos a informação da distribuição. No caso, utilizaremos o TIM2, logo será um timer que se encontra no barramento ABP2.
Existem diversos registradores para configurar um timer já que podemos utiliza-los das formas mais diversas. Nos vídeos a seguir tem uma explicação bastante detalhada:
No nosso caso, vamos apenas configurar o timer para que uma interrupção seja gerada a cada segundo.
Sabendo que o barramento ABP1 possui um clock de 72Mhz, precisamos encontrar um divisor (PSC) e valor de contagem (ARR) que nos gere este período.
O divisor é o número pelo qual os pulsos do barramento serão divididos, ou seja, a velocidade do contador. Se dividirmos o clock 72000000 por 720000 teremos 1000 ciclos por segundo ou 1khz.
Colocando o contador para o valor de 999, teremos 1000 ciclos de contagem para ocorrer um overflow e consequentemente nossa interrupção. Se temos 1000 ciclos por segundo e contamos até 1000 para o overflow teremos então 1 overflow por segundo, ou seja, um overflow segundo a segundo.
Não podemos simplesmente utilizar um divisor com o mesmo valor do clock porque o registrador deste divisor é de apenas 16 bits. Existem formas de dividir o clock antes da aplicação do PSC, mas para fins desta explicação estes números são o bastante.
Utilizando o CMSIS podemos ativar nossa interrupção para o TIM2 com apenas uma linha de código e efetuar o tratamento simplesmente criando uma função com a assinatura:
void TIM2_IRQHandler(void);
Toda vez que uma interrupção ocorrer em quaisquer uma das atividades configuradas no TIM2, essa função será chamada.
frustrantemente grande, 25Kb para ser mais exato com otimização no nível 3.
Na tentativa de reduzir mais, resolvi extrair as estruturas necessárias e converter as chamadas do CMSIS para simples manipulações de registradores. O resultado finalmente chegou a meros 3Kb.
Este resultado não é nem de longe o menor possível, mas considerando o tamanho do exemplo compilado no IDE Arduino ficamos com apenas 25% do tamanho original.
Repositório com o código: https://github.com/renatoaquino/stm32blink
Para construir a prova de conceito utilizei uma placa conhecida por Blue Pill.
As vantagens são o preço, a facilidade de uso com o IDE Arduino e as especificações:
-Microcontrolador STM32F103C8T6 Cortex-M3 de 32 Bits rodando a 72Mhz;
-20Kb de RAM;
-64Kb de flash;
-4 Timers
-2 SPI’s
-2 I2C’s
-2 USART’s
-até 40 GPIO’s.
Se você está habituado ao IDE do Arduino a maneira mais fácil de começar com o Blue Pill é o projeto stm32duino. Depois de configurado, podemos simplesmente abrir o exemplo Blink e enviar para a memória do dispositivo.
O problema de verdade só começa a aparecer quando adicionamos um pouco mais de código e ultrapassamos a barreira dos 64Kb da memória flash. Este simples exemplo Blink gera um binário de 12Kb, o que para projetos maiores é um desperdício.
Vou utilizar este exemplo para descrever como cheguei a 3Kb com a mesma funcionalidade e durante o processo descrever um pouco este microcontrolador.
Como diria Jack, vamos por partes.
O que o exemplo faz é:1 - ligar o led
2 - aguardar um segundo
3 - desligar o led
4 - aguardar um segundo
5 - repetir o processo
Colocando em termos do microcontrolador, podemos dizer:
1 - ativar o GPIO PC13
2 - "aguardar" por um segundo
3 - desativar o GPIO PC13
4 - "aguardar" por um segundo
5 - repetir o processo
Ligar o led tornou-se ativar GPIO PC13 porque é neste "pino" que o led presente na placa está ligado.
A palavra "aguardar"está entre aspas porque, embora pareça algo trivial, existem diversas formas de efetuar em um microcontrolador.
Como o IDE do Arduino é focado na simplicidade para os iniciantes de eletrônica digital, praticamente inexiste opção de otimização do código.
Lendo o livro Discovering the STM32 MicroController fui apresentado a lib CMSIS, fornecida pelo fabricante do processador. Esta biblioteca tem a intenção de criar uma API de controle comum entre os vários modelos de processadores para reduzir a curva de aprendizado.
GPIO
GPIO's são os pinos do microcontrolador que podemos controlar programaticamente.podem ser utilizados para qualquer fim desde que se respeite os limites de tensão e corrente do mesmo.
No caso do STM32 a maioria dos pinos suporta nominalmente 3.3v mas alguns são tolerantes a 5v.
Para ativar um GPIO é necessário ativar o barramento ao qual o mesmo está ligado, configurar a velocidade de operação e o modo de operação.
Por modo de operação entendemos entrada, saída ou modo de função alternativa. A função alternativa é o que possibilita que o mesmo pino que é utilizado como GPIO possa ser usado para uma conexão USART ou SPI por exemplo. Nestes modos alternativos o controlador faz uso do pino atendendo a um destes protocolos sem que você precise implementar em código o mesmo.
TIMER
Controlar o tempo é uma necessidade constante quando falamos em microcontroladores. Seja para verificar o estado de um sinal de tempos em tempos, fazer um relógio ou piscar um led necessitamos de uma forma controlada de passar o tempo.Um simples loop grande o bastante pode causar um delay de tempo e sabendo quantas instruções o mesmo toma e a velocidade do clock interno podemos calcular quanto tempo se passou. No entanto esta é uma forma ineficiente de delay já que o processador fica ocupado apenas para passar o tempo.
No STM32 temos diversos timers e eles são distribuídos entre os barramentos do microcontrolador.
No manual do mesmo conseguimos a informação da distribuição. No caso, utilizaremos o TIM2, logo será um timer que se encontra no barramento ABP2.
Existem diversos registradores para configurar um timer já que podemos utiliza-los das formas mais diversas. Nos vídeos a seguir tem uma explicação bastante detalhada:
No nosso caso, vamos apenas configurar o timer para que uma interrupção seja gerada a cada segundo.
Sabendo que o barramento ABP1 possui um clock de 72Mhz, precisamos encontrar um divisor (PSC) e valor de contagem (ARR) que nos gere este período.
O divisor é o número pelo qual os pulsos do barramento serão divididos, ou seja, a velocidade do contador. Se dividirmos o clock 72000000 por 720000 teremos 1000 ciclos por segundo ou 1khz.
Colocando o contador para o valor de 999, teremos 1000 ciclos de contagem para ocorrer um overflow e consequentemente nossa interrupção. Se temos 1000 ciclos por segundo e contamos até 1000 para o overflow teremos então 1 overflow por segundo, ou seja, um overflow segundo a segundo.
Não podemos simplesmente utilizar um divisor com o mesmo valor do clock porque o registrador deste divisor é de apenas 16 bits. Existem formas de dividir o clock antes da aplicação do PSC, mas para fins desta explicação estes números são o bastante.
Interrupções
No STM32 o tratamento de interrupções sempre passa pelo NVIC ou Nested Vectored Interupt Controller. O NVIC é basicamente o controlador de interrupções e entre suas funções estão a ativação e escalonamento por prioridade das interrupções.Utilizando o CMSIS podemos ativar nossa interrupção para o TIM2 com apenas uma linha de código e efetuar o tratamento simplesmente criando uma função com a assinatura:
void TIM2_IRQHandler(void);
Toda vez que uma interrupção ocorrer em quaisquer uma das atividades configuradas no TIM2, essa função será chamada.
Código
Após programar a versão utilizando apenas o CMSIS e compilar com o GCC, o resultado foi algofrustrantemente grande, 25Kb para ser mais exato com otimização no nível 3.
Na tentativa de reduzir mais, resolvi extrair as estruturas necessárias e converter as chamadas do CMSIS para simples manipulações de registradores. O resultado finalmente chegou a meros 3Kb.
Este resultado não é nem de longe o menor possível, mas considerando o tamanho do exemplo compilado no IDE Arduino ficamos com apenas 25% do tamanho original.
Repositório com o código: https://github.com/renatoaquino/stm32blink
Comentários