STM32固件库(标准外设库)入门学习 第三章GPIO

STM32固件库(标准外设库)入门学习 第三章GPIO



前言

本学习教程,参考B站江科大自化协STM32视频,型号为STM32F103C8T6。


一、GPIO简介

示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。
在这里插入图片描述
GPIO都是挂载在APB2总线上,其中GPIO外设的名称是按照GPIOA、GPIOB、GPIOC等来命名,每个GPIO外设都有16个引脚,编号从0到15,PA0……PA15。每个GPIO模块内包括寄存器和驱动。

寄存器就是一段特殊的存储器,内核可以用过APB2总线对寄存器进行读写,这样就可以完成输出电平和读取电平的功能,这个寄存器的每一位对应一个引脚。其中输出寄存器写1,对应引脚就会输出高电平,写0就输出低电平;输入寄存器读取为1,就证明对应的端口目前是高电平,读取为0,就是低电平。STM32是32位的单片机,所以STM32内部的寄存器都是32位的,但端口只有16位,所以这个寄存器只有低16位对应的有端口,高16位是没有用到的,数据手册上也很清楚的表明了这一点。
在这里插入图片描述
驱动器是用来增加信号的驱动能力。寄存器只负责存储数据,如果要进行点灯这样的操作,还是需要驱动器来负责增大驱动能力,这些就是GPIO的整体基本结构。


在这里插入图片描述

输入部分:

I/O引脚接了保护二极管,这个对输入电压进行限幅,上接3.3V,下接0V,如果输入电压比3.3V还要高,那上方这个二极管导通,输入电压产生的电流就会直接流入VDD,不会流入内部电路,避免过高的电压对内部这些电路产生伤害。如果输入电压比VSS还低,可以有负电压的,此时下方二极管导通,带你留会从VSS直接流出去,而不会从内部电路汲取电流,保护内部电路。输入电压在0~3.3V之间,那两个二极管均不会导通,这时二极管对电路没有影响,这就是保护二极管的用途。

往左看,此时连接了一个上拉电阻(接VDD)和一个下拉电阻(接VSS),开关可以通过程序进行配置,上面导通、下面断开,就是上拉输入模式;上面断开、下面导通,就是下拉输入模式;两个都断开就是浮空输入模式。上拉下拉的作用就是为了给输入提供一个默认的输入电平,对于一个数字的端口,输入不是高电平就是低电平,那如果输入引脚啥都不接,那算作高还是低,实际情况是,若输入不接,此时输入处于一种浮空的状态,引脚的输入电平极易受外界干扰而改变,为了避免引脚悬空导致的输入数据不确定,就需要在这里加上上拉或者下拉电阻。如果接入上拉电阻,当引脚悬空时,还有上拉电阻来保证引脚的高电平,所以上拉输入又可以称作默认位高电平的输入模式。下拉同理,默认为低电平的输入方式。这就像是太空的物体来到了地球上,如果不施加外力,由于重力的下拉作用,默认还是回到地面的。这个上拉电阻和下拉电阻的阻值都是比较大的,是一种弱上拉和弱下拉,目的是尽量不影响正常的输入操作。

再到TTL肖特基触发器(翻译错误,应为施密特触发器),这个作用就是对输入电压进行整形的,执行逻辑是如果输入电压大于某一上限值,输出就会瞬间升为高电平;如果输入电压小于某一下限值,输出就会瞬间降为低电平。对于施密特触发器,只有高于上限或者低于下限,输出才会变化。经过施密特触发器整形的波形就可以输入到输入数据寄存器,再用程序读取输入数据寄存器对应某一位的数据,就可以知道端口的输入电平。

上面还有两路线路,这些就是连接到片上外设的一些端口,其中有模拟输入,这个是连接到ADC上,ADC需要接收模拟量,所以这根线是要接再施密特触发器前面的。另一个是复用功能输入,这个连接到其他需要读取端口的外设上,比如串口的输入引脚等,这根线接收的是数字量,所以在施密特触发器后面。

输出部分:

输出部分可以由输出数据寄存器片上外设控制,通过数据选择器接到输出控制部分。

若选择输出数据寄存器进行控制,就是普通的IO口输出,写此数据寄存器的某一位就可以操作对应的某个端口了,左边还有一个位设置/清楚寄存器,这个可以用来单独操作输出数据寄存器的某一位,而不影响其他位,因为输出数据寄存器同时控制16个端口,并且这个寄存器只能整体读写,所以如果想单独控制其中某一个端口而不影响其他端口的话,就需要特殊的方式:
(1)第一种方式就是先读出这个寄存器,用按位与和按位或的方式更改某一位,最后再将更改后的数据写回去,再C语言中就是&=和|=的操作,这种方法比较麻烦,效率不高,对IO口操作而言不合适;
(2)第二种方式通过设置这个位设置和位清楚寄存器,若想对某一位置1的操作,在位设置寄存器的对应位写1即可,剩下不需要操作的位写0,这样它内部就会有电路,自动将输出数据寄存器中对应位置为1,而剩下写0的位则保持不变。相对某一位清0,则在清除寄存器中相应位写1即可,这样内部电路就会把这一位清0;
(3)第三种方式就是读写STM32中的“位带”区域,与51单片机中的位寻址类似,在STM32中专门分配的有一段地址区域,这段地址映射了RAM和外设寄存器所有的位,读写这段地址中的数据,就相当于读写所映射位置的某一位,这就是位带的操作方式。
库函数就是用了位设置和位清除的方法。

输出控制后街道两个MOS管,上P-MOS,下N-MOS,MOS管就是一种电子开关,信号控制开关的导通和关闭,开关负责将IO口接到VDD或者VSS,此时可以选择推挽、开漏或关闭三种输出方式:
(1)推挽输出模式下,P-MOS、N-MOS均有效。输出数据寄存器为1时,上管导通,下管断开,输出直接接到VDD,输出高电平;输出数据寄存器为0时,上管断开,下管导通,输出直接接到VSS,就是输出低电平。这种模式下高低电平均有较强的驱动能力,所以推挽输出模式也可以叫强推输出模式。在推挽输出模式下,STM32对IO口具有绝对的控制权,高低电平都由STM32决定。
(2)开漏输出模式下,P-MOS是无效的,只有N-MOS工作,输出数据寄存器为1时,下管断开,输出相当于断开,也就是高阻模式;输出数据寄存器为0时,下管导通,输出直接接到VSS,就是输出低电平,这种模式下,只有低电平有驱动能力,高电平没有驱动能力。这种模式可以作为通信协议的驱动方式,比如I2C通信的引脚,就是使用开漏模式,在多机通信的情况下,这个模式可以避免各个设备的相互干扰。开漏模式还可以用于输出5V的电平信号,比如在IO口外接一个上拉电阻,到5V地电源,当输出低电平时,由内部地N-MOS直接接VSS,当输出高电平时,由外部地上拉电阻拉高至5V,这样就可以输出5V地电平信号,用于兼容5V电平地设备。
(3)关闭输出模式,这个是当引脚配置为输入模式的时候,两个MOS管都无效,及输出关闭,端口的电平由外部信号来控制。
在这里插入图片描述


GPIO的MODEy输出速度,这个根据低功耗和稳定性确定,要求不高一般配置成50 MHz。

很多单片机或芯片使用了高电平弱驱动,低电平强驱动的规则,这样可以一定程度上避免高低电平打架,如果高电平驱动能力弱,就不能使用第二种连接方法。

在这里插入图片描述
对于功率稍微大一点的,直接用IO口驱动导致STM32负担过重,这时就可以用一个三极管驱动电路来完成驱动任务,上图为PNP三极管驱动电路,三极管左边为基极,带箭头的是发射极,剩下的为集电极,基极给低电平,三极管导通,蜂鸣器响,基极给高电平,三极管截止,蜂鸣器不响;下图为NPN三极管驱动电路,左为基极,带箭头的是发射极,剩下的是集电极,基极给高电平,三极管导通,蜂鸣器响,基极给低电平,三极管截止,蜂鸣器不响。PNP三极管接在上面,NPN接在下面,是因为三极管的通断是需要在发射极和基极直接产生一定的开启电压,上图负载接在发射极一边,会导致三极管不能开启。


二、GPIO输出

1.点亮LED灯接线图

在这里插入图片描述

2.keilkill批处理文件

keilkill批处理文件,可以删去Listings和Objects两个文件夹,这些都是工程的中间文件,若想把工程分享给别人,先双击批处理keilkill文件,删除中间文件,程序大小可以缩小到10%,此时再打包此文件夹,分享给别人。
在这里插入图片描述

3.程序编程步骤

第一步:使用RCC开启GPIO时钟;
在这里插入图片描述
第二步:使用GPIO_Init函数初始化GPIO,需要先定义结构体;
在这里插入图片描述
第三步:使用输出或者输入函数控制GPIO口。上下两种方式都可以。
在这里插入图片描述

4.RCC库函数

先看RCC有哪些库函数,都在library里找到rcc.h这个文件,双击打开。在.h文件下面,一般都是库函数所有函数的声明。在这里有很多RCC的库函数,但实际上大部分库函数都不会用到,最常用的只有这三个函数:RCC AHB外设时钟控制、RCC APB2外设时钟控制、RCC APB1外设时钟控制。这些都是一样的使用方法,第一个参数选择外设,第二个参数选择使能或失能。若不清楚哪个外设是连接在哪个总线上的,根据列表查找。
在这里插入图片描述

5.GPIO库函数

在library里找到gpio.h这个文件,然后拖到最后。
(1)GPIO_DeInit,调用此函数后,所指定的GPIO外设就会被复位;
(2)GPIO_AFIODeInit,可以复位AFIO外设;
(3)GPIO_Init,非常重要的函数,用结构体的参数,初始化GPIO口,需要先定义一个结构体变量,然后再给结构体赋值,最后调用这个函数,这个函数的内部就会自动读取结构体的值,然后把外设的各个参数配置好,这个Init函数在STM32中基本所有的外设都有,一般初始化外设都是使用Init函数完成;
(4)GPIO_StructInit,这个函数可以把结构体变量赋一个默认值;
(5)GPIO_ReadInputDataBit、GPIO_ReadInputData、GPIO_ReadOutputDataBit、GPIO_ReadOutputData为GPIO的读取函数;GPIO_ReadInputDataBit用来读取输入寄存器某一个端口输入值,返回值是uint8_t,代表端口的高低电平,GPIO_ReadInputData读取整个输入寄存器,参数只有一个GPIOx,用来指定外设,返回值为uint16_t,是一个16位数据,每一位代表一个端口值,GPIO_ReadOutputDataBit 用来读取输出数据寄存器的某一个位,原则上来说他不是一个用于读取端口的输入数据,此函数一般用于输出模式下,看自己输出的是什么,GPIO_ReadOutputData 读取整个输出寄存器。
(6)GPIO_SetBits、GPIO_ResetBits、GPIO_WriteBit、GPIO_Write为GPIO的写入函数。 GPIO_SetBits可以把指定的端口设置为高电平,GPIO_ResetBits可以把指定端口设置为低电平,GPIO_WriteBit前两个参数是指定端口,第三个BitValue是根据值来设定指定端口,GPIO_Write第一个参数是选择外设,第二个参数是PortValue,这个函数可以同时对16个端口进行写入操作。
(7)GPIO_PinLockConfig用来锁定GPIO配置,调用这个函数,参数指定某个引脚,那此引脚的配置就会被锁定,防止意外更改,这也是GPIO函数。
(8)其余放到AFIO配置中讲。
在这里插入图片描述

6.程序实现

把delay函数模块放到新建System
在这里插入图片描述
在这里插入图片描述

7.点亮LED流水灯

在这里插入图片描述
在这里插入图片描述
上图时按位或把7个端口全部配置为推免模式,当然也可以用ALL(下图),把16为全部配置为推免模式
在这里插入图片描述在这里插入图片描述

8.蜂鸣器

在这里插入图片描述
在这里插入图片描述

9.使用库函数的方法(小结)

第一种先打开.h文件的最后,看有哪些函数,然后右键函数转到定义,查看函数和参数的用法。
第二种打开资料里的参考文档,库函数用户手册。
第三种百度搜索,参考他人代码。


三、GPIO输入

1.按键

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

按键抖动:由于按键内部使用的是机械式弹簧片来进行通断的,所以在按下和松手的瞬间会伴随有一连串的抖动。需要增加一个过滤,消除这种抖动,最简单的办法就是加一段延时,把这段时间耗过去,这样就没问题了。关于施密特触发器有两个阈值能否用来来消除抖动,不可靠,施密特触发器是整形,抖动的电平有可能会在抖动期内高于施密特触发器的高阈值,或低于触发器的低阈值,不能完全避免被误判的情况。
在这里插入图片描述

2.传感器模块

传感器模块:传感器元件(光敏电阻/热敏电阻/红外接收管等)的电阻会随外界模拟量的变化而变化,通过与定值电阻串联分压即可得到模拟电压输出,对电路来说,检测电压非常容易了,再通过电压比较器进行二值化即可得到数字电压输出。
在这里插入图片描述
N1为传感器所代表的可变电阻,阻值可以根据环境的光线、温度等模拟量进行变化;

R1为和N1进行分压的定制电阻,R1与N1串联,一端接VCC正极,一端接GND负极,构成基本的分压电路;

C2为一个滤波电容,给中间的电压输出进行滤波,用来滤除一些干扰,保证输出电压波形平滑。一般在电路中遇到一端接在电路中,一端接地的电容,都可以考虑一下这是不是滤波电容的作用,若是滤波电容,那么这个电容就是用来保证电路稳定的,并不是电路的主要框架,这时候在分析电路的时候,就可以先把这个电容抹掉,方便分析电路。

利用分压定理分析传感器电阻的阻值变化对输出电压的影响,当然还可以用上下拉电阻的思维来分析。当N1阻值变小时,下拉作用就会增强,中间的AO端的电压就会拉低,极端情况下,N1阻值为0,AO输出被完全下拉,输出0V;当N1阻值变大时,下拉作用就会减弱,中间的AO端由于R1的上拉作用,电压就会升高,极端情况下,N1阻值无穷大,相当于断路,AO输出被R1上拉,输出VCC。

关于上拉下拉理解,AO端相当于一根杆子,R1上拉电阻相当于拴在屋顶的弹簧,将杆子往上拉,N1下拉电阻相当于拴在地面的弹簧,将杆子往下拉,电阻阻值越小,弹簧弹性越小,拉力越强,杆子的高度相当于电路中的电压,若只有上拉弹簧或者下拉弹簧,杆子肯定被拉到了屋顶或者地面,在电路中就相当于中间点的电压为VCC或者GND,两弹簧相互拉扯的时候,中间的输出端就会向拉力强的一端偏移,至于便宜多少,取决于两个弹簧之间的弹力之差了,弹力一致,杆子会处于居中的位置。

AO电压仅需两个电压分压即可得到。这个模块还支持数字输出,数字输出就是对AO进行二值化输出,这里二值化是通过芯片LM393来完成,LM393是一个电压比较器芯片(运算放大器),内有两个独立的电压比较器电路,剩下的是VCC和GND供电。运算放大器同向输入端的电压大于反向输入端的电压时,输出就会瞬间升高为最大值也就是输出接VCC;反之,当运算放大器同向输入端的电压小于反向输入端的电压时,输出就会瞬间降低为最小值也就是输出接GND。这样就可以对一个模拟电压进行二值化。IN+接AO端(模拟电压端),IN-接一个电位器,这个电位器的接法也是分压电阻的原理,拧动电位器,IN-就会生成一个可调的阈值电压,两个电压进行比较,最终输出结果就是DO,数字电压输出。

指示灯电路,左边的是电源指示灯,通电就亮;右边是DO输出指示灯,他可以指示DO的输出电平,低电平点亮,高电平熄灭;DO还接了个R5上拉电阻,这是为了保障默认输出为高电平。

3.单片机按键用法

按键一般采用下接的方式,这个原因与前文LED的接法类似,是电路设计的习惯和规范。
在这里插入图片描述
第一个图是按键最常用的接法,但K1按下时,PA0被直接下拉到GND,此时读取PA0口的电压就是低电平;按键松手时,PA0被悬空,悬空会出现引脚的电压不确定,所以在这种接法下,必须要求PA0是上拉输入(IPU)的模式,此时引脚悬空,PA0就是高电平,否则就会出现引脚电压不确定的错误现象。这种情况,按下按键为低电平,松手为高电平。

第二个图,相比第一个图,外部接了一个上拉电阻,此时PA0引脚可以配置为浮空输入(IN_FLOATING)或者上拉输入(IPU),若配置成上拉输入,那就是内外两个上拉电阻共同作用,此时高电平会更强一些,对应高电平就更加稳定。但是此时当引脚被强行拉到低时,损耗也就会大一些。
在这里插入图片描述
第三个图,PA0通过按键接到3.3V,此时要求PA0必须配置成下拉输入(IPD)模式。按键按下时,引脚为高电平,松手时,引脚回到默认低电平。一般单片机可能没有下拉输入模式,所以最好还是用上面一、二的接法。
第四个图,外加一个下拉电阻,此时PA0引脚可以配置为浮空输入(IN_FLOATING)或者下拉输入(IPD)。

4.单片机所用C语言知识

在这里插入图片描述
uint8_t无符号8位整型数据,int8_t 8位整型数据。推荐使用stdlint关键字定义。
在这里插入图片描述
在这里插入图片描述
对变量类型重命名,使用typedef,它会检查是否是变量类型。
在这里插入图片描述
对于C语言的数据来说,主要就是两个功能,一个是定义数据,一个是引用数据。结构体也是数据类型,那么应跟其他数据类型类似,那应该也分为定义和使用。结构体引用有两种:
(1)结构体变量名.结构体成员名;
(2)结构体指针名(即p结构体变量名)->结构体成员名。
为什么要用指针的方式,是因为结构体是一种组合数据类型,在函数之间的数据传递中,通常用的是地址传递,而不是值传递(值传递消耗内存,复制的是内容,指针相当于直接操作指向地址内存,快)。指针传递,子函数得到的就是结构体的首地址,这时可以用->运算符快速引用结构体成员。
在这里插入图片描述
枚举也是数据类型,同样也是两个问题,定义于引用。定义后EnumName只能取FALSE或TRUE。若不,将会警告:enumerated type mixed with another type 枚举中混入其他类型。EnumName也不能取0,若要用0,必须加上类型强转,“EnumName a;a = (EnumName)0;”。


5.按键控制LED接线图

在这里插入图片描述


6.代码封装

如果把驱动代码混在主函数里,会比较乱,不容易管理,也不容易移植,所以需要把驱代码封装起来,单独放在另外的.c和.h里面,这就是模块化编程的方式。操作方式为:
(1)打开工程文件夹,新建一个Hardware,用来存放硬件驱动,回到keil,点击三个箱子,打开工程管理,新建一个Hardware组。
在这里插入图片描述
在这里插入图片描述
点击魔术棒按钮,线c/c++,把新建的Hardware文件夹添加到头文件路径列表中。
在这里插入图片描述
右键添加新的C文件与h文件,命名均为LED,此文件用于封装LED的驱动程序,路径也要修改过来。.c文件用来存放驱动程序的主体代码;.h用来存放这个驱动程序可以对外提供的函数或变量的声明。
在这里插入图片描述
在.c文件,右键#include "stm32f10x.h"头文件。
在这里插入图片描述
在.h文件,添加一个防止头文件重复包含的代码,格式都是固定的。“#ifndef __LED_H”如果没有定义这个字符串,那么定义“#define __LED_H”。最后加上#endif,这个与#ifndef组成的括号,的函数和变量声明就放在这个括号里。最后以空行结尾。
在这里插入图片描述


7.按键控制LED程序

封装LED代码,打开.c文件。
在这里插入图片描述
这个函数需要被外部引用,所以先复制第一行,放到.h文件里,后加分号。这样就是对模块外部声明,这个函数是可以被外部调用的函数。
在这里插入图片描述
回到main.c。
在这里插入图片描述
定义led1和2的开关函数。
在这里插入图片描述
声明开关函数。
在这里插入图片描述
主程序调用。
在这里插入图片描述
编写按键用法,在Hardware文件下增加Key.c和Key.h文件,并修改路径。
在这里插入图片描述
套路一样。
按下按键1点亮,按下按键2熄灭
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
想实现按一下点亮,按一下熄灭,则需要在LED驱动代码加上红框中的代码。
在这里插入图片描述
在这里插入图片描述

驱动模块写好后,在上面加上注释,说明函数的用法、参数的取值和返回值的这些东西。


8.光敏传感器控制蜂鸣器

遮住光时,输出指示灯灭,输出高电平;有光时,输出指示灯亮,输出低电平
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


总结

初始化时钟,定义结构体、赋值结构体、初始化GPIO外设,利用8个读写函数。驱动函数要做好封装,方便分工合作,多人写驱动,一人负责主函数。

  • 5
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZRob

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值