STM32F407普通IO口模拟串口实现不定长数据收发

这篇具有很好参考价值的文章主要介绍了STM32F407普通IO口模拟串口实现不定长数据收发。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


前言

因为项目中用到的串口比较多,STM32F407VET6自带的串口不够用了,所以只能考虑用模拟串口来实现功能。普通的IO口来模拟串口需要先了解串口的时序图,需要用到两个IO引脚即收发引脚,两个定时器,一个用于发送延时使用,一个用于产生中断接收数据。代码的初始化主要用CubeMx自动生成,功能代码自己完成。下面一起来看看吧。

目录

前言

一、普通IO模拟串口原理

二、实际应用

1.STM32CubeMx初始化

2.数据发送和接收功能代码

测试功能代码

一、普通IO模拟串口原理

单片机普通io模拟串口的关键在于弄清楚串口的时序图,也是普通IO需要严格的遵循串口协议规则,串口时序图如下图所示。

模拟串口,stm32,单片机,c语言

 一个起始位是从高电平到低电平,中间八位是数据位和奇偶校验位 ,第十位是停止位,这样就接收了一个字节的数据。

        模拟串口还有最重要的一点是串口波特率,一般常见的有115200和9600,当波特率是115200时,发送1bit数据需要 1/115200 = 8.68us;所以,根据协议每一位数据传输电平持续8.68us,当波特率是9600时,发送1bit数据需要 1/9600= 104us;所以,根据协议每一位数据传输电平持续104us。

        在接收的地方,通过中断来触发启动,然后启动一个定时器,根据波特率的电平持续时间,在电平的中间去检测,是高电平还是低电平,从而确定bit的值。所以,检测到下降沿后延时一下,再启动定时器,定时去判断IO的高低。

模拟串口,stm32,单片机,c语言

 此处参考:48 STM32普通IO模拟usart串口_stm32io口模拟uart_Chasing_Chasing的博客-CSDN博客

二、实际应用

1.STM32CubeMx初始化

首先是选择STM32F407VET6芯片,然后配置始终和需要的IO口和定时器

我配置的主时钟比较高,为168MHz

模拟串口,stm32,单片机,c语言

 下面是两个定时器,定时器6是用来发送延时的,定时器7是经过104us产生中断接受数据电平的

模拟串口,stm32,单片机,c语言

模拟串口,stm32,单片机,c语言

下面配置两个普通的IO口,我配置的是PF3和PF4, PF3用来发送数据,PF4用来接收数据,PF4需要下降触发中断,下面细讲。

模拟串口,stm32,单片机,c语言

模拟串口,stm32,单片机,c语言

 配置完以后记得使能相应的中断,定时器7的中断和PF4的下降沿中断。

模拟串口,stm32,单片机,c语言

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 */
  }


测试功能代码

模拟串口,stm32,单片机,c语言

 我测试使用的是正点原子的探索者开发板STM32F407ZGT6,连好线下载好程序后,打开串口助手进行不定长数据的收发测试。

下面是测试发送函数

模拟串口,stm32,单片机,c语言

下面是测试将接收到的数据发送回来 

模拟串口,stm32,单片机,c语言

到这里整个模拟串口串口就结束了,希望对你有帮助,也希望共同成长,一起建设我们伟大的祖国!

源码下载: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模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • stm32f407探索者开发板(十四)——IO引脚复用和映射

    端口复用和重映射都是和单片机的I/O口有关系,端口复用是将一个I/O赋予多个功能,通过设置I/O的工作模式来切换不同的功能。重映射是将某些I/O口上面的功能映射到其他I/O口上面去。但是注意一点:重映射的I/O都是厂家设置好的,不能自己更改。 端口复用 什么是端口复用

    2024年02月16日
    浏览(15)
  • 关于STM32F407ZGT6的USB损坏后使用ST-Link和USART1实现串口功能

    开发板:STM32F407ZGT6; 目标:想使用软件“串口调试助手” 情况:开发板上的USB_UART口所在器件损坏或者直接没有;   解决办法:查看该开发板的原理图,可得:串口1的RX接TXD,串口1的TX接RXD,那么按如下步骤操作: 1、现在使用USB转TTL模块,将串口1的RX接USB转TTL模块的TXD,

    2024年02月08日
    浏览(20)
  • STM32F407 ADC+DMA+定时器 定时采样模拟量

    项目中需要对多个通道的电压进行一定频率的AD采样,由于采样过程贯穿整个任务,为了使采样过程尽可能不占用CPU资源,采用定时器触发的多通道ADC扫描采样,且采样数据由DMA传到RAM中的缓存。 这样做有以下几个好处:1、由定时器触发ADC采样,这样采样的频率可控,且定时

    2024年02月14日
    浏览(20)
  • 关于STM32F407ZGT6的USB_UART端口损坏后使用ST-Link和USART1实现串口功能

    开发板:STM32F407ZGT6; 目标:想使用软件“串口调试助手” 情况:开发板上的USB_UART口所在器件损坏或者直接没有;   解决办法:查看该开发板的原理图,可得:串口1的RX接TXD,串口1的TX接RXD,那么按如下步骤操作: 1、现在使用USB转TTL模块,将串口1的RX接USB转TTL模块的TXD,

    2024年02月07日
    浏览(18)
  • stm32F407学习DAY.14 在DMA模式下进行USART串口数据收发(正点原子例程为例)

    目录 一、DMA配置 1、DMA1和DMA2的请求映射 2、DMA挂载总线 3、DMA相关库函数 ​4、DMA配置过程(以串口1为例) 1)进行时钟使能 2)等待DMA可配置 3)初始化DMA(串口1的TX为DMA2 数据流7 通道4,RX为DMA2 数据流5 通道4) a.DMA外设地址par: b.DMA存储器0地址mar: c.数据传输量ndtr: 4)

    2024年02月04日
    浏览(22)
  • STM32F407实现1588v2(ptpd)

    硬件: STM32F407ZGT6开发板 软件: VSCode arm-none-eabi-gcc openOCD st-link 在github搜到一个在NUCLEO-F429ZI开发板上移植ptpd的example,因为和F407差别很小,所以就打算用这个demo移植到手头的开发板上。因为目前只需要slave,所以只调试了slave。据介绍,master好像原作者没有充分测试过。 源项

    2024年02月08日
    浏览(15)
  • 基于STM32F407实现超声波测距(SR04)

    今天要实现的功能是超声波测距,这一功能在很多的地方都能用到,比如:在智能小车上可以添加超声波避障功能。今天需要用到SR04超声波模块,在使用这一模块的时候我很会接触到时序图。 模块如图所示: 模块有四个引脚 VCC 供 5V电源, GND 为地线, TRIG 触 发 控 制 信 号

    2024年02月11日
    浏览(23)
  • 从STM32F407到AT32F407(一)

    雅特力公司的MCU有着性能超群,价格优越的巨大优势,缺点是相关资料少一些,我们可以充分利用ST的现有资源来开发它。 我用雅特力的STM32F437开发板,使用原子 stm32f407的开发板自带程序,测试串口程序,原设定串口波特率为115200,但是输出乱码,波特率改成230400,串口输

    2024年02月02日
    浏览(29)
  • 利用是stm32cubemx实现双极性spwm调制 基于stm32f407vet6

    【双极性SPWM调制讲解以及基于stm32的代码生成-哔哩哔哩】 https://b23.tv/ytFxdkL 双极性spwm调制虽然没有单极性好用,但比单极性要简单易懂一些,以下教程是如何利用stm32实现双极性spwm调制.   •T1,T2不能同时导通,T3,T4也不能同时导通,否则短路烧管子。 •解决方法:T1与T2用高

    2024年02月15日
    浏览(20)
  • RT-Thread使用PWM实现灯亮度调节——STM32F407

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 RT-Thread使用PWM实现灯亮度调节——STM32F407ZG 作为新入门的嵌入式选手,最近在学习RT-Thread操作系统,鉴于自己健忘的记性,打算记录下来后面好回顾学习。 今天要总结的是RT-Thread使用PWM实现灯亮度调节

    2024年02月15日
    浏览(14)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包