资讯详情

STM32串口发送和接收多个数据教程基于气体传感器实战

前言

最近,在一个项目中,我们需要检测电缆沟中的各种气体浓度,所以我们使用了一个气体传感器,它是RS485通信需要实现RS485转串口与STM32通信,MCU为STM32F429,485芯片为MAX3485。


1.接收和发送串口数据

在这里,我想谈谈串口的这一部分,从事stm32开发已经好几年了,自以为是对的stm32已经掌握的很好了,后来才发现自己只是浮于表面,没有好好的深入学习stm32的底层

1.简介

串口是MCU软件开发过程中至关重要的外部接口和重要的调试工具MCU都会有串口。 串口设置步骤:

  1. 串口时钟使能,GPIO时钟使能。

  2. 设置引脚复用器映射:调用GPIO_PinAFConfig函数。

  3. GPIO 初始化设置:要设置模式为复用功能。

  4. 串口参数初始化:设置波特率、字长、奇偶校验等参数。

  5. 打开中断和初始化 NVIC,中断能量(如果需要打开中断,则需要此步骤)。

  6. 使能串口。

  7. 编写中断处理函数:函数名格式为 USARTxIRQHandler(x 对应串口号)。 7中有一个中断处理函数需要注意,当使用的串口是USART中断处理函数名: USARTxIRQHandler(x 对应串口号),使用串口时UART中断处理函数名: UARTxIRQHandler(x 对应串口号),记住这一点,否则串口中断函数无法进入,而且DEBUG时该中断函数无法设置断点。

2.串口配置程序

我用串口5,UART5,TX为PC12,RX为PD2

void Usart5_Init(u32 bound) { 
          GPIO_InitTypeDef GPIO_InitStructure; //1.GPIO口初始化   USART_InitTypeDef  USART_InitStructure; //串口初始化  NVIC_InitTypeDef   NVIC_InitStructure; ///中断初始化    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_GPIOD,ENABLE); //使能GPIOC时钟  RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART5,ENABLE);//使能USART5时钟    ///打开串口5对应IO引脚复用映射  GPIO_PinAFConfig(GPIOC, GPIO_PinSource12, GPIO_AF_UART5);  GPIO_PinAFConfig(GPIOD, GPIO_PinSource2, GPIO_AF_UART5);    //IO初始配置口  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;  GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AF ;  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz  GPIO_Init(GPIOC, &GPIO_InitStructure);     //IO初始配置口   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;  GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AF ;  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;  GPIO_InitStructure
       
        .GPIO_PuPd 
        = GPIO_PuPd_UP
        ; GPIO_InitStructure
        .GPIO_Speed 
        = GPIO_Speed_50MHz
        ; 
        //速度50MHz 
        GPIO_Init
        (GPIOD
        , 
        &GPIO_InitStructure
        )
        ; 
        //GPIO // RS485EN5,此处代码与RS485有关 GPIO_InitStructure
        .GPIO_Pin 
        = GPIO_Pin_3
        ; 
        // GPIO_InitStructure
        .GPIO_Mode 
        = GPIO_Mode_OUT
        ;
        // GPIO_InitStructure
        .GPIO_Speed 
        = GPIO_Speed_100MHz
        ;
        //100MHz GPIO_InitStructure
        .GPIO_PuPd 
        = GPIO_PuPd_DOWN
        ;
        // 
        GPIO_Init
        (GPIOD
        , 
        &GPIO_InitStructure
        )
        ;
        //初始化GPIOD 
        //串口初始化配置 USART_InitStructure
        .USART_BaudRate 
        = bound
        ;
        //波特率 USART_InitStructure
        .USART_WordLength 
        = USART_WordLength_8b
        ;
        //数据位 USART_InitStructure
        .USART_StopBits 
        = USART_StopBits_1
        ;
        //停止位 USART_InitStructure
        .USART_Parity 
        = USART_Parity_No
        ;
        //奇偶校验位 USART_InitStructure
        .USART_Mode 
        = USART_Mode_Rx 
        | USART_Mode_Tx
        ; 
        //收发模式 USART_InitStructure
        .USART_HardwareFlowControl 
        = USART_HardwareFlowControl_None
        ;
        //无硬件数据流控制 
        USART_Init
        (UART5
        ,
        &USART_InitStructure
        )
        ; 
        USART_Cmd
        (UART5
        , ENABLE
        )
        ; 
        //使能串口5 
        USART_ITConfig
        (UART5
        , USART_IT_RXNE
        , ENABLE
        )
        ;
        //开启相关中断 
        // USART5 NVIC初始化 NVIC_InitStructure
        .NVIC_IRQChannel
        = UART5_IRQn
        ; NVIC_InitStructure
        .NVIC_IRQChannelPreemptionPriority
        = 
        3
        ; NVIC_InitStructure
        .NVIC_IRQChannelSubPriority
        = 
        3
        ; NVIC_InitStructure
        .NVIC_IRQChannelCmd
        = ENABLE
        ; 
        NVIC_Init
        (
        &NVIC_InitStructure
        )
        ; 
        } 
       

3.串口发送程序

此处有多个发送程序,但是每个串口发送程序都是通过往UART5->DR寄存器里写数据来实现发送数据,接收同理,都是读UART5->DR寄存器的数据。 当需要发送数据时,在while中调用下面这些函数就可以。

/发送一个字节数据
//input:byte,待发送的数据
void UART5_send_byte(uint8_t byte)
{ 
        
	while(USART_GetFlagStatus(UART5,USART_FLAG_TC)==RESET);//等待发送完成
	//UART5->DR=byte; //byte
	USART_SendData(UART5,byte);
}

//发送多字节数据
void UART5_Send_bytes(uint8_t *Buffer, uint8_t Length)
{ 
        
	uint8_t i=0;
	while(i<Length)
	{ 
        
		UART5_send_byte(Buffer[i++]);
	}
	GPIO_ResetBits(GPIOD, GPIO_Pin_3);	//USART5 SEND DATA 开关 1:send 0;receive
}

//发送多字节数据+校验和
void UART5_Send(uint8_t *Buffer, uint8_t Length)
{ 
        
	uint8_t i=0;
	while(i<Length)
	{ 
        
		if(i<(Length-1))
		Buffer[Length-1]+=Buffer[i];//累加Length-1前的数据
		if(i==(Length-1))
		{ 
        
			Buffer[Length-1]=Buffer[Length-1]&0xFF;
		}
		UART5_send_byte(Buffer[i++]);
	}
}

4.串口接收数据

串口接收数据的方式有两种,1是采用轮询方式,2是采用中断方式。采用中断来接收串口数据不需要手动调用接收函数,因为串口的接收数据实在中断中所实现的,是需要编写中断函数和接收数据处理函数即可。

void usart5_data_analyse(u8 *buf,u8 num,u8 WhichGas) //串口接受数据处理程序
{ 
        
//接收数据处理函数,自己根据需求来填写
}
void UART5_IRQHandler(void) //中断处理函数,当发送中断时MCU自动调用该函数。
{ 
        
	uint8_t res; static u8 i1=0;
	if(USART_GetITStatus(UART5, USART_IT_RXNE) != RESET)//判断接收标志
	{ 
        
		res = UART5->DR;
		UART5_RX_BUF[i1] = res;
		i1++;
		if(i1 > 17 )//
		{ 
        		
			usart5_data_analyse(UART5_RX_BUF,18,WhichGas);//接收数据处理函数
			i1=0;//缓存清0
			UART5_RX_STA=1;
		}
	}
}

串口的中断接收数据方式有两种,一种是空闲中断IDLE,一种是RXNE(读数据寄存器非空),当RXNE位被置 1 的时候,就是提示已经有数据被接收到了并且可以读出来了。这时候我们要做的就是尽快去读取 USART_DR,通过读 USART_DR 可以将该位清零,也可以向该位写 0,直接清除。

5.USART框图

这个框图就很清楚的描述清楚了串口的数据收发过程,发送过程是1-->2-->3, 接收过程是:4-->5-->6

在这里插入图片描述

6.USART_SR串口状态寄存器

有几个寄存器需要了解一下。

当 TDR 寄存器的内容已传输到移位寄存器时,该位由硬件置 1。如果 USART_CR1 寄存器 中 TXEIE 位 = 1,则会生成中断。通过对 USART_DR 寄存器执行写入操作将该位清零。 0:数据未传输到移位寄存器 1:数据传输到移位寄存器

如果已完成对包含数据的帧的发送并且 TXE 置 1,则该位由硬件置 1。如果 USART_CR1 寄存 器中 TCIE = 1,则会生成中断。该位由软件序列清零(读取 USART_SR 寄存器,然后写入 USART_DR 寄存器)。 TC 位也可以通过向该位写入‘0’来清零。建议仅在多缓冲区通信 时使用此清零序列。 0:传送未完成 1:传送已完成

当 RDR 移 位 寄 存 器 的 内 容 已 传 输 到 USART_DR 寄 存 器 时,该 位 由 硬 件 置 1。如 果 USART_CR1 寄存器中 RXNEIE = 1,则会生成中断。通过对 USART_DR 寄存器执行读入 操作将该位清零。 RXNE 标志也可以通过向该位写入零来清零。建议仅在多缓冲区通信时使 用此清零序列。 0:未接收到数据 1:已准备好读取接收到的数据

检测到空闲线路时,该位由硬件置 1。如果 USART_CR1 寄存器中 IDLEIE = 1,则会生成中 断。该位由软件序列清零(读入 USART_SR 寄存器,然后读入 USART_DR 寄存器)。 0:未检测到空闲线路 1:检测到空闲线路

在 RXNE = 1 的情况下,当移位寄存器中当前正在接收的字准备好传输到 RDR 寄存器时,该 位由硬件置 1。如果 USART_CR1 寄存器中 RXNEIE = 1,则会生成中断。该位由软件序列清 零(读入 USART_SR 寄存器,然后读入 USART_DR 寄存器)。 0:无上溢错误 1:检测到上溢错误

二、USART转RS485

1.串口转RS485电路设计

采用MAX3485芯片实现串口转485. 需要注意的一点是,RE和DE引脚是用来控制数据的接收和发送,当RE和DE为高电平时,芯片处于发送状态,RE和DE为低电平时,芯片处于接收状态

三、实现USART发送和接收多个数据

串口发送和接受多个数据也是在单个发送和接收的基础上实现的,由于项目需要读取四个气体的浓度,所以需要串口分别发送四个数据请求并接受四个串口数据,注意数据的格式都是16进制

oid Get_Gas_Density(void) //该函数先延时100s,如何轮流发送四个数据并接受四个数据,接受数据是通过中断来实现
{ 
        
		if(tx_times==1)
		{ 
        		
			for(WaitTime=0;WaitTime<100;WaitTime++) //wait 30s
			{ 
        
				delay_ms(1000);
			}
			tx_times=2;
		}
		if(tx_times==2)
		{ 
        
				UART5_RX_STA = 0;
				GPIO_SetBits(GPIOD, GPIO_Pin_3);	//USART5 SEND DATA 开关 1:send 0;receive
				delay_ms(50);
				LED1=0;
				WhichGas = 0;
				UART5_Send_bytes( UART5_TX_BUF_1, 12);
				LED1=1;			
				tx_times = 3;
				delay_ms(1000);
		}
		else	if((tx_times==3)&&(UART5_RX_STA))
		{ 
        
				UART5_RX_STA = 0;
				GPIO_SetBits(GPIOD, GPIO_Pin_3);	//USART5 SEND DATA 开关 1:send 0;receive
				delay_ms(50);
				LED1=0;
				WhichGas = 1;
				UART5_Send_bytes( UART5_TX_BUF_2, 12);
				LED1=1;
				tx_times = 4;
				delay_ms(1000);	
		}
		else	if((tx_times==4)&&(UART5_RX_STA))
		{ 
        
				UART5_RX_STA = 0;
				GPIO_SetBits(GPIOD, GPIO_Pin_3);	//USART5 SEND DATA 开关 1:send 0;receive
				delay_ms(50);
				LED1=0;
				WhichGas = 2;
				UART5_Send_bytes( UART5_TX_BUF_3, 12);	
				LED1=1;
				tx_times = 5;
				delay_ms(1000);
		}
				else	if((tx_times==5)&&(UART5_RX_STA))
		{ 
        
				UART5_RX_STA = 0;
				GPIO_SetBits(GPIOD, GPIO_Pin_3);	//USART5 SEND DATA 开关 1:send 0;receive
				delay_ms(50);
				LED1=0;
				WhichGas = 3;
				UART5_Send_bytes( UART5_TX_BUF_4, 12);		
				LED1=1;
				tx_times=2;
				delay_ms(1000);	
		}
		else
		{ 
        }
	
}
void usart5_data_analyse(u8 *buf,u8 num,u8 WhichGas) //串口接受数据处理程序
{ 
        

	CalcDedmsoty[1] = (buf[11]&0x0f) * 100;
	CalcDedmsoty[0] = ((buf[11]>>4)&0x0f) * 1000;
	CalcDedmsoty[2] = (buf[12]&0x0f) ;
	CalcDedmsoty[3] = ((buf[12]>>4)&0x0f) * 10;
	GasDensity[WhichGas] = CalcDedmsoty[0]+CalcDedmsoty[1]+CalcDedmsoty[2]+CalcDedmsoty[3];
	switch(buf[13])
	{ 
        
		case 0x00:
			GasDensity[WhichGas]	=	GasDensity[WhichGas];
			break;
		case 0x01:
			GasDensity[WhichGas] = GasDensity[WhichGas]/10;
			break;
		case 0x02:
			GasDensity[WhichGas] = GasDensity[WhichGas]/100;
			break;
		case 0x03:
			GasDensity[WhichGas] = GasDensity[WhichGas]/1000;
			break;
	}
}
void UART5_IRQHandler(void)
{ 
        
	uint8_t res; static u8 i1=0;
	if(USART_GetITStatus(UART5, USART_IT_RXNE) != RESET)//判断接收标志
	{ 
        
		res = UART5->DR;
		UART5_RX_BUF[i1] = res;
		i1++;
		if(i1 > 17 )//
		{ 
        		
			usart5_data_analyse(UART5_RX_BUF,18,WhichGas);
			LED2=0;
			i1=0;//缓存清0
			UART5_RX_STA=1;
			LED2=1;
		}
	}
}

标签: 气体传感器几种类型

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台