Objetivo
O objetivo deste artigo é apresentar uma aplicação fazendo-se o uso da linguagem C++ em sistemas embarcados, sem se preocupar em abordar todas as características da linguagem, muito menos o ambiente de desenvolvimento e o hardware. Ou seja, o intuito principal é passar a filosofia de encapsulamento, levando o código ao mais alto nível possível. Entendendo esta essência, pode-se conseguir boa estruturação e reutilização do código sem muitas preocupações. Vale lembrar que é necessário que o leitor tenha conhecimentos sobre linguagens de orientação a objetos (OOP – Object-Oriented Programming).
Aplicação
A ferramenta utilizada para desenvolvimento desta aplicação foi o Keil uVision 5.20, o qual pode ser adquirido no próprio site, bastando apenas preencher um simples formulário. A versão vigente é v5.21a e o hardware utilizado é o kit STM32F0 Discovery, o qual já utilizei em Primeiros passos com a Placa STM32F0 Discovery.
Código
O código da aplicação será apresentado em três partes. Na primeira será apresentado o arquivo “main.cpp”, cujo conteúdo é os includes, a inicialização do hardware, as declarações dos objetos e a aplicação. A segunda parte apresenta o arquivo “GPIO.cpp” o qual contém o código de todos os métodos da classe. E, por fim, na terceira parte é apresentado o arquivo “GPIO.h”, onde temos a declaração de uma variável global, enumerações e a criação da classe.
No arquivo “main.cpp” podemos ver que dentro da rotina principal há a criação do objeto “Obj_GPIO” que será alvo da aplicação, a rotina de inicialização/configuração do clock do microcontrolador “vSystemCoreClockSetHSE()”, a inicialização da instância por meio da função-membro construtora e, por último, o loop já com a aplicação em uso. Vide código 1 logo abaixo.
Código 1:
/*
Brief: using C++ programming language to develop embedded systems.
Author: Eder Tadeu
Date: August 4th, 2016
*/
#include "GPIO.h"
void vDummyDelay(void)
{
const unsigned long cuiDelay = 4800000;
unsigned long ulCounter = 0;
while(ulCounter++ <= cuiDelay){}
}
void vSystemCoreClockSetHSE(void)
{
/* Habilita o HSE como fonte de clock */
RCC->CR |= ((uint32_t)RCC_CR_HSEON);
/* Aguarda pela estabilização do HSE */
while ((RCC->CR & RCC_CR_HSERDY) == 0);
/* HSE selecionado como "system clock */
RCC->CFGR = RCC_CFGR_SW_HSE;
/* Aguardando HSE para ser usado como "system clock" */
while ((RCC->CFGR & RCC_CFGR_SWS) == RESET);
/* Habilita o Buffer de Pré-Busca */
FLASH->ACR = FLASH_ACR_PRFTBE;
/* Aguarda um cliclo */
FLASH->ACR |= FLASH_ACR_LATENCY;
/* Fator de divisão do clock do barramento AHB (:1), AHB = SYSCLK */
RCC->CFGR |= RCC_CFGR_HPRE_DIV1;
/* Fator de divisão do clock do barramento APB (:1), APB = AHB*/
RCC->CFGR |= RCC_CFGR_PPRE_DIV1;
/* Desabilita o PLL para configuração */
RCC->CR &= ~RCC_CR_PLLON;
/* Configuração do PLL = HSE * 6 = 48 MHz */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL));
RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_PREDIV1 | RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLMULL6);
/* Habilita o PLL */
RCC->CR |= RCC_CR_PLLON;
/* Aguarda o PLL estar pronto, equanto isso não faz nada! */
while((RCC->CR & RCC_CR_PLLRDY) == 0) __NOP();
/* Seleciona PLL como "system clock" */
RCC->CFGR &= ~RCC_CFGR_SW;
RCC->CFGR |= RCC_CFGR_SW_PLL;
/* Aguarda PLL ser o "system clock" */
while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);
}
int main(void)
{
/* Creation of GPIO object */
class GPIOClass Obj_GPIO;
/* Inicializa a fonte de clock HSE - High Speed External */
vSystemCoreClockSetHSE();
/* Init ports, pins and their modes */
Obj_GPIO.vGPIOInit(GPIO_C, OutputMode, GPIO_PIN_8);
Obj_GPIO.vGPIOInit(GPIO_C, OutputMode, GPIO_PIN_9);
for(;;)
{
Obj_GPIO.vGPIOWriteOutput(GPIO_C, GPIO_PIN_8, SET);
Obj_GPIO.vGPIOWriteOutput(GPIO_C, GPIO_PIN_9, SET);
vDummyDelay();
Obj_GPIO.vGPIOWriteOutput(GPIO_C, GPIO_PIN_8, RESET);
Obj_GPIO.vGPIOWriteOutput(GPIO_C, GPIO_PIN_9, RESET);
vDummyDelay();
}
}
- a construtora GPIOClass(), a qual inicializa o dado pGPIO com “NULL”;
- vGPIOEnable(), que habilita o clock do GPIO informado como parâmetro;
- vGPIOGetAddress(), que obtêm o endereço do port que se deseja inicializar;
- o vGPIOInit(), que realiza a inicialização do pino e, por fim;
- vGPIOWriteOutput(), que altera o nível lógico do(s) pino(s) informado(s).
O código de cada um pode ser visto abaixo.
Código 2:
#include "GPIO.h"
#include "stddef.h"
/* Brief : Construction function.
* Arguments : None.
* Return : None.
*/
GPIOClass::GPIOClass(void)
{
/* Init stGPIO pointer with NULL value */
pGPIO = NULL;
}
/* Brief : Enable GPIO bus clock.
* Arguments : Port.
* Return : None.
*/
void GPIOClass::vGPIOEnable(unsigned char Port)
{
switch(Port)
{
case GPIO_A: RCC->AHBENR |= (1UL << 17); /* Enable GPIOA clock */
break;
case GPIO_B: RCC->AHBENR |= (1UL << 18); /* Enable GPIOB clock */
break;
case GPIO_C: RCC->AHBENR |= (1UL << 19); /* Enable GPIOC clock */
break;
case GPIO_D: RCC->AHBENR |= (1UL << 20); /* Enable GPIOD clock */
break;
case GPIO_F: RCC->AHBENR |= (1UL << 22); /* Enable GPIOF clock */
break;
default: break;
}
}
/* Brief : Get the informed GPIO address.
* Arguments : Port.
* Return : None.
*/
void GPIOClass::vGPIOGetAddress(unsigned char Port)
{
switch(Port)
{
case GPIO_A: pGPIO = GPIOA; /* Load the pointer with GPIOA adress */
break;
case GPIO_B: pGPIO = GPIOB; /* Load the pointer with GPIOB address */
break;
case GPIO_C: pGPIO = GPIOC; /* Load the pointer with GPIOC address */
break;
case GPIO_D: pGPIO = GPIOD; /* Load the pointer with GPIOD address */
break;
case GPIO_F: pGPIO = GPIOF; /* Load the pointer with GPIOF address */
break;
default: break;
}
}
/* Brief : Initialization of pins.
* Arguments : Port, IO mode and pin.
* Return : None.
*/
void GPIOClass::vGPIOInit(unsigned char Port, unsigned char IOType, unsigned char Pin)
{
vGPIOEnable(Port);
vGPIOGetAddress(Port);
switch(IOType)
{
case InputMode: pGPIO->MODER &= ~(3UL << 2 * Pin); /* Reset - input */
break;
case OutputMode: pGPIO->MODER &= ~(3UL << 2 * Pin); /* Reset - input */
pGPIO->MODER |= (1UL << 2 * Pin); /* Output */
pGPIO->OTYPER &= ~(1UL << Pin); /* Push-pull */
break;
case AltFuncMode: pGPIO->MODER &= ~(3UL << 2 * Pin); /* Reset - input */
pGPIO->MODER |= (2UL << 2 * Pin); /* Alternate */
break;
case AnalogMode: pGPIO->MODER &= ~(3UL << 2 * Pin); /* Reset - input */
pGPIO->MODER |= (3UL << 2 * Pin); /* Analog */
break;
default: break;
}
pGPIO->OSPEEDR &= ~(3UL << 2 * Pin);/* Reset - Low speed */
pGPIO->OSPEEDR |= (1UL << 2 * Pin);/* Medium speed */
pGPIO->PUPDR &= ~(3UL << 2 * Pin);/* No pull-up, no pull-down */
}
void GPIOClass::vGPIOWriteOutput(unsigned char Port, unsigned char Pin, bool Value)
{
vGPIOGetAddress(Port);
if(Value)
{
pGPIO->BSRR |= (GPIO_Mask[Pin]);
}
else
{
pGPIO->BSRR |= (GPIO_Mask[Pin] << 16);
}
}
#ifndef __GPIO_H
#define __GPIO_H
#endif
/* User includes */
#include "stm32f0xx.h"
/* User defines */
#define QTY_GPIO_PINS (unsigned char)16
#define RESET (bool)0
#define SET !(RESET)
/* User enums */
enum IOType
{
InputMode ,
OutputMode ,
AltFuncMode,
AnalogMode ,
};
enum PIN
{
GPIO_PIN_0,
GPIO_PIN_1,
GPIO_PIN_2,
GPIO_PIN_3,
GPIO_PIN_4,
GPIO_PIN_5,
GPIO_PIN_6,
GPIO_PIN_7,
GPIO_PIN_8,
GPIO_PIN_9,
GPIO_PIN_10,
GPIO_PIN_11,
GPIO_PIN_12,
GPIO_PIN_13,
GPIO_PIN_14,
GPIO_PIN_15,
GPIO_PIN_NONE
};
enum PORT
{
GPIO_A,
GPIO_B,
GPIO_C,
GPIO_D,
GPIO_E,
GPIO_F,
GPIO_NONE
};
/* User variables */
const unsigned long GPIO_Mask[QTY_GPIO_PINS] = {
1UL << GPIO_PIN_0,
1UL << GPIO_PIN_1,
1UL << GPIO_PIN_2,
1UL << GPIO_PIN_3,
1UL << GPIO_PIN_4,
1UL << GPIO_PIN_5,
1UL << GPIO_PIN_6,
1UL << GPIO_PIN_7,
1UL << GPIO_PIN_8,
1UL << GPIO_PIN_9,
1UL << GPIO_PIN_10,
1UL << GPIO_PIN_11,
1UL << GPIO_PIN_12,
1UL << GPIO_PIN_13,
1UL << GPIO_PIN_14,
1UL << GPIO_PIN_15};
/* User class */
class GPIOClass
{
private: GPIO_TypeDef *pGPIO;
public:
GPIOClass();
void vGPIOInit (unsigned char Port, unsigned char IOType, unsigned char Pin);
void vGPIOWriteOutput(unsigned char Port, unsigned char Pin, bool Value);
void vGPIOClearOutput(unsigned char Port, unsigned char Pin);
void vGPIOGetAddress (unsigned char Port);
void vGPIOEnable (unsigned char Port);
};
Conclusão
Vimos que o exemplo apresentado cria um encapsulamento para o GPIO do microcontrolador, consequentemente, uma abstração de camada. Porém, este exemplo não determina nenhuma regra, assim o leitor poderá realizar seu encapsulamento da forma que lhe for conveniente. Além disso, poderá também estender a outros periféricos. Realizando este trabalho aos poucos, pode-se conseguir uma grande herança no futuro, pois ao criar classes para certos tipos de periféricos, cria-se, assim, uma biblioteca.
Saiba mais
Orientação a objeto em C: Encapsulamento com estruturas opacas
Orientação a objeto em C: Polimorfismo
Referências
A Complete Guide to Programming C++ – Ulla Kirch-Prinz/Peter Prinz










Muito bacana iniciar esta conversa sobre C/C++. O mito de que C/C++ infla o código ou mesmo complica demais é para aqueles que estão acomodados com o C. Uma vez iniciado no C/C++, você não volta mais atrás e começa a ver o C como uma linguagem tão limitada quanto o Basic, principalmente no quesito reuso de código e sucessão do conhecimento.
Embora que dá para usar boas práticas de OOP em C. A meu ver, OOP está mais ligada a metodologia de desenvolvimento de que a linguagem propriamente dita.
Sim, e também a capacidade do compilador.
Amigo Mauricio, acomodado é uma palavra muito forte… Até porque você está precisando estudar um pouco mais… Pergunto, se C++ não gasta processo extra e não “infla” conforme você comentou então me explique porque o uso de C++ exige memória heap? É porque ele usa alocação dinâmica de memória…. então as estruturas, ah desculpa, os objetos são alocados dinamicamente.
Há uma lenda acerca do overhead de C++ em sistemas embarcados baremetal. Eu mesmo não tenho opinião formada a respeito.
Sim, porém esse “overhead” está mais ligado ao “leakage of memory” (RAM) devido a criação de objetos e o não “free()” deles, já que, eles são alocados no heap. Ou seja, falta de gerenciamento ou “carinho” no código, leva ao desperdício de memória e “bugs”.