[STC32F12K54入门第三步]USART1+Modbus RTU从机

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


提示:以下是本篇文章正文内容,下面案例可供参考

一、Modbus RTU是什么?

想要了解的去看看我STM32高级篇的Modbus RTU的文章或者自己去网上看看。其实很简单。就是一些报文格式,然后解析格式。本文主要是驱动程序。注意博主是默认你会Modbus RTU协议的哈,注释里面会讲一些Modbus RTU的报文格式。

二、Modbus RTU程序展示

这是程序的流程图
在这里插入图片描述

1.串口配置

这里我们使用USART1串口来配置
USART.h

#ifndef __USART_H
#define __USART_H
#include <STC32G.H>
#include <String.h>
#include "System.h"
//#define MAIN_Fosc     56000000UL  //定义主时钟
#define Baudrate      115200L
#define TM            (65536 -(MAIN_Fosc/Baudrate+2)/4)

extern u16 Modbus_timeOut;
extern unsigned char buff[1024];
extern char rx_len;

void Uart1_Init(void);
void Usart1_Send(unsigned char dat);
void Usart1_Send_Str(unsigned char* dat,unsigned short dat_len);

#endif

USART.c

#include "USART.h"
#include "stdio.h"
unsigned char buff[1024];
char rx_len;
u16 Modbus_timeOut;
int i=0;

/*函数名:Uart1_Init(void)
*功能:串口1初始化 波特率:115200
*形参:无
*返回值:无
*修改时间:2023/7/1
*作者:小夏
*/
void Uart1_Init(void){
	S1_S1=1;
	S1_S0=0;   //使用P1.6,P1.7
	SCON=0x40;
	REN=1;
	S1BRT =1;
	T2L  = TM;
	T2H  = TM>>8;
	AUXR |= 0x14;	//定时器2时钟1T模式,开始计时
	rx_len=0;
	//开启中断
	ES=1;   
	EA=1;
	
}
/*函数名:Usart1_Send(unsigned char dat)
*功能:串口一发送一个数据
*形参:dat char数据
*返回值:无
*修改时间:2023/7/1
*作者:小夏
*/
void Usart1_Send(unsigned char dat)
{
	SBUF=dat;
	while(TI==0);
	TI=0;
}
/*函数名:Usart1_Send_Str(unsigned char* dat,unsigned short dat_len)
*功能:串口一发送字符串数据
*形参:dat char数据    dat_len发送的数据长度
*返回值:无
*修改时间:2023/7/1
*作者:小夏
*/
void Usart1_Send_Str(unsigned char* dat,unsigned short dat_len)
{
	while(dat_len--){
		Usart1_Send(*dat++);
	}
}
/*函数名:USART_BackCall_IRQ(void) interrupt 4
*功能:串口一的中断处理函数
*形参:无
*返回值:无
*修改时间:2023/7/1
*作者:小夏
*/
void USART_BackCall_IRQ(void) interrupt 4
{
	
		if(RI){
			RI=0;
			if(rx_len<sizeof(buff)){
				buff[rx_len++]=SBUF;
				P21=!P21;
			}	
      Modbus_timeOut=20;			
		}
	
} 
/*函数名:putchar(char c)
*功能:串口一的串口重定向
*形参:无
*返回值:无
*修改时间:2023/7/1
*作者:小夏
*/
char putchar(char c)
{
	Usart1_Send(c);
	return c;
}

2.Timer定时器配置

Timer.h

#ifndef __Timer_H
#define __Timer_H
#include <STC32G.H>
#include <String.h>
#include "System.h"

void Timer_Init(void);
#endif

Timer.c

#include "Timer.h"
#include "USART.h"

u16 time;

/*函数名:TM0_Isr() interrupt 1
*功能:Timer0中断处理函数
*形参:无
*返回值:无
*修改时间:2023/7/1
*作者:小夏
*/
void TM0_Isr() interrupt 1
{
	time++;
	if(time>=1){
		if(Modbus_timeOut>1)Modbus_timeOut--;
		if(Modbus_timeOut>1)Modbus_timeOut--;
		if(Modbus_timeOut>1)Modbus_timeOut--;
		time=0;
	}
	
}

/*函数名:Timer_Init(u16 Per)
*功能:Timer0初始化 1ms让LED灯电平变换
*形参:无
*返回值:无
*修改时间:2023/6/56
*作者:小夏
*/
void Timer_Init(void){
		TMOD=0x00;
		TL0=0xCF; //1ms  由于我们使用的晶振是56mhz,所以1ms跳动5600次,这里是65535-5600. 
		TH0=0xFD;
		TR0=1;
		ET0=1;
		EA=1;	
}

3.配置CRC16校验位和Modbus RTU发送函数

CRC16.h

#ifndef _CRC16_H
#define _CRC16_H

#include <STC32G.H>
#include <String.h>
#include "System.h"

unsigned int GetCRC16(unsigned char *pPtr,unsigned char ucLen);	/* 获得CRC16校验值 */
unsigned char make_rs485_replay_str(unsigned short addr,unsigned short len,unsigned char* source,unsigned char* dest,unsigned char devicdID,unsigned char func);
#endif 

CRC16.c

/*************************************************************************************
文件名称:crc16.c
版    本:V1.0
日    期:2020-5-11
编    著:Eric Xie
说    明:CRC校验表
修改日志:



**************************************************************************************/
#include "crc16.h"

const unsigned char TabH[] = {  //CRC高位字节值表
        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  
    } ;  
const unsigned char TabL[] = {  //CRC低位字节值表
        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校验
 * 入口参数:u8 *ptr,u8 len
 * 出口参数:u16
 * 函数功能:根据入口参数数组的值计算crc16校验值 并返回
**************************************************************************************/
unsigned int GetCRC16(unsigned char *pPtr,unsigned char ucLen)
{ 
    unsigned int  uiIndex;
    unsigned char ucCrch = 0xFF;  		//高CRC字节
    unsigned char ucCrcl = 0xFF;  		//低CRC字节 
    while (ucLen --)  			//计算指定长度CRC
    {
        uiIndex = ucCrch ^ *pPtr++;
        ucCrch  = ucCrcl ^ TabH[uiIndex];
        ucCrcl  = TabL[uiIndex];
    }
    
    return ((ucCrch << 8) | ucCrcl);  
} 

/*函数名:unsigned char make_rs485_replay_str(unsigned short addr,unsigned short len,unsigned char* source,unsigned char* dest,unsigned char devicdID,unsigned char func)
*功能:串口1初始化 波特率:115200
*形参:unsigned short addr,  //读的起始地址
*unsigned short len,   //主机读的数据数量
*unsigned char* source, //读取到的主机的报文
*unsigned char* dest,   //上传到主机的buff包
*unsigned char devicdID,//从机地址
*unsigned char func     //功能码
*返回值:unsigned char 返回数据数量
*修改时间:2023/7/1
*作者:小夏
*/
unsigned char make_rs485_replay_str(unsigned short addr,unsigned short len,unsigned char* source,unsigned char* dest,unsigned char devicdID,unsigned char func)
{
	unsigned short crc;
	unsigned char i=0;
	unsigned char p=0;
	dest[0]=devicdID;
	dest[1]=func;
	//printf("dest[2]=0x%x\r\n",dest[1]);
	if(func==0x03)
	{
		dest[2]=len*2;
		p=3;
		for(i=0;i<len;i++)
		{
			if(addr+i>=0 && addr+i<64)
			{
				//printf("size=%d\r\n",sizeof(info.data)/2);
				dest[p]=0xFFAD>>8;
				dest[p+1]=0xFFAD;  
				//dest[p]=TEST>>8;	
				//dest[p+1]=TEST;				
			}
			else			
			{
				dest[p]=0;
				dest[p+1]=0;
			}
			p+=2;
		}
	}
	crc=GetCRC16(dest,p);
	dest[p]=crc>>8;
	dest[p+1]=crc;
	return p+2;
}	

4.主函数

main.c

#include <STC32G.H>
#include "Timer.h"
#include "System.h"
#include "USART.h"
#include "Timer.h"
#include "crc16.h"
#include "stdio.h"

u8 Modbus_begin[]="Modbus Begin\r\n";
unsigned char send[128];
unsigned short startAddr;
unsigned short len2;
unsigned short len;
unsigned short crc;
unsigned short crc2;
int main(void){
	GPIO_Init();
	Timer_Init();
	Uart1_Init();
	delay_ms(20);
	
	while(1){
		if(Modbus_timeOut==1)
			{
				Modbus_timeOut=0;
				
				if(rx_len>=5)
				{
					//printf("rx_len=%d\r\n",rx_len);
					crc =GetCRC16((unsigned char*)buff,rx_len-2);//计算接收数据的CRC16校验
					crc2=((buff[rx_len-2]<<8)+buff[rx_len-1]);//读取串口接收的数据的CRC校验位
					if(crc==crc2)
					{
						if(buff[0]==0x01) //判断modebus从机地址
						{
/*-------------------------------------------------------------------------------------
								Modbus RTU协议
协议报文     举例
(主机接收从
机数据)	    (PULL)Tx:01          03        00 00           00 03           05 CB
                   从机地址位  功能码     寄存器地址    提取的数据个数     CRC16校验位
			(Slave)Rx:01         03           06             00 01        00 02         00 03         FD 74
				  从机地址位    功能码   上报数据字节数     第一个数据    第二个数据    第三个数据    CRC16校验位
// 功能码0x03 保持读寄存器,就是主机会定时不断请求从机的命令					
-------------------------------------------------------------------------------------*/								
							if(buff[1]==0x03)//判读modebus功能地址 读功能码
							{
															 
								startAddr=(buff[2]<<8)+buff[3];//提取起始地址
								len2=(buff[4]<<8)+buff[5]; //提取结束地址
								len=make_rs485_replay_str(startAddr,len2,(unsigned char*)buff,send,buff[0],buff[1]);
								//dats[0]=usart_dat.szRx[2];
								if(len>0)
								{
								  Usart1_Send_Str(send,len);								
								}
							}
/*-------------------------------------------------------------------------------------
								Modbus RTU协议
协议报文     举例
(从机发送数 (PULL)Tx:01           06      00 00          00 02           08 0B
据到主机)        从机地址位    功能码   起始地址       结束地址        CRC16校验位
  			(Slave)Tx:01          06      00 00          00 02           08 0B
                  从机地址位     功能码   起始地址       结束地址       CRC16校验位
// 功能码0x06 写一个寄存器,就是主机写一个命令到从机						
-------------------------------------------------------------------------------------*/	
							
						else if(buff[1]==0x06)//写功能码 
						{
							unsigned short addr=(buff[2]<<8)+buff[3];
							unsigned short datas=(buff[4]<<8)+buff[5];
							//HAL_UART_Transmit_DMA(&huart1,(unsigned char*)buff,rx_len);
							 if(addr==0x00){
									if(datas==0x01){
											P23=!P23;
									}									
								}
					 }
					}
				}	
						rx_len=0;					
		 }	
		}
	}		
}	

	
		

5.效果展示

0x03:保持读线圈
在这里插入图片描述
0x06:写一个线圈 写入 0x01改变P21的led电平
在这里插入图片描述


总结

Modbus RTU就是这个样子。嘿嘿,下一篇会讲STC32F驱动ESP32获取时间等数据。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

单片有机机

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值