实例讲解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++,以及经验分享、行业资讯、物联网等技术知
评论
  • 根据环洋市场咨询(Global Info Research)项目团队最新调研,预计2030年全球无人机锂电池产值达到2457百万美元,2024-2030年期间年复合增长率CAGR为9.6%。 无人机锂电池是无人机动力系统中存储并释放能量的部分。无人机使用的动力电池,大多数是锂聚合物电池,相较其他电池,锂聚合物电池具有较高的能量密度,较长寿命,同时也具有良好的放电特性和安全性。 全球无人机锂电池核心厂商有宁德新能源科技、欣旺达、鹏辉能源、深圳格瑞普和EaglePicher等,前五大厂商占有全球
    GIRtina 2025-01-07 11:02 71浏览
  • 这篇内容主要讨论三个基本问题,硅电容是什么,为什么要使用硅电容,如何正确使用硅电容?1.  硅电容是什么首先我们需要了解电容是什么?物理学上电容的概念指的是给定电位差下自由电荷的储藏量,记为C,单位是F,指的是容纳电荷的能力,C=εS/d=ε0εrS/4πkd(真空)=Q/U。百度百科上电容器的概念指的是两个相互靠近的导体,中间夹一层不导电的绝缘介质。通过观察电容本身的定义公式中可以看到,在各个变量中比较能够改变的就是εr,S和d,也就是介质的介电常数,金属板有效相对面积以及距离。当前
    知白 2025-01-06 12:04 173浏览
  • 村田是目前全球量产硅电容的领先企业,其在2016年收购了法国IPDiA头部硅电容器公司,并于2023年6月宣布投资约100亿日元将硅电容产能提升两倍。以下内容主要来自村田官网信息整理,村田高密度硅电容器采用半导体MOS工艺开发,并使用3D结构来大幅增加电极表面,因此在给定的占位面积内增加了静电容量。村田的硅技术以嵌入非结晶基板的单片结构为基础(单层MIM和多层MIM—MIM是指金属 / 绝缘体/ 金属) 村田硅电容采用先进3D拓扑结构在100um内,使开发的有效静电容量面积相当于80个
    知白 2025-01-07 15:02 75浏览
  • By Toradex 秦海1). 简介嵌入式平台设备基于Yocto Linux 在开发后期量产前期,为了安全以及提高启动速度等考虑,希望将 ARM 处理器平台的 Debug Console 输出关闭,本文就基于 NXP i.MX8MP ARM 处理器平台来演示相关流程。 本文所示例的平台来自于 Toradex Verdin i.MX8MP 嵌入式平台。  2. 准备a). Verdin i.MX8MP ARM核心版配合Dahlia载板并
    hai.qin_651820742 2025-01-07 14:52 48浏览
  • 大模型的赋能是指利用大型机器学习模型(如深度学习模型)来增强或改进各种应用和服务。这种技术在许多领域都显示出了巨大的潜力,包括但不限于以下几个方面: 1. 企业服务:大模型可以用于构建智能客服系统、知识库问答系统等,提升企业的服务质量和运营效率。 2. 教育服务:在教育领域,大模型被应用于个性化学习、智能辅导、作业批改等,帮助教师减轻工作负担,提高教学质量。 3. 工业智能化:大模型有助于解决工业领域的复杂性和不确定性问题,尽管在认知能力方面尚未完全具备专家级的复杂决策能力。 4. 消费
    丙丁先生 2025-01-07 09:25 80浏览
  • 每日可见的315MHz和433MHz遥控模块,你能分清楚吗?众所周知,一套遥控设备主要由发射部分和接收部分组成,发射器可以将控制者的控制按键经过编码,调制到射频信号上面,然后经天线发射出无线信号。而接收器是将天线接收到的无线信号进行解码,从而得到与控制按键相对应的信号,然后再去控制相应的设备工作。当前,常见的遥控设备主要分为红外遥控与无线电遥控两大类,其主要区别为所采用的载波频率及其应用场景不一致。红外遥控设备所采用的射频信号频率一般为38kHz,通常应用在电视、投影仪等设备中;而无线电遥控设备
    华普微HOPERF 2025-01-06 15:29 127浏览
  • 彼得·德鲁克被誉为“现代管理学之父”,他的管理思想影响了无数企业和管理者。然而,关于他的书籍分类,一种流行的说法令人感到困惑:德鲁克一生写了39本书,其中15本是关于管理的,而其中“专门写工商企业或为企业管理者写的”只有两本——《为成果而管理》和《创新与企业家精神》。这样的表述广为流传,但深入探讨后却发现并不完全准确。让我们一起重新审视这一说法,解析其中的矛盾与根源,进而重新认识德鲁克的管理思想及其著作的真正价值。从《创新与企业家精神》看德鲁克的视角《创新与企业家精神》通常被认为是一本专为企业管
    优思学院 2025-01-06 12:03 119浏览
  • 在智能家居领域中,Wi-Fi、蓝牙、Zigbee、Thread与Z-Wave等无线通信协议是构建短距物联局域网的关键手段,它们常在实际应用中交叉运用,以满足智能家居生态系统多样化的功能需求。然而,这些协议之间并未遵循统一的互通标准,缺乏直接的互操作性,在进行组网时需要引入额外的网关作为“翻译桥梁”,极大地增加了系统的复杂性。 同时,Apple HomeKit、SamSung SmartThings、Amazon Alexa、Google Home等主流智能家居平台为了提升市占率与消费者
    华普微HOPERF 2025-01-06 17:23 145浏览
  • 本文介绍Linux系统更换开机logo方法教程,通用RK3566、RK3568、RK3588、RK3576等开发板,触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。制作图片开机logo图片制作注意事项(1)图片必须为bmp格式;(2)图片大小不能大于4MB;(3)BMP位深最大是32,建议设置为8;(4)图片名称为logo.bmp和logo_kernel.bmp;开机
    Industio_触觉智能 2025-01-06 10:43 87浏览
  • 根据Global Info Research项目团队最新调研,预计2030年全球封闭式电机产值达到1425百万美元,2024-2030年期间年复合增长率CAGR为3.4%。 封闭式电机是一种电动机,其外壳设计为密闭结构,通常用于要求较高的防护等级的应用场合。封闭式电机可以有效防止外部灰尘、水分和其他污染物进入内部,从而保护电机的内部组件,延长其使用寿命。 环洋市场咨询机构出版的调研分析报告【全球封闭式电机行业总体规模、主要厂商及IPO上市调研报告,2025-2031】研究全球封闭式电机总体规
    GIRtina 2025-01-06 11:10 104浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦