嵌入式|基于STM32Cube的ModbusRTU编写[保持寄存器读写]
【嵌入式|基于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串口相关代码
我们首先要根据modbus的功能码来推测接收包的位数
对于保持寄存器的读写:
A.读保持寄存器
读保持寄存器的请求帧图下所示:
SlaveID | FunctionCode | StartADDR | Length | CRC |
---|---|---|---|---|
从站ID | 功能码 | 起始地址 | 寄存器数量 | CRC校验 |
1位 | 1位 | 2位 | 2位 | 2位 |
B.写保持寄存器
SlaveID | FunctionCode | StartADDR | Length | Code Length | Code | CRC |
---|---|---|---|---|---|---|
从站ID | 功能码 | 起始地址 | 寄存器数量 | 字节数 | 寄存器值 | CRC校验 |
1位 | 1位 | 2位 | 2位 | 1位 | Code Length位 | 2位 |
所以得到串口中断函数如下:
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);
}
状态转移函数如下:(想让懒狗博主一条条分析是不可能的!)
voidmodbus_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;
/********D′è??à????′??÷********/
case 12:
addr[cnt]=a;
cnt++;
if(cnt>4)
{
cnt=0;
startaddr= addr[1];
bufflen= addr[4];
e.stat = 13;
//×a′?íê±?
}
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
#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);
voidmodbus_onebit(char a);
#endif // 2_H_INCLUDED
5.main.c函数下包含
/* USER CODE BEGIN Includes */
#include "stdio.h"
#include //自己写的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!!!!!!接下来会推出一系列的关于串口使用的分享,有需要的猿们敬请关注!!!!!
以上内容欢迎大家转载引用,标明出处即可!!!!!
推荐阅读
- 基于微信小程序带后端ssm接口小区物业管理平台设计
- 基于|基于 antd 风格的 element-table + pagination 的二次封装
- 基于爱,才会有“愿望”当“要求”。2017.8.12
- javaweb|基于Servlet+jsp+mysql开发javaWeb学生成绩管理系统
- JavaScript|vue 基于axios封装request接口请求——request.js文件
- 韵达基于云原生的业务中台建设 | 实战派
- EasyOA|EasyOA 基于SSM的实现 未完成总结与自我批判
- 嵌入式(编译内核、根文件系统等)
- 基于stm32智能风扇|基于stm32智能风扇_一款基于STM32的智能灭火机器人设计
- stm32|基于STM32和freeRTOS智能门锁设计方案