1. 程式人生 > >通訊整理

通訊整理

                                        幾種常見的通訊整理

一、SPI通訊

 


/*
SPI為全雙工通訊方式
*/
unsigned char ADS1118_Read(unsigned char data1)
{
  unsigned char i;
unsigned char temp;
unsigned char Din;
  temp = data1;
  for(i=0;i<8;i++)
  {
Din = Din<<1;
if(0x80&temp)
SET_ADS1118_IN;
else
CLR_ADS1118_IN;
DelayMs(1);
SET_ADS1118_CLK;
DelayMs(1);
if(ADS1118_OUT)
Din |= 0x01;
 DelayMs(1);
 CLR_ADS1118_CLK;
DelayMs(1);
temp = (temp<<1);
  }
  return Din;
}

二、I2C通訊

#ifndef __IIC_H_
#define __IIC_H_
#include "ZLG7289.H"
typedef unsigned char u8;
typedef unsigned int u16;
typedef unsigned long u32;

sbit SDA = P1^2; //定義SDA  PIN5
sbit SCL = P1^3; //定義SCL  PIN6
sbit INTB=P1^1;

/**************External Recall****************/
// void SetQN8035(u8 address, u8 dat);
// u32 GetQN8035(u8 address1,u8 address2);
void I2C_Start(void);
void I2C_Stop(void);
void S_ACK(void);
void S_NoACK(void);
void I2C_WriteAbyte(u8 dat);
u8 I2C_ReadAbyte(void);
#endif

 

#include "IIC.H"
#include "intrins.h"
extern DelayMs(unsigned int i);
void I2C_Stop(void);
//========================================================================
// 函式:I2C函式
// 模擬I2C
// 引數:無
//返回:無
//========================================================================
#define SLAW    0xA2
#define SLAR    0xA3

void I2C_Delay(void)   //I2C延遲函式
{
  unsigned long i;
 for (i=0;i<200;i++)
     _nop_();
}

void I2C_Start(void)               //start the I2C, SDA High-to-low when SCL is high
{
    SDA = 1;
    I2C_Delay();
    SCL = 1;
    I2C_Delay();
    SDA = 0;
    I2C_Delay();
    SCL = 0;
    I2C_Delay();
}      

void I2C_Stop(void)                 //STOP the I2C, SDA Low-to-high when SCL is high
{
    SDA = 0;
    I2C_Delay();
    SCL = 1;
    I2C_Delay();
    SDA = 1;
    I2C_Delay();
}

void S_ACK(void)              //Send ACK (LOW)
{
    SDA = 0;
    I2C_Delay();
    SCL = 1;
    I2C_Delay();
    SCL = 0;
    I2C_Delay();
}

void S_NoACK(void)           //Send No ACK (High)
{
    SDA = 1;
    I2C_Delay();
    SCL = 1;
    I2C_Delay();
    SCL = 0;
    I2C_Delay();
}

 void I2C_Check_ACK(void)         //Check ACK, If F0=0, then right, if F0=1, then error
{
    SDA = 1;
    I2C_Delay();
    SCL = 1;
    I2C_Delay();
    F0  = SDA;
    SCL = 0;
    I2C_Delay();
}

void I2C_WriteAbyte(u8 dat)     //write a byte to I2C
{
    u8 i;
    i = 8;
    do
    {
        if(dat & 0x80)  SDA = 1;
        else            SDA = 0;
        dat <<= 1;
        I2C_Delay();
        SCL = 1;
        I2C_Delay();
        SCL = 0;
        I2C_Delay();
    }
    while(--i);
}

u8 I2C_ReadAbyte(void)          //read A byte from I2C
{
    u8 i,dat;
    i = 8;
    SDA = 1;
    do
    {
        SCL = 1;
        I2C_Delay();
        dat <<= 1;
        if(SDA)     dat++;
        SCL  = 0;
        I2C_Delay();
    }
    while(--i);
    return(dat);
}

 void WriteNbyte(u8 addr, u8 *p, u8 number)          /*  WordAddress,First Data Address,Byte lenth   */
                                                            //F0=0,right, F0=1,error
{
    I2C_Start();            //È·Á¢I2CÁ¬½Ó
    I2C_WriteAbyte(SLAW);
    I2C_Check_ACK();
    if(!F0)
    {
        I2C_WriteAbyte(addr);
        I2C_Check_ACK();
        if(!F0)
        {
            do
            {
                I2C_WriteAbyte(*p);     p++;
                I2C_Check_ACK();
                if(F0)  break;
            }
            while(--number);
        }
    }
    I2C_Stop();
}

void ReadNbyte(u8 addr, u8 *p, u8 number)       /*  WordAddress,First Data Address,Byte lenth   */
{
    I2C_Start();             //È·Á¢I2CÁ¬½Ó
    I2C_WriteAbyte(SLAW);
    I2C_Check_ACK();
    if(!F0)
    {
        I2C_WriteAbyte(addr);
        I2C_Check_ACK();
        if(!F0)
        {
            I2C_Start();
            I2C_WriteAbyte(SLAR);
            I2C_Check_ACK();
            if(!F0)
            {
                do
                {
                    *p = I2C_ReadAbyte();   p++;
                    if(number != 1)     S_ACK();    //send ACK
                }
                while(--number);
                S_NoACK();          //send no ACK
            }
        }
    }
    I2C_Stop();
}

三、USART通訊

現實生活中, 我們總是要與人打交道,互通有無。微控制器也一樣,需要跟各種裝置互動。例如汽車的顯示儀表需要知道汽車的轉速及電動機的執行引數,那麼顯示儀表就需要從汽車的底層控制器取得資料。而這個資料的獲得過程就是一個通訊過程。類似的例子還有控制器通常是微控制器或者PLC與變頻器的通訊。通訊的雙方需要遵守一套既定的規則也稱為協議,這就好比我們人之間的對話,需要在雙方都遵守一套語言語法規則才有可能達成對話。

通訊協議又分為硬體層協議和軟體層協議。硬體層協議主要規範了物理上的連線,傳輸電平訊號及傳輸的秩序等硬體性質的內容。常用的硬體協議有串列埠,IIC, SPI, RS485, CAN和 USB。軟體層協議則更側重上層應用的規範,比如modbus協議。

好了,那這裡我們就著重介紹51微控制器的串列埠通訊協議,以下簡稱串列埠。串列埠的6個特徵如下。

        (1)、物理上的連線至少3根,分別是Tx資料傳送線,Rx資料接收線,GND共用地線。
(2)、0與1的約定。RS232電平,約定﹣5V至﹣25V之間的電壓訊號為1,﹢5V至﹢25V之間的電壓訊號為0 。TTL電平,約定5V的電壓訊號為1,0V電壓訊號為0 。CMOS電平,約定3.3V的電壓訊號為1,0V電壓訊號為0 。其中,CMOS電平一般用於ARM晶片中。
(3)、傳送秩序。低位先發。
(4)、波特率。收發雙方共同約定的一個數據位(0或1)在資料傳輸線上維持的時間。也可理解為每秒可以傳輸的位數。常用的波特率有300bit/s, 600bit/s, 2400bit/s, 4800bit/s, 9600bit/s。
(5)、通訊的起始訊號。傳送方在沒有傳送資料時,應該將Tx置1 。 當需傳送時,先將Tx置0,並且保持1位的時間。接受方不斷地偵測Rx,如果發現Rx常時間變高後,突然被拉低(置為0),則視為傳送方將要傳送資料,迅速啟動自己的定時器,從而保證了收發雙方定時器同步定時。

(6)、停止訊號。傳送方傳送完最後一個有效位時,必須再將Tx保持1位的時間,即為停止位。

好了,理論暫時到這裡,現在我們要做一個實驗,將一個位元組從51單片機發送到電腦串列埠除錯助手上。這個實驗的目的是為了掌握串列埠通訊協議的收發過程。

虛擬串列埠

實驗一、虛擬串列埠實驗

一般微控制器都有專門的串列埠引腳,51裡面分別是P3.0和P3.1,這些引腳擁有串列埠的硬體電路,因此使用它們並不需要設定訊號的傳送停止。為了掌握協議,我們使用其他的引腳來模擬串列埠,所以也叫虛擬串列埠。這裡我們選用P1.0,然而注意到我們51微控制器要傳送資料給電腦,必須經過一個串列埠轉USB裝置(即TTL電平轉換為RS232電平),而限於我們的開發板只有P3.0與P3.1連線到了串列埠轉USB裝置,所以我們可以將P1.0短接到P3.1 。 下圖是這個串列埠轉USB的原理圖。

好了直接上程式碼吧。

 

 
  1. #include "reg51.h"

  2. /*

  3. 將P1.0虛擬成串列埠傳送腳TX

  4. 以9600bit/s的位元率向外傳送資料

  5. 因為波特率是 9600bit/s

  6. 所以me傳送一位的時間是 t=1000000us/9600=104us

  7. */

  8. sbit TX=P3^1; //P1^0 output TTL signal, need to transferred to rs232 signal, can be connected to P3^1

  9. #define u16 unsigned int //巨集定義

  10. #define u8 unsigned char

  11. u8 sbuf;

  12. bit ti=0;

  13.  
  14. void delay(u16 x)

  15. {

  16. while(x--);

  17. }

  18. void Timer0_Init()

  19. {

  20. TMOD |= 0x01;

  21. TH0=65440/256;

  22. TH0=65440%256;

  23. TR0=0;

  24. }

  25.  
  26. void Isr_Init()

  27. {

  28. EA=1;

  29. ET0=1;

  30. }

  31.  
  32. void Send_Byte(u8 dat)

  33. {

  34. sbuf=dat;//通過引入全域性變數sbuf,可以儲存形參dat

  35. TX=0; //A 起始位

  36. TR0=1;

  37. while(ti==0); //等待發送完成

  38. ti=0; //清除傳送完成標誌

  39. }

  40.  
  41. void TF0_isr() interrupt 1 //每104us進入一次中斷

  42. {

  43. static u8 i; //記錄進入中斷的次數

  44.  
  45. TH0=65440/256;

  46. TL0=65440%256;

  47. i++;

  48. if(i>=1 && i<=8)

  49. {

  50. if((sbuf&(1<<(i-1)))==0) // (sbuf&(1<<(i-1)))表示取出i-1位

  51. {

  52. TX=0;

  53. }

  54. else

  55. {

  56. TX=1;

  57. }

  58. }

  59. if(i==9) //停止位

  60. {

  61. TX=1;

  62. }

  63. if(i==10)

  64. {

  65. TR0=0;

  66. i=0;

  67. ti=1; //傳送完成

  68. }

  69. }

  70.  
  71. void main()

  72. {

  73. TX=1; //使TX處於空閒狀態

  74. Timer0_Init();

  75. Isr_Init();

  76. while(1)

  77. {

  78. Send_Byte(65); //0x41

  79. delay(60000);

  80. }

  81. }

實驗引入了定時器0來控制傳送線上的各個位的保持時間。首先main函式進入,TX置1則使傳送線處於空閒,這時候傳送方和接受方都處於空閒。接下來初始化定時器0,TR0置0表示還不要啟動定時器0。接著中斷系統初始化,此時中斷系統已經開啟。進入while迴圈,先進Send_Byte()函式,將65傳給形參dat,dat再將65賦值給sbuf,到這裡準備工作就做好了。接著TX置0,這個是起始位,要保持這個起始位104us。於是就啟動定時器TR0置1,計時器開始計數。當第一次溢位的時候,也就是過了104us,進入中斷,同時接收方也偵測到了這個突然被拉低的訊號,於是迅速啟動自己的定時器。進入中斷子函式後,先是重灌定時器初值,然後i加1,也就是當i=1時,就應該傳送資料的最低位了,總共有8位資料,所以使用條件語句if(i>=1 && i<=8)來判斷是否傳送完資料位。然後再通過if(i==9) 來發送停止位,最後當i=10時,也就是傳送完了,這時候要關閉定時器(那麼程式也就),同時i置0,ti置1(才能跳出while(ti==0)迴圈),最後將ti置0,保證下次要傳送位元組時讓程式停留在while(ti==0)。

 

片上串列埠

以上說的是虛擬串列埠,上文中談到與串列埠相關的引腳P3.0與P3.1,事實上51微控制器自帶片上串列埠,那這個串列埠又該怎麼使用呢?

片上串列埠支援同步模式與非同步模式。簡單來說同步模式就是指有時鐘線,而非同步模式無時鐘線。這裡的時鐘線是指在同步通訊時,用一根線專門傳輸時鐘訊號,這個訊號用來與要傳送的每一位保持同步,這樣就避免了例如非同步通訊中因為採用定時器而引入的時間誤差。

片上串列埠還支援8位模式和9位模式。如下圖所示

其中D0-D7是一個位元組的8個位。9位模式只是多了一個位TB8,這個TB8的作用是奇偶校驗或多機通訊。奇偶校驗原理這不加分析。多機通訊時比如主機只發送資料給網路中的一臺地址為0x02的裝置,這時候先讓TB8為1,前面的D0-D7則為地址即0x02,之後再讓TB8為0,前面的D0-D7則為資料了。

上面設定了片上串列埠的模式,另外還要設定串列埠的波特率。

片上串列埠的波特率等於定時器1工作在方式2時溢位率的32分頻。如果要定時器1工作在方式2,那麼TMOD=0x20。另外要保證為32分頻,我們還必須設定計數器初值。設晶振為11.0592Mhz,則定時器的計數脈衝為F=f/12,則定時器每計一個脈衝的時間為T=12/f。又令計數器的起點為x,則溢位一次要計的脈衝數為(256-x)。所以在計數起點為x時,溢位一次的時間為t=12/f*(256-x)。則對應的溢位率為1/t=f/(12*(256-x))。對應的波特率就為b=f/(384*(256-x))。

x=256-f/(384*b)

其中f為晶振頻率,b為希望的波特率,x為定時器的計數起點TH1的值。

例如當晶振為11.0592M,希望波特率為9600bit/s,則TH1=253。題外話,我們同樣可以演算出在其他常用波特率情況下,TH1始終為一個整數。這裡也就解釋了為什麼51裡面選用了11.0592M的晶振而不是12M,這樣就保證了串列埠的時序更加準確,雖然犧牲了定時器的準確度。

實驗二,片外串列埠傳送一個位元組。

好了現在開始我們的實驗之旅。直接看程式碼吧。

 

 
  1. #include "reg51.h"

  2. #define u16 unsigned int

  3. #define u8 unsigned char

  4.  
  5. void delay(u16 x)

  6. {

  7. while(x--);

  8. }

  9.  
  10. void Uart_Init() //串列埠初始化

  11. {

  12. SCON=0x50; //8位非同步模式

  13. TMOD|=0x20; //定時器1工作方式2

  14. TH1=253;//9600bit/s

  15. TR1=1;

  16. }

  17.  
  18. void Send_Byte(u8 dat)

  19. {

  20. SBUF=dat; //啟動傳送,只需要把傳送內容給SBUF這個暫存器

  21. while(TI==0); //等待發送完成,因為TI為1時表示在傳送停止位

  22. TI=0;

  23. }

  24.  
  25. void main()

  26. {

  27. Uart_Init();

  28. while(1)

  29. {

  30. Send_Byte('m');

  31. delay(60000);

  32. }

  33. }


實驗二較之實驗一,程式碼減少了很多,而且不用考慮繁瑣的位傳送時序。只需要明白各個暫存器SCON,TMOD,TCON,SBUF的用法。TI是SCON中的第一位,為傳送中斷請求標誌位。在本方式中,在停止位開始傳送時由內部硬體置位,響應中斷後TI必須又軟體清零。

實驗三、片上串列埠傳送一個字串

上面介紹瞭如何傳送一個位元組,那如何傳送一個字串甚至文字呢?這裡我們首先介紹下字串的概念。

字串:從儲存器的某個地址開始,連續存放多個字元的ASCII碼,並且在最後一個字元的後面存放一個0,這段連續的記憶體空間就叫字串,最後的0叫字串的結束符。注意這裡的0和加單引號的0不是一個概念,加單引號的0是指0的ASCII碼。

陣列與字串的關係:字串是陣列的一種特殊情況,陣列在特定條件下可當做字串用。C語言用雙引號描述一個字串,如“abcd”。

下面我們通過一個實驗來展示如何傳送字串。我們實驗的目標是列印字串“Hello World ! 第一!”到印表機。直接上程式碼。

 

 
  1. #include "reg51.h"

  2. #define u16 unsigned int

  3. #define u8 unsigned char

  4.  
  5. void delay(u16 x)

  6. {

  7. while(x--);

  8. }

  9.  
  10. void Uart_Init() //串列埠初始化

  11. {

  12. SCON=0x50; //8位非同步模式

  13. TMOD|=0x20; //定時器1工作方式2

  14. TH1=253;//9600bit/s

  15. TR1=1;

  16. }

  17.  
  18. void Send_Byte(u8 dat) //串列埠傳送一個位元組

  19. {

  20. SBUF=dat; //啟動傳送,只需要把傳送內容給SBUF這個暫存器

  21. while(TI==0); //等待發送完成,因為TI為1時表示在傳送停止位

  22. TI=0;

  23. }

  24.  
  25. void Send_String(u8 *str) //傳送一個字串 *str為字串第一個字元的地址

  26. {

  27. abc: //標號

  28. if(*str != 0)

  29. {

  30. Send_Byte(*str);

  31. str++;

  32. goto abc;

  33. }

  34. }

  35.  
  36. void main()

  37. {

  38. Uart_Init();

  39. while(1)

  40. {

  41. Send_String("Hello World! 第一!");

  42. Send_Byte(10);

  43. delay(60000);

  44. delay(60000);

  45. }

  46. }

實驗效果

宣告一下:最後面一個USART通訊的是借鑑別人的部落格的,沒有實驗過,但是前面兩個都是經過實踐的,都是沒有問題的,配置的時候注意時序問題,其它的就沒什麼了。。。。。