转自:
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,这并不重要 - 这只是让从机发送任何已经准备好的移位寄存器的一种方式。
启用SPI
像微控制器上的许多事情一样,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设置例程开始时,写到:
- 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的八位。我将逐一浏览所有八位数据 - 即使是我没有使用的数据 - 你可以看到:
- SPCR = 0; // just to be sure
- // SPCR |= (1 << SPIE); // not using interrupts, so leaving this at 0
- SPCR |= (1 << SPE); // enable SPI
- // SPCR |= (1 << DORD); // we want to keep the default MSB first, so not using this
- SPCR |= (1 << MSTR); // set master mode
- // SPCR |= (1 << CPOL); // leaving this set to 0
- // SPCR |= (1 << CPHA); // leaving this set to 0
- SPCR |= (1 << SPR1); // using a prescaler setting of 64 (1,0) in this register
- // SPCR |= (1 << SPR0);
这样我们就准备好了。 在第2部分中,我们将开始使用SPI总线。