Czy wolisz polską wersję strony elektroda?
Nie, dziękuję Przekieruj mnie tamHow to program Arduino with C language
You can program an Arduino with C in two practical ways:
Arduino IDE method — easiest: write C-style code using setup() and loop().
Arduino sketches are compiled as C++, but most normal C syntax works.
Bare-metal C method — more advanced: write pure C for the microcontroller, compile with avr-gcc, and upload with avrdude.
This is closest to “real embedded C” programming.
For most beginners, start with the Arduino IDE. If you want pure C and direct register control, use the AVR toolchain.
The normal Arduino programming environment uses files like:
void setup() {
}
void loop() {
}
This looks like C, but technically Arduino sketches are compiled as C++. The Arduino framework provides functions such as:
pinMode()
digitalWrite()
digitalRead()
delay()
analogRead()
Serial.print()
So when people say “Arduino C”, they usually mean C/C++ with Arduino libraries.
However, the microcontroller on boards like the Arduino Uno is an ATmega328P, and it can be programmed directly in standard embedded C using registers such as:
DDRB
PORTB
PINB
TCCR1A
ADMUX
UCSR0B
That is called bare-metal C programming.
This is the simplest method.
Open the Arduino IDE and write:
int ledPin = 13;
void setup(void) {
pinMode(ledPin, OUTPUT);
}
void loop(void) {
digitalWrite(ledPin, HIGH);
delay(500);
digitalWrite(ledPin, LOW);
delay(500);
}
This is not “pure C” internally because the Arduino core is C++, but the syntax is very close to C.
You can use normal C features:
typedef struct {
int pin;
unsigned long onTime;
unsigned long offTime;
} LedConfig;
LedConfig led = {13, 500, 500};
void setup(void) {
pinMode(led.pin, OUTPUT);
}
void loop(void) {
digitalWrite(led.pin, HIGH);
delay(led.onTime);
digitalWrite(led.pin, LOW);
delay(led.offTime);
}
This approach is good if you want to learn:
while still using Arduino’s easy upload and serial monitor tools.
You can also split your project into .c and .h files.
For example:
blink_c.h#ifndef BLINK_C_H
#define BLINK_C_H
#ifdef __cplusplus
extern "C" {
#endif
void led_on(void);
void led_off(void);
void led_init(void);
#ifdef __cplusplus
}
#endif
#endif
blink_c.c#include <avr/io.h>
#include "blink_c.h"
void led_init(void) {
DDRB |= (1 << PB5); // PB5 = Arduino Uno digital pin 13
}
void led_on(void) {
PORTB |= (1 << PB5);
}
void led_off(void) {
PORTB &= ~(1 << PB5);
}
.ino#include "blink_c.h"
void setup(void) {
led_init();
}
void loop(void) {
led_on();
delay(500);
led_off();
delay(500);
}
This is a useful hybrid approach:
Note: the .ino file is compiled as C++, while .c files are compiled as C. The extern "C" wrapper in the header prevents C++ name-mangling problems.
If you want to program the Arduino Uno like a normal AVR development board, you can avoid the Arduino framework completely.
You need:
avr-gcc — AVR C compileravr-libc — AVR C standard libraryavr-objcopy — converts output to HEX formatavrdude — uploads HEX file to the ArduinoThis method is common in professional embedded development because it gives you full control over the hardware.
Create a file named:
main.c
Put this code inside:
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
int main(void) {
// Arduino Uno LED on digital pin 13 is connected to PB5
DDRB |= (1 << PB5); // Set PB5 as output
while (1) {
PORTB |= (1 << PB5); // LED ON
_delay_ms(500);
PORTB &= ~(1 << PB5); // LED OFF
_delay_ms(500);
}
return 0;
}
Explanation:
| Code | Meaning |
|---|---|
DDRB |
Data Direction Register for Port B |
PORTB |
Output register for Port B |
PB5 |
Bit 5 of Port B, connected to Arduino Uno pin 13 |
DDRB |= (1 << PB5) |
Configure PB5 as output |
PORTB |= (1 << PB5) |
Set PB5 high |
PORTB &= ~(1 << PB5) |
Set PB5 low |
_delay_ms(500) |
Delay for 500 ms |
For Arduino Uno / ATmega328P:
avr-gcc -Wall -Os -DF_CPU=16000000UL -mmcu=atmega328p -c main.c -o main.o
Then link it:
avr-gcc -mmcu=atmega328p main.o -o main.elf
Convert it to HEX:
avr-objcopy -O ihex -R .eeprom main.elf main.hex
The main.hex file is the firmware image that will be uploaded to the Arduino.
On Linux, the port may be:
/dev/ttyACM0
On macOS, something like:
/dev/tty.usbmodemXXXX
On Windows, something like:
COM3
Example upload command:
avrdude -c arduino -p m328p -P COM3 -b 115200 -U flash:w:main.hex:i
Change COM3 to your actual serial port.
For older Arduino Nano bootloaders, you may need:
-b 57600
instead of:
-b 115200
Use the Arduino IDE first:
void setup(void) {
pinMode(13, OUTPUT);
}
void loop(void) {
digitalWrite(13, HIGH);
delay(500);
digitalWrite(13, LOW);
delay(500);
}
Then gradually replace Arduino functions with register-level code.
For example, instead of:
pinMode(13, OUTPUT);
digitalWrite(13, HIGH);
you can write:
DDRB |= (1 << PB5);
PORTB |= (1 << PB5);
This teaches you how the microcontroller really works.
Use:
main.c
avr-gcc
avr-libc
avrdude
Makefile
This gives you more control over:
To avoid typing long commands every time, create a Makefile:
MCU = atmega328p
F_CPU = 16000000UL
PORT = COM3
BAUD = 115200
TARGET = main
CC = avr-gcc
OBJCOPY = avr-objcopy
CFLAGS = -Wall -Os -DF_CPU=$(F_CPU) -mmcu=$(MCU)
all: $(TARGET).hex
$(TARGET).o: $(TARGET).c
$(CC) $(CFLAGS) -c $(TARGET).c -o $(TARGET).o
$(TARGET).elf: $(TARGET).o
$(CC) -mmcu=$(MCU) $(TARGET).o -o $(TARGET).elf
$(TARGET).hex: $(TARGET).elf
$(OBJCOPY) -O ihex -R .eeprom $(TARGET).elf $(TARGET).hex
upload: $(TARGET).hex
avrdude -c arduino -p m328p -P $(PORT) -b $(BAUD) -U flash:w:$(TARGET).hex:i
clean:
rm -f *.o *.elf *.hex
Then use:
make
make upload
For modern Arduino development, common workflows are:
| Workflow | Best for |
|---|---|
| Arduino IDE | Beginners, quick experiments |
| Arduino CLI | Scripted builds, automation |
| VS Code + PlatformIO | Larger projects, professional workflow |
Bare-metal avr-gcc |
Learning low-level embedded C |
| Microchip Studio | AVR development on Windows |
For serious embedded work, many engineers prefer:
For 8-bit AVR boards such as Uno and Nano, bare-metal C is still very useful educationally. For newer boards based on ARM, ESP32, or RP2040, the toolchain changes, but the embedded C principles remain similar.
On the Arduino Uno:
Arduino digital pin 13 = ATmega328P pin PB5
So this Arduino code:
digitalWrite(13, HIGH);
roughly corresponds to:
PORTB |= (1 << PB5);
And this:
pinMode(13, OUTPUT);
roughly corresponds to:
DDRB |= (1 << PB5);
The register method is faster and smaller, but less portable.
For example:
digitalWrite(13, HIGH);
works on many Arduino boards.
But:
PORTB |= (1 << PB5);
is specific to AVR chips where PB5 exists and is connected to the LED.
main() in the .ino sketch unless you know how the build system links the Arduino core. The Arduino core already provides main() and calls setup() and loop().To program Arduino with C:
setup() and loop()..c and .h files to your Arduino project.avr-gcc, avr-libc, and avrdude.DDRB and PORTB.