EXPERIMENT – 08
Question : Write am embedded C code to convert analog input at AIN0.1 to digital value and display
it on LCD.
Code :
#include <c8051f120.h> // Include the header for the C8051F120
microcontroller
// Define special function registers (SFRs) for ADC, Timer,
and other peripherals
sfr16 ADC0_Data = 0xbe; // ADC data register for ADC0 (16-
bit)
sfr16 RCAP3 = 0xca; // Capture register for Timer 3
sfr16 TMR3 = 0xcc; // Timer 3 register
// Define system constants
#define SYSCLK 49000000 // System clock frequency in Hz
(49.0 MHz)
#define SAMPLE_RATE 5000 // ADC sample rate (5 kHz)
#define INT_DEC 256 // Integrate and decimate factor
(ADC averaging)
#define SAR_CLK 2500000 // ADC sample clock (2.5 MHz)
#define TIMER0_RELOAD_HIGH 0x3C // Timer 0 high byte reload
value
#define TIMER0_RELOAD_LOW 0xB0 // Timer 0 low byte reload
value
// Define bit-addressable variables
sbit LED = P1^6; // LED connected to P1.6
sbit SW1 = P3^7; // Switch 1 connected to P3.7
sbit RS = P4^2; // LCD Register Select (RS) pin
connected to P4.2
sbit RW = P4^1; // LCD Read/Write (RW) pin connected to
P4.1
sbit EN = P4^0; // LCD Enable (EN) pin connected to P4.0
// Declare global variables
unsigned int ia;
unsigned int measurement;
bit adc_flag; // Flag for ADC conversion completion
bit timer0_flag; // Flag for Timer 0 interrupt
unsigned int result; // Variable to store ADC result
unsigned char disp_data[5]; // Array to store data to be
displayed on LCD
// Function prototypes
void OSCILLATOR_Init(void);
void PORT_Init(void);
void ADC0_Init(void);
void TIMER3_Init(int counts);
void ADC0_ISR(void);
void send_lcd_data(void);
void lcd_initialize(void);
void lcd_command(unsigned char);
void lcd_data(unsigned char);
void lcd_busy(void);
void lcd_display(void);
void Timer0_Init(void);
// Main program entry point
void main (void)
{
char SFRPAGE_SAVE = SFRPAGE; // Save the current SFR page
// Disable watchdog timer to prevent reset
WDTCN = 0xde;
WDTCN = 0xad;
// Initialize the oscillator, ports, timers, and ADC
OSCILLATOR_Init();
PORT_Init();
TIMER3_Init(SYSCLK / SAMPLE_RATE); // Initialize Timer 3
for ADC sampling rate
ADC0_Init();
Timer0_Init();
// Enable ADC and LCD
SFRPAGE = ADC0_PAGE;
AD0EN = 1; // Enable ADC0
lcd_initialize(); // Initialize the LCD display
EA = 1; // Enable global interrupts
// Main loop
while (1)
{
// Check if ADC conversion is complete
if (adc_flag == 1)
{
// Calculate and convert ADC result to a
meaningful value (e.g., temperature or voltage)
measurement = result * 2.4 * 1000 / 4095; //
Convert ADC result to mV (12-bit ADC)
// Split the measurement into digits and store
them in disp_data array for LCD display
disp_data[0] = (measurement / 10000) + 0x30;
disp_data[1] = ((measurement % 10000) / 1000) +
0x30;
disp_data[2] = ((measurement % 1000) / 100) +
0x30;
disp_data[3] = ((measurement % 100) / 10) + 0x30;
disp_data[4] = (measurement % 10) + 0x30;
}
// Check if Timer0 interrupt has occurred
if (timer0_flag)
{
adc_flag = 0; // Reset ADC flag
timer0_flag = 0; // Reset Timer0 flag
SFRPAGE = TMR3_PAGE;
TR3 = 0; // Disable Timer 3
lcd_command(0x01); // Clear the LCD display
send_lcd_data(); // Send the data to the LCD
SFRPAGE = TMR3_PAGE;
TR3 = 1; // Re-enable Timer 3
}
}
}
// Initialize the system oscillator (PLL configuration)
void OSCILLATOR_Init(void)
{
int loop;
char SFRPAGE_SAVE = SFRPAGE;
SFRPAGE = CONFIG_PAGE;
OSCICN = 0x83; // Enable internal oscillator
CLKSEL = 0x00; // Select internal oscillator as clock
source
// Initialize PLL (Phase-Locked Loop)
PLL0CN = 0x00; // Disable PLL
FLSCL = 0x10; // Flash access time configuration
PLL0CN |= 0x01; // Enable PLL
PLL0DIV = 0x01; // Divide by 2 for PLL input frequency
PLL0FLT = 0x01; // Enable PLL filter
PLL0MUL = 0x02; // Multiply PLL by 2
// Wait for PLL to stabilize
for (loop = 0; loop < 256; loop++);
PLL0CN |= 0x02; // Enable PLL output
while (!(PLL0CN & 0x10)); // Wait for PLL lock
CLKSEL = 0x02; // Select PLL as clock source
SFRPAGE = SFRPAGE_SAVE; // Restore SFR page
}
// Initialize GPIO ports
void PORT_Init(void)
{
char SFRPAGE_SAVE = SFRPAGE;
SFRPAGE = CONFIG_PAGE;
XBR0 = 0x37; // Enable crossbar for required peripherals
XBR1 = 0x7e;
XBR2 = 0x5c;
P7MDOUT = 0xff; // Set port 7 as push-pull output
P7 = 0x00; // Initialize P7 to 0
P4MDOUT = 0xff; // Set port 4 as push-pull output
P4 = 0x00; // Initialize P4 to 0
SFRPAGE = SFRPAGE_SAVE; // Restore SFR page
}
// Initialize ADC0 for conversion
void ADC0_Init(void)
{
char SFRPAGE_SAVE = SFRPAGE;
SFRPAGE = ADC0_PAGE;
ADC0CN = 0x04; // Enable ADC and single-ended mode
REF0CN = 0x07; // Enable internal voltage reference
(VREF)
AMX0CF = 0x00; // Select AIN0 (analog input) as input to
ADC
AMX0SL = 0x01; // Select AIN0.1 as input channel
// Configure ADC for 2.5 MHz clock and gain factor 1
ADC0CF = (SYSCLK / SAR_CLK / 2) << 3;
ADC0CF |= 0x00; // No PGA gain
EIE2 |= 0x02; // Enable ADC interrupts
SFRPAGE = SFRPAGE_SAVE; // Restore SFR page
}
// Initialize Timer0 for periodic interrupts
void Timer0_Init(void)
{
char SFRPAGE_SAVE = SFRPAGE;
SFRPAGE = TIMER01_PAGE;
// Configure Timer0 for 16-bit mode with a 1:48 prescaler
TH0 = 0x3C;
TL0 = 0xB0;
TMOD = 0x01; // Timer0 in mode 1 (16-bit)
CKCON = 0x02; // Prescaler 1:48
ET0 = 1; // Enable Timer0 interrupt
TR0 = 1; // Start Timer0
SFRPAGE = SFRPAGE_SAVE; // Restore SFR page
}
// Initialize Timer 3 for ADC sampling rate
void TIMER3_Init(int counts)
{
char SFRPAGE_SAVE = SFRPAGE;
SFRPAGE = TMR3_PAGE;
TMR3CN = 0x00; // Disable Timer3
TMR3CF = 0x08; // Select 16-bit mode
RCAP3 = -counts; // Set reload value for Timer3
TMR3 = RCAP3; // Load Timer3 with initial value
EIE2 &= ~0x01; // Disable Timer3 interrupts
TR3 = 1; // Start Timer3
SFRPAGE = SFRPAGE_SAVE; // Restore SFR page
}
// ADC interrupt service routine (ISR)
void ADC0_ISR(void) interrupt 15
{
static unsigned int int_dec = INT_DEC, temp3;
static long accumulator = 0L;
char SFRPAGE_SAVE = SFRPAGE;
static int temp;
temp = ADC0H; // Read ADC high byte
temp = temp << 8; // Shift it to the high position
result = temp + ADC0L; // Combine with ADC low byte to
get result
AD0INT = 0; // Clear ADC interrupt flag
SFRPAGE = TMR3_PAGE;
TR3 = 0; // Disable Timer3 while processing
TMR3 = RCAP3; // Reload Timer3
adc_flag = 1; // Set ADC flag to indicate new data
SFRPAGE = SFRPAGE_SAVE; // Restore SFR page
}
// Timer0 ISR (interrupt service routine)
void Timer0_ISR(void) interrupt 1
{
char SFRPAGE_SAVE;
static unsigned int int_counter = 0;
SFRPAGE_SAVE = SFRPAGE;
// Reload Timer0 registers for next cycle
TH0 = TIMER0_RELOAD_HIGH;
TL0 = TIMER0_RELOAD_LOW;
int_counter++;
if (int_counter == 20) // Timer0 overflow after 20 counts
{
int_counter = 0;
timer0_flag = 1; // Set Timer0 flag for processing
}
SFRPAGE = SFRPAGE_SAVE; // Restore SFR page
}
// Send data to LCD (to display result)
void send_lcd_data(void)
{
int i;
char SFRPAGE_SAVE = SFRPAGE;
SFRPAGE = CONFIG_PAGE;
for (i = 0; i < 5; i++) // Loop through each digit of the
display data
{
lcd_data(disp_data[i]); // Send each character to LCD
}
lcd_command(0x02); // Set LCD cursor to home position
SFRPAGE = SFRPAGE_SAVE; // Restore SFR page
}
// Initialize LCD display
void lcd_initialize(void)
{
char SFRPAGE_SAVE = SFRPAGE;
SFRPAGE = CONFIG_PAGE;
lcd_command(0x30); // Initialize LCD in 5x7 matrix mode
lcd_command(0x0F); // Turn on display and blinking cursor
lcd_command(0x06); // Set entry mode (increment cursor)
lcd_command(0x01); // Clear display screen
SFRPAGE = SFRPAGE_SAVE; // Restore SFR page
}
// Send command to LCD
void lcd_command(unsigned char temp1)
{
char SFRPAGE_SAVE = SFRPAGE;
SFRPAGE = CONFIG_PAGE;
lcd_busy(); // Wait until LCD is not busy
RS = 0; // Command mode
RW = 0; // Write mode
P7 = temp1; // Send the command to P7
EN = 1; // Enable the LCD to latch data
for (ia = 0; ia < 5; ia++) {}
EN = 0;
SFRPAGE = SFRPAGE_SAVE; // Restore SFR page
}
// Send data to LCD
void lcd_data(unsigned char temp2)
{
char SFRPAGE_SAVE = SFRPAGE;
SFRPAGE = CONFIG_PAGE;
lcd_busy(); // Wait until LCD is not busy
RS = 1; // Data mode
RW = 0; // Write mode
P7 = temp2; // Send the data to P7
EN = 1; // Enable the LCD to latch data
for (ia = 0; ia < 50; ia++) {}
EN = 0;
SFRPAGE = SFRPAGE_SAVE; // Restore SFR page
}
// Check if the LCD is busy before sending new data/command
void lcd_busy(void)
{
for (ia = 0; ia < 6500; ia++) {} // Simple delay loop
}
Here’s a detailed list of the registers that are configured in the provided code, along with the
reasoning for each specific value assigned to them:
1. Oscillator and Clock Configuration
a. Register: OSCICN
i. Value: 0x83
ii. Configures the internal oscillator to 12 MHz by enabling it (OSCICN[7] = 1)
and selecting it as the clock source (OSCICN[3:2] = 0b11).
b. Register: CLKSEL
i. Value: 0x00
ii. Selects the internal oscillator as the clock source (CLKSEL[1:0] = 0b00).
c. Register: PLL0CN
i. Value: 0x00
ii. Disables the PLL during initialization (PLL0CN[1:0] = 0b00).
d. Register: FLSCL
i. Value: 0x10
ii. Sets the flash access time for the high-frequency system clock.
e. Register: PLL0CN
i. Value: 0x01
ii. Enables the PLL (PLL0CN[0] = 1).
f. Register: PLL0DIV
i. Value: 0x01
ii. Divides the PLL input by 2 (PLL0DIV[7:0] = 0x01), halving the PLL's input
frequency.
g. Register: PLL0FLT
i. Value: 0x01
ii. Enables the PLL filter (PLL0FLT[0] = 1).
h. Register: PLL0MUL
i. Value: 0x02
ii. Sets the PLL multiplier to 2 (PLL0MUL[2:0] = 0x02), doubling the PLL's input
frequency.
i. Register: CLKSEL
i. Value: 0x02
ii. Selects the PLL as the system clock source after it has locked (CLKSEL[1:0] =
0b10).
2. Port Configuration
a. Register: XBR0
i. Value: 0x37
ii. Enables the crossbar for required peripherals, allowing routing of specific
signals (e.g., ADC, UART) to appropriate I/O pins.
b. Register: XBR1
i. Value: 0x7E
ii. Configures additional crossbar options, enabling other features like UART,
SPI, and I2C.
c. Register: XBR2
i. Value: 0x5C
ii. Configures the second stage of the crossbar for enhanced functionality,
including routing signals to external pins.
d. Register: P7MDOUT
i. Value: 0xFF
ii. Sets all pins on Port 7 (P7) as push-pull outputs (P7MDOUT[7:0] = 0xFF).
e. Register: P7
i. Value: 0x00
ii. Initializes Port 7 pins to a low state (P7 = 0x00).
f. Register: P4MDOUT
i. Value: 0xFF
ii. Sets all pins on Port 4 (P4) as push-pull outputs (P4MDOUT[7:0] = 0xFF).
g. Register: P4
i. Value: 0x00
ii. Initializes Port 4 pins to a low state (P4 = 0x00).
3. ADC Configuration
a. Register: ADC0CN
i. Value: 0x04
ii. Enables ADC conversion (ADC0CN[7] = 1), selects single-ended mode
(ADC0CN[6] = 0), sets result alignment to left (ADC0CN[5] = 0), and
configures normal mode (ADC0CN[4:0] = 0b00100).
b. Register: REF0CN
i. Value: 0x07
ii. Enables the internal voltage reference (VREF) for stable ADC conversions
(REF0CN[2:0] = 0x07).
c. Register: AMX0CF
i. Value: 0x00
ii. Configures the analog multiplexer to select AIN0 for conversion.
d. Register: AMX0SL
i. Value: 0x01
ii. Selects the AIN0.1 pin as the specific analog input channel for the ADC.
e. Register: ADC0CF
i. Value: (SYSCLK / SAR_CLK / 2) << 3
ii. Configures the ADC clock to 2.5 MHz by dividing and bit-shifting the system
clock (target SAR clock rate).
f. Register: EIE2
i. Value: 0x02
ii. Enables the ADC interrupt (EIE2[1] = 1), triggering an interrupt service
routine upon conversion completion.
4. Timer Configuration
a. Register: TH0 and TL0
i. Value: 0x3C and 0xB0
ii. Sets the reload value for Timer 0 to 0x3CB0, establishing a specific interval
for periodic interrupts.
b. Register: TMOD
i. Value: 0x01
ii. Configures Timer 0 in mode 1 (16-bit auto-reload mode) for repeated
interrupts.
c. Register: CKCON
i. Value: 0x02
ii. Selects a 1:48 prescaler for Timer 0’s clock, slowing it down for precise
timing.
d. Register: ET0
i. Value: 1
Enables Timer 0 interrupt (ET0 = 1) to execute the timer interrupt service
ii. routine upon overflow.
e. Register: TR0
i. Value: 1
ii. Starts Timer 0 (TR0 = 1), enabling counting and interrupt generation.
f. Register: TMR3CN
i. Value: 0x00
ii. Disables Timer 3 during initialization (TMR3CN = 0x00).
g. Register: TMR3CF
i. Value: 0x08
ii. Configures Timer 3 as a 16-bit timer using the system clock as its source
(TMR3CF = 0x08).
h. Register: RCAP3
i. Value: -counts
ii. Loads the reload value for Timer 3 to set an appropriate count for the
sample rate.
i. Register: TR3
i. Value: 1
ii. Starts Timer 3 (TR3 = 1), used to trigger ADC conversions at the specified
rate.
5. LCD Configuration
a. Functions: lcd_command() / lcd_data()
b. These functions control communication with the LCD, including commands for
clearing the display, configuring settings, and displaying data.
CONCLUSION
In this experiment, a microcontroller-based system was designed to sample analog data via the ADC,
process it, and display the result on an LCD. Key components such as the oscillator, ADC, and timers
were configured to enable accurate sampling and real-time display updates. The system effectively