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

В статье будет просто, подробно и ясно описано, как запустить, например, веб-сервер, на замечательной и недорогой микросхеме W5100 компании Wiznet.
Чем же она замечательна?
Во-вторых – недорогая.
И во-первых – всю работу она делает за Вас. Вам же остается лишь лениво слать-принимать ТЕКСТОВЫЕ (точнее — HTML) данные.
Дисклаймер1: Инженеграм-электронщикам с… дцатилетним стажем статья может показаться поверхностной и упрощенной, поскольку они и так (возможно) в теме. Данная статья рассчитана на тех, кто имеет некоторый опыт в электронике, или, по крайней мере, в программировании микроконтроллеров, и просто, черт подери, хочет, наконец (слитно), запустить эту… (зачеркнуто) (ладно, пусть будет «замечательную») микросхему W5100.
Дисклаймер 2: Я не ставил целью всеобщее обучение, полный перевод даташит и растолковывание всех его пунктов. Моей задачей является прояснить запутанные моменты и показать как пользоваться чипом W5100 и как запустить на нем аппликацию (например – веб-сервер).
На основе моих разъяснений и примера аппликации, каждый, кто имеет опыт в микроконтроллерах и хотя бы основные понятия в сетях – сможет разобраться во всем остальном самостоятельно.
Дисклаймер3: Пожалуйста, не проводите этот эксперимент дома. И желательно, чтобы рядом находился кто-нибудь из взрослых.
Статья не предполагает дать всеобъемлющие знания по обширной сетевой теме, для этого есть мануалы, но предоставит возможность понимать что, в принципе, происходит и зачем, и какую информацию искать для углубленного понимания сабжекта.
Однако, представленный в конце пример позволит Вам разобраться, как использовать интернет (точнее – Ethernet и TCP/IP ) в своих аппликациях на микроконтроллерах, и, несмотря на простоту, он может быть вполне применен практически. Вы сможете его усложнять, поскольку поймете, как работать со связкой клиент-сервер, и, наконец, имея под рукой что-то работающее и теперь уже много лучше понимая тему – Вам будет намного легче углублять свои познания, не засыпая над десятком страниц с описанием назначения регистров, принципов настройки NAT, или правильным указанием смещения фрагментов в пакетах.
("… фрагменты в пакетах.." Что то такое вроде у Вуди Аллена было?)
По своему опыту: обычно, человеку, который пишет статью, сабжект ясен и понятен, поэтому он пишет коротко и «ясно».
Однако, это часто не так для читателя, который может быть не в теме, и часто вынужден гадать, что же именно автор имел в виду, и следует ли понимать некое высказывание вот так, или совершенно наоборот? Поэтому, там, где можно, я не стану рассусоливать, а в тех местах, где нужно разжевать до полной ясности и однозначности понимания – не стану экономить слов.
При этом, помните, что никто не идеален (даже я), так что простите, если кто ушел неудовлетворенным.
Для дочитавщих до конца — бонус: в конце статьи будет приведен РАБОТАЮЩИЙ код на простом «Си» с кучей комментов, чтоб даже пятикласснику было понятно, что происходит в программе. Код написан для обычного PICa (не все ж еще вокруг AVR-щики и Arduin-щики?).
Если у вас пока нет особого желания разводить плату и паять на нее микросхему в корпусе TQFP80, рекомендую взять обычный ардуиновский Ethernet Shield на базе W5100. Также потребуется простой и недорогой PIC, макетка для PICa и немного соединительных проводов, Ethernet патч-корд и, по вкусу, сериальный кабель RS232, на случай, если вам вдруг интересно, что происходит внутри сервера во время работы (в код вставлены строки для вывода попутной информации – на терминал).
Всем, имеющим некоторый опыт в разработке, эта ситуация знакома (или нет? Или это только я такой лузер?)
Вы берет некий чип, на котором необходимо запустить аппликацию. Для простоты допустим, что вам также знакома тема/область, в которой даннаая аппликация будет использоваться (например Ethernet, TCP/IP, HTTP, CGI etc).
Вначале Вы берете даташит на чип, в 100500 страниц,
Данная статья призвана помочь читателям пропустить нудные этапы и сразу перейти к стадии «все заработало» и далее — по тексту.
Много лет назад ( даже чуть больше), еще в до- Windows98 времена, мой товарищ поварчивал, что вот мол, развелось этих лже-юзеров, DOSовских комманд ничерта не знают, думают, что раз есть Windows 3.11, то в config.sys лезть не обязательно, а туда же, — хотят компьютер иметь.
И даже я, баггер со стажем, поддерживаю прогресс, увеличившиеся возможности для творчества новичков, и разнообразие видов юзеров.
Вероятно, есть смысл начать с конца и двигаться наверх.
Вы запускаете программу браузера, вводите в ней название сайта – и получаете на экране содержимое веб-странички.
Разумеется браузер получает не красивую страничку с картинками, а кучу текста, 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/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:
В первой строке («… 200 ОК..») сервер сообщает, что запрос верный (то есть такой файл имеется).
В строке «Content-Length: 185» сервер предупреждает клиента (то есть браузер) о длине передаваемого файла (точнее — именно и конкретно о длине веб страницы, которая идет сразу после HTTP заголовков).
Для того, чтобы обеспечить более менее корректную работу с браузером, нам (серверу) надо будет отдавать в ответ (помимо HTML файла) по крайней мере три параметра в заголовке HTTP:
HTTP/1.1 200 OK
Content-Type: text/html ( если мы посылаем именно это, а не, например, картинку).
Content-Length: (посчитаем потом)
На самом деле, современные браузеры весьма либеральны, поэтому можно отбросить и третью строку заголовка. Тем более, что в нашем примере, после отдачи клиенту «веб-страницы», мы разрываем соединение, поэтому клиент ( браузер) и так догадается, что он получил все.
Но… Если Ваша страница будет длинной, или, например, вы собираетесь посылать картинки и т.п. — лучше все-таки указывать длину посылаемого блока данных (Content-Length).
Когда я писал выше, что W5100 делает за нас всю работу с Ethernet и TCP/IP, я чуть приукрасил действительность. Кое-что (совсем капельку) нам все-таки придется делать самим. А именно: управлять процессом установления соединения и его завершением.
Коротко говоря, сервер и клиент общаются пересылкой друг-другу IP пакетов, которые могут содержать «полезные» данные (например – HTML страничка), а могут быть чисто служебными, без «полезных» данных.
Откроем даташит микросхемы W5100 (да-да, теперь уже пора) и рассмотрим, как общаются клиент (браузер) и W5100 в режиме сервера:

Вначале W5100 в режиме OPEN открывает/создает сокет (сетевое соединение) и переходит в режим «слушания» (LISTEN), дожидаясь запроса от клиента (т.е. браузера).
Когда запрос от клиента приходит, и между сервером и клиентом устанавливается соединение, сервер (W5100) переходит в режим ESTABLISHED и между сторонами происходит обмен данными.
На заметку: по уставу, максимальный размер данных в IP пакете – 1500 байт. Если ваши данные не укладываются в один пакет (большой размер HTML кода, или нужно передать картинку итп) – придется разбить данные на порции менее 1500 байт и передавать их одну за другой.
Далее, когда стороны высказали друг-другу все, что в душе нагорело, кто-то из них (как и в жизни) первым «бросает трубку», посылая запрос на разьединение (Disconnect Request).
Примечание: не всегда разьединение наступает именно после (полной) передачи данных. Например, у вас медленный канал, или пошли помехи, и браузер устал ждать завершения и прекратил соединение по своему тайм-ауту. Либо что-то не заладилось у веб-сервера и он решил прервать соединение в самом интересном месте. Ну, в общем, всякое ведь в жизни бывает.
После того, как связь между ними разорвана, сервер переходит в режим закрытия соединения (сокета) — CLOSED (не, ну все, что нужно – было сказано/отослано, че еще ждать то?).
Все.
Если вы сторонник мимолетных связей и/или одного раза Вам хватило с лихвой – на этом можно остановиться.
Если же вы оптимист по жизни ( или просто настойчивый) и полагаете, что к вашему серверу, вполне возможно, будут еще обращаться, и не раз — вам просто следует вернуться к шагу “OPEN” и далее — по картинке. (Порядочные сервера обычно так и поступают). Хотя… может быть в будущем появятся дешевые пластиковые одноразовые сервера? Типа использовал 1 раз – и выбросил?
Можно было бы обойтись и без этого ( благодаря W5100 ), но я все же упомяну. В реальной жизни при каждом обмене акетами между клиентом и сервером и при каждой смене статуса соединения, в пересылаемых пакетах устанавливаются флаги, говорящие о том, что сейчас происходит и как с этим жить.
Возьмем самый обычный IP стек:
Пусть не испугает никого красивое слово «стек», колдуны и маги от IBM-360 и AS-400 специально выдумывают заумные термины, чтобы подавить у простых смертных всякое желание потрогать немытыми руками их Храм. На самом деле это просто протокол, формат передачи данных, в котором расписано какой длины и в каком следуют порядке служебные поля и собственно данные. (Жванецкий: «… и даже болгары не могут понять нашего выражения, что «несмотря на погодные условия» — означает просто дождь!»)

Нас интересуют три старших бита в седьмом байте: ФЛАГИ.
Комбинациями этих трех битов формируют следующие флаги: FIN, SYN, RST, PSH, ACK, URG.
FIN — указывает на окончание сессии;
SYN — инициализация соединения;
RST — прерывание соединения (обычно – в ответ на ошибку);
ACK — подтверждение получения ( пакета);
PSH — упрощенно — требование немедленной высылки подтверждения;
URG — указание на архисрочноть данного пакета.
Если вам любопытно (что похвально и полезно), как же в реальности происходит обмен пакетами и какие флаги при этом выставляются и в каких случаях, а ко всему еще — для чего предназначены остальные поля заголовка (хорошо, хорошо, — «стека») – вам придется найти это самостоятельно. В данной статье мы рассматривать это не будем, поскольку W5100 берет всю заботу о пакетах и флагах на себя, мы же имеем дело с уже свершившимся фактом – статусом соединения на данный момент (OPEN, LISTEN, ESTABLISHED, CLOSED).
Что ж, далее оттягивать этот момент не представляется возможным, пора рассмотреть W5100 с железной и программной сторон.

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

Как видно из рисунка:
с адреса 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 минут в бою» — никто ж не отменял.
Да, так вот, — алгоритм.
Я, как правило, люблю понимать, что делается в программе, и предпочитаю иметь фло-чарт. Точно так же, несмотря на непреодолимое желание сразу писать код, обычно мне удается себя одернуть и начать с рисования фло-чарт, чтобы вначале отладить логику работы на нем, а уж потом переходить к коду. Так что, давайте разберемся как будет работать наш веб-сервер (см. картинку):

Если принцип «лучше 100 раз увидеть — чем 1 раз услышать» еще работает, то, наверное, нет смысла описывать то, что написано на картинке?
Всего два слова:
Управление процессом будет происходить через S0_CR (Socket0 Command Register) (0х0401).
Проверка текущих состояний ( соединения и прочих) – через S0_SR (Socket0 Status Register) (0х0403).
В принципе, проверка состояний может осуществляться через регистр прерываний Сокета (то есть по прерываниям), но, подозреваю, в большинстве ваших аппликаций суперскорость не потребуется и для таких случаев работа через опрос Регистра Состояния – вполне оправдана.

Здесь опять же нет смысла рассусоливать — на картинке все обозначено.
Совет: 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 тэгов обязательно должна быть пара этих сочетаний (то есть \r\n\r\n или 0D0A0D0A в кодах ASCII) иначе не только сами_знаете_какой браузер но и другие тоже Вас не поймут.
А, ну и еще ( все-таки ценный опыт и Вам стоит о нем знать). Еще одна ложка дегтя в бочку чересчур щепетильного браузера (ну, вы поняли): я как-то ошибся и в HTTP заголовке, в указании длины последующего далее HTML кода (Content-Lenght: ...) указал буквально на 1 символ больше, чем на самом деле (ну, то есть символов, скажем, 665, а я указал, что их будет 666).
Так вот, все браузеры проглотили страничку глазом не моргнув, а IE честно до посинения дожидался, когда придет 666-й символ и пока он не прийдет – отказывался вообще показывать страничку.
Я обычно пишу программы составляя их из логических блоков-функций, а сами функции придумываю-расписываю потом. Поэтому у меня в примере вначале идет объявление функций, потом main(), и в самом конце – раписаны подробно функции. Кроме того, мне больше нравится, когда вначале ( и недалеко от констант и переменных) идет main() и можно быстро его просмотреть и разобраться в происходящем, а не продираться через тонны функций, прежде чем добраться до основного блюда.
Также, стоит отметить, что программа поддерживает ping запросы ( то есть Вы можете послать ping на адрес нашего сервера ( 192.168.0.155) — и получить ответ.
На самом деле отклик на ping — это не очевидность.
Бывают случаи/варианты, когда сервис есть (и вполне себе рабочий), но на ping не отвечает.
Дело в том, что утилита ping посылает запросы через один из подвидов IP проткола, а именно ICMP (Internet Control Message Protocol), который и поддерживает диагностические функции, сообщая об ошибках в случаях неудачной доставки IP пакетов ( подробнее — в RFC 792 ), поэтому, Вы должны обеспечить поддержку в том числе и этого протокола, дабы ping-и проходили успешно.
В чипе W5100 эта поддержка уже реализована.
Ах да, еще: я не стал разбивать программу на несколько файлов, а сделал все одним файлом, чтобы Вам легче было в ней ориентироваться.
Программа – не идеал изящного кода, разумеется ее можно улучшить, можно избавиться от половины констант и переменных и трети функций, но в данном случае моей задачей являлось маскимально облегчить понимание того, что происходит и как программа работает.
И, вы не поверите, но мы дошли до конца.
Вот прям в двух дюймах ниже — та самая обещанная программа «за 5 минут».
Ну-с, понеслась!

В статье будет просто, подробно и ясно описано, как запустить, например, веб-сервер, на замечательной и недорогой микросхеме 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 багов. Ну, конечно! Именно об этом и было сказано в даташит/эррате. Ну, теперь-то понятно ЧТО ИМЕННО они там имели в виду (а о чем вы сами могли бы догадаться).
Все.
Аппликация заработала.
Теперь Вы – спец по данному чипу.
Фанфары. Премия. Пиво. Толпы поклонниц. Слава и следующий дидлайн.
и внимательно его читаете. Затем вы перечитываете его еще несколько раз и начинаете, наконец, понимать, как с данным чипом работать. Затем вы берете errata и повторяете те же итерации.
После прочтения оных вы, наконец, понимаете, как чип работает, как его правильно программировать (100500 регистров -1), но пока, все же, остается не совсем ясным, как ПРАВИЛЬНО его надо запускать. (Например, имеет ли значение порядок обращение к регистрам, и если да – то какой порядок правильный, а если не имеет значения – то точно ли он не имеет значения?).
Разумеется, много полезной информации м��жно найти в инете и на форумах, но из всех найденных там примеров не совсем ясно, какие НА САМОМ ДЕЛЕ работают, какие работают несмотря на обилие ужасающих ошибок (о которых Вы пока и не подозреваете), а какие «примеры» – просто плод фантазии аффтара, который написал, да не проверил, надеясь, что проканает.
Ок, вы прошли все эти этапы, ТЕПЕРЬ Вам уж точно все понятно.
Вы правильно подключаете все пины чипа к чему следует, правильно подключаете чип к процессору, правильно устанавливаете все регистры и что там у него еще внутри, и – о чудо! – наконец-то…
Да-да, все в порядке – ничего не заработало.
Это самый противный этап – когда все сделано правильно и строго по даташит, но все равно не работает. Вы долго и муторно разбираетесь, в чем же проблема, и наконец н��ходите 1...5...17 багов. Ну, конечно! Именно об этом и было сказано в даташит/эррате. Ну, теперь-то понятно ЧТО ИМЕННО они там имели в виду (а о чем вы сами могли бы догадаться).
Все.
Аппликация заработала.
Теперь Вы – спец по данному чипу.
Фанфары. Премия. Пиво. Толпы поклонниц. Слава и следующий дидлайн.
Данная статья призвана помочь читателям пропустить нудные этапы и сразу перейти к стадии «все заработало» и далее — по тексту.
Лирика — 2 (можно пропустить)
Много лет назад ( даже чуть больше), еще в до- Windows98 времена, мой товарищ поварчивал, что вот мол, развелось этих лже-юзеров, DOSовских комманд ничерта не знают, думают, что раз есть Windows 3.11, то в config.sys лезть не обязательно, а туда же, — хотят компьютер иметь.
пропустим по Лирике 2 ?
«пойти за хлебом… это какой КОД?».Я всегда считал, что кто-то любит ковыряться во внутрях, а кто-то просто хочет пользоваться и не париться. И оба вида имеют право на существование. (Гай Кавасаки: «… Как микроволновка может выдавать сообщение об ошибке? Микроволновка должна просто разогревать! Никто не должен проходить специальные курсы программирования микроволновок!»).
Сегодня часто слышны сетования, что, вот, мол, эти Ардуины портят народ и плодят неучей – нет чтоб отучиться лет n и набраться опыта еще лет n+1 — все бросились клепать свои проекты, не пытаясь получить глубокие знания в программировании и электронике.
Ужас, ужас.
И что?
Я считаю, что это нормально — человек с творческой жилкой и креативом придумал задумку (или наоборот) и хочет ее реализовать. Не имея глубочайших познаний. И это нормально.
Также, на заре интернета, была эдакая мода гордо указывать внизу странички: «сайт создан полностью вручную в notepad.exe. Никаких HTML-редакторов !». Ничего не напоминает?
При этом я, например, начинал не с Си и даже не с ассемблера — первый свой комп, собранный с нуля и абсолютно пустой, чтобы залить в него для начала хотя бы загрузчик, я программировал в двоичном коде 16-тью тумблерами шины адреса и 8-ю тумблерами шины данных.
Мне долго еще во сне приходили длинные ряды большик красных нулей и единичек, а когда мама послала купить хлеба, первой мыслью было:
Сегодня часто слышны сетования, что, вот, мол, эти Ардуины портят народ и плодят неучей – нет чтоб отучиться лет 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/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…
После “… /” и до “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>ну шо,<br><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 в режиме сервера:

Вначале W5100 в режиме OPEN открывает/создает сокет (сетевое соединение) и переходит в режим «слушания» (LISTEN), дожидаясь запроса от клиента (т.е. браузера).
Когда запрос от клиента приходит, и между сервером и клиентом устанавливается соединение, сервер (W5100) переходит в режим ESTABLISHED и между сторонами происходит обмен данными.
На заметку: по уставу, максимальный размер данных в IP пакете – 1500 байт. Если ваши данные не укладываются в один пакет (большой размер HTML кода, или нужно передать картинку итп) – придется разбить данные на порции менее 1500 байт и передавать их одну за другой.
Далее, когда стороны высказали друг-другу все, что в душе нагорело, кто-то из них (как и в жизни) первым «бросает трубку», посылая запрос на разьединение (Disconnect Request).
Примечание: не всегда разьединение наступает именно после (полной) передачи данных. Например, у вас медленный канал, или пошли помехи, и браузер устал ждать завершения и прекратил соединение по своему тайм-ауту. Либо что-то не заладилось у веб-сервера и он решил прервать соединение в самом интересном месте. Ну, в общем, всякое ведь в жизни бывает.
После того, как связь между ними разорвана, сервер переходит в режим закрытия соединения (сокета) — CLOSED (не, ну все, что нужно – было сказано/отослано, че еще ждать то?).
Все.
Если вы сторонник мимолетных связей и/или одного раза Вам хватило с лихвой – на этом можно остановиться.
Если же вы оптимист по жизни ( или просто настойчивый) и полагаете, что к вашему серверу, вполне возможно, будут еще обращаться, и не раз — вам просто следует вернуться к шагу “OPEN” и далее — по картинке. (Порядочные сервера обычно так и поступают). Хотя… может быть в будущем появятся дешевые пластиковые одноразовые сервера? Типа использовал 1 раз – и выбросил?
Можно было бы обойтись и без этого ( благодаря W5100 ), но я все же упомяну. В реальной жизни при каждом обмене акетами между клиентом и сервером и при каждой смене статуса соединения, в пересылаемых пакетах устанавливаются флаги, говорящие о том, что сейчас происходит и как с этим жить.
Возьмем самый обычный IP стек:
Пусть не испугает никого красивое слово «стек», колдуны и маги от IBM-360 и AS-400 специально выдумывают заумные термины, чтобы подавить у простых смертных всякое желание потрогать немытыми руками их Храм. На самом деле это просто протокол, формат передачи данных, в котором расписано какой длины и в каком следуют порядке служебные поля и собственно данные. (Жванецкий: «… и даже болгары не могут понять нашего выражения, что «несмотря на погодные условия» — означает просто дождь!»)

Нас интересуют три старших бита в седьмом байте: ФЛАГИ.
Комбинациями этих трех битов формируют следующие флаги: FIN, SYN, RST, PSH, ACK, URG.
FIN — указывает на окончание сессии;
SYN — инициализация соединения;
RST — прерывание соединения (обычно – в ответ на ошибку);
ACK — подтверждение получения ( пакета);
PSH — упрощенно — требование немедленной высылки подтверждения;
URG — указание на архисрочноть данного пакета.
Если вам любопытно (что похвально и полезно), как же в реальности происходит обмен пакетами и какие флаги при этом выставляются и в каких случаях, а ко всему еще — для чего предназначены остальные поля заголовка (хорошо, хорошо, — «стека») – вам придется найти это самостоятельно. В данной статье мы рассматривать это не будем, поскольку W5100 берет всю заботу о пакетах и флагах на себя, мы же имеем дело с уже свершившимся фактом – статусом соединения на данный момент (OPEN, LISTEN, ESTABLISHED, CLOSED).
Краткое, насколько это возможно, описание W5100
Что ж, далее оттягивать этот момент не представляется возможным, пора рассмотреть W5100 с железной и программной сторон.

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

Как видно из рисунка:
с адреса 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 минут в бою» — никто ж не отменял.
Да, так вот, — алгоритм.
Я, как правило, люблю понимать, что делается в программе, и предпочитаю иметь фло-чарт. Точно так же, несмотря на непреодолимое желание сразу писать код, обычно мне удается себя одернуть и начать с рисования фло-чарт, чтобы вначале отладить логику работы на нем, а уж потом переходить к коду. Так что, давайте разберемся как будет работать наш веб-сервер (см. картинку):

Если принцип «лучше 100 раз увидеть — чем 1 раз услышать» еще работает, то, наверное, нет смысла описывать то, что написано на картинке?
Всего два слова:
Управление процессом будет происходить через S0_CR (Socket0 Command Register) (0х0401).
Проверка текущих состояний ( соединения и прочих) – через S0_SR (Socket0 Status Register) (0х0403).
В принципе, проверка состояний может осуществляться через регистр прерываний Сокета (то есть по прерываниям), но, подозреваю, в большинстве ваших аппликаций суперскорость не потребуется и для таких случаев работа через опрос Регистра Состояния – вполне оправдана.
Подключение Ethernet Shield — к PIC контроллеру

Здесь опять же нет смысла рассусоливать — на картинке все обозначено.
Совет: 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 тэгов обязательно должна быть пара этих сочетаний (то есть \r\n\r\n или 0D0A0D0A в кодах ASCII) иначе не только сами_знаете_какой браузер но и другие тоже Вас не поймут.
А, ну и еще ( все-таки ценный опыт и Вам стоит о нем знать). Еще одна ложка дегтя в бочку чересчур щепетильного браузера (ну, вы поняли): я как-то ошибся и в HTTP заголовке, в указании длины последующего далее HTML кода (Content-Lenght: ...) указал буквально на 1 символ больше, чем на самом деле (ну, то есть символов, скажем, 665, а я указал, что их будет 666).
Так вот, все браузеры проглотили страничку глазом не моргнув, а IE честно до посинения дожидался, когда придет 666-й символ и пока он не прийдет – отказывался вообще показывать страничку.
Я обычно пишу программы составляя их из логических блоков-функций, а сами функции придумываю-расписываю потом. Поэтому у меня в примере вначале идет объявление функций, потом main(), и в самом конце – раписаны подробно функции. Кроме того, мне больше нравится, когда вначале ( и недалеко от констант и переменных) идет main() и можно быстро его просмотреть и разобраться в происходящем, а не продираться через тонны функций, прежде чем добраться до основного блюда.
Также, стоит отметить, что программа поддерживает ping запросы ( то есть Вы можете послать ping на адрес нашего сервера ( 192.168.0.155) — и получить ответ.
На самом деле отклик на ping — это не очевидность.
Бывают случаи/варианты, когда сервис есть (и вполне себе рабочий), но на ping не отвечает.
Дело в том, что утилита ping посылает запросы через один из подвидов IP проткола, а именно ICMP (Internet Control Message Protocol), который и поддерживает диагностические функции, сообщая об ошибках в случаях неудачной доставки IP пакетов ( подробнее — в RFC 792 ), поэтому, Вы должны обеспечить поддержку в том числе и этого протокола, дабы ping-и проходили успешно.
В чипе W5100 эта поддержка уже реализована.
Ах да, еще: я не стал разбивать программу на несколько файлов, а сделал все одним файлом, чтобы Вам легче было в ней ориентироваться.
Программа – не идеал изящного кода, разумеется ее можно улучшить, можно избавиться от половины констант и переменных и трети функций, но в данном случае моей задачей являлось маскимально облегчить понимание того, что происходит и как программа работает.
И, вы не поверите, но мы дошли до конца.
Вот прям в двух дюймах ниже — та самая обещанная программа «за 5 минут».
Ну-с, понеслась!
Исходный код (пароль - обычный - 6 звездочек)
// ===== Простой веб-сервер на базе 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 OK\r\nContent-Type: text/html\r\nContent-Lenght: 43\r\n\r\n<HTML><CENTER>HELLO WORLD !</CENTER></HTML>"};
// символы для создания странички «index.html”
CONST char ERROR404[222] = { "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Lenght: 39\r\n\r\n<HTML><CENTER>ERROR 404</CENTER></HTML>"};
// символы для создания странички «ошибка 404”
// закомментил чтоб была на примете,
// Можно использовать и такой вариант для «ошибка 404», код сам по себе верный, но...
//CONST char ERROR404[222] = { "HTTP/1.1 404 Page not found\r\nContent-Type: text/html\r\nContent-Lenght: 39\r\n\r\n<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... \r\n");
// для отладки (вывод на терминал) МОЖНО УДАЛИТЬ
//ooooooo( соединение уже установлено) в принятых пакетах - ДАННЫЕ? oooooo
if ( Socket0_Received_Data_Size() == 0 )
{
printf("\r\n> (Zero) Received Data size is: %Lu (bytes) \r\n", S0_RX_RSR);
// для отладки (вывод на терминал) МОЖНО УДАЛИТЬ
goto CheckFIN; // раз ДАННЫХ нет о и передавать в отет нечего
// поэтому сразу идем на проверку флагаFIN
}
// раз данных нет ( размер=0) то уходим на этап проверки закрытия соединения
else
{
printf("\r\n> (NonZero) Received Data size is: %Lu (bytes) \r\n", 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(" \r\n\r\n > =============== W5100 ВЕБ-СЕРВЕР ==========\r\n\r\n");
// для отладки (вывод на терминал) МОЖНО УДАЛИТЬ
}
//--------------------------------------------------------------
//------------- ОТКРЫТИЕ СОКЕТА 0 ----------------------------
int Open_Socket0(void)
{
// для отладки (вывод на терминал) МОЖНО УДАЛИТЬ
printf(" \r\n =========== TRY # %u ====================\r\n", try);
if (try ==255) try =0;
else try++;
printf("\r\n> Start Open Socket... \r\n");
// устанавливаем Сокет – в режим ТСР, остальные опции отключаем
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... \r\n");
// для отладки (вывод на терминал) МОЖНО УДАЛИТЬ
// засылаем в регистр команд команду перевода сокета в режим СЛУШАТЬ
// *СЛУШАТЬ – т.к. мы – Сервер, а инициатива исходит от Клиента
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... \r\n");
// для отладки (вывод на терминал) МОЖНО УДАЛИТЬ
// *после того как соединение установлено, сокет готов к приему/передаче данных
}
//---- проверка были ли приняты ДАННЫЕ -------------
//-----------------------------------------------------
// * «служебные» пакеты между сторонами соединения могут и пересылаться
// но нас интересует были ли в них именно ДАННЫЕ
// (например, запрос страницы/файла с веб-сервера)
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) \r\n", 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 \r\n", 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 \r\n\r\n",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)\r\n\r\n", S0_RX_Start_Addr );
printf("> Going to print-out Received Data... \r\n\r\n");
printf("ooooooooooooooooooooooooooooooooooooooooooo\r\n");
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("\r\noooooooooooooooo END of received data oooooooooooooo\r\n\r\n");
}
// --- проверяем или от Клиента пришел запрос на 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("\r\n>----------- parsing HTTP header-------------\r\n");
// для отладки (вывод на терминал) МОЖНО УДАЛИТЬ
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("\r\n> -------- END of parsing HTTP header -------\r\n");
// для отладки (вывод на терминал) МОЖНО УДАЛИТЬ
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("\r\n>Data length is: %Lu \r\n", datalength);
printf("\r\n>--- Filling TX buffer w data: -----------\r\n");
// для отладки (вывод на терминал) МОЖНО УДАЛИТЬ
// теперь приступаем к собственно заполнению буфера ТХ – нашими данными
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("\r\n>--- end of Filling -----------\r\n");
// для отладки (вывод на терминал) МОЖНО УДАЛИТЬ
// все, все данные для передачи мы разместили в буфере,
// указали, где наши данные заканчиваются, теперь можно их и передавать
// засылаем в Регистр Команд сокета - команду SEND
SetW5100register(S0_CR, SEND);
printf("> Data was sent \r\n\r\n");
// для отладки (вывод на терминал) МОЖНО УДАЛИТЬ
}
//-----------------------------------------------------------
//-- отправка Клиенту страницы «index.html» ---------------
//-------------------------------------------------------------------
void Socket0_Send_index_html (void) // send index.html "page" index.html
{
// расписывать не стану – все абсолютно так же как при посылке страницы «ошибка 404»
// только «строка» содержащая HTTP заголовки и HTML коды – другая
int16 n;
char TXbyte;
int16 datalength;
printf("\r\n>...... going to send INDEX.HTML.....\r\n");
// для отладки (вывод на терминал) МОЖНО УДАЛИТЬ
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("\r\n INDEX[i], datalegth -------------\r\n");
// для отладки (вывод на терминал) МОЖНО УДАЛИТЬ
//вычисляем ДЛИНУ блока данных
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("\r\n>Data length is: %Lu \r\n", datalength);
printf("\r\n>--- Filling TX buffer w data: -----------\r\n");
// для отладки (вывод на терминал) МОЖНО УДАЛИТЬ
// заполняем буфер – нашими данными на передачу
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("\r\n>--- end of Filling -----------\r\n");
// для отладки (вывод на терминал) МОЖНО УДАЛИТЬ
// буфер заполнен нашим блоком данных, можно их передавать Клиенту
// пишем в Регистр Команд сокета - команду SEND
SetW5100register(S0_CR, SEND);
printf("> Data was sent \r\n\r\n");
// для отладки (вывод на терминал) МОЖНО УДАЛИТЬ
}
//--------------------------------------------------------
//--- содержит ли пакет, пришедший от Клиента, установленный флаг 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 ..... \r\n");
// для отладки (вывод на терминал) МОЖНО УДАЛИТЬ
// засылаем в Регистр Команд сокета0 код на ЗАКРЫТИЕ сокета
SetW5100register(S0_CR, CLOSE);
printf("> ------ Socket CLOSED ----- \r\n\r\n");
// для отладки (вывод на терминал) МОЖНО УДАЛИТЬ
}
// ---- ЗАПИСЬ (посылка) байта через 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 **************
