STM32的SPI通信(软件读写W25Q64)

在了解完I2C通信后,不免会接触到到SPI通信。而一开始,可能会觉得两者好似没什么区别。为什么要学SPI呢,I2C和SPI有什么区别呢。为此我详细展开说说。

1.什么是 SPI?

SPI,全称 Serial Peripheral Interface,中文翻译为串行外设接口,是一种 主从式、同步、全双工 的串行通信协议。

三个关键词:

主从式:一个主机控制多个从设备(主发起,从响应)

同步式:数据传输由时钟信号(SCK)控制同步

全双工:可以边发送边接收,MOSI/MISO 两条独立数据线

2.SPI 的 4 根基本信号线结构

名称 主机作用方向 描述
SCK 主机 → 从机 同步时钟线,主机输出时钟,控制数据采样节奏
MOSI 主机 → 从机 主机输出、从机输入,主机发数据给从机
MISO 从机 → 主机 从机输出、主机输入,从机回传数据给主机
CS 主机 → 从机 片选信号(Chip Select),低电平有效,用来告诉从机"你被选中通信了"

注意:

SCK、MOSI、CS 都是主机控制输出;

MISO 是主机读取输入;

SPI 是一种基于边沿的协议 ------ 数据变化和数据采样都是"在时钟跳变沿发生"。

3.SPI 是怎么工作的?(核心机制)

接下来我来对比 SPI 四种模式(Mode 0~3)的行为差异,包括:

CPOL(时钟空闲电平)

CPHA(数据采样边沿)

MOSI 数据什么时候改变

从机什么时候采样数据

这四种模式是 SPI 协议中最核心也最容易混淆的部分。我现在彻底讲明白它们的逻辑区别、时序差异、电平行为和通信节奏。

CPOL 与 CPHA 是什么含义?

CPOL:决定 SCK 在"空闲状态"的电平(即不传数据时 SCK 是高还是低)

CPOL = 0 → 空闲时 SCK = 低电平

CPOL = 1 → 空闲时 SCK = 高电平

CPHA:决定 在哪一个边沿采样数据

CPHA = 0 → 第一个边沿采样

CPHA = 1 → 第二个边沿采样

注意:这里是边沿,不是具体到上升沿还是下降沿,第一个边沿可能是上升沿也可能是下降沿。

第一个边沿 = 从空闲态开始的第一个跳变(比如 CPOL=0,SCK 从 0→1 是第一个边沿)

模式 CPOL CPHA SCK 空闲状态 采样边沿 传输边沿(MOSI 改变时刻)
Mode 0 0 0 低电平 上升沿 下降沿
Mode 1 0 1 低电平 下降沿 上升沿
Mode 2 1 0 高电平 下降沿 上升沿
Mode 3 1 1 高电平 上升沿 下降沿

我以发送一个字节数据(0xA5 = 10100101)为例,分析 SPI 每一位是如何传输的。

Step by Step 时序过程( SPI Mode 0 )

通信准备阶段:

主机将 CS 拉低,激活目标从机(W25Q64)

数据准备完毕,等待时钟同步

第一位传输(发送 bit 7:1)

主机将 MOSI 设置为 1

主机将 SCK 从低拉高(上升沿)

从机在上升沿采样 MOSI 上的值(记录 bit7=1)

主机将 SCK 拉低,完成本位传输

第二位传输(bit6 = 0)

主机将 MOSI=0

SCK 上升沿

从机采样 0

SCK 下降沿结束

重复 8 次后,完整发送 1 字节

发送完后,主机将 CS 拉高,表示结束通信

若是读取数据,主机会在每一位 SCK 上升沿读 MISO 上的电平

SPI Mode 1(CPOL = 0,CPHA = 1)

空闲时 SCK 为低,采样在下降沿,数据改变在上升沿

通信准备阶段:

主机将 CS 拉低,激活目标从机(W25Q64)

SCK 处于空闲低电平(CPOL = 0)

数据准备完毕,等待时钟同步

第一位传输(发送 bit7 = 1)

主机将 SCK 拉高(上升沿)

主机将 MOSI 设置为 1

主机将 SCK 拉低(下降沿)

从机在 下降沿采样 MOSI 上的值(记录 bit7 = 1)

第二位传输(bit6 = 0)

主机将 SCK 拉高

主机将 MOSI 设置为 0

主机将 SCK 拉低

从机在下降沿采样 0

重复 8 次后,完整发送 1 字节

主机将 CS 拉高,表示通信结束

若是读取数据:

主机会在 每一位的 SCK 下降沿 读取 MISO 上的电平

SPI Mode 2(CPOL = 1,CPHA = 0)

空闲时 SCK 为高,采样在下降沿,数据改变在上升沿

通信准备阶段:

主机将 CS 拉低,激活目标从机

SCK 初始为高电平(CPOL = 1)

数据准备完毕,等待同步

第一位传输(bit7 = 1)

主机将 MOSI 设置为 1

主机将 SCK 从高拉低(下降沿)

从机在 下降沿采样 MOSI = 1

主机将 SCK 拉高(恢复空闲)

第二位传输(bit6 = 0)

主机将 MOSI = 0

主机将 SCK 拉低

从机采样 0

主机将 SCK 拉高

重复 8 次后,完整发送 1 字节

主机将 CS 拉高,结束通信

若是读取数据:

主机会在 每一位 SCK 下降沿 读取 MISO 电平

SPI Mode 3(CPOL = 1,CPHA = 1)

空闲时 SCK 为高,采样在上升沿**,数据改变在下降沿

通信准备阶段:

主机将 CS 拉低,激活从机

SCK 初始为高电平(CPOL = 1)

数据准备完毕,等待同步

第一位传输(bit7 = 1)

主机将 SCK 拉低

主机将 MOSI 设置为 1

主机将 SCK 从低拉高(上升沿)

从机在 上升沿采样 MOSI = 1

第二位传输(bit6 = 0)

主机将 SCK 拉低

主机将 MOSI 设置为 0

主机将 SCK 拉高

从机采样 0

重复 8 次后,完整发送 1 字节

主机将 CS 拉高,结束通信

若是读取数据:

主机会在 每一位 SCK 上升沿 读取 MISO 电平

W25Q64 是什么?

W25Q64 是一款 64M-bit(8MB)SPI NOR Flash 存储芯片,常用于存储配置文件、图片、数据日志等。它支持:

SPI Mode 0/3 接口

支持标准读、页写、扇区擦除等

支持写保护、掉电保护

多种容量(如 Q32、Q64、Q128)

W25Q64 内部结构概览

参数 数值
总容量 64Mbit = 8MB
页大小(Page) 256 字节
扇区大小(Sector) 4KB(16 页)
块大小(Block) 64KB
地址范围 0x000000 ~ 0x7FFFFF

写数据前必须先擦除所在页或扇区!否则写入无效。写数据前必须先擦除所在页或扇区!否则写入无效。

W25Q64 使用的 SPI 协议模式

默认使用 SPI Mode 0(CPOL = 0,CPHA = 0)

通信速率支持高达 80MHz(我们软件模拟约几十 KHz)

通信格式:主机发送 命令 + 地址 + 数据,从机响应结果或接收写入

4.举一个SPI读写W25Q64(软件)例子

从 STM32F103 使用 GPIO 模拟 SPI 信号,实现:

初始化 GPIO

与 W25Q64 通信(读ID、擦除扇区、写数据、读数据)

OLED 显示验证数据读写是否成功

通信的整体逻辑步骤(功能流程)

初始化 SPI 引脚(MySPI_Init)

启动 SPI 通信(CS 拉低,MySPI_Start)

发送命令(如读 ID,擦除、写数据)

可选:写入地址或数据内容

读取或写入数据

结束通信(CS 拉高,MySPI_Stop)

在 OLED 上显示通信结果

第一阶段:软件 SPI 的本质与引脚配置

SPI 是什么?

SPI 是一种同步串行通信协议,使用 4 根信号线:

CS:片选(低电平有效)

SCK:时钟,由主机控制

MOSI:主出从入,主机发给从机

MISO:主入从出,从机回应主机

在 Mode 0 模式下(CPOL=0,CPHA=0):

SCK 空闲为低电平

在 SCK 上升沿采样输入数据(MISO)

MOSI 数据必须在 SCK 上升沿前准备好

软件模拟 SPI 的基本操作函数
模拟 SPI 片选信号 CS(/SS)
cpp 复制代码
void MySPI_W_SS(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}

使用 PA4 模拟 SPI 的 CS(Chip Select)信号线

BitValue = 0 表示选中从机(W25Q64)

BitValue = 1 表示释放从机

W25Q64 是在 CS 低电平有效 时才响应 SPI 命令。这个函数用于 SPI 通信的开始与结束。

模拟时钟线 SCK
cpp 复制代码
void MySPI_W_SCK(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue);
}

使用 PA5 模拟 SPI 的时钟线 SCK(Serial Clock)

SPI 模式 0 规定:空闲时为低电平,在 上升沿采样

模拟 MOSI 输出
cs 复制代码
uint8_t MySPI_R_MISO(void)
{
	return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);
}

使用 PA7 作为 MOSI(主出从入)

STM32 控制这根线向 W25Q64 发送数据位(高位先发)

引脚初始化:MySPI_Init引脚初始化:

模拟读取 MISO(主入从出)
cpp 复制代码
void MySPI_W_MOSI(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue);
}

使用 PA6 作为 MISO,从 W25Q64 读取回来的每一位数据就在这里被读取。

读取的是从机在时钟上升沿送出的位电平。

cpp 复制代码
// 1. 打开 GPIOA 的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

// 2. 配置 MOSI(PA7)、SCK(PA5)、CS(PA4)为推挽输出
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);

// 3. 配置 MISO(PA6)为上拉输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_Init(GPIOA, &GPIO_InitStructure);

// 4. 初始状态:SS = 1 表示未选中,SCK = 0 表示时钟空闲
MySPI_W_SS(1);
MySPI_W_SCK(0);

推挽能拉高/拉低,速度快,适合 SPI

SPI 通信控制函数
cpp 复制代码
void MySPI_Start(void)
{
	MySPI_W_SS(0);   // 选中从机(CS 拉低)
}
void MySPI_Stop(void)
{
	MySPI_W_SS(1);   // 释放从机(CS 拉高)
}

第二阶段:模拟传输一个字节的过程(MySPI_SwapByte)

实现原理

SPI 每传输 1 个字节(8 位):

每一位数据在 MOSI 上输出(主机发)

在 SCK 上升沿让从机采样(主出)

同时主机也在上升沿读取 MISO(主入)

cpp 复制代码
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
    uint8_t i, ByteReceive = 0x00;

    for (i = 0; i < 8; i++)
    {
        // 第 i 位:先将 MOSI 输出(最高位先)
        MySPI_W_MOSI(ByteSend & (0x80 >> i));  // 将第 i 位送到 MOSI
        
        MySPI_W_SCK(1); // SCK 拉高(上升沿)
        
        // 在上升沿采样 MISO 线,主机读取从机的回应位
        if (MySPI_R_MISO() == 1) ByteReceive |= (0x80 >> i);
        
        MySPI_W_SCK(0); // SCK 拉低,准备下一位
    }

    return ByteReceive;
}

初始化变量:i 为循环索引;ByteReceive 用来存储收到的数据。

例如 ByteSend = 0b10100001

第一次发送 bit7:ByteSend & 0x80 = 0x80

输出对应位电平到 MOSI

SPI Mode 0:从机在 SCK 上升沿采样数据

主机此时也应从 MISO 线上采样数据

若当前位为高电平,则置 ByteReceive 的对应位为 1

第三阶段:JEDEC ID 的读取过程(读取芯片型号)

第三阶段:JEDEC ID 的读取过程(读取芯片型号)

cpp 复制代码
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
    MySPI_Start();                  // CS = 0,激活 Flash
    MySPI_SwapByte(0x9F);           // 发送 JEDEC ID 指令
    *MID = MySPI_SwapByte(0x00);    // 接收厂商 ID(通常为 0xEF)
    *DID = MySPI_SwapByte(0x00);    // 高位 Device ID(如 0x40)
    *DID <<= 8;
    *DID |= MySPI_SwapByte(0x00);   // 低位 Device ID(如 0x17)
    MySPI_Stop();                   // CS = 1,结束通信

}

每个字节都通过 MySPI_SwapByte() 传输,从机 MISO 返回对应 ID 信息。

第四阶段:擦除操作(扇区擦除)

写使能
cpp 复制代码
void W25Q64_WriteEnable(void)
{
	MySPI_Start();
	MySPI_SwapByte(W25Q64_WRITE_ENABLE); // 0x06
	MySPI_Stop();
}

所有写操作前必须执行该命令。否则写入失败。

等待芯片空闲
cpp 复制代码
void W25Q64_WaitBusy(void)
{
	uint32_t Timeout;
	MySPI_Start();
	MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1); // 0x05,读取状态寄存器
	Timeout = 100000;
	while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01) // bit0=WIP=1表示忙
	{
		Timeout --;
		if (Timeout == 0) break;
	}
	MySPI_Stop();
}

为什么要擦除?

W25Q64 的 NOR Flash 写入特性:

不能直接改写为 0 → 1

必须先将 1 全部清零(擦除为 0xFF),才能写入

cpp 复制代码
void W25Q64_SectorErase(uint32_t Address)
{
    W25Q64_WriteEnable();          // 必须先写使能(WEL=1)

    MySPI_Start();                 // CS = 0
    MySPI_SwapByte(0x20);          // 发送擦除指令:Sector Erase
    MySPI_SwapByte(addr >> 16);    // 地址高8位
    MySPI_SwapByte(addr >> 8);     // 地址中8位
    MySPI_SwapByte(addr);          // 地址低8位
    MySPI_Stop();                  // CS = 1

    W25Q64_WaitBusy();             // 等待擦除完成(最多100ms)

}

地址不需要对齐 0x000;芯片内部按地址所在扇区处理(每 4KB 为一个)

第五阶段:写入数据(页写)

写前必须:

写使能(0x06)

地址不要跨页(每页 256 字节)

cpp 复制代码
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{
    W25Q64_WriteEnable();               // 打开写使能(WEL=1)

    MySPI_Start();                      // 开始通信
    MySPI_SwapByte(0x02);               // Page Program 指令
    MySPI_SwapByte(addr >> 16);         // 地址高8位
    MySPI_SwapByte(addr >> 8);          // 地址中8位
    MySPI_SwapByte(addr);               // 地址低8位

    for (i = 0; i < Count; i++)
    {
        MySPI_SwapByte(DataArray[i]);   // 发送要写入的数据(MOSI)
    }

    MySPI_Stop();                       // 结束通信

    W25Q64_WaitBusy();                  // 等待写入完成

}

第六阶段:读取数据(Read Data)

cpp 复制代码
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
    MySPI_Start();
    MySPI_SwapByte(0x03);                // Read Data 指令
    MySPI_SwapByte(addr >> 16);          // 地址高8位
    MySPI_SwapByte(addr >> 8);           // 中8位
    MySPI_SwapByte(addr);                // 低8位

    for (i = 0; i < Count; i++)
    {
        DataArray[i] = MySPI_SwapByte(0x00); // 每读取一个字节,发一个 dummy byte
    }

    MySPI_Stop();

}

主机在 每个 SCK 上升沿读取 MISO,从机按地址返回数据。

5.指令函数

cpp 复制代码
#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
类别 宏定义 指令码 用途说明
写控制 W25Q64_WRITE_ENABLE 0x06 使能写操作(写/擦前必须执行)
W25Q64_WRITE_DISABLE 0x04 禁止写操作,防止误写
状态寄存器 W25Q64_READ_STATUS_REGISTER_1 0x05 读取状态寄存器1(BUSY/写使能位)
W25Q64_READ_STATUS_REGISTER_2 0x35 读取状态寄存器2(包含QE位)
W25Q64_WRITE_STATUS_REGISTER 0x01 同时写SR1 和 SR2(共2字节)
写入操作 W25Q64_PAGE_PROGRAM 0x02 页编程(最多256字节)
W25Q64_QUAD_PAGE_PROGRAM 0x32 四线页编程(用于Quad SPI)
擦除操作 W25Q64_SECTOR_ERASE_4KB 0x20 擦除一个 4KB 扇区
W25Q64_BLOCK_ERASE_32KB 0x52 擦除一个 32KB 块
W25Q64_BLOCK_ERASE_64KB 0xD8 擦除一个 64KB 块
W25Q64_CHIP_ERASE 0xC7 整片擦除(耗时较久)
W25Q64_ERASE_SUSPEND 0x75 暂停擦除操作(用于多任务)
W25Q64_ERASE_RESUME 0x7A 恢复擦除操作
电源管理 W25Q64_POWER_DOWN 0xB9 进入低功耗待机模式
W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID 0xAB 唤醒 + 读取设备ID
模式控制 W25Q64_HIGH_PERFORMANCE_MODE 0xA3 启用高性能模式(某些版本支持)
W25Q64_CONTINUOUS_READ_MODE_RESET 0xFF 重置连续读模式
识别ID W25Q64_MANUFACTURER_DEVICE_ID 0x90 读取制造商 + 设备ID(需发送地址)
W25Q64_READ_UNIQUE_ID 0x4B 读取唯一芯片ID(64-bit)
W25Q64_JEDEC_ID 0x9F JEDEC 标准ID(1字节厂商 + 2字节设备)
读取数据 W25Q64_READ_DATA 0x03 标准读取(低速,最兼容)
W25Q64_FAST_READ 0x0B 快速读取(需发送 Dummy Byte)
W25Q64_FAST_READ_DUAL_OUTPUT 0x3B 双线输出快速读
W25Q64_FAST_READ_DUAL_IO 0xBB 双线输入输出快速读
W25Q64_FAST_READ_QUAD_OUTPUT 0x6B 四线输出快速读
W25Q64_FAST_READ_QUAD_IO 0xEB 四线输入输出快速读
W25Q64_OCTAL_WORD_READ_QUAD_IO 0xE3 八线 32-bit 快速读取(部分芯片支持)
占位字节 W25Q64_DUMMY_BYTE 0xFF 常用于读取时"填充"MOSI

6.main函数

cpp 复制代码
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "W25Q64.h"

uint8_t MID;
uint16_t DID;

uint8_t ArrayWrite[] = {0x01, 0x02, 0x03, 0x04};
uint8_t ArrayRead[4];

int main(void)
{
	OLED_Init();
	W25Q64_Init();
	
	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, 12, DID, 4);
	
	W25Q64_SectorErase(0x000000);
	W25Q64_PageProgram(0x000000, ArrayWrite, 4);
	
	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)
	{
		
	}
}

擦除第一个扇区(地址 0x000000 起始的 4KB)

NOR Flash 写入前必须擦除,否则无法从 0 改写为 1!(擦除后,所有数据位变为1)

7.SPI通信与I2C区别

对比项 SPI(Serial Peripheral Interface) I²C(Inter-Integrated Circuit)
通信方式 全双工,主从同步通信 半双工,主从同步通信
信号线数量 通常需要 4 根线:MISO, MOSI, SCLK, CS(每个从机单独 CS) 只需 2 根线:SCL(时钟), SDA(数据)
通信速率 较高,可达几十 Mbps 较低,一般为 100kHz、400kHz,最多几 Mbps
引脚数量 多(每个从机需独立 CS 引脚) 少(多个从机共用总线)
总线架构 一主多从,但每个从机需要独立片选 一主多从,多个从机可共享 2 根线
地址机制 无地址,主机用 CS 选中具体从机 有 7 位或 10 位设备地址
传输控制 主机控制时钟(SCLK)与片选 主机控制时钟(SCL),通过地址访问从机
数据方向 MOSI、MISO 分别用于写入和读取(全双工 SDA 单线收发数据(半双工
硬件成本 较高(线多,占用 GPIO 多) 较低(线少,占用 GPIO 少)
软件协议复杂度 简单,无握手或仲裁 较复杂,有起始/停止位、ACK/NACK、仲裁机制等
常见应用场景 Flash、SD 卡、屏幕、W25Q64 等高速外设 EEPROM、RTC、传感器、OLED、MPU6050 等低速外设

具体外设举例

外设 使用通信协议 原因简述
W25Q64 SPI 高速读写需求,SPI 支持页写、块擦除等
MPU6050 I²C 内置 I²C 接口,低速传感器,节省引脚
OLED (SSD1306) I²C 或 SPI 小数据量,I²C 节省引脚,SPI 提高速度
SD 卡 SPI 大容量文件操作,SPI 高速全双工性能优越
相关推荐
科大饭桶8 分钟前
Linux系统编程Day9 -- gdb (linux)和lldb(macOS)调试工具
linux·服务器·c语言·c++
2301_785038181 小时前
c++初学day1(类比C语言进行举例,具体原理等到学到更深层的东西再进行解析)
c语言·c++·算法
普中科技2 小时前
【普中STM32精灵开发攻略】--第 11 章 SysTick系统定时器
stm32·单片机·嵌入式硬件·物联网·arm·普中科技
李夕2 小时前
掌握工程化固件烧录,开启你的技术进阶之路-FPGA ISE(xilinx)
嵌入式硬件·fpga·固件
Hello_Embed3 小时前
STM32HAL 快速入门(二):用 CubeMX 配置点灯程序 —— 从工程生成到 LED 闪烁
笔记·stm32·单片机·学习·嵌入式软件
HW-BASE9 小时前
《C语言》指针练习题--1
c语言·开发语言·单片机·算法·c
泽虞10 小时前
数据结构与算法
c语言·数据结构·算法
遇见尚硅谷10 小时前
C语言:20250805学习(文件预处理)
服务器·c语言·学习
DIY机器人工房11 小时前
一个程序通过 HTTP 协议调用天气 API,解析 JSON 格式的天气数据,提取关键信息并格式化输出:日期、天气状况、温度范围、风向、湿度等核心气象数据。
嵌入式硬件·嵌入式·diy机器人工房