提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
提示:以下是本篇文章正文内容,下面案例可供参考
一、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电平文章来源:https://www.toymoban.com/news/detail-526518.html
总结
Modbus RTU就是这个样子。嘿嘿,下一篇会讲STC32F驱动ESP32获取时间等数据。文章来源地址https://www.toymoban.com/news/detail-526518.html
到了这里,关于[STC32F12K54入门第三步]USART1+Modbus RTU从机的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!