初级会员

- 积分
- 104
- 金钱
- 104
- 注册时间
- 2021-6-2
- 在线时间
- 25 小时
|
本帖最后由 JYun 于 2021-6-10 17:46 编辑
本贴是根据一博主改写的代码,实现了一个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] = '\0';
}
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 data\r\n"); //测试发送
while(1) //主循环
{
Delay_Us(5); //延时,串口中断先处理数据
if(COM_RX_END == 1) //检测到数据处理完成
{
VirtualCOM_StringSend(COM_RX_BUF); //打印显示
CLR_Buf(); //清空数组
}
}
}
效果如下

|
|