ENC28J60 with lwIP: DHCP Client
Hardware and Software Requirements
To complete the project successfully, you need the below items:
- Any microcontroller that has Serial Peripheral Interface (SPI)
- Any ENC28J60-based breakout board
- RJ45 for Ethernet connection for local communication
The STM32F401RE Nucleo Board shown in figure below was used for this demonstration.
The ENC28J60 board was used for Ethernet connection.
I also used KiCad to design an own interface module to avoid using a breadboard or having wires all over the place. The KiCad files can be shared upon request.
Hardware Setup
| Signal | Connection |
|---|---|
| 3.3V | NUCLEO 3V3 → ENC28J60 VCC |
| GND | NUCLEO GND → ENC28J60 GND |
| MOSI | PA7 (SPI1 MOSI) → SI |
| MISO | PA6 (SPI1 MISO) → SO |
| SCK | PA5 (SPI1 SCK) → SCK |
| CS | PB6 (GPIO Output) → CS |
| INT | PC7 (EXTI7) → INT |
ENC28J60 Driver Development
void enc28j60_initDr(enc28j60Drv * dev, spiChipSel cs, spiChipDSl dCS, slaveRead rd, slaveWrite wr, intHandler hdle, delayMs delay);
void enc28j60_strtDr(enc28j60Drv * dev);
void enc28j60_sftRst(enc28j60Drv * dev);
bool enc28j60_intPnd(enc28j60Drv * dev);
void enc28j60_intSet(enc28j60Drv * dev);
void enc28j60_intCls(enc28j60Drv * dev);
bool enc28j60_etherReceive(enc28j60Drv * dev, uint8_t * data, uint16_t length);
bool enc28j60_etherTransmit(enc28j60Drv * dev, uint8_t * data, uint16_t length);
lwIP Integration
err_t enc28j60_translate(struct netif *netif, struct pbuf *p) {
if(bTxBusy != true) {
dMesgPrint(DEBUG_INFO, "TX started!\r\n");
struct pbuf *q;
uint16_t total_len = p->tot_len;
uint16_t offset = 0;
/* Copy the full chained pbuf into linear ENC buffer */
for (q = p; q != NULL; q = q->next)
{
memcpy(&dev.txPkt.data[offset], q->payload, q->len);
offset += q->len;
}
/* Safety: offset must equal total length */
if (offset != total_len) {
return ERR_BUF;
}
(void) enc28j60_etherTransmit(&dev, dev.txPkt.data, total_len);
bTxBusy = true;
return ERR_OK;
}else {
dMesgPrint(DEBUG_ERROR, "TX busy!\r\n");
return ERR_INPROGRESS;
}
}
void ethernet_do_translation_to_pbub(enc28j60Drv * dev, struct pbuf *p) {
uint16_t len = dev->rxPkt.rxPktLen.u16PktLen;
if (len > p->tot_len) len = p->tot_len;
pbuf_take(p, dev->rxPkt.data, len);
}
DHCP Client Specific Code
enc28j60_initDr(&dev, spi1ChipSelect, spi1ChipDeSelect,
spi1Read, spi1Write, NULL, delayMsFunction);
enc28j60_strtDr(&dev);
lwip_init();
netif_add_noaddr(&my_netif, NULL, ethernet_init, ethernet_input);
netif_set_default(&my_netif);
netif_set_up(&my_netif);
dhcp_set_struct(&my_netif, &myDhcpClient);
dhcp_start(&my_netif);
while(true)
{
if(u32FinerTimer >= 1)
{
u32FinerTimer -= 1;
dhcp_fine_tmr();
HAL_GPIO_TogglePin(GreenLED1_GPIO_Port, GreenLED1_Pin);
}
if(u32CoarseTimer >= 120)
{
u32CoarseTimer -= 120;
dhcp_coarse_tmr();
}
}
Ping Terminal Outputs
EIR REG --> 64
6) Receive Packet Pending Interrupt Flag bit
Next Point address --> 5030
ETH --> Length out of range
ETH --> RX OK
PKT number 147413
PKT length 98
ip_input: iphdr->dest 0xca08a8c0 netif->ip_addr 0xca08a8c0 (0x8a8c0, 0x8a8c0, 0xca000000)
ip4_input: packet accepted on interface en
ip4_input:
IP header:
+-------------------------------+
| 4 | 5 | 0x00 | 84 | (v, hl, tos, len)
+-------------------------------+
| 1324 |010| 0 | (id, flags, offset)
+-------------------------------+
| 64 | 1 | 0xa28b | (ttl, proto, chksum)
+-------------------------------+
| 192 | 168 | 8 | 215 | (src)
+-------------------------------+
| 192 | 168 | 8 | 202 | (dest)
+-------------------------------+
ip4_input: p->len 84 p->tot_len 84
ip4_output_if: en0
IP header:
+-------------------------------+
| 4 | 5 | 0x00 | 84 | (v, hl, tos, len)
+-------------------------------+
| 1324 |010| 0 | (id, flags, offset)
+-------------------------------+
| 255 | 1 | 0xe38a | (ttl, proto, chksum)
+-------------------------------+
| 192 | 168 | 8 | 202 | (src)
+-------------------------------+
| 192 | 168 | 8 | 215 | (dest)
+-------------------------------+
ip4_output_if: call netif->output()
Source Code
The source code for the project can be found here — checkout the dhcpClient tag.