13397158231   jevian_ma@worldflying.cn

esp32c3接入ch390h实现以太网上网的移植记录

2024-02-29 03:03:04

esp32c3是上海乐鑫公司基于risc-v架构推出的低成本无线soc,同时支持蓝牙与wifi,唯独不支持以太网。ch390h是南京沁恒公司出品的spi接口以太网phy+mac转换芯片。两家都是优秀的国产芯片厂商。沃航科技秉持的支持国产的精神,公司所有的产品全部采用国产芯片研发,近期由于项目需要传输协议使用tls加密,原方案由于芯片ram不足,无法实现,考虑多种方案后决定利用esp32c3配合ch390h实现,下面记录下esp32c3移植ch390h的过程,遇到的问题以及解决的方法:

一、重写spi函数

1.参考esp32的库文件,spi初始化函数改写为如下内容:

spi_bus_config_t buscfg = {
    .miso_io_num = g_miso_pin,
    .mosi_io_num = g_mosi_pin,
    .sclk_io_num = g_sck_pin,
    .quadwp_io_num = -1,
    .quadhd_io_num = -1,
};
// Initialize the SPI bus
ret = spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO);
if (ret != ESP_OK)
{
    ESP_LOGE(TAG, "spi_bus_initialize fail");
    return -2;
}
spi_device_interface_config_t devcfg = {
    .clock_speed_hz = 50 * 1000 * 1000, // Clock out at 50 MHz
    .mode = 0,                          // SPI mode 0
    .queue_size = 20,                   // We want to be able to queue 20 transactions at a time
    .spics_io_num = g_cs_pin,           // CS pin
    .command_bits = 0,
    .address_bits = 8,
};
ret = spi_bus_add_device(SPI2_HOST, &devcfg, &g_spi);
if (ret != ESP_OK)
{
    ESP_LOGE(TAG, "spi_bus_add_device fail");
    return -3;
}

这上面要注意的是command_bits设置为0且address_bits设置为8,这样就不发送cmd时序,只从address开始发送了,这是根据ch390h的spi规则来的。

2.重写spi写函数如下内容:

static esp_err_t ch390h_spi_write(uint32_t addr, const void *value, uint32_t len)
{
    spi_transaction_t trans = {
        .addr = addr,
        .length = 8 * len,
        .tx_buffer = value,
    };
    if (xSemaphoreTake(g_spi_lock, pdMS_TO_TICKS(500)))
    {
        esp_err_t ret = spi_device_polling_transmit(g_spi, &trans);
        xSemaphoreGive(g_spi_lock);
        if (ret != ESP_OK)
        {
            ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
            return ESP_FAIL;
        }
    }
    return ESP_OK;
}

注意在spi读写过程都加了一个全局锁g_spi_lock以保证读写不会发生冲突。

3.重写spi读函数如下内容:

static esp_err_t ch390h_spi_read(uint32_t addr, void *value, uint32_t len)
{
    spi_transaction_t trans = {
        .flags = len <= 4 ? SPI_TRANS_USE_RXDATA : 0, // use direct reads for registers to prevent overwrites by 4-byte boundary writes
        .addr = addr,
        .length = 8 * len,
        .rx_buffer = value,
    };
    if (xSemaphoreTake(g_spi_lock, pdMS_TO_TICKS(200)))
    {
        esp_err_t ret = spi_device_polling_transmit(g_spi, &trans);
        xSemaphoreGive(g_spi_lock);
        if (ret != ESP_OK)
        {
            ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
            return ESP_FAIL;
        }
    }
    if ((trans.flags & SPI_TRANS_USE_RXDATA) && len <= 4)
    {
        memcpy(value, trans.rx_data, len); // copy register values to output
    }
    return ESP_OK;
}

注意在spi读写过程都加了一个全局锁g_spi_lock以保证读写不会发生冲突。

4.重写读写寄存器/内存的函数

uint8_t ch390_read_reg(uint8_t reg)
{
    uint8_t value;
    ch390h_spi_read(reg | OPC_REG_R, &value, 1);
    return value;
}

void ch390_write_reg(uint8_t reg, uint8_t value)
{
    ch390h_spi_write(reg | OPC_REG_W, &value, 1);
}

void ch390_read_mem(uint8_t *data, int length)
{
    ch390h_spi_read(OPC_MEM_READ, data, length);
}

void ch390_write_mem(uint8_t *data, int length)
{
    ch390h_spi_write(OPC_MEM_WRITE, data, length);
}

二、修改南京沁恒提供的ch390h开发包中的CH390.c与CH390.h文件

1.删除ch390_write_reg、ch390_read_reg、ch390_write_mem与ch390_read_mem这四个函数的函数实现,删除ch390_interface_register与set_index的函数实现与定义,删除ch390_interface_t这个结构体的定义

2.将CH390.cch390_if.rst、ch390_if.delay_us替换成esp32c3支持的gpio_set_level以及vTaskDelay

三、创建如下全局变量用来存储ch390h的对象

static esp_eth_phy_t g_phy; // esp32c3 eth驱动框架要求的phy层控制器对象
static esp_eth_mac_t g_mac; // esp32c3 eth驱动框架要求的mac层控制器对象
static gpio_num_t g_rst_pin; // ch390h 复位引脚
static gpio_num_t g_int_pin; // ch390h 中断引脚
static gpio_num_t g_cs_pin; // ch390h spi片选引脚
static gpio_num_t g_sck_pin; // ch390h spi时钟引脚
static gpio_num_t g_miso_pin; // ch390h spi主机接收从机发送引脚
static gpio_num_t g_mosi_pin; // ch390h spi主机发送从机接收引脚
static spi_device_handle_t g_spi; // esp32c3 spi控制器对象
static SemaphoreHandle_t g_spi_lock; // freertos的spi锁对象,避免spi总线同时被两个task调用
static eth_link_t g_link_status; // 全局保存的网线状态
static esp_eth_mediator_t *g_mac_mediator; // esp32c3 eth驱动框架要求的mac层调用器对象,在驱动的回调中返回给我们
static esp_eth_mediator_t *g_phy_mediator; // esp32c3 eth驱动框架要求的phy层调用器对象,在驱动的回调中返回给我们
static TaskHandle_t rx_task_hdl; // freertos的task对象

四、实现初始化ch390h的函数

int esp_eth_init_ch390(gpio_num_t int_pin, gpio_num_t rst_pin, gpio_num_t cs_pin, gpio_num_t sck_pin, gpio_num_t miso_pin, gpio_num_t mosi_pin, esp_eth_config_t *config_out)
{
    g_phy.init = ch390_phy_init;                   // Initialize Ethernet PHY
    g_phy.reset_hw = ch390_phy_reset_hw;           // Hardware Reset Ethernet PHY
    g_phy.get_link = ch390_phy_get_link;           // Get Ethernet PHY link status
    g_phy.set_mediator = ch390_phy_set_mediator;   // Sets PHY speed mode
    g_phy.autonego_ctrl = ch390_phy_autonego_ctrl; // Configure auto negotiation
    g_mac.set_mediator = ch390_mac_set_mediator;   // Set mediator for Ethernet MAC
    g_mac.init = ch390_mac_init;                   // Initialize Ethernet MAC
    g_mac.set_link = ch390_mac_set_link;           // Set link status of MAC
    g_mac.set_addr = ch390_mac_set_addr;           // Set MAC address
    g_mac.get_addr = ch390_mac_get_addr;           // Get MAC address
    g_mac.transmit = ch390_mac_transmit;           // Transmit packet from Ethernet MAC
    g_rst_pin = rst_pin;
    g_int_pin = int_pin;
    g_cs_pin = cs_pin;
    g_sck_pin = sck_pin;
    g_miso_pin = miso_pin;
    g_mosi_pin = mosi_pin;
    g_link_status = ETH_LINK_DOWN;
    g_spi_lock = xSemaphoreCreateMutex();
    // Install GPIO ISR handler to be able to service SPI Eth modules interrupts
    esp_err_t ret = gpio_install_isr_service(0);
    if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE)
    {
        ESP_LOGE(TAG, "GPIO ISR handler install failed");
        esp_restart();
    }
    gpio_config_t c_rst_pin = {
        .pin_bit_mask = BIT64(g_rst_pin),
        .mode = GPIO_MODE_OUTPUT,
        .pull_up_en = GPIO_PULLUP_DISABLE,
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .intr_type = GPIO_INTR_DISABLE,
    };
    gpio_config(&c_rst_pin);
    gpio_config_t c_int_pin = {
        .pin_bit_mask = BIT64(g_int_pin),
        .mode = GPIO_MODE_INPUT,
        .pull_up_en = GPIO_PULLUP_DISABLE,
        .pull_down_en = GPIO_PULLDOWN_ENABLE,
        .intr_type = GPIO_INTR_POSEDGE,
    };
    gpio_config(&c_int_pin);
    gpio_isr_handler_add(g_int_pin, ch340h_isr_handler, NULL);
    // 创建一个新的task用来处理ch390h发来的gpio中断
    if (xTaskCreatePinnedToCore(ch390h_task, "ch390h_task", 2048, NULL, 15, &rx_task_hdl, tskNO_AFFINITY) != pdPASS)
    {
        ESP_LOGE(TAG, "create task ch390h_task fail");
        return -1;
    }
    spi_bus_config_t buscfg = {
        .miso_io_num = g_miso_pin,
        .mosi_io_num = g_mosi_pin,
        .sclk_io_num = g_sck_pin,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
    };
    // Initialize the SPI bus
    ret = spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "spi_bus_initialize fail");
        return -2;
    }
    spi_device_interface_config_t devcfg = {
        .clock_speed_hz = 50 * 1000 * 1000, // Clock out at 50 MHz
        .mode = 0,                          // SPI mode 0
        .queue_size = 20,                   // We want to be able to queue 20 transactions at a time
        .spics_io_num = g_cs_pin,           // CS pin
        .command_bits = 0,
        .address_bits = 8,
    };
    ret = spi_bus_add_device(SPI2_HOST, &devcfg, &g_spi);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "spi_bus_add_device fail");
        return -3;
    }
    ch390_hardware_reset();
    ch390_default_config(); // set ch390 to default config
    // Read and print ch390 ID
    uint16_t vid = ch390_get_vendor_id();
    uint16_t pid = ch390_get_product_id();
    ESP_LOGI(TAG, "ID: %04x%04x", vid, pid);
    esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(&g_mac, &g_phy);
    memcpy(config_out, &eth_config, sizeof(esp_eth_config_t));
    return 0;
}

五、实现驱动中会调用的函数

esp_err_t ch390_phy_init(esp_eth_phy_t *phy)
{
    return ESP_OK;
}

esp_err_t ch390_phy_reset_hw(esp_eth_phy_t *phy)
{
    return ESP_OK;
}

esp_err_t ch390_phy_get_link(esp_eth_phy_t *phy)
{
    eth_link_t link;
    if (ch390_get_link_status())
    {
        link = ETH_LINK_UP;
    }
    else
    {
        link = ETH_LINK_DOWN;
    }
    if (link != g_link_status)
    {
        g_phy_mediator->on_state_changed(g_phy_mediator, ETH_STATE_LINK, (void *)link);
        g_link_status = link;
    }
    return ESP_OK;
}

esp_err_t ch390_phy_set_mediator(esp_eth_phy_t *phy, esp_eth_mediator_t *mediator)
{
    g_phy_mediator = mediator;
    return ESP_OK;
}

esp_err_t ch390_phy_autonego_ctrl(esp_eth_phy_t *phy, eth_phy_autoneg_cmd_t cmd, bool *autonego_en_stat)
{
    return ESP_OK;
}

esp_err_t ch390_mac_init(esp_eth_mac_t *mac)
{
    return ESP_OK;
}

esp_err_t ch390_mac_set_mediator(esp_eth_mac_t *mac, esp_eth_mediator_t *eth)
{
    g_mac_mediator = eth;
    return ESP_OK;
}

esp_err_t ch390_mac_set_link(esp_eth_mac_t *mac, eth_link_t link)
{
    return ESP_OK;
}

esp_err_t ch390_mac_set_addr(esp_eth_mac_t *mac, uint8_t *addr)
{
    return ESP_OK;
}

esp_err_t ch390_mac_get_addr(esp_eth_mac_t *mac, uint8_t *addr)
{
    return ESP_OK;
}

esp_err_t ch390_mac_transmit(esp_eth_mac_t *mac, uint8_t *buf, uint32_t length)
{
    ch390_send_packet(buf, length);
    return ESP_OK;
}

大家可以看到,很多驱动函数都不需要做什么真实有意义的事情,只要实现一个函数体,保证程序调用不死机即可。

真正有价值的函数只有ch390_phy_get_link、ch390_mac_transmit、ch390_phy_set_mediatorch390_mac_set_mediator

ch390_phy_set_mediatorch390_mac_set_mediator也只是将调用器对象保存起来而已。

六、实现ch390h触发的中断服务

IRAM_ATTR static void ch340h_isr_handler(void *arg)
{
    BaseType_t high_task_wakeup = pdFALSE;
    vTaskNotifyGiveFromISR(rx_task_hdl, &high_task_wakeup);
    if (high_task_wakeup != pdFALSE)
    {
        portYIELD_FROM_ISR();
    }
}

static void ch390h_task(void *arg)
{
    while (1)
    {
        // check if the task receives any notification
        ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(10 * 1000));
        while (gpio_get_level(g_int_pin))
        {
            uint8_t int_status = ch390_get_int_status();
            if (int_status & ISR_LNKCHG)
            {
                ch390_phy_get_link(&g_phy);
            }
            if (int_status & ISR_ROS)
            {
                ESP_LOGE(TAG, "Receive overflow\r\n");
            }
            if (int_status & ISR_ROO)
            {
                ESP_LOGE(TAG, "Overflow counter overflow\r\n");
            }
            if (int_status & ISR_PR)
            {
                while (1)
                {
                    uint8_t *buf = (uint8_t *)malloc(ETH_MAX_PACKET_SIZE);
                    uint8_t rx_status;
                    uint32_t len = ch390_receive_packet(buf, &rx_status);
                    if (len == 0)
                    {
                        free(buf);
                        break;
                    }
                    g_mac_mediator->stack_input(g_mac_mediator, buf, len);
                }
            }
        }
    }
}

七、主函数调用驱动

// Init Ethernet driver to default and install it
esp_eth_config_t eth_config;
if (esp_eth_init_ch390(CH390_INT_PIN, CH390_RST_PIN, CH390_CS_PIN, CH390_SCK_PIN, CH390_MISO_PIN, CH390_MOSI_PIN, &eth_config))
{
    ESP_LOGE(TAG, "init ch390 fail");
    return;
}
esp_eth_handle_t eth_handle;
ESP_ERROR_CHECK(esp_eth_driver_install(&eth_config, &eth_handle));
uint8_t mac_addr[6];
ch390_get_mac(mac_addr);
ESP_LOGI(TAG, "Ethernet HW Addr %02x:%02x:%02x:%02x:%02x:%02x", mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
// Initialize TCP/IP network interface aka the esp-netif (should be called only once in application)
ESP_ERROR_CHECK(esp_netif_init());
// Create default event loop that running in background
ESP_ERROR_CHECK(esp_event_loop_create_default());

// Attach Ethernet driver to TCP/IP stack
esp_netif_config_t cfg = ESP_NETIF_DEFAULT_ETH();
esp_netif_t *eth_netif = esp_netif_new(&cfg);
ESP_ERROR_CHECK(esp_netif_attach(eth_netif, esp_eth_new_netif_glue(eth_handle)));

// Register user defined event handers
ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, &eth_event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &got_ip_event_handler, NULL));
ESP_ERROR_CHECK(esp_eth_start(eth_handle));

注册的以太网以及ip处理函数根据业务场景进行编写,下面是一个简单版本

// Event handler for Ethernet events
void eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
    // we can get the ethernet driver handle from event data
    switch (event_id)
    {
        case ETHERNET_EVENT_CONNECTED:
            ESP_LOGI(TAG, "Ethernet Link Up");
            break;
        case ETHERNET_EVENT_DISCONNECTED:
            ESP_LOGI(TAG, "Ethernet Link Down");
            break;
        case ETHERNET_EVENT_START:
            ESP_LOGI(TAG, "Ethernet Started");
            break;
        case ETHERNET_EVENT_STOP:
            ESP_LOGI(TAG, "Ethernet Stopped");
            break;
        default:
            ESP_LOGI(TAG, "Default Event");
            break;
    }
}

// Event handler for IP_EVENT_ETH_GOT_IP
static void got_ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
    ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
    const esp_netif_ip_info_t *ip_info = &event->ip_info;
    ESP_LOGI(TAG, "Ethernet Got IP Address");
    ESP_LOGI(TAG, "~~~~~~~~~~~");
    ESP_LOGI(TAG, "ETHIP:" IPSTR, IP2STR(&ip_info->ip));
    ESP_LOGI(TAG, "ETHMASK:" IPSTR, IP2STR(&ip_info->netmask));
    ESP_LOGI(TAG, "ETHGW:" IPSTR, IP2STR(&ip_info->gw));
    ESP_LOGI(TAG, "~~~~~~~~~~~");
}

主函数调用完后就能像正常程序一样调用了。


文章作者:沃航科技

优秀产品推荐:可编程网络IO控制器

上一篇:沃航硬件产品接入物联网平台说明

下一篇:沃航环境监控解决方案

联系我们

  • 地址:武汉市东湖高新开发区光谷总部国际1栋2412室
  • QQ:932773931
  • 电话:027-59761089-806
  • 手机:13397158231
  • 邮箱:jevian_ma@worldflying.cn

关注公众号

扫码添加微信

沃航(武汉)科技有限公司版权所有

备案号:鄂ICP备16014230号-1

本网站由提供CDN加速/云存储服务