I have used this FAST PWM mode to trigger two interrupt service routines. The timer compares register A sets the frequency. I have not enabled the output compare pins. since it was used by some other peripheral. So by doing this, I generate a PWM signal by issuing a command in the ISR.
/*
* main.c
*
* Created: 10 July 2023 10:47:23 PM
* Author: abhay
*/
#define F_CPU 16000000
#include <xc.h>
#include <stdio.h>
#include "util/delay.h"
#include <avr/interrupt.h>
#include "uart.h"
#define LED_ON PORTB |= (1<<5)
#define LED_OFF PORTB &= ~(1<<5)
// Function to send a character via UART
int UART_putchar(char c, FILE *stream) {
if (c == '\n')
UART_putchar('\r', stream); // Add carriage return before newline
while (!(UCSR0A & (1 << UDRE0))); // Wait for the transmit buffer to be empty
UDR0 = c; // Transmit the character
return 0;
}
// Create a FILE structure to redirect the printf stream to UART
FILE uart_output = FDEV_SETUP_STREAM(UART_putchar, NULL, _FDEV_SETUP_WRITE);
ISR(TIMER3_COMPA_vect){
PORTD |= (1<<6);
}
ISR(TIMER3_COMPB_vect){
PORTD &= ~(1<<6);
}
int main(void)
{
USART_Init();
// Redirect stdout stream to UART
stdout = &uart_output;
DDRB |= (1<<5); // set Data direction to output for PB5
LED_OFF; // set output to high
DDRD |= (1 << 6); //set PD6 as output
/*
F_CPU = 16000000
Prescaler = 64
Frequency = 50Hz
Period = 0.020 s
step time = 1/(F_CPU/Prescaler) = 0.000004 s
number of steps = 0.020/0.000004 = 5000
*/
TCNT3 = 0; // Timer counter initial value = 0
// Output Compare A value = 5000 or 20 Milli second
OCR3A = 5000;
// Output Compare B value = 500 or 2 Milli second
OCR3B = 500;
// Fast PWM
TCCR1A |= (1 << WGM31)|(1 << WGM30);
// Prescaler: 64
TCCR3B |= (1 << WGM32)|(1<<WGM32)|(1 << CS31)|(1 << CS30);
// Enable Timer Interrupt for Overflow, Compare match A and Compare Match B
TIMSK3 |= (1 << OCIE3B)|(1 << OCIE3A)|(1<<TOIE3);
// Enable Global Interrupt
sei();
while(1)
{
OCR3B = 250;//5% 1ms
_delay_ms(500);
OCR3B = 375;//7.5% 1.5ms
_delay_ms(100);
OCR3B = 500;//10% 2ms
_delay_ms(500);
}
}
Timers are essential components in microcontrollers that allow precise timing and synchronization for various applications. The ATmega328PB microcontroller offers several timer/counters, including Timer/Counter 1 (TC1), which is a 16-bit timer with advanced features. In this blog post, I will explore how to utilize Timer 1 in CTC (Clear Timer on Compare Match) mode on the ATmega328PB microcontroller.
Hardware Setup
Before we proceed with the code, ensure you have the necessary hardware setup. You will need an ATmega328PB microcontroller, a 16MHz crystal oscillator, and any additional components required for your specific application. Connect the crystal oscillator to the XTAL1 and XTAL2 pins of the microcontroller to provide a stable clock signal.
UART Communication Initialization
In this example, we will utilize UART communication for debugging or output purposes. Make sure you have already implemented the necessary UART functions or library. The UART initialization code should include setting the baud rate, enabling the transmitter and receiver, and configuring the data format (e.g., number of data bits, parity, and stop bits). We will also redirect the stdout stream to the UART using the stdio.h library, allowing us to use the printf function for UART output.
Timer 1 Configuration in CTC Mode
Let’s dive into the code and configure Timer 1 in CTC mode. Here’s an example code snippet:
/*
* main.c
*
* Created: 7/9/2023 12:47:23 AM
* Author: abhay
*/
#define F_CPU 16000000
#include <xc.h>
#include <stdio.h>
#include "util/delay.h"
#include <avr/interrupt.h>
#include "uart.h"
// Function to send a character via UART
int UART_putchar(char c, FILE *stream) {
if (c == '\n')
UART_putchar('\r', stream); // Add carriage return before newline
while (!(UCSR0A & (1 << UDRE0))); // Wait for the transmit buffer to be empty
UDR0 = c; // Transmit the character
return 0;
}
// Create a FILE structure to redirect the printf stream to UART
FILE uart_output = FDEV_SETUP_STREAM(UART_putchar, NULL, _FDEV_SETUP_WRITE);
ISR(TIMER1_COMPA_vect){
printf("2. compare match A\n");
}
ISR(TIMER1_COMPB_vect){
printf("1. compare match B\n");
}
int main(void)
{
USART_Init();
// Redirect stdout stream to UART
stdout = &uart_output;
DDRB |= (1<<5); // set Data direction to output for PB5
PORTB |= (1<<5); // set output to high
/*
* Timer 1
* Mode of operation : CTC
* When Output Compare A register value equals the
* Timer Counter register (TCNT1) it resets the Timer-Counter-register value
* and generates a interrupt.
* Only OCR1A will reset the timer counter.
* OCR1B can be used to generate a compare match between TCNT1 = 0 and OCR1A
*
*/
TCNT1 = 0; // Timer counter initial value = 0
OCR1BH = 0x3D; // Output Compare B value = 0x3d09 or 1 second
OCR1BL = 0x09;
OCR1AH = 0x7a; // Output Compare A value = 0x7a12 or 2 second
OCR1AL = 0x12;
TCCR1B |= (1<<WGM02)|(1 << CS12)|(1 << CS10); // CTC Prescaler: 1024
TIMSK1 |= (1 << OCIE1B)|(1 << OCIE1A)|(1<<TOIE1);
sei(); // Enable Global Interrupt
while(1)
{
printf(" This is Main:\n");
_delay_ms(2500);
//TODO:: Please write your application code
}
}
In this code snippet, we first initialize the UART communication and redirect the stdout stream to the UART output using the FDEV_SETUP_STREAM macro. The UART_putchar function is used to send a character via UART, ensuring that newline characters (\n) are preceded by a carriage return character (\r) for proper line endings.
Next, we configure Timer/Counter 1 (TC1) for CTC mode and set the prescaler to 1024, which divides the clock frequency to generate a suitable timebase. The TCCR1A and TCCR1B registers are set accordingly.
We then set the compare values (OCR1A and OCR1B) to determine the time intervals at which we want to generate interrupts. In this example, OCR1A is set for 2 second delay, and OCR1B is set for approximately 1 seconds delay.
Finally, we enable the Timer/Counter TC1 compare match interrupts (OCIE1A and OCIE1B) using the TIMSK1 register, and we enable global interrupts with the sei() function.
Interrupt Service Routines (ISRs)
The code snippet defines two Interrupt Service Routines (ISRs): TIMER1_COMPA_vect and TIMER1_COMPB_vect. These ISRs will be executed when a compare match occurs for Output Compare A and Output Compare B, respectively. In this example, we use these ISRs to print messages to the UART output. You can modify these ISRs to perform any desired actions based on your specific application requirements.
Putting It All Together
Once you have set up the UART communication, configured Timer 1 in CTC mode, and defined the necessary ISRs, you can utilize the precise timing capabilities of Timer 1 in your main program loop. Use the printf function to output information via UART, and the compare match interrupts will handle the precise timing events.
while (1) {
printf(" This is Main:\n");
_delay_ms(2500);
// Additional code and operations
// ...
}
In the above example, the main program loop will execute continuously, printing “This is the main program loop” every 1 second using the printf function. The _delay_ms function provides a delay of 2500 milliseconds (2.5 second) between each iteration of the loop.
Conclusion
Utilizing Timer 1 in CTC mode on the ATmega328PB microcontroller provides precise timing capabilities for various applications. By configuring Timer 1, setting compare match values, and utilizing compare match interrupts, you can achieve accurate timing control in your embedded systems. When combined with UART communication, you can easily monitor and debug your code by printing relevant information via the UART interface.
Remember to consult the ATmega328PB datasheet and relevant documentation for more details on Timer 1, CTC mode, and other timer features. Ensure that you have correctly configured your hardware setup, including the crystal oscillator and UART connection, to match the code requirements.
Using Timer 1 in CTC mode
with UART communication opens up a range of possibilities for precise timing and debugging capabilities in your projects. Experiment with different compare match values and integrate this functionality into your applications to enhance timing accuracy and control.
The simplest mode of operation is the Normal mode (WGM0[2:0] = 0x0). In this mode, the counting direction is always up (incrementing), and no counter clear is performed. The counter simply overruns when it passes its maximum 8-bit value (TOP=0xFF) and then restarts from the bottom (0x00). In Normal mode operation, the Timer/Counter Overflow flag (TOV0) will be set in the same clock cycle in which the TCNT0 becomes zero. In this case, the TOV0 flag behaves like a ninth bit, except that it is only set, not cleared. However, combined with the timer overflow interrupt that automatically clears the TOV0 flag, the timer resolution can be increased by software. There are no special cases to consider in the Normal mode, a new counter value can be written any time.
The output compare unit can be used to generate interrupts at some given time. Using the output compare to generate waveforms in Normal mode is not recommended since this will occupy too much of the CPU time.
The counter will always count from 0 to 255 and then after 255 it will set the overflow bit and start the counting from 0.
Without Interrupt
We can use this to generate delays. But This is a type of blocking code.
/*
* main.c
*
* Created: 7/9/2023 12:47:23 AM
* Author: abhay
*/
#define F_CPU 16000000UL
#include <xc.h>
#include <util/delay.h>
void T0delay();
int main(void)
{
DDRB |= (1<<5);
PORTB |= (1<<5);
while(1)
{
PORTB |= (1<<PINB5);
T0delay();
PORTB &= ~(1<<PINB5);
T0delay();
//TODO:: Please write your application code
}
}
void T0delay()
{
/*
F_CPU/prescaler = clock for timer
1/clock for timer = timer step time
for 1 second the timer will take : 1/timer step time
16000000/1024 = 15625 clock
1/15625 = 0.000064 = 64us (counter step size)
64us x 256 = 0.016384 sec (0verflow time)
64us x 255 - tcnt +1 = 0.016384 sec (0verflow time)
64us x (255 - tcnt +1 )= x
tcnt = x / 64us ) -256
for 1 sec Delay
=> 1/0.016384 sec = 61.03515625
Taking only integer
so 61 overflows needs to occur for 0.999424 Seconds
1-0.999424 = 0.000576 seconds
0.000576/ (counter step size) = required steps
0.000576/0.00064 = 9 steps
Note: this will be close to 1 second. but it will be longer due to overhead added by the instructions.
*/
for(int i = 0; i< 61;i++){
TCNT0 = 0;
TCCR0A = 0;
TCCR0B |= (1<<CS00)|(1<<CS02); //prescaler 1024
while( (TIFR0 & 0x1) == 0); // Wait for overflow flag
TCCR0B = 0;
TIFR0 = 0x1;
}
//9 steps timer code
TCNT0 = 255-9;
TCCR0A = 0;
TCCR0B |= (1<<CS00)|(1<<CS02); //prescaler 1024
while( (TIFR0 & 0x1) == 0); // Wait for overflow flag
TCCR0B = 0;
TIFR0 = 0x1;
}
Normal Mode with Interrupt
#define F_CPU 16000000UL
#include <xc.h>
#include <avr/interrupt.h>
ISR(TIMER0_OVF_vect){
PORTB ^= (1<<PINB5); // Toggle the GPIO
}
int main(void)
{
DDRB |= (1<<5); // set Data direction to output for PB5
PORTB |= (1<<5); // set output to high
TCNT0 = 0;
TCCR0A = 0; // Normal Mode
TCCR0B |= (1<<CS00)|(1<<CS02); //prescaler 1024
TIMSK0 |= (1 << TOIE0); // Overflow Interrupt Enable Bit
sei(); // Enable Global Interrupt
while(1)
{
//TODO:: Please write your application code
}
}
When I opened the case. I found a PCB which is screwed to a big heatsink. I unscrewed the bolts and saw that there is S109AFTG. The IC is sandwiched between the PCB and the heatsink. A small aluminum block is also used for heat transfer between the IC and the heatsink.
It has a different step size which can be selected by the DIP switches. The motor driver has a maximum of 1/32 step size.
which means 1.8°/ 32 = 0.05625°
360°/ 0.05625° = 6400 steps
So a full rotation will be in 6400 steps.
You will need a power source such as a Switched Mode Power Supply which can supply at least 2 Amps.
If your application needs more torque you will need a power source that can provide a high current without dropping the voltage.
Sometimes we are so busy in our work or in our day-to-day life that we forget to water our plants on time. Or in the summertime when the plants need additional water to sustain themselves in the high-temperature region like New Delhi.
This is a simple project that one can assemble and implement within a few minutes.
To make this project you will need some modules which are readily available in the market.
Arduino UNO x 1
Moisture Sensor x 1
A 5V relay x 1
5V water pump x 1
A short length of plastic or rubber tube x 1 – 1.5m
Rechargeable Power Bank x 1
#define sense A0
#define relay 9
void setup() {
// put your setup code here, to run once:
pinMode(sense, INPUT);
pinMode(relay, OUTPUT);
Serial.begin(9600);
}
int val;
void loop() {
// put your main code here, to run repeatedly:
val = analogRead(sense);
Serial.println(val);
if (val < 600) /* adjust this value to control how much soil must be moist */
{
digitalWrite(relay, HIGH);
}
else
{
digitalWrite(relay, LOW);
}
delay(400);
}
AT24C32 is an i2c compatible serial EEPROM which can be programmed using a microcontroller.
The AT24C32 provides 32,768 bits of serial electrically erasable and programmable read-only memory (EEPROM). The device’s cascadable feature allows up to 8 devices to share a common 2- wire bus. The device is optimized for use in many industrial and commercial applications where low power and low voltage operation are essential. The AT24C32/64 is available in space-saving 8-pin JEDEC PDIP, 8-pin JEDEC SOIC, 8-pin EIAJ SOIC, and 8-pin TSSOP (AT24C64) packages and is accessed via a 2-wire serial interface. In addition, the entire family is available in 2.7V (2.7V to 5.5V) and 1.8V (1.8V to 5.5V) versions.
/*
* main.c
*
* Created: 8/24/2022 10:53:05 PM
* Author: abhay
*/
#define F_CPU 16000000
#include <xc.h>
#include "util/delay.h"
#include "uart.h"
#include <stdio.h>
#define FALSE 0
#define TRUE 1
void EEOpen();
uint8_t EEWriteByte(uint16_t,uint8_t);
uint8_t EEReadByte(uint16_t address);
int main(void)
{
UART_Init();
EEOpen();
char buff[20];
sprintf(buff,"Hello EEPROM TEST \nBy: \t ABHAY");
UART_SendString(buff);
//Fill whole eeprom 32KB (32768 bytes)
//with number 7
uint16_t address;
char failed;
failed = 0 ;
for(address=0;address< (32768);address++)
{
sprintf(buff,"address = %d \n",address);
UART_SendString(buff);
if(EEWriteByte(address,5)==0)
{
//Write Failed
sprintf(buff,"write Failed %x \n",address);
UART_SendString(buff);
failed = 1;
break;
}
}
if(!failed)
{
//We have Done it !!!
sprintf(buff,"Write Success !\n");
UART_SendString(buff);
}
while(1)
{
//TODO:: Please write your application code
//Check if every location in EEPROM has
//number 7 stored
failed=0;
for(address=0;address < 32768 ; address++)
{
if(EEReadByte(address)!=5)
{
//Failed !
sprintf(buff,"Verify Failed %x \n",address);
UART_SendString(buff);
failed=1;
break;
}
}
if(!failed)
{
//We have Done it !!!
sprintf(buff,"Write Success !\n");
UART_SendString(buff);
}
}
}
void EEOpen()
{
//Set up TWI Module
TWBR0 = 5;
TWSR0 &= (~((1<<TWPS1)|(1<<TWPS0)));
}
uint8_t EEWriteByte(uint16_t address,uint8_t data)
{
do
{
//Put Start Condition on TWI Bus
TWCR0=(1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
//Poll Till Done
while(!(TWCR0 & (1<<TWINT)));
//Check status
if((TWSR0 & 0xF8) != 0x08)
return FALSE;
//Now write SLA+W
//EEPROM @ 00h
TWDR0=0b10100000;
//Initiate Transfer
TWCR0=(1<<TWINT)|(1<<TWEN);
//Poll Till Done
while(!(TWCR0 & (1<<TWINT)));
}while((TWSR0 & 0xF8) != 0x18);
//Now write ADDRH
TWDR0=(address>>8);
//Initiate Transfer
TWCR0=(1<<TWINT)|(1<<TWEN);
//Poll Till Done
while(!(TWCR0 & (1<<TWINT)));
//Check status
if((TWSR0 & 0xF8) != 0x28)
return FALSE;
//Now write ADDRL
TWDR0=(address);
//Initiate Transfer
TWCR0=(1<<TWINT)|(1<<TWEN);
//Poll Till Done
while(!(TWCR0 & (1<<TWINT)));
//Check status
if((TWSR0 & 0xF8) != 0x28)
return FALSE;
//Now write DATA
TWDR0=(data);
//Initiate Transfer
TWCR0=(1<<TWINT)|(1<<TWEN);
//Poll Till Done
while(!(TWCR0 & (1<<TWINT)));
//Check status
if((TWSR0 & 0xF8) != 0x28)
return FALSE;
//Put Stop Condition on bus
TWCR0=(1<<TWINT)|(1<<TWEN)|(1<<TWSTO);
//Wait for STOP to finish
while(TWCR0 & (1<<TWSTO));
//Wait untill Writing is complete
_delay_ms(1);
//Return TRUE
return TRUE;
}
uint8_t EEReadByte(uint16_t address)
{
uint8_t data;
//Initiate a Dummy Write Sequence to start Random Read
do
{
//Put Start Condition on TWI Bus
TWCR0=(1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
//Poll Till Done
while(!(TWCR0 & (1<<TWINT)));
//Check status
if((TWSR0 & 0xF8) != 0x08)
return FALSE;
//Now write SLA+W
//EEPROM @ 00h
TWDR0=0b10100000;
//Initiate Transfer
TWCR0=(1<<TWINT)|(1<<TWEN);
//Poll Till Done
while(!(TWCR0 & (1<<TWINT)));
}while((TWSR0 & 0xF8) != 0x18);
//Now write ADDRH
TWDR0=(address>>8);
//Initiate Transfer
TWCR0=(1<<TWINT)|(1<<TWEN);
//Poll Till Done
while(!(TWCR0 & (1<<TWINT)));
//Check status
if((TWSR0 & 0xF8) != 0x28)
return FALSE;
//Now write ADDRL
TWDR0=(address);
//Initiate Transfer
TWCR0=(1<<TWINT)|(1<<TWEN);
//Poll Till Done
while(!(TWCR0 & (1<<TWINT)));
//Check status
if((TWSR0 & 0xF8) != 0x28)
return FALSE;
//*************************DUMMY WRITE SEQUENCE END **********************
//Put Start Condition on TWI Bus
TWCR0=(1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
//Poll Till Done
while(!(TWCR0 & (1<<TWINT)));
//Check status
if((TWSR0 & 0xF8) != 0x10)
return FALSE;
//Now write SLA+R
//EEPROM @ 00h
TWDR0=0b10100001;
//Initiate Transfer
TWCR0=(1<<TWINT)|(1<<TWEN);
//Poll Till Done
while(!(TWCR0 & (1<<TWINT)));
//Check status
if((TWSR0 & 0xF8) != 0x40)
return FALSE;
//Now enable Reception of data by clearing TWINT
TWCR0=(1<<TWINT)|(1<<TWEN);
//Wait till done
while(!(TWCR0 & (1<<TWINT)));
//Check status
if((TWSR0 & 0xF8) != 0x58)
return FALSE;
//Read the data
data=TWDR0;
//Put Stop Condition on bus
TWCR0=(1<<TWINT)|(1<<TWEN)|(1<<TWSTO);
//Wait for STOP to finish
while(TWCR0 & (1<<TWSTO));
//Return TRUE
return data;
}
The DS1307 Real Time Clock uses I2c communication lines to connect with the microcontroller.
I2C uses two lines commonly known as Serial Data/Address or SDA and Serial Clock Line or SCL. The two lines SDA and SCL are standardised and they are implemented using either an open collector or open drain configuration. What this means is that you need to pull these lines UP to VCC. For complete information on how the i2C is implemented in ATmega328PB, you need to go through the section of the datasheet called TWI or Two-Wire Serial Interface.
To start I2C in ATmega328PB, first the SCL frequency needs to set which must be under 100KHz .
To set the SCL frequency you set two registers TWBR0 and TWSR0.
TWSR0 has two bit 0 and bit 1; which sets the prescaler for the clock to the TWI.
Then TWBR0 needs to be set which can anything from 0 to 255.
THen you need to write the I2C functions for start, repeated start, data trasmission and recepetion and stop.
/*
* main.c
*
* Created: 8/20/2022 2:08:09 PM
* Author: abhay
*/
#define F_CPU 16000000
#include <xc.h>
#include <avr/interrupt.h>
#include <stdio.h>
#include "util/delay.h"
#include "uart.h"
#define Device_Write_address 0xD0 /* Define RTC DS1307 slave address for write operation */
#define Device_Read_address 0xD1 /* Make LSB bit high of slave address for read operation */
#define TimeFormat12 0x40 /* Define 12 hour format */
#define AMPM 0x20
int second,minute,hour,day,date,month,year;
void TWI_init_master(void) // Function to initialize master
{
TWBR0=127; // Bit rate
TWSR0= (1<<TWPS1)|(1<<TWPS0); // Setting prescalar bits
// SCL freq= F_CPU/(16+2(TWBR).4^TWPS)
}
uint8_t I2C_Start(char write_address); /* I2C start function */
uint8_t I2C_Repeated_Start(char read_address); /* I2C repeated start function */
void I2C_Stop(); /* I2C stop function */
void I2C_Start_Wait(char write_address); /* I2C start wait function */
uint8_t I2C_Write(char data); /* I2C write function */
int I2C_Read_Ack(); /* I2C read ack function */
int I2C_Read_Nack(); /* I2C read nack function */
void RTC_Read_Clock(char read_clock_address)
{
I2C_Start(Device_Write_address); /* Start I2C communication with RTC */
I2C_Write(read_clock_address); /* Write address to read */
I2C_Repeated_Start(Device_Read_address); /* Repeated start with device read address */
second = I2C_Read_Ack(); /* Read second */
minute = I2C_Read_Ack(); /* Read minute */
hour = I2C_Read_Nack(); /* Read hour with Nack */
I2C_Stop(); /* Stop i2C communication */
}
void RTC_Read_Calendar(char read_calendar_address)
{
I2C_Start(Device_Write_address);
I2C_Write(read_calendar_address);
I2C_Repeated_Start(Device_Read_address);
day = I2C_Read_Ack(); /* Read day */
date = I2C_Read_Ack(); /* Read date */
month = I2C_Read_Ack(); /* Read month */
year = I2C_Read_Nack(); /* Read the year with Nack */
I2C_Stop(); /* Stop i2C communication */
}
int main(void)
{
char buffer[20];
const char* days[7]= {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
UART_Init();
TWI_init_master();
sei();
I2C_Start(Device_Write_address); /* Start I2C communication with RTC */
I2C_Write(0); /* Write address to read */
I2C_Write(0x00); //sec
I2C_Write(0x00); //min /* Write address to read */
I2C_Write(0x17); //hour
I2C_Write(0x03); //tuesday
I2C_Write(0x23); //day
I2C_Write(0x09); //month
I2C_Write(0x21); //year
I2C_Stop(); /* Stop i2C communication */
while(1)
{
//TODO:: Please write your application code
RTC_Read_Clock(0);
//UART_Transmit(second);
sprintf(buffer, "\n%02x:%02x:%02x ", (hour & 0b00011111), minute, second);
UART_SendString(buffer);
RTC_Read_Calendar(3);
sprintf(buffer, "%02x/%02x/%02x %s", date, month, year,days[day-1]);
UART_SendString(buffer);
_delay_ms(1000);
}
}
uint8_t I2C_Start(char write_address) /* I2C start function */
{
uint8_t status; /* Declare variable */
TWCR0 = (1<<TWSTA)|(1<<TWEN)|(1<<TWINT); /* Enable TWI, generate start condition and clear interrupt flag */
while (!(TWCR0 & (1<<TWINT))); /* Wait until TWI finish its current job (start condition) */
status = TWSR0 & 0xF8; /* Read TWI status register with masking lower three bits */
if (status != 0x08) /* Check weather start condition transmitted successfully or not? */
return 0; /* If not then return 0 to indicate start condition fail */
TWDR0 = write_address; /* If yes then write SLA+W in TWI data register */
TWCR0 = (1<<TWEN)|(1<<TWINT); /* Enable TWI and clear interrupt flag */
while (!(TWCR0 & (1<<TWINT))); /* Wait until TWI finish its current job (Write operation) */
status = TWSR0 & 0xF8; /* Read TWI status register with masking lower three bits */
if (status == 0x18) /* Check weather SLA+W transmitted & ack received or not? */
return 1; /* If yes then return 1 to indicate ack received i.e. ready to accept data byte */
if (status == 0x20) /* Check weather SLA+W transmitted & nack received or not? */
return 2; /* If yes then return 2 to indicate nack received i.e. device is busy */
else
return 3; /* Else return 3 to indicate SLA+W failed */
}
uint8_t I2C_Repeated_Start(char read_address) /* I2C repeated start function */
{
uint8_t status; /* Declare variable */
TWCR0 = (1<<TWSTA)|(1<<TWEN)|(1<<TWINT); /* Enable TWI, generate start condition and clear interrupt flag */
while (!(TWCR0 & (1<<TWINT))); /* Wait until TWI finish its current job (start condition) */
status = TWSR0 & 0xF8; /* Read TWI status register with masking lower three bits */
if (status != 0x10) /* Check weather repeated start condition transmitted successfully or not? */
return 0; /* If no then return 0 to indicate repeated start condition fail */
TWDR0 = read_address; /* If yes then write SLA+R in TWI data register */
TWCR0 = (1<<TWEN)|(1<<TWINT); /* Enable TWI and clear interrupt flag */
while (!(TWCR0 & (1<<TWINT))); /* Wait until TWI finish its current job (Write operation) */
status = TWSR0 & 0xF8; /* Read TWI status register with masking lower three bits */
if (status == 0x40) /* Check weather SLA+R transmitted & ack received or not? */
return 1; /* If yes then return 1 to indicate ack received */
if (status == 0x20) /* Check weather SLA+R transmitted & nack received or not? */
return 2; /* If yes then return 2 to indicate nack received i.e. device is busy */
else
return 3; /* Else return 3 to indicate SLA+W failed */
}
void I2C_Stop() /* I2C stop function */
{
TWCR0=(1<<TWSTO)|(1<<TWINT)|(1<<TWEN); /* Enable TWI, generate stop condition and clear interrupt flag */
while(TWCR0 & (1<<TWSTO)); /* Wait until stop condition execution */
}
void I2C_Start_Wait(char write_address) /* I2C start wait function */
{
uint8_t status; /* Declare variable */
while (1)
{
TWCR0 = (1<<TWSTA)|(1<<TWEN)|(1<<TWINT); /* Enable TWI, generate start condition and clear interrupt flag */
while (!(TWCR0 & (1<<TWINT))); /* Wait until TWI finish its current job (start condition) */
status = TWSR0 & 0xF8; /* Read TWI status register with masking lower three bits */
if (status != 0x08) /* Check weather start condition transmitted successfully or not? */
continue; /* If no then continue with start loop again */
TWDR0 = write_address; /* If yes then write SLA+W in TWI data register */
TWCR0 = (1<<TWEN)|(1<<TWINT); /* Enable TWI and clear interrupt flag */
while (!(TWCR0 & (1<<TWINT))); /* Wait until TWI finish its current job (Write operation) */
status = TWSR0 & 0xF8; /* Read TWI status register with masking lower three bits */
if (status != 0x18 ) /* Check weather SLA+W transmitted & ack received or not? */
{
I2C_Stop(); /* If not then generate stop condition */
continue; /* continue with start loop again */
}
break; /* If yes then break loop */
}
}
uint8_t I2C_Write(char data) /* I2C write function */
{
uint8_t status; /* Declare variable */
TWDR0 = data; /* Copy data in TWI data register */
TWCR0 = (1<<TWEN)|(1<<TWINT); /* Enable TWI and clear interrupt flag */
while (!(TWCR0 & (1<<TWINT))); /* Wait until TWI finish its current job (Write operation) */
status = TWSR0 & 0xF8; /* Read TWI status register with masking lower three bits */
if (status == 0x28) /* Check weather data transmitted & ack received or not? */
return 0; /* If yes then return 0 to indicate ack received */
if (status == 0x30) /* Check weather data transmitted & nack received or not? */
return 1; /* If yes then return 1 to indicate nack received */
else
return 2; /* Else return 2 to indicate data transmission failed */
}
int I2C_Read_Ack() /* I2C read ack function */
{
TWCR0=(1<<TWEN)|(1<<TWINT)|(1<<TWEA); /* Enable TWI, generation of ack and clear interrupt flag */
while (!(TWCR0 & (1<<TWINT))); /* Wait until TWI finish its current job (read operation) */
return TWDR0; /* Return received data */
}
int I2C_Read_Nack() /* I2C read nack function */
{
TWCR0=(1<<TWEN)|(1<<TWINT); /* Enable TWI and clear interrupt flag */
while (!(TWCR0 & (1<<TWINT))); /* Wait until TWI finish its current job (read operation) */
return TWDR0; /* Return received data */
}
When you enable the communication using the UART. You have the flexibility to either use the Polling or Interrupt method to continue with your programming.
Polling halts the execution of the program and waits for the UART peripheral to receive something so that program execution must continue. But it eats a lot of the computing time.
So, Interrupt Service Routine is written and implemented such the program execution does not stop. It will stop when there is an interrupt and when there is data in the UDR0 register of UART. Then the ISR will execute and then transfer the control to the main program. Which saves a lot of computing time.
you have to add an interrupt library in your program.
#include <avr/interrupt.h>
Then you need to enable the Global interrupt flag.
.
.
.
int main()
{
.
.
.
sei(); // This is Set Enable Interryupt
while(1)
{
// This is your application code.
}
}
Then you need to enable the UART receive complete interrupt. by setting ‘1’ to RXCIE0 bit of USCR0B register.
Write the ISR function which takes “USART0_RX_vect” as the argument.
The above code shows you how to implement UART receive complete ISR. It is not a full initialisation code. You still have to write the UBRR and the frame control to enable the uart peripheral.
ATmega328PB is a new semiconductor microcontroller from Microchip semiconductors. I have used its previous generation which is ATmega328 and ATmega328P. They were usually found on Arduino Uno and Arduino nano.
This new IC has a temperature sensor built into it. Which is handy for measuring the die temperature. Which can make device stable in high-temperature design. It is not accurate as a dedicated temperature sensor. But it gives you a rough idea. Using this you can the processes.