STM Урок 77. LAN. ENC28J60. Подключаем внешние прерывания (EXTI)



 

Урок 77

 

LAN. ENC28J60. Подключаем внешние прерывания (EXTI)

 

Предыдущий урок Программирование МК STM32 Следующий урок

 

В уроке 74 мы разобрались, как можно задействовать и обрабатывать внешние прерывания на микроконтроллерах STM32, причём сделали мы это в основном для того, чтобы данный накопленный опыт использовать для программирования нашего сетевого модуля, так как микросхема ENC28J60, являющаяся  его сердцем, умеет выдавать определённый сигнал в момент, когда буфер для чтения заполнится. Это позволит нам исключить ошибки, возникающие из-за обработки пришедших пакетов в бесконечном цикле, а также исключить задержки, так как мы уже будем данные пакеты обрабатывать незамедлительно после того, когда они у нас появятся в буфере.

Во-первых, я решил немного навести порядок в соединениях. Всё, что можно я разместил на макетной плате. Для этого к переходнику USART я подпаял снизу штырьевые линейки, на которых также есть необходимые контакты GND, TXD и RXD. Так как на модуле LAN, который мы использовали до этого, нет возможности подпаять такие линейки, то я взял фирменный модуль от OLIMEX, выполненный на той же микросхеме, но уже имеющий в наличии контакты снизу для расположения на макетной плате и пока буду использовать его. ST-Link я буду использовать дешёвый, так как он имеет меньший размер и вполне справляется с возложенными на него обаязанностями, только питание я с него убрал, так как я считаю, что тока, которым может снабдить его питающая часть, недостаточно для полноправной работы микросхемы ENC28J60. Поэтому в качестве питающего устройства мы будем использовать понижающий DC-DC преобразователь на 3 ампера, который питать будем обычным блоком питания на 12 вольт с разъёмом 5,5 мм. Можно использовать любой блок питания, лишь бы он выдерживал хороший максимальный ток нагрузки (наш на 2 ампера) и напряжение на его выходе чтобы превышало 3,3 вольта. Теперь у нас всё выглядит вполне культурно и мы уверены, что у нас ничего не отвалится. Также контакт модуля INT подключим на ножку микроконтроллера PA2, так как задача нашего урока — использование внешних прерываний от модуля. Вот так вот сегодня выглядит наша схема (нажмите на картинку для увеличения изображения)

 

image00_500

 

Проект создадим из проекта ENC28J60_ARP урока 71 и назовём его ENC28J60_INT.

Откроем данный проект в Cube MX и включим на ножке PA2 внешние прерывания

 

image01

Перейдём в Configuration и настроим на нашей ножке внешние прерывания по нисходящему фронту в разделе GPIO, резистор никуда не подтягиваем, так как у нас не кнопка а вполне осознанные уровни из микросхемы

 

image02

 

Зайдём в NVIC и включим там глобальные прерывания на EXTI2

 

image04

 

Сгенерируем проект, откроем его в Keil, подключим все наши библиотеки, а также настроим программатор на авторезет. Попробуем собрать проект, прошить его и проверить работоспособность пингов и наших ARP-запросов в терминальной программе.

Если всё работает, то продолжим писать код.

 

 

Закомментируем вызов функции опроса сети в бесконечном цикле, так как опрашивать мы будем теперь нашу сеть в обработчике внешних прерываний

 

/* USER CODE BEGIN 3 */

  //net_poll();

}

/* USER CODE END 3 */

 

Теперь создадим обработчик прерываний внизу главного модуля

 

//----------------------------------------------------------

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)

{

}

/* USER CODE END 4 */

 

В теле данного обработчика отфильтруем события именно на определённой ножке и вызовем там функцию опроса сети

 

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)

{

  if(GPIO_Pin== GPIO_PIN_2){

    net_poll();

  } else{

    __NOP();

  }

}

 

И, самое главное, нам нужно не забыть, что прерывания также надо разрешить и самой микросхеме. Когда-то мы написали такой код, но потом его закомментировали.

 

 

Перейдём в модуль enc28j60.c и раскомментируем данную строку в инициализации

 

enc28j60_SetBank(ECON1);

enc28j60_writeOp(ENC28J60_BIT_FIELD_SET,EIE,EIE_INTIE|EIE_PKTIE);

 

Раз уж мы сюда пришли, то уберём также задержку по окончании отправки пакета

 

  enc28j60_writeOp(ENC28J60_BIT_FIELD_SET, ECON1,ECON1_TXRTS);

  HAL_Delay(1);

}

 

Попробуем собрать код и пропинговать нашу плату, а также опросить какой-нибудь адрес с помощью ARP-запроса в терминальной программе

 

image05

 image06

 

Всё работает. Теперь пакеты у нас обрабатываются тогда, когда это необходимо.

 

 

Давайте немного заодно займёмся оптимизацией наших сетевых обработок, так как у нас очень много времени тратится на отправку байтов в USART, причём делаем мы это ещё до отправки ответных пакетов, то есть не вовремя, тем самым задерживая процесс обработки пакета.

Начнём с обработки пакетов ARP REQUEST.

Для вернёмся в файл net.c в функцию eth_read и сначала закомментируем там всё, связанное с USART

 

if(frame->type==ETH_ARP)

{

  // sprintf(str1,"%02X:%02X:%02X:%02X:%02X:%02X-%02X:%02X:%02X:%02X:%02X:%02X; %d; arp\r\n",

  // frame->addr_src[0],frame->addr_src[1],frame->addr_src[2],

  // frame->addr_src[3],frame->addr_src[4],frame->addr_src[5],

  // frame->addr_dest[0],frame->addr_dest[1],frame->addr_dest[2],

  // frame->addr_dest[3],frame->addr_dest[4],frame->addr_dest[5],len);

  // HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

  res = arp_read(frame,len-sizeof(enc28j60_frame_ptr));

  if(res==1)

  {

    arp_send(frame);

  }

  else if(res==2)

  {

    arp_table_fill(frame);

  }

}

if(frame->type==ETH_IP)

{

  // sprintf(str1,"%02X:%02X:%02X:%02X:%02X:%02X-%02X:%02X:%02X:%02X:%02X:%02X; %d; ip\r\n",

  // frame->addr_src[0],frame->addr_src[1],frame->addr_src[2],

  // frame->addr_src[3],frame->addr_src[4],frame->addr_src[5],

  // frame->addr_dest[0],frame->addr_dest[1],frame->addr_dest[2],

  // frame->addr_dest[3],frame->addr_dest[4],frame->addr_dest[5],len);

  // HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

  ip_read(frame,len-sizeof(ip_pkt_ptr));

}

 

Также если вдруг прийдёт какой-нибудь залётный пакет, не относящийся ни к ARP, ни к IP, то для него сделаем отдельную обработку

 

else if(frame->type==ETH_IP)

{

  // sprintf(str1,"%02X:%02X:%02X:%02X:%02X:%02X-%02X:%02X:%02X:%02X:%02X:%02X; %d; ip\r\n",

  // frame->addr_src[0],frame->addr_src[1],frame->addr_src[2],

  // frame->addr_src[3],frame->addr_src[4],frame->addr_src[5],

  // frame->addr_dest[0],frame->addr_dest[1],frame->addr_dest[2],

  // frame->addr_dest[3],frame->addr_dest[4],frame->addr_dest[5],len);

  // HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

  ip_read(frame,len-sizeof(ip_pkt_ptr));

}

else

{

  sprintf(str1,"%02X:%02X:%02X:%02X:%02X:%02X-%02X:%02X:%02X:%02X:%02X:%02X; %d; %04X",

  frame->addr_src[0],frame->addr_src[1],frame->addr_src[2],frame->addr_src[3],frame->addr_src[4],frame->addr_src[5],

  frame->addr_dest[0],frame->addr_dest[1],frame->addr_dest[2],frame->addr_dest[3],frame->addr_dest[4],frame->addr_dest[5],

  len, be16toword(frame->type));

  HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

  HAL_UART_Transmit(&huart1,(uint8_t*)"\r\n",2,0x1000);

}

 

Идём теперь в файл arp.c в функцию arp_read и также уберём в ветке условия ARP_REQUEST всё связанное с USART

 

if(msg->op==ARP_REQUEST)

{

  // sprintf(str1,"\r\nrequest\r\nmac_src %02X:%02X:%02X:%02X:%02X:%02X\r\n",

  // msg->macaddr_src[0],msg->macaddr_src[1],msg->macaddr_src[2],

  // msg->macaddr_src[3],msg->macaddr_src[4],msg->macaddr_src[5]);

  // HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

  // sprintf(str1,"ip_src %d.%d.%d.%d\r\n",

  // msg->ipaddr_src[0],msg->ipaddr_src[1],msg->ipaddr_src[2],msg->ipaddr_src[3]);

  // HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

  // sprintf(str1,"mac_dst %02X:%02X:%02X:%02X:%02X:%02X\r\n",

  // msg->macaddr_dst[0],msg->macaddr_dst[1],msg->macaddr_dst[2],

  // msg->macaddr_dst[3],msg->macaddr_dst[4],msg->macaddr_dst[5]);

  // HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

  // sprintf(str1,"ip_dst %d.%d.%d.%d\r\n",

  // msg->ipaddr_dst[0],msg->ipaddr_dst[1],msg->ipaddr_dst[2],msg->ipaddr_dst[3]);

  // HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

  res=1;

}

 

Теперь идём в функцию arp_send и вот здесь после отправки пакета мы всю информацию и покажем в терминале, не забывая про то, что источник с приёмником у нас теперь поменялись местами

 

  eth_send(frame,sizeof(arp_msg_ptr));

  sprintf(str1,"%02X:%02X:%02X:%02X:%02X:%02X(%d.%d.%d.%d)-",

    frame->addr_dest[0],frame->addr_dest[1],frame->addr_dest[2],frame->addr_dest[3],frame->addr_dest[4],frame->addr_dest[5],

    msg->ipaddr_dst[0],msg->ipaddr_dst[1],msg->ipaddr_dst[2],msg->ipaddr_dst[3]);

  HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

  sprintf(str1,"%02X:%02X:%02X:%02X:%02X:%02X(%d.%d.%d.%d) arp request\r\n",

    frame->addr_src[0],frame->addr_src[1],frame->addr_src[2],frame->addr_src[3],frame->addr_src[4],frame->addr_src[5],

    msg->ipaddr_src[0],msg->ipaddr_src[1],msg->ipaddr_src[2],msg->ipaddr_src[3]);

  HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

}

 

Отправим пинг на нашу плату, нам дожен будет прийти хотя бы один ARP-запрос в терминальную программу

 

image07

 

Давайте ещё оптимизируем таким же образом запрос с ответом ICMP.

В функции icmp_read также закомментируем всё лишнее

 

if((len>=sizeof(icmp_pkt_ptr))&&(icmp_pkt->msg_tp==ICMP_REQ))

{

  //sprintf(str1,"icmp request\r\n");

  //HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

  icmp_pkt->msg_tp=ICMP_REPLY;

 

А всю информацию мы выведем в этой же функции, но только после после отправки пакета (mac-адреса в данном случае нам уже не нужны)

 

  ip_send(frame,len+sizeof(ip_pkt_ptr));

  sprintf(str1,"%d.%d.%d.%d-%d.%d.%d.%d icmp request\r\n",

    ip_pkt->ipaddr_dst[0],ip_pkt->ipaddr_dst[1],ip_pkt->ipaddr_dst[2],ip_pkt->ipaddr_dst[3],

    ip_pkt->ipaddr_src[0],ip_pkt->ipaddr_src[1],ip_pkt->ipaddr_src[2],ip_pkt->ipaddr_src[3]);

  HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

}

 

Соберём код, прошьём контроллер и посмотрим результат в терминальной программе после посылки пинга нашему модулю, а также посмотрим время отклика

 

image08

 

Мы видим, что время отклика стало намного меньше, хотя при использовании AVR было ещё меньше. Возможно, это связано с кодом библиотеки HAL, в avr мы обращались к регистрам напрямую.

В данном занятии мы решили сразу 2 задачи — использование внешних прерываний для своевременного принятия и обработки пакетов от сетевого модуля, а также оптимизация кода засчёт исключения лишних посылок в шину USART.

Всем спасибо за внимание!

 

 

Предыдущий урок Программирование МК STM32 Следующий урок

 

Исходный код

 

 

Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6

Программатор недорогой можно купить здесь ST-Link V2

Ethernet LAN Сетевой Модуль можно купить здесь ENC28J60 Ethernet LAN Сетевой Модуль.

Переходник USB to TTL можно приобрести здесь ftdi ft232rl

 

 

Смотреть ВИДЕОУРОК (нажмите на картинку)

 

STM LAN. ENC28J60. Подключаем внешние прерывания (EXTI)

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*