单片机基础
单片机
单片机定义
内含CPU
内存RAM
存储空间ROM
通过外部或内部时钟输入完成指令的处理
能够通过外部编程使其独立完成工作
8051内核、AVR内核、ARM内核(部分)
可以把单片机看成微型电脑,内部带有了内存和硬盘,只不过比较小、功耗低因此性能也差
可实现的功能
CPU可以处理指令进行运算
RAM相当于电脑内存,可以临时存放运算结果
ROM相当于电脑硬盘,可以存储程序
实现外部引脚从电源电压与GND的切换
点亮小灯、读取按键、串行数据输入输出、读写外部器件等
单片机程序框架
语句→函数→文件→框架→库
学习单片机过程会遇到的坑
不要速成
打牢基础软硬件知识
先弄明白怎么做,再弄明白为什么
做完如何改进、实现更多的功能,然后再看原理、单片机内部如何实现
不要翻一本书从头翻到尾,看不进去的
看不懂没关系,先用,用会了再回头弄明白原理
充分学习和吸收优秀代码(郭天祥的代码其实不是很规范,不值得学习)
找大厂如华为、中兴内部培训的资料,借鉴它们的编程规范
OpenHarmony开发者文档 - 《华为鸿蒙操作系统(OpenHarmony) v1.0 开发者文档》
推荐周立功代码规范。周立功标准代码编写规范 (amobbs.com 阿莫电子论坛 - 东莞阿莫电子网站)
延时代码
#include <STC8.H> void Delay500ms() { unsigned char i,j,k: i=29; j=14; k=54; do { do { while(--k); }while(--j); }while(--i); } void main() { while(1) { P2=0xA5; Delay500ms(); P2=0x5A; Delay500ms(); } }
单片机C语言
不建议看谭浩强,建议看比较新的、外文翻译过来的书,或直接看单片机相关的书,里面会直接讲C语言基础
有时可以把Delay500ms写成Delay_500ms
软延时:通过程序把它消耗在一个无意义的事情上,让其浪费掉这些时间
硬延时/定时:通过内部硬件电路结构实现
单片机资源非常有限,必须重点关注各种数据类型的位,防止浪费单片机的资源。
char | 8位 |
0xFF (1111 1111) ,最高位为符号位,1负0正。 从1111 1111(-128)~0111 1111(+127),一共可代表255个数。 再进位到1000 0000就翻转变成-1出现错误了,因此使用char时必须注意不要超出其运行范围(-128~127) |
unsigned char | 8位 | 0xFF,严格限制在0~255,否则255再+1变成0,再加1变成1 |
int | 16位 | 0xFFFF |
unsigned int | 16位 | 0~65535 |
long | 32位 | |
float | 浮点,表示小数时使用 |
位数可以简单理解为线。如8位单片机内部有8根总线,可以一次性并行传输8个数。同理,32位单片机可以同时写32个数,以此类推。因此8位单片机尽量用8位的char。
而对ARM单片机最小就是32位,如果写8位,其他的相当于浪费了。因此在ARM里存在一个16位的short
花样点灯: 可以随便想如何点灯,然后编程实现,非常锻炼编程能力!
1、for
2、while
3、位操作(来源)
————————————————
版权声明:下文为CSDN博主「MoreWindows」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/morewindows/article/details/7354571
————————————————
1.只能用于整形数据,不可用于float和double
2.算数移位和逻辑移位不同
算术移位 | 左移<<操作低位补0 | 右移>>操作高位补符号位 |
逻辑移位 | 左移<<操作低位补0 | 右移>>操作高位补0 |
int a = -15, b = 15; printf("%d %d\n", a >> 2, b >> 2);
15=0000 1111(二进制),右移二位,最高位由符号位填充将得到0000 0011即3
-15 = 1111 0001(二进制),右移二位,最高位由符号位填充将得到1111 1100即-4
常见用法
1.判断奇偶:最未位为0是偶数,为1是奇数
用if ((a & 1) == 0)判断a是不是偶数
eg.输出0到100之间的所有奇数
for (i = 0; i < 100; ++i) if (i & 1) printf("%d ", i); putchar('\n');
2.交换两数
void Swap(int &a, int &b) { if (a != b) { a ^= b; b ^= a; a ^= b; } }
3.变换符号(正变负,负变正)
取反后加1
#include <stdio.h> int SignReversal(int a) { return ~a + 1; } int main() {int a = 7, b = -12345; printf("%d %d\n", SignReversal(a), SignReversal(b)); return 0; }
4.求绝对值(负数按上面变换符号即可)
移位来取符号位,int i = a >> 31
int my_abs(int a) { int i = a >> 31; return ((a ^ i) - i); }
5.用位操作压缩素数表的空间占用
数组在内存上也是连续分配的一段空间,完全可以“认为”是一个很长的整数
//使用位操作压缩后的筛素数方法 #include <stdio.h> #include <memory.h> const int MAXN = 100; int flag[MAXN / 32 + 1]; int primes[MAXN / 3 + 1], pi; void GetPrime_1() { int i, j; pi = 0; memset(flag, 0, sizeof(flag)); for (i = 2; i < MAXN; i++) if (!((flag[i / 32] >> (i % 32)) & 1)) { primes[pi++] = i; for (j = i; j < MAXN; j += i) flag[j / 32] |= (1 << (j % 32)); } } void PrintfArray() { for (int i = 0; i < pi; i++) printf("%d ", primes[i]); putchar('\n'); } int main() { GetPrime_1(); PrintfArray(); return 0; }
6.高低位交换
二进制的数34520:10000110 11011000
高低位交换后是十进制的55430: 11011000 10000110
实现:右移时会执行逻辑右移即高位补0,因此x右移8位将得到00000000 10000110。
x左移8位将得到11011000 00000000。可以发现只要将x>>8与x<<8这两个数相或就可以得到11011000 10000110
#include <stdio.h> template <class T> void PrintfBinary(T a) { int i; for (i = sizeof(a) * 8 - 1; i >= 0; --i) { if ((a >> i) & 1) putchar('1'); else putchar('0'); if (i == 8) putchar(' '); } putchar('\n'); } int main() { printf("交换前: "); unsigned short a = 3344520; PrintfBinary(a); printf("交换后: "); a = (a >> 8) | (a << 8); PrintfBinary(a); return 0; }
7.二进制逆序
字符串的逆序,可以从字符串的首尾开始,依次交换两端的数据
用位操作的高低位交换来处理二进制逆序
类似于归并排序的分组处理
第一步:每2位为一组,组内高低位交换
10 00 01 10 11 01 10 00
-->01 00 10 01 11 10 01 00
第二步:每4位为一组,组内高低位交换
0100 1001 1110 0100
-->0001 0110 1011 0001
第三步:每8位为一组,组内高低位交换
00010110 10110001
-->01100001 00011011
第四步:每16位为一组,组内高低位交换
01100001 00011011
-->00011011 01100001
#include <stdio.h> template <class T> void PrintfBinary(T a) { int i; for (i = sizeof(a) * 8 - 1; i >= 0; --i) { if ((a >> i) & 1) putchar('1'); else putchar('0'); if (i == 8) putchar(' '); } putchar('\n'); } int main() { printf("二进制逆序\n"); printf("逆序前: "); unsigned short a = 34520; PrintfBinary(a); printf("逆序后: "); a = ((a & 0xAAAA) >> 1) | ((a & 0x5555) << 1); a = ((a & 0xCCCC) >> 2) | ((a & 0x3333) << 2); a = ((a & 0xF0F0) >> 4) | ((a & 0x0F0F) << 4); a = ((a & 0xFF00) >> 8) | ((a & 0x00FF) << 8); PrintfBinary(a); }
8.二进制中1的个数
【1】直接移位再判断
【2】循环移位计数
【3】先打一个表再计算
【4】分组
第一步:每2位为一组,组内高低位相加
10 00 01 10 11 01 10 00
-->01 00 01 01 10 01 01 00
第二步:每4位为一组,组内高低位相加
0100 0101 1001 0100
-->0001 0010 0011 0001
第三步:每8位为一组,组内高低位相加
00010010 00110001
-->00000011 00000100
第四步:每16位为一组,组内高低位相加
00000011 00000100
-->00000000 00000111
#include <stdio.h> template <class T> void PrintfBinary(T a) { int i; for (i = sizeof(a) * 8 - 1; i >= 0; --i) { if ((a >> i) & 1) putchar('1'); else putchar('0'); if (i == 8) putchar(' '); } putchar('\n'); } int main() { printf("二进制中1的个数\n"); unsigned short a = 34520; printf("原数 %6d的二进制为: ", a); PrintfBinary(a); a = ((a & 0xAAAA) >> 1) + (a & 0x5555); a = ((a & 0xCCCC) >> 2) + (a & 0x3333); a = ((a & 0xF0F0) >> 4) + (a & 0x0F0F); a = ((a & 0xFF00) >> 8) + (a & 0x00FF); printf("计算结果%6d的二进制为: ", a); PrintfBinary(a); return 0; }
9.找到成对出现的数字中缺失的数字
有一个数只出现一次而且其它数字都出现了偶数次
可用异或运算的两个特性——1.自己与自己异或结果为0,2.异或满足交换律。
将这些数字全异或一遍,结果就一定是那个仅出现一个数。
#include <stdio.h> int main() { printf("缺失的数字\n"); const int MAXN = 15; int a[MAXN] = {1, 347, 6, 9, 13, 65, 889, 712, 889, 347, 1, 9, 65, 13, 712}; int lostNum = 0; for (int i = 0; i < MAXN; i++) lostNum ^= a[i]; printf("缺失的数字为: %d\n", lostNum); return 0; }
练习
1.入门级
按键长按与短按实现不同功能
按键切换流水灯效果、LED灯亮度
2.刮目相看级
音乐方块游戏
呼吸灯
3.骨灰级
打地鼠
定时器
定时器的本质是“装桶”
进入中断,调用中断服务函数
自由定时器:不一定要装满,可以装完一定值之后读取有多少数并通过计算获得时间
从左到右路过哪里就打开哪里!
不要着急写程序,先把范例读一读,找到其中的不同,并判断这些不同会产生哪些区别,再动手去写。
(比如X位自动重载中不同位数,例程有什么变化)
#include <STC8.H> unsigned int Counter=0; void TIMERO(void) interrupt 1 Counter++; if(Counter==1000) P20=!P20: } Counter=0; } void TimerOInit(void) //1000微秒@12.000Hz { AUXR|=0x80; //定时器时钟1T模式 TMOD&=0xF0; //设置定时器模式 TLO=0x20; //设置定时初值 THO=0xD1; //设置定时初值 TFO=0; //清除TF0标志 TRO=1; //定时器0开始计时 } void main(void) { TimerOInit(); ET0=1; EA=1; while(1); }
主要来源: