02 IO口的操作

文章目录

  • 前言
  • 一、IO的概念
    • 1.IO接口
    • 2.IO端口
  • 二、CPU和外设进行数据传输的方法
    • 1.程序控制方式
      • 1.1 无条件
      • 1.2 查询方式
    • 2.中断方式
    • 3.DMA方式
  • 一、方法介绍和代码编写
    • 1.前置知识
    • 2.程序方式
      • 1.1 无条件方式
        • 1.1.1 打开对应的GPIO口
        • 1.1.2 初始化对应的GPIO引脚
          • 1.1.2.1 推挽输出
          • 1.1.2.2 开漏输出
          • 1.1.2.3 浮空输入
          • 1.1.2.4 上拉输入
          • 1.1.2.5 下拉输入
          • 1.1.2.6 模拟输入
        • 1.1.3 对其中的GPIO引脚进行操作
          • 1.1.3.1 GPIO_SetBits
          • 1.1.3.2 GPIO_ResetBits
          • 1.1.3.3 GPIO_Write
          • 1.1.3.4 GPIO_WriteBit
    • 3.查询方式
      • 3.1 设置输入模式
      • 3.2 判断条件
        • 3.2.1 GPIO_ReadInputDataBit
        • 3.2.2 GPIO_ReadInputData
        • 3.2.3 GPIO_ReadOutputDataBit
        • 3.2.4 GPIO_ReadOutputData
      • 3.3 执行内容
    • 4.中断方式
      • 4.1 初始化引脚
      • 4.2 初始化外部中断
      • 4.3 设置NVIC
      • 4.4 重写中断服务函数
    • 5.DMA方式
  • 总结

前言

之前已经介绍了环境的搭建和调试的方法,这一篇文章我们就开始介绍一下如何对外设进行操作,这一节我会结合多种外设的操作来将所有方式的操作介绍给大家,并手把手介绍如何使用一个小显示屏OLED,这对我们后面的操作有很大的帮助。

一、IO的概念

1.IO接口

IO接口是能让CPU和外设能够进行信息交换的逻辑电路,在stm32中,IO接口是已经集成进单片机中了,我们不需要自己搭建一个IO接口电路,可以直接就使用其中的IO接口了。

2.IO端口

IO端口是CPU可以直接访问的一些寄存器,这些寄存器可以控制外设的状态和传输一些信息。

我们对IO设备进行操作时其实就是对IO端口进行一些操作,只不过在stm32中,这个设备更加的高级,这个设备被称为GPIO,接下来我们就是对这些GPIO口进行操作,以达到我们控制外设的目的。

二、CPU和外设进行数据传输的方法

这里总共有4种方法,在这一节中我都会涉及到,只不过中断和DMA我会简单的介绍,先会用,后面还会详细的介绍一下对应的内部结构。

1.程序控制方式

这个是由程序直接进行控制,这里有两种方法,一种是无条件,另一种是查询方式。

1.1 无条件

无条件传输方式是默认外设已经是处于就绪状态,就比如我们对LED灯进行操作,就可以直接使用无条件方式,因为它已经处于就绪状态,不会有忙状态,所以可以直接进行数据的传输。

这种方法的特点就是程序简单,但缺点就是没办法对特别复杂的电路进行操作。

1.2 查询方式

查询方式是在我们要进行数据传输之前,先查看一下外设的状态或者是条件是否满足,如果就绪或者是条件满足则开始进行数据的传输,否则就就绪查询。比如我们通过按键对外设进行控制,或者有些外设在给它传递信号后他会返回一个应答信号或者是其他的信号,当它发送后我们才能继续操作就可以使用这种方法。

这种方法的优点就是可以根据条件来控制外设的状态,缺点就是实时性差。

2.中断方式

如果外设准备好时,会主动发送一个信号过来,我们通过程序将这个信号设定为中断出发,当接受到这个信号后,CPU会停止当前执行的代码,转去执行对应设定好的中断服务程序。

这种方法的优点就是实时性好,速度快,但是代码比较难写。

我们可以使用中断的方法来代替查询方法,这样可以提高CPU的实时性和速度,但是就是代码比较复杂,但速度是最主要的,不要太在意复杂性。

3.DMA方式

这种方法又称为直接数据传送方法,之前外设和内存进行数据交换的时候,需要先经过CPU,然后才可以进行数据交换,但是DMA就是直接跳过这个过程,不经过CPU,直接就让外设和内存进行数据交换,这种方法又一个DMA控制器(DMAC)进行控制。

这种方法的优点是速度快,适合大量数据的交换,缺点就是需要依赖硬件环境。

上面介绍了一下传送数据的方式,接下来我们就把每一个的方法详细介绍一下。

一、方法介绍和代码编写

这里需要先介绍一下stm32的GPIO口和对应的引脚。

1.前置知识

在STM32中将IO端口分为了多个GPIO口,每个GPIO口又有多个引脚,这些我们可以直接通过C语言进行控制。

我们使用的stm32f103c8t6有GPIOA到GPIOC这总共3个GPIO口,而每个GPIO口中又有16个引脚,我们可以对这每个引脚进行操作。

2.程序方式

1.1 无条件方式

无条件方式是在学单片机操作中首先会给大家介绍的一种方法,这种方法很简单,操作起来也很容易。

用无条件方法对GPIO操作我们可以简单分为一下这几步:

1.打开对应的GPIO口

2.初始化对应的GPIO引脚

3.对GPIO引脚进行操作

1.1.1 打开对应的GPIO口

首先在操作之前我们需要开启对应操作的GPIO引脚的时钟,使用的函数是RCC_APB2PeriphClockCmd()这个函数可以开启你要使用的GPIO口的时钟,当开启时钟后我们可以对其中的引脚进行操作。

函数原型如下:

void RCC_APB2PeriphClockCmd(u32 RCC_APB2Periph, FunctionalState NewState);

第一个成熟是选择对应的APB2,我们可以选择的APB2的值如下:

描述
RCC_APB2Periph_AFIO功能复用I/O时钟
RCC_APB2Periph_GPIOAGPIOA时钟
RCC_APB2Periph_GPIOBGPIOB时钟
RCC_APB2Periph_GPIOCGPIOC时钟
RCC_APB2Periph_GPIODGPIOD时钟
RCC_APB2Periph_GPIOEGPIOE时钟
RCC_APB2Periph_ADC1ADC1的时钟
RCC_APB2Periph_ADC2ADC2的时钟
RCC_APB2Periph_TIM1TIM1的时钟
RCC_APB2Periph_SPI1SPI1的时钟
RCC_APB2Periph_USART1USART1的时钟
RCC_APB2Periph_ALL开启全部的APB2的时钟

这里先介绍一下APB2的一些内容,后面会用到APB1还会给大家介绍一下。

第二个参数就是使能或者失能,可以填写使能ENABLE或者失能DISABLE

例如我们在GPIOB口中的PB0引脚上接了一个LED灯,我们的初始化就可以这样写:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

这样我们就可以打开对应的GPIO口的时钟了,就可以继续对GPIO口中的引脚进行操作了。

这里需要注意一下,这个打开时钟的操作是必须的,如果你没有对其打开,那会导致引脚没办法使用,时钟这个东西就是CPU的心脏,如果没有心脏,那它就shi了。

1.1.2 初始化对应的GPIO引脚

当打开时钟后我们就可以对其中的引脚进行操作了。引脚是在GPIO口中的,必须得上一步初始化后我们对引脚初始化才有用。

这里对对应的引脚进行初始化需要用到一个结构体,然后将写好的结构体传递到函数中进行初始化,我们使用的结构体类型为:GPIO_InitTypeDef,我们需要先利用它来创建一个结构体变量,然后对这个变量进行初始化即可。

这个结构体的原型如下:

typedef struct{u16 GPIO_Pin;GPIOSpeed_TypeDef GPIO_Speed;GPIOMode_TypeDef GPIO_Mode;
}GPIO_InitTypeDef;

GPIO_Pin是需要填写的对应GPIO引脚

参数描述
GPIO_Pin_None没有引脚被选中
GPIO_Pin_x选中GPIOx引脚
GPIO_Pin_All全部选中

GPIO_Speed是设置引脚的速率

参数描述
GPIO_Speed_2MHz最高输出速率2MHz
GPIO_Speed_10MHz最高输出速率10MHz
GPIO_Speed_50MHz最高输出速率10MHz

GPIO_Mode是设置引脚的工作模式

参数描述
GPIO_Mode_Out_PP推挽输出
GPIO_Mode_Out_OD开漏输出
GPIO_Mode_AF_PP复用开漏输出
GPIO_Mode_AF_OD复用推挽输出
GPIO_Mode_AIN模拟输入
GPIO_Mode_IN_FLOATING浮空输入
GPIO_Mode_IPU上拉输入
GPIO_Mode_IPD下拉输入

这个还是比较复杂,所以我这给大家一个一个的介绍一下:

1.1.2.1 推挽输出

这个可以理解为输出的就是最大电压和0电压,效率高。高电平就是5V,低电平就是0V。

1.1.2.2 开漏输出

这个高电平不是很高,如果要让其输出高电平则需要外接一个上拉电阻,但它对电流的吸收很强,这种模式一般对那种需要复用的引脚使用,用来接收电平信息。

1.1.2.3 浮空输入

没有上拉和下拉电阻的输入,默认为一种中间态,一点浮动都会被接收到。

1.1.2.4 上拉输入

在输入内部增加了一个上拉电阻,在没有电平来的情况下,这个引脚默认的电平是高电平,一般用这种方式来接受下降沿的电平信息。

1.1.2.5 下拉输入

在输入的内部增加了一个下拉电阻,在没有电平进入的情况下,这个引脚默认的电平是低电平,一般用这种方式来接受上升沿的电平信息。

1.1.2.6 模拟输入

一般用直接引脚来输入模拟信号后进行数模转换,后面在数模转换的时候会使用到这个引脚。

其实这些方式慢慢使用都能记得住的,我们知道了初始化结构体后我们就可以创建一个初始化结构体并为其配置

GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;

如果要对多个引脚进行初始化的话,我们可以使用逻辑与运算符,将多个引脚连接在一起,比如我要初始化0,1,5,7引脚,那么代码可以这样写:

GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;

当这样配置好后我们需要使用到GPIO_Init()函数将GPIO口和GPIO引脚进行初始化,函数原型:

void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);

第一个参数是GPIO口的名称,第二个参数是刚才初始化的GPIO引脚的结构体地址。

我们将刚才设置好的内容拿来初始化吧:

GPIO_Init(GPIOB, &GPIO_InitStruct);

这样就可以完成对GPIO口和对应的引脚进行初始化了。

如果要对多个不同模式的引脚初始化,我们可以在写好一个初始化结构体后调用一下初始化函数,例如PA5是推挽输出,PC3是上拉输入,那我们的初始化代码可以这样写:

GPIO_InitTypeDef GPIO_InitStruct = {0};GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOC, &GPIO_InitStruct);
1.1.3 对其中的GPIO引脚进行操作

这里就可以对其引脚进行操作了,一般操作就是输入或者是输出,这个操作需要看上面初始化的模式是什么,如果是输出,那只能输出,如果是输入,那只能输入。

这里有一个特殊的,就是开漏输出,这个模式也可以使用输入的函数来读取外部的引脚电平。

1.1.3.1 GPIO_SetBits

这个函数可以将对应的引脚置为1。

函数原型如下:

void GPIO_SetBits(GPIO_TypeDef* GPIOx, u16 GPIO_Pin);

第一个参数是对应的GPIO口,第二个是GPIO引脚。例如我要对PB6置1,代码可以这样写:

GPIO_SetBits(GPIOB, GPIO_Pin_6);

这里注明一下,在单片机开发中,有一个类型规范,这个规范是在标准库中是定义好的,我们可以直接拿来使用,比如上面的u16就是对应着unsigned short无符号的短整形,其他的可以类推。

1.1.3.2 GPIO_ResetBits

这个函数可以将对应的引脚置为0.

函数原型如下:

void GPIO_ResetBits(GPIO_TypeDef* GPIOx, u16 GPIO_Pin);
1.1.3.3 GPIO_Write

这个引脚是对GPIO口的所有引脚进行操作。

函数原型如下:

void GPIO_Write(GPIO_TypeDef* GPIOx, u16 PortVal);

第一个参数是GPIO口,第二个参数是这个GPIO口的所有引脚的状态,例如我们要让GPIOA的PA7引脚为高电平,其他引脚为低电平,那么代码可以这样写:

GPIO_Write(GPIOA, 0x0080);    // 0000 0000 1000 0000
1.1.3.4 GPIO_WriteBit

这个函数是对特定位进行操作。

函数原型如下:

void GPIO_WriteBit(GPIO_TypeDef* GPIOx, u16 GPIO_Pin, BitAction BitVal);

第一个参数是GPIO口,第二个参数是对应的引脚,第三个是设置的电平,这有两个参数可以填写,高电平Bit_SET和低电平Bit_RESET,当然,如果你记不住,你也可以直接用0和1来代替。

比如我们要对PB0变成高电平,那么代码可以这样写:

GPIO_WriteBits(GPIOB, GPIO_Pin_0, Bit_SET);
// 或者
GPIO_WriteBits(GPIOB, GPIO_Pin_0, 1);

上面两种都是可以的写法。

上面的都是输出的函数,现在我们利用这个来做一个LED流水灯,首先先用面包版搭建一个电路

1.jpg

流水灯的引脚挨着连着单片机的PA口的0到7引脚,然后对其进行初始化,代码如下:

#include <stm32f10x.h>// 延时函数,使用的是SysTick定时器
void delay(unsigned int time){unsigned int temp;SysTick -> LOAD = 9000 * time;SysTick -> CTRL = 0x01;SysTick -> VAL = 0;do{temp = SysTick -> CTRL;}while((temp & 0x01) && (!(temp & (1 << 16))));SysTick -> CTRL = 0;SysTick -> VAL = 0;
}int main(){GPIO_InitTypeDef GPIO_InitStruct = {0};unsigned int value = 0x0001;unsigned char i;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStruct);while(1){value = 0x0001;for (i = 0; i < 8; i++){GPIO_Write(GPIOA, ~value);value <<= 1;delay(1000);}}return 0;
}

这里是直接使用GPIO_Write()函数对GPIOA口进行操作,这种方式很简单,如果直接用GPIO_WriteBit(),就很麻烦,大家可以试试。

3.查询方式

这种方式需要查询外设的状态后才能开始接下来的工作,使用查询方式的大概步骤如下:

1.设置输入模式

2.判断是否满足条件

3.满足条件传输数据

4.不满足继续查询

3.1 设置输入模式

和之前设置输出一样,使用初始化函数即可初始化,这里可以选择上拉输入、输出模式和浮空输入,当然也可以设置成开漏输出。

这个选择需要根据外设的电路来决定,就拿按键来举例,假如按钮的另一边的接地,当按下按键后,单片机的引脚就会受到地电平,如果设置成下拉输入,那单片机的接口默认是低电平,按下后还是接受到低电平,这样就没有区别,所以就不能设置成下拉输入。

如果设置成浮空输入,那只要有一点变化就会发生电平变化,不稳定也不确定,所以一般在严格规范下最好不要使用,自己玩可以。

如果设置成上拉输入,在默认情况下引脚的电平是高电平,当外设输入低电平时就会发生电平的变化,所以就能选择出选择的模式。

根据外设可以确定好输入的模式后就可以进行初始化。

3.2 判断条件

这里使用到判断和输入函数,所以这里给大家介绍一下全部的输入模式。

3.2.1 GPIO_ReadInputDataBit

这个函数是读取指定引脚的电平。

函数原型如下:

u8 GPIO_ReadInitputDataBit(GPIO_TypeDef* GPIOx, u16 GPIO_Pin);
3.2.2 GPIO_ReadInputData

读取指定GPIO口的电平值。

u16 GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
3.2.3 GPIO_ReadOutputDataBit

读取指定引脚输出的电平值。

u8 GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, u16 GPIO_Pin);
3.2.4 GPIO_ReadOutputData

读取指定GPIO口的输出的电平值。

u16 GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);

学习完输入函数后我们可以利用一下输入函数来做一个查询方式的流水灯。当按键按下时,流水灯启动,没按下时就停止。

这里假如只有一个按键接到PB0上,按下按钮后接受到低电平,我们的判断代码就可以这样写:

if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0){}
// 或者
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == Bit_RESET){}

3.3 执行内容

我们在写好的判断语句内部写上我们需要执行的代码即可完成查询方式的数据交换。

完整代码如下:

#include "stm32f10x.h"void delay(unsigned int time){unsigned int temp;SysTick -> LOAD = 9000 * time;SysTick -> CTRL = 0x01;SysTick -> VAL = 0;do{temp = SysTick -> CTRL;}while((temp & 0x01) && (!(temp & (1 << 16))));SysTick -> CTRL = 0;SysTick -> VAL = 0;
}int main(){GPIO_InitTypeDef GPIO_InitStruct = {0};unsigned short value = 0x0001;u8 i;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStruct);GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;GPIO_Init(GPIOB, &GPIO_InitStruct);while(1){if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0){value = 0x0001;for (i = 0; i < 8; i++){GPIO_Write(GPIOA, ~value);value <<= 1;delay(1000);}}}return 0;
}

当按键按下后,流水灯会开始启动,执行完一次后就会结束,我们可以感觉这个代码再来改写一下,当按下按钮后流水灯启动直到再次按下按钮后结束流水灯。

改写后的代码如下:

#include "stm32f10x.h"void delay(unsigned int time){unsigned int temp;SysTick -> LOAD = 9000 * time;SysTick -> CTRL = 0x01;SysTick -> VAL = 0;do{temp = SysTick -> CTRL;}while((temp & 0x01) && (!(temp & (1 << 16))));SysTick -> CTRL = 0;SysTick -> VAL = 0;
}int main(){GPIO_InitTypeDef GPIO_InitStruct = {0};unsigned short value = 0x0001;u8 i, flag = 0;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStruct);GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;GPIO_Init(GPIOB, &GPIO_InitStruct);while(1){if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0){delay(100);    // 消抖flag = 0;while(1){value = 0x0001;for (i = 0; i < 8; i++){GPIO_Write(GPIOA, ~value);value <<= 1;delay(1000);if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0){flag = 1;break;}}if (flag){delay(100);break;}}}}return 0;
}

这样就可以实现改功能了。

在上面的代码中出现了个消抖,因为按键在按下的过程中是会有抖动的,我们需要消除这个抖动让接受的数据唯一,并且要错过按下后电平还是原来的电平时又进入到循环中的问题。

上面的只是用延时函数简单的消除了一下,可以在延时完成后加上一个循环来保证该电平又恢复到原来的状态。

4.中断方式

这种方式的原理不会在这一篇文章中说明,等后面会专门出一篇文章来介绍一下原理和复杂应用后面再说,先简单的介绍一下。

中断设置方式很简单

1.初始化引脚

2.初始化中断EXTI

3.设置NVIC的参数

4.重写中断服务程序

4.1 初始化引脚

这里初始化引脚和前面的一样,也是设置为输入模式,但设置完成后需要将初始化的引脚绑定到EXTI外部中断上,这里使用的函数是GPIO_EXTILineConfig()

函数的原型是

void GPIO_EXTILineConfig(u8 GPIO_PortSource, u8 GPIO_PinSource);

将指定引脚映射到EXTI上,然后我们就可以初始化EXTI外部中断了。

第二个参数并不是直接填写引脚号,这个是需要填写通道值,总共有0到15可以选择,这0到15需要感觉选择的引脚号了决定,因为32单片机的中断是将GPIO的引脚号一致的接入到一个通道中,比如通道0接的是PA0到PE0中的所有0号引脚,然后以此类推。

比如我们要将GPIOB口的PB4引脚打开EXTI中断,那么映射函数如下:

GPIO_EXTILineConfig(GPIOB, GPIO_PinSource4);

4.2 初始化外部中断

这里需要使用到EXTI的结构体进行初始化,该结构体类型为:EXTI_InitTypeDef,原型为:

typedef struct{u32 EXTI_Line;EXTIMode_TypeDef EXTI_Mode;EXTIrigger_TypeDef EXTI_Trigger;FunctionalState EXTI_LineCmd;
}GPIO_InitTypeDef;

第一个参数EXTI_Line是选择中断线路,总共有18根外部中断线,这个可以根据自己的安排来安排对应的线路,对应的中断服务入口地址是不同的。

第二个参数EXTI_Mode是选择上面选择的线路模式。有两个选项可以填写:

EXTI_Mode_Event:设置EXTI线路为事件请求。

EXTI_Mode_Interrupt:设置EXTI线路为中断请求。

第三个参数是设置线路的触发边沿,有三个可选的参数:

EXTI_Trigger_Falling:设置输入线路为下降沿触发。

EXTI_Trigger_Rising:设置输入线路为上升沿触发。

EXTI_Trigger_Rising_Falling:设置输入线路为上升沿和下降沿触发。

第四个参数是选中的线路是否开启,有两个参数,一个是使能ENABLE另一个是失能DISABLE

当设置完成后我们可以用EXTI_Init()函数将刚才初始化的结构体传入进去进行初始化。

4.3 设置NVIC

NVIC是一个中断优先级判断的一个逻辑电路,我们可以为我们前面设置的中断设置一下中断的优先级,优先级后面会详细的说明,这里只简单的介绍一下如何初始化。

设置NVIC也是需要使用到一个结构体来进行初始化,这个结构体类型为:NVIC_InitTypeDef,结构体原型如下:

typedef struct{u8 NVIC_IRQChannel;u8 NVIC_IRQChannelPreemptionPrior;u8 NviC_IRQChannelSubPriority;FunctionalState NVIC_IRQChannelCmd;
}NVIC_InitTypeDef;

第一个参数是选择指定的IRQ通道,这里简单介绍一下我们要用的外部中断线的中断通道:

参数描述
EXTI0_IRQn外部中断线0中断
EXTI1_IRQn外部中断线1中断
EXTI2_IRQn外部中断线2中断
EXTI3_IRQn外部中断线3中断
EXTI4_IRQn外部中断线4中断
EXTI9_5_IRQn外部中断线5~9中断
EXTI15_10_IRQn外部中断线10~15中断

第二个参数是设置IRQ通道的抢占优先级,抢占优先级就是当在执行这个中断的时候,由来了一个中断,那这个新来的中断是否会干扰当前执行的中断就得看这个抢占优先级,指定的优先级数值越低,优先级越高,这里填写数值就可以了。

第三个参数是优先级,也是填写数值即可,优先级是确定当同一时刻都接受到中断源时,谁先执行就按照这个优先级来确定。

第四个参数是使能或者失能,也就是ENABLEDISABLE

当填写好这个初始化结构体后我们调用NVIC_Init()函数将填写好的结构体传入进去即可。

4.4 重写中断服务函数

这个中断服务函数可以在startup_stm32f10x_hd.s文件中查看,其中这个就是中断服务函数的列表:

2.png

比如说我们这设置好外部中断通道0中断,那么我们要重写EXTI0_IRQHandler函数,重写方法很简单,我们先定义一下这个函数:

void EXTI0_IRQHandler(){}

然后在内部先判断一下是不是该通道的线路触发,因为我们可以让一个通道选择多条线,所以需要有一个判断,当确实是这个线路后我们清除一下该通道的标志位然后就可以开始我们的工作了。

判断和清理的代码如下:

void EXTI0_IRQHandler(){if (EXTI_GetITStatus(EXTI_Line0) == SET){    // 判断线路0是否触发EXTI_ClearFlag(EXTI_Line0);     // 清理线路0的标志位}
}

当然,如果只有一个线路,那就可以不用判断,如果你嫌麻烦。

知道了中断的设置后我们可以写出对应的代码了,完整代码如下:

#include "stm32f10x.h"u8 flag = 0;void MX_EXTI_Init(){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOA, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);    // 开启中断的时钟// 中断初始化函数EXTI_InitTypeDef EXTI_InitStruct = {0};NVIC_InitTypeDef NVIC_InitStruct = {0};GPIO_InitTypeDef GPIO_InitStruct = {0};GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStruct);GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_Init(GPIOA, &GPIO_InitStruct);GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);     // 设置外部线路EXTI_InitStruct.EXTI_Line = EXTI_Line0;EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;    // 下降沿触发EXTI_InitStruct.EXTI_LineCmd = ENABLE;EXTI_Init(&EXTI_InitStruct);NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);      // NVIC优先级分组NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn;NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStruct);
}void delay(unsigned int time){unsigned int temp;SysTick -> LOAD = 9000 * time;SysTick -> CTRL = 0x01;SysTick -> VAL = 0;do{temp = SysTick -> CTRL;}while((temp & 0x01) && (!(temp & (1 << 16))));SysTick -> CTRL = 0;SysTick -> VAL = 0;
}void EXTI0_IRQHandler(void){if (EXTI_GetITStatus(EXTI_Line0) == SET){EXTI_ClearFlag(EXTI_Line0);flag = 1;}
}int main(){u32 value;u8 i;MX_EXTI_Init();while(1){if (flag){flag = 0;value = 0x0001;for (i = 0; i < 8; i++){// GPIO_WriteBit(GPIOA, GPIO_Pin_0, 1);GPIO_Write(GPIOA, ~value);value <<= 1;delay(1000);}}}return 0;
}

5.DMA方式

DMA方式只适合大量数据的交换,这里不给大家介绍,等后面介绍一下存储芯片会使用到这种方式的。

总结

这一章是学会单片机的起始,后面的内容都会围绕着这些东西来进行操作,像什么点灯、控制OLED屏幕或者大屏幕、操作传感器、控制通讯模块等等等等都是和IO口的操作脱离不了关系的,学完这个后后面会很容易的掌握单片机的很多操作,我们只要勤加练习就可以学会单片机,单片机其实很简单。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/643833.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Docker常用命令(镜像、容器、网络)

一、镜像 1.1 存出镜像 将镜像保存成为本地文件 格式&#xff1a;docker save -o 存储文件名 存储的镜像docker save -o nginx nginx:latest 1.2 载入镜像 将镜像文件导入到镜像库中 格式&#xff1a;docker load < 存出的文件或docker load -i 存出的文件…

【大语言模型LLM】- Meta开源推出的新一代大语言模型 Llama 3

&#x1f525;博客主页&#xff1a;西瓜WiFi &#x1f3a5;系列专栏&#xff1a;《大语言模型》 很多非常有趣的模型&#xff0c;值得收藏&#xff0c;满足大家的收集癖&#xff01; 如果觉得有用&#xff0c;请三连&#x1f44d;⭐❤️&#xff0c;谢谢&#xff01; 长期不…

c++的策略模式,就是多态

一、定义&#xff1a; 策略模式定义了一系列的算法&#xff0c;并将每一个算法封装起来&#xff0c;而且使它们还可以相互替换。 策略模式让算法独立于使用它的客户而独立变化。 二&#xff0c;核心 抽象策略&#xff08;抽象基类&#xff09;&#xff08;Strategy&#xff09…

【01-机器学习入门:理解Scikit-learn与Python的关系】

文章目录 前言Python与机器学习Scikit-learn简介Scikit-learn与Python的关系使用Scikit-learn进行机器学习结语前言 在当今的数据科学和人工智能领域,机器学习已经成为了一个不可或缺的组成部分。而对于那些刚刚踏入这一领域的新手来说,理解机器学习的基本概念和找到合适的工…

消灭AI“耗电巨兽”?暴雨服务器推出液冷节能降耗算力方案

在科技飞速发展的今天&#xff0c;人工智能已成为驱动未来的重要力量。随着AI及大模型技术的进一步普及和应用场景的拓宽&#xff0c;相关算力需求呈指数级增长&#xff0c;大规模的AI训练和推理过程均需消耗大量电力&#xff0c;如同一个巨大的电力黑洞&#xff0c;吞噬着海量…

安全小课堂丨什么是暴力破解?如何防止暴力破解

什么是暴力破解&#xff1f; 暴力破解也可称为穷举法、枚举法&#xff0c;是一种比较流行的密码破译方法&#xff0c;也就是将密码进行一一推算直到找出正确的密码为止。比如一个6位并且全部由数字组成的密码&#xff0c;可能有100万种组合&#xff0c;也就是说最多需要尝试10…

图书个性化推荐系统,基于 SpringBoot+Vue+MySQL 开发的图书个性化推荐系统设计实现

目录 一. 前言 二. 功能模块 2.1. 前台首页功能模块 2.3. 管理员功能模块 2.4. 学生功能模块 三. 部分代码实现 一. 前言 本论文主要论述了如何使用JAVA语言开发一个图书个性化推荐系统&#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;采用B/S架…

Jackson 2.x 系列【31】Spring Boot 集成之字典翻译

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Jackson 版本 2.17.0 本系列Spring Boot 版本 3.2.4 源码地址&#xff1a;https://gitee.com/pearl-organization/study-jaskson-demo 文章目录 1. 场景描述2. 案例演示2.1 修改枚举2.2 定义注解…

华为OD机试 - 智能驾驶 - 广度优先搜索(Java 2024 C卷 200分)

华为OD机试 2024C卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷C卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;每一题都有详细的答题思路、详细的代码注释、样例测试…

spring的跨域问题

跨域问题 什么是跨域解决跨域 什么是跨域 跨域问题本质是浏览器的一种保护机制&#xff0c;它的初衷是为了保证用户的安全&#xff0c;防止恶意网站窃取数据。如果出现了以下情况中的任意一种&#xff0c;那么它就是跨域请求&#xff1a; 1、协议不同&#xff0c;如 http 和 h…

软件测试之【软件测试概论二】

读者大大们好呀&#xff01;&#xff01;!☀️☀️☀️ &#x1f525; 欢迎来到我的博客 &#x1f440;期待大大的关注哦❗️❗️❗️ &#x1f680;欢迎收看我的主页文章➡️寻至善的主页 文章目录 前言软件测试模型瀑布模型V模型W&#xff08;双V&#xff09;模型测试活动 软…

AI视频下载:零基础2小时学会开发 Chrome扩展程序

无论您是有抱负的Web开发人员、AI爱好者还是生产力黑客&#xff0c;本课程都提供了宝贵的见解和实践经验&#xff0c;帮助您利用AI和Chrome扩展的力量来简化Web自动化&#xff0c;改善各个行业和领域的用户体验&#xff0c;解锁AI驱动生产力的潜力&#xff01; 此课程面向以下…