情報技術科 No.37 向山 純平
目次
①研究の背景
給食を食べる際、残りの数が分からず不便に感じた。メニューの人気度が具体的な数値として分かれば、給食の量をそれにあわせて増減でき柔軟な対応ができる。すなわち、食品の無駄(フードロス)を省くことができると考えたことが研究の動機である。
②仮説とねらい
ハードウェアの授業で、マイコンやカウンタ、7セグ、順序回路について学習した。これらを用いることで研究の動機を満たす装置を実現出来ると考えた。
また、残量(人数)検知は、距離センサを応用することで実現できるはずだ。
③研究内容
- 人数検知の研究
- プログラムの作成
- 入出力装置の設計、製作
④技術的知識
入力装置(送信側)には、マイコン、残量を数えるためのセンサが必要である。
出力装置(受信側)には、マイコン、3bitの7セグメントLEDと給食の総数を入力するためのボタン、現在、A定食、B定食・・・のうち何が表示されているかを示すための表示器が必要である。
なお、装置間の通信にはワイヤレス通信を用いることで、利便性を高めた。
1.マイコンについて
今回の研究ではESP-32と呼ばれるマイコンを使用した。このマイコンひとつでWi-FiやBluetooth通信を行える。加えて、Arduino開発環境が使えるため、インターネット上に公開されている様々なライブラリを用いて開発ができる。
2.給食の残量検知の方法
給食の総数はあらかじめ入力するものとすると、総数から列のある一か所を通過する人数を引いていくことにより、給食の残りの数をカウントできる。ここで、通過する人数をカウントするため、赤外線式の距離センサを用いた。
3.赤外線式距離センサについて
研究の肝である、赤外線式の距離センサの仕組みを説明する。
- センサに付いているLEDから赤外線を照射する
- 物にあたって赤外線が反射する
- 反射された赤外線をセンサで読み取る
上図の通り、センサモジュールに搭載されているLEDから赤外線が照射される。照射した方向に物体がある場合、赤外線が反射され、センサに届く。この時、センサモジュールと物体との距離の違いによって、赤外線の入射角が異なるため、距離が分かるという仕組みである。なお、この方式はPSD方式と呼ばれる。
4.人数検知の方法
上の画像は、センサの出力電圧とその変化をプロットしたもので、前者が青色、後者が赤色で表示されている。この研究では、精度の向上のためにセンサの出力電圧の変化から人数の検知を行っている。
5.マイコン間の通信
マイコン間通信は、開発元が提供しているESP-NOWを用いた。これは、コネクションレスのWi-Fi通信プロトコルの一種で、ルーター等を中継することなく直接通信が可能なものである。
データの送受信は上の通りである。なお、個体の識別は、MACアドレスの比較で行っている。
⑤取り組んだ内容
1.ブレッドボードを用いた実装回路の試作及びプログラムの作成
上図のように結線し、動作の確認とプログラムの作成を行った。
コントローラ(送信側)のプログラム
/*************esp-now(WiFi)のライブラリ****************/
#include <esp_now.h>
#include <WiFi.h>
/******************************************************/
/********************ADCのライブラリ*******************/
#include "driver/adc.h"
#include "esp_adc_cal.h"
/******************************************************/
#include <Ticker.h>//タイマー割り込み(ticker)のライブラリ
Ticker ticker1;//関数の設定
#define T_SHIFT 5000
#define N_TIMES 4
#define DIFF1 70
#define TIME_DELAY 200
#define SENCE_1 13
volatile int value[2] = {};
volatile int tmp[2] = {};
volatile bool time_flg = false;
volatile bool led_flg = false;
volatile long led_time;
/*************esp-now(WiFi)の関数の設定******************/
esp_now_peer_info_t esp_ap;
const uint8_t *peer_addr = esp_ap.peer_addr;
const esp_now_peer_info_t *peer = &esp_ap;
uint8_t mac[6] = {0x8C, 0xAA, 0xB5, 0x85, 0x47, 0x4D}; //AP MAC 8C:AA:B5:85:47:4D
uint8_t data[2];
/******************************************************/
void setup() {
Serial.begin(115200);
pinMode(2, OUTPUT);
/********ADCの設定(ADC2とWiFiは両立できない)********/
adc_gpio_init(ADC_UNIT_1, ADC_CHANNEL_4);//ADC1のCH4(GPIO:32)の初期化
adc_gpio_init(ADC_UNIT_1, ADC_CHANNEL_4);//ADC1のCH5(GPIO:33)の初期化
adc1_config_width(ADC_WIDTH_BIT_12);// ADC1の解像度を12bit(0~4095)に設定
adc1_config_channel_atten(ADC1_CHANNEL_4, ADC_ATTEN_DB_11);// ADC1の減衰を11dBに設定
adc1_config_channel_atten(ADC1_CHANNEL_5, ADC_ATTEN_DB_11);// ADC1の減衰を11dBに設定
/****************************************************/
/***********ticker(タイマー割り込みの設定)***********/
ticker1.attach_ms(2000, send_data);//データの送信
/****************************************************/
/**************esp-now(WiFi)の設定*******************/
WiFi.mode(WIFI_STA);
for (int i = 0; i < 6; i++ ) esp_ap.peer_addr[i] = (uint8_t) mac[i];
esp_ap.channel = 1;
esp_ap.encrypt = 0;
esp_now_init();
bool exists = esp_now_is_peer_exist(peer_addr);
if (exists) {
Serial.println("Already Paired");//Already Paired
} else {
esp_err_t addStatus = esp_now_add_peer(peer);
if (addStatus == ESP_OK) {
Serial.println("Pair Success");//Pair Success
} else {
Serial.println("Pair Failed");//Pair Failed
}
}
}
/******************************************************/
void send_data() {
data[0] = lowByte(value[0]);
data[1] = highByte(value[0]);
data[2] = lowByte(value[1]);
data[3] = highByte(value[1]);
esp_err_t result = esp_now_send(peer_addr, data, sizeof(data));
value[0] = 0;
value[1] = 0;
}
void loop() {
int i, j , s_value[2] = {}, trig[2] = {};
digitalWrite(2, led_flg );
for (i = 0; i < 2; i++) {
for (j = 0; j < N_TIMES; j++) {
if (i == 0)s_value[0] = adc1_get_raw(ADC1_CHANNEL_4);
if (i == 1)s_value[1] = adc1_get_raw(ADC1_CHANNEL_5);
trig[i] += (s_value[i] - tmp[i]);
tmp[i] = s_value[i];
}
trig[i] /= N_TIMES;
//Serial.print("raw_data:");
//Serial.print(s_value[0]);
//Serial.print(",");
//Serial.print("trigger:");
//Serial.println(trig[0]);
//Serial.println(value[0], DEC);
if (time_flg == false && trig[i] > DIFF1) {
led_flg = true;
led_time = millis();
value[i]++;
time_flg = true;
}
if (led_flg == true && (millis() - led_time) - 500 == 0) {
led_flg = false;
}
if (millis() % 700 == 0) {
time_flg = false;
}
}
}
スレーブ(受信側)のプログラム
/******************************************************/
/******************written by ei838 *******************/
/******************************************************/
#include <Ticker.h>
#include <esp_now.h>
#include <WiFi.h>
#define ap_pass "espcast_pass_0000"
Ticker ticker1;
Ticker ticker2;
#define A_LUNCH 0
#define B_LUNCH 1
#define CURRY 2
#define NOODLE 3
#define T_SHIFT 5000
#define DIFF1 72
#define N_TIMES 4
#define DSPIN 18 // (15) DS [SER] on 74HC595
#define RCLKPIN 21 // (4) ST_CP [RCLK] on 74HC595
#define SRCLKPIN 19 // (5) SH_CP [SRCLK] on 74HC595
#define BT_CONNECT 2
#define LED_A 4
#define LED_B 5
#define LED_C 22
#define LED_N 23
#define CH_SW 26
#define K1_SW 34
#define K2_SW 35
#define K3_SW 32
#define SEG_K_1 27
#define SEG_K_2 14
#define SEG_K_3 13
//int t_delay = 1000; //usタイマー設定
//int tmp[4] = {};
byte num[11] {
//fgcdeabD
B10111110, //0
B00100010, //1
B01011110, //2
B01110110, //3
B11100010, //4
B11110100, //5
B11111100, //6
B10100110, //7
B11111110, //8
B11110110, //9
B00000000, //init
};
volatile int value[4] = {};
volatile int val;
volatile int keta = 3;
volatile int kind_c = 0;
volatile int sense_value;
volatile int convert = 0;
volatile bool init_flg = false;
volatile long t_limit;
char mac_list[2][18] = {"8C:AA:B5:86:10:68", ""};//送信元のMACアドレス
/**************シフトレジスタの初期化*****************/
void initialization() {
digitalWrite(RCLKPIN, LOW); //送信中のRCLKをLowにする
shiftOut(DSPIN, SRCLKPIN, LSBFIRST, num[10]); //0で初期化
digitalWrite(RCLKPIN, HIGH); //送信終了後RCLKをHighにする
}
/**************************************************/
/*************シフトレジスタへの書き込み***************/
void shift_digit(int i) {
digitalWrite(RCLKPIN, LOW); //送信中のRCLKをLowにする
shiftOut(DSPIN, SRCLKPIN, LSBFIRST, num[i]); //0~9を7セグに表示
digitalWrite(RCLKPIN, HIGH); //送信終了後RCLKをHighにする
}
/**************************************************/
/*******************LED制御関数*********************/
void LED_indicate (int a) { //LOWで点灯
switch (a) {
case 0:
digitalWrite(LED_N, HIGH);
digitalWrite(LED_A, LOW);
break;
case 1:
digitalWrite(LED_A, HIGH);
digitalWrite(LED_B, LOW);
break;
case 2:
digitalWrite(LED_B, HIGH);
digitalWrite(LED_C, LOW);
break;
case 3:
digitalWrite(LED_C, HIGH);
digitalWrite(LED_N, LOW);
break;
default:
digitalWrite(LED_A, HIGH);
digitalWrite(LED_B, HIGH);
digitalWrite(LED_C, HIGH);
digitalWrite(LED_N, HIGH);
break;
}
}
/*************************************************/
/*************7セグメントLED制御(桁)***************/
void seg_print() {
unsigned long t_millis;
t_millis = millis();
initialization();//7セグの初期化
switch (keta) {
case 3:
digitalWrite(SEG_K_1, LOW);
digitalWrite(SEG_K_3, HIGH);
shift_digit((val / 100) % 10);
keta--;
break;
case 2:
digitalWrite(SEG_K_3, LOW);
digitalWrite(SEG_K_2, HIGH);
shift_digit( (val / 10) % 10);
keta--;
break;
case 1:
digitalWrite(SEG_K_2, LOW);
digitalWrite(SEG_K_1, HIGH);
shift_digit(val % 10);
keta = 3;
break;
}
}
/**************************************************/
/********************種類変更***********************/
void kind_shift() {
if (init_flg == true) {
if (kind_c == 3)kind_c = 0;
else kind_c++;
LED_indicate(kind_c);
}
}
/**************************************************/
/********************データ受信*********************/
void OnDataRecv(const uint8_t *mac_addr, const uint8_t *data, int data_len) {
char macStr[18];
snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X", mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
Serial.print("Last Packet Sent to: ");
Serial.println(macStr);
int tmp1 = makeWord(data[1], data[0]);
//int tmp2 = makeWord(data[3], data[2]);
if (init_flg == true) {
if (strcmp(mac_list[0], macStr) == 0 && value[0] != 0) {
value[0] -= tmp1;
//value[1] -= tmp2;
}
if (strcmp(mac_list[1], macStr) == 0 && value[2] != 0) {
value[2] -= tmp1;
//value[3] -= tmp2;
}
}
}
/**************************************************/
/*********************初期設定**********************/
void setup() {
Serial.begin(115200);
ticker1.attach_ms(1, seg_print);//7セグの制御
ticker2.attach_ms(5000, kind_shift);//種類の変更
/*****************esp_now(WiFi)設定*****************/
WiFi.mode(WIFI_AP);
char* SSID = "espcast_a1";
bool result = WiFi.softAP(SSID, ap_pass, 1, 0);
if (!result) {
Serial.println("WiFi AP Failed");
} else {
Serial.println("AP SSID: " + String(SSID));
}
Serial.print("AP MAC: ");
Serial.println(WiFi.softAPmacAddress());
WiFi.disconnect();
if (esp_now_init() == ESP_OK) {
Serial.println("ESPNow Init Success");
} else {
Serial.println("ESPNow Init Failed");
ESP.restart();
}
esp_now_register_recv_cb(OnDataRecv);
/**************************************************/
/****************ピンの入出力設定******************/
pinMode(RCLKPIN, OUTPUT);
pinMode(DSPIN, OUTPUT);
pinMode(SRCLKPIN, OUTPUT);
pinMode(SEG_K_1, OUTPUT);
pinMode(SEG_K_2, OUTPUT);
pinMode(SEG_K_3, OUTPUT);
pinMode(LED_A, OUTPUT); //
pinMode(LED_B, OUTPUT); //
pinMode(LED_C, OUTPUT) ; //
pinMode(LED_N, OUTPUT) ; //
pinMode(BT_CONNECT, OUTPUT) ; //
digitalWrite(LED_A, HIGH);
digitalWrite(LED_B, HIGH);
digitalWrite(LED_C, HIGH);
digitalWrite(LED_N, HIGH);
pinMode(CH_SW, INPUT); //choose sw
pinMode(K1_SW, INPUT); //sw1 一桁目
pinMode(K2_SW, INPUT); //sw2 二桁目
pinMode(K3_SW, INPUT); //sw3 三桁目
/**************************************************/
input_num();
init_flg = true;
}
/**************************************************/
/******************総量の入力***********************/
void input_num() {
int sw1, sw2, sw3, kind = 0;
while (kind < 4) {
sw1 = !digitalRead(K1_SW);
sw2 = !digitalRead(K2_SW);
sw3 = !digitalRead(K3_SW);
val = value[kind];
LED_indicate(kind);
if (!digitalRead(CH_SW)) {
kind++;
delay(150);
}
if (sw1 == HIGH) {
value[kind]++;
delay(150);
}
if (sw2 == HIGH) {
value[kind] += 10;
delay(150);
}
if (sw3 == HIGH) {
value[kind] += 100;
delay(150);
}
}
LED_indicate(4);
kind_c = 0;
}
/**************************************************/
/**************************************************/
/********************メイン関数*********************/
void loop() {
val = value[0];
}
/**************************************************/
2. KICADを用いた実装回路の設計
まず、上図の回路図を作成した。これは、マイコンや7セグメントLEDを制御するための回路である。7セグメントLEDには12Vを、マイコンには5Vを供給するため、12Vから5Vに降圧している。
次に、7セグメントLED、総数を入力するためのスイッチ、給食の種類を表示させるLEDの回路である。7セグメントLEDが2ビットと1ビットに分けられているのは、基板サイズを100mm×100mmに収めるためである。
これらの回路図をもとに基板設計を行っていくと…
なお、基板はそれぞれ複数枚発注するので、LED表示器、入力スイッチの基板は7セグメントLEDの基板に組み込んだ形となっている。こうすることで、基板をカットする手間は増えるが、値段を安くできる。
3. 外装の設計及び3Dプリンタを用いた製作
3DCADはオートデスク株式会社のFusion360を使用した。
これらのデータをSTLファイルで書き出し、3Dプリンタで造形する。
⑥結果
上のような装置が出来上がった。最終的に80%以上の確率で検知できるようになった。しかし、センサと人の距離や、着ている服の色(赤外線の反射率が異なる)に左右されてしまうことが分かった。
⑦感想
予定していた所までは研究、製作できた。しかし、検知が「確実」ではなかったため実用化には至らなかった。ハードルは上がってしまうが、検知の部分を、Raspberrypiとカメラを用いて画像認識を行えば、精度や、利便性の向上が望めるだろう。