日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

485通信c语言讲解,51单片机485通讯讲解 通俗易懂

發布時間:2023/12/9 编程问答 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 485通信c语言讲解,51单片机485通讯讲解 通俗易懂 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

2017-6-14 21:48 上傳

點擊文件名下載附件

51單片機

第九節:串口通訊之485通訊

(1)? ?? ? 開場白:

串口通訊有三種常用的硬件方式。

第一種是TTL方式。比如兩個單片機的RX,TX引腳直接連接上(甲的RX連接乙的TX, 甲的TX連接乙的RX),這種通訊距離最短,局限于兩三米長的距離。

第二種是232方式。兩個CPU之間都經過了兩個MAX232等電平轉換芯片,比如單片機跟電腦的串口通訊。這種最大傳輸距離大概十米左右。

第三種是485方式。兩個CPU之間都經過了兩個MAX485等電平轉換芯片,此種方式在工控上應用最多,尤其是距離長,要求一臺主機控制多臺設備的情況下。從地址呼叫原理上看,一臺主機應該可以控制N多臺從機,但是書上說一臺主機最多可以控制32個從機,可能主要是從電阻匹配的角度來考慮,我沒驗證過,我只搞過一臺主機控制十幾臺從機的項目。485的最大傳輸距離大概在1000米左右。

這三種方式在編程上還是會有一些差異。TTL方式與232方式的編程幾乎完全兼容,唯一需要注意的是,發送一串數據時,每個字節延時間隔的大小可能會有些差異。

485通訊跟前面兩種方式有一個最大的區別是,485通訊要多增加一個IO口來控制數據流的方向,輸出低電平時表示接受數據的狀態,輸出高電平時表示發送數據的狀態。485的通訊協議一般都是用主機對從機廣播呼叫的模式。即平時所有從機處于接受數據的狀態(流控IO口處于低電平),主機發送呼叫某個從機的地址,相應的從機收到數據后,馬上切換到發送數據的狀態(流控IO口處于高電平),然后往主機返回對應的數據,從機對主機發送完數據之后,從機又馬上切換到接受數據的狀態(流控IO口處于低電平)。

這節利用第八節的232通訊程序修改成485通訊程序。

(2)功能需求:

無論是單片機還是上位機,最好在固定協議前多發送一個填充的無效字節0x00,因為硬件原因,第一個字節往往容易丟失。

通訊協議:XX YY??EB 00 55

其中后三位 EB 00 55就是我所說的數據尾,它的有效數據XX YY在數據尾的前面。

任意時刻,從電腦“串口調試助手”上位機收到的一堆數據中,只要此數據中包含關鍵字EB 00 55 ,并且此關鍵字前面兩個字節的數據XX YY 分別為01 02,那么就往上位機發送“eb??00 aa”表示確認,同時蜂鳴器叫一聲。否則,往上位機發送“eb??00 55”表示出錯。

(3)硬件原理:

在電腦的串口處加一個232轉485的通訊模塊。然后把單片機串口通訊的那兩個引腳經過一個MAX485芯片,直接與電腦處的232轉485的通訊模塊相連接,多增加一根單片機的IO口,用此IO連接MAX485的第2和第3引腳,實現流控的功能。當此IO處于低電平時為接收數據的狀態,當此IO口處于高電平時為發送數據的狀態。

(4)源碼適合的單片機:PIC18f4520,晶振為22.1184MHz,波特率115200

(5)源代碼講解如下:

#include? ?? ?? ?//包含芯片相關頭文件

//補充說明:吳堅鴻程序風格是這樣的,凡是輸出IO后綴都是_dr,凡是輸入的//IO后綴都//是_sr

#define??beep_dr??LATA2??//蜂鳴器輸出

#define??rede_dr??LATC5??//485通訊的流控

//補充說明:吳堅鴻程序風格是這樣的,凡是做延時計數閥值的常量

//前綴都用cnt_表示。

#define cnt_voice_time? ?150??//蜂鳴器響的聲音長短的延時閥值

#define cnt_send? ?300? ?? ?? ???//確保接收緩沖區沒有繼續接收數據,是變量

//send_cnt的溢出閥值

Void usart_service();? ?? ???//串口通訊服務程序,放在main函數里

unsigned char asy_recieve();??//把串口緩沖區的數據一個個提取出來

void eusart_send(unsigned char t_data); //串口發送一個字節的數據

Void Buf_clear() ;??//把余下的緩沖區清零

void Delay11(unsigned int MS); //延時函數

//補充說明:吳堅鴻程序風格是這樣的,凡是計數器延時的變量

//后綴都用_cnt表示。

unsigned int voice_time_cnt;? ?? ???//蜂鳴器響的聲音長短的計數延時

unsigned int send_cnt=0;? ?? ?? ?? ?//一串數據從上位機發過來的時候,他們每個字節之間//的延時間隔很短,如果他們的延時間隔一旦超過了這個send_cnt變量的延時,那么就認為他們的一串數據已經發送完畢

//補充說明:吳堅鴻程序風格是這樣的,凡是涉及統計數量的變量

//后綴都用_total表示。

unsigned int RCREG_total;? ?? ?? ?? ?//統計串口緩沖區已經收了多少個數據

unsigned int RCREG_read_total;? ?//統計已經從串口緩沖區讀出了多少個數據

//補充說明:吳堅鴻程序風格是這樣的,凡是用來更新的標識變量,比如液晶刷屏,或者有新接收的串口數據更新等等,后綴一律用_update表示

Unsigned char send_update=0;??//一旦有數據從上位機發送過來,就會引發串口接收中斷,在串口中斷里,我把send_update=1表示目前正在接收數據,警告單片機先不要//猴急,等串口中斷把所有從上位機連續發送過來的一堆數據接收完,再處理。那么什么///時候才知道發送的數據已經發送完畢了呢?用send_cnt識別。因為在串口中斷里,我///每次都會把send_cnt=0,而在main函數里,一旦發現send_update==1,send_cnt就//會開始自加,當它超過某個數值的時候,就會自動把send_update=0,表示目前已經沒//有數據發送了。而如果有數據不斷從上位機傳來,send_cnt永遠也不會超過某個數值,//因為每個中斷它都被清零,這個原理跟看門口狗喂狗的原理很像。

//補充說明:吳堅鴻程序風格是這樣的,凡是用來接收數據的緩沖區數組后綴都用_buf表//示

Unsigned char RCREG_buf[50];??//串口接收緩沖區,讀者可以根據實際項目設置大小

Unsigned char RCREG_buf_temp[50];??//臨時處理串口數據的緩沖區,可以不用那么大

//補充說明:吳堅鴻程序風格是這樣的,凡是自鎖變量名, 后綴都用_lock表示。

Unsigned char send_lock=0;

//補充說明:吳堅鴻程序風格是這樣的,凡是在main函數中用的中間變量,前綴m_,后//綴用_char或者_int表示類型

Unsigned int m_int;? ?? ?//中間變量,只要是用在main函數里,誰都可以重復用。

//主程序

main()

{

ADCON0=0x00;

ADCON1=0x0f;? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?//全部為數字信號

ADCON2=0xa1;? ?? ?? ?? ?? ?? ?? ?? ?? ???//右對齊

RBPU=0;? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?//上拉電阻

SSPEN=0;? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? //決定RA5不作為串口

TRISA2=0;??//蜂鳴器輸出

TRISC5=0;??//485通訊的流控輸出

BRG16=0;? ?? ?? ?//設置串口通信寄存器

BRGH=0;

SPBRGH=0x00;

SPBRG=0x02;? ???//22.1184MHz晶振,115200波特率

SYNC=0;

SPEN=1;

TX9=0;

TXEN=1;

TXIF=1;

RX9=0;

CREN=1;

RCIE=1;

PEIE=1;

GIE=1;

T1CON=0x24;? ???//定時器中斷配置

TMR1H=0xFE;

TMR1L=0xEF;

TMR1IF=0;

TMR1IE=1;

TMR1ON=1;

TMR1IE=1;

//補充說明,以上的內容為寄存器配置,每種不同的單片機會有點差異,

//大家不用過度關注以上寄存器的配置,只要知道有這么一回事即可

beep_dr=0;? ?? ?? ?? ?? ?? ?? ?? ?? ???//關蜂鳴器,上電初始化IO

rede_dr=0;? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?//流控IO,從機處于接收數據的狀態

while(1)

{

CLRWDT(); //喂看門狗,大家不用過度關注此行

usart_service();? ?? ???//串口通訊服務

}

}

//中斷

void interrupt timer1rbint(void)

{

if(RCIE==1&&RCIF==1)? ?//串口中斷,一次只能接受一個字節

{

RCIE=0;

RCIF=0;

++RCREG_total;? ?//以下代碼是鴻哥的在所有串口項目中用到的標準代碼

if(RCREG_total>50)??//超過緩沖區

{

RCREG_total=50;

}

RCREG_buf[RCREG_total-1]=RCREG;??//依次把上位機來的數據存入數組緩沖區

send_update=1;? ? //通知單片機目前正在接收數據

send_cnt=0;? ?? ???//及時喂狗,雖然main函數那邊不斷在累加,但是只要串口的數//據還沒發送完畢,那么它永遠也長不大,因為每個中斷都被清零,很可憐。

RCIE=1;

}

if(TMR1IE==1&&TMR1IF==1)? ? //定時中斷

{

TMR1IF=0;? ???//定時中斷標志位關閉

TMR1ON=0;? ? //定時中斷開關關閉

if(voice_time_cnt)? ?? ?? ?? ?? ?? ?? ? //控制蜂鳴器聲音的長短

{

beep_dr=1;? ?? ???//蜂鳴器響

--voice_time_cnt;? ?? ???//蜂鳴器響的聲音長短的計數延時

}

else

{

Asm(“nop”);? ?//添加此行空指令為了使else的內容跟if的內容對稱,意義不大

beep_dr=0;? ?? ?//蜂鳴器停止

}

TMR1H=0xFe;? ?//重新設置定時時間間隔

TMR1L=0x00;

TMR1ON=1;? ?? ???//定時中斷開關打開

}

}

void usart_service()??//串口服務程序,在main函數里

{

if(send_update==1)??//說明目前串口正在接收數據,不要讀緩沖區數據

{

send_lock=1;? ???//開自鎖標志

++send_cnt;? ? //只要有數據接收,send_cnt每次都被串口中斷清零

if(send_cnt>cnt_send)? ?//延時一段時間,確認緩沖區沒有繼續接受數據

{

send_cnt=0;

send_update=0;

}

}

Else??//說明當前沒有繼續接收數據了

{

if(send_lock==1)? ? //在數據已經接收完畢,并且還沒有處理過數據的情況下

{

send_lock=0;? ?//處理一次就鎖起來,不用每次都進來,除非有新接收的數據

m_int=0;? ?? ?? ?//中間變量清零,用來統計處理了多少個剛剛接收的數據

while(RCREG_read_total

{

CLRWDT();

RCREG_buf_temp[m_int]=asy_recieve();

If(m_int>=4)? ?//說明接收了5個數據以上(2個有效數據加3個數據尾)

{

if(RCREG_buf_temp[m_int-2]==0xeb&&RCREG_buf_temp[m_int-1]==0x00&&RCREG_buf_temp[m_int]==0x55)??//數據尾”eb 00 55”判斷

{

if(RCREG_buf_temp[m_int-4]==0x01&& RCREG_buf_temp[m_int-3]==0x02)

//有效數據是否為01 02的判斷

{

eusart_send(0x00); //串口發送一個填充的無效字節0x00,避免第一個字節丟失而引起的問題

eusart_send(0xeb); //串口發送應答的數據

eusart_send(0x00); //串口發送應答的數據

eusart_send(0xaa); //串口發送應答的數據

voice_time_cnt= cnt_voice_time;? ? //蜂鳴器響“滴”一聲就停

}

Else

{

eusart_send(0x00); //串口發送一個填充的無效字節0x00,避免第一個字節丟失而引起的問題

eusart_send(0xeb); //串口發送應答的數據

eusart_send(0x00); //串口發送應答的數據

eusart_send(0x55); //串口發送應答的數據

}

Break;? ?//跳出循環

}

}

m_int++;? ? //中間變量加1,

}

Buf_clear();? ?//把緩沖區的下標清零,方便下一堆數據接收與處理

}

}

}

Void Buf_clear()? ?//把緩沖區的下標清零

{

RCREG_read_total =0;

RCREG_total=0;

}

unsigned char asy_recieve()??//把串口緩沖區的數據一個個提取出來

{

unsigned char RCREG_dt;

++RCREG_read_total;? ? //已經讀出了多少個數據

RCREG_dt=RCREG_buf[RCREG_read_total -1];

if(RCREG_read_total >=RCREG_total) //只要把全部數據都讀完,馬上把緩沖區清零

{

RCREG_read_total =0;

RCREG_total=0;

}

return RCREG_dt;

}

void eusart_send(unsigned char t_data) //串口發送一個字節的數據

{

unsigned int error_delay;

Rede_dr=1;? ???//流控IO,切換至發送數據的狀態,準備發送數據,

//以下幾個空延時是必要的。

asm("nop");

asm("nop");

asm("nop");

asm("nop");

TXREG=t_data;? ?//發送數據

error_delay=0;//等待把數據發送完畢

while(1)??//此處也可以直接用延時替代.

{

CLRWDT();

if(TXIF==1)? ?//等待把數據發送完畢

{

break;

}

Else

{

++error_delay;

if(error_delay>200)??//超時也要退出,不能死等

{

break;

}

}

}

Delay11(1);? ? //此處最玄機,要特別注意。每發送完一個字節,由于不同的項目,這//里的延時間隔都不一樣,讀者根據實際情況來改。這里最容易出問題,必須要延時,尤其是連續發送一堆數據的時候.讀者也可以改成計數延時的方式,有興趣的自己動腦筋,程序框架需要改動一點。

Rede_dr=0;? ?//流控IO,發送完數據之后,務必馬上把從機切換回接收數據的狀態,把總//線釋放

}

//延時函數

void Delay11(unsigned int MS)

{

unsigned char us,usn;

while(MS!=0)? ?? ?? ???//for 12M

{

CLRWDT();

usn = 2;

while(usn!=0)

{? ?? ? CLRWDT();

us=0xf5;

while (us!=0){us--;};

usn--;

}

MS--;

}

}

(6)小結:

485跟TTL與232通訊唯一的編程區別就是多增加一個流控IO(本文是rede_dr)。此IO除了在發送數據的時候為高電平,發送完數據之后,其他任意時刻都必須為低電平,起到釋放總線的作用。略微要注意的細節是,在流控IO從低電平切換到高電平的時候,不要馬上發送數據,中間有必要加幾個空延時指令(asm(“nop”);)。

(未完待續,下節更精彩,不要走開哦)

總結

以上是生活随笔為你收集整理的485通信c语言讲解,51单片机485通讯讲解 通俗易懂的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。