OrangePi ZERO 3

最近刷视频突然有了个买块开发板的念头,而且这个念头一形成就挥之不去;忍耐了几周,终于还是下手了!

由于是第一次买Orangepi,且网上关于Orangepi的资料也不如Raspberry pi,故记录一下使用过程,方便之后查阅。

Ubuntu 系统

参照网上教程,不多赘述

不过需注意一点:要找适合自己开发板内存的系统镜像

刷完使用串口线连接电脑与板子即可,我使用的工具是:MobaXterm

部署博客

开发板架构

刷完系统先看下镜像版本与开发板 CPU 架构

  • 查看版本
1
lsb_release -a	
  • 查看架构
1
uname -m (或 arch)		

返回参数说明:

  • x86_64、x64、AMD64 是同一个东西,都为x86架构;

  • aarch64 是 ARM 架构的 64 位版本

要根据架构选择对应的源!否则在 sudo apt update 时会出错!

(若开发板的cpu是arm架构,默认匹配都是x86架构处理器的软件包,用错源会找不到所需的包信息,就会报错)

连接WiFi

若接了网线可跳过此步

  • 查看附近所有wifi
1
nmcli dev wifi list	
  • 连接wifi
1
sudo nmcli device wifi connect "WiFi名称" password "密码"	
  • 查看连接wifi后的ip,方便之后连接SSH
1
ifconfig	

换源

我刷的系统为 Ubuntu 22.04,不同版本系统可能会有差异,可百度对应版本换源方法;此处以22.04为例:

  • 先备份
1
sudo cp /etc/apt/sources.list /etc/apt/sources.list.bk	
  • 修改
1
sudo vim /etc/apt/sources.list

将以下内容覆盖即可:

此处用的是 中科大源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 默认注释了源码仓库,如有需要可自行取消注释
deb https://mirrors.ustc.edu.cn/ubuntu-ports/ jammy main restricted universe multiverse
# deb-src https://mirrors.ustc.edu.cn/ubuntu-ports/ jammy main restricted universe multiverse

deb https://mirrors.ustc.edu.cn/ubuntu-ports/ jammy-security main restricted universe multiverse
# deb-src https://mirrors.ustc.edu.cn/ubuntu-ports/ jammy-security main restricted universe multiverse

deb https://mirrors.ustc.edu.cn/ubuntu-ports/ jammy-updates main restricted universe multiverse
# deb-src https://mirrors.ustc.edu.cn/ubuntu-ports/ jammy-updates main restricted universe multiverse

deb https://mirrors.ustc.edu.cn/ubuntu-ports/ jammy-backports main restricted universe multiverse
# deb-src https://mirrors.ustc.edu.cn/ubuntu-ports/ jammy-backports main restricted universe multiverse

# 预发布软件源,不建议启用
# deb https://mirrors.ustc.edu.cn/ubuntu-ports/ jammy-proposed main restricted universe multiverse
# deb-src https://mirrors.ustc.edu.cn/ubuntu-ports/ jammy-proposed main restricted universe multiverse

改时区

1
sudo orangepi-config

找到 timezone ,改为 Asia shanghai 保存并退出即可

  • 查看系统日期及时间
1
date

更新

依次执行即可:

1
2
sudo apt-get update
sudo apt-get upgrade

部署 hexo 博客

参考文章用树莓派服务器运行Hexo博客网页 - 知乎

跟着上面文章来就行,我这里简单记录一下使用到的命令

1
ssh-copy-id -p 22 orangepi@192.168.10.11
  • 安装git
1
sudo apt-get install git
  • 查看是否安装成功
1
git --version
  • 安装nginx
1
sudo apt-get install nginx
  • 查看是否安装成功
1
nginx -v
  • 其他

以下操作最好不要使用root用户

1
2
3
4
5
6
7
8
sudo mkdir /var/repo/
sudo chown -R $USER:$USER /var/repo/
sudo chmod -R 755 /var/repo/
cd /var/repo/
git init --bare web_blog.git
sudo mkdir -p /var/www/hexo
sudo chown -R $USER:$USER /var/www/hexo
sudo chmod -R 755 /var/www/hexo
  • 脚本
1
sudo vim /var/repo/web_blog.git/hooks/post-receive

填写如下内容:

1
2
#!/bin/bash
git --work-tree=/var/www/hexo --git-dir=/var/repo/web_blog.git checkout -f
  • 赋予脚本可执行权限
1
sudo chmod +x /var/repo/web_blog.git/hooks/post-receive
  • 修改nginx配置
1
sudo vim /etc/nginx/sites-available/default

将其中路径改为你自己的hexo目录的路径

  • 重启nginx即可
1
sudo service nginx restart

内网穿透

因为我还有个服务器,故这里直接用frp穿透

赋予可执行权限:

1
chmod +x frpc

贴一下frpc.ini的配置内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[common]
server_addr = 服务器外网IP
server_port = 7000
authentication_method = token
token = 自己想个密码

[SSH]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 6000

[Web]
type = tcp
local_ip = 127.0.0.1
local_port = 80
remote_port = 3000

  • 使其开机自启
1
sudo vim /etc/systemd/system/frpc.service

填入以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
[Unit]
Description=FRP Client Service

[Service]
Type=simple
Restart=always
RestartSec=3
ExecStart=/home/orangepi/frp/frpc -c /home/orangepi/frp/frpc.ini
# 这里应为自己frp路径

[Install]
WantedBy=multi-user.target
  • 使其生效
1
2
3
4
5
6
7
8
9
10
11
# 重载
sudo systemctl daemon-reload

# 开机自启
sudo systemctl enable frpc.service

# 启动
sudo systemctl start frpc.service

# 运行状态
sudo systemctl status frpc.service

散热

可以搞个 外壳+散热片+散热扇 淘宝有自己搜

  • 获取CPU温度
1
cat /sys/class/thermal/thermal_zone0/temp

上面的值除以1000即为CPU温度

有了CPU温度即可编程实现温度高于多少度则自动开风扇、低于多少度自动关;

哦,对了,可能需要个三极管来做开关;因为板子上引脚电压是没办法使用编程来控制高低电平的

编程

在SOC上控制引脚与在单片机上略有不同

不过Orangepi zero 3自带了 WiringPi库可用来控制引脚,直接使用该库即可,库函数都封装好的,直接调用即可;

外设

我还买了DHT11与OLED屏用来检测并显示一些内容,代码也给贴一下,注释还是比较详细的:

获取ip并输出到文件

  • 创建脚本
1
vim get_ip.sh

脚本内容如下:

1
2
3
4
5
# 获取IP地址
IP_ADDRESS=$(ifconfig wlan0 | grep "inet " | awk '{print $2}')

# 将IP地址保存到文件
echo $IP_ADDRESS > /home/orangepi/code/ip_adr.txt

赋予脚本可执行权限

1
sudo chmod +x get_ip.sh
  • 定时执行
1
2
3
4
crontab -e

# 10min执行一次,后边为脚本所在路径
*/10 * * * * /home/orangepi/code/get_ip.sh

整体C代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <wiringPi.h>

#include "font.h"
#include "oled.h"

#define DHT11_IO 8 // DHT11 DATA脚引脚号 15脚 IO-3

unsigned char humidity = 0;
float temperature = 0.0;
float temp_old = 0.0;

unsigned char cpu_buf[6] = {0};
unsigned char ip_buf[20] = {0};

struct display_info disp;

int get_cpu_temp(void) {
float cpu_temp = 0.0;
char path[] = "/sys/class/thermal/thermal_zone0/temp";
int fd = 0;

fd = open(path, O_RDONLY);
if (fd < 0) {
perror("open");
return -1;
}

lseek(fd, 0, 0);
read(fd, cpu_buf, sizeof(cpu_buf));
cpu_temp = ((cpu_buf[0] - 48) * 10) + (cpu_buf[1] - 48) +
((cpu_buf[2] - 48) / 10.0) + ((cpu_buf[3] - 48) / 100.0);
printf("CPU:%.2f\n", cpu_temp);
close(fd);
}

int get_ip_adr(void) {
char path[] = "/home/orangepi/code/ip_adr.txt";
int fd = 0;

fd = open(path, O_RDONLY);
if (fd < 0) {
perror("open");
return -1;
}

lseek(fd, 0, 0);
read(fd, ip_buf, sizeof(ip_buf));
printf("IP:%s\n", ip_buf);
close(fd);
}

void send_start_signal() {
// 输出
pinMode(DHT11_IO, OUTPUT);
digitalWrite(DHT11_IO, HIGH);
digitalWrite(DHT11_IO, LOW);
delay(20); // 18ms < 延时 < 30ms
digitalWrite(DHT11_IO, HIGH);

// 输入
pinMode(DHT11_IO, INPUT);
pullUpDnControl(DHT11_IO, PUD_UP);
delayMicroseconds(35);
while (!digitalRead(DHT11_IO)); // 等待 DHT11低电平应答 83us 结束
}

unsigned char get_dht11_data(void) {
// 主机发送起始信号
send_start_signal();

unsigned char dht11_buf[5] = {0};
// 解析并保存数据
for (unsigned char i = 0; i < 5; i++) {
for (unsigned char j = 0; j < 8; j++) {
while (digitalRead(
DHT11_IO)); // 等待 DHT11高电平通知主机准备接收数据 87us 结束
while (!digitalRead(DHT11_IO)); // 等待回复的54us低电平结束

// 接收回复的高电平 延时35us看是什么电平 -
// 高电平则发的为1,低电平发送的为0
delayMicroseconds(35);

// 保存数据
dht11_buf[i] <<= 1;
if (digitalRead(DHT11_IO) == 1) {
dht11_buf[i]++; // 如果为1,则加上1
}
}
}

// 校验
float temp = 0;
if (dht11_buf[4] ==
(dht11_buf[0] + dht11_buf[1] + dht11_buf[2] + dht11_buf[3])) {
temp = dht11_buf[3] / 10.0;
temperature = dht11_buf[2] + temp;
humidity = dht11_buf[0];

// 高于45度的数据使用上次数据代替
if (temp_old != temperature && temperature < 45) {
temp_old = temperature;
} else {
temperature = temp_old;
}

printf("TEMP:%.2f\n", temperature);
printf("HUM:%d\n", humidity);

return 0;
} else {
return 1;
}
}

int oled_show(struct display_info* disp) {
char buf_t[50] = {0};
char buf_h[50] = {0};

// 环境温度
sprintf(buf_t, "TEMP:%.2f", temperature);
oled_putstrto(disp, 0, 18 + 5, buf_t);
disp->font = font1;

// 环境湿度
sprintf(buf_h, "HUM:%d", humidity);
oled_putstrto(disp, 0, 27 + 7, buf_h);
disp->font = font1;

// cpu温度
char cpu_b[50] = {'C', 'P', 'U', ':', '0', '0', '.'};
memcpy(&cpu_b[4], &cpu_buf[0], 1);
memcpy(&cpu_b[5], &cpu_buf[1], 1);
memcpy(&cpu_b[7], &cpu_buf[2], 1);
memcpy(&cpu_b[8], &cpu_buf[3], 1);
oled_putstrto(disp, 0, 36 + 9, cpu_b);
disp->font = font1;

// 本地ip
char ip_b[50] = {'I', 'P', ':'};
memcpy(&ip_b[3], &ip_buf[0], sizeof(ip_buf));
oled_putstrto(disp, 0, 45 + 11, ip_b);
disp->font = font1;

oled_send_buffer(disp);
}

void show_error(int err, int add) { printf("\nERROR: %i, %i\n\n", err, add); }

void show_usage(char* progname) {
printf("\nUsage:\n%s <I2C bus device node >\n", progname);
}

void* pth_fun(void* date) {
while (1) {
get_ip_adr();
get_cpu_temp();
get_dht11_data();
oled_clear(&disp);
oled_show(&disp);
// delay(2000);
pthread_exit(NULL);
}
}

int main(int argc, char* argv[]) {
int err = 0;
char filename[32] = {0};

// 判断有无指定 IIC 通道
if (argc < 2) {
show_usage(argv[0]);
return -1;
}

if (wiringPiSetup() == -1) {
printf("Setup wiringPi failed!");
return 1;
}

// 初始化 disp 结构体
memset(&disp, 0, sizeof(disp));

// 将 IIC 通道、IIC 设备地址、字体信息 写入 disp 结构体
sprintf(filename, "%s", argv[1]);
disp.address = OLED_I2C_ADDR;
disp.font = font2;

err = oled_open(&disp, filename);

if (err < 0) {
show_error(1, err);
} else {
err = oled_init(&disp);
if (err < 0) {
show_error(2, err);
} else {
printf("---oled ready---\n");
}
}

oled_clear(&disp);
while (1) {
pthread_t thread_id;
int p = pthread_create(&thread_id, NULL, pth_fun, NULL);
if (p < 0) {
perror("pthread_create");
return -1;
}
sleep(2);
}

return 0;
}

编译:gcc main.c -o main -lwiringPi -lwiringPiDev -lpthread -lcrypt -lrt

执行:sudo ./main /dev/i2c-3

使用了线程,因为不用线程的话直接运行占用内存与CPU比较大,改成这样好多了。由于对Linux编程并不是很熟,暂时只能先这样用,之后再慢慢研究研究。

  • 开机自启
1
sudo vim /etc/systemd/system/my_main.service

填入如下内容:

1
2
3
4
5
6
7
8
9
10
11
[Unit]
Description=Run dht11 and oled

[Service]
Type=simple
Restart=always
RestartSec=3
ExecStart=/home/orangepi/code/main /dev/i2c-3

[Install]
WantedBy=multi-user.target
  • 启用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 重载
sudo systemctl daemon-reload

# 开机自启
sudo systemctl enable my_main.service

# 启动
sudo systemctl start my_main.service

# 运行状态
sudo systemctl status my_main.service

# 查看所有已启动的服务
sudo systemctl list-units --type=service
  • 停用
1
2
3
4
5
6
7
8
9
10
11
# 停止
sudo systemctl stop my_main.service

# 禁止开机自启
sudo systemctl disable my_main.service

# 删除
sudo rm /etc/systemd/system/my_main.service

# 重载
sudo systemctl daemon-reload

还可以加一层保底,以防程序自己退出或被杀进程

  • 创建脚本
1
vim start_main.sh

填入如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/bash  

# 设定要检查的进程名
PROCESS_NAME="main"

# 使用pgrep检查进程是否存在
# 如果pgrep返回了进程ID(即$?为0),则进程正在运行
if pgrep -x "$PROCESS_NAME" > /dev/null
then
echo "$PROCESS_NAME is already running."
else
# 如果进程未运行,则启动
echo "Starting $PROCESS_NAME..."
sudo systemctl restart my_main.service
# 检查main是否成功启动
sleep 2 # 等待几秒以确保程序有时间启动
if pgrep -x "$PROCESS_NAME" > /dev/null
then
echo "$PROCESS_NAME started successfully."
else
echo "Failed to start $PROCESS_NAME."
fi
fi

赋予可执行权限

1
chmod +x start_main.sh

定时执行

1
2
3
4
crontab -e

# 30min执行一次上面的脚本
*/30 * * * * /home/orangepi/code/start_main.sh

其他

还想部署一下alist来着,不过docker仓库被墙了,这里记一下怎样本地部署

首先需要将alist包下载下来,然后上传到开发板

  • 开始安装
1
docker load -i alist.tar.gz
  • 运行
1
2
3
4
5
6
7
8
docker run -d --restart=always \
-v /home/orangepi/alist:/opt/alist/data \
-p 5000:5244 \
-e PUID=$(id -u) \
-e PGID=$(id -g) \
-e UMASK=022 \
--name alist \
xhofe/alist:latest

解释一下:

docker run -d -p 5000:5244 –name alist xhofe/alist:latest
-d作用:在后台运行容器,并且打印容器id
-t作用:分配一个伪TTY
-i作用:即使没有attached,也要保持STDIN打开状态
-p: 将主机的端口映射到容器的一个端口 主机端口:容器内部的端口
–name:给容器起一个名字,比如叫做alist

  • 获取alist密码
1
docker exec -it alist ./alist admin log
  • 重置密码
1
docker exec -it alist ./alist admin set your_new_password
  • 其他
1
2
3
4
ps -ef | grep alist  # 查看alist进程
ps -ef | grep frps

find / -name xxx # 查找名字为xxx的文件