0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

如何实现IO模拟串口通信?

STM32嵌入式开发 来源:CSDN 2023-09-14 15:33 次阅读

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

既然我们要进行对串口的模拟,因此我们要先了解uart相关的通信协议:带你快速对比SPI、UART、I2C通信的区别与应用!,这篇文章进行了详细介绍,此处不再赘述。

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

wKgaomUCt7SAbtP5AAAxSRtVr9E659.jpg

接下来,让我们一起学习如何实现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口相应的位带操作函数进行宏定义从而使得在对不同的电平的进行转换的时候更为方便,并且减少了调用其他函数的过程所消耗的时间,程序执行效率更高。

在本次的传输过程中我选用的是使用波特率速率为9600bps,也就是1s中发送9600个数据位(bit),因此对每个位数据进行计算1000000us/9600可以得出,发一个bit的数据需要进行大概需要 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





审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 单片机
    +关注

    关注

    6002

    文章

    43990

    浏览量

    620981
  • 串口通信
    +关注

    关注

    32

    文章

    1578

    浏览量

    54878
  • GPIO
    +关注

    关注

    16

    文章

    1139

    浏览量

    50613
  • UART接口
    +关注

    关注

    0

    文章

    123

    浏览量

    15070

原文标题:在STM32中如何通过IO口模拟串口通信

文章出处:【微信号:c-stm32,微信公众号:STM32嵌入式开发】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    STM32 IO模拟实现软件串口的方法

    STM32 IO模拟实现软件串口的方法
    发表于 12-06 07:29

    请问STM8单片机IO模拟串口通信如何实现

    请问STM8单片机IO模拟串口通信如何实现
    发表于 02-21 06:23

    IO模拟UART实现

    IO模拟UART实现 本应用用于扩展UART端口,在单片机自带的UART口不够用的情况下,使用GPIO和定时器实现模拟UART
    发表于 03-26 09:20 68次下载

    IO模拟串口UART

    IO模拟串口UART 本文介绍GPIO模拟UART的算法和实现
    发表于 04-03 14:11 86次下载

    IO模拟串口通讯方法实例

    本内容介绍了IO模拟串口通讯的实现方法及实例分析
    发表于 04-20 17:34 130次下载
    <b class='flag-5'>IO</b><b class='flag-5'>模拟</b><b class='flag-5'>串口</b>通讯方法实例

    串口通信的原理,IO模拟UART串口通信

    UART串口波特率,常用的值是300、600、1200、2400、4800、9600、14400、19200、28800、38400、57600、115200等速率。IO模拟UART串行通信
    的头像 发表于 05-04 15:26 2.1w次阅读
    <b class='flag-5'>串口</b><b class='flag-5'>通信</b>的原理,<b class='flag-5'>IO</b>口<b class='flag-5'>模拟</b>UART<b class='flag-5'>串口</b><b class='flag-5'>通信</b>

    STM32中IO模拟串口输出的乱码现象

    因为芯片串口不够用,只好用IO模拟串口,在网上下载了个模拟串口的程序,可运行,但发现
    发表于 06-21 07:57 4490次阅读
    STM32中<b class='flag-5'>IO</b>口<b class='flag-5'>模拟</b><b class='flag-5'>串口</b>输出的乱码现象

    几种IO模拟串口"硬核"操作

    1、聊一聊 好了,今天为大家带来几种IO模拟串口硬核操作,相信大家对类似于串口这样的电平类通信会有新的认识。 2、
    的头像 发表于 10-13 17:44 1.3w次阅读
    几种<b class='flag-5'>IO</b>口<b class='flag-5'>模拟</b><b class='flag-5'>串口</b>"硬核"操作

    几种IO模拟串口&quot;硬核&quot;操作

    1、聊一聊 好了,今天为大家带来几种IO模拟串口"硬核"操作,相信大家对类似于串口这样的电平类通信会有新的认识。 2、
    的头像 发表于 02-10 11:51 4423次阅读
    几种<b class='flag-5'>IO</b>口<b class='flag-5'>模拟</b><b class='flag-5'>串口</b>&quot;硬核&quot;操作

    【妙招】一份 " IO模拟串口 " 独门秘籍

    1、聊一聊 好了,今天为大家带来几种IO模拟串口"硬核"操作,相信大家对类似于串口这样的电平类通信会有新的认识。 2、
    发表于 01-30 06:35 34次下载
    【妙招】一份 " <b class='flag-5'>IO</b>口<b class='flag-5'>模拟</b><b class='flag-5'>串口</b> " 独门秘籍

    几种IO模拟串口“硬核”操作

    对类似于串口这样的电平类通信会有新的认识。 2、IO模拟串口需求 "IO
    的头像 发表于 04-04 11:28 1967次阅读
    几种<b class='flag-5'>IO</b>口<b class='flag-5'>模拟</b><b class='flag-5'>串口</b>“硬核”操作

    stm32使用IO模拟串口

    STM32使用IO模拟串口期望效果:使用普通IO模拟串口
    发表于 12-04 20:21 17次下载
    stm32使用<b class='flag-5'>IO</b><b class='flag-5'>模拟</b><b class='flag-5'>串口</b>

    STM8S103系列IO模拟串口通信实现真正串口

    STM8s103f3p3单片机只有一个串口,有时候在实际项目中,我们需要用到多个串口实现项目要求,此时,我们可以通过IO口来模拟
    发表于 12-27 18:51 25次下载
    STM8S103系列<b class='flag-5'>IO</b>口<b class='flag-5'>模拟</b><b class='flag-5'>串口</b><b class='flag-5'>通信</b>(<b class='flag-5'>实现</b>真正<b class='flag-5'>串口</b>)

    单片机IO模拟UART串口通信

    为了让大家充分理解 UART 串口通信的原理,我们先把 P3.0 和 P3.1 当做 IO 口来进行模拟实际串口
    发表于 02-09 10:25 23次下载
    单片机<b class='flag-5'>IO</b>口<b class='flag-5'>模拟</b>UART<b class='flag-5'>串口</b><b class='flag-5'>通信</b>

    一份“IO模拟串口”独门秘籍

    今天为大家带来几种IO模拟串口"硬核"操作,相信大家对类似于串口这样的电平类通信会有新的认识。
    的头像 发表于 05-14 09:55 3352次阅读
    一份“<b class='flag-5'>IO</b>口<b class='flag-5'>模拟</b><b class='flag-5'>串口</b>”独门秘籍