Here’s a compact, field-tested way to build a temperature & humidity meter on AT89C52 (8051) with two sensor options:
1) What you’ll build
- MCU: AT89C52 @ 11.0592 MHz (or 12 MHz)
- Sensor (pick one):
- Output: 1602 LCD (parallel) or UART serial to PC
- Power: 5 V (with 3.3 V LDO if your sensor is 3.3 V)
2) Hardware at a glance
Option A — DHT11/DHT22 (single data wire)
- DHTx VCC → 5 V, GND → GND
- DHTx DATA → P3.7 (any GPIO) with 10 kΩ pull-up to 5 V
- Keep data wire ≤20 cm for stability; add 100 nF near sensor
Option B — SHT21/SHT31 (I²C, more accurate)
- SCL → P1.6, SDA → P1.7 (any GPIOs are fine)
- 4.7 kΩ pull-ups from SCL/SDA to 3.3 V (most SHTx are 3.3 V)
- If MCU is 5 V: either use a level shifter or power MCU I/O at 3.3 V-tolerant levels
- Add 100 nF decoupling close to the sensor
LCD 1602 (optional, 5 V parallel)
- RS→P2.0, RW→GND, E→P2.1, D4..D7→P2.4..P2.7, 10 kΩ contrast pot on V0
- Or skip LCD and print via UART (P3.0 RXD / P3.1 TXD @ 9600 bps)
3) Timing & protocol
DHT11/22 (simplified)
- MCU pulls DATA low ≥18 ms, then release (input, pull-up).
- Sensor replies 80 µs low + 80 µs high.
- Sends 40 bits: each bit = 50 µs low, then high for 26–28 µs (0) or ~70 µs (1).
- Frame: Humidity_int | Humidity_dec | Temp_int | Temp_dec | Checksum (sum of previous 4 bytes, LSB 8 bits).
SHT21/SHT31 (I²C)
- Send measure command (e.g., SHT21 Temp: 0xF3, RH: 0xF5; SHT31 single-shot: 0x2400)
- Read 2 data bytes + CRC (optional).
- Convert using datasheet formulas (e.g., SHT21: T(°C)= -46.85 + 175.72 * rawT / 65536 RH(%)= -6 + 125 * rawRH / 65536).
4) Minimal 8051 code (Keil C-style)
A) DHT11 driver (works for DHT22 too—just parse decimals)
#include <REG52.H>
sbit DHT = P3^7;
void delay_us(unsigned int us) { while(us--) { _nop_(); _nop_(); } } // tune per Fclk
void delay_ms(unsigned int ms){ unsigned int i; while(ms--) for(i=0;i<120;i++); } // ~1ms@11.0592MHz
bit dht_wait_level(bit level, unsigned int timeout_us){
while(timeout_us--){
if (DHT == level) return 1;
delay_us(1);
}
return 0; // timeout
}
bit dht_read_byte(unsigned char *b){
unsigned char i, val=0;
for(i=0;i<8;i++){
// wait for 50us low
if(!dht_wait_level(0, 100)) return 0;
// wait for high then measure high width
if(!dht_wait_level(1, 100)) return 0;
// after line goes high, wait ~40us then sample
delay_us(40);
val <<=1;
if(DHT) val |= 1; // high ~70us => '1', still high at 40us
// wait for line to drop before next bit (guard)
if(!dht_wait_level(0, 100)) return 0;
}
*b = val; return 1;
}
bit dht_read(int *tempC, int *humi){
unsigned char h_int,h_dec,t_int,t_dec,chk;
// start signal
DHT = 0; // set as output low
delay_ms(18);
DHT = 1; // release (input with pull-up)
delay_us(30);
// sensor response: 80us low + 80us high
if(!dht_wait_level(0, 120)) return 0;
if(!dht_wait_level(1, 120)) return 0;
if(!dht_read_byte(&h_int)) return 0;
if(!dht_read_byte(&h_dec)) return 0;
if(!dht_read_byte(&t_int)) return 0;
if(!dht_read_byte(&t_dec)) return 0;
if(!dht_read_byte(&chk)) return 0;
if(((h_int + h_dec + t_int + t_dec) & 0xFF) != chk) return 0;
*humi = h_int; // DHT11: decimals ≈ 0; DHT22: combine int/dec if needed
*tempC = t_int;
return 1;
}
B) Bit-banged I²C + SHT21 read (higher accuracy)
sbit SDA = P1^7;
sbit SCL = P1^6;
void i2c_delay(){ _nop_(); _nop_(); _nop_(); }
void i2c_start(){ SDA=1; SCL=1; i2c_delay(); SDA=0; i2c_delay(); SCL=0; }
void i2c_stop(){ SDA=0; SCL=1; i2c_delay(); SDA=1; i2c_delay(); }
bit i2c_ack(){ bit a; SDA=1; SCL=1; i2c_delay(); a=SDA; SCL=0; return (a==0); }
void i2c_write(unsigned char d){
char i; for(i=0;i<8;i++){ SDA=(d&0x80); SCL=1; i2c_delay(); SCL=0; d<<=1; }
i2c_ack();
}
unsigned char i2c_read(bit ack){
char i; unsigned char d=0;
SDA=1; for(i=0;i<8;i++){ d<<=1; SCL=1; i2c_delay(); if(SDA) d|=1; SCL=0; }
SDA = ack ? 0 : 1; SCL=1; i2c_delay(); SCL=0; SDA=1; return d;
}
#define SHT21_ADDR 0x40
float sht21_read_temp(){
i2c_start();
i2c_write((SHT21_ADDR<<1)|0); // write
i2c_write(0xF3); // trigger temp
i2c_stop();
// crude wait (no clock stretching handling here)
{ unsigned int i; for(i=0;i<30000;i++); }
i2c_start();
i2c_write((SHT21_ADDR<<1)|1); // read
unsigned char msb = i2c_read(1);
unsigned char lsb = i2c_read(1);
(void)i2c_read(0); // CRC byte (ignored here)
i2c_stop();
unsigned int raw = ((unsigned int)msb<<8) | (lsb & 0xFC);
return -46.85 + 175.72 * ((float)raw / 65536.0);
}
float sht21_read_rh(){
i2c_start();
i2c_write((SHT21_ADDR<<1)|0);
i2c_write(0xF5); // trigger RH
i2c_stop();
{ unsigned int i; for(i=0;i<30000;i++); }
i2c_start();
i2c_write((SHT21_ADDR<<1)|1);
unsigned char msb = i2c_read(1);
unsigned char lsb = i2c_read(1);
(void)i2c_read(0);
i2c_stop();
unsigned int raw = ((unsigned int)msb<<8) | (lsb & 0xFC);
return -6.0 + 125.0 * ((float)raw / 65536.0);
}
Notes:
• Adjust delays to your clock so I²C is ~100 kHz.
• For production, handle clock stretching, CRC, and error paths.
5) Display / Logging
UART (quickest):
Init UART0 at 9600 bps and printf("T=%dC RH=%d%%\r\n", tC, RH); each second.
LCD 1602:
Init in 4-bit mode; show two lines like T=25C and RH=53%.
Sampling: 1–2 Hz is fine for room sensing; average 4–8 samples to smooth noise.
*6) Accuracy, calibration, and protection
*
- DHT11: ±2 °C / ±5%RH (rough). DHT22/AM2302: better. SHT21/SHT31: ±0.3–0.4 °C / ±2%RH (recommended).
- Add a breather slot in the enclosure; keep sensor away from regulators and LCD backlights (heat!).
- If long cables: add TVS diode and series 100 Ω on data lines.
- Power: 5 V rail with 100 nF near MCU; for I²C sensors at 3.3 V, use an LDO + common ground.
7) BOM (example)
- AT89C52, 11.0592 MHz crystal + 2×22 pF
- DHT22 or SHT31 + 4.7 kΩ pull-ups
- 1602 LCD (optional) + 10 kΩ pot
- 5 V supply, LDO 3.3 V (if SHTxx)
- 100 nF decoupling caps, 10 kΩ pull-up (DHT), headers, TVS (optional)
TL;DR
- Want fast & simple? Use DHT22, one data pin, parse 40 bits.
- Want accuracy & stability? Use SHT21/SHT31 over bit-banged I²C and apply datasheet formulas.
- Show results via UART or 1602 LCD.
Top comments (0)