#include <avr/interrupt.
h>
#include <Arduino.h>
#include <Arduino_FreeRTOS.h>
#include <queue.h>
#include <Wire.h>
#include <stdlib.h>
#include <TimeLib.h>
#include <DS1307RTC.h>
#define R1 4
#define G1 5
#define B1 6
#define R2 7
#define G2 8
#define B2 9
#define CLK 10
#define OE 11
#define STB 12
#define A A0
#define B A1
#define C A2
#define M A3
#define BUZZER_PIN 31
//bao thuc
TaskHandle_t alarmHandle = NULL; // Handle cho taskAlarm
volatile bool alarmSetting = true; // Đang trong chế độ cài đặt hay countdown
volatile int alarmMinutes = 0; // Số phút cài đặt
volatile bool buzzerOn = false; // Buzzer đang kêu hay không
volatile int alarmHour = 0; // Giờ cài báo thức
volatile int alarmMinute = 0; // Phút cài báo thức
volatile int alarmSecond = 0; // Giây còn lại khi countdown
volatile bool alarmFinished = false;
//chinh gio
volatile int adjustIndex = 0; // Vị trí chỉnh: 0,1,2,3
volatile bool adjusting = false; // Đang trong chế độ chỉnh giờ
volatile int editingHour = 0, editingMinute = 0; // Biến tạm để chỉnh
volatile bool blinkState = false; // Trạng thái nhấp nháy
TaskHandle_t adjustHandle = NULL; // Task chỉnh giờ handle
#define BTN_SET 3 // D3 - giữ để vào chế độ chỉnh
#define BTN_BRIGHT 2 // Nút duy nhất để điều chỉnh sáng
volatile int brightnessLevel = 64; // Giá trị từ 16 (sáng) đến 240 (tối)
unsigned long pressStart = 0;
bool isHolding = false;
unsigned char Buffer_display[3][16][4];
TaskHandle_t animationHandle = NULL;
TaskHandle_t realTimeHandle = NULL;
char col[32];
int wday, day1, month1, year1;
char color_row[4] = { 0x01, 0x03, 0x06, 0x02 };
const char font[][7] = {
0x38,0x44,0x4C,0x54,0x64,0x44,0x38, //0 48
0x10,0x30,0x10,0x10,0x10,0x10,0x38, //1 49
0x38,0x44,0x04,0x18,0x20,0x40,0x7C, //2 50
0x38,0x44,0x04,0x38,0x04,0x44,0x38, //3 51
0x08,0x18,0x28,0x48,0x7C,0x08,0x08, //4 52
0x7C,0x40,0x40,0x78,0x04,0x44,0x38, //5 53
0x18,0x20,0x40,0x78,0x44,0x44,0x38, //6 54
0x7C,0x04,0x08,0x10,0x20,0x20,0x20, //7 55
0x38,0x44,0x44,0x38,0x44,0x44,0x38, //8 56
0x38,0x44,0x44,0x3C,0x04,0x08,0x30, //9 57
0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};
const char dayOfTheWeek[7][3][7] = {
0x38, 0x44, 0x40, 0x38, 0x04, 0x44, 0x38, 0x00, 0x00, 0x48, 0x48, 0x48, 0x58, 0x28, 0x00,
0x00, 0x70, 0x48, 0x48, 0x48, 0x48, // Su
0x44, 0x6C, 0x54, 0x44, 0x44, 0x44, 0x44, 0x00, 0x00, 0x38, 0x44, 0x44, 0x44, 0x38, 0x00,
0x00, 0x70, 0x48, 0x48, 0x48, 0x48, // Mo
0x7C, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x48, 0x48, 0x48, 0x58, 0x28, 0x00,
0x00, 0x38, 0x40, 0x38, 0x04, 0x38, // Tu
0x44, 0x44, 0x54, 0x54, 0x54, 0x54, 0x28, 0x00, 0x00, 0x38, 0x44, 0x78, 0x40, 0x38, 0x00,
0x00, 0x70, 0x48, 0x48, 0x48, 0x48, // We
0x7C, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x40, 0x40, 0x70, 0x48, 0x48, 0x48, 0x48, 0x00,
0x00, 0x48, 0x48, 0x48, 0x58, 0x28, // Th
0x7C, 0x40, 0x40, 0x78, 0x40, 0x40, 0x40, 0x00, 0x00, 0x58, 0x24, 0x20, 0x20, 0x70, 0x10,
0x00, 0x10, 0x10, 0x10, 0x10, 0x18, // Fr
0x38, 0x44, 0x40, 0x38, 0x04, 0x44, 0x38, 0x00, 0x00, 0x38, 0x04, 0x3C, 0x44, 0x3C, 0x00,
0x20, 0x78, 0x20, 0x20, 0x28, 0x10, // Sa
};
const char miniFont[][3] = {
0xf8, 0x88, 0xf8, // 0
0x40, 0xf8, 0x00, // 1
0xb8, 0xa8, 0xe8, // 2
0xa8, 0xa8, 0xf8, // 3
0xe0, 0x20, 0xf8, // 4
0xe8, 0xa8, 0xb8, // 5
0xf8, 0xa8, 0xb8, // 6
0x80, 0x80, 0xf8, // 7
0xf8, 0xa8, 0xf8, // 8
0xe8, 0xa8, 0xf8 // 9
};
const char *monthName[12] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
tmElements_t tm;
QueueHandle_t mode = NULL;
ISR(TIMER1_OVF_vect) {
static unsigned char z = 0;
static unsigned long tStart = 0;
digitalWrite(OE, HIGH); // tắt LED
unsigned char i;
for (i = 0; i < 4; i++) {
data_transfet(Buffer_display[0][z][i], Buffer_display[1][z][i],
Buffer_display[2][z][i], Buffer_display[0][z + 8][i],
Buffer_display[1][z + 8][i], Buffer_display[2][z + 8][i]);
rows(z);
digitalWrite(STB, LOW);
digitalWrite(STB, HIGH);
tStart = micros();
while ((micros() - tStart) < brightnessLevel)
; // giữ OE = HIGH trong thời gian brightnessLevel
digitalWrite(OE, LOW); // bật LED
z++;
if (z == 8) z = 0;
TCNT1 = 64000;
}
bool getTime(const char *str) {
int Hour, Min, Sec;
if (sscanf(str, "%d:%d:%d", &Hour, &Min, &Sec) != 3) return false;
[Link] = Hour;
[Link] = Min;
[Link] = Sec;
return true;
bool getDate(const char *str) {
char Month[12];
int Day, Year;
uint8_t monthIndex;
if (sscanf(str, "%s %d %d", Month, &Day, &Year) != 3) return false;
for (monthIndex = 0; monthIndex < 12; monthIndex++) {
if (strcmp(Month, monthName[monthIndex]) == 0) break;
if (monthIndex >= 12) return false;
[Link] = Day;
[Link] = monthIndex + 1;
[Link] = CalendarYrToTm(Year);
return true;
}
void setup() {
[Link](9600);
[Link]();
pinMode(G1, OUTPUT); // cài đặt chế độ hoạt động của các chân
pinMode(R1, OUTPUT);
pinMode(B1, OUTPUT);
pinMode(R2, OUTPUT);
pinMode(G2, OUTPUT);
pinMode(B2, OUTPUT);
pinMode(A, OUTPUT);
pinMode(B, OUTPUT);
pinMode(C, OUTPUT);
pinMode(CLK, OUTPUT);
pinMode(OE, OUTPUT);
pinMode(STB, OUTPUT);
pinMode(M, INPUT);
pinMode(BTN_BRIGHT, INPUT_PULLUP);
pinMode(BUZZER_PIN, OUTPUT);
digitalWrite(BUZZER_PIN, LOW); // Buzzer OFF ban đầu
pinMode(30, INPUT_PULLUP); // Nút D30 chống nhảy loạn
mode = xQueueCreate(1, sizeof(int));
int status = 0;
xQueueSend(mode, &status, 0);
pinMode(3, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(3), changeOnMode, RISING);
getDate();
cli(); //Tắt ngắt toàn cục
TCCR1A = 0; //Reset Timer1
TCCR1B = 0;
TIMSK1 = 0;
TCCR1B |= (1 << CS11);
TCNT1 = 64000;
TIMSK1 |= (1 << TOIE1);
sei();
xTaskCreate(taskRealTime, "2", 128, NULL, 2, &realTimeHandle);
xTaskCreate(animation, "3", 256, NULL, 2, &animationHandle);
xTaskCreate(taskAdjustTime, "AdjustTime", 256, NULL, 2, &adjustHandle);
vTaskSuspend(adjustHandle); // Ban đầu tạm ngưng task chỉnh giờ
xTaskCreate(taskAlarm, "AlarmTask", 256, NULL, 2, &alarmHandle);
vTaskSuspend(alarmHandle);
vTaskStartScheduler();
void loop() {
}
void changeOnMode() {
static int moe = 0;
moe = (++moe) % 7;
xQueueOverwriteFromISR(mode, &moe, 0);
vTaskResume(realTimeHandle);
if (moe == 5) {
vTaskResume(adjustHandle); // Nếu vào mode chỉnh giờ, kích hoạt task chỉnh giờ
} else {
vTaskSuspend(adjustHandle); // Nếu ra khỏi mode chỉnh giờ, tạm ngưng task chỉnh giờ
if (moe == 6) {
vTaskResume(alarmHandle);
} else {
vTaskSuspend(alarmHandle);
void animation(void *pv) {
int x, y = 0, b;
int last_b = -1;
while (1) {
xQueuePeek(mode, &b, 0);
if (b != last_b) {
if (b == 4) {
showBrightnessBar2(); // Vào chế độ chỉnh sáng thì vẽ luôn
last_b = b;
if (b == 1) {
for (x = 0; x < 16; x++) {
draw_icon(x * 4, 0, (y + x) % 3 + 1);
draw_icon1(32 - x * 4, 15, (y + x) % 3 + 1);
y++;
delay(100);
} else if (b == 0) {
for (y = 0; y < 7; y++) {
for (x = 0; x < 5; x++) {
if ((dayOfTheWeek[wday - 1][0][y] & (0x40 >> x)) != 0) set_pixel(x + 0, y + 9, 0x05);
else set_pixel(x + 0, y + 9, 0x00);
vTaskDelay(20);
for (y = 0; y < 7; y++) {
for (x = 0; x < 5; x++) {
if ((dayOfTheWeek[wday - 1][1][y] & (0x40 >> x)) != 0) set_pixel(x + 5, y + 9, 0x05);
else set_pixel(x + 5, y + 9, 0x00);
}
vTaskDelay(20);
for (y = 0; y < 7; y++) {
for (x = 0; x < 5; x++) {
if ((dayOfTheWeek[wday - 1][2][y] & (0x40 >> x)) != 0) set_pixel(x + 10, y + 9, 0x05);
else set_pixel(x + 10, y + 9, 0x00);
vTaskDelay(20);
put_mini_number(16, 11, day1 / 10, 0x04);
put_mini_number(20, 11, day1 % 10, 0x04);
vTaskDelay(20);
put_mini_number(25, 11, month1 / 10, 0x07);
put_mini_number(29, 11, month1 % 10, 0x07);
delay(5000);
for (x = 31; x >= 0; x--) {
for (y = 9; y < 16; y++) {
set_pixel(x, y, 0x00);
vTaskDelay(5);
put_char(4, 9, 2, 0x04);
vTaskDelay(20);
put_char(10, 9, 0, 0x04);
vTaskDelay(20);
put_char(16, 9, year1 / 10, 0x04);
vTaskDelay(20);
put_char(22, 9, year1 % 10, 0x04);
delay(5000);
for (x = 31; x >= 0; x--) {
for (y = 8; y < 16; y++) {
set_pixel(x, y, 0x00);
vTaskDelay(5);
} else if (b == 2) {
delay(50);
for (x = 0; x < 31; x++) {
y = analogRead(M);
y = abs(y - 133);
if (y < 5) y = 0;
drawRowFalling(x, sqrt(y) * 5);
} else if (b == 3) {
delay(50);
for (x = 0; x < 31; x++) {
y = analogRead(M);
y = abs(y - 133);
if (y < 5) y = 0;
drawRowFallingTopDown(x, sqrt(y) * 5);
}
} else if (b == 4) {
if (digitalRead(BTN_BRIGHT) == LOW) {
if (!isHolding) {
pressStart = millis(); // lưu thời điểm bắt đầu nhấn
isHolding = true;
// Nếu giữ lâu hơn 0.5 giây
if (millis() - pressStart > 500) {
brightnessLevel -= 8; // tăng độ sáng (giảm giá trị)
if (brightnessLevel < 16) brightnessLevel = 16;
showBrightnessBar2(); // nếu có hàm hiển thị
vTaskDelay(200 / portTICK_PERIOD_MS); // delay để tăng dần
} else {
// Nếu vừa nhả nút và thời gian giữ < 500ms ⇒ nhấn nhanh
if (isHolding && millis() - pressStart <= 500) {
brightnessLevel += 16; // giảm độ sáng
if (brightnessLevel > 240) brightnessLevel = 240;
showBrightnessBar2();
isHolding = false;
vTaskDelay(50 / portTICK_PERIOD_MS);
}else if (b == 5){
vTaskDelay(100 / portTICK_PERIOD_MS);
}else if (b == 6){
vTaskDelay(100 / portTICK_PERIOD_MS);
void taskRealTime(void *pv) {
int nowStatus, nextStatus;
xQueuePeek(mode, &nowStatus, 0);
while (1) {
xQueuePeek(mode, &nextStatus, 0);
if (nowStatus != nextStatus) {
nowStatus = nextStatus;
vTaskDelete(animationHandle);
clear_display();
xTaskCreate(animation, "3", 128, NULL, 2, &animationHandle);
if (nowStatus == 0) {
getTime();
} else if (nowStatus == 1) {
getTime1();
} else {
vTaskSuspend(realTimeHandle);
}
}
void showBrightnessBar2() {
// Xóa toàn bộ màn hình trước
for (int x = 0; x < 32; x++) {
for (int y = 0; y < 16; y++) {
set_pixel(x, y, 0x00);
// Vẽ viền hình chữ nhật (cột và hàng thứ 2 ngoài cùng)
for (int y = 1; y <= 14; y++) {
set_pixel(1, y, 0x04); // Cột bên trái
set_pixel(30, y, 0x04); // Cột bên phải
for (int x = 1; x <= 30; x++) {
set_pixel(x, 1, 0x04); // Hàng trên
set_pixel(x, 14, 0x04); // Hàng dưới
// Ánh xạ brightnessLevel (0~255) -> số lượng cột sáng (0~26)
int numColumns = map(brightnessLevel, 0, 255, 0, 26);
// Vẽ các cột sáng bên trong hình chữ nhật
for (int x = 0; x < numColumns; x++) {
for (int y = 3; y <= 12; y++) { // Bỏ cột và hàng thứ 3, chỉ vẽ trong vùng sạch
set_pixel(x + 3, y, 0x04); // x+3 để bắt đầu từ cột 3
}
void taskAdjustTime(void *pv) {
unsigned long lastBlink = 0;
unsigned long pressStart = 0;
bool isHolding = false;
static bool lastD30State = HIGH;
int nowMode;
while (1) {
xQueuePeek(mode, &nowMode, 0);
if (nowMode != 5) {
vTaskSuspend(NULL);
if (!adjusting) {
// Lấy giờ phút từ DS1307
[Link](0x68);
[Link]((byte)0x00);
[Link]();
[Link](0x68, 7);
[Link](); // bỏ giây
editingMinute = bcd2dec([Link]());
editingHour = bcd2dec([Link]());
adjusting = true;
adjustIndex = 0;
// Mỗi vòng đều clear màn hình và vẽ lại
clear_display();
// Vẽ giờ phút màu xanh dương
put_mini_number_2(1, 0, editingHour / 10, (adjustIndex == 0 && blinkState) ? 0x00 : 0x04);
put_mini_number_2(8, 0, editingHour % 10, (adjustIndex == 1 && blinkState) ? 0x00 : 0x04);
draw_point(15, 5, 0x07);
draw_point(15, 9, 0x07);
put_mini_number_2(18, 0, editingMinute / 10, (adjustIndex == 2 && blinkState) ? 0x00 :
0x04);
put_mini_number_2(25, 0, editingMinute % 10, (adjustIndex == 3 && blinkState) ? 0x00 :
0x04);
// Blink số đang chỉnh mỗi 500ms
if (millis() - lastBlink >= 500) {
blinkState = !blinkState;
lastBlink = millis();
// Xử lý nút D2 tăng giảm
if (digitalRead(BTN_BRIGHT) == LOW) {
if (!isHolding) {
pressStart = millis();
isHolding = true;
if (millis() - pressStart > 500) { // Giữ lâu để giảm
adjust_value(-1);
vTaskDelay(200 / portTICK_PERIOD_MS);
} else {
if (isHolding && millis() - pressStart <= 500) { // Nhấn nhanh để tăng
adjust_value(1);
isHolding = false;
// Xử lý nút D30 đổi vị trí chỉnh
bool currentD30State = digitalRead(30);
if (lastD30State == HIGH && currentD30State == LOW) {
adjustIndex = (adjustIndex + 1) % 4;
lastD30State = currentD30State;
// Xử lý nút D3 lưu và thoát
if (digitalRead(3) == LOW) {
[Link](0x68);
[Link]((byte)0x00);
[Link](dec2bcd(0)); // Reset giây
[Link](dec2bcd(editingMinute));
[Link](dec2bcd(editingHour));
[Link]();
adjusting = false;
int backToMode = 0;
xQueueOverwrite(mode, &backToMode);
vTaskResume(realTimeHandle);
vTaskSuspend(NULL);
vTaskDelay(50 / portTICK_PERIOD_MS);
void adjust_value(int delta) {
switch (adjustIndex) {
case 0: editingHour += delta * 10; break; // Hàng chục giờ
case 1: editingHour += delta; break; // Đơn vị giờ
case 2: editingMinute += delta * 10; break; // Hàng chục phút
case 3: editingMinute += delta; break; // Đơn vị phút
// Giới hạn hợp lệ
if (editingHour > 23) editingHour = 0;
if (editingHour < 0) editingHour = 23;
if (editingMinute > 59) editingMinute = 0;
if (editingMinute < 0) editingMinute = 59;
void taskAlarm(void *pv) {
unsigned long lastMillis = 0;
unsigned long pressStart = 0;
bool isHolding = false;
static bool lastD30State = HIGH;
int nowMode;
while (1) {
xQueuePeek(mode, &nowMode, 0);
if (nowMode != 6) {
vTaskSuspend(NULL); // Không phải mode 6 thì ngủ
if (alarmSetting) {
// Đang trong chế độ cài đặt giờ phút báo thức
getTime(); // Lấy giờ thực tế hiển thị (đồng hồ b == 0)
// In giờ:phút mini font
put_mini_number(1, 11, alarmHour / 10, 0x05);
put_mini_number(5, 11, alarmHour % 10, 0x05);
draw_icon3(9, 12, 0x05); // chấm trên
draw_icon3(9, 14, 0x05); // chấm dưới
put_mini_number(11, 11, alarmMinute / 10, 0x05);
put_mini_number(15, 11, alarmMinute % 10, 0x05);
// Xử lý nút D2: tăng phút (giữ để tăng nhanh)
if (digitalRead(BTN_BRIGHT) == LOW) {
if (!isHolding) {
pressStart = millis();
isHolding = true;
if (millis() - pressStart > 500) { // Giữ lâu
alarmMinute++;
if (alarmMinute >= 60) {
alarmMinute = 0;
alarmHour++;
if (alarmHour >= 24) alarmHour = 0;
vTaskDelay(200 / portTICK_PERIOD_MS);
} else {
if (isHolding && millis() - pressStart <= 500) { // Nhấn nhanh
alarmMinute++;
if (alarmMinute >= 60) {
alarmMinute = 0;
alarmHour++;
if (alarmHour >= 24) alarmHour = 0;
isHolding = false;
// Xử lý nút D30 để xác nhận bắt đầu countdown
bool currentD30State = digitalRead(30);
if (lastD30State == HIGH && currentD30State == LOW) {
alarmSetting = false; // Xác nhận cài đặt
alarmSecond = 0; // Bắt đầu từ giây 0
alarmFinished = false; // Reset trạng thái hoàn thành
lastMillis = millis();
lastD30State = currentD30State;
} else {
// Đang trong chế độ đếm ngược giờ phút giây
getTime(); // Vẫn hiển thị giờ thực
// In giờ:phút:giây mini font
put_mini_number(1, 11, alarmHour / 10, 0x04);
put_mini_number(5, 11, alarmHour % 10, 0x04);
draw_icon3(9, 12, 0x04);
draw_icon3(9, 14, 0x04);
put_mini_number(11, 11, alarmMinute / 10, 0x04);
put_mini_number(15, 11, alarmMinute % 10, 0x04);
draw_icon3(19, 12, 0x04);
draw_icon3(19, 14, 0x04);
put_mini_number(21, 11, alarmSecond / 10, 0x04);
put_mini_number(25, 11, alarmSecond % 10, 0x04);
// Countdown mỗi giây
if (millis() - lastMillis >= 1000) {
lastMillis = millis();
if (!alarmFinished) { // Chỉ đếm khi chưa kết thúc
if (alarmSecond == 0) {
if (alarmMinute == 0) {
if (alarmHour == 0) {
// Hết giờ: bật buzzer 1 lần duy nhất
buzzerOn = true;
digitalWrite(BUZZER_PIN, HIGH);
alarmFinished = true; // Khóa, không cho buzzer bật lại
} else {
alarmHour--;
alarmMinute = 59;
alarmSecond = 59;
} else {
alarmMinute--;
alarmSecond = 59;
} else {
alarmSecond--;
// Ấn nút D30 để tắt buzzer
bool currentD30State = digitalRead(30);
if (lastD30State == HIGH && currentD30State == LOW && buzzerOn) {
buzzerOn = false;
digitalWrite(BUZZER_PIN, LOW);
lastD30State = currentD30State;
// Nếu ấn nút D3 để đổi mode thì reset mọi thứ
if (digitalRead(3) == LOW) {
alarmSetting = true;
alarmHour = 0;
alarmMinute = 0;
alarmSecond = 0;
buzzerOn = false;
alarmFinished = false;
digitalWrite(BUZZER_PIN, LOW);
int backToMode = 0;
xQueueOverwrite(mode, &backToMode);
vTaskResume(realTimeHandle);
vTaskSuspend(NULL);
vTaskDelay(50 / portTICK_PERIOD_MS);
void data_transfet(char byte_r1, char byte_g1, char byte_b1, char byte_r2, char byte_g2, char
byte_b2) { // chyển dữ liệu vào màn hình
unsigned char i, mask;
for (i = 0; i < 8; i++) {
mask = 0x80 >> i;
digitalWrite(R1, byte_r1 & mask);
digitalWrite(G1, byte_g1 & mask);
digitalWrite(B1, byte_b1 & mask);
digitalWrite(R2, byte_r2 & mask);
digitalWrite(G2, byte_g2 & mask);
digitalWrite(B2, byte_b2 & mask);
digitalWrite(CLK, LOW);
digitalWrite(CLK, HIGH); // tạo xung clk cho phép chuyển dữ liệu
void displayDayOfWeek(int wday, uint8_t color) {
int x_offset = 0; // Vị trí bắt đầu X
int y_offset = 9; // Vị trí bắt đầu Y (hàng dưới LED)
for (int letter = 0; letter < 3; letter++) { // Duyệt 3 chữ cái
for (int y = 0; y < 7; y++) { // Duyệt 7 hàng pixel
for (int x = 0; x < 5; x++) { // Duyệt 5 cột pixel mỗi chữ
if ((dayOfTheWeek[wday - 1][letter][y] & (0x40 >> x)) != 0) {
set_pixel(x + x_offset, y + y_offset, color);
} else {
set_pixel(x + x_offset, y + y_offset, 0x00); // Tắt pixel nền
x_offset += 6; // Sau mỗi chữ cái cách nhau 6 pixel
void rows(unsigned char row_number) { // chọn hàng được sáng
switch (row_number) {
case 0:
digitalWrite(A, LOW);
digitalWrite(B, LOW);
digitalWrite(C, LOW);
break;
case 1:
digitalWrite(A, HIGH);
digitalWrite(B, LOW);
digitalWrite(C, LOW);
break;
case 2:
digitalWrite(A, LOW);
digitalWrite(B, HIGH);
digitalWrite(C, LOW);
break;
case 3:
digitalWrite(A, HIGH);
digitalWrite(B, HIGH);
digitalWrite(C, LOW);
break;
case 4:
digitalWrite(A, LOW);
digitalWrite(B, LOW);
digitalWrite(C, HIGH);
break;
case 5:
digitalWrite(A, HIGH);
digitalWrite(B, LOW);
digitalWrite(C, HIGH);
break;
case 6:
digitalWrite(A, LOW);
digitalWrite(B, HIGH);
digitalWrite(C, HIGH);
break;
case 7:
{
digitalWrite(A, HIGH);
digitalWrite(B, HIGH);
digitalWrite(C, HIGH);
break;
void set_pixel(unsigned char x, unsigned char y, unsigned char color) {
if (x > 31 || x < 0 || y > 15 || y < 0) return;
if ((color & 0x01) != 0) Buffer_display[0][y][x / 8] = Buffer_display[0][y][x / 8] | (0x80 >> (x %
8));
else Buffer_display[0][y][x / 8] = Buffer_display[0][y][x / 8] & (~(0x80 >> (x % 8)));
if ((color & 0x02) != 0) Buffer_display[1][y][x / 8] = Buffer_display[1][y][x / 8] | (0x80 >> (x %
8));
else Buffer_display[1][y][x / 8] = Buffer_display[1][y][x / 8] & (~(0x80 >> (x % 8)));
if ((color & 0x04) != 0) Buffer_display[2][y][x / 8] = Buffer_display[2][y][x / 8] | (0x80 >> (x %
8));
else Buffer_display[2][y][x / 8] = Buffer_display[2][y][x / 8] & (~(0x80 >> (x % 8)));
void put_char(int i, int j, char txt, char color) {
char x = 0, y = 0, z = txt;
for (y = 0; y < 7; y++) {
for (x = 0; x < 5; x++) {
if ((font[txt][y] & (0x40 >> x)) != 0) set_pixel(x + i, y + j, color);
else set_pixel(x + i, y + j, 0x00);
void drawRowFalling(int x, int inputHeight) {
// Giới hạn chiều cao đầu vào
if (x < 10 && inputHeight > 14) inputHeight = 14;
else if (x < 20 && inputHeight > 10) inputHeight = 10;
else if (inputHeight > 6) inputHeight = 6;
// Lật ngược hệ trục: 0 ở trên → 15 ở dưới
inputHeight = 15 - inputHeight;
// Nếu input cao hơn → cập nhật ngay
if (inputHeight < col[x]) {
col[x] = inputHeight;
} else if (col[x] < 15) {
col[x]++; // Rơi từ từ xuống
// Vẽ cột sáng
int i = 14;
for (; i > col[x] + 1; i--) {
set_pixel(x, i, 0x02); // xanh lá
}
// Vệt sáng trên đỉnh
if (i <= 14) {
set_pixel(x, i, 0x03); // cam
i--;
if (i < 13) {
set_pixel(x, i, 0x01); // đỏ
i--;
// Tắt phần dưới
for (; i >= 0; i--) {
set_pixel(x, i, 0x00);
void drawRowFallingTopDown(int x, int inputHeight) {
// Giới hạn chiều cao đầu vào
if (x < 10 && inputHeight > 14) inputHeight = 14;
else if (x < 20 && inputHeight > 10) inputHeight = 10;
else if (inputHeight > 6) inputHeight = 6;
// Không cần lật trục
// Vì bây giờ 0 ở trên, 15 ở dưới
// Nếu input cao hơn → cập nhật ngay
if (inputHeight > col[x]) {
col[x] = inputHeight;
} else if (col[x] > 0) {
col[x]--; // Rút ngược lên từ từ
// Vẽ cột sáng
int i = 0;
for (; i < col[x] - 1; i++) {
set_pixel(x, i, 0x02); // xanh lá (giữa cột)
// Vệt sáng đầu đỉnh
if (i < 15) {
set_pixel(x, i, 0x03); // cam (đỉnh)
i++;
if (i < 15) {
set_pixel(x, i, 0x01); // đỏ (đuôi)
i++;
}
// Tắt phần dưới
for (; i < 15; i++) {
set_pixel(x, i, 0x00);
int bcd2dec(byte num) {
return ((num / 16 * 10) + (num % 16));
int dec2bcd(byte num) {
return ((num / 10 * 16) + (num % 10));
void getDate() {
[Link](0x68);
[Link]((byte)0x00);
[Link]();
[Link](0x68, 7);
day1 = [Link]();
day1 = [Link]();
day1 = [Link]();
wday = bcd2dec([Link]());
day1 = bcd2dec([Link]());
month1 = bcd2dec([Link]());
year1 = bcd2dec([Link]());
}
void getTime() {
[Link](0x68);
[Link]((byte)0x00);
[Link]();
[Link](0x68, 7);
int x;
x = bcd2dec([Link]() & 0x7f);
put_mini_number(24, 3, x / 10, 0x03);
put_mini_number(28, 3, x % 10, 0x03);
if (x % 2 == 0) {
draw_point(11, 2, 0x01);
draw_point(11, 5, 0x01);
} else {
draw_point(11, 2, 0x00);
draw_point(11, 5, 0x00);
x = bcd2dec([Link]());
put_char(13, 1, x / 10, 0x02);
put_char(18, 1, x % 10, 0x02);
x = bcd2dec([Link]());
put_char(1, 1, x / 10, 0x02);
put_char(6, 1, x % 10, 0x02);
if (x == 0) {
getDate();
}
void getTime1() {
[Link](0x68);
[Link]((byte)0x00);
[Link]();
[Link](0x68, 7);
int x;
x = bcd2dec([Link]() & 0x7f);
if (x % 2 == 0) {
draw_point(15, 5, 0x02);
draw_point(15, 9, 0x02);
} else {
draw_point(15, 5, 0x00);
draw_point(15, 9, 0x00);
x = bcd2dec([Link]());
put_mini_number_2(18, 3, x / 10, 0x01);
put_mini_number_2(25, 3, x % 10, 0x01);
x = bcd2dec([Link]());
put_mini_number_2(1, 3, x / 10, 0x01);
put_mini_number_2(8, 3, x % 10, 0x01);
if (x == 0) {
getDate();
}
}
void draw_point(int x, int y, char color) {
set_pixel(x, y, color);
set_pixel(x + 1, y, color);
set_pixel(x + 1, y + 1, color);
set_pixel(x, y + 1, color);
void put_mini_number(int i, int j, char txt, char color) {
char x, y;
for (y = 0; y < 3; y++) {
for (x = 0; x < 5; x++) {
if ((miniFont[txt][y] & (0x80 >> x)) != 0) set_pixel(y + i, x + j, color);
else set_pixel(y + i, x + j, 0x00);
void put_mini_number_2(int i, int j, char txt, char color) {
char x, y;
for (y = 0; y < 3; y++) {
for (x = 0; x < 5; x++) {
int px = 2 * y + i;
int py = x * 2 + j;
// Bỏ qua nếu px, py vượt quá phạm vi
if (px > 31 || py > 14) continue;
if ((miniFont[txt][y] & (0x80 >> x)) != 0) {
draw_point(px, py, color);
} else draw_point(px, py, 0x00);
void draw_icon(int x, int y, char color) {
set_pixel(x, y, color);
set_pixel(x + 1, y + 1, color);
set_pixel(x + 1, y, color);
set_pixel(x + 2, y, color);
void draw_icon3(int x, int y, char color) {
set_pixel(x, y, color);
void draw_icon1(int x, int y, char color) {
set_pixel(x, y, color);
set_pixel(x + 1, y - 1, color);
set_pixel(x + 1, y, color);
set_pixel(x + 2, y, color);
}
void clear_display() {
unsigned char i, j;
for (i = 0; i < 16; i++)
for (j = 0; j < 32; j++) {
set_pixel(j, i, 0x00);