標(biāo)題:
我的ModBus主機(jī)-UART篇
[打印本頁]
作者:
qinlu123
時間:
2021-11-15 09:46
標(biāo)題:
我的ModBus主機(jī)-UART篇
眾所周知,ModBus從機(jī)很好實(shí)現(xiàn),而主機(jī)就稍微麻煩一點(diǎn)。下面我將介紹這幾年來我用到的ModBus主機(jī)方案,既作為分享又作為一個記錄與總結(jié)。
談到ModBus就不得不說UART,UART作為ModBus協(xié)議的承載是整個ModBus通信的基礎(chǔ)。
UART的基本收發(fā)功能通過文件“UartDebug.c”和“UartDebug.h”來實(shí)現(xiàn),首先來看“UartDebug.h”文件的內(nèi)容。
#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
復(fù)制代碼
"Header.h"文件包含了基本的單片機(jī)信息,移植代碼的時候只需要將相應(yīng)的頭文件替換掉就可以了,本章最后會貼出"Header.h"的具體內(nèi)容。
“#define BUFMAX 32” 設(shè)置收發(fā)緩沖區(qū)的大;
“#define SLIENTTIME 5” 總線靜默時間閾值,用于判斷該幀數(shù)據(jù)是否接收完畢;
“#define RE_DE1 PCout(1)”和“#define RE_DE2 PFout(8)”為RS485芯片收發(fā)控制IO;
下面將介紹“struct UartDebugMember”結(jié)構(gòu)體成員
“void (*REDE)(uint8_t a);” 該函數(shù)指針為RS485芯片收發(fā)控制函數(shù);
“UART_HandleTypeDef *Uart;” 使用的UART端口,該成員涉及到底層若更換其他MCU或者使用其他庫函數(shù)需要作出相應(yīng)修改;
“uint16_t *UartData;” 串口接收到的一個字節(jié);
“uint8_t TransmitBuf[BUFMAX+1];” 發(fā)送緩沖區(qū);
“uint8_t ReceiveBuf[BUFMAX+1];” 接收緩沖區(qū);
“uint8_t RecPointClearEn;” 接收字節(jié)數(shù)清零使能;
“uint8_t ReceivePoint;” 接收字節(jié)數(shù);
“uint8_t DataTimeCount;” 當(dāng)前的總線靜默時間
“uint8_t DataTimeCountEn;” 當(dāng)前的總線靜默時間計時使能
“uint8_t ReceiveFinish;” 幀數(shù)據(jù)接收完成標(biāo)志
關(guān)于函數(shù)我們將在下面的"UartDebug.c"中進(jìn)行介紹。
#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 : 串口接收數(shù)據(jù)
*******************************************************************************/
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;
}
/*需要根據(jù)波特率以及幀與幀之間的間隔時間調(diào)整觸發(fā)時間*/
else if(UDM->DataTimeCount > SLIENTTIME)
{
UDM->ReceiveFinish=1;
UDM->DataTimeCountEn=0;
}
else
{
UDM->DataTimeCount++;
}
}
/*******************************************************************************
*Function Name : UartClearBuffer
*Input :
*Return :
*Description : 清除接收緩沖區(qū)
*******************************************************************************/
void ClearRxData(struct UartDebugMember *UDM)
{
UDM->ReceivePoint=0;
UDM->RecPointClearEn=1;
UDM->ReceiveFinish=0;
}
/*******************************************************************************
*Function Name : TransmitData
*Input :
*Return :
*Description : 串口發(fā)送一幀數(shù)據(jù)
*******************************************************************************/
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 : 串口發(fā)送字符串
*******************************************************************************/
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)
{
}
復(fù)制代碼
“struct UartDebugMember U_D_Uart2,U_D_Uart3,U_D_Uart4,U_D_Uart7;” 因為電路板上使用了usart2、usart3、usart4、usart7,所以需要定義4個相應(yīng)的UartDebugMember 結(jié)構(gòu)體實(shí)體。
“void UartDebugInit(void)” 串口初始化,該函數(shù)涉及底層,若使用其他型號單片機(jī)或者使用其他庫函數(shù)需要作出相應(yīng)修改。這里使用的是HAL庫。
“void DataReceive(struct UartDebugMember *UDM)” 串口接收函數(shù),該函數(shù)需要在串口接收中斷里調(diào)用如下所示。
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);
}
}
復(fù)制代碼
該函數(shù)的功能一是將接收到的數(shù)據(jù)存進(jìn)接收緩沖區(qū);二是將DataTimeCount清零以及將DataTimeCountEn置1,這點(diǎn)很重要。
“void TimeCountReceive(struct UartDebugMember *UDM)”該函數(shù)用來計算總線靜默時間和判斷幀數(shù)據(jù)接收是否完成,該函數(shù)需要每隔1ms執(zhí)行一次,如下所示。
static void Task_1ms(void)
{
TimeCountReceive(&U_D_Uart2);
TimeCountReceive(&U_D_Uart3);
TimeCountReceive(&U_D_Uart4);
TimeCountReceive(&U_D_Uart7);
}
復(fù)制代碼
“void ClearRxData(struct UartDebugMember *UDM)”該函數(shù)用來清除接收緩沖區(qū)(其實(shí)僅清除的接收個數(shù))和接收完成標(biāo)志,需要在數(shù)據(jù)處理完成后調(diào)用,具體用法會在后續(xù)章節(jié)中介紹。
“void TransmitData(struct UartDebugMember *UDM,unsigned char *Buf,unsigned char Length)”和“void SendString(struct UartDebugMember *UDM,char *String)”都是發(fā)送函數(shù)沒什么特別注意的地方。
“void RS485_REDE_1(uint8_t a)”、“void RS485_REDE_2(uint8_t a)”和“void Null(uint8_t a)”都是RS485芯片收發(fā)控制的函數(shù),可以在“void UartDebugInit(void)”中看到“U_D_Uart2”和“U_D_Uart3”使用的是函數(shù)“Null”而“U_D_Uart4”和“U_D_Uart7”分別使用了函數(shù)“void RS485_REDE_1(uint8_t a)”和“void RS485_REDE_2(uint8_t a)”這是因為usart2和usart3連接的是RS232芯片而usart4和usart7連接的是RS485芯片。
至此UART篇就介紹完了,下面是"Header.h"的具體內(nèi)容。
#ifndef __HEADER_H
#define __HEADER_H
#include "stm32f4xx_hal.h"
#include "gpio_bool-M4.h"
#endif
復(fù)制代碼
#ifndef __GPIO_BOOL_H
#define __GPIO_BOOL_H
#include "stm32f4xx_hal.h"
//位帶操作,實(shí)現(xiàn)51類似的GPIO控制功能
//具體實(shí)現(xiàn)思想,參考<<CM3權(quán)威指南>>第五章(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
復(fù)制代碼
作者:
小蝸牛單片機(jī)
時間:
2022-1-4 14:14
留個腳印
作者:
yinhw01
時間:
2022-1-15 11:26
努力學(xué)習(xí)一下,雖然對初學(xué)的我有點(diǎn)難
作者:
溫柔的郎
時間:
2022-1-31 13:34
樓主講了這么多,辛苦了!
若能完美制作一個Modbus RTU主從站,配合著主從站例程講解,大家就能夠更好的理解樓主的Modbus了。
是對大家都科普,也是對樓主知識的驗證和提高!!
畢竟Modbus RTU在單片機(jī)里不容易做好的,能把這個做好,還是有2把刷子的,
作者:
qhp777
時間:
2023-5-12 16:45
對初學(xué)的我有點(diǎn)難,工作需要啊
1
作者:
用戶名8
時間:
2024-1-23 16:31
樓主太有心了,寫的這么詳細(xì),對學(xué)習(xí)很有用
作者:
linlin1
時間:
2024-7-10 15:34
對初學(xué)的我有點(diǎn)難,請問這是哪個那個型號單片機(jī)呢
作者:
qinlu123
時間:
2024-11-11 14:30
linlin1 發(fā)表于 2024-7-10 15:34
對初學(xué)的我有點(diǎn)難,請問這是哪個那個型號單片機(jī)呢
驅(qū)動是不分單片機(jī)的
歡迎光臨 (http://www.torrancerestoration.com/bbs/)
Powered by Discuz! X3.1