Đại Huỳnh gửi vào
- 31722 lượt xem
Giới thiệu
Chào các bạn! Đến hẹn lại lên, chủ nhật này tôi xin giới thiệu cho các bạn cách đọc cảm biến ánh sáng TEPT 5700 và cảm biến nhiệt LM35DZ để hiển thị lên LCD 1602 (vì chủ nhật này tôi có việc bận nên tiện tay đăng luôn). Ngoài ra tôi sẽ dùng chỉ số đọc được từ cảm biến ánh sáng TEPT 5700 để điều khiển contrast (độ tương phản (đậm) của chữ), backlight (đèn) của LCD 1602. Do thời gian tìm hiểu chỉ hơn 1 tuần nên nhiều kiến thức về điện - điện tử của tôi sẽ không được tốt, chỉ là kiến thức của dân Newbie thôi :)
Bài viết này của tôi sử dụng rất nhiều code của lập trình hướng đối tượng và con trỏ. Đồng thời bài viết này tái sử dụng lại code và phát triển thêm từ lớp Timer của bài viết trước millis() - Tạo 1 đồng hồ theo thời gian thực và Lịch làm việc cho các Pin
Có nhiều bạn suy nghĩ tại sao tôi phải viết thêm thư viện làm chi cho dài dòng và cực thân. Tôi xin trả lời là tôi muốn code của tôi bên trong file .ino phải ngắn, sạch sẽ, gọn gàng và có thể tùy biến cũng như kế thừa ở những project tôi sẽ làm sau này. Nếu bạn muốn làm 1 project hơi quá với sức lập trình của bạn, bạn có thể tham khảo hoặc sử dụng thư viện viết sẵn của tôi để mau chóng hiện thực hóa vấn đề. https://www.dropbox.com/s/vaidtt886o8t0kk/Timer%26ActionLibrary.zip?dl=0 (mirror)
Phần cứng
- Arduino Uno
- Breadboard
- Dây cắm breadboard
- LCD 1602
- Cảm biến ánh sáng TEPT5700
- Cảm biến nhiệt LM35DZ
- Điện trở 330 Ohm x 1 cái
- Điện trở 10k Ohm x 1 cái
- Điện trở 100k Ohm x 2 cái
Phần mềm
- Ở phiên bản này tôi dùng code lập trình hướng đối tượng để điều khiển logic chương trình 1 cách rõ ràng, ngắn gọn, dễ hiểu. Các bạn vui lòng xem lại bài viết trước của tôi về Timer. Nếu các bạn nóng lòng muốn vọc cũng không sao, cứ tải Thư viện mẫu của tôi xuống cho tiện.
- Đầu tiên hãy nhìn vào file Actions.h
- TimeAction: hành động dựa trên thời gian.
- Tham số đầu vào là: thời gian sẽ kích hoạt hành động, con trỏ hàm mô tả hành động sẽ làm khi được kích hoạt ( tham số đầu vào của con trỏ hàm là float tôi sẽ giải thích bên dưới).
- Hàm update() là 1 hàm virtual: tăng thời gian trôi qua lên 1 khoảng delta dựa trên Timer. Hàm này sẽ được diễn đạt cụ thể ở các lớp con kế thừa.
- Hàm reset() là hàm virtual sẽ reset biến _ellapsedTime trở về 0 và _isDone trở về false ( bạn sẽ chưa thấy nhiều sự hiện hữu của hàm này trong code nhưng biết đâu ở những project sau sẽ dùng tới nhiều thì sao ).
- OneTimeAction: kế thừa TimeAction. Lớp này diễn tả hành động sẽ chạy 1 lần duy nhất.
- Hàm update(): nếu thời gian trôi qua lớn hơn hoặc bằng thời gian kích hoạt thì sẽ kích hoạt hành động và đánh dấu hành động đã hoàn thành.
- MultiTimeAction: kế thừa TimeAction. Tương tự OneTimeAction nhưng sẽ chạy n lần tương ứng với tham số đầu vào truyền vào.
- ProgressAction: kế thừa TimeAction. Lớp này diễn tả hành động sẽ chạy trong khoảng thời gian cho trước. Biến float trong con trỏ hàm sẽ trả về phần trăm hoàn thành tiến trình ( 0 -> 1 )
- RepeatForeverAction: kế thừa TimeAction. Lớp này diển tả hành động sẽ chạy lặp đi lặp lại mãi mãi theo thời gian ( thay thế cho WorkScheduler của bài trước nhé ).
- SequenceAction: kế thừa TimeAction. Lớp này diễn tả các hành động con của lớp này sẽ được thực hiện tuần tự nhau. Ví dụ: có các hành động A, B, C thì sẽ hoạt động như sau: A -> B -> C
- ParallelAction: kế thừa TimeAction. Lớp này diễn tả các hành động con sẽ được chạy song song đồng thời cùng nhau.
- TimeAction: hành động dựa trên thời gian.
- Kế tiếp hãy nhìn 1 tí xiu vào file Array.h
- Tôi dùng LinkedList như 1 dạng Array trong Arduino. Ở đây tôi tạo 1 lớp gọi là LinkedNode. Các bạn có thể tham khảo hình cho dễ hiểu nhé
- Tôi dùng kỹ thuật gọi là tạo template class. T ở đây là 1 kiểu dữ liệu thuộc 1 lớp gì đó tôi vẫn chưa biết.
- Tôi sẽ viết thêm 1 lớp Array ở đâu nhưng vì chưa kịp thời gian tôi sẽ tạm thời không nói gì về lớp này cả và nó cũng không có trong file .zip đâu
Logic chương trình
- Bắt đầu hiển thị màn hình chào.
- Đọc thông số từ LM35DZ để hiển thị lên LCD.
- Đọc thông số từ TEPT5700 để hiển thị lên LCD.
- Dùng thông số từ TEPT5700 điều khiển backlight, contrast của LCD.
Cảm biến nhiệt LM35DZ
- Theo như tôi nghiên cứu thì cảm biến nhiệt LM35DZ sẽ sản xuất ra V trong khoảng 0 -> 1. Mà ta có 1 độ = 10mV vậy nhiệt độ mà LM35DZ có thể nhận biết nằm trong khoảng 0-100 độ C.
- Tôi sẽ dùng công thức tính nhiệt độ của các bài tham khảo khác nhưng sẽ dùng cách khác để dễ hiểu hơn: Temperature = (5.0f * analogRead(tempPin) * 100.0f / 1024.0f)
- Tôi biết việc đọc sẽ chính xác hơn nếu dùng hiệu điện thế 1.1V nhưng vì tôi dùng thêm 1 cổng analog cho việc đọc cảm biến ánh sáng nên tôi chưa biết cách chuyển đổi qua lại cho hợp lý giữa 1.1V và 5V.
- Công thức tính của tôi như sau: float temperature = mapFloat(analogRead(tempPin) * 5.0f / 1024.0f, 0.0f, 1.0f, 0.0f, 100.0f);
- Các bạn có thể hiểu như sau: tôi lấy số điện thế đọc được từ tempPin tính ra tỉ lệ trên thang 1024. Dựa trên tỉ lệ đó tôi sẽ map giá trị đó từ khoảng hoạt động của LM35DZ từ 0-> +1V với khoảng nhiệt độ có thể nhận biết được là từ 0->100 độ C.
- Các bạn nên tham khảo thêm về các bài viết về LM35 khác để hiểu thêm nhé. Tôi chỉ nói nhanh qua phần này thôi.
Cảm biến ánh sáng TEPT5700
- Cảm biến TEPT5700 2 chân: 1 dài, 1 ngắn như đèn led nên cách cắm cũng 1 như vậy và TEPT5700 sẽ trả lấy kết quả ở dạng analog.
- Theo như tôi google ngâm cứu thì cảm biến ánh sáng này chỉ hoạt động nhạy ở khi dùng điện trở 200k Ohm cắm ngay chân GND. Ở bài này tôi dùng 2 cái 100k Ohm vì ... tôi làm biếng đi mua vì không tiện đường :(
- Giá trị trả về nằm trong khoảng 0-1024 tương ứng với không có ánh sáng và ánh sáng mạnh.
LCD 1602 (HD44780)
- Tôi dùng loại màn hình này vì đã có thư viện quản lý viết sẵn trong Arduino, thông dụng, dễ mua, dễ tìm.
- Tìm hiểu cách lắp ở trang chủ Arduino thì tôi cảm nhận là cực kì khó nhìn vì đơn giản là trong mạch hướng dẫn không có cắm nguồn cho đèn backlight nên màn hình tối mịt.
- Tôi dùng điện trở 10k Ohm cho dòng vào backlight ( cổng 15 ) nếu bạn không bỏ điện trở vào thì dòng điện rất mạnh và đèn rất sáng. Các bản có thể thêm biến trở hoặc điện trở khác để điều chỉnh độ sáng max của lcd.
- Tôi dùng điện trở 1k Ohm cho dòng vào contrast ( cổng 2 ) thay vì 1 cái biến trở chỉnh tay vì tôi sẽ điều khiển bằng chương trình.
- Tôi dùng cổng 11 cho RS ( Register Select ), 12 cho E ( Enable ).
- Tôi dùng cổng 2->6 tương ứng với các chân D4->D7.
- Các chân còn lại không cần quan tâm vì tôi chưa đủ trình độ để quan tâm nó làm gì :D.
- Sau đây là sơ đồ chân của LCD 1602 cho các bạn nhìn cho dễ tưởng tượng:
Sơ đồ mạch điện
Sơ đồ mạch này tôi cắm thiếu 1 điện trở 100k Ohm ở cảm biến ánh sáng.
Các bạn nhớ thêm vào, nếu không thêm cũng không ảnh hưởng nhiều.
Source code
#include "Actions.h" #include "LiquidCrystal.h" LiquidCrystal lcd(11, 12, 2, 3, 4, 5); // Pin điều khiển độ tương phản của LCD int contrastPin = 9; // Pin điều khiển độ sáng đèn nền của LCD int backlightPin = 10; // Giá trị nhận được từ cảm biến ánh sáng float lightSensitivity = 0.0f; // Giá trị nhận được từ cảm biến nhiệt float temperature = 0.0f; // Hành động của chương trình khi chạy SequenceAction *program; // Sự kiện đọc nhiệt độ void OnThermo_Read(float delta) { temperature = mapFloat(analogRead(A0) * 5.0f / 1024.0f, 0.0f , 1.0f, 0.0f, 100.0f); } // Sự kiện đọc cường độ ánh sáng void OnLightSensor_Read(float delta) { lightSensitivity = mapFloat(analogRead(A2), 0.0f, 1024.0f, 0.0f, 255.0f); analogWrite(backlightPin, 255.0f - lightSensitivity); } // Sự kiện LCD bắt đầu tiến trình sáng đèn void OnLCD_LightUpProgress(float p) { analogWrite(backlightPin, 255.0f * p); } // Sự kiện LCD bắt đầu tiếnh trình điều chỉnh độ tương phản void OnLCD_AutoCorrectContrast(float p) { analogWrite(contrastPin, 128.0f * p); } // Sự kiện LCD bắt đầu hiển thị void OnLCD_BeginDisplay(float dt) { lcd.setCursor(0, 0); lcd.print("Hello! Dai Huynh"); } // Sự kiện LCD hiển thị void OnLCD_Display(float dt) { // Clear screen before render lcd.clear(); // Print light value lcd.setCursor(0, 0); if (lightSensitivity <= 50) { lcd.print("Dark"); } else if (lightSensitivity < 100){ lcd.print("Low"); } else if (lightSensitivity < 150) { lcd.print("Normal"); } else if (lightSensitivity < 200) { lcd.print("Shining"); } else { lcd.print("Flash"); } // Print thermo value lcd.setCursor(0, 1); lcd.print(temperature); } void setup() { Timer::getInstance()->initialize(); pinMode(contrastPin, OUTPUT); pinMode(backlightPin, OUTPUT); // Khai báo các hành động khởi động của chương trình // Đầu tiền là hiện đèn lên từ từ ProgressAction *fadeIn = new ProgressAction(2000UL, OnLCD_LightUpProgress); // Sau đó sẽ ghi câu chào ( chạy 1 lần duy nhất ) OneTimeAction *beginDisplay = new OneTimeAction(500UL, OnLCD_BeginDisplay); // Sau đó là điều chỉnh độ tương phản. Ở đây tôi fix cứng là 128 vì độ tương phản này dễ nhìn nhất // Độ tương phản quá cao hay quá thấp sẽ dẫn đến việc không thấy gì cả ProgressAction *autoCorrectContrast = new ProgressAction(2000UL, OnLCD_AutoCorrectContrast); // Khai báo các hành động chính của chương trình // Đọc giá trị cảm biến ánh sáng RepeatForeverAction *readLightValue = new RepeatForeverAction(500UL, OnLightSensor_Read); // Đọc giá trị cảm biến nhiệt RepeatForeverAction *readThermoValue = new RepeatForeverAction(500UL, OnThermo_Read); // Hiển thị lên màn hình // Tôi cho thời gian refresh là 1 khung hình 1 giây. Nếu muốn các bạn có thể cho // số khung hình là 10-30. Tham số truyền vào tương ứng = 1000 / số khung hình mong muốn RepeatForeverAction *lcdDisplay = new RepeatForeverAction(1000UL, OnLCD_Display); // Khai báo tiến trình chào của chương trình // Đây là 1 tiến trình làm theo trình tự. SequenceAction *startActions = new SequenceAction(); // Add các hành động đã khai báo vào. startActions->add(fadeIn)->add(beginDisplay)->add(autoCorrectContrast); // Khai báo tiến trình chính của chương trình // Đây là 1 tiến trình chạy song song cùng lúc nhiều chương trình ParallelAction *mainActions = new ParallelAction(); // Add các hành động đã khai báo vào mainActions->add(readLightValue)->add(readThermoValue)->add(lcdDisplay); // Cuối cùng là chương trình chính // Đây là 1 tiến trình làm theo trình tự từ : các tiến trình chào -> hoạt động chính program = new SequenceAction(); program->add(startActions)->add(mainActions); // Xóa màn hình chính và khai báo số dòng và số cột lcd.clear(); lcd.begin(16, 2); } void loop() { Timer::getInstance()->update(); program->update(); Timer::getInstance()->resetTick(); } /* * Hàm map sử dụng tham số kiểu float. * Mặc định thì Arduino có hàm map, nhưng nó chỉ hỗ trợ kiểu số nguyên */ float mapFloat(float value, float in_min, float in_max, float out_min, float out_max) { return (value - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; }
Các bạn xem thêm video để biết thêm chi tiết nhé.
Tổng kết
- Hi vọng bài viết sẽ mang đến cho các bạn thêm nhiều kinh nghiệm trong lập trình cũng như về các sensor đơn giản trong Arduino. Tôi không có nhiều kiến thức về điện tử nên có chỗ nào chưa đúng, chưa rõ các bạn cứ ý kiến hay chia sẻ thêm ở mục comment.
- Trong quá trình code hoặc sử dụng thư viên của tôi nếu lỗi hay muốn cải tiến vui lòng comment luôn
- Niềm vui trong việc tìm tòi và sáng tạo là niềm vui không bao giờ hết. Chúc các bạn 1 ngày cuối tuần vui vẻ.