用stm32中断实现led亮灭灯和串口通信

  • Post author:
  • Post category:其他





前言

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

  2. 采用串口中断方式重做上周的串口通信作业,实现:当stm32接收到字符“s”时,停止持续发送“hello windows!”; 当接收到字符“t”时,持续发送“hello windows!”

  3. 采用串口中断方式重做上周的串口通信作业,分别实现:1)当stm32接收到字符“s”时,停止持续发送“hello windows!”; 当接收到字符“t”时,持续发送“hello windows!”(提示:采用一个全局标量做信号灯);2)当stm32接收到字符“stop stm32!”时,停止持续发送“hello windows!”; 当接收到字符“go stm32!”时,持续发送“hello windows!”(提示:要将接收到的连续字符保存到一个字符数组里,进行判别匹配。写一个接收字符串的函数

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




一、用中断实现最小核心板的呼吸灯



1.用STM32CubeMx创建工程



1.1 配置引脚

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

A1持续输出高电平,设置为GPIO_Output

A7持续输出低电平,设置GPIO_Output

B5模拟开关,设置为GPIO_EXTI5

在这里插入图片描述



1.2 配置Exit,Sys,GPIO

点开NVIC,找到EXIT line[9:5] interrupts后勾选

在这里插入图片描述

点开SYS,将Debug改成Serial Wire

在这里插入图片描述

点开GPIO,把A1配置为高电平、A7配置为低电平,把B5中断配置为上升沿和下降沿都触发

User Label可以为当前引脚设置一个别名,方便后续自己调用,这里我改为了SWITCH。

在这里插入图片描述



1.3 创建工程

点开Project Manager,输入Project文件名,并选择路径,然后点击Code Generator将第二个方格中的第一个勾选,然后点击GENERATE CODE,然后点击OPEN PROJECT跳到keil5

在这里插入图片描述



2.用keil调试代码

在目录中找到stm32f1xx_it.c,在HAL_GPIO_EXTI_IRQHandler按F12跳到该函数

在这里插入图片描述

找到 -weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)这个函数,将其删除,写上新的进去

在这里插入图片描述

    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_A4_GPIO_Port,LED_A4_Pin,GPIO_PIN_RESET);//把A4变为低电位
        //高电位
        else
            HAL_GPIO_WritePin(LED_A4_GPIO_Port,LED_A4_Pin,GPIO_PIN_SET);//把A4变为高电位
        }
    }

写进去后,发现SWITCH_GPIO_Port,SWITCH_Pin,LED_A4_GPIO_Port,LED_A4_Pin这些会报错

这是因为我们没有在这个stm32f1xx_hal_gpio.c中定义这些变量,在main.h中找到这些定义复制过去,发现少几个变量,自己定义即可。

在这里插入图片描述

在这里插入图片描述

然后点击编译,显示编译成功,也无错误警告,并生成了hex文件

在这里插入图片描述



3.烧录运行

  • 首先,将我们手上的stm32f103c8进行连线,这里,笔者使用的usb转ttl实现,所以首先连接USB转TTL,将USB转TTL的3v3连接上STM32的3v3,groud连接上ground,TXD连接A10,RXD连接A9。
  • 将led灯一端连接A4端口,一端连接3.3v
  • 笔者这里选的B5为中端口,所以用一根线连接B5,另一端先不连

我们打开flymcu,选择hex文件,开始编程

在这里插入图片描述

连线

在这里插入图片描述

在这里插入图片描述



二、HAL库中断方式进行串口通信



1.使用STM32F103C8配置项目

  • 同样,先点击ACCESS TO MCU SELECTOR,选择板子STM32F103C8,然后点击START PROJECT
  • 配置RCC

    将High Speed Clock(HSE)的那个改成Crystal/Ceramic Reasonator。

在这里插入图片描述

  • 设置SYS

    将Debug改成Serial Wire

在这里插入图片描述

  • 设置USART

    将Mode改成Asynchronous

在这里插入图片描述

  • 设置NVIC

    勾选SART1 global interrupt

在这里插入图片描述

  • 创建项目

    填入Project Name,选择一个非中文路径,将IDE改成MDK-ARM,将Min Version选择V5

在这里插入图片描述



2.用Keil配置代码

在STM32CubeMx上选择open project直接跳到keil5



2.1 在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.开始发送

在这里插入图片描述



2.2 接收中断函数和while循环


函数原型


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)

;

在这里插入图片描述

在while循环中添加如下代码,此代码表示当flag为1时,一直发送信息

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

在这里插入图片描述



2.3 重写中断处理函数

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);  
}

在这里插入图片描述

  • 烧录运行后即发现,当发送t后,start,便开始发送hello Windows

在这里插入图片描述

  • 发送s后,中断stop

在这里插入图片描述

  • 然后继续发送t,就可以重新开始发送

在这里插入图片描述



2.4 改写中断处理函数

要求:

当stm32接收到字符“stop stm32!”时,停止持续发送“hello windows!”; 当接收到字符“go stm32!”时,持续发送“hello windows!”


strcmp(s1, s2)

;

如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。

全部代码


#include "main.h"
#include "usart.h"
#include "gpio.h"
#include "stdio.h"
#include "string.h"


/* 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;


void SystemClock_Config(void);

int main(void)
{

  HAL_Init();

  SystemClock_Config();


  MX_GPIO_Init();
  MX_USART1_UART_Init();

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


  while (1)
  {

	  if(flag == 1)
	  {
	  	printf("go stm32 Hello windows!\r\n");
	  }
	  
	  else
	  {

	  }
		HAL_Delay(500);
  }

}


void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};


  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();
  }


  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();
  }
}


void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{

  UNUSED(huart);

	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);   //ÔÙ¿ªÆô½ÓÊÕÖжÏ
}

void Error_Handler(void)
{

  __disable_irq();
  while (1)
  {
  }

}



3.烧录实现

用flymcu烧录

在这里插入图片描述

用串口调试助手

发送go stm32!即开始发送

在这里插入图片描述

发送stop stm32!后中断然后继续发送

在这里插入图片描述



三、STM32采用串口DMA



1.DMA简介

一般情况下,存储器与IO设备的数据交互都要通过CPU来进行,而DMA控制器可以实现存储器和IO设备间数据的直接交换。


DMA 传输将数据从一个地址空间复制到另外一个地址空间

。当CPU 初始化这个传输动作,传输动作本身是由 DMA 控制器来实行和完成。

DMA用来提供在

外设和存储器之间或者存储器和存储器之间



高速数据传输

。无须CPU的干预,通过DMA数据可以快速地移动。这就

节省了CPU的资源来做其他操作


。典型的例子就是移动一个外部内存的区块到芯片内部更快的内存区。像是这样的操作并没有让处理器工作拖延,反而可以被重新排程去处理其他的工作。DMA 传输对于高效能 嵌入式系统算法和网络是很重要的。

在实现DMA传输时,是由DMA控制器直接掌管总线,可以直接控制IO设备与内存之间的数据交换,因此,存在着一个总线控制权转移问题。即DMA传输前,CPU要把总线控制权交给DMA控制器,而在结束DMA传输后,DMA控制器应立即把总线控制权再交回给CPU。一个完整的DMA传输过程必须经过DMA请求、DMA响应、DMA传输、DMA结束4个步骤。

在这里插入图片描述



1.1 工作状态和方式

  1. 被动态(受控器):未取得总线控制权,受CPU的控制
  2. 主动态(主控器):接管并取得总线控制权,取代CPU而成为系统的主控者


数据块传送方式

:在I/O接口电路中设置一个比较大的数据缓冲区,一般能存放一个数据块,I/O接口电路与内存之间的数据交换以数据块为单位。总线仲裁器判定究竟是DMA控制器还是CPU能获得总线的使用权。


周期挪用方式

:当I/O接口没有DMA请求时,CPU按程序要求访问内存;一旦I/P接口有DMA请求,则I/O接口挪用一个或几个周期。缺点是:数据输入或庶出过程中实际占用了CPU时间。


交替访存方式

:CPU与DMA控制器交替访问内存。不需要总线使用权的申请、建立和归还过程。

效率高,但实现起来有困难,基本上不被使用。

DMA方式

特点



1、内存既可以被CPU访问也可以被DMA控制器访问,CPU和DMA控制器会竞争总线的使用权,因而需要仲裁机制

2、外部设备与内存之间的整个数据交换过程全部在DMA控制器的控制下完成,CPU能够与外部设备并行工作,大大提高了效率

3、在DMA方式开始之前CPU要对DMA控制器进行初始化,在DMA方式结束之后,CPU要申请中断,对内存缓冲区进行后处理。

I/O接口电路DMA传送方式:

在这里插入图片描述



1.2 DMA传输

数据传输源和目的地都可以寻址整个4 GB区域中的外围设备和存储器,其地址介于0x0000 0000和0xFFFF FFFF之间。传输方向使用DMA_SxCR寄存器中的DIR [1:0]位进行配置,并提供三种可能性:存储器到外围设备,外围设备到存储器或存储器到存储器的传输。

  • 外设到存储器模式
  • 存储器到外设模式
  • 存储器到存储器模式


传输方式



1.

DMA_Mode_Normal,正常模式


2.

DMA_Mode_Circular ,循环传输模式


DMA的数据传输不需要占用cpu时间,所以数据传输前需要提前高速高速源和目标传输的数据块(指字节,半字或字)个数。即:设置好DMA 数据流 x 数据项数寄存器(DMA_SxNDTR),里面放置需要传输的数据项的个数(NDTR=numbers of data to transfer).使能数据流后,此寄存器为只读,用于指示要传输的剩余数据项数。每次 DMA 传输后,此寄存器将递减。


普通模式时,传输完成后,此寄存器保持为零,除非将DMA数据流配置寄存器的EN置1。

循环模式时循环模式该寄存器会重载初始值。


原理



在这里插入图片描述



1.3 DMA主要特征和中断

·每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发,这些功能通过软件来配置。

·在同一个DMA模块上,多个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),优先权设置相等时由硬件决定(请求0优先于请求1,依此类推)。

·独立数据源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。

·支持循环的缓冲器管理。

·每个通道都有3个事件标志(DMA半传输、DMA传输完成和DMA传输出错),这3个事件标志逻辑或成为一个单独的中断请求。

·存储器和存储器间的传输、外设和存储器、存储器和外设之间的传输。

·闪存、SRAM、外设的SRAM、APB1、APB2和AHB外设均可作为访问的源和目标。

·可编程的数据传输数目:最大为65535(0xFFFF)。


中断

  • 每个DMA通道都可以在DMA传输过半、传输完成和传输错误时产生中断;为应用的灵活性考虑,通过设置寄存器的不同位来打开这些中断。

在这里插入图片描述

即使没开启,也可以通过查询这些位来获得当前 DMA 传输的状态。这里常用的是 TCIFx位,即数据流 x 的 DMA 传输完成与否标志



2.用STM32CubeMx创建工程



2.1 新建项目

点击ACCESS TO MCU SELECTOR,在Part Number中选择我们得STM32核心版STM32F103C8,然后点击Start Project

  • 设置RCC

在这里插入图片描述

  • 配置串口

    找到USART1设置,将Mode改成

    Asynchronous

    ,然后点击下面

    Parameter Settings

    ,查看波特率,这些是否正确

在这里插入图片描述

然后点击

NVIC Setting

,勾选Enabled

在这里插入图片描述

然后点击

DMA Settings

,点击

ADD

添加如下两个通道,并将传输速率设置为

中速Medium

在这里插入图片描述

点击System view,选择DMA,然后点击Add,选择MEMTOMEM.

在这里插入图片描述

  • 时钟设置

    将HCLK改为72,点击回车OK.

    在这里插入图片描述

  • PROJECT Manager

    输入项目名称,全英路径,将IDE改成MDK-ARM,后面改成V5

在这里插入图片描述

  • Code Generator

在这里插入图片描述

配置完成点击GENERATE CODE,点击Open Project即可打开keil5

在这里插入图片描述



2.2 用keil调试代码

在main.c的主函数中添加如下代码

uint8_t Senbuff[] = "Hello world!";  //定义数据发送数组

在这里插入图片描述

在while循环中添加如下代码

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

在这里插入图片描述

然后点击编译,没有错误警告

在这里插入图片描述



2.3 硬件连接

我们这里使用USB转TTL进行连接

  1. 3V3 —> 3V3
  2. GND —> GND
  3. RXD —> A9
  4. TXD —> A10
  5. 核心板boot0接1 核心板boot1接0

请添加图片描述



3.烧录运行



3.1 用flymcu烧录

首先点击搜索串口查看我们的串口,然后再下面选择我们之前在keil编译生成的hex文件

在这里插入图片描述

点击开始编程,写入途中,USB转TTL上蓝灯会闪烁,停止后即为写入成功

在这里插入图片描述



3.2 串口调试助手查看结果

点击打开串口,则会收到Hello world

在这里插入图片描述




总结

本篇文章主要学习stm32中断、DMA通信原理和编程方法,用stm32F103核心板的GPIOA端一管脚接一个LED,GPIOB端口一引脚接一个开关(用杜邦线模拟代替)。采用中断模式编程,当开关接高电平时,LED亮灯;接低电平时,LED灭灯。并采用串口中断方式来实现hello windows得串口通信,最后了解了DMA,并采用串口DMA方式,用115200bps或更高速率向上位机连续发送数据。很多问题,希望大家指导指导。

参考:https://blog.csdn.net/qq_47281915/article/details/121024427

参考:https://blog.csdn.net/Qxiaofei_/article/details/119029425

参考:

https://blog.csdn.net/as480133937/article/details/104827639/

https://blog.csdn.net/m0_56561130/article/details/118702500

https://blog.csdn.net/qq_42604176/article/details/112447972

https://zhuanlan.zhihu.com/p/138573828



版权声明:本文为qq_53144725原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。