STM32 模拟串口(UART)使用

学习目标:

由于在项目中需要用到多路的串口使用,而自己的单片机目前来讲没法满足我们项目所需要的串口需求,因此要对普通的GPIO进行转换为UART进行使用。从而使得我们单片机能够得到多一路的串口。

学习内容:

1、添加头文件,对编写模拟串口的函数进行梳理

首先我们先添加相应的头文件。既然我们要进行对串口的模拟,因此我们要先了解uart相关的通信协议。由于UART的通信方式是由1个起始位,8个数据位,包含一个奇偶校验位,和结束位构成因此我们将使用单片机中的两个普通的IO口电平的高低进行对相应时序的模拟。
在这里插入图片描述

#include "stm32f10x.h"
#include "vuart2.h"

2、宏定义

使用到的io口为

#define OI2_TXD	PDout(6)
#define OI2_RXD	PDin(7)

#define BuadRate2_9600	104 
#define Recive2_Byte 19 //接收缓冲器的个数
u8 len2 = 0;	//接收计数
u8 USART2_buf[Recive2_Byte];  //接收缓冲区

将IO口相应的位带操作函数进行宏定义从而使得在对不同的电平的进行转换的时候更为方便,并且减少了调用其他函数的过程所消耗的时间,程序执行效率更高。
在本次的传输过程中我选用的是使用波特率速率为9600,也就是1s中发送9600个字节,因此对每个字节数据进行计算1000000us/9600可以得出,发一个字节的数据需要进行大概需要 104.16us并且对于相应的电平持续时间要求误差不能超过±5%因此对我们进行时间的控制要求就显得比较重要了。

3、枚举出各个位

enum{
	COM_START_BIT,
	COM_D0_BIT,
	COM_D1_BIT,
	COM_D2_BIT,
	COM_D3_BIT,
	COM_D4_BIT,
	COM_D5_BIT,
	COM_D6_BIT,
	COM_D7_BIT,
	COM_STOP_BIT,
};

u8 recvStat2 = COM_STOP_BIT;
u8 recvData2 = 0;

4、IO——TXD进行模拟

void IO2_TXD(u8 Data)
{
	u8 i = 0;
	OI2_TXD = 0;  
	delay_us(BuadRate2_9600);
	for(i = 0; i < 8; i++)
	{
		if(Data&0x01)
			OI2_TXD = 1;  
		else
			OI2_TXD = 0; 	
		
		delay_us(BuadRate2_9600);
		Data = Data>>1;
	}
	OI2_TXD = 1;
	delay_us(BuadRate2_9600);
}

由于发送的信号是将TXD信号进行拉低处理,因此在拉低TXD相应的IO口之后进行延时处理,再进行循环对我们需要发送的各个位的数据继续进行发送循环发送完成之后将电平拉高代表停止位。

5、构建发送函数

void USART2_Send(u8 *buf, u8 len2)
{
	u8 t;
	for(t = 0; t < len2; t++)
	{
		IO2_TXD(buf[t]);
	}
}

其中的*buf为需要发送的数据,len2为数据长度,进行循环调用IO_TXD进行一个字节一个字节的数据发送。

6、对使用到的IO口进行初始化

void IO2Config(void)
 {
	GPIO_InitTypeDef  GPIO_InitStructure;//初始化gpio
	NVIC_InitTypeDef NVIC_InitStructure;//中断初始化函数
 	EXTI_InitTypeDef EXTI_InitStruct;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOC, ENABLE);	 //使能PD,PC端口时钟 
	 
	 //SoftWare Serial TXD
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;	    		//选择io口6
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 //IO口速度为50MHz	 
	GPIO_Init(GPIOD, &GPIO_InitStructure);	  				
	GPIO_SetBits(GPIOD,GPIO_Pin_6); 						//TXD默认电平拉高
	 
	//SoftWare Serial RXD
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		
	GPIO_Init(GPIOD, &GPIO_InitStructure);	 

	GPIO_EXTILineConfig(GPIO_PortSourceGPIOD, GPIO_PinSource7);		//对D7的下降沿进行中断采样,当接收到下降沿时代表接收到数据触发中断处理函数
	EXTI_InitStruct.EXTI_Line = EXTI_Line7;//用到了中断7
	EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt;
	EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Falling; //下降沿触发中断
	EXTI_InitStruct.EXTI_LineCmd=ENABLE;
	EXTI_Init(&EXTI_InitStruct);//初始化中断


	NVIC_InitStructure.NVIC_IRQChannel= EXTI9_5_IRQn ; //中断发生于9-5的中断之中
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2; 
	NVIC_InitStructure.NVIC_IRQChannelSubPriority =2;  
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;  
	NVIC_Init(&NVIC_InitStructure);  
}

7、使能定时器5

void TIM5_Int_Init(u16 arr,u16 psc)
{
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); //时钟使能
	
	//定时器TIM5初始化
	TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
	TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
	TIM_ClearITPendingBit(TIM5, TIM_FLAG_Update);
	TIM_ITConfig(TIM5,TIM_IT_Update,ENABLE ); //使能指定的TIM5中断,允许更新中断

	//中断优先级NVIC设置
	NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;  //TIM5中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  //先占优先级1级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;  //从优先级1级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
	NVIC_Init(&NVIC_InitStructure);  //初始化NVIC寄存器			 
}

对TIM5进行初始化操作使得定时器可以检测到各个位的电平持续性时间从而对接收到的数据进行分析。计时结束后进入中断TIM5处理。

8、中断处理函数

void EXTI9_5_IRQHandler(void)
{
	if(EXTI_GetFlagStatus(EXTI_Line7) != RESET)//对中断标志位进行采集
	{
		if(OI2_RXD == 0) 
		{
			if(recvStat2 == COM_STOP_BIT)
			{
				recvStat2 = COM_START_BIT;//将当前的状态设置为开始位
				TIM_Cmd(TIM5, ENABLE);//开启定时器计数
			}
		}
		EXTI_ClearITPendingBit(EXTI_Line7); //清除中断标志
	}
}

9、定时器中断处理函数

void TIM5_IRQHandler(void)
{  
	if(TIM_GetFlagStatus(TIM5, TIM_FLAG_Update) != RESET)
	{
		TIM_ClearITPendingBit(TIM5, TIM_FLAG_Update);	//清除中断标志位
		recvStat2++;	//将位置移动到第一位的数据
		if(recvStat2 == COM_STOP_BIT)//当运行到停止位时进入
		{
			TIM_Cmd(TIM5, DISABLE);//停止tim5
			USART2_buf[len2++] = recvData2;//将采集到的各个数据传递给USART2_buf
			if(len2 > Recive2_Byte-1)//将数据通过回显到串口调试助手中
			{
				len2 = 0;
				USART2_Send(USART2_buf,Recive2_Byte);
			}
			return;
		}
		if(OI2_RXD)//采集RXD各个电平
		{
			recvData2 |= (1 << (recvStat2 - 1));
		}else{
			recvData2 &= ~(1 << (recvStat2 - 1));
		}	
  }		
}

整体代码

vuart2.c


#include "stm32f10x.h"
#include "vuart2.h"
/**
*软件串口的实现(IO模拟串口)
* 波特率:9600    1-8-N
* TXD : PD6
* RXD : PD7
* 使用外部中断对RXD的下降沿进行触发,使用定时器5按照9600波特率进行定时数据接收。
* Demo功能: 接收11个数据,然后把接收到的数据发送出去
*/


#define OI2_TXD	PDout(6)
#define OI2_RXD	PDin(7)

#define BuadRate2_9600	104 
#define Recive2_Byte 19 //接收缓冲器的个数
u8 len2 = 0;	//接收计数
u8 USART2_buf[Recive2_Byte];  //接收缓冲区

enum{
	COM_START_BIT,
	COM_D0_BIT,
	COM_D1_BIT,
	COM_D2_BIT,
	COM_D3_BIT,
	COM_D4_BIT,
	COM_D5_BIT,
	COM_D6_BIT,
	COM_D7_BIT,
	COM_STOP_BIT,
};

u8 recvStat2 = COM_STOP_BIT;
u8 recvData2 = 0;

void IO2_TXD(u8 Data)
{
	u8 i = 0;
	OI2_TXD = 0;  
	delay_us(BuadRate2_9600);
	for(i = 0; i < 8; i++)
	{
		if(Data&0x01)
			OI2_TXD = 1;  
		else
			OI2_TXD = 0; 	
		
		delay_us(BuadRate2_9600);
		Data = Data>>1;
	}
	OI2_TXD = 1;
	delay_us(BuadRate2_9600);
}
	
void USART2_Send(u8 *buf, u8 len2)
{
	u8 t;
	for(t = 0; t < len2; t++)
	{
		IO2_TXD(buf[t]);
	}
}
	
 void IO2Config(void)
 {
	GPIO_InitTypeDef  GPIO_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
 	EXTI_InitTypeDef EXTI_InitStruct;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOC, ENABLE);	 //使能PB,PC端口时钟 
	 
	 //SoftWare Serial TXD
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;	    
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 //IO口速度为50MHz	 
	GPIO_Init(GPIOD, &GPIO_InitStructure);	  				
	GPIO_SetBits(GPIOD,GPIO_Pin_6); 						
	 
	//SoftWare Serial RXD
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		
	GPIO_Init(GPIOD, &GPIO_InitStructure);	 

	GPIO_EXTILineConfig(GPIO_PortSourceGPIOD, GPIO_PinSource7);
	EXTI_InitStruct.EXTI_Line = EXTI_Line7;
	EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt;
	EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Falling; //下降沿触发中断
	EXTI_InitStruct.EXTI_LineCmd=ENABLE;
	EXTI_Init(&EXTI_InitStruct);


	NVIC_InitStructure.NVIC_IRQChannel= EXTI9_5_IRQn ; 
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2; 
	NVIC_InitStructure.NVIC_IRQChannelSubPriority =2;  
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;  
	NVIC_Init(&NVIC_InitStructure);  
}
 
void TIM5_Int_Init(u16 arr,u16 psc)
{
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); //时钟使能
	
	//定时器TIM5初始化
	TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
	TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
	TIM_ClearITPendingBit(TIM5, TIM_FLAG_Update);
	TIM_ITConfig(TIM5,TIM_IT_Update,ENABLE ); //使能指定的TIM5中断,允许更新中断

	//中断优先级NVIC设置
	NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;  //TIM4中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  //先占优先级1级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;  //从优先级1级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
	NVIC_Init(&NVIC_InitStructure);  //初始化NVIC寄存器			 
}
void EXTI9_5_IRQHandler(void)
{
	if(EXTI_GetFlagStatus(EXTI_Line7) != RESET)
	{
		if(OI2_RXD == 0) 
		{
			if(recvStat2 == COM_STOP_BIT)
			{
				recvStat2 = COM_START_BIT;
				TIM_Cmd(TIM5, ENABLE);
			}
		}
		EXTI_ClearITPendingBit(EXTI_Line7);
	}
}

void TIM5_IRQHandler(void)
{  
	if(TIM_GetFlagStatus(TIM5, TIM_FLAG_Update) != RESET)
	{
		TIM_ClearITPendingBit(TIM5, TIM_FLAG_Update);	
		recvStat2++;
		if(recvStat2 == COM_STOP_BIT)
		{
			TIM_Cmd(TIM5, DISABLE);
			USART2_buf[len2++] = recvData2;
		if(len2 > Recive2_Byte-1)
		{
			len2 = 0;
			USART2_Send(USART2_buf,Recive2_Byte);
		}
			return;
		}
		if(OI2_RXD)
		{
			recvData2 |= (1 << (recvStat2 - 1));
		}else{
			recvData2 &= ~(1 << (recvStat2 - 1));
		}	
  }		
}

vuart2.h

#ifndef __VUART2__H
#define __VUART2__H
#include "stm32f10x.h"

void IO2_TXD(u8 Data);
void USART2_Send(u8 *buf, u8 len);
void IO2Config(void);
void TIM5_Int_Init(u16 arr,u16 psc);
#endif
  • 8
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值