<meter id="pryje"><nav id="pryje"><delect id="pryje"></delect></nav></meter>
          <label id="pryje"></label>

          新聞中心

          EEPW首頁 > 嵌入式系統(tǒng) > 設(shè)計(jì)應(yīng)用 > 51單片機(jī)串口通信的發(fā)送與接收 字符串

          51單片機(jī)串口通信的發(fā)送與接收 字符串

          作者: 時(shí)間:2016-11-18 來源:網(wǎng)絡(luò) 收藏
          51單片機(jī)的串口,是個(gè)全雙工的串口,發(fā)送數(shù)據(jù)的同時(shí),還可以接收數(shù)據(jù)。

          當(dāng)串行發(fā)送完畢后,將在標(biāo)志位 TI 置 1,同樣,當(dāng)收到了數(shù)據(jù)后,也會(huì)在 RI 置 1。
          無論 RI 或 TI 出現(xiàn)了 1,只要串口中斷處于開放狀態(tài),單片機(jī)都會(huì)進(jìn)入串口中斷處理程序。
          在中斷程序中,要區(qū)分出來究竟是發(fā)送引起的中斷,還是接收引起的中斷,然后分別進(jìn)行處理。
          看到過一些書籍和文章,在串口收、發(fā)數(shù)據(jù)的處理方法上,很多人都有不妥之處。
          接收數(shù)據(jù)時(shí),基本上都是使用“中斷方式”,這是正確合理的。
          即:每當(dāng)收到一個(gè)新數(shù)據(jù),就在中斷函數(shù)中,把 RI 清零,并用一個(gè)變量,通知主函數(shù),收到了新數(shù)據(jù)。
          發(fā)送數(shù)據(jù)時(shí),很多的程序都是使用的“查詢方式”,就是執(zhí)行 while(TI ==0); 這樣的語句來等待發(fā)送完畢。
          這時(shí),處理不好的話,就可能帶來問題。
          看了一些網(wǎng)友編寫的程序,發(fā)現(xiàn)有如下幾條容易出錯(cuò):
          1.有人在發(fā)送數(shù)據(jù)之前,先關(guān)閉了串口中斷!等待發(fā)送完畢后,再打開串口中斷。
          這樣,在發(fā)送數(shù)據(jù)的等待期間內(nèi),如果收到了數(shù)據(jù),將不能進(jìn)入中斷函數(shù),也就不會(huì)保存的這個(gè)新收到的數(shù)據(jù)。
          這種處理方法,就會(huì)遺漏收到的數(shù)據(jù)。
          2.有人在發(fā)送數(shù)據(jù)之前,并沒有關(guān)閉串口中斷,當(dāng) TI = 1 時(shí),是可以進(jìn)入中斷程序的。
          但是,卻在中斷函數(shù)中,將 TI 清零!
          這樣,在主函數(shù)中的while(TI ==0);,將永遠(yuǎn)等不到發(fā)送結(jié)束的標(biāo)志。
          3.還有人在中斷程序中,并沒有區(qū)分中斷的來源,反而讓發(fā)送引起的中斷,執(zhí)行了接收中斷的程序。
          對(duì)此,做而論道發(fā)表自己常用的方法:
          接收數(shù)據(jù)時(shí),使用“中斷方式”,清除 RI 后,用一個(gè)變量通知主函數(shù),收到新數(shù)據(jù)。
          發(fā)送數(shù)據(jù)時(shí),也用“中斷方式”,清除 TI 后,用另一個(gè)變量通知主函數(shù),數(shù)據(jù)發(fā)送完畢。
          這樣一來,收、發(fā)兩者基本一致,編寫程序也很規(guī)范、易懂。
          更重要的是,主函數(shù)中,不用在那兒死等發(fā)送完畢,可以有更多的時(shí)間查看其它的標(biāo)志。


          實(shí)例:
          求一個(gè)PC與單片機(jī)串口通信的程序,要求如下:
          1、如果在電腦上發(fā)送以$開始的字符串,則將整個(gè)字符串原樣返回(字符串長度不是固定的)。
          2、如果接收到1,則將P10置高電平,接收到0,P10置低電平。(用來控制一個(gè)LED
          單片機(jī)是STC89C52RC/晶振11.0592/波特率要求是9600或4800。謝謝!
          問題補(bǔ)充:可能會(huì)將【$ABCD,123456,987654ccc,aasdasd,aaaa,sssd,4D】這樣的字符串(字符串長度約為50-150個(gè)字符)傳送給單片機(jī),只能能原樣返回。

          本文引用地址:http://www.ex-cimer.com/article/201611/315873.htm
          1. 最佳答案:
          2. 下列程序,已經(jīng)調(diào)試成功。
          3. #include
          4. sbitLED=P1^0;
          5. unsignedcharUART_buff;
          6. bitNew_rec=0,Send_ed=1,Money=0;
          7. //----------------------------------------------
          8. voidmain(void)
          9. {
          10. SCON=0x50;//串口方式1,8-n-1,允許接收.
          11. TMOD=0x20;//T1方式2
          12. TH1=0xFD;[url=]//9600bps@11.0592MHz[/url]
          13. TL1=0xFD;
          14. TR1=1;
          15. ES=1;//開中斷.
          16. EA=1;
          17. while(Money==0);//等著交費(fèi),呵呵,等著接收$.
          18. while(1){
          19. if((New_rec==1)&&(Send_ed==1)){//如果收到新數(shù)據(jù)及發(fā)送完畢
          20. SBUF=UART_buff;//那就發(fā)送.
          21. New_rec=0;
          22. Send_ed=0;
          23. }}
          24. }
          25. //----------------------------------------------
          26. voidser_int(void)interrupt4
          27. {
          28. if(RI==1){//如果收到.
          29. RI=0;//清除標(biāo)志.
          30. New_rec=1;
          31. UART_buff=SBUF;//接收.
          32. if(UART_buff==1)LED=1;
          33. if(UART_buff==0)LED=0;
          34. if(UART_buff==$)Money=1;
          35. }
          36. else{//如果送畢.
          37. TI=0;//清除標(biāo)志.
          38. Send_ed=1;
          39. }
          40. }
          41. //----------------------------------------------

          串口接收程序是基于串口中斷的,單片機(jī)的串口每次接收到一字節(jié)數(shù)據(jù)產(chǎn)生一次中斷,然后再讀取某個(gè)寄存器就可以得到串口接收的數(shù)據(jù)了。然而在實(shí)際應(yīng)用當(dāng)中,基本上不會(huì)有單字節(jié)接收的情況。一般都是基于一定串口通信協(xié)議的多字節(jié)通信。在422或者485通信中,還可能是一個(gè)主機(jī)(一般是計(jì)算機(jī))帶多個(gè)從機(jī)(相應(yīng)的有單片機(jī)的板卡)。這就要求我們的單片機(jī)能夠在連續(xù)接收到的串口數(shù)據(jù)序列中識(shí)別出符合自己板卡對(duì)應(yīng)的通信協(xié)議,來進(jìn)行控制操作,不符合則不進(jìn)行任何操作。簡而言之就是,單片機(jī)要在一串?dāng)?shù)據(jù)中找到符合一定規(guī)律的幾個(gè)字節(jié)的數(shù)據(jù)。

          先來說下怎樣定串口協(xié)議吧。這個(gè)協(xié)議指的不是串口底層的協(xié)議,而是前面提到的數(shù)據(jù)幀協(xié)議。一般都是有幀頭(2~3個(gè)字節(jié)吧),數(shù)據(jù)(長度根據(jù)需要),結(jié)束位(1位,有時(shí)候設(shè)計(jì)成校驗(yàn)字節(jié),最簡單的校驗(yàn)也就是前面所有數(shù)據(jù)求和)。

          比如0xaa 0x55 +(數(shù)據(jù)部分省略)+校驗(yàn)和(除了aa 55 之外數(shù)據(jù)的和),如果要是多板卡的話有時(shí)候還要在幀頭后面加一個(gè)板選字節(jié)(相當(dāng)于3字節(jié)幀頭了)。

          第一次寫串口接收程序的時(shí)候,我首先想到的就是定義一個(gè)全局變量(實(shí)際上最好是定義局部靜態(tài)變量),初始值設(shè)置為0,然后每進(jìn)一次中斷+1,然后加到串口通信協(xié)議的長度的時(shí)候再清零。然后判斷幀頭、校驗(yàn)。寫完了之后我自己都覺得不對(duì),一旦數(shù)據(jù)錯(cuò)開了一位,后面就永遠(yuǎn)都接收不到數(shù)了。無奈看了一下前輩們的代碼,跟我的思路差不多,只不過那個(gè)計(jì)數(shù)值跟接收到的數(shù)據(jù)時(shí)同時(shí)判斷的,而且每次中斷都要判斷,一旦不對(duì)計(jì)數(shù)的那個(gè)變量就清零。

          廢話少說,直接上一段代碼讓大家看看就明白了。(通信協(xié)議姑且按照簡單的aa 55 一個(gè)字節(jié)數(shù)據(jù) 一個(gè)字節(jié)校驗(yàn),代碼是基于51單片機(jī)的)。接收成功則在中斷程序中把串口接收成功標(biāo)志位置1。

          1. 然后串口中斷部分
          2. voidser()interrupt4
          3. {
          4. staticunsignedcharcount;//串口接收計(jì)數(shù)的變量
          5. RI=0;//手動(dòng)清某個(gè)寄存器,大家都懂的
          6. receive[count]=SBUF;
          7. if(count==0&&receive[count]==0xaa)//同時(shí)判斷count跟收到的數(shù)據(jù)
          8. {
          9. count=1;
          10. }
          11. elseif(count==1&&receive[count]==0x55)
          12. {
          13. count=2;
          14. }
          15. elseif(count==2)
          16. {
          17. count++;
          18. }
          19. elseif(count==3&&receive[count]==receive[2])//判斷校驗(yàn)和,數(shù)據(jù)多的話是求//和,或者其他的校驗(yàn)方法,也可能是固定的幀尾
          20. {
          21. count=0;
          22. uart_flag=1;//串口接收成功標(biāo)志,為1時(shí)在主程序中回復(fù),然后清零
          23. ES=0;//關(guān)中斷,回復(fù)完了再ES=1;
          24. }
          25. else
          26. {
          27. count=0;//判斷不滿足條件就將計(jì)數(shù)值清零
          28. }
          29. }

          第一次做的串口大概就按照這個(gè)方法寫完了(我后來看過其他的代碼,有人用switch語句寫的,邏輯跟這個(gè)也差不多,不過我還是感覺用if else來寫清晰一些),

          不過在測試的時(shí)候發(fā)現(xiàn)了bug,如果數(shù)據(jù)幀發(fā)送一半,然后突然停止,再來重新發(fā),就會(huì)丟失一幀的數(shù)據(jù)。比如先接受到aa 55,然后斷了,再進(jìn)來aa 55 01 01,就不受控制了。后來我也想到一個(gè)bug,如果在多設(shè)備通信中,屬于其他設(shè)備的的幀數(shù)據(jù)最后一位是aa(或者最后兩位為aa 55 ,或者最后3位為aa 55 板選),下一次通信的數(shù)據(jù)就接收不到了。

          當(dāng)時(shí)對(duì)于數(shù)據(jù)突然中斷的bug,沒有想到很好的解決辦法,不過這種情況幾率極小,所以一直用這個(gè)方法寫也沒有問題。多設(shè)備通信最后一位恰好是aa的幾率也很小,出問題的可能也很小。當(dāng)時(shí)項(xiàng)目里面的控制數(shù)據(jù)跟校驗(yàn)恰好不可能出現(xiàn)aa,于是我把if(count==0&&receive[count]==0xaa)改成了if(receive[count]==0xaa)其他都沒變,解決了,沒有bug了。

          后來我又寫了幾次單片機(jī)程序,才想到了一些解決問題的方法——不過改天再接著寫吧,太累了,明天還要上班呢。

          在后來的項(xiàng)目中,真的遇到了數(shù)據(jù)位跟校驗(yàn)位都可能出現(xiàn)aa的情況。我考慮到每次數(shù)據(jù)都是連續(xù)發(fā)送的(至少我們用labwindows做的上位機(jī)程序是這樣的),成功接收到了一幀數(shù)據(jù)是要有一定時(shí)間回復(fù)的,也就是說如果接收到一半,但是很長時(shí)間沒接收到數(shù)據(jù),把計(jì)數(shù)值count清零就ok啦。涉及時(shí)間的問題自然要用定時(shí)器來實(shí)現(xiàn)啦。

          這次的通信協(xié)議如下,串口波特率19200,2個(gè)幀頭aa 55 ,一個(gè)板選,6字節(jié)數(shù)據(jù),一個(gè)校驗(yàn)字節(jié)(除幀頭外其他數(shù)據(jù)的和)。

          1. 全局變量定義
          2. unsignedcharboardAddr;//板選地址,通過檢測幾個(gè)io引腳,具體怎么得到的就不寫了,很簡單的
          3. unsignedcharg_DatRev[10]={0};//接收緩存
          4. bitretFlag=0;//為1代表串口接收到了一幀數(shù)據(jù)
          5. 串口初始化函數(shù),晶振22.1184
          6. voidinit_uart()
          7. {
          8. SCON=0x50;//串口方式1允許接收
          9. TMOD=0x21;//定時(shí)器1,方式2,8位自動(dòng)重載,同時(shí)配置定時(shí)器0,工作方式1
          10. PCON=0x80;//波特率加倍
          11. TH1=0xfa;
          12. TL1=0xfa;//寫入串口定時(shí)器初值
          13. TH0=(65536-2000)/256;//寫入定時(shí)器0初值,串口傳輸一個(gè)字節(jié)時(shí)間為(1/19200)*10,計(jì)算得0.52ms
          14. TL0=(65536-2000)%256;//定時(shí)器0定時(shí)大約1ms多
          15. EA=1;
          16. ET0=1;//波特率:1920022.1184M初值:250(0xfa)
          17. IE|=0x90;
          18. TR1=1;
          19. }
          20. 串口中斷函數(shù)
          21. voidUART_INT(void)interrupt4
          22. {
          23. staticunsignedcharcount;//串口接收計(jì)數(shù)的變量
          24. RI=0;
          25. g_DatRev[count]=SBUF;
          26. if(g_DatRev[count]==0xaa&&count==0)//幀頭
          27. {
          28. count=1;
          29. }
          30. elseif(count==1&&g_DatRev[count]==0x55)
          31. {
          32. count=2;
          33. }
          34. elseif(count==2&&g_DatRev[2]==boardAddr)
          35. {
          36. CK=g_DatRev[count];
          37. count=3;
          38. }
          39. elseif(count>=3&&count<9)
          40. {
          41. CK+=g_DatRev[count];
          42. count++;
          43. }
          44. elseif(count==9&&CK==g_DatRev[9])
          45. {
          46. ES=0;
          47. retFlag=1;
          48. count=0;
          49. }
          50. else
          51. {
          52. count=0;
          53. }
          54. resettimer();
          55. }
          56. //判斷count不為0的話就啟動(dòng)定時(shí)器
          57. voidresettimer()
          58. {
          59. TR0=0;
          60. TH0=(65536-2000)/256;
          61. TL0=(65536-2000)%256;
          62. if(count!=0)
          63. {
          64. TR0=1;
          65. }
          66. }
          67. 定時(shí)器中斷函數(shù)
          68. voidT0_time()interrupt1
          69. {
          70. TR0=0;
          71. TH0=(65536-2000)/256;
          72. TL0=(65536-2000)%256;
          73. count=0;
          74. }

          這種方法的確是本人自己想出來的,別人可能也這樣做過,但我這個(gè)絕對(duì)不是抄襲或者模仿來的。這樣寫的確可以避免前面提到過的bug,不過代價(jià)是多用了一個(gè)定時(shí)器的資源,而且中斷函數(shù)里的內(nèi)容更多了,占用了更多的時(shí)間。

          要是能把第一種方法改進(jìn)一下就好了,主要是那個(gè)校驗(yàn)不能為aa的那個(gè)bug,因?yàn)楫吘箓鬏數(shù)揭话胪蝗粩嗔说目赡苄允欠浅P〉?。后來我想第一個(gè)判斷if(count==0&&receive[count]==0xaa)好像有點(diǎn)太嚴(yán)格了,考慮到第二字節(jié)的幀頭,跟板選地址不可能為aa,于是把這個(gè)改寫為if(count>=0&&count<=2&& receive[count]==0xaa),這樣就把bug出現(xiàn)的幾率降到了非常小,也只是在前一幀結(jié)尾數(shù)據(jù)恰好為 aa 55 板選 的時(shí)候才出現(xiàn),幾率是多少大家自己算一下吧,呵呵。這樣我自己覺得,昨天寫的那種方法改進(jìn)到這個(gè)程度,應(yīng)該算可以啦,反正我是很滿意了。

          實(shí)際上我還想過其他的方法,比如緩存的數(shù)組采用移位寄存的方式。拿前面的4個(gè)字節(jié)的協(xié)議為例。

          1. voidser()interrupt4
          2. {
          3. unsignedchari;
          4. RI=0;
          5. for(i=0;i<3;i++)
          6. {
          7. receive[i]=receive[i+1];
          8. }
          9. receive[3]=SBUF;
          10. if(reveive[0]==0xaa&&receive[1]==0x55&&receive[2]==receive[3])
          11. {
          12. ret_flag=1;
          13. ES=0;
          14. }
          15. }

          這段代碼看上去可是簡單明了,這樣判斷可是不錯(cuò)啊,同時(shí)判斷幀頭跟校驗(yàn)不會(huì)產(chǎn)生前面提到的bug。說實(shí)話當(dāng)時(shí)我剛想出這種方法并寫出來的時(shí)候,馬上就被我給否了。那個(gè)for循環(huán)可真是很占時(shí)間的啊,延時(shí)函數(shù)都是這樣寫的。每次都循環(huán)一下,這延時(shí)太長,通信速度太快的話就不能接收到下一字節(jié)數(shù)據(jù)了。最要命的是這個(gè)時(shí)間的長度是隨著通信協(xié)議幀的字節(jié)數(shù)增加而增加的,如果一次要接收幾十個(gè)字節(jié),肯定就玩完了。這種方法我一次都沒用過。

          不過我居然又想出來了這種方法的改良措施,是前兩天剛想出來的,呵呵,還沒有實(shí)踐過呢。

          下面代碼的協(xié)議就按第二段程序(定時(shí)器清零的那個(gè)協(xié)議,一共10字節(jié))

          全局變量

          1. bitret_flag;
          2. unsignedcharreceive[256]={0};
          3. unsignedcharboardaddress;
          4. 中斷函數(shù)
          5. voidser()interrupt4
          6. {
          7. staticunsignedchari=0;
          8. staticunsignedchartotal=0;
          9. RI=0;
          10. receive[i]=SBUF;
          11. total=total-receive[i-7]+receive[i-1];
          12. if(receive[i-9]==0xaa&&receive[i-8]==0x55
          13. &&receive[i-7]==boardaddress&&receive[i]==total
          14. )
          15. {
          16. ret_flag=1;
          17. ES=0;
          18. }
          19. i++;
          20. }


          之所以要定義256個(gè)長度的數(shù)組,就是為了能夠讓數(shù)組“首尾相接”。因?yàn)? -1 = 255 , 255+1 = 0。而且我在計(jì)算校驗(yàn)的時(shí)候也改進(jìn)了算法,不會(huì)因?yàn)閿?shù)據(jù)長度的增加而增加計(jì)算校驗(yàn)值的時(shí)間。這種方法也是我不久前才想出來的,所以還沒有經(jīng)過實(shí)際的驗(yàn)證。上面的代碼可能會(huì)有邏輯上的錯(cuò)誤,如果真有錯(cuò)誤,有網(wǎng)友看出來的話,請(qǐng)?jiān)谙旅媪粞愿嬖V我。這個(gè)方法也是我原創(chuàng)的哦,別人也肯能會(huì)想到,不過我這個(gè)絕對(duì)不是抄襲別人的。

          上面的代碼最大的缺點(diǎn)就是變量定義的太多了,太占ram資源了,編譯的時(shí)候可能會(huì)出現(xiàn)錯(cuò)誤,畢竟51單片機(jī)才128字節(jié)的ram(有的資源也很豐富的,比如c8051系列的),這一下子就是256字節(jié)的變量。不過對(duì)于資源多一些的單片機(jī),這樣寫還是可以的。要是能有4bit在一起的數(shù)據(jù)類型就好了,呵呵,verilog代碼里面是可以的,C語言里貌似不行啊。

          要想能在例如51單片機(jī)上運(yùn)行,只能按照下面的折中方式了,也就是把i相關(guān)的量都與一個(gè)0x0f

          1. 全局變量
          2. bitret_flag;
          3. unsignedcharreceive[16]={0};//可以考慮在定義時(shí)加上idata,畢竟還可能是32
          4. //或者64長度的數(shù)組呢unsignedcharidatareceive[16]={0};
          5. unsignedcharboardaddress;
          6. 中斷函數(shù)
          7. voidser()interrupt4
          8. {
          9. staticunsignedchari=0;
          10. staticunsignedchartotal=0;
          11. RI=0;
          12. receive[i&0x0f]=SBUF;
          13. total=total-receive[(i-7)&0x0f]+receive[(i-1)&0x0f];
          14. if(receive[(i-9)&0x0f]==0xaa&&receive[(i-8)&0x0f]==0x55
          15. &&receive[(i-7)&0x0f]==boardaddress&&receive[i&0x0f]==total
          16. )
          17. {
          18. ret_flag=1;
          19. ES=0;
          20. }
          21. i++;
          22. }



          評(píng)論


          技術(shù)專區(qū)

          關(guān)閉
          看屁屁www成人影院,亚洲人妻成人图片,亚洲精品成人午夜在线,日韩在线 欧美成人 (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })();