ATMEGA的SPI总线 - 第1部分

转自:

1. https://www.yiboard.com/thread-782-1-1.html

2.https://mansfield-devine.com/speculatrix/2018/01/avr-basics-spi-on-the-atmega-part-1/

当AVR与其他器件进行数据交互时,我们需要选择采用哪种方式。这里可以使用UART、I2C等经典的串口方式,也可以选择串行外设接口(SPI)。我比较喜欢SPI总线方式。那么让我们来聊一聊这种总线形式。

关系

在SPI中的一个重要的概念就是主从关系。一个设备作为主机,负责产生时钟信号并启动每次通信。除了时钟之外,其他设备在很多方面都像主机一样操作,而且只有在被呼叫时才会回应。

一般情况下,SPI总线至少由四条线组成 - 也就是说,它需要每个器件的四个引脚。 他们是:

●    MOSI:主机输出,从机输入 - 数据从主机传输到从机。

●    MISO:主机输入,从机输出 - 用于从从机到主机的数据传输。

●    SCK:时钟线,有时也被标记为CLK。

●    SS:从机选择 - 有时也被标记为片选(CS);这根线激活从机并启动通信。

和其他总线不一样的是,SPI总线总是只有一根MOSI、一根MISO和一根SCK。但是该总线是设计用于连接多个设备。每个总线上只有一个主机设备(不同于提供多主设备模式的I2C)。但是你可以有多个从设备,每个都需要自己的SS线。

这被认为是SPI的弱势之一,尤其是当您尝试使用GPIO数目较少的微控制器时。它至少需要将四个引脚连接在一个从机设备上,每个附加的从设备需要另一个引脚。但是,这种总线方式非常简单,通讯速度可以很快。

专用硬件

谈到GPIO引脚,可以使用任何GPIO引脚来实现一种“软件模拟串行”方式的SPI。事实上,在使用移位寄存器等设备时,我已经做到了这一点。但是,您需要负责切换时钟引脚,以产生必要的脉冲,并将数据移入或移出数据引脚。这并不难,但像AVR微控制器这样的设备已经内置了硬件来为您做所有这些。

我使用ATMEGA328P进行实验,所以我将指出这一点,但所有这些都应该轻松转移到其他微控制器。我们来看看ATMEG328P的引脚分布。

<ignore_js_op>

 

在右下角你会看到四个粉红色的标签,表明引脚16-19是我们的SPI引脚。当然,它们也是普通的GPIO,但是当启用SPI功能时,它们承担SPI的角色,我们稍后会看到。通过使用这些引脚分配的目的,你需要在代码中做更少的工作。

不同之处在于SS引脚。说实话,你用这个GPIO是非常随意的。即使启用SPI,您仍然必须将SS引脚设置为输出,并在适当的时刻将其切换为高电平或低电平。如果你想用另一个引脚,可以自己选择。如果你想要连接不止一个从设备,你必须使用其他的GPIO。

转移数据

在讨论如何使用SPI之前,让我们花点时间考虑它是如何工作的。

您可以将每个器件(主器件和从器件)的SPI部分视为一个移位寄存器,在每个时钟脉冲中,一次输入一位和输出一位。

当时钟滴答时,从主机的移位寄存器发送一位到从机上,其余位移位。从机的移位寄存器有足够的空间来接受这个位,因为在同一个时钟脉冲上,从机已经向主机发送了一位数据,并且也一起移动了。在八个时钟脉冲之后,两个器件已经交换了这些移位寄存器中的全部字节。

这就是SPI的秘密:每当主机发送数据时,无论数据是否有意义(通常不是),它都会返回。事实上,有很多时候,主机只是想刺激从机放弃一些数据,而这是通过发送任何东西来实现的。它可以是全0或全1,这并不重要 - 这只是让从机发送任何已经准备好的移位寄存器的一种方式。

启用S​​PI

像微控制器上的许多事情一样,SPI通过巧妙地使用寄存器来控制。和往常一样,在AVR中,宏(通过avr / io.h)定义了寄存器和其中的位,所以我们可以简单地使用这些名称来处理接口。

我们不会在这里详细介绍SPI,但是我们将看看能让您获得简单的SPI解决方案的关键要素。我们的重点在于使用AVR作为主设备。

SPCR - SPI控制寄存器

SPCR是建立SPI的关键寄存器。所有八位用于配置接口,它们是:

7
6
5
4
3
2
1
0
SPIE
SPE
DORD
MSTR
CPOL
CPHA
SPR1
SPR0

根据数据表,当ATMEGA328P上电时,SPCR设置为0。个人而言,我并不介意花费一个或两个时钟周期,在我的SPI设置例程开始时,写到:

  1. 1SPCR = 0;
复制代码

那我们来看看这些位的含义:

●    SPIE - SPI中断使能。将其设置为1时,只要另一个寄存器中的另一个位(SPSR中的SPIF精确)被置位,就会触发SPI中断。

●    SPE - SPI启用。这是至关重要的,因为它有效地打开SPI并使这些引脚承担SPI的角色。

●    DORD - 数据顺序。这控制数据是首先发送最高有效位(MSB - 即第7位)还是最低有效位(LSB,第0位)。在默认状态(0)是MSB,这就是我喜欢它。如果您以此方式滚动或者从属设备期望它,则将此设置为1为Little-Endian。

●    CPOL、CPHA - 时钟极性和时钟相位。这些需要更多的解释,我们马上就会谈到。

●    SPR1、SPR0 - SPI时钟频率。它们与SPSR寄存器中的SPI2X位一起使用来设置时钟速度,从而设置数据移动的速率。这是一个与处理器的振荡器时钟频率(Fosc)相关的预分频器。

相位和极性

时钟极性和相位的设置取决于从机的工作方式和期望值。有四种模式:

SPI模式
条件
前边沿
后边沿
0(0,0)
CPOL = 0,CPHA = 0
采样(上升沿)
设置(下降沿)
1(0,1)
CPOL = 0,CPHA = 1
设置(上升沿)
采样(下降沿)
2(1,0)
CPOL = 1,CPHA = 0
采样(下降沿)
设置(上升沿)
3(1,1)
CPOL = 1,CPHA = 1
设置(下降沿)
采样(上升沿)

您需要阅读从设备的数据表以了解其要求。例如,我最近搞砸了一个23LCV512的串行RAM芯片。其数据表说明如下:

The device is accessed via the SI pin, with data being clocked in on the rising edge of SCK.

数据在上升沿“锁存”(又称锁存或采样)意味着它必须是上表中的模式0或模式3。这取决于上升沿是被认为是“领先”还是“落后”。你怎么知道的?回到数据表。它应该显示时序图。这里是我们的RAM芯片的例子:

<ignore_js_op>

 

在输入版本中的所有内容开始之前,看看SCK线路是如何变低的?当SCK变高时,'MSB'发生。所以这里的上升沿也是领先的,这意味着我们想要模式0,在很多圈子里也被称为0,0。

设置速度

设置SPCR中的SPR0和SPR1位以及SPSR中的SPI2X位可将时钟频率设置为用于运行微控制器(Fosc)的振荡器的特定频率的一小部分。所以,如果你像我一样在16MHz下运行你的处理器,并且设置一个预分频值(比如16),那么SPI总线将以1MHz运行。预分频值越大,SPI总线越慢。

你可能会认为你想尽可能快地走,但是你可能会遇到问题,特别是如果你的从机的电线很长。正如我在面包板上做了很多这样的事情,我倾向于使用第二个最慢的64位设置,推测我可以随时尝试提高速度。

SPI2X
SPR1
SPR0
SCK频率
0
0
0
Fosc / 4
0
0
1
Fosc / 16
0
1
0
Fosc / 64
0
1
1
Fosc / 128
1
0
0
Fosc / 2
1
0
1
Fosc / 8
1
1
0
Fosc / 32
1
1
1
Fosc / 64

SPSR - SPI状态寄存器

在这里我们只关心三个位,如果说实话,我主要关心的只有一个位。

7
6
5
4
3
2
1
0
SPIF
WCOL
-
-
-
-
-
SPI2X

●    SPIF - SPI中断标志。当数据传输和输入完成时自动设置。如果您已经使能了全局中断和SPCR中的SPIE位,则当该标志置位时将触发中断。处理该中断将自动清除该标志,就像读取SPI数据寄存器(SPDR)一样。所以大多数情况下,你只需要读取这个标志就可以了。

●    WCOL - 写入碰撞标志。

●    SPI2X - 时钟速度设置的一部分。

准备设置

在我们检查第三个寄存器之前,让我们来设置SPI。我将假定AVR将作为一个主机,我们不会搞乱中断,数据顺序将是MSB第一。我将要进行通讯的是串行RAM芯片,它的时钟极性(CPOL)为0,时钟相位(CPHA)为0。我将使用处理器速度的1/64作为总线的速度。

让我们配置SPCR的八位。我将逐一浏览所有八位数据 - 即使是我没有使用的数据 - 你可以看到:

  1. SPCR = 0;                 // just to be sure
  2. // SPCR |= (1 << SPIE);   // not using interrupts, so leaving this at 0
  3. SPCR |= (1 << SPE);       // enable SPI
  4. // SPCR |= (1 << DORD);   // we want to keep the default MSB first, so not using this
  5. SPCR |= (1 << MSTR);      // set master mode
  6. // SPCR |= (1 << CPOL);   // leaving this set to 0
  7. // SPCR |= (1 << CPHA);   // leaving this set to 0
  8. SPCR |= (1 << SPR1);      // using a prescaler setting of 64 (1,0) in this register
  9. // SPCR |= (1 << SPR0);
复制代码

这样我们就准备好了。 在第2部分中,我们将开始使用SPI总线。

posted @ 2019-05-15 10:06  梅长苏枫笑  阅读(890)  评论(0编辑  收藏  举报