Веб-сервер за 5 минут на базе PIC и W5100

в 11:24, , рубрики: diy или сделай сам, ethernet, W5100, веб-сервер, Железо

Все, что вы хотели узнать о том, как за 5 минут запустить простой веб-сервер на чипе W5100, но стеснялись спросить.

image

В статье будет просто, подробно и ясно описано, как запустить, например, веб-сервер, на замечательной и недорогой микросхеме W5100 компании Wiznet.

Чем же она замечательна?
Во-вторых – недорогая.
И во-первых – всю работу она делает за Вас. Вам же остается лишь лениво слать-принимать ТЕКСТОВЫЕ (точнее — HTML) данные.

Дисклаймер1: Инженеграм-электронщикам с… дцатилетним стажем статья может показаться поверхностной и упрощенной, поскольку они и так (возможно) в теме. Данная статья рассчитана на тех, кто имеет некоторый опыт в электронике, или, по крайней мере, в программировании микроконтроллеров, и просто, черт подери, хочет, наконец (слитно), запустить эту… (зачеркнуто) (ладно, пусть будет «замечательную») микросхему W5100.

Дисклаймер 2: Я не ставил целью всеобщее обучение, полный перевод даташит и растолковывание всех его пунктов. Моей задачей является прояснить запутанные моменты и показать как пользоваться чипом W5100 и как запустить на нем аппликацию (например – веб-сервер).

На основе моих разъяснений и примера аппликации, каждый, кто имеет опыт в микроконтроллерах и хотя бы основные понятия в сетях – сможет разобраться во всем остальном самостоятельно.

Дисклаймер3: Пожалуйста, не проводите этот эксперимент дома. И желательно, чтобы рядом находился кто-нибудь из взрослых.

SCOPE

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

Однако, представленный в конце пример позволит Вам разобраться, как использовать интернет (точнее – Ethernet и TCP/IP ) в своих аппликациях на микроконтроллерах, и, несмотря на простоту, он может быть вполне применен практически. Вы сможете его усложнять, поскольку поймете, как работать со связкой клиент-сервер, и, наконец, имея под рукой что-то работающее и теперь уже много лучше понимая тему – Вам будет намного легче углублять свои познания, не засыпая над десятком страниц с описанием назначения регистров, принципов настройки NAT, или правильным указанием смещения фрагментов в пакетах.

("… фрагменты в пакетах.." Что то такое вроде у Вуди Аллена было?)

По своему опыту: обычно, человеку, который пишет статью, сабжект ясен и понятен, поэтому он пишет коротко и «ясно».
Однако, это часто не так для читателя, который может быть не в теме, и часто вынужден гадать, что же именно автор имел в виду, и следует ли понимать некое высказывание вот так, или совершенно наоборот? Поэтому, там, где можно, я не стану рассусоливать, а в тех местах, где нужно разжевать до полной ясности и однозначности понимания – не стану экономить слов.

При этом, помните, что никто не идеален (даже я), так что простите, если кто ушел неудовлетворенным.

Для дочитавщих до конца — бонус: в конце статьи будет приведен РАБОТАЮЩИЙ код на простом «Си» с кучей комментов, чтоб даже пятикласснику было понятно, что происходит в программе. Код написан для обычного PICa (не все ж еще вокруг AVR-щики и Arduin-щики?).

Ингредиенты

Если у вас пока нет особого желания разводить плату и паять на нее микросхему в корпусе TQFP80, рекомендую взять обычный ардуиновский Ethernet Shield на базе W5100. Также потребуется простой и недорогой PIC, макетка для PICa и немного соединительных проводов, Ethernet патч-корд и, по вкусу, сериальный кабель RS232, на случай, если вам вдруг интересно, что происходит внутри сервера во время работы (в код вставлены строки для вывода попутной информации – на терминал).

Лирика (можно пропустить)

Всем, имеющим некоторый опыт в разработке, эта ситуация знакома (или нет? Или это только я такой лузер?)

Вы берет некий чип, на котором необходимо запустить аппликацию. Для простоты допустим, что вам также знакома тема/область, в которой даннаая аппликация будет использоваться (например Ethernet, TCP/IP, HTTP, CGI etc).

Вначале Вы берете даташит на чип, в 100500 страниц,

не пропускать лирику

и внимательно его читаете. Затем вы перечитываете его еще несколько раз и начинаете, наконец, понимать, как с данным чипом работать. Затем вы берете errata и повторяете те же итерации.

После прочтения оных вы, наконец, понимаете, как чип работает, как его правильно программировать (100500 регистров -1), но пока, все же, остается не совсем ясным, как ПРАВИЛЬНО его надо запускать. (Например, имеет ли значение порядок обращение к регистрам, и если да – то какой порядок правильный, а если не имеет значения – то точно ли он не имеет значения?).

Разумеется, много полезной информации можно найти в инете и на форумах, но из всех найденных там примеров не совсем ясно, какие НА САМОМ ДЕЛЕ работают, какие работают несмотря на обилие ужасающих ошибок (о которых Вы пока и не подозреваете), а какие «примеры» – просто плод фантазии аффтара, который написал, да не проверил, надеясь, что проканает.

Ок, вы прошли все эти этапы, ТЕПЕРЬ Вам уж точно все понятно.

Вы правильно подключаете все пины чипа к чему следует, правильно подключаете чип к процессору, правильно устанавливаете все регистры и что там у него еще внутри, и – о чудо! – наконец-то…
Да-да, все в порядке – ничего не заработало.

Это самый противный этап – когда все сделано правильно и строго по даташит, но все равно не работает. Вы долго и муторно разбираетесь, в чем же проблема, и наконец находите 1...5...17 багов. Ну, конечно! Именно об этом и было сказано в даташит/эррате. Ну, теперь-то понятно ЧТО ИМЕННО они там имели в виду (а о чем вы сами могли бы догадаться).

Все.
Аппликация заработала.
Теперь Вы – спец по данному чипу.
Фанфары. Премия. Пиво. Толпы поклонниц. Слава и следующий дидлайн.

Данная статья призвана помочь читателям пропустить нудные этапы и сразу перейти к стадии «все заработало» и далее — по тексту.

Лирика — 2 (можно пропустить)

Много лет назад ( даже чуть больше), еще в до- Windows98 времена, мой товарищ поварчивал, что вот мол, развелось этих лже-юзеров, DOSовских комманд ничерта не знают, думают, что раз есть Windows 3.11, то в config.sys лезть не обязательно, а туда же, — хотят компьютер иметь.

пропустим по Лирике 2 ?

Я всегда считал, что кто-то любит ковыряться во внутрях, а кто-то просто хочет пользоваться и не париться. И оба вида имеют право на существование. (Гай Кавасаки: «… Как микроволновка может выдавать сообщение об ошибке? Микроволновка должна просто разогревать! Никто не должен проходить специальные курсы программирования микроволновок!»).

Сегодня часто слышны сетования, что, вот, мол, эти Ардуины портят народ и плодят неучей – нет чтоб отучиться лет n и набраться опыта еще лет n+1 — все бросились клепать свои проекты, не пытаясь получить глубокие знания в программировании и электронике.
Ужас, ужас.
И что?
Я считаю, что это нормально — человек с творческой жилкой и креативом придумал задумку (или наоборот) и хочет ее реализовать. Не имея глубочайших познаний. И это нормально.

Также, на заре интернета, была эдакая мода гордо указывать внизу странички: «сайт создан полностью вручную в notepad.exe. Никаких HTML-редакторов !». Ничего не напоминает?

При этом я, например, начинал не с Си и даже не с ассемблера — первый свой комп, собранный с нуля и абсолютно пустой, чтобы залить в него для начала хотя бы загрузчик, я программировал в двоичном коде 16-тью тумблерами шины адреса и 8-ю тумблерами шины данных.
Мне долго еще во сне приходили длинные ряды большик красных нулей и единичек, а когда мама послала купить хлеба, первой мыслью было:

«пойти за хлебом… это какой КОД?».

И даже я, баггер со стажем, поддерживаю прогресс, увеличившиеся возможности для творчества новичков, и разнообразие видов юзеров.

Client – Server. В общих чертах

Вероятно, есть смысл начать с конца и двигаться наверх.

Вы запускаете программу браузера, вводите в ней название сайта – и получаете на экране содержимое веб-странички.
Разумеется браузер получает не красивую страничку с картинками, а кучу текста, HTML-тэгов, линки на картинки/видео и т.п., собирает все это вместе, и выдает вам в виде красивой (иногда) веб-странички.

Все это передается между сервером и браузером по протоколу HTTP, который также управляет и установлением соединения между ними и их поведением.

Поскольку мы имеем дело с сетью/интернетом, то, для того, чтобы быть переданными по правилам, принятым в сети/интернете, все эти данные (HTML, вокруг которого «обернут» HTTP) заворачиваются в новый «слой» — TCP/IP.

TCP (Transmission Control Protocol) используется для того, чтобы обеспечить гарантированное установление соединений и доставку пакетов данных. Веб браузеры (как и почтовые клиенты) используют именно TCP.

Вероятно, я должен упомянуть о том, что помимо ТСР существует и UDP (User Datagram Protocol) который вообще никак не озадачивается такой мелочью, как установление соединения и гарантированная доставка сообщений, а раз время на это не тратит, то и строчит пакетами как из пулемета, но использоваться может только если соединение (канал) качественный и надежный, либо если не предъявляются жесткие требования к потере (точнее НЕ потере) пакетов (господи, да кто их считает!).

В данной статье UDP рассматриваться не будет, но W5100 умеет и его.

Поскольку наш веб-сервер будет соединен с компьютером через Ethernet-соединение, все это «оборачивается» в еще один слой – Ethernet frames.

На самом деле, как вы знаете, мы могли бы передавать TCP/IР даже и по обычному RS-232 каналу, безо всякого Ethernet.

Итак, мы имеем матрешку: Ethernet фрейм, состоящий из служебных байтов («заголовков») и байтов с собственно данными (Payload).

Эти данные содержат в себе TCP/IP пакеты, которые, в свою очередь, также состоят из байтов заголовков и собственно данных.

А эти последние данные, в свою очередь, состоят из HTTP-заголовков и давно уже ожидающих и успевших чуть остыть HTML-форматированного текста и/или картинок.

Сказ про Etherтet, ТСР и IP — мы опустим, потому что все эти сложности W5100 берет на себя, снимая с нас головную боль (вот в чем ее прелесть!) (я имею в виду прелесть W5100, не головной боли).

А вот на HTTP остановимся капельку подробнее, потому что именно нам придется самим забирать его у W5100 и обрабатывать (когда веб-браузер будет делать запрос к нашему серверу) и наоборот — формировать его и отдавать W5100, когда мы (сервер) будем отвечать веб-браузеру.

Полностью раскрыть все нюансы HTTP протокола в данной статье не представляется возможным (отсылаю любопытных к HTTP/1.1 RFC 2616), здесь же мы рассмотрим самое основное и то, что поможет нам поднять простой (но вполне юзабельный !) веб-сервер.

Когда мы открываем браузером веб-сайт (например: opticaldt.us — не реклама!) браузер отсылает на веб-сервер сайта запрос:

GET http://opticaldt.us/ HTTP/1.1
Host: opticaldt.us
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8,ru;q=0.6

Обратите внимание на эту строку: GET opticaldt.us

После “… us/” и до “HTTP/1.1” — пусто.
Это значит, что, по умолчанию, запрашивается файл index.html (или index.htm).
Если в данной субдиректории на сервере действительно имеется файл index.html, то браузер успешно его откроет. Если же этого файла нет (либо вообще, вы сделали запрос н отсутствующий файл с любым именем и расширением), то сервер ответит вам страничкой «error 404» (которая также возникает не извоздуха — отдать ее в ответ на неверный запрос – задача сервера. То есть вот прям лично вы должны создать такую страницу).

Как видно из заголовка, клиент (браузер) сообщает о себе много любопытного и полезного.
Поскольку наш сервер будет достаточно простым, и ничего такого особенного мы не станем посылать клиенту (например, не станем вытягивать из заголовка все интимные подробности и огорашивать юзера чем-то вроде: «Здравствуйте, вы пришли с такого-то ip, живете по такому-то адресу на втором этаже, хотя квартира вообще то не ваша, счет провайдеру закончится через 3 дня, не забудьте пополнить, вашего кота Федора пора бы покормить, с такой-то резолюцией экрана и WindowsXP негоже заходить на наш восхитительный сайт, который много лучше смотрелся бы, будь у вас не Mozilla, a IE 11, ах да, у вас ведь украинский ip, поэтому «здоровэньки булы», хотя нет, это же Евпатория, поэтому «и снова здравствуйте! И т.д. и т.п.), нас, при запросе от браузера, будет интересовать только первая строка – нам нужно будет ее, как говорят в народе «пропарсить», чтобы выяснить, какой именно файл клиент (браузер) желает получить от нашего сервера.

В простейшем варианте, просто чтоб протестировать, что сервер работает, можно вообще на любой запрос (то есть запрос любого файла) отсылать одну и ту же HTML страницу.
(Забегая вперед, в нашем примере мы все же будем отдавать «файл» index.html если запросят именно его, и страницу с ошибкой, если запрашивать будут что-либо иное).

Итак, мы сделали упомянутый выше запрос, по счастливому стечению обстоятельств на данном сайте в данном месте имеется файл index.html, и сервер отвечает браузеру сообщением, состоящим из заголовка и собственно содержимого файла index.html:

HTTP/1.1 200 OK
Date: Sun, 13 Jul 2014 21:32:49 GMT
Server: Apache
Accept-Ranges: bytes
Vary: Accept-Encoding
Content-Length: 185
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html

<html><head><meta http-equiv="Content-Type" content="text/html; charset=Windows-1251"><title>W5100 WebServer</title></head><body><center>ну шо,
<b> хелло World ?!</b></center></html>

В первой строке («… 200 ОК..») сервер сообщает, что запрос верный (то есть такой файл имеется).
В строке «Content-Length: 185» сервер предупреждает клиента (то есть браузер) о длине передаваемого файла (точнее — именно и конкретно о длине веб страницы, которая идет сразу после HTTP заголовков).

Для того, чтобы обеспечить более менее корректную работу с браузером, нам (серверу) надо будет отдавать в ответ (помимо HTML файла) по крайней мере три параметра в заголовке HTTP:

HTTP/1.1 200 OK
Content-Type: text/html ( если мы посылаем именно это, а не, например, картинку).
Content-Length: (посчитаем потом)

На самом деле, современные браузеры весьма либеральны, поэтому можно отбросить и третью строку заголовка. Тем более, что в нашем примере, после отдачи клиенту «веб-страницы», мы разрываем соединение, поэтому клиент ( браузер) и так догадается, что он получил все.

Но… Если Ваша страница будет длинной, или, например, вы собираетесь посылать картинки и т.п. — лучше все-таки указывать длину посылаемого блока данных (Content-Length).

Совсем чуть чуть о TCP/IP

Когда я писал выше, что W5100 делает за нас всю работу с Ethernet и TCP/IP, я чуть приукрасил действительность. Кое-что (совсем капельку) нам все-таки придется делать самим. А именно: управлять процессом установления соединения и его завершением.

Коротко говоря, сервер и клиент общаются пересылкой друг-другу IP пакетов, которые могут содержать «полезные» данные (например – HTML страничка), а могут быть чисто служебными, без «полезных» данных.

Откроем даташит микросхемы W5100 (да-да, теперь уже пора) и рассмотрим, как общаются клиент (браузер) и W5100 в режиме сервера:

image

Вначале W5100 в режиме OPEN открывает/создает сокет (сетевое соединение) и переходит в режим «слушания» (LISTEN), дожидаясь запроса от клиента (т.е. браузера).

Когда запрос от клиента приходит, и между сервером и клиентом устанавливается соединение, сервер (W5100) переходит в режим ESTABLISHED и между сторонами происходит обмен данными.

На заметку: по уставу, максимальный размер данных в IP пакете – 1500 байт. Если ваши данные не укладываются в один пакет (большой размер HTML кода, или нужно передать картинку итп) – придется разбить данные на порции менее 1500 байт и передавать их одну за другой.

Далее, когда стороны высказали друг-другу все, что в душе нагорело, кто-то из них (как и в жизни) первым «бросает трубку», посылая запрос на разьединение (Disconnect Request).

Примечание: не всегда разьединение наступает именно после (полной) передачи данных. Например, у вас медленный канал, или пошли помехи, и браузер устал ждать завершения и прекратил соединение по своему тайм-ауту. Либо что-то не заладилось у веб-сервера и он решил прервать соединение в самом интересном месте. Ну, в общем, всякое ведь в жизни бывает.

После того, как связь между ними разорвана, сервер переходит в режим закрытия соединения (сокета) — CLOSED (не, ну все, что нужно – было сказано/отослано, че еще ждать то?).

Все.

Если вы сторонник мимолетных связей и/или одного раза Вам хватило с лихвой – на этом можно остановиться.
Если же вы оптимист по жизни ( или просто настойчивый) и полагаете, что к вашему серверу, вполне возможно, будут еще обращаться, и не раз — вам просто следует вернуться к шагу “OPEN” и далее — по картинке. (Порядочные сервера обычно так и поступают). Хотя… может быть в будущем появятся дешевые пластиковые одноразовые сервера? Типа использовал 1 раз – и выбросил?

Можно было бы обойтись и без этого ( благодаря W5100 ), но я все же упомяну. В реальной жизни при каждом обмене акетами между клиентом и сервером и при каждой смене статуса соединения, в пересылаемых пакетах устанавливаются флаги, говорящие о том, что сейчас происходит и как с этим жить.

Возьмем самый обычный IP стек:

Пусть не испугает никого красивое слово «стек», колдуны и маги от IBM-360 и AS-400 специально выдумывают заумные термины, чтобы подавить у простых смертных всякое желание потрогать немытыми руками их Храм. На самом деле это просто протокол, формат передачи данных, в котором расписано какой длины и в каком следуют порядке служебные поля и собственно данные. (Жванецкий: «… и даже болгары не могут понять нашего выражения, что «несмотря на погодные условия» — означает просто дождь!»)

image

Нас интересуют три старших бита в седьмом байте: ФЛАГИ.

Комбинациями этих трех битов формируют следующие флаги: FIN, SYN, RST, PSH, ACK, URG.

FIN — указывает на окончание сессии;
SYN — инициализация соединения;
RST — прерывание соединения (обычно – в ответ на ошибку);
ACK — подтверждение получения ( пакета);
PSH — упрощенно — требование немедленной высылки подтверждения;
URG — указание на архисрочноть данного пакета.

Если вам любопытно (что похвально и полезно), как же в реальности происходит обмен пакетами и какие флаги при этом выставляются и в каких случаях, а ко всему еще — для чего предназначены остальные поля заголовка (хорошо, хорошо, — «стека») – вам придется найти это самостоятельно. В данной статье мы рассматривать это не будем, поскольку W5100 берет всю заботу о пакетах и флагах на себя, мы же имеем дело с уже свершившимся фактом – статусом соединения на данный момент (OPEN, LISTEN, ESTABLISHED, CLOSED).

Краткое, насколько это возможно, описание W5100

Что ж, далее оттягивать этот момент не представляется возможным, пора рассмотреть W5100 с железной и программной сторон.

image

W5100 с одной стороны подключается к Ethernet коннектору (через трансформатор и согласующие цепи), с другой – с микроконтроллеру.
К микроконтроллеру ее можно подключать (по вкусу и ситуации) либо с использованием шин адреса и данных, либо через SPI интерфейс.
Как именно подключать чип – я расписывать не стану, готовых схем с описанием полно в интернете. Тем более (ладно, раскрою секрет), что мы будем в нашем примере использовать готовую плату с этим чипом.

Чип содержит в себе Ethernet PHY и МАС, реализованные в железе обработчики Ethernet фреймов и нескольких протоколов группы IP, позволяет создать до 4 сокетов (сетевых соединений) и содержит RX и ТХ буферы по 8Кбайт памяти каждый, которые можно распределять между каждым сокетом по-братски или по справеделивости (или поровну), выделяя 1,2,4 или 8 КБ на сокет (но не забывая, что размер всего буфера – 8КБ на прием и 8КБ на передачу).

Распределение памяти:

image

Как видно из рисунка:

с адреса 0х0000 по адрес 0х002F расположены общие для всех регистры,
с адреса 0х0400 по адрес 0х07FF – 4 одинаковые группы регистров – по одной на каждый сокет,
с адреса 0х4000 по адрес 0х5FFF — буфер передачи ( 8КБ), который вы можете распределить между сокетами по вашему усмотрению,
с адреса 0х6000 по адрес 0х7FFF — буфер приема ( 8КБ), который вы также можете распределить между сокетами по вашему усмотрению.

Все регистры – 8 битные. Большинство можно и читать и писать.

Мы не станем рассматривать все регистры, а только те, которые потребуются нам для создания веб-сервера, однако, Вы легко сможете понять общую идею и далее развить ее под свои конкретные задачи.

Важный момент: в W5100 используется порядок байт, который в народе называется «биг эндиэн» или «сетевой порядок». Глубокий же научный смысл сводится к тому, что сначала идет (передается) старший бит старшего байта и последним – младший бит младшего байта. Это значит, что, например, для записи 4 байт ай-пи адреса Вашего сервера (скажем 192.168.0.155) в предназначенные для этого регистры Source IP Address 0x000F … 0x0012 (SIPR0 … SIPR3), Вам следует записать «192» в регистр SIPR0, «168» — в SIPR1, «0» — в SIPR2 и «155» — в SIPR3.

Общие регистры

(Общие – значит влияют на работу всего чипа и всех сокетов, тоесть не относятся к конкретному сокету).

0х0000 MR — Mode – тут все понятно – через них мы задаем режим работы;
0х0001 ..0x0004 GAR0..3 — Gateway Address (напр. 192.168.0.254 );
0х0005… 0х0008 SUBR0..3 — Subnet mask Address (напр. 255.255.255.0);
0x0009..0x000E SHAR0..5 — Source Hardware Address
( проще говоря – МАС адрес, например 0A 1B 2C 3D 4E 5F).
(если Вы не собираетесь наводнить рынок Вашими серверами, то можете указать любой МАС, главное чтобы в вашей сети не было устройств с таким же МАС адресом – нам ведь не нужны конфликты, верно?)

0x000F… 0x0012 SIPR0..3 – Source IP Address – адрес нашего веб-сервера, например: 192.168.0.155;
0х001А — RMSR – RX Memory Size — здесь Вы указываете сколько памяти буфера приема придется на каждый сокет;
0х001В — ТMSR – ТX Memory Size — здесь Вы указываете сколько памяти буфера передачи придется на каждый сокет;

Регистры сокетов

В нашем примере мы будем работать только с одним сокетом и только с теми его регистрами, которые нужны для TCP/IP сервера, поэтому рассмотрим лишь эти регистры:

0х0400 S0_MR — Socket0 Mode Register — устанавливаем режим работы данного конкретного сокета;
0х0401 S0_CR — Socket0 Command Register– здесь мы будет выставлять команды для управления состоянием соединения;
0х0403 S0_SR – Socket0 Status Register — отсюда мы будем читать данные о текущем статусе происходящего;
0х0404, 0х0405 S0_PORT0, S0_PORT1 — Socket0 Source Port — порт, по которому доступен наш сервер (если корректнее – порт по которому доступен открытый нами сокет).

Если по-честному, то обращаясь к сервису, мы должны указывать полностью и протокол, и адрес сервера данного сервиса, и номер порта (а также и название файла и полный путь к нему).

Например, если нам нужен файл index.html, находящийся на сервере с адресом 192.168.0.155 с доступом к сокету через порт 123, то в строке URL браузера (то есть в адресной строке) нам надо будет написать:

192.168.0.155:123/index.html (подождите, пока не нажимайте Enter).

Но… Для соединений вида «http://… » (то есть для протокола HTTP) принято выделять порт 80.
Поэтому, если мы назначим для Socket0 Source Port порт номер 80, то можем не вводить его в адресной строке браузера, поскольку, если порт не указан, браузер по определению будет обращаться именно к этому порту, в результате URL вида 192.168.0.155/index.html вполне будет работать без ошибок. (Говоря более обобщенно — это справедливо для всех случаев, для которых имеются дефолтные порта
– если вы их не указываете при доступе к сервису, клиент идет по дефолтному порту: 80 для HTTP, 23 для Telnet и так далее).

Также, как мы разбирали ранее, если не указывать название запрашиваемого файла, то обеими сторонами принято подразумевать, что речь идет о файле index.html (или index.htm) (или index.php). Поэтому, указав в адресной строке браузера: 192.168.0.155, мы получим верный отклик сервера (то есть, если клиент на запрашивает у нас конкретное имя файла, то мы понимаем, что он ожидает именно файл index ) (при этом помним, что если, например, клиент запросил директорию, содержимое которой мы стесняемся показывать всем и каждому (например – картинки с котиками, или скрипты и т.п.) то вполне комильфо будет выдать ему страничку «ошибка 404»).

Ну и чтоб совсем уж сэкономить время на ввод адреса — если не указывать тип протокола, то браузер автоматически будет считать, что речь идет о HTTP (если, конечно, браузер – не IE который любит повы… Ок, не важно, забыли).

Таким образом, просто написав в строке адреса 192.168.0.155 — все стороны поймут, что речь идет о 192.168.0.155:80/index.html

Значит, решено, да? Зададим в нашем сервере адрес порта сокета – 80.
При этом не забываем про «биг – эндиэн», поэтому 80 (точнее – 0080) запишется так: S0_PORT0 = 00, S0_PORT1 = 80 (да, и не забудьте – это десятичные «80», а не хекса).

0х0420, 0х0421 S0_TX_FSR0, S0_TX_FSR1 — Socket0 TX Free Size – размер свободного места в буфере передачи Сокета 0;
0х0422, 0х0423 S0_TX_RD0, S0_TX_RD1 — Socket0 TX Read Pointer (* только чтение) – указывает в буфере передачи на начало блока данных, которые (данные) мы собираемся послать клиенту.

Тут есть хитрая фишка ( Wiznet решила подгорчить канфэтку): если, например, вы прочли Socket0 TX Read Pointer, и он указывает, например, на адрес 0х4200, то… правильно — это совершенно не означает, что вы должны свои данные на передачу писать в буфер начиная именно с этого места. Ага, ага… («если женщина за рулем показывает левый поворот – это вовсе не означает, что она собирается ехать прямо!» (с)). Подробнее об этом мы поговорим чуть позже.

0х0424, 0х0425 S0_TX_WR0, S0_TX_WR1 — Socket0 TX Write Pointer – указывает в буфере передачи на конец блока данных, которые (данные) мы собираемся послать клиенту.

Тут необходимо (снова) сделать важное пояснение.
Два последних регистра не определяют физические адреса, по которым мы должны записать в буфере блок данных на передачу. Эти адреса мы должны вычислить сами.

Еще одно примечание: допустим, Вы выделили на буфер передачи 2КБ. Вы прочли Socket0 TX Read Pointer и оказалось, что он указывает на последние 10 байт буфера. А Вы вообще то намеревались передать (т.е. записать в буфер) 1000 байт. Что делать?
Все просто: вы пишете свои данные на передачу в эти 10 байт, затем переходите в самое начало буфера и продолжаете писать оставшиеся данные дальше (тоесть из 1000 байт Ваших данных, 10 байт разместятся в конце буфера, а остальная порция в 990 байт – в начале буфера).
W5100 знает об этом безобразии ( собственно говоря, он их и намутил) и потому, дойдя до конца – сам перескочит в начало буфера и терпеливо дочитает оставшуюся порцию.

Ну а сейчас давайте разберемся с этой мутотенью — как из пойнтеров, указанных в регистрах, вычислить физические адреса, по которым Вам и следует писать данные в буфер передачи.

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

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

Итак, введем для удобства переменные:

S0_TX_BASE — базовый адрес буфера передачи Сокета0.

Если вы посмотрите на картинку распределения памяти, то он вроде бы равен 0х4000.
Ну а вдруг у вас чип W5100 сидит не на нулевом адресе? То есть, например, чиповский «нулевой» адрес смещен в адресном пространстве вашей системы, и в вашей системе ( например) нулевой адрес чипа равен 0х1984.
Далее: если с Сокетом0 все ясно, то базовый адрес Сокета1 (помимо потенциального смещения адреса чипа) зависит еще и от того, сколько Вы выделили памяти в буфере на каждый сокет.
Тоесть можно опять же жестко забить, а можно «по-правильному» прочесть TMSR регистр (в случае буфера передачи), узнать оттуда сколько буферной памяти распределено по каждому сокету, и таким образом «вычислить» где начинается область буферной памяти для каждого сокета.

В общем, я Вас предупредил, а в своем примере я его, для простоты, жестко забил:
S0_TX_BASE = 0х4000.

S0_TX_MASK — маска, понадобится нам далее в «расчетах».

Точно также, маску, по-хорошему, надо вычислять для каждого сокета исходя из того, что записано (вами же, кстати) в регистре распределения памяти буфера между сокетами (TMSR).

Я ничего не вычисляю (раньше я думал, что я – лентяй, но, став постоянным читателем Хабра узнал, что все в порядке – у меня просто прокрастинация), на каждый сокет я выделил по 2КБ (которые, как вы помните, равны не 2000 грам, но 2048 байт), соответственно маска — это 2048 -1 = 2047 ( 0x07FF ).

Итого, у нас в примере будет: S0_TX_MASK = 0x07FF.

Теперь добавим путаницы, а затем с честью ее распутаем:

Как я писал выше, регистры (read only) S0_TX_RD0, S0_TX_RD1– это указатель на начало блока Ваших данных на передачу, расположенных в буфере передачи сокета.
Если говорить более корректно – это указатель на тот конкретный байт данных, который будет передаваться наружу, в сеть, в данный момент (ну, тоесть не прям щаз пока вы читаете, а когда дело дойдет до передач).
И данные будут передаваться до тех пор, пока текущий адрес (в буфере) передаваемого байта не дойдет до конца блока ваших данных, который (указатель на конец блока), записан в паре регистров S0_TX_WR0, S0_TX_WR1.

Тоесть, по факту, W5100 вытаскивает из буфера передачи байт ваших данных, на который указывает пара регистров S0_TX_RD0, S0_TX_RD1, выплевывает его наружу в сеть, затем инкрементирует регистр и переходит к следующему байту, на который этот регистр указывает, и так этот регистр (пара) и инкрементируется, пока не сравняются по значению с парой регистров S0_TX_WR0, S0_TX_WR1, и в этот момент передача данных закончится. (Соответственно, передав все ваши данные, содержимое S0_TX_RD0, S0_TX_RD1 станет равно содержимому S0_TX_WR0, S0_TX_WR1).

Джентльмены, я прощу прощения за всю эту нудятину, очень не хотелось никого грузить, но без этого – никак (умом Визнету не объять). Потерпите еще чуть чуть, это скоро закончится, coffee break совсем близко.

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

Наплодим несколько переменных:

Смещение: S0_TX_OFFSET = make16 ( S0_TX_RD0, S0_TX_RD1 )

// создаем 16-разрядную переменную «смещение» из двух 8-разрядных регистров-пойнтеров
// не забываем, что поскольку «биг-эндиэн», старший байт находится в регистре S0_TX_RD0,
// а младший — в S0_TX_RD1

По жизни, этот пойнтер может указывать вообще за пределы адресного пространства чипа (когда почитаете эти регистры в живой аппликации – убедитесь сами), поэтому надо отрезать лишнее, чтобы ячейка памяти, на которую указывает пойнтер, лежала внутри отведенной для данного Сокета зоны (то есть в физическом диапазоне адресов).
Придадим значению регистров более близкую к реальности величину (значение):

S0_TX_OFFSET = ( S0_TX_OFFSET & S0_TX_MASK )

Теперь вычислим настоящий, физический адрес, по которому нам надо начинать записывать наши данные на передачу:

S0_TX_Start_Addr = S0_TX_OFFSET + S0_TX_BASE

Ну что ж, теперь у нас есть пойнтер, который указывает чипу с какого места начинать считывать байты на передачу, мы рассчитали физический адрес в буфере передачи нашего сокета (Сокет0), и (допустим) мы уже залили наши данные на передачу — в буфер передачи чипа, начиная с рассчитанного нами физического адреса.

Как чип узнает, до каких пор ему считывать данные из буфера передачи?
На это ему укажет упомянутый ранее регистр (пара) – пойнтер Socket0 TX Write Pointer.

Пока что пойнтер чтения Socket0 TX Read Pointer и пойнтер записи Socket0 TX Write Pointer содержат одинаковые значения.
Потому что (см. выше) после передачи блока данных их содержимое становится одинаковым, и подозреваю (в даташит об этом ни слова), после ресет чипа – тоже.

Итак, последний штрих – нам необходимо указать на конец блока данных в буфере передачи.
Внимание! — в отличие от физического адреса, который нам был нужен чтобы загрузить данные в буфер передачи, здесь мы будем оперировать именно с внутренним чиповским представлением самого себя – в памяти.
Тоесть мы просто увеличим содержимое пойнтера записи на длину передаваемого блока данных:

Socket0 TX Write Pointer = Socket0 TX Write Pointer + Длина_Блока_Данных.

Предполагая, что у Вас уже голова кругом идет, напомню, что под «Socket0 TX Write Pointer» (16 бит) мы подразумеваем содержимое пары 8-битных регистров S0_TX_WR0 (старший байт) и S0_TX_WR1 (младший байт).

(И — да, — если устали – можете попить кофе и перекурить, я подожду...)

Стойте! Перед перекуром… Даже не знаю, как вам сказать… Мы сейчас рассмотрели работу с регистрами при передаче данных. Понимаете, дело в том, что с регистрами приема данных – примерно такая же байда – преобразование пойнтеров – в физические адреса, длина блока.
Давайте вот как сделаем: принцип то Вы наверное поняли? В примере программы на Си я все этапы буду подробно комментировать, так что не запутаетесь. А если все же что-то останется непонятным – я в комментах отвечу. Deal?

Алгоритм

Если у меня и была надежда получить инвайт за статью, то она тает с каждым новым абзацем.

Да — наверняка думают читатели — вот это купились так купились на »… сервер за 5 минут"! Подождите — это вы еще код не видели! Нет, то есть он, конечно, не короткий, а немного наоборот, но это все изза обилия комментов и вставок для отладки. Если лишнее поубирать, чтобы остался только полезный код — его вообще можно записать на салфетке! И потом, лучше потратить 10 минут на чтение тут, чем 2 дня на чтение даташит в 100 страниц. Ну и опять же — «тяжело в ученье — 5 минут в бою» — никто ж не отменял.

Да, так вот, — алгоритм.

Я, как правило, люблю понимать, что делается в программе, и предпочитаю иметь фло-чарт. Точно так же, несмотря на непреодолимое желание сразу писать код, обычно мне удается себя одернуть и начать с рисования фло-чарт, чтобы вначале отладить логику работы на нем, а уж потом переходить к коду. Так что, давайте разберемся как будет работать наш веб-сервер (см. картинку):

image

Если принцип «лучше 100 раз увидеть — чем 1 раз услышать» еще работает, то, наверное, нет смысла описывать то, что написано на картинке?

Всего два слова:

Управление процессом будет происходить через S0_CR (Socket0 Command Register) (0х0401).

Проверка текущих состояний ( соединения и прочих) – через S0_SR (Socket0 Status Register) (0х0403).

В принципе, проверка состояний может осуществляться через регистр прерываний Сокета (то есть по прерываниям), но, подозреваю, в большинстве ваших аппликаций суперскорость не потребуется и для таких случаев работа через опрос Регистра Состояния – вполне оправдана.

Подключение Ethernet Shield — к PIC контроллеру

image

Здесь опять же нет смысла рассусоливать — на картинке все обозначено.

Совет: 2х3 хидер на плате (он под платой, на рисунке не виден) — довольно хлипкий и глючный. Рекомендую не пользоваться им, а подпаять провода прямо на плату, со стороны компонентов.

Подключение платы – к пинам микроконтроллера рисовать не стал. Это расписано в программе и, кроме того, Вы можете подключать вообще как угодно, просто изменив в программе назначения пинов по своему вкусу.

Программа (проверено – работает!)

Как настоящий ученый (?), прежде, чем проверять что-то на людях (или лабораторных животных) – я вначале проверил ее работу на себе, поэтому можете ей доверять, если вы еще не утратили эту способность.

Программа написана на CCS компайлере. Ее без проблем легко переделать (портировать – слишком громко будет сказано) на любой удобный IDE.

В качестве контроллера использовался PIC16F77 (просто оказался под рукой на готовой демо-платке). Точно также вы легко можете подправить ее (практически – одной строчкой в определении процессора) под любой PIC. Ресурсов она жрет мало, а если повыкидывать все функции print (полезные при отладке но никак не влияющие на программу), то код уменьшится еще раза в два.

Как я говорил ранее, пример напичкан «принтами» для вывода происходящего через RS232 — на экран терминала, для облегчения понимания процесса. Вы можете их спокойно поудалять – на работу сервера это никак не повлияет.

В программе сервера мы будет просто (простейшим способом) анализировать заголовок HTTP от клиента, чтобы узнать, какой файл он запрашивает. На самом деле мы будем искать запрос файла index.html «по умолчанию». То есть, когда название файла не прописывается – подразумевается именно индекс файл.

Запрос этот выглядит так:

GET / HTTP/1.1

Обратите внимание на пробел после первого "/" и до «HTTP» – в этом месте могла бы быть ваша реклама могло быть имя запрашиваемого файла.

Раз там пусто ( пробел) – значит запрашивается именно index.html. (Ок, объяснение не совсем корректно. Обычно, заходя на сайт, вы просто указываете имя сайта, безо всяких там слэш и имени файла. Пацаны с обеих сторон проплатили кому надо, договорились, что, если имя файла не указано, то будем выдавать в ответ индекс файл).

Например ввод в адресной строке браузера такого адреса:

192.168.0.155/kotiki.jpg

Отошлет на сервер запрос, который выглядит так:

GET /kotiki.jpg HTTP/1.1

Я к чему это так долго размусоливаю — мы будем просто проверять есть ли в HTTP заголовке пробел после слэш и если есть – отсылать клиенту «страничку» index.html (ну ооочень простую). А если там хоть что-то другое — то страничку с ошибкой_404.

То есть все будет очень простенько, потому что у нас и пример ведь тоже простой, причем пример запуска веб-сервера а не файловой системы и генерации HTML кода.

Но… (ааааа! афтар достал уже!) ага, опять но. Тут вот какая фишка: стандартный HTTP заголовок от сервера, в случае «страница не найдена», начинается так:

HTTP/1.1 404 Page not found

(В отличие от ситуаций когда все ок, и сервер отсылает HTTP/1.1 200 OK)

Если мы отправим браузеру нашу HTML страничку с каким угодно красивым сообщением об ошибке, и таким вот заголовком, то большинство из них красиво отобразят нашу красивую страничку. Кроме Internet Explorer. Он, получив такую строку в заголовке, просто похерит нашу страничку и выдаст свой ответ Керзону в стиле а-ля Internet Explorer.

Также, как бы мне ни не хотелось Вас утомлять, надо указать еще и вот на что: каждая строка в HTTP заголовке должна заканчиваться символами «возврат каретки» и «перевод строки» — r n ( или в кодах ASCII: 0D 0A ). И желательно именно в таком порядке, иначе некоторые браузеры ( не буду снова упоминать его всуе) могут Вас не понять. Далее, между последней строкой HTTP заголовка и началом HTML тэгов обязательно должна быть пара этих сочетаний (то есть rnrn или 0D0A0D0A в кодах ASCII) иначе не только сами_знаете_какой браузер но и другие тоже Вас не поймут.

А, ну и еще ( все-таки ценный опыт и Вам стоит о нем знать). Еще одна ложка дегтя в бочку чересчур щепетильного браузера (ну, вы поняли): я как-то ошибся и в HTTP заголовке, в указании длины последующего далее HTML кода (Content-Lenght: ...) указал буквально на 1 символ больше, чем на самом деле (ну, то есть символов, скажем, 665, а я указал, что их будет 666).
Так вот, все браузеры проглотили страничку глазом не моргнув, а IE честно до посинения дожидался, когда придет 666-й символ и пока он не прийдет – отказывался вообще показывать страничку.

Я обычно пишу программы составляя их из логических блоков-функций, а сами функции придумываю-расписываю потом. Поэтому у меня в примере вначале идет объявление функций, потом main(), и в самом конце – раписаны подробно функции. Кроме того, мне больше нравится, когда вначале ( и недалеко от констант и переменных) идет main() и можно быстро его просмотреть и разобраться в происходящем, а не продираться через тонны функций, прежде чем добраться до основного блюда.

Ах да, еще: я не стал разбивать программу на несколько файлов, а сделал все одним файлом, чтобы Вам легче было в ней ориентироваться.

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

И, вы не поверите, но мы дошли до конца.
Вот прям в двух дюймах ниже — та самая обещанная программа «за 5 минут».

Ну-с, понеслась!

// =====  Простой веб-сервер на базе W5100 от  Wiznet  ======
//   * Для удобства использован Ардуиновский Ethernet Shield  
//   на базе W5100
//===========================================================


#include    <16F77.H>
 

#fuses HS                  // внешний кварц, высокочастотный

#use   delay(clock=16M) )  //   используется кварц на 16 МГц
                           //  если у Вас другой – укажите здесь

#use   rs232(baud=19200, xmit=PIN_A2, rcv=PIN_A3)
// определяем пины для RS232
// *  в CCS компайлере функция « printf» - встроенная, 
//  и можно использовать  вообще ЛЮБЫЕ пины ввода-вывода
// ** не реклама
 


// определяем пины PICa  для подключение к Ethernet  Shield

#define RESET   pin_B2       // *active LOW
#define MISO    pin_B3       //  input
#define MOSI    pin_B5       //  output
#define SS      pin_B0       // output.  SlaveSelect,  Active LOW
#define SCK     pin_B1       // output.  

  

//********** прописываем регистры   W5100  *********************
//**************************************************************

#define MR    0x0000  // MODE register

#define GAR0  0x0001  // GATEWAY Addr register0   (MSB)
#define GAR1  0x0002  // GATEWAY Addr register1
#define GAR2  0x0003  // GATEWAY Addr register2
#define GAR3  0x0004  // GATEWAY Addr register3   (LSB)

#define SUBR0 0x0005  // SUBNET MASK Addr register0  (MSB)
#define SUBR1 0x0006  // SUBNET MASK Addr register1
#define SUBR2 0x0007  // SUBNET MASK Addr register2
#define SUBR3 0x0008  // SUBNET MASK Addr register3   (LSB)

// MAC адрес сервера
#define SHAR0 0x0009  // SOURCE HARDWARE  Addr register0 (MSB)
#define SHAR1 0x000A  // SOURCE HARDWARE  Addr register1
#define SHAR2 0x000B  // SOURCE HARDWARE  Addr register2
#define SHAR3 0x000C  // SOURCE HARDWARE  Addr register3
#define SHAR4 0x000D  // SOURCE HARDWARE  Addr register4
#define SHAR5 0x000E  // SOURCE HARDWARE  Addr register5  (LSB)

#define SIPR0 0x000F  // Source IP  Addr register0  (MSB)
#define SIPR1 0x0010  // Source IP  Addr register1
#define SIPR2 0x0011  // Source IP  Addr register2
#define SIPR3 0x0012  // Source IP  Addr register3  (LSB)



#define RMSR  0x001A  // RX memory size  (1K,2K,4K or 8K per socket, from total 8K)
#define TMSR  0x001B  // TX memory size  (1K,2K,4K or 8K per socket, from total 8K)



//-- Регистры  Socket0 (* в примере используется только этот Сокет )

#define S0_MR   0x0400   // Socket0 MODE register
#define S0_CR   0x0401   // Socket0 COMMAND register

 
#define S0_SR   0x0403   // Socket0 STATUS register

#define S0_PORT0   0x0404   // Socket0 SOURCE Port register0 (H byte)
#define S0_PORT1   0x0405   // Socket0 SOURCE Port register1 (L byte)

 

#define S0_TX_FSR0    0x0420   // Socket0 TX Free SIZE register0
#define S0_TX_FSR1    0x0421   // Socket0 TX Free SIZE register1

#define S0_TX_RD0    0x0422   // Socket0 TX Read POINTER register0
#define S0_TX_RD1    0x0423   // Socket0 TX Read POINTER register1

#define S0_TX_WR0    0x0424   // Socket0 TX Write POINTER register0
#define S0_TX_WR1    0x0425   // Socket0 TX Write POINTER register1


#define S0_RX_RSR0  0x0426   // Socket0 RX Received SIZE register0 (H byte)
#define S0_RX_RSR1  0x0427   // Socket0 RX Received SIZE register1 ( L byte)

#define S0_RX_RD0  0x0428   // Socket0 RX Read POINTER0  (H byte)
#define S0_RX_RD1  0x0429   // Socket0 RX Read POINTER1  (L byte)



// ----- Коды команд ( используются в  Регистре Команд Сокета0 ) -----
#define OPEN     0x01
#define LISTEN   0x02
#define CONNECT  0x04
#define DISCON   0x08
#define CLOSE    0x10
#define SEND     0x20
#define SEND_MAC   0x21
#define SEND_KEEP   0x02
#define RECV     0x40


// ----- Коды состояний ( используются в Регистре STATUS  сокета0 ) ---
#define SOCK_CLOSED     0x00
#define SOCK_INIT       0x13
#define SOCK_LISTEN     0x14
#define SOCK_ESTABLISHED   0x17
#define SOCK_CLOSE_WAIT    0x1C
 

// ---------- определяем «свои» переменные  -----------
#define SERVER_IP0   192   //  Наш сервер будет  192.168.0.155
#define SERVER_IP1   168
#define SERVER_IP2    0    
#define SERVER_IP3   155    

#define SERVER_PORT0   0   //  Наш порт будет       :80
#define SERVER_PORT1   80
 
#define GATEWAY_IP0   192  // Гэйтвэй адрес. Если у Вас другой - измените
#define GATEWAY_IP1   168
#define GATEWAY_IP2   0
#define GATEWAY_IP3   254

#define SUBNET_MASK0   255  // Маска подсети  ( Типовая)
#define SUBNET_MASK1   255
#define SUBNET_MASK2   255
#define SUBNET_MASK3   0

#define MAC0   0x00  // МАС адрес любой, главное, чтобы в Вашей сети
#define MAC1   0x1A  // не было устройств  с таким же  МАС
#define MAC2   0x2B
#define MAC3   0x3C
#define MAC4   0x4D
#define MAC5   0x5E

 
 
 
//----------- объявляем собственные функции  ---------------

void SSPI_write(int Data);      // запись  1 байта в W5100   через SPI
int  SSPI_read ( void);         // чтение 1 байта из W5100 через SPI 

void SetW5100register (int16 regaddr, int data);  
                // установка(запись) грегистров W5100  через SPI

int  GetW5100register (int16 regaddr);               
                // чтение байта из регистров W5100 через SPI


void Init (void);          // инициализация W5100  и Системы

int Open_Socket0(void);    // открывает сокет и возвращает статус (удачно/неудачно)

int Listen_Socket0(void);   // слушает сокет и возвращает статус (удачно/неудачно)

int Socket0_Connection_Established (void);   
     // проверка или соединение установлено и возвращает статус  (да/нет)

int Socket0_Received_Data_Size (void);       
      //  возвращает размер блока ДАННЫХ, принятых от Клиента ( 0 если данных нет) 

            void Socket0_Received_Data_Reading (void);
           // выводит на терминал принятые данные
           // после отладки ф-ция может быть удалена
 
int  Socket0_FIN_Received(void);      
  // проверяет  пришел ли от Клиента флаг FIN  и возвращает да/нет

void Socket0_Disconnect(void);  
  // пишет в регистр W5100  команду на разрыв соединения

int  Socket0_Closed(void);    // поверяет или Сокет ЗАКРЫТ и возвращает да/нет          

int  Socket0_Connection_Timeout(void);  
   // проверяет не произошел ли тайм-аут соединения  и возвращает да/нет

void Socket0_Closing(void);           // закрывает Сокет

int  Socket0_Received_Request_is_index_html(void);  
  // проверяет или  в данных принятых от Клиента запрашивается файл Index.html
  //  и возвращает да/нет


void Socket0_Send_index_html (void);  // отсылает Клиенту  «страничку»  index.html

  
void Socket0_Send_404_error (void); // Отсылает Клиенту  «страничку»  «ошибка 404»


//------------  собственные переменные  -----------------------------------

int16 S0_RX_BASE;      //  начальный адрес памяти, выделенной в RX буфере для Сокета0
int16 S0_RX_MASK;      // РАЗМЕР RX  буфера Сокета0
int16 S0_RX_OFFSET;    // указатель на начало принятого блока данных в RX буфере 
int16 S0_RX_Start_Addr; // ФИЗИЧЕСКИЙ  адрес начала принтого блока данных в RX буфере 
int16 S0_RX_RSR ;       //  РАЗМЕР принятого от Клиента блока данных

int16 S0_TX_BASE;    // начальный адрес памяти, выделенной в ТX буфере для Сокета0
int16 S0_TX_MASK;     // РАЗМЕР ТX  буфера Сокета0
int16 S0_TX_OFFSET ;  
   // указатель на начальный  адрес в памяти  ТХ буфера, куда следует записать блок 
   // данных для передачи Клиенту  

int16 S0_TX_Start_Addr;  
       // ФИЗИЧЕСКИЙ  Начальный адрес в памяти ТХ буфера, куда следует записать 
       // блок  данных для передачи Клиенту  

int16 S0_TX_End_Addr; 
   // указатель на Конечный адрес в памяти ТХ буфера, до которого W5100
   //  должен дойти при считывании данных отсылаемых Клиенту


// --- строки символов для использования как HTTP  заголовки и «веб-странички»
// * размер  массива [222] взят с потолка и с запасом. Потом программа  расчитает  реальную длину данных
// ** CONST – чтобы  расположить строку в ПРОГРАММНОЙ памяти ( где места полно)

CONST  char INDEX[222]  = { "HTTP/1.1 200 OKrnContent-Type: text/htmlrnContent-Lenght: 43rnrn<HTML><CENTER>HELLO WORLD !</CENTER></HTML>"};
// символы для  создания  странички «index.html”

CONST  char ERROR404[222] = { "HTTP/1.1 200 OKrnContent-Type: text/htmlrnContent-Lenght: 39rnrn<HTML><CENTER>ERROR 404</CENTER></HTML>"};
// символы для  создания  странички «ошибка 404”

 
// закомментил чтоб была на примете,
//  Можно  использовать и такой вариант для «ошибка 404», код сам по себе верный, но...
//CONST  char ERROR404[222] = { "HTTP/1.1 404 Page not foundrnContent-Type: text/htmlrnContent-Lenght: 39rnrn<HTML><CENTER>ERROR 404</CENTER></HTML>"};
// .. но IExplorer  получив в HTTP заголовке "HTTP/1.1 404 Page not found" 
// отобразит СОБСТВЕННУЮ страницу ошибки, а НЕ ту, что мы ему посылаем






         int try=0;  //  временно, для отладки. Номер обращения браузера –к серверу


//===================== MAIN ============================================
//=======================================================================
void main()
{

   
Init();   // инициализация  W5100  и системы ( сервера)

 

OpenSocket0:     

//ooooooooooooooooooo ОТКРЫВАЕМ СОКЕТ 0 oooooooooooooooooooooooo

 if ( ! Open_Socket0() ) goto OpenSocket0; // цикл пока Сокет не откроется


 
//ooooooooooooooooo  СЛУШАЕМ СОКЕТ  ooooooooooooooooooooooooo

 if ( Listen_Socket0() == FALSE ) goto OpenSocket0;

            // если  сокет не «прослушивается» - уходим заново на открытие сокета

 
//oooooooooooooo Соединение УСТАНОВЛЕНО ? oooooooooooooo

CheckConnection:     //метка

  if (Socket0_Connection_Established() == FALSE ) goto CheckConnection;
// цикл пока соединение не установится ( тоесть пока не придет запрос от браузера)
    
                   printf("> Connection Established... rn");
                   // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ
                   
                   
                   
//ooooooo( соединение уже установлено)  в принятых пакетах -  ДАННЫЕ? oooooo               
                   
  if  ( Socket0_Received_Data_Size() == 0 )  
  
  {
      printf("rn> (Zero) Received Data size is: %Lu (bytes) rn", S0_RX_RSR);
                   // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ
  
  goto CheckFIN;   // раз ДАННЫХ нет о и передавать в отет нечего
                            // поэтому сразу идем на проверку флагаFIN
  }
  // раз данных нет ( размер=0) то уходим на этап проверки закрытия соединения



  
   else    
   {
   printf("rn> (NonZero) Received Data size is: %Lu (bytes) rn", S0_RX_RSR);
                   // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ
   }
  
 
// oooooooooo ( Данные в пакете есть) Процесс обработки*   ooooooooooooooo 

//на самом деле просто  выводит на терминал принятые данные
// после отладки этот кусок можно удалить


                 Socket0_Received_Data_Reading();   
                   // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ
        


    
// oooooooooooooo  Процесс ПЕРЕДАЧИ данных  ooooooooooooooooooooooooooooooo
// тоесть отсылаем браузеру HTTP заголовок и HTML страничку

 
if ( Socket0_Received_Request_is_index_html() ==  TRUE)   Socket0_Send_index_html ();
   // если  клиент запрашивает "index.html"  то остылаем ему  index.html

else  Socket0_Send_404_error (); 
   //  если запрос любого другого файла  то  отсылаем  «страничку»  «ошибка 404»
   
  


//ooooooooooo    Получен флаг  FIN ? ooooooooooooooooooooooooooo

//  тоесть проверка требует ли  Клиент разрыва соединения

CheckFIN:

if ( Socket0_FIN_Received() == TRUE) goto  CloseConnection;

     //  если FIN  пришел  - то  уходим закрывать СОКЕТ0





//ooooooooooo    Разрыв соединения   ooooooooooooooooooooooo 


  Socket0_Disconnect();    //  разрыв СОЕДИНЕНИЯ Сокета0





//ooooooooooo   Сокет ЗАКРЫТ ?    ooooooooooooooooooooooooooooooo

if (Socket0_Closed() == TRUE )  goto CloseConnection;
    // если Сокет закрыт – уходим закрывать Соединение




//ooooooooooo   Не наступил ли тайм-аут по соединению  ?  ooooooooooooooooooo


if ( Socket0_Connection_Timeout() == TRUE)  goto CloseConnection;
  // сокет не закрыт, но наступил тайм-аут, – уходим закрывать сокет




CloseConnection:

//ooooooooooo   Закрываем Сокет    ooooooooooooooooooooooooooooooooooo
 
  Socket0_Closing(); 
 

 
 
 
//oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo






goto OpenSocket0;   

// соединение с Клиентом отработано, цикл завершился.
//  Уходим в начало и запускаем все по новой ( ждем новых запросов «веб-сайта» )
 
}


//===================  END of MAIN ===========================
//============================================================





//---------------------------------------------------------------
//--------------------------INIT -------------------------------
void Init (void)
{


output_low(RESET); //  генерируем «Сброс» для Ардуиновского Ethernet  шилда 
delay_ms(1);
output_high(RESET);

output_low(SCK);     // готовим пины (уровни) для  SPI
output_high(SS) ;

//программный сброс чипа W5100.
SetW5100register(MR, 0x80);   // пишем код  RST  в W5100 Mode Register



//------------ Настраиваем  память (W5100) для Сокета 0 -------------
 
 

SetW5100register(RMSR, 0x55);   // настраиваем RX буфер: по 2КБ под каждый сокет 


S0_RX_BASE = 0x6000;     //  базовый адрес RX  буфера    для Сокета0

S0_RX_MASK = 0x07FF ;   // (2048 -1 )= 0x07FF, RX Маска ( = длина_буфера – 1 )


SetW5100register(TMSR, 0x55);   // настраиваем ТX буфер: по 2КБ под каждый сокет
 
S0_TX_BASE = 0x4000;  // базовый адрес TX буфера  для Сокета0
 
S0_TX_MASK = 0x07FF;  // (2048 -1 )= 0x07FF, ТХ Маска ( = длина_буфера – 1 )

 

//------------ прописываем свой МАС адрес   --------------
//  т.е.  просто закидываем в МАС регистры W5100 
//    – свои переменные  с определенными ранее величинами


 SetW5100register(SHAR0, MAC0);  
 SetW5100register(SHAR1, MAC1); 
 SetW5100register(SHAR2, MAC2);
 SetW5100register(SHAR3, MAC3);
 SetW5100register(SHAR4, MAC4);
 SetW5100register(SHAR5, MAC5);
  
 
//------------  прописываем свой  IP  --------------
// так же -  раскидываем в регистры W5100 – свои переменные

SetW5100register(SIPR0, SERVER_IP0);
SetW5100register(SIPR1, SERVER_IP1);
SetW5100register(SIPR2, SERVER_IP2);
SetW5100register(SIPR3, SERVER_IP3);

 

//------------  прописываем свой PORT  --------------
// так же – свои константы – в регистры

SetW5100register(S0_PORT0, SERVER_PORT0);
SetW5100register(S0_PORT1, SERVER_PORT1); 

//------------ прописываем  Gateway addr  --------------
// так же – свои константы – в регистры

SetW5100register(GAR0, GATEWAY_IP0); 
SetW5100register(GAR1, GATEWAY_IP1); 
SetW5100register(GAR2, GATEWAY_IP2); 
SetW5100register(GAR3, GATEWAY_IP3); 

//------------ set Subnet Mask  --------------

SetW5100register(SUBR0, SUBNET_MASK0); 
SetW5100register(SUBR1, SUBNET_MASK1); 
SetW5100register(SUBR2, SUBNET_MASK2); 
SetW5100register(SUBR3, SUBNET_MASK3); 
 
      printf(" rnrn > =============== W5100 ВЕБ-СЕРВЕР ==========rnrn");
      // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ
}





//--------------------------------------------------------------
//-------------  ОТКРЫТИЕ СОКЕТА 0  ----------------------------

int Open_Socket0(void)
{

                 // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ

                 printf(" rn =========== TRY # %u   ====================rn", try);
                 if (try ==255) try =0;
                 else try++;
              
              
                 printf("rn>  Start Open Socket... rn");
          
              


// устанавливаем Сокет – в режим ТСР, остальные опции отключаем 
SetW5100register(S0_MR, 0x01);   

// засылаем в регистр команд – команду ОТКРЫТЬ ( сокет)
SetW5100register(S0_CR, OPEN);  

// проверяем или сокет открылся успешно
// * в режиме ТСР , по  «SOCK_INIT»  можно проверить или сокет открылся
if (GetW5100register(S0_SR) != SOCK_INIT)  // проверяем STATUS регистр
   {
    SetW5100register(S0_CR, CLOSE); // если не открылся то закрываем
    return FALSE ;                //  и выходим с кодом 0
   }

 return TRUE;     //открылся успешно.  Выходим с кодом 1
}




//--------------------------------------------------------------
//-------------  Переводим Сокет0 в режим  СЛУШАТЬ  ------------

int Listen_Socket0 (void)
{
 
                 printf("> Sock opened. Go Listen... rn");
                 // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ


// засылаем в регистр команд команду перевода сокета в режим СЛУШАТЬ
// *СЛУШАТЬ – т.к. мы – Сервер, а инициатива  исходит от Клиента

SetW5100register(S0_CR, LISTEN);  


if ( GetW5100register(S0_SR) != SOCK_LISTEN)  //  проверяем  Регистр Состояния
 {
  SetW5100register(S0_CR, CLOSE); //  если сокет не вошел в режим СЛУШАТь
  return FALSE;                   // закрываем его и выходим с кодом 0   
 } 
 
return TRUE;               //  сокет в режиме СЛУШАТЬ,  выходим с  кодом 1
 
}


//--------------------------------------------------------------------------
//-------------   проверка Установлено  ли  соединение с  Сокетом0 ? --------
// * в смысле от клиента ( браузера)

int Socket0_Connection_Established(void)
{

// если W5100  получает от клиента запрос на соединение, он отсылает ему пакет 
// с  установленным флагом ACK и  изменяет свой статуст на SOCK_ESTABLISHED
// («соединение с сокетом установлено)
// Проверить это можно либо  проверкой бита в регистре прерываний, 
// либо как здесь -  проверкой  регистра состояния


if  ( GetW5100register(S0_SR) == SOCK_ESTABLISHED)  return TRUE ;

// читаем Регистр Состояния.  
// Если  комбинация битов (код) = «SOCK_ESTABLISHED» то выходим с 1

else return FALSE;     // если нет – выходим с кодом 0


                   printf("> Connection Established... rn");
                 // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ


// *после того как соединение установлено,  сокет готов к приему/передаче данных

}



//----    проверка были ли приняты ДАННЫЕ -------------
//-----------------------------------------------------

// * «служебные» пакеты между сторонами  соединения могут и пересылаться
// но нас интересует были ли в них именно ДАННЫЕ
//  (например, запрос  страницы/файла  с веб-сервера)


int Socket0_Received_Data_Size (void)  

// на самом деле  интересует   размер принятых данных = НОЛЬ или нет

{

S0_RX_RSR = make16 (GetW5100register(S0_RX_RSR0), GetW5100register(S0_RX_RSR1) );  
// читаем два  8-битных регистра размера принятых данных 
// RSR0 – старш байт, RSR1 – младш байт
// и «собираем» 16-битное слово -  размер принятых данных


                printf("> Received Data size is: %Lu (bytes) rn", S0_RX_RSR);
                 // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ

if (S0_RX_RSR == 0 ) return FALSE;   // нет ДАННЫХ ( т.е. размер=0 )
                                     // выходим с кодом 0

else  return TRUE;    // размер данных не нулевой ( т.е. данные  -есть)
                      // выходим с кодом 1



}

//---------- вывод на терминал принятых ДАННЫХ ---------------
//------------------------------------------------------------
// * функция  полезна только при отладке.  Ее МОЖНО УДАЛИТЬ

void Socket0_Received_Data_Reading (void) 
{
 int16 n; 
 int RXbyte;


  S0_RX_OFFSET = make16 ( GetW5100register(S0_RX_RD0), GetW5100register(S0_RX_RD1) );  
// из двух  8-разрядных регистров  склеиваем  16 разрядную переменную -
// УКАЗАТЕЛЬ на  начало  принятых данных в RX буфере  сокета0


                 printf("> S0_RX_RD (RX mem read pointer) = %LX rn", make16 ( GetW5100register(S0_RX_RD0), GetW5100register(S0_RX_RD1) )  );
       

  S0_RX_OFFSET = (S0_RX_OFFSET & S0_RX_MASK ) ;
 // отсекаем лишнее  чтобы  укладывался в размеры выделенного под Сокет0 буфера

                 printf("> S0_RX_Offset = S0_RX_RD & S0_RX_MASK = %LX rnrn",S0_RX_OFFSET );
    

  S0_RX_Start_Addr = S0_RX_OFFSET + S0_RX_BASE ;

//вычисляем ФИЗИЧЕСКИЙ адрес начала  области памяти хранящей принятые данные


                 printf("> S0_RX_Start_Addr = S0_RX_OFFSET + S0_RX_BASE = %LX (physical)rnrn", S0_RX_Start_Addr );
 

                  printf(">  Going  to print-out Received Data... rnrn");
                  printf("ooooooooooooooooooooooooooooooooooooooooooorn");


for (n=0; n < S0_RX_RSR ; n++)   
 {
  
   if ( S0_RX_Start_Addr > (S0_RX_BASE + S0_RX_MASK)  )  S0_RX_Start_Addr = S0_RX_BASE;

   RXbyte = GetW5100register(S0_RX_Start_Addr);
  
  
                   printf("%c", RXbyte);

   S0_RX_Start_Addr++;
 
 }

                   printf("rnoooooooooooooooo END of  received data oooooooooooooornrn");
                   
 
}


// --- проверяем или от Клиента пришел запрос на INDEX.HTML  файл ------
//----------------------------------------------------------------------

int Socket0_Received_Request_is_index_html(void)  
{
int RXbyte=0;

 S0_RX_OFFSET = make16 ( GetW5100register(S0_RX_RD0), GetW5100register(S0_RX_RD1) );  
// из двух  8-разрядных регистров  склеиваем  16 разрядную переменную -
// УКАЗАТЕЛЬ на  начало  принятых данных в RX буфере  сокета0


 

 S0_RX_OFFSET = (S0_RX_OFFSET & S0_RX_MASK ) ;
// отсекаем лишнее  чтобы  укладывался в размеры выделенного под Сокет0 буфера

 
 S0_RX_Start_Addr = S0_RX_OFFSET + S0_RX_BASE ;  
//вычисляем ФИЗИЧЕСКИЙ адрес начала  области памяти хранящей принятые данные

  
 
 
                printf("rn>----------- parsing HTTP header-------------rn");
                 // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ

 
 while (RXbyte != 0x2F)   // ищем первый  "/"  в HTTP  заголовке
 {
  
    if ( S0_RX_Start_Addr > (S0_RX_BASE + S0_RX_MASK)  )  S0_RX_Start_Addr = S0_RX_BASE;
// начало блока данных не обязательно м располагаться в начале  буфера
// проверяем, не дошли  ли до КОНЦА  буфера
// если да – идем в самое НАЧАЛО буфера – данный продолжаются именно с того места

   RXbyte = GetW5100register(S0_RX_Start_Addr);  
// читаем из буфера RX байт,  на который  указывает СтартовыйАДрес
  
  
                      printf("%c", RXbyte);
                 // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ


   S0_RX_Start_Addr++;  // инкрементируем Адрес – теперь он указывает на следующ юайт
 
 }
 
// раз мы здесь значит уже дошли («отловили») до первого в HTTP заголовке символа “/”
// и сейчас Адрес  указывает на следующий за "/"  символ.  
// Ради него и затевался весь сыр-бор

    RXbyte = GetW5100register(S0_RX_Start_Addr); //считываем этот  символ
 
 
 
          printf("rn> -------- END of parsing HTTP header -------rn");
                 // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ


 
     if (RXbyte == 0x20)  return TRUE;  
// если это «пробел»  - значит клиент запрашивает файл без названия, тоесть index.html
//  выходим с кодом подтверждения 1

     else return FALSE;                
// если это был не пробел а любой другой символ, значит  запрашивается НЕ index.html
// выходим с кодом ошибки - 0

}





//------   отправка Клиенту страницы «ошибка 404» ------------
//-------------------------------------------------------------

// заполняем ТХ буфер сокета0  блоком данных из HTTP заголовка и   HTML  кода
//  страницы «ошибка 404»,  затем  показываем W5100  начало и конец 
// этого блока данных в буфере,  и даем команду  ОТОСЛАТЬ

void Socket0_Send_404_error (void) 
{   int16 n;
char TXbyte;
int16 datalength;



  S0_TX_OFFSET = make16 ( GetW5100register(S0_TX_RD0), GetW5100register(S0_TX_RD1) );  // из двух  8-разрядных регистров  склеиваем  16 разрядную переменную - УКАЗАТЕЛЬ 
// на  место, откуда можно начать размещать блок данных в ТX буфере  сокета0
  
  
  S0_TX_OFFSET = (S0_TX_OFFSET & S0_TX_MASK ) ;
// отсекаем лишнее  чтобы  укладывался в размеры выделенного под Сокет0 буфера
  
  
  S0_TX_Start_Addr = S0_TX_OFFSET + S0_TX_BASE ;
//вычисляем ФИЗИЧЕСКИЙ начальный адрес для размещения блока данных в буфере ТХ
  

  //вычисляем ДЛИНУ строки  содержащей HTTP заголовки и HTML  коды
  datalength=0;
  while ( ERROR404[datalength] !=0) datalength++;  
// попросту  инкрементируем «datalength» пока не наткнемся на первый 0х00 в строке
//  (0х00 -  признак конца данных)
 
 


// Для того, чтобы W5100 передала блок Данных, его необходимо разместить 
// в ТХ буфере Сокета, начиная с адреса, который ммы вычислили ранее.
// После этого, W5100 необходимо указать   КОНЕЦ блока данных в буфере.

// Указание на конец нашего блока данных мы записываем в соотв регистр W5100:


// Вытаскиваем из пары регистров - указателей конца блока данных их текущее значение
// и склеиваем из них  2-байтный указатель
// *( в отличие от предыдущего  вычисления ФИЗИЧЕСКОГО адреса начала длока данных
// здесь мы имеем дело именно с «внутренним» указателем адреса)
  S0_TX_End_Addr = make16 ( GetW5100register(S0_TX_WR0), GetW5100register(S0_TX_WR1) );  

// добавляем к получившемуся -  вычисленную ранее длину нашего блока данных
      S0_TX_End_Addr += datalength ;      // increment to  fatalength
   // получившееся  значение ( указатель на конец наших данных) вновь «расклеииваем» 
   // на  2 байта и  записываем в  соотв  регистры   
      SetW5100register(S0_TX_WR0, make8( S0_TX_End_Addr ,1) );  // старший байт 
      SetW5100register(S0_TX_WR1, make8( S0_TX_End_Addr,0) );  // младший байт
 
      
                 printf("rn>Data length is: %Lu rn", datalength);
                 printf("rn>--- Filling TX buffer  w  data: -----------rn");
                 // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ
      
      
 // теперь приступаем к собственно заполнению  буфера ТХ – нашими  данными
For (n=0; n < datalength; n++)  // цикл  на длину блока данных
  {
     TXbyte = ERROR404[n];  
// читаем текущий байт из строки,  в которой записан весь код «веб страницы»
// ( HTTP заголовки и  HTML коды)
 

// поскольку «выданный» нам от W5100  адрес начала блока данных в ТХ буфере  
// может не совпадать с  началом  буфера, а  находиться, например в 10 байтах
// от конца буфера, необходимо при записи каждого нового байта наших данных
// проверять, не вылезли  ли  мы за границы буфера
 
  if (S0_TX_Start_Addr  > (S0_TX_BASE + S0_TX_MASK)) S0_TX_Start_Addr = S0_TX_BASE;
 // .. и если  дошли до края буфера – то   продолжать следует с НАЧАЛА буфера

// * W5100  в курсе этих манипуляций (это  вообще была  ее  идея а не наша ))и когда //будет передавать данные  - также, в  случае, когда дойдет до конца буфера  
//( именно БУФЕРА,  а НЕ до указателя на КОНЕЦ Данных) – также будет продолжать
//  от начала буфера
 

 // с ТЕКУЩИМ адресом  записи текущего байта мы разобрались выше 
// и сейчас просто пишем текущий байт данных – в  соотв  ячейку памяти буфера ТХ 
  SetW5100register( S0_TX_Start_Addr, TXbyte ) ;   
  
               
                       putc(TXbyte);   
                    // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ

  S0_TX_Start_Addr++ ;
  // инкрементируем текущий адрес (для записи следующего байта данных)
  
  }     
      
                  printf("rn>--- end of  Filling  -----------rn");
                    // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ

// все, все данные для передачи мы разместили в буфере, 
// указали, где наши данные заканчиваются, теперь можно их и передавать    
  
    //  засылаем в Регистр Команд сокета  - команду SEND  
    SetW5100register(S0_CR, SEND);       
      
                    printf("> Data was  sent rnrn");
                    // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ

}


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

//-- отправка Клиенту страницы «index.html» ---------------
//-------------------------------------------------------------------
void Socket0_Send_index_html (void) // send index.html "page"  index.html
{

// расписывать не стану – все абсолютно так же как при посылке страницы «ошибка 404»
// только  «строка»  содержащая  HTTP  заголовки и  HTML  коды – другая

int16 n;
char TXbyte;
int16 datalength;

                    printf("rn>......  going to send INDEX.HTML.....rn");
                    // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ

  S0_TX_OFFSET = make16 ( GetW5100register(S0_TX_RD0), GetW5100register(S0_TX_RD1) );  // склеиваем  2-байтный указатель на началь адрес для размещения блока данных
  
  
  S0_TX_OFFSET = (S0_TX_OFFSET & S0_TX_MASK ) ;
  
  
  S0_TX_Start_Addr = S0_TX_OFFSET + S0_TX_BASE ;
  // вычисляем ФИЗИЧЕСКИЙ  начальный адрес  для размещения блока данных


                   printf("rn  INDEX[i], datalegth -------------rn");
                    // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ
      
      
  //вычисляем ДЛИНУ блока данных
  datalength=0;

  while ( INDEX[datalength] !=0) datalength++; 
 // считаем длину данных – пока не дойдем до первого  0х00 – признака конца данных
  
                    printf("%c  %Lu ",INDEX[datalength], datalength );
                      // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ
               
      
       

// записываем в  W5100  УКАЗАТЕЛЬ  на КОНЕЦ блока данных на передачу
 S0_TX_End_Addr = make16 ( GetW5100register(S0_TX_WR0), GetW5100register(S0_TX_WR1) );  // «склеиваем» вместе 2 байта текущего  значения

      S0_TX_End_Addr += datalength ;      // increment to  fatalength
      // добавляем длину нашего блока данных

 // новое значение заново распихиваем по двум 1-байтным регистрам     
      SetW5100register(S0_TX_WR0, make8( S0_TX_End_Addr ,1) );  // старш байт
      SetW5100register(S0_TX_WR1, make8( S0_TX_End_Addr,0) );  // младш байт

                 
                  printf("rn>Data length is: %Lu rn", datalength);
                  printf("rn>--- Filling TX buffer  w  data: -----------rn");
                    // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ
           
      
 // заполняем буфер – нашими данными на передачу
For (n=0; n < datalength; n++)
  {
   TXbyte= INDEX[n]; 
// читаем текущий байт из строки  содержащей «веб страницу»  index.html
// (т.е.   HTTP Header  + HTML code)
  
  if (S0_TX_Start_Addr  > (S0_TX_BASE + S0_TX_MASK)) S0_TX_Start_Addr = S0_TX_BASE;
  // проверяем не дошли ли до края буфера, и если да – перескакиваем на начало
  
  SetW5100register( S0_TX_Start_Addr, TXbyte ) ;  
   // зарисываем текущий байт блока данных ( «веб страницы») – в  буфер ТХ 
  

                putc(TXbyte);  // printout to Terminal ( for testing purpose)
                      // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ
   
  S0_TX_Start_Addr++ ;
  // переходим к следующему адресу ТХ буфера
  
  }     
      
                 printf("rn>--- end of  Filling  -----------rn");
                    // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ
    
  // буфер заполнен нашим блоком данных, можно их передавать Клиенту

    //  пишем в Регистр Команд сокета - команду SEND  
    SetW5100register(S0_CR, SEND);       
      
                     printf("> Data was  sent rnrn");
                    // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ

}

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

 

//--- содержит ли пакет, пришедший от Клиента,  установленный флаг FIN ? ------
//-----------------------------------------------------------------------------

int Socket0_FIN_Received(void)
{ 
// проверяем  не пожелал ли  клиент разрыва отношений (тоесть соединения)
// послав нам  флаг FIN  -  запрос на разрыв соединения

// можно проверять через проверку бита в регистре прерываний
// либо как здесь – через проверку  Регистра Состояния Сокета0


// выходим с  1  если  FIN пришел  и  с 0  если  FINа не было
if ( GetW5100register(S0_SR) == SOCK_CLOSE_WAIT) return TRUE;
else return FALSE;

}


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


 
// ------ разрываем СОЕДИНЕНИЕ для  Socket0   -----------------
//-------------------------------------------------------------
void Socket0_Disconnect(void)
{


//  отключаем сокет от СОЕДИНЕНИЯ с клиентом
// заслав соотв команду в Регистр Команд  сокета0

SetW5100register(S0_CR, DISCON);

}





// ------  проверка или Сокет0 ЗАКРЫТ --------------------
//--------------------------------------------------------
int Socket0_Closed(void)
{
// Сокет мож быть закрыт после засылки нами в Регистр Команд Сокета
//  команды ЗАКРЫТь (CLOSE), или  после тайм-аута, или  при разрыве соединения

// проверка или  Сокет0 действительно закрыт

// * можно делать через проверку  бита в регистре прерываний,
// либо как здесь -  проверкой Регистра Состояния
 

If  ( GetW5100register(S0_SR) == SOCK_CLOSED) return TRUE;
else return FALSE;
// выходим с 1  если Сокет (или СОЕДИНЕНИЕ сокета0) закрыто
// либо с 0 если сокет  до сих пор не закрылся


}





 
// -----  нет ли ТАЙМ-АУТа по соединению Сокета0 ? ------------
//-------------------------------------------------------------

int Socket0_Connection_Timeout(void)
{
// если на линии ошибки, или  клиент хочет закрыть соединение, или  от клиента давно
// ничего не приходит   итп – проверяем соединение на тайм-аут
//  иначе будет некорректно,  если наш  сервер будет слать клиенту пакеты
// ( с  соответствующими TCP флагами ) в том порядке, который подразумевается 
// для нормального процесса обмена пакетами


// тайм-аут проверяется либо через биты в  регистре прерываний
// либо как у нас – через проверку Регистра Состояния сокета0

// ПРИЕЧАНИЕ: как видим, регистр проверяется на состояние «SOCK_CLOSED»
// - какая тут связь с тайм-аутом??
// Дело в том что в Регистре Состояний нет отдельного кода для тайм-аут,
// но в W5100  код «SOCK_CLOSED»  связан также и  с тайм-аутом
// поэтому  проверка на этот код – вполне легитимна

If  ( GetW5100register(S0_SR) == SOCK_CLOSED) return TRUE;
else return FALSE;

// выходим с 1  если   тайм-аут  наступил, либо с 0 если тайм-аута не было

}



// ------------  ЗАКРЫТИЕ Сокета0    ----------------------------
//----------------------------------------------------------------


void Socket0_Closing(void)
{
//  should be performed in case that connection is closed after data exchange,
// socket should be closed with Timeout occurrence, 
// or forcible disconnection is necessary due to abnormal operation etc.
  

                    printf(">going to Close Socket0 ..... rn");
                      // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ
           
// засылаем в Регистр Команд сокета0  код на ЗАКРЫТИЕ сокета
SetW5100register(S0_CR, CLOSE);


                   printf(">  ------ Socket CLOSED    ----- rnrn");
                      // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ

}
 




// ---- ЗАПИСЬ (посылка) байта  через SPI   -------------
//--------------------------------------------------------


//* у меня название функции «SSPI» оттого, что в компайлере есть собственная 
// готовая йункция (SPI… )но она глючная. Поэтому я написал свой вариант 
// а чтоб не было конфликтов (названий) в компайлере – дал ей другое имя

// назначение функции – послать ОДИН БАЙТ через SPI   
//  более верхние уровни «протокола»  обмена (тоесть запись всех ТРЕХ байтов)  реализует 
//  другая функция  - функция для записи в РЕГИСТР

//* состояние линаа данных -  валидное для W5100 при  переходе клока из 0 в 1

void SSPI_write( int Data)
{
int i;
int mask=0x80; // ставим маску на СТАРШИЙ бит (тоесть начинаем со СТАРШЕГО бита)
                // так  как по  протоколу  SPI для W5100 данные следуют 
                // от СТАРШЕГО бита -  к МЛАДШЕМУ  ( MSB first)


output_low(SCK);   // просто чтоб быть уверенными что клок – в «исходном» положении

for (i=0; i<8;i++)    // цикл для считывания 8 бит
 { 
  output_low(MOSI);   // просто выставляем «данные» на линии -  в 0 

  if ( (mask & Data) != 0) output_high(MOSI);   
          // если  (маска & ДАННЫЕ)  =1  то  выставляем на линию 1
         // если нет – то на линии  так и остается 0, выставленный строчкой выше


  output_high(SCK);   
   // выдаем пульс клока ( из 0 в 1)  - именно по нему  происходит запись бита 
   // с линии  данных SPI – в   регистры W5100
 
  mask = mask>>1; ;  // сдвигаем маску  на 1 бит вправо
                    // *можно поставить  эту операцию и  после  цикла
                    // но в этом месте этим заодно  обеспечивается 
                    // некотор задержка между тактами (тоесть частота клока)

 
  output_low(SCK); // завершаем  клок, переводим его в «исходное» состояние ( 0 )
 }
 // «.. и так восэм  рас» (с)

}
//----------------------------------------------------------



//----------- ЧТЕНИЕ байта с линии   SPI  ------------------
//-----------------------------------------------------------


//* у меня название «SSPI» оттого, что в компайлере есть собственная 
// готовая йункция (SPI… )но она глючная. Поэтому я написал свой вариант 
// а чтоб не было конфликтов в компайлере – дал ей другое имя

// назначение функции – прочесть  ОДИН БАЙТ
//  более верхние уровни «протокола» реализует 
// собственно функция  чтения РЕГИСТРА

// *данные при чтении из регистров W5100 -  валидные при  переходе клока из 1 в 0

//* тоесть если при передаче  мы  сначала  выставляли данные на линию
//  и только после этого  подтверждали их валидность переводом клока из 0 в 1
// а  на прниеме  мы сначала  выставляем  клок в 1,  читаем данные
// и «защелкиваем» данные  переходом клока из 1 в 0


int SSPI_read ( void)
{
int Data=0;
int i;
int mask=0x80; // ставим маску на СТАРШИЙ бит (тоесть начинаем со старшего бита)
                // так  как по  протоколу  SPI для W5100 данные следуют 
                // от СТАРШЕГО бита -  к МЛАДШЕМУ  ( MSB first)


output_low(SCK);   // просто чтоб быть уверенными что клок – в «исходном» положении

for (i=0; i<8;i++)  // цикл для считывания 8 бит

 { 
  output_high(SCK);   // выдаем пульс клока ( из 0 в 1)      
    
  if ( input(MISO)!= 0) Data = Data | mask ; 
  // если на линии 1, то  делаем ИЛИ  маски – и текущего  значения «собираемого» байта
  
  mask = mask>>1; ;   // сдвигаем маску  на 1 бит вправо
                    // *можно поставить и  после  цикла
                    // но в этом месте этим заодно  обеспечивается 
                    // некотор задержка между тактами (тоесть частота клока)

  
  output_low(SCK); // завершаем  клок, переводим его в «исходное» состояние ( 0 )

 }

return Data;
}
//---------------------------------------------------------------



//-------  ЗАПИСЬ  ( установка)  регистров W5100 -------------
//--------------------------------------------------------------
void SetW5100register (int16 regaddr, int8 data)
// 2 аргумента: 16 бит адреса регистра и 8 бит данных для записи

{
output_low(SS); // выставляем Чип Селект ( ставим в 0)

SSPI_write (0xF0);  // сперва  посылаем в регистр команду ЗАПИСЬ

                                   //*  make8 – преобразует 16 бит - в  8 бит
SSPI_write (  make8(regaddr,1) );  // выделяем из адреса старший байт  (MSB)
                                   // и пишем  его на SPI     


SSPI_write (  make8(regaddr,0) );  // выделяем из адреса младший байт  (LSB)
                                   // и шлем  его на SPI  

SSPI_write (data);   // пишем на SPI ДАННЫЕ для записи в регистр 


output_high(SS);     //  снимаем ЧипСелект  ( ставим в 1)

}
//---------------------------------------------------------




//-----  ЧТЕНИЕ содержимого регистров W5100 ----------
//----------------------------------------------------
int  GetW5100register (int16 regaddr)
//аргумент – 2-байтовый адрес регистра
// возвращает: 1 байт считанных из регистра данных
{
int RegData;

output_low(SS); // выставляем Чип Селект ( ставим в 0)


SSPI_write (0x0F);  // сперва  засылаем команду ЧТЕНИЕ

                                   //*  make8 – преобразует 16 бит в  8 бит
SSPI_write (  make8(regaddr,1) );  //  выделяем из адреса старший байт  (MSB)
                                   // и выставляем его на SPI     

SSPI_write (  make8(regaddr,0) );  //  выделяем из адреса младший байт  (LSB)
                                   // и выставляем его на SPI  

RegData = SSPI_read ();    // теперь в ответ W5100  выдаст нам содержимое (8 бит) 
                           // регистра по засланному перед этим адресу


output_high(SS);    //  снимаем ЧипСелект  ( ставим в 1)

return RegData;	// выходим с  прочитанным байтом

}
 
 
  
//*************  END of PROGRAM  **************

Автор: PEACE_dez

Источник


  1. Василий:

    Очень полезное руководство. Помогло разобраться как реально работает чип.

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js