STM32_11(SPI)

1.SPI通信

  • SPI (Serial Peripheral Interface) は、モトローラによって開発されたユニバーサル データ バスです。
  • 4つの通信ライン: SCK (シリアルクロック)、MOSI (マスター出力スレーブ入力)、MISO (マスター入力スレーブ出力)、SS (スレーブ選択)
  • MOSI: マスターデバイス出力とスレーブデバイス入力のデータ信号線です; MISO: マスターデバイス入力とスレーブデバイス出力のデータ信号線です。 
  • 同期、全二重
  • バス上での複数のデバイスのマウントをサポート (1 つのマスター、複数のスレーブ)

1. SPIハードウェア回路

  • すべての SPI デバイスの SCK、MOSI、および MISO は相互に接続されています。
  • ホストはまた、各スレーブの SS ピンに接続される複数の SS 制御線も引き出します。
  • 出力ピンはプッシュプル出力として構成され、入力ピンはフローティング入力またはプルアップ入力として構成されます。
  • ホストが選択できるスレーブは 1 つだけです。複数のスレーブを選択するとデータの競合が発生します。スレーブの SS ピンが High の場合、スレーブが選択されていないことを意味し、その MISO ピンをハイ インピーダンス状態に切り替える必要があります。つまり、ピンが切断されています。これにより、1 行上の複数の出力によって引き起こされるレベルの競合を防ぐことができます。

2. シフトダイヤグラム

        シフトレジスタにはクロック入力が付いていますが、SPIは上位優先なのでクロックが来るたびにシフトレジスタは左にシフトします(スレーブのシフトレジスタも同様です)。シフト レジスタのクロック ソースはホストによって提供されます (ここではボー レート ジェネレータと呼ばれます)。ホストが生成するクロックはホストのシフト レジスタを駆動してシフトします。同時に、このクロックは SCK ピンからも出力され、スレーブのシフト レジスタに接続されます。マスター シフト レジスタの左側からシフトアウトされたデータは、MOSI ピンを介してスレーブ シフト レジスタの右側に入力され、スレーブ シフト レジスタのデータは、次に従ってマスター シフト レジスタの右側に入力されます。味噌。

        まず、ボーレート ジェネレーター クロックの立ち上がりエッジで、すべてのシフト レジスタが 1 ビット左に移動し、シフトされたビットがピンに配置されることが規定されており、ボー レート ジェネレーター クロックの立ち下がりエッジで、ピンのビットはシフト レジスタの最下位ビットにサンプリングされて入力されます。

3. SPIタイミング基本ユニット

①開始条件と終了条件

  • 開始条件:SS がハイレベルからローレベルに切り替わる
  • 終了条件:SSがローレベルからハイレベルに切り替わる

②モード0

  • 1バイトをスワップ(モード0)
  • CPOL=0: アイドル状態では、SCK はローレベルです
  • CPHA=0: SCK の最初のエッジでデータがシフトインされ、2 番目のエッジでデータがシフトアウトされます。

一般に、MOSI と MISO はどちらも SCK よりも半サイクル進んでいます。

②モード1

  • 1バイトをスワップ(モード1)
  • CPOL=0: アイドル状態では、SCK はローレベルです
  • CPHA=1 (クロック位相: 最初のクロック サンプルがシフトインされるか、2 番目のクロック サンプルがシフトインされるかを決定します): SCK の最初のエッジがデータをシフトアウトし、2 番目のエッジがデータをシフトインします。

        SS が High の場合、MISO は中央のラインを使用してハイ インピーダンス状態を示し、SS の立ち下がりエッジの後、スレーブの MISO は出力をオンにすることができます。 SS の立ち上がりエッジの後、スレーブの MISO はハイ インピーダンス状態に戻す必要があります。 SCK が立ち上がりエッジにあるとき、マスターとスレーブは同時にデータを削除します。マスターは MOSI を介して最上位ビットを削除します。このときの MOSI のレベルは、ホストがデータ B7 を送信したいことを示し、スレーブはデータを削除しますMISO を介した最上位ビットこのとき、MISO はスレーブがデータ B7 を送信したいことを意味します。その後、クロックが動作し、立ち下がりエッジが生成されます。このとき、ホストとスレーブは同時にデータを移動する必要があります (データ サンプリング)。ここで、ホストは B7 を削除し、スレーブ シフト レジスタの最下位ビットを入力します。 . スレーブは B7 を削除し、ホスト シフト レジスタに入ります (ビット レジスタの最下位ビット)。クロックパルスが生成されると、1 ビットのデータが送信されます。

③SPIタイミング(命令送信)

        ここではモード 0 を使用しています。まず、SS はハイレベル、SCK はローレベルです。 SS が立ち下がりエッジを生成し、タイミングが開始されます。立ち下がりエッジで、MOSI と MISO がデータの比較を開始します。MOSI の命令コードはまだ 0 であるため、ローのままです。MISO スレーブにはホストに送信するデータがありません。ピン電圧 Ping には変換がありません。スレーブは入力をサンプリングして 0 を取得し、マスターは入力をサンプリングして 1 を取得します。その後、ホストがデータ 1 を送信したいと考え、SCK が立ち下がりエッジで立ち下がり、データが転送され、ホストが MOSI に 1 を転送し、MOSI がハイ レベルになります。ホストが 0 を送信すると、SCK が立ち下がりエッジで立ち下がり、MOSI が 0 になります。 SCK の立ち上がりエッジでデータがサンプリングされ、スレーブはデータを 0 として受信します。一般に、変化期間は SCK が Low のとき、読み取り期間は SCK が High のときです (データは立ち下がりエッジで変換され、立ち上がりエッジでサンプリングされます)。

        この SPI タイミングは、ホストが 0x06 をスレーブの 0xFF に交換することを表します。

④ SPIタイミング(指定アドレスへの書き込み)

        SS で指定されたデバイスに書き込みコマンド (0x02) を送信し、指定されたアドレス (Address[23:0]) に指定されたデータ (Data) を書き込みます。

⑤ SPIタイミング(指定アドレス読み出し)

        SS で指定されたデバイスに書き込みコマンド (0x02) を送信し、指定されたアドレス (Address[23:0]) に指定されたデータ (Data) を書き込みます。

2. SPI ペリフェラル

  • STM32 にはハードウェア SPI トランシーバ回路が組み込まれており、クロック生成、データ トランシーバなどの機能をハードウェアで自動的に実行できるため、CPU の負担が軽減されます。
  • 構成可能な 8 ビット/16 ビット データ フレーム、ハイエンドが最初/ローエンドが最初
  • クロック周波数:fPCLK / (2、4、8、16、32、64、128、256)
  • マルチマスターモデル、マスターまたはスレーブ操作をサポート
  • 半二重/単信通信に削減可能
  • DMAをサポート
  • I2Sプロトコルに対応
  • STM32F103C8T6 ハードウェア SPI リソース: SPI1、SPI2

1. SPI ブロック図

        シフトレジスタの右側の低ビットデータは、MOSIから1つずつシフトし、味oのデータは左シフトレジスタのハイビットデータに少し移動します。

        これにより、ローポジションを先にするかハイポジションを先にするかを制御できます。

        一連のデータを連続して送信する必要がある場合、最初のデータは送信バッファ (TDR) に書き込まれますが、シフト レジスタにシフトするデータがない場合は、TDR データが直ちにシフト レジスタに転送され、シフトが開始されます。転送 入力の瞬間に、ステータス レジスタの TXE が 1 に設定されます (送信レジスタが空であることを示します)。その直後、次のデータを事前に TDR に書き込んで待機することができます。前のデータが送信されたら、 、すぐに次のデータを追うことができ、シームレスな間欠連続伝送を実現します。データがシフト レジスタに到達すると、データを移動するためのクロックが自動的に生成され、削除プロセス中に MISO データも移動されます。データの削除が完了すると、データの移動も完了します。このとき、転送データはシフトレジスタから受信バッファRDRに一括転送され、このときステータスレジスタRXNEは1(受信レジスタが空ではないことを示す)にセットされます。 RXNEが1にセットされたことを検出したら、できるだけ早くRDRからデータを読み出す必要があり、次のデータが来る前にRDRを読み出すことで連続受信を実現します。

2. SPIの基本構造

3. メインモード全二重連続伝送(高効率)

4. 不連続伝送(コードフレンドリー)

ステップ:

1. TXE が 1 に設定されるまで待ちます。

2. TDR レジスタにデータを書き込みます。

3. RXNE が 1 になるまで待ちます。

4. RDR が受信したデータを読み取ります。

3. W25Q64モジュール

  • W25Qxx シリーズは、データ ストレージ、フォント ストレージ、ファームウェア プログラム ストレージなどによく使用される、低コスト、コンパクト、使いやすい不揮発性メモリです。
  • 記憶媒体:Nor Flash(フラッシュメモリ)
  • クロック周波数: 80MHz / 160MHz (デュアル SPI) / 320MHz (クアッド SPI)
  • 記憶容量(24ビットアドレス):

    W25Q40: 4Mビット / 512KByte

    W25Q80: 8Mビット / 1MByte

    W25Q16: 16Mビット / 2MByte

    W25Q32: 32Mビット / 4MByte

    W25Q64: 64Mビット / 8MByte

    W25Q128: 128Mビット / 16MByte

    W25Q256: 256Mビット / 32Mバイト

1. W25Q64 ハードウェア回路図

 

 

        /CS (/ は、低レベルがアクティブであるか、CS の上にある水平線があり、これも低レベルであることを意味します。

        ここでのCSはSS、DIはMOSI、DOはMISOに相当します。

2. フラッシュ撮影時の注意事項

  • 書き込み操作中:
  • 書き込み動作前に書き込み許可を実行する必要があります
  • 各データビットは 1 から 0 にのみ書き換えることができ、0 から 1 に書き換えることはできません。
  • データを書き込む前にデータを消去する必要があり、消去後はすべてのデータ ビットが 1 になります。
  • 消去は最小消去単位に従って実行する必要があります(最小消去単位は 4096 バイトのセクターで、最大の消去単位はすべて消去できます)
  • 複数バイトを連続して書き込む場合、最大 1 ページ(256 バイト)までのデータを書き込むことができ、ページ末尾以降のデータはページ先頭に戻されて上書きされます。
  • 書き込み動作が完了すると、チップはビジー状態になり、新しい読み取りおよび書き込み動作に応答しなくなります。
  • 読み取り操作中: 読み取りシーケンスを直接呼び出します。有効化なし、追加操作なし、ページ制限なし。読み取り操作の終了後に読み取り操作はビジー状態になりませんが、ビジー状態では読み取りできません。

4. コード部分

1. SPI ソフトウェア構成コード

#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 アドレスのパッケージ化

#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. ソフトウェア SPI が 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. ハードウェア SPI 読み取り 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. メインプログラム

#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)
    {
        
    }
}

おすすめ

転載: blog.csdn.net/qq_45475497/article/details/134683310