32单片机系列

51的学习暂时告一段落,也要继续学习stm32了。目前用的板子是江协的stm32f103c8的版本,先尝试着学习一下标准库的用法,虽然很难,但可以很好的理解计算机底层内容,尽力学吧,实在受不了就转向stmcubmax加hal库的方案。

1.GPIO

GPIO全拼叫General Purpose Input Output(通用输入输出)简称IO口,作用是用来控制连接在此GPIO口上的外设,通俗来说,就是单片机芯片通过控制IO口的电流输出,来起到控制外设的作用。

GPIO原理图

屏幕截图2024-12-12174243

一共有8种方式,但是一般常用输出方式就是推挽输出和开漏输出,而常用的输入方式是上拉输入和下拉输入,两者的区别就在于当没有外设给io口电平时,二者的默认电平不一样。

推挽输出同时支持高低电平驱动,方便快速切换电平,但是不支持线与(这个目前还没接触过),开漏输出就只支持低电平输入,电压取决于外部电压。

点灯+蜂鸣器

点灯

又是熟悉的点灯环节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "stm32f10x.h"                  // Device header
int main()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //初始化时钟
GPIO_InitTypeDef GPIO_InitStructure; //定义gpio的一个结构体
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //分别设置结构体的三个参数
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化GPIO
GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_RESET); //可以写入对应IO口的值
while(1)
{
}
}

效果图

A441DB3829A9723EC109E8F8ACA8D7E1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include "stm32f10x.h"   // Device header
#include "Delay.h"
int main()
{
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_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_RESET);
while(1)
{
GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_RESET);
Delay_ms(500);
GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_SET);
Delay_ms(500);
}
}

这个就引入了延时函数,可以实现小灯的频闪,但是这个32的delay函数跟51一样,还是通过执行无用的指令来拖延时间。

蜂鸣器

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"
int main()
{
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_ResetBits(GPIOB,GPIO_Pin_12);
Delay_ms(500);
GPIO_SetBits(GPIOB,GPIO_Pin_12);
Delay_ms(500);
}
}

本质上是一样的,无非就是改一下初始化的GPIO口,和调整对应的赋值。

然后蜂鸣器有三个接口,用公母线连接到面包板上,分别是vcc,gnd和控制音频的引脚。也是跟小灯一样的赋值即可。

GPIO输入

按钮控制点灯

首先是按钮驱动的配置

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
#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(10);
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0);
KeyNum=1;
}
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11)==0)
{
Delay_ms(10);
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11)==0);
KeyNum=2;
}

return KeyNum;
}

都是先是正常的初始化函数,即设置GPIO参数,然后再写读取按钮函数,通过返回8位的int数据,来表示按钮的状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "stm32f10x.h"   // Device header
#include "Delay.h"
#include "LED.h"
#include "key.h"
uint8_t KeyNum;
int main()
{
LED_Init();
Key_Init();
while(1)
{
KeyNum = Key_GetNum();
if(KeyNum==1)
{
LED1_Turn();
}
if(KeyNum==2)
{
LED2_Turn();
}
}
}

这是主函数,逻辑很简单,就是通过判断Key_GetNum返回的值来判断按键的状态,其中,LED_Turn函数的内容就是如果GPIO输出为1就置为0,为0就置为1,的一个简单的反转函数。

光敏传感器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "stm32f10x.h"                  // Device header
void LightSensor_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_13;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_13);
}
uint8_t LightSensor_Get(void)
{
return GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_13);
}

都大同小异,光敏传感器驱动也是一样的初始化函数加一个读取函数。

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 "LED.h"
#include "key.h"
#include "Buzzer.h"
#include "LightSensor.h"
int main()
{
Buzzer_Init();
LightSensor_Init();
while(1)
{
if(LightSensor_Get()==1)
Buzzer_ON();
else
Buzzer_OFF();
}
}

这样就实现了当光不照射时,蜂鸣器一直响,当光照射时,蜂鸣器不响。

OLED

这个跟51单片机类似,用的是江科协的库,直接调用对应的函数即可。

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 "LED.h"
#include "OLED.h"
int main()
{
OLED_Init();
OLED_ShowChar(1,1,'A');
OLED_ShowString(1,3,"HelloWorld");
OLED_ShowNum(2,1,12345,5);
OLED_ShowSignedNum(2,7,-66,3);
OLED_ShowHexNum(3,1,0xAA55,4); //显示十六进制
OLED_ShowBinNum(4,1,0xAA55,16); //显示二进制
while(1)
{
}
}

中断

对射红外传感器计次

这里跟51单片机的定时器情况类似,32就直接引入了中断的概念,像我的stm32f103有68种中断函数,有硬件中断也有定时器中断还有EXIT中断等等。

1735054774083

这就是EXIT中断的简要流程图,我们也需要根据图上的内容来依次配置寄存器,用hal库。

首先第一个是AFIO,AFIO的作用是从多个GPIO接口中筛选出一个16接口来交给EXIT,避免线太多的麻烦,所以在配置EXIT中断时,选择监视的接口的端口位数不能有重复的,就比如不能同时设置GPIO_PinB1和GPIO_PinA1同时作为EXIT检测的io口,这样会出错。

然后就是EXIT的设置,跟GPIO的初始化配置类似,先是定义一个结构体,然后再交给hal库取值处理。

最后就是NVIC,这个东西的作用就是起到全局调度,设置好中断优先级以后,NVIC会配置好中断步骤,然后再交给。中断优先级可以分为抢占优先级和子优先级,其中抢占优先级是可以进行中断嵌套的,而子优先级的中断只能当本次中断执行结束后再执行。

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
#include "stm32f10x.h"                  // Device header
uint16_t CountSensor_Count=0;
void CountSensor_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);

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);

GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14); //AFIO配置

EXTI_InitTypeDef EXTI_InitStructre;
EXTI_InitStructre.EXTI_LineCmd = ENABLE; //打开使能
EXTI_InitStructre.EXTI_Mode = EXTI_Mode_Interrupt; //设置外设还是中断
EXTI_InitStructre.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿触发模式
EXTI_InitStructre.EXTI_Line = EXTI_Line14; //设置具体的线
EXTI_Init(&EXTI_InitStructre);

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++;
EXTI_ClearITPendingBit(EXTI_Line14);
}
}

这就是简单的一个光线传感器的计数模块,利用了中断函数进行计数。

旋转编码计次

这里用到了一个简单的旋转编码器,在连接vcc跟gnd和两个信号传输线后就可以正常使用了。有A相和B相输出,其中两向之间存在相位差,正转:如果A相先于B相变化,则表示编码器正转。反转:如果B相先于A相变化,则表示编码器反转。

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
74
75
76
77
78
#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);

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);

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_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_0) == 0)
{
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_1) == 0)
{
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
{
Encoder_Count ++;
}
}
EXTI_ClearITPendingBit(EXTI_Line1);
}
}

编码器核心代码如上,当触发中断的时候,快速判断另一个头是不是低电平,来实现判断正转和反转,并进行+1和-1的操作。在主程序中只用num+=Encoder_Get()即可,并打印出num的值。