【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);
        }
    }

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Go-unsafe详解

Go语言unsafe包 Go语言的unsafe包提供了一些底层操作的函数&#xff0c;这些函数可以绕过Go语言的类型系统&#xff0c;直接操作内存。虽然这些函数很强大&#xff0c;但是使用不当可能会导致程序崩溃或者产生不可预料的行为。因此&#xff0c;使用unsafe包时必须小心谨慎。 …

吴恩达ChatGPT《Prompt Engineering》笔记

ChatGPT 提示词工程师教程 1. 课程介绍 1.1 ChatGPT 相关术语 LLM&#xff1a;Large Language Model&#xff0c;大语言模型 Instruction Tuned LLM&#xff1a;经过指令微调的大语言模型 Prompt&#xff1a;提示词 RLHF&#xff1a;Reinforcement Learning from Human F…

机器视觉初步6:图像分割专题

图像分割是一种图像处理技术&#xff0c;它将图像划分为具有相似特征的区域。常见的图像分割方法包括阈值分割、边缘分割、区域分割、基于阈值的方法、基于边缘的方法、基于区域的方法、聚类分割、基于图论的方法、基于深度学习的方法。 文章目录 1.阈值分割2.边缘分割3.区域分…

CloFormer实战:使用CloFormer实现图像分类任务(二)

文章目录 训练部分导入项目使用的库设置随机因子设置全局参数图像预处理与增强读取数据设置Loss设置模型设置优化器和学习率调整算法设置混合精度&#xff0c;DP多卡&#xff0c;EMA定义训练和验证函数训练函数验证函数调用训练和验证方法 运行以及结果查看测试热力图可视化展示…

kali常用ping命令探测

ping 判断目标主机网络是否畅通 ping $ip -c 1其中&#xff0c;-c 1 表示发送一个数据包 traceroute 跟踪路由 traceroute $domain ARPING 探测局域网IP ARP&#xff08;地址解析协议&#xff09;&#xff0c;将IP地址转换成MAC地址arping $ip -c 1 #!/bin/ bash######…

基于matlab使用先导校准来补偿阵列不确定性(附源码)

一、前言 此示例说明如何使用先导校准来提高天线阵列在存在未知扰动时的性能。 原则上&#xff0c;可以轻松设计理想的均匀线性阵列&#xff08;ULA&#xff09;来执行阵列处理任务&#xff0c;例如波束成形或到达方向估计。在实践中&#xff0c;没有理想的阵列。例如&#xff…

初识轻量级分布式任务调度平台 xxl-job

文章目录 前言xxl-job的目录结构项目依赖 (父 pom.xml)xxl-job-admin 启动xxl-job-executor-sample (项目使用示例)xxl-job-executor-sample-frameless : 不使用框架的接入方式案例xxl-job-executor-sample-springboot : springboot接入方案案例 xxl-job执行器器启动流程分析调…

linux_centos7.9/ubuntu20.04_下载镜像及百度网盘分享链接

1、镜像下载站点 网易开源镜像&#xff1a;http://mirrors.163.com/ 搜狐开源镜像&#xff1a;http://mirrors.sohu.com/ 阿里开源镜像&#xff1a;https://developer.aliyun.com/mirror/ 首都在线科技股份有限公司&#xff1a;http://mirrors.yun-idc.com/ 常州贝特康姆软件技…

C++【红黑树】

✨个人主页&#xff1a; 北 海 &#x1f389;所属专栏&#xff1a; C修行之路 &#x1f383;操作环境&#xff1a; Visual Studio 2019 版本 16.11.17 文章目录 &#x1f307;前言&#x1f3d9;️正文1、认识红黑树1.1、红黑树的定义1.2、红黑树的性质1.3、红黑树的特点 2、红黑…

三分钟学习一个python小知识1-----------我的对python的基本语法的理解

文章目录 一、变量定义二、数据类型三、条件语句四、循环语句五、函数定义总结 一、变量定义 在Python中&#xff0c;使用等号&#xff08;&#xff09;进行变量的定义&#xff0c;并不需要声明变量的类型&#xff0c;Python会自动根据赋值的数据类型来判断变量的类型&#xf…

chatgpt赋能python:Python构造和析构:介绍和实例

Python 构造和析构&#xff1a;介绍和实例 当你编写 Python 程序时&#xff0c;你可能会注意到一个名为构造函数和析构函数的概念。这些函数可以在创建和删除一个对象时自动执行一些操作。本文将深入介绍 Python 中的构造和析构概念。 构造函数 Python 使用一种名为 __init_…

戴尔U盘重装系统Win10步骤和详细教程

戴尔电脑深受用户们的喜欢&#xff0c;那么如何使用U盘给戴尔电脑重装Win10系统呢&#xff0c;这让很多用户都犯难了&#xff0c;以下就是小编给大家分享的戴尔U盘重装系统Win10步骤和详细教程&#xff0c;按照这个教程操作&#xff0c;就能顺利完成戴尔U盘重装Win10系统的操作…

3、互联网行业及产品经理分类

上一篇文章&#xff1a;2、产品经理的工作内容_阿杰学编程的博客-CSDN博客 1、产品经理分类 我们把产品经理划分成这样两个大的类型&#xff0c;一个是传统行业的&#xff0c;一个是互联网行业的。这个简单了解一下就行。 这个里面会发现绝大多数也是体育劳动&#xff0c;你比…

Nautilus Chain:模块化Layer3的先行者

“模块化特性的 Nautilus Chain 正在成为 Layer3 的早期定义者之一&#xff0c;并有望进一步推动区块链更广泛的应用与实践 ” 自以太坊创始人 Vitalik Buterin 在去年提出 Layer3 的概念后&#xff0c;行业始终对“Layer3”进行讨论&#xff0c;并期望推动该概念&#xff0c;从…

微服务框架

流量入口Nginx 在上图中可以看到&#xff0c;Nginx作为整个架构的流量入口&#xff0c;可以理解为一个外部的网关&#xff0c;它承担着请求的路由转发、负载均衡、动静分离等功能。作为一个核心入口点&#xff0c;Nginx肯定要采用多节点部署&#xff0c;同时通过keepalived来实…

【云原生 · Docker】轻松学会dockerfile构建镜像

目录 &#x1f349;dockerfile是什么 &#x1f349;镜像的缓存特性 &#x1f349;dockerfile命令 &#x1f352;FROM &#x1f352;RUN &#x1f352;CMD &#x1f352;LABEL &#x1f352;EXPOSE &#x1f352;ENV &#x1f352;ADD &#x1f352;COPY &#x1f352;ENTRYPOIN…

Background-1 基础知识 sqli-Labs Less1-Less-4

文章目录 一、Less-1二、Less-2三、Less-3四、Less-4总结 一、Less-1 http://sqli:8080/Less-1/?id1在第一关我们可以尝试增加一个单引号进行尝试 http://sqli:8080/Less-1/?id1错误显示如下&#xff1a; near 1 LIMIT 0,1 at line 1推测语法的结构 select *from where **…

【从零开始学习JAVA | 第六篇】面向对象综合训练

目录 前言&#xff1a; 1.文字版格斗游戏&#xff1a; 2.对象数组1 前言&#xff1a; 前面我们已经讲解了JAVA面向程序对象思想的关键要素&#xff1a;封装。我们将利用本篇进行几个小型的练习&#xff0c;帮助我们更好的理解面向对象编程这种思想。 1.文字版格斗游戏&#x…

SpringSecurity过滤指定url【.antMatchers(***).permitAll()】失效问题

SpringSecurity过滤指定url【.antMatchers(***).permitAll()】失效问题 问题描述 在使用SpringSecurity作为后端验证框架时&#xff0c;遇到配置一些接口不需要token验证&#xff0c;直接放行&#xff0c;但是配置之后没有生效&#xff0c;一直究其原因。 项目配置 因为要进…

ES6相关概念

什么是ES6&#xff1f; ES 的全称是 ECMAScript , 它是由 ECMA 国际标准化组织,制定的一项脚本语言的标准化规范。 为什么使用 ES6 ? 每一次标准的诞生都意味着语言的完善&#xff0c;功能的加强。JavaScript语言本身也有一些令人不满意的地方。 变量提升特性增加了程序运行…