ATmega328P: Temperature Monitoring and Logging.
ATmega328P: Temperature Monitoring and Logging.
PROJECT
Student surname Mngomezulu
Student number 220383130
Practical number Project Phase 2
GA 5 competence achieved
YES NO
SB Mngomezulu
In this report we will look into the design and implementation of temperature
monitoring and logging through an ATmega328P. Temperature and time will be
displayed on a 20x4 LCD that is configured for I2C operation through a PCF8574
I2C port expander. The temperature will be measured through an LM35 module and
the data getting logged internally within the ATmega328P EEPROM memory
whenever a variation of 4ºC is detected within an hour. A DS3232 RTC will be used
to maintain time keeping in this exercise.
Literature Review
Project Objectives
Methodology
Components
• ATMega328P
• DS3232
• LM35 Module
• I2C LCD with PCF8574 module
The backbone of the project will be the ATmega328P, this microcontroller is a low
power 16MHz 8-bit microcontroller with 32 registers. The ATmega328P has
Bluetooth and WiFi capabilities, ADC conversion, I2C capabilities and an internal
EEPROM. The ATmega328P will be used to control these components as inputs and
outputs.
The LCD has an I2C module integrated with it and this allows the best use of the
microcontroller since only two pins are used for data transfer when using the I2C
protocol to communicate with modules. The two pins can also be used for other
modules as well distinguished by their addresses, hence we were able to wire the
RTC module via the same pins.
For temperature sensing the LM35 uses analog-to-digital conversion (ADC) which is
not too complicated compared to DS18B20 1 Wire Communication. The LM35 has a
high accuracy of ±0.5˚C which makes it ideal for the requirement we are addressing.
The DS3232 has high accuracy compared to other real time counter (RTC) modules.
Compared to the DS1307 for example, the DS3232 has an accuracy of ±2 ppm with
a built-in temperature compensated crystal oscillator while the latter has an accuracy
of ±20 ppm with an external crystal that is prone to drift and the inaccuracy has a
serious impact over long periods of time or as temperature varies. Since we are
measuring temperature the DS3232 is the best option because it is temperature
compensated.
Simulation
Proteus will be used for simulation of the project. For simulation components do not
always come integrated with the modules they need for I2C protocol functionality,
hence they have added to the circuitry. The placement of components must be done
strategically so that it allows one to see the circuit clearly when debugging. Most of
the protection measures which would have been necessary for the real setup are not
essential for simulation circuitry however they will be addressed in this report. It’s
also important to know where the location of your files is because for simulation a
hex file created by Microchip Studio will be used for the simulation.
Firmware Development
The code will be done on Microchip Studio using C-language. By using Microchip
Studio we can talk to the registers of the ATmega328P directly while other software
like Arduino may be buggy and slower.
Microchip Studio is specifically designed for AVR microcontrollers hence it’s suitable
for direct register control requirements unlike Arduino which is largely reliant on
libraries to execute processes. Microchip Studio has advanced debugging tools like
viewing EEPROM during execution and such features are crucial for development.
The functionality code of a project works better if the requirements are introduced in
stages. The first would be testing if the LCD is configured correctly by displaying a
simple string before adding values read from ADCs. To make sure that the ADC is
responsive you measure the voltage across the reference pin and the ground pin, or
the power pin and it must correspond with the way it is varied or tuned.
To make sure that the EEPROM is logging correctly an USART will be added, and it
will display data as it is logged on the EEPROM and it will be used to read the data
logged in a decimal form instead of the hexadecimal format which the EEPROM
stores the data in.
System Design
Requirement Analysis
This project was designed to meet specific functional and technical requirements
relevant to embedded environmental monitoring systems:
• User Readability: Display must present time and temperature clearly and
update in near real-time.
These components are chosen because of their compatibility and efficiency. The
LCD and the RTC will be configured in a I2C protocol configuration minimising the
connections that will be going to the microcontroller. Therefore, that means the
PCF8574 and the DS3232 will be connected to I2C pins SDA and SCL and the LM35
will be connected to ADC pin of the ATmega328P
Circuit Diagram
Firmware Implementation
The sequence at which the firmware will be applied needs to follow a certain order
for some functionalities otherwise the code might not work as intended. The
sequence which we will follow starts with setting the microcontroller itself which is a
stage that involves defining the frequency and setting the pinout. In this case since
we will be only simulating Proteus instead of using a CPU frequency of 16MHz,
which applies for the ATmega328P hardware, we used 1MHz. The address of the
PCF8574 I2C module used to communicate with LCD is also stated as determined
After macro definitions the next step is defining the required libraries for your project
and for those functions that do not have libraries readily available you have to define
the functions on the code, or you can create a library yourself. In the code I opted to
define the functions in the code instead of going for creating my own library.
#include <avr/io.h>
#include <util/delay.h>
#include <avr/eeprom.h>
#include <stdio.h>
// I2C Function Prototypes
void I2C_Init(void);
void I2C_Start(void);
void I2C_Stop(void);
void I2C_Write(uint8_t data);
uint8_t I2C_ReadACK(void);
void I2C_Init(void) {
// Set SCL and SDA as output
TWBR = 72; // Set I2C clock (100kHz for 16MHz CPU)
}
void I2C_Start(void) {
TWCR = (1 << TWSTA) | (1 << TWEN) | (1 << TWINT); // Start condition
while (!(TWCR & (1 << TWINT))); // Wait for completion
}
void I2C_Stop(void) {
TWCR = (1 << TWSTO) | (1 << TWEN) | (1 << TWINT); // Stop condition
}
uint8_t I2C_ReadACK(void) {
TWCR = (1 << TWEN) | (1 << TWINT) | (1 << TWEA);
while (!(TWCR & (1 << TWINT)));
return TWDR;
}
uint8_t I2C_ReadNACK(void) {
TWCR = (1 << TWEN) | (1 << TWINT);
while (!(TWCR & (1 << TWINT)));
return TWDR;
}}
The I2C_Init() function configures the I2C clock speed by setting the Two-Wire Bit
Rate Register (TWBR). With a CPU clock of 16 MHz, the TWBR value was selected
to achieve a standard 100 kHz I2C communication speed. This ensures compatibility
with typical I2C devices while balancing speed and reliability.
The I2C_Start() function generates a start condition on the I2C bus by setting the
appropriate control bits in the Two-Wire Control Register (TWCR). It waits until the
start condition is transmitted before proceeding, ensuring that the bus is properly
engaged before any communication takes place.
Data transmission is handled by the I2C_Write() function. It loads a data byte into
the Two-Wire Data Register (TWDR) and initiates the transfer by manipulating
TWCR. The function waits until transmission is complete before returning,
guaranteeing that each byte is successfully sent before the next action is taken.
The ADC_Init function is for initialising the ADC, it starts by setting the REFS0 bit of
the ADMUX register to 1 which configures the ADC to use the microcontrollers’ V CC
as the reference voltage. Then from the ADCSRA the ADC is enabled and a
prescaler of 128 is set.by setting all ADPS# bits to 1s.
void ADC_Init(void) {
ADMUX = (1 << REFS0); // Select AVcc as reference
ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
// Enable ADC, prescaler 128
}
uint16_t ADC_Read(uint8_t channel) {
ADMUX = (ADMUX & 0xF8) | channel; // Select ADC channel
ADCSRA |= (1 << ADSC); // Start conversion
while (ADCSRA & (1 << ADSC)); // Wait for conversion to finish
return ADC;
}
addr += 3;
eeprom_write_byte(&eeprom_addr, addr);
}
void EEPROM_ReadLog() {
uint8_t addr = 2;
UART_SendString("\nTemperature Log\r\n");
char buffer[32];
sprintf(buffer, "Time: %02d:%02d Temp: %dC\r\n", hour, minute, temp);
UART_SendString(buffer);
addr += 3;
_delay_ms(1);
}
}
void EEPROM_PrintLastLog() {
uint8_t current_addr = eeprom_read_byte((uint8_t*)0); // Log pointer is at
address 0
char buffer[32];
sprintf(buffer, "Time: %02d:%02d" " Temp: %d\xB0""C\r\n", hour, minute,
temp);
UART_SendString(buffer);
}
The main() function orchestrates the initialization and continuous operation of the
temperature logging system. It begins by invoking the initialization routines for the
int main(void) {
I2C_Init();
LCD_Init();
ADC_Init();
UART_Init();
EEPROM_ReadLog();
while (1) {
// Display Temperature
DisplayTemperature(temperature);
_delay_ms(1000);
}
}
core modules, namely I2C_Init(), LCD_Init(), ADC_Init(), EEPROM_Init(), and
UART_Init(). These setups ensure that communication interfaces, data acquisition
units, and storage modules are properly configured before normal operation begins.
Following setup, the LCD is prepared with a welcome message ("Temp Logger
Ready"), giving visual feedback that the system has booted correctly. After a brief
delay, the LCD is cleared again, and static labels ("Time:" and "Temp:") are
positioned to reserve spaces for dynamic updates.
Within the infinite while(1) loop, the program continually reads the current time from
the DS3232 RTC using DS3232_ReadTime(), and measures the analogue signal
from the LM35 temperature sensor via the ADC_Read() function. The ADC result is
then converted into a human-readable temperature value in degrees Celsius.
The loop concludes with a 1-second delay (_delay_ms(1000)), setting the update
and monitoring cycle frequency to once per second. This design ensures timely data
acquisition while avoiding excessive resource usage, resulting in a reliable and
efficient embedded logging system.
Limitations & Applicability
Limitations
The internal EEPROM of the ATmega328P is only of 1KB in size and this limits it to
only 341 entries of hour, minute and temperature readings. When using this system
as a long-term application it will require that older data gets overwritten to
accommodate for new data. The EEPROM also a finite lifespan of 100000
write/erase cycles therefore this is a limited timespan solution.
The circuit is configured through a direct connection of the LM35 to the ADC and this
configuration does not accommodate for measuring negative temperatures, reading
temperatures requires additional complicated components and firmware
configuration.
EEPROM writes are slow compared to RAM speed and this might cause the logging
process to block other time sensitive tasks if a diligent approach is not applied in the
sequence of implementing the tasks.
The EEPROM also doesn’t support checksums and error correction which means
when the data gets corrupted there is no way of telling. The other feature which is
not accommodated for by the ATmega238P on the memory logging is the wear-
levelling of the EEPROM which will result in risk of unreliability in the long term.
The DS3232 RTC tends to count a tad quicker in simulation environment and this is
mainly due to Proteus’ time emulation model limitations. This however is not the case
with an actual DS3232 hardware component which is highly accurate with a
temperature compensated crystal oscillator.
Firmware Does Not Compensate for Hardware Limitations
The coding approach applied is design-focused hence it does not compensate for
hardware limitations that can be addressed through software. There is the issue of
the EEPROM size which typically the viable solution would be to configure it to
overwrite old data once all addresses have been used up however, I will not
implement this as I believe extending the memory size is the best solution. The same
will apply to wear levelling techniques, I will not implement any since I believe
hardware solutions are the best solution and even address the problem for long term
solutions.
Applicability
This embedded system showcases several design principles and integration
strategies that can be applied in other domains:
Embedded C Programming:
The use of C allows for direct hardware control and efficient resource management,
making the approach applicable to bare-metal programming, firmware development
for wearable devices, consumer electronics, and real-time industrial control systems.
Testing
The development and testing of this embedded temperature monitoring and logging
system followed a modular, layered approach. The tests and implementation will
begin with most basic output functionalities and then integrating progressive
functionalities and components as we move forward.
The initial phase of development is focused on integrating the I2C LCD with the MCU
and testing that its configured right to display basic data in string format. Before
introducing any dynamic values from the ADC readings or counters, it was important
to make sure that I2C module and the LCD are correctly connected. It is important to
know which address to use for your I2C module as it depends on how you have
connected your address pins for the module and if the code does not use the right
address you will fail to reach to it. I have come to notice that the address for Proteus
simulation is 0x27 whereas for the actual hardware when configured similarly its
0x3F. The other challenge that surfaced was the LCD not displaying and turns out
there was a need for a delay during the initialisation of the I2C LCD otherwise some
commands and data would overlap each other before some functions are
implemented. When the timing issues and addresses have been corrected the string
format data was displayed and then the system was ready for displaying real-time
updated data from the ADC and the RTC.
Once the LCD has proven to be functional and operating as expected, the next
logical addition to the system is displaying the clock using the RTC module. The
code can be used to set the time and in reality the DS3232 has to have a battery
keep the clock running even when other components are not operational.
Following successful clock display, attention shifted to reading analogue values from
the LM35DZ sensor through the ADC0 channel. The goal during pre-integration was
to capture the raw ADC value and display it directly on the LCD to verify the
sampling process. Pre-integration testing revealed fluctuating readings that did not
align with the expected voltage from the sensor. Investigation showed that an
incorrectly set ADC prescaler were the causes. Implementing a prescaler of 128
corrected the readings. Once these modifications were integrated, the ADC value
remained consistent and could be updated on the LCD every second without display
artifacts.
Once satisfied with the ADC value display, the next logical step was to convert these
values into meaningful temperature data. The LM35DZ outputs 10 mV per °C, and
the ADC of the ATmega328P with a 5V reference and 10-bit resolution produces a
step size of approximately 4.88 mV. This meant that the raw ADC value needed to be
multiplied by 0.488 to convert it into °C. Pre-integration testing involved writing this
conversion in C and confirming output correctness via USART. The biggest
challenge during this stage was rounding errors due to integer division. To address
this, floating-point arithmetic was used temporarily to validate results, after which a
fixed-point approximation was adopted for efficiency. Post-integration testing showed
accurate real-time temperature values displayed on the LCD, matching those
obtained through simulation and multimeter validation
The final integration phase involved using the USART to transmit significant
temperature variations—defined as a 4°C or more change—and to cross-verify that
the EEPROM and LCD were synchronized in real time. Pre-integration USART
testing included displaying basic strings and temperature data on a serial terminal.
Interrupt-driven USART transmission was added to reduce blocking behavior. A key
challenge was ensuring that USART output did not conflict with LCD updates,
particularly when both used data derived from the same ADC readings. This was
resolved by introducing a data buffering scheme and synchronizing display refresh
cycles. Final testing showed that whenever a qualifying temperature change
occurred, both the LCD and EEPROM updated simultaneously, and the USART
accurately reflected this event on the serial monitor.
Results
LCD Display
• The LCD module reliably displayed real-time temperature and clock readings
with an update rate of 1 second.
• No character flickering, ghosting, or communication errors were observed
during simulation.
• Display layout remained stable with dynamic data updates throughout testing.
• Temperature readings obtained from the LM35DZ sensor through the ADC0
channel were consistent and stable.
• ADC configurations with a prescaler of 128 effectively reduced noise and
provided smooth readings.
• Temperature conversions from ADC values matched expectations based on
theoretical calculations and virtual voltmeter checks within Proteus.
EEPROM Logging
USART Communication
RTC Performance
• The DS3232 RTC module maintained accurate clock readings throughout the
simulation.
• Minor timing acceleration due to Proteus simulation behavior was observed
but was consistent and did not impact system functionality.
This project aligns comprehensively with the ECSA GA5 outcomes, as detailed
below:
1. Applicability and Limitations of Method, Skill, or Tool
The methods and tools used throughout this project were carefully assessed for their
applicability and limitations before implementation. Proteus simulation was selected
due to its ability to model microcontroller behaviour closely without needing physical
hardware. The LM35DZ temperature sensor, EEPROM internal memory, and USART
communication were chosen based on their suitability for environmental monitoring
within the constraints of available resources. Limitations such as EEPROM wear,
simulation inaccuracies, ADC noise, and I2C communication delays were identified
and considered during the design and testing stages, as discussed under Limitations
and Applicability.
Each method and tool was applied systematically and correctly to achieve the
required result. LCD interfacing was established with verified I2C communication, the
ADC was configured with an appropriate prescaler to stabilize analogue readings,
temperature values were accurately calculated using proper scaling from ADC
readings, and EEPROM writes were conditioned to prevent unnecessary memory
wear. USART communication was integrated in a non-blocking manner to ensure
real-time operation. These correct applications led to a fully functional system, as
documented in Section 8.
Testing was conducted both at the subsystem level and at the full system integration
level. Each module was independently verified before integration, and their
combined performance was tested to ensure seamless operation. System outputs
such as LCD displays, EEPROM contents, and USART serial outputs were cross-
verified for consistency and correctness. Furthermore, a comparative analysis was
performed to evaluate system behavior under different update delays (1 second vs.
300ms), reinforcing the assessment of timing trade-offs. All results met the intended
functional requirements of the project.
4. Computer Applications Created, Selected, and Used
Proteus was used extensively to create a virtual simulation environment for the
embedded system, replacing the need for immediate physical prototypes.
Additionally, microcontroller firmware was developed in C, leveraging custom I2C
libraries, EEPROM management functions, ADC configuration, and USART routines.
The disciplined use of these tools enabled successful completion of the project
objectives. The choice of applications and tools directly supported the project goals,
confirming the fulfillment of GA5 expectations.
Overall, the project not only achieved its technical aims but also systematically
adhered to professional engineering practices aligned with ECSA's GA5 competency
outcomes.
Conclusion