Ох, давно хотел уже задокументировать это здесь, да всё времени не было. И вот, спустя полтора года…
Какую проблему решаем?
Стоит задача включать и выключать ПК нажимая на кнопки включения\выключения и сброса не руками, а через интернет (ну или через тоннель в интранет). В идеале спросонья через виджет в телефоне а-ля узнал погоду за бортом, включил ПК и можно идти ставить кофе.
Для этого было решено пилить какой-то контроллер Atmega с каким-то ethernet-свистком, чтобы он через оптопары замыкал кнопки включения. А заодно сообщал об успехе или неуспехе затеи.
Немного покопавшись я решил закупить основанную на enc28j60 приблуду для ардуины, а заодно и саму ардуину. Изучив особенности программирования enc28j60 руками (без высокоуровневых IDE и готовых библиотек) и бегло осмотрев errata, который, надо отметить, чуть ли не больше даташита, стало ясно, что на низком уровне рулить “этим” сущий ад и боль. Поэтому, будем собирать всё на базе добротного китайского клона Arduino nano или функционального аналога (лучше просто взять arduino, правда).
В итоге система будет состоять из основного модуля в виде связки Arduino+enc28j60 и выносного блока с оптопарами и резисторами.
В целом выглядеть и подключаться всё будет так:
Но перед тем, как начать…
Поставим себе пару пакетов:
1. dev-embedded/arduino
2. sci-electronics/eagle (ветка 7.x.x и лучше из нестабильной ветки)
Все необходимые файлы лежат в проекте на GitHub:
https://github.com/developersu/NoWOL
Про выносной модуль
Файлы для Eagle CAD находятся в директории
eagle_files/m2Board/ .
Необходимые библиотеки в
eagle_files/lib/ .
Для выносного модуля понадобится:
Part |
Value |
Device |
Package |
Library |
Sheet |
IC2 |
|
КР293КП4А |
DIL08 |
ic-package |
1 |
IC3 |
|
КР293КП4А |
DIL08 |
ic-package |
1 |
IN |
|
PINHD-1X6 |
1X06 |
pinhead |
1 |
OUT |
|
PINHD-1X6 |
1X06 |
pinhead |
1 |
R1 |
450Ω или 470Ω |
R-EU_0204/5 |
0204/5 |
resistor |
1 |
R2 |
450Ω или 470Ω |
R-EU_0204/5 |
0204/5 |
resistor |
1 |
R3 |
330Ω |
R-EU_0204/5 |
0204/5 |
resistor |
1 |
TO_MCU |
|
PINHD-1X5 |
1X05 |
pinhead |
1 |
По-поводу R1 и R2 резисторов, я собрал две пары, чтобы получилось 450 Ом в каждой. Думаю, что если взять просто пару штук на 470 Ом всё тоже будет работать. Но всё же обещать не буду.
Его принципиальная схема выглядит весьма просто:
Разводка выполняется на односторонней плате:
* 'cuz soviet works better
Если присмотреться, разводка слегка отличается от той, что приведена на первом рисунке. Тут я убрал перемычку.
Как вы уже поняли, в качестве оптопар используется отечественная схема КР293КП4А в корпусе DIP8. Внутри каждой микросхемы находится 2 оптопары. Одна из схем замыкает и размыкает кнопки (питание и сброс), а вторая говорит микроконтроллеру включена ли система, определяя это по светодиоду-индикатору питания. Если первую микросхему замыкает МК, то вторую — ПК. Через отдельный пин микроконтроллер зажигает индикатор питания сам, когда понимает, что и система (ПК) тоже его зажгла. Уж не знаю.. может это несколько избыточно и можно было сделать проще. Впрочем, получилось как получилось.
Примечание: наверное надо было навесить на вторую оптопару какие-то резисторы.. но блин, datasheet по ним такой немногословный. В общем как-то работает уже больше года, так что, пока всё не сгорело, имеет право на жизнь.
Если всё спаять и приправить должным количеством пыли, получится примерно следующее:
Про программирование Arduino
Теперь перейдём к программированию Arduino. Как говорилось ранее, предполагается использование Arduino Nano.
Я уже не помню что куда нажимать и подключать, чтобы это дело прошить но у меня есть скетч!
Для этого скетча понадобится библиотека
ehtercard. И скетч и библиотека находятся в директории
arduino_sketchbook/.
А вот и сам код:
#include <EtherCard.h>
#define PWR_PIN 6
#define RST_PIN 7
#define PWR_LED_IN 8
#define PWR_LED_OUT 5
int i;
// Зададим MAC адрес устройства
static byte mymac[] = { 0xDE,0xAD,0xBE,0xEE,0xEE,0xEF };
// Чем больше данных отображается на странице, тем больше требуется буфер для них
byte Ethernet::buffer[900];
BufferFiller bfill;
//Массив задействованных контактов для управления оптопарами
int LedPins[] = {PWR_PIN,RST_PIN};
//Массив для фиксации изменений
boolean PinUpdate = 0; //100-reset 200-power 5050-powerOFF
const char http_OK[] PROGMEM =
"HTTP/1.0 200 OK\r\n"
"Content-Type: text/html\r\n"
"Pragma: no-cache\r\n\r\n"
"\r\n"
"<html>"
"<head><title>"
"noWOL"
"</title><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"><style>"
"body { font-family: monospace;\n"
"background-color: #000000;\n"
"font-size: 30px;\n"
"font-weight: bold;\n"
"color: #b2b2b2;}\n"
"a:link, a:visited { display: inline-block;\n"
"color: #b2b2b2;\n"
"font-weight: 700;\n"
"text-decoration: none;\n"
"padding: .3em 2em;\n"
"outline: none;\n"
"border: 2px solid;\n"
"border-radius: 15px;\n"
"transition: 0.2s;}\n"
"a:hover {color: #fefefe;}\n"
"</style></head>"
"<body><center>"
;
const char http_Found[] PROGMEM =
"HTTP/1.0 302 Found\r\n"
"Location: /\r\n\r\n";
const char http_Unauthorized[] PROGMEM =
"HTTP/1.0 401 Unauthorized\r\n"
"Content-Type: text/html\r\n\r\n"
"401 Unauthorized";
void homePage()
{
bfill.emit_p(PSTR("$F"
" "
"$F<p>"
"<a href='?RESET=on'> RESET </a><p>" //D6
"<a href='?POWER0=on'> POWER </a><p>" //D7 short
"<a href='?POWER1=on'>POWER 5sec</a><br />"), //D7 long
//"PWR LED out D5 : $F <br /></body></html>"),
http_OK,
(digitalRead(PWR_LED_IN) == HIGH)?PSTR("<span style=\"color: #AA0000\"> ▄▄▄▄▄▄</span>"):PSTR("<span style=\"color: #00c600\"> ▄▄▄▄▄▄</span>")),//⬤
// (bitRead(PORTD,PWR_LED_OUT) == HIGH)?PSTR("HIGH"):PSTR("LOW")),
"</center></body>"
"</html>"
;
}
void pick_pin(int pin, int del) {
digitalWrite(pin,HIGH);
delay(del);
digitalWrite(pin,LOW);
PinUpdate = 0;
}
void setup()
{
// SET UART
// Serial.begin(9600);
//Change default ethercard CS - pin 8 to 10
//if (ether.begin(sizeof Ethernet::buffer, mymac, 10) == 0)
// Serial.println( "Access to Ethernet controller failed");
ether.begin(sizeof Ethernet::buffer, mymac, 10);
ether.dhcpSetup();
// if (!ether.dhcpSetup())
// Serial.println("DHCP routine failed");
//Print over UART configuration recieved:
// ether.printIp("IP: ", ether.myip);
// ether.printIp("GW: ", ether.gwip);
// ether.printIp("DNS: ", ether.dnsip);
for(i = 0; i < 2; i++) {
pinMode(LedPins[i],OUTPUT);
}
pinMode(PWR_LED_OUT,OUTPUT);
pinMode(PWR_LED_IN,INPUT_PULLUP); // LED-питания как вход
}
void loop()
{
delay(1);
if (digitalRead(PWR_LED_IN) == HIGH) { // если мать выключена
if (bitRead(PORTD,PWR_LED_OUT) == HIGH) digitalWrite(PWR_LED_OUT,LOW);
}
else if (bitRead(PORTD,PWR_LED_OUT) == LOW) {
digitalWrite(PWR_LED_OUT,HIGH);
}
switch (PinUpdate) {
case 0:
break;
case 1:
pick_pin(RST_PIN, 200);
break;
case 2:
pick_pin(PWR_PIN, 200);
break;
case 3:
pick_pin(PWR_PIN, 5050);
break;
}
word len = ether.packetReceive(); //Проверить ethernet пакеты
word pos = ether.packetLoop(len); //Проверить TCP пакеты
if (pos) {
bfill = ether.tcpOffset();
char *data = (char *) Ethernet::buffer + pos;
if (strncmp("GET /", data, 5) != 0) {
bfill.emit_p(http_Unauthorized);
}
else {
data += 5;
if (data[0] == ' ') {
homePage(); //Если обнаружено изменения на станице, то запускаем функцию
}
//"10" = количество символов "?RESET=on ".
else if (strncmp("?RESET=on ", data, 10) == 0) {
bfill.emit_p(http_Found);
PinUpdate = 1;
}
else if (strncmp("?POWER0=on ", data, 11) == 0) {
bfill.emit_p(http_Found);
PinUpdate = 2;
}
else if (strncmp("?POWER1=on ", data, 11) == 0) {
bfill.emit_p(http_Found);
PinUpdate = 3;
}
else {
//Страница не найдена
bfill.emit_p(http_Unauthorized);
}
}
ether.httpServerReply(bfill.position());
}
}
Теперь, когда всё что надо прошито, соединяем как указано на рисунке в начале публикации и подключаем это дело к сети. На машрутизаторе должен быть настроен DHCP сервер.
Мак-адрес нового устройства, как ясно из исходного кода, будет
de:ad:be:ee:ee:ef. Лучше сразу назначить постоянную аренду. В OpenWRT с LuCI идём в:
«Сеть» → «DHCP и DNS» → вкладка «Основные настройки» → «Постоянные аренды» → «Добавить».
Пишем там как показано на рисунке:
Когда всё настроено и подключено, заходим на адрес http://192.168.1.100 и видим следующее:
Только вот квадратик будет красным, а не зеленым. Это и есть индикатор питания.
Назначение кнопок пояснять нет смысла. Последняя симулирует долгое нажатие, что приводит к принудительному отключению.
То, что всё это находится в интрасети и не отсвечивает в мир меня вполне устраивает, ведь при подключении по VPN доступ появляется и в Android-приложении и через WEB-интерфейс. Ах да! Для всего этого дела есть ещё и Android-приложение :) Но о нём во второй части.
Вредные советы
Как бы не использовать Arduino?
Лучше таки его использовать.
Но можно и спаять свою ардуину
, с блекджеком и профусетками, чтобы она была поменьше и конкретно под этот проект.
Сразу оговорюсь, я наделал кучу ошибок и паял отдельные костыли чтобы всё сошлось.. потом переделал схему, потом ещё пару раз, и уже не помню на сколько там правильная разводка. Так что лучше внимательно изучить и прислать мне фидбек в виде линки на исправленную версию. Или апробировать, если всё правильно =)
Используется двусторонний текстолит. Перемычки тут не помогут.
Находится всё в директории
eagle_files/atmegaBoard/ .
Список компонентов:
Part |
Value |
Device |
Package |
Library |
Sheet |
C1 |
22pF |
C-EUC0603 |
C0603 |
resistor |
1 |
C2 |
22pF |
C-EUC0603 |
C0603 |
resistor |
1 |
C3 |
10uF |
CC0603 |
C0603 |
eagle-ltspice |
1 |
C4 |
22uF |
CC0603 |
C0603 |
eagle-ltspice |
1 |
IC1 |
atmega328p |
atmega328p |
TQFP32-08 |
avr-6 |
1 |
IC3 |
LM1117IMPX-3.3 |
LM1117IMPX-3.3 |
SOT223 |
lm1117 |
1 |
JP1 |
|
PINHD-2X5 |
2X05 |
pinhead |
1 |
JP3 |
|
PINHD-1X2 |
1X02 |
pinhead |
1 |
Q1 |
16MHz |
XTAL/S |
QS |
special |
1 |
SV1 |
|
MA05-1 |
MA05-1 |
con-lstb |
1 |
U1 |
|
USB"" |
USB-MICRO-SMD |
MicroUSB |
1 |
Схема принципиальная:
Сама разводка:
Что тут добавить.. земля на гребёнке справа. JP3 нужен для прошивки схемы. Прошивается она через Arduino IDE.. Кажется с помощью самой ардуины. Ну или через USBasp программатор. В общем, главное ничего не перепутать. А настройки должны быть вроде таких:
1)
2)
В следующей части будет обзор Android-приложения. APK файл находится в папке Android_APK.
Итого
У меня вся эта разработка шла не очень гладко, но получилось вполне сносно. Вот конечный результат в корпусе из спичек. Для антуража покрашено йодом.
Что касается дальнейшего развития этой инициативы, то я планирую добавить сюда ещё один выносной модуль для ещё одного ПК.
--
Особая благодарность
Unyuu за неоценимый вклад. Во многом благодаря его советам этот проект стал возможен.
Текстовый материал распространяется по
лицензии Creative Commons С указанием авторства-Некоммерческая-С сохранением условий 4.0 Всемирная.
Обратите внимание: это ограничение не касается графических изображений и исходного кода. Если на самом изображении не указано иного, они доступны по
лицензии Creative Commons С указанием авторства 4.0 Всемирная.