糖尿病康复,内容丰富有趣,生活中的好帮手!
糖尿病康复 > 嵌入式系统开发11——中断及串口通信进阶

嵌入式系统开发11——中断及串口通信进阶

时间:2024-03-05 08:05:39

相关推荐

嵌入式系统开发11——中断及串口通信进阶

本文主要介绍stm32中断、DMA通信原理和编程方法,在理论学习的基础上,使用stm32tubemx和HAL库,完成STM32中断方式点灯、中断方式的串口通信以及DMA方式的串口通信编程练习。

目录

一、什么是中断二、STM32外部中断模式控制LED亮灭1、任务要求2、设计思路3、创建工程4、代码编写5、编译6、硬件连接7、烧录8、运行效果9、改进三、HAL库UART函数库介绍1、串口发送/接收函数2、串口中断函数3、串口查询函数四、中断方式实现串口通信1、任务要求2、创建工程3、代码编写4、编译5、硬件连接6、烧录7、运行效果8、进阶练习五、DMA简介六、STM32采用DMA方式实现串口通信1、任务要求2、创建工程3、代码编写4、编译5、硬件连接6、烧录7、运行效果七、总结

一、什么是中断

中断通常被定义为一个事件,该事件能够改变处理器执行指令的顺序。这样的事件与 CPU 芯片内外部硬件电路产生的电信号相对应。

中断是指计算机在执行期间,系统内发生任何非寻常的或非预期的急需处理事件,使得CPU暂时中断当前正在执行的程序而转去执行相应的事件处理程序,待处理完毕后又返回原来被中断处继续执行或调度新的进程执行的过程。

中断分为同步中断异步中断

同步中断——同步中断是当指令执行时由 控制单元产生的,之所以称为同步,是因为只有在一条指令终止执行后 CPU 才会发出中断

异步中断——异步中断是由其他硬件设备依照 CPU 时钟信号随机产生的。

通常我们所说的中断指的是异步中断,我们将同步中断称为异常。(异常是由程序的错误产生的,或者是由内核必须处理的异常条件产生的)

特点:

应用程序不必关心中断的发生与处理中断服务程序不必关心应用程序的执行状态中断是“上层应用”与“底层代码”的“分割边界”

中断的作用

跟据中断的定义,我们可以通过中断使处理器转而去优先运行正常控制流之外的代码。

当中断信号达到肘, CPU 必须停止它当前正在做的事情,并且切换到一个另一个活动。为了做到这就要在内核态堆钱保存程序计数器的当前值 (寄存器的内容) ,并把与中断类型相关的地址放进程序计数量。

中断处理是指CPU响应中断,转入中断处理程序,系统开始处理中断。

中断响应是指CPU收到中断请求后转向相应的事件处理程序。

开中断后,系统就可以响应其他的中断了,关中断后,系统不响应其他的中断除非优先级高的中断。

中断屏蔽是指在中断请求产生后,系统用软件方式有选择地封锁部分中断而允许其余部分中断仍能得到响应。

中断的类型及优先级

中断的类型

硬中断:通过处理器中断信号线产生的中断软中断:通过非法指令或特殊指令触发的中断

中断优先级

多个中断同时出现时,处理器先响应高优先级的中断低优先级中断的ISR执行时,可以被高优先级中断再次打断ISR比App Code拥有更高的执行优先级

二、STM32外部中断模式控制LED亮灭

1、任务要求

stm32F103核心板的GPIOA端一管脚接一个LED,GPIOB端口一引脚接一个开关(用杜邦线模拟代替)。采用中断模式编程,当开关接高电平时,LED亮灯;接低电平时,LED灭灯

2、设计思路

这里我选用的核心板为STM32F103C8T6最小核心板

设置PA5端接LEDPB5模拟开关,通过PB5接入高低电平来模拟开关开断

A5输出控制灯的亮灭,设置为GPIO_Output

B5模拟开关,设置为GPIO_EXTI5

A0持续输出高电平,设置为GPIO_Output,当开关B5接到A0时,LED亮灯

A2持续输出低电平,设置为GPIO_Output,当开关B5接到A2时,LED灭灯

3、创建工程

芯片选择

打开STM32CubeMX,点击ACCESS TO MCU SELECTOR

选择对应的芯片,这里我用到的是STM32F103C8T6,在搜索框输入STM32F103C8,双击搜索到的芯片

外设设置

设置SYS,点击System Core ->SYS,将Debug改为Serial Wire

设置指示灯LED引脚PA5,设置引脚模式为输出模式GPIO_Output,对于LED对应的PA5管脚,名字设为LED,其他默认即可

设置按键引脚PB5,设置引脚为外部中断功能,PB5与外部中断线EXIT5连接 ,设置为GPIO_EXIT5GPIO mode设置为External Interrupt Mode with Rising/Falling edge trigger detection,即上升沿和下降沿都可以触发,名字设为SWITCH

External Interrupt Mode with Rising edge trigger detection上升沿

External Interrupt Mode with Falling edge trigger detection下降沿

External Interrupt Mode with Rising/Falling edge trigger detection上升沿和下降沿

设置A0GPIO_Output,输出高电平

设置A2GPIO_Output,持续输出低电平,这里默认设置就好

使能对应的外部中断线,点击Enabled

配置中断优先级

大多数情况下不必设置中断优先级,直接使用中断编号设置的默认中断优先级

时钟设置

将HCLK设置为36MHz

生成工程

设置工程名字,存储路径和编译环境

4、代码编写

打开生成的项目,找到stm32f1xx_it.c

找到EXTI9_5_IRQHandler这个函数,右键HAL_GPIO_EXTI_IRQHandler这个语句,选择Go to Definition of ’ …,或者是直接按F12跳到该函数

这时,会提示如下信息,系统提示找不到该函数

修复方法很简单,点击魔法棒,把 Browse Information 沟选上,然后rebuild重新编译一次

此时再次跳转就可以成功跳转到该函数处了,HAL_GPIO_EXTI_IRQHandler是一个中断服务函数,当捕获到上升沿或者下降沿,就会触发中断,进入到这个函数里面

然后就会执行HAL_GPIO_EXTI_Callback(GPIO_Pin)函数,此函数为回调函数,向下找就可以找到该函数,我们打开可以发现前面有个weak。前面的__weak表示此函数为虚函数,这个函数该函数是给用户自己重写的,可以在这里根据不同的中断来执行不同的处理。在这里我们需要根据B5的不同中断来实现A5电平的转变,从而实现LED的亮灭。

在将该函数替换成下面这一段代码

函数代码:

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){if(GPIO_Pin == SWITCH_Pin){//获取B5的电位GPIO_PinState pinState = HAL_GPIO_ReadPin(SWITCH_GPIO_Port,SWITCH_Pin);//低电位if(pinState==GPIO_PIN_RESET)HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_RESET);//把A5变为低电位,灯灭//高电位elseHAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_SET);//把A5变为高电位,灯亮}}

这里会提示代码中有很多错误,这是因为在cubeMX里面定义的引脚名称,在工程里面全部都定义在main.h里面,而在stm32f1xx_hal_gpio.c里面没有包含main.h这个头文件

此时只需要在stm32f1xx_hal_gpio.c前面加上一句#include "main.h"即可,这时这里就没有错误了

5、编译

点击魔法棒,在Output下勾选Create HEX File

Target下将编译器改为Version 5

Debug下选择Use Simulator,将Dialog DLL改为DARMSTM.DLL,将Paremeter改为-pSTM323F103C8

点击 Rebuild 进行编译

6、硬件连接

这里分为烧录电路的连接实验电路的连接

烧录电路的连接:

连接方法:

注意将核心板上的BOOT0设置为1,BOOT1设置为0

实验电路的连接:

7、烧录

将硬件连接到电脑串口上,打开FlyMcu烧录软件,找到生成的hex文件,点击打开

搜索串口

设置波特率为115200bps

点击开始编程,进行烧录

8、运行效果

GPIOB5端口一引脚接一个开关(用杜邦线模拟代替),当开关B5接到A2时,输入高电平,LED亮灯;当开关B5接到A0时,输入低电平,LED灭灯

实现效果:

STM32外部中断模式控制LED亮灭(浮空闪烁)

可以发现,当B5不接输入的时候,LED灯会一直闪烁,这是因为GPIO悬空时的值是不确定的,,一定要上拉或下拉,有的单片机内部集成了上拉或下拉电阻,就不需要在外围电路加电阻了。于是,我做了如下改进

9、改进

在使用CubeMX配置STM32时,将PB5设置为Pull-Up,即设置为下拉,在不接入输入时,默认输出高电平,不触发中断,LED保持熄灭,其他步骤不变

实现效果:

STM32外部中断方式实现开关LED(浮空熄灭)

三、HAL库UART函数库介绍

UART结构体定义

UART_HandleTypeDef huart1;

1、串口发送/接收函数

HAL_UART_Transmit();串口发送数据,使用超时管理机制HAL_UART_Receive();串口接收数据,使用超时管理机制HAL_UART_Transmit_IT();串口中断模式发送HAL_UART_Receive_IT();串口中断模式接收HAL_UART_Transmit_DMA();串口DMA模式发送HAL_UART_Transmit_DMA();串口DMA模式接收

说明:

串口发送数据

HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)

功能:串口发送指定长度的数据。如果超时没发送完成,则不再发送,返回超时标志(HAL_TIMEOUT)

参数:

UART_HandleTypeDef *huart UATR的别名 如 : UART_HandleTypeDef

huart1:别名为huart1

*pData:需要发送的数据

Size:发送的字节数

Timeout:最大发送时间,发送数据超过该时间退出发送

示例:

HAL_UART_Transmit(&huart1, (uint8_t *)ZZX, 3, 0xffff);//串口发送三个字节数据,最大传输时间0xffff

2、串口中断函数

HAL_UART_IRQHandler(UART_HandleTypeDef *huart); //串口中断处理函数HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart); //串口发送中断回调函数HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart); //串口发送一半中断回调函数(用的较少)HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart); //串口接收中断回调函数HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);//串口接收一半回调函数(用的较少)HAL_UART_ErrorCallback(); //串口接收错误函数

串口接收中断回调函数

HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);

功能:HAL库的中断进行完之后,并不会直接退出,而是会进入中断回调函数中,用户可以在其中设置代码,串口中断接收完成之后,会进入该函数,该函数为空函数,用户需自行修改。

参数:

UART_HandleTypeDef *huart UATR的别名

如 : UART_HandleTypeDef huart1; 别名就是huart1

举例:

HAL_UART_RxCpltCallback(&huart1){//用户设定的代码 }

串口中断处理函数

HAL_UART_IRQHandler(UART_HandleTypeDef *huart);

功能:对接收到的数据进行判断和处理 判断是发送中断还是接收中断,然后进行数据的发送和接收,在中断服务函数中使用。

如果接收数据,则会进行接收中断处理函数

/* UART in mode Receiverif((tmp_flag != RESET) && (tmp_it_source != RESET)){UART_Receive_IT(huart);}

如果发送数据,则会进行发送中断处理函数

/* UART in mode Transmitterif (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET)){UART_Transmit_IT(huart);return;}

3、串口查询函数

HAL_UART_GetState(); //判断UART的接收是否结束,或者发送数据是否忙碌

举例

while(HAL_UART_GetState(&huart4) == HAL_UART_STATE_BUSY_TX)//检测UART发送结束

四、中断方式实现串口通信

1、任务要求

采用串口中断方式重做上周的串口通信作业,分别实现:

1)当stm32接收到字符“s”时,停止持续发送“hello windows!”; 当接收到字符“t”时,持续发送“hello windows!”(提示:采用一个全局标量做信号灯);

2)当stm32接收到字符“stop stm32!”时,停止持续发送“hello windows!”; 当接收到字符“go stm32!”时,持续发送“hello windows!”(提示:要将接收到的连续字符保存到一个字符数组里,进行判别匹配)。

2、创建工程

打开STM32CubeMX,点击ACCESS TO MCU SELECTOR

选择对应的芯片,这里我用到的是STM32F103C8T6,在搜索框输入STM32F103C8,双击搜索到的芯片

设置RCC

设置SYS,将Debug改为Serial Wire

设置USART1,设置MODE为异步通信,波特率为115200 Bits/s,传输数据长度为8 Bit,奇偶检验无,停止位1

设置NVIC,点击 NVIC Settings 一栏使能接收中断

修改项目名称、存储位置和编译器版本,创建项目

打开项目,点击Open Project

3、代码编写

在main函数前定义全局变量:

char c;//指令 s:停止 t:开始char hello[]="hello windows!\n";//输出信息char tips[]="CommandError\n";//提示1,命令错误char tips1[]="Start.....\n";//提示2,开始传输char tips2[]="Stop......\n";//提示3,停止传输int flag=0;//标志 s:停止发送 t:开始发送

在main函数中设置接收中断:

函数说明:

函数原型

HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

功能

功能:串口中断接收,以中断方式接收指定长度数据。

大致过程是,设置数据存放位置,接收数据长度,然后使能串口接收中断。

接收到数据时,会触发串口中断。

再然后,串口中断函数处理,直到接收到指定长度数据

而后关闭中断,进入中断接收回调函数,不再触发接收中断。(只触发一次中断)

参数

UART_HandleTypeDef *huart UATR的别名

huart1 *pData 接收到的数据存放地址

Size 接收的字节数

HAL_UART_Receive_IT(&huart1, (uint8_t *)&c, 1);

main函数中的while循环里面添加传输代码:

if(flag==1){//发送信息HAL_UART_Transmit(&huart1, (uint8_t *)&hello, strlen(hello),0xFFFF); //延时HAL_Delay(1000);}

这里用到了strlen()函数,需要在main.c前面加上一句#include "string.h",来声明包含strlen()函数的头文件

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

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){//当输入的指令为s时,发送提示并改变flagif(c=='s'){flag=0;HAL_UART_Transmit(&huart1, (uint8_t *)&tips2, strlen(tips2),0xFFFF); }//当输入的指令为t时,发送提示并改变flagelse if(c=='t'){flag=1;HAL_UART_Transmit(&huart1, (uint8_t *)&tips1, strlen(tips1),0xFFFF); }//当输入不存在指令时,发送提示并改变flagelse {flag=0;HAL_UART_Transmit(&huart1, (uint8_t *)&tips, strlen(tips),0xFFFF); }//重新设置中断HAL_UART_Receive_IT(&huart1, (uint8_t *)&c, 1); }

** main.c 修改部分代码:**

#include "main.h"#include "usart.h"#include "gpio.h"#include <string.h>void SystemClock_Config(void);char c;//指令 s:停止 t:开始char hello[]="hello windows!\n";//输出信息char tips[]="CommandError\n";//提示1,命令错误char tips1[]="Start.....\n";//提示2,开始传输char tips2[]="Stop......\n";//提示3,停止传输int flag=0;//标志 s:停止发送 t:开始发送int main(void){HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART1_UART_Init();//设置接受中断HAL_UART_Receive_IT(&huart1, (uint8_t *)&c, 1);//当flag为1时,每秒发送一次信息//当flag为0时,停止while (1){if(flag==1){//发送信息HAL_UART_Transmit(&huart1, (uint8_t *)&hello, strlen(hello),0xFFFF); //延时HAL_Delay(1000);}}}void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){//当输入的指令为s时,发送提示并改变flagif(c=='s'){flag=0;HAL_UART_Transmit(&huart1, (uint8_t *)&tips2, strlen(tips2),0xFFFF); }//当输入的指令为t时,发送提示并改变flagelse if(c=='t'){flag=1;HAL_UART_Transmit(&huart1, (uint8_t *)&tips1, strlen(tips1),0xFFFF); }//当输入不存在指令时,发送提示并改变flagelse {flag=0;HAL_UART_Transmit(&huart1, (uint8_t *)&tips, strlen(tips),0xFFFF); }//重新设置中断HAL_UART_Receive_IT(&huart1, (uint8_t *)&c, 1); }/* USER CODE END 4 *//*** @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 */}

4、编译

点击魔法棒,在Output下勾选Create HEX File

Target下将编译器改为Version 5

Debug下选择Use Simulator,将Dialog DLL改为DARMSTM.DLL,将Paremeter改为-pSTM323F103C8

点击Rebuild进行编译

5、硬件连接

连接方法:

注意将核心板上的BOOT0设置为1,BOOT1设置为0

6、烧录

打开FlyMcu进行烧录

7、运行效果

打开串口调试助手 SSCOM,打开并配置串口设置

点击打开串口

这里一定要取消勾选加回车换行,否则就会出现下面这种情况,因为勾选后,串口助手会在发送数据后面加上回车换行,然后就会导致不能实现正常的功能

在发送框输入“t”,点击发送,电脑向stm32发送字母“t”stm32接收到之后输出提示信息,并开始发送数据,每间隔一秒发送一次hello windows!

在发送框输入“s”,点击发送,电脑向stm32发送字母“s”stm32接收到之后输出提示信息,并停止发送数据

再次发送“t”“s”stm32仍然可以收发信息

实现效果:

中断实现串口通信(基础版)

8、进阶练习

当stm32接收到字符“stop stm32!”时,停止持续发送“hello windows!”; 当接收到字符“go stm32!”时,持续发送“hello windows!”(提示:要将接收到的连续字符保存到一个字符数组里,进行判别匹配)。

(1)新建工程,按照上面的串口通信重新创一个即可

(2)在mian.c中写入以下代码

/* USER CODE BEGIN Header *//********************************************************************************* @file : main.c* @brief: Main program body******************************************************************************* @attention** Copyright (c) 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"#include "stdio.h"#include "string.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 */uint8_t aRxBuffer;uint8_t Uart1_RxBuff[256];uint8_t str1[20] = "stop stm32";uint8_t str2[20] = "go stm32";uint8_t Uart1_Rx_Cnt = 0;uint8_tcAlmStr[] = "Êý¾ÝÒç³ö(´óÓÚ256)\r\n";unsigned int flag = 1;/* 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 *//*** @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_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1);/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */if(flag == 1){printf("ppm Hello windows!\r\n");}else{//printf("stop stm32 NO!\r\n");}HAL_Delay(500);}/* USER CODE END 3 */}/*** @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_HSE;RCC_OscInitStruct.HSEState = RCC_HSE_ON;RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;RCC_OscInitStruct.HSIState = RCC_HSI_ON;RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;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_DIV2;RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK){Error_Handler();}}/* USER CODE BEGIN 4 *//* USER CODE BEGIN 4 *//*** @brief Rx Transfer completed callbacks.* @param huart pointer to a UART_HandleTypeDef structure that contains*the configuration information for the specified UART module.* @retval None*/void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){/* Prevent unused argument(s) compilation warning */UNUSED(huart);/* NOTE: This function Should not be modified, when the callback is needed,the HAL_UART_TxCpltCallback could be implemented in the user file*/if (strcmp(Uart1_RxBuff, str1) == 0) flag = 0;if (strcmp(Uart1_RxBuff, str2) == 0) flag = 1;//if(Uart1_RxBuff[0]=='g') flag = 1;//if(Uart1_RxBuff[0]=='s') flag = 0;if(Uart1_Rx_Cnt >= 255) //Òç³öÅжÏ{Uart1_Rx_Cnt = 0;memset(Uart1_RxBuff,0x00,sizeof(Uart1_RxBuff));HAL_UART_Transmit(&huart1, (uint8_t *)&cAlmStr, sizeof(cAlmStr),0xFFFF);}else{Uart1_RxBuff[Uart1_Rx_Cnt++] = aRxBuffer; //½ÓÊÕÊý¾Ýת´æif((Uart1_RxBuff[Uart1_Rx_Cnt-1] == 0x0A)&&(Uart1_RxBuff[Uart1_Rx_Cnt-2] == 0x0D)) //ÅжϽáÊøλ{HAL_UART_Transmit(&huart1, (uint8_t *)&Uart1_RxBuff, Uart1_Rx_Cnt,0xFFFF); //½«ÊÕµ½µÄÐÅÏ¢·¢ËͳöÈ¥Uart1_Rx_Cnt = 0;memset(Uart1_RxBuff,0x00,sizeof(Uart1_RxBuff)); //Çå¿ÕÊý×é}}HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1); //ÔÙ¿ªÆô½ÓÊÕÖжÏ}/* USER CODE END 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 */

(3)编译烧录

(4)实现效果

实现效果:

中断通信(进阶)

五、DMA简介

什么是DMA

我们知道CPU有转移数据、计算、控制程序转移等很多功能,系统运作的核心就是CPU,

CPU无时不刻的在处理着大量的事务,但有些事情却没有那么重要,比方说数据的复制和存储数据,如果我们把这部分的CPU资源拿出来,让CPU去处理其他的复杂计算事务,是不是能够更好的利用CPU的资源呢?

因此:转移数据(尤其是转移大量数据)是可以不需要CPU参与。比如希望外设A的数据拷贝到外设B,只要给两种外设提供一条数据通路,直接让数据由A拷贝到B 不经过CPU的处理。

DMA就是基于以上设想设计的,它的作用就是解决大量数据转移过度消耗CPU资源的问题。有了DMA使CPU更专注于更加实用的操作–计算、控制等。

直接存储器访问 (DMA)

DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。

DMA传输过程的初始化和启动由CPU完成,传输过程由DMA控制器来执行,无需CPU参与,从而节省CPU资源,提高利用率。

DMA传输方式

DMA的作用就是实现数据的直接传输,而去掉了传统数据传输需要CPU寄存器参与的环节,主要涉及四种情况的数据传输,但本质上是一样的,都是从内存的某一区域传输到内存的另一区域(外设的数据寄存器本质上就是内存的一个存储单元)。四种情况的数据传输如下:

外设到内存内存到外设内存到内存外设到外设

普通模式:

传输结束后(即要传输数据的数量达到零),将不再产生DMA操作。若

开始新的DMA传输,需在关闭DMA通道情况下,重新启动DMA传输。

循环模式:

可用于处理环形缓冲区和连续数据流(例如ADC扫描模式)。当激活循

环模式后,每轮传输结束时,要传输的数据数量将自动用设置的初始值

进行加载, 并继续响应DMA请求。

DMA传输参数

数据传输需要:

1 数据的源地址

2 数据传输位置的目标地址

3 传递数据多少的数据传输量

4 进行多少次传输的传输模式

当用户将参数设置好,主要涉及源地址、目标地址、传输数据量这三个,DMA控制器就会启动数据传输,当剩余传输数据量为0时 达到传输终点,结束DMA传输 ,当然,DMA 还有循环传输模式 当到达传输终点时会重新启动DMA传输。

也就是说只要剩余传输数据量不是0,而且DMA是启动状态,那么就会发生数据传输。

DMA数据传输的四个要素

① 传输源 :DMA数据传输的来源

② 传输目标:DMA数据传输的目的

③ 传输数量:DMA传输数据的数量

④ 触发信号:启动一次DMA数据传输的动作

DMA控制器特点

STM32F411微控制器具备两个DMA控制器:DMA1和DMA2,每个控制器有8个数据流,每个数据流可以映射到8个通道(或请求);

每一个DMA控制器用于管理一个或多个外设的存储器访问请求,并通过总线仲裁器来协调各个DMA请求的优先级;

数据流(stream)是用于连接传输源和传输目标的数据通路,每个数据流可以配置为不同的传输源和传输目标,这些传输源和传输目标称为通道(Channel);

具备16字节的FIFO。使能FIFO功能后,源数据先送入FIFO,达到FIFO的触发阈值后,再传送到目标地址。

DMA工作框图

上方的框图,我们可以看到STM32内核,存储器,外设及DMA的连接,这些硬件最终通过各种各样的线连接到总线矩阵中,硬件结构之间的数据转移都经过总线矩阵的协调,使各个外设和谐的使用总线来传输数据。

有DMA的情况下,ADC采集的数据是怎样存放到SRAM中的?

在发生一个事件后,外设向DMA控制器发送一个请求信号。DMA控制器根据通道的优先权处理请求。当DMA控制器开始访问发出请求的外设时,DMA控制器立即发送给它一个应答信号。当从DMA控制器得到应答信号时,外设立即释放它的请求。一旦外设释放了这个请求,DMA控制器同时撤销应答信号。DMA传输结束,如果有更多的请求时,外设可以启动下一个周期。

总之,每次DMA传送由3个操作组成:

从外设数据寄存器或者从当前外设/存储器地址寄存器指示的存储器地址取数据,第一次传输时的开始地址是DMA_CPARx或DMA_CMARx寄存器指定的外设基地址或存储器单元;存数据到外设数据寄存器或者当前外设/存储器地址寄存器指示的存储器地址,第一次传输时的开始地址是DMA_CPARx或DMA_CMARx寄存器指定的外设基地址或存储器单元;执行一次DMA_CNDTRx寄存器的递减操作,该寄存器包含未完成的操作数目。

DMA方式的接口函数

六、STM32采用DMA方式实现串口通信

1、任务要求

STM32采用串口DMA方式,用115200bps或更高速率向上位机连续发送数据。

2、创建工程

打开STM32CubeMX,点击ACCESS TO MCU SELECTOR

选择对应的芯片,这里我用到的是STM32F103C8T6,在搜索框输入STM32F103C8,双击搜索到的芯片

设置RCC

设置串口

使能中断

DMA设置

点击DMA Settings的Add添加通道,传输速率设置为中速Medium

模式设置为Normal,右侧选择Memory

在System view下选择DMA,点击Add,添加MEMTOMEM

时钟设置

设置好工程名称、存储路径和编译器版本之后,导出Keil工程文件即可

打开工程

3、代码编写

main.c文件添加如下代码

uint8_t hello[] = "hello windows!\n"; //定义数据发送数组

在主函数while循环里面加入如下代码:

HAL_UART_Transmit_DMA(&huart1, (uint8_t *)hello, sizeof(hello));HAL_Delay(1000);

4、编译

点击魔法棒,在Output下勾选Create HEX File

Target下将编译器改为Version 5

Debug下选择Use Simulator,将Dialog DLL改为DARMSTM.DLL,将Paremeter改为-pSTM323F103C8

点击Rebuild进行编译

5、硬件连接

连接方法:

注意将核心板上的BOOT0设置为1,BOOT1设置为0

6、烧录

打开FlyMcu进行烧录

7、运行效果

打开串口调试助手 SSCOM,打开并配置串口设置

点击打开串口,开始以DMA方式进行向上位机发送"hello windows!"

实现效果:

DMA串口通信

七、总结

本篇文章主要讲到stm32中断、DMA通信原理和编程方法,并且在理论学习的基础上,通过使用stm32tubemx和HAL库,完成STM32中断方式点灯、中断方式的串口通信以及DMA方式的串口通信等编程练习。嵌入式学习不能只知道学习理论知识而不动手实践,也不能天天敲代码而原理一点也不懂,两者要兼得。只有同时掌握理论知识和实践方法,才能学好嵌入式。希望大家在学习嵌入式的过程中,也要做到理论与实践并重。能够做到理论指导实践,利用实践操作来加深对理论知识的理解,二者相辅相成。

参考列表:

1.stm32外部中断模式控制灯亮灭

2.HAL库中断方式进行串口通信

3.stm32hal库串口DMA收发

4.【STM32】HAL库 STM32CubeMX教程十一—DMA (串口DMA发送接收)

5.【嵌入式12】DMA通信原理及编程实验,DMA方式向上位机连续发送数据

6.【嵌入式11】HAL库实验中断开关点灯及串口通信

如果觉得《嵌入式系统开发11——中断及串口通信进阶》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。