Compare commits

..

10 Commits

Author SHA1 Message Date
80478c7400 gxht30 2025-11-10 12:34:10 +06:00
e448282aed fix: correctly deinit rcc before hse sw 2025-11-10 12:00:21 +06:00
6e29b34542 120mhz clock 2025-11-10 02:55:12 +06:00
7877bb42f4 clean up header file 2025-11-09 21:08:08 +06:00
fd3d66e424 tweak lwipopts.h 2025-11-09 20:27:31 +06:00
a0ca17e68f 48k -> 20k RAM 2025-11-09 13:00:16 +06:00
5445c27271 fix: add tx queue 2025-11-09 12:15:13 +06:00
06012c613a proper RX DMA 2025-11-08 21:14:56 +06:00
e814012f09 get RXIF to work 2025-11-08 21:01:04 +06:00
fcd60d1eb8 readme 2025-11-08 20:39:52 +06:00
12 changed files with 736 additions and 203 deletions

View File

@@ -38,6 +38,10 @@
"vector": "c",
"memory_resource": "c",
"__config": "c",
"string": "c"
"string": "c",
"atomic": "c",
"__bit_reference": "c",
"err.h": "c",
"httpd.h": "c"
}
}

View File

@@ -23,9 +23,9 @@ LWIP_C_FILES += $(NETIFFILES)
LWIP_C_FILES += $(HTTPFILES)
LWIP_C_FILES_WITH_PATH := $(LWIP_C_FILES)
LWIP_PORT_FILES := $(wildcard $(PORT_DIR)/*.c)
LWIP_PORT_FILES := $(wildcard $(PORT_DIR)/*.c $(PORT_DIR)/arch/*.c)
INCLUDE_DIRS ?= \
INCLUDE_DIRS += \
-I./inc \
-I$(LWIP_DIR)/src/include \
-I$(PORT_DIR)

36
README.md Normal file
View File

@@ -0,0 +1,36 @@
# lwIP Ethernet Driver for CH32V208
This is a simple ethernetif.c driver to get lwIP working on the WCH CH32V208 MCU using the ch32fun lib.
It uses the chip's internal 10Mbps Ethernet MAC. The MAC address is pulled from the chip's 6-byte unique ID.
The provided main.c is an example that starts up, gets an IP address via DHCP, and runs a small HTTP server.
## Usage
1. Initialize lwIP and add the network interface.
2. Poll the driver and service lwIP's timers in your main loop.
```c
netif_add(&g_netif, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &ethernet_input);
netif_set_default(&g_netif);
netif_set_up(&g_netif);
dhcp_start(&g_netif);
while (1) {
// poll for incoming packets
ethernetif_input(&g_netif);
// handle lwIP timers (for TCP, DHCP, etc.)
sys_check_timeouts();
// poll link for link up/down cb
ethernetif_link_poll(&g_netif);
}
```
## Impl note
This driver is kinda functional but not optimized
- **Packet RX:** ~~This is done by polling~~ RXIF works now. You must call `ethernetif_input()` continuously in your main loop to check for and process incoming packets
- **Packet TX:** TX isn't exactly typical DMA? The CPU has to copy the packet into a single transmit buffer and then manually start the transmission. An ISR will signal when the buffer is free to send the next packet

View File

@@ -1,9 +1,9 @@
#ifndef _FUNCONFIG_H
#define _FUNCONFIG_H
#define FUNCONF_USE_HSE 1
// #define FUNCONF_USE_HSE 1
#define FUNCONF_SYSTEM_CORE_CLOCK 120000000
#define FUNCONF_PLL_MULTIPLIER 15
// #define FUNCONF_PLL_MULTIPLIER 15
#define FUNCONF_SYSTICK_USE_HCLK 1
#endif

295
inc/gxht30_hw_i2c.h Normal file
View File

@@ -0,0 +1,295 @@
#ifndef _GXHT30_CH32_HW_I2C_H
#define _GXHT30_CH32_HW_I2C_H
#include <stdbool.h>
#include <stdint.h>
#include "ch32fun.h"
#include "ch32v20xhw.h"
// I2C Configuration
#define GXHT30_I2C_CLKRATE 400000
#define GXHT30_I2C_PRERATE 2000000
#define GXHT30_I2C_TIMEOUT_MAX 250000
// GXHT30 I2C Addresses
#define GXHT30_I2C_ADDR_DEFAULT 0x44
#define GXHT30_I2C_ADDR_ALT 0x45
// Commands
#define GXHT30_CMD_MEAS_MSB 0x2C
#define GXHT30_CMD_MEAS_LSB 0x06
#define GXHT30_CMD_SOFT_RESET_MSB 0x30
#define GXHT30_CMD_SOFT_RESET_LSB 0xA2
#define GXHT30_CMD_HEATER_ON_MSB 0x30
#define GXHT30_CMD_HEATER_ON_LSB 0x6D
#define GXHT30_CMD_HEATER_OFF_MSB 0x30
#define GXHT30_CMD_HEATER_OFF_LSB 0x66
#define GXHT30_CMD_STATUS_MSB 0xF3
#define GXHT30_CMD_STATUS_LSB 0x2D
#define GXHT30_CMD_CLEAR_STATUS_MSB 0x30
#define GXHT30_CMD_CLEAR_STATUS_LSB 0x41
// I2C Event Masks
#define GXHT30_I2C_EVT_MASTER_MODE_SELECT \
((uint32_t)0x00030001) // BUSY, MSL, SB
#define GXHT30_I2C_EVT_MASTER_TRANSMITTER_MODE \
((uint32_t)0x00070082) // BUSY, MSL, ADDR, TXE, TRA
#define GXHT30_I2C_EVT_MASTER_RECEIVER_MODE \
((uint32_t)0x00030002) // BUSY, MSL, ADDR
#define GXHT30_I2C_EVT_MASTER_BYTE_TRANSMITTED \
((uint32_t)0x00070084) // TRA, BUSY, MSL, TXE, BTF
#define GXHT30_I2C_EVT_MASTER_BYTE_RECEIVED \
((uint32_t)0x00030040) // BUSY, MSL, RXNE
// Sensor Data Structure
typedef struct {
float temperature; // Temperature in Celsius
float humidity; // Relative humidity in %
uint8_t error; // Last error code
} GXHT30_Data;
// Status Register Structure
typedef struct {
uint8_t alert_pending; // Bit 15: Alert status
uint8_t heater_on; // Bit 13: Heater status
uint8_t humidity_alert; // Bit 11: Humidity tracking alert
uint8_t temperature_alert; // Bit 10: Temperature tracking alert
uint8_t reset_detected; // Bit 4: System reset detected
uint8_t command_status; // Bit 1: Last command execution status
uint8_t crc_status; // Bit 0: Write data CRC checksum status
uint16_t raw_status; // Raw 16-bit status value
} GXHT30_Status;
// Error Codes
enum GXHT30_Error {
GXHT30_OK = 0,
GXHT30_ERR_TIMEOUT,
GXHT30_ERR_CRC,
GXHT30_ERR_I2C,
GXHT30_ERR_BUSY
};
static inline uint8_t _gxht30_i2c_check_event(uint32_t event_mask) {
uint32_t status = I2C1->STAR1 | (I2C1->STAR2 << 16);
return (status & event_mask) == event_mask;
}
static inline uint8_t _gxht30_wait_event(uint32_t event_mask) {
int32_t timeout = GXHT30_I2C_TIMEOUT_MAX;
while (!_gxht30_i2c_check_event(event_mask) && (timeout-- > 0));
return timeout > 0 ? GXHT30_OK : GXHT30_ERR_TIMEOUT;
}
static inline uint8_t _gxht30_wait_flag(uint32_t flag) {
int32_t timeout = GXHT30_I2C_TIMEOUT_MAX;
while (!(I2C1->STAR1 & flag) && (timeout-- > 0));
return timeout > 0 ? GXHT30_OK : GXHT30_ERR_TIMEOUT;
}
static inline uint8_t _gxht30_i2c_start(uint8_t addr, uint8_t direction) {
// wait until bus is not busy
int32_t timeout = GXHT30_I2C_TIMEOUT_MAX;
while ((I2C1->STAR2 & I2C_STAR2_BUSY) && (timeout-- > 0));
if (timeout <= 0) return GXHT30_ERR_TIMEOUT;
// gen START
I2C1->CTLR1 |= I2C_CTLR1_START;
if (_gxht30_wait_event(GXHT30_I2C_EVT_MASTER_MODE_SELECT) != GXHT30_OK)
return GXHT30_ERR_TIMEOUT;
// send addr
I2C1->DATAR = (addr << 1) | direction;
uint32_t event = (direction == 0) ? GXHT30_I2C_EVT_MASTER_TRANSMITTER_MODE
: GXHT30_I2C_EVT_MASTER_RECEIVER_MODE;
return _gxht30_wait_event(event);
}
static inline uint8_t _gxht30_i2c_write_byte(uint8_t data) {
I2C1->DATAR = data;
return _gxht30_wait_flag(I2C_STAR1_TXE);
}
static inline uint8_t _gxht30_i2c_read(uint8_t* buffer, uint8_t length) {
for (uint8_t i = 0; i < length; i++) {
if (i == length - 1) {
I2C1->CTLR1 &= ~I2C_CTLR1_ACK; // NACK last byte
}
if (_gxht30_wait_flag(I2C_STAR1_RXNE) != GXHT30_OK) {
I2C1->CTLR1 |= I2C_CTLR1_ACK;
return GXHT30_ERR_TIMEOUT;
}
buffer[i] = I2C1->DATAR;
}
I2C1->CTLR1 |= I2C_CTLR1_ACK; // re-enable ACK
return GXHT30_OK;
}
static inline bool _gxht30_crc8_check(uint8_t msb, uint8_t lsb, uint8_t crc) {
uint8_t calc_crc = 0xFF;
uint8_t data[2] = {msb, lsb};
for (uint8_t byte = 0; byte < 2; byte++) {
calc_crc ^= data[byte];
for (uint8_t i = 0; i < 8; i++) {
calc_crc = (calc_crc & 0x80) ? (calc_crc << 1) ^ 0x31 : (calc_crc << 1);
}
}
return calc_crc == crc;
}
// init I2C hw
static inline void gxht30_i2c_init(void) {
uint16_t tempreg;
RCC->APB2PCENR |= RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO;
RCC->APB1PCENR |= RCC_APB1Periph_I2C1;
// PB6 (SCL) and PB7 (SDA)
GPIOB->CFGLR &= ~(0xff << (4 * 6));
GPIOB->CFGLR |= ((GPIO_Speed_10MHz | GPIO_CNF_OUT_OD_AF) << (4 * 6)) |
((GPIO_Speed_10MHz | GPIO_CNF_OUT_OD_AF) << (4 * 7));
// rst I2C1
RCC->APB1PRSTR |= RCC_APB1Periph_I2C1;
RCC->APB1PRSTR &= ~RCC_APB1Periph_I2C1;
// i2c frequency
tempreg = I2C1->CTLR2;
tempreg &= ~I2C_CTLR2_FREQ;
tempreg |= (FUNCONF_SYSTEM_CORE_CLOCK / GXHT30_I2C_PRERATE) & I2C_CTLR2_FREQ;
I2C1->CTLR2 = tempreg;
// Fast Mode 400kHz
tempreg =
(FUNCONF_SYSTEM_CORE_CLOCK / (3 * GXHT30_I2C_CLKRATE)) & I2C_CKCFGR_CCR;
tempreg |= I2C_CKCFGR_FS;
I2C1->CKCFGR = tempreg;
// en I2C and ACK
I2C1->CTLR1 |= I2C_CTLR1_PE | I2C_CTLR1_ACK;
}
static inline uint8_t gxht30_send_command(uint8_t addr, uint8_t cmd_msb,
uint8_t cmd_lsb) {
uint8_t err;
if ((err = _gxht30_i2c_start(addr, 0)) != GXHT30_OK) return err;
if ((err = _gxht30_i2c_write_byte(cmd_msb)) != GXHT30_OK) return err;
if ((err = _gxht30_i2c_write_byte(cmd_lsb)) != GXHT30_OK) return err;
if (_gxht30_wait_event(GXHT30_I2C_EVT_MASTER_BYTE_TRANSMITTED) != GXHT30_OK)
return GXHT30_ERR_TIMEOUT;
I2C1->CTLR1 |= I2C_CTLR1_STOP;
return GXHT30_OK;
}
// read temp and humidity
static inline uint8_t gxht30_read_data(uint8_t addr, GXHT30_Data* data) {
uint8_t rx_data[6];
uint8_t err;
if ((err = gxht30_send_command(addr, GXHT30_CMD_MEAS_MSB,
GXHT30_CMD_MEAS_LSB)) != GXHT30_OK) {
data->error = err;
return err;
}
// read data
if ((err = _gxht30_i2c_start(addr, 1)) != GXHT30_OK) {
data->error = err;
return err;
}
if ((err = _gxht30_i2c_read(rx_data, 6)) != GXHT30_OK) {
data->error = err;
return err;
}
I2C1->CTLR1 |= I2C_CTLR1_STOP;
// verify crc
if (!_gxht30_crc8_check(rx_data[0], rx_data[1], rx_data[2]) ||
!_gxht30_crc8_check(rx_data[3], rx_data[4], rx_data[5])) {
data->error = GXHT30_ERR_CRC;
return GXHT30_ERR_CRC;
}
// calc values
uint16_t temp_raw = (rx_data[0] << 8) | rx_data[1];
uint16_t hum_raw = (rx_data[3] << 8) | rx_data[4];
data->temperature = (float)temp_raw * 0.00267033f - 45.0f;
data->humidity = (float)hum_raw * 0.0015259f;
data->error = GXHT30_OK;
return GXHT30_OK;
}
static inline uint8_t gxht30_read_status(uint8_t addr, GXHT30_Status* status) {
uint8_t rx_data[3];
uint8_t err;
if ((err = _gxht30_i2c_start(addr, 0)) != GXHT30_OK) return err;
if ((err = _gxht30_i2c_write_byte(GXHT30_CMD_STATUS_MSB)) != GXHT30_OK)
return err;
if ((err = _gxht30_i2c_write_byte(GXHT30_CMD_STATUS_LSB)) != GXHT30_OK)
return err;
if (_gxht30_wait_event(GXHT30_I2C_EVT_MASTER_BYTE_TRANSMITTED) != GXHT30_OK)
return GXHT30_ERR_TIMEOUT;
I2C1->CTLR1 |= I2C_CTLR1_START;
if (_gxht30_wait_event(GXHT30_I2C_EVT_MASTER_MODE_SELECT) != GXHT30_OK)
return GXHT30_ERR_TIMEOUT;
I2C1->DATAR = (addr << 1) | 0x01;
if (_gxht30_wait_event(GXHT30_I2C_EVT_MASTER_RECEIVER_MODE) != GXHT30_OK)
return GXHT30_ERR_TIMEOUT;
if ((err = _gxht30_i2c_read(rx_data, 3)) != GXHT30_OK) return err;
I2C1->CTLR1 |= I2C_CTLR1_STOP;
if (!_gxht30_crc8_check(rx_data[0], rx_data[1], rx_data[2]))
return GXHT30_ERR_CRC;
uint16_t raw = (rx_data[0] << 8) | rx_data[1];
status->raw_status = raw;
status->alert_pending = (raw >> 15) & 0x01;
status->heater_on = (raw >> 13) & 0x01;
status->humidity_alert = (raw >> 11) & 0x01;
status->temperature_alert = (raw >> 10) & 0x01;
status->reset_detected = (raw >> 4) & 0x01;
status->command_status = (raw >> 1) & 0x01;
status->crc_status = raw & 0x01;
return GXHT30_OK;
}
static inline uint8_t gxht30_soft_reset(uint8_t addr) {
return gxht30_send_command(addr, GXHT30_CMD_SOFT_RESET_MSB,
GXHT30_CMD_SOFT_RESET_LSB);
}
static inline uint8_t gxht30_heater_on(uint8_t addr) {
return gxht30_send_command(addr, GXHT30_CMD_HEATER_ON_MSB,
GXHT30_CMD_HEATER_ON_LSB);
}
static inline uint8_t gxht30_heater_off(uint8_t addr) {
return gxht30_send_command(addr, GXHT30_CMD_HEATER_OFF_MSB,
GXHT30_CMD_HEATER_OFF_LSB);
}
static inline uint8_t gxht30_clear_status(uint8_t addr) {
return gxht30_send_command(addr, GXHT30_CMD_CLEAR_STATUS_MSB,
GXHT30_CMD_CLEAR_STATUS_LSB);
}
#endif // _GXHT30_CH32_HW_I2C_H

167
main.c
View File

@@ -3,6 +3,7 @@
#include "ch32fun.h"
#include "ch32v20xhw.h"
#include "ethernetif.h"
#include "gxht30_hw_i2c.h"
#include "lwip/apps/httpd.h"
#include "lwip/dhcp.h"
#include "lwip/init.h"
@@ -25,49 +26,69 @@
#define PLL_MULTIPLIER 15
#define STATS_PRINT_INTERVAL_MS 10000
#define SENSOR_READ_INTERVAL_MS 5000
#define STATUS_READ_INTERVAL_MS 30000
struct netif g_netif;
static volatile int g_httpd_is_initialized = 0;
int clock_init(void);
void led_init(void);
void lwip_stack_init(void);
int clock_init(void) {
RCC->INTR = 0x009f0000;
RCC->CTLR &= ~(RCC_HSE_ON | RCC_PLLON);
RCC->CFGR0 = 0x00000000;
static void set_sysclk_to_120mhz_from_hse(void) {
uint32_t startup_counter = 0;
RCC->CTLR |= RCC_HSE_ON;
for (int timeout = HSE_STARTUP_TIMEOUT; timeout > 0; timeout--) {
if (RCC->CTLR & RCC_HSERDY) break;
if (timeout == 1) {
printf("Error: HSE failed to start\n");
return -1;
}
}
RCC->INTR = 0x009F0000; // clear PLL, CSSC, HSE, HSI and LSI ready flags.
// switch processor back to HSI so we don't eat dirt.
RCC->CFGR0 = 0;
// disable PLL so we can play with it.
RCC->CTLR &= ~RCC_PLLON;
// not sure why, need to reset here, otherwise PLLXTPRE is set.
RCC->CFGR0 = RCC_PLLSRC;
RCC->CFGR0 |= (uint32_t)RCC_PPRE1_DIV2;
RCC->CFGR2 = (PREDIV1_DIVISOR - 1);
RCC->CFGR0 |= RCC_PLLSource_HSE_Div1 | RCC_PLLMul_15;
// enable HSE
RCC->CTLR |= RCC_HSEON;
do {
startup_counter++;
} while (!(RCC->CTLR & RCC_HSERDY) &&
(startup_counter < HSE_STARTUP_TIMEOUT));
if (RCC->CTLR & RCC_HSERDY) {
/*
* HCLK (AHB) = SYSCLK / 2
* PCLK2 (APB2) = HCLK / 1
* PCLK1 (APB1) = HCLK / 2
* USB Clock = PLLCLK / 5 (to get 48MHz for USB)
*/
RCC->CFGR0 |=
RCC_HPRE_DIV2 | RCC_PPRE2_DIV1 | RCC_PPRE1_DIV2 | RCC_USBPRE_DIV5;
/*
* When RCC_USBPRE is 3 it changes HPE div to /2 instead of /4, so:
* PLL Source: HSE
* HSE Divider for PLL: /2
* PLL Multiplier: x15
* PLL Clock = (32MHz / 2) * 15 = 240 MHz
*/
uint32_t pll_config = RCC_PLLSRC_HSE | RCC_PLLXTPRE_HSE | RCC_PLLMULL15;
RCC->CFGR0 =
(RCC->CFGR0 & ~(RCC_PLLSRC | RCC_PLLXTPRE | RCC_PLLMULL)) | pll_config;
RCC->CTLR |= RCC_PLLON;
printf("Main PLL en. Waiting for lock...\n");
for (int timeout = PLL_LOCK_TIMEOUT; timeout > 0; timeout--) {
if (RCC->CTLR & RCC_PLLRDY) break;
if (timeout == 1) {
printf("Error: Main PLL lock failed\n");
return -1;
}
}
printf("Main PLL Locked\n");
// wait for pll to lock
while (!(RCC->CTLR & RCC_PLLRDY)) {
}
// PLL as the system clock src
RCC->CFGR0 = (RCC->CFGR0 & ~RCC_SW) | RCC_SW_PLL;
while ((RCC->CFGR0 & RCC_SWS) != RCC_SWS_PLL);
printf("System clock set to %dMHz.\n",
(HSE_CLOCK_MHZ / PREDIV1_DIVISOR) * PLL_MULTIPLIER);
return 0;
while ((RCC->CFGR0 & RCC_SWS) != RCC_SWS_PLL) {
}
} else {
printf("HSE failed to start\n");
}
}
void led_init(void) {
@@ -159,16 +180,18 @@ void ethernetif_print_stats(void) {
int main() {
SystemInit();
if (clock_init() != 0) {
// eating dirt?
while (1);
}
set_sysclk_to_120mhz_from_hse();
print_clock_registers();
systick_init();
led_init();
lwip_stack_init();
gxht30_i2c_init();
gxht30_soft_reset(GXHT30_I2C_ADDR_DEFAULT);
Delay_Ms(10);
uint32_t last_led_toggle_time = 0;
uint32_t last_link_poll_time = 0;
#if LWIP_STATS
@@ -176,6 +199,14 @@ int main() {
#endif
int led_state = 0;
uint32_t last_sensor_read_time = 0;
uint32_t last_status_read_time = 0;
GXHT30_Data sensor_data = {0};
GXHT30_Status sensor_status = {0};
printf("GXHT30 Sensor initialized\n");
while (1) {
ethernetif_input(&g_netif);
sys_check_timeouts();
@@ -192,15 +223,69 @@ int main() {
}
#endif
uint32_t now = millis();
if (now - last_led_toggle_time > LED_TOGGLE_INTERVAL_MS) {
if (led_state) {
GPIOA->BSHR = (1 << LED1_PIN);
// Read sensor data periodically
if (millis() - last_sensor_read_time > SENSOR_READ_INTERVAL_MS) {
if (gxht30_read_data(GXHT30_I2C_ADDR_DEFAULT, &sensor_data) ==
GXHT30_OK) {
int16_t temp_int = (int16_t)(sensor_data.temperature * 100);
int16_t hum_int = (int16_t)(sensor_data.humidity * 100);
int16_t temp_whole = temp_int / 100;
int16_t temp_decimal = temp_int % 100;
if (temp_decimal < 0) temp_decimal = -temp_decimal;
int16_t hum_whole = hum_int / 100;
int16_t hum_decimal = hum_int % 100;
printf("Temperature: %d.%02d C | Humidity: %d.%02d %%\n", temp_whole,
temp_decimal, hum_whole, hum_decimal);
} else {
GPIOA->BSHR = (1 << (LED1_PIN + 16));
printf("Sensor error: %d\n", sensor_data.error);
}
led_state = !led_state;
last_led_toggle_time = now;
last_sensor_read_time = millis();
}
// Read status register periodically
if (millis() - last_status_read_time > STATUS_READ_INTERVAL_MS) {
if (gxht30_read_status(GXHT30_I2C_ADDR_DEFAULT, &sensor_status) ==
GXHT30_OK) {
printf("Status: ");
if (sensor_status.alert_pending) printf("ALERT ");
if (sensor_status.heater_on) printf("HEATER_ON ");
if (sensor_status.humidity_alert) printf("HUM_ALERT ");
if (sensor_status.temperature_alert) printf("TEMP_ALERT ");
if (sensor_status.reset_detected) printf("RESET ");
if (sensor_status.command_status) printf("CMD_ERR ");
if (sensor_status.crc_status) printf("CRC_ERR ");
if (sensor_status.raw_status == 0x0010) {
printf("OK (reset detected on startup)");
} else if (sensor_status.raw_status == 0x0000) {
printf("OK");
}
printf("\n");
// Clear reset flag after first read if you want
// if (sensor_status.reset_detected) {
// gxht30_clear_status(GXHT30_I2C_ADDR_DEFAULT);
// }
} else {
printf("Status read error\n");
}
last_status_read_time = millis();
}
// uint32_t now = millis();
// if (now - last_led_toggle_time > LED_TOGGLE_INTERVAL_MS) {
// if (led_state) {
// GPIOA->BSHR = (1 << LED1_PIN);
// } else {
// GPIOA->BSHR = (1 << (LED1_PIN + 16));
// }
// led_state = !led_state;
// last_led_toggle_time = now;
// }
}
}

View File

@@ -32,4 +32,6 @@
#define LWIP_RAND() ((u32_t)rand())
#include "arch/sys_arch.h"
#endif /* LWIP_ARCH_CC_H */

View File

@@ -1,8 +1,9 @@
#include "sys_arch.h"
#include "ch32fun.h"
#include "lwip/def.h"
#include "lwip/sys.h"
#include "systick.h"
typedef uint32_t sys_prot_t;
static unsigned long next = 1;
int rand(void) {
@@ -12,14 +13,14 @@ int rand(void) {
void srand(unsigned int seed) { next = seed; }
uint32_t sys_now(void) { return systick_millis; }
uint32_t sys_now(void) { return millis(); }
sys_prot_t sys_arch_protect(void) {
unsigned int old_mstatus;
__asm__ volatile("csrrci %0, mstatus, 8" : "=r"(old_mstatus));
return old_mstatus;
__disable_irq();
return 1;
}
void sys_arch_unprotect(sys_prot_t pval) {
__asm__ volatile("csrw mstatus, %0" : : "r"(pval));
(void)pval;
__enable_irq();
}

8
port/arch/sys_arch.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef LWIP_ARCH_SYS_ARCH_H
#define LWIP_ARCH_SYS_ARCH_H
#include <stdint.h>
typedef uint32_t sys_prot_t;
#endif /* LWIP_ARCH_SYS_ARCH_H */

View File

@@ -13,35 +13,60 @@
#define IFNAME0 'e'
#define IFNAME1 'n'
#define ETH_RXBUFNB 4
#define ETH_TXBUFNB 1
#define ETH_RX_BUF_SZE ETH_MAX_PACKET_SIZE
#define ETH_TX_BUF_SZE ETH_MAX_PACKET_SIZE
typedef struct {
volatile uint32_t head; // producer idx: next free slot to write to
volatile uint32_t tail; // consumer idx: next slot to be txed
volatile bool is_full; // for N=1 size
} tx_queue_t;
struct ethernetif {
ETH_DMADESCTypeDef* DMARxDescToGet;
ETH_DMADESCTypeDef* DMATxDescToSet;
ETH_DMADESCTypeDef* rx_desc_head; // next desc to be filled by DMA
ETH_DMADESCTypeDef* rx_desc_tail; // next desc to be read by CPU
tx_queue_t tx_q;
};
__attribute__((aligned(4))) ETH_DMADESCTypeDef DMARxDscrTab[ETH_RXBUFNB];
__attribute__((aligned(4))) ETH_DMADESCTypeDef DMATxDscrTab[ETH_TXBUFNB];
__attribute__((aligned(4))) uint8_t MACRxBuf[ETH_RXBUFNB * ETH_RX_BUF_SZE];
__attribute__((aligned(4))) uint8_t MACTxBuf[ETH_TXBUFNB * ETH_TX_BUF_SZE];
__attribute__((aligned(4))) ETH_DMADESCTypeDef g_dma_rx_descs[ETH_RX_BUF_COUNT];
__attribute__((aligned(4))) ETH_DMADESCTypeDef g_dma_tx_descs[ETH_TX_BUF_COUNT];
__attribute__((
aligned(4))) uint8_t g_mac_rx_bufs[ETH_RX_BUF_COUNT * ETH_RX_BUF_SIZE];
__attribute__((
aligned(4))) uint8_t g_mac_tx_bufs[ETH_TX_BUF_COUNT * ETH_TX_BUF_SIZE];
static volatile bool g_link_interrupt_flag = false;
static struct ethernetif eth_state;
static struct ethernetif g_eth_state;
static volatile bool g_link_irq_flag = false;
static inline void tx_queue_init(tx_queue_t* q) {
q->head = 0;
q->tail = 0;
q->is_full = false;
}
static inline bool tx_queue_is_empty(const tx_queue_t* q) {
return !q->is_full && (q->head == q->tail);
}
static inline bool tx_queue_is_full(const tx_queue_t* q) { return q->is_full; }
static inline void tx_queue_produce(tx_queue_t* q) {
q->head = (q->head + 1) % ETH_TX_BUF_COUNT;
if (q->head == q->tail) {
q->is_full = true;
}
}
static inline void tx_queue_consume(tx_queue_t* q) {
q->tail = (q->tail + 1) % ETH_TX_BUF_COUNT;
q->is_full = false;
}
static void low_level_init(struct netif* netif);
static err_t low_level_output(struct netif* netif, struct pbuf* p);
static struct pbuf* low_level_input(struct netif* netif);
void WritePHYReg(uint8_t reg_add, uint16_t reg_val);
uint16_t ReadPHYReg(uint8_t reg_add);
void phy_write_reg(uint8_t reg_add, uint16_t reg_val);
uint16_t phy_read_reg(uint8_t reg_add);
static void eth_get_mac_in_uc(uint8_t* mac) {
static void eth_get_mac_addr(uint8_t* mac) {
// Mac is backwards.
const uint8_t* macaddr = (const uint8_t*)(ROM_CFG_USERADR_ID + 5);
const uint8_t* macaddr_src = (const uint8_t*)(ROM_CFG_USERADR_ID + 5);
for (int i = 0; i < 6; i++) {
mac[i] = *(macaddr--);
mac[i] = *(macaddr_src--);
}
}
@@ -50,7 +75,7 @@ err_t ethernetif_init(struct netif* netif) {
netif->hostname = "lwip-ch32";
#endif
netif->state = &eth_state;
netif->state = &g_eth_state;
netif->name[0] = IFNAME0;
netif->name[1] = IFNAME1;
@@ -60,7 +85,7 @@ err_t ethernetif_init(struct netif* netif) {
MIB2_INIT_NETIF(netif, snmp_ifType_ethernet_csmacd, 10000000); // 10Mbps
netif->hwaddr_len = ETH_HWADDR_LEN;
eth_get_mac_in_uc(netif->hwaddr);
eth_get_mac_addr(netif->hwaddr);
printf("MAC Address: %02X:%02X:%02X:%02X:%02X:%02X\n", netif->hwaddr[0],
netif->hwaddr[1], netif->hwaddr[2], netif->hwaddr[3], netif->hwaddr[4],
@@ -103,106 +128,126 @@ static void low_level_init(struct netif* netif) {
ETH10M->ECON2 = RB_ETH_ECON2_DEFAULT;
// init TX descriptors
ethernetif->DMATxDescToSet = DMATxDscrTab;
for (int i = 0; i < ETH_TXBUFNB; i++) {
DMATxDscrTab[i].Status = 0;
DMATxDscrTab[i].Buffer1Addr = (uint32_t)&MACTxBuf[i * ETH_TX_BUF_SZE];
DMATxDscrTab[i].Buffer2NextDescAddr =
(uint32_t)&DMATxDscrTab[(i + 1) % ETH_TXBUFNB];
tx_queue_init(&ethernetif->tx_q);
for (int i = 0; i < ETH_TX_BUF_COUNT; i++) {
g_dma_tx_descs[i].Status = 0;
g_dma_tx_descs[i].Buffer1Addr =
(uint32_t)&g_mac_tx_bufs[i * ETH_TX_BUF_SIZE];
g_dma_tx_descs[i].Buffer2NextDescAddr =
(uint32_t)&g_dma_tx_descs[(i + 1) % ETH_TX_BUF_COUNT];
}
// init RX descriptors
ethernetif->DMARxDescToGet = DMARxDscrTab;
for (int i = 0; i < ETH_RXBUFNB; i++) {
DMARxDscrTab[i].Status = 0;
DMARxDscrTab[i].Buffer1Addr = (uint32_t)&MACRxBuf[i * ETH_RX_BUF_SZE];
DMARxDscrTab[i].Buffer2NextDescAddr =
(uint32_t)&DMARxDscrTab[(i + 1) % ETH_RXBUFNB];
ethernetif->rx_desc_head = g_dma_rx_descs;
ethernetif->rx_desc_tail = g_dma_rx_descs;
for (int i = 0; i < ETH_RX_BUF_COUNT; i++) {
g_dma_rx_descs[i].Status = ETH_DMARxDesc_OWN;
g_dma_rx_descs[i].Buffer1Addr =
(uint32_t)&g_mac_rx_bufs[i * ETH_RX_BUF_SIZE];
g_dma_rx_descs[i].Buffer2NextDescAddr =
(uint32_t)&g_dma_rx_descs[(i + 1) % ETH_RX_BUF_COUNT];
}
// set RX buffer start and enable receiver
ETH10M->ERXST = ethernetif->DMARxDescToGet->Buffer1Addr;
ETH10M->ERXST = ethernetif->rx_desc_head->Buffer1Addr;
ETH10M->ECON1 = RB_ETH_ECON1_RXEN;
WritePHYReg(PHY_BMCR, PHY_BMCR_RESET);
phy_write_reg(PHY_BMCR, PHY_BMCR_RESET);
Delay_Ms(200);
WritePHYReg(PHY_BMCR, PHY_BMCR_FULL_DUPLEX);
phy_write_reg(PHY_BMCR, PHY_BMCR_FULL_DUPLEX);
ETH10M->EIR = 0xFF; // clear all interrupt flags
ETH10M->EIE = RB_ETH_EIE_INTIE | RB_ETH_EIE_TXIE | RB_ETH_EIE_LINKIE |
RB_ETH_EIE_TXERIE | RB_ETH_EIE_RXERIE | RB_ETH_EIE_R_EN50;
ETH10M->EIE = RB_ETH_EIE_INTIE | RB_ETH_EIE_RXIE | RB_ETH_EIE_TXIE |
RB_ETH_EIE_LINKIE | RB_ETH_EIE_TXERIE | RB_ETH_EIE_RXERIE |
RB_ETH_EIE_R_EN50;
NVIC_EnableIRQ(ETH_IRQn);
}
static err_t low_level_output(struct netif* netif, struct pbuf* p) {
(void)netif;
if (DMATxDscrTab[0].Status & ETH_DMATxDesc_OWN) {
LINK_STATS_INC(link.drop);
return ERR_BUF;
static void tx_start_if_possible(void) {
// if TXRTS bit is set, MAC is busy sending a packet
if (ETH10M->ECON1 & RB_ETH_ECON1_TXRTS) {
return;
}
struct ethernetif* ethernetif = &g_eth_state;
if (tx_queue_is_empty(&ethernetif->tx_q)) {
return;
}
// get descriptor for the next packet to send
uint32_t idx = ethernetif->tx_q.tail;
ETH_DMADESCTypeDef* dma_desc = &g_dma_tx_descs[idx];
uint16_t len = dma_desc->Status;
// tell MAC which buffer to send
ETH10M->ETXLN = len;
ETH10M->ETXST = dma_desc->Buffer1Addr;
// start tx
ETH10M->ECON1 |= RB_ETH_ECON1_TXRTS;
}
static err_t low_level_output(struct netif* netif, struct pbuf* p) {
struct ethernetif* ethernetif = netif->state;
err_t errval = ERR_OK;
if (tx_queue_is_full(&ethernetif->tx_q)) {
// should this be ERR_BUF or ERR_MEM? does ERR_MEM re-queue the packet?
// queue full, drop pkt
errval = ERR_BUF;
// errval = ERR_MEM;
} else {
uint32_t current_idx = ethernetif->tx_q.head;
uint8_t* tx_buf_ptr = (uint8_t*)g_dma_tx_descs[current_idx].Buffer1Addr;
uint32_t len = 0;
uint8_t* tx_buf_ptr = (uint8_t*)DMATxDscrTab[0].Buffer1Addr;
for (struct pbuf* q = p; q != NULL; q = q->next) {
memcpy(&tx_buf_ptr[len], q->payload, q->len);
len += q->len;
}
ETH10M->ETXLN = len;
ETH10M->ETXST = (uint32_t)tx_buf_ptr;
DMATxDscrTab[0].Status |= ETH_DMATxDesc_OWN;
ETH10M->ECON1 |= RB_ETH_ECON1_TXRTS;
g_dma_tx_descs[current_idx].Status = len;
tx_queue_produce(&ethernetif->tx_q);
}
LINK_STATS_INC(link.xmit);
MIB2_STATS_NETIF_ADD(netif, ifoutoctets, len);
return ERR_OK;
tx_start_if_possible();
return errval;
}
static struct pbuf* low_level_input(struct netif* netif) {
struct ethernetif* ethernetif = netif->state;
struct pbuf* p = NULL;
if (!(ETH10M->EIR & RB_ETH_EIR_RXIF)) {
// if OWN bit is set, it's still owned by DMA and no packet rdy
if (ethernetif->rx_desc_tail->Status & ETH_DMARxDesc_OWN) {
return NULL;
}
uint16_t len = ETH10M->ERXLN;
// packet ready
uint32_t len = (ethernetif->rx_desc_tail->Status & ETH_DMARxDesc_FL) >> 16;
if (len < MIN_ETH_FRAME_SIZE || len > ETH_MAX_PACKET_SIZE) {
LINK_STATS_INC(link.lenerr);
ETH10M->EIR = RB_ETH_EIR_RXIF;
ETH10M->ECON1 |= RB_ETH_ECON1_RXEN;
return NULL;
}
uint8_t* current_rx_buffer_ptr =
(uint8_t*)ethernetif->DMARxDescToGet->Buffer1Addr;
struct pbuf* p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
if (p != NULL) {
uint8_t* buffer = (uint8_t*)ethernetif->rx_desc_tail->Buffer1Addr;
uint32_t offset = 0;
for (struct pbuf* q = p; q != NULL; q = q->next) {
memcpy(q->payload, current_rx_buffer_ptr + offset, q->len);
memcpy(q->payload, buffer + offset, q->len);
offset += q->len;
}
LINK_STATS_INC(link.recv);
MIB2_STATS_NETIF_ADD(netif, ifinoctets, len);
} else {
LINK_STATS_INC(link.memerr);
LINK_STATS_INC(link.drop);
MIB2_STATS_NETIF_INC(netif, ifindiscards);
}
// move to next descriptor
ethernetif->DMARxDescToGet =
(ETH_DMADESCTypeDef*)ethernetif->DMARxDescToGet->Buffer2NextDescAddr;
ETH10M->ERXST = (uint32_t)ethernetif->DMARxDescToGet->Buffer1Addr;
ETH10M->EIR = RB_ETH_EIR_RXIF;
ETH10M->ECON1 |= RB_ETH_ECON1_RXEN;
// give buffer back to DMA
ethernetif->rx_desc_tail->Status = ETH_DMARxDesc_OWN;
// advance read pointer to the next descriptor in the ring
ethernetif->rx_desc_tail =
(ETH_DMADESCTypeDef*)ethernetif->rx_desc_tail->Buffer2NextDescAddr;
return p;
}
@@ -217,12 +262,12 @@ void ethernetif_input(struct netif* netif) {
}
void ethernetif_link_poll(struct netif* netif) {
if (!g_link_interrupt_flag) return;
g_link_interrupt_flag = false;
if (!g_link_irq_flag) return;
g_link_irq_flag = false;
// supposedly, first read latches link status 2nd get cur val
(void)ReadPHYReg(PHY_BMSR);
uint16_t bmsr = ReadPHYReg(PHY_BMSR);
(void)phy_read_reg(PHY_BMSR);
uint16_t bmsr = phy_read_reg(PHY_BMSR);
if (bmsr & PHY_BMSR_LINK_STATUS) {
if (!netif_is_link_up(netif)) {
@@ -239,36 +284,73 @@ void ethernetif_link_poll(struct netif* netif) {
void ETH_IRQHandler(void) __attribute__((interrupt)) __attribute__((used));
void ETH_IRQHandler(void) {
uint32_t flags = ETH10M->EIR;
struct ethernetif* ethernetif = &g_eth_state;
if (flags & RB_ETH_EIR_RXIF) {
ETH10M->EIR = RB_ETH_EIR_RXIF;
// descriptor should be owned by DMA
if (ethernetif->rx_desc_head->Status & ETH_DMARxDesc_OWN) {
ETH_DMADESCTypeDef* next_desc =
(ETH_DMADESCTypeDef*)ethernetif->rx_desc_head->Buffer2NextDescAddr;
// if next descriptor OWN bit is 0, ring is full and we must drop
if (!(next_desc->Status & ETH_DMARxDesc_OWN)) {
LINK_STATS_INC(link.drop);
} else {
// process and re-arm
ethernetif->rx_desc_head->Status &= ~ETH_DMARxDesc_OWN;
// write packet len into status field for CPU
ethernetif->rx_desc_head->Status |=
(ETH_DMARxDesc_FS | ETH_DMARxDesc_LS |
(ETH10M->ERXLN << ETH_DMARxDesc_FrameLengthShift));
// advance descripotor ptr
ethernetif->rx_desc_head = next_desc;
// re-arm receiver with new emtpy buf
ETH10M->ERXST = (uint32_t)ethernetif->rx_desc_head->Buffer1Addr;
}
}
}
if (flags & RB_ETH_EIR_TXIF) {
DMATxDscrTab[0].Status &= ~ETH_DMATxDesc_OWN;
ETH10M->EIR = RB_ETH_EIR_TXIF;
if (!tx_queue_is_empty(&ethernetif->tx_q)) {
LINK_STATS_INC(link.xmit);
tx_queue_consume(&ethernetif->tx_q);
}
tx_start_if_possible();
}
if (flags & RB_ETH_EIR_TXERIF) {
DMATxDscrTab[0].Status &= ~ETH_DMATxDesc_OWN;
ETH10M->EIR = RB_ETH_EIR_TXERIF;
LINK_STATS_INC(link.err);
if (!tx_queue_is_empty(&ethernetif->tx_q)) {
tx_queue_consume(&ethernetif->tx_q);
}
tx_start_if_possible();
}
if (flags & RB_ETH_EIR_RXERIF) {
ETH10M->EIR = RB_ETH_EIR_RXERIF;
ETH10M->ECON1 |= RB_ETH_ECON1_RXEN;
ETH10M->ECON1 |= RB_ETH_ECON1_RXEN; // re-enable receiver
LINK_STATS_INC(link.err);
}
if (flags & RB_ETH_EIR_LINKIF) {
g_link_interrupt_flag = true;
g_link_irq_flag = true;
ETH10M->EIR = RB_ETH_EIR_LINKIF;
}
}
void WritePHYReg(uint8_t reg_add, uint16_t reg_val) {
void phy_write_reg(uint8_t reg_add, uint16_t reg_val) {
R32_ETH_MIWR = (reg_add & RB_ETH_MIREGADR_MASK) | RB_ETH_MIWR_MIIWR |
(reg_val << RB_ETH_MIWR_DATA_SHIFT);
}
uint16_t ReadPHYReg(uint8_t reg_add) {
uint16_t phy_read_reg(uint8_t reg_add) {
ETH10M->MIERGADR = reg_add;
return ETH10M->MIRD;
}

View File

@@ -4,26 +4,32 @@
#include "lwip/err.h"
#include "lwip/netif.h"
void run_tx_test(void);
void WritePHYReg(uint8_t reg_add, uint16_t reg_val);
uint16_t ReadPHYReg(uint8_t reg_add);
/* Unique device ID */
#define ROM_CFG_USERADR_ID 0x1FFFF7E8
#define ETH_DMARxDesc_FrameLengthShift 16
#define ETH_MAX_PACKET_SIZE \
1536 /* ETH_HEADER + VLAN_TAG + MAX_ETH_PAYLOAD + ETH_CRC */
/* Ethernet Frame Size Definitions */
#define ETH_HEADER \
14 /* 6 byte Dest addr, 6 byte Src addr, 2 byte length/type \
*/
14 /* 6 byte Dest addr, 6 byte Src addr, 2 byte length/type */
#define ETH_CRC 4 /* Ethernet CRC */
#define ETH_EXTRA 2 /* Extra bytes in some cases */
#define VLAN_TAG 4 /* optional 802.1q VLAN Tag */
#define MIN_ETH_PAYLOAD 46 /* Minimum Ethernet payload size */
#define MAX_ETH_PAYLOAD 1500 /* Maximum Ethernet payload size */
#define ETH_MAX_PACKET_SIZE \
1536 /* ETH_HEADER + VLAN_TAG + MAX_ETH_PAYLOAD + ETH_CRC */
#define MIN_ETH_FRAME_SIZE (ETH_HEADER + MIN_ETH_PAYLOAD) /* 60 bytes */
/* Buffer Configuration */
#define ETH_RX_BUF_COUNT 4
#define ETH_TX_BUF_COUNT 2
#define ETH_RX_BUF_SIZE ETH_MAX_PACKET_SIZE
#define ETH_TX_BUF_SIZE ETH_MAX_PACKET_SIZE
/* DMA descriptor stuff */
#define ETH_DMARxDesc_FrameLengthShift 16
typedef struct {
uint32_t volatile Status; /* Status */
uint32_t ControlBufferSize; /* Control and Buffer1, Buffer2 lengths */
@@ -32,29 +38,52 @@ typedef struct {
} ETH_DMADESCTypeDef;
/**
* @brief Initialize the ethernet interface and lwIP network stack.
* This function should be passed as the init function to netif_add().
* Should be called at the beginning of the program to set up the
* network interface. It calls the function low_level_init() to do the
* actual setup of the hardware.
*
* @param netif The lwIP network interface structure to be initialized.
* @return ERR_OK if the loopif is initialized, ERR_MEM if private data couldn't
* be allocated.
* This function should be passed as a parameter to netif_add().
*
* @param netif the lwip network interface structure for this ethernetif
* @return ERR_OK if the loopif is initialized
* ERR_MEM if private data couldn't be allocated
* any other err_t on error
*/
err_t ethernetif_init(struct netif* netif);
/**
* @brief This function should be called periodically from your main loop
* to check for incoming packets and pass them to lwIP.
* This function should be called when a packet is ready to be read
* from the interface. It uses the function low_level_input() that
* should handle the actual reception of bytes from the network
* interface. Then the type of the received packet is determined and
* the appropriate input function is called.
*
* @param netif The lwIP network interface structure.
* @param netif the lwip network interface structure for this ethernetif
*/
void ethernetif_input(struct netif* netif);
/**
* @brief This function should be called periodically from your main loop
* This function should be called periodically from the main loop
* to check the link status and update lwIP accordingly.
*
* @param netif The lwIP network interface structure..
* @param netif the lwip network interface structure for this ethernetif
*/
void ethernetif_link_poll(struct netif* netif);
/**
* Write a value to PHY register.
*
* @param reg_add PHY register address.
* @param reg_val Value to write.
*/
void phy_write_reg(uint8_t reg_add, uint16_t reg_val);
/**
* Read a value from PHY register.
*
* @param reg_add PHY register address.
* @return Register value.
*/
uint16_t phy_read_reg(uint8_t reg_add);
#endif /* __ETHERNETIF_H */

View File

@@ -11,59 +11,50 @@
// #define ETHARP_DEBUG LWIP_DBG_ON
#define NO_SYS 1
// Core locking
#define SYS_LIGHTWEIGHT_PROT 0
// Memory options
#define MEM_ALIGNMENT 4
#define MEM_SIZE (10 * 1024) // 4KB of RAM for lwIP heap
// Pbuf options
#define PBUF_POOL_SIZE 24 // Enough for bursts
#define PBUF_POOL_BUFSIZE 600 // ~500 byte typical packet + header
// TCP options
#define LWIP_TCP 1
#define TCP_MSS 1460
#define TCP_SND_BUF (2 * TCP_MSS)
// UDP options
#define LWIP_UDP 1
// ICMP options
#define LWIP_ICMP 1
// DHCP options
#define LWIP_DHCP 1
// Checksum options
// #define CHECKSUM_GEN_IP 0
// #define CHECKSUM_GEN_UDP 0
// #define CHECKSUM_GEN_TCP 0
// #define CHECKSUM_CHECK_IP 0
#define CHECKSUM_CHECK_UDP 0
// #define CHECKSUM_CHECK_TCP 0
// #define LWIP_CHECKSUM_ON_COPY 1
#define SYS_LIGHTWEIGHT_PROT 1
#define LWIP_NETCONN 0
#define LWIP_SOCKET 0
// Statistics
#define LWIP_STATS 0
#define LINK_STATS 0
#define MIB2_STATS 0
// Memory options
#define MEM_ALIGNMENT 4
#define MEM_SIZE (4 * 1024)
// Pbuf options
#define PBUF_POOL_SIZE 6
#define PBUF_POOL_BUFSIZE 590
#define MEMP_NUM_PBUF 6 // default 16
// TCP options
#define LWIP_TCP 1
#define TCP_MSS 536
#define TCP_SND_BUF (2 * TCP_MSS)
#define TCP_WND (4 * TCP_MSS)
#define TCP_QUEUE_OOSEQ 0
#define LWIP_UDP 1
#define MEMP_NUM_UDP_PCB 3 // # of concurrent UDP "connections"
#define LWIP_ICMP 1
#define LWIP_DHCP 1
#define LWIP_HTTPD 1
// Use a read-only filesystem populated by makefsdata
// #define HTTPD_FSDATA_FILE "fsdata_custom.c"
#define LWIP_HTTPD_FS_SUPPORT 1
#define HTTPD_USE_CUSTOM_FSDATA 1
#define LWIP_NETIF_HOSTNAME 1
#define LWIP_NETIF_LINK_CALLBACK 1
#define LWIP_NETIF_STATUS_CALLBACK 1
#define LWIP_SUPPORT_CUSTOM_PBUF 1 // for zero-copy
#define LWIP_SUPPORT_CUSTOM_PBUF 1
#define CHECKSUM_GEN_IP 1
#define CHECKSUM_GEN_UDP 1
#define CHECKSUM_GEN_TCP 1
#define CHECKSUM_CHECK_IP 1
#define CHECKSUM_CHECK_UDP 1
#define CHECKSUM_CHECK_TCP 1
#define LWIP_CHECKSUM_ON_COPY 0
#define LWIP_STATS 0
#define LINK_STATS 0
#endif /* __LWIPOPTS_H__ */