SPI
前置知识
SPI(Serial Peripheral Interface,串行外设接口)是一种在嵌入式系统中广泛使用的高速、全双工、同步的串行通信协议。
常用于需要高速数据交换的场景,例如连接闪存(Flash)、SD卡、显示屏驱动器、ADC/DAC转换器以及各种传感器。
SPI接口有3线、4线两种形式。其工作机制如下表所示区别:
4线SPI
3线SPI
接口说明
SPI接口在C1201上的位置,在40Pin上有两个SPI接口,硬件引脚分别为SPI0、SPI1;在Jetson-io配置工具中分别为spi1和spi3。
注意
Jetson Orin 系列的设备树设计,会根据不同的引脚复用模式,给硬件控制器分配不同的软件别名;
为了和其他 Jetson 系列(如 Xavier)保持兼容,在jetson-io配置工具中会给它们分配不同的编号:
SPI0 → spi1;
SPI1 → spi3
硬件手册/引脚图 |
jetson-io配置工具 |
引脚 |
说明 |
|---|---|---|---|
SPI0 |
spi1 |
19、21、23、24、26 |
- |
SPI1 |
spi3 |
13、16、18、22、37 |
- |
接口实验
小技巧
本章节实验中使用C1201载板的40Pin中的SPI接口进行测试
实验1:测试SPI总线的回环读写;
实验2:使用SPI总线读取一个RFID的电子标签;
实验3:使用SPI总线读取一个气压和温度传感器;
请前置安装必要的依赖,如下:
sudo pip3 install spidev Jetson.GPIO
实验1:回环实验
通过Jetson-io设置spi引脚
检查spi状态
ls /dev/spi*
/dev/spidev0.0 /dev/spidev0.1 /dev/spidev1.0 /dev/spidev1.1
spi设备节点状态正常
注意
spidev0.0 / spidev0.1:属于硬件引脚编号的同一总线 spi0,对应两个片选 CS0 和 CS1
spidev1.0 / spidev1.1:属于硬件引脚编号的同一总线 spi1,对应两个片选 CS0 和 CS1
spi接口回环测试
用杜邦线短接 MOSI(Pin 19)和 MISO(Pin 21)
采用python脚本测试:
#!/usr/bin/env python3
import spidev
# 初始化SPI
spi = spidev.SpiDev()
spi.open(0, 0) # 打开总线0,设备0 (即 /dev/spidev0.0)
spi.max_speed_hz = 1000000 # 设置SPI速度(1MHz)
spi.mode = 0b00 # 设置SPI模式为0(CPOL=0, CPHA=0)
# 准备测试数据
test_data = [0xAA, 0xBB, 0xCC] # 可以任意更改
# 发送并接收数据
received_data = spi.xfer2(test_data)
# 打印结果
print(f"发送的数据: {[hex(x) for x in test_data]}")
print(f"接收的数据: {[hex(x) for x in received_data]}")
# 判断是否成功
if test_data == received_data:
print("✅ SPI回环测试成功!")
else:
print("❌ SPI回环测试失败!")
spi.close()
实验结果
在命令终端中看到脚本运行出现如下结果时,即说明SPI回环测试成功
sudo python3 rfid_loop_test.py
发送的数据: ['0xaa', '0xbb', '0xcc']
接收的数据: ['0xaa', '0xbb', '0xcc']
✅ SPI回环测试成功!
实验2:RFID读卡实验
小技巧
本章节实验中使用C1201载板的40Pin中的SPI3接口读取一个RFID的电子标签设备MFRC-522
MFRC-522 RFID射频IC卡感应模块
MFRC-522是应用于13.56MHz非接触式通信中高集成度的读写卡芯片。 MF RC522利用了先进的调制和解调概念,集成了在13.56MHz下所有类型的被动非接触式通信方式和协议。支持14443A兼容应答器信号。 数字部分处理ISO14443A帧和错误检测。此外,还支持快速CRYPTO1加密算法,用于验证MIFARE系列产品。MFRC522支持MIFARE系列更高速的非接触式通信,双向数据传输速率高达424kbit/s。 它与主机间通信采用SPI模式。
电气参数
工作电流:13~26mA/直流3.3V
空闲电流:10~13mA/直流3.3V
休眠电流:<80uA
峰值电流:<30mA
工作频率:13.56MHz
引脚接口

SDA:片选
SCK:时钟
MOSI:主设备输出
MISO:主设备输入
GND:接地
RST:复位
VCC:3.3v电压输入
IRQ:中断引脚
实验步骤
spi接口设置
使用jetson-io脚本设置C1201的40Pin的spi3接口
硬件连线
MFRC-522 |
C1201.Pin40排针引脚编号 |
说明 |
|---|---|---|
SDA |
pin18 (SPI1_CS0) |
- |
SCK |
pin13 (SPI1_SCK) |
- |
MOSI |
pin37 (SPI1_MOSI) |
- |
MISO |
pin22 (SPI1_MISO) |
- |
GND |
pin6 (GND) |
- |
RST |
pin31 (GPIO) |
- |
VCC |
pin1(3.3v) |
- |
IRQ |
无需连接 |
- |
安装必要的工具包
sudo apt update
sudo apt install -y python3-pip python3-dev spi-tools
安装mfrc522的Python支持库
sudo pip3 install mfrc522
读取RFID数据的Python代码
#!/usr/bin/env python3
"""
Orin Nano RC522 RFID读取脚本
硬件连接:
RC522模块 -> Orin Nano引脚:
- SDA -> Pin 18 (SPI3_CS0)
- SCK -> Pin 13 (SPI3_SCK)
- MOSI -> Pin 37 (SPI3_DOUT)
- MISO -> Pin 22 (SPI3_DIN)
- GND -> Pin 6 (GND)
- RST -> Pin 31 (GPIO)
- VCC -> Pin 1 (3.3V)
"""
import time
import spidev
import Jetson.GPIO as GPIO
# 引脚定义 (BOARD模式,物理引脚编号)
RST_PIN = 31 # RC522复位引脚
# RC522寄存器定义
class MFRC522:
# 命令定义
PCD_IDLE = 0x00
PCD_AUTHENT = 0x0E
PCD_RECEIVE = 0x08
PCD_TRANSMIT = 0x04
PCD_TRANSCEIVE = 0x0C
PCD_RESETPHASE = 0x0F
PCD_CALCCRC = 0x03
# 寄存器定义
CommandReg = 0x01
ComIEnReg = 0x02
DivIEnReg = 0x03
ComIrqReg = 0x04
DivIrqReg = 0x05
ErrorReg = 0x06
Status1Reg = 0x07
Status2Reg = 0x08
FIFODataReg = 0x09
FIFOLevelReg = 0x0A
WaterLevelReg = 0x0B
ControlReg = 0x0C
BitFramingReg = 0x0D
CollReg = 0x0E
ModeReg = 0x11
TxModeReg = 0x12
RxModeReg = 0x13
TxControlReg = 0x14
TxASKReg = 0x15
TxSelReg = 0x16
RxSelReg = 0x17
RxThresholdReg = 0x18
DemodReg = 0x19
MifareReg = 0x1C
SerialSpeedReg = 0x1F
CRCResultRegH = 0x21
CRCResultRegL = 0x22
ModWidthReg = 0x24
RFCfgReg = 0x26
GsNReg = 0x27
CWGsCfgReg = 0x28
ModGsCfgReg = 0x29
TModeReg = 0x2A
TPrescalerReg = 0x2B
TReloadRegH = 0x2C
TReloadRegL = 0x2D
TCounterValueRegH = 0x2E
TCounterValueRegL = 0x2F
TestSel1Reg = 0x30
TestSel2Reg = 0x31
TestPinEnReg = 0x32
TestPinValueReg = 0x33
TestBusReg = 0x34
AutoTestReg = 0x35
VersionReg = 0x36
AnalogTestReg = 0x37
TestDAC1Reg = 0x38
TestDAC2Reg = 0x39
TestADCReg = 0x3A
def __init__(self, bus=2, device=0, speed=1000000):
"""初始化RC522模块"""
# 初始化GPIO
GPIO.setmode(GPIO.BOARD)
GPIO.setup(RST_PIN, GPIO.OUT)
# 初始化SPI
self.spi = spidev.SpiDev()
# 尝试打开SPI设备 (根据您的ls输出,可能是spidev2.0)
spi_devices = [(2, 0), (1, 0), (0, 0)] # 尝试SPI2, SPI1, SPI0
spi_opened = False
for bus_try, device_try in spi_devices:
try:
self.spi.open(bus_try, device_try)
self.spi.max_speed_hz = speed
self.spi.mode = 0 # SPI模式0
self.spi.bits_per_word = 8
spi_opened = True
print(f"成功打开SPI设备: /dev/spidev{bus_try}.{device_try}")
break
except Exception as e:
print(f"无法打开/dev/spidev{bus_try}.{device_try}: {e}")
if not spi_opened:
raise Exception("无法打开任何SPI设备,请检查SPI配置")
# 初始化RC522
self.init()
def write_register(self, address, value):
"""写入寄存器"""
# RC522 SPI通信格式: MSB=0表示写,后跟7位地址
address = (address << 1) & 0x7E
self.spi.xfer([address, value])
def read_register(self, address):
"""读取寄存器"""
# RC522 SPI通信格式: MSB=1表示读,后跟7位地址
address = ((address << 1) & 0x7E) | 0x80
result = self.spi.xfer([address, 0])
return result[1]
def set_bitmask(self, address, mask):
"""设置寄存器位掩码"""
current = self.read_register(address)
self.write_register(address, current | mask)
def clear_bitmask(self, address, mask):
"""清除寄存器位掩码"""
current = self.read_register(address)
self.write_register(address, current & (~mask))
def init(self):
"""初始化RC522模块"""
print("初始化RC522模块...")
# 复位RC522
self.reset()
# 关闭CRC
self.write_register(self.TxModeReg, 0x00)
self.write_register(self.RxModeReg, 0x00)
# 设置定时器
self.write_register(self.TModeReg, 0x80)
self.write_register(self.TPrescalerReg, 0xA9)
self.write_register(self.TReloadRegL, 0xE8)
self.write_register(self.TReloadRegH, 0x03)
# 设置调制宽度
self.write_register(self.TxASKReg, 0x40)
self.write_register(self.ModeReg, 0x3D)
# 启用天线
self.set_bitmask(self.TxControlReg, 0x03)
# 检查版本
version = self.read_register(self.VersionReg)
print(f"RC522版本: 0x{version:02X}")
if version == 0x91:
print("检测到MFRC522 v1.0")
elif version == 0x92:
print("检测到MFRC522 v2.0")
else:
print("未知RC522版本")
print("RC522初始化完成")
def reset(self):
"""复位RC522模块"""
# 通过GPIO复位
GPIO.output(RST_PIN, GPIO.LOW)
time.sleep(0.001)
GPIO.output(RST_PIN, GPIO.HIGH)
time.sleep(0.05)
# 软件复位
self.write_register(self.CommandReg, self.PCD_RESETPHASE)
time.sleep(0.05)
# 等待复位完成
while self.read_register(self.CommandReg) & (1 << 4):
time.sleep(0.01)
def antenna_on(self):
"""开启天线"""
value = self.read_register(self.TxControlReg)
if ~(value & 0x03):
self.set_bitmask(self.TxControlReg, 0x03)
def antenna_off(self):
"""关闭天线"""
self.clear_bitmask(self.TxControlReg, 0x03)
def to_card(self, command, send_data):
"""与卡片通信"""
back_data = []
back_length = 0
status = 0
irq_en = 0x00
wait_irq = 0x00
last_bits = None
n = 0
if command == self.PCD_AUTHENT:
irq_en = 0x12
wait_irq = 0x10
if command == self.PCD_TRANSCEIVE:
irq_en = 0x77
wait_irq = 0x30
self.write_register(self.ComIEnReg, irq_en | 0x80)
self.clear_bitmask(self.ComIrqReg, 0x80)
self.set_bitmask(self.FIFOLevelReg, 0x80)
self.write_register(self.CommandReg, self.PCD_IDLE)
# 写入数据到FIFO
for i in range(len(send_data)):
self.write_register(self.FIFODataReg, send_data[i])
self.write_register(self.CommandReg, command)
if command == self.PCD_TRANSCEIVE:
self.set_bitmask(self.BitFramingReg, 0x80)
# 等待命令完成
i = 2000
while True:
n = self.read_register(self.ComIrqReg)
i -= 1
if ~((i != 0) and ~(n & 0x01) and ~(n & wait_irq)):
break
self.clear_bitmask(self.BitFramingReg, 0x80)
if i != 0:
if (self.read_register(self.ErrorReg) & 0x1B) == 0x00:
status = 1
if n & irq_en & 0x01:
status = 0
elif command == self.PCD_TRANSCEIVE:
n = self.read_register(self.FIFOLevelReg)
last_bits = self.read_register(self.ControlReg) & 0x07
if last_bits != 0:
back_length = (n - 1) * 8 + last_bits
else:
back_length = n * 8
if n == 0:
n = 1
if n > 16:
n = 16
# 读取接收到的数据
for i in range(n):
back_data.append(self.read_register(self.FIFODataReg))
else:
status = 0
return status, back_data, back_length
def request(self, req_mode=0x26):
"""寻卡请求"""
self.write_register(self.BitFramingReg, 0x07)
tag_type = [req_mode]
status, back_data, back_bits = self.to_card(self.PCD_TRANSCEIVE, tag_type)
if (status != 0) and (back_bits == 0x10):
print("检测到卡片")
return 1, back_data[0]
else:
print("未检测到卡片")
return 0, []
def anticoll(self):
"""防冲突,获取卡片序列号"""
ser_num_check = 0
ser_num = []
self.write_register(self.BitFramingReg, 0x00)
ser_num.append(0x93)
ser_num.append(0x20)
status, back_data, back_bits = self.to_card(self.PCD_TRANSCEIVE, ser_num)
if status == 1:
if len(back_data) == 5:
for i in range(4):
ser_num_check = ser_num_check ^ back_data[i]
if ser_num_check != back_data[4]:
status = 0
else:
status = 0
return status, back_data
def calculate_crc(self, data):
"""计算CRC校验值"""
self.clear_bitmask(self.DivIrqReg, 0x04)
self.set_bitmask(self.FIFOLevelReg, 0x80)
for i in range(len(data)):
self.write_register(self.FIFODataReg, data[i])
self.write_register(self.CommandReg, self.PCD_CALCCRC)
i = 0xFF
while True:
n = self.read_register(self.DivIrqReg)
i -= 1
if not ((i != 0) and not (n & 0x04)):
break
result = []
result.append(self.read_register(self.CRCResultRegL))
result.append(self.read_register(self.CRCResultRegH))
return result
def select_tag(self, ser_num):
"""选择卡片"""
buf = []
buf.append(0x93)
buf.append(0x70)
for i in range(5):
buf.append(ser_num[i])
crc = self.calculate_crc(buf)
buf.append(crc[0])
buf.append(crc[1])
status, back_data, back_length = self.to_card(self.PCD_TRANSCEIVE, buf)
if (status == 1) and (back_length == 0x18):
print("卡片选择成功")
return back_data[0]
else:
print("卡片选择失败")
return 0
def auth(self, auth_mode, block_addr, sector_key, ser_num):
"""验证卡片扇区"""
buff = []
# 验证指令+块地址+密钥+序列号
buff.append(auth_mode)
buff.append(block_addr)
for i in range(len(sector_key)):
buff.append(sector_key[i])
for i in range(4):
buff.append(ser_num[i])
status, back_data, back_length = self.to_card(self.PCD_AUTHENT, buff)
if not(status):
print("验证失败")
else:
print("验证成功")
return status
def read(self, block_addr):
"""读取数据块"""
recv_data = []
recv_data.append(0x30)
recv_data.append(block_addr)
crc = self.calculate_crc(recv_data)
recv_data.append(crc[0])
recv_data.append(crc[1])
status, back_data, back_length = self.to_card(self.PCD_TRANSCEIVE, recv_data)
if not(status):
print("读取失败")
else:
if len(back_data) == 16:
print("读取成功")
return back_data
else:
print("数据长度错误")
return None
def write(self, block_addr, write_data):
"""写入数据块"""
buff = []
buff.append(0xA0)
buff.append(block_addr)
crc = self.calculate_crc(buff)
buff.append(crc[0])
buff.append(crc[1])
status, back_data, back_length = self.to_card(self.PCD_TRANSCEIVE, buff)
if not(status) or (back_length != 4) or ((back_data[0] & 0x0F) != 0x0A):
print("写入准备失败")
return False
print("开始写入数据...")
# 发送数据
buff = []
for i in range(16):
buff.append(write_data[i])
crc = self.calculate_crc(buff)
buff.append(crc[0])
buff.append(crc[1])
status, back_data, back_length = self.to_card(self.PCD_TRANSCEIVE, buff)
if not(status) or (back_length != 4) or ((back_data[0] & 0x0F) != 0x0A):
print("写入失败")
return False
print("写入成功")
return True
def read_card_uid(self):
"""读取卡片UID(主要功能)"""
print("等待RFID卡片靠近...")
# 寻卡请求
status, tag_type = self.request()
if status != 1:
return None
# 防冲突,获取序列号
status, uid_data = self.anticoll()
if status == 1:
# 将UID转换为十六进制字符串
uid_hex = ''.join([f'{x:02X}' for x in uid_data[:4]])
return uid_data[:4], uid_hex
else:
return None
def cleanup(self):
"""清理资源"""
print("清理RC522资源...")
try:
# 关闭天线
self.antenna_off()
# 关闭SPI
self.spi.close()
except Exception as e:
print(f"清理资源时出错: {e}")
finally:
# 清理GPIO
GPIO.cleanup()
print("资源清理完成")
def main():
"""主函数"""
rc522 = None
try:
print("=" * 50)
print("Orin Nano RC522 RFID读取程序")
print("=" * 50)
# 初始化RC522
rc522 = MFRC522()
print("\n✅ RC522模块初始化成功!")
print("请将RFID卡片靠近读卡器...")
print("按Ctrl+C退出程序\n")
card_count = 0
while True:
# 尝试读取卡片UID
result = rc522.read_card_uid()
if result is not None:
uid_data, uid_hex = result
card_count += 1
print(f"\n🎉 检测到RFID卡片 #{card_count}")
print(f"卡片UID: {uid_hex}")
print(f"字节数据: {[f'0x{x:02X}' for x in uid_data]}")
print(f"十进制: {[int(x) for x in uid_data]}")
print("-" * 30)
# 短暂延时,避免重复读取同一张卡
time.sleep(2)
else:
# 没有检测到卡片,短暂延时后继续
time.sleep(0.1)
except KeyboardInterrupt:
print("\n\n程序被用户中断")
except Exception as e:
print(f"\n❌ 发生错误: {e}")
import traceback
traceback.print_exc()
finally:
if rc522 is not None:
rc522.cleanup()
print("程序退出")
if __name__ == "__main__":
# 检查权限
import os
if os.geteuid() != 0:
print("注意:此程序需要sudo权限运行")
print("请使用: sudo python3 rc522_reader.py")
exit(1)
main()
实验结果
实验3:气压温度传感器
小技巧
本章节实验中使用C1201载板的40Pin中的SPI1接口读取一个SPI总线的大气压强传感器GY-BMP280
GY-BMP280大气压强传感器模块
电气参数
工作电压:1.7~3.6V DC
温度测量范围:0℃~65℃
温度测量误差:±0.5℃ MAX±1℃;分辨率0.1℃
相对湿度测量范围:0%~100% 湿度响应时间:大于1s
相对湿度测量误差:±2%;湿度迟滞:±1%;分辨率0.8%
气压测量范围:300~1100hPa(百帕斯卡)
气压测量误差:±1hPa 分辨率0.18Pa
数字接口类型:I2C(从模式3.4MHz)或SPI(3线或4线制,从模式10MHz)
尺寸:15.4mm(长)*11.6mm(宽)*2.4mm(高)
带M3固定螺丝孔。方便安装及固定。
引脚接口

VCC: 电源
GND: 地
SCL: I2C/SPI串行时钟线
SDA:12C串行数据线/3线SPI串行数据输入/输出端口/4线SPI串行数据输入端口
CSB:片选引脚,接高电平(默认)为I2C通信接口,低电平为SPI通信接口
SDO: I2C地址选择位/4线SPI串行输出端口
实验步骤
SPI1接口设置 使用Jetson-IO脚本启用40Pin的SPI1接口
硬件连线
GY-BMP280 |
C1201.Pin40排针引脚编号 |
说明 |
|---|---|---|
VCC |
pin1(3.3v) |
- |
GND |
pin6(GND) |
- |
SCL |
pin23 (SPI0_SCK) |
SPI串行时钟线 |
SDA |
pin19 (SPI0_MOSI) |
SPI串行数据输入端口 |
CSB |
pin24 (SPI0_CS0) |
- |
SDO |
pin21 (SPI0_MISO) |
- |
安装依赖库
sudo pip3 install spidev Jetson.GPIO
读取传感器数据脚本代码
#!/usr/bin/env python3
"""
GY-BMP280大气压强传感器SPI0接口读取脚本 - 滚动显示版本
每秒自动新增一条数据,在终端上滚动显示
"""
import time
import spidev
import Jetson.GPIO as GPIO
import os
import sys
# 引脚定义 (基于BOARD模式,物理引脚编号)
CSB_PIN = 24 # CSB片选引脚,必须设置为低电平启用SPI模式
# BMP280寄存器地址定义
BMP280_CHIP_ID = 0xD0
BMP280_RESET = 0xE0
BMP280_STATUS = 0xF3
BMP280_CTRL_MEAS = 0xF4
BMP280_CONFIG = 0xF5
BMP280_PRESS_MSB = 0xF7
BMP280_PRESS_LSB = 0xF8
BMP280_PRESS_XLSB = 0xF9
BMP280_TEMP_MSB = 0xFA
BMP280_TEMP_LSB = 0xFB
BMP280_TEMP_XLSB = 0xFC
# 校准参数寄存器地址
BMP280_DIG_T1 = 0x88
BMP280_DIG_T2 = 0x8A
BMP280_DIG_T3 = 0x8C
BMP280_DIG_P1 = 0x8E
BMP280_DIG_P2 = 0x90
BMP280_DIG_P3 = 0x92
BMP280_DIG_P4 = 0x94
BMP280_DIG_P5 = 0x96
BMP280_DIG_P6 = 0x98
BMP280_DIG_P7 = 0x9A
BMP280_DIG_P8 = 0x9C
BMP280_DIG_P9 = 0x9E
class BMP280_SPI0:
"""BMP280 SPI0接口驱动类"""
def __init__(self):
"""初始化BMP280传感器"""
print("初始化BMP280传感器...")
# 初始化GPIO
GPIO.setmode(GPIO.BOARD)
GPIO.setup(CSB_PIN, GPIO.OUT)
# 重要: CSB引脚必须设置为低电平启用SPI模式
GPIO.output(CSB_PIN, GPIO.LOW)
print("✅ CSB引脚设置为低电平,启用SPI模式")
# 初始化SPI - 使用SPI0总线
self.spi = spidev.SpiDev()
try:
# 使用SPI0总线,设备0
self.spi.open(0, 0)
self.spi.max_speed_hz = 1000000
self.spi.mode = 0
self.spi.bits_per_word = 8
self.spi.lsbfirst = False
print("✅ 成功打开SPI设备: /dev/spidev0.0")
except Exception as e:
print(f"❌ 无法打开SPI0设备: {e}")
raise e
# 初始化BMP280
self.init_bmp280()
print("✅ BMP280传感器初始化完成")
def write_register(self, register, value):
"""写入寄存器"""
tx_data = [register & 0x7F, value]
self.spi.xfer(tx_data)
def read_register(self, register):
"""读取寄存器"""
tx_data = [register | 0x80, 0x00]
rx_data = self.spi.xfer(tx_data)
return rx_data[1]
def read_registers(self, register, length):
"""连续读取多个寄存器"""
tx_data = [register | 0x80] + [0x00] * length
rx_data = self.spi.xfer(tx_data)
return rx_data[1:]
def init_bmp280(self):
"""初始化BMP280传感器"""
print("开始初始化BMP280传感器...")
# 检查芯片ID
chip_id = self.read_register(BMP280_CHIP_ID)
print(f"读取芯片ID: 0x{chip_id:02X}")
if chip_id != 0x58:
raise Exception(f"错误的芯片ID: 0x{chip_id:02X},预期: 0x58")
print(f"✅ 检测到BMP280传感器 (芯片ID: 0x{chip_id:02X})")
# 读取校准参数
self.read_calibration_data()
# 软复位
self.write_register(BMP280_RESET, 0xB6)
time.sleep(0.01)
# 等待传感器就绪
while self.read_register(BMP280_STATUS) & 0x01:
time.sleep(0.01)
# 配置传感器
ctrl_meas = (0x02 << 5) | (0x02 << 2) | 0x03
self.write_register(BMP280_CTRL_MEAS, ctrl_meas)
config = (0x05 << 5) | (0x04 << 2) | 0x00
self.write_register(BMP280_CONFIG, config)
print("✅ BMP280配置完成")
def read_calibration_data(self):
"""读取校准参数"""
print("读取BMP280校准参数...")
self.dig_T1 = self.read_uint16(BMP280_DIG_T1)
self.dig_T2 = self.read_int16(BMP280_DIG_T2)
self.dig_T3 = self.read_int16(BMP280_DIG_T3)
self.dig_P1 = self.read_uint16(BMP280_DIG_P1)
self.dig_P2 = self.read_int16(BMP280_DIG_P2)
self.dig_P3 = self.read_int16(BMP280_DIG_P3)
self.dig_P4 = self.read_int16(BMP280_DIG_P4)
self.dig_P5 = self.read_int16(BMP280_DIG_P5)
self.dig_P6 = self.read_int16(BMP280_DIG_P6)
self.dig_P7 = self.read_int16(BMP280_DIG_P7)
self.dig_P8 = self.read_int16(BMP280_DIG_P8)
self.dig_P9 = self.read_int16(BMP280_DIG_P9)
print("✅ 校准参数读取完成")
def read_uint16(self, register):
"""读取16位无符号整数 (小端序)"""
data = self.read_registers(register, 2)
return data[0] | (data[1] << 8)
def read_int16(self, register):
"""读取16位有符号整数 (小端序)"""
data = self.read_registers(register, 2)
value = data[0] | (data[1] << 8)
if value > 32767:
value -= 65536
return value
def read_raw_data(self):
"""读取原始温度和气压数据"""
# 读取气压数据 (20位)
press_data = self.read_registers(BMP280_PRESS_MSB, 3)
raw_pressure = (press_data[0] << 12) | (press_data[1] << 4) | (press_data[2] >> 4)
# 读取温度数据 (20位)
temp_data = self.read_registers(BMP280_TEMP_MSB, 3)
raw_temperature = (temp_data[0] << 12) | (temp_data[1] << 4) | (temp_data[2] >> 4)
return raw_temperature, raw_pressure
def compensate_temperature(self, raw_temperature):
"""补偿温度计算 (返回摄氏度)"""
var1 = ((raw_temperature >> 3) - (self.dig_T1 << 1)) * self.dig_T2 >> 11
var2 = (((raw_temperature >> 4) - self.dig_T1) * ((raw_temperature >> 4) - self.dig_T1) >> 12) * self.dig_T3 >> 14
t_fine = var1 + var2
temperature = (t_fine * 5 + 128) >> 8
return temperature / 100.0, t_fine
def compensate_pressure(self, raw_pressure, t_fine):
"""补偿气压计算 (返回hPa)"""
var1 = t_fine - 128000
var2 = var1 * var1 * self.dig_P6
var2 = var2 + ((var1 * self.dig_P5) << 17)
var2 = var2 + (self.dig_P4 << 35)
var1 = ((var1 * var1 * self.dig_P3) >> 8) + ((var1 * self.dig_P2) << 12)
var1 = ((1 << 47) + var1) * self.dig_P1 >> 33
if var1 == 0:
return 0
p = 1048576 - raw_pressure
p = (((p << 31) - var2) * 3125) // var1
var1 = (self.dig_P9 * (p >> 13) * (p >> 13)) >> 25
var2 = (self.dig_P8 * p) >> 19
p = ((p + var1 + var2) >> 8) + (self.dig_P7 << 4)
return p / 25600.0
def calculate_altitude(self, pressure, sea_level_pressure=1013.25):
"""根据气压计算海拔高度"""
altitude = 44330.0 * (1.0 - pow(pressure / sea_level_pressure, 0.1903))
return altitude
def read_sensor_data(self):
"""读取完整的传感器数据"""
try:
raw_temperature, raw_pressure = self.read_raw_data()
temperature, t_fine = self.compensate_temperature(raw_temperature)
pressure = self.compensate_pressure(raw_pressure, t_fine)
altitude = self.calculate_altitude(pressure)
return {
'temperature': temperature,
'pressure': pressure,
'altitude': altitude
}
except Exception as e:
print(f"读取传感器数据错误: {e}")
return None
def cleanup(self):
"""清理资源"""
try:
self.write_register(BMP280_CTRL_MEAS, 0x00)
self.spi.close()
except Exception as e:
print(f"清理资源时出错: {e}")
finally:
GPIO.cleanup()
def clear_screen():
"""清屏函数"""
os.system('clear')
def display_data_table(data_history, max_display_lines=20):
"""显示数据表格,支持滚动显示"""
clear_screen()
print("=" * 70)
print("GY-BMP280大气压强传感器实时数据监测 (滚动显示模式)")
print("=" * 70)
print("硬件连接: SPI0总线 | 每秒自动更新 | 按Ctrl+C退出")
print("=" * 70)
# 显示表头
print(f"{'序号':<6} {'时间(s)':<10} {'温度(°C)':<12} {'气压(hPa)':<14} {'海拔(m)':<12}")
print("-" * 70)
# 显示最新数据(最多显示max_display_lines行)
start_idx = max(0, len(data_history) - max_display_lines)
for i in range(start_idx, len(data_history)):
data = data_history[i]
seq_str = f"{i+1:<6}"
time_str = f"{data['elapsed_time']:05.1f}"
temp_str = f"{data['temperature']:.2f} °C"
pressure_str = f"{data['pressure']:.2f} hPa"
altitude_str = f"{data['altitude']:.2f} m"
print(f"{seq_str} {time_str:<10} {temp_str:<12} {pressure_str:<14} {altitude_str:<12}")
# 显示统计信息
if data_history:
latest = data_history[-1]
print("-" * 70)
print(f"最新数据: 温度 {latest['temperature']:.2f}°C | 气压 {latest['pressure']:.2f}hPa | 海拔 {latest['altitude']:.2f}m")
print(f"数据统计: 总记录 {len(data_history)} 条 | 运行时间 {latest['elapsed_time']:.1f} 秒")
print("=" * 70)
def main():
"""主函数"""
bmp280 = None
data_history = [] # 存储历史数据
max_display_lines = 15 # 最大显示行数
try:
# 初始化BMP280传感器
bmp280 = BMP280_SPI0()
print("\n✅ BMP280传感器准备就绪!")
print("开始读取传感器数据(每秒自动新增一条,滚动显示)...")
print("按Ctrl+C退出程序")
time.sleep(2) # 给用户时间阅读提示信息
read_count = 0
start_time = time.time()
while True:
try:
current_time = time.time()
elapsed_time = current_time - start_time
# 读取传感器数据
sensor_data = bmp280.read_sensor_data()
if sensor_data is not None:
# 添加时间戳并保存到历史数据
data_record = {
'elapsed_time': elapsed_time,
'temperature': sensor_data['temperature'],
'pressure': sensor_data['pressure'],
'altitude': sensor_data['altitude'],
'timestamp': current_time
}
data_history.append(data_record)
read_count += 1
# 更新显示
display_data_table(data_history, max_display_lines)
# 每秒更新一次
next_update = current_time + 1.0
sleep_time = next_update - time.time()
if sleep_time > 0:
time.sleep(sleep_time)
else:
# 读取失败时显示错误信息
clear_screen()
print("❌ 读取传感器数据失败,尝试重新读取...")
time.sleep(1)
except KeyboardInterrupt:
break
except Exception as e:
clear_screen()
print(f"❌ 读取数据时出错: {e}")
time.sleep(1)
# 计算统计信息
total_time = time.time() - start_time
avg_rate = read_count / total_time if total_time > 0 else 0
clear_screen()
print("=" * 70)
print("数据读取完成统计")
print("=" * 70)
print(f"总读取次数: {read_count}")
print(f"总运行时间: {total_time:.1f} 秒")
print(f"平均读取频率: {avg_rate:.2f} Hz")
print("=" * 70)
except KeyboardInterrupt:
print("\n\n程序被用户中断")
except Exception as e:
print(f"\n❌ 发生错误: {e}")
import traceback
traceback.print_exc()
finally:
if bmp280 is not None:
bmp280.cleanup()
print("程序退出")
if __name__ == "__main__":
# 检查权限
if os.geteuid() != 0:
print("注意:此程序需要sudo权限运行")
print("请使用: sudo python3 bmp280_rolling_display.py")
exit(1)
# 检查依赖库
try:
import spidev
import Jetson.GPIO as GPIO
except ImportError as e:
print(f"❌ 缺少依赖库: {e}")
print("请安装: sudo pip3 install spidev Jetson.GPIO")
exit(1)
main()
实验结果