提示:文章寫完后,目錄可以自動生成,如何生成可參考右邊的幫助文檔
提示:以下是本篇文章正文內(nèi)容,下面案例可供參考
一、Modbus RTU是什么?
想要了解的去看看我STM32高級篇的Modbus RTU的文章或者自己去網(wǎng)上看看。其實(shí)很簡單。就是一些報(bào)文格式,然后解析格式。本文主要是驅(qū)動程序。注意博主是默認(rèn)你會Modbus RTU協(xié)議的哈,注釋里面會講一些Modbus RTU的報(bào)文格式。
二、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 //定義主時(shí)鐘
#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;
/*函數(shù)名:Uart1_Init(void)
*功能:串口1初始化 波特率:115200
*形參:無
*返回值:無
*修改時(shí)間: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; //定時(shí)器2時(shí)鐘1T模式,開始計(jì)時(shí)
rx_len=0;
//開啟中斷
ES=1;
EA=1;
}
/*函數(shù)名:Usart1_Send(unsigned char dat)
*功能:串口一發(fā)送一個數(shù)據(jù)
*形參:dat char數(shù)據(jù)
*返回值:無
*修改時(shí)間:2023/7/1
*作者:小夏
*/
void Usart1_Send(unsigned char dat)
{
SBUF=dat;
while(TI==0);
TI=0;
}
/*函數(shù)名:Usart1_Send_Str(unsigned char* dat,unsigned short dat_len)
*功能:串口一發(fā)送字符串?dāng)?shù)據(jù)
*形參:dat char數(shù)據(jù) dat_len發(fā)送的數(shù)據(jù)長度
*返回值:無
*修改時(shí)間:2023/7/1
*作者:小夏
*/
void Usart1_Send_Str(unsigned char* dat,unsigned short dat_len)
{
while(dat_len--){
Usart1_Send(*dat++);
}
}
/*函數(shù)名:USART_BackCall_IRQ(void) interrupt 4
*功能:串口一的中斷處理函數(shù)
*形參:無
*返回值:無
*修改時(shí)間: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;
}
}
/*函數(shù)名:putchar(char c)
*功能:串口一的串口重定向
*形參:無
*返回值:無
*修改時(shí)間:2023/7/1
*作者:小夏
*/
char putchar(char c)
{
Usart1_Send(c);
return c;
}
2.Timer定時(shí)器配置
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;
/*函數(shù)名:TM0_Isr() interrupt 1
*功能:Timer0中斷處理函數(shù)
*形參:無
*返回值:無
*修改時(shí)間: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;
}
}
/*函數(shù)名:Timer_Init(u16 Per)
*功能:Timer0初始化 1ms讓LED燈電平變換
*形參:無
*返回值:無
*修改時(shí)間: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校驗(yàn)位和Modbus RTU發(fā)送函數(shù)
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校驗(yàn)值 */
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校驗(yàn)表
修改日志:
**************************************************************************************/
#include "crc16.h"
const unsigned char TabH[] = { //CRC高位字節(jié)值表
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低位字節(jié)值表
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
} ;
/*************************************************************************************
* 函數(shù)說明: CRC16校驗(yàn)
* 入口參數(shù):u8 *ptr,u8 len
* 出口參數(shù):u16
* 函數(shù)功能:根據(jù)入口參數(shù)數(shù)組的值計(jì)算crc16校驗(yàn)值 并返回
**************************************************************************************/
unsigned int GetCRC16(unsigned char *pPtr,unsigned char ucLen)
{
unsigned int uiIndex;
unsigned char ucCrch = 0xFF; //高CRC字節(jié)
unsigned char ucCrcl = 0xFF; //低CRC字節(jié)
while (ucLen --) //計(jì)算指定長度CRC
{
uiIndex = ucCrch ^ *pPtr++;
ucCrch = ucCrcl ^ TabH[uiIndex];
ucCrcl = TabL[uiIndex];
}
return ((ucCrch << 8) | ucCrcl);
}
/*函數(shù)名: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, //主機(jī)讀的數(shù)據(jù)數(shù)量
*unsigned char* source, //讀取到的主機(jī)的報(bào)文
*unsigned char* dest, //上傳到主機(jī)的buff包
*unsigned char devicdID,//從機(jī)地址
*unsigned char func //功能碼
*返回值:unsigned char 返回?cái)?shù)據(jù)數(shù)量
*修改時(shí)間: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.主函數(shù)
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);//計(jì)算接收數(shù)據(jù)的CRC16校驗(yàn)
crc2=((buff[rx_len-2]<<8)+buff[rx_len-1]);//讀取串口接收的數(shù)據(jù)的CRC校驗(yàn)位
if(crc==crc2)
{
if(buff[0]==0x01) //判斷modebus從機(jī)地址
{
/*-------------------------------------------------------------------------------------
Modbus RTU協(xié)議
協(xié)議報(bào)文 舉例
(主機(jī)接收從
機(jī)數(shù)據(jù)) (PULL)Tx:01 03 00 00 00 03 05 CB
從機(jī)地址位 功能碼 寄存器地址 提取的數(shù)據(jù)個數(shù) CRC16校驗(yàn)位
(Slave)Rx:01 03 06 00 01 00 02 00 03 FD 74
從機(jī)地址位 功能碼 上報(bào)數(shù)據(jù)字節(jié)數(shù) 第一個數(shù)據(jù) 第二個數(shù)據(jù) 第三個數(shù)據(jù) CRC16校驗(yàn)位
// 功能碼0x03 保持讀寄存器,就是主機(jī)會定時(shí)不斷請求從機(jī)的命令
-------------------------------------------------------------------------------------*/
if(buff[1]==0x03)//判讀modebus功能地址 讀功能碼
{
startAddr=(buff[2]<<8)+buff[3];//提取起始地址
len2=(buff[4]<<8)+buff[5]; //提取結(jié)束地址
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協(xié)議
協(xié)議報(bào)文 舉例
(從機(jī)發(fā)送數(shù) (PULL)Tx:01 06 00 00 00 02 08 0B
據(jù)到主機(jī)) 從機(jī)地址位 功能碼 起始地址 結(jié)束地址 CRC16校驗(yàn)位
(Slave)Tx:01 06 00 00 00 02 08 0B
從機(jī)地址位 功能碼 起始地址 結(jié)束地址 CRC16校驗(yàn)位
// 功能碼0x06 寫一個寄存器,就是主機(jī)寫一個命令到從機(jī)
-------------------------------------------------------------------------------------*/
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電平文章來源:http://www.zghlxwxcb.cn/news/detail-526518.html
總結(jié)
Modbus RTU就是這個樣子。嘿嘿,下一篇會講STC32F驅(qū)動ESP32獲取時(shí)間等數(shù)據(jù)。文章來源地址http://www.zghlxwxcb.cn/news/detail-526518.html
到了這里,關(guān)于[STC32F12K54入門第三步]USART1+Modbus RTU從機(jī)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!