实验任务
一、用stm32F103核心板的GPIOA端一管脚接一个LED,GPIOB端口一引脚接一个开关(用杜邦线模拟代替)。采用中断模式编程,当开关接高电平时,LED亮灯;接低电平时,LED灭灯。
二、
二、采用串口中断方式重做上周的串口通信作业(一)当stm32接收到字符“s”时,停止持续发送“hello windows!”;当接收到字符“t”时,持续发送“hellowindows!”(提示:采用一个全局标量做信号灯)
实验任务一
中断
认识中断
,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行
中断的优先级
当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源
中断嵌套
当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回
中断执行流程
stm32的中断设置![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/98dabd9830624f86bb648696b858099c.png#pic_center)
简单总结
1.68个可屏蔽中断通道,包含EXTI、TIM、ADC、USART、SPI、I2C、RTC等多个外设
2. 使用NVIC统一管理中断,每个中断通道都拥有16个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级
NVIC
NVIC,又名嵌套向量中断控制器,它的任务就是提前对接入的中断进行优先级排序,然后告诉CPU这些中断应该先后进行哪个。减轻了CPU的工作负担。
优先级分组
1、NVIC的中断优先级由优先级寄存器的4位(0~15)决定,这4位可以进行切分,分为高n位的抢占优先级和低4-n位的响应优先级
2、抢占优先级高的可以中断嵌套,响应优先级高的可以优先排队,抢占优先级和响应优先级均相同的按中断号排队
EXTI![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/1c98539bc1d545bdbce77ca1e773eb4f.png#pic_center)
·上升沿触发
数字电平从低电平(数字“0”)变为高电平(数字“1”)的那一瞬间叫作上升沿。 上升沿触发是当信号有上升沿时的开关动作,当电位由低变高而触发输出变化的就叫上升沿触发。也就是当测到的信号电位是从低到高也就是上升时就触发,叫做上升沿触发。
·下降沿触发
数字电路中,数字电平从高电平(数字“1”)变为低电平(数字“0”)的那一瞬间叫作下降沿。 [1] 下降沿触发是当信号有下降沿时的开关动作,当电位由高变低而触发输出变化的就叫下降沿触发。也就是当测到的信号电位是从高到低也就是下降时就触发,叫做下降沿触发。
·双边沿触发
也就是无论是从低电平到高电平还是从高电平到低电平都能触发。
·软件触发
通过软件主动查询信号或仪器当前状态,符合条件则控制系统采集信号。软触发有着更大的柔性,但系统整体速度和测量精度一般不如硬触发,特别是有着复杂信号处理模块的时候。软件触发会占用到一定的计算机资源。使用软件触发的话可以先设计一个查询程序,在规定的时间间隔里定时查询触发信号,如果有触发信号,则输出一个真值的布尔量,执行数据采集的分支,如果没有触发信号,则输出一个假值的布尔量,执行空分支。
基本结构
AFIO复用IO口
AFIO作为一个数组选择器外设,用来给GPIO外设选择中断的引脚。
①.如果只有一个中断,则GPIO引脚可以随意选择,若选择GPIOA Pin15,则AFIO则选择数字15输出到EXTI 。
②.如果有多个中断,则GPIO引脚则要注意不能同时选择相同标号引脚,例如两个中断,一个设为GPIOA15触发,一个设为GPIOB15触发,因为AFIO是数字选择器,这样就只能识别到一个15。
创建项目
代码实现
1.配置RCC,把涉及到的外设的时钟全部打开
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //打开GPIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //打开AFIO时钟
//EXTI 和 NVIC已经默认开启时钟,不需要再开启
2.配置GPIO,选择端口为输入模式(GPIOB14号引脚)
GPIO_InitTypeDef GPIO_InitStruct;//定义配置初始化结构体
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; //对于外部中断来说,要选择浮 空、上拉或者下拉输入其中一个模式,这里选择的是浮空触发。如果不清楚看ST参考手册GPIO外设配置表(113页EXTI输入线)
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
3.配置AFIO,选择我们用的GPIO连接到后面EXTI
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);//用AFIO配置需要的中断引脚选择
4.配置EXTI,选择触发方式(例如上升沿,下降沿或双边沿),触发响应方式(中断或者事件触发)
EXTI_InitTypeDef EXTI_InitStruct;
EXTI_InitStruct.EXTI_Line = EXTI_Line14;//选择PB14对应的14号线路;
EXTI_InitStruct.EXTI_LineCmd = ENABLE;//开启中断
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;//选择是中断还是事件触发
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising_Falling;//配置为下降沿触发
EXTI_Init(&EXTI_InitStruct);
5.配置NVIC,给外设配置合适的优先级,最后通过NVIC中断信号进入CPU
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//先配置一下指定中断分组(这里选择抢占优先级和响应优先级)
NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn;//这里参数需要到整个文件搜索,不同类型的芯片终端通道列表不一样,选择MD的芯片找到10-15EXTI的通道
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
//指定优先级,因为只有一个中断源,优先级随意设置
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; //指定抢占优先级
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1; //指定响应优先级
NVIC_Init(&NVIC_InitStruct);
6.中断函数的写入
//第六步写中断函数(在启动文件startup里面找,找对应之前开启的10-15通道)
void EXTI15_10_IRQHandler(void)//必须无参无返回值
{
//因为有10-15EXTI都能进来所以一般首先要进行判断EXTI14的中断标志位是不是为1
if(EXTI_GetITStatus(EXTI_Line14)== SET)
{
//这里是用PA5引脚点亮小灯泡
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//打开GOIO时钟
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14)==Bit_RESET)//判断现在是低电平还是高电平,对应开关等
{
GPIO_SetBits(GPIOA,GPIO_Pin_5);
//GPIO_ResetBits(GPIOA,GPIO_Pin_5);
}
else if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14)==Bit_SET)
{
//GPIO_SetBits(GPIOA,GPIO_Pin_5);
GPIO_ResetBits(GPIOA,GPIO_Pin_5);
}
//每次执行中断后都应该清除中断标志位,不然会一直申请中断,程序就卡死在中断里面
EXTI_ClearITPendingBit(EXTI_Line14);
}
}
7.LED.h头文件引入
#ifndef _LED_H
#define _LED_H
void LED_Init(void);
#endif
LED.c用来存储驱动程序的主体代码LED.h用来存放这个驱动程序可以对外提供的函数或变量声明其他都是F103C8T6对应标准库所需的文件,直接添加就好
8.main.c主函数
#include "stm32f10x.h" // Device header
#include "LED.h"
int main(void)
{
LED_Init();
while(1)
{
}
}
开发板实验结果
实验任务二
一、当stm32接收到字符“s”时,停止持续发送“hello windows!”;当接收到字符“t”时,持续发送“hellowindows!”(提示:采用一个全局标量做信号灯)
#include "stm32f10x.h"
#include "misc.h"
#include <string.h> // 包含 string.h 以使用 strlen
volatile uint8_t send_enabled = 0; // 全局变量,控制发送行为
void USART_Configuration(void) {
USART_InitTypeDef USART_InitStructure;//外设的初始化变量
GPIO_InitTypeDef GPIO_InitStructure;//初始化GPIO
// 打开 GPIO 与 USART 端口的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
// 配置 USART1 Tx (PA.09) 为复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;//选择引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//设置输出速度
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置 USART1 Rx (PA.10) 为浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//选择引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输出
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置 USART 参数
USART_InitStructure.USART_BaudRate = 9600;//波特率设置
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字节长为8位
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//收发模式
USART_Init(USART1, &USART_InitStructure);//初始化外设Usart1寄存器
// 使能 USART
USART_Cmd(USART1, ENABLE);
// 使能接收中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
// 配置 NVIC
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//设置串口1中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;//抢占优先级0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;//子优先级为0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能
NVIC_Init(&NVIC_InitStructure);
}
void USART1_IRQHandler(void) {
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
char data = USART_ReceiveData(USART1);
if(data == 's') { // 接收到 's' 停止发送
send_enabled = 0;
} else if (data == 't') { // 接收到 't' 开始发送
send_enabled = 1;
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
void Delay(__IO uint32_t nCount) {//延迟函数
for(; nCount != 0; nCount--);
}
int main(void) {
SystemInit();//系统时钟初始化,保证系统时钟配置正确
USART_Configuration();
char *str = "hello windows!\r\n";
while(1) {
if(send_enabled) {
for(uint32_t i = 0; i < strlen(str); i++) {
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
USART_SendData(USART1, str[i]);
}
}
Delay(5000000);
}
}
1.初始化配置:
系统时钟初始化 (SystemInit()): 在main函数开始调用,保证系统时钟配置正确。SystemInit()函数申明位于system_stm32f10x.h头文件中,内容在system_stm32f10x.c文件中。
串口配置 (USART_Configuration()): 配置USART1的发送端口(PA.09)和接收端口(PA.10),设定波特率、字长、停止位、奇偶校验位和硬件流控。同时,使能USART1的接收中断。
2.中断服务程序 (USART1_IRQHandler()): 该函数作为USART1接收中断的处理程序,当接收到数据时,会检查中断标志位。根据接收到的字符更新全局控制变量 send_enabled。如果收到字符为’s’,则设置 send_enabled 为0,停止发送操作;如果收到字符为’t’,则设置 send_enabled 为1,开始或继续发送。
3.信息发送控制: 在main循环中,根据send_enabled的值决定是否发送字符串 “hello windows!\r\n”。通过strlen()函数获取待发送字符串的长度,并在一个for循环中通过USART_SendData()函数逐字符发送。
4.延时功能: 在发送一次字符串后,使用Delay()函数进行延时,延时函数通过简单的循环实现,以减缓发送速度,便于观察结果。
一些定义的整理
使能
单片机中的使能通常指的是控制某个器件或模块的开关信号。例如,单片机中的GPIO口可以用来控制LED灯的亮灭,其中需要用到一个使能信号,即一个控制LED灯开关的信号。在程序中,通过设置GPIO口的状态(高电平或低电平)来控制LED灯的亮灭,从而实现使能的功能。除了LED灯,单片机中的其他器件或模块,如LCD显示屏、声音模块等,也需要通过使能信号来控制其开关状态
GPIO与PIN 的区别
STM32的GPIO和PIN都是指引脚,但它们的含义和用法略有不同。 GPIO是General Purpose Input/Output的缩写,通用输入输出引脚,是STM32芯片上的一个模块,它可以控制和读取多个引脚的状态,可以通过寄存器配置来实现不同的功能,例如输入、输出、上拉、下拉、中断等。 PIN是指引脚,是GPIO模块中的单个引脚,每个引脚都有一个唯一的编号,例如PA0、PB1等。可以通过寄存器配置来控制和读取单个引脚的状态,例如设置引脚为输入或输出、设置引脚电平等。 因此,GPIO是一个功能模块,而PIN是GPIO模块中的一个单个引脚。
RCC
STM32单片机的RCC(Reset and Clock Control)时钟模块主要负责系统时钟的控制和管理,包括以下作用: 1. 确定CPU的工作频率:RCC模块可以设置CPU的时钟频率,从而控制单片机的工作速度。 2. 控制外设时钟:RCC模块可以为外设提供时钟信号,使外设能够正常工作。 3. 确定系统时钟源:RCC模块可以选择外部晶振或内部RC振荡器作为系统时钟源,从而控制整个系统的时钟。 4. 确定时钟分频系数:RCC模块可以设置时钟分频系数,从而控制时钟信号的频率。 5. 控制低功耗模式:RCC模块可以控制单片机的低功耗模式,从而延长电池寿命。RCC时钟模块是STM32单片机系统时钟控制和管理的核心模块
二、当stm32接收到字符“stop stm32!”时,停止持续发送“hello windows!”;当接收到字符“go stm32!”时,持续发送“hello windows!”(提示:要将接收到的连续字符保存到一个字符数组里,进行判别匹配。写一个接收字符串的函数。)
#include "stm32f10x.h"
#include "misc.h"
#include <string.h>
#define BUFFER_SIZE 100
volatile char buffer[BUFFER_SIZE];
volatile int buffer_index = 0;
volatile int send_enabled = 0;
void Delay(__IO uint32_t nCount) {
for (; nCount != 0; nCount--);
}
void USART_Configuration(void) {
USART_InitTypeDef USART_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
// USART Tx (PA.09) 配置为复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// USART Rx (PA.10) 配置为浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = 9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 开启接收中断
USART_Cmd(USART1, ENABLE);
}
void NVIC_Configuration(void) {
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void USART1_IRQHandler(void) {
//串口1的中断响应函数,当串口1 发生了相应的中断后,就会跳到该函数执行
if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
//接收中断
char data = (char)USART_ReceiveData(USART1);
if (buffer_index < BUFFER_SIZE - 1) {
buffer[buffer_index++] = data;
buffer[buffer_index] = '\0'; // 保持字符串结尾
char* temp_buffer = (char*)buffer; // 创建一个非 volatile 指针
if (strstr(temp_buffer, "stop stm32!") != NULL) {
send_enabled = 0;
buffer_index = 0; // 清空缓冲区
} else if (strstr(temp_buffer, "go stm32!") != NULL) {
send_enabled = 1;
buffer_index = 0; // 清空缓冲区
}
}
}
}
int main(void) {
SystemInit();
USART_Configuration();
NVIC_Configuration();
char *str = "hello windows!\r\n";
while (1) {
if (send_enabled) {
for (uint32_t i = 0; i < strlen(str); i++) {
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
USART_SendData(USART1, str[i]);
}
}
Delay(5000000);
}
}
1.系统初始化
首先是系统的基本初始化,包括系统时钟和串口配置。这一步是为了配置和启动基本的硬件设备,确保STM32可以正常工作并进行数据通信。
2.USART配置
GPIO配置: 配置GPIOA的9号脚(Tx发射端)为复用推挽输出,10号脚(Rx接收端)为浮空输入,以适应串口通信的物理连接需求。
USART设置: 设置串口1的波特率、字长、停止位、奇偶校验位和流控,以及启用其接收和发送功能。
3.中断配置
使能USART接收中断: 这允许设备在接收到数据时自动触发中断服务程序(ISR),处理接收到的数据。
NVIC配置: 配置中断控制器,设置中断优先级和中断通道,确保USART中断可以被有效处理。
4.中断服务程序(ISR)
这里是处理接收数据的核心逻辑:
数据接收: 从USART数据寄存器读取接收到的数据。
缓冲区管理: 将接收到的数据逐字节存储到缓冲区,并在缓冲区末尾维护字符串结束符。
字符串匹配: 检查缓冲区中是否包含特定控制命令(如"stop stm32!“或"go stm32!”),并根据匹配结果更新发送模式的使能状态。
控制逻辑:当接收到"stop stm32!"命令时,停止发送数据;接收到"go stm32!“命令时,开始或继续发送数据。
5.主循环
主循环中包含了数据发送的逻辑:
条件判断: 根据send_enabled变量的状态决定是否发送数据。
数据发送: 若允许发送数据,则循环发送预设的字符串"hello windows!\r\n”。
延时: 在发送完一次数据后进行延迟,以便观察发送数据的效果。
心得
太难理解了,老天,太抽象了。
参考博客:
第12周–中断编程入门-CSDN博客
STM32以中断的方式点亮LED小灯(标准库)_stm32按键中断控制led灯-CSDN博客
STM32之USART-串口通信(含串口实验详细解析)_stm32 串口11bit-CSDN博客