STM8学习笔记---Modbus通信协议简单移植

最新更新时间:2021-09-15来源: eefocus关键字:STM8  Modbus  通信协议  移植

Modbus是一种串行通信协议,在工业中应用是比较广泛的。关于Modbus的介绍网上资料很多,这里就不细说了。刚开始接触的时候看Modbus的介绍,光是协议的介绍有几百页,还有各种命令,各种链路层的应用,看了几天,越看越糊涂,越看越不会用。   


最后在单片机上移植成功后才感觉Modbus协议没那么复杂,如果刚开始学的时候,没必要把Modbus协议中每个功能都去了解。就把它当做简单的串口协议,只使用最简单的几个命令就行了。熟悉之后再慢慢了解其他功能。


下面就从单片机串口通信角度去理解Modbus协议,及如何将协议移植到单片机上。


先看看Modbus的协议

从大的方面来讲,协议总共由4部分组成: 地址、功能、数据、校验。


地址1个字节,也就是设备的地址范围是 0 --- 255。


功能码也就是命令,也是一个字节,范围是0---255。


数据位在不同的情况下有不同的长度。


校验位一般用的是CRC校验。


下来看看功能码都有哪些

常用的功能码有表格上面的这些,可以理解为一个数字代表的一种命令。给单片机移植时用03、06、16这三个命令就够用了。


这里面读线圈、写单个线圈、写单个寄存器等等,到底什么是线圈?什么是寄存器?这些都是什么意思?


简单的理解线圈就是位操作。比如说单片机控制了8路的继电器输出,为了方便表示继电器的状态,就用8个位来表示8个继电器的状态,比如0表示继电器断开,1表示继电器吸合。这样0x00就表示8路继电器全部断开,0xFF表示8路继电器全部吸合。


寄存器是字节操作,比如传感器采集温度的时候用一个字节表示当前温度,比如当前温度28℃,就用0x1C表示。

如果理解不了寄存器和线圈的含义就不用管它,就把他当做一个命令来看,在单片机中使用时03、06、16这三个命令就能满足基本需求,下面就单独分析一下这三个命令的含义。


03是读多个保持寄存器值,读取的个数可以设置,比如有8组温度传感器采集数据,要读取温度值,可以一组一组去读,也可以一次性读多个值,读取的个数自己设置。


先看看03的命令格式

请求就是单片机主机发送数据,正常响应就是主机发送的命令格式正确时,从机回复的数据。当主机发送的数据从机不能正确识别时,从机要返回异常响应数据,告诉主机发送的命令有错误。

这里解释一下命令里面各个位的含义,这里是采集8组温度传感器的数值,假如一个从机有8路温度传感器,这个从机的地址就定义为0x01,这个地址根据实际项目可以自己定义。功能码为0x03,这里使用Modbus规定的功能码,意思是读多个寄存器。起始地址为两个字节,表示从第几个温度传感器开始读取数据,寄存器数量也为两个字节,表示要读取几个温度传感器的值。由于只有8路温度传感器,所以起始地址的范围就是 0x0000 ---- 0x0007。寄存器数量的范围为0x0001---0x0008,最少要读取一个寄存器的值,最多读8个寄存器的值。最后就是CRC校验, CRC具体的校验方式这里不用关心,使用的时候直接调用校验函数就行。


这里要注意请求数据的时候要发送起始地址和请求数量,而返回数据的时候就没有请求地址了,只有发送的寄存器字节数。


比如现在要读取第一个温度传感器的值,那么请求数据格式如下:


从站地址     功能码   起始地址高位  起始地址低位  寄存器数量高位  寄存器数量低位  CRC校验高位  CRC校验低位


0x01             0x03            0x00              0x00                   0x00                   0x01                 xx                   xx


从0地址开始,读取1个寄存器的值,也就是读取第一个温度传感器的值。


正常响应返回数据格式如下


从站地址     功能码     字节数   寄存器数量高位  寄存器数量低位  CRC校验高位  CRC校验低位


0x01              0x03      0x02            0x00                  0x1E                  XX                      XX


读取到了2个字节寄存器的值,寄存器值为 0x001E, 0x001E对应的十进制数为30,说明第一个温度传感器的温度值为30℃。


那么异常响应是什么情况下会用到?假如请求数据发送的是读取第9个温度传感器的值,从机接收到数据后发现没有第9个传感器,说明主机发送的地址值超过范围了,那么从机这时就要给主机发送异常响应。常用的异常响应码有下面几种

从异常响应码中可以看出来,地址值不在范围内的异常码为0x02,Modbus规定返回异常响应时,差错码的值为功能码的值加上0x80,当前功能码为0x03,所以返回的差错码数值为0x83,差错码数值为0x02。


请求数据:


从站地址     功能码   起始地址高位  起始地址低位  寄存器数量高位  寄存器数量低位  CRC校验高位  CRC校验低位


0x01             0x03            0x00              0x09                   0x00                   0x01                 xx                   xx


异常响应:


从站地址    差错码   异常码   CRC校验   


0x01            0x83         0x02    xx


再看一个读取多个寄存器值的示例:

下面在看0x06写单个保持寄存器,就是给一个指定的寄存器中写入数据。通信格式如下:

通信示例如下:

可以看到写单个保持寄存器的请求命令和正常响应命令是完全相同的,这个就更好理解了。这块要注意 差错码的值为功能码的值加上0x80,当前功能码为0x06,所以返回的差错码数值为0x86。


下来在看看16(0x10)写多个保持寄存器,写多个保存寄存器和读多个寄存器基本一样,只不过一个是读,一个是写。

这块要注意 差错码的值为功能码的值加上0x80,当前功能码为0x10,所以返回的差错码数值为0x90。


通信示例如下:

响应命令只返回写的寄存器数量,而不返回写的寄存器值,这个和写单个寄存器是不同的。


通过上面的分析对Modbus就会有个大概的了解了,它也没有想得那么复杂。


下面就看看用代码如何实现上面这3个命令的功能。


首先看串口发送和接收代码的实现


#include "uart.h"

#include "stdio.h"

#include "main.h"

 

u8 ReceiveBuf[MaxDataLen] = {0};

u8 RecIndexLen = 0;

void Uart1_IO_Init( void )

{

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

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

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

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

}

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

void Uart1_Init( unsigned int baudrate )

{

    unsigned int baud;

    baud = 16000000 / baudrate;

    Uart1_IO_Init();

    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;       //接收中断使能

}

//阻塞式发送函数

void SendChar( unsigned char dat )

{

    while( ( UART1_SR & 0x80 ) == 0x00 ); //发送数据寄存器空

    UART1_DR = dat;

}

//发送一组数据

void Uart1_Send( unsigned char* DataAdd, unsigned char len )

{

    unsigned char i;

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

    {

        SendChar( DataAdd[i] );

    }

    //SendChar(0x0d);     //发送回车换行,测试用

    //SendChar(0x0a);

}

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

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

__interrupt void UART1_Handle( void )

{

    u8 res = 0;

    res = UART1_DR;

    ReceiveBuf[RecIndexLen++] = res;

    return;

}

          串口代码和常规的用法是一样的,初始化IO口和波特率,然后用中断接收数据,ReceiveBuf数组用来存放接收的数据,RecIndexLen用来统计接收数据的长度。


         一组数据接收完毕之后,调用数据处理函数,来处理接收到的数据。


//处理接收到的数据

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

void DisposeReceive( void )

{

    u16 CRC16 = 0, CRC16Temp = 0;

    if( ReceiveBuf[0] == SlaveID )                                 //地址等于本机地址 地址范围:1 - 32

    {

        CRC16 = App_Tab_Get_CRC16( ReceiveBuf, RecIndexLen - 2 );  //CRC校验 低字节在前 高字节在后 高字节为报文最后一个字节

        CRC16Temp = ( ( u16 )( ReceiveBuf[RecIndexLen - 1] << 8 ) | ReceiveBuf[RecIndexLen - 2] );

        if( CRC16 != CRC16Temp )

        {

            err = 4;                                               //CRC校验错误

        }

        StartRegAddr = ( u16 )( ReceiveBuf[2] << 8 ) | ReceiveBuf[3];

        if( StartRegAddr > 0x07 )

        {

            err = 2;                                               //起始地址不在规定范围内 00 - 07    1 - 8号通道

        }

        if( err == 0 )

        {

            switch( ReceiveBuf[1] )                                //功能码

            {

                case 3:                                            //读多个寄存器

                {

                    Modbus_03_Slave();

                    break;

                }

                case 6:                                            //写单个寄存器

                {

                    Modbus_06_Slave();

                    break;

                }

                case 16:                                           //写多个寄存器

                {

                    Modbus_16_Slave();

                    break;

                }

                default:

                {

                    err = 1;                                       //不支持该功能码

                    break;

                }

            }

        }

        if( err > 0 )

        {

            SendBuf[0] = ReceiveBuf[0];

            SendBuf[1] = ReceiveBuf[1] | 0x80;

            SendBuf[2] = err;                                      //发送错误代码

            CRC16Temp = App_Tab_Get_CRC16( SendBuf, 3 );           //计算CRC校验值

[1] [2] [3]
关键字:STM8  Modbus  通信协议  移植 编辑:什么鱼 引用地址:http://news.eeworld.com.cn/mcu/ic548016.html

上一篇:STM8S003F3 使用定时器来计算方波周期的方法
下一篇:STM8学习笔记---ADC多通道采样

关注eeworld公众号 快捷获取更多信息
关注eeworld公众号
快捷获取更多信息
关注eeworld服务号 享受更多官方福利
关注eeworld服务号
享受更多官方福利

推荐阅读

STM8 低功耗模式 STM8应用笔记
四种STM8低功耗模式的主要特性如表12。(表12:STM8S低功耗模式管理)1.如果外设时钟未被关闭2.包括通讯外设的中断(参见中断向量表)STM8等待(Wait)模式在运行模式下执行WFI(等待中断)指令,可进入等待模式。此时CPU停止运行,但外设与中断控制器仍保持运行,因此功耗会有所降低。等待模式可与PCG(外设时钟门控),降低CPU时钟频率,以及选择低功耗时钟源(LSI,HSI)相结合使用,以进一步降低系统功耗。参见时钟控制(CLK)的说明。在等待模式下,所有寄存器与RAM的内容保持不变,之前所定义的时钟配置也保持不变(主时钟状态寄存器CLK_CMSR)。当一个内部或外部中断请求产生时,CPU从等待模式唤醒并恢复工作
发表于 2021-09-03
<font color='red'>STM8</font> 低功耗模式 <font color='red'>STM8</font>应用笔记
一个STM8串口**程序教程
现在越来越多人用STM8了,我也跟上大众,一起凑热闹。刚开始以为STM8可以随便用串口**程序,于是从阿莫那买了个STM8S Mini Kit,买回来后发现不能串口**,郁闷之极。后来咬咬牙,再买一个三合一,用ST-Link**程序,真是冤大头。后来想想再试试如何用串口**程序,多一种方式备用也好。在网上搜索,发现几乎没有详细的相关教程,只好自己探索了。经过多次摸索,终于明白了如何用串口**程序到STM8了,而且不像网上流传那样是一次性的。不敢独享,做成教程,本文以三合一板为例,所有步骤均带电操作。正文第一步:打开STVP,选择Configure -> Configure ST Visual Programmer。第二步选择
发表于 2021-09-03
一个<font color='red'>STM8</font>串口**程序教程
stm8 pwm实现核心代码
;     TIM2_IER = 0x00;        TIM2_CR1 |= 0x01;        //TIM2_BKR = 0xc0;}STM8S103F3P6  输出 3路 PWM亲测可用。
发表于 2021-09-03
IAR+STM8_EXTI外部IO中断
MCU:STM8S207SB要操作的IO口:PD7 采用下降沿触发中断代码:123456789101112131415161718192021222324252627282930313233343536#include <iostm8s207sb.h> #define LED1_FLASH PD_ODR_ODR3 = !PD_ODR_ODR3 // 开发板上的LED1接在PD3上 void GPIO_init(void){PD_DDR = 0x08; // 配置PD端口的方向寄存器PD3输出PD_CR1 = 0x08; // 设置PD3为推挽输出PD_CR2 = 0x80; // 使能PD7外部中断
发表于 2021-09-03
STM8使用TIM1测量PWM波
一基本TIM1测量PWM波形原理根据上图所示测量PWM的基本思想如下:每个上升沿开始计数,监测到下降沿时记录下当前值为高电平时间,再检测到一个下降沿记录下当前值为周期,同时清零计数器开始测量下个周期。二TIM1 的硬件结构1 信号如何输入的打开手册 翻到  17.5.2 输入模块 如图61,输入部分对相应的TIx输入信号采样,并产生一个滤波后的信号TIxF。然后,一个带极性选择的边缘监测器产生一个信号(TIxFPx),它可以作为触发模式控制器的输入触发或者作为捕获控制。该信号通过预分频进入捕获寄存器(ICxPS)。根据该图和说明了解到和PWM测量相关的信息1) TIM1有四个外部IO引脚可以输入信号 TIM1_CHx2)&
发表于 2021-09-03
<font color='red'>STM8</font>使用TIM1测量PWM波
从头学STM8单片机之"Hello,world"
  最近做了一个数显电压表,电路上支持交流或直流电压显示,软件上支持七种不同的满量程档位,可以自由切换,有小数点自动移位,无效零消隐,超量程指示等功能,修改外部元件就可以做成不同量程的交流或直流电压表,不会编程的朋友也可以DIY出各种不同量程的交流或直流电压表。  主要元件有STM8S003F3单片机一片,自带ADC,0.56寸三位共阴数码管一个,再就是阻容稳压什么的,电路也很简单,爱好者自制很方便。本贴的程序都能在此硬件上测试通过。下面是电路原理图:  自从去年开始学了单片机以后,发现单片机没有想象中那么难,所以想做一个教程和大家一起共同学习。我的编译环境是IAR的EWSTM8-2102,本贴代码都在此环境下测试通过,其它编译
发表于 2021-09-02
从头学<font color='red'>STM8</font>单片机之
小广播
何立民专栏 单片机及嵌入式宝典

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

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