В этой статье я расскажу вас о том, как собрать минимально рабочую связку 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 |
Запускаем, пингуем, открываем страницу.