模拟串口通信 2.0 版本!!
我在前面的文章里面有写了 虚拟串口通信,虽然说能用,但是用过的小伙伴都说 “好!”
优缺点:
先说一点,2.0版本并不适用于同硬件串口的所有场合,仅仅针对自己开发的电子垃圾的主从结构,比如自己的两块板子通信,或者硬件串口没得了又想追加串口不得已的情况。
优点:
1.可以设置任意波特率, 并且从机不可以识别任意波特率数据(只要主时钟频率高就行了)。
2.可以实现不定长度数据收发,并且用户能完成收发标志 1.0版本可不行。
3.减少资源消耗,同比1.0版本,在芯片内部外设资源上面有所减少。
4.同比1.0版本 大大提高收发数据稳定性。反正我实验没有丢过数据。
好下面说缺点:
缺点:
1. 2.0版本相对于1.0版本,硬件接线上面多出了1根时钟线(相当于 一个串口收发需要三根线,三根线,三根线,如果有多路虚拟串口,则每一路仅需要追加 2 根线,笑哭!!! )。
2. 因为是任意波特率所以无法和其他硬件串口做对接 如果非要接 那你就吧波特率规定到和硬件一样。
3. 串口通信点对点,懂得都懂。只不过用的IO模拟 ,有些引脚上面的驱动能力要强那么丢丢,一对多可试试。
好!!到这里有人就会问了:三根线,你走SPI啥!再不济你走I2C,I2C还少根线。你三根线还是模拟的数据玩个鸡毛!!
嗯?问的好!我竟无言以对,但是这是全双工通信,而且没有比较严格的主从关系,而且通信只能主机发起,不能从机发起。
好!!那又有人会问:那和SPI 有啥子区别啥,都是三线全双工通信。
好那就看下面:
这是一个数据收发的时序图,上面可以看到从主模块出来的TX是在时钟的下降沿发出通信起始位,然后在每个下降沿到达时改变信号状态,在时钟上升沿进行数据采集,而RX则是反过来,在上升沿发起通信并且修改电平状态,下降沿采集状态。所以在两数据通信的时候上升和下降沿都有意义。
首先是主控板 :
下面来看配置情况:
这里我定义的三个通用IO口
IO_CLK :时钟输出脚(GPIO)
IO_RX :接受输入脚(GPIO)
IO_TX :数据输出脚(GPIO)
开一个定时器 使能 更新溢出中断(定时时间自己设置一个就行)
以上就是基本的软件配置信息,这一块比较简单大家大概配置一下就行了,剩下的就是代码部分了
/* *****************************************************************
*Copyright (C), 2019-2023, TTT
*Filename: M_File_Oled.c
*Author : ZY
*Created Date : 15/02/2023
*Abstract : 处理全局参数
*History :
*V1.0.0 : Initial (by ZY,15/02/2023)
***************************************************************** */
#include "M_File_IOUart.h"
//定义结构体
struct IOUart IOUart_h_Struct={ .IO_UART_RecvStat = COM_STOP_BIT};
/*****************************************
* 函数名: IoUartSendByte
* 功能说明: 模拟串口发送1个字节数据
* 形参:Byte 要发送的字节
* 返回值: 无
******************************************/
void IoUartSendByte(uint8_t Byte)
{
uint8_t tmp = 0;
static uint8_t count = 0;
static uint8_t Old_IO_UART_StartTXFlag = 0;
// 开始位
if(IOUart_h_Struct.IO_UART_StartTXFlag1BIT != Old_IO_UART_StartTXFlag)
{
if(IOUart_h_Struct.IO_UART_StartTXFlag1BIT == 1)
{
IO_UART_TXD(0); //将TXD的引脚的电平置低
count = 0;
}
Old_IO_UART_StartTXFlag = IOUart_h_Struct.IO_UART_StartTXFlag1BIT;
}
else
{
if(count < 8)
{
tmp = (Byte >> count) & 0x01;
if(tmp==0)
{
IO_UART_TXD(0);
}
else
{
IO_UART_TXD(1);
}
count++;
}
else
{
IO_UART_TXD(1);//将TXD的引脚的电平拉高
IOUart_h_Struct.IO_UART_StartTXFlag1BIT = 0; //结束 发送数据
Old_IO_UART_StartTXFlag = 0;
count =0;
}
}
}
/*******************************************************************************
*Name : IO_UART_SendData
*Syntax : void IO_UART_SendData(const char *Data,uint32_t size)
*Parameters(in) :
*Parameters(out) : 无
*Return value : 无
*Description : IO串口发送字符串
********************************************************************************/
void IO_UART_SendString(const char *String)
{
for(int i=0; i<strlen(String); i++)
{
IoUartSendByte(String[i]);
}
}
/*******************************************************************************
*Name : IO_UART_SendData
*Syntax : void IO_UART_SendData(const char *Data,uint32_t size)
*Parameters(in) :
*Parameters(out) : 无
*Return value : 无
*Description : IO串口发送数据
********************************************************************************/
void IO_UART_SendData(const char *Data,uint32_t size)
{
if(IOUart_h_Struct.IO_UART_StartTXFLAGBuff == 0)
{
IOUart_h_Struct.IO_Send_Size = size;
memcpy(IOUart_h_Struct.IO_UART_TXBuff,Data,size);
IOUart_h_Struct.IO_UART_StartTXFLAGBuff = 1;
}
}
/*******************************************************************************
*Name : IO_UART_ReciveData
*Syntax : void IO_UART_ReciveData(void)
*Parameters(in) :
*Parameters(out) : 无
*Return value : 无
*Description : IO串口接收字符串
********************************************************************************/
void IO_UART_ReciveData(void)
{
if(IOUart_h_Struct.IO_UART_StartRxFlag == 1)
{
IOUart_h_Struct.IO_UART_RecvStat++;
if(IOUart_h_Struct.IO_UART_RecvStat == COM_STOP_BIT)
{
//接收到完整的1个字节数据
if(IOUart_h_Struct.IO_UART_RxNum < _IO_RX_LENGHT)
{
IOUart_h_Struct.IO_UART_RxBuff[IOUart_h_Struct.IO_UART_RxNum++] = IOUart_h_Struct.IO_UART_RecvData; //存入缓冲区
}
else
{
IOUart_h_Struct.IO_UART_RxNum = 0;
}
IOUart_h_Struct.IO_UART_StartRxFlag = 0;
IOUart_h_Struct.IO_UART_RecvData = 0;
return;
}
if(IO_UART_RXD)//读取接收引脚的状态
{
IOUart_h_Struct.IO_UART_RecvData |= (1 << (IOUart_h_Struct.IO_UART_RecvStat - 1));
}
else
{
IOUart_h_Struct.IO_UART_RecvData &= ~(1 << (IOUart_h_Struct.IO_UART_RecvStat - 1));
}
}
}
/*******************************************************************************
*Name : IO_UART_Send_Buff
*Syntax : void IO_UART_Send_Buff(void)
*Parameters(in) :
*Parameters(out) : 无
*Return value : 无
*Description : IO串口发送数据处理
********************************************************************************/
void IO_UART_Send_Buff(void)
{
static uint8_t OLD_IO_UART_StartTXFLAGBuff = 0;
static uint8_t OLD_Send_Count = 0;
if(OLD_IO_UART_StartTXFLAGBuff!= IOUart_h_Struct.IO_UART_StartTXFLAGBuff)
{
if(IOUart_h_Struct.IO_UART_StartTXFLAGBuff == 1){
OLD_Send_Count = 0;
IOUart_h_Struct.IO_UART_StartTXFlag1BIT = 1; //发送数据
}
OLD_IO_UART_StartTXFLAGBuff = IOUart_h_Struct.IO_UART_StartTXFLAGBuff;
}
else
{
if(IOUart_h_Struct.IO_UART_StartTXFLAGBuff == 1)
{
if(OLD_Send_Count<IOUart_h_Struct.IO_Send_Size)
{
IoUartSendByte(IOUart_h_Struct.IO_UART_TXBuff[OLD_Send_Count]);
if(IOUart_h_Struct.IO_UART_StartTXFlag1BIT == 0)
{
OLD_Send_Count++;
if(OLD_Send_Count<IOUart_h_Struct.IO_Send_Size)
{
IOUart_h_Struct.IO_UART_StartTXFlag1BIT = 1; //发送数据
}
}else{}
}else{
IOUart_h_Struct.IO_UART_StartTXFLAGBuff = 0;
IOUart_h_Struct.IO_Send_Size =0 ;
}
}
}
}
/*******************************************************************************
*Name : IO_UART_Time_CountDown_Deal
*Syntax : void IO_UART_Time_CountDown_Deal(void)
*Parameters(in) :
*Parameters(out) : 无
*Return value : 无
*Description : IO串口倒计时处理
********************************************************************************/
void IO_UART_Time_CountDown_Deal(void)
{
if(IOUart_h_Struct.IO_UART_RxTimeOut>0)
{
if(IOUart_h_Struct.IO_UART_RxTimeOut == 1)
{
IOUart_h_Struct.IO_UART_RxTimeOut --;
IOUart_h_Struct.IO_UART_RxOK = 1; //一条数据接收完成
IOUart_h_Struct.IO_UART_RxNum = 0;
//完整接受到一条数据
//TsUserM_h_Flag.e_u_UartAppceptFlag[0] = ON;
return ;
}
IOUart_h_Struct.IO_UART_RxTimeOut--;
}
else
{
}
}
/* *****************************************************************
*Copyright (C), 2019-2023, TTT
*Filename: M_File_IOUart.h
*Author : ZY
*Created Date : 15/02/2023
*Abstract : 处理全局参数
*History :
*V1.0.0 : Initial (by ZY,15/02/2023)
***************************************************************** */
#ifndef __M_FILE_IOUart_H__
#define __M_FILE_IOUart_H__
//#include "M_File_Flag.h"
/*自己定义导入的.h 文件 ******************************************************/
#define _IO_RX_LENGHT 200 //接收数据长度
#define _IO_RX_TIMECOUNTDOWN 15 //接收数据超时倒计时
/*
*@串口接收字节
*/
enum
{
COM_START_BIT = 0,
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,
};
struct IOUart{
uint8_t IO_UART_RecvData; //接收数据
uint8_t IO_UART_RecvStat; //接收状态
uint16_t IO_UART_RxNum; //接收数据长度
uint8_t IO_UART_RxBuff[_IO_RX_LENGHT]; //模拟串口接收数据缓冲区
uint8_t IO_UART_TXBuff[_IO_RX_LENGHT]; //模拟串口发送数据缓冲区
uint32_t IO_UART_RxTimeOut; //模拟串口接收超时计数
uint8_t IO_UART_RxOK; //数据接收完成标志
uint8_t IO_UART_StartRxFlag; //开始接收标志,1开始接收,0不接收
uint8_t IO_UART_StartTXFlag1BIT; //开始发送标志,1开始发送,0不发送
uint8_t IO_UART_StartTXFLAGBuff; //多字节发送
uint16_t IO_Send_Size;
};
/*把下面的引脚重新指定到自己指定的引脚上面 打开宏定义 ******************************************************/
#if 0
//读取Rx脚
#define IO_UART_RXD HAL_GPIO_ReadPin(IO_RX_GPIO_Port,IO_RX_Pin) //模拟串口RX端
//设置Tx脚拉高拉低
#define IO_UART_TXD(n) if(n) HAL_GPIO_WritePin(IO_TX_GPIO_Port, IO_TX_Pin, GPIO_PIN_SET); \
else HAL_GPIO_WritePin(IO_TX_GPIO_Port, IO_TX_Pin, GPIO_PIN_RESET);
//设置时钟脚
#define IO_UART_CLK(n) if(n) HAL_GPIO_WritePin(IO_CLK_GPIO_Port, IO_CLK_Pin, GPIO_PIN_SET); \
else HAL_GPIO_WritePin(IO_CLK_GPIO_Port, IO_CLK_Pin, GPIO_PIN_RESET);
#endif
extern struct IOUart IOUart_h_Struct;
extern void IO_UART_Send_Buff(void);
extern void IO_UART_SendData(const char *Data,uint32_t size);
extern void IO_UART_Time_CountDown_Deal(void);
extern void IO_UART_ReciveData(void);
#endif
定时器中断回调里面处理部分:
HAL_GPIO_TogglePin(IO_CLK_GPIO_Port, IO_CLK_Pin); //翻转输出时钟
if(HAL_GPIO_ReadPin(IO_CLK_GPIO_Port, IO_CLK_Pin)) //判断电平 当前输出为 高电平
{
IO_UART_Send_Buff();
}
else //引脚拉低
{
IO_UART_Time_CountDown_Deal();
IO_UART_ReciveData(); //处理虚拟串口接收数据
if((IO_UART_RXD == GPIO_PIN_RESET)&&(IOUart_h_Struct.IO_UART_StartRxFlag == 0)) //判定是否有低电平数据进入
{
if(IOUart_h_Struct.IO_UART_RecvStat == COM_STOP_BIT) //状态为停止位
{
IOUart_h_Struct.IO_UART_RecvStat = COM_START_BIT; //设置状态为起始位
IOUart_h_Struct.IO_UART_StartRxFlag = 1; //开始接收
IOUart_h_Struct.IO_UART_RecvData = 0; //接收数据
IOUart_h_Struct.IO_UART_RxTimeOut = _IO_RX_TIMECOUNTDOWN;//接收到数据把接收超时清零
}
}
}
上面是核心代码,如果接受不定长度代码 可在 IO_UART_Time_CountDown_Deal 函数里面 写接受完成标志。
模拟串口 printf 数据
/*******************************************************************************
Name :FlagM_Uart_Printf
Syntax :void UartM_485Printf(char *fmt,...)
Parameters(in) :None :-
Parameters(out) :None :-
Return value :- :-
Description :模拟串口数据打印
|******************************************************************************/
__align(8) char usart_txBuff[USART_TXBUFF_SIZE]; //字节对齐缓冲区
void FlagM_IOUart_Printf(char *fmt,...)
{
uint32_t length;
va_list ap;
va_start(ap,fmt);
vsprintf(usart_txBuff,fmt,ap);
va_end(ap);
length=strlen((const char*)usart_txBuff);
IO_UART_SendData(usart_txBuff,length);
}
从机部分(时钟来来自主机):
IO_CLK :外部中断脚(EXIT)上升下降沿触发
IO_RX :接受输入脚(GPIO)
IO_TX :数据输出脚(GPIO)
基本配置比较简单
代码部分:
核心部分一样的,差异就是 主机在定时回调里面处理数据,而从机则是在外部中断中回调里处理数据
if(GPIO_Pin == IO_CLK_Pin)
{
if(IO_UART_CLK == GPIO_PIN_RESET) //RX 时钟引脚为低电平
{
IO_UART_Time_CountDown_Deal(); //处理虚拟串口接收数据
IO_UART_ReciveData(); //处理接受的数据
if((IO_UART_RXD == GPIO_PIN_RESET)&&(IOUart_h_Struct.IO_UART_StartRxFlag == 0))
{
if(IOUart_h_Struct.IO_UART_RecvStat == COM_STOP_BIT)//状态为停止位
{
IOUart_h_Struct.IO_UART_RecvStat = COM_START_BIT; //设置状态为起始位
IOUart_h_Struct.IO_UART_StartRxFlag = 1; //开始接收
IOUart_h_Struct.IO_UART_RecvData = 0; //接收数据
IOUart_h_Struct.IO_UART_RxTimeOut = _IO_RX_TIMECOUNTDOWN;//接收到数据把接收超时清零
}
}
}
else if(IO_UART_CLK == GPIO_PIN_SET)
{
//这里是数据发送部分 上升沿发送数据
IO_UART_Send_Buff();
}
}
如有问题可以加群讨论: 764284134