imuncle / imuncle.github.io

大叔的个人小站
https://imuncle.github.io/
78 stars 17 forks source link

对比赛代码框架的思考 #115

Open imuncle opened 2 years ago

imuncle commented 2 years ago

前段时间稚晖君做出了一个迷你机械臂,非常强大,里面使用的示教器Peak的软件部分借鉴的是另一个开源项目X-TRACK,在好奇心的驱使下我去大致阅读了两份代码,除却LVGL图形设计部分外,整个工程的框架设计对我有写额外的启发。

稚晖君自己也对X-TRACK写过一个分析文档,他绘制的代码结构如下图所示。

img

其中的消息框架HAL层对我有些启发。

消息框架

消息框架是一个消息发布订阅机制,类似于ROS里面的话题功能,这个机制可以将各个模块相互独立起来,而且对于一对多的消息传递这种情况,消息框架是非常容易管理的。

因为源代码中使用C++编写消息框架,而单片机开发用的更多的还是C语言,于是我自己简单写了一个C语言版的,其中动态数组cvector的实现修改自这篇博客

include

include

include

define MIN_LEN 5

define EXPANED_VAL 1

struct _cvector { void *cv_pdata; size_t cv_len, cv_tot_len, cv_size; };

typedef struct _cvector *cvector;

cvector cvector_create (const size_t size ); void cvector_destroy (const cvector cv ); size_t cvector_length (const cvector cv ); void cvector_pushback (const cvector cv, void memb ); void* cvector_val_at (const cvector cv, size_t index );

endif


* cvector.c
```c
#include "cvector.h"

// size: 数组成员的大小
cvector cvector_create(const size_t size)
{
    cvector cv = (cvector)malloc(sizeof (struct _cvector));

    if (!cv) return NULL;

    cv->cv_pdata = malloc(MIN_LEN * size);

    if (!cv->cv_pdata)
    {
        free(cv);
        return NULL;
    }

    cv->cv_size = size;
    cv->cv_tot_len = MIN_LEN;
    cv->cv_len = 0;

    return cv;
}

void cvector_destroy(const cvector cv)
{
    free(cv->cv_pdata);
    free(cv);
    return;
}

size_t cvector_length(const cvector cv)
{
    return cv->cv_len;
}

void* cvector_pushback(const cvector cv, void *memb)
{
    if (cv->cv_len >= cv->cv_tot_len)
    {
        void *pd_sav = cv->cv_pdata;
        // 以cv_tot_len为最小单位进行扩张,避免反复realloc
        cv->cv_tot_len <<= EXPANED_VAL;
        cv->cv_pdata = realloc(cv->cv_pdata, cv->cv_tot_len * cv->cv_size);
    }

    memcpy((char *)cv->cv_pdata + cv->cv_len * cv->cv_size, memb, cv->cv_size);
    cv->cv_len++;

    return cv->cv_pdata + (cv->cv_len-1) * cv->cv_size;
}

void* cvector_val_at(const cvector cv, size_t index)
{
    return cv->cv_pdata + index * cv->cv_size;
}

include "cvector.h"

typedef unsigned char uint8_t;

typedef void(sub_callback)(uint8_t data, uint8_t len);

typedef struct publisher_t { char pub_topic; cvector subs; void(publish)(struct publisher_t pub, uint8_t data, uint8_t len); } Publisher;

typedef struct subscriber_t { char *sub_topic; sub_callback callback; } Subscriber;

void pub_commit(Publisher pub, uint8_t data, uint8_t len); void pub_sub_init(); Publisher create_publisher(char topic); void create_subscriber(char* topic, sub_callback bind_callback);

endif


* pub_sub.c
```c
#include "cvector.h"
#include "pub_sub.h"
#include <stdio.h>

cvector pub_lists;
cvector sub_lists;

void pub_sub_init()
{
    pub_lists = cvector_create(sizeof(Publisher));
    sub_lists = cvector_create(sizeof(Subscriber));
}

void pub_commit(Publisher* pub, uint8_t *data, uint8_t len)
{
    int sub_len = cvector_length(pub->subs);
    for(int i = 0; i < sub_len; i++)
    {
        void* val = cvector_val_at(pub->subs, i);
        sub_callback callback = *(sub_callback*) val;
        callback(data, len);
    }
}

Publisher* create_publisher(char* topic)
{
    Publisher p;
    p.pub_topic = topic;
    p.subs = cvector_create(sizeof(sub_callback));
    int sub_len = cvector_length(sub_lists);
    for(int i = 0; i < sub_len; i++)
    {
        void* val = cvector_val_at(sub_lists, i);
        Subscriber *sub = (Subscriber*) val;
        if(!strcmp(topic, sub->sub_topic))
        {
            cvector_pushback(p.subs, &sub->callback);
        }
    }
    p.publish = pub_commit;
    void* pub = cvector_pushback(pub_lists, &p);
    return (Publisher*) pub;
}

void create_subscriber(char* topic, sub_callback bind_callback)
{
    Subscriber s;
    s.sub_topic = topic;
    s.callback = bind_callback;
    int pub_len = cvector_length(pub_lists);
    for(int i = 0; i < pub_len; i++)
    {
        void* val = cvector_val_at(pub_lists, i);
        Publisher *pub = (Publisher*) val;
        if(!strcmp(topic, pub->pub_topic))
        {
            cvector_pushback(pub->subs, &bind_callback);
        }
    }
    cvector_pushback(sub_lists, &s);
}

当然只是很粗糙是实现了基本功能,舍去了消息队列缓冲,直接暴力的采用了阻塞式消息发布,也没有简单是否重复订阅同一话题,是否重复发布同一话题。

HAL层

第二个点是HAL层的设计,通过回调函数指针的方式,将底层硬件的配置代码与上层的逻辑代码分离,也增加了代码的复用性。这里以X-TRACK中的Encorder为例介绍一下。

/**

/**

define EXTIx_IRQHANDLER(n) \

do{\ if(EXTI_GetIntStatus(EXTI_Line##n) != RESET)\ {\ if(EXTI_Function[n]) EXTI_Function[n]();\ EXTI_ClearIntPendingBit(EXTI_Line##n);\ }\ }while(0)

/**

void HAL::Encoder_Init() { pinMode(CONFIG_ENCODER_A_PIN, INPUT_PULLUP); pinMode(CONFIG_ENCODER_B_PIN, INPUT_PULLUP); pinMode(CONFIG_ENCODER_PUSH_PIN, INPUT_PULLUP);

attachInterrupt(CONFIG_ENCODER_A_PIN, Encoder_EventHandler, FALLING);

EncoderPush.EventAttach(Encoder_PushHandler);

}



可以看到,通过`attachInterrupt`函数接口,将回调函数传给`exti`,在外部中断触发的时候,就会直接调用`Encoder_EventHandler`函数了。

大家最常用的方法是直接在`EXTI0_IRQHandler`函数里调用`Encoder_EventHandler`函数,这样底层和上层就耦合在一起了,而通过函数指针的方式,二者就基本相互独立。

# 新的框架

于是我对新框架有了一些想法,以下是我对新框架的构想。

![QQ图片20211025170805](https://user-images.githubusercontent.com/35989223/138667871-f5a64e43-1de4-4ecc-bbe8-8cd62029682c.png)

# 结语

可能是以往的固有思路限制了我的想象,虽然这个框架在纯软件开发的时候都能想得到,但是却没有想过单片机这种低功耗,少资源的设备上能不能使用,答案是也可以,只要适当的简化就行了。