czwartek, 18 listopada 2021

STM32H7 - FreeRTOS - RS485, przerwanie RX od USART

W tym poście chciałbym opisać komunikację po RS485.

[Źródło: https://www.st.com/content/st_com/en/products/evaluation-tools/product-evaluation-tools/mcu-mpu-eval-tools/stm32-mcu-mpu-eval-tools/stm32-nucleo-boards/nucleo-h753zi.html#overview]

Program:


Na samym początku uruchomienie interfejsu USART oraz ustawienie pinu odpowiadającego za ustawienie kierunku transmisji. Ta część została opisana w innym poście dla tego układu pod tym linkiem.

  1. void RS485_InitProt(void) {
  2.     RS485_InitRTSPin();
  3.     RS485_SetControlPinToReceiveData();
  4.     RS485_USART2_Init();
  5.     RS485_SetControlPinToSendData();
  6. }

  1. static void RS485_SetControlPinToSendData(void) {
  2.     RS485_RTS_PORT->BSRR = RS485_RTS_PIN;
  3. }
  4.  
  5. static inline void RS485_SetControlPinToReceiveData(void) {
  6.     RS485_RTS_PORT->BSRR = (uint32_t)RS485_RTS_PIN << 16U;
  7. }
  8.  
  9. static void RS485_InitRTSPin(void) {
  10.     GPIO_InitTypeDef GPIO_InitStruct = {0};
  11.  
  12.     __HAL_RCC_GPIOB_CLK_ENABLE();
  13.  
  14.     GPIO_InitStruct.Pin = GPIO_PIN_5;
  15.     GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  16.     GPIO_InitStruct.Pull = GPIO_NOPULL;
  17.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  18.     HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
  19. }
  20.  
  21. static uint8_t RS485_USART2_Init(void) {
  22.     huart2.Instance = USART2;
  23.     huart2.Init.BaudRate = 9600;
  24.     huart2.Init.WordLength = UART_WORDLENGTH_8B;
  25.     huart2.Init.StopBits = UART_STOPBITS_1;
  26.     huart2.Init.Parity = UART_PARITY_NONE;
  27.     huart2.Init.Mode = UART_MODE_TX_RX;
  28.     huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  29.     huart2.Init.OverSampling = UART_OVERSAMPLING_16;
  30.     huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
  31.     huart2.Init.ClockPrescaler = UART_PRESCALER_DIV1;
  32.     huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
  33.  
  34.     if (HAL_UART_Init(&huart2) != HAL_OK) { return 0x01u;  }
  35.     if (HAL_UARTEx_SetTxFifoThreshold(&huart2, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK) { return 0x02u; }
  36.     if (HAL_UARTEx_SetRxFifoThreshold(&huart2, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK) { return 0x03u; }
  37.     if (HAL_UARTEx_DisableFifoMode(&huart2) != HAL_OK) { return 0x04u; }
  38.  
  39.     HAL_NVIC_DisableIRQ(USART2_IRQn);
  40.     HAL_NVIC_SetPriority(USART2_IRQn, 0, 1);
  41.     HAL_NVIC_EnableIRQ(USART2_IRQn);
  42.     HAL_NVIC_ClearPendingIRQ(USART2_IRQn);
  43.     __HAL_UART_ENABLE_IT(&huart2, UART_IT_RXNE);
  44.     USART2->CR1 |= USART_CR1_RXNEIE;
  45.  
  46.     return 0x00u;
  47. }

Standardowa obsługa przerwania będzie wyglądała następująco:

  1. void USART2_IRQHandler(void) {
  2.   /* USER CODE BEGIN USART2_IRQn 0 */
  3.  
  4.   /* USER CODE END USART2_IRQn 0 */
  5.   HAL_UART_IRQHandler(&huart2);
  6.   /* USER CODE BEGIN USART2_IRQn 1 */
  7.   RS485_GetDataToBuffer();
  8.   /* USER CODE END USART2_IRQn 1 */
  9. }
  10.  
  11. void RS485_GetDataToBuffer(void) {
  12.     uint8_t dane = 0;
  13.     dane = ((USART2)->RDR);
  14.     RS485_SaveDataToBuffer(dane);
  15.     RS485_ClearRXFlags();
  16. }
  17.  
  18. static void RS485_ClearRXFlags(void) {
  19.     #ifdef __HAL_UART_CLEAR_PEFLAG
  20.         __HAL_UART_CLEAR_PEFLAG(&huart2);
  21.     #endif
  22.     #ifdef __HAL_UART_CLEAR_FEFLAG
  23.         __HAL_UART_CLEAR_FEFLAG(&huart2);
  24.     #endif
  25.     #ifdef __HAL_UART_CLEAR_NEFLAG
  26.         __HAL_UART_CLEAR_NEFLAG(&huart2);
  27.     #endif
  28.     #ifdef __HAL_UART_CLEAR_OREFLAG
  29.         __HAL_UART_CLEAR_OREFLAG(&huart2);
  30.     #endif
  31.     #ifdef __HAL_UART_CLEAR_IDLEFLAG
  32.         __HAL_UART_CLEAR_IDLEFLAG(&huart2);
  33.     #endif
  34.  
  35.     HAL_NVIC_ClearPendingIRQ(USART2_IRQn);
  36. }
  37.  
  38. static void RS485_SaveDataToBuffer(uint8_t recData) {
  39.     RS485_Readers_Comm.recDataTimeout = 0;
  40.     RS485_Readers_Comm.receiveData[RS485_Readers_Comm.recDataCounter] = recData;
  41.     RS485_Readers_Comm.recDataCounter++;
  42.     RS485_Readers_Comm.startRecData = 1;
  43. }

Obsługa odebranych danych odbywa się w zadaniu:

  1. void TaskRS485(void const *argument) {
  2.     RS845_InitParameters(&RS485_Readers_Comm, &deviceSetting);
  3.     RS485_SetStartData(&RS485_Readers_Comm, &deviceSetting);
  4.  
  5.     for(;;) {
  6.        RS485_Send_Command(&RS485_Readers_Comm);
  7.        RS485_CheckTimeoutAfterSendData(&RS485_Readers_Comm, &RS485_HeadData_t[0]);
  8.        RS485_SendResetCommandToHead(&RS485_Readers_Comm, &RS485_HeadData_t[0]);
  9.        RS485_ReaderCommunication(&RS485_Readers_Comm, &RS485_HeadData_t[0]);
  10.        osDelay(200);
  11.     }
  12. }

W celu synchronizacji przerwania z zadaniem można wykorzystać semafor lub task notification. Poniżej przedstawię przykład z drugim rozwiązaniem.

Na samym początku należy wywołać zadanie gdy pojawi się przerwanie od odebranych danych z UART. 

  1. void USART2_IRQHandler(void)
  2. {
  3.   /* USER CODE BEGIN USART2_IRQn 0 */
  4.  
  5.   /* USER CODE END USART2_IRQn 0 */
  6.   HAL_UART_IRQHandler(&huart2);
  7.   /* USER CODE BEGIN USART2_IRQn 1 */
  8.   BaseType_t xHigherPriorityTaskWoken = pdFALSE;
  9.   configASSERT( xTaskToNotifyatUARTRx != NULL );
  10.   vTaskNotifyGiveFromISR( xTaskToNotifyatUARTRx, &xHigherPriorityTaskWoken );
  11.   portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
  12.   /* USER CODE END USART2_IRQn 1 */
  13. }

Poniżej obsługa odebranych danych. 

  1. TaskHandle_t xTaskToNotifyatUARTRx;
  2. osThreadId taskUARTListeHandle;
  3.  
  4. void TaskUsart(void const *argument) {
  5.     configASSERT( xTaskToNotifyatUARTRx == NULL );
  6.     xTaskToNotifyatUARTRx = xTaskGetCurrentTaskHandle();
  7.  
  8.     uint8_t receiveData = 0x00;
  9.  
  10.     for(;;) {
  11.         HAL_UART_Receive_IT(&huart2, &receiveData, 1);
  12.         uint32_t ulNotificationValue = ulTaskNotifyTake( pdTRUE, portMAX_DELAY );
  13.         if(ulNotificationValue == 1) {
  14.             RS485_SaveDataInBuffer(receiveData);
  15.         }
  16.         else { }
  17.     }
  18. }
  19.  
  20. void RS485_SaveDataInBuffer(uint8_t data) {
  21.     RS485_SaveDataToBuffer(data);
  22. }
  23.  
  24. static void RS485_SaveDataToBuffer(uint8_t recData) {
  25.     RS485_Readers_Comm.recDataTimeout = 0;
  26.     RS485_Readers_Comm.receiveData[RS485_Readers_Comm.recDataCounter] = recData;
  27.     RS485_Readers_Comm.recDataCounter++;
  28.     RS485_Readers_Comm.startRecData = 1;
  29. }

Na samym początku następuje odbiór pojedynczego bajta danych. Dalej dane zostają dodane do bufora, ich odbiór jest obsłużony w osobnym zadaniu.