ARM Mbed OS for Raspberry Pi Pico RP2040: Code Examples (Part 3)#
เนื้อหาในส่วนนี้สาธิตการเขียนโค้ดโดยใช้ Mbed OS และ Arduino IDE สำหรับบอร์ดไมโครคอนโทรลเลอร์ที่ใช้ชิป RP2040 เช่น Raspberry Pi Pico โดยนำเสนอเป็น ตอนที่ 3 ต่อจาก ตอนที่ 1 | ตอนที่ 2
Keywords: Mbed OS, Raspberry Pi Pico, RP2040, Mbed-enabled Platforms, RTOS Programming
- การสื่อสารระหว่าง ISR กับเธรดโดยใช้ Semaphore
- การตรวจสอบการเปลี่ยนตำแหน่งของโมดูล Rotary Encoder
- การอ่านค่าจากโมดูลเซ็นเซอร์ HTU21D
- การอ่านค่าจากโมดูลเซ็นเซอร์ DHT22
▷ การสื่อสารระหว่าง ISR กับเธรดโดยใช้ Semaphore#
โดยทั่วไป RTOS รองรับการสื่อสารระหว่างฟังก์ชันที่ทำหน้าที่เป็น ISR (Interrupt Service Routine หรือเรียกว่า Interrupt Handler) กับเธรดหลักในระบบ ซึ่งก็มีหลายวิธีให้เลือกได้
ตัวอย่างนี้ สาธิตการใช้งานสิ่งที่เรียกว่า "เซมาฟอร์" (Semaphore)
โดยกำหนดให้มีค่า 0 หรือ 1 เท่านั้น (เป็นแบบไบนารี)
และใช้คลาส Semaphore
ของ Mbed OS
เซมาฟอร์สามารถใช้ได้กับการทำงานของ ISR ในกรณีที่ต้องการเพิ่มค่าของเซมาฟอร์ (Semaphore Release) และจะไม่มีการหยุดการทำงานของ ISR (ไม่ถูกบล็อการทำงานและอยู่ในสถานะรอ)
ในทางตรงข้าม ถ้าเธรดต้องการจะใช้หรือลดค่าของเซมาฟอร์ (Semaphore Acquire) แต่มีจำนวนเป็น 0 (ต่ำสุดแล้ว) จะถูกเปลี่ยนสถานะให้เป็นการรอ (Waiting) จนกว่าจะใช้งานเซมาฟอร์ได้ (มีค่ามากกว่า 0)
#include "mbed.h"
using namespace mbed;
using namespace rtos;
#define LED_PIN (p25) // GP25 pin (on-board LED)
#define BUTTON_PIN (p16) // GP16 pin
#define MIN_PULSEWIDTH_MS (20)
DigitalOut led( LED_PIN );
InterruptIn button( BUTTON_PIN, PullUp );
// create a binary semaphore
Semaphore sem(0,1); // count=0, maxcount=1
volatile uint32_t rise_time = 0, fall_time = 0;
uint64_t next_timeout;
void isr_button() { // ISR function
uint32_t now = Kernel::get_ms_count();
if ( button.read() ) { // high
rise_time = now;
if ( rise_time - fall_time > MIN_PULSEWIDTH_MS ) {
sem.release(); // release the semaphore
}
} else { // low
fall_time = now;
}
}
void setup() {
SerialUSB.begin( 115200 );
while(!SerialUSB){} // wait until the Serial port is open.
button.rise( isr_button );
button.fall( isr_button );
next_timeout = Kernel::get_ms_count();
}
std::string sbuf;
uint32_t event_cnt = 0;
void loop() {
// wait until the semaphore becomes avaiable (with timeout)
if ( sem.try_acquire_until(next_timeout) ) {
led = !led; // toggle the LED
event_cnt += 1; // increment the event counter
sbuf = "event count: ";
sbuf += std::to_string( event_cnt );
SerialUSB.println( sbuf.c_str() );
}
next_timeout = Kernel::get_ms_count() + 100; // update timeout
}
▷ การตรวจสอบการเปลี่ยนตำแหน่งของโมดูล Rotary Encoder#
โมดูล Rotary Encoder เป็นอุปกรณ์ที่สร้างสัญญาณดิจิทัลแบบพัลส์ (Pulses) และมี 2 ช่อง (A และ B) โดยปรกติสัญญาณทั้งสองจะมีสถานะลอจิกเป็น High แต่เมื่อใช้มือหมุนในทิศทางทวนหรือตามเข็มนาฬิกา จะเกิดการเปลี่ยนแปลงลอจิกที่ช่องสัญญาณ A และ B ในลักษณะพัลส์ แต่การเปลี่ยนแปลงของแต่ละช่องจะเกิดขึ้นไม่พร้อมกัน จำนวนพัลส์ที่เกิดขึ้นจะขึ้นอยู่กับตำแหน่งเชิงมุมที่เปลี่ยนไป อุปกรณ์ประเภทนี้สามารถนำมาใช้สร้างสัญญาณอินพุตให้ไมโครคอนโทรลเลอร์
รูป: ตัวอย่างโมดูล Rotary Encoder
รูป: ตัวอย่างรูปคลื่นสัญญาณ A และ B จากโมดูล Rotary Encoder เมื่อมีการหมุนเกิดขึ้น
โค้ดตัวอย่างต่อไปนี้ สาธิตการตรวจสอบการเกิดสัญญาณพัลส์ที่ช่องสัญญาณ A และ B และระบุทิศทางการหมุน
ซึ่งจะทำให้ค่าของตัวนับ encoder_count
เพิ่มขึ้นหรือลดลงตามทิศทางการหมุน
หลักการทำงานคือ การตรวจสอบการเปลี่ยนแปลงโดยใช้อินเทอร์รัพท์ภายนอก ทั้งขอบขาขึ้นและขอบขาลง
และใช้คำสั่ง attachInterrupt(...)
ของ Arduino API
เมื่อเกิดอินเทอร์รัพท์ที่ช่องสัญญาณ A ในแต่ละครั้ง ฟังก์ชันที่ทำหน้าที่เป็น ISR (Interrupt Service Routine) สำหรับอินเทอร์รัพท์ดังกล่าว จะทำงานและตรวจสอบดูว่า มีสัญญาณพัลส์แบบ Low ที่ช่อง A เกิดขึ้น และมีความกว้างไม่ต่ำกว่าค่าที่กำหนดไว้หรือไม่ (เช่น 20 มิลลิวินาที)
ถ้าเงื่อนไขดังกล่าวเป็นจริง ให้ถือว่ามีการเปลี่ยนตำแหน่งไปหนึ่งตำแหน่ง สถานะของสัญญาณช่อง B ในขณะนั้น จะถูกใช้ในการระบุทิศทางการเปลี่ยนแปลงที่เกิดขึ้น (เพิ่มขึ้นหรือลดลงทีละ 1)
#define PIN_A (16)
#define PIN_B (17)
#define MIN_PULSE_MS (20)
volatile int32_t encoder_count = 0;
volatile uint32_t last_time = 0;
void isr_pin_a() { // ISR function
uint32_t now = millis();
int a = digitalRead( PIN_A );
int b = digitalRead( PIN_B );
if ( now - last_time >= MIN_PULSE_MS ) {
if (a==0) {
encoder_count += b ? -1 : 1;
}
}
last_time = now;
}
void setup() {
SerialUSB.begin(115200);
while(!SerialUSB){}
attachInterrupt( PIN_A, isr_pin_a, CHANGE );
pinMode( PIN_A, INPUT_PULLUP );
pinMode( PIN_B, INPUT_PULLUP );
}
int saved_count = 0;
void loop() {
if ( saved_count != encoder_count ) {
saved_count = encoder_count;
SerialUSB.println( saved_count );
}
delay(5);
}
ถ้าจะเปลี่ยนมาใช้คลาสของ Mbed Drivers เช่น InterruptIn
และ DigitalIn
สำหรับขาสัญญาณอินพุต A และ B และใช้คำสั่งของ Mbed OS (RTOS) ก็มีตัวอย่างดังนี้
เช่น การใช้คำสั่ง Kernel::get_ms_count(...)
แทนที่คำสั่ง millis(...)
สำหรับการอ่านค่าตัวเลขเวลาของระบบในขณะนั้น (หน่วยเป็นมิลลิวินาที)
#include "mbed.h"
using namespace mbed;
using namespace rtos;
#define PIN_A (p16)
#define PIN_B (p17)
#define MIN_PULSE_MS (20)
InterruptIn pin_a( PIN_A );
DigitalIn pin_b( PIN_B, PullUp );
// create a binary semmaphore
Semaphore sem(0,1); // count=0, maxcount=1
volatile int32_t encoder_count = 0;
volatile uint32_t last_time = 0;
void isr_pin_a() {
uint32_t now = Kernel::get_ms_count();
int a = pin_a.read();
int b = pin_b.read();
if ( now - last_time >= MIN_PULSE_MS ) {
if (a==0) {
encoder_count += b ? -1 : 1;
sem.release(); // release the semaphore
}
}
last_time = now;
}
void setup() {
SerialUSB.begin( 115200 );
while(!SerialUSB){}
pin_a.rise( isr_pin_a );
pin_a.fall( isr_pin_a );
}
int saved_count = 0;
void loop() {
// wait until the semaphore becomes available
sem.acquire();
saved_count = encoder_count;
SerialUSB.println( saved_count );
}
จากโค้ดตัวอย่าง จะเห็นได้ว่า มีการใช้เซมาฟอร์แบบไบนารี (Binary Semaphore)
สำหรับการสื่อสารกันระหว่างฟังก์ชันสำหรับ ISR และการทำงานของเธรดหลักที่ทำคำสั่งต่าง ๆ
ในฟังก์ชัน main() {...}
นอกจากการใช้เซมาฟอร์แล้ว เรายังสามารถใช้คลาส Queue
ของ Mbed OS
แทนได้สำหรับการส่งข้อมูลจาก ISR ไปยังเธรดหลัก ตามตัวอย่างต่อไปนี้ ใ
นตัวอย่างนี้ได้เลือกใช้ชนิดข้อมูลเป็นเลขจำนวนเต็ม (int
) สำหรับนำไปใช้กับ Queue
และอ้างอิงข้อมูลโดยใช้พอยน์เตอร์
ในตัวอย่างนี้ ISR จะนำข้อมูลไปใส่ลงใน Queue
ถ้ายังไม่เต็มความจุ
และเป็นตามเงื่อนไขที่กำหนด
(เมื่อมีการตรวจสอบพบว่า มีการเปลี่ยนตำแหน่งของโมดูล Rotary Encoder)
และเธรดหลักจะคอยอ่านข้อมูลจาก Queue
#include "mbed.h"
using namespace mbed;
using namespace rtos;
#define PIN_A (p16)
#define PIN_B (p17)
#define MIN_PULSE_MS (20)
#define QUEUE_CAPACITY (4)
InterruptIn pin_a( PIN_A );
DigitalIn pin_b( PIN_B, PullUp );
Queue<int, QUEUE_CAPACITY> queue;
volatile int encoder_count = 0;
volatile uint32_t last_time = 0;
void isr_pin_a() {
uint32_t now = Kernel::get_ms_count();
int a = pin_a.read();
int b = pin_b.read();
if ( now - last_time >= MIN_PULSE_MS ) {
if (a==0) {
encoder_count += b ? -1 : 1;
// send data to the queue
queue.try_put( (int *)&encoder_count );
}
}
last_time = now;
}
void setup() {
SerialUSB.begin( 115200 );
while(!SerialUSB){}
// enable interrupt for pin A (both rising and falling edges)
pin_a.rise( isr_pin_a );
pin_a.fall( isr_pin_a );
}
void loop() {
int *data_ptr;
while ( queue.count() > 0 ) { // if queue is not empty
// get data from the queue
if ( queue.try_get( &data_ptr ) ) {
SerialUSB.println( *data_ptr );
}
}
}
รูป: ตัวอย่างการต่อวงจรทดลองบนเบรดบอร์ดสำหรับการใช้งานโมดูล Rotary Encoder
▷ การอ่านค่าจากโมดูลเซ็นเซอร์ HTU21D#
โมดูล [HTU21D] (Datasheet)
มีไอซีเซ็นเซอร์ของบริษัท TE Connectivity สำหรับวัดอุณหภูมิและความชื้นสัมพัทธ์ในอากาศ
ให้ข้อมูลแบบดิจิทัลและสื่อสารข้อมูลด้วยบัส I2C (แอดเดรส 0x40
)
ข้อมูลเชิงเทคนิคสำหรับ HTU21D
- Relative humidity measurement: from 0% to 100%
- Humidity Accuracy: ± 2% RH (20%RH to 80%RH)
- Humidity Resolution: 12 bits (max.)
- Temperature measurement: from -40°C to 125°C
- Temperature Accuracy: ± 0.3°C @25 deg.C
- Temperature Resolution: 14 bits (max.)
ตัวอย่างนี้สาธิตการเขียนโค้ดโดยใช้ Arduino API สำหรับบอร์ด Pico เพื่อให้อุปกรณ์ทำหน้าที่เป็น I2C Master เชื่อมต่อสื่อสารกับโมดูล HTU21D ที่ทำหน้าที่เป็น I2C Slave เลือกใช้ความเร็วของบัส (I2C Bus Speed) เท่ากับ 400kHz
#include "Wire.h"
#define SDA_PIN (p6)
#define SCL_PIN (p7)
#define I2C_ADDR (0x40) // HTU21D device address
#define TRIG_TEMP_MEASURE_NOHOLD (0xF3)
#define TRIG_HUMD_MEASURE_NOHOLD (0xF5)
// Note The MbedI2C class is derived from Arduino's HardwareI2C.
arduino::MbedI2C i2c( SDA_PIN, SCL_PIN ); // the same as Wire
void write_u8( uint8_t addr, uint8_t data ) {
i2c.beginTransmission(addr);
i2c.write(data);
i2c.endTransmission();
}
bool read_bytes( uint8_t addr, uint8_t *buf, uint8_t len ) {
i2c.requestFrom( addr, len );
if ( i2c.available() == len ) {
for ( uint8_t i=0; i < len; i++ ) {
buf[i] = i2c.read();
}
return true;
}
return false;
}
uint8_t check_crc( uint16_t value, uint8_t chsum ) {
const uint32_t SHIFTED_DIVISOR = 0x988000;
uint32_t remainder = (uint32_t)value << 8;
remainder |= chsum;
uint32_t divisor = (uint32_t)SHIFTED_DIVISOR;
for (int i=0 ; i < 16 ; i++) {
if ( remainder & (uint32_t)1<<(23-i) ) {
remainder ^= divisor;
}
divisor >>= 1;
}
return (uint8_t)remainder;
}
bool read_humidity( uint8_t addr, float& humidity ) {
// start the measurement for humidity (with no bus holding)
write_u8( addr, TRIG_HUMD_MEASURE_NOHOLD );
delay( 30 );
uint8_t buf[3];
if ( read_bytes(addr, buf, 3) ) {
uint16_t value = ((uint16_t)buf[0] << 8) | buf[1];
if (check_crc(value,buf[2]) != 0) { // crc error
return false;
}
value &= 0xfffc;
humidity = (0.00190735*value) - 6;
return true;
}
return false;
}
bool read_temperature( uint8_t addr, float& temperature ) {
// start the measurement for temperature (with no bus holding)
write_u8( addr, TRIG_TEMP_MEASURE_NOHOLD );
delay( 85 );
uint8_t buf[3];
if ( read_bytes(addr, buf, 3) ) {
uint16_t value = ((uint16_t)buf[0] << 8) | buf[1];
if (check_crc(value,buf[2]) != 0) { // crc error
return false;
}
value &= 0xfffc;
temperature = (0.00268127*value) - 46.85;
return true;
}
return false;
}
void setup() {
SerialUSB.begin( 115200 );
while(!SerialUSB){} // wait until the Serial port is open.
Serial.println( "HTU21D I2C T&H Sensor Demo..." );
// start the I2C master
i2c.begin();
// set the I2C clock frequency
i2c.setClock( 400000 );
}
char sbuf[32];
String strbuf;
void loop() {
float temp, humid;
strbuf = "Temperature: ";
if ( read_temperature(I2C_ADDR, temp) ) {
sprintf( sbuf, "%.1f", temp );
} else {
sprintf( sbuf, "--.-" );
}
strbuf += sbuf;
strbuf += " deg.C, Relative Humidity: ";
if ( read_humidity(I2C_ADDR, humid) ) {
sprintf( sbuf, "%.2f", humid );
} else {
sprintf( sbuf, "--.--" );
}
strbuf += sbuf;
strbuf += " %RH";
SerialUSB.println( strbuf.c_str() );
delay(1000);
}
ถ้าจะเปลี่ยนมาลองใช้คำสั่งของ Mbed Drivers สำหรับ I2C Master ก็มีตัวอย่างโค้ดดังนี้
#include "mbed.h"
#include "I2C.h"
using namespace mbed;
using namespace rtos;
using namespace std::chrono;
#define SDA_PIN (p6)
#define SCL_PIN (p7)
#define I2C_ADDR (0x40)
#define TRIG_TEMP_MEASURE_NOHOLD (0xF3)
#define TRIG_HUMD_MEASURE_NOHOLD (0xF5)
I2C i2c_bus( SDA_PIN, SCL_PIN ); // sda and scl pins
const uint8_t addr8bit = I2C_ADDR << 1;
void write_u8( uint8_t addr, uint8_t data ) {
i2c_bus.write( addr, (const char*)&data, 1 );
}
bool read_bytes( uint8_t addr, uint8_t *buf, uint8_t len ) {
if ( i2c_bus.read( addr, (char*)buf, 3 ) != 0 ) {
return false;
}
return true;
}
uint8_t check_crc( uint16_t value, uint8_t chsum ) {
const uint32_t SHIFTED_DIVISOR = 0x988000;
uint32_t remainder = (uint32_t)value << 8;
remainder |= chsum;
uint32_t divisor = (uint32_t)SHIFTED_DIVISOR;
for (int i=0 ; i < 16 ; i++) {
if ( remainder & (uint32_t)1<<(23-i) ) {
remainder ^= divisor;
}
divisor >>= 1;
}
return (uint8_t)remainder;
}
bool read_humidity( uint8_t addr, float& humidity ) {
// start the measurement for humidity (with no bus holding)
write_u8( addr, TRIG_HUMD_MEASURE_NOHOLD );
ThisThread::sleep_for( 30ms );
uint8_t buf[3];
if ( read_bytes(addr, buf, 3) ) {
uint16_t value = ((uint16_t)buf[0] << 8) | buf[1];
if (check_crc(value,buf[2]) != 0) { // crc error
return false;
}
value &= 0xfffc;
humidity = (0.00190735*value) - 6;
return true;
}
return false;
}
bool read_temperature( uint8_t addr, float& temperature ) {
// start the measurement for temperature (with no bus holding)
write_u8( addr, TRIG_TEMP_MEASURE_NOHOLD );
ThisThread::sleep_for( 85ms );
uint8_t buf[3];
if ( read_bytes(addr, buf, 3) ) {
uint16_t value = ((uint16_t)buf[0] << 8) | buf[1];
if (check_crc(value,buf[2]) != 0) { // crc error
return false;
}
value &= 0xfffc;
temperature = (0.00268127*value) - 46.85;
return true;
}
return false;
}
void setup() {
SerialUSB.begin( 115200 );
while(!SerialUSB){} // wait until the Serial port is open.
Serial.println( "HTU21D I2C T&H Sensor Demo..." );
// set the I2C clock frequency
i2c_bus.frequency(400000);
}
char sbuf[32];
std::string strbuf;
void loop() {
float temp, humid;
strbuf = "Temperature: ";
if ( read_temperature( addr8bit, temp ) ) {
sprintf( sbuf, "%.1f", temp );
} else {
sprintf( sbuf, "--.-" );
}
strbuf += sbuf;
strbuf += " deg.C, Relative Humidity: ";
if ( read_humidity( addr8bit, humid ) ) {
sprintf( sbuf, "%.2f", humid );
} else {
sprintf( sbuf, "--.--" );
}
strbuf += sbuf;
strbuf += " %RH";
SerialUSB.println( strbuf.c_str() );
ThisThread::sleep_for( 1000ms );
}
รูป: ตัวอย่างการต่อวงจรทดลองสำหรับโมดูล HTU21D และมีโมดูล BH1750 ใช้งานร่วมกัน
รูป: ตัวอย่างข้อความเอาต์พุตที่ได้รับและแสดงค่าของโมดูลเซ็นเซอร์ HTU21D
▷ การอ่านค่าจากโมดูลเซ็นเซอร์ DHT22#
โมดูล AM2302 / DHT22 เป็นตัวอย่างของเซ็นเซอร์สำหรับนำมาใช้เพื่อวัดค่าอุณหภูมิ (Temperature)
และความชื้นสัมพัทธ์ในอากาศ (Relative Humidity: RH)
ใช้ขาสัญญาณดิจิทัลแบบสองทิศทางเพียงเส้นเดียวในการสื่อสารข้อมูลกับไมโครคอนโทรลเลอร์
ขา DATA
จะต้องมีตัวต้านทานแบบ Pull-up ต่ออยู่ด้วย (เช่น 4.7kΩ)
และสามารถใช้แรงดันไฟเลี้ยง VCC = +3.3V ได้
รูป: ตัวอย่างโมดูล DHT22 Breakout Boards
ข้อมูลบิตที่ได้จากโมดูลนี้ในการอ่านค่าแต่ละครั้งจะมีจำนวน 40 บิต หรือ 5 ไบต์ แบ่งเป็น
- 16-bit Relative Humidity
- 16-bit Temperature
- 8-bit Checksum
จากลำดับข้อมูลบิตที่ได้รับจากโมดูล DHT22 ถ้าจะจำแนกว่า บิตใดเป็น 0 หรือ 1 จะต้องตรวจสอบจากความกว้างของสัญญาณพัลส์ที่ถูกส่งออกมาตามลำดับ (ศึกษาเพิ่มเติมได้จากเอกสาร Datasheet)
ลองมาดูตัวอย่างเขียนโค้ด โดยสร้างคลาส (C++ Class)
และมีตัวอย่างการใช้งานดังนี้ โดยใช้กับโมดูล DHT22 จำนวน 2 ชุด
นำขาสัญญาณ DATA
มาต่อเข้าที่ขา p16
และ p17
ของบอร์ด Pico ตามลำดับ
// file: DHT22.h
#ifndef __DHT22__
#define __DHT22__
#include "Wire.h"
class DHT22 {
public:
DHT22( PinName pin ) : data_pin(pin), status(0) {}
~DHT22() {}
bool readSensor( float &temperature, float &humidity );
uint8_t getStatus() { return status; }
protected:
bool read_byte( uint8_t &value );
inline bool is_data_high() {
return (digitalRead(data_pin)==1);
}
inline bool is_data_low() {
return (digitalRead(data_pin)==0);
}
inline void data_high() { digitalWrite(data_pin, 1); }
inline void data_low() { digitalWrite(data_pin, 0); }
inline void data_input() { pinMode(data_pin, INPUT); }
inline void data_output() { pinMode(data_pin, OUTPUT); }
private:
PinName data_pin;
uint8_t status;
};
#endif
// fille: DHT22.cpp
#include "DHT22.h"
bool DHT22::read_byte( uint8_t &value ) {
uint8_t cnt, result = 0x00;
for( int i=0; i < 8; i++ ) {
cnt = 0;
while( is_data_low() ) { // wait until DATA goes HIGH.
delayMicroseconds(2);
if ( ++cnt > 100 ) { // timeout
status |= 0x01; // error: DATA is stuck LOW.
return false;
}
}
delayMicroseconds(30); // wait for 30 usec
result <<= 1;
if ( is_data_high() ) { // if DATA is HIGH, this bit is 1.
result |= 1;
}
cnt = 0;
while( is_data_high() ) { // wait until DATA goes LOW.
delayMicroseconds(2);
if ( ++cnt > 100 ) { // timeout
status |= 0x02; // error: DATA is stuck HIGH.
return false;
}
}
}
value = result; // one data byte
status = 0; // status: ok
return true;
}
bool DHT22::readSensor( float &temperature, float &humidity ) {
uint8_t buf[5];
status = 0;
// DATA must be configured as output.
data_output();
// send a start signal
data_low();
delayMicroseconds(500); // at least 500 msec
data_high();
// release the DATA pin and wait for a response signal
data_input();
delayMicroseconds(30);
if ( is_data_high() ) { // LOW expected
status |= 0x02; // error: DATA is stuck HIGH.
}
delayMicroseconds(80);
if ( is_data_low() ){ // HIGH expected
status |= 0x01; // error: DATA is stuck lOW.
}
delayMicroseconds(80);
if ( status != 0 ) { // if an error has occurred
data_output();
data_high();
return false;
}
status = 0;
// Now the DATA line must be LOW.
uint8_t value, checksum = 0x00;
for ( int i=0; i < 5; i++ ) { // read 5 bytes into the buffer
if ( !read_byte( value ) ) { // error
status |= 0x04;
break;
}
buf[i] = value;
if ( i < 4 ) {
checksum += value;
}
}
data_output();
data_high();
data_input();
if ( buf[4] != checksum ) {
status |= 0x08; // checksum error
return false;
}
// temperature in Celsius (0.1 step)
uint16_t temp = (buf[2] << 8) + buf[3];
if (temp & 0x8000) { // The sign bit is 1.
temp &= 0x7fff;
temp = -temp;
}
temperature = 0.1 * temp;
// rel. humidity in percent (0.1 step)
uint16_t humid = (buf[0] << 8) + buf[1];
humidity = 0.1 * humid;
return (status==0);
}
โค้ดสาธิตการใช้งานคลาส DHT22
// TEST CODE: Single-Thread Version
#include "DHT22.h"
const int NUM_SENSORS = 2;
// create two DHT22 instances
DHT22 *sensors[] = { new DHT22( p16 ), new DHT22( p17 ) };
char sbuf[64]; // used when calling sprintf()
String strbuf;
void setup() {
SerialUSB.begin(115200);
while(!SerialUSB) {}
SerialUSB.println( "DHT22 demo..." );
}
void loop() {
float _temp, _humid;
for ( int id=0; id < NUM_SENSORS; id++ ) { // for each sensor device
sprintf( sbuf, "DHT22 (id=%d): ", id );
strbuf = sbuf;
uint32_t t_start = micros();
bool ok = sensors[id]->readSensor(_temp, _humid);
uint32_t t_end = micros();
if (ok) {
sprintf( sbuf, "T: %.1f deg.C, H: %.2f %%RH",
_temp, _humid );
} else {
sprintf( sbuf, "T: --.- deg.C, H: --.-- %%RH (err=0x%02X)",
sensors[id]->getStatus() );
}
strbuf += sbuf;
strbuf += ", exec (usec): ";
strbuf += (t_end - t_start);
SerialUSB.println( strbuf.c_str() );
delay(1000);
}
}
รูป: ตัวอย่างการสร้างไฟล์ .ino / .h / .cpp สำหรับ Arduino Sketch
รูป: ตัวอย่างการต่อวงจรทดลองสำหรับโมดูล DHT22 จำนวน 2 อุปกรณ์
รูป: ตัวอย่างข้อความเอาต์พุตเมื่ออ่านค่าจากโมดูล DHT22 จำนวน 2 อุปกรณ์
ถ้าจะลองเปลี่ยนมาสร้าง Mbed Thread สำหรับการอ่านค่าจากโมดูล DHT22 แต่ละตัว
โดยเว้นระยะห่างอย่างน้อยประมาณ 2 วินาที สำหรับการอ่านค่าแต่ละครั้ง และใช้ DHT22 ทั้งหมด 5 อุปกรณ์
(เลือกใช้ขา p14
, p15
, p16
, p17
และ p18
ตามลำดับ) ก็มีตัวอย่างดังนี้
///////////////////////////////////////////////////////////
// TEST CODE: Multi-threaded version
///////////////////////////////////////////////////////////
#include "DHT22.h"
#include "mbed.h"
#include "rtos.h"
using namespace mbed;
using namespace rtos;
const PinName DHT22_PINS[] = { p14, p15, p16, p17, p18 };
const int NUM_SENSORS = sizeof(DHT22_PINS)/sizeof(PinName);
DHT22 *sensors[ NUM_SENSORS ];
Thread *threads[ NUM_SENSORS ];
Mutex mutex; // used to protect SerialUSB
void thread_func( void *arg ) {
int id = (int)arg;
DHT22 *sensor = sensors[ id ];
float temp, humid;
while(1) {
// raise the priority level of this Thread
osThreadSetPriority( osThreadGetId(), osPriorityAboveNormal );
uint32_t t_start = micros();
bool ok = sensor->readSensor( temp, humid );
uint32_t t_end = micros();
// lower the priority level of this Thread
osThreadSetPriority( osThreadGetId(), osPriorityNormal );
if (ok) {
char sbuf[80];
mutex.lock();
sprintf( sbuf, "DHT22 (id=%d) T: %.1f deg.C, H: %.2f %%RH, %lu usec",
id, temp, humid, (t_end-t_start) );
SerialUSB.println( sbuf );
SerialUSB.flush();
mutex.unlock();
}
thread_sleep_for( 2000 ); // repeat sensor reading every 2sec
}
}
void setup() {
SerialUSB.begin(115200);
while(!SerialUSB) {}
SerialUSB.println( "DHT22 / Multi-threading demo..." );
SerialUSB.flush();
for ( int i=0; i < NUM_SENSORS; i++ ) {
// create a new DHT22 instance
sensors[i] = new DHT22( DHT22_PINS[i] );
// create a new Thread instance
threads[i] = new Thread( osPriorityNormal, OS_STACK_SIZE );
// start the new Thread
threads[i]->start( callback(thread_func,(void *)i) );
thread_sleep_for( 10 );
}
osDelay( osWaitForever ); // force the main thread to wait forever
}
void loop() {}
รูป: ตัวอย่างการต่อวงจรทดลองสำหรับโมดูล DHT22 จำนวน 5 อุปกรณ์
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
Created: 2021-04-16 | Last Updated: 2021-12-18