# stream **Repository Path**: coding_everything/stream ## Basic Information - **Project Name**: stream - **Description**: 串口数据流处理库 - **Primary Language**: C - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 4 - **Created**: 2022-11-26 - **Last Updated**: 2022-11-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # STREAM框架 ## 简介 这里的stream可不是那个有名的游戏平台。stream有“流”的含义,对应这个框架是用于处理串口数据流的。 stream框架的开发初衷是为了解决多个串口协议同时生效时的串口中断处理时间过长的问题。通过建立缓存将串口数据保存下来,然后在主函数中再处理。这样可以保证中断能快速的退出,不影响其他函数的执行。 ## 框架构成 为了兼容多种串口协议,stream框架没有使用结束符触发处理的结构。因为在协议多了之后,每次扫描判断结束符会是一笔不小的开销,同时一个协议的正常数据可能也会是另一个协议的结束符。因此本框架采用了中断记录+超时触发的结构。一般而言,一条数据帧内的数据都会是连续发出的,而帧与帧之间都会有一定的时间间隔,所以这个结构肯定可以适应绝大多数串口协议。 ## 框架与组件 其实这个框架真的很简单,串口中断保存数据,定时器判断是否超时,就这两点就没了。简单,意味着执行效率高;简单,意味着可扩展性强。 说到扩展性,本git仓库目前内置了1个比对组件、还提供了2个协议组件,可供大家评估stream的扩展性。只要弄懂这几个组件,就可以自己开发出新的组件了。 - 比对组件:将接收到的数据和字符串进行比对。 - modbus-rtu从机组件:工控设备常见的通信协议。上位机不计其数,属于嵌入式工程师开发设备首选。 - fur组件:ECBM工作室自研协议,人机交互最强,数据可直读,普通串口助手就能用。 ## STREAM框架安装和设置 ### 安装攻略 第一步:添加stream.c到工程中,然后在main.c里加载头文件stream.h。![1](https://gitee.com/ecbm/stream/raw/master/%E5%9B%BE%E7%89%87/1.png) 第二步,定义时间基准,用于超时判断。在ECBM库中,通过使能TIMER库,然后在main.c中添加 ```c timer_init();//初始化定时器。 timer_set_timer_mode(0,2000);//定时器0每2000us中断一次。 timer_start(0);//开启定时器0。 ``` 如上所示,配置定时器每2mS中断一次。然后在定时器中断中加入超时判断函数 ```c void tim_fun(void)TIMER0_IT_NUM{ ECBM_STREAM_TIMEOUT_RUN(); } ``` 第三步,对接串口。由于stream是基于串口的应用框架,所以选择一个串口来定义stream的数据输入输出函数。在ECBM库可以这样定义 ```c void ecbm_stream_out(esu8 dat){ uart_char(1,dat); } void uart1_receive_callback(void){ ECBM_STREAM_IN(SBUF); } ``` 定义ecbm_stream_out函数是向串口1发送一个字节。同时在串口1的接收回调中加入ECBM_STREAM_IN,于是每接收到一个字节就会执行一次ECBM_STREAM_IN。 第四步,定义stream执行组件函数。可以不放组件,但是这个函数必须定义。 ```c void ecbm_stream_exe(esu8 dat){ dat=dat; //目前还是空的,没有组件。 } ``` 第五步,在循环中添加stream的ecbm_stream_main函数。最终效果如下: ```c #include "ecbm_core.h" //加载库函数的头文件。 #include "stream.h" void main(){ //main函数,必须的。 system_init(); //系统初始化函数,也是必须的。 timer_init(); //初始化定时器。 timer_set_timer_mode(0,2000);//定时器0每2000us中断一次。 timer_start(0); //开启定时器0。 while(1){ ecbm_stream_main(); } } void tim_fun(void)TIMER0_IT_NUM{ ECBM_STREAM_TIMEOUT_RUN(); } void ecbm_stream_out(esu8 dat){ uart_char(1,dat); } void uart1_receive_callback(void){ ECBM_STREAM_IN(SBUF); } void ecbm_stream_exe(esu8 dat){ dat=dat; //目前还是空的,没有组件。 } ``` ### 设置攻略 安装完毕之后,还要根据实际应用设置两个参数。打开stream.h,切换到图形化配置界面可以看到两个选项。 - 队列缓存大小:stream框架采用队列缓存,先入先出。该参数的大小应大于协议一帧的最大内容。否则当缓存爆掉的时候,就会丢失数据。比如fur协议,最大可能会出现“[65535@255]|=0x1123;”共20个字符,那么把缓存大小设定为21。最好不要不多不少得设置为20,防止因干扰出现单个杂波使得整个缓存爆掉。推荐在ram够用的情况下,缓存越大越好。 - 数据帧超时时间:stream框架在超时的时候才会去处理数据,因此该时间需要根据实际情况设定。在此页面设置的值是指定时器中断的次数。如果该值填100,而定时器2mS中断一次。那么当串口接收最后一个字节之后过了2*100=200mS就会触发超时。然后主循环里的ecbm_stream_main函数就会开始执行。ecbm_stream_main函数会判断超时标志位,不超时的话不会执行。如果超时了但是主循环还没执行到ecbm_stream_main函数的话也不会去解析数据。这一点需要注意,特别是上位机也有超时判断的时候。 ## 比对组件 比对组件比较简单,因此已经集成到stream.c中。因此不需要改动工程,只需要在ecbm_stream_exe函数里添加比对组件的函数ecbm_stream_strcmp即可。 比对组件的使用方法就是通过比较串口接收的数据和给定字符串的比对值,当比对值和字符串的字数相等的时候,说明比对成功。 示例: ```c u8 cmp_on=0,cmp_off=0; void ecbm_stream_exe(esu8 dat){ dat=dat; ecbm_stream_strcmp(dat,"LED_ON" ,&cmp_on ); ecbm_stream_strcmp(dat,"LED_OFF",&cmp_off); if(cmp_on ==6){P55=0;} if(cmp_off==7){P55=1;} } ``` 解说:首先定义两个变量用于储存比对值。然后变量cmp_on对应着字符串“LED_ON”的比对值,当cmp_on的值和字符串“LED_ON”的字符数量相等时,令P5.5输出低电平。通常会用单片机的低电平来驱动LED亮,那么此时LED是亮的。同理,当cmp_off的值和“LED_OFF”的字符个数相等时,令P5.5输出高电平LED灭。 ## modbus-rtu从机组件 ### 简介 modbus-rtu组件就是常说的MODBUS了,是一个在工业上非常常见的通信协议。它有很多种变种,其中用的最多的就是modbus-rtu和modbus-ascii。他们的区别就是rtu以原始数据表示数据,ascii以字符形式表示数据。ascii更加方便人类读取,rtu在解码方面更方便机器读取,所以这里采用的是modbus-rtu。 ### 使用攻略 modbus的用法、原理和通信格式在网上随处可见,这里就不赘述了。本组件收纳了常用的7个指令: 1. 【01】读线圈。 2. 【05】写单个线圈。 3. 【03】读寄存器。 4. 【06】写单个寄存器。 5. 【10】写多个寄存器。 6. 【02】读离散量输入。 7. 【04】读输入寄存器。 ### 从机安装攻略 第一步:添加stream_modbus.c到工程中,然后在main.c里加载头文件stream_modbus.h。 第二步:定义crc错误回调函数es_modbus_rtu_crc_err_callback。可以写上错误后发送指令到上位机请求重发,也可以留空不写。但是一定要定义,不然单片机就会跑飞。 ```c void es_modbus_rtu_crc_err_callback(void){ //不做什么处理的话,可以留空。 } ``` 定义ID获取函数es_modbus_rtu_get_id。可以返回一个变量或者一个常量。但是不能是广播地址且在总线中为唯一值。这个ID用于modbus通信,只有ID匹配上的数据才会被本机接收。 ```c esu8 es_modbus_rtu_get_id(void){ return 1; } ``` 第三步:根据实际需求定义底层读写函数。比如需要读写线圈(位变量)就需要【01】读线圈和【05】写单个线圈这两指令,对应需要定义es_modbus_cmd_write_bit和es_modbus_cmd_read_bit函数。 ```c void es_modbus_cmd_write_bit(esu16 addr,esu8 dat){ if((addr>=0)&&(addr<=7)){//前8个位变量都是保存在一个u8变量buf中。 if(dat){//假如要写1, buf|=1<=0)&&(addr<=7)){//前8个位变量都是保存在一个u8变量buf中。 if(buf&(1<=1024)&&(addr<=1033)){ //地址在1024~1033的时候,对应返回main_info的0~9。 return main_info[addr-1024]; }else if(addr==65535){//也可以返回常量。 return (UART_BAUD/100);//当访问地址65535时,返回串口波特率(当然波特率数值会很大,比如115200,超过了u16型能显示的范围,所以数值除以了100。这样显示就1152了)。 } return 0; } ``` es_fur_data_in函数是将一个数放入寄存器的函数,该函数的参数会给出寄存器的地址和要放入的值,然后需要自己写上赋值部分。和上面一样自由度很大: ```c void es_fur_data_in(esu16 addr,esu16 dat){ if((addr>=1024)&&(addr<=1033)){ //地址在1024~1033的时候,对应返回main_info的0~9。 main_info[addr-1024]=dat; }else if(addr==65535){//当往65535写值的时候,就会放大100倍再设置成新的波特率。 uart_set_baud((u32)dat*100); } } ``` 第三步:定义好上面的基础函数之后,把对接函数es_fur_exe放入ecbm_stream_exe里就好了。 ```c void ecbm_stream_exe(esu8 dat){ dat=dat; es_fur_exe(dat); } ``` 至此,FUR组件已经可以使用了。 ### 主机使用攻略 未完待续