一、从之前的公司看到过一种工业控制化方式,类似于PLC形式。当时有个高级工程师使用MODBUS协议,对其进行封装。控制板使用STM32F103VET6芯片,可以通过维控触摸屏连接控制板。到现在耿耿于怀,那会刚出大学门,技术层次封锁,只知道一个总线协议。现在有时间去移植这个协议了。写这篇文章熟悉下移植过程
移植CSDN一个前辈的协议链接如下:
原文链接:https://blog.csdn.net/weixin_41542513/article/details/95167095
二、总线移植首先要知道哪几个点最重要,初步看官方协议无非就是,校验码核对,MODBUS功能码处理函数,各分支功能码功能实现。写这个也是怕忘记,方便自己以后移植。
1、校验函数:网上很多可以借鉴:
/**
1. 功能:根据ModBus规则计算CRC16
2. 参数:
3. _pBuf:待计算数据缓冲区,计算得到的结果存入_pBuf的最后两字节
4. _usLen:待计算数据长度(字节数)
5. 返回值:16位校验值
*/
unsigned short int getModbusCRC16(unsigned char *_pBuf, unsigned short int _usLen)
{
unsigned short int CRCValue = 0xFFFF; //初始化CRC变量各位为1
unsigned char i,j;for(i=0;i<_usLen;++i)
{
CRCValue ^= *(_pBuf+i); //当前数据异或CRC低字节
for(j=0;j<8;++j) //一个字节重复右移8次
{
if((CRCValue & 0x01) == 0x01) //判断右移前最低位是否为1
{
CRCValue = (CRCValue >> 1)^0xA001; //如果为1则右移并异或表达式
}else
{
CRCValue >>= 1; //否则直接右移一位
}
}
}
return CRCValue;
}
2、对应这个校验码,自己可以写验证函数去验证,随意定义一个接收数组玩下就知道了这里给一个链接工具,也是网友给的,可以根据写的前六个16进制字节自己计算。然后和自己程序验证的结果对比就知道了:
3、MODBUS接收处理函数,链接还是第一条的链接:调试时可以自己搞几个返回值,观察下处理函数卡哪里了。4
/**
1. 功能:ModBus服务函数
2. 参数:无
3. 返回值:无
*/
void RS485_Service(void)
{
// USART_SendString(USART1,"123456");
// RS485_Addr=(u16)FLASH_data[1];//读取FLASH里设备地址 0X03
u16 recCRC;
if(RS485_FrameFlag==1)
{
if(RS485_RX_BUFF[0]==RS485_Addr)//地址正确
{
if((RS485_RX_BUFF[1]==0x01)||(RS485_RX_BUFF[1]==0x02)||(RS485_RX_BUFF[1]==0x03)||(RS485_RX_BUFF[1]==0x04)||(RS485_RX_BUFF[1]==0x05)||(RS485_RX_BUFF[1]==0x06)||(RS485_RX_BUFF[1]==0x15)||(RS485_RX_BUFF[1]==0x16))//功能码正确
{
startRegAddr=(((u16)RS485_RX_BUFF[2])<<8)|RS485_RX_BUFF[3];//获取寄存器起始地址
if(startRegAddr<1000)//寄存器地址在范围内
{
calCRC=getModbusCRC16(RS485_RX_BUFF,RS485_RX_CNT-2);//计算所接收数据的CRC
recCRC=RS485_RX_BUFF[RS485_RX_CNT-2]|(((u16)RS485_RX_BUFF[RS485_RX_CNT-1])<<8);//接收到的CRC(低字节在前,高字节在后)
if(calCRC==recCRC)//CRC校验正确
{
SysTick_Delay_Ms(10);
//toggleLED();
switch(RS485_RX_BUFF[1])//根据不同的功能码进行处理
{
case 01://读输出开关量
{
Modbus_01_Solve();
break;
}case 02://读输入开关量
{
Modbus_02_Solve();
break;
}
case 03: //读多个保持寄存器
{
Modbus_03_Solve();
break;
}
case 04: //读多个输入寄存器
{
Modbus_04_Solve();
break;
}
case 05://写单个输出开关量
{
Modbus_05_Solve();
break;
}case 06: //写单个保持寄存器
{
Modbus_06_Solve();
break;
}
case 15://写多个输出开关量
{
Modbus_15_Solve();
break;
}case 16: //写多个保持寄存器
{
Modbus_16_Solve();
break;
}}
//
}
else//CRC校验错误
{
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1]|0x80;
RS485_TX_BUFF[2]=0x04; //异常码
RS485_SendData(RS485_TX_BUFF,3);
}
}
else//寄存器地址超出范围
{
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1]|0x80;
RS485_TX_BUFF[2]=0x02; //异常码
RS485_SendData(RS485_TX_BUFF,3);
}
}
else//功能码错误
{
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1]|0x80;
RS485_TX_BUFF[2]=0x01; //异常码
RS485_SendData(RS485_TX_BUFF,3);
}
}RS485_FrameFlag=0;//复位帧结束标志
RS485_RX_CNT=0;//接收计数器清零
//RS485_TX_EN=0;//开启接收模式
//GPIO_ResetBits(GPIOA,GPIO_Pin_9);//默认接收状态
}
}
4、功能码实现函数:
(1)01功能码
//Modbus功能码01处理程序
//读输出开关量
void Modbus_01_Solve(void)//-------------------------程序已验证可用
{
u16 ByteNum;
u16 i;
RegNum= (((u16)RS485_RX_BUFF[4])<<8)|RS485_RX_BUFF[5];//获取寄存器数量
if((startRegAddr+RegNum)<100)//寄存器地址+数量在范围内
{
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1];
ByteNum=RegNum/8;//字节数
if(RegNum%8) ByteNum+=1;//如果位数还有余数,则字节数+1
RS485_TX_BUFF[2]=ByteNum;//返回要读取的字节数
for(i=0;i<RegNum;i++)
{
if(i%8==0) RS485_TX_BUFF[3+i/8]=0x00;
RS485_TX_BUFF[3+i/8]>>=1;//低位先发送
RS485_TX_BUFF[3+i/8]|=((*Modbus_OutputIO[startRegAddr+i])<<7)&0x80;
if(i==RegNum-1)//发送到最后一个位了
{
if(RegNum%8) RS485_TX_BUFF[3+i/8]>>=8-(RegNum%8);//如果最后一个字节还有余数,则剩余MSB填充0
}
}
calCRC=getModbusCRC16(RS485_TX_BUFF,ByteNum+3);
RS485_TX_BUFF[ByteNum+3]=calCRC&0xFF;
RS485_TX_BUFF[ByteNum+4]=(calCRC>>8)&0xFF;
RS485_SendData(RS485_TX_BUFF,ByteNum+5);
}
else//寄存器地址+数量超出范围
{
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1]|0x80;
RS485_TX_BUFF[2]=0x02; //异常码
RS485_SendData(RS485_TX_BUFF,3);
}
}
(2)02功能码
//Modbus功能码02处理程序 -----必须先配置初始化按键才可以OK KEY_Init();
//读输入开关量
void Modbus_02_Solve(void)//-------------------------程序已验证可用
{
u16 ByteNum;
u16 i;
RegNum= (((u16)RS485_RX_BUFF[4])<<8)|RS485_RX_BUFF[5];//获取寄存器数量
if((startRegAddr+RegNum)<100)//寄存器地址+数量在范围内
{
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1];
ByteNum=RegNum/8;//字节数
if(RegNum%8) ByteNum+=1;//如果位数还有余数,则字节数+1
RS485_TX_BUFF[2]=ByteNum;//返回要读取的字节数
for(i=0;i<RegNum;i++)
{
if(i%8==0) RS485_TX_BUFF[3+i/8]=0x00;
RS485_TX_BUFF[3+i/8]>>=1;//低位先发送
RS485_TX_BUFF[3+i/8]|=((*Modbus_InputIO[startRegAddr+i])<<7)&0x80;
if(i==RegNum-1)//发送到最后一个位了
{
if(RegNum%8) RS485_TX_BUFF[3+i/8]>>=8-(RegNum%8);//如果最后一个字节还有余数,则剩余MSB填充0
}
}
calCRC=getModbusCRC16(RS485_TX_BUFF,ByteNum+3);
RS485_TX_BUFF[ByteNum+3]=calCRC&0xFF;
RS485_TX_BUFF[ByteNum+4]=(calCRC>>8)&0xFF;
RS485_SendData(RS485_TX_BUFF,ByteNum+5);
}
else//寄存器地址+数量超出范围
{
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1]|0x80;
RS485_TX_BUFF[2]=0x02; //异常码
RS485_SendData(RS485_TX_BUFF,3);
}
}
(3)03功能码:
//Modbus功能码03处理程序
//读保持寄存器
void Modbus_03_Solve(void) //-------------------------程序已验证可用
{
u8 i;
RegNum= (((u16)RS485_RX_BUFF[4])<<8)|RS485_RX_BUFF[5];//获取寄存器数量
// printf("寄存器数量=%d\n",a);
if((startRegAddr+RegNum)<1000)//寄存器地址+数量在范围内
{
// USART_SendString(USART1,"3");
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1];
RS485_TX_BUFF[2]=RegNum*2;
for(i=0;i<RegNum;i++)
{
RS485_TX_BUFF[3+i*2]=(*Modbus_HoldReg[startRegAddr+i]>>8)&0xFF; //先发送高字节
RS485_TX_BUFF[4+i*2]=*Modbus_HoldReg[startRegAddr+i]&0xFF; //后发送低字节
}
calCRC=getModbusCRC16(RS485_TX_BUFF,RegNum*2+3);
RS485_TX_BUFF[RegNum*2+3]=calCRC&0xFF;
RS485_TX_BUFF[RegNum*2+4]=(calCRC>>8)&0xFF;
RS485_SendData(RS485_TX_BUFF,RegNum*2+5);
}
else//寄存器地址+数量超出范围
{
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1]|0x80;
RS485_TX_BUFF[2]=0x02; //异常码
RS485_SendData(RS485_TX_BUFF,3);
}
}
(4)、04功能码
//Modbus功能码04处理程序
//读输入寄存器
void Modbus_04_Solve(void) //-------------------------程序已验证可用
{
u8 i;
RegNum= (((u16)RS485_RX_BUFF[4])<<8)|RS485_RX_BUFF[5];//获取寄存器数量
if((startRegAddr+RegNum)<1000)//寄存器地址+数量在范围内
{
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1];
RS485_TX_BUFF[2]=RegNum*2;
for(i=0;i<RegNum;i++)
{
RS485_TX_BUFF[3+i*2]=(*Modbus_HoldReg[startRegAddr+i]>>8)&0xFF; //先发送高字节
RS485_TX_BUFF[4+i*2]=*Modbus_HoldReg[startRegAddr+i]&0xFF; //后发送低字节
}
calCRC=getModbusCRC16(RS485_TX_BUFF,RegNum*2+3);
RS485_TX_BUFF[RegNum*2+3]=calCRC&0xFF;
RS485_TX_BUFF[RegNum*2+4]=(calCRC>>8)&0xFF;
RS485_SendData(RS485_TX_BUFF,RegNum*2+5);
}
else//寄存器地址+数量超出范围
{
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1]|0x80;
RS485_TX_BUFF[2]=0x02; //异常码
RS485_SendData(RS485_TX_BUFF,3);
}
}
(5)、05功能码
//Modbus功能码05处理程序
//写单个输出开关量
void Modbus_05_Solve(void)//-------------------------程序已验证可用
{
if(startRegAddr<100)//寄存器地址在范围内
{
if((RS485_RX_BUFF[4]==0xFF)||(RS485_RX_BUFF[5]==0xFF)) *Modbus_OutputIO[startRegAddr]=0x01;
else *Modbus_OutputIO[startRegAddr]=0x00;
if(RS485_RX_BUFF[2]&0x05){
RS485_RX_BUFF[2]&=~0x05;
if(RS485_RX_BUFF[5]==0xff)SetY((((u16)RS485_RX_BUFF[2])<<8)|RS485_RX_BUFF[3]);
else RstY((((u16)RS485_RX_BUFF[2])<<8)|RS485_RX_BUFF[3]);
}
if(RS485_RX_BUFF[2]&0x08){
RS485_RX_BUFF[2]&=~0x08;
if(RS485_RX_BUFF[5]==0xff)SetM((((u16)RS485_RX_BUFF[2])<<8)|RS485_RX_BUFF[3]);
else RstY((((u16)RS485_RX_BUFF[2])<<8)|RS485_RX_BUFF[3]);
}
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1];
RS485_TX_BUFF[2]=RS485_RX_BUFF[2];
RS485_TX_BUFF[3]=RS485_RX_BUFF[3];
RS485_TX_BUFF[4]=RS485_RX_BUFF[4];
RS485_TX_BUFF[5]=RS485_RX_BUFF[5];calCRC=getModbusCRC16(RS485_TX_BUFF,6);
RS485_TX_BUFF[6]=calCRC&0xFF;
RS485_TX_BUFF[7]=(calCRC>>8)&0xFF;
RS485_SendData(RS485_TX_BUFF,8);
}
else//寄存器地址超出范围
{
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1]|0x80;
RS485_TX_BUFF[2]=0x02; //异常码
RS485_SendData(RS485_TX_BUFF,3);
}
}
(6)06功能码
//Modbus功能码06处理程序
//写单个保持寄存器
void Modbus_06_Solve(void)//-------------------------程序已验证可用
{
*Modbus_HoldReg[startRegAddr]=RS485_RX_BUFF[4]<<8;//高字节在前 修改为高字节在前,低字节在后
*Modbus_HoldReg[startRegAddr]|=((u16)RS485_RX_BUFF[5]);//低字节在后
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1];
RS485_TX_BUFF[2]=RS485_RX_BUFF[2];
RS485_TX_BUFF[3]=RS485_RX_BUFF[3];
RS485_TX_BUFF[4]=RS485_RX_BUFF[4];
RS485_TX_BUFF[5]=RS485_RX_BUFF[5];calCRC=getModbusCRC16(RS485_TX_BUFF,6);
RS485_TX_BUFF[6]=calCRC&0xFF;
RS485_TX_BUFF[7]=(calCRC>>8)&0xFF;
RS485_SendData(RS485_TX_BUFF,8);
if(startRegAddr==8||startRegAddr==9)
FLASH_WriteMoreData(0x800FC00,FLASH_data,2);}
(7)15功能码
//Modbus功能码15处理程序
//写多个输出开关量
void Modbus_15_Solve(void)//-------------------------程序已验证可用
{
u16 i;
RegNum=(((u16)RS485_RX_BUFF[4])<<8)|RS485_RX_BUFF[5];//获取寄存器数量
if((startRegAddr+RegNum)<100)//寄存器地址+数量在范围内
{
for(i=0;i<RegNum;i++)
{
if(RS485_RX_BUFF[7+i/8]&0x01) *Modbus_OutputIO[startRegAddr+i]=0x01;
else *Modbus_OutputIO[startRegAddr+i]=0x00;
RS485_RX_BUFF[7+i/8]>>=1;//从低位开始
}RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1];
RS485_TX_BUFF[2]=RS485_RX_BUFF[2];
RS485_TX_BUFF[3]=RS485_RX_BUFF[3];
RS485_TX_BUFF[4]=RS485_RX_BUFF[4];
RS485_TX_BUFF[5]=RS485_RX_BUFF[5];
calCRC=getModbusCRC16(RS485_TX_BUFF,6);
RS485_TX_BUFF[6]=calCRC&0xFF;
RS485_TX_BUFF[7]=(calCRC>>8)&0xFF;
RS485_SendData(RS485_TX_BUFF,8);
}
else//寄存器地址+数量超出范围
{
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1]|0x80;
RS485_TX_BUFF[2]=0x02; //异常码
RS485_SendData(RS485_TX_BUFF,3);
}
}
(8)16功能码
//Modbus功能码16处理程序
//写多个保持寄存器
void Modbus_16_Solve(void)//-------------------------程序已验证可用
{
u8 i;
RegNum= (((u16)RS485_RX_BUFF[4])<<8)|RS485_RX_BUFF[5];//获取寄存器数量
if((startRegAddr+RegNum)<1000)//寄存器地址+数量在范围内
{
for(i=0;i<RegNum;i++)
{
*Modbus_HoldReg[startRegAddr+i]=RS485_RX_BUFF[7+i*2]<<8;//高字节在前 修改为高字节在前,低字节在后
*Modbus_HoldReg[startRegAddr+i]|=((u16)RS485_RX_BUFF[8+i*2]);//低字节在后
}RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1];
RS485_TX_BUFF[2]=RS485_RX_BUFF[2];
RS485_TX_BUFF[3]=RS485_RX_BUFF[3];
RS485_TX_BUFF[4]=RS485_RX_BUFF[4];
RS485_TX_BUFF[5]=RS485_RX_BUFF[5];calCRC=getModbusCRC16(RS485_TX_BUFF,6);
RS485_TX_BUFF[6]=calCRC&0xFF;
RS485_TX_BUFF[7]=(calCRC>>8)&0xFF;
RS485_SendData(RS485_TX_BUFF,8);
}
else//寄存器地址+数量超出范围
{
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1]|0x80;
RS485_TX_BUFF[2]=0x02; //异常码
RS485_SendData(RS485_TX_BUFF,3);
}
}
5、然后就是串口初始化处理了,大佬少给了串口中断,这里补上
/**
1. 功能:初始化UART
2. 参数:None
3. 返回值:None
*/
void initUART(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef USART_Nvic_InitTypedef;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); //使能USART1时钟
/*********************GPIO Config***************************/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //发送管脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //接收管脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
//485使能脚
/*
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;//PA1 通用推挽输出
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_Init(GPIOG,&GPIO_InitStructure);
GPIO_ResetBits(GPIOA,GPIO_Pin_9);//默认接收状态
*/
//RS485_TX_EN=0;//默认为接收模式
//USART_DeInit(USART1);//复位串口1
//读FLASH第64页内容
u32 BaudRate=9600;
FLASH_ReadMoreData(0x800FC00,FLASH_data,2);
/********************UART Config*****************************/
switch (FLASH_data[0]) //波特率选择
{
case 1:BaudRate=4800;break;
case 2:BaudRate=9600;break;
case 3:BaudRate=14400;break;
case 4:BaudRate=19200;break;
case 5:BaudRate=38400;break;
case 6:BaudRate=57600;break;
case 7:BaudRate=115200;break;
default:BaudRate=9600;break;
}if(FLASH_data[0]>0xff) FLASH_data[1]=0x0f; //设备地址初始化
USART_InitStructure.USART_BaudRate = BaudRate; //设置波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //8bits数据位
USART_InitStructure.USART_StopBits = USART_StopBits_1; //1bit停止位
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); //设置生效
/*****************配置串口中断*****************************/
USART_Nvic_InitTypedef.NVIC_IRQChannel=USART1_IRQn;
USART_Nvic_InitTypedef.NVIC_IRQChannelPreemptionPriority=4;
USART_Nvic_InitTypedef.NVIC_IRQChannelSubPriority=0;
USART_Nvic_InitTypedef.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&USART_Nvic_InitTypedef);
USART_Cmd(USART1, ENABLE); //使能串口外设
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //使能串口接收中断
// memset(RS485_RX_BUFF,0,2047);
}
6、是不是发现有个数据发送的函数没定义出来:这里给出,大佬好像是忘记了:
一个是我自己用串口检测,增加的字符串发送,一个是协议那个数据
//发送字符串
void USART_SendString(USART_TypeDef* USARTx,char* str)
{
while(*str!='\0')//发送字符不为'\0',循环
{
USART_ClearFlag(USARTx,USART_FLAG_TC);//确保第一次发送数据显示第一个字符
USART_SendData(USARTx,* str); //发送单个字符
while(USART_GetFlagStatus(USARTx,USART_FLAG_TC)!=SET);//检测发送完成标志位是否置位
str++;
}
}void RS485_SendData(u8 *buff,u8 len)
{
// RS485_TX_EN=1;//切换为发送模式
while(len--)
{
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);//等待发送区为空
USART_SendData(USART1,(*buff++));
}
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);//等待发送完成
}
7、开始初始化时钟了,有个空闲设置3.5MS
initTIMx(TIM3,63000-1,4-1,TIM_IT_Update,ENABLE);//pscarl 2520-1,p 100-1 Tout=(4-1+1)*(63000-1+1)/72,000,000=4/2,000=2ms period=4 prescaler=63000 63000 3.5ms
代码如下:
/**
* 功能:初始化定时器
* 参数:
* TIMx:指定待设置的定时器,TIM1-TIM4
* prescaler:设置预分频值 0-65535
* period:设置中断周期,即设置重装载寄存器的值 0-65535
* IT_Source:中断源,比如更新中断,四个通道的输入比较中断,取值查看:TIM_interrupt_sources
* NewState:是否使能IT_Source参数指定的中断,ENABLE,DISABLE
* 返回值:None
*/
void initTIMx(TIM_TypeDef* TIMx,u16 prescaler,u16 period,u16 IT_Source,FunctionalState NewState)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
switch((u32)TIMx)
{
case (u32)TIM1: RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);break; //开启定时器1时钟
case (u32)TIM2: RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);break; //开启定时器2时钟
case (u32)TIM3: RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);break; //开启定时器3时钟
case (u32)TIM4: RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);break; //开启定时器4时钟
/*其他密度的单片机对case进行删减即可兼容,本程序针对中密度单片机*/
default : break;
}
// RCC_PCLK1Config(RCC_HCLK_Div2);
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分频因子,该分频不是预分频值,要区分
TIM_TimeBaseStructure.TIM_Period = period; //设置定时器周期 重装值
TIM_TimeBaseStructure.TIM_Prescaler =prescaler; //设置预分频值
TIM_TimeBaseInit(TIMx, &TIM_TimeBaseStructure); //生效更改
TIM_Cmd(TIMx, DISABLE); //开启计数器
TIM_ITConfig(TIMx,IT_Source,NewState); //使能定时器更新中断
// TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级0级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级3级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
TIM_Cmd(TIM3, ENABLE); //使能TIMx外设
}
8、定时器3设置中断函数:
/****************************************定时器中断服务函数************************************************/
//用定时器3判断接收空闲时间,当空闲时间大于指定时间,认为一帧结束
u8 RS485_FrameFlag=0;
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3, TIM_IT_Update) == SET) //由于定时器中断源很多,因此要判断是哪个中断源触发的中断
{
TIM_ClearITPendingBit(TIM3, TIM_IT_Update); //软件清除中断挂起位,否则会一直卡死在中断服务函数
TIM_Cmd(TIM3,DISABLE);//停止定时器
//RS485_TX_EN=1;//停止接收,切换为发送状态
//GPIO_SetBits(GPIOA,GPIO_Pin_9)
RS485_FrameFlag=1;//置位帧结束标记
}
}
9、该设置串口了,我这边用的C8T6,F103应该都是一样的,PA9,PA10:
/**
1. 功能:初始化UART
2. 参数:None
3. 返回值:None
*/
void initUART(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef USART_Nvic_InitTypedef;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); //使能USART1时钟
/*********************GPIO Config***************************/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //发送管脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //接收管脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
//485使能脚
/*
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;//PA1 通用推挽输出
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_Init(GPIOG,&GPIO_InitStructure);
GPIO_ResetBits(GPIOA,GPIO_Pin_9);//默认接收状态
*/
//RS485_TX_EN=0;//默认为接收模式
//USART_DeInit(USART1);//复位串口1
//读FLASH第64页内容
u32 BaudRate=9600;
FLASH_ReadMoreData(0x800FC00,FLASH_data,2);
/********************UART Config*****************************/
switch (FLASH_data[0]) //波特率选择
{
case 1:BaudRate=4800;break;
case 2:BaudRate=9600;break;
case 3:BaudRate=14400;break;
case 4:BaudRate=19200;break;
case 5:BaudRate=38400;break;
case 6:BaudRate=57600;break;
case 7:BaudRate=115200;break;
default:BaudRate=9600;break;
}if(FLASH_data[0]>0xff) FLASH_data[1]=0x0f; //设备地址初始化
USART_InitStructure.USART_BaudRate = BaudRate; //设置波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //8bits数据位
USART_InitStructure.USART_StopBits = USART_StopBits_1; //1bit停止位
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); //设置生效
/*****************配置串口中断*****************************/
USART_Nvic_InitTypedef.NVIC_IRQChannel=USART1_IRQn;
USART_Nvic_InitTypedef.NVIC_IRQChannelPreemptionPriority=4;
USART_Nvic_InitTypedef.NVIC_IRQChannelSubPriority=0;
USART_Nvic_InitTypedef.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&USART_Nvic_InitTypedef);
USART_Cmd(USART1, ENABLE); //使能串口外设
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //使能串口接收中断
// memset(RS485_RX_BUFF,0,2047);
}
10、设置串口1接收处理函数
void USART1_IRQHandler(void)
{
u8 res;
u8 err;
if(USART_GetITStatus(USART1, USART_IT_RXNE)) //判断接收数据寄存器是否有数据
{
if(USART_GetFlagStatus(USART1,USART_FLAG_NE|USART_FLAG_FE|USART_FLAG_PE))err=1;//帧错误或校验错误检测到噪音
else err=0;
res=USART_ReceiveData(USART1); //读接收到的字节,同时相关标志自动清除
if((RS485_RX_CNT<2047)&&(err==0))
{
// USART_SendString(USART1,"2");
RS485_RX_BUFF[RS485_RX_CNT]=res;
RS485_RX_CNT++;
TIM_ClearITPendingBit(TIM3,TIM_IT_Update);//清除定时器溢出中断
TIM_SetCounter(TIM3,0);//当接收到一个新的字节,将定时器3复位为0,重新计时(相当于喂狗)
TIM_Cmd(TIM3,ENABLE);//开始计时
}
}
}
11、控制板初始化IO:
//控制板IO口初始化
void BoardGPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOE|RCC_APB2Periph_AFIO, ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable, ENABLE);//禁用JTAG和SWD
// GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);//禁用JTAG,使能SWD
//定义输入IO口
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;//上拉输入
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_8|GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_15;
GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9;
GPIO_Init(GPIOB, &GPIO_InitStructure);// GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;
// GPIO_Init(GPIOC, &GPIO_InitStructure);// GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;
// GPIO_Init(GPIOD, &GPIO_InitStructure);// GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6;
// GPIO_Init(GPIOE, &GPIO_InitStructure);
//定义输出IO口
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
// GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;
// GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10|GPIO_Pin_11;
GPIO_Init(GPIOB, &GPIO_InitStructure);// GPIO_InitStructure.GPIO_Pin=GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;
// GPIO_Init(GPIOC, &GPIO_InitStructure);// GPIO_InitStructure.GPIO_Pin=GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;
// GPIO_Init(GPIOD, &GPIO_InitStructure);// GPIO_InitStructure.GPIO_Pin=GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;
// GPIO_Init(GPIOE, &GPIO_InitStructure);
ALLOUT(OFF);
}
12、重定义Printf函数:
#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
13、定义输出初始状态:看上拉还是下拉
//所有输出口
#define ALLOUT(a); out1=a; \
out2=a; \
// out3=a; \
// out4=a; \
// out5=a; \
// out6=a; \
// out7=a; \
// out8=a;
14、大佬定义的初始映射,勉强看懂:
/**
1. 功能:Modbus寄存器和STM32单片机寄存器的映射关系
2. 参数:无
3. 返回值:无
*/
void Modbus_RegMap(void)
{
//输入开关量寄存器指针指向
//&PEin(4):取PE4的地址,(vu32*)&PEin(4)将PE4地址强制转换为uw32类型的地址,Modbus_InputIO[0]=(vu32*)&PEin(4);
//将转换好的地址送给地址指针Modbus_InputIO[0];
// Modbus_InputIO[0]=(vu32*)&PAin(0); //KEY0
// Modbus_InputIO[1]=(vu32*)&PBin(9); //KEY1
// Modbus_InputIO[2]=(vu32*)&PBin(8); //KEY2
// Modbus_InputIO[3]=(vu32*)&PBin(7); //KEY3
// Modbus_InputIO[4]=(vu32*)&PBin(6); //KEY4
// Modbus_InputIO[5]=(vu32*)&PBin(5); //KEY5
// Modbus_InputIO[6]=(vu32*)&PBin(4); //KEY6
// Modbus_InputIO[7]=(vu32*)&PBin(3); //KEY7
// Modbus_InputIO[8]=(vu32*)&PAin(15); //KEY8
// Modbus_InputIO[9]=(vu32*)&PAin(12); //KEY9
// Modbus_InputIO[10]=(vu32*)&PAin(11); //KEY10
// Modbus_InputIO[11]=(vu32*)&PAin(8); //KEY11
//输出开关量寄存器指针指向
Modbus_OutputIO[0]=(vu32*)&PBout(10); //LED0
Modbus_OutputIO[1]=(vu32*)&PBout(11); //LED1
// Modbus_OutputIO[2]=(vu32*)&PCout(15); //LED2
// Modbus_OutputIO[3]=(vu32*)&PAout(1); //LED3
// Modbus_OutputIO[4]=(vu32*)&PAout(2); //LED4
// Modbus_OutputIO[5]=(vu32*)&PAout(3); //LED5
// Modbus_OutputIO[6]=(vu32*)&PAout(4); //LED6
// Modbus_OutputIO[7]=(vu32*)&PAout(5); //LED7
// Modbus_OutputIO[0]=1;
//保持寄存器指针指向
Modbus_HoldReg[0]=(u16*)&testData1; //测试数据1
Modbus_HoldReg[1]=(u16*)&testData2; //测试数据2
Modbus_HoldReg[2]=(u16*)&testData3; //测试数据3
Modbus_HoldReg[3]=(u16*)&testData4; //测试数据4
Modbus_HoldReg[4]=(u16*)&testData5; //测试数据5
Modbus_HoldReg[5]=(u16*)&testData6; //测试数据6
Modbus_HoldReg[6]=(u16*)&testData7; //测试数据7
Modbus_HoldReg[7]=(u16*)&testData8; //测试数据8
Modbus_HoldReg[8]=(u16*)&FLASH_data[0];//串口波特率
Modbus_HoldReg[9]=(u16*)&FLASH_data[1];//设备地址
//输入寄存器指针指向
Modbus_InputReg[0]=(u16*)&testData1; //测试数据1
Modbus_InputReg[1]=(u16*)&testData2; //测试数据2
Modbus_InputReg[2]=(u16*)&testData3; //测试数据3
Modbus_InputReg[3]=(u16*)&testData4; //测试数据4
Modbus_InputReg[4]=(u16*)&testData5; //测试数据5
Modbus_InputReg[5]=(u16*)&testData6; //测试数据6
Modbus_InputReg[6]=(u16*)&testData7; //测试数据7
Modbus_InputReg[7]=(u16*)&testData8; //测试数据8
}
15、给出main函数:
int main(void)
{SysTick_Init(); // 滴答定时器初始化
SysTick_Delay_Ms(1000);
initTIMx(TIM3,36000-1,4-1,TIM_IT_Update,ENABLE);
SysTick_Delay_Ms(1000);
ModbusInit();
IOInit();
initUART(); // 串口初始化
SysTick_Delay_Ms(1000);
Modbus_RegMap();
while(1)
{
RS485_Service();
// SysTick_Delay_Ms(2);
}
}
16:、给出一些定义:
寄存器,还有形参
u16 FLASH_data[2];
extern u8 RS485_FrameFlag; //帧结束标记
u8 RS485_Addr=3; //从机地址
//u16 RS485_Frame_Distance=4; //数据帧最小间隔(ms),超过此时间则认为是下一帧
extern u8 RS485_RX_BUFF[2048]; //接收缓冲区2048字节
extern u16 RS485_RX_CNT; //接收计数器
u8 RS485_TX_BUFF[2048]; //发送缓冲区
u16 RS485_TX_CNT=0; //发送计数器
//RS485服务程序,用于处理接收到的数据(请在主函数中循环调用)
u16 startRegAddr;
u16 RegNum;
u16 calCRC;
u8 RS485_RX_BUFF[2048]; //接收缓冲区2048字节
u16 RS485_RX_CNT=0; //接收计数器vu32 *Modbus_InputIO[100]; //输入开关量寄存器指针(这里使用的是"位带"操作)
vu32 *Modbus_OutputIO[100]; //输出开关量寄存器指针(这里使用的是"位带"操作)
u16 *Modbus_HoldReg[1000]; //保持寄存器指针
u16 *Modbus_InputReg[1000]; //输入寄存器指针
u32 testData1=100,testData2=200,testData3=300,testData4=400;
u32 testData5=500,testData6=600,testData7=700,testData8=800;
17、保存寄存器,写入和读内部保存寄存器:
附大佬链接:
//读取指定地址的半字(16位数据)
uint16_t FLASH_ReadHalfWord(uint32_t address)
{
return *(__IO uint16_t*)address;
}
//从指定地址开始读取多个数据
void FLASH_ReadMoreData(uint32_t startAddress,uint16_t *readData,uint16_t countToRead)
{
uint16_t dataIndex;
for(dataIndex=0;dataIndex<countToRead;dataIndex++)
{
readData[dataIndex]=FLASH_ReadHalfWord(startAddress+dataIndex*2);
}
}
//读取指定地址的全字(32位数据)
uint32_t FLASH_ReadWord(uint32_t address)
{
uint32_t temp1,temp2;
temp1=*(__IO uint16_t*)address;
temp2=*(__IO uint16_t*)(address+2);
return (temp2<<16)+temp1;
}
//从指定地址开始写入多个数据
void FLASH_WriteMoreData(uint32_t startAddress,uint16_t *writeData,uint16_t countToWrite)
{
if(startAddress<FLASH_BASE||((startAddress+countToWrite*2)>=(FLASH_BASE+1024*FLASH_SIZE)))
{
return;//非法地址
}
FLASH_Unlock(); //解锁写保护
uint32_t offsetAddress=startAddress-FLASH_BASE; //计算去掉0X08000000后的实际偏移地址
uint32_t sectorPosition=offsetAddress/SECTOR_SIZE; //计算扇区地址,对于STM32F103VET6为0~255
uint32_t sectorStartAddress=sectorPosition*SECTOR_SIZE+FLASH_BASE; //对应扇区的首地址
FLASH_ErasePage(sectorStartAddress);//擦除这个扇区
uint16_t dataIndex;
for(dataIndex=0;dataIndex<countToWrite;dataIndex++)
{
FLASH_ProgramHalfWord(startAddress+dataIndex*2,writeData[dataIndex]);
}
FLASH_Lock();//上锁写保护
}
//————————————————
//版权声明:本文为CSDN博主「豪哥追求卓越」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
//原文链接:https://blog.csdn.net/weixin_41542513/article/details/94356514