3、Python基础回顾:数据类型、控制流、函数定义、文件操作
好,咱们进入第三章。说实话,很多嵌入式工程师对Python又爱又恨。爱的是它写脚本快,恨的是总觉得它「不靠谱」。我刚开始也这么想,直到我在一个ARM项目里用Python写了个自动化测试脚本,一口气跑了三天三夜没出问题。嗯,从那以后我就踏实了。
这一章咱们快速过一遍Python的核心基础。别嫌啰嗦,我敢打赌,你写嵌入式脚本时踩的坑,十有八九都出在这些基础点上。
3.1 数据类型:别小看这些「小东西」
Python的数据类型,说白了就那几种。但嵌入式场景下,有些坑你得提前知道。
数字类型
整型、浮点型、复数型。嵌入式里最常用的是整型和浮点型。我个人习惯,凡是涉及寄存器地址、位操作,一律用整型。浮点型在MCU上跑,性能开销大,能不用就不用。
# 整型
reg_addr = 0x40021000
bit_mask = 0b00001111
# 浮点型
voltage = 3.3
current = 0.15
# 复数型(嵌入式里用得少,但信号处理时会用到)
impedance = 50 + 20j
我曾经踩过的坑:在Python 2里,整数除法会直接截断。比如 5 / 2 = 2。Python 3修复了这个问题,但如果你在旧系统上跑脚本,记得用 from __future__ import division。
字符串与字节串
嵌入式里,字符串和字节串的转换是家常便饭。你想想看,串口发过来的数据,本质上是字节流,不是字符串。
# 字符串
uart_cmd = "AT+RESET\r\n"
# 字节串
uart_data = b'\x01\x02\x03\xFF'
# 转换
cmd_bytes = uart_cmd.encode('ascii') # 字符串 -> 字节串
data_str = uart_data.decode('utf-8', errors='ignore') # 字节串 -> 字符串
小技巧:处理串口数据时,我建议一律用字节串。字符串在编码解码时容易出乱码,尤其是遇到非ASCII字符时。我在一个GPS解析项目里就吃过这个亏,后来全改成字节串操作,问题迎刃而解。
列表与元组
列表可变,元组不可变。这个大家都知道。但嵌入式里,我推荐多用元组。为什么?因为元组可以作为字典的键,列表不行。而且元组更轻量,性能略好。
# 列表
sensor_readings = [25.3, 26.1, 24.8]
sensor_readings.append(25.0) # 可以修改
# 元组
gpio_pins = (0, 1, 2, 3)
# gpio_pins[0] = 5 # 会报错!元组不可修改
# 元组作为字典键
pin_config = {
(0, 'input'): 'GPIOA',
(1, 'output'): 'GPIOB'
}
字典与集合
字典在嵌入式脚本里太常用了。配置参数、寄存器映射、设备状态,用字典管理起来特别清晰。
# 字典
device_config = {
'baudrate': 115200,
'data_bits': 8,
'stop_bits': 1,
'parity': 'none'
}
# 集合(去重利器)
error_codes = {0x01, 0x02, 0x03, 0x01} # 自动去重,结果是 {0x01, 0x02, 0x03}
3.2 控制流:让代码「活」起来
控制流没什么高深的,就是条件判断和循环。但嵌入式场景下,有些写法更高效。
条件判断
if-elif-else,老生常谈了。但我有个习惯:把最可能成立的条件放在最前面。这样能减少判断次数,在循环里尤其明显。
status = read_device_status()
# 我习惯这样写
if status == 0x00:
# 正常状态(最常见)
handle_normal()
elif status == 0xFF:
# 错误状态
handle_error()
else:
# 其他状态
handle_unknown()
重点:Python没有switch-case语句。如果你有多个条件要判断,可以用字典映射代替长长的if-elif链。代码更简洁,性能也更好。
def handle_cmd_01():
print("执行命令01")
def handle_cmd_02():
print("执行命令02")
cmd_handlers = {
0x01: handle_cmd_01,
0x02: handle_cmd_02,
}
cmd = 0x01
handler = cmd_handlers.get(cmd)
if handler:
handler()
else:
print("未知命令")
循环
for循环和while循环。嵌入式里,for循环用得更多,尤其是遍历数据时。
# 遍历列表
for reading in sensor_readings:
print(f"温度: {reading}°C")
# 遍历字典
for key, value in device_config.items():
print(f"{key}: {value}")
# 带索引的遍历
for i, pin in enumerate(gpio_pins):
print(f"引脚{i}: GPIO{pin}")
注意:在嵌入式脚本里,尽量避免无限循环。如果一定要用while True,记得加个退出条件或超时机制。我曾经写过一个串口监听脚本,忘了加退出条件,结果在服务器上跑了整整一个周末……
3.3 函数定义:把逻辑「封装」起来
函数是代码复用的基础。嵌入式脚本里,我建议把每个独立功能都封装成函数。这样调试起来方便,也容易移植。
基本函数定义
def read_sensor(sensor_id):
"""读取传感器数据"""
# 模拟读取
data = 25.0 + sensor_id * 0.1
return data
def write_register(addr, value):
"""写入寄存器"""
print(f"写入寄存器 0x{addr:08X}, 值 0x{value:02X}")
# 实际写入操作...
参数传递
Python的参数传递很灵活。默认参数、关键字参数、可变参数,用好了能省不少事。
# 默认参数
def configure_uart(baudrate=115200, data_bits=8, parity='none'):
print(f"配置UART: {baudrate} {data_bits} {parity}")
# 关键字参数
configure_uart(parity='even', baudrate=9600)
# 可变参数
def log_messages(*messages):
for msg in messages:
print(f"[LOG] {msg}")
log_messages("启动", "初始化完成", "等待命令")
个人经验:写函数时,尽量让每个函数只做一件事。如果一个函数超过20行,我通常会考虑拆分成更小的函数。这样测试起来方便,出错了也容易定位。
返回值
函数可以返回多个值,实际上返回的是一个元组。
def read_device_info():
"""读取设备信息"""
device_id = 0x1234
firmware_version = "2.1.0"
status = 0x00
return device_id, firmware_version, status
# 解包
dev_id, fw_ver, st = read_device_info()
print(f"设备ID: 0x{dev_id:04X}, 固件版本: {fw_ver}")
3.4 文件操作:读写配置与日志
嵌入式脚本里,文件操作主要用来读写配置文件、保存日志、导出数据。Python的文件操作很简单,但有几个细节要注意。
打开与关闭文件
我强烈推荐使用with语句。它能自动关闭文件,省心省力。
# 推荐写法
with open('config.ini', 'r') as f:
content = f.read()
print(content)
# 文件自动关闭
# 不推荐写法
f = open('config.ini', 'r')
content = f.read()
f.close() # 容易忘记
读写模式
| 模式 | 说明 | 嵌入式常用场景 |
|---|---|---|
| 'r' | 只读 | 读取配置文件 |
| 'w' | 写入(覆盖) | 保存新配置 |
| 'a' | 追加 | 写入日志 |
| 'rb' | 二进制只读 | 读取固件文件 |
| 'wb' | 二进制写入 | 写入固件文件 |
逐行读取
处理日志文件时,逐行读取比一次性读取更省内存。
# 逐行读取日志
with open('system.log', 'r') as f:
for line in f:
if 'ERROR' in line:
print(f"发现错误: {line.strip()}")
写入CSV文件
嵌入式测试中,经常需要把数据导出成CSV格式,方便用Excel分析。
import csv
# 写入CSV
data = [
['时间', '温度', '湿度'],
['2024-01-01 10:00', 25.3, 60],
['2024-01-01 10:01', 25.5, 61],
]
with open('sensor_data.csv', 'w', newline='') as f:
writer = csv.writer(f)
writer.writerows(data)
核心要点:文件操作时,记得处理异常。嵌入式环境里,文件可能被占用、磁盘可能满、路径可能不存在。用try-except包一下,能避免脚本意外崩溃。
try:
with open('config.ini', 'r') as f:
config = f.read()
except FileNotFoundError:
print("配置文件不存在,使用默认配置")
config = default_config
except PermissionError:
print("没有权限读取配置文件")
exit(1)
好了,Python基础就回顾到这里。这些内容看起来简单,但都是嵌入式脚本开发的基石。下一章咱们会把这些知识串起来,写一个完整的自动化测试脚本。到时候你会发现,这些基础点一个都跑不掉。