本文是学习野火的指南针开发板过程的学习笔记,可能有误,详细请看B站野火官方配套视频教程
(这个教程真的讲的很详细,给官方三连吧)
注意概念混淆:串口通信通常指通用的串行总线接口,它可以支持多种协议,包括RS-485。当串口设备按照RS-485协议工作时,就是在串口通信的基础上应用了RS-485的具体标准规则。可见,RS-485本质只是串口通信的一种应用场景、是一种电气标准,它规范了如何在两线或多线上实现全双工的串行通信,特别适合于长距离、多节点的数据传输。
01 RS-485通讯协议简介
与CAN类似,RS-485是一种工业控制环境中常用的通讯协议,它具有抗干扰能力强、传输距离远的特点。RS-485通讯协议由RS-232协议改进而来,协议层不变,只是改进了物理层,因而保留了串口通讯协议应用简单的特点。
RS-485与RS-232通讯协议的特性对比:
差分信号线具有很强的干扰能力,特别适合应用于电磁环境复杂的工业控制环境中,RS-485协议主要是把RS-232的信号改进成差分信号,从而大大提高了抗干扰特性
S-485与RS-232的只是物理层上的差异,它们的协议层是相同的,也是使用串口数据包的形式传输数据。而由于RS-485具有强大的组网功能,人们在基础协议之上还制定了MODBUS协议,被广泛应用在工业控制网络中。此处说的基础协议是指前面串口章节中讲解的,仅封装了基本数据包格式的协议(基于数据位),而MODBUS协议是使用基本数据包组合成通讯帧格式的高层应用协议(基于数据包或字节)。感兴趣的读者可查找MODBUS协议的相关资料了解。
由于RS-485与RS-232的协议层没有区别,进行通讯时,我们同样是使用STM32的USART外设作为通讯节点中的串口控制器,再外接一个RS-485收发器芯片把USART外设的TTL电平信号转化成RS-485的差分信号即可。
02 RS-485—通讯实验
以霸道开发板的MAX485为例
两个开发板之间的a接a,b接b;PC2为收发控制引脚
代码结构:
使用串口1打印输出到电脑
485通信使用串口2(头文件改为自己的文件名)
bsp_485.c
/**
******************************************************************************
* @file bsp_485.c
* @author fire
* @version V1.0
* @date 2015-xx-xx
* @brief 485驱动
******************************************************************************
* @attention
*
* 实验平台:野火 F103-霸道 STM32 开发板
* 论坛 :http://www.firebbs.cn
* 淘宝 :https://fire-stm32.taobao.com
*
******************************************************************************
*/
#include "./485/bsp_485.h"
#include <stdarg.h>
static void Delay(__IO u32 nCount);
/// 配置USART接收中断优先级
static void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* Configure the NVIC Preemption Priority Bits */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
/* Enable the USARTy Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = RS485_INT_IRQ;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
/*
* 函数名:RS485_Config
* 描述 :USART GPIO 配置,工作模式配置
* 输入 :无
* 输出 : 无
* 调用 :外部调用
*/
void RS485_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
/* config USART clock */
RCC_APB2PeriphClockCmd(RS485_USART_RX_GPIO_CLK|RS485_USART_TX_GPIO_CLK|RS485_RE_GPIO_CLK, ENABLE);
RCC_APB1PeriphClockCmd(RS485_USART_CLK, ENABLE);
// 将USART Tx的GPIO配置为推挽复用模式
GPIO_InitStructure.GPIO_Pin = RS485_USART_TX_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(RS485_USART_TX_GPIO_PORT, &GPIO_InitStructure);
// 将USART Rx的GPIO配置为浮空输入模式
GPIO_InitStructure.GPIO_Pin = RS485_USART_RX_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(RS485_USART_RX_GPIO_PORT, &GPIO_InitStructure);
/* 485收发控制管脚 */
GPIO_InitStructure.GPIO_Pin = RS485_RE_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(RS485_RE_GPIO_PORT, &GPIO_InitStructure);
/* USART 模式配置*/
USART_InitStructure.USART_BaudRate = RS485_USART_BAUDRATE;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No ;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(RS485_USART, &USART_InitStructure);
/*使能USART*/
USART_Cmd(RS485_USART, ENABLE);
/*配置中断优先级*/
NVIC_Configuration();
/* 使能串口接收中断 */
USART_ITConfig(RS485_USART, USART_IT_RXNE, ENABLE);
/*控制485芯片进入接收模式*/
GPIO_ResetBits(RS485_RE_GPIO_PORT,RS485_RE_PIN);
}
/***************** 发送一个字符 **********************/
//使用单字节数据发送前要使能发送引脚,发送后要使能接收引脚。
void RS485_SendByte( uint8_t ch )
{
/* 发送一个字节数据到USART1 */
USART_SendData(RS485_USART,ch);
/* 等待发送完毕 */
while (USART_GetFlagStatus(RS485_USART, USART_FLAG_TXE) == RESET);
}
/***************** 发送指定长度的字符串 **********************/
void RS485_SendStr_length( uint8_t *str,uint32_t strlen )
{
unsigned int k=0;
RS485_TX_EN() ;// 使能发送数据
do
{
RS485_SendByte( *(str + k) );
k++;
} while(k < strlen);
/*加短暂延时,保证485发送数据完毕*/
Delay(0xFFF);
RS485_RX_EN() ;// 使能接收数据
}
/***************** 发送字符串 **********************/
void RS485_SendString( uint8_t *str)
{
unsigned int k=0;
RS485_TX_EN() ;// 使能发送数据
do
{
RS485_SendByte( *(str + k) );
k++;
} while(*(str + k)!='\0');
/*加短暂延时,保证485发送数据完毕*/
Delay(0xFFF);
RS485_RX_EN() ;// 使能接收数据
}
//中断缓存串口数据
#define UART_BUFF_SIZE 1024
volatile uint16_t uart_p = 0;
uint8_t uart_buff[UART_BUFF_SIZE];
void bsp_RS485_IRQHandler(void)
{
if(uart_p<UART_BUFF_SIZE)
{
if(USART_GetITStatus(RS485_USART, USART_IT_RXNE) != RESET)
{
uart_buff[uart_p] = USART_ReceiveData(RS485_USART);
uart_p++;
USART_ClearITPendingBit(RS485_USART, USART_IT_RXNE);
}
}
else
{
USART_ClearITPendingBit(RS485_USART, USART_IT_RXNE);
// clean_rebuff();
}
}
//获取接收到的数据和长度
char *get_rebuff(uint16_t *len)
{
*len = uart_p;
return (char *)&uart_buff;
}
//清空缓冲区
void clean_rebuff(void)
{
uint16_t i=UART_BUFF_SIZE+1;
uart_p = 0;
while(i)
uart_buff[--i]=0;
}
static void Delay(__IO uint32_t nCount) //简单的延时函数
{
for(; nCount != 0; nCount--);
}
bsp_485.h
使用485通信(头文件改为自己的文件名)
#ifndef _RS485_H
#define _RS485_H
#include "stm32f10x.h"
#include <stdio.h>
/*USART号、时钟、波特率*/
#define RS485_USART USART2
#define RS485_USART_CLK RCC_APB1Periph_USART2
#define RS485_USART_BAUDRATE 115200
/*RX引脚*/
#define RS485_USART_RX_GPIO_PORT GPIOA
#define RS485_USART_RX_GPIO_CLK RCC_APB2Periph_GPIOA
#define RS485_USART_RX_PIN GPIO_Pin_3
/*TX引脚*/
#define RS485_USART_TX_GPIO_PORT GPIOA
#define RS485_USART_TX_GPIO_CLK RCC_APB2Periph_GPIOA
#define RS485_USART_TX_PIN GPIO_Pin_2
/*485收发控制引脚*/
#define RS485_RE_GPIO_PORT GPIOC
#define RS485_RE_GPIO_CLK RCC_APB2Periph_GPIOC
#define RS485_RE_PIN GPIO_Pin_2
/*中断相关*/
#define RS485_INT_IRQ USART2_IRQn
#define RS485_IRQHandler USART2_IRQHandler
/// 不精确的延时
static void RS485_delay(__IO u32 nCount)
{
for(; nCount != 0; nCount--);
}
/*控制收发引脚*/
//进入接收模式,必须要有延时等待485处理完数据
#define RS485_RX_EN() RS485_delay(1000); GPIO_ResetBits(RS485_RE_GPIO_PORT,RS485_RE_PIN); RS485_delay(1000);
//进入发送模式,必须要有延时等待485处理完数据
#define RS485_TX_EN() RS485_delay(1000); GPIO_SetBits(RS485_RE_GPIO_PORT,RS485_RE_PIN); RS485_delay(1000);
/*debug*/
#define RS485_DEBUG_ON 1
#define RS485_DEBUG_ARRAY_ON 1
#define RS485_DEBUG_FUNC_ON 1
// Log define
#define RS485_INFO(fmt,arg...) printf("<<-RS485-INFO->> "fmt"\n",##arg)
#define RS485_ERROR(fmt,arg...) printf("<<-RS485-ERROR->> "fmt"\n",##arg)
#define RS485_DEBUG(fmt,arg...) do{\
if(RS485_DEBUG_ON)\
printf("<<-RS485-DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\
}while(0)
#define RS485_DEBUG_ARRAY(array, num) do{\
int32_t i;\
uint8_t* a = array;\
if(RS485_DEBUG_ARRAY_ON)\
{\
printf("<<-RS485-DEBUG-ARRAY->>\n");\
for (i = 0; i < (num); i++)\
{\
printf("%02x ", (a)[i]);\
if ((i + 1 ) %10 == 0)\
{\
printf("\n");\
}\
}\
printf("\n");\
}\
}while(0)
#define RS485_DEBUG_FUNC() do{\
if(RS485_DEBUG_FUNC_ON)\
printf("<<-RS485-FUNC->> Func:%s@Line:%d\n",__func__,__LINE__);\
}while(0)
void RS485_Config(void);
void RS485_SendByte( uint8_t ch );
void RS485_SendStr_length( uint8_t *str,uint32_t strlen );
void RS485_SendString( uint8_t *str);
void bsp_RS485_IRQHandler(void);
char *get_rebuff(uint16_t *len);
void clean_rebuff(void);
#endif /* _RS485_H */
其中
/*控制收发引脚*/ //进入接收模式,必须要有延时等待485处理完数据 #define RS485_RX_EN() RS485_delay(1000); GPIO_ResetBits(RS485_RE_GPIO_PORT,RS485_RE_PIN); RS485_delay(1000); //进入发送模式,必须要有延时等待485处理完数据 #define RS485_TX_EN() RS485_delay(1000); GPIO_SetBits(RS485_RE_GPIO_PORT,RS485_RE_PIN); RS485_delay(1000);
注意因为485的反应没有STM32快,所以我们采取一定延时,并且定义在宏中
main.c和中断服务函数
void RS485_IRQHandler(void)
{
bsp_RS485_IRQHandler();
}
/**
******************************************************************************
* @file main.c
* @author fire
* @version V1.0
* @date 2016-xx-xx
* @brief 485通讯例程
******************************************************************************
* @attention
*
* 实验平台:野火 F103-霸道 STM32 开发板
* 论坛 :http://www.firebbs.cn
* 淘宝 :https://fire-stm32.taobao.com
*
******************************************************************************
*/
#include "stm32f10x.h"
#include "./usart/bsp_debug_usart.h"
#include "./led/bsp_led.h"
#include "./key/bsp_key.h"
#include "./485/bsp_485.h"
static void Delay(__IO u32 nCount);
/**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(void)
{
char *pbuf;
uint16_t len;
LED_GPIO_Config();
/*初始化USART1*/
Debug_USART_Config();
/*初始化485使用的串口,使用中断模式接收*/
RS485_Config();
LED_BLUE;
Key_GPIO_Config();
printf("\r\n 欢迎使用野火F103-霸道 STM32 开发板。\r\n");
printf("\r\n 野火F103-霸道 485通讯实验例程\r\n");
printf("\r\n 实验步骤:\r\n");
printf("\r\n 1.使用导线连接好两个485通讯设备\r\n");
printf("\r\n 2.使用跳线帽连接好:霸道V1底板:5V <---> C/4-5V 485-D <-----> PA2 485-R <-----> PA3 \r\n");
printf("\r\n 霸道V2底板:3V3 <---> CAN/485_3V3 485TX <-----> PA2 485RX <-----> PA3 \r\n");
printf("\r\n 3.若使用两个野火开发板进行实验,给两个开发板都下载本程序即可。\r\n");
printf("\r\n 4.准备好后,按下其中一个开发板的KEY1键,会使用485向外发送0-255的数字 \r\n");
printf("\r\n 5.若开发板的485接收到256个字节数据,会把数据以16进制形式打印出来。 \r\n");
while(1)
{
/*按一次按键发送一次数据*/
if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON)
{
uint16_t i;
LED_BLUE;
RS485_TX_EN();
for(i=0;i<=0xff;i++)
{
RS485_SendByte(i); //发送数据
}
/*加短暂延时,保证485发送数据完毕*/
Delay(0xFFF);
RS485_RX_EN();
LED_GREEN;
printf("\r\n发送数据成功!\r\n"); //使用调试串口打印调试信息到终端
}
else
{
LED_BLUE;
pbuf = get_rebuff(&len);
if(len>=256)
{
LED_GREEN;
printf("\r\n接收到长度为%d的数据\r\n",len);
RS485_DEBUG_ARRAY((uint8_t*)pbuf,len);
clean_rebuff();
}
}
}
}
static void Delay(__IO uint32_t nCount) //简单的延时函数
{
for(; nCount != 0; nCount--);
}
/*********************************************END OF FILE**********************/