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에서 다시 전송 한 데이터입니다. 왼쪽의 보내기를 클릭하면 왼쪽에 보낸 데이터가 오른쪽에 표시됩니다.
올바른 데이터 프레임과 잘못된 데이터 프레임이있을 때마다 나는 다시 매료되고 매료되었습니다. . . 디버깅.