Sobre o que vamos falar?
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.
Pra que fazer isso, Andre?
- GPIO: Este sempre trará rotinas relacionadas à ler e escrever informações de pinos.
- Timer: Este driver sempre trará rotinas relacionadas à contagem de tempo.
- 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…
Cabeçalhos públicos
- Definições necessárias para o seu uso.
- As funções e rotinas disponíveis.
- Helper dos testes unitários.
Tipo de rotinas
“Rotina fulana! Faça isto já!”
“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?
Vamos por a mão na massa!
- 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
- 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.
- hal, drivers, myGpio.
- hal, drivers, myTimer.
- hal, myBoard.
- libs, cmsis_os.
- products, blinky, app, appButton.
- products, blinky, app, appLed.
Os cabeçalhos básicos
/**
* @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
- 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.
/**
* @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
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:
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.
- Faça uma breve descrição do que o módulo deve fazer.
- Este módulo é independente ou ele fornecerá funções para outros módulos?
- Que dependências este módulo terá?
- Que tipos específicos este módulo precisará declarar para poder ser usado?
- Este módulo será todo desenvolvido por nós ou será a abstração de módulos de terceiros?
- Que rotinas o módulo terá?
- Que respostas cada rotina precisa ter? Quais são bloqueantes e quais são não bloqueantes?
Bolacha ou biscoito? (Não, esse não, Andre!) 🤣
- 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
- 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
- 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.
- 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
- 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
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 !
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.