Начинаем подготовку к выпуску наших роботов в Сеть делать там разные интересные вещи, но для начала придется разобраться с необходимой стартовой рутиной: напишем программу, которая подключает плату ChipKIT к беспроводной сети WiFi - открытой или защищенной паролем.
ChipKIT WF32 подключение к беспроводной сети WiFi from
1i7 on
Vimeo.
Подключенный к плате ChipKIT WF32 светодиод показывает статус беспроводного подключения: он горит, когда плата подключена к вайфай, и не горит, когда плата не подключена. Как видно из видео, реакция на события включения/выключения точки со стороны платы требует некоторого времени (10-15 секунд), но в целом все работает достаточно надежно.
В роли точки доступа выступает Ётафон, раздающий вайфай в режиме модема (эта функция доступна на любом смартфоне с Андроид); вместо него подойдет обычная домашняя точка доступа или iPhone, который тоже умеет раздавать интернет через WiFi.
В Андроиде Настройки > Ещё... > Режим модема
Оборудование
В серии ChipKIT с WiFi умеют работать платы:
ChipKIT Wi-FIRE (судя по всему уже поступила в продажу)
ChipKIT WF32
ChipKIT Uno32 с
WiFi Shield
Библиотеки и документация
Среду разработки MPIDE для плат ChipKIT для Linux/Mac/Windows нужно
качать на сайте chipkit.net. Для работы с WiFi также потребуются дополнительные библиотеки - их можно найти например на
странице платы ChipKIT WF32 в магазине Digilent (см список закачек Support Documents внизу старницы: архив
chipKIT Network and USB Libs-xxx.zip). В этом же архиве содержится необходимая документация и инструкции по установке (в Linux архив нужно распаковать в ~/Documents/mpide-sketches/libraries, в Windows - в папку "C:\Users\MyName\Documents\mpide\libraries").
Замечание: Библиотека для работы с WiFi на платах ChipKIT представляет собой простую C++ обертку над стеком Tcp и WiFi для процессоров PIC32 от Microchip (он входит в состав пакета MLA -
Microchip libraries for applications), поэтому этот код не является переносимым внутри всей платформы Arduino - он будет работать только на платах серии ChipKIT. Во-первых, насколько мне известно, в платформе Arduino не предусмотрен стандартный API для работы с WiFi, поэтому WiFi-шилды для обычных Ардуин используют свои вызовы; во-вторых, код стека Tcp и WiFi, написанный для PIC32, будет проблематично запустить на процессорах AVR на обычной Arduino из-за чисто технических причин и особенностей архитектур; в-третьих, это все равно нельзя делать, тк. библиотеки MLA Microchip публикует под проприетарной лицензией - хотя исходный код стека доступен и его можно модифицировать под нужды проекта, лицензия не позволяет запускать его на чипах других производителей (в частности, AVR).
Алгоритм
Программа постоянно ищет сеть WiFi с заданным именем (lasto4ka). Если сеть найдена, пытается к ней подключиться с заданным типом безопасности: используя пароль (WPA2) или как к открытой сети без пароля. Если подключение удалось, программа зажигает лампочку статуса и печатает сообщение "WiFi is ON, do something with network" в отладочный порт Serial UART раз в 5 секунд. Если подключение теряется, программа гасит лампочку переходит в режим поиска сети с заданными параметрами.
Отладочные сообщения о ходе выполнения программы на плате можно посмотреть в MPIDE в окне Tools/Serial Monitor.
Замечание: Имя сети, тип подключения (открытая сеть/пароль WPA2) и сам пароль (в случае WPA2) задаются жестко в коде прошивки, иначе нам пришлось бы дополнительно придумывать, каким образом доставлять их на плату.
Собственно код
Вся прошивка умещаяется в одном файле
chipkit_wifi.pde.
Подключим необходимые библиотеки:
#include
#include
#include
Параметры для подключения к точке доступа: открытая или запароленная, имя сети и пароль:
// Точка доступа ВайФай
boolean USE_SECURITY_OPEN = false;
const char* wifi_ssid = "lasto4ka";
const char* wifi_wpa2_passphrase = "robotguest";
Укажем, каким образом будем получать IP-адрес - динамический (точка сама назначит по своему усмотрению в момент подключения) или статический (попросим точку назначить адрес, нужный нам):
boolean USE_STATIC_ADDRESS = true;
// статический IP-адрес для текущего хоста - если включен режим
// USE_STATIC_ADDRESS = true, то попросим у точки Wifi дать его нам
IPv4 host_ip = {192,168,43,117};
Сюда будем сохранять идентификатор активного подключения для внутренних нужд программы:
// Подключение к WiFi
int connectionId = DWIFIcK::INVALID_CONNECTION_ID;
Назначим пин для светодиода, который будет отображать текущий статус подключения:
// Пин для лампочки статуса WiFi
#define WIFI_STATUS_PIN 13
В сетапе ничего интересного - включаем последовательный порт UART для вывода отладочных сообщений по USB в MPIDE на компьютере, переводим пин для лампочки со статусом подключения к вайфай в режим вывода.
void setup() {
Serial.begin(9600);
Serial.println("Start wifi network connection demo");
pinMode(WIFI_STATUS_PIN, OUTPUT);
}
Всё интересное находится в бесконечном цикле loop.
Сетевой стек для PIC32 на ChipKIT (как и стек USB) не является многопоточным - в прошивке, которую мы загрузим на плату, не будет такого секретного системного фонового потока, который бы держал связь с точкой вайфай, следил за статусом подключения, передачей данных и т.п. В нашей программе есть только один поток - loop, поэтому все эти операции должны осуществляться внутри него в промежутке между выполнением остальных полезных действий - для этого нам достаточно поместить вызов DNETcK::periodicTasks() в код так, чтобы он время от времени вызывался например раз за цикл.
void loop() {
DNETcK::STATUS networkStatus;
// Держим Tcp-стек в живом состоянии
DNETcK::periodicTasks();
....
}
На очередной итерации проверяем, подключены ли мы к точке, при помощи вызова DWIFIcK::isConnected(): если не подключены гасим лампочку статуса и начинаем попытку подключения.
if(!DWIFIcK::isConnected(connectionId)) {
// Не подключены к WiFi - выключим лампочку
digitalWrite(WIFI_STATUS_PIN, LOW);
bool connectedToWifi = false;
// Подключимся к сети Wifi
Serial.println("Connecting wifi...");
....
}
Для начала получим доступ к оборудованию WiFi при помощи системного вызова DWIFIcK::connect() с нужными параметрами (имя сети, пароль, статус возврата): если хотим подключиться к открытой сети, передаем только ее имя, если к защищенной, то вызываем вариант с паролем.
// Сначала получим доступ к оборудованию:
// выбрать способ подключения (открытый или с паролем)
if(USE_SECURITY_OPEN) {
// Подключиться к открытой сети WiFi.
Serial.print("SSID: ");
Serial.println(wifi_ssid);
connectionId = DWIFIcK::connect(wifi_ssid, &networkStatus);
} else {
// Подключиться к сети WiFi, защищенной WPA2 с паролем.
Serial.print("SSID: ");
Serial.print(wifi_ssid);
Serial.print(", WPA2 passphrase: ");
Serial.println(wifi_wpa2_passphrase);
connectionId = DWIFIcK::connect(wifi_ssid, wifi_wpa2_passphrase, &networkStatus);
}
На этом этапе подключения к самой точке (проверка имени и пароля) еще не происходит - если чип WiFi установлен на плату и работает корректно, то вызов DWIFIcK::connect() почти сразу вернет корректный идентификатор подключения connectionId.
if(connectionId != DWIFIcK::INVALID_CONNECTION_ID) {
// На этом этапе подключение будет создано, даже если указанная
// сеть Wifi недоступна или для нее задан неправильный пароль
Serial.print("Connection created, connection id=");
Serial.println(conectionId, DEC);
...
}
Процесс подключения к точке происходит дальше - при старте инициализации IP-стека. Вызываем нужный вариант DNETcK::begin() для подключения с динамическим или статическим IP-адресом.
// Теперь попробуем подключиться к самой точке доступа - инициализируем Ip-стек
Serial.print("Initializing IP stack...");
if(USE_STATIC_ADDRESS) {
// подключимся со статическим ip-адресом
DNETcK::begin(host_ip);
} else {
// подключиться с динамическим ip-адресом
DNETcK::begin();
}
Как уже говорилось выше, сетевой стек на ChipKIT реализован для работы в однопоточных системах, поэтому большинство вызовов не являются блокирующими. В том числе вызов DNETcK::begin() вернется почти сразу не дожидаясь результатов подключения к точке, поэтому дождемся их самостоятельно внутри несложного цикла, который завершит работу в двух случаях: если мы успешно подключились к точке доступа и получили IP-адрес или если попытка подключения завершилась с ошибкой (не найдена сеть с нужным именем, не подошел пароль и т.п.):
// К открытой сети может подключиться с первой попытки, к сети с паролем
// может потребоваться несколько циклов (если пароль для сети неправильный,
// то ошибка вылезет тоже на этом этапе).
bool initializing = true;
while(initializing) {
// Вызов isInitialized заблокируется до тех пор, пока стек не будет
// инициализирован или не истечет время ожидания (по умолчанию 15 секунд).
// Если сеть не подключится до истечения таймаута и при этом не произойдет
// ошибка, isInitialized просто вернет FALSE без кода ошибки, при необходимости
// можно вызвать его повторно до успеха или ошибки.
if(DNETcK::isInitialized(&networkStatus)) {
// Стек IP инициализирован
connectedToWifi = true;
initializing = false;
} else if(DNETcK::isStatusAnError(networkStatus)) {
// Стек IP не инициализирован из-за ошибки,
// в этом месте больше не пробуем
initializing = false;
}
}
}
Подключились успешно, зажигаем статусную лампочку:
if(connectedToWifi) {
// Подключились к Wifi
Serial.println("Connected to wifi");
printNetworkStatus();
// включим лампочку
digitalWrite(WIFI_STATUS_PIN, HIGH);
}
Не получилось подключиться - отправим в отладочный порт код ошибки, корректно завершим работу сетевого стека и через 4 секунды попробуем всё заново на следующей итерации:
} else {
// Так и не получилось подключиться
Serial.print("Failed to connect wifi, status: ");
printDNETcKStatus(networkStatus);
Serial.println();
// Нужно корректно завершить весь стек IP и Wifi, чтобы
// иметь возможность переподключиться на следующей итерации
DNETcK::end();
DWIFIcK::disconnect(conectionId);
conectionId = DWIFIcK::INVALID_CONNECTION_ID;
// Немного подождем и попробуем переподключиться на следующей итерации
Serial.println("Retry after 4 seconds...");
delay(4000);
}
На этом блок подключения к точке WiFi закончен. В случае неудачи программа ждет 4 секунды и повторяет попытку на следующей итерации. В случае удачного подключения на следующей итерации управление передается следующему блоку, который будет или принимать входящие сетевые подключения или сам выходить в сеть на внешние ресурсы или делать что-то еще - об этом в следующих лабах.
void loop() {
...
if(!DWIFIcK::isConnected(conectionId)) {
...
} else {
// Подключены к WiFi - здесь можно делать любые операции, для которых необходима сеть
Serial.println("WiFi is ON, do something with network");
delay(5000);
}
}
исходники прошивки,
подсветка синтаксиса.