Modbus程序
Modbus的功能实现应该是:
1、从机
从机应该时刻处于接收状态,以便于主机能够随时访问从机数据。具体的实现思路为:从机将485的使能位使能为接收状态,然后当接收到数据的时候将数据存储到一个接收缓存中,当接收完成标志位为1 的时候,对存储的数据进行解析。然后返回相应的数据
2、主机
上位机模仿无线模块,通过USART1向主机发送查询请求,主机接收到之后。通过485模块直接向从机发送查询/修改等命令然后再将收到的数据发送回上位机。
从机代码
一、主函数
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
delay_init(168); //延时初始化
uart_init(4800); //串口初始化波特率为115200
RS485_Init(4800);
LED_Init(); //初始化与LED连接的硬件接口
TIM3_Int_Init(100-1,8400-1);//10ms
while(1)
{
if(RecvData_Flag ==1){
Baowenjiexi();
RecvData_Flag=0;
}
}
}
在主函数中通过 uart_init(4800) 和 RS485_Init(4800);
分别设置uart1 和uart2 的波特率
二、定时器函数(定时器中断)
timer.h
#ifndef _TIMER_H
#define _TIMER_H
#include "sys.h"
extern u16 RecvData_Flag;//数据是否接收完成:0为未接收;1为接收完成
extern u16 Time_last; // 距离上次接收数据过去多少时间
extern u16 RecvData_Length;//接收到的数据长度
void TIM3_Int_Init(u16 arr,u16 psc);
#endif
timer.c
#include "timer.h"
#include "led.h"
//通用定时器3中断初始化
//arr:自动重装值。
//psc:时钟预分频数
//定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us.
//Ft=定时器工作频率,单位:Mhz
//这里使用的是定时器3!
void TIM3_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); ///使能TIM3时钟
TIM_TimeBaseInitStructure.TIM_Period = arr; //自动重装载值
TIM_TimeBaseInitStructure.TIM_Prescaler=psc; //定时器分频
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);//初始化TIM3
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //允许定时器3更新中断
TIM_Cmd(TIM3,ENABLE); //使能定时器3
NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn; //定时器3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01; //抢占优先级1
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x03; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
u16 RecvData_Flag=0;//数据是否接收完成:0为未接收;1为接收完成
u16 RecvData_Length=0;//接收到的数据长度
u16 Time_last=0; // 距离上次接收数据过去多少时间
//定时器3中断服务函数
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断
{
if(Time_last<60000){
Time_last++;
}
if((Time_last>10)&&(RecvData_Length!=0)){
RecvData_Flag=1;
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位
}
}
通过使用定时器中断当延时到达十毫秒并且接收数据的长度不等于零的时候我们就认定信息接收完毕,就将接收完毕状态位RecvData_Flag
置为1
三、Modbus相关函数
modbus.h
#ifndef _MODBUS_H
#define _MODBUS_H
#include "sys.h"
extern u8 SendBuf[100];
void Baowenjiexi(void);
void Data_Send(u8 *sendbuf,u16 send_len);
void Data_Send1(u8 *sendbuf,u16 send_len);
u16 get_CRC(u8 *buf,u16 len);
#endif
modbus.c
#include "modbus.h"
#include "usart.h"
#include "timer.h"
#include "crc16.h"
#include "rs485.h"
u8 Modbus_SendBuf[100];
u8 Modbus_RecvBuf[100];
u16 modbus_io[100];
u16 modbus_id=1; //id
u16 modbus_code;//modbus功能码
u16 modbus_CRC;//校验位
u16 modbus_Package_sum=0;
u16 i,t;
void Data_Send(u8 *sendbuf,u16 send_len)
{
RS485_TX_EN=1;
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
for(t=0;t<send_len;t++)
{
USART_SendData(USART2, sendbuf[t]); //向串口2发送数据
while(USART_GetFlagStatus(USART2,USART_FLAG_TC)!=SET);//等待发送结束
}
RS485_TX_EN=0;
}
void Data_Send1(u8 *sendbuf,u16 send_len)
{
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
for(t=0;t<send_len;t++)
{
USART_SendData(USART1, sendbuf[t]); //向串口1发送数据
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
}
}
void Baowenjiexi(){
//printf("%02X",RecvData_Length);
Data_Send1(Modbus_RecvBuf,RecvData_Length);
//RecvData_Length=0;
//需增加校验位计算
modbus_CRC=crc16tablefast(Modbus_RecvBuf,RecvData_Length-2);
//printf("0x%02X\r\n",modbus_CRC);
//printf("0x%02X\r\n",Modbus_RecvBuf[RecvData_Length-2]<<8|Modbus_RecvBuf[RecvData_Length-1]);
//printf("\r\n报文校验...\r\n");
if(modbus_CRC==(Modbus_RecvBuf[RecvData_Length-2]<<8|Modbus_RecvBuf[RecvData_Length-1]))//校验是否通过
{
//printf("\r\n报文校验通过:\r\n");
if(modbus_id==Modbus_RecvBuf[0])
{
//printf("\r\nID通过\r\n");
modbus_code=Modbus_RecvBuf[1];
switch(modbus_code){
case 3:
//主机要求从机反馈一些内容
Modbus_SendBuf[0]=modbus_id;//ID
Modbus_SendBuf[1]=3; //功能码
Modbus_SendBuf[2]=(Modbus_RecvBuf[4]<<8|Modbus_RecvBuf[5])*2;//字节码长度=寄存器长度x2
for(modbus_Package_sum=0;modbus_Package_sum<Modbus_SendBuf[2];modbus_Package_sum+=2)
{
Modbus_SendBuf[3+modbus_Package_sum]=modbus_io[Modbus_RecvBuf[4]<<8|Modbus_RecvBuf[5]+modbus_Package_sum/2>>8];
Modbus_SendBuf[4+modbus_Package_sum]=modbus_io[Modbus_RecvBuf[4]<<8|Modbus_RecvBuf[5]+modbus_Package_sum/2];
}
Modbus_SendBuf[3+modbus_Package_sum]=crc16tablefast(Modbus_SendBuf,3+modbus_Package_sum)>>8;
Modbus_SendBuf[4+modbus_Package_sum]=crc16tablefast(Modbus_SendBuf,3+modbus_Package_sum);
Data_Send(Modbus_SendBuf,5+modbus_Package_sum);
RecvData_Length=0;
break;
case 6:
for(i=0;i<RecvData_Length;i++)
{
Modbus_SendBuf[i] = Modbus_RecvBuf[i];
}
Data_Send(Modbus_SendBuf,RecvData_Length);
RecvData_Length=0;
break;
case 16:
for(i=0;i<RecvData_Length;i++)
{
Modbus_SendBuf[i] = Modbus_RecvBuf[i];
}
Data_Send(Modbus_SendBuf,RecvData_Length);
RecvData_Length=0;
break;
}
}
}else{
//printf("\r\n报文校验未通过:\r\n");
RecvData_Length=0;
}
}
Data_Send(u8 *sendbuf,u16 send_len)
以及Data_Send1(u8 *sendbuf,u16 send_len)
函数分别表示向串口2以及串口1发送数据,数据的内容为sendbuf的内容,而send_len则代表发送数据的长度。
Baowenjiexi()
是执行报文解析的函数,其是根据报文的格式进行报文拆分,由于接收到的报文是存储在Modbus_RecvBuf
(例如:0x01 0x03 0x00 0x00 0x00 0x0A 0xC5 0xCD)中的,在Modbus格式中Modbus_RecvBuf[0]
(0x01)存储的是要进行通讯的地址,第二个字节Modbus_RecvBuf[1]
(0x03)是操作的类型,再往后的两个字节( 0x00 0x00)是代表主机要跟哪个id的从机通讯,最后两个字节(0xC5 0xCD)是校验码。在报文解析中,首先要校验的就是校验码,这决定了从机收到的信息是否正确。如果校验码正确,则对比当前收到的报文的id信息和本机的id信息是否一致,一致的话就进行相应操作码的反馈。
四、CRC校验码
CRC16.h
#ifndef __CRC16_H
#define __CRC16_H
#include "stm32f4xx.h"
//
//变量声明
extern uint8_t crc16_data[];
//
//函数声明
uint16_t crc16tablefast(uint8_t *ptr, uint16_t len);
#endif
CRC16.c
#include "crc16.h"
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef int int32_t;
const uint16_t polynom = 0xA001
uint16_t crc16bitbybit(uint8_t *ptr, uint16_t len)
{
uint8_t i;
uint16_t crc = 0xffff;
if (len == 0) {
len = 1;
}
while (len--) {
crc ^= *ptr;
for (i = 0; i<8; i++)
{
if (crc & 1) {
crc >>= 1;
crc ^= polynom;
}
else {
crc >>= 1;
}
}
ptr++;
}
return(crc);
}
const uint8_t crctablehi[] = {
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
};
/* Table of CRC values for low杘rder byte */
const uint8_t crctablelo[] = {
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
};
uint16_t crc16table(uint8_t *ptr, uint16_t len)
{
uint8_t crchi = 0xff;
uint8_t crclo = 0xff;
uint16_t index;
while (len--)
{
index = crclo ^ *ptr++;
crclo = crchi ^ crctablehi[index];
crchi = crctablelo[index];
}
return (crchi << 8 | crclo);
}
const uint16_t crctalbeabs[]= {
0x0000, 0xCC01, 0xD801, 0x1400, 0xF001, 0x3C00, 0x2800, 0xE401,
0xA001, 0x6C00, 0x7800, 0xB401, 0x5000, 0x9C01, 0x8801, 0x4400
};
uint16_t crc16tablefast(uint8_t *ptr, uint16_t len)
{
uint16_t crc = 0xffff;
uint16_t i;
uint8_t ch;
for (i = 0; i < len; i++) {
ch = *ptr++;
crc = crctalbeabs[(ch ^ crc) & 15] ^ (crc >> 4);
crc = crctalbeabs[((ch >> 4) ^ crc) & 15] ^ (crc >> 4);
}
crc = ((crc & 0x00FF) << 8) | ((crc & 0xFF00) >> 8); //??????
return crc;
}
在Modbus CRC校验算法中,数据包的每一个字节都被视为一个多项式系数。发送端通过一系列位操作和数学运算,将数据包转换成一个CRC校验值,并将其附加到数据包的尾部。接收端在接收数据包时,使用相同的算法重新计算CRC校验值,并将其与接收到的CRC校验值进行比较,以验证数据是否正确。
五、Uart相关
usart.h
#ifndef __USART_H
#define __USART_H
#include "stdio.h"
#include "stm32f4xx_conf.h"
#include "sys.h"
#define USART_REC_LEN 100 //定义最大接收字节数 100
#define EN_USART1_RX 1 //使能(1)/禁止(0)串口1接收
extern u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
extern u16 USART_RX_STA; //接收状态标记
//如果想串口中断接收,请不要注释以下宏定义
void uart_init(u32 bound);
#endif
usart.c
#include "sys.h"
#include "usart.h"
#include "timer.h"
#if SYSTEM_SUPPORT_OS
#include "includes.h" //ucos 使用
#endif
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void sys_exit(int x)
{
x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
USART1->DR = (u8) ch;
return ch;
}
#endif
#if EN_USART1_RX //如果使能了接收
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误
extern u8 Modbus_RecvBuf[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.
extern u16 RecvData_Length;
//接收状态
//bit15, 接收完成标志
//bit14, 接收到0x0d
//bit13~0, 接收到的有效字节数目
u16 USART_RX_STA=0; //接收状态标记
//初始化IO 串口1
//bound:波特率
void uart_init(u32 bound){
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能USART1时钟
//串口1对应引脚复用映射
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //GPIOA9复用为USART1
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //GPIOA10复用为USART1
//USART1端口配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9与GPIOA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA9,PA10
//USART1 初始化设置
USART_InitStructure.USART_BaudRate = bound;//波特率设置
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
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(USART1, &USART_InitStructure); //初始化串口1
USART_Cmd(USART1, ENABLE); //使能串口1
//USART_ClearFlag(USART1, USART_FLAG_TC);
#if EN_USART1_RX
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启相关中断
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串口1中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器、
#endif
}
void USART1_IRQHandler(void) //串口1中断服务程序
{
u8 Res;
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
OSIntEnter();
#endif
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
Time_last=0;
Res =USART_ReceiveData(USART1);//(USART1->DR); //读取接收到的数据
Modbus_RecvBuf[RecvData_Length&0X3FFF]=Res ;
RecvData_Length++;
}
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
OSIntExit();
#endif
}
#endif
uart_init(u32 bound)
是用来初始化uart1的,其中的参数bound是要设置的波特率
void USART1_IRQHandler(void)
是uart1 的中断服务函数,其作用是当接收到一个数据产生中断的时候,将数据存储到Modbus_RecvBuf
中,并且将接收数据长度的变量RecvData_Length
加一。
五、RS485 相关
rs485.h
#ifndef __RS485_H
#define __RS485_H
#include "stdio.h"
#include "stm32f4xx_conf.h"
#include "sys.h"
extern u8 Modbus_RecvBuf[100]; //接收缓冲,最大100个字节
extern u16 RecvData_Length; //接收到的数据长度
#define USART_REC_LEN 100 //定义最大接收字节数 100
#define EN_USART2_RX 1 //0,不接收;1,接收.
//模式控制
#define RS485_TX_EN PGout(8) //485模式控制.0,接收;1,发送.
//如果想串口中断接收,设置EN_USART2_RX为1,否则设置为0
void RS485_Init(u32 bound);
#endif
rs485.c
#include "rs485.h"
#include "sys.h"
#include "timer.h"
#include "modbus.h"
#if EN_USART2_RX //如果使能了接收
//串口2中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误
extern u8 Modbus_RecvBuf[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.
extern u16 RecvData_Length;
//初始化IO 串口1
//bound:波特率
void RS485_Init(u32 bound){
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2 ,ENABLE);//使能USART2时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG,ENABLE); //使能GPIOG时钟
//PG8推挽输出,485模式控制
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //GPIOG8
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度100MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOG,&GPIO_InitStructure); //初始化PG8
//串口1对应引脚复用映射
GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_USART1); //GPIOA9复用为USART1
GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_USART1); //GPIOA10复用为USART1
//USART2端口配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3; //GPIOA9与GPIOA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA1,PA3
//USART2 初始化设置
USART_InitStructure.USART_BaudRate = bound;//波特率设置
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
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(USART2, &USART_InitStructure); //初始化串口1
USART_Cmd(USART2, ENABLE); //使能串口1
//USART_ClearFlag(USART1, USART_FLAG_TC);
#if EN_USART2_RX
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启相关中断
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;//串口1中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器、
#endif
RS485_TX_EN=0; //默认为接收模式
}
void USART2_IRQHandler(void) //串口1中断服务程序
{
u8 Res;
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
OSIntEnter();
#endif
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
Time_last=0;
Res =USART_ReceiveData(USART2);//(USART2->DR); //读取接收到的数据
Modbus_RecvBuf[RecvData_Length&0X3FFF]=Res ;
RecvData_Length++;
}
//printf("接收了:%02X",RecvData_Length);
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
OSIntExit();
#endif
}
#endif
RS485 的函数的作用与uart1 的几乎相同,由于硬件电路中有一个485使能(RS485_RE)因此要对对应的GPIO进行初始化。外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
//PG8推挽输出,485模式控制
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //GPIOG8
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度100MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOG,&GPIO_InitStructure); //初始化PG8
该使能使用宏定义在rs485.h
中:#define RS485_TX_EN PGout(8) //485模式控制.0,接收;1,发送.
由于RS485设备的从机不能主动发起一次会话,因此在代码中将RS485_TX_EN=0; //默认为接收模式
默认为接收模式
主机代码
主机所实现的功能只是从串口1接收到数据之后,通过串口2的485接口发送给从机,然后将从机发送回的数据通过串口1再发送到上位机,因此用不到中间的Modbus相关的代码
一、主函数
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "beep.h"
#include "key.h"
#include "timer.h"
#include "modbus.h"
#include "rs485.h"
extern u8 Modbus_RecvBuf_Uart[USART_REC_LEN];
extern u16 RecvData_Length_Uart;
extern u8 Modbus_RecvBuf[USART_REC_LEN];
extern u16 RecvData_Length;
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
delay_init(168); //延时初始化
uart_init(4800); //串口初始化波特率为115200
RS485_Init(4800);
LED_Init(); //初始化与LED连接的硬件接口
TIM3_Int_Init(100-1,8400-1);//10ms
// Data_Send(senddata,8);
while(1)
{
if(RecvData_Flag ==1){
//Rs485接收到的数据
Data_Send1(Modbus_RecvBuf,RecvData_Length);
RecvData_Length=0;
RecvData_Flag=0;
}else if(RecvData_Flag ==2){
//Uart接收到的数据
Data_Send(Modbus_RecvBuf_Uart,RecvData_Length_Uart);
RecvData_Length_Uart=0;
RecvData_Flag=0;
}
}
}
主函数的改动主要在于while
循环中,通过两个标志位来确定中断是哪个串口发生的,如果是串口1发生的,则将数据通过串口2发送出去;如果是串口2发生的,则通过串口1发送出去。
二、定时器函数
timer.h
#ifndef _TIMER_H
#define _TIMER_H
#include "sys.h"
extern u16 RecvData_Flag;//数据是否接收完成:0为未接收;1为接收完成
extern u16 Time_last; // 距离上次接收数据过去多少时间
extern u16 RecvData_Length;//Rs485接收到的数据长度
extern u16 RecvData_Length_Uart;//uart1接收到的数据长度
void TIM3_Int_Init(u16 arr,u16 psc);
#endif
定时器的头文件中,将接收数据的长度分开,RecvData_Length
表示Rs485接收到的数据长度;RecvData_Length_Uart
则表示uart1接收到的数据长度。
timer.c
#include "timer.h"
#include "led.h"
//通用定时器3中断初始化
//arr:自动重装值。
//psc:时钟预分频数
//定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us.
//Ft=定时器工作频率,单位:Mhz
//这里使用的是定时器3!
void TIM3_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); ///使能TIM3时钟
TIM_TimeBaseInitStructure.TIM_Period = arr; //自动重装载值
TIM_TimeBaseInitStructure.TIM_Prescaler=psc; //定时器分频
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);//初始化TIM3
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //允许定时器3更新中断
TIM_Cmd(TIM3,ENABLE); //使能定时器3
NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn; //定时器3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01; //抢占优先级1
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x03; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
u16 RecvData_Flag=0;//数据是否接收完成:0为未接收;1为接收完成
u16 RecvData_Length=0;//接收到的数据长度
u16 RecvData_Length_Uart=0;//接收到的数据长度
u16 Time_last=0; // 距离上次接收数据过去多少时间
extern u16 RecvData_Length_Uart1;
//定时器3中断服务函数
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断
{
if(Time_last<60000){
Time_last++;
}
if((Time_last>10)&&(RecvData_Length!=0)){
RecvData_Flag=1;
}
if((Time_last>10)&&(RecvData_Length_Uart!=0)){
RecvData_Flag=2;
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位
}
}
```c
主机的`timer.c`相较于从机的区别在于标志位的值为1的时候,表示是串口2(也就是Rs485)所接收到的数据长度;当标志位的值为2的时候,表示的是串口1所接受的数据长度。主函数根据此标志位来确定进行什么样的操作。
## 三、Modbus相关函数
#### modbus.h
```c
#ifndef _MODBUS_H
#define _MODBUS_H
#include "sys.h"
extern u8 SendBuf[100];
void Data_Send(u8 *sendbuf,u16 send_len);
void Data_Send1(u8 *sendbuf,u16 send_len);
u16 get_CRC(u8 *buf,u16 len);
#endif
modbus.c
#include "modbus.h"
#include "usart.h"
#include "timer.h"
#include "crc16.h"
#include "rs485.h"
u8 Modbus_SendBuf[100];
u8 Modbus_RecvBuf[100];
u16 modbus_io[100];
u16 modbus_id=1; //id
u16 modbus_code;//modbus功能码
u16 modbus_CRC;//校验位
u16 modbus_Package_sum=0;
u16 i,t;
void Data_Send1(u8 *sendbuf,u16 send_len)//向串口1发送数据
{
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
for(t=0;t<send_len;t++)
{
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
USART_SendData(USART1, sendbuf[t]);
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
}
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
}
void Data_Send(u8 *sendbuf,u16 send_len) //向串口2发送数据
{
RS485_TX_EN=1;
//Data_Send1(sendbuf,send_len);
for(t=0;t<send_len;t++)
{
while(USART_GetFlagStatus(USART2,USART_FLAG_TC)!=SET);//等待发送结束
USART_SendData(USART2, sendbuf[t]);
while(USART_GetFlagStatus(USART2,USART_FLAG_TC)!=SET);//等待发送结束
}
RS485_TX_EN=0;
}
因为不需要本地解析,所以只保留了两个发送函数。
四、Uart相关函数
usart.h
#ifndef __USART_H
#define __USART_H
#include "stdio.h"
#include "stm32f4xx_conf.h"
#include "sys.h"
#define USART_REC_LEN 100 //定义最大接收字节数 100
#define EN_USART1_RX 1 //使能(1)/禁止(0)串口1接收
extern u8 Modbus_RecvBuf_Uart[100];
extern u16 USART_RX_STA; //接收状态标记
//如果想串口中断接收,请不要注释以下宏定义
void uart_init(u32 bound);
#endif
usart.c
#include "sys.h"
#include "usart.h"
#include "timer.h"
#if SYSTEM_SUPPORT_OS
#include "includes.h" //ucos 使用
#endif
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void sys_exit(int x)
{
x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
USART1->DR = (u8) ch;
return ch;
}
#endif
#if EN_USART1_RX //如果使能了接收
u16 USART_RX_STA=0; //接收状态标记
//初始化IO 串口1
//bound:波特率
void uart_init(u32 bound){
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能USART1时钟
//串口1对应引脚复用映射
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //GPIOA9复用为USART1
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //GPIOA10复用为USART1
//USART1端口配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9与GPIOA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA9,PA10
//USART1 初始化设置
USART_InitStructure.USART_BaudRate = bound;//波特率设置
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
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(USART1, &USART_InitStructure); //初始化串口1
USART_Cmd(USART1, ENABLE); //使能串口1
//USART_ClearFlag(USART1, USART_FLAG_TC);
#if EN_USART1_RX
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启相关中断
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串口1中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器、
#endif
}
extern u16 RecvData_Length_Uart;
u8 Modbus_RecvBuf_Uart[100];
void USART1_IRQHandler(void) //串口1中断服务程序
{
u8 Res;
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
OSIntEnter();
#endif
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
Time_last=0;
Res =USART_ReceiveData(USART1);//(USART1->DR); //读取接收到的数据
Modbus_RecvBuf_Uart[RecvData_Length_Uart&0X3FFF]=Res ;
//USART_SendData(USART1, Res);
RecvData_Length_Uart++;
}
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
OSIntExit();
#endif
}
#endif
相较于从机代码,只是将中断函数中的收到数据的长度改为RecvData_Length_Uart++
来确保中断能够判断出哪个串口收到了数据,以及将接收接收缓存与串口2(Rs485)区分开,改为Modbus_RecvBuf_Uart[100]
。
五、RS485相关函数
rs485.h
#ifndef __RS485_H
#define __RS485_H
#include "stdio.h"
#include "stm32f4xx_conf.h"
#include "sys.h"
extern u8 Modbus_RecvBuf[100]; //接收缓冲,最大100个字节
extern u16 RecvData_Length; //接收到的数据长度
#define USART_REC_LEN 100 //定义最大接收字节数 100
#define EN_USART2_RX 1 //0,不接收;1,接收.
//模式控制
#define RS485_TX_EN PGout(8) //485模式控制.0,接收;1,发送
//如果想串口中断接收,设置EN_USART2_RX为1,否则设置为0
void RS485_Init(u32 bound);
#endif
rs485.c
#include "rs485.h"
#include "sys.h"
#include "timer.h"
#include "modbus.h"
#if EN_USART2_RX //如果使能了接收
//串口2中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误
extern u8 Modbus_RecvBuf[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.
extern u16 RecvData_Length;
//初始化IO 串口1
//bound:波特率
void RS485_Init(u32 bound){
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2 ,ENABLE);//使能USART2时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG,ENABLE); //使能GPIOG时钟
//PG8推挽输出,485模式控制
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //GPIOG8
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度100MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOG,&GPIO_InitStructure); //初始化PG8
//串口1对应引脚复用映射
GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_USART1); //GPIOA9复用为USART1
GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_USART1); //GPIOA10复用为USART1
//USART2端口配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3; //GPIOA9与GPIOA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA1,PA3
//USART2 初始化设置
USART_InitStructure.USART_BaudRate = bound;//波特率设置
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
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(USART2, &USART_InitStructure); //初始化串口1
USART_Cmd(USART2, ENABLE); //使能串口1
//USART_ClearFlag(USART1, USART_FLAG_TC);
#if EN_USART2_RX
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启相关中断
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;//串口1中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器、
#endif
RS485_TX_EN=0; //默认为接收模式
}
void USART2_IRQHandler(void) //串口1中断服务程序
{
u8 Res;
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
OSIntEnter();
#endif
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
Time_last=0;
Res =USART_ReceiveData(USART2);//(USART2->DR); //读取接收到的数据
Modbus_RecvBuf[RecvData_Length&0X3FFF]=Res ;
RecvData_Length++;
}
//printf("接收了:%02X",RecvData_Length);
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
OSIntExit();
#endif
}
#endif
RS485代码并未改动。