STM32 IO口模拟串口

本文深入探讨了串口通信的基础知识,包括波特率、起始信号、终止信号、数据大小和数据校验等关键概念,并通过STM32单片机的实例代码展示了如何模拟USART进行串口通信。同时,提到了中断、定时器和外部中断在串口通信中的应用。
摘要由CSDN通过智能技术生成

红叶何时落水

什么是串口呢?简单来说它是一种通信协议;

串口是一个泛称,UART、TTL、RS232、RS485都遵循类似的通信时序协议,因此都被通称为串口。

而对于单片机来说,串口通信遵守TTL电平标准

高电平 1 2.4V~5V

低电平 0 0V~0.5V

它可以实现两个设备之间的通信,一般来说,它是全双工通信,需要接三根线。

 便可以实现双向通信

既然他是一种通信协议,那么它有哪些协议呢?

 

1.波特率

我们知道在二进制中只有01两种状态,那么我们的发送设备发送一串二进制数00010110,之后,

接受设备接收到了这一段波形,那么该如何将其中的信息解析出来呢?

在串口中,波特率就是用来约束两者之间的协议之一。比如说,我发送了一段0和1构成的波形,这段波形我希望他有8个二进制的信息,那么我让每一个二进制信息都保持一秒钟,然后在发生变化。也就是说,00010110是一段时长为8秒的波形。前三秒为低电平,代表了000,4秒为高电平,代表了1,后面类似。

我希望这段波形中有八个信息。那么如果我想让别人知道其中的信息,那么我就得告诉别人我这段信息中,每一秒代表一个信息。那么别人在获取我这段时长为8秒的波形后,就会一秒一秒的数八次,依次找出每个二进制信息。

那么,每一秒有一个信息,这就是波特率。1秒一个信息,波特率为1;1秒9600个信息,波特率为9600;

2.起始信号与终止信号

那么,我们如果想要连续发好多个信号,我们不能把所有信息连到一起发送出去,就像我们写英语作文,不能把所有单词的字母挨得特别紧,一点空也不留。Iloveyou 与 I love you; 效果上来看后一个更好。而起始信号和终止信号就像单词之间的空格。

我们规定起始信号就是一个下降沿,即由高电平下降到低电平,后面的八位数据就是一个单词。八位数据写完后,我们将电平变为高电平,告诉别人这个单词结束了。这个高电平可由0.5、1、1.5或2个逻辑1的数据位表示,只要双方约定一致即可。

3.数据大小

通常我们规定你一次只能发送八个二进制数据,别人也只数八个,多出来的就不数了。

4.数据校验

我们如何保证我们将这段信息发送出去后,信息在传输的路上没有发生变化呢?

由于我们的信息中只有1和0两种状态,那么这就意味着1的个数可以是0~8,不是奇数就是偶数。

那么,我们可以选择奇校验 当这八个数据里由0或2或4...个1时,我们设置校验位为1,那么这九个数据里就有奇数个1。如果八个数据里1的个数本来就为奇数个,那么,校验位设置为0.保证数据里1的个数为奇数。接受者数一数1的的个数,就大概知道数据有没有被破坏。

偶校验同理。当然,我们也可以选择不校验。(反正没啥用)

知道了这些,我们便可以利用单片机IO口来模拟一个usart

打字打不动了,直接放代码吧。写一点注释算了

#include "stm32f10x.h"                  // Device header
#include "Timer.h"
#include "Delay.h"
extern u8 data;
extern u8 re_data;
int flag3 = 0;
int flag2 = 0;
int flag = 0;
void GPIO_init() {//两个IO口初始化
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_2;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	PA0 = 1;
	
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_1;//PA1
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //  
	GPIO_Init(GPIOA, &GPIO_InitStructure);//
	PA1 = 1;
}
void Timer_Init(u32 psc, u32 arr)//定时器2初始化,注意定时器2的主频为72M,原因去看时钟树
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	
	TIM_InternalClockConfig(TIM2);
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period = arr - 1;
	TIM_TimeBaseInitStructure.TIM_Prescaler = psc - 1;
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//通用定时器没有这个功能,高级定时器可以实现,2的64次方计数
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
	
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;//这里的名字加上Handler,就是中断函数名
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitStructure);
	GPIO_init();
	TIM_Cmd(TIM2, ENABLE);//使能定时器
	
}

void send_data() {
		if(flag && flag < 10) {
			TIM_ClearITPendingBit(TIM2, TIM_IT_Update);	

			PA0 = (data >> (flag - 1)) & 0x01;//发送八位数据
			flag++;
		} else if(!flag) {
			TIM_ClearITPendingBit(TIM2, TIM_IT_Update);	
			PA0 = 0;//制造起始位
			flag++;
		} else {
			PA0 = 1;//制造结束位
			flag = 0;
			TIM_Cmd(TIM2, DISABLE);
			TIM_ITConfig(TIM2,TIM_IT_Update,DISABLE);
			Delay_ms(1);//这里要加一个延时,否者会连续发送两个字节
			EXTI->IMR |= EXTI_Line1;//开启外部中断
			TIM_ClearITPendingBit(TIM2, TIM_IT_Update);	
		}
}

void recive_data() {
		int i = 0;
		int counter = 0;
		if(flag2 && flag2 < 17) {
			TIM_ClearITPendingBit(TIM2, TIM_IT_Update);	
			for(i = 0; i < 9; i++) 
				counter += GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_1);//获取引脚口状态
			re_data = (!(flag2 % 2) && flag2 < 16) ? ((counter > 3) ? (re_data|0x80)>>1 : re_data >> 1) : (flag2 == 16 && counter > 3) ? (re_data|0x80) : re_data;//通过位操作,串行八位数据
			flag2++;
		} else if(!flag2) {
			re_data = 0;
			TIM_ClearITPendingBit(TIM2, TIM_IT_Update);	
			for(i = 0; i < 3; i++) {
				if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_1)) {//判断起始位
					TIM_Cmd(TIM2, DISABLE);
					EXTI->IMR |= EXTI_Line1;
					break;
				}
			} 
			
			if(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_1))
				flag2++;
		} else {
			TIM_ClearITPendingBit(TIM2, TIM_IT_Update);	
			flag2 = 0;	
			flag3 = 1;
			PA1 = 1;
			TIM2->ARR = 750;		//寄存器改变定时器频率				
		}
}


void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
	{
		if(!flag3)
			recive_data();
		else 
			send_data();
	}
}


void EXTIX_Init(void) {
 	  EXTI_InitTypeDef EXTI_InitStructure;
 	  NVIC_InitTypeDef NVIC_InitStructure;
  	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//ÍⲿÖжϣ¬ÐèҪʹÄÜAFIOʱÖÓ
	
    //GPIOA.1	  
  	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource1);

   	EXTI_InitStructure.EXTI_Line=EXTI_Line1;
  	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;	
  	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//下降沿触发
  	EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  	EXTI_Init(&EXTI_InitStructure);	
	
	NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;			
  	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;	
  	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;					
  	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;								
  	NVIC_Init(&NVIC_InitStructure);  	 

}
void EXTI1_IRQHandler(void)
{
	TIM2->ARR = 375;
	flag3 = 0;
	flag2 = 0;
	TIM_Cmd(TIM2, ENABLE);//使能定时器
	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//开启定时器中断
	EXTI->IMR &= ~(EXTI_Line1);//寄存器关闭外部中断
	EXTI_ClearITPendingBit(EXTI_Line1);  
}

注意的点

寄存器操作改变引脚口状态,三种不同方式的优缺点

定时器主频

中断的开启与关闭

数据的|与&以及<<操作

寄存器改变定时器频率

定时器重复技术计数功能

以下是使用STM32IO模拟串口发送和接收程序的示例代码: ```c #include "stm32f10x.h" #define UART_TX GPIO_Pin_0 #define UART_RX GPIO_Pin_1 #define TX_PORT GPIOA #define RX_PORT GPIOA #define BAUD_RATE 9600 void delay_us(uint32_t us) { uint32_t i = 0; for(i = 0; i < us * 8; i++); } void delay_ms(uint32_t ms) { uint32_t i = 0; for(i = 0; i < ms * 8000; i++); } void GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = UART_TX; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(TX_PORT, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = UART_RX; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(RX_PORT, &GPIO_InitStructure); } void USART_SendByte(uint8_t byte) { uint8_t i = 0; GPIO_ResetBits(TX_PORT, UART_TX); delay_us(1000000 / BAUD_RATE); for(i = 0; i < 8; i++) { if(byte & 0x01) { GPIO_SetBits(TX_PORT, UART_TX); } else { GPIO_ResetBits(TX_PORT, UART_TX); } byte >>= 1; delay_us(1000000 / BAUD_RATE); } GPIO_SetBits(TX_PORT, UART_TX); delay_us(1000000 / BAUD_RATE); } uint8_t USART_ReceiveByte(void) { uint8_t byte = 0; uint8_t i = 0; while(GPIO_ReadInputDataBit(RX_PORT, UART_RX) == Bit_RESET); delay_us(1000000 / BAUD_RATE / 2); for(i = 0; i < 8; i++) { byte >>= 1; if(GPIO_ReadInputDataBit(RX_PORT, UART_RX) == Bit_SET) { byte |= 0x80; } delay_us(1000000 / BAUD_RATE); } return byte; } int main(void) { uint8_t data = 0; GPIO_Configuration(); while(1) { USART_SendByte(0xAA); delay_ms(1000); data = USART_ReceiveByte(); } } ``` 在这个例子中,我们使用PA0和PA1两个IO分别模拟串口发送和接收。首先我们需要配置GPIO的模式和速度,然后使用USART_SendByte函数向串口发送一个字节,使用USART_ReceiveByte函数从串口接收一个字节。在发送一个字节时,我们先将TX设置为低电平,然后延时一段时间,然后依次发送8个位,每个位之间都需要延时一段时间,最后将TX设置为高电平。在接收一个字节时,我们首先等待RX为高电平,然后延时一半的位时间,依次接收8个位,每个位之间都需要延时一段时间,并将接收到的位组合成一个字节返回。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

红叶落水

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

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

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

打赏作者

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

抵扣说明:

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

余额充值