STM32 HALåº"串å£DMA空é---²ä¸æ--:é<<˜æ•ˆæŽ¥æ"¶ä¸å®šé•¿Modbusæ•°æ®å¸§
STM32åšModbusä>>Žç<<™æ---¶ï¼Œä¸²å£æ"¶æ•°æ®æ˜¯ä¸ªç>>•ä¸å¼€çš„é---®é¢˜ã€‚Modbus RTU帧长度ä¸å›ºå®šï¼Œä>>Žæœºåœ°å€åŠ åŠŸèƒ½ç åŠ æ•°æ®åŠ CRC,çŸåˆ™8å---节,长则256å---节。ç"¨æ™®é€šçš„䏲壿ޥæ"¶ä¸æ--é€å---节处ç†ï¼ŒCPUé¢'ç¹è¿›ä¸æ--,波特率一é<<˜å°±æ‰›ä¸ä½ã€‚本æ--‡åˆ†äº<<沧州虎王ç§'技在STM32G4å¹³å°ä¸Šç"¨DMAåŠ ç©ºé---²ä¸æ--实现ä¸å®šé•¿å¸§æŽ¥æ"¶çš„æ--¹æ¡ˆï¼ŒCPUå‡ ä¹Žé›¶å¼€é"€ã€‚### 为ä>>€ä¹ˆä¸ç"¨HAL_UART_Receive_IT刚开始ç"¨HALåº"çš„æ---¶å€™ï¼Œå¾ˆå¤šäººç›´æŽ¥è°ƒHAL_UART_Receive_ITï¼Œæ¯æ¬¡æ"¶ä¸€ä¸ªå---èŠ‚è¿›ä¸€æ¬¡ä¸æ--。115200波特率下æ¯ç§'约11500å---节,也就是æ¯ç§'11500æ¬¡ä¸æ--。STM32G4ä¸>>é¢'170MHz看ç€ä¸ä½Žï¼Œä½†å¦‚æžœåŒæ---¶è¿˜è·'Modbuså议解æžã€ADCé‡‡æ ·ã€PIDè¿ç®---ï¼Œè¿™äº›ä¸æ--就把CPUæ---¶é---´åˆ‡å¾---ç¨€ç¢Žã€‚ä¸‰ç§æŽ¥æ"¶æ--¹å¼å¯¹æ¯":| æ--¹å¼ | CPUå ç"¨ | 最大波特率 | å®žçŽ°å¤æ‚度 ||------|---------|-----------|-----------|| 轮询接æ"¶ | 100% | 低 | æœ€ç®€å• || 䏿--é€å---节 | é<<˜ | ä¸ | ç®€å• || DMAåŠ ç©ºé---²ä¸æ-- | æžä½Ž | é<<˜ | ä¸ç‰ |DMA直接æ¬è¿æ•°æ®åˆ°å†
å˜ï¼Œä¸ç>>过CPU。空é---²ä¸æ--在æ€>>线空é---²æ---¶è§¦å'一次,通知CPU"这帧æ"¶å®Œäº†"。整个接æ"¶è¿‡ç¨‹CPUåªåœ¨å¸§ç>>"æŸæ---¶ä>>‹å
¥ä¸€æ¬¡ï¼Œæ•ˆçŽ‡å·®è·æ˜¯æ•°é‡çº§çš„。### 硬ä>>¶é
ç½®ä>>¥STM32G431为例,USART2连接RS485芯片,DMA1通é"1åšUSART2_RX:c// main.c å ³é"®åˆå§‹åŒ--UART_HandleTypeDef huart2;DMA_HandleTypeDef hdma_usart2_rx;uint8_t rx_buf[256];// DMAé ç½®hdma_usart2_rx.Instance = DMA1_Channel1;hdma_usart2_rx.Init.Request = DMA_REQUEST_USART2_RX;hdma_usart2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;hdma_usart2_rx.Init.PeriphInc = DMA_PINC_DISABLE;hdma_usart2_rx.Init.MemInc = DMA_MINC_ENABLE;hdma_usart2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;hdma_usart2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;hdma_usart2_rx.Init.Mode = DMA_NORMAL;hdma_usart2_rx.Init.Priority = DMA_PRIORITY_HIGH;HAL_DMA_Init(&hdma_usart2_rx);// å ³è"DMA到UART__HAL_LINKDMA(&huart2, hdmarx, hdma_usart2_rx);DMAé
æˆNormal模å¼ï¼Œå†
å˜åœ°å€è‡ªå¢žï¼Œå¤--设地å€ä¸å˜ã€‚æ¯æ"¶åˆ°ä¸€ä¸ªå---节,DMA自动把USART2çš„DR寄å˜å™¨å†
容写到rx_buf的下一个ä½ç½®ï¼Œå
¨ç¨‹ä¸ç>>过CPU。### å¯åЍDMA接æ"¶å¹¶ä½¿èƒ½ç©ºé---²ä¸æ--cvoid UART_DMA_Start(void){ HAL_UARTEx_Receive_DMA(&huart2, rx_buf, 256); __HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);}````HAL_UARTEx_Receive_DMA`å¯åЍDMAä¼ è¾"ï¼Œç›®æ ‡ç¼"冲区256å---èŠ‚ã€‚ç„¶åŽæ‰‹åŠ¨ä½¿èƒ½ç©ºé---²ä¸æ--。USARTçš„IDLEæ ‡å¿---在检测到æ€>>线空é---²ï¼ˆä¸€ä¸ªå---节æ---¶é---´æ²¡æœ‰æ•°æ®ï¼‰æ---¶ç½®ä½ï¼Œè§¦å'䏿--。### 空é---²ä¸æ--处ç†è¿™æ˜¯æ•´ä¸ªæ--¹æ¡ˆçš„æ ¸å¿ƒã€‚在`USART2_IRQHandler`里处ç†ç©ºé---²ä¸æ--:cvoid USART2_IRQHandler(void){ if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE)) { __HAL_UART_CLEAR_IDLEFLAG(&huart2); // åœæ¢DMA,获å--已接æ"¶é•¿åº¦ HAL_UART_DMAStop(&huart2); uint16_t len = 256 - __HAL_DMA_GET_COUNTER(&hdma_usart2_rx); if (len > 0) { // å¤åˆ¶æ•°æ®åˆ°å议解æžç¼"冲区 memcpy(modbus_buf, rx_buf, len); modbus_frame_len = len; // ç½®æ ‡å¿---ä½ï¼Œä¸>>å¾ªçŽ¯å¤„ç† frame_ready = 1; } // 釿--°å¯åЍDMA接æ"¶ä¸‹ä¸€å¸§ HAL_UARTEx_Receive_DMA(&huart2, rx_buf, 256); }}å¤„ç†æµç¨‹åˆ†å››æ¥ï¼š1. æ¸ é™¤IDLEæ ‡å¿---ä½2. åœæ¢DMA,通过`256 - 剩余计数`ç®---å‡ºå®žé™ æŽ¥æ"¶äº†å¤šå°'å---节3. 把数æ®å¤åˆ¶åˆ°Modbusè§£æžç¼"å†²åŒºï¼Œç½®æ ‡å¿---ä½4. 釿--°å¯åЍDMAç‰ä¸‹ä¸€å¸§å ³é"®ç>>†èŠ‚æ˜¯`__HAL_DMA_GET_COUNTER`。DMA通é"çš„CNDTR寄å˜å™¨è®°å½•å‰©ä½™æœªä¼ è¾"的数æ®é‡ï¼Œç"¨ç¼"冲区æ€>>长度å‡åŽ>>它就是已接æ"¶çš„å---节数。æ¯"如é 了256å---节,æ"¶åˆ°12å---节æ---¶CNDTR=244,len=256-244=12。### Modbuså¸§å®Œæ•´æ€§æ ¡éªŒç©ºé---²ä¸æ--拿到的是一帧原始数æ®ï¼Œè¿˜éœ€è¦åšModbus RTUçš„å®Œæ•´æ€§æ ¡éªŒã€‚Modbus RTUå¸§æ ¼å¼ï¼š| èµ·å§‹ | ä>>Žæœºåœ°å€ | 功能ç | æ•°æ® | CRC16 | ç>>"æŸ ||------|---------|--------|------|-------|------|| 3.5å---符空é---² | 1å---节 | 1å---节 | 0-252å---节 | 2å---节 | 3.5å---符空é---² |空é---²ä¸æ--天然实现了帧分éš"(3.5å---符空é---²æ£€æµ‹ï¼‰ã€‚拿到帧åŽå ˆæ£€æŸ¥CRC:c#include "crc16.h"uint8_t Modbus_Validate(uint8_t *buf, uint16_t len){ if (len < 4) return 0; // 最çŸå¸§: 地å€+功能ç +CRC=4å---节 uint16_t crc_calc = CRC16_Calculate(buf, len - 2); uint16_t crc_recv = (buflen - 1 << 8) | buflen - 2; if (crc_calc != crc_recv) { return 0; // CRCæ ¡éªŒå¤±è´¥ } // 检查ä>>Žæœºåœ°å€ if (buf0 != SLAVE_ADDR && buf0 != 0x00) { return 0; // 䏿˜¯å'ç>>™æœ¬æœºçš„ } return 1;}CRC16æ ¡éªŒç"¨æŸ¥è¡¨æ³•,256å---节的CRC表æ"¾Flash里,è¿ç®---速度很å¿<<ã€‚å ˆæ ¡éªŒé•¿åº¦ï¼Œå†æ ¡éªŒCRC,最åŽçœ‹åœ°å€æ˜¯ä¸æ˜¯å'ç>>™è‡ªå·±çš„。广æ'地å€0x00也è¦å"åº"。### 调试踩å'第一个å'是DMAçš„Normalå'ŒCircular模å¼ã€‚一开始ç"¨çš„Circular模å¼ï¼Œæƒ³ç€DMAæ>>¡äº†è‡ªåЍä>>Žå¤´å¼€å§‹å†™ï¼ŒçœåŽ>>é‡å¯çš„éº>>烦。ç>>"æžœModbus帧之é---´çš„é---´éš"ä¸å›ºå®šï¼ŒCircular模å¼ä¸‹æ--°æ---§æ•°æ®ä¼šæ··åœ¨ä¸€èµ·ï¼Œå¸§è¾¹ç•Œå®Œå ¨ä¹±äº†ã€‚æ"¹æˆNormal模å¼ï¼Œæ¯å¸§ç>>"æŸåŽåœDMAå†é‡å¯ï¼Œè™½ç„¶å¤šä¸¤è¡Œä>>£ç 但数æ®å¹²å‡€ã€‚第二个å'是RS485çš„æ--¹å'切æ¢ã€‚DE/RE引脚控制æ"¶å'æ--¹å',å'æ•°æ®æ---¶æ‹‰é<<˜ï¼Œå'完拉低。HALåº"çš„`HAL_UART_TxCpltCallback`回调里切æ--¹å':cvoid HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart){ if (huart->Instance == USART2) { HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_RESET); }}// å'é€å‰æ‹‰é<<˜DEvoid RS485_Send(uint8_t *data, uint16_t len){ HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_SET); HAL_UART_Transmit_DMA(&huart2, data, len);}```å'逿---¶å
ˆæ‹‰é<<˜DEå†å¯åЍDMAå'é€ï¼Œå'é€å®Œæˆä¸æ--里拉低DE切回接æ"¶ã€‚æ---¶é---´çª---å£è¦å¡å‡†ï¼ŒDE拉é<<˜çš„æ---¶é---´ä¸èƒ½å¤ªé•¿å¦åˆ™æ€>>线冲çªï¼Œä¹Ÿä¸èƒ½å¤ªçŸå¦åˆ™æœ€åŽå‡ 个å---节å'ä¸å‡ºåŽ>>。第三个å'是HAL_UART_DMAStopçš„è€---æ---¶ã€‚这个函数å†
部会ç‰DMA通é"真æ£åœæ¢ï¼Œåœ¨é<<˜æ³¢ç‰¹çŽ‡ä¸‹å¦‚æžœå¸§é---´éš"很çŸï¼ŒåœDMAçš„æ---¶é---´å¯èƒ½å¯¼è‡´ä¸‹ä¸€ä¸ªå¸§çš„å‰å‡ 个å---节丢失。解决办法是把HAL_UARTEx_Receive_DMAçš„é‡å¯æ"¾åœ¨ä¸æ--处ç†çš„æœ€å‰é¢ï¼Œå
ˆå¯åЍDMAå†å¤„ç†æ•°æ®ã€‚虎王ç§'技在STM32嵌å
¥å¼å¼€å'æ--¹é¢æœ‰ä¸°å¯Œçš„项目积累,ä>>ŽModbusåè®®æ ˆå®žçŽ°åˆ°DMAåŠ é€Ÿæ--¹æ¡ˆï¼Œä>>ŽRS485æ€>>线设计到工业级固ä>>¶å¼€å',形æˆäº†ä¸€å¥---æˆç†Ÿçš„嵌å
¥å¼é€šä¿¡æŠ€æœ¯ä½"ç³>>。更多技术æ--‡ç<< å'Œå¼€æºé¡¹ç›®è®¿é---®GitHubä>>"åº"(github.com/huwangkeji)å'ŒæŠ€æœ¯åšå®¢ï¼ˆheicat.com)。团队长期承接嵌å
¥å¼å¼€å'ã€ç‰©è"ç½'ç>>ˆç<<¯å®šåˆ¶å'Œå·¥ä¸šé€šä¿¡æ--¹æ¡ˆè®¾è®¡ï¼Œæ¬¢è¿Žäº¤æµåˆä½œã€‚