Résolvez le problème selon lequel le port série STM32 ne peut pas recevoir de données en même temps lors du téléchargement de données à haute vitesse

1. Description du problème

Dans un projet précédent, le port série 3 du STM32F7 était utilisé pour la transmission de données et les données du robot étaient téléchargées régulièrement. L'ordinateur supérieur (station au sol) recevait les données et s'affichait sur l'interface. En même temps, l'ordinateur supérieur pouvait envoyer données au robot pour atteindre l'objectif de réglage des paramètres et d'étalonnage. Le téléchargement des données est normal, la distribution des données a toujours été un peu problématique et cela ne fonctionne pas à tout moment. Si vous modifiez les paramètres pendant un certain temps, vous pouvez le modifier pendant un certain temps, ce qui est très fascinant. Parce que le lien pour le téléchargement des données est l'envoi du port série supérieur de l'ordinateur - le port série inférieur de l'ordinateur reçoit - l'ordinateur inférieur modifie les paramètres - l'ordinateur inférieur télécharge de nouveaux paramètres - l'ordinateur supérieur reçoit - l'ordinateur supérieur affiche les nouveaux paramètres, je suis toujours confus, je pensais qu'il y avait un problème avec l'ordinateur hôte (multithreading LabWindows). Récemment, j'ai le temps de résumer, et finalement localisé le problème est que le port série STM32 se verrouille et ne peut pas recevoir de données lorsqu'il est téléchargé à un haute vitesse .

Permettez-moi de commencer par la conclusion: la raison de l'impossibilité de recevoir est l'utilisation de HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)cette fonction, qui provoque le blocage du port série.

2. Solution

(1) Utilisez la transmission d'octets au lieu de la transmission de trame

Après le débogage, on constate qu'il y a HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)un problème avec cette fonction. Jetons un œil à ce qui est fait à l'intérieur de cette fonction.

HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
    
    
	uint16_t *tmp;
	uint32_t tickstart = 0U;

	/* Check that a Tx process is not already ongoing */
	if (huart->gState == HAL_UART_STATE_READY)
	{
    
    
		if ((pData == NULL) || (Size == 0U))
		{
    
    
			return HAL_ERROR;
		}

		/* Process Locked */
		__HAL_LOCK(huart);

		huart->ErrorCode = HAL_UART_ERROR_NONE;
		huart->gState = HAL_UART_STATE_BUSY_TX;

		/* Init tickstart for timeout managment*/
		tickstart = HAL_GetTick();

		huart->TxXferSize = Size;
		huart->TxXferCount = Size;
		while (huart->TxXferCount > 0U)
		{
    
    
			huart->TxXferCount--;
			if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK)
			{
    
    
				return HAL_TIMEOUT;
			}
			if ((huart->Init.WordLength == UART_WORDLENGTH_9B) && (huart->Init.Parity == UART_PARITY_NONE))
			{
    
    
				tmp = (uint16_t *)pData;
				huart->Instance->TDR = (*tmp & (uint16_t)0x01FFU);
				pData += 2;
			}
			else
			{
    
    
				huart->Instance->TDR = (*pData++ & (uint8_t)0xFFU);
			}
		}
		if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TC, RESET, tickstart, Timeout) != HAL_OK)
		{
    
    
			return HAL_TIMEOUT;
		}

		/* At end of Tx process, restore huart->gState to Ready */
		huart->gState = HAL_UART_STATE_READY;

		/* Process Unlocked */
		__HAL_UNLOCK(huart);

		return HAL_OK;
	}
	else
	{
    
    
		return HAL_BUSY;
	}
}

Il y a deux lignes de code __HAL_LOCK(huart)et les __HAL_UNLOCK(huart)commentaires indiquent que le processus est verrouillé et que le processus est déverrouillé. Cet élément sera tué lorsque vous le verrez. Le port série peut-il être verrouillé et l'interruption de réception peut être saisie? ? C'est difficile à dire, mais il y a un problème. HAL_UART_TransmitIl s'agit d'envoyer un tas de données à la fois, une fois que la quantité de données envoyées est importante, cela conduira inévitablement à l'échec de la réception normale. D'accord, changeons la méthode, au lieu de l'envoyer sous forme de trame de données, en l'envoyant en un seul octet.

Regardez la bibliothèque standard précédente qui a une fonction d'envoi à un octet:

void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)
{
    
    
  /* Check the parameters */
  assert_param(IS_USART_ALL_PERIPH(USARTx));
  assert_param(IS_USART_DATA(Data)); 
    
  /* Transmit Data */
  USARTx->DR = (Data & (uint16_t)0x01FF);
}

Mais la bibliothèque HAL est partie, pas plus. . . Peu importe, écrivez-en un vous-même:

int Uart1SendChar(u8 ch)
{
    
    
    while((USART1->ISR&0X40)==0);//循环发送,直到发送完毕   
	USART1->TDR=(u8)ch; 
    return ch;	
}

C'est très simple, c'est-à-dire, remplissez toujours les données dans le registre de données d'envoi TDR, et elles seront envoyées.

Modifiez maintenant les données d'envoi pour utiliser la fonction ci-dessus pour envoyer. Par exemple, il s'est avéré utiliser

HAL_UART_Transmit(&UART1_Handler, send_buf, 25, 1000);

Correspond à

for (i=0;i<25;i++)
{
    
    
	Uart1SendChar(send_buf[i]);
}

Tester à nouveau, bingo, la réception est normale.

(2) Utilisez DMA pour envoyer

La deuxième méthode consiste à utiliser DMA, accès direct au stockage, elle présente l'avantage que la transmission des données ne passe pas par le CPU, ce qui économise des ressources CPU et améliore la vitesse de calcul. Il existe de nombreux tutoriels sur le port série DMA, et l'un des problèmes les plus ennuyeux est le traitement des fonctions d'interruption.La bibliothèque HAL m'a vraiment aveuglé. Bien que la fonction d'envoi et de réception de données de longueur variable ait finalement été réalisée, la bibliothèque ne l'a pas comprise et s'est sentie très triste. Plus tard, je prévois d'écrire un article sur le mécanisme d'interruption de la communication série / communication série DMA pour sécher complètement le code de la bibliothèque HAL, donc je n'entrerai pas dans les détails ici.

Ensuite, nous utiliserons le DMA du port série 1 pour envoyer des données et le DMA pour recevoir des données de longueur variable, puis utiliserons la méthode normale du port série 2 pour imprimer les données reçues par le port série 1 à des fins d'observation.

Créez un nouveau projet, ouvrez le port série 1, le port série 2 et le port série 3 dans Cubemx, ouvrez leur DMA en même temps et vérifiez l'interruption.

Insérez la description de l'image ici
Insérez la description de l'image iciLa configuration des ports série 1, 2 et 3. est la même. Faites simplement attention à sélectionner le Sream correspondant.

Le code généré par défaut contient déjà le code du pilote pour le port série et le DMA. Appelez- HAL_UART_Transmit_DMAle directement .

Comme l'interruption d'inactivité du port série est utilisée pour recevoir des données de longueur variable, l'interruption d'inactivité du port série doit être activée et la fonction d'initialisation du port série par défaut doit être modifiée.

static void MX_USART1_UART_Init(void)
{
    
    

	/* USER CODE BEGIN USART1_Init 0 */

	/* USER CODE END USART1_Init 0 */

	/* USER CODE BEGIN USART1_Init 1 */

	/* USER CODE END USART1_Init 1 */
	huart1.Instance = USART1;
	huart1.Init.BaudRate = 115200;
	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;
	huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
	huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
	if (HAL_UART_Init(&huart1) != HAL_OK)
	{
    
    
		Error_Handler();
	}
	/* USER CODE BEGIN USART1_Init 2 */
	__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);		   //使能idle中断
	HAL_UART_Receive_DMA(&huart1, rx_buffer, BUFFER_SIZE); //打开DMA接收,数据存入rx_buffer数组中。
	/* USER CODE END USART1_Init 2 */
}

Les deux dernières lignes de code sont ajoutées, ce qui permet d'ouvrir l'interruption IDLE et de démarrer le transfert DMA du port série.

Ajoutez ensuite la fonction d'interruption, ajoutez d'abord la déclaration de données externes dans le fichier d'interruption (stm32F7xxx.it.c) généré par cubemx,

extern volatile uint8_t rx_len;
extern volatile uint8_t recv_end_flag;
extern uint8_t rx_buffer[100];
extern char BUFFER_SIZE;

L'extern utilisé, car il est défini dans main.c, ajoute la définition à l'en-tête de main.c:

volatile uint8_t rx_len;
volatile uint8_t recv_end_flag;
uint8_t rx_buffer[100];
char BUFFER_SIZE;

La fonction d'interruption y est écrite directement USART1_IRQHandler(void)et la fonction de rappel est l'enfer. . .

void USART1_IRQHandler(void)
{
    
    
	/* USER CODE BEGIN USART1_IRQn 0 */

	/* USER CODE END USART1_IRQn 0 */
	HAL_UART_IRQHandler(&huart1);
	/* USER CODE BEGIN USART1_IRQn 1 */

	uint32_t tmp_flag = 0;
	uint32_t temp;
	tmp_flag = __HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE);    //获取IDLE标志位
	if ((tmp_flag != RESET))      //idle标志被置位
	{
    
    
		__HAL_UART_CLEAR_IDLEFLAG(&huart1);
	    //清除标志位
		temp = huart1.Instance->ISR; 			//清除状态寄存器SR(F0的HAL库USART_TypeDef结构体中名字为ISR:USART Interrupt and status register),读取SR可以清楚该寄存器
		temp = huart1.Instance->RDR; 			//读取数据寄存器中的数据,读取DR(F0中为RDR:USART Receive Data register)
		HAL_UART_DMAStop(&huart1);
		temp = hdma_usart1_rx.Instance->NDTR;   //获取DMA中未传输的数据个数,NDTR寄存器分析见下面
		rx_len = BUFFER_SIZE - temp;       		//总计数减去未传输的数据个数,得到已经接收的数据个数
		recv_end_flag = 1;                 		//接受完成标志位置1
	}
	/* USER CODE END USART1_IRQn 1 */
}

Dans la fonction d'interruption, obtenez d'abord le bit indicateur d'interruption inactif IDLE, puis effacez le bit indicateur, arrêtez la transmission DMA du port série, obtenez le nombre de données reçues, définissez l'indicateur d'achèvement de la réception et placez le traitement des données reçues dans la fonction principale .

Ajoutez les tampons d'envoi du port série 1 et du port série 2 dans main.c:

uint8_t i = 0;
uint8_t count = 0;
uint8_t send_buf[25] = {
    
    0};
uint8_t send[25] = {
    
    0};
send_buf[0] = 0xAA;
send_buf[1] = 0xAA;
for (i = 2; i < 25; i++)
	send_buf[i] = 0x50;

Puis modifiez la boucle while (1)

while (1)
{
    
    
	/* USER CODE END WHILE */
	HAL_UART_Transmit_DMA(&huart1, send_buf, 25); //开启DMA传输
												  //	HAL_UART_Transmit(&huart1,send_buf,25,1000);//开启DMA传输
	if (recv_end_flag == 1)
	{
    
    
		//printf("rx_len=%d\r\n", rx_len);					//打印接收长度
		count++;
		send[0] = count; 
		HAL_UART_Transmit(&huart2, send, 1, 200);			//接收数据打印出来,标记接收数据的数量
		HAL_UART_Transmit(&huart2, rx_buffer, rx_len, 200); //接收数据打印出来
		HAL_UART_Transmit(&huart2, &send[1], 2, 200); //接收数据打印出来
		for (uint8_t i = 0; i < rx_len; i++)
		{
    
    
			rx_buffer[i] = 0; //清接收缓存
		}
		rx_len = 0;											   //清除计数
		recv_end_flag = 0;									   //清除接收结束标志位
		HAL_UART_Receive_DMA(&huart1, rx_buffer, BUFFER_SIZE); //重新打开DMA接收
	}

	HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
	HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_1);
	HAL_Delay(20);
	/* USER CODE BEGIN 3 */
}

Dans la boucle while, les données sont téléchargées régulièrement via le port série 1 DMA, et les données reçues par le port série 1 sont imprimées via le port série 2. Ouvrez les deux assistants de débogage du port série sur l'ordinateur, connectez respectivement les deux ports série et vérifiez les données renvoyées depuis le port série 1 et le port série 2. Comme le montre la figure, le côté gauche représente les données renvoyées par le port série 1. Cliquez sur Envoyer pour envoyer les données à la carte. Les données de droite sont les données renvoyées par le port série 2. Cliquez sur Envoyer sur la gauche et les données envoyées sur la gauche seront affichées sur la droite.

J'ai remarqué qu'à chaque fois qu'il y a une trame de bonnes données et une mauvaise trame de données, eh bien, je suis à nouveau fasciné, fasciné. . . Débogage.

Insérez la description de l'image ici

Je suppose que tu aimes

Origine blog.csdn.net/qq_30267617/article/details/115276566
conseillé
Classement