Inter e Intra #2 – Interfaces!

Olá pessoal, estamos de volta!
 
O nosso pequeno artigo 🙃 de hoje é a segunda parte do nosso projeto Inter e Intra, aonde estou mostrando os conceitos básicos desta arquitetura, utilizando de um projeto de exemplo simples com brinquedos de pelúcia fofinhos.
 
Se você ainda não leu a primeira parte desta série de artigos, sugiro que você comece lá! Também tem a minha palestra de introdução à este assunto, que vale muito a pena dar uma assistida! Confira!

Sobre o que vamos falar?

Hoje vou focar na arquitetura Inter, nos cabeçalhos públicos e em outros cabeçalhos importantes para a construção do nosso repositório. Ter estes arquivos definidos é um passo muito importante para estarmos prontos para mandar ver no código!
 
Enquanto isso as atividades na empresa continuam à todo vapor! No momento o foco está mais com as equipes de hardware e de mecânica. Isto é ótimo pois assim ficamos mais tranquilos para trabalharmos do nosso lado… mas logo logo os olhos vão se virar para cá, então é bom estarmos prontos!
A última reunião da equipe foi super produtiva!

Princípios da arquitetura Inter

A arquitetura Inter foca em separar as responsabilidades funcionais do seu produto em diferentes módulos. O seu produto irá fazer uma série de atividades fantásticas mas não é legal que todo o código delas fique todo junto, num único emaranhado de um ou poucos arquivos.

Precisamos separar toda a lógica que vamos implementar em módulos.

Pra que fazer isso, Andre?

Dentre as vantagens da separação do código em módulos, temos:
 
Redução de dependências
Se eu mudo alguma coisa do meu código do GPIO, isso não deveria mudar nada nos outros lugares totalmente não relacionados – por exemplo, como o de um cálculo de CRC. Não é mesmo?
Os módulos devem saber o mínimo possível uns dos outros. De fato, eles só devem saber o necessário para que eles possam exercer as suas atividades. Isso vai manter o nosso repositório super flexível.
 
Troca de implementações
Num dado produto um módulo pode ser implementado de uma maneira, enquanto que em outro produto ele é totalmente diferente. Mas ainda assim, na visão das outras partes, eles fornecem as mesmas funcionalidades e atendem ao mesmo cabeçalho, que deve ser genérico.
É muito fácil exemplificar este ponto com módulos de drivers, porque cada microcontrolador tem periféricos diferentes, os quais exigem uma lógica diferente para funcionar, mas mesmo assim eles ainda entregam as mesmas funcionalidades. Por exemplo:
  • GPIO: Este sempre trará rotinas relacionadas à ler e escrever informações de pinos.
  • Timer: Este driver sempre trará rotinas relacionadas à contagem de tempo.
 
Mas isto não vale apenas para drivers, na verdade isso deve valer para todos os módulos do seu repositório. Dando um exemplo ‘mais alto nível’:
  • Um módulo de sistema de arquivos sempre nos proverá rotinas relacionadas à leitura e escrita de arquivos, independente se é implementado o sistema X, Y, Z…
 
Simplificação de código
É impressionante como que implementações outrora complexas se tornam extremamente simples quando separadas em diferentes módulos. Isso acontece porque a lógica aparenta ser mais complicada do que realmente é quando ela não está corretamente distribuída. Se uma lógica está complicada de implementar, provavelmente ela pode ser ‘quebrada’ em módulos menores e ser simplificada. É isto que vamos fazer aqui.
 
Testes, testes, testes!
Separando o código em módulos fica mais fácil fazer os testes unitários dos seus produtos, porque num código modularizado, além dele ser mais simples, também se pode ‘mockar‘ as dependências, podendo testar cada módulo isoladamente.

Cabeçalhos públicos

As peças chaves da arquitetura Inter são os cabeçalhos públicos. Como o nome diz, eles são headers (arquivos com extensão ‘.h’) que declaram as capacidades que um dado módulo possui. Eles declaram:
  • Definições necessárias para o seu uso.
  • As funções e rotinas disponíveis.
  • Helper dos testes unitários.

Tipo de rotinas

As rotinas que um módulo pode fornecer podem ser bloqueantes ou não bloqueantes.
 
Uma rotina bloqueante realiza todas as atividades que são esperadas daquela rotina quando ela é chamada, levando todo o tempo necessário para isso, e só depois retornando da sua chamada.
 
Seria como dar a ordem:

“Rotina fulana! Faça isto já!”

Estas rotinas têm a vantagem de simplificar a lógica não só de suas implementações mas também a de seus usuários. Porém, elas são ‘gulosas’.. como estes usuários ficam ‘parados’ esperando por elas, eles ficam bloqueados e isto pode causar problemas em sistemas aonde o determinismo tem bastante importância – se alguma atividade precisa ser realizada num determinado tempo, estes bloqueios podem comprometer este requisito de ser cumprido. Além do mais, como eles estão bloqueados, eles ficam impedidos de realizar algum ‘plano B’ no caso de problemas ou de eventos inesperados.
 
Uma rotina não bloqueante, porém, pode retornar a chamada e depois continuar a trabalhar na tarefa que lhe foi pedida, avisando em um tempo futuro quando ela acabar. Elas fazem esta notificação ‘a posteriori’ usando de callbacks, que são rotinas que os usuários passam para o módulo não bloqueante. Esta operação com callbacks ficará mais clara quando vermos as declarações dos cabeçalhos do nosso exemplo.
 
Seria como dar a ordem:

“Rotina ciclana! Comece a fazer isto e me avise por aqui [aponta um callback] quando você terminar!” 

Este tipo de operação é ideal para sistemas determinísticos, pois seus clientes ficam livres para realizar outras atividades enquanto o módulo está trabalhando. Porém, a lógica envolvida para operar estas rotinas é um pouco mais complicada, o que faz muitos desenvolvedores preferirem implementar rotinas bloqueantes. Muitas vezes, entretanto, estas decisões se tornam grandes erros.

Que tipo de rotina usar, Andre?

Eu costumo encorajar a sempre usar rotinas não bloqueantes, a não ser que a atividade que o módulo tem que fazer seja ‘simples’ ou que se tenha alguma necessidade daquilo ser bloqueante. Rotinas não bloqueantes encorajam o seu sistema a ser determinístico e robusto. Porém, em situações como em rotinas de inicialização ou em drivers simples, eu costumo usar rotinas bloqueantes mesmo.

Vamos por a mão na massa!

Já ficamos demais na teoria, vamos trabalhar. O que faremos aqui hoje?
  • Definiremos que módulos teremos em nosso primeiro projeto.
  • O que cada um precisa fornecer.
  • O que cada um precisa dos outros (dependências).
  • Vamos criar os cabeçalhos públicos.

Definição dos módulos

Bem, não existe uma regra restrita de como os módulos são definidos, isso varia muito de desenvolvedor para desenvolvedor… alguns preferem uma certa arquitetura, enquanto outros preferem ir por outro caminho. Conforme a pessoa vai ganhando experiência, ela vai se tornando capaz de definir melhor os módulos. De certa maneira, fazer isto é uma ‘arte’!
 
Vamos recapitular o que o nosso produto precisa fazer. Na hora de bolar alguma arquitetura eu costumo ‘sintetizar’ a necessidade presente em uma ou mais frases, para me ajudar a ‘enxergar’ o que é preciso.
 
No primeiro artigo tivemos nossa primeira ‘especificação do produto’, aquele conjunto de bullets que bolamos depois da nossa primeira reunião. Usando eles de base e focando na parte do firmware, temos as seguintes afirmações:
  • O produto deve coordenar um LED, fazendo ele piscar de acordo com uma entrada digital. Nesta entrada está ligado um push-button, o qual vai ser pressionado pela crianç… pelo usuário!
  • Quando o botão não estiver pressionado, o LED deve piscar com um período total de [TBD1] ms, duty 50%.
  • Quando o botão estiver pressionado, o LED deve piscar com um período total de [TBD2] ms, duty 50%.
  • O botão é controlado pelo nosso produto, então ele precisa ter seu nível mantido por pulls  e uma outra saída digital ser responsável por dar o nível quando o botão for pressionado.
  • A entrada digital deve ser lida periodicamente e seu nível reavaliado a cada [TBD3] ms.
Como deu pra perceber alguns valores ainda não ficaram definidos, deixei como [TBD] To Be Defined. Os seus valores vão impactar como o produto se comporta e é melhor deixar para a liderança de empresa decidir estas características. Você prontamente já enviou um e-mail para eles sobre isto!
 
Com este descritivo pronto, vamos separar os módulos. A seguir listo alguns fatores que considero ao definir eles e, de tabela, vou levantando quais deles serão necessários para o nosso projeto:
 
O que é mais atrelado ao hardware? O que é menos?
Identificar módulos de drivers geralmente é simples, em grande parte eles são feitos pensando em operações ‘típicas’ para todos os microcontroladores. Por exemplo, todos eles geralmente têm entradas e saídas digitais, timers, ADCs, portas seriais, etc.
 
Para o nosso brinquedo vamos precisar atuar em entradas e saídas digitais e também vamos precisar contar tempo. Serão dois drivers.
 
Outro ponto importante é que todo microcontrolador precisa ser inicializado. Por exemplo, clocks precisam ser configurados, alguns outros periféricos precisam ser habilitados… até mesmo alguns pinos precisam passar por uma condição inicial. Assim, eu costumo ter um módulo relacionado à placa que o produto está sendo compilado e este módulo já é operado no main, logo na inicialização.
 
Assim, para o nosso projeto, já temos três módulos definidos:
  • hal, drivers, myGpio.
  • hal, drivers, myTimer.
  • hal, myBoard.
 
O que eu só vejo sendo usado em apenas um produto? O que eu já vejo sendo compartilhado por vários deles?
As regras de negócio do seu repositório geralmente nascem na camada de aplicação e, conforme for visto que mais de um produto vai utilizar do mesmo, a lógica vai ‘descendo’ para as camadas de bibliotecas. Porém, caso já esteja claro que um módulo vai ser compartilhado, então pode valer a pena que ele nasça como uma lib.
 
Aqui no nosso projeto está claro que teremos um módulo de sistema operacional e faz todo o sentido ele ser usado por outros futuros produtos, então temos mais um módulo definido, para ser uma lib. Para uso de RTOS, vamos seguir o padrão CMSIS-OS:
  • libs, cmsis_os.
 
Como podemos dividir as responsabilidades do nosso produto?
No nosso produto existem duas atividades bem distintas: uma para fazer a leitura do push-button, outra para coordenar o LED. Lembre-se que estamos em estágios bem iniciais do produto, nós estamos usando push-buttons e LEDs para fazermos nossos protótipos, ninguém nos disse ainda o que vai ter nos brinquedos… por exemplo, se ao invés de push buttons, for ter um acelerômetro e assim a ‘entrada de comandos’ for diferente? Neste caso, apenas o módulo do push button deveria ser mudado, o do LED deveria permanecer inalterado. Por tudo isso, é interessante termos um módulo para cada funcionalidade!
 
Assim, temos os dois últimos módulos do nosso projeto:
  • products, blinky, app, appButton.
  • products, blinky, app, appLed.

Os cabeçalhos básicos

Nossos produtos não consistirão apenas de módulos, precisamos também de outros cabeçalhos que fazem definições elementares, tais como a declaração dos tipos básicos, funções de debug, etc. Estes são cabeçalhos básicos e muitos dos módulos vão depender deles. Assim, eles são ‘auxiliares’ e ficam declarados em nosso diretório de helpers. São eles:
 
myDefs.h
Este cabeçalho define todos os tipos básicos das variáveis dos nossos produtos, além de declarar outros tipos que são comumente usados em nosso repositório. Abaixo a primeira declaração dele, contendo apenas o que precisaremos para o nosso primeiro produto. É natural este cabeçalho ir crescendo conforme novos tipos (como callbacks) forem sendo necessários.
/** 
 * @file myDefs.h 
 * @brief Header file containing common definitions for the repository. 
 * 
 * This file will contain types, enums and defines that can be used 
 *  anywhere throughout the repository. 
 */

#ifndef MY_DEFS_H 
#define MY_DEFS_H 

/******************************************************************************* 
 *  INCLUDES 
 ******************************************************************************/ 
#include <stdint.h> 
#include <stdbool.h> 

/******************************************************************************* 
 *  PUBLIC DEFINITIONS - TYPES 
 ******************************************************************************/ 
/** 
 * @brief Basic type that all logic should use to indicate success or failure. 
 * 
 * This enumeration should be used to represent if an operation went well or 
 *  not. Notice that the success result has the value of zero. 
 */ 
typedef enum 
{ 
  myRet_OK = 0, 
  myRet_Fail, 
} myRet_t;

/******************************************************************************* 
 *  PUBLIC DEFINITIONS - CALLBACKS 
 ******************************************************************************/ 
/** 
 * @brief Basic callback for simple actions. 
 * 
 * This callback is used when a module doesn't need to provide any data when  
 *  it'll answer back the result of an operation. 
 */ 
typedef void (*myCbk_t)(void); 

#endif
O que temos neste arquivo:
  • Inclusão dos tipos básicos da biblioteca C, os quais sempre usaremos quando possível. Recomendo que sempre trabalhe com os tipos ‘uintx_t’, mas isso é assunto para outro post.
  • Criação do seu tipo de retorno padrão. ‘myRet_t’ será o seu tipo mais importante, ele indicará se alguma operação foi (ou não) bem sucedida. Repare que o valor de ‘sucesso’ é zero, isso é particularmente útil na hora de trabalhar com os testes unitários, pois com isso os valores default dos mocks serão de sucesso e nossos testes ficarão mais clean.
  • Criação do seu primeiro tipo de callback. Este é o mais simples, que simplesmente chama algo, sem argumento nenhum. Vamos usar ele já na nossa rotina não bloqueante do timer.
 
myAssert.h
É uma excelente prática utilizarmos de assertions durante o desenvolvimento do nosso código. Um assertion bem colocado pode te poupar de horas e horas de investigação quando algum problema acontece. Sendo assim, vamos fazer um cabeçalho dele:
/** 
 * @file myAssert.h 
 * @brief Header file containing definitions for logic assertion. 
 * 
 * This file contains macros for asserting operations. 
 */ 
#ifndef MY_ASSERT_H 
#define MY_ASSERT_H 

/******************************************************************************* 
 *  INCLUDES 
 ******************************************************************************/ 
#include "myDefs.h" 

/******************************************************************************* 
 *  PUBLIC DEFINITIONS 
 ******************************************************************************/ 
#define myASSERT(EXP) if((EXP) == false) { while(1); } 

#endif
Esse é o tipo de assertion mais simples que existe – caso uma expressão não for verdadeira, o código simplesmente fica num laço infinito.
Este cabeçalho pode ser incrementado de várias maneiras e, inclusive, futuramente ele poderá ter um source, mas para o que precisamos agora, é mais que suficiente.

Como ficou nosso diretório?

Agora que definimos os nossos cabeçalhos públicos e os cabeçalhos básicos, vamos atualizar o nosso repositório:

Temos os nossos primeiros cabeçalhos!

Criando os cabeçalhos públicos

Com os módulos definidos e nossos cabeçalhos básicos declarados, podemos partir para as declarações de nossos cabeçalhos públicos.

Para criar cada um, vamos usar de uma checklist, com pontos para respondermos:
  1. Faça uma breve descrição do que o módulo deve fazer.
  2. Este módulo é independente ou ele fornecerá funções para outros módulos?
  3. Que dependências este módulo terá?
  4. Que tipos específicos este módulo precisará declarar para poder ser usado?
  5. Este módulo será todo desenvolvido por nós ou será a abstração de módulos de terceiros?
  6. Que rotinas o módulo terá?
  7. Que respostas cada rotina precisa ter? Quais são bloqueantes e quais são não bloqueantes?
  8. Bolacha ou biscoito? (Não, esse não, Andre!) 🤣
 
Vamos começar dos módulos das aplicações e vamos descendo até os drivers.
appButton.h
  • Faça uma breve descrição do que o módulo deve fazer.

Este módulo deve realizar a leitura periódica de uma entrada digital. Dependendo do nível desta entrada, ele deve atuar no módulo de LED para mudar o período que ele pisca. Este módulo deve configurar o necessário para o uso de um push button: uma saída digital e a entrada com um pull.

  • Este módulo é independente ou ele fornecerá funções para outros módulos?

Ele é totalmente independente, tendo apenas uma rotina para a sua inicialização.

  • Que dependências este módulo terá?

appLed.

cmsis_os.

myGpio.

myTimer.

  • Que tipos específicos este módulo precisará declarar para poder ser usado?

Nenhum.

  • Este módulo será todo desenvolvido por nós ou será a abstração de módulos de terceiros?

Será desenvolvido totalmente por nós.

  • Que rotinas o módulo terá?

Rotina de inicialização do módulo.

  • Que respostas cada rotina precisa ter? Quais são bloqueantes e quais são não bloqueantes?

A inicialização é bloqueante e apenas responde se foi bem sucedida (ou não).

/**
 * @file appButton.h
 * @brief Interface header file for the Button application.
 *
 * This module provides the routines that external parties can call in order
 *  to interact with the Button application.
 */
 
#ifndef APP_BUTTON_H
#define APP_BUTTON_H

/*******************************************************************************
 *  INCLUDES
 ******************************************************************************/
#include "myDefs.h"

/*******************************************************************************
 *  PUBLIC PROTOTYPES
 ******************************************************************************/
/**
 * @brief Initialization routine for the Button Application
 *
 * This routine should be called by your initializer logic so that the Button
 *  application can start.
 * It should be called only once. After it is called, the module will handle
 *  its initialization by itself.
 * @return Success / Failure
 */
myRet_t appButton_Init(void);

#endif
appLed.h
  • Faça uma breve descrição do que o módulo deve fazer.

Este módulo deve coordenar uma saída digital, ligada à um LED. Ele deve manter este LED piscando de acordo com um intervalo de tempo, intervalo este passado pelos clientes.

  • Este módulo é independente ou ele fornecerá funções para outros módulos?

Além da inicialização, ele fornecerá uma função para alterar o período de piscar do LED.

  • Que dependências este módulo terá?

cmsis_os.

myGpio.

myTimer.

  • Que tipos específicos este módulo precisará declarar para poder ser usado?

Nenhum.

  • Este módulo será todo desenvolvido por nós ou será a abstração de módulos de terceiros?

Será desenvolvido totalmente por nós.

  • Que rotinas o módulo terá?

Rotina de inicialização do módulo.

Rotina para definir o período de piscar do LED.

  • Que respostas cada rotina precisa ter? Quais são bloqueantes e quais são não bloqueantes?

As rotinas são bloqueantes e apenas respondem se foram bem sucedidas (ou não).

/**
 * @file appLed.h
 * @brief Interface header file for the LED application.
 *
 * This module provides the routines that external parties can call in order
 *  to interact with the LED application.
 */
 
#ifndef APP_LED_H
#define APP_LED_H

/*******************************************************************************
 *  INCLUDES
 ******************************************************************************/
#include "myDefs.h"

/*******************************************************************************
 *  PUBLIC PROTOTYPES
 ******************************************************************************/
/**
 * @brief Initialization routine for the LED Application
 *
 * This routine should be called by your initializer logic so that the LED
 *  application can start.
 * It should be called only once. After it is called, the module will handle
 *  its initialization by itself.
 * @return Success / Failure
 */
myRet_t appLed_Init(void);

/**
 * @brief Request application to change the LED blinking period.
 *
 * @param period New period value, in [ms]
 * @return Success / Failure
 */
myRet_t appLed_SetBlinkingPeriod(uint32_t period);

#endif
cmsis_os.h
  • Faça uma breve descrição do que o módulo deve fazer.

Este módulo deve fornecer os serviços do sistema operacional. Em particular, deve prover a capacidade de iniciarmos threads e de enviarmos mensagens para elas.

  • Este módulo é independente ou ele fornecerá funções para outros módulos?

Este módulo fornecerá funções para outros módulos.

  • Que dependências este módulo terá?

Da implementação do sistema operacional.

  • Que tipos específicos este módulo precisará declarar para poder ser usado?

Os seus tipos para os artefatos do sistema operacional.

  • Este módulo será todo desenvolvido por nós ou será a abstração de módulos de terceiros?

Será a abstração de módulos de terceiros. Inclusive na maioria das vezes estes mesmos terceiros que irão nos fornecer o cabeçalho deste módulo.

  • Que rotinas o módulo terá?

As rotinas de uso abstraído do sistema operacional.

  • Que respostas cada rotina precisa ter? Quais são bloqueantes e quais são não bloqueantes?

São inúmeras rotinas, estas informações já estão definidas e o cabeçalho já está implementado. Não precisamos nos preocupar.

 
A lib de sistema operacional é, infelizmente, a primeira lib que precisamos em nosso repositório. Eu digo infelizmente porque ela não segue totalmente o conceito de cabeçalho público que estou apresentando aqui para vocês. Eu mostrei que o cabeçalho público de um módulo é único e que as suas implementações deveriam seguir o mesmo. Aqui, pelo fato de que cada RTOS ter as suas características, é preciso que cada implementação tenha o seu próprio cabeçalho, divergindo do que falei. Mas deixo claro que ainda assim existe compatibilidade entre eles e mudando a implementação não afeta a compilação de seus clientes! De qualquer maneira, vamos prosseguir assim.
 
Lembrando que esta lib será usada somente pelos nossos submódulos de sistema, para implementar os nossos objetos ativos.
 
A primeira implementação que vamos usar do CMSIS-OS é para o FreeRTOS, eu estarei adicionando ela ao nosso repositório quando começarmos a implementar as nossas aplicações.
myBoard.h
  • Faça uma breve descrição do que o módulo deve fazer.

Este módulo deve realizar a inicialização da placa do nosso produto, iniciando o core do microcontrolador e quaisquer pinos que forem necessários.

  • Este módulo é independente ou ele fornecerá funções para outros módulos?

Ele é independente, apenas fornecendo uma rotina para a sua inicialização.

  • Que dependências este módulo terá?

Apenas do SDK do fabricante do microcontrolador.

  • Que tipos específicos este módulo precisará declarar para poder ser usado?

Nenhum.

  • Este módulo será todo desenvolvido por nós ou será a abstração de módulos de terceiros?

Será desenvolvido por nós.

  • Que rotinas o módulo terá?

Rotina de inicialização do módulo.

  • Que respostas cada rotina precisa ter? Quais são bloqueantes e quais são não bloqueantes?

As rotinas são bloqueantes e apenas respondem se foram bem sucedidas (ou não).

/**
 * @file myBoard.h
 * @brief Header file for board specific operations.
 *
 * This header provides the types and routines for board modules.
 *  A board module provides routines and definitions to identify the board
 *    and put it to operate.
 */
 
#ifndef MY_BOARD_H
#define MY_BOARD_H

/*******************************************************************************
 *  INCLUDES
 ******************************************************************************/
#include "myDefs.h"

/*******************************************************************************
 *  PUBLIC FUNCTIONS / ROUTINES
 ******************************************************************************/
/**
 * @brief Initialization routine for the board.
 */
void myBoard_Init(void);

#endif

myGpio.h

  • Faça uma breve descrição do que o módulo deve fazer.

Este módulo deve atuar sobre os pinos do microcontrolador, fornecendo funções de entrada e saída digitais simples. Os pinos podem ser configurados como saídas e colocados em níveis lógicos altos ou baixos. Também, eles podem ser configurados como entradas digitais, com o módulo indicando se eles estão em nível lógico alto ou baixo. As entradas podem ter pull-ups ou pull-downs habilitados.

  • Este módulo é independente ou ele fornecerá funções para outros módulos?

Ele fornecerá funções para que outros módulos possam configurar e usar os pinos do microcontrolador.

  • Que dependências este módulo terá?

Apenas do SDK do fabricante do microcontrolador.

  • Que tipos específicos este módulo precisará declarar para poder ser usado?

Ele precisará declarar tipos específicos e uma estrutura de configuração que um cliente indique como deseja que um pino seja operado.

  • Este módulo será todo desenvolvido por nós ou será a abstração de módulos de terceiros?

Será desenvolvido por nós.

  • Que rotinas o módulo terá?

Rotinas para configuração e uso dos pinos.

  • Que respostas cada rotina precisa ter? Quais são bloqueantes e quais são não bloqueantes?

As rotinas são bloqueantes, as de inicialização e saída digital apenas respondem se foram bem sucedidas (ou não) e as de entrada digital informam o nível lógico atual dos pinos.

/**
 * @file myGpio.h
 * @brief Header file for general purpose input output drivers.
 *
 * This header provides the types and routines for gpio drivers.
 *  A gpio driver provides routines for using pins for digital input or
 *    digital output operations.
 */

#ifndef MY_GPIO_H
#define MY_GPIO_H

/*******************************************************************************
 *  INCLUDES
 ******************************************************************************/
#include "myDefs.h"

/*******************************************************************************
 *  PUBLIC DEFINITIONS
 ******************************************************************************/
/**
 * @brief Type used by the driver to represent the logic level on a pin.
 */
typedef enum
{
  myGpioLvl_Lo = 0,
  myGpioLvl_Hi,
} myGpioLvl_t;

/**
 * @brief Type used by the driver to represent the direction of pin.
 */
typedef enum
{
  myGpioDir_Inpt = 0,
  myGpioDir_Outp,
} myGpioDir_t;

/**
 * @brief Type used by the driver to represent the pull setting of an input pin.
 */
typedef enum
{
  myGpioPull_No = 0,
  myGpioPull_Up,
  myGpioPull_Dw,
} myGpioPull_t;

/**
 * @brief Structure containing all the info needed to initialize a gpio pin.
 */
typedef struct
{
  uint8_t port;
  uint8_t pin;
  myGpioDir_t direction;
  myGpioPull_t pull;
} myGpioPars_t;

/**
 * @brief Typedef declaring a forward declared struct that represents
 *          a gpio pin.
 */
typedef struct myGpioPinStruct_t * myGpioPin_t;

/*******************************************************************************
 *  PUBLIC FUNCTIONS / ROUTINES
 ******************************************************************************/
/**
 * @brief Initialization routine for a gpio pin.
 * @param pin If successful, it will be written with the data required to use
 *              this pin in the future.
 * @param pars Structure containing all the data required to initialize this
 *              pin.
 * @return Success / Failure
 */
myRet_t myGpio_Init(myGpioPin_t * pin, myGpioPars_t * pars);

/**
 * @brief Gets the level of a given pin.
 * @param pin Info about the pin to get the level from.
 * @return Current level. If routine fails, it returns low level.
 */
myGpioLvl_t myGpio_Get(myGpioPin_t pin);

/**
 * @brief Sets the level of an output pin.
 * @param pin Info about the pin to get the level from.
 * @param lvl Level to set the pin to.
 * @return Success / Failure
 */
myRet_t myGpio_Set(myGpioPin_t pin, myGpioLvl_t lvl);

#endif
myTimer.h
  • Faça uma breve descrição do que o módulo deve fazer.

Este módulo deve usar de periféricos do microcontrolador para prover contagem de tempo, na escala de milissegundos. Como exemplo, os clientes querem ser notificados depois que um certo tempo passou. Algumas partes vão querer ser periodicamente notificados, enquanto que outras apenas uma única vez (single shot). Por enquanto não temos clientes que vão usar deste último caso.

  • Este módulo é independente ou ele fornecerá funções para outros módulos?

Ele fornecerá interfaces para que os clientes possam configurar e requisitar pedidos de contagem de tempo.

  • Que dependências este módulo terá?

Apenas do SDK do fabricante do microcontrolador.

  • Que tipos específicos este módulo precisará declarar para poder ser usado?

Ele precisará declarar tipos específicos e uma estrutura de configuração para indicar como um cliente deseja que um timer seja operado.

  • Este módulo será todo desenvolvido por nós ou será a abstração de módulos de terceiros?

Será desenvolvido por nós.

  • Que rotinas o módulo terá?

Rotinas para configuração e uso dos timers.

  • Que respostas cada rotina precisa ter? Quais são bloqueantes e quais são não bloqueantes?

A rotina de inicialização é bloqueante, a de contagem de tempo são é bloqueante.

/**
 * @file myTimer.h
 * @brief Header file for simple time counting drivers.
 *
 * This header provides the types and routines for timer drivers.
 *  A timer driver provides routines for using timer peripherals for time
 *    counting operations.
 */
 
#ifndef MY_TIMER_H
#define MY_TIMER_H

/*******************************************************************************
 *  INCLUDES
 ******************************************************************************/
#include "myDefs.h"

/*******************************************************************************
 *  PUBLIC DEFINITIONS
 ******************************************************************************/
/**
 * @brief Type used by the driver to determine a timer's operating mode.
 */
typedef enum
{
  myTimerMode_Periodic = 0,
} myTimerMode_t;

/**
 * @brief Structure containing all the info needed to initialize a timer.
 */
typedef struct
{
  myTimerMode_t mode;
} myTimerPars_t;

/**
 * @brief Typedef declaring a forward declared struct that represents
 *          a timer.
 */
typedef struct myTimerStruct_t * myTimer_t;

/*******************************************************************************
 *  PUBLIC FUNCTIONS / ROUTINES
 ******************************************************************************/
/**
 * @brief Initialization routine for a timer.
 * @param timer If successful, it will be written with the data required to use
 *              this timer in the future.
 * @param pars Structure containing all the data required to initialize this
 *              timer.
 * @return Success / Failure
 */
myRet_t myTimer_Init(myTimer_t * timer, myTimerPars_t * pars);

/**
 * @brief Starts the time counting operation for a timer.
 * @param timer Timer to start the operation
 * @param period Time, in ms, to count
 * @param cbk Callback to be called when timer expires
 * @return Success / Failure. If successful, timer will start and callback
 *          will eventually be called.
 */
myRet_t myTimer_Start(myTimer_t timer, uint32_t period, myCbk_t cbk);

#endif

Finalizando...

Pois bem pessoal! Acabamos mais uma parte muito importante do nosso projeto, identificando as atividades que precisam ser feitas, como vamos dividir elas em módulos e que interfaces estes módulos vão ter!
Estou ansioso para ver algum código rodando, e você?
No próximo artigo estaremos preparando o nosso ambiente, falando um pouco dos testes unitários e das IDEs que vamos utilizar com as nossas placas de desenvolvimento.
 
repositório está atualizado com os cabeçalhos que definimos aqui hoje!
 
Ficou com alguma dúvida? Entre em contato – ou deixe um comentário – e ficarei feliz em lhe ajudar! Cadastre seu e-mail para receber na sua caixa de entrada os próximos artigos!
 
Até a próxima!

2 comentários em “Inter e Intra #2 – Interfaces!”

  1. André, conteúdo de OURO, estou aprendendo como nunca. Na parte dos callbacks eu tive que fritar os neurônios pra entender mas depois que eu aprendí, um novo horizonte se abriu. Agora não entendo muito bem ainda a parte dos “assertions” e sua relação com os testes unitários, mas vou ficar ligado aqui pra continuar aprendendo contigo. Muito obrigado pela sua disposição em compartilhar esse conhecimento fantástico. Abraços !

    1. Olá novamente, Wagner!
      Obrigado pelos comentários e por estar acompanhando, fico feliz destas informações estarem lhe sendo úteis! Quanto aos assertions e testes unitários, bem… eles não tem relação nenhuma! Tenho certeza que nos próximos posts isto ficará mais claro… outro abraço!

Não é possível comentar.