STM32 ADC de base

Cet article présente la configuration de base d’un ADC sur le STM32F446. Dans un premier exemple, l’ADC1 sera configuré sur le canal 0 pour une lecture démarrée dans l’interruption du timer 2. Dans un second exemple, la lecture sera démarrée automatiquement par l’événement CC2 (Capture/Compare canal2) du timer 2. Tous les exemples utiliseront l’interruption de fin de conversion. Le code complet se trouve à la fin de l’article.

Read More


USART2 avec Classe

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


System Workbench (STM32)

ST supporte officiellement 3 IDE:

Plusieurs autres IDE sont disponibles, dont celle de Atollic (maintenant propriété de ST) mais je recommande d’utiliser System Workbench car c’est celui priorisé par ST et il est gratuit. Keil a une version gratuite limitée et IAR n’est commercial.

Installation

Pour télécharger System Workbench , allez sur le site OpenSTM32. Vous devez créer un compte (c’est gratuit) et ensuite télécharger l’installeur qui convient à votre système d’exploitation.


Comment utiliser le port série virtuel du Nucléo

Lorsque le Nucléo est relié au PC à l’aide du port USB, il créé un port série virtuel qui permet d’échanger des données (ou messages). Pour voir le nom du port série dans Windows, il suffit d’aller dans Périphériques et imprimantes. Vous verrez alors un périphérique du nom de STM32 STlink.

Dans les propriétés de ce périphérique, vous verrez l’ensemble des fonctions de ce dernier, dont Virtual COM Port. Sur mon ordinateur, il apparaît sur le COM6.

Comme pour le Arduino, c’est le UART qui est relié à D0 et D1 qui est utilisé. Dans le cas du Nucléo F446RE, c’est le UART2 avec les pins A2 et A3 qu’il faut utiliser.

Pour utiliser des périphériques du microcontrôleur, il faut d’abord activer l’horloge. Nous allons donc débuter par activer l’horloge sur UART2 et le port A.

 /* Active le clock sur UART2 et GPIOA */
 RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);

Ensuite, nous allons configurer A2 et A3 qui sont respectivement TX et RX pour le UART2. Cette assignation est une fonction alternative de A2 et A3. Nous allons donc spécifier qu’ils utilisent une fonction alternative (AF) puis nous allons spécifier que la fonction alternative est UART2.

  /* Configuration des GPIO*/
 GPIO_InitTypeDef GPIO_InitStructure;
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3; 
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
 GPIO_Init(GPIOA, &GPIO_InitStructure);
 GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_USART2);
 GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_USART2);

La prochaine étape consiste à configurer UART2. Dans cette exemple, la configuration sera 115200 bauds, 8 bits de données, aucune parité et 1 stop bit. La dernière ligne active le UART2.

 /* Configuration du UART2 */
 USART_InitTypeDef USART_InitStructure;
 USART_InitStructure.USART_BaudRate = 115200;
 USART_InitStructure.USART_WordLength = USART_WordLength_8b;
 USART_InitStructure.USART_StopBits = USART_StopBits_1;
 USART_InitStructure.USART_Parity = USART_Parity_No;
 USART_InitStructure.USART_HardwareFlowControl =          USART_HardwareFlowControl_None;
 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
 USART_Init(USART2, &USART_InitStructure);
 USART_Cmd(USART2, ENABLE);

Afin de vérifier le fonctionnement du programme, voici une fonction qui affiche envoie une chaîne de caractères. La boucle vérifie que le caractère courant est différent de zéro (le caractère de fin). Elle attend ensuite que le transmetteur soit libre, puis elle envoie un caractère et repositionne le curseur pour le prochain caractère.

void uart2Puts(char *s)
{
 while(*s)
 {
 while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
 USART_SendData(USART2, *s++);
 }
}

Le programme suivant envoie le message « Bonjour le mode » puis il retourne en écho les caractères qu’il reçoit. La fonction uart2Init() regroupe l’ensemble du code d’initialisation présenté au début de cet article.

int main(void)
{
 uart2Init();
 uart2Puts("Bonjour le monde \r\n");
 while(1) // Faire un écho à l'infini
 {
   uint16_t rxData;
   while(USART_GetFlagStatus(USART2, USART_FLAG_RXNE) == RESET);
   rxData = USART_ReceiveData(USART2);
   while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
   USART_SendData(USART2, rxData);
 }
}

STM32 Nucleo F446RE : Blink de base

Comme premier code, nous allons commuter la LED2 du Nucleo afin de mesurer la performance des librairies.

Lors des tests, l’onde carré observée était d’environ 2.4MHz. Ce n’est pas la méthode la plus rapide, car les fonctions utilisées pour le changement d’état de la LED2 présentent une surcharge notable.

#include "stm32f4xx.h"
#include "system_stm32f4xx.h"
#include "stm32f4xx_hal_gpio.h"
#include "stm32f4xx_hal_rcc.h"

GPIO_InitTypeDef GPIO_InitStructure;

void main(void)
{
  HAL_Init();
  __GPIOA_CLK_ENABLE();
  GPIO_InitStructure.Pin = GPIO_PIN_5;
  GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStructure.Pull = GPIO_PULLUP;
  GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);

  while (1)
  {
     HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
     HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
  }
}

SMT32 Nucleo F446RE : Blink rapide

Les fonctions qui permettent de contrôler les I/O du STM32 sont pratiques mais, elles sont également très lentes. Ces fonctions ne se limitent pas à imposer le niveau des sorties, elles font aussi quelques vérifications.

Il est possible de contrôler directement les I/O du STM32 en utilisant directement le port de sortie via le registre de sortie (ODR pour Output Data Register).

Pour obtenir l’information sur les différents registres, il faut consulter le manuel de référence sur le site de ST.

lien : www.st.com/resource/en/reference_manual/dm00135183.pdf

Voici un petit code qui permettra de commuter rapidement une sortie.  Sur le NUCLEOF446RE, la LED2 est reliée à PA5. Lors des tests, cette version donnait une période de 80ns, soit une fréquence de 12MHz.

#include "stm32f446xx.h"

#define LED2_INIT RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; \
        GPIOA->MODER |= GPIO_MODER_MODER5_0
#define LED2_ON (GPIOA->ODR |= 0x0020)
#define LED2_OFF (GPIOA->ODR &= ~0x0020)
#define LED2_TOGGLE (GPIOA->ODR ^= 0x0020)

int main (void)
{
 RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;

 LED2_INIT;
 while (1)
 {
   LED2_ON;
   LED2_OFF;
 }
}

Finalement, il est également possible de faire une méthode hybride, et d’utiliser les fonctions pour l’initialisation et modifier directement les registres pour le contrôle.

#include "stm32f4xx.h"
#include "stm32f4xx_hal_gpio.h"

GPIO_InitTypeDef GPIO_InitStructure;
#define LED2_ON (GPIOA->ODR |= 0x0020)
#define LED2_OFF (GPIOA->ODR &= ~0x0020)

void main(void)
{
   HAL_Init();
   __GPIOA_CLK_ENABLE();
   GPIO_InitStructure.Pin = GPIO_PIN_5;
   GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
   GPIO_InitStructure.Pull = GPIO_PULLUP;
   GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
   HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);

   while (1)
   {
     LED2_ON;
     LED2_OFF;
   }
}

Ajouter un fichier source dans un projet SMT32 sous Eclipse

Après la création d’un projet, tous les fichiers d’entête seront disponibles automatiquement, mais, si le fichier source n’est pas explicitement ajouté au projet, le linker donnera une erreur lors de la compilation, car il ne trouvera pas le code à associer aux fonctions.

Par exemple, dans ce projet, nous désirons utiliser les fonctions du adc. Le fichier smt32f4xx_hal_adc.h est disponible dans les fichiers inclus mais pas le smt32f4xx_hal_adc.c qui contient le code des fonctions.

incl1inc2

Il est possible d’ajouter des fichiers sources, selon les besoins du projet. Il faut aller dans les propriétés du projet, sous C/C++General>>Paths and Symbols>>Source Location puis cliquer sur Edit Filter.

incl4

Dans la fenêtre du filtre, il est maintenant possible de retirer le fichier désiré de la liste des fichiers exclus à la compilation. Sélectionner le fichier et appuyer sur Remove ajoute ce dernier à la liste des sources.

incl3

La liste des sources inclut maintenant le fichier.

inc2

Voilà!