垃圾分类子项目2 - 加入舵机控制

本文介绍了一个基于Linux的项目,通过udev规则固定摄像头设备号,使用wiringPi库控制舵机,并实现垃圾类型识别,通过多线程优化串口通信和CPU占用,利用阿里云接口进行图像识别。
摘要由CSDN通过智能技术生成

观前提醒: 本文建立子在上一篇的基础上实现,加入了舵机开盖功能

上一篇: 垃圾分类子项目-识别垃圾类型-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/video0

3.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;
}

  • 26
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值