MODBUS RTU在STM32单片机上应用

MODBUS RTU在STM32单片机上应用

1.MODBUS RTU协议程序流程图

MODBUS RTU在STM32单片机上应用_第1张图片
       ~~~~~~       有两点需要注意的:1、modbus rtu标准协议规定:每帧数据间隔至少为3.5个字符时间。 2、接收发送数据最好改为中断模式,不然通信速度不够。

2.MODBUS RTU程序编写

CRC校验函数

 /**
 1. 功能:根据ModBus规则计算CRC16
 2. 参数:
 3.       _pBuf:待计算数据缓冲区,计算得到的结果存入_pBuf的最后两字节
 4.       _usLen:待计算数据长度(字节数)
 5. 返回值:16位校验值
*/
static 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;            
} 

定时器程序

用定时器3判断接收空闲时间,当空闲时间大于指定时间,认为一帧结束
       ~~~~~~       在主函数调用这个定时器初始化函数并传进参数,使用定时器为TIM3,进行7200分频(72Mhz),即100us计数溢出一次,选用更新中断,使能跟新中断。

  1. 定时器初始化
/**
 * 功能:初始化定时器
 * 参数:
 *      TIMx:指定待设置的定时器,TIM1-TIM4
 *      prescaler:设置预分频值 0-65535
 *      period:设置中断周期,即设置重装载寄存器的值 0-65535
 *      IT_Source:中断源,比如更新中断,四个通道的输入比较中断,取值查看:TIM_interrupt_sources 
 *      NewState:是否使能IT_Source参数指定的中断,ENABLE,DISENABLE
 * 返回值:None
 */
void initTIMx(TIM_TypeDef* TIMx,u16 prescaler,u16 period,u16 IT_Source,FunctionalState NewState)
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    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;
    }

    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, ENABLE);                                          //开启计数器
    TIM_ITConfig(TIMx,IT_Source,NewState);                          //使能定时器更新中断    
}
  1. 定时器更新中断
/****************************************定时器中断服务函数************************************************/
//用定时器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;//置位帧结束标记
	}
}

串口程序

  1. 串口初始化函数
/**
 1. 功能:初始化UART
 2. 参数:None
 3. 返回值:None
 */
void initUART(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;

    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_Cmd(USART1, ENABLE);                                                     //使能串口外设
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);                                 //使能串口接收中断
}
  1. 串口中断服务函数
/****************************中断服务函数****************************************/
/**
 * 功能:串口1中断服务函数
 * 参数:None
 * 返回值:None
 */
u8 RS485_RX_BUFF[2048];      //接收缓冲区2048字节
u16 RS485_RX_CNT=0;          //接收计数器
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))
     {     
       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);//开始计时
     }
  }    
}

实现NVIC相关函数

/******************************************************************
 * 文件:NVIC.c
 * 功能:实现NVIC相关函数
 * 日期:2018-02-26
 * 作者:zx
 * 版本:Ver.1.0 | 最初版本
 * 
 * Copyright (C) 2018 zx. All rights reserved.
*******************************************************************/
#include "NVIC/NVIC.h"

/**
 * 功能:初始化NVIC,初始化内容包括:分配优先级分组号,分配对应中断优先级
 * 参数:NVIC_PriorityGroup : 设置优先级分组号
 *      参数值:NVIC_PriorityGroup_x
 * 返回值:None
 */
void initNVIC(u32 NVIC_PriorityGroup)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup); //中断分组
    setNVIC(USART1_IRQn,0,1,ENABLE);              //使能串口1总中断
    setNVIC(TIM3_IRQn,0,2,ENABLE);                //使能定时器3总中断
    
}

/**
 * 功能:设置对应外设中断
 * 参数:InterruptNum: 对应中断的中断编号
 *       PreemptionPriority:指定抢占优先级
 *       SubPriority:指定子优先级
 *       NVIC_Sta:使能或者失能中断
 * 返回值:None
 */  
void setNVIC(u8 InterruptNum, u8 PreemptionPriority, u8 SubPriority, FunctionalState NVIC_Sta)
{
    NVIC_InitTypeDef NVIC_InitStructure;                                       //定义NVIC初始化结构体

    NVIC_InitStructure.NVIC_IRQChannel = InterruptNum;                         //指定配置的中断
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = PreemptionPriority; //设置抢占优先级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = SubPriority;               //设置子优先级
    NVIC_InitStructure.NVIC_IRQChannelCmd = NVIC_Sta;                          //使/失能中断
    NVIC_Init(&NVIC_InitStructure);                                            //设置生效
}

FLASH读写相应函数

#include "MCU_FLASH/MCU_FLASH.h"
//从指定地址开始读取多个数据
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);
  }
}

//读取指定地址的半字(16位数据)
uint16_t FLASH_ReadHalfWord(uint32_t address)
{
  return *(__IO uint16_t*)address; 
}

//读取指定地址的全字(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;            //计算扇区地址,对于STM32f103C8T6为0~64
  
  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();//上锁写保护
}

MODBUS协议相关函数

  1. 头文件
/******************************************************************
 * 文件:modbus.h
 * 功能:声明modbus相关函数
 * 日期:2019-6-25
 * 作者:haoge
 * 版本:Ver.1.0 | 最初版本
 * 
 * Copyright (C) 2019 haoge. All rights reserved.
*******************************************************************/
#ifndef __MODBUS_H
#define __MODBUS_H
#include "stm32f10x.h"


//位带操作,实现51类似的GPIO控制功能
//IO口操作宏定义
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum)) 
//IO口地址映射
#define GPIOA_ODR_Addr    (GPIOA_BASE+12) //+12 的原因是ORD地址=基地址+偏移量12
#define GPIOB_ODR_Addr    (GPIOB_BASE+12)  
#define GPIOC_ODR_Addr    (GPIOC_BASE+12) 
#define GPIOD_ODR_Addr    (GPIOD_BASE+12)  
#define GPIOE_ODR_Addr    (GPIOE_BASE+12)  
#define GPIOF_ODR_Addr    (GPIOF_BASE+12)     
#define GPIOG_ODR_Addr    (GPIOG_BASE+12)    
#define GPIOH_ODR_Addr    (GPIOH_BASE+12)     
#define GPIOI_ODR_Addr    (GPIOI_BASE+12)      
#define GPIOA_IDR_Addr    (GPIOA_BASE+8)  //+8 的原因是IRD地址=基地址+偏移量8
#define GPIOB_IDR_Addr    (GPIOB_BASE+8)   
#define GPIOC_IDR_Addr    (GPIOC_BASE+8)   
#define GPIOD_IDR_Addr    (GPIOD_BASE+8) 
#define GPIOE_IDR_Addr    (GPIOE_BASE+8) 
#define GPIOF_IDR_Addr    (GPIOF_BASE+8)  
#define GPIOG_IDR_Addr    (GPIOG_BASE+8)  
#define GPIOH_IDR_Addr    (GPIOH_BASE+8)   
#define GPIOI_IDR_Addr    (GPIOI_BASE+8)   
 
//IO口操作,只对单一的IO口!
//确保n的值小于16!
#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //输出 
#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //输入 
 
#define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  //输出 
#define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)  //输入 
 
#define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)  //输出 
#define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)  //输入 
 
#define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n)  //输出 
#define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n)  //输入 
 
#define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr,n)  //输出 
#define PEin(n)    BIT_ADDR(GPIOE_IDR_Addr,n)  //输入
 
#define PFout(n)   BIT_ADDR(GPIOF_ODR_Addr,n)  //输出 
#define PFin(n)    BIT_ADDR(GPIOF_IDR_Addr,n)  //输入
 
#define PGout(n)   BIT_ADDR(GPIOG_ODR_Addr,n)  //输出 
#define PGin(n)    BIT_ADDR(GPIOG_IDR_Addr,n)  //输入
 
#define PHout(n)   BIT_ADDR(GPIOH_ODR_Addr,n)  //输出 
#define PHin(n)    BIT_ADDR(GPIOH_IDR_Addr,n)  //输入
 
#define PIout(n)   BIT_ADDR(GPIOI_ODR_Addr,n)  //输出 
#define PIin(n)    BIT_ADDR(GPIOI_IDR_Addr,n)  //输入

void Modbus_RegMap(void);
void RS485_Service(void);
void Modbus_01_Solve(void);
void Modbus_02_Solve(void);
void Modbus_03_Solve(void);
void Modbus_04_Solve(void);
void Modbus_05_Solve(void);
void Modbus_06_Solve(void);
void Modbus_15_Solve(void);
void Modbus_16_Solve(void);
extern void RS485_SendData(u8 *buff,u8 len);
#endif

2.Modbus寄存器和STM32单片机寄存器的映射关系函数

extern u16 FLASH_data[2];
//
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;
/**
 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(8);   //KEY0
        Modbus_InputIO[1]=(vu32*)&PBin(10);  //KEY1
        //Modbus_InputIO[2]=(vu32*)&PEin(2); //KEY2
        //Modbus_InputIO[3]=(vu32*)&PAin(0); //KEY3
        //输出开关量寄存器指针指向
        Modbus_OutputIO[0]=(vu32*)&PBout(1);  //LED0
        Modbus_OutputIO[1]=(vu32*)&PBout(11); //LED1
        Modbus_OutputIO[2]=(vu32*)&PBout(14); //LED2
        Modbus_OutputIO[3]=(vu32*)&PBout(15); //LED3

        //保持寄存器指针指向
        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
}
  1. MODBUS_RTU服务函数
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;
/**
 1. 功能:ModBus服务函数
 2. 参数:无
 3. 返回值:无
*/
void RS485_Service(void)
{      
        RS485_Addr=(u8)FLASH_data[1];//读取FLASH里设备地址
        u16 recCRC;
        if(RS485_FrameFlag==1)
        {
                if(RS485_RX_BUFF[0]==RS485_Addr)//地址正确
                {							
                  if((RS485_RX_BUFF[1]==01)||(RS485_RX_BUFF[1]==02)||(RS485_RX_BUFF[1]==03)||(RS485_RX_BUFF[1]==04)||(RS485_RX_BUFF[1]==05)||(RS485_RX_BUFF[1]==06)||(RS485_RX_BUFF[1]==15)||(RS485_RX_BUFF[1]==16))//功能码正确
                  {
                                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校验正确
                                        {
                                          //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);//默认接收状态
        }                
}
  1. MODBUS_RTU功能码函数
    Modbus功能码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);
        }
}

Modbus功能码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);
        }
}

Modbus功能码03处理程序

//Modbus功能码03处理程序
//读保持寄存器
void Modbus_03_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);
        }
}

Modbus功能码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);
        }
}

Modbus功能码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;

                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);
        }
}

Modbus功能码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);

}

Modbus功能码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);
        }
}

Modbus功能码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);
        }
}

你可能感兴趣的:(STM32单片机开发,MODBUS_RTU)