Atmel的I2C通用串行接口(USI)Sucks

2020-08-27 07:53:53

与USART和SPI一样,I2C绝对是微控制器与外设通信最常用的接口。为了实现I2C总线,您只需要两个集电极引脚,一个用于SCL(时钟)线,一个用于SDA(数据)线。它必须是集电极开路,因为在协议期间,有时两个器件同时驱动时钟线,如果一个器件驱动高电平,另一个驱动低电平,可能会导致短路。这样,由于上拉电阻,总线线路默认为高-如果设备想要线路变低,它只需通过内部晶体管将其短路到地。从VCC到GND的所有路径都包含高值电阻。

因此,如果微控制器制造商想要允许您在没有外部晶体管的情况下使用I2C协议,他们必须至少为您提供两个开漏IO。例如,Atmel在ATTiny85中就是这样做的。然而,它们没有为您提供任何有用的硬件来管理总线事务,而是为您提供了它们的通用串行接口,而这对于I2C来说几乎毫无用处。

如果我只有两个开路的漏极输入,没有硬件外设,我的微控制器将不得不照看公交车。它将不得不将时钟设置为高、等待、设置为低时钟、等待、更新数据线、等待、将时钟设置为高并且一遍又一遍地等待在总线上发送数据,这使得没有时间做任何其他事情。这显然是不可取的,但如果您的微控制器不做任何其他事情或任何时间紧迫的事情,则可能是可以接受的。

我想要的是让我的微控制器能够告诉外围设备,“去公交车上发送消息,如果你遇到任何错误或完成了,就打断我-我会做其他事情的”。这样,不会浪费时钟周期,我们可以专注于更重要的任务-这意味着进程是非阻塞的,因为它不会阻止处理器做其他事情。

USI提供多种功能,其中大部分都有使其不适合I2C的警告。

USIDR可以加载数据,这些数据可以一次一位地移位到数据总线上。这意味着处理器不必存储正常内存中的数据,然后提取最高有效位,将其放到总线上,并在每个时钟周期手动移位剩下的数据。太好了,我们节省了内存和时钟周期。但请稍等,USIDR寄存器只有8位长,基本I2C事务需要18位!因此,我们可以将8位放入寄存器,但我们仍然必须在内存中存储至少10位。事实上,要从多个器件读取单个字节需要36位时钟(不包括停止/开始位),以下是TVP7002数据手册中的重点。

有一个4位计数器可用,每当新位移位到数据总线上时,该计数器就会递增。这很好,因为它省去了每次我们将数据转移到总线上时递增内存中我们自己的计数器的工作。哦,等等,它只能数到16,因为它只有4位长的…。我刚才不是说最小有用的I2C事务是18位吗?

如果我们希望我们的处理器在I2C事务发生时继续处理,我们需要其他东西来为我们计时。幸运的是,USI可以通过芯片中的硬件计数器进行计时。每当计数器达到某个值时,它就会复位,下一位就会移位到总线上。但它不会同时触发时钟!因此,处理器必须捕获计数器中断并手动切换时钟,在这一点上,我们不妨自己移位数据位,但至少现在一切都被中断驱动了,对吗?

但请记住,I2C协议坚持数据线只有在时钟线为低电平时才能改变。因此,时钟线和数据线需要切换到彼此异相的交替周期。因此,我们要么需要第二个计数器,与数据计数器180度异相(很难做,而且很浪费),要么我们只是在每一秒计数器中断时手动切换所有东西。

突然之间,我们发现自己在每个中断上执行大量的时钟周期。鉴于I2C通常以100 kHz运行,并且我们需要定时器在每个时钟周期触发4次以捕获数据线和时钟线上的所有边沿,因此我们需要每秒中断40万次。ATTiny的内部振荡器只有8 MHz,所以每个中断有20个时钟周期,不是很多。即使在20 MHz的最高速度下,我们每个中断也只有50个时钟周期-如果你想实现其他任何事情,这并不多。

所有这些结合在一起,USI几乎不会为您节省任何时钟周期或内存。这可能就是为什么我所见过的所有使用USI的I2C实现都是阻塞实现的原因--这在很多情况下都很好。

公平地说,USI确实提供了一种检测停止和启动条件的方法,如果您编写的是I2C从机而不是主机,这可能会很有用-但如果您是主机,您无论如何都会自己生成这些条件。

尽管该数据手册从未说明USI将用作I2C总线,但该数据手册通过“双线同步数据传输”对此进行了大量的暗示。我希望他们能说得更清楚一点,那是不合适的。