前言
RS485使用modbus协议通讯实例演示,参考正点原子。
一、main.c
#include "led.h" #include "delay.h" #include "key.h" #include "sys.h" #include "lcd.h" #include "usart.h" #include "rs485.h" //Modbus寄存器和单片机寄存器的映射关系 vu32 *Modbus_InputIO[100];//输入开关量寄存器指针(这里使用的是位操作) vu32 *Modbus_OutputIO[100];//输出开关量寄存器指针(这里使用的是位操作) /*********************************************** 函数功能:配置io输出口 *************************************************/ void DO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE|RCC_APB2Periph_GPIOC, ENABLE ); //使能GPIOE 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; //PE8-PE15 作为推挽输出引脚 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz GPIO_Init(GPIOE, &GPIO_InitStructure); //初始化GPIOE GPIO_ResetBits(GPIOE,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_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_13; // GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz GPIO_Init(GPIOC, &GPIO_InitStructure); //根据设定参数初始化GPIOD.5 GPIO_ResetBits(GPIOC,GPIO_Pin_8|GPIO_Pin_13); //PC.10 输出高 } //CRC校验 const u8 auchCRCHi[] = { 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} ; const u8 auchCRCLo[] = { 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} ; / /************************************************************** 函数功能:生成CRC校验码 **************************************************************/ u16 CRC_Compute(u8 *puchMsg, u16 usDataLen) { u8 uchCRCHi = 0xFF ; u8 uchCRCLo = 0xFF ; u32 uIndex ; while (usDataLen--) { uIndex = uchCRCHi ^ *puchMsg++ ; uchCRCHi = uchCRCLo ^ auchCRCHi[uIndex] ; uchCRCLo = auchCRCLo[uIndex] ; } return ((uchCRCHi<< 8) | (uchCRCLo)) ; } u16 RegNum; u8 RS485_TX_BUFF[10];//发送缓冲区 u8 RS485_RX_BUFF[10];//接收缓冲区 u16 calCRC; u16 startRegAddr; //寄存器起始地址 u16 recCRC; 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=CRC_Compute(RS485_TX_BUFF,6); RS485_TX_BUFF[6]=calCRC&0xFF; RS485_TX_BUFF[7]=(calCRC>>8)&0xFF; RS485_Send_Data(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_Send_Data(RS485_TX_BUFF,3); } } //Modbus功能码15处理函数 void Modbus_15_Solve(void) { DO_Init(); RegNum=(((u16)RS485_RX_BUFF[4])<<8)|RS485_RX_BUFF[5];//获取寄存器数量 if((startRegAddr+RegNum)<100)//寄存器地址+数量在范围内 { if(RS485_RX_BUFF[6]==0x01) { switch(RS485_RX_BUFF[7]) { case 0x01: { GPIO_ResetBits(GPIOE,GPIO_Pin_8); delay_ms(100); //开机功能 GPIO_SetBits(GPIOE,GPIO_Pin_8); break; } case 0x02: { GPIO_ResetBits(GPIOE,GPIO_Pin_10); delay_ms(100); //关机 GPIO_SetBits(GPIOE,GPIO_Pin_10); break; } case 0x04: { GPIO_ResetBits(GPIOE,GPIO_Pin_9); delay_ms(100); //激励封锁 GPIO_SetBits(GPIOE,GPIO_Pin_9); break; } case 0x08: { GPIO_ResetBits(GPIOE,GPIO_Pin_11); delay_ms(500); //复位 GPIO_SetBits(GPIOE,GPIO_Pin_11); break; } } } else { RS485_TX_BUFF[0]=RS485_RX_BUFF[0]; RS485_TX_BUFF[1]=RS485_RX_BUFF[1]|0x80; RS485_TX_BUFF[2]=0x02; //异常码 RS485_Send_Data(RS485_TX_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=CRC_Compute(RS485_TX_BUFF,6); RS485_TX_BUFF[6]=calCRC&0xFF; RS485_TX_BUFF[7]=(calCRC>>8)&0xFF; RS485_Send_Data(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_Send_Data(RS485_TX_BUFF,3); } } int main(void) { u8 key; u8 i=0; u8 rs485buf[10]; delay_init(); //延时函数初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级 uart_init(115200); //串口初始化为115200 LED_Init(); //初始化与LED连接的硬件接口 LCD_Init(); //初始化LCD KEY_Init(); //按键初始化 RS485_Init(9600); //初始化RS485串口波特率9600 while(1) { RS485_Receive_Data(rs485buf,&key); if(key)//接收到有数据 { if(key>10)key=10;//最大是10个数据. for(i=0;i<key;i++) { RS485_RX_BUFF[i]=rs485buf[i]; } if(RS485_RX_BUFF[0]==0x01)//地址正确 { if((RS485_RX_BUFF[1]==00)||(RS485_RX_BUFF[1]==02)||(RS485_RX_BUFF[1]==0x03)||(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=CRC_Compute(RS485_RX_BUFF,8);//计算所接收到数据的CRC recCRC=RS485_RX_BUFF[9]|(((u16)RS485_RX_BUFF[8])<<8);//????CRC(?????,?????) if(calCRC==recCRC)//CRC正确 { / switch(RS485_RX_BUFF[1])//判断功能码 { case 0x05://写单个输出开关量 { Modbus_05_Solve(); break; } case 0x15://写多个输出开关量 { Modbus_15_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_Send_Data(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_Send_Data(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_Send_Data(RS485_TX_BUFF,3); } } RS485_RX_CNT=0;//清零 RS485_TX_EN=0;//开始接收 } } }
二、rs485.c
#include "sys.h" #include "rs485.h" #include "delay.h" // //RS485驱动 代码 //All rights reserved // #ifdef EN_UART4_RX //如果使能了接收 //接收缓存区 u8 RS485_RX_BUF[64]; //接收缓冲,最大64个字节. //接收到的数据长度 u8 RS485_RX_CNT=0; void UART4_IRQHandler(void) { u8 res; if(USART_GetITStatus(UART4, USART_IT_RXNE) != RESET) //接收到数据 { res =USART_ReceiveData(UART4); //读取接收到的数据 if(RS485_RX_CNT<64) { RS485_RX_BUF[RS485_RX_CNT]=res; //记录接收到的值 RS485_RX_CNT++; //接收数据增加1 } } } #endif //初始化IO 串口2 //pclk1:PCLK1时钟频率(Mhz) //bound:波特率 void RS485_Init(u32 bound) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);//使能GPIOC时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART4,ENABLE);//使能UART4时钟 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //Pc9端口配置 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOC, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //PC10 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽 GPIO_Init(GPIOC, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;//PC11 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入 GPIO_Init(GPIOC, &GPIO_InitStructure); RCC_APB1PeriphResetCmd(RCC_APB1Periph_UART4,ENABLE);//复位串口2 RCC_APB1PeriphResetCmd(RCC_APB1Periph_UART4,DISABLE);//停止复位 #ifdef EN_UART4_RX //如果使能了接收 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(UART4, &USART_InitStructure); ; //初始化串口 NVIC_InitStructure.NVIC_IRQChannel = UART4_IRQn; //使能串口2中断 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3; //先占优先级2级 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级2级 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道 NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器 USART_ITConfig(UART4, USART_IT_RXNE, ENABLE);//开启中断 USART_Cmd(UART4, ENABLE); //使能串口 #endif RS485_TX_EN=0; //默认为接收模式 } //RS485发送len个字节. //buf:发送区首地址 //len:发送的字节数(为了和本代码的接收匹配,这里建议不要超过64个字节) void RS485_Send_Data(u8 *buf,u8 len) { u8 t; RS485_TX_EN=1; //设置为发送模式 for(t=0;t<len;t++) //循环发送数据 { while(USART_GetFlagStatus(UART4, USART_FLAG_TC) == RESET); USART_SendData(UART4,buf[t]); } while(USART_GetFlagStatus(UART4, USART_FLAG_TC) == RESET); RS485_RX_CNT=0; RS485_TX_EN=0; //设置为接收模式 } //RS485查询接收到的数据 //buf:接收缓存首地址 //len:读到的数据长度 void RS485_Receive_Data(u8 *buf,u8 *len) { u8 rxlen=RS485_RX_CNT; u8 i=0; *len=0; //默认为0 delay_ms(10); //等待10ms,连续超过10ms没有接收到一个数据,则认为接收结束 if(rxlen==RS485_RX_CNT&&rxlen)//接收到了数据,且接收完成了 { for(i=0;i<rxlen;i++) { buf[i]=RS485_RX_BUF[i]; } *len=RS485_RX_CNT; //记录本次数据长度 RS485_RX_CNT=0; //清零 } }
三、rs485.h
#ifndef __RS485_H #define __RS485_H #include "sys.h" extern u8 RS485_RX_BUF[64]; //接收缓冲,最大64个字节 extern u8 RS485_RX_CNT; //接收到的数据长度 //模式控制 #define RS485_TX_EN PCout(9) //485模式控制.0,接收;1,发送. //如果想串口中断接收,请不要注释以下宏定义 #define EN_UART4_RX 1 //0,不接收;1,接收. void RS485_Init(u32 bound); void RS485_Send_Data(u8 *buf,u8 len); void RS485_Receive_Data(u8 *buf,u8 *len); #define READ_COIL 01 #define READ_DI 02 #define READ_HLD_REG 03 #define READ_AI 04 #define SET_COIL 05 #define SET_HLD_REG 06 #define WRITE_COIL 15 #define WRITE_HLD_REG 16 void DO_Init(void); void Modbus_01_Solve(void); void Modbus_02_Solve(void); void Modbus_03_Solve(void); void Modbus_05_Solve(void); void Modbus_06_Solve(void); void Modbus_15_Solve(void); void Modbus_16_Solve(void); void RS485_Service(void); void Timer7_Init(void); void RS485_TX_Service(void); void Master_Service(u8 SlaverAddr,u8 Fuction,u16 StartAddr,u16 ValueOrLenth); void RS485_RX_Service(void); void modbus_rtu(void); void modbus_text(void); //空载测试输出函数,注意在实际应用的时候屏蔽掉 u16 Master_ReadReginput(u8 teamcounter); u16 Master_sendReginput(u8 teamcounter); //u16 RS485_RX_BUFFback(u8 count); u16 volback(void); //电压值读回,本函数嵌套位置位于解析函数中; u16 erroeback(void);//报错信息 u16 errpaceback(void);// 接收错误信息 u16 stateback(void);//通讯情况测试 #endif