STM32F407的Modbus做为主站与从站通讯

在调试STM32F407的串口Modbus通讯之前,也使用过Modbus通讯,只不过都是在PLC或则昆仑通态的触摸屏上使用直接调用现成的库里面的模块,驱动就可以,相对于STM32来,使用PLC库里面的模块和触摸屏驱动都是初始化后配参数就可以了,但是用32写的时候比较麻烦了一些。由于STM32没有RS485通讯端口只能用UART端口转成RS485加了一个485芯片,在调试Modbus通信按照plc和触摸屏的经验慢慢琢磨也是挺有意思的。当最后你都调通的时候心里那种成就感是对于一个理工男来说是非常爽的!不啰嗦了下面介绍Modbus通信流程以及功能码:                                                                                

  1、数据目的:读输入继电器    1区      功能码:02        通信格式: 设备地址     功能码    起始地址     读数据位个数     CRC校验

                              例:       01             02         00 00           00 01                  B9 CA

                                                                                                         例 :01 02 00 00 00 01 B9 CA

                           返回数据:01 02 01 01 60 48    //返回01表示继电器1状态

                                01 02 01 00 A1 88    //返回00表示继电器0状态

  2、数据目的:读输出继电器    0区      功能码:01        通信格式: 设备地址     功能码    起始地址     读数据位个数     CRC校验

                                例:       01             01         00 00           00 01                   FD CA

                                                                                                         例:01 01 00 00 00 01 FD CA

                           返回数据:01 01 01 01 90 48    //返回01表示继电器1状态

                               01 01 01 00 51 88    //返回00表示继电器0状态

  3、数据目的:写输出继电器    0区      功能码:05        通信格式: 设备地址     功能码    起始地址       数据                CRC校验

                                例:       01             05         00 00           00 00                 CD CA

                                                                                                         例:01 05 00 00 00 00 CD CA    //写继电器0状态

                                                                                                         例:01 05 00 00 FF FFCC 7A    //写继电器1状态

  4、数据目的:读3区16位寄存器数据   3区      功能码:04        通信格式: 设备地址     功能码    起始地址    数据位数         CRC校验    

                                     例:      01              04         00 01           00 00              60 0A

                                                                                                              例:01 04 00 01 00 01 60 0A    //写继电器0状态

                                          返回数据:01 04 02 00 05 79 33    //返回02带边数据位数 00 05代表返回的数据

  5、数据目的:读4区16位寄存器数据   4区      功能码:03        通信格式: 设备地址     功能码    起始地址    数据位数         CRC校验

                                     例:    01              03         00 01           00 00              D5 CA

                                                                                                                     例:01 03 00 01 00 01 D5 CA    //写继电器0状态

                                                 返回数据:01 03 02 00 05 78 47    //返回02带边数据位数 00 05代表返回的数据

  6、数据目的:写4区16位寄存器数据   4区      功能码:06 /10        通信格式: 设备地址     功能码    起始地址       数据            CRC校验

                                              例:     01              06         00 01           00 00              9C 6B

                                                                                                               例:01 06 00 01 0E 03 9C 6B   //写00 01数据0E 03

啰嗦了半天功能码,接下来写一下Modbus的在STM32中的流程:

  1、发送需要读取从站的地址以及功能码

  2、发送完成后开始200ms定时中断等待从站数据是否返回(定时器中断优先级低于串口中断)若无数据返回重复1步骤

  3、有数据返回开启2ms中断,接收间隔超过2ms代表数据接收完成。

  4、解析数据,返回数据;

 extern u8 RS485_BUFF[100];
 u8 Rcv_Len;
 /* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
extern u8 Uart0_send_buff[]; 
extern u8 Uart0_rev_buff[];
extern vs8 Uart0_send_counter;
extern vu8 Uart0_rev_count;//com0串口接收计数器
extern vu8 Uart0_rev_comflag;
extern vu8 Crc_counter;//com0校验计数器
extern vu8 *Uart0_send_pointer ;//com0串口发送指针
extern vu8 Uart_send_flag;//从机响应超时标志

/*******************************************************************************
* 函 数 名         : RS485_Init
* 函数功能           : USART2初始化函数
* 输    入         : bound:波特率
* 输    出         : 无
*******************************************************************************/  
void RS485_Init(u32 bound)
{
    GPIO_InitTypeDef GPIO_InitStructure;       
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOG,ENABLE); //使能GPIOAG时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//使能USART2时钟
    
    //串口2引脚复用映射
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_USART2); //GPIOA2复用为USART2
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_USART2); //GPIOA3复用为USART2
    
    
    //USART2    
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3; //GPIOA2与GPIOA3
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;    //速度100MHz
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
    GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA2,PA3
    
    
    //PG8推挽输出,485模式控制  
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //GPIOG8
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;    //速度100MHz
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
    GPIO_Init(GPIOG,&GPIO_InitStructure); //初始化PG8
    
    //USART2 初始化设置
    USART_InitStructure.USART_BaudRate = bound;//波特率设置
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
    USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
    USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;    //收发模式
    USART_Init(USART2, &USART_InitStructure); //初始化串口2
    
    USART_Cmd(USART2, ENABLE);  //使能串口 2
    
    USART_ClearFlag(USART2, USART_FLAG_TC);
        
    USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启接受中断

    //Usart2 NVIC 配置
    NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;//抢占优先级3
    NVIC_InitStructure.NVIC_IRQChannelSubPriority =1;        //子优先级2
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;            //IRQ通道使能
    NVIC_Init(&NVIC_InitStructure);    //根据指定的参数初始化VIC寄存器、
    
    RS485_TX_EN=0;                //默认为接收模式    
}
/*******************************************************************************
* 函 数 名         : USART2_IRQHandler
* 函数功能             : USART2中断函数
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/ 
void USART2_IRQHandler(void)
{
    if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
    {
    
        if(Uart_send_flag==1)
        {
            Uart_send_flag=0;//标志没有超时
            TIM_Cmd(TIM2, DISABLE);
            TIM_DeInit( TIM2);//复位TIM2定时器
        }
        if(Uart0_rev_comflag != 1)
        {
            if(Uart0_rev_count < 100)
            {
                Uart0_rev_buff[Uart0_rev_count++]= USART_ReceiveData(USART2);
                         TIM_DeInit( TIM2);//复位TIM2定时器
             TIM2_Init(2,8400);
            }
            else
            {
                USART_ReceiveData(USART2);//如果接收计数器大于100,放弃接收的数据,防止同总线上有其它数据长大于100的信息导致本接收缓存溢出

            } 
        }    
        else
        {
            USART_ReceiveData(USART2);//如果接收没有处理,放弃接收的数据
        }
      }  
      if(USART_GetITStatus(USART2, USART_IT_TXE) != RESET)
    {   
        
           Uart0_send_counter--; 
        if(Uart0_send_counter>0) 
        {
      /* Write one byte to the transmit data register */
      USART_SendData(USART2, *Uart0_send_pointer++);
                
        }
        else
        {     
        delay_ms(1);
                RS485_TX_EN=0;
        //    s8 i ;
        USART_ITConfig(USART2, USART_IT_TXE, DISABLE);
           Uart_send_flag = 1;// 数据发送完成
              TIM_DeInit( TIM2);//复位TIM2定时器
        TIM2_Init(20,8400);

        }
             
      }
    
}
extern u8 RS485_BUFF[100];     //UART2  外设485数据缓存区
extern u8 mag_nav_cmd[50];     //磁导航modbus指令
extern u8 inertia_cmd[50];     //惯性导航modbus指令
extern u8 readrfid_cmd[50];    //读取ID卡modbus指令

extern u8 Uart0_send_buff[]; 
extern u8 Uart0_rev_buff[];
extern vs8 Uart0_send_counter;
extern vu8 Uart0_rev_count;//com0串口接收计数器
extern vu8 Uart0_rev_comflag;
extern vu8 Crc_counter;//com0校验计数器
extern vu8 *Uart0_send_pointer ;//com0串口发送指针
extern vu8 Uart_send_flag;//从机响应超时标志
void Modbus_Function_3(uint8_t device_id,uint8_t reg_H_addr,uint8_t reg_L_addr,uint8_t red_num_H,uint8_t red_num_L); 
u8 time_state;
extern u8 sent_data[20];
/*******************************************************************************
* 函 数 名         : TIM2_Init
* 函数功能             : TIM2初始化函数
* 输    入         : per:重装载值
                               psc:分频系数
* 输    出         : 无
*******************************************************************************/
void TIM2_Init(u32 per,u16 psc)
{
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;   //TIM初始化结构体
    NVIC_InitTypeDef NVIC_InitStructure;                 //优先级结构体
       //APB1  42M                        //APB1_TIM2 84M  
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);  //使能TIM2时钟
    
    TIM_TimeBaseInitStructure.TIM_Period=(per-1)*10;     //自动装载症////
    TIM_TimeBaseInitStructure.TIM_Prescaler=psc-1;   //分频系数////分频系数和计数值决定定时器值
    TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;  
    TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //设置向上计数模式
    TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);   // 初始化定时器TIM2
    
    TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //开启定时器中断
    TIM_ClearITPendingBit(TIM2,TIM_IT_Update);   //清楚定时器标志位
    
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;  //定时器中断通道
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;//抢占优先级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority =2;        //子优先级
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;            //IRQ通道使能
    NVIC_Init(&NVIC_InitStructure);    
    
    TIM_Cmd(TIM2,ENABLE); //使能定时器    
}

/*******************************************************************************
* 函 数 名         : TIM2_IRQHandler
* 函数功能             : TIM2中断函数
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/
void TIM2_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
    {    
      u8 i;
        
        /* TIM IT DISABLE [使能TIM2溢出中断]*/
    TIM_ITConfig(TIM2, TIM_IT_Update, DISABLE);
        /* TIM2 DISABLE counter */
        TIM_Cmd(TIM2, DISABLE);
        TIM_DeInit( TIM2);//复位TIM2定时器
        if(Uart_send_flag==1)
            {
                Uart_send_flag=3;//说明超时
            }
        else
        {
              Uart0_rev_comflag = 1;//接收完成
            for(i=0;i<Uart0_rev_count+1;i++)
            {
              RS485_BUFF[i]= Uart0_rev_buff[i];
            }
            Crc_counter = Uart0_rev_count;//crc校验计数器赋值
            Uart0_rev_count = 0;//接收计数器清零
        }
    }    
    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}  
原文地址:https://www.cnblogs.com/qilai/p/10601027.html