Il est possible (et pratique) de faire de l’orienté objet sur les STM32. Cet article présente un exemple pour le USART2 du Nucléo-F446RE. Le code source complet est disponible sur sourceForge.

Voici le UML de la classe STM32F446RE_USART2 que nous allons réaliser.

Le contrôle des données en entrée et en sortie se fera via les interruptions, ce qui explique l’amitié pour le gestionnaire d’interruption. De plus, la classe implémentera le patron singleton afin de permettre le partage du périphérique entre différents éléments d’un programme.

Il est possible de configurer les périphériques des STM32 à l’aide de la librairie HAL ou des librairies standards. Je préfère configurer directement les registres à partir des informations disponibles dans le manuel de référence et la feuille de spécification.

Le USART2 du Nucléo est relié au lien USB qui présente un lien série virtuel sur l’ordinateur (pratique pour le débogage). La figure suivante montre que TX et RX du USART2 sont respectivement reliés à PA2 et PA3.

Le constructeur fera dans un premier temps la configuration des horloges, puis celle des broches E/S avant de terminer avec la configuration des registres spécifiques au USART2.

STM32F446RE_USART2::STM32F446RE_USART2()   
{
// Activer les horloges
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
RCC->APB1ENR |= RCC_APB1ENR_USART2EN;
// Configurer IO pour RX et TX
// PA2 et PA3 en Alternate fonction
GPIOA->MODER |= GPIO_MODER_MODER2_1 | GPIO_MODER_MODER3_1;
// PA2 et PA3 en High speed
GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR2;
GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR3 ;
GPIOA->AFR[0] |= GPIO_AF_USART2 << 8 ; //PA2 en TX = AF7
GPIOA->AFR[0] |= GPIO_AF_USART2 << 12 ; //PA3 en RX = AF7
// Pour réduire le bruit sur l'alimentation
SYSCFG->CMPCR |= SYSCFG_CMPCR_CMP_PD;
// Par default la configuration est 8N1
USART2->CR1 |= USART_CR1_RE | USART_CR1_TE;
// Active RX et TX
setBaudRate(9600);
// Active l'interruption pour USART2
NVIC_EnableIRQ(USART2_IRQn);
// Interruption de tx activée sur transmission
USART2->CR1 |= USART_CR1_RXNEIE;
USART2->CR1 |= USART_CR1_UE; // USART activé
}

Le calcul de la vitesse de transmission (baudrate) utilise la vitesse de l’horloge (ici 180MHz) afin de déterminer la valeur du registre BRR. Notez que pour les vitesse au delà de 2.8Mbps, il sera nécessaire de diminuer le sur-échantillonnent de 16 à 8.

void STM32F446RE_USART2::setBaudRate(uint32_t baudrate) 
{
USART2->BRR = (SystemCoreClock>>2) / baudrate;
}

La méthode getInstance appel le constructeur si l’instance n’est pas déjà créée, puis elle retourne l’adresse de l’instance.

STM32F446RE_USART2* STM32F446RE_USART2::getInstance() 
{
if (instance == 0)
instance = new STM32F446RE_USART2();
return instance;
}

La méthode dataAvailable retourne vrai lorsqu’il y a des données dans le tampon de réception.

bool STM32F446RE_USART2::dataAvailable() const
{
return !rxBuffer.isEmpty();
}

La méthode read retourne simplement un octet retiré de rxBuffer. BufferTemplate gère déjà les demandes lorsque la file est vide et retourne le dernier élément.

uint8_t STM32F446RE_USART2::read()
{
return rxBuffer.rem();
}

La méthode write reçoit un octet à transmettre. Elle ajoute donc l’octet au tampon de transmission et si le USART2 ne transmettait pas déjà, il active l’interruption pour le registre de transmission vide.

void STM32F446RE_USART2::write(uint8_t data)
{
txBuffer.add(data);
if(!isTransmitting)
{
isTransmitting = true;
USART2->CR1 |= USART_CR1_TXEIE; // active l'interruption.
}
}

Finalement, le gestionnaire d’interruption pour le USART2 traite la réception et la transmission des octets. Le fait qu’il soit ami avec USART2 lui permet un accès plus direct et donc plus rapide vers les tampons. Notez que pour améliorer les performances, vous pourriez utiliser des tableaux de données plutôt que la classe BufferTemplate, ou encore la DMA selon l’application visée.

extern "C" 
{
void USART2_IRQHandler(void)
{
volatile unsigned int isr;
isr = USART2->SR;
// RX Data
if (isr & USART_SR_RXNE)
{
USART2->SR &= ~USART_SR_RXNE;
STM32F446RE_USART2::instance->rxBuffer.add(USART2->DR);
}
// TX Done
if ((isr & USART_SR_TXE))
{
USART2->SR &= ~USART_SR_TXE;
if(STM32F446RE_USART2::instance->txBuffer.isEmpty())
{
STM32F446RE_USART2::instance->isTransmitting = false;
USART2->CR1 &= (~USART_CR1_TXEIE);
}
else
{
USART2->DR = STM32F446RE_USART2::instance->txBuffer.rem();
STM32F446RE_USART2::instance->isTransmitting = true;
}
}
}
}

Documents