Odrive_0.5.5运行代码分析_(四)_详解SPI总线

概述

由于odrive采用的stm32型号gpio少,所以只用一个spi,于是出现多设备链接的总线结构

Stm32SpiArbiter类

class Stm32SpiArbiter {
public:
    struct SpiTask {
        SPI_InitTypeDef config;
        Stm32Gpio ncs_gpio;
        const uint8_t* tx_buf;
        uint8_t* rx_buf;
        size_t length;
        void (*on_complete)(void*, bool);
        void* on_complete_ctx;
        bool is_in_use = false;
        struct SpiTask* next;
    };

    Stm32SpiArbiter(SPI_HandleTypeDef* hspi): hspi_(hspi) {}

    /**
     * Reserves the task for the caller if it's not in use currently.
     *
     * This can be used by the caller to ensure that the task structure is not
     * overwritten while it's in use in a preceding transfer.
     *
     * Example:
     *
     *     if (acquire_task(&task)) {
     *         transfer_async(&task)
     *     }
     *
     * A call to release_task() makes the task available for use again.
     */
    static bool acquire_task(SpiTask* task);

    /**
     * Releases the task so that the next call to `acquire_task()` returns true.
     * This should usually be called inside the on_complete() callback after
     * the rx buffer has been processed.
     */
    static void release_task(SpiTask* task);

    /**
     * @brief Enqueues a non-blocking transfer.
     * 
     * Once the transfer completes, fails or is aborted, the callback is invoked.
     * 
     * This function is thread-safe with respect to all other public functions
     * of this class.
     * 
     * @param task: Contains all configuration data for this transfer.
     *        The struct pointed to by this argument must remain valid and
     *        unmodified until the completion callback is invoked.
     */
    void transfer_async(SpiTask* task);

    /**
     * @brief Executes a blocking transfer.
     * 
     * If the SPI is busy this function waits until it becomes available or
     * the specified timeout passes, whichever comes first.
     * 
     * Returns true on successful transfer or false otherwise.
     * 
     * This function is thread-safe with respect to all other public functions
     * of this class.
     * 
     * @param config: The SPI configuration to apply for this transfer.
     * @param ncs_gpio: The active low GPIO to actuate during this transfer.
     * @param tx_buf: Buffer for the outgoing data to be sent. Can be null unless
     *        rx_buf is null too. 
     * @param rx_buf: Buffer for the incoming data to be sent. Can be null unless
     *        tx_buf is null too.
     */
    bool transfer(SPI_InitTypeDef config, Stm32Gpio ncs_gpio, const uint8_t* tx_buf, uint8_t* rx_buf, size_t length, uint32_t timeout_ms);

    /**
     * @brief Completion method to be called from HAL_SPI_TxCpltCallback,
     * HAL_SPI_RxCpltCallback and HAL_SPI_TxRxCpltCallback.
     */
    void on_complete();

private:
    bool start();
    
    SPI_HandleTypeDef* hspi_;
    SpiTask* task_list_ = nullptr;
};

可以看到,它有一个task结构体或者说单向链表,然后是几个函数,最后私有成员有一个start(),该函数时对spi依据task内容进行的初始化并启动接受或发送,均采用dma方式

static bool acquire_task(SpiTask* task);

这个函数是判断这个任务是不是当前要调用的任务

 if (acquire_task(&task)) {
       transfer_async(&task)
          }

用法如上

static void release_task(SpiTask* task);

bool Stm32SpiArbiter::acquire_task(SpiTask* task) {
    return !__atomic_exchange_n(&task->is_in_use, true, __ATOMIC_SEQ_CST);
}

当is_in_use为false时返回true,且变成true
这里使用了强制顺序执行命令,防止返回结果错误

static void release_task(SpiTask* task);

void Stm32SpiArbiter::release_task(SpiTask* task) {
    task->is_in_use = false;
}

释放任务,以便下一次调用 acquire_task() 返回true。这通常应该在之后的on_complete()回调中调用rx缓冲区已被处理。

void transfer_async(SpiTask* task);

void Stm32SpiArbiter::transfer_async(SpiTask* task) {
    task->next = nullptr;
    
    // Append new task to task list.
    // We could try to do this lock free but we could also use our time for useful things.
    SpiTask** ptr = &task_list_;
    CRITICAL_SECTION() {
        while (*ptr)
            ptr = &(*ptr)->next;
        *ptr = task;
    }

    // If the list was empty before, kick off the SPI arbiter now
    if (ptr == &task_list_) {
        if (!start()) {
            if (task->on_complete) {
                (*task->on_complete)(task->on_complete_ctx, false);
            }
        }
    }
}

第一部分是将task挂载到链表的尾部,第二部分是启动发送接收,然后调用完成回调函数

transfer(...)

bool Stm32SpiArbiter::transfer(SPI_InitTypeDef config, Stm32Gpio ncs_gpio, const uint8_t* tx_buf, uint8_t* rx_buf, size_t length, uint32_t timeout_ms) {
    volatile uint8_t result = 0xff;

    SpiTask task = {
        .config = config,
        .ncs_gpio = ncs_gpio,
        .tx_buf = tx_buf,
        .rx_buf = rx_buf,
        .length = length,
        .on_complete = [](void* ctx, bool success) { *(volatile uint8_t*)ctx = success ? 1 : 0; },
        .on_complete_ctx = (void*)&result,
        .is_in_use = false,
        .next = nullptr
    };

    transfer_async(&task);

    while (result == 0xff) {
        osDelay(1); // TODO: honor timeout
    }

    return result;
}

这里创建一个任务并启动发生接受,之后等待传输完成,也就是说,当一个线程调用spi函数时,它会被阻塞,并且发送接收完后在最多1个心跳之内响应 以上想法不全面,由于在dma中断中也会调用task的callback所以,正确的应为 首先,这个spi框架只有在用户(上层)自己定义的回调中改变传入的result引用的值才能退出等待(阻塞)。在完成任务创建和尝试发送接受后如果回调函数改变result的值,则线程不阻塞,但由于函数返回task的作用域消失,存在一定风险。当在dma完成中断中改变时则无风险,但会阻塞线程。
本人还写了一个类似的spi框架,通过维护一个fifo规避上述问题,且时任务挂载时间固定,任务发送时间更快(对初始化经行精简)详见(待补充)

void on_complete();

void Stm32SpiArbiter::on_complete() {
    if (!task_list_) {
        return; // this should not happen
    }

    // Wrap up transfer
    task_list_->ncs_gpio.write(true);
    if (task_list_->on_complete) {
        (*task_list_->on_complete)(task_list_->on_complete_ctx, true);
    }

    // Start next task if any
    SpiTask* next = nullptr;
    CRITICAL_SECTION() {
        next = task_list_ = task_list_->next;
    }
    if (next) {
        start();
    }
}

这里就是简单的调用完成回调并启动下一个任务的发送接收。
具体中断在board.c中查看 spi使用spi3
本文完…

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值