고속 데이터 업로드시 STM32 시리얼 포트가 동시에 데이터를 수신 할 수없는 문제 해결

1. 문제 설명

이전 프로젝트에서는 STM32F7 직렬 포트 3을 데이터 전송에 사용하고 로봇 데이터를 정기적으로 업로드했습니다. 상위 컴퓨터 (지상국)가 데이터를 수신하여 인터페이스에 표시했습니다. 동시에 상위 컴퓨터가 전송할 수있었습니다. 매개 변수 조정 및 교정 목적을 달성하기 위해 로봇에 데이터를 전송합니다. 데이터 업로드는 정상이고 데이터 배포는 항상 약간 문제가 있고 항상 작동하지 않습니다. 잠시 동안 매개 변수를 변경하면 잠시 변경할 수 있습니다. 매우 매력적입니다. 데이터 다운로드를위한 링크는 상위 컴퓨터 직렬 포트 전송이기 때문에-하단 컴퓨터 직렬 포트 수신-하단 컴퓨터는 매개 변수를 수정-하단 컴퓨터는 새 매개 변수를 업로드-상단 컴퓨터는 수신-상단 컴퓨터는 새 매개 변수를 표시합니다. 호스트 컴퓨터 (LabWindows 멀티 스레딩)에 문제가 있다고 항상 혼란 스러웠습니다. 최근 요약 할 시간이 있는데 마지막으로 문제는 STM32 직렬 포트가 잠기고 데이터를 수신 할 수 없다는 것입니다. 고속 .

결론부터 시작하겠습니다 : 수신 할 수없는 이유는 HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)이 기능을 사용하기 때문에 직렬 포트가 차단되기 때문입니다. 때로는 STM32의 HAL 라이브러리 기능이 실제로 내 것입니다.

2. 솔루션

(1) 프레임 전송 대신 바이트 전송 사용

디버깅 후이 HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)함수에 문제 있는 것으로 확인 되었으니이 함수 안에서 어떤 일이 일어나는지 살펴 보겠습니다.

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;
	}
}

두 줄의 코드가 __HAL_LOCK(huart)있고 __HAL_UNLOCK(huart)주석은 프로세스가 잠겨 있고 프로세스가 잠금 해제되었다고 말합니다.이 것은 당신이 볼 때 죽을 것입니다. 직렬 포트를 잠그고 수신 인터럽트를 입력 할 수 있습니까? ? 말하기 어렵지만 문제가 있습니다. HAL_UART_Transmit한 번에 많은 데이터를 전송하는 것인데, 전송 된 데이터의 양이 많으면 불가피하게 정상적인 수신에 실패하게됩니다. 좋아요, 데이터 프레임으로 보내는 대신 단일 바이트로 보내는 방법을 변경해 보겠습니다.

단일 바이트 전송 기능이있는 이전 표준 라이브러리를 살펴보십시오.

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);
}

그러나 HAL 라이브러리는 사라졌습니다. . . 상관 없습니다. 직접 작성하세요.

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

이것은 매우 간단합니다. 즉, 항상 송신 데이터 레지스터 TDR에 데이터를 채우면 전송됩니다.

이제 전송 데이터를 변경하여 위의 기능을 사용하여 전송하십시오. 예를 들어,

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

에 해당하는

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

다시 테스트하십시오. 빙고, 수신은 정상입니다.

(2) DMA를 사용하여 전송

두 번째 방법은 DMA, 직접 스토리지 액세스를 사용하는 것입니다. 데이터 전송이 CPU를 통과하지 않아 CPU 리소스를 절약하고 계산 속도를 향상시키는 이점이 있습니다. 직렬 포트 DMA에 대한 많은 자습서가 있으며 매우 성가신 문제 중 하나는 인터럽트 기능의 처리입니다 .HAL 라이브러리는 나를 정말 눈 멀게했습니다. 가변 길이 데이터를주고받는 기능이 마침내 실현되었지만 도서관은 그것을 이해하지 못하고 매우 슬펐습니다. 나중에 HAL 라이브러리 코드를 완전히 건조시키기 위해 직렬 통신 / 직렬 DMA 통신의 인터럽트 메커니즘에 대한 기사를 작성할 계획이므로 여기서는 자세히 설명하지 않겠습니다.

다음으로 직렬 포트 1의 DMA를 사용하여 데이터를 보내고 DMA를 사용하여 가변 길이 데이터를 수신 한 다음 직렬 포트 2의 일반적인 방법을 사용하여 직렬 포트 1에서 수신 한 데이터를 출력하여 관찰합니다.

새 프로젝트를 만들고 Cubemx에서 직렬 포트 1, 직렬 포트 2 및 직렬 포트 3을 열고 DMA를 동시에 열고 인터럽트를 확인합니다.

여기에 사진 설명 삽입
여기에 사진 설명 삽입직렬 포트 1, 2, 3의 구성은 동일하므로 해당 Sream을 선택하는 데주의하십시오.

기본적으로 생성 된 코드에는 이미 직렬 포트 및 DMA에 대한 드라이버 코드가 있습니다. 직접 전화 HAL_UART_Transmit_DMA하십시오.

직렬 포트 유휴 인터럽트는 가변 길이 데이터를 수신하는 데 사용되므로 직렬 포트 유휴 인터럽트를 활성화하고 기본 직렬 포트 초기화 기능을 수정해야합니다.

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 */
}

마지막 두 줄의 코드가 추가되어 IDLE 인터럽트를 열고 직렬 포트 DMA 전송을 시작합니다.

그런 다음 인터럽트 함수를 추가하고 먼저 cubemx가 생성 한 인터럽트 파일 (stm32F7xxx.it.c)에 외부 데이터 선언을 추가하십시오.

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

사용 된 extern은 main.c에 정의되어 있으므로 main.c의 헤드에 정의를 추가합니다.

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

인터럽트 함수는 직접 작성되며 USART1_IRQHandler(void)콜백 함수는 지옥입니다. . .

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 */
}

인터럽트 기능에서 먼저 IDLE 유휴 인터럽트 플래그 비트를 얻은 다음 플래그 비트를 지우고 직렬 포트 DMA 전송을 중지하고 수신 된 데이터 수를 얻고 수신 완료 플래그를 설정하고 수신 된 데이터 처리를 주 기능에 배치합니다. .

main.c에 직렬 포트 1과 직렬 포트 2의 전송 버퍼를 추가합니다.

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;

그런 다음 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 */
}

while 루프에서 데이터는 직렬 포트 1 DMA를 통해 정기적으로 업로드되고 직렬 포트 1에서 수신 한 데이터는 직렬 포트 2를 통해 인쇄됩니다. 컴퓨터에서 두 개의 직렬 포트 디버깅 도우미를 열고 두 개의 직렬 포트를 각각 연결 한 다음 직렬 포트 1과 직렬 포트 2에서 다시 전송 된 데이터를 확인합니다. 그림과 같이 왼쪽은 시리얼 포트 1로 다시 전송 된 데이터입니다. Send를 클릭하여 보드로 데이터를 전송합니다. 오른쪽의 데이터는 직렬 포트 2에서 다시 전송 한 데이터입니다. 왼쪽의 보내기를 클릭하면 왼쪽에 보낸 데이터가 오른쪽에 표시됩니다.

올바른 데이터 프레임과 잘못된 데이터 프레임이있을 때마다 나는 다시 매료되고 매료되었습니다. . . 디버깅.

여기에 사진 설명 삽입

추천

출처blog.csdn.net/qq_30267617/article/details/115276566