一 基本概述

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. 飞桨paddlespeech语音唤醒推理C INT8 定点实现

    前面的文章(飞桨paddlespeech语音唤醒推理C定点实现)讲了INT16的定点实现.因为目前商用的语音唤醒方案推理几乎都是INT8的定点实现,于是我又做了INT8的定点实现. 实现前做了一番调研 ...

  2. 谁更适合搭配甜点显卡?i7-13700KF、锐龙7 7800X3D对比:游戏相当 生产力Intel强了50%

    一.前言:如果搭配2000元甜点显卡 i7-13700KF和锐龙7 7800X3D谁更有性价比? 现在AMD最受欢迎的处理器无疑是拥有96MB三级缓存的锐龙7 7800X3D,这是一颗专为游戏而生的处 ...

  3. ElasticSearch7.3学习(十七)----搜索结果字段解析及time_out字段解析

    1.搜索结果字段解析 首先插入一条测试数据 PUT /my_index/_doc/1 { "title": "2019-09-10" } 然后无条件搜索所有 G ...

  4. 【Flink入门修炼】1-2 Mac 搭建 Flink 源码阅读环境

    在后面学习 Flink 相关知识时,会深入源码探究其实现机制.因此,需要现在本地配置好源码阅读环境. 本文搭建环境: Mac M1(Apple Silicon) Java 8 IDEA Flink 官 ...

  5. Delphi 官方 MD5

    去官方文档搜就行了,引入System.Hash 单元: http://docwiki.embarcadero.com/Libraries/Athens/en/System.Hash.THashMD5 ...

  6. layui 的tab标签,选项卡的删除,是先切换,后删除,其实这样设计挺好的。

    layui的 tab关闭时,是先触发 切换事件,然后再触发删除事件,这一点留意,其实这样设计挺好的.那么留意点,就是先向主进程发送切换消息,然后再向主进程发送删除消息, 这样反而更加有利于,主进程代码 ...

  7. NC24734 [USACO 2010 Mar G]Great Cow Gathering

    题目链接 题目 题目描述 Bessie is planning the annual Great Cow Gathering for cows all across the country and, ...

  8. 未配置Datasource时, 启动 SpringBoot 程序报错的问题

    SpringBoot will show error if there is no datasource configuration in application.yml/application.pr ...

  9. 惠普CP1025 因转印离合器导致打印不全大片空白的问题

    问题症状 自检只打印出一部分, 后面大部分都是空白. 如果是碳盒缺粉, 应该是均匀地浅或者空白, 如果是成像鼓的问题, 应该是从上到下成条状的不均匀, 这样显示一节后空白的情况是没见过, 上网查有类似 ...

  10. Zabbix 配置笔记

    Zabbix Server 安装参考 https://www.cnblogs.com/clsn/p/7885990.html 安装脚本 #!/bin/bash #clsn #设置解析 注意:网络条件较 ...