51单片机学习(C语言)
1.认识单片机
单片机也被称为单片微控器,属于一种集成式电路芯片。单片机中主要包含CPU、只读存储器ROM和随机存储器RAM等。单片机这块芯片组成了一个系统,通过集成电路技术的应用,将数据运算与处理能力集成到芯片中,实现对数据的高速化处理。单片机凭借着强大的数据处理技术和计算功能可以在智能电子设备中充分应用。简单地说,单片机是一台微型计算机,它的体积小、质量轻、价格便宜、为学习、应用和开发提供了便利条件。 学习单片机可以了解一些计算机的基本概念,了解电脑的一些基本操作,电路及其元器件的基本理论。
1.1工具
51单片机,keil5(编写程序),stc-isp(下载程序),单片机驱动程序。
1.2基本程序框架与逻辑运算
打开keil5,建立新的project,编写新的程序的时候需要用到以下的基本框架
#include
void main()
{
while(1)
{
}
}
此处引入的头文件是对应单片机外部设备的,没有这个头文件,编写的程序就没法编译。 一般需要单片机一直执行代码,所以用while(1)使得循环一直进行下去。 除此之外,我们还需要一些逻辑运算:
算数 判断
+ /*加*/ > /*大于*/
- /*减*/ < /*小于*/
* /*乘*/ == /*等于*/
/ /*除*/ <= /*小于等于*/
% /*取余*/ >= /*大于等于*/
= /*赋值*/ != /*不等于*/
逻辑
&& /*逻辑与*/
|| /*逻辑或*/
! /*逻辑非*/
以上均为C语言常用的运算符号及其含义。 现在来介绍一下C语言中不是太常用,但在单片机中比较常用的运算符:
>> 按位右移
<< 按位左移
& 按位与
| 按位或
^ 按位异或
~ 按位取反
举例说明各自的作用:
按位右移
0011 0100 >>1 (按位右移一位) -> 0001 1010
按位左移
0011 0100 <<2 (按位左移两位)-> 1101 0000
按位与
0011 0100 & 0010 1010
(两个数的每一位分别比较,都是1,该位结果为1,否则为0)
0011 0100
0010 1010 -> 0010 0000
按位或
0011 0100 | 0010 1010
(两个数的每一位分别比较,都是0,该位结果为0,否则为1)
0011 0100
0010 1010 -> 0011 1110
按位异或
0011 0100 ^ 0010 1010
(两个数的每一位分别比较,如果相同,该位结果为0,否则为1)
0011 0100
0010 1010 -> 0001 1110
按位取反
~0010 1010
(原来为1,取反为0,原来为0,取反为1)
~0010 1010 -> 1101 0101
1.3延时函数
延时函数简单来说就是让单片机在设定的时间段里什么都不做,等过了这个时间段,再执行下一步内容。 原理其实很简单,就是让单片机做一些没有意义的计算来占用时间,因此设计延时函数还要考虑到单片机的执行速度。举个例子,我有一个每秒执行一千次计算的单片机,我可以设一个值为一千的变量,让单片机将这个变量一次减一,直到减到0,这样会做一千次运算,就延时了一秒。 进行延时操作的时候要额外引入一个头文件#include
void Delay1ms() //@11.0592MHz
{
unsigned char i, j;
_nop_();
_nop_();
_nop_();
i = 11;
j = 190;
do
{
while (--j);
} while (--i);
}
注意:不同单片机有不同的晶振频率,不同频率的延时代码也是不一样的。在stc-isp中要好选择对应的频率和所需延时时间,才能生成对应代码。
我们也可以对这段代码进行改造,让它变成我们想延时多少秒就延时多少秒的函数,只需要在函数里面加入一个while循环和一个控制循环的变量就好了:
void Delayxms(unsigned char xms) //@11.0592MHz
{
unsigned char i, j;
while(xms>0){
_nop_();
_nop_();
_nop_();
i = 11;
j = 190;
do
{
while (--j);
} while (--i);
xms--;
}
2.外部设备
2.1 LED模块
单片机上有独立的一列LED,一共八个,根据开发板原理图,此列LED对应着的是P2寄存器。 以下是在
sfr P2 = 0xA0;
sbit P2_0 = 0xA0;
sbit P2_1 = 0xA1;
sbit P2_2 = 0xA2;
sbit P2_3 = 0xA3;
sbit P2_4 = 0xA4;
sbit P2_5 = 0xA5;
sbit P2_6 = 0xA6;
sbit P2_7 = 0xA7;
为了方便与八位的二进制对应,一般采用十六进制来赋值,需要在十六进制数前加上0x来表示十六进制。 注意第一个LED对应的是P2_0,第八个对应的是P2_7
2.1.1单个LED亮灭
我们直接改变P2寄存器的内容就能控制LED的亮灭:
P2=0XFE; //1111 1110
这里我们直接使P2为1111 1110,也就是第一位为0,其他为1,那么这时第一个LED会亮起,其他保持熄灭状态。 当然,我们也可以单独控制单个LED的亮灭,对单个的P2_0,P2_1,P2_2,···P2_7去赋值:
P2_6 = 0x00;
单独给P2_6赋值为0,就能单独控制第七个LED灯亮起来,其他LED状态不变。
2.1.2单个LED闪烁
要实现LED闪烁,要用到延时函数,简单来说,就是让LED亮起,然后延时,再让LED熄灭,然后延时,就能实现LED的闪烁了,利用这个原理,我们可以让LED一个接着一个亮起来,实现LED的流水灯:
while(1){
P2=0XFE; //1111 1110
Delay100ms(ss);
P2=0XFD; //1111 1101
Delay100ms(ss);
P2=0XFB; //1111 1011
Delay100ms(ss);
P2=0XF7; //1111 0111
Delay100ms(ss);
}
2.2独立按键模块
2.2.1按键控制
单片机上有一排四个的独立按键,有了这四个按键,我们可以做一些通过按键来触发的程序,比如用按键来控制LED的亮灭。根据开发板原理图,独立按键所对应的是P3寄存器的前四位。 以下是在
sfr P3 = 0xB0;
sbit P3_0 = 0xB0;
sbit P3_1 = 0xB1;
sbit P3_2 = 0xB2;
sbit P3_3 = 0xB3;
当我们按下第一个按钮时,P3_0的值为零=0,没有按下,P3_0的值为1,所以,我们要构建一个if语句来判断按键是否被按下了,再来执行之后对应的操作。
while(1)
{
if(P3_0==0)
{
P2=0x01;
}
}
这里我们做了个判断语句,如果按下了第一个按钮,第一个LED就会亮起,如果松开第一个按钮,第一个LED就会熄灭。 这听起来虽然好像没什么毛病,但我们仔细想想,要想让灯亮起来,我们岂不是要一直按着按钮,那不得累死。所以我们换个思路,按下按钮后松开,LED就亮起,再按下按钮后松开,LED就熄灭。
while(1)
{
if(P3_0==0)
{
while(P3_0==0)
{
}
P2_0=~P2_0;
}
}
我们做一个空的while循环,如果检测到按钮还是按下的,就停在循环里面,直到它检测到松开按钮,才让灯亮起来。再按下,再松开,LED才会熄灭。
2.2.2按键消抖
对于机械开关,当机械触点断开闭合的时候,由于机械触点的弹性作用,一个开关在闭合的时候不会马上稳定地接通,在断开时也不会一下子断开,即在开关闭合和断开的瞬间会伴随一连串的抖动,这一连串的抖动会对我们的操作带来一定的影响,所以我们可以通过一些设置来避免这样的干扰。 我们在检测到按键按下和按键松开的时候可以延时20ms,跳过按键抖动的时间,也就是按键消抖。 使用延时函数,就可以实现按键消抖了:
while(1){
if(P3_1==0){
Delayxms(20); //消除按下按键的抖动
while(P3_1==0);
Delayxms(20); //消除松开按键的抖动
P2_0=~P2_0;
}
}
2.2.3多个按键的控制
实现多个按键控制,需要多个if语句判断:
while(1){
if(P3_0==0){
Delayxms(20);
while(P3_0==0);
Delayxms(20);
P2_0=~P2_0;
}else if(P3_1==0){
Delayxms(20);
while(P3_1==0);
Delayxms(20);
P2_1=~P2_1;
}
}
理解了多个按键判断的原理,就可以实现一些稍微复杂一点的控制,比如当有一个LED亮起时,按下第一个按钮,这个LED熄灭,它左边的LED亮起;按下第二个按键,这个LED熄灭,它右边的LED亮起(简单说就是用两个按键控制LED灯光移动):
unsigned char add=0;
P2_0=0;
while(1){
if(P3_0==0){ //检测按下第一个按钮
Delayxms(30); //消抖
while(P3_0==0);
Delayxms(30);
if(add==7){ //移动位置的逻辑判断
add=0;
}else{
add++;
}
P2=~(0x01< }else if(P3_1==0){ //检测按下第二个按钮 Delayxms(30); //消抖 while(P3_1==0); Delayxms(30); if(add==0){ //移动位置的逻辑判断 add=7; }else{ add--; } P2=~(0x01< } } 简单利用按位左移或者按位右移,用按键来控制移动量,就可以实现用按键控制LED灯光移动了。 2.3LED数码管模块 LED数码管区别于LED点列的是它可以显示数字,而不单是点,利用LED数码管,可以做一些数字的输出。 相比LED点列,LED数码管也更加复杂。 如图可见,LED数码管组件共有八个数码管,从LED1~LED8,这八个数码管与74HC138编译器中的P22、P23、P24相对应;每个数码管里面还有八个LED,分别是a、b、c、d、e、f、g、dp,这八个LED与74HC245中的P0寄存器相对应。 我们在使用时,要做两步动作,第一步是选中我们要用的数码管,第二步是亮起我们需要的LED。也就是说,我们要同时控制着选择数码管的P22、P23、P24和控制LED的P0。 2.3.1控制数码管 根据74HC138编译器的原理图,我们可以发现它用三位的二进制来控制着八个数码管。三位的二进制数刚好一共是八个,每一个对应一个数码管: P2_4=1; P2_3=1; P2_2=1; //LED8 P2_4=1; P2_3=1; P2_2=0; //LED7 P2_4=1; P2_3=0; P2_2=1; //LED6 P2_4=1; P2_3=0; P2_2=0; //LED5 P2_4=0; P2_3=1; P2_2=1; //LED4 P2_4=0; P2_3=1; P2_2=0; //LED3 P2_4=0; P2_3=0; P2_2=1; //LED2 P2_4=0; P2_3=0; P2_2=0; //LED1 我们选择对应数码管,只需要改变P22、P23、P24的值即可。 2.3.2亮起LED(静态) 根据LED原理图,我们可以发现八位的寄存器P0对应着这八个LED。和之前的LED模块不同,数码管LED对应寄存器值为1就是亮起,对应为0就是熄灭。 以下是a、b、c、d、e、f、g、dp与八位寄存器对应关系: dp g f e d c b a 0 0 0 0 0 0 0 0 最低位对应a,最高位对应dp,如此类推中间的几个LED,得到其对应关系。 我们希望在第一个数码管上面显示数字3,首先要让P2_4=0; P2_3=0; P2_2=0; 再让a、b、c、d、g亮起来,也就是让P0的值为0100 1111: while(1){ P2_4=0; P2_3=0; P2_2=0; P0=0100 1111; { 2.3.3动态数码管显示 很多时候我们需要再不同数码管上面显示不同的数字,这时就要用到动态数码管显示了。由于我们人的眼睛在观察高速运动的事物时会产生残影,也就是视觉暂留现象,最高分别能力是二十四分之一秒,而单片机速度很快,可以让单片机以较短的频率在不同数码管中分别显示数字,这样就可以达到在不同数码管中显示数字的目的了。 不过我们不能直接在上一个数码管中显示完就让下一个数码管显示,因为这会导致数码管显示篡位,我们也要给数码管显示“消抖”一下,就是加上一个短的延时函数。具体延时多少,要看我们到底要在多少个数码管上面显示数字,如果只在两个数码管上显示,延时可以设置到1ms;如果要在八个数码管中都要显示数字,延时就要更加短,短到10us。 以下为点亮两个数码管的示例: while(1){ P2_4=0; P2_3=0; P2_2=0; //LED1 P0=0x3F; //显示0 Delay1ms(); P2_4=0; P2_3=0; P2_2=1; //LED2 P0=0x6F; //显示9 Delay1ms(); 2.3.4LED数码管快速显示的小技巧 我们在点亮LED数码管的时候,往往要为显示哪个数码管,显示什么数字而计算半天,这样非常不方便,而且还非常费时间,这里有个小技巧可以解决这个问题: 我们直接建立个函数,当我们要在数码管显示数字的时候,直接调用这个函数就可以了,不需要做多余的计算。 首先在纸上把每个数码管对应的P22、P23、P24数值算出来,再把LED显示数字时P0对应的值算出来,整理一下,就可以开始构建函数了: 我们构建一个数组,用来存放各个数字所对应的P0值,这样可以直接调用数组。再构建一个switch case语句,把八个数码管对应的P22、P23、P24状态写进去就好了。 unsigned char li[10]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F}; void nixie(unsigned char location,num){ switch(location){ case 1: P2_4=1; P2_3=1; P2_2=1; break; case 2: P2_4=1; P2_3=1; P2_2=0; break; case 3: P2_4=1; P2_3=0; P2_2=1; break; case 4: P2_4=1; P2_3=0; P2_2=0; break; case 5: P2_4=0; P2_3=1; P2_2=1; break; case 6: P2_4=0; P2_3=1; P2_2=0; break; case 7: P2_4=0; P2_3=0; P2_2=1; break; case 8: P2_4=0; P2_3=0; P2_2=0; break; } P0=li[num]; } 这样,当我们想要在数码管显示数字时,只需要调用nixie()就可以了。 推荐链接
发表评论