在前三篇文章中由简到烦的介绍了模拟串口的设计规则,但是在前三篇文章中所实现的方法并不能满足我们在实际工程中的使用。在这篇文章中,我将详细的描述模拟串口的实现,并提供相关代码来供大家参考。
1 原理
为了书写的方便,我将使用我的模版文件,关于模版文件的详细介绍请参考这篇文章。同样的,我们认为一个字节是10个位【起始位(1bit)+数据位(8bit)+停止位(1bit)】。
同样的为了方便,我们先实现发送功能再实现接收功能。为了扩大本文章所述内容的适用性,我将使用MAX3485芯片,关于这款芯片的使用方法我将进行详细介绍,同时也请参见《》。
1.1 设计逻辑
发送数据:串口调试助手1发送数据——调用模拟串口发送——模拟串口发送数据——串口调试助手2接收数据
接收数据:串口调试助手2发送数据——模拟串口接收(中断接收)——模拟串口接收数据——调用自带串口——自带串口发送数据——串口调试助手1接收数据
1.2 IO口模拟串口发送原理
空闲状态为高电平,开始位为0,然后发送8个数据位,然后是奇偶校验位,停止位为高电平。数字电路中只有0、1两种状态,这是我们用IO口可以实现的,我们认为高电平是1,低电平是0。也就是说我们用只到了IO口的输出功能(对于实现TX功能的IO口而言),那么到底发多长时间的高电平呢?这是由TIM2决定的,TIM2通过计数器实现。这个时间取决于什么呢?取决于波特率。也就是说,只要我们初始化设置好了TIM2我们就不需要考虑时间问题了。为方便我们现发送低位,然后逐渐右移来发送高位。如何判断发送完一个字节呢?我们认为10位为一个字节。如何判断发送玩所有的字节呢?我们使用一个缓存区(也就是数组)缓存区的字节发送完了我们就认为发送完了。
1.3 IO口模拟串口接收原理
发送我们可以自己来控制(也就是通过函数开相关中断),但是接收怎么处理呢?我们认为不发送的状态就是接收状态,也就是说,只要不是发送,我们接收数据的中断就是使能的。如何判断数据来了呢?我们知道起始位是高电平,因此我们只用到了IO口的输入功能(对于实现RX功能的IO口而言),我们在初始化状态下设置此IO口为低电平,只要IO口接收到了高电平我们就会知道。我们是怎么知道的呢?因为这时会产生一个IO外部中断,所以我们就可以进入中断函数来操作了。进入IO外部中断函数之后我们就开启TIM2的比较捕获中断来判断接下来的8个位是什么电平。开启比较捕获中断后我们就进入的比较捕获中断函数,在这个函数中我们判断IO口的位来给我们的接收字节变量赋值。当接收了10位以后(起始位的时候就开始计数了)我们就认为接收了1个字节。当接收了8个字节的时候我们就可以放入我们的缓存区(数组),接收字节容量的大小根据缓存区的大小来决定。我们接收制定的大小以后,我们就可以在更新中断函数里面写相关的操作代码了,我们这里写的是调用自带串口发送数据。
2 实现过程
同样的,在实现过程中,我们在工程文件夹SimUART中共分了4个文件夹
System:存放系统文件;
Project:存放项目文件;
User:存放main.c和UserApp.c;
My_Lib:存放其它常用的文件。
根据我们将用到的单片机的资源,我们在My_Lib中分了5个文件夹,分别是:
Time:与定时器和中断相关函数的文件。
SimUART:与模拟串口相关函数的文件。
USART:与串口相关函数的文件(因为我们需要串口进行测试)。
EXTI:与中断相关函数的文件,我们用到中断初始化。
MAX3485:我们用到MAX3485芯片。
下面我贴出相关函数的.c文件,而.h文件省略不写,有需要的同学可以点击下载使用。我的编程环境是IAR,需要自己建立IAR工程。下面详细介绍(Project和System省略不写,其中System只用了stm8s.h)。
2.1 一切从main()函数开始
同样的,我们建立完工程后需要从main()函数开始,我们使用的是模版工程(大家可以点击查看设计思路),所以我们main.c不需要做任何改动,我们从初始化开始。
2.2 初始化
初始化相关代码如下:
/*************************************************
*Function: All_Config
*Calls: void
*Called By: main.c
*Input: void
*OUTPUT: void
*Return: void
*DESCRIPTION: 1.初始化函数
2.选择内部16M晶振
*Others: nothing
*************************************************/
void All_Config()
{
//必备初始化
Clock_Config(); //时钟初始化
PilotLED_Init(); //初始化指示灯
TIM4_Init(); //定时器4初始化
//常规初始化
UART_Init(SYS_CLOCK, 9600); //串口初始化
MAX3485_Init(); //485通讯
//功能初始化
/*****************************************
1.模拟串口
*****************************************/
SimUART_IO_Init(); //模拟串口IO口初始化
TIM2_Init(); //定时器2初始化
EXTI_Init(); //中断初始化
}
我们的指示灯使用的是TIM4来控制,所以需要初始化TIM4。如果不需要485通讯可以把这里的485初始化去掉。其他的不说,我下面只介绍模拟串口的初始化功能,其他请参见代码。根据原理我们知道,我们需要用到TXD管脚的输出功能和RXD管脚的输入功能,具体代码如下:
/*********************************************************
*Function: IO_Init
*Calls: void
*Called By: All_Config
*Input: void
*OUTPUT: void
*Return: void
*DESCRIPTION: 1.模拟串口IO口初始化(RX:PC3 TX:PC4)
2.用到的模拟串口IO口的初始化PC4:发送 PC3:接收
3.使用不同的IO口作为模拟串口的时候,我们在使
用中断(这里使用的TIM2)需要选用不同的中断向量。
*Others: nothing
*********************************************************/
void SimUART_IO_Init()
{
/************************************
1.模拟串口IO口初始化
2.PC4:发送
3.PC3:接收
************************************/
//TXD:TXD位推挽输出 PC4
SimUART_PORT->ODR |= SimUART_PIN_TX;
SimUART_PORT->DDR |= SimUART_PIN_TX;
SimUART_PORT->CR1 |= SimUART_PIN_TX;
SimUART_PORT->CR2 &= ~SimUART_PIN_TX;
//RXD:悬浮输入 高电平 PC3
SimUART_PORT->IDR |= SimUART_PIN_RX;
SimUART_PORT->DDR &= ~SimUART_PIN_RX;
SimUART_PORT->CR1 &= ~SimUART_PIN_RX;
SimUART_PORT->CR2 &= ~SimUART_PIN_RX;
}
2.3 模拟串口发送数据
完成时钟、定时器、中断的初始化以后我们就可以开始主体程序的设计了,逻辑伪代码如下:
void SimUART_TxByte( u8 SendData )
{
将发送数据放入缓存区;
缓存区待发送计数++;
缓存区放满了 就不放了;
开启发送 相关的中断;
}
void SimUART_TxBytes(const u8* SendData , u8 len )
{
//循环添加,直到添加完毕
while( len )
{
将发送数据放入缓存区;
需要添加的计数--;
需要发送的数据的指针++;
缓存区待发送计数++;
缓存区放满了 就不放了;
}
开启发送 相关的中断;
}
具体实现代码如下:
/*************************************************
*Function: SimUART_TxByte
*Input: u8 SendData:需要发送的字节
*OUTPUT: void
*DESCRIPTION: 发送一个字节
*************************************************/
void SimUART_TxByte( u8 SendData )
{
TxDataBuf[SimUART_SendBuf_IN] = SendData;
SimUART_SendBuf_IN++;
//缓存区放满了 就不放了
if( SimUART_SendBuf_IN >= sizeof(TxDataBuf) )
{
SimUART_SendBuf_IN = 0;
}
//开启发送 相关的中断
TIM2->IER |= TIM2_IER_UIE; //更新中断使能
}
/*************************************************
『本文转载自网络,版权归原作者所有,如有侵权请联系删除』