09 - AVR Programming in C

Updated 4 Oct 2026

#Summarized

Introduction to C Programming

Basic C Program Structure

C programs follow a specific structure that's case-sensitive (all C instructions must be in lowercase).

  • Pre-processor Directives:
    • #include statements link your program with necessary header files
    • Similar to Swift's import statements for frameworks
    • Example: #include <stdio.h> includes standard input/output functions
  • Header Files:
    • Collections of pre-built functions you can use in your program
    • All header files have .h extension
    • Like Swift frameworks that provide ready-to-use functionality
  • main() Function:
    • Required in every C program
    • All executable code goes inside this function
    • Return type is typically int
    • Similar to @main attribute in Swift for app entry point
  • Return Statement:
    • return 0; marks the end of a C program
    • Indicates successful program execution
  • Comments:
    • Single-line comments begin with //
    • Just like in Swift
  • Statement Terminator:
    • Each statement must end with a semicolon (;)

C Keywords

Keywords are reserved words with special meaning in C. They cannot be used as variable names. There are 32 keywords in C language.

Identifiers in C

Identifiers are names given to variables, constants, functions, and user-defined data.

Rules for Identifiers:

  1. Can only contain alphanumeric characters (a-z, A-Z, 0-9) and underscore _
  2. First character must be a letter or underscore
  3. Case-sensitive (similar to Swift)
  4. Cannot use keywords as identifiers
  5. No special characters allowed

C Operators

C has various operators for mathematical and logical operations. These are organized into categories:

Arithmetic Operators:

OperatorDescriptionExample
+Additionc = a + b;
-Subtractionc = a - b;
*Multiplicationc = a * b;
/Divisionc = a / b;
%Modulus (remainder)c = a % b;
++Increment by 1c = a++;
--Decrement by 1c = a--;

Relational Operators:

OperatorDescriptionExample
==Equal toa == b
!=Not equal toa != b
>Greater thana > b
<Less thana < b
>=Greater than or equal toa >= b
<=Less than or equal toa <= b

Logical Operators:

OperatorDescriptionExample
&&Logical AND(a > 2) && (b < 10)
|Logical OR(a > 2) | (b < 10)
!Logical NOT!(a > 2)

Bitwise Operators:

  • A=0b01110010, B=0b11001010
  • use often in AVR programing in C
OperatorDescriptionExampleResult
&Bitwise ANDA & B0b01000010
|Bitwise ORA | B0b11111010
^Bitwise XORA ^ B0b10111000
~Bitwise NOT~A0b10001101
<<Left shiftA << 20b11001000
>>Right shiftA >> 20b00011100

Assignment Operators:

Arithmetic Assignment:

OperatorDescriptionExample
=Simple assignmenta = b;
+=Add and assigna += b; (same as a = a + b;)
-=Subtract and assigna -= b; (same as a = a - b;)
*=Multiply and assigna *= b; (same as a = a * b;)
/=Divide and assigna /= b; (same as a = a / b;)
%=Modulus and assigna %= b; (same as a = a % b;)

Logic Assignment:

OperatorDescriptionExample
&=AND and assignA &= B; (same as A = A & B;)
|=OR and assignA |= B; (same as A = A | B;)
^=XOR and assignA ^= B; (same as A = A ^ B;)

Data Types in C

The data type determines what kind of values a variable can store. In C, every variable must be declared before use.

Integer Data Types

Variables of this type store whole numbers (no decimals). For example:

int x;
unsigned int y;
Data TypeSize in BytesData Range
int or signed int2-32,768 to 32,767
unsigned int2 (16 bits)0 to 65,535 (2162^{16})
short int or signed short int1-128 to 127
unsigned short int10 to 255
long int or signed long int4-2,147,483,648 to 2,147,483,647
unsigned long int40 to 4,294,967,295

Floating Point Data Types

  • These store numbers with decimal points (real numbers). For example:
float x;
long double y;
Data TypeSize in BytesData Range
float43.4E-38 to 3.4E+38
double81.7E-308 to 1.7E+308
long double103.4E-4932 to 1.1E+4932

Character Data Types

  • These store a single character or small number. For example:
char x;
unsigned char y;
Data TypeSize in BytesData Range
char or signed char1-128 to 127
unsigned char10 to 255

Void Data Type

This indicates "no value" and is typically used for functions that don't return anything:

void MyFunc() {
    a = b + 1;
}

Decision Making in C

  • Decision making allows the program to execute different code based on conditions.
  • The most common form is the if...else statement:
// Syntax
if (condition1) {
    statement block1;
} else if (condition2) {
    statement block2;
} else if (condition3) {
    statement block3;
} else {
    default statements;
}
// Example
#include <stdio.h>
void main() {
    int a = 2;
    if (a == 1) {
        printf("A is small");
    } else if (a == 2) {
        printf("A is medium");
    } else {
        printf("A is large");
    }
}

Loops in C

Loops allow you to execute a block of code multiple times. C has three main types of loops:

While Loop

The while loop checks a condition first, then executes the code if the condition is true:

// Syntax
while (condition) {
    a set of statements;
}
// Example
#include <stdio.h>
void main() {
    int x = 1;
    while (x <= 10) {
        printf("%d\t", x);
        x++;
    }
}

For Loop

  • The for loop combines initialization, condition checking, and increment/decrement in one statement:
    • Variable initialization (e.g., x = 0;)
    • Condition (e.g. while (x <= 10)) – repeat the loop when the condition is true.
    • Variable increment or decrement ( x++ or x-- or x = x + 2)
// Syntax
for (initialization; condition; increment/decrement) {
    a set of statements;
}
// Example
#include <stdio.h>
void main() {
    int x;
    for (x = 1; x <= 10; x++) {
        printf("%d\t", x);
    }
}

Do-While Loop

The do-while loop executes the code block first, then checks the condition:

// Syntax
do {
    a set of statements;
} while (condition);
// Example
#include <stdio.h>
void main() {
    int a, i;
    a = 5;
    i = 1;
    do {
        printf("%d\t", a*i);
        i++;
    } while (i <= 10);
}

Important: The do-while loop always executes at least once, even if the condition is initially false!


Basic C Commands in AVR Programming

AVR Programming in C

AVR-Specific Headers

#include <avr/io.h>

With this header file, all the AVR registers are automatically recognized in your code:

  • You can use them directly without declaration (DDRB, PINC, PORTD, TCNT0, etc.)
  • This is similar to how importing UIKit in Swift gives you immediate access to all UI components

I/O Pin Programming

With #include <avr/io.h>, you can access the three key I/O registers
for each port (B, C, D):

  • DDRx: Data Direction Register (configures pins as input or output)
  • PINx: Input register (reads values from pins)
  • PORTx: Output register (sends values to pins)

Where x is B, C, or D (depending on which port you're working with).

Setting Pins as Inputs

DDRB = 0x00;   // All pins in Port B are inputs
PORTB = 0xFF;  // Enable pull-up resistors

After this configuration, PINB will contain the data read from Port B pins.

Setting Pins as Outputs

DDRB = 0xFF;   // All pins in Port B are outputs

After this configuration, data written to PORTB will be sent to the physical pins.

  • Setting or reading the values of these registers follows the C programming rule (similar to other high-level programming languages).

Example: Reading and Writing I/O

This program reads values from Port B (configured as inputs) and sends them to Port D (configured as outputs):

#include <avr/io.h>
 
int main(void) {
    DDRB = 0x00;  // Port B pins as inputs
    PORTB = 0xFF; // Enable pull-up resistors
    DDRD = 0xFF;  // Port D pins as outputs
    
    while(1) {    // Infinite loop
        PORTD = PINB;  // Read from B, write to D
    } 
    return 0;
}

Time Delay Functions

To create precise time delays, you need two components:

#define F_CPU 16000000UL  // Define CPU frequency (16 MHz for Arduino UNO)
#include <util/delay.h>   // Include delay functions
  • Important: You must define F_CPU before including the delay header!
  • After including these, you can use two key functions:
CommandExplanation
_delay_us(x);Delay for x microseconds
_delay_ms(x);Delay for x milliseconds

The following C program will toggle (on/off) the pins D every 1 second.

#define F_CPU 16000000UL 
#include <util/delay.h>
#include <avr/io.h>
 
int main(void) {
    DDRD = 0xFF;  // Port D as output
    
    while(1) {
        PORTD = 0xFF;      // Turn all LEDs on
        _delay_ms(1000);   // Wait 1 second
        PORTD = 0x00;      // Turn all LEDs off
        _delay_ms(1000);   // Wait 1 second
    }
    return 0;
}
  • อันนี้แค่ On/Off LED for 1 second.

Data Serialization in C

  • Serialization means sending data one bit at a time through a single pin. Bit-shifting operators are essential for this:

CommandMeaningResult (binary)
0b00010000>>3Shift right 3 times0b00000010
0xF1<<1Shift left 1 time0b11100010
8>>4Shift right 4 times0b00000000

Bit Manipulation in C

When working with AVR microcontrollers, you often need to modify just a single bit in a register. C provides efficient ways to do this:

Setting a Single Bit (Make it 1)

PORTB |= (1 << n)  // Sets bit n of PORTB to 1

Clearing a Single Bit (Make it 0)

PORTB &= ~(1 << n)  // Clears bit n of PORTB to 0

Toggling a Single Bit (Flip it)

PORTB ^= (1 << n)  // Toggles bit n of PORTB

Where n is the bit position (0-7) you want to manipulate.

อันนี้หมายถึงว่าอยากเปลี่ยนแค่ Bit เดียวใน C มันต้องทำถึง 3 ขั้นตอนเลยนะ เยอะมาก555 อยาก Manipulate Bit 3 ก็ให้ n = 3— n คือที่ Bit that you want to do~

Example: Single Bit Manipulation

This program configures PB5 as an input and PC7 as an output, then reads from PB5 and controls PC7 accordingly:

#include <avr/io.h>
 
int main(void) {
    DDRD &= ~(1<<5);  // Configure bit 5 of Port B as input
    DDRC |= (1<<7);   // Configure bit 7 of Port C as output
    
    while(1) {
        if(PINB & (1<<5))
            PORTC |= (1<<7);    // If PB5 is high, set PC7 high
        else
            PORTC &= ~(1<<7);   // If PB5 is low, set PC7 low
    }
    return 0;
}

📄 Lab 8 - 01 Set+Clear.pdf


AVR Timer Programming in C

Timer Programming in C

  • AVR microcontrollers have powerful built-in timer/counter modules that can be programmed for various timing applications.

Key Timer0 Registers

  • TCNT0: Timer/Counter Register (holds the actual count value)
  • TCCR0A/TCCR0B: Timer/Counter Control Registers (configure the timer's mode)
  • OCR0A/OCR0B: Output Compare Registers (used for compare modes)
  • TIFR0: Timer Interrupt Flag Register (contains status flags)

Important Timer Flags

  • TOV0: Timer Overflow Flag (set when timer overflows)
  • OCF0A/OCF0B: Output Compare Match Flags (set when timer matches OCR values)

Example: Normal Mode with no Pre-scaling Frequency

The following program will toggle the bits of PORTB with some delay by
using Timer0. Assume the crystal frequency is 16MHz.

#include <avr/io.h>
 
void T0Delay();
int main() {
	DDRB = 0xFF;
	
	while(1) {
		PORTB = 0x55;
		T0Delay();
		PORTB = 0xAA;
		T0Delay();
	}
}

Example: Using Timer0 in C

void T0Delay() {
	TCNT0 = 0x20; // load TCNT0
	TCCR0A = 0;
	TCCR0B = 0x01; 
	// Timer0, Normal Mode, No prescaler
	while((TIFR0 & (1<<TOV0)) == 0); 
	// wait for Timer0 to roll over
	TCCR0B = 0;
	TIFR0 = 1<<TOV0; // Clear TOV0
}

Computing Time Delay

In C programming for AVR, time delays are calculated by:

  • Number of clock pulses = 1 + 0xFF - 0x20 = 224 pulses
  • Delay = Number of pulses × Clock period
    • The clock-pulse time duration = 116 MHz=0.0625 μs\frac{1}{16\text{ MHz}}=0.0625\text{ }\mu s
    • The time delay is equal to 24×0.0625 μs=14 μs24\times 0.0625\text{ }\mu s=14\text{ }\mu s

AVR Interrupt Programming in C

Steps of Interrupt Programming in C

  1. Include the interrupt header file:
    #include <avr/interrupt.h>
  2. Enable specific interrupt flag(s):
    • For Timer0 interrupt: Set TOIE0 in TIMSK0 register
    • For external interrupt INT0: Set INT0 in EIMSK register
  3. Enable/disable global interrupts:
    • Enable all interrupts: sei();
    • Disable all interrupts: cli();
  4. Define Interrupt Service Routine (ISR):
    ISR(interrupt_vector_name) {
        // Code to execute when interrupt occurs
    }
  • When the interrupt happens, the microcontroller will go to execute the ISR.
  • The ISR consists of a set of commands to do a task(s) when the interrupt happens.
  • Defining the ISR in C is shown in the next page.

Define the interrupt service routine (ISR) in C

  • The ISR has the following structure.

    ISR(interrupt vector name) {
    	// our program
    }
  • The term interrupt vector name specifies the interrupt vector name (shown in the table on the next two pages, right-hand side); when this interrupt happens, the commands in this ISR will be executed.

  • For example, here is the ISR of TIMER0_OVF_vect; when the Timer0 overflow happens (i.e., TOV = 1), the microcontroller will execute this ISR.

    ISR  (TIMER0_OVF_vect) {
    	//our C commands;
    }

A List of Interrupt Vector Names

Example: Using Timer0 Interrupt

This program uses Timer0 to toggle pin PB5 every 2μs via interrupt, while simultaneously copying data from Port C to Port D in the main loop:

#include <avr/io.h>
#include <avr/interrupt.h>
 
ISR(TIMER0_OVF_vect) {    // ISR for Timer0 overflow
    TCNT0 = -32;          // Reload timer for next 2μs
    PORTB ^= (1<<5);      // Toggle pin PB5
}
 
int main() {
    DDRB |= (1<<5);       // Set PB5 as output
    
    TCNT0 = -32;          // Initial timer value for 2μs
    TCCR0A = 0x00;        // Normal mode
    TCCR0B = 0x01;        // No prescaler
    
    TIMSK0 = (1<<TOIE0);  // Enable Timer0 overflow interrupt
    sei();                // Enable global interrupts
    
    DDRC = 0x00;          // Set Port C as input
    PORTC = 0xFF;         // Enable pull-up resistors
    DDRD = 0xFF;          // Set Port D as output
    
    while(1) {            // Main loop
        PORTD = PINC;     // Copy Port C inputs to Port D outputs
    }
}

📄 Lab 8 - 02 Timer Interrupt.pdf

Example: Using the External Interrupt INT0

This program toggles pin PB5 whenever the INT0 pin (PD2) detects a falling edge (e.g., when a button is pressed), concurrently, the main task of this program is to repeat the while loop.:

#include <avr/io.h>
#include <avr/interrupt.h>
 
int main() {
    DDRB = 1<<5;          // Set PB5 as output
    PORTD = 1<<2;         // Enable pull-up on PD2 (INT0 pin)
    EICRA = 0x2;          // Configure INT0 for falling edge
    
    EIMSK = (1<<INT0);    // Enable external interrupt INT0
    sei();                // Enable global interrupts
    
    while(1);             // Wait here (main loop does nothing)
}
 
ISR(INT0_vect) {          // ISR for external interrupt INT0
    PORTB ^= (1<<5);      // Toggle PB5
}

📄 Lab 8 - 03 External Interrupt.pdf

Pin change ก็แค่เปลี่ยน INT0_vect เป็น PCINT0_vect — ในภาษา C แค่ชื่อของ Interrupt เท่านั้นนะ ที่สำคัญ แต่ถ้าเป็นใน Assembly เป็น Address

📄 Lab 8 - 04 Pin Change Interrupt.pdf