STM32串口(RS485)通信与技巧

一、概述
1. STM32引入了同步异步串口USART,但一般不使用同步功能,只用异步功能,同步和异步详见本博客:同步/异步,串行/并行,SPI/I2C/UART/RS485/CAN等简单介绍与区别
2. 由于STM32时钟系统还未总结,对串口波特率等详细讲解暂无说明,但可以看一下博文Modbus协议通信要点_fengel_cs的专栏-CSDN博客,知道波特率与bit传输关系。
3. 由于RS485基于uart通信,这里会提到485的一些通信技巧。由于RS485属于半双工,因此多了一个片选控制,即收发之前要切换片选引脚。

二、uart使用初始化
1. uart时钟初始化,使用哪个uart,就必须使能uart时钟。STM32的uart属于一个外设,因此时钟由外设时钟使能寄存器控制(APB1ENR和APB2ENR),根据不同的uart号决定在APB1ENR还是在APB2ENR中设置。
2. 串口参数的配置,比如是否要奇偶校验,是否由停止位,数据位长度和波特率等设置。一般来说基于HAL库就是设置UART_HandleTypeDef结构体。
       实质是设置串口相关控制寄存器,基于ARM架构的CPU都是这样,不管怎么封装最终都是寄存器操作。
3. 相关GPIO引脚的初始化,cpu的相关引脚大部分都是可复用,这里需要设置用于uart及引脚上下拉等功能。
        HAL库一般是调用HAL_GPIO_Init函数实现。
4. 中断使能,HAL库一般调用HAL_XXX_SetPriority函数设置uart的IRQ中断优先级,HAL_XXX_EnableIRQ使能uart的IRQ中断通道。
5. 中断服务函数、收发函数、清缓冲区函数等封装和修改。如果使用DMA,还得初始化DMA。

三、uart的中断服务函数
1. STM32的中断注册一般在启动代码(.s文件)中进行,如果是ucosII,会有相应中断向量表等接口,但最终都是在启动代码中进行。
2. STM32中断服务函数基于HAL库的接口固定,即不能被用户更改,即接口HAL_UART_IRQHandler。也就是中断发生后就会调用注册的HAL_UART_IRQHandler函数。此函数的参数一定要与HAL_UART_Init函数参数一样,即串口的总结构体UART_HandleTypeDef。
3. 当uart相关中断发生后,程序运行到此中断服务函数,然后根据如下寄存器标记得到具体中断类型:
    UART_FLAG_PE
    UART_IT_PE
    UART_FLAG_FE
    UART_IT_ERR
    UART_FLAG_NE
    UART_IT_ERR
    UART_FLAG_ORE
    UART_IT_ERR
    UART_FLAG_RXNE
    UART_IT_RXNE
    UART_FLAG_TXE
    UART_IT_TXE
    UART_FLAG_TC
    UART_IT_TC

四、发送数据
1. 发送数据前的操作,开启接收中断,便于发送完后接收应答数据。HAL库一般使用HAL_UART_Receive_IT()接口。关于此接口的说明会在接收数据中详细说明。

2. 发送数据前信号量获取
        由于有可能有多个任务同时使用uart发送数据,为了避免冲突,定义一个信号量,在发送前只有获取成功才发送。否则阻塞发送任务,直到获取或超时。
   
3. 发送数据的HAL接口一般为HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
   (1) 此函数第一个参数为串口句柄,第二个参数为要发送数据的缓冲区地址,第三个参数为要发送数据长度。
   (2) 此函数中,会把指向发送数据的指针赋值给HAL库发送缓冲区指针即huart->pTxBuffPtr,并把发送数据的长度分别赋值给
       huart->TxXferSize和 huart->TxXferCount以备后面使用。   
   (3) 最后使能发送缓冲区为空的中断(注意不是发送移位寄存器移完中断)
   
4. 发送中断标志说明,即UART_IT_TXE 和 UART_IT_TC 标志说明。
   (1) UART_IT_TXE 发送缓冲区空中断:如果使能这个中断,中断发生后,代表发送缓冲区为空,可以往数据寄存器写入数据了,但并不代表数据发送完成了。
   (2) UART_IT_TC 发送完成中断:如果使能此中断,中断发生后,代表UART在缓冲区的数据发送完成了,并且转移到移位寄存器上的数据也全部放到总线上。
   (3) 这两个标志的区别在于:它们分别表示数据在发送过程中,在两个不同的阶段中的完成情况。TXE 表示数据被从发送缓冲区(程序可以看到)中取走,转移到的移位寄存器(程序不可以看到)中,此时发送缓冲是空的,可以向其中补充新的数据了。而 TC 则表示最后放入发送缓冲区的数据已经完成了从移位寄存器向发送信号线 Tx 上的转移。所以,判定数据最终发送完成的标志是 TC,而不是 TXE.

       通过对上述两个中断标记的说明,在HAL_UART_Transmit_IT函数中使能了UART_IT_TXE中断(__HAL_UART_ENABLE_IT(huart, UART_IT_TXE))。
   即调用一次HAL_UART_Transmit_IT函数就会使能一次UART_IT_TXE中断。    

5. 上面调用HAL_UART_Transmit_IT函数发送数据后,会指定发送数据和数据长度长度,并打开发送缓冲区为空中断。这样当发送完一个字节后,发送缓冲区为空中断就会产生,就会响应上面使能的UART_IT_TXE中断。因此系统会调用总中断入口HAL_UART_IRQHandler(UART_HandleTypeDef *huart)函数,
在此函数中用如下方法判断是UART_IT_TXE中断则调用UART_Transmit_IT(UART_HandleTypeDef *huart)函数:
   tmp1 = __HAL_UART_GET_FLAG(huart, UART_FLAG_TXE);
   tmp2 = __HAL_UART_GET_IT_SOURCE(huart, UART_IT_TXE);
   if((tmp1 != RESET) && (tmp2 != RESET))
   {
      UART_Transmit_IT(huart);
   }
        在UART_Transmit_IT函数中,根据串口句柄中的huart->pTxBuffPtr和huart->TxXferCount(这两个变量已经在HAL_UART_Transmit_IT函数中赋值)
决定HAL_UART_Transmit_IT函数要求要发送的数据是否发送完成,如果huart->TxXferCount等于0代表完成,完成后会禁止发送缓冲区为空的中断,并打开移位寄存器传输完成中断(即全部数据成功丢到uart串口总线上的中断)。
        否则就发送一个数据并递减huart->TxXferCount变量,并且不禁止UART_IT_TXE中断,这样发送完一个字节UART_IT_TXE中断又会被触发,又会循环地调用UART_Transmit_IT函数进行发送数据并并递减huart->TxXferCount变量,然后判断其是否为0,如果为0,就禁止发送缓冲区为空的中断(UART_IT_TXE中断),并打开UART_IT_TC中断,侦听移位寄存器是否移完所有数据到信号线上,如果是就响应UART_IT_TC中断。

6. 发送数据彻底完成中断UART_IT_TC
        前面第5点描述了如果所有数据发送完成,但是并没有响应UART_IT_TC中断,即移位寄存器可能还不是空的,数据还没有全部移位到信号线上。假如数据全部移动到信号线上,就会触发UART_IT_TC中断。
        同样,UART_IT_TC中断发生后系统会调用中断处理总入口HAL_UART_IRQHandler函数,在此函数中通过下面方法判断中断类型为UART_IT_TC中断:
则调用所有数据彻底发送完成要结束的函数UART_EndTransmit_IT。   
  tmp1 = __HAL_UART_GET_FLAG(huart, UART_FLAG_TC);
  tmp2 = __HAL_UART_GET_IT_SOURCE(huart, UART_IT_TC);
  if((tmp1 != RESET) && (tmp2 != RESET))
  {
    UART_EndTransmit_IT(huart);
  }
        注意,UART_EndTransmit_IT函数是所有数据彻底发送完成,都已经全部移动到信号线上了,end结束了,没有处理器的事了。而UART_Transmit_IT函数代表一个字节传输完成要调用的函数,所有数据还未完成。
        UART_EndTransmit_IT函数中,首先禁止UART_IT_TC中断,因为已经发送完成,不需要这个中断了。然后会调用HAL_UART_TxCpltCallback回调函数,此函数名由HAL库决定不能更改,但是用户可以重写此函数内容。在此函数里面可以做一些特殊处理,比如RS485通信是半双工通信,发送完成后需要控制一根片选引脚从而控制485芯片切换到接收状态,然而现实通信中,就可以在此回调函数中进行切换最保险,因为已经确保数据已经发送完成(也可以延时个几百微妙)。
        如果有些设备响应较慢,发送完成后,为了保险起见,保证发送完成,可以在HAL_UART_TxCpltCallback回调函数中把一个初始值为0的全局变量设置为1,在时钟中断(OS_CPU_SysTickHandler中断函数,一般1ms调用一次)中,判断到此变量为1并且小于3,就对此变量加一,这样在其他任务中可以判断到此变量被加到了3,就切换485状态为接收模式。
        当然,有些485从设备响应特别快,如果发送完成后没有立即切换为接收状态,485主设备就有可能收到从设备的数据包的前几个字节丢失。因为主设备数据彻底发送完成后,从设备回应并已经把数据丢到总线上,但是主设备没有来得及切换485芯片为接收模式,等切换过来时已经晚了1ms左右就会出现这样的情况。

        切换收发状态,比如是485通信,既然发送已经完成,那么就需要切换到接收模式,实际应用中,接收模式也不是马上切换,因为移位寄存器把数据转移到了信号线,但是在信号线还需要时间传输,发送方可以再等1-5个时钟(时钟中断中赋值变量)再转换为接收模式。

五、接收数据
1. 假如是RS485通信,如果没有切换位接收模式,STM32不会接收到任何数据,也不会响应任何接收中断,哪怕对方已经向总线上丢了数据。

2. UART_IT_ERR/UART_IT_PE/UART_FLAG_PE这些标记用于串口收发过程中的中断优先级、错误等处理,HAL_UART_IRQHandler函数中进行。

3. 发送数据前一般会调用HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)函数中使能接收中断。
        这样,只要接收到一个字符,就可以通过如下方式判断是否中断从而调用UART_Receive_IT(huart)函数:
  tmp1 = __HAL_UART_GET_FLAG(huart, UART_FLAG_RXNE);
  tmp2 = __HAL_UART_GET_IT_SOURCE(huart, UART_IT_RXNE);

        在UART_Receive_IT函数中会对接收到的数据进行存储,并进行一些其他处理,这些处理主要分为两类:
    (1)链路层协议中规定了每一包数据中有接收结束标记,那么只要接收到此标记,就调用__HAL_UART_DISABLE_IT(huart, UART_IT_RXNE)结束
         此轮接收,代表此帧数据完成(比如485主机发了一包后,收到从机应答包,就是根据此标记决定应答包全部收完)。
    (2)链路层协议中没有结束标记,就只能延时,如果延时一段时间仍然没有收到数据,认为此帧数据完成。
         比如Modbus协议中,如果主机发了一包给从机,从机应答后,主机收数据,如果主机收到最后一个数据后,3.5个字符长时间后仍然没有收到数据,就认为从机应道数据帧接收完成。

        在UART_Receive_IT(huart)函数中会根据上述情况判断是否禁止接收中断。主要看链路层协议,比如mosbus协议帧中本来就没有结束标记,最后两个字节为CRC校验,我们不能判断是否一帧接收完,那么就不要禁止接收中断。如果是有结束标记的链路协议,那么收到一帧中结束
标记后,代表一帧接收完毕,我们可以禁止接收中断。
        当然,对于modbus协议本身规定,当字符与字符间不能超过1.5个字符时长,帧与帧之间需要间隔3.5个字符长度,具体要根据波特率算。mosbus协议本身还规定,主机发起请求后,若从机300ms内未应答,则可以认为本次请求失败。

4. 这里描述对于链路层协议没有结束标记的通信具体过程,以modbus协议为例,在UART_Receive_IT函数中是永远都不会关闭接收中断的,
        因为它是通过延时来决定是否接收完成。假如接收到一个字节数据,接收中断被触发,由uart的中断总入口HAL_UART_IRQHandler中,会调用UART_Receive_IT函数,在此函数中,使一个全局变量A为1(这个变量在发送数据前会被设置位0),然后在时钟中断中(假如1ms进行一次时钟中断)判断这个变量是否大于0,如果大于0,代表已经接收到数据,并自加这个变量,当自加到某个值B后,就认为modbus一帧接收完毕,置位接收完成标记C,这样链路层就根据接收完成标记C判断是否成功收到从机的回应,并开始解析回应是否正确。
   
5. 上面方法的妙处就在于,接收到数据让A为1代表接收到数据,每一次接收到一个字符都会置为1,如果没有数据可接收了,那么时钟中断中对A自加就会顺利进行(因为中断服务中的UART_Receive_IT函数不会置1了),直到自加到某个值B,然后判断是否到达B从而设置C,链路层判断到如果C为1,则接收结束,如果在自加过程中有干扰,偶尔又接收到一个数据,那么A任然会被设置为1,那么时钟中断中判断到只要大于0,又把以前自加结果清掉并重新自加A,继续判断A是否自加到了B,从而决定是否要置位C。

        通过这样的方式,实现了链路层协议中帧没有结束符的帧的可靠接收。也使用了modbus协议自身特点,帧与帧之间间隔不小于3.5个字符,所以A自加到B值得时间只需要大于3.5个字符即可。


六、总结

1. 本文主要总结了STM32的UART如何初始化并启用;然后总结了STM32的的uart的中断系统;最后总结了根据不同协议,比如modbus无结束符和有结束符协议,uart的收发套路。

2. 当然,对于没有结束符的协议,还可以自己加一个结束符,比如0x0d和0x0a。另外对于接收数量统计和接收标记等全部可以放在同一个全局变量。

        比如一个int型变量的bit31可以作为接收标记,bit30可以作为发送标记,剩下的bit0-bit29为接收数量,这个变量自加后,由于预先知道自加长度不会影响bit30-31,所以一个32位的全局变量可以做很多事。

  • 1
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
STM32与RS485传感器通信的步骤如下: 1. 首先,配置STM32串口通信功能。使用STM32的GPIO和USART模块,将其中一个USART配置为RS485模式。这可以通过设置USART的控制寄存器来实现,具体的设置可以参考STM32的官方文档和参考手册。 2. 确保RS485传感器的硬件连接正确。将RS485传感器的数据线连接到STM32开发板的USART接口的RX(接收)和TX(发送)引脚。另外,还需要将RS485传感器的DE(数据使能)和RE(接收使能)引脚连接到STM32开发板上的GPIO引脚。 3. 在STM32的代码中,使用串口通信的相关函数来实现与RS485传感器的通信。首先,使用GPIO控制DE和RE引脚的状态,以控制数据的发送接收。当需要发送数据时,将DE引脚置高,RE引脚置低;当需要接收数据时,将DE引脚置低,RE引脚置高。 4. 使用USART的发送函数发送需要传输的数据到RS485传感器。可以使用STM32HAL库或者直接操作USART寄存器来实现数据的发送。 5. 等待RS485传感器返回数据。当需要接收数据时,使用USART的接收函数从USART接收缓冲区中读取数据。可以通过轮询或者中断方式来接收数据。 6. 根据RS485传感器的通信协议,解析接收到的数据。根据具体的协议,可以使用相关的函数或者算法来解析数据,并提取出需要的信息。 需要注意的是,在使用STM32与RS485传感器通信时,要确保使用的波特率和数据格式(如数据位数、停止位数)与RS485传感器的设置相匹配。此外,还需确保电源供应稳定,保持良好的地线连接,以避免通信干扰和误差。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值