Arduino 与 MicroPython 基础入门:PWM、GPIO、ADC、串口、Socket 与计时基础
- 嵌入式开发
- 13小时前
- 39热度
- 0评论
这篇笔记整理了 Arduino UNO 与 ESP32-C3 MicroPython 开发里最常用的基础知识,包括 PWM、时间函数、GPIO、ADC、多线程、串口、Socket 以及 Arduino 常见数值类型。内容尽量保持“短、准、方便查阅”,适合作为入门和速查参考。
一、PWM 输出基础:
Arduino
analogWrite()与 MicroPythonPWM
PWM(脉宽调制)通过周期性切换高低电平来调节平均输出效果,本质上不是真正的模拟电压。
核心思想(面积等效原理):在具有惯性的负载(如电机、电感、LED)上,快速开关的高频脉冲会因为惯性而“平滑”成平均值。脉冲宽度越宽,平均能量越大,等效于连续的模拟电压。
数学上,平均电压约等于:电源电压 × 占空比。

1.1 Arduino PWM 函数原型
void analogWrite(uint8_t pin, int value);
pin:要输出 PWM 的引脚编号value:占空比设定值,通常为0~255- 官方说明:在支持的引脚上输出 PWM 波
典型写法:
pinMode(pin, OUTPUT);
analogWrite(pin, value);
value范围通常为0~2550表示始终低电平255表示始终高电平128约等于 50% 占空比
支持 PWM 的引脚在开发板丝印上通常带有
~标记。
analogWrite()输出的是 PWM 波形,不是真正 DAC 模拟输出。
1.2 MicroPython PWM 构造原型
PWM 类 – 脉宽调制 — MicroPython 中文文档
machine.PWM(dest, *, freq, duty_u16, duty_ns)
dest:要绑定为 PWM 输出的引脚对象,通常是Pin对象freq:PWM 频率,单位 Hzduty_u16:16 位占空比,范围通常为0~65535,数值越细,调节越平滑duty_ns:以纳秒表示的高电平持续时间
典型写法:
from machine import Pin, PWM
pin = Pin(2, Pin.OUT)
pwm = PWM(pin, freq=1000, duty_u16=32768)
freq用于设置 PWM 频率duty_u16用于设置占空比
PWM 输出只有高电平和低电平两种状态,但某些负载或电路会对其进行“平均化”处理:
- LED 依赖人眼视觉暂留,表现为亮度变化
- 电机因机械惯性,表现为转速变化
- 经过 RC 滤波后,可得到较平滑的平均电压
1.3 PWM 常见概念:周期、频率、占空比、分辨率
- 周期
T:一个完整 PWM 循环所需时间 - 频率
f:每秒重复的次数,公式:f = 1 / T - 高频率通常更不容易让电机、LED 等负载出现明显抖动或可闻噪声
- 占空比:高电平时间占整个周期的比例,公式:
Duty = t_high / T × 100% - 例如:
f = 1kHz时,周期T = 1ms;若高电平持续0.25ms,则占空比为25% - 分辨率:占空比可调的离散档位数
- Arduino UNO 的
analogWrite()常见为 8 位,即0~255,共 256 级 - MicroPython 的
duty_u16常见为 16 位,即0~65535,共 65536 级
- Arduino UNO 的
- 分辨率越高,占空比步进越细,调光、调速通常越平滑
- PWM 生成方式通常可理解为比较器比较:一个输入是锯齿波/三角波(载波),另一个是调制信号(目标值)。当调制信号高于载波时输出高电平,否则输出低电平,从而形成不同宽度的脉冲
- Arduino UNO 默认 PWM 频率会随引脚和定时器不同而变化,常见约为 490Hz 或 980Hz
- 在很多芯片里,PWM 频率和分辨率往往互相制约:频率调得越高,可用于细分占空比的计数空间可能越小
二、时间函数与延时:
millis()、micros()、ticks_us()
时间函数的原理,本质上是读取芯片内部定时器累计的计数值。程序运行后,计数会按固定节拍不断增加,因此可以换算成毫秒或微秒来做计时;延时函数则是持续等待,直到计数达到设定时长。
2.1 Arduino 计时函数
micros() | Arduino Documentation
unsigned long millis(void);
unsigned long micros(void);
micros():返回自 Arduino 主板开始运行当前程序以来的微秒数millis():返回自主板开始运行当前程序以来的毫秒数micros()达到上限后会回绕:大约 70 分钟后溢出归零,2^32 / 1000000 / 60 ≈ 71.58分钟millis()达到上限后会回绕:约 49.7 天
循环耗时示例:
unsigned long start = micros();
// 循环中的代码
unsigned long elapsed = micros() - start;
Arduino 没有像 ticks_diff() 这样的专门函数,但通常可直接用无符号减法计算时间差,即用“当前值 - 起始值”来处理回绕后的计时。
例如:1 - 4294967290 = 7(按 2^32 取模)
static inline unsigned long ticks_diff(unsigned long t1, unsigned long t0) {
return t1 - t0;
}
// 注意传入顺序:当前时刻 - 起始时刻
- 适用于
millis()/micros()返回值的差值计算 - 依赖
unsigned long的回绕特性:无符号减法天然按 模2^32运算
2.2 MicroPython 计时函数
time – 时间相关功能 — MicroPython 中文文档
time.time()
time.ticks_ms()
time.ticks_us()
time.ticks_diff(t1, t0)
time():获取当前时间戳,表示自 EPOCH 起点开始累计的秒数- EPOCH 可理解为时间系统约定的“零点”
- 若 RTC 未正确设置,则
time()返回的可能是自上次上电、复位或其他硬件参考点开始累计的秒数 ticks_ms():返回毫秒计数,更适合程序运行计时ticks_us():返回微秒计数,更适合更精细的程序运行计时ticks_ms()/ticks_us()不是无限增大,该计数器达到上限后会回绕ticks_diff():用于处理回绕后的差值计算,因此不建议直接相减
循环耗时示例:
import time
start = time.ticks_us()
# 循环中的代码
elapsed = time.ticks_diff(time.ticks_us(), start)
2.3 Arduino 与 MicroPython 延时函数
Arduino:
delay() | Arduino Documentation
void delay(unsigned long ms);
void delayMicroseconds(unsigned int us);
delay(ms):延时指定毫秒delayMicroseconds(us):延时指定微秒
MicroPython:
time.sleep(seconds)
time.sleep_ms(ms)
time.sleep_us(us)
sleep(seconds):延时指定秒sleep_ms(ms):延时指定毫秒sleep_us(us):延时指定微秒
三、数字引脚与 GPIO:输入输出最常用的基础
GPIO(General Purpose Input/Output)即通用输入输出引脚,可用于读取高低电平,或输出高低电平控制外设。
3.1 Arduino 数字引脚函数
void pinMode(uint8_t pin, uint8_t mode);
void digitalWrite(uint8_t pin, uint8_t val);
int digitalRead(uint8_t pin);
pin:引脚编号mode:引脚模式,常用INPUT、OUTPUT、INPUT_PULLUPval:输出电平,常用LOW、HIGH
典型写法:
pinMode(13, OUTPUT);
digitalWrite(13, HIGH);
pinMode(2, INPUT_PULLUP);
int state = digitalRead(2);
digitalWrite():输出高低电平digitalRead():读取引脚电平状态
3.2 MicroPython GPIO
Pin 类 – 控制 I/O 引脚 — MicroPython 中文文档
machine.Pin(id, mode=-1, pull=-1, *, value, drive, alt)
pin.value([x])
id:引脚编号,ESP32 上通常直接写 GPIO 号mode:引脚模式,常用Pin.IN、Pin.OUTpull:上下拉设置,常用Pin.PULL_UP、Pin.PULL_DOWNvalue([x]):不带参数时读取电平,带参数时设置输出电平
典型写法:
from machine import Pin
led = Pin(2, Pin.OUT)
led.value(1)
key = Pin(4, Pin.IN, Pin.PULL_UP)
state = key.value()
- Arduino 常用板级引脚号
- MicroPython 在 ESP32 上通常直接使用 GPIO 编号
四、模拟输入 ADC:把电压读成数字
ADC(Analog to Digital Converter)用于把模拟电压转换为数字量,便于程序读取。
4.1 Arduino 模拟输入
int analogRead(uint8_t pin);
pin:模拟输入引脚,UNO 常用A0~A5- 返回值在 UNO 上通常为
0~1023 - 默认参考电压常用
5V
典型写法:
int value = analogRead(A0);
0表示接近 0V1023表示接近参考电压
4.2 MicroPython 模拟输入
ADC 类 – 模数转换 — MicroPython 中文文档
machine.ADC(id)
adc.read()
id:ADC 引脚对象或通道read():读取 ADC 数值- 在 ESP32 上常见返回范围为
0~4095
典型写法:
from machine import ADC, Pin
adc = ADC(Pin(4))
value = adc.read()
0表示输入电压较低- 较大数值表示输入电压较高
- 不同芯片的 ADC 位数和参考范围可能不同
五、多线程与并发:Arduino UNO 和 ESP32-C3 有什么不同
多线程用于让程序同时处理多个任务。需要注意:同时做多件事不一定等于真正并行执行,还可能只是快速切换执行。
5.1 Arduino UNO 的并发思路
Arduino UNO 本身没有操作系统,也没有官方原生多线程机制,通常只有一个 loop() 主循环。
- UNO 一般是单线程、单核执行
- 常见做法不是开线程,而是用
millis()+ 状态机 + 非阻塞写法来“并发”处理多个任务 - 中断可以处理突发事件,但中断不等于多线程
典型思路:
unsigned long t1 = 0;
unsigned long t2 = 0;
void loop() {
unsigned long now = millis();
if (now - t1 >= 100) {
t1 = now;
// 任务1
}
if (now - t2 >= 500) {
t2 = now;
// 任务2
}
}
- 这种方式常叫“时间片轮询”或“非阻塞任务调度”
- 对 UNO 来说,这通常比强行做多线程更实用
5.2 ESP32-C3 MicroPython 多线程
ESP32-C3 是单核芯片,但 MicroPython 中通常可以使用 _thread 模块创建线程。
import _thread
- 可以创建多个线程并发执行任务
- 由于 ESP32-C3 是单核,多个线程通常是轮流调度执行,不是真正双核并行
- 线程之间共享变量时要注意同步问题
- 不同固件版本对
_thread的支持程度可能略有差异
典型写法:
import _thread
import time
def task():
# 子线程死循环
while True:
# 打印线程状态 + 时间戳(保留2位小数)
print(f"[{time.time():.2f}] thread running")
time.sleep_ms(500)
# 启动多线程
_thread.start_new_thread(task, ())
# 主循环
while True:
# 打印主循环状态 + 时间戳
print(f"[{time.time():.2f}] main loop")
time.sleep(2)

_thread.start_new_thread(function, args):启动新线程- 更适合把网络、串口、传感器轮询等任务拆开处理
- 如果任务很简单,很多时候
uasyncio也比多线程更常用
六、串口输入输出:Arduino Serial 与 MicroPython UART
串口通信常用于与电脑、传感器、模块或其他单片机交换数据。常见操作包括:初始化波特率、发送数据、判断是否收到数据、读取数据。
6.1 Arduino 串口
Serial | Arduino Documentation
Serial.begin(baud);
Serial.print(val);
Serial.println(val);
int Serial.available();
int Serial.read();
Serial.begin(baud):初始化串口,常见如9600、115200print():发送文本或数字,不自动换行println():发送后自动换行available():返回接收缓冲区中可读取的字节数read():读取 1 个字节,没有数据时通常返回-1
典型写法:
void setup() {
Serial.begin(115200);
}
void loop() {
if (Serial.available()) {
int ch = Serial.read();
Serial.print("recv: ");
Serial.println(ch);
}
}
- Arduino UNO 上硬件串口主要是
D0(RX)/D1(TX) - 它通常和 USB 串口共用,因此下载程序、串口监视器、外设通信之间可能互相影响
6.2 MicroPython 串口
UART 类——双工串行通信总线 — MicroPython中文 1.17 文档
from machine import UART
machine.UART(id, baudrate=9600, tx=..., rx=...)
uart.write(data)
uart.any()
uart.read([n])
UART(id, baudrate=..., tx=..., rx=...):创建串口对象并指定波特率、TX、RX 引脚write(data):发送数据any():返回接收缓冲区中可读取的字节数read([n]):读取数据,不传参数时尽量读取当前全部可读数据
典型写法:
from machine import UART, Pin
uart = UART(1, baudrate=115200, tx=Pin(0), rx=Pin(1))
uart.write("hello\n")
if uart.any():
data = uart.read()
print(data)
- 在 ESP32-C3 上,串口号和默认引脚会因固件或板卡而异,实际项目里常手动指定
tx/rx read()返回的通常是bytes,如需文本可再做解码
七、MicroPython Socket 基础:联网后怎么收发数据
Socket 可理解为网络通信的“接口端点”,常用于 TCP / UDP 通信。在 MicroPython 里,通常先联网,再通过 socket 模块创建套接字。
7.1 常见导入方式
import socket
7.2 常见流程
import network
import socket
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect("WiFi名", "密码")
addr = socket.getaddrinfo("example.com", 80)[0][-1]
s = socket.socket()
s.connect(addr)
s.send(b"GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")
data = s.recv(1024)
s.close()
socket.getaddrinfo(host, port):把域名和端口解析成可连接地址socket.socket():创建 socket 对象connect(addr):连接远端主机send()/recv():发送和接收数据close():关闭连接- 如果是网页请求,常见是 TCP;如果是局域网广播、简单数据包,也可能用 UDP
7.3 常见说明
- Socket 更像底层网络接口,HTTP、MQTT 等协议通常都建立在它之上
recv(n)不一定一次返回完整数据,实际项目中常需要循环接收- 不同固件版本提供的方法可能略有差异
- 如果网络不稳定,建议配合超时、异常处理一起使用
八、Arduino UNO 常见数值类型:范围、字节大小与区别
下表以 Arduino UNO / ATmega328P 为参考,不同架构开发板(如 ESP32、ARM)上的大小可能不同。
| 类型 | 字节数 | 位数 | 常见范围 | 说明 |
|---|---|---|---|---|
bool |
1 | 8 | false / true |
布尔类型 |
char |
1 | 8 | -128 ~ 127 |
有符号字符 |
unsigned char |
1 | 8 | 0 ~ 255 |
无符号字符 |
byte |
1 | 8 | 0 ~ 255 |
Arduino 常用字节类型 |
int |
2 | 16 | -32768 ~ 32767 |
UNO 上是 16 位 |
unsigned int |
2 | 16 | 0 ~ 65535 |
无符号整型 |
word |
2 | 16 | 0 ~ 65535 |
Arduino 常用无符号 16 位 |
long |
4 | 32 | -2147483648 ~ 2147483647 |
长整型 |
unsigned long |
4 | 32 | 0 ~ 4294967295 |
常用于 millis() / micros() |
float |
4 | 32 | 约 ±3.4E38 |
单精度浮点数 |
double |
4 | 32 | 约 ±3.4E38 |
在 UNO 上通常与 float 相同 |
int在 UNO 上是 16 位,不像 32 位电脑环境里是 32 位unsigned long常用于时间计数、位运算和较大范围数值float与double在 UNO 上通常都是 4 字节
8.1 float 和 double 的区别
- 在很多电脑和高性能平台上,
float通常是 4 字节单精度,double通常是 8 字节双精度 - 但在 Arduino UNO / ATmega328P 上,
double通常与float相同,都是 4 字节 - 因此在 UNO 上使用
double,通常不会比float获得更高精度 - 如果代码要移植到 ESP32、STM32、PC 等平台,
double的大小和精度可能会不同
九、常见误区与速查结论
analogWrite()输出的是 PWM 波形,不是严格意义上的模拟电压millis()、micros()、ticks_ms()、ticks_us()都会回绕,做差值时要用正确方法- Arduino UNO 没有官方原生多线程,常见做法是 主循环 + 非阻塞调度
- MicroPython 的
UART.read()和 Socket 的recv()读到的通常是 bytes - Arduino UNO 上
double往往和float一样,别默认它一定更高精度
如果你只是想快速查用法,优先记住这几个关键词:PWM、GPIO、ADC、串口、Socket、计时回绕、float/double 差异。
如果你正在学习 Arduino UNO 或 ESP32-C3 MicroPython,那么上面这些 PWM、GPIO、ADC、串口、Socket 与计时函数,就是最值得优先掌握的一批基础能力。后续继续深入时,可以再从中断、定时器、I2C、SPI、滤波、PID 控制与网络协议逐步扩展。
十、WordPress 发布与 SEO 信息
10.1 SEO 标题(Title)
Arduino 与 MicroPython 入门扫盲:PWM、GPIO、ADC、串口、Socket 与计时基础
10.2 SEO 描述(Meta Description)
本文系统整理 Arduino UNO 与 ESP32-C3 MicroPython 的基础知识,包括 PWM、GPIO、ADC、串口、Socket、millis、micros、ticks_us 等核心内容,适合入门学习与开发速查。
10.3 文章摘要(Excerpt)
这是一篇面向入门与查阅场景的 Arduino / MicroPython 基础笔记,涵盖 PWM 输出、时间函数、GPIO、ADC、串口通信、Socket 网络通信、多线程以及 Arduino UNO 常见数值类型,适合作为 ESP32-C3 与 Arduino UNO 开发的基础速查表。
10.4 推荐 Slug
arduino-micropython-pwm-gpio-adc-uart-socket-guide
10.5 推荐分类(Category)
- 嵌入式开发
- Arduino
- MicroPython
10.6 推荐标签(Tags)
- Arduino
- Arduino UNO
- MicroPython
- ESP32-C3
- PWM
- GPIO
- ADC
- UART
- Socket
- millis
- micros
- ticks_us
- 单片机入门
10.7 SEO 关键词参考
- Arduino PWM
- MicroPython PWM
- Arduino GPIO
- ESP32-C3 MicroPython
- Arduino ADC
- MicroPython UART
- Arduino 串口通信
- MicroPython Socket
- millis 和 micros 区别
- Arduino UNO 数据类型