STM32
直播中

mintsy

9年用户 1492经验值
擅长:MEMS/传感技术
私信 关注
[问答]

如何实现一个io口模拟串口对字符串进行收发?

如何实现一个io口模拟串口对字符串进行收发?

回帖(1)

李广汇

2021-12-8 10:02:11
本贴是根据一博主改写的代码,实现了一个io口模拟串口对字符串进行收发。原帖地址:https://blog.csdn.net/wxh0000mm
话不多说进入正题,串口通信协议发送一个字节默认为10个bit,其中包括开始位、停止位和中间八个数据位。数据固定开始位为低电平,结束位为高电平。如果我们发送字母a最终会以二进制0 0110 0001 1形式进行数据传输。






对串口通信协议有了基础的了解写代码就好办了,发送函数是很简单的,只需要根据通信协议,在字符bit位为1的时候拉高引脚,bit位为0的时候将引脚拉低即可进行数据的传输。以波特率9600为例:传输一个bit位的间隔就是1/9600s,就约等于104us发送一个bit位。


下面是io口模拟的发送函数


/*********************模拟串口发送数据***********************************/
//IO口引脚定义
void VirtualCOM_TX_GPIOConfig(void)
{
        GPIO_InitTypeDef GPIO_InitStruct;
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
        /* PA4设为数据输出口,模拟TX */
       
        GPIO_InitStruct.GPIO_Pin = COM_TX_PIN;
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;     //设置为推挽输出
        GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(COM_TX_PORT, &GPIO_InitStruct)
        GPIO_SetBits(COM_TX_PORT, COM_TX_PIN);           //设置默认位为高电平
}


定义了数据发送引脚即可利用串口通信协议,间隔一定延时对单个bit位进行传输


//模拟引脚发送单个字节
void VirtualCOM_ByteSend(u8 val)
{
        u8 i = 0;
        COM_DATA_LOW;             //引脚拉低,即将发送数据
        Delay_Us(BuadRate9600);  //延时104us
        for(i = 0; i < 8; i++) //8位数据位
        {
            if(val & 0x01)    //如果bit位为1
                    COM_DATA_HIGH;  //引脚拉高
            else    //否则拉低
                    COM_DATA_LOW;
            Delay_Us(BuadRate9600);
            val >>= 1;
        }
        COM_DATA_HIGH;   //停止位
        Delay_Us(BuadRate9600);
}


有了单个字节的发送函数,直接调用该函数即可发送字符串


void VirtualCOM_StringSend(u8 *str)
{
        while(*str != 0)
        {
                VirtualCOM_ByteSend(*str);
                str++;
        }
}


发送的流程明白了,接收也是同样的道理,不过接收就不能用延时去进行接收,因为程序代码运行需要一定的周期,一旦数据量大了就无法准确的进行数据的接收。所以我们需要在检测到开始位的时候去开启一起定时器,定期接收bit数据位。


/*********************串口变量初始化***********************************/
u8 recvStat = COM_STOP_BIT;
u8 recvData = 0x00;
u8 COM_RX_BUF[COM_REC_LEN];
u16 COM_RX_STA = 0;
u8 COM_RX_END = 1;


/*********************模拟串口接收数据***********************************/
//接收引脚定义
void VirtualCOM_RX_GPIOConfig(void)
{
        GPIO_InitTypeDef GPIO_InitStruct;
        EXTI_InitTypeDef EXTI_InitStruct;
        NVIC_InitTypeDef NVIC_InitStruct;
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO,ENABLE);
       
        /* PA5设为数据输入口,模拟RX */
        GPIO_InitStruct.GPIO_Pin = COM_RX_PIN;
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
        GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(COM_RX_PORT,&GPIO_InitStruct);
        GPIO_SetBits(COM_RX_PORT, COM_RX_PIN);     //设置默认位为高电平
       
        EXTI_InitStruct.EXTI_Line = EXTI_Line5;              //中断线
        EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;     //中断模式
        EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿中断
        EXTI_InitStruct.EXTI_LineCmd = ENABLE;
        EXTI_Init(&EXTI_InitStruct);
       
        NVIC_InitStruct.NVIC_IRQChannel = EXTI9_5_IRQn;      //外部中断,边沿触发
        NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
        NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStruct);
}


//串口外部中断
void EXTI9_5_IRQHandler(void)
{
        if(EXTI_GetITStatus(EXTI_Line5) != RESET)
        {
                if(!COM_RX_STAT)     //检测引脚高低电平,如果是低电平,则说明检测到下升沿
                {
                        if(recvStat == COM_STOP_BIT)   //状态为停止位
                        {
                                recvStat = COM_START_BIT;   //接收开始位
                                COM_RX_END = 0;             //标志数据是否处理完成
                                Delay(63);
                                TIM_Cmd(TIM2,ENABLE);      //开启定时器
                        }
                }
                EXTI_ClearITPendingBit(EXTI_Line5);  //清除EXTI_Line1中断挂起标志位
        }
}


//清空字符数组
void CLR_Buf(void)
{
        unsigned char y;
        for(y = 0;y < COM_REC_LEN;y ++ )
        {
                COM_RX_BUF[y] = '';
        }
        COM_RX_STA = 0;
}


本程序利用定时器2,定时104us执行一次中断处理函数。


/********************定时器2*******************************/
//配置定时器2
void TIM2_Configuration(u16 arr,u16 psc)
{
        TIM_TimeBaseInitTypeDef TIM_TimBaseStruct;
        NVIC_InitTypeDef NVIC_InitStruct;
       
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);    //使能TIM2的时钟
        TIM_DeInit(TIM2);                                       //复位定时器2
        TIM_InternalClockConfig(TIM2);                          //使用内部时钟给TIM2提供时钟源
       
        TIM_TimBaseStruct.TIM_Period = arr;                     //设置计数溢出大小,每计period个数就产生一个更新事件
        TIM_TimBaseStruct.TIM_Prescaler = psc;                  //预分频系数为72,这样计数器时钟为72MHz/72 = 1MHz
        TIM_TimBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;     //设置时钟分频
        TIM_TimBaseStruct.TIM_CounterMode = TIM_CounterMode_Up; //设置计数器模式为向上计数模式
        TIM_TimeBaseInit(TIM2,&TIM_TimBaseStruct);              //将配置应用到TIM2中
       
        TIM_ClearFlag(TIM2,TIM_FLAG_Update);     //清除溢出中断标志
        TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //开启TIM2的中断
        TIM_Cmd(TIM2,DISABLE);                   //关闭定时器TIM2
       
        NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;      //通道设置为TIM2中断
        NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;   //响应式中断优先级1
        NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;      //打开中断
        NVIC_Init(&NVIC_InitStruct);
}


//定时器2中断函数
void TIM2_IRQHandler(void)
{
        if(TIM_GetITStatus(TIM2,TIM_IT_Update) != RESET)  //检测是否发生溢出更新事件
        {
                TIM_ClearITPendingBit(TIM2,TIM_FLAG_Update);    //清除中断标志
               
                recvStat++;                    //改变状态机
                if(recvStat == COM_STOP_BIT)   //收到停止位
                {
                        COM_RX_END = 1;            //标志数据处理完成
                        TIM_Cmd(TIM2,DISABLE);     //关闭定时器
                        Delay(63);                 //延时指令周期,等待下一字节处理
                        COM_RX_BUF[COM_RX_STA] = recvData;   //将当前处理完的字节存入数组里面
                        COM_RX_STA++;
                       
                        if(COM_RX_STA > (COM_REC_LEN -1))
                                COM_RX_STA = 0;
                }
               
                if(COM_RX_STAT)
                {
                        recvData |= (1 << (recvStat - 1));
                }
                else
                {
                        recvData &= ~(1 <<(recvStat - 1));
                }
        }
}


以下是虚拟串口和定时器2头文件


/*********************串口头文件*******************************/
#ifndef __COM_H
#define __COM_H
#include "sys.h"
#include "delay.h"


#define BuadRate9600 104
#define BuadRate1200 830


#define COM_REC_LEN 200              //定义最大接收字节数 200


#define COM_TX_PORT GPIOA
#define COM_TX_PIN GPIO_Pin_4


#define COM_RX_PORT GPIOA
#define COM_RX_PIN GPIO_Pin_5


#define COM_RX PAout(5)  
#define COM_DATA_HIGH GPIO_SetBits(COM_TX_PORT, GPIO_Pin_4)   //高电平
#define COM_DATA_LOW  GPIO_ResetBits(COM_TX_PORT, GPIO_Pin_4) //低电平


#define COM_RX_STAT GPIO_ReadInputDataBit(COM_RX_PORT, GPIO_Pin_5)


enum {
        COM_START_BIT, //停止位
        COM_D0_BIT, //bit0
        COM_D1_BIT, //bit1
        COM_D2_BIT, //bit2
        COM_D3_BIT, //bit3
        COM_D4_BIT, //bit4
        COM_D5_BIT, //bit5
        COM_D6_BIT, //bit6
        COM_D7_BIT, //bit7
        COM_STOP_BIT,  //结束位
};


extern u8 recvStat;
extern u8 recvData;


extern u8 COM_RX_BUF[COM_REC_LEN];   //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
extern u16 COM_RX_STA;                            //接收状态标记
extern u8 COM_RX_END;


void VirtualCOM_TX_GPIOConfig(void);
void VirtualCOM_RX_GPIOConfig(void);
void VirtualCOM_ByteSend(u8 val);
void VirtualCOM_StringSend(u8 *str);
void EXTI9_5_IRQHandler(void);
void CLR_Buf(void);


#endif


/*********************定时器文件*******************************/
#ifndef __TIMER_H
#define __TIMER_H
#include "sys.h"
#include "com.h"


extern unsigned char Count_timer;
extern unsigned char Flag_timer_1S;


void TIM3_Int_Init(u16 arr,u16 psc);
void TIM2_Configuration(u16 arr,u16 psc);
void TIM3_IRQHandler(void);
void TIM2_IRQHandler(void);


#endif


我使用的单片机晶振为72MHZ,对其进行一个八分频,在延时文件中定义了不精准延时函数,程序运行一个指令周期为1/9us。


/**************不精准延时********************/
void Delay(u32 t)
{
        while(t--);
}


下面是主函数调用


int main(void)
{       
        NVIC_Configuration();
        TIM3_Int_Init(999,7199);                  //开启定时器3,计数100ms
        LED_Init();
        Delay_Init();
        KEY_Init();
        TIM2_Configuration(BuadRate9600,71);  //定时器2 104us
        VirtualCOM_TX_GPIOConfig();
        VirtualCOM_RX_GPIOConfig();
        LED0 = 1;
        VirtualCOM_StringSend("com tx datarn"); //测试发送
        while(1)   //主循环
        {
                Delay_Us(5);        //延时,串口中断先处理数据   
                if(COM_RX_END == 1)  //检测到数据处理完成
                {
                        VirtualCOM_StringSend(COM_RX_BUF);  //打印显示
                        CLR_Buf();  //清空数组
                }
        }
}


效果如下



举报

更多回帖

发帖
×
20
完善资料,
赚取积分