树莓派初体验
吃灰很久的树莓派总算拿出来用了几下了, 浅浅记录一下.
给 apt 换源
这个 Ubuntu 和树莓派(debian)什么的基本上一样.
编辑/etc/apt/sources.list
, 用 vi/vim 或 nano 啥的都没什么所谓.
sudo vim /etc/apt/sources.list
注释掉其它东西, 加上这个东西.
deb https://mirrors.tuna.tsinghua.edu.cn/debian bookworm main contrib non-free-firmware
deb https://mirrors.tuna.tsinghua.edu.cn/debian-security bookworm-security main contrib non-free-firmware
deb https://mirrors.tuna.tsinghua.edu.cn/debian bookworm-updates main contrib non-free-firmware
然后编辑/etc/apt/sources.list.d/raspi.list
sudo vim /etc/apt/sources.list.d/raspi.list
注释其它东西, 加上这个
deb https://mirrors.tuna.tsinghua.edu.cn/raspberrypi bookworm main
完成之后运行这个
sudo apt update
sudo apt full-upgrade
这样就换好了.
使用 wiringPi
玩树莓派主要还是因为接口吧, 如果只是为了 linux 的话感觉不如 wsl.
如果用 python 的话需要下载其他库, 我这里打算用 c, 下载 wiringPi
拉取 wiringPi 代码
sudo apt install git # 如果没有git的话
git clone https://github.com/WiringPi/WiringPi.git
导航至代码根目录并编译代码
cd WiringPi
./build debian
安装 wiringPi, 这里可能版本不一样, 注意一下拉取的版本
mv debian-template/wiringpi-xxx.deb .
sudo apt install ./wiringpi-xxx.deb
这样就安装好了, 可以使用gpio readall
看看自己的 gpio 接口.
之后要编译使用 wiringPi 的代码时, 要带上 wiringPi
gcc -o app app.c -lwiringPi
即使不写代码, 也可以通过这个库来做一些简单的事情.
可以拿一个 led 灯或者发光二极管什么的, 我买的 led 模块带 gnd, vcc, in, 使用gpio readall
查看自己的引脚, 看着图插一下.
gnd 对应地线, 插 Name 为 0V 的, vcc 是电源, 插 Name 为 3.3V 或者 5V 的. in 的话找个 Name 以 GPIO 开头的引脚就可以了.
使用这个命令来设置引脚的状态
gpio mode 引脚编号 out
这个引脚编号是 wiringPi 的编号, 对应 wPi, 不要找成 BCM 了. 找刚刚插 in 的引脚.
然后可以用这个命令来控制高低电平
gpio write 引脚编号 1 # 0的话就是低电平
没意外的话 led 灯该亮了, 调成低电平就不亮.
gpio read 引脚编号
这个可以查看某个引脚的电平
一般是用程序来控制引脚情况而不是在命令行控制. 可以参考一下这个代码.
#include <stdio.h>
#include <wiringPi.h>
int main(void) {
if (wiringPiSetup() == -1) {
printf("init error");
return 1;
}
int pin = 7;
pinMode(pin, OUTPUT);
printf("start");
while(1) {
digitalWrite(pin, HIGH);
delay(500);
digitalWrite(pin, LOW);
delay(500);
}
return 0;
}
这个是 wiringPi 仓库给的示例代码, 没意外的话执行的结果是亮 0.5s 再暗 0.5s, 一直循环.
使用 rust 控制 gpio
由于我最近在学 rust, 找了一下 rust 相关的库, 还真找到了, 叫 rppal, 地址在这里.
如果要使用的话, 在 Cargo.toml 里加上
[dependencies]
rppal = "0.22.1"
build 或者 run 之后就会下载并使用库了.
下面是一个控制 led 的示例代码
use std::error::Error;
use std::thread;
use std::time::Duration;
use rppal::gpio::Gpio;
fn main() -> Result<(), Box<dyn Error>> {
let mut pin = Gpio::new()?.get(23)?.into_output();
for _ in 0..5 {
pin.set_high();
thread::sleep(Duration::from_millis(500));
pin.set_low();
thread::sleep(Duration::from_millis(500));
}
Ok(())
}
这个库使用的是 BCM 编号, 找的时候不要找错了.
使用 I2C
我买了个温湿度模块, 用的是 I2C 通信, 也稍微记录一下.
感觉有点买错了, 网上没多少树莓派用这个模块?不过好在有文档在. 文档.
在 5.2 传感器读取流程有写该怎么做.
有一个地方需要注意, 连线顺序应该是先连接地线, 再连接电源, 然后才是两条数据线; 拔线时也是一样的, 先拔出数据线, 然后是电源, 最后才是地线, 不要先拔电源或者先接数据线!我的树莓派就因为拔线顺序错了, 产生了错误的电流路径, 然后文件系统坏了, 最后只能重刷了.
下面是用c写的代码
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <wiringPi.h>
#include <wiringPiI2C.h>
#include <unistd.h>
#define AHT30_ADDR 0x38
// commands
#define AHT30_CMD_INIT 0xE1
#define AHT30_CMD_MEASURE 0xAC
#define AHT30_CMD_SOFT_RESET 0xBA
unsigned char Calc_CRC8(unsigned char *message, unsigned char Num) {
unsigned char i;
unsigned char byte;
unsigned char crc = 0xFF;
for (byte = 0; byte < Num; byte++) {
crc ^= (message[byte]);
for (i = 8; i > 0; --i) {
if (crc & 0x80)
crc = (crc << 1) ^ 0x31;
else
crc = (crc << 1);
}
}
return crc;
}
int init_sensor(int fd) {
wiringPiI2CWrite(fd, AHT30_CMD_SOFT_RESET);
delay(20);
printf("Sensor initialized\n");
return 0;
}
int read_sensor_data(int fd, float *temperature, float *humidity) {
uint8_t cmd[3] = {AHT30_CMD_MEASURE, 0x33, 0x00};
uint8_t data[7];
uint32_t st, srh;
int status;
if (write(fd, cmd, 3) != 3) {
printf("Error: Failed to send measurement command\n");
return -1;
}
delay(80);
if (read(fd, data, 7) != 7) {
printf("Error: Failed to read sensor data\n");
return -1;
}
// 检查状态 (Bit7, 1=busy, 0=ready)
status = data[0];
if (status & 0x80) {
printf("Error: Sensor busy, measurement not ready\n");
return -1;
}
// 检查
unsigned char crc = Calc_CRC8(data, 6);
if (crc != data[6]) {
printf("Error: CRC check failed. Expected: %02X, Got: %02X\n", data[6], crc);
return -2;
}
// 提取湿度 (20 bits)
srh = ((uint32_t)data[1] << 12) | ((uint32_t)data[2] << 4) | (data[3] >> 4);
// 提取温度 (20 bits)
st = ((uint32_t)(data[3] & 0x0F) << 16) | ((uint32_t)data[4] << 8) | data[5];
// 计算
*humidity = (float)srh / 1048576.0 * 100.0;
*temperature = (float)st / 1048576.0 * 200.0 - 50.0;
return 0;
}
int main() {
int fd;
float temperature, humidity;
if (wiringPiSetup() == -1) {
printf("WiringPi initialization failed\n");
return 1;
}
fd = wiringPiI2CSetup(AHT30_ADDR);
if (fd == -1) {
printf("Failed to init I2C communication\n");
return 2;
}
if (init_sensor(fd) != 0) {
printf("Sensor initialization failed\n");
return 3;
}
delay(10);
if (read_sensor_data(fd, &temperature, &humidity) == 0) {
printf("Temperature: %.1f °C\n", temperature);
printf("Humidity: %.1f %%RH\n", humidity);
} else {
printf("Failed to read sensor data\n");
return 4;
}
return 0;
}
这是一个用 rust 写的示例代码
use rppal::i2c::I2c;
use std::thread;
use std::time::Duration;
const AHT30_ADDR: u16 = 0x38;
const AHT30_CMD_RESET: u8 = 0xba;
const AHT30_CMD_MEASURE: u8 = 0xac;
fn calc_crc8(message: &[u8], num: usize) -> u8 {
let mut crc = 0xff_u8;
for byte in message.iter().take(num) {
crc ^= byte;
for _ in 0..8 {
if (crc & 0x80) != 0 {
crc = (crc << 1) ^ 0x31;
} else {
crc <<= 1;
}
}
}
crc
}
fn init_aht30(i: &mut I2c) -> Result<(), rppal::i2c::Error> {
let command: [u8; 1] = [AHT30_CMD_RESET];
i.write(&command[..])?;
thread::sleep(Duration::from_millis(20));
println!("init over");
Ok(())
}
fn read_data(i: &mut I2c) -> Result<(f32, f32), String> {
let command: [u8; 3] = [AHT30_CMD_MEASURE, 0x33, 0x00];
i.write(&command)
.map_err(|e| format!("send error, {}", e))?;
thread::sleep(Duration::from_millis(80));
let mut data = [0u8; 7];
i.read(&mut data)
.map_err(|e| format!("read error: {}", e))?;
let status = data[0];
if (status & 0x80) != 0 {
return Err("sensor busy".to_string());
}
let crc = calc_crc8(&data[0..6], 6);
if crc != data[6] {
return Err(format!(
"crc check failed. Expected {:02X}, Got {:02X}",
data[6], crc
));
}
let srh: u32 = ((data[1] as u32) << 12) | ((data[2] as u32) << 4) | ((data[3] >> 4) as u32);
let st: u32 = (((data[3] & 0x0f) as u32) << 16) | ((data[4] as u32) << 8) | (data[5] as u32);
let humidity = (srh as f32) / 1048576.0 * 100.0;
let temperature = (st as f32) / 1048576.0 * 200.0 - 50.0;
Ok((temperature, humidity))
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut instance = I2c::new()?;
instance.set_slave_address(AHT30_ADDR)?;
thread::sleep(Duration::from_millis(5));
init_aht30(&mut instance)?;
match read_data(&mut instance) {
Ok((temperature, humidity)) => {
println!("temperature: {:.2}", temperature);
println!("humidity: {:.2}%", humidity);
}
Err(e) => {
println!("Error : {}", e);
}
}
Ok(())
}
calc_crc8 是文档给的, 不过是用 C 写的, 这里用 rust 写了. 这个 AHT30_ADDR
是通过这个命令得到的
i2cdetect -y 1
如果连线正确的话, 应该能看到一个数字, 那个就是地址. 不管是 wiringPi, 还是 rppal, 貌似都自动发送了 0x70 写指令和 0x71 读指令, 这里就没有写了.