1. 开发环境搭建:从零开始配置Python嵌入式平台
嵌入式开发听起来很高大上,但其实入门门槛并不高。我刚开始接触嵌入式时,也是从最简单的树莓派和ESP32开始的。这两种开发板对Python的支持都非常友好,特别适合初学者上手。
树莓派的环境搭建最简单,基本上就是一张SD卡和一个电源适配器的事。先去树莓派官网下载最新的Raspberry Pi OS镜像,用BalenaEtcher这类工具烧录到SD卡中。第一次启动时记得在boot分区新建一个名为"ssh"的空文件,这样就能通过SSH远程访问了。
ESP32的环境稍微复杂一点,但用MicroPython也很简单。你需要先安装esptool工具,然后用它来刷写MicroPython固件。这里有个小技巧:刷写前一定要先擦除flash,否则可能会遇到各种奇怪的问题。
# 安装esptool
pip install esptool
# 擦除ESP32的flash
esptool --chip esp32 --port COM3 erase_flash
# 刷写MicroPython固件
esptool --chip esp32 --port COM3 --baud 460800 write_flash -z 0x1000 firmware.bin
实际使用中,我推荐用Thonny IDE来开发MicroPython项目。它内置了ESP32和树莓派的支持,可以直接在IDE里上传文件、运行代码,调试起来特别方便。记得第一次连接时要在Thonny里选择正确的解释器和端口,不然会找不到设备。
2. GPIO控制入门:让LED灯闪烁起来
GPIO是嵌入式开发中最基础也最重要的部分。说白了就是通过程序来控制引脚的电平高低,从而控制外接设备。树莓派和ESP32的GPIO控制方法略有不同,但思路是一样的。
在树莓派上,我们常用RPi.GPIO这个库。设置引脚时要注意选择正确的编号模式,BCM模式是按芯片的GPIO编号,BOARD模式是按物理引脚编号。我建议用BCM模式,因为大多数教程和示例代码都用这种模式。
import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM) # 使用BCM编号
GPIO.setup(18, GPIO.OUT) # 设置18号引脚为输出
try:
while True:
GPIO.output(18, GPIO.HIGH) # 输出高电平
time.sleep(1) # 等待1秒
GPIO.output(18, GPIO.LOW) # 输出低电平
time.sleep(1) # 再等1秒
finally:
GPIO.cleanup() # 清理GPIO设置
ESP32用MicroPython的话,方法更简单一些:
from machine import Pin
import time
led = Pin(2, Pin.OUT) # ESP32开发板上的内置LED通常接在GPIO2上
while True:
led.value(1) # 点亮LED
time.sleep(1)
led.value(0) # 熄灭LED
time.sleep(1)
我在刚开始学的时候,在这个简单的LED闪烁实验上就踩过坑。有一次怎么调代码LED都不亮,后来才发现是电阻没接对。所以如果你也遇到类似问题,先检查硬件连接,再用万用表量一下引脚电压,很多时候问题不在代码上。
3. 传感器数据读取:用DHT11测量温湿度
传感器是嵌入式系统的"感官",让我们能够获取现实世界的数据。DHT11温湿度传感器是最常用的入门级传感器,价格便宜,使用简单,虽然精度不是特别高,但做实验完全够用。
DHT11有三个引脚:VCC、DATA、GND。接线时要注意上拉电阻,虽然很多开发板模块已经内置了,但如果用的是裸传感器,记得在DATA引脚和VCC之间接一个10K的电阻。
树莓派上使用DHT11需要安装Adafruit_DHT库:
pip install Adafruit_DHT
然后就可以用下面的代码读取数据了:
import Adafruit_DHT
import time
sensor = Adafruit_DHT.DHT11
pin = 4 # 数据引脚接在GPIO4上
while True:
humidity, temperature = Adafruit_DHT.read_retry(sensor, pin)
if humidity is not None and temperature is not None:
print(f"温度: {temperature:.1f}°C, 湿度: {humidity:.1f}%")
else:
print("传感器读取失败,检查连接")
time.sleep(2) # 每2秒读取一次
ESP32上用MicroPython的话,方法稍有不同:
import dht
from machine import Pin
import time
sensor = dht.DHT11(Pin(4)) # 数据引脚接在GPIO4上
while True:
try:
sensor.measure()
temp = sensor.temperature()
hum = sensor.humidity()
print(f"温度: {temp}°C, 湿度: {hum}%")
except OSError as e:
print("传感器读取错误:", e)
time.sleep(2)
DHT11读取失败是常见问题,特别是接线较长时。如果经常读取失败,可以尝试减小上拉电阻的阻值,或者在代码中增加重试机制。我一般会在读取失败时延迟一段时间再重试,而不是立即重试,这样成功率更高。
4. I2C通信:连接多个设备的智能总线
I2C是一种非常常用的串行通信协议,只需要两根线(SDA和SCL)就能连接多个设备。每个设备都有唯一的地址,主设备通过地址来选择与哪个从设备通信。
树莓派上使用I2C需要先启用I2C接口:
sudo raspi-config
# 选择 Interfacing Options -> I2C -> Yes
然后安装python3-smbus库:
sudo apt-get install python3-smbus
读取I2C设备数据的代码示例:
import smbus
import time
bus = smbus.SMBus(1) # 树莓派新版用1,旧版用0
address = 0x68 # 设备地址,具体看传感器手册
# 扫描I2C设备
def scan_i2c_devices():
devices = []
for addr in range(0x03, 0x78):
try:
bus.read_byte(addr)
devices.append(hex(addr))
except: pass
return devices
print("找到的I2C设备:", scan_i2c_devices())
# 读取数据示例
while True:
try:
data = bus.read_byte_data(address, 0x00) # 从寄存器0x00读取数据
print(f"读取到的数据: {data}")
except Exception as e:
print("读取失败:", e)
time.sleep(1)
ESP32的MicroPython实现:
from machine import I2C, Pin
import time
i2c = I2C(0, scl=Pin(22), sda=Pin(21), freq=100000) # 创建I2C对象
# 扫描设备
devices = i2c.scan()
print("找到的I2C设备:", [hex(addr) for addr in devices])
# 读取数据
while True:
try:
data = i2c.readfrom(0x68, 2) # 从地址0x68读取2字节数据
print("读取到的数据:", data)
except Exception as e:
print("读取失败:", e)
time.sleep(1)
I2C常见问题包括地址冲突、上拉电阻不足等。如果通信不稳定,可以尝试降低通信频率,或者检查硬件连接。我习惯在代码中加入异常处理,这样即使某个传感器出现问题,也不会影响整个系统运行。
5. SPI通信:高速数据传输的利器
SPI是另一种常用的串行通信协议,比I2C速度更快,但需要更多的线。SPI通常用于需要高速数据传输的场景,比如显示屏、SD卡等。
SPI有四根线:MOSI(主出从入)、MISO(主入从出)、SCK(时钟)、CS(片选)。每个从设备都需要单独的CS线,这是和I2C最大的不同。
树莓派上使用SPI也需要先启用:
sudo raspi-config
# 选择 Interfacing Options -> SPI -> Yes
安装spidev库:
pip install spidev
SPI通信示例代码:
import spidev
import time
spi = spidev.SpiDev()
spi.open(0, 0) # 打开SPI设备0,片选0
spi.max_speed_hz = 1000000 # 设置SPI速度
def read_adc(channel):
# 发送读取命令,返回模拟值
adc = spi.xfer2([1, (8 + channel) << 4, 0])
data = ((adc[1] & 3) << 8) + adc[2]
return data
while True:
for ch in range(8): # 读取8个通道
value = read_adc(ch)
print(f"通道{ch}: {value}")
time.sleep(1)
ESP32的MicroPython实现:
from machine import SPI, Pin
import time
spi = SPI(1, baudrate=1000000, polarity=0, phase=0,
sck=Pin(14), mosi=Pin(13), miso=Pin(12))
# 创建片选引脚
cs = Pin(15, Pin.OUT)
cs.value(1) # 默认不选中
def spi_transfer(data):
cs.value(0) # 选中设备
result = spi.read(len(data), data) # 发送并接收数据
cs.value(1) # 取消选中
return result
while True:
data = spi_transfer(b'\x01\x02\x03') # 发送3字节数据
print("接收到的数据:", data)
time.sleep(1)
SPI通信中最容易出问题的是时序和片选信号。一定要确保在数据传输前后正确控制CS引脚,否则数据可能会错乱。如果通信距离较远,还要考虑信号衰减和干扰问题。
6. 中断处理:实时响应外部事件
中断是嵌入式系统中处理外部事件的重要机制。相比轮询方式,中断可以在事件发生时立即响应,提高了系统的实时性和效率。
在GPIO中断中,常见的有上升沿触发、下降沿触发、电平触发等模式。比如按钮按下通常是下降沿触发,松开是上升沿触发。
树莓派的中断处理示例:
import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM)
button_pin = 17
# 设置上拉电阻,按钮按下时引脚接地
GPIO.setup(button_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)
def button_callback(channel):
print("按钮被按下!")
# 这里可以添加处理逻辑
# 添加中断检测,下降沿触发,去抖动时间200ms
GPIO.add_event_detect(button_pin, GPIO.FALLING,
callback=button_callback, bouncetime=200)
try:
print("等待按钮按下...")
while True:
time.sleep(1) # 主程序继续运行
finally:
GPIO.cleanup()
ESP32的MicroPython中断处理:
from machine import Pin
import time
button = Pin(14, Pin.IN, Pin.PULL_UP) # 按钮接在GPIO14上,启用上拉电阻
def button_handler(pin):
print("按钮状态改变:", pin.value())
# 注意:在中断处理函数中不要做耗时操作
# 注册中断处理函数,下降沿触发
button.irq(trigger=Pin.IRQ_FALLING, handler=button_handler)
print("等待按钮按下...")
while True:
time.sleep(1) # 主循环
中断处理中最常见的问题是"抖动"。机械按钮在按下和松开时会产生多次电平变化,这就是抖动。解决方法是在硬件上加电容滤波,或者在软件中设置去抖动时间。
另一个重要原则是中断处理函数要尽可能短小,不要在里面做耗时操作。如果需要处理复杂逻辑,可以设置一个标志位,在主循环中处理实际逻辑。
7. 多线程编程:同时处理多个任务
在复杂的嵌入式应用中,经常需要同时处理多个任务。比如一边读取传感器数据,一边控制执行器,还要响应网络请求。这时候就需要多线程编程了。
Python的标准threading模块在树莓派上可以直接使用,但在MicroPython中通常使用_uasyncio库来实现类似功能。
树莓派上的多线程示例:
import threading
import time
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
led_pin = 18
GPIO.setup(led_pin, GPIO.OUT)
# 第一个线程:控制LED闪烁
def led_thread():
while True:
GPIO.output(led_pin, GPIO.HIGH)
time.sleep(0.5)
GPIO.output(led_pin, GPIO.LOW)
time.sleep(0.5)
# 第二个线程:打印传感器数据
def sensor_thread():
count = 0
while True:
print(f"传感器读数: {count}")
count += 1
time.sleep(1)
# 创建并启动线程
t1 = threading.Thread(target=led_thread)
t2 = threading.Thread(target=sensor_thread)
t1.daemon = True # 设置为守护线程,主程序退出时自动结束
t2.daemon = True
t1.start()
t2.start()
try:
while True:
time.sleep(0.1) # 主线程
except KeyboardInterrupt:
print("程序退出")
GPIO.cleanup()
ESP32使用_uasyncio的示例:
import uasyncio as asyncio
from machine import Pin
import time
led = Pin(2, Pin.OUT)
async def blink_led():
while True:
led.value(1)
await asyncio.sleep(0.5)
led.value(0)
await asyncio.sleep(0.5)
async def read_sensor():
count = 0
while True:
print(f"传感器读数: {count}")
count += 1
await asyncio.sleep(1)
async def main():
# 同时运行两个任务
await asyncio.gather(blink_led(), read_sensor())
# 运行主函数
try:
asyncio.run(main())
except KeyboardInterrupt:
print("程序退出")
多线程编程中要注意线程安全问题。如果多个线程同时访问共享资源(如全局变量、硬件设备),需要加锁避免冲突。在嵌入式系统中,还要注意线程优先级和实时性要求。
8. 网络通信:让设备连接世界
现在的嵌入式设备大多需要联网能力,无论是远程控制还是数据上传。Python提供了丰富的网络编程库,从底层的socket到高级的HTTP客户端。
树莓派上有完整的Python环境,可以直接使用requests等高级库。ESP32的MicroPython通常使用更轻量级的实现。
树莓派上的HTTP客户端示例:
import requests
import time
import json
# 物联网平台的数据上报接口
url = "http://api.example.com/iot/data"
headers = {"Content-Type": "application/json"}
def read_sensor():
# 模拟传感器读数
return {"temperature": 25.6, "humidity": 60.2}
while True:
try:
data = read_sensor()
response = requests.post(url, data=json.dumps(data), headers=headers)
if response.status_code == 200:
print("数据上报成功")
else:
print(f"上报失败: {response.status_code}")
except Exception as e:
print(f"网络错误: {e}")
time.sleep(30) # 每30秒上报一次
ESP32的MicroPython网络示例:
import network
import socket
import time
from machine import Pin
# 连接WiFi
def connect_wifi(ssid, password):
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
print("连接WiFi...")
wlan.connect(ssid, password)
# 等待连接,最多10秒
for _ in range(10):
if wlan.isconnected():
break
time.sleep(1)
if wlan.isconnected():
print("网络配置:", wlan.ifconfig())
return True
else:
print("连接失败")
return False
# 创建TCP服务器
def create_tcp_server():
addr = socket.getaddrinfo("0.0.0.0", 80)[0][-1]
sock = socket.socket()
sock.bind(addr)
sock.listen(1)
print("服务器启动,监听端口80")
return sock
# 主程序
if connect_wifi("你的WiFi名", "你的密码"):
server = create_tcp_server()
while True:
try:
conn, addr = server.accept()
print("客户端连接:", addr)
request = conn.recv(1024)
print("请求内容:", request)
# 简单的HTTP响应
response = """HTTP/1.1 200 OK
Content-Type: text/html
<h1>Hello from ESP32</h1>
<p>这是一个简单的Web服务器</p>"""
conn.send(response)
conn.close()
except Exception as e:
print("服务器错误:", e)
time.sleep(1)
网络编程中要注意错误处理和重试机制。无线网络可能不稳定,设备可能会暂时断开连接。好的网络程序应该能够处理这些异常情况,并在连接恢复后继续工作。
9. 智能家居综合实战:温控风扇系统
现在我们把前面学到的所有知识用到一个实际项目中:智能温控风扇系统。这个系统会根据环境温度自动控制风扇开关,同时提供Web界面用于手动控制和状态监控。
系统功能:
- 实时监测温湿度
- 温度超过阈值自动开启风扇
- Web界面显示当前状态
- 支持手动控制 override
硬件清单:
- 树莓派4B(或ESP32)
- DHT11温湿度传感器
- 5V小风扇(带MOSFET控制)
- 电阻、导线等
接线说明:
- DHT11数据线接GPIO4
- 风扇控制线通过MOSFET接GPIO18
- 注意电平转换和电流要求
完整代码实现:
import RPi.GPIO as GPIO
import Adafruit_DHT
import threading
from flask import Flask, render_template_string
import time
# 初始化
GPIO.setmode(GPIO.BCM)
FAN_PIN = 18
SENSOR_PIN = 4
SENSOR_TYPE = Adafruit_DHT.DHT11
GPIO.setup(FAN_PIN, GPIO.OUT)
GPIO.output(FAN_PIN, GPIO.LOW)
# 全局状态
current_temp = 0
current_humidity = 0
fan_status = False
auto_mode = True
manual_override = False
# Web界面模板
HTML_TEMPLATE = """
<!DOCTYPE html>
<html>
<head>
<title>智能温控风扇</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.status { padding: 20px; background: #f0f0f0; border-radius: 10px; }
.button { padding: 10px 20px; margin: 5px; border: none; border-radius: 5px; cursor: pointer; }
.on { background: #4CAF50; color: white; }
.off { background: #f44336; color: white; }
.auto { background: #2196F3; color: white; }
</style>
</head>
<body>
<h1>智能温控风扇系统</h1>
<div class="status">
<h2>当前状态</h2>
<p>温度: {{ temp }}°C, 湿度: {{ humidity }}%</p>
<p>风扇状态: <strong>{{ '运行中' if fan else '停止' }}</strong></p>
<p>模式: <strong>{{ '自动' if auto else '手动' }}</strong></p>
</div>
<div>
<h2>控制</h2>
<a href="/fan/on"><button class="button on">开启风扇</button></a>
<a href="/fan/off"><button class="button off">关闭风扇</button></a>
<a href="/mode/auto"><button class="button auto">自动模式</button></a>
</div>
<div>
<h2>系统信息</h2>
<p>更新时间: {{ time }}</p>
<p>自动模式阈值: 26°C</p>
</div>
</body>
</html>
"""
# 传感器读取线程
def sensor_thread():
global current_temp, current_humidity, fan_status, auto_mode
while True:
humidity, temperature = Adafruit_DHT.read_retry(SENSOR_TYPE, SENSOR_PIN)
if humidity is not None and temperature is not None:
current_temp = temperature
current_humidity = humidity
# 自动模式下的温控逻辑
if auto_mode and not manual_override:
if temperature > 26 and not fan_status:
GPIO.output(FAN_PIN, GPIO.HIGH)
fan_status = True
print("温度过高,开启风扇")
elif temperature <= 25 and fan_status:
GPIO.output(FAN_PIN, GPIO.LOW)
fan_status = False
print("温度正常,关闭风扇")
time.sleep(2)
# Flask Web服务器
app = Flask(__name__)
@app.route('/')
def index():
return render_template_string(HTML_TEMPLATE,
temp=current_temp,
humidity=current_humidity,
fan=fan_status,
auto=auto_mode,
time=time.strftime('%Y-%m-%d %H:%M:%S'))
@app.route('/fan/<status>')
def control_fan(status):
global fan_status, manual_override, auto_mode
if status == 'on':
GPIO.output(FAN_PIN, GPIO.HIGH)
fan_status = True
manual_override = True
auto_mode = False
elif status == 'off':
GPIO.output(FAN_PIN, GPIO.LOW)
fan_status = False
manual_override = True
auto_mode = False
return index()
@app.route('/mode/auto')
def set_auto_mode():
global auto_mode, manual_override
auto_mode = True
manual_override = False
return index()
if __name__ == '__main__':
# 启动传感器线程
sensor_thread = threading.Thread(target=sensor_thread)
sensor_thread.daemon = True
sensor_thread.start()
# 启动Web服务器
print("启动Web服务器,访问 http://树莓派IP:5000")
app.run(host='0.0.0.0', port=5000, debug=False)
这个项目综合运用了GPIO控制、传感器读取、多线程、网络编程等技术。在实际部署时,你可能还需要考虑以下问题:
- 电源管理:风扇功率较大时,需要外接电源,不能直接从GP口取电
- 安全保护:添加温度过高保护,防止传感器故障导致系统异常
- 持久化配置:将阈值配置保存到文件,支持动态修改
- 远程访问:配置DDNS或内网穿透,实现远程访问
调试这种综合项目时,建议分模块测试。先确保传感器读数准确,再测试风扇控制,最后集成Web界面。遇到问题时,查看系统日志,通常能找到线索。
1816



被折叠的 条评论
为什么被折叠?



