一、概述

这里主要是记录 ESP32 中进行 I2C 通行的基本程序,也可以说是 I2C 总线驱动程序,当然这里只是作为主模式,从模式我还没需要这个需求,以后有机会贴上。此笔记的主要目的是防止以后写 I2C 通信时,忘记代码而记录的,需要的小伙伴可以收藏一下。

  1. I2C 通信原理

    在了解程序之前需要先了解 I2C 的通信原理,这里我就不记录了,网上有很多大佬已经完成了这个事,下面是两位大佬的博客,可以参考一下。

    一文搞懂I2C通信:https://zhuanlan.zhihu.com/p/282949543

    一文搞懂I2C通信总线:https://blog.csdn.net/m0_38106923/article/details/123673285

  2. I2C总线仲裁机制

    通过了解 I2C 通信原理后,可以知道 I2C 支持 一主多从和多主多从 的通行方式,这里需要注意一下多主多从模式,需要了解一下 I2C 的仲裁机制,有需要的可以看下面两位大佬的博客。

    I2C总线仲裁机制:https://www.cnblogs.com/yuanqiangfei/p/15781416.html

    I2C总线仲裁机制:https://blog.csdn.net/lpwsw/article/details/121778724

  3. I2C 遵循3个机制

    • “线与”机制:

      多主机时,总线具有“线与”的逻辑功能,即只要有一个节点发送低电平时,总线上就表现为低电平。

    • SDA回读机制

      总线被启动后,多个主机在每发送一个数据位时都要对自己的输出电平进行检测,只要检测的电平与自己发出的电平一致,就会继续占用总线。

    • 低电平优先机制

      由于线与的存在,当多主机发送时,谁先发送低电平谁就会掌握对总线的控制权。

二、I2C程序分析

一般总线的驱动程序,多数是两个套路,首先初始化,然后完成读写功能即可。

  1. 初始化

    这里的初始化,主要是配置 I2C 的功能定义,见代码分析

    esp_err_t esp32_i2c_init()
    {
    // 初始化I2C配置
    i2c_config_t i2c_config=
    {
    .mode = I2C_MODE_MASTER, // 设置i2c模式
    .sda_io_num = I2c_SDA_IO, // 设置SDA引脚
    .scl_io_num = I2c_SCL_IO, // 设置SCL引脚
    .sda_pullup_en = GPIO_PULLUP_ENABLE, // 设置上拉使能
    .scl_pullup_en = GPIO_PULLUP_ENABLE, // 设置上拉使能
    .master.clk_speed = I2C_MASTER_FREQ_HZ, // 设置时钟频率400kbit
    };
    // 设置I2C
    i2c_param_config(I2c_NUM,&i2c_config);
    // 注册I2C服务及使能
    esp_err_t err = i2c_driver_install(I2c_NUM, i2c_config.mode, 0, 0, 0);
    return err;
    }
  2. I2C 的读取程序

    首先看看怎么读取一个字节数据

    static esp_err_t i2c_read8(uint8_t dev_addr, uint8_t reg_addr, uint8_t* read_data)
    {
    esp_err_t err = 0; /* 写地址 */
    i2c_cmd_handle_t cmd = i2c_cmd_link_create(); // 新建操作I2C句柄
    i2c_master_start(cmd); // 启动I2C
    i2c_master_write_byte(cmd, ( TCS34725_address << 1 ) | I2C_MASTER_WRITE, ACK_CHECK_EN); // 发送地址+写+检查ack
    i2c_master_write_byte(cmd, reg_addr, ACK_CHECK_EN); // 发送ID寄存器地址
    i2c_master_stop(cmd); // 关闭发送I2C
    err = i2c_master_cmd_begin(I2c_NUM, cmd, 1000 / portTICK_RATE_MS);
    i2c_cmd_link_delete(cmd); /* 读取数据 */
    cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, ( dev_addr << 1 ) | I2C_MASTER_READ, ACK_CHECK_EN);
    i2c_master_read_byte(cmd, read_data, NACK_VAL);
    i2c_master_stop(cmd);
    err = i2c_master_cmd_begin(I2c_NUM, cmd, 1000 / portTICK_RATE_MS);
    i2c_cmd_link_delete(cmd);
    return err;
    }

    读取两个字节数据

    static esp_err_t i2c_read16(uint8_t dev_addr, uint8_t reg_addr, uint16_t* read_data)
    {
    esp_err_t err = 0;
    uint8_t read_data_h, read_data_l;
    /* 写地址 */
    i2c_cmd_handle_t cmd = i2c_cmd_link_create(); // 新建操作I2C句柄
    i2c_master_start(cmd); // 启动I2C
    i2c_master_write_byte(cmd, ( TCS34725_address << 1 ) | I2C_MASTER_WRITE, ACK_CHECK_EN); // 发送地址+写+检查ack
    i2c_master_write_byte(cmd, reg_addr, ACK_CHECK_EN); // 发送ID寄存器地址
    i2c_master_stop(cmd); // 关闭发送I2C
    err = i2c_master_cmd_begin(I2c_NUM, cmd, 1000 / portTICK_RATE_MS);
    i2c_cmd_link_delete(cmd); /* 读取数据 */
    cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, ( dev_addr << 1 ) | I2C_MASTER_READ, ACK_CHECK_EN);
    i2c_master_read_byte(cmd, &read_data_l, ACK_VAL);
    i2c_master_read_byte(cmd, &read_data_h, NACK_VAL);
    i2c_master_stop(cmd);
    err = i2c_master_cmd_begin(I2c_NUM, cmd, 1000 / portTICK_RATE_MS);
    i2c_cmd_link_delete(cmd);
    *read_data = read_data_h << 8 | read_data_l;
    return err;
    }

    读取多个字节

    是不是发现像上面这样读取比较麻烦,不利于代码的复用,下面是一个读取函数搞定 I2C 的读取需求

    static esp_err_t i2c_read(uint8_t dev_addr, uint8_t* data_rd, size_t size)
    {
    esp_err_t err;
    if (size == 0) {
    return ESP_OK;
    }
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, ( dev_addr << 1 ) | I2C_MASTER_READ, ACK_CHECK_EN);
    if (size > 1) {
    i2c_master_read(cmd, data_rd, size - 1, ACK_VAL);
    }
    i2c_master_read_byte(cmd, data_rd + size - 1, NACK_VAL);
    i2c_master_stop(cmd);
    /* I2C 发送函数 */
    err = i2c_master_cmd_begin(I2c_NUM, cmd, 1000 / portTICK_RATE_MS);
    i2c_cmd_link_delete(cmd);
    return err;
    }
  3. I2C的写程序

    写一个字节数据

    static esp_err_t i2c_write8(uint8_t dev_addr, uint8_t reg_addr, uint8_t write_data)
    {
    esp_err_t err = 0;
    i2c_cmd_handle_t cmd = i2c_cmd_link_create(); // 新建操作I2C句柄
    i2c_master_start(cmd); // 启动I2C
    i2c_master_write_byte(cmd, ( dev_addr << 1 ) | I2C_MASTER_WRITE, ACK_CHECK_EN); // 发送地址+写+检查ack
    i2c_master_write_byte(cmd, reg_addr, ACK_CHECK_EN); // 发送ID寄存器地址
    i2c_master_write_byte(cmd, write_data, ACK_CHECK_EN); // 将数据写入寄存器中
    i2c_master_stop(cmd); // 关闭发送I2C
    err = i2c_master_cmd_begin(I2c_NUM, cmd, 1000 / portTICK_RATE_MS);
    i2c_cmd_link_delete(cmd); // 删除句柄
    return err;
    }

    写多个字节

    static esp_err_t i2c_write(uint8_t dev_addr, uint8_t* write_data, size_t size)
    {
    esp_err_t err;
    i2c_cmd_handle_t cmd = i2c_cmd_link_create(); // 新建操作I2C句柄
    i2c_master_start(cmd); // 启动I2C
    i2c_master_write_byte(cmd, ( dev_addr << 1 ) | I2C_MASTER_WRITE, ACK_CHECK_EN); // 发送地址+写+检查ack
    i2c_master_write(cmd, write_data, size, ACK_CHECK_EN); // 发送ID寄存器地址
    i2c_master_stop(cmd); // 关闭发送I2C
    err = i2c_master_cmd_begin(I2c_NUM, cmd, 100/portTICK_PERIOD_MS); // I2C 发送函数
    i2c_cmd_link_delete(cmd); // 删除句柄
    return err;
    }

三、程序源码

头文件

/**
* @file esp32_i2c_drive.h
*
*/ #ifndef _ESP32_I2C_DRIVE_H_
#define _ESP32_I2C_DRIVE_H_ #ifdef __cplusplus
extern "C" {
#endif /*********************
* INCLUDES
*********************/
#include "driver/i2c.h" /*********************
* DEFINES
*********************/
#define CODE_SIMPLIFY // I2C 驱动代码精简模式 // I2C配置
#define I2c_SDA_IO (21)
#define I2c_SCL_IO (22)
#define I2c_NUM (I2C_NUM_0)
#define I2C_MASTER_FREQ_HZ (400000)
#define ACK_CHECK_EN (0x1) // 主机检查从机的ACK
#define ACK_CHECK_DIS (0x0)
#define ACK_VAL (0x0) // 主机读取时的应答信号
#define NACK_VAL (0x1) /**********************
* TYPEDEFS
**********************/ /**********************
* GLOBAL PROTOTYPES
**********************/
esp_err_t esp32_i2c_init();
esp_err_t esp32_i2c_write8(uint8_t dev_addr, uint8_t reg_addr, uint8_t write_data);
esp_err_t esp32_i2c_read8(uint8_t dev_addr, uint8_t reg_addr, uint8_t* read_data);
esp_err_t esp32_i2c_read16(uint8_t dev_addr, uint8_t reg_addr, uint16_t* read_data); // esp_err_t esp32_i2c_write(uint8_t* write_data, size_t size);
// esp_err_t esp32_i2c_read(uint8_t* data_rd, size_t size); /**********************
* MACROS
**********************/ #ifdef __cplusplus
} /* extern "C" */
#endif #endif /* _ESP32_I2C_DRIVE_H_ */

源文件

#include "esp32_i2c_drive.h"
#include "TCS34725.h" /***************************************************************
文件名 : esp32_i2c_drive.c
作者 : jiaozhu
版本 : V1.0
描述 : esp32_i2c_drive esp32的i2c驱动文件。
其他 : 无
日志 : 初版 V1.0 2022/12/30
***************************************************************/ /**
* @brief 初始化I2C
*
* @return esp_err_t 初始化成功返回 0
*/
esp_err_t esp32_i2c_init()
{
// 初始化I2C配置
i2c_config_t i2c_config=
{
.mode = I2C_MODE_MASTER, // 设置i2c模式
.sda_io_num = I2c_SDA_IO, // 设置SDA引脚
.scl_io_num = I2c_SCL_IO, // 设置SCL引脚
.sda_pullup_en = GPIO_PULLUP_ENABLE, // 设置上拉使能
.scl_pullup_en = GPIO_PULLUP_ENABLE, // 设置上拉使能
.master.clk_speed = I2C_MASTER_FREQ_HZ, // 设置时钟频率400kbit
};
// 设置I2C
i2c_param_config(I2c_NUM,&i2c_config);
// 注册I2C服务及使能
esp_err_t err = i2c_driver_install(I2c_NUM, i2c_config.mode, 0, 0, 0);
return err;
} #if defined(CODE_SIMPLIFY)
/**
* @brief 通过I2C写入多个数据
*
* @param write_data 数据地址,类型为 [寄存器地址] [数据地址] [寄存器地址] [数据地址] ......
* @param size 写入的数据长度
* @return esp_err_t 操作成功返回 0
*/
static esp_err_t i2c_write(uint8_t dev_addr, uint8_t* write_data, size_t size)
{
esp_err_t err;
i2c_cmd_handle_t cmd = i2c_cmd_link_create(); // 新建操作I2C句柄
i2c_master_start(cmd); // 启动I2C
i2c_master_write_byte(cmd, ( dev_addr << 1 ) | I2C_MASTER_WRITE, ACK_CHECK_EN); // 发送地址+写+检查ack
i2c_master_write(cmd, write_data, size, ACK_CHECK_EN); // 发送ID寄存器地址
i2c_master_stop(cmd); // 关闭发送I2C
err = i2c_master_cmd_begin(I2c_NUM, cmd, 100/portTICK_PERIOD_MS); // I2C 发送函数
i2c_cmd_link_delete(cmd); // 删除句柄
return err;
} /**
* @brief 通过I2C读取设备数据,但是需要配合 i2c_write 使用才能读取
*
* @param data_rd 数据存储地址
* @param size 需要读取的数据长度
* @return esp_err_t 操作成功返回 0
*/
static esp_err_t i2c_read(uint8_t dev_addr, uint8_t* data_rd, size_t size)
{
esp_err_t err;
if (size == 0) {
return ESP_OK;
}
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, ( dev_addr << 1 ) | I2C_MASTER_READ, ACK_CHECK_EN);
if (size > 1) {
i2c_master_read(cmd, data_rd, size - 1, ACK_VAL);
}
i2c_master_read_byte(cmd, data_rd + size - 1, NACK_VAL);
i2c_master_stop(cmd);
/* I2C 发送函数 */
err = i2c_master_cmd_begin(I2c_NUM, cmd, 1000 / portTICK_RATE_MS);
i2c_cmd_link_delete(cmd);
return err;
} #else
/**
* @brief 往 I2C 中写入一个8位的数据
*
* @param dev_addr 设备地址
* @param reg_addr 寄存机地址
* @param write_data 需要写入的数据
* @return esp_err_t 无错误时返回 0
*/
static esp_err_t i2c_write8(uint8_t dev_addr, uint8_t reg_addr, uint8_t write_data)
{
esp_err_t err = 0;
i2c_cmd_handle_t cmd = i2c_cmd_link_create(); // 新建操作I2C句柄
i2c_master_start(cmd); // 启动I2C
i2c_master_write_byte(cmd, ( dev_addr << 1 ) | I2C_MASTER_WRITE, ACK_CHECK_EN); // 发送地址+写+检查ack
i2c_master_write_byte(cmd, reg_addr, ACK_CHECK_EN); // 发送ID寄存器地址
i2c_master_write_byte(cmd, write_data, ACK_CHECK_EN); // 将数据写入寄存器中
i2c_master_stop(cmd); // 关闭发送I2C
err = i2c_master_cmd_begin(I2c_NUM, cmd, 1000 / portTICK_RATE_MS);
i2c_cmd_link_delete(cmd); // 删除句柄
return err;
} /**
* @brief 读取一个8位的数据
*
* @param dev_addr 设备地址
* @param reg_addr 寄存器地址
* @param read_data 数据存储地址
* @return esp_err_t 无错误时返回 0
*/
static esp_err_t i2c_read8(uint8_t dev_addr, uint8_t reg_addr, uint8_t* read_data)
{
esp_err_t err = 0; /* 写地址 */
i2c_cmd_handle_t cmd = i2c_cmd_link_create(); // 新建操作I2C句柄
i2c_master_start(cmd); // 启动I2C
i2c_master_write_byte(cmd, ( TCS34725_address << 1 ) | I2C_MASTER_WRITE, ACK_CHECK_EN); // 发送地址+写+检查ack
i2c_master_write_byte(cmd, reg_addr, ACK_CHECK_EN); // 发送ID寄存器地址
i2c_master_stop(cmd); // 关闭发送I2C
err = i2c_master_cmd_begin(I2c_NUM, cmd, 1000 / portTICK_RATE_MS);
i2c_cmd_link_delete(cmd); /* 读取数据 */
cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, ( dev_addr << 1 ) | I2C_MASTER_READ, ACK_CHECK_EN);
i2c_master_read_byte(cmd, read_data, NACK_VAL);
i2c_master_stop(cmd);
err = i2c_master_cmd_begin(I2c_NUM, cmd, 1000 / portTICK_RATE_MS);
i2c_cmd_link_delete(cmd);
return err;
} /**
* @brief 读取一个16位的数据
*
* @param dev_addr 设备地址
* @param reg_addr 寄存器地址
* @param read_data 数据存储地址
* @return esp_err_t 无错误时返回 0
*/
static esp_err_t i2c_read16(uint8_t dev_addr, uint8_t reg_addr, uint16_t* read_data)
{
esp_err_t err = 0;
uint8_t read_data_h, read_data_l;
/* 写地址 */
i2c_cmd_handle_t cmd = i2c_cmd_link_create(); // 新建操作I2C句柄
i2c_master_start(cmd); // 启动I2C
i2c_master_write_byte(cmd, ( TCS34725_address << 1 ) | I2C_MASTER_WRITE, ACK_CHECK_EN); // 发送地址+写+检查ack
i2c_master_write_byte(cmd, reg_addr, ACK_CHECK_EN); // 发送ID寄存器地址
i2c_master_stop(cmd); // 关闭发送I2C
err = i2c_master_cmd_begin(I2c_NUM, cmd, 1000 / portTICK_RATE_MS);
i2c_cmd_link_delete(cmd); /* 读取数据 */
cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, ( dev_addr << 1 ) | I2C_MASTER_READ, ACK_CHECK_EN);
i2c_master_read_byte(cmd, &read_data_l, ACK_VAL);
i2c_master_read_byte(cmd, &read_data_h, NACK_VAL);
i2c_master_stop(cmd);
err = i2c_master_cmd_begin(I2c_NUM, cmd, 1000 / portTICK_RATE_MS);
i2c_cmd_link_delete(cmd);
*read_data = read_data_h << 8 | read_data_l;
return err;
}
#endif esp_err_t esp32_i2c_write8(uint8_t dev_addr, uint8_t reg_addr, uint8_t write_data)
{
#if defined(CODE_SIMPLIFY)
uint8_t write_buf[2];
write_buf[0] = reg_addr;
write_buf[1] = write_data;
return i2c_write(dev_addr, write_buf, 2);
#else
return i2c_write8(dev_addr, reg_addr, write_data);
#endif
} esp_err_t esp32_i2c_read8(uint8_t dev_addr, uint8_t reg_addr, uint8_t* read_data)
{
#if defined(CODE_SIMPLIFY)
esp_err_t err = 0;
err = i2c_write(dev_addr, &reg_addr, 1);
err = i2c_read(dev_addr, read_data, 1);
return err;
#else
return i2c_read8(dev_addr, reg_addr, read_data);
#endif
} esp_err_t esp32_i2c_read16(uint8_t dev_addr, uint8_t reg_addr, uint16_t* read_data)
{
#if defined(CODE_SIMPLIFY)
uint8_t buf[2];
esp_err_t err = 0;
err = i2c_write(dev_addr, &reg_addr, 1);
err = i2c_read(dev_addr, buf, 2);
*read_data = buf[1] << 8 | buf[0];
return err;
#else
return i2c_read16(dev_addr, reg_addr, read_data);
#endif
}

注意:到此笔记已经结束了,上面程序各位可以直接拿去使用,但是拒绝商用,出现任何问题概不负责。

参考链接

一文搞懂I2C通信:https://zhuanlan.zhihu.com/p/282949543

一文搞懂I2C通信总线:https://blog.csdn.net/m0_38106923/article/details/123673285

I2C总线仲裁机制:https://www.cnblogs.com/yuanqiangfei/p/15781416.html

I2C总线仲裁机制:https://blog.csdn.net/lpwsw/article/details/121778724

ESP32 I2C 总线主模式通信程序的更多相关文章

  1. STM32F4XX中断方式通过IO模拟I2C总线Master模式

    STM32的I2C硬核为了规避NXP的知识产权,使得I2C用起来经常出问题,因此ST公司推出了CPAL库,CPAL库在中断方式工作下仅支持无子地址 的器件,无法做到中断方式完成读写大部分I2C器件.同 ...

  2. I2C总线完全版——I2C总线的结构、工作时序与模拟编程

    I2C总线的结构.工作时序与模拟编程 I2C总线的结构.工作时序与模拟编程I2C总线(Inter Integrated Circuit)是飞利浦公司于上个世纪80年代开发的一种"电路板级&q ...

  3. I2C总线信号时序总结

    I2C总线信号时序总结 总线空闲状态  I2C总线总线的SDA和SCL两条信号线同时处于高电平时,规定为总线的空闲状态.此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电 ...

  4. 总线接口与计算机通信(一)I2C总线

    1.  I2C总线的基本概念    1)发送器(Transmitter):发送数据到总线的器件    2)接收器(Receiver):从总线接收数据的器件    3)主机(Master):初始化发送. ...

  5. I2C总线信号时序总结【转】

    本文转载自:https://i.cnblogs.com/EditPosts.aspx?opt=1 I2C总线信号时序总结 总线空闲状态  I2C总线总线的SDA和SCL两条信号线同时处于高电平时,规定 ...

  6. 浅谈I2C总线

    I2C总线概述 I2C(Inter-Integrated Circuit)总线是一种由PHILIPS公司在80年代开发的两线式串行总线,用于连接微控制器及其外围设备.I2C总线最主要的优点是其简单性和 ...

  7. I2C总线协议的软件模拟实现方法

    I2C总线协议的软件模拟实现方法 在上一篇博客中已经讲过I2C总线通信协议,本文讲述I2C总线协议的软件模拟实现方法. 1. 简述 所谓的I2C总线协议的软件模拟实现方法,就是用软件控制GPIO的输入 ...

  8. Linux+I2C总线分析(主要是probe的方式)

    Linux I2C 总线浅析 ㈠ Overview Linux的I2C体系结构分为3个组成部分: ·I2C核心: I2C核心提供了I2C总线驱动和设备驱动的注册.注销方法,I2C通信方法(即“algo ...

  9. I2C总线通信

    UART 属于异步通信,比如电脑发送给单片机,电脑只负责把数据通过TXD 发送出来即可,接收数据是单片机自己的事情.而 I2C 属于同步通信, SCL 时钟线负责收发双方的时钟节拍, SDA 数据线负 ...

  10. Linux驱动之I2C总线设备以及驱动

    [ 导读] 本文通过阅读内核代码,来梳理一下I2C子系统的整体视图.在开发I2C设备驱动程序时,往往缺乏对于系统整体的认识,导致没有一个清晰的思路.所以从高层级来分析一下I2C系统的设计思路,将有助于 ...

随机推荐

  1. 记录--react native 封装人脸 检测、美颜组件

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 本组件目前只能用在React Native 的iOS端 本组件来之实际中的开发需求:可以检测并且标记人脸,实现基本的美颜,可进行拍照.换行 ...

  2. 快速上手系列:Oracle

    一 简介 1.为何需要数据库?存储大量数据,方便检索和访问. 2.文件组成: 数据文件:扩展名是.DBF,用于存储数据库数据的文件,数据库表和数据文件不存在一对一对应关系 控制文件:扩展名是.CTL, ...

  3. elasticsearch使用painless的一些简单例子

    目录 1.背景 2.准备数据 2.1 mapping 2.2 插入数据 3.例子 3.1 (update)更新文档 id=1 的文档,将 age 加 2岁 3.2 (update_by_query)如 ...

  4. Oracle与MySQL的差异和对比

    Oracle与MySQL的差异和对比:配套hands-on参考脚本. 方便客户针对培训课件内容进行动手实践,加强理解. --------------------------------- -- 主题: ...

  5. verilog之display

    verilog之display 1.函数简介 $display是用于显示不同格式的变量的函数,用于测试过程中观察数据数据的特点.该观测不如波形图直观,但是如果可以详细的设置好观测点,有时可以达到事半功 ...

  6. verilog之原语设计

    verilog之原语设计 1.原语作用 在一般的verilog设计中,一般采用数字逻辑设计,由软件将数字逻辑转化为特定的数字电路.但是,对于某些特殊的领域,有可能需要用户直接自定义数字电路以达到对指定 ...

  7. Spring Cloud服务之Nacos作为注册中心与配置中心

    1.创建maven父工程管理jar包版本 创建maven骨架,删除多余部分文件.只留pom文件,添加依赖 <packaging>pom</packaging> <pare ...

  8. 详解数仓对象设计中序列SEQUENCE原理与应用

    本文分享自华为云社区<GaussDB(DWS)对象设计之序列SEQUENCE原理与使用方法介绍>,作者:VV一笑. 1. 前言 适用版本:8.2.1及以上版本 序列SEQUENCE用来生成 ...

  9. 5 CSS伪类选择器

    5 伪类选择器 anchor伪类:专用于控制链接的显示效果 More Actions:link a:link 选择所有未被访问的链接. :visited a:visited 选择所有已被访问的链接. ...

  10. 深入理解 C# 编程:枚举、文件处理、异常处理和数字相加

    C# 枚举 枚举是一个特殊的"类",表示一组常量(不可更改/只读变量). 要创建枚举,请使用 enum 关键字(而不是 class 或 interface),并用逗号分隔枚举项: ...