Viết firmware đồng hồ đơn giản với Arduino
- Trung cấp
- Bài viết
- Từ 16 tuổi
- 14 phút đọc

Firmware đồng hồ có thể bắt đầu bằng Arduino core, thư viện màn hình và các phần xử lý giờ, thời tiết, cấu hình tách nhau. Thiết kế dưới đây dùng vòng lặp có lịch công việc và trạng thái rõ ràng. Mục tiêu là sửa từng phần mà vẫn giữ mặt giờ hoạt động.
Mục lục
- Bo chạy firmware
- Màn được thư viện hỗ trợ
Bạn sẽ học được gì
- Chia mã theo trách nhiệm thay vì gom mọi việc vào phần vẽ.
- Mô tả vòng lặp lấy dữ liệu theo lịch có giới hạn chờ.
- Thiết kế menu nút bấm, báo thức và cập nhật OTA có đường phục hồi.
Cần biết trước
Nên đọc về giờ và thời tiết cùng cấu hình LCD; cần hiểu hàm và cấu trúc dữ liệu cơ bản.
Nên bắt đầu từ bộ công cụ nào?
Với bản mẫu đề xuất, chọn Arduino core cho bo được hỗ trợ và dùng các ví dụ chính thức làm điểm khởi đầu. Arduino có setup() chạy khi khởi động hoặc đặt lại. Ví dụ SimpleTime của Espressif minh họa WiFi.begin(), configTime() và getLocalTime() cho kết nối và giờ mạng.
Trước tiên hãy nạp ví dụ giờ mạng lên đúng bo, xem kết quả qua cổng nối tiếp, rồi thử ví dụ riêng của thư viện màn hình. Khi cả hai hoạt động độc lập mới ghép chúng. Ghi lại phiên bản core, thư viện, mã bo và cấu hình màn trong ghi chú cấu hình để có thể dựng lại bản đang chạy.
Dùng cổng nối tiếp để kiểm đường dữ liệu khi phát triển. Trong bản dùng hằng ngày, nút bấm chỉnh báo thức và đèn; mục “Cài Wi-Fi” mới mở trang cài đặt nội bộ qua điểm truy cập. Espressif có API cho chế độ trạm và điểm truy cập; trang biểu mẫu, việc xác nhận địa danh và cách đóng chế độ cài đặt vẫn là phần ứng dụng cần viết.
Đừng coi ví dụ có sẵn là toàn bộ sản phẩm. Một ví dụ chờ kết nối chỉ minh họa đường đi thành công. Đồng hồ thực tế còn cần hủy chờ, thử lại và cho phép người dùng thao tác khi mạng hỏng.
- Thử hiển thị
- Thêm dữ liệu mạng
- Khôi phục cấu hình
Chia mã và trạng thái ra sao?
Dùng một bản dữ liệu chung cho phần hiển thị, với trạng thái của từng nguồn. Cấu trúc đề xuất như sau.
| Phần mã | Đầu vào | Kết quả |
|---|---|---|
| Cấu hình | Lựa chọn đã xác nhận | Địa danh, tọa độ, múi giờ, đèn và lịch báo thức |
| Nút và menu | Sự kiện nhấn đã lọc | Chuyển mục, sửa bản tạm, lưu hoặc hủy |
| Báo thức | Giờ hợp lệ, lịch đã lưu | Lần hẹn kế tiếp, trạng thái reo hoặc báo lại |
| OTA | Yêu cầu cập nhật đã xác nhận | Tiến độ, lỗi, phiên bản và trạng thái kiểm tra khởi động |
| Giữ giờ | Đồng hồ cục bộ và lần đồng bộ | Giờ cùng trạng thái hợp lệ |
| Thời tiết | Tọa độ và phiên bản lựa chọn | Dữ liệu, dấu thời gian, trạng thái |
| Hiển thị | Bản dữ liệu và hồ sơ màn | Mặt đồng hồ hoặc trang cài đặt |
| Nguồn | Tín hiệu nguồn được phần cứng hỗ trợ | Chế độ đèn và lịch hoạt động |
Preferences giữ dữ liệu qua khởi động lại và mất nguồn, phù hợp cho lựa chọn nhỏ. Trong thiết kế này, chỉ ghi khi người dùng xác nhận thay đổi. Giờ đang chạy và bộ đếm lập lịch được giữ ở trạng thái vận hành; lưu cấu hình không thay thế khối giữ giờ dự phòng.
Tách trạng thái đã lưu khỏi trạng thái đang sửa. Khi người dùng hủy cài đặt, dùng lại bản đã xác nhận. Khi lưu, kiểm địa danh và tọa độ trước khi thay bản đang dùng. Cách tổ chức này cho phép thử biểu mẫu mà chưa cần đổi phần vẽ mặt giờ.
- Giờ và độ hợp lệ
- Thời tiết và độ cũ
- Bản dữ liệu hiển thị
- Bộ hiển thị
Vòng lặp nên chạy như thế nào?
Ưu tiên đọc nút và cập nhật mặt giờ trong khi những công việc mạng tiến từng bước theo thời hạn. Mã dưới đây là mã giả mô tả cấu trúc, chưa phải sketch nạp được; các hàm dịch vụ là giao diện cần triển khai cho bo và thư viện đã chọn.
setup:
settings = loadConfirmedSettings()
display.begin(screenProfile)
buttons.begin(buttonProfile)
drawUnavailableFace()
alarm.restoreScheduleAndHandledOccurrence(settings)
ota.runPendingBootSelfTest(settings, display, buttons, alarm)
network.startIfConfigured(settings)
loop:
events = buttons.pollDebouncedEvents()
if alarm.isRinging():
alarm.handleStopAndSnooze(events)
else:
menu.handle(events)
network.tickUntilDeadline()
timeService.tickIfDue()
alarm.tick(timeService, monotonicClock, settings)
weatherService.tickIfDue(settings.locationVersion)
acceptOnlyValidCurrentLocationResults()
ota.tickIfConfirmedAndAllowed(powerState, alarm, menu)
view = makeSnapshot(timeService, weatherService, powerState, alarm, ota)
redrawChangedRegions(view)
lighting.applyPolicy(buttons, powerState)
Thiết kế tick nghĩa là mỗi lần gọi làm phần việc đã sẵn sàng rồi trả quyền điều khiển. Đặt tên hàm tick không tự làm một thư viện đồng bộ thành bất đồng bộ. Nếu HTTP đang chặn, đặt timeout hữu hạn và đo phản hồi nút trong lúc chờ; khi không đạt yêu cầu, dùng tác vụ mạng riêng hoặc API bất đồng bộ có ranh giới dữ liệu rõ ràng.
Yêu cầu HTTPS cần xác minh máy chủ theo khả năng thư viện đã chọn. Tài liệu Espressif mô tả dùng chứng chỉ gốc hoặc gói chứng chỉ để xác minh. Hãy chuẩn bị giờ hợp lệ và cấu hình chứng chỉ trước khi thử đường tải thời tiết; giữ lỗi xác minh là lỗi riêng để chẩn đoán, thay vì bỏ kiểm chứng chỉ.
Sau lỗi, lên lịch lần thử tiếp theo và trả về mặt đồng hồ. Khi thành công, thay bản dữ liệu đã kiểm tra rồi yêu cầu vẽ lại. Thời tiết không cần được tải lại trong từng lượt vẽ. Sau khi các đường lỗi cơ bản ổn định, thêm báo thức và OTA theo các trạng thái dưới đây; chế độ ngủ phải giữ đúng lịch đánh thức.
- Đọc thao tác
- Tiến công việc sẵn sàng
- Kiểm thời hạn
- Trả về vòng lặp
Cài đặt và báo thức bằng nút bấm thế nào?
Thiết kế đề xuất dùng ba nút Menu, + và −, với thao tác lưu và hủy hiện ngay trên màn hình. Arduino giải thích hiện tượng dội nút: một lần bấm cơ học có thể bị đọc thành nhiều lần. Ví dụ Debounce dùng millis() theo dõi thời gian để lọc tín hiệu; ứng dụng vẫn phải tự phân biệt nhấn ngắn với giữ nút.
| Trạng thái | Menu | + | − |
|---|---|---|---|
| Mặt giờ | Nhấn mở menu | Bật đèn | Xem lịch báo thức |
| Chọn mục | Nhấn để mở; giữ để thoát | Mục tiếp theo | Mục trước |
| Sửa một trường | Nhấn sang trường kế; giữ để hủy | Tăng giá trị | Giảm giá trị |
| Trang xác nhận | Nhấn để chọn Lưu hoặc Hủy | Chuyển lựa chọn | Chuyển lựa chọn |
| Đang reo | Nhấn để dừng lần này | Báo lại | Bật đèn, giữ nguyên chuông |
Menu đề xuất gồm Báo thức, Đèn nền, Giờ và múi giờ, Cài Wi-Fi, Cập nhật, Thông tin. Mỗi trang chỉ sửa một giá trị; tên trường và lựa chọn đang chọn luôn hiện rõ. Khi giữ Menu đã phát sinh lệnh hủy, lần thả nút không được tạo thêm lệnh nhấn ngắn. Chống dội và nhận dạng giữ nút dùng bộ đếm thời gian, tránh vòng chờ khiến mặt giờ ngừng cập nhật.
Để đặt báo thức, mở Báo thức, chọn Bật, chỉnh giờ, phút, ngày lặp và khoảng báo lại, rồi xác nhận Lưu. Bản đầu chỉ có một lịch, cho phép lặp hằng ngày hoặc chọn ngày trong tuần. Hủy hoặc hết thời gian thao tác sẽ bỏ bản sửa tạm; mặt giờ hiện lịch đã lưu cùng trạng thái bật/tắt. Dừng chuông chỉ kết thúc lần đang reo, còn Tắt báo thức là thay đổi lịch phải lưu.
Trong mô hình đề xuất, cấu hình gồm mã lịch, giờ địa phương, phút, ngày lặp, bật/tắt và khoảng báo lại. Preferences lưu cấu hình đã xác nhận. Trạng thái vận hành gồm chờ giờ hợp lệ, đã hẹn, đang reo và báo lại. Khi chưa có giờ hợp lệ, hiện “Chờ đặt giờ” và chưa kích chuông. Người dùng có thể đặt ngày, giờ và múi giờ bằng nút khi mất mạng; thời tiết vẫn báo chưa có dữ liệu.
Mỗi lần hẹn có khóa gồm mã lịch, ngày địa phương và giờ hẹn. Trước khi reo, ghi khóa đã xử lý rồi mới bật đầu ra âm thanh. Lựa chọn này ưu tiên tránh reo lặp sau khởi động lại, nhưng mất nguồn giữa hai bước có thể làm bỏ lỡ chuông; cần ghi rõ và thử giới hạn ấy.
Lần đồng bộ lùi giờ không xóa khóa. Với giờ bị bỏ qua khi đổi múi giờ hoặc chỉnh tiến đồng hồ, chính sách bản đầu là bỏ lần đã qua và hẹn lần kế tiếp, không reo bù. Giờ địa phương lặp lại khi đổi quy tắc giờ chỉ reo một lần theo khóa đó.
Báo lại dùng bộ đếm đơn điệu, tức bộ đếm đo thời gian trôi qua độc lập với việc chỉnh giờ lịch. Trong bản đề xuất, khởi động lại hủy lần báo lại và hiện thông báo, nhưng vẫn giữ lịch gốc. Bản đầu hoãn ngủ sâu trong lúc chờ báo lại. Chuông tự dừng khi đạt thời hạn reo đã cấu hình. Mất mạng khi giờ cục bộ còn hợp lệ không ngăn kiểm lịch; mất toàn bộ nguồn vẫn cần giải pháp giữ giờ riêng.
Wi-Fi và địa danh dài nhập ở trang cài đặt trên điện thoại. Chọn Cài Wi-Fi bằng nút mới mở chế độ này; màn hình hiện cách truy cập và cách thoát. Nút vẫn chỉnh được báo thức, giờ và đèn khi không có điện thoại. Đây là đặc tả thao tác cần triển khai, chưa phải thư viện menu có sẵn.
- Cụm nút Menu, + và −
- Trường đang sửa và xác nhận
- Lịch báo thức đã lưu
- Hủy về bản đã lưu
Cập nhật OTA để thêm chức năng và sửa lỗi ra sao?
OTA, viết đầy đủ là Over-the-Air update, cho phép thay firmware qua kết nối mạng mà không cắm cáp nạp mỗi lần. Espressif mô tả cập nhật ứng dụng vào vùng chưa được chọn khởi động, xác minh ảnh rồi mới đổi lựa chọn cho lần khởi động sau. Cấu hình cần ít nhất hai vùng ứng dụng OTA và vùng dữ liệu OTA.
Bản đề xuất chỉ cập nhật ứng dụng; giữ nguyên bootloader, bảng phân vùng và vùng cấu hình. Khi chuẩn bị bản nạp có dây đầu tiên, phải chọn bố cục flash đủ chứa bản hiện tại lẫn bản tiếp theo. Ghi đúng loại bo, LCD và phiên bản cấu trúc cấu hình vào hồ sơ phát hành. Một tệp dành cho màn khác có thể sai phần cứng dù cùng họ vi điều khiển, nên ứng dụng phải kiểm hồ sơ tương thích trước khi cài.
Một luồng cập nhật minh họa có thể đi qua các bước sau.
- Người dùng mở Cập nhật, xem phiên bản hiện tại, bản mới và ghi chú chức năng hoặc lỗi đã sửa. Kiểm tra phiên bản chỉ hiện thông báo; cài đặt cần xác nhận riêng.
- Chỉ cho cài khi dùng USB ổn định, không sửa cấu hình, không đang reo hoặc báo lại, và còn đủ khoảng trống trước lịch chuông kế tiếp. Nếu chưa đạt, hiện lý do hoãn và vẫn giữ mặt giờ.
- Tải từ địa chỉ phát hành đã cấu hình qua HTTPS có xác minh máy chủ. Espressif cũng hỗ trợ kiểm chữ ký ảnh firmware khi được cấu hình; bản sản phẩm đề xuất yêu cầu ảnh ký bằng khóa phát hành tin cậy. Phần khóa, chứng chỉ và kiểm chữ ký phải được triển khai trước khi mở tính năng.
- Kiểm bo đích, hồ sơ phần cứng, kích thước và tính hợp lệ của ảnh trước khi chấp nhận. Tải dở, lỗi xác minh hoặc sai hồ sơ phải báo lỗi; giữ bản hiện tại và cho thử lại, thay vì khởi động bản chưa đủ điều kiện.
- Khi ảnh đã được chấp nhận, kiểm lại điều kiện nguồn và báo thức trước khi khởi động lại. Sau khởi động, kiểm đọc cấu hình, vẽ màn, xử lý nút và khởi tạo báo thức; chỉ sau đó mới xác nhận bản mới hoạt động.
Rollback là quay về ứng dụng trước nếu bản mới không đạt kiểm tra. Espressif yêu cầu bật CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE cho cơ chế này; ứng dụng gọi esp_ota_mark_app_valid_cancel_rollback() khi kiểm tra đạt. Không suy ra Arduino core đang dùng đã bật rollback chỉ vì có chức năng tải OTA. Cần dựng và kiểm bootloader phù hợp, hoặc đánh dấu chưa hỗ trợ phục hồi tự động. Giữ cổng nạp có dây để phục hồi khi cơ chế OTA không dùng được.
Kiểm khởi động chỉ kiểm chức năng cục bộ, không đòi Internet hoặc thời tiết mới. Nếu cấu trúc cấu hình thay đổi, bản đề xuất đọc bản cũ và tạo bản chuyển đổi riêng; chỉ xác nhận sau khi kiểm tra đạt. Bản firmware trước vẫn phải đọc được cấu hình còn giữ lại khi rollback. Phục hồi ứng dụng cần được thử cùng dữ liệu, thay vì mặc định lưu Preferences sẽ giải quyết mọi thay đổi.
OTA có khoảng gián đoạn khi khởi động lại. Bản mẫu phải công bố giới hạn này và hoãn cập nhật quanh giờ báo thức theo cửa sổ đã cấu hình. Nếu cần bảo đảm chuông trong mọi lần mất nguồn hay cập nhật, phải thiết kế cơ chế báo thức độc lập; bài này chưa đưa ra bảo đảm đó.
- Tải từ nơi phát hành đã xác minh
- Vùng nhận bản ứng dụng mới
- Bản ứng dụng trước để phục hồi
- Kiểm tra trước khi chấp nhận
Ví dụ trong đời sống
Bắt đầu bằng mặt giờ dùng dữ liệu giả. Thêm giờ mạng, thử rút Wi-Fi và xác nhận mặt giờ vẫn cập nhật. Tiếp theo thêm phản hồi thời tiết lưu sẵn để kiểm dữ liệu thiếu, rồi thay nguồn giả bằng HTTP thật. Cuối cùng lưu địa danh và khởi động lại để kiểm khôi phục.
Nếu nút bật đèn chỉ phản hồi sau khi HTTP hết thời hạn, đã tìm được chỗ chặn. Rút ngắn giới hạn chờ hoặc chuyển công việc mạng khỏi đường xử lý nút, rồi thử lại cùng tình huống. Đây là trình tự phát triển đề xuất, chưa phải firmware đã nạp trên phần cứng.
Tiếp tục ví dụ với một lịch báo thức đã lưu. Tắt Wi-Fi trước giờ hẹn, dùng + để báo lại và Menu để dừng lần reo tiếp theo. Sau đó thử bản OTA có lỗi đọc cấu hình trong môi trường kiểm thử: bản mới phải không được xác nhận; kiểm bản trước cùng lịch đã lưu sau phục hồi. Đây là các ca thử đề xuất, chưa phải kết quả trên bo.
Hiểu lầm thường gặp
Một tệp duy nhất luôn dễ phát triển nhất. Chia mã theo trách nhiệm giúp khoanh vùng phần cần sửa khi đổi màn hoặc API, như trong ví dụ trên.
Lưu mọi giá trị là sẽ khôi phục được giờ. Cấu hình đã lưu và đồng hồ tiếp tục chạy là hai cơ chế khác nhau.
Câu hỏi thường gặp
Có cần viết trình điều khiển màn từ đầu không?
Ưu tiên module có thư viện hỗ trợ đúng cấu hình. Chỉ tự viết driver khi yêu cầu phần cứng thực tế bắt buộc.
Có phải cắm cáp để thêm chức năng mới không?
Sau bản nạp có dây đầu tiên đã chuẩn bị OTA, người dùng có thể xác nhận cập nhật qua menu. Vẫn cần cáp khi phục hồi lỗi ngoài khả năng OTA hoặc đổi bố cục flash.
Mã giả có nạp trực tiếp lên bo được không?
Chưa. Nó xác định cấu trúc công việc; sketch thực tế cần mã bo, driver, kết nối và chứng chỉ phù hợp.
Thuật ngữ trong bài
Tên tiếng Việt, tên tiếng Anh và nghĩa dùng trong bài. Di chuột, chạm hoặc dùng phím Tab tới từ có gạch chân chấm để xem giải thích ngay khi đọc.
- Preferences
- Thư viện lưu cấu hình nhỏ qua khởi động lại.
- setup()
- Hàm chuẩn bị chạy một lần lúc bắt đầu sketch.
- HTTPS
- HTTP dùng kết nối TLS; xác minh máy chủ cần cấu hình chứng chỉ phù hợp.
- OTAOver-the-Air update
- Cập nhật firmware qua kết nối mạng thay cho cáp nạp ở mỗi lần phát hành.
- RollbackApplication rollback
- Cơ chế quay về ứng dụng trước khi bản mới không được xác nhận hoạt động, nếu đã bật và còn bản hợp lệ.
- Chống dội nútDebounce
- Lọc các chuyển trạng thái do tiếp điểm cơ học để nhận thao tác nhấn ổn định.
Bài này chưa được đánh dấu là đã xem
Thấy nội dung sai hoặc chưa an toàn?
Báo cho quản trị viên qua email. Thư có sẵn tên bài và đường dẫn.
Có thể bạn cũng quan tâm
Trung cấpĐồng bộ giờ và thời tiết theo vị trí cho đồng hồ
Lấy giờ mạng, chọn địa danh, lưu tọa độ và xử lý thời tiết cũ; phân biệt mất Wi-Fi với mất giờ và nhiệt độ ngoài trời với trong phòng.
Internet vạn vật (IoT)7 phút đọc
Trung cấpChọn LCD và chữ lớn cho đồng hồ để bàn
So sánh kiểu LCD, đèn nền và kích thước chữ để thiết kế đồng hồ gọn hoặc màn lớn dễ đọc từ ghế, giường.
Internet vạn vật (IoT)7 phút đọc
Trung cấpĐồng hồ để bàn thông minh cần những khối nào?
Thiết kế đồng hồ LCD có Wi-Fi, thời tiết và pin sạc: vai trò từng khối, chọn phần cứng và giữ firmware dễ phát triển.
Internet vạn vật (IoT)7 phút đọc
Trung cấpMáy tính đồng hành nối với bộ điều khiển bay như thế nào?
Theo dõi kết nối nối tiếp hoặc Ethernet, giao thức hai chiều và tín hiệu cho biết lệnh hay kết nối đã mất.
Internet vạn vật (IoT)6 phút đọc
Nguồn
Các nguồn này viết cho người đọc lớn hơn. Nếu thấy khó, hãy đọc cùng người lớn.
- setup() — Arduino language reference(mở trong thẻ mới)
ArduinoBậc C: nguồn khácĐọc ngày 1 tháng 11, 2026
- Preferences API(mở trong thẻ mới)
EspressifBậc C: nguồn khácĐọc ngày 1 tháng 11, 2026
- Wi-Fi API(mở trong thẻ mới)
EspressifBậc C: nguồn khácĐọc ngày 1 tháng 11, 2026
- SimpleTime example(mở trong thẻ mới)
EspressifBậc C: nguồn khácĐọc ngày 1 tháng 11, 2026
- ESP HTTP Client — HTTPS server verification(mở trong thẻ mới)
EspressifBậc C: nguồn khácĐọc ngày 1 tháng 11, 2026
- Over The Air Updates (OTA) — app update and rollback(mở trong thẻ mới)
EspressifBậc C: nguồn khácĐọc ngày 1 tháng 11, 2026
- ESP HTTPS OTA — server verification and signed images(mở trong thẻ mới)
EspressifBậc C: nguồn khácĐọc ngày 1 tháng 11, 2026
- Debounce on a Pushbutton(mở trong thẻ mới)
ArduinoBậc C: nguồn khácĐọc ngày 1 tháng 11, 2026
