硬汉嵌入式论坛

 找回密码
 立即注册
查看: 100|回复: 6
收起左侧

【开源】一个串行通讯私有协议框架

[复制链接]

12

主题

112

回帖

153

积分

初级会员

积分
153
发表于 昨天 17:55 | 显示全部楼层 |阅读模式
开源一个串行通讯私有协议框架,请求-应答模式,超时检测,CRC,消息回调机制。


https://gitee.com/davidxu1993/serial-private-protocol

https://github.com/DavidXuZZF/serial-private-protocol

[C] 纯文本查看 复制代码
/**
 * @file        pp_custom_config.h
 * @author      David Xu ([email]davidxuzzf@gmail.com[/email])
 * @brief       User define this file to custom pp. The macro define
 *              in this file can instead the item in pp_config.h .
 * @version     1.0.0
 * 
 * MIT License
 * 
 * Copyright (c) 2024 DAVID XU
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#ifndef example_pp_custom_config_h
#define example_pp_custom_config_h

#ifdef __cplusplus
extern "C" {
#endif

/* 本配置文件以rt-thread为例 */
// #include <rtthread.h>

/* 串行接收缓存长度 */
#define PP_RINGBUFFER_LEN (4 * 1024)

/* 用于临时保存一帧数据的缓存,至少比最大报文长度多20字节 */
#define PP_FRAMEBUFFER_LEN (1024 + 20)

/* 应答超时检测功能的使能。且每条发送报文都会申请一个超时检测的动态内存。 */
/* !!!该功能需要动态内存分配功能使能。 */
#define PP_TIMEOUT_DET_EN (1)

/* 允许的最大payload长度,仅用于接收报文合法性的检查。 */
#define PP_PAYLOAD_MAX_LEN (1024)

/* 标准输出接口 */
#define PP_PRINTF rt_kprintf

/* 调试日志输出使能 */
#define PP_LOG_DEBUG_EN (1)

/* 错误日志输出使能 */
#define PP_LOG_ERROR_EN (1)

/* 动态内存分配功能使能 */
/* !!!若不使能,协议句柄使用的是全局变量,因此不能让同一个协议实例化多个实例。除非自己改pp_template_handle_new函数 */
/* !!!由于应答超时检查功能需要动态内存,因此若不使能,超时检查功能将失效。 */
#define PP_DYNAMIC_MEMORY_EN (1)

/* 动态内存分配函数 */
#define PP_MALLOC rt_malloc

/* 动态内存释放函数 */
#define PP_FREE rt_free

/* 超时断帧时间,写0则不使能超时断帧功能 */
#define FRAME_TIMEOUT (2000)

/* 轮询延时,用于释放cpu资源 */
#define POLL_DELAY (rt_thread_mdelay(1))

#ifdef __cplusplus
}
#endif

#endif /* example_pp_custom_config_h */


[C] 纯文本查看 复制代码
/**
 * @file        main.c
 * @author      David Xu ([email]davidxuzzf@gmail.com[/email])
 * @brief       example.
 * @version     1.0.0
 * 
 * MIT License
 * 
 * Copyright (c) 2024 DAVID XU
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <rtthread.h>
#include "inc/pp_template.h"

#define LOG_D(fmt, ...) rt_kprintf(fmt "\n", ##__VA_ARGS__)

/* 协议句柄 */
struct pp_template_handle *h_template = NULL;

/**
 * @brief       串口发送回调函数,即用户调用硬件发送数据。
 * 
 * @param       data 发送数据
 * @param       len  发送数据长度
 * @return      int  发送的字节数。等于len时,表示发送成功。等于其他值时,表示发送失败。
 */
static int my_hw_send_cb(const uint8_t *data, uint16_t len);

/**
 * @brief       事件回调函数,用于处理协议事件。包括接收的应答报文事件和应答超时事件。
 * 
 * @param       h 协议句柄
 * @param       evt 事件码
 * @param       param 事件参数
 */
static void my_notify_cb(struct pp_handle *h, uint8_t evt, const void *param);

/**
 * @brief       获取时间戳。
 * 
 * @return      uint32_t 时间戳
 */
uint32_t pp_get_time_tick(void)
{
    return rt_tick_get();
}

int main(void)
{
    h_template = pp_template_handle_new();
    if (!h_template)
    {
        printf("create template handle fail.");
        exit(-1);
    }

    pp_template_handle_init(h_template, my_hw_send_cb, my_notify_cb);

    while (1)
    {
        pp_poll((struct pp_handle *)h_template);
    }

    pp_template_handle_delete(h_template);

    exit(0);
}

static void my_notify_cb(struct pp_handle *h, uint8_t evt, const void *param)
{
    switch (evt)
    {
        /* 从机收到的事件 */
        case EVT_SET_SPEED:
            {
                const param_of_notify_set_speed_t *evt_param = param;

                /* 从机收到主机设置转速的命令,此处执行主机的操作,然后应答主机 */
                // 伪代码 ret = set_speed(evt_param->speed);

                /* 应答主机 */
                param_of_notify_resp_of_set_speed_t resp_param;
                resp_param.result.index  = evt_param->frame_index;
                resp_param.result.result = ret; /* 设置转速的执行情况 注意 类型为pp_err_t */
                resp_param.speed         = evt_param->speed;
                pp_template_resp_of_set_speed(h, &resp_param);
            }
            break;
        case EVT_REQ_TEMPERATURE:
            {
                const param_of_notify_req_temperature_t *evt_param = param;

                /* 类似同上 */
            }
            break;
        case EVT_SET_TEMPERATURE:
            {
                const param_of_notify_set_temperature_t *evt_param = param;

                /* 类似同上 */
            }
            break;

        /* 主机收到的事件 */
        case EVT_RESP_OF_SET_SPEED:
            {
                const param_of_notify_resp_of_set_speed_t *evt_param = param;

                if (evt_param->result.result == PP_EOK)
                {
                    /* 收到从机的应答,且没有出错 */
                    LOG_D("Set speed success. index:%d, speed:%.2f", evt_param->frame_index, evt_param->speed);
                }
                else if (evt_param->result.result == PP_ETIMEOUT)
                {
                    /* 说明主机没有收到从机应答SET_SPEED报文,此处可以选择重发或者记录丢包率。 */
                    LOG_D("Set speed timeout. index:%d, speed:%.2f", evt_param->frame_index, evt_param->speed);
                }
                else
                {
                    /* 其他情况,用户自定义处理机制。 */
                }
            }
            break;
        case EVT_RESP_OF_REQ_TEMPERATURE:
            {
                const param_of_notify_resp_of_req_temperature_t *evt_param = param;

                /* 用户自定义处理机制 */
            }
            break;
        case EVT_RESP_OF_SET_TEMPERATURE:
            {
                const param_of_notify_resp_of_set_temperature_t *evt_param = param;

                /* 用户自定义处理机制 */
            }
            break;
        default:
            LOG_D("unkown event.\n");
            break;
    }
}

/************************************************************************************
 *                                  P O R T
 * 
 ***********************************************************************************/

/* This func must be call, when you recieve data. */
void hw_recv_data(uint8_t *data, uint16_t len)
{
    /* 串口收到的数据,通过这个函数保存到接收buffer中 */
    pp_save_hw_recv_data((struct pp_handle *)h_template, data, len);
}

static int my_hw_send_cb(const uint8_t *data, uint16_t len)
{
    /* Use USB,UART,TCP etc. to send data */

    // 伪代码 rt_hw_uart_send(data, len);

    return len; /* !!!若发送成功必须返回len,若发送失败不得返回len */
}


发现bug欢迎指正,共同进步。
回复

使用道具 举报

0

主题

35

回帖

35

积分

新手上路

积分
35
发表于 3 小时前 | 显示全部楼层
你这个有点复杂了
我的做法是弄一个结构体存放所有通讯涉及的变量,然后弄一个参数读写接口直接读写该结构体,不用为其中每个参数都写一个判断或函数
(有需要的话,为读写接口增加钩子回调,读写指定数据的前或后主动调用某用户函数)
超时判断也用不着动态内存分配吧,对实时性有要求的场合是不能用动态内存分配的
譬如,我的超时判断:
https://github.com/dukelec/cdbus ... /utils/cdbus_uart.c
串口无论收到啥数据,无论数据长短,只管喂给 cduart_rx_handle 即可,如果底层串口是 dma 环型接收且存在环回分割,则分两次喂

参数读写接口,见:https://github.com/dukelec/cdbus ... r/common_services.c 的 p5_handler 含数

协议本身文档:https://github.com/dukelec/cdnet ... A%E7%A4%BA%E8%8C%83
回复

使用道具 举报

25

主题

216

回帖

291

积分

高级会员

积分
291
QQ
发表于 3 小时前 | 显示全部楼层
优点
1.比较简单易用

缺点
1.POLL_DELAY 会阻塞,显然导致这个库的使用需要单独占用一个线程,产生额外的调度和线程栈消耗,可以改为非阻塞形式

可改进
1.类似 package_append_byte 这些简单函数的封装,固然符合软件设计哲学,但是在不开优化时等效于额外创建了两个临时变量,造成两次堆栈损耗。可以干脆手工填一下或者使用宏形式。
2.crc 部分功能函数应该可覆盖,g_crc16_table 会造成 RAM 损耗,大部分工程里都有硬件 CRC 或者其他的 CRC 实现,如果真的资源受限,这点 RAM 也是需要省下来的。

我觉得你这个协议框架是定位于资源受限的简单 MCU 上,如果人家资源很丰富没必要用,可以往优化资源占用方向优化。
回复

使用道具 举报

12

主题

112

回帖

153

积分

初级会员

积分
153
 楼主| 发表于 2 小时前 | 显示全部楼层
yono 发表于 2025-7-4 09:32
优点
1.比较简单易用

感谢指导。

1. POLL_DELAY确实是阻塞的,但是可以不用,pp_poll本身就是轮询方式,并没有阻塞。
2. g_crc16_table不会占用RAM,因为是static const变量。
3. 形如package_append_byte的函数,确实可以使用宏或者内联优化,当时是为了考虑大小端问题,定义了一些打包函数,后续会继续优化。
回复

使用道具 举报

12

主题

112

回帖

153

积分

初级会员

积分
153
 楼主| 发表于 2 小时前 | 显示全部楼层
dukelec 发表于 2025-7-4 09:12
你这个有点复杂了
我的做法是弄一个结构体存放所有通讯涉及的变量,然后弄一个参数读写接口直接读写该结构 ...

膜拜大佬。我先研究一下你的代码。
回复

使用道具 举报

25

主题

216

回帖

291

积分

高级会员

积分
291
QQ
发表于 2 小时前 | 显示全部楼层
WALL_E 发表于 2025-7-4 09:53
感谢指导。

1. POLL_DELAY确实是阻塞的,但是可以不用,pp_poll本身就是轮询方式,并没有阻塞。

笔误笔误,ROM,有一颗只有60k flash的芯片被写爆的案例
回复

使用道具 举报

0

主题

35

回帖

35

积分

新手上路

积分
35
发表于 2 小时前 | 显示全部楼层
关于 crc 我的做法是同时支持硬件 crc 和软件 crc,其中软件 crc (modbus crc) 支持 512 字节的表、32 字节的表、以及不查表
其中测试过 32 字节表的计算速度比 512 字节的表慢不了多少,但是 rom 节省很多,适合 rom 空间有限又追求计算速度的场合
一个项目往往硬件 crc 和 软件 crc 要同时使用,譬如 硬件 crc 在处理串口数据接收的时候,又想计算某段 flash 数据的 crc,或者有多个串口通讯的场合
硬件crc我的代码支持加锁,可以分时给多任务复用,但是会额外增加关中断时间,我会尽量避免

支持分段计算的硬件 crc 代码(适合 stm32、at32、gd32 等):
https://github.com/dukelec/cdnet ... tm32/arch_wrapper.c

软件 crc 代码:
https://github.com/dukelec/cdnet/blob/master/utils/modbus_crc.c
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2025-7-4 12:42 , Processed in 0.293180 second(s), 23 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表