//說明:
//理論上這是一個小型時鐘的裝置,可掛置於電腦上方
//當前方45公分內有人(大約為頭到電腦的距離),他會開始倒數計時30秒(較好觀察,可改),並顯現小紅人
//當倒計時結束,小紅人會開始閃爍,同時撥放音樂,提醒你起來動一動
//當你離開45公分以外,小紅人的標誌會消失
#include <SPI.h>
#include <WiFi.h>
#include "p.h" // 少女的祈禱音符定義,放置p.h
//LED燈中彎的是正極,直的是負極
#include <WiFi.h> //引入函式庫
// 網路設定
const char* ssid = "Your wifi ssid"; // ESP32 引入密碼
const char* password = "Your password"; // 12345678
// 取得網路時間相關參數設定
const char* ntpServer = "time.google.com"; // Google - NTP Server
const long gmtOffset_sec = 28800; //台灣時區+8hr,28800=8*60*60
const int daylightOffset_sec = 0; //台灣無日光節約時間
// 共陰極、共陽極設定
#define hardwareConfig 0 //COMMON_ANODE=1 COMMON_CATHODE=0
// 條件編譯
#if hardwareConfig==0
// 共陰極(表示電燈一端全部街上負極,另一側接正極就是亮燈反之則不亮)
#define digitOn 0
#define digitOff 1
#define light 1
#define dark 0
#else
// 共陽極(同理但和共陰極相反)
#define digitOn 1
#define digitOff 0
#define light 0
#define dark 1
#endif
#define pin_A 25
#define pin_B 21
#define pin_C 4
#define pin_D 2
#define pin_E 15
#define pin_F 13
#define pin_G 14
#define pin_DP 40
#define pin_D1G1 33
#define pin_D1G2 32
#define pin_D1G3 22
#define pin_D1G4 19
#define pin_D1G5 26
#define pin_D1G6 27
int digitPins[6]={pin_D1G1, pin_D1G2, pin_D1G3, pin_D1G4, pin_D1G5, pin_D1G6};
int pin[8]={pin_A, pin_B, pin_C, pin_D ,pin_E ,pin_F ,pin_G ,pin_DP }; //陣列
int num[10][8]={
{light, light, light, light, light, light, dark, dark},
{dark, light, light, dark, dark, dark, dark, dark},
{light, light, dark, light, light, dark, light, dark},
{light, light, light, light, dark, dark, light, dark},
{dark, light, light, dark, dark, light, light, dark},
{light, dark, light, light, dark, light, light, dark},
{light, dark, light, light, light, light, light, dark},
{light, light, light, dark, dark, light, dark, dark},
{light, light, light, light, light, light, light, dark},
{light, light, light, light, dark, light, light, dark},
};
// 超音波腳位
#define trigPin 12
#define echoPin 35
double temp = 20.0; // 攝氏溫度20度,影響音速
// MAX7219 CS腳位
const byte CS = 5;
// 音樂參數
int BPM = 72;
float tempo = 60.0 / BPM;
int keyTransition = 0;
int musical_Alphabet[] = {
NOTE_C3, NOTE_G4, NOTE_C5, NOTE_E5, NOTE_G5, NOTE_C6, NOTE_E6, NOTE_D6,
NOTE_D6, NOTE_C6, NOTE_C6, NOTE_B5, NOTE_A5, NOTE_A5, NOTE_,
NOTE_G3, NOTE_B3, NOTE_D4, NOTE_G4, NOTE_B4, NOTE_D5, NOTE_B5, NOTE_A5,
NOTE_A5, NOTE_G5, NOTE_G5, NOTE_F5, NOTE_E5, NOTE_E5, NOTE_,
NOTE_C3, NOTE_G4, NOTE_C5, NOTE_E5, NOTE_G5, NOTE_C6, NOTE_E6, NOTE_D6,
NOTE_D6, NOTE_C6, NOTE_C6, NOTE_B5, NOTE_A5, NOTE_A5, NOTE_,
NOTE_G3, NOTE_B3, NOTE_D4, NOTE_G4, NOTE_B4, NOTE_D5, NOTE_C3, NOTE_E4, NOTE_G4, NOTE_C5, NOTE_E5, NOTE_G5,
NOTE_G2, NOTE_F4, NOTE_G4, NOTE_E5, NOTE_D5, NOTE_C5,
};
float beat[] = {
1.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3, 1.5, 0.5,
2.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3, 1, 1,
1.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3, 1.5, 0.5,
2.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3, 1, 1,
1.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3, 1.5, 0.5,
2.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3, 1, 1,
1.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3,
1.0 / 3, 1.0 / 3, 1.0 / 3, 2.0 / 3, 1.0 / 3, 2,
};
// LED矩陣圖案
byte person[9][8] = {
{0x60, 0x60, 0x30, 0x78, 0x18, 0x34, 0x22, 0x66},
{0x60, 0x60, 0x30, 0x7C, 0x5A, 0x34, 0x22, 0x42},
{0x60, 0x60, 0x30, 0x7C, 0xB2, 0x18, 0x66, 0x02},
{0xC0, 0xC0, 0x78, 0x64, 0xB0, 0x28, 0xC6, 0x02},
{0xC0, 0xC0, 0x60, 0x78, 0xB4, 0x3A, 0x26, 0xE2},
{0xC0, 0xC0, 0x60, 0x78, 0xB4, 0x32, 0x4C, 0xC4},
{0xC0, 0xC0, 0x60, 0x78, 0x74, 0x30, 0x58, 0x28},
{0x60, 0x60, 0x30, 0x38, 0x18, 0x14, 0x1C, 0x04},
{0x18, 0x18, 0x3C, 0x5A, 0x5A, 0x18, 0x24, 0x66}
};
// CS腳位
#define CS 5
// 閃爍開關與控制參數
bool blink = false;
unsigned long lastBlinkTime = 0;
const unsigned long blinkInterval = 500;
// 計時相關
const unsigned long intervalMillis = 30UL * 1000; // 30秒
unsigned long startMillis = 0;
bool timing = false;
void max7219(byte reg, byte data) {
digitalWrite(CS, LOW);
SPI.transfer(reg);
SPI.transfer(data);
digitalWrite(CS, HIGH);
}
byte reverse(byte b) {
b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
return b;
}
double readUltrasound() {
digitalWrite(trigPin, LOW);
delayMicroseconds(5);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
double echoTime = pulseIn(echoPin, HIGH);
double c = 331.5 + 0.607 * temp;
c = c * 100 / 1000000;
double cm = (echoTime / 2) * c;
return cm;
}
void playMusic() {
for (int i = 0; i < sizeof(musical_Alphabet) / sizeof(int); i++) {
tone(25, musical_Alphabet[i], beat[i] * tempo * 1000);
delay(beat[i] * tempo * 1000 + keyTransition);
}
}
void displayPerson(bool on) {
for (byte row = 0; row < 8; row++) {
max7219(row + 1, on ? person[0][row] : 0);
}
}
void setup() {
Serial.begin(115200);
// Connect to Wi-Fi
Serial.print("Connecting to ");
Serial.println(ssid);
//WiFi.begin(ssid, password); // 以STA(網路終端)模式連接到WiFi基地台
WiFi.begin("Wokwi-GUEST", "", 6); //wokwi提供的虛擬 WiFi 接入點
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected.");
Serial.print("IP位址: ");
Serial.println(WiFi.localIP()); // 回傳分配到的IP位址
Serial.print("WiFi RSSI: ");
Serial.println(WiFi.RSSI()); // 回傳接收訊號強度(以 dBm 為單位)
// Init and get the time
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
printLocalTime(); // 自定輸出時間函式
//七段顯示器初始化
for(int i=0; i<6; i++){
pinMode(digitPins[i], OUTPUT);
}
for(int i=0; i<8; i++){
pinMode(pin[i], OUTPUT);
}
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
pinMode(CS, OUTPUT);
digitalWrite(CS, HIGH);
SPI.begin();
max7219(0x09, 0); // 解碼模式
max7219(0x0A, 8); // 亮度
max7219(0x0B, 7); // 掃描限制(8行)
max7219(0x0C, 1); // 開機
max7219(0x0F, 0); // 顯示測試關閉
// 清除顯示
for (byte i = 0; i < 8; i++) max7219(i + 1, 0);
pinMode(25, OUTPUT); // 蜂鳴器
noTone(25);
}
void printLocalTime(){
struct tm timeinfo;
if(!getLocalTime(&timeinfo)){
Serial.println("Failed to obtain time");
return;
}
Serial.print("Hour: "); //顯示 時(24小時制)
Serial.println(timeinfo.tm_hour);
Serial.print("Minute: "); //顯示 分
Serial.println(timeinfo.tm_min);
Serial.print("Second: "); //顯示 秒
Serial.println(timeinfo.tm_sec);
int num1 = timeinfo.tm_min / 10;
int num2 = timeinfo.tm_min % 10;
int num3 = timeinfo.tm_sec / 10;
int num4 = timeinfo.tm_sec % 10;
int num5 = timeinfo.tm_hour / 10;
int num6 = timeinfo.tm_hour % 10;
display(num1, num2, num3, num4, num5, num6, 100);
}
void loop() {
printLocalTime();
double distance = readUltrasound();
if (distance <= 45.0) {
if (!timing) {
timing = true;
startMillis = millis();
}
} else {
timing = false;
}
if (timing) {
unsigned long elapsed = millis() - startMillis;
if (elapsed >= intervalMillis) {
// 播放音樂
playMusic();
// 閃爍動畫
unsigned long blinkStart = millis();
while (millis() - blinkStart < 5000) { // 閃爍5秒
unsigned long currentMillis = millis();
if (currentMillis - lastBlinkTime >= blinkInterval) {
blink = !blink;
displayPerson(blink);
lastBlinkTime = currentMillis;
}
}
timing = false; // 播放完停止計時,等待下次偵測
for (byte i = 0; i < 8; i++) max7219(i + 1, 0); // 清除畫面
} else {
displayPerson(true);
}
} else {
for (byte i = 0; i < 8; i++) max7219(i + 1, 0); // 無偵測清除畫面
}
}
int scanTime =1.0/(16*6)*1000;
//1.0表示可除至小數位,而一個輪迴顯示完6個數字是1/16秒,為人類視覺暫留的上限,1000是秒變毫秒
void display(int num1, int num2, int num3, int num4, int num5, int num6, int ms){
for(int i=0; i<ms/(scanTime*6); i++){ //即1/16秒
displayNum(0,num1); delay (scanTime);
displayNum(1,num2); delay (scanTime);
displayNum(2,num3); delay (scanTime);
displayNum(3,num4); delay (scanTime);
displayNum(4,num5); delay (scanTime);
displayNum(5,num6); delay (scanTime);
}
} //ms 是毫秒
void displayNum(int p, int n) {//p:position n:number
//關閉共用角位
for(int i=0; i<6; i++){
digitalWrite(digitPins[i], digitOff);
}
//準備數字訊號
for(int i=0; i<8; i++){
digitalWrite(pin[i], num[n][i]);
}
//打入數字訊號
digitalWrite(digitPins[p], digitOn);
}