当前位置: 首页 > news >正文

STM32串口发送时使用奇偶校验学习感悟——Even(偶校验)

一、相关概念的补充

在有效数据之后,有一个可选的数据校验位。由于数据通信相对更容易受到外部干扰导致传输
数据出现偏差,可以在传输过程加上校验位来解决这个问题。校验方法有奇校验 (odd)、偶校验
(even)、0 校验 (space)、1 校验 (mark) 以及无校验 (noparity),它们介绍如下:
奇校验要求有效数据和校验位中“1”的个数为奇数,比如一个 8 位长的有效数据为:01101001,
此时总共有 4 个“1”,为达到奇校验效果,校验位为“1”,最后传输的数据将是 8 位的有效数据
加上 1 位的校验位总共 9 位

偶校验与奇校验要求刚好相反,要求帧数据和校验位中“1”的个数为偶数,比如数据帧:
11001010,此时数据帧“1”的个数为 4 个,所以偶校验位为“0”。
0 校验是不管有效数据中的内容是什么,校验位总为“0”,1 校验是校验位总为“1”。
在无校验的情况下,数据包中不包含校验位。

当我们加上校验位后,并且使能了相关配置之后,要明确的就是,最后传输的数据是8位有效数据+1位校验位=9位数据,校验位的添加是硬件自动完成的,对于我们来说是透明的,不用关心,只要在CubeMX中配置好即可

二、主要步骤

(1)CubeMX配置

因为我工程里面使用串口空闲终端(IDLE)来接收数据,所以首先我们要在CubeMX中设置一下,数据位为9位(8位数据位和1位校验位),因为这里我们要接收完整的9位数据,然后相关DMA数据的接收对其宽度我们这里都给half a word 也就是两个字节,16位


串口通讯的数据包由发送设备通过自身的 TXD 接口传输到接收设备的 RXD 接口。在串口通讯
的协议层中,规定了数据包的内容,它由启始位、主体数据、校验位以及停止位组成,通讯双方
的数据包格式要约定一致才能正常收发数据

(2)核心代码

1.usart.c

/* USER CODE BEGIN Header */
#include "stdio.h"
#include <string.h>
#include <stdarg.h>
/********************************************************************************* @file    usart.c* @brief   This file provides code for the configuration*          of the USART instances.******************************************************************************* @attention** Copyright (c) 2025 STMicroelectronics.* All rights reserved.** This software is licensed under terms that can be found in the LICENSE file* in the root directory of this software component.* If no LICENSE file comes with this software, it is provided AS-IS.********************************************************************************/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "usart.h"/* USER CODE BEGIN 0 */// 定义接收缓冲区
uint16_t Rx_buffer[BUFFER_SIZE];
// 定义DMA回显数据发送缓冲区 (这就是我们要发送的“货物”)
uint16_t TxBuffer[BUFFER_SIZE]; 
uint16_t rx_len = 0;/* USER CODE END 0 */UART_HandleTypeDef huart1;
DMA_HandleTypeDef hdma_usart1_rx;
DMA_HandleTypeDef hdma_usart1_tx;/* USART1 init function */void MX_USART1_UART_Init(void)
{/* USER CODE BEGIN USART1_Init 0 *//* USER CODE END USART1_Init 0 *//* USER CODE BEGIN USART1_Init 1 *//* USER CODE END USART1_Init 1 */huart1.Instance = USART1;huart1.Init.BaudRate = 115200;huart1.Init.WordLength = UART_WORDLENGTH_9B;huart1.Init.StopBits = UART_STOPBITS_1;huart1.Init.Parity = UART_PARITY_EVEN;huart1.Init.Mode = UART_MODE_TX_RX;huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;huart1.Init.OverSampling = UART_OVERSAMPLING_16;if (HAL_UART_Init(&huart1) != HAL_OK){Error_Handler();}/* USER CODE BEGIN USART1_Init 2 */// 启动DMA接收,使用循环模式HAL_UART_Receive_DMA(&huart1, (uint8_t *)Rx_buffer, BUFFER_SIZE);// 使能IDLE中断__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);/* USER CODE END USART1_Init 2 */}void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{GPIO_InitTypeDef GPIO_InitStruct = {0};if(uartHandle->Instance==USART1){/* USER CODE BEGIN USART1_MspInit 0 *//* USER CODE END USART1_MspInit 0 *//* USART1 clock enable */__HAL_RCC_USART1_CLK_ENABLE();__HAL_RCC_GPIOA_CLK_ENABLE();/**USART1 GPIO ConfigurationPA9     ------> USART1_TXPA10     ------> USART1_RX*/GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;GPIO_InitStruct.Alternate = GPIO_AF7_USART1;HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);/* USART1 DMA Init *//* USART1_RX Init */hdma_usart1_rx.Instance = DMA2_Stream2;hdma_usart1_rx.Init.Channel = DMA_CHANNEL_4;hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;hdma_usart1_rx.Init.Mode = DMA_NORMAL;hdma_usart1_rx.Init.Priority = DMA_PRIORITY_MEDIUM;hdma_usart1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK){Error_Handler();}__HAL_LINKDMA(uartHandle,hdmarx,hdma_usart1_rx);/* USART1_TX Init */hdma_usart1_tx.Instance = DMA2_Stream7;hdma_usart1_tx.Init.Channel = DMA_CHANNEL_4;hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE;hdma_usart1_tx.Init.MemInc = DMA_MINC_ENABLE;hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;hdma_usart1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;hdma_usart1_tx.Init.Mode = DMA_NORMAL;hdma_usart1_tx.Init.Priority = DMA_PRIORITY_MEDIUM;hdma_usart1_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;if (HAL_DMA_Init(&hdma_usart1_tx) != HAL_OK){Error_Handler();}__HAL_LINKDMA(uartHandle,hdmatx,hdma_usart1_tx);/* USART1 interrupt Init */HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);HAL_NVIC_EnableIRQ(USART1_IRQn);/* USER CODE BEGIN USART1_MspInit 1 *//* USER CODE END USART1_MspInit 1 */}
}void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
{if(uartHandle->Instance==USART1){/* USER CODE BEGIN USART1_MspDeInit 0 *//* USER CODE END USART1_MspDeInit 0 *//* Peripheral clock disable */__HAL_RCC_USART1_CLK_DISABLE();/**USART1 GPIO ConfigurationPA9     ------> USART1_TXPA10     ------> USART1_RX*/HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9|GPIO_PIN_10);/* USART1 DMA DeInit */HAL_DMA_DeInit(uartHandle->hdmarx);HAL_DMA_DeInit(uartHandle->hdmatx);/* USART1 interrupt Deinit */HAL_NVIC_DisableIRQ(USART1_IRQn);/* USER CODE BEGIN USART1_MspDeInit 1 *//* USER CODE END USART1_MspDeInit 1 */}
}/* USER CODE BEGIN 1 */// 发送一个字节
void USART1_SendByte(uint8_t Byte)
{HAL_UART_Transmit(&huart1, &Byte, 1, HAL_MAX_DELAY);
}// 发送数组
void USART1_SendArray(uint8_t *Array, uint16_t Length)
{HAL_UART_Transmit(&huart1, Array, Length, HAL_MAX_DELAY);
}// 发送字符串
void USART1_SendString(char *String)
{HAL_UART_Transmit(&huart1, (uint8_t*)String, strlen(String), HAL_MAX_DELAY);
}// 发送数字
void USART1_SendNumber(uint32_t Number, uint8_t Length)
{uint8_t i;for(i = 0; i < Length; i++){USART1_SendByte(Number / (uint32_t)pow(10, Length - i - 1) % 10 + '0');}
}// 重定向printf到串口
void printf_uart1(char *format,...){va_list args;va_start(args, format);static char buffer[1000];int len = vsnprintf(buffer, sizeof(buffer), format, args);va_end(args);if (len > 0) {HAL_UART_Transmit(&huart1, (uint8_t *)buffer, len, 100);}
}
/* USER CODE END 1 */

2.stm32f4××_it.c (主要是对USART1_IRQHandler的处理)

/* USER CODE BEGIN Header */
/********************************************************************************* @file    stm32f4xx_it.c* @brief   Interrupt Service Routines.******************************************************************************* @attention** Copyright (c) 2025 STMicroelectronics.* All rights reserved.** This software is licensed under terms that can be found in the LICENSE file* in the root directory of this software component.* If no LICENSE file comes with this software, it is provided AS-IS.********************************************************************************/
/* USER CODE END Header *//* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "stm32f4xx_it.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "usart.h"
#include "string.h"
/* USER CODE END Includes *//* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN TD *//* USER CODE END TD *//* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD *//* USER CODE END PD *//* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM *//* USER CODE END PM *//* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV *//* USER CODE END PV *//* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN PFP *//* USER CODE END PFP *//* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 *//* USER CODE END 0 *//* External variables --------------------------------------------------------*/
extern TIM_HandleTypeDef htim2;
extern DMA_HandleTypeDef hdma_usart1_rx;
extern DMA_HandleTypeDef hdma_usart1_tx;
extern UART_HandleTypeDef huart1;
/* USER CODE BEGIN EV *//* USER CODE END EV *//******************************************************************************/
/*           Cortex-M4 Processor Interruption and Exception Handlers          */
/******************************************************************************/
/*** @brief This function handles Non maskable interrupt.*/
void NMI_Handler(void)
{/* USER CODE BEGIN NonMaskableInt_IRQn 0 *//* USER CODE END NonMaskableInt_IRQn 0 *//* USER CODE BEGIN NonMaskableInt_IRQn 1 */while (1){}/* USER CODE END NonMaskableInt_IRQn 1 */
}/*** @brief This function handles Hard fault interrupt.*/
void HardFault_Handler(void)
{/* USER CODE BEGIN HardFault_IRQn 0 *//* USER CODE END HardFault_IRQn 0 */while (1){/* USER CODE BEGIN W1_HardFault_IRQn 0 *//* USER CODE END W1_HardFault_IRQn 0 */}
}/*** @brief This function handles Memory management fault.*/
void MemManage_Handler(void)
{/* USER CODE BEGIN MemoryManagement_IRQn 0 *//* USER CODE END MemoryManagement_IRQn 0 */while (1){/* USER CODE BEGIN W1_MemoryManagement_IRQn 0 *//* USER CODE END W1_MemoryManagement_IRQn 0 */}
}/*** @brief This function handles Pre-fetch fault, memory access fault.*/
void BusFault_Handler(void)
{/* USER CODE BEGIN BusFault_IRQn 0 *//* USER CODE END BusFault_IRQn 0 */while (1){/* USER CODE BEGIN W1_BusFault_IRQn 0 *//* USER CODE END W1_BusFault_IRQn 0 */}
}/*** @brief This function handles Undefined instruction or illegal state.*/
void UsageFault_Handler(void)
{/* USER CODE BEGIN UsageFault_IRQn 0 *//* USER CODE END UsageFault_IRQn 0 */while (1){/* USER CODE BEGIN W1_UsageFault_IRQn 0 *//* USER CODE END W1_UsageFault_IRQn 0 */}
}/*** @brief This function handles System service call via SWI instruction.*/
void SVC_Handler(void)
{/* USER CODE BEGIN SVCall_IRQn 0 *//* USER CODE END SVCall_IRQn 0 *//* USER CODE BEGIN SVCall_IRQn 1 *//* USER CODE END SVCall_IRQn 1 */
}/*** @brief This function handles Debug monitor.*/
void DebugMon_Handler(void)
{/* USER CODE BEGIN DebugMonitor_IRQn 0 *//* USER CODE END DebugMonitor_IRQn 0 *//* USER CODE BEGIN DebugMonitor_IRQn 1 *//* USER CODE END DebugMonitor_IRQn 1 */
}/*** @brief This function handles Pendable request for system service.*/
void PendSV_Handler(void)
{/* USER CODE BEGIN PendSV_IRQn 0 *//* USER CODE END PendSV_IRQn 0 *//* USER CODE BEGIN PendSV_IRQn 1 *//* USER CODE END PendSV_IRQn 1 */
}/*** @brief This function handles System tick timer.*/
void SysTick_Handler(void)
{/* USER CODE BEGIN SysTick_IRQn 0 *//* USER CODE END SysTick_IRQn 0 */HAL_IncTick();/* USER CODE BEGIN SysTick_IRQn 1 *//* USER CODE END SysTick_IRQn 1 */
}/******************************************************************************/
/* STM32F4xx Peripheral Interrupt Handlers                                    */
/* Add here the Interrupt Handlers for the used peripherals.                  */
/* For the available peripheral interrupt handler names,                      */
/* please refer to the startup file (startup_stm32f4xx.s).                    */
/******************************************************************************//*** @brief This function handles EXTI line3 interrupt.*/
void EXTI3_IRQHandler(void)
{/* USER CODE BEGIN EXTI3_IRQn 0 *//* USER CODE END EXTI3_IRQn 0 */HAL_GPIO_EXTI_IRQHandler(KEY1_Pin);/* USER CODE BEGIN EXTI3_IRQn 1 *//* USER CODE END EXTI3_IRQn 1 */
}/*** @brief This function handles TIM2 global interrupt.*/
void TIM2_IRQHandler(void)
{/* USER CODE BEGIN TIM2_IRQn 0 *//* USER CODE END TIM2_IRQn 0 */HAL_TIM_IRQHandler(&htim2);/* USER CODE BEGIN TIM2_IRQn 1 *//* USER CODE END TIM2_IRQn 1 */
}/*** @brief This function handles USART1 global interrupt.*/
void USART1_IRQHandler(void)
{/* USER CODE BEGIN USART1_IRQn 0 *//* USER CODE END USART1_IRQn 0 */HAL_UART_IRQHandler(&huart1);/* USER CODE BEGIN USART1_IRQn 0 */static uint8_t parity_error_flag = 0;  // 奇偶错误标志// 检查奇偶校验错误标志(优先级高于IDLE中断,先处理错误)if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_PE) != RESET) {parity_error_flag = 1;  // 标记发生奇偶错误__HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_PE);  // 清除标志}// 检查是否是IDLE中断if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET){// 清除IDLE标志位__HAL_UART_CLEAR_IDLEFLAG(&huart1);// 停止DMA接收HAL_UART_DMAStop(&huart1);// 计算接收长度rx_len = BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);if(rx_len > 0){// 若发生奇偶错误,丢弃数据不进行回显并提示if (parity_error_flag) {memset(Rx_buffer,0,sizeof(Rx_buffer));printf_uart1("接收数据包含奇偶校验错误,已丢弃\r\n");parity_error_flag = 0;  // 重置标志} else {printf_uart1("接收数据不包含奇偶校验错误,下面进行数据回显\r\n");// 提取低8位数据位,过滤校验位for(uint16_t i=0; i<rx_len; i++){TxBuffer[i] = Rx_buffer[i] & 0xFF; // 只取低8位数据}// 发送纯数据位,长度为rx_lenHAL_UART_Transmit_DMA(&huart1, (uint8_t*)TxBuffer, rx_len);}}// 重新启动DMA接收HAL_UART_Receive_DMA(&huart1,(uint8_t*)Rx_buffer, BUFFER_SIZE);}/* USER CODE END USART1_IRQn 1 */
}/*** @brief This function handles DMA2 stream2 global interrupt.*/
void DMA2_Stream2_IRQHandler(void)
{/* USER CODE BEGIN DMA2_Stream2_IRQn 0 *//* USER CODE END DMA2_Stream2_IRQn 0 */HAL_DMA_IRQHandler(&hdma_usart1_rx);/* USER CODE BEGIN DMA2_Stream2_IRQn 1 *//* USER CODE END DMA2_Stream2_IRQn 1 */
}/*** @brief This function handles DMA2 stream7 global interrupt.*/
void DMA2_Stream7_IRQHandler(void)
{/* USER CODE BEGIN DMA2_Stream7_IRQn 0 *//* USER CODE END DMA2_Stream7_IRQn 0 */HAL_DMA_IRQHandler(&hdma_usart1_tx);/* USER CODE BEGIN DMA2_Stream7_IRQn 1 *//* USER CODE END DMA2_Stream7_IRQn 1 */
}/* USER CODE BEGIN 1 *//* USER CODE END 1 */

上述代码中实现了对接收到的数据中校验位的剥离,若奇偶校验无错误,UART_FLAG_PE为RESET,相关的parity_error_flag不会被设置,进入正常的数据回显阶段,而在回显之前,程序已经帮我们将校验位剥离出去,剩下完整的有效数据给TxBuffer,也就是说Rx_buffer包含了完整的九位数据(包括了一位校验位),TxBuffer里面只有8位有效数据

在这段代码中,从 9 位数据中剥离校验位的操作,核心思路是区分 “有效数据位” 和 “校验位”,通过位运算保留低 8 位有效数据,屏蔽第 9 位的校验位,具体实现逻辑如下:

一、核心思路

当串口配置为 “9 位字长(含校验位)” 时,每帧实际传输的数据由两部分组成:

  • 低 8 位(bit0~bit7):用户发送的有效数据(如0x010x02等);
  • 第 9 位(bit8):硬件自动添加的校验位(用于奇偶校验验证,不属于有效数据)。

校验位的作用仅为 “验证数据传输是否正确”,一旦完成校验(通过或失败),就无需保留。因此,需要从 9 位完整数据中剥离第 9 位校验位,只保留低 8 位有效数据,避免校验位干扰后续的数据处理或回显。

二、具体实现(代码解析)

代码中通过按位与运算(&0xFF) 实现校验位的剥离,关键语句如下:

for(uint16_t i=0; i<rx_len; i++){TxBuffer[i] = Rx_buffer[i] & 0xFF; // 只取低8位数据
}
原理说明:
  1. 数据存储形式:由于 9 位数据无法用 8 位变量(uint8_t)完整存储,Rx_buffer实际应为uint16_t类型(16 位变量,足够容纳 9 位数据)。此时,接收的 9 位数据在内存中存储为:

    • bit0~bit7:有效数据(8 位);
    • bit8:校验位(1 位);
    • bit9~bit15:无意义(因仅需 9 位,高位补 0)。
  2. 按位与运算(&0xFF)的作用0xFF是十六进制常量,对应二进制为00000000 11111111(16 位表示)。当Rx_buffer[i](9 位数据,存储在 16 位变量中)与0xFF进行按位与时:

    • 低 8 位(bit0~bit7):与11111111运算后,保留原始有效数据;
    • 第 9 位(bit8)及更高位(bit9~bit15):与0运算后,被强制清零(即剥离校验位)。

    例如:

    • 接收的 9 位数据为1 00000001(第 9 位校验位为 1,低 8 位有效数据为0x01),存储在 16 位变量中为00000001 00000001
    • 0xFF00000000 11111111)运算后,结果为00000000 00000001,即仅保留低 8 位的0x01,校验位被剥离。

稍后我会将完整的工程上传,需要的可以自行获取,或者评论区联系我

三.整个实践过程中的雷点(踩到的雷)

(1)Rx_buffer 和Tx_buffer 不使用uint16_t类型

未修改前 Rx_buffer 是 uint8_t 类型,接收时确实会自动截断 9 位数据,但这是 “被动丢弃校验位”,而非 “正确的校验位与数据位分离”—— 看似 “分离” 了,实则会导致校验逻辑异常和数据混乱,具体分析如下:

1、uint8_t 接收时的 “自动截断” 原理

当串口配置为 “9 位字长(含校验位)”,但接收缓冲区是 uint8_t(仅 8 位)时:

  • STM32 的 UART 数据寄存器(DR)是 16 位,接收 9 位数据后,会将 低 8 位(数据位)存入 uint8_t 变量第 9 位(校验位)被硬件自动丢弃(因为 uint8_t 没有第 9 位的存储空间)。
  • 这个过程确实是 “自动截断”,客观上实现了 “数据位保留、校验位丢弃”,但本质是 “被动丢弃”,而非 “主动分离”。

2、为什么 “自动截断” 不能替代 “主动分离”?(核心问题)

虽然 uint8_t 会截断校验位,但这种方式存在两个致命问题,导致之前的通信异常:

  1. 硬件校验逻辑仍依赖完整 9 位数据,截断不影响校验结果串口的奇偶校验是由硬件在接收完整 9 位数据时完成的 —— 不管缓冲区是 uint8_t 还是 uint16_t,硬件都会先检查 “8 位数据 + 1 位校验位” 是否符合奇偶规则,再决定是否置位 PE 错误标志。你之前用 uint8_t 时,偶校验发送 0x01 正常,是因为硬件校验通过后,截断保留了正确的低 8 位数据

  2. 截断会导致数据存储和回显的混乱当 uint8_t 截断第 9 位时,若串口配置或通信参数有偏差(比如数据位长度误设为 9 位无校验),会导致 “校验位被当作数据位截断”,或 “数据位被当作校验位丢弃”:

    • 例如:若串口误配置为 “9 位数据位 + 无校验”,发送 0x101(9 位数据),uint8_t 会截断为 0x01,丢失高 1 位数据;
    • 之前你看到的 “奇怪正方形符号”,部分原因就是截断后的数据包含了校验位被误判的控制字符(比如截断后的低 8 位恰好是 0x80 等不可见字符)。

3、总结:“被动截断” vs “主动分离”

方式本质核心问题
uint8_t 自动截断被动丢弃第 9 位(校验位)1. 校验逻辑依赖硬件,截断不解决校验匹配问题;2. 数据存储混乱,易出现显示异常。
uint16_t + &0xFF主动保留低 8 位(数据位)1. 可控分离,明确保留有效数据;2. 不影响硬件校验逻辑,数据处理更可靠。
  • uint16_t 缓冲区 + 位运算是 “主动可控的分离方案”,既能完整接收 9 位数据供硬件校验,又能通过位运算精准提取有效数据,适合需要校验位验证且数据处理严谨的场景(如你的需求),所以这种方式更严谨,对我们来说更好一些

(2)Rx_buffer 和Tx_buffer 使用了uint16_t类型,但是DMA数据对齐DataLength没有使用halfword的时候

我刚开始DMA 接收的配置为:

hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;  // 外设端按字节对齐
hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;     // 内存端按字节对齐
  • 问题点:9 位数据必须用 16 位(半字)存储(因为 1 个字节只能存 8 位,放不下 9 位),但 DMA 被配置为按字节(8 位)对齐传输
  • 后果:DMA 会将 16 位的DR寄存器数据拆成两个 8 位字节传输到内存,而 STM32 内存默认是小端模式(低地址存低字节,高地址存高字节),导致数据存储顺序颠倒。

大小端模式相关概念补充:【数据存储】大端存储||小端存储(超详细解析,小白一看就懂!!!)https://blog.csdn.net/weixin_45031801/article/details/136471384?ops_request_misc=%257B%2522request%255Fid%2522%253A%25221c34ef43c63f0195930ea552f6e066d7%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=1c34ef43c63f0195930ea552f6e066d7&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-136471384-null-null.142^v102^pc_search_result_base2&utm_term=%E5%A4%A7%E7%AB%AF%E5%AD%98%E5%82%A8%E5%92%8C%E5%B0%8F%E7%AB%AF%E5%AD%98%E5%82%A8&spm=1018.2226.3001.4187

现象解释

串口通信的 “串行传输” 特性决定的:串口发送多字节数据时,会按照 “先发送低地址字节,后发送高地址字节” 的顺序逐字节传输

(1)发送0x01(单字节)时,Rx_buffer[0] 显示0x0001
  • 串口助手发送的0x01是 8 位有效数据,STM32 会自动添加 1 位偶校验位(0x01有 1 个 1,偶校验位为 1,使总 1 的个数为偶数)。
  • 实际接收的 9 位数据为:0b100000001(二进制),对应 16 位寄存器值为0x0101(低 9 位有效)。
  • DMA 按字节传输:先传低字节0x01,再传高字节0x01(高 7 位无效,实际可能为 0),最终存储到uint16_t类型的Rx_buffer中,表现为0x0001(高字节被截断或清零)。
(2)发送0x0001(两字节)时,Rx_buffer[0]  显示0x0100
  • 串口助手发送两个字节0x000x01,STM32 分别为每个字节添加校验位后接收。
  • DMA 按字节传输:先接收0x00的低字节存到内存低地址,再接收0x01的低字节存到内存高地址(这里其实并不和大小端存储矛盾,通过串口助手发送的 “0x0001”,本质是手动输入的两个独立字节(而非一个 16 位数值),串口助手会按照你输入的顺序逐字节发送,所以这里是先发送的00,00就被放到了低地址,然后01就被放到了高地址)
  • 由于Rx_bufferuint16_t(16 位)数组,小端模式下,低地址字节(0x00)作为低 8 位,高地址字节(0x01)作为高 8 位,最终表现为0x0100(高低字节颠倒)。
解决方案

将 DMA 的数据对齐方式修改为半字(16 位),匹配 9 位数据的存储需求:

// 在HAL_UART_MspInit函数中,修改DMA接收配置
hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;  // 外设按半字(16位)对齐
hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;     // 内存按半字(16位)对齐

修改后,DMA 会按 16 位为单位传输数据,直接匹配 USART 的 16 位DR寄存器和uint16_t类型的Rx_buffer,避免字节拆分导致的顺序颠倒问题,解决这一问题的

本质原因如下:
1. 字节对齐(8 位)的问题:强制拆分 16 位寄存器数据

当 DMA 按字节对齐时,USART 的 16 位 DR 寄存器(存储 9 位数据:8 位有效数据 + 1 位校验位)会被 DMA 拆成两个 8 位字节传输到内存:

  • 比如一帧 9 位数据是 1 0000 0001(校验位 1 + 数据 0x01),对应 DR 寄存器的 16 位值是 0x0101(低 9 位有效)。
  • 字节对齐的 DMA 会先传低 8 位 0x01 到内存低地址,再传高 8 位 0x01 到内存高地址。
  • 但如果是连续两帧数据(比如 0x00 和 0x01),两帧的低 8 位会被依次存入连续的内存地址,小端模式下 16 位变量读取时就会出现顺序颠倒(如前所述的0x0100)。
2. 半字对齐(16 位)的解决:完整传输一帧数据

改为半字对齐后,DMA 会将 USART 的 16 位 DR 寄存器作为一个整体(16 位)传输,直接存入uint16_t类型的Rx_buffer数组元素中:

  • 每帧 9 位数据(8 位 + 1 位校验)对应 DR 寄存器的 16 位值(如0x0101),会被完整存入Rx_buffer[i],不拆分。
  • 连续发送两帧数据(0x00 和 0x01)时:
    • 第一帧(0x00 + 校验位)的 16 位值存入Rx_buffer[0]
    • 第二帧(0x01 + 校验位)的 16 位值存入Rx_buffer[1]
    • 两个元素独立存储,彼此没有字节交叉,自然不会出现 “高低字节颠倒”。

(3)知识补充:开启校验后,1 个字节(8 位)→ 加 1 位校验位 → 变成 9 位 → 用 16 位变量存

现象:当我发送数据0x01的时候,Rx_buffer[0]为0x0101,但是当我通过串口发送数据0x0001的时候,Rx_buffer[1]为0x0101?上述现象是什么情况?

当时懵逼的地方在于,发送0x0001时候,Rx_buffer[0]不应该为0x0101吗?怎么是Rx_buffer[1]为0x0101呢?

这种现象的核心原因是9 位数据格式(8 位有效数据 + 1 位校验位)的存储特性与 DMA 半字对齐(16 位)传输的结合,导致每个接收的 8 位数据会被扩展为包含校验位的 16 位值存储在Rx_buffer

1. 9 位数据的组成规则(关键背景)

你的串口配置为 UART_WORDLENGTH_9B + UART_PARITY_EVEN(偶校验),这意味着:

  • 每帧数据包含 8 位有效数据 + 1 位偶校验位(共 9 位)。
  • 偶校验位的规则:确保 8 位数据中 “1” 的个数为偶数。如果数据中 “1” 的个数是奇数,校验位为 1;如果是偶数,校验位为 0。

2. 发送0x01时,Rx_buffer[0] = 0x0101的原因

  • 发送的0x01是 8 位有效数据,二进制为 0000 0001
  • 该数据中 “1” 的个数为 1(奇数),根据偶校验规则,校验位必须为 1(使总 “1” 的个数为 2,偶数)。
  • 因此,实际接收的 9 位数据为:1 0000 0001(前 1 位是校验位,后 8 位是有效数据)。
  • STM32 的 USART 接收 9 位数据时,会将这 9 位存入 16 位的DR寄存器(低 9 位有效,高 7 位无效),因此 16 位值为 0000 0001 0000 0001(十六进制0x0101)。
  • DMA 按半字(16 位)对齐传输,直接将DR寄存器的 16 位值存入Rx_buffer[0],因此Rx_buffer[0] = 0x0101

3. 发送0x0001时,Rx_buffer[1] = 0x0101的原因

这里需要明确:你发送的0x0001实际是两个连续的 8 位数据0x000x01),串口会按帧依次接收:

  • 第一个字节0x00:8 位数据为 0000 0000,其中 “1” 的个数为 0(偶数),因此校验位为 0。9 位数据为 0 0000 0000,存入 16 位寄存器后为 0000 0000 0000 00000x0000),对应Rx_buffer[0] = 0x0000

  • 第二个字节0x01:与单独发送0x01的情况完全一致,9 位数据为 1 0000 0001,16 位值为0x0101,存入Rx_buffer[1],因此Rx_buffer[1] = 0x0101

http://www.dtcms.com/a/583149.html

相关文章:

  • 畅想网络网站建设推广wordpress文章顺序
  • 网络营销 网站识图
  • 国外网站做淘宝客北京朝阳区建设工作办公网站
  • 扬州网站建设价格网络促销
  • 新手练习做网站哪个网站比较合适网站 多线
  • 学习日报 20251107|服务染色”和“灰度发布
  • 佛山电商网站制作团队静态网页设计用什么软件
  • 郑州网站制作哪家招聘南京网站建设希丁哥
  • 义乌制作网站要多少钱抖音推广公司
  • seo的优点有哪些长沙企业seo服务
  • 济南网站制作多少钱水网站源码
  • diagrams画C4视图示例(未完..)
  • 做网站素材在哪找网站框架设计
  • 创建网站需要准备哪些资料口碑好的网站建设
  • 接口自动化测试实战指南
  • 网站建设通查询公众号开发者中心
  • php网站怎么做302WordPress里面备份功能在哪
  • 网站建设及网页设计免费国内linux服务器
  • Shape-Guided Dual-Memory Learning for 3D Anomaly Detection 论文精读
  • 一般情况下新网站收录需要多长时间审核完成啊
  • 有教做翻糖的网站吗建设信用卡商城网站
  • 云蝠智能VoiceAgent核心功能升级,提升语音交互体验
  • 2025年11月7日 AI快讯
  • 专门做酒店网站WordPress文章添加灯箱
  • 门头沟青岛网站建设wordpress支持页面模版
  • 网站转化下降原因本单位二级网站建设管理制度
  • 做演讲视频的网站温州网站升级
  • 西安前端开发招聘烟台软件优化网站
  • 网站开发人员工具餐饮vi设计网站
  • 【Svelte】如何实现多个钩子?