【STM32】软件I2C

news2024/11/27 4:36:30

【STM32】软件I2C

I2C简介

I2C总线是一种串行、半双工的总线,主要用于近距离、低速的芯片之间的通信。I2C总线有两根双向的信号线,一根数据线SDA用于收发数据,一根时钟线SCL用于通信双方时钟的同步。

在这里插入图片描述

  • 在一个i2c通讯总线中,可连接多个i2c通讯设备(分为主机和从机)。
  • 主机有权发起和结束一次通信,从机只能被动呼叫。当总线上有多个主机同时启动总线时,i2c也具备冲突检测和仲裁的功能来防止错误产生。
  • 每个连接到i2c总线上的器件都有一个唯一的地址(7bit或者10bit),且每一个器件都可以作为主机也可以作为从机(但同一时刻只能有一个主机)。
  • 串行的8位双向数据传输速率在标准模式下可达100Kbit/s,快速模式下可达400Kbit/s,高速模式下可达3.4Mbit/s。

I2C协议

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

在这里插入图片描述

有几点需要重点说明,下面会详细介绍每一个环节

  • SLAVE_ADDRESS有7bit格式或者10bit格式。
  • 除开起始和终止信号,每次发送或者读取一字节数据后,都需要等待ack或者发送ack。
  • 寄存器地址或者值也肯能占多字节,先发送高字节的数据。

起始和终止信号

在这里插入图片描述

起始信号:当SCL在高电平期间SDA从高电平向低电平切换

终止信号:当SCL在高电平期间SDA从低电平向高电平切换

数据有效性

SDA数据线在SCL的每一个时钟周期传输一位数据。

  • SCL高电平期间:SDA表示的数据有效,此时SDA的电平要稳定,SDA为高电平时表示数据“1”,为低电平时表示数据“0”。

  • SCL低电平期间:SDA的数据无效,一般在这个时候SDA进行电平切换,为下一次表示数据做好准备。

    在这里插入图片描述

    数据和地址按8位进行传输,先传输数据的高位,每次传输的字节数不受限制.

I2C地址及数据方向

I2C总线上的每一个设备都有自己的独立地址,主机发起通讯时,通过SDA信号线发送设备地址(SLAVE_ADDRESS)来查找从机.I2C协议规定设备地址可以是7位或者10位,实际中7位地址应用比较广泛.紧跟设备地址的一个数据位用来表示数据传输方向,第8位或第11位.

  • 数据方向位为"1":表示主机由从机读数据
  • 数据方向位为"0":表示主机向从机写数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5s4ho6wg-1687146230817)(C:\Users\DELL\AppData\Roaming\Typora\typora-user-images\image-20230619104122234.png)]

在这里插入图片描述

10bit slave_address要分两个字节传输,高字节需要补上标志位"11110".

I2C设备寄存器地址和数据

上文中,我们默认设备寄存器地址和数据都是一个字节,但是实际项目中寄存器地址可能是两字节,数据可能是两字节或四字节.这种情况下其实和单字节类似,只不过是将多字节拆分为单字节传输,中间还是需要ACK.

举例,16位寄存器地址和32位数据的读时序:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k416fjAD-1687146230820)(C:\Users\DELL\AppData\Roaming\Typora\typora-user-images\image-20230619111806577.png)]

Burst模式

burst模式其实就是连续模式,连续写或者读.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bgcq56P6-1687146230821)(C:\Users\DELL\AppData\Roaming\Typora\typora-user-images\image-20230619112048014.png)]

还是只传输一个地址,但是值可以是多个,写入的地址则是在原地址上递增.

写寄存器时序,就不举例了.就是多接收数据,在不要数据的时候发送NACK,否则还是继续发送ACK.理论上,burst的长度是没有限制,但是实际I2C设备是有限制的,具体就需要看I2C设备的说明了.

应答和非应答信号

I2C的数据或者地址传输都带响应.响应包括"ACK"和"NACK"两种信号.作为数据接收端时,当收到I2C传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送"ACK"信号,发送方会继续发送下一个数据;若接收方希望结束数据传输,则向对方发送"NACK"信号.发送方接收到信号后会产生一个停止信号,结束信号传输.

注意一点,在等待应答信号前,需要释放SDA的控制权,将SDA总线置为高电平.

数据发送端会释放SDA的控制权,由数据接收端控制SDA,给发送端传输应答或非应答信号

  • SDA为高电平:表示非应答信号(NACK)

  • SDA为低电平:表示应答信号(ACK)

软件I2C GPIO引脚配置

这里先说一下,为什么要使用软件模拟i2c,硬件上不是已经实现了i2c吗?我们在stm32中直接将引脚配置成i2c,然后使用hal库不就可以实现i2c通信吗?

理论上是的,但是据说,STM32芯片I2C有bug,所以很少人愿意冒险。

所以,要使用软件模拟I2C的时候,GPIO需要配置为开漏输出,至于为什么这样配置,可以参考(2条消息) I2C 开漏输出与上拉电阻_jiangdf的博客-CSDN博客

软件I2C的实现

软件设计的几个目标

  • GPIO配置由外部实现(其实也想放在这里实现的,但是使能GPIO时钟有点绕)
  • 支持7位或10位SLAVE_ADDRESS
  • 支持多字节寄存器地址或数据
  • 支持多个I2C速率

先看头文件

/*
 * @Date: 2023-06-12 11:03:57
 * @LastEditors: zdk
 * @LastEditTime: 2023-06-16 14:30:05
 * @FilePath: \haptics_evb_h7_bootloaderd:\01Project\haptic\code_new\haptics_evb_h7\haptics_evb_h7\Core\Inc\i2c_sw.h
 */
#ifndef I2C_SW_H
#define I2C_SW_H

#ifdef __cplusplus
#define
extern "c"
{
#endif

#include "gpio.h"
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>

#define I2C_WRITE (0x00)
#define I2C_READ  (0x01)
#define I2C_10BIT_SLAVE_ADDRESS_INDICATOR (0b11110)

    typedef enum
    {
        Standard_Mode = 100 * 1000,
        Fast_Mode = 400 * 1000,
        Fast_Mode_Plus = 1000 * 1000,
    } I2C_SW_Speed_Mode_e; //i2c速率 unit Hz

    typedef enum
    {
        One_Byte = 1,
        Two_Byte = 2,
        Four_Byte = 4
    } Data_Width_e;//数据长度(寄存器地址或者值的数据长度)

    typedef enum
    {
        Slave_Address_7Bit,
        Slave_Address_10Bit,
    } Slave_Address_Width_e;//i2c slave address的位宽

    typedef struct
    {
        uint16_t scl_pin;
        uint16_t sda_pin;
        GPIO_TypeDef* scl_port;
        GPIO_TypeDef* sda_port;
        I2C_SW_Speed_Mode_e speed_mode;
        uint16_t slave_addr;
        Slave_Address_Width_e slave_address_width;
        Data_Width_e slave_reg_addr_width;
        Data_Width_e slave_reg_value_width;
    } I2C_SW_Handle_t;

    /**
     * @description: 检测slave address的i2c设备是否存在
     * @param {I2C_SW_Handle_t*} handle
     * @return {*}
     */     
    bool i2c_device_exist(I2C_SW_Handle_t* handle);

    /**
     * @description: 读寄存器
     * @param {I2C_SW_Handle_t*} handle
     * @param {uint32_t} reg_addr
     * @param {uint32_t} len
     * @param {uint32_t*} buf
     * @return {*}
     */    
    int i2c_reg_read(I2C_SW_Handle_t* handle, uint32_t reg_addr, uint32_t len, uint32_t* buf);

    /**
     * @description: 写寄存器
     * @param {I2C_SW_Handle_t*} handle
     * @param {uint32_t} reg_addr
     * @param {uint32_t} len
     * @param {uint32_t*} buf
     * @return {*}
     */    
    int i2c_reg_write(I2C_SW_Handle_t* handle, uint32_t reg_addr, uint32_t len, const uint32_t* buf);
    
#ifdef __cplusplus
#define
}
#endif

#endif

再看源文件,源文件中有多个本地函数可以介绍一下

/**
 * @description: 延时函数,用于控制i2c频率
 * @param {I2C_SW_Speed_Mode_e} speed_mode
 * @return {*}
 */
static void i2c_delay(I2C_SW_Speed_Mode_e speed_mode);

/**
 * @description: 将时钟信号拉低
 * @param {I2C_SW_Handle_t*} handle
 * @return {*}
 */
static void scl_l(I2C_SW_Handle_t* handle);

/**
 * @description: 将时钟信号拉高
 * @param {I2C_SW_Handle_t*} handle
 * @return {*}
 */
static void scl_h(I2C_SW_Handle_t* handle);

/**
 * @description: 将数据信号拉低
 * @param {I2C_SW_Handle_t*} handle
 * @return {*}
 */
static void sda_l(I2C_SW_Handle_t* handle);

/**
 * @description: 将数据信号拉高
 * @param {I2C_SW_Handle_t*} handle
 * @return {*}
 */
static void sda_h(I2C_SW_Handle_t* handle);

/**
 * @description: 读取数据信号的电平
 * @param {I2C_SW_Handle_t*} handle
 * @return {*}
 */
static char sda_read(I2C_SW_Handle_t* handle);


/**
 * @description: 产生i2c起始信号
 * @param {I2C_SW_Handle_t*} handle
 * @return {*}
 */
static void i2c_start(I2C_SW_Handle_t* handle);

/**
 * @description: 产生i2c终止信号
 * @param {I2C_SW_Handle_t*} handle
 * @return {*}
 */
static void i2c_stop(I2C_SW_Handle_t* handle);

/**
 * @description: 产生i2c应答信号
 * @param {I2C_SW_Handle_t*} handle
 * @return {*}
 */
static void i2c_ack(I2C_SW_Handle_t* handle);

/**
 * @description: 产生i2c非应答信号
 * @param {I2C_SW_Handle_t*} handle
 * @return {*}
 */
static void i2c_no_ack(I2C_SW_Handle_t* handle);

/**
 * @description: 等待i2c应答信号
 * @param {I2C_SW_Handle_t*} handle
 * @return {*}
 */
static bool i2c_wait_ack(I2C_SW_Handle_t* handle);

/**
 * @description: i2c发送一字节数据
 * @param {I2C_SW_Handle_t*} handle
 * @param {char} data
 * @return {*}
 */
static void i2c_send_byte(I2C_SW_Handle_t* handle, char data);

/**
 * @description: i2c读取一字节数据
 * @param {I2C_SW_Handle_t*} handle
 * @return {*}
 */
static char i2c_recv_byte(I2C_SW_Handle_t* handle);

/**
 * @description: 发送slave address信息
 * @param {I2C_SW_Handle_t*} handle
 * @param {char} rw 读或者写方向
 * @return {*}
 */
static bool i2c_send_slave_address_with_wait_ack(I2C_SW_Handle_t* handle, char rw);

/**
 * @description: 发送要读或写的地址信息
 * @param {I2C_SW_Handle_t*} handle
 * @param {uint32_t} address
 * @return {*}
 */
static bool i2c_send_one_reg_address_with_wait_ack(I2C_SW_Handle_t* handle, uint32_t address);

/**
 * @description: 接收寄存器地址值信息
 * @param {I2C_SW_Handle_t*} handle
 * @param {uint32_t*} value
 * @param {bool} no_ack
 * @return {*}
 */
static bool i2c_recv_one_reg_value_with_ack(I2C_SW_Handle_t* handle, uint32_t* value, bool no_ack);

/**
 * @description: 发送寄存器值信息
 * @param {I2C_SW_Handle_t*} handle
 * @param {uint32_t} value
 * @return {*}
 */
static bool i2c_send_one_reg_value_with_wait_ack(I2C_SW_Handle_t* handle, uint32_t value);

static void i2c_delay(I2C_SW_Speed_Mode_e speed_mode)
{
    uint32_t temp;
    SysTick->LOAD = SystemCoreClock / 8 / (speed_mode * 2) - 1;
    SysTick->VAL = 0X00; //
    SysTick->CTRL = 0X01; //
    do
    {
        temp = SysTick->CTRL; //
    }
    while((temp & 0x01) && (!(temp & (1 << 16)))); //
    SysTick->CTRL = 0x00; //
    SysTick->VAL = 0X00; //
}

static void scl_l(I2C_SW_Handle_t* handle)
{
    HAL_GPIO_WritePin(handle->scl_port, handle->scl_pin, GPIO_PIN_RESET);
}

static void scl_h(I2C_SW_Handle_t* handle)
{
    HAL_GPIO_WritePin(handle->scl_port, handle->scl_pin, GPIO_PIN_SET);
}

static void sda_l(I2C_SW_Handle_t* handle)
{
    HAL_GPIO_WritePin(handle->sda_port, handle->sda_pin, GPIO_PIN_RESET);
}

static void sda_h(I2C_SW_Handle_t* handle)
{
    HAL_GPIO_WritePin(handle->sda_port, handle->sda_pin, GPIO_PIN_SET);
}
static char sda_read(I2C_SW_Handle_t* handle)
{
    return HAL_GPIO_ReadPin(handle->sda_port, handle->sda_pin);
}

static void i2c_start(I2C_SW_Handle_t* handle)
{
    scl_h(handle);
    sda_h(handle);
    i2c_delay(handle->speed_mode);

    sda_l(handle);
    i2c_delay(handle->speed_mode);

    scl_l(handle);
    i2c_delay(handle->speed_mode);
}

static void i2c_stop(I2C_SW_Handle_t* handle)
{
    scl_h(handle);
    i2c_delay(handle->speed_mode);

    sda_l(handle);
    i2c_delay(handle->speed_mode);

    sda_h(handle);
    i2c_delay(handle->speed_mode);
}

static void i2c_ack(I2C_SW_Handle_t* handle)
{
    scl_l(handle);
    i2c_delay(handle->speed_mode);

    sda_l(handle);
    i2c_delay(handle->speed_mode);

    scl_h(handle);
    i2c_delay(handle->speed_mode);

    scl_l(handle);
    i2c_delay(handle->speed_mode);

    sda_h(handle);
    i2c_delay(handle->speed_mode);
}

static void i2c_no_ack(I2C_SW_Handle_t* handle)
{
    sda_h(handle);
    i2c_delay(handle->speed_mode);

    scl_h(handle);
    i2c_delay(handle->speed_mode);

    scl_l(handle);
    i2c_delay(handle->speed_mode);
}

static bool i2c_wait_ack(I2C_SW_Handle_t* handle)
{
    sda_h(handle);
    i2c_delay(handle->speed_mode);

    scl_h(handle);
    i2c_delay(handle->speed_mode);

    int retry_cnt = 10;
    while(retry_cnt--)
    {
        if(!sda_read(handle))
        {
            scl_l(handle);
            i2c_delay(handle->speed_mode);
            return true;
        }
    }
    scl_l(handle);
    i2c_delay(handle->speed_mode);

    return false;
}

static void i2c_send_byte(I2C_SW_Handle_t* handle, char data)
{
    for(int offset = 0; offset < 8; offset++)
    {
        scl_l(handle);
        i2c_delay(handle->speed_mode);

        if(data & 0x80)
            sda_h(handle);
        else
            sda_l(handle);
        i2c_delay(handle->speed_mode);

        scl_h(handle);
        i2c_delay(handle->speed_mode);

        data <<= 1;
    }

    scl_l(handle);
    i2c_delay(handle->speed_mode);
}

static char i2c_recv_byte(I2C_SW_Handle_t* handle)
{
    sda_h(handle);
    i2c_delay(handle->speed_mode);

    char value = 0;
    for(int offset = 0; offset < 8; offset++)
    {
        scl_h(handle);
        i2c_delay(handle->speed_mode);

        value <<= 1;
        if(sda_read(handle))
            value++;

        scl_l(handle);
        i2c_delay(handle->speed_mode);
    }

    return value;
}

static bool i2c_send_slave_address_with_wait_ack(I2C_SW_Handle_t* handle, char rw)
{
    if(handle->slave_address_width == Slave_Address_7Bit)
    {
        i2c_send_byte(handle, (handle->slave_addr << 1) | rw);
        if(!i2c_wait_ack(handle))
            return false;
    }
    else if(handle->slave_address_width == Slave_Address_10Bit)
    {
        uint8_t h_addr = (I2C_10BIT_SLAVE_ADDRESS_INDICATOR << 7)
                         | ((handle->slave_addr >> 8 & 0x03) << 2) | rw;
        i2c_send_byte(handle, h_addr);
        if(!i2c_wait_ack(handle))
            return false;

        uint8_t l_addr = handle->slave_addr & 0xff;
        i2c_send_byte(handle, l_addr);
        if(!i2c_wait_ack(handle))
            return false;
    }
    return true;
}

static bool i2c_send_one_reg_address_with_wait_ack(I2C_SW_Handle_t* handle, uint32_t address)
{
    for(int j = 0; j < handle->slave_reg_addr_width; j++)
    {
        uint8_t offset = (handle->slave_reg_addr_width - 1 - j) * 8;
        uint8_t temp_reg_addr = (address >> offset) & 0xff;

        i2c_send_byte(handle, temp_reg_addr);
        if(!i2c_wait_ack(handle))
            return false;
    }
    return true;
}

static bool i2c_recv_one_reg_value_with_ack(I2C_SW_Handle_t* handle, uint32_t* value, bool no_ack)
{
    for(int k = 0; k < handle->slave_reg_value_width; k++)
    {
        uint8_t offset = (handle->slave_reg_value_width - 1 - k) * 8;
        *value |= (i2c_recv_byte(handle) << offset);

        if(no_ack)
            i2c_no_ack(handle);
        else
            i2c_ack(handle);
    }
    return true;
}

static bool i2c_send_one_reg_value_with_wait_ack(I2C_SW_Handle_t* handle, uint32_t value)
{
    for(int k = 0; k < handle->slave_reg_value_width; k++)
    {
        uint8_t offset = (handle->slave_reg_value_width - 1 - k) * 8;

        i2c_send_byte(handle, (value >> offset) & 0xff);
        if(!i2c_wait_ack(handle))
            return false;
    }
    return true;
}

我们可以看到,比其他博客多了好几个函数,那些都是因为需要兼容多字节寄存器地址或者数据使用的.其中i2c_delay打算另外写一篇文章详细介绍(但是目前好像只在100MHz的时候比较准)

对外接口就是这几个:

bool i2c_device_exist(I2C_SW_Handle_t* handle)
{
    bool ret = 0;

    i2c_start(handle);

    ret = i2c_send_slave_address_with_wait_ack(handle, I2C_WRITE);

    i2c_stop(handle);

    return ret;
}

int i2c_reg_read(I2C_SW_Handle_t* handle, uint32_t reg_addr, uint32_t len, uint32_t* buf)
{
    i2c_start(handle);

    if(!i2c_send_slave_address_with_wait_ack(handle, I2C_WRITE))
        goto fail;

    if(!i2c_send_one_reg_address_with_wait_ack(handle, reg_addr))
        goto fail;

    i2c_start(handle);

    if(!i2c_send_slave_address_with_wait_ack(handle, I2C_READ))
        goto fail;

    for(int i = 0; i < len; i++)
    {
        i2c_recv_one_reg_value_with_ack(handle, &buf[i], len - 1 == i);
    }
    i2c_stop(handle);
    return 0;

fail:
    i2c_stop(handle);
    return -1;
}

int i2c_reg_write(I2C_SW_Handle_t* handle, uint32_t reg_addr, uint32_t len, const uint32_t* buf)
{
    i2c_start(handle);

    if(!i2c_send_slave_address_with_wait_ack(handle, I2C_WRITE))
        goto fail;

    if(!i2c_send_one_reg_address_with_wait_ack(handle, reg_addr))
        goto fail;

    for(int i = 0; i < len; i++)
    {
        if(! i2c_send_one_reg_value_with_wait_ack(handle, buf[i]))
            goto fail;
    }

    i2c_stop(handle);
    return 0;

fail:
    i2c_stop(handle);
    return -1;
}

那么以上一个软件I2C功能就完成了.

软件I2C的使用

首先,在STM32CubeMX中配置GPIO.

在这里插入图片描述

然后,实例化软件I2C句柄对象.

    I2C_SW_Handle_t* handle;

    handle = malloc(sizeof(I2C_SW_Handle_t));
    handle->scl_pin = TEMP_I2C_SCL_Pin;
    handle->scl_port = TEMP_I2C_SCL_GPIO_Port;
    handle->sda_pin = TEMP_I2C_SDA_Pin;
    handle->sda_port = TEMP_I2C_SDA_GPIO_Port;
    handle->slave_addr = TMP117_I2C_ADDRESS;
	handle->slave_address_width=Slave_Address_7Bit;
    handle->speed_mode = Standard_Mode;
    handle->slave_reg_addr_width = One_Byte;
    handle->slave_reg_value_width = Two_Byte;

最后,调用接口

    if(i2c_device_exist(handle))
        printf("hello i2c_address=0x%02x is exist\r\n", handle->slave_addr);

    uint32_t device_id = 0;
    if(0 == i2c_reg_read(handle, 0x0f, 1, &device_id))
        printf("device_id=0x%04x \r\n", device_id);

    uint32_t temp = 0;
    while(1)
    {
        if(0 == i2c_reg_read(handle, 0x00, 1, &temp))
        {
            printf("read_value=0x%04x  read_temp=%0.5f\r\n", temp, (int32_t)temp * 0.0078125);
        }
    }

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

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

相关文章

怎么显示文件后缀名?查看文件后缀名可以这样做!

案例&#xff1a;在我的电脑上&#xff0c;看不到文件的后缀名&#xff0c;这会导致命名时出现重复文件后缀的情况&#xff0c;给我带来了不好的体验。怎么才能看到文件的后缀名呢&#xff1f;如何操作&#xff1f; 在日常使用电脑的过程中&#xff0c;我们经常需要查看文件的…

实习记录(二)Java常用工具库

一.Lombok 1.背景概述 Lombok是一个非常高效的专用于Java的自动构建插件库&#xff0c;其简化了 JavaBean 的编写&#xff0c;避免了冗余和样板式代码的出现&#xff0c;让编写的类更加简洁明了&#xff0c;可以帮助大家节省很多重复低效的代码编写。比如重复性的Setter、Gett…

【c语言】五道经典练习题④

目录 ①、年月日经过n天后的日期 ②、坐标排序 ③、统计文件中出现某个单词的次数 ④、输出含for的行 ⑤、比较两个文本是否相等 ①、年月日经过n天后的日期 题述&#xff1a;定义包含年月日表示的日期的结构体&#xff0c;写程序实现计算某年某月某日过n天后的日期是哪年…

肠道细菌阻碍阿卡波糖的降血糖作用

我们知道&#xff0c;口服抗糖尿病药是治疗糖尿病的有效方式之一。然而&#xff0c;患者对抗糖尿病药的反应程度各不相同&#xff0c;例如&#xff0c;有些患者在长期使用阿卡波糖后会产生耐药性。 阿卡波糖通常在饭前口服。它抑制人α-葡萄糖苷酶达到降血糖作用&#xff0c;包…

GWO-VMD-近似熵-极限学习机的轴承故障诊断软件,以西储大学轴承数据为例,采用MATLABAPP开发

采用灰狼算法优化VMD两个参数&#xff0c;以包络熵为最小适应度值&#xff0c;在最佳参数下提取采用近似熵指标提取西储大学轴承数据的特征向量&#xff0c;最后选用极限学习机ELM进行故障诊断。将以上程序集成在MATLABAPP进行开发。 首先是这个软件的各个界面展示。 软件启动…

云安全技术(四)之云计算安全的设计原则

计算安全的设计原则 Understand Design Principles of Secure Cloud Computing 1.1 云安全数据生命周期 Cloud secure data lifecycle 数据始终是安全保护的首要问题。必须深刻了解数据生命周期&#xff0c;以便正确制定和遵守安全策略&#xff0c;把握正确的步骤顺序&#xf…

万博智云与品高股份完成产品兼容性互认证,持续助力国产化生态建设

近日&#xff0c;万博智云的HyperBDR云容灾软件与广州市品高股份有限公司&#xff08;简称&#xff1a;品高股份&#xff09;旗下产品品高基础架构云资源管理软件V9.0完成了产品兼容性认证。 经万博智云和品高云双方人员的共同测试&#xff0c;得出结论&#xff1a; HyperBDR…

【C/C++数据结构与算法】C语言链表

目录 一、单链表 二、双向循环链表 三、判断链表是否带环 四、链表的回文结构判断 五、复制带随机指针的链表 一、单链表 优点&#xff1a;头部增删效率高&#xff0c;动态存储无空间浪费 缺点&#xff1a;尾部增删、遍历效率低&#xff0c;不支持随机访问节点 头结点&…

【夜深人静学习数据结构与算法 | 第六篇】贪心算法

目录 前言&#xff1a; 引入: 贪心算法&#xff1a; 455. 分发饼干 - 力扣&#xff08;LeetCode&#xff09; 376. 摆动序列 - 力扣&#xff08;LeetCode&#xff09; 53. 最大子数组和 - 力扣&#xff08;LeetCode&#xff09; 122. 买卖股票的最佳时机 II - 力扣&a…

【Python 随练】统计字符类型个数

题目&#xff1a; 输入一行字符&#xff0c;分别统计出其中英文字母、空格、数字和其它字符的个数。 简介&#xff1a; 在本篇博客中&#xff0c;我们将解决一个字符统计问题&#xff1a;输入一行字符&#xff0c;统计其中英文字母、空格、数字和其他字符的个数。我们将提供…

学习python爬虫需要掌握哪些库?

Python爬虫是指使用Python编写的程序&#xff0c;用来自动化地获取互联网上的数据。通过爬取网站的HTML内容&#xff0c;并解析和提取所需的数据&#xff0c;可以实现自动化地收集、分析和处理大量的在线数据。 学习Python爬虫需要掌握以下几个核心库&#xff1a; Requests&am…

【ARM AMBA AXI 入门 9 - AXI 总线 AxPROT 与安全之间的关系 】

文章目录 介绍ARM Trustzone的安全扩展简介 1.1 AXI AxPROT 介绍1.1.1 AXI 对 Trustzone的支持 介绍 ARMv8 架构中的AXI&#xff08;Advanced eXtensible Interface&#xff09;总线与NS&#xff08;Non-Secure&#xff09;位密切相关。NS位是指在ARM TrustZone安全扩展中定义…

LeetCode 1254. Number of Closed Islands【DFS,BFS,并查集】中等

本文属于「征服LeetCode」系列文章之一&#xff0c;这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁&#xff0c;本系列将至少持续到刷完所有无锁题之日为止&#xff1b;由于LeetCode还在不断地创建新题&#xff0c;本系列的终止日期可能是永远。在这一系列刷题文章…

单片机MCU如何实现让部分代码运行在RAM中

随着单片机硬件的发展&#xff0c;其中的RAM和flash越做越大。MCU在实际的使用中&#xff0c;通常程序都是运行在flash上的&#xff0c;RAM的高速空间并没有得到充分的利用&#xff0c;如果我们的程序需要运行的更快&#xff0c;系统有更好的实时性&#xff0c;我们可以考虑将这…

CSS查缺补漏之《常用长度单位(px、em、rem、%、vw/vh、vmin/vmax)》

此文内容较少&#xff0c;轻轻松松掌握&#xff0c;莫要有压力~ 正如现实生活中长度具有mm、dm、cm、m等&#xff0c;在css中&#xff0c;也具备多种长度单位&#xff0c;本文对常用的几种单位进行详细举例介绍~ px&#xff1a;像素单位 初学css时&#xff0c;px单位经常被使用…

【Leetcode60天带刷】day08字符串——344.反转字符串, 541. 反转字符串II,剑指Offer 05.替换空格,151.翻转字符串里的单词

题目&#xff1a; 344. 反转字符串 编写一个函数&#xff0c;其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。 不要给另外的数组分配额外的空间&#xff0c;你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。 示例 1&#xff1a; 输入&…

基于SpringBoot+Vue的“漫画之家”系统设计与实现

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架下…

新电脑机环境安装笔记

「Navicat_15.0.25_64bit_Setup.exe」 下载https://www.aliyundrive.com/s/b9xUw2JpuJb Navicat Keygen Patch v5.6.0 下载 https://www.aliyundrive.com/s/YYyE5BQMMuN 全程断网操作 patch 将安装目录选中 提示 check 64 mysql安装&#xff1a; https://baijiahao.baidu…

因子分析——SPSS实例分析

【续上篇主成分分析】 因子分析常用于通过可观测变量推断出其背后的公共因子&#xff08;也称为隐变量&#xff09;&#xff0c;样本在公共因子上的取值变化影响其在可观测变量上的取值&#xff0c;因为一般公共因子的个数小于可观测变量的数目&#xff0c;所以因子分析也可以…

渠道归因(一)传统渠道归因

渠道归因&#xff08;一&#xff09;传统渠道归因 小P&#xff1a;小H&#xff0c;我又来了。。。最近在做ROI数据&#xff0c;但是有个问题。。。 小H&#xff1a;什么问题&#xff0c;不就是收入/成本吗&#xff1f; 小P&#xff1a;是的&#xff0c;每个渠道的成本很容易计算…