学习板:STM32F103ZET6
参考:
STM32F103(二十三)通用同步异步收发器(USART)
485是一种2线、半双工的通信,传输距离可达到1Km以上,通过A线、B线两线的电压差来传递信号。
485芯片接口电平很低,可以兼容TTL电路,所以可以直接接入到TTL电路。发送信号“1”时两线电压差为+(2~6)V,发送信号“0”时两线电压差为 -(2 ~6)V。
因为需要根据A线、B线两端电压差来确定逻辑“1”和“0”,所以收发双方都需要接一个匹配电阻,一般为120Ω。
包括我近期项目选用的一款485芯片:
其中:
RO:接收数据输出端,485通信过程中,终端信号接收引脚。
RE:接收数据使能,低电平有效,终端的485芯片RE引脚置0,进入接收模式。
DI:发送数据输入端,485通信过程中,始端信号发送引脚。
DE:发送数据使能,高电平有效,始端的485芯片DE引脚置1,进入发送模式。
A、B:信号传输线,准确的讲,只是起到导电作用。
VCC、GND:供电。
485通信为半双工通信,即同一时间只能接收或发送数据。而接收数据使能RE低电平有效,发送数据使能DE高电平有效,所以这两个引脚可以共接一个IO口。有以下情况:
①当IO口输出低电平时,接收数据使能RE有效,进入接收模式
②当IO口输出高电平时,发送数据使能DE有效,进入发送模式。
以SP485芯片为例,总结:
①发送模式时,DI输出“1”或“0”时传输线A、B上的电平情况
②接收模式时,传输线上不同电平时,RO引脚输出电平情况
上图是我对SP485的测试结果,主设备用的F1板子,从设备为F4板子。再详述一下:
①当主设备设置为发送模式时,DI引脚发送“1”,则传输线上:A线高电平、B线低电平…同时从设备为接收模式,当A线为高电平、B线为低电平时,RO引脚输出高电平。
②当主设备设置为发送模式时,DI引脚发送“0”,则传输线上:A线低电平、B线高电平…同时从设备为接收模式,当A线为低电平、B线为高电平时,RO引脚输出低电平。
所以省略中间过程,我们知道:主设备发送“1”,从设备收到“1”,主设备发送“0”,从设备收到“0”。而485芯片没有时钟引脚,所以我们想到用异步串行传输USART。主设备USART一次输出8位的数,从设备一次接收8位的数,只需保证双方串口的波特率、停止位、校验位等一致即可
我用的板子上使用的是串口2,USART_TX通过跳线帽与485的DI引脚连接;USART_RX通过跳线帽与485的RO引脚连接;PD7控制RE和DR,之前也总结到过,这里当PD7=0时为接收模式,当PD7=1时,为发送模式。
所以除了硬件连接,其他部分都是USART的代码
通过第一部分总结,我们知道485通信可以通过双方的USART来输出和接收信号,所以USART收发函数就是485通信的函数,而485模块只需要设置为接收或发送模式,双方设置好后,其实就是两块板子之间的USART通信。
void My485_Send_One_Data(u8 data)
{
PDout(7)=1;//发送数据使能
while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);//等待上一次数据发送完成
USART_SendData(USART2,data);
}
上述代码中,首先PD7输出高电平,将485设置为发送数据模式。然后等待上一次数据发送完毕后,开启本次数据传输。代码比较简单。
void My485_Send_Len_data(u8 data[],u16 len)
{
u16 t=0;
for(t=0;t<len;t++)
{
My485_Send_One_Data(data[t]);
delay_ms(10);//最好延迟,防止对方反应不过来
}
}
这个函数也比较简单,data[]为传递的数据存储数组,len为需要传递的数据个数。其中My485_Send_One_Data(data[t]);
函数是上面写的那个函数。
发送数据的函数一般都比较好写,麻烦的是接收数据的函数,因为接收数据函数要用到中断,所以还需要配置中断。直接附已经调试好的代码,然后再讲思想:
void My485_Receive_data(u8 len)
{
NVIC_InitTypeDef NVIC_InitStructure;
PDout(7)=0;//接收数据使能
NUM=0;
RS485_Receive_Num=len;
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ; //抢占优先级 3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级 3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);
}
extern u8 buffer[200];//接收的数据存储在这个数组
extern u8 RS485_Receive_Num;//储存需要接收的数据个数
extern u8 NUM; //统计现在接收到几个数据
void USART2_IRQHandler(void)
{
u8 temp;
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) //接收到数据
{
temp=USART_ReceiveData(USART2);
if(RS485_Receive_Num>NUM)
{
buffer[NUM]=temp;
NUM++;
}
if(RS485_Receive_Num<=NUM)
USART_ITConfig(USART2, USART_IT_RXNE, DISABLE);
}
}
在void My485_Receive_data(u8 len)
函数中,将485模块设置为接收模式;将NUM这个全局变量清零,这个变量是用来统计目前已经接收到多少个数据。然后就是NVIC中断优先级设置和使能中断
这个接收函数配置好中断后,USART2就开始检测是否有数据输入了,只要有数据输入,就产生一次中断进入中断服务函数,然后接收到数据、存储数据、清除中断、等待下一次数据到来。当接收到我们需要接收到的数据个数时,再失能中断
数据存储的数组我采用的是全局数组,因为数据处理都是在中断服务函数中处理的,数组的地址没法传递。
在void My485_Receive_data(u8 len)
函数中可以再加一段程序,将储存数据的buffer[]数组清零,不过我觉得没必要,只需要保证通信不会出现异常,规定好接收数据的多少就可以了。
注意理解这个思想就好了,比如后面的例题,我把中断服务函数的以下代码放在了主函数中:
if(RS485_Receive_Num<=NUM)
USART_ITConfig(USART2, USART_IT_RXNE, DISABLE);
F1板子发送一组1~100的100个数,通过双方485通信,F4接收到数据并通过串口打印在串口监视器上。
F1板子发送数据,所以只用到上面写的第二个函数,需要配置USART2和485模块。
F4板子接收数据用到上面写的第三个函数,需要配置USART2和485模块。同时需要串口打印,所以需要配置USART1(我的两块板子都是:485与USART2连接,USART1通过RS232与USB连接)
485.h代码:
#ifndef _485_
#define _485_
#include "sys.h"
void My485_Init(u32 bound);
void My485_Send_One_Data(u8 data);
void My485_Send_Len_data(u8 *,u16 );
void My485_Receive_data(u8 len);//参数:读取数据个数
#endif
485.c代码:
#include "485.h"
#include "stm32f10x.h"
void My485_Init(u32 bound)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //PA2 TX
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3;
GPIO_Init(GPIOA, &GPIO_InitStructure); //PA3 RX
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_7;
GPIO_Init(GPIOD, &GPIO_InitStructure); //PD7 使能发送、接收
USART_InitStructure.USART_BaudRate=bound;
USART_InitStructure.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;
USART_InitStructure.USART_Parity=USART_Parity_No;
USART_InitStructure.USART_StopBits=USART_StopBits_1;
USART_InitStructure.USART_WordLength=USART_WordLength_8b;
USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
USART_Init(USART2,&USART_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ; //抢占优先级 3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级 3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART2,ENABLE);
}
extern u8 buffer[200];//接收的数据存储在这个数组
extern u8 RS485_Receive_Num;//储存需要接收的数据个数
extern u8 NUM; //统计现在接收到几个数据
void USART2_IRQHandler(void)
{
u8 temp;
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) //接收到数据
{
temp=USART_ReceiveData(USART2);
if(RS485_Receive_Num>NUM)
{
buffer[NUM]=temp;
NUM++;
}
}
}
void My485_Send_One_Data(u8 data)
{
PDout(7)=1;//发送数据使能
while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);//等待上一次数据发送完成
USART_SendData(USART2,data);
}
void My485_Send_Len_data(u8 data[],u16 len)
{
u16 t=0;
for(t=0;t<len;t++)
My485_Send_One_Data(data[t]);
}
void My485_Receive_data(u8 len)
{
NVIC_InitTypeDef NVIC_InitStructure;
PDout(7)=0;//接收数据使能
NUM=0;
RS485_Receive_Num=len;
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ; //抢占优先级 3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级 3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);
}
主函数代码:
#include "stm32f10x.h"
#include "485.h"
#include "delay.h"
u8 buffer[200];//接收的数据存储在这个数组
u8 RS485_Receive_Num=0;//储存需要接收的数据个数
u8 NUM=0; //统计现在接收到几个数据
u8 arr[200]; //发送的数据储存在这个数组
int main(void)
{
u16 i;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
delay_init();
My485_Init(115200);
for(i=0;i<100;i++)
arr[i]=i+1;//赋初值
delay_ms(1500);
My485_Send_Len_data(arr,100);
while(1)
{
}
}
主函数写的是按下复位后,隔1.5后发送100个数据
USART1头文件代码(为了串口打印):
#ifndef _USART_
#define _USART_
#include "stm32f4xx.h"
#include "stm32f4xx_conf.h"
void Myusart1_Init(u32);
#endif
USART的.c文件代码(为了串口打印):
#include "myusart.h"
#include "stm32f4xx.h"
#include "sys.h"
#include "string.h"
void Myusart1_Init(u32 baud)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度100MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA9,PA10
USART_InitStructure.USART_BaudRate=baud;
USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;
USART_InitStructure.USART_Parity=USART_Parity_No;
USART_InitStructure.USART_StopBits=USART_StopBits_1;
USART_InitStructure.USART_WordLength=USART_WordLength_8b;
USART_Init(USART1,&USART_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2 ; //抢占优先级 3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //子优先级 3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART1, ENABLE);
}
485.h代码:
#ifndef _485_
#define _485_
#include "sys.h"
void My485_Init(u32 bound);
void My485_Send_One_Data(u8 data);
void My485_Send_Len_data(u8 *,u16 );
void My485_Receive_data(u8 len);//参数:读取数据个数
#endif
485.c代码:
#include "485.h"
#include "stm32f4xx.h"
void My485_Init(u32 bound)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOG,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_USART2);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_USART2);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度100MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_8;
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;
GPIO_Init(GPIOG,&GPIO_InitStructure); //PG8 发送接收使能
USART_InitStructure.USART_BaudRate=bound;
USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;
USART_InitStructure.USART_Parity=USART_Parity_No;
USART_InitStructure.USART_StopBits=USART_StopBits_1;
USART_InitStructure.USART_WordLength=USART_WordLength_8b;
USART_Init(USART2,&USART_InitStructure);
USART_Cmd(USART2, ENABLE);
}
extern u8 buffer[200];//接收的数据存储在这个数组
extern u8 RS485_Receive_Num;//储存需要接收的数据个数
extern u8 NUM; //统计现在接收到几个数据
void USART2_IRQHandler(void)
{
u8 temp;
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) //接收到数据
{
temp=USART_ReceiveData(USART2);
if(RS485_Receive_Num>NUM)
{
buffer[NUM]=temp;
NUM++;
}
}
}
void My485_Send_One_Data(u8 data)
{
PGout(8)=1;//发送数据使能
while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);//等待上一次数据发送完成
USART_SendData(USART2,data);
}
void My485_Send_Len_data(u8 data[],u16 len)
{
u16 t=0;
for(t=0;t<len;t++)
My485_Send_One_Data(data[t]);
}
void My485_Receive_data(u8 len)
{
NVIC_InitTypeDef NVIC_InitStructure;
PGout(8)=0;//接收数据使能
NUM=0;
RS485_Receive_Num=len;
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ; //抢占优先级 3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级 3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);
}
main.c代码:
#include "stm32f4xx.h"
#include "myusart.h"
#include "delay.h"
#include "485.h"
u8 buffer[200];//接收的数据存储在这个数组
u8 RS485_Receive_Num=0;//储存需要接收的数据个数
u8 NUM=0; //统计现在接收到几个数据
u8 arr[200]; //发送的数据储存在这个数组
int main(void)
{
u16 i;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
Myusart1_Init(115200);
My485_Init(115200);
delay_init(168);
My485_Receive_data(100);//接收100个数据
while(1)
{
if(RS485_Receive_Num<=NUM)
{
NUM=0;
USART_ITConfig(USART2, USART_IT_RXNE, DISABLE);
for(i=0;i<RS485_Receive_Num;i++)
{
USART_SendData(USART1,buffer[i]);//通过串口监视器显示
delay_ms(10);//一定要延时,保证每个数据能准确发送到串口
}
}
// USART_SendData(USART1,buffer[45]);//通过串口监视器显示
}
}
①F1板子485端子A线与F4板子485端子A线相连,B线与B线相连。
②保证F1板子与F4板的USART2与485模块相连。
③保证F4板子USART1与USB输出相连。
USB连接F4板子并打开串口监视器。同时复位F1、F4板子,察看数据接收情况:
485通信,主要还是用到上一篇博客总结的USART通信。双方485模块只是提供了长途导线连接接口。明白了USART通信和485模块的作用,485通信应该很好理解的。
当然,对于大型项目,32的CPU无法满足计算要求,可以利用485通信在上位机中处理数据:
感兴趣的朋友可以去尝试一下,上位机代码是VS用C#编的。