STM32_11(SPI)

1. SPI communication

  • SPI (Serial Peripheral Interface) is a universal data bus developed by Motorola
  • Four communication lines: SCK (Serial Clock), MOSI (Master Output Slave Input), MISO (Master Input Slave Output), SS (Slave Select)
  • MOSI: It is the data signal line for master device output and slave device input; MISO: The data signal line for master device input and slave device output; 
  • Synchronous, full duplex
  • Supports mounting multiple devices on the bus (one master, multiple slaves)

1. SPI hardware circuit

  • The SCK, MOSI, and MISO of all SPI devices are connected together.
  • The host also leads out multiple SS control lines, which are connected to the SS pins of each slave.
  • Output pins are configured as push-pull outputs and input pins are configured as floating or pull-up inputs.
  • The host can only select one slave. Selecting multiple slaves will cause data conflicts. When the slave's SS pin is high, it means that the slave is not selected, and its MISO pin must be switched to a high-impedance state, that is, the pin is disconnected. This can prevent level conflicts caused by multiple outputs on one line.

2. Shift diagram

        The shift register has a clock input. Because SPI is high-order first, so every time a clock comes, the shift register will shift to the left. The same is true for the shift register of the slave. The clock source of the shift register is provided by the host, here it is called the baud rate generator, and the clock it generates drives the shift register of the host to shift. At the same time, this clock is also output through the SCK pin and connected to the shift register of the slave. The data shifted out from the left side of the master shift register is input to the right side of the slave shift register through the MOSI pin, and the data in the slave shift register is input to the right side of the master shift register according to MISO.

        First of all, it is stipulated that on the rising edge of the baud rate generator clock, all shift registers move one bit to the left, and the shifted bits are placed on the pins. On the falling edge of the baud rate generator clock, the bits on the pins are sampled input. to the lowest bit of the shift register.

3. SPI timing basic unit

① Starting conditions and ending conditions

  • Starting condition: SS switches from high level to low level
  • Termination condition: SS switches from low level to high level

② Mode 0

  • Swap one byte (mode 0)
  • CPOL=0: In idle state, SCK is low level
  • CPHA=0: The first edge of SCK shifts data in, and the second edge shifts data out.

Generally speaking, both MOSI and MISO are half a cycle ahead of SCK.

② Mode 1

  • Swap one byte (mode 1)
  • CPOL=0: In idle state, SCK is low level
  • CPHA=1 (clock phase: determines whether the first clock sample is shifted in or the second clock sample is shifted in): the first edge of SCK shifts data out, and the second edge shifts data in

        When SS is high, MISO uses a middle line to indicate the high-impedance state. After the falling edge of SS, the MISO of the slave is allowed to turn on the output. After the rising edge of SS, the slave's MISO must be returned to the high-impedance state. When SCK is on the rising edge, the master and the slave remove data at the same time. The master removes the highest bit through MOSI. The level of MOSI at this time indicates that the host wants to send data B7, and the slave removes the highest bit through MISO. At this time MISO means that the slave wants to send data B7. Then the clock runs and a falling edge is generated. At this time, the host and slave need to move in data at the same time, that is, data sampling. Here the host removes B7 and enters the lowest bit of the slave shift register. The slave removes B7 and enters the host shift register. The lowest bit of the bit register. Once a clock pulse is generated, one bit of data is transmitted.

③ SPI timing (sending instructions)

        ​​​​Here we are using mode 0. First, SS is high level and SCK is low level. SS generates a falling edge, and the timing begins. At the falling edge, MOSI and MISO begin to compare data. The instruction code of MOSI is still 0, so it remains low. The MISO slave has no data to send to the host. The pin voltage Ping has no transformation. The slave samples the input and gets 0, and the master samples the input and gets 1. After that, the host wants to send data 1, SCK falls on the falling edge, the data is moved out, the host moves 1 out to MOSI, and MOSI becomes high level. When the host sends 0, SCK falls on the falling edge and MOSI becomes 0. On the rising edge of SCK, data is sampled and the slave receives data as 0. Generally speaking, the changing period is when SCK is low and the reading period is when it is high (data is converted on the falling edge and sampled on the rising edge).

        This SPI timing represents that the host exchanges 0x06 for the slave's 0xFF.

④ SPI timing (write to specified address)

        Send a write command (0x02) to the device specified by SS, and then write the specified data (Data) at the specified address (Address[23:0]).

⑤ SPI timing (specified address read)

        Send a write command (0x02) to the device specified by SS, and then write the specified data (Data) at the specified address (Address[23:0]).

2. SPI peripherals

  • STM32 integrates a hardware SPI transceiver circuit, which can automatically perform clock generation, data transceiver and other functions by the hardware, reducing the burden on the CPU.
  • Configurable 8-bit/16-bit data frame, high-end first/low-end first
  • Clock frequency: fPCLK / (2, 4, 8, 16, 32, 64, 128, 256)
  • Supports multi-master model, master or slave operation
  • Can be reduced to half-duplex/simplex communication
  • Support DMA
  • Compatible with I2S protocol
  • STM32F103C8T6 hardware SPI resources: SPI1, SPI2

1. SPI block diagram

        The low-bit data on the right side of the shift register is shifted out of MOSI one bit one by one, and the data in MISO is moved out bit by bit to the high-bit data of the left shift register.

        This can control whether low position first or high position first.

        If we need to send a series of data continuously, the first data is written into the transmit buffer (TDR). When the shift register has no data to shift, the TDR data will immediately be transferred to the shift register and start shifting. This transfer At the input moment, the TXE of the status register will be set to 1 (indicating that the transmit register is empty). Immediately afterwards, the next data can be written to the TDR in advance and wait. Once the previous data is sent, the next data can be followed immediately, achieving seamless Intermittent continuous transmission. When the data reaches the shift register, it will automatically generate a clock to move the data out. During the removal process, the MISO data will also be moved in. Once the data removal is complete, the data move-in is also complete. At this time, the transferred data is transferred from the shift register to the receive buffer RDR as a whole. At this time, the status register RXNE is set to 1 (indicating that the receive register is not empty). When we detect that RXNE is set to 1, we need to read the data out of the RDR as soon as possible. We can read out the RDR before the next data comes to achieve continuous reception.

2. SPI basic structure

3. Main mode full-duplex continuous transmission (higher efficiency)

4. Discontinuous transmission (code friendly)

step:

1. Wait for TXE to be set to 1;

2. Write data to the TDR register;

3. Wait for RXNE to be 1;

4. Read the data received by RDR.

3. W25Q64 module

  • The W25Qxx series is a low-cost, compact, and easy-to-use non-volatile memory that is often used in data storage, font storage, firmware program storage, etc.
  • Storage medium: Nor Flash (flash memory)
  • Clock frequency: 80MHz / 160MHz (Dual SPI) / 320MHz (Quad SPI)
  • Storage capacity (24-bit address):

    W25Q40:  4Mbit / 512KByte

    W25Q80:  8Mbit / 1MByte

    W25Q16:  16Mbit / 2MByte

    W25Q32:  32Mbit / 4MByte

    W25Q64:  64Mbit / 8MByte

    W25Q128:  128Mbit / 16MByte

    W25Q256: 256Mbit / 32MByte

1. W25Q64 hardware circuit diagram

 

 

        /CS (/ means low level is active or there is a horizontal line above CS which is also low level.

        CS here corresponds to SS, DI corresponds to MOSI, and DO corresponds to MISO.

2. Flash operation precautions

  • During write operation:
  • Before writing operation, write enable must be performed
  • Each data bit can only be rewritten from 1 to 0, but cannot be rewritten from 0 to 1.
  • Before writing data, it must be erased first. After erasing, all data bits become 1.
  • Erasing must be performed according to the smallest erasing unit (the smallest erasing unit is a sector of 4096 bytes, and the largest one can erase all)
  • When writing multiple bytes continuously, a maximum of one page (256 bytes) of data can be written. Data beyond the end of the page will be returned to the top of the page and overwritten.
  • After the write operation is completed, the chip enters the busy state and does not respond to new read and write operations.
  • During read operation: Directly call the read sequence, no enablement, no additional operations, no page limit, the read operation will not enter the busy state after the end of the read operation, but it cannot be read in the busy state

4. Code part

1. SPI software configuration code

#include "Bsp_SPI.h"

/* SS写数据 */
void Bsp_SPI_W_SS(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}

/* SCK写数据 */
void Bsp_SPI_W_SCK(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue);
}

/* MOSI写数据 */
void Bsp_SPI_W_MOSI(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue);
}

/* MISO读数据 */
uint8_t Bsp_SPI_R_MISO(void)
{
    return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);
}

/* SPI初始化 */
void Bsp_SPI_Init(void)
{
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    Bsp_SPI_W_SS(1);
    Bsp_SPI_W_SCK(0);

}

/* SPI起始 */
void Bsp_SPI_Start(void)
{
    Bsp_SPI_W_SS(0);
}

/* SPI终止 */
void Bsp_SPI_Stop(void)
{
    Bsp_SPI_W_SS(1);
}

/* MOSI和MISO交换字节 */
/* 模式0 */
uint8_t Bsp_SPI_SwapByte(uint8_t ByteSend)
{
    uint8_t ByteReceive = 0x00;

    for (uint8_t i = 0; i < 8; i++)
    {
        Bsp_SPI_W_MOSI(ByteSend & (0x80 >> i));
        Bsp_SPI_W_SCK(1);
        if (Bsp_SPI_R_MISO() == 1)
        {
            ByteReceive |= (0x80 >> i);
        }
        Bsp_SPI_W_SCK(0);
    }

    return ByteReceive;
}

/* 模式1 */
/*uint8_t Bsp_SPI_SwapByte(uint8_t ByteSend)
{
    uint8_t ByteReceive = 0x00;

    for (uint8_t i = 0; i < 8; i++)
    {
        Bsp_SPI_W_SCK(1);
        Bsp_SPI_W_MOSI(ByteSend &(0x80 >> i));
        Bsp_SPI_W_SCK(0);
        if (Bsp_SPI_R_MISO == 1)
        {
            ByteReceive |= (0x80 >> i);
        }
    }

    return ByteReceive;
}*/

/* 模式3 */
/*uint8_t Bsp_SPI_SwapByte(uint8_t ByteSend)
{
    uint8_t ByteReceive = 0x00;

    for (uint8_t i = 0; i < 8; i++)
    {
        Bsp_SPI_W_SCK(0);
        Bsp_SPI_W_MOSI(ByteSend &(0x80 >> i));
        Bsp_SPI_W_SCK(1);
        if (Bsp_SPI_R_MISO == 1)
        {
            ByteReceive |= (0x80 >> i);
        }
    }

    return ByteReceive;
}*/

/* 模式2 */
/*uint8_t Bsp_SPI_SwapByte(uint8_t ByteSend)
{
    uint8_t ByteReceive = 0x00;

    for (uint8_t i = 0; i < 8; i++)
    {
        Bsp_SPI_W_MOSI(ByteSend &(0x80 >> i));
        Bsp_SPI_W_SCK(0);
        if (Bsp_SPI_R_MISO == 1)
        {
            ByteReceive |= (0x80 >> i);
        }
        Bsp_SPI_W_SCK(1);
    }

    return ByteReceive;
}*/

2. W25Q64 address packaging

#ifndef __W25Q64_INS_H
#define __W25Q64_INS_H

#define W25Q64_WRITE_ENABLE                         0x06
#define W25Q64_WRITE_DISABLE                        0x04
#define W25Q64_READ_STATUS_REGISTER_1               0x05
#define W25Q64_READ_STATUS_REGISTER_2               0x35
#define W25Q64_WRITE_STATUS_REGISTER                0x01
#define W25Q64_PAGE_PROGRAM                         0x02
#define W25Q64_QUAD_PAGE_PROGRAM                    0x32
#define W25Q64_BLOCK_ERASE_64KB                     0xD8
#define W25Q64_BLOCK_ERASE_32KB                     0x52
#define W25Q64_SECTOR_ERASE_4KB                     0x20
#define W25Q64_CHIP_ERASE                           0xC7
#define W25Q64_ERASE_SUSPEND                        0x75
#define W25Q64_ERASE_RESUME                         0x7A
#define W25Q64_POWER_DOWN                           0xB9
#define W25Q64_HIGH_PERFORMANCE_MODE                0xA3
#define W25Q64_CONTINUOUS_READ_MODE_RESET           0xFF
#define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID     0xAB
#define W25Q64_MANUFACTURER_DEVICE_ID               0x90
#define W25Q64_READ_UNIQUE_ID                       0x4B
#define W25Q64_JEDEC_ID                             0x9F
#define W25Q64_READ_DATA                            0x03
#define W25Q64_FAST_READ                            0x0B
#define W25Q64_FAST_READ_DUAL_OUTPUT                0x3B
#define W25Q64_FAST_READ_DUAL_IO                    0xBB
#define W25Q64_FAST_READ_QUAD_OUTPUT                0x6B
#define W25Q64_FAST_READ_QUAD_IO                    0xEB
#define W25Q64_OCTAL_WORD_READ_QUAD_IO              0xE3

#define W25Q64_DUMMY_BYTE                           0xFF                // 空地址,代表没用的地址,因为在传输过程中总会只有发送或只有接收的时候

#endif

3. Software SPI reads W25Q64

#include "Bsp_W25Q64.h"

void W25Q64_Init(void)
{
    Bsp_SPI_Init();
}

/* 读取ID */
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
    Bsp_SPI_Start();
    Bsp_SPI_SwapByte(W25Q64_JEDEC_ID);               // 发送0x9F指令码
    *MID = Bsp_SPI_SwapByte(W25Q64_DUMMY_BYTE);      // 返回制造厂商ID
    *DID = Bsp_SPI_SwapByte(W25Q64_DUMMY_BYTE);      // 返回设备ID的高8位
    *DID <<= 8;                         
    *DID |= Bsp_SPI_SwapByte(W25Q64_DUMMY_BYTE);     // 返回设备ID的低8位
    Bsp_SPI_Stop();
}

/* 使能 */
void W25Q64_WriteEnable(void)
{
    Bsp_SPI_Start();
    Bsp_SPI_SwapByte(W25Q64_WRITE_ENABLE);         
    Bsp_SPI_Stop();
}

/* 读状态寄存器1(主要读取BUSY位,判断是否在忙) */
void W25Q64_WaitBusy(void)
{
    uint32_t TimeOut = 100000;

    Bsp_SPI_Start();
    Bsp_SPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);   
    while ((Bsp_SPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)        // 判断状态寄存器1的最低位,也就是BUSY位是否在忙。0:忙     1:在忙
    {
        TimeOut--;
        if (TimeOut == 0)
        {
            break;
        }
    }
    Bsp_SPI_Stop();
}

/* 页编程 */
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataAarry, uint16_t Length)
{
    W25Q64_WriteEnable();                       // 使能
    Bsp_SPI_Start();
    Bsp_SPI_SwapByte(W25Q64_PAGE_PROGRAM);
    Bsp_SPI_SwapByte(Address >> 16);
    Bsp_SPI_SwapByte(Address >> 8);
    Bsp_SPI_SwapByte(Address);

    for (uint16_t i = 0; i < Length; i++)
    {
        Bsp_SPI_SwapByte(DataAarry[i]);
    }
    Bsp_SPI_Stop();

    W25Q64_WaitBusy();
}

/* 扇区擦除 */
void W25Q64_SectorErase(uint32_t Address)
{
    W25Q64_WriteEnable();                       // 使能
    Bsp_SPI_Start();
    Bsp_SPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
    Bsp_SPI_SwapByte(Address >> 16);
    Bsp_SPI_SwapByte(Address >> 8);
    Bsp_SPI_SwapByte(Address);
    Bsp_SPI_Stop();

    W25Q64_WaitBusy();
}

/* 读数据 */
void W25Q64_ReadData(uint32_t Address, uint8_t *DataAarry, uint32_t Length)
{
    Bsp_SPI_Start();
    Bsp_SPI_SwapByte(W25Q64_READ_DATA);
    Bsp_SPI_SwapByte(Address >> 16);
    Bsp_SPI_SwapByte(Address >> 8);
    Bsp_SPI_SwapByte(Address);

    for (uint32_t i = 0; i < Length; i++)
    {
        DataAarry[i] = Bsp_SPI_SwapByte(W25Q64_DUMMY_BYTE);
    }
    Bsp_SPI_Stop();
}

4. Hardware SPI reading W25Q64

#include "Bsp_SPI.h"

/* SS写数据 */
void Bsp_SPI_W_SS(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}

/* SPI初始化 */
void Bsp_SPI_Init(void)
{
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    SPI_InitTypeDef SPI_InitStructure;
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;                    // 分频系数,分频越高速度越快(注意:SPI1是72M,SPI2是36M。因为SPI1是在APB2,SPI2是在APB1)
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;                                            // 选择第一个边沿采样还是第二个边沿采样
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;                                              // 时钟极性:因为在这里选择模式0
    SPI_InitStructure.SPI_CRCPolynomial = 7;                                                // CRC校验多项式,默认值7
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;                                       // 8位数据帧
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;                      // 双线全双工模式
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;                                      // 高位先行
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;                                           // 选择向前设备时SPI主机
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;                                               // NSS引脚,一般没用到选择软件NSS
    SPI_Init(SPI1, &SPI_InitStructure);

    SPI_Cmd(SPI1, ENABLE);

    Bsp_SPI_W_SS(1);

}

/* SPI起始 */
void Bsp_SPI_Start(void)
{
    Bsp_SPI_W_SS(0);
}

/* SPI终止 */
void Bsp_SPI_Stop(void)
{
    Bsp_SPI_W_SS(1);
}

/* MOSI和MISO交换字节 */
/* 模式0 */
uint8_t Bsp_SPI_SwapByte(uint8_t ByteSend)
{
    uint32_t TimeOut = 10000;
    uint8_t ByteReceive;
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != 1)
    {
        TimeOut--;
        if (TimeOut == 0)
        {
            break;
        } 
    }
    SPI_I2S_SendData(SPI1, ByteSend);
    TimeOut = 10000;
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != 1)
    {
        TimeOut--;
        if (TimeOut == 0)
        {
            break;
        } 
    }
    ByteReceive = SPI_I2S_ReceiveData(SPI1);
    return  ByteReceive;
}

5. Main program

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Bsp_W25Q64.h"

uint8_t MID;
uint16_t DID;
uint8_t ArrayWrite[] = {0x11, 0x22, 0x33, 0x44};
uint8_t ArrayRead[4];

int main(void)
{

    OLED_Init();                        
    W25Q64_Init();

    /* 显示厂商和设备ID号 */
    OLED_ShowString(1, 1, "MID:    DID:");
    OLED_ShowString(2, 1, "W:");
    OLED_ShowString(3, 1, "R:");

    W25Q64_ReadID(&MID, &DID);
    OLED_ShowHexNum(1, 5, MID, 2);
    OLED_ShowHexNum(1, 13, DID, 4);

    W25Q64_SectorErase(0x000000);                    // 只要末尾3个十六进制数为0,那肯定是扇区的起始地址(不擦除,掉电不丢失)。如果不擦除则改写则会数据出错,因为只能1变0不能0变1的操作所以修改数据前得擦除。
    W25Q64_PageProgram(0x00000, ArrayWrite, 4);      // 写入数据ArrayWrite,并且写入的数据不能跨页

    W25Q64_ReadData(0x000000, ArrayRead, 4);         // 读数据

    /* 显示写入数据 */
    OLED_ShowHexNum(2, 3, ArrayWrite[0], 2);
    OLED_ShowHexNum(2, 6, ArrayWrite[1], 2);
    OLED_ShowHexNum(2, 9, ArrayWrite[2], 2);
    OLED_ShowHexNum(2, 12, ArrayWrite[3], 2);

    /* 显示读出数据 */
    OLED_ShowHexNum(3, 3, ArrayRead[0], 2);
    OLED_ShowHexNum(3, 6, ArrayRead[1], 2);
    OLED_ShowHexNum(3, 9, ArrayRead[2], 2);
    OLED_ShowHexNum(3, 12, ArrayRead[3], 2);

    while (1)
    {
        
    }
}

Guess you like

Origin blog.csdn.net/qq_45475497/article/details/134683310