web-dev-qa-db-fra.com

STM32F4 UART pilote HAL

J'essaie de comprendre comment utiliser ce nouveau pilote HAL. Je souhaite recevoir des données à l'aide de la HAL_UART_Receive_IT() qui configure l'appareil pour exécuter une fonction d'interruption lors de la réception des données.

Le problème est que vous devez spécifier la longueur des données à lire avant le déclenchement de l'interruption. Je prévois d'envoyer des commandes de type console de longueur variable, donc je ne peux pas avoir une longueur fixe. Je suppose que la seule façon de le faire serait de lire des caractères uniques à la fois et de créer une chaîne distincte.

Le pilote HAL semble avoir un problème où si vous définissez la fonction HAL_UART_Receive_IT() pour recevoir x nombre de caractères, puis essayez d'envoyer plus de x caractères, il y aura une erreur.

Actuellement, je n'ai aucune idée si j'y vais de la bonne façon, des idées?

18
HammerFet

J'ai décidé d'aller avec DMA pour faire fonctionner la réception. J'utilise un tampon circulaire de 1 octet pour gérer les données telles qu'elles sont tapées sur le terminal série de l'émetteur. Voici mon code final (seulement le recevoir une partie, plus d'informations sur la transmission en bas).

Quelques définitions et variables:

#define BAUDRATE              9600
#define TXPIN                 GPIO_PIN_6
#define RXPIN                 GPIO_PIN_7
#define DATAPORT              GPIOB
#define UART_PRIORITY         6
#define UART_RX_SUBPRIORITY   0
#define MAXCLISTRING          100 // Biggest string the user will type

uint8_t rxBuffer = '\000'; // where we store that one character that just came in
uint8_t rxString[MAXCLISTRING]; // where we build our string from characters coming in
int rxindex = 0; // index for going though rxString

Configurer IO:

__GPIOB_CLK_ENABLE();
__USART1_CLK_ENABLE();
__DMA2_CLK_ENABLE();

GPIO_InitTypeDef GPIO_InitStruct;

GPIO_InitStruct.Pin = TXPIN | RXPIN;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(DATAPORT, &GPIO_InitStruct);

Configurer l'UART:

UART_HandleTypeDef huart1;
DMA_HandleTypeDef hdma_usart1_rx;

huart1.Instance = USART1;
huart1.Init.BaudRate = BAUDRATE;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
HAL_UART_Init(&huart1);

Configurer DMA:

extern DMA_HandleTypeDef hdma_usart1_rx; // assuming this is in a different file

hdma_usart1_rx.Instance = DMA2_Stream2;
hdma_usart1_rx.Init.Channel = DMA_CHANNEL_4;
hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart1_rx.Init.MemInc = DMA_MINC_DISABLE;
hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart1_rx.Init.Mode = DMA_CIRCULAR;
hdma_usart1_rx.Init.Priority = DMA_PRIORITY_LOW;
hdma_usart1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
HAL_DMA_Init(&hdma_usart1_rx);

__HAL_LINKDMA(huart, hdmarx, hdma_usart1_rx);

HAL_NVIC_SetPriority(DMA2_Stream2_IRQn, UART_PRIORITY, UART_RX_SUBPRIORITY);
HAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn);

Configurer DMA interruption:

extern DMA_HandleTypeDef hdma_usart1_rx;

void DMA2_Stream2_IRQHandler(void)
{
    HAL_NVIC_ClearPendingIRQ(DMA2_Stream2_IRQn);
    HAL_DMA_IRQHandler(&hdma_usart1_rx);
}

Démarrez DMA:

__HAL_UART_FLUSH_DRREGISTER(&huart1);
HAL_UART_Receive_DMA(&huart1, &rxBuffer, 1);

DMA reçoit le rappel:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    __HAL_UART_FLUSH_DRREGISTER(&huart1); // Clear the buffer to prevent overrun

    int i = 0;

    print(&rxBuffer); // Echo the character that caused this callback so the user can see what they are typing

    if (rxBuffer == 8 || rxBuffer == 127) // If Backspace or del
    {
        print(" \b"); // "\b space \b" clears the terminal character. Remember we just echoced a \b so don't need another one here, just space and \b
        rxindex--; 
        if (rxindex < 0) rxindex = 0;
    }

    else if (rxBuffer == '\n' || rxBuffer == '\r') // If Enter
    {
        executeSerialCommand(rxString);
        rxString[rxindex] = 0;
        rxindex = 0;
        for (i = 0; i < MAXCLISTRING; i++) rxString[i] = 0; // Clear the string buffer
    }

    else
    {
        rxString[rxindex] = rxBuffer; // Add that character to the string
        rxindex++;
        if (rxindex > MAXCLISTRING) // User typing too much, we can't have commands that big
        {
            rxindex = 0;
            for (i = 0; i < MAXCLISTRING; i++) rxString[i] = 0; // Clear the string buffer
            print("\r\nConsole> ");
        }
    }
}

C'est donc à peu près tout le code pour recevoir des caractères et construire une chaîne (tableau de caractères) qui montre ce que l'utilisateur a entré. Si l'utilisateur frappe en arrière ou en arrière, le dernier caractère du tableau est écrasé et s'il appuie sur Entrée, ce tableau est envoyé à une autre fonction et traité comme une commande.

Pour voir comment la commande analyse et transmet le code, consultez mon projet ici

Merci à @Flip et @Dormen pour leurs suggestions!

15
HammerFet

La réception de données alors que le registre de données (DR) est plein entraînera une erreur de dépassement. Le problème est que la fonction UART_Receive_IT(UART_HandleTypeDef*) arrêtera la lecture du registre DR une fois qu'il aura reçu suffisamment de données. Toute nouvelle donnée entraînera l'erreur de dépassement.

J'ai plutôt utilisé une structure circulaire DMA recevant la structure. Vous pouvez ensuite utiliser currentPosInBuffer - uart->hdmarx->Instance->NDTR pour déterminer la quantité de données reçues que vous n'avez pas encore traitées.

C'est un peu plus compliqué car alors que le DMA effectue lui-même la mise en tampon circulaire, vous devez implémenter manuellement le bouclage au début si vous dépassez la fin de la mémoire tampon.

J'ai également trouvé un problème où le contrôleur dit qu'il a transféré les données (c'est-à-dire NDTR a diminué) mais les données ne sont pas encore dans le tampon. Il peut s'agir d'un problème de conflit d'accès DMA/bus, mais c'est ennuyeux.

6
Flip

Les pilotes STM32 UART sont un peu loufoques. La seule façon dont ils fonctionnent hors de la boîte est si vous connaissez le nombre exact de caractères que vous allez recevoir. Si vous voulez recevoir un numéro non spécifié de personnages il y a quelques solutions que j'ai rencontrées et essayées:

  1. Définissez le nombre de caractères à recevoir sur 1 et créez une chaîne distincte. Cela fonctionne mais a des problèmes lors de la réception de données très rapidement, car chaque fois que le pilote lit le rxBuffer, il dissout l'interruption, donc certains caractères peuvent être perdus.

  2. Définissez le nombre de caractères à recevoir à la plus grande taille de message possible et implémentez un délai d'expiration, après quoi tout le message est lu.

  3. Écrivez votre propre fonction UART_Receive_IT, qui écrit directement dans un tampon circulaire. C'est plus de travail, mais c'est ce que j'ai trouvé qui fonctionne le mieux à la fin. Cependant, vous devez changer certains des pilotes hal, donc le code est moins portable.

Une autre façon consiste à utiliser DMA comme le suggère @Flip.

3
Domen Kern

Avoir une approche différente patcher par exemple "void USART2_IRQHandler (void)" dans le fichier "stm32l0xx_it.c" (ou l4xx selon les besoins). Chaque fois qu'un personnage est reçu, cette interruption est appelée. Il y a un espace pour insérer le code utilisateur qui reste inchangé lors de la mise à jour avec le générateur de code CubeMX. Pièce:

void USART2_IRQHandler(void)
{
  /* USER CODE BEGIN USART2_IRQn 0 */

  /* USER CODE END USART2_IRQn 0 */
  HAL_UART_IRQHandler(&huart2);
  /* USER CODE BEGIN USART2_IRQn 1 */
  usart_irqHandler_callback( &huart2 ); // patch: call to my function 
  /* USER CODE END USART2_IRQn 1 */
}

Je fournis un petit tampon de caractères et démarre la fonction IT de réception. Jusqu'à 115200 bauds, il n'a jamais consommé plus de 1 octet, laissant le reste du tampon inutilisé.

st = HAL_UART_Receive_IT( &huart2, (uint8_t*)rx2BufIT, RX_BUF_IT_SIZE );

Lors de la réception d'un octet, je le capture et le mets dans mon propre tampon en anneau et remets le pointeur de caractère et le compteur de retour:

// placed in my own source-code module:
void usart_irqHandler_callback( UART_HandleTypeDef* huart ) {
  HAL_UART_StateTypeDef  st;
  uint8_t c;
  if(huart->Instance==USART2) {
    if( huart->RxXferCount >= RX_BUF_IT_SIZE ) {
      rx2rb.err = 2;           // error: IT buffer overflow
    }
    else {
      huart->pRxBuffPtr--;     // point back to just received char
      c = (uint8_t) *huart->pRxBuffPtr; // newly received char
      ringbuf_in( &rx2rb, c ); // put c in rx ring-buffer
      huart2.RxXferCount++;    // increment xfer-counter avoids end of rx
    }
  }
}

Cette méthode s'est avérée assez rapide. Réception d'un seul octet en utilisant IT ou DMA désinitialise toujours et a besoin d'initialiser à nouveau le processus de réception qui s'est avéré trop lent. Le code ci-dessus n'est qu'une trame; J'avais l'habitude de compter les caractères de nouvelle ligne ici dans une structure de statut qui me permet à tout moment de lire les lignes terminées de la mémoire tampon en anneau. Il faut également vérifier si un caractère reçu ou un autre événement a provoqué l'interruption.
MODIFIER:
Cette méthode s'est avérée fonctionner correctement avec USARTS qui ne sont pas pris en charge par DMA et utilisez plutôt l'informatique. Utilisation de DMA avec 1 octet en mode circulaire) est plus court et plus facile à implémenter lors de l'utilisation du générateur CubeMX avec la bibliothèque HAL.

0
peets

J'ai dû faire face au même problème dans mon projet. Ce que j'ai fait, c'est commencer à lire 1 octet avec HAL_USART_Receive_IT() juste après l'initialisation périphérique.

Ensuite, j'ai écrit un rappel sur le transfert terminé qui met l'octet dans un tampon, définit un indicateur si la commande est terminée et appelle à nouveau HAL_USART_Receive_IT() pour un autre octet.

Cela semble bien fonctionner pour moi car je reçois des commandes via l'USART dont le premier octet me dit combien d'octets de plus la commande va être longue. Peut-être que ça pourrait aussi marcher pour vous!

0
LoriB

Habituellement, j'ai écrit ma propre implémentation de tampon circulaire UART. Comme indiqué précédemment, les fonctions d'interruption de la bibliothèque STM32 HAL UART sont un peu étranges. Vous pouvez écrire votre propre tampon circulaire avec seulement 2 tableaux et pointeurs utilisant UART drapeaux d'interruption.

0
ctasdemir