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,連線好串列埠,可以觀察串列埠的輸出如下:
本文來自部落格園,作者:chinjinyu,轉載請註明原文連結:https://www.cnblogs.com/chinjinyu/p/17677594.html