Zephry I2C和SPI驱动器介绍和操作FM24V10闪存

Posted 17岁boy想当攻城狮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Zephry I2C和SPI驱动器介绍和操作FM24V10闪存相关的知识,希望对你有一定的参考价值。

目录

前言

一、I2C Drive

1. 开启方法

2. 使用方法

3. 主要API介绍

3.1 i2c_configure

3.2 i2c_transfer

3.3 i2c_recover_bus

3.4 i2c_slave_register

3.5 i2c_slave_unregister

3.6 i2c_slave_driver_register

3.7 i2c_slave_driver_unregister

3.8 i2c_write

3.9 i2c_read

4.0 i2c_write_read

4.1 i2c_burst_read

4.2  i2c_burst_write

4. I2C示例:读写与擦除”FM24V10 FLASH“

4.1 FM24V10 FLASH简介

4.2 着手开发

二、SPI Dirve

1. 开启方法

2. 使用方法

3. 结构体介绍

3.1 结构体介绍

4. 主要API介绍

4.1 spi_write

4.2 spi_read

4.3 spi_transceive

5 简单的读写示例

5.1 写

5.2 读


前言

本文涉及到的I2C与SPI通讯原理可以参考这篇文章:一文详细介绍GPIO、I2C、SPI通讯原理以及物理层原理

一、I2C Drive

1. 开启方法

在当前工程的prj.conf文件中输入以下内容:

CONFIG开头的代表包含指定Drive,格式:CONFIG_Drive名称

CONFIG_I2C=y

编译时Zephry会自动把I2C Drive代码一并编译进来,我们就可以在开发阶段使用I2C的功能了

2. 使用方法

在你代码文件中包含“drivers/i2c.h”即可

#include <drivers/i2c.h>

3. 主要API介绍

3.1 i2c_configure

3.1.1 函数介绍

函数原型

作用

返回值

int i2c_configure(const struct device *dev, uint32_t dev_config)配置主机的i2c控制器0成功,非0失败

3.1.2 参数介绍

参数名

类型

介绍

devconst struct device *指向I2C Device的指针
dev_configuint32_t配置值,要求32位

dev_config可取以下选项

宏定义

作用

I2C_SPEED_STANDARD标准模式:100 kHz
I2C_SPEED_FAST快速模式:400 kHz
I2C_SPEED_FAST_PLUS快速模式+:1 MHz
I2C_SPEED_HIGH高速模式:3.4 MHz
I2C_SPEED_ULTRA超快速模式:5 MHz
I2C_ADDR_10_BITS使用10位寻址。不推荐使用-改用I2C_MSG_ADDR_10_BITS 
I2C_MODE_MASTER使当前控制器作为主控制器

3.2 i2c_transfer

3.2.1 函数介绍

函数原型

作用

返回值

int i2c_transfer(const struct device *dev, struct i2c_msg *msgs, uint8_t num_msgs, uint16_t addr)在主模式下执行到另一个I2C设备的数据传输0成功,非0失败

3.2.2 参数介绍

参数名

类型

介绍

devconst struct device *指向I2C Device的指针
msgsstruct i2c_msg *要传输的消息的msgs数组
num_msgsuint8_t要传输的msgs数组长度
addruint16_tI2C目标设备的地址

3.3 i2c_recover_bus

3.3.1 函数介绍

函数原型

作用

返回值

int i2c_recover_bus(const struct device *dev)恢复I2C总线,在I2C通讯时出现了异常错误导致后续无法正常通讯,可以使用这个API尝试恢复I2C,若不行需要重新断电复位0成功,非0失败

3.3.2 参数介绍

参数名

类型

介绍

devconst struct device *指向I2C Device的指针

3.4 i2c_slave_register

3.4.1 函数介绍

函数原型

作用

返回值

static inline int i2c_slave_register(const struct device *dev,  struct i2c_slave_config *cfg)将提供的配置注册为控制器的从属设备0成功,非0失败

3.4.2 参数介绍

参数名

类型

介绍

devconst struct device *指向I2C Device的指针
cfgstruct i2c_slave_config *带有I2C驱动程序使用的函数和参数的cfg Config struct

3.5 i2c_slave_unregister

3.5.1 函数介绍

函数原型

作用

返回值

static inline int i2c_slave_unregister(const struct device *dev, struct i2c_slave_config *cfg)将提供的配置注销为从属设备0成功,非0失败

3.5.2 参数介绍

参数名

类型

介绍

devconst struct device *指向I2C Device的指针
cfgstruct i2c_slave_config *带有I2C驱动程序使用的函数和参数的cfg Config struct

3.6 i2c_slave_driver_register

3.6.1 函数介绍

函数原型

作用

返回值

int i2c_slave_driver_register(const struct device *dev)指示I2C从设备将自身注册到I2C控制器0成功,非0失败

3.6.2 参数介绍

参数名

类型

介绍

devconst struct device *指向I2C Device的指针

3.7 i2c_slave_driver_unregister

3.7.1 函数介绍

函数原型

作用

返回值

int i2c_slave_driver_unregister(const struct device *dev);指示I2C从设备从I2C控制器注销自身0成功,非0失败

3.7.2 参数介绍

参数名

类型

介绍

devconst struct device *指向I2C Device的指针

3.8 i2c_write

3.8.1 函数介绍

函数原型

作用

返回值

static inline int i2c_write(const struct device *dev,  const uint8_t *buf, uint32_t num_bytes, uint16_t addr)将数据写入I2C设备0成功,非0失败

3.8.2 参数介绍

参数名

类型

介绍

devconst struct device *指向I2C Device的指针
bufconst uint8_t *传输数据的buf内存池
num_bytesuint32_t传输数据的buf大小
addruint16_tI2C设备地址

3.8.3 备注

此函数写入一次会产生起始与终止信号

3.9 i2c_read

3.9.1 函数原型

参数名

作用

返回值

static inline int i2c_read(const struct device *dev, uint8_t *buf, uint32_t num_bytes, uint16_t addr)从I2C设备读取一定数量的数据0成功,非0失败

3.9.2 参数介绍

参数名

类型

介绍

devconst struct device *指向I2C Device的指针
bufuint8_t *存储数据的buf内存池
num_bytesuint32_t存储数据的buf大小
addruint16_tI2C设备地址

3.9.3 备注

此函数读取一次会产生起始与终止信号

4.0 i2c_write_read

4.0.1 函数介绍

函数原型

作用

返回值

static inline int i2c_write_read(const struct device *dev, uint16_t addr, const void *write_buf, size_t num_write, void *read_buf, size_t num_read)从I2C设备写入然后读取数据0成功,非0失败

4.0.2 参数介绍

参数名

类型

介绍

devconst struct device *指向I2C Device的指针
addruint16_tI2C设备地址
write_bufconst void *指向要写入的数据的指针
num_writesize_t要写入的字节数
read_bufvoid *指向读取数据的存储器的指针
num_readsize_t要读取的字节数

4.0.3 备注

此函数只有在读取之后才会产生终止信号

4.1 i2c_burst_read

4.1.1 函数介绍

函数原型

作用

返回值

static inline int i2c_burst_read(const struct device *dev, uint16_t dev_addr, uint8_t start_addr, uint8_t *buf, uint32_t num_bytes)从I2C设备的内部地址读取多个字节0成功,非0失败

4.1.2 参数介绍

参数名

类型

介绍

devconst struct device *指向I2C Device的指针
dev_addruint16_tI2C设备地址
start_addruint8_t读取的地址
bufuint8_t *指向读取数据的存储器的指针
num_bytesuint32_t要读取的字节数

4.2  i2c_burst_write

4.2.1 函数介绍

函数原型

作用

返回值

static inline int i2c_burst_write(const struct device *dev, uint16_t dev_addr, uint8_t start_addr, const uint8_t *buf, uint32_t num_bytes)将多个字节写入I2C设备的内部地址。0成功,非0失败

4.2.2 参数介绍

参数名

类型

介绍

devconst struct device *指向I2C Device的指针
dev_addruint16_tI2C设备地址
start_addruint8_t写入的地址
bufuint8_t *指向写入数据的存储器的指针
num_bytesuint32_t要写入的字节数

4. I2C示例:读写与擦除”FM24V10 FLASH“

4.1 FM24V10 FLASH简介

FM24V10的FLASH是EEPROM 1MB的铁电工艺存储器,以下是它的特性简介

型号

容量

FLASH

特性

掉电存储时间

支持通讯协议

I2C地址

读写次数

最大频率

工作电压

工作温度

FM24V101MBEEPROM掉电不丢失十年I2C/SPI0x50100万亿次3.4MHZ2.0V-3.6V-40°C至+ 85°C

FM24V10相当于一个小型MCU,内部有一个FLASH,我们通过与它进行通讯来读写擦除它内部的FLASH,用来存储我们的数据,它支持I2C与SPI,这里我们可以通过I2C来控制。

FM24V10在传输读写时候要求前两个字节是地址位,地址高位与地址的低位

 有些I2C设备有限制,最大一次只能传输256个字节,所以在FM24V10里除去地址位,一次最大只能写入254个。

4.2 着手开发

以下开发时我对Zephry的I2C API进行封装了,为了我能更方便的调用。

4.2.1 基本头文件

#include <zephyr.h>
#include <sys/printk.h>
#include <device.h>
#include <drivers/i2c.h>
#include <stdlib.h>
#include <string.h>

4.2.2 用宏定义将设备信息定义出来

这些信息在FM24V10的介绍里都有提到

最后的宏函数是用于打包高位与低位地址的

#define FM24V10_FLASH       0x50            //i2c addr
#define I2C_DRIVE_NAME      "I2C_1"         //zephry i2c drive name
#define FM24V10_ADDR_NUM    2               //fm24v10 Address byte
#define ADDR_H              0               //addr height
#define ADDR_L              0               //addr low
#define BUF_LEN             8               //buff len
#define SER_START(startBuf,h,l) startBuf[0]=h;startBuf[1]=l;

 这里说一下如何知道自己I2C的驱动名称是什么

4.2.3 寻找自己的驱动名称

4.2.3.1 build目录下寻找

开启I2C之后并编译之后在build/zephry/zephry.dts里搜索i2c1可以找到如下定义

i2c1: arduino_i2c: i2c@40005400 {
      compatible = "st,stm32-i2c-v2";
      clock-frequency = < 0x61a80 >;
      #address-cells = < 0x1 >;
      #size-cells = < 0x0 >;
      reg = < 0x40005400 0x400 >;
      clocks = < &rcc 0x2 0x200000 >;
      interrupts = < 0x1f 0x0 >, < 0x20 0x0 >;
      interrupt-names = "event", "error";
      status = "okay";
      label = "I2C_1";
      pinctrl-0 = < &i2c1_scl_pb8 &i2c1_sda_pb9 >;
      bme280@76 {
          compatible = "bosch,bme280";
          status = "okay";
          label = "BME280";
          reg = < 0x76 >;
      };
  };

可以看到其中的label就是”I2C_1“

4.2.3.2 boards目录下寻找

在boards目录下找到你的板子型号,如我的是boards/arm/stm32f746g_disco

在这个文件夹下打开你的dts文件,然后找i2c

&i2c1 {
    pinctrl-0 = <&i2c1_scl_pb8 &i2c1_sda_pb9>;
    status = "okay";
    clock-frequency = <I2C_BITRATE_FAST>;
    bme280@76 {
        compatible = "bosch,bme280";
        status = "okay";
        label = "BME280";
        reg = <0x76>;
    };
};

可以看到这个i2c前面有一个”&“引用运算符,就代表这个是引用别的地方的,那么只有在头文件里了,我们可以一步一步分析头文件最终到”dts/arm/st/f7/stm32f745.dtsi“这个文件里

i2c4: i2c@40006000 {
           compatible = "st,stm32-i2c-v2";
           clock-frequency = <I2C_BITRATE_STANDARD>;
           #address-cells = <1>;
           #size-cells = <0>;
           reg = <0x40006000 0x400>;
           clocks = <&rcc STM32_CLOCK_BUS_APB1 0x01000000>;
           interrupts = <95 0>, <96 0>;
           interrupt-names = "event", "error";
           status = "disabled";
           label = "I2C_4";
       };

就可以看到最终的定义了。

4.2.3.3 添加overlay文件

在你的工程目录下新建目录”boards“,然后在这个目录下新建一个后缀名为”.overlay“的文件,然后在里面引用i2c,并添加label属性就可以了

&i2c1 {
    label="I2c_1"
};

4.2.4 定义I2C设备句柄

此代码模式为单列模式,所以定义一个全局的i2c设备句柄

//par
const struct device* i2c_dev = NULL;

4.2.5 init函数

这个函数的作用就是实例化i2c_dev设备指针,调用device_get_binding获取zephry i2c的设备驱动指针

成功返回0,否则返回非0

int fm24v10_init(){
 
    if(i2c_dev != NULL){
 
        return -1;
 
    }
 
    i2c_dev = device_get_binding(I2C_DRIVE_NAME);  if(i2c_dev == NULL) return -2;
 
    return 0;
 
}

4.2.6 ser_buf

此函数的作用是将高位地址与低位地址还有buff打包成一个新的buff

组合结构为:高位地址->低位地址→DATA 按照此顺序进行打包

void ser_buf(uint8_t* m_buff,uint8_t h,uint8_t l,uint8_t* buff,size_t len){
 
        memset(m_buff,0,len+FM24V10_ADDR_NUM);
        m_buff[0] = h;
        m_buff[1] = l;
        memcpy(m_buff+2,buff,len);
}

4.2.7 fm24v10_write

此函数会调用ser_buf进行打包,然后调用i2c_write发送出去,因为fm24v10要求前两位是高低地址位,所以前两个字节进行一次打包。

int fm24v10_write(uint8_t addr_height,uint8_t addr_low,uint8_t* buff,size_t len){
 
    //check ptr
    if(buff == NULL || i2c_dev == NULL) return -1;
 
    //check ready
    if(!device_is_ready(i2c_dev)) return -2;
 
    //serialize buff
    uint8_t m_buff[BUF_LEN+FM24V10_ADDR_NUM] = {0};
    ser_buf(m_buff,addr_height,addr_low,buff,len);
 
    //write
    if(i2c_write(i2c_dev,m_buff,len+FM24V10_ADDR_NUM,FM24V10_FLASH) !=0) return -4;
 
    return 0;
 
}

4.2.8 fm24v10_read

此函数的作用是读取数据,在读取时候需要先向FM24V10发送地址位,但是这期间不能有停止位,所以我们需要内部使用i2c_write_read这个函数来实现,它会先写在读取,期间不会产生停止位

int fm24v10_read(uint8_t addr_height,uint8_t addr_low,uint8_t* buff, size_t len){
 
    //check ptr
    if(buff == NULL || i2c_dev == NULL) return -1;
 
    //check ready
    if(!device_is_ready(i2c_dev)) return -2;
 
    //read
    uint8_t startBuf[FM24V10_ADDR_NUM] = {0};
    SER_START(startBuf,addr_height,addr_low);
    if(i2c_write_read(i2c_dev,FM24V10_FLASH,startBuf,FM24V10_ADDR_NUM,buff,len) != 0){
 
        return -3;
 
    }
 
    return 0;
 
}

4.2.9 printu

打印函数,因为zephry i2c下用uint8进行通讯,所以写一个函数用来打印,比较方便

void printu(uint8_t *buff,size_t len){
 
    for(int i = 0; i < len;++i){
 
        printk("%d",buff[i]);
 
    }
 
    printk("\\n");
 
}

4.3 main函数

main函数就是先初始化,然后写入,在读取

void main(void)
{
    printk("---------- FM240V10 Flash - I2C ----------\\n");
 
    if(fm24v10_init() != 0) {
 
        printk("Error fm240v10 init\\n");
        return;
    }
 
    uint8_t wrBuf[BUF_LEN] = {1,2,3,4,5,6,7,8};
    uint8_t rdBuf[BUF_LEN] = {0};
 
    if(fm24v10_write(ADDR_H,ADDR_L,wrBuf,BUF_LEN) != 0){
 
        printk("Error write flash\\n");
        return;
 
    }
 
    if(fm24v10_read(ADDR_H,ADDR_L,rdBuf,BUF_LEN) != 0){
 
        printk("Error read flash\\n");
        return;
 
    }
 
    printk("Write:");
    printu(wrBuf,BUF_LEN);
    printk(" Read:");
    printu(rdBuf,BUF_LEN);
 
    return;
 
}

4.3.1 完整代码

#include <zephyr.h>
#include <sys/printk.h>
#include <device.h>
#include <drivers/i2c.h>
#include <stdlib.h>
#include <string.h>
 
#define FM24V10_FLASH       0x50            //i2c addr
#define I2C_DRIVE_NAME      "I2C_1"         //zephry i2c drive name
#define FM24V10_ADDR_NUM    2               //fm24v10 Address byte
#define ADDR_H              0               //addr height
#define ADDR_L              0               //addr low
#define BUF_LEN             8               //buff len
#define SER_START(startBuf,h,l) startBuf[0]=h;startBuf[1]=l;
//par
const struct device* i2c_dev = NULL;
 
//func
int fm24v10_init(){                                                                                                    
    if(i2c_dev != NULL){
 
        return -1;
 
    }
 
    i2c_dev = device_get_binding(I2C_DRIVE_NAME);  if(i2c_dev == NULL) return -2;
 
    return 0;
 
}
 
void ser_buf(uint8_t* m_buff,uint8_t h,uint8_t l,uint8_t* buff,size_t len){
 
        memset(m_buff,0,len+FM24V10_ADDR_NUM);
        m_buff[0] = h;
        m_buff[1] = l;
        memcpy(m_buff+2,buff,len);
}
 
//write
int fm24v10_write(uint8_t addr_height,uint8_t addr_low,uint8_t* buff,size_t len){
 
    //check ptr
    if(buff == NULL || i2c_dev == NULL) return -1;
 
    //check ready
    if(!device_is_ready(i2c_dev)) return -2;
 
    //serialize buff
    uint8_t m_buff[BUF_LEN+FM24V10_ADDR_NUM] = {0};
    ser_buf(m_buff,addr_height,addr_low,buff,len);
 
    //write
    if(i2c_write(i2c_dev,m_buff,len+FM24V10_ADDR_NUM,FM24V10_FLASH) !=0) return -4;
 
    return 0;
 
}
 
//read
int fm24v10_read(uint8_t addr_height,uint8_t addr_low,uint8_t* buff, size_t len){
 
    //check ptr
    if(buff == NULL || i2c_dev == NULL) return -1;
 
    //check ready
    if(!device_is_ready(i2c_dev)) return -2;
 
    //read
    uint8_t startBuf[FM24V10_ADDR_NUM] = {0};
    SER_START(startBuf,addr_height,addr_low);
    if(i2c_write_read(i2c_dev,FM24V10_FLASH,startBuf,FM24V10_ADDR_NUM,buff,len) != 0){
 
        return -3;
 
    }                 
        return 0;
 
}
 
void printu(uint8_t *buff,size_t len){
 
    for(int i = 0; i < len;++i){
 
        printk("%d",buff[i]);
 
    }
 
    printk("\\n");
 
}
 
void main(void)
{
    printk("---------- FM240V10 Flash - I2C ----------\\n");
 
    if(fm24v10_init() != 0) {
 
        printk("Error fm240v10 init\\n");
        return;
    }
 
    uint8_t wrBuf[BUF_LEN] = {1,2,3,4,5,6,7,8};
    uint8_t rdBuf[BUF_LEN] = {0};
 
    if(fm24v10_write(ADDR_H,ADDR_L,wrBuf,BUF_LEN) != 0){
 
        printk("Error write flash\\n");
        return;
 
    }
 
    if(fm24v10_read(ADDR_H,ADDR_L,rdBuf,BUF_LEN) != 0){
 
        printk("Error read flash\\n");
        return;
 
    }
 
    printk("Write:");
    printu(wrBuf,BUF_LEN);
    printk(" Read:");
    printu(rdBuf,BUF_LEN);
 
    return;
 
}

4.3.2 运行结果

使用west编译并烧录到flash之后使用minicom可以看到如下输出:

Zephyr OS build zephyr-v2.6.0-1753-g365ff6db9f02  ***
---------- FM240V10 Flash - I2C ----------
Write:12345678
 Read:12345678

即代表正确向flash 0x0的起始地址写入8字节,并成功读取出来,因为是断电保存的,即便你断电后在从这个地址读取依然是这个字节。

4.3.3 擦除

其实擦除也很简单,因为I2C的限制,每次最大只能发送256个,这里我改成128,因为这样字节差上我比较好发送,FM24V10不支持擦除的指令,但是支持随机读写,所以擦除对于它们来说意义不大,其次1MB擦除比较耗时,所以一般情况下不建议写擦除功能,这里我写了一个可以用于参考

是以128字节为单位擦除,此函数会非常耗时,一般不建议写擦除,因为对于随机存储来说,擦除本身就没有意义。

void erase(){        
    //buff大小为130,前两位为地址位                                                                                                     
    uint8_t buff[130] = {0};
    //长度等于1mb除于128字节
    size_t len = 1048576/128;
    //每次递增时地址+128偏移
    for(uint16_t i = 0;i<len;i+=128){
        //地址从高位到低位写入到
        buff[0]=i >> 8;
        buff[1]=i & 0xff;
        i2c_write(i2c_dev,buff,130,FM24V10_FLASH);
    }
}

二、SPI Dirve

1. 开启方法

与I2C Dirve一样,在prj.conf文件中添加CONFIG来开启SPI Dirve

CONFIG_SPI=y

2. 使用方法

与I2C一致包含头文件即可

#include <drivers/spi.h>

3. 结构体介绍

3.1 结构体介绍

3.1.1 spi_config

3.1.1.1 结构体定义

struct spi_config {
    uint32_t        frequency;
    uint16_t        operation;
    uint16_t        slave;
 
    const struct spi_cs_control *cs;
};

3.1.1.2 成员介绍

成员名

类型

作用

frequencyuint32_t通讯频率,即时钟工作频率
operationuint16_t工作模式
slaveuint16_t从设备编号,下标从0开始

3.1.1.2.1 operation的额外介绍

operation比较特殊,SPI是通过设置CPOL和CPHA标志位来确定SPI的工作模式,可以通过设置这个值来控制

Zephry通过对它的BIT位进行设置来控制对于的功能,与GPIO的特殊功能寄存器一样,不同的位代表不同的功能

名称

BIT

作用

operational mode0设置主从,此位决定当前SPI是主还是从,可以取宏:SPI_OP_MODE_MASTER、SPI_OP_MODE_SLAVE
mode1 : 3CPOL与CPHA的工作模式
transfer4LSB或MSB优先,即大小端优先模式,若设置为MSB则优先传输高位,LSB则优先传输低位,主从必须对应
word_size5 : 10数据帧的大小(以位为单位),一次传输多少位,一般与SPI的数据寄存器大小一致
lines11 : 12SO线的工作模式,这个模式下面会介绍一下
cs_hold13是否需要让CS线处于HOLD状态,即SPI通讯断开后不要断开CS线
lock_on14启动锁模式,工作时候不可被打断,占用模式
cs_active_high15CS信号的逻辑电平,0代表发送片选CS信号时低电平有效,1即高电平有效

3.1.1.2.2 lines位的介绍

lines位是告诉zephry当前spi的通讯线是几根,每根线的作用是什么,根据实际情况设置

lines具有如下工作模式

协议

数据线数量及功能

通讯方式

Single SPI(标准SPI)1根发送,1根接收全双工
Dual SPI(双线SPI)收发共用2根数据线半双工
Qual SPI(四线SPI)收发共用4根数据线半双工
Octal SPI(八线SPI)收发共用8根数据线半双工

一般情况下都是默认的,不需要修改这个位

针对这些位的设置,Zephry为了方便我们设置,为我们提供了许多宏,可以直接对这些位进行设置,这样就省去了位操作运算,Zephry已经为我们封装好了

3.1.1.2.3 位设置宏

  • operational mode

名称

作用

SPI_OP_MODE_MASTER设置当前SPI为MASTER
SPI_OP_MODE_SLAVE设置当前SPI为SLAVE
SPI_OP_MODE_GET获取当前SPI是MASTER还是SLAVE
  • mode

名称

作用

SPI_MODE_CPOL设置CPOL为1,若要设置为0需要使用取反运算符!SPI_MODE_CPOL
SPI_MODE_CPHA设置CPHA为1,若要设置为0需要使用取反运算符!SPI_MODE_CPHA
SPI_MODE_LOOP此模式仅应用测试,所有发送的数据都会回到DR寄存器,用于测试是否已经发送出去了,且发送数据是否正确,此模式需要控制器支持
SPI_MODE_GET获取当前工作模式
  • transfer

名称

作用

SPI_TRANSFER_MSB高位优先传输
SPI_TRANSFER_LSB低位优先传输
  • word_size

名称

作用

SPI_WORD_SIZE_SHIFT设置每次传输5位
SPI_WORD_SET(_word_size_)设置自定义每次传输多少位,_word_size_是一个参数
SPI_WORD_SIZE_GET获取当前传输多少位
  • lines

名称

作用

名称

作用

SPI_LINES_SINGLESingle SPI(标准SPI)
SPI_LINES_DUALDual SPI(双线SPI)
SPI_LINES_QUADQual SPI(四线SPI)
SPI_LINES_OCTALOctal SPI(八线SPI)
  • cs_hold

名称

作用

SPI_HOLD_ON_CS使能CS HOLD,若要取消使能使用取反运算符:!SPI_HOLD_ON_CS
  • lock_on

名称

作用

SPI_LOCK_ON使能LOCK,若要取消使能使用取反运算符:!SPI_LOCK_ON
  • cs_active_high

名称

作用

SPI_CS_ACTIVE_HIGH片选信号为高电平,若要低电平则反:!SPI_CS_ACTIVE_HIGH

有些宏函数是不需要参数的,因为它们的值会根据operation高低位来设置,如SPI_OP_MODE_MASTER是0,刚好在赋值时对应operation的第0位

以上的宏设置需要使用C语言的或运算,比如设置一次传输8BIT,和当前SPI设备为主设备:

operation = SPI_WORD_SET(8) | SPI_OP_MODE_MASTER,		//设置一次传输8BIT,以及当前SPI为主设备

或运算的特性是进行或运算的BIT位有一个为1,则为1,利用这个特性我们可以实现用两个不不同BIT位的值进行或运算就可以设置不同的位了,这里假设第7位对应的是工作模式,第3位对应的是锁功能,我们想将第7位与第3位置1:

state = 64 | 4

底层过程如下:

state是8位的,其BIT位如下:

0 0 0 0 0 0 0 0

64的BIT位如下:

0 1 0 0 0 0 0 0

4的BIT位如下:

0 0 0 0 0 1 0 0

64 | 4

0 1 0 0 0 0 0 0

0 0 0 0 0 1 0 0

=

0 1 0 0 0 1 0 0

然后将这个值赋予给state,那么state的对应的BIT就成功被设置了

3.1.2  spi_buf_set

3.1.2.1 结构体定义

struct spi_buf_set {
    const struct spi_buf *buffers;
    size_t count;
};

3.1.2.2 成员介绍

成员名

类型

作用

buffersconst struct spi_buf传输时候的buff,需要注意的是这个变量需要在定义时完成初始化,因为它有const关键字
countsize_tspi_buf指向数组有多少个,这个值比较特殊,spi_buf指向的地址可能是一个队列,连续的,count用于表示spi_buf有几个

3.1.3 spi_buf

3.1.3.1 结构体定义

struct spi_buf {
    void *buf;
    size_t len;
};

3.1.3.2 成员介绍

成员名

类型

作用

bufvoid *要传输的缓冲区指针
lensize_t缓冲区数据长度

4. 主要API介绍

4.1 spi_write

4.1.1 函数介绍

函数原型

作用

返回值

static inline int spi_write(const struct device *dev, const struct spi_config *config, const struct spi_buf_set *tx_bufs)向SPI设备写入数据0成功,非0失败

4.1.2 参数介绍

参数名

类型

介绍

devconst struct device *指向SPI Device的指针
configconst struct spi_config *指向spi的配置属性的指针
tx_bufsconst struct spi_buf_set *指向spi buf的结构指针

4.2 spi_read

4.2.1 函数介绍

函数原型

作用

返回值

static inline int spi_read(const struct device *dev, const struct spi_config *config, const struct spi_buf_set *rx_bufs)读取SPI设备内容0成功,非0失败

4.2.2 参数介绍

参数名

类型

介绍

参数名

类型

介绍

devconst struct device *指向SPI Device的指针
configconst struct spi_config *指向spi的配置属性的指针
rx_bufsconst struct spi_buf_set *指向spi buf的结构指针

4.3 spi_transceive

4.3.1 函数介绍

函数原型作用返回值
int spi_transceive(const struct device *dev, const struct spi_config *config, const struct spi_buf_set *tx_bufs,const struct spi_buf_set *rx_bufs)写入或读取 I2C数据0成功,非0失败

4.3.2 参数介绍

参数名类型介绍
devconst struct device *指向SPI Device的指针
configconst struct spi_config *指向spi的配置属性的指针
tx_bufsconst struct spi_buf_set *指向spi写入buf的结构指针
rx_bufsconst struct spi_buf_set *指向spi接收buf的结构指针

4.3.3 备注

这个函数可以实现同时读写功能,spi_write与spi_read都基于这个函数实现,spi_write在调用时候会将rx设置为空,而spi_read调用时会将tx设置为空

5 简单的读写示例

SPI设备一般默认一次只能传输最大到255字节

5.1 写

//包含基本头文件
#include <zephyr.h>
#include <sys/printk.h>
#include <device.h>
#include <drivers/sensor.h>
#include <drivers/spi.h>
#include <stdlib.h>
#include <string.h>
//SPI最大传输字节
#define SPI_MAX_MSG_LEN 255
//传输时的buff
static uint8_t txmsg[SPI_MAX_MSG_LEN]={0,0};
//缓冲区指针
static struct spi_buf tx = {
    .buf = txmsg,
};
//传输结构体
const static struct spi_buf_set tx_bufs = {
    .buffers = &tx, //缓冲区指针
    .count=1,   //一个缓冲区指针
};
//配置项结构体
static struct spi_config spi_cfg = {
    .operation = SPI_WORD_SET(8) | SPI_OP_MODE_MASTER,		//设置一次传输8BIT,以及当前SPI为主设备
    .slave = 0,												//选择第0个设备
    .frequency = 1000000U,									//设置通讯频率
};

/*
 * 先判断SPI设备是否正常
 * 然后调用SPI写函数
 *
 */
void main(){
    //get bem device
    const struct device* dev = device_get_binding("SPI_2");
    if(dev == NULL){
 
        printk("ERR: No Device Drivers!\\n");
        return;
 
    }
 
    //check device
    if(!device_is_ready(dev)){
 
        printk("ERR: Device Was Not Ready - %s\\n",dev->name);
        return;
 
    }
 
    if(spi_write(dev,&spi_cfg,&tx_bufs) != 0){
        printk("Spi Write Error\\n");
        return;
    }
 
    printk("Spi Write Success\\n");
    return;
}

5.2 读

//包含基本头文件
#include <zephyr.h>
#include <sys/printk.h>
#include <device.h>
#include <drivers/sensor.h>
#include <drivers/spi.h>
#include <stdlib.h>
#include <string.h>
//SPI最大传输字节
#define SPI_MAX_MSG_LEN 255
//传输时的buff
static uint8_t rxmsg[SPI_MAX_MSG_LEN]={0,0};
//缓冲区指针
static struct spi_buf rx = {
    .buf = rxmsg,
};
//传输结构体
const static struct spi_buf_set rx_bufs = {
    .buffers = &tx, //缓冲区指针
    .count=1,   //一个缓冲区指针
};

//配置项结构体
static struct spi_config spi_cfg = {
    .operation = SPI_WORD_SET(8) | SPI_OP_MODE_MASTER,		//设置一次传输8BIT,以及当前SPI为主设备
    .slave = 0,												//选择第0个设备
    .frequency = 1000000U,									//设置通讯频率
};

/*
 * 先判断SPI设备是否正常
 * 然后调用SPI写函数
 *
 */
void main(){
    //get bem device
    const struct device* dev = device_get_binding("SPI_2");
    if(dev == NULL){
 
        printk("ERR: No Device Drivers!\\n");
        return;
 
    }
 
    //check device
    if(!device_is_ready(dev)){
 
        printk("ERR: Device Was Not Ready - %s\\n",dev->name);
        return;
 
    }
 
    if(spi_write(dev,&spi_cfg,&rx_bufs) != 0){
        printk("Spi Read Error\\n");
        return;
    }
 
    printk("Spi Read Success\\n");
    return;
}

以上是关于Zephry I2C和SPI驱动器介绍和操作FM24V10闪存的主要内容,如果未能解决你的问题,请参考以下文章

驱动之SPI,UART,I2C的介绍与应用20170118

nrf52832用I2C和SPI两种方式驱动LIS2DS12

[SPI&I2C]I2C和SPI协议介绍

BMP280 driver对接单片机I2C或者SPI总线接口

Zephry传感器模型介绍和bme240测试

Zephry传感器模型介绍和bme240测试