实例讲解STM32通过IO模拟串口通信

一起学嵌入式 2023-07-19 07:50

扫描关注一起学嵌入式,一起学习,一起成长

在项目中需要用到多路的串口使用,而自己的单片机目前来讲没法满足我们项目所需要的串口需求,因此要对普通的GPIO进行转换为UART进行使用。从而使得我们单片机能够得到多一路的串口。

既然我们要进行对串口的模拟,因此我们要先了解uart相关的通信协议。通过分析波形,透彻理解 UART 通信,这篇文章进行了详细介绍,此处不再赘述。

UART的通信方式是由1个起始位,8个数据位,包含一个奇偶校验位,和结束位构成。因此我们将使用单片机中的两个普通的IO口电平的高低进行对相应时序的模拟。

接下来,让我们一起学习如何实现IO模拟串口通信。

添加头文件

首先我们先添加相应的头文件。

#include "stm32f10x.h"
#include "vuart2.h"

宏定义

使用到的io口为
#define OI2_TXD PDout(6)
#define OI2_RXD PDin(7)

#define BuadRate2_9600 104 
#define Recive2_Byte 19 //接收缓冲器的个数
u8 len2 = 0//接收计数
u8 USART2_buf[Recive2_Byte];  //接收缓冲区

将IO口相应的位带操作函数进行宏定义从而使得在对不同的电平的进行转换的时候更为方便,并且减少了调用其他函数的过程所消耗的时间,程序执行效率更高。

在本次的传输过程中我选用的是使用波特率速率为9600,也就是1s中发送9600个字节,因此对每个字节数据进行计算1000000us/9600可以得出,发一个字节的数据需要进行大概需要 104.16us并且对于相应的电平持续时间要求误差不能超过±5%因此对我们进行时间的控制要求就显得比较重要了。

枚举出各个位

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,
};

u8 recvStat2 = COM_STOP_BIT;
u8 recvData2 = 0;

IO——TXD进行模拟

void IO2_TXD(u8 Data)
{
    u8 i = 0;
    OI2_TXD = 0;  
    delay_us(BuadRate2_9600);
    for(i = 0; i < 8; i++)
    {
        if(Data&0x01)
            OI2_TXD = 1;  
        else
            OI2_TXD = 0;  
        
        delay_us(BuadRate2_9600);
        Data = Data>>1;
    }
    OI2_TXD = 1;
    delay_us(BuadRate2_9600);
}

由于发送的信号是将TXD信号进行拉低处理,因此在拉低TXD相应的IO口之后进行延时处理,再进行循环对我们需要发送的各个位的数据继续进行发送循环发送完成之后将电平拉高代表停止位。

构建发送函数

void USART2_Send(u8 *buf, u8 len2)
{
    u8 t;
    for(t = 0; t < len2; t++)
    {
        IO2_TXD(buf[t]);
    }
}

其中的*buf为需要发送的数据,len2为数据长度,进行循环调用IO_TXD进行一个字节一个字节的数据发送。

IO口初始化

void IO2Config(void)
 {
    GPIO_InitTypeDef  GPIO_InitStructure;//初始化gpio
    NVIC_InitTypeDef NVIC_InitStructure;//中断初始化函数
     EXTI_InitTypeDef EXTI_InitStruct;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOC, ENABLE);  //使能PD,PC端口时钟 
     
     //SoftWare Serial TXD
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;       //选择io口6
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;    //推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   //IO口速度为50MHz  
    GPIO_Init(GPIOD, &GPIO_InitStructure);       
    GPIO_SetBits(GPIOD,GPIO_Pin_6);       //TXD默认电平拉高
     
    //SoftWare Serial RXD
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  
    GPIO_Init(GPIOD, &GPIO_InitStructure);  

    GPIO_EXTILineConfig(GPIO_PortSourceGPIOD, GPIO_PinSource7);  //对D7的下降沿进行中断采样,当接收到下降沿时代表接收到数据触发中断处理函数
    EXTI_InitStruct.EXTI_Line = EXTI_Line7;//用到了中断7
    EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt;
    EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Falling; //下降沿触发中断
    EXTI_InitStruct.EXTI_LineCmd=ENABLE;
    EXTI_Init(&EXTI_InitStruct);//初始化中断


    NVIC_InitStructure.NVIC_IRQChannel= EXTI9_5_IRQn ; //中断发生于9-5的中断之中
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2
    NVIC_InitStructure.NVIC_IRQChannelSubPriority =2;  
    NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;  
    NVIC_Init(&NVIC_InitStructure);  
}

定时器初始化

void TIM5_Int_Init(u16 arr,u16 psc)
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); //时钟使能
    
    //定时器TIM5初始化
    TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
    TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
    TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
    TIM_ClearITPendingBit(TIM5, TIM_FLAG_Update);
    TIM_ITConfig(TIM5,TIM_IT_Update,ENABLE ); //使能指定的TIM5中断,允许更新中断

    //中断优先级NVIC设置
    NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;  //TIM5中断
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  //先占优先级1级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;  //从优先级1级
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
    NVIC_Init(&NVIC_InitStructure);  //初始化NVIC寄存器    
}

对TIM5进行初始化操作使得定时器可以检测到各个位的电平持续性时间从而对接收到的数据进行分析。计时结束后进入中断TIM5处理。

外部中断处理函数

void EXTI9_5_IRQHandler(void)
{
    if(EXTI_GetFlagStatus(EXTI_Line7) != RESET)//对中断标志位进行采集
    {
        if(OI2_RXD == 0
        {
            if(recvStat2 == COM_STOP_BIT)
            {
                recvStat2 = COM_START_BIT;//将当前的状态设置为开始位
                TIM_Cmd(TIM5, ENABLE);//开启定时器计数
            }
        }
        EXTI_ClearITPendingBit(EXTI_Line7); //清除中断标志
    }
}

定时器中断处理函数

void TIM5_IRQHandler(void)
{  
    if(TIM_GetFlagStatus(TIM5, TIM_FLAG_Update) != RESET)
    {
        TIM_ClearITPendingBit(TIM5, TIM_FLAG_Update); //清除中断标志位
        recvStat2++; //将位置移动到第一位的数据
        if(recvStat2 == COM_STOP_BIT)//当运行到停止位时进入
        {
            TIM_Cmd(TIM5, DISABLE);//停止tim5
            USART2_buf[len2++] = recvData2;//将采集到的各个数据传递给USART2_buf
            if(len2 > Recive2_Byte-1)//将数据通过回显到串口调试助手中
            {
                len2 = 0;
                USART2_Send(USART2_buf,Recive2_Byte);
            }
            return;
        }
        if(OI2_RXD)//采集RXD各个电平
        {
            recvData2 |= (1 << (recvStat2 - 1));
        }else{
            recvData2 &= ~(1 << (recvStat2 - 1));
        } 
  }  
}

整体代码

vuart2.c

#include "stm32f10x.h"
#include "vuart2.h"
/**
*软件串口的实现(IO模拟串口)
* 波特率:9600    1-8-N
* TXD : PD6
* RXD : PD7
* 使用外部中断对RXD的下降沿进行触发,使用定时器5按照9600波特率进行定时数据接收。
* Demo功能: 接收11个数据,然后把接收到的数据发送出去
*/



#define OI2_TXD PDout(6)
#define OI2_RXD PDin(7)

#define BuadRate2_9600 104 
#define Recive2_Byte 19 //接收缓冲器的个数
u8 len2 = 0//接收计数
u8 USART2_buf[Recive2_Byte];  //接收缓冲区

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,
};

u8 recvStat2 = COM_STOP_BIT;
u8 recvData2 = 0;

void IO2_TXD(u8 Data)
{
    u8 i = 0;
    OI2_TXD = 0;  
    delay_us(BuadRate2_9600);
    for(i = 0; i < 8; i++)
    {
        if(Data&0x01)
            OI2_TXD = 1;  
        else
            OI2_TXD = 0;  
        
        delay_us(BuadRate2_9600);
        Data = Data>>1;
    }
    OI2_TXD = 1;
    delay_us(BuadRate2_9600);
}
    
void USART2_Send(u8 *buf, u8 len2)
{
    u8 t;
    for(t = 0; t < len2; t++)
    {
        IO2_TXD(buf[t]);
    }
}
    
 void IO2Config(void)
 {
    GPIO_InitTypeDef  GPIO_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
     EXTI_InitTypeDef EXTI_InitStruct;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOC, ENABLE);  //使能PB,PC端口时钟 
     
     //SoftWare Serial TXD
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;     
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;    //推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   //IO口速度为50MHz  
    GPIO_Init(GPIOD, &GPIO_InitStructure);       
    GPIO_SetBits(GPIOD,GPIO_Pin_6);       
     
    //SoftWare Serial RXD
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  
    GPIO_Init(GPIOD, &GPIO_InitStructure);  

    GPIO_EXTILineConfig(GPIO_PortSourceGPIOD, GPIO_PinSource7);
    EXTI_InitStruct.EXTI_Line = EXTI_Line7;
    EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt;
    EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Falling; //下降沿触发中断
    EXTI_InitStruct.EXTI_LineCmd=ENABLE;
    EXTI_Init(&EXTI_InitStruct);


    NVIC_InitStructure.NVIC_IRQChannel= EXTI9_5_IRQn ; 
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2
    NVIC_InitStructure.NVIC_IRQChannelSubPriority =2;  
    NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;  
    NVIC_Init(&NVIC_InitStructure);  
}
 
void TIM5_Int_Init(u16 arr,u16 psc)
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); //时钟使能
    
    //定时器TIM5初始化
    TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
    TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
    TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
    TIM_ClearITPendingBit(TIM5, TIM_FLAG_Update);
    TIM_ITConfig(TIM5,TIM_IT_Update,ENABLE ); //使能指定的TIM5中断,允许更新中断

    //中断优先级NVIC设置
    NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;  //TIM4中断
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  //先占优先级1级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;  //从优先级1级
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
    NVIC_Init(&NVIC_InitStructure);  //初始化NVIC寄存器    
}
void EXTI9_5_IRQHandler(void)
{
    if(EXTI_GetFlagStatus(EXTI_Line7) != RESET)
    {
        if(OI2_RXD == 0
        {
            if(recvStat2 == COM_STOP_BIT)
            {
                recvStat2 = COM_START_BIT;
                TIM_Cmd(TIM5, ENABLE);
            }
        }
        EXTI_ClearITPendingBit(EXTI_Line7);
    }
}

void TIM5_IRQHandler(void)
{  
    if(TIM_GetFlagStatus(TIM5, TIM_FLAG_Update) != RESET)
    {
        TIM_ClearITPendingBit(TIM5, TIM_FLAG_Update); 
        recvStat2++;
        if(recvStat2 == COM_STOP_BIT)
        {
            TIM_Cmd(TIM5, DISABLE);
            USART2_buf[len2++] = recvData2;
        if(len2 > Recive2_Byte-1)
        {
            len2 = 0;
            USART2_Send(USART2_buf,Recive2_Byte);
        }
            return;
        }
        if(OI2_RXD)
        {
            recvData2 |= (1 << (recvStat2 - 1));
        }else{
            recvData2 &= ~(1 << (recvStat2 - 1));
        } 
  }  
}

vuart2.h

#ifndef __VUART2__H
#define __VUART2__H
#include "stm32f10x.h"

void IO2_TXD(u8 Data);
void USART2_Send(u8 *buf, u8 len);
void IO2Config(void);
void TIM5_Int_Init(u16 arr,u16 psc);
#endif

原文:https://blog.csdn.net/weixin_48426161/article/details/125905515?spm=1001.2014.3001.5501

文章来源于网络,版权归原作者所有,如有侵权,请联系删除。



扫码,拉你进高质量嵌入式交流群


关注我【一起学嵌入式】,一起学习,一起成长。


觉得文章不错,点击“分享”、“”、“在看” 呗!

一起学嵌入式 公众号【一起学嵌入式】,RTOS、Linux编程、C/C++,以及经验分享、行业资讯、物联网等技术知
评论 (0)
  •   通用装备论证与评估系统平台解析   北京华盛恒辉通用装备论证与评估系统平台是服务军事装备全生命周期管理的综合性信息化平台,通过科学化、系统化手段,实现装备需求论证、效能分析等核心功能,提升装备建设效益。   应用案例   目前,已有多个通用装备论证与评估系统在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润通用装备论证与评估系统。这些成功案例为通用装备论证与评估系统的推广和应用提供了有力支持。   一、系统分层架构   (一)数据层   整合装备性能、作战、试验等多源异
    华盛恒辉l58ll334744 2025-04-24 16:14 76浏览
  •   电磁频谱数据综合管理平台系统解析   一、系统定义与目标   北京华盛恒辉电磁频谱数据综合管理平台融合无线传感器、软件定义电台等前沿技术,是实现无线电频谱资源全流程管理的复杂系统。其核心目标包括:优化频谱资源配置,满足多元通信需求;运用动态管理与频谱共享技术,提升资源利用效率;强化频谱安全监管,杜绝非法占用与干扰;为电子战提供频谱监测分析支持,辅助作战决策。   应用案例   目前,已有多个电磁频谱数据综合管理平台在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润电磁频谱数
    华盛恒辉l58ll334744 2025-04-23 16:27 190浏览
  • 前言本文主要演示基于TL3576-MiniEVM评估板HDMI OUT、DP 1.4和MIPI的多屏同显、异显方案,适用开发环境如下。Windows开发环境:Windows 7 64bit、Windows 10 64bitLinux开发环境:VMware16.2.5、Ubuntu22.04.5 64bitU-Boot:U-Boot-2017.09Kernel:Linux-6.1.115LinuxSDK:LinuxSDK-[版本号](基于rk3576_linux6.1_release_v
    Tronlong 2025-04-23 13:59 134浏览
  •   高海拔区域勤务与装备保障调度系统平台解析   北京华盛恒辉高海拔区域勤务与装备保障调度系统平台专为高海拔特殊地理环境打造,致力于攻克装备适应、人员健康保障、物资运输及应急响应等难题。以下从核心功能、技术特点、应用场景及发展趋势展开全面解读。   应用案例   目前,已有多个高海拔区域勤务与装备保障调度系统在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润高海拔区域勤务与装备保障调度系统。这些成功案例为高海拔区域勤务与装备保障调度系统的推广和应用提供了有力支持。   一、核心
    华盛恒辉l58ll334744 2025-04-24 10:13 88浏览
  •   陆地装备体系论证与评估综合平台系统解析   北京华盛恒辉陆地装备体系论证与评估综合平台系统是契合现代军事需求而生的专业系统,借助科学化、智能化手段,实现对陆地装备体系的全方位论证与评估,为军事决策和装备发展提供关键支撑。以下从功能、技术、应用及展望展开分析。   应用案例   目前,已有多个陆地装备体系论证与评估综合平台在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润陆地装备体系论证与评估综合平台。这些成功案例为陆地装备体系论证与评估综合平台的推广和应用提供了有力支持。
    华盛恒辉l58ll334744 2025-04-24 10:53 100浏览
  •   航空兵训练与战术对抗仿真平台系统解析   北京华盛恒辉航空兵训练与战术对抗仿真平台系统是现代军事训练的关键工具,借助计算机技术构建虚拟战场,支持多兵种协同作战模拟,为军事决策、训练及装备研发提供科学依据。   应用案例   目前,已有多个航空兵训练与战术对抗仿真平台在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润航空兵训练与战术对抗仿真平台。这些成功案例为航空兵训练与战术对抗仿真平台的推广和应用提供了有力支持。   一、系统架构与核心功能   系统由模拟器、计算机兵力生
    华盛恒辉l58ll334744 2025-04-24 16:34 87浏览
  •   海上训练与保障调度指挥平台系统解析   北京华盛恒辉海上训练与保障调度指挥平台系统是现代海上作战训练的核心枢纽,融合信息技术、GIS、大数据及 AI 等前沿技术,旨在实现海上训练高效组织、作战保障科学决策。以下从架构功能、应用场景、系统优势及发展挑战展开解读。   应用案例   目前,已有多个海上训练与保障调度指挥平台在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润海上训练与保障调度指挥平台。这些成功案例为海上训练与保障调度指挥平台的推广和应用提供了有力支持。   一
    华盛恒辉l58ll334744 2025-04-24 15:26 76浏览
  •   后勤实验仿真系统平台深度解析   北京华盛恒辉后勤实验仿真系统平台依托计算机仿真技术,是对后勤保障全流程进行模拟、分析与优化的综合性工具。通过搭建虚拟场景,模拟资源调配、物资运输等环节,为后勤决策提供数据支撑,广泛应用于军事、应急管理等领域。   应用案例   目前,已有多个后勤实验仿真系统平台在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润后勤实验仿真系统平台。这些成功案例为后勤实验仿真系统平台的推广和应用提供了有力支持。   一、核心功能   (一)后勤资源模拟
    华盛恒辉l58ll334744 2025-04-23 15:39 165浏览
  •   有效样本分析决策系统平台全面解析   一、引言   北京华盛恒辉有效样本分析决策系统在当今数据驱动的时代,企业、科研机构等面临着海量数据的处理与分析挑战。有效样本分析决策系统平台应运而生,它通过对样本数据的精准分析,为决策提供有力支持,成为提升决策质量和效率的关键工具。   应用案例   目前,已有多个有效样本分析决策系统在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润有效样本分析决策系统。这些成功案例为有效样本分析决策系统的推广和应用提供了有力支持。   二、平台概述
    华盛恒辉l58ll334744 2025-04-24 11:13 89浏览
  •   无人机结构仿真与部件拆解分析系统平台解析   北京华盛恒辉无人机结构仿真与部件拆解分析系统无人机技术快速发展的当下,结构仿真与部件拆解分析系统平台成为无人机研发测试的核心工具,在优化设计、提升性能、降低成本等方面发挥关键作用。以下从功能、架构、应用、优势及趋势展开解析。   应用案例   目前,已有多个无人机结构仿真与部件拆解分析系统在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润无人机结构仿真与部件拆解分析系统。这些成功案例为无人机结构仿真与部件拆解分析系统的推广和应用提
    华盛恒辉l58ll334744 2025-04-23 15:00 201浏览
我要评论
0
1
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦