stm32利用定时器中断实现普通io模拟串口对字符串进行收发

本贴是根据一博主改写的代码,实现了一个io口模拟串口对字符串进行收发。原帖地址:https://blog.csdn.net/wxh0000mm
话不多说进入正题,串口通信协议发送一个字节默认为10个bit,其中包括开始位、停止位和中间八个数据位。数据固定开始位为低电平,结束位为高电平。如果我们发送字母a最终会以二进制0 0110 0001 1形式进行数据传输。
在这里插入图片描述

对串口通信协议有了基础的了解写代码就好办了,发送函数是很简单的,只需要根据通信协议,在字符bit位为1的时候拉高引脚,bit位为0的时候将引脚拉低即可进行数据的传输。以波特率9600为例:传输一个bit位的间隔就是1/9600s,就约等于104us发送一个bit位。

下面是io口模拟的发送函数

/*********************模拟串口发送数据***********************************/
//IO口引脚定义
void VirtualCOM_TX_GPIOConfig(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	/* PA4设为数据输出口,模拟TX */
	
	GPIO_InitStruct.GPIO_Pin = COM_TX_PIN;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;     //设置为推挽输出
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(COM_TX_PORT, &GPIO_InitStruct)
	GPIO_SetBits(COM_TX_PORT, COM_TX_PIN);           //设置默认位为高电平
}

定义了数据发送引脚即可利用串口通信协议,间隔一定延时对单个bit位进行传输

//模拟引脚发送单个字节
void VirtualCOM_ByteSend(u8 val)
{
	u8 i = 0;
	COM_DATA_LOW;             //引脚拉低,即将发送数据
	Delay_Us(BuadRate9600);  //延时104us
	for(i = 0; i < 8; i++) //8位数据位
	{
	    if(val & 0x01)    //如果bit位为1
	            COM_DATA_HIGH;  //引脚拉高
	    else    //否则拉低
	            COM_DATA_LOW;
	    Delay_Us(BuadRate9600);
	    val >>= 1;
	}
	COM_DATA_HIGH;   //停止位
	Delay_Us(BuadRate9600);
}

有了单个字节的发送函数,直接调用该函数即可发送字符串

void VirtualCOM_StringSend(u8 *str)
{
	while(*str != 0)
	{
		VirtualCOM_ByteSend(*str);
		str++;
	}
}

发送的流程明白了,接收也是同样的道理,不过接收就不能用延时去进行接收,因为程序代码运行需要一定的周期,一旦数据量大了就无法准确的进行数据的接收。所以我们需要在检测到开始位的时候去开启一起定时器,定期接收bit数据位。

/*********************串口变量初始化***********************************/
u8 recvStat = COM_STOP_BIT;
u8 recvData = 0x00;
u8 COM_RX_BUF[COM_REC_LEN];
u16 COM_RX_STA = 0;
u8 COM_RX_END = 1;

/*********************模拟串口接收数据***********************************/
//接收引脚定义
void VirtualCOM_RX_GPIOConfig(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	EXTI_InitTypeDef EXTI_InitStruct;
	NVIC_InitTypeDef NVIC_InitStruct;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO,ENABLE);
	
	/* PA5设为数据输入口,模拟RX */
	GPIO_InitStruct.GPIO_Pin = COM_RX_PIN;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(COM_RX_PORT,&GPIO_InitStruct);
	GPIO_SetBits(COM_RX_PORT, COM_RX_PIN);     //设置默认位为高电平
	
	EXTI_InitStruct.EXTI_Line = EXTI_Line5;              //中断线
	EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;     //中断模式
	EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿中断
	EXTI_InitStruct.EXTI_LineCmd = ENABLE;
	EXTI_Init(&EXTI_InitStruct);
	
	NVIC_InitStruct.NVIC_IRQChannel = EXTI9_5_IRQn;      //外部中断,边沿触发
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStruct);
}

//串口外部中断
void EXTI9_5_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line5) != RESET)
	{
		if(!COM_RX_STAT)     //检测引脚高低电平,如果是低电平,则说明检测到下升沿
		{
			if(recvStat == COM_STOP_BIT)   //状态为停止位
			{
				recvStat = COM_START_BIT;   //接收开始位
				COM_RX_END = 0;             //标志数据是否处理完成
				Delay(63);
				TIM_Cmd(TIM2,ENABLE);      //开启定时器
			}
		}
		EXTI_ClearITPendingBit(EXTI_Line5);  //清除EXTI_Line1中断挂起标志位
	}
}

//清空字符数组
void CLR_Buf(void)
{
	unsigned char y;
	for(y = 0;y < COM_REC_LEN;y ++ )
	{
		COM_RX_BUF[y] = '\0';
	}
	COM_RX_STA = 0;
}

本程序利用定时器2,定时104us执行一次中断处理函数。

/********************定时器2*******************************/
//配置定时器2
void TIM2_Configuration(u16 arr,u16 psc)
{
	TIM_TimeBaseInitTypeDef TIM_TimBaseStruct;
	NVIC_InitTypeDef NVIC_InitStruct;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);    //使能TIM2的时钟
	TIM_DeInit(TIM2);                                       //复位定时器2
	TIM_InternalClockConfig(TIM2);                          //使用内部时钟给TIM2提供时钟源
	
	TIM_TimBaseStruct.TIM_Period = arr;                     //设置计数溢出大小,每计period个数就产生一个更新事件
	TIM_TimBaseStruct.TIM_Prescaler = psc;                  //预分频系数为72,这样计数器时钟为72MHz/72 = 1MHz
	TIM_TimBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;     //设置时钟分频
	TIM_TimBaseStruct.TIM_CounterMode = TIM_CounterMode_Up; //设置计数器模式为向上计数模式
	TIM_TimeBaseInit(TIM2,&TIM_TimBaseStruct);              //将配置应用到TIM2中
	
	TIM_ClearFlag(TIM2,TIM_FLAG_Update);     //清除溢出中断标志
	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //开启TIM2的中断
	TIM_Cmd(TIM2,DISABLE);                   //关闭定时器TIM2
	
	NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;      //通道设置为TIM2中断
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;   //响应式中断优先级1
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;      //打开中断
	NVIC_Init(&NVIC_InitStruct);
}

//定时器2中断函数
void TIM2_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM2,TIM_IT_Update) != RESET)  //检测是否发生溢出更新事件
	{
		TIM_ClearITPendingBit(TIM2,TIM_FLAG_Update);    //清除中断标志
		
		recvStat++;                    //改变状态机
		if(recvStat == COM_STOP_BIT)   //收到停止位
		{
			COM_RX_END = 1;            //标志数据处理完成
			TIM_Cmd(TIM2,DISABLE);     //关闭定时器
			Delay(63);                 //延时指令周期,等待下一字节处理
			COM_RX_BUF[COM_RX_STA] = recvData;   //将当前处理完的字节存入数组里面
			COM_RX_STA++;
			
			if(COM_RX_STA > (COM_REC_LEN -1))
				COM_RX_STA = 0;
		}
		
		if(COM_RX_STAT)
		{
			recvData |= (1 << (recvStat - 1));
		}
		else
		{
			recvData &= ~(1 <<(recvStat - 1));
		}
	}
}

以下是虚拟串口和定时器2头文件

/*********************串口头文件*******************************/
#ifndef __COM_H
#define __COM_H
#include "sys.h"
#include "delay.h"

#define BuadRate9600 104
#define BuadRate1200 830

#define COM_REC_LEN 200  	    //定义最大接收字节数 200

#define COM_TX_PORT GPIOA
#define COM_TX_PIN GPIO_Pin_4

#define COM_RX_PORT GPIOA
#define COM_RX_PIN GPIO_Pin_5

#define COM_RX PAout(5)  
#define COM_DATA_HIGH GPIO_SetBits(COM_TX_PORT, GPIO_Pin_4)   //高电平
#define COM_DATA_LOW  GPIO_ResetBits(COM_TX_PORT, GPIO_Pin_4) //低电平

#define COM_RX_STAT GPIO_ReadInputDataBit(COM_RX_PORT, GPIO_Pin_5)

enum {
	COM_START_BIT, //停止位
	COM_D0_BIT, //bit0
	COM_D1_BIT, //bit1
	COM_D2_BIT, //bit2
	COM_D3_BIT, //bit3
	COM_D4_BIT, //bit4
	COM_D5_BIT, //bit5
	COM_D6_BIT, //bit6
	COM_D7_BIT, //bit7
	COM_STOP_BIT,  //结束位
};

extern u8 recvStat;
extern u8 recvData;

extern u8 COM_RX_BUF[COM_REC_LEN];   //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符 
extern u16 COM_RX_STA;         		   //接收状态标记 
extern u8 COM_RX_END;

void VirtualCOM_TX_GPIOConfig(void);
void VirtualCOM_RX_GPIOConfig(void);
void VirtualCOM_ByteSend(u8 val);
void VirtualCOM_StringSend(u8 *str);
void EXTI9_5_IRQHandler(void);
void CLR_Buf(void);

#endif

/*********************定时器文件*******************************/
#ifndef __TIMER_H
#define __TIMER_H
#include "sys.h" 
#include "com.h"

extern unsigned char Count_timer;
extern unsigned char Flag_timer_1S;

void TIM3_Int_Init(u16 arr,u16 psc);
void TIM2_Configuration(u16 arr,u16 psc);
void TIM3_IRQHandler(void);
void TIM2_IRQHandler(void);

#endif

我使用的单片机晶振为72MHZ,对其进行一个八分频,在延时文件中定义了不精准延时函数,程序运行一个指令周期为1/9us。

/**************不精准延时********************/
void Delay(u32 t)
{
	while(t--);
}

下面是主函数调用

int main(void) 
{	
	NVIC_Configuration();
	TIM3_Int_Init(999,7199);	          //开启定时器3,计数100ms
	LED_Init();
	Delay_Init();
	KEY_Init();
	TIM2_Configuration(BuadRate9600,71);  //定时器2 104us
	VirtualCOM_TX_GPIOConfig();
	VirtualCOM_RX_GPIOConfig();
	LED0 = 1;
	VirtualCOM_StringSend("com tx data\r\n"); //测试发送
	while(1)   //主循环
	{
		Delay_Us(5);        //延时,串口中断先处理数据   
		if(COM_RX_END == 1)  //检测到数据处理完成
		{
			VirtualCOM_StringSend(COM_RX_BUF);  //打印显示
			CLR_Buf();  //清空数组
		}
	}
}

效果如下
在这里插入图片描述

  • 6
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值