单片机入门心得(连载)
(一)汇编入门
最近看到几个寻求单片机入门的帖子,一时心血来潮,把自己的一些入门心得写了下来,希望能对初学者有所帮助吧。
可能很多人学习单片机的开始都是一章一章的的去阅读教程,我也这样做过,结果就是没多久就昏昏欲睡了。对于初学者来说,什么随机存储器啊,只读存储器啊,寄存器啊,寻址方式啊,周期啊,指令啊。等等等等,简直就跟看天书一样。其实,我认为对于初学者来说,没必要了解这么多,学习总是一个循序渐进的过程,不要妄想着能一下子就把单片机的理解透了,然后再去动手做实验,做项目,这是很不现实的。
学习单片机的时候,要想着单片机能做什么我就学什么,我想要做什么就学什么,不懂,就翻书,再不行,就上网找。那么首先单片机能做些什么呢?单片机能做的事情很多很多,恐怕说个几天几夜都说不完。可能很多人会这么说,这么多的功能,这么多的例子,究竟从何学起啊!但是在我看来,单片机能做的只有两件事而你要做的也只有这两件事情:第一,输出高低电平;第二,接收高低电平的输入。假如单片机没有输入输出功能,那么程序编得在怎么超凡脱俗,也没有任何意义。因为,没有了跟外围器件的通信,单片机还有什么用呢!那么跟外围器件的通信靠的是什么呢?高电平(+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也挡不住,看到它,谁也跑不动。好了,言归正传,这一句其实用来调试程序是非常好用的,不知道怎么用,就先记住吧,或许以后有用,或许永远也没用,一家之言,每个人有每个人的方法。
写了一个晚上,竟然一不小心关掉了就没有了,不是有自动保存吗,为什么只保存了一点点啊
谢谢小编,对初学者还是有帮助的,希望再接再励!
请教下,对**.epm 、**.hex 的文档,有什么反汇编软件能转换成原代码吗!谢谢!
一般编译器都有反汇编功能,不过只能生成汇编代码,但是生成的代码跟原程序是有很大区别的,我从来不接反汇编的项目,还不如自己从新写一个
(二)I2C协议
如果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!
嗯,今天就先讲到这里吧。欲知后事,请听下回分解。
(二)I2C协议
如果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!
嗯,今天就先讲到这里吧。欲知后事,请听下回分解。
单片机60G的资料
讲得真仔细。谢谢。
讲得真仔细。谢谢。
不错不错,小编辛苦了
老师写的很风趣,很形象,只是我等菜鸟只能看C语言啊,,,,
谢谢小编,对初学者还是有帮助的,希望再接再励!
太好了,很风趣,跟着学习也不累,谢谢小编,不过,连到这就没了吗
学好单片机工资能过万么?
不错的文件,值得收藏