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:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM