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
  • 其他
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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
/*
编译:
gcc -O2 -Wall -Wextra -o sensor_app main.c -lwiringPi -lpthread

gcc main.c -o sensor_app -lwiringPi -lpthread

执行:
sudo ./sensor_app /dev/i2c-3
*/

#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#include <wiringPi.h>

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

/* 硬件相关定义 */
#define DHT11_PIN 8 // DHT11 DATA引脚 15脚 PC8 IO-3
#define I2C_DEVICE_ADDR 0x3C // OLED I2C地址
#define CPU_TEMP_PATH "/sys/class/thermal/thermal_zone0/temp"
#define IP_ADDR_PATH "/home/orangepi/code/ip_adr.txt"
#define WATCHDOG_TIMEOUT 10 // 看门狗超时时间(秒)
#define FILTER_WINDOW_SIZE 5 // 滤波窗口大小

/* 全局数据结构 */
typedef struct
{
// 传感器数据
float env_temp;
float cpu_temp;
unsigned char humidity;
char ip_addr[20];

// 时间数据
char time_str[9];
char date_str[11];

// 滤波数据
float temp_buffer[FILTER_WINDOW_SIZE];
unsigned char humi_buffer[FILTER_WINDOW_SIZE];
unsigned char filter_index;

// 看门狗
time_t last_update;

// 同步锁
pthread_mutex_t lock;
} sensor_data_struct;
sensor_data_struct sensor_data;

struct display_info disp;

/* 初始化GPIO */
int init_gpio(void)
{
if (wiringPiSetup() == -1)
{
fprintf(stderr, "Failed to initialize wiringPi\n");
return -1;
}
return 0;
}

/* 温度滤波 */
static void update_filtered_temp(float new_temp)
{
pthread_mutex_lock(&sensor_data.lock);

sensor_data.temp_buffer[sensor_data.filter_index % FILTER_WINDOW_SIZE] = new_temp;
float sum = 0;
for (int i = 0; i < FILTER_WINDOW_SIZE; i++)
{
sum += sensor_data.temp_buffer[i];
}
sensor_data.env_temp = sum / FILTER_WINDOW_SIZE;

pthread_mutex_unlock(&sensor_data.lock);
}

/* 湿度滤波 */
static void update_filtered_humi(unsigned char new_humi)
{
pthread_mutex_lock(&sensor_data.lock);

sensor_data.humi_buffer[sensor_data.filter_index % FILTER_WINDOW_SIZE] = new_humi;
unsigned short sum = 0;
for (int i = 0; i < FILTER_WINDOW_SIZE; i++)
{
sum += sensor_data.humi_buffer[i];
}
sensor_data.humidity = sum / FILTER_WINDOW_SIZE;

pthread_mutex_unlock(&sensor_data.lock);
}

/* 读取CPU温度 */
static void read_cpu_temp(void)
{
static int fd = -1;
char buf[8] = {0};

if (fd < 0 && (fd = open(CPU_TEMP_PATH, O_RDONLY)) < 0)
{
perror("Open CPU temp");
return;
}

lseek(fd, 0, SEEK_SET);
if (read(fd, buf, sizeof(buf)) > 0)
{
float temp = atoi(buf) / 1000.0;
pthread_mutex_lock(&sensor_data.lock);
sensor_data.cpu_temp = temp;
pthread_mutex_unlock(&sensor_data.lock);
}
}

/* 读取IP地址 */
static void read_ip_address(void)
{
static time_t last_read = 0;
time_t now = time(NULL);

if (now - last_read > 3600)
{
FILE* fp = fopen(IP_ADDR_PATH, "r");
if (fp)
{
pthread_mutex_lock(&sensor_data.lock);
if (fgets(sensor_data.ip_addr, sizeof(sensor_data.ip_addr), fp) == NULL)
{
printf("Failed to open ip_adr.txt\n");
}
pthread_mutex_unlock(&sensor_data.lock);
fclose(fp);
last_read = now;
}
}
}

/* 读取DHT11数据 */
static int read_dht11(void)
{
uint8_t data[5] = {0};
const int max_retry = 10;
int retry = 0;

for (retry = 0; retry < max_retry; retry++)
{
uint32_t timeout;

// 发送启动信号
pinMode(DHT11_PIN, OUTPUT);
digitalWrite(DHT11_PIN, HIGH);
digitalWrite(DHT11_PIN, LOW);
delay(20); // 18ms < 延时 < 30ms
digitalWrite(DHT11_PIN, HIGH);

// 等待响应
pinMode(DHT11_PIN, INPUT);
pullUpDnControl(DHT11_PIN, PUD_UP);
delayMicroseconds(35); // 35us
timeout = micros();
while (!digitalRead(DHT11_PIN))
{
if (micros() - timeout > 250) // 250ms
{
printf("nack1\n");
goto OCCUR_ERROR;
}
}

// 解析并保存数据
for (unsigned char i = 0; i < 5; i++)
{
for (unsigned char j = 0; j < 8; j++)
{
// 等待 DHT11高电平通知主机准备接收数据 87us 结束
timeout = micros();
while (digitalRead(DHT11_PIN))
{
if (micros() - timeout > 250) // 250ms
{
printf("nack2\n");
goto OCCUR_ERROR;
}
}

timeout = micros();
// 等待回复的54us低电平结束
while (!digitalRead(DHT11_PIN))
{
if (micros() - timeout > 250) // 250ms
{
printf("nack3\n");
goto OCCUR_ERROR;
}
}

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

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

// 校验
if (data[4] == (data[0] + data[1] + data[2] + data[3]))
{
update_filtered_temp(data[2] + data[3] * 0.1);
update_filtered_humi(data[0]);
sensor_data.filter_index++;

printf("TEMP:%.1f RH:%d\n", data[2] + data[3] * 0.1, data[0]);
return 0;
}

OCCUR_ERROR:
delay(250); // 重试间隔 250ms
}
return -1;
}

/* 初始化OLED */
int init_oled(char* i2c_dev)
{
memset(&disp, 0, sizeof(disp));
disp.address = I2C_DEVICE_ADDR;
disp.font = font2;

if (oled_open(&disp, i2c_dev) < 0 || oled_init(&disp) < 0)
{
fprintf(stderr, "OLED initialization failed\n");
return -1;
}
return 0;
}

/* 时间更新 */
static void update_time_date(void)
{
time_t now = time(NULL);
struct tm* tm_info = localtime(&now);

pthread_mutex_lock(&sensor_data.lock);
strftime(sensor_data.time_str, sizeof(sensor_data.time_str), "%H:%M", tm_info);
strftime(sensor_data.date_str, sizeof(sensor_data.date_str), "%Y-%m-%d", tm_info);
pthread_mutex_unlock(&sensor_data.lock);
}

/* 显示时间 */
static void show_time_screen(void)
{
char time_buf[16], date_buf[16];

pthread_mutex_lock(&sensor_data.lock);
strncpy(time_buf, sensor_data.time_str, sizeof(time_buf));
strncpy(date_buf, sensor_data.date_str, sizeof(date_buf));
pthread_mutex_unlock(&sensor_data.lock);

disp.font = font2;
oled_putstrto(&disp, 0, 15, time_buf);
oled_putstrto(&disp, 50, 15, date_buf);
oled_send_buffer(&disp);
}

/* 显示传感器数据 */
static void show_sensor_screen(void)
{
char buf[32];
float env_temp, cpu_temp;
unsigned char humidity;
char ip[20];

pthread_mutex_lock(&sensor_data.lock);
env_temp = sensor_data.env_temp;
humidity = sensor_data.humidity;
cpu_temp = sensor_data.cpu_temp;
strncpy(ip, sensor_data.ip_addr, sizeof(ip));
pthread_mutex_unlock(&sensor_data.lock);

// oled_clear(&disp);
disp.font = font2;

snprintf(buf, sizeof(buf), "TEMP:%.1fC", env_temp);
oled_putstrto(&disp, 0, 29, buf);

snprintf(buf, sizeof(buf), "RH:%d%%", humidity);
oled_putstrto(&disp, 74, 29, buf);

snprintf(buf, sizeof(buf), "CPU:%.1fC", cpu_temp);
oled_putstrto(&disp, 0, 43, buf);

snprintf(buf, sizeof(buf), "IP:%s", ip);
oled_putstrto(&disp, 0, 57, buf);

oled_send_buffer(&disp);
}

/* 看门狗线程 */
void* watchdog_thread(void* arg)
{
(void)arg;

while (1)
{
sleep(WATCHDOG_TIMEOUT);

time_t now = time(NULL);
pthread_mutex_lock(&sensor_data.lock);
time_t last = sensor_data.last_update;
pthread_mutex_unlock(&sensor_data.lock);

if (now - last > WATCHDOG_TIMEOUT)
{
fprintf(stderr, "WATCH DOG TIMEOUT\n");
// 可重新启动该程序...
exit(EXIT_FAILURE);
}
}
return NULL;
}

/* 数据采集线程 */
void* sensor_thread(void* arg)
{
(void)arg;

while (1)
{
time_t now = time(NULL);

pthread_mutex_lock(&sensor_data.lock);
sensor_data.last_update = now; // 喂狗
pthread_mutex_unlock(&sensor_data.lock);

update_time_date();
read_cpu_temp();

// 每2秒更新一次数据
if (now % 2 == 0)
{
read_dht11();
read_ip_address();
}
usleep(100000); // 100ms
}
return NULL;
}

int main(int argc, char* argv[])
{
// 参数个数判断,判断是否有指定IIC通道
if (argc < 2)
{
fprintf(stderr, "用法: %s <I2C设备>\n", argv[0]);
return EXIT_FAILURE;
}

// 硬件初始化
if (init_gpio() != 0 || init_oled(argv[1]) != 0)
{
return EXIT_FAILURE;
}

// 初始化数据结构
pthread_mutex_init(&sensor_data.lock, NULL);
memset(sensor_data.temp_buffer, 0, sizeof(sensor_data.temp_buffer));
memset(sensor_data.humi_buffer, 0, sizeof(sensor_data.humi_buffer));
sensor_data.filter_index = 0;
sensor_data.last_update = time(NULL);

// 创建线程
pthread_t tid[2];
if (pthread_create(&tid[0], NULL, sensor_thread, NULL) != 0 || pthread_create(&tid[1], NULL, watchdog_thread, NULL) != 0)
{
perror("Thread creation failed");
return EXIT_FAILURE;
}

while (1)
{
// 分两个函数,方便做分页显示
show_time_screen();
show_sensor_screen();
sleep(1);
}

// 清理资源
pthread_mutex_destroy(&sensor_data.lock);
oled_clear(&disp);
return EXIT_SUCCESS;
}

编译:gcc main.c -o sensor_app -lwiringPi -lpthread

执行:sudo ./sensor_app /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_app.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="sensor_app"

# 使用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 start my_main.service
# 检查是否成功启动
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_app.sh

定时执行

1
2
3
4
crontab -e

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