基于STM32Cube的ModbusRTU编写[保持寄存器读写]

之前我移植了freemodbus的modbusTCP协议,感觉很好用,最近需要写一个基于STM32的modbusRTU就让我比较僵硬,本人至今没有搞懂freemodbus中modbusRTU的串口和定时器是怎么配合的,而且发现很多是要求使用的是RS232或者RS485接口,本人的目的是直接通过电脑串口完成,所以就自己写了一个Modbus的处理协议。
关键:只用了一个串口就搞定!!!

主要想法:

1.接收一个完整的Modbus数据帧
2.写一个处理函数,将数据帧进行状态机分析,生成一个返回数据帧
3.对数据帧进行CRC校验,校验码放在返回数据帧结尾
4.通过串口发送返回数据帧

生成STM32cube程序

1、根据自己的stm32的芯片型号来选择,我这里是STM32F767IGTx

在这里插入图片描述

2、选好芯片之后照旧设置RCC为外部时钟

在这里插入图片描述

3、使能串口1(usart1)以及中断,如图:

模式设为异步(Asynchronous)其他默认,波特率可以自己改,默认为115200Bits/s。
在这里插入图片描述
勾选中断使能
在这里插入图片描述

4、看原理图,找到串口对应引脚,如图:

我这里是

PA10——>USART1_RX  
PA9——>USART1_TX

在这里插入图片描述

5、根据对应引脚设置串口引脚,如图:

找到PA9、PA10引脚左键点击分别选择USART1_TX和USART1_RX
(不用担心选错选反,针脚的功能是ST公司已经定义好了的)
在这里插入图片描述

6、设置时钟树,如图:

这里会搞的按自己习惯搞,不会搞的默认就好,但是不能有里面是红色的框(红色框就是错了意思)
在这里插入图片描述

7、项目设置,如图:

红框里的按照自己的Keil版本来
在这里插入图片描述
个人喜欢把.c/.h文件分开
在这里插入图片描述

8、点击右上角的‘GENERATE CODE’直接生成代码,如图:

在这里插入图片描述

9、实现串口中断能够完整接收一个Modbus数据包:

打开项目后先打开Application/User的中断控制函数stm32f7xx_it.c在stm32f7xx_it.c中找到串口1的中断位置添加modbus串口相关代码

扫描二维码关注公众号,回复: 9613613 查看本文章

我们首先要根据modbus的功能码来推测接收包的位数

对于保持寄存器的读写:
A.读保持寄存器
读保持寄存器的请求帧图下所示:

SlaveID FunctionCode StartADDR Length CRC
1位 1位 2位 2位 2位

总和为0~7位,共8位

B.写保持寄存器

SlaveID FunctionCode StartADDR Length Code Length Code CRC
1位 1位 2位 2位 1位 Code Length位 2位

总和为 0~8 ~8+Code Length 位,共9+Code Length位

所以得到串口中断函数如下:

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
	extern uint8_t buff1[100];	//接收的数据包
	extern int buff1stat;		//接收数据包当前位
	extern uint8_t RxByte;		//中断接收暂存位置
	extern int flag;			//接收结束标志符
  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);	//中断启动使能标识
  /* USER CODE BEGIN USART1_IRQn 1 */
	buff1[buff1stat]=RxByte;	//将暂存内容放入接收数据包
	buff1stat++;				//数据包位置后移1位
	HAL_UART_Receive_IT(&huart1,&RxByte,1);	//重新启动接收中断,等待下一位数据到来
	if(buff1[1]==0x03)			//判断功能码,功能码位0x30则为读寄存器,请求数据包应为8位
	{
		if(buff1stat==8)		//数据到8位接收结束符置1,清除接收数据包当前位
		{
			buff1stat=0;
			flag=1;
		}
	}
	if(buff1[1]==0x10)			//判断功能码,功能码位0x30则为读寄存器,请求数据包应为9+Code Length位
	{
		if(buff1[6]!=0x00)		//当Code Length位不为0 且数据到9+Code Length位时,接收结束符置1,清除接收数据包当前位
		{
			if(buff1stat==9+buff1[6])
			{
				buff1stat=0;
				flag=1;
			}
		}
		
	}
  /* USER CODE END USART1_IRQn 1 */
}

并在mian函数的 /* USER CODE BEGIN 2 */处使能接收中断:

  /* USER CODE BEGIN 2 */
	HAL_UART_Receive_IT(&huart1,&RxByte,1);
  /* USER CODE END 2 */

以上,串口接收函数编写结束!!!可以实现根据Modbus数据内容来确定数据包长度并储存下来的功能了

10、分析接收数据包,生成发送数据包:

先建立一个保持寄存器功能的.c.h文件包,按照状态机来分析处理接收到的modbus数据包
以下为我画的状态转移流程图:
在这里插入图片描述
状态处理函数如下:(想让懒狗博主一条条分析是不可能的!)

void ReadHoldRegister(uint8_t buffIN[100],uint8_t buffOUT[100])
{
	int i,j;
	e.stat = 0;
	for (i = 0; i < 10; i++)
	{
		modbus_onebit(buffIN[i]);
		
		if (e.stat == 100)
		{

		}
		if (e.stat == 0)
		{

		}
		if (e.stat == 1)
		{
			buffOUT[i]= buffIN[i];
		}
		if (e.stat == -1)
		{
			
		}
		if (e.stat == 2)
		{
			buffOUT[i] = buffIN[i];
		}
		if (e.stat == 3)
		{			
		}
		if (e.stat == 4)
		{
			buffOUT[2] = bufflen*2;
		}
		if (e.stat == 5)
		{
			for (j = 0,n=0; j < bufflen * 2; j= j + 2,n++)
			{
				buffOUT[3 + j] = (DB_R[n+startaddr] >> 8) & 0x00FF;
				buffOUT[3 + j+1] = DB_R[n+startaddr]& 0x00FF;	
			}
		}
		
		if (e.stat == 12)
		{
			buffOUT[i] = buffIN[i];
		}
		if (e.stat == 13)
		{
			for (j = 0,n=0; j < bufflen; j= j + 2,n++)
			{		
				DB_R[n+startaddr]=(buffIN[7 + j] << 8)+buffIN[7 + j + 1];
			}
		}
	}
	if(buffIN[1]==0x03)
	CrcCheck(buffOUT,3+bufflen*2);
	else if(buffIN[1]==0x10)
	CrcCheck(buffOUT,6);
	
}

状态转移函数如下:(想让懒狗博主一条条分析是不可能的!)

void  modbus_onebit(char a)
{
	switch (e.stat)
	{
	case 0:
		if (a == 0x01)
			e.stat = 1;
		break;
	case 1:
		if(a==0x03)
			e.stat = 2;
		if(a==0x10)
			e.stat=12;
		break;
	case 2:
		addr[cnt] = a;
		cnt++;
		e.stat = 3;	
		break;
	case 3:
		addr[cnt] = a;
		cnt++;
		if (cnt > 3)
		{
			cnt = 0;
			startaddr= addr[1];
			bufflen= addr[3];
			e.stat = 4;
		}
		break;
	case 4:
		e.stat = 5;
		break;
	case 5:
		if(n==bufflen)
		e.stat = 100;
		break;
	case 6:
		e.stat = 6;
		break;
	case 7:
		e.stat = -1;
		break;
	case 8:
		e.stat = 100;
		break;
/********дÈë¶à¸ö¼Ä´æÆ÷********/	
	case 12:
		addr[cnt]=a;
		cnt++;
	if(cnt>4)
	{
		cnt=0;
		startaddr= addr[1];
		bufflen= addr[4];
		e.stat = 13;	//ת´æÍê±Ï
	}	
	break;
	case 13:
		if(n==bufflen)
		e.stat = 100;
		break;


	}
}

CRC校验函数如下:(想让懒狗博主一条条分析是不可能的!)

void CrcCheck(unsigned char *buf,int len)
{
	unsigned short crc = 0xFFFF;
	unsigned char i,j=0;
	while(j < len)
	{
		crc ^= buf[j];
		for(i = 0; i < 8; i++)
		{
			if(crc & 0x01)
				{
				crc >>= 1;
				crc ^= 0xA001;
				}
			else
			crc >>= 1;
		}
		j++;
	}
	buf[j] = crc % 0x100;
	buf[j+1]=crc / 0x100;
}

按着上面的函数就可以处理一个数据包
之后就是把接收到的数据包放到函数处理的过程了

11、生成数据包,完成操作,发送数据包,游戏结束!:

在main函数主循环添加如下代码:

  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
	
    /* USER CODE BEGIN 3 */
		
  if(flag==1)
		{
			ReadHoldRegister(buff1,buff2);	//上面写的函数,不懂的滑上去看
			if(buff1[1]==0x03)				//判断功能码,0x03为读寄存器,发送5+2*length位,不懂为什么的的滑回前面去看
				{
				for(count=0;count<5+buff1[5]*2;count++)
				HAL_UART_Transmit(&huart1, &buff2[count],1,0xFFFF);
				}
			else if(buff1[1]==0x10)			//判断功能码,0x10为写寄存器,发送8位,不懂为什么的的滑回前面去看
				{
				for(count=0;count<=7;count++)
				HAL_UART_Transmit(&huart1, &buff2[count],1,0xFFFF);
				}
//for(count = 0; count < 100; count++)buff1[count]=0x00;		//数据接收数组清空,看你们心情加不加,我这里加和不加不影响使用,我留着是图个debug
			flag=0;							//数据接收结束位清0,等待下次接收
		}
	}

防止有些大哥脑卡,不会自己定义变量,搬过去说我的函数用不了,我粘一下我的变量定义以及位置

1.main.c函数下int main(void)后面

	int main(void)
{
  /* USER CODE BEGIN 1 */

	DB_R[0] = 0x0102;
	DB_R[1] = 0x0204;
	DB_R[2] = 0x0306;
	DB_R[3] = 0x0408;
	int count;
  /* USER CODE END 1 */

2.main.c函数下/* USER CODE BEGIN PD */内

/* USER CODE BEGIN PD */
	
	uint8_t buff1[100];
	int buff1stat=0;
	uint8_t buff2[100];
	int flag=0;
	uint8_t RxByte;
	extern uint16_t DB_R[100];
/* USER CODE END PD */

3.自己写的modbushr.c文件下,定义void ReadHoldRegister(uint8_t buffIN[100],uint8_t buffOUT[100])函数前

#include <modbushr.h>
#include "main.h"

	uint16_t DB_R[100];
	MB e;
	int cnt,n,bufflen,startaddr;
	uint8_t addr[100];

4.自己写的modbushr.h文件下

#ifndef __MODBUSHR_H
#define __MODBUSHR_H
 
typedef struct
{
  int stat;				//Modbusµ±Ç°Î»×´Ì¬
}MB; 
 
void CrcCheck(unsigned char *buf,int len);
void ReadHoldRegister(unsigned char *buff1,unsigned char *buff2);
void  modbus_onebit(char a);
 
#endif // 2_H_INCLUDED

5.main.c函数下包含

/* USER CODE BEGIN Includes */
#include "stdio.h"
#include <modbushr.h>   //自己写的modbus,内容就是上面的那三个处理函数
/* USER CODE END Includes */

12、实验结果:

本次设计是modbus的从站设计,此时需要一个modbus的主站与其通讯测试
这里在github上下了一个测试工具ModbusTOOL

1.嵌入式串口连上电脑USB,并启动keil的debug,运行程序,监视DB_R寄存器(我自己初始化了寄存器内容如图)
在这里插入图片描述
2.打开Modbus Tool的Modbus Master
3.找到当前使用的串口是多少,设定波特率与嵌入式一致后点击connect
在这里插入图片描述

4.点击读、写保持寄存器测试功能
这里我们先读初始化保持寄存器的内容
在这里插入图片描述
由图可知,我们已经读取到初始化的保持寄存器内容

此时可以写入自己想要写入的数字,点击写入保持寄存器(写数据的位置只能在上图中Start往后数Size个位置之内)我讲0-3寄存器写了0x1111,0x2222,0x3333,0x4444四个数字
在这里插入图片描述
下方绿字说写入成功,功能码为16
此时我们看keil的debug的BD_R有没有改变
在这里插入图片描述
由图可知,寄存器BD_R的0-3位置已经变成了0x1111,0x2222,0x3333,0x4444证明试验结果正确,至此,实验结束!!!

		  Good Game!!!!!!

接下来会推出一系列的关于串口使用的分享,有需要的猿们敬请关注!!!!!

以上内容欢迎大家转载引用,标明出处即可!!!!!

发布了20 篇原创文章 · 获赞 53 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/ASWaterbenben/article/details/102585330
今日推荐