基于中断/DMA方式的串口通信

一、串口通讯协议和RS-232的介绍以及USB/TTL转232模块的工作原理

1、 串口协议和RS-232标准:

(1)串口协议:
串口通讯(Serial Communication)是一种设备间非常常用的串行通讯方式,因为它简单、便捷,因此大部分电子设备都支持该通讯方式,电子工程师在调试设备时也经常使用该通 讯方式输出调试信息。
在计算机科学里,大部分复杂的问题都可以通过分层来简化。如芯片被分为内核层和片上外设;STM32标准库则是在寄存器与用户代码之间的软件层。对于通讯协议,我们也以分层的方式来理解,最基本的是把它分为物理层和协议层。
在这里插入图片描述
(2)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标准,我们可以更好地理解和应用串行通信技术。

2、RS232电平与TTL电平的区别

根据通讯使用的电平标准不同,串口通讯可分为 TTL标准和 RS-232标准:
在这里插入图片描述
从表格中不难看出,两种标准划分的逻辑电压不同。在电子电路中常使用 TTL 的电平标准,理想状态下,使用 5V 表示二进制逻辑 1,使用 0V 表示逻辑 0;而为了增加串口通讯的远距离传输及抗干扰能力,它使用-15V表示逻辑 1,+15V 表示逻辑 0。

3、USB/TTL转232“模块(CH340芯片为例)

(1)基本原理:
USB转串口即实现计算机USB接口到物理串口之间的转换。可以为没有串口的计算机或其他USB主机增加串口,使用USB转串口设备等于将传统的串口设备变成了即插即用的USB设备。

  USB主机检测到USB转串口设备插入后,首先会对设备复位,然后开始USB枚举过程。USB枚举时过程会获取设备描述符、配置描述符、接口描述符等。描述符中会包含USB设备的厂商ID,设备ID和Class类别等信息。操作系统会根据该信息为设备匹配相应的USB设备驱动。

  USB虚拟串口的实现在系统上依赖于USB转串口驱动,一般由厂家直接提供,也可以使用操作系统自带的CDC类串口驱动等。驱动主要分为2个功能,其一注册USB设备驱动,完成对USB设备的控制与数据通讯,其二注册串口驱动,为串口应用层提供相应的实现方法。

串口收发对应的驱动数据流向一览表:

发送or接收数据流向
串口发送串口应用发送数据→USB串口驱动获取数据→驱动将数据经过USB通道发送给USB串口设备→USB串口设备接收到数据通过串口发送
串口接收USB串口设备接收串口数据→将串口数据经过USB打包后上传给USB主机→USB串口驱动获取到通过USB上传的串口数据→驱动将数据保存在串口缓冲区提供给串口应用读取

(2)CH340模块介绍:
CH340电路与实物图:
在这里插入图片描述
TXD:发送端,一般表示为自己的发送端,正常通信必须接另一个设备的RXD。

RXD:接收端,一般表示为自己的接收端,正常通信必须接另一个设备的TXD。

正常通信的时候本身的TXD永远接设备的RXD。

二.基于HAL库中断方式进行串口通信

1.STM32CubeMX配置

(1)STM32CubeMX选择单片机(此处以STM32F103C8T6型号单片机为例)
在这里插入图片描述
(2)配置GPIO:PA0。如果仅仅是完成串口通信的话,这一步可以跳过。但是根据实验要求,为了区分串口通信的开启与关闭,要使用一个LED灯来显示。当串口通信开启(STM32向电脑发送信息)的时候,LED灯亮,当串口通信关闭(STM32停止向电脑发送消息)的时候,LED灯灭。
在这里插入图片描述
(3)配置USART1,我们使用USART1进行数据传输。在这个界面按下图进行配置。我们对USART1的配置要做的只有两件事:一是选择串口工作模式为异步,二是开启USART1全局中断
在这里插入图片描述
(4)进入Project Manager(工程管理),进行工程设置点击生成工程与代码:
在这里插入图片描述
在这里插入图片描述

三.完善通过中断方式控制串口通信的keil5工程

1.本工程中几个函数简介:

HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_int *data, uint16_t Size)
 
/*
	huart:使用哪个串口进行通信
	data: 一个地址,用于保存接受到的数据
	Size: 接收的数据个数
*/

在调用此函数后,程序会将对应串口的接收中断开启,当我们向单片机发送数据时会触发这个中断。在触发这个中断后,程序会接收数据到你传入的地址中,会读取Size个数据。读取完成后,关闭接收中断使能。
由于程序在接收完数据后会关闭接收中断。因此这个函数我们要写在main的死循环中,保证接收中断可以一直开启。

HAL_UART_Transmit_IT:

HAL_GPIO_WritePin(GPIOX,GPIO_PIN_X,GPIO_PIN_STATUS)
/*
	GPIOX:目标GPIO的组号
	GPIO_PIN_X: 目标GPIO的引脚编号 
	GPIO_PIN_STATUS: 引脚状态
*/

使用这个函数开启发送中断,发送寄存器为空时触发中断,将要发送的数据送入发送寄存器并发送。发送完成后关闭中断。在此实验中,我们把它当做普通的发送函数即可。

HAL_GPIO_WritePin:

HAL_GPIO_WritePin(GPIOX,GPIO_PIN_X,GPIO_PIN_STATUS)
/*
	GPIOX:目标GPIO的组号
	GPIO_PIN_X: 目标GPIO的引脚编号 
	GPIO_PIN_STATUS: 引脚状态
*/

使用这个函数修改GPIO_ODR寄存器,将非复用输出的GPIO引脚输出电平设置成自己想要的。

2.完善keil5工程代码:

(1)首先,点击刚刚生成的keil5工程文件,双击main.c文件,然后再main.c中找到图示框住的函数, 接着右击此函数,进入其定义的地方处:
在这里插入图片描述
(2)将图中框住的部分改为SET即可:此步骤是将这个GPIO口设置为高电平,初始时不亮!
在这里插入图片描述
(3)回到main.c文件中,详细编写主要代码:
(1:用字符串进行判断,因此接受变量用数组来储存,Size要改成数组的大小为:6。单片机收到串口助手发的信息后,与"stop2!"和"start"进行匹配。根据匹配结果执行不同的代码。“stop!”,"start"与收到的数据都用uint8_t数组保存。为执行匹配操作,我们需要写一个函数对每一位进行判断与匹配:

int strEqual(char rcData[15],char rcData2[15]){
	for(uint8_t i = 0 ; i < 15 ; i++){
		if (rcData[i] != rcData2[i]) return 0;
	}
	return 1;
}

(2:在main函数前面添加上如下代码(接收信息储存数组,接收信息匹配处理函数,信息标志flag):

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;
}
 
char rcData[6] = "start";
uint8_t flag=1;

(3:main里面的while(1)替换为如下信息接收与发送处理代码:

        if(flag==0)
		{
			HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_SET);
		}
		else if(flag==1)
		{					
			HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_RESET);
		    uint8_t hello[20]="hello windows!\n";
		    HAL_UART_Transmit_IT(&huart1,hello,20);
		    HAL_Delay(600);
		}

(4:在main函数下面重写中断处理函数

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	//当输入的指令为“stop!"时,发送提示并改变flag=0
	if(strEqual(rcData,"stop!"))
	{
		flag=0;
	}
	
	//当输入的指令为"start"时,发送提示并改变flag=1
	else if(strEqual(rcData,"start"))
	{
		flag=1;
	}
	//重新设置中断
    HAL_UART_Receive_IT(&huart1,(uint8_t*)rcData,5);
}

(5:完善之后的main.c的全部代码编写如下所示:

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 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 "usart.h"
#include "gpio.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 ---------------------------------------------------------*/
 
/* USER CODE BEGIN PV */
 
/* USER CODE END PV */
 
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
 
/* USER CODE END PFP */
 
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
 
/* USER CODE END 0 */
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;
}
 
char rcData[6] = "start";
uint8_t flag=1;
 
int main(void)
{
  	HAL_Init();
  	SystemClock_Config();
  	MX_GPIO_Init();
  	MX_USART1_UART_Init();
 
	HAL_UART_Receive_IT(&huart1,(uint8_t*)rcData,5);
	
  	while (1)
  	{
		if(flag==0)
		{
			HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_SET);
		}
		else if(flag==1)
		{					
			HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_RESET);
		    uint8_t hello[20]="hello windows!\n";
		    HAL_UART_Transmit_IT(&huart1,hello,20);
		    HAL_Delay(600);
		}
  }
}
 
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	//当输入的指令为“stop!"时,发送提示并改变flag=0
	if(strEqual(rcData,"stop!"))
	{
		flag=0;
	}
	
	//当输入的指令为"start"时,发送提示并改变flag=1
	else if(strEqual(rcData,"start"))
	{
		flag=1;
	}
	//重新设置中断
    HAL_UART_Receive_IT(&huart1,(uint8_t*)rcData,5);
}
/**
  * @brief System Clock Configuration
  * @retval None
  */
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();
  }
}
/* 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 */

在这里插入图片描述

四.烧录运行结果

在这里插入图片描述

五.基于中断控制串口通信的keil5仿真调试

1、进入keil5仿真:

(1)点击第一步,Target界面中,选择跟正确的晶振大小,使用8MHz的外部晶振:
在这里插入图片描述
(2)接着进行Debug页的设置:
在这里插入图片描述

(3)选择逻辑分析仪:
在这里插入图片描述
(4)点击Setup设置添加要进行观察的引脚波形信息:
波形信息配置:添加波形信息的时候,如果自己选择的串口配置的是USART1,就在添加信息里面输入:USART1_SR;同理可得,如果自己选择的串口配置的是USART2,就在添加信息里面输入:USART2_SR;我项目里配置的是USART1,所以我在添加信息时就输入:USART1_SR。
在这里插入图片描述

2、开始仿真:

(1)进行仿真
在这里插入图片描述
调整图象缩放更容易观察

(2)仿真结果:
在这里插入图片描述

总结:

基于中断/DMA方式的串口通信实验需要根据具体应用需求选择合适的通信协议和方式,并正确配置参数和处理数据。通过合理的配置和优化,可以提高数据传输的效率和可靠性,满足实际应用需求。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在使用STM32 HAL库进行串口通信时,可以使用中断接收和DMA发送的方式来提高通信效率。 首先需要初始化串口,并配置接收中断DMA发送。以下是一个示例代码: ``` UART_HandleTypeDef huart1; void MX_USART1_UART_Init(void) { huart1.Instance = USART1; huart1.Init.BaudRate = 115200; 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; if (HAL_UART_Init(&huart1) != HAL_OK) { Error_Handler(); } /* Enable the UART Parity Error Interrupt */ HAL_UART_Receive_IT(&huart1, rxBuffer, 1); /* Enable the DMA transfer for transmit */ HAL_UART_Transmit_DMA(&huart1, txBuffer, strlen((char *)txBuffer)); } void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart == &huart1) { /* process received data */ HAL_UART_Receive_IT(&huart1, rxBuffer, 1); } } void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { if (huart == &huart1) { /* transmit completed */ } } void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) { if (huart == &huart1) { /* handle UART error */ } } ``` 在上面的代码中,`USART1`是串口的实例,`rxBuffer`和`txBuffer`是接收和发送缓冲区。在串口初始化时,使用`HAL_UART_Receive_IT`函数开启接收中断,并使用`HAL_UART_Transmit_DMA`函数开启DMA发送。在接收中断回调函数`HAL_UART_RxCpltCallback`中,可以对接收到的数据进行处理,并继续接收下一个字节。在发送完成回调函数`HAL_UART_TxCpltCallback`中,可以进行一些操作,例如将发送缓冲区中的数据更新,等待下一次发送。在出现UART错误时,`HAL_UART_ErrorCallback`函数会被调用,可以在该函数中处理错误。 需要注意的是,在使用DMA发送时,需要保证发送缓冲区的数据不会被修改,直到DMA发送完成。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值