STM32F4 UART Hal Driver
j'essaie de trouver comment utiliser ce nouveau pilote hal. Je veux recevoir des données à l'aide de la HAL_UART_Receive_IT()
qui configure l'appareil pour exécuter une fonction d'interruption lorsque les données sont reçues.
Problème, c'est que vous devez spécifier la longueur de données à lire avant l'interruption se déclenche. Je prévois d'envoyer une console comme des commandes de longueur variable, donc je ne peux pas avoir de longueur fixe. Je suppose que la seule façon de le faire serait de lire une seule caractères à la fois, et de construire un chaîne.
le pilote Hal semble avoir un problème si vous positionnez le HAL_UART_Receive_IT()
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 je vais sur le droit chemin, des idées?
6 réponses
j'ai décidé d'aller avec DMA pour faire fonctionner la réception. J'utilise un tampon circulaire d'un octet pour traiter les données car il est tapé sur le terminal série de l'émetteur. Voici mon code final (seulement la partie de réception, plus d'informations sur transmettre 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 = '0'; // 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
mettre en place des 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);
mettre en place des 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);
mettre en place des DMA interrompre:
extern DMA_HandleTypeDef hdma_usart1_rx;
void DMA2_Stream2_IRQHandler(void)
{
HAL_NVIC_ClearPendingIRQ(DMA2_Stream2_IRQn);
HAL_DMA_IRQHandler(&hdma_usart1_rx);
}
début DMA:
__HAL_UART_FLUSH_DRREGISTER(&huart1);
HAL_UART_Receive_DMA(&huart1, &rxBuffer, 1);
DMA recevoir de 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> ");
}
}
}
donc c'est à peu près tout le code pour recevoir les caractères et construire une chaîne de caractères (char array) qui montre ce que l'Utilisateur a saisi. Si l'utilisateur touche backspace ou del, le dernier caractère du tableau est écrasé et s'il touche enter, ce tableau est envoyé à une autre fonction et traité comme une commande.
pour voir comment fonctionne l'analyse de commande et transmettre le code, Voir mon projet Ici
merci à @Flip et @Dormen pour leurs suggestions!
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*)
cessera de lire le registre DR une fois qu'il aura reçu suffisamment de données. Toute nouvelle donnée causera l'erreur de dépassement.
ce que j'ai fait était plutôt d'utiliser une structure de réception DMA circulaire. Vous pouvez ensuite utiliser currentPosInBuffer - uart->hdmarx->Instance->NDTR
pour déterminer combien de données ont été reçues que vous n'avez pas encore traitées.
C'est un peu plus compliqué parce que tout le DMA fait le tampon circulaire lui-même, vous devez implémenter manuellement le loopback au début si vous passez la fin du tampon.
j'ai aussi trouvé un bug où le contrôleur dit qu'il a transféré les données (i.e. NDTR
a diminué), mais les données ne sont pas encore dans la mémoire tampon. C'est peut-être un problème d'accès DMA / bus, mais c'est ennuyeux.
les pilotes de UART STM32 sont un peu chelous. La seule façon de les sortir de la boîte est de connaître le nombre exact de caractères que vous allez recevoir. Si vous souhaitez recevoir un nombre quelconque de caractères, il existe quelques solutions que j'ai trouvé et essayé:
définir la quantité de caractères à recevoir à 1 et construire une chaîne séparée. Cela fonctionne, mais a des problèmes lors de la réception des données très rapide, parce que chaque fois que le pilote lit le rxBuffer il désactive l'interruption, de sorte que certains personnages peuvent être perdus.
définir la quantité de caractères à recevoir à la plus grande taille de message possible et mettre en œuvre un délai d'attente, après quoi le message entier est lu.
écrire votre propre fonction UART_Receive_IT, qui écrit directement dans un tampon circulaire. C'est plus de travail, mais c'est ce qui fonctionne le mieux à la fin. Vous devez changer certains pilotes hal, donc le code est moins portable.
une autre façon est d'utiliser DMA comme @Flip suggéré.
j'ai dû faire face au même problème dans mon projet.
Ce que j'ai fait est de commencer à lire 1 octet avec HAL_USART_Receive_IT()
juste après l'initialisation périphérique.
puis j'ai écrit un callback sur Transfert complet qui met le byte dans un buffer, met un drapeau si la commande est complète et ensuite appelle HAL_USART_Receive_IT()
encore une fois pour un autre byte.
cela semble bien fonctionner pour moi puisque je reçois des commandes par L'intermédiaire de L'USART dont le premier octet me dit combien d'octets de plus la commande va être longue. Peut-être ça pourrait marcher pour toi aussi!
habituellement, j'ai écrit ma propre implémentation de tampon circulaire UART. Comme dit plus haut, les fonctions d'interruption UART de la bibliothèque HAL STM32 sont un peu étranges. Vous pouvez écrire votre propre tampon circulaire avec seulement 2 Tableaux et pointeurs en utilisant les drapeaux d'interruption UART.
avoir une approche différente patching par exemple" void USART2_IRQHandler(void) "dans le fichier" stm32l0xx_it.c" (ou l4xx si nécessaire). Chaque fois qu'un caractère est reçu cette interruption est appelée. Il y a de l'espace pour insérer du code utilisateur qui reste inchangé lors de la mise à jour avec CubeMX code generator. Patch:
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 je démarre la fonction receive IT. Jusqu'à 115200 bauds il n'a jamais consommé plus de 1 octet laissant le reste de la zone tampon inutilisé.
st = HAL_UART_Receive_IT( &huart2, (uint8_t*)rx2BufIT, RX_BUF_IT_SIZE );
quand je reçois un octet, je le capture et je le mets dans mon propre ring-buffer et je renvoie le caractère-pointer et-counter:
// 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. Recevoir un seul octet en L'utilisant ou DMA dés-initialise toujours et doit initialiser à nouveau le processus de réception qui s'est avéré être trop lent. Le code ci-dessus est seulement un cadre; j'avais l'habitude de compter les caractères newline ici dans une structure d'état qui me permet à tout moment de lire lignes complétées à partir du ring-buffer. Aussi une vérification si un caractère reçu ou un autre événement a causé l'interruption doit être inclus.
EDIT:
Cette méthode a bien fonctionné avec les USARTS qui ne sont pas supportés par DMA et l'utilisent à la place.
L'utilisation de Dma avec 1 byte en mode circulaire est plus courte et plus facile à mettre en œuvre lorsqu'on utilise CubeMX generator avec la bibliothèque HAL.