STM8单片机串口同时识别自定义协议和Modbus协议

最新更新时间:2022-01-15来源: eefocus关键字:STM8  单片机  自定义协议  Modbus协议 手机看文章 扫描二维码
随时随地手机看文章

  在单片机开发中,串口是最常用的和外界交换数据的渠道,要使用串口,那必不可少的就是通信协议,通信协议就是单片机和外界通信的语言,要想正常和其他设备正常交流,首先语言必须相通。


  在实际开发过程中由于各种原因,导致很多时候单片机和外界其他设备协议不兼容,在使用的时候就比较麻烦。比如单片机要和两个设备通信,但是这两个设备的通信协议的不一样,在使用时单片机就必须使用两个串口分别和两个设备通信。如果这两个设备同时使用时还不感觉到资源浪费,如果每次只接一个设备,那么另一个串口也不能作为其他功能使用,还得留着备用。这样的话单片机的资源就被白白浪费掉了。于是想着能不能在一个串口上支持两个协议,让单片机自动去识别接收到的数据使用的是哪个协议。


  一般使用通信协议接收数据时,都需要通过判断数据头和数据尾来确定什么时候开始接收数据,什么时候停止接收数据。但是如果要兼容多个协议的话,就不能使用这个方式去接收数据。必须先将一组数据接收完毕,然后根据数据的特点去分析数据使用的是哪个协议。


  首先要实现的就是如何判断一组数据是否接收完毕。


  实现的大概思路就是,单片机使用中断去接收数据,同时记录接收到的数据长度,在主函数中循环的去读取串口接收到的字符长度,如果超过一定时间之后,串口中接收到的数据长度没有发生变化,就说明一组数据接收完毕了。


  比如在主函数中读取到了当前串口数据长度不为0,说明此时串口正在接收数据,此时记录下当前串口数据长度,延时一段时间再去读取一次串口接收数据的长度,如果此时数据长度和上一次数据长度一样,说明串口接收数据结束了,就可以去处理接收到的数据了,如果此时数据长度和上一次的数据长度不一样,说明串口正在接收数据,接收数据还未结束,不能去处理数据。


  下面就通过一个工程案例来演示一个串口兼容两种协议的使用方法。


  设备默认使用的是自定义协议,协议格式如下:

[ 头1 ] (0xA5) [ 头2 ] (0x5A) [ 地址 ] [ 命令 ] [ 数据高位 ] [ 数据低位 ] [ 尾1 ] (0x55) [尾2] (0xAA)


  后来设备需要和市场上其他的工业设备对接,而大多数工业设备使用的都是Modbus协议,如果直接将设备协议修改为Modbus协议的话,那么好多旧设备和新设备就会不兼容,为了兼容旧设备同时又要对接其他工业设备,那么设备要在自定义协议的基础上兼容Modbus协议。Modbus协议格式如下:


[ 地址 ] [ 功能码 ] [ 起始地址高 ] [ 起始地址低] [ 总寄存器数高 ] [ 总寄存器数低 ] [ CRC低 ] [ CRC高 ]


  下面分析这两种协议的特点。


  首先看自定义协议,自定义协议的数据长度是固定的,同时数据的开始和结尾都是由两个字节标识。那么识别自定义协议就和容易了,直接判断数据头和数据为就行了。当串口接收一组数据结束后,判断接收到的数据 是不是以 0xA5和0X5A 开头,同时以 0x55 和0xAA结尾。如果是那么就使用自定义协议去解析数据。


  接下来分析Modbus协议,由于设备都是单独使用的,不需要级联,所以设备的地址是固定的0x01,同时由于设备的功能比较简单,所以在使用Modubus协议的时候,只用到了读保持寄存器(0x03)和写保持寄存器(0x06)这两个功能,所以Modbus的数据开头只有两种情况 0x01 0x03 和 0x01 0x06,如果数据开头是这两种情况,那么就使用Modbus协议去解析数据。


  通过对协议的分析,思路已经很清晰了,接下来使用代码来实现。


struct uart_info

{

    u8 cnt;

    u8 rec_buf[10];

};


struct uart_info uart1;


//在Library Options中将Printf formatter改成Large

//重新定向putchar函数,使支持printf函数

int putchar( int ch )

{

    while( !( UART1_SR & 0X80 ) ); //循环发送,直到发送完毕

    UART1_DR = ( u8 ) ch;

    return ch;

}

static void uart_io_init( void )

{

    PD_DDR |= ( 1 << 5 ); //输出模式 TXD

    PD_CR1 |= ( 1 << 5 ); //推挽输出

    PD_DDR &= ~( 1 << 6 ); //输入模式 RXD

    PD_CR1 &= ~( 1 << 6 ); //浮空输入

}

//波特率最大可以设置为38400

void uart_init( unsigned int baudrate )

{

    unsigned int baud;


    uart_io_init();


    baud = 16000000 / baudrate;

    UART1_CR1 = 0;

    UART1_CR2 = 0;

    UART1_CR3 = 0;

    UART1_BRR2 = ( unsigned char )( ( baud & 0xf000 ) >> 8 ) | ( ( unsigned char )( baud & 0x000f ) );

    UART1_BRR1 = ( ( unsigned char )( ( baud & 0x0ff0 ) >> 4 ) );

    UART1_CR2_bit.REN = 1;        //接收使能

    UART1_CR2_bit.TEN = 1;        //发送使能

    UART1_CR2_bit.RIEN = 1;       //接收中断使能

}



//接收中断函数 中断号18

#pragma vector = 20                             // IAR中的中断号,要在STVD中的中断号上加2

__interrupt void UART1_Handle( void )

{

    unsigned char res = 0;

    UART1_SR &= ~( 1 << 5 );                    //RXNE 清零


    res = UART1_DR;

    if( uart1.cnt < 9 )

        uart1.rec_buf[uart1.cnt++] = res;

}


  定义一个结构体来存储接收到的数据长度和数据,由于自定义协议和Modbus协议最长的数据只有8个字节,所以这里的数组长度设置10就可以了。下面初始化串口使用的IO口,设置数据位和波特率。最后在中断中接收数据并存储到数组中。


  接下来在编写一个函数用来分析接收到的数据。


//检测串口数据

void read_uart( void )

{

    static u8 recevie_buf[10];

    static u8 recevie_cnt = 0;

    u8 i = 0;


    delay_ms( 10 ); //隔一段时间检测一次串口数据长度,如果数据长度没有发生变化说明串口接收数据完成


    if( ( uart1.cnt != recevie_cnt ) && ( uart1.cnt > 6 ) )

    {

        recevie_cnt = uart1.cnt;

        for( i = 0; i < recevie_cnt; i++ )            //拷贝数据

        {

            recevie_buf[i] = uart1.rec_buf[i];

        }

        //根据接收到的数据区分协议

        //自定义协议

        if( ( recevie_buf[0] == 0xA5 ) && ( recevie_buf[1] == 0x5A ) )

        {

            self_define_protocol( recevie_buf, recevie_cnt );

        }

        //modbus协议

        if( ( recevie_buf[0] == 0x01 ) && ( ( recevie_buf[1] == 0x03 ) || ( recevie_buf[1] == 0x06 ) ) )

        {

            modbus_protocol( recevie_buf, recevie_cnt );

        }


        //清空数组

        for( i = 0; i < 10; i++ )

        {

            uart1.rec_buf[i] = 0;

            recevie_buf[i] = 0;

        }

        uart1.cnt = recevie_cnt  = 0;

    }

}


  由于此设备中两种协议的数据长度都比较小,所以这里并没有分两次去判断数据长度,然后根据数据长度来判断数据接收是否完毕。只是延时10ms之后去判断数据长度,当接收的数据长度大于6时,说明数据基本已经接收完成了。由于数据的最大长度是8,所以接收的数据长度大于6时,基本数据已经接收完了,在中断中接收8个数据速度还是非常快的。如果数据量比较大,数据比较长时,最好还是通过数据长度去判断比较可靠。这里为了编写方便,就简单的时候延时去判断了。


  当串口接收的数据长度大于6时,说明一组数据已经接收完毕了,此时需要将串口接收的数据拷贝一份出来在使用。那为什么要将数据拷贝出来,而不是直接使用串口接收缓存区的数据呢?这是为了防止在处理数据的过程中国,串口又接收到了新的数据,这样新数据就会将旧的数据覆盖掉,有可能导致数据异常。为了数据的安全性将数据拷贝一份使用,即使在处理数据过程中串口缓存区的数据发生了变化,也不会破坏掉上一次接收的数据。


  数据拷贝结束后,就根据数据的特点来判断当前接收到的数据使用的是哪种协议。如果接收到的数据前两个是 0xA5和0x5A那么就直接调用自定义协议处理函数,如果前面的数据是0x01和0x03或者是0x01和0x06那么就直接调用Modbus协议去处理函数。


  接下来就可以单独编写两个函数分别处理这这种协议。


//处理自定义协议

// [头1](0xA5) [头2](0x5A) [地址]  [命令]  [数据高位] [数据低位]  [尾1](0x55) [尾2](0xAA)

//A5 5A 00 01 01 90 55 AA

void self_define_protocol( u8 arr[], u8 size )

{


    if( ( arr[0] == 0xA5 ) && ( arr[1] == 0x5A ) && ( arr[6] == 0x55 ) && ( arr[7] == 0xAA ) )

    {

//根据命令值执行不同的动作

    }

}


//处理modbus协议

//[地址][功能码][起始地址高][起始地址低][总寄存器数高][总寄存器数低][CRC低][CRC高]

//01 03 00 00 00 01 84 0A

//01 06 00 00 03 E8 89 74

void modbus_protocol( u8 arr[], u8 size )

{

    //调用 modbus相关处理代码

}


  将数据协议识别出来之后,就可以按照通常处理协议的方式去处理数据了。最后在主函数中循环的调用数据查询函数,检查串口是否有接收到数据。


void main( void )

{

   

    __asm( "sim" );                             //禁止中断

    SysClkInit();

    delay_init( 16 );

    LED_GPIO_Init();

    uart_init( 9600 );

    __asm( "rim" );                             //开启中断

    while( 1 )

    {

        read_uart();

    }

}


  接下来分别用这两种协议测试代码。

在这里插入图片描述

  使用串口助手发送自定义协议时,代码就会调用自定义协议处理函数。

在这里插入图片描述

  发送Modbus协议时,代码就会调用Modbus协议处理函数。


  这样根据协议的特点可以通过代码自动去识别协议的类型,用一个串口就可以实现不同协议的解析。按照同样的方法还可以解析更多的协议。当前在不同的协议使用的波特率要相同,否则波特率不同解析出来的数据就会出现错误,导致协议解析失败。

关键字:STM8  单片机  自定义协议  Modbus协议 编辑:什么鱼 引用地址:http://news.eeworld.com.cn/mcu/ic558812.html

上一篇:IAR软件中直接查看编译后代码大小
下一篇:在STM8单片机中自己实现 printf()函数功能

推荐阅读

利用定时器使单片机输出方波
在P1^0口输出方波。 废话不多说,直接上代码。#include <reg52.h>sbit FB = P1^0;void Init_ET_0()//定时器初始化函数{ TMOD = 0x01;//启用定时器 0 ,工作模式为 模式1 。 EA = 1;//开总中断 ET0 = 1;//开定时器开关 TR0 = 1;//启动定时器 TH0 = (65536 - 500)/256;//给高八位赋初值 TL0 = (65536 - 500)%256;//给低八位赋初值}void Servoce_ET_0() interrupt 1 //定时器服务函数{ TH0 = (65536 - 500)/256;//重装高八位 TL0
发表于 2022-08-11
利用定时器使<font color='red'>单片机</font>输出方波
51单片机外接ADC0808制作简易电压表
main.c#include<reg52.h>#define uchar unsigned char#define uint unsigned int sbit Start = P3^0;sbit EOC=P3^1; //sbit OE=P3^2 ; //uchar code table [] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x40};//段选uchar code table_SMG [] = {0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07};//位选 //------------------------变量区-
发表于 2022-08-11
51<font color='red'>单片机</font>外接ADC0808制作简易电压表
新唐N76E003+GPRS 单片机复位故障排除手记
应用场景GPRS模块采用AIR202/AIR208,电源方案是JW5033;单片机采用新唐N76E003,双串口,18K flash,电源与GPRS的DCDC 即JW5033共用一个5V输入电源。问题描述GPRS联网时或联网成功后收发数据的数据,新唐单片机复位重启。故障排除1、单片机电源前增加电容初步怀疑是供电不足,使用示波器观察后排除此原因,如果真的是供电不足,GPRS模块也应该会同时重启,实际上单片机模块重启时GPRS模块一直正常工作。2、设置单片机P2.0引脚为输入模式而非复位模式将P2.0引脚由复位引脚更改为输入引脚后,故障排除。我的理解是更改为输入引脚后,射频干扰不足以引起单片机复位了,深层次的原因不详。
发表于 2022-08-10
新唐N76E003+GPRS <font color='red'>单片机</font>复位故障排除手记
关于51单片机的地址空间溢出以及解决方法的个人看法(proteus无法仿真)
在51单片机程序编写中,当变量过多而且数据类型过大的话,容易在编译的时候出现以下的问题,具体表现为: *** ERROR L107: ADDRESS SPACE OVERFLOWSPACE: DATA SEGMENT: _DATA_GROUP_LENGTH: 0019HProgram Size: data=130.0 xdata=7 code=3267Target not created.这样子也就是所谓的编译失败,不能生成HEX文件,更加无法烧写到程序中去,那么如何解决这个问题呢,我在网上找到了解法,网上的具体步骤如下:1.先点击这个图标2.在其中的memory model 将small改成compact选项,那么即可编译通过。3
发表于 2022-08-10
关于51<font color='red'>单片机</font>的地址空间溢出以及解决方法的个人看法(proteus无法仿真)
51单片机脉冲信号的计数和LCD显示
设计目标和思路在个人剂量仪开发的过程中,从探头输出的核脉冲信号经过后期放大,成形,滤波以及甄别之后,成为一系列脉冲信号输出,这种脉冲信号由于衰变的随机性,并不是固定频率的脉冲信号。我们本次设计就是针对这种脉冲进形脉冲信号的计数以及在LCD1602显示屏上面的显示。这是C语言的代码部分#include"reg51.h" #include <stdio.h>#include <math.h>#include <string.h>#include <intrins.h>#define uint8_t unsigned char //0-255#define uint16
发表于 2022-08-10
51<font color='red'>单片机</font>脉冲信号的计数和LCD显示
51单片机之74HC138译码器控制数码管进行100s计数
/** I use a timer in my code to control the time I use 138decoder int the hardware**/#include<reg51.h>#define uint unsigned int#define uchar unsigned charsbit LSA=P2^2;sbit LSB=P2^3;sbit LSC=P2^4;uchar code tab_1[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};uchar tab_2[2];uchar counter=0,time=0,counter_T
发表于 2022-08-10

推荐帖子

头文件
在IAREW有#include<intrinsics.h> 但是在ICC中编译不过去, 在ICC中对应的头文件是什么?头文件
mz_flying 嵌入式系统
FPGA设计的八个重要知识点,你都会吗
1.面积与速度的平衡与互换这里的面积指一个设计消耗FPGA/CPLD的逻辑资源的数量,对于FPGA可以用消耗的FF(触发器)和LUT(查找表)来衡量,更一般的衡量方式可以用设计所占的等价逻辑门数。面积和速度这两个指标贯穿FPGA/CPLD设计的时钟,是设计质量的评价的终极标准——面积和速度是一对对立统一的矛盾体。要求一个同时具备设计面积最小、运行频率最高是不现实的。更科学的设计目标应该是在满足设计时序要求(包括对设计频率的要求)的前提下,占用最小的芯片面积。或
xyd18025265652 FPGA/CPLD
EEWORLD下载中心鼎力奉献(二)程序员修炼之道--从小工到专家
EEWORLD下载中心鼎力奉献(二)程序员修炼之道--从小工到专家在上一个专题中,我们给大家搜集整理了国外优秀的C语言学习书籍,大家可以根据自己的需要选取其中的学习。如果能够系统的学习其中的一部分,那么相信大家一定能够很好地掌握C语言编程。那么学会了C语言编程后,接下来就是要学习一些编程经验和编程方法,以使我们的编程水平能够得到大幅度的提高。在这一个专题中,我们搜集了一些编程修炼方面的书籍,通过这些数据,可以是大家的编程水平打到一个相对高的水平。该专题包含的内容及下载地址如下,照
tiankai001 下载中心专版
毕业设计,基于单片机的航模控制系统设计
希望各位大哥帮帮忙啊!!!在此小弟感激不尽!!!毕业设计,基于单片机的航模控制系统设计
tttplay 单片机
白皮书:SSB-1
SSB-1白皮书:SSB-1
1234 测试/测量
什么是 UWB,为什么会出现在我的手机中?超宽带技术,解释
“超宽带无线技术”,通常也称为UWB,在过去几年中逐渐被接受和采用。简而言之,UWB是一种短距离无线通信协议,与Wi-Fi、蓝牙和NFC等现有标准共存。不过,不要将其与Verizon的超宽带移动网络混淆——这是一种完全不同的5GmmWave技术,名称相似。[localvideo]0d5d0663e749a7bd1e505c5a040a6a0e[/localvideo] 什么是UWB,为什么会出现在我的手机中?超宽带技术
btty038 RF/无线
小广播
实战 培训 开发板 精华推荐

何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2022 EEWORLD.com.cn, Inc. All rights reserved