解码SPI接口(Flash读写+RFID卡号获取)

硬件SPI实现W25Q128 Flash读写

image

原理

  • SPI协议:同步、全双工、高速通信总线,需4根信号线(MOSI主发从收、MISO主收从发、SCK时钟、CS片选);

图片2

  • 支持4种工作模式,本工程使用模式0(SCK空闲低电平,第一个边沿采样数据)。

图片3

模式 CPOL CPHA 时钟空闲状态 数据采样沿
0 0 0 低电平 奇数边沿(一个脉冲的第一个)
1 0 1 低电平 偶数边沿(一个脉冲的第二个)
2 1 0 高电平 奇数边沿(一个脉冲的第一个)
3 1 1 高电平 偶数边沿(一个脉冲的第二个)
  • W25Q128芯片:128Mbit(16MB)NOR Flash,采用SPI接口;

图片4

  • 内存按“Page(256Byte)→Sector(16Page=4KB)→Block(16Sector=64KB)”划分;

图片5

  • 擦除后存储单元默认值为0xFF,支持扇区/块/整片擦除,编程前需先擦除。
  • 硬件SPI优势:由MCU硬件模块实现,时序精准、占用CPU资源少,传输速率高于IO模拟。

硬件配置(引脚定义)

图片6

SPI信号线 引脚(查阅原理图) 模式 功能描述
SCK GPIOB_Pin3 复用推挽 时钟信号(主设备产生)
MOSI GPIOB_Pin5 复用推挽 主设备输出→从设备输入
MISO GPIOB_Pin4 复用推挽 主设备输入→从设备输出
CS GPIOB_Pin14 通用推挽 片选信号(低电平选中Flash)
UART1_TX GPIOA_Pin9 复用推挽 串口输出(打印调试信息)
UART1_RX GPIOA_Pin10 复用推挽 串口输入(接收PC指令)

指令

图片7

  • 读取厂商/设备ID

图片8

  • 写使能

图片9

  • 读状态寄存器

图片10

  • 写失能

图片11

  • 擦除扇区

图片12

  • 读数据

图片13

  • 页编程

图片14

代码

/* Includes ------------------------------------------------------------------*/
#include "stm32f4xx.h"
#include <string.h>
#include <stdio.h>
#include <stdbool.h>

// 重定向fputc函数,支持printf通过USART1打印
int fputc(int ch, FILE *f)
{
    while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); // 等待发送缓冲区空
    USART_SendData(USART1, ch);  // 发送字节
    return (ch);
}

/* Private define ------------------------------------------------------------*/
// SPI外设及时钟配置
#define sFLASH_SPI                           SPI1
#define sFLASH_SPI_CLK                       RCC_APB2Periph_SPI1
#define sFLASH_SPI_CLK_INIT                  RCC_APB2PeriphClockCmd

// SPI引脚定义(PB3/4/5复用为SPI1)
#define sFLASH_SPI_SCK_PIN                   GPIO_Pin_3
#define sFLASH_SPI_SCK_GPIO_PORT             GPIOB
#define sFLASH_SPI_SCK_GPIO_CLK              RCC_AHB1Periph_GPIOB
#define sFLASH_SPI_SCK_SOURCE                GPIO_PinSource3
#define sFLASH_SPI_SCK_AF                    GPIO_AF_SPI1

#define sFLASH_SPI_MISO_PIN                  GPIO_Pin_4
#define sFLASH_SPI_MISO_GPIO_PORT            GPIOB
#define sFLASH_SPI_MISO_GPIO_CLK             RCC_AHB1Periph_GPIOB
#define sFLASH_SPI_MISO_SOURCE               GPIO_PinSource4
#define sFLASH_SPI_MISO_AF                   GPIO_AF_SPI1

#define sFLASH_SPI_MOSI_PIN                  GPIO_Pin_5
#define sFLASH_SPI_MOSI_GPIO_PORT            GPIOB
#define sFLASH_SPI_MOSI_GPIO_CLK             RCC_AHB1Periph_GPIOB
#define sFLASH_SPI_MOSI_SOURCE               GPIO_PinSource5
#define sFLASH_SPI_MOSI_AF                   GPIO_AF_SPI1

// Flash片选引脚(PB14)
#define sFLASH_CS_PIN                        GPIO_Pin_14
#define sFLASH_CS_GPIO_PORT                  GPIOB
#define sFLASH_CS_GPIO_CLK                   RCC_AHB1Periph_GPIOB

// 片选控制宏
#define sFLASH_CS_LOW()       GPIO_ResetBits(sFLASH_CS_GPIO_PORT, sFLASH_CS_PIN)  // 选中Flash
#define sFLASH_CS_HIGH()      GPIO_SetBits(sFLASH_CS_GPIO_PORT, sFLASH_CS_PIN)    // 取消选中

/**
  * @brief  底层硬件初始化(GPIO+SPI时钟使能、引脚复用配置)
  * @param  None
  * @retval None
  * @note   1. 使能SPI1和对应GPIO时钟
  *         2. 配置SPI引脚为复用功能,推挽输出,下拉电阻
  *         3. 片选引脚配置为通用推挽输出,无上下拉
  */
void sFLASH_LowLevel_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;

  sFLASH_SPI_CLK_INIT(sFLASH_SPI_CLK, ENABLE);  // 使能SPI1时钟

  // 使能所有相关GPIO时钟
  RCC_AHB1PeriphClockCmd(sFLASH_SPI_SCK_GPIO_CLK | sFLASH_SPI_MISO_GPIO_CLK |
                         sFLASH_SPI_MOSI_GPIO_CLK | sFLASH_CS_GPIO_CLK, ENABLE);

  // 配置SPI引脚复用功能
  GPIO_PinAFConfig(sFLASH_SPI_SCK_GPIO_PORT, sFLASH_SPI_SCK_SOURCE, sFLASH_SPI_SCK_AF);
  GPIO_PinAFConfig(sFLASH_SPI_MISO_GPIO_PORT, sFLASH_SPI_MISO_SOURCE, sFLASH_SPI_MISO_AF);
  GPIO_PinAFConfig(sFLASH_SPI_MOSI_GPIO_PORT, sFLASH_SPI_MOSI_SOURCE, sFLASH_SPI_MOSI_AF);

  // SPI引脚通用配置(复用模式、50MHz速率、推挽、下拉)
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_DOWN;

  // 配置SCK引脚
  GPIO_InitStructure.GPIO_Pin = sFLASH_SPI_SCK_PIN;
  GPIO_Init(sFLASH_SPI_SCK_GPIO_PORT, &GPIO_InitStructure);

  // 配置MOSI引脚
  GPIO_InitStructure.GPIO_Pin = sFLASH_SPI_MOSI_PIN;
  GPIO_Init(sFLASH_SPI_MOSI_GPIO_PORT, &GPIO_InitStructure);

  // 配置MISO引脚
  GPIO_InitStructure.GPIO_Pin = sFLASH_SPI_MISO_PIN;
  GPIO_Init(sFLASH_SPI_MISO_GPIO_PORT, &GPIO_InitStructure);

  // 配置片选引脚(通用输出模式)
  GPIO_InitStructure.GPIO_Pin = sFLASH_CS_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
  GPIO_Init(sFLASH_CS_GPIO_PORT, &GPIO_InitStructure);
}

/**
  * @brief  SPI和Flash初始化(配置SPI参数+初始化状态)
  * @param  None
  * @retval None
  * @note   1. SPI配置:主模式、全双工、8位数据、模式0、软件片选、4分频、MSB先行
  *         2. 初始状态下取消片选(CS=高),避免误操作
  */
void sFLASH_Init(void)
{
  SPI_InitTypeDef  SPI_InitStructure;

  sFLASH_LowLevel_Init();  // 底层硬件初始化

  sFLASH_CS_HIGH();  // 初始取消选中Flash

  // SPI参数配置
  SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  // 全双工模式
  SPI_InitStructure.SPI_Mode = SPI_Mode_Master;                       // 主设备模式
  SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;                   // 8位数据宽度
  SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;                          // 模式0:SCK空闲低电平
  SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;                        // 模式0:第一个边沿(上升沿)采样
  SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;                           // 软件控制片选
  SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;  // 波特率分频(APB2时钟/4)
  SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;                  // 高位先行(SPI标准)
  SPI_InitStructure.SPI_CRCPolynomial = 7;                            // CRC多项式(未使用,填默认值)
  SPI_Init(sFLASH_SPI, &SPI_InitStructure);

  SPI_Cmd(sFLASH_SPI, ENABLE);  // 使能SPI1外设
}

/**
  * @brief  SPI发送一个字节,并接收从设备返回的字节(全双工特性)
  * @param  byte:要发送的字节
  * @retval 从设备返回的字节
  * @note   1. 等待发送缓冲区空→发送字节→等待接收缓冲区非空→读取接收字节
  *         2. 硬件SPI自动同步时钟和数据,无需手动控制SCK
  */
uint8_t sFLASH_SendByte(uint8_t byte)
{
  while (SPI_I2S_GetFlagStatus(sFLASH_SPI, SPI_I2S_FLAG_TXE) == RESET);  // 等待发送缓冲区空
  SPI_I2S_SendData(sFLASH_SPI, byte);  // 发送字节
  while (SPI_I2S_GetFlagStatus(sFLASH_SPI, SPI_I2S_FLAG_RXNE) == RESET); // 等待接收完成
  return SPI_I2S_ReceiveData(sFLASH_SPI);  // 返回接收字节
}

/**
  * @brief  读取W25Q128的厂商ID和设备ID
  * @param  None
  * @retval 16位ID(高8位:厂商ID,低8位:设备ID)
  * @note   1. 指令:0x90(Read Manufacturer/Device ID)
  *         2. 地址:需发送24位地址0x000000
  *         3. 正常返回:厂商ID=0xEF(华邦),设备ID=0x17(W25Q128),组合为0xEF17
  */
uint16_t sFLASH_ReadID(void)
{
  uint16_t id = 0;
  uint8_t  Temp0 = 0, Temp1 = 0;

  sFLASH_CS_LOW();  // 1. 选中Flash

  sFLASH_SendByte(0x90);  // 2. 发送读ID指令

  sFLASH_SendByte(0x00);  // 3. 发送24位地址(A23-A0=0x000000)
  sFLASH_SendByte(0x00);
  sFLASH_SendByte(0x00);

  Temp0 = sFLASH_SendByte(0xFF);  // 4. 读取厂商ID(0xEF)
  Temp1 = sFLASH_SendByte(0xFF);  // 5. 读取设备ID(0x17)

  sFLASH_CS_HIGH();  // 6. 取消选中

  id = (Temp0 << 8) | Temp1;  // 组合ID(高8位厂商ID,低8位设备ID)
  return id;
}

/**
  * @brief  发送写使能指令(W25Q128编程/擦除前必须执行)
  * @param  None
  * @retval None
  * @note   1. 指令:0x06(Write Enable)
  *         2. 功能:置位状态寄存器的WEL位(写使能锁存),允许后续编程/擦除操作
  */
void sFLASH_WriteEnable(void)
{
  sFLASH_CS_LOW();  // 选中Flash
  sFLASH_SendByte(0x06);  // 发送写使能指令
  sFLASH_CS_HIGH();  // 取消选中
}

/**
  * @brief  发送写失能指令(禁止编程/擦除操作)
  * @param  None
  * @retval None
  * @note   1. 指令:0x04(Write Disable)
  *         2. 功能:清零状态寄存器的WEL位,禁止编程/擦除操作
  *         3. 上电后或编程/擦除完成后,WEL位会自动清零
  */
void sFLASH_WriteDisable(void)
{
  sFLASH_CS_LOW();  // 选中Flash
  sFLASH_SendByte(0x04);  // 发送写失能指令
  sFLASH_CS_HIGH();  // 取消选中
}

/**
  * @brief  等待Flash写/擦除操作完成(查询忙标志)
  * @param  None
  * @retval None
  * @note   1. 指令:0x05(Read Status Register-1)
  *         2. 状态寄存器bit0=1表示忙(写/擦除中),bit0=0表示空闲
  *         3. 写/擦除是自定时操作,需等待完成后再执行下一条指令
  */
void sFLASH_WaitForWriteEnd(void)
{
  uint8_t flashstatus = 0;

  sFLASH_CS_LOW();  // 选中Flash
  sFLASH_SendByte(0x05);  // 发送读状态寄存器指令

  do
  {
    flashstatus = sFLASH_SendByte(0x00);  // 发送空字节获取状态寄存器值
  } while ((flashstatus & 0x01) == SET);  // 等待bit0(忙标志)清零

  sFLASH_CS_HIGH();  // 取消选中
}

/**
  * @brief  擦除指定扇区(4KB)
  * @param  SectorAddr:扇区起始地址(需对齐4KB边界,如0x000000、0x001000等)
  * @retval None
  * @note   1. 指令:0x20(Sector Erase)
  *         2. 流程:写使能→发送指令→发送24位扇区地址→等待擦除完成→写失能
  *         3. 擦除后扇区内所有单元值为0xFF
  *         4. 扇区地址计算:SectorAddr = 扇区编号 × 4096(4KB)
  */
void sFLASH_EraseSector(uint32_t SectorAddr)
{
  sFLASH_WriteEnable();  // 1. 写使能

  sFLASH_CS_LOW();  // 2. 选中Flash

  sFLASH_SendByte(0x20);  // 3. 发送扇区擦除指令

  // 4. 发送24位扇区地址(高位→低位)
  sFLASH_SendByte((SectorAddr & 0xFF0000) >> 16);  // 地址高8位
  sFLASH_SendByte((SectorAddr & 0xFF00) >> 8);     // 地址中8位
  sFLASH_SendByte(SectorAddr & 0xFF);              // 地址低8位

  sFLASH_CS_HIGH();  // 5. 取消选中

  sFLASH_WaitForWriteEnd();  // 6. 等待擦除完成(约几十毫秒)

  sFLASH_WriteDisable();  // 7. 写失能
}

/**
  * @brief  页编程(向指定地址写入1~256字节数据)
  * @param  pBuffer:存储待写入数据的缓冲区指针
  * @param  WriteAddr:Flash内部起始地址(需在目标页范围内)
  * @param  NumByteToWrite:写入字节数(1~256,超过会覆盖当前页起始地址)
  * @retval None
  * @note   1. 指令:0x02(Page Program)
  *         2. 流程:写使能→发送指令→发送地址→写入数据→等待完成→写失能
  *         3. 页大小为256字节,地址低8位超过255会自动回卷(覆盖当前页数据)
  *         4. 只能将0xFF改为其他值,不能直接覆盖非0xFF数据(需先擦除)
  */
void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
  sFLASH_WriteEnable();  // 1. 写使能

  sFLASH_CS_LOW();  // 2. 选中Flash

  sFLASH_SendByte(0x02);  // 3. 发送页编程指令

  // 4. 发送24位写入地址(高位→低位)
  sFLASH_SendByte((WriteAddr & 0xFF0000) >> 16);
  sFLASH_SendByte((WriteAddr & 0xFF00) >> 8);
  sFLASH_SendByte(WriteAddr & 0xFF);

  // 5. 循环写入数据
  while (NumByteToWrite--)
  {
    sFLASH_SendByte(*pBuffer);  // 发送当前字节
    pBuffer++;  // 指向下一字节
  }

  sFLASH_CS_HIGH();  // 6. 取消选中

  sFLASH_WaitForWriteEnd();  // 7. 等待编程完成(约几微秒)

  sFLASH_WriteDisable();  // 8. 写失能
}

/**
  * @brief  从指定地址读取指定长度数据
  * @param  pBuffer:存储读取数据的缓冲区指针
  * @param  ReadAddr:Flash内部起始地址
  * @param  NumByteToRead:读取字节数
  * @retval None
  * @note   1. 指令:0x03(Read Data)
  *         2. 流程:选中Flash→发送指令→发送地址→循环读取数据→取消选中
  *         3. 读取无长度限制,可跨页/跨扇区读取
  *         4. 发送空字节(0x00)生成时钟,接收从设备返回的数据
  */
void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{
  sFLASH_CS_LOW();  // 1. 选中Flash

  sFLASH_SendByte(0x03);  // 2. 发送读数据指令

  // 3. 发送24位读取地址(高位→低位)
  sFLASH_SendByte((ReadAddr & 0xFF0000) >> 16);
  sFLASH_SendByte((ReadAddr & 0xFF00) >> 8);
  sFLASH_SendByte(ReadAddr & 0xFF);

  // 4. 循环读取数据
  while (NumByteToRead--)
  {
    *pBuffer = sFLASH_SendByte(0x00);  // 发送空字节获取数据
    pBuffer++;  // 指向缓冲区下一位置
  }

  sFLASH_CS_HIGH();  // 5. 取消选中
}

/**
  * @brief  微秒级延时
  * @param  nus:延时时间(单位:微秒)
  * @retval None
  * @note   1. 基于SysTick定时器实现,时钟源为21MHz(需根据实际系统时钟调整重载值)
  *         2. 重载值计算:nus × 时钟频率(MHz) - 1
  */
void delay_us(u32 nus)
{
    SysTick->CTRL = 0;              // 关闭SysTick定时器
    SysTick->LOAD = nus * 21 - 1;   // 设置重载值(21MHz时钟)
    SysTick->VAL  = 0;              // 清除当前计数值
    SysTick->CTRL = 1;              // 启动定时器(使用处理器时钟)
    while ((SysTick->CTRL & 0x00010000) == 0);  // 等待计数完成(COUNTFLAG位)
    SysTick->CTRL = 0;              // 关闭定时器
}

/**
  * @brief  毫秒级延时
  * @param  nms:延时时间(单位:毫秒)
  * @retval None
  * @note   1. 基于SysTick定时器实现,时钟源为21MHz,存在微小误差
  *         2. 内部循环调用微秒延时,重载值=21×1000-1(1毫秒)
  */
void delay_ms(u32 nms)
{
    while(nms--)
    {
        SysTick->CTRL = 0;              // 关闭SysTick定时器
        SysTick->LOAD = 21 * 1000 - 1;  // 设置重载值(1毫秒)
        SysTick->VAL  = 0;              // 清除当前计数值
        SysTick->CTRL = 1;              // 启动定时器
        while ((SysTick->CTRL & 0x00010000) == 0);  // 等待计数完成
        SysTick->CTRL = 0;              // 关闭定时器
    }
}

/**
  * @brief  USART1初始化(用于串口调试,打印Flash ID和读写数据)
  * @param  baud:串口波特率(本工程使用115200)
  * @retval None
  * @note   1. USART1引脚:TX=PA9,RX=PA10(复用)
  *         2. 配置:8位数据位、1位停止位、无校验、无流控、全双工
  *         3. 使能接收中断,支持串口数据回显
  */
static void PC_Config(u32 baud)
{
  USART_InitTypeDef USART_InitStructure;
  NVIC_InitTypeDef NVIC_InitStructure;
  GPIO_InitTypeDef GPIO_InitStructure;

  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);  // 使能GPIOA时钟
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); // 使能USART1时钟

  // 配置PA9/PA10为USART1复用功能
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1);

  // 串口引脚配置(复用推挽、100MHz、上拉)
  GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;
  GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9 | GPIO_Pin_10;
  GPIO_Init(GPIOA, &GPIO_InitStructure);

  // USART参数配置
  USART_InitStructure.USART_BaudRate    = baud;
  USART_InitStructure.USART_WordLength  = USART_WordLength_8b;
  USART_InitStructure.USART_StopBits    = USART_StopBits_1;
  USART_InitStructure.USART_Parity      = USART_Parity_No;
  USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
  USART_Init(USART1, &USART_InitStructure);

  // 配置USART1中断(抢占优先级0)
  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);

  USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);  // 使能接收中断(收到数据触发)

  while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);  // 等待发送缓冲区空
  USART_ClearITPendingBit(USART1, USART_IT_RXNE);  // 清除中断标志位
  USART_Cmd(USART1, ENABLE);  // 使能USART1
}

/**
  * @brief  主函数(Flash读写测试流程)
  * @param  None
  * @retval None
  * @note   流程:初始化→读Flash ID→擦除扇区→页编程→读取数据→串口打印结果
  */
int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);  // NVIC优先级分组4(抢占优先级0~15)

    PC_Config(115200);  // 初始化USART1(115200波特率)
    sFLASH_Init();      // 初始化Flash和SPI

    printf("ID = %#X\r\n", sFLASH_ReadID());  // 读取并打印Flash ID(预期0xEF17)

    sFLASH_EraseSector(0x000000);  // 擦除0号扇区(地址0x000000~0x000FFF)

    sFLASH_WritePage((uint8_t *)"helloworld", 0x000000, 10);  // 向0x000000写入10字节数据

    uint8_t buf[10] = {0};
    sFLASH_ReadBuffer(buf, 0x000005, 5);  // 从0x000005读取5字节(预期"world")
    printf("buf is [%s]\r\n", buf);

    while (1);  // 死循环
}

/**
  * @brief  USART1中断服务函数(接收数据回显)
  * @param  None
  * @retval None
  * @note   收到PC端数据后,立即回传给PC端,用于调试验证
  */
void USART1_IRQHandler(void)
{
    uint8_t data = 0;

    if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)  // 判断是否为接收中断
    {
        data = USART_ReceiveData(USART1);  // 读取接收数据
        USART_SendData(USART1, data);      // 回传数据
    }
}

image

注意

  • 地址对齐:扇区擦除地址需对齐4KB(0x1000),页编程地址低8位不能超过255(否则回卷)。
  • 读写顺序:编程前必须先擦除(擦除后为全1)。
  • 忙等待:写/擦除操作后必须调用sFLASH_WaitForWriteEnd(),否则后续指令会被忽略。
  • 时钟配置:SPI波特率不能超过W25Q128的最大时钟(133MHz),持续数据传输速率为35MHZ,本工程4分频足够安全。
  • 引脚复用:硬件SPI引脚必须配置为对应复用功能,否则通信失败。

IO模拟SPI原理

  • IO模拟SPI:不依赖MCU硬件SPI模块,通过软件控制4个通用IO口模拟SPI时序(SCK、MOSI输出,MISO输入,CS输出)。
  • 优势:引脚选择灵活(可使用任意空闲IO),无需占用硬件SPI资源,适配无硬件SPI的MCU或SPI资源已占用的场景。
  • 劣势:时序由软件控制,占用CPU资源,传输速率低于硬件SPI,需精准控制延时(本工程未额外加延时,时钟速率由代码执行速度决定)。

硬件配置(引脚定义)

SPI信号线 引脚 模式 功能描述
SCK GPIOB_Pin3 通用推挽 时钟信号(软件控制高低电平)
MOSI GPIOB_Pin5 通用推挽 主设备输出→从设备输入
MISO GPIOB_Pin4 通用输入 主设备输入→从设备输出
CS GPIOB_Pin14 通用推挽 片选信号(低电平选中)
UART1_TX GPIOA_Pin9 复用推挽 串口输出(调试)
UART1_RX GPIOA_Pin10 复用推挽 串口输入(调试)

代码(差异部分)

/* Private define ------------------------------------------------------------*/
#define MODE_0   1  // 1=SPI模式0,0=SPI模式3

// IO模拟SPI引脚定义
#define sFLASH_SPI_SCK_PIN                   GPIO_Pin_3
#define sFLASH_SPI_SCK_GPIO_PORT             GPIOB
#define sFLASH_SPI_SCK_GPIO_CLK              RCC_AHB1Periph_GPIOB

#define sFLASH_SPI_MISO_PIN                  GPIO_Pin_4
#define sFLASH_SPI_MISO_GPIO_PORT            GPIOB
#define sFLASH_SPI_MISO_GPIO_CLK             RCC_AHB1Periph_GPIOB

#define sFLASH_SPI_MOSI_PIN                  GPIO_Pin_5
#define sFLASH_SPI_MOSI_GPIO_PORT            GPIOB
#define sFLASH_SPI_MOSI_GPIO_CLK             RCC_AHB1Periph_GPIOB

#define sFLASH_CS_PIN                        GPIO_Pin_14
#define sFLASH_CS_GPIO_PORT                  GPIOB
#define sFLASH_CS_GPIO_CLK                   RCC_AHB1Periph_GPIOB

// 引脚电平控制宏
#define sFLASH_CS_LOW()       GPIO_ResetBits(sFLASH_CS_GPIO_PORT, sFLASH_CS_PIN)
#define sFLASH_CS_HIGH()      GPIO_SetBits(sFLASH_CS_GPIO_PORT, sFLASH_CS_PIN)
#define sFLASH_SCK_LOW()      GPIO_ResetBits(sFLASH_SPI_SCK_GPIO_PORT, sFLASH_SPI_SCK_PIN)
#define sFLASH_SCK_HIGH()     GPIO_SetBits(sFLASH_SPI_SCK_GPIO_PORT, sFLASH_SPI_SCK_PIN)
#define sFLASH_MOSI_LOW()     GPIO_ResetBits(sFLASH_SPI_MOSI_GPIO_PORT, sFLASH_SPI_MOSI_PIN)
#define sFLASH_MOSI_HIGH()    GPIO_SetBits(sFLASH_SPI_MOSI_GPIO_PORT, sFLASH_SPI_MOSI_PIN)
#define sFLASH_MISO_READ()    GPIO_ReadInputDataBit(sFLASH_SPI_MISO_GPIO_PORT, sFLASH_SPI_MISO_PIN)

/* Private function prototypes -----------------------------------------------*/
// 同硬件SPI版本,仅新增/修改sFLASH_LowLevel_Init()和sFLASH_SendByte()

/**
  * @brief  底层IO初始化(配置SPI引脚为输入/输出模式)
  * @param  None
  * @retval None
  * @note   1. SCK、MOSI、CS配置为推挽输出,MISO配置为输入模式
  *         2. 根据MODE_0宏定义设置SCK初始电平(模式0=低,模式3=高)
  */
void sFLASH_LowLevel_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;

  // 使能所有相关GPIO时钟
  RCC_AHB1PeriphClockCmd(sFLASH_SPI_SCK_GPIO_CLK | sFLASH_SPI_MISO_GPIO_CLK |
                         sFLASH_SPI_MOSI_GPIO_CLK | sFLASH_CS_GPIO_CLK, ENABLE);

  // 配置SCK、MOSI为推挽输出模式
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_DOWN;

  // 配置SCK引脚
  GPIO_InitStructure.GPIO_Pin = sFLASH_SPI_SCK_PIN;
  GPIO_Init(sFLASH_SPI_SCK_GPIO_PORT, &GPIO_InitStructure);

  // 配置MOSI引脚
  GPIO_InitStructure.GPIO_Pin = sFLASH_SPI_MOSI_PIN;
  GPIO_Init(sFLASH_SPI_MOSI_GPIO_PORT, &GPIO_InitStructure);

  // 配置MISO引脚为输入模式
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
  GPIO_InitStructure.GPIO_Pin = sFLASH_SPI_MISO_PIN;
  GPIO_Init(sFLASH_SPI_MISO_GPIO_PORT, &GPIO_InitStructure);

  // 配置CS引脚为推挽输出模式
  GPIO_InitStructure.GPIO_Pin = sFLASH_CS_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
  GPIO_Init(sFLASH_CS_GPIO_PORT, &GPIO_InitStructure);

#if MODE_0
    sFLASH_SCK_LOW();  // 模式0:SCK空闲低电平
#else
    sFLASH_SCK_HIGH(); // 模式3:SCK空闲高电平
#endif
}

/**
  * @brief  SPI初始化(仅初始化IO,无SPI外设配置)
  * @param  None
  * @retval None
  * @note   初始状态取消片选(CS=高),避免误操作
  */
void sFLASH_Init(void)
{
  sFLASH_LowLevel_Init();  // 初始化IO引脚
  sFLASH_CS_HIGH();        // 取消选中Flash
}

/**
  * @brief  模拟SPI模式0发送一个字节,并接收从设备返回字节
  * @param  byte:要发送的字节
  * @retval 从设备返回的字节
  * @note   模式0时序:
  *         1. SCK初始为低(空闲状态)
  *         2. 高位先行,依次发送8位数据(bit7→bit0)
  *         3. 拉高SCK(第一个边沿),从设备采样MOSI数据,主设备读取MISO数据
  *         4. 拉低SCK,完成一个bit传输,准备下一bit数据,循环8次
  */
#if MODE_0
uint8_t sFLASH_SendByte(uint8_t byte)
{
    uint8_t cnt = 0;
    uint8_t data = 0; // 存储接收数据

    sFLASH_SCK_LOW();  // 确保SCK为空闲低电平

    for(cnt = 0; cnt < 8; cnt++)  // 循环发送8位(高位先行)
    {
        // 准备当前bit数据(bit7先发送)
        if(byte & 0x80)  // 判断当前最高位是否为1
            sFLASH_MOSI_HIGH();
        else
            sFLASH_MOSI_LOW();

        byte <<= 1;  // 左移一位,准备下一位数据

        sFLASH_SCK_HIGH();  // 拉高SCK(上升沿),从设备采样

        data <<= 1;  // 接收数据左移一位,准备存储新bit
        data |= sFLASH_MISO_READ();  // 读取MISO引脚电平(0或1)

        sFLASH_SCK_LOW();  // 拉低SCK,完成一个bit传输
    }

    return data;
}

/**
  * @brief  模拟SPI模式3发送一个字节,并接收从设备返回字节
  * @param  byte:要发送的字节
  * @retval 从设备返回的字节
  * @note   模式3时序:
  *         1. SCK初始为高(空闲状态)
  *         2. 高位先行,依次发送8位数据(bit7→bit0)
  *         3. 拉低SCK(第一个边沿),准备数据
  *         4. 拉高SCK,完成一个bit传输,循环8次
  */
#else
uint8_t sFLASH_SendByte(uint8_t byte)
{
    uint8_t cnt = 0;
    uint8_t data = 0; // 存储接收数据

    sFLASH_SCK_HIGH();  // 确保SCK为空闲高电平

    for(cnt = 0; cnt < 8; cnt++)  // 循环发送8位(高位先行)
    {
        sFLASH_SCK_LOW();  // 拉低SCK,准备数据

        // 准备当前bit数据(bit7先发送)
        if(byte & 0x80)  // 判断当前最高位是否为1
            sFLASH_MOSI_HIGH();
        else
            sFLASH_MOSI_LOW();

        byte <<= 1;  // 左移一位,准备下一位数据

        sFLASH_SCK_HIGH();  // 拉高SCK,完成一个bit传输

        data <<= 1;  // 接收数据左移一位,准备存储新bit
        data |= sFLASH_MISO_READ();  // 读取MISO引脚电平(0或1)
    }

    return data;
}
#endif

// 其他函数(ReadID、WriteEnable、EraseSector等)与硬件SPI版本完全一致
// 主函数流程也与硬件SPI版本一致,此处省略重复代码

注意

  • 时序匹配:主设备和从设备(W25Q128)的SPI模式必须一致(W25Q128支持模式0和3),默认优先选择模式0。
  • 引脚方向:严格区分输入/输出引脚(MISO为输入,其余为输出),配置错误会导致通信失败。
  • 速率控制:若传输速率过快(代码执行过慢),可在SCK电平切换处添加delay_us(1)等延时,确保从设备能正确采样。

IO模拟SPI实现RFID模块(MFRC522)卡号获取

基本概念

  • 芯片定位:MFRC522 是恩智浦(NXP)推出的高度集成化射频卡读写器芯片,专为 13.56MHz 非接触式通信设计,是 RFID 系统的核心驱动 IC。
  • 核心功能:支持对 ISO/IEC 14443A 协议、MIFARE 系列卡片(如 M1 卡)的读写操作,可实现无接触式信息采集与数据交互,是物联网 “万物互联” 理念的关键实现器件之一。
  • 核心优势:集成度高(内置发射 / 接收电路、解调解码模块、帧处理与错误检测单元)、通信速率灵活(支持 106Kbit/s、424Kbit/s、848Kbit/s)、接口多样化,适配多种嵌入式系统场景。

RFID 系统结构组成(基于 MFRC522)

RFID 系统需软硬件协同,MFRC522 作为阅读器核心,系统整体结构如下:

图片15

工作原理(含 M1 卡交互流程)

能量与信号传输原理

图片17

图片16

阅读器与 M1 卡通信流程

image

通信接口

MFRC522 支持多种主机接口,适配不同硬件设计需求:

  • 支持接口:SPI、I2C、UART;
  • 本次应用选择:SPI 接口(通过 MCU IO 口模拟时序实现,灵活适配硬件资源)。

工作模式

图片18

硬件接线(IO 模拟 SPI 方案)

因采用 IO 口模拟 SPI 时序控制,需从 MCU 选取 4 个空闲 IO 引脚,对应 SPI 核心信号线:

SPI 信号线 功能说明 MCU 引脚配置
SCK(串行时钟) 同步数据传输时钟(主机产生) 输出模式
MOSI(主出从入) 主机向 MFRC522 发送数据 输出模式
MISO(主入从出) MFRC522 向主机返回数据 输入模式
CS(片选) 选择目标 MFRC522 芯片(低电平有效) 输出模式

图片19

代码(含MFRC522驱动适配)

MFRC522.h

#ifndef _MFRC522_H_
#define _MFRC522_H_

#include "stm32f4xx.h"
#include <stdint.h>

/************************ MFRC522 SPI引脚定义 ************************/
// SPI引脚映射(STM32F4)
#define MFRC522_SPI_SCK_PIN         GPIO_Pin_7
#define MFRC522_SPI_SCK_GPIO_PORT   GPIOC
#define MFRC522_SPI_SCK_GPIO_CLK    RCC_AHB1Periph_GPIOC

#define MFRC522_SPI_MISO_PIN        GPIO_Pin_4
#define MFRC522_SPI_MISO_GPIO_PORT  GPIOA
#define MFRC522_SPI_MISO_GPIO_CLK   RCC_AHB1Periph_GPIOA

#define MFRC522_SPI_MOSI_PIN        GPIO_Pin_15
#define MFRC522_SPI_MOSI_GPIO_PORT  GPIOG
#define MFRC522_SPI_MOSI_GPIO_CLK   RCC_AHB1Periph_GPIOG

#define MFRC522_CS_PIN              GPIO_Pin_9
#define MFRC522_CS_GPIO_PORT        GPIOC
#define MFRC522_CS_GPIO_CLK         RCC_AHB1Periph_GPIOC

#define MFRC522_RST_PIN             GPIO_Pin_7
#define MFRC522_RST_GPIO_PORT       GPIOB
#define MFRC522_RST_GPIO_CLK        RCC_AHB1Periph_GPIOB

/************************ 引脚操作宏 ************************/
#define MFRC522_CS_LOW()        GPIO_ResetBits(MFRC522_CS_GPIO_PORT, MFRC522_CS_PIN)
#define MFRC522_CS_HIGH()       GPIO_SetBits(MFRC522_CS_GPIO_PORT, MFRC522_CS_PIN)

#define MFRC522_SCK_LOW()       GPIO_ResetBits(MFRC522_SPI_SCK_GPIO_PORT, MFRC522_SPI_SCK_PIN)
#define MFRC522_SCK_HIGH()      GPIO_SetBits(MFRC522_SPI_SCK_GPIO_PORT, MFRC522_SPI_SCK_PIN)

#define MFRC522_MOSI_LOW()      GPIO_ResetBits(MFRC522_SPI_MOSI_GPIO_PORT, MFRC522_SPI_MOSI_PIN)
#define MFRC522_MOSI_HIGH()     GPIO_SetBits(MFRC522_SPI_MOSI_GPIO_PORT, MFRC522_SPI_MOSI_PIN)

#define MFRC522_RST_LOW()       GPIO_ResetBits(MFRC522_RST_GPIO_PORT, MFRC522_RST_PIN)
#define MFRC522_RST_HIGH()      GPIO_SetBits(MFRC522_RST_GPIO_PORT, MFRC522_RST_PIN)

#define MFRC522_MISO_READ()     GPIO_ReadInputDataBit(MFRC522_SPI_MISO_GPIO_PORT, MFRC522_SPI_MISO_PIN)

/************************ MFRC522 命令字 ************************/
#define PCD_IDLE              0x00    // 空闲状态
#define PCD_AUTHENT           0x0E    // 验证密钥
#define PCD_TRANSCEIVE        0x0C    // 发送并接收数据
#define PCD_RESETPHASE        0x0F    // 复位
#define PCD_CALCCRC           0x03    // 计算CRC

/************************ Mifare卡命令字 ************************/
#define PICC_REQIDL           0x26    // 寻未休眠卡
#define PICC_REQALL           0x52    // 寻所有卡
#define PICC_ANTICOLL1        0x93    // 防冲突
#define PICC_AUTHENT1A        0x60    // 验证A密钥
#define PICC_AUTHENT1B        0x61    // 验证B密钥
#define PICC_READ             0x30    // 读块
#define PICC_WRITE            0xA0    // 写块
#define PICC_HALT             0x50    // 休眠卡

/************************ MFRC522 寄存器地址 ************************/
#define CommandReg            0x01
#define ComIEnReg             0x02
#define ComIrqReg             0x04
#define ErrorReg              0x06
#define Status2Reg            0x08
#define FIFODataReg           0x09
#define FIFOLevelReg          0x0A
#define ControlReg            0x0C
#define BitFramingReg         0x0D
#define CollReg               0x0E
#define ModeReg               0x11
#define TxControlReg          0x14
#define TxAutoReg             0x15
#define TModeReg              0x2A
#define TPrescalerReg         0x2B
#define TReloadRegL           0x2D
#define TReloadRegH           0x2C
#define CRCResultRegL         0x22
#define CRCResultRegH         0x21
#define DivIrqReg             0x05

/************************ 通信状态码 ************************/
#define MI_OK                 0       // 成功
#define MI_NOTAGERR           1       // 无卡
#define MI_ERR                2       // 错误

/************************ 常量定义 ************************/
#define MAX_LEN               18      // FIFO最大长度

/************************ 函数声明 ************************/
void Delay1_us(uint16_t count);
void MFRC522_LowLevel_Init(void);    // 底层硬件初始化
void MFRC522_Init(void);             // MFRC522初始化(对外接口)
void MFRC522_Reset(void);            // 复位MFRC522
void Write_MFRC522(uint8_t addr, uint8_t val); // 写寄存器
uint8_t Read_MFRC522(uint8_t addr);  // 读寄存器
void SetBitMask(uint8_t reg, uint8_t mask);    // 设置寄存器位
void ClearBitMask(uint8_t reg, uint8_t mask);  // 清除寄存器位
void AntennaOn(void);                // 开启天线
void AntennaOff(void);               // 关闭天线
uint8_t MFRC522_ToCard(uint8_t command, uint8_t *sendData, uint8_t sendLen, uint8_t *backData, uint16_t *backLen); // 卡通信
uint8_t MFRC522_Request(uint8_t reqMode, uint8_t *TagType); // 寻卡
uint8_t MFRC522_Anticoll(uint8_t *serNum);      // 防冲突(读卡号)
void CalulateCRC(uint8_t *pIndata, uint8_t len, uint8_t *pOutData); // 计算CRC
uint8_t MFRC522_SelectTag(uint8_t *serNum);     // 选卡
uint8_t MFRC522_Auth(uint8_t authMode, uint8_t BlockAddr, uint8_t *Sectorkey, uint8_t *serNum); // 验证密钥
uint8_t MFRC522_Read(uint8_t blockAddr, uint8_t *recvData); // 读卡块
uint8_t MFRC522_Write(uint8_t blockAddr, uint8_t *writeData); // 写卡块
void MFRC522_Halt(void);             // 休眠卡

#endif

MFRC522.c

#include "MFRC522.h"

/************************ 微秒延时函数 ************************/
void Delay1_us(uint16_t nus)
{
    u32 reload = 0;

    // 关闭Systick定时器
    SysTick->CTRL = 0;
    // 计算重装载值:nus * 21(21MHz时钟,1μs计数21次)
    reload = nus * 21;
    // 设置重装载值(最大不超过16777215)
    SysTick->LOAD = reload > 0xFFFFFF ? 0xFFFFFF : reload;
    // 清零当前值和COUNTFLAG位
    SysTick->VAL = 0;
    // 使能Systick,选择外部时钟源(CLKSOURCE=0),不产生中断(TICKINT=0)
    SysTick->CTRL = 1;

    // 等待计数完成(COUNTFLAG=1)
    while(!(SysTick->CTRL & (1 << 16)));

    // 关闭Systick定时器
    SysTick->CTRL = 0;
}

/************************ SPI单字节收发 ************************/
uint8_t MFRC522_SendByte(uint8_t byte)
{
    uint8_t cnt = 0;
    uint8_t data = 0;
    
    MFRC522_SCK_LOW(); // SCK初始低电平(SPI Mode0)
    
    for(cnt=0; cnt<8; cnt++) // 高位先行
    {
        // 发送1bit
        if(byte & 0x80)
            MFRC522_MOSI_HIGH();
        else
            MFRC522_MOSI_LOW();
        byte <<= 1;
        
        // 拉高SCK,触发从机采样
        MFRC522_SCK_HIGH();
        
        // 读取1bit
        data <<= 1;
        data |= MFRC522_MISO_READ();
        
        // 拉低SCK,准备下一位
        MFRC522_SCK_LOW();
    }
    return data;
}

/************************ 写MFRC522寄存器 ************************/
void Write_MFRC522(uint8_t addr, uint8_t val)
{
    MFRC522_CS_LOW();                          // 选中设备
    MFRC522_SendByte((addr << 1) & 0x7E);      // 地址格式:0XXXXXX0
    MFRC522_SendByte(val);                     // 写数据
    MFRC522_CS_HIGH();                         // 取消选中
}

/************************ 读MFRC522寄存器 ************************/
uint8_t Read_MFRC522(uint8_t addr)
{
    uint8_t val;
    MFRC522_CS_LOW();                          // 选中设备
    MFRC522_SendByte(((addr << 1) & 0x7E) | 0x80); // 地址格式:1XXXXXX0
    val = MFRC522_SendByte(0xFF);              // 读数据(发送0xFF触发从机返回)
    MFRC522_CS_HIGH();                         // 取消选中
    return val;
}

/************************ 设置寄存器位掩码 ************************/
void SetBitMask(uint8_t reg, uint8_t mask)
{
    uint8_t tmp = Read_MFRC522(reg);
    Write_MFRC522(reg, tmp | mask);
}

/************************ 清除寄存器位掩码 ************************/
void ClearBitMask(uint8_t reg, uint8_t mask)
{
    uint8_t tmp = Read_MFRC522(reg);
    Write_MFRC522(reg, tmp & (~mask));
}

/************************ 开启天线 ************************/
void AntennaOn(void)
{
    uint8_t temp = Read_MFRC522(TxControlReg);
    if((temp & 0x03) == 0)
    {
        SetBitMask(TxControlReg, 0x03);
    }
}

/************************ 关闭天线 ************************/
void AntennaOff(void)
{
    ClearBitMask(TxControlReg, 0x03);
}

/************************ 复位MFRC522 ************************/
void MFRC522_Reset(void)
{
    MFRC522_RST_HIGH();
    Delay1_us(1);
    MFRC522_RST_LOW();
    Delay1_us(1);
    MFRC522_RST_HIGH();
    Delay1_us(1);
    Write_MFRC522(CommandReg, PCD_RESETPHASE);
}

/************************ 底层硬件初始化(GPIO) ************************/
void MFRC522_LowLevel_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    // 使能所有相关GPIO时钟
    RCC_AHB1PeriphClockCmd(MFRC522_SPI_SCK_GPIO_CLK | MFRC522_SPI_MISO_GPIO_CLK |
                           MFRC522_SPI_MOSI_GPIO_CLK | MFRC522_CS_GPIO_CLK |
                           MFRC522_RST_GPIO_CLK, ENABLE);

    // 配置SCK/MOSI/CS/RST为推挽输出
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;

    // SCK
    GPIO_InitStructure.GPIO_Pin = MFRC522_SPI_SCK_PIN;
    GPIO_Init(MFRC522_SPI_SCK_GPIO_PORT, &GPIO_InitStructure);

    // MOSI
    GPIO_InitStructure.GPIO_Pin = MFRC522_SPI_MOSI_PIN;
    GPIO_Init(MFRC522_SPI_MOSI_GPIO_PORT, &GPIO_InitStructure);

    // CS
    GPIO_InitStructure.GPIO_Pin = MFRC522_CS_PIN;
    GPIO_Init(MFRC522_CS_GPIO_PORT, &GPIO_InitStructure);

    // RST
    GPIO_InitStructure.GPIO_Pin = MFRC522_RST_PIN;
    GPIO_Init(MFRC522_RST_GPIO_PORT, &GPIO_InitStructure);

    // 配置MISO为上拉输入
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_InitStructure.GPIO_Pin = MFRC522_SPI_MISO_PIN;
    GPIO_Init(MFRC522_SPI_MISO_GPIO_PORT, &GPIO_InitStructure);

    // 初始电平
    MFRC522_CS_HIGH();
    MFRC522_SCK_LOW();
    MFRC522_MOSI_LOW();
    MFRC522_RST_HIGH();
}

/************************ MFRC522初始化(对外接口) ************************/
void MFRC522_Init(void)
{
    MFRC522_LowLevel_Init(); // 底层GPIO初始化
    MFRC522_Reset();         // 复位芯片

    // 配置定时器(25ms超时)
    Write_MFRC522(TModeReg, 0x8D);        // 自动计数模式
    Write_MFRC522(TPrescalerReg, 0x3E);   // 预分频值
    Write_MFRC522(TReloadRegL, 0x32);     // 计数低8位
    Write_MFRC522(TReloadRegH, 0x00);     // 计数高8位
    Write_MFRC522(TxAutoReg, 0x40);       // 100%ASK调制
    Write_MFRC522(ModeReg, 0x3D);         // CRC初始值0x6363
    Write_MFRC522(CommandReg, 0x00);      // 启动MFRC522

    AntennaOn(); // 开启天线
}

/************************ 与卡片通信核心函数 ************************/
uint8_t MFRC522_ToCard(uint8_t command, uint8_t *sendData, uint8_t sendLen, uint8_t *backData, uint16_t *backLen)
{
    uint8_t status = MI_ERR;
    uint8_t irqEn = 0x00;
    uint8_t waitIRq = 0x00;
    uint8_t n, lastBits;
    uint16_t i;

    // 根据命令配置中断
    switch(command)
    {
        case PCD_AUTHENT:
            irqEn = 0x12;
            waitIRq = 0x10;
            break;
        case PCD_TRANSCEIVE:
            irqEn = 0x77;
            waitIRq = 0x30;
            break;
        default:
            break;
    }

    Write_MFRC522(ComIEnReg, irqEn | 0x80); // 使能中断
    ClearBitMask(ComIrqReg, 0x80);          // 清除所有中断标志
    SetBitMask(FIFOLevelReg, 0x80);         // 清空FIFO
    Write_MFRC522(CommandReg, PCD_IDLE);   // 空闲模式

    // 写入发送数据到FIFO
    for(i=0; i<sendLen; i++)
    {
        Write_MFRC522(FIFODataReg, sendData[i]);
    }

    // 执行命令
    Write_MFRC522(CommandReg, command);
    if(command == PCD_TRANSCEIVE)
    {
        SetBitMask(BitFramingReg, 0x80); // 开始发送
    }

    // 等待中断(超时10000次)
    i = 10000;
    do
    {
        n = Read_MFRC522(ComIrqReg);
        i--;
    } while((i != 0) && !(n & 0x01) && !(n & waitIRq));

    ClearBitMask(BitFramingReg, 0x80); // 停止发送

    // 处理响应
    if(i != 0)
    {
        if(!(Read_MFRC522(ErrorReg) & 0x1B)) // 无错误(缓冲区溢出/冲突/CRC/协议)
        {
            status = MI_OK;
            if(n & irqEn & 0x01)
            {
                status = MI_NOTAGERR; // 无卡
            }

            // 接收数据处理
            if(command == PCD_TRANSCEIVE)
            {
                n = Read_MFRC522(FIFOLevelReg); // FIFO中数据长度
                lastBits = Read_MFRC522(ControlReg) & 0x07; // 最后字节的有效位
                if(lastBits != 0)
                    *backLen = (n-1)*8 + lastBits;
                else
                    *backLen = n*8;

                if(n == 0) n = 1;
                if(n > MAX_LEN) n = MAX_LEN;

                // 读取FIFO数据
                for(i=0; i<n; i++)
                {
                    backData[i] = Read_MFRC522(FIFODataReg);
                }
            }
        }
        else
        {
            status = MI_ERR; // 通信错误
        }
    }

    Write_MFRC522(ControlReg, 0x80); // 停止定时器
    Write_MFRC522(CommandReg, PCD_IDLE); // 空闲模式
    return status;
}

/************************ 寻卡 ************************/
uint8_t MFRC522_Request(uint8_t reqMode, uint8_t *TagType)
{
    uint8_t status;
    uint16_t backBits;

    Write_MFRC522(BitFramingReg, 0x07); // 配置发送位数
    TagType[0] = reqMode;
    status = MFRC522_ToCard(PCD_TRANSCEIVE, TagType, 1, TagType, &backBits);

    if((status != MI_OK) || (backBits != 0x10))
    {
        status = MI_ERR;
    }

    return status;
}

/************************ 防冲突(读卡号) ************************/
uint8_t MFRC522_Anticoll(uint8_t *serNum)
{
    uint8_t status;
    uint8_t i, serNumCheck = 0;
    uint16_t unLen;

    ClearBitMask(Status2Reg, 0x08);
    ClearBitMask(CollReg, 0x80);
    Write_MFRC522(BitFramingReg, 0x00);

    serNum[0] = PICC_ANTICOLL1;
    serNum[1] = 0x20;

    status = MFRC522_ToCard(PCD_TRANSCEIVE, serNum, 2, serNum, &unLen);

    if(status == MI_OK)
    {
        // 校验卡号
        for(i=0; i<4; i++)
        {
            serNumCheck ^= serNum[i];
        }
        if(serNumCheck != serNum[4])
        {
            status = MI_ERR;
        }
    }

    SetBitMask(CollReg, 0x80);
    return status;
}

/************************ 计算CRC ************************/
void CalulateCRC(uint8_t *pIndata, uint8_t len, uint8_t *pOutData)
{
    uint16_t i;
    uint8_t n;

    ClearBitMask(DivIrqReg, 0x04); // 清除CRC中断
    SetBitMask(FIFOLevelReg, 0x80); // 清空FIFO
    Write_MFRC522(CommandReg, PCD_IDLE);

    // 写入数据到FIFO
    for(i=0; i<len; i++)
    {
        Write_MFRC522(FIFODataReg, pIndata[i]);
    }

    Write_MFRC522(CommandReg, PCD_CALCCRC); // 开始计算CRC

    // 等待CRC计算完成
    i = 1000;
    do
    {
        n = Read_MFRC522(DivIrqReg);
        i--;
    } while((i != 0) && !(n & 0x04));

    // 读取CRC结果
    pOutData[0] = Read_MFRC522(CRCResultRegL);
    pOutData[1] = Read_MFRC522(CRCResultRegH);
}

/************************ 选卡 ************************/
uint8_t MFRC522_SelectTag(uint8_t *serNum)
{
    uint8_t i, status, size;
    uint16_t recvBits;
    uint8_t buffer[9];

    buffer[0] = PICC_ANTICOLL1;
    buffer[1] = 0x70;
    buffer[6] = 0x00;

    // 填充卡号和校验位
    for(i=0; i<4; i++)
    {
        buffer[i+2] = serNum[i];
        buffer[6] ^= serNum[i];
    }

    CalulateCRC(buffer, 7, &buffer[7]); // 计算CRC
    ClearBitMask(Status2Reg, 0x08);

    status = MFRC522_ToCard(PCD_TRANSCEIVE, buffer, 9, buffer, &recvBits);

    if((status == MI_OK) && (recvBits == 0x18))
    {
        size = buffer[0];
    }
    else
    {
        size = 0;
    }

    return size;
}

/************************ 验证密钥 ************************/
uint8_t MFRC522_Auth(uint8_t authMode, uint8_t BlockAddr, uint8_t *Sectorkey, uint8_t *serNum)
{
    uint8_t status;
    uint16_t recvBits;
    uint8_t i;
    uint8_t buff[12];

    buff[0] = authMode;
    buff[1] = BlockAddr;
    for(i=0; i<6; i++) // 密钥
    {
        buff[i+2] = Sectorkey[i];
    }
    for(i=0; i<4; i++) // 卡号
    {
        buff[i+8] = serNum[i];
    }

    status = MFRC522_ToCard(PCD_AUTHENT, buff, 12, buff, &recvBits);

    if((status != MI_OK) || (!(Read_MFRC522(Status2Reg) & 0x08)))
    {
        status = MI_ERR;
    }

    return status;
}

/************************ 读卡块 ************************/
uint8_t MFRC522_Read(uint8_t blockAddr, uint8_t *recvData)
{
    uint8_t status;
    uint16_t unLen;

    recvData[0] = PICC_READ;
    recvData[1] = blockAddr;
    CalulateCRC(recvData, 2, &recvData[2]);

    status = MFRC522_ToCard(PCD_TRANSCEIVE, recvData, 4, recvData, &unLen);

    if((status != MI_OK) || (unLen != 0x90))
    {
        status = MI_ERR;
    }

    return status;
}

/************************ 写卡块 ************************/
uint8_t MFRC522_Write(uint8_t blockAddr, uint8_t *writeData)
{
    uint8_t status;
    uint16_t recvBits;
    uint8_t i;
    uint8_t buff[18];

    buff[0] = PICC_WRITE;
    buff[1] = blockAddr;
    CalulateCRC(buff, 2, &buff[2]);

    status = MFRC522_ToCard(PCD_TRANSCEIVE, buff, 4, buff, &recvBits);

    if((status == MI_OK) && (recvBits == 4) && ((buff[0] & 0x0F) == 0x0A))
    {
        // 写入16字节数据
        for(i=0; i<16; i++)
        {
            buff[i] = writeData[i];
        }
        CalulateCRC(buff, 16, &buff[16]);

        status = MFRC522_ToCard(PCD_TRANSCEIVE, buff, 18, buff, &recvBits);

        if((status != MI_OK) || (recvBits != 4) || ((buff[0] & 0x0F) != 0x0A))
        {
            status = MI_ERR;
        }
    }
    else
    {
        status = MI_ERR;
    }

    return status;
}

/************************ 休眠卡 ************************/
void MFRC522_Halt(void)
{
    uint16_t unLen;
    uint8_t buff[4];

    buff[0] = PICC_HALT;
    buff[1] = 0;
    CalulateCRC(buff, 2, &buff[2]);

    MFRC522_ToCard(PCD_TRANSCEIVE, buff, 4, buff, &unLen);
}

main.c

#include "stm32f4xx.h"
#include "MFRC522.h"
#include <stdio.h>
#include <string.h>
/************************ 微秒延时函数 ************************/
void delay_us(u32 nus)
{
    u32 reload = 0;

    // 关闭Systick定时器
    SysTick->CTRL = 0;
    // 计算重装载值:nus * 21(21MHz时钟,1μs计数21次)
    reload = nus * 21;
    // 设置重装载值(最大不超过16777215)
    SysTick->LOAD = reload > 0xFFFFFF ? 0xFFFFFF : reload;
    // 清零当前值和COUNTFLAG位
    SysTick->VAL = 0;
    // 使能Systick,选择外部时钟源(CLKSOURCE=0),不产生中断(TICKINT=0)
    SysTick->CTRL = 1;

    // 等待计数完成(COUNTFLAG=1)
    while(!(SysTick->CTRL & (1 << 16)));

    // 关闭Systick定时器
    SysTick->CTRL = 0;
} 

/************************ 毫秒延时函数 ************************/
void delay_ms(u32 nms)
{
    while(nms--)
    {
        delay_us(1000); // 每次延时1000μs(1ms)
    }
}
/************************ USART1初始化 ************************/
void USART1_Init(uint32_t baudrate)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    USART_InitTypeDef USART_InitStruct;
    NVIC_InitTypeDef NVIC_InitStruct;

    // 使能时钟
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

    // 配置PA9(TX)、PA10(RX)为复用功能
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1);

    // TX引脚:推挽复用输出
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    // RX引脚:浮空输入
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    // 配置USART1
    USART_InitStruct.USART_BaudRate = baudrate;
    USART_InitStruct.USART_WordLength = USART_WordLength_8b;
    USART_InitStruct.USART_StopBits = USART_StopBits_1;
    USART_InitStruct.USART_Parity = USART_Parity_No;
    USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(USART1, &USART_InitStruct);

    // 使能USART1
    USART_Cmd(USART1, ENABLE);
}

/************************ 主函数 ************************/
int main(void)
{
    uint8_t status;
    uint8_t card_type[2];    // 卡类型
    uint8_t card_id[5];      // 卡号(4字节+1字节校验)

    // 1. 系统初始化

    USART1_Init(115200);     // 串口1初始化(用于打印)
    MFRC522_Init();          // MFRC522初始化

    printf("MFRC522 RFID Test Program\r\n");
    printf("Waiting for card...\r\n");

    // 2. 主循环
    while(1)
    {
        // 寻卡(寻所有卡)
        status = MFRC522_Request(PICC_REQALL, card_type);

        if(status == MI_OK) // 检测到卡片
        {
            printf("Card detected!\r\n");

            // 防冲突,读取卡号
            status = MFRC522_Anticoll(card_id);
            if(status == MI_OK)
            {
                // 打印卡号(十六进制)
                printf("Card ID: 0x%02X%02X%02X%02X\r\n",
                       card_id[0], card_id[1], card_id[2], card_id[3]);

                // 选卡(可选,选中后可进行读写操作)
                MFRC522_SelectTag(card_id);

                // 休眠卡(可选,休眠后需重新寻卡)
                MFRC522_Halt();
            }
            else
            {
                printf("Anticoll error!\r\n");
            }
        }
        else if(status == MI_NOTAGERR)
        {
            // 无卡(无需打印,避免刷屏)
        }

        delay_ms(500); // 延时500ms,降低扫描频率
    }
}

/************************ 重定向fputc到串口 ************************/
int fputc(int ch, FILE *f)
{
    // 等待发送缓冲区为空
    while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    // 发送字符
    USART_SendData(USART1, (uint8_t)ch);
    return ch;
}

image

程序移植步骤(以MFRC522为例)

下载源码

获取 MFRC522 参考代码包,核心文件为 MFRC522.c(驱动实现)和 MFRC522.h(头文件声明)。

分析源码

  • 主函数流程
  • 核心函数(底层)

移植文件

MFRC522.cMFRC522.h 复制到项目 HARDWARE 文件夹中。

添加驱动

在 MDK 中通过「Manage Project Items」,将 MFRC522.c 添加到「HARDWARE」分组。

配置路径

在项目选项「C/C++」的「Include Paths」中,添加 MFRC522.h 所在文件夹路径,确保编译器能识别头文件。

编译工程(头文件依赖检查)

首次编译,验证头文件引用是否正确,解决依赖缺失问题。

底层适配

  • 管脚适配:修改 MFRC522.h 中 SPI 信号线对应的 MCU 引脚定义(如 SCK 对应 GPIOB_Pin_3);
  • 时序适配:根据所选 SPI 工作模式(如模式 0),调整 IO 口电平变化时序(时钟边沿、数据采样时机)。

编译工程(语法检查)

二次编译,修正代码语法错误、引脚定义错误等。

观察结果

下载程序到开发板,通过 LCD 显示、串口打印等方式,观察卡片 ID 读取、数据读写等结果。

posted @ 2026-01-26 19:49  YouEmbedded  阅读(1)  评论(0)    收藏  举报