基于空闲中断的MODBUS通讯协议

一.简介

MODBUS协议,最为一款工业通讯协议,由于其可靠性和免费性,得到了广泛的应用,本文是基于STM8L051F3单片机,利用IAR for STM8,通过UASRT接收中断中的空闲中断,来实现MODBUS的通讯。
(友情提示:在需要用到通讯协议的条件,尽量在预算足够的情况下,选择大FLASH的单片机,以免内存溢出,无法编译通过!!! )

二.干货

1.配置USART_DMA

1.1 USART.h

   	#ifndef _UART_H
	#define _UART_H
	
	  //头文件自行添加
	
	extern unsigned char RxBuffer[] ;    //数据接受的缓存处
	void UART_Init(u32 bound);//串口初始化
	
	#endif

1.2 USART.c

#include "UART.h"

#define USART_DMA_CHANNEL_RX   DMA1_Channel2       
#define USART_DMA_CHANNEL_TX   DMA1_Channel1
#define USART_DMA_FLAG_TCRX    (uint16_t)DMA1_FLAG_TC2
#define USART_DMA_FLAG_TCTX    (uint16_t)DMA1_FLAG_TC1
#define USART_DR_ADDRESS       (uint16_t)0x5231        //USART_RX的地址
/* USART1 Data register Address */
#define DATA_TO_RECEIVE        (uint8_t) 0x0A   //接收数组大小为10
unsigned char RxBuffer[DATA_TO_RECEIVE] ;


void UART_DMA_Config(void)
{
   /* Deinitialize DMA channels */
   //DMA_GlobalDeInit();
 
   DMA_DeInit(DMA1_Channel1);
  //DMA_DeInit(DMA1_Channel2);
   CLK_PeripheralClockConfig(CLK_Peripheral_DMA1, ENABLE);
   /* DMA channel Rx of USART Configuration */
   DMA_Init(USART_DMA_CHANNEL_RX, 
            (uint16_t)RxBuffer, 
            (uint16_t)USART_DR_ADDRESS,
            DATA_TO_RECEIVE, 
            DMA_DIR_PeripheralToMemory, 
            DMA_Mode_Normal,
            DMA_MemoryIncMode_Inc, 
            DMA_Priority_Low, 
            DMA_MemoryDataSize_Byte);
   
   /* DMA channel Tx of USART Configuration */
//   DMA_Init(USART_DMA_CHANNEL_TX, 
//            (uint16_t)TxBuffer, 
//            DMA_DIR_MemoryToPeripheral, 
//            DMA_Mode_Normal,
//            DMA_MemoryIncMode_Inc, 
//            DMA_Priority_High, 
//            DMA_MemoryDataSize_Byte);
 
   /* Enable the USART Tx/Rx DMA requests */
 //  USART_DMACmd(USART1, USART_DMAReq_TX, ENABLE);
    USART_DMACmd(USART1, USART_DMAReq_RX, ENABLE);
 
   /* Global DMA Enable */
   DMA_GlobalCmd(ENABLE);
    
   /* Enable the USART Tx DMA channel */
  // DMA_Cmd(USART_DMA_CHANNEL_TX,DISABLE);
   /* Enable the USART Rx DMA channel */
   DMA_Cmd(USART_DMA_CHANNEL_RX, ENABLE); 
}

void UART_Init(u32 bound)
{
   /* USART configured as follow:
         - BaudRate = USART_BAUDRATE baud  
         - Word Length = 8 Bits
         - One Stop Bit
         - No parity
         - Receive and transmit enabled
         - USART Clock disabled
   */
   
   CLK_PeripheralClockConfig(CLK_Peripheral_USART1, ENABLE);
   
   SYSCFG_REMAPPinConfig(REMAP_Pin_USART1TxRxPortA,ENABLE);  //引脚映射
   
   GPIO_Init(GPIOA, GPIO_Pin_2, GPIO_Mode_Out_PP_High_Fast);//TXD
   GPIO_Init(GPIOA, GPIO_Pin_3, GPIO_Mode_In_PU_No_IT);//RXD
   GPIO_ExternalPullUpConfig(GPIOA, GPIO_Pin_2, ENABLE);
   GPIO_ExternalPullUpConfig(GPIOA, GPIO_Pin_3, ENABLE);


   USART_DeInit(USART1);                //复位UART1
   USART_Init(USART1, (uint32_t)bound, USART_WordLength_8b, USART_StopBits_1,
               USART_Parity_No, (USART_Mode_TypeDef)(USART_Mode_Tx | USART_Mode_Rx));
 
   /* USART DMA */
   UART_DMA_Config();
   
  // while (DMA_GetFlagStatus((DMA_FLAG_TypeDef)USART_DMA_FLAG_TCTX) == RESET);
   //while (DMA_GetFlagStatus((DMA_FLAG_TypeDef)USART_DMA_FLAG_TCRX) == RESET);
   
   USART_ClearFlag(USART1,USART_FLAG_TC);
   USART_ITConfig(USART1,USART_IT_IDLE,ENABLE);
   /* USART Disable */
   USART_Cmd(USART1, ENABLE);  
}

2.编写MODBUS通讯文件

2.1 MODBUS.h

	#ifndef _rs485_H
	#define _rs485_H
	
	 //头文件自行添加

	extern unsigned int  regGroup[4];  //Modbus16位寄存器组
	extern unsigned char flagFrame;    //MODBUS处理标志位
															 
	void RS485_Init(unsigned long int bound);   //RS485通讯初始化
	void UartDriver(void); //串口驱动函数
	unsigned char rs485_UartWrite(unsigned char *buf2 ,unsigned char len2);  //串口发送数据
	static unsigned int GetCRC16(unsigned char *ptr,  unsigned char len);    //CRC校验
	
	#endif

2.2 MODBUS.c

#include "MY_rs485.h"

u8 flagFrame=0;                         //帧接收完成标志,即接收到一帧新数据

#define  MAX_REG         4
unsigned int regGroup[MAX_REG]={1,0,0,0};  //Modbus16位寄存器组,地址为0x00~REGMAX

//RS485初始化
void RS485_Init(u32 bound)
{
  UART_Init(bound);
}

//计算发送的数据长度,并且将数据放到*buf数组中                     
u8 UartRead(u8 *buf, u8 len)  
{
  u8 i;
  for(i=len;i>0;i--)//检测实际接收到的数据长度
  {
    if(RxBuffer[i]!=0x00)
      break;
  }
  len=i+1;       //读取长度设置为实际接收到的数据长度
  for(i=0;i<len;i++)  //拷贝接收到的数据到接收指针中
  {
    *buf=RxBuffer[i];  //将数据复制到buf中
    buf++;
  }
  return len;                   //返回实际读取长度
}

// 发送数据
u8 rs485_UartWrite(u8 *buf ,u8 len)
{
  u8 i=0; 
  Delay(3);                                                               //3MS延时
  for(i=0;i<len;i++)
  {
    USART_SendData8(USART1,buf[i]);	                                  //通过USARTx外设发送单个数据
    while(!USART_GetFlagStatus (USART1,USART_FLAG_TXE));             //检查指定的USART标志位设置与否,发送数据空位标志
  }
  return 1;
}

//放在循环中处理的函数
void UartDriver()   
{
  unsigned char i=0,cnt;
  unsigned int crc;
  unsigned char crch,crcl;
  static u8 len;
  static u8 buf[10];
  char bound_flag=0;
  if(flagFrame)            //帧接收完成标志
  {
    flagFrame=0;           //帧接收完成标志清零
    len = UartRead(buf,sizeof(buf)-1);   //将接收到的命令读到缓冲区中
    if(buf[0]==(unsigned char)(regGroup[0]))                   //判断地址是不是0x01
    {
      crc=GetCRC16(buf,len-2);       //计算CRC校验值
      crch=crc>>8;    				//crc高位
      crcl=crc&0xFF;					//crc低位
      if((buf[len-2]==crch)&&(buf[len-1]==crcl))  //判断CRC校验是否正确
      {
        switch (buf[1]) 
        {
        case 0x03:                      //读多个寄存器  功能码  起始地址 2字节 寄存器数量 2字节 
          if((buf[2]<<8+buf[3])>=0 && (buf[2]<<8+buf[3]) <=MAX_REG )  //寄存器地址支持0x0000~REGMAX  寄存器地址 : (buf[2]*4+buf[3]) 
          {
              i=buf[2]<<8;
              i=i+buf[3];       //提取寄存器地址
              cnt=buf[5];               //提取待读取的寄存器数量
              buf[2]=cnt*2;             //读取数据的字节数,为寄存器*2
              len=3;							
              while(cnt--)
              {
                buf[len++]=regGroup[i]>>8;//寄存器高字节补0
                buf[len++]=regGroup[i++];//低字节
              }
            break;
          }
          else            //寄存器地址不被支持时,返回错误码
          {   
            buf[1]=0x83;  //功能码最高位置1
            buf[2]=0x02;  //设置异常码为02-无效地址
            len=3;
            break;
          }
        case 0x06:                              //写入单个寄存器
          if((buf[2]==0x00)&&(buf[3]<=MAX_REG))   //寄存器地址支持0x0000-REGMAX
          {
            if(buf[3]==1)
            {
              bound_flag=1;
            }
            if(buf[3]<=MAX_REG-1)
            {
              regGroup[buf[3]]=buf[4]*256+buf[5]; //保存寄存器数据
            }
            len -=2;                 //长度-2以重新计算CRC并返回原帧
            break;
          }
          else  
          {			//寄存器地址不被支持,返回错误码
            buf[1]=0x86;           //功能码最高位置1
            buf[2]=0x02;           //设置异常码为02-无效地址
            len=3;
            break;
          }
        default:    //其他不支持的功能码
          buf[1]=0x80;     //功能码最高位置1
          buf[2]=0x01;     //设置异常码为01—无效功能
          len=3;
          break;
        }
        crc=GetCRC16(buf,len);           //计算CRC校验值
        buf[len++]=crc>>8;           //CRC高字节
        buf[len++]=crc&0xff;        //CRC低字节
        rs485_UartWrite(buf,len);  //发送响应帧
        if(bound_flag)
        {
          bound_flag=0;
          UART_Init(regGroup[1]);
        }
      }
    }
  }
}

static unsigned int GetCRC16(unsigned char *ptr,  unsigned char len)
{ 
  u16 index;
  u8 crch = 0xFF;  //高CRC字节
  u8 crcl = 0xFF;  //低CRC字节
  u8  TabH[] = {  //CRC高位字节值表
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,  
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,  
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,  
    0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,  
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,  
    0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,  
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,  
    0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,  
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,  
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,  
    0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,  
    0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,  
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,  
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,  
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,  
    0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,  
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,  
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,  
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,  
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,  
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,  
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,  
    0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,  
    0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,  
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,  
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40  
  } ;  
  u8 TabL[] = {  //CRC低位字节值表
    0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,  
    0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,  
    0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,  
    0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,  
    0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4,  
    0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,  
    0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,  
    0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,  
    0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,  
    0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,  
    0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED,  
    0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,  
    0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,  
    0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,  
    0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,  
    0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,  
    0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,  
    0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,  
    0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,  
    0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,  
    0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,  
    0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,  
    0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,  
    0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,  
    0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,  
    0x43, 0x83, 0x41, 0x81, 0x80, 0x40  
  } ;
  
  while (len--)  //计算指定长度CRC
  {
    index = crch ^ *ptr++;
    crch = crcl ^ TabH[index];
    crcl = TabL[index];
  }
  
  return ((crch<<8) | crcl);  
}

3.其他

1.空闲中断中

	volatile char a;
	if(USART1->SR&0x10)  //判断是否是空闲中断
	{
	  //清除空闲中断标志位
	  a=USART1->SR;
	  a=USART1->DR;
	  
	  //MODBUS处理标志位置1
	  flagFrame=1;  
	  
	   //重新设置接收起始位置,等待接收//
	  DMA1_Channel2->CCR &= 0xfe;   
	  DMA1_Channel2->CNBTR=10;
	  DMA1_Channel2->CCR |= 0x01;
	}

2.mian函数中

void main(void)
{
  /* Infinite loop */
  enableInterrupts();      //开启总中断
  RS485_Init(9600);        //RS485初始化
  while (1)
  {
    UartDriver();          //在空闲时处理主机的指令
  }
}

三.总结

相比于由定时器监控的MODBUS协议,利用STM8L(也可以使用在STM32中)的USART接收空闲中断,思路和内容简单可以节约大概30%的占用内存。
----------------------------------------------------------------------有问题,欢迎在下方评论。

  • 9
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值