基于串口的BLE模组CC2640R2使用总结

news2024/10/11 16:26:22

之前写过蓝牙控制芯片nRF52832的一篇概述,里面主要记录了蓝牙的分层结构,需要的话可参考:nRF52832蓝牙概述_路溪非溪的博客-CSDN博客

这篇文章记录的是蓝牙模组的基本使用。

二者有何区别呢?

nRF52832是一款基于蓝牙的主控芯片,灵活度较高,能够实现更多的功能,它几乎和STM32芯片类似,有各种各样实现功能的外设,蓝牙只是其主打的一个外设而已,蓝牙协议实现得很全面。

而BLE模组,基本上就是作为一个透传的模块来使用,等同于一根导线,试想下,如果是一个传感器需要跟主控芯片通信,通过导线就可以了,但是蓝牙因为是无线的,没有导线,那怎么传输信号呢?这时候就需要一个透传模块。而CC2640R2所起的就是这个作用。

这个概念,就是透传。

什么是透传?

假如我现在有个蓝牙从机在板子上和单片机通过串口连接,这时,蓝牙主机通过蓝牙协议连接上蓝牙从机,此时,双方如何通信呢?

蓝牙主机发消息给蓝牙从机,蓝牙从机模组不会对这个数据进行任何额外的处理,而是会直接将这个数据转发给串口,此时单片机通过串口读到数据;当单片机需要发数据给主机时,直接将数据通过串口发送给蓝牙从机模组,同样的,蓝牙从机模组不会对数据进行任何的额外处理,而是直接将数据转发给主机。

从上面描述的过程来看,是不是蓝牙从机就好像不存在一样,单片机通过串口就能直接跟主机进行通信,我们也不需要在蓝牙从机上做任何数据操作,就好像这个蓝牙从机就是一根导线。

这种传输模式,就叫做透明传输。

理解透传之后,我们再来了解一下蓝牙模组的模式。

AT指令模式和透传模式

其实,市面上的各种集成模组,大部分都是采样这样的方式来实现,即存在两种模式,那就是AT指令模式和数据透传模式。

为什么有这两种模式呢?就以BLE模组CC2640来讲一下。

首先,如果要让模组实现功能,肯定要对其进行一些设置,设置成功之后,才能进行正确的数据传输,任何外设都是如此,一定是要先初始化,设置需要的一些参数,之后才能正常通信。

蓝牙模组也不例外,所以,模组通常就集成了相应的AT指令,我们只要发送指令给模组,就能对模组进行设置,此时,蓝牙模组就处于AT指令模式,在这种模式下,单片机发送的指令,模组在接收之后,并不会透传出去,而是会自己执行;当我们在AT指令模式下将模组的参数设置好,蓝牙也连接上了之后,就可以进入透传模式,这时,单片机给模组发的数据,就会透传出去,模组本身并不会对其进行任何额外处理。

示例如下:

对于这两种模式的切换,不同的模组有不同的方式,有的模组会通过一个引脚的电平切换来实现模式的转换,而有的模组,在连接之前处于AT指令模式,只要连接上了蓝牙,就会自动进入到透传模式,不用进行额外的操作。

BLE模组CC2640采用的就是第二种自动切换的方式。

透传模式,是模组内部就实现了数据的转发,而不需要我们去实现。

注意:

如果处于透传模式,即使发了AT指令,模组也不会执行,而是会当做数据透传出去。

说明下:

nRF52832不是蓝牙模组,而是一个蓝牙主控芯片,所以除了蓝牙的协议,其他部分需要我们自行设置和实现,就比如透传,就需要我们自己写代码去实现数据的转发。

蓝牙主机和蓝牙从机

蓝牙通信中,分为主机和从机,有的模组实现了从机功能,就只能作为从机;有的模组实现了主机功能,就只能作为主机;有的模组既实现了主机功能,又实现了从机功能,所以是主从一体的,既能配置为主机,也能配置为从机。所以在购买蓝牙模组时,就需要注意自己到底需要买的是哪种类型的模组。像nRF52832蓝牙主控芯片,购买时并不存在是主机还是从机一说,而是我们自己用代码去实现主机还是从机的功能。

通常来说,蓝牙协议是一个通用协议,只要主从双方,都是使用的蓝牙协议,并且,从机是在主机的连接范围之内,就可以连接。就像单片机的程序,只要是同一款芯片,不管是烧到哪个芯片里,作用都是一样的,要做的,可能就是改变一下引脚定义,应用软件部分是不用修改的。

再次补充介绍下蓝牙协议

基本概念

蓝牙版本

由此可见

蓝牙协议是一个通用协议;

BLE是属于蓝牙4.0以上版本的规范。

设备类型

一般来说,手机都是双模设备,既支持蓝牙低功耗,又支持传统蓝牙,所以,不管蓝牙模组使用的是BLE还是传统蓝牙,手机都能和他们建立连接并通信。

CC2640就是一个单模的设备,只支持BLE。

BLE体系结构

从对链路层的描述我们可以知道:

1、一个设备,同一时刻,要么是主机,要么是从机,不可能同时既是主机又是从机。

2、只有建立连接之后,才会互传数据,所以为什么要建立连接之后模组才能进入透传模式,就是这个原因。

3、为什么需要AT指令模式,就是因为设备上电后,不可能直接进入连接态,必须先设置,然后经由广播态或者发起态才能进入连接态。因此,我们在编写程序时,一定是先设置AT指令,开启广播,连接成功后才会发送透传数据。

链路层信道映射

BLE广播、扫描和连接事件

广播事件

广播是从设备发起的,可以被主设备扫描到,并能对扫描进行响应,即扫描响应。

扫描事件

扫描是主机的行为。

连接事件

注意,建立连接之前,走的都是广播信道,建立连接之后,就会走数据信道。

Profile、Service、Characteristic和UUID

CC2640使用概述

CC2640 R2 透传分为蓝牙主机版本,以及蓝牙从机版本

本文以蓝牙从机为例,场景是:MCU和手机APP的通信。

透传模式,又叫桥接模式。

桥接模式:这是常用模式,搭建传统 MCU 与手机蓝牙之间的通信桥梁。用户MCU 可以通过模块的通用串口和移动设备进行双向通讯,用户也可以通过特定的串口 AT 指令,对某些通讯参数进行管理控制。用户数据的具体含义由上层应用程序自行定义。移动设备可以通过 APP 对模块进行写操作,写入的数据将通过串口发送给用户的 MCU。模块收到来自用户 MCU 串口的数据包后,将自动转发给移动设备。此模式下的开发,用户必须负责主 MCU 的代码设计,以及智能移动设备端 APP 代码设计。

模块通过初始设置后会自动进行广播,与打开特定 APP 的手机会对其进行扫描和对接,成功之后便可以通过 BLE 协议对其进行监控。
实物图展示:
引脚说明:
这里面除了串口引脚之外,还有几个比较重要的引脚。
Wakeup引脚,用来唤醒模组,之后才能进行串口传输。
BleCtrl引脚,用来控制广播的开启和关闭。
BleState引脚,用来监测模组当前处于连接还是断开状态。
一般的工作步骤是这样的。
上电后,先要发指令,发指令时,要Wakeup唤醒模组,然后才能发,指令发完之后,根据实际情况,在必要时通过BleCtrl打开广播,打开广播后,主机就可以连接模组了,连接成功后,模组就会进入透传模式,此时,就可以收发数据了,发送数据时,也要先唤醒模组。
然后,通过BleState实时监测蓝牙的连接和断开,以便做不同的处理。
桥接模式
可以看到,这里的通道分为BLE参数配置通道和用户数据通道,就是分别在AT指令模式和透传模式下使用的信道。
AT指令说明
此处仅部分AT指令,更多查看数据手册。
需要注意的是,AT指令的结尾就是回车换行符,也就是\r\n
指令格式如下:
模组主动发出的提示信息
关于指令的详细内容,自行查看数据手册。
以下仅以设置模组名称举例说明。
有的指令只能写,有的指令只能读,有的只能既能写也能读。
比如设置模组名称指令,既能写,也能读。
写的时候,就是给模组取名。
读的时候,就是读当前模组的名称。
比如上述的回复:
AT+OK
TTC-BLE
首先,总是会返回AT+OK+回车换行,之后如果有内容,再返回内容+回车换行。

透传的软件实现

先附上代码,然后再进行说明。

#include "ble.h"
#include "a_board.h"

//定义串口接收数据相关的变量
uint8_t g_Uart1RecvBuf[200];//接收缓冲,一帧数据
uint16_t g_Uart1RecvCount = 0;//接收数目
uint8_t USART1_FRAME_RX_STA = 0;//接收状态标记

void ble_uart_send_data(uint8_t *data, uint16_t len);
void wakeup_ble_usart(void);

//初始化蓝牙模块的IO(除串口外)
//PA8   BLE-WAKEUP-MCU输出
//PC7   BLECtrl-MCU输出
//PC8   BLEState-MCU输入
//PC9   BLE-INT-MCU输入
static void ble_io_init(void)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
        
    //配置时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC, ENABLE);	 //使能PA端口时钟

    //IO通用参数配置
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 //IO口速度为50MHz
    
    //PA8
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;				 //PA8 端口配置
    GPIO_Init(GPIOA, &GPIO_InitStructure);					 //根据设定参数初始化GPIOA.8
    
    //PC7
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;				 //PC7端口配置
    GPIO_Init(GPIOC, &GPIO_InitStructure);					 //根据设定参数初始化GPIOC.7
    
    //PC89
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
	GPIO_Init(GPIOC, &GPIO_InitStructure);  
}

//蓝牙的通信串口初始化
//PA9   BLE-TX-串口1
//PA10  BLE-RX-串口1
static void ble_usart_init()
{
	//GPIO端口设置
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);	//使能USART1时钟和GPIOA时钟

	//USART1_TX   GPIOA.9
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
	GPIO_Init(GPIOA, &GPIO_InitStructure);

	//USART1_RX	  GPIOA.10初始化
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
	GPIO_Init(GPIOA, &GPIO_InitStructure);  

	//USART 初始化设置
	USART_InitStructure.USART_BaudRate = 256000;//串口波特率
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
	USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收发模式
    USART_Init(USART1, &USART_InitStructure); //初始化串口1
    
    //USART1中断配置
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化NVIC寄存器
    
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接收中断
    USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);//开启串口空闲中断
    
    USART_Cmd(USART1, ENABLE);                    //使能串口1
}

//蓝牙串口1数据发送函数
void usart1_send_data(uint8_t *txData, uint8_t txLength)
{
	for(uint8_t i = 0; i < txLength; i++)
	{
        USART_SendData(USART1, txData[i]);//向串口1发送数据
		while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) != SET);//等待发送结束
	}
}

//蓝牙-串口1数据接收函数
void USART1_IRQHandler(void)                	//串口1中断服务程序
{
    uint8_t clear;//清除空闲中断
    
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断
	{
        USART_ClearFlag(USART1, USART_IT_RXNE);
		g_Uart1RecvBuf[g_Uart1RecvCount++] = USART_ReceiveData(USART1);
	}
    else if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)
	{
		clear = USART1->SR; // 清除空闲中断
        clear = USART1->DR; // 清除空闲中断

		USART1_FRAME_RX_STA = 1;//一帧数据接收完成
	}
}

//唤醒蓝牙串口传输
void wakeup_ble_usart(void)
{
    GPIO_ResetBits(GPIOA, GPIO_Pin_8);//置低电平
}

//关闭蓝牙串口传输
void close_ble_usart(void)
{
    GPIO_SetBits(GPIOA, GPIO_Pin_8);//置高电平
}

//开启从机广播
void open_slave_broadcast(void)
{
    GPIO_ResetBits(GPIOC, GPIO_Pin_7);//置低电平
}

//关闭从机广播
void close_slave_broadcast(void)
{
    GPIO_SetBits(GPIOC, GPIO_Pin_7);//置高电平
}

//发送指令
void ble_uart_send_cmd(char *cmd)
{
	int len = strlen(cmd);
    
    wakeup_ble_usart();//唤醒蓝牙串口传输
    delay_ms(2);
    
    //发送指令数据
	usart1_send_data((uint8_t *)cmd, len);
}

//发送透传数据
void ble_uart_send_data(uint8_t *data, uint16_t len)
{
	wakeup_ble_usart();//唤醒蓝牙串口传输
    delay_ms(2);
	
    //发送透传数据
	usart1_send_data(data, len);
}

//蓝牙初始化
void ble_init(void)
{
    ble_io_init();//蓝牙io初始化
    ble_usart_init();//蓝牙串口初始化
    
    //设置蓝牙名称
    ble_uart_send_cmd("AT+NAME=TEST\r\n");
    
    //初始先关闭从机广播
    close_slave_broadcast();
}

//蓝牙透传接收到数据后的处理函数
void ble_received_data_handler(void)
{
    if(USART1_FRAME_RX_STA)
    {   
        //接收到一帧数据后,就直接发给模拟板
        SendDataToAnBoard(g_Uart1RecvBuf, g_Uart1RecvCount);
        
        USART1_FRAME_RX_STA = 0;
        g_Uart1RecvCount = 0;
    }
}

//检测蓝牙当前状态
uint8_t CheckBleState(void)
{
    if(GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_8) == SET)
    {
        return 0;//断开状态
    }
    else
    {
        return 1;//连接状态
    }
}

注意,这里从机取的蓝牙名称,要在主机的过滤器有效范围内。

补充说明:

这里需要对串口做一些补充。

其实,不管是不是串口,或者其他接收数据的外设,都有一个值得重点注意的问题。

那就是,接收数据时,不要一个一个地去处理,而是必须先将其存储在一个缓存中,然后需要的时候再去缓存里拿数据进行处理转发等操作。

为什么?因为底层数据接收的速度非常快,而通常数据处理的速度就慢得多,慢的操作绝对无法跟上快的操作,所以必定导致一种现象:数据来得太快,我拿数据处理时,当前数据还没处理完,就已经过去好多数据没被接收到,这样,就会漏数据。而且,可能导致数据位错乱,导致拿到的数据是乱码。

可以看到,上面单次处理的方式,在第四次处理时,你以为你处理的只是第4个点的数据,其实,可能已经过去成百上千个数据了,如果用一个变量来接收,那早就被覆盖了。

所以,必须先缓存下来,然后需要的时候去处理

注意,我这里没有说建议,而是必须,因为如果不这么做的话,数据就不对。

还有一个问题就是,很多时候,我们都必须知道接收了多少字节的数据,这样才方便处理,如果接收的是定长数据就比较好办,但如果是不定长数据就比较麻烦。之前提到过一种串口空闲中断+DMA的方式,因为DMA提供了一个函数,可以间接算出接收了多少字节。但是如果不用DMA呢?那就可以通过串口接收中断+空闲中断的方式来处理。

举例说明

这里假如接收到一帧5个字节的数据,那么,接收中断就会进入5次,在第5次接收中断之后,就会出现一个空闲中断,此时,就不会再进入接收中断,而是空闲中断,这样,就能知道到底接收了多少个字节的数据。

这一点非常非常重要。

可以参考这篇文章:STM32使用串口IDLE中断的两种接收不定长数据的方式

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

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

相关文章

QUIC协议科普导入(一)

一&#xff1a;QUIC协议导入 QUIC是一个通用的传输层网络协议&#xff0c;最初由Google的Jim Roskind设计&#xff0c;2012年实现并部署&#xff0c;2013年随着实验范围的扩大而公开发布&#xff0c;并向IETF描述。虽然长期处于互联网草案阶段&#xff0c;但在从Chrome浏览器到…

【考研数学】高等数学第五模块 —— 级数(3,傅里叶级数)

系列文章 【考研数学】高等数学第五模块 —— 级数&#xff08;1&#xff0c;常数项级数&#xff09; 【考研数学】高等数学第五模块 —— 级数&#xff08;2&#xff0c;幂级数&#xff09; 文章目录 引言三、傅里叶级数3.1 周期为 2 π 2\pi 2π 的函数的傅里叶级数3.2 定…

VS Code输出窗口显示中文乱码的解决办法

今天用requests测试web接口&#xff0c; response的编码是utf-8&#xff0c;结果在输出窗口显示的中文乱码&#xff0c;查了很多资料&#xff0c;最终找了一个比较好解决方案 url "XXXXXX"payloadeid16331117402headers {User-Agent: Apifox/1.0.0 (https://apifox…

SpringBoot原理-自动配置-概述

自动配置 SpringBoot的自动配置就是当Spring容器启动后&#xff0c;一些配置类、bean对象就会自动存入IOC容器中&#xff0c;不需要我们手动去声明&#xff0c;从而简化了开发&#xff0c;省去了繁琐的配置操作。启动一个SpringBoot项目后&#xff0c;观察如下

CRM系统主要通过什么来提升销售业绩

我们常说的CRM就是客户关系管理系统&#xff0c;它可以收集、整理和分析客户数据&#xff0c;帮助企业深入了解客户&#xff0c;提高客户转化率。CRM还可以提供市场获客、线索分配、售后服务等功能。下面就来说说&#xff0c;CRM主要是干什么的&#xff1f; CRM主要是做以下几…

当所有行业都在数字化转型时,实体商家如何快速“破局”

当今世界&#xff0c;信息技术创新日新月异&#xff0c;数字化、网络化、智能化深入发展。 国家多次明确强调推进“数字中国”建设&#xff0c;持续促进数字技术和实体经济深度融合&#xff0c;协同推进数字产业化和产业数字化。 对于实体商家来说&#xff0c;数字化转型已经不…

Tomcat服务部署、优化及多实例实验(Nginx+Tomcat负载均衡、动静分离)

目录 1 Tomcat 1.1 tomcat 构成 1.2 什么是 servlet&#xff1f; 1.3 什么是 JSP? 1.4 Tomcat 功能组件结构 1.5 Container 结构分析 1.6 Tomcat 请求过程 2 Tomcat 服务部署 2.1 环境部署 2.1.1 ​编辑 2.1.2 设置JDK环境变量 2.2 安装启动Tomcat 2.3 优化tomca…

第4章_瑞萨MCU零基础入门系列教程之瑞萨 MCU 源码设计规范

本教程基于韦东山百问网出的 DShanMCU-RA6M5开发板 进行编写&#xff0c;需要的同学可以在这里获取&#xff1a; https://item.taobao.com/item.htm?id728461040949 配套资料获取&#xff1a;https://renesas-docs.100ask.net 瑞萨MCU零基础入门系列教程汇总&#xff1a; ht…

使用 FastChat 运行 CodeLlama-7b-Instruct-hf

使用 FastChat 运行 CodeLlama-7b-Instruct-hf 1. 确认 FactChat 支持的 Model2. 升级依赖3. 启动 controller4. 启动 CodeLlama5. 启动 api server6. VSCode 中使用 CodeLlama 1. 确认 FactChat 支持的 Model 访问 model_support.md&#xff0c;确认 codellama/CodeLlama-7b-…

浅谈电商平台API接口能做什么电商功能应用【以淘宝电商为例】

API接口我们很多行业都需要用到。作为电商从业者&#xff0c;更是对电商平台的各种API的功能和应用了如指掌&#xff01;本文就以淘宝平台API接口&#xff0c;封装的商品详情&#xff0c;SKU等各类商品数据API接口为例&#xff0c;我们来看看API接口都能怎么应用&#xff0c;而…

SpringBoot自动配置原理及使用流程

SpringBoot自动配置原理及使用流程 SpringBoot自动配置原理 具体流程 1、导入场景 以starter-web为例 场景启动器导入了相关场景的所有依赖&#xff0c;如&#xff1a;starter-json,starter-tomcat,spring-webmvc。 每个场景启动器都引入了一个spring-boot-starter,核心场景…

【ROS 06】机器人系统仿真

对于ROS新手而言&#xff0c;可能会有疑问:学习机器人操作系统&#xff0c;实体机器人是必须的吗&#xff1f;答案是否定的&#xff0c;机器人一般价格不菲&#xff0c;为了降低机器人学习、调试成本&#xff0c;在ROS中提供了系统的机器人仿真实现&#xff0c;通过仿真&#x…

h5开发网站-页面内容不够高时,如何定位footer始终位于页面的最底部

一、问题描述&#xff1a; 在使用h5开发页面时&#xff0c;会遇到这个情况&#xff1a;当整个页面高度不足以占满显示屏一屏&#xff0c;页脚不是在页面最底部&#xff0c;影响用户视觉。想让页脚始终在页面最底部&#xff0c;我们可能会想到用&#xff1a; 1.min-height来控…

【深度学习】 Python 和 NumPy 系列教程(四):Python容器:2、元组tuple详解(初始化、索引和切片、元组特性、常用操作、拆包、遍历)

目录 一、前言 二、实验环境 三、Python容器&#xff08;Containers&#xff09; 0. 容器介绍 2. 元组&#xff08;Tuple&#xff09; 1. 初始化 a. 使用小括号() b. 省略小括号 c. tuple() 函数 2. 访问元组元素 a. 索引 b. 切片 3. 元组的特性 a. 不可变 b. 包…

CSS 斜条纹进度条

效果&#xff1a; 代码&#xff1a; html: <div class"active-line flex"><!-- lineWidth&#xff1a;灰色背景 --><div class"bg-line"><div v-for"n in 30" class"gray"></div></div><div…

由于电脑出现msvcr110.dll提示错误的解决方法

最近&#xff0c;我在尝试运行一款新的软件时&#xff0c;突然遇到了一个错误提示&#xff0c;提示说缺少msvcr110.dll文件&#xff0c;导致软件无法启动。在使用电脑过程中&#xff0c;我们常常会遇到一些系统文件丢失的问题。其中&#xff0c;msvcr110.dll是Windows操作系统中…

什么是Linux

什么是Linux&#xff1f; 不知道大家是什么时候开始接触Linux&#xff0c;我记得我是大三的时候&#xff0c;那时候通过国嵌、韦东山的教学视频&#xff0c;跟着搭bootloader&#xff0c;修改内核&#xff0c;制作根文件系统&#xff0c;一步步&#xff0c;视频真的很简单&…

C++的继承以及virtual的底层实现

1.继承的基本形式 1.还是举每次讲继承都会举得一个例子&#xff1a;老师和学生都有人类的共同信息----姓名&#xff0c;性别&#xff0c;身份证等等&#xff0c;而学生有学工号&#xff0c;课表。老师有上班时间等等&#xff0c;所以在类中就有了继承这一说&#xff0c;子类继…

UMA 2 - Unity Multipurpose Avatar☀️五.如何使用别人的Recipe和创建自己的服饰Recipe

文章目录 🟥 使用别人的Recipe1️⃣ 导入UMA资源效果展示2️⃣ 更新Library3️⃣ 试一下吧🟧 创建自己的服饰Recipe1️⃣ 创建自己的服饰Recipe2️⃣ 选择应用到的Base Recipe3️⃣ 指定显示名 / 佩戴位置 / 隐藏部位4️⃣ 给该服饰Recipe指定Slot / Overlay🚩 赋予Slot�…

【洛谷 P1105】平台 题解(结构体排序+枚举)

平台 题目描述 空间中有一些平台。给出每个平台的位置&#xff0c;请你计算从每一个平台的边缘落下之后会落到哪一个平台上。注意&#xff0c;如果某两个平台的某个两边缘横坐标相同&#xff0c;物体从上面那个平台落下之后将不会落在下面那个平台上。平台可能会重叠。 如果…