S1-05二进制信号量和计数器信号量

二进制信号量

二进制信号量,又叫二值信号量,要么是0,要么是1,也是通过Take和Give方式获取和释放,用于控制对共享资源的访问。在每次访问共享资源之前需要获取二进制信号量,若已被获取则任务会被阻塞直到二进制信号量可用。不同于互斥信号量,二进制信号量可以通过多次获取而被同一个任务持有,即可用于同一任务对多个共享资源的排他性访问。
二进制信号量和互斥信号量有很大差别,具体表现如下:

  • 使用场景:互斥信号量(Mutex)通常用于多线程环境中的临界区访问控制,以确保每次只有一个线程可以访问这个临界区。它的初始值为1,可以通过 xSemaphoreTake() 和 xSemaphoreGive() 函数来获取和释放。* 二进制信号量(Binary Semaphore)初始值也为1, 但是它通常被用于线程同步,即用于线程之间的通信,表示某个线程执行完毕,另外一个线程才能开始执行。它可以通过 xSemaphoreTake() 和 xSemaphoreGive() 函数来获取和释放。
  • 特性:互斥信号量可以防止多个线程同时访问同一个共享资源,从而避免竞态条件的出现。当一个线程占用了互斥信号量,其他线程必须等待该线程释放信号量后才能执行。因此,互斥信号量适合用于单个资源的访问控制。* 二进制信号量适合用于线程同步,通过等待或发送信号量,不同线程之间可以协调工作,避免竞争和冲突的发生。例如,一个线程在完成某个操作后,可以通过发送信号量来通知另一个线程执行相应的操作。
  • 实现方式:互斥信号量通常基于二进制信号量实现,由于它只有一个计数器,因此当一个线程请求互斥信号量时,如果该信号量已被占用,则该线程将被阻塞。待互斥信号量被释放后,下一个请求该信号量的线程将得到通知并获得该信号量。* 二进制信号量是一种抽象的概念,可以使用多种方法进行实现,比如锁、信号、事件等。

二进制信号量的应用

前面讲过,二进制信号量用于在两个任务间传递数据,也就是我们可以在一个任务中释放信号量,另一个任务中获取信号量。这就与互斥信号量有着本质的区别,互斥信号量的获取和释放必须在同一任务中进行,跨任务就会出错。
基于二进制信号量这种特性,我们首先能想到应用的就是开关,本节课的例程就是使用按键开关和LED的互动,按动一次开关,LED灯打开,再按动一次,LED关闭。

代码共享位置:https://wokwi.com/projects/362766325741473793

#define KEY_PIN 20
#define LED_PIN 14
SemaphoreHandle_t led = NULL; // 二进制信号量
void led_task(void *param_t){
  pinMode(LED_PIN, OUTPUT);
  while(1){
    if(xSemaphoreTake(led, 1000) == pdTRUE){ //pdTRUE 和 pdPASS 值是相同的,用哪个都可以
      digitalWrite(LED_PIN,!digitalRead(LED_PIN));
      vTaskDelay(200);
    }
  }
}
// 按键监控
void key_task(void *param_t){
  pinMode(KEY_PIN, INPUT_PULLUP);   // 设置为带输出的上拉
  while(1){
    if(digitalRead(KEY_PIN)==LOW){
      // 按键按下了
      xSemaphoreGive(led);
      vTaskDelay(200); // 等待去抖,这里不用换算,是为了节省时间,我们不需要精确延时
    }
  }
}
void setup() {
  Serial.begin(115200);
  led = xSemaphoreCreateBinary(); //创建二进制信号量
  xTaskCreate(key_task, "KEY-MON", 1024, NULL, 1, NULL);
  xTaskCreate(led_task, "LED-DSP", 1024, NULL, 1, NULL);
}
void loop() {
  delay(10);
}

二进制信号量同样使用 SemaphoreHandle_t 对象存放句柄,Mutex通过 xSemaphoreCreateMutex 函数创建, 而二进制信号量则通过 xSemaphoreCreateBinary 创建,如果不对二进制信号量进行初始化而直接使用的话会报内存溢出的错误。
Mutex创建后初值非零,所以可以直接Take到,而二进制信号量创建后初值为 0,所有不能直接Take,而是需要通过 xSemaphoreGive 先放入,然后才可以通过 xSemaphoreTake 进行获取,其他的操作与Mutex相同。

程序 key_task 现成模拟的是对按键开关的扫描,首先对微动按键开关所在的引脚进行初始化,因为需要读取开关的值,所以我们用的是上拉输出(INPUT_PULLUP)模式,而次引脚默认情况下可以读到的是高电平(HIGH),因为开关的另一边电路与GND相连,所以当按下开关是接地导通,读出的值是低(LOW),如果读出的电流是低,那表示开关被按下了 通过xSemaphoreGive(led) 改变二值信号量的值。
另一个任务 led_task 则通过 xSemaphoreTake 一直等待信号的到达,当信号到达后出发LED引脚的电平翻转,实现LED亮灭的目的。

微动按键开关

微动按键开关是硬件设计中比较常用的元件,一般有四个引脚,横向观看,引脚分为上下两组,每组都是相连的,而开关在正常情况下上下是不连通的,只有当按下开关的时候才会接通。
在这里插入图片描述

A插脚 B基座 C弹片 D按钮 E盖板
理想环境下,当我们按下开关时,引脚接收高电平,抬起时,引脚继续回复低电平,但打脸来的是那么得快,因为开关属于机械零件,在按下和抬起的瞬间会,内部的弹片会产生震动,这个阶段如果我们用示波器测量,发现收到的波形并不是一个严格意义上的方波,而是在按下和抬起的前后出现了轻微的抖动。

在这里插入图片描述

用示波器测试波形

所以我们要在第一次判断到按键电平变化时(这里我们采集的是低电平,有时候采集的是高电平)首先要有一段时间的延迟,这段延迟大概在1050ms之间,延迟后再进行二次采集,而当第二次产生电平变动时,有可能是开关的释放,这时候我们收到第二次电平变化后仍然采取一个1050ms的延时,确保开关是真的被放开了,所以我们在写代码的时候应该是:

if(digitalRead(KEY_PIN)==LOW){
    delay(30);
    if(digitalRead(KEY_PIN)==LOW){
        // 触发按键响应
    }
}

而我们模拟器不存在抖动的情况,我们也就不需要做这一步了,但需要注意的是 xSemaphoreGive 函数会立即返回,如果不加下面的延时,程序会一直发送信号,而另一个线程接收信号后也没有延迟,这就造成了多次触发,所以我们在释放和获取之后都有一个200的延迟(这里的200不是严格意义上的ms,而是200个Tick,因为我们没有用转换函数)。

为什么不能换成全局变量?

代码共享位置:https://wokwi.com/projects/362790977921224705

#define KEY_PIN 20
#define LED_PIN 14

virtual bool key_down = false;    // 是否按下了按键
void led_task(void *param_t){
  pinMode(LED_PIN, OUTPUT);
  while(1){
    if(key_down){ //pdTRUE 和 pdPASS 值是相同的,用哪个都可以
      digitalWrite(LED_PIN,!digitalRead(LED_PIN));
      vTaskDelay(200);
      key_down = false;
    }
    vTaskDelay(100);  // 一定要让出CPU,否则会一直在这里循环
  }
}
// 按键监控
void key_task(void *param_t){
  pinMode(KEY_PIN, INPUT_PULLUP);   // 设置为带输出的上拉
  while(1){
    if(digitalRead(KEY_PIN)==LOW){
      // 按键按下了
      key_down = true;
      vTaskDelay(200); // 等待去抖,这里不用换算,是为了节省时间,我们不需要精确延时
    }
    vTaskDelay(100);  // 一定要让出CPU,否则会一直在这里循环
  }
}
void setup() {
  Serial.begin(115200);
  xTaskCreate(key_task, "KEY-MON", 1024, NULL, 1, NULL);
  xTaskCreate(led_task, "LED-DSP", 1024, NULL, 1, NULL);
}
void loop() {
  delay(10);
}

这样做理论上也是可以实现的,如果只是做一个开关的话,也是可以做到点灯的。
但问题就在于,原来我们在LED任务中,我妈是通过 xSemaphoreTake 实现等待的,而此时CPU已经让出给其他线程,我们的CPU利用率是很低的,但在上述例程中,采用了轮训的方式,每间隔一段时间就扫描一次按键是否被按下了,这种效率是极低的,在多任务情况下,轮训线程占用了CPU其他程序就得让路,这就造成了不必要的资源浪费,所以我们在开发过程中尽量使用二值信号量来代替线程间的通讯,减少资源消耗。

同样,在第一个例子中,key的扫描我们也用到了轮训,这无疑也会造成资源的浪费,所以我们还可以对第一个二进制信号量的程序进行修改,把key的轮训改为中断方式。

初认中断

在第一课讲到程序执行的时候,我们有个图提到了中断。
在这里插入图片描述

中断(Interrupt)指的是计算机执行程序时,由于硬件的某些信号或者软件的需要,导致CPU中止正在执行的程序转而处理另一个事件或者程序的机制。中断可以使得CPU在不同任务之间快速地切换,提高计算机的并发性和响应能力。
中断的触发条件通常包括硬件中断和软件中断两种情况:

  1. 硬件中断是指由外部设备发出的,需要及时处理的信号,比如输入输出设备的请求、定时器、时钟等。当这些信号被检测到后,CPU会在当前运行任务的中断点处保存当前状态并跳转到中断处理程序去执行。
  2. 软件中断是指在程序中特意插入的一段代码,用于实现某个具体功能或者服务。软件中断也可以被看作是一种人为中断,例如系统调用、软件异常或者进程间通信等。

我们这里使用的是按键中断,也就是硬终端其中的一种,当引脚的电平发生变化的时候就会触发,触发中断后不管CPU当前在干什么(只要不是处于优先级更高的其他中断),都会跳到中断服务函数中执行。
这里我们试试简单用到了中断,后续会有专门的可成详细讲解,同学么在此只需要简单了解即可。

代码共享位置:https://wokwi.com/projects/362768211562473473

#define KEY_PIN 20
#define LED_PIN 14
SemaphoreHandle_t led = NULL; // 二进制信号量
volatile TickType_t keyDeounce = 0;   // 按下按钮的时间
void led_task(void *param_t){
  pinMode(LED_PIN, OUTPUT);
  while(1){
    // 这种去抖方式是很Low的,正确的方式要使用定时器。
    if((xSemaphoreTake(led, 1000) == pdTRUE) && ((xTaskGetTickCount() - keyDeounce) < 200)){
      digitalWrite(LED_PIN,!digitalRead(LED_PIN));
      vTaskDelay(500);
    }
  }
}
// 中断服务函数
void IRAM_ATTR ISR() {
  keyDeounce = xTaskGetTickCountFromISR();    // 记录下按下的时间,用于放抖动,正式开发中不要这样写,有Bug
  xSemaphoreGiveFromISR(led, NULL);
}
void setup() {
  Serial.begin(115200);
  led = xSemaphoreCreateBinary(); //创建二进制信号量
  xTaskCreate(led_task, "LED-DSP", 1024, NULL, 1, NULL);
  // 安装中断
  pinMode(KEY_PIN, INPUT_PULLUP);
  attachInterrupt(KEY_PIN, ISR, FALLING);
}
void loop() {
  delay(10);
}

在setup函数中,通过 attachInterrupt(KEY_PIN, ISR, FALLING) 安装了中断服务函数 ISR,FALLING是下降沿触发,另外还有上升沿、跳变沿等方式。
在安装中断服务函数之前,需要将引脚设置为带上拉的输入,以方便读取电平状态。
在Arduino中,中断服务函数要通过 IRAM_ATTR 进行定义。
另外就是,中断中使用的很多FreeRTOS函数和外面的不同,都加有FromISR的后缀(具体等到中断章节再细讲)。
中断服务函数中首先记录了触发中断(按键)的时间,用于比较,然后通过 xSemaphoreGiveFromISR 方式释放了一个二进制信号量,这与在任务中释放函数有所不同。
在LED点灯的任务中,首先判断信号量是否被释放了,放抖动用。
其他的和原函数相同。
这样改外之后,CPU使用率瞬间降下来了,给其他可能存在的任务留下了很大的资源空间。

计数器信号量

FreeRTOS的信号量还剩最后一种,叫做计数器信号量。
计数器信号量可以看作是一个内部维护计数的信号量,当计数值为0时表示当前没有可用的信号量,而当计数值大于0时则表示还有可用的信号量。每个任务在使用共享资源之前都需要获取信号量许可,当信号量计数器为正时,任务可以得到许可并访问共享资源,同时信号量的计数器会减1。当任务释放共享资源时,可以通过给信号量计数器加上一个值来释放许可。如果信号量计数器为0,所有试图获取许可的任务都将被阻塞,等待计数器变成非0值。
相对于二进制信号量,计数器信号量可以允许多个任务同时访问同一共享资源,并且支持多对多的任务访问模式。因此,在实际应用中,计数器信号量更适合那些需要控制访问数量的场景。

计数器信号量应用的场景

计数器信号量(Counting Semaphore)主要用于多个任务之间同步和控制访问共享资源的场景。下面列举了一些计数器信号量常见的应用场景:

  1. 任务同步:当多个任务需要在某个时刻完成某项任务时,可以使用计数器信号量来控制任务的执行流程,确保任务按预期顺序执行。
  2. 缓冲区管理:当多个任务需要访问同一个缓冲区时,可以使用计数器信号量来控制缓冲区的访问数量,避免出现竞争条件。
  3. 系统资源分配:当多个任务需要访问同一个系统资源(如堆、队列等)时,可以利用计数器信号量来确保系统资源的安全性和有效性。
  4. 输入/输出控制:当多个任务需要共享输入/输出设备时,可以使用计数器信号量来控制设备的访问数量,同时避免出现数据竞争和冲突。
  5. 动态优先级调度:当多个任务需要实时响应某种事件时,可以基于计数器信号量实现动态优先级调度机制,以确保系统的响应速度和稳定性。

需要注意的是,计数器信号量需要合理设置初始值和计数步长,以适应不同的应用场景和需求。在实际应用中,需要根据具体情况进行合理的调整和优化。

缓冲区管理,我们之前用的互斥信号量可以完全代替;输入输出控制上一个例程中开关灯的例子我们用了二进制信号来那个也实现了;任务同步和动态优先级调度在下面章节关于时间标志组我们会讲到;排除这些,计数器信号量最重要的应用场景就是系统资源分配。

有这样一个例子,我们班一共有20台示波器,但我们一共有52个同学,如果我们都要使用示波器的时候测量按键抖动,这时候我们52个同学应该如何分配呢?只能排队,先到先得,但用完后要还回去,让给其他同学用,如果你在需要用,还得排队等。
这时候我们就可以创建一个计数器信号量,最大值是20,表示我们一共有20个示波器的资源,初始值也是20,表示我们有20个闲置资源,也就是20台示波器。
有同学需要借走的时候就使用 xSemaphoreTake 获取,返回pdPASS或者pdTRUE表示后去成功,用完后依然要通过 xSemaphoreGive 还setup阶段,通过 xSemaphoreCreateCounting(CAPACITY,FOOD) 构造了一个容积是100,初始值是0的计数器信号量,并通过预设制造了一些厨师和一些吃货。
回来。
对应到物联网开发中,这个资源可能是一台设备上的多个网卡、多个USB、多个串口、IIC、SPI等外设,也可以是多个缓冲区等内部资源。
这就是计数器信号量的一种用法。

还有一种用法是生产者和消费者的关系。
拿回我们上几节课讲到的吃货和厨子的例子。
假设这次餐厅中一开始并没有汉堡可以吃,来了许多个吃货,都等待着厨子做汉堡,我们的厨子可以是一个也可以是多个,这样就形成了多对多的关系。
冰箱的容积是100,意味着厨子最多可以做100个汉堡,如果做多了就放不进去了,吃货们当把冰箱里食物吃完的时候就需要等待。
这时候我们初始化计数器信号量的时候就要告诉句柄,最大容积是100,当前是0,然后厨子们通过 xSemaphoreTake 做汉堡,吃货们通过 xSemaphoreGive 吃汉堡。
对应物联网的开发中,可以用一个分布式计算的例子理解:
生产者产生需要计算的数据,并放入到队列中,消费者从队列中读取数据并进行计算,计算完毕后再拿第二组数据,循环往复。
产生数据的生产者可能存在多个,而计算数据的消费者也可能存在多个,但大家对同一个计数单元进行操作。(当然,这种方式后面我们也会用到消息队列的方式解决)

计数器信号量例程

代码共享位置:https://wokwi.com/projects/362794364621551617

volatile int16_t quantity = 100;   // 食物的剩余数量
volatile int16_t eatenCount=0;     // 总共吃掉的实物数量
SemaphoreHandle_t hamburg = NULL; // 汉堡计数器信号量句柄
volatile uint8_t foodie_num=0;    // 吃货计数器
volatile uint8_t chef_num=0;      // 厨子计数器
// 吃货线程
void foodie_task(void *param_t){
  int16_t eaten = 0;        // 吃掉的食物累计
  uint8_t my_num = ++foodie_num;
  while(1){
    if(xSemaphoreTake(hamburg, 1000) == pdPASS){
      eaten++;
      printf("[吃货] %d 号吃货吃了一个汉堡,我一共吃了%d 个。\n", my_num, eaten);
    }else{
      printf("[吃货] %d 号吃货没有等到汉堡!\n", my_num);
    }
    vTaskDelay(pdMS_TO_TICKS(random(500,2000)));
  }
}
// 厨师线程
void chef_task(void *param_t){
  uint8_t my_num = ++chef_num;
  while(1){
    if(xSemaphoreGive(hamburg) == pdTRUE){
      printf("[厨子] %d 号厨子生产了一个汉堡,冰箱里一共有%d个汉堡。\n", my_num, uxSemaphoreGetCount(hamburg));
    }else{
      printf("[厨子] %d厨子,冰箱已满,无法制作汉堡!\n", my_num);
    }
    vTaskDelay(pdMS_TO_TICKS(random(100,1000)));
  }
}
#define FOODIE_COUNT  3     // 吃货的总数量
#define CHEF_COUNT    1     // 厨子的总数量
#define CAPACITY      100   // 冰箱的容量
#define FOOD          0     // 冰箱内初始食物的数量
void setup() {
  Serial.begin(115200);
  hamburg = xSemaphoreCreateCounting(CAPACITY,FOOD);    // 创建一个计数器信号量,容量是100,初始值是0
  for(int i=0; i<FOODIE_COUNT; i++){xTaskCreate(foodie_task, "Foodie", 1024*4, NULL, 1, NULL);}
  for(int i=0; i<CHEF_COUNT; i++){xTaskCreate(chef_task, "Chef", 1024*4, NULL, 1, NULL);}
}
void loop() {
  delay(10);
}

例程中,通过修改 FOODIE_COUNT、CHEF_COUNT来调厨师和吃货的数量,也就是生产者和消费者的数量;通过CAPACITY、FOOD来调整冰箱的大小和初始食物量,也就是计数器的容积和初始值。
setup阶段,通过 xSemaphoreCreateCounting(CAPACITY,FOOD) 构造了一个容积是100,初始值是0的计数器信号量,并通过预设制造了一些厨师和一些吃货。
chef_task 任务中,厨师随机时间生产汉堡,通过 xSemaphoreGive(hamburg) 把汉堡放入到冰箱里,如果冰箱里还有空位(值没有超过100),则返回pdTRUE,表示释放成功,如果返回的是其他值则表示释放失败,也就是计数器满了。
计数器当前数值可以通过 uxSemaphoreGetCount(hamburg) 查看。
foodie_task 任务中,吃货通过 xSemaphoreTake(hamburg, 1000) 获得一个汉堡,如果冰箱里现在没有汉堡(计数器值为0),可以等待一秒钟,但一秒中后还没有获取到,则表示获取失败。
这个例程通过厨师不断释放信号量,厨子不断获取信号量的方式讲述了计数器信号量的应用。

关于信号量的所有API,可以参考:https://www.freertos.org/zh-cn-cmn-s/a00113.html

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

技术专栏——你所不知道的 RocketMQ 的集群管理:副本机制

这些精彩的技术类型的体系化文章&#xff0c;后面我会放到公众号上&#xff0c;并集中在合集“分布式消息中间件专栏”中&#xff0c;欢迎大家去订阅我的公众号和视频号“架构随笔录”&#xff0c;大家可以订阅合集&#xff0c;这样更加方便喔&#xff0c;后面会出电子版本&…

电脑上不安装Oracle,但是虚拟机装了Oracle,怎么连接到虚拟机里的Oracle数据库呢?

1、准备工作 1.1、确定数据库版本信息 注&#xff1a;如果知道数据库的版本信息&#xff0c;这个步骤可以跳过。 比较简单的方法&#xff0c;直接看数据库的安装位置&#xff0c;也就是数字&#xff08;但是这个方法确定就是&#xff0c;不好确定是多少位的数据库&#xff09;…

AI智能分析网关V4烟火检测算法解决方案

一、背景需求 根据国家消防救援局公布的数据显示&#xff0c;2023年共接报处置各类警情213.8万起&#xff0c;督促整改风险隐患397万处。火灾危害巨大&#xff0c;必须引起重视。传统靠人工报警的方法存在人员管理难、场地数量多且分散等问题&#xff0c;无法有效发现险情降低…

微信小程序(二)事件绑定

注释很详细&#xff0c;直接上代码 新增内容&#xff1a; 点击事件绑定注册页面设置页面初始化数据事件处理函数的实现更新数据并更新视图 源码&#xff1a; index.wxml <!-- 页面的数据绑定 --> <view>{{msg}}</view> <!-- 绑定点击事件 --> <but…

openssl3.2 - 官方demo学习 - cipher - aesgcm.c

文章目录 openssl3.2 - 官方demo学习 - cipher - aesgcm.c概述笔记END openssl3.2 - 官方demo学习 - cipher - aesgcm.c 概述 AES-256-GCM 在这个实验中验证了EVP_CIPHER_fetch()中算法名称字符串的来源定位. 在工程中配置环境变量PATH, 且合并环境. 这样就不用将openSSL的D…

【Python】编程练习的解密与实战(四)

​&#x1f308;个人主页&#xff1a;Sarapines Programmer&#x1f525; 系列专栏&#xff1a;《Python | 编程解码》⏰诗赋清音&#xff1a;云生高巅梦远游&#xff0c; 星光点缀碧海愁。 山川深邃情难晤&#xff0c; 剑气凌云志自修。 目录 &#x1fa90;1. 初识Python &a…

Spring Boot - Application Events 的发布顺序_ApplicationStartedEvent

文章目录 Pre概述Code源码分析 Pre Spring Boot - Application Events 的发布顺序_ApplicationEnvironmentPreparedEvent 概述 Spring Boot 的广播机制是基于观察者模式实现的&#xff0c;它允许在 Spring 应用程序中发布和监听事件。这种机制的主要目的是为了实现解耦&#…

linux 服务器上安装 ftp(亲测有效)

目录 1 需求2 安装 1 需求 服务器上需要安装ftp 2 安装 1 下载地址 https://developer.aliyun.com/packageSearch?wordvsftpd或者 https://rpmfind.net/linux/rpm2html/search.php?queryvsftpd2 如何判断 服务器是否安装了ftp rpm -qa | grep vsftpd出现提示则表示已安装…

什么是国密算法

国密算法是指由中国国家密码管理局发布的密码算法标准&#xff0c;旨在保障国家信息安全。目前&#xff0c;国家密码管理局已发布了一系列国产商用密码标准算法&#xff0c;包括SM1&#xff08;SCB2&#xff09;、SM2、SM3、SM4、SM7、SM9以及祖冲之密码算法&#xff08;ZUC)等…

thinkphp学习09-数据库的数据新增

单数据新增 使用 insert()方法可以向数据表添加一条数据&#xff0c;更多的字段采用默认 public function index() {$data [username > 犬夜叉,password > 123,gender > 男,email > wjl163.com,price > 999,details > 犬夜叉介绍];echo Db::name(user)-&g…

【教学类-43-18】A4最终版 20240111 数独11.0 十宫格X*Y=Z套(n=10),套用没有分割行列的A4横版模板

作品展示&#xff1a; 撑满格子的10宫格数独50%难度 50空 背景需求&#xff1a; 大4班有3位男孩做9宫格数独&#xff08;81格子&#xff0c;30%难度 24空&#xff09;非常娴熟&#xff0c;我观察他们基本都在10分钟内完成&#xff0c;其中一位男孩把九宫格题目给我看时表达自…

android 13.0 Launcher3长按app弹窗设置为圆角背景功能实现二

1.前言 在13.0的系统ROM定制化开发中,在进行一些Launcher3的定制化开发中,在使用app的弹窗的功能时,会弹出应用信息和 微件之类的内容,所以在定制需求中,需要默认设置为圆角背景,接下来就来分析下相关功能的实现如图: 2.Launcher3长按app弹窗设置为圆角背景功能实现二的…

【目标检测】评价指标:混淆矩阵概念及其计算方法(yolo源码)

本篇文章首先介绍目标检测任务中的评价指标混淆矩阵的概念&#xff0c;然后介绍其在yolo源码中的实现方法。 目标检测中的评价指标&#xff1a; mAP概念及其计算方法(yolo源码/pycocotools) 混淆矩阵概念及其计算方法(yolo源码) 本文目录 1 概念2 计算方法 1 概念 在分类任务中…

rime中州韵小狼毫 汉语拼音输入方案

在word中&#xff0c;我们可以轻易的给汉字加上拼音&#xff0c;如下&#x1f447;&#xff1a; 但是&#xff0c;如何单独的输入拼音呢&#xff1f;例如输入 pīn yīn, 再如 zhōng guō。今天我们分享一个使用rime中州韵小狼毫须鼠管输入法配置的输入汉语拼音的输入方案。功…

WPS - 表格虚线变成实线解决方案(Office 同上)

1、选中表格区域&#xff0c;在表格中选中需要调整为实线的表格区域 2、点击设置单元格格式&#xff0c;鼠标进行右击并点击设置单元格格式选项 3、选择实线&#xff0c;在单元格格式下的边框&#xff0c;调整到实线 4、设置为实线&#xff0c;即可将表格的虚线设置为实线

(ros2)gazebo颜色设置

在gazebo当中不用再设置颜色了&#xff0c;因为完全可以使用urdf的设置 <robot name"base" xmlns:xacro"http://wiki.ros.org/wiki/xacro"><xacro:property name"PI" value"3.1415926"/><!--定义一个变量PI&#xff0…

研发日记,Matlab/Simulink避坑指南(三)——向上取整Bug

文章目录 前言 背景 问题 排查 解决 总结 前言 见《研发日记&#xff0c;Matlab/Simulink避坑指南&#xff08;一&#xff09;——Data Store Memory模块执行时序Bug》 见《研发日记&#xff0c;Matlab/Simulink避坑指南(二)——非对称数据溢出Bug》 背景 在一个嵌入式软…

vi/vim 编辑器 --基本命令

1 vi/vim编辑器介绍 vi 是visual interface 的简称&#xff0c;是Linux中最经典的文本编辑器 vim是vi的加强版。兼容了vi的所有指令&#xff0c;不仅能编辑文本&#xff0c;而且具有shell程序编辑的功能&#xff0c;可以通过不同颜色的字体辨别语法的正确性&#xff0c;极大…

七麦数据js逆向(补环境版)

本文目标地址如下&#xff0c;使用base64解码获得 aHR0cHM6Ly93d3cucWltYWkuY24vcmFuay9tYXJrZXRSYW5rL21hcmtldC82L2NhdGVnb3J5LzUvY29sbGVjdGlvbi9hbGwvZGF0ZS8yMDI0LTAxLTEy 本文逆向破解分为扣代码版和补环境版&#xff0c;扣代码版请看专栏另一篇文章 废话不多说了&#…

Docker 安装以及加速器配置

通常我们因为安装docker出现许多错误&#xff0c;使用解压版安装方便快捷&#xff0c;并且增加加速器的配置&#xff0c;以及可视化界面的配置&#xff0c;让我们的成长更近了一步 1. 虚拟机网络配置 虚拟机使用nat模式&#xff0c;配置ens33如下&#xff1a; TYPEEthernet P…