• ATtiny88初体验(六):SPI


    ATtiny88初体验(六):SPI

    SPI介绍#

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

    image.png

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

    ATtiny88的SPI时钟频率不能超过 fOSC/4 ,双倍速率模式下不能超过 fOSC/2

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

    image.png

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

    image.png

    image.png

    image.png

    寄存器#

    image.png

    • SPIE :写入1使能SPI中断。
    • SPE :写入1使能SPI。
    • DORD :数据方向,写入1为LSB优先,写入0为MSB优先。
    • MSTR :主机/从机模式选择,写入1为主机模式,写入0为从机模式。
    • CPOL :时钟极性。
    • CPHA :时钟相位。
    • SPR[1:0] :SPI时钟速率选择。
      image.png

    image.png

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

    image.png

    代码#

    下面的代码演示了使用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 
    #include 
    #include 
    #include 
    #include 
    
    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,连接好串口,可以观察串口的输出如下:

    image.png

    参考资料#

    1. ATtiny88 Datasheet
    2. Programming and Interfacing ATMEL's AVRs
  • 相关阅读:
    Grom 如何解决 SQL 注入问题
    [QT] QMap使用详解
    【零基础学Java】第十七篇 异常
    10 次面试 9 次被刷?吃透这 500 道大厂 Java 高频面试题后,怒斩 offer
    后端:推荐 2 个 .NET 操作的 Redis 客户端类库
    我为什么使用Linux做开发
    分析网上的一篇“浪漫烟花“程序<VS-C++>
    【electron】【附排查清单】记录一次逆向过程中,fetch无法请求http的疑难杂症(net::ERR_BLOCKED_BY_CLIENT)
    音频 scipy 中 spectrogram 的运作机制
    算法题:给定一个数组和一个目标和,从数组中找到两个数字相加等于目标和,输出这两个数字的下标
  • 原文地址:https://www.cnblogs.com/chinjinyu/p/17677594.html