/** ****************************************************************************** * @file uart.c * @author Разваляев Алексей * @brief Драйвер UART на основе PLIB035. * Этот файл содержит: * + Инициализацию UART0/UART1 * + Управление FIFO * + Передачу и приём данных в blocking и interrupt режимах * + Общий обработчик прерываний UART * + Функции настройки GPIO для UART * + Очередь передачи для предотвращения потери данных * ****************************************************************************** * @attention * * Использование этого драйвера предполагает наличие корректных настроек: * - Определены конфигурационные структуры uartx_config в periph_config.h * - Определены пины TX/RX для UART0/UART1 * ****************************************************************************** * @verbatim ============================================================================== ##### Как использовать этот драйвер ##### ============================================================================== 1. Настройка в periph_config.h: (+) Определите uart0_config, uart1_config для нужных UART (+) Используйте макросы для направления: • UART_Direction_None - прием и передача отключены • UART_Direction_RxTx - полный дуплекс • UART_Direction_Tx - только передача • UART_Direction_Rx - только прием 2. Инициализация: (+) uart_init_first() - настройка GPIO, тактирования и прерываний (+) uart_init(&huart, &config) - инициализация конкретного UART 3. Callback-функции: (+) UART_Set_Callback(&huart, UART_Callback_Rx, func) - при завершении приема (+) UART_Set_Callback(&huart, UART_Callback_Tx, func) - при завершении передачи (+) UART_Set_Callback(&huart, UART_Callback_Idle, func) - при обнаружении IDLE (+) UART_Set_Callback(&huart, UART_Callback_Error, func) - при ошибках 4. Запуск UART: (+) UART_Start(&huart, TxFifoLevel, RxFifoLevel) - включение UART и настройка FIFO 5. Передача и приём данных: - Режим Polling: (+) UART_Transmit(&huart, buf, size, timeout) - блокирующая передача (+) UART_Receive(&huart, buf, size, timeout) - блокирующий прием - Режим Interrupt: (+) UART_Transmit_IT(&huart, buf, size) - неблокирующая передача (+) UART_Receive_IT(&huart, buf, size) - неблокирующий прием 6. Обработка прерываний: (+) Прерывания обрабатывают TX FIFO, RX FIFO, ошибки и состояние IDLE 7. GPIO для UART: (+) Пины настраиваются автоматически при вызове uart_init_first() (+) При необходимости можно вызвать uart0_gpio_deinit() для восстановления ============================================================================== ##### Особенности работы ##### ============================================================================== - Очередь передачи (USE_TX_QUEUE): - Циклический буфер на 32 сообщения (TX_QUEUE_SIZE) - Предотвращает потерю данных при частой отправке - Включается автоматически при USE_TX_QUEUE=1 в uart.h - Сообщения обрабатываются последовательно по завершении предыдущей передачи - FIFO и прерывания: - Аппаратный FIFO 16 байт для приема и передачи - Уровни прерываний настраиваются через TxFifoLevel и RxFifoLevel - Прерывание TX FIFO генерируется когда FIFO не полон - Прерывание RX FIFO генерируется когда FIFO не пуст - Состояние IDLE: - Обнаруживается по таймауту приема (32 бит-времени) - Генерирует прерывание RecieveTimeout - Полезно для определения конца пакета переменной длины - Обработка ошибок: - Обрабатываются все типы ошибок UART: фрейм, паритет, переполнение, break - При ошибке вызывается ErrCallback и сбрасываются флаги ошибок - Ошибки не останавливают работу UART - GPIO автоматическая настройка: - Пины TX/RX настраиваются в AltFunc режим при инициализации - Поддерживаются стандартные пины (PB10/PB11 для UART0, PB8/PB9 для UART1) - Режимы работы: - Polling: простой, но блокирующий, подходит для инициализации и отладки - Interrupt: неблокирующий, требует прерываний и поддерживает callback-функций - Queue: расширенный interrupt режим с буферизацией сообщений @endverbatim ****************************************************************************** */ //-- Includes ------------------------------------------------------------------ #include "periph_config.h" UART_HandleTypeDef huart0; /*!< Хендл UART0 */ UART_HandleTypeDef huart1; /*!< Хендл UART1 */ //-- Private function prototypes ----------------------------------------------- static int __uart_fifo_receive(UART_HandleTypeDef *huart, uint8_t it_mode); static int __uart_fifo_transmit(UART_HandleTypeDef *huart, uint8_t it_mode); #if USE_TX_QUEUE==1 static void __uart_tx_queue_process(UART_HandleTypeDef *huart); static OperationStatus __uart_tx_queue_push(UART_HandleTypeDef *huart, const uint8_t *buf, uint16_t size); static OperationStatus __uart_tx_queue_pop(UART_HandleTypeDef *huart); #endif //-- Defines ------------------------------------------------------------------- //-- UART Init functions ------------------------------------------------------- /** * @brief Первичная инициализация UART (UART0 / UART1) * @details Настройка UART и хендлов: GPIO, тактирование и прерывания */ void uart_init_first(void) { #if (USE_UART0==1) // Настройка пинов для UART0 uart0_gpio_init(); // Включаем тактирование UART0 RCU_UARTClkConfig(UART0_Num, RCU_PeriphClk_PLLClk, 0, DISABLE); RCU_UARTClkCmd(UART0_Num, ENABLE); UART_DeInit(UART0); // Инициализируем UART0 huart0.Instance = UART0; uart_init(&huart0, &uart0_config); NVIC_EnableIRQ(UART0_TD_IRQn); NVIC_EnableIRQ(UART0_RX_IRQn); NVIC_EnableIRQ(UART0_TX_IRQn); NVIC_EnableIRQ(UART0_E_RT_IRQn); #endif #if (USE_UART1==1) // Настройка пинов для UART1 uart1_gpio_init(); // Включаем тактирование UART1 RCU_UARTClkConfig(UART1_Num, RCU_PeriphClk_PLLClk, 0, DISABLE); RCU_UARTClkCmd(UART1_Num, ENABLE); UART_DeInit(UART1); // Инициализируем UART1 NVIC_EnableIRQ(UART1_TD_IRQn); NVIC_EnableIRQ(UART1_RX_IRQn); NVIC_EnableIRQ(UART1_TX_IRQn); NVIC_EnableIRQ(UART1_E_RT_IRQn); huart1.Instance = UART1; uart_init(&huart1, &uart1_config); #endif } /** * @brief Инициализация UART * @param htmr указатель на хендл UART * @param NewConfig указатель на новую конфигурацию UART, иначе используется та, что в структуре * @retval OperationStatus OK - если всё хорошо, ERROR - если ошибка */ OperationStatus uart_init(UART_HandleTypeDef *huart, UART_ExtInit_TypeDef *NewConfig) { if(!huart || !huart->Instance) return ERROR; if(NewConfig != NULL) { huart->Config = NewConfig; } if(huart->Config == NULL) { return ERROR; } UART_Init(huart->Instance, &huart->Config->UART_Init); return OK; } /** * @brief Установка коллбека UART * @param htmr указатель на хендл UART * @param CallbackType Тип коллбека * @param Callback Функция коллбека * @retval void */ OperationStatus UART_Set_Callback(UART_HandleTypeDef* huart, UART_CallbackTypeDef CallbackType, void (*Callback)(void)) { if (!huart || !huart->Instance || !huart->Config) return ERROR; switch(CallbackType) { case UART_Callback_Rx: huart->Config->RxCallback = Callback; break; case UART_Callback_Tx: huart->Config->TxCallback = Callback; break; case UART_Callback_Idle: huart->Config->IdleCallback = Callback; break; case UART_Callback_Error: huart->Config->ErrCallback = Callback; break; default: return ERROR; } return OK; } //-- UART API functions -------------------------------------------------------- /** * @brief Запуск UART и инициализация FIFO * @param huart указатель на хендл UART * @param TxFifoLevel уровень прерывания TX FIFO * @param RxFifoLevel уровень прерывания RX FIFO * @retval OperationStatus OK - если успешно, ERROR - при ошибке */ OperationStatus UART_Start(UART_HandleTypeDef *huart, UART_FIFOLevel_TypeDef TxFifoLevel, UART_FIFOLevel_TypeDef RxFifoLevel) { if (!huart) return ERROR; UART_ITFIFOLevelRxConfig(huart->Instance, RxFifoLevel); UART_ITFIFOLevelTxConfig(huart->Instance, TxFifoLevel); huart->TxBufPtr = NULL; huart->TxCount = 0; huart->TxSize = 0; huart->TxBusy = 0; huart->RxBufPtr = NULL; huart->RxCount = 0; huart->RxSize = 0; huart->RxBusy = 0; #if USE_TX_QUEUE==1 huart->TxQueue.QueueHead = 0; huart->TxQueue.QueueTail = 0; huart->TxQueue.QueueCount = 0; huart->TxQueue.QueueEmpty = 1; huart->TxQueue.QueueFull = 0; for (uint8_t i = 0; i < TX_QUEUE_SIZE; i++) { huart->TxQueue.Queue[i].Buf = NULL; huart->TxQueue.Queue[i].Size = 0; huart->TxQueue.Queue[i].InUse = 0; } #endif UART_Cmd(huart->Instance, ENABLE); return OK; } /** * @brief Передача данных по UART (блокирующий режим) * @param huart указатель на хендл UART * @param buf указатель на буфер данных * @param size размер данных в байтах * @param timeout таймаут ожидания (мс) * @retval OperationStatus OK - если успешно, ERROR - при ошибке или таймауте */ OperationStatus UART_Transmit(UART_HandleTypeDef *huart, uint8_t *buf, uint16_t size, uint32_t timeout) { if (!huart || !buf || size == 0) return ERROR; // Если TX занят, возвращаем ERROR if (huart->TxBusy) return ERROR; huart->TxBufPtr = buf; huart->TxSize = size; huart->TxCount = 0; huart->TxBusy = 1; uint32_t starttick = millis(); // Отправляем пока всё не отправится while(__uart_fifo_transmit(huart, 0) == 0) { if(millis() - starttick > timeout) return ERROR; } return OK; } /** * @brief Приём данных по UART (блокирующий режим) * @param huart указатель на хендл UART * @param buf указатель на буфер приёма * @param size количество принимаемых байт * @param timeout таймаут ожидания (мс) * @retval OperationStatus OK - если успешно, ERROR - при ошибке или таймауте */ OperationStatus UART_Receive(UART_HandleTypeDef *huart, uint8_t *buf, uint16_t size, uint32_t timeout) { if (!huart || !buf || size == 0) return ERROR; // Если RX занят, возвращаем ERROR if (huart->RxBusy) return ERROR; huart->RxBufPtr = buf; huart->RxSize = size; huart->RxCount = 0; huart->RxBusy = 1; uint32_t starttick = millis(); // Принимаем всё пока всё не примется while(__uart_fifo_transmit(huart, 0) == 0) { if(millis() - starttick > timeout) return ERROR;; } return OK; } /** * @brief Передача данных по UART (прерывания) * @param huart указатель на хендл UART * @param buf указатель на буфер данных * @param size размер данных в байтах * @retval OperationStatus OK - если успешно, ERROR - если передача уже идёт */ OperationStatus UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *buf, uint16_t size) { if (!huart || !buf || size == 0) return ERROR; #if USE_TX_QUEUE==1 // Автоматически используем очередь if (huart->TxQueue.QueueFull) return ERROR; OperationStatus status = __uart_tx_queue_push(huart, buf, size); if (status != OK) return ERROR; if (!huart->TxBusy) { __uart_tx_queue_process(huart); } return OK; #else // Без очереди // Если TX занят, возвращаем ERROR if (huart->TxBusy) return ERROR; huart->TxBufPtr = buf; huart->TxSize = size; huart->TxCount = 0; huart->TxBusy = 1; // Включаем прерывания по TX FIFO UART_ITCmd(huart->Instance, UART_ITSource_TxFIFOLevel, ENABLE); __uart_fifo_transmit(huart, 1); return OK; #endif } /** * @brief Приём данных по UART (прерывания) * @param huart указатель на хендл UART * @param buf указатель на буфер приёма * @param size количество принимаемых байт * @retval OperationStatus OK - если успешно, ERROR - если приём уже идёт */ OperationStatus UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *buf, uint16_t size) { if (!huart || !buf || size == 0) return ERROR; // Если RX занят, возвращаем ERROR if (huart->RxBusy) return ERROR; huart->RxBufPtr = buf; huart->RxSize = size; huart->RxCount = 0; huart->RxBusy = 1; // Включаем только RX прерывания UART_ITCmd(huart->Instance, UART_ITSource_RxFIFOLevel | UART_ITSource_RecieveTimeout | UART_ITSource_ErrorFrame | UART_ITSource_ErrorParity | UART_ITSource_ErrorOverflow | UART_ITSource_ErrorBreak, ENABLE); return OK; } //-- UART Handler functions --------------------------------------------------- /** * @brief Общий обработчик прерываний UART * @param huart указатель на хендл UART * @details Обрабатывает RX/TX FIFO, ошибки и события Idle * @retval void */ void uart_irq_handler(UART_HandleTypeDef *huart) { if (!huart || !huart->Instance || !huart->Config) return; // GPIO_SetBits(GPIOA, GPIO_Pin_7); UART_TypeDef *uart = huart->Instance; uint32_t mis = uart->MIS; // Ошибки if (mis & (UART_ITSource_ErrorFrame | UART_ITSource_ErrorParity | UART_ITSource_ErrorOverflow | UART_ITSource_ErrorBreak)) { if (huart->Config->ErrCallback) huart->Config->ErrCallback(); UART_ErrorStatusClear(uart, UART_Error_All); UART_ITStatusClear(uart, mis); } // RX FIFO if (mis & UART_ITSource_RxFIFOLevel) { // Принимаем if(__uart_fifo_receive(huart, 1)) { // Когда всё приняли, флаги сбросили внутри функции } UART_ITStatusClear(uart, UART_ITSource_RxFIFOLevel); } // IDLE / Receive Timeout if (mis & UART_ITSource_RecieveTimeout) { UART_ITStatusClear(uart, UART_ITSource_RecieveTimeout); huart->RxBusy = 0; // Выключаем RX прерывания до следующего вызова UART_Receive_IT UART_ITCmd(uart, UART_ITSource_RxFIFOLevel | UART_ITSource_RecieveTimeout, DISABLE); if (huart->Config->IdleCallback) huart->Config->IdleCallback(); } // TX FIFO if (mis & UART_ITSource_TxFIFOLevel) { // Отправляем if(__uart_fifo_transmit(huart, 1)) { // Когда всё отправили, флаги сбросили внутри функции } UART_ITStatusClear(uart, UART_ITSource_TxFIFOLevel); } // Конец передачи if (mis & UART_ITSource_TransmitDone) { UART_ITStatusClear(uart, UART_ITSource_TransmitDone); huart->TxBusy = 0; if (huart->Config->TxCallback) huart->Config->TxCallback(); #if USE_TX_QUEUE==1 // Если есть очередь, обрабатываем следующий пакет __uart_tx_queue_process(huart); #endif } // GPIO_ClearBits(GPIOA, GPIO_Pin_7); } //-- UART GPIO functions ------------------------------------------------------- /** * @brief Инициализация GPIO для UART0 */ void uart0_gpio_init(void) { #if USE_UART0==1 // Получаем структуры GPIO_Init_TypeDef *tx_config = gpio_get_init(UART0_GPIO_Port, UART0_Tx_Pin); GPIO_Init_TypeDef *rx_config = gpio_get_init(UART0_GPIO_Port, UART0_Rx_Pin); // TX пин if (uart0_config.UART_Init.Tx == ENABLE && tx_config != NULL) { GPIO_StructInit(tx_config); tx_config->AltFunc = ENABLE; tx_config->Digital = ENABLE; tx_config->Pin = UART0_Tx_Pin; GPIO_Init(UART0_GPIO_Port, tx_config); } // RX пин if (uart0_config.UART_Init.Rx == ENABLE && rx_config != NULL) { GPIO_StructInit(rx_config); rx_config->AltFunc = ENABLE; rx_config->Digital = ENABLE; rx_config->Pin = UART0_Rx_Pin; GPIO_Init(UART0_GPIO_Port, rx_config); } #endif } /** * @brief Деинициализация GPIO для UART0 */ void uart0_gpio_deinit(void) { #if USE_UART0==1 // Получаем структуры GPIO_Init_TypeDef *tx_config = gpio_get_init(UART0_GPIO_Port, UART0_Tx_Pin); GPIO_Init_TypeDef *rx_config = gpio_get_init(UART0_GPIO_Port, UART0_Rx_Pin); // Восстанавливаем оригинальные настройки из таблицы if (tx_config != NULL) { GPIO_StructInit(rx_config); rx_config->Pin = UART0_Tx_Pin; GPIO_Init(UART0_GPIO_Port, tx_config); } if (rx_config != NULL) { GPIO_StructInit(rx_config); rx_config->Pin = UART0_Rx_Pin; GPIO_Init(UART0_GPIO_Port, rx_config); } #endif } /** * @brief Инициализация GPIO для UART1 */ void uart1_gpio_init(void) { #if USE_UART1==1 // Получаем структуры GPIO_Init_TypeDef *tx_config = gpio_get_init(UART1_GPIO_Port, UART1_Tx_Pin); GPIO_Init_TypeDef *rx_config = gpio_get_init(UART1_GPIO_Port, UART1_Rx_Pin); // TX пин if (uart1_config.UART_Init.Tx == ENABLE && tx_config != NULL) { GPIO_StructInit(tx_config); tx_config->AltFunc = ENABLE; tx_config->Digital = ENABLE; tx_config->Pin = UART1_Tx_Pin; GPIO_Init(UART1_GPIO_Port, tx_config); } // RX пин if (uart1_config.UART_Init.Rx == ENABLE && rx_config != NULL) { ; GPIO_StructInit(rx_config); rx_config->AltFunc = ENABLE; rx_config->Digital = ENABLE; rx_config->Pin = UART1_Rx_Pin; GPIO_Init(UART1_GPIO_Port, rx_config); } #endif } /** * @brief Деинициализация GPIO для UART1 */ void uart1_gpio_deinit(void) { #if USE_UART1==1 // Получаем структуры GPIO_Init_TypeDef *tx_config = gpio_get_init(UART1_GPIO_Port, UART1_Tx_Pin); GPIO_Init_TypeDef *rx_config = gpio_get_init(UART1_GPIO_Port, UART1_Rx_Pin); // Восстанавливаем оригинальные настройки из таблицы if (tx_config != NULL) { GPIO_StructInit(rx_config); rx_config->Pin = UART1_Tx_Pin; GPIO_Init(UART1_GPIO_Port, tx_config); } if (rx_config != NULL) { GPIO_StructInit(rx_config); rx_config->Pin = UART1_Rx_Pin; GPIO_Init(UART1_GPIO_Port, rx_config); } #endif } //-- UART private functions ---------------------------------------------------- /** * @brief Приём данных из RX FIFO * @param huart указатель на хендл UART * @param it_mode режим работы (0 — polling, 1 — прерывания) * @retval int 1 — приём завершён, 0 — данные ещё принимаются */ static int __uart_fifo_receive(UART_HandleTypeDef *huart, uint8_t it_mode) { while (!UART_FlagStatus(huart->Instance, UART_Flag_RxFIFOEmpty) && huart->RxCount < huart->RxSize) { huart->RxBufPtr[huart->RxCount++] = UART_RecieveData(huart->Instance); if (huart->RxCount == huart->RxSize) { huart->RxBusy = 0; if(it_mode) { // Выключаем RX прерывания UART_ITCmd(huart->Instance, UART_ITSource_RxFIFOLevel | UART_ITSource_RecieveTimeout, DISABLE); } if (huart->Config->RxCallback) huart->Config->RxCallback(); return 1; } } return 0; } /** * @brief Передача данных в TX FIFO * @param huart указатель на хендл UART * @param it_mode режим работы (0 — polling, 1 — прерывания) * @retval int 1 — передача завершена, 0 — данные ещё передаются */ static int __uart_fifo_transmit(UART_HandleTypeDef *huart, uint8_t it_mode) { while (!UART_FlagStatus(huart->Instance, UART_Flag_TxFIFOFull) && huart->TxCount < huart->TxSize) { UART_SendData(huart->Instance, huart->TxBufPtr[huart->TxCount++]); } if (huart->TxCount == huart->TxSize) { if(it_mode) { // Выключаем FIFO прерывание UART_ITCmd(huart->Instance, UART_ITSource_TxFIFOLevel, DISABLE); // Включаем TransmitDone прерывание, коллбек будет по нему UART_ITCmd(huart->Instance, UART_ITSource_TransmitDone, ENABLE); } else { while(!UART_FlagStatus(huart->Instance, UART_Flag_TxFIFOEmpty)); // ждем пока не опустошится буфер huart->TxBusy = 0; if (huart->Config->TxCallback) huart->Config->TxCallback(); } return 1; } return 0; } #if USE_TX_QUEUE==1 static void __uart_tx_queue_process(UART_HandleTypeDef *huart) { if (!huart->TxBusy && !huart->TxQueue.QueueEmpty) { __uart_tx_queue_pop(huart); } } static OperationStatus __uart_tx_queue_push(UART_HandleTypeDef *huart, const uint8_t *buf, uint16_t size) { if (!huart || !buf || size == 0 || huart->TxQueue.QueueFull) return ERROR; huart->TxQueue.Queue[huart->TxQueue.QueueHead].Buf = buf; huart->TxQueue.Queue[huart->TxQueue.QueueHead].Size = size; huart->TxQueue.Queue[huart->TxQueue.QueueHead].InUse = 1; huart->TxQueue.QueueHead = (huart->TxQueue.QueueHead + 1) % TX_QUEUE_SIZE; huart->TxQueue.QueueCount++; huart->TxQueue.QueueEmpty = 0; if (huart->TxQueue.QueueCount == TX_QUEUE_SIZE) huart->TxQueue.QueueFull = 1; return OK; } static OperationStatus __uart_tx_queue_pop(UART_HandleTypeDef *huart) { if (!huart || huart->TxQueue.QueueEmpty) return ERROR; const uint8_t *buf = huart->TxQueue.Queue[huart->TxQueue.QueueTail].Buf; uint16_t size = huart->TxQueue.Queue[huart->TxQueue.QueueTail].Size; if (huart->TxBusy) return ERROR; huart->TxBufPtr = buf; huart->TxSize = size; huart->TxCount = 0; huart->TxBusy = 1; UART_ITCmd(huart->Instance, UART_ITSource_TxFIFOLevel, ENABLE); __uart_fifo_transmit(huart, 1); huart->TxQueue.Queue[huart->TxQueue.QueueTail].Buf = NULL; huart->TxQueue.Queue[huart->TxQueue.QueueTail].Size = 0; huart->TxQueue.Queue[huart->TxQueue.QueueTail].InUse = 0; huart->TxQueue.QueueTail = (huart->TxQueue.QueueTail + 1) % TX_QUEUE_SIZE; huart->TxQueue.QueueCount--; huart->TxQueue.QueueFull = 0; if (huart->TxQueue.QueueCount == 0) huart->TxQueue.QueueEmpty = 1; return OK; } #endif