久久久久久久999_99精品久久精品一区二区爱城_成人欧美一区二区三区在线播放_国产精品日本一区二区不卡视频_国产午夜视频_欧美精品在线观看免费

 找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 18854|回復: 11
打印 上一主題 下一主題
收起左側

第12章 指針的基礎與1602液晶的初步認識

  [復制鏈接]
跳轉到指定樓層
樓主
ID:1 發表于 2013-9-28 15:03 | 只看該作者 |只看大圖 回帖獎勵 |倒序瀏覽 |閱讀模式
鄭重聲明:
本教材現以連載的方式由網絡發布,并將于2014年由清華大學出版社出版最終完整版,版權歸作者和清華大學出版社所有。本著開源、分享的理念,本教材可以自由傳播及學習使用,但是務必請注明出處來自www.kingst.org,未經作者同意不得用于任何商業目的。最終解釋權歸金沙灘工作室所有.

我們在上C語言課的時候,學到指針,每一位教C語言的老師都會告訴我們一句:指針是C語言的靈魂。由此可見,指針是否學會是判斷一個人是否真正學會C語言的重要指標之一,但是很多同學只知道其重要性,卻沒學會其靈活性。
簡單的程序,100來行代碼,不需要指針我們可以輕松搞定,但是當代碼寫到幾千上萬行甚至更多的時候,利用指針就可以直接而快速的處理內存中的各種數據結構中的數據,特別是數組、字符串和內存的動態分配等,它為函數之間各類數據傳遞提供了簡潔便利的方法。說了這么多作用估計大家沒用過指針也體會不到,但這里就是表達這樣一個意思,指針很重要,必須要學會。
指針相對其他知識點來說比較難講,主要在于例子不好舉。簡單的程序用指針去做會把簡單的程序搞復雜,復雜的程序用指針去寫牽扯的知識太多可能又不好理解。從一個角度講,沒學會指針就等于沒學會C語言,所以再難也不是我們學不好的理由。這節課我就從我對指針的理解盡可能的把指針形象的介紹給大家,幫大家啃下這塊硬骨頭,同學們學習這節課內容也要打起十二分精神,集中注意力認真去學,爭取拿下指針。
12.1 指針的基本概念和指針變量的聲明12.1.1 變量的地址
要研究指針,我們得先來深入理解內存地址這個概念。打個比方:整個內存就相當于一個擁有很多房間的大樓,每個房間都有房間號,比如從101102103直到NNN,我們可以說這些房間號就是房間的地址,相應的內存中的每個單元也都有自己的編號,比如從0x000x010x02直到0xNN,我們同樣可以說這些編號就是內存單元的地址。房間里可以住人,對應的內存單元里就可以“住進”變量了:假如一位名字叫A的人住在101房間,我們可以說A的住址就是101,或者101就是A的住址;對應的,假如一個名為x的變量住在編號為0x00的這個內存單元中,那么我們可以說變量x的內存地址就是0x00,或者0x00就是變量x的地址。
基本的內存單元是字節,英文單詞為Byte,我們所使用的STC89C52RC單片機共有512字節的RAM,就是我們所謂的內存,但它分為內部256字節和外部256字節,我們僅以內部的256字節為例,很明顯其地址的編號從0開始就是0x000xFF。我們用C語言定義的各種變量就存在0x000xFF的地址范圍內,而不同類型的變量會占用不同數量的內存單元,即字節,可以結合前面講過的C語言變量類型深入理解。現在,假如我們現在定義了unsigned char a = 1;  unsigned char b = 2;  unsigned int c = 3;  unsigned long d = 4; 這樣4個變量,我們把這4個變量分別放到內存中,就會是表12-1中所列的樣子,我們先來大概了解一下他們的存儲方式。
12-1 變量存儲方式
內存地址
存儲的數據
0xFF
... ...
... ...
... ...
0x07
d
0x06
d
0x05
d
0x04
d
0x03
c
0x02
c
0x01
b
0x00
a
    變量abcd之間的變量類型不同,因此在內存中所占的存儲單元也不一樣,ab都占一個字節,c占了2個字節,而d占了4個字節。那么,a的地址就是0x00b的地址就是0x01c的地址就是0x02d的地址就是0x04,它們的地址的表達方式可以寫成:&a&b&c&d。這樣就代表了相應變量的地址,C語言中變量前加一個&表示取這個變量的地址,&這個符號就叫做“取址符”。
講到這里,有一點延伸內容,大家可以了解下:比如變量cunsigned int類型的,占了2個字節,存儲在了0x020x03這兩個內存地址上,那么0x02是他的低字節還是高字節呢?這個問題由所用的C編譯器與CPU架構共同決定,單片機類型不同就有可能不同,大家知道這么回事即可。比如:在我們使用的keil+51單片機的環境下,0x02存的是高字節,0x03存的是低字節。這是編譯底層實現上的細節問題,并不影響上層的應用,如下這兩種情況在應用上就絲毫不受這個細節的影響:強制類型轉換——b = (unsigned char) c,那么b的值一定是c的低字節;取地址——&c,則得到一定是0x02,這都是C語言本身所決定的規則,不因單片機編譯器的不同而有所改變。
實際生活中,我們要尋找一個人有兩種方式,一種方式是通過它的名字來找人,還有第二種方式就是通過它的住宅地址來找人。我們在派出所的戶籍管理系統的信息輸入方框內,輸入小明的家庭住址,系統會自動指向小明的相關信息,輸入小剛的家庭住址,系統會自動指向小剛的相關信息。這個供我們輸入地址的這個方框,在戶籍管理系統叫做“地址輸入框”。
那么,在C語言中,我們要訪問一個變量,同樣有兩種方式:一種是通過變量名來訪問,另一種自然就是通過變量的地址來訪問了。在C語言中,地址就等同于指針,變量的地址就是變量的指針。我們要把地址送到上邊那個所謂的“地址輸入框”內,這個“地址輸入框”既可以輸入x的指針,又可以輸入y的指針,所以相當于一個特殊的變量——保存指針的變量,因此稱之為指針變量,簡稱為指針,而通常我們說的指針就是指指針變量。
地址輸入框輸入誰的地址,指向的就是這個人的信息,而給指針變量輸入哪個普通變量的地址,它自然就指向了這個變量的內容,通常的說法就是指針指向了該變量。
12.1.2 指針變量的聲明
C語言中,變量的地址往往都是編譯系統自動分配的,對我們用戶來說,我們是不知道某個變量的具體地址的。所以我們定義一個指針變量p,把普通變量a的地址直接送給指針變量p就是p = &a;這樣的寫法。
對于指針變量p的定義和初始化,一般有兩種方式,這兩種方式,初學者很容易混淆,因此這個地方沒別的方法,就是死記硬背,記住即可。
方法1:定義時直接進行初始化賦值。
       unsigned   char   a;
       unsigned   char   *p = &a;
方法2:定義后再進行賦值。
       unsigned   char   a;
       unsigned   char  *p;
       p = &a;
大家仔細看會看出來這兩種寫法的區別,它們都是正確的。我們在定義的指針變量前邊加了個*,這個*p就代表了這個p是個指針變量,不是個普通的變量,它是專門用來存放變量地址的。此外,我們定義*p的時候,用了unsigned char來定義,這里表示的是這個指針指向的變量類型是unsigned char型的。
指針變量似乎比較好理解,大家也能很容易就聽明白。但是為什么很多人弄不明白指針呢?因為在C語言中,有一些運算和定義,他們是有區別的,很多同學就是沒弄明白他們的區別,指針就始終學不好。這里我要重點強調兩個區別,只要把這兩個區別弄明白了,起碼指針變量這部分就不是問題了。這兩個重點現在大家死記硬背,直接記住即可,靠理解有可能混淆概念。
第一個重要區別:指針變量p和普通變量a的區別。
我們定義一個變量a,同時也可以給變量a賦值a = 1,也可以賦值a = 2
我們定義一個指針變量p,另外還定義了一個普通變量a=1,普通變量b=2,那么這個指針變量可以指向a的地址,也可以指向b的地址,可以寫成p = &a,也可以寫成p = &b,但是就不能寫成p = 1或者p = 2或者p = a,這三種表達方式都是錯的。
因此這個地方,不要看到定義*p的時候前邊有個unsigned char型,就錯誤的賦值p=1,這個只是說明p指向的變量是這個unsigned char類型的,而p本身,是指針變量,不可以給他賦值普通的值或者變量,后邊我們會直接把指針變量稱之為指針,大家要注意一下這個小細節。
    前邊這個區別似乎比較好理解,還有第二個重要區別,一定要記清楚。
第二個重要區別:定義指針變量*p和取值運算*p的區別。
*”這個符號,在我們的C語言有三個用法,第一個用法很簡單,乘法操作就是用這個符號,這里就不講了。
第二個用法,是定義指針變量的時候用的,比如unsigned char *p,這個地方使用“*”代表的意思是p是一個指針變量,而非普通的變量。
還有第三種用法,就是取值運算,和定義指針變量是完全兩碼事,比如:
unsigned   char   a = 1;
unsigned   char   b = 2;
unsigned   char  *p;
p = &a;
b = *p;
這樣兩步運算完了之后,b的值就成了1了。在指針這塊,&a表示取a這個變量的地址,把這個地址送給p之后,再用*p運算表示的是取指針變量p指向的地址的變量的值,又把這個值送給了b,最終的結果相當于b=a。同樣是*p,放在定義的位置就是定義指針變量,放在程序中就是取值運算。
這兩個重要區別,大家可以反復閱讀三四遍,把這兩個重要區別弄明白,指針的大門就順利的踏進去一只腳了。至于詳細的用法,我們后邊用得多了就會慢慢熟悉起來了。
12.1.3 指針的簡易應用程序
前邊我們提到了,指針的意義往往在小程序里是體現不出來的,對于簡易程序來說,有時候用了指針,反而可能比沒用指針還麻煩,但是為了讓大家鞏固一下指針的用法,我還是寫了個使用指針的流水燈程序,目的是讓大家從簡單程序開始了解指針,當程序復雜的時候不至于手足無措。

#include <reg52.h>

sbit  ADDR0 = P1^0;
sbit  ADDR1 = P1^1;
sbit  ADDR2 = P1^2;
sbit  ADDR3 = P1^3;
sbit  ENLED = P1^4;

void ShiftLeft(unsigned char *p);

void main(void)
{
    unsigned int  i = 0;
    unsigned char buf = 0x01;

    ADDR0 = 0;   //選擇獨立LED
    ADDR1 = 1;
    ADDR2 = 1;
    ADDR3 = 1;
    ENLED = 0;   //LED總使能

    while(1)
    {
        P0 = ~buf;               //緩沖值取反送到P0
        for (i=0; i<20000; i++); //延時
        ShiftLeft(&buf);         //緩沖值左移一位
        if (buf == 0)            //如移位后為0則重賦初值
        {
            buf = 0x01;
        }
    }
}

void ShiftLeft(unsigned char *p)
{
    *p = *p << 1;  //利用指針變量可以向函數外輸出運算結果
}

這是一個使用指針實現流水燈的例子,純粹是為了講指針而寫這樣一段程序,程序中傳遞的是buf的地址,把這個地址值直接傳遞給函數ShiftLeft的形參指針變量p,也就是p指向了buf。對比之前的函數調用,大家是否看明白,如果是普通變量傳遞,只能單向的,也就是說,主函數傳遞給子函數的值,子函數只能使用卻不能改變。而現在我們傳遞的是指針,不僅僅我們的子函數可以使用buf里邊的值,而且還可以對buf里邊的值進行改變。
此外再強調一句,只要是*p前邊帶了變量類型如unsigned char,就是表示定義了一個指針變量p,程序中的*p,是指p所指向的內容。
通過理論的學習和這樣一個例程,我想大家對指針應該有概念了,至于它的靈活應用,需要我們在后邊的程序中去體會,理論上就不再過多贅述了。
[size=14.0000pt]12.2 [size=14.0000pt]指向數組元素的指針[size=14.0000pt]12.2.1 [size=14.0000pt]指向數組元素的指針的介紹和運算法則
所謂指向數組元素的指針,其本質還是變量的指針。因為我們的數組里的每個元素,其實都可以直接看成是一個變量,所以指向數組元素的指針,也就是變量的指針。
指向數組元素的指針不難,但很常用。我們用程序來解釋會比較直觀一些。
unsigned  char  number[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
unsigned  char *p;
如果我們寫p = &number[0];那么指針p就指向了number的第0號元素,也就是把number[0]的地址賦值給了p,同理,如果寫p =&number[1];p就指向了數組number的第1號元素,p=&number[x];其中x的取值范圍是0<=x<=9,表示p指向了數組number的第x號元素。
指針本身,也可以進行幾種簡單的運算,這幾種運算對于數組元素的指針來說應用最多。
1、比較運算。比較的前提是兩個指針指向同種類型的對象,比如兩個指針變量pq他們指向了具有同種數據類型的數組,那他們可以進行<>>=<===等關系運算。如果p==q為真的話,表示這兩個指針指向的是同一個元素。
2、指針和整數可以直接進行加減運算。比如還是上邊我們那個指針p和數組number,如果p = &number[0],那么p+1就指向了number[1]p+9就指向了number[9]。當然了,如果p = &number[9]p-9也就指向了number[0]
3、兩個指針變量在一定條件下可以進行減法運算。如p = &number[0]; q = &number[9];那么q-p的結果就是9。但是這個地方大家要特別注意,這個9代表的是元素的個數,而不是真正的地址差值。如果我們的number的變量類型是unsigned int型,占2個字節,q-p的結果依然是9,他代表的是數組元素的個數。
在數組元素指針這里還有一種情況,就是我們的數組名字代表了數組元素的首地址,也就是說
p = &number[0];
p = number;
這兩種表達方式是等價的,因此以下幾種表達形式和內容需要大家格外注意一下。
1、根據指針的運算規則,p+x代表的是number[x]的地址,那么number+x代表的也是number[x]的地址。或者說,他們指向的都是number數組的第x號元素。
2、*(p+x)*(number+x)都表示number[x]
3、指向數組元素的指針也可以表示成數組的形式,也就是說,允許指針變量帶下標,即p[ i]*(p+i)等價。但是為了避免混淆與規范起見,這里我們建議大家不要寫成前者,而一律采用后者的寫法。但如果看到別人那么寫,也知道是怎么回事即可。
二維數組元素的指針和一維數組類似,需要介紹的內容不多。假如現在一個指針變量p和一個二維數組number[3][4],它的地址的表達方式也就是p=&number[0][0],有一個地方要注意,既然數組名代表了數組元素的首地址,那么也就是說pnumber都是指數組的首地址。對二維數組來說,number[0]number[1]number[2]都可以看成是一維數組的數組名字,所以number[0]等價于&number[0][0]number[1]等價于&number[1][0]number[2]等價于&number[2][0]。加減運算和一維數組是類似的,不再詳述。
12.2.2 指向數組元素的指針應用例程
在我們的C語言里邊,sizeof()可以用來獲取括號內的對象所占用的內存字節數,雖然它寫作函數的形式,但它并不是一個函數,而是C語言的一個關鍵字,sizeof()整體在程序代碼中就相當于一個常量,也就是說這個獲取操作是在程序編譯的時候進行的,而不是在程序運行的時候進行。這是一個實際編程中很有用的關鍵字,靈活運用它可以為程序帶來更好可讀性、易維護性和可移植性,在后續的例程學習中將會慢慢有所體會的。
sizeof()括號中的可以是變量名,也可以是變量類型,其結果是等效的。而其更大的用處是與數組名搭配使用,這樣可以獲取整個數組占用的字節數,就不用自己動手計算了。
下面我們提供了一個簡單的串口演示例程,可以體驗一下指針和sizeof()的用法。例程首先接收上位機下發的命令,根據命令值分別把不同數據的數據回發給上位機,程序還用到了指針的自增運算,也就是+1運算,大家可以認真考慮一下指針ptrTxd在串口發送的過程中的指向是如何變化的。在上位機串口調試助手中分別下發1,2,3,4,就會得到不同的數組回發,注意這里都用十六進制發送和十六進制顯示。
此外,這個程序還應用到一個小技巧,這里大家要學會使用。我們前邊講了串口發送中斷標志位TI是硬件置位,軟件清零的。通常來講,我們想一次發送多個數據的時候,就需要把第一個字節寫入SBUF,然后在等待發送中斷,再在后續中斷中在發送剩余的數據,這樣我們的數據發送過程就被拆分到了兩個地方——主循環內和中斷服務函數內,無疑就使得程序結構變得零散了。這個時候,為了使程序結構盡量緊湊,在啟動發送的時候,不是向SBUF中寫入第一個待發的字節,而是直接讓TI=1,注意,這時候會馬上進入串口中斷,因為中斷標志位置1了,但是串口線上并沒有發送任何數據,于是,我們所有的數據發送都可以在中斷中進行,而不用再分為兩部分了。大家可以在程序中體會一下這個技巧的好處。

#include <reg52.h>

bit cmdArrived = 0;   //命令到達標志,即接收到上位機下發的命令
unsigned char cmdIndex = 0; //命令索引,即與上位機約定好的數組編號
unsigned char cntTxd = 0;   //串口發送計數器
unsigned char *ptrTxd = 0;  //串口發送指針

unsigned char array1[1] = {1};
unsigned char array2[2] = {1,2};
unsigned char array3[4] = {1,2,3,4};
unsigned char array4[8] = {1,2,3,4,5,6,7,8};

void ConfigUART(unsigned int baud);

void main ()
{
    ConfigUART(9600);  //配置波特率為9600
    EA = 1;  //開總中斷

    while(1)
    {
        if (cmdArrived)
        {
            cmdArrived = 0;
            switch (cmdIndex)
            {
                case 1:
                    ptrTxd = array1;         //數組1的首地址賦值給發送指針
                    cntTxd = sizeof(array1); //數組1的長度賦值給發送計數器
                    TI = 1;                //手動方式啟動發送中斷,處理數據發送
                    break;
                case 2:
                    ptrTxd = array2;
                    cntTxd = sizeof(array2);
                    TI = 1;
                    break;
                case 3:
                    ptrTxd = array3;
                    cntTxd = sizeof(array3);
                    TI = 1;
                    break;
                case 4:
                    ptrTxd = array4;
                    cntTxd = sizeof(array4);
                    TI = 1;
                    break;
                default:
                    break;
            }
        }
    }
}

void ConfigUART(unsigned int baud)  //串口配置函數,baud為波特率
{
    SCON = 0x50;   //配置串口為模式1
    TMOD &= 0x0F;  //清零T1的控制位
    TMOD |= 0x20;  //配置T1為模式2
    TH1 = 256 - (11059200/12/32) / baud;  //計算T1重載值
    TL1 = TH1;     //初值等于重載值
    ET1 = 0;       //禁止T1中斷
    ES  = 1;       //使能串口中斷
    TR1 = 1;       //啟動T1
}


void InterruptUART() interrupt 4
{
    if (RI)  //接收到字節
    {
        RI = 0;           //手動清零接收中斷標志位
        cmdIndex = SBUF;  //接收到的數據保存到命令索引中
        cmdArrived = 1;   //設置命令到達標志
    }
    if (TI)  //字節發送完畢
    {
        TI = 0;    //手動清零發送中斷標志位
        if (cntTxd > 0)  //有待發送數據時,繼續發送后續字節
        {
            SBUF = *ptrTxd;  //發出指針指向的數據
            cntTxd--;        //發送計數器遞減
            ptrTxd++;        //發送指針遞增
        }
    }
}
12.3 字符數組和字符指針12.3.1 常量和符號常量
在程序運行過程中,其值不能被改變的量稱之為常量。常量分為不同的類型,有整型常量如123100等;浮點型常量3.140.56-4.8;字符型常量’a’,’b’,’0’;字符串常量”a”,”abc”,”1234”,”1234abcd”等。
細心的同學會發現,整型和浮點型常量我們直接寫的數字,而字符型常量用單引號來表示一個字符,用雙引號來表示一個字符串,尤其大家要注意’a’和”a”是不一樣的,這個等會我們要詳細介紹。
常量一般有2種表現形式。
1、直接常量:直接以值的形式表示的常量稱之為直接常量。上述舉例這些都是直接常量,直接寫出來了。
2、符號常量:用標識符命名的常量稱之為符號常量,就是為上面的直接常量再取一個名字。使用符號常量一是方便理解,提高程序可讀性,更重要的是方便程序的后續維護,習慣上符號常量我們都用大寫字母和下劃線來命名。
比如,我們可以把3.14取名為PI(即π)。再比如,我們上節課的串口程序,我們用的波特率是9600,如果用符號常量來進行提前聲明的話,那我們要修改成其他速率的話,就不用在程序中找9600修改了,直接修改聲明處就可以了,兩種方法舉例說明。
1、用const聲明。比如我們在程序開始位置定義一個符號常量BAUD
    定義形式是   const   類型   符號常量名字=常量值;
const  unsigned  int  BAUD = 9600;       /*注意結尾有個分號*/
我們就可以在程序中直接把9600改成BAUD,這樣我們如果要改波特率的話,直接在程序開頭位置改一下這個值就可以了。
2、用預處理命令#define來完成,預處理命令我們先來認識#define
    定義形式是  #define   符號常量名   常量值
#define  BAUD   9600                   /*注意結尾沒有分號*/
這樣定義以后,只要在程序中出現BAUD的話,意思就是完全替代了后邊的9600這個數字。
不知大家是否記得,我們之前在數碼管真值表,用了一個code關鍵字
    unsigned char code LedChar[] = {   //用數組來表示數碼管真值表
        0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
        0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8e
};
我們當時說加了code之后,這個真值表的數據只能被使用,不能被改變,如果我們直接寫LedChar[0] = 1;這樣就錯了。實際上code這個關鍵字是51單片機特有的,如果是其他型號的單片機我們只需要寫成const unsigned char LedChar[]={}就可以了,自動保存到FLASH里,而51單片機只用const而不加code的話,這個數組會保存到RAM中,而不會保存到FLAHS中,鑒于此,在51這個體系下,const反倒變得不那么重要了,它的作用被code取代了,這里大家知道這么回事即可。
我們來對各種類型的常量做進一步說明。
整型常量和浮點型常量沒太多內容在里邊了,之前我們應用都很熟練了,整型直接寫數字就是十進制如128,前邊0x開頭的表示是十六進制0x80,浮點型直接寫帶小數點的數據就可以了。
字符型常量是由一對單引號括起來的單個字符。它分為兩種形式,一種是普通字符,一種是轉義字符。
1、普通字符就是那些我們可以直接書寫直接看到的有形的字符,比如阿拉伯數字09,英文字符Az,以及標點符號等。它們都是ASCII碼表中的字符,而它們在單片機中都占用一個字節的空間,其值就是對應的ASCII碼值。比如’a’的值是97’A’的值是65,0’的值是48,如果定義一個變量unsigned char a = ’a’,那么變量a的值就是97
2、除了上述這些字符之外,還有一些特殊字符,它們一些是無形的,像回車符、換行符這些都是看不到的,還有一些像’、”這類字符它們已經有特殊用途了,想象一下如果寫’’’你覺得編譯器會怎么取解釋。針對這些特殊符號,為了可以讓它們正常進入到我們的程序代碼中,C語言就規定了轉義字符,它是以反斜杠(\)開頭的特定字符序列,讓它們來表示這些特殊字符,比如我們用\n來代表換行,這塊內容我們后邊有程序說明。我們用一個簡單表格來說明一下常用的轉義字符的意思,如表12-2所示。
12-2 常用轉義字符及含義
字符形式
含義
\n
換行
\t
橫向跳格(相當于Tab)
\v
豎向跳格
\b
退格
\r
光標移到行首
\\
反斜杠字符’\’
\’
單引號字符
\”
雙引號字符
\f
走紙換頁
\0
空值
    表格不需要大家記住,用到了,過來查就可以了。
字符串常量是用雙引號括起來的字符序列,一般我們都稱之字符串。如”a”,”1234”,”welcome to www.kingst.org”等都是字符串常量。字符串常量在內存中按順序逐個存儲字符串中的字符的ASCII碼值,并且特別注意,最后還有一個字符’\0’,’0’字符的ASCII碼值是0,它是字符串結束標志,在寫字符串的時候,這個‘\0’是隱藏的,我們看不到,但是實際卻是存在的。所以”a”就比’a’多了一個’\0’,”a”的就占了2個字節,而’a’只占一個字節。
還有一個地方要注意,就是字符串中的空格,也是一個字符,比如”welcome to www.kingst.org”一共占了26個字節的空間。其中21個字母,2’.’,2個‘ ’(空格字符)以及一個’\0’。
[size=14.0000pt]12.3.2 [size=14.0000pt]字符和字符串數組應用例程
為了對比字符串,字符數組,常量數組的區別,我們寫個了簡單的演示程序,定義了4個數組分別是:
unsigned char array1[] = "1-Hello!\r\n";
    unsigned char array2[] = {'2', '-', 'H', 'e', 'l', 'l', 'o', '!', '\r', '\n'};
    unsigned char array3[] = {51,  45,  72,  101, 108, 108, 111, 33,  13,   10};
unsigned char array4[] = "4-Hello!\r\n";
在串口調試助手下,發送十六進制的1234,使用字符形式顯示的話,會分別往電腦上送這4個數組中對應的那個數組。我們只是在起始位置做了區分,其他均沒做區分。大家可以比較一下效果。
此外還要說明一點,數組1和數組4,數組1我們是發完整的字符串,而數組4我們僅僅發送數組中的字符,沒有發結束符號。串口調試助手用字符形式顯示是沒有區別的,但是大家如果改用十六進制顯示,大家會發現數組1比數組4多了一個字節’\0’的ASCII00

#include <reg52.h>

bit cmdArrived = 0;   //命令到達標志,即接收到上位機下發的命令
unsigned char cmdIndex = 0; //命令索引,即與上位機約定好的數組編號
unsigned char cntTxd = 0;   //串口發送計數器
unsigned char *ptrTxd = 0;  //串口發送指針

unsigned char array1[] = "1-Hello!\r\n";
unsigned char array2[] = {'2', '-', 'H', 'e', 'l', 'l', 'o', '!', '\r', '\n'};
unsigned char array3[] = {51,  45,  72,  101, 108, 108, 111, 33,  13,   10};
unsigned char array4[] = "4-Hello!\r\n";

void ConfigUART(unsigned int baud);

void main ()
{
    ConfigUART(9600);  //配置波特率為9600
    EA = 1;  //開總中斷

    while(1)
    {
        if (cmdArrived)
        {
            cmdArrived = 0;
            switch (cmdIndex)
            {
                case 1:
                    ptrTxd = array1;         //數組1的首地址賦值給發送指針
                    cntTxd = sizeof(array1); //數組1的長度賦值給發送計數器
                    TI = 1;                //手動方式啟動發送中斷,處理數據發送
                    break;
                case 2:
                    ptrTxd = array2;
                    cntTxd = sizeof(array2);
                    TI = 1;
                    break;
                case 3:
                    ptrTxd = array3;
                    cntTxd = sizeof(array3);
                    TI = 1;
                    break;
                case 4:
                    ptrTxd = array4;
                    cntTxd = sizeof(array4) - 1;//字符串長度為數組長度減1
                    TI = 1;
                    break;
                default:
                    break;
            }
        }
    }
}

void ConfigUART(unsigned int baud)  //串口配置函數,baud為波特率
{
    SCON = 0x50;   //配置串口為模式1
    TMOD &= 0x0F;  //清零T1的控制位
    TMOD |= 0x20;  //配置T1為模式2
    TH1 = 256 - (11059200/12/32) / baud;  //計算T1重載值
    TL1 = TH1;     //初值等于重載值
    ET1 = 0;       //禁止T1中斷
    ES  = 1;       //使能串口中斷
    TR1 = 1;       //啟動T1
}

void InterruptUART() interrupt 4
{
    if (RI)  //接收到字節
    {
        RI = 0;           //手動清零接收中斷標志位
        cmdIndex = SBUF;  //接收到的數據保存到命令索引中
        cmdArrived = 1;   //設置命令到達標志
    }
    if (TI)  //字節發送完畢
    {
        TI = 0;    //手動清零發送中斷標志位
        if (cntTxd > 0)  //有待發送數據時,繼續發送后續字節
        {
            SBUF = *ptrTxd;  //發出指針指向的數據
            cntTxd--;        //發送計數器遞減
            ptrTxd++;        //發送指針遞增
        }
    }
}

12.4 1602液晶的認識12.4.1 1602液晶的硬件接口介紹
[size=14.0000pt]
前邊我們講的流水燈、數碼管、LED點陣這三種都是LED設備,這節課我們來學習一下LCD顯示設備——1602液晶。那個大大的,平時第一行顯示16個小黑塊,第二行什么都不顯示的東西就是1602液晶,是不是早就注意到它了呢?
大家學習這些電子器件,頭腦中要逐漸形成一種意識,不管是我們的單片機,還是74HC138,甚至我們的三極管等等,都是有數據手冊的。不管是設計電路還是編寫程序,器件的數據手冊是我們最好的參考資料,那我們今天來學習1602,首先就要看它的數據手冊。手冊大家可以在網上找到,這里我講的時候只挑手冊的重點講。
首先我們來看一個主要技術參數表格,如表12-3所示。
12-3 1602液晶主要技術參數
顯示容量
16 x 2個字符
芯片工作電壓
4.5~5.5V
工作電流
2.0mA(5.0V)
模塊最佳工作電壓
5.0V
字符尺寸
2.95 x 4.35mm (寬乘高)
1602液晶,從它的名字我們就可以理解他的顯示容量,就是可以顯示2行,每行16個字符的液晶。他的工作電壓4.5V5.5V,這個我們設計電路的時候,直接按照5V系統設計,但是保證我們的5V系統最低不能低于4.5V。在5V工作電壓下測量它的工作電流是2mA,大家注意,這個2mA僅僅是指液晶,而它的黃綠背光都是用LED做的,所以功耗不會太小的,一二十毫安還是有的。
1602液晶一共16個引腳,每個引腳的功能,我們都可以在它的數據手冊上獲得。而這些基本的信息,在我們設計電路和編寫代碼之前,必須先看明白,如表12-4所示。
12-4 1602液晶引腳功能
編號
符號
引腳說明
編號
符號
引腳說明
1
VSS
電源地
9
D2
Data  I/O
2
VDD
電源正極
10
D3
Data  I/O
3
VL
液晶顯示偏壓信號
11
D4
Data  I/O
4
RS
數據/命令選擇端(H/L)
12
D5
Data  I/O
5
R/W
/寫選擇端(H/L)
13
D6
Data  I/O
6
E
使能信號
14
D7
Data  I/O
7
D0
Data  I/O
15
BLA
背光源正極
8
D1
Data  I/O
16
BLK
背光源負極
液晶的電源12腳以及背光電源1516腳,不用多說,正常接就可以了。
3腳叫做液晶顯示偏壓信號,大家注意到小黑塊沒有,當我們要顯示一個字符的時候,有的黑點顯示,有的黑點就不能顯示,這樣就可以實現我們想要的字符了。我們這個3腳就是用來調整顯示的黑點和不顯示的之間的對比度,調整好了對比度,就可以讓我們的顯示更加清晰一些。在進行電路設計實驗的時候,通常的辦法是在這個引腳上接個電位器,也就是我們初中學過的滑動變阻器,是一個東西。通過調整電位器的分壓值,來調整3腳的電壓。而當產品批量生產的時候,我們可以把我們調整好的這個值直接用簡單電路來實現,就如同在我們板子上,我們直接使用的是一個18歐的下拉電阻,市面上有的1602的下拉電阻大概11.5k也是比較合適的值。
4腳是數據命令選擇端。我們的液晶,有時候我們要發送一些命令,讓他實現我們想要的一些狀態,有時候我們要發給他一些數據,讓他顯示出來,液晶就通過這個引腳來判斷接收到的是數據還是命令,這個引腳我們接到了ADDR0上,通過跳線帽和P1.0連接在一起。大家注意學會讀手冊,看到我們這個引腳描述里:數據/命令選擇端,而后跟了括號(H/L),他的意思就是當這個引腳是H(High)高電平的時候,是數據,當這個引腳是L(Low)低電平的時候,是命令。
5腳和4腳用法類似,只是功能是讀寫選擇端。我們既可以寫給液晶數據或者命令,也可以讀取液晶內部的數據,就是控制這個引腳。因為液晶本身內部有RAM,實際上我們送給液晶的命令或者數據,液晶需要先保存在緩存里,然后再寫到內部的寄存器或者RAM中,這個就需要一定的時間。所以我們再進行讀寫操作之前,首先要讀一下液晶當前狀態,是不是在“忙”,如果不忙,我們可以讀寫數據,如果在“忙”,我們需要等待液晶忙完了,再進行操作。讀狀態是常用的,不過讀液晶數據我接觸的場合沒怎么用過,大家了解這個功能即可。這個引腳我們接到了ADDR1上,通過跳線帽和P1.1連接在一起。
6腳是使能信號,很關鍵,液晶的讀寫命令和數據,都要靠它才能正常讀寫,我們后邊詳細講這個引腳怎么用。這個引腳我們通過跳線帽接到了ENLCD上,這個位置的跳線帽是為了和我們將來以后另外一個12864液晶的切換使用的。
714引腳就是8個數據引腳了,我們就是通過這8個引腳讀寫數據和命令的。我們統一接到了P0總線上了。我們來看一下我們開發板上的1602的原理圖,如圖12-1所示。
圖12-1 1602液晶原理圖
12.4.2 1602液晶的讀寫時序介紹[size=14.0000pt]
1602液晶內部帶了80個字節的RAM,用來存儲我們發送的數據,他的結構如圖12-2所示。

圖12-2 1602內部RAM結構圖
第一行的地址是00H27H,第二行的地址從40H67H,其中第一行00H0FH是和液晶上第一行16個字符顯示位置相互對應,第二行40H4FH是和第二行16個字符顯示位置相互對應的。而每行都多出來一部分,是為了顯示移動字幕設置的。1602字符液晶是顯示字符的,因此他是跟ASCII字符表是對應的。比如我們給00H這個地址寫一個’a’,也就是10進制的97,液晶的最左上方的那個小塊就會顯示一個a。此外,我們本章學過指針了,液晶內部有個數據指針,它指向哪里,我們寫的那個數據就會送到相應的那個地址里。
液晶有一個狀態字字節,我們可以通過了解這個狀態字的內容,就可以知道1602液晶的一些內部情況,如表12-5所示。
STA0-6
當前數據的指針的值
STA7
讀寫操作使能
1:禁止   0:允許
這個狀態字節的8位,最高位表示了當前液晶是不是“忙”,如果這個狀態字是1表示液晶正“忙”,禁止我們讀寫數據或者命令,如果是0,則可以進行讀寫。而低7位就表示了當前數據地址指針的位置。
1602的基本操作時序,一共有4個,這些大家都不需要記住,但是都需要理解,因為我們現在不是為了應付考試了,所以不需要你把手冊背的滾瓜爛熟,但是你寫程序的時候,打開手冊能看懂如何操作,還要再提醒一句,單片機讀外部狀態前,必須先保證自己是高電平哦。
我們這里要做1602液晶程序,因此我們我們的聲明寫成:
#define LCD1602_DB   P0
sbit LCD1602_RS = P1^0;
sbit LCD1602_RW = P1^1;
sbit LCD1602_E  = P1^5;
1、讀狀態:RS=LR/W=HE=H。這是個很簡單的邏輯,就是說,我們直接寫
    LCD1602_DB = 0xFF;
    LCD1602_RS = 0;
LCD1602_RW = 1;
LCD1602_E = 1;
sta = LCD1602_DB;
這樣就把當前液晶的狀態字讀到了sta這個變量中,我們可以通過判斷sta的最高位來了解當前液晶是否處于“忙”狀態,也可以得知當前數據的指針位置。兩個問題,問題一是如果我們當前讀到的狀態是“不忙”,那么我們程序可以進行讀寫操作,如果當前狀態是“忙”,那么我們還得繼續等待重新判斷液晶的狀態;問題二,大家可以看我們的原理圖,我們的流水燈、數碼管、點陣,1602液晶都用到了P0口總線,我們讀完了液晶狀態繼續保持LCD1602_E 是高電平的話,1602液晶會繼續輸出它的狀態值,輸出的這個值會占據了P0總線,干擾到流水燈數碼管等其他外設,所以我們讀完了狀態,通常要把這個引腳拉低來釋放總線,這里我們用了一個do...while循環語句來實現。
    LCD1602_DB = 0xFF;
    LCD1602_RS = 0;
    LCD1602_RW = 1;
    do   // do...while循環語句
    {
        LCD1602_E = 1;
        sta = LCD1602_DB; //讀取狀態字
        LCD1602_E = 0;  //讀完了要關閉使能,防止液晶輸出數據干擾總線
} while (sta & 0x80); //bit7等于1表示液晶正忙,重復檢測直到其等于0為止
2、讀數據:RS=HR/W=LE=H。這個邏輯也很簡單,但是讀數據不常用,大家了解一下就可以了,這里就不詳細解釋了。
3、寫指令:RS=LR/W=LD0~D7=指令碼,E=高脈沖。
        這個在邏輯上沒什么難的,只是E=高脈沖這個問題要解釋一下。這個指令一共有4條語句,其中前三條語句順序無所謂,但是E=高脈沖這一句很關鍵。實際上流程是這樣的:因為我們現在是寫數據,所以我們首先要保證我們的E引腳是低電平狀態,而前三句不管我們怎么寫,1602液晶只要沒有接收到E引腳的使能控制,它都不會來讀總線上的信號的。當通過前三句準備好數據之后,E使能引腳從低電平到高電平變化,然后E使能引腳再從高電平到低電平出現一個下降沿,1602液晶內部一旦檢測到這個下降沿后,并且檢測到RS=LR/W=L,就馬上來讀取D0~D7的數據,完成單片機寫1602指令過程。歸納總結我們寫了個E=高脈沖,就是E使能引腳先從低拉高,再從高拉低,形成一個高脈沖,就是這個意思。
4、寫數據:RS=HR/W=LD0~D7=數據,E=高脈沖。
寫數據和寫指令是類似的,就是把RS改成H,把總線改成數據即可。
此外,這里要提一句,液晶1602所使用的通信時序是摩托羅拉公司所創立的6800時序,大家知道這么回事即可。
   
這里還要說明一個問題,就是從這4個時序大家可以看出來,我們的LCD1602的使能引腳E,高電平的時候是有效,低電平的時候是無效。如果這個引腳是個高電平的話,那么就可能被液晶認為是讀指令或者讀數據,1602就會占用P0口往外送數據。由于P0口同時控制了LED小燈、數碼管、點陣LED以及1602液晶,如果1602往外送數據,P0口就會干擾到其他外設。因此我們如果正常情況下,用單片機IO口直接連到1602的這個E引腳上,只要我們用LED小燈、數碼管、點陣,那么我們程序上來就寫一句LCD1602_E=0,就可以避免1602干擾到其他外設。我們之前的程序沒有加這句,是因為我們板子在這個引腳上加了一個15K的下拉電阻,這個下拉電阻就可以保證這個引腳上電默認后是低電平,如圖12-3所示。

12-3 液晶下拉電阻
    如果不加這個下拉電阻,剛開始講點亮LED小燈的時候,我們就得寫一句:LCD1602_E=0,可能很多初學者容易弄不明白,所以我們才加了這樣一個電路。但是在實際開發過程中,就不必要這樣了。如果這是個實際產品,能用軟件去處理的,我們就不會用硬件去實現,所以大家在做實際產品的時候,這塊電路可以直接去掉,只需要在程序中最開始多加一條語句即可。


12.4.3 1602液晶的指令介紹
[size=14.0000pt]
和單片機寄存器的用法類似,1602液晶在使用的時候,我們首先要進行初始化的功能配置,1602液晶有以下幾個指令需要了解。
1、顯示模式設置。
寫指令0x38,設置16x2顯示,5x7點陣,8位數據接口。這條指令對我們這個液晶來說是固定的,必須寫0x38,大家仔細看會發現我們的液晶實際上內部點陣是5x8的,還有一些1602液晶還兼容串行通信,用2IO口即可,但是速度慢,我們這個液晶固定的0x38的模式。
2、顯示開/關以及光標設置指令。
這里有2條指令,第一條指令一個字節中8位,其中高5位是固定的00001,低3位我們假設是DCB從高到底表示,D=1表示開顯示,D=0表示關顯示;C=1表示顯示光標,C=0表示不顯示光標;B=1表示光標閃爍,B=0表示光標不閃爍。
第二條指令高6位是固定的000001,低2位我們分別用NS從高到底表示,其中N=1表示讀或者寫一個字符后,指針自動加1,光標自動加1N=0表示讀或者寫一個字符后指針自動減1,光標自動減1S=1表示寫一個字符后,整屏顯示左移(N=1)或右移(N=0),以達到光標不移動而屏幕移動的效果,如同我們的計算器輸入一樣的效果,而S=0表示寫一個字符后,整屏顯示不移動。
3、清屏指令。
固定的,寫入01H表示顯示清屏,其中包含了數據指針清零,所有的顯示清零。寫入02H后,僅僅是數據指針清零,顯示不清零。
4RAM地址設置指令
該指令碼的最高位為1,低7位為RAM的地址,RAM地址與液晶上字符的關系如上圖12-2 所示。通常,我們在讀寫數據之前都要先設置好地址,然后再進行數據的讀寫操作。
12.4.4 1602液晶簡易程序例程[size=14.0000pt]
1602液晶手冊提供了一個初始化過程,但是它寫的比較復雜,我們這邊總結了一個更加簡易方便的過程提供給大家,手冊上描述的那個,大家僅僅作為了解就可以了,下面我把程序寫出來大家看下,我們的初始化只用了4條語句,沒有像手冊介紹的那么繁瑣。

#include <reg52.h>

#define LCD1602_DB   P0

sbit LCD1602_RS = P1^0;
sbit LCD1602_RW = P1^1;
sbit LCD1602_E  = P1^5;

void LcdInit();
void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);

void main ()
{
    unsigned char str[] = "Kingst Studio";

    LcdInit();
    LcdShowStr(2, 0, str);
    LcdShowStr(0, 1, "Welcome to KST51");

    while(1)
    {}
}

void LcdWaitReady()  //讀取“忙”表示,等待液晶準備好
{
    unsigned char sta;

    LCD1602_DB = 0xFF;
    LCD1602_RS = 0;
    LCD1602_RW = 1;
    do   // do...while循環語句
    {
        LCD1602_E = 1;
        sta = LCD1602_DB; //讀取狀態字
        LCD1602_E = 0;  //讀完了要關閉使能,防止液晶輸出數據干擾總線
    } while (sta & 0x80); //bit7等于1表示液晶正忙,重復檢測直到其等于0為止
}
void LcdWriteCmd(unsigned char cmd)  //寫入命令函數
{
    LcdWaitReady();
    LCD1602_RS = 0;
    LCD1602_RW = 0;
    LCD1602_DB = cmd;
    LCD1602_E  = 1;
    LCD1602_E  = 0;
}
void LcdWriteDat(unsigned char dat)  //寫入數據函數
{
    LcdWaitReady();
    LCD1602_RS = 1;
    LCD1602_RW = 0;
    LCD1602_DB = dat;  
    LCD1602_E  = 1;
    LCD1602_E  = 0;
}
void LcdInit()  //液晶初始化函數
{
    LcdWriteCmd(0x38);  //16*2顯示,5*7點陣,8位數據接口
    LcdWriteCmd(0x0C);  //顯示器開,光標關閉
    LcdWriteCmd(0x06);  //文字不動,地址自動加1
    LcdWriteCmd(0x01);  //清屏
}
void LcdShowStr(unsigned char x, unsigned char y,  unsigned char *str)  //顯示字符串,屏幕起始坐標(x,y),字符串指針str
{
    unsigned char addr;

    //由輸入的顯示坐標計算顯示RAM的地址
    if (y == 0)
    {
        addr = 0x00 + x; //第一行字符地址從0x00起始
    }
    else
    {
        addr = 0x40 + x; //第二行字符地址從0x40起始
    }

    //由起始顯示RAM地址連續寫入字符串
    LcdWriteCmd(addr | 0x80); //寫入起始地址
    while (*str != '\0')      //連續寫入字符串數據,直到檢測到結束符
    {
        LcdWriteDat(*str);    //注意*str就是這個指針指向的內容
        str++;
    }
}
程序我注釋的已經很詳細了,有幾個地方再說兩句。首先,我們把程序所有的功能都使用函數模塊化了,這樣非常有利于我們程序的維護,不管要寫一個什么樣的功能,只要調用相應的函數就可以了,大家注意學習這個技巧。
其次,我們使用液晶的習慣,也是喜歡用數學上的X,Y坐標來進行定位,而和數學不同的是,液晶的左上角的坐標是x=0y=0,往右邊是x+偏移,下邊是y+偏移。
第三,第一次接觸多個參數傳遞的函數,稍微注意熟悉一下。
第四,讀寫數據和指令程序,每次都必須進行“忙”判斷。
第五,領略一下指針在這個地方的巧妙用法,你可以嘗試不用指針改寫程序試試,感受一下指針的優勢。
12.5 作業
1、把本節課的指針相關內容,反復學習35遍,徹底弄懂了指針是怎么回事,不知道如何用沒關系,即使是背,也得把這部分背下來,等到后邊我們用的時候,就可以實現頓悟。學會指針,就是突破了C語言的一道重要的堡壘。
2、把1602所有的指令功能都應用一遍,能夠靈活使用1602液晶顯示任意字符串。
[size=12.0000pt]3、嘗試通過串口調試助手下發字符在1602液晶上顯示出來。

評分

參與人數 1黑幣 +5 收起 理由
秋葉原48 + 5

查看全部評分

分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏8 分享淘帖 頂2 踩
回復

使用道具 舉報

沙發
ID:69833 發表于 2014-12-16 18:58 來自手機 | 只看該作者
挺好挺好,學會了不少東西
回復

使用道具 舉報

板凳
ID:74433 發表于 2015-3-24 21:18 | 只看該作者
講的很詳細透徹
回復

使用道具 舉報

地板
ID:74245 發表于 2015-11-19 08:11 | 只看該作者
真的寫得很好,很適合初學者。
回復

使用道具 舉報

5#
ID:225339 發表于 2017-8-27 14:30 | 只看該作者
仔細看了這篇教程 我也在開發板上試了 是存在問題的  就是 字符的 重復顯示 我改了一下  現在 編譯通過且成功 特地分享
/*問題 1602重復顯示*/
#include<reg52.h>
#define LCD1602_DB  P0                //1602 IO口
sbit LCD1602_RD=P1^2;        // 數據命令選擇端  H DATA  L CMD
sbit LCD1602_RW=P1^3;   //  讀寫端    H  READ  L WRITE                                                                                                                     
sbit LCD1602_E=P1^4;        //   使能
void readbusy();      //判忙 1忙 0空
void write_data(unsigned char dat);
void write_cmd(unsigned char cmd);
void LCD1602_INIT();
unsigned char code word1[]={"cjb study 1602"};
void LCDShowstr(unsigned char addr_start,unsigned char *p);   //x  y表示坐標 p表示指針變量 即數組word的首地址
void main()
{

        LCD1602_INIT();
        LCDShowstr(0x80,word1);
        while(1){}
}
void LCD1602_INIT()                  //1602INIT
{
        write_cmd(0x38);           //顯示模式設置  1602 固定命令
        write_cmd(0x0c);                //開顯示器 關閉光標   0000 1DCB   D=1開顯示 C=1顯示光標 B=1 光標閃爍
        write_cmd(0x06);                 //文字不動  地址自動加1   0000 01NS N=1 寫入一個字符后 地址自動加1 S=0 寫入一個字符 整屏不移動
        write_cmd(0x01);                //清屏
}
/*1602每次的寫入都要確保其處于不忙的狀態  狀態字的最高位D7代表其是否忙碌1 禁止寫入 0 應許寫入*/
void readbusy()
{
        unsigned char sta;
        LCD1602_DB=0XFF;    //P0置位,判斷D7是不是1602拉低的
        LCD1602_RD=0;
        LCD1602_RW=1;    //讀D7狀態           RD  RW 不能寫錯
        do{
        LCD1602_E=1;      //使能
        sta=LCD1602_DB;
        }while(sta&0x80);                //等到sta 首位為0 時 退出循環  此時1602處于空閑狀態
        LCD1602_E=0;                  //讀完了關閉使能  液晶輸出數據對總線的干擾
}
void write_data(unsigned char dat)                 //寫數據
{
    readbusy();
        LCD1602_RD=1;                           //參考時序圖
        LCD1602_RW=0;
        LCD1602_DB=dat;
        LCD1602_E=1;
        LCD1602_E=0;
}
void write_cmd(unsigned char cmd)        //寫命令
{
    readbusy();
        LCD1602_RD=0;                                   //參考時序圖
        LCD1602_RW=0;
    LCD1602_DB=cmd;
        LCD1602_E=1;
        LCD1602_E=0;
}
void LCDShowstr(unsigned char addr_start,unsigned char *p)
{
        write_cmd(addr_start);
        while(*p!='\0')
        {
                write_data(*p++);
        }
}
至于問題出在哪里 我認為 是坐標哪里有問題  希望大神求解
回復

使用道具 舉報

6#
ID:95509 發表于 2017-10-12 16:32 | 只看該作者
耐心學習!謝謝樓主
回復

使用道具 舉報

7#
ID:155764 發表于 2017-11-1 10:36 | 只看該作者
絕世好貼!
回復

使用道具 舉報

8#
ID:220927 發表于 2018-1-4 12:09 | 只看該作者
指針的使用最為靈活也最麻煩,這降解真心好,理解好的畫用的非常好
回復

使用道具 舉報

9#
ID:262591 發表于 2018-1-16 11:59 來自手機 | 只看該作者
最近在學指針,非常棒的文章。因為讀的比較仔細,所以看到流水燈例程時發現個小錯誤,應該是P1=~buf
回復

使用道具 舉報

10#
ID:281447 發表于 2018-3-20 16:23 | 只看該作者
液晶屏“讀數據”操作,應該是R/W = H吧?
回復

使用道具 舉報

11#
ID:93625 發表于 2018-7-12 13:21 | 只看該作者
學習內容越來越難了,努力中
回復

使用道具 舉報

12#
ID:339500 發表于 2018-8-2 02:23 | 只看該作者
為什么不直接出個新版講解單片機視頻呢,主頁推薦的郭天祥的視頻第一模糊,第二視頻播放速度和聲音速度延遲8S左右,雖然我可以通過下載視頻通過軟件吧延遲擬補回來但是真的太麻煩了,而且郭視頻也需要新一代更新更新呀
回復

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規則

手機版|小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術交流QQ群281945664

Powered by 單片機教程網

快速回復 返回頂部 返回列表
主站蜘蛛池模板: 一区二区三区亚洲 | 一区二区三区四区av | 日韩欧美精品一区 | a在线观看 | 欧美精品一区在线发布 | 国产va | 99精品视频一区二区三区 | 久久在视频 | 欧美日韩在线一区二区 | 在线视频第一页 | 欧美激情精品久久久久久 | a级片在线| 91在线精品一区二区 | 中文字幕日本一区二区 | 日本 欧美 三级 高清 视频 | 成人av网页 | 欧美精品网站 | 不卡欧美 | 成人综合视频在线观看 | 超碰在线久| 日韩一区二区免费视频 | 高清色| 国产九九av | 日本精品国产 | 在线观看视频亚洲 | 精品国产乱码久久久久久a丨 | 日日夜夜天天综合 | 成人免费黄色 | 亚洲国产一区二区三区 | 国产精品久久久久久妇女 | 毛片99| 伊人操| 国产重口老太伦 | 日韩一区二区在线看 | 日韩在线看片 | 91精品国产色综合久久 | 国产成在线观看免费视频 | 国产激情视频 | 四虎影院欧美 | 涩涩视频在线播放 | 91免费在线 |