Arduino 与 ATmega328P 入门笔记
Uno/Nano 常用功能速查
Arduino Uno 与经典 Nano 常以 ATmega328P 为核心。它们适合作为学习微控制器的入门平台:开发板把供电、时钟、复位和 USB 串口下载等电路集成起来,Arduino IDE 则提供了简化的程序框架和常用外设接口。
本文以 16 MHz、5 V 的经典 ATmega328P Uno/Nano 为例,整理开发板规格、GPIO、PWM、ADC、中断、串口,以及 OLED、DHT11 和 WS2812 的常用操作。不同版本的开发板和模块可能存在引脚、供电或库配置差异,接线前应以具体板卡资料为准。
1. 开发板与芯片概览
ATmega328P 是 8 位 AVR 微控制器。常见的经典 Uno 与 Nano 开发板主要规格如下:
| 项目 | 经典 Uno/Nano 常见规格 |
|---|---|
| 工作电压 | 5 V |
| 时钟频率 | 16 MHz |
| 数字 I/O | 14 路(D0–D13) |
| 模拟输入 | Uno:A0–A5;经典 Nano 通常还引出 A6、A7(仅模拟输入) |
| PWM 输出 | D3、D5、D6、D9、D10、D11 |
| 硬件 UART | 1 路(D0/RX、D1/TX) |
| ADC | 10 位,读数通常为 0–1023 |
以上是常见板卡的概览,不代表所有采用 ATmega328P 的兼容板都完全相同。尤其要注意:Nano 的 A6/A7 通常不能作为数字 I/O 使用;D0、D1 与 USB 串口共用,连接其他电路可能影响下载和串口通信。


编译与下载
在 Arduino IDE 中选择对应的开发板型号和串口端口,编译后通过 USB 下载程序。不同 Nano 克隆板使用的 USB 转串口芯片和 bootloader 可能不同;若上传失败,检查板型、处理器/bootloader 选项、端口和数据线,并参考板卡说明。


2. Arduino 程序的基本结构
Arduino 草图通常包含 setup() 和 loop():setup() 在上电或复位后执行一次,用于初始化;loop() 随后不断重复执行。
1void setup() {
2 // 初始化代码:配置引脚、串口、传感器等
3}
4
5void loop() {
6 // 主循环:反复执行任务
7}
3. GPIO:数字输入与输出
常用函数
1pinMode(pin, mode); // 配置 INPUT、OUTPUT 或 INPUT_PULLUP
2 digitalWrite(pin, HIGH); // 输出高电平;也可写 LOW
3int state = digitalRead(pin); // 读取 HIGH 或 LOW
INPUT_PULLUP 会启用芯片内部上拉电阻。使用它连接按钮时,常见接法是按钮一端接输入引脚、另一端接 GND:未按下读到 HIGH,按下读到 LOW。外部电路仍需遵守芯片引脚电压和电流限制。
LED 闪烁
下面使用 D13(经典 Uno/Nano 的板载 LED 通常连接在该引脚;也可使用限流电阻和外接 LED)。不要把 D0、D1 当作普通 LED 引脚,以免干扰串口。
1const int ledPin = 13;
2
3void setup() {
4 pinMode(ledPin, OUTPUT);
5}
6
7void loop() {
8 digitalWrite(ledPin, HIGH);
9 delay(1000);
10 digitalWrite(ledPin, LOW);
11 delay(1000);
12}
按钮控制 LED
1const int buttonPin = 2;
2const int ledPin = 13;
3
4void setup() {
5 pinMode(buttonPin, INPUT_PULLUP);
6 pinMode(ledPin, OUTPUT);
7}
8
9void loop() {
10 // 内部上拉接法:按下为 LOW
11 const bool pressed = (digitalRead(buttonPin) == LOW);
12 digitalWrite(ledPin, pressed ? HIGH : LOW);
13}
机械按钮会抖动;简单状态控制通常可以先直接读取,若需要可靠的单次按键事件或精确计数,应增加软件消抖或硬件消抖。
时间函数
1delay(1000); // 阻塞等待 1000 ms
2unsigned long ms = millis(); // Arduino 启动以来经过的毫秒数
3unsigned long us = micros(); // Arduino 启动以来经过的微秒数
millis() 返回无符号计数值,约 49.7 天后会回绕;micros() 也会回绕(在经典 16 MHz AVR 板上约每 70 分钟)。用无符号差值判断时间间隔可安全处理回绕:
1if ((unsigned long)(millis() - previousMs) >= intervalMs) {
2 previousMs = millis();
3 // 执行周期任务
4}
4. PWM:模拟效果输出
ATmega328P 的指定引脚可通过 analogWrite() 输出 PWM(脉宽调制)信号。经典 Uno/Nano 的 PWM 引脚为 D3、D5、D6、D9、D10、D11,常用取值为 0–255:0 对应持续低电平,255 对应持续高电平,中间值表示不同占空比。它不是 DAC,不会直接产生稳定的中间模拟电压。
1const int pwmPin = 9;
2
3void setup() {
4 pinMode(pwmPin, OUTPUT);
5 analogWrite(pwmPin, 128); // 约 50% 占空比
6}
7
8void loop() {
9}
经典 Uno 上 D9 的 PWM 频率约为 490 Hz,具体频率因引脚和定时器配置而异。若程序或库重新配置了相关定时器,频率也可能改变。可用示波器或逻辑分析仪在引脚与 GND 间测量。

5. ADC:读取模拟量
analogRead(pin) 将模拟输入转换为数字读数。在经典 Uno/Nano 的默认配置下,ADC 为 10 位,读数范围是 0–1023。可测电压范围由模拟参考电压决定;默认参考通常与板卡的 DEFAULT 参考相关,不能不加确认地假设一定精确为 5.000 V。输入电压不得超出芯片和开发板允许范围。
1const int analogPin = A0;
2
3void setup() {
4 Serial.begin(9600);
5}
6
7void loop() {
8 const int rawValue = analogRead(analogPin);
9
10 // 示例假定实际参考电压为 5.0 V;更准确换算应使用实测 Vref
11 const float referenceVoltage = 5.0f;
12 const float voltage = rawValue * (referenceVoltage / 1023.0f);
13
14 Serial.print("ADC: ");
15 Serial.print(rawValue);
16 Serial.print(" | Voltage: ");
17 Serial.print(voltage, 3);
18 Serial.println(" V");
19
20 delay(200);
21}
换算公式为:
其中 N 是 ADC 读数,Vref 是实际参考电压。实际精度还受参考源误差、噪声、输入源阻抗和 ADC 特性影响。

6. 外部中断
经典 Uno/Nano 的 ATmega328P 通常在 D2、D3 提供外部中断,对应 Arduino 中断号 0、1。推荐用 digitalPinToInterrupt(pin) 将引脚号转换为中断号:
1attachInterrupt(digitalPinToInterrupt(pin), isr, mode);
常见触发模式包括 LOW、CHANGE、RISING 和 FALLING。电平触发的 LOW 在引脚保持低电平时可能持续进入中断,按钮场景通常考虑边沿触发。
中断服务函数(ISR)应尽量短:不要在其中调用 delay()、做串口输出或执行耗时操作。与主循环共享的变量通常声明为 volatile;ISR 中只设置标志,实际工作放到 loop() 中完成。
1const int buttonPin = 2;
2const int ledPin = 13;
3
4volatile bool buttonPressed = false;
5bool ledState = false;
6
7void handleInterrupt() {
8 buttonPressed = true;
9}
10
11void setup() {
12 pinMode(buttonPin, INPUT_PULLUP);
13 pinMode(ledPin, OUTPUT);
14 attachInterrupt(digitalPinToInterrupt(buttonPin), handleInterrupt, FALLING);
15}
16
17void loop() {
18 if (buttonPressed) {
19 buttonPressed = false;
20 ledState = !ledState;
21 digitalWrite(ledPin, ledState ? HIGH : LOW);
22 }
23}
volatile 只要求编译器每次从内存读取变量,并不保证复合操作或多字节访问的原子性。上例中的 bool 标志在 ATmega328P 上通常可通过简单读写使用;共享多字节数据时,需要额外考虑原子访问和临界区。按钮抖动也可能产生多个边沿,实际项目仍需消抖。
7. UART 串口
经典 Uno/Nano 的硬件 UART 使用 D0/RX 和 D1/TX,并通常经 USB 转串口芯片连接电脑。板载串口与外部设备连接时,注意 TX/RX 交叉连接、共地和电平兼容;不要让 5 V 信号直接进入不耐 5 V 的设备。
1void setup() {
2 Serial.begin(9600);
3}
4
5void loop() {
6 Serial.println("Hello, world!");
7
8 if (Serial.available() > 0) {
9 const char received = Serial.read();
10 Serial.print("Received: ");
11 Serial.println(received);
12 }
13
14 delay(1000);
15}
Serial.print()输出内容但不自动换行。Serial.println()输出内容并追加换行符。Serial.available()返回接收缓冲区中可读取的字节数。Serial.read()读取一个字节;文本协议需自行定义分隔符和完整帧的处理方式。
需要按行接收时,可以使用 Serial.readStringUntil('\n'),但该调用会等待换行或超时;对时序敏感的程序,更适合用非阻塞方式逐字节组帧。

串口监视器中的波特率应与程序的 Serial.begin() 一致。发送一行文本时,应确认监视器的行结束符设置与程序读取方式匹配。
8. I²C OLED(SSD1306)
常见 0.96 英寸 SSD1306 OLED 模块可通过 I²C 与开发板通信。经典 Uno/Nano 的硬件 I²C 引脚通常是 A4/SDA、A5/SCL;若板上标有 SDA/SCL 专用引脚,也可按板卡标注连接。模块供电电压、逻辑电平和 I²C 地址需以模块规格为准,常见地址为 0x3C,也有 0x3D。
在 Arduino IDE 的库管理器中安装 Adafruit SSD1306 和 Adafruit GFX Library。
1#include <Wire.h>
2#include <Adafruit_GFX.h>
3#include <Adafruit_SSD1306.h>
4
5#define SCREEN_WIDTH 128
6#define SCREEN_HEIGHT 64
7
8Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
9
10void setup() {
11 Serial.begin(9600);
12
13 if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
14 Serial.println(F("SSD1306 初始化失败,请检查接线和 I2C 地址。"));
15 while (true) {
16 }
17 }
18
19 display.clearDisplay();
20 display.setTextSize(1);
21 display.setTextColor(SSD1306_WHITE);
22 display.setCursor(0, 0);
23 display.println(F("Hello, world!"));
24
25 display.setTextSize(2);
26 display.setCursor(0, 16);
27 display.println(F("SSD1306"));
28 display.display(); // 将缓冲区内容写入屏幕
29}
30
31void loop() {
32}
Adafruit SSD1306 库先在 MCU 内存缓冲区绘图,调用 display.display() 后才会把内容传输到屏幕。128×64 单色帧缓冲约占 1024 字节 SRAM;ATmega328P 只有 2 KB SRAM,因此同时使用较多库或较大的缓冲区时要留意内存占用。
9. DHT11 温湿度传感器
安装 Adafruit 的 DHT sensor library(若 IDE 提示依赖,也安装 Adafruit Unified Sensor)。裸 DHT11 通常需要数据线上拉电阻;带 PCB 的三针模块可能已集成上拉电阻。供电电压应按模块规格选择。
1#include <DHT.h>
2
3#define DHTPIN 2
4#define DHTTYPE DHT11
5
6DHT dht(DHTPIN, DHTTYPE);
7
8void setup() {
9 Serial.begin(9600);
10 dht.begin();
11}
12
13void loop() {
14 delay(2000); // DHT11 采样间隔较长,遵循传感器/库要求
15
16 const float humidity = dht.readHumidity();
17 const float temperature = dht.readTemperature();
18
19 if (isnan(humidity) || isnan(temperature)) {
20 Serial.println(F("读取 DHT11 失败,请检查接线。"));
21 return;
22 }
23
24 Serial.print(F("Humidity: "));
25 Serial.print(humidity, 1);
26 Serial.print(F(" % | Temperature: "));
27 Serial.print(temperature, 1);
28 Serial.println(F(" C"));
29}
传感器读数可能因型号、模块和环境而有误差。发生 NaN 时,优先检查供电、数据引脚、上拉电阻、库依赖和采样间隔。
10. 串口绘图仪
串口绘图仪适合观察随时间变化的数值。每次采样输出一行数值,多个通道之间使用逗号或制表符分隔,并以换行结束。不要把解释性文字、单位符号混进数值数据行。
1#include <DHT.h>
2
3#define DHTPIN 2
4#define DHTTYPE DHT11
5
6DHT dht(DHTPIN, DHTTYPE);
7
8void setup() {
9 Serial.begin(9600);
10 dht.begin();
11}
12
13void loop() {
14 delay(2000);
15
16 const float humidity = dht.readHumidity();
17 const float temperature = dht.readTemperature();
18 if (isnan(humidity) || isnan(temperature)) {
19 return;
20 }
21
22 // 每行一帧;具体标签解析格式可能随 Arduino IDE 版本而异
23 Serial.print("Temperature:");
24 Serial.print(temperature, 1);
25 Serial.print(",Humidity:");
26 Serial.println(humidity, 1);
27}
绘图仪的标签语法和界面会因 Arduino IDE 版本不同而有差异。如果键值对标签没有被识别,可先改为仅输出 温度,湿度 两列数值,并查看对应 IDE 版本的串口绘图仪说明。

11. WS2812 / NeoPixel 灯带
WS2812 通过单线时序数据级联控制。MCU 的数据输出连接第一颗灯的 DIN(有些资料标为 DI),灯珠的 DOUT/DO 再连接下一颗的 DIN。还需连接共地,并按灯带规格提供足够的电源;多颗灯珠不应由 MCU GPIO 直接供电。
在库管理器中安装 Adafruit NeoPixel。以下例子假设数据线连接 D12、灯珠顺序为 GRB、时序为 800 kHz;具体芯片版本和灯带配置不同时应调整参数。
1#include <Adafruit_NeoPixel.h>
2
3#define LED_PIN 12
4#define NUM_LEDS 3
5
6Adafruit_NeoPixel pixels(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);
7
8void setup() {
9 pixels.begin();
10 pixels.setPixelColor(0, pixels.Color(80, 0, 0)); // 红
11 pixels.setPixelColor(1, pixels.Color(0, 80, 0)); // 绿
12 pixels.setPixelColor(2, pixels.Color(0, 0, 80)); // 蓝
13 pixels.show();
14}
15
16void loop() {
17}

12. 实践中的几个注意点
- 确认板型与引脚映射:Uno、Nano 及兼容板的引脚引出和 USB 转串口电路可能不同。
- 注意电源与逻辑电平:传感器、显示屏和灯带的供电范围不尽相同;共地不等于信号电平兼容。
- 为 SRAM 留余量:ATmega328P 的 SRAM 只有 2 KB。避免不必要的大型
String、过多缓冲区和动态内存分配;固定文本可考虑F()宏。 - 串口占用 D0/D1:下载或使用 USB 串口时,尽量不要让外部电路同时驱动这两个引脚。
- 中断保持简短:ISR 中只做必要标记,耗时处理放回主循环,并为机械按键考虑消抖。
- 核对模块资料:I²C 地址、OLED 分辨率、NeoPixel 色彩顺序以及供电要求都可能因模块而异。
参考资料
- Arduino 官方语言参考:查看
pinMode()、digitalRead()、analogRead()、attachInterrupt()等 API。 - Arduino Uno Rev3 官方规格:查看开发板接口和规格。
- 太极创客:Arduino 中文教程与实践资料。
- 立创开发板文档:开发板与外设参考。
- W3Cschool Arduino 教程:Arduino 入门资料。
GitHub Discussions
评论区
使用 GitHub 登录,欢迎友好交流。