??《上一篇》 ???《主目錄》 ???《下一篇》
一、基礎(chǔ)知識(shí)點(diǎn)
了解 RS485 Modbus協(xié)議技術(shù) 。本實(shí)驗(yàn)是基于STM32F103開發(fā) 實(shí)現(xiàn) 通過(guò)RS-485實(shí)現(xiàn)modbus協(xié)議。
準(zhǔn)備好了嗎?開始我的show time。
二、開發(fā)環(huán)境
1、硬件開發(fā)準(zhǔn)備
主控:STM32F103ZET6
RS485收發(fā)器:SP3485P
2、軟件開發(fā)準(zhǔn)備
軟件開發(fā)使用虛擬機(jī) + VScode + STM32Cube 開發(fā)STM32,在虛擬機(jī)中直接完成編譯下載。
該部分可參考:軟件開發(fā)環(huán)境構(gòu)建
三、STM32CubeMX相關(guān)配置
1、STM32CubeMX基本配置
本實(shí)驗(yàn)基于CubeMX詳解構(gòu)建基本框架 進(jìn)行開發(fā)。
2、STM32CubeMX RS485 相關(guān)配置
(1)發(fā)送接收控制腳配置(GPIO配置)
gpio輸出電平: 低(控制引腳默認(rèn)低電平,芯片處于讀狀態(tài))
gpio模式: 推挽輸出
gpio上下拉設(shè)置: 不上下拉
gpio輸出速度: 低速
gpio命名: RS485_DE_nRE (與硬件標(biāo)識(shí)一致,便于代碼編寫)
(2)串口UART3配置
根據(jù)硬件引腳連接,RS485芯片連接UART3通信
基本配置: 實(shí)驗(yàn)波特率采用9600、數(shù)據(jù)位8bit、無(wú)奇偶校驗(yàn)、停止位1bit
數(shù)據(jù)方向: 接收發(fā)送
DMA配置: Add添加發(fā)送和接收的DMA,DMA參數(shù)保持默認(rèn)狀態(tài)
(3)中斷配置
實(shí)驗(yàn)中接收數(shù)據(jù)采用空閑觸發(fā);發(fā)送數(shù)據(jù)采用DMA發(fā)送觸發(fā)后發(fā)送完成中斷
UART3總中斷(USART3 global interrupt)必須打開(為了發(fā)送完成中斷實(shí)現(xiàn))
UART_RX (DMA1 channel3 global interrupt) DMA接收中斷不打開,取消對(duì)鉤(這里對(duì)鉤無(wú)法改變,后續(xù)解決)
UART_TX (DMA1 channel2 global interrupt) DMA發(fā)送中斷打開。
進(jìn)行NVIC中斷等級(jí)配置(0等級(jí)最高)
上述講到無(wú)法取消DMA接收中斷,原因是選中了強(qiáng)制DMA中斷(右上角藍(lán)色框,取消對(duì)鉤就ok)
四、Vscode代碼講解
1、初始化相關(guān)中斷
#ifdef STM32_F407_RS485_Modbus
printf("----DWB 此程序通過(guò)RS-485實(shí)現(xiàn)modbus協(xié)議----\r\n");
__HAL_UART_ENABLE_IT(&huart3, UART_IT_IDLE); // 使能串口3空閑中斷
HAL_UART_Receive_DMA(&huart3, UART3.pucRec_Buffer, UART3_Rec_LENGTH);
#endif
2、RS485 結(jié)構(gòu)體 以及函數(shù)實(shí)現(xiàn)
typedef struct
{
uint8_t* pucSend_Buffer; //發(fā)送緩存指針
uint8_t* pucRec_Buffer; //接收緩存指針
void (*SendArray)(uint8_t*, uint16_t); //串口發(fā)送數(shù)組
void (*SendString)(uint8_t*); //串口發(fā)送字符串
void (*RS485_Set_SendMode)(void); //RS-485接口設(shè)置為發(fā)送模式
void (*RS485_Set_RecMode)(void); //RS-485接口設(shè)置為接收模式
/* data */
} UART_t;
// 串口發(fā)數(shù)組
static void SendArray(uint8_t* p_Arr,uint16_t LEN)
{
UART3.RS485_Set_SendMode();
HAL_UART_Transmit_DMA(&huart3,p_Arr,LEN);
}
// RS485接口設(shè)置發(fā)送模式
static void RS485_Set_SendMode()
{
HAL_GPIO_WritePin(RS485_DE_nRE_GPIO_Port, RS485_DE_nRE_Pin,,GPIO_PIN_SET);
}
// RS485接口設(shè)置接收模式
static void RS485_Set_RecMode()
{
HAL_GPIO_WritePin(RS485_DE_nRE_GPIO_Port, RS485_DE_nRE_Pin,,GPIO_PIN_RESET);
}
3、RS485 Modbus發(fā)送
重構(gòu)接收回調(diào)函數(shù)(整個(gè)DMA發(fā)送過(guò)程后面有講解)
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
/* Prevent unused argument(s) compilation warning */
if(huart->Instance == huart3.Instance)
{
UART3.RS485_Set_RecMode();
}
}
4、RS485 Modbus接收
接收使用空閑中斷 ,在串口總中斷中添加空閑中斷檢測(cè)。
void USART3_IRQHandler(void)
{
/* USER CODE BEGIN USART3_IRQn 0 */
if(__HAL_UART_GET_FLAG(&huart3, UART_FLAG_IDLE)) // 判斷空閑中斷標(biāo)志位
{
__HAL_UART_CLEAR_IDLEFLAG(&huart3); // 1、清除中斷標(biāo)志位
HAL_UART_IdleCallback(&huart3); // 2、空閑中斷回調(diào)函數(shù)
}
/* USER CODE END USART3_IRQn 0 */
HAL_UART_IRQHandler(&huart3);
/* USER CODE BEGIN USART3_IRQn 1 */
/* USER CODE END USART3_IRQn 1 */
}
在 Drivers/STM32F1xx_HAL_Driver/Inc/stm32f1xx_hal_uart.h 文件中回調(diào)函數(shù)并沒(méi)有串口空閑中斷回調(diào)函數(shù)
重構(gòu)空閑中斷回調(diào)函數(shù)
void HAL_UART_IdleCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == huart3.Instance)
{
Modbus.Protocol_Analysis(&UART3); // 接收數(shù)據(jù)解析
HAL_UART_Receive_DMA(&huart3, UART3.pucRec_Buffer, UART3_Rec_LENGTH); // 重新開啟接收DMA(在數(shù)據(jù)解析中會(huì)暫時(shí)關(guān)閉接收DMA)
}
}
5、Modbus 收發(fā)數(shù)據(jù)詳解
(1)Modbus結(jié)構(gòu)體
typedef struct
{
uint16_t addr;
void (*Protocol_Analysis)(UART_t*);
} Modbus_t;
(2)Modbus接收數(shù)據(jù)整體框架
#define UART_Order_Index 8
#define FunctionCode_Read_Register 0x03
#define FunctionCode_Write_Register 0x06
#define UART3_Send_LENGTH 20
#define UART3_Rec_LENGTH 20
static void Protocol_Analysis(UART_t* UART)
{
UART_t* const COM_UART = UART;
uint8_t i = 0, Index = 0;
// 1、關(guān)閉接收
HAL_UART_AbortReceive(&huart3);
// 2、整理接收數(shù)據(jù)
for(i=0; i<UART3_Rec_LENGTH; i++)
{
if(Index == 0)
{
if(*(COM_UART->pucRec_Buffer+i) != Modbus.addr)
continue;
}
*(COM_UART->pucRec_Buffer + Index) = *(COM_UART->pucRec_Buffer + i);
// 取7字節(jié)
if(Index == UART_Order_Index)
break;
Index++;
}
// 4、校驗(yàn)碼
CRC_16.CRC_Value = CRC_16.CRC_Check(COM_UART->pucRec_Buffer, 6);
CRC_16.CRC_H = (u_int8_t)(CRC_16.CRC_Value >> 8);
CRC_16.CRC_L = (u_int8_t)CRC_16.CRC_Value;
if(((*(COM_UART->pucRec_Buffer+6) == CRC_16.CRC_L) && (*(COM_UART->pucRec_Buffer+7) == CRC_16.CRC_H))
||
((*(COM_UART->pucRec_Buffer+6) == CRC_16.CRC_H) && (*(COM_UART->pucRec_Buffer+7) == CRC_16.CRC_L)))
{
//校驗(yàn)地址
if((*(COM_UART->pucRec_Buffer+0)) == Modbus.addr)
{
// 5、數(shù)據(jù)處理
if((*(COM_UART->pucRec_Buffer+1)) == FunctionCode_Read_Register)
{
Modbus_Read_Register(COM_UART);
}
else if((*(COM_UART->pucRec_Buffer+1)) == FunctionCode_Write_Register)
{
Modbus_Wrtie_Register(COM_UART);
}
}
}
//清緩存
for(i=0;i<UART3_Rec_LENGTH;i++)
{
*(COM_UART->pucRec_Buffer+i) = 0x00;
}
}
Modbus_Read_Register函數(shù)數(shù)據(jù)解析(協(xié)議數(shù)據(jù):地址碼+功能碼+數(shù)據(jù)長(zhǎng)度(字節(jié))+發(fā)送數(shù)據(jù)+CRC)連續(xù)讀取從設(shè)備寄存器值返回給主設(shè)備。
static void Modbus_Read_Register(UART_t* UART)
{
UART_t* const COM_UART = UART;
//校驗(yàn)地址
if((*(COM_UART->pucRec_Buffer+2) == 0x9C) && (*(COM_UART->pucRec_Buffer+3) == 0x41))
{
回應(yīng)數(shù)據(jù)
//地址碼
*(COM_UART->pucSend_Buffer+0) = Modbus.addr;
//功能碼
*(COM_UART->pucSend_Buffer+1) = FunctionCode_Read_Register;
//數(shù)據(jù)長(zhǎng)度(字節(jié))
*(COM_UART->pucSend_Buffer+2) = 2;
//發(fā)送數(shù)據(jù)
// deep status
*(COM_UART->pucSend_Buffer+3) = 0;
*(COM_UART->pucSend_Buffer+4) = Deep.Read_Deep();
*(COM_UART->pucSend_Buffer+5) = 0;
*(COM_UART->pucSend_Buffer+6) = 0x66;
//插入CRC
CRC_16.CRC_Value = CRC_16.CRC_Check(COM_UART->pucSend_Buffer,7); //計(jì)算CRC值
CRC_16.CRC_H = (uint8_t)(CRC_16.CRC_Value >> 8);
CRC_16.CRC_L = (uint8_t)CRC_16.CRC_Value;
*(COM_UART->pucSend_Buffer+7) = CRC_16.CRC_L;
*(COM_UART->pucSend_Buffer+8) = CRC_16.CRC_H;
//發(fā)送數(shù)據(jù)
UART3.SendArray(COM_UART->pucSend_Buffer,9);
}
}
Modbus_Wrtie_Register函數(shù)數(shù)據(jù)解析。從主設(shè)備獲取控制從設(shè)備外設(shè)的數(shù)值,解析后控制外設(shè)。
static void Modbus_Wrtie_Register(UART_t* UART)
{
UART_t* const COM_UART = UART;
uint8_t i=0;
//回應(yīng)數(shù)據(jù)
for(i=0;i<8;i++)
{
*(COM_UART->pucSend_Buffer+i) = *(COM_UART->pucRec_Buffer+i);
}
//發(fā)送數(shù)據(jù)
UART3.SendArray(COM_UART->pucSend_Buffer,8);
//解析數(shù)據(jù),控制外設(shè)
if((*(COM_UART->pucRec_Buffer+2) == 0x9C) && (*(COM_UART->pucRec_Buffer+3) == 0x42))
{
if(*(COM_UART->pucRec_Buffer+5) == Deep_Status_ON )
Deep.Deep_Enable();
else
Deep.Deep_Disable();
}
}
為什么要使能DMA發(fā)送完成中斷才會(huì)觸發(fā)UART的發(fā)送完成中斷?
答案就在代碼里,帶大家解析一遍相關(guān)代碼:
// 調(diào)用HAL_UART_Transmit_DMA函數(shù)實(shí)現(xiàn)DMA發(fā)送
HAL_UART_Transmit_DMA
-> huart->hdmatx->XferCpltCallback = UART_DMATransmitCplt; // 設(shè)置發(fā)送完成回調(diào)函數(shù)
static void UART_DMATransmitCplt(DMA_HandleTypeDef *hdma)
{
UART_HandleTypeDef *huart = (UART_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent;
/* DMA Normal mode*/
if ((hdma->Instance->CCR & DMA_CCR_CIRC) == 0U)
{
huart->TxXferCount = 0x00U;
/* Disable the DMA transfer for transmit request by setting the DMAT bit
in the UART CR3 register */
CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAT);
/* Enable the UART Transmit Complete Interrupt */
SET_BIT(huart->Instance->CR1, USART_CR1_TCIE); // 當(dāng)DMA發(fā)送完成后,會(huì)使能串口發(fā)送完成中斷
}
/* DMA Circular mode */
else
{
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
/*Call registered Tx complete callback*/
huart->TxCpltCallback(huart);
#else
/*Call legacy weak Tx complete callback*/
HAL_UART_TxCpltCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
}
}
當(dāng)DMA發(fā)送完成后,會(huì)使能串口發(fā)送完成中斷。配置打開UART3中斷總開關(guān)。
HAL_UART_IRQHandler
-> if (((isrflags & USART_SR_TC) != RESET) && ((cr1its & USART_CR1_TCIE) != RESET))
-> UART_EndTransmit_IT(huart);
-> HAL_UART_TxCpltCallback(huart); // 回調(diào)函數(shù)為弱函數(shù),可重構(gòu)
五、結(jié)果演示以及報(bào)文解析
實(shí)驗(yàn)測(cè)試使用USB轉(zhuǎn)RS485工具。從設(shè)備板子上A B接口連接USB轉(zhuǎn)RS485工具上對(duì)應(yīng)A B接口。主設(shè)備為PC端安裝的MThings進(jìn)行Modbus收發(fā)數(shù)據(jù)測(cè)試。有興趣的小伙伴可以體驗(yàn)下MTings官網(wǎng)
發(fā)送數(shù)據(jù)報(bào)文解析
[2023-03-05 13:13:03-802]COM34-發(fā)送:01 06 9c 42 00 01 c6 4e
[2023-03-05 13:13:03-827]COM34-接收:01 06 9c 42 00 01 c6 4e
0x01:主機(jī)要查詢的從設(shè)備地址
0x06:功能碼 修改寫操作
0x9c 0x42:寄存器地址0x9c42轉(zhuǎn)十進(jìn)制地址為40,002
0x00 0x01:寫入地址的數(shù)值為0x01 (控制從設(shè)備蜂鳴器打開)
0xc6 0x4e:CRC校驗(yàn)碼
[2023-03-05 13:13:04-980]COM34-發(fā)送:01 06 9c 42 00 00 07 8e
[2023-03-05 13:13:05-012]COM34-接收:01 06 9c 42 00 00 07 8e
0x01:主機(jī)要查詢的從設(shè)備地址
0x06:功能碼 修改寫操作
0x9c 0x42:寄存器地址0x9c42轉(zhuǎn)十進(jìn)制地址為40,002
0x00 0x01:寫入地址的數(shù)值為0x00 (控制從設(shè)備蜂鳴器關(guān)閉)
0xc6 0x4e:CRC校驗(yàn)碼
接收數(shù)據(jù)報(bào)文解析
[2023-03-05 13:41:54-954]COM34-發(fā)送:01 06 9c 42 00 01 c6 4e
[2023-03-05 13:41:54-977]COM34-接收:01 06 9c 42 00 01 c6 4e
0x01:從設(shè)備地址
0x06:功能碼 修改寫操作
0x9c 0x42:寄存器地址0x9c42轉(zhuǎn)十進(jìn)制地址為40,002
0x00 0x01:寫入地址的數(shù)值為0x00 (控制從設(shè)備蜂鳴器關(guān)閉)
0xc6 0x4e:CRC校驗(yàn)碼
[2023-03-05 13:41:56-289]COM34-發(fā)送:01 03 9c 41 00 02 ba 4f
0x01:主機(jī)要查詢的從設(shè)備地址
0x03:功能碼 查詢讀操作
0x9c 0x42:寄存器地址0x9c41轉(zhuǎn)十進(jìn)制地址為40,001
0x00 0x02:讀取兩個(gè)數(shù)據(jù)(一個(gè)數(shù)據(jù)2字節(jié))
0xba 0x4f:CRC校驗(yàn)碼
[2023-03-05 13:41:56-320]COM34-接收:01 03 02 00 01 00 66 d9 a3
0x01:告訴主機(jī)自己從設(shè)備地址
0x03:功能碼 讀操作
0x00 0x01:讀出第一個(gè)數(shù)據(jù)為0x01,當(dāng)前蜂鳴器打開狀態(tài)
0x00 0x66:讀取第二個(gè)數(shù)據(jù)為0x66(該值是本猿在代碼中寫死的值,后續(xù)功能會(huì)結(jié)合本章節(jié)modbus功能通信,敬請(qǐng)期待)
0xd9 0xa3:CRC校驗(yàn)碼文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-779346.html
六、代碼下載
STM32開發(fā)(六)STM32F103 RS485 Modbus通信代碼文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-779346.html
到了這里,關(guān)于STM32開發(fā)(六)STM32F103 通信 —— RS485 Modbus通信編程詳解的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!