STM32的蓝牙通讯(HAL库)

蓝牙基础知识(了解即可):

1.是一种利用低功率无线电,支持设备短距离通信的无线电技术,能在包括移动电话、PDAQ、无线耳机、笔记本电脑、相关外设等众多设备之间进行无线信息交换,蓝牙工作在全球通用的2.4 GHz(2.4 至 2.485 GH)ISM(即工业、科学、医学)频段(和WI-FI频段一样就能结合起来用),使用IEEE802.11协议。

第一~三蓝牙重心都往高速传输速率发展,但是由于WI-FI已经发展的很好了,所以没必要将发展理念和WI-FI重合到来第4代就将重心向低功耗靠拢

第四代蓝牙:主推Low Energy低功耗,BLE(Bluetooth Low Energy)低功耗功能。

第五代蓝牙开启物联网时代大门,在低功耗模式下具备更快更远的传输能力,为我们提供两种选择:低功耗远距离传输/高速近距离传输。(物联网广泛应用)

第六代蓝牙:新增加了信道功能(就是可以定位连接了蓝牙的设备,提高到1cm的精度)

蓝牙技术类型:

蓝牙协议包括两种技术:BR(Basic Rate经典蓝牙)和LE(Low Energy)都是使用2.4G无线电频率因此双模设备可以共享同一个天线。

这两种技术都包括搜索(discovery)管理、连接(connection)管理等机制,但它们是相互独立的,不能互通的技术!

厂商如果只实现了一种,那么只能与同样实现该技术的设备互通。如果厂商要确保能和所有的蓝牙设备互通,那么就只能同时实现两种技术,而不去管是否真的需要。

2.市场上常见蓝牙架构:

SOC蓝牙单芯片方案

此类芯片一般可以直接做为MCU用,这类产品一般用于消费类电子,集成度很高,调一调参数可以直接使用,常见的有蓝牙耳机,音响,蓝牙心率等产品。

SOC蓝牙+MCU方案

接外部主机,WI-FI就是这种架构蓝牙芯片内置射频模块,电路和物理层的各种支持,但是自己不烧写应用逻辑,那么我们就可以通过调用它给定的规则来调用它的功能符合我们的项目设想(蓝牙手表上网):

在集成好的蓝牙芯片基础上,通过特定的接口(UART居多),发送自定义的command来达到想要的功能,比如发送0x01代表搜索周围设备。

外设一个单芯片方案,其自定义指令包含蓝牙芯片(BT Controller)、微控制单元(MCU)以及蓝牙协议栈(BT Host)。

此部分的应用,将蓝牙作为一个外设使用,用于远程通信,例如网上卖的一些蓝牙串口。

比如我们后面应用就是采用的这种方案。 STM32作为MCU使用,ESP32作为蓝牙芯片使用。

蓝牙host+controller分开方案

单独将协议给功能更加强大的MCU处理目的就是为了实现更加强大的功能:

这种应用算是蓝牙最复杂的应用,客户需要使用蓝牙的场景有很多,涉及的蓝牙协议也有很多,需要将Host与Controller分开,集成更多的蓝牙协议,比如蓝牙电话(HFP)、蓝牙音频(A2DP)、蓝牙音乐控制(AVRCP)、蓝牙电话本(PBAP)、蓝牙短信(MAP),手机蓝牙等。

此部分应用,将定制蓝牙的各种服务,实现蓝牙多功能需求。

3.蓝牙协议栈:很复杂,厂家规定且打包好了,我们会用它的函数或者指令就行(一般都有用户指南,可以根据提示自己内测)

蓝牙芯片架构:

蓝牙的核心系统,由一个Host和一个或多个Controller组成。

(1)BT Host:逻辑实体(控制软件架构),在HCI(Host Controller Interface(host和Controller的接口层))的上层。

(2)BT Controller:逻辑实体(控制偏底层和硬件),在HCI(Host Controller Interface)的下层。

根据Host与Controller的组成关系,常见的蓝牙芯片也分为以下几种:

(1)单模蓝牙芯片:单一传统蓝牙的芯片,单一低功耗蓝牙的芯片。即1个Host结合1个Controller。

(2)双模蓝牙芯片:同时支持传统蓝牙和低功耗蓝牙的芯片。即1个Host结合多个Controller

对于BT Host而言虽然下层有不同的BT Controller但是我们都可以用软件来分开控制,写代码而已,所以就一个BT Host就行。

蓝牙协议是通信协议的一种,一般而言,我们把某个协议的实现代码称为协议栈(protocol stack),BLE协议栈就是实现低功耗蓝牙协议的代码。

4.BLE低功耗蓝牙协议栈框架

要实现一个BLE应用,首先需要一个支持BLE射频的芯片,然后还需要提供一个与此芯片配套的BLE协议栈,最后在协议栈上开发自己的应用

可以看出BLE协议栈是连接芯片和应用的桥梁,是实现整个BLE应用的关键。

简单来说,因为是由上到下的层层协议,所以BLE协议栈主要用来对你的应用数据进行层层封包(类似与CAN协议的控制段,应答段等),以生成一个满足BLE协议的空中数据包,也就是说,把应用数据包裹在一系列的帧头(header)和帧尾(tail)中。

蓝牙协议规定了两个层次的协议,分别为蓝牙核心协议(Bluetooth Core)和蓝牙应用层协议(Bluetooth Application)。

蓝牙核心协议关注对蓝牙核心技术的描述和规范,它只提供基础的机制,并不关心如何使用这些机制;

蓝牙应用层协议,是在蓝牙核心协议的基础上,根据具体的应用需求,百花齐放,定义出各种各样的策略,如FTP、文件传输、局域网等等。

ESP32-C3中的蓝牙功能:

ESP32-C3支持Bluetooth 5(LE)。下载好固件之后(我们前面下载的固件已经支持Wi-Fi和蓝牙了),STM32仍然可以通过AT指令操作蓝牙。

1.BLE角色划分

在 Bluetooth LE 协议栈中不同的层级有不同的角色划分。这些角色划分互不影响。

LL:设备可以划分为主机和从机,从机广播,主机可以发起连接。

GAP:定义了4种特定角色:广播者、观察者、外围设备和中心设备。(我们在编程不会涉及它)

GATT:设备可以分为服务端和客户端。定义了数据的组织结构和服务器端提供哪些服务,客户端就可以调用哪些服务。如果其为主机广播信号发起连接,本质底层是联网操作和上层是否是服务器或客户端无关,如果对方连接了主机,对方就是服务器,主机就是客户端,就好比你用手机监听到蓝牙,连上蓝牙不就是将手机数据发到蓝牙上,我们就可以戴蓝牙耳机听音乐了

2.BLE的地址(主机和从机建立连接后就知晓对方地址)

分为公共地址和随机地址。公共地址:BLE的公共地址,就类似于我们日常的身份证号码,是全球唯一的且不可改变的。为了保证BLE公共地址的全球唯一性,其需要向IEEE购买,然后IEEE组织就会对应地分配公共地址给买家。BLE的公共地址是全球唯一的,且在BLE设备的整个生命周期都不会改变。总长度为6个字节,共48位。

随机地址除了公共地址类型之外,还有一个随机地址类型,提供额外的隐私保护,其又分为静态地址(Static Address)和私有地址(Private Address),它们之间主要通过最高的2位有效位来区分。

(1)静态地址。总长度也是48位,但是最高位的2位必须是0b11。随机部分至少有一个位是0和1。也就是说不能全部是0或全部是1。设备重启之前不会改变。

(2)私有地址。总长度也是48位。最高位是00/01。随机部分至少有一个位是0和1。也就是说不能全部是0或全部是1。定时更新改变。

3.蓝牙扫描和通讯,蓝牙结点要广播信息通知别人进行连接

咱们蓝牙就作为LL的从机角色和GATT的服务端角色每各一段时间就发送广播信号,告诉别的设备咱们的存在。蓝牙可以使用40多个信道,但是广播信号只允许在37,38,39三个信道发出去,所以我们就能配是按顺序依次发出去,还是单独选一信道给发出去

通过扫描,主机能收到从机的广播包和回应数据包,然后给这些从机设备发连接请求,连接上后就能通信了。 这里还能配蓝牙能不能扫到,扫到能不能连,只能谁来连.......都能设,可以看用户指南

通讯可以配的:一个从机设备包括一个或者多个服务;一个服务中又可以包括一条或者多条特征值,每个特征值都有自己的属性 Property,属性的取值有:可读 Read、可写 Write、通知 Notify、指示Indicate。每个服务和特征值都有自己的唯一标识 UUID,标准UUID为128位,蓝牙协议栈中一般采用16位,也就是两个字节的UUID格式。

蓝牙通讯案例:透传模式下收发数据(选的芯片ESP32-C3)

用户指南翻到Bluetooth LE AT实例就有教怎么配的,我们选这个例子

.设置蓝牙透传模式:

透传模式:设备 A 发送一串字节流,通过蓝牙透传模式,设备 B 接收到的就是完全相同的字节流,就如同这两个设备直接用一根线连接起来进行数据传输一样,ESP32无需先知道数据有多长也不用做一堆命令解析的操作了,避免了普通数据传输模式的很多麻烦。

针对蓝牙的操作:

1.在手机端下载Bluetooth LE(随便在你手机的商城那搜索蓝牙助手,随便下个就行都能用)

2.初始化蓝牙

    printf("设置模块角色为:服务器...\n");uint8_t *cmd = "AT+BLEINIT=2\r\n";ESP32_SendCmd(cmd, strlen((char *)cmd));

3.创建蓝牙服务

    // 3. 服务端创建服务printf("服务端创建服务...\n");cmd = "AT+BLEGATTSSRVCRE\r\n";ESP32_SendCmd(cmd, strlen((char *)cmd));

4.开启蓝牙服务

    printf("服务端开启服务...\n");cmd = "AT+BLEGATTSSRVSTART\r\n";ESP32_SendCmd(cmd, strlen((char *)cmd));

5.获取/设置蓝牙自己的MAC地址,厂家就给设备买了唯一的MAC地址,我们直接查询就行,避免我们蓝牙和别的蓝牙名称一致分不出来

    printf("获取服务端MAC地址...\n");cmd = "AT+BLEADDR?\r\n";ESP32_SendCmd(cmd, strlen((char *)cmd));

6.设置广播参数,这内容太长了,大家去翻用户指南看会更清晰

    printf("设置蓝牙广播参数...\n");cmd = "AT+BLEADVPARAM=50,50,0,0,7,0\r\n";ESP32_SendCmd(cmd, strlen((char *)cmd));

7.设置广播数据

    printf("设置广播数据...\n");cmd = "AT+BLEADVDATAEX=\"mydevice\",\"A123\",\"0102030405\",1\r\n";ESP32_SendCmd(cmd, strlen((char *)cmd));

8.开启蓝牙广播

    // 8. 开始广播printf("开始广播...\n");cmd = "AT+BLEADVSTART\r\n";ESP32_SendCmd(cmd, strlen((char *)cmd));

9.查询蓝牙给连接的设备提供了什么服务,可写也可以不写

10.配置蓝牙 SPP

 

    printf("配置 BLE SPP 参数...\n");cmd = "AT+BLESPPCFG=1,1,7,1,5\r\n";ESP32_SendCmd(cmd, strlen((char *)cmd));

11.使能蓝牙 SPP(反馈OK,就说明能给手机发数据和收数据了)和手机连上了就开启,断开了就关闭

11.1使能SPP,和手机连上了就开启,断开了就关闭,要接收变化状态就给SYSMSG配成100就是4呗

    printf("设置系统提示信息...\n");cmd = "AT+SYSMSG=4\r\n";ESP32_SendCmd(cmd, strlen((char *)cmd));

static uint8_t BLE_IsConnChanged(uint8_t msg[])
{// 1. 如果是BLE建立连接,就进入SPP透传模式if (strstr((char *)msg, "+BLECONN:") != NULL){printf("有BLE客户端连接,马上进入SPP模式...\n");uint8_t * cmd = "AT+BLESPP\r\n";ESP32_SendCmd(cmd, strlen((char *)cmd));return 1;}// 2. 如果是BLE断开连接,就退出透传模式else if (strstr((char *)msg, "+BLEDISCONN:") != NULL){printf("BLE客户端断开连接,即将退出SPP模式...\n");// 直接串口发送 +++ 退出透传模式HAL_UART_Transmit(&huart2, "+++", 3, 1000);// 延时等待HAL_Delay(2000);return 1;}// 3. 如果是其它类型的连接变化(Wi-Fi),不做任何处理,返回1else if(strstr((char *)msg, "WIFI CONNECTED") != NULL|| strstr((char *)msg, "WIFI GOT IP") != NULL|| strstr((char *)msg, "+DIST_STA_IP:") != NULL){return 1;}// 4. 其它情况默认是正常数据return 0;
}

11.2STM32通过串口给ESP32发什么,蓝牙就会通过电磁波向外发送一样的数据

// 发送数据
void BLE_SendData(uint8_t txBuff[], uint16_t txLen)
{// 直接通过串口发送数据HAL_UART_Transmit(&huart2, txBuff, txLen, 1000);
}

11.3接收数据,没有进入SPP模式时接收到的都是蓝牙的内部数据,不是手机发来的,所以要判断连没连上手机

// 接收(读取)数据
void BLE_ReadData(uint8_t rxBuff[], uint16_t *rxLen)
{// 从串口接收数据或状态改变信息HAL_UARTEx_ReceiveToIdle(&huart2, rxBuff, 1024, rxLen, 1000);// 如果是空数据,rxLen = 0,就直接返回if (*rxLen == 0){return;}// 如果是连接变化信息,执行相应的SPP操作,并清零len,表示不是正常数据if ( BLE_IsConnChanged(rxBuff) ){*rxLen = 0;}
}

针对手机的操作:

1.蓝牙开启广播后,手机就能在蓝牙助手那连蓝牙了

2.连上后,要勾选需要的服务(点单),勾上蓝牙就能上菜了

3.通过串口助手给蓝牙发数据

ble.h

#ifndef __BLE_H
#define __BLE_H#include "esp32.h"// 初始化
void BLE_Init(void);// 发送数据
void BLE_SendData(uint8_t txBuff[], uint16_t txLen);// 接收(读取)数据
void BLE_ReadData(uint8_t rxBuff[], uint16_t *rxLen);#endif

ble.c


#include "ble.h"// 初始化
void BLE_Init(void)
{// 1. 初始化ESP32ESP32_Init();// 2. 设置蓝牙模块角色:2-serverprintf("设置模块角色为:服务器...\n");uint8_t *cmd = "AT+BLEINIT=2\r\n";ESP32_SendCmd(cmd, strlen((char *)cmd));// 3. 服务端创建服务printf("服务端创建服务...\n");cmd = "AT+BLEGATTSSRVCRE\r\n";ESP32_SendCmd(cmd, strlen((char *)cmd));// 4. 服务端开启服务printf("服务端开启服务...\n");cmd = "AT+BLEGATTSSRVSTART\r\n";ESP32_SendCmd(cmd, strlen((char *)cmd));// 5. 获取服务端MAC地址printf("获取服务端MAC地址...\n");cmd = "AT+BLEADDR?\r\n";ESP32_SendCmd(cmd, strlen((char *)cmd));// 6. 设置蓝牙广播参数printf("设置蓝牙广播参数...\n");cmd = "AT+BLEADVPARAM=50,50,0,0,7,0\r\n";ESP32_SendCmd(cmd, strlen((char *)cmd));// 7. 设置广播数据printf("设置广播数据...\n");cmd = "AT+BLEADVDATAEX=\"mydevice\",\"A123\",\"0102030405\",1\r\n";ESP32_SendCmd(cmd, strlen((char *)cmd));// 8. 开始广播printf("开始广播...\n");cmd = "AT+BLEADVSTART\r\n";ESP32_SendCmd(cmd, strlen((char *)cmd));// 9. 配置 BLE SPP参数printf("配置 BLE SPP 参数...\n");cmd = "AT+BLESPPCFG=1,1,7,1,5\r\n";ESP32_SendCmd(cmd, strlen((char *)cmd));// 10. 配置在透传模式下,连接发生变化时会传输一个提示信息printf("设置系统提示信息...\n");cmd = "AT+SYSMSG=4\r\n";ESP32_SendCmd(cmd, strlen((char *)cmd));
}// 发送数据
void BLE_SendData(uint8_t txBuff[], uint16_t txLen)
{// 直接通过串口发送数据HAL_UART_Transmit(&huart2, txBuff, txLen, 1000);
}// 自定义函数,判断是否有连接变化信息;如果有则返回1,没有返回0
static uint8_t BLE_IsConnChanged(uint8_t msg[]);// 接收(读取)数据
void BLE_ReadData(uint8_t rxBuff[], uint16_t *rxLen)
{// 从串口接收数据或状态改变信息HAL_UARTEx_ReceiveToIdle(&huart2, rxBuff, 1024, rxLen, 1000);// 如果是空数据,rxLen = 0,就直接返回if (*rxLen == 0){return;}// 如果是连接变化信息,执行相应的SPP操作,并清零len,表示不是正常数据if ( BLE_IsConnChanged(rxBuff) ){*rxLen = 0;}
}static uint8_t BLE_IsConnChanged(uint8_t msg[])
{// 1. 如果是BLE建立连接,就进入SPP透传模式if (strstr((char *)msg, "+BLECONN:") != NULL){printf("有BLE客户端连接,马上进入SPP模式...\n");uint8_t * cmd = "AT+BLESPP\r\n";ESP32_SendCmd(cmd, strlen((char *)cmd));return 1;}// 2. 如果是BLE断开连接,就退出透传模式else if (strstr((char *)msg, "+BLEDISCONN:") != NULL){printf("BLE客户端断开连接,即将退出SPP模式...\n");// 直接串口发送 +++ 退出透传模式HAL_UART_Transmit(&huart2, "+++", 3, 1000);// 延时等待HAL_Delay(2000);return 1;}// 3. 如果是其它类型的连接变化(Wi-Fi),不做任何处理,返回1else if(strstr((char *)msg, "WIFI CONNECTED") != NULL|| strstr((char *)msg, "WIFI GOT IP") != NULL|| strstr((char *)msg, "+DIST_STA_IP:") != NULL){return 1;}// 4. 其它情况默认是正常数据return 0;
}

main.c

// 全局变量,接收数据缓冲区、长度
uint8_t rxBuff[1024];
uint16_t rxLen;int main(void)
{/* USER CODE BEGIN 1 *//* USER CODE END 1 *//* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* USER CODE BEGIN Init *//* USER CODE END Init *//* Configure the system clock */SystemClock_Config();/* USER CODE BEGIN SysInit *//* USER CODE END SysInit *//* Initialize all configured peripherals */MX_GPIO_Init();MX_USART1_UART_Init();// MX_USART2_UART_Init();/* USER CODE BEGIN 2 */// 蓝牙初始化BLE_Init();/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){// 读取数据BLE_ReadData(rxBuff, &rxLen);// 如果接收到数据,就原样发回去if (rxLen > 0){printf("接收到数据!数据长度 = %d, 内容 = %.*s\n", rxLen, rxLen, rxBuff);BLE_SendData(rxBuff, rxLen);// 将长度清0rxLen = 0;}/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}/* USER CODE END 3 */
}

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

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

相关文章

方案B,version1

我们重新设计起步阶段的步骤,目标是:通过运行PowerShell脚本和配置GitHub Actions工作流(deploy.yml)来实现自动化部署。 要求: 用私有仓库(my-website-source-SSH)存储源码。 通过GitHub Actions自动构建(这里只是简单的Hello World,所以构建步骤可以简化为复制文件…

Linux --- 进程

一、进程概念 在 Linux 系统中,​​进程(Process)​​ 是程序执行的动态实例,是操作系统进行资源分配和调度的基本单位。 ​​1. 程序 vs 进程​​ ​​程序(Program)​​:是静态的代码集合&…

Cgroup 控制组学习(三)在容器中使用 CGroups

一、CGroups 关于mememory的限制操作 cgroup关于cpu操作 关于memeory cgroup的几个要点 ① memeory限额类 1、memory.limit_bytes:硬限制--> 限制最大内存使用量,单位有k、m、g三种,填-1则代表无限制,默认是字节2、memory.soft_limi…

SpringBoot面试基础知识

SpringBoot 是面试中后端开发岗位的高频考点,以下是核心考点整理:1. SpringBoot 基础概念- 定义:SpringBoot 是 Spring 框架的简化版,通过“自动配置”“起步依赖”等特性,简化 Spring 应用的搭建和开发,减…

Java面试全方位解析:从基础到AI的技术交锋

Java面试全方位解析:从基础到AI的技术交锋 面试场景:互联网大厂Java工程师岗位面试 面试官:您好,我是今天的面试官,接下来我们将进行三轮技术面试。 谢飞机:您好您好!我是谢飞机,特别…

Web Worker:解锁浏览器多线程,提升前端性能与体验

目录 一、Web Worker 是什么? 核心特性 类型 二、为什么需要 Web Worker?(单线程的痛点) 三、Web Worker 的典型使用场景 四、一个简单的代码示例 (专用 Worker) 五、使用 Web Worker 的注意事项 六、总结 一、Web Worker 是什么? 简…

LabVIEW命令行调用与传参功能

该功能一方面借助 Formatinto String 构建命令行字符串,实现LabVIEW 环境下命令行调用 VI 并传参;另一方面,针对 Mac 平台,通过解析应用 Info.plist 文件,处理 LabVIEW 可执行文件路径,完善跨平台命令行调用…

使用FRP搭建内网穿透工具,自己公网服务器独享内外网端口转发

内网穿透,也即 NAT 穿透,进行 NAT 穿透是为了使具有某一个特定源 IP 地址和源端口号的数据包不被 NAT 设备屏蔽而正确路由到内网主机。简单来说,就是让互联网(外网)设备能访问局域网(内网)设备提…

JavaWeb01——基础标签及样式(黑马视频笔记)

1.如何用VScode写html代码 1. 首先在vscode上安装一些插件,插件如下: 2.打开你要写入的html文件的文件夹,然后右击“ 新建文件”,命名 “xxx.html”, 3.如果是写 css文件,那么也是右击“新建文件”,命名“x…

在2G大小的文件中,找出高频top100的单词

将 2GB 的大文件分割为 2048 个大小为 512KB 的小文件,采用流式读取方式处理,避免一次性加载整个文件导致内存溢出。初始化一个长度为 2048 的哈希表数组,用于分别统计各个小文件中单词的出现频率。利用多线程并行处理机制遍历所有 2048 个小…

基于LNMP分布式个人云存储

1.准备工作a.关闭两台虚拟机的安全软件客户端:[rootmaster ~]# systemctl stop firewalld [rootmaster ~]# systemctl disable firewalld [rootmaster ~]# systemctl status firewalld ○ firewalld.service - firewalld - dynamic firewall daemonLoaded: loaded (…

指针运算全攻略:加减、比较与排序

常见的指针指针运算说明1.指针与整数的加减运算对指针可以进行加法运算&#xff0c;即p n或者p - n。其结果依旧是一个是一个指针&#xff0c;新的指针是在原来的地址值基础上加上/减去n *(sizeof(指针指向的数据类型)&#xff09;个字节。示例&#xff1a;#include<stdio.…

物联网安装调试-物联网网关

物联网网关作为连接终端设备与云平台的核心枢纽,其分类与选型需结合功能定位、硬件性能、连接方式及应用场景等多维度考量。以下从分类体系和产品推荐两方面系统梳理,助您高效决策: 🔧 一、物联网网关分类体系 1. 按功能定位划分 类型 核心能力 典型场景 代表产品 边缘计…

Jenkins教程(自动化部署)

Jenkins教程(自动化部署) 1. Jenkins是什么&#xff1f; Jenkins是一个开源的、提供友好操作界面的持续集成(CI)工具&#xff0c;广泛用于项目开发&#xff0c;具有自动化构建、测试和部署等功能。Jenkins用Java语言编写&#xff0c;可在Tomcat等流行的servlet容器中运行&…

linux 驱动验证是否成功 之 查看moudle信息

这些是 Linux 内核模块&#xff08;.ko&#xff09;中的元信息&#xff08;metadata&#xff09;&#xff0c;可以通过如下方式查看&#xff1a;✅ 1. 使用 modinfo 命令查看已加载或已编译模块信息 示例&#xff1a; modinfo aw2013.ko输出内容大概如下&#xff1a; filename:…

浏览器关闭之前请求接口到后端

2025.07.24今天我学习了如何在浏览器关闭之前请求一个接口返回到后端。可以用performance.navigation判断是浏览器关闭&#xff0c;还是浏览器刷新&#xff0c;因为我这边只需要浏览器关闭的时候才去触发1. 利用performance API&#xff08;刷新检测&#xff09; 刷新页面时&am…

MySQL批量数据处理与事务管理

MySQL是一种广泛应用的关系型数据库管理系统&#xff0c;尤其在数据分析和业务逻辑处理方面具有重要地位。在数据量庞大的业务场景中&#xff0c;批量数据处理和事务管理是提高效率和保障数据一致性的重要手段。掌握高效的批量数据操作方法与事务管理技巧&#xff0c;不仅能够提…

iOS网络之异步加载

为什么你的图片要异步加载&#xff1f;在仿写天气预报时&#xff0c;我们常常需要从网络加载天气图标&#xff0c;例如显示某个小时的天气状态图标。这看似简单的事情&#xff0c;如果处理不当&#xff0c;却很容易造成界面卡顿&#xff0c;甚至影响整个 App 的用户体验。错误做…

C#值类型属性的典型问题

问题复现&#xff1a;值类型属性的副本问题以下代码展示了值类型属性的典型问题&#xff1a;struct Point {public int X;public int Y; }class MyClass {public Point Position {get; set;} }// 使用属性修改结构体&#xff08;无效&#xff01;&#xff09; var obj new MyC…

机器学习基础-k 近邻算法(从辨别水果开始)

一、生活中的 "分类难题" 与 k 近邻的灵感 你有没有这样的经历&#xff1a;在超市看到一种从没见过的水果&#xff0c;表皮黄黄的&#xff0c;拳头大小&#xff0c;形状圆滚滚。正当你犹豫要不要买时&#xff0c;突然想起外婆家的橘子好像就是这个样子 —— 黄色、圆…