esp32移植canopen协议栈(三)

前言

本文续接上一篇文章,在之前的工程中,我们实现了can驱动的封装,离我们移植canopen更近一步了,但canopen的启动,sdo,pdo等调度都与时间密切相关,我们还需要实现一个定时器给canopen协议栈做心跳使用,本文我们来使用esp32 + 现代c++封装一个好用的timer类出来,为移植canopen打基础。

一、创建timer组件

先看下我们现在的工程目录。
在这里插入图片描述
我们需要在components目录下创建一个新的文件夹timer, 在timer文件夹下,新建timer.hpp的cpp文件,如下图所示。
在这里插入图片描述

二、timer接口设计

我们得timer基于esp32的高精度定时器实现。我们理想的效果是类似如下方式

以下是伪代码
auto handle = new Timer(...);
handle->startOnce(...);
handle->startPeriod(...);
if (handle->isStart()) {
	handle->stop();
}
大概需要这几个接口

三、timer接口实现

esp32的高精度定时器需要init,我们把这一部分放到Timer的构造函数中。但是我们每次申请一个定时器的时候都需要走构造函数,这个init我们全局只需要调用一次。这种场景可以考虑c++的单例模式,但貌似不是那么好,想一想也许std::call_once更为适合。
在这里插入图片描述
因为定时器有两种模式,单次和周期,我们可以用一个枚举类代表这两种模式。
在这里插入图片描述
未知,单次,周期三种状态,构造函数中状态设置为UNKNOW
在这里插入图片描述
我们还需要一个回调函数,保存用户传入的函数。在启动的时候我们要求用户要传入函数,以及函数的参数。

在这里插入图片描述

接下来我们实现startOnce函数,我们需要用户传入时间和callback, 意为多久之后执行这个callback. 也就是我们需要一个时间间隔,callback, 以及callback的参数。但这样就有问题了,因为用户传入的函数不固定,函数的参数也不固定,但我们这边确实固定的void(*)(void)啊。还好我们用的是现代c++,可以使用变长函数模板+参数绑定来实现这个目的。因为esp32的timer的回调是个c语言的函数指针,我们需要使用一个静态的成员函数来封装我们c++的function(可调用对象)。于是我们得startOnce可以写成如下
在这里插入图片描述
定义一个_handle来操作定时器的启停状态。定义一个静态成员函数来作为c语言函数指针,参数为this,即把本对象传入到函数里。利用c++的万能引用和参数完美转发实现callback的参数绑定。也实现了isStart接口(判断是否启动过定时器)。
在这里插入图片描述
接下来我们实现startPeriod接口,类似startOnce接口。
在这里插入图片描述
接下来我们实现我们得实现stop接口
在这里插入图片描述
我们得在析构函数中判断定时器是否被销毁,如果没有,我们要手动销毁。
在这里插入图片描述
为了防止重复启动定时器,我们需要再启动时判断该定时器是否被启用了,这样就可以覆盖了。
在这里插入图片描述
为了多线程并发考虑,我们需要再启动定时器这个阶段中进行加锁,保证这个操作的原子性。
在这里插入图片描述
在这里插入图片描述

最后要禁用拷贝,只准移动。
在这里插入图片描述
timer.hpp完整源码如下

#pragma once 

#include "esp_timer.h"
#include <chrono>
#include <mutex>
#include <functional>

class Timer {
private:
    enum class TimerMode : uint8_t {
        UNKNOW,
        ONCE, 
        PERIOD,
    };

    using Callback = std::function<void()>;
private:
    static inline std::once_flag _flag;
    TimerMode _mode;
    Callback _callback;
    esp_timer_handle_t _handle {nullptr};
    std::mutex _mutex;

private:
    static void defaultTimerCallback(void *arg) {
        static_cast<Timer *>(arg)->_callback();   
    }

public:
    explicit Timer() : _mode(TimerMode::UNKNOW) {
        std::call_once(_flag, esp_timer_init);
    }

    Timer(const Timer &) = delete;
    Timer &operator=(const Timer &) = delete;
    Timer(Timer &&) noexcept = default;
    Timer& operator=(Timer &&) noexcept = default;


    bool isStart(void) const {
        return nullptr != _handle;
    }

    template <typename F, typename ...Args>
    bool startOnce(const std::chrono::system_clock::duration &duration, F &&f, Args &&...args) {
        stop();
        std::lock_guard<std::mutex> lock(_mutex);
        _mode = TimerMode::ONCE;
        _callback = std::bind(std::forward<F>(f), std::forward<Args>(args)...);


        esp_timer_create_args_t onceArg = { 
            .callback = defaultTimerCallback,  
            .arg = this,                      
        };

        esp_timer_create(&onceArg, &_handle);
        return esp_timer_start_once(_handle, std::chrono::duration_cast<std::chrono::microseconds>(duration).count()) == ESP_OK;
    }

    template <typename F ,typename ...Args>
    bool startPeriod(const std::chrono::system_clock::duration &duration, F &&f, Args &&...args) {
        stop();
        std::lock_guard<std::mutex> lock(_mutex);
        _mode = TimerMode::PERIOD;
        _callback = std::bind(std::forward<F>(f), std::forward<Args>(args)...);

        esp_timer_create_args_t periodArg = {
            .callback = defaultTimerCallback,
            .arg = this,
        };

        esp_timer_create(&periodArg, &_handle);
        return esp_timer_start_periodic(_handle, std::chrono::duration_cast<std::chrono::microseconds>(duration).count()) == ESP_OK;
    }

    void stop(void) {
        std::lock_guard<std::mutex> lock(_mutex);
        if (_handle) {
            esp_timer_stop(_handle);
            esp_timer_delete(_handle);
            _handle = nullptr;
        }
    }
    ~Timer() {
        stop();
    }
};

四、验证

在main目录的CMakeLists.txt里包含我们刚写好的timer.hpp的路径
在这里插入图片描述
在app_main函数中写下如下验证代码
在这里插入图片描述
编译并下载到板子中,查看打印,确实和我们想的一致。
在这里插入图片描述
main.cpp的源码如下

#include <iostream>
#include "can.hpp"
#include <thread>
#include <chrono>
#include "timer.hpp"


int print(const char *s) {
    std::cout << s << std::endl;
    return 0;
}

extern "C" auto app_main() {
    
    using namespace std::chrono_literals;

    auto timer = new Timer();
    timer->startOnce(1s, print, "hello,world 111\n");
    std::this_thread::sleep_for(2s);
    std::cout << "timer is run ? " << std::boolalpha << timer->isStart() << std::endl;
    timer->stop();
    std::cout << "timer is run ? " << std::boolalpha << timer->isStart() << std::endl;

    auto timer2 = new Timer();
    timer2->startPeriod(2s, []{std::cout << "wo shi ni die" << std::endl;});
    std::cout << "timer2 is run ? " << std::boolalpha << timer2->isStart() << std::endl;
    std::this_thread::sleep_for(5s);
    timer2->stop();
    std::cout << "timer2 is run ? " << std::boolalpha << timer2->isStart() << std::endl;

    auto can = new Can0(500);

    while (true) {

        if (auto rx = can->read()) {
            can->write(*rx); // 回显
        }

        // 以上代码等同于
        /*
            std::optional<CanMessage> rx = can->read();
            if (rx.has_value()) {
                can->write(rx.value());
            }
        */

        // std::this_thread::sleep_for(1s);
    }

    delete can;
    delete timer;
    delete timer2;
}

五、总结

本文使用esp32封装了一个定时器,实际验证可以运行,下一步移植canopen(canfestival)协议栈。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值