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

 

 

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

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

 

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

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

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

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

*