基于新塘51单片机的485通信
序言
最近有一个项目用新塘的51FB9A单片机做时控开关(上位机控制)控制继电器,但是我原来也没整过485啊,本来以为这玩意很难。没想到才了一些坑之后还觉得挺简单的。这玩意就是串口嘛。没啥难的,485都已经20年了基本上,很多资料都是大把的多。
485介绍
1. Modbus总线协议的一种。相比于232,485有更好的传输速度和更远的传输距离,更好的抗干扰性。
2. 基本上很多的远距离串行通信都是用的CANBUS和RS485,我因为工作原因所以也使用的是485。
3.回顾一下485,半双工通信,差分信号:AB线电压差来决定这个0和1(因为现在做单片机我暂时不
考虑传导辐射,差分干扰等问题,这些交给硬件去解决),匹配电阻120Ω,这些都是基本了解即可。
485IC
我使用的485IC是淘宝上面那种2元一个的485模块用的IC就是MAX485,就是这玩意!相信这个引脚图已经很清晰了。
一开始的时候我就比较好奇(因为我比较菜而且也没做过485)485怎么和单片机通信的呢,后来查了一大堆资料,就是串口的串行异步通信,没啥玩意。和串口通信的最大区别就是这个使用485的时候得使能引脚。这个貌似不算什么大问题吧。
485代码
下面就是正题了,讲一讲485的通信主要是 总结一下我踩过的坑。所谓坑就是你不知道哪里有坑,只有一脚踩下去了
才发现卧槽,这竟然有个坑!
下面是我的代码,写的不好,还要继续修改,现在只是架子。这个只是其中的主要部分,有需要整个工程就把邮箱给我,我给你发过去。
#include "N76E003.h"
#include "Common.h"
#include "Delay.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "string.h"
/*
文件:TIM_Switch_485
名称:时控开关
工程师:两颗楠木
版本号:V3.0
时间:2021.6.10
功能:从上位机接收到数据控制多个单片机进而控制多个继电器开关
上位机通过USB转485和单片机串口通信
协议:单片机接收:01/02(0F主机信息,01从机信息)+0X(组地址)+地址(从机地址)+开关状态
单片机发送:01(01从机信息)+0X(组地址)+地址(从机地址)+开关状态
备注:485总线无竞争机制,无干扰处理。
*/
#define Master_Com 0x0F//主机发送
#define Slave_Com 0x01//从机发送
#define Address_Group 0x01//高地址(分组地址)
#define Address_Slave 0x03//低地址(组内具体从机)
#define Function_number_Close 0x01//高地址
#define Function_number_Open 0x02//低地址
struct DataAdress
{
uint8_t *DataArray_Address;
uint8_t *IndexData;
} ArrayAdress;
static uint8_t *Addres ;
void RS485(void)
{
int i;
if(*ArrayAdress.IndexData==4)
{
EA=0;//中断保护,不利于实时性
//用了全局变量就可以不用这个指针了,但是我写完了,不删除了。
if(*(ArrayAdress.DataArray_Address++)==Master_Com)
{
if(*(ArrayAdress.DataArray_Address++)==Address_Group)
{
if(*(ArrayAdress.DataArray_Address++)==Address_Slave)
{
if(*(ArrayAdress.DataArray_Address)==Function_number_Close)
{
P04=1;
P05=0;//拉低发送信息
Send_Data_To_UART0(Master_Com);
Send_Data_To_UART0(Address_Group);
Send_Data_To_UART0(Address_Slave);
Send_Data_To_UART0(Function_number_Close);
P05=1;
}
else if(*ArrayAdress.DataArray_Address==Function_number_Open)
{
P04=0;
P05=0;
Send_Data_To_UART0(Slave_Com);
Send_Data_To_UART0(Address_Group);
Send_Data_To_UART0(Address_Slave);
Send_Data_To_UART0(Function_number_Open);
P05=1;
}
}
}
/*memset函数做的事情是将一个数值复制到一片地址中,第一个参数是地址,第二个是数据最后一个是大小*/
}
*ArrayAdress.IndexData=0;
/*memset(Addres,NULL,4);*/
for( i=0;i<4;i++)
{
*ArrayAdress.DataArray_Address=0;
ArrayAdress.DataArray_Address--;
}
EA=1;//解除保护
}
}
void main (void)
{
P05_PushPull_Mode;
P04_PushPull_Mode;
//P05=1;P05是485IC使能脚(低电平接收)引脚已经由硬件拉高
P04=0;
InitialUART0_Timer1(19200);//UART0 Baudrate initial,T1M=0,SMOD=0
while(1)
{
RS485();
}
}
void SerialPort0_ISR(void) interrupt 4
{
static uint8_t DataArray[4],Index=0,Master_Slave_Flag=0;
if(RI==1)
{
if((Master_Com==SBUF)||Master_Slave_Flag)
{
Master_Slave_Flag=1;
DataArray[Index++]=SBUF;
if(Index==4)
{
ArrayAdress.IndexData=&Index;
Addres=ArrayAdress.DataArray_Address=DataArray;
Master_Slave_Flag=0;
}
}
}
RI=0;
}
更新代码2021.6.11
注意事项
1.这个我在做的时候要清空数组和下标但是我做的时候忘记了,导致了数组越界引用,但是代码没错,只是在最后有一个提示没仔细看后来在DEBUG的时候发现这个数据不对(Index由于越界引用变成了0X0A再取余本来应该是0现在变成了2)。
2. Usrt0Dat和中断中的局部变量i:就很奇怪这个Usrt0Dat会自减(会随着这个局部变量i变化)但是程序中没有设置让他变化(自减),虽然对于程序没有什么影响,但是在监测时看到了!就很离谱!并且在程序运行到最后时i本来由0会变成1。我就很迷惑
3. 这个通信协议中最后的那个发送给上位机的值要给延时,不然在多机通信时会发生:最先发送数据的单片机可以发送应答信号,其他单片机都不能发送应答信号。或者上位机无法正常和单片机通信等情况。但是加了这个延时就好了,我真是一脸蒙圈。我为什么在这里加呢我觉得这个是因为通信协议内部原因,就想锁死接收或者演示一段时间。没想到真的可以!
4. 这一周就是在改BUG,比如说接受乱码:这个是因为AB线插反了
5. 当多机通信时出现只有一个从机正常其他从机都不能正常控制可能问题是
存储时序的数据包出现错误。(无法有效清除,或者过度清楚,Debug一下基本就没啥问题了)
6.还比如说从机正常接收数据并且可以发送数据到串口就是没输出,有可能是硬件虚焊了。
7.当有从机出现问题时可能会影响整个通信。