MODBUS协议,最为一款工业通讯协议,由于其可靠性和免费性,得到了广泛的应用,本文是基于STM8L051F3单片机,利用IAR for STM8,通过UASRT接收中断中的空闲中断,来实现MODBUS的通讯。
(友情提示:在需要用到通讯协议的条件,尽量在预算足够的情况下,选择大FLASH的单片机,以免内存溢出,无法编译通过!!! )
#ifndef _UART_H
#define _UART_H
//头文件自行添加
extern unsigned char RxBuffer[] ; //数据接受的缓存处
void UART_Init(u32 bound);//串口初始化
#endif
#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);
}
#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
#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);
}
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;
}
void main(void)
{
/* Infinite loop */
enableInterrupts(); //开启总中断
RS485_Init(9600); //RS485初始化
while (1)
{
UartDriver(); //在空闲时处理主机的指令
}
}
相比于由定时器监控的MODBUS协议,利用STM8L(也可以使用在STM32中)的USART接收空闲中断,思路和内容简单可以节约大概30%的占用内存。
----------------------------------------------------------------------有问题,欢迎在下方评论。