Modbus通信协议应用——风速传感器HS-FS01(串口显示)

风速传感器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通信协议得到广泛应用的原因是:

  •  公开发表并且无版权要求
  • 易于部署和维护
  • 对供应商来说,修改移动本地的比特或字节没有很多限制

在介绍通信协议之前,首先要明白通信协议三要素:

  1. 通信接口标准(即硬件协议,如电平高低)
  2. 通信格式      (异步通信中,双方必须统一设置的参数,数据的传送方式、传送数据的长度、校验的方法,和传输速率)   
  3. 通信数据格式 (规定传输的内容,数据帧的结构)

本片内容只介绍Modbus三种通信方式的第二条,异步串行传输(Rs485)所对应的两种通信模式MODBUS  ASCII 模式和MODBUS RTU 模式。两种模式在通讯上有所不同。

  • MODBUS ASCII

主要优点是允许字符之间的时间间隔长达1s,也不会出现错误。同时也是其缺点,传输速度较慢。

ASCII模式通信格式约定

  1. 起始位 :1位
  2. 数据长度 : 7位 ,低位先送
  3. 校验位 : 1位(有校验)  , 0位(无校验)
  4. 停止位 : 1位(有校验) ,2位(无校验)
  5. 波特率 : 可选

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. 起始位 : 1位
  2. 数据长度 : 8位 ,低位先送
  3. 校验位 : 1位(有校验) ,0位(无校验)
  4. 停止位 : 1位(有检验) ,2位(无校验)
  5. 波特率 : 可选

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 TX
	
	GPIO_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 RX
	
	USART_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; //send
	for(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 enable
	TIM_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 set
		
	TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);   //tim3 it set
	
	NVIC_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  set
	
	TIM_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++ ; 	// 计算CRC
        uchCRCHi = uchCRCLo ^ auchCRCHi[uIndex] ;
        uchCRCLo = auchCRCLo[uIndex] ;
    }

    return ( uchCRCHi << 8 | uchCRCLo ) ;
}

猜你喜欢

转载自blog.csdn.net/Kk_01110001B/article/details/83098246
今日推荐