stm32使用io口模拟串口通信

      好久没写代码了,无聊再回顾回顾hal库的开发知识,所以使用io口模拟了串口通信。使用的是stm32f103c8t6,主要使用的就是俩gpio口、系统滴答定时器Systick用来延时、TIM2用来定时采样数据。

一、GPIO初始化TX和RX

使用PA1模拟RX,PA2模拟TX,由于串口通信空闲状态下为高电平,所以输入口配置为上拉输入

/*
		Function: io模拟串口初始化函数
*/
void MY_UART_INIT(void) {
	__HAL_RCC_GPIOA_CLK_ENABLE();
	GPIO_InitTypeDef gpio_init_struct;
	//配置tx PA2
	gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;
	gpio_init_struct.Pull = GPIO_PULLUP; //上拉
	gpio_init_struct.Pin = TX_PIN;
	gpio_init_struct.Speed = GPIO_SPEED_FREQ_LOW;
	HAL_GPIO_Init(GPIOA, &gpio_init_struct);
	//配置rx PA1
	gpio_init_struct.Mode = GPIO_MODE_INPUT;
	gpio_init_struct.Pin = RX_PIN;
	HAL_GPIO_Init(GPIOA, &gpio_init_struct);
}

二、通过GPIO口模拟TX发送

发送的主要思路就是按数据帧格式发送,其中先发送起始位(低电平表示起始),然后发送8位数据位,最后发送停止位(高电平代表停止)。按照提前约定好的波特率,如本文设置1200波特率,则表示1秒发送1200个bit位,那么一个bit位的发送时间间隔就是1/1200=0.000833s,也就是833us,因此每发送一个bit位,延时833us,延时函数采用滴答定时器。

/*
		Function: 采用系统滴答定时器实现1us延时函数
*/
void delay_us(uint32_t nus) {
	HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq() / 1000000);
	HAL_Delay(nus - 1);
	HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq() / 1000);//延时完再改回去1ms
}

//系统滴答定时器的中断函数
void SysTick_Handler(void)
{
  HAL_IncTick();
}

/*
		Function: 串口发送字节函数
*/
void send_byte(uint8_t data) {
	set_tx(GPIO_PIN_RESET);//起始信号
	delay_us(833);//波特率为1200
	uint8_t count = 0;
	//低位发起
	while (count < 8) {
		if (data & 0x01)
			set_tx(GPIO_PIN_SET);
		else
			set_tx(GPIO_PIN_RESET);
		delay_us(833);
		data >>= 1;
		count++;
	}
	set_tx(GPIO_PIN_SET);//截止信号
	delay_us(833);
}

/*
		Function: 串口发送字符串函数
*/
void send_str(uint8_t *str) {
	while (*str != '\0') {
		send_byte(*str);
		str++;
	}
}

三、定时器初始化

定时器主要是用来采集RX的数据,预分频系数和重装载值根据波特率来配置,因为上面已经讲到1200波特率对应的采样间隔是833us,所以设置72分频,重装载值为833。至于计算的话,具体是:定时器2挂载在APB1总线上,时钟频率是72MHz,所以72分频后,每us计数1,重装载设置为833,就是每隔833us进入一次中断,可以在中断中对RX传输的每个bit进行采样,组合为实际的值,放入缓冲区中。

/*
		Function: 定时器2初始化
*/
void TIM2_Init(void)
{
  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 72 - 1;
  htim2.Init.Period = 833 - 1;
  HAL_TIM_Base_Init(&htim2);
	HAL_TIM_Base_Start_IT(&htim2);
}

/*
		Function: 定时器2初始化回调函数
*/
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim) {
	__HAL_RCC_TIM2_CLK_ENABLE();
	//设置定时器中断的中断优先级
	HAL_NVIC_SetPriority(TIM2_IRQn, 2, 0);
	HAL_NVIC_EnableIRQ(TIM2_IRQn);
}

四、定时器中断采样RX接收数据

设置好定时器预分频系数和重装载值后,在中断函数中采样RX每个bit,然后组合为实际的byte,放入缓冲区中。

/*
		Function: TIM2定时器中断处理函数
*/
void TIM2_IRQHandler(void) {
	HAL_TIM_IRQHandler(&htim2);
}

//在中断处理函数中,识别每个bit的位置和状态,放入局部static变量data中
//一个完整的byte接收完后,放入缓冲区cache中
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
	if (htim->Instance == TIM2) {
		uint8_t temp = HAL_GPIO_ReadPin(RX_GPIOx, RX_PIN); //读取rx线的数据
		static uint8_t data = 0;
		static uint8_t cnt = 0;
		if (state == state_stop && !temp) { //下降沿且上一帧已结束,则表示新的帧开始
			state = state_start;
		} else if (state == state_start && cnt < 8) {
			if (temp) data |= (0x01 << cnt);
			else data &= ~(0x01 << cnt);
			cnt++;
		} else if (cnt == 8 && temp) { //停止位是高电平才算正常结束
			state = state_stop;
			cache[cache_len++] = data;
			data = 0;
			cnt = 0;
		} else {
			//此帧有问题,另作处理,这里不考虑有问题...
		}
		
	}
}

五、主函数

主函数中通过io模拟串口接收上位机传来的数据,并通过io口模拟串口发送出去

/******************************************************************
author: 				luo jincheng
date: 					2024/09/08
description:		
	1:	本文件是主程序入口,使用io口模拟串口通信
	2:	程序逻辑是上位机发送数据,程序收到后原样返回
******************************************************************/

extern uint8_t cache_len;
extern uint8_t cache[200];

int main(void) {
	HAL_Init();
	SystemClock_Config();
	TIM2_Init();
	MY_UART_INIT();

	while (1) {
		//缓冲区不为空时(即接收到数据了),将缓冲区数据发送给上位机
		if (cache_len > 0) {
			for (int i = 0; i < cache_len; i++) {
				send_byte(cache[i]);
			}
			cache[0] = '\0';
			cache_len = 0;
		}
	}
}

六、测试结果

测试通过

STM32中,通过普通IO模拟串口通信,需要自己编写相关的发送和接收函数来实现数据的处理和解析。下面是一个示例代码,可以供你参考: ```c #include "stm32f1xx.h" #define UART_TX GPIO_PIN_9 // 发送引脚 #define UART_RX GPIO_PIN_10 // 接收引脚 #define BAUDRATE 9600 // 波特率 GPIO_InitTypeDef GPIO_InitStruct; void delay_us(uint32_t us) { uint32_t tickstart = HAL_GetTick(); uint32_t wait = us * (SystemCoreClock / 1000000U); while ((HAL_GetTick() - tickstart) < wait) { } } void UART_GPIO_Init(void) { __HAL_RCC_GPIOA_CLK_ENABLE(); // 使能GPIOA时钟 GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; GPIO_InitStruct.Pin = UART_TX; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // 配置发送引脚为输出模式 GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pin = UART_RX; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // 配置接收引脚为输入模式 } void UART_SendByte(uint8_t data) { uint8_t i; HAL_GPIO_WritePin(GPIOA, UART_TX, GPIO_PIN_RESET); // 发送起始位 for (i = 0; i < 8; i++) { // 按位发送数据 if (data & 0x01) { HAL_GPIO_WritePin(GPIOA, UART_TX, GPIO_PIN_SET); } else { HAL_GPIO_WritePin(GPIOA, UART_TX, GPIO_PIN_RESET); } data >>= 1; delay_us(1000000 / BAUDRATE); } HAL_GPIO_WritePin(GPIOA, UART_TX, GPIO_PIN_SET); // 发送停止位 delay_us(1000000 / BAUDRATE); } uint8_t UART_ReceiveByte(void) { uint8_t data = 0; uint8_t i; while (HAL_GPIO_ReadPin(GPIOA, UART_RX) == GPIO_PIN_RESET) { // 等待接收起始位 } delay_us(1000000 / BAUDRATE / 2); // 延时半个波特率时钟周期,等待数据位 for (i = 0; i < 8; i++) { // 按位接收数据 data >>= 1; if (HAL_GPIO_ReadPin(GPIOA, UART_RX) == GPIO_PIN_SET) { data |= 0x80; } delay_us(1000000 / BAUDRATE); } while (HAL_GPIO_ReadPin(GPIOA, UART_RX) == GPIO_PIN_SET) { // 等待接收停止位 } return data; } int main(void) { HAL_Init(); UART_GPIO_Init(); // 初始化GPIO while (1) { UART_SendByte(0xAA); // 发送数据 uint8_t data = UART_ReceiveByte(); // 接收数据 } } ``` 在这个示例代码中,我们使用GPIOA的Pin9和Pin10分别模拟串口的发送和接收。在发送数据时,我们将发送引脚拉低,并按照位的顺序依次将数据输出到发送引脚上。在接收数据时,我们轮询接收引脚的电平状态,按照位的顺序将数据接收下来。需要注意的是,由于使用IO模拟串口的收发,在高速通讯时容易出现误码和数据丢失等问题,因此需要充分测试和优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值