8051 Microcontroller Peripheral Interfacing: RTC, Sensors, and Communication Protocols

DS1302 Real-Time Clock Implementation

The DS1302 communicates via a three-wire synchronous serial interface consisting of Clock (CLK), Data (IO), and Chip Enable (CE) lines. The device stores time data in Binary-Coded Decimal (BCD) format, requiring conversion for standard decimal display.

BCD Conversion Logic:

  • Decimal to BCD: bcd_value = (decimal / 10) * 16 + (decimal % 10)
  • BCD to Decimal: decimal = (bcd_value / 16) * 10 + (bcd_value % 16)

Driver Implementation:

sbit RTC_CLK = P1^7;
sbit RTC_DAT = P2^3;
sbit RTC_RST = P1^3;

void rtc_write_byte(unsigned char cmd) {
    unsigned char i;
    for (i = 0; i < 8; i++) {
        RTC_CLK = 0;
        RTC_DAT = cmd & 0x01;
        cmd >>= 1;
        RTC_CLK = 1;
    }
}

unsigned char rtc_read_byte(unsigned char addr) {
    unsigned char i, result = 0x00;
    RTC_RST = 0;
    RTC_CLK = 0;
    RTC_RST = 1;
    rtc_write_byte(addr);
    
    for (i = 0; i < 8; i++) {
        RTC_CLK = 0;
        result >>= 1;
        if (RTC_DAT) result |= 0x80;
        RTC_CLK = 1;
    }
    
    RTC_RST = 0;
    RTC_CLK = 0;
    RTC_CLK = 1;
    RTC_DAT = 0;
    RTC_DAT = 1;
    return result;
}

void rtc_write_register(unsigned char addr, unsigned char data) {
    RTC_RST = 0;
    RTC_CLK = 0;
    RTC_RST = 1;
    rtc_write_byte(addr);
    rtc_write_byte(data);
    RTC_RST = 0;
}

Time Setting and Reading:

void configure_rtc(unsigned char *time_buf) {
    unsigned char idx;
    rtc_write_register(0x8E, 0x00);  // Disable write protection
    for (idx = 0; idx < 3; idx++) {
        rtc_write_register(0x84 - 2*idx, time_buf[idx]);
    }
    rtc_write_register(0x8E, 0x80);  // Enable write protection
}

void fetch_rtc(unsigned char *time_buf) {
    unsigned char idx;
    for (idx = 0; idx < 3; idx++) {
        time_buf[idx] = rtc_read_byte(0x85 - 2*idx);
    }
}

DS18B20 Temperature Sensor

This digital thermometer utilizes a single-wire bus protocol requiring precise timing. The sensor returns 12-bit temperature data in two's complement format within its scratchpad memory.

Communication Protocol:

sbit ONE_WIRE = P1^4;

void onewire_delay(unsigned int ticks) {
    ticks *= 12;
    while (ticks--);
}

bit onewire_reset(void) {
    bit presence;
    ONE_WIRE = 1;
    onewire_delay(12);
    ONE_WIRE = 0;
    onewire_delay(80);
    ONE_WIRE = 1;
    onewire_delay(10);
    presence = ONE_WIRE;
    onewire_delay(5);
    return presence;
}

void onewire_write(unsigned char byte_val) {
    unsigned char bit_idx;
    for (bit_idx = 0; bit_idx < 8; bit_idx++) {
        ONE_WIRE = 0;
        ONE_WIRE = byte_val & 0x01;
        onewire_delay(5);
        ONE_WIRE = 1;
        byte_val >>= 1;
    }
    onewire_delay(5);
}

unsigned char onewire_read(void) {
    unsigned char bit_idx, data_byte = 0;
    for (bit_idx = 0; bit_idx < 8; bit_idx++) {
        ONE_WIRE = 0;
        data_byte >>= 1;
        ONE_WIRE = 1;
        if (ONE_WIRE) data_byte |= 0x80;
        onewire_delay(5);
    }
    return data_byte;
}

Temperature Acquisition:

float acquire_temperature(void) {
    unsigned char lsb, msb;
    int raw_temp;
    
    onewire_reset();
    onewire_write(0xCC);  // Skip ROM command
    onewire_write(0x44);  // Initiate temperature conversion
    
    onewire_reset();
    onewire_write(0xCC);
    onewire_write(0xBE);  // Read scratchpad
    
    lsb = onewire_read();
    msb = onewire_read();
    raw_temp = (msb << 8) | lsb;
    
    return raw_temp / 16.0;
}

I2C Bus Foundation

The Inter-Integrated Circuit protocol requires clock synchronization and open-drain data lines with external pull-up resistors.

Bit-Banging Implementation:

sbit I2C_SCL = P2^0;
sbit I2C_SDA = P2^1;

void i2c_timing_delay(unsigned char duration) {
    while (duration--) {
        _nop_();
    }
}

void i2c_start_condition(void) {
    I2C_SDA = 1;
    I2C_SCL = 1;
    i2c_timing_delay(5);
    I2C_SDA = 0;
    i2c_timing_delay(5);
    I2C_SCL = 0;
}

void i2c_stop_condition(void) {
    I2C_SDA = 0;
    I2C_SCL = 1;
    i2c_timing_delay(5);
    I2C_SDA = 1;
    i2c_timing_delay(5);
}

void i2c_transmit_byte(unsigned char payload) {
    unsigned char shift_count;
    for (shift_count = 0; shift_count < 8; shift_count++) {
        I2C_SCL = 0;
        i2c_timing_delay(5);
        I2C_SDA = (payload & 0x80) ? 1 : 0;
        i2c_timing_delay(5);
        I2C_SCL = 1;
        payload <<= 1;
        i2c_timing_delay(5);
    }
    I2C_SCL = 0;
}

bit i2c_receive_ack(void) {
    bit ack_status;
    I2C_SCL = 1;
    i2c_timing_delay(5);
    ack_status = I2C_SDA;
    I2C_SCL = 0;
    i2c_timing_delay(5);
    return ack_status;
}

unsigned char i2c_receive_byte(void) {
    unsigned char shift_count, rx_data = 0;
    for (shift_count = 0; shift_count < 8; shift_count++) {
        I2C_SCL = 1;
        i2c_timing_delay(5);
        rx_data <<= 1;
        if (I2C_SDA) rx_data |= 0x01;
        I2C_SCL = 0;
        i2c_timing_delay(5);
    }
    return rx_data;
}

void i2c_send_ack(bit ack_val) {
    I2C_SCL = 0;
    I2C_SDA = ack_val;
    i2c_timing_delay(5);
    I2C_SCL = 1;
    i2c_timing_delay(5);
    I2C_SCL = 0;
    I2C_SDA = 1;
    i2c_timing_delay(5);
}

PCF8591 ADC/DAC Converter

This device provides four analog inputs and one analog output via I2C. Address 0x90 enables write operations, while 0x91 enables read operations.

Control Byte Configuration:

  • Channel selection: Bits 0-1 (0x00 to 0x03)
  • Auto-increment: Bit 2
  • Analog output enable: Bit 6
unsigned char pcf8591_adc(unsigned char channel_cfg) {
    unsigned char adc_result;
    
    i2c_start_condition();
    i2c_transmit_byte(0x90);
    i2c_receive_ack();
    i2c_transmit_byte(0x40 | channel_cfg);  // Enable DAC, select channel
    i2c_receive_ack();
    
    i2c_start_condition();
    i2c_transmit_byte(0x91);
    i2c_receive_ack();
    adc_result = i2c_receive_byte();  // Discard first conversion
    i2c_send_ack(0);
    adc_result = i2c_receive_byte();  // Valid data
    i2c_send_ack(1);
    i2c_stop_condition();
    
    return adc_result;
}

void pcf8591_dac(unsigned char dac_value) {
    i2c_start_condition();
    i2c_transmit_byte(0x90);
    i2c_receive_ack();
    i2c_transmit_byte(0x40);  // Enable analog output
    i2c_receive_ack();
    i2c_transmit_byte(dac_value);
    i2c_receive_ack();
    i2c_stop_condition();
}

Voltage calculation: voltage = adc_result * 5.0 / 255

AT24C02 EEPROM Storage

This 2K-bit memmory organizes data in to 256 bytes of addressable space. Write operations require 5ms settling time between page writes.

Memory Operations:

void eeprom_page_write(unsigned char *src_ptr, unsigned char mem_addr, unsigned char length) {
    i2c_start_condition();
    i2c_transmit_byte(0xA0);
    i2c_receive_ack();
    i2c_transmit_byte(mem_addr);
    i2c_receive_ack();
    
    while (length--) {
        i2c_transmit_byte(*src_ptr++);
        i2c_receive_ack();
        i2c_timing_delay(200);  // Write cycle delay
    }
    i2c_stop_condition();
}

void eeprom_sequential_read(unsigned char *dest_ptr, unsigned char mem_addr, unsigned char length) {
    i2c_start_condition();
    i2c_transmit_byte(0xA0);
    i2c_receive_ack();
    i2c_transmit_byte(mem_addr);
    i2c_receive_ack();
    
    i2c_start_condition();
    i2c_transmit_byte(0xA1);
    i2c_receive_ack();
    
    while (length--) {
        *dest_ptr++ = i2c_receive_byte();
        i2c_send_ack(length ? 0 : 1);
    }
    i2c_stop_condition();
}

NE555 Frequecny Measurement

Configure Timer0 as a 16-bit counter (Mode 1, C/T=1) to count external pulses from the NE555 output. Timer1 provides the gating timebase.

Timer Configuration:

void Timer0_Init_Counter(void) {
    AUXR &= 0x7F;
    TMOD &= 0xF0;
    TMOD |= 0x05;  // Counter mode, 16-bit
    TL0 = 0;
    TH0 = 0;
    TF0 = 0;
    TR0 = 1;
}

void Timer1_Init_Timer(void) {
    AUXR &= 0xBF;
    TMOD &= 0x0F;
    TMOD |= 0x10;  // Timer mode, 16-bit
    TL1 = 0x18;
    TH1 = 0xFC;    // 1ms @ 12MHz
    TF1 = 0;
    TR1 = 1;
    ET1 = 1;
    EA = 1;
}

Interrupt Service Routine:

unsigned int pulse_count;
unsigned int gate_timer;

void timer1_isr(void) interrupt 3 {
    if (++gate_timer >= 1000) {  // 1-second gate
        gate_timer = 0;
        pulse_count = (TH0 << 8) | TL0;
        TH0 = 0;
        TL0 = 0;
    }
    // Additional display multiplexing code here
}

Software PWM Generation

Generate pulse-width modulation using Timer1 interrupts at 100μs intervals to achieve 1kHz PWM with 100 steps resolution.

unsigned char pwm_counter;
unsigned char duty_threshold;

void Timer1_Init_PWM(void) {
    AUXR &= 0xBF;
    TMOD &= 0x0F;
    TL1 = 0x9C;
    TH1 = 0xFF;    // 100μs @ 12MHz
    TF1 = 0;
    TR1 = 1;
    ET1 = 1;
}

void timer1_pwm_isr(void) interrupt 3 {
    if (++pwm_counter >= 100) pwm_counter = 0;
    PWM_OUTPUT_PIN = (pwm_counter < duty_threshold) ? 1 : 0;
}

Duty cycle percentages map directly to threshold values (e.g., 25% duty = threshold 25).

Ultrasonic Distance Measurement

Utilize the PCA module or external interrupt timing to measure echo pulse duration. Speed of sound calculation: distance = time × 0.017 cm/μs.

sbit TRIG_PIN = P1^0;
sbit ECHO_PIN = P1^1;

void trigger_pulse(void) {
    unsigned char burst;
    for (burst = 0; burst < 8; burst++) {
        TRIG_PIN = 1;
        _nop_(); _nop_();
        TRIG_PIN = 0;
        _nop_(); _nop_();
    }
}

unsigned int measure_distance(void) {
    unsigned int echo_time;
    
    CMOD = 0x00;
    CH = 0; CL = 0;
    trigger_pulse();
    
    CR = 1;
    while (ECHO_PIN && !CF);
    CR = 0;
    
    if (CF) {
        CF = 0;
        return 0;  // Out of range
    }
    
    echo_time = (CH << 8) | CL;
    return (unsigned int)(echo_time * 0.017);
}

UART Serial Communication

Implement full-duplex serial communication using Timer2 as the baud rate generator to preserve Timer0 and Timer1 for other peripherals.

void uart_initialize(void) {
    SCON = 0x50;   // Mode 1, 8-bit UART, enable receiver
    AUXR |= 0x01;  // Select Timer2 as baud generator
    AUXR |= 0x04;  // Timer2 in 1T mode
    T2L = 0xC7;
    T2H = 0xFE;    // 9600bps @ 12MHz
    AUXR |= 0x10;  // Start Timer2
    ES = 1;
    EA = 1;
}

void uart_transmit(unsigned char tx_data) {
    SBUF = tx_data;
    while (!TI);
    TI = 0;
}

void uart_send_string(unsigned char *str_ptr) {
    while (*str_ptr) {
        uart_transmit(*str_ptr++);
    }
}

// Interrupt-driven receive
unsigned char rx_buffer[16];
unsigned char rx_index;

void uart_isr(void) interrupt 4 {
    if (RI) {
        rx_buffer[rx_index++] = SBUF;
        RI = 0;
    }
}

Tags: 8051 Microcontroller Embedded Systems DS1302 RTC DS18B20 Temperature Sensor I2C Protocol

Posted on Sat, 12 Sep 2026 16:22:53 +0000 by 3rve