STM32 -- USB通信 ( 虚拟串口)

news2024/10/9 23:15:39

本篇操作:

  • 通过CubeMX + Keil,配置STM32作为USB设备端,与电脑进行通信(CDC);
  • 通用带USB功能的 STM32 芯片 (如F1、F4等,系统时钟配置不同,代码通用)。

目录

一、 STM32内置USB、虚拟串口介绍

二、CubeMX 新建 虚拟串口工程

三、Keil工程的配置

四、增加需要的代码

五、发送的实现

六、接收


一、 STM32内置USB、虚拟串口介绍

STM32 芯片,绝大部分型号都带内置USB,如常用的 F1、F4、H7、G4 等系列,能够通过USB接口与计算机或其他USB设备进行通信。

STM32内置的USB,均可支持USB 2.0标准,可以支持三种传输速率:

  1. 高速模式:最高可达480 Mbps  (部分型号支持,且需搭配外部芯片,不常用 )
  2. 全速模式:最高可达12 Mbps      (最常用)
  3. 低速模式:最高可达1.5 Mbps     

高速模式,需要搭配外围USB PHY芯片,如USB3300,硬件成本偏高 。
全速模式,电路很简单。从机在PCB布线时,仅需把STM32的引脚PA11、PA12,  连接至USB座的DP、DM,然后,PA12(DP线)用1.5K电阻上拉至3.3V。具体如下图:

上拉说明

插拔检测:设备未插入时,主机端DP、DM为低电平,当发现被置高,即为有设备插入;

区分速率:DM线上拉是低速模式,DP线上拉是全速\高速模式;

上拉电压:3.3V。USB通信电平是3.3V,而不是总线供电的5V。

USB虚拟串口,简称VPC,Virtual Port Com 的简写。但更习惯于把虚拟串口叫作: CDC,因为它是利用 USB 的 CDC类 实现的一种通信接口。

我们可以利用STM32自带的USB功能,通过CubeMX的配置,很方便地实现一个USB虚拟串口,从而通过USB线,实现电脑与STM32的数据互传。

哪些win系统支持虚拟串口?

Win10、Win11 已带虚拟串口驱动;无需安装任何驱动; 

Win7 要提前手动安装驱动,否则无法识别 :虚拟串口驱动 下载


二、CubeMX 新建 虚拟串口工程

本篇为了工程的清晰,将从0开始, 新建一个虚拟串口通信的工程。

日常做项目,不建议新建,而是复制已有的旧工程,通过CubeMX增删需要的功能。

这样能减少一些常用功能的再次配置,如按键、UART等;

复用旧工程里已验证过的功能,能有效地减少常用功能的调试时间。

1、以芯片型号新建

2、搜索芯片型号

3、设置调试模式

进入配置页面后,养成习惯,优先设置调试模式:Serial Wire。

4、选择晶振源

外部高速晶振源(HSE):Crystal/Ceramic Resonator

5、USB工作模式

USB_OTG_FS:选择 Device_Only;  设备模式(从机模式);   其它参数,默认。

有些芯片型号,如F103系列,CubeMX上的显示是:USB_FS,配置步骤是一样的。

6、中间件组件

USB_DEVICE:选择 CDC (VPC);  其它参数,默认;

6、配置系统时钟

① 当启用USB功能后,进入时钟配置页面时,弹窗: 是否自动配置系统时钟?  选择:No !

② 先确认板上的晶振值

  • 配置时钟前,很重要的一个事:先核对开发板上的晶振频率(在晶振上的数字)!
  • 晶振频率配置错误时,编译不会报错,但系统可能不运行、通信错乱等,后期排查很费时间!
  • 目前STM32的板子,常用的外部高速晶振有三种:8M、12M、25M。

③ STM32F103 时钟配置

晶振值输入分频输出倍频USB分频APB1分频APB2分频系统 时钟
8191.52172MHz

④ STM32F4xx 时钟配置

注意:F4系列,各板商略有不同,大部分是25M, 少部分是8M,使用效果一样。

晶振值输入分频输出倍频输出分频USB分频APB1分频APB2分频系统时钟
25253362742168MHz

7、工程配置

  • 工程名称、路径,这两项,必须英文。否则,生成的工程将会缺少启动文件
  • 开发工具:MDK-ARM,  即生成Keil工程 
  • 堆大小,建议:0x400
  • 栈大小,建议:0x1000

8、文件和代码的配置

9、生成

稍等 片刻:

生成的工程文件夹:

Keil工程的入口文件:


三、Keil工程的配置

按上述,双击打开Keil工程。

1、新建的工程,需要设置一次仿真器参数 。(点击 OK 保存,否则无效

2、配置常用的调试选项

下面这两项是非必要的,建议打勾使用;编译后生效; 打勾会令编译速度变慢;

  • Debug Infomation: 生成调试信息。debug模式中无法设置断点,就是这个选项没打勾。
  • Bowse Infomation: 生成追踪信息。如,右击函数、变量,点击弹出菜单:Go To Definition...

 3、编译 验证

  • 0 Error,正程正常。
  • 有 Error,失败;应该是 (2-7) 那一步工程名称、路径有中文。修改后重新生成即可。
  • 先别烧录,别烧录,别烧录。


四、增加需要的代码

通过 CubeMX 配置后生成的工程,它已带需要的初始化代码、配置代码、基础函数等。

我们只需在工程里,按需进行简单的配置、修改代码,即可使用。

 1、包含 USB接口 的头文件

  • 打开 main.c文件,大约第26行,配对的 /* USER CODE ...... Includes */ 注释之间,
  • 添加:#include  "usbd_cdc_if.h"

完成后,是这个样子的:

2、增加 USB模拟插拔

我们在调试STM32程序期间,需要反复地 修改程序、编译、烧录;  这是常规操作,用于调试其它通信模块,如DHT11、ESP8266等,是没有问题的,但用于调试USB的通信,就会翻车。

当虚拟串口所用的USB线一直插在USB口上,程序重新烧录后,程序的重新运行,将导致通信错误、USB端口"假死"等现象; 

上文中已介绍,电脑端USB口没有插入设备时,DP和DM线,是低电平状态,而设备端的DP线,有1.5K电阻上拉到3.3V,当设备插入到电脑USB口,USB口的DP线就会被置高电平,主机是依靠这个机制判断设备是否插入、拔出,继而触发不同的动作,如枚举、释放端口等。

当虚拟串口所用的USB线一直插在USB口上,在STM32烧录程序重新运行后,程序里的USB代码等待着主机方发起枚举过程;而这个期间虚拟串口的USB线没有断开,主机方认为设备方一直在线,早已枚举成功,一直对其轮询数据收发。双方“南辕北辙,胡言乱语”,注定翻车。

正常操作是:每次烧录前,先把虚拟串口的USB线拔下来,烧录好了,再插上,......。

为了避免调试期间频繁地手动操作,我们可以在程序开跑后、USB初始化前,用代码把PA12置低,使D+线为低电平,持续一段时间,模拟USB拔出动作,令主机认为设备已断开连接,释放端口;
然后,当程序运行到后面的USB初始化函数时,PA12会被正常配置(DP线电平被置高),USB主机就会"发现"有设备插入,开始尝试枚举、配置;

具体操作:

  • 打开 usbd_conf.c 文件,大约第70行附近,找到HAL_PCD_MspInit ( )函数;
  • 在两对 /* USER ... 0 */ 注释之间,约75行,添加PA12引脚置低电平操作,  如下(可复制):
    __HAL_RCC_GPIOA_CLK_ENABLE();                   // 使能GPIOA端口
    GPIO_InitStruct.Pin = GPIO_PIN_12;              // 引脚PA12, 即D+
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;     // 引脚工作模式
    GPIO_InitStruct.Pull = GPIO_PULLDOWN;           // 下拉
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;    // 引脚反转速度
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);         // 初始化
    HAL_Delay(5);                                   // 持续片刻

注意,最后一行的延时,是必须的,建议在5ms左右;

完成后,是这个样子的:

增加这段代码后,再无需手动插拔 虚拟串口的USB 线了,程序将模拟 “ 断开、插入”;

再次编译,确保上述操作正常 (先别烧录)。


五、发送的实现

发送数据的函数;

uint8_t  CDC_Transmit_FS ( uint8_t* Buf,  uint16_t Len );

        函数接受两个参数:数据缓冲区的地址、字节数。

        如果USB设备正忙,它会返回USBD_BUSY状态。

        这个函数的作用是设置传输数据的缓冲区,并标记数据包为待发送。数据并非立刻发出,而是被存储在USB外设的缓冲区中,等待主机轮询请求传输。

在main.c 的 while 循环中,添加三行测试代码:1行延时、两行发送数据;

注意:是共三行,1行间隔延时,2行发送,下面会解释具体原因!

新手,要省时间,就请按步骤操作 :

  • 先别插虚拟串口所用的USB线
  • 编译、烧录代码
  • 打开串口助手, (这时是没有插虚拟串口USB线的),查看目前有哪些端口号
  • 插入虚拟串口的USB线,到开发板的 USB-Slave接口 
  • ( 前提:win10、11系统已带虚拟串口驱动; win7要手动安装驱动:虚拟串口下载)
  • ( 电脑会自动识别到设备;如果是第一次使用虚拟串口,电脑将自动安装驱动程序)
  • 检查串口助手,发现多了一个端口号, 选择它,波特率等参数不用修改,打开端口;

串口助手,可以一直接收到数据了!!

效果如下图:

2、发送进阶(1)

上图中,只能收到第一行"Hello",而第二行发出的数据:"借点钱 "却没收到!!

不是没收到。其实,从STM32程序的角度,是没有发出数据!

先说说,USB虚拟串口通信的几个重点 (特指:USB2.0、全速模式、中断传输):

  • USB是轮询机制,主机对设备不断轮询,间隔最小1ms;不是固定的1ms,  是最小间隔时间;
  • USB的数据,是按包传输的; 
  • 每个设备,每1ms,最多传输1包数据;
  • 每包最多64字节(有效负载);

再说说,CDC_Transmit_FS ( ) 函数:

  • 它的第2个参数,"字节数",范围:0~2048; 这个2048可以在CubeMX里进行设置大小; 
  • 字节数 <= 64,算1包。如:发3个字节,也算1包。
  • 字节数 == 0,也算1包。俗称:空包; 如果上一帧刚好发送64字节,再发一个空包作为结束包;
  • 字节数 > 64, CDC_Transmit_FS ( ) 背后有缓存,它自动分包,1ms左右发1包,直至发完;
  • 如果上一包还没发完,再次调用CDC_Transmit_FS ( ) ,将放弃本次调用。

上面while循环中,连续、两次调用CDC_Transmit_FS ( ) .

  • 第1次调用,将正常发出一包数据;
  • 第2次调用,再发送一包。但是,没有间隔1ms以上,导致了第2次的发送,被舍弃了。

我们先打开 CDC_Transmit_FS ( ) 函数,看看函数原型。

在代码中,右击CDC_Transmit_FS ( ) ,弹出菜单,选择Go To Definition...,将跳转到函数位置;

或者,在左侧文件树中,双击打开usbd_cdc_if.c 文件 ,CDC_Transmit_FS ( ) 位于大约280行 ;

特别注意:

usbd_cdc_if.c 是应用层文件,我们对收、发有啥特殊需求,通过修改文件里的发送函数、接收回调函数、类请求函数,基本都能实现。

下图,是 CDC_Transmit_FS ( ) 函数截图。

红框的内容:当设备忙时,直接放弃发送,:

修改:

  • 注释掉 if 体3行代码;
  • 增加等待发送空闲、判断超时,如下6行;
  • 整个CDC_Transmit_FS ( ) 函数,如下:(可复制)
uint8_t CDC_Transmit_FS(uint8_t *Buf, uint16_t Len)
{
    uint8_t result = USBD_OK;
    /* USER CODE BEGIN 7 */
    
    USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef *)hUsbDeviceFS.pClassData; // 获得设备的状态信息结构体
    // if (hcdc->TxState != 0){
    // return USBD_BUSY;
    // }
    uint32_t timeStart = HAL_GetTick();
    while (hcdc->TxState)
    {
        if (HAL_GetTick() - timeStart > 20)
            return USBD_BUSY;
    }

    USBD_CDC_SetTxBuffer(&hUsbDeviceFS, Buf, Len);
    result = USBD_CDC_TransmitPacket(&hUsbDeviceFS);

    /* USER CODE END 7 */
    return result;
}

完成后,是这个样子:

再次编译、烧录程序。

串口助手,现在是这个样子的:

连续发送已实现了。

3、发送进阶(2)

上述方法能够简单地解决连续发送失败的问题。

但它存在一个显著缺点:由于其阻塞性质,频密连续发送时,将导致运行“死等”,影响程序效率。

(对于大部分场景,上述方法已足够。本节内容,只是为你预埋一种备用思路,无需死磕。)

如果项目对实时性有较高的要求,可以通过结合使用发送数据函数 CDC_Transmit_FS() 和发送完成回调函数 CDC_TransmitCplt_FS() 来提高传输效率。CDC_TransmitCplt_FS() 会在 CDC_Transmit_FS() 函数发送数据完毕后自动被调用。

根据这两个函数的特点,可以设计一套高效的发送缓存机制。例如,可以维护一个发送队列,当 CDC_Transmit_FS() 完成发送后,CDC_TransmitCplt_FS() 被调用时,从队列中取出下一个数据项进行发送,这样可以确保数据传输的连续性。

这个发送完成回调函数 CDC_TransmitCplt_FS() ,位于发送函数 CDC_Transmit_FS() 的正下方。

至于具体的代码实现,不同项目需求各异,无法提供一个通用的解决方案。因此,需要根据具体的项目需求,进行针对性的设计和优化,不能一药治百病。


六、接收

1、接收方式的简述

当USB CDC接收到来自USB主机的数据时,触发中断进入中断函数,继而自动调用接收回调函数:

int8_t  CDC_Receive_FS (uint8_t* Buf, uint32_t *Len);  

我们就在这个回调函数里,处理接收到的数据!

它在 usbd_cdc_if.c 文件,位于发送函数的正上方;

函数内部,生成的代码里,只有2行执行代码,指定下次接收的存放位置;  如下图所示:

而本次所接收到的数据,该如何处理,需要我们自行添加代码。

CDC_Transmit_FS (uint8_t*  Buf,  uint32_t  *Len), 接收回调函数:

  • uint8_t* Buf:   指向接收缓冲区的指针,即数据缓存的地址。
  • uint16_t* Len: 当前数据包的字节数。

注意事项:

  • 每当接收到一包数据,硬件自动触发中断函数, 继而调用此接收回调函数,无需人工调用。
  • 与发送机制相似,每间隔1ms,最多接收1包数据,每包最大64字节。。
  • 如果需要接收超过64字节的数据帧,注意,指上位机发送的1个完整数据帧,而非USB的单包数据,如,上位机发来一张图片数据,8350个字节,则需要在此回调函数中添加额外的代码来判断帧数据传输完整结束 、手动将多个数据包拼接成完整的数据帧。
  • 接收到数据时,缓存不会提前自动清零,新数据从Buf的起始位置开始,覆盖存放。
  • 由于该回调函数是被中断函数调用的,因此建议函数内部的处理尽可能地简短,以避免影响系统的实时性(中断函数运行期间,会令程序持续挂起)。

2、接收示范

本节将示范:

  • 通过串口助手,发送字符串 
  • STM32(设备方)收到数据后,把收到的字节数、字符串,发回串口助手显示(主机方)

在函数内的注释行  /* USER CODE BEGIN */  下方,添加4行自定义代码(可复制)。

    char myStr[64] = {0};                                                     // 定义一个数组,用于存放要输出的字符串
    sprintf(myStr, "\r\r收到 %d 个字节;\r内容是:%s\r\r", *Len, (char *)Buf); // 格式化字符串
    CDC_Transmit_FS((uint8_t *)myStr, strlen(myStr));                         // 发送
    memset(Buf, 0, 64);                                                       // 处理完数据,清0接收缓存;

注意:

  • 用char 声明myStr[ ], 是因为此处想把它作为一段字符串空间;
  • sprintf是C语言标准输入输出库的函数,如果报错没有这个函数,就:#include <stdio.h>
  • 获取字节数,是*Len,而不是Len;  因为它在函数参数里的声明,是一个指针;
  • 为了格式化成字符串,Buf用了(char*)进行强制转换成字符类型; 
  • CDC_Transmit_FS( )里,用了strlen获取字符串的字节数,它只对字符串有效,对其它数据类型无效;  如果报错没有这个函数,就:#include  <string.h>
  • 如果传输的是16进制数,用uint8_t 声明上面数组,然后修改sprintf的格式化方式。

添加完成后,文件是这样子的:

再次编译、烧录程序。

串口助手,打开对应的端口号(波特率等参数不用修改),

在发送区,以ASCII方式,发送字符串(因为添加的代码里用%s格式化,处理的是字符串),

然后,串口助手的接收区,马上能接收到刚才发出的数据!

至此,已实现接收的处理。

3、接收进阶

上面,我们已明白如何使用、处理接收到的数据。

在接收回调函数中,直接操作数据的发送,通常是安全的,因为这种操作耗时非常短,最多等待1次主机轮询周期(1ms)。这种情况下,不会对系统的稳定性和数据接收造成显著影响。

但是,如果在接收回调函数中执行耗时较长的操作,如显示到LCD或存储到Flash等,这些操作可能需要数毫秒到数十毫秒才能完成。耗时较长的操作,可能会导致接收过程中出现漏包现象。

因为接收回调函数是由USB中断服务程序调用的,属于中断处理的一部分,在回调函数执行期间,主程序还处于中断挂起状态,其他代码和中断也会被暂停执行,形象地描述:“卡死”。

如果中断服务程序的执行操作较耗时,会导致下一包数据无法及时进入中断,从而造成数据丢失。

举例说明:

  • A (主机)每隔1ms扔出1枚鸡蛋,B(中断服务函数)负责接鸡蛋。
  • 当B处置鸡蛋的时间占时极短(只要比A扔出的间隔更短,如0.5ms),那,没问题。
  • 当B处置鸡蛋的时间较长,接了鸡蛋还要写上价格,再放置到货架,共20ms, 那肯定就接不住A持续扔过来的鸡蛋了,每接1个,就会丢失后面的19个,再接1个,再丢失19个.......

我们需要采取一种策略,使得程序的中断响应、“卡死”占时,尽可能地短:

  • 接收数据:在中断回调函数中,我们仅执行必要的数据复制操作,即把接收到的数据迅速复制到外部缓存中。这一操作的耗时通常在us级别;
  • 处理数据:在主程序的while循环中,我们再对数据进行进一步的处理。由于这一处理过程不占用中断资源,因此不会影响程序对新数据的接收;

这种策略通过分离数据接收和数据处理两个步骤,确保了程序能够快速响应连续的数据流,同时避免了因处理时间过长而导致的数据丢失。

操作共4个步骤,具体如下:

① 增加全局变量

在usbd_cdc_if.c文件大约97行,配对的注释内,定义两个变量:

/* USER CODE BEGIN PRIVATE_VARIABLES */
uint8_t  myUsbRxData[64] = { 0 };   // 接收到的数据
uint16_t myUsbRxNum = 0;            // 接收到的字节数

/* USER CODE END PRIVATE_VARIABLES */

现在,它俩只是本地变量,等会要在main中用extern再声明一次,才能被外部调用。

完成后,是这个样子的:

② 修改接收回调函数

在CDC_Receive_FS() 里,删除我们上节增加的测试代码;

把Buf和*Len的数据,复制到我们刚才的两个变量里。函数修改成:

static int8_t CDC_Receive_FS(uint8_t *Buf, uint32_t *Len)
{
    /* USER CODE BEGIN 6 */
    // 把Buf里面的数据,复制到外部缓存
    memset(myUsbRxData, 0, 64);                     // 清0缓存区 
    memcpy(myUsbRxData, Buf, *Len);                 // 把接收到的数据,复制到自己的缓存区中
    myUsbRxNum = *Len;                              // 复制字节数    
    memset(Buf, 0, 64);                             // 处理完数据,清0接收缓存;                       
    
    // CubeMX生成的代码,保留    
    USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);   // 设置下-个接收缓冲区
    USBD_CDC_ReceivePacket(&hUsbDeviceFS);          // 启动下一个数据包的接收
    return (USBD_OK);                               
    /* USER CODE END 6 */
}

完成后,是这个样子的:

现在,数据接收部分,已处理好了。

以后回调函数运行时,只复制数据至外部缓存(备用),中断时间占用极短,不会影响下包接收。

③ 在外部用extern声明变量,令外部可调用数据

外部,哪个文件里要使用CDC接收的数据,就在这个文件里,用extern声明那俩变量。

如,可以在LCD文件,也可以在SD卡的文件中,都行。

建议在main.h文件中声明,其它文件再#include "main.h",这样,可以令变量全局可用。

  • 打开 main.c,右击空白,点击"Toggle Header/Code File",可以跳转到头文件:main.h  

在main.h中,大约38行,找到 配对的注释行 /* USER CODE BEGIN ET */

用 extern 再次声明刚才两个变量。如下(可复制):

注意,是只声明,不要赋值,否则编译错误。

/* USER CODE BEGIN ET */
extern uint8_t myUsbRxData[ ] ;
extern uint16_t myUsbRxNum ;

/* USER CODE END ET */

完成后,是这个样子的:

代码规范:
        这里的示范,使用全局变量,只是为了更清晰地演示操作思路。

        项目中,尽量避免使用全局变量;不同文件间的数据获取,可以封装成函数,如 CDC_GetRxData()、CDC_GetRxNum(),返回数据地址、接收的字节数。    

④ 使用接收到数据

在main.c的while循环中,通过判断myUsbRxNum的值,只要大于0,就表示收到数据了

记得每次处理完数据,把myUsbRxNum置0,以便于下一轮的判断。

再次烧录,烧录程序。

打开串口助手,发送测试文本,可以发现,能成功收到STM32发过来的回传数据。

题外话:

        如果,外部处理数据的速度跟不上,如,在while里每次收到数据都要显示到LCD,LCD的速度远慢于USB的传输,那,还不是变相丢了数据?!会的!

        那是程序逻辑和时间片机制的问题了,3天3夜也嗑不完!

        本节只讨论:确保每一包数据,都能被正常接收到。外部能否及时处理,不述。

至此,本篇完结。

如有错漏,望留言指正,及时更新!!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

高质量带货短视频素材来源推荐

在抖音带货时&#xff0c;寻找高质量视频素材至关重要。今天&#xff0c;我为大家分享五个可以下载高清无水印带货短视频素材的网站&#xff0c;帮助你轻松获取灵感和素材&#xff01; 蛙学网 蛙学网作为国内领先的短视频素材平台&#xff0c;提供多种类的带货短视频素材。无论…

[QT GUI Tips] Qt creator + PySide6 如何让图像控件的尺寸变化和窗口一致

前言&#xff1a;【这是个AI不会回答的问题】 Qt Creator 新的版本又发出了&#xff0c;Pyside6 有很多新功能。但是&#xff0c;一些传统的方法要被淘汰了。 一个经典的例子是&#xff1a; 我有个一个图像要显示在Form里面的图像控件上&#xff0c;OK&#xff0c; 我现在拖…

操作系统-系统调用

应用程序调用printf(),会触发系统调用write() 1、概念 操作系统服务的编程接口&#xff0c;通常由高级语言编写&#xff08;C/C&#xff09;&#xff0c;程序访问通常是通过高层次的API接口而不是直接进行系统调用。 2、三种最常用的应用程序编程接口&#xff08;API&#xf…

从零开始:网页在线制作入门指南

如果你对网页在线制作感兴趣&#xff0c;想学习如何从零开始创建一个网页&#xff0c;这个教程将带你了解基础步骤、所需工具以及如何将设计交付给开发人员的完整过程。接下来&#xff0c;让我们开始吧&#xff01; 一、 明确目标群体与网站用途 在启动网页制作之前&#xff…

【UE】简单介绍“Extra Win Function”插件的功能

“Extra Win Function”插件包含32个C类封住成的蓝图节点供用户使用&#xff0c;下面简单介绍19个可能常用的节点的功能。 1. “Is Internet Available” 检查是否可接入互联网 2. “Get Device Platform” 获取设备平台名称 3. “Get Android Device RAMSize” 获取RAM 大小 …

Leetcode 486. 预测赢家

1、心路历程 这道题最开始想到的做法是回溯&#xff0c;因为看起来遍历就可以做&#xff0c;但是又想到同时需要维护两个人的数据就有点懵了。后来提示说用动态规划做是OK的。 这道题最难的地方在于&#xff0c;需要把输赢建模成“净胜分”&#xff0c;这样就能把两个主体合并…

如何在Android Studio中找到CMakeLists.txt的打印信息

根据Android Studio 中的 CMake message 输出位置在哪里&#xff1f; - 简书 (jianshu.com) 的描述&#xff0c;MESSAGE函数打印的STATUS级别的信息在android studio中是看不到的。所以采用WARNING级别打印。 下面使用android studio 2023.2.1打印信息。编译之后&#xff0c;点…

[实用工具]Docker安装nextcloud实现私有云服务

Nextcloud是一款开源的云存储和协作平台&#xff0c;允许用户在自己的服务器上存储和访问文件&#xff0c;同时提供强大的协作工具。它可以替代商业云存储服务&#xff0c;让用户拥有完全控制和自主管理自己的数据。 Nextcloud支持文件上传和下载&#xff0c;可以通过Web界面、…

指针 (八)例题深度解析

有关于指针的基本知识点我们都已经讲解完了&#xff0c;不出意外的话&#xff0c;这一篇就是我们指针的最后一期了&#xff0c;今天我们就来看一看一些有关于指针运算的例题&#xff1a; 每一道题诸君都应该有着自己先独立思考的能力&#xff0c;咱们不要怕面对&#xff0c;不…

算法笔记day02

目录 游游的you 腐烂的苹果 孩子们的游戏 游游的you 游游的you__牛客网 这里需要注意 oooo 是3分而不是两分。 算法思路&#xff1a; 拼出you可以得2分&#xff0c;先拼出所有的you&#xff0c;在将所有的o拼在一起即可。 选3个字母个数最小的就是能拼出you的个数。 #in…

LeetCode讲解篇之34. 在排序数组中查找元素的第一个和最后一个位置

文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 这题让我们求目标值的左边界和右边界&#xff0c;我们可以采用二分查找搜索有序数组内大于等于目标值的最左边的下标 然后我们只需要在有序数组查找一下大于等于target的最左边下标 如果该下标越界或者下标对应…

陈奂仁「仓鼠艺术奥德赛」游戏体验上线:踏上沉浸式艺术之旅

今年 2 月&#xff0c;陈奂仁&#xff08;Hanjin&#xff09;在 The Sandbox 推出「仓鼠涂鸦」系列&#xff0c;将艺术与实用性融为一体。该系列包括 1,500 个以仓鼠为灵感的人物化身&#xff0c;其中更包括 8 款向著名艺术家致敬、独一无二的 NFT。这些人物化身通过「表情」&a…

芝法酱学习笔记(0.6)——nexus与maven私库

一、私库的需求 在一个公司中&#xff0c;后端程序员通常几十上百个。在没有镜像私库的情况下&#xff0c;每当引入新库时&#xff0c;大家都会从maven中央仓库下载一遍这个库。这样无疑十分浪费。再加之国家的防火墙政策&#xff0c;许多人下载lib包可能还会十分缓慢。不同程…

【黑马点评】8-12达人探店、好友关注、附近商户、用户签到、UV统计功能

【黑马点评】8-12达人探店、好友关注、附近商户、用户签到、UV统计功能 8 达人探店8.1 达人探店-发布探店笔记8.2 达人探店-查看探店笔记8.3 达人探店-点赞功能8.4 点赞排行榜 9 好友关注9.1 好友关注-关注和取消关注9.2 好友关注-共同关注9.3 好友关注-Feed流实现方案9.4 好友…

懒人笔记-QT程序UOS打包篇

懒人笔记-uos打包篇 前言1、deploy2、组织打包目录2.1 控制文件2.1.1 control的内容&#xff1a;2.1.2 postinst的内容&#xff1a;2.1.3 postrm的内容&#xff1a; 2.2 执行程序2.3 开机自启&#xff08;可选项&#xff09; 3、输出deb安装包4、服务卸载4.1 服务卸载4.2 程序按…

信息论笔记

知识点 学习视频链接 信息论简介和概率论复习 信息的定义 信息、信号、消息的概念 香农信息 信息论的研究对象和目的 信源&#xff1a;产生消息和消息序列的源编码器&#xff1a;将消息变为适合信道传输的物理量信道&#xff1a;传输或者储藏信号的媒介译码器&#xf…

mysql事务使用和事务隔离级别与sqlserver的比较

在 MySQL 中&#xff0c;事务 (Transaction) 是一个将一组 SQL 语句作为一个整体执行的机制。事务确保要么所有操作都执行成功&#xff0c;要么在遇到错误时回滚到之前的状态&#xff0c;从而保证数据库数据的一致性和完整性。 事务的四大特性&#xff08;ACID&#xff09; 事…

RISC-V笔记——基础

1. 前言 RISC-V旨在支持广泛的定制和专业化。RISC-V的ISA是由一个基本整型ISA和其它对基本ISA的可选扩展组成。每个整型ISA可以使用一个或多个可选的ISA扩展进行扩展。 基本整型ISA精选了最小的一组指令&#xff0c;这些指令足以为编译器、汇编器、链接器和操作系统提供足够的…

iPhone相册怎么删除相同照片

我们都太喜爱用iPhone拍照了&#xff0c;我们的iPhone相册就像是一个永远不想清理的衣柜&#xff0c;堆满了各种美好瞬间和意外的重复照片。面对成百上千的照片&#xff0c;有时候我们不禁想&#xff1a;这些相同的照片到底是怎么混进来的&#xff1f;今天&#xff0c;就让我来…

SpringBoot集成RocketMQ实现六种消息

1. 简介 RocketMQ 支持多种消息类型以满足不同的业务需求 普通消息&#xff08;Standard Message&#xff09;&#xff1a; 这是最常用的消息类型&#xff0c;适用于大多数场景。 可以设置延迟级别&#xff08;Delay Levels&#xff09;&#xff0c;但不支持消息轨迹。 顺序消…