0%

TIM简介

•TIM(Timer)定时器
•定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
•16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时
•不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
•根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型

定时器类型

类型编号总线功能
高级定时器TIM1、TIM8APB2拥有通用定时器全部功能,并额外具有重复计数器、死区生成、互补输出、刹车输入等功能
通用定时器TIM2、TIM3、TIM4、TIM5APB1拥有基本定时器全部功能,并额外具有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能
基本定时器TIM6、TIM7APB1拥有定时中断、主模式触发DAC的功能

•STM32F103C8T6定时器资源:TIM1、TIM2、TIM3、TIM4
不同的型号,定时器的数量一般是不同的。操作一个外设之前,要查询是否有这个外设。

基本定时器

基本定时器结构图
预分频器、自动重装载寄存器、CNT计数器共同构成了基本的计数计时单元,这块电路叫时基单元。
预分频器之前,连接的是基准计数时钟的输入,来到控制器。
由于基本定时器只能选择内部时钟,所以可以认为直接连到内部时钟CK_INT,内部时钟来自RCC的TIMxCLK,这里的频率值一般是系统的主频72MHz。
因此通向时基单元的计数基准频率为72MHz

预分频器对72MHz的计数时钟进行分频,比如预分频器寄存器写0,就是不分频,或者是1分频。此时输入频率=输出频率=72MHz。
如果预分频器写1,那就是2分频,输出频率=输入频率/2=36MHz。
写2,为3分频,输出频率=输入频率/3=24MHz。
(预分频器的值和实际的分频系数差1,实际分频系数=预分频器+1,预分频器是16位的,最大值为65535,也就是65536分频)

计数器对分频后的计数时钟进行计数,计数时钟每有一个上升沿,计数器的值+1,最大值是65535。超过65535,变为0。

自动重装寄存器存的是写入的计数目标。
在运行时,计数值不断自增,自动重装值是固定的目标,当计数值和自动重装值相等时,代表时间到了。
会产生中断信号,并且清零计数器。

主模式触发DAC模式,可以让内部硬件在不受到程序控制的情况下自动运行(可以减轻CPU负担)。
在使用DAC时,会用DAC输出一段波形,每隔一段时间触发依次DAC,让它输出下一个电压点。(定时器的更新不需要通过中断来触发DAC转换,只需要把更新时间通过主模式映射到TRGO,再通过TRGO触发DAC。无需软件参与)

基本定时器计时,只能向上计数。

通用定时器

基本定时器
通用定时器框图中,中间的部分和基本定时器是一样的。

通用定时器和高级定时器除了向上计数,还支持向下计数和中央对其模式。
(向下计数模式,就是从重装值开始,向下自减,直到0,回到重装值并且申请中断)
(中央对其模式,就是从0开始,向上自增,计数到重装值,申请中断,然后再向下自减,减到0,再申请中断)
通常使用向上计数模式,其他模式使用少。

上面部分是内外时钟选择和主从触发模式的结构。
对于基本定时器,定时只能选择系统内部时钟,即系统频率72MHz。
通用定时器还可以选择外部时钟。
第一个外部时钟是来自TIMx_ETR引脚上的外部时钟,即在PA0上接一个外部方波时钟,然后配置内部的极性选择、边沿检测和预分频电路,在配置输入滤波电路,可以对外部时钟进行整形。
滤波后的信号,分为2路,一路去ETRF进入触发控制器,可以选择作为时基单元的时钟。这一路叫外部时钟模式2。另一路去TRGI,用作触发输入来使用,可以触发定时器的从模式,叫做外部时钟模式1。

ITR信号的时钟信号来自其他定时器,实现定时器级联。

TI1F_ED,连接的是输入捕获单元的CH1引脚,也就是从CH1引脚获得时钟,上升沿和下降沿都有效,这个时钟还能通过TI1FP1和TI2FP2来获得。

对于时钟,通常使用内部的72MHz时钟,如果要使用外部时钟,首选ETR引脚外部时钟模式2的输入。

右下角可以用于输出PWM波形,驱动电机,左侧是输入捕获电路,有4个通道,对应CH1到CH4引脚。可以用于测输入方波的频率。
中间的捕获/比较寄存器,是输入捕获和输出比较电路公用的,输入捕获和输出比较不能同时使用,因此寄存器共用,引脚共用。

高级定时器

高级定时器
增加重复次数计数器,可以实现每隔几个计数周期,才发生一次更新时间和更新中断。(相当于对输出的更新信号做一次分频)

定时中断基本结构

定时中断基本结构

预分频器时序

预分频器时序
•计数器计数频率:CK_CNT = CK_PSC / (PSC + 1)

计数器时序

计数器时序
•计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)= CK_PSC / (PSC + 1) / (ARR + 1)

计数器无预装时序

计数器无预装时序

计数器有预装时序

计数器有预装时序

对射式红外传感器计次

CountSensor.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include "stm32f10x.h"                  // Device header

uint16_t CountSensor_Count;

void CountSensor_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//开启AFIO外设
//EXTI和NVIC外设为默认开启,无需手动开启
//配置GPIO外设
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入,默认高电平
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//配置AFIO外设
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);
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);
//配置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 CountSensor_Count;
}

void EXTI15_10_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line14) == SET)
{
CountSensor_Count++;//触发中断,加1
EXTI_ClearITPendingBit(EXTI_Line14);//清除中断
}
}

mian.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "CountSensor.h"


int main(void)
{
OLED_Init();//初始化OLED
CountSensor_Init();//初始化传感器
OLED_ShowString(1,1,"Count:");

while(1)
{
OLED_ShowNum(1,7,CountSensor_Get(),5);//调用CountSensor_Get()函数,得到计次值
}
}

旋转编码器计次

Encoder.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include "stm32f10x.h"                  // Device header

int16_t Encoder_Count;

void Encoder_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//开启AFIO外设
//EXTI和NVIC外设为默认开启
//配置GPIO外设
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入,默认高电平
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//配置AFIO外设
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource0);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1);

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);
//配置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_Get(void)
{
int16_t Temp;
Temp = Encoder_Count;
Encoder_Count = 0;
return Temp;
}

void EXTI0_IRQHandler(void)//反转
{
if (EXTI_GetITStatus(EXTI_Line0) == SET)
{
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == 0)
{
Encoder_Count--;
}
EXTI_ClearITPendingBit(EXTI_Line0);
}
}

void EXTI1_IRQHandler(void)//正传
{
if (EXTI_GetITStatus(EXTI_Line1) == SET)
{
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0) == 0)
{
Encoder_Count++;
}
EXTI_ClearITPendingBit(EXTI_Line1);
}
}

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

•中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源。(紧急可以把优先级设高,不紧急可以设低)

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

STM32中断

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

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

NVIC基本结构

NVIC基本结构
用来统一分配中断优先级和管理中断。
NVIC根据每个中断的优先级。(类似叫号系统)

NVIC优先级分组

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

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

一般紧急:响应优先级类似插队(后来但是先处理)
更紧急:抢占优先级,中断现在处理的任务,先处理。

分组方式抢占优先级响应优先级
分组00位,取值为04位,取值为0~15
分组11位,取值为0~13位,取值为0~7
分组22位,取值为0~32位,取值为0~3
分组33位,取值为0~71位,取值为0~1
分组44位,取值为0~150位,取值为0

值越小,优先级越高。

EXTI简介

•EXTI(Extern Interrupt)外部中断

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

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

•支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断(比如PA0和PB0不能同时用,端口Pin一样的)

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

触发响应方式:中断响应/事件响应
中断响应:申请中断,让CPU执行中断函数。
事件响应:触发事件,外部中断信号不会通向CPU,而是通向其他外设,比如出发ADC转换、DMA等。
EXTI简介

AFIO复用IO口

•AFIO主要用于引脚复用功能的选择和重定义

•在STM32中,AFIO主要完成两个任务:复用功能引脚重映射、中断引脚选择
AFIO复用IO口

EXTI框图

EXTI框图

Keil 调试模式1
电机放大镜d按钮,进入调试模式。

Keil 调试模式2
红色框住的窗口,是C语言翻译成的汇编程序。
绿色窗口,是寄存器组和状态标志位信息。
粉色窗口,为C语言程序。

Keil 调试模式3
上图为程序运行控制栏。
依次为复位、全速运行、停止全速运行。
单步运行、跳过当前行单步运行、跳出当前函数单步运行、跳到光标指定位置单步运行。

Keil 调试模式4
这个按钮是符号窗口,在这里可以查看到所有寄存器的值。
Keil 调试模式5
想要看某个寄存器的值,可以右键,添加到观察栏。

Keil 调试模式6
这里可以看到所有外设信息,比如选择GPIOA,就可以看到GPIOA外设所有寄存器。
ODR寄存器是输出寄存器,ODR0,就是PA0的输出寄存器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#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,"Hello World!");
OLED_ShowNum(2,1,12345,5);
OLED_ShowSignedNum(2,7,-66,2);
OLED_ShowHexNum(3,1,0xAA55,4);
OLED_ShowBinNum(4,1,0xAA55,16);
//OLED_Clear();
}

为什么在OLED(I2C接口)驱动中,推挽输出模式(PP)能正常工作,而开漏输出模式(OD)不能,即使I2C协议标准要求开漏输出?

因为OLED模块内部可能没有上拉电阻,或者上拉电阻的阻值过大

1. I2C协议与开漏输出的标准配置

  • 理论上:I2C总线是一个多主多从、双向、半双工的串行总线。它通过两条线通信:

    • SDA(串行数据线):传输数据。

    • SCL(串行时钟线):提供时钟信号。

  • 开漏输出(Open-Drain)的原理

    • 在OD模式下,GPIO引脚内部的MOSFET只能将线路拉低到地(GND),或者高阻态释放

    • 无法主动输出高电平(VCC)

    • 线路上的高电平必须依靠外部上拉电阻将电压拉至VCC。

  • 为什么I2C标准要求开漏输出?

    • 防止总线冲突:如果多个设备同时向总线输出,一个输出高(推挽),一个输出低(开漏),会造成短路。开漏模式只能拉低,不能主动拉高,避免了电源间的直接冲突,实现了“线与”功能。

    • 电平兼容:不同设备的供电电压可能不同(如3.3V和5V)。只要上拉电阻接到合适的电压,开漏模式可以方便地进行电平转换。

    • 总线可以双向通信:SDA线需要被主机和从机共同驱动。开漏模式使得任何一方都可以安全地拉低线路。

2. 你的实际情况:为什么OD不工作,PP却工作?

  • OD模式不工作(无显示)

    • 当你的PB8/PB9配置为开漏输出时,单片机只能将SDA/SCL线拉低。

    • 当单片机释放总线(输出高电平)时,引脚处于高阻态。此时,线路上的电压完全依赖于外部上拉电阻

    • 如果你的OLED模块没有集成上拉电阻,或者电阻值非常大(例如>100kΩ),那么上拉能力极弱。 导致的结果是:

      1. 时钟线SCL无法上升到稳定的高电平:时钟信号失真,OLED无法正确识别时钟边沿。

      2. 数据线SDA的高电平建立缓慢:在高速通信下,电平还没升到逻辑“1”就被采样了,导致数据错误。

      3. 总体表现:I2C通信失败,初始化或数据传输都无法完成,屏幕自然没有任何显示。用逻辑分析仪或示波器看,会看到波形“爬坡”非常缓慢,幅值不足。

  • PP模式正常工作

    • 当配置为推挽输出时,GPIO内部有PMOS和NMOS两个MOSFET。

    • 输出高电平时,PMOS导通,直接连接到VDD(3.3V),主动驱动线路为高电平。

    • 输出低电平时,NMOS导通,将线路拉低到地。

    • 在这种情况下,即使总线上没有外部上拉电阻,单片机也能靠自身强力输出稳定的高电平和低电平,保证了通信波形的完整性,因此OLED可以正常初始化和显示。

结论:根本原因是你使用的硬件电路(单片机板+OLED模块)组合缺少了I2C总线必需的外部上拉电阻

解决方案(从优到次)

  1. 最佳实践 - 添加上拉电阻并改回OD模式

    • 在 PB8(SCL)和 PB9(SDA) 各自连接到 VCC(3.3V) 的线上,添加一个4.7kΩ - 10kΩ的电阻

    • 将GPIO模式改回 GPIO_MODE_OUTPUT_OD (开漏输出)。

    • 这是最标准、最安全、最兼容I2C协议的做法。它允许总线被多个设备驱动,也符合电平规范。

  2. 临时方案 - 继续使用PP模式(不推荐长期使用)

    • 如果你只是单个主机驱动单个OLED从机,并且没有其他设备挂载到这两根线上,推挽模式在短期内可以“凑合用”

    • 严重缺点

      • 破坏了I2C的“线与”特性,不能再挂载其他I2C设备,否则有短路风险。

      • 如果OLED模块内部有弱上拉,可能会形成电流冲突,长期可能损坏器件。

      • 代码移植到标准硬件上可能反而出问题。

OLED

函数作用
OLED_Init();初始化
OLED_Clear();清屏
OLED_ShowChar(1, 1, ‘A’);显示一个字符
OLED_ShowString(1, 3, “HelloWorld!”);显示字符串
OLED_ShowNum(2, 1, 12345, 5);显示十进制数字
OLED_ShowSignedNum(2, 7, -66, 2);显示有符号十进制数字
OLED_ShowHexNum(3, 1, 0xAA55, 4);显示十六进制数字
OLED_ShowBinNum(4, 1, 0xAA55, 16);显示二进制数字

OLED

1
2
3
4
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//用来读取输入数据寄存器某一个位的输入值
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);//读取整个输入数据寄存器
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//用来读取输出数据寄存器某一个位的输入值
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);//读取整个输出数据寄存器

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "stm32f10x.h"                  // Device header
#include "delay.h"
#include "LED.h"
#include "key.h"

uint8_t KeyNum;//这里定义的是全局变量,和key.c中定义的局部变量是不一样的
int main(void)
{
LED_Init();
KEY_Init();
while(1)
{
KeyNum = Key_GetNum();
if(KeyNum == 1)
{
LED1_Turn();
}
else if(KeyNum == 2)
{
LED2_Turn();
}
}
}

LED.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include "stm32f10x.h"                  // Device header

void LED_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA,GPIO_Pin_1 | GPIO_Pin_2);
}

void LED1_ON(void)
{
GPIO_ResetBits(GPIOA,GPIO_Pin_1);
}
void LED1_OFF(void)
{
GPIO_SetBits(GPIOA,GPIO_Pin_1);
}
void LED1_Turn(void)
{
if(GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_1) == 0)//读取PA1的值,类似LD=,做判断用
{
GPIO_SetBits(GPIOA,GPIO_Pin_1);
}
else
{
GPIO_ResetBits(GPIOA,GPIO_Pin_1);
}
}
void LED2_ON(void)
{
GPIO_ResetBits(GPIOA,GPIO_Pin_2);
}
void LED2_OFF(void)
{
GPIO_SetBits(GPIOA,GPIO_Pin_2);
}
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);
}
}

key.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include "stm32f10x.h"                  // Device header
#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_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);//延时20ms因为会抖动
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;
}

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

传感器模块:传感器元件(光敏电阻/热 敏电阻/红外接收管等)的电阻会随外界模拟量的变化而变化,通过与定值电阻分压即可得到模拟电压输出,再通过电压比较器进行二值化即可得到数字电压输出。
GPIO输入
一般按键使用下接的方式,和LED的接法类似,是习惯和规范。

LED闪烁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include "stm32f10x.h"                  // Device header
#include "delay.h"
int main(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
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);
while(1)
{
//GPIO_ResetBits(GPIOA,GPIO_Pin_0);
//Delay_ms(500);
//GPIO_SetBits(GPIOA,GPIO_Pin_0);
//Delay_ms(500);
//GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_SET);
//Delay_ms(500);
//GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_RESET);
//Delay_ms(500);
GPIO_WriteBit(GPIOA,GPIO_Pin_0,(BitAction)0);
Delay_ms(500);
GPIO_WriteBit(GPIOA,GPIO_Pin_0,(BitAction)1);
Delay_ms(500);
}
}

(BitAction)0为强制类型转换,把1和0类型转换为BitAction的枚举类型。
LED无论是长脚插在PA0口(高电平),还是短脚插在PA0口(低电平),都会闪烁,说明:在推挽模式下,高低电平都有驱动能力。

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
改为GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
只能短脚插在PA0,灯会闪烁。长脚插PA0,灯灭。
说明开漏模式的高电平是没有驱动能力的,相当于高阻态。

LED流水灯

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include "stm32f10x.h"                  // Device header
#include "delay.h"
int main(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.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_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 0001
Delay_ms(500);
GPIO_Write(GPIOA,~0x0004);//0000 0000 0000 0001
Delay_ms(500);
GPIO_Write(GPIOA,~0x0008);//0000 0000 0000 0001
Delay_ms(500);
GPIO_Write(GPIOA,~0x0010);//0000 0000 0000 0001
Delay_ms(500);
GPIO_Write(GPIOA,~0x0020);//0000 0000 0000 0001
Delay_ms(500);
GPIO_Write(GPIOA,~0x0040);//0000 0000 0000 0001
Delay_ms(500);
GPIO_Write(GPIOA,~0x0080);//0000 0000 0000 0001
Delay_ms(500);
}
}

GPIO_InitStructure.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口。

蜂鸣器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include "stm32f10x.h"                  // Device header
#include "delay.h"
int main(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
while(1)
{
GPIO_SetBits(GPIOB,GPIO_Pin_12);
Delay_ms(100);
GPIO_ResetBits(GPIOB,GPIO_Pin_12);
Delay_ms(100);
GPIO_SetBits(GPIOB,GPIO_Pin_12);
Delay_ms(700);
GPIO_ResetBits(GPIOB,GPIO_Pin_12);
Delay_ms(100);
}
}

GPIO简介

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

GPIO基本结构

在STM32中,所有GPIO挂载在APB2总线。
每个GPIO有16个引脚,编号从0-15。
GPIO基本结构
在每个GPIO模块中,主要包含寄存器和驱动器,内核通过APB2总线对内核进行读写。
输出寄存器写1,对应的引脚输出高电平,写0,输出低电平。
输入寄存器写1,对应端口目前为高电平,写0,为低电平。
这个寄存器的低16位对应端口,高16位没有使用。

GPIO位结构

GPIO位结构
上面是输入部分,下面为输出部分。

输入部分:

I/O引脚,两个保护二极管,主要是对输入电压进行限幅的。
输入驱动和器连接上拉电阻和下拉电阻。上拉电阻连接VDD,下拉电阻连接VSS。开关通关程序进行配置。如果上面导通,下面断开,为上拉输入模式,如果上面断开,下面导通,为下拉输入模式,如果都断开,为浮空输入模式。
TTL肖特基触发器(施密特触发器),对输入电压进行整形。如果输入电压大于某一阈值,输出为高电平。输入电压小于某一阈值,输出为低电平。
经过施密特触发器整形的波形,可以直接写入数据寄存器。
上面的2路输入,连接到片上外设。模拟输入,连接到ADC上。为模拟量,在施密特触发器前面。
复用功能输入,连接到其他需要读取端口的外设上。为数字量,在施密特触发器后面。

输出部分

数字部分由输出寄存器或片上外设控制。
选择输出寄存器控制,就是普通的IO口输出。写这个数据寄存器的某一位可以操作端口。
经过输出控制,接到2个MOS管,可以选择推挽、开漏或关闭3种模式。

在推挽输出模式下,P-MOS和N-MOS均有效。
数据寄存器为1时,上管导通,下管断开。输出接到VDD,输出高电平。
数据寄存器为0时,下官导通,上管断开,输出接到VSS,输出低电平。
在推挽输出模式下,STM32对IO口有绝对控制权。

在开漏输出模式下,P-MOS无效,只有N-MOS工作。
数据寄存器为1时,下管断开,输出相当于断开,为高阻模式。
数据寄存器为0时,下管导通,输出直接接到VSS,输出低电平。
这种模式下,只有低电平有驱动能力,高电平没有驱动能力。
开漏模式可以作为通讯协议的驱动方式,比如I2C通信。
开漏模式还可以输出5V电平的信号。

在关闭模式下,两个MOS管都无效,输出关闭,端口的电平由外部信号控制。

GPIO模式

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

模式名称性质特征
浮空输入数字输入可读取引脚电平,若引脚悬空,则电平不确定
上拉输入数字输入可读取引脚电平,内部连接上拉电阻,悬空时默认高电平
下拉输入数字输入可读取引脚电平,内部连接下拉电阻,悬空时默认低电平
模拟输入模拟输入GPIO无效,引脚直接接入内部ADC
开漏输出数字输出可输出引脚电平,高电平为高阻态,低电平接VSS
推挽输出数字输出可输出引脚电平,高电平接VDD,低电平接VSS
复用开漏输出数字输出由片上外设控制,高电平为高阻态,低电平接VSS
复用推挽输出数字输出由片上外设控制,高电平接VDD,低电平接VSS