ATtiny88初體驗(六):SPI

2023-09-04 18:00:43

ATtiny88初體驗(六):SPI

SPI介紹

ATtiny88自帶SPI模組,可以實現資料的全雙工三線同步傳輸。它支援主從兩種模式,可以設定為LSB或者MSB優先傳輸,有7種可程式化速率,支援從空閒模式喚醒。

注意:為了使用SPI模組,必須將 PRR 暫存器中的 PRSPI 位設定為0。

ATtiny88的SPI時脈頻率不能超過 \(f_{OSC}/4\) ,雙倍速率模式下不能超過 \(f_{OSC}/2\)

當SPI使能時,MOSI、MISO、SCK、SS引腳的方向會被覆蓋,具體見下表:

根據SCK的極性和相位不同,SPI分為四種模式:

暫存器

  • SPIE :寫入1使能SPI中斷。
  • SPE :寫入1使能SPI。
  • DORD :資料方向,寫入1為LSB優先,寫入0為MSB優先。
  • MSTR :主機/從機模式選擇,寫入1為主機模式,寫入0為從機模式。
  • CPOL :時鐘極性。
  • CPHA :時鐘相位。
  • SPR[1:0] :SPI時鐘速率選擇。

  • SPIF :SPI中斷標誌,執行完中斷後自動清除,或者通過先讀 SPSR 暫存器,再存取 SPDR 暫存器清除。
  • WCOL :寫衝突標誌,在資料傳輸期間對 SPDR 暫存器進行寫操作時置位,通過先讀 SPSR 暫存器,再存取 SPDR 暫存器清除。
  • SPI2X :SPI速率加倍。在主機模式下,向此位寫入1使SPI時鐘速率加倍,最大速率為 \(f_{OSC}/2\) 。在從機模式下,最大速率還是隻有 \(f_{OSC}/4\)

程式碼

下面的程式碼演示了使用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,連線好串列埠,可以觀察串列埠的輸出如下:

參考資料

  1. ATtiny88 Datasheet
  2. Programming and Interfacing ATMEL's AVRs