
Olá pessoal! Como passaram por este ‘começo’ de ano? 🙈
Continuando a nossa série de artigos, hoje vamos elaborar o primeiro driver do nosso projeto, o driver de GPIO. Lembrando que na verdade não é um mas sim dois drivers, um para o STM32F10x e outro para o KL25!

Até então falamos apenas sobre a arquitetura Inter e terminamos de preparar o nosso repositório. Vamos agora falar sobre os drivers e após terminarmos, poderemos entrar na tão aguardada arquitetura Intra!
O approach dos nossos artigos irá mudar um pouco, porque o conteúdo agora é mais prático. Vamos ter menos texto e vamos ter mais código… se prepare, gitHub! 😎
Atualizações no repositório!
Nosso repositório está carregado com todas as novidades para este artigo. As mudanças estão distribuídas em diversos commits, cada um contendo um pequeno grupo de entrega. Na última entrega coloquei a tag para este artigo, Inter_Intra_#4.1!
O que temos de novo:
- Os drivers GPIO totalmente implementados.
- Testes unitários para eles, rodando e passando.
- Algumas pequenas novidades em alguns helpers.
Para a galera que está estudando o conteúdo mais minuciosamente, recomendo que clonem o repositório (caso ainda não o fizeram 🙃), estudem os arquivos, rodem os testes unitários, tentem entender o que foi feito e porque foi feito de tal maneira.
Quanto à galera que está apenas acompanhando os artigos, sem problemas, eu vou estar aqui passando pelos principais pontos dessas implementações e explicando os aspectos mais importantes! 👍
Vamos começar?
Nosso setup e as nossas necessidades
Vamos recapitular o que precisamos que os nossos drivers de GPIO façam.
Primeiramente, ainda não elaborei nada sobre o nosso setup do projeto, eu apenas mostrei algumas fotos no primeiro artigo. Deixe-me corrigir isso…
No diagrama acima estão identificados todos os pinos que vamos utilizar e quais funções de IO precisaremos de cada um deles. Basicamente utilizaremos os LEDs das próprias placas e apenas vamos colocar dois botões externos.
Com isso podemos ver que, no escopo dos GPIOs, vamos precisar de:
- Pino de LED, uma saída digital que pode ser nível alto ou baixo.
- Pino de botão, uma entrada digital com um pull-up habilitado.
- Pino de terra, uma saída digital, em nível baixo.
Relembrando a nossa filosofia de desenvolvimento: implementamos apenas o mínimo necessário, mas sempre sobre uma arquitetura que permite a adição de novas funcionalidades no futuro. 👍
SDKs são importantes!
Os drivers devem evitar ao máximo manipular diretamente os periféricos do microcontrolador mas sim utilizar de recursos fornecido pelo fabricante – um SDK, por exemplo. Vamos fazer exatamente isto aqui:
- A STM fornece um ‘STM32F1xx_HAL_Driver‘.
- A NXP possui um HAL, chamado por ela de FSL (aparentemente é uma abreviação de ‘Freescale’ mas divago👀)
As grandes vantagens de ir por este caminho:
- Conseguimos colocar o driver para funcionar sem precisar saber e entender tanto do microcontrolador.
- Os SDKs são feitas por equipes que manjam bastante dos seus dispositivos e já investiram boas horas trabalhando com eles. Assim, estas bibliotecas podem já contornar vários problemas conhecidos, itens de erratas, etc.
- Reduzimos bastante a ‘escovação de bits’.
- Para os testes unitários, é bem menos difícil 👀 fazer mocks de HALs/SDKs do que fazer dos periféricos do microcontrolador.
Eu já tinha anteriormente pego os arquivos destes SDKs e colocados eles em nosso repositório, aqui.
myDriverDefs.h
Uma coisa nova que fiz foi de adicionar um arquivo de cabeçalho novo nos diretórios de cada driver, que é chamado de myDriverDefs.h.
A ideia ao criar este cabeçalho é de permitir que ele seja incluso por outras partes a fim de que suas definições sejam úteis para ‘amarrar algumas informações’.
O primeiro uso que temos para eles é de dar nomes para as portas e pinos de seus microcontroladores. Com isso, conseguimos nomear os pinos que as nossas aplicações vão usar para acionar os LEDs e lerem os botões.
Exemplo do myDriverDefs.h do STM32F10x
Exemplo do myDriverDefs.h do KL25
Eu receio que isto deve ter ficado um pouco vago e até mesmo confuso para muitos de vocês mas prometo que vai ficar mais claro nos próximos artigos, quando formos elaborar os módulos das nossas aplicações. 🙏 Mas precisamos já disto pronto…
Detalhes das implementações
Vamos agora falar sobre o conteúdo dos arquivos dos drivers. Abaixo o link deles no repositório, caso queiram ir acompanhando:
Escolhendo os Includes
O primeiro ponto de atenção que gostaria de dar para as implementações é para a lista de includes:
Lista de includes no STM32f10x
Lista de includes no KL25
Nos dois casos a lista se resume em três grupos:
- Cabeçalhos básicos da nossa arquitetura.
- Cabeçalhos dos SDKs (no STM temos apenas um, no KL temos quatro)
- projConfig.h
Prestem atenção no projConfig.h. Este cabeçalho permitirá que a gente faça alguns ajustes no nosso driver dependendo do produto/projeto que vamos usar ele.
Este cabeçalho sempre deve estar disponível nos diretórios dos projetos dos produtos:
Como usamos eles? Implementando o nosso driver de tal forma que algumas coisas sejam ajustáveis por #defines. Aí podemos declarar eles dentro dos projConfigs!
Por exemplo, estes nossos drivers vão possuir limite de quantos ‘clientes’ (pinos) eles vão suportar. Na seção de declarações privadas fazemos do jeito mostrado abaixo, criando uma definição DRIVER_GPIO_PIN_AMOUNT e deixando com um valor padrão.
Se existir o define no projConfig, usa o valor dado. Senão, usa este valor padrão (quatro, no caso):
DRIVER_GPIO_PIN_AMOUNT só será declarado se ele não tiver sido antes.
Criando múltiplas instâncias
Os nossos drivers vão precisar gerenciar diferentes pinos, utilizados por diferentes clientes. Como fazemos essa organização? 🤔
Fazemos isso montando o driver com suporte para múltiplas instâncias, aonde cada instância é um cliente/pino. 👀
Primeiramente, identificamos quais dados vão ser específicos de cada instância e colocamos eles em uma estrutura:
Estrutura de instanciação no driver STM32F10x
Estrutura de instanciação no driver KL25
Reparem que o conteúdo da estrutura é diferente para cada driver. 👍
A seguir, criamos um vetor desta estrutura, aonde cada posição armazenará os dados de uma instância. Adicionamos também uma variável contadora, para controlar qual é a próxima instância ‘vazia’:
myGpio_Struct armazena os dados de cada instância
Reparem que aqui usamos aquela definição que mencionei antes, DRIVER_GPIO_PIN_AMOUNT. Vê como que ela limita quantos itens do vetor podemos ter e, assim, limita a capacidade do driver?
Quando myGpio_Init for chamado por um cliente:
- …uma posição deste vetor será alocada para ele,
- …seus dados serão escritos nesta posição,
- …o endereço desta posição é a instância devolvida para o cliente! 👀👀👀
Na imagem abaixo destaco cada um destes momentos, no driver do KL25:
No driver do STM32F10x o processo é basicamente o mesmo. Você consegue abrir o seu arquivo e identificar isto acontecendo? 💭
Usando dos SDKs
Vimos como o driver é configurado e como ele faz para suportar múltiplas instâncias. Para fechar este tópico, vamos ver como ele faz o que ele foi feito para fazer, a manipulação dos GPIOs dos microcontroladores.
Ele faz isso usando chamando as rotinas dos SDKs, enviando informações para elas e processando as suas respostas. No meio deste processo, o driver é encarregado de fazer as conversões de dados necessárias.
Dando um rápido exemplo, veja como que o driver do STM32F10x usa do HAL para mudar o nível de um pino:
myGpio_Set, STM32F10x. Repare no HAL_GPIO_WritePin
Testes Unitários
Aqui entramos na parte mais complicada de drivers, a parte de… elaborar seus testes. 🙈
Aqui o diretório de testes para cada um dos drivers:
Os testes estão implementados e estão todos passando. Todos eles são relativamente simples e fáceis de entender. Assim não vou entrar em detalhes deles per si. Mas caso tenha alguma dúvida, entrem em contato comigo! ✌
Peraí Andre, mas você não disse que eles são complicados? 🤦♂️
Sim… eles são complicados, mas o difícil não são os testes, mas sim a preparação dos setups de testes.
Sabe por que isso? Dê uma passeada no conteúdo dos arquivos de um dos SDKs que usamos (belos exemplos aqui e aqui). 👀
Vai lá dar uma olhada, eu aguardo! 😅
Foi lá ver? Pois é, são de arquivos como estes que nossos drivers dependem para fazer os seus trabalhos. Para testar os drivers, precisamos fazer os mocks desses arquivos! 😯
Destacando alguns problemas deles:
- A organização de suas declarações é [meio] bagunçada;
- A cadeia de dependências é bem longa, alcançando diversos arquivos diferentes.
- Algumas macros são… trechos de códigos inlined! Veja a macro abaixo, por exemplo, que usamos no nosso driver:
Como testar que o nosso código chama algo assim? Lembrando que, para testar chamadas, precisamos de protótipos de funções… Da maneira que está, não dá!
Socorro! O que fazemos? 😣
É importante que nos lembremos que o nosso objetivo aqui é de apenas permitir que os nossos arquivos dos drivers sejam testáveis. Para isso, precisamos apenas de definições simples e de protótipos para as rotinas dos SDKs que nossas lógicas chamam. Todo o resto é descartável. Podemos usar de arquivos de SDK… ‘falsos‘. 💡
Assim, o que eu faço é o seguinte:
- Faço um trabalho exploratório, identificando todos os cabeçalhos do SDK que são realmente usados e o que do conteúdo deles realmente é necessário pelo driver.
- Faço uma cópia destes arquivos para o diretório de support dos testes unitários.
- Nestas cópias eu faço uma edição pesada, limpando tudo que não é usado pelo driver, removendo dependências que podem ser simplificadas, transformando algumas macros em protótipo de funções, etc.
As atividades acima tomam um bom tempo, já adianto! 😅 O lado bom é que, depois de feitas, se no futuro o driver precisar de algo novo do SDK, basta incrementar estes arquivos com a novidade e tudo deve ser bem rápido!
Enfim, este é um assunto muito muito muito longo ⏱ e definitivamente está fora do contexto desta série. Para os mais curiosos, os arquivos de SDK ‘falsos’ que fiz para o nosso projeto estão a seguir:
- Mocks de suporte – STM32F10x
- Mocks de suporte – KL25.
- (viram que estes diretórios também tem projConfigs.h?) 👀
Estou à disposição para tirar quaisquer dúvidas e, se acharem interessante, posso elaborar algum material sobre isso. Aguardo seu feedback! 🙂
E com isso... temos os nossos primeiros drivers!
Neste artigo detalhei um pouco mais do nosso projeto e mostrei como montar alguns drivers de GPIO que, apesarem de serem básicos, atendem o nosso cabeçalho comum e possuem todas as propriedades de robustez que vamos precisar.
No nosso próximo artigo vamos trabalhar em um driver um pouco mais complicado, o driver de timer!
Eae, o que acharam? Participem, entrem em contato, comentem, façam perguntas! Até a próxima! 👊