Modbus 一個工業上常用的通訊協議、一種通訊約定。Modbus協議包括RTU、ASCII、TCP。其中MODBUS-RTU最常用,比較簡單,在單片機上很容易實現。modbus協議簡單解析如下:
1、主機對從機寫數據操作
如果單片機接收到一個報文那么就對報文進行解析執行相應的處理,如上面報文:
01 06 00 01 00 17 98 04
從機地址 功能號 數據地址 數據 CRC校驗
假如本機地址是 1 ,那么單片機接收到這串數據根據數據計算CRC校驗判斷數據是否正確,如果判斷數據無誤,則結果是:
HoldDataReg[1] = 0x0017;
MODBUS主機就完成了一次對從機數據的寫操作,實現了通訊。
2、主機對從機讀數據操作
主機進行讀HoldDataReg[1] 操作,則報文是:
01 03 00 01 00 01 D5 CA
從機地址 功能號 數據地址 讀取數據個數 CRC校驗
那么單片機接收到這串數據根據數據計算CRC校驗判斷數據是否正確,如果判斷數據無誤,則結果是:返回信息給主機,返回的信息也是有格式的:
返回內容:
01 03 02 0017 F8 4A
從機地址 功能號 數據字節個數 兩個字節數據 CRC校驗
MODBUS主機就完成了一次對從機數據的讀操作,實現了通訊。
上傳的程序是根據手把手教你51單片機的例程修改而來,能實現讀/寫操作(03,06碼),成功通過modbus調試精靈測試,讀寫通訊正常!
由于剛接觸modbus通訊協議,之前一直出現通訊超時的問題,找了網上很多資料,都不得其解,所以把程序拿出來和大家分享。希望和大家相互學習、共同進步!
單片機源程序如下:
- #include "rs485.h"
- #include "SysTick.h"
- #include "crc16.h"
- #include "led.h"
- /*******************************************************************************
- * 函 數 名 : RS485_Init
- * 函數功能 : USART2初始化函數
- * 輸 入 : bound:波特率
- * 輸 出 : 無
- *******************************************************************************/
- u8 USART2_RX_BUF[64]; //接收緩沖,最大64字節
- u8 USART2_RX_CNT=0; //接收字節計數器
- u8 flagFrame=0; //幀接收完成標志,即接收到一幀新數據
- unsigned char regGroup[5]; //Modbus寄存器組,地址為0x00~0x04
- void RS485_Init(u32 bound)
- {
- GPIO_InitTypeDef GPIO_InitStructure;
- USART_InitTypeDef USART_InitStructure;
- NVIC_InitTypeDef NVIC_InitStructure;
-
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG|RCC_APB2Periph_GPIOA,ENABLE); //使能GPIOA\G時鐘
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//使能USART2時鐘
-
- /* 配置GPIO的模式和IO口 */
- GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2; //TX-485 //串口輸出PA2
- GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //復用推挽輸出
- GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
- GPIO_Init(GPIOA,&GPIO_InitStructure); /* 初始化串口輸入IO */
-
- GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3; //RX-485 //串口輸入PA3
- GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING; //模擬輸入
- GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
- GPIO_Init(GPIOA,&GPIO_InitStructure);
-
- GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3; //CS-485
- GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; //推挽輸出
- GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
- GPIO_Init(GPIOG,&GPIO_InitStructure);
-
- //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=2;//搶占優先級3
- NVIC_InitStructure.NVIC_IRQChannelSubPriority =2; //子優先級2
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
- NVIC_Init(&NVIC_InitStructure); //根據指定的參數初始化VIC寄存器、
-
- RS485_TX_EN=0; //默認為接收模式
- }
-
- //1ms定時
- void TIM2_Init()
- {
- TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
- NVIC_InitTypeDef NVIC_InitStructure;
-
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//使能TIM4時鐘
-
- TIM_TimeBaseInitStructure.TIM_Period=1000; //自動裝載值
- TIM_TimeBaseInitStructure.TIM_Prescaler=72-1; //分頻系數
- TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
- TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //設置向上計數模式
- TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
-
- TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //開啟定時器中斷
- TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
-
- NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;//定時器中斷通道
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//搶占優先級
- NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子優先級
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
- NVIC_Init(&NVIC_InitStructure);
-
- TIM_Cmd(TIM2,ENABLE); //使能定時器
- }
- //計算發送的數據長度,并且將數據放到*buf數組中
- u8 UartRead(u8 *buf, u8 len)
- {
- u8 i;
- if(len>USART2_RX_CNT) //指定讀取長度大于實際接收到的數據長度時
- {
- len=USART2_RX_CNT; //讀取長度設置為實際接收到的數據長度
- }
- for(i=0;i<len;i++) //拷貝接收到的數據到接收指針中
- {
- *buf=USART2_RX_BUF[i]; //將數據復制到buf中
- buf++;
- }
- USART2_RX_CNT=0; //接收計數器清零
- return len; //返回實際讀取長度
- }
- u8 rs485_UartWrite(u8 *buf ,u8 len) //發送
- {
- u8 i=0;
- GPIO_SetBits(GPIOG,GPIO_Pin_3); //發送模式
- delay_ms(3); //3MS延時
- for(i=0;i<=len;i++)
- {
- USART_SendData(USART2,buf[i]); //通過USARTx外設發送單個數據
- while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET); //檢查指定的USART標志位設置與否,發送數據空位標志
- }
- GPIO_ResetBits(GPIOG,GPIO_Pin_3); //設置為接收模式
- }
- //串口驅動函數,檢測數據幀的接收,調度功能函數,需在主循環中調用
- void UartDriver()
- {
- unsigned char i=0,cnt;
- unsigned int crc;
- unsigned char crch,crcl;
- static u8 len;
- static u8 buf[60];
- if(flagFrame) //幀接收完成標志,即接收到一幀新數據
- {
- flagFrame=0; //幀接收完成標志清零
- len = UartRead(buf,sizeof(buf)); //將接收到的命令讀到緩沖區中
- if(buf[0]==0x01) //判斷地址是不是0x01
- {
- crc=GetCRC16(buf,len-2); //計算CRC校驗值,出去CRC校驗值
- crch=crc>>8; //crc高位
- crcl=crc&0xFF; //crc低位
- if((buf[len-2]==crch)&&(buf[len-1]==crcl)) //判斷CRC校驗是否正確
- {
- switch (buf[1]) //按功能碼執行操作
- {
- case 0x03: //讀數據
- if((buf[2]==0x00)&&(buf[3]<=0x05)) //寄存器地址支持0x0000~0x0005
- {
-
- if(buf[3]<=0x04)
- {
- i=buf[3];//提取寄存器地址
- cnt=buf[5]; //提取待讀取的寄存器數量
- buf[2]=cnt*2; //讀取數據的字節數,為寄存器*2,因modbus定義的寄存器為16位
- len=3;
- while(cnt--)
- {
- buf[len++]=0x00; //寄存器高字節補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]<=0x05)) //寄存器地址支持0x0000-0x0005
- {
- if(buf[3]<=0x04)
- {
- i=buf[3]; //提取寄存器地址
- regGroup[i]=buf[5]; //保存寄存器數據
- led3=0;
- }
- 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); //發送響應幀
- }
- }
- }
- }
-
- void UartRxMonitor(u8 ms) //串口接收監控
- {
- static u8 USART2_RX_BKP=0; //定義USART2_RC_BKP暫時存儲詩句長度與實際長度比較
- static u8 idletmr=0; //定義監控時間
- if(USART2_RX_CNT>0)//接收計數器大于零時,監控總線空閑時間
- {
- if(USART2_RX_BKP!=USART2_RX_CNT) //接收計數器改變,即剛接收到數據時,清零空閑計時
- {
- USART2_RX_BKP=USART2_RX_CNT; //賦值操作,將實際長度給USART2_RX_BKP
- idletmr=0; //將監控時間清零
- }
- else ////接收計數器未改變,即總線空閑時,累計空閑時間
- {
- //如果在一幀數據完成之前有超過3.5個字節時間的停頓,接收設備將刷新當前的消息并假定下一個字節是一個新的數據幀的開始
- if(idletmr<5) //空閑時間小于1ms時,持續累加
- {
- idletmr +=ms;
- if(idletmr>=5) //空閑時間達到1ms時,即判定為1幀接收完畢
- {
- flagFrame=1;//設置命令到達標志,幀接收完畢標志
- }
- }
- }
- }
- else
- {
- USART2_RX_BKP=0;
- }
- }
-
-
-
-
-
- /*******************************************************************************
- * 函 數 名 : USART2_IRQHandler
- * 函數功能 : USART2中斷函數
- * 輸 入 : 無
- * 輸 出 : 無
- *******************************************************************************/
- void USART2_IRQHandler(void)
- ……………………
- …………限于本文篇幅 余下代碼請從51黑下載附件…………
復制代碼
所有資料51hei提供下載:
29. RS485通信實驗.rar
(273.59 KB, 下載次數: 4563)
2018-10-16 13:44 上傳
點擊文件名下載附件
|