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