当前位置: 首页 > news >正文

STC15单片机-RS-485通信

RS-485接口

RS-485为工业设备常用的传输接口,采用差分信号逻辑+2V~+6V表示逻辑1,-6V~-2V表示逻辑0。通信方式为半双工,工作模式为主从模式

在低速、短距离、无干扰的场合可以采用普通的双绞线,反之,在高速、长线传输时,则必须采用阻抗匹配(一般为120欧 )的RS485专用电缆,而在干扰恶劣的环境下还应采用铠装型双绞屏蔽电缆。

差分传输是一种信号传输的技术,区别于传统的一根信号线一根地线的做法,差分传输在这两根线上都传输信号,这两个信号的振幅相同,相位相反。在这两根线上的传输的信号就是差分信号。信号接收端比较这两个电压的差值来判断发送端发送的逻辑状态。在电路板上,差分走线必须是等长、等宽、紧密靠近、且在同一层面的两根线。

实现485通信的SP3485芯片

芯片介绍

SP3481SP3485是一系列+3.3V低功耗半双工收发器,它们完全满足RS-485和 RS-422串行协议的要求;并且,SP3481、SP3485采用单一+3.3V的工作电源。

​ SP3481、SP3485与Sipex的SP481、SP483和SP485的管脚互相兼容,同时兼容工业标准规范。SP3481和SP3485符合RS-485/RS-422串行协议的电气规范,数据传输速率可高达10Mbps(带负载)。SP3481还包含低功耗关断模式。

特点

  1. RS-485和 RS-422收发器
  2. 工作电源为+3.3V
  3. 可与+0.5V的逻辑电路共同工作
  4. 发送器/接收器使能
  5. 低功耗关断模式(SP3481)
  6. -7V~+12V的共模输入电压范围
  7. 允许在同一串行总线上连接32个收发器
  8. 与工业标准75176管脚配置兼容
  9. 发送器输出短路保护

引脚示意图

在这里插入图片描述

引脚功能

RO:接收器输出

RE:接收器输出使能(低电平有效)

DE:发送器输出使能(高电平有效)

DI:发送器输入

GND:接地连接

A:发送器输出/接收器输入反相

B:发送器输出/接收器输入反相

Vcc:正极电源(+3.3V<VCC<+3.60V)

发送器

​ SP3481 和SP3485的发送器输出是差分输出,满足RS-485和 RS-422标准。空载时输出电压的大小为0V~+3.3V。即使在差分输出连接了54欧负载的条件下,发送器仍可保证输出电压大于1.5V。SP3481和 SP3485有一根使能控制线(高电平有效)。DE (Pin3)上的逻辑高电平将使能发送器的差分输出。如果 DE (Pin3)为低,则发送器输出呈现三态。
​ SP3481和SP3485收发器的数据传输速率可高达10Mbps。发送器输出最大250mA Isc的限制使SP3481和SP3485可以受-7.0V~+12.0V共模范围内的任何短路情况,保护IC不受到损坏。

接收器

​ SP3481和 SP3485接收器的输入是差分输入,输入灵敏度可低至土200mV。接收器的输入电阻通常为15千欧(最小为 12千欧)。一7V~+12V的宽共模方式范围允许系统之间存在大的零电位偏差。SP3481和SP3485的接收器有一个三态使能控制脚。如果 !RE(Pin2)为低,接收器使能,反之接收器禁止。
​ SP3481和SP3485接收器的数据传输速率可高达10Mbps。两者的接收器都有故障自动保护( fail-safc)特性,该特性可以使得输出在输入悬空时为高电平状态。

SP3481的关断模式

​ SP3481可以工作在关断模式。要使能关断模式,发送器和接收器必须同时禁能。当DE(Pin3)为低且 !RE(Pin2)为高时SP3481进入关断模式。关断模式下,电源电流通常降至1uA,最大为10uA。

发送功能真值表

在这里插入图片描述

如果DE为1,则为发送模式

DI为1,则A端电压大于B端电压,使A-B的电压差在+2V ~ +6V之间,输出逻辑1

DI为0,则A端电压小于B端电压,使A-B的电压差在-6V ~ -2V之间,输出逻辑0

接收功能真值表

在这里插入图片描述

如果DE为0,则为接收模式

A和B的电压差大于0.2V(+200mv)时,则RO口输出1

A和B的电压差小于-0.2V(-200mv)时,则RO口输出0

RS-485原理图

在这里插入图片描述

该原理图设置时,模式控制位RE和DE接在了一起,用单片机的一个引脚控制,单片机该引脚输出高电平,则为发送模式,单片机输出低电平,则为接收模式

单片机发送数据:要发送的数据通过单片机的串口MCU_TXD1发送到DI口,若485芯片处于发送模式,则会将会这些数据再从右边的A、B端口发送出去

单片机接收数据:从A、B端口接收到的电压差信号经过转换,再从RO口输入到单片机的串口接收口MCU_RXD1

程序

文件结构

在这里插入图片描述

与串口通信的文件结构相同,485相关的函数写到了UART1.c源文件中

main.c ->主函数文件,包含main函数等;

Public.c ->公共函数文件,包含Delay延时函数等;

Sys_init ->系统初始化函数,包含GPIO初始化函数等;

LED.c->LED外设函数,包含LED打开、关闭函数等;

Timer0.c ->定时器函数,包含定时器初始化,中断函数等;

KEY1.c->按键1函数,包含按键检测,中断函数等;

KEY2.c ->按键⒉函数,包含按键状态机检测函数等;

PWM. c ->PWM初始化、亮度调节、占空比储存与恢复函数等;

IAP.c->字节读、字节写、扇区擦除等函数。

UART1.c->串口1初始化、发送、中断等函数。

说明:RS-485接口位于串口1。

程序运行结果

系统上电,会通过RS-485发送系统初始化信息,然后不断检测按键2的状态,按键2按下后会调节PWM灯的亮度,在调节亮度的同时,按键的状态会转为RS-485差分信号发送出去,可以通过电脑串口助手查看发送出去的数据,需要用到RS-485转串口工具

在这里插入图片描述

如何查看RS-485数据

那通过RS-485 A、B端发送的差分信号如何查看呢?除非有另一台设备可以接收该485信号,并做出相应的回应;在没有多余设备的情况下,可以用RS-485转串口工具,将接收到的485信号再转换为串口信号,将工具插到电脑上后,可以通过串口助手查看原本单片机发送的数据

因为开发板上已经集成了SP3485芯片,所以可以直接将开发板的A、B端子和工具的A、B端子用杜邦线连接起来,然后将工具的另一头插入到电脑USB接口中,在串口助手中查看MCU串口的数据,当然,在程序的串口中断中如果写了接收处理,那电脑串口助手也是可以通过RS-485转串口工具把数据发送到单片机上的

其数据传输原理如图
在这里插入图片描述

UART1.c:

UART.h和UART1.h和串口的一样,不变

在编写串口通信的过程中已经把RS-485的函数也定义了,这次只需把函数体实现就行

在串口通信的基础上,先把串口1从P30和P31切换到P36和P37,把AUXR1第7位清0,第6位置1即可;

添加RS-485的宏定义,分别实现RS-485发送模式和RS-485接收模式的函数,UART1_MAX485_DE_nRE为宏名,实质是P20口,将P20口置为1则是发送模式,置0则是接收模式;

然后在串口发送字符、发送数组、发送字符串函数中添加RS-485设置发送模式函数和RS-485设置接收模式函数,先将RS-485设置为发送模式,然后进行串口发送,发送完毕后,再将RS-485设置为接收模式;这个可以看原理图进行理解,SP3485芯片与单片机的通信是通过串口实现的,将SP3485设置为发送模式后,单片机将数据通过串口发送给SP3485,SP3485再将数据转换为差分信号从A、B端发送出去

/* Includes ------------------------------------------------------------------*/
#include <main.h>

/* Private define-------------------------------------------------------------*/
#define S1_S0 BIT6      //定义串口切换寄存器AUXR1的第6位
#define S1_S1 BIT7      //定义串口切换寄存器AUXR1的第7位

#define UART1_MAX485_DE_nRE   P20           //定义模式控制引脚
#define UART1_MAX485_SendMode   (bit)1      //RS-485设置为发送模式
#define UART1_MAX485_RecMode    (bit)0      //RS-485设置为接收模式

#define UART1_Send_LENGTH 20
#define UART1_Rec_LENGTH  10
/* Private variables----------------------------------------------------------*/
static uint8_t idata ucSend_Buffer[UART1_Rec_LENGTH]  = {0};
static uint8_t idata ucRec_Buffer[UART1_Rec_LENGTH]   = {0x00};
/* Private function prototypes------------------------------------------------*/
static void Init();                                 //串口初始化
static void SendData(uint8_t dat);                  //串口发送字符
static void SendArray(uint8_t *p_Arr,uint16_t LEN); //串口发送数组
static void SendString(uint8_t *p_Str);             //串口发送字符串
static void Protocol();                             //串口协议

static void RS485_Set_SendMode();                   //RS-485设置为发送模式
static void RS485_Set_RecMode();                    //RS-485设置为接收模式

/* Public variables-----------------------------------------------------------*/
UART_t idata UART1 = 
{
  Band_115200,
  FALSE,
  FALSE,
  0,

  ucSend_Buffer,
  ucRec_Buffer,
  Init,
  SendData,
  SendArray,
  SendString,
  Protocol,

  RS_485,
  RS485_Set_SendMode,
  RS485_Set_RecMode
};

/*
* @name   Init
* @brief  串口1初始化
* @param  None
* @retval None   
*/
static void Init()
{
  //把串口1映射到RS-485连接到的P37和P36引脚,
  AUXR1 &= ~S1_S1;        //AUXR1第7位清0
  AUXR1 |=  S1_S0;        //AUXR1第6位置1

  SCON = 0x50;		//8位数据,可变波特率,REN位置1,开启中断
  //辅助寄存器AUXR的第6位T1x12置1,设置定时器1的速度是传统8051的12倍,不分频
	AUXR |= 0x40;
  //1111 1110 最低位S1ST2清0,选择定时器1作为串口1的波特率发生器
	AUXR &= 0xFE;		//串口1选择定时器1为波特率发生器
  
	TMOD &= 0x0F;		//设定定时器1为16位自动重装方式
	switch (UART1.ucBandRate)
    {
        case Band_4800:   TL1 = 0xCD; TH1 = 0xFD; break;
        case Band_9600:   TL1 = 0xE0; TH1 = 0xFE; break;
        case Band_19200:  TL1 = 0x70; TH1 = 0xFF; break;
        case Band_115200: TL1 = 0xE8; TH1 = 0xFF; break;
        default:          TL1 = 0xCD; TH1 = 0xFD; break;
    }
	ET1 = 0;		//禁止定时器1中断
	TR1 = 1;		//启动定时器1
}

/*
* @name   SendData
* @brief  发送字符
* @param  dat:待发送的数据
* @retval None   
*/
static void SendData(uint8_t dat)
{
  while(UART1.ucTX_Busy_Flag);    //等待前面的数据发送完,串口中断中发送数据后标志位会置FALSE
  UART1.ucTX_Busy_Flag = TRUE;    //置为忙碌标志位
  SBUF = dat;                     //写数据到UART寄存器
}

/*
* @name   SendArray
* @brief  发送数组
* @param  p_Arr:数组首地址,LEN:发送长度
* @retval None   
*/
static void SendArray(uint8_t *p_Arr,uint16_t LEN)
{
  uint16_t i = 0;

  UART1.RS485_Set_SendMode();   //RS-485设置为发送模式

  for(i = 0;i<LEN;i++)
  {
    UART1.UART_SendData(*(p_Arr+i));
  }
  while(UART1.ucTX_Busy_Flag);   //等待数据发送完
  Public.Delay_ms(1);            //等待RS-485数据传输完
  
  UART1.RS485_Set_RecMode();    //让RS-485切回到接收模式,默认处于接收模式
}

/*
* @name   SendString
* @brief  发送字符串
* @param  p_Arr:字符串首地址
* @retval None   
*/
static void SendString(uint8_t *p_Str)
{
  UART1.RS485_Set_SendMode();     //RS-485设置为发送模式

  while(*(p_Str) != '\0')
  {
    UART1.UART_SendData(*(p_Str++));
  }
  while(UART1.ucTX_Busy_Flag);
  Public.Delay_ms(1);             //等待RS-485数据传输完

  UART1.RS485_Set_RecMode();      //RS-485设置为接收模式
}

/*
* @name   RS485_Set_SendMode
* @brief  RS_485接口设置为发送模式
* @param  None
* @retval None   
*/
static void RS485_Set_SendMode()
{
  //485模式控制引脚置1,设置为发送模式
  UART1_MAX485_DE_nRE = UART1_MAX485_SendMode;
  //延时一会,待硬件稳定
  Public.Delay_ms(1);
}

/*
* @name   RS485_Set_RecMode
* @brief  RS_485接口设置为接收模式
* @param  None
* @retval None   
*/
static void RS485_Set_RecMode()
{
  //485模式控制引脚清0,设置为接收模式
  UART1_MAX485_DE_nRE = UART1_MAX485_RecMode;
  //延时一会,待硬件稳定
  Public.Delay_ms(1);
}

/*
* @name   putchar
* @brief  字符发送函数重定向
* @param  c:发送的字符
* @retval char   
*/
extern char putchar(char ch)
{
  UART1.UART_SendData((uint8_t)ch);   //在putchar函数内直接调用串口发送字符函数
  return ch;
}

/*
* @name   UART1_isr
* @brief  串口1中断处理函数
* @param  None
* @retval None   
*/
void UART1_isr() interrupt 4
{
  if(RI)
  {
    RI = (bit)0;                   //清除接收中断标志
    /*UART1_Rec_LENGTH宏定义为10,所以接收的数据不能超过10个字节
    UART1.ucRec_Cnt表示数组下标,初始化为0*/
    if(UART1.ucRec_Cnt < UART1_Rec_LENGTH)
    {
      ucRec_Buffer[UART1.ucRec_Cnt++] = SBUF;
    }
    UART1.ucRec_Flag = TRUE;      //接收完成标志位
  }

  if(TI)
  {
    TI = (bit)0;                    //清除发送中断标志
    UART1.ucTX_Busy_Flag = FALSE;   //清除忙碌标志
  }
}

/*
* @name   Protocol
* @brief  串口协议
* @param  None
* @retval None   
*/
static void Protocol()
{

}
/********************************************************
  End Of File
********************************************************/

PWM.c:

只放被改变的PWM灯调整亮度的函数,其他如占空比设置函数、IAP备份和恢复函数不变

在区分按键单击、双击或长按后,分别开启RS-485发送模式,将按键状态用重定向的printf函数发送出去,再将RS-485切回接收模式

/*
* @name   PWM_LED_Adjust_Brightness
* @brief  PWM灯调整亮度
* @param  None
* @retval None   
*/
static void PWM_LED_Adjust_Brightness()
{
    
    if(KEY2.KEY_Flag == TRUE)
    {
        //单击 亮度 0-20-40-60-80-100-0 循环调节
        //双击 亮度 100
        //长按 亮度 0
        if(KEY2.Click == TRUE)
        {
            switch (PWM.Duty)
            {
              case Duty_0:    PWM.Duty = Duty_20; break;
              case Duty_20:   PWM.Duty = Duty_40; break;
              case Duty_40:   PWM.Duty = Duty_60; break;
              case Duty_60:   PWM.Duty = Duty_80; break;
              case Duty_80:   PWM.Duty = Duty_100;break;
              case Duty_100:  PWM.Duty = Duty_0;  break;
              default: PWM.Duty = Duty_0; break;
            }

            UART1.RS485_Set_SendMode();           //RS-485设置为发送模式
            printf("KEY2 click detected\r\n\r\n");    //打印单击信息
            UART1.RS485_Set_RecMode();            //RS-485设置为接收模式
        }
        //检测双击
        else if(KEY2.Double_Click == TRUE)
        {
          PWM.Duty = 100;

          UART1.RS485_Set_SendMode();                   //RS-485设置为发送模式
          printf("KEY2 double click detected\r\n\r\n");     //打印双击信息
          UART1.RS485_Set_RecMode();                    //RS-485设置为接收模式
        }
        //检测长按
        else if(KEY2.Press == TRUE)
        {
          PWM.Duty = 0;

          UART1.RS485_Set_SendMode();                   //RS-485设置为发送模式
          printf("KEY2 press detected\r\n\r\n");            //打印长按信息
          UART1.RS485_Set_RecMode();                    //RS-485设置为接收模式
        }
        //设置占空比,调整亮度
        PWM_Duty_Set(PWM.Duty);
        
        //标志位清零
        KEY2.KEY_Flag     = FALSE;
        KEY2.Click        = FALSE;
        KEY2.Double_Click = FALSE;
        KEY2.Press        = FALSE;

        //备份占空比
        PWM.IAP_Duty_Backup(IAP_PWM_DUTY_ADDR,PWM.Duty);
    }
}

main.c:

主函数中调用按键2检测函数和PWM灯调整亮度函数即可

/******************************************************************************
  * @file    main.c 
  * @author  
  * @version V1.0
  * @date    2022-xx-xx
  * @Conpany 
  * @project STC15实战项目
*******************************************************************************/

/* Includes ------------------------------------------------------------------*/
#include <main.h>

/* Private define-------------------------------------------------------------*/

/* Private variables----------------------------------------------------------*/
// uint16_t Cnt = 0;	//初始化自动加1的变量
// float f = 3.14;		//浮点数

/* Public variables-----------------------------------------------------------*/

/* Private function prototypes------------------------------------------------*/

/*
* @name   main
* @brief  主函数
* @param  void	
* @retval int      
*/
int main(void)
{
	//系统初始化
	Hradware.Sys_Init();

	//串口1发送初始化信息,实际会转为RS-485信号传输
	UART1.UART_SendString("Initialization completed,system startup!\r\n");
	//系统主循环
	while(1)
	{
		//按键检测
		//KEY1.KEY_Detect();
		KEY2.KEY_Detect();
		PWM.PWM_LED_Adjust_Brightness();
		
		//RS-485发送字符串
		//UART1.UART_SendString("hello RS-485\r\n");
		//Public.Delay_ms(1000);
	}
}
/********************************************************
  End Of File
********************************************************/

485自动收发

除了用单片机控制SP3485E芯片完成接收发送之外,还有一种方式是485自动收发的,不怎么用单片机控制,这主要由硬件来实现

如下图

在这里插入图片描述

1.假如UART1_TX发送1,(其实在空闲模式下,单片机引脚一般也是输出高电平)三极管导通,此时RE,DE接地,是低电平,所以芯片处于接收模式,DI已经接地,这个低电平默认不会发送到右边,但右边A和B分别接了上下拉电阻,A为高电平,B为低电平,所以就发送了一个1即高电平出去

2.假如UART1_TX发送0,则三极管截止,上拉电阻R19接到了VCC,所以DE是高电平,此时处于发送模式,而DI是低电平,所以便将低电平发送了出去

总的来说,是巧妙地利用了A和B的上下拉电阻,UART1_TX为1,芯片处于接收模式,也能通过上下拉将高电平发送出去

相关文章:

  • 【JAVA-1】JDK、JRE安装及卸载,有手就会!
  • Python 操作MySql数据库(封装、优雅)
  • 《蓝海战略》让你竞争中获得优势
  • RequestMapping注解
  • 实战Spring Boot集成quartz任务调度框架
  • 神经网络深度学习(五)初始化
  • jvm虚拟机学习一class文件
  • 【编程题】【Scratch四级】2021.12 森林运动会
  • chapter 01 字符串的那些事
  • 给Python漫画分集标题下载工具开发Qt界面
  • 电子学会2022年6月青少年软件编程(图形化)等级考试试卷(二级)答案解析
  • 【Redis实战】生产中使用Redis的一些注意事项
  • 线性代数学习笔记8-3:二次型、合同矩阵、标准型、规范型
  • 06-vue-routers.js的基本使用,路由从定向,router-link代替a标签及样式,路由嵌套,路由动画
  • 【OpenCV】Chapter9.边缘检测与图像分割
  • css的样式优先级
  • Java 9 被无情抛弃,Java 8 直接升级到 Java 10!!
  • js递归,无限分级树形折叠菜单
  • php面试题 汇集2
  • seaborn 安装成功 + ImportError: DLL load failed: 找不到指定的模块 问题解决
  • Webpack 4 学习01(基础配置)
  • 阿里云前端周刊 - 第 26 期
  • 初识 webpack
  • 从setTimeout-setInterval看JS线程
  • 看完九篇字体系列的文章,你还觉得我是在说字体?
  • 可能是历史上最全的CC0版权可以免费商用的图片网站
  • 聊聊sentinel的DegradeSlot
  • 面试题:给你个id,去拿到name,多叉树遍历
  • 前端性能优化--懒加载和预加载
  • 如何学习JavaEE,项目又该如何做?
  • 使用Tinker来调试Laravel应用程序的数据以及使用Tinker一些总结
  • 小程序测试方案初探
  • 正则表达式小结
  • Mac 上flink的安装与启动
  • ​一文看懂数据清洗:缺失值、异常值和重复值的处理
  • #Java第九次作业--输入输出流和文件操作
  • #考研#计算机文化知识1(局域网及网络互联)
  • $分析了六十多年间100万字的政府工作报告,我看到了这样的变迁
  • (175)FPGA门控时钟技术
  • (Note)C++中的继承方式
  • (附源码)springboot“微印象”在线打印预约系统 毕业设计 061642
  • (附源码)ssm基于web技术的医务志愿者管理系统 毕业设计 100910
  • (附源码)计算机毕业设计SSM智慧停车系统
  • (附源码)计算机毕业设计高校学生选课系统
  • (七)c52学习之旅-中断
  • (实战)静默dbca安装创建数据库 --参数说明+举例
  • (转) Android中ViewStub组件使用
  • *Django中的Ajax 纯js的书写样式1
  • .NET Core 网络数据采集 -- 使用AngleSharp做html解析
  • .net FrameWork简介,数组,枚举
  • .net 重复调用webservice_Java RMI 远程调用详解,优劣势说明
  • .Net(C#)自定义WinForm控件之小结篇
  • .Net7 环境安装配置
  • .net访问oracle数据库性能问题
  • .NET牛人应该知道些什么(2):中级.NET开发人员