B001-Atmega16-SPI Flash

本文详细介绍了使用Atmega16通过SPI接口与GD25Q32 SPI Flash进行通信的步骤,包括SPI初始化、读取RDID、读写状态寄存器、读写擦除数据的操作,以及实现SPI接口管理器以处理多个从机和读写冲突的问题。还探讨了同时操作三个SPI Flash的策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


主要内容:
第一步: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:主机发送 数据 到从机,实质就是用 MISOMOSI两条线、将两个移位寄存器连接成一个 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在空闲时是高电平࿰
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值