主要内容:
第一步:SPI的结构
第二步:SPI的初始化
第三步:Atmega16的SPI自发自收
第四步:与SPI Flash连接(GD25Q32B)
第五步:读取SPI Flash的RDID(GD25Q32B)
第六步:SPI Flash操作接口(GD25Q32B)
第七步:SPI接口管理器
第八步:同时操作三个SPI Flash
-------------------------------------------------------------------------------------------------------------------------------------
开发环境:AVR Studio 4.19 + avr-toolchain-installer-3.4.1.1195-win32.win32.x86
芯片型号:ATmega16
芯片主频:8MHz
-------------------------------------------------------------------------------------------------------------------------------------
第一步: SPI的结构
1、下面是一个主机( MASTER)和一个从机( SLAVE)连接时的情况:主机和从机中都各自有一个 8bit的移位寄存器,在时钟的驱动下进行移位操作,这有点像汇编中循环移位指令的执行。
输入输出引脚:
● SS:主机通过拉低从机的 SS引脚,来使能从机的 SPI接口; SS引脚平时为高电平
● SCLK:主机发出时钟信号 SCLK,来驱动主机和从机这两个移位寄存器的数据移位(通常是一个时钟的上升沿锁存数据,下降沿移出数据)
● MISO:主机接收 来自 从机的数据
● MOSI:主机发送 数据 到从机,实质就是用 MISO和 MOSI两条线、将两个移位寄存器连接成一个 16bit的 循环移位寄存器
工作过程(数据循环移位的过程):
1、主机拉低 SS#引脚电平,使能从机
2、在主机和从机的移位寄存器里面,分别放入各自的数据(不放入的话、里面当然也会有一个初始的随机数)
3、主机的 SCLK开始发出时钟脉冲
4、在每个 SCLK脉冲内、主机和从机都完成 1次数据移位,互相交换了 1bit(通常是:上升沿锁存数据、下降沿移出数据)
5、连续8个 SCLK脉冲后、主机和从机通过移位互换了各自的 8bit数据,此时读取各自的移位寄存器、就可以得到它们交换到的数据
6、主机拉高 CS#引脚电平,结束本次数据交换,本次共完成了 8bit数据交换,如果需要交换更多数据,连续发送 SCLK时钟脉冲即可
● 所以说, SPI通信的本质就是主机和从机的数据互换、通过移位的方式。
-------------------------------------------------------------------------------------------------------------------------------------
第二步: SPI的初始化
1、知道了SPI接口如何工作、下面就开始初始化Atmega16的SPI接口。初始化过程要做如下几件事:
● 选择哪个SPI接口作为主机,哪个作为从机
● 设置SPI的 SCLK时钟的频率
● 选择是左移(先发送最高位MSB)还是右移(先发送最高位LSB)
● 设置是上升沿移位还是下降沿移位
● 选择是上升沿锁存还是下降沿锁存(数据一般是先锁存再移位,这就涉及到移位寄存器的硬件操作)
● 设置SPI引脚(MOSI和MISO引脚的方向要看数据移位的方向来定:移出就是输出引脚、移入就是输入引脚)
● 选择是否开启传输完成中断(每次完成了 8bit的数据移位后、就会产生1次中断,或者说8个SCLK脉冲后、就会产生中断)
这里使用下面定义的初始化函数来进行初始化:
void Drv_SPI_init(const uint8_t device_mode, const uint8_t shift_mode, const uint8_t shift_first, const uint8_t clk_source);
设置参数如下:
● device_mode 主从模式选择(主机:SPI_MODE_MSTR, 从机:SPI_MODE_SLAVE)
● shift_mode 传输模式(锁存/移位模式)
● shift_first 首先移出MSB(左移)还是LSB(右移)
● clk_source SCLK频率
2、根据 Atmega16的Datasheet、 SPI的初始化接口如下:
Drv_SPI.h:
// ==========================================================================================================
// Copyright (c) 2016 Manon.C <codingmanon@163.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
// associated documentation files (the "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject
// to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ---------------------------------
// 本文定义了Atmega16下的SPI初始化
//
// ==========================================================================================================
#ifndef __DRV_SPI_H__
#define __DRV_SPI_H__
#include <avr/io.h>
#include <avr/interrupt.h>
#include "Drv_IO_Port.h"
#include "config.h"
// SPI的传输模式(CPOL:时钟极性)(CPHA:采样边沿)
// ---------------------------------------------------
// mode | CPOL | CPHA | 前沿 | 后沿
// ---------------------------------------------------
// 0 | 0 | 0 | 采样锁存(↑) | 移出 (↓)
// ---------------------------------------------------
// 1 | 0 | 1 | 移出 (↑) | 采样锁存(↓)
// ---------------------------------------------------
// 2 | 1 | 0 | 采样锁存(↓) | 移出 (↑)
// ---------------------------------------------------
// 3 | 1 | 1 | 移出 (↓) | 采样锁存(↑)
// ---------------------------------------------------
typedef enum
{
SPI_SHIFT_FIRST_MSB = 0,
SPI_SHIFT_FIRST_LSB = 1,
SPI_MODE_SLAVE = 0,
SPI_MODE_MSTR = 1,
SPI_SHIFT_MODE_00 = 0, // 传输模式
SPI_SHIFT_MODE_01 = 1,
SPI_SHIFT_MODE_02 = 2,
SPI_SHIFT_MODE_03 = 3,
SPI_CLK_SOURCE_DIV_2 = 0,
SPI_CLK_SOURCE_DIV_4 = 1,
SPI_CLK_SOURCE_DIV_8 = 2,
SPI_CLK_SOURCE_DIV_16 = 3,
SPI_CLK_SOURCE_DIV_32 = 4,
SPI_CLK_SOURCE_DIV_64 = 5,
SPI_CLK_SOURCE_DIV_128 = 6
} DRV_SPI_SETTINHG;
#define Drv_SPI_set_SS() Drv_IO_set_bit(PORTB, PB4)
#define Drv_SPI_clr_SS() Drv_IO_clr_bit(PORTB, PB4)
void Drv_SPI_init(const uint8_t device_mode, const uint8_t shift_mode, const uint8_t shift_first, const uint8_t clk_source);
void Drv_SPI_INT_Enable(const uint8_t enable);
uint8_t Drv_SPI_clr_SPIF(void);
void Drv_SPI_write_byte(uint8_t data);
uint8_t Drv_SPI_read_byte(uint8_t dummy);
#endif // #ifndef __DRV_SPI_H__
Drv_SPI.c:
// ==========================================================================================================
// Copyright (c) 2016 Manon.C <codingmanon@163.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
// associated documentation files (the "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject
// to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ---------------------------------
// 本文定义了Atmega16下的SPI初始化
//
// ==========================================================================================================
#include "Drv_SPI.h"
// ==========================================================================================================
// SPI初始化
//
// 参数:device_mode 主从模式(主机:SPI_MODE_MSTR, 从机:SPI_MODE_SLAVE)
// shift_mode 传输模式(锁存/移位模式)
// shift_first 首先移出MSB(左移)还是LSB(右移)
// clk_source SCLK频率
//
// 说明:
// (1). 当MSTR=1 && SS引脚为输入IO时,如果SS引脚被拉低,会导致MSTR=0 && SPIF=1,使得机器变成从机模式
//
// ==========================================================================================================
void Drv_SPI_init(const uint8_t device_mode, const uint8_t shift_mode, const uint8_t shift_first, const uint8_t clk_source)
{
uint8_t spr10;
uint8_t spi2x;
uint8_t cpol;
uint8_t cpha;
switch(shift_mode)
{
case SPI_SHIFT_MODE_00: cpol = 0; cpha = 0;
break;
case SPI_SHIFT_MODE_01: cpol = 0; cpha = 1;
break;
case SPI_SHIFT_MODE_02: cpol = 1; cpha = 0;
break;
case SPI_SHIFT_MODE_03: cpol = 1; cpha = 1;
break;
default : return;
}
switch(clk_source)
{
case SPI_CLK_SOURCE_DIV_2: spr10 = 0; spi2x = 1;
break;
case SPI_CLK_SOURCE_DIV_4: spr10 = 0; spi2x = 0;
break;
case SPI_CLK_SOURCE_DIV_8: spr10 = 1; spi2x = 1;
break;
case SPI_CLK_SOURCE_DIV_16: spr10 = 1; spi2x = 0;
break;
case SPI_CLK_SOURCE_DIV_32: spr10 = 2; spi2x = 1;
break;
case SPI_CLK_SOURCE_DIV_64: spr10 = 2; spi2x = 0;
break;
case SPI_CLK_SOURCE_DIV_128: spr10 = 3; spi2x = 0;
break;
default : return;
}
// IO设置
if(SPI_MODE_MSTR == device_mode)
{
Drv_IO_set_bit(PORTB, PB4);
Drv_IO_mode_bit(DDRB, PB4, IO_OUTPUT); // SS输出(拉高)、以禁止SPI从机
Drv_IO_clr_bit(PORTB, PB5);
Drv_IO_mode_bit(DDRB, PB5, IO_OUTPUT); // MOSI输出(拉低)
Drv_IO_clr_bit(PORTB, PB6);
Drv_IO_mode_bit(DDRB, PB6, IO_INPUT); // MISO输入(拉低)
Drv_IO_clr_bit(PORTB, PB7);
Drv_IO_mode_bit(DDRB, PB7, IO_OUTPUT); // SCLK输出(拉低)
}
else
{
Drv_IO_set_bit(PORTB, PB4);
Drv_IO_mode_bit(DDRB, PB4, IO_INPUT); // SS输入(拉高|如果PUD使能、将打开上拉)
Drv_IO_clr_bit(PORTB, PB5);
Drv_IO_mode_bit(DDRB, PB5, IO_INPUT); // MOSI输入(拉低)
Drv_IO_clr_bit(PORTB, PB6);
Drv_IO_mode_bit(DDRB, PB6, IO_OUTPUT); // MISO输出(拉低)
Drv_IO_clr_bit(PORTB, PB7);
Drv_IO_mode_bit(DDRB, PB7, IO_INPUT); // SCLK输入(拉低)
}
// 初始化
SPCR = ( 1 << SPE ) |
((shift_first & 0x01) << DORD) | // 先发送MSB或LSB
((device_mode & 0x01) << MSTR) | // 主从模式
((cpol & 0x01) << CPOL) | // 时钟极性
((cpha & 0x01) << CPHA) | // 前后沿
((spr10 & 0x03) << SPR0) ; // 时钟频率
SPSR &= ~(1 << SPI2X);
SPSR |= (spi2x << SPI2X); // 时钟倍频
}
// ==========================================================================================================
// SPI传输完成中断使能
//
// ==========================================================================================================
void Drv_SPI_INT_Enable(const uint8_t enable)
{
if(DISABLE == enable)
{
SPCR &= ~(1 << SPIE);
}
else
{
SPCR |= (1 << SPIE);
}
Drv_SPI_clr_SPIF();
}
// ==========================================================================================================
// 清除中断标志位SPIF
//
// 说明:
// (1). SPIF在进入ISR时自动清0,或先读SPSR、在访问SPDR,可清0
// (2). 增加一个返回值是为了避免编译器将这段代码优化掉,因为优化器可能会认为这段段码对外部无任何影响
//
// ==========================================================================================================
uint8_t Drv_SPI_clr_SPIF(void)
{
uint8_t temp;
temp = SPSR;
temp += SPDR;
return temp;
}
// ==========================================================================================================
// 将数据写入SPDR、并等待写入结束
//
// 说明:
// (1). 使用这个函数、必须禁止SPI中断,否则中断会自动清0 SPIF,导致这里永远等不到SPIF=0的时刻
//
// ==========================================================================================================
void Drv_SPI_write_byte(uint8_t data)
{
Drv_SPI_clr_SPIF();
SPDR = data;
while(0 == (SPSR & (1 << SPIF)))
{}
}
// ==========================================================================================================
// 读取SPDR中的数据
//
// 说明:
// (1). 发送无意义的数据SPI_COMMAND_DUMMY去启动SCLK、与从机交换数据
//
// ==========================================================================================================
uint8_t Drv_SPI_read_byte(uint8_t dummy)
{
uint8_t data;
Drv_SPI_write_byte(dummy);
data = SPDR;
return data;
}
// ==========================================================================================================
// SPI传输完成中断
//
// 说明:
// (1). 使用主机模式
//
// ==========================================================================================================
ISR(SPI_STC_vect)
{
// 主机模式下,如果SS引脚配置为输入、SS引脚收到低电平、就会导致SPI接口自动变为从机模式
// 所以这里需要检查主机模式
if(SPI_MODE_SLAVE == (SPCR & (1 << MSTR)))
{
SPCR |= (SPI_MODE_MSTR << MSTR);
}
}
我们需要在上电之后等待10ms以上、再去初始化GD25Q32B,因为它在10ms后才可以正常访问:
其中tPUW=10ms。
-------------------------------------------------------------------------------------------------------------------------------------
第三步: Atmega16的SPI自发自收
1、既然 SPI结构核心就是个移位寄存器,那么主机不必与从机连接成一个 循环移位寄存器,主机自己首尾相连、也是可以进行循环移位操作的。也就是说、主机自己首尾相连、成为一个 8bt的 循环移位寄存器,当然也可以自发自收。
主机如下连接:
2、我们将SPI初始化如下(设置为主机模式、先发送最高位MSB、SCLK时钟的频率为主时钟的128分频=62.5KHz):
Drv_SPI_init(SPI_MODE_MSTR, SPI_SHIFT_MODE_00, SPI_SHIFT_FIRST_MSB, SPI_CLK_SOURCE_DIV_128);
Drv_SPI_INT_Enable(ENABLE);
然后、让SPI发送一个8bit数据:
Drv_SPI_clr_SS();
SPI_SHIFT_END = 0; // 这个变量在中断服务程序中置1
SPDR = 0x9F;
while(0 == SPI_SHIFT_END) {} // 8bit数据发送完毕后、会进入中断服务程序、将这个变量置1
temp2016 = SPDR; // 读取循环移位后的数值
Drv_SPI_set_SS();
测试如下:
(1)、 CH2是SCLK信号,图中两个鼠标竖线之间的5个脉冲总共是12.38KHz,那么每个脉冲大概就是 62KHz,和设置的基本一样。
(2)、 CH1是MOSI信号,我们发送的是 0x9F=0b10011111。
在上图中、8个脉冲对应的数值正好就是 0b10011111 = 0x9F。
(3)、在发送数据时、我们是需要在程序里面将数据写入寄存器SPDR、写入后SPI接口的SCLK信号就会 自动产生。
并在8bit数据发送结束后 自动停止SCLK信号、并产生中断。
(4)、MOSI引脚在空闲时就是高电平、也就是在发送前和发送后都是保持高电平。
在Datasheet里面虽然说MOSI的引脚方向由用户程序定义,但MOSI的电平却是由SPI接口自己决定(保持为高电平)。
在时序图中、MOSI在空闲时是高电平