Skip to content

STM32 学习

资料下载

链接:https://pan.baidu.com/s/1xROXFM0g7Kxlptd9NBlW-A 提取码:k2qu --来自百度网盘超级会员V6的分享

1. 入门

1.1 STM32简介

什么是STM32

STM32是ST公司基于ARM Cortex-M内核开发的32位微控制器

STM32常应用在嵌入式领域,如智能车、无人机、机器人、无线通信、物联网、工业控制、娱乐电子产品等

STM32功能强大、性能优异片上资源丰富、功耗低,是一款经典的嵌入式微控制器

目前STM32有如下产品系列覆盖:

  1. 主流系列:STM32F0、STM32F1
  2. 高性能系列:STM32F4、STM32F7、STM32H7
  3. 低功耗系列:STM32L0、STM32L1、STM32L4
  4. 混合信号系列:STM32G0、STM32G4
  5. 无线系列:STM32WB、STM32WL

每一个产品系列下都有不同型号的芯片,对应着不同的资源与性能,具体各型号的资源性能参数可以在官网查询。

如何选择开发板

大多数人会选择STM32F103ZET6或者STM32F407ZGT6作为学习STM32系列的第一块芯片,下面我们将这两个类型做一个简单比较。

项目STM32F103ZET6STM32F407ZGT6
内核ARM Cortex®-M3ARM Cortex®-M4
主频72MHz168MHz
浮点运算单元单精度FPU
DSP指令集
16位定时器8个,6个可配置四路通道12个,每个都可配置四路通道
32位定时器2个
SPI3个,18M/s3个,42M/s
IIC2个3个
串口5个6个
CAN1个2个
ADC3×12位,21通道3×12位,24通道
DAC2×12位2×12位
编程方法寄存器、库函数寄存器、库函数

从以上对比可以看出来,STM32F407相对于STM32F103性能更加强大,外设更加丰富,最主要的是加入了FPU浮点运算单元和DSP指令集,这是Cortex®-M4内核的一个较为强大的升级。而两者都可以基于寄存器和库函数编程,虽然两个芯片寄存器不同,但是现阶段基本都是基于库函数进行编程,尤其是ST近几年主推的HAL库,两者编程十分相似,几乎没有区别,都可以胜任初学的要求。而在电子设计竞赛之中,拥有DSP指令集的STM32F407可以做FFT(快速傅里叶变化)等数学运算,优势非常明显,所以我的初学推荐是STM32F407,且以HAL库为主体,使用CubeMX+Keil或CubeIDE软件学习。

ARM

  • ARM既指ARM公司,也指ARM处理器内核提供商,
  • ARM公司是全球领先的半导体知识产权(IP),全世界超过95%的智能手机和平板电脑都采用ARM架构
  • ARM公司设计ARM内核,半导体厂商完善内核周边电路并生产芯片

image-20240516151004013

STM32命名规则

STM32F103C8T6

  • STM32=基于ARM@核心的32位微控制器

  • 产品类型

    F=通用类型

  • 产品子类型

    101=基本型 102=USB基本型,USB2.0全速设备 103=增强型 105或107=互联型

  • 引脚数目:

    T=36引脚

    C=48引脚

  • 8 闪存存储器容量

    4=16K字节的闪存存储器 6=32K字节的闪存存储器 8=64K字节的闪存存储器 B=128K字节的闪存存储器 C=256K字节的闪存存储器 D=384K字节的闪存存储器 E=512K字节的闪存存储器

  • T封装

    H=BGA T= LQFP U=VFQFPN Y=WLCSP64

  • 6-温度范围

    6=工业级温度范围,-40°C~85°C 7=工业级温度范围,-40°C~105°C

img

开发板

image-20240517120054749

我在网上买的开发板

tb_image_share_1718966535502.jpg

1.2 环境准备

  • STM面包板入门套件
  • Windows电脑
  • 万用表,示波器,镊子,剪刀

image-20240516160635965

1.3 开发环境安装

  • 安装Keil5 MDK
  • 安装器件支持包
  • 软件注册
  • 安装STLINK驱动
  • 安装USB转串口驱动

1) 安装Keil5 MDK

第一步:解压一下MDK,出现一下目录

image-20240518212032380

如果想用最新版本的,请使用以下链接:Arm Keil | MDK-Community edition

点击download就行了

image-20240518155358583

填写个人信息

image-20240518155539876

2024年5月18号,最新版本的就是539.exe,大概1g左右耐心下载。

image-20240518155548931

第二步:双击exe文件

安装软件。选择安装路径

image-20240518212301730

next,随便填写一下信息

image-20240518213207917

这里会弹出安装驱动的东西

image-20240518213325416

接下来会弹出安装 器件支持包的提示

image-20240518213408209

2) 安装器件支持包

这个支持包是Keil5才需要安装的,像Keil4和之前的老版本,是不需要安装的。

这是因为,现在ARM的芯片型号非常多,升级换代的速度非常快。同时支持所有型号的芯片,这个占用内存非常大。

所以把器件支持包独立出来了,安装分为2种方式,第一种就是离线安装,第二种就是在线安装

下面介绍,如何进行离线安装

离线安装的数据包,如下图所示。双击即可安装成功

image-20240518214001587

如果打不开就选择打开方式:我的是"C:\Keil_v5\UV4\PackUnzip.exe"。或者换成中文路径

接下来,就是离线安装.

image-20240518214830882

点击next

image-20240518214840737

这样子我们就安装好了

image-20240518215055584

image-20240518215035198

3) 软件注册

keil5是付费软件,现在开始破解

第一步:复制CID

image-20240518215725789

image-20240518215323474

第二步,解压注册机

image-20240518215414382

第三步:关掉音量,友情提示!!! 输入CID,选择ARM。然后点击Generate。

image-20240518215545957

然后点击lince management

image-20240518215629845

最后出现

image-20240518215704944

说明安装成功

4) 安装STLINK驱动(二选一)

ST-LINK是专门针对意法半导体STM8和STM32系列芯片的仿真器。ST-LINK /V2指定的SWIM标准接口和JTAG/SWD标准接口。

JTAG (Joint Test Action Group,联合测试行动小组)是一种国际标准测试协议(IEEE 1149.1兼容)主要用于芯片内部测试。现在多数的高级器件都支持JTAG协议。

串行调试(SerialWire Debug),应该可以算是一种和JTAG不同的调试模式,使用的调试协议也应该不一样,所以最直接的体现在调试接口上,与JTAG的20个引脚相比,SWD只需要4个(或者5个)引脚结构简单。

https://www.bilibili.com/video/BV1DE411F72E

如何烧录程序?

  1. 驱动安装
  2. 硬件连接
  3. 更改设置

image-20240610195318924

安装ST-LINK驱动

image-20240518222831268

ST-LINK烧录测试

打开电脑的设备管理器,能看到这个就说明成功了。

image-20240610202400975

4) DAP安装(二选一)

如果买的面包板的话,给的都是DAP仿真器。没有使用STLINK

什么是DAP?

特点:

  1. 在线仿真调试功能,采用高速的ARM内核处理协议,性能稳定、快速!
  2. DAP支持所有C0TEX-M系列的ARM处理器!STLINKV2只支持STM32和STM8!
  3. 与MDK、GCC、IAR等环境无缝配合,插上即可识别!
  4. 支持SWD仿真和USB虚拟串口接口!
  5. 为目标板供电。

DAP接线

下面的图为详细接线图

DAP&DAPmini接线图

image-20240610200144751

下图为实物接线图

image-20240610201223441

DAPmini&DAP都为免驱仿真器。DAPmini&DAP的虚拟串口win10是免驱,win7需要安装虚拟串口驱动。注意:DAPmini 没有供电灯,信号灯只在烧录的时候闪烁。

买的面包板,这个就是芯片

IMG_20240518_211505

DAP烧录测试

DAP仿真器在KEIL中下载程序,DEBUG设备是cmsis-dap,点击setting会识别出DAP设备,如设备未识别请检查接线,接线错误,设备也会识别不出来

image-20240610201556695

image-20240610201707127

然后一定要按一下复位键,黄色按钮,让程序重头运行。

cbf14b1fff179cdec6fa5e9600f37c1

5) 安装USB转串口驱动

把这个插上,如果没有驱动的情况下

IMG_20240518_223047

解压完工具软件后,找到CH340驱动

image-20240518223230786

最小系统电路

image-20240518154446011

单片机光有芯片是无法进行工作的,需要连接最基本的电路

1.4 库函数开发

库函数开发流程

  1. 建立工程文件夹,Keil中新建工程,选择型号
  2. 工程文件夹里建立Start、Library、User等文件夹,复制固件库里面的文件到工程文件夹
  3. 工程里对应建立Start、Library、User等同名称的分组,然后将文件夹内的文件添加到工程分组里
  4. 工程选项,C/C++,Include Paths内声明所有包含头文件的文件夹
  5. 工程选项,C/C++Define内定义USE STDPERIPH DRIVER
  6. 工程选项,Debug,下拉列表选择对应调试器,Settings,Flash,Download里勾选Reset and Run

如果使用库函数开发的话,需要用到写好的库函数

解压固件库文件

image-20240518223610637

解压后的文件

image-20240518223732433

新建工程

image-20240519111513568

复制以下的3组文件

第一组

image-20240519112141795

第二组还有复制以下的东西。

image-20240519112339918

第三组

image-20240519112731927

在自己新建的工程文件夹下 新建一个Start文件夹,文件夹名字随意。

image-20240519112446068

复制文件如下

image-20240519112820430

点击Source Group ,然后单击一下,重新命名一下 Start

image-20240519112925585

然后右键单击,把所有文件都添加进来

image-20240519113041933

文件类型选择All Files。选择md结尾的启动文件,

image-20240519113226732

image-20240519113311753

image-20240519113324609

添加结束后的效果

image-20240519113358878

然后需要配置头文件路径

image-20240519113626081

点击ok

image-20240519113703119

然后新建User文件夹

image-20240519113817037

再新建组

image-20240519113831788

image-20240519113846784

image-20240519113951504

然后右键插入 stm32f10x.h

image-20240519114103349

新建Library,打开

image-20240610203350000

找到以下目录

image-20240610203640423

把所有东西都复制到 Library目录下

在把inc目录的头文件全部复制到Library中

image-20240610203805983

然后添加组

image-20240610203904923

把刚刚复制的所有东西都添加进来了。效果如下所示。

image-20240610204012934

再把所有以下三个文件,添加到User目录下

image-20240610204140638

然后添加宏定义。

下面的字符串是啥意思?如果你定义了USE_STDPERIPH_DRIVER这个宏定义,下面的配置才起作用。

image-20240610204434517

按照一下配置信息

image-20240610204725458

点击箱子按钮,设置编译的优先级

image-20240610204831913

点击编译按钮,测试

image-20240610204935945

库函数开发测试

c++
#include "stm32f10x.h"                  // Device header

int main(void)
{
	
	
	// 配置端口模式
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_13;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOC,&GPIO_InitStructure);
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE); // 设置函数的时钟
	// GPIO_SetBits(GPIOC,GPIO_Pin_13); // 设置为高电平
	GPIO_ResetBits(GPIOC,GPIO_Pin_13);
	while(1)
	{
		
	
	}
	
}

然后重新插拔一下,

问题汇总

找不到core_cm3.c的错误

如何找不到core_cm3文件,是软件太新,同样方法把路径:keil5的安装目录的PACK/ARM/CMSIS/Include包含即可

针对2-2新建工程中写了main函数进行编译时出现错误的问题: D:\Keil5\ARM\PACK\Keil\STM32F1xx_DFP\2.2.0\Device\Include\stm32f10x.h(483): error: #5: cannot open source input file "core_cm3.h": No such file or directory 这是因为你装的MDK版本太新了,比如MDK5.12/5.13,它们不会从MDK安装目录去查找头文件。 所以导致这个错误。 解决方法如下: 把core_cm3.h文件复制到keil软件安装的include目录下: D:\MDK\install\ARM\PACK\Keil\STM32F1xx_DFP\2.2.0\Device\Include

image-20240519114820174

error: non-ASM statement in naked 错误

问题分析

keil5版本更新了的缘故,使用了keil5默认的版本6的编译器,而固件库还是支持版本5的编译器。

解决办法

在这里插入图片描述

安装Compiler Version 5

在将keil升级到5.37版本后使用的默认编译器位compiler version6 但是使用这个编译器编译stm32等芯片之前的库文件会有许多报错,但是正点原子,野火等stm32开发板的资料大多都采用的stm32的库文件

而且keil5.37后没有自带arm compiler v5版本的编译器,显示miss compiler version 5 ,可以自行在arm官网上下载v5版编译器

Downloads - Arm Developer

但是需要注册登录,而且账号要审核后才能使用,大概1天,操作非常麻烦

如果嫌麻烦,直接在百度云下载即可 百度网盘下载链接:https://pan.baidu.com/s/1q3XS6ch6PLfJHW3a3P1RwQ?pwd=uezi 提取码:uezi

  1. 将安装包解压到本地
  2. 找到Keil MDK的ARM 文件夹
  3. 在ARM文件夹中创建ARMCC文件夹

image-20240519115757260

运行之前解压的文件中的ARMCompiler506_b960.msi文件

image-20240519115845328

运行之后一直点击Next,直到碰到这个界面

image-20240519115908374

点击Browse并将文件夹换成我们刚才创建的ARMCC那个文件夹

image-20240519115948064

然后配置文件

image-20240519120102202

image-20240519120118640

出现一下界面

image-20240519120142338

结束

image-20240519120205727

最后再次编译,就一点问题没得

image-20240519120228906

1.5 面包板的使用

https://www.bilibili.com/video/BV1p14y1y7qi

上面为面包板正面图,下面为面包板撕掉的样子。

image-20240611161204296

中间的金属是竖放的,5个相通的

2. GPIO输出

2.1 GPIO介绍

什么是GPIO

GPlO,全称为 General Purpose Input Output(通用输入输出),是一种通用的数字输入/输出端口。在嵌入式系统中,GPIO被设计为灵活的引脚,可以被配置为输入或输出,以满足不同的应用需求。

  • 可配置为8种输入输出模式。
  • 引脚电平:0V~3.3V,部分引脚可容忍5V
  • 输出模式下可控制端口输出高低电平,用以驱动LED、控制蜂鸣器,模拟通信协议输出时序等。
  • 输入模式下可读取端口的高低电平或电压,用于读取按键输入、外接模块电平信号输入、ADC电压采集、模拟通信协议接收数据等。

GPIO特点

  • IO口数量:

    不同型号的微控制器,芯片,或者开发板上的 GPIO 数量可能不同。通过查阅相应的选型手册、数据手册或者开发板文档,可以了解每个型号上可用的 GPIO 引脚数量和功能。

  • 快速翻转:

    GPIO 可以快速地翻转其输出状态。每次翻转的速度可能取决于具体的芯片型号和工作频率。一些微控制器甚至可以在极短的时间内完成翻转,例如在几个时钟周期内。

  • 中断支持:

    每个 GPIO 引脚都可以配置为中断触发源。当引脚的电平状态变化(上升沿、下降沿、或者边沿触发)时,可以触发相应的中断服务程序。这使得 GPIO 可以用于实时事件处理。

  • 工作模式:

    GPIO 支持多种工作模式,具体的工作模式可能因芯片型号而异。常见的工作模式包括输入模式、输出模式、复用功能模式(例如,配置 GPIO 为串口、SPI、I2C 等功能)、模拟输入模式等。

image-20240611111357039

image-20240611111403463

在这里插入图片描述

在STM32 F1系列中,每个GPIO端口都包含了通用输入和通用输出功能。以下是F1系列IO端口基本结构和工作流程的详细介绍:

F1系列IO端口基本结构:

  1. 通用输入:

    • 通用输入的外部引脚信号首先经过保护二极管,然后进入输入驱动器。
    • 在输入驱动器内部,信号可以经过上拉电阻、下拉电阻、模拟输入(如果配置为模拟输入引脚)、TTL(肖特基触发器)等。
  2. 通用输出:

    • 通用输出的数据可以通过输出控制器进行读写。
    • 输出控制器包含输出数据寄存器(ODR),它存储了输出端口的当前状态。通过对ODR的写入,可以改变输出端口的状态。
  3. 执行机构:

    执行机构包括P-MOS(P型金属氧化物半导体场效应晶体管)和N-MOS(N型金属氧化物半导体场效应晶体管)管。这些管通过输出控制器的控制机构来进行控制。

  4. 控制机构:

    控制机构是一个逻辑电路,通过外部输入(例如输入数据寄存器的值)来控制执行机构中的P-MOS和N-MOS管的通断状态。

  5. 复用功能:

    GPIO引脚可以配置为复用功能输入或输出。在复用功能输入模式下,TTL(肖特基触发器)用于将引脚连接到片上外设,例如USART、SPI、I2C等。

工作流程

  1. 通用输入流程:

    • 外部引脚信号通过保护二极管进入输入驱动器。
    • 输入驱动器内部可能包括上拉电阻、下拉电阻、模拟输入等功能。
    • TTL肖特基触发器将输入信号传递到复用功能输入或者通过输入数据寄存器(IDR)读取。
  2. 通用输出流程:

    • 数据可以通过输出数据寄存器(ODR)进行写入,改变输出端口的状态。
    • 输出控制器的执行机构控制P-MOS和N-MOS管,决定引脚的输出状态。
    • 复用功能输出可以通过片上外设进行配置。

这样的结构和流程使得F1系列的GPIO引脚可以适应多种应用场景,既可以用于普通的输入输出,也可以配置为连接各种片上外设,提供更多的灵活性和功能。

通过配置GPIO的端口配置寄存器,端口可以配置成以下8种模式。

image-20240611155031764

2.2 LED闪烁&LED流水灯&蜂鸣器

LED闪烁

image-20240611161651815

代码

c++
#include "stm32f10x.h"                  // Device header

int main(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); // 设置函数的时钟

	// 配置端口模式
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	

	// GPIO_SetBits(GPIOC,GPIO_Pin_13); // 设置为高电平
	GPIO_ResetBits(GPIOA,GPIO_Pin_0);
	while(1)
	{
		
	
	}
	
}

注意事项:

  • 注意!!!这里一定要在魔术棒/ C/C++的页面下把C99mode勾选上!否则会报错,就算是拉到前面也不会有正确的反馈。

  • A0引脚是负极,负极是高电平的话,和正极相等,不会产生电流,灯就不亮

  • 用“指南者”板子的,要把GPIOA改为GPIOB,可以亮绿灯,具体的亮灯规则还需要参考《零死角》那本书

下面是实物图

1718104925509

下面开始LED闪烁的步骤。需要延时函数

c++
#include "stm32f10x.h"                  // Device header

int main(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); // 设置函数的时钟

	// 配置端口模式
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; // 设置GPIO口模式,
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0; 
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	

	// GPIO_SetBits(GPIOC,GPIO_Pin_13); // 设置为高电平
	// GPIO_ResetBits(GPIOA,GPIO_Pin_0);
	//GPIO_SetBits(GPIOA,GPIO_Pin_0);
	
	// GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_RESET); // 设置A0口为低电平,低电平点亮。
	GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_SET);// 设置A0口为高电平,LED熄灭。
	while(1)
	{
		
	
	}
	
}

复制以下这两个文件

image-20240611201214028

文件内容如下:Delay.c

c++
#include "stm32f10x.h"

/**
  * @brief  微秒级延时
  * @param  xus 延时时长,范围:0~233015
  * @retval
  */
void Delay_us(uint32_t xus)
{
	SysTick->LOAD = 72 * xus;				//设置定时器重装值
	SysTick->VAL = 0x00;					//清空当前计数值
	SysTick->CTRL = 0x00000005;				//设置时钟源为HCLK,启动定时器
	while(!(SysTick->CTRL & 0x00010000));	//等待计数到0
	SysTick->CTRL = 0x00000004;				//关闭定时器
}

/**
  * @brief  毫秒级延时
  * @param  xms 延时时长,范围:0~4294967295
  * @retval
  */
void Delay_ms(uint32_t xms)
{
	while(xms--)
	{
		Delay_us(1000);
	}
}
 
/**
  * @brief  秒级延时
  * @param  xs 延时时长,范围:0~4294967295
  * @retval
  */
void Delay_s(uint32_t xs)
{
	while(xs--)
	{
		Delay_ms(1000);
	}
}

Delay.h

C++
#ifndef __DELAY_H
#define __DELAY_H

void Delay_us(uint32_t us);
void Delay_ms(uint32_t ms);
void Delay_s(uint32_t s);

#endif

复制Delay文件

image-20240611201129898

点击ADD Files按钮,然后选择system中的Delay.h 和Delay.c 添加进去。

image-20240611201424592

添加头文件。

image-20240611201633431

输入下面的代码,就变成点灯大师了。

c++
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
int main(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); // 设置函数的时钟

	// 配置端口模式
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; // 设置GPIO口模式,
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0; 
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	

	// GPIO_SetBits(GPIOC,GPIO_Pin_13); // 设置为高电平
	// GPIO_ResetBits(GPIOA,GPIO_Pin_0);
	//GPIO_SetBits(GPIOA,GPIO_Pin_0);
	
	// GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_RESET); // 设置A0口为低电平,低电平点亮。
	// GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_SET);// 设置A0口为高电平,LED熄灭。
	while(1)
	{
		
		GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_RESET); // 设置A0口为低电平,低电平点亮。
		Delay_ms(500);
		GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_SET);// 设置A0口为高电平,LED熄灭。
		Delay_ms(500);
	}
	
}

LED流水灯

首先看一下面包板的接线图

3-2 LED流水灯

实物图

1718961502228

代码

c++
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
int main(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); // 设置函数的时钟

	// 配置端口模式
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; // 设置GPIO口模式,
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_All; 
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	

	while(1)
	{
		
		GPIO_Write(GPIOA,~0x0001); // 0000 0000 0000 0001
		Delay_ms(500);
		GPIO_Write(GPIOA,~0x0002); // 0000 0000 0000 0010
		Delay_ms(500);
		GPIO_Write(GPIOA,~0x0004); // 0000 0000 0000 0100
		Delay_ms(500);
		GPIO_Write(GPIOA,~0x0008); // 0000 0000 0000 1000
		Delay_ms(500);
		
		GPIO_Write(GPIOA,~0x0010); // 0000 0000 0001 0000
		Delay_ms(500);
		GPIO_Write(GPIOA,~0x0020); // 0000 0000 0010 0000
		Delay_ms(500);
		GPIO_Write(GPIOA,~0x0040); // 0000 0000 0100 0000
		Delay_ms(500);
		GPIO_Write(GPIOA,~0x0080); // 0000 0000 1000 0000
		Delay_ms(500);
	}
	
}

蜂鸣器

面包板接线图

3-3 蜂鸣器

A15,B3,B4这几个接口不能选,如果要用作普通端口的话,需要进行一些配置

实物图

1718963392572

代码

c++
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
int main(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); // 设置函数的时钟

	// 配置端口模式
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; // 设置GPIO口模式,
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_12; 
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	

	while(1)
	{
		
		GPIO_ResetBits(GPIOB,GPIO_Pin_12); // 0000 0000 0000 0001
		Delay_ms(100);
		GPIO_SetBits(GPIOB,GPIO_Pin_12); // 0000 0000 0000 0010
		Delay_ms(100);
		GPIO_ResetBits(GPIOB,GPIO_Pin_12); // 0000 0000 0000 0001
		Delay_ms(100);
		GPIO_SetBits(GPIOB,GPIO_Pin_12); // 0000 0000 0000 0010
		Delay_ms(700);
		
	}
	
}

3. GPIO输入

按键介绍:常见的输入设备,按下导通,松手断开

按键抖动:由于按键内部使用的是机械式弹簧片来进行通断的,所以在按下和松手的瞬间会伴随有一连串的抖动

传感器模块

传感器模块:传感器元件(光敏电阻/热敏电阻/红外接收管等)的电阻会随外界模拟量的变化而变化,通过与定值电阻分压即可得到模拟电压输出,再通过电压比较器进行二值化即可得到数字电压输出。

image-20240621221621440

按键控制LED

接线图

3-4 按键控制LED

实物图

IMG_20240622_122449

代码

使用模块化编程,新建Hardware文件夹,

image-20240622113442171

image-20240622113544616

image-20240622122528959

Key.c

c++
#include "stm32f10x.h" 
#include "Delay.h"

void Key_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); // 设置函数的时钟
	GPIO_InitTypeDef GPIO_InitStructure;
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; // 设置GPIO口模式,
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1 | GPIO_Pin_11; 
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);

}


uint8_t Key_GetNum(void)
{
	uint8_t KeyNum=0;
	
	if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0)
	{
		Delay_ms(20);
		while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0);
		Delay_ms(20);
		KeyNum=1;
	}
	
		if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11)==0)
	{
		Delay_ms(20);
		while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11)==0);
		Delay_ms(20);
		KeyNum=2;
	}
	return KeyNum;
}

Key.h

c++
#ifndef __KEY_H
#define __KEY_H

void Key_Init(void);
uint8_t Key_GetNum(void);


#endif

LED.c

c++
#include "stm32f10x.h"  

void LED_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); // 设置函数的时钟
	GPIO_InitTypeDef GPIO_InitStructure;
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; // 设置GPIO口模式,
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1 | GPIO_Pin_2; 
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
}

void LED1_ON(void)
{
	GPIO_ResetBits(GPIOA,GPIO_Pin_1);
}

void LED1_Turn(void)
{

	if(GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_1)==0)
	{
		GPIO_SetBits(GPIOA,GPIO_Pin_1);
	}else{
		GPIO_ResetBits(GPIOA,GPIO_Pin_1);
	}
}

void LED2_Turn(void)
{

	if(GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_2)==0)
	{
		GPIO_SetBits(GPIOA,GPIO_Pin_2);
	}else{
		GPIO_ResetBits(GPIOA,GPIO_Pin_2);
	}
}

void LED1_OFF(void)
{
  GPIO_SetBits(GPIOA,GPIO_Pin_1);
}

void LED2_ON(void)
{
	GPIO_ResetBits(GPIOA,GPIO_Pin_2);
}

void LED2_OFF(void)
{
  GPIO_SetBits(GPIOA,GPIO_Pin_2);
}

LED.h

c++
#ifndef __LED_H
#define __LED_H

void LED_Init(void);
void LED1_ON(void);
void LED1_OFF(void);
void LED2_ON(void);
void LED2_OFF(void);
void LED2_Turn(void);
void LED1_Turn(void);

#endif

main.c

c++
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"

uint8_t KeyNum;


int main(void)
{
	LED_Init();
	Key_Init();
	
	while(1)
	{
		KeyNum=Key_GetNum();
		if(KeyNum==1)
		{
			LED1_Turn();
		}
		if(KeyNum==2)
		{
			LED2_Turn();
		}
	}
	
}

光敏传感器控制蜂鸣器

接线图

3-5 光敏传感器控制蜂鸣器

实物图

image-20240622162256259

代码

image-20240622162324127

Buzzer.c

c++
#include "stm32f10x.h" 


void Buzzer_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); // 设置函数的时钟
	GPIO_InitTypeDef GPIO_InitStructure;
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; // 设置GPIO口模式,
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_12; 
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
}

void Buzzer_ON(void)
{
	GPIO_ResetBits(GPIOB,GPIO_Pin_12);
}

void Buzzer_OFF(void)
{
  GPIO_SetBits(GPIOB,GPIO_Pin_12);
}

void Buzzer_Turn(void)
{

	if(GPIO_ReadOutputDataBit(GPIOB,GPIO_Pin_12)==0)
	{
		GPIO_SetBits(GPIOB,GPIO_Pin_12);
	}else{
		GPIO_ResetBits(GPIOB,GPIO_Pin_12);
	}
}

Buzzer.h

c++
#ifndef __BUZZER_H
#define __BUZZER_H

void Buzzer_Init(void);
void Buzzer_ON(void);
void Buzzer_OFF(void);
void Buzzer_Turn(void);



#endif

LightSensor.c

c++
#include "stm32f10x.h" 


void LightSensor_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); // 设置函数的时钟
	GPIO_InitTypeDef GPIO_InitStructure;
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; // 设置GPIO口模式,
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_13; 
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
}


uint8_t LightSensor_Get(void)
{
	return GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_13);
}

LightSensor.h

c++
#ifndef __LIGHTSENSOR_H
#define __LIGHTSENSOR_H


void LightSensor_Init(void);
uint8_t LightSensor_Get(void);



#endif

main.c

c++
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "Buzzer.h"
#include "LightSensor.h"



int main(void)
{
	Buzzer_Init();
	LightSensor_Init();
	
	while(1)
	{
		if(LightSensor_Get()==1)
		{
			Buzzer_ON();
		}else{
			Buzzer_OFF();
		}
	}
	
}

4. OLED

4.1 简介

OLED(Organic Light Emitting Diode) 有机发光二极管 OLED显示屏:性能优异的新型显示屏,具有功耗低、相应速度快、 宽视角、轻薄柔韧等特点 0.96寸OLED模块:小巧玲珑、占用接口少、简单易用,是电子设计 中非常常见的显示屏模块 供电:3~5.5V,通信协议:I2C/SPI,分辨率:128x64

调试方式

  • 串口调试:通过串口通信,将调试信息发送到电脑端,电脑使用串口助手显示调试信息
  • 显示屏调试:直接将显示屏连接到单片机,将调试信息打印在显示屏上
  • Keil调试模式:借助Keil软件的调试模式,可使用单步运行、设置断点、查看寄存器及变量等功能

4.2 OLED显示屏幕

接线图

4-1 OLED显示屏

实物图

image-20240622213035797

代码

复制这3个文件

image-20240622165052677

然后添加到驱动文件目录

image-20240622165025595

image-20240622165125477

OLED.c

c++
#include "stm32f10x.h"
#include "OLED_Font.h"

/*引脚配置*/
#define OLED_W_SCL(x)		GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x))
#define OLED_W_SDA(x)		GPIO_WriteBit(GPIOB, GPIO_Pin_9, (BitAction)(x))

/*引脚初始化*/
void OLED_I2C_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
 	GPIO_Init(GPIOB, &GPIO_InitStructure);
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
 	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	OLED_W_SCL(1);
	OLED_W_SDA(1);
}

/**
  * @brief  I2C开始
  * @param
  * @retval
  */
void OLED_I2C_Start(void)
{
	OLED_W_SDA(1);
	OLED_W_SCL(1);
	OLED_W_SDA(0);
	OLED_W_SCL(0);
}

/**
  * @brief  I2C停止
  * @param
  * @retval
  */
void OLED_I2C_Stop(void)
{
	OLED_W_SDA(0);
	OLED_W_SCL(1);
	OLED_W_SDA(1);
}

/**
  * @brief  I2C发送一个字节
  * @param  Byte 要发送的一个字节
  * @retval
  */
void OLED_I2C_SendByte(uint8_t Byte)
{
	uint8_t i;
	for (i = 0; i < 8; i++)
	{
		OLED_W_SDA(Byte & (0x80 >> i));
		OLED_W_SCL(1);
		OLED_W_SCL(0);
	}
	OLED_W_SCL(1);	//额外的一个时钟,不处理应答信号
	OLED_W_SCL(0);
}

/**
  * @brief  OLED写命令
  * @param  Command 要写入的命令
  * @retval
  */
void OLED_WriteCommand(uint8_t Command)
{
	OLED_I2C_Start();
	OLED_I2C_SendByte(0x78);		//从机地址
	OLED_I2C_SendByte(0x00);		//写命令
	OLED_I2C_SendByte(Command); 
	OLED_I2C_Stop();
}

/**
  * @brief  OLED写数据
  * @param  Data 要写入的数据
  * @retval
  */
void OLED_WriteData(uint8_t Data)
{
	OLED_I2C_Start();
	OLED_I2C_SendByte(0x78);		//从机地址
	OLED_I2C_SendByte(0x40);		//写数据
	OLED_I2C_SendByte(Data);
	OLED_I2C_Stop();
}

/**
  * @brief  OLED设置光标位置
  * @param  Y 以左上角为原点,向下方向的坐标,范围:0~7
  * @param  X 以左上角为原点,向右方向的坐标,范围:0~127
  * @retval
  */
void OLED_SetCursor(uint8_t Y, uint8_t X)
{
	OLED_WriteCommand(0xB0 | Y);					//设置Y位置
	OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4));	//设置X位置高4位
	OLED_WriteCommand(0x00 | (X & 0x0F));			//设置X位置低4位
}

/**
  * @brief  OLED清屏
  * @param
  * @retval
  */
void OLED_Clear(void)
{  
	uint8_t i, j;
	for (j = 0; j < 8; j++)
	{
		OLED_SetCursor(j, 0);
		for(i = 0; i < 128; i++)
		{
			OLED_WriteData(0x00);
		}
	}
}

/**
  * @brief  OLED显示一个字符
  * @param  Line 行位置,范围:1~4
  * @param  Column 列位置,范围:1~16
  * @param  Char 要显示的一个字符,范围:ASCII可见字符
  * @retval
  */
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{      	
	uint8_t i;
	OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8);		//设置光标位置在上半部分
	for (i = 0; i < 8; i++)
	{
		OLED_WriteData(OLED_F8x16[Char - ' '][i]);			//显示上半部分内容
	}
	OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8);	//设置光标位置在下半部分
	for (i = 0; i < 8; i++)
	{
		OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]);		//显示下半部分内容
	}
}

/**
  * @brief  OLED显示字符串
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的字符串,范围:ASCII可见字符
  * @retval
  */
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
{
	uint8_t i;
	for (i = 0; String[i] != '\0'; i++)
	{
		OLED_ShowChar(Line, Column + i, String[i]);
	}
}

/**
  * @brief  OLED次方函数
  * @retval 返回值等于X的Y次方
  */
uint32_t OLED_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;
	while (Y--)
	{
		Result *= X;
	}
	return Result;
}

/**
  * @brief  OLED显示数字(十进制,正数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~4294967295
  * @param  Length 要显示数字的长度,范围:1~10
  * @retval
  */
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i++)							
	{
		OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0');
	}
}

/**
  * @brief  OLED显示数字(十进制,带符号数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:-2147483648~2147483647
  * @param  Length 要显示数字的长度,范围:1~10
  * @retval
  */
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
{
	uint8_t i;
	uint32_t Number1;
	if (Number >= 0)
	{
		OLED_ShowChar(Line, Column, '+');
		Number1 = Number;
	}
	else
	{
		OLED_ShowChar(Line, Column, '-');
		Number1 = -Number;
	}
	for (i = 0; i < Length; i++)							
	{
		OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0');
	}
}

/**
  * @brief  OLED显示数字(十六进制,正数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~0xFFFFFFFF
  * @param  Length 要显示数字的长度,范围:1~8
  * @retval
  */
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i, SingleNumber;
	for (i = 0; i < Length; i++)							
	{
		SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;
		if (SingleNumber < 10)
		{
			OLED_ShowChar(Line, Column + i, SingleNumber + '0');
		}
		else
		{
			OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A');
		}
	}
}

/**
  * @brief  OLED显示数字(二进制,正数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
  * @param  Length 要显示数字的长度,范围:1~16
  * @retval
  */
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i++)							
	{
		OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length - i - 1) % 2 + '0');
	}
}

/**
  * @brief  OLED初始化
  * @param
  * @retval
  */
void OLED_Init(void)
{
	uint32_t i, j;
	
	for (i = 0; i < 1000; i++)			//上电延时
	{
		for (j = 0; j < 1000; j++);
	}
	
	OLED_I2C_Init();			//端口初始化
	
	OLED_WriteCommand(0xAE);	//关闭显示
	
	OLED_WriteCommand(0xD5);	//设置显示时钟分频比/振荡器频率
	OLED_WriteCommand(0x80);
	
	OLED_WriteCommand(0xA8);	//设置多路复用率
	OLED_WriteCommand(0x3F);
	
	OLED_WriteCommand(0xD3);	//设置显示偏移
	OLED_WriteCommand(0x00);
	
	OLED_WriteCommand(0x40);	//设置显示开始行
	
	OLED_WriteCommand(0xA1);	//设置左右方向,0xA1正常 0xA0左右反置
	
	OLED_WriteCommand(0xC8);	//设置上下方向,0xC8正常 0xC0上下反置

	OLED_WriteCommand(0xDA);	//设置COM引脚硬件配置
	OLED_WriteCommand(0x12);
	
	OLED_WriteCommand(0x81);	//设置对比度控制
	OLED_WriteCommand(0xCF);

	OLED_WriteCommand(0xD9);	//设置预充电周期
	OLED_WriteCommand(0xF1);

	OLED_WriteCommand(0xDB);	//设置VCOMH取消选择级别
	OLED_WriteCommand(0x30);

	OLED_WriteCommand(0xA4);	//设置整个显示打开/关闭

	OLED_WriteCommand(0xA6);	//设置正常/倒转显示

	OLED_WriteCommand(0x8D);	//设置充电泵
	OLED_WriteCommand(0x14);

	OLED_WriteCommand(0xAF);	//开启显示
		
	OLED_Clear();				//OLED清屏
}

OLED.h

c++
#ifndef __OLED_H
#define __OLED_H

void OLED_Init(void);
void OLED_Clear(void);
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char);
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String);
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length);
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);

#endif

OLED_Font.h

c++
#ifndef __OLED_FONT_H
#define __OLED_FONT_H

/*OLED字模库,宽8像素,高16像素*/
const uint8_t OLED_F8x16[][16]=
{
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//  0
	
	0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x33,0x30,0x00,0x00,0x00,//! 1
	
	0x00,0x10,0x0C,0x06,0x10,0x0C,0x06,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//" 2
	
	0x40,0xC0,0x78,0x40,0xC0,0x78,0x40,0x00,
	0x04,0x3F,0x04,0x04,0x3F,0x04,0x04,0x00,//# 3
	
	0x00,0x70,0x88,0xFC,0x08,0x30,0x00,0x00,
	0x00,0x18,0x20,0xFF,0x21,0x1E,0x00,0x00,//$ 4
	
	0xF0,0x08,0xF0,0x00,0xE0,0x18,0x00,0x00,
	0x00,0x21,0x1C,0x03,0x1E,0x21,0x1E,0x00,//% 5
	
	0x00,0xF0,0x08,0x88,0x70,0x00,0x00,0x00,
	0x1E,0x21,0x23,0x24,0x19,0x27,0x21,0x10,//& 6
	
	0x10,0x16,0x0E,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//' 7
	
	0x00,0x00,0x00,0xE0,0x18,0x04,0x02,0x00,
	0x00,0x00,0x00,0x07,0x18,0x20,0x40,0x00,//( 8
	
	0x00,0x02,0x04,0x18,0xE0,0x00,0x00,0x00,
	0x00,0x40,0x20,0x18,0x07,0x00,0x00,0x00,//) 9
	
	0x40,0x40,0x80,0xF0,0x80,0x40,0x40,0x00,
	0x02,0x02,0x01,0x0F,0x01,0x02,0x02,0x00,//* 10
	
	0x00,0x00,0x00,0xF0,0x00,0x00,0x00,0x00,
	0x01,0x01,0x01,0x1F,0x01,0x01,0x01,0x00,//+ 11
	
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x80,0xB0,0x70,0x00,0x00,0x00,0x00,0x00,//, 12
	
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,//- 13
	
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x30,0x30,0x00,0x00,0x00,0x00,0x00,//. 14
	
	0x00,0x00,0x00,0x00,0x80,0x60,0x18,0x04,
	0x00,0x60,0x18,0x06,0x01,0x00,0x00,0x00,/// 15
	
	0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,
	0x00,0x0F,0x10,0x20,0x20,0x10,0x0F,0x00,//0 16
	
	0x00,0x10,0x10,0xF8,0x00,0x00,0x00,0x00,
	0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//1 17
	
	0x00,0x70,0x08,0x08,0x08,0x88,0x70,0x00,
	0x00,0x30,0x28,0x24,0x22,0x21,0x30,0x00,//2 18
	
	0x00,0x30,0x08,0x88,0x88,0x48,0x30,0x00,
	0x00,0x18,0x20,0x20,0x20,0x11,0x0E,0x00,//3 19
	
	0x00,0x00,0xC0,0x20,0x10,0xF8,0x00,0x00,
	0x00,0x07,0x04,0x24,0x24,0x3F,0x24,0x00,//4 20
	
	0x00,0xF8,0x08,0x88,0x88,0x08,0x08,0x00,
	0x00,0x19,0x21,0x20,0x20,0x11,0x0E,0x00,//5 21
	
	0x00,0xE0,0x10,0x88,0x88,0x18,0x00,0x00,
	0x00,0x0F,0x11,0x20,0x20,0x11,0x0E,0x00,//6 22
	
	0x00,0x38,0x08,0x08,0xC8,0x38,0x08,0x00,
	0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x00,//7 23
	
	0x00,0x70,0x88,0x08,0x08,0x88,0x70,0x00,
	0x00,0x1C,0x22,0x21,0x21,0x22,0x1C,0x00,//8 24
	
	0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,
	0x00,0x00,0x31,0x22,0x22,0x11,0x0F,0x00,//9 25
	
	0x00,0x00,0x00,0xC0,0xC0,0x00,0x00,0x00,
	0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,//: 26
	
	0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,
	0x00,0x00,0x80,0x60,0x00,0x00,0x00,0x00,//; 27
	
	0x00,0x00,0x80,0x40,0x20,0x10,0x08,0x00,
	0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x00,//< 28
	
	0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00,
	0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x00,//= 29
	
	0x00,0x08,0x10,0x20,0x40,0x80,0x00,0x00,
	0x00,0x20,0x10,0x08,0x04,0x02,0x01,0x00,//> 30
	
	0x00,0x70,0x48,0x08,0x08,0x08,0xF0,0x00,
	0x00,0x00,0x00,0x30,0x36,0x01,0x00,0x00,//? 31
	
	0xC0,0x30,0xC8,0x28,0xE8,0x10,0xE0,0x00,
	0x07,0x18,0x27,0x24,0x23,0x14,0x0B,0x00,//@ 32
	
	0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,
	0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20,//A 33
	
	0x08,0xF8,0x88,0x88,0x88,0x70,0x00,0x00,
	0x20,0x3F,0x20,0x20,0x20,0x11,0x0E,0x00,//B 34
	
	0xC0,0x30,0x08,0x08,0x08,0x08,0x38,0x00,
	0x07,0x18,0x20,0x20,0x20,0x10,0x08,0x00,//C 35
	
	0x08,0xF8,0x08,0x08,0x08,0x10,0xE0,0x00,
	0x20,0x3F,0x20,0x20,0x20,0x10,0x0F,0x00,//D 36
	
	0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,
	0x20,0x3F,0x20,0x20,0x23,0x20,0x18,0x00,//E 37
	
	0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,
	0x20,0x3F,0x20,0x00,0x03,0x00,0x00,0x00,//F 38
	
	0xC0,0x30,0x08,0x08,0x08,0x38,0x00,0x00,
	0x07,0x18,0x20,0x20,0x22,0x1E,0x02,0x00,//G 39
	
	0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,
	0x20,0x3F,0x21,0x01,0x01,0x21,0x3F,0x20,//H 40
	
	0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0x00,
	0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//I 41
	
	0x00,0x00,0x08,0x08,0xF8,0x08,0x08,0x00,
	0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,0x00,//J 42
	
	0x08,0xF8,0x88,0xC0,0x28,0x18,0x08,0x00,
	0x20,0x3F,0x20,0x01,0x26,0x38,0x20,0x00,//K 43
	
	0x08,0xF8,0x08,0x00,0x00,0x00,0x00,0x00,
	0x20,0x3F,0x20,0x20,0x20,0x20,0x30,0x00,//L 44
	
	0x08,0xF8,0xF8,0x00,0xF8,0xF8,0x08,0x00,
	0x20,0x3F,0x00,0x3F,0x00,0x3F,0x20,0x00,//M 45
	
	0x08,0xF8,0x30,0xC0,0x00,0x08,0xF8,0x08,
	0x20,0x3F,0x20,0x00,0x07,0x18,0x3F,0x00,//N 46
	
	0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,
	0x0F,0x10,0x20,0x20,0x20,0x10,0x0F,0x00,//O 47
	
	0x08,0xF8,0x08,0x08,0x08,0x08,0xF0,0x00,
	0x20,0x3F,0x21,0x01,0x01,0x01,0x00,0x00,//P 48
	
	0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,
	0x0F,0x18,0x24,0x24,0x38,0x50,0x4F,0x00,//Q 49
	
	0x08,0xF8,0x88,0x88,0x88,0x88,0x70,0x00,
	0x20,0x3F,0x20,0x00,0x03,0x0C,0x30,0x20,//R 50
	
	0x00,0x70,0x88,0x08,0x08,0x08,0x38,0x00,
	0x00,0x38,0x20,0x21,0x21,0x22,0x1C,0x00,//S 51
	
	0x18,0x08,0x08,0xF8,0x08,0x08,0x18,0x00,
	0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,//T 52
	
	0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,
	0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,//U 53
	
	0x08,0x78,0x88,0x00,0x00,0xC8,0x38,0x08,
	0x00,0x00,0x07,0x38,0x0E,0x01,0x00,0x00,//V 54
	
	0xF8,0x08,0x00,0xF8,0x00,0x08,0xF8,0x00,
	0x03,0x3C,0x07,0x00,0x07,0x3C,0x03,0x00,//W 55
	
	0x08,0x18,0x68,0x80,0x80,0x68,0x18,0x08,
	0x20,0x30,0x2C,0x03,0x03,0x2C,0x30,0x20,//X 56
	
	0x08,0x38,0xC8,0x00,0xC8,0x38,0x08,0x00,
	0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,//Y 57
	
	0x10,0x08,0x08,0x08,0xC8,0x38,0x08,0x00,
	0x20,0x38,0x26,0x21,0x20,0x20,0x18,0x00,//Z 58
	
	0x00,0x00,0x00,0xFE,0x02,0x02,0x02,0x00,
	0x00,0x00,0x00,0x7F,0x40,0x40,0x40,0x00,//[ 59
	
	0x00,0x0C,0x30,0xC0,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x01,0x06,0x38,0xC0,0x00,//\ 60
	
	0x00,0x02,0x02,0x02,0xFE,0x00,0x00,0x00,
	0x00,0x40,0x40,0x40,0x7F,0x00,0x00,0x00,//] 61
	
	0x00,0x00,0x04,0x02,0x02,0x02,0x04,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//^ 62
	
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,//_ 63
	
	0x00,0x02,0x02,0x04,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//` 64
	
	0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,
	0x00,0x19,0x24,0x22,0x22,0x22,0x3F,0x20,//a 65
	
	0x08,0xF8,0x00,0x80,0x80,0x00,0x00,0x00,
	0x00,0x3F,0x11,0x20,0x20,0x11,0x0E,0x00,//b 66
	
	0x00,0x00,0x00,0x80,0x80,0x80,0x00,0x00,
	0x00,0x0E,0x11,0x20,0x20,0x20,0x11,0x00,//c 67
	
	0x00,0x00,0x00,0x80,0x80,0x88,0xF8,0x00,
	0x00,0x0E,0x11,0x20,0x20,0x10,0x3F,0x20,//d 68
	
	0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,
	0x00,0x1F,0x22,0x22,0x22,0x22,0x13,0x00,//e 69
	
	0x00,0x80,0x80,0xF0,0x88,0x88,0x88,0x18,
	0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//f 70
	
	0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,
	0x00,0x6B,0x94,0x94,0x94,0x93,0x60,0x00,//g 71
	
	0x08,0xF8,0x00,0x80,0x80,0x80,0x00,0x00,
	0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,//h 72
	
	0x00,0x80,0x98,0x98,0x00,0x00,0x00,0x00,
	0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//i 73
	
	0x00,0x00,0x00,0x80,0x98,0x98,0x00,0x00,
	0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,//j 74
	
	0x08,0xF8,0x00,0x00,0x80,0x80,0x80,0x00,
	0x20,0x3F,0x24,0x02,0x2D,0x30,0x20,0x00,//k 75
	
	0x00,0x08,0x08,0xF8,0x00,0x00,0x00,0x00,
	0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//l 76
	
	0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x00,
	0x20,0x3F,0x20,0x00,0x3F,0x20,0x00,0x3F,//m 77
	
	0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,
	0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,//n 78
	
	0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,
	0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,//o 79
	
	0x80,0x80,0x00,0x80,0x80,0x00,0x00,0x00,
	0x80,0xFF,0xA1,0x20,0x20,0x11,0x0E,0x00,//p 80
	
	0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x00,
	0x00,0x0E,0x11,0x20,0x20,0xA0,0xFF,0x80,//q 81
	
	0x80,0x80,0x80,0x00,0x80,0x80,0x80,0x00,
	0x20,0x20,0x3F,0x21,0x20,0x00,0x01,0x00,//r 82
	
	0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,
	0x00,0x33,0x24,0x24,0x24,0x24,0x19,0x00,//s 83
	
	0x00,0x80,0x80,0xE0,0x80,0x80,0x00,0x00,
	0x00,0x00,0x00,0x1F,0x20,0x20,0x00,0x00,//t 84
	
	0x80,0x80,0x00,0x00,0x00,0x80,0x80,0x00,
	0x00,0x1F,0x20,0x20,0x20,0x10,0x3F,0x20,//u 85
	
	0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,
	0x00,0x01,0x0E,0x30,0x08,0x06,0x01,0x00,//v 86
	
	0x80,0x80,0x00,0x80,0x00,0x80,0x80,0x80,
	0x0F,0x30,0x0C,0x03,0x0C,0x30,0x0F,0x00,//w 87
	
	0x00,0x80,0x80,0x00,0x80,0x80,0x80,0x00,
	0x00,0x20,0x31,0x2E,0x0E,0x31,0x20,0x00,//x 88
	
	0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,
	0x80,0x81,0x8E,0x70,0x18,0x06,0x01,0x00,//y 89
	
	0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x00,
	0x00,0x21,0x30,0x2C,0x22,0x21,0x30,0x00,//z 90
	
	0x00,0x00,0x00,0x00,0x80,0x7C,0x02,0x02,
	0x00,0x00,0x00,0x00,0x00,0x3F,0x40,0x40,//{ 91
	
	0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,//| 92
	
	0x00,0x02,0x02,0x7C,0x80,0x00,0x00,0x00,
	0x00,0x40,0x40,0x3F,0x00,0x00,0x00,0x00,//} 93
	
	0x00,0x06,0x01,0x01,0x02,0x02,0x04,0x04,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//~ 94
};

#endif

main.c

c++
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"


int main(void)
{
	OLED_Init();
	OLED_ShowChar(1,1,'A');
	OLED_ShowString(1,3,"HelloWorld!");
	OLED_ShowNum(2,1,12345,6);
	OLED_ShowSignedNum(2,7,-66,2);
	OLED_ShowHexNum(3,1,0xAA55,4);
	OLED_ShowBinNum(4,1,0xAA55,16);
	
	while(1)
	{
		
	
	
	}
}

5. EXIT外部中断

5.1 中断系统

本节讲解中断系统

什么是中断

在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行

中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源

中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回。

中断执行流程图

image-20240622224628904

STM32中断

68个可屏蔽中断通道,包含EXTI、TIM、ADC,USART、SPI、I2C、RTC等多个外设

使用NVIC统一管理中断,每个中断通道都拥有16个可编程的优先等级,可对优先级 进行分组,进一步设置抢占优先级和响应优先级

什么是NVIC?

image-20240622230442902

NVIC是内核外设,是CPU的小助手

NVIC优先级分组

的中断优先级由优先级寄存器的4位(0~15)决定,这4位可以进行切分,分为高n位的抢占优先级和低4-n位的响应优先级

抢占优先级高的可以中断嵌套,响应优先级高的可以优先排队,抢占优先级和响应优先级均相同的按中断号排队

image-20240622230651906

EXTI外部中断

EXTI(Extern Interrupt)可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序

支持的触发方式:上升沿/下隆沿/双边沿/软件触发

支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断

通道数:16个GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒

触发响应方式:中断响应/事件响应

image-20240622231135088

什么样的设备,适合使用外部中断呢??

对于STM32而言,想要获取的信号是外部驱动的很快的突发信号。

旋转编码器:用来测量位置、速度或旋转方向的装置,当其旋转轴旋转时,其输出端可以输出与旋转速度和方向对应的方波信号,读取方波信号的频率和相位信息即可得知旋转轴的速度和方向

类型:机械触点式/霍尔传感器式/光栅式

5.2 对射式红外传感器计次

接线图

5-1 对射式红外传感器计次

实线图

image-20240623221143434

代码

image-20240623220920776

CountSensor.c

c++
#include "stm32f10x.h" 


uint16_t Num;

void CountSensor_Init(void)
{
	
	// 第一步,配置RCC,把EXTI设计到的外设时钟都打开
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); // 设置函数的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); 
	
	// 第二步,配置GPIO,选择我们的端口为输入模式
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; // 设置GPIO口模式,
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_14; 
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
	// 第三步,配置AFIO, 选择GPIO,连接到后面的EXTI
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);
	
	// 第四步,配置EXTI,选择边沿触发方式,比如上升沿,下降沿,或者双边沿
	EXTI_InitTypeDef EXTI_InitStructure;
	EXTI_InitStructure.EXTI_Line=EXTI_Line14;
	EXTI_InitStructure.EXTI_LineCmd=ENABLE; // 开始中断
	EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt; // 指定外部中断线的模式。第一种是中断模式,第二种是事件模式
	EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling; //选择边沿触发方式,比如上升沿,下降沿,或者双边沿
	EXTI_Init(&EXTI_InitStructure); // 因为上面是GPIO_Mode_IPU设置为高电平,所以触发中断是下降
	
	// 第五步,配置NVIC,给中断选择一个合适的优先级
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel=EXTI15_10_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
	
	NVIC_Init(&NVIC_InitStructure);


}

uint16_t CountSensor_Get(void)
{
	return Num;
}

void EXTI15_10_IRQHandler(void)
{
	
	if(EXTI_GetITStatus(EXTI_Line14)==SET)
	{
		Num++;
		EXTI_ClearITPendingBit(EXTI_Line14); // 清除中断标志位
	}
	

}

CountSensor.h

c++
#ifndef __COUNT_SENSOR_H
#define __COUNT_SENSOR_H


void CountSensor_Init(void);
uint16_t CountSensor_Get(void);


#endif

main.c

c++
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "CountSensor.h"

int main(void)
{
	OLED_Init();
  CountSensor_Init();
	
	OLED_ShowString(1,1,"Count:");
	
	while(1)
	{
		OLED_ShowNum(1,7,CountSensor_Get(),5);
	
	}
}

5.3 旋转编码器计次

接线图

5-2 旋转编码器计次

实线图

image-20240623224547105

代码

image-20240623224413093

Encoder.c

c++
#include "stm32f10x.h"  

int16_t Encoder_Num;

void Encoder_Init(void)
{
	// 第一步,配置RCC,把EXTI设计到的外设时钟都打开
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); // 设置函数的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); 
	
	// 第二步,配置GPIO,选择我们的端口为输入模式
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; // 设置GPIO口模式,
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0 | GPIO_Pin_1; 
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
	// 第三步,配置AFIO, 选择GPIO,连接到后面的EXTI
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource0);
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1);
	
	// 第四步,配置EXTI,选择边沿触发方式,比如上升沿,下降沿,或者双边沿
	EXTI_InitTypeDef EXTI_InitStructure;
	EXTI_InitStructure.EXTI_Line=EXTI_Line0 | EXTI_Line1;
	EXTI_InitStructure.EXTI_LineCmd=ENABLE; // 开始中断
	EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt; // 指定外部中断线的模式。第一种是中断模式,第二种是事件模式
	EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling; //选择边沿触发方式,比如上升沿,下降沿,或者双边沿
	EXTI_Init(&EXTI_InitStructure); // 因为上面是GPIO_Mode_IPU设置为高电平,所以触发中断是下降
	
	// 第五步,配置NVIC,给中断选择一个合适的优先级
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel=EXTI0_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
	
	NVIC_Init(&NVIC_InitStructure);
	
	NVIC_InitStructure.NVIC_IRQChannel=EXTI1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=2;
	
	NVIC_Init(&NVIC_InitStructure);
	

}

int16_t Encoder_GetNum(void)
{
	int16_t Temp;
	Temp=Encoder_Num;
	Encoder_Num=0;
	return Temp;
}

void EXTI0_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line0)==SET)
	{
		if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0)
		{
			Encoder_Num--;
		}
		EXTI_ClearITPendingBit(EXTI_Line0); // 清除中断标志位
	}

}

void EXTI1_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line1)==SET)
	{
		if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0)==0)
		{
			Encoder_Num++;
		}
		EXTI_ClearITPendingBit(EXTI_Line1); // 清除中断标志位
	}
	

}

Encoder.h

c++
#ifndef __ENCODER_H
#define __ENCODER_H


void Encoder_Init(void);
int16_t Encoder_GetNum(void);


#endif

main.c

c++
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Encoder.h"

int16_t Num;
int main(void)
{
	OLED_Init();
  Encoder_Init();
	
	OLED_ShowString(1,1,"Num:");
	
	while(1)
	{
		Num+=Encoder_GetNum();
		OLED_ShowSignedNum(1,5,Num,5);
	
	}
}

6. TIM定时中断

参考资料

【STM32】STM32系列教程汇总(更新...)_stm32教程-CSDN博客

STM32开发笔记—外设系列_二土电子的博客-CSDN博客

https://blog.csdn.net/qq_63090569/article/details/127939523

STM32f103c8t6小白学习笔记(一)从点灯开始之USB转TTL_usb转ttl怎么接线-CSDN博客

STM32学习笔记(一)从点灯开始之ST-LINK V2_stlink v2接线-CSDN博客

【正点原子STM32】GPIO(简介、IO端口基本结构、GPIO的八种模式、GPIO寄存器、通用外设驱动模型、GPIO配置步骤、编程实战)_gpio 驱动能力-CSDN博客