/** ****************************************************************************** * @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): (+) Определить структуры UART_ExtInit_TypeDef для нужных UART: uart0_config, uart1_config (+) Настроить скорость, стоп-биты, четность, длину данных (+) Настроить FIFO и направление (Tx, Rx или оба) (+) Определить callback-функции (можно NULL) (+) Определить пины TX/RX для UART0/UART1 (+) Включить/отключить очередь сообщений для отправки: USE_TX_QUEUE в uart.h 2. Инициализация UART: (+) uart_init_first() — первичная настройка GPIO, тактирования и NVIC (+) uart_init(&huart, &config) — инициализация конкретного UART с конфигурацией 3. Callback-функции: (+) UART_Set_Callback(&huart, UART_Callback_Rx, Callback) — вызов при приёме данных (+) UART_Set_Callback(&huart, UART_Callback_Tx, Callback) — вызов при завершении передачи (+) UART_Set_Callback(&huart, UART_Callback_Idle, Callback) — вызов при событии Idle (+) UART_Set_Callback(&huart, UART_Callback_Error, Callback) — вызов при ошибках 4. Запуск UART и FIFO: (+) UART_Start(&huart, TxFifoLevel, RxFifoLevel) — включает UART и настраивает FIFO 5. Передача и приём данных: - Режим Polling (blocking): (+) UART_Transmit(&huart, buf, size, timeout) — передача данных с ожиданием (+) UART_Receive(&huart, buf, size, timeout) — приём данных с ожиданием - Режим Interrupt (non-blocking): (+) UART_Transmit_IT(&huart, buf, size) — передача данных через прерывания (+) UART_Receive_IT(&huart, buf, size) — приём данных через прерывания 6. Обработка прерываний UART: (+) uart_irq_handler(&huart) — общий обработчик ISR для RX/TX FIFO, ошибок и Idle - В обработчиках автоматически вызываются соответствующие callback-функции и сбрасываются флаги - Поддерживается очередь передачи для предотвращения потери данных 7. GPIO для UART: (+) uart0_gpio_init()/uart0_gpio_deinit() — инициализация и деинициализация UART0 (+) uart1_gpio_init()/uart1_gpio_deinit() — инициализация и деинициализация UART1 8. Режимы работы: (+) Polling (blocking) — функции блокируются до завершения передачи/приёма (+) Interrupt (non-blocking) — передача/приём через прерывания, управление через callback @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