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.c中ch390_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, ð_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_mediator与ch390_mac_set_mediator
而ch390_phy_set_mediator与ch390_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, ð_config)) { ESP_LOGE(TAG, "init ch390 fail"); return; } esp_eth_handle_t eth_handle; ESP_ERROR_CHECK(esp_eth_driver_install(ð_config, ð_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, ð_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, "~~~~~~~~~~~"); }
主函数调用完后就能像正常程序一样调用了。
文章作者:沃航科技