32单片机系列
51的学习暂时告一段落,也要继续学习stm32了。目前用的板子是江协的stm32f103c8的版本,先尝试着学习一下标准库的用法,虽然很难,但可以很好的理解计算机底层内容,尽力学吧,实在受不了就转向stmcubmax加hal库的方案。
1.GPIO
GPIO全拼叫General Purpose Input Output(通用输入输出)简称IO口,作用是用来控制连接在此GPIO口上的外设,通俗来说,就是单片机芯片通过控制IO口的电流输出,来起到控制外设的作用。
GPIO原理图

一共有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_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) { } }
|
效果图

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #include "stm32f10x.h" #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" #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" #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" #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" 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" #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中断等等。

这就是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" 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); 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"
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的值。