GPIO外设输入功能
- 输入部分硬件电路
- 按键简介
- 传感器模块简介
- 按键和传感器模块的硬件电路
- C语言的学习
- C语言数据类型
- 宏定义
- typedef
- 结构体
- 枚举
- C语言知识总结
- 按键控制LED灯&光敏传感器蜂鸣器
- GPIO总结
- GPIO使用方法总结
- 模块化编程的方法:
两个程序:按键控制LED,光敏传感器控制LED灯
GPIO输入部分直接从外部硬件设备开始
C语言的相关知识点
C语言的数据类型、宏定义、typedef、结构体和枚举 知识点,是库函数里经常出现的东西,了解这些有利于对库函数的执行逻辑更加清晰明了。C语言的if else、加减乘除最基本的可以自学我的C++部分
同时C语言的指针。对于指针的含义和方法,这是需要熟练掌握的
输入部分硬件电路
按键简介
抖动通常在5-10ms之间,人分辨不出来,但对于高速运行的单片机而言,这就漫长了。所以要对抖动进行过滤,否则会出现按键按一下,单片机反映了多次的现象。按键松手时也会有一段抖动。
最简单的方法是软件加一段延时,把抖动时间耗过去
传感器模块简介
这些传感器都是利用传感器元件,
比如光线越强,光敏电阻的阻值越小
温度越高,热敏电阻的阻值越小
红外光线越强,红外接收管的阻值越小。但是电阻的变化不易被直接观察,所以一般通过分压来输出
N1代表可变电阻(相对于各传感器,可以对应为光敏电阻、热敏电阻、红外接收管)
上下拉电阻在单片机中经常出现,弱上拉、弱下拉、强上拉、强下拉。这里强弱指的是电阻阻值的大小,弱上拉的电阻阻值大,驱动能力弱一点,上拉下拉指接到VCC、GND。
分到的模拟电压表示强度,
二值化比较通常用于检测通断,所以IN+阈值不需要过多的调整
按键和传感器模块的硬件电路
下接按键的方式(常用下接方式,这是电路设计的习惯和规范),上接按键的方式
第一个图最常用的按键接法(需要上拉输入的模式,默认高电平,否则会出现引脚电压不确定的错误现象)
第二个图外部接了上拉电阻(可以配置成浮空输入或者上拉输入,如果是上拉输入,那就是内外两个上拉电阻共同作用,这时高电平更强一些,对应的高电平更加稳定,但当引脚被强行拉低时,损耗也会大一些)
第三个图(需单片机引脚可以配置成下拉输入模式,一般的单片机可能不一定有下拉输入模式,所以用的不多)
第四个图(下拉输入模式或者浮空输入模式)
C语言的学习
C语言数据类型
在51单片机中int是16位的,而在STM32单片机中,int是32位的。和51的不同之处,不要弄混了
右边是C语言的stdint.h文件和ST对这些变量的重命名。因为左边的名字比较长,而且int的位数根据系统的不同还有可能不一样,还有char本义字符型意思,按名字来说应该存放字符,但是我们用单片机来存放整数而不是字符。
综合以上原因,C语言和ST就给这些变量换了个名字。C语言提供的有stdint.h这个头文件,使用了新的名字
int8_t就是char的新名字,表示意思就是8位整数数据。
-t表示这个用typedef重名命名的变量类型,
unsigned char 的新名字就是uint8_t,意思是无符号8位整型数据
等
代表的就是前面的数据类型,只是换了一个名字而已。
s8、u8、s16 、u16、就是ST库函数以前用的名字
新版库函数仍然支持这些写法,明确了这是库函数老的类型,是为了传统目的保留的(兼容老版本)
推荐使用stdint关键字的定义,因为是新版库函数使用的方式,也是C语言stdint.h头文件提供的官方定义
宏定义
此处就是用ABC 直接替代 12345
如:#define GPIO_Pin_10 ((uint16_t)0x0400)
GPIO_Pin_10 替换的就是 ((uint16_t)0x0400),加了强制类型转换,是为了严谨性考虑的,目前我们暂时不用管。这里0x0400表示第10号口,但这不方便理解,所以用宏定义将10号口改一下名字,叫GPIO_Pin_10。
宏定义除了替换之外,还有其他的用法,可以后期再学。
typedef
用途和宏定义差不多,
区别:宏定义的新的名字在左边,typedef的新名字在右边。(原来的名字也可以用)
宏定义不需要分号,typedef后面必须加分号。
宏定义任何名字都可以换,而typedef只能专门给变量类型换名字
所以宏定义的改名范围更宽,只不过对于变量类型重命名而言,使用typedef更加安全。
宏定义无脑改名,typedef会对命名进行检查,如果不是变量类型的名字,就报错
结构体
组合数据类型,一大堆基本数据类型的集合,数组只能组合相同类型的数据,想组合不同类型的数据就用结构体
变量就是定义数据和引用数据的
所以struct 要{}来打包变量,此处就是定义一个结构体变量StructName,包含了char 型的x,int型的y,float型的z
定义数据:
int a; 定义一个int型数据叫a,
int b[5]; 定义一个5个int型数据的数组
struct c; 定义一个结构体类型,叫c
但是既然是不同类型的数组组合,就需要告诉编译器是哪些数据的组合,所以还得加一个附加说明,告诉它是哪些数据,在struct后面加一个花括号,要打包哪些变量
struct{char x;int y;float z;} c; 这才是结构体的完整定义
定义一个结构体变量,名字叫c,其中包含了char 型的x,int型的y,float型的z3个子项
引用数据:
a=66;
b[0]=66; 数组名b加上方括号取索引,比如第0个元素等于66,
引用c 需要用结构体的名字c,然后用.运算符取索引,索引不是0,1,2,3,4,5了,而是结构体子项的名字,比如c.x=‘A’; c.y=66; c.z=3.14;
结构体成员多,一般会换行来写。
结构体的特殊用法
如果每次定义都写 struct{char x;int y;float z;} 这么一大串,那就麻烦,所以typedef的作用就出来了
typedef struct{char x;int y;float z;} StructName; 用StructName来替换前面的一长串,看上去就简单多了。
另一种引用方式:
pStructName->x=‘A’; pStructName是结构体的首地址,也就是结构体指针,加上->运算符,再加结构体成员名
加结构体指针的引用方式的原因:结构体是一种组合数据类型,在函数的数据传递过程中,通常用地址传递而不是值传递(地址传递、值传递在指针讨论 用法和利弊)
比如这个地方,结构体变量在传给GPIO_Init函数时,传递的是结构体的指针
对应GPIO_Init函数用结构体指针来接收,里面再引用结构体成员的时候,就可以使用->这个符号来引用
也可以用*号引出指针变量的内容,再用.点来引用结构体也是可以的,只不过用->这个运算符号更加方便
这个结构体就是一个数据打包的过程,首先将参数写到结构体的这3个变量里,然后统一打包,将结构体传递到函数里,函数里面再把这个结构体拆包出来,读取变量。这就是使用结构体的过程。(当需要参数很多时不方便管理就需要结构体了)
枚举
比如定义一个变量存储星期的值,那理论上取值是1-7,如果定义的整形变量,那变量任意存什么数都行,不会收到限制,这时可能出现数据不合法。比如星期8的出现,如果需要程序更加安全,就可以定义一个取值受限制的整形变量:枚举
也可以当成宏定义的集合:
定义和结构体差不多,这里是enum,然后是变量名字,中间是花括号,但注意里面是逗号,隔开
这样可以限制变量的取值范围,只能取花括号里面的定值。
另外,里面的定义,如果按照顺序累加的,那后面的幅值可以省略,编译器会自动添加上去。
同时也可以用typedef改一下名
比如:
typedef enum{
MONDAY=1,
TUESDAY,
WEDNESDAY
} week_t;
week_t week;
week MONDAY; //week = 1;
week = 8; //就会编译器报警:枚举里混入了其他变量
int a=MONDAY; //这样的用法和宏定义就差不多了,所以说枚举也是一个宏定义的集合,就是这个意思。
C语言知识总结
实际都不是C语言最根本的语法,没有这些东西照样可以完成功能,这些创造出来是为了更好地管理工程。虽然表面上多此一举,但是工程复杂起来后,使用这些知识可以使编程更加快捷、更容易理解,更不容易出错。
按键控制LED灯&光敏传感器蜂鸣器
对于驱动代码而言,一般都会封装起来,单独放在另外的.c和.h文件里。(模块化编程的方式)
led.c文件存放驱动程序的主体代码,led.h用来存放驱动程序可以对外提供的函数或变量的声明
led.c的第一行,需要添加 stm32f10x.h文件
led.h要添加一个防止头文件重复包含的代码
这里面的函数代码思想值得学习。这8个函数都值得学习
在编写代码时,如果不显示代码提示的话,可以按一下快捷键ctrl+Alt+空格,这样就可以弹出代码提示框了
在主函数里面,代码变得更加简介,初始化就是初始化,开灯就是开灯,关灯就是关灯,不需要再管底层的各种参数了。这就是模块化编程的好处。
GPIO_ReadInputDataBit 读取输入数据寄存器某一个端口的输入值的 参数:GPIOx,GPIO_Pin 用来指定某一个端口,返回值是uint8_t,代表这个端口的高低电平
GPIO_ReadInputData 读取整个输入数据寄存器的 参数GPIOx 用来指定外设,返回值uint16_t,是16位的数据,每一位代表一个端口值
GPIO_ReadOutputDataBit 读取输出数据寄存器某一个端口的输入值的 参数:GPIOx,GPIO_Pin 用来指定某一个端口,返回值是uint8_t,代表这个端口的高低电平(原则上来说,并不是用来读取端口的输入数据的,一般用于输出模式下,用来看一下自己的输出是什么)
GPIO_ReadOutputData 读取整个输出数据寄存器的
下面是读取的对应关系
变量全局变量,局部变量,定义域不一样。名称相同但是强龙不压地头蛇。
函数写好后,尽量在函数上方加一些注释,说明函数的用途,参数的取值和返回值的意思。时间久了自己看起来也知道,别人看也容易理解。
GPIO总结
GPIO使用方法总结
首先初始化时钟,然后定义结构体,赋值结构体,
GPIO_MODE 可以选择8中输入输出模式
GPIO_Pin 可以选择引脚,可以用按位或的方式同时选中多个引脚
GPIO_Speed 选择输出速度,要求不高的话直接使用50MHz即可
最后使用GPIO_Init函数,将指定的GPIO外设初始化好
然后是8个读取和写入的函数,读写主要用这些函数即可。
模块化编程的方法:
自己做产品外围硬件比较多,这时候就尽量把每个硬件的驱动函数单独提取出来,封装在.c和.h文件里,这样有利于简化主函数的逻辑。主函数里面有更重要的任务要完成,不要让驱动函数混在主函数里面。另外把硬件驱动提取出来,也有利于我们移植程序,还有利于进行分工合作,比如让别人来写驱动函数。主要的精力就可以集中在主函数的逻辑上了。
最后既然要做封装,函数的注释就要写清楚,这样方柏霓使用这个模块的人快速上手这些函数。