# 按键简介
按键:常见的输入设备,按下导通,松手断开
按键抖动:由于按键内部使用的是机械式弹簧片来进行通断的,所以在按下和松手的瞬间会伴随有一连串的抖动
# 传感器模块简介
传感器模块:传感器元件(传感器模块就是利用传感器元件,比如如下图的光敏电阻 / 热敏电阻 / 红外接收管等)的电阻会随外界模拟量的变化而变化(比如光线越强,光敏电阻的阻值就越小),通过与定值电阻进行串联分压即可得到模拟电压输出,再通过电压比较器进行二值化(二值化就是要么是高要么是低)即可得到数字电压输出
如下为传感器模块的基本电路,详细介绍。
这个 N1 就是传感器元件所代表的可变电阻,它的阻值可以根据环境的光线、温度等模拟两进行变化
N1 上面的 R1,是和 N1 进行分压的定值电阻,R1 和 N1 串联,一端接 VCC 一端接 VSS,这就构成了基本的分压电路,AO 电压就由 R1 和 N1 两个电阻的分压得到。
AO 电压就由 R1 和 N1 两个电阻的分压得到。
N1 左边的 C2 是一个滤波电容,它是为了给中间的电压输出进行滤波的,用来滤除一些干扰,保证输出电压波形的平滑。一般我们在电路中遇到一端接到电路中,另一端接地的电容,都可以考虑一下是不是滤波电容的作用,并不是电路的主要框架,这时候我们进行分析电路时,就可以先把这个电容抹掉,这样就可使我们的电路分析更加简单。
二值化输出是通过这个 LM393 芯片来完成,是一个电压比较器芯片,里面由两个独立的电压比较器电路,然后剩下的是 VCC 和 GND 供电,里面电容是一个电源供电的滤波电容,这个电压比较器其实就是一个运算放大器,当同向输入端的电压大于反向输入端的电压时,输出就会瞬间升高为最大值也就是输出接 VCC;反之当同向输入端的电压小于反向输入端的电压时,输出就会瞬间降为最小值,也就是输出接 GND,这样就可以对一个模拟电压进行二值化了,DO 就是最后数字电压的输出。
可以用上下拉电阻的思维分析传感器电阻的阻值变化对输出电压的影响,如下:
AO 这个输出端可以把它想象成一个水平杆子(下图红色直线),R1 上拉电阻相当于拴在上方的弹簧,将杆子向上拉,N1 下拉电阻相当于拴在地面的弹簧,将杆子向下拉;电阻的阻值越小,弹簧的拉力就越强,杆子的高度就相当于电路中的电压,杠子向拉力强的一端偏移(取决于两个弹簧的弹力之差);如果上下弹簧拉力一致,杆子处于居中位置也就是电路输出 VCC/2 的电压;如果上面的阻值小,拉力强,输出电压就会变高;反之下面的阻值小,输出电压就会变低 ;如果上下拉电阻的阻值都为 0,就是两个无穷大的力在对抗,在电路中呈现的就是电源短路(应该避免)。单片机电路中会常出现这种上拉下拉电阻,比如弱上拉,强上拉等(强和弱就是指电阻阻值的大小,也就是这个弹簧拉力大小) ,最终输出电压就是在弹簧拉扯下最终杆子的高低。
# 按键和传感器硬件电路
下接按键的方式如下,一般来说我们用下接按键的方式,这个原因和 LED 的接法类似,是电路设计习惯和规范;下左图中,按键按下时,PA0 直接下拉到 GND,此时读取 PA0 口的电压就是低电平,在这种接法下,必须要求 PA0 是上拉输入模式,使按键松下,还是高电平。下右图,外部接了一个上拉电阻,当按键松手时,引脚由于上拉作用,保持为高电平,此时 PA0 引脚就可以配置为浮空输入或者上拉输入。
上接按键的方式(仅了解)如下,左图 1 中,要求将 PA0 必须配置成下拉输入模式,松手时,引脚会回到默认值低电平。
传感器模块电路如下,DO 是数字输出端口,PA0 用于读取数字量。
# 嵌入式 C 语言基础
# C 语言数据类型
关键字 | 位数 | 表示范围 | stdint | ST |
---|---|---|---|---|
char | 8 | -128 ~ 127 | int8_t | s8 |
unsigned char | 8 | 0 ~ 255 | uint8_t | u8 |
short | 16 | -32768 ~ 32767 | int16_t | s16 |
unsigned short | 16 | 0 ~ 65535 | uint16_t | u16 |
int | 32 | -2147483648 ~ 2147483647 | int32_t | s32 |
unsigned int | 32 | 0 ~ 4294967295 | uint32_t | u32 |
long | 32 | -2147483648 ~ 2147483647 | ||
unsigned long | 32 | 0 ~ 4294967295 |
单片机中用 char 也就是 int8_t 来存放整数而不是字符等,有一定的区别需要注意。
C 语言提供的 stdint 头文件,使用新的名字。比如 int8_t 就是 char 的新名字,表示的意思就是 8 位整型的数据,右边加个_t 表示这是用 typedef 重新命名的变量类型。
ST 关键字是老版本用的。
# C 语言宏定义
前为新,后为旧
关键字: #define
用途:用一个字符串代替一个数字,便于理解,防止出错;提取程序中经常出现的参数,便于快速修改
// 定义宏定义 | |
#define ABC 12345 | |
// 引用宏定义 | |
int a = ABC; // 等效于 int a = 12345;` |
# C 语言 typedef
前为旧,后为新
typedef 只能专门给变量类型换名字,更加安全(不是变量类型的名字是不行的);所以宏定义的改名范围更宽
关键字: typedef
用途:将一个比较长的变量类型名换个名字,便于使用
// 定义 typedef | |
typedef unsigned char uint8_t; | |
// 引用 typedef | |
uint8_t a; // 等效于 unsigned char a; |
# C 语言结构体
结构体也是一种数据类型,比如 char
、 int
等是基本数据类型;数组是由许多相同基本数据类型的组合;与数组一样,但是若组合不同的数据类型就用结构体。
关键字: struct
用途:数据打包,不同类型变量的集合
// 定义结构体变量 | |
struct { | |
char x; | |
int y; | |
float z; | |
} StructName; |
StructName
是结构体变量的名字
因为结构体变量类型较长,所以通常用 typedef
更改变量类型名
// 引用结构体成员 | |
StructName.x = 'A'; | |
StructName.y = 66; | |
StructName.z = 1.23; |
或是用结构体指针方式,因为结构体是一种组合数据类型,在函数之间的数据传递中,通常用的是地址传递而不是值传递。
杠大于号 表示结构体成员名
pStructName->x = 'A'; //pStructName 为结构体的地址 | |
pStructName->y = 66; | |
pStructName->z = 1.23; |
代码理解如下
struct c; | |
// 定义了一个结构体类型,名字叫 c,这是不完整的,还需加一个附加声明 {打包的变量},如下 | |
struct {char x; int y; float z} c; | |
// 定义一个结构体变量,名字叫 c,其中包含 char 型的 x,int 型的 y 和 float 型的 z 三个子项 | |
c.x = 'a'; | |
c.y = 66; | |
c.z = 1.23; | |
// 结构体的引用,需写结构体名字 c,然后用运算符取索引,索引是结构体子项的名字,如 结构体名称。结构体子项 |
使用 typedef
解决结构体名字太长的问题
struct {char x; int y; float z} c; | |
struct {char x; int y; float z} d; | |
// 再定义 d,结构体数据类型就太长了 | |
typedef struct {char x; int y; float z} struct_t; | |
// 使用 typedef 定义一个新名字叫 struct_t,再定义结构体时,就用这个新名字,如下 | |
//typedef struct {char x; int y; float z} struct_t; 这个太长,写成下面这个格式 | |
typedef struct { | |
char x; | |
int y; | |
float z | |
} struct_t; | |
// 如下为结构体的数据类型 | |
struct { | |
char x; | |
int y; | |
float z | |
} | |
// 使用 typedef 将结构体换了个名字叫 struct_t(结构体的数据类型),之后用 struct_t 来进行结构体的定义 | |
typedef struct { | |
char x; | |
int y; | |
float z | |
} struct_t; | |
struct_t c; //struct_t 是结构体数据类型名字,c 是结构体变量的名字 | |
struct_t d; | |
// 使用 typedef 定义后的新名字来定义结构体,struct_t 是结构体数据类型,c 是结构体变量的名字 | |
c.x = 'a'; | |
// 使用结构体变量的名字,然后用点,来引出结构体成员的数据,这样就可以进行数据的写入和读取了 |
# C 语言枚举
和结构体差不多,也是一个数据类型。只能在它给定的参数列表里赋值,不能赋其它的值。
枚举值也不是必须赋值给枚举变量的,也可以赋值给随意一个变量。所以说枚举也是一个宏定义的集合。
关键字: enum
用途:
- 定义一个取值受限制的整型变量,用于限制变量取值范围(比如我们定义一个变量用来存储星期的值,理论上这个变量只能取 1 到 7 的值,若定义的是整形变量这时可能会出现数据不合法,比如星期 8 的情况出现,所以这时候需要定义一个取值受限制的整形变量,这个变量就是枚举);
- 宏定义的集合
定义枚举变量:
enum{FALSE = 0, TRUE = 1} EnumName; // 需要用逗号,限制 EnumName 的取值范围 |
因为枚举变量类型较长,所以通常用 typedef 更改变量类型名:
typedef enum{FALSE = 0, TRUE = 1} EnumName_t |
使用新的名字 EnumName_t
来定义枚举变量,命名为 EnumName
:
EnumName_t EnumName
只能引用下面两种:
EnumName = FALSE; // EnumName = 0 | |
EnumName = TRUE ; // EnumName = 1 |
引用枚举成员: EnumName = FALSE; EnumName = TRUE;
# 其它
对于 C 语言来说,主要由两个功能,一个是定义数据,一个是引用数据。
数组的引用如下:
int b[5]; | |
// 是定义了一个 5 个 int 型数据的数组,名字叫 b | |
b[0] = 11; | |
// 数组的引用是数组名 b,加上方括号取索引;数组的第 0 个元素等于 11 | |
b[1] = 66; |