// Copyright 2016 Rob Riggs // All rights reserved. #ifndef NUCLEOTNC #include "Log.h" #include "bm78.h" #endif #include "SerialPort.hpp" #include "PortInterface.h" #include "HdlcFrame.hpp" #include "Kiss.hpp" #include "main.h" #include "stm32l4xx_hal.h" #include "cmsis_os.h" #include #include #include #include #ifdef NUCLEOTNC extern UART_HandleTypeDef huart2; UART_HandleTypeDef& huart_serial = huart2; #else extern UART_HandleTypeDef huart3; UART_HandleTypeDef& huart_serial = huart3; #endif extern osMessageQId ioEventQueueHandle; std::atomic uart_error{HAL_UART_ERROR_NONE}; std::atomic txDoneFlag{true}; uint8_t tmpBuffer[mobilinkd::tnc::TX_BUFFER_SIZE]; uint8_t tmpBuffer2[mobilinkd::tnc::TX_BUFFER_SIZE]; constexpr const int RX_BUFFER_SIZE = 127; unsigned char rxBuffer[RX_BUFFER_SIZE * 2]; // 3 chunks of 128 bytes. The first byte in each chunk is the length. typedef mobilinkd::tnc::memory::Pool< 3, RX_BUFFER_SIZE + 1> serial_pool_type; serial_pool_type serialPool; #ifndef NUCLEOTNC void log_frame(mobilinkd::tnc::hdlc::IoFrame* frame) { int pos = 0; for (auto c: *frame) { if (isprint(int(c))) pos += sprintf((char*)tmpBuffer2 + pos, " %c ", c); else pos += sprintf((char*)tmpBuffer2 + pos, "/%02x", c); if (pos > 80) { DEBUG((char*)tmpBuffer2); pos = 0; } } DEBUG((char*)tmpBuffer2); } #endif // HAL does not have HAL_StatusTypeDef UART_DMAPauseReceive(UART_HandleTypeDef *huart) { /* Process Locked */ __HAL_LOCK(huart); if ((huart->RxState == HAL_UART_STATE_BUSY_RX) && (HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR))) { /* Disable PE and ERR (Frame error, noise error, overrun error) interrupts */ CLEAR_BIT(huart->Instance->CR1, USART_CR1_PEIE); CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE); /* Disable the UART DMA Rx request */ CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR); } /* Process Unlocked */ __HAL_UNLOCK(huart); return HAL_OK; } HAL_StatusTypeDef UART_DMAResumeReceive(UART_HandleTypeDef *huart) { /* Process Locked */ __HAL_LOCK(huart); if (huart->RxState == HAL_UART_STATE_BUSY_RX) { /* Clear the Overrun flag before resuming the Rx transfer */ __HAL_UART_CLEAR_FLAG(huart, UART_CLEAR_OREF); /* Reenable PE and ERR (Frame error, noise error, overrun error) interrupts */ SET_BIT(huart->Instance->CR1, USART_CR1_PEIE); SET_BIT(huart->Instance->CR3, USART_CR3_EIE); /* Enable the UART DMA Rx request */ SET_BIT(huart->Instance->CR3, USART_CR3_DMAR); } /* Process Unlocked */ __HAL_UNLOCK(huart); return HAL_OK; } extern "C" void startSerialTask(void const* arg) __attribute__((optimize("-O1"))); void startSerialTask(void const* arg) { using namespace mobilinkd::tnc; auto serialPort = static_cast(arg); const uint8_t FEND = 0xC0; const uint8_t FESC = 0xDB; const uint8_t TFEND = 0xDC; const uint8_t TFESC = 0xDD; enum State {WAIT_FBEGIN, WAIT_FRAME_TYPE, WAIT_FEND, WAIT_ESCAPED}; State state = WAIT_FBEGIN; hdlc::IoFrame* frame = hdlc::acquire_wait(); HAL_UART_Receive_DMA(&huart_serial, rxBuffer, RX_BUFFER_SIZE * 2); __HAL_UART_ENABLE_IT(&huart_serial, UART_IT_IDLE); while (true) { osEvent evt = osMessageGet(serialPort->queue(), osWaitForever); if (evt.status != osEventMessage) { continue; } if (evt.value.v < FLASH_BASE) // Assumes FLASH_BASE < SRAM_BASE. { // Error received. hdlc::release(frame); #ifndef NUCLEOTNC ERROR("UART Error: %08lx", uart_error.load()); #endif uart_error.store(HAL_UART_ERROR_NONE); frame = hdlc::acquire_wait(); HAL_UART_Receive_DMA(&huart_serial, rxBuffer, RX_BUFFER_SIZE * 2); __HAL_UART_ENABLE_IT(&huart_serial, UART_IT_IDLE); continue; } auto block = (serial_pool_type::chunk_type*) evt.value.p; auto data = static_cast(block->buffer); uint8_t end = data[0] + 1; for (uint8_t i = 1; i != end; ++i) { uint8_t c = data[i]; switch (state) { case WAIT_FBEGIN: if (c == FEND) state = WAIT_FRAME_TYPE; break; case WAIT_FRAME_TYPE: if (c == FEND) break; // Still waiting for FRAME_TYPE. if (c < 8 or c == 0xFF) { frame->type(c); state = WAIT_FEND; } else { WARN("Bad frame type"); state = WAIT_FBEGIN; } break; case WAIT_FEND: switch (c) { case FESC: state = WAIT_ESCAPED; break; case FEND: frame->source(hdlc::IoFrame::SERIAL_DATA); if (osMessagePut( ioEventQueueHandle, reinterpret_cast(frame), osWaitForever) != osOK) { hdlc::release(frame); } if (hdlc::ioFramePool().size() < (hdlc::ioFramePool().capacity() / 4)) { UART_DMAPauseReceive(&huart_serial); while (hdlc::ioFramePool().size() < (hdlc::ioFramePool().capacity() / 2)) { osThreadYield(); } UART_DMAResumeReceive(&huart_serial); } frame = hdlc::acquire_wait(); state = WAIT_FBEGIN; break; default: if (not frame->push_back(c)) { hdlc::release(frame); state = WAIT_FBEGIN; // Drop frame; frame = hdlc::acquire_wait(); } } break; case WAIT_ESCAPED: state = WAIT_FEND; switch (c) { case TFESC: if (not frame->push_back(FESC)) { hdlc::release(frame); state = WAIT_FBEGIN; // Drop frame; frame = hdlc::acquire_wait(); } break; case TFEND: if (not frame->push_back(FEND)) { hdlc::release(frame); state = WAIT_FBEGIN; // Drop frame; frame = hdlc::acquire_wait(); } break; default: hdlc::release(frame); state = WAIT_FBEGIN; // Drop frame; frame = hdlc::acquire_wait(); } break; } } serialPool.deallocate(block); } } extern "C" void HAL_UART_TxCpltCallback(UART_HandleTypeDef*) { txDoneFlag = true; } extern "C" void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart) { uint32_t len = (RX_BUFFER_SIZE * 2) - __HAL_DMA_GET_COUNTER(huart->hdmarx); if (len == 0) return; if (len > RX_BUFFER_SIZE) { len = RX_BUFFER_SIZE; // wrapped. } auto block = serialPool.allocate(); if (!block) return; memmove(block->buffer + 1, rxBuffer, len); block->buffer[0] = len; auto status = osMessagePut(mobilinkd::tnc::getSerialPort()->queue(), (uint32_t) block, 0); if (status != osOK) serialPool.deallocate(block); } extern "C" void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { uint32_t hdmarx = __HAL_DMA_GET_COUNTER(huart->hdmarx); uint32_t len = 0; if (hdmarx > RX_BUFFER_SIZE) { len = RX_BUFFER_SIZE; // wrapped. } else { len = RX_BUFFER_SIZE - hdmarx; } if (len == 0) return; auto block = serialPool.allocate(); if (!block) return; memmove(block->buffer + 1, rxBuffer + RX_BUFFER_SIZE, len); block->buffer[0] = len; auto status = osMessagePut(mobilinkd::tnc::getSerialPort()->queue(), (uint32_t) block, 0); if (status != osOK) serialPool.deallocate(block); } extern "C" void idleInterruptCallback(UART_HandleTypeDef* huart) { uint32_t len = (RX_BUFFER_SIZE * 2) - __HAL_DMA_GET_COUNTER(huart->hdmarx); if (len == 0) return; auto block = serialPool.allocate(); if (!block) return; HAL_UART_AbortReceive(huart); if (len > RX_BUFFER_SIZE) { // Second half len = len - RX_BUFFER_SIZE; memmove(block->buffer + 1, rxBuffer + RX_BUFFER_SIZE, len); } else { // First half memmove(block->buffer + 1, rxBuffer, len); } block->buffer[0] = len; HAL_UART_Receive_DMA(huart, rxBuffer, RX_BUFFER_SIZE * 2); auto status = osMessagePut(mobilinkd::tnc::getSerialPort()->queue(), (uint32_t) block, 0); if (status != osOK) serialPool.deallocate(block); } extern "C" void HAL_UART_ErrorCallback(UART_HandleTypeDef* huart) { osMessagePut(mobilinkd::tnc::getSerialPort()->queue(), huart->ErrorCode, 0); uart_error.store((huart->gState<<16) | huart->ErrorCode); huart->ErrorCode = HAL_UART_ERROR_NONE; huart->gState = HAL_UART_STATE_READY; } namespace mobilinkd { namespace tnc { void SerialPort::init() { if (serialTaskHandle_) return; osMessageQDef(uartQueue, 32, void*); queue_ = osMessageCreate(osMessageQ(uartQueue), 0); osMutexDef(uartMutex); mutex_ = osMutexCreate(osMutex(uartMutex)); osThreadDef(serialTask, startSerialTask, osPriorityAboveNormal, 0, 128); serialTaskHandle_ = osThreadCreate(osThread(serialTask), this); #ifndef NUCLEOTNC DEBUG("serialTaskHandle_ = %p", serialTaskHandle_); #endif } bool SerialPort::open() { if (open_ or !serialTaskHandle_) return open_; open_ = true; return open_; } void SerialPort::close() { open_ = false; } bool SerialPort::write(const uint8_t* data, uint32_t size, uint8_t type, uint32_t timeout) { if (!open_) return false; uint32_t start = osKernelSysTick(); if (osMutexWait(mutex_, timeout) != osOK) return false; using ::mobilinkd::tnc::kiss::slip_encoder; auto slip_iter = slip_encoder((const char*)data, size); auto slip_end = slip_encoder(); size_t pos = 0; memset(tmpBuffer, 0, TX_BUFFER_SIZE); tmpBuffer[pos++] = 0xC0; // FEND tmpBuffer[pos++] = type; // KISS Data Frame while (slip_iter != slip_end) { tmpBuffer[pos++] = *slip_iter++; if (pos == TX_BUFFER_SIZE) { while (!txDoneFlag) osThreadYield(); memcpy(TxBuffer, tmpBuffer, TX_BUFFER_SIZE); txDoneFlag = false; while (open_ and HAL_UART_Transmit_DMA(&huart_serial, TxBuffer, TX_BUFFER_SIZE) == HAL_BUSY) { if (osKernelSysTick() - start > timeout) { osMutexRelease(mutex_); txDoneFlag = true; return false; } osThreadYield(); } pos = 0; memset(TxBuffer, 0, TX_BUFFER_SIZE); } } // Buffer has room for at least one more byte. tmpBuffer[pos++] = 0xC0; while (!txDoneFlag) osThreadYield(); memcpy(TxBuffer, tmpBuffer, pos); txDoneFlag = false; while (open_ and HAL_UART_Transmit_DMA(&huart_serial, TxBuffer, pos) == HAL_BUSY) { if (osKernelSysTick() - start > timeout) { osMutexRelease(mutex_); txDoneFlag = true; return false; } osThreadYield(); } osMutexRelease(mutex_); return true; } bool SerialPort::write(const uint8_t* data, uint32_t size, uint32_t timeout) { if (!open_) return false; uint32_t start = osKernelSysTick(); if (osMutexWait(mutex_, timeout) != osOK) return false; size_t pos = 0; memset(TxBuffer, 0, TX_BUFFER_SIZE); auto first = data; auto last = data + size; while (first != last) { TxBuffer[pos++] = *first++; if (pos == TX_BUFFER_SIZE) { while (open_ and HAL_UART_Transmit(&huart_serial, TxBuffer, TX_BUFFER_SIZE, timeout) == HAL_BUSY) { if (osKernelSysTick() - start > timeout) { osMutexRelease(mutex_); return false; } osThreadYield(); } pos = 0; memset(TxBuffer, 0, TX_BUFFER_SIZE); } } while (open_ and HAL_UART_Transmit(&huart_serial, TxBuffer, TX_BUFFER_SIZE, timeout) == HAL_BUSY) { if (osKernelSysTick() - start > timeout) { osMutexRelease(mutex_); return false; } osThreadYield(); } while (open_ and HAL_UART_Transmit(&huart_serial, (uint8_t*)"\r\n", 2, timeout) == HAL_BUSY) { if (osKernelSysTick() - start > timeout) { osMutexRelease(mutex_); return false; } osThreadYield(); } osMutexRelease(mutex_); return true; } /* * Abort the DMA transmission. Release the mutex and the frame. Set the * txDoneFlag so other writes may be attempted. * * This really sucks. The BM78 seems to just give up the ghost in BLE mode * when connected for long periods of time (and long is relative, but * typically more than an hour). To deal with this, we reset the device * and the client needs to attempt to reconnect when disconnection is * detected. */ bool SerialPort::abort_tx(hdlc::IoFrame* frame) { HAL_UART_AbortTransmit(&huart_serial); hdlc::release(frame); #ifndef NUCLEOTNC WARN("SerialPort::write timed out -- DMA aborted."); HAL_GPIO_WritePin(BT_RESET_GPIO_Port, BT_RESET_Pin, GPIO_PIN_RESET); osDelay(1); HAL_GPIO_WritePin(BT_RESET_GPIO_Port, BT_RESET_Pin, GPIO_PIN_SET); bm78_wait_until_ready(); #endif txDoneFlag = true; osMutexRelease(mutex_); return false; } bool SerialPort::write(hdlc::IoFrame* frame, uint32_t timeout) { if (!open_) { hdlc::release(frame); return false; } uint32_t start = osKernelSysTick(); if (osMutexWait(mutex_, timeout) != osOK) { hdlc::release(frame); return false; } using ::mobilinkd::tnc::kiss::slip_encoder2; hdlc::IoFrame::iterator begin = frame->begin(); hdlc::IoFrame::iterator end = frame->begin(); std::advance(end, frame->size() - 2); // Drop FCS slip_encoder2 slip_iter{begin}; slip_encoder2 slip_end{end}; size_t pos = 0; tmpBuffer[pos++] = 0xC0; // FEND tmpBuffer[pos++] = static_cast(frame->type()); // KISS Data Frame while (slip_iter != slip_end) { tmpBuffer[pos++] = *slip_iter++; if (pos == TX_BUFFER_SIZE) { while (!txDoneFlag) { // txDoneFlag set in HAL_UART_TxCpltCallback() above when DMA completes. if (osKernelSysTick() - start > timeout) { return abort_tx(frame); // Abort DMA xfer on timeout. } else { osThreadYield(); } } memcpy(TxBuffer, tmpBuffer, TX_BUFFER_SIZE); txDoneFlag = false; while (open_ and HAL_UART_Transmit_DMA(&huart_serial, TxBuffer, TX_BUFFER_SIZE) == HAL_BUSY) { // This should not happen. HAL_BUSY should not occur when txDoneFlag set. if (osKernelSysTick() - start > timeout) { return abort_tx(frame); // Abort DMA xfer on timeout. } else { osThreadYield(); } } pos = 0; } } // Buffer has room for at least one more byte. tmpBuffer[pos++] = 0xC0; while (!txDoneFlag) { // txDoneFlag set in HAL_UART_TxCpltCallback() above when DMA completes. if (osKernelSysTick() - start > timeout) { return abort_tx(frame); // Abort DMA xfer on timeout. } else { osThreadYield(); } } memcpy(TxBuffer, tmpBuffer, TX_BUFFER_SIZE); txDoneFlag = false; while (open_ and HAL_UART_Transmit_DMA(&huart_serial, TxBuffer, pos) == HAL_BUSY) { // This should not happen. HAL_BUSY should not occur when txDoneFlag set. if (osKernelSysTick() - start > timeout) { return abort_tx(frame); // Abort DMA xfer on timeout. } else { osThreadYield(); } } osMutexRelease(mutex_); hdlc::release(frame); DEBUG("SerialPort::write COMPLETE"); return true; } SerialPort* getSerialPort() { static SerialPort instance; return &instance; } }} // mobilinkd::tnc