一、增加垃圾桶开关盖功能
- 环境准备
二、PWM 频率的公式
三、pthread_detach分离线程,使其在退出时能够自动释放资源
四、具体代码实现
- 图像识别数据及调试信息
- wget-log打印日志文件
五、增加OLED显示功能
六、功能点实现语音交互视频
一、增加垃圾桶开关盖功能
实现功能:使用语音模块和摄像头在香橙派上做垃圾智能分类识别, 同时根据识别结果开关不同的垃圾桶的盖子。
环境准备
在《语音模块和阿里云图像识别结合》搭建环境的基础上, 接上用于开关盖的舵机(舵机模块可以直接粘在垃圾桶内侧),当前代码里仅用了2个舵机用于示例代码的编写,可以自行多购买3个垃圾桶和舵机用于区分4垃圾类型,接线位置如下:
实物图
二、PWM 频率的公式
这个 PWM 频率的公式可以更详细地表示为:
P W M f r e q = 1 × 1 0 6 pulse-width × range \\{PWMfreq} = \frac{1 \times 10^6}{\text{pulse-width} \times \text{range}} \ PWMfreq=pulse-width×range1×106
其中:
- (\text{PWMfreq}) 是 PWM 的频率(赫兹)。
- (1 \times 10^6) 是为了将频率从赫兹(Hz)转换为微秒(μs)。
- (\text{pulse-width}) 是每个 PWM 脉冲的宽度(微秒)。
- (\text{range}) 是 PWM 的范围,即 PWM 值的最大范围。
这个公式的基本思想是,PWM 的频率与脉冲宽度和范围有关。脉冲宽度表示每个 PWM 脉冲的持续时间,而范围表示 PWM 值的最大范围。通过调整这两个参数,可以控制 PWM 的频率。
三、pthread_detach分离线程,使其在退出时能够自动释放资源
pthread_detach
函数是 POSIX 线程库提供的一个函数,用于将一个线程标记为可被回收的。标记为可被回收的线程在退出时会自动释放其占用的系统资源,无需等待其他线程调用 pthread_join
。
具体来说,当一个线程被标记为可被回收时,其退出状态会自动被收回。这对于那些不需要其他线程等待其结束的线程是有用的,因为它允许主线程或其他线程继续执行而无需等待这个线程的完成。
#include <pthread.h>
int pthread_detach(pthread_t thread);
pthread_detach
的参数是一个线程标识符(pthread_t
类型的变量),它表示要被标记为可被回收的线程。- 如果线程标识符为
thread
的线程处于 joinable 状态,那么它会被标记为可被回收,并且在线程退出时,其资源将被自动释放。 - 如果线程已经处于 detached 状态,或者线程标识符不对应一个现存的线程,
pthread_detach
函数将返回适当的错误码。
示例用法:
#include <pthread.h>
void *thread_function(void *arg) {
// 线程的执行体
// ...
return NULL;
}
int main() {
pthread_t my_thread;
// 创建线程
if (pthread_create(&my_thread, NULL, thread_function, NULL) != 0) {
// 线程创建失败处理
return 1;
}
// 将线程标记为可被回收
if (pthread_detach(my_thread) != 0) {
// 线程标记失败处理
return 1;
}
// 主线程继续执行而不用等待子线程的结束
// ...
return 0;
}
在这个例子中,my_thread
线程被创建后立即被标记为可被回收,主线程可以继续执行而不用等待 my_thread
线程的完成。
总结:
你可以将这种机制称为“分离线程”或“分离父子线程”。当你将一个线程标记为可被回收,这个线程就不再和主线程形成关联,主线程不需要显式地等待它的结束。这样的线程就像“自洁”一样,它在结束时会自动释放资源。
这种机制对于那些主线程不关心其返回值,也不需要等待其结束的辅助线程是非常有用的。这样,主线程和辅助线程可以并行执行,提高了程序的性能。
四、具体代码实现
- 增加用于实现开光盖(驱动舵机)的源码文件(pwm.c):
#include <wiringPi.h>
#include <softPwm.h>
#include "pwm.h"
// 根据PWM 频率公式:PWMfreq = 1 x 10^6 / (100 x range) 。
// 要得到PWM频率为50Hz,则range设置周期分为200步,周期20ms,控制精度相比硬件PWM较低。
// 设置指定PWM引脚的输出,实现模拟PWM
void pwm_write(int pwm_pin)
{
pinMode(pwm_pin, OUTPUT);
softPwmCreate(pwm_pin, 0, 200); // 创建软件PWM,初始占空比为0%,范围为0到200
softPwmWrite(pwm_pin, 10); // 设置占空比为10% 45度
delay(1000); // 延时1秒
softPwmStop(pwm_pin); // 停止软件PWM
}
// 停止指定PWM引脚的输出
void pwm_stop(int pwm_pin)
{
pinMode(pwm_pin, OUTPUT);
softPwmCreate(pwm_pin, 0, 200); // 创建软件PWM,初始占空比为0%,范围为0到200
softPwmWrite(pwm_pin, 5); // 设置占空比为5% 0度
delay(1000); // 延时1秒
softPwmStop(pwm_pin); // 停止软件PWM
}
- pwm.h代码:
#ifndef __PWM__H
#define __PWM__H
#define PWM_GARBAGE 7 // 干垃圾
#define PWM_RECOVERABLE_GARBAGE 5 // 可回收垃圾
#define PWM_WET_GARBAGE 8 // 湿垃圾
#define PWM_HAZARDOUS_GARBAGE 9 // 有害垃圾
void pwm_write(int pwm_pin); // 设置指定PWM引脚的输出
void pwm_stop(int pwm_pin); // 停止指定PWM引脚的输出
#endif
- 修改main.c代码,调整整体main函数的代码架构,利用多线程实现具体的功能(用到了线程里的条件变量控制线程间的数据同步)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <wiringPi.h>
#include <pthread.h>
#include "uartTool.h"
#include "garbage.h"
#include "pwm.h"
int serial_fd = -1; // 串口文件描述符
pthread_cond_t cond; // 条件变量,用于线程之间的条件同步
pthread_mutex_t mutex; // 互斥锁,用于线程之间的互斥访问
// 判断进程是否在运行
static int detect_process(const char * process_name)
{
int n = -1; // 存储进程PID,默认为-1
FILE *strm;
char buf[128] = {0}; // 缓冲区
// 构造命令字符串,通过ps命令查找进程
sprintf(buf, "ps -ax | grep %s|grep -v grep", process_name);
// 使用popen执行命令并读取输出
if ((strm = popen(buf, "r")) != NULL) {
if (fgets(buf, sizeof(buf), strm) != NULL) {
printf("buf = %s\n", buf); //打印缓存区的内容
n = atoi(buf); // 将进程ID字符串转换为整数
printf("n = %d\n", n); // 打印下进程的PID
}
}
else {
return -1; // popen失败
}
pclose(strm); // 关闭popen打开的文件流
return n;
}
// 获取语音线程
void *pget_voice(void *arg)
{
unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0X55, 0xAA};
int len = 0;
printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);
// 串口未打开,退出线程
if (-1 == serial_fd) {
printf("%s|%s|%d: open serial failed\n", __FILE__, __func__, __LINE__);
pthread_exit(0);
}
printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);
// 循环读取串口数据
while (1) {
len = my_serialGetstring(serial_fd, buffer);
printf("%s|%s|%d, len = %d\n", __FILE__, __func__, __LINE__, len);
// 检测到特定数据,发出信号唤醒其他线程
if (len > 0 && buffer[2] == 0x46) {
printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);
pthread_mutex_lock(&mutex);
buffer[2] = 0x00;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
system(WGET_CMD);
}
}
pthread_exit(0);
}
// 发送语音线程
void *psend_voice(void *arg)
{
pthread_detach(pthread_self());
unsigned char *buffer = (unsigned char *)arg;
// 串口未打开,退出线程
if (-1 == serial_fd) {
printf("%s|%s|%d: open serial failed\n", __FILE__, __func__, __LINE__);
pthread_exit(0);
}
// buffer不为空时,通过串口发送数据(分类结果)
if (NULL != buffer) {
my_serialSendstring(serial_fd, buffer, 6);
}
pthread_exit(0);
}
// 控制垃圾桶线程
void *popen_trash_can(void *arg)
{
pthread_detach(pthread_self());
unsigned char *buffer = (unsigned char *)arg;
// 根据垃圾类型控制PWM
if (buffer[2] == 0x43) { // 可回收垃圾
printf("%s|%s|%d: buffer[2] = 0x%x\n", __FILE__, __func__, __LINE__, buffer[2]);
pwm_write(PWM_RECOVERABLE_GARBAGE);
delay(2000);
pwm_stop(PWM_RECOVERABLE_GARBAGE);
}
else if (buffer[2] == 0x41) { // 干垃圾
printf("%s|%s|%d: buffer[2] = 0x%x\n", __FILE__, __func__, __LINE__, buffer[2]);
pwm_write(PWM_GARBAGE);
delay(2000);
pwm_stop(PWM_GARBAGE);
}
else if (buffer[2] == 0x42) { // 湿垃圾
printf("%s|%s|%d: buffer[2]=0x%x\n", __FILE__, __func__, __LINE__,buffer[2]);
pwm_write(PWM_WET_GARBAGE);
delay(2000);
pwm_stop(PWM_WET_GARBAGE);
}
else if (buffer[2] == 0x44) { // 有害垃圾
printf("%s|%s|%d: buffer[2]=0x%x\n", __FILE__, __func__, __LINE__,buffer[2]);
pwm_stop(PWM_HAZARDOUS_GARBAGE);
delay(2000);
pwm_write(PWM_HAZARDOUS_GARBAGE);
}
pthread_exit(0);
}
// 垃圾分类线程
void *pcategory(void *arg)
{
unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0X55, 0xAA};
char *category = NULL;
pthread_t send_voice_tid, trash_tid;
while (1) {
printf("%s|%s|%d: \n", __FILE__, __func__, __LINE__);
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);
printf("%s|%s|%d: \n", __FILE__, __func__, __LINE__);
buffer[2] = 0x00;
// 在执行wget命令之前添加调试输出
printf("Executing wget command...\n");
// 使用系统命令拍照
system(WGET_CMD);
// 在执行wget命令之后添加调试输出
printf("Wget command executed.\n");
// 判断垃圾种类
if (0 == access(GARBAGE_FILE, F_OK)) {
category = garbage_category(category);
if (strstr(category, "干垃圾")) {
buffer[2] = 0x41;
}
else if (strstr(category, "湿垃圾")) {
buffer[2] = 0x42;
}
else if (strstr(category, "可回收垃圾")) {
buffer[2] = 0x43;
}
else if (strstr(category, "有害垃圾")) {
buffer[2] = 0x44;
}
else {
buffer[2] = 0x45; // 未识别到垃圾类型
}
}
else {
buffer[2] = 0x45; // 识别失败
}
// 开垃圾桶开关
pthread_create(&trash_tid, NULL, psend_voice, (void *)buffer);
// 开语音播报线程
pthread_create(&send_voice_tid, NULL, popen_trash_can, (void *)buffer);
// buffer[2] = 0x00;
// 删除拍照文件
remove(GARBAGE_FILE);
}
pthread_exit(0);
}
int main(int argc, char *argv[])
{
int ret = -1;
int len = 0;
char *category = NULL;
pthread_t get_voice_tid, category_tid;
wiringPiSetup();
// 初始化串口和垃圾分类模块
garbage_init ();
// 用于判断mjpg_streamer服务是否已经启动
ret = detect_process ("mjpg_streamer");
if (-1 == ret) {
printf("detect process failed\n");
goto END;
}
// 打开串口
serial_fd = my_serialOpen (SERIAL_DEV, BAUD);
if (-1 == serial_fd) {
printf("open serial failed\n");
goto END;
}
// 开语音线程
printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);
pthread_create(&get_voice_tid, NULL, pget_voice, NULL);
// 开阿里云交互线程
printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);
pthread_create(&category_tid, NULL, pcategory, NULL);
// 创建互斥锁和条件变量
pthread_join(get_voice_tid, NULL);
pthread_join(category_tid, NULL);
// 销毁互斥锁和条件变量
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
// 关闭串口
close(serial_fd);
END:
// 释放垃圾分类资源
garbage_final();
return 0;
}
图像识别数据及调试信息
wget-log打印日志文件
wget-log
文件名通常是 wget
命令行工具的默认日志文件名,用于记录 wget
下载命令执行过程中的信息、警告和错误。wget
是一个用于在命令行中下载文件的工具,而 wget-log
文件则用于记录执行 wget
命令时产生的输出。
如果你在使用类似如下的 wget
命令:
wget [URL]
wget
默认会将日志输出到 wget-log
文件中。如果你希望更改日志文件的名称,可以使用 -o
选项,例如:
wget -o mylog.txt [URL]
上述命令将日志输出到名为 mylog.txt
的文件中。因此,wget-log
文件的生成通常取决于 wget
命令的使用方式。
阿里云的相关操作(比如通过 wget
下载文件)也可能产生 wget-log
文件,具体情况可能取决于你执行的命令和阿里云环境的设置。如果有特定的 wget
命令或阿里云操作,你可以提供更多的上下文,以便我更好地理解你的问题。
五、增加OLED显示功能
- 环境配置
cat /boot/orangepiEnv.txt
ls -a /dev/i2c-3
- 《OLED屏应用-IIC协议》直接添在garbage项目中添加2个OLED实现代码文件
myoled.h
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdint.h>
#include "oled.h"
#include "font.h"
#ifndef __MYOLED__H
#define __MYOLED__H
int myoled_init(void);
int oled_show(void *arg);
#endif
myoled.c:
#include "myoled.h"
#define FILENAME "/dev/i2c-3"
static struct display_info disp;
// 在 OLED 上显示垃圾分类结果
int oled_show(void *arg)
{
unsigned char *buffer = (unsigned char *)arg;
// 在 OLED 上显示提示信息
oled_putstrto(&disp, 0, 9+1, "THis garbage is:");
disp.font = font2;
// 根据垃圾类型显示相应信息
switch(buffer[2])
{
case 0x41:
oled_putstrto(&disp, 0, 20, "Dry_garbage");
break;
case 0x42:
oled_putstrto(&disp, 0, 20, "Wet_garbage");
break;
case 0x43:
oled_putstrto(&disp, 0, 20, "Recycle_garbage");
break;
case 0x44:
oled_putstrto(&disp, 0, 20, "Hazardous_garbage");
break;
case 0x45:
oled_putstrto(&disp, 0, 20, "recognition failed");
break;
}
disp.font = font2;
// 发送显示缓冲区到 OLED
oled_send_buffer(&disp);
return 0;
}
// 初始化 OLED
int myoled_init(void)
{
int e;
disp.address = OLED_I2C_ADDR;
disp.font = font2;
// 打开 OLED 设备文件
e = oled_open(&disp, FILENAME);
// 初始化 OLED
e = oled_init(&disp);
return e;
}
- 然后修改下main.c文件, 增加OLED线程,用于显示识别后的垃圾类型:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <wiringPi.h>
#include <pthread.h>
#include "uartTool.h"
#include "garbage.h"
#include "pwm.h"
#include "myoled.h"
int serial_fd = -1; // 串口文件描述符
pthread_cond_t cond; // 条件变量,用于线程之间的条件同步
pthread_mutex_t mutex; // 互斥锁,用于线程之间的互斥访问
// 判断进程是否在运行
static int detect_process(const char * process_name)
{
int n = -1; // 存储进程PID,默认为-1
FILE *strm;
char buf[128] = {0}; // 缓冲区
// 构造命令字符串,通过ps命令查找进程
sprintf(buf, "ps -ax | grep %s|grep -v grep", process_name);
// 使用popen执行命令并读取输出
if ((strm = popen(buf, "r")) != NULL) {
if (fgets(buf, sizeof(buf), strm) != NULL) {
printf("buf = %s\n", buf); //打印缓存区的内容
n = atoi(buf); // 将进程ID字符串转换为整数
printf("n = %d\n", n); // 打印下进程的PID
}
}
else {
return -1; // popen失败
}
pclose(strm); // 关闭popen打开的文件流
return n;
}
// 获取语音线程
void *pget_voice(void *arg)
{
unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0X55, 0xAA};
int len = 0;
printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);
// 串口未打开,退出线程
if (-1 == serial_fd) {
printf("%s|%s|%d: open serial failed\n", __FILE__, __func__, __LINE__);
pthread_exit(0);
}
printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);
// 循环读取串口数据
while (1) {
len = my_serialGetstring(serial_fd, buffer);
printf("%s|%s|%d, len = %d\n", __FILE__, __func__, __LINE__, len);
// 检测到特定数据,发出信号唤醒其他线程
if (len > 0 && buffer[2] == 0x46) {
printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);
pthread_mutex_lock(&mutex);
buffer[2] = 0x00;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
system(WGET_CMD);
}
}
pthread_exit(0);
}
// 发送语音线程
void *psend_voice(void *arg)
{
pthread_detach(pthread_self());
unsigned char *buffer = (unsigned char *)arg;
// 串口未打开,退出线程
if (-1 == serial_fd) {
printf("%s|%s|%d: open serial failed\n", __FILE__, __func__, __LINE__);
pthread_exit(0);
}
// buffer不为空时,通过串口发送数据(分类结果)
if (NULL != buffer) {
my_serialSendstring(serial_fd, buffer, 6);
}
pthread_exit(0);
}
// 控制垃圾桶线程
void *popen_trash_can(void *arg)
{
pthread_detach(pthread_self());
unsigned char *buffer = (unsigned char *)arg;
// 根据垃圾类型控制PWM
if (buffer[2] == 0x43) { // 可回收垃圾
printf("%s|%s|%d: buffer[2] = 0x%x\n", __FILE__, __func__, __LINE__, buffer[2]);
pwm_write(PWM_RECOVERABLE_GARBAGE);
delay(2000);
pwm_stop(PWM_RECOVERABLE_GARBAGE);
}
else if (buffer[2] == 0x41) { // 干垃圾
printf("%s|%s|%d: buffer[2] = 0x%x\n", __FILE__, __func__, __LINE__, buffer[2]);
pwm_write(PWM_GARBAGE);
delay(2000);
pwm_stop(PWM_GARBAGE);
}
else if (buffer[2] == 0x42) { // 湿垃圾
printf("%s|%s|%d: buffer[2]=0x%x\n", __FILE__, __func__, __LINE__,buffer[2]);
pwm_write(PWM_WET_GARBAGE);
delay(2000);
pwm_stop(PWM_WET_GARBAGE);
}
else if (buffer[2] == 0x44) { // 有害垃圾
printf("%s|%s|%d: buffer[2]=0x%x\n", __FILE__, __func__, __LINE__,buffer[2]);
pwm_stop(PWM_HAZARDOUS_GARBAGE);
delay(2000);
pwm_write(PWM_HAZARDOUS_GARBAGE);
}
pthread_exit(0);
}
// 在线程中显示 OLED
void *poled_show(void *arg)
{
// 分离线程,使其在退出时能够自动释放资源
pthread_detach(pthread_self());
// 初始化 OLED
myoled_init();
// 在 OLED 上显示垃圾分类结果
oled_show(arg);
// 退出线程
pthread_exit(0);
}
// 垃圾分类线程
void *pcategory(void *arg)
{
unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0X55, 0xAA};
char *category = NULL;
pthread_t send_voice_tid, trash_tid, oled_tid;
while (1) {
printf("%s|%s|%d: \n", __FILE__, __func__, __LINE__);
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);
printf("%s|%s|%d: \n", __FILE__, __func__, __LINE__);
buffer[2] = 0x00;
// 在执行wget命令之前添加调试输出
printf("Executing wget command...\n");
// 使用系统命令拍照
system(WGET_CMD);
// 在执行wget命令之后添加调试输出
printf("Wget command executed.\n");
// 判断垃圾种类
if (0 == access(GARBAGE_FILE, F_OK)) {
category = garbage_category(category);
if (strstr(category, "干垃圾")) {
buffer[2] = 0x41;
}
else if (strstr(category, "湿垃圾")) {
buffer[2] = 0x42;
}
else if (strstr(category, "可回收垃圾")) {
buffer[2] = 0x43;
}
else if (strstr(category, "有害垃圾")) {
buffer[2] = 0x44;
}
else {
buffer[2] = 0x45; // 未识别到垃圾类型
}
}
else {
buffer[2] = 0x45; // 识别失败
}
// 开垃圾桶开关
pthread_create(&trash_tid, NULL, psend_voice, (void *)buffer);
// 开语音播报线程
pthread_create(&send_voice_tid, NULL, popen_trash_can, (void *)buffer);
//oled显示线程
pthread_create(&oled_tid, NULL, poled_show, (void *)buffer);
// buffer[2] = 0x00;
// 删除拍照文件
remove(GARBAGE_FILE);
}
pthread_exit(0);
}
int main(int argc, char *argv[])
{
int ret = -1;
int len = 0;
char *category = NULL;
pthread_t get_voice_tid, category_tid;
wiringPiSetup();
// 初始化串口和垃圾分类模块
garbage_init ();
// 用于判断mjpg_streamer服务是否已经启动
ret = detect_process ("mjpg_streamer");
if (-1 == ret) {
printf("detect process failed\n");
goto END;
}
// 打开串口
serial_fd = my_serialOpen (SERIAL_DEV, BAUD);
if (-1 == serial_fd) {
printf("open serial failed\n");
goto END;
}
// 开语音线程
printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);
pthread_create(&get_voice_tid, NULL, pget_voice, NULL);
// 开阿里云交互线程
printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);
pthread_create(&category_tid, NULL, pcategory, NULL);
// 创建互斥锁和条件变量
pthread_join(get_voice_tid, NULL);
pthread_join(category_tid, NULL);
// 销毁互斥锁和条件变量
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
// 关闭串口
close(serial_fd);
END:
// 释放垃圾分类资源
garbage_final();
return 0;
}
六、功能点实现语音交互视频
垃圾分类