眾所周知,ModBus從機很好實現,而主機就稍微麻煩一點。下面我將介紹這幾年來我用到的ModBus主機方案,既作為分享又作為一個記錄與總結。
談到ModBus就不得不說UART,UART作為ModBus協議的承載是整個ModBus通信的基礎。
UART的基本收發功能通過文件“UartDebug.c”和“UartDebug.h”來實現,首先來看“UartDebug.h”文件的內容。
- #ifndef __UartDebug_H
- #define __UartDebug_H
- #include "Header.h"
- #include "usart.h"
- #define BUFMAX 32
- #define SLIENTTIME 5
- #define RE_DE1 PCout(1)
- #define RE_DE2 PFout(8)
- struct UartDebugMember
- {
- void (*REDE)(uint8_t a);
- UART_HandleTypeDef *Uart;
- uint16_t *UartData;
- uint8_t TransmitBuf[BUFMAX+1];
- uint8_t ReceiveBuf[BUFMAX+1];
- uint8_t RecPointClearEn;
- uint8_t ReceivePoint;
- uint8_t DataTimeCount;
- uint8_t DataTimeCountEn;
- uint8_t ReceiveFinish;
- };
- extern struct UartDebugMember U_D_Uart2,U_D_Uart3,U_D_Uart4,U_D_Uart7;
- void UartDebugInit(void);
- void DataReceive(struct UartDebugMember *UDM);
- void TimeCountReceive(struct UartDebugMember *UDM);
- void ClearRxData(struct UartDebugMember *UDM);
- void TransmitData(struct UartDebugMember *UDM,unsigned char *Buf,unsigned char Length);
- void SendString(struct UartDebugMember *UDM,char *String);
- void RS485_REDE_1(uint8_t a);
- void RS485_REDE_2(uint8_t a);
- void Null(uint8_t a);
- #endif
復制代碼
"Header.h"文件包含了基本的單片機信息,移植代碼的時候只需要將相應的頭文件替換掉就可以了,本章最后會貼出"Header.h"的具體內容。
“#define BUFMAX 32” 設置收發緩沖區的大小;
“#define SLIENTTIME 5” 總線靜默時間閾值,用于判斷該幀數據是否接收完畢;
“#define RE_DE1 PCout(1)”和“#define RE_DE2 PFout(8)”為RS485芯片收發控制IO;
下面將介紹“struct UartDebugMember”結構體成員
“void (*REDE)(uint8_t a);” 該函數指針為RS485芯片收發控制函數;
“UART_HandleTypeDef *Uart;” 使用的UART端口,該成員涉及到底層若更換其他MCU或者使用其他庫函數需要作出相應修改;
“uint16_t *UartData;” 串口接收到的一個字節;
“uint8_t TransmitBuf[BUFMAX+1];” 發送緩沖區;
“uint8_t ReceiveBuf[BUFMAX+1];” 接收緩沖區;
“uint8_t RecPointClearEn;” 接收字節數清零使能;
“uint8_t ReceivePoint;” 接收字節數;
“uint8_t DataTimeCount;” 當前的總線靜默時間
“uint8_t DataTimeCountEn;” 當前的總線靜默時間計時使能
“uint8_t ReceiveFinish;” 幀數據接收完成標志
關于函數我們將在下面的"UartDebug.c"中進行介紹。
- #include "UartDebug.h"
- struct UartDebugMember U_D_Uart2,U_D_Uart3,U_D_Uart4,U_D_Uart7;
- void UartDebugInit(void)
- {
- U_D_Uart2.Uart = &huart2;
- U_D_Uart2.UartData = &Rdata_UART2;
- U_D_Uart2.REDE = Null;
- HAL_UART_Receive_IT(U_D_Uart2.Uart,(uint8_t *)U_D_Uart2.UartData,1);
-
- U_D_Uart3.Uart = &huart3;
- U_D_Uart3.UartData = &Rdata_UART3;
- U_D_Uart3.REDE = Null;
- HAL_UART_Receive_IT(U_D_Uart3.Uart,(uint8_t *)U_D_Uart3.UartData,1);
-
- U_D_Uart4.Uart = &huart4;
- U_D_Uart4.UartData = &Rdata_UART4;
- U_D_Uart4.REDE = RS485_REDE_1;
- HAL_UART_Receive_IT(U_D_Uart4.Uart,(uint8_t *)U_D_Uart4.UartData,1);
-
- U_D_Uart7.Uart = &huart7;
- U_D_Uart7.UartData = &Rdata_UART7;
- U_D_Uart7.REDE = RS485_REDE_2;
- HAL_UART_Receive_IT(U_D_Uart7.Uart,(uint8_t *)U_D_Uart7.UartData,1);
- }
- /*******************************************************************************
- *Function Name : DataReceive
- *Input :
- *Return :
- *Description : 串口接收數據
- *******************************************************************************/
- void DataReceive(struct UartDebugMember *UDM)
- {
- if(UDM->RecPointClearEn)
- {
- UDM->ReceivePoint=0;
- UDM->RecPointClearEn=0;
- }
- if(UDM->ReceivePoint<=BUFMAX)
- {
- UDM->ReceiveBuf[UDM->ReceivePoint]=*(uint8_t *)UDM->UartData;
- UDM->ReceivePoint++;
- }
- UDM->DataTimeCount=0;
- UDM->DataTimeCountEn=1;
- HAL_UART_Receive_IT(UDM->Uart,(uint8_t *)UDM->UartData,1);
- }
- /*******************************************************************************
- *Function Name : TimeCountReceive
- *Input :
- *Return :
- *Description : 接收計時
- *******************************************************************************/
- void TimeCountReceive(struct UartDebugMember *UDM)
- {
- if(!UDM->DataTimeCountEn)
- {
- UDM->DataTimeCount=0;
- }
- /*需要根據波特率以及幀與幀之間的間隔時間調整觸發時間*/
- else if(UDM->DataTimeCount > SLIENTTIME)
- {
- UDM->ReceiveFinish=1;
- UDM->DataTimeCountEn=0;
- }
- else
- {
- UDM->DataTimeCount++;
- }
- }
- /*******************************************************************************
- *Function Name : UartClearBuffer
- *Input :
- *Return :
- *Description : 清除接收緩沖區
- *******************************************************************************/
- void ClearRxData(struct UartDebugMember *UDM)
- {
- UDM->ReceivePoint=0;
- UDM->RecPointClearEn=1;
- UDM->ReceiveFinish=0;
- }
- /*******************************************************************************
- *Function Name : TransmitData
- *Input :
- *Return :
- *Description : 串口發送一幀數據
- *******************************************************************************/
- void TransmitData(struct UartDebugMember *UDM,unsigned char *Buf,unsigned char Length)
- {
- unsigned char i;
- UDM->REDE(1);
- for(i=0;i<Length;i++)
- {
- HAL_UART_Transmit(UDM->Uart,&Buf[i],1,1);
- }
- UDM->REDE(0);
- HAL_UART_Receive_IT(UDM->Uart,(uint8_t *)UDM->UartData,1);
- }
- /*******************************************************************************
- *Function Name : SendString
- *Input :
- *Return :
- *Description : 串口發送字符串
- *******************************************************************************/
- void SendString(struct UartDebugMember *UDM,char *String)
- {
- UDM->REDE(1);
- while(*String!='\0')
- {
- HAL_UART_Transmit(UDM->Uart,(uint8_t *)String,1,1);
- String++;
- }
- UDM->REDE(0);
- HAL_UART_Receive_IT(UDM->Uart,(uint8_t *)UDM->UartData,1);
- }
- /**/
- void RS485_REDE_1(uint8_t a)
- {
- if(a)
- {
- RE_DE1 = 1;
- }
- else
- {
- RE_DE1 = 0;
- }
- }
- void RS485_REDE_2(uint8_t a)
- {
- if(a)
- {
- RE_DE2 = 1;
- }
- else
- {
- RE_DE2 = 0;
- }
- }
- void Null(uint8_t a)
- {
- }
復制代碼 “struct UartDebugMember U_D_Uart2,U_D_Uart3,U_D_Uart4,U_D_Uart7;” 因為電路板上使用了usart2、usart3、usart4、usart7,所以需要定義4個相應的UartDebugMember 結構體實體。
“void UartDebugInit(void)” 串口初始化,該函數涉及底層,若使用其他型號單片機或者使用其他庫函數需要作出相應修改。這里使用的是HAL庫。
“void DataReceive(struct UartDebugMember *UDM)” 串口接收函數,該函數需要在串口接收中斷里調用如下所示。- void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
- {
- /* NOTE: This function should not be modified, when the callback is needed,
- the HAL_UART_RxCpltCallback can be implemented in the user file
- */
- if(huart == U_D_Uart2.Uart)
- {
- DataReceive(&U_D_Uart2);
- }
- if(huart == U_D_Uart3.Uart)
- {
- DataReceive(&U_D_Uart3);
- }
- if(huart == U_D_Uart4.Uart)
- {
- DataReceive(&U_D_Uart4);
- }
- if(huart == U_D_Uart7.Uart)
- {
- DataReceive(&U_D_Uart7);
- }
- }
復制代碼
該函數的功能一是將接收到的數據存進接收緩沖區;二是將DataTimeCount清零以及將DataTimeCountEn置1,這點很重要。
“void TimeCountReceive(struct UartDebugMember *UDM)”該函數用來計算總線靜默時間和判斷幀數據接收是否完成,該函數需要每隔1ms執行一次,如下所示。
- static void Task_1ms(void)
- {
- TimeCountReceive(&U_D_Uart2);
- TimeCountReceive(&U_D_Uart3);
- TimeCountReceive(&U_D_Uart4);
- TimeCountReceive(&U_D_Uart7);
- }
復制代碼
“void ClearRxData(struct UartDebugMember *UDM)”該函數用來清除接收緩沖區(其實僅清除的接收個數)和接收完成標志,需要在數據處理完成后調用,具體用法會在后續章節中介紹。
“void TransmitData(struct UartDebugMember *UDM,unsigned char *Buf,unsigned char Length)”和“void SendString(struct UartDebugMember *UDM,char *String)”都是發送函數沒什么特別注意的地方。
“void RS485_REDE_1(uint8_t a)”、“void RS485_REDE_2(uint8_t a)”和“void Null(uint8_t a)”都是RS485芯片收發控制的函數,可以在“void UartDebugInit(void)”中看到“U_D_Uart2”和“U_D_Uart3”使用的是函數“Null”而“U_D_Uart4”和“U_D_Uart7”分別使用了函數“void RS485_REDE_1(uint8_t a)”和“void RS485_REDE_2(uint8_t a)”這是因為usart2和usart3連接的是RS232芯片而usart4和usart7連接的是RS485芯片。
至此UART篇就介紹完了,下面是"Header.h"的具體內容。
- #ifndef __HEADER_H
- #define __HEADER_H
- #include "stm32f4xx_hal.h"
- #include "gpio_bool-M4.h"
- #endif
復制代碼- #ifndef __GPIO_BOOL_H
- #define __GPIO_BOOL_H
- #include "stm32f4xx_hal.h"
- //位帶操作,實現51類似的GPIO控制功能
- //具體實現思想,參考<<CM3權威指南>>第五章(87頁~92頁).M4同M3類似,只是寄存器地址變了.
- //IO口操作宏定義
- #define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
- #define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
- #define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
- //IO口地址映射
- #define GPIOA_ODR_Addr (GPIOA_BASE+20) //0x40020014
- #define GPIOB_ODR_Addr (GPIOB_BASE+20) //0x40020414
- #define GPIOC_ODR_Addr (GPIOC_BASE+20) //0x40020814
- #define GPIOD_ODR_Addr (GPIOD_BASE+20) //0x40020C14
- #define GPIOE_ODR_Addr (GPIOE_BASE+20) //0x40021014
- #define GPIOF_ODR_Addr (GPIOF_BASE+20) //0x40021414
- #define GPIOG_ODR_Addr (GPIOG_BASE+20) //0x40021814
- #define GPIOH_ODR_Addr (GPIOH_BASE+20) //0x40021C14
- #define GPIOI_ODR_Addr (GPIOI_BASE+20) //0x40022014
- #define GPIOJ_ODR_ADDr (GPIOJ_BASE+20) //0x40022414
- #define GPIOK_ODR_ADDr (GPIOK_BASE+20) //0x40022814
- #define GPIOA_IDR_Addr (GPIOA_BASE+16) //0x40020010
- #define GPIOB_IDR_Addr (GPIOB_BASE+16) //0x40020410
- #define GPIOC_IDR_Addr (GPIOC_BASE+16) //0x40020810
- #define GPIOD_IDR_Addr (GPIOD_BASE+16) //0x40020C10
- #define GPIOE_IDR_Addr (GPIOE_BASE+16) //0x40021010
- #define GPIOF_IDR_Addr (GPIOF_BASE+16) //0x40021410
- #define GPIOG_IDR_Addr (GPIOG_BASE+16) //0x40021810
- #define GPIOH_IDR_Addr (GPIOH_BASE+16) //0x40021C10
- #define GPIOI_IDR_Addr (GPIOI_BASE+16) //0x40022010
- #define GPIOJ_IDR_Addr (GPIOJ_BASE+16) //0x40022410
- #define GPIOK_IDR_Addr (GPIOK_BASE+16) //0x40022810
- //IO口操作,只對單一的IO口!
- //確保n的值小于16!
- #define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //輸出
- #define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) //輸入
- #define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n) //輸出
- #define PBin(n) BIT_ADDR(GPIOB_IDR_Addr,n) //輸入
- #define PCout(n) BIT_ADDR(GPIOC_ODR_Addr,n) //輸出
- #define PCin(n) BIT_ADDR(GPIOC_IDR_Addr,n) //輸入
- #define PDout(n) BIT_ADDR(GPIOD_ODR_Addr,n) //輸出
- #define PDin(n) BIT_ADDR(GPIOD_IDR_Addr,n) //輸入
- #define PEout(n) BIT_ADDR(GPIOE_ODR_Addr,n) //輸出
- #define PEin(n) BIT_ADDR(GPIOE_IDR_Addr,n) //輸入
- #define PFout(n) BIT_ADDR(GPIOF_ODR_Addr,n) //輸出
- #define PFin(n) BIT_ADDR(GPIOF_IDR_Addr,n) //輸入
- #define PGout(n) BIT_ADDR(GPIOG_ODR_Addr,n) //輸出
- #define PGin(n) BIT_ADDR(GPIOG_IDR_Addr,n) //輸入
- #define PHout(n) BIT_ADDR(GPIOH_ODR_Addr,n) //輸出
- #define PHin(n) BIT_ADDR(GPIOH_IDR_Addr,n) //輸入
- #define PIout(n) BIT_ADDR(GPIOI_ODR_Addr,n) //輸出
- #define PIin(n) BIT_ADDR(GPIOI_IDR_Addr,n) //輸入
- #define PJout(n) BIT_ADDR(GPIOJ_ODR_Addr,n) //輸出
- #define PJin(n) BIT_ADDR(GPIOJ_IDR_Addr,n) //輸入
- #define PKout(n) BIT_ADDR(GPIOK_ODR_Addr,n) //輸出
- #define PKin(n) BIT_ADDR(GPIOK_IDR_Addr,n) //輸入
- #endif
復制代碼
|