观前提醒: 本文建立子在上一篇的基础上实现,加入了舵机开盖功能
上一篇: 垃圾分类子项目-识别垃圾类型-CSDN博客
固定摄像头设备号:
为什么要设置
我们在拔插摄像团队的时候经常遇到摄像头设备号变动的情况,这就导致我们之前设置好的开机自启脚本失效
具体参考大佬的这篇博文: 【linux】linux下摄像头设置固定的设备名-udev-CSDN博客
以下是我自己的设备号标注,不必观看
^第一步查看外设的设备号,主设备号:辅助设备号。使用命令:lsusb
Bus 002 Device 003: ID 1e45:8022 Suyin HD Camera
1e45:8022
1e45:8022
查看usb设备的信息
^^^^使用如下命令:udevadm info -a -p /sys/class/video4linux/video03.3 编辑规则
^^^^进入/etc/udev/rules.d/文件夹下,新建video.rules文件,文件内容如下:KERNEL=="video*" , ATTRS{idVendor}== "32e4", ATTRS{idProduct}=="9422", ATTR{index}=="0",MODE:="0777", SYMLINK+="came0"
KERNEL=="video*" , ATTRS{idVendor}== "32e4", ATTRS{idProduct}=="9422", ATTR{index}=="1",MODE:="0777", SYMLINK+="came1摄像头的USB 接口:
2-1:1.0
KERNELS=="2-1:1.0"
========================================================
主程序代码:
编译命令:
gcc -o test *c *h -I /usr/include/python3.10/ -l python3.10 -l wiringPi
运行:
-E -- 保留当前用户的 环境变量
sudo -E ./test
部分问题解决:
pinMode: You have not called one of the wiringPiSetup functions, so I'm aborting your program before it crashes anyway.
翻译: pinMode:您还没有调用wiringPiSetup函数之一,因此我将在您的程序崩溃之前中止它。解决方式: 在main 函数前面初始化部分加入: wiringPiSetup();
问题2: 另一个舵机不动
原因: buffer[2] 的复位位置,放下了子线程后面 -- 父子线程是并行的,父线程调用的给buffer[2] 复位,子线程就处理不到buffer[2] 的实际值了
解决方法: 换个位置 -- 放到创建子线程前 -- > 这段代码是父线程单独先执行的 具体可以放到 拍照前面
cpu优化:
问题3:CPU 占用率过高
原因:vscode 远程连接编辑 产生打的node 服务 -- 导致CPU 占有率比较高
解决方法: vscode 上编译完,之后关闭vscode ,在mobaxtrem 里面打开
屏蔽 代码新思路:
#if0 // 条件一直是不成立,一直不执行 -- 屏蔽
//需要屏蔽的代码
#endif
====================================
case1: 实现基本的图像识别控制 舵机 -- 垃圾桶开盖雏形
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> // access()
#include <error.h> // remove()
#include <wiringPi.h>
#include <softPwm.h>
#include "uartTool.h"
#include "garbage.h"
#include "pwm.h"
static int detect_process(const char *process_name) // 判断进程是否在运行
{
int n = -1;
FILE *strm;
char buf[128] = {0};
sprintf(buf, "ps -ax | grep %s|grep -v grep", process_name); // 组合进程名字,为完整命令
if ((strm = popen(buf, "r")) != NULL) // 通过 popen 的 方式去执行
{
if (fgets(buf, sizeof(buf), strm) != NULL) // 执行完后 判断是否能拿到正确的进程号,空格分开,第一个字符串就是进程号
{
n = atoi(buf); // 拿到就放回 进程号,不然 返回 -1
}
}
else
{
return -1; // 执行失败
}
pclose(strm);
return n;
}
int main(int argc, char **argv)
{
int serial_fd = -1;
int len = 0;
int ret = -1;
char *category = NULL;
unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0x55, 0xAA}; // 初始化 buffer[2] -- 关联垃圾类型
wiringPiSetup();
garbage_init(); // 先初始化 阿里云接口
ret = detect_process("mjpg_streamer"); // 用于判断mjpg_streamer服务是否已经启动
if (-1 == ret)
{
puts("detect process failed");
goto END;
}
serial_fd = myserialOpen(SERIAL_DEV, BAUD); // 初始化串口,打开串口设备(语言模块)
if (serial_fd == -1)
{ // 初始化串口失败
goto END;
}
while (1) // 等待语言输入
{
len = serialGetstring(serial_fd, buffer); // 通过串口获得语言输入
printf("lend=%d, buf[2]=0x%x\n", len, buffer[2]);
if (len > 0 && buffer[2] == 0x46) // 判断是否 需要启动识别
{
buffer[2] = 0x00; // 判断完后 恢复,方便下次判断
system(WGET_CMD); // 拍照
if (access(GARBAGE_FILE, F_OK) == 0) // 判断 文件存在
{
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;
//}
}
printf("buffer[2] = 0x%x\n",buffer[2]);
serialSendstring(serial_fd, buffer, 6); // 将识别到的数据发送到串口,回传给语音模块
if(buffer[2] == 0x43){
pwm_write(PWM_GARBAGE1);
delay (5000); //开盖5s
pwm_stop(PWM_GARBAGE1); //停止写入波形
}
else if(buffer[2] == 0x45){
printf("start\n");
pwm_write(PWM_GARBAGE2);
delay (5000); //开盖5s
pwm_stop(PWM_GARBAGE2); //停止写入波形
}
buffer[2] = 0x00; // 发送完后,一堆有效数据位清零,方便下一次调用
remove(GARBAGE_FILE); // 清理缓存 删除刚刚拍摄的图片,避免对下一次拍摄造成干扰
}
}
close(serial_fd); // 关闭串口文件描述符 fd
END:
garbage_final();
return 0;
}
============================================
常用出错判断; -- 方便定位出错位置
if(-1 == serial_fd)
{
printf("%s | %s | %s:open serial failed\n",__FILE__,__func__,__LINE__);//三个宏的含义: 文件名 - main.c,函数名 - pget_voice ,行号 - 138
pthread_exit(0); //串口打开失败 -->退出
}
格式化操作:
alt + shift + 左键 拉下来 就能删掉n列 -- 实现 n 列的缩进
优化: 不用join 来等待线程退出
pthread_detach(pthread_self()); // pthread_self -- 拿到自己的线程id --> 与父进程分离,不然开盖等待时间太长影响下一次识别
main 函数里的变量和全局变量尽量不要重复,不然肯呢个造成调用错误
-------------------------
====================================================
case2 - 使用线程优化代码:
思路:
在main 里面创建两个线程: 语音线程 和 阿里云交互线程 语音线程 - 负责判断是否有语言命令输入阿里云交互线程: 下面有两个线程:舵机控制线程 和 语言播报线程
这样就有三个线程同时执行了:主线程: 处理接收到的信号,进行拍照 和 图像识别,判断垃圾类型
舵机控制线程:根据 主线程得到的垃圾类型,使用对应舵机(打开对应垃圾桶)
语音播报线程: 根据主线程得到垃圾类型,播报对应语言
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> // access()
#include <error.h> // remove()
#include <wiringPi.h>
#include <softPwm.h>
#include <pthread.h>
#include "uartTool.h"
#include "garbage.h"
#include "pwm.h"
static int detect_process(const char *process_name) // 判断进程是否在运行
{
int n = -1;
FILE *strm;
char buf[128] = {0};
sprintf(buf, "ps -ax | grep %s|grep -v grep", process_name); // 组合进程名字,为完整命令
if ((strm = popen(buf, "r")) != NULL) // 通过 popen 的 方式去执行
{
if (fgets(buf, sizeof(buf), strm) != NULL) // 执行完后 判断是否能拿到正确的进程号,空格分开,第一个字符串就是进程号
{
n = atoi(buf); // 拿到就放回 进程号,不然 返回 -1
}
}
else
{
return -1; // 执行失败
}
pclose(strm);
return n;
}
int serial_fd = -1; // 线程调用 -- 定义为全局
pthread_cond_t cond; // 设置条件变量
pthread_mutex_t mutex; // 设置线程锁
void *pget_voice(void *arg) // 语言播放线程函数
{
int len = 0;
unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0x55, 0xAA}; // 初始化 buffer[2] -- 关联垃圾类型
if (-1 == serial_fd)
{
printf("%s | %s | %d:open serial failed\n", __FILE__, __func__, __LINE__); // 三个宏的含义: 文件名 - main.c,函数名 - pget_voice ,行号 - 138
pthread_exit(0); // 串口打开失败 -->退出
}
while (1)
{
len = serialGetstring(serial_fd, buffer); // 通过串口获得语言输入
if (len > 0 && buffer[2] == 0x46) // 判断是否 接到识别指令
{
pthread_mutex_lock(&mutex); // 先上锁,保证后面执行这块不会被打断
buffer[2] = 0x00; // 判断完后的复位
pthread_cond_signal(&cond); // 发送信号,告诉阿里云线程开始识别了
pthread_mutex_unlock(&mutex); // 解锁,与上锁包含的代码块执行的时候不会被打断
}
}
pthread_exit(0);
}
void *popen_trash_can(void *arg) // 开盖
{
pthread_detach(pthread_self());
unsigned char *buffer = (unsigned char *)arg;
if(buffer[2] == 0x43){
pwm_write(PWM_GARBAGE1);
delay (2000); //开盖5s
pwm_stop(PWM_GARBAGE1); //停止写入波形
}
else if(buffer[2] != 0x45){
pwm_write(PWM_GARBAGE2);
delay (2000); //开盖5s
pwm_stop(PWM_GARBAGE2); //停止写入波形
}
pthread_exit(0);
}
void *psend_voice(void *arg) // 发送语言播报
{
pthread_detach(pthread_self()); // pthread_self -- 拿到自己的线程id --> 与父进程分离,不然开盖等待时间太长影响下一次识别
unsigned char *buffer = (unsigned char *)arg;
if (-1 == serial_fd) //判断串口是否打开
{
printf("%s | %s | %d:open serial failed\n", __FILE__, __func__, __LINE__); // 三个宏的含义: 文件名 - main.c,函数名 - pget_voice ,行号 - 138
pthread_exit(0); // 串口打开失败 -->退出
}
printf("buffer[2] = 0x%x\n",buffer[2]);
if(NULL!=buffer) //有数据
serialSendstring(serial_fd, buffer, 6); // 将识别到的数据发送到串口,回传给语音模块,语言模块收到数据后进行相应输出 -- 实现语言播报
pthread_exit(0);
}
void *pcategory(void *arg) // 阿里云 -- 垃圾类型识别线程函数
{
unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0x55, 0xAA}; // 初始化 buffer[2] -- 关联垃圾类型
char *category = NULL;
pthread_t send_voice_tid,trash_tid;
while (1)
{
pthread_mutex_lock(&mutex); // 拿锁
pthread_cond_wait(&cond, &mutex); // 等待 接受到信号 -- 才能开始识别
pthread_mutex_unlock(&mutex);
// 开始识别
buffer[2] = 0x00; // 拍照前复位
system(WGET_CMD); // 拍照
if (access(GARBAGE_FILE, F_OK) == 0) // 判断 文件存在
{
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, popen_trash_can, (void*)buffer);
// 创建语音播报线程
pthread_create(&send_voice_tid, NULL, psend_voice, (void*)buffer);
// buffer[2] = 0x00; // 发送完后,一堆有效数据位清零,方便下一次调用
remove(GARBAGE_FILE); // 清理缓存 删除刚刚拍摄的图片,避免对下一次拍摄造成干扰
}
pthread_exit(0);
}
int main(int argc, char **argv)
{
int len = 0;
int ret = -1;
char *category = NULL;
pthread_t get_voice_tid, category_tid; // 创建线程id
unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0x55, 0xAA}; // 初始化 buffer[2] -- 关联垃圾类型
wiringPiSetup(); // 初始化wiringPi库
garbage_init(); // 初始化 阿里云接口
ret = detect_process("mjpg_streamer"); // 用于判断mjpg_streamer服务是否已经启动
if (-1 == ret)
{
puts("detect process failed");
goto END;
}
serial_fd = myserialOpen(SERIAL_DEV, BAUD); // 初始化串口,打开串口设备(语言模块)
if (serial_fd == -1)
{ // 初始化串口失败
goto END;
}
// 创建语言线程 -- 注意第一个参数类型是指针变量 pthread_t *
pthread_create(&get_voice_tid, NULL, pcategory, NULL);
// 创建阿里云交互线程
pthread_create(&category_tid, NULL, pget_voice, NULL);
// 第二个参数表示接收到的返回值 -- 没有就NULL
pthread_join(get_voice_tid, NULL); // 等待线程退出
pthread_join(category_tid, NULL);
pthread_mutex_destroy(&mutex); // 释放锁
pthread_cond_destroy(&cond); // 释放条件变量
close(serial_fd); // 关闭串口文件描述符 fd
END:
garbage_final();
return 0;
}