Archives par mot-clé : stm32cubeIDE

Mise en œuvre de l’USART avec le DMA

Mise en oeuvre de l'USART avec le DMA

objectif de la manip :

Une chaine de caractère est envoyée automatiquement par l'USART du microcontrôleur. L'envoi est piloté par le contrôleur DMA sans intervention du coeur du microcontrôleur (il déclenche seulement l'envoi et c'est le DMA qui gère le reste).

Matériel requis

  • un ordinateur avec un terminal (putty, termite,...)
  • une carte nucleo-L073RZ ou F446RE ou autre...
  • un câble USB pour programmer et émuler la liaison série.

Configuration de l'USART

USART_Config_Sans_NVIC_params.png
Pas obligé d'activer les interruptions USART en mode circular buffer.

A noter que si on se met en Mode Circular et Increment Address coché pour Memory, il suffit de faire une seule fois l'appel à la fonction HAL_UART_Transmit_DMA(&huart2, testMessage, sizeof(testMessage)) pour que l'envoi se répète inlassablement.

    // Appel de ces deux fonctions dans cet ordre la et pas l'inverse sinon cela ne fonctionne pas
    MX_DMA_Init();
    MX_USART2_UART_Init();
  uint8_t testMessage[]="Message envoye automatiquement grace au controleur DMA\r\n";

    HAL_UART_Transmit_DMA(&huart2, testMessage, sizeof(testMessage));
    while(1)
    {
        //on ne fait rien
    }

En mode normal, cela donne :

Attention, il faut activer les interruptions USART !

while (1)
{
    HAL_UART_Transmit_DMA(&huart2, testMessage, sizeof(testMessage));
    HAL_Delay(1000);
}

Tests

Ouvrir un terminal (putty) et le configurer en 115200 bauds, 8 bits, pas de parité. 1 bit de stop.
Programmer la carte.
Le message doit s'afficher toutes les secondes à l'écran du terminal.

Problèmes fréquents

La fonction d'initialisation du DMA MX_DMA_Init() est appelée avant celle de l'USART MX_USART_init();
Et c'est le drame. Rien ne fonctionne. Bienvenue en STM32zarbie.

  MX_GPIO_Init();
  MX_USART2_UART_Init();
  MX_DMA_Init();

Mise en œuvre de l’ADC avec le DMA

Mise en oeuvre de l'ADC avec le DMA

Matériel requis

  • un ordinateur avec un terminal (putty, termite,...)
  • une carte nucleo-L073RZ ou STM32F446RE ou autre
  • Un potentiomètre de 10kOhm par exemple relié à PA0 ou la carte IHM (mbed-application-shield)

Création du projet - Côté configuration

Configuration USART

On configure l'USART2 en 115200 baud.

Configuration ADC

Les choses importantes ici sont la résolution (12 bits), on aurait pu prendre moins.
Les paramètre Continous conversion Mode et Discontinuous Conversion Mode peuvent être indifféremment Enable ou Disable.
Le paramètre DMA Continuous Requests peut être aussi indifféremment Enable ou Disable.


Notons que les événements (interrupt) issues de l'ADC ne sont pas autorisées. Ce sera le contrôleur DMA qui se chargera d'informer d'une fin de transfert d'un échantillon vers l'emplacement mémoire où il sera stocké.

Configuration du DMA pour l'ADC

Comme dans cet exemple, on n'échantillonne qu'une seule entrée et on ne veut à l'instant t qu'une seule valeur, on peut être soit en mode Circular soit Normal, on peut cocher Increment Address ou non.
Dans notre cas, on aura une précision sur 12 bits donc on prend des word (16 bits).

Programmation - Côté code donc...

A rebrousse poil, cela donne :

Récupération des échantillons

Dans main.c et dans une section genre / USER CODE BEGIN 4 /

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc){
    ADC_FLAG=TRUE;
}

Dans la fonction main de main.c :

 if (ADC_FLAG == TRUE)
      {
        ADC_FLAG = FALSE;
        sprintf(message,"%ld\n\r", /*(uint16_t)*/pdata); //on peut caster mais pas obligé
        HAL_UART_Transmit(&huart2, message , sizeof(message), HAL_MAX_DELAY);
        HAL_ADC_Start_DMA(&hadc, &pdata, 1);

      }

On aura évidemment fait auparavant :

uint8_t ADC_FLAG;
#define TRUE 1
#define FALSE 0
uint32_t  pdata = 0; //very important de le mettre en uint32_t
uint8_t message[10]; //ça c'est pour envoyer une chaîne au PC

et in fine dans le main de main.c

uint8_t welcomeMessage[]="Test ADC & DMA sur PA0 8 bits 115200 bauds\r\n";  //un message d'accueil ne fait jamais de mal

  HAL_UART_Transmit(&huart2, welcomeMessage , sizeof(welcomeMessage), HAL_MAX_DELAY); //et on le balance

  HAL_ADC_Start_DMA(&hadc, &pdata, 1); //on lance les hostivités

  while (1)
  {
      if (ADC_FLAG == TRUE) //sur fin de conversion (merci le callback AL_ADC_ConvCpltCallback)
      {
        ADC_FLAG = FALSE;
        sprintf(message,"%ld\n\r", /*(uint16_t)*/pdata); //on peut caster mais pas obligé
        HAL_UART_Transmit(&huart2, message , sizeof(message), HAL_MAX_DELAY);
        HAL_ADC_Start_DMA(&hadc, &pdata, 1); // et rebelote

      }
  }
}

Attention !

Seule cet ordre fonctionne. Si on s'amuse à mettre MX_DMA_Init(); après MX_ADC_Init(); ça déraille complètement, on n'est plus sur 12 bits mais sur 8... Bienvenue en STM32zarbie.

  MX_GPIO_Init();
  MX_USART2_UART_Init();
  MX_DMA_Init();
  MX_ADC_Init();

void MX_ADC_Init(void) (extrait)

Si on ne veut numériser qu'un seul échantillon sous 8 bits ou plus, alors on peut se mettre en circular buffer ou non.
Avoir à l'esprit que les conversions seront lancées avec la fonction :
HAL_ADC_Start_DMA(&hadc, &pdata, 1);
Cette fonction signifie que le contrôleur DMA va lancer la conversion dont l'échantillon sera stockée dans pdata.

    hadc.Init.ContinuousConvMode = DISABLE;
    hadc.Init.DiscontinuousConvMode = DISABLE;
    hadc.Init.DMAContinuousRequests = ENABLE;

Et quand cela fonctionne : exemple de sortie sur Putty

Mise en œuvre de l’ADC

Mise en oeuvre de l'ADC sans le DMA

Matériel requis

  • un ordinateur avec un terminal (putty, termite,...)
  • une carte nucleo-L073RZ ou STM32F446RE ou autre
  • Un potentiomètre de 10kOhm par exemple relié à PA0 ou la carte IHM (mbed-application-shield)

  • Création du projet - Côté configuration

    Configuration USART

    On configure l'USART2 en 115200 baud.

Configuration ADC

Les choses importantes ici sont la résolution (12 bits), on aurait pu prendre moins.
Les paramètre Continous conversion Mode et Discontinuous Conversion Mode peuvent être indifféremment Enable ou Disable.

Si on met Enable à Conversion Continuous Mode plus besoin de faire à chaque fois un ADC_Start(), on le fait au début une fois pour toute.
Evidemment, si on fait un ADC_Stop(), va falloir remettre un ADC_Start() quand on veut un nouvel échantillon.

Fonction main de main.c :

    MX_GPIO_Init();
    MX_DMA_Init();
    MX_USART2_UART_Init();
    MX_ADC_Init();
    HAL_UART_Transmit(&huart2, welcomeMessage , sizeof(welcomeMessage), HAL_MAX_DELAY);

    uint8_t pdata = 0;
    uint8_t message[5];
    HAL_ADC_Start(&hadc);
  while (1)
  {
        HAL_ADC_Start(&hadc);
        HAL_ADC_PollForConversion(&hadc, 1);
        pdata = HAL_ADC_GetValue(&hadc);
        sprintf(message,"%d\r\n",HAL_ADC_GetValue(&hadc));
        HAL_UART_Transmit(&huart2, message , sizeof(message), HAL_MAX_DELAY);
        HAL_ADC_Stop(&hadc);
        HAL_Delay(200);
  }
}

Fonction MX_ADC_Init de main.c :

static void MX_ADC_Init(void)
{
  ADC_ChannelConfTypeDef sConfig = {0};

  hadc.Instance = ADC1;
  hadc.Init.OversamplingMode = DISABLE;
  hadc.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV1;
  hadc.Init.Resolution = ADC_RESOLUTION_8B;
  hadc.Init.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
  hadc.Init.ScanConvMode = ADC_SCAN_DIRECTION_FORWARD;
  hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc.Init.ContinuousConvMode = ENABLE;
  hadc.Init.DiscontinuousConvMode = DISABLE;
  hadc.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc.Init.DMAContinuousRequests = DISABLE;
  hadc.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  hadc.Init.Overrun = ADC_OVR_DATA_PRESERVED;
  hadc.Init.LowPowerAutoWait = DISABLE;
  hadc.Init.LowPowerFrequencyMode = ENABLE;
  hadc.Init.LowPowerAutoPowerOff = DISABLE;
  if (HAL_ADC_Init(&hadc) != HAL_OK)
  {
    Error_Handler();
  }

  /** Configure for the selected ADC regular channel to be converted.
  */
  sConfig.Channel = ADC_CHANNEL_0;
  sConfig.Rank = ADC_RANK_CHANNEL_NUMBER;
  if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
}

Mettre en oeuvre d’une LED avec un microcontrôleur STM32L073 (ou autre de chez ST)

Mise en oeuvre d'une led branchée sur la sortie PA5 du STM32L073

Choisir un nom de projet explicite, par exemple :

GPIO_PA5_LD2_SET_RESET_TOGGLE_L073RZ

Et dans le main avant le while (1) :

Allumage de la led

  HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_SET);

Extinction de la led

  HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);

Clignotement de la led

  HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);

code du de la boucle while du main complet

  while (1)
  {
      //Décommenter la ligne suivante pour allumer la led 2 branchée sur PA5
//    HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_SET);
      // Décommenter les deux lignes suivantes pour faire clignoter la led branchée sur PA5
      HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
      HAL_Delay(500);
  }

Note :

Si on appelle la fonction HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_SET);
comme des bourrins, dans un while(1) sans mettre de délais (c'est pas malin de l'appeler en permanance...) la led ne s'allumera pas. Qu'on se le dise.

Objectif pour un étudiant : le faire en moins de 8 minutes

Utiliser un encodeur dans vos projets

Encodeur rotatif sous STM32

Mise en oeuvre sur un STM32-F429ZI (nucleo-F429ZI)

  • Mise en oeuvre d'un codeur rotatif avec bouton central.

  • Mise en garde : désactiver l'ethernet qui s'il n'est pas configuré fait planter au démarrage.

  • Au niveau du Timer à utiliser
    utiliser un timer qui permet le mode Encoder
    Attention : Sur la nucleo-F429ZI le timer2 ne semble pas bien connecté sur PA0 et PA1 (A vérifier)

  • Timer3 : PC7 et PA6. Testé avec succès.

  • GPIO : s'il n'y a pas de résistances de pull-up, les rajouter en interne.

Step 0 : configuration de l'USART3 histoire de communiquer avec le PC et putty (115200 8N1)

usart3

Step 1 : configuration des horloges du STM32

encoder_clock_config

Step 2 : configuration du Timer 3

tim3_step1

Step 3 : configuration du Timer 3, suite

tim3_step2

Step 4 : et dans le main

// ne pas oublier
#include "stdio.h"
int main(void)
{
    uint16_t EncVal = 0;
    uint8_t msg[]="Encoder test with default settings of project\r\n";

    /* MCU Configuration--------------------------------------------------------*/

    /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
    HAL_Init();
    /* Configure the system clock */
    SystemClock_Config();

    /* Initialize all configured peripherals */
    MX_GPIO_Init();
    MX_USART3_UART_Init();
    MX_TIM3_Init();
    uint8_t Uart_Buf[70];
    HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);
    HAL_UART_Transmit(&huart3, msg, sizeof(msg), 1000);
  while (1)
  {
        EncVal = (TIM3->CNT)>>2; //selon les valeurs configurées, pas nécessaire de faire une division par 4
        // EncVal = __HAL_TIM_GET_COUNTER(&htim2);
         int len = sprintf(Uart_Buf, "%d\r\n",EncVal);
         HAL_UART_Transmit(&huart3, Uart_Buf, len, 1000);
         HAL_Delay(100);
  }
}

Step 5 : Hardware

encodeur

Bibliographie

https://stm32world.com/wiki/STM32_Rotary_Encoder
https://stm32f4-discovery.net/2014/08/library-26-rotary-encoder-stm32f4/

Nucleo-F429ZI Schematic and pinout

foo@bar:~$ wget https://www.st.com/content/ccc/resource/technical/layouts_and_diagrams/schematic_pack/63/95/5c/0c/d6/af/4b/e2/nucleo_144pins_sch.zip/files/nucleo_144pins_sch.zip/jcr:content/translations/en.nucleo_144pins_sch.zip

Nucleo-F446RE Schematic and pinout

foo@bar:~$ wget https://www.st.com/content/ccc/resource/technical/layouts_and_diagrams/schematic_pack/71/1e/2a/ac/b5/c1/4b/a9/nucleo_64pins_sch.zip/files/nucleo_64pins_sch.zip/jcr:content/translations/en.nucleo_64pins_sch.zip

STM32F429ZI Reference Manual
https://www.st.com/resource/en/reference_manual/rm0090-stm32f405415-stm32f407417-stm32f427437-and-stm32f429439-advanced-armbased-32bit-mcus-stmicroelectronics.pdf

USB