Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
imuncle opened this issue Oct 25, 2021 · 0 comments
Open

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

imuncle opened this issue Oct 25, 2021 · 0 comments
Labels
RM 参加RoboMaster比赛中的成长记录 STM32 STM32学习开发记录

Comments

@imuncle
Copy link
Owner

imuncle commented Oct 25, 2021

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

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

img

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

消息框架

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

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

  • cvector.h
#ifndef CVECTOR_H
#define CVECTOR_H

# include <stdio.h>  
# include <stdlib.h>  
# include <string.h>  

# 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
#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;
}
  • pub_sub.h
#ifndef PUB_SUB_H
#define PUB_SUB_H

#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
#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为例介绍一下。

  • exit.c
/*外部中断回调函数指针数组*/
static EXTI_CallbackFunction_t EXTI_Function[16] = {0};

/**
  * @brief  外部中断初始化
  * @param  Pin: 引脚编号
  * @param  function: 回调函数
  * @param  Trigger_Mode: 触发方式
  * @param  PreemptionPriority: 抢占优先级
  * @param  SubPriority: 子优先级
  * @retval 无
  */
void EXTIx_Init(uint8_t Pin, EXTI_CallbackFunction_t function, EXTITrigger_Type Trigger_Mode, uint8_t PreemptionPriority, uint8_t SubPriority)
{
    EXTI_InitType EXTI_InitStructure;
    NVIC_InitType NVIC_InitStructure;
    uint8_t Pinx;

    if(!IS_PIN(Pin))
        return;

    Pinx = GPIO_GetPinNum(Pin);

    if(Pinx > 15)
        return;
    
    EXTI_Function[Pinx] = function;

    //GPIO中断线以及中断初始化配置
    RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_AFIO, ENABLE);
    GPIO_EXTILineConfig(GPIO_GetPortNum(Pin), Pinx);

    EXTI_InitStructure.EXTI_Line = EXTI_GetLinex(Pin);//设置中断线
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//设置触发模式,中断触发(事件触发)
    EXTI_InitStructure.EXTI_Trigger = Trigger_Mode;//设置触发方式
    EXTI_InitStructure.EXTI_LineEnable = ENABLE;
    EXTI_Init(&EXTI_InitStructure);     //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器

    NVIC_InitStructure.NVIC_IRQChannel = EXTI_GetIRQn(Pin);                    //使能所在的外部中断通道
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = PreemptionPriority;      //抢占优先级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = SubPriority;                //子优先级
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                     //使能外部中断通道
    NVIC_Init(&NVIC_InitStructure);
}

/**
  * @brief  外部中断初始化 (Arduino)
  * @param  Pin: 引脚编号
  * @param  function: 回调函数
  * @param  Trigger_Mode: 触发方式
  * @retval 无
  */
void attachInterrupt(uint8_t Pin, EXTI_CallbackFunction_t function, EXTITrigger_Type Trigger_Mode)
{
    EXTIx_Init(Pin, function, Trigger_Mode, EXTI_PreemptionPriority_Default, EXTI_SubPriority_Default);
}

#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)

/**
  * @brief  外部中断入口,通道0
  * @param  无
  * @retval 无
  */
void EXTI0_IRQHandler(void)
{
    EXTIx_IRQHANDLER(0);
}
  • HAL_Encorder.cpp
static void Encoder_EventHandler()
{
    if(!EncoderEnable || EncoderDiffDisable)
    {
        return;
    }

    int dir = (digitalRead(CONFIG_ENCODER_B_PIN) == LOW ? -1 : +1);
    EncoderDiff += dir;
    Buzz_Handler(dir);
}

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

结语

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

@imuncle imuncle added RM 参加RoboMaster比赛中的成长记录 STM32 STM32学习开发记录 labels Oct 25, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
RM 参加RoboMaster比赛中的成长记录 STM32 STM32学习开发记录
Projects
None yet
Development

No branches or pull requests

1 participant