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

描述

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

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

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

UART接口

接下来,让我们一起学习如何实现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

 





审核编辑:刘清

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

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分