资讯详情

RT-Thread记录(十五、I/O 设备模型之SPI设备)

学习这篇文章I/O 设备模型之SPI设备使用,I/O 最后一篇关于设备模型的文章。 

目录

  • 前言
  • 一、SPI 通讯基础
  • 二、SPI 设备操作函数
    • 2.1 挂载 SPI 设备
    • 2.2 配置 SPI 设备
    • 2.3 访问 SPI设备
      • 2.3.1 查找 SPI 设备
      • 2.3.2 自定义数据传输
      • 2.3.3 数据收发函数
      • 2.3.4 特殊场景
  • 三、SPI 设备测试
    • 3.1 SPI 设备使用步骤
    • 3.2 测试
  • 结语

前言

本文应该是 RT-Thread I/O 最后一篇设备模型,SPI 设备的学习测试。

我之前说过,我的记录是为了应用,但实际上我们在使用它 RT-Thread 有很多常用的设备,官方或许多开发人员已经为我们写了驱动程序和软件包,我们不需要再写一篇文章,大多数时候直接导入软件包,直接呼叫现成的 API 函数就可以了。

RT-Thread 接下来的文章系列应该更新几篇文章 本文将使用软件包和组件 SPI 学习测试设备。

?? 本 RT-Thread 专栏记录的开发环境: RT-Thread记录(一、RT-Thread 版本、RT-Thread Studio开发环境 及 配合CubeMX快速开发) RT-Thread记录(二、RT-Thread内核启动过程 — 启动文件及源码分析) ?? RT-Thread 内核篇系列博文链接: RT-Thread记录(三、RT-Thread 线程操作函数和线程管理FreeRTOS的比较) RT-Thread记录(四、RT-Thread 时钟节拍和软件定时器) RT-Thread记录(五、RT-Thread 临界保护) RT-Thread记录(六、IPC信号量、相互排斥和事件集的机制) RT-Thread记录(七、IPC机制之邮箱、消息队列) RT-Thread记录(八、理解 RT-Thread 内存管理) RT-Thread记录(九、RT-Thread 中断处理和阶段总结) ?? 在STM32L051C8 上使用 RT-Thread 应用文系列博文连接: RT-Thread 应用篇 — 在STM32L051上使用 RT-Thread (一)无线温湿度传感器 之 新建项目) RT-Thread 应用篇 — 在STM32L051上使用 RT-Thread (二)无线温湿度传感器 之 CubeMX配置) RT-Thread 应用篇 — 在STM32L051上使用 RT-Thread (三)无线温湿度传感器 之 I2C通讯) RT-Thread 应用篇 — 在STM32L051上使用 RT-Thread (4)无线温湿度传感器 之 串口通讯) ?? RT-Thread 设备系列博文链接: RT-Thread记录(十、全面了解 RT-Thread I/O 设备模型) RT-Thread记录(十一)I/O 设备模型之UART设备 — 源码解析) RT-Thread记录(十二)I/O 设备模型之UART设备 — 使用测试) RT-Thread记录(十三、I/O 设备模型之PIN设备) RT-Thread记录(十四)I/O 设备模型之ADC设备)

一、SPI 通讯基础

SPI 通信基础知识介绍不多,原则和基础可以在网上查询。本文作为应用所需的简要概述:

SPI是串行外设界面(Serial Peripheral Interface)缩写是一种高速、全双工、同步通信总线,SPI 通信速度可以达到几十个M,芯片管脚上只有四条线:

(1)MISO– Master Input Slave Output,从设备数据输出输入主设备数据; (2)MOSI– Master Output Slave Input,从设备数据输入主设备数据输出; (3)SCLK – Serial Clock,由主设备产生的时钟信号; (4)CS – Chip Select,主设备控制从设备使能信号。

在这里插入图片描述

SPI 以主从方式工作,通常有一个主设备和一个或多个从设备。

SPI 通信有四种模式,由 CPOL 和 CPHA 决定:

CPOL=0,表示当SCLK=0点处于空闲状态,空闲低电平,所以有效状态是SCLK在高电平时 CPOL=1,表示当SCLK=1:00处于空闲状态,空闲高电平,所以有效状态是SCLK低电平时 CPHA=0,表示数据采样在第一个边缘 CPHA=1.表示数据采样在第二个边缘

如下表格:

CPOL CPHA 说明
1 1 时钟空闲为高电平,第二时钟边缘开始采样
0 0 时钟空闲为低电平,采样始于第一时钟边缘
1 0 时钟空闲为高电平,采样始于第一时钟边缘
0 1 时钟空闲为低电平,在第二个时钟边沿开始采样

对于我们的从机设备,如传感器,支持模式将在手册中说明:例如,我们今天要测试的 SPI Flash:

二、SPI 设备操作函数

来了解一下 RT-Thread 提供的 SPI 设备操作函数:

函数 描述
rt_spi_bus_attach_device() SPI 设备需要挂载至注册设备 SPI 总线,挂载SPI 设备
rt_spi_configure() 配置 SPI 设备
rt_device_find() 根据 SPI 设备名称搜索设备获取设备句柄
rt_spi_transfer_message() 定制传输数据
rt_spi_transfer() 传输一次数据
rt_spi_send() 发送数据一次
rt_spi_recv() 接受一次数据
rt_spi_send_then_send() 连续两次发送
rt_spi_send_then_recv() 接收前先发送

与前面的设备不同的是,SPI 因为一主多从可以, SPI 设备多了一个挂载操作,就是 RT-Thread 注册系统驱动 SPI 然后我们需要使用自己的总线 SPI 设备挂载到总线,使设备能够操作 。

☆ 数据函数的自定义传输 rt_spi_transfer_message 作为核心,其实这个函数可以用来表达,下面就解释一下。☆

.

2.1 挂载 SPI 设备

SPI 驱动注册完 SP 总线,需要用 SPI 挂载函数将要使用的 SPI 设备需要挂载到已经注册好的 SPI 总线上:

/* 参数 描述 device SPI 设备句柄 name SPI 设备名称 bus_name SPI 总线名称 user_data 用户数据指针 返回 —— RT_EOK 成功 其他错误码 失败 */
rt_err_t rt_spi_bus_attach_device(struct rt_spi_device *device,
                                  const char           *name,
                                  const char           *bus_name,
                                  void                 *user_data)

此函数用于挂载一个 SPI 设备到指定的 SPI 总线,并向内核注册 SPI 设备,并将 user_data 保存到 SPI 设备的控制块里。

一般 SPI 总线命名原则为 spix, SPI 设备命名原则为 spixy ,如 spi10 表示挂载在 spi1 总线上的 0 号设备。 user_data 一般为 SPI 设备的 CS 引脚指针,进行数据传输时 SPI 控制器会操作此引脚进行片选。

对于我们测试使用的 STM32 而言,有专门的挂载函数 rt_hw_spi_device_attach

/* 参数 描述 bus_name SPI 总线名称 device_name SPI 设备名称 后面2个参数是设置片选引脚: cs_gpiox GPIOA、GPIOB之类... cs_gpio_pin GPIO口名称 返回 —— RT_EOK 成功 其他错误码 失败 */
rt_err_t rt_hw_spi_device_attach(const char *bus_name, 
								 const char *device_name, 
								 GPIO_TypeDef *cs_gpiox, 
								 uint16_t cs_gpio_pin)

2.2 配置 SPI 设备

上面介绍 SPI 通讯基础的时候讲到过 SPI 的工作模式等细节,RT-Thread 里使用 SPI 配置函数进行配置:

/* 参数 描述 device SPI 设备句柄 cfg SPI 配置参数指针 返回 —— RT_EOK 成功 */
rt_err_t rt_spi_configure(struct rt_spi_device        *device,
                          struct rt_spi_configuration *cfg)

...

/** * SPI configuration structure */
struct rt_spi_configuration
{ 
        
    rt_uint8_t mode;        /* 模式 */
    rt_uint8_t data_width;  /* 数据宽度,可取8位、16位、32位 */
    rt_uint16_t reserved;   /* 保留 */
    
    rt_uint32_t max_hz;     /* 最大频率 */
};


/** * 上面结构体第一个参数: mode * SPI configuration structure * 其中与 SPI mode 相关的宏定义有 */
#define RT_SPI_CPHA (1<<0) /* bit[0]:CPHA, clock phase */
#define RT_SPI_CPOL (1<<1) /* bit[1]:CPOL, clock polarity */
/* 设置数据传输顺序是MSB位在前还是LSB位在前 */
#define RT_SPI_LSB (0<<2) /* bit[2]: 0-LSB */
#define RT_SPI_MSB (1<<2) /* bit[2]: 1-MSB */
/* 设置SPI的主从模式 */
#define RT_SPI_MASTER (0<<3) /* SPI master device */
#define RT_SPI_SLAVE (1<<3) /* SPI slave device */

#define RT_SPI_CS_HIGH (1<<4) /* Chipselect active high */
#define RT_SPI_NO_CS (1<<5) /* No chipselect */
#define RT_SPI_3WIRE (1<<6) /* SI/SO pin shared */
#define RT_SPI_READY (1<<7) /* Slave pulls low to pause */

#define RT_SPI_MODE_MASK (RT_SPI_CPHA | RT_SPI_CPOL | RT_SPI_MSB | RT_SPI_SLAVE | RT_SPI_CS_HIGH | RT_SPI_NO_CS | RT_SPI_3WIRE | RT_SPI_READY)
/* 设置时钟极性和时钟相位 */
#define RT_SPI_MODE_0 (0 | 0) /* CPOL = 0, CPHA = 0 */
#define RT_SPI_MODE_1 (0 | RT_SPI_CPHA) /* CPOL = 0, CPHA = 1 */
#define RT_SPI_MODE_2 (RT_SPI_CPOL | 0) /* CPOL = 1, CPHA = 0 */
#define RT_SPI_MODE_3 (RT_SPI_CPOL | RT_SPI_CPHA) /* CPOL = 1, CPHA = 1 */

#define RT_SPI_BUS_MODE_SPI (1<<0)
#define RT_SPI_BUS_MODE_QSPI (1<<1)

/** * 上面结构体第二个和第四个参数: data_width 和 max_hz */
//根据 SPI 主设备及 SPI 从设备可发送及接收的数据宽度格式 和频率 设置。


 /* * 示例程序 */
 struct rt_spi_configuration cfg;
 cfg.data_width = 8;
 cfg.mode = RT_SPI_MASTER | RT_SPI_MODE_0 | RT_SPI_MSB;
 cfg.max_hz = 20 * 1000 *1000;                           /* 20M */

 rt_spi_configure(spi_dev, &cfg);

2.3 访问 SPI设备

前面的两个函数类似于 SPI 的初始化工作,接下来就是我们熟悉的设备操作函数:

2.3.1 查找 SPI 设备

I/O 设备模型通用的查找函数:

/* 参数 描述 name SPI 设备名称 返回 —— 设备句柄 查找到对应设备将返回相应的设备句柄 RT_NULL 没有找到设备 */
rt_device_t rt_device_find(const char* name);

注意事项和 ADC 设备一样,用来接收的设备句柄不是使用rt_device_t ,但是与 ADC 也有不一样的地方,具体如下图:

因为 SPI 设备的接口体并没有 typedef 重定义,所以使用起来还得直接使用结构体指针表示。

2.3.2 自定义数据传输

自定义传输函数rt_spi_transfer_message,是访问 SPI 设备的关键函数!

获取到 SPI 设备句柄就可以使用 SPI 设备管理接口访问 SPI 设备器件,进行数据收发:

/* 参数 描述 device SPI 设备句柄 message 消息指针 返回 —— RT_NULL 成功发送 非空指针 发送失败,返回指向剩余未发送的 message 的指针 */
struct rt_spi_message *rt_spi_transfer_message(struct rt_spi_device  *device,
                                               struct rt_spi_message *message)

其中第二个参数,消息的结构体,这也是发送消息的关键:

/** * SPI message structure */
struct rt_spi_message
{ 
        
    const void *send_buf;           /* 发送缓冲区指针,其值为 RT_NULL 时, 表示本次传输为只接收状态,不需要发送数据。*/
    void *recv_buf;                 /* 接收缓冲区指针,其值为 RT_NULL 时, 表示本次传输为只发送状态,不需要保存接收到的数据 */
    rt_size_t length;               /* 发送 / 接收 数据字节数,单位为 word , 长度为 8 位时,每个 length 占用 1 个字节; 当数据长度为 16 位时,每个 length 占用 2 个字节*/
    struct rt_spi_message *next;    /* 指向继续发送的下一条消息的指针 , 若只发送一条消息,则此指针值为 RT_NULL。 多个待传输的消息通过 next 指针以单向链表的形式连接在一起。*/
    
    unsigned cs_take    : 1;        /* 片选选中 cs_take 值为 1 时,表示在传输数据前,设置对应的 CS 为有效状态。*/
    unsigned cs_release : 1;        /* 释放片选 cs_release 值为 1 时,表示在数据传输结束后,释放对应的 CS。*/
};

关于最后两个参数:

传输的第一条消息 cs_take 需置为 1,设置片选为有效,

传输的最后一条消息的 cs_release 需置 1,释放片选。

struct rt_spi_message msg1;


msg1.send_buf   = send_buf;
msg1.recv_buf   = receive_buf;
msg1.length     = send_length;
msg1.cs_take    = 1;     			// 传输之前要先把总线拉低
msg1.cs_release = 1;				// 传输之后要把总线释放
msg1.next       = RT_NULL;


rt_spi_transfer_message(struct rt_spi_device *device, &msg1);

struct rt_spi_message msg1,msg2;

uint8 id[5] = { 
        0};

msg1.send_buf   = send_buf;
msg1.recv_buf   = RT_NULL;
msg1.length     = send_length;
msg1.cs_take    = 1;     		// 传输之前要先把总线拉低
msg1.cs_release = 0;				// 本次结束之后并不释放总线,因为还要发送,所以为0
msg1.next       = &msg2;

msg2.send_buf   = RT_NULL;
msg2.recv_buf   = id;
msg2.length     = 5; 		//接收5个字节
msg2.cs_take    = 0; 		//前面已经拉低了,没有释放,所以这里是不需要拉低的
msg2.cs_release = 1;		//但是这个完成以后,需要释放总线,这是结尾
msg2.next       = RT_NULL;


rt_spi_transfer_message(struct rt_spi_device *device, &msg1);

struct rt_spi_message msg1,msg2,msg3;


msg1.send_buf   = send_buf;
msg1.recv_buf   = RT_NULL;
msg1.length     = length1;
msg1.cs_take    = 1;     		// 传输之前要先把总线拉低
msg1.cs_release = 0;				// 本次结束之后并不释放总线,因为还要发送,所以为0
msg1.next       = &msg2;

msg2.send_buf   = RT_NULL;
msg2.recv_buf   = receive_buff;
msg2.length     = length2; 		
msg2.cs_take    = 0; 		//前面已经拉低了,没有释放,所以这里是不需要拉低的
msg2.cs_release = 0;		//这里也不需要释放,前面会拉,后面会放
msg2.next       = &msg3;


msg3.send_buf   = RT_NULL;
msg3.recv_buf   = receive_buff;
msg3.length     = len3; 		//
msg3.cs_take    = 0; 		//前面已经拉低了,没有释放,所以这里是不需要拉低的
msg3.cs_release = 1;		//但是这个完成以后,需要释放总线,这是结尾
msg3.next       = RT_NULL;


rt_spi_transfer_message(struct rt_spi_device *device, &msg1);

2.3.3 数据收发函数

除了上面通用的自定义数据传输函数, RT-Thread 还提供了一系列简单的数据收发函数,其实都是通过上面的函数演变而来,我们也简单的过一遍:

/* 参数 描述 device SPI 设备句柄 send_buf 发送数据缓冲区指针 recv_buf 接收数据缓冲区指针 length 发送/接收 数据字节数 返回 —— 0 传输失败 非 0 值 成功传输的字节数 */
rt_size_t rt_spi_transfer(struct rt_spi_device *device,
                          const void           *send_buf,
                          void                 *recv_buf,
                          rt_size_t             length)

使用此函数等同于:

struct rt_spi_message msg;

msg.send_buf   = send_buf;
msg.recv_buf   = recv_buf;
msg.length     = length;
msg.cs_take    = 1;
msg.cs_release = 1;
msg.next        = RT_NULL;


rt_spi_transfer_message(struct rt_spi_device *device, &msg);

/* 参数 描述 device SPI 设备句柄 send_buf 发送数据缓冲区指针 length 发送数据字节数 返回 —— 0 发送失败 非 0 值 成功发送的字节数 */
rt_inline rt_size_t rt_spi_send(struct rt_spi_device *device,
                                const void           *send_buf,
                                rt_size_t             length)
{ 
        
    return rt_spi_transfer(device, send_buf, RT_NULL, length);
}

此函数直接是上面函数忽略接收数据的效果,可以直接看上面的函数内容。

/* 参数 描述 device SPI 设备句柄 recv_buf 接收数据缓冲区指针 length 接收数据字节数 返回 —— 0 接收失败 非 0 值 成功接收的字节数 */
rt_inline rt_size_t rt_spi_recv(struct rt_spi_device *device,
                                void                 *recv_buf,
                                rt_size_t             length)
{ 
        
    return rt_spi_transfer(device, RT_NULL, recv_buf, length);
}

与上面发送一次数据相反,传输一次数据函数忽略接收的数据。

/* 参数 描述 device SPI 设备句柄 send_buf1 发送数据缓冲区 1 指针 send_length1 发送数据缓冲区 1 数据字节数 send_buf2 发送数据缓冲区 2 指针 send_length2 发送数据缓冲区 2 数据字节数 返回 —— RT_EOK 发送成功 -RT_EIO 发送失败 */
rt_err_t rt_spi_send_then_send(struct rt_spi_device *device,
                               const void           *send_buf1,
                               rt_size_t             send_length1,
                               const void           *send_buf2,
                               rt_size_t             send_length2)

本函数适合向 SPI 设备中写入一块数据,第一次先发送命令和地址等数据,第二次再发送指定长度的数据。

之所以分两次发送而不是合并成一个数据块发送,或调用两次 rt_spi_send(),是因为在大部分的数据写操作中,都需要先发命令和地址,长度一般只有几个字节。如果与后面的数据合并在一起发送,将需要进行内存空间申请和大量的数据搬运。

而如果调用两次 rt_spi_send(),那么在发送完命令和地址后,片选会被释放,大部分 SPI 设备都依靠设置片选一次有效为命令的起始,所以片选在发送完命令或地址数据后被释放,则此次操作被丢弃。

使用此函数等同于:

struct rt_spi_message msg1,msg2;

msg1.send_buf   = send_buf1;
msg1.recv_buf   = RT_NULL;
msg1.length     = send_length1;
msg1.cs_take    = 1;
msg1.cs_release = 0;
msg1.next       = &msg2;

msg2.send_buf   = send_buf2;
msg2.recv_buf   = RT_NULL;
msg2.length     = send_length2;
msg2.cs_take    = 0;
msg2.cs_release = 1;
msg2.next       = RT_NULL;


rt_spi_transfer_message(struct rt_spi_device *device, &msg1);

/* 参数 描述 device SPI 从设备句柄 send_buf 发送数据缓冲区指针 send_length 发送数据缓冲区数据字节数 recv_buf 接收数据缓冲区指针 recv_length 接收数据字节数 返回 —— RT_EOK 成功 -RT_EIO 失败 */
rt_err_t rt_spi_send_then_recv(struct rt_spi_device *device,
                               const void           *send_buf,
                               rt_size_t             send_length,
                               void                 *recv_buf,
                               rt_size_t             recv_length)

本函数适合从 SPI 从设备中读取一块数据,第一次会先发送一些命令和地址数据,然后再接收指定长度的数据。

使用此函数等同于:

struct rt_spi_message msg1,msg2;

msg1.send_buf   = send_buf;
msg1.recv_buf   = RT_NULL;
msg1.length     = send_length;
msg1.cs_take    = 1;
msg1.cs_release = 0;
msg1.next       = &msg2;

msg2.send_buf   = RT_NULL;
msg2.recv_buf   = recv_buf;
msg2.length     = recv_length;
msg2.cs_take    = 0;
msg2.cs_release = 1;
msg2.next       = RT_NULL;


rt_spi_transfer_message(struct rt_spi_device *device, &msg1);

2.3.4 特殊场景

特殊场景部分暂时并不能体会其中的意义,所以这里直接套用官方的说明,等以后再使用过程中如果确实遇到问题,再来更新自己的心得体会。

在一些特殊的使用场景,某个设备希望独占总线一段时间,且期间要保持片选一直有效,期间数据传输可能是间断的,则可以按照如所示步骤使用相关接口。传输数据函数必须使用 rt_spi_transfer_message(),并且此函数每个待传输消息的片选控制域 cs_takecs_release 都要设置为 0 值,因为片选已经使用了其他接口控制,不需要在数据传输的时候控制。

在多线程的情况下,同一个 SPI 总线可能会在不同的线程中使用,为了防止 SPI 总线正在传输的数据丢失,从设备在开始传输数据前需要先获取 SPI 总线的使用权,获取成功才能够使用总线传输数据:

/* 参数 描述 device SPI 设备句柄 返回 —— RT_EOK 成功 错误码 失败 */
rt_err_t rt_spi_take_bus(struct rt_spi_device *device)

从设备获取总线的使用权后,需要设置自己对应的片选信号为有效:

/* 参数 描述 device SPI 设备句柄 返回 —— 0 成功 错误码 失败 */
rt_err_t rt_spi_take(struct rt_spi_device *device)

使用 rt_spi_transfer_message() 传输消息时,所有待传输的消息都是以单向链表的形式连接起来的:

/* 参数 描述 list 待传输的消息链表节点 message 新增消息指针 */
rt_inline void rt_spi_message_append(struct rt_spi_message *list,
                                     struct rt_spi_message *message)

传输完成释放片选:

/* device SPI 设备句柄 返回 —— 0 成功 错误码 失败 */
rt_err_t rt_spi_release(struct rt_spi_device *device)

从设备不在使用 SPI 总线传输数据,必须尽快释放总线,这样其他从设备才能使用 SPI 总线传输数据:

/* 参数 描述 device SPI 设备句柄 返回 —— RT_EOK 成功 */
rt_err_t rt_spi_release_bus(struct rt_spi_device *device);

三、SPI 设备测试

与上一篇文章说的 ADC 设备类似,我们可以通过,但是也需要注意他的使用步骤:

3.1 SPI 设备使用步骤

board.h 文件中,我们可以查看其中关于 SPI的 使用步骤的注释:

.

.

.

.

比如我使用的开发板原理图(忽略当时的引脚标号,这里应该是 SPI1,当时写标号居然写的是 SPI2 ):

查看对应的手册资料:

所以我们需要使能的是 SPI1:

.

.

和上一篇文章的 ADC 设备一样进行操作,如下图:

到这一步,我们已经能够找到我们需要的 HAL_SPI_MspInit 文件了,通过 spi.h 头文件找到 spi.c 文件中的这个函数:

.

.

.

.

在上一篇文章 ADC 步骤中已经讲解过,使用 STM32CubeMX 设置以后,文件会自动使能:

到这里 SPI 的配置就算全部完成了,我们可以直接在应用程序中,使用 SPI 设备操作函数实现 SPI 的读取。

3.2 测试

我们板载的是SPI设备是 W25Q128 ,我们测试一下 RT-Thread 的 SPI 设备模型是否能够正常通行,这里只做简单的读取 ID 的测试,官方的示例也是针对 W25Qxx 系列的,但是我还是按照自己的理解来进行。

我们根据上面的使用步骤,配置好 SPI ,我们应用程序什么都不操作,看看初始化以后是否有 spi1 总线设备,如下图:

.

确认了上电初始化以后 spi1 总线就已经存在,我们就可以使用 SPI 的操作函数进行,我们先把 spi 设备挂载上 spi 总线,然后进行必要的配置,操作代码如图:

到这一步,看可以看设备是否正常注册:

.

好了,接下来就可以经常正常的操作了,官方的示例是读取 W25Qxx 的 ID,至于读取 ID 操作流程,是需要查看 芯片手册的,但是我还想想到曾经在裸机使用过这个 SPI Flash ,那么我可以直接参考以前的驱动代码,这样就省去了再一次的手册查看资料 = = !

上一下裸机的有关操作代码:

//读取芯片ID W25Q128的ID:0XEF17
u16 SPI_Flash_ReadID()
{ 
        
	u16 Temp = 0;	  
	W25Qxx_CS_ON;				    
	SPI1_ReadWriteByte(W25X_ManufactDeviceID);// 
	SPI1_ReadWriteByte(0x00); 	    
	SPI1_ReadWriteByte(0x00); 	    
	SPI1_ReadWriteByte(0x00); 	 // 
	Temp|=SPI1_ReadWriteByte(0xFF)<<8;  
	Temp|=SPI1_ReadWriteByte(0xFF);	 
	W25Qxx_CS_OFF;				    
	return Temp;	
}


//指令表
#define W25X_WriteEnable 0x06 
#define W25X_WriteDisable 0x04 
#define W25X_ReadStatusReg 0x05 
#define W25X_WriteStatusReg 0x01 
#define W25X_ReadData 0x03 
#define W25X_FastReadData 0x0B 
#define W25X_FastReadDual 0x3B 
#define W25X_PageProgram 0x02 
#define W25X_BlockErase 0xD8 
#define W25X_SectorErase 0x20 
#define W25X_ChipErase 0

标签: w25传感器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台