【香橙派系列教程】(十七) 视觉垃圾桶-功能完善优化

【十七】视觉垃圾桶-功能完善优化

文章目录

  • 【十七】视觉垃圾桶-功能完善优化
    • 一、增加垃圾桶开关盖
      • 1.引脚
      • 2.PWM 频率的公式
      • 3.PWM_API
        • softPwmCreate
        • softPwmWrite
        • 附加说明
        • softPwmStop
      • 4.代码
        • pwm.c
        • pwm.h
        • main.c
    • 二、项目代码优化
      • 编译运行
    • 三、增加OLED 屏幕显示功能
        • myoled.h
        • myoled.c
        • main.c
    • 附录:
      • 1.pthread_detach
      • 2.pthread_detach 和 pthread_join的区别
      • 3.舵机测试代码
      • 4.wget-log打印日志文件

一、增加垃圾桶开关盖

1.引脚

实现功能:使用语音模块和摄像头在香橙派上做垃圾智能分类识别, 同时根据识别结果开关不同的垃圾桶的盖子。

主要用到的PWM引脚:5和7。

image-20240724220636887

2.PWM 频率的公式

这个 PWM 频率的公式可以更详细地表示为:

P W M f r e q = 1 × 1 0 6 ( p u l s e − w i d t h ) × r a n g e PWMfreq = \frac{1 \times 10^6}{(pulse-width) \times range} PWMfreq=(pulsewidth)×range1×106
其中:

  1. P W M f r e q PWMfreq PWMfreq :是 PWM 的频率(赫兹)。

  2. 1 × 1 0 6 {1 \times 10^6} 1×106 :是为了将频率从赫兹(Hz)转换为微秒(μs)。

  3. $pulse-width $ 是每个 PWM 脉冲的宽度(微秒)。

  4. × r a n g e \times range ×range 是 PWM 的范围,即 PWM 值的最大范围。

这个公式的基本思想是,PWM 的频率与脉冲宽度和范围有关。脉冲宽度表示每个 PWM 脉冲的持续时间,而范围表示 PWM 值的最大范围。通过调整这两个参数,可以控制 PWM 的频率。

在之前《官方外设开发》一节中我们也讲到了舵机的开发,我们用到的是定时器模拟pwm,一个进程只能创建一个定时器,也就意味着只能驱动一个舵机,所以如果我们想要驱动多个的话,就需要下面的方法了。

range设置为200时,PWM的频率将是50Hz。这里的range值表示PWM周期被分成200个步进,每个步进的时间为:0.5微秒。
每个步进时间 = 周期时间 步数 = 100 μ s 200 = 0.5 μ s \text{每个步进时间} = \frac{\text{周期时间}}{\text{步数}} = \frac{100\mu s}{200} = 0.5\mu s 每个步进时间=步数周期时间=200100μs=0.5μs

3.PWM_API

softPwmCreatesoftPwmWrite 函数用于在不支持硬件PWM或者需要额外PWM通道的微控制器或类似设备上实现软件PWM控制。虽然这些函数不是标准C库的一部分,但是它们的API可能有如下特点:

softPwmCreate
  • 函数原型

    void softPwmCreate(uint8_t pin, uint16_t value, uint16_t range);
    
  • 参数

    • pin:指定用于PWM输出的数字引脚编号。
    • value:初始PWM值,可以是0(表示PWM输出为0%占空比)或其他值,取决于range参数。
    • range:定义PWM周期的分辨率。在这个范围内,PWM值将被分割,从而控制占空比。
  • 功能:初始化指定引脚的软件PWM功能,并设置其初始值和PWM周期的分辨率。

softPwmWrite
  • 函数原型

    void softPwmWrite(uint8_t pin, uint16_t value);
    
  • 参数

    • pin:指定要修改PWM值的数字引脚编号。
    • value:新的PWM值,这个值将影响引脚的占空比,其具体范围由softPwmCreate函数中的range参数决定。
  • 功能:设置指定引脚的PWM占空比。这个函数通常在PWM创建后被调用来改变输出信号的占空比。

附加说明
  • 周期和频率:PWM的周期是高电平和低电平时间的总和,频率是周期的倒数。在softPwmCreate中设置的range参数会影响周期的计算。
  • 精度range参数的值越大,PWM的控制精度越高,但同时可能需要更复杂的算法来计算定时器中断。
  • 线程安全:在多线程环境中使用这些API时,需要注意线程安全问题,以避免竞态条件。
  • 平台依赖性:这些API可能是针对特定硬件平台或软件库设计的,因此它们的实现和行为可能会因平台而异。

请注意,由于softPwmCreatesoftPwmWrite不是标准API,具体的函数原型、参数和行为可能会根据实际使用的库或框架有所不同。

softPwmStop

softPwmStop 函数用于停止软件PWM信号的API函数。

函数原型示例

void softPwmStop(uint8_t pin);
  • pin:指定要停止PWM输出的数字引脚编号。

注意事项

  • 在使用 softPwmStop 之前,确保已经通过 softPwmCreate 成功创建了PWM信号。
  • 如果 softPwmStop 用于释放资源,确保在重新使用PWM功能之前重新初始化。
  • 在多线程环境中,如果 softPwmStop 影响共享资源,需要考虑线程同步和互斥。

image-20240326165346171

4.代码

pwm.c

pwm.c(增加用于实现开关盖(驱动舵机)的源码文件)

#include <wiringPi.h>
#include <softPwm.h>
//根据公式:PWMfreq = 1 x 10^6 / (100 x range) ,要得到PWM频率为50Hz,则range为200,即周期分为200步,控制精度相比硬件PWM较低。
void pwm_write(int pwm_pin)
{pinMode(pwm_pin, OUTPUT);softPwmCreate(pwm_pin,0,200);// 起始值为0,周期范围设置为200步。, 周期20mssoftPwmWrite(pwm_pin,10);//1ms 45度,这将产生一个占空比为5%的PWM信号(因为10是200步中的一小部分)。delay(1000);softPwmStop(pwm_pin);
}
void pwm_stop(int pwm_pin)
{pinMode(pwm_pin, OUTPUT);softPwmCreate(pwm_pin,0,200);// range设置周期分为200步, 周期20mssoftPwmWrite(pwm_pin,5);//0.5ms 0度delay(1000);softPwmStop(pwm_pin);
}
pwm.h

pwm.h

#ifndef __PWM__H
#define __PWM__H
#define PWM_GARBAGE 7
#define PWM_RECOVERABLE_GARBAGE 5
void pwm_write(int pwm_pin);
void pwm_stop(int pwm_pin);
#endif
main.c

main.c里增加调用舵机的控制代码:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <wiringPi.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){if(fgets(buf, sizeof(buf), strm) != NULL){printf("buf=%s\n",buf);n = atoi(buf);printf("n=%d\n",n);}}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};wiringPiSetup();garbage_init();ret = detect_process("mjpg_streamer");if ( -1 == ret){printf("detect process failed\n");goto END;}serial_fd = myserialOpen(SERIAL_DEV, BAUD);if (-1 == serial_fd){printf("open serial failed\n");goto END;}while(1){len = serialGetstring(serial_fd, buffer);if (len > 0 && buffer[2] == 0x46){buffer[2] = 0x00;system(WGET_CMD);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;}printf("buffer[2] =%d\n", buffer[2]);serialSendstring(serial_fd, buffer, 6);if (buffer[2] == 0x43){pwm_write(PWM_RECOVERABLE_GARBAGE);delay(2000);pwm_stop(PWM_RECOVERABLE_GARBAGE);}else if (buffer[2] != 0x45){printf("start\n");pwm_write(PWM_GARBAGE);delay(2000);pwm_stop(PWM_GARBAGE);}buffer[2] = 0x00;remove(GARBAGE_FILE);}}
END:garbage_final();return 0;
}

二、项目代码优化

在之前实现的代码中, 主函数是单线程执行的, 导致整个代码的可扩展性非常差,比如想加OLED显示或者添加网络控制变得非常复杂,
而且执行一次识别开关盖的流程非常长。因此,调整下代码架构,增加并发功能、提升代码的可扩展性和执行效率。

  1. 代码大致流程图如下:

垃圾桶加线程

  1. 修改main.c代码,调整整体main函数的代码架构,利用多线程实现具体的功能(用到了线程里的条件变量控制线程间的数据同步)如下:

image-20240726174310479

#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,默认为-1FILE *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 *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;// 根据垃圾类型控制PWMif (buffer[2] == 0x43|buffer[2] == 0x42|buffer[2] == 0x44) {		// 可回收垃圾,湿垃圾,有害垃圾//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);}pthread_exit(0);
}
// 获取语音线程
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;system(WGET_CMD);pthread_cond_signal(&cond);pthread_mutex_unlock(&mutex);			}}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);// 删除拍照文件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);END:// 关闭串口close(serial_fd);// 释放垃圾分类资源garbage_final();return 0;
}

编译运行

编译 
gcc -o test *.c *.h -I /usr/include/python3.10 -l python3.10  -lwiringPi -lwiringPiDev -lpthread -lm -lcrypt -lrt执行 
sudo -E ./test查看进程 
ps -ax | grep mjpg_streamer | grep -v grep
ps -ax | grep ./test | grep -v grep
ps aux | grep './test' | grep -v grep | awk '{print $2}'

image-20240726215607015

三、增加OLED 屏幕显示功能

详细可参照《官方外设开发》一节:

在之前请确保已经配置好环境:查看是否已经加上i2c-3,如果没有的话自己加上

cat /boot/orangepiEnv.txt
ls -a /dev/i2c-3

image-20240330151832514

myoled.h
#ifndef __MYOLED__H
#define __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"#define FILENAME "/dev/i2c-3"int myoled_init(void);
int oled_show(void *arg);#endif
myoled.c
#include <myoled.h>struct display_info disp;
int myoled_init(void)
{int e;memset(&disp, 0, sizeof(disp));disp.address = OLED_I2C_ADDR;disp.font = font2;oled_open(&disp, FILENAME);e = oled_init(&disp);return e;
}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;// 发送显示缓冲区到 OLEDoled_send_buffer(&disp);//oled_putpixel(disp, 60, 45);//oled_putstr(disp, 1, "hello");return 0;
}
main.c
#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,默认为-1FILE *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 *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;// 根据垃圾类型控制PWMif (buffer[2] == 0x43|buffer[2] == 0x42|buffer[2] == 0x44) {		// 可回收垃圾,湿垃圾,有害垃圾//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);}pthread_exit(0);
}
void *popen_trash_can(void *arg)
{pthread_detach(pthread_self());// 初始化 OLEDmyoled_init();// 在 OLED 上显示垃圾分类结果oled_show(arg);pthread_exit(0);   
}
// 获取语音线程
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;system(WGET_CMD);pthread_cond_signal(&cond);pthread_mutex_unlock(&mutex);			}}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, popen_trash_can, (void *)buffer);// 删除拍照文件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);END:// 关闭串口close(serial_fd);// 释放垃圾分类资源garbage_final();return 0;
}
编译 
gcc -o test *.c *.h -I /usr/include/python3.10 -l python3.10  -lwiringPi -lwiringPiDev -lpthread -lm -lcrypt -lrt执行 
sudo -E ./test查看进程 
ps -ax | grep mjpg_streamer | grep -v grep
ps -ax | grep ./test | grep -v grep
ps aux | grep './test' | grep -v grep | awk '{print $2}'

附录:

1.pthread_detach

pthread_detach 是 POSIX 线程库(pthreads)中的一个函数,用于控制线程的分离状态。在 POSIX 线程编程中,线程可以是分离的或非分离的。线程的分离状态决定了当线程终止时其资源是否自动释放。

这种机制对于那些主线程不关心其返回值,也不需要等待其结束的辅助线程是非常有用的。这样,主线程和辅助线程可以并行执行,提高了程序的性能。

1.函数原型

int pthread_detach(pthread_t thread);
  • thread:要分离的线程标识符。

2.功能

  • pthread_detach 函数将指定的线程设置为分离状态。
  • 如果线程已经终止,pthread_detach 会自动释放该线程的资源,包括线程的返回值和任何关联的线程特定数据(thread-specific data, TSD)。

3.返回值

  • 成功时返回 0。
  • 失败时返回错误码,常见的错误码包括:
    • ESRCH:指定的线程标识符无效或找不到。
    • EINVAL:线程标识符无效或线程已经被分离。

4.使用场景

  • 当你希望线程终止后自动释放资源时,可以调用 pthread_detach
  • 在线程创建后立即分离线程是一种常见的做法,这样可以避免在线程终止时需要显式地调用 pthread_join

5.示例代码

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>void* thread_function(void* arg) {printf("Thread is running.\n");return NULL;
}int main() {pthread_t tid;int result;// 创建线程result = pthread_create(&tid, NULL, thread_function, NULL);if (result != 0) {perror("Failed to create thread");return 1;}// 分离线程result = pthread_detach(tid);if (result != 0) {perror("Failed to detach thread");return 1;}printf("Thread is detached.\n");return 0;
}

在这个示例中:

  1. 创建一个线程并立即调用 pthread_detach 将其分离。
  2. 这样,当线程终止时,其资源会自动释放,不需要调用 pthread_join

6.注意事项

  • 一旦线程被分离,就不能再次分离或通过 pthread_join 等待其终止。
  • 如果线程在分离之前已经终止,pthread_detach 会立即释放线程的资源。
  • 在多线程环境中,确保线程的资源在适当的时候被释放是非常重要的,以避免资源泄漏。

pthread_detach 是管理线程生命周期和资源的有用工具,特别是在需要创建大量短命线程的应用程序中。

2.pthread_detach 和 pthread_join的区别

pthread_detachpthread_join 都是 POSIX 线程库(pthreads)中的函数,它们在线程管理中扮演着不同的角色,尤其是在处理线程终止和资源回收方面。以下是这两个函数的主要区别及其在线程管理中的角色:

特性/函数pthread_detachpthread_join
功能将线程设置为分离状态等待线程终止并获取返回值
资源回收自动回收资源,无需 pthread_join必须显式调用以回收资源
返回值不保留,无法通过 pthread_join 获取可以获取线程的返回值。
使用场景- 不需要线程返回值
- 希望线程终止后立即释放资源
- 创建大量短命线程,如并行处理任务
- 需要获取线程返回值
- 需要同步线程终止
- 确保数据一致性和完整性
- 需要精确控制线程生命周期和资源回收的场景。
线程状态将线程设置为分离状态,无法再次分离或加入可以等待线程终止,但不能将线程设置为分离状态。

3.舵机测试代码

pwmtest.c

#include <wiringPi.h>
#include <softPwm.h>
#include <stdio.h>
//根据公式:PWMfreq = 1 x 10^6 / (100 x range) ,要得到PWM频率为50Hz,则range为200,即周期分为200步,控制精度相比硬件PWM较低。
void pwm_write(int pwm_pin)
{pinMode(pwm_pin, OUTPUT);softPwmCreate(pwm_pin,0,200);// 起始值为0,周期范围设置为200步。, 周期20mssoftPwmWrite(pwm_pin,10);//1ms 45度,这将产生一个占空比为5%的PWM信号(因为10是200步中的一小部分)。delay(1000);softPwmStop(pwm_pin);
}
void pwm_stop(int pwm_pin)
{pinMode(pwm_pin, OUTPUT);softPwmCreate(pwm_pin,0,200);// range设置周期分为200步, 周期20mssoftPwmWrite(pwm_pin,22);//2.2ms 140度大概角度delay(1000);softPwmStop(pwm_pin);
}int main()
{wiringPiSetup();pwm_write(7);delay(2000);pwm_stop(7);return 0;
}
编译 
gcc pwmtest.c -o pwm -lwiringPi -lwiringPiDev -lpthread -lm -lcrypt -lrt
sudo ./pwm

4.wget-log打印日志文件

image-20240726220029848

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 命令或阿里云操作,你可以提供更多的上下文,以便我更好地理解你的问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/411254.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

小白之 FastGPT Windows 本地化部署

目录 引言环境步骤1. 安装 docker2. 启动 docker3. 浏览器访问4. One API 配置语言模型、向量模型渠道和令牌5. 创建 FastGPT 知识库6. 创建 FastGPT 应用 官方文档 引言 部署之前可以先看一下 RAG 技术原理&#xff0c;也可以后面回过头来看&#xff0c;对一些概念有些了解&a…

Qt+FFmpeg开发视频播放器笔记(一):环境搭建

一、FFmpeg介绍 FFmpeg是一个开源的跨平台多媒体处理工具集&#xff0c;它可以用于处理音频、视频和其他多媒体数据。FFmpeg提供了一组功能强大的命令行工具&#xff0c;用于音频和视频的编解码、转换、处理、流媒体传输等任务。 FFmpeg支持多种音频和视频格式&#xff0c;包…

【自动化】考试答题自动化完成答案,如何实现100%正确呢

一、科目仿真考试不能自动答题 我的答案是可以的&#xff0c;电脑程序可以模拟人的操作完成所有的答题并提交结束考试 二、分析页面内容 完成一个题目&#xff0c;包括判断题&#xff0c;对与错2选1答案&#xff0c;单选题ABCD4选1答案&#xff0c;多选题大家想一想 F12查看按…

C语言 ——— 将动态版本的通讯录实现为文件存储联系人模式

目录 前言 在退出通讯录之前 在运行通讯录之前 前言 在这篇博客中&#xff0c;实现了动态版本的通讯录&#xff0c;接下来会增加函数&#xff0c;能用文件存储通讯录中的联系人 C语言 ——— 在控制台实现通讯录&#xff08;增删查改、动态开辟内存空间&#xff09;-CSDN…

#网络高级 笔记

modbus_tcp协议 modbus_rtu协议和modbus库 http协议和web服务器搭建 服务器原码分析和基于WebServer的工业数据采集项目 第H5&#xff0c;即网页制作&#xff0c;项目完善 一、modbus起源 1.起源 Modbus由Modicon公司于1979年开发&#xff0c;是一种工业现场总线协议标准 Mo…

python将字典数据保存为json文件

目录 一、json库介绍 二、字典生成json文件 1、导入 json 模块 2、将字典数据保存为 json 文件 (1) 创建一个python字典 (2) 指定要保存的 json 文件路径 (3) 将字典数据存为 json 文件 3、读取 json文件&#xff0c;并打印 一、json库介绍 方法作用json.dumps()将py…

对数据处理过程中,缺失值和异常值应该怎么处理?

创作不易&#xff0c;您的关注、点赞、收藏和转发是我坚持下去的动力&#xff01; 大家有技术交流指导、论文及技术文档写作指导、项目开发合作的需求可以私信联系我。 在数据处理过程中&#xff0c;缺失值和异常值的处理是非常重要的步骤&#xff0c;它们可能会对模型的性能…

Datawhale AI夏令营第五期学习!

学习日志 日期&#xff1a; 2024年8月27日 今日学习内容&#xff1a; 今天&#xff0c;我学习了如何在深度学习任务中使用卷积神经网络&#xff08;CNN&#xff09;进行图像分类的基本流程&#xff0c;并成功地在JupyterLab中运行了一个完整的项目。以下是我今天的学习和操作…

【扩散模型(六)】IP-Adapter 是如何训练的?2 源码篇(IP-Adapter Plus)

系列文章目录 【扩散模型&#xff08;二&#xff09;】IP-Adapter 从条件分支的视角&#xff0c;快速理解相关的可控生成研究【扩散模型&#xff08;三&#xff09;】IP-Adapter 源码详解1-训练输入 介绍了训练代码中的 image prompt 的输入部分&#xff0c;即 img projection…

【Verilog 数字系统设计教程】Verilog 基础:硬件描述语言入门指南

目录 摘要 1. 引言 2. Verilog 历史与发展 3. Verilog 基本语法 4. Verilog 模块与端口 5. 组合逻辑与时序逻辑 6. 时钟域与同步设计 7. 测试与仿真 8. Verilog 高级特性 任务&#xff08;Tasks&#xff09; 函数&#xff08;Functions&#xff09; 多维数组 结构体…

【二叉树】OJ题目

&#x1f31f;个人主页&#xff1a;落叶 目录 单值⼆叉树 【单值二叉树】代码 相同的树 【相同二叉树】代码 对称⼆叉树 【对称二叉树】代码 另一颗树的子树 【另一颗树的子树】代码 二叉树的前序遍历 【二叉树前序遍历】代码 二叉树的中序遍历 【二叉树中序遍历】…

【大模型】llama系列模型基础

前言&#xff1a;llama基于transformer架构&#xff0c;与GPT相似&#xff0c;只用了transformer的解码器部分。本文主要是关于llama&#xff0c;llama2和llama3的结构解读。 目录 1. llama1.1 整体结构1.2 RoPE1.3 SwiGLU 激活函数 2. llama22.2 GQA架构2.3 RLHF3. llama3 参考…

CAD中命令和系统变量

屏幕去除菜单全屏显示&#xff1a; ThisDrawing.SendCommand ("CLEANSCREENON ") 恢复原始&#xff1a;ThisDrawing.SendCommand ("CLEANSCREENOFF ") CAD中系统变量决定图形的基本设置。 第一个系统变量&#xff1a;uscicon vba代码如下&#xff1a; …

【Linux】——Rocky Linux配置静态IP

Rocky Linux配置静态IP Rocky Linux Rocky Linux 进入官网进行下载&#xff0c;下载版本自定义 官网link 获取ip地址 ip addr 获取服务器ip地址 进入网络配置文件目录&#xff1a; cd /etc/NetworkManager/system-connections/vi打开ens33.nmconnection 在IPv4下输入配置信…

Ubuntu美化为类Windows风格

博主的系统为 Ubuntu22.04 参考文献&#xff1a;How to Make Ubuntu Look Like Windows 11 | 22.04 GNOME 43 / 42 | Linux AF Tech 可能遇到的bug的解决方法&#xff1a;如何在 Linux 中安装和更改 GNOME 主题 先来一下视频演示&#xff1a; 下面正式开始安装。在主文件夹下打…

DWF 支持的 TON 链 Telegram 免费宠物游戏 Gatto_game,推出 “Paws Up! 世界锦标赛”

TON 链在这轮牛市里无疑是一匹脱缰的黑马&#xff0c;创造了一个又一个爆款&#xff0c;为持有者带来了不菲的收益。 Gatto_game 是一款 TON链 Tamagotchi 电子宠物风格的 P2E web3 游戏。可以通过喂养升级&#xff0c;参加比赛赚取 $TON 或者 $GTON &#xff0c;或许就是下一个…

python解释器[源代码层面]

1 PyDictObject 在c中STL中的map是基于 RB-tree平衡二元树实现&#xff0c;搜索的时间复杂度为O(log2n) Python中PyDictObject是基于散列表(散列函数)实现&#xff0c;搜索时间最优为O(1) 1.1 散列列表 问题&#xff1a;散列冲突&#xff1a;多个元素计算得到相同的哈希值 …

软件设计原则之依赖倒置原则

依赖倒置原则&#xff08;Dependency Inversion Principle, DIP&#xff09;是软件设计中一个非常重要的原则&#xff0c;它属于面向对象设计的SOLID原则之一。这个原则的核心在于通过抽象来降低模块间的耦合度&#xff0c;使得系统更加灵活和可维护。 目录 依赖倒置原则的基本…

「软件测试」最全面试问题和回答,全文背熟不拿下offer算我输

3.公司这边测试人员分配比例 4.进入公司&#xff0c;我这边大概的工作安排 5&#xff0c;公司这么后续发展机会还有培养 6&#xff0c;有没有培训 7&#xff0c;面试没有回答上的问题&#xff0c;再去请教 2.5 你的职业发展规划和职业目标 根据公司况&#xff0c;个人原因…

【Spring Boot 3】自定义拦截器

【Spring Boot 3】自定义拦截器 背景介绍开发环境开发步骤及源码工程目录结构总结背景 软件开发是一门实践性科学,对大多数人来说,学习一种新技术不是一开始就去深究其原理,而是先从做出一个可工作的DEMO入手。但在我个人学习和工作经历中,每次学习新技术总是要花费或多或…