#include "reg52.h" #include "intrins.h" typedef unsigned int u16; typedef unsigned char u8; #define led P2 u16 ret; void delay(u16 i) { while(i--) {}; } void main(void) { while(1) { led=0xfe; //D1亮 ,其它燈不亮 delay(60000); _crol_(led,1); //0xFD D2亮,其它不亮 delay(60000); } }
我最開始的代碼大概是上面這個樣子的,我的預期是先是D1亮,然后是D1滅,D2亮,結果是始終是D1亮。百思不得其解,於是開始了漫長的調試。
感覺問題應該出在_crol_這個函數的前后,F9下了兩個斷點
ctrl+F5開啟調試
在watch中添加P2這個寄存器,led是P2的別名,因為我們想看它的值。
F10步過_crol_函數之后發現P2的值更本沒有改變,P2的值初始化是0xFF,然后經過我們的賦值,它是0xFE,經過_crol_它的值還是0xFE, 這就很奇怪了,然后我就想着是不是這個函數有什么問題
於是定義了一個變量ret來接受_crol_函數的返回值,並把ret也作為watch的對象,看一下它的值是怎么變化的。
經過調試發現最后ret的值正好是0xFD,所以_crol_的返回值才是我們要的結果。
_crol_(led,1)並不會修改led的值,它是把led的值復制一份,然后修改之后把這個結果以返回值的方式存放起來。
所以產生這個問題的原因是沒有閱讀_crol_的官方文檔,不知道被操作數的結果是以什么形式返回的。
這里是_crol_函數的官方解釋:
http://www.keil.com/support/man/docs/c51/c51__crol_.htm?_ga=2.234590429.235304523.1557741929-1916941500.1557741929
#include "reg52.h" #include "intrins.h" typedef unsigned int u16; typedef unsigned char u8; #define led P2 void delay(u16 i) { while(i--) {}; } void main(void) { while(1) { led=0xfe; //D1亮 ,其它燈不亮 delay(60000); led=_crol_(led,1); //0xFD D2亮,其它不亮 delay(60000); led=_crol_(led,1); //D3亮 delay(60000); led=_crol_(led,1); //D4亮 delay(60000); led=_crol_(led,1); //D5亮 delay(60000); led=_crol_(led,1); //D6亮 delay(60000); led=_crol_(led,1); //D7亮 delay(60000); led=_crol_(led,1); //D8亮 delay(60000); //讓D7開始亮 一直到D1 led=_cror_(led,1); //D7亮 delay(60000); led=_cror_(led,1); //D6亮 delay(60000); led=_cror_(led,1); //D5亮 delay(60000); led=_cror_(led,1); //D4亮 delay(60000); led=_cror_(led,1); //D3亮 delay(60000); led=_cror_(led,1); //D2亮 delay(60000); } }
這個程序的while循環最后一點代碼要解釋一下當D2亮起之后,P2的位狀態是1111 1101,然后delay一下,然后就到了循環開始的部分了,這里讓led初始化fe了,所以第一個燈又亮了,
也就是每循環一次,led就被初始化一次。
這個代碼是可以繼續優化的,D2到D8亮起來用的是同樣的代碼,我們可以放在一個for循環里面 D7到D2也可以放在一個for循環里面。
比如for (i=0;i<7;i++)
{
led=_crol(led,1);
}
for (i=0;i<6;i++)
{
led=_cror(led,1);
}
這里有個很重要的點,就是為什么我們可以通過循環右移和循環左移來控制燈的亮滅?
其實就是從原理圖來的,我們看下圖再說。
有圖可知,P2的第0位控制D1,等等等 P2的第7位控制D8,而且只需要將P2的第0位設置位低電平就可以讓D1亮起來,設置高電平就可以讓D1滅了。
P2是什么它是一個寄存器的名字,它有8個位,從第0位到第7位
根據流水燈的定義,先讓D1亮起來,那么此時需要P2的值是1111 1110 然后需要D2亮起來,此時需要P2的值是1111 1101,然后需要D3亮起來,此時需要P2的值是1111 1110
.。。。需要D8亮起來,需要P2的值是0111 1111
從1111 1101 到1111 1101 到1111 1011 再到0111 1111 是不是發現就是0的位置向左移動了?_crol_就有這種功能啊。
_crol_的實現是這樣的,它叫做循環左移,你把1111 1110 左移一位之后 ,那么新生成的那個數是1111110X,最末尾的這個X是多少呢?就是1111 11110 最高位被擠出去的那一位,然后補回到了1111110X的最低位,也就是最后的結果是1111 1101 ,循環左移,這個循環很重要。
我畫了一張圖解釋這個過程,循環右移也是一樣的。
C語言中還有左移和右移的操作,能不能用在這里呢?
while(1) { P2=0xFE; //1111 1110 delay(60000); P2=P2<<1; delay(60000); P2=P2<<1; delay(60000); }
從0xFE(1111 1110)每次左移一位的結果
1111 1100
1111 1000
可以看出左移運算符是把高位擠出去之后,新生成的數的低位是用0填充的,並不能滿足我們流水燈的定義。
所以最符號我們要求的就是循環左移函數和循環右移函數。