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

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

一、什么是中断

中断通常被定义为一个事件,该事件能够改变处理器执行指令的顺序。这样的事件与 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变为低电位,灯灭
	
	//高电位
	else
		HAL_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、硬件连接

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

烧录电路的连接:
连接方法:

USB转TTL STM32F103C8T6
GND G
3V3 3V3
RXD PA9
TXD PA10

在这里插入图片描述

注意将核心板上的BOOT0设置为1,BOOT1设置为0
在这里插入图片描述

实验电路的连接:

引出端口 目的端口
3.3V LED正极
A5 LED负极
B5 A0/A2

在这里插入图片描述

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 Receiver
if((tmp_flag != RESET) && (tmp_it_source != RESET))
{
	UART_Receive_IT(huart);
}

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

/* UART in mode Transmitter
if (((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时,发送提示并改变flag
	if(c=='s'){
    
    
		flag=0;
		HAL_UART_Transmit(&huart1, (uint8_t *)&tips2, strlen(tips2),0xFFFF); 
	}
	
	//当输入的指令为t时,发送提示并改变flag
	else if(c=='t'){
    
    
		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);  
}

在这里插入图片描述

** 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时,发送提示并改变flag
	if(c=='s'){
    
    
		flag=0;
		HAL_UART_Transmit(&huart1, (uint8_t *)&tips2, strlen(tips2),0xFFFF); 
	}
	
	//当输入的指令为t时,发送提示并改变flag
	else if(c=='t'){
    
    
		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);  
}
/* 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、硬件连接

连接方法:

USB转TTL STM32F103C8T6
GND G
3V3 3V3
RXD PA9
TXD PA10

在这里插入图片描述

注意将核心板上的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) 2022 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_t	cAlmStr[] = "Êý¾ÝÒç³ö(´óÓÚ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、硬件连接

连接方法:

USB转TTL STM32F103C8T6
GND G
3V3 3V3
RXD PA9
TXD PA10

在这里插入图片描述

注意将核心板上的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库实验中断开关点灯及串口通信

猜你喜欢

转载自blog.csdn.net/qq_52068373/article/details/127397152