Как сделать сеть на ENC28J60, UIP и STM32

В этой статье я расскажу вас о том, как собрать минимально рабочую связку ENC28J60 + UIP, с использвоанием STM32. Проект будет использовать также FreeRtos.

Заготовку проекта FreeRtos на STM32F2 можно взять отсюда.

Ещё нам понадобиться исходники uip и драйвер для ENC28J60

Драйвер по ссылке предназначен для работы на ардуино, поэтому ему требуются минимальные правки, чтобы он нормально работал на STM32:

Удалить платформо зависимые дефайны и определения, относящиеся к Arduino

1
2
3
4
5
6
7
8
9
#include <avr/io.h>
#include <inttypes.h>
#include <avr/interrupt.h>
 
#if (ARDUINO >= 100)
#include <Arduino.h>
#else
#include <WProgram.h>
#endif

Добавив свой платформо зависимый инклюд

1
#include "stm32f2xx_conf.h"

Добавить реализацию 2-х разных  задержек, точность не нужна, так что простым циклом

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void Delay(__IO uint32_t nTime)
{
   for(int x=0; x<nTime; x++)
   {
       for(uint32_t i=0; i<20000; i++)
       {
          asm("NOP");
       }
   }
}
 
void delayMicroseconds(uint32_t nTime)
{
    while (nTime--)
    {
        for(int i=0; i<20; i++)
        {
            asm("NOP");
        }
    }
}

Драйвер использует такие определения типов данных как word и byte, заменим их на более привычные uin32_t и uint8_t соответственно.

Полностью переписать блок инициализации SPI.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
GPIO_InitTypeDef GPIO_InitStructure;//структура для настройки GPIO
SPI_InitTypeDef SPI_InitStructure;
 
//Initialize SPI
//**************************************************
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);//активируем клок SPI
 
/*!< Connect SPI pins to AF5 */
GPIO_PinAFConfig(GPIOA, 5, GPIO_AF_SPI2);
GPIO_PinAFConfig(GPIOA, 6, GPIO_AF_SPI2);
GPIO_PinAFConfig(GPIOA, 7, GPIO_AF_SPI2);
 
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
 
/*!< SPI SCK pin configuration */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_Init(GPIOA, &GPIO_InitStructure);
 
/*!< SPI MOSI pin configuration */
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_6;
GPIO_Init(GPIOA, &GPIO_InitStructure);
 
/*!< SPI MISO pin configuration */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_Init(GPIOA, &GPIO_InitStructure);
 
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA, &GPIO_InitStructure);
disableChip();
 
/*!< SPI configuration */
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
 
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(SPI1, &SPI_InitStructure);
 
/*!< Enable the sFLASH_SPI */
SPI_Cmd(SPI1, ENABLE);
 
//настроим вывод, который управляет питанием ENC28J60
 
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOC, &GPIO_InitStructure);
 
//настроим вывод, который управляет сбросом ENC28J60
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA, &GPIO_InitStructure);
 
//придавим ресет
GPIO_ResetBits(GPIOA, GPIO_Pin_8);
 
//подождём немного
Delay(100);
 
//отпустим ресет
GPIO_SetBits(GPIOA, GPIO_Pin_8);
 
//подождём немного
Delay(100);
 
}

Полностью заменить функцию waitspi() , потому что в STM32 по другому ждём окончания передачи SPI

1
#define waitspi()   while((SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY)))

Полностью переписать функции disableChip() и enableChip, для активации чипа нужно опустить на ноль вывод, подключенный к выводу CS ENC28J60, и наоборот.

1
2
3
4
static void enableChip()
{
GPIO_ResetBits(GPIOA, GPIO_Pin_4);
}

Переписать процедуру sendSPI:

static void sendSPI(uint8_t data)
{
SPI_I2S_SendData(SPI1, data);
waitspi();
}

В строках

1
byte result = SPDR;

код читает SPI, у нас всё по взрослому, поэтому в нескольких местах заменяем на

1
result = SPI_I2S_ReceiveData(SPI1);

В функции

1
enc28j60InitWithCs( )

Удалить инициализацию выводов в строках

1
2
3
4
// initialize I/O
enc28j60ControlCs = csPin;
// ss as output:
pinMode(csPin, OUTPUT);

Инициализацию выводов я перенёс к инициализации SPI.

И последнее, особенность SPI в STM32 требует, чтобы после любой записи SPI было произведено чтение, а этот драйвер в некоторых местах просто пишет, но не читает, это тоже надо исправить, если драйвер только пишет, то сразу после этого вставьте инструкцию

SPI_I2S_ReceiveData(SPI1);

На этом портирование драйвера законченна, вы можете скачать уже адаптированный драйвер в составе проекта, приложенного к статье.

Приступаем к портированию uip, папка uip отправляеться к нам в проект без изменений, в проект подключаются все файлы .c, кроме timer.c, он нам не нужен, потому что есть операционка.

Из папки unix в корень проекта копируем файл uip-conf.h, в нём хранятся настройки uip, пока оставляем по умолчанию. В этом же файле указанно приложение, которое будет использовать uip, по умолчанию это #include «webserver.h», чтож, нас вполне устроит веб сервер, поэтому заходим в папку apps(исходных кодов uip) и забираем оттуда к себе в корень проекта папку webserver, и добавляем к себе в проект все исходные файлы сервера.

Теперь надо обеспечить стыковку ENC28J60 и uip, для этого в main.c создаём 2 простых задачи

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
//буфер, нужный для UIP
//*********************************************
#define BUF ((struct uip_eth_hdr *)&uip_buf[0])
//*********************************************
 
void vTask_uIP_periodic(void *pvParameters)
{
  uint32_t i;
  uint8_t delay_arp = 0;
 
  while(1)
  {
    vTaskDelay(configTICK_RATE_HZ/2); // полсекунды
    delay_arp++;
    for (i = 0; i < UIP_CONNS; i++) {       uip_periodic(i);       if (uip_len > 0) {
        uip_arp_out();
        enc28j60PacketSend(uip_len,(uint8_t *) uip_buf);
      }
    }
 
#if UIP_UDP
    for(i = 0; i < UIP_UDP_CONNS; i++) {       uip_udp_periodic(i);       if(uip_len > 0) {
        uip_arp_out();
        network_send();
      }
    }
#endif /* UIP_UDP */
 
    if (delay_arp >= 20) 
    { // один раз за 20 проходов цикла, около 10 сек.
      delay_arp = 0;
      uip_arp_timer();
    }
    taskYIELD();
  }
}
//--------------------------------------------------------------
void vTask_uIP(void *pvParameters) 
{
  while(1)
  {      
    uip_len = enc28j60PacketReceive(UIP_BUFSIZE, (uint8_t *) uip_buf);
 
    if (uip_len > 0) 
    {        
      if (BUF->type == htons(UIP_ETHTYPE_IP)) 
      {
        uip_arp_ipin();
        uip_input();
        if (uip_len > 0) {
          uip_arp_out();
          enc28j60PacketSend(uip_len,(uint8_t *) uip_buf);
        }
      } else if (BUF->type == htons(UIP_ETHTYPE_ARP)) 
      {
        uip_arp_arpin();
        if (uip_len > 0) {
          enc28j60PacketSend(uip_len,(uint8_t *) uip_buf);
        }
      }
    } 
    // если поставить тут Yeld, то задача vTask_uIP_periodic никогда не получит
    // управления
    vTaskDelay(1);
    // taskYIELD();
  }
}

vTask_uIP обеспечивает постоянную проверку данных от ENC28J60 и передачу их в uip, vTask_uIP_periodic будет периодически рассылать ARP пакеты. Обратите внимание, что задача vTask_uIP_periodic никогда не должна прервать vTask_uIP, для этого у ней меньше приоритет.

Ещё добавляем код инициализации стека, перед главным циклом.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  // это будет наш МАС-адрес
  struct uip_eth_addr mac = { { 0x00, 0xa1, 0xb2, 0x34, 0x43, 0x43 } };
 
  //проинитим SPI
  enc28j60SpiInit();
 
  //проинитим наш  enc28j60
  enc28j60Init(mac.addr);
 
  // инициализация стека
  uip_init();
  uip_arp_init();
 
  // установим наш МАС
  uip_setethaddr(mac);
 
  // установим адрес хоста (не используем dhcp)
  // наш хост будет доступен по адресу 192.168.0.100
  uip_ipaddr_t ipaddr;
  uip_ipaddr(ipaddr, 192, 168, 0, 100);
  uip_sethostaddr(ipaddr);
  uip_ipaddr(ipaddr, 192, 168, 0, 1);
  uip_setdraddr(ipaddr);
  uip_ipaddr(ipaddr, 255, 255, 255, 0);
  uip_setnetmask(ipaddr);
 
  httpd_init();

 

На этом этапе проект должен собираться, с одной ошибкой:

Error[Li005]: no definition for «uip_log»

отключим логгирование uip в uip_conf.h:

#define UIP_CONF_LOGGING         0

Надо сказать, что библиотека uip довольно «грязная» и выдаёт много предупреждений, либо игнорируем, либо давим их конструкциями :

1
2
3
#pragma diag_suppress=pe550
...проблемная строка...
#pragma diag_default=pe550

Запускаем, пингуем, открываем страницу.

 Скачать проект для IAR

 

Оставить комментарий