目录

一、研究背景

二、硬件准备

三、软件准备

 四、实验原理

五、单片机代码

 六、Python读取串口处理数据

七、开发总结

一、研究背景

         学院给了14天的时间,一枚专业课紧的大三狗匆忙做出来。探索出一种研究阳极氧化工艺电解液中金属离子浓度在线测量和监控的设备和方式

二、硬件准备

          阳极氧化工艺电解液金属离子浓度在线监控仪,主要由高精度耐腐蚀溶液密度传感器探头、显示控制器、声光提醒装置、通讯端口、单片机以及控制PC组成。    

 

三、软件准备

         除了Keil5、Pycharm还有ISP-STC软件,考虑到会同时使用单片机和Pycharm读取串口,因此还需要额外准备虚拟串口软件MX虚拟串口。

 四、实验原理

       (1)RS485原理

        RS-485是串口通讯标准,常用于工业、自动化、汽车和建筑物管理等领域。相比于RS232总线, RS485总线采用AB双线进行差分传输,弥补了通信距离短且速率低下的特点。

        长距离布线传输时会有信号衰减,且噪声和干扰很大,传输线上的电压幅度变化体现这一点。但是采用AB线差分传输时,以A线作为信号传输线,以B线为传输参考线,利用运算元件进行差值相减可以排除干扰,输出正确的信号,这种方式叫做共模抑制,其原理如图

         RS485总线有两线制和四线制两种接线方式,四线制只能支持一对一之间的通信,现实中生活生产中多采用两线制度。如图7所示,RS485总线支持单主机单从机模式以及单主机多从机这两种通信结构,本项目实践采用的是单主机单从机结构。

          

        RS485通信设备需要使用单片机上MX485模块,如图8所示,为一个MCU控制RS485通信的示意图。如图9和图10,根据《A7双核开发板原理图》需要将RXD2与P3.0、TXD2与P3.1以及485_RE与P1.1相连。AB接口为总线,RXD2为接收器输入,TXD2为发送器输出。485_RE控制485模块的工作方式,当485_RE为1时,设置为接收器;当485_RE为0时,设置为发送器。

        主机发送给从机或从机发送给主机时都会占用AB总线,所以RS485多采用半双工模式。主机通过485_RE引脚控制MX485为发送模式,从PC端串口向MX485的TXD2引脚发送一个字节,MX485将该字节数据转化为差分信息流通过AB线进行传输。当数据发送完毕时,主机立即设置MX485为接收模式。从机的信息传输与主机类似。

        (2)Modbus通讯协议

        泽耀科技数传电台,可以通过RS485总线进行通信,通信协议是Modbus协议。

        Modbus通信协议分为“03”、“04”“06”和“10”功能码,“03”表示读多路寄存输入,输出电台采用即为“03”功能码模式。如表1和表2所示,表1为主机发送报文,表2为从机回复报文,参考数传电台说明书,得到相应的寄存器说明,如图11所示。主机和从机之间发送的字节均为bytes格式.

        

        (3)单片机与PC串口通讯

        如图12和图13所示所示,51单片机内部自带UART(Universal Asynchronous Receiver Transmitter,通用异步收发器),可以实现单片机的单向或双向串口通讯。

   

        SCON寄存器

        SCON寄存器格式如图14所示,其中SM0和SM1控制串口的工作方式,如图15所示。SM2:允许方式2或方式3多机通信控制位;REN:允许/禁止穿行接收控制位。由软件置位REN,REN=1为允许串行接收状态,可启动串行接收器RxD,开始接收信息;软件复位REN,即REN=0,则禁止接收;

        TI: 发送中断请求标志位。在方式0,当串行发送数据第8位结束时,由内部硬件自动置位,即TI=1,向主机请求中断,响应中断后必须用软件复位,即TI=0。在其他方式中,则在停止位开始发送时由内部硬件置位,必须用软件复位;

        RI:接收中断请求标志位。在方式0,当串行接收到第8位结束时由内部硬件自动置位RI=1,向主机请求中断,响应中断后必须用软件复位,即RI=0。在其他方式中,串行接收到停止位的中间时刻由内部硬件置位,即RI=1(例外情况见SM2说明),必须由软件复位,即RI=0。

        

         

        PCON寄存器

       PCON寄存器的格式如图16所示,SMOD作为波特率选择位。当用软件置位SMOD,即SMOD=1,则使串行通信方式1、2、3的波特率加倍;当SMOD=0,则各工作方式的波特率不加倍。复位时SMOD=0。

        TMOD寄存器

        TMOD寄存器的格式如图17所示,分为定时器1 T1和定时器0 T0。定时器的工作方式由M1和M0决定,如图18所示。

        

五、单片机代码

        首先用Keil对485头文件设置

#include

#include "intrins.h"

typedef unsigned int u16;

typedef unsigned char u8;

sbit RS485DIR=P1^1;//为0时处于接收状态,为1时处于发送状态

bit flagFrame = 0; //帧接收完成标志,即接收到一帧新数据

bit flagTxd = 0; //单字节发送完成标志,用来替代TXD中断标志位

unsigned char cntRxd = 0; //接收字节计数器

unsigned char pdata bufRxd[64]; //接收字节缓冲区

extern void UartAction(u8 *buf, u8 len);

void ConfigUart(u16 baud)//串口初始化

{

RS485DIR = 0; //RS485设置为接收方向

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 delay10us(u16 i)//延时10us函数

{

do

{

_nop_();

_nop_();

_nop_();

_nop_();

_nop_();

_nop_();

_nop_();

_nop_();

}while(--i);

}

void RS485_write(u8 *buf,u8 len)//发送请求读取命令

{

RS485DIR=1;//RS485设置为发送状态

while(len--)//循环发送所有字节

{

flagTxd=0;//清零发送标志

SBUF=*buf++;//发送一个字节数据

while(!flagTxd);//等待该字节发送完成

}

delay10us(5);//等待停止位完成

RS485DIR=0;//RS485设置为接收状态

}

unsigned char RS485_read(u8 *buf,u8 len)//接收数据命令

{

u8 i;

RS485DIR=0;//RS485设置为接收状态

if(len>cntRxd)//指定读取长度大于实际接收的长度时

{

len=cntRxd;

}

for(i=0;i

{

*buf++=bufRxd[i];

}

cntRxd=0;//接收计数器清零

RS485DIR=1;//RS485设置为接收状态

return len;//返回实际读取长度

}

void RS485Monitor(u8 ms)//串口接收监控,由于空闲时间判定帧结束,需在定时中断中调用,ms定时间间隔

{

static u8 cntbkp=0;

static u8 idletmr=0;

if(cntRxd>0)//当接收计数器大于零时,监控总线空闲时间

{

if(cntbkp!=cntRxd)//当接收计数器改变,即为刚刚接收到数据时,清零空闲计时

{

cntbkp=cntRxd;

idletmr=0;

}

else//总线空闲时,累计空闲时间

{

if(idletmr<30)//空闲时间小于30ms,继续增加

{

idletmr+=ms;

if(idletmr>=30)//空闲时间达到30ms时刻,判定一帧接收完毕

{

flagFrame=1;//设置帧数接收完成

}

}

}

}

else

{

cntbkp=0;

}

}

void RS485_Driver()//串口驱动函数,监测数据帧数的接收

{

unsigned char len;

unsigned char pdata buf[40];

if(flagFrame)//有点命令到达时,读取处理命令

{

flagFrame=0;

len=RS485_read(buf,sizeof(buf)-2);//将接收到的命令读取到缓冲区中

UartAction(buf,len);//传递数据帧数,调用动作执行函数

}

}

void InteruptRS485() interrupt 4//串口中断服务函数

{

if(RI)//接收到新字节

{

RI=0;//清零接收中断标志

if(cntRxd

{

bufRxd[cntRxd++]=SBUF;

}

}

if(TI)//字节发送完毕

{

TI=0;//清零发送中断标志位

flagTxd=1;//设置字节发送完成标志

}

}

         接着是主程序的设置。

#include

#include "485测试.h"

#include "intrins.h"

unsigned char T0RH = 0; //T0重载值的高字节

unsigned char T0RL = 0; //T0重载值的低字节

void ConfigTimer0(unsigned int ms);

extern void RS485_Driver();

extern void ConfigUart(unsigned int baud);

extern void Rs485Monitor(unsigned char ms);

extern void RS485_write(unsigned char *buf, unsigned char len);

void main()

{

EA = 1; //开总中断

ConfigTimer0(1); //配置T0定时1ms

ConfigUart(9600); //配置波特率为9600

while (1)

{

RS485_Driver(); //调用串口驱动

}

}

void UartAction(unsigned char *buf, unsigned char len)//串口动作函数

{

RS485_write(buf, len);

//RS485_read(buf,len);

}

void ConfigTimer0(unsigned int ms)//配置启动t0,ms为定时时间

{

unsigned long tmp; //临时变量

tmp = 11059200 / 12; //定时器计数频率

tmp = (tmp * ms) / 1000; //计算所需的计数值

tmp = 65536 - tmp; //计算定时器重载值

tmp = tmp + 33; //补偿中断响应延时造成的误差

T0RH = (unsigned char)(tmp>>8); //定时器重载值拆分为高低字节

T0RL = (unsigned char)tmp;

TMOD &= 0xF0; //清零T0的控制位

TMOD |= 0x01; //配置T0为模式1

TH0 = T0RH; //加载T0重载值

TL0 = T0RL;

ET0 = 1; //使能T0中断

TR0 = 1; //启动T0

}

void InterruptTimer0() interrupt 1/* T0中断服务函数,执行串口接收监控 */

{

TH0 = T0RH; //重新加载重载值

TL0 = T0RL;

RS485Monitor(1); //串口接收监控

}

         将程序生成的Hex文件通过isp烧入51单片机中。所示连接好接线方式。然后将文件通过ISP烧录给单片机,启动单片机,向串口助手输入01 03 00 01 00 05 f5 cb,每1s自动发送一次,请求比重浓度、波美度、百分比浓度、温度、状态运行码。如图20所示,当串口助手接受区有从机回复数据接收(01 03 0a 27 03 00 00 00 00 00 d0 00 01 40 cf),电台红绿交替闪烁,证明单片机代码可以成功实现控制数据接收的功能。

  

 六、Python读取串口处理数据

        由于要一个串口只能被一个程序范围,而COM6端口要同时被单片机和Pycharm访问,所以需要使用一个虚拟串口软件。

        保密一部分,嘿嘿~反正都能前面485通讯那么难的都做完了,用Python写个读串口还不简简单单?直接上图!

七、开发总结

        事实上,我刚做这个项目就上网遍历了一下,几乎没有人做普中51单片机与485通讯的案例。于是想着应该是创新,就没有用Arduino或者STM32来做。于是踩了个比较大的坑,你也看到了,看法过程中足足用了4个软件,改bug的过程中相当繁琐,然后这个UI做得也不行。这个方案不是没有人做,而是太麻烦了导致没有人做。我的方案有效代码拢共500多行,而且还时不时报错;我同组有Arduino开发的同学也就300多行的代码就解决了。

         下次加把劲用Arduino ESP32 试一下。感谢你的阅读!

查看原文