第12周——中断编程入门

一、中断原理及其开发编程方法
(一)中断概述
      STM32微控制器的中断功能异常强大,几乎每个外设都能触发中断。中断机制是其关键组成部分,使处理器能够在主程序执行的同时立即响应外部事件。中断可由硬件或软件触发,并且具有高优先级,能够在主程序执行之前执行。通过中断,系统能够异步处理事件,有效提升响应速度和并发处理能力。在STM32中,中断被广泛应用于通信、定时器、ADC、DMA等各种任务。深入理解中断机制对于充分发挥STM32的功能和性能至关重要。

1、中断的基本概念和流程
(1)中断基本概念

当我们在看书时接到快递电话,便会放下当前手中所进行的活动前去取快递,当在取快递的过程中肚子痛,便会中断当前行动去上卫生间。

其中上图中的关系按照中断系统的规则整理如下:


中断基本概念:计算机在执行程序过程中,当出现异常情况(断电等)或特殊请求(数据传输等)时,计算机暂停现行程序的运行,转向对这些异常情况或特殊请求进行处理,处理完毕后再返回到现行程序的中断处,继续执行原程序,这就是“中断”。

(2)中断的处理流程
     中断处理流程:中断请求 → 中断响应 → 中断服务 → 中断返回

中断请求:中断请求是中断源向CPU发出中断请求信号,此时中断控制系统的中断请求寄存器被置位,向CPU请求中断。
中断响应:CPU的中断系统判断中断源的中断请求是否符合中断响应条件,如果符合条件,则暂时中断当前程序并控制程序跳转到中断服务程序。
中断服务:为处理中断而编写的程序称为中断服务程序,是由开发人员针对具体中断所要实现的功能进行设计和编写的,需要由开发人员来实现。
中断返回:CPU退出中断服务程序,返回到中断请求响应之前被中止的位置继续执行主程序。这部分操作同样由硬件来实现,不需要开发人员进行处理。
2、STM32中断系统结构及其工作原理

STM32中断系统的结构和工作原理如下:

中断请求来源:STM32的中断请求可以来自外部和内部两个方面。外部中断是由GPIO口引脚的电平或边沿信号变化触发,而内部中断通常是由硬件模块(如定时器、ADC)或软件产生的。
NVIC控制器:在STM32中,所有中断请求都由NVIC(Nested Vectored Interrupt Controller)控制器进行管理和调度。NVIC是一个基于向量表的中断控制器,通过优先级和向量表来实现对中断请求的管理。
中断分组:STM32将中断分为多个组别,每个组别包含一组中断请求。不同组别的中断请求可以具有不同的优先级,并且可以使用优先级抢占和屏蔽机制来确保系统的实时性和可靠性。STM32中断分组方式可选为0~4个前缀,用于设定中断优先级组和亚组。
中断服务程序:当中断事件发生后,CPU会暂停当前任务并跳转到相应的中断服务程序,处理该事件。中断服务程序通常包括以下几个步骤:
         保存CPU寄存器的值:包括堆栈指针、程序计数器等。

        处理中断请求:根据外部或内部中断的类型进行相应的处理,如清除标志位、读取数据等操作。

        执行用户自定义代码:根据实际需求执行用户自定义的代码段。

        恢复CPU寄存器的值:将保存在堆栈中的寄存器值恢复到其原始状态,以便CPU继续执行之前的任务。

中断优先级:STM32中,所有中断请求都具有唯一的编号(IRQn),并且可以根据编号和中断分组方式确定其优先级。优先级高的中断可以打断正在执行的低优先级中断,从而确保系统的实时性和可靠性。如果多个中断请求的优先级相同,则可以使用优先级抢占机制来确定响应顺序。
3、中断向量表及存储位置
       STM32Fl03各个中断对应的中断服务程序的入口地址统一存放在STM32Fl03的中断向量表中。STM32Fl03的中断向量表一般位于其存储器的 0 地址处。

(二)中断控制器

1、NVIC的功能和特点

NVIC的基本结构:

(1)NVIC的功能
中断优先级管理: NVIC允许为每个中断分配优先级,以确定在多个中断同时发生时应该先处理哪一个。优先级高的中断将优先得到处理,确保关键任务的及时响应。
中断向量表管理: NVIC维护着中断向量表其中存储了每个中断对应的中断服务程序的入口地址。当中断发生时,CPU会根据中断号从向量表中获取相应的中断服务程序地址,从而实现快速的中断响应。
中断使能和禁止: NVIC允许对特定中断进行使能或禁止,以控制中断的触发。通过使能或禁止特定中断,可以灵活地控制系统对外部事件的响应。
中断处理和嵌套: NVIC支持中断的嵌套处理,即在处理一个中断时,仍然能够响应更高优先级的中断。这种机制确保了系统在处理紧急事件时的及时响应。
低功耗特性: NVIC设计考虑了功耗优化,能够在处理中断时尽可能地降低功耗,延长系统的电池寿命。
(2)NVIC的特性
根据STM32手册中搜索得到嵌套向量中断控制器的特性如下图所示:

     固件库文件 core_cm3.h 的最后,还提供了 NVIC 的一些函数,这些函数遵循 CMSIS 规则,只要是 Cortex-M3 的处理器都可以使用,具体如下:

2、中断优先级的设置方法及其规则
        在STM32中,中断优先级由向量表中的优先级字段确定,其中包括主组和子组。主组决定了不同中断源之间的优先级关系,而子组则决定了同一主组内不同中断源的优先级。为了配置外部中断的优先级,STM32提供了一个特定的寄存器,即中断优先级寄存器(NVIC_IPRx)。该寄存器的宽度为8位,但绝大多数情况下只使用高4位,因此实际上支持的优先级数目较少,如下图所示:

    用于表达优先级的这4bit,又被分组成抢占优先级和子优先级。如果有多个中断同时响应,抢占优先级高的就会抢占抢占优先级低的优先得到执行,如果抢占优先级相同,就比较子优先级。如果抢占优先级和子优先级都相同的话,就比较他们的硬件中断编号,编号越小,优先级越高。

(1)中断优先级分组
        用于设置中断的优先级分组,此函数只有一个参数NVIC_PriorityGroup,其取值共有5组,每组的抢占优先级和响应优先级所占位数均不同,取值范围不同。

中断分组管理函数:设置优先级分组可调用库函数void NVIC_PriorityGroupConfig(uint32_t  NVIC_PriorityGroup)实现,有关 NVIC 中断相关的库函数都在库文件 misc.c 和 misc.h 中。

NVIC中断配置的相关函数存放在标准外设库misc.c和misc.h文件中,共定义了5个相关函数及NVIC初始化结构体,如下所示:

(2)中断优先级规则
        优先级设置分为主优先级和子优先级两部分。

        主优先级具有高优先级的中断请求会立即被响应。主优先级使用8位或16位表示,由中断请求的NVIC IRQ通道号决定。当多个中断请求同时发生时,具有较高主优先级的请求将保证首先被响应。

        子优先级用于在具有相同主优先级的中断请求之间进行优先级划分。子优先级可以根据具体需求使用0~N位(其中N为分组方式中的子优先级位数)进行设置。当多个中断请求具有相同主优先级时,具有较高的子优先级的请求将被优先响应。

在进行中断优先级设置时,需要遵循以下几个规则:

高抢先优先级的中断可以打断低抢先优先级的中断服务,构成中断嵌套;
中断优先级的数值越小,优先级级别越高;
抢占优先级的优先级总是高于响应优先级;
中断优先级判断:先判断抢占优先级的大小,如果抢占优先级相同,则比较响应优先级的大小,若抢占优先级和响应优先级均相同,则根据中断向量表中的顺序来决定;
Reset NMI Hard Fault的优先级为负,且不可修改,高于普通的中断优先级。
(3)中断优先级设置
首先需要配置NVIC(Nested Vectored Interrupt Controller)寄存器,以使得CPU能够正确地响应中断请求。及会使用到中断初始化函数:void NVIC_Init();函数。如下所示:

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);
}
然后,可以使用__NVIC_SetPriority()函数来设置中断优先级。该函数有两个参数:中断向量号和优先级值。使用__NVIC_GetPriority()函数可以获得当前中断的优先级。

3、中断使能和禁止的方法
中断使能和禁止方法:

使能中断:使用NVIC_EnableIRQ()函数,将对应中断的中断向量号作为参数传入。

禁止中断:使用NVIC_DisableIRQ()函数,将对应中断的中断向量号作为参数传入。

(三)中断类型和应用
1、外部中断
(1)EXTI简介
EXTI(Extern Interrupt)外部中断
EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序
支持的触发方式:上升沿/下降沿/双边沿/软件触发 支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断
通道数:16个GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒
触发响应方式:中断响应/事件响应
(2)EXTI功能框图
        EXTI 的功能框图包含了 EXTI 最核心内容,掌握了 EXTI 功能框图,就可以全面了解 EXTI 的核心内容。在编程时,这一整体把握会使思路更加清晰。在功能框图中,通过斜杠和“20”字样标注的信号线表明,在控制器内部存在着类似的信号线路,总计有 20 个。这与 EXTI 具有 20 个中断/事件线的事实相符合。因此,只要理解其中一个线路的原理,其他 19 个线路的原理也就清楚了。

框图解读:

输入线:EXTI有19个中断/事件输入线,这些输入线可以通过寄存器设置为任意一个GPIO,也可以是一些外设事件。
边沿检测电路:它会根据上升沿触发选择寄存器(EXTI_RTSR)和下降沿出发选择器(EXTI_FTSR)对应的设置来控制信号触发。
(3)配置EXTI寄存器
上升沿触发:

下降沿触发:

(4)中断/事件控制线

通用I/〇端口以下图的方式连接到16个外部中断/事件线上:

EXTI0 至 EXTI15 用于 GPIO,通过编程控制可以实现任意一个 GPIO 作为 EXTI 的输入源。由表EXTI 中断 _ 事件线 可知, EXTI0 可以通过 AFIO 的外部中断配置寄存器 1(AFIO_EXTICR1) 的EXTI0[3:0] 位选择配置为 PA0、 PB0、 PC0、 PD0、 PE0、 PF0、 PG0,见图 EXTI0 输入源选择。其他 EXTI 线 (EXTI 中断/事件线) 使用配置都是类似的。

2、定时器中断
STM32微控制器上的定时器(Timer)模块提供了强大的定时和计数功能,可用于执行各种定时任务,例如生成精确的时间延迟、测量时间间隔、PWM(脉冲宽度调制)输出等。定时器中断是利用定时器模块中的中断功能,以实现在特定时间间隔或计数条件下触发中断处理程序的功能。

定时器中断通常涉及以下步骤:

 初始化定时器模块:在使用定时器中断之前,需要先初始化定时器模块,包括选择定时器的类型(基本定时器、通用定时器等)、配置定时器的时钟源和分频因子、设置计数器的自动重装载值等。

配置定时器中断:确定触发定时器中断的条件,可以是计数器达到特定值、定时器溢出、捕获到特定事件等。在STM32中,通常需要配置中断使能寄存器(如TIMx_DIER)来使能定时器中断,并设置相应的中断触发条件。

编写中断处理程序:一旦定时器中断被触发,控制器会跳转到预先定义的中断处理程序。在中断处理程序中,可以执行各种任务,例如更新计数器的值、执行特定的操作、清除中断标志等。

启用全局中断使能:在使用定时器中断之前,需要确保全局中断使能位(如__enable_irq())被设置,以确保中断能够正常触发和响应。

中断优先级设置:在某些情况下,可能需要设置定时器中断的优先级,以确保在多个中断同时触发时能够按照设定的优先级顺序进行处理。 

利用定时器中断,可以实现诸如定时采样、周期性数据处理、周期性任务执行等功能,为STM32应用程序的实时性和可靠性提供了重要支持。

3、DMA中断
STM32 DMA中断是指在STM32微控制器的DMA传输完成时触发的中断。使用DMA(Direct Memory Access,直接内存访问)可以在不占用CPU时间的情况下,实现大量数据的高速传输。通过启用DMA中断,可以在DMA传输完成后及时地处理传输结果,以达到更加灵活和高效的数据传输方式。

        在STM32微控制器中,DMA是由DMA控制器单独管理的。它可以向外部设备或内部存储器执行数据传输操作,而无需CPU参与。对于每个DMA通道,都有自己的配置寄存器和状态寄存器。需要使用这些寄存器来配置DMA传输参数,如数据长度、传输方向、源地址和目的地址等,并启用DMA中断。

        当DMA传输完成后,触发DMA中断,在中断服务程序中可以读取或写入DMA寄存器来获取或修改DMA状态,也可以执行其他操作,如切换GPIO引脚状态、修改变量值等。需要注意,由于DMA操作涉及内存和外设的数据交换,因此需要保证数据传输的正确性和可靠性。

        在使用DMA中断时,需要根据具体应用场景选择合适的DMA通道和传输参数,并合理设置中断触发条件和优先级。同时,还需要优化中断服务程序的实现,避免占用过多CPU时间,以充分发挥DMA传输的高效性。

(四)中断处理函数
        中断处理函数是一种特殊的函数,用于响应外部中断或异常事件。在STM32微控制器中,中断处理函数通常由硬件自动调用,在中断事件发生时立即执行。中断处理函数需要快速响应和处理中断请求,并确保系统正常运行。

中断处理函数通常包括以下几个方面:

1、中断服务程序入口:
        中断处理函数必须具有正确的函数声明和命名方式,以便编译器正确识别和生成相应的中断服务程序入口。在STM32微控制器中,中断处理函数通常采用以下格式:

void EXTI0_IRQHandler(void)
{
    //中断服务程序代码
}
其中,EXTI0_IRQHandler表示外部中断线0的中断服务程序入口。需要根据具体应用场景选择相应的中断服务程序入口。

2、中断标志位清除:
        在进入中断处理函数之前,需要先清除相应的中断标志位,以避免重复中断和误判中断。在STM32微控制器中,可以通过以下方式清除中断标志位:

void EXTI0_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXTI_Line0) != RESET)
    {
        EXTI_ClearITPendingBit(EXTI_Line0);
        // 中断服务程序代码
    }
}
其中,EXTI_GetITStatus函数用于检查中断标志位是否已经被置位,EXTI_Line0表示外部中断线0,EXTI_ClearITPendingBit函数用于清除中断标志位并退出中断。

3、中断服务程序代码:
中断服务程序代码是实现中断响应和处理的核心部分。需要根据具体应用场景编写相应的中断服务程序代码,以确保系统正常运行。在编写中断服务程序代码时,需要注意以下几个方面:

       ① 快速响应:中断响应时间必须要足够快,以避免延迟和数据丢失等问题。

       ② 简单可靠:中断服务程序代码必须简单易懂,并尽量避免复杂的操作和处理流程,以提高中断处理效率和可靠性。

       ③ 避免阻塞:中断服务程序代码不应该使用阻塞的函数或方法,以避免影响其他任务的执行或导致系统死锁。

       ④ 注意同步:中断服务程序代码需要正确地管理共享资源,避免资源竞争和数据不一致等问题。

4、中断服务程序退出:
        在中断服务程序执行完毕后,需要正确退出中断并返回到主程序或其他任务。在STM32微控制器中,可以通过以下方式退出中断:

void EXTI0_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXTI_Line0) != RESET)
    {
        EXTI_ClearITPendingBit(EXTI_Line0);
        // 中断服务程序代码
    }
    NVIC_ClearPendingIRQ(EXTI0_IRQn);
其中,NVIC_ClearPendingIRQ函数用于清除中断挂起位并退出中断

二、中断编程的实际应用
(一)中断控制LED亮灭
题目要求:

用stm32F103核心板的GPIOA端一管脚接一个LED,GPIOB端口一引脚接一个开关(用杜邦线模拟代替)。采用中断模式编程,当开关接高电平时,LED亮灯;接低电平时,LED灭灯。如果完成后,尝试在main函数while循环中加入一个串口每隔1s 发送一次字符的代码片段,观察按键中断对串口发送是否会带来干扰或延迟。

代码:

LED.c

#include "stm32f10x.h"                  // Device header
 
/**
  * 函    数:LED初始化
  * 参    数:无
  * 返 回 值:无
  */
void LED_Init(void)
{
    /*开启时钟*/
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);        //开启GPIOA的时钟
    
    /*GPIO初始化*/
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);        
 
    GPIO_ResetBits(GPIOA, GPIO_Pin_0);
}
exti_key.c

#include "exti_key.h"
#include "misc.h"
 
void EXTI_Key_Init(void)
{   
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; // 使用 B 口的引脚 1
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn; // 使用与 GPIOB 引脚 1 相关的外部中断通道
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure); 
    EXTI_InitTypeDef EXTI_InitStructure;
    EXTI_ClearITPendingBit(EXTI_Line1);  
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1); // 将 GPIOB 和引脚 1 配置为外部中断
    EXTI_InitStructure.EXTI_Line = EXTI_Line1;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);
}
main.c

#include "stm32f10x.h"                  // Device header
#include "LED.h"
#include "exti_key.h"
 
int main(void)
{
    LED_Init();
    GPIO_ResetBits(GPIOA,GPIO_Pin_0);
    EXTI_Key_Init();
 
    while (1)
    {
    }
}
//void EXTI1_IRQHandler(void)
//{
//    if(EXTI_GetITStatus(EXTI_Line1) != RESET)
//    {
//        GPIO_WriteBit(GPIOA,GPIO_Pin_0,(BitAction)((1-GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_0))));
//        EXTI_ClearITPendingBit(EXTI_Line1);
//    }
//}
//两种方法
uint8_t led = 1;
 
void EXTI1_IRQHandler(void)
{
      if(EXTI_GetITStatus(EXTI_Line1) != RESET)
      {
        led = ~led; //状态翻转
        //如果等于1,则PB1复位点亮,否则置1熄灭
        if(led == 1)
            GPIO_ResetBits(GPIOA,GPIO_Pin_0);
        else
            GPIO_SetBits(GPIOA,GPIO_Pin_0);    
     }
     EXTI_ClearITPendingBit(EXTI_Line1); //清除EXTI1的中断标志位
}
效果展示:

中断控制LED

(二)中断控制串口通信
1、题目1:

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

代码:

#include "stm32f10x.h"
#include "misc.h"
#include <string.h>
 
volatile uint8_t send_enabled = 0;  // 全局变量,控制发送行为
 
void USART_Configuration(void) {
    USART_InitTypeDef USART_InitStructure;
    GPIO_InitTypeDef GPIO_InitStructure;
 
    // 打开 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;
    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
    USART_Cmd(USART1, ENABLE);
 
    // 使能接收中断
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
 
    // 配置 NVIC
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 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);
    }
}
2、题目2:当stm32接收到字符“stop stm32!”时,停止持续发送“hello windows!”; 当接收到字符“go stm32!”时,持续发送“hello windows!”(提示:要将接收到的连续字符保存到一个字符数组里,进行判别匹配。写一个接收字符串的函数。

代码:

NVIC.c

#include "stm32f10x.h"                  // Device header
 
 
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);
}
 
Serial.c

#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>
 
/**
  * 函    数:串口初始化
  * 参    数:无
  * 返 回 值:无
  */
void Serial_Init(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);
}
main.c

#include "stm32f10x.h"
#include "misc.h"
#include <string.h>
#include "Delay.h"
#include "Serial.h"
#include "NVIC.h"
 
 
#define BUFFER_SIZE 100
volatile char buffer[BUFFER_SIZE];
volatile int buffer_index = 0;
volatile int send_enabled = 0;
 
 
void USART1_IRQHandler(void) {
    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();
    Serial_Init();
    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_ms(500);
    }
}

  • 11
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值