STM32 407 RS485通信实现数据收发【我的创作纪念日】

1. 前言

本例中的485驱动,基于标准库编写,不是HAL库,请大家注意。
最近搞嵌入式程序,踩了不少坑,这里统一记录一下。

2. 收获

1.串口通信,数据是一个字节一个字节的发送,对方收到的数据是放在了寄存器中(这个是由硬件控制的),软件在接收数据时,中断处理函数中再把寄存器中的数据取出来,一般是放在一个缓冲区里。因为写入的数据量 和写入时间不确定性 ,会导致软件在读取串口的时候读取的数据不完整 或者一次读取"一条半"这样的问题…
这时候软件上就要解决一个问题,如何保证收到的数据是完整的
一般有下面几种方式:
1. 发送的数据长度是知道的,那么当接收到相应长度的数据时,认为接收完成了。
2. 数据本身有格式,比如数据头+数据+校验信息。那么每当收到下一个数据头时,认为上一个数据收完了。
3. 接收中断处理函数中,添加定时器,当超过一段时间,还没收到数据,认为一段数据收完了。具体设置多长时间,需要根据通信双方的需要,来进行设置。(本例中,使用的此方法)

数据发送到数据接收的切换,中间要加时延。今天测试程序时,发现一个问题:RS485串口发送完数据后,RS485串口接收,中间要加一定时延,才能保证接收的数据是完整的。但有一个问题:为啥要加这个时延?怎么量化计算?

3.程序

3.1 USART GPIO配置,工作模式配置

/*
 * 函数名:_485_Config
 * 描述:USART GPIO配置,工作模式配置
 */
void _485_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;

	/* config USART clock */
	RCC_AHB1PeriphClockCmd(_485_USART_RX_GPIO_CLK|_485_USART_TX_GPIO_CLK|_485_RE_GPIO_CLK, ENABLE);
	RCC_APB2PeriphClockCmd(_485_USART_CLK, ENABLE); // USART1,USART6属于APB2    RCC_APB2PeriphClockCmd, USART2,USART3属于APB1  RCC_APB1PeriphClockCmd

//	RCC_APB2PeriphClockCmd(_485_USART_CLK,ENABLE);
//	GPIO_PinRemapConfig(_485_USART_CLK,ENABLE);
	
	  /* Connect PXx to USARTx_Tx*/
  GPIO_PinAFConfig(_485_USART_RX_GPIO_PORT,_485_USART_RX_SOURCE, _485_USART_RX_AF);

  /* Connect PXx to USARTx_Rx*/
  GPIO_PinAFConfig(_485_USART_TX_GPIO_PORT,_485_USART_TX_SOURCE,_485_USART_TX_AF);

	
	/* USART GPIO config */
   /* Configure USART Tx as alternate function push-pull */
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;

  GPIO_InitStructure.GPIO_Pin = _485_USART_TX_PIN  ;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(_485_USART_TX_GPIO_PORT, &GPIO_InitStructure);

  /* Configure USART Rx as alternate function  */
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; 
	GPIO_InitStructure.GPIO_Pin = _485_USART_RX_PIN;
  GPIO_Init(_485_USART_RX_GPIO_PORT, &GPIO_InitStructure);

  
  /* 485收发控制管教 */
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  GPIO_InitStructure.GPIO_Pin = _485_RE_PIN  ;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(_485_RE_GPIO_PORT, &GPIO_InitStructure);
	  
	/* USART mode config */
	USART_InitStructure.USART_BaudRate = _485_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(_485_USART, &USART_InitStructure); 
  USART_Cmd(_485_USART, ENABLE);
	
	NVIC_Configuration();
	/* 使能串口接收中断 */
	USART_ITConfig(_485_USART, USART_IT_RXNE, ENABLE);
	
	GPIO_ResetBits(_485_RE_GPIO_PORT,_485_RE_PIN); //默认进入接收模式
}

3.2 GPIO宏定义

// 串口1 定义:PA9 Tx,PA10 Rx,PA8 DE
#define _485_USART                             USART1
#define _485_USART_CLK                         RCC_APB2Periph_USART1
#define _485_USART_BAUDRATE                    115200

#define _485_USART_RX_GPIO_PORT                GPIOA
#define _485_USART_RX_GPIO_CLK                 RCC_AHB1Periph_GPIOA
#define _485_USART_RX_PIN                      GPIO_Pin_10
#define _485_USART_RX_AF                       GPIO_AF_USART1
#define _485_USART_RX_SOURCE                   GPIO_PinSource10

#define _485_USART_TX_GPIO_PORT                GPIOA
#define _485_USART_TX_GPIO_CLK                 RCC_AHB1Periph_GPIOA
#define _485_USART_TX_PIN                      GPIO_Pin_9
#define _485_USART_TX_AF                       GPIO_AF_USART1
#define _485_USART_TX_SOURCE                   GPIO_PinSource9


#define _485_RE_GPIO_PORT												GPIOA
#define _485_RE_GPIO_CLK												RCC_AHB1Periph_GPIOA
#define _485_RE_PIN															GPIO_Pin_8

#define _485_INT_IRQ                 						USART1_IRQn
#define _485_IRQHandler                         USART1_IRQHandler

每一个外设,都有对应的地址。通过对这个地址操作,操作这些外设。
在这里插入图片描述

3.3 接收中断处理函数

定义了一个缓冲区来专门存放接收到的数据。

//串口中断处理函数---接收
#define UART_BUFF_SIZE      1024
volatile    uint16_t uart_p = 0;
uint8_t     uart_buff[UART_BUFF_SIZE];
extern uint8_t g_recvMotorData;

void bsp_485_IRQHandler(void)
{
	//	printf("come here1");
			//g_recvMotorData = 1;
		uint8_t clear;	
	//Delay(115200/8); 
    if(uart_p<UART_BUFF_SIZE)
    {
        while(USART_GetITStatus(_485_USART, USART_IT_RXNE) != RESET)
        {
            uart_buff[uart_p] = USART_ReceiveData(_485_USART);
            uart_p++;
            TIM_Cmd(BASIC_TIM, ENABLE);	
			USART_ClearITPendingBit(_485_USART, USART_IT_RXNE);
        }
    }
		else
		{ //接收的数据长度超过UART_BUFF_SIZE 时,这次数据全部丢弃
			USART_ClearITPendingBit(_485_USART, USART_IT_RXNE);
			//clean_rebuff();       
		}
		//printf("uart_p = %u",uart_p);
	if (uart_p == 1) // 用来判断是否收到一帧数据 https://blog.csdn.net/ASKLW/article/details/79246786  uart_p == 1  USART_GetITStatus(_485_USART, USART_IT_IDLE) != RESET
	{
//  clear = _485_USART->SR; //先读SR,再读DR才能完成idle中断的清零,否则一直进入中断
//  clear = _485_USART->DR;	

	uint16_t len = 0;
//			char str[1024] ={0};
//		  sprintf(str, "%d", ii);  
//			Usart_SendByte(USART6, len);
			
//Usart_SendString(USART6, get_rebuff(&len));	
//			printf("come here");
		g_recvMotorData = 1;
	}
}

这个中断处理函数,是与启动文件中的中断号对应的。当硬件感知到来了一个中断时,调用对应的中断处理函数。
在这里插入图片描述

3.4 发送和接收函数

***************** 发送一个字符  **********************/
// 使用单字节数据发送前要使能发送引脚,发送后要使能接收引脚
void _485_SendByte(  uint8_t ch )
{
	/* 发送一个字节数据到USART1 */
	USART_SendData(_485_USART,ch);
		
	/* 等待发送完成 */
	while (USART_GetFlagStatus(_485_USART, USART_FLAG_TXE) == RESET);	
	
}
/*****************  发送指定长度的字符串 **********************/
void _485_SendStr_length( uint8_t *str,uint32_t strlen )
{
	unsigned int k=0;

	_485_TX_EN();	//	使能发送数据	
    do 
    {
        _485_SendByte( *(str + k) );
        k++;
    } while(k < strlen);
		
	/*加短暂延时,保证485发送数据完毕*/
	Delay(0xFFF);
		
	_485_RX_EN()	;//	使能接收数据
}


/*****************  发送字符串 **********************/
void _485_SendString(  uint8_t *str) //字符发的什么样,收的就是什么样子。
{
	unsigned int k=0;
	
	_485_TX_EN()	;//	使能发送数据
	
    do 
    {
        _485_SendByte(  *(str + k) );
        k++;
    } while(*(str + k)!='\0');
	
	/*加短暂延时,保证485发送数据完毕*/
	Delay_us(100);// 延时1ms
		
	_485_RX_EN()	;//	使能接收数据
}

void _485_SendArray(uint8_t *arr, uint8_t len)
{
	_485_TX_EN();// 切换到发送模式
	if (len < 1)
	{
		return;
	}
	for (uint8_t i = 0; i < len; i++)
	{
		_485_SendByte(arr[i]);
	}
	_485_RX_EN();// 切换到接收模式
}


/*****************  发送一个16位数,此计算结果低位在前高位在后 **********************/
void _485_SendHalfWord(uint16_t ch)
{
	uint8_t temp_h, temp_l;
	
	/* 取出高八位 */
	temp_h = (ch&0XFF00)>>8;
	/* 取出低八位 */
	temp_l = ch&0XFF;
	
	/* 发送低八位 */
	_485_SendByte(temp_l);
	while (USART_GetFlagStatus(_485_USART, USART_FLAG_TXE) == RESET);	
	
	/* 发送高八位 */
	_485_SendByte(temp_h);
	while (USART_GetFlagStatus(_485_USART, USART_FLAG_TXE) == RESET);
}

3.4 主函数


		_485_SendArray(cmd_6064, 8); 
        Delay_us(100);// 加1ms时延;‌RS485串口发送完数据后,为了保证接收的数据完整性,中间需要加一定的时延。
		if (g_timer) 
		{
				g_timer = 0;
				_485_RX_EN();
				uint16_t len =0;
				char * pbuf = get_rebuff(&len);
				for (int i = 0; i < len; i++)
				{
					printf("%c", pbuf[i]);
				}
				clean_rebuff();// 用完之后,清空缓冲区
							
	}
					

里面定时器相关内容,略写了。

参考

  1. modbus串口编程接收到的数据不完整问题
  2. 本文章将会展示至 里程碑专区 ,您也可以在 专区 内查看其他创作者的纪念日文章
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

同志啊为人民服务!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值