系列文章目录
提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
例如:第一章 Python 机器学习入门之pandas的使用
前言
初步了解main函数之后,先看一下串口流怎么实现的,这样以后在这个基础上去做修改也方便一些。
一、串口外设初始化
在main函数中找到board_init()函数。
好在这部分都是C代码比较好看。
if (odrv.config_.enable_uart_a) {
uart_a->Init.BaudRate = odrv.config_.uart_a_baudrate;
MX_UART4_Init();
}
if (odrv.config_.enable_uart_b) {
uart_b->Init.BaudRate = odrv.config_.uart_b_baudrate;
MX_USART2_UART_Init();
可以看到UART_A用的是串口4,UART_B用的是串口2。波特率默认115200。
另外从odrive_main.h里可以看到,最多应该是可以支持到3个串口的,默认只启用UART_A。
bool enable_uart_a = true;
bool enable_uart_b = false;
bool enable_uart_c = false;
uint32_t uart_a_baudrate = 115200;
uint32_t uart_b_baudrate = 115200;
uint32_t uart_c_baudrate = 115200;
二、串口任务相关
main函数中还声明了一个串口事件队列
// Create an event queue for UART
osMessageQDef(uart_event_queue, 4, uint32_t);
uart_event_queue = osMessageCreate(osMessageQ(uart_event_queue), NULL);
这个队列在多个文件都调用了,先晚一点讲。
先看串口相关的任务如何创建的。
在rtos_main任务函数中,会初始化通信相关的任务。
static void rtos_main(void*) {
// Init USB device
MX_USB_DEVICE_Init();
// Start ADC for temperature measurements and user measurements
start_general_purpose_adc();
//osDelay(100);
// Init communications (this requires the axis objects to be constructed)
init_communication();
void init_communication(void) {
//printf("hi!\r\n");
// Dual UART operation not supported yet
if (odrv.config_.enable_uart_a && odrv.config_.enable_uart_b) {
odrv.misconfigured_ = true;
}
if (odrv.config_.enable_uart_a && uart_a) {
start_uart_server(uart_a);
} else if (odrv.config_.enable_uart_b && uart_b) {
start_uart_server(uart_b);
}
所以看来目前的版本最高就支持双串口通信了。
// TODO: allow multiple UART server instances
void start_uart_server(UART_HandleTypeDef* huart) {
huart_ = huart;
uart_tx_stream.huart_ = huart;
// DMA is set up to receive in a circular buffer forever.
// We dont use interrupts to fetch the data, instead we periodically read
// data out of the circular buffer into a parse buffer, controlled by a state machine
HAL_UART_Receive_DMA(huart_, dma_rx_buffer, sizeof(dma_rx_buffer));
dma_last_rcv_idx = 0;
// Start UART communication thread
osThreadDef(uart_server_thread_def, uart_server_thread, osPriorityNormal, 0, stack_size_uart_thread / sizeof(StackType_t) /* the ascii protocol needs considerable stack space */);
uart_thread = osThreadCreate(osThread(uart_server_thread_def), NULL);
}
启用串口服务(任务)里面配置了串口接收DMA,
#define UART_TX_BUFFER_SIZE 64
#define UART_RX_BUFFER_SIZE 64
// DMA open loop continous circular buffer
// 1ms delay periodic, chase DMA ptr around
static uint8_t dma_rx_buffer[UART_RX_BUFFER_SIZE];
这里可能有个小问题,通过HAL_UART_Receive_DMA来创建的DMA配置,使用了一个内存地址。无论UART_A还是UART_B创建的话都是同一个缓冲区,所以ODrive只能选择一个串口进行通信。然后就会缓冲区冲突。并且创建的任务也是同一个,看来应该是只能选择一个了。
static void uart_server_thread(void * ctx) {
(void) ctx;
if (odrv.config_.uart0_protocol == ODrive::STREAM_PROTOCOL_TYPE_FIBRE) {
fibre_over_uart.start({});
} else if (odrv.config_.uart0_protocol == ODrive::STREAM_PROTOCOL_TYPE_ASCII
|| odrv.config_.uart0_protocol == ODrive::STREAM_PROTOCOL_TYPE_ASCII_AND_STDOUT) {
ascii_over_uart.start();
}
进入到串口任务中具体看,一开始的通信类型也只是判断了UART0,看来如果想换串口,还得修改一下代码呢。
osEvent event = osMessageGet(uart_event_queue, osWaitForever);
if (event.status != osEventMessage) {
continue;
}
之后进入阻塞一直等待串口事件的来临。
串口事件从代码看就只有三种,1 2 3……
case 1: {
// This event is triggered by the control loop at 8kHz. This should be
// enough for most applications.
// At 1Mbaud/s that corresponds to at most 12.5 bytes which can arrive
// during the sleep period.
// Check for UART errors and restart receive DMA transfer if required
if (huart_->RxState != HAL_UART_STATE_BUSY_RX) {
HAL_UART_AbortReceive(huart_);
HAL_UART_Receive_DMA(huart_, dma_rx_buffer, sizeof(dma_rx_buffer));
dma_last_rcv_idx = 0;
}
// Fetch the circular buffer "write pointer", where it would write next
uint32_t new_rcv_idx = UART_RX_BUFFER_SIZE - huart_->hdmarx->Instance->NDTR;
if (new_rcv_idx > UART_RX_BUFFER_SIZE) { // defensive programming
continue;
}
// Process bytes in one or two chunks (two in case there was a wrap)
if (new_rcv_idx < dma_last_rcv_idx) {
uart_rx_stream.did_receive(dma_rx_buffer + dma_last_rcv_idx,
UART_RX_BUFFER_SIZE - dma_last_rcv_idx);
dma_last_rcv_idx = 0;
}
if (new_rcv_idx > dma_last_rcv_idx) {
uart_rx_stream.did_receive(dma_rx_buffer + dma_last_rcv_idx,
new_rcv_idx - dma_last_rcv_idx);
dma_last_rcv_idx = new_rcv_idx;
}
} break;
状态1:检查UART接收状态 / 获取DMA接收的当前进度 / 处理接收到的数据
这里串口的接收逻辑用了DMA做了一个环形的队列。
如果没有溢出的情况下,则从队列中读取增量数据。如果溢出了先读上一个环增加的,再读新一环新增的。稍微有一点点绕 ,逻辑上是OK的。
在接收完成之后,会通过回调调用处理函数。回调的配置在开始的start里面。
void AsciiProtocol::start() {
TransferHandle dummy;
rx_channel_->start_read(rx_buf_, &dummy, MEMBER_CB(this, on_read_finished));
}
void AsciiProtocol::on_read_finished(ReadResult result) {
if (result.status != kStreamOk) {
return;
}
for (;;) {
uint8_t* end_of_line = std::find_if(rx_buf_, result.end, [](uint8_t c) {
return c == '\r' || c == '\n' || c == '!';
});
if (end_of_line >= result.end) {
break;
}
if (read_active_) {
process_line({rx_buf_, end_of_line});
} else {
// Ignoring this line cause it didn't start at a new-line character
read_active_ = true;
}
// Discard the processed bytes and shift the remainder to the beginning of the buffer
size_t n_remaining = result.end - end_of_line - 1;
memmove(rx_buf_, end_of_line + 1, n_remaining);
result.end = rx_buf_ + n_remaining;
}
// No more new-line characters in buffer
if (result.end >= rx_buf_ + sizeof(rx_buf_)) {
// If the line becomes too long, reset buffer and wait for the next line
result.end = rx_buf_;
read_active_ = false;
}
TransferHandle dummy;
rx_channel_->start_read({result.end, rx_buf_ + sizeof(rx_buf_)}, &dummy, MEMBER_CB(this, on_read_finished));
}
最终是在process_line里面进行处理的。
void AsciiProtocol::process_line(cbufptr_t buffer) {
这个就是整个串口流。
不过看下来 还没有调试输出的地方。 这么复杂的工程没有打印信息嘛?