ATtiny88初体验(六):SPI

SPI介绍

ATtiny88自带SPI模块,可以实现数据的全双工三线同步传输。它支持主从两种模式,可以配置为LSB或者MSB优先传输,有7种可编程速率,支持从空闲模式唤醒。

ATtiny88初体验(六):SPI-LMLPHP

注意:为了使用SPI模块,必须将 PRR 寄存器中的 PRSPI 位设置为0。

ATtiny88的SPI时钟频率不能超过 \(f_{OSC}/4\) ,双倍速率模式下不能超过 \(f_{OSC}/2\)

当SPI使能时,MOSI、MISO、SCK、SS引脚的方向会被覆盖,具体见下表:

ATtiny88初体验(六):SPI-LMLPHP

根据SCK的极性和相位不同,SPI分为四种模式:

ATtiny88初体验(六):SPI-LMLPHP

ATtiny88初体验(六):SPI-LMLPHP

ATtiny88初体验(六):SPI-LMLPHP

寄存器

ATtiny88初体验(六):SPI-LMLPHP

  • SPIE :写入1使能SPI中断。
  • SPE :写入1使能SPI。
  • DORD :数据方向,写入1为LSB优先,写入0为MSB优先。
  • MSTR :主机/从机模式选择,写入1为主机模式,写入0为从机模式。
  • CPOL :时钟极性。
  • CPHA :时钟相位。
  • SPR[1:0] :SPI时钟速率选择。
    ATtiny88初体验(六):SPI-LMLPHP

ATtiny88初体验(六):SPI-LMLPHP

  • SPIF :SPI中断标志,执行完中断后自动清除,或者通过先读 SPSR 寄存器,再访问 SPDR 寄存器清除。
  • WCOL :写冲突标志,在数据传输期间对 SPDR 寄存器进行写操作时置位,通过先读 SPSR 寄存器,再访问 SPDR 寄存器清除。
  • SPI2X :SPI速率加倍。在主机模式下,向此位写入1使SPI时钟速率加倍,最大速率为 \(f_{OSC}/2\) 。在从机模式下,最大速率还是只有 \(f_{OSC}/4\)

ATtiny88初体验(六):SPI-LMLPHP

代码

下面的代码演示了使用ATtiny88的SPI模块与W25Q32 Flash模块进行通信,读取Flash的ID信息。源文件的组织结构如下:

.
├── Makefile
├── inc
│   ├── serial.h
│   └── serial_stdio.h
└── src
    ├── main.c
    ├── serial.c
    └── serial_stdio.c

src/main.c 源文件的代码如下:

#include <stdint.h>
#include <stdio.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <serial_stdio.h>

static void spi_setup(void);
static uint8_t spi_read_and_write(uint8_t data);
static void w25qxx_read_device_id(void *id, uint8_t n);
static void w25qxx_read_manufacturer_device_id(void *id, uint8_t n);
static void w25qxx_read_unique_id(void *id, uint8_t n);
static void w25qxx_read_jedec_id(void *id, uint8_t n);

int main(void)
{
    cli();
    stdio_setup();              // initialize stdio and redirect it to serial
    spi_setup();                // initialize spi module
    sei();

    printf("=================================\r\n");

    // read device id of spi flash
    uint8_t buf[8];
    w25qxx_read_device_id(buf, 1);
    printf("device id: 0x%02X.\r\n", buf[0]);

    // read manufacturer and device id of spi flash
    w25qxx_read_manufacturer_device_id(buf, 2);
    printf("manufacturer & device id: 0x%02X%02X.\r\n", buf[0], buf[1]);

    // read unique id of spi flash
    w25qxx_read_unique_id(buf, 8);
    printf("unique id: 0x");
    for (uint8_t i = 0; i < 8; i++) {
        printf("%02X", buf[i]);
    }
    printf(".\r\n");

    // read jedec id of spi flash
    w25qxx_read_jedec_id(buf, 3);
    printf("jedec id: 0x%02X%02X%02X.\r\n", buf[0], buf[1], buf[2]);

    for (;;);
}

static void spi_setup(void)
{
    // initialize gpios
    // PB2 -> SS
    // PB3 -> MOSI
    // PB4 -> MISO
    // PB5 -> SCK
    DDRB |= _BV(DDB2) | _BV(DDB3) | _BV(DDB5);
    PORTB |= _BV(PORTB2) | _BV(PORTB3) | _BV(PORTB5);

    // enable spi, msb first, master mode, mode 3, prescaler = 64
    SPCR = _BV(SPE) | _BV(MSTR) | _BV(CPOL) | _BV(CPHA) | _BV(SPR1) | _BV(SPR0);
    SPSR = _BV(SPI2X);
}

static uint8_t spi_read_and_write(uint8_t data)
{
    SPDR = data;
    while (!(SPSR & _BV(SPIF)));
    return SPDR;
}

static void w25qxx_read_device_id(void *id, uint8_t n)
{
    if (n > 1) {
        n = 1;
    }

    PORTB &= ~_BV(PORTB2);
    spi_read_and_write(0xAB);
    spi_read_and_write(0xFF);
    spi_read_and_write(0xFF);
    spi_read_and_write(0xFF);
    while (n--) {
        *(uint8_t *)id++ = spi_read_and_write(0xFF);
    }
    PORTB |= _BV(PORTB2);
}

static void w25qxx_read_manufacturer_device_id(void *id, uint8_t n)
{
    if (n > 2) {
        n = 2;
    }

    PORTB &= ~_BV(PORTB2);
    spi_read_and_write(0x90);
    spi_read_and_write(0xFF);
    spi_read_and_write(0xFF);
    spi_read_and_write(0x00);
    while (n--) {
        *(uint8_t *)id++ = spi_read_and_write(0xFF);
    }
    PORTB |= _BV(PORTB2);
}

static void w25qxx_read_unique_id(void *id, uint8_t n)
{
    if (n > 8) {
        n = 8;
    }

    PORTB &= ~_BV(PORTB2);
    spi_read_and_write(0x4B);
    spi_read_and_write(0xFF);
    spi_read_and_write(0xFF);
    spi_read_and_write(0xFF);
    spi_read_and_write(0xFF);
    while (n--) {
        *(uint8_t *)id++ = spi_read_and_write(0xFF);
    }
    PORTB |= _BV(PORTB2);
}

static void w25qxx_read_jedec_id(void *id, uint8_t n)
{
    if (n > 3) {
        n = 3;
    }

    PORTB &= ~_BV(PORTB2);
    spi_read_and_write(0x9F);
    while (n--) {
        *(uint8_t *)id++ = spi_read_and_write(0xFF);
    }
    PORTB |= _BV(PORTB2);
}

编译并下载程序到ATtiny88,连接好串口,可以观察串口的输出如下:

ATtiny88初体验(六):SPI-LMLPHP

参考资料

  1. ATtiny88 Datasheet
  2. Programming and Interfacing ATMEL's AVRs
09-04 17:12