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;
}
}