[Giới thiệu]
Công nghệ vi điều khiển là công nghệ chủ đạo không thể thiếu trong tự động hóa công nghiệp hiện đại, điện tử, kỹ thuật điện và Internet vạn vật (IoT). Khi cuộc sống của chúng ta ngày càng thông minh hơn, công nghệ vi điều khiển đã thâm nhập vào hầu hết mọi khía cạnh của cuộc sống hàng ngày của chúng ta, chẳng hạn như trong nồi cơm điện thông minh, loa thông minh, v.v.
Với ý nghĩ này, loạt bài viết "Học lại 51 bộ vi điều khiển" nhằm mục đích giúp những người mới bắt đầu làm quen với công nghệ vi điều khiển. Chúng ta sẽ bắt đầu với tác vụ đơn giản nhất là-bật một đèn LED-và dần dần tiến tới triển khai các mô-đun như nút điều khiển, màn hình LCD1602, cảm biến nhiệt độ DS18B20 và DS1302 cũng như giao tiếp giữa hai bộ vi điều khiển. Chúng tôi cũng sẽ đề cập đến các giao thức truyền thông phần cứng như UART, I²C và SPI. Bằng cách kết hợp các khái niệm này với kỹ thuật lập trình C, chúng tôi sẽ sử dụng các dự án kỹ thuật thực tế{10}}để minh họa các phương pháp lập trình, cho phép bạn áp dụng linh hoạt các con trỏ và cấu trúc C để đạt được lập trình mô-đun.
Bây giờ, chúng ta hãy quay lại chủ đề chính: sử dụng bộ vi điều khiển 51 để điều khiển đèn LED và tạo hiệu ứng đèn thở.
[Đèn thở hoạt động như thế nào]
Trước tiên chúng ta hãy xem hiệu ứng ánh sáng thở hoạt động như thế nào.
Đèn thở dần dần sáng lên rồi mờ dần, lặp lại chu kỳ này theo cách giống như hơi thở. Tuy nhiên, vì các chân của bộ vi điều khiển chỉ có thể xuất ra 1 (bật) hoặc 0 (tắt), làm thế nào có thể đạt được hiệu ứng chuyển tiếp dần dần?
Điều này là do sự tồn tại lâu dài của thị giác trong mắt chúng ta. Khi chúng ta nhìn vào một vật nào đó, hình ảnh do mắt chúng ta tạo ra tồn tại trong 0,04 giây (con số này được tìm thấy trên mạng).
Nếu chúng ta tính toán dựa trên 0,04 giây thì tương đương với 40 mili giây. Do đó, khi đèn LED bật và tắt trong 20 ms mỗi đèn, mắt người sẽ thấy nó như thể nó sáng liên tục.

Hiệu ứng của việc đèn LED nhấp nháy trong 20 mili giây và tắt trong 20 mili giây có giống như việc đèn LED luôn sáng không?
Haha, chắc chắn là khác. Khi ánh sáng luân phiên bật và tắt sau mỗi 20 mili giây, hiệu ứng mà chúng ta nhìn thấy sẽ mờ hơn so với khi nó sáng liên tục. Nếu chúng ta giả sử rằng độ sáng của đèn chiếu sáng liên tục là 100% thì độ sáng của đèn bật và tắt cứ sau 20 mili giây là 50%. Dựa vào đó mà chúng ta có thể điều chỉnh độ sáng của đèn LED.

Lúc này, chúng ta có thể điều chỉnh độ sáng của đèn LED (bằng cách đặt thời lượng ở mức cao trong chu kỳ 40ms). Đây là nguyên tắc đằng sau phương pháp điều khiển độ sáng -PWM (Điều chế độ rộng xung) nổi tiếng và việc đặt thời lượng của mức cao tương đương với việc điều chỉnh chu kỳ nhiệm vụ (tức là thời lượng của mức cao chia cho tổng chu kỳ: 20/40=50%).
Ở đây, yếu tố quan trọng nhất là chu kỳ nhiệm vụ này. Ví dụ: nếu khoảng thời gian là 20 mili giây, với đèn LED bật xen kẽ giữa 10 mili giây và tắt 10 mili giây thì độ sáng cảm nhận được vẫn là 50% (tức là chu kỳ hoạt động là 10/20=50%).
Tiếp theo, hãy xem cách thực hiện điều này trong chương trình.
[Thực hiện chương trình]
Bật đèn LED
Đầu tiên, hãy bắt đầu bằng cách bật đèn LED, sau đó chúng ta sẽ dần dần thực hiện hiệu ứng đèn thở. Phần cứng chúng tôi sẽ sử dụng như sau:
| Ban phát triển | Ban phát triển đào tạo vi điều khiển ZeroOne |
|---|---|
| Mô hình vi điều khiển | STC89C52 |
| Giao diện LED | Chân P4^4 |

Từ sơ đồ, chúng ta có thể thấy rằng đèn LED được kết nối với chân P4^4 của vi điều khiển. Khi bộ vi điều khiển xuất ra số 1, đèn LED sẽ bật; khi nó xuất ra 0, đèn LED sẽ tắt. Vì vậy, chương trình bật đèn LED khá đơn giản như hình dưới đây:

Chương trình bật đèn LED khá đơn giản; Tôi chắc chắn mọi người đều biết cách thực hiện.
Điều chỉnh độ sáng LED
Tiếp theo, chúng ta sẽ triển khai một chức năng cho phép chúng ta điều chỉnh độ sáng (tức là điều chỉnh chu kỳ nhiệm vụ), như sau:

Xác định biến tĩnh `duty_cycle` để lưu trữ chu kỳ nhiệm vụ. Khi `flag` bằng 1, chu kỳ nhiệm vụ tăng dần lên 255, sau đó đặt `flag` thành 0 và `duty_cycle` giảm dần từ 255 xuống 0. Lặp lại chu trình này.
Haha, tại thời điểm này, bạn có thể nghĩ đèn thở đã hoạt động nhưng thực tế không phải vậy. Nếu bạn không tin tôi, hãy tự mình thử đoạn mã trên.
Vậy chính xác thì vấn đề nằm ở đâu?
Vấn đề nằm ở lệnh gọi trực tiếp của chúng ta tới hàm cài đặt độ sáng-`set_led_luminance()`. Chức năng này mất 40 ms để hoàn thành một chu kỳ, nghĩa là chu kỳ nhiệm vụ không thể thay đổi trong 40 ms đó; nếu không, việc điều chỉnh độ sáng sẽ không hoạt động. Chúng ta hãy xem xét lại hàm `breath_led`. Sau mỗi lệnh gọi tới `set_led_luminance()` để đặt chu kỳ nhiệm vụ, nó sẽ ngay lập tức thay đổi giá trị `duty_cycle` mà không cần đợi 40 mili giây.
Tại thời điểm này, chúng ta cần thêm bộ hẹn giờ phần mềm để cập nhật giá trị `duty_cycle` sau khi hết 40 mili giây. Chương trình được sửa đổi như sau:

Lưu ý: Thời lượng hẹn giờ chỉ cần lớn hơn 40 ms (nghĩa là giá trị của `s_breathCounter` phải lớn hơn 255), nhưng tốt nhất nên đặt nó thành bội số của chu kỳ. Ví dụ: nếu chu kỳ của chúng tôi là 255 (tức là 256 giá trị từ 0 đến 255), chúng tôi có thể đặt chu kỳ thành gấp đôi giá trị đó: 256 * 2 - 1=511 (tức là 512 giá trị từ 0 đến 511).
Thế là xong rồi-đèn thở của chúng ta đã hoàn tất! Điều đó không đơn giản sao? (* ̄︶ ̄)
Tiếp theo là phần thưởng của ngày hôm nay.
Mặc dù chúng tôi đã đạt được hiệu ứng ánh sáng thở nhưng mã vẫn chưa đủ ngắn gọn hoặc tinh tế-mà nó sử dụng nhiều câu lệnh if và else. Hãy xem liệu chúng ta có thể đơn giản hóa nó hơn nữa không.
Trước tiên, hãy đơn giản hóa phần này của hàm set_led_luminance(), như minh họa trong hình bên dưới.

Trước khi đơn giản hóa điều này, chúng ta hãy tìm hiểu một mẹo nhanh về C: phép toán AND theo bit.

Từ đó, chúng ta biết rằng dù là 1 hay 0, việc thực hiện phép toán AND theo bit với 0 sẽ dẫn đến 0.
Cho dù đó là 1 hay 0, việc thực hiện phép toán AND theo bit với 1 sẽ dẫn đến giá trị ban đầu.
Để thuận tiện, chúng tôi sử dụng ký hiệu thập lục phân (có tiền tố là "0x"); ví dụ: 0xff tương ứng với 255 ở dạng thập phân. Vì thế,
Khi một số nhỏ hơn hoặc bằng 0xff được AND với 0xff, kết quả sẽ là chính số đó, như hiển thị bên dưới

Điều gì xảy ra nếu bạn thực hiện thao tác AND theo bit giữa một số lớn hơn 0xff và 0xff?

Kết quả là số dư khi chia số này cho (0xff + 1) (tức là kết quả vẫn nằm trong khoảng từ 0 đến 0xff).
Với phép toán AND theo bit này, đoạn mã trên có thể được đơn giản hóa thành

Bằng cách này, giá trị của s_Counter sẽ luôn nằm trong phạm vi từ 0x00 đến 0xff.
Tương tự, bộ đếm thời gian phần mềm trong hàm Breath_led ở trên cũng có thể được đơn giản hóa như sau:

Ở dòng 3, 0x1ff là 511 ở dạng thập phân. Điều kiện đúng khi giá trị của s_breathCounter là (0x1ff+1) hoặc 512, vì 512 & 0x1ff=0. Dấu chấm than trước nó biểu thị thao tác KHÔNG theo bit (chính xác là điều kiện được đáp ứng khi giá trị của `s_breathCounter` là bội số của 512; điều này giúp loại bỏ nhu cầu đặt lại `s_breathCounter`. Tôi hy vọng lời giải thích này là hợp lý rõ ràng-xin hãy suy nghĩ kỹ). Nếu điều kiện được đáp ứng, chu kỳ nhiệm vụ bắt đầu tăng hoặc giảm.
Nhưng không phải chu kỳ nhiệm vụ nằm trong khoảng từ 0 đến 255 sao? Tại sao dòng 5 cũng trở thành 0x1ff (511)? Đừng lo lắng-hãy nhìn vào dòng 8. Chúng tôi lại trừ 0xff nên phạm vi chu kỳ nhiệm vụ vẫn từ 0 đến 255.
Dòng 7–10 có nghĩa là: Khi `duty_cycle > (0xff)`, tức là 256–511, trừ 0xff tương đương với việc tăng từ 1 lên 255, do đó độ sáng tăng dần.
Khi nhiệm vụ_cycle<= 0xff, the duty_cycle increases from 0 to 255, while the set brightness is 255 - duty_cycle. This effectively decreases the brightness from 255 to 0, causing the light to gradually dim. This achieves the breathing light effect.
Haha, bạn có nghĩ rằng sự đơn giản hóa của chúng ta đã kết thúc?
Không, không, không
Trên thực tế, các dòng từ 7 đến 10 có thể được đơn giản hóa hơn nữa. Đây là lúc hàm giá trị tuyệt đối phát huy tác dụng.
Cái gì? Tại sao phải sử dụng hàm giá trị tuyệt đối?
Hãy xem dòng 10: 0xff - Duty_cycle tương đương với Duty_cycle - 0xff rồi lấy giá trị tuyệt đối. Được rồi, đây là mã đơn giản:
Hàm macro để lấy giá trị tuyệt đối như sau
Cuối cùng, tôi đã bao gồm toàn bộ mã đơn giản bên dưới

Bạn nghĩ gì? Nó không đơn giản sao? (* ̄︶ ̄)




