找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 2327|回复: 5
打印 上一主题 下一主题
收起左侧

单片机入门心得(连载)

[复制链接]
跳转到指定楼层
楼主
ID:67563 发表于 2014-11-27 19:14 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
(一)汇编入门
最近看到几个寻求单片机入门的帖子,一时心血来潮,把自己的一些入门心得写了下来,希望能对初学者有所帮助吧。
可能很多人学习单片机的开始都是一章一章的的去阅读教程,我也这样做过,结果就是没多久就昏昏欲睡了。对于初学者来说,什么随机存储器啊,只读存储器啊,寄存器啊,寻址方式啊,周期啊,指令啊。。。等等等等,简直就跟看天书一样。其实,我认为对于初学者来说,没必要了解这么多,学习总是一个循序渐进的过程,不要妄想着能一下子就把单片机的理解透了,然后再去动手做实验,做项目,这是很不现实的。
学习单片机的时候,要想着单片机能做什么我就学什么,我想要做什么就学什么,不懂,就翻书,再不行,就上网找。那么首先单片机能做些什么呢?单片机能做的事情很多很多,恐怕说个几天几夜都说不完。可能很多人会这么说,这么多的功能,这么多的例子,究竟从何学起啊!但是在我看来,单片机能做的只有两件事而你要做的也只有这两件事情:第一,输出高低电平;第二,接收高低电平的输入。假如单片机没有输入输出功能,那么程序编得在怎么超凡脱俗,也没有任何意义。因为,没有了跟外围器件的通信,单片机还有什么用呢!那么跟外围器件的通信靠的是什么呢?高电平(+3.3V或+5V)和低电平(0V)。那么我们的目的就很明确了,学习单片机的目的就是让单片机的各个管脚输入或输出高或低电平。在程序上代表高低电平的就是数字量1和0。也就是说,程序的最终目的就是在各个管脚上输入或输出1或0。所有的程序都是为了达成这个目的而设计的。换句话来说,只要能在你想要的管脚输入或输出你想要实现的高或低电平,那么你的目的就已经达到了,不要去管你的程序有多么的臃肿或是不堪入目,这个会随着你学习的深入和经验的积累而逐渐改善,不需要着急。
举个最简单的例子,在单片机的P1.0的管脚上接一个LED灯,要让LED灯点亮,就是在P1.0管脚上输出高电平,要让LED灯熄灭,就是在P1.0脚上输出低电平。那么怎么样才能在P1.0脚上输出高或低电平呢?不知道,那就去翻书一条一条的去找指令。哦,找到一条SETB置位指令,置位P1.0那不就是把1赋给P1.0吗,P1.0置1,不就是输出高电平了吗?至于是不是,谁试谁知道。不过,先不要着急,既然找到了输出高电平的指令,那么顺便找找输出低电平的指令。好了,没错,就是你了CLR。那么现在就可以编程序了:
             ORG        0000H
             JMP         MAIN
             OGR        0030H               ;如果不能理解这几条指令的意思,那就直接套用就可以了
MAIN:
             SETB       P1.0                  ;输出高电平,点亮LED灯
             CLR         P1.0                  ;输出低电平,熄灭LED灯
             END
好了,程序完成,很简单吧。可是,这个只是一亮一灭,我要它不停的闪烁怎么办?简单!多加一句跳转指令就行了,跳转指令上面就有JMP,那好吧,再改一下程序
             ORG        0000H
             JMP         MAIN
             OGR        0030H
MAIN:
             SETB       P1.0
             CLR         P1.0
             JMP         MAIN
             END
大功告成
可是,程序运行之后,看不到LED灯一亮一灭啊!怎么回事?这是当然了,单片机CUP的运行速度是以微秒来计的,人的眼睛是反应不过来的。那要怎么办呢?让CPU停一下等个一两秒再执行下一条指令?那显然不行,地球人都知道。那就找点事情给CPU去忙吧,不管它干什么都行,只要再这段时间内不要去碰P1.0管脚就行了。那么让它去做什么呢,国际上-_-!!!通常让它去数数,因为CPU每数一个数的时间都是一样的,比如说1微秒,那么数1 000个数,就是1毫秒,数1 000 000个数就是一秒。那么怎么样让CPU去数数呢?继续找指令表,我找。。。找到一个INC,每执行一次,操作数加1,那我要数到1 000 000的时候停止呢,怎么办?不知道。不知道!那要你干什么,一边去吧你,顺便把你兄弟DEC也带走,我不想再见到你们!我再找。。。这个好像有点用JZ,累加器A中为0就跳转,好像可以啊,我先让CPU跳一边去然后给A一个数1 000 000,让A从1 000 000减到0,A为0时再跳转回来不就行了?不过累加器A是什么?不知道?那就再翻书。。。哦,好像A最大只能到255,到不了1 000 000,怎么办?255就255吧,先试试再说,看能不能看出变化。那么怎么给A送数呢?MOV呗!好了,那谁谁谁,你给我回来,DEC别看了,说的就是你!嗯,再改一下程序
             ORG        0000H
             JMP         MAIN
             OGR        0030H
MAIN:
             SETB       P1.0
             MOV        A,#255            ;给A一个数,让CPU去数
             JMP         WAIT               ;CPU给我一边数数去
LED_OFF:
             CLR         P1.0
             MOV        A,#255            ;
             JMP         WAIT1             ;再来一个
LED_ON:
             JMP         MAIN
WAIT:
             DEC        A                     ;A-1
             JZ           LED_OFF         ;等于0就跳回去
             JMP         WAIT              ;不等于0就继续减
WAIT1:
             DEC        A                     ;A-1
             JZ           LED_ON          ;等于0就跳回去
             JMP         WAIT1            ;不等于0就继续减
             END
编译,,排错,运行,大功告成
好了,程序编完了,也能运行了,不过现在高兴是不是太早了,你在JMP来JMP去的,JMP的我头都晕了,那我要是要再延长一点时间,你岂不是要JMP个没完没了了?!难道就没有别的方法了吗?那好吧,我在翻翻书。真是书到用时方恨少啊。。。咦,这个看起来有点意思,CALL,是不是跟打电话一样,不管你在哪里,一个CALL,就能找到你啊。不过这个ACALL和LCALL又有神马不同呢,难道还有国内长途和国际长途之分?不管了,就用你了LCALL,反正不用花钱。
             ORG        0000H
             JMP         MAIN
             OGR        0030H
MAIN:
             SETB       P1.0
             MOV        A,#255
             LCALL     WAIT               ;我CALL
             LCALL     WAIT               ;我再CALL
             LCALL     WAIT               ;
             LCALL     WAIT               ;
             LCALL     WAIT               ;我CALL,CALL,CALL。。。
             CLR         P1.0
             LCALL     WAIT
             LCALL     WAIT
             LCALL     WAIT
             LCALL     WAIT              ;哈哈哈。。。CALL个够,爽
             JMP         MAIN
WAIT:  
             DEC        A                     ;A-1
             JNZ         WAIT              ;没数完,继续。。。
             RET                               ;数完了,那我挂电话了,有时间再CALL你啊
             END
好了,这回看起来舒服多了。不过累加器A,看起来你有点意见?A:“废话!你不知道老子很忙的吗!分分钟几十万上下,你叫我给你数数?你确定,你的脑袋没被驴给踢过?老子纵横机湖几十年,阅人无数,就没见过你这么白的程序员!”好吧,大哥,你牛,我惹不起你我躲的起。我再翻书,幸好这不是在考试,我想怎么翻就怎么翻。。。有了!就是你了DJNZ,减1不为0就跳转。咦,怎么没有减1为0跳转的呢?也不知道创造汇编的那位大神是怎么想的。好吧,这不是我们这些小菜鸟该管的,还是改我的程序比较靠谱一点
             ORG        0000H
             JMP         MAIN
             OGR        0030H
MAIN:
             SETB       P1.0
             MOV        R0,#255         ;那就换一个呗
             LCALL     WAIT               ;我CALL
             LCALL     WAIT               ;我再CALL
             LCALL     WAIT               ;
             LCALL     WAIT               ;
             LCALL     WAIT               ;我CALL,CALL,CALL。。。
             CLR         P1.0
             LCALL     WAIT
             LCALL     WAIT
             LCALL     WAIT
             LCALL     WAIT              ;哈哈哈。。。
             JMP         MAIN
WAIT:  
             DJNZ       R0,WAIT        ;没数完,继续
             RET                               ;数完了,那我挂电话了,有时间再CALL你啊
             END
好了,终于终于终于编完了,其实单片机也不怎么难嘛,呵呵。
最后,再介绍一句,其实
DJNZ       R0,WAIT
这句,还可以换成
DJNZ       R0,$
这样,减1不为0就等待,其实我想介绍的是这一句
JMP         $
这是个死循环,原地跳步,用来调试程序是非常好用的。不知道创造这句的大神是不是要告诉全世界的程序员,美元的魅力连CPU也挡不住,看到它,谁也跑不动。好了,言归正传,这一句其实用来调试程序是非常好用的,不知道怎么用,就先记住吧,或许以后有用,或许永远也没用,一家之言,每个人有每个人的方法。

评分

参与人数 1黑币 +50 收起 理由
admin + 50 共享资料的积分奖励!

查看全部评分

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏2 分享淘帖 顶 踩
回复

使用道具 举报

沙发
ID:67563 发表于 2014-11-27 19:15 | 只看该作者
(二)IC协议

如果CCAV要举办“菊花郎杯我最喜爱的单片机通信协议”的话,我以菊花郎集团首席执行官名誉担保,I2C一定会摘得

桂冠。什么?你说SPI应用更广泛?但是,对于初学者来说SPI在很多时候往往数据发送过去之后,就如石沉大海,完

全不知所以。当年,小菜鸟我操控SPI协议的时候,简直就像是中了七伤拳的至尊宝:“二当家的,老子踩了你那么久

,就算不痛也随便应付两声嘛!”相比之下I2C就乖巧多了,至少你踩他8脚,他会回应一声。现在,大家知道我为什

么要选I2C了吧!

好了,我们言归正传,I2C的ACK确实是非常的好用,I2C有没有写对,请看ACK!一目了然,真的是太方便了!I2C真的

这么简单吗?是不是参照着协议图写完就OK了呢?未必!以我自己的经历来看,曾经无数次的,拿着仿真器,对着I2C

协议时序图,一个时钟一个时钟的校验,或是在客户那里对着示波器一个脉冲一个脉冲的读I2C数据。那时候,总是担

心着是不是多了或是少了一个脉冲啊,究竟是在上升沿还是下降沿读写数据啊。。。等等,总之,就是心里没底。哎

,小菜鸟真是伤不起,伤不起啊伤不起。。。

咦,又跑题了。对于I2C,我总结了3点原则。自从有了这3点(怎么听起来这么别扭呢),再也不用为I2C协议

担心啦!请看:
(1)、在I2C起始信号之前,和结束信号之后,确保SCL和SDA脚为高电平;
(2)、在每个函数(结束信号函数除外)之后,确保SCL为低电平;
(3)、请参考以上两条(哈哈,其实只有两点啦,但是我要是说只有两点那岂不是很没面子)
是不是不明觉厉呢。别着急,我们慢慢来分析。
我们先从起始和结束信号函数开始分析,
void I2c_Start(void)
{
        SCL_HIGH;
        SDA_HIGH;
        nop();
        SDA_LOW;
        nop();
        SCL_LOW;
}
void I2c_Stop(void)
{
        SDA_LOW;
        nop();
        SCL_HIGH;
        nop();
        SDA_HIGH;
}
对照这两个函数请看第一点,对于I2c_Stop()的SCL_HIGH和SDA_HIGH,这两个没什么可说的,这是协议的结束信号,

只要没写反了就行,连这个也写反了的童鞋,自己面壁思过去。这里要说明一下的是I2c_Start()开始的两个SCL_HIGH

和SDA_HIGH,有没有童鞋觉得每次结束信号都把SCL和SDA拉高了,还有没有必要每次都在I2c_Start()里面再写一遍啊

,太浪费了,勤俭节约是中华民族的光荣传统,可是我要说的是,可能在某个不为人知

的阴暗角落,或许I2C的某根线就被偷偷的改变了状态。这怎么可能!我自己编的程序难度有没有用到I2C还不知道吗

?可是,在工作中可能你接手的程序已经不知道经过了多少人的缝缝补补,特别是在一些大型企业中尤为如此。So,

安全第一,安全第一!还有就是你编写的I2C程序可能会有他人调用,与人方便自己方便吧!对了,还有第2点,这点

跟I2c_Stop()没就关系,你可以凉凉去了。当然对于I2c_Start()也没什么可说的,协议写了嘛,SCL为低,必须的!

好了,你们两个可以去领盒饭了,下一个I2c_Write(u8 uByte)轮到你了,还看,说的就是你了!上来躺好,放心不用

解剖,我们只看两点,好吧,就看一点
void I2c_write(u8 uByte)        //unsigned char 定义为u8,以后不在说明
{
        u8 i;
        
        for(i=0; i<8; i++)
        {
                if(uByte & 0x80)
                {
                        SDA_HIGH;
                }
                else
                {
                        SDA_LOW;
                }
                SCL_HIGH;        //我高,划破长空,前面的一切不管
                nop();
                SCL_LOW;        //我低,一场春梦,生与死一切成空
                uByte <<= 1;
        }
}
写数据其实就是8个时钟脉冲,这个做个循环就行了,依次把一个字节的数据写到SDA脚。关键是SCL的时钟脉冲怎么写

,根据第二点准则,所有I2C通信函数都以SCL低电平结束(结束信号除外),所以,写脉冲首先就是把SCL拉高,然后

再拉低,这就是一个bit的写时序了,循环8次,结束写字节操作。你看,最后还是以SCL低为结束,完美收官!耶!真是太有默契了,赞一个先!

当当当当,下面有请我们今天的男猪脚I2c_WriteAck()隆重登场!咦,你脚下踩的是什么?
u8 I2c_WriteAck(void)
{
        u8 ack;

        SDA_IN;                //对于51单片机来说,设置为输入其实就是把管脚置1,所以这句等同于SDA_HIGH,
                        //这句很重要,因为你可能在写数据的时候把SDA拉低了,所以比不可少
        SCL_HIGH;
        nop();
        if(READ_SDA)        //Only you 能保护我,让螃蟹和蚌精无法吃我
                ack;= 1;
        else
                ack;= 0;
        SCL_LOW;
        SDA_OUT;        //对于51来说,这句为空

        return ack;
}
根据第二准则,写操作必定是以SCL低为结束,那么,SCL也是以拉高为开始。现在大家知道我为什么要强调必须以SCL

低为函数的结束了吧!这样一来对于每一个函数,都可以独立去分析,不必去理会在这之前的时钟是什么状态。别看

ACK这么简单,I2C协议是生是死就看他的脸色了!对于用I2C通信的所有产品,不管别人对我说的是什么问题,我首先

闻到第一句就是ACK有没有响应?嗯,调试等一下再说,先看读操作。“O~O~Only you!”O你个头。。。

好了,轮到你们了,I2c_Read()、I2c_ReadAck()和I2c_ReadNAck(),都以前上来吧
u8 I2c_Read(void)
{
        u8 i;
        u8 uByte;
        
        SDA_IN;                //亲,别忘了伦家哦
        for(i=0; i<8; i++)
        {
                uByte <<= 1;
                SCL_HIGH;
                if(READ_SDIO)
                        uByte |= 0x01;
                SCL_LOW;
                nop();
        }
        
        SDA_OUT;        //“我呢,我呢!”你啊,看情况吧
        
        return uByte;
}
void I2c_ReadAck(void)
{
        SDA_LOW;
        nop();
        SCL_HIGH;
        nop();
        SCL_LOW;
}
void I2c_ReadNack(void)
{
        SDA_HIGH;
        nop();
        SCL_HIGH;
        nop();
        SCL_LOW;
}
这三个函数就不一一分析了,大家以此类推吧。

呼,I2C协议至此大功告成了,照此分析,童鞋们再也不用担心出错了!大家想怎么玩就怎么玩,即使是量大的日子也

不用担心侧漏了(咦,这句好像在哪里听过)!

好了,高兴三分钟就够了。是不是I2C协议写好就完事OK了呢?我可以很负责任的告诉你:NO!
嗯,今天就先讲到这里吧。欲知后事,请听下回分解。。。
回复

使用道具 举报

板凳
ID:68618 发表于 2014-11-27 22:37 来自手机 | 只看该作者
楼主太给力了,我刚看了两次,非常精辟。对我很有用,既学到了技术又提高了信心感谢
回复

使用道具 举报

地板
ID:26188 发表于 2014-11-27 23:49 来自手机 | 只看该作者
一口气看完了,意犹未尽,等待楼主的第3篇连载
回复

使用道具 举报

5#
ID:1 发表于 2014-11-28 16:09 来自手机 | 只看该作者
写的不错,对初学者很有帮助。看来此贴可以加入精华了。等待楼主的下一篇文章
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表