Linux驱动应用编程(四)IIC(获取BMP180温度/气压数据)

news2024/11/15 11:16:51

本文目录

  • 一、基础
    • 1. 查看开发板手册,获取可用IIC总线
    • 2. 挂载从机,查看从机地址。
    • 3. 查看BMP180手册,使用命令读/写某寄存器值。
    • 4. 查看BMP180手册通信流程。
  • 二、IIC常用API
    • 1. iic数据包/报
    • 2. ioctl函数
  • 三、数据包如何被处理
  • 四、代码编写流程
    • 1. IIC读取数据
    • 2. IIC写入数据
    • 3. 读取校验参数
    • 4. 读取未校准的温度值
    • 5. 读取未校准的气压值
    • 6. 将未校准的测量值转为真实值
  • 五、完整代码

  
   在 Linux ARM 平台上使用 I2C 时,不需要手动编写 I2C 时序是因为 Linux 内核和硬件抽象层已经处理了这些复杂的细节,提供了高层次的接口供开发者使用。
  无论是哪个IIC从机设备,我们只需要实现 IIC读数据和IIC写数据即可。然后根据不同设备的手册规则来向寄存器写或者读数据,从而实现某些特定的功能。

一、基础

1. 查看开发板手册,获取可用IIC总线

在这里插入图片描述

   香橙派OrangepiAipor引脚只有两个IIC可以使用,分别是IIC6和IIC7。分别对应如下两个设备节点。
在这里插入图片描述
  

2. 挂载从机,查看从机地址。

   我们将从机设备随便连接到IIC的其中一个上面,这里我们使用BMP180作为从机设备连接香橙派的IIC7总线,对应的设备节点为i2c-7。我们可以使用命令来查看IIC总线7上挂载的设备的地址。命令:i2cdetect -y -r 7。这样我们就获得了从机的地址(当然可以查看BMP180手册获得)。
在这里插入图片描述
  

3. 查看BMP180手册,使用命令读/写某寄存器值。

BMP180手册中寄存器地址分布如下:在这里插入图片描述

●读取 i2c-7 总线上,从机设备地址为0x77 ,寄存器地址为 0xF7上的值。

i2cget -y 7 0x77 0xF7

在这里插入图片描述
在这里插入图片描述

●写入i2c-7 总线上,从机设备地址为0x77 ,寄存器地址为 0xF4,值为0X2E。

i2cset 7 0x77 0xF4 0x2E

  

4. 查看BMP180手册通信流程。

   我们可以发现,通信大致流程就是往寄存器里写值,然后读取寄存器的值。将获取的值通过公式转换为真实的温度/压力值。

在这里插入图片描述

二、IIC常用API

1. iic数据包/报

头文件:#include <linux/i2c.h>

●数据包:
   在 Linux 内核中,struct i2c_msg 结构体用于描述 I2C 消息,这是在 I2C 总线上传输的数据块。它被用作 ioctl 调用的一部分,通过 I2C_RDWR 命令进行 I2C 读写操作。

struct i2c_msg {
    __u16 addr;   /* 从设备地址 */
    __u16 flags;  /* 消息标志,写:0, 读:1 */
    __u8 *buf;    /* 数据缓冲区。对于写操作,这里存储的是要发送的数据;对于读操作,这里存储的是接收到的数据。 */
    __u16 len;    /* 数据缓冲区长度 */
};

●数据报:包含多个数据包。

struct i2c_rdwr_ioctl_data {
    struct i2c_msg *msgs;  /* 指向 I2C 消息数组的指针 */
    __u32 nmsgs;           /* 消息的数量 */
};

2. ioctl函数

   是一个系统调用,专门用来让程序与设备进行通信。它有点像是一个“万能”函数,通过它可以向设备发送各种控制命令或者配置设备的某些参数。具体使用看下面的内容理解。相当于将消息报传给设备。

int ioctl(int fd, unsigned long request, ...);
//int fd :设备的文件描述符。
//unsigned long request:请求,例如可读可写等。
//...(可变参数):根据 request 的不同,ioctl 可能需要一个或多个额外的参数。这些参数的类型和数量取决于具体的控制命令。

三、数据包如何被处理

当数据包传输到设备时,设备的硬件或驱动程序会根据I2C协议进行解析和处理。

  1. 设备地址识别: 首先,设备会检查数据包中的地址字段,以确定它是否是被设备所识别的地址。如果是,则设备将继续处理数据包;如果不是,则设备会忽略该数据包。

  2. 数据包解析: 设备会根据数据包的格式进行解析。对于写数据包,设备会读取数据包中的数据内容,并根据寄存器地址将这些数据写入到对应的寄存器中。对于读数据包,设备会从指定的寄存器中读取数据,并将这些数据放置在响应数据包的缓冲区中。

  3. 数据处理: 一旦数据被写入或读取,设备可能会执行相应的操作。这可能包括修改设备内部的状态、更新设备的寄存器值、执行特定的功能等。

四、代码编写流程

   在IIC通信中,我们最主要的就是写出IIC读和IIC写的函数。无论是读还是写,我们在与设备通信时传输的第一个字节必须是要操作的寄存器的地址,因为数据的读写通常是通过向设备发送特定的寄存器地址来触发的。

1. IIC读取数据

   传入打开的IIC设备文件描述符、要读取的从机设备地址、 要读取的寄存器地址、将数据读取到哪、读多少。

/*
uint8_t slave_addr :从机地址
uint8_t reg_addr :要读取的寄存器
uint8_t* buffer:读取的数据存在哪
int length:读取的长度
*/
int iic_read(int fd, uint8_t slave_addr, uint8_t reg_addr, uint8_t* buffer, int length)
{
    struct i2c_msg msgs[2]; //读数据包以及写数据包
    struct i2c_rdwr_ioctl_data pack; //数据报
    int ret;

//第一个数据包:用于向目标设备发送要操作的寄存器地址。
    msgs[0].addr  = slave_addr;
    msgs[0].flags = 0;     // 写方向
    msgs[0].buf   = &reg_addr;  
    msgs[0].len   = sizeof(reg_addr);
    
//第二个数据包:表明是读取寄存器的内容。
    msgs[1].addr  = slave_addr;
    msgs[1].flags = 1;    // 读方向
    msgs[1].buf   = buffer;  //从寄存器读取的内容存到buffer中。
    msgs[1].len   = length; //读取的长度

    pack.msgs = msgs;
    pack.nmsgs = 2;//

//ioctl函数将消息报 pack 发送给指定的设备。
    ret = ioctl(fd, I2C_RDWR, &pack);
    if (ret < 0) {
        perror("ioctl I2C_RDWR failed");
        return -1;
    }

    return 0;
}

2. IIC写入数据

   传入打开的IIC设备文件描述符、要写入的从机设备地址、 要写入的寄存器地址、写什么数据、写多少。

   注意:这里我们要将寄存器地址和写入的数据放到一个数据包中传输。即第一个字节为寄存器地址,后面为传输的值。具体为什么使用一个数据包而不是两个,原因不太清楚,我使用两个数据包传输时,有问题。

int iic_write(int fd, uint8_t slave_addr, uint8_t reg_addr, uint8_t* data, int length)
{
    struct i2c_rdwr_ioctl_data pack;
    struct i2c_msg msg;
    uint8_t buffer[length + 1]; // 为了包含寄存器地址,需要额外的空间
    int ret;

    buffer[0] = reg_addr; // 将寄存器地址作为第一个字节

    // 将要写入的数据拷贝到缓冲区中
    memcpy(buffer + 1, data, length);

    msg.addr   = slave_addr;
    msg.flags  = 0; // 写方向
    msg.len    = length + 1; // 包含了寄存器地址
    msg.buf    = buffer;

    pack.msgs  = &msg;
    pack.nmsgs = 1;

    ret = ioctl(fd, I2C_RDWR, &pack);
    if (ret < 0) {
        perror("ioctl I2C_RDWR failed");
        return -1;
    }

    return 0;
}

3. 读取校验参数

在这里插入图片描述

   由于在Linux-arm下是大端序,则先读取高位,再读低位。因为每个数据占两个字节,所以要将这两个字节的数据进行位移操作来合并为一个数据。

#define Slave_Addr  0x77

void read_calibration_data(int fd, int16_t* AC1, int16_t* AC2, int16_t* AC3, 
							uint16_t* AC4, uint16_t* AC5, uint16_t* AC6, int16_t* B1, 
							int16_t* B2, int16_t* MB, int16_t* MC, int16_t* MD) 
{
    uint8_t buffer[22];  //每个数据占2个字节,共有11个数据。
    iic_read(fd, Slave_Addr , 0xAA,  buffer, 22);
    *AC1 = (buffer[0] << 8) | buffer[1];
    *AC2 = (buffer[2] << 8) | buffer[3];
    *AC3 = (buffer[4] << 8) | buffer[5];
    *AC4 = (buffer[6] << 8) | buffer[7];
    *AC5 = (buffer[8] << 8) | buffer[9];
    *AC6 = (buffer[10] << 8) | buffer[11];
    *B1 = (buffer[12] << 8) | buffer[13];
    *B2 = (buffer[14] << 8) | buffer[15];
    *MB = (buffer[16] << 8) | buffer[17];
    *MC = (buffer[18] << 8) | buffer[19];
    *MD = (buffer[20] << 8) | buffer[21];
}


//下面内容只是为了演示如何使用而已。
int main()
{
	int16_t AC1, AC2, AC3, B1, B2, MB, MC, MD;
    uint16_t AC4, AC5, AC6;
   // 读取校准数据
    read_calibration_data(fd, &AC1, &AC2, &AC3, &AC4, &AC5, &AC6, &B1, &B2, &MB, &MC, &MD);
}

4. 读取未校准的温度值

在这里插入图片描述

由于在Linux-arm下是大端序,则先读取高位,再读低位。

#define  Slave_Addr  0x77
#define  Data_Out_MSB 0xF6
#define  Data_Out_LSB 0xF7

#define  Tempture_Pressure_reg  0xF4
// 启动温度测量

int main()
{
	uint8_t send_data[1];
	uint8_t receive_data[2];
	int32_t raw_temp;  //未校准的温度数据。
	send_data[0] = 0x2e;  //要写入的数据

    if (iic_write(fd, Slave_Addr, Tempture_Pressure_reg , send_data,1) <0) {  //开始测量温度
        perror("iic_write error");
        close(fd);
        return -1;
    }
    usleep(4500); // 等待测量完成

    // 读取未校准的温度数据
    if (iic_read(fd, Slave_Addr, Data_Out_MSB , receive_data, 2) < 0) {
        perror("iic_read error");
        close(fd);
        return -1;
    }
    raw_temp= receive_data[0]<<8|receive_data[1];  //未校准的温度值
}

5. 读取未校准的气压值

在这里插入图片描述
由于在Linux-arm下是大端序,则先读取高位,再读低位。

#define  Slave_Addr  0x77
#define  Data_Out_MSB 0xF6
#define  Data_Out_LSB 0xF7
#define  Data_Out_XLSB 0xF8
#define Tempture_Pressure_reg 0xF4
// 启动温度测量

/*
通常,"oss" 的取值范围在 0 到 3 之间,代表不同的过采样率。具体取值对应的过采样率取决于传感器型号和制造商的实现。在 BMP180 中,oss 的取值对应着以下过采样率:
  	oss = 0: 单次采样
  	oss = 1: 2 倍过采样
  	oss = 2: 4 倍过采样
  	oss = 3: 8 倍过采样
本文采用单次采样即可。
*/
int main()
{
  uint8_t send_data[1];
  uint8_t receive_data[3];
  int32_t raw_pressure;  //未校准的压力数据。
  send_data[0] = 0x34;  //要写入的数据

  if (iic_write(fd, Slave_Addr, Tempture_Pressure_reg , send_data,1) <0) {  //开始测量压力
      perror("iic_write error");
      close(fd);
      return -1;
  }
  usleep(4500); // 等待测量完成

  // 读取未校准的压力数据
  if (iic_read(fd, Slave_Addr, Data_Out_MSB , receive_data, 3) < 0) {
      perror("iic_read error");
      close(fd);
      return -1;
  }
  raw_pressure =(receive_data[0]<<16|receive_data[1]<<8|receive_data[0]) >>8;  //未校准的压力值
}

6. 将未校准的测量值转为真实值

在这里插入图片描述

注意:代码中的右移多少位就相当于乘了2的几次方。左移相当于除。

//这里的参数很多都是校准参数。
void calculate_true_values(int32_t raw_temp, int32_t raw_pressure, int32_t* true_temp, int32_t* true_pressure, int16_t AC1, int16_t AC2, int16_t AC3, uint16_t AC4, uint16_t AC5, uint16_t AC6, int16_t B1, int16_t B2, int16_t MB, int16_t MC, int16_t MD) {
    int32_t X1, X2, X3, B3, B5, B6, B7, p;
    uint32_t B4;

    // 温度计算,
    X1 = (raw_temp - AC6) * AC5 >> 15;
    X2 = (MC << 11) / (X1 + MD);
    B5 = X1 + X2;
    *true_temp = (B5 + 8) >> 4;

    // 压力计算
    B6 = B5 - 4000;
    X1 = (B2 * (B6 * B6 >> 12)) >> 11;
    X2 = AC2 * B6 >> 11;
    X3 = X1 + X2;
    B3 = (((AC1 * 4 + X3) << 1) + 2) >> 2;
    X1 = AC3 * B6 >> 13;
    X2 = (B1 * (B6 * B6 >> 12)) >> 16;
    X3 = ((X1 + X2) + 2) >> 2;
    B4 = AC4 * (uint32_t)(X3 + 32768) >> 15;
    B7 = ((uint32_t)raw_pressure - B3) * (50000 >> 1);
    if (B7 < 0x80000000) {
        p = (B7 * 2) / B4;
    } else {
        p = (B7 / B4) * 2;
    }
    X1 = (p >> 8) * (p >> 8);
    X1 = (X1 * 3038) >> 16;
    X2 = (-7357 * p) >> 16;
    *true_pressure = p + ((X1 + X2 + 3791) >> 4);
}


五、完整代码

iic.c

#include <stdint.h>
#include <string.h>
#include "iic.h"

int iic_init(const char *device)
{            
   return open(device,O_RDWR);  //可读可写   
}

int iic_close(int fd)
{
   return close(fd);
}

/*
uint8_t slave_addr :从机地址
uint8_t reg_addr :要读取的寄存器
uint8_t* buffer:读取的数据存在哪
int length:读取的长度
*/
int iic_read(int fd, uint8_t slave_addr, uint8_t reg_addr, uint8_t* buffer, int length)
{
    struct i2c_msg msgs[2]; //读数据包以及写数据包
    struct i2c_rdwr_ioctl_data pack; //数据报
    int ret;

//第一个数据包:用于向目标设备发送要操作的寄存器地址。
    msgs[0].addr  = slave_addr;
    msgs[0].flags = 0;     // 写方向
    msgs[0].buf   = &reg_addr;  
    msgs[0].len   = sizeof(reg_addr);
    
//第二个数据包:表明是读取寄存器的内容。
    msgs[1].addr  = slave_addr;
    msgs[1].flags = 1;    // 读方向
    msgs[1].buf   = buffer;  //从寄存器读取的内容存到buffer中。
    msgs[1].len   = length; //读取的长度

    pack.msgs = msgs;
    pack.nmsgs = 2;//

//ioctl函数将消息报 pack 发送给指定的设备。
    ret = ioctl(fd, I2C_RDWR, &pack);
    if (ret < 0) {
        perror("ioctl I2C_RDWR failed");
        return -1;
    }

    return 0;
}

//IIC写数据
int iic_write(int fd, uint8_t slave_addr, uint8_t reg_addr, uint8_t* data, int length)
{
    struct i2c_rdwr_ioctl_data pack;
    struct i2c_msg msg;
    uint8_t buffer[length + 1]; // 为了包含寄存器地址,需要额外的空间
    int ret;

    buffer[0] = reg_addr; // 将寄存器地址作为第一个字节

    // 将要写入的数据拷贝到缓冲区中
    memcpy(buffer + 1, data, length);

    msg.addr   = slave_addr;
    msg.flags  = 0; // 写方向
    msg.len    = length + 1; // 包含了寄存器地址
    msg.buf    = buffer;

    pack.msgs  = &msg;
    pack.nmsgs = 1;

    ret = ioctl(fd, I2C_RDWR, &pack);
    if (ret < 0) {
        perror("ioctl I2C_RDWR failed");
        return -1;
    }

    return 0;
}

//读取校准参数
void read_calibration_data(int fd, int16_t* AC1, int16_t* AC2, int16_t* AC3, uint16_t* AC4, uint16_t* AC5, uint16_t* AC6, int16_t* B1, int16_t* B2, int16_t* MB, int16_t* MC, int16_t* MD) {
    uint8_t buffer[22];
    iic_read(fd, Slave_Addr, 0xAA, buffer, 22);
    *AC1 = (buffer[0] << 8) | buffer[1];
    *AC2 = (buffer[2] << 8) | buffer[3];
    *AC3 = (buffer[4] << 8) | buffer[5];
    *AC4 = (buffer[6] << 8) | buffer[7];
    *AC5 = (buffer[8] << 8) | buffer[9];
    *AC6 = (buffer[10] << 8) | buffer[11];
    *B1 = (buffer[12] << 8) | buffer[13];
    *B2 = (buffer[14] << 8) | buffer[15];
    *MB = (buffer[16] << 8) | buffer[17];
    *MC = (buffer[18] << 8) | buffer[19];
    *MD = (buffer[20] << 8) | buffer[21];
}

//计算真实值
void calculate_true_values(int32_t raw_temp, int32_t raw_pressure, int32_t* true_temp, int32_t* true_pressure, int16_t AC1, int16_t AC2, int16_t AC3, uint16_t AC4, uint16_t AC5, uint16_t AC6, int16_t B1, int16_t B2, int16_t MB, int16_t MC, int16_t MD) {
    int32_t X1, X2, X3, B3, B5, B6, B7, p;
    uint32_t B4;

    // 温度计算
    X1 = (raw_temp - AC6) * AC5 >> 15;
    X2 = (MC << 11) / (X1 + MD);
    B5 = X1 + X2;
    *true_temp = (B5 + 8) >> 4;

    // 压力计算
    B6 = B5 - 4000;
    X1 = (B2 * (B6 * B6 >> 12)) >> 11;
    X2 = AC2 * B6 >> 11;
    X3 = X1 + X2;
    B3 = (((AC1 * 4 + X3) << 1) + 2) >> 2;
    X1 = AC3 * B6 >> 13;
    X2 = (B1 * (B6 * B6 >> 12)) >> 16;
    X3 = ((X1 + X2) + 2) >> 2;
    B4 = AC4 * (uint32_t)(X3 + 32768) >> 15;
    B7 = ((uint32_t)raw_pressure - B3) * (50000 >> 1);
    if (B7 < 0x80000000) {
        p = (B7 * 2) / B4;
    } else {
        p = (B7 / B4) * 2;
    }
    X1 = (p >> 8) * (p >> 8);
    X1 = (X1 * 3038) >> 16;
    X2 = (-7357 * p) >> 16;
    *true_pressure = p + ((X1 + X2 + 3791) >> 4);
}

iic.h

#ifndef __IIC_H
#define __IIC_H

#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>

#define  Slave_Addr 0x77
#define  Data_Out_MSB 0xF6
#define  Data_Out_LSB 0xF7
#define  Data_Out_XLSB 0xF8
#define  Tempture_Pressure_reg 0xF4


int iic_init(const char *device);
int iic_close(int fd);
int iic_read(int fd, uint8_t slave_addr, uint8_t reg_addr, uint8_t* buffer, int length);
int iic_write(int fd, uint8_t slave_addr, uint8_t reg_addr, uint8_t* data, int length);
void read_calibration_data(int fd, int16_t* AC1, int16_t* AC2, int16_t* AC3, uint16_t* AC4, uint16_t* AC5, uint16_t* AC6, int16_t* B1, int16_t* B2, int16_t* MB, int16_t* MC, int16_t* MD);
void calculate_true_values(int32_t raw_temp, int32_t raw_pressure, int32_t* true_temp, int32_t* true_pressure, int16_t AC1, int16_t AC2, int16_t AC3, uint16_t AC4, uint16_t AC5, uint16_t AC6, int16_t B1, int16_t B2, int16_t MB, int16_t MC, int16_t MD) ;
#endif

main.c

#include <stdio.h>
#include <fcntl.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdint.h>
#include <linux/i2c.h>
#include <string.h>
#include "iic.h"



int main() {
    int fd;
    int32_t raw_temp, raw_pressure, true_temp, true_pressure;
    int16_t AC1, AC2, AC3, B1, B2, MB, MC, MD;
    uint16_t AC4, AC5, AC6;
	uint8_t send_data[1];
	uint8_t receive_temp[2];
	uint8_t receive_pressure[3];
	
	
    // 打开I2C设备
    fd = iic_init("/dev/i2c-7");
    if (fd < 0) {
        perror("iic_init error");
        return -1;
    }

    // 读取校准数据
    read_calibration_data(fd, &AC1, &AC2, &AC3, &AC4, &AC5, &AC6, &B1, &B2, &MB, &MC, &MD);

    //开始测量温度 
    send_data[0] = 0x2e;  //要写入的数据
    if (iic_write(fd, Slave_Addr, Tempture_Pressure_reg , send_data, 1) <0) { 
        perror("iic_write error");
        close(fd);
        return -1;
    }
    usleep(4500); // 等待测量完成

    // 读取未校准的温度数据
    if (iic_read(fd, Slave_Addr, Data_Out_MSB , receive_temp, 2) < 0) {
        perror("iic_read error");
        close(fd);
        return -1;
    }
    raw_temp= receive_temp[0]<<8|receive_temp[1];  //未校准的温度值
    
	
	
	send_data[0] = 0x34;  //要写入的数据
    if (iic_write(fd, Slave_Addr, Tempture_Pressure_reg , send_data, 1) <0) {  //开始测量压力
        perror("iic_write error");
        close(fd);
        return -1;
    }
    usleep(4500); // 等待测量完成

    // 读取未校准的压力数据
    if (iic_read(fd, Slave_Addr, Data_Out_MSB , receive_pressure, 3) < 0) {
        perror("iic_read error");
        close(fd);
        return -1;
    }
    raw_pressure =(receive_pressure[0]<<16|receive_pressure[1]<<8|receive_pressure[0]) >>8;  //未校准的压力值
  
    // 计算实际温度和压力
    calculate_true_values(raw_temp, raw_pressure, &true_temp, &true_pressure, AC1, AC2, AC3, AC4, AC5, AC6, B1, B2, MB, MC, MD);

    // 输出实际温度和压力
    printf("True Temperature: %.2f C\n", true_temp / 10.0);
    printf("True Pressure: %.2f hPa\n", true_pressure / 100.0);

    // 关闭I2C设备
    close(fd);
    return 0;
}

在这里插入图片描述

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

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

相关文章

配网终端通讯管理板,稳控装置通讯管理卡,铁路信号通讯管理卡

配网终端通讯管理板 ● 配网终端通讯管理板 ● ARM Cortex™-A5 &#xff0c;533MHz ● 256MB RAM&#xff0c;512MB FLASH 配网终端通讯管理板 ARM Cortex™-A5 &#xff0c;533MHz 256MB RAM&#xff0c;512MB FLASH 2x10/100/1000Mbps LAN&#xff08;RJ45&#xff09; 6x…

FastAPI系列 4 -路由管理APIRouter

FastAPI系列 -路由管理APIRouter 文章目录 FastAPI系列 -路由管理APIRouter一、前言二、APIRouter使用示例1、功能拆分2、users、books模块开发3、FastAPI主体 三、运行结果 一、前言 未来的py开发者请上座&#xff0c;在使用python做为后端开发一个应用程序或 Web API&#x…

MySQL数据库---LIMIT、EXPLAIN详解

分页查询 语法 select _column,_column from _table [where Clause] [limit N][offset M]select * : 返回所有记录limit N : 返回 N 条记录offset M : 跳过 M 条记录, 默认 M0, 单独使用似乎不起作用 limit N,M : 相当于 limit M offset N , 从第 N 条记录开始, 返回 M 条记录…

贪心算法学习三

例题一 解法&#xff08;贪⼼&#xff09;&#xff1a; 贪⼼策略&#xff1a; ⽤尽可能多的字符去构造回⽂串&#xff1a; a. 如果字符出现偶数个&#xff0c;那么全部都可以⽤来构造回⽂串&#xff1b; b. 如果字符出现奇数个&#xff0c;减去⼀个之后&#xff0c;剩下的…

对象存储OSS 客户端签名直传的安全风险和解决方法

1. 前言 阿里云对象存储OSS&#xff08;Object Storage Service&#xff09;是一款海量、安全、低成本、高可靠的云存储服务&#xff0c;可提供99.9999999999%&#xff08;12个9&#xff09;的数据持久性&#xff0c;99.995%的数据可用性。多种存储类型供选择&#xff0c;全面…

AXI Quad SPI IP核中命令的使用

1 双通道SPI和混合内存模式下支持的常用命令 对于配置中Mode设置为Dual且Slave Device设置为Mixed的情况&#xff0c;IP核支持表3-1中列出的命令。这些命令在Winbond、Micron和Spansion内存设备上具有相同的命令、地址和数据行为。 某些命令&#xff0c;如fast read、dual I/…

产品创新:驱动企业增长的核心动力

在当今快速变化的市场环境中&#xff0c;产品创新已成为企业生存和发展的关键。产品创新不仅涉及全新产品或服务的开发&#xff0c;也包括对现有产品或服务的持续改进和优化。本文将深入探讨产品创新的定义、重要性以及如何通过创新驱动企业增长&#xff0c;并结合实际案例进行…

每位比特币人都终将成为一个国际主义者

原创 | 刘教链 周末BTC&#xff08;比特币&#xff09;趁势向着30日均线回归&#xff0c;现于69k一线悬停。7万刀以下加仓的机会窗口&#xff0c;和那蹉跎一生的岁月一样&#xff0c;过一天少一天&#xff0c;在每个纠结和拧巴的日子里&#xff0c;在软弱和彷徨的等待中&#x…

Python 算法交易实验71 QTV200数据流设计

说明 结构作为工程的基础&#xff0c;应该在最初的时候进行合理设计。这一次版本迭代&#xff0c;我希望最终实现的效果&#xff0c;除了在财务方法可以达到预期&#xff0c;在工程方面应该可以支持长期的维护、演进。 内容 1 财务表现期待 假设初始为60万资金作为主动资金…

Java学习 - MyBatis - 初识MyBatis

前言 什么是持久化 持久化是将程序数据在持久状态和瞬时状态间转换的机制&#xff0c;将数据保存到可永久保存的存储设备中。最常见的就是将内存中的对象存储在数据库中&#xff0c;或者存在磁盘文件、XML 数据文件中等等。其中&#xff0c;文件 IO 属于持久化机制&#xff0…

Web后端开发(请求-数组集合、日期、JSON参数)(三)

数组参数&#xff1a;请求参数名与形参数组名称相同且请求参数为多个&#xff0c;定义数组类型形参即可接收参数 RequestMapping("/arrayParam") public String arrayParam(String[] hobby){System.out.println(Arrays.toString(hobby));return "OK"; } …

Spring Event如何优雅实现系统业务解耦

Spring Event如何优雅实现系统业务解耦 一、介绍 Spring事件&#xff08;Spring Event&#xff09;是Spring框架的一项功能&#xff0c;它允许不同组件之间通过发布-订阅机制进行解耦的通信。在Spring中&#xff0c;事件是表示应用程序中特定事件的对象&#xff0c;例如用户注…

AI数据分析:根据Excel表格数据绘制柱形图

工作任务&#xff1a;将Excel文件中2013年至2019年间线上图书的销售额&#xff0c;以条形图的形式呈现&#xff0c;每个条形的高度代表相应年份的销售额&#xff0c;同时在每个条形上方标注具体的销售额数值 在deepseek中输入提示词&#xff1a; 你是一个Python编程专家&#…

OpenGauss数据库-4.表的创建、修改与删除

第1关&#xff1a;创建表 gsql -d postgres -U gaussdb -W passwd123123 create database testdb; \c testdb; passwd123123 create table test_table (test_id integer not null,test_info char(36)); 第2关&#xff1a;修改表 gsql -d testsb -U gaussdb -W passwd123123 …

Python实现删除Word文档中带有“指定内容”的段落文本(7)

前言 本文是该专栏的第7篇,后面会持续分享Python办公自动化干货知识,记得关注。 在处理word文档内容的时候,有时候我们需要一个干净整洁的文本内容。比如说,如下图所示的情况: 在处理上述word文档内容的时候,我们希望将文本底部的“下载链接”以及“附件信息”两个段落,…

Virustotal查询恶意进程

1、使用netstat查看可疑进程 执行ls -al /proc/$PID/exe确认可疑进程对应的文件&#xff1b;若文件未被删除&#xff0c;则直接上传文件到Virustotal进行检测&#xff0c;或者计算出文件对应的md5&#xff0c;使用md5去Virustotal进行查询&#xff1b;若文件已被删除&#xff0…

aabb c++

题目描述 查找形如"aabb"的四位完全平方数&#xff0c;也即前两位数字相同&#xff0c;后两位数字也相同。 输入 无 输出 若干行&#xff0c;每行一个符合条件的四位数&#xff08;从小到大&#xff09;。 分析&#xff1a; 完全平方数&#xff1a; &#xff…

[图解]建模相关的基础知识-07

1 00:00:04,710 --> 00:00:08,900 这是划分&#xff0c;下一个是有序对的概念 2 00:00:11,720 --> 00:00:13,800 我们知道集合是不分顺序的 3 00:00:15,090 --> 00:00:18,200 我们花括号来代表集合的话 4 00:00:18,210 --> 00:00:21,000 AB花括号等于BA花括号 …

ORA-01652 表空间不够解决方案

前章&#xff1a;出现表空间不足不要手动强制删除对应数据文件存储目录下的DBF文件&#xff0c;需要用SQL语句进行数据文件的DROP&#xff0c;否则会导致ORA-01033报错&#xff0c;因为我没有开启数据库的归档所以不能通过RECOVER的形式找回数据文件最后只能重装本地ORACLE。 …

首途第三十三套清新简约卡片风格蓝紫渐变色短视频模板 | 苹果CMSV10主题

首途第三十三套清新简约卡片风格蓝紫渐变色短视频模板 | 苹果CMSV10主题 我们的简约风格&#xff0c;以纯洁的白色和深邃的紫色为主色调&#xff0c;为您提供了一种清新、时尚的浏览体验。在这个简洁而美丽的界面中&#xff0c;您可以轻松畅享各种精彩短视频。我们专注于简单的…