前言
STM32是一款基于ARM Cortex-M处理器的32位微控制器系列,具有丰富的外设和强大的性能,广泛应用于嵌入式系统开发。串口通信是嵌入式系统中常用的通信方式之一,可以实现与外部设备的数据交互。
本实验旨在介绍如何在STM32微控制器上使用中断和DMA(直接内存访问)方式进行串口通信。中断方式通过使能串口接收中断,当接收到数据时,通过中断服务函数进行数据处理。DMA方式则通过配置DMA通道,实现数据的直接传输,减轻CPU的负担,提高通信效率。
在本实验中,我将使用STM32的内部串口模块和相关外设,通过中断和DMA方式实现串口通信。首先,我将介绍STM32串口模块的配置和初始化。然后,我们将详细讲解中断方式和DMA方式的实现步骤和原理,并给出相应的代码示例。最后,我们将进行实验验证,通过发送和接收数据,检验通信功能的正确性和稳定性。
通过完成本实验,够掌握STM32中断和DMA方式的串口通信原理和实现方法,为后续的嵌入式系统开发奠定基础。同时,也将深入了解STM32的外设配置和使用,提升对嵌入式系统的理解和实践能力。
STM32串口通信硬件以及通信协议
串口协议
STM32F103串口通信协议
STM32F103微控制器的串口通信协议,该协议使用UART串口通信进行数据传输,并定义了数据帧格式、通信流程和错误处理等内容。
数据帧格式
数据帧采用以下格式进行传输
起始位 | 数据位 | 奇偶校验位 | 停止位 |
---|---|---|---|
1位 | 8位 | 1位 | 1位 |
起始位:逻辑低电平表示起始位,用于标识数据帧的开始。
数据位:8位数据位用于传输实际的数据。
奇偶校验位:用于检测数据传输过程中的错误,可以选择奇校验、偶校验或无校验。
停止位:逻辑高电平表示停止位,用于标识数据帧的结束。
通信流程
初始化串口通信参数,包括波特率、数据位、校验位和停止位等。
发送端准备数据,将数据按照数据帧格式组织,并发送给接收端。
接收端接收数据,检查起始位、校验位和停止位的正确性。
如果数据校验正确,接收端将数据提取出来进行处理;如果数据校验错误,接收端发送错误消息给发送端。
发送端和接收端通过该协议进行连续的数据传输。
错误处理
在数据传输过程中,可能会发生以下错误情况:
起始位错误:接收端未正确检测到起始位,导致数据解析错误。
校验位错误:接收端在校验数据时发现错误,表明数据传输过程中发生了错误。
停止位错误:接收端未正确检测到停止位,导致数据解析错误。
在出现错误时,接收端应向发送端发送错误消息,以便重新发送数据或采取其他纠错措施。
示例代码
以下是使用STM32Cube HAL库配置STM32F103串口通信的示例代码:
// 初始化串口通信
void UART_Init()
{
huart.Instance = USART1;
huart.Init.BaudRate = 9600;
huart.Init.WordLength = UART_WORDLENGTH_8B;
huart.Init.StopBits = UART_STOPBITS_1;
huart.Init.Parity = UART_PARITY_NONE;
huart.Init.Mode = UART_MODE_TX_RX;
huart.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart.Init.OverSampling = UART_OVERSAMPLING_16;
HAL_UART_Init(&huart);
}
// 发送数据
void UART_SendData(uint8_t* data, uint16_t length)
{
HAL_UART_Transmit(&huart, data, length, HAL_MAX_DELAY);
}
// 接收数据
void UART_ReceiveData(uint8_t* data, uint16_t length)
{
HAL_UART_Receive(&huart, data, length, HAL_MAX_DELAY);
}
以上代码演示了如何使用STM32Cube HAL库配置和使用串口通信功能。你可以根据具体的需求进行修改和扩展。
以上就是基于STM32F103的串口通信协议的介绍,通过该协议可以实现可靠的数据传输和错误处理。你可以根据实际应用需求进行相应的调整和扩展。
RS-232标准
RS-232(Recommended Standard 232)是一种常用的串行通信标准,用于在计算机和外部设备之间进行数据传输。它定义了电气特性、信号传输方式和接口连接等方面的规范。
电气特性
RS-232标准规定了通信线路的电气特性,包括信号电平、波特率和时钟频率等。根据RS-232标准,信号电平分为正负两种,分别表示逻辑1和逻辑0。通常,正电平表示逻辑0,负电平表示逻辑1。波特率是指数据传输的速率,通常以每秒传输的位数(bps)表示。时钟频率则是指传输中使用的时钟信号频率。
信号传输方式
RS-232标准使用异步传输方式,即每个数据字节之间没有固定的时间间隔。数据传输以数据帧为单位,每个数据帧包括起始位、数据位、校验位和停止位。起始位用于标识数据帧的开始,停止位用于标识数据帧的结束。数据位用于传输实际的数据,校验位用于检测数据传输中的错误。
接口连接
RS-232标准规定了连接计算机和外部设备的接口连接方式和引脚定义。一般来说,RS-232标准使用9针或25针的D型插座连接器。其中,9针的连接器常用于较新的设备,而25针的连接器则常用于较旧的设备。接口连接的引脚定义包括数据线、控制线和地线等。
9线连接示意图:
应用领域
由于其广泛的应用和成熟的技术,RS-232标准在许多领域中仍然被广泛使用。它常用于计算机与打印机、调制解调器、终端设备和工业自动化设备等外部设备之间的通信。尽管现在有许多新的通信标准和接口出现,但RS-232标准仍然在许多传统应用和设备中发挥着重要作用。
以上就是RS-232标准的介绍,该标准定义了串行通信的电气特性、信号传输方式和接口连接等方面的规范。通过了解RS-232标准,我们可以更好地理解和应用串行通信技术。
RS232电平与TTL电平的区别
RS232电平和TTL电平是两种常见的串行通信电平标准,它们在电气特性、电平范围和使用环境等方面存在一些区别。
RS232电平
电气特性:RS232电平使用正负电平表示逻辑1和逻辑0,通常正电平表示逻辑0,负电平表示逻辑1。根据RS232标准,信号电平范围在-15V至+15V之间。
电平范围:RS232电平的电压范围较大,可以在较长距离上进行可靠的数据传输。
使用环境:由于RS232电平的电压范围较大,它主要用于长距离和工业环境中的通信应用。
TTL电平
电气特性:TTL电平使用0V和5V(或3.3V)的电平表示逻辑1和逻辑0。通常0V表示逻辑0,5V(或3.3V)表示逻辑1。
电平范围:TTL电平的电压范围较小,一般在0V至5V(或3.3V)之间。
使用环境:由于TTL电平的电压范围较小,它主要用于短距离和低功耗的通信应用,如嵌入式系统和电子设备之间的通信。
对比
特性 RS232电平 TTL电平
电气特性 正负电平 0V和5V(或3.3V)
电平范围 -15V至+15V 0V至5V(或3.3V)
适用环境 长距离和工业环境 短距离和低功耗环境
RS232和TTL电平在电气特性、电平范围和使用环境方面存在明显的区别。根据具体的应用需求,我们可以选择适合的电平标准来进行串行通信。需要注意的是,在使用不同电平标准时,需要进行电平转换以确保正常的数据传输。
基于HAL库中断方式进行串口通信
STM32CubeMX配置
STM32CubeMX选择单片机(此处以STM32F103C8T6型号单片机为例)
在main函数前面定义全局变量
char c;//指令 0:停止 1:开始
char message[]="hello Windows\n";//输出信息
char tips[]="CommandError\n";//提示1
char tips1[]="Start.....\n";//提示2
char tips2[]="Stop......\n";//提示3
int flag=0;//标志 0:停止发送 1.开始发送
在main函数中设置接收中断
函数:
HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
函数功能
该函数用于通过中断方式接收一定长度的数据。当接收到指定长度的数据或发生错误时,会触发相应的中断回调函数。
参数
huart: UART_HandleTypeDef类型的指针,表示UART控制器的句柄。
pData: uint8_t类型的指针,表示接收数据的缓冲区指针。
Size: uint16_t类型的参数,表示要接收的数据的长度。
返回值
HAL_OK:函数执行成功。
HAL_BUSY:UART正忙,不能执行接收操作。
HAL_ERROR:函数执行出错。
说明
该函数会启动UART接收,并启用接收完成中断。当接收到指定长度的数据时,会触发UART_RX_CPLT_Callback()回调函数,用户可以在回调函数中对接收到的数据进行处理。
需要注意的是,为了使能接收完成中断,需要提前在中断优先级组配置中设置相应的中断优先级,以便正确处理中断。
示例
uint8_t rxBuffer[10];
UART_HandleTypeDef huart;
// 初始化UART句柄和其他相关配置
// 启动接收10个字节的数据
HAL_UART_Receive_IT(&huart, rxBuffer, 10);
// 接收完成中断回调函数
void UART_RX_CPLT_Callback(UART_HandleTypeDef *huart)
{
// 在这里处理接收到的数据
}
在while循环中添加一下代码
if(flag==1)
{
//发送信息
HAL_UART_Transmit(&huart1, (uint8_t *)&message, strlen(message),0xFFFF);
//延时
HAL_Delay(1000);
};
可能出现以下报错
解决方法
添加以下头文件即可
#include
程序
在main函数下面添加以下代码:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
//当输入的指令为0时,发送提示并改变flag
if(c=='0')
{
flag=0;
HAL_UART_Transmit(&huart1, (uint8_t *)&tips2, strlen(tips2),0xFFFF);
}
//当输入的指令为1时,发送提示并改变flag
else if(c=='1')
{
flag=1;
HAL_UART_Transmit(&huart1, (uint8_t *)&tips1, strlen(tips1),0xFFFF);
}
//当输入不存在指令时,发送提示并改变flag
else
{
flag=0;
HAL_UART_Transmit(&huart1, (uint8_t *)&tips, strlen(tips),0xFFFF);
}
//重新设置中断
HAL_UART_Receive_IT(&huart1, (uint8_t *)&c, 1);
}
基于HAL库实现DMA串口通信
DMA(Direct Memory Access ) 简介
Direct Memory Access(DMA)是一种计算机系统中用于数据传输的技术。它允许数据在外设和内存之间直接进行传输,而无需CPU的干预。DMA技术可以提高数据传输的效率,减轻CPU的负担。
DMA的工作原理
DMA的工作原理如下:
1、CPU配置DMA控制器,指定数据的源地址、目的地址和传输长度等参数。
2、DMA控制器根据CPU的配置,直接从源地址读取数据,然后将数据写入目的地址。
3、一旦数据传输完成,DMA控制器会发出中断信号通知CPU。
DMA的优势
DMA技术相比于CPU直接处理数据传输具有以下优势:
1、减轻CPU负担:DMA技术可以在数据传输过程中不需要CPU的干预,从而释放CPU的处理能力,使其可以同时执行其他任务。
2、高效的数据传输:DMA可以实现高速、连续的数据传输,而不会因为CPU的繁忙而导致传输延迟。
3、灵活性:DMA可以支持多种数据传输模式,如单次传输、循环传输和块传输等,以满足不同的应用需求。
DMA的应用领域
DMA技术广泛应用于各种领域,包括但不限于以下方面:
1、音频和视频处理:DMA技术可以实现高速的音频和视频数据传输,用于实时的音频和视频处理。
2、网络通信:DMA技术可以用于网络数据包的接收和发送,提高网络通信的效率。
3、存储设备:DMA技术可以用于硬盘、固态硬盘等存储设备的数据读写操作,提高数据传输速度。
4、图形处理:DMA技术可以用于图形处理器(GPU)和显示器之间的数据传输,提高图形渲染的效率。
DMA的应用示例
下表是一些常见的DMA应用示例:
应用实例 | 描述 |
---|---|
音频播放 | 使用DMA从内存中读取音频数据,并传输给音频编解码器进行播放。 |
图像采集 | 使用DMA从图像传感器中读取图像数据,并传输到内存或显示器进行处理或显示。 |
存储器备份 | 使用DMA从一个存储设备读取数据,并将数据直接传输到另一个存储设备进行备份。 |
网络数据传输 | 使用DMA从网络接口读取数据包,并将数据包传输到内存进行处理或发送。 |
以上就是对Direct Memory Access(DMA)的简介。DMA技术可以提高数据传输的效率,减轻CPU的负担,广泛应用于各个领域。
STM32CubeMX配置
选择和上面相同的单片机,创建工程
此处省略
配置RCC,开启外部高速时钟
然后生成代码即可!
程序
代码解释
HAL_UART_Transmit_DMA()
函数原型**
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
函数说明**
使用DMA传输UART数据,将指定长度的数据从pData缓冲区通过DMA传输到UART外设寄存器。
参数说明
huart: UART_HandleTypeDef结构体指针,表示UART外设句柄
pData: 数据缓冲区指针,需要传输的数据存储在这个缓冲区
Size: 需要传输的数据长度
返回值
HAL_OK: 传输成功
HAL_ERROR: 传输失败
HAL_BUSY: DMA传输正在进行中
HAL_TIMEOUT: 传输超时
示例
uint8_t tx_buffer[] = "Hello World";
HAL_StatusTypeDef state;
state = HAL_UART_Transmit_DMA(&huart2, (uint8_t*)tx_buffer, strlen(tx_buffer));
if(state != HAL_OK) {
// error handling
}
首先定义一个字符串缓冲区,然后调用HAL_UART_Transmit_DMA函数通过DMA将缓冲区数据发送到UART外设。
HAL_UART_Receive_DMA():串口DMA模式接收
函数原型
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
函数说明
使用DMA从UART外设接收指定长度的数据,并存储到pData缓冲区。
参数说明
huart: UART_HandleTypeDef结构体指针,表示UART外设句柄
pData: 接收数据缓冲区指针
Size: 需要接收的数据长度
返回值
HAL_OK: 接收成功
HAL_ERROR: 接收失败
HAL_BUSY: DMA传输正在进行中
HAL_TIMEOUT: 接收超时
示例
uint8_t rx_buffer[100];
HAL_StatusTypeDef state;
state = HAL_UART_Receive_DMA(&huart2, rx_buffer, 100);
if(state != HAL_OK) {
// error handling
}
// 在DMA完成后,rx_buffer中存储了接收到的数据
通过HAL_UART_Receive_DMA函数启动DMA,从UART外设接收100字节数据到rx_buffer缓冲区。
实验任务
STM32系统给上位机(win10)连续发送“hello windows!”;当上位机给stm32发送字符“stop”后,stm32暂停发送“hello windows!”;发送一个字符“start”后,stm32继续发送;
编写代码的思路
定义发送和接收缓冲区,以及标志变量is_sending。
配置UART,设置接收中断。
发送任务:
检查is_sending标志,如果为1则继续发送;否则跳出。
发送字符串"hello windows!"。
判断发送完成标志,如果为1则清除标志并跳回开始发送。
接收任务:
在接收中断服务函数中,将接收到的数据保存到接收缓冲区。
判断接收到的字符,如果为’stop’则设置is_sending为0;如果为’start’则设置为1。
主函数中循环调用发送和接收任务。
代码框架
uint8_t tx_buf[] = "hello windows!";
uint8_t rx_buf;
uint8_t is_sending = 1;
void UART_Send(void) {
if(is_sending == 1) {
HAL_UART_Transmit(&huart, tx_buf, sizeof(tx_buf), 1000);
}
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if(rx_buf == 'stop')
is_sending = 0;
else if(rx_buf == 'start')
is_sending = 1;
}
int main(void) {
while(1) {
UART_Send();
UART_Receive();
}
}
通过HAL库完成UART发送和接收,实现PC与STM32的双向通信控制。
最终完成后main函数中的代码:
#include "main.h"
#include "dma.h"
#include "usart.h"
#include "gpio.h"
void SystemClock_Config(void);
uint8_t flag=1;
uint8_t rx_buf[6];//接收串口数据存放的数组
int strEqual(char rcData[6],char rcData2[6])
{
for(uint8_t i = 0 ; i < 6 ; i++){
if (rcData[i] != rcData2[i]) return 0;
}
return 1;
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
//当输入的指令为“stop!"时,发送提示并改变flag=0
if(strEqual(rx_buf,"stop!"))
{
flag=0;
}
//当输入的指令为"start"时,发送提示并改变flag=1
else if(strEqual(rx_buf,"start"))
{
flag=1;
}
HAL_UART_Receive_DMA(&huart1,(uint8_t*)rx_buf,5);
}
int main(void)
{
HAL_Init();
uint8_t message[] = "hello windows!\n"; //定义数据发送数组
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
HAL_UART_Receive_DMA(&huart1,(uint8_t*)rx_buf,5);//设置DMA接收到的数据存放在rx_buf中
while (1)
{
if(flag==1)
{
HAL_UART_Transmit_DMA(&huart1, (uint8_t *)message, sizeof(message));
HAL_Delay(600);
}
}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
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_HSI;
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_0) != HAL_OK)
{
Error_Handler();
}
}
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 */
用Keil的软件仿真逻辑分析仪功能观察串口输出波形
参考文章:
链接:
https://blog.csdn.net/weixin_63019977/article/details/133749827?spm=1001.2014.3001.5502
总结
在这个的实验的基础上完成了对于利用中断控制串口通信:向单片机发送连续字符串的实验。
总的来说,是温习前面已经会了的中断控制串口通信;从局部来说,也是进步:学习并且尝试了新的内容:向单片机发送连续字符串的实验而不是单个字符。