• ESP8266 SPI 开发之软件驱动代码分析


     一 基本概述

       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:

  • 相关阅读:
    Tensorflow Tutorial 2: image classifier using convolutional neural network Part-1(译)
    TensorFlow Tutorial: Practical TensorFlow lesson for quick learners
    TensorFlow Tutorial: Practical TensorFlow lesson for quick learners
    Builder Pattern(译)
    Mysql锁机制
    Monitorenter
    非静态内部类
    ConcurrentModificationException
    Http2.0协议
    Docker化 springboot项目
  • 原文地址:https://www.cnblogs.com/dylancao/p/12883415.html
Copyright © 2020-2023  润新知