MODBUS移植STM32,STM32做从机
MODBUS学习日志
一、MODBUS通信协议
1、通信协议
- 硬件层协议:解决传输问题,相当于路
- 串口通信协议 : RS232、RS485、CAN总线
文章图片
单工通信只支持信号在一个方向上传输(正向或反向),任何时候不能改变信号的传输方向。为保证正确传送数据信号,接收端要对接收的数据进行校验,若校验出错,则通过监控信道发送请求重发的信号。此种方式适用于数据收集系统,如气象数据的收集、电话费的集中计算等。例如计算机和打印机之间的通信是单工模式,因为只有计算机向打印机传输数据,而没有相反方向的数据传输。还有在某些通信信道中,如单工无线发送等。1.1.2、半双工方式(需要上层软件做协议)(half-duplex)
文章图片
半双工通信允许信号在两个方向上传输,但某一时刻只允许信号在一个信道上单向传输。因此,半双工通信实际上是一种可切换方向的单工通信。此种方式适用于问讯、检索、科学计算等数据通信系统;传统的对讲机使用的就是半双工通信方式。由于对讲机传送及接收使用相同的频率,不允许同时进行。因此一方讲完后,需设法告知另一方讲话结束(例如讲完后加上’OVER’),另一方才知道可以开始讲话。1.1.3、全双工方式(full-duplex)
文章图片
全双工通信允许数据同时在两个方向上传输,即有两个信道,因此允许同时进行双向传输。全双工通信是两个单工通信方式的结合,要求收发双方都有独立的接收和发送能力。全双工通信效率高,控制简单,但造价高。计算机之间的通信是全双工方式。一般的电话、手机也是全双工的系统,因为在讲话时可以听到对方的声音。参考链接: https://blog.csdn.net/iningwei/article/details/100134783
1.2、主从模式:
主从模式,是数据库设计模式中最常见、也是大家日常设计工作中用的最多的一种模式,它描述了两个表之间的主从关系,是典型的“一对多”关系。
规定要求:
1. 系统中只有一个设备时主机
2. 系统中的所有从机不可以主动向主机发数据
3. 系统中的主机和所有从机上电后都处于监听状态
4. 任何一次的数据交换都要由主机发起
4.1、将自己转为发送状态
4.2、主机按照预先约定的格式,发出寻址数据帧
4.3、恢复自己的接受状态,等待所寻址的从机响应
1.3、软件层协议:解决传输的目的 1.3.1、主从模式
- 整个系统只能有一个主机,每个从机必须有一个唯一的地址(0~247)
- 其中0号地址位广播地址:主机向0号地址的设备发数据包,也就是要把该数据包发给所有的从设备。0号地址的数据包所有从机是不回应的。
文章图片
>MODBUS的两种传输方式:RTU方式和ASC方式
>
>RTU方式:也叫十六进制 例如:发送0x03:0000 0011
>
>RTU方式:也叫十六进制 例如:发送0x03:0000 0011
>
>ASC方式:0x03{发送0 :0x30:0011 0000 }{ 发送3:0x33:0011 0011}
>
>所以ASC的通信效率低,但是方便调试,使用实验;
工业上都采用RTU方式,效率高
2.3、RTU方式
文章图片
? 1、从机地址 2、功能码(127个) 3、数据1~数据n 4、校验码(CRCL、CRCH)
文章图片
其中: 1~3参与CRC16校验2.4、ASC方式
从机是以接收数据停止时间达到3.5个字节以上,那么就认为主机的寻址帧完成,并开始处理。
例如:波特率:9600bt/s
所以每位数据传输的时间T=1000000us/9600=104us
一字节时间位=10T=1004us(起始位 8位 停止位)(串口格式)
所以时间为:3.5*10T=3645us
文章图片
1、: 2、地址 3、功能码 4、数据1~数据n 5、(地址数据)采用LRC校验=((地址+功能码+数据1数据n)%256)+1=(0255) 6、13 10(回车 换行)
2.5、CRC简述
1.将一个 16 位寄存器装入十六进制 FFFF (全 1). 将之称作 CRC 寄存器.3、从设备的回应数据包格式
2.将报文的第一个 8 位字节与 16 位 CRC 寄存器的低字节异或,结果置于 CRC 寄存器.
3.将 CRC 寄存器右移 1 位 (向 LSB 方向), MSB 充零. 提取并检测 LSB.
4.(如果 LSB 为 0): 重复步骤 3 (另一次移位).
(如果 LSB 为 1): 对 CRC 寄存器异或多项式值 0xA001 (1010 0000 0000 0001).
5.重复步骤 3 和 4,直到完成 8 次移位。当做完此操作后,将完成对 8 位字节的完整操作。
6.对报文中的下一个字节重复步骤 2 到 5,继续此操作直至所有报文被处理完毕。
7.CRC 寄存器中的最终内容为 CRC 值.
8.当放置 CRC 值于报文时,高低字节必须交换。
- 回应数据包和主机查询的数据包格式包是一致的
- 正常回应时,功能码与主机发的功能码一致(1~127)
- 异常的回应,功能码要在收到的功能码基础上加上128 例如:发 0x03 收:0x03 +128
- 硬件上具备串口
- 硬件上需要定时器(精确到毫秒级)
二、MODBUS移植STM32流程 1、系统初始化设计流程
开始 配置系统时钟为72MHZ 配置基本定时器为1MS 配置串口为9600bts,并开启接受中断 使能定时器和串口中断,串口中断优先级>定时器中断优先级 1.1、配置系统时钟
SysClock_Configuration(RCC_PLLSource_HSE_Div1,RCC_CFGR_PLLMULL9);
//设置系统时钟,外部设置为72MHZ,内部设置为64MHZ
1.2、配置基本定时器的步骤
void BASIC_TIM_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
BASIC_TIM_APBxClock_FUN(BASIC_TIM_CLK, ENABLE);
//开启定时器时钟,即内部时钟CK_INT=72M
TIM_TimeBaseStructure.TIM_Period=TIM6_Period;
//自动重装载寄存器周的值(计数值)
// 累计TIM_Period 个频率后产生一个更新或者中断
// 时钟预分频数为71,则驱动计数器的时钟CK_CNT = CK_INT / (71+1)=1M
TIM_TimeBaseStructure.TIM_Prescaler= TIM6_Prescaler;
//TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
// 时钟分频因子 ,基本定时器没有,不用管
//TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
// 计数器计数模式,基本定时器只能向上计数,没有计数模式的设置
//TIM_TimeBaseStructure.TIM_RepetitionCounter=0;
// 重复计数器的值,基本定时器没有,不用管
TIM_TimeBaseInit(BASIC_TIM, &TIM_TimeBaseStructure);
// 初始化定时器
TIM_ClearFlag(BASIC_TIM, TIM_FLAG_Update);
// 清除计数器中断标志位
TIM_ITConfig(BASIC_TIM,TIM_IT_Update,ENABLE);
// 开启计数器中断
TIM_Cmd(BASIC_TIM, ENABLE);
// 使能计数器
//BASIC_TIM_APBxClock_FUN(BASIC_TIM_CLK, DISABLE);
// 暂时关闭定时器的时钟,等待使用
}
基本定时器头文件
#ifdefBASIC_TIM6// 使用基本定时器TIM6
#define BASIC_TIMTIM6
#define BASIC_TIM_APBxClock_FUNRCC_APB1PeriphClockCmd
#define BASIC_TIM_CLKRCC_APB1Periph_TIM6
#define BASIC_TIM_IRQTIM6_IRQn
#define BASIC_TIM_IRQHandlerTIM6_IRQHandler
#define TIM6_Period(1000)
#define TIM6_Prescaler(72-1)
定时器中断函数
void BASIC_TIM_IRQHandler (void)//定时器中断函数
{
if ( TIM_GetITStatus( BASIC_TIM, TIM_IT_Update) != RESET )
{
TIM_ClearITPendingBit(BASIC_TIM , TIM_FLAG_Update);
}
}
配置定时器中断使能
void ALL_NVIC_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
// 设置中断组为1
NVIC_InitStructure.NVIC_IRQChannel = BASIC_TIM_IRQ ;
// 设置中断来源
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
// 设置主优先级为 1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
// 设置抢占优先级为3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
C
}
主程序结构
int main(void)
{ SysClock_Configuration(RCC_PLLSource_HSE_Div1,RCC_CFGR_PLLMULL9);
//设置系统时钟,外部设置为72MHZ,内部设置为64MHZ
BASIC_TIM_Config();
//定时器配置为1MS
ALL_NVIC_Init();
//配置中断优先级
}
运行程序,判断是否到定时器中断中的断点完成定时器1MS定时
1.3、配置串口GPIO口
void USART_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
// 打开串口GPIO 的时钟
DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);
// 打开串口外设的时钟
// 将USART1 Tx 的GPIO 配置为推挽复用模式
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);
// 将USART Rx 的GPIO 配置为浮空输入模式
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
// 配置串口的工作参数
USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
// 配置波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
// 配置 针数据字长
USART_InitStructure.USART_StopBits = USART_StopBits_1;
// 配置停止位
USART_InitStructure.USART_Parity = USART_Parity_No ;
// 配置校验位
USART_InitStructure.USART_HardwareFlowControl =USART_HardwareFlowControl_None;
// 配置硬件流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
// 配置工作模式,收发一起
USART_Init(DEBUG_USART, &USART_InitStructure);
// 完成串口的初始化配置
USART_ITConfig(DEBUG_USART, USART_IT_RXNE, ENABLE);
// 使能串口接收中断
USART_Cmd(DEBUG_USART, ENABLE);
// 使能串口
}
串口头文件
// 串口2-USART1
#define DEBUG_USARTUSART2
#define DEBUG_USART_CLKRCC_APB1Periph_USART2
#define DEBUG_USART_APBxClkCmdRCC_APB1PeriphClockCmd
#define DEBUG_USART_BAUDRATE9600
// USART GPIO 引脚宏定义
#define DEBUG_USART_GPIO_CLKRCC_APB2Periph_GPIOA
#define DEBUG_USART_GPIO_APBxClkCmdRCC_APB2PeriphClockCmd
#define DEBUG_USART_TX_GPIO_PORTGPIOA
#define DEBUG_USART_TX_GPIO_PINGPIO_Pin_2
#define DEBUG_USART_RX_GPIO_PORTGPIOA
#define DEBUG_USART_RX_GPIO_PINGPIO_Pin_3
// USART GPIO 中断
#define DEBUG_USART_IRQUSART2_IRQn
#define DEBUG_USART_IRQHandlerUSART2_IRQHandler
串口中断函数
void DEBUG_USART_IRQHandler(void)
{
uint8_t ucTemp;
if (USART_GetITStatus(DEBUG_USART,USART_IT_RXNE)!=RESET)//判断是否有数据接收
{
ucTemp = USART_ReceiveData( DEBUG_USART );
//将接收的一个字节保存
USART_SendData(DEBUG_USART,ucTemp);
//保存后发送调试助手,
}
}
串口中断使能函数
void ALL_NVIC_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
// 设置中断组为1
NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ ;
// 设置中断来源
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
// 设置主优先级为 1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
// 设置抢占优先级为0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
主函数结构
int main(void)
{
SysClock_Configuration(RCC_PLLSource_HSE_Div1,RCC_CFGR_PLLMULL9);
//设置系统时钟,外部设置为72MHZ,内部设置为64MHZ
USART_Config();
ALL_NVIC_Init();
}
运行程序,判断是否到串口中断中的断点完成串口配置
1.4、配置定时器作用于串口
当串口接受完数据,开启定时器计数,当时间>8T就开始处理数据此时需要配置MODBUS的参数,如下
typedef struct
{
unsigned charmyadd;
//本设备的地址
unsigned charrcbuf[100];
//MODBUS接收缓冲区
unsigned inttimout;
//MODbus的数据断续时间
unsigned charrecount;
//MODbus端口已经收到的数据个数
unsigned chartimrun;
//MODbus定时器是否计时的标志
unsigned charreflag;
//收到一帧数据的标志
unsigned charSendbuf[100];
//MODbus发送缓冲区
}MODBUS;
extern MODBUS modbus;
//声明全局变量,然后在C文件中调用
串口中断配置如下
void DEBUG_USART_IRQHandler(void)
{
uint8_t ucTemp;
if (USART_GetITStatus(DEBUG_USART,USART_IT_RXNE)!=RESET)//判断是否有数据接收
{
ucTemp = USART_ReceiveData( DEBUG_USART );
//将接收的一个字节保存
modbus.rcbuf[modbus.recount++]=ucTemp;
//保存到MODBUS的接收缓存区
modbus.timout=0;
//串口接收数据的过程中,定时器不计时
if(modbus.recount==1)//收到主机发来的一帧数据的第一字节
{
modbus.timrun=1;
//启动定时
}
}
}
定时器中断配置如下
void BASIC_TIM_IRQHandler (void)//定时器中断函数
{
if ( TIM_GetITStatus( BASIC_TIM, TIM_IT_Update) != RESET )
{
if(modbus.timrun!=0)//串口发送数据是否结束,结束就让定时器定时
{
modbus.timout++;
//定时器定时1毫秒,并开始记时
if(modbus.timout>=8)//间隔时间达到了时间,假设为8T,实际3.5T即可
{
modbus.timrun=0;
//关闭定时器--停止定时
modbus.reflag=1;
//收到一帧数据,开始处理数据
}
}
TIM_ClearITPendingBit(BASIC_TIM , TIM_FLAG_Update);
}
}
运行程序,判断是否进入到定时器处理modbus.reflag=1;
1.5、配置处理数据包程序
void Mosbus_Event(void)
{
u16 crc;
u16 rccrc;
if(modbus.reflag==0)//没有收到MODbus的数据包
{
return ;
//没有收到处理指令,继续等待下一条数据
}
crc= crc16(&modbus.rcbuf[0], modbus.recount-2);
//计算校验码
rccrc=modbus.rcbuf[modbus.recount-2]*256 + modbus.rcbuf[modbus.recount-1];
//收到的校验码
if(crc ==rccrc)//数据包符合CRC校验规则
{
if(modbus.rcbuf[0] == modbus.myadd)//确认数据包是否是发给本设备的
{
switch(modbus.rcbuf[1])//分析功能码
{
case 0:break;
case 1:break;
case 2:break;
case 3:Modbud_fun3();
break;
//3号功能码处理
case 4:break;
case 5:break;
case 6:Modbud_fun6();
break;
//6号功能码处理
case 7:break;
}
}
else if(modbus.rcbuf[0] == 0)//广播地址,不处理
{
}
}//数据包不符合CRC校验规则
modbus.recount=0;
//清除缓存计数
modbus.reflag=0;
//重新开始执行处理函数C
}
处理流程图
modbus.reflag==0 modbus.reflag==1 不符合 符合 不是 广播地址 是 开始 是否接受完数据,并开始处理 计算校验码 数据包是否符合CRC校验规则 确认数据发给本设备地址 数据包是否发给本设备地址 分析功能码功能 按功能处理数据 结束处理并将计数和使能关掉 1.6、功能码程序
文章图片
6号功能码程序
void Modbud_fun6()//6号功能码处理,写寄存器
{
unsigned int Regadd;
unsigned int val;
unsigned int i,crc,j;
i=0;
Regadd=modbus.rcbuf[2]*256+modbus.rcbuf[3];
//得到要修改的地址
val=modbus.rcbuf[4]*256+modbus.rcbuf[5];
//修改后的值
Reg[Regadd]=val;
//修改本设备相应的寄存器
//以下为回应主机
modbus.Sendbuf[i++]=modbus.myadd;
//发送本设备地址
modbus.Sendbuf[i++]=0x06;
//发送功能码
modbus.Sendbuf[i++]=Regadd/256;
//发送修改地址高位
modbus.Sendbuf[i++]=Regadd%256;
//发送修改地址低位
modbus.Sendbuf[i++]=val/256;
//发送修改的值高位
modbus.Sendbuf[i++]=val%256;
//发送修改的值低位
crc=crc16(modbus.Sendbuf,i);
//校验地址、功能码、地址、数据
modbus.Sendbuf[i++]=crc/256;
//发送CRC的值高位
modbus.Sendbuf[i++]=crc%256;
//发送CRC的值低位
for(j=0;
j
3号功能码程序
void Modbud_fun3(void)//3号功能码处理---主机要读取本从机的寄存器
{
u16 Regadd;
u16 Reglen;
u16 byte;
u16 i,j;
u16 crc;
Regadd=modbus.rcbuf[2]*256+modbus.rcbuf[3];
//得到要读取的寄存器的首地址
Reglen=modbus.rcbuf[4]*256+modbus.rcbuf[5];
//得到要读取的寄存器的数量
i=0;
modbus.Sendbuf[i++]=modbus.myadd;
//发送本设备地址
modbus.Sendbuf[i++]=0x03;
//发送功能码
byte=Reglen*2;
//要返回的数据字节数
//modbus.Sendbuf[i++]=byte/256;
modbus.Sendbuf[i++]=byte%256;
//发送要返回的数据字节数
for(j=0;
j
三、试验现象 【MODBUS移植STM32,STM32做从机】
文章图片
推荐阅读
- 基于stm32智能风扇|基于stm32智能风扇_一款基于STM32的智能灭火机器人设计
- stm32|基于STM32和freeRTOS智能门锁设计方案
- STM32F4|STM32F4 TIM6 TIM7 基本定时器
- 三宝成功抢救大宝中国干细胞组,另一例脐血造血干细胞移植
- LUA|单片机脚本语言移植lua到stm32MDK
- STM32 CUbeIDE 定时器中断使用
- STM32F1系列ADC电压采集
- linux移植过程出现“can't open /dev/tq2440_serial0: No such file or directory”
- PC/SC在android上的移植
- system_stm32f0xx.h(基于v1.2.1)