ODrive学习笔记三——串口流

系列文章目录

提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
例如:第一章 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) {

这个就是整个串口流。
不过看下来 还没有调试输出的地方。 这么复杂的工程没有打印信息嘛?


总结

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奔跑的Lunzi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值