前言
因为项目中用到的串口比较多,STM32F407VET6自带的串口不够用了,所以只能考虑用模拟串口来实现功能。普通的IO口来模拟串口需要先了解串口的时序图,需要用到两个IO引脚即收发引脚,两个定时器,一个用于发送延时使用,一个用于产生中断接收数据。代码的初始化主要用CubeMx自动生成,功能代码自己完成。下面一起来看看吧。
目录
前言
一、普通IO模拟串口原理
二、实际应用
1.STM32CubeMx初始化
2.数据发送和接收功能代码
测试功能代码
一、普通IO模拟串口原理
单片机普通io模拟串口的关键在于弄清楚串口的时序图,也是普通IO需要严格的遵循串口协议规则,串口时序图如下图所示。
一个起始位是从高电平到低电平,中间八位是数据位和奇偶校验位 ,第十位是停止位,这样就接收了一个字节的数据。
模拟串口还有最重要的一点是串口波特率,一般常见的有115200和9600,当波特率是115200时,发送1bit数据需要 1/115200 = 8.68us;所以,根据协议每一位数据传输电平持续8.68us,当波特率是9600时,发送1bit数据需要 1/9600= 104us;所以,根据协议每一位数据传输电平持续104us。
在接收的地方,通过中断来触发启动,然后启动一个定时器,根据波特率的电平持续时间,在电平的中间去检测,是高电平还是低电平,从而确定bit的值。所以,检测到下降沿后延时一下,再启动定时器,定时去判断IO的高低。
此处参考:48 STM32普通IO模拟usart串口_stm32io口模拟uart_Chasing_Chasing的博客-CSDN博客
二、实际应用
1.STM32CubeMx初始化
首先是选择STM32F407VET6芯片,然后配置始终和需要的IO口和定时器
我配置的主时钟比较高,为168MHz
下面是两个定时器,定时器6是用来发送延时的,定时器7是经过104us产生中断接受数据电平的
下面配置两个普通的IO口,我配置的是PF3和PF4, PF3用来发送数据,PF4用来接收数据,PF4需要下降触发中断,下面细讲。
配置完以后记得使能相应的中断,定时器7的中断和PF4的下降沿中断。
2.数据发送和接收功能代码
数据发送主要是根据串口时序图进行发送相应的电平位,iousart.h代码如下:
#ifndef __IOUSART_H_ #define __IOUSART_H_ #include "stm32f4xx_hal.h" #include "gpio.h" //#include "sys.h" //#include "delay.h" //定义通信波特率 #define BaudRate_9600 104 //1000000us/9600=104.1666 发送1个位所需要的时间 //GPIO TX脚宏定义 #define iouart1_TXD(n) if(n) HAL_GPIO_WritePin(GPIOF, GPIO_PIN_3, GPIO_PIN_SET); \ else HAL_GPIO_WritePin(GPIOF, GPIO_PIN_3, GPIO_PIN_RESET); //GPIO RX脚宏定义 #define iouart1_RXD() HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_4) #define SUartLength 200 //模拟串口缓冲区长度 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_delayUs(volatile uint32_t nTime); void iouart1_delayUs_104(void); void S_Uart_Send_Buff(uint8_t *buff,uint8_t length); void S_Uart_Send_Str(uint8_t *str); void S_Uart_One_Tx(uint8_t Data); uint8_t S_Uart_Rx_Handler(uint8_t *buf,uint8_t *length); #endif /* __LED_H */
iousart.c代码如下:#include <stdio.h> #include "iousart.h" #include "tim.h" #include "string.h" uint32_t recvData = 0; uint8_t recvStat = COM_STOP_BIT; uint8_t SUartCnt; //模拟串口缓冲区长位置 uint8_t SUartBuff[SUartLength]; uint8_t len; uint8_t buff[200]; //模拟串口缓冲区 /*************************************** * 函 数 名: Delay_Ms * 功能说明: 延时 * 形 参:nTime,单位为uS * 返 回 值: 无 ****************************************/ void iouart1_delayUs(volatile uint32_t nTime) { uint16_t tmp; tmp = __HAL_TIM_GetCounter(&htim6); //获得 TIM6 计数器的值 if(tmp + nTime <= 65535) while( (__HAL_TIM_GetCounter(&htim6) - tmp) < nTime ); else { __HAL_TIM_SetCounter(&htim6,0); //设置 TIM3 计数器寄存器值为0 while( __HAL_TIM_GetCounter(&htim6) < nTime ); } } /***************************************** * 函 数 名: iouart1_SendByte * 功能说明: 模拟串口发送一字节数据 * 形 参:无 * 返 回 值: 无 ******************************************/ //模拟串口发送一个字节 void S_Uart_One_Tx(uint8_t Data) { uint8_t i; iouart1_TXD(0); //起始位 iouart1_delayUs(104); for(i=0; i<8; i++) { if(Data & 0x01) //串口协议 先发LSB { iouart1_TXD(1); } else { iouart1_TXD(0); } iouart1_delayUs(104); Data >>= 1; } iouart1_TXD(1); //结束标志位 iouart1_delayUs(104); } //模拟串口发送数组 void S_Uart_Send_Buff(uint8_t *buff,uint8_t length) { for(uint8_t i=0; i<length; i++) { S_Uart_One_Tx(buff[i]); } } //模拟串口发送字符串 void S_Uart_Send_Str(uint8_t *str) { // uint32_t i = 0; uint32_t len = strlen((const char*)str); for(uint8_t i=0; i<len; i++) { S_Uart_One_Tx(str[i]); } } //接收串口数据处理 uint8_t S_Uart_Rx_Handler(uint8_t *buf,uint8_t *length) { *length = 0; if(SUartCnt > 0) //模拟串口缓冲区不为空 { *length = SUartCnt; memcpy(buf,SUartBuff,*length); //将SUartBuff里面的数据复制到buf里面,长度为*length SUartCnt = 0; // memset(SUartBuff,0x00,256); //清除缓存 } return *length; }
数据接收是下降沿触发中断后,先延时52us再开始进行数据接收,这样可以使得每次读取到的电平处于中间,有利于数据的准确性,触发中断后在中断回调函数中延时52us后关闭外部中断并开启定时器7进行104us的数据位读取,等到读取到停止位的时候,开启外部中断并关闭定时器7,进行数据存储。外部中断处理函数和定时器中断回调函数代码如下:void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if(GPIO_Pin == GPIO_PIN_4) { if(iouart1_RXD() == 0) { if(recvStat == COM_STOP_BIT) { HAL_NVIC_DisableIRQ(EXTI4_IRQn); //关闭下降沿中断 recvStat = COM_START_BIT; //延时52us iouart1_delayUs(52); //延时超过检测电平的正中间 HAL_TIM_Base_Start_IT(&htim7); //开启定时器7 } } } }
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { /* USER CODE BEGIN Callback 0 */ /* USER CODE END Callback 0 */ if (htim->Instance == TIM1) { HAL_IncTick(); } /* USER CODE BEGIN Callback 1 */ if(htim->Instance == htim7.Instance) { recvStat++; if(recvStat == COM_STOP_BIT)//COM_D7_BIT { //到这里接收完成一个字节数据 HAL_NVIC_EnableIRQ(EXTI4_IRQn);//开启下降沿中断 HAL_TIM_Base_Stop(&htim7);//关闭定时器 __HAL_TIM_SetCounter(&htim7,0);//定时器清零 if(SUartCnt < SUartLength) SUartBuff[SUartCnt++] = recvData; //存入缓冲区 else SUartCnt = 0; recvData =0; return; } if(iouart1_RXD()) { recvData |= (1 << (recvStat - 1)); }else{ recvData &= ~(1 << (recvStat - 1)); } } /* USER CODE END Callback 1 */ }
经过接收串口数据处理函数对数据的处理,在主函数main里面进行测试
。
uint8_t S_Uart_Rx_Handler(uint8_t *buf,uint8_t *length)
while (1)
{
if(S_Uart_Rx_Handler(buff,&len))
{
S_Uart_Send_Buff(buff,len); //将收到的数据发送出去
memset(buff,0x00,200); //清除缓存
}
// S_Uart_One_Tx('a');
// S_Uart_Send_Buff(p_Arr1,sizeof(p_Arr1) / sizeof(p_Arr1[0]));
// S_Uart_Send_Str(AT_CMD_softAP);HAL_Delay(100);
/* USER CODE END WHILE *//* USER CODE BEGIN 3 */
}
测试功能代码
我测试使用的是正点原子的探索者开发板STM32F407ZGT6,连好线下载好程序后,打开串口助手进行不定长数据的收发测试。
下面是测试发送函数
下面是测试将接收到的数据发送回来
到这里整个模拟串口串口就结束了,希望对你有帮助,也希望共同成长,一起建设我们伟大的祖国!文章来源:https://www.toymoban.com/news/detail-729652.html
源码下载:https://download.csdn.net/download/weixin_64705314/87894084?spm=1001.2014.3001.5503文章来源地址https://www.toymoban.com/news/detail-729652.html
到了这里,关于STM32F407普通IO口模拟串口实现不定长数据收发的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!