一 基本概述

esp8266的SPI代码流程非常的清晰,主要有三部分构成: spi_init 配置 spi_trans 配置 data_transfer 配置这三块组成。

在这里,笔者就针对spi的这些流程,做一个简单的源码分析。

一 初始化源码分析

spi 源码初始化函数中,主要是完成软硬件的接口配置和参数配置,我们看一下这里面都做了一些什么呢?

虽然代码不少,但是一个函数的核心代码也就那么多:

esp_err_t spi_init(spi_host_t host, spi_config_t *config)
这个函数的核心代码如下:
    spi_set_event_callback(host, &config->event_cb);
spi_set_mode(host, &config->mode);
spi_set_interface(host, &config->interface);
spi_set_clk_div(host, &config->clk_div);
spi_set_dummy(host, &dummy_bitlen);
spi_set_intr_enable(host, &config->intr_enable);
spi_intr_register(spi_intr, NULL);
spi_intr_enable();

这代码逻辑很清楚了:

spi_set_mode是设置是master模式还是slave模式。这里面主要涉两种模式的参数配置,记得改参数一定要用这两个啊:

核心代码如下所示:

    if (SPI_MASTER_MODE == *mode) {
// Set to Master mode
SPI[host]->pin.slave_mode = false;
SPI[host]->slave.slave_mode = false;
// Master uses the entire hardware buffer to improve transmission speed
SPI[host]->user.usr_mosi_highpart = false;
SPI[host]->user.usr_miso_highpart = false;
SPI[host]->user.usr_mosi = true;
// Create hardware cs in advance
SPI[host]->user.cs_setup = true;
// Hysteresis to keep hardware cs
SPI[host]->user.cs_hold = true;
SPI[host]->user.duplex = true;
SPI[host]->user.ck_i_edge = true;
SPI[host]->ctrl2.mosi_delay_num = 0;
SPI[host]->ctrl2.miso_delay_num = 1;
} else {
// Set to Slave mode
SPI[host]->pin.slave_mode = true;
SPI[host]->slave.slave_mode = true;
SPI[host]->user.usr_miso_highpart = true;
// MOSI signals are delayed by APB_CLK(80MHz) mosi_delay_num cycles
SPI[host]->ctrl2.mosi_delay_num = 2;
SPI[host]->ctrl2.miso_delay_num = 0;
SPI[host]->slave.wr_rd_sta_en = 1;
SPI[host]->slave1.status_bitlen = 31;
SPI[host]->slave1.status_readback = 0;
// Put the slave's miso on the highpart, so you can only send 256bits
// In Slave mode miso, mosi length is the same
SPI[host]->slave1.buf_bitlen = 255;
SPI[host]->cmd.usr = 1;
}

接下来是软硬件接口的配置,不同模式下接口参数不同,GPIO是不变的:

    switch (host) {
case CSPI_HOST: {
// Initialize SPI IO
PIN_PULLUP_EN(PERIPHS_IO_MUX_SD_CLK_U);
PIN_FUNC_SELECT(PERIPHS_IO_MUX_SD_CLK_U, FUNC_SPICLK); if (interface->mosi_en) {
PIN_PULLUP_EN(PERIPHS_IO_MUX_SD_DATA1_U);
PIN_FUNC_SELECT(PERIPHS_IO_MUX_SD_DATA1_U, FUNC_SPID_MOSI);
} if (interface->miso_en) {
PIN_PULLUP_EN(PERIPHS_IO_MUX_SD_DATA0_U);
PIN_FUNC_SELECT(PERIPHS_IO_MUX_SD_DATA0_U, FUNC_SPIQ_MISO);
} if (interface->cs_en) {
PIN_PULLUP_EN(PERIPHS_IO_MUX_SD_CMD_U);
PIN_FUNC_SELECT(PERIPHS_IO_MUX_SD_CMD_U, FUNC_SPICS0);
}
}
break; case HSPI_HOST: {
// Initialize HSPI IO
PIN_PULLUP_EN(PERIPHS_IO_MUX_MTMS_U);
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTMS_U, FUNC_HSPI_CLK); //GPIO14 is SPI CLK pin (Clock) if (interface->mosi_en) {
PIN_PULLUP_EN(PERIPHS_IO_MUX_MTCK_U);
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, FUNC_HSPID_MOSI); //GPIO13 is SPI MOSI pin (Master Data Out)
} if (interface->miso_en) {
PIN_PULLUP_EN(PERIPHS_IO_MUX_MTDI_U);
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDI_U, FUNC_HSPIQ_MISO); //GPIO12 is SPI MISO pin (Master Data In)
} if (interface->cs_en) {
PIN_PULLUP_EN(PERIPHS_IO_MUX_MTDO_U);
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDO_U, FUNC_HSPI_CS0);
}
}
break;
}

接下来是分频系数的设置,这个岁spi的速影响特别大,记住,spi的速率就靠它了:

esp_err_t spi_set_clk_div(spi_host_t host, spi_clk_div_t *clk_div)
            switch (host) {
case CSPI_HOST: {
SET_PERI_REG_MASK(PERIPHS_IO_MUX_CONF_U, SPI0_CLK_EQU_SYS_CLK);
}
break; case HSPI_HOST: {
SET_PERI_REG_MASK(PERIPHS_IO_MUX_CONF_U, SPI1_CLK_EQU_SYS_CLK);
}
break;
} SPI[host]->clock.clk_equ_sysclk = true;

接下来就是一些简单的流程了,就是配置中断并使能:

    spi_set_intr_enable(host, &config->intr_enable);
spi_intr_register(spi_intr, NULL);
spi_intr_enable();

二 配置源码分析

在配置源码中,最核心的函数就是:spi_trans(HSPI_HOST, trans);

在这个函数中,都做了一些什么呢?

    if (SPI_MASTER_MODE == spi_object[host]->mode) {
ret = spi_master_trans(host, trans);
} else {
ret = spi_slave_trans(host, trans);
}

看上面源码,就知道了,所有的核心处理都在函数:spi_xxx_trans里面了。

接下来,我们看这个函数都干了啥:

    // Set the cmd length and transfer cmd
if (trans.bits.cmd && trans.cmd) {
SPI[host]->user.usr_command = 1;
SPI[host]->user2.usr_command_bitlen = trans.bits.cmd - 1;
SPI[host]->user2.usr_command_value = *trans.cmd;
} else {
SPI[host]->user.usr_command = 0;
} // Set addr length and transfer addr
if (trans.bits.addr && trans.addr) {
SPI[host]->user.usr_addr = 1;
SPI[host]->user1.usr_addr_bitlen = trans.bits.addr - 1;
SPI[host]->addr = *trans.addr;
} else {
SPI[host]->user.usr_addr = 0;
} // Set mosi length and transmit mosi
if (trans.bits.mosi && trans.mosi) {
SPI[host]->user.usr_mosi = 1;
SPI[host]->user1.usr_mosi_bitlen = trans.bits.mosi - 1; for (x = 0; x < trans.bits.mosi; x += 32) {
y = x / 32;
SPI[host]->data_buf[y] = trans.mosi[y];
}
} else {
SPI[host]->user.usr_mosi = 0;
} // Set the length of the miso
if (trans.bits.miso && trans.miso) {
SPI[host]->user.usr_miso = 1;
SPI[host]->user1.usr_miso_bitlen = trans.bits.miso - 1;
} else {
SPI[host]->user.usr_miso = 0;
} // Call the event callback function to send a transfer start event
if (spi_object[host]->event_cb) {
spi_object[host]->event_cb(SPI_TRANS_START_EVENT, NULL);
} // Start transmission
SPI[host]->cmd.usr = 1;

大致看一下,就知道了,这个函数就是做数据传输配置的。

三 数据传输源码分析

在数据接收和发送里面,这有两个东西特别关键:

为了节省buffer空间和提升速率,这里使用了ring_buffer,这个在嵌入式中是一个非常实用的技巧,笔者见过几个系统都用过,非常的不错,有兴趣的同学可以学着写一个。

    // Write data to the ESP8266 Slave use "SPI_MASTER_WRITE_DATA_TO_SLAVE_CMD" cmd
trans.cmd = &cmd;
trans.addr = &addr;
trans.mosi = write_data;
cmd = SPI_MASTER_WRITE_DATA_TO_SLAVE_CMD;
addr = 0;
write_data[0] = 1;
write_data[1] = 0x11111111;
write_data[2] = 0x22222222;
write_data[3] = 0x33333333;
write_data[4] = 0x44444444;
write_data[5] = 0x55555555;
write_data[6] = 0x66666666;
write_data[7] = 0x77777777; while (1) {
gettimeofday(&now, NULL);
time_start = now.tv_usec;
for (x = 0;x < 100;x++) {
spi_trans(HSPI_HOST, trans);
write_data[0]++;
}
gettimeofday(&now, NULL);
time_end = now.tv_usec; ESP_LOGI(TAG, "Master wrote 3200 bytes in %d us", (int)(time_end - time_start));
vTaskDelay(100 / portTICK_RATE_MS);
}

其实,这段代码不是很复杂,估计很多人都能看懂的,真是代码写的特别好。

四 总结感想

SPI接口应用实在是太广泛了,后来笔者使用两个系统对通,验证基本没啥问题,后面就是上一下图了,接下来,就可以上我们的牛逼系统了,wifi图传,就看同的速率了。

硬件连线:

master log:

slave log:

ESP8266 SPI 开发之软件驱动代码分析的更多相关文章

  1. Linux时间子系统之(十七):ARM generic timer驱动代码分析

    专题文档汇总目录 Notes:ARM平台Clock/Timer架构:System counter.Timer以及两者之间关系:Per cpu timer通过CP15访问,System counter通 ...

  2. 三星framebuffer驱动代码分析

    一.驱动总体概述 本次的驱动代码是Samsung公司为s5pv210这款SoC编写的framebuffer驱动,对应于s5pv210中的内部外设Display Controller (FIMD)模块. ...

  3. [置顶] 自娱自乐7之Linux UDC驱动2(自编udc驱动,现完成枚举过程,从驱动代码分析枚举过程)

    花了半个月,才搞定驱动中的枚举部分,现在说linux的枚举,windows可能有差别. 代码我会贴在后面,现在只是实现枚举,你可能对代码不感兴趣,我就不分析代码了,你可以看看 在<自娱自乐1&g ...

  4. Linux时间子系统(十七) ARM generic timer驱动代码分析

    一.前言 关注ARM平台上timer driver(clocksource chip driver和clockevent chip driver)的驱动工程师应该会注意到timer硬件的演化过程.在单 ...

  5. platform总线驱动代码分析

    /************************************************************************/ Linux内核版本:2.6.35.7 运行平台:三 ...

  6. 基于等待队列及poll机制的按键驱动代码分析和测试代码

    按键驱动分析: #include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> ...

  7. USB转串口驱动代码分析

    1.USB插入时,创建设备 [plain] view plaincopy DriverObject->DriverExtension->AddDevice = USB2COM_PnPAdd ...

  8. 低功耗蓝牙4.0BLE编程-nrf51822开发(11)-蓝牙串口代码分析

    代码实例:点击打开链接 实现的功能是从uart口发送数据至另一个蓝牙串口,或是从蓝牙读取数据通过uart打印出数据. int main(void) { // Initialize leds_init( ...

  9. 支持阻塞操作和轮询操作的globalfifo设备驱动代码分析以及测试代码

    #include <linux/module.h> #include <linux/types.h> #include <linux/fs.h> #include ...

  10. 常用 Java 静态代码分析工具的分析与比较

    常用 Java 静态代码分析工具的分析与比较 简介: 本文首先介绍了静态代码分析的基 本概念及主要技术,随后分别介绍了现有 4 种主流 Java 静态代码分析工具 (Checkstyle,FindBu ...

随机推荐

  1. Tire树 学习笔记

    定义与基本求法 定义 又称字典树,用边表示字母,从根节点到树上某一节点路径形成一个字符串. 例如 \(charlie:\) 基本求法 廷显然的,往树中存就行了,查询也是显然的,通过一道例题来理解吧: ...

  2. ZR 七连 Day 1 游记

    ZR 七连 Day 1 游记 游记篇 赛前搞笑事件 今天是第一场正睿,还是要 好好对待 的 $ 17:59:58 $ 还在吃饭 $ 17:59:59 $ 做出重要决定,先打着比赛,有空就吃一口包子 $ ...

  3. MySQL-分区表和分区介绍

    一.MySQL分区简介 1.数据库分区 MySQL是一种常用的关系型数据库管理系统,分区表是一种在MySQL数据库中处理大规模数据的最佳方案之一,其主要目的是为了在特定的SQL操作中减少数据读写的总量 ...

  4. C语言程序设计之字符串处理

    C语言程序设计-字符串处理 第一题:回文数判断 问题描述] 回文是正读和倒读都一样的句子.读入一个最大长度不超过50个字符的句子,判断其是否是回文. [输入形式] 输入一个最大长度不超过50个字符的句 ...

  5. 从零开始的微信小程序入门教程(二),初识WXML与WXSS

    壹 ❀ 引 时隔大半年,我终于开始写小程序入门教程的第二篇了,其实我也在纳闷,这么久的时间我到底干了什么,仔细一想,我学了JavaScript部分进阶知识,学了ES6,系统性的去复习了angularj ...

  6. NC17871 CSL分苹果

    题目链接 题目 题目描述 CSL手上有n个苹果,第i个苹果的质量是wi,现在他想把这些苹果分给他的好朋友wavator和tokitsukaze.但是CSL为了不让他们打架,根据质量决定尽量地均分成两堆 ...

  7. Apache log4j2远程代码执行漏洞

    漏洞描述 Apache Log4j2是一个基于Java的日志记录工具.该工具重写了Log4j框架,并且引入了大量丰富的特性.该日志框架被大量用于业务系统开发,用来记录日志信息.大多数情况下,开发者可能 ...

  8. 【framework】IMS启动流程

    1 前言 ​ IMS 是 InputManagerService 的简称,主要负责输入事件管理. 1.1 基本概念 输入设备:屏幕.电源/音量.键鼠.充电口.蓝牙.wifi 等 设备节点:当输入设备可 ...

  9. SSIS数据同步系统

    缘起: 若干年前,刚到一家3C类的电商公司做DBA,到公司没几天,公司的CEO和研发总监,说现在要做个事情: 把IDC的数据库的数据,要同步一份到仓库,因单量大,仓库经常爆仓,仓库网络不好时,可以直接 ...

  10. Java控制语句

    1.介绍 从本质上讲,程序是一系列指令.控制结构是可以改变我们如何执行这些指令的代码块. 在本教程中,我们将探讨Java中的控制结构. 有三种控制结构: 条件分支,用于在两条或多条路径之间进行选择.J ...