48 STM32普通IO模拟usart串口

1.引言

本次实验的原因是因为最近接触到一款单片机,只有一个串口,但项目中要用到至少2个串口,所以一个串口好少啊,没办法,只能另寻出路,通过普通IO来模拟usart串口了。然后,经过一番资料搜索,代码搬运,终于成功,组合出一个io模拟串口的模块,该模块成功实现了9600-8-N的串口数据收发,为方便记忆以及学习,特此记录!

2.普通IO模拟串口原理

普通io模拟串口,也需要严格的遵循串口协议规则,具体的规则可百度一下。

当波特率是115200时,发送1bit数据需要 1/115200 = 8.68us;所以,根据协议每一位数据传输电平持续8.68us

当波特率是9600时,发送1bit数据需要 1/9600= 104us;所以,根据协议每一位数据传输电平持续104us

这里要注意,在115200的时候,因为stm32默认的delay_us的不精准,会导致模拟串口的数据收发错误,在此可以建议用定时器延时,以达到精准操作。

在接收的地方,通过中断来触发启动,然后启动一个定时器,根据波特率的电平持续时间,在电平的中间去检测,是高电平还是低电平,从而确定bit的值。所以,检测到下降沿后延时一下,再启动定时器,定时去判断IO的高低。

STM32 IO口模拟串口通讯 - ziye334 - ziye334的博客

3.代码实现 

本次使用stm32f103c8t6实验板,所用到的硬件资源如下:

1.GPIO两个

2.外部中断1路

3.定时器两个

io模拟串口头文件:iousart.h

#ifndef __IOUSART_H_
#define	__IOUSART_H_

#include "stm32f10x.h"

//对应波特率的1个电平持续时间
//(1/9600) = 104us
#define IO_USART_SENDDELAY_TIME 	104	

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,
};


void iouart1_SendByte(uint8_t uByte);
void iouart1_init(void);

#endif /* __LED_H */

io模拟串口源文件:iousart.c

#include <stdio.h>
#include "iousart.h"
#include "delay.h"

//
//io模拟串口-9600-8-N
//定时器2+定时器4+外部中断1路+2个GPIO
//
u8 recvData = 0;
u8 recvStat = COM_STOP_BIT;
//
//用以模拟串口延时
void TIM2_Int_Init(void)
{
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);

  	TIM_TimeBaseStructure.TIM_Period = 65535 - 1;
  	TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1;	//设置了用来作为 TIM3 时钟频率除数的预分频值
 	TIM_TimeBaseStructure.TIM_ClockDivision = 0;	//时钟分割为0
  	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//TIM3 向上计数模式

  	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
	TIM_Cmd(TIM2,ENABLE);
}

//用以模拟io串口数据接收计时
void TIM4_Int_Init(void)
{
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	NVIC_InitTypeDef NVIC_InitStructure;

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //时钟使能

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

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

//模拟串口1初始化
void iouart1_init(void)	
{
	GPIO_InitTypeDef  GPIO_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	EXTI_InitTypeDef EXTI_InitStruct;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOB, ENABLE);	 //使能PB,PC端口时钟 

	//SoftWare Serial TXD
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;	    
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 //IO口速度为50MHz	 
	GPIO_Init(GPIOB, &GPIO_InitStructure);	  				
	GPIO_SetBits(GPIOB, GPIO_Pin_8); 						

	 
	//SoftWare Serial RXD
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		
	GPIO_Init(GPIOB, &GPIO_InitStructure);	 

	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource9);
	EXTI_InitStruct.EXTI_Line = EXTI_Line9;
	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=3; 
	NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;  
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;  
	NVIC_Init(&NVIC_InitStructure);  

	TIM2_Int_Init();
	TIM4_Int_Init();
}


void iouart1_TXD(uint8_t option)
{
	if(1 == option)
	{
		GPIO_SetBits(GPIOB, GPIO_Pin_8);
	}
	else
	{
		GPIO_ResetBits(GPIOB, GPIO_Pin_8);	
	}
}

uint8_t iouart1_RXD(void)
{
	return GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_9);
}

/***************************************
*	函 数 名: Delay_Ms
*	功能说明: 延时
*	形    参:nTime,单位为uS
*	返 回 值: 无
****************************************/
void iouart1_delayUs(volatile u32 nTime)
{ 

	u16 tmp;

	tmp = TIM_GetCounter(TIM2);		//获得 TIM3 计数器的值
	
	if(tmp + nTime <= 65535)
		while( (TIM_GetCounter(TIM2) - tmp) < nTime );
	else
	{
		TIM_SetCounter(TIM2, 0);//设置 TIM3 计数器寄存器值为0
		while( TIM_GetCounter(TIM2) < nTime );
	}
}

/*****************************************
*	函 数 名: iouart1_SendByte
*	功能说明: 模拟串口发送一字节数据
*	形    参:无
*	返 回 值: 无
******************************************/
void iouart1_SendByte(u8 datatoSend)
{
	u8 i, tmp;

	// 开始位
	iouart1_TXD(0);	//将TXD的引脚的电平置低
	iouart1_delayUs(IO_USART_SENDDELAY_TIME);	

	for(i = 0; i < 8; i++)
	{
		tmp	= (datatoSend >> i) & 0x01;

		if(tmp == 0)
		{
			iouart1_TXD(0);
			iouart1_delayUs(IO_USART_SENDDELAY_TIME);	//0		
		}
		else
		{
			iouart1_TXD(1);
			iouart1_delayUs(IO_USART_SENDDELAY_TIME);	//1		
		}
	}
	
	// 结束位
	iouart1_TXD(1);//将TXD的引脚的电平置高
	iouart1_delayUs(IO_USART_SENDDELAY_TIME);	
}


void EXTI9_5_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line9) != RESET)
	{
		if(iouart1_RXD() == 0) 
		{
			if(recvStat == COM_STOP_BIT)
			{
				recvStat = COM_START_BIT;
				//这里要延时一下
				iouart1_delayUs(50);
				TIM_Cmd(TIM4, ENABLE);
			}
		}
		EXTI_ClearITPendingBit(EXTI_Line9);
	}
}


void TIM4_IRQHandler(void)
{  
	if(TIM_GetITStatus(TIM4, TIM_FLAG_Update) != RESET)
	{
		TIM_ClearITPendingBit(TIM4, TIM_FLAG_Update);	
		 recvStat++;
		if(recvStat == COM_STOP_BIT)
		{
			TIM_Cmd(TIM4, DISABLE);
			//到这里就接收到完整的一个字节数据
			recvData = recvData;
			
			
			return;
		}
		if(iouart1_RXD())
		{
			recvData |= (1 << (recvStat - 1));
		}else{
			recvData &= ~(1 << (recvStat - 1));
		}	
  }		
}

以上代码,测试过是可以的。over!

猜你喜欢

转载自blog.csdn.net/Chasing_Chasing/article/details/116458804
48