STM32外设串口资源用完了怎么办--------串口模拟解决问题(再也不用多个STM32或其它MCU)

之前做项目的时候遇到了一个问题,当把MCU本身的串口资源用完的时候,却还需要使用多几个串口,又不想使用几个MCU解决这个问题。那么模拟串口是解决这个问题的一种方法。


下图是我对串口通信时序图的个人理解:在这里插入图片描述


好对串口有了大致的了解后我们将理解的捋一下编写程序的思路去实现串口的通信:

1、 使能PA时钟。

RCC->RCC_APB2ENR|=1<<2;      //PA时钟使能

2、 我们是IO模拟的串口传输实验,所以要配置两个IO口。因为数据传输本质就是0和1的传输,所以我这里将PA5设置为推挽输出(0、1变换),PA6设置为上拉输入(高电平时停止状态,只有低电平才会触发数据传输)。

GPIOA->GPIO_CRL&=0xFF00FFFF;	//将要设置的位清零
GPIOA->GPIO_CRL|=0x00300000;   //PA5推挽输出
GPIOA->GPIO_CRL|=0X00080000;   //PA4输入模式
GPIOA->GPIO_ODR|=1<<5;			//PA5默认高电平
GPIOA->GPIO_ODR|=0x30;			//PA4上拉输入模式

3、 设置发送数据的波特率,例如我这里用的波特率是9600,那么节拍时间是104us,我编写的例程没用写太多的波特率对应节拍值。
我们可以写出一一对应的节拍值,如:

									  #define BuadRate1200	832		/* 波特率1200 */
									  #define BuadRate2400 	416		/* 波特率2400 */
									  #define BuadRate4800 	208		/* 波特率4800 */
									  #define BuadRate9600 	104		/* 波特率9600 */
													.
													.
												    .

4、 编写发送一个字节数据函数。(开始信号->节拍时间->第零位数据->节拍时间->第一位数据->节拍时间…第七位数据->节拍时间->停止位信号->节拍时间)。

void TX_Byte(u8 data)
{
	int i=0;	
	TX_Low();	//低电平,起始位
	delay_us(BuadRate);		//节拍时间
	for(i=0;i<8;i++)
	{
		if(data & 0x01)
		{
			TX_Hight();
		}
		else
		{
			TX_Low();
		}
		delay_us(BuadRate);	//每传一个数据就需要延迟一个脉冲
		data>>=1;			//是从最低位开始传输的
	}
	TX_Hight();	//高电平,停止位
	delay_us(BuadRate);
}

5、 编写发送一个字符串数据的函数。

void TX_String(u8 *str)
{
	while(*str!=0)
	{
	TX_Byte(*str);
	str++;
	}
}

6、 编写接收数据的函数;
① 一些说明:
接收数据和发送数据是同一个原理的,只不过我们不可能将接收数据放到while循环上面让它一直重复查询等待接收数据信号,不然整个MUC都为它服务了,其它事啥都不用干了。
那我们用什么来识别接收数据信号呢?我们可以使用中断来触发,当高电平信号跳变为低电平信号时中断就触发,接收数据就开始启动,这样就不会一直占用MCU的资源。
这里我还说一下,我接收数据还使用了定时器作为节拍时间,很多人有疑问,为什么要用定时器做节拍时间呢,不是已经有delay延时函数来做节拍时间了吗? 因为delay延时函数的原理是让MCU在那空运行,等于啥事也没干。而定时器时间精准、有条序的
启动、接收、结束,充分利用MCU资源。降低了MCU资源占用率(比起寄存器那是没法比的)。
我这里用图描述一下定时器工作:

在这里插入图片描述

② 中断初始化配置和定时器初始化配置(这里中断我就不多说明了,我上一篇博客有详细说明)
Ⅰ、 中断:

ex_nvic_config(0,4,1);		//设置连接中断总线,PA4,下降沿触发
NVIC_Init(2,2,10,2);		//EXTIX-PY.X

Ⅱ、 定时器:
在这里插入图片描述

RCC->RCC_APB1ENR|=1<<1;	//TIME3时钟使能
TIME3->TIME_PSC=psc;	//预分频器72,得到1Mhz的计数时钟	
TIME3->TIME_ARR=arr;	//设定计数器自动重装值//刚好1MHz TIME3->TIME_CR1&=~(1<<4);//计数器向上计数
TIME3->TIME_DIER|=1<<0;	//允许更新中断(开启中断)
TIME3->TIME_CR1&=~(1<<0);//禁用定时器
NVIC_Init(1,3,29,2);	//抢占1,子优先级3,组2	

③ 原理:
接收起始信号触发中断(打开定时器)->节拍时间(delay)->定时器打开开始计数->运行接收第0个数据位的程序->定时器计数值到达复位->定时器重新开始计数->运行接收第1个数据位的程序->定时器计数值到达复位…->定时器重新开始计数->运行接收第7个数据位的程序->定时器计数值到达复位->定时器重新开始计数->结束接收程序,停止定时器等待下一次数据的接收并将接收的一个字节数据保存定义好的缓冲区中->定时器计数值到达复位。

void EXTI4_IRQHandler(void)
{
   if( (EXTI->PR&(1<<4)) != 0 )     //中断事件发生,挂起寄存器置位
   {
     if(!(GPIOA->GPIO_IDR&(1<<4)))	 //下降沿低电平
     { 	
		if(receiverstate==STOP_BIT)
		{
			receiverstate = START_BIT;    //接收开始
			delay_us(BuadRate/2);		//因为这里跳转到定时器接收数据需要时间,所以节拍就不是BuadRate这个了	
			TIME3->TIME_CR1|=1<<0;		//定时器使能			
		}
     }
   }
	//清除中断挂起标志位,否则会被认为中断没有被处理而循环再次进入中断
	EXTI->PR|=1<<4;   // //清除LINE3上的中断标志位 
}

//定时器接收数据服务函数
void TIM3_IRQHandler(void)
{
	if(TIME3->TIME_SR&0x0001)	//溢出中断
	{
		TIME3->TIME_SR&=~(1<<0);  //清除中断标志
		receiverstate+=1;          //接收数据位往下移动   也就是到了数据位0 -> D0_BIT,
		if(receiverstate == STOP_BIT)  //如果是停止标志就关闭定时器接收
		{
			TIME3->TIME_CR1&=~(1<<0);	//禁用定时器
			USART_BUF[len_buf] = receiverdata;
			len_buf+=1;
			return;
		}
		if((GPIOA->GPIO_IDR&(1<<4)))  //如果是高电平
		{
			receiverdata |= (1<<(receiverstate-1));   //存放的是8位数据,要从第0位开始存储
		}
		else
		{
			receiverdata &= ~(1<<(receiverstate-1));	
		}
	}
	
}

串口调试窗口:
在这里插入图片描述

完整的程序:

TIME.h

/***************************************************************
著作人 	邓家文  广州大学华软软件学院   
文件名	:TIME.h
作者	   : 邓家文
版本	   : V1.0
描述	   : 基于中断与定时器的IO模拟驱动文件。
其他	   : 无
博客 	   : https://blog.csdn.net/morecrazylove?spm=1011.2124.3001.5343
日志	   : 初版V1.0 2021/7/12 邓家文创建
***************************************************************/
#ifndef __TIME_H
#define __TIME_H
#include "RCC.h"

#define TIME2_BASE 							(0x40000000)
#define TIME3_BASE							(0x40000400)
#define TIME4_BASE							(0x40000800)
#define TIME5_BASE							(0x40000C00)

typedef struct
{
	volatile unsigned int TIME_CR1;
	volatile unsigned int TIME_CR2;
	volatile unsigned int TIME_SMCR;
	volatile unsigned int TIME_DIER;
	volatile unsigned int TIME_SR;
	volatile unsigned int TIME_EGR;
	volatile unsigned int TIME_CCMR1;
	volatile unsigned int TIME_CCMR2;
	volatile unsigned int TIME_CCER;
	volatile unsigned int TIME_CNT;
	volatile unsigned int TIME_PSC;
	volatile unsigned int TIME_ARR;
	unsigned int SAVE1;
	volatile unsigned int TIME_CCR1;
	volatile unsigned int TIME_CCR2;
	volatile unsigned int TIME_CCR3;
	volatile unsigned int TIME_CCR4;
	unsigned int SAVE2;
	volatile unsigned int TIME_DCR;
	volatile unsigned int TMIE_DMAR;
}TIME_Type;

#define TIME2 				((TIME_Type*) TMIE2_BASE)
#define TIME3				((TIME_Type*) TIME3_BASE)
#define TIME4				((TIME_Type*) TMIE4_BASE)
#define TIME5				((TIME_Type*) TMIE5_BASE)

void TIME3_init(u16 arr,u16 psc);

#endif

TIME.c

#include "TIME.h"
#include "nvic.h"

//模拟串口初始化 TIME3   psc=71
void TIME3_init(u16 arr,u16 psc)
{
	RCC->RCC_APB1ENR|=1<<1;	//TIME3时钟使能
	TIME3->TIME_PSC=psc;	//预分频器72,得到1Mhz的计数时钟	
	TIME3->TIME_ARR=arr;	//设定计数器自动重装值//刚好1MHz
	TIME3->TIME_CR1&=~(1<<4);//计数器向上计数
	TIME3->TIME_DIER|=1<<0;	//允许更新中断(开启中断)
	TIME3->TIME_CR1&=~(1<<0);//禁用定时器
	NVIC_Init(1,3,29,2);	//抢占1,子优先级3,组2	
}

IO_SIMULATE_USART.h

IO_SIMULATE_USART.h
/***************************************************************
著作人 	邓家文  广州大学华软软件学院   
文件名	:IO_SIMULATE_USART.h
作者	   : 邓家文
版本	   : V1.0
描述	   : 定时器驱动文件。
其他	   : 无
博客 	   : https://blog.csdn.net/morecrazylove?spm=1011.2124.3001.5343
日志	   : 初版V1.0 2021/7/12 邓家文创建
***************************************************************/
#ifndef __IO_SIMULATE_USART_H
#define __IO_SIMULATE_USART_H
#include "RCC.h"
#include "gpio.h"
#include "delay.h"
#include "exti.h"

/* 用来表示数据接收的是哪一位了 */
enum{
	START_BIT,	//起始位
	D0_BIT,
	D1_BIT,
	D2_BIT,
	D3_BIT,
	D4_BIT,
	D5_BIT,
	D6_BIT,
	D7_BIT,
	STOP_BIT,		//停止位
};

void USART_IO_init(void);     //IO初始化配置
void TX_Byte(u8 data);		//一个字节发送
void TX_String(u8 *str);		//一个字符串发送

extern u8 USART_BUF[11];   //接收缓冲区
extern u8 len_buf;         //接收的数据长度

#endif

IO_SIMULATE_USART.c

#include "IO_SIMULATE_USART.h"
#include "TIME.h"

#define BuadRate		104          //设置波特率的

#define TX_Hight()		{GPIOA->GPIO_ODR|=1<<5;}			//PA5输出高电平
#define TX_Low()		{GPIOA->GPIO_ODR&=~(1<<5);}		//PA5输出低电平

u8 USART_BUF[11];   //接收缓冲区
u8 len_buf = 0;     //接收的数据长度
u8 receiverstate = STOP_BIT;  //定义状态机,检测第一次下降沿信号到来
u8 receiverdata = 0;          //接收的数据,这是一个字节8位的数据
	
void USART_IO_init(void)
{
	RCC->RCC_APB2ENR|=1<<2;      //PA时钟使能
	GPIOA->GPIO_CRL&=0xFF00FFFF;	//将要设置的位清零
	GPIOA->GPIO_CRL|=0x00300000;  //PA5推挽输出
	GPIOA->GPIO_CRL|=0X00080000;  //PA4输入模式
	GPIOA->GPIO_ODR|=1<<5;		//PA5默认高电平
	GPIOA->GPIO_ODR|=0x30;		//PA4上拉输入模式
	ex_nvic_config(0,4,1);		//设置连接中断总线,PA4,下降沿触发
	NVIC_Init(2,2,10,2);		//EXTIX-PY.X
}

/* 发送单字节数据 */
void TX_Byte(u8 data)
{
	int i=0;	
	TX_Low();	//低电平,起始位
	delay_us(BuadRate);		//节拍时间
	for(i=0;i<8;i++)
	{
		if(data & 0x01)
		{
			TX_Hight();
		}
		else
		{
			TX_Low();
		}
		delay_us(BuadRate);	//每传一个数据就需要延迟一个脉冲
		data>>=1;			//是从最低位开始传输的
	}
	TX_Hight();	//高电平,停止位
	delay_us(BuadRate);
}

/* 发送一个字符串数据 */
void TX_String(u8 *str)
{
	while(*str!=0)
	{
		TX_Byte(*str);
		str++;
	}
}

/* 中断服务函数,判断开始信号的 */
void EXTI4_IRQHandler(void)
{
   if( (EXTI->PR&(1<<4)) != 0 )     //中断事件发生,挂起寄存器置位
   {
     if(!(GPIOA->GPIO_IDR&(1<<4)))	 //下降沿低电平
     { 	
		if(receiverstate==STOP_BIT)
		{
			receiverstate = START_BIT;    //接收开始
			delay_us(BuadRate/2);		//因为这里跳转到定时器接收数据需要时间,所以节拍就不是BuadRate这个了	
			TIME3->TIME_CR1|=1<<0;		//定时器使能			
		}
     }
   }
	//清除中断挂起标志位,否则会被认为中断没有被处理而循环再次进入中断
	EXTI->PR|=1<<4;   // //清除LINE3上的中断标志位 
}

//定时器接收数据服务函数
void TIM3_IRQHandler(void)
{
	if(TIME3->TIME_SR&0x0001)	//溢出中断
	{
		TIME3->TIME_SR&=~(1<<0);  //清除中断标志
		receiverstate+=1;          //接收数据位往下移动   也就是到了数据位0 -> D0_BIT,
		if(receiverstate == STOP_BIT)  //如果是停止标志就关闭定时器接收
		{
			TIME3->TIME_CR1&=~(1<<0);	//禁用定时器
			USART_BUF[len_buf] = receiverdata;
			len_buf+=1;
			return;
		}
		if((GPIOA->GPIO_IDR&(1<<4)))  //如果是高电平
		{
			receiverdata |= (1<<(receiverstate-1));   //存放的是8位数据,要从第0位开始存储
		}
		else
		{
			receiverdata &= ~(1<<(receiverstate-1));	
		}
	}
	
}

test.c

/***************************************************************
著作人 	邓家文  广州大学华软软件学院
文件名	:test.c
作者	   : 邓家文
版本	   : V1.0
描述	   : 主函数
其他	   : 无
博客 	   : https://blog.csdn.net/morecrazylove?spm=1011.2124.3001.5343
日志	   : 初版V1.0 2021/7/12 邓家文创建
***************************************************************/
#include "gpio.h"
#include "clock.h"
#include "nvic.h"
#include "exti.h"
#include "key.h"
#include "beep.h"
#include "delay.h"
#include "IO_SIMULATE_USART.h"
#include "TIME.h"
	
int main(void)
{  
	RCC_Config(9);
	delay_init(72);
	exti_init();
	USART_IO_init();
	TIME3_init(104,71);  //1MHz , 9600=104us
	while(1)
	{
	
		if(len_buf>0)
		{
			TX_String(USART_BUF);
			len_buf = 0;
		}
	}
}

总结

谢谢大家的关注,希望给大家有所帮助。本人技术有限,写的哪里不好的地方大家多多包涵,欢迎大家提出问题,给予纠正。

  • 8
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

邓家文007

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

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

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

打赏作者

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

抵扣说明:

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

余额充值