风速传感器HS-FS01 485型采用Modbus-Rtu通信协议,本代码部分基于正点原子f1战舰V3。
首先,了解一下什么是Modbus协议。
Modbus是一种串行通信协议,是Modicon公司(现在的施耐德电气 Schneider Electric)于1979年为使用可编程逻辑控制器(PLC)通信而发表。Modbus已经成为工业领域通信协议的业界标准(De facto),并且现在是工业电子设备之间常用的连接方式。
Modbus通讯协议是应用层报文传输协议(OSI模型第7层),它定义了一个与通信层无关的协议数据单元(PDU),即PDU=功能码+数据域。Modbus协议只能主站和从站之间通信,从站和从站之间不能通信。Modbus某些特征是固定的,如信息帧结构,帧顺序,通信错误,异常情况处理,及所执行的功能码。其他特征是可选的(传输介质、波特率、奇偶校验、停止位个数、参数字址定义)。
Modbus协议能够应用在不同类型的总线或网络。对应不同的总线或网络,Modbus协议引入一些附加域映射成应用数据单元(ADU),即ADU=附加域+PDU。目前,Modbus有下列三种通信方式:
1. 以太网,对应的通信模式是MODBUS TCP。
2. 异步串行传输(各种介质如有线Rs232-/422/485/;光纤、无线等),对应的通信模式是MODBUS RTU或MODBUS ASCII。
3. 高速令牌传递网络,对应的通信模式是Modbus PLUS
Modbus通信协议得到广泛应用的原因是:
- 公开发表并且无版权要求
- 易于部署和维护
- 对供应商来说,修改移动本地的比特或字节没有很多限制
在介绍通信协议之前,首先要明白通信协议三要素:
- 通信接口标准(即硬件协议,如电平高低)
- 通信格式 (异步通信中,双方必须统一设置的参数,数据的传送方式、传送数据的长度、校验的方法,和传输速率)
- 通信数据格式 (规定传输的内容,数据帧的结构)
本片内容只介绍Modbus三种通信方式的第二条,异步串行传输(Rs485)所对应的两种通信模式MODBUS ASCII 模式和MODBUS RTU 模式。两种模式在通讯上有所不同。
- MODBUS ASCII
主要优点是允许字符之间的时间间隔长达1s,也不会出现错误。同时也是其缺点,传输速度较慢。
ASCII模式通信格式约定
- 起始位 :1位
- 数据长度 : 7位 ,低位先送
- 校验位 : 1位(有校验) , 0位(无校验)
- 停止位 : 1位(有校验) ,2位(无校验)
- 波特率 : 可选
ASCII模式数据格式约定
信息编码 : 16进制。
LRC校验码(参与校验的位置从地址码到数据区)
算法:(8位校验码) 相邻2个16进制符相加求和。 取其和的低八位的补码为校验码。
例:
求和: H01+H03+H21+H02+H00+H02 = H29 求补码 : H29的补码为 :HD7 则LRC校验码为HD7
ASCII模式数据传送约定
ASCII模式数据传送约定在数据格式中每个16进制字符都转换成ASCII码发送。
如上图则转换为: : 0 1 0 3 2 1 ········································
对应的ASCII码 : (0x) 30 31 30 33 32 31 ········································
- MODBUS RTU
主要优点是在相同波特率下其传输的字符的密度高于ASCII模式,每个信息必须连续传输。同时也是其缺点,传输过程中更容易出错。
RTU模式通信格式约定
- 起始位 : 1位
- 数据长度 : 8位 ,低位先送
- 校验位 : 1位(有校验) ,0位(无校验)
- 停止位 : 1位(有检验) ,2位(无校验)
- 波特率 : 可选
RTU模式数据格式约定
信息编码 : 16进制
CRC校验码(参与校验的位置从地址码到数据区)
算法 :(16位校验码) 由于算法比较复杂,附相关代码 (附一)
RTU模式数据传送约定
RTU模式数据传送约定按数据格式中16进制字符进行连续发送,如果发送帧期间,出现大于1.5个字符的停止时间时,则信息会出现错误。
发送实例:
/*******************************************************************************************************************************************/
- 常用功能码
风速传感器HS-FS01使用的功能码。其他功能码请参考 MODBUS功能码
modbus.c 和modbus.h (CRC校验函数在附一中)
modbus.h
/*****************************************************************/
#ifndef __MODBUS_H__
#define __MODBUS_H__
#include "stm32f10x.h"
#define T_R_Mode PDout(7)typedef struct mod{u8 address; //设备地址u8 sendbuf[64];//发送缓冲区u8 recbuf[64]; //接受缓冲区u8 timflag; //数据接收时间u8 timrun; //定时器启动标志u8 reflag; //接收到一帧数据的标志u8 recount; //接受的数据个数u8 secount; //发送的数据个数}Modbus;void modbus_init(void);
void modbus_fun3(u8 add,u16 readd,u16 renum);
void modbus_display(void);
void modbus_event(void);#endif/******************************************************************/modbus.c
/******************************************************************/#include "modbus.h"
#include "sys.h"
#include "misc.h"
#include "ModbusCRC.h"
#include "stdio.h"extern vu8 temp;
Modbus modbus;void modbus_init()
{GPIO_InitTypeDef GPIO_InitTypeStruct;USART_InitTypeDef USART_InitTypeStruct;NVIC_InitTypeDef NVIC_InitTypeStruct;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOD,ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);GPIO_InitTypeStruct.GPIO_Mode=GPIO_Mode_Out_PP;GPIO_InitTypeStruct.GPIO_Pin=GPIO_Pin_7;GPIO_InitTypeStruct.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOD,&GPIO_InitTypeStruct); // 收发控制GPIO_InitTypeStruct.GPIO_Mode=GPIO_Mode_AF_PP;GPIO_InitTypeStruct.GPIO_Pin=GPIO_Pin_2;GPIO_InitTypeStruct.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitTypeStruct); //PA2 USART2 TXGPIO_InitTypeStruct.GPIO_Mode=GPIO_Mode_IN_FLOATING;GPIO_InitTypeStruct.GPIO_Pin=GPIO_Pin_3;GPIO_InitTypeStruct.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitTypeStruct); //PA3 USART2 RXUSART_InitTypeStruct.USART_BaudRate=9600;USART_InitTypeStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;USART_InitTypeStruct.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;USART_InitTypeStruct.USART_Parity=USART_Parity_No;USART_InitTypeStruct.USART_StopBits=USART_StopBits_2;USART_InitTypeStruct.USART_WordLength=USART_WordLength_8b;USART_Init(USART2,&USART_InitTypeStruct);USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);NVIC_InitTypeStruct.NVIC_IRQChannel=USART2_IRQn;NVIC_InitTypeStruct.NVIC_IRQChannelCmd=ENABLE;NVIC_InitTypeStruct.NVIC_IRQChannelPreemptionPriority=1;NVIC_InitTypeStruct.NVIC_IRQChannelSubPriority=2;NVIC_Init(&NVIC_InitTypeStruct);USART_Cmd(USART2,ENABLE);T_R_Mode=1; //默认为发送模式}void modbus_fun3(u8 add,u16 readd,u16 renum)
{u16 crc,i;modbus.secount=0;modbus.sendbuf[modbus.secount++]=add; //读取设备地址modbus.sendbuf[modbus.secount++]=0x03; //功能码modbus.sendbuf[modbus.secount++]=readd/256; //读取寄存器高地址modbus.sendbuf[modbus.secount++]=readd%256; //读取寄存器低地址modbus.sendbuf[modbus.secount++]=renum/256; //读取寄存器数量高八位modbus.sendbuf[modbus.secount++]=renum%256; //读取寄存器数量低八位crc=crc16(modbus.sendbuf,modbus.secount); //计算CRC校验码modbus.sendbuf[modbus.secount++]=crc/256; //校验码高八位modbus.sendbuf[modbus.secount++]=crc%256; //校验码低八位T_R_Mode=1; //sendfor(i=0;i<modbus.secount;i++){USART_SendData(USART2,modbus.sendbuf[i]);while(!USART_GetFlagStatus(USART2,USART_FLAG_TC));}T_R_Mode=0;
}void modbus_display()
{u8 i;printf("----------------------------------\r\n");printf("-----------发送的命令为-----------\r\n");for(i=0;i<modbus.secount;i++){printf("%2X ",modbus.sendbuf[i]);}printf("\r\n");printf("------------接收的命令为----------\r\n");for(i=0;i<modbus.recount;i++){printf("%2X ",modbus.recbuf[i]);}printf("\r\n");printf("------------------------------------\r\n");printf("*************************************\r\n");
}void modbus_event()
{u16 crc,rccrc;if(modbus.reflag==0) return ; //没有数据crc=crc16(modbus.recbuf,modbus.recount);rccrc=modbus.recbuf[modbus.recount-2]*256+modbus.recbuf[modbus.recount-1];if(crc==rccrc){modbus_display(); /*使用HS-FS01风速传感器时直接显示不检查校验码*/}modbus.secount=0;modbus.recount=0;modbus.reflag=0;
}void USART2_IRQHandler()
{if(USART_GetITStatus(USART2,USART_IT_RXNE)!=RESET){temp=USART_ReceiveData(USART2);if(modbus.reflag==1) return ; //有数据正在处理modbus.recbuf[modbus.recount++]=temp;modbus.timflag=0; //清零计时位if(modbus.recount==1) //主机发送的一个数据的第一字节modbus.timrun=1; //启动定时}
}/******************************************************************/
tim.c 和 tim.h
tim.h
/**************************************************************/#ifndef __TIM_H__
#define __TIM_H__
#include "stm32f10x.h"void tim3_init(u16 arr,u16 psc);#endif
/***************************************************************/tim.c
/****************************************************************/
#include "tim.h"
#include "modbus.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_tim.h"
#include "misc.h"extern Modbus modbus;void tim3_init(u16 arr,u16 psc) //arr 999 psc 71
{NVIC_InitTypeDef NVIC_InitTypeStruct;TIM_TimeBaseInitTypeDef TIM_TimeBaseInitTypeStruct;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); // tim3 enableTIM_TimeBaseInitTypeStruct.TIM_ClockDivision=TIM_CKD_DIV1;TIM_TimeBaseInitTypeStruct.TIM_CounterMode=TIM_CounterMode_Up;TIM_TimeBaseInitTypeStruct.TIM_Period=arr;TIM_TimeBaseInitTypeStruct.TIM_Prescaler=psc;TIM_TimeBaseInitTypeStruct.TIM_RepetitionCounter=0;TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitTypeStruct); // tim3 base setTIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //tim3 it setNVIC_InitTypeStruct.NVIC_IRQChannel=TIM3_IRQn;NVIC_InitTypeStruct.NVIC_IRQChannelCmd=ENABLE;NVIC_InitTypeStruct.NVIC_IRQChannelPreemptionPriority=1;NVIC_InitTypeStruct.NVIC_IRQChannelSubPriority=1;NVIC_Init(&NVIC_InitTypeStruct); //tim3 nvic setTIM_Cmd(TIM3,ENABLE); //tim3 cmd set}void TIM3_IRQHandler() //tim it
{if(TIM_GetITStatus(TIM3,TIM_IT_Update)!=RESET){if(modbus.timrun==1) //定时器开始计时{modbus.timflag++; if(modbus.timflag>=8) //8ms 计时 默认8ms接收完成{modbus.timrun=0; //关闭计时modbus.reflag=1; //接收到一帧数据标志 } } }TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
}/******************************************************************/
main.c
main.c#include "stm32f10x.h"
#include "tim.h"
#include "modbus.h"
#include "misc.h" /*使用HS-FS01风速传感器时直接显示,不检查校验码*/
#include "usart.h" /*请自行修改modbus_event(); 函数*/vu8 temp;int main()
{NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);uart_init(72);tim3_init(999,71);modbus_init();while(1){modbus_fun3(0x02,0x2A,0x01); //发送指令modbus_event(); //处理数据}
}
附一:
/* CRC 高位字节值表 */
const uchar auchCRCHi[] = {0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,0x80, 0x41, 0x00, 0xC1, 0x81, 0x40
} ;
/* CRC低位字节值表*/
const uchar auchCRCLo[] = {0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4,0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED,0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,0x43, 0x83, 0x41, 0x81, 0x80, 0x40
} ;/******************************************************************
功能: CRC16校验
输入:
输出:
******************************************************************/
uint crc16( uchar *puchMsg, uint usDataLen )
{uchar uchCRCHi = 0xFF ; // 高CRC字节初始化uchar uchCRCLo = 0xFF ; // 低CRC 字节初始化unsigned long uIndex ; // CRC循环中的索引while ( usDataLen-- ) // 传输消息缓冲区{uIndex = uchCRCHi ^ *puchMsg++ ; // 计算CRCuchCRCHi = uchCRCLo ^ auchCRCHi[uIndex] ;uchCRCLo = auchCRCLo[uIndex] ;}return ( uchCRCHi << 8 | uchCRCLo ) ;
}