【STM32】软件I2C(支持多字节)

news2024/9/21 3:23:05

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/674008.html

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

相关文章

Linux国产操作系统,UCA-系统工程师学习必备技能,系统状态查询和进程管理,uname命令last命令 top命令和 ps命令 kill命令

目录 ​编辑 1.系统状态查询 1. 1.uname命令 1.2. last命令 1.3. top命令 2.进程管理 2.1. ps命令 2.2. kill命令 1.系统状态查询 1. 1.uname命令 rootuos~#: uname rootuos~#: uname -r 4.19是内核&#xff0c;0-6-amd64是架构。 rootuos~#: uname -a eagle操作系统的…

【深入浅出 Spring Security(十一)】授权原理分析和持久化URL权限管理

授权原理分析和持久化URL权限管理 一、必须知道的三大组件&#xff08;Overview&#xff09;二、FilterSecurityInterceptor 源码分析SecurityMetadataSource 分析 三、自定义 FilterSecurityMetadataSource 对象&#xff08;实战&#xff09;自定义表CustomSecurityMetadataSo…

【C++】构造函数调用规则

欢迎来到博主 Apeiron 的博客&#xff0c;祝您旅程愉快 &#xff01;时止则止&#xff0c;时行则行。动静不失其时&#xff0c;其道光明。 1、缘起 &#xff08;1&#xff09;默认情况下&#xff0c;C 编译器至少给一个类添加 3 个函数 ① 默认构造函数&#xff08;无参&#…

多无人车自动编队

matlab2016b可运行 Kaveh Fathian - Distributed Formation Control of Cars with Collision Avoidance (google.com)

极致呈现系列之:Echarts桑基图的流动旋律

目录 什么是桑基图桑基图的特点及应用场景Echarts中桑基图的常用属性Vue3中创建桑基图美化桑基图 在各种复杂系统中&#xff0c;我们经常需要了解不同流量之间的关系和流动情况。这种信息的可视化呈现对于我们理解系统的结构和转移过程至关重要。桑基图作为一种强大的可视化工具…

Lambda and Collections

我们先从最熟悉的Java集合框架(Java Collections Framework, JCF)开始说起。 为引入Lambda表达式&#xff0c;Java8新增了java.util.funcion包&#xff0c;里面包含常用的函数接口&#xff0c;这是Lambda表达式的基础&#xff0c;Java集合框架也新增部分接口&#xff0c;以便与…

tf卡打不开怎么办?tf卡数据丢失怎么恢复

TF卡打不开怎么办&#xff1f;当TF卡中的数据丢失后&#xff0c;又如何恢复呢&#xff1f;下面小编已为您梳理和归纳了答案&#xff01;请继续阅读下文。 一、TF卡打不开怎么办&#xff1f; 首先&#xff0c;我们需要了解导致TF卡读不出来的具体原因&#xff0c;这可能包括没…

ch8_4中断系统

为什么需要中断&#xff1f; 输入&#xff0c;输出。 计算机程序调试&#xff1b;发生异常事件&#xff1b; 都需要由中断系统进行处理. 引发中断的各种因素包括&#xff1a;人为设置中断&#xff0c;程序性事故&#xff0c; 硬件故障&#xff0c;I/O设备&#xff0c;外部事件等…

英伟达驱动安装

https://zhuanlan.zhihu.com/p/60307377 https://www.nvidia.cn/Download/index.aspx?langcn

路由器的工作原理详解

什么叫路由&#xff1f; 路由器的英文是 Router&#xff0c;也就是「找路的工具」。找什么路&#xff1f;寻找各个网络节点之间的路。 换句话说&#xff0c;路由器就像是快递中转站&#xff0c;包裹会经过一个个的中转站&#xff0c;从遥远的地方寄到你家附近&#xff0c;数据…

驱动开发:内核远程线程实现DLL注入

在笔者上一篇文章《驱动开发&#xff1a;内核RIP劫持实现DLL注入》介绍了通过劫持RIP指针控制程序执行流实现插入DLL的目的&#xff0c;本章将继续探索全新的注入方式&#xff0c;通过NtCreateThreadEx这个内核函数实现注入DLL的目的&#xff0c;需要注意的是该函数在微软系统中…

Java多线程阻塞队列(BlockingDeque)的简析

目录 一.什么是阻塞队列(BlockingDeque) 二.阻塞队列有什么用? 三.运用阻塞队列来实现一个最简单的生产者消费者 四.模拟实现阻塞队列 一.什么是阻塞队列(BlockingDeque) 既然叫做阻塞队列,那么他就满足两个特性 1.队列:先进先出 2.阻塞:空了不让出,满了不让进 &#…

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######…

云原生监控平台 Prometheus 从部署到监控

1.监控系统架构设计 角色 节点 IP地址 监控端 Prometheus &#xff0c;Grafana&#xff0c;node_exporter &#xff0c;Nginx 47.120.35.251 被监控端1 node_exporter 47.113.177.189 被监控端2 mysqld_exporter&#xff0c;node_exporter&#xff0c;Nginx&#xff…

Centos7下载安装mysql

参考文档&#xff1a;https://xie.infoq.cn/article/5da9bfdfbdaabf7b0b982ab6e https://blog.csdn.net/Lance_welcome/article/details/107314575 一、下载mysql 5.7 # 下载mysql5.7.42版本 wget https://cdn.mysql.com//Downloads/MySQL-5.7/mysql-5.7.42-linux-glibc2.12-…

Mysql 表的七种连接方式【附带练习sql】

连接 七种JOIN介绍 图形连接方式说明SQL内连接共有部分SELECT <select_list> FROM TableA A INNER JOIN TableB B ON A.Key B.Key;左连接A表独有共有部分SELECT <select_list> FROM TableA A LEFT JOIN TableB B ON A.Key B.Key;右连接B表独有共有部分SELECT &…

字符设备驱动内部实现原理解析以及分步注册流程和代码示例

1、字符设备驱动内部实现原理解析 原理&#xff1a;用户层调用 open() 函数打开设备文件&#xff0c;用ls-i查看inode号并找到与之对应的struct inode 结构体。在struct inode 结构体中&#xff0c;可以找到与文件关联的 struct cdev 设备驱动结构体。设备驱动结构体中包含了文…

Spring6 数据校验 Validation

1、Spring Validation概述 在开发中&#xff0c;经常遇到参数校验的需求&#xff0c;比如用户注册的时候&#xff0c;要校验用户名不能为空、用户名长度不超过20个字符、手机号是合法的手机号格式等等。如果使用普通方式&#xff0c;会把校验的代码和真正的业务处理逻辑耦合在一…

FL Studio 21中文永久版网盘下载(含Key.reg注册表补丁)

FL Studio 21全称Fruity Loops Studio&#xff0c;就是大家熟悉的水果编曲软件&#xff0c;一个全能的音乐制作软件&#xff0c;包括编曲、录音、剪辑和混音等诸多功能&#xff0c;让你的电脑编程一个全能的录音室。FL Studio 21版本发布了&#xff0c;为我们带来了多种新功能&…

在Centos Stream 9上Docker的实操教程(八) - Docker可视化管理工具

&#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Micro麦可乐的博客 &#x1f425;《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程&#xff0c;入门到实战 &#x1f33a;《RabbitMQ》…