本帖最后由 Gavin3389 于 2021-11-16 11:29 编辑
#申请原创# 485自动收发,为你节省一根IO线
@21小跑堂
背景:资源使用过剩,IO引脚紧张。 目的:验证485自动收发电路,确定是否可以节省收发控制脚,满足日常应用。 简述: 众所周知,485属于半双工差分通信,传输距离比232远,抗干扰强,且总线可以并联使用。但是,在常规的应用中,mcu端除了收、发数据线外,还需要额外的一个IO引脚,作为收发的方向控制。 原理分析:
首先,RS-485的电气特性: 逻辑‘1’以两线间的电压差为+(2—6)V表示; 逻辑‘0’以两线间的电压差为-(2—6)V表示。 不管怎么变换,这里的电气特性‘1’和‘0’是不会变的。 那么,已此为基准,先从理论上分析下收发状态。
接收时: 默认没有数据时,TX为高电平,三极管导通,RE为低电平使能, RO收数据有效,MAX485为接收态,正常接收数据。
发送时:. 发送“1”时,TX为高电平,三极管导通,DE为低电平,此时收发器处于接收状态, 驱动器就变成了高阻态,也就是发送端与A\B断开了, 此时A\B之间的电压就取决于A\B的上下拉电阻了,A为高电平、B为低电平, 也就成为了逻辑“1”了。 发送“0”时,TX为低电平,三极管截止,DE为高电平,驱动器使能, 此时正好DI是接地的,也就是低电平, 驱动器也就会驱动输出B为1,A为0,也就是所谓的逻辑“0”0了。
理解自收发的作用,关键是巧妙的使用了收发控制RE和DE的作用, 尤其是DE为0时,驱动器与A\B之间就是高阻态,也就是断开状态,而且A\B都要有上下拉电阻。然后就有了逻辑0-1之间的切换了。所以很巧妙。
然而这里依然需要注意两点: 1、 虽然通过这种形式,可以摆脱“收发控制”,但是对于485来说,依然是“半双工”的通讯模式,如果是全双工,就不行了,因为TX为1时,接收使能,此时从机如果回复数据,那么也就乱了。 2、 TX,RX引脚均需要上拉电阻,这一点特别重要。 开始的时候,所参照的电路就没有上拉电阻,折腾了半天。
~~~~~~~~~~~~~~~~~~~~~~~好了,原理分析结束,开始下一步~~~~~~~~~~~~~~~~~~~~~~~~
电路搭建: 找了半天,也没找到合适的PCB,只能委曲求全,在通用板上自己焊接,问题是,手里都是贴装器件,通用板却是插装通用板,无奈,只好开启对付模式,勉勉强强的算是把电路焊接完成,好在电路简单,基本上按照原理图的方向和顺序进行焊接。
TTL端:左侧2.54单排插针,自上而下定义为VDD3V3\RX \TX \GND 485端:右侧绿端子,自上而下定义为B \A
怎么测试呢?手里有个之前新唐的开发板M471KIV1.1 TTL端连接开发板,485端连接PC通过串口助手显示数据。 这里测试的时候, 本来想直接使用例程代码的,通过串口0进行收发测试,但是不知怎的了,就是不能进入接收中断,这个例程明明跑过的,现在却不通了。 然后,改变了下策略,使用串口0的发送,串口1的接收, 将串口1的接收数据通过串口0发送出来,这要注意的是,保证两个串口的波特率一致。
初始化代码如下: /*----------------------------------------------------------------------*/
/* Init UART0 */
/*----------------------------------------------------------------------*/
void UART0_Init(void)
{
SYS_UnlockReg();
/* Enable UART clock */
CLK_EnableModuleClock(UART0_MODULE);
/* Select UART clock source from HIRC */
CLK_SetModuleClock(UART0_MODULE, CLK_CLKSEL1_UART0SEL_HIRC, CLK_CLKDIV0_UART0(1));
/*----------------------------------------------------------------------*/
/* Init I/O Multi-function */
/*----------------------------------------------------------------------*/
/* Set GPB multi-function pins for UART0 RXD and TXD */
SYS->GPB_MFPH = (SYS->GPB_MFPH & ~(SYS_GPB_MFPH_PB12MFP_Msk | SYS_GPB_MFPH_PB13MFP_Msk)) |
(SYS_GPB_MFPH_PB12MFP_UART0_RXD | SYS_GPB_MFPH_PB13MFP_UART0_TXD);
SYS_LockReg();
SYS_ResetModule(UART0_RST); // Reset UART0
UART_Open(UART0, 9600); //Configure UART0 and set UART0 baud rate
/* Enable UART RDA and THRE interrupt */
NVIC_EnableIRQ(UART0_IRQn);
UART_EnableInt(UART0, UART_INTEN_RDAIEN_Msk);
// UART_EnableInt(UART0, (UART_INTEN_RDAIEN_Msk | UART_INTEN_THREIEN_Msk));
}
/*----------------------------------------------------------------------*/
/* Init UART1 */
/*----------------------------------------------------------------------*/
void UART1_Init(void)
{
SYS_UnlockReg();
CLK_SetModuleClock(UART1_MODULE, CLK_CLKSEL1_UART1SEL_HIRC, CLK_CLKDIV0_UART1(1));
CLK_EnableModuleClock(UART1_MODULE);
SYS->GPB_MFPL = (SYS->GPB_MFPL & ~(SYS_GPB_MFPL_PB2MFP_Msk | SYS_GPB_MFPL_PB3MFP_Msk)) |
(SYS_GPB_MFPL_PB2MFP_UART1_RXD | SYS_GPB_MFPL_PB3MFP_UART1_TXD);
SYS_LockReg();
SYS_ResetModule(UART1_RST);
UART_Open(UART1, 9600);
NVIC_EnableIRQ(UART1_IRQn);
UART_EnableInt(UART1,UART_INTEN_RDAIEN_Msk);
}
中断处理: /*---------------------------------------------------------------------------------------------------------*/
/* ISR to handle UART Channel 0 interrupt event */
/*---------------------------------------------------------------------------------------------------------*/
void UART0_IRQHandler(void)
{
if (UART_GET_INT_FLAG(UART0,UART_INTSTS_RDAINT_Msk))
{
while(!UART_GET_RX_EMPTY(UART0))
g_u8RecData[g_u32comRhead++] = UART_READ(UART0);
UART_Write(UART0, g_u8RecData, g_u32comRhead);
}
g_u32comRhead = 0;
}
void UART1_IRQHandler(void)
{
if (UART_GET_INT_FLAG(UART1,UART_INTSTS_RDAINT_Msk))
{
while(!UART_GET_RX_EMPTY(UART1))
{
UART1_RX_BUF[USART1_RX_STA] = UART_READ(UART1);
USART1_RX_STA++;
// UART_ClearIntFlag(UART1, UART_INTSTS_RDAINT_Msk);
}
}
if(USART1_RX_STA>(USART1_REC_LEN-1))USART1_RX_STA=0;//接收数据错误,重新开始接收
UART_Write(UART0,UART1_RX_BUF,1);
USART1_RX_STA=0;
UART_ClearIntFlag(UART1, (UART_INTSTS_RLSINT_Msk| UART_INTSTS_BUFERRINT_Msk));
// if(UART1->FIFOSTS & (UART_FIFOSTS_BIF_Msk | UART_FIFOSTS_FEF_Msk | UART_FIFOSTS_PEF_Msk | UART_FIFOSTS_RXOVIF_Msk))
// {
// UART_ClearIntFlag(UART1, (UART_INTSTS_RLSINT_Msk| UART_INTSTS_BUFERRINT_Msk));
// }
}
主函数,这里其实没什么了,还是贴一下吧 int32_t main(void)
{
/* Init System, IP clock and multi-function I/O. */
SYS_Init();
UART0_Init();
UART1_Init();
// UART2_Init();
printf("\n\nCPU [url=home.php?mod=space&uid=72445]@[/url] %dHz\n", SystemCoreClock);
printf("+-------------------------------------------------+\n");
printf("| M471KI8AE Study test |\n");
printf("+-------------------------------------------------+\n\n");
// GY39_Init();
//
// GY39_flag=0;GY39_num=0;
//
GPIO_Init();
// UART_Write(UART1,CSData,10);
delay_ms(500);
// ASK_GY39_T_Pa_RH_He();
while(1)
{
// delay_ms(500);
// UART_Write(UART0,UART1_RX_BUF,10);
};
}
这里是9600波特率的截屏:
后面又测试了其它几组波特率,发现在波特率过高的时候,会有乱码出现。 现在不确定是面包板飞线的原因,还是三极管8050的原因。 手里没有其他三极管了,所以,这里暂时没有验证。
结论: 通过上述电路搭建和测试过程,可以看出。在一定的波特率下,采用这种电路形式是可以实现485的自动收发的,从而达到节省控制io引脚的目的。 有兴趣的朋友,可以亲自验证,尝试用到自己的电路中。
后记:
说道这里,网上还有一种电路,电路如下,这也是最开始看到,咋一看好像差别不大,当时就拿来尝试,结果,半天也不通,这才静下心来,一步步的分析电平变化,觉得有问题,又回去百度,才确定了上述的电路原理。而这里其实最关键的就是,tx、rx没有上拉电阻。
网上这个电路的博客,帖子其实还是很多的,也许他们的测试条件不一样,或许MCU端默认加了上拉,但是作为原理验证和测试,单纯的按照这个电路,我是没有成功的,这里拿出来说一下,只是单纯的强调下上拉电阻的重要性。
至此,
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~(完)~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|