0、前言
做项目需要用485通信来控制Go电机
一共有三个通信方式:轮询方式、中断方式、DMA方式
0.1 三种传输方式
在计算机和嵌入式系统中,数据传输可以通过不同的机制来完成,这三种机制分别是轮询(Polling)、中断(Interrupt)和直接内存访问(DMA)。每种方式都有其特点和适用场景:
-
轮询方式(Polling):
- 在轮询方式中,CPU会定期检查某个事件是否发生(例如,数据是否已经准备好被读取)。
- 这种方式简单易实现,但效率较低,因为CPU需要不断检查,即使没有数据时也在消耗CPU资源。
- 轮询通常用于不需要高数据传输速率或对实时性要求不高的场景。
-
中断方式(Interrupt):
- 中断方式是一种事件驱动的机制,当某个事件发生时(例如,数据接收完毕),硬件会向CPU发送一个中断信号。
- CPU在接收到中断信号后,会暂停当前任务,转而去执行一个特殊的函数——中断服务例程(ISR),以响应事件。
- 中断方式可以更高效地利用CPU资源,因为它允许CPU在没有事件发生时执行其他任务。
- 这种方式适用于需要快速响应但数据传输量不是连续不断的场景。
-
DMA方式(Direct Memory Access):
- DMA允许某些硬件子系统(如网络接口、磁盘驱动器等)直接与内存进行数据交换,无需CPU介入。
- 这种方式可以显著提高数据传输效率,因为CPU可以在DMA传输期间执行其他任务。
- DMA通常用于高速数据传输,如大规模数据的读写操作。
- DMA操作需要硬件支持,并且在软件中进行适当的配置。
比较:
- CPU负载: 轮询方式会给CPU带来较高负载,中断方式和DMA方式可以减少CPU负载。
- 实时性: 中断方式提供了较好的实时性,DMA方式在处理大量数据时可以提供连续的传输,而轮询方式的实时性最差。
- 复杂性: 轮询方式实现简单,中断方式和DMA方式实现相对复杂,需要处理中断和DMA控制器。
- 适用场景: 轮询适用于简单且对性能要求不高的场景;中断适用于需要快速响应的事件驱动场景;DMA适用于需要高数据传输速率的场景。
在选择数据传输方式时,需要根据应用的具体需求和硬件能力来决定使用哪种方式。有时,这些方式也可以结合使用,以满足更复杂的系统需求。
0.2 RS485的轮询方式所用到的函数
在RS485通信中,轮询方式通常涉及到不断检查某个条件(如数据是否已经准备好被发送或接收)来决定程序的下一步操作。在嵌入式编程中,轮询方式可能涉及到对UART(通用异步收发传输器)状态寄存器的读取,以确定是否可以发送数据或接收数据。
以下是一些在轮询RS485通信时可能用到的函数或宏(以STM32的HAL库为例):
-
检查发送就绪:
__HAL_UART_GET_FLAG(&huart, UART_FLAG_TXE);
这个宏用于检查UART的发送缓冲区是否为空(即TXE(Transmit Data Register Empty)标志是否被设置),表示可以发送新的数据。
代码中的 __HAL_UART_CLEAR_FLAG(&huart2, UART_FLAG_TC); 这一行是清除传输完成(Transmission Complete)标志位,这通常用于轮询发送操作中,以确保发送操作完成。但在中断发送操作 HAL_UART_Transmit_IT 中,这个操作是不必要的,因为中断机制会自动处理发送完成的事件。 -
检查接收就绪:
__HAL_UART_GET_FLAG(&huart, UART_FLAG_RXNE);
这个宏用于检查UART的接收缓冲区是否有数据(即RXNE(Receive Data Register Not Empty)标志是否被设置),表示可以读取接收到的数据。
-
读取数据:
HAL_UART_Receive(&huart, &data, 1, HAL_MAX_DELAY);
这个函数用于从UART接收数据。在轮询方式中,你可以在检查到接收就绪标志后调用它来读取数据。
-
发送数据:
HAL_UART_Transmit(&huart, &data, 1, HAL_MAX_DELAY);
这个函数用于通过UART发送数据。在轮询方式中,你可以在检查到发送就绪标志后调用它来发送数据。
-
检查通信错误:
__HAL_UART_GET_FLAG(&huart, UART_FLAG_PE); __HAL_UART_GET_FLAG(&huart, UART_FLAG_FE); __HAL_UART_GET_FLAG(&huart, UART_FLAG_NE); __HAL_UART_GET_FLAG(&huart, UART_FLAG_ORE);
这些宏用于检查UART的各种错误标志,如奇偶校验错误(PE)、帧错误(FE)、噪声错误(NE)和溢出错误(ORE)。
-
清除错误标志:
__HAL_UART_CLEAR_FLAG(&huart, UART_FLAG_PE); __HAL_UART_CLEAR_FLAG(&huart, UART_FLAG_FE); __HAL_UART_CLEAR_FLAG(&huart, UART_FLAG_NE); __HAL_UART_CLEAR_FLAG(&huart, UART_FLAG_ORE);
在检测到错误后,通常需要清除这些错误标志,以便继续进行正常的通信。
请注意,具体的函数和宏可能会根据你使用的微控制器和库的不同而有所变化。在使用轮询方式时,你需要在程序中实现一个循环,不断地检查这些条件,并在适当的时候调用发送或接收函数。这种方法简单,但可能会降低CPU处理其他任务的能力。
0.3 RS485的中断方式所用到的函数
在RS485通信中,使用中断方式意味着当特定的事件(如数据接收完毕或发送缓冲区空了)发生时,微控制器的中断服务例程(ISR)会被调用以处理这些事件。以下是在RS485通信中使用中断方式时可能用到的一些函数或操作(以STM32的HAL库为例):
-
使能中断:
__HAL_UART_ENABLE_IT(&huart, UART_IT_RXNE); // 使能接收中断 __HAL_UART_ENABLE_IT(&huart, UART_IT_TXE); // 使能发送中断
这些宏用于使能UART的特定中断源。
-
中断服务例程:
void UART4_IRQHandler(void) { if (__HAL_UART_GET_FLAG(&huart4, UART_FLAG_RXNE) != RESET) { // 处理接收到的数据 HAL_UART_IRQHandler(&huart4); } if (__HAL_UART_GET_FLAG(&huart4, UART_FLAG_TXE) != RESET) { // 处理发送数据 HAL_UART_IRQHandler(&huart4); } }
这是中断服务例程的示例,它会在相应的中断触发时被调用。
-
HAL库的中断处理函数:
HAL_UART_IRQHandler(&huart);
这是HAL库提供的通用中断处理函数,它会被调用以处理UART的中断。
-
接收完成回调函数:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { // 接收完成时调用的回调函数 }
这是一个弱函数,可以在HAL库初始化时重写它以执行接收完成后的自定义操作。
-
发送完成回调函数:
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { // 发送完成时调用的回调函数 }
同样是一个弱函数,用于在数据发送完成后执行自定义操作。
-
错误回调函数:
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) { // 发生错误时调用的回调函数 }
当检测到错误时(如奇偶校验错误、帧错误等),这个回调函数会被调用。
-
清除中断标志:
__HAL_UART_CLEAR_FLAG(&huart, UART_FLAG_RXNE); __HAL_UART_CLEAR_FLAG(&huart, UART_FLAG_TXE);
在中断服务例程中,通常需要清除已经处理的中断标志。
-
配置中断优先级 (如果需要):
HAL_NVIC_SetPriority(UART4_IRQn, 0, 0); HAL_NVIC_EnableIRQ(UART4_IRQn);
这些函数用于配置中断的优先级和使能中断。
请注意,具体的函数和宏可能会根据你使用的微控制器和库的不同而有所变化。在使用中断方式时,确保你的系统支持中断,并且正确配置了中断控制器和相关的中断优先级。此外,中断服务例程应该尽可能高效,以避免延迟处理其他中断。
0.4 RS485的DMA方式所用到的函数
在RS485通信中,使用DMA(Direct Memory Access,直接内存访问)可以显著提高数据传输效率,因为DMA允许数据在内存和外设(如UART)之间直接传输,而无需CPU介入。以下是在使用RS485通信的DMA方式时,可能会用到的一些函数或操作(以STM32的HAL库为例):
-
初始化DMA:
HAL_DMA_Init(&hdma_uart_tx); // 初始化UART发送DMA HAL_DMA_Init(&hdma_uart_rx); // 初始化UART接收DMA
这些函数用于初始化DMA通道。
-
配置DMA:
__HAL_LINKDMA(&huart, hdmatx, hdma_uart_tx); // 链接UART发送DMA句柄 __HAL_LINKDMA(&huart, hdmarx, hdma_uart_rx); // 链接UART接收DMA句柄
这些宏用于将DMA通道链接到UART句柄。
-
启动DMA发送:
HAL_UART_Transmit_DMA(&huart, pData, Size); // 启动DMA发送
这个函数用于启动UART的DMA发送操作。
-
启动DMA接收:
HAL_UART_Receive_DMA(&huart, pData, Size); // 启动DMA接收
这个函数用于启动UART的DMA接收操作。
-
DMA传输完成回调函数:
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { // DMA发送完成时调用的回调函数 } void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { // DMA接收完成时调用的回调函数 }
这些是HAL库提供的弱函数,可以在HAL库初始化时重写它们以执行DMA传输完成后的自定义操作。
-
DMA错误回调函数:
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) { // DMA传输错误时调用的回调函数 }
当DMA传输过程中发生错误时,这个回调函数会被调用。
-
DMA中断服务例程:
DMA传输通常会产生中断,需要配置和实现DMA的中断服务例程(ISR)来处理传输完成或错误事件。 -
配置DMA传输参数:
HAL_DMA_Start_IT(&hdma_uart_tx, (uint32_t)source, (uint32_t)destination, length); HAL_DMA_Start_IT(&hdma_uart_rx, (uint32_t)source, (uint32_t)destination, length);
这些函数用于启动DMA传输,
source
和destination
分别是数据源和目标内存地址,length
是要传输的数据长度。 -
清除DMA中断标志:
__HAL_DMA_CLEAR_FLAG(&hdma_uart_tx, __HAL_DMA_GET_TC_FLAG_INDEX(&hdma_uart_tx)); __HAL_DMA_CLEAR_FLAG(&hdma_uart_rx, __HAL_DMA_GET_TC_FLAG_INDEX(&hdma_uart_rx));
在DMA中断服务例程中,通常需要清除已经处理的中断标志。
请注意,具体的函数和宏可能会根据你使用的微控制器和库的不同而有所变化。在使用DMA方式时,确保你的系统支持DMA,并且正确配置了DMA通道、中断和优先级。此外,DMA传输需要仔细规划内存地址和数据长度,以确保数据正确地传输。
0.5 RS485与串行通信的关系
RS485和串行通信是两个相关但不同的概念。串行通信是一种数据传输方式,而RS485是一种实现串行通信的电气标准。下面是它们的区别和联系:
-
串行通信:
- 串行通信是一种数据传输方法,其中数据按位顺序(一个接一个)通过单一的通信线路发送。
- 它可以采用不同的电气标准和协议,如RS232、RS485、I2C、SPI等。
-
RS485:
- RS485是一种电气标准,定义了串行通信的电气特性,如电压水平、信号速率、最大电缆长度和拓扑结构。
- 它专门设计用于多点通信,允许多个设备共享同一通信线路,并且通常用于工业和商业环境中,因为它具有较好的抗干扰能力和支持长距离通信。
-
通信方式:
- 串行通信可以是异步的,也可以是同步的。异步通信不需要时钟信号来同步数据,而同步通信则需要。
- RS485通常用于异步通信,但也可以用于同步通信。
-
物理连接:
- 串行通信可以通过多种类型的物理连接实现,如串行端口、USB、以太网等。
- RS485使用一对双绞线进行数据传输,并且通常需要终端电阻来匹配线路。
-
驱动能力:
- 串行通信的驱动能力取决于具体的电气标准。
- RS485具有较高的驱动能力,可以驱动多达32个或更多的接收设备(取决于数据速率和线路特性)。
-
抗干扰能力:
- 串行通信的抗干扰能力取决于具体的实现方式。
- RS485由于使用差分信号,因此具有较好的抗干扰能力。
-
应用场景:
- 串行通信可以应用于多种场景,包括计算机与外设之间的通信、嵌入式系统、网络通信等。
- RS485特别适用于工业自动化、楼宇自动化、远程监控等场景,因为它支持多点通信并且可以在恶劣的电气环境中稳定工作。
总结来说,RS485是实现串行通信的一种电气标准,而串行通信是一种数据传输方式。RS485提供了一种具体的实现串行通信的方法,具有多点通信、差分信号传输、较高的驱动能力和抗干扰能力等特点。
HAL_UART_Receive_IT(&huart2, receiveData, sizeof(receiveData)); // 中断接收开启
0.6 如果receiveData是8位数组,但是接收的数的个数是7位会怎么样?
如果 receiveData
是一个8位(即8字节)数组,但是只接收7个字节的数据,使用 HAL_UART_Receive_IT
函数开启中断接收时,将发生以下情况:
-
接收7个字节:中断驱动的USART将接收7个字节的数据到
receiveData
数组中。 -
缓冲区剩余空间:
receiveData
数组的最后一个字节将保持未被改动,因为没有足够的数据来填充它。 -
回调函数:一旦接收到7个字节,
HAL_UART_RxCpltCallback
回调函数将被触发,因为接收操作已经完成。 -
数据完整性:如果你的应用逻辑期望总是接收8个字节的数据,那么在处理接收到的数据之前,你需要检查实际接收到的数据量。如果只接收到7个字节,你需要确保应用逻辑能够正确处理这种情况,以避免潜在的数据不一致问题。
-
继续接收:如果你想继续接收更多的数据,可以在回调函数中重新调用
HAL_UART_Receive_IT
来启动下一次接收操作。 -
错误处理:如果接收到的数据量少于预期,你可能需要在应用逻辑中实现错误处理机制,比如请求重发数据或采取其他恢复措施。
-
内存未越界:由于
HAL_UART_Receive_IT
只接收指定数量的数据(通过Size
参数指定),所以不会发生缓冲区溢出。
示例代码:
// 假设receiveData是一个8字节大小的数组
uint8_t receiveData[8];
// 启动中断接收,期望接收8个字节,但实际只接收7个字节
HAL_UART_Receive_IT(&huart2, receiveData, 8);
// 接收完成回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART2) {
// 检查实际接收到的数据量
size_t receivedSize = __HAL_UART_GET_RX_SIZE(&huart2);
// 处理接收到的数据
for (size_t i = 0; i < receivedSize; i++) {
// 处理每个接收到的数据字节
}
// 根据需要,可以在这里重新启动接收操作
// HAL_UART_Receive_IT(&huart2, receiveData, 8);
}
}
注意事项:
- 确保在设计通信协议时考虑到接收数据量少于预期的情况,并在应用逻辑中进行适当处理。
- 在回调函数中,检查实际接收到的数据量,并据此做出响应。
- 如果使用固定大小的数组来接收可变长度的数据,需要在应用中实现相应的逻辑来确保数据完整性和系统稳定性。
0.7 参考资料
【【STM32F103C8T6系列】10. 实验例程——485(USART串口)通讯实验】 https://www.bilibili.com/video/BV19w411r7ka/?share_source=copy_web&vd_source=77e36f24add8dc77c362748ffb980148
PDF: STM32G474 开发指南 正点原子 DMG474 开发板教程 第28章
串口原理参考:《 STM32G474 开发指南 正点原子 DMG474 开发板教程 第17章》
1、RS485介绍
485只是物理层,代码逻辑还是UART串口通信,相当于是电路有RS485转换芯片,所以还是用UART串口通信方式
1.1 HAL_UART函数详解
HAL_UART_IRQHandler
是一个中断处理函数,它是HAL库中的标准中断服务例程(ISR),用于处理UART的中断。而下面列出的一系列以 HAL_UART_
开头的回调函数是用户可定义的回调,它们在HAL库中被声明为弱(weak)函数,允许用户根据需要重写它们。
下面是这些回调函数的区别和用途:
-
HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
:- 这是UART的中断处理函数,通常由HAL库在初始化时自动连接到相应的中断向量。
- 它应该由HAL库调用,而不是用户直接调用。
- 这个函数会根据中断的类型(例如接收、发送、错误等)调用相应的内部处理函数。
-
HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
:- 这是传输完成(Transmit Complete)回调函数。
- 它在UART发送操作完成时被调用,即所有数据已经发送出去。
-
HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart)
:- 这是传输过半(Transmit Half Complete)回调函数。
- 它在UART发送操作完成一半时被调用,用于通知用户数据发送进度。
-
HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
:- 这是接收完成(Receive Complete)回调函数。
- 它在UART接收操作完成时被调用,即所有数据已经接收完毕。
-
HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart)
:- 这是接收过半(Receive Half Complete)回调函数。
- 它在UART接收操作完成一半时被调用,用于通知用户数据接收进度。
-
HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
:- 这是错误回调函数。
- 它在UART发生错误时被调用,例如奇偶校验错误、帧错误、噪声检测错误等。
-
HAL_UART_AbortCpltCallback(UART_HandleTypeDef *huart)
:- 这是中止完成(Abort Complete)回调函数。
- 它在UART传输被中止操作完成时被调用。
-
HAL_UART_AbortTransmitCpltCallback(UART_HandleTypeDef *huart)
:- 这是发送中止完成(Abort Transmit Complete)回调函数。
- 它在UART发送操作被用户中止时被调用。
-
HAL_UART_AbortReceiveCpltCallback(UART_HandleTypeDef *huart)
:- 这是接收中止完成(Abort Receive Complete)回调函数。
- 它在UART接收操作被用户中止时被调用。
用户可以根据需要重写这些回调函数,以便在特定事件发生时执行自定义的操作。例如,当接收到数据时,可以在 HAL_UART_RxCpltCallback
中添加代码来处理这些数据。如果用户没有重写这些函数,HAL库提供的默认弱实现将被调用,通常只是什么也不做或者提供一个空函数体。
HAL_UART_IRQHandler
函数是中断处理的核心,而回调函数则提供了一种机制,允许用户在HAL库处理完中断后执行额外的处理。
2、cubemx配置
利用外部晶振,否则内部矩阵容易温漂,RS485容易乱码:
注意:RS485得添加GPIO作为DE,来控制发送/接收模式
3、代码(不行)
似乎是少了DE控制引脚
main.c
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2024 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"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart1;
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
uint8_t aTxBuffer[] = "HelloWorld";
#define COUNTOF(__BUFFER__) (sizeof(__BUFFER__) / sizeof(*(__BUFFER__)))
#define TXBUFFERSIZE (COUNTOF(aTxBuffer) - 1)
/* Size of Reception buffer */
#define RXBUFFERSIZE 100//接收缓冲区,定义100字节
uint8_t aRxBuffer[RXBUFFERSIZE];
__IO ITStatus UartReady = RESET;
uint8_t RX_len;//接收字节计数
void UsartReceive_IDLE(UART_HandleTypeDef *huart)//空闲中断回调函数
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1); //清除中断
RX_len = RXBUFFERSIZE - huart1.RxXferCount; //计算接收数据长度
HAL_UART_AbortReceive_IT(huart); //终止接收
HAL_UART_Receive_IT(&huart1, (uint8_t*)aRxBuffer, RXBUFFERSIZE); //接收完数据后再次打开中断接收函数
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
HAL_UART_Transmit(&huart1, (uint8_t *)aTxBuffer, TXBUFFERSIZE,1000);//发送Helloworld 10字节
if (HAL_UART_Receive_IT(&huart1, (uint8_t *)aRxBuffer, RXBUFFERSIZE) != HAL_OK)//接收中断打开,空闲中断打开
{
}
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
if(RX_len)
{
HAL_UART_Transmit_DMA(&huart1, (uint8_t *)aRxBuffer, RX_len); //如果接收到数据,把接收的内容发送出去
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV2;
RCC_OscInitStruct.PLL.PLLN = 75;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief USART1 Initialization Function
* @param None
* @retval None
*/
static 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 = 9600;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
huart1.Init.ClockPrescaler = UART_PRESCALER_DIV1;
huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_SetTxFifoThreshold(&huart1, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_SetRxFifoThreshold(&huart1, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_DisableFifoMode(&huart1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART1_Init 2 */
/* USER CODE END USART1_Init 2 */
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOF_CLK_ENABLE();
__HAL_RCC_GPIOE_CLK_ENABLE();
/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
main.h
/* USER CODE BEGIN EFP */
void UsartReceive_IDLE(UART_HandleTypeDef *huart);
/* USER CODE END EFP */
stm32g4xx_it.c
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file stm32g4xx_it.c
* @brief Interrupt Service Routines.
******************************************************************************
* @attention
*
* Copyright (c) 2024 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 "stm32g4xx_it.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* 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 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 Prefetch 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 */
}
/******************************************************************************/
/* STM32G4xx 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_stm32g4xx.s). */
/******************************************************************************/
/**
* @brief This function handles USART1 global interrupt / USART1 wake-up interrupt through EXTI line 25.
*/
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
uint32_t isrflags = READ_REG(huart1.Instance->ISR);
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
if((isrflags&UART_IT_IDLE) != RESET) //判断是否为IDLE中断
{
UsartReceive_IDLE(&huart1); //调用IDLE中断处理函数
}
/* USER CODE END USART1_IRQn 1 */
}
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
3.1、可行代码
加上了DE引脚
main.c
4、加上DMA
参考视频:
【【工作STM32】第10集 STM32串口DMA模式与收发不定长数据 | keysking的stm32教程】 https://www.bilibili.com/video/BV1do4y1F7wt/?share_source=copy_web&vd_source=77e36f24add8dc77c362748ffb980148
cubemx配置
代码也就是,将3中的代码的
HAL_UART_Receive_IT
HAL_UART_Transmit_IT
全部改为:
HAL_UART_Receive_DMA
HAL_UART_Transmit_DMA
5、DMA不定长数据收发
将该函数放到main.c重新定义:
补充一个小细节
6、总结