1.前言-单片机的通讯
在单片机通讯方式多种多样的今天,基本可以划分为两类,即同步和异步通信。单片机要正常交流(即交换数据和读写命令)离不开通讯,单片机之间或者单片机与及外设之间的通讯都离不开这两类通讯。
通讯方式的分类
同步和异步通信怎么区别?
带时钟同步信号传输的是同步传输,不带时钟同步信号的是异步传输(此时要求通讯双方同波特率)。
下面我将通过基于stm32f103芯片以及MDK5软件进行开发485通讯实验(其实485通讯就是利用uart串口实现的),需要准备:
一台装着MDK5软件的电脑
ST-LInk烧录器,STM32正点原子精英开发板2套(包含TFTLCD显示屏)
两根杜邦线
2.485通讯简介
要开展485通讯实验之前,485得对自己进行一次自我介绍。485通讯本质上是通过串口经过485芯片改变电压与及阻抗,内在的信息没有改变,之后通过电压电流等信号传给另一个单片机的485芯片,该芯片接至该单片进的串口。
485(一般称作RS485/EIA-485)是隶属于OSI模型物理层的电气特性规定为2线,半双工,多点通信的标准。它的电气特性和RS-232大不一样。用缆线两端的电压差值来表示传递信号。RS485仅仅规定了接受端和发送端的电气特性。它没有规定或推荐任何数据协议。
RS485的特点包括:
1) 接口电平低,不易损坏芯片。RS485的电气特性:逻辑“1”以两线间的电压差为+(2~6)V表示;逻辑“0”以两线间的电压差为-(2~6)V表示。接口信号电平比RS232降低了,不易损坏接口电路的芯片,且该电平与TTL电平兼容,可方便与TTL 电路连接。
2) 传输速率高。10米时,RS485的数据最高传输速率可达35Mbps,在1200m时,传输速度可达100Kbps。
3) 抗干扰能力强。RS485接口是采用平衡驱动器和差分接收器的组合,抗共模干扰能力增强,即抗噪声干扰性好。
4) 传输距离远,支持节点多。RS485总线最长可以传输1200m以上(速率≤100Kbps)
一般最大支持32个节点,如果使用特制的485芯片,可以达到128个或者256个节点,最大的可以支持到400个节点。
RS485推荐使用在点对点网络中,线型,总线型,不能是星型,环型网络。理想情况下RS485需要2个匹配电阻,其阻值要求等于传输电缆的特性阻抗(一般为120Ω)。没有特性阻抗的话,当所有的设备都静止或者没有能量的时候就会产生噪声,而且线移需要双端的电压差。没有终接电阻的话,会使得较快速的发送端产生多个数据信号的边缘,导致数据传输出错。485推荐的连接方式如图29.1.1所示:
在上面的连接中,如果需要添加匹配电阻,我们一般在总线的起止端加入,也就是主机和设备4上面各加一个120Ω的匹配电阻。
由于RS485具有传输距离远、传输速度快、支持节点多和抗干扰能力更强等特点,所以RS485有很广泛的应用。
精英STM32开发板采用SP3485作为收发器,该芯片支持3.3V供电,最大传输速度可达10Mbps,支持多达32个节点,并且有输出短路保护。该芯片的框图如图29.1.2所示:
图中A、B总线接口,用于连接485总线。RO是接收输出端,DI是发送数据收入端,RE是接收使能信号(低电平有效),DE是发送使能信号(高电平有效)。
本章,我们通过该芯片连接STM32的串口2,实现两个开发板之间的485通信。本章将实
现这样的功能:通过连接两个精英STM32开发板的RS485接口,然后由KEY0控制发送,当按下一个开发板的KEY0的时候,就发送5个数据给另外一个开发板,并在两个开发板上分别显示发送的值和接收到的值。
本章,我们只需要配置好串口2,就可以实现正常的485通信了,串口2的配置和串口1基本类似,只是串口的时钟来自APB1,最大频率为36Mhz。
3.软件代码的实现
在此,我将对几个重要的函数展开详细说明,我觉得外设模块化进行开发跟函数模块化开发一样重要,一个函数最多只放几十行代码,同时前面要有函数说明。这样,你写的代码会方便以后的修改,同时也可以让别人快速知道你这个软件实现的是什么功能。
void RS485_Init(u32 bound) ; void RS485_Receive_Data(u8 *ReceiveBuf,u8 *len); void RS485_Send_Data(u8 *SendBuf ,u8 len); void RS485_Init(u32 bound) { GPIO_InitTypeDef GPIO_InitType; USART_InitTypeDef USART_InitType; NVIC_InitTypeDef NVIC_InitType; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOD,ENABLE); //使能GPIOA和GPIOD的时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE); //使能USART2的时钟 GPIO_InitType.GPIO_Pin=GPIO_Pin_7; //接收或者发送模式控制引脚PD7(初始化之后才能用) GPIO_InitType.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出 GPIO_InitType.GPIO_Speed=GPIO_Speed_50MHz; //这个引脚是485芯片中的发送接收控制引脚 GPIO_Init(GPIOD,&GPIO_InitType); //当PDout(7)输出的是高电平‘1’时是发送,为‘0’时是接收 GPIO_InitType.GPIO_Pin=GPIO_Pin_2; //USART2的TX引脚是PA2 GPIO_InitType.GPIO_Mode=GPIO_Mode_AF_PP; GPIO_InitType.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(GPIOA,&GPIO_InitType); GPIO_InitType.GPIO_Pin=GPIO_Pin_3; //USART2的RX引脚是PA3 GPIO_InitType.GPIO_Mode=GPIO_Mode_IN_FLOATING; GPIO_InitType.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(GPIOA,&GPIO_InitType); #ifdef EN_RS485_RX USART_InitType.USART_BaudRate=bound; //串口2的初始化 USART_InitType.USART_HardwareFlowControl=USART_HardwareFlowControl_None; USART_InitType.USART_Mode=USART_Mode_Rx|USART_Mode_Tx; USART_InitType.USART_Parity=USART_Parity_No; //其中重要的是USART_Mode要既能接收又能发送 USART_InitType.USART_StopBits=USART_StopBits_1; //USART2的波特率都设置为9600 USART_InitType.USART_WordLength=USART_WordLength_8b; USART_Init(USART2,&USART_InitType); NVIC_InitType.NVIC_IRQChannel=USART2_IRQn; //USART2中断初始化 NVIC_InitType.NVIC_IRQChannelCmd=ENABLE; //其实这个中断主要是对发送的数据及时处理,使接收时便捷高效 NVIC_InitType.NVIC_IRQChannelPreemptionPriority=3; NVIC_InitType.NVIC_IRQChannelSubPriority=3; NVIC_Init(&NVIC_InitType); USART_ITConfig(USART2,USART_IT_RXNE,ENABLE); //要用到USART2中断除了初始化之外当然要对中断进行配置以及使能 USART_Cmd(USART2,ENABLE); //明确发生什么时会进入串口中断 #endif RX485_TX_EN=0; //默认接收模式 } 这里的串口中断函数,发送的数据被接收数据寄存器接收到,就会触发中断,进行处理 #ifdef EN_RS485_RX u8 RS485_RX_BUF[64]; u8 RS485_RX_CNT=0; void USART2_IRQHandler(void) //发送完数据就使能接收中断,使串口可以接收数据到缓冲区(数组RS485_RX_BUF)中 { u8 Res; if(USART_GetITStatus(USART2,USART_IT_RXNE)!=RESET) { Res=USART_ReceiveData(USART2); if(RS485_RX_CNT<64) //一次接收的数据最多为64,超过之后将不会对发送过来的数据进行缓存 { RS485_RX_BUF[RS485_RX_CNT]=Res; RS485_RX_CNT++; } } } #endif void RS485_Send_Data(u8 *SendBuf ,u8 len) //发送处理函数 { u8 i; RX485_TX_EN=1; for(i=0;i<len;i++) //把长度为len的数组SendBuf发送出去 { while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET); //等待发送移位数据寄存器的数据非空时(即发送结束)跳出来 USART_SendData(USART2,SendBuf[i]); //发送数据 } while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET); RX485_TX_EN=0; //默认接收模式 RS485_RX_CNT=0; } void RS485_Receive_Data(u8 *ReceiveBuf,u8 *len) //接收处理函数 { u8 rxlen,i=0; rxlen=RS485_RX_CNT; delay_ms(10); if((rxlen==RS485_RX_CNT)&&rxlen) //把中断函数处理之后的缓存数据取出来 { for(i=0;i<rxlen;i++) { ReceiveBuf[i]=RS485_RX_BUF[i]; //传递接收到的数据以及长度回去 } *len=RS485_RX_CNT; RS485_RX_CNT=0; //对接收的个数进行清零,方便下次接收 } }