移植MODBUSRTU协议

一、从之前的公司看到过一种工业控制化方式,类似于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进制字节自己计算。然后和自己程序验证的结果对比就知道了:

http://www.ip33.com/crc.html

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

  • 6
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值