STM32 SPI 通信协议介绍及项目操作

1. SPI 协议简介

SPI(Serial Peripheral Interface,串行外设接口)是一种高速的全双工串行通信协议,用于微控制器与外设之间的数据传输。与 I²C 和 UART 不同,SPI 通常用于高速数据传输,在嵌入式系统中用于与各种外设(如 EEPROM、ADC、DAC、传感器、显示屏等)进行通信。

1.1 SPI 协议特点
  • 全双工通信:数据可以同时从主设备发送到从设备,同时也可以从从设备接收数据。
  • 主从模式:SPI 是一个主从模式协议,数据传输由主设备控制。主设备通过 SPI 总线上的时钟信号(SCK)来同步数据传输。
  • 高速通信:SPI 支持较高的传输速率,适用于需要大量数据快速交换的应用。
  • 四个信号线:SPI 通信通过四根线进行数据传输:
  • MOSI(Master Out Slave In):主设备输出到从设备的数据线。
  • MISO(Master In Slave Out):从设备输出到主设备的数据线。
  • SCK(Serial Clock):时钟信号,由主设备提供,用于同步数据传输。
  • SS/CS(Slave Select/Chip Select):选择信号,由主设备控制,用于选择与哪一从设备进行通信。
  • 1.2 SPI 数据传输的基本步骤
    1. CS 选通:主设备拉低从设备的 CS 信号,表示开始与该从设备通信。
    2. 时钟信号(SCK)生成:主设备通过 SCK 信号控制数据传输的时序。
    3. 数据传输:数据通过 MOSI 或 MISO 信号线进行传输,主设备和从设备交换数据。
    4. CS 释放:通信结束时,主设备拉高 CS 信号,表示数据传输完成。

    2. STM32 SPI 外设介绍

    STM32 系列微控制器提供了丰富的 SPI 外设支持,可以通过 STM32 的 SPI 外设与多个从设备进行高速数据通信。SPI 在 STM32 中具有如下特点:

  • 多种模式:支持主模式、从模式、全双工和半双工模式。
  • 高速数据传输:支持的最高时钟速率高达数十兆赫兹,适用于高带宽要求的应用。
  • DMA 支持:STM32 支持通过 DMA(直接内存访问)进行 SPI 数据传输,可以显著提高效率并减轻 CPU 负担。
  • spi通信时的采样时钟极性由CPOL和CPHA两个决定,CPOL是时钟极性,CPHA是时钟相位,时钟极性主要决定CLK在空闲时的高低电CPOL为1时说明CLK在空闲时候为高电平,CPOL为0时说明CLK在空闲状态为低电平,CPHA是时钟相位,主要决定采样数据时是奇数边采样还是偶数边采样,同时数据的在传输过程中可以设置CR1寄存器中的LSBFIRST高位先行MSB还是低位先行LSB,同时还支持双线全双工,双线单向以及单线模式,其中双线单向模式可以用MOSI和MISO两线同时进行传输数据,同时流程3)通信开始,SCK 时钟开始运行。MOSI 把发送缓冲区中的数据一位一位地传输出去;
    MISO 则把数据一位一位地存储进接收缓冲区中;
    4)当发送完一帧数据的时候,状态寄存器SR 中的TXE 标志位会被置1、表示传输完
    一帧,发送缓冲区已空;类似地,当接收完一帧数据的时候,RXNE标志位会被置1.表示传输完一帧,接收缓冲区非空;
    5) 等到 TXE标志位为士时,若还要继续发送数据,则再次往数据寄存器 DR 写人数据
    即可;等到 RXNE标志位为1时,通过读取数据寄存器 DR可以获取接收缓冲区中的内容。假如我们使能了TXE或 RXNE中断,TXE或 RXNE置1时会产生SPI中断信号,进
    人同一个中断服务丽数。到SPI中断服务程序后,可通过检查寄存器位来了解是哪一个事件,再分别进行处理。也可以使用 DMA 方式来收发数据寄存器 DR中的数据。

    3. STM32 SPI 通信配置吃辣

    3.1 硬件连接

    SPI 通信需要以下信号线:

  • MOSI:主设备输出数据线。
  • MISO:从设备输出数据线。
  • SCK:时钟信号线。
  • CS/SS:选择信号线,用于选择特定的从设备。
  • 以 STM32F4 系列为例,假设使用 SPI1,常见的引脚连接为:

  • MOSI(PA7)
  • MISO(PA6)
  • SCK(PA5)
  • CS(PA4)
  • 3.2 CubeMX 配置
    1. 启用 SPI 外设:在 STM32CubeMX 中,选择 SPI1(或 SPI2、SPI3 等)并配置相关参数,如时钟频率、数据位长度、极性、相位等。
    2. 引脚配置:将 SPI 信号引脚配置为 SPI 模式。
    3. 配置 DMA(可选):若使用 DMA 进行 SPI 数据传输,确保 DMA 控制器和 SPI 外设正确配置。
    4. 生成代码:配置完成后,生成代码并将其导入到 IDE 中进行开发。
    3.3 代码实现

    以下是一个简单的 STM32 SPI 主设备通信示例,展示了如何使用 SPI 进行数据传输:

    main.c

    #include "main.h"
    #include "stm32f4xx_hal.h"
    
    // SPI 句柄声明
    SPI_HandleTypeDef hspi1;
    
    // SPI 初始化函数
    void SPI1_Init(void) {
        hspi1.Instance = SPI1;
        hspi1.Init.Mode = SPI_MODE_MASTER;              // 设置为主设备模式
        hspi1.Init.Direction = SPI_DIRECTION_2LINES;    // 全双工模式
        hspi1.Init.DataSize = SPI_DATASIZE_8BIT;        // 数据位长度 8 位
        hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;      // 时钟极性(空闲时 SCK 低电平)
        hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;          // 时钟相位(数据在 SCK 的第一个边沿采样)
        hspi1.Init.NSS = SPI_NSS_SOFT;                  // 软件管理片选信号
        hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16; // 设置 SPI 时钟分频器
        hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;         // 高位优先
        hspi1.Init.TIMode = SPI_TIMODE_DISABLE;         // 禁用 TI 模式
        hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;  // 禁用 CRC 校验
        hspi1.Init.CRCPolynomial = 10;                  // CRC 多项式
        if (HAL_SPI_Init(&hspi1) != HAL_OK) {
            // 初始化失败,错误处理
            Error_Handler();
        }
    }
    
    // 向 SPI 从设备发送数据
    void SPI_Transmit(uint8_t *data, uint16_t size) {
        HAL_SPI_Transmit(&hspi1, data, size, HAL_MAX_DELAY);
    }
    
    // 从 SPI 从设备接收数据
    void SPI_Receive(uint8_t *data, uint16_t size) {
        HAL_SPI_Receive(&hspi1, data, size, HAL_MAX_DELAY);
    }
    
    // 主函数
    int main(void) {
        HAL_Init();
        SystemClock_Config();
    
        // 初始化 SPI1
        SPI1_Init();
    
        uint8_t txData[] = {0x01, 0x02, 0x03}; // 要发送的数据
        uint8_t rxData[3]; // 接收的数据缓冲区
    
        // 向 SPI 从设备发送数据
        SPI_Transmit(txData, sizeof(txData));
    
        // 从 SPI 从设备接收数据
        SPI_Receive(rxData, sizeof(rxData));
    
        // 无限循环
        while (1) {
        }
    }
    
    // 错误处理函数
    void Error_Handler(void) {
        while (1) {
        }
    }
    
    // 系统时钟配置函数(由 STM32CubeMX 自动生成)
    void SystemClock_Config(void) {
        // 省略时钟配置代码,STM32CubeMX 自动生成
    }
    

    4. 代码解释

  • SPI1_Init:该函数初始化 SPI1 外设,设置 SPI 的模式(主模式)、数据位长度(8 位)、时钟极性、时钟相位、波特率等。
  • SPI_Transmit:通过 HAL_SPI_Transmit 向 SPI 从设备发送数据。
  • SPI_Receive:通过 HAL_SPI_Receive 从 SPI 从设备接收数据。
  • main:在主函数中,初始化 SPI1,发送数据并接收数据。
  • 5. 调试和测试

    1. 使用串口输出或逻辑分析仪,检查 SPI 总线上的信号。
    2. 确保从设备的时钟极性、时钟相位和其他 SPI 配置与主设备匹配。
    3. 如果使用 DMA 进行数据传输,可以通过 HAL_SPI_Transmit_DMAHAL_SPI_Receive_DMA 进行异步数据传输。

    6. 常见问题和解决方案

  • 时钟配置不匹配:确保主设备和从设备的时钟极性和时钟相位一致。
  • 数据传输错误:检查数据位长度、传输方向(MOSI/MISO)和 CS 线的控制是否正确。
  • DMA 配置问题:如果使用 DMA,确保 DMA 通道与 SPI 外设正确关联,并启用 DMA 中断处理。
  • 7. 扩展应用

  • 使用 SPI 与 EEPROM 通信:通过 SPI 与 EEPROM 存储器进行数据读写。
  • 显示屏控制:通过 SPI 控制液晶显示屏(如 SSD1306、ILI9341)进行图像显示。
  • 传感器数据读取:使用 SPI 与传感器(如加速度计、陀螺仪)进行高速数据交换。
  • 项目:使用SPI通信将单片机与W25Q64芯片进行数据交换:

    ### W25Q64 芯片介绍

    **W25Q64** 是 Winbond 公司生产的一款 **64M-bit(8M-byte)** 串行闪存芯片,采用 **SPI(Serial Peripheral Interface)** 接口进行通信。该芯片广泛应用于嵌入式系统中,用于存储程序代码、配置数据、日志等信息。

    #### 主要特性:
    1. **容量**:64M-bit(8M-byte),分为 128 个块(Block),每个块 64KB,每个块又分为 16 个扇区(Sector),每个扇区 4KB。
    2. **接口**:支持标准 SPI、Dual SPI 和 Quad SPI 接口,最高时钟频率可达 104MHz(在 Quad SPI 模式下)。
    3. **电压范围**:2.7V 至 3.6V,适合低功耗应用。
    4. **写保护**:支持硬件和软件写保护功能,保护数据不被意外修改。
    5. **擦除和编程**:
       – 支持扇区擦除(4KB)、块擦除(32KB 或 64KB)和整片擦除。
       – 页编程(Page Program)操作,每页 256 字节。
    6. **耐久性**:每个扇区可擦写至少 10 万次,数据保存时间可达 20 年。
    7. **封装**:常见的封装形式有 SOIC-8、WSON-8 和 USON-8 等。

    #### 引脚功能:
    – **CS**:片选信号,低电平有效。
    – **DO(MISO)**:数据输出(主设备输入)。
    – **WP**:写保护,低电平有效。
    – **GND**:地。
    – **DI(MOSI)**:数据输入(主设备输出)。
    – **CLK**:时钟信号。
    – **HOLD**:保持信号,低电平暂停通信。
    – **VCC**:电源(2.7V-3.6V)。

    ### W25Q64 的用途

    1. **程序存储**:
       – 在 STM32 等微控制器中,W25Q64 可用于存储固件代码(如 Bootloader 或应用程序),尤其是在片上 Flash 容量不足时。
       – 通过 SPI 接口加载程序到 RAM 中运行。

    2. **数据存储**:
       – 存储系统配置参数、用户数据、日志信息等。
       – 适合需要频繁更新数据的场景,如传感器数据记录。

    3. **固件升级**:
       – 通过外部接口(如 UART、USB 或网络)将新固件存储到 W25Q64 中,再通过 Bootloader 更新主程序。

    4. **扩展存储**:
       – 在需要大容量存储的应用中(如音频、图像处理),W25Q64 可以作为外部存储扩展。

    5. **缓存或临时存储**:
       – 在某些应用中,W25Q64 可以作为临时缓存,存储中间计算结果或通信数据。

    ### STM32 与 W25Q64 的连接

    在 STM32 中,W25Q64 通常通过 SPI 接口连接。以下是典型的连接方式:

    | W25Q64 引脚 | STM32 引脚 |
    |————-|————|
    | CS          | GPIO       |
    | DO (MISO)   | SPI_MISO   |
    | DI (MOSI)   | SPI_MOSI   |
    | CLK         | SPI_SCK    |
    | GND         | GND        |
    | VCC         | 3.3V       |
    | WP          | 高电平(或 GPIO 控制) |
    | HOLD        | 高电平(或 GPIO 控制) |

    ### 开发注意事项

    1. **SPI 配置**:
       – 在 STM32 中配置 SPI 接口时,需设置正确的时钟极性(CPOL)和相位(CPHA),通常为模式 0 或模式 3。
       – SPI 时钟频率应根据 W25Q64 的规格书设置,避免过高频率导致通信失败。

    2. **擦除和编程**:
       – 在写入数据前,必须先擦除对应的扇区或块。
       – 每次写入的数据不能超过 256 字节(一页)。

    3. **写保护**:
       – 如果需要保护某些数据,可以通过写保护引脚或软件命令实现。

    4. **电源管理**:
       – 确保 W25Q64 的电源稳定,避免电压波动导致数据丢失或损坏。

    ### 示例代码(STM32 HAL 库)

    以下是一个简单的 W25Q64 初始化及读写示例:

    ```c
    #include "stm32f1xx_hal.h"
    #include "w25q64.h"
    
    SPI_HandleTypeDef hspi1;
    
    void W25Q64_Init(void) {
        // 初始化 SPI
        hspi1.Instance = SPI1;
        hspi1.Init.Mode = SPI_MODE_MASTER;
        hspi1.Init.Direction = SPI_DIRECTION_2LINES;
        hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
        hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
        hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
        hspi1.Init.NSS = SPI_NSS_SOFT;
        hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
        hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
        hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
        hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
        hspi1.Init.CRCPolynomial = 10;
        HAL_SPI_Init(&hspi1);
    
        // 初始化 W25Q64
        W25Q64_Reset();
    }
    
    void W25Q64_Read(uint32_t address, uint8_t *data, uint32_t size) {
        uint8_t cmd[4] = {W25Q64_CMD_READ, (address >> 16) & 0xFF, (address >> 8) & 0xFF, address & 0xFF};
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // CS 低电平
        HAL_SPI_Transmit(&hspi1, cmd, 4, HAL_MAX_DELAY);
        HAL_SPI_Receive(&hspi1, data, size, HAL_MAX_DELAY);
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // CS 高电平
    }
    
    void W25Q64_Write(uint32_t address, uint8_t *data, uint32_t size) {
        W25Q64_WriteEnable();
        uint8_t cmd[4] = {W25Q64_CMD_PAGE_PROGRAM, (address >> 16) & 0xFF, (address >> 8) & 0xFF, address & 0xFF};
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // CS 低电平
        HAL_SPI_Transmit(&hspi1, cmd, 4, HAL_MAX_DELAY);
        HAL_SPI_Transmit(&hspi1, data, size, HAL_MAX_DELAY);
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // CS 高电平
        W25Q64_WaitForWriteEnd();
    }
    ```
    
    ---

    W25Q64 是一款 **SPI 接口的串行闪存芯片**,它并没有传统意义上的“寄存器”供用户直接读写。相反,W25Q64 通过 **命令(Command)** 来实现各种功能,例如读取数据、写入数据、擦除扇区、读取状态寄存器等。

    以下是如何使用 W25Q64 的命令来实现其功能的详细说明。

    ### W25Q64 的命令集

    W25Q64 通过 SPI 接口发送特定的命令来执行操作。每个命令由一个 **1 字节的操作码(Opcode)** 和可能的附加参数(如地址、数据等)组成。

    以下是常用的 W25Q64 命令:

    | 命令名称             | 操作码 (Hex) | 功能描述                                                                 |
    |———————-|————–|————————————————————————–|
    | **Write Enable**      | 0x06         | 使能写操作(在写、擦除或编程之前必须执行)                                |
    | **Write Disable**     | 0x04         | 禁用写操作                                                                |
    | **Read Status Reg-1** | 0x05         | 读取状态寄存器 1(用于检查忙状态或写保护状态)                            |
    | **Read Status Reg-2** | 0x35         | 读取状态寄存器 2(用于检查其他状态位)                                    |
    | **Write Status Reg**  | 0x01         | 写入状态寄存器(用于配置写保护或 Quad SPI 模式等)                        |
    | **Page Program**      | 0x02         | 页编程(写入数据,每次最多 256 字节)                                     |
    | **Sector Erase**      | 0x20         | 扇区擦除(擦除 4KB 的数据)                                               |
    | **Block Erase**       | 0xD8         | 块擦除(擦除 64KB 的数据)                                                |
    | **Chip Erase**        | 0xC7         | 整片擦除(擦除整个芯片的数据)                                            |
    | **Read Data**         | 0x03         | 读取数据(从指定地址开始读取数据)                                         |
    | **Fast Read**         | 0x0B         | 快速读取数据(支持更高的时钟频率)                                         |
    | **Power Down**        | 0xB9         | 进入低功耗模式                                                            |
    | **Release Power Down**| 0xAB         | 退出低功耗模式                                                            |

    ### 状态寄存器

    W25Q64 有两个状态寄存器(Status Register-1 和 Status Register-2),用于指示芯片的当前状态或配置。

    #### **Status Register-1 (SR1)**
    | 位   | 名称          | 描述                                                                 |
    |——|—————|———————————————————————-|
    | BIT7 | SRP0          | 状态寄存器保护位(与写保护相关)                                      |
    | BIT6 | SEC           | 扇区/块保护位                                                         |
    | BIT5 | TB            | 顶部/底部保护位                                                       |
    | BIT4 | BP2           | 块保护位 2                                                            |
    | BIT3 | BP1           | 块保护位 1                                                            |
    | BIT2 | BP0           | 块保护位 0                                                            |
    | BIT1 | WEL           | 写使能锁存位(1 表示写使能,0 表示写禁用)                             |
    | BIT0 | BUSY          | 忙状态位(1 表示芯片忙,0 表示芯片空闲)                               |

    #### **Status Register-2 (SR2)**
    | 位   | 名称          | 描述                                                                 |
    |——|—————|———————————————————————-|
    | BIT7 | SUS           | 挂起状态位                                                            |
    | BIT6 | CMP           | 互补保护位                                                            |
    | BIT5 | LB3           | 安全寄存器锁定位 3                                                    |
    | BIT4 | LB2           | 安全寄存器锁定位 2                                                    |
    | BIT3 | LB1           | 安全寄存器锁定位 1                                                    |
    | BIT2 | QE            | Quad SPI 使能位                                                       |
    | BIT1 | –             | 保留                                                                 |
    | BIT0 | SRP1          | 状态寄存器保护位 1                                                    |

    ### 如何使用命令和状态寄存器

    以下是一些常见操作的实现方法:

    #### 1. **读取状态寄存器**
    ```c

    uint8_t W25Q64_ReadStatusReg1(void) {
        uint8_t status = 0;
        uint8_t cmd = W25Q64_CMD_READ_STATUS_REG1;
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // CS 低电平
        HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY);
        HAL_SPI_Receive(&hspi1, &status, 1, HAL_MAX_DELAY);
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // CS 高电平
        return status;

    }
    ```

    #### 2. **写使能**
    在执行写操作(如页编程、擦除)之前,必须先发送写使能命令。

    ```c
    void W25Q64_WriteEnable(void) {
        uint8_t cmd = W25Q64_CMD_WRITE_ENABLE;
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // CS 低电平
        HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY);
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // CS 高电平
    }
    ```

    #### 3. **等待芯片空闲**
    在执行写操作后,需要等待芯片完成操作(通过检查状态寄存器的 BUSY 位)。

    ```c
    void W25Q64_WaitForWriteEnd(void) {
        while (W25Q64_ReadStatusReg1() & 0x01); // 等待 BUSY 位为 0
    }
    ```

    #### 4. **页编程(写入数据)**

    ```c
    void W25Q64_PageProgram(uint32_t address, uint8_t *data, uint16_t size) {
        W25Q64_WriteEnable();
        uint8_t cmd[4] = {W25Q64_CMD_PAGE_PROGRAM, (address >> 16) & 0xFF, (address >> 8) & 0xFF, address & 0xFF};
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // CS 低电平
        HAL_SPI_Transmit(&hspi1, cmd, 4, HAL_MAX_DELAY);
        HAL_SPI_Transmit(&hspi1, data, size, HAL_MAX_DELAY);
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // CS 高电平
        W25Q64_WaitForWriteEnd();
    }
    ```

    #### 5. **扇区擦除**

    ```c
    void W25Q64_SectorErase(uint32_t address) {
        W25Q64_WriteEnable();
        uint8_t cmd[4] = {W25Q64_CMD_SECTOR_ERASE, (address >> 16) & 0xFF, (address >> 8) & 0xFF, address & 0xFF};
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // CS 低电平
        HAL_SPI_Transmit(&hspi1, cmd, 4, HAL_MAX_DELAY);
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // CS 高电平
        W25Q64_WaitForWriteEnd();
    }

    W25Q64 的功能通过 **命令** 来实现,而不是传统的寄存器操作。你需要通过 SPI 接口发送特定的命令来执行读取、写入、擦除等操作。状态寄存器用于检查芯片的当前状态(如是否忙、是否写使能等)。

    在实际开发中,建议参考 W25Q64 的官方数据手册,了解每个命令的详细用法和时序要求。

    操作:

    1配置关于spi通信GPIO的时钟,以及spi通信外设时钟使能

    2配置spi通信结构体,对结构体成员赋值,成员包括数据传输方向例如双线全双工,单线只接受,单线只发送等,通信模式(主机模式和从机模式),数据帧大小,时钟极性,时钟相位(主要针对采用的边沿的,引脚模式,以及分频因子设置,数据高位先行还是地位先行

    3调用初始化函数,对SPI结构体进行初始化,然后调用对NSS引脚置高电平,然后选中从机开始通信并等待地址发送数据TXE标志位置一

    4调用数据发送函数,并且采用while循环等待接受数据RXNE标志位置一,最后调用读取数据函数以读出存储芯片里面的数据,

    5然后在主函数里面设置数组存储数据并且调用模块函数把数据显示在上位机或者OLED屏上

    #include  "mpu6050.h"
    #include "IIC.h"
    #include  "w25q64.h"
    uint8_t MID;
    uint16_t DID;
    uint8_t writarry[]={0x55,0x66,0X77,0x88};
    uint8_t writread[4];
    int main(void){
    	  W25Q64_Init();
    	  OLED_Init();
    	OLED_ShowString (1,1,"DID:  MID:");
    	OLED_ShowString (2,1,"W:");
    	OLED_ShowString (3,1,"R:");
    	 W25Q64_ReadID(&MID,&DID);
    	OLED_ShowHexNum (1,5,MID,2); 
    	OLED_ShowHexNum (1,12,DID,4);
       W25Q64_SectorErase(0x000000);
    	 W25Q64_PageProgram(0x0000FF, writarry,4);
    	 W25Q64_ReadData(0x0000FF,writread,4);
    	 OLED_ShowHexNum (2,3,writarry[0],2);
    	 OLED_ShowHexNum (2,6,writarry[1],2);
    	 OLED_ShowHexNum (2,9,writarry[2],2);
    	 OLED_ShowHexNum (2,12,writarry[3],2);
    	 OLED_ShowHexNum  (3,3, writread[0],2);
    	 OLED_ShowHexNum (3,6, writread[1],2);
    	 OLED_ShowHexNum (3,9, writread[2],2);
    	 OLED_ShowHexNum (3,12,writread[3],2);
    	  while(1){
    			
    		}
    }
    #include "stm32f10x.h"                  // Device header
    #include "spi.h"
    #include "W25q64_ins.h"
    #include "w25q64.h"
    void W25Q64_Init(void)
    {
    	MySPI_Init();
    }
    
    void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
    {
    	MySPI_Start();
    	MySPI_SwapByte(W25Q64_JEDEC_ID);
    	*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
    	*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
    	*DID <<= 8;
    	*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);
    	MySPI_Stop();
    }
    
    void W25Q64_WriteEnable(void)
    {
    	MySPI_Start();
    	MySPI_SwapByte(W25Q64_WRITE_ENABLE);
    	MySPI_Stop();
    }
    
    void W25Q64_WaitBusy(void)
    {
    	uint32_t Timeout;
    	MySPI_Start();
    	MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);
    	Timeout = 100000;
    	while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)
    	{
    		Timeout --;
    		if (Timeout == 0)
    		{
    			break;
    		}
    	}
    	MySPI_Stop();
    }
    
    void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
    {
    	uint16_t i;
    	
    	W25Q64_WriteEnable();
    	
    	MySPI_Start();
    	MySPI_SwapByte(W25Q64_PAGE_PROGRAM);
    	MySPI_SwapByte(Address >> 16);
    	MySPI_SwapByte(Address >> 8);
    	MySPI_SwapByte(Address);
    	for (i = 0; i < Count; i ++)
    	{
    		MySPI_SwapByte(DataArray[i]);
    	}
    	MySPI_Stop();
    	
    	W25Q64_WaitBusy();
    }
    
    void W25Q64_SectorErase(uint32_t Address)
    {
    	W25Q64_WriteEnable();
    	
    	MySPI_Start();
    	MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
    	MySPI_SwapByte(Address >> 16);
    	MySPI_SwapByte(Address >> 8);
    	MySPI_SwapByte(Address);
    	MySPI_Stop();
    	
    	W25Q64_WaitBusy();
    }
    
    void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
    {
    	uint32_t i;
    	MySPI_Start();
    	MySPI_SwapByte(W25Q64_READ_DATA);
    	MySPI_SwapByte(Address >> 16);
    	MySPI_SwapByte(Address >> 8);
    	MySPI_SwapByte(Address);
    	for (i = 0; i < Count; i ++)
    	{
    		DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
    	}
    	MySPI_Stop();
    }
    
    #include "stm32f10x.h"                  // Device header
    #include "spi.h"
    void MySPI_Init(){
    	RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOA,ENABLE );
    	RCC_APB2PeriphClockCmd ( RCC_APB2Periph_SPI1,ENABLE );
    	 GPIO_InitTypeDef  GPIOinitsturt;
    	 //配置关于GPIO的NSS输出引脚
    	 GPIOinitsturt .GPIO_Mode =GPIO_Mode_Out_PP;
    	 GPIOinitsturt .GPIO_Pin =GPIO_Pin_4;
    	 GPIOinitsturt .GPIO_Speed = GPIO_Speed_50MHz;
    	 GPIO_Init (GPIOA ,&GPIOinitsturt);
    	//配置关于GPIO的CLK和MOSI引脚输出
    	 GPIOinitsturt .GPIO_Mode =GPIO_Mode_AF_PP ;
    	 GPIOinitsturt .GPIO_Pin =GPIO_Pin_5|GPIO_Pin_7;
    	 GPIOinitsturt .GPIO_Speed =GPIO_Speed_50MHz;
    	 GPIO_Init (GPIOA,&GPIOinitsturt);
    	//配置关于GPIO的MISO引脚输出
    	 GPIOinitsturt .GPIO_Mode = GPIO_Mode_IPU ;
    	 GPIOinitsturt .GPIO_Pin =GPIO_Pin_6;
    	 GPIOinitsturt .GPIO_Speed =GPIO_Speed_50MHz;
    	 GPIO_Init (GPIOA,&GPIOinitsturt);
    	 //初始化SPI外设
    	SPI_InitTypeDef   spi_initsturt;
    	spi_initsturt.SPI_BaudRatePrescaler =SPI_BaudRatePrescaler_128 ;
    	spi_initsturt.SPI_CPHA =SPI_CPHA_1Edge ; 
    	spi_initsturt.SPI_CPOL =SPI_CPOL_Low;
    	spi_initsturt.SPI_CRCPolynomial =7;
    	spi_initsturt.SPI_DataSize =SPI_DataSize_8b ;
    	spi_initsturt.SPI_Direction =SPI_Direction_2Lines_FullDuplex ;
    	spi_initsturt.SPI_FirstBit =SPI_FirstBit_MSB; 
    	spi_initsturt.SPI_Mode =SPI_Mode_Master ;
    	spi_initsturt.SPI_NSS =SPI_NSS_Soft ;
      SPI_Init (SPI1,&spi_initsturt);
    	SPI_Cmd(SPI1,ENABLE );
    	myspi_w_ss(1);
    	
    	
    }
    void myspi_w_ss(uint8_t BitValue)
    {
    	GPIO_WriteBit(GPIOA ,  GPIO_Pin_4, (BitAction) BitValue);
    	
    }
    void myspi_w_sck(uint8_t BitValue)
    {
    	GPIO_WriteBit(GPIOA ,  GPIO_Pin_5, (BitAction) BitValue);
    	
    }
    	
    	void myspi_w_mosi(uint8_t BitValue)
    {
    	GPIO_WriteBit(GPIOA ,  GPIO_Pin_7, (BitAction) BitValue);
    	
    }
    	void MySPI_Start(void)
    	{
    		 myspi_w_ss(0);
    		
    	}
    void MySPI_Stop(void )
    	{
    		 myspi_w_ss(1);
    		
    	}
    	uint8_t  MySPI_SwapByte(uint8_t  byesent)
    	{
    		
    		 while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE)==RESET);
    		 SPI_I2S_SendData(SPI1, byesent);
    		 while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE)==RESET);
    		 return  SPI_I2S_ReceiveData(SPI1);
    		
    	}

    作者:想要成为糕手。

    物联沃分享整理
    物联沃-IOTWORD物联网 » stm32-spi通信

    发表回复