新闻  |   论坛  |   博客  |   在线研讨会
这个寄存器多余了吗?
鱼鹰谈单片机 | 2021-06-17 06:51:21    阅读:401   发布文章

上次写过一篇笔记《介绍一个高效无隐患输出 IO 的方法》,介绍了如何避免直接操作 ODR 导致的隐患问题,然后有道友留言对以下代码提出了疑问:

void out_data(uint8_t byte)
{
  GPIOA->BSRR = ((uint16_t)byte << 8);  // set
  byte = ~byte;  // 打断后续运行
  GPIOA->BRR = ((uint16_t)byte << 8);  // reset
}

那就是两条操作寄存器的代码间如果产生中断或者任务切换了,会不会产生影响,它的电平是否处于不稳定状态。

这里鱼鹰来解释一下,并给出具体的解决方案。

首先中断影响,因为中断处理时间几乎在微妙级别,如果你的中断处理在毫秒级别,那么你的系统实时性一定不怎么高。

所以中断在这里造成的影响比较小。

为什么明明分开操作了,影响还是比较小呢,这是因为如果你这个代码用于并口总线驱动,那么总线驱动一般会用另外的 IO 变化来确定并口数据的稳定性。

比如说 SPI 总线(非并口),会定义 CLK 上升沿或下降沿才开始采集数据,并口一般也有这样的规定,这样就保证了即使并口数据没有一次性输出,因为另外的信号线没有产生下降沿或下降沿,从机也不会对并口上的数据采样的。

但是不能保证有些并口总线规范会定义最长的时间,但即使有,微秒级别也差不多没什么问题。

但还有一种情况是系统使用了 RTOS 。

这样会导致切换到另一个线程,而这个线程的执行时间根本不确定,执行毫秒级别是正常的事情,所以,这种情况该如何处理呢?

两种方法,关中断或关调度器。(关于这些内容可以看历史文章,比如《信号量保护之禁止中断》,《嵌入式系统优先级详解》等)

但是对于几行代码就要使用这些代码,还是太奢侈了一些,虽然对系统的效率影响不大,但毕竟还是不爽,那么是否有更好的办法可以解决这个问题。

有个道友的留言提醒了鱼鹰,就是有一个寄存器是可以同时操作 set 和 reset 的。

以前初学 STM32 的时候,看到这个寄存器可以同时操作 set 和 reset,而另一个寄存器也可以操作 reset,以为功能重复了,谁知道在这里等着鱼鹰呢。

这个寄存器就是 BSRR。

1.png

通过它,就能用一条语句完成多个 IO 的同步操作。

(这里有个错误,应该是操作 32 bit,毕竟它在库函数中可是 32 bit 数据。另外特别注意框出来的地方,不过除非你的代码有问题,不然没人会没事同时操作一个 IO 的 BSy、BRy)。

在这里特别感谢这几位道友的留言提醒。

上面的代码可以改成这样:

// 仅用于 8 位数据 假设使用 PA8~15
void out_data(uint8_t byte)
{
  GPIOA->BSRR = ((uint32_t)((uint8_t)~byte) << (0 + 16)) | ((uint32_t)byte << 0);
  // GPIOA->BSRR = ((uint32_t)(~byte) << (8 + 16)) | ((uint32_t)byte << 8); // 错误写法
}
// 使用宏,更高效,任意位数
#define GPIOA_RESET_SET(data, offset, msk)  GPIOA->BSRR = ((uint32_t)(~(data) & (msk)) << ((offset) + 16)) | ((uint32_t)((data) & (msk)) << (offset)) 
// 等效代码  PA8~PA15 输出
#define GPIOA_RESET_SET_BYTE(byte)          GPIOA_RESET_SET(byte, 8, 0xff)

如果你的代码对性能要求比较高,建议使用宏,但同时你需要知道里面还有一个 & 的计算,这个会在运行时计算,而对于固定的 8 bit 数据,这个计算可以通过强制转化去掉(就像前面的代码一样,并没有 & 计算),这样效率会更高,另一种方法是使用内联函数,在使用上应该会比宏更加安全(优化级别提高的情况下)。

希望本期笔记对各位道友开发项目有所帮助,如有帮助,欢迎转发支持鱼鹰。

*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。

参与讨论
登录后参与讨论
推荐文章
最近访客