В данной статье пойдет речь о том, как я ограничил время просмотра телевизора для ребенка с помощью Arduino.
С некоторых пор меня стали не устраивать оценки ребенка в школе. На планшет и смартфон были установлены пароли, на ПК ограничено время использования, но остался телевизор. Прятать кабель каждый день надоело, да и не факт, что он уже не купил свой. В результате решил сделать устройство, которое бы ограничивало время просмотра ТВ, т.е. просто отключало бы 220В.
Для реализации своей идеи использовал:
1. Double Side Prototype PCB Tinned Universal Breadboard 2x8 cm 20mmx80mm FR4:
2. 5V One 1 Channel Relay Module Board Shield For PIC AVR DSP ARM MCU Arduino:
3. USB Nano V3.0 ATmega328 16M 5V Micro-controller CH340G board For Arduino:
4. Mifare RC522 Card Read Antenna RF Module RFID Reader IC Card Proximity Module:
5. White 3-5V 0.96" SPI Serial 128X64 OLED LCD LED Display Module for Arduino:
При включении устройства оно считывает информацию из EEPROM на наличие записанных карт (максимальное количество карт я ограничил в 6 штук). В EEPROM хранятся последние 4 байта UID'а карты переведенные в десятичный формат. Для чтения и записи в EEPROM использовал библиотеку EEPROM2.h.
cardPresent = readCards();
boolean readCards() {
cardPresent = false;
for(int k = 0; k <6; k++) {
EEPROM_read(k*6+4, time[k]);
if(time[k] >= 0) {
cardPresent = true;
EEPROM_read(k*6, cards[k]);
}
}
return cardPresent;
}
Для работы с модулем MFRC522 использовал библиотеку MFRC522.h. Считываем карты следующим образом:
MFRC522 mfrc522(SS_PIN, RST_PIN);
void setup() {
SPI.begin(); // Init SPI bus
mfrc522.PCD_Init(); // Init MFRC522
void loop() {
// Look for new cards
if ( ! mfrc522.PICC_IsNewCardPresent()) {
return;
}
// Select one of the cards
if ( ! mfrc522.PICC_ReadCardSerial()) {
return;
}
String s = dump_byte_array(mfrc522.uid.uidByte, mfrc522.uid.size);
mfrc522.PICC_HaltA(); //останавливаем считывание карты до того момента как ее уберут от считывателя (иначе функция dump_byte_array будет выполняться несколько раз, пока карту не уберут от считывателя).
Serial.println(s);
}
В случае если карты отсутствуют в EEPROM первая приложенная карта назначается мастером и записывается в ячейку 0:
if(!cardPresent) { //если карт нет, то записать карту как мастер
short master = 0;
EEPROM_write(0, uiddec);
EEPROM_write(4, master);
message = "NEW MASTER";
cardPresent = true;
readCards();
}
Вывод сообщений на монитор. Использовал библиотеку HCuOLED.h:
/* Create an instance of the library (uncomment one of the lines below) */
//HCuOLED HCuOLED(SSD1307, SS_DI, DC_DI, RST_DI); // For SSD1307 displays (HCMODU0050 & HCMODU0052)
HCuOLED HCuOLED(SH1106, CS_DI, DC_DI, RST_DI); // For SH1106 displays (HCMODU0058 & HCMODU0059)
void setup() {
HCuOLED.Reset();
}
void drawUid(char *s) {
HCuOLED.Erase(0,16,120,32);
HCuOLED.SetFont(MedProp_11pt);
HCuOLED.Cursor(0,16);
HCuOLED.Print(s);
HCuOLED.Refresh();
}
void drawLong(long uid) {
HCuOLED.Erase(0,33,120,48);
HCuOLED.SetFont(MedProp_11pt);
HCuOLED.Cursor(0,33);
HCuOLED.Print(uid);
HCuOLED.Refresh();
}
void drawCardTime(String s) {
char mess[12];
s.toCharArray(mess, 12);
HCuOLED.Erase(0,49,120,80);
HCuOLED.SetFont(MedProp_11pt);
HCuOLED.Cursor(0,49);
HCuOLED.Print(mess);
HCuOLED.Refresh();
}
Теперь приведу код функции String dump_byte_array(byte *buffer, byte bufferSize). Она вызывается после считывания карты. Работа устройства реализована следующим образом:
1. Переводим последние 4 байта в десятичный формат и выводим на экран uid карты в обоих форматах, ищем совпадение uid'а с тем что мы считали из EEPROM:
String s;
unsigned long uiddec = 0;
unsigned long temp;
char uid[8];
for (byte m = (bufferSize > 4 ? (bufferSize - 4) : 0); m < bufferSize; m++) { //берем только последние 4 байта и переводим в десятичную систему
unsigned long p = 1;
for(int k = 0; k < bufferSize-m-1; k++) {
p = p*256;
}
uiddec += p*buffer[m];
s = s + (buffer[m] < 0x10 ? "0" : "");
s = s + String(buffer[m], HEX);
}
s.toCharArray(uid, 8);
drawUid(uid);
drawLong(uiddec);
message = "unknow";
short currentCard = -1;
for(int k = 0; k <6; k++) { //сравнимаем полученный uid с тем что хранится в массиве карт
if((time[k] >=0) && (cards[k] == uiddec)) {
currentCard = k;
}
}
2. Далее идет код в котором реализована логика устройства. В случае если карта известна, но она не мастер, то вызывается функция onRelay(Relay, currentCard), которая проверяет достаточно ли прошло времени для включения.
String onRelay(int relayPin, short card) {
String messa;
if(time[card] == 0) { //мастер пришел
offAllCards(); //выключить все карты
messa = "master ON";
digitalWrite(relayPin, LOW); //включить реле
} else {
if(!isOn[card]) { //если карта не включена
unsigned long lastOn = (lastTime[card] == NULL ? 0 : lastTime[card]); //последнее включение карты
if((millis()-lastOn)/60000 > (freqOn - 1)) { //если последнее включение было раньше, чем разрешенная частота использования
messa = "time:" + String(time[card]);
lastTime[card] = someCardsIsOn() + time[card]*60000; //установить время последнего использования в максимальное время из действующих карт + разрешенное время
isOn[card] = true; //пометить что это одна из действующих карт
digitalWrite(relayPin, LOW); //включить реле
} else { //сообщить когда снова можно будет пользоваться
short waitminutes = freqOn - (millis()-lastOn)/60000; //сколько осталось минут до возможности включить
messa = "Wait:" + String(waitminutes/60) + "h" + String(waitminutes%60)+ "m";
//messa = String(waitminutes);
}
} else {
messa = "Allready ON";
}
}
return messa;
}
void offAllCards() {
for(int k = 0; k < 10; k++) {
isOn[k] = false;
}
}
unsigned long someCardsIsOn() { //если какая-то карта уже активна, то время работы устройства увеличивается на время новой карты
unsigned long maxtime = millis();
for(int k = 0; k <6; k++) {
if(isOn[k]&&(lastTime[k] > maxtime)) {
maxtime = lastTime[k];
}
}
return maxtime;
}
if(currentCard > 0) { //карта найдена, но она не мастер
message = onRelay(Relay, currentCard);
} else {
if(currentCard == 0) { //приложили мастер, если новая карта приложена ранее, то записать ее в EEPROM, если нет, то сделать выход мастеру
if(!masterPresent) {
newCard = false;
masterPresent = true;
message = "master in";
onRelay(Relay, currentCard);
} else {
if(newCard) {
for(int k = 1; k < 6; k++) {
if(time[k] < 0) {
writeNewCard(newCardUid,k);
//Serial.println("new card write to " + String(k));
newCard = false;
message = "card added";
break;
}
}
} else {
masterPresent=false;
offRelay(Relay);
message = "master out";
}
}
} else { //если время меньше 0, то неизвестная карта, если мастер есть, то добавить, если нет то сообщить, что карта неизвестна
if(masterPresent) {
newCard = true;
newCardUid = uiddec;
message = "confirm?";
} else {message = "unknow";}
}
}
if(!cardPresent) { //если карт нет, то записать карту как мастер
short master = 0;
EEPROM_write(0, uiddec);
EEPROM_write(4, master);
message = "NEW MASTER";
cardPresent = true;
readCards();
}
if(uiddec == 696374757) { //для сброса устройства есть специальная карта
eraseCards();
cardPresent = readCards();
message = "ERASED";
}
drawCardTime(message);
}
void eraseCards() {
short master = -1;
for(int t = 0; t < 40; t++) {
EEPROM_write(t*2, master);}
readCards();
//Serial.println("Cards erased");
}
void writeNewCard(unsigned long uid, int k) {
short time = 30;
EEPROM_write(k*6, uid);
EEPROM_write(k*6+4, time);
readCards();
}
Теперь не забудем отключить устройство когда закончится время, для этого в самое начало функции loop() добавим следующий код:
if((millis()/10000)%2 ^ i) { //check each 10 seconds
i = !i;
if(!masterPresent) {
if(millis() > (someCardsIsOn()-1000)) {
offRelay(Relay);
message = "Time over";
} else {
message = "Remain:" + String((someCardsIsOn() - millis())/60000+1);
Serial.println(message);
}
drawCardTime(message);
HCuOLED.Refresh();
}
}
Код скетча целиком:
/*
* Typical pin layout used:
* -----------------------------------------------------------------------------------------
* MFRC522 Arduino Arduino Arduino Arduino Arduino
* Reader/PCD Uno Mega Nano v3 Leonardo/Micro Pro Micro
* Signal Pin Pin Pin Pin Pin Pin
* -----------------------------------------------------------------------------------------
* RST/Reset RST 9 5 D9 RESET/ICSP-5 RST
* SPI SS SDA(SS) 10 53 D10 10 10
* SPI MOSI MOSI 11 / ICSP-4 51 D11 ICSP-4 16
* SPI MISO MISO 12 / ICSP-1 50 D12 ICSP-1 14
* SPI SCK SCK 13 / ICSP-3 52 D13 ICSP-3 15
*/
#include <SPI.h>
#include <MFRC522.h>
#include <HCuOLED.h>
#include <EEPROM2.h>
#define RST_PIN 6 //
#define SS_PIN 7 //
/* Digital pin number for the displays chip select pin */
#define CS_DI 10
/* Digital pin number for the displays data/command pin */
#define DC_DI 9
/* Digital pin number for the displays reset pin */
#define RST_DI 8
#define Relay 3
#define freqOn 300 //как часто можно включать в минутах
unsigned long cards[6]; //uids карт читаются из eeprom
unsigned long newCardUid; //переменная для хранения неизвестной карты
boolean i = false;
short time[6]; //время на которое карта может включить устройство в минутах, читается из eeprom
unsigned long lastTime[6]; //время последнего использования карты
boolean isOn[6]; //активна ли карта в данный момент
boolean cardPresent; //показывает есть ли хоть одна карта в eeprom
boolean masterPresent = false; //показывает присутствие мастера
boolean newCard = false; //показывает присутствие новой карты
String message;
/* Create an instance of the library (uncomment one of the lines below) */
//HCuOLED HCuOLED(SSD1307, SS_DI, DC_DI, RST_DI); // For SSD1307 displays (HCMODU0050 & HCMODU0052)
HCuOLED HCuOLED(SH1106, CS_DI, DC_DI, RST_DI); // For SH1106 displays (HCMODU0058 & HCMODU0059)
MFRC522 mfrc522(SS_PIN, RST_PIN); // Create MFRC522 instance
void setup() {
Serial.begin(9600); // Initialize serial communications with the PC
//while (!Serial); // Do nothing if no serial port is opened (added for Arduinos based on ATMEGA32U4)
pinMode(Relay, OUTPUT);
digitalWrite(Relay, HIGH);
SPI.begin(); // Init SPI bus
mfrc522.PCD_Init(); // Init MFRC522
ShowReaderDetails(); // Show details of PCD - MFRC522 Card Reader details
//Serial.println(F("Scan PICC to see UID, type, and data blocks..."));
HCuOLED.Reset();
//HCuOLED.SetFont(MedProp_11pt);
//HCuOLED.Cursor(40,0);
//HCuOLED.Print("Uid:");
//eraseCards();
cardPresent = readCards();
}
void loop() {
if((millis()/10000)%2 ^ i) { //check each 10 seconds
i = !i;
if(!masterPresent) {
if(millis() > (someCardsIsOn()-1000)) {
offRelay(Relay);
message = "Time over";
} else {
message = "Remain:" + String((someCardsIsOn() - millis())/60000+1);
Serial.println(message);
}
drawCardTime(message);
HCuOLED.Refresh();
}
}
// Look for new cards
if ( ! mfrc522.PICC_IsNewCardPresent()) {
return;
}
// Select one of the cards
if ( ! mfrc522.PICC_ReadCardSerial()) {
return;
}
String s = dump_byte_array(mfrc522.uid.uidByte, mfrc522.uid.size);
mfrc522.PICC_HaltA();
Serial.println(s);
}
void ShowReaderDetails() {
// Get the MFRC522 software version
byte v = mfrc522.PCD_ReadRegister(mfrc522.VersionReg);
Serial.print(F("MFRC522 Software Version: 0x"));
Serial.print(v, HEX);
if (v == 0x91)
Serial.print(F(" = v1.0"));
else if (v == 0x92)
Serial.print(F(" = v2.0"));
else
Serial.print(F(" (unknown)"));
Serial.println("");
// When 0x00 or 0xFF is returned, communication probably failed
if ((v == 0x00) || (v == 0xFF)) {
Serial.println(F("WARNING: Communication failure, is the MFRC522 properly connected?"));
}
}
String dump_byte_array(byte *buffer, byte bufferSize) {
String s;
unsigned long uiddec = 0;
unsigned long temp;
char uid[8];
for (byte m = (bufferSize > 4 ? (bufferSize - 4) : 0); m < bufferSize; m++) { //берем только последние 4 байта и переводим в десятичную систему
unsigned long p = 1;
for(int k = 0; k < bufferSize-m-1; k++) {
p = p*256;
}
uiddec += p*buffer[m];
s = s + (buffer[m] < 0x10 ? "0" : "");
s = s + String(buffer[m], HEX);
}
s.toCharArray(uid, 8);
drawUid(uid);
drawLong(uiddec);
message = "unknow";
short currentCard = -1;
for(int k = 0; k <6; k++) { //сравнимаем полученный uid с тем что хранится в массиве карт
if((time[k] >=0) && (cards[k] == uiddec)) {
currentCard = k;
}
}
if(currentCard > 0) { //карта найдена, но она не мастер
message = onRelay(Relay, currentCard);
} else {
if(currentCard == 0) { //приложили мастер, если новая карта приложена ранее, то записать ее в EEPROM, если нет, то сделать выход мастеру
if(!masterPresent) {
newCard = false;
masterPresent = true;
message = "master in";
onRelay(Relay, currentCard);
} else {
if(newCard) {
for(int k = 1; k < 6; k++) {
if(time[k] < 0) {
writeNewCard(newCardUid,k);
//Serial.println("new card write to " + String(k));
newCard = false;
message = "card added";
break;
}
}
} else {
masterPresent=false;
offRelay(Relay);
message = "master out";
}
}
} else { //если время меньше 0, то неизвестная карта, если мастер есть, то добавить, если нет то сообщить, что карта неизвестна
if(masterPresent) {
newCard = true;
newCardUid = uiddec;
message = "confirm?";
} else {message = "unknow";}
}
}
if(!cardPresent) { //если карт нет, то записать карту как мастер
short master = 0;
EEPROM_write(0, uiddec);
EEPROM_write(4, master);
message = "NEW MASTER";
cardPresent = true;
readCards();
}
if(uiddec == 696374757) {
eraseCards();
cardPresent = readCards();
message = "ERASED";
}
drawCardTime(message);
return s;
}
void drawUid(char *s) {
HCuOLED.Erase(0,16,120,32);
HCuOLED.SetFont(MedProp_11pt);
HCuOLED.Cursor(0,16);
HCuOLED.Print(s);
HCuOLED.Refresh();
}
void drawLong(long uid) {
HCuOLED.Erase(0,33,120,48);
HCuOLED.SetFont(MedProp_11pt);
HCuOLED.Cursor(0,33);
HCuOLED.Print(uid);
HCuOLED.Refresh();
}
void drawCardTime(String s) {
char mess[12];
s.toCharArray(mess, 12);
HCuOLED.Erase(0,49,120,80);
HCuOLED.SetFont(MedProp_11pt);
HCuOLED.Cursor(0,49);
HCuOLED.Print(mess);
HCuOLED.Refresh();
}
void eraseCards() {
short master = -1;
for(int t = 0; t < 40; t++) {
EEPROM_write(t*2, master);}
readCards();
//Serial.println("Cards erased");
}
boolean readCards() {
cardPresent = false;
for(int k = 0; k <6; k++) {
EEPROM_read(k*6+4, time[k]);
if(time[k] >= 0) {
cardPresent = true;
EEPROM_read(k*6, cards[k]);
//Serial.print(cards[k]);
//Serial.print(" - ");
//Serial.println(time[k]);
}
}
return cardPresent;
}
void writeNewCard(unsigned long uid, int k) {
short time = 30;
EEPROM_write(k*6, uid);
EEPROM_write(k*6+4, time);
readCards();
}
String onRelay(int relayPin, short card) {
String messa;
if(time[card] == 0) { //мастер пришел
offAllCards(); //выключить все карты
messa = "master ON";
digitalWrite(relayPin, LOW); //включить реле
} else {
if(!isOn[card]) { //если карта не включена
unsigned long lastOn = (lastTime[card] == NULL ? 0 : lastTime[card]); //последнее включение карты
if((millis()-lastOn)/60000 > (freqOn - 1)) { //если последнее включение было раньше, чем разрешенная частота использования
messa = "time:" + String(time[card]);
lastTime[card] = someCardsIsOn() + time[card]*60000; //установить время последнего использования в максимальное время из действующих карт + разрешенное время
isOn[card] = true; //пометить что это одна из действующих карт
digitalWrite(relayPin, LOW); //включить реле
} else { //сообщить когда снова можно будет пользоваться
short waitminutes = freqOn - (millis()-lastOn)/60000; //сколько осталось минут до возможности включить
messa = "Wait:" + String(waitminutes/60) + "h" + String(waitminutes%60)+ "m";
//messa = String(waitminutes);
}
} else {
messa = "Allready ON";
}
}
return messa;
}
void offRelay(int relayPin) {
offAllCards();
digitalWrite(relayPin, HIGH);
}
void offAllCards() {
for(int k = 0; k < 10; k++) {
isOn[k] = false;
}
}
unsigned long someCardsIsOn() {
unsigned long maxtime = millis();
for(int k = 0; k <6; k++) {
if(isOn[k]&&(lastTime[k] > maxtime)) {
maxtime = lastTime[k];
}
}
return maxtime;
}
Схема подключения:
Демонстрация работы:
Что хотелось бы изменить/добавить:
1. Сделать возможность на изменение времени использования устройства для карты и ее удаление (прикладывая мастер несколько раз изменять время: 30, 45, 60, 90, 120, -1).
2. Добавить на схему кнопку с помощью которой можно было бы сбрасывать устройство.
Автор: tmv002