内容节选自本人毕业论文,有改动。感谢追光拾忆者提供的思路,来自STM32普通串口一对多通信/USART无需添加485拓展_追光拾忆者的博客-CSDN博客_串口一对多
当我们使用的单片机uart接口不足,我们可以利用UART实现总线型一主多从通讯结构,如图所示。
图1 UART总线型通讯结构
如果直接将从机的TX连接到主机的RX,那么从机之间的TX引脚就连接在一起,如图2所示。当Slave1尝试向Master发送信息时,由于UART以低电平为起始标志,Slave1的TX引脚为低电平,而Slave2的TX引脚为高电平。此时电流由Slave2的TX引脚流向Slave1的TX引脚,Slave1的TX引脚灌电流升高。由于器件存在内阻,电流增大导致节点p电压升高,最终我们看到Slave1的TX引脚电平升高了。
由于引脚电平升高,Master的RX端接收不到Slave1的TX端发来的低电平,串口通信也就无从开始。
图2 灌电流引起引脚电平升高
解决方案的其中之一是将从机的通信引脚设置为开漏输出。在开漏输出模式下,引脚在高电平逻辑下既不输出高电平,也不输出低电平,而是高阻态,需要外接上拉电阻以输出高电平。由于上拉电阻的存在,灌电流非常小,也就避免了引脚电平升高的问题。但是,我们往往无法对从机进行二次开发,即无法改变其通信引脚的输出模式。
图3展示了另外一种解决方案,此方案中,从机的TX引脚与二极管阴极相连,二极管阳极连接到主机的RX引脚。由于二极管的单向导通性,灌电流将被限制在一个极小的范围,相当于断路,从而避免了引脚低电平升高的问题。由于主机的RX引脚是一个上拉输入的状态,因此二极管截止的时候也不会影响高电平的采样。
图3 使用二极管限制灌电流
本人制作了一个简陋测试板来验证该设计(简陋得一,几分钟画完),原理图及3d示意图如下。
经验证,该方案可以实现一对多通讯。 实际上,根据原理,N+1个从机只需要N个二极管,这个就留给你们去验证吧。
测试板开源链接:uart总线 - 立创EDA开源硬件平台
在总线通讯中,通讯协议的重要性不容忽视,如果通讯节点不分主从,随意发送,可能出现多个节点同时发送信息的情况,那将是一场灾难。
1.主从区分
通信保护的第一步,是将除了主设备以外的所有设备,改造为从机。从机总处于被动状态,只有主机向从机发送信息时,从机才能进行回应,其余时刻保持静默,由主机对所有从机根据需要进行轮询。
2.通信流程保护
除了主从区分之外,我们希望对每个线程的单次收发流程进行保护。在一个线程使用UART通信的过程中,如果RTOS切换到另一个使用UART通信的线程,可能造成两个从机同时发送信息的情况,这是我们不愿意看到的。
因此,我们在UART总线系统中,提供总线锁定与解锁接口uart_lock与uart_unlock,其原理为互斥量的获取与释放。代码如下:
int uart_lock(void)
{
if (osOK != osMutexWait(uart_mutex_id, osWaitForever))
{
TRACE( 0 , "%s %d osMutexWait fail",__func__,__LINE__);
return 0;
}
return 1;
}
int uart_unlock(void)
{
if (osOK != osMutexRelease(uart_mutex_id))
{
TRACE( 0 , "%s %d osMutexRelease fail",__func__,__LINE__);
return 0;
}
return 1;
}
线程在发送信息前使用uart_lock来锁定UART资源,一轮通信完成后使用uart_unlock解锁UART资源,以供其他线程使用。加入该互斥锁后,任一时刻,只能够有一个线程访问UART资源,实现了对单次通信过程的保护。
互斥锁会带来一个死锁的问题。如果一个线程的通信过程出现故障,如某从机通信线路断开,该线程无法收到从机回应,将维持等待状态而不释放锁,其他线程也无法获取锁,UART系统将无法继续运行。为此,需要在每个可能持有锁的线程中为互斥锁增加一个保护措施,当某线程持有锁的时间过长,互斥锁将被释放。代码如下:
event = osMessageGet(_my_msg_id, 100);
if(event.osStatus != osOK)
{
TRACE( 0 , "%s %d overtime",__func__,__LINE__);
uart_unlock();
return;
}