Урок 40
Часть 9
LAN. ENC28J60
В предыдущей части нашего урока мы наконец-то ответили на запрос ARP, а также изучили заголовок протокола IP.
Давайте теперь разберёмся немного с расчётом контрольной суммы заголовков. Она расчитывается практически одинаково для всех типов заголовков и пакетов, поэтому функция будет универсальная.
Общий принцип расчёта следующий. Заголовок разбивается на 16-битные величины. Они все складываются. Если сумма не умещается в 16 битную величину, что чаще и бывает, оно разбивается на два 16-битных числа — старшее полуслово и младшее, и они затем также складываются. Если опять не умещается, повторяем данную операцию, пока не влезет в 16 бит. Полученную величину сначала конвертируем в big endian, затем инвертируем, то есть вместо всех единиц записываем нули, а вместо нулей — единицы. Есть небольшой нюанс. Вдруг у нас длина заголовка будет состоять из нечётного числа байтов. Тогда последний байт выравниваем нулём, то есть существующий один байт становится старшим, а в младший пишем все нули. Как-то так. Можно заранее дополнить нулями до чётной длины весь заголовок, это кому как нравится. Также не забываем, что при расчёте контрольной суммы само поле с контрольной суммой также считается, поэтому его нужно сначала обнулить, а затем уже после расчёта заполнить значением контрольной суммы.
Давайте же напишем данную функцию где-нибудь повыше, чтобы была видна из других функций
//———————————————
uint16_t checksum(uint8_t *ptr, uint16_t len)
{
}
//———————————————
ptr — адрес заголовка, контрольную сумму которого мы рассчитываем,
len — длина заголовка.
Возвращаем значение рассчитанной контрольной суммы.
Добавим переменную для суммы
uint16_t checksum(uint8_t *ptr, uint8_t uint16_t len)
{
uint16_t sum = 0;
Добавим условный цикл, отслеживающий окончание заголовка
uint16_t sum = 0;
while(len >1)
{
}
В теле условного цикла посчитаем и прибавим к общей сумме очередную двухбайтовую величину
while(len >1)
{
sum += (uint16_t) (((uint32_t)*ptr<<8)|*(ptr+1));
}
Как мы видим, для того чтобы получить big endian, необязательно пользоваться макросом и терять лишние машинные циклы, достаточно наоборот сдвинуть влево и сделать сразу старшим младший байт и прибавить к нему по «ИЛИ» старший байт, который автоматически станет младшим.
Далее здесь же в теле условного цикла сместим указатель дальше на 2 пункта и значение длины заголовка уменьшим на 2
sum += (uint16_t) (((uint32_t)*ptr<<8)|*(ptr+1));
ptr+=2;
len-=2;
Выйдем из тела цикла и дальше в нашей функции проверим длину заголовка на чётность способом проверки переменной len на оставшийся байт. Если байт действительно остался, то сдвинем его влево, тем самым заодно освободим правую часть для дополнения нулями до чётности
len-=2;
}
if (len) sum+=((uint32_t)*ptr)<<8;
Дальше нам по условию расчёта суммы необходимо узнать, поместилась ли наша сума в размер 16 бит. Для этого мы создадим ещё один условный цикл, в теле которого мы будем постоянно складывать между собой старшую и младшую части суммы до тех пор, пока сумма не уместится в 16-битный размер
if (len) sum+=((uint32_t)*ptr)<<8;
while (sum>>16) sum=(uint16_t)sum+(sum>>16);
И по окончанию функции нам осталось только лишь сконвертировать сумму в формат big endian, затем её проинвертировать и вернуть из функции
while (sum>>16) sum=(uint16_t)sum+(sum>>16);
return ~be16toword((uint16_t)sum);
}
Добавим в функцию чтения IP-пакета переменную для результата, а также установим указатель на пакет
uint8_t ip_read(enc28j60_frame_ptr *frame, uint16_t len)
{
uint8_t res = 0;
ip_pkt_ptr *ip_pkt = (void*)(frame->data);
}
Добавим в данную функцию фильтрацию по версии протокола, длине заголовка и соответствию адреса получателя нашему IP, и вернём результат
ip_pkt_ptr *ip_pkt = (void*)(frame->data);
if( (ip_pkt->verlen == 0x45) && (ip_pkt->ipaddr_dst == IP_ADDR))
{
}
return res;
Вычислим размер данных в байтах
if( (ip_pkt->verlen == 0x45) && (ip_pkt->ipaddr_dst == IP_ADDR))
{
//длина данных
len = be16toword(ip_pkt->len) — sizeof(ip_pkt_ptr);
Сравним контрольную сумму из поля контрольной суммой с расчитанной нами пока в текстовом виде
len = be16toword(ip_pkt->len) — sizeof(ip_pkt_ptr);
sprintf(str1,«rnip_cs 0x%04Xrn», ip_pkt->cs);
USART_TX((uint8_t*)str1,strlen(str1));
ip_pkt->cs=0;
sprintf(str1,«ip_cs 0x%04Xrn», checksum((void*)ip_pkt,sizeof(ip_pkt_ptr)));
USART_TX((uint8_t*)str1,strlen(str1));
Вызовем нашу функцию в функции eth_read
else if (frame->type==ETH_IP)
{
USART_TX((uint8_t*)«; ip»,4);
ip_read(frame, len — sizeof(enc28j60_frame_ptr));
Соберём код, прошьём контроллер и сверим пришедшую и расчитанную контрольную сумму опять с помощью утилиты ping
Всё сходится. Отлично!
Теперь можно код сравнения удалить. Мы затем будем сравнивать по-другому
sprintf(str1,«rnip_cs 0x%04Xrn», ip_pkt->cs);
USART_TX((uint8_t*)str1,strlen(str1));
ip_pkt->cs=0;
sprintf(str1,«ip_cs 0x%04Xrn», checksum((void*)ip_pkt,sizeof(ip_pkt_ptr)));
USART_TX((uint8_t*)str1,strlen(str1));
Вернёмся в функцию ip_read и узнаем тип протокола
len = be16toword(ip_pkt->len) — sizeof(ip_pkt_ptr);
if (ip_pkt->prt==IP_ICMP)
{
}
else if (ip_pkt->prt==IP_TCP)
{
}
else if (ip_pkt->prt==IP_UDP)
{
}
Добавим функцию чтения ICMP-пакета
//———————————————————
uint8_t icmp_read(enc28j60_frame_ptr *frame, uint16_t len)
{
}
//———————————————————
Вызовем данную функцию в функции ip_read
if (ip_pkt->prt==IP_ICMP)
{
icmp_read(frame,len);
}
В следующей части нашего урока мы допишем все незаконченные функции и увидим наконец-то результат нашей работы — ответы на запросы из утилиты Ping.
Предыдущая часть Программирование МК AVR Следующая часть
Техническая документация:
Документация на микросхему ENC28J60
Перечень ошибок ENC28J60 (Errata)
Приобрести плату Atmega 328p Pro Mini можно здесь.
Приобрести программатор USBASP USBISP с адаптером можно здесь USBASP USBISP 3.3 с адаптером
Ethernet LAN Сетевой Модуль можно купить здесь ENC28J60 Ethernet LAN Сетевой Модуль.
Смотреть ВИДЕОУРОК (нажмите на картинку)
Добрый день!
Большое спасибо за такую огромную работу, что проделали Вы.
Такие уроки очень нужны и важны.
Одно замечание:
uint16_t checksum(uint8_t *ptr, uint16_t len) {
uint16_t sum = 0; // вот здесь переменная объявляется 16-битная, маловато будет. Вероятно имелось ввиду uint32_t
while(len >1) {
sum += (uint16_t) (((uint32_t)*ptr<<8)|*(ptr+1));
ptr+=2;
len-=2;
}
if (len) sum+=((uint32_t)*ptr)<>16) sum=(uint16_t)sum+(sum>>16); // а тут сдвиг 16 бит на 16 бит, что особо смысла не имеет
return ~be16toword((uint16_t)sum);
}
*)
while (sum>>16) sum=(uint16_t)sum+(sum>>16); // сдвиг 16 на 16
Совершенно согласен! Студия ругается на этот момент, и железо неправильно рассчитывает сумму.