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口的操作脱离不了关系的,学完这个后后面会很容易的掌握单片机的很多操作,我们只要勤加练习就可以学会单片机,单片机其实很简单。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Hadoop】-Hive部署[12]

目录 思考 VMware虚拟机部署 规划 步骤1&#xff1a;安装MySQL数据库 步骤2&#xff1a;配置Hadoop 步骤3&#xff1a;下载解压Hive 步骤4&#xff1a;提供MySQL Driver包 步骤5&#xff1a;配置Hive 步骤6&#xff1a;初始化元数据库 步骤7&#xff1a;启动Hive&…

TDSQL同一个所属Set显示3个备份节点

欢迎关注“数据库运维之道”公众号&#xff0c;一起学习数据库技术! 本期将为大家分享《TDSQL同一个所属Set显示3个备份节点》的处置案例。 关键词&#xff1a;分布式数据库、TDSQL、备份节点 1、问题描述 登录赤兔管理平台&#xff0c;单击左侧导航栏“实例管理/集群管理”…

漫谈-AI 时代的信息模型

模型化- 数字化转型的重要基石 在各行各业推行数字化转型过程中&#xff0c;构建信息化模型十分重要&#xff0c;它是数字化转型的基石。事实上&#xff0c;数字化转型的核心是“万物皆模型”&#xff0c;在工业领域&#xff0c;以德国为主导的工业4.0 发展进程中&#xff0c;…

Access denied for user ‘zabbix‘@‘localhost‘ (using password: NO)

现象 排查过程 进入数据库show grants for zabbixlocalhost;select host,user from mysql.user;cat /etc/zabbix/zabbix_server.conf | grep DB | grep -vE ‘#|$’cat /etc/zabbix/web/zabbix.conf.php | grep DB 解决办法 mysql 8.0以下 DPassword123.com mariadb -e "…

java多线程-并发和并行

进程 并发 进程中的线程是由CPU进行调度的&#xff0c;但是CPU能够处理的进程数量有限为了保证所有的线程都在运行&#xff0c;CPU会快速切换&#xff0c;给外界的感觉就是所有的线程都在运行&#xff0c;这就是并发。 并行

【力扣 Hot100 | 第六天】4.21(最长连续序列)

文章目录 10.最长连续序列10.1题目10.2解法&#xff1a;哈希法10.2.1哈希思路10.2.2代码实现 10.最长连续序列 10.1题目 给定一个未排序的整数数组 nums &#xff0c;找出数字连续的最长序列&#xff08;不要求序列元素在原数组中连续&#xff09;的长度。 请你设计并实现时…

php 编译安装oracel扩展

第一步安装Oracle客户端 1&#xff0c;需要下载基础包和sdk oracle客户端下载链接&#xff1a;Oracle Instant Client Downloads for Linux x86-64 (64-bit) https://www.oracle.com/database/technologies/instant-client/linux-x86-64-downloads.html 选择最新版本 versi…

国产PLC有哪些,哪个牌子比较好用?

你知道国产PLC有哪些吗,哪个牌子更好用吗&#xff1f; 今天拿出国产先锋的汇川与台达对比&#xff0c;注&#xff1a;视频后方有各品牌学习资料免费送&#xff0c;需要的移步自取。话说回来&#xff0c;只要基于Codesys开发的都比较好用&#xff0c;只是使用底层芯片不同&…

2013-2021年各省经济韧性相关测度指标面板数据

2013-2021年各省经济韧性相关测度指标面板数据 1、时间&#xff1a;2013-2021年 2、指标&#xff1a;城镇化率 %、财政科学技术支出&#xff08;亿元&#xff09;、万人高等教育在校人数&#xff08;万人&#xff09;、财政教育支出&#xff08;亿元&#xff09;、第三产业占…

AD 21、22 软件安装教程

AD2022安装包链接 链接&#xff1a;https://pan.baidu.com/s/1oMNbXibQ1Zjl0RTLdPDVGw 提取码&#xff1a;xfs4 软件下载 1.以管理员身份运行 2. 3. 4. 5.路径最好改为C盘以外的&#xff0c;如D盘&#xff0c;要新建一个空文件夹 6. 7.下载好以后 8.在Crack文件夹下找…

程序员周末提升计划:朝网络安全工程师转型之路

作为一名软件开发人员&#xff0c;我一直对网络安全充满兴趣&#xff0c;并希望在未来转型成为一名网络安全工程师。面对网络安全领域的挑战和机遇&#xff0c;我制定了一个周末提升计划&#xff0c;希望能系统地增强我的技能并为这一跨界做好准备。下面&#xff0c;我将分享我…

有没有学网络空间安全的学长,想知道学长们毕业以后都去干嘛了?

我作为一个零基础小白到白帽黑客&#xff0c;也认识到了很多零基础小白的&#xff0c;有一些网络空间安全的学员&#xff0c;但是大多数还是非计算机相关专业的学员。他们通过系统学习网络安全&#xff0c;掌握黑客技术之后&#xff0c;都找到了自己满意的工作。 同学A&#x…

软文发稿对于企业的重要性

随着社会的发展和科技的进步&#xff0c;软文发稿已成为企业和个人推广和传播信息的一种非常重要的方式。它以隐性的广告形式&#xff0c;通过内容发布&#xff0c;为品牌广告和产品推广铺设了一条隐形高速公路。下面我们就详细解析一下软文发稿的优点和好处。 软文发稿帮助增…

AutoDL运行SCRFD

pycharm-autodl 1.租服务器 3080ti 镜像&#xff1a;PyTorch 1.10.0 Python 3.8(ubuntu20.04) Cuda 11.3 2.jupyterLab激活conda vim ~/.bashrc在最底部添加 source /root/miniconda3/etc/profile.d/conda.sh重启 bash激活conda conda activate base3.pycharm远程连接aut…

【嵌入式AI部署神经网络】STM32CubeIDE上部署神经网络之指纹识别(Pytorch)——篇一|环境搭建与模型初步部署篇

前言&#xff1a;本篇主要讲解搭建所需环境&#xff0c;以及基于pytorch框架在stm32cubeide上部署神经网络&#xff0c;部署神经网络到STM32单片机&#xff0c;本篇实现初步部署模型&#xff0c;没有加入训练集与验证集&#xff0c;将在第二篇加入。篇二详细讲解STM32CubeIDE上…

基于研发过程改进的质量度量模型

随着企业规模和产品项目的不断扩张&#xff0c;全面、精准、高效地保障产品质量成为组织的核心挑战。为了应对这一挑战&#xff0c;企业应寻求采用数字化和智能化的研发过程管理方案&#xff0c;以实现对研发活动的精细化量化控制&#xff0c;并利用数据分析工具深入洞察产品质…

Interpreter 解释器

意图 给定一个语言&#xff0c;定义它的文法的一种表示&#xff0c;并定义一个解释器&#xff0c;这个解释器使用该表示来解释语言中的句子。 结构 AbstractExpression声明一个程序的解释操作&#xff0c;这个接口为抽象语法树中所有结点所共享。TerminalExpression实现与文法…

【IR 论文】Query2doc — 使用 LLM 做 Query Expansion 来提高信息检索能力

论文&#xff1a;Query2doc: Query Expansion with Large Language Models ⭐⭐⭐⭐⭐ Microsoft Research, EMNLP 2023 文章目录 背景介绍Query2doc 论文速读实现细节实验结果和分析总结分析 背景介绍 信息检索&#xff08;Information Retrieval&#xff0c;IR&#xff09;指…

谷歌收录工具有什么好用的?

如果是想促进谷歌的收录&#xff0c;其实能用的手段无非就两个&#xff0c;谷歌GSC以及爬虫池 谷歌gsc就不用说了&#xff0c;作为谷歌官方提供的工具&#xff0c;他能提供最准确的数据&#xff0c;并且可以提交每天更新的链接&#xff0c;进而促进收录&#xff0c;只要你的页面…

【unity】三维数学应用(计算线和面的交点)

【unity】三维数学应用&#xff08;计算线和面的交点&#xff09; 实现方法有多种&#xff0c;下面介绍一种简单的方法。利用一个点指向面上任意点的向量&#xff0c;到该面法线的投影长度相同的基本原理&#xff0c;结合相似三角形既可以求出交点。 原理 如下图 GD组成的线段…