由于PIC處理器對(duì)位操作是最高效的,所以把一些BOOL變量放在一個(gè)內(nèi)存的位中,既可以達(dá)到運(yùn)算速度快,又可以達(dá)到最大限度節(jié)省空間的目的。
在C中的位操作有多種選擇。
*********************************************
如:char x;x=x|0B00001000; /*對(duì)X的4位置1。*/
char x;x=x&0B11011111; /*對(duì)X的5位清0。*/
把上面的變成公式則是:
#define bitset(var,bitno)(var |=1《《bitno)
#define bitclr(var,bitno)(var &=~(1《《bitno))
則上面的操作就是:char x;bitset(x,4)
char x;bitclr(x,5)
*************************************************
但上述的方法有缺點(diǎn),就是對(duì)每一位的含義不直觀,最好是能在代碼中能直觀看出每一位代表的意思,這樣就能提高編程效率,避免出錯(cuò)。
如果我們想用X的0-2位分別表示溫度、電壓、電流的BOOL值可以如下:
unsigned char x @ 0x20; /*象匯編那樣把X變量定義到一個(gè)固定內(nèi)存中。*/
bit temperature@ (unsigned)&x*8+0; /*溫度*/
bit voltage@ (unsigned)&x*8+1; /*電壓*/
bit current@ (unsigned)&x*8+2; /*電流 */
這樣定義后X的位就有一個(gè)形象化的名字,不再是枯燥的1、2、3、4等數(shù)字了。
可以對(duì)X全局修改,也可以對(duì)每一位進(jìn)行操作:
char=255;
temperature=0;
if(voltage)。..。..
*****************************************************************
還有一個(gè)方法是用C的struct結(jié)構(gòu)來定義:
如:
struct cypok{
temperature:1; /*溫度*/
voltage:1; /*電壓*/
current:1; /*電流*/
none:4;
}x @ 0x20;
這樣就可以用
x.temperature=0;
if(x.current)。..。
等操作了。
**********************************************************
上面的方法在一些簡(jiǎn)單的設(shè)計(jì)中很有效,但對(duì)于復(fù)雜的設(shè)計(jì)中就比較吃力。如象在多路工業(yè)控制上。前端需要分別收集多路的多路信號(hào),然后再設(shè)定控制多路的多路輸出。如:有2路控制,每一路的前端信號(hào)有溫度、電壓、電流。后端控制有電機(jī)、喇叭、繼電器、LED。如果用匯編來實(shí)現(xiàn)的話,是很頭疼的事情,用C來實(shí)現(xiàn)是很輕松的事情,這里也涉及到一點(diǎn)C的內(nèi)存管理(其實(shí)C的最大優(yōu)點(diǎn)就是內(nèi)存管理)。采用如下結(jié)構(gòu):
union cypok{
struct out{
motor:1; /*電機(jī)*/
relay:1; /*繼電器*/
speaker:1; /*喇叭*/
led1:1; /*指示燈*/
led2:1; /*指示燈*/
}out;
struct in{
none:5;
temperature:1; /*溫度*/
voltage:1; /*電壓*/
current:1; /*電流*/
}in;
char x;
};
union cypok an1;
union cypok an2;
上面的結(jié)構(gòu)有什么好處呢?聽小弟道來:
細(xì)分了信號(hào)的路an1和an2;
細(xì)分了每一路的信號(hào)的類型(是前端信號(hào)in還是后端信號(hào)out):
an1.in ;
an1.out;
an2.in;
an2.out;
然后又細(xì)分了每一路信號(hào)的具體含義,如:
an1.in.temperature;
an1.out.motor;
an2.in.voltage;
an2.out.led2;等
這樣的結(jié)構(gòu)很直觀的在2個(gè)內(nèi)存中就表示了2路信號(hào)。并且可以極其方便的擴(kuò)充。
如添加更多路的信號(hào),只需要添加:
union cypok an3;
union cypok an4;
。。。。。。。。。。。。。。。
從上面就可以看出用C的巨大好處。
PICC每日一貼。(初談如何從匯編轉(zhuǎn)向PICC)
小弟不才,特拋磚引玉,與大家共勉。
聊聊如何從匯編轉(zhuǎn)向PICC。
因?yàn)镠IDE-TECH PICC破解版很多,所以HIDE PICC有比其它PICC有更多的用戶,雖然它的編譯效率不是最好。最好的是CCS,但沒破戒版。。。,不過用HIDE PICC精心安排函數(shù)一樣可以獲得很高的編譯效率,還是人腦是第一的。
當(dāng)然要求你要有C語言的基礎(chǔ)。PICC不支持C++,這對(duì)于習(xí)慣了C++的朋友還得翻翻C語言的書。
C代碼的頭文件一定要有
#include《pic.h》
它是很多頭文件的集合,C編譯器在pic.h中根據(jù)你的芯片自動(dòng)栽入相應(yīng)的其它頭文件。
這點(diǎn)比匯編好用。
載入的頭文件中其實(shí)是聲明芯片的寄存器和一些函數(shù)。
順便摘抄一個(gè)片段:
static volatile unsigned char TMR0 @ 0x01;
static volatile unsigned char PCL @ 0x02;
static volatile unsigned char STATUS @ 0x03;
可以看出和匯編的頭文件中定義寄存器是差不多的。如下:
TMR0 EQU 0X01;
PCL EQU 0X02;
STATUS EQU 0X03;
都是把無聊的地址定義為大家公認(rèn)的名字。
一:怎么附值?
如對(duì)TMR0附值:
匯編中:MOVLW 200;
MOVWF TMR0;當(dāng)然得保證當(dāng)前頁面在0,不然會(huì)出錯(cuò)。
C語言:TMR0=200;//無論在任何頁面都不會(huì)出錯(cuò)。
可以看出來C是很直接了當(dāng)?shù)?。并且最大好處是操作一個(gè)寄存器時(shí)候,不用考慮頁面的問題。一切由C自動(dòng)完成。
二:怎么位操作?
匯編中的位操作是很容易的。在C中更簡(jiǎn)單。
C的頭文件中已經(jīng)對(duì)所有可能需要位操作的寄存器的每一位都有定義名稱:
如:PORTA的每一個(gè)I/O口定義為:RA0、RA1、RA2。。。RA7。
OPTION的每一位定義為:PS0、PS1、PS2 、PSA 、T0SE、T0CS、INTEDG 、RBPU。
可以對(duì)其直接進(jìn)行運(yùn)算和附值。
如:
RA0=0;
RA2=1;
在匯編中是:
BCF PORTA,0;
BSF PORTA,2;
可以看出2者是大同小異的,只是C中不需要考慮頁面的問題。
三:內(nèi)存分配問題:
在匯編中定義一個(gè)內(nèi)存是一件很小心的問題,要考慮太多的問題,稍微不注意就會(huì)出錯(cuò)。比如16位的運(yùn)算等。用C就不需要考慮太多。
下面給個(gè)例子:
16位的除法(C代碼):
INT X=5000;
INT Y=1000;
INT Z=X/Y;
而在匯編中則需要花太多精力。
給一個(gè)小的C代碼,用RA0控制一個(gè)LED閃爍:
#include《pic.h》
void main(){
int x;
CMCON=0B111; file://關(guān)掉A口比較器,要是有比較器功能的話。
ADCON1=0B110; file://關(guān)掉A/D功能,要是有A/D功能的話。
TRISA=0; file://A口全為輸出。
loop:RA0=!RA0;
for(x=60000;--x;){;} file://延時(shí)
goto loop;
}
說說RA0=!RA0的意思:PIC對(duì)PORT寄存器操作都是先讀取----修改----寫入。
上句的含義是程序先讀RA0,然后取反,最后把運(yùn)算后的值重新寫入RA0,這就實(shí)現(xiàn)了閃爍的功能。
?。ㄒ稽c(diǎn)經(jīng)驗(yàn))如何有效的實(shí)時(shí)控制LED閃爍。
在很多設(shè)計(jì)中需要有精彩而實(shí)用的LED閃爍來表示設(shè)備工作正常與否和工作狀態(tài)。
在一些實(shí)時(shí)性要求不高的設(shè)計(jì)中可以用插入延時(shí)來控制LED閃爍。
它的缺點(diǎn)現(xiàn)而易見:1:LED閃爍方式反映慢。2:在延時(shí)過程不能干其它工作(中斷除外),浪費(fèi)了資源。3:代碼雍長(zhǎng),真正控制LED就幾個(gè)個(gè)指令,其它的延時(shí)代碼占了99%的空間。
如果用TMR1或TMR2來做一個(gè)時(shí)鐘,上面的種種缺點(diǎn)就可以避免,使得你可以騰出大量的時(shí)間做更有效的工作。
下面是用TMR1作時(shí)鐘的C代碼(RB1、RB2、RB3控制LED)示例:
void set_tmr1(){
TMR1L=0xdc;
TMR1H=0xb; /*設(shè)定初值3036*/
T1CON=0B10001; /*設(shè)定TMR1 0.125s溢出一次*/
}
void interrupt time(){
if(TMR1IF){
T1CON=0B10000; /*關(guān)閉TMR1*/
TMR1L=0xdc;
TMR1H=0xb; /*TMR1設(shè)初值
T1CON=0B10001; /*從新設(shè)分頻比,打開TMR1*/
if(s++》8){ /*每S清0*/
s=0;
if(ss++》60)/*每分鐘清0*/
ss=0;
}
TMR1IF=0;
return;
}
}unsigned char s; /*每0.125S累加1*/
unsigned char ss; /*每1秒累加1*/
void main(){
set_tmr1();
。..。..。.; /*設(shè)定I/O口,開TMR1中斷*/
while(1){
if(。..) /*判斷閃爍方式語句,下同*/
RB1=(bit)(s》4); /*每1s閃爍一次,占空比50%(調(diào)節(jié)》后面值可以改變)*/
if(。..)
RB2=(bit)(!ss); /*每1分鐘閃爍一次,亮1秒,熄59秒*/
if(。..)
RB3=(bit)(s==0 || s==2 || s== 4 || s== 6); /*每0.25S閃爍一次*/
。..。..。..; /*其它工作*/
}
}
這樣的框架對(duì)于基于要求實(shí)時(shí)性高的軟件查詢的程序是很有效的。
在PICC中使用常數(shù)指針。
常數(shù)指針使用非常靈活,可以給編程帶來很多便利。
我測(cè)試過,PICC也支持常數(shù)指針,并且也會(huì)自動(dòng)分頁,實(shí)在是一大喜事。
定義一個(gè)指向8位RAM數(shù)據(jù)的常數(shù)指針(起始為0x00):
#define DBYTE ((unsigned char volatile *) 0)
定義一個(gè)指向16位RAM數(shù)據(jù)的常數(shù)指針(起始為0x00):
#define CWORD ((unsigned int volatile *) 0)
((unsigned char volatile *) 0)中的0表示指向RAM區(qū)域的起始地址,可以靈活修改它。
DBYTE[x]中的x表示偏移量。
下面是一段代碼1:
char a1,a2,a3,a4;
#define DBYTE ((unsigned char volatile *) 0)
void main(void){
long cc=0x89abcdef;
a1=DBYTE[0x24];
a2=DBYTE[0x25];
a3=DBYTE[0x26];
a4=DBYTE[0x27];
while(1);
}
2:
char a1,a2,a3,a4;
#define DBYTE ((unsigned char volatile *) 0)
void pp(char y){
a1=DBYTE[y++];
a2=DBYTE[y++];
a3=DBYTE[y++];
a4=DBYTE[y];
}
void main(void){
long cc=0x89abcdef;
char x;
x=&cc;
pp(x);
while(1);
}
3:
char a1,a2,a3,a4;
#define DBYTE ((unsigned char volatile *) 0)
void pp(char y){
a1=DBYTE[y++];
a2=DBYTE[y++];
a3=DBYTE[y++];
a4=DBYTE[y];
}
void main(void){
bank1 static long cc=0x89abcdef;
char x;
x=&cc;
pp(x);
while(1);
}
關(guān)于BOOL量的一點(diǎn)應(yīng)用。
/*bit型變量只能是全局的或靜態(tài)的,
而有時(shí)我門在實(shí)際應(yīng)用中既要改變某“位“變量的值;
又要保證這個(gè)函數(shù)的獨(dú)立性;那不可避免的要把
這個(gè)函數(shù)做成有參函數(shù),可是bit型變量是不能用做參數(shù)的;
那該咋辦泥?還好!有位段。
看看:*/
/********************************************/
union FLAG
{
unsigned char BYTE;
struct
{
unsigned char b0:1;
unsigned char b1:1;
unsigned char b2:1;
unsigned char b3:1;
unsigned char b4:1;
unsigned char b5:1;
unsigned char b6:1;
unsigned char b7:1;
}bool;
};
/********************************************/
union FLAG mode;
#define auto_bit mode.bool.b0
#define cool_bit mode.bool.b1
#define dar_bit mode.bool.b2
#define fan_bit mode.bool.b3
#define heat_bit mode.bool.b4
#define swing_bit mode.bool.b5
#define bed_bit mode.bool.b6
#define time_bit mode.bool.b7
/********************************************/
void mode_task(in_mode)
union FLAG *in_mode;
{
in_mode -》 bool.b0=1;
in_mode -》 bool.b5=1;
/*也可這樣寫
in_mode -》 BYTE|=0x21;*/
}
/********************************************/
void main(void)
{
mode.BYTE=0X00;
while(1)
{
mode_task(&mode);
}
}
/********************************************/
這樣寫多爽!
這里涉及了結(jié)構(gòu),聯(lián)合,位段,及指針;可得先把基礎(chǔ)概念搞清楚!
用PICC寫高效的位移操作。
在許多模擬串行通信中需要用位移操作。
以1-W總線的讀字節(jié)為例,原廠的代碼是:
unsigned char read_byte(void)
{
unsigned char i;
unsigned char value = 0;
for (i = 0; i 《 8; i++)
{
if(read_bit()) value| = 0 x 01《《i;
// reads byte in, one byte at a time and then
// shifts it left
delay(10); // wait for rest of timeslot
}
return(value);
}
雖然可以用,但編譯后執(zhí)行效率并不高效,這也是很多朋友認(rèn)為C一定不能和匯編相比的認(rèn)識(shí)提供了說法。
其實(shí)完全可以深入了解C和匯編之間的關(guān)系,寫出非常高效的C代碼,既有C的便利,又有匯編的效率。
首先對(duì) for (i = 0; i 《 8; i++)做手術(shù),改成遞減的形式:
for(i=8;i!=0;i--),因?yàn)?a target="_blank">CPU判斷一個(gè)數(shù)是否是0(只需要一個(gè)指令),比判斷一個(gè)數(shù)是多大來的快(需要3個(gè)指令)。
再對(duì)value| = 0 x 01《《i;做手術(shù)。
value| = 0 x 01《《i;其實(shí)是一個(gè)低水平的代碼,效率低,DALLAS的工程師都是NO1,奇怪為什么會(huì)如此疏忽。
仔細(xì)研究C語言的位移操作,可以發(fā)現(xiàn)C總是先把標(biāo)志位清0,然后再把此位移入字節(jié)中,也就是說,當(dāng)前移動(dòng)進(jìn)字節(jié)的位一定是0。
那么,既然已經(jīng)是0了,我們就只剩下一個(gè)步驟:判斷總線狀態(tài)是否是高來決定是否改寫此位,而不需要判斷總線是低的情況。
于是改寫如下代碼:
for(i=8;i!=0;i--){
value》》=1; //先右移一位,value最高位一定是0
if(read_bit()) value|=0x80; //判斷總線狀態(tài),如果是高,就把value的最高位置1
}
這樣一來,整個(gè)代碼變得極其高效,編譯后根本就是匯編級(jí)的代碼。
再舉一個(gè)例子:
在采集信號(hào)方面,經(jīng)常是連續(xù)采集N次,最后求其平均值。
一般的,無論是用匯編或C,在采集次數(shù)上都推薦用8,16,32、64、128、256等次數(shù),因?yàn)檫@些數(shù)都比較特殊,對(duì)于MCU計(jì)算有很大好處。
我們以128次采樣為例:注:sampling()為外部采樣函數(shù)。
unsigned int total;
unsigned char i,val;
for(i=0;i《128;i++){
total+=sampling();
}
val=total/128;
以上代碼是很多場(chǎng)合都可以看見的,但是效率并不怎么樣,狂浪費(fèi)資源。
結(jié)合C和匯編的關(guān)系,再加上一些技巧,就可以寫出天壤之別的匯編級(jí)的C代碼出來
首先分析128這個(gè)數(shù)是0B10000000,發(fā)現(xiàn)其第7位是1,其他低位全是0,那么就可以判斷第7位的狀態(tài)來判斷是否到了128次采樣次數(shù)
在分析除以128的運(yùn)算,上面的代碼用了除法運(yùn)算,浪費(fèi)了N多資源,完全可以用右移的方法來代替之
val=total/128等同于val=(unsigned char)(total》》7);
再觀察下去:total》》7還可以變通成(total《《1)》》8,先左移動(dòng)一位,再右移動(dòng)8位,不就成了右移7位了么?
可知道位移1,4,8的操作只需要一個(gè)指令哦。
有上面的概驗(yàn)了,就可以寫出如下的代碼:
unsigned int total;
unsigned char i=0
unsigned char val;
while(?。╥&0x80)){ //判斷i第7位,只需要一個(gè)指令。
total+=sampling();
i++;
}
val=(unsigned char)((total《《1)》》8); //幾個(gè)指令就代替了幾十個(gè)指令的除法運(yùn)算
哈哈,發(fā)現(xiàn)什么?代碼量竟然可以減少一大半,運(yùn)算速度可以提高幾倍。
再回頭,就可以理解為什么采樣次數(shù)要用推薦的一些特殊值了。
評(píng)論
查看更多