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通讯讲解 通俗易懂相关推荐

  1. 单片机与gsm通信c语言,实现51单片机与GSM模块通信介绍

    51单片机与GSM模块实现通信 1.检测串口线的好坏 1)将串口线插在电脑上,用短路子短接串口的2脚和3脚 2)打开串口调试助手 3)点击自动发送,在自动发送的窗口中随便发个数据,看看能不能接收到若能 ...

  2. 单片机红外通信c语言,用51单片机实现红外通讯源码

    /************************************************************************************** *            ...

  3. sja1000编程c语言,基于51单片机SJA1000 CAN通讯实现(C语言程序)

    经过一个星期的艰苦奋斗,终于将两个SJA1000通过51单片机成功通讯了!采用的是Pelican工作模式,扩展帧数据格式,验收滤波器是采用单滤波扩展帧模式. 发送和接收代码都全部相同样! 一 实物图 ...

  4. c语言编写51单片机中断程序,执行过程是怎样的?

    Q:c语言编写51单片机中断程序,执行过程是怎样的? 例如程序: #include<reg52.h>   void main(void)   {    EA=1;      //开放总中断 ...

  5. 【C 语言实现51单片机计时器】

    C语言实现51单片机计时器 代码如下 接线如下 代码如下 #include <reg51.h>sbit Hc38A=P2^2; sbit Hc38B=P2^3; sbit Hc38C=P2 ...

  6. 8255A红绿灯c语言程序,51单片机外接8255A做成的交通灯程序及PROTEUS仿真结果(附对应C语言程序).doc...

    51单片机外接8255A做成的交通灯程序及PROTEUS仿真结果(附对应C语言程序) 51单片机外接8255A做成的交通灯程序及PROTEUS仿真结果(附对应C语言程序)2010-04-21 22:0 ...

  7. 51单片机蓝牙通讯精准控制步进电机简易教学

    51单片机蓝牙通讯精准控制步进电机 1.蓝牙模块 本次用到的蓝牙模块为HC-05,关于HC-05怎么配置的文章已经写过很多,这里就不一一介绍了,我下面具体讲一些关于单片机蓝牙数据的收发简单快捷的方法实 ...

  8. 51单片机串口通讯 +Proteus仿真实验

    51单片机串口通讯 +Proteus仿真实验 ✨注意不要使用Proteus 8 Professional 8.13版本串口通信会出错.

  9. 51单片机modbus通讯源码

    51单片机modbus通讯源码 编号:779655439312452自动化大学仕

  10. 485通信c语言编程linux,Rs485通信单片机C程序

    /******************************************************************************* *  标题:              ...

最新文章

  1. 百度神马搜狗360网站地图sitemap,主动提交推送插件
  2. 记一次redis规模化运维讨论会
  3. AJAX异步检查,检查用户名是否存在
  4. linux那些事之LRU(1)
  5. [软件工程-设计模式] GRASP软件设计的模式和原则
  6. swap函数_C++ vector成员函数实现[持续更新]
  7. BZOJ 1072 排列
  8. 医院耗材管理系统开发_13
  9. 飞信2016 5.6.8820.0超级精简版
  10. Windows批处理(cmd/bat)快速新建文件夹
  11. H.264协议:Annex B格式和AVCC格式
  12. centos7安装python开发环境(python3,postgresql,sublime,supervisor)
  13. python-静态网页爬取
  14. jdk8官方下载路径
  15. css 文本超出...
  16. Java Web项目中使用Freemarker生成Word文档
  17. 嘉曼服饰上市破发,大跌16%:公司市值37亿 刘溦家族色彩浓厚
  18. 3、浙江移动网页Post登陆分析RSA算法【Post/Js逆向笔记】
  19. UVM实战 卷I学习笔记2——为验证平台加入各个组件(1)
  20. 哪些平台可以查看医学类文献?

热门文章

  1. PowerDesigner绘制ER图
  2. 三级网络技术无纸化模拟软件 (未来)教育
  3. 蚂蚁课堂视频笔记思维导图-4期 四、微服务安全
  4. 吞食天地2完全版乱码怎么解决_PDF转Word如何转换?PDF转Word乱码怎么解决?
  5. ydisk安卓版本_Y Disk HD
  6. python综合应用_班级管理系统
  7. Oracle修改字段长度
  8. SocketTool.exe 端口占用
  9. 五种MATLAB画圆方式程序
  10. vb难还是c语言难java_c语言难还是vb难?