这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » MCU » 34岁“高龄”学习单片机,以此贴记录重难点,坚持!坚持!、

共150条 6/15 |‹ 4 5 6 7 8 9 ›| 跳转至
菜鸟
2014-05-13 16:23:13     打赏
51楼

继续更新

这两天主要在回头分析之前写过的那些程序,因为学完中断和定时后,需要理解的原理少了,而单片机中C语言编程的思路的重要性越发凸显。在分析这些程序的过程中,一方面复习了之前学过的知识,另一方面也对单片机的编程有了进一步的认识。我的体会是:

1.在做例程时,必须理解每一条语句的内在含义。单片机的语句都很简单,但是,同样的一条语句,使用的位置不同,却产生了完全不同的意义。

2.写程序时,对单片机电路的整体和细节都要有把握。这样才能做到理解并灵活应用。

3.得动态地把握电路的变化,每一条代码都会改变单片机电路的状态,思路必须跟的上。

编程本来就够抽象了,电路也是抽象的,必须做到把这些抽象在头脑里具体化,逻辑还得连贯。

我觉得单片机最难可能也就是这里。

比如定义了一个给中断计数的a,到哪一步这个值必须要清零,在某时用这个中断时应该取什么值来判断。又或者经过一连串的键盘抖动检测的while或if嵌套后,一条语句具体应该放在哪个循环内,没搞清楚某时的电路和程序的状态,放错了地方肯定会出错。还有标记位的调用等等

或许这只是我作为新手的困惑,高手运用语句可能就如用自己手脚眼睛一般随心所欲。


菜鸟
2014-05-13 16:34:32     打赏
52楼

第五课开始讲的那个例题感觉非常经典,几乎综合了之前学过的所有东西。所以我着重复习了这个。并给程序加上了注释,以备查阅:

 

/*程序流程:
程序初始化时为6个数码管显示765432


--->通过“定时器1”来让这个数以1/10秒的速度递减至765398,
 同时流水灯从上到下流动,使用“定时器0”控制其速度为500毫秒每次。


---->数码管递减完成后,停止,流水灯停止流动,开始闪烁。


3秒后(定时器0控制)---> 关闭流水灯
                                      数码管显示HELLO
*/
#include
#include  //流水灯要用到_crol_()
#define uint unsigned int
#define uchar unsigned char
uchar code tabledu[]={
0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,
0x39,0x5e,0x79,0x71,
0x76,0x79,0x38,0x3f,0x00};
uchar temp,t0,t1,flag,flag1;
uint num,speed,bai,shi,ge;
sbit duan=P2^6;
sbit wei=P2^7;
void init();
void display(uint,uint,uint,uint,uint,uint);
void delay(uint speed);


void main()

   init();       //调用初始化子程序

 while(1)  
 {
  if(flag1!=1)  //调用中断0中定义的flag1,如果标志位flag1不等于1,则继续执行数码管减数到398的子程序,如果flag1=1,则执行else{}中显示HELLO的显示子程序。
  {
    display(7,6,5,bai,shi,ge);//调用数码管显示子程序(前3位因为不变so直接赋常量)
   }
   else
   {
      display(16,17,18,18,19,20);
   }
 }

 
}

void init()  //初始化子程序。
{
  speed=1;  //设定个延时的速度,即扫描显示速度。
  num=432;  //给num赋初值432
  temp=0xfe;  //给temp赋初值。
  P1=temp;     //temp值赋给P1口,即让第一个发光二极管亮
  TMOD=0x11;  //给中断0和中断1的TMOD寄存器赋值,确定其工作方式
  TH0=(65536-50000)/256;
  TL0=(65536-50000)%256;
  TH1=(65536-50000)/256;
  TL1=(65536-50000)%256; //给中断0和中断1的高8为何低8位赋初值,定时为每50毫秒中断一次。
  //此处的理解为:将50000微秒分配到高8位(最大65536)和低8位(最大256)中, 中断即可识别出我们要定时多久。
  EA=1;   //打开IE总中断
  ET0=1;   
  ET1=1;  //打开IE的T0和T1中断
  TR0=1;
  TR1=1;   //启动中断T0和中断T1

}

void time0() interrupt 1  //中断0
{
 TH0=(65536-50000)/256; //中断开始时,必须再次初始化高8位和低8位
 TL0=(65536-50000)%256;  
 t0++;  //定义变量t0自加,即t0为每次中断自加一次,为中断计数。       
        /*备注:中断中写程序最好不要写太多
        一方面难查,另一方面程序执行需要时间,
       每条单周期指令大概1微妙,超过中断时间
       会影响中断的执行。更加注意不要写延时。*/
  if(flag!=1)   //调用中断1中的标志位flag,如果成立,继续执行if{}内语句(流水灯流动),否则执行else{}中语句(流动灯闪烁(调用前已在中断1中停止流动))。
  {
   if(t0==10) //如果时间流逝500毫秒,执行IF{}内语句。
   {
    t0=0;   //清零
    temp=_crol_(temp,1);//左移一位 ,即流水灯换下一个点亮。
    P1=temp;    //再将TEMP赋给P1口
   }
  }   
   else   //else内代码为实现流水灯闪烁。
    {
      if(t0%4==0)  //T0中断每进入一次为50毫秒,我们让其200毫秒闪烁一次。 所以给T0的条件就是:4的整数倍,表示为t0%4==0, 同理,如果是每50毫秒闪烁,就是t0%1==0,100毫秒:t0%2==0
       
     P1=~P1; //给所有的P1口(所有流水灯)值取反。
     //以上2条语句在代码内实际意义为每隔200毫秒对P1口的值取反一次。即实现了流水灯每400毫秒亮灭各一次,达到闪烁的目的。
    if(t0==60) //如果t0=60,即闪烁了50毫秒*60=3秒
     {
      TR0=0;//关闭T0定时器
      P1=0xff;//把P1口关闭。为什么0xff是关闭?
      flag1=1;//定义一个标志位flag1,赋值为1,给中断1调用,用于实现HELLO的显示。
     }
     
    }
}
void timer1() interrupt 3  //中断1
{
 TH1=(65536-50000)/256;
 TL1=(65536-50000)%256;
 t1++;
  if(t1==2)   //当t1增加至2时,时间流逝100毫秒,即1/10秒
  {
   t1=0;  // t1清零
   num--;  // 对num自减
   bai=num/100; 
   shi=num%100/10;
   ge=num%10;    //将num分解为bai,shi,ge,便于table数组单独调用)
   if(num==398) //如果num自减至398
   {
    TR0=0;  //关闭流水灯定时器T0
    TH0=(65536-50000)/256;
    TL0=(65536-50000)%256;
    TR0=1; //打开T0定时器。以上4条语句为将T0定时器还原并重新打开,准备写3秒钟计时。
    t0=0;  //t0清零
    P1=0xff; //关闭P1口。即初始化流水灯,等待闪烁。重要,否则会有1个流水灯闪烁很大可能与其他7个不同步。  

   TR1=0;  //关闭数码管定时器T1
    flag=1; //当执行至此时,定义标志位flag并赋值,给中断0拿去调用。
   } //为何视频中写在P1=0xff前,主函数调用flag仍然可以使P1=0xff起作用?
  }
}


void display(uint aa,uint bb,uint cc,uint bai,uint shi,uint ge)
//数码管显示子程序。
{     
      
   duan=1;    //存疑。为何必须先送段选?
   P0=tabledu[aa];  //如果先送位选,数码管的数字会整体右移一位,最后一位跑到最前。
   duan=0;
   P0=0xff;   //重要给P0口送个无显示的空值。无此语句,段选口的送的值保持在锁存端,接着位选一打开又会送给位选,和位选送的值混在一起,导致数码管显示混乱不正常。
   wei=1;
   P0=0xfe;
   wei=0;
  
   delay(speed);

   duan=1;
   P0=tabledu[bb];
   duan=0;
   P0=0xff;
   wei=1;
   P0=0xfd;
   wei=0;
      delay(speed);

   duan=1;
   P0=tabledu[cc];
   duan=0;
   P0=0xff; 
   wei=1;
   P0=0xfb;
   wei=0;
  
  
   delay(speed);
   duan=1;
   P0=tabledu[bai];
   duan=0;
   P0=0xff;
   wei=1;
   P0=0xf7;
   wei=0;
   
      delay(speed);
   duan=1;
   P0=tabledu[shi];
   duan=0;
   P0=0xff;
   wei=1;
   P0=0xef;
   wei=0;
     
   delay(speed);
      duan=1;
   P0=tabledu[ge];
   duan=0;
   P0=0xff;
   wei=1;
   P0=0xdf;
   wei=0;
       
    delay(speed);
}

void delay(uint speed)     //延时子程序。
{
 uint x,y;
 for(x=speed;x>0;x--)  
  for(y=110;y>0;y--);
}

 


菜鸟
2014-05-13 16:53:57     打赏
53楼

有几点想不通的,盼指点 呵呵:

 

//为何视频中写在P1=0xff前,主函数调用flag后,P1=0xff仍然可以起作用
//存疑。为何必须先送段选? 如果先送位选,数码管的数字会整体右移一位,最后一位跑到最前

(似乎第一次送的位选被无视了,第1个段选和第2个位选结合,依次往下,循环。。)花了几个小时在这里,始终想不通啊~

 

 


菜鸟
2014-05-13 19:08:22     打赏
54楼

刚和一个朋友交流,他用开发板操作先送位选没有问题,看来又是坑爹的仿真在作怪~


菜鸟
2014-05-13 20:35:32     打赏
55楼
矩阵键盘检测。
视频里现场写的代码给弄复杂了,难理解,简化一下以后好多了
主要就是那个与操作是不必要的。把按键取出的数与了以后,最后还要再重新从P3口取完整的数。因为如果不重新取P3口完整的值,会导致扫描2 、3、4行时取得同样的值。比如第一行第一个键被按下取的值是0xee,和0xf0与操作后得到0xe0。扫描第2行时,按下第一个键,取得的值是0xed,和0xf0与操作后仍然是0xe0,剩下的也同样。就没法区分了。
所以完全没必要做“与”操作,增加了复杂性,还容易出错。第一行直接判断是否为0xfe,第二行直接判断是否为0xfd,以此类推,就可以了。

#include
#define uint unsigned int
#define uchar unsigned char
sbit duan=P2^6;
sbit wei=P2^7;
sbit d1=P1^0;
uchar temp,num;
uchar code table[]={
0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,
0x39,0x5e,0x79,0x71};
void delay(uint);
void main()
{
wei=1;
P0=0xc0;
wei=0;
duan=1;
P0=0x00;
duan=0;
while(1)
{
/* P3=0xfe;
temp=P3;
temp=temp&0xf0;
if(temp!=0xf0) *///这里视频写的“与”操作根本多余的吧,改掉~~。
P3=0xfe; //第一行赋值为0,P3值即为0xfe
temp=P3;
if(temp!=0xfe) //如果为真,则执行if{}内代码,如果为假,
//则跳出if{}执行(去检测第2行按键有没有被按下)
{ //视频里用while循环,感觉用if好点
delay(5);
temp=P3;
//temp=temp&0xf0;
//if(temp!=0xf0)
if(temp!=0xfe)
{
temp=P3;//取回P3口的值(有可能是按下某件得到的值,必须要取),在swith case语句中进行比较。
switch(temp)
{
case 0xee:num=0;
break;
case 0xde:num=1;
break;
case 0xbe:num=2;
break;
case 0x7e:num=3; //分别比较几个算好的码,相同则赋给相关的值,并将其值赋给num,以方便显示时调用。
break;
}

duan=1;
P0=table[num];//打开段选口,让P0口显示数值
duan=0; //注意,这里,必须放在最里面的if下,否则逻辑出问题。
while(temp!=0xfe)//松手检测。
{
temp=0xfe;
}
}
}

P3=0xfd; //将第2行赋值为0,P3口的值即为0xfd
temp=P3;
if(temp!=0xfd) //开始扫描第2行有无键按下
{
delay(5);
temp=P3;
temp=temp&0xf0;
if(temp!=0xfd)
{
temp=P3;
switch(temp)
{
case 0xed:num=4;
break;
case 0xdd:num=5;
break;
case 0xbd:num=6;
break;
case 0x7d:num=7;
break;
}

duan=1;
P0=table[num];
duan=0;
while(temp!=0xfd)
{
temp=0xfd;
}
}
}

P3=0xfb; //开始扫描第三行有无键按下
temp=P3;
if(temp!=0xfb)
{
delay(5);
temp=P3;

if(temp!=0xfb)
{
temp=P3;
switch(temp)
{
case 0xeb:num=8;
break;
case 0xdb:num=9;
break;
case 0xbb:num=10;
break;
case 0x7b:num=11;
break;
}

duan=1;
P0=table[num];
duan=0;
while(temp!=0xfb)
{
temp=0xfb;

}
}
}

P3=0xf7; //开始扫描第4行有无键按下
temp=P3;
if(temp!=0xf7)
{
delay(5);
temp=P3;
if(temp!=0xf7)
{
temp=P3;
switch(temp)
{
case 0xe7:num=12;
break;
case 0xd7:num=13;
break;
case 0xb7:num=14;
break;
case 0x77:num=15;
break;
}

duan=1;
P0=table[num];
duan=0;
while(temp!=0xf7)
{
temp=0xf7;
}
}
}


}

}
void delay(uint z) //延迟子程序
{
uint x,y;
for(x=z;x>0;x--)
for(y=110;y>0;y--);
}


菜鸟
2014-05-13 21:02:53     打赏
56楼

前面没弄明白的都理清了,真的很轻松,晚上睡个好觉了。

帖子也不是我的笔记本,没事大家都可以进来聊聊自己啊,有空多交流交流,说不定就碰出点火花 呵呵

    

 


工程师
2014-05-13 23:39:56     打赏
57楼
问题的话可以单独开一个问题贴进行讨论。。。

助工
2014-05-14 15:08:55     打赏
58楼

我44 岁了,也玩单片机


院士
2014-05-14 16:33:52     打赏
59楼
你也很了不起

工程师
2014-05-14 21:41:49     打赏
60楼
我十八岁  哈哈学习中

共150条 6/15 |‹ 4 5 6 7 8 9 ›| 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]