3 GPIO通用输入输出口
3.1 GPIO简介
- GPIO(General Purpose Input Output)通用输入输出口;
- 可配置为8种输入输出模式;
- 引脚电平:0V~3.3V,部分引脚可容忍5V;
- 但是输出最大只能输出3.3V,因为供电就只有3.3V;
- 具体哪些端口可以容忍5V,可以参考STM32的引脚定义,带FT的就可以容忍5V;
- 输出模式下可控制端口输出高低电平,用以驱动LED、控制蜂鸣器、模拟通信协议输出时序等;
- 输入模式下可读取端口的高低电平或电压,用于读取按键输入、外接模块电平信号输入、ADC电压采集、模拟通信协议接收数据等。
3.2 GPIO基本结构
- 最左边的是APB2外设总线,在STM32中,所有的GPIO都是挂载在APB2外设总线上的;
- GPIO的外设是按照GPIOA、GPIOB、GPIOC……这样的方式来命名的;
- 每个GPIO外设有16个引脚,编号是0~15,第0号引脚称作PA0,1号引脚称作PA1……;
- 在每个GPIO模块内,主要包含了寄存器和驱动器;
- 寄存器:
- 寄存器就是一段特殊的存储器,内核可以通过APB2总线对寄存器进行读写,这样就可以完成读取电平和输出电平的功能;
- 寄存器的每一位对应一个引脚;
- 输出寄存器写1,对应的引脚就会输出高电平,写0则输出低电平;输入寄存器读取为1,说明对应的端口目前是高电平,读取为0说明是低电平;
- 因为STM32是32位的单片机,所以STM32内部的寄存器都是32位的。但是此处的端口(引脚)只有16位,所以这个寄存器只有低16位对应的有端口,高16位是没有用到的;
- 驱动器:用来增强信号和驱动能力。
- 寄存器:
3.3 GPIO位结构
- 左边三个是寄存器,中间部分是驱动器,右边就是某个I/O口的引脚;
- 整体结构可以分为两部分,上面是输入部分,下面是输出部分;
- 输入部分:
- I/O引脚处接了两个保护二极管,是对输入电压进行限幅的,上面的二极管接VDD,即3.3V,下面的二极管接VSS,即0V;
- 若输入电压比3.3V还要高,那么上方的二极管就会导通,输入电压产生的电流就会直接流入VDD而不会流入内部电路,这样就可以避免过高的电压对内部电路造成损伤;
- 若输入电压低于0V,这个电压是相当于VSS的电压的,所以是可以有负电压的,此时下面的二极管就会导通,电流会从VSS流出去,而不会从内部电路汲取电流,也是用于保护电路;
- 若输入电压在0~3.3V之间,那么两个二极管均不会导通;
- I/O引脚连接到了一个上拉电阻和下拉电阻,上拉电阻至VDD,下拉电阻至VSS,此处的两个开关是可以通过程序配置的;
- 若上面导通,下面断开,就是上拉输入模式;
- 若下面导通,上面断开,就是下拉输入模式;
- 若两个都断开,就是浮空输入模式;
- 上拉和下拉的作用就是为了给输入提供一个默认的输入电平。因为对于一个输入的端口,输入不是高电平就是低电平。那么如果输入引脚什么都不接,此时这种情况算是低电平还是高电平呢?这就不好说了。实际情况是:如果输入什么都不接,这时输入就会处于一种浮空的状态,引脚的输入电平极易受外界干扰而改变。为了避免引脚悬空导致的输入数据不稳定,就要给其加上上拉电阻或者下拉电阻;
- 如果接入上拉电阻,当引脚悬空时,还有上拉电阻来保证引脚的高电平,所以上拉输入又可以称作是默认为高电平的输入模式。下拉同理,默认为低电平的输入模式;
- 上拉电阻和下拉电阻的阻值是比较大的,是一种弱上拉和弱下拉,目的是尽量不影响正常的输入操作;
- 继续往左是一个肖特基触发器(应该是斯密特触发器),作用是对输入电压进行整型,它的执行逻辑是:如果输入电压大于某一阈值,输出就会瞬间升为高电平;如果输出电压小于某一阈值,输出就会瞬间降为低电平;
- 经过斯密特触发器整形的波形就可以直接写入输入数据寄存器了,我们再用程序读取输入数据寄存器对应某一位的数据,就可以知道端口的输入电平了;
- 上面还有两路线路,是连接到片上外设的一些端口;
- 模拟输入,连接到ADC上,因为ADC需要接收模拟量,所以这根线是接到斯密特触发器前面的;
- 复用功能输入,连接到其它需要读取端口的外设,比如串口的输入引脚等,这根线接收的是数字量,所以接到斯密特触发器后面;
- I/O引脚处接了两个保护二极管,是对输入电压进行限幅的,上面的二极管接VDD,即3.3V,下面的二极管接VSS,即0V;
- 输出部分:
- 输出部分由输出数据寄存器或片上外设控制,两种控制方式通过数据选择器接到了输出控制部分;
- 如果选择通过输出数据寄存器控制,就是普通的I/O口输出,写这个数据寄存器的某一位就可以操作对应的某一个端口的;
- 左边还有一个位设置/清除寄存器,可以用来单独操作输出数据寄存器的某一位,而不影响其它位。因为这个输出数据寄存器同时控制16个端口,并且这个寄存器只能整体读写,所以如果想单独控制其中某一个端口而不影响其它端口的话,就需要一些特殊的操作方式;
- 第一种方式:先读出这个寄存器,然后用按位与或按位或的方式更改某一位,再将更改后的数据写回去;
- 麻烦,效率不高;
- 第二种方法:通过设置这个位设置/清除寄存器;
- 如果我们要对某一位进行置1的操作,在位设置寄存器的对应位写1即可,剩下不需要操作的位写0,这样它内部就会有电路自动将输出数据寄存器中对应位置1,而剩下写0的位则保持不变,这样就保证了只操作某一位而不影响其它位;
- 如果我们要对某一位进行置0的操作,在位清除寄存器的对应位写1即可,这样内部电路就会把这一位清零;
- 第三种方法:读写STM32中的“位带”区域
- 这个“位带”的作用和51单片机中位寻址的作用差不多。在STM32中,有一段专门分配的地址区域,这段地址映射了RAM和外设寄存器所有的位,读写这段地址中的数据,就相当于读写锁映射地址的某一位;
- 第一种方式:先读出这个寄存器,然后用按位与或按位或的方式更改某一位,再将更改后的数据写回去;
- 输出控制的右边接到了两个MOS管,上面是P-MOS,下面是N-MOS;
- MOS管就是一种电子开关,我们的信号来控制开关的导通和关闭,开关负责将I/O口接到VDD或VSS;
- 此处可以选择推挽、开漏或关闭三种输出方式;
- 推挽模式下,P-MOS和N-MOS均有效;
- 数据寄存器为1时,上管导通,下管断开,输出直接接到VDD,就是输出高电平;
- 数据寄存器为0时,上管断开,下管导通,输出直接接到VSS,就是输出低电平;
- 这种模式下,高低电平均有较强的驱动能力,所以推挽输出模式也称作强推输出模式;
- 在推挽输出模式下,STM32对I/O口具有绝对的控制权,高低电平都由STM32说了算;
- 开漏模式下,P-MO无效,只有N-MOS在工作;
- 数据寄存器为1时,下管断开,这是输出相当于断开,也就是高阻模式;
- 数据寄存器为0时,下管导通,输出直接接到VSS,也就是输出低电平;
- 这个模式下,只有低电平有驱动能力,高电平无驱动能力;
- 开漏模式可以作为通信协议的驱动方式,比如I2C总线的引脚,就是使用的开漏模式;
- 在多机通信的情况下,这种模式可以避免各个设备的互相干扰;
- 开漏模式还可以用于输出5V的电平信号。比如在I/O口外接一个上拉电阻到5V的电源,当输出低电平时,由内部的N-MOS直接接VSS;当输出高电平时,由外部的上拉电阻拉高至5V,这样就可以输出5V的电平信号,用于兼容一些5V电平的设备;
- 关闭模式,当引脚配置为输入模式的时候,两个MOS管都无效,也就是输出关闭,端口的电平由外部信号控制。
- 推挽模式下,P-MOS和N-MOS均有效;
- 输出部分由输出数据寄存器或片上外设控制,两种控制方式通过数据选择器接到了输出控制部分;
3.4 GPIO模式
-
通过配置GPIO的端口配置寄存器,端口可以配置成以下8种模式:
模式名称 性质 特征 浮空输入(IN_FLOATING) 数字输入 可读取引脚电平,若引脚悬空,则电平不确定 上拉输入(IPU,In Pull Up) 数字输入 可读取引脚电平,内部连接上拉电阻,悬空时默认高电平 下拉输入(IPD,In Pull Down) 数字输入 可读取引脚电平,内部连接下拉电阻,悬空时默认低电平 模拟输入(AIN,Analog IN) 模拟输入 GPIO无效,引脚直接接入内部ADC 开漏输出(Out_OD,Out Open Drain) 数字输出 可输出引脚电平,高电平为高阻态,低电平接VSS 推挽输出(Out_PP,Out Push Pull) 数字输出 可输出引脚电平,高电平接VDD,低电平接VSS 复用开漏输出(AF_OD,Atl Open Drain) 数字输出 由片上外设控制,高电平为高阻态,低电平接VSS 复用推挽输出(AF_PP,Atl Push Pull) 数字输出 由片上外设控制,高电平接VDD,低电平接VSS -
浮空/上拉/下拉输入:
-
模拟输入:
-
开漏/推挽输出:
-
复用开漏/推挽输出:
3.5 LED和蜂鸣器简介
- LED:发光二极管,正向通电点亮,反向通电不亮;
- 有源蜂鸣器:内部自带振荡源,将正负极接上直流电压即可持续发声,频率固定;
- 无源蜂鸣器:内部不带振荡源,需要控制器提供振荡脉冲才可发声,调整提供振荡脉冲的频率,可发出不同频率的声音;
- 硬件电路:
- 左上是LED的低电平驱动电路,左下是LED的高电平驱动电路;
- GPIO在推挽输出模式下,高低电平均有比较强的驱动能力,所以两种接法均可;
- 但是在单片机的电路中,一般倾向于低电平驱动的接法。因为很多的单片机和芯片,都采用的是高电平弱驱动、低电平强驱动的规则,这样在一定程度上可以避免高低电平打架;
- 右上是PNP三极管的驱动电路,三极管的左边是基极,带箭头的是发射极,剩下的是集电极。基极给低电平,三极管就会导通,蜂鸣器就有电流;基极给低电平,三极管截止,蜂鸣器就没有电流;
- 右下是NPN三极管的驱动电路,驱动逻辑与上面的相反;
- PNP三极管最好接在上面,NPN三极管最好接在下面。因为三极管的通断,是需要发射极和基极直接产生一定的开启电压的。如果把负载接在发射极这一边,可能会导致三极管不能开启;
- PNP三极管最好接在上面,NPN三极管最好接在下面。因为三极管的通断,是需要发射极和基极直接产生一定的开启电压的。如果把负载接在发射极这一边,可能会导致三极管不能开启;
- 左上是LED的低电平驱动电路,左下是LED的高电平驱动电路;
3.6 面包板
3.7 LED闪烁
- 新建System文件夹存放
Delay
函数相关代码; Delay.h
:#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.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); } }
mian.c
:#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_Mode_Out_OD,开漏输出模式,高电平无驱动能力,LED的接线方式就不可以短脚接负,长脚接引脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //输出引脚 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //输出速度 //第一个参数是选择哪一个GPIO,第二个参数是一个指向结构体的指针 GPIO_Init(GPIOA, &GPIO_InitStructure); while(1) { //以下几种写法均可 //点亮LED GPIO_ResetBits(GPIOA, GPIO_Pin_0); Delay_ms(500); //熄灭LED GPIO_SetBits(GPIOA, GPIO_Pin_0); Delay_ms(500); //点亮LED GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_RESET); Delay_ms(500); //熄灭LED GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_SET); Delay_ms(500); //点亮LED GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)1); Delay_ms(500); //熄灭LED GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)0); Delay_ms(500); } }
3.8 LED流水灯
mian.c
:#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_All; //输出引脚,此处配置成所有的总共16个引脚,也可以按照GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2的方式来选择多个引脚 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //输出速度 //第一个参数是选择哪一个GPIO,第二个参数是一个指向结构体的指针 GPIO_Init(GPIOA, &GPIO_InitStructure); while(1) { //第二个参数是指定写到输出数据寄存器(GPIO里的ODR寄存器)的值 GPIO_Write(GPIOA, ~0x0001); //0000 0000 0000 0001,对应PA0~PA15共16个端口。因为是低电平点亮,所以还要加一个按位取反的符号 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.9 蜂鸣器
main.c
:#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_PP; //推挽输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //输出引脚 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //输出速度 //第一个参数是选择哪一个GPIO,第二个参数是一个指向结构体的指针 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); } }
3.10 按键简介
- 按键:常见的输入设备,按下导通,松手断开;
- 按键抖动:由于按键内部使用的是机械式弹簧片来进行通断的,所以在按下和松手的瞬间会伴随有一连串的抖动;
3.11 传感器模块简介
- 传感器模块:传感器元件(光敏电阻/热敏电阻/红外接收管等)的电阻会随外界模拟量的变化而变化,通过与定值电阻分压即可得到模拟电压输出,再通过电压比较器进行二值化即可得到数字电压输出;
- 按键和传感器的硬件电路:
- 左边4幅图是按键的硬件电路;上两图是下接按键的方式(常用),下两图是上接按键的方式;
- 左上图。是按键的最常用接法。随便选择一个GPIO口,此处选择的是PA0,然后通过K1接地。当按键按下时,PA0直接被下拉到GND,此处读取PA0口的电压就是低电平。当按键松手时,PA0被悬空,就会导致引脚的电压不确定,为了避免出现这种情况,必须要求PA0是上拉输入的模式。如果PA0是上拉输入的模式,引脚悬空时,PA0就是高电平。所以这种方式下,按下按键引脚为低电平,松开按键引脚为高电平;
- 右上图。相比较第一个图,多了一个上拉电阻。当按键松手时,引脚由于上拉作用,保持高电平。当按键按下时,引脚直接接到GND,引脚就为低电平。这种情况下,引脚不会出现悬空状态,所以PA0引脚配置为浮空输入或上拉输入。如果是上拉输入,那么就是和STM32内部的上拉电阻共同作用了,这时高电平就会更强一些,对应高电平就更加稳定,当然这样的话,当引脚被强行拉到低时,损耗也就会大一些;
- 左下图。PA0通过按键接到3.3V,这种接线要求PA0配置成下拉输入的模式。当按键按下时,引脚为高电平,松手时,引脚默认低电平。这要求低电平的引脚可以配置成下拉输入的模式,一般单片机不一定有下拉输入的模式,所以最好还是用上面的接法;
- 右下图。在左下图的基础上,外接一个下拉电阻,这个接法PA0需要配置成下拉输入模式或者浮空输入模式;
- 总结:
- 上面的接法是按键按下时引脚是低电平,松手是高电平;下面的接法是按键按下时是高电平,松手是低电平;
- 左边的接法要求引脚上上拉或者下拉输入模式,右边的接法允许引脚是浮空输入的模式;
- 右边的图是传感器的硬件电路;
- 左边4幅图是按键的硬件电路;上两图是下接按键的方式(常用),下两图是上接按键的方式;
3.12 按键控制LED
- 新建Hardware文件夹来存放硬件驱动相关代码;
- 项目目录结构:
LED.h
:#ifndef __LED_H #define __LED_H void LED_Init(void); void LED1_ON(void); void LED1_OFF(void); void LED1_Turn(void); void LED2_ON(void); void LED2_OFF(void); void LED2_Turn(void); #endif
LED.c
:#include "stm32f10x.h" // Device header /** * 函 数:LED初始化 * 参 数:无 * 返 回 值:无 */ void LED_Init(void) { /*开启时钟*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟 /*GPIO初始化*/ 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); //将PA1和PA2引脚初始化为推挽输出 /*设置GPIO初始化后的默认电平,使LED灯熄灭*/ GPIO_SetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_2); //设置PA1和PA2引脚为高电平 } /** * 函 数:LED1开启 * 参 数:无 * 返 回 值:无 */ void LED1_ON(void) { GPIO_ResetBits(GPIOA, GPIO_Pin_1); //设置PA1引脚为低电平 } /** * 函 数:LED1关闭 * 参 数:无 * 返 回 值:无 */ void LED1_OFF(void) { GPIO_SetBits(GPIOA, GPIO_Pin_1); //设置PA1引脚为高电平 } /** * 函 数:LED1状态翻转 * 参 数:无 * 返 回 值:无 */ void LED1_Turn(void) { if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_1) == 0) //获取输出寄存器的状态,如果当前引脚输出低电平 { GPIO_SetBits(GPIOA, GPIO_Pin_1); //则设置PA1引脚为高电平 } else //否则,即当前引脚输出高电平 { GPIO_ResetBits(GPIOA, GPIO_Pin_1); //则设置PA1引脚为低电平 } } /** * 函 数:LED2开启 * 参 数:无 * 返 回 值:无 */ void LED2_ON(void) { GPIO_ResetBits(GPIOA, GPIO_Pin_2); //设置PA2引脚为低电平 } /** * 函 数:LED2关闭 * 参 数:无 * 返 回 值:无 */ void LED2_OFF(void) { GPIO_SetBits(GPIOA, GPIO_Pin_2); //设置PA2引脚为高电平 } /** * 函 数:LED2状态翻转 * 参 数:无 * 返 回 值:无 */ void LED2_Turn(void) { if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_2) == 0) //获取输出寄存器的状态,如果当前引脚输出低电平 { GPIO_SetBits(GPIOA, GPIO_Pin_2); //则设置PA2引脚为高电平 } else //否则,即当前引脚输出高电平 { GPIO_ResetBits(GPIOA, GPIO_Pin_2); //则设置PA2引脚为低电平 } }
Key.h
:#ifndef __KEY_H #define __KEY_H void Key_Init(void); uint8_t Key_GetNum(void); #endif
key.c
:#include "stm32f10x.h" // Device header #include "Delay.h" /** * 函 数:按键初始化 * 参 数:无 * 返 回 值:无 */ void Key_Init(void) { /*开启时钟*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟 /*GPIO初始化*/ 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); //将PB1和PB11引脚初始化为上拉输入 } /** * 函 数:按键获取键码 * 参 数:无 * 返 回 值:按下按键的键码值,范围:0~2,返回0代表没有按键按下 * 注意事项:此函数是阻塞式操作,当按键按住不放时,函数会卡住,直到按键松手 */ uint8_t Key_GetNum(void) { uint8_t KeyNum = 0; //定义变量,默认键码值为0 if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) //读PB1输入寄存器的状态,如果为0,则代表按键1按下 { Delay_ms(20); //延时消抖 while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0); //等待按键松手 Delay_ms(20); //延时消抖 KeyNum = 1; //置键码为1 } if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0) //读PB11输入寄存器的状态,如果为0,则代表按键2按下 { Delay_ms(20); //延时消抖 while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0); //等待按键松手 Delay_ms(20); //延时消抖 KeyNum = 2; //置键码为2 } return KeyNum; //返回键码值,如果没有按键按下,所有if都不成立,则键码为默认值0 }
main.c
:#include "stm32f10x.h" // Device header #include "Delay.h" #include "LED.h" #include "Key.h" uint8_t KeyNum; //定义用于接收按键键码的变量 int main(void) { /*模块初始化*/ LED_Init(); //LED初始化 Key_Init(); //按键初始化 while (1) { KeyNum = Key_GetNum(); //获取按键键码 if (KeyNum == 1) //按键1按下 { LED1_Turn(); //LED1翻转 } if (KeyNum == 2) //按键2按下 { LED2_Turn(); //LED2翻转 } } }
3.13 光敏传感器控制蜂鸣器
- 项目结构:
Buzzer.h
:#ifndef __BUZZER_H #define __BUZZER_H void Buzzer_Init(void); void Buzzer_ON(void); void Buzzer_OFF(void); void Buzzer_Turn(void); #endif
Buzzer.C
:#include "stm32f10x.h" // Device header /** * 函 数:蜂鸣器初始化 * 参 数:无 * 返 回 值:无 */ void Buzzer_Init(void) { /*开启时钟*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟 /*GPIO初始化*/ GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB12引脚初始化为推挽输出 /*设置GPIO初始化后的默认电平*/ GPIO_SetBits(GPIOB, GPIO_Pin_12); //设置PB12引脚为高电平 } /** * 函 数:蜂鸣器开启 * 参 数:无 * 返 回 值:无 */ void Buzzer_ON(void) { GPIO_ResetBits(GPIOB, GPIO_Pin_12); //设置PB12引脚为低电平 } /** * 函 数:蜂鸣器关闭 * 参 数:无 * 返 回 值:无 */ void Buzzer_OFF(void) { GPIO_SetBits(GPIOB, GPIO_Pin_12); //设置PB12引脚为高电平 } /** * 函 数:蜂鸣器状态翻转 * 参 数:无 * 返 回 值:无 */ void Buzzer_Turn(void) { if (GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_12) == 0) //获取输出寄存器的状态,如果当前引脚输出低电平 { GPIO_SetBits(GPIOB, GPIO_Pin_12); //则设置PB12引脚为高电平 } else //否则,即当前引脚输出高电平 { GPIO_ResetBits(GPIOB, GPIO_Pin_12); //则设置PB12引脚为低电平 } }
LightSensor.h
:#ifndef __LIGHT_SENSOR_H #define __LIGHT_SENSOR_H void LightSensor_Init(void); uint8_t LightSensor_Get(void); #endif
LightSensor.c
:#include "stm32f10x.h" // Device header /** * 函 数:光敏传感器初始化 * 参 数:无 * 返 回 值:无 */ void LightSensor_Init(void) { /*开启时钟*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟 /*GPIO初始化*/ 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); //将PB13引脚初始化为上拉输入 } /** * 函 数:获取当前光敏传感器输出的高低电平 * 参 数:无 * 返 回 值:光敏传感器输出的高低电平,范围:0/1 */ uint8_t LightSensor_Get(void) { return GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13); //返回PB13输入寄存器的状态 }
main.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) //如果当前光敏输出1 { Buzzer_ON(); //蜂鸣器开启 } else //否则 { Buzzer_OFF(); //蜂鸣器关闭 } } }