六.modbus实现主机对从设备读取数据包和从设备写入数据包
1.准备好硬件上能精确到1ms的定时器
2. 先完成一个MODBUS所需要的软件上的定时器
void Timer2_Init(void) //1ms产生一次更新事件
{
TIM_TimeBaseInitTypeDef TIM_TimerBaseInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
TIM_DeInit(TIM2); //TIMx寄存器重设为缺省值
TIM_TimerBaseInitStructure.TIM_Period = 1000 - 1; //1ms
TIM_TimerBaseInitStructure.TIM_Prescaler = 72 - 1; //72 / 72 = 1MHz --> 1us
TIM_TimerBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimerBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2,&TIM_TimerBaseInitStructure);
TIM_Cmd(TIM2,ENABLE);
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
}
void TIM2_IRQHandler() //定时器2的中断服务子程序 1ms一次中断
{
u8 st;
st = TIM_GetFlagStatus(TIM2, TIM_FLAG_Update);
if(st==SET)
{
TIM_ClearFlag(TIM2,TIM_FLAG_Update);
if(modbus.timrun!=0)
{
modbus.timout++;
if(modbus.timout>=8) //间隔时间达到了时间
{
modbus.timrun=0; //关闭定时器--停止定时器
modbus.reflag=1; //收到一帧数据
}
}
}
}
烧录程序进行仿真,得到每1ms,定时器工作一次
3. (1)主函数进来后,
(2)把定时器准备好,
(3)中断准备好,
(4)然后调用485的初始化函数
LED_Init();
Timer2_Init();
Modbus_Init();
Modbus_NVIC_Init();
while(1)
{
Modbus_Event(); //处理modbus数据
}
[初始化485的收发控制脚(配置成一个零电平), 初始化485所要用到的串口(配置串口), 初始化了这个串口的一些属性,而且允许这个串口接收数据触发中断,然后在通过这个中断初始化函数,激活2号串口的中断函数,把他他的优先级设置的高一点,定时器的优先级滞后一点,这样串口就有了接收和发送的能力]
void RS485_Init(void)
{
USART_InitTypeDef USART_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC | RCC_APB2Periph_AFIO, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOC, &GPIO_InitStructure);
RS485_RT_0; //使MAX485芯片处于接收状态
//USART1_TX PB.10
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA,&GPIO_InitStructure);
//USART1_RX PB.11
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA,&GPIO_InitStructure);
//Usart1 NVIC
USART_DeInit(USART2);
USART_InitStructure.USART_BaudRate = 9600;
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_DeInit(USART2); //USART2寄存器重设为缺省值 //使代码更健壮
USART_Init(USART2, &USART_InitStructure);
USART_ITConfig(USART2, USART_IT_RXNE,ENABLE);
USART_Cmd(USART2,ENABLE);
USART_ClearFlag(USART2,USART_FLAG_TC);
}
把他的优先级设置的高一点,定时器的优先级滞后一点
void Modbus_NVIC_Init()
{
//中断优先级NVIC设置
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //TIM3中断
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //先占优先级1级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //从优先级2级
NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; //使能串口2中断
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //先占优先级1级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级2级
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStructure中指定的参数初始化外设NVIC寄存器
}
串口就有了接收和发送的能力
void USART2_IRQHandler() //MODBUS字节接收中断
{
u8 st,sbuf;
st=USART_GetFlagStatus(USART2,USART_IT_RXNE);
if(st==SET)
{
sbuf = USART2->DR;
if(modbus.reflag==1) //有数据包正在处理
{
return ;
}
modbus.rcbuf[modbus.recount++]=sbuf;
modbus.timout=0;
if(modbus.recount==1) //收到主机发来的一帧数据的第一个字节
{
modbus.timrun=1; //启动定时
}
}
}
测试串口的收发能力
void RS485_byte(u8 d) //485发送一个字节
{
RS485_RT_1;
USART_SendData(USART2, d);
while(USART_GetFlagStatus(USART2,USART_FLAG_TC) == RESET);
USART_ClearFlag(USART2,USART_FLAG_TC);
RS485_RT_0;
}
到此准备工作完成
加入CRC校验
**
- 现在实现modbus协议
**
主要实现modbus的数据接收功能
首先在头文件中定义modbus的结构体,和类型
因为波特率 9600
1位数据的时间为 10000000us / 9600bit / s = 104us
一个字节为 104us*10位 = 1040us
所以 MODBUS确定一个数据帧完成的时间为 1040us*3.5 = 3.64ms ->10ms
typedef struct
{
u8 myadd; //本机设备地址
u8 rcbuf[100]; //MODBUS接收缓冲区
u16 timout; //MODBUS的数据断续时间
u8 recount; //MODBUS端口已经收到的数据个数
u8 timrun; //MODBUS定时器是否计时的标志
u8 reflag; //收到一帧数据的标志
u8 Sendbuf[100];//MODBUS发送缓冲区
}MODBUS;
extern MODBUS modbus;
```
在编写modbus初始化函数,在这里485的初始化函数要归属于modbus的初始化(485就是modbus的一个载体)
void Modbus_Init(void)
{
modbus.myadd = 4; //本从设备地址
modbus.timrun = 0; //modbus定时器停止计时
RS485_Init();
}
测试modbus的收发
在这里modbus已经收到数据帧,接下来就要对收到的数据帧做处理
void Modbus_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) //广播地址
{
}
}
modbus.recount = 0;
modbus.reflag = 0;
}
参考文件