从0开始使用面对对象C语言搭建一个基于OLED的图形显示框架(协议层封装)
目录
协议层设计,以IIC为例子
关于软硬件IIC
设计的一些原则
完成协议层的抽象
刨析我们的原理
如何完成我们的抽象
插入几个C语言小技巧
完成软件IIC通信
开始我们的IIC通信
结束我们的IIC通信
发送一个字节
(重要)完成命令传递和数据传递
最终一击,完成我们的IIC通信
硬件IIC
关于架构设计概述等内容,笔者放到了:从0开始使用面对对象C语言搭建一个基于OLED的图形显示框架-CSDN博客,任何疑问可以到这里看看,这是一个总览。
协议层设计,以IIC为例子
我们先按照最经典的软硬件IIC为例子!笔者大部分接触到的都是4针脚的使用IIC协议通信的OLED片子。所以,笔者打算优先的搭建起来IIC部分的代码。所有完整的代码放到了:MCU_Libs/OLED/library/OLED/Driver at main · Charliechen114514/MCU_Libs (github.com),这个文件夹内部都是协议层的代码。
关于软硬件IIC
软硬件IIC都是完成IIC通信协议的东西。但区别在于,我们到底是使用自己手动模拟的IIC还是使用专门硬件特化的IIC。
关于IIC,看到这里的朋友都很熟悉了:IIC(Inter-Integrated Circuit)是一种常用的串行通信总线协议,用于微控制器与传感器、显示模块等外设之间的通信。而我们的软件IIC就是使用GPIO来模拟IIC时序。
优点:
灵活性强,可以使用任意引脚进行通信,不受特定硬件限制。
适用于不具备硬件IIC模块的微控制器。
可以方便地调节时序,兼容性较好。
缺点:
通信效率较低,占用CPU资源较多。
对实时性要求高的应用不太适合。
稳定性较差,容易受程序时序影响。
硬件IIC则是将IIC应答处理委托给了专门的硬件。
优点
通信速度快,效率高,因为由专用硬件处理时序。
占用CPU资源少,适合需要高实时性的场合。
通信稳定可靠,不易受到程序时序干扰。
缺点:
只能使用特定的IIC引脚,不够灵活。
不同微控制器之间的硬件IIC兼容性可能存在差异。
部分微控制器可能没有硬件IIC模块,导致无法使用硬IIC。
我们大概清楚了。代码上的实现就不会复杂。下面我们就可以开始聊一聊设计了。
设计的一些原则
你认为这样的代码好看吗?
void OLED_ShowImage(int16_t X, int16_t Y, uint8_t Width, uint8_t Height, const uint8_t *Image)
{uint8_t i = 0, j = 0;int16_t Page, Shift;/*将图像所在区域清空*/OLED_ClearArea(X, Y, Width, Height);/*遍历指定图像涉及的相关页*//*(Height - 1) / 8 + 1的目的是Height / 8并向上取整*/for (j = 0; j < (Height - 1) / 8 + 1; j ++){/*遍历指定图像涉及的相关列*/for (i = 0; i < Width; i ++){if (X + i >= 0 && X + i <= 127) //超出屏幕的内容不显示{/*负数坐标在计算页地址和移位时需要加一个偏移*/Page = Y / 8;Shift = Y % 8;if (Y < 0){Page -= 1;Shift += 8;}if (Page + j >= 0 && Page + j <= 7) //超出屏幕的内容不显示{/*显示图像在当前页的内容*/OLED_DisplayBuf[Page + j][X + i] |= Image[j * Width + i] << (Shift);}if (Page + j + 1 >= 0 && Page + j + 1 <= 7) //超出屏幕的内容不显示{ /*显示图像在下一页的内容*/OLED_DisplayBuf[Page + j + 1][X + i] |= Image[j * Width + i] >> (8 - Shift);}}}}
}
好吧,好像大部分人的代码都是这样的。
那这样呢?
void CCGraphicWidget_draw_image(CCDeviceHandler* handler,CCGraphic_Image* image)
{if(!image->sources_register) return;handler->operations.draw_area_device_function(handler, image->point.x, image->point.y,image->image_size.width, image->image_size.height, image->sources_register);
}
你需要在乎image是如何实现的吗?你需要知道如何完成OLED图像的显示是如何做的吗?
你不需要!
这段代码无非就是告诉了你一件事情:提供一个设备句柄作为“告知一个设备,在上面绘制”,告知一个“图像”你需要绘制,直接提供进来,由设备自己约定的方法绘制即可。怎么绘制的?你需要关心吗?你不需要。
直到你需要考虑设备是如何工作的时候,你会看一眼内部的设备
void oled_helper_draw_area(OLED_Handle* handle, uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t* sources)
{// 嘿!超出绘制范围了if(x > POINT_X_MAX) return;if(y > POINT_Y_MAX) return;
// clear the area before being set// 先清理一下这个区域,不要干扰赋值oled_helper_clear_area(handle, x, y , width, height);
for(uint16_t j = 0; j < (height -1) / 8 + 1; j++){for(uint16_t i = 0; i < width; i++){if(x + i > OLED_WIDTH){break;}if(y / 8 + j > OLED_HEIGHT - 1){return;}
OLED_GRAM[y / 8 + j][x + i] |= sources[j * width + i] << (y % 8);
if(y / 8 + j + 1 > OLED_HEIGHT - 1){continue;}
OLED_GRAM[y / 8 + j + 1][x + i] |= sources[j * width + i] >> (8 - y % 8);}}
}
原来如此,是通过写OLED缓存赋值就可以把这个事情给搞明白的——但是当你不关心如何实现的时候,你并不需要付出心血代价把代码看懂然后——哦我的天,这个我压根不关心!当然,代价就是多支付若干次的函数调用(笑)
这就是架构抽象带来的对开发的好处,但是只有这个不足以让我们使用复杂的抽象,我们一定还有别的好处,不是吗?让我们慢慢看吧!
完成协议层的抽象
我已经说过,我们的OLED框架是由协议层(使用何种协议进行通信?),设备层(这个设备可以做什么?),图像层(可以使用设备绘制哪一些图像?),组件层(可以使用图像绘制哪一些组件?),层层递进,保证互相之间互不干扰。我们下面就着重的关心协议层。协议层需要完成的就是将委托的命令(OLED命令)和委托的数据(OLED数据)发送到设备上即可。
刨析我们的原理
协议需要进行初始化,对于硬件,特别是HAL库,只需要咔咔调API就能把事情做完了。但是对于软件IIC,事情就需要麻烦一些,我们需要自己完成IIC时序的通信。
让我们看看IIC的基本原理,基本上看,就是:通知起始通信,通知数据(他是命令还是数据并不关心)和通知停止。
-
起始条件(Start Condition) 主设备将SDA从高电平拉低,同时保持SCL为高电平。当SDA从高到低时形成起始条件(START),通知从设备通信即将开始。
-
地址传输(Address Transmission) 主设备发送一个7位或10位的从设备地址,紧接着是1位的读写方向标志位(R/W位)。
-
R/W为0表示写操作,主设备发送数据
-
R/W为1表示读操作,主设备接收数据 每发送一位数据时,SCL产生一个时钟脉冲(SCL上升沿锁存数据)。
-
-
应答信号(ACK/NACK) 从设备在收到地址和R/W位后,如果能够正常接收数据,会在下一个时钟周期内将SDA拉低产生应答信号ACK(Acknowledge)。如果不响应,则保持SDA为高电平,产生非应答信号NACK(Not Acknowledge)。
-
数据传输(Data Transmission) 主设备根据读写操作继续发送或接收数据,每次传输8位数据。
-
写操作:主设备发送数据,从设备应答ACK
-
读操作:从设备发送数据,主设备应答ACK 每个字节传输完成后,从设备需发送ACK信号以确认接收正常。
-
-
停止条件(Stop Condition) 通信结束时,主设备将SDA从低电平拉高,同时保持SCL为高电平。当SDA从低到高时形成停止条件(STOP),表示通信结束。
说了一大堆,其实就是:
-
起始条件:SDA高变低,SCL保持高
-
数据传输:SDA根据数据位变化,SCL上升沿锁存数据
-
应答信号:从设备将SDA拉低产生ACK,高电平为NACK
-
停止条件:SDA低变高,SCL保持高
所以这样看来,无非就是使用两个引脚,按照上述规则进行高低电平的按照时序的拉高拉低。
话里有话,我的意思就是:软件IIC需要知道你使用哪两个引脚进行通信,需要你来告知如何完成上面的协议约定控制设备。最终我们提供的,是像我们跟人聊天一般的:
嘿!我用软件IIC发送了一个Byte的命令/数据!
这是重点!也是我们协议层抽象的终点:完成委托给我们的数据传输的任务,其他的任何事情都与我们无关,也不在乎这个数据到底是啥!
如何完成我们的抽象
软件IIC需要知道你使用哪两个引脚进行通信,需要你来告知如何完成上面的协议约定控制设备!我再强调的一次!
所以,我们给一个被抽象为软件IIC的实体,提供一个配置,这个配置委婉的提醒了我们的IIC使用哪两个引脚进行通信。最终这个软件IIC实体将会提供可以完成“委托给我们的数据传输的任务”这个任务,需要注意的是,OLED发送数据需要区分他是命令还是数据。这样来看,我们最终就是提供两套方法:
/* command send fucntion */
typedef void(*SendCommand)(void*, uint8_t);
/* data send fucntion */
typedef void(*SendData)(void*, uint8_t*, uint16_t);
/* driver level oled driver's functionalities */
typedef struct __OLED_Operations{SendCommand command_sender;SendData data_sender;
}OLED_Operations;
好像很是罕见!这是一个包装了函数指针的结构体。说的拗口,让我们引入面对对象的设计逻辑来再阐述上面的句子。
这是一个可以保证完成数据传输的OLED方法。调用这个方法,就可以保证我们完成了一个字节传递的命令,或者是完成一系列字节的数据传输
又问我咋做的?先别管,你现在需要知道的是——我一调用!他就能干好这个事情!实现是下面的事情!它隶属于我们的协议实体的结构体,如下所示
/* this will make the gpio used for iic */
typedef struct __OLED_SOFT_IIC_Private_Config
{/* soft gpio handling */ OLED_IICGPIOPack sda;OLED_IICGPIOPack scl;uint32_t accepted_time_delay;uint16_t device_address;OLED_Operations operation;
}OLED_SOFT_IIC_Private_Config;
OLED_IICGPIOPack sda
表示用于IIC的SDA(数据线)引脚配置。
OLED_IICGPIOPack
应该是一个结构体或类型,定义了与GPIO相关的参数,比如引脚号、端口等。该成员用来指定IIC通信中用作SDA的具体引脚。
OLED_IICGPIOPack scl
表示用于IIC的SCL(时钟线)引脚配置。
同样是
OLED_IICGPIOPack
类型,用来配置时钟信号线(SCL)的具体引脚。这个成员和
sda
一起决定了软IIC使用的GPIO引脚。
uint32_t accepted_time_delay
用于设置IIC时序中的时间延迟。
因为软IIC需要软件控制时序,这个值可能表示每个时钟周期的延迟时间(以微秒或纳秒为单位)。
调节这个值可以改变IIC的通信速度,从而适配不同的外设设备。
uint16_t device_address
IIC从设备的地址。
IIC通信中,每个从设备都有唯一的地址,用于主设备区分不同的从设备。
这个值通常是7位或10位地址,需要根据设备规格书配置。
OLED_Operations operation
表示IIC通信的操作类型。
OLED_Operations
定义了常见的IIC操作,比如READ
(读操作)、WRITE
(写操作)等。
初始化的办法,这里就只需要按部就班的赋值。
void oled_bind_softiic_handle(OLED_SOFT_IIC_Private_Config* config,OLED_IICGPIOPack* sda, OLED_IICGPIOPack* scl,uint16_t device_address,uint32_t accepted_time_delay
)
{config->accepted_time_delay = accepted_time_delay;config->device_address = device_address;config->sda = *sda;config->scl = *scl;config->operation.command_sender = ?config->operation.data_sender = ?/* we need to init the gpio type for communications */
}
我们的函数写到下面就顿住了。对啊,咋发送啊?咋操作啊?这才是这个时候我们思考的问题:如何实现软件IIC呢?
我们首先需要完成的是:初始化我们的引脚,让他们可以完成传递电平的任务。
static void __pvt_on_init_iic_gpio(OLED_SOFT_IIC_Private_Config* config)
{/* Enable the GPIOB clock *//* 这就是把时钟打开了而已,是__HAL_RCC_GPIOB_CLK_ENABLE的一个等价替换 *//* #define OLED_ENABLE_GPIO_SCL_CLK() __HAL_RCC_GPIOB_CLK_ENABLE()#define OLED_ENABLE_GPIO_SDA_CLK() __HAL_RCC_GPIOB_CLK_ENABLE()*/// 为什么这样做。。。你换引脚了直接改上面的#define不香吗?集中起来处理一坨屎而不是让你的史满天飞到处改OLED_ENABLE_GPIO_SCL_CLK();OLED_ENABLE_GPIO_SDA_CLK();
GPIO_InitTypeDef GPIO_InitStructure = {0};/* configuration */GPIO_InitStructure.Pin = config->sda.pin | config->scl.pin;GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_OD; // 开漏模式GPIO_InitStructure.Pull = GPIO_NOPULL; // 不上拉也不下拉GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOB, &GPIO_InitStructure);// 这个是一个非常方便的宏,是笔者自己封装的:/*#define SET_SCL(config, pinstate) \do{\HAL_GPIO_WritePin(config->scl.port, config->scl.pin, pinstate);\}while(0)
#define SET_SDA(config, pinstate) \do{\HAL_GPIO_WritePin(config->sda.port, config->sda.pin, pinstate);\}while(0)*/SET_SCL(config, 1);SET_SDA(config, 1);
}
插入几个C语言小技巧
结构体的使用更加像是对一个物理实体的抽象,比如说我们的软件IIC实体由两个GPIO引脚,提供一个OLED地址和延迟时间组成,他可以发送命令和数据
/* this will make the gpio used for iic */ typedef struct __OLED_SOFT_IIC_Private_Config {/* soft gpio handling */ OLED_IICGPIOPack sda;OLED_IICGPIOPack scl;uint32_t accepted_time_delay;uint16_t device_address;OLED_Operations operation; }OLED_SOFT_IIC_Private_Config;
这样的抽象也就呼之欲出了
为什么使用do while呢?答案是:符合大部分人的使用习惯。
避免宏定义中的语法问题 在宏中使用
do { } while(0);
可以确保宏内容被当作一个独立的语句块执行。 例如:#define MY_MACRO(x) do { if (x) func(); } while (0)这样,即使在使用时加上分号也不会引发编译错误:
if (condition) MY_MACRO(1); // 正确处理,避免语法歧义 else other_func();如果直接使用
{}
而不加do-while(0)
,编译器可能会报错或者导致意外的逻辑问题。提升代码的可读性与可维护性
do { } while(0);
语法块明确限制了语句作用范围,避免宏或语句中的变量污染外部作用域,从而增强代码的封装性。兼容语法规则,减少隐患
do { } while(0);
总能确保语法结构合法,即使宏中包含复杂的控制语句也不会影响逻辑。#define SAFE_BLOCK do { statement1; statement2; } while(0)这样即便加了分号也能正常执行,符合常规语句格式。
避免空语句问题 使用
do-while(0)
可以有效避免空语句可能带来的逻辑漏洞。你问我担心开销?拜托!编译器会自动优化!全给你消的一点不剩了,完全就是正常的调用,为啥不用?
为什么在函数的起头带上static?
保证我们的函数在文件作用域是私有的,不会跟其他函数起冲突的。说白了,就是我说的:你需要在干别的事情还要担心一下自己的软件IIC是咋工作的吗?你不需要!担心是一个有病的行为。所以,他保证了接口是简洁的。
完成软件IIC通信
开始我们的IIC通信
软件IIC通信开始,需要先拉高SDA和SCL保证处于高电平,然后拽低SDA和SCL的电平
static void __pvt_on_start_iic(OLED_SOFT_IIC_Private_Config* config)
{SET_SDA(config, 1);SET_SCL(config, 1);SET_SDA(config, 0);SET_SCL(config, 0);
}
结束我们的IIC通信
设置我们的SDA先低,之后让SDA和SCL都处于高电平结束战斗
static void __pvt_on_stop_iic(OLED_SOFT_IIC_Private_Config* handle)
{SET_SDA(handle, 0); SET_SCL(handle, 1); SET_SDA(handle, 1);
}
发送一个字节
发送一个目标字节给我们的设备,你不需要关心这个字节是什么,你不需要现在关心它!
static void __pvt_iic_send_bytes(OLED_SOFT_IIC_Private_Config* handle, uint8_t data)
{ for (uint8_t i = 0; i < 8; i++){ SET_SDA(handle,!!(data & (0x80 >> i)));SET_SCL(handle,1); SET_SCL(handle,0); }SET_SCL(handle,1); SET_SCL(handle,0);
}
!!
的作用是将任意数值转换为布尔值,保证我们发的就是0和1,(0x80 >> i)
萃取了从高向低数的第I位数字发送,也就是往SDA电平上传递我们的data上的第I位。之后拉起释放SCL告知完成传递。
(重要)完成命令传递和数据传递
我们现在开始想起来,我们最终的目的是:完成一个字节命令的传递或者是传递一系列的数据比特。结合手册,我们来看看实际上怎么做。
按照顺序,依次传递
-
开启IIC通信
-
设备的地址
-
数据类型(是命令还是数据)
-
数据本身。
-
结束IIC通信
/*#define DATA_PREFIX (0x40)#define CMD_PREFIX (0x00)
*/
static void __pvt_iic_send_command(void* pvt_handle, uint8_t cmd)
{OLED_SOFT_IIC_Private_Config* config = (OLED_SOFT_IIC_Private_Config*)pvt_handle;
__pvt_on_start_iic(config);__pvt_iic_send_bytes(config, config->device_address);__pvt_iic_send_bytes(config, CMD_PREFIX);__pvt_iic_send_bytes(config, cmd);__pvt_on_stop_iic(config);
}
static void __pvt_iic_send_data(void* pvt_handle, uint8_t* data, uint16_t size)
{OLED_SOFT_IIC_Private_Config* config = (OLED_SOFT_IIC_Private_Config*)pvt_handle;__pvt_on_start_iic(config);__pvt_iic_send_bytes(config, config->device_address);__pvt_iic_send_bytes(config, DATA_PREFIX);for(uint16_t i = 0; i < size; i++)__pvt_iic_send_bytes(config, data[i]);__pvt_on_stop_iic(config);
}
最终一击,完成我们的IIC通信
/*config: Pointer to an OLED_SOFT_IIC_Private_Config structure that contains the configuration settings for the software I2C communication,such as timing, pins, and other relevant parameters.config should be blank or uninitialized.sda: Pointer to an OLED_GPIOPack structure that represents the GPIO configuration for the Serial Data (SDA) line of the software I2C interface.
scl: Pointer to an OLED_GPIOPack structure that represents the GPIO configuration for the Serial Clock (SCL) line of the software I2C interface.
device_address: The 7-bit I2C address of the device that the software I2C communication is targeting, typically used to identify the device on the I2C bus.
accepted_time_delay: A timeout value in milliseconds, specifying the maximum allowed delay for the software I2C communication process.
*/
void oled_bind_softiic_handle(OLED_SOFT_IIC_Private_Config* config,OLED_IICGPIOPack* sda, OLED_IICGPIOPack* scl,uint16_t device_address,uint32_t accepted_time_delay
){config->accepted_time_delay = accepted_time_delay;config->device_address = device_address;config->sda = *sda;config->scl = *scl;config->operation.command_sender = __pvt_iic_send_command;config->operation.data_sender = __pvt_iic_send_data;__pvt_on_init_iic_gpio(config);
}
我们把方法和数据都传递给这个软件iic实体,现在,他就能完成一次软件IIC通信了。给各位看看如何使用
config->operation.command_sender(config, oled_spi_init_command[i]);
可以看到,我们的结构体函数指针就是这样使用的。
硬件IIC
硬件IIC事情就会简单特别多,原因在于,我们有专门的硬件帮助我们完成IIC通信
#ifndef OLED_HARD_IIC_H
#define OLED_HARD_IIC_H
#include "OLED/Driver/oled_config.h"
#include "stm32f1xx_hal.h"
#include "stm32f1xx_hal_i2c.h"
typedef struct __OLED_HARD_IIC_Private_Config{I2C_HandleTypeDef* pvt_handle;uint32_t accepted_time_delay;uint16_t device_address;OLED_Operations operation;
}OLED_HARD_IIC_Private_Config;
/* handle binder, bind the raw data to the oled driverblank_config: Pointer to an OLED_HARD_IIC_Private_Config structure that holds the configuration settings for the I2C communication, typically initializing the OLED hardware interface.raw_handle: Pointer to an I2C_HandleTypeDef structure, representing the raw I2C peripheral handle used to configure and manage I2C communication for the device.
device_address: The 7-bit I2C address of the device to which the communication is being established, typically used for identifying the target device on the I2C bus.
accepted_time_delay: A timeout value in milliseconds that specifies the maximum allowable delay for the I2C communication process.
*/
void bind_hardiic_handle(OLED_HARD_IIC_Private_Config* blank_config,I2C_HandleTypeDef* raw_handle,uint16_t device_address,uint32_t accepted_time_delay
);
#endif
现在我们可以不需要两个引脚了,只需要客户端提供一个硬件IIC句柄就好。
#include "OLED/Driver/hard_iic/hard_iic.h"
static void __pvt_hardiic_send_data(void* pvt_handle, uint8_t* data, uint16_t size)
{OLED_HARD_IIC_Private_Config* config = (OLED_HARD_IIC_Private_Config*)pvt_handle;for (uint8_t i = 0; i < size; i ++){HAL_I2C_Mem_Write(config->pvt_handle,config->device_address,DATA_PREFIX,I2C_MEMADD_SIZE_8BIT,&data[i], 1, config->accepted_time_delay); //依次发送Data的每一个数据}
}
static void __pvt_hardiic_send_command(void* pvt_handle, uint8_t cmd)
{OLED_HARD_IIC_Private_Config* config = (OLED_HARD_IIC_Private_Config*)pvt_handle;HAL_I2C_Mem_Write(config->pvt_handle, config->device_address,CMD_PREFIX,I2C_MEMADD_SIZE_8BIT,&cmd,1,config->accepted_time_delay);
}
void bind_hardiic_handle(OLED_HARD_IIC_Private_Config* blank_config,I2C_HandleTypeDef* raw_handle,uint16_t device_address,uint32_t accepted_time_delay
)
{blank_config->accepted_time_delay = accepted_time_delay;blank_config->device_address = device_address;blank_config->pvt_handle = raw_handle;blank_config->operation.command_sender = __pvt_hardiic_send_command;blank_config->operation.data_sender = __pvt_hardiic_send_data;
}
HAL_I2C_Mem_Write函数直接完成了我们的委托,注意的是,我们每一次的调用这个函数,内部都是重新开始一次IIC通信的,所以,发送数据的时候,只能一个字节一个字节的发送(因为每一次都要指定这个是数据还是命令)。这一点,SPI协议的OLED就要好很多!(内部的引脚高低就直接决定了整个是命令还是数据,不需要通过解析传递的数据本身!)
这样,一个典型的基于软硬件IIC的协议层抽象就完成了。如果你着急测试的话,可以自己替换原本OLED的操作。
我们下一篇,就是开始抽象OLED的设备层。
目录导览
总览
协议层封装
OLED设备封装
绘图设备抽象
基础图形库封装
基础组件实现
动态菜单组件实现
相关文章:

从0开始使用面对对象C语言搭建一个基于OLED的图形显示框架(协议层封装)
目录 协议层设计,以IIC为例子 关于软硬件IIC 设计的一些原则 完成协议层的抽象 刨析我们的原理 如何完成我们的抽象 插入几个C语言小技巧 完成软件IIC通信 开始我们的IIC通信 结束我们的IIC通信 发送一个字节 (重要)完成命令传递和…...

Mac M1 源码安装FFmpeg,开启enable-gpl 和 lib x264
1、第一步:下载并安装minicoda curl -O https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-arm64.shsh Miniconda3-latest-MacOSX-arm64.sh2、第二步:安装必要的依赖 conda install -c conda-forge gcc make nasm yasm3、第三步ÿ…...

【Quest开发】手柄单手抓握和双手抓握物体切换
V72更新以后非常智能哈,配置物体简单多了。 选择需要被抓取的物体鼠标右键单击它,点Add Grab Interaction,按它要求的配置就行 配好以后长这样 把这个选项取消勾选就能切换成双手抓一个物体了,不需要像以前一样用各种grabTransfo…...

DB-GPT试用
继续上一篇 DB-GPT的安装 https://blog.csdn.net/berryreload/article/details/142845190 访问http://xxx:5670 访问这里 创建数据库连接 http://10.168.1.208:5670/construct/database 访问这里,点击刷新 http://10.168.1.208:5670/construct/app 刷新后才能出…...

《Ollama Python 库》
Ollama Python 库 Ollama Python 库提供了将 Python 3.8 项目与 Ollama 集成的最简单方法。 先决条件 应该安装并运行 Ollama拉取一个模型以与库一起使用:例如ollama pull <model>ollama pull llama3.2 有关可用模型的更多信息,请参阅 Ollama.com。…...

Java的Integer缓存池
Java的Integer缓冲池? Integer 缓存池主要为了提升性能和节省内存。根据实践发现大部分的数据操作都集中在值比较小的范围,因此缓存这些对象可以减少内存分配和垃圾回收的负担,提升性能。 在-128到 127范围内的 Integer 对象会被缓存和复用…...

Ubuntu16.04编译安装Cartographer 1.0版本
说明 官方文档 由于Ubuntu16.04已经是很老的系统,如果直接按照Cartographer官方安装文档安装会出现代码编译失败的问题,本文给出了解决这些问题的办法。正常情况下执行本文给出的安装方法即可成功安装。 依赖安装 # 这里和官方一致 # Install the req…...

Qt调用FFmpeg库实时播放UDP组播视频流
基于以下参考链接,通过改进实现实时播放UDP组播视频流 https://blog.csdn.net/u012532263/article/details/102736700 源码在windows(qt-opensource-windows-x86-5.12.9.exe)、ubuntu20.04.6(x64)(qt-opensource-linux-x64-5.12.12.run)、以…...

C# 类与对象详解
.NET学习资料 .NET学习资料 .NET学习资料 在 C# 编程中,类与对象是面向对象编程的核心概念。它们让开发者能够将数据和操作数据的方法封装在一起,从而构建出模块化、可维护且易于扩展的程序。下面将详细介绍 C# 中类与对象的相关知识。 一、类的定义 …...

【Elasticsearch 基础入门】Centos7下Elasticsearch 7.x安装与配置(单机)
Elasticsearch系列文章目录 【Elasticsearch 基础入门】一文带你了解Elasticsearch!!!【Elasticsearch 基础入门】Centos7下Elasticsearch 7.x安装与配置(单机) 目录 Elasticsearch系列文章目录前言单机模式1. 安装 J…...

大模型本地部署使用方法(Ollama脚手架工具、FisherAI浏览器大模型插件、AnythingLLM大模型集成应用平台)
一、Ollama (一)Ollama简介 Ollama是一个专为在本地环境中运行和定制大型语言模型而设计的工具。它提供简单高效的接口,用于创建、运行和管理这些模型,方便用户直接使用,也方便用作后台服务支撑其它应用程序。熟悉网…...

【华为OD-E卷 - 报数游戏 100分(python、java、c++、js、c)】
【华为OD-E卷 - 报数游戏 100分(python、java、c、js、c)】 题目 100个人围成一圈,每个人有一个编码,编号从1开始到100。 他们从1开始依次报数,报到为M的人自动退出圈圈,然后下一个人接着从1开始报数&…...

深入理解Spring框架:从基础到实践
前言 Spring框架是一个开源的企业级应用开发框架,它为Java开发者提供了灵活的架构支持,特别是在依赖注入(IOC)和面向切面编程(AOP)方面。本文将通过具体的示例,带你从Spring框架的概述、IOC容器…...

一觉醒来全球编码能力下降100000倍,新手小白的我决定科普C语言——函数
1. 函数的概念 数学中我们其实就⻅过函数的概念,⽐如:⼀次函数 y kx b ,k和b都是常数,给⼀个任意的 x,就得到⼀个y值。其实在C语⾔也引⼊函数(function)的概念,有些翻译为…...

CentOS 上安装 Go (Golang)
1. 检查系统环境 确保系统为 CentOS 7 或 CentOS 8,或者其他兼容的 Linux 发行版。 cat /etc/os-release2. 安装依赖 安装一些必要的工具: sudo yum update -y sudo yum install -y wget tar3. 下载 Go 从 Go 官方下载页面获取适用于 Linux 的最新版…...

软件模拟I2C案例前提须知——EEPROM芯片之M24C02
引言 了解了I2C的基础知识后,我们将来使用一个I2C案例实践来深入理解I2C通讯,即软件模拟I2C。顾名思义,就是利用软件方式通过模拟I2C协议要求的时序或者说一些相关规定来实现一个I2C通讯协议,然后利用模拟出的I2C协议来实现两个设…...

GIS教程:全国数码商城系统
文章目录 注册高德地图API普通网页中测试地图加载地图添加标记地图配置点标记 Marker添加弹框创建vue项目并添加高德地图创建项目加载高德地图项目首页布局封装axios和配置代理服务器获取城市热门信息获取城市区县信息获取区县商城信息获取指定城市区县的经纬度坐标将地图缩放到…...

BroadCom-RDMA博通网卡如何进行驱动安装和设置使得对应网口具有RDMA功能以适配RDMA相机
BroadCom-RDMA博通网卡如何进行驱动安装和设置使得对应网口具有RDMA功能以适配RDMA相机 BroadCom-RDMA 博通网卡Baumer-RDMA 万兆网相机Baumer工业相机RDMA功能的技术背景BroadCom-RDMA博通网卡如何进行驱动安装和设置具有RDMA功能一、安装ZVA-BroadCom-RDMA网卡二、设备管理器…...

分布式微服务系统架构第90集:现代化金融核心系统
#1.1 深化数字化转型,核心面临新挑战 1、架构侧:无法敏捷协同数字金融经营模式转型。 2、需求侧:业务需求传导低效始终困扰金融机构。 3、开发侧:创新产品上市速度低于期望。 4、运维侧:传统面向资源型监控体系难以支撑…...

进阶数据结构——双向循环链表
目录 前言一、定义与结构二、特点与优势三、基本操作四、应用场景五、实现复杂度六、动态图解七、代码模版(c)八、经典例题九、总结结语 前言 这一期我们学习双向循环链表。双向循环链表不同于单链表,双向循环链表是一种特殊的数据结构&…...

记录一次,PyQT的报错,多线程Udp失效,使用工具如netstat来检查端口使用情况。
1.问题 报错Exception in thread Thread-1: Traceback (most recent call last): File "threading.py", line 932, in _bootstrap_inner File "threading.py", line 870, in run File "main.py", line 456, in udp_recv IndexError: list…...

安装anaconda3 后 电脑如何单独运行python,python还需要独立安装吗?
安装anaconda3 后 电脑如何单独运行python,python还需要独立安装吗? 电脑第一此安装anaconda用于jupyter notebook使用。 但是在运行cmd的时候,输入python --version 显示未安装或跳转商店提示安装。 明明我可以运行python但是为什么cmd却说我没安装呢…...

电子电气架构 --- 汽车电子拓扑架构的演进过程
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 简单,单纯,喜欢独处,独来独往,不易合同频过着接地气的生活…...

ASP.NET Core 中使用依赖注入 (DI) 容器获取并执行自定义服务
目录 一、ASP.NET Core 中使用依赖注入 (DI) 容器获取并执行自定义服务 1. app.Services 2. GetRequiredService() 3. Init() 二、应用场景 三、依赖注入使用拓展 1、使用场景 2、使用步骤 1. 定义服务接口和实现类 2. 注册服务到依赖注入容器 3. 使用依赖注入获取并…...

leetcode——验证二叉搜索树(java)
给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下: 节点的左子树只包含小于当前节点的数。 节点的右子树只包含 大于 当前节点的数。 所有左子树和右子树自身必须也是二叉搜索树。 示例 1: 输入…...

搜索引擎快速收录:关键词布局的艺术
本文来自:百万收录网 原文链接:https://www.baiwanshoulu.com/21.html 搜索引擎快速收录中的关键词布局,是一项既精细又富有策略性的工作。以下是对关键词布局艺术的详细阐述: 一、关键词布局的重要性 关键词布局影响着后期页面…...

VLN视觉语言导航基础
0 概述 视觉语言导航模型旨在构建导航决策模型 π π π,在 t t t时刻,模型能够根据指令 W W W、历史轨迹 τ { V 1 , V 2 , . . . , V t − 1 } \tau\{V_1,V_2,...,V_{t-1}\} τ{V1,V2,...,Vt−1}和当前观察 V t { P t , R t , N ( V t ) } V_…...

4 Hadoop 面试真题
4 Hadoop 面试真题 1. Apache Hadoop 3.0.02. HDFS 3.x 数据存储新特性-纠删码Hadoop面试真题 1. Apache Hadoop 3.0.0 Apache Hadoop 3.0.0在以前的主要发行版本(hadoop-2.x)上进行了许多重大改进。 最低要求的Java版本从Java 7增加到Java 8 现在&…...

java练习(2)
回文数(题目来自力扣) 给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。 回文数 是指正序(从左向右)和倒序(从右向左)读都是一样的整…...

vscode命令面板输入 CMake:build不执行提示输入
CMake:build或rebuild不编译了,弹出:> [Add a new preset] , 提示输入发现settings.jsons设置有问题 { "workbench.colorTheme": "Default Light", "cmake.pinnedCommands": [ "workbench.action.tasks.configu…...