【笔记】C51程序学习笔记
发表于|更新于
|阅读量:
前言
Windows上标准51单片机的C51学习笔记
注意:本文所有代码均按照sdcc编译器代码规范编写,如果使用的Keli编译器,需要自行修改代码
存储空间的访问
- 因为RAM容量较小,所以如果有大量数据需要存储(例如数组),可以通过
code
关键字强制将变量的值存入ROM
- 如果使用的是增强型51单片机(例如STC12C5A60S2单片机),可以通过
xdata
关键字强制将变量的值存入外扩内存
操作2进制位
- 在操作2进制位时,如果需要改变2进制位,通常不使用算术运算符,通常使用按位运算符
指定位为0
- 任意位与0相与,结果不变
- 任意位与1相与,结果为0
举例:将末位置0
指定位为1
- 任意位与0相或,结果不变
- 任意位与1相或,结果为1
举例:将末位置1
指定位取反
- 任意位与0异或,结果不变
- 任意位与1异或,结果取反
举例:将末位置取反
固定格式
1 2 3 4 5 6 7 8 9
| #include<reg52.h>
void main(void) { while (1) { ... } }
|
引入头文件
为引脚赋值
- I/O端口都是可以位寻址的,所以既可以按字节赋值,也可以按位赋值
按字节赋值
按位赋值
循环语句提升效率
- 循环要以递减的方式迭代。在编译成汇编代码后,递减的方式迭代要比递增的方式迭代执行效率更高
1 2 3 4 5 6
| int i = 100; while (i > 0) { ... i--; }
|
1 2 3 4
| for (int i = 100; i > 0; i--) { ... }
|
发光二极管
拉电流和灌电流
拉电流
- 拉电流时
- 输入为1时,发光二极管亮灯
- 输入为0时,发光二极管不亮灯
- 拉电流为200微安
1 2
| 单片机引脚 <- 发光二极管正级 发光二极管负级 -> 限流电阻 -> 电源负级
|
灌电流
- 灌电流时
- 输入为0时,发光二极管亮灯
- 输入为1时,发光二极管不亮灯
- 灌电流为20毫安,所以通常使用灌电流,才能让发光二极管达到正常亮度
1 2
| 单片机引脚 <- 限流电阻 <- 发光二极管负级 发光二极管正级 -> 电源正级
|
软件延迟函数
- 通过600~700次的空循环,可以实现延迟1ms左右
1 2 3 4 5 6
| void delay(unsigned int ms) { unsigned int j; while (ms--) for (j = 600; j > 0; j--); }
|
通过延迟实现闪烁
- 频率越高闪烁越快
- 每1s内,实现1次亮和1次灭,这样的频率为1Hz
- 因为人眼有余晖时间,所以人眼可识别的最大闪烁频率为50Hz
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #include<8052.h>
void delay(unsigned int ms) { unsigned int j; while (ms--) for (j = 600; j > 0; j--); }
void main(void) { while (1) { unsigned int time = 1; unsigned int count = 50; unsigned int delay_time = 1000 * time / 2 / count; P1_0 = 0; delay(delay_time); P1_0 = 1; delay(delay_time); } }
|
通过调整占空比改变亮度
- 亮度的决定因素:发光二极管闪烁时,亮的时间越大,灭的时间越小,亮度越亮
- 占空比:灭的时间 / 亮的时间 + 灭的时间
- 当亮和灭的时间间隔发生改变时,占空比就会改变,发光二极管亮度就会改变
- 50Hz在1秒内的的两次间隔之和为20ms,所以能产生19级亮度
- 当亮1ms,灭19ms,亮度最暗
- 当亮19ms,灭1ms,亮度最亮
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #include<8052.h>
void delay(unsigned int ms) { unsigned int j; while (ms--) for (j = 600; j > 0; j--); }
void max() { P1_0 = 0; delay(19); P1_0 = 1; delay(1); }
void mim() { P1_0 = 0; delay(1); P1_0 = 1; delay(19); }
|
STC12C5A60S2的特殊功能寄存器
引入头文件
传送门
1
| #include<STC12C5A60S2.h>
|
切换端口工作模式
工作模式
工作模式 |
M1寄存器的值 |
M0寄存器的值 |
备注 |
标准双向口模式(缺省值) |
0 |
0 |
灌电流为20毫安,拉电流为200微安 |
强推挽输出模式(强推模式) |
0 |
1 |
灌电流为20毫安,拉电流提升为20毫安 |
开漏模式 |
1 |
0 |
- |
高阻模式 |
1 |
1 |
- |
设置方法
- 每个位可以单独设置工作模式,由2个寄存器对应位的值决定
1 2
| P0M1 = 0x00; P0M0 = 0x00;
|
按键
按键是一个输入外设
按键按下时,输入为0
按键没有按下时,输入为1
软件消抖
- 按键按下和抬起时,会有抖动现象产生,抖动现象通常会在按下和抬起的10ms~20ms之间发生
- 软件消抖通过利用软件延迟函数实现
- 软件消抖的效果
- 按下消抖效果:按下时进入循环,此时通过延迟10ms,解决10ms之内按下瞬间出现反复按的情况。
- 抬起消抖效果:如果按住并且一直没抬起,此时会进入无限循环,也就是不会做处理。抬起时通过延迟10ms,解决10ms之内按下瞬间出现反复按的情况
按下后执行业务
P0_0
:按键的连接点
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<8052.h>
void delay(unsigned int ms) { unsigned int j; while (ms--) for (j = 600; j > 0; j--); }
void main(void) { while (1) { while (!P0_0) { delay(10); if (!P0_0) { ... } while (!P0_0); delay(10); }
} }
|
抬起后执行业务
P0_0
:按键的连接点
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
| #include<8052.h>
void delay(unsigned int ms) { unsigned int j; while (ms--) for (j = 600; j > 0; j--); }
void main(void) { while (1) { while (!P0_0) { delay(10); while (!P0_0); delay(10); ... }
} }
|
按键检测函数
- 为每一个按键按下赋予键值,在主函数中通过判断减值,实现指定业务
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
| #include<8052.h>
void delay(unsigned int ms) { unsigned int j; while (ms--) for (j = 600; j > 0; j--); }
unsigned char key_scan(void) { unsigned char key_num = 0; if (!P0_0 || !P0_1) { delay(10); if (!P0_0) key_num = 1; if (!P0_1) key_num = 2; while (!P0_0 || !P0_1); delay(10); } return key_num; }
void main(void) { while (1) { switch (key_scan()) { case 1: // 业务代码 break; case 2: // 业务代码 break; } } }
|
七段数码管
数码管的引脚一般都在上下,共阴和共阳是由内部决定的
共阳数码管(灌电流)点亮:位信号(com端)接高电平,段信号(a、b、c、d、e、f、g、dp端)接低电平
- 本案例接线方式
- 位信号1接P0.3、…、位信号4接P0.0
- 段信号a接P1.0、…、段信号g接P1.6、段信号dep接P1.7
共阴数码管(拉电流)点亮:位信号(com端)接低电平,段信号(a、b、c、d、e、f、g、dp端)接高电平
不应该将位信号同时点亮,如果需要全部点亮每个位,可以采用动态刷新技术
不同位数的七段数码管
- 1位七段数码管一共有10个引脚
- 2个com端(这两个引脚内部是想通的)
- a(上)、b(右上)、c(右下)、d(下)、e(左下)、f(左上)、g(中)、dp(小数点)端
- 2位七段数码管一共有10个引脚
- com1端、com2端
- a(上)、b(右上)、c(右下)、d(下)、e(左下)、f(左上)、g(中)、dp(小数点)端
- N位七段数码管一共有
(8+N)
个引脚
- com1端、…、comN端
- a(上)、b(右上)、c(右下)、d(下)、e(左下)、f(左上)、g(中)、dp(小数点)端
全部点亮
- 通过动态扫描(刷新),实现全部点亮检查坏点:通过快速切换不同位上的数字,实现现实多个位上的数字扫描
- 因为人眼能识别的最大闪烁频率为50Hz,所以为了骗过人眼,需要将所有数字在20ms内完成闪烁
- 七段码:每个数字位用于显示数字的16进制值,16进制数的每一位决定哪一个笔划亮灯
- 从com端到a~dep端如果是灌电流,则com端输出0时,数码管指定位亮灯
- 选位:选位阶段需要指定哪个位亮
- 送段:送出段信号阶段需要指定亮哪个数字
- 延迟:如果是4位数码管,每个位的延迟应设置为5ms
- 灭段:为了解决拖影(拖尾)现象,将亮灯的数字熄灭
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
| #include<8052.h>
__sfr __at (0x94) P0M0; __sfr __at (0x93) P0M1; __sfr __at (0x92) P1M0; __sfr __at (0x91) P1M1;
void delay(unsigned int ms) { unsigned int j; while (ms--) for (j = 600; j > 0; j--); } void main(void) { P0M1 = 0x00; P0M0 = 0xFF;
unsigned char num[] = {0xC0, 0xEC, 0x92, 0x98,0xCC, 0x89, 0x81, 0xDC, 0x80, 0x88}; unsigned char w[] = {0x01, 0x02, 0x04, 0x08}; while (1) { for (unsigned char i = 0; i < 4; i++) { P0 = w[i]; P1 = num[8]; delay(5); P1 = 0xFF; } } }
|
封装为函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| void display(unsigned char *arr) { static unsigned char i = 0; unsigned char num[] = {0xC0, 0xEC, 0x92, 0x98,0xCC, 0x89, 0x81, 0xDC, 0x80, 0x88}; P0 = 0x01 << i; P1 = num[arr[i]]; delay(5); P0 = 0x00; i += 1; i %= 4; }
void main(void) { while (1) { unsigned char arr[4] = {0, 0, 0, 0}; display(arr); delay(2); } }
|
点阵屏
8(列)*8(行)的点阵屏的操作逻辑与8位数码管理论上相同
分辨率:8(列)*8(行)
像素:64
点阵屏的引脚是由方向决定的,共阴和共阳也是由方向决定的
共阳(灌电流)接线方式(引脚在左右)点亮:列信号接高电平(高选列),行信号接低电平(低送行)
共阴(拉电流)接线方式(引脚在上下)点亮:列信号接低电平(低选列),行信号接高电平(高送行)
- 本案例接线方式(单屏)
- 列信号1接P0.0、…、位信号8接P0.7
- 行信号1接P1.0、…、段信号8接P1.7
- 本案例接线方式(多屏)
- 列信号1接P0.0、…、位信号8接P0.7
- 列信号9接P2.0、…、位信号16接P2.7
- 行信号1接P1.0、…、段信号8接P1.7
不应该将列信号同时点亮,如果需要全部点亮每个列,可以采用动态刷新技术
取模:将文字转化成点阵
切屏:每次刷新8列
滚屏:每次刷新1列
单屏
全部点亮
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
| #include<8052.h>
__sfr __at (0x94) P0M0; __sfr __at (0x93) P0M1; __sfr __at (0x92) P1M0; __sfr __at (0x91) P1M1;
void delay(unsigned int ms) { unsigned int j; while (ms--) for (j = 600; j > 0; j--); }
void main(void) { P0M1 = 0x00; P0M0 = 0xFF; P1M1 = 0x00; P1M0 = 0xFF;
unsigned char num[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; unsigned char l[] = {0xFE, 0xFD, 0xFB, 0xF7, 0xEF, 0xDF, 0xBF, 0x7F};
while (1) { for (unsigned char i = 0; i < 8; i++) { P0 = l[i]; P1 = num[i]; delay(2); P1 = 0xFF; } } }
|
双屏
全部点亮
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
| #include<8052.h>
__sfr __at (0x94) P0M0; __sfr __at (0x93) P0M1; __sfr __at (0x92) P1M0; __sfr __at (0x91) P1M1;
void delay(unsigned int ms) { unsigned int j; while (ms--) for (j = 600; j > 0; j--); }
void main(void) { P0M1 = 0x00; P0M0 = 0xFF; P1M1 = 0x00; P1M0 = 0xFF;
unsigned char num[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; unsigned char l[] = {0xFE, 0xFD, 0xFB, 0xF7, 0xEF, 0xDF, 0xBF, 0x7F};
while (1) { for (unsigned char i = 0; i < 8; i++) { if (i < 8) { P2 = 0xFF; P0 = l[i]; } else { P0 = 0xFF; P2 = l[i]; } P1 = num[i]; delay(1); P1 = 0xFF; } } }
|
蜂鸣器(Buzz)
有源蜂鸣器:内置了震荡源芯片的蜂鸣器,接上电点就能响,但是不能改变振动频率所以只能发出一种音调
无源蜂鸣器:没有内置震荡源芯片的蜂鸣器,接上电后,需要给脉冲信号才能响,可以通过改变震动频发出不同的音调
拉电流和灌电流都可以使蜂鸣器发声
因为人耳识别声音的范围为5Hz~20kHz,所以延迟最大为100ms
软件延迟实现蜂鸣器5Hz声音
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include<8052.h>
void delay(unsigned int ms) { unsigned int j; while (ms--) for (j = 600; j > 0; j--); }
void main(void) { P0_0 = 1; while (1) { P0_0 = ~P0_0; delay(100); } }
|
定时/计数器实现蜂鸣器500Hz声音
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
| #include<8052.h>
void init() { IE = 0x82; TMOD = 0x01; TH0 = (65536 - 1000) / 256; TL0 = (65536 - 1000) % 256; TR0 = 1; }
void main(void) { init(); P1_0 = 1; while (1) {
} }
void timer0(void) __interrupt 1 { TR0 = 0;
if (P1_0) P1_0 = 0; else P1_0 = 1;
TH0 = (65536 - 1000) / 256; TL0 = (65536 - 1000) % 256; TR0 = 1; }
|
定时/计数器实现蜂鸣器指定频率声音
<frequency>
:频率
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
| #include<8052.h>
void init() { IE = 0x82; TMOD = 0x01; TH0 = (65536 - 1000000/<frequency>/2) / 256; TL0 = (65536 - 1000000/<frequency>/2) % 256; TR0 = 1; }
void main(void) { init(); P1_0 = 1; while (1) {
} }
void timer0(void) __interrupt 1 { TR0 = 0;
if (P1_0) P1_0 = 0; else P1_0 = 1;
TH0 = (65536 - 1000000/<frequency>/2) / 256; TL0 = (65536 - 1000000/<frequency>/2) % 256; TR0 = 1; }
|
定时/计数器实现蜂鸣器音阶
- 全音符的音长大约在1600ms
- 2分音符的音长大约在800ms
- 4分音符的音长大约在400ms(~600ms)
- 8分音符的音长大约在200ms
- 16分音符的音长大约在100ms
- 32分音符的音长大约在50ms
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
| #include<8052.h>
unsigned int h;
unsigned int l;
void init() { IE = 0x82; TMOD = 0x01; }
void delay(unsigned int ms) { unsigned int j; while (ms--) for (j = 600; j > 0; j--); }
void main(void) { init(); P1_0 = 1;
unsigned int pitch[] = {523, 587, 659, 698, 784, 880, 988}; unsigned int tone[] = {1, 1, 5, 5, 6, 6, 5}; unsigned int rhythm[] = {1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2}; unsigned char i = 0;
while (1) { h = (65536 - 1000000/pitch[tone[i] - 1]/2) / 256; l = (65536 - 1000000/pitch[tone[i] - 1]/2) % 256; TH0 = h; TL0 = l; TR0 = 1; delay(rhythm[i] * 600); TR0 = 0; P1_0 = 0; i += 1; i %= sizeof(tone)/sizeof(tone[0]); } }
void timer0(void) __interrupt 1 { TR0 = 0;
if (P1_0) P1_0 = 0; else P1_0 = 1;
TH0 = h; TL0 = l; TR0 = 1; }
|
C大调音符频率对照表
- 中音的频率约定于2倍的低音
- 高音的频率约定于2倍的中音
- 以此类推
序号 |
音符 |
频率 |
1 |
低音1 |
262 |
2 |
低音2 |
294 |
3 |
低音3 |
330 |
4 |
低音4 |
349 |
5 |
低音5 |
392 |
6 |
低音6 |
440 |
7 |
低音7 |
494 |
8 |
中音1 |
523 |
9 |
中音2 |
587 |
10 |
中音3 |
659 |
11 |
中音4 |
698 |
12 |
中音5 |
784 |
13 |
中音6 |
880 |
14 |
中音7 |
988 |
15 |
高音1 |
1046 |
16 |
高音2 |
1175 |
17 |
高音3 |
1318 |
18 |
高音4 |
1397 |
19 |
高音5 |
1568 |
20 |
高音6 |
1760 |
21 |
高音7 |
1976 |
完成
参考文献
哔哩哔哩——江科大自化协