Урок 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, так как задача нашего урока — использование внешних прерываний от модуля. Вот так вот сегодня выглядит наша схема (нажмите на картинку для увеличения изображения)
Проект создадим из проекта ENC28J60_ARP урока 71 и назовём его ENC28J60_INT.
Откроем данный проект в Cube MX и включим на ножке PA2 внешние прерывания
Перейдём в Configuration и настроим на нашей ножке внешние прерывания по нисходящему фронту в разделе GPIO, резистор никуда не подтягиваем, так как у нас не кнопка а вполне осознанные уровни из микросхемы
Зайдём в NVIC и включим там глобальные прерывания на EXTI2
Сгенерируем проект, откроем его в 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-запроса в терминальной программе
Всё работает. Теперь пакеты у нас обрабатываются тогда, когда это необходимо.
Давайте немного заодно займёмся оптимизацией наших сетевых обработок, так как у нас очень много времени тратится на отправку байтов в 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-запрос в терминальную программу
Давайте ещё оптимизируем таким же образом запрос с ответом 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);
}
Соберём код, прошьём контроллер и посмотрим результат в терминальной программе после посылки пинга нашему модулю, а также посмотрим время отклика
Мы видим, что время отклика стало намного меньше, хотя при использовании AVR было ещё меньше. Возможно, это связано с кодом библиотеки HAL, в avr мы обращались к регистрам напрямую.
В данном занятии мы решили сразу 2 задачи — использование внешних прерываний для своевременного принятия и обработки пакетов от сетевого модуля, а также оптимизация кода засчёт исключения лишних посылок в шину USART.
Всем спасибо за внимание!
Предыдущий урок Программирование МК STM32 Следующий урок
Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6
Программатор недорогой можно купить здесь ST-Link V2
Ethernet LAN Сетевой Модуль можно купить здесь ENC28J60 Ethernet LAN Сетевой Модуль.
Переходник USB to TTL можно приобрести здесь ftdi ft232rl
Смотреть ВИДЕОУРОК (нажмите на картинку)
Добавить комментарий