龙芯1c库是把龙芯1c的常用外设的常用功能封装为一个库,类似于STM32库。Git地址:https://gitee.com/caogos/OpenLoongsonLib1c

I2C接口是常用的接口之一,很多传感器都是使用I2C接口,本文使用普通GPIO模拟I2C,实现与温湿度传感器AM2320正常通信。 先展示如何使用模拟I2C接口,然后再来看看怎么封装这些接口的。

龙芯1c库中模拟I2C接口使用示例

模拟I2C接口简介

先来看看封装了那些接口,如下

// 模拟i2c的接口信息
typedef struct
{unsigned int scl_gpio;          // SCL所在gpio引脚unsigned int sda_gpio;          // SDA所在gpio引脚int delay_time;                 // 周期的1/2,单位us
}simulate_i2c_t;/** 模拟i2c初始化* @i2c_info i2c的接口信息*/
void simulate_i2c_init(simulate_i2c_t *i2c_info);/** 模拟I2C的开始* @i2c_info i2c接口信息*/
void simulate_i2c_start(simulate_i2c_t *i2c_info);/** 模拟I2C的停止* @i2c_info i2c接口信息*/
void simulate_i2c_stop(simulate_i2c_t *i2c_info);/** 给从设备发送一个ack应答信号* @i2c_info i2c接口信息*/
void simulate_i2c_send_ack(simulate_i2c_t *i2c_info);/** 给从设备发送一个no ack非应答信号* @i2c_info i2c接口信息*/
void simulate_i2c_send_no_ack(simulate_i2c_t *i2c_info);/** 读取从设备的ack应答信号* @i2c_info i2c接口信息* @ret 读取到的信号。0表示应答,1表示非应答*/
unsigned int simulate_i2c_read_ack(simulate_i2c_t *i2c_info);/** 主设备从从设备那里读取一个8bit数据* @i2c_info i2c接口信息* @ret 读取的数据*/
unsigned char simulate_i2c_read_byte(simulate_i2c_t *i2c_info);/** 主设备写8bit数据到从设备* @i2c_info i2c接口信息* @data 待写数据*/
void simulate_i2c_write_byte(simulate_i2c_t *i2c_info, unsigned char data);

系统初始化时,首先调用simulate_i2c_init()对gpio初始化,然后调用simulate_i2c_start()发送I2C的开始信号,发送开始信号之后,一般需要调用simulate_i2c_write_byte()发送I2C子设备地址,然后才是调用simulate_i2c_write_byte()或simulate_i2c_read_byte()收发数据,每一个字节的数据后面都有一个ACK或NACK信号(发送地址时也有ack信号),根据情况调用simulate_i2c_send_ack(),simulate_i2c_send_no_ack()或者simulate_i2c_read_ack(),最后调用simulate_i2c_stop()发送停止信号,结束一次I2C通信过程。
为啥每个函数都有个相同的入参“simulate_i2c_t *i2c_info”呢?目的是告诉每个函数模拟I2C的GPIO引脚和I2C时钟周期,这样可以实现一个程序中同时支持多个不同的模拟I2C接口,接多个传感器。理论上GPIO越多,同时支持模拟I2C的个数就越多。
本文通过温湿度传感器AM2320来测试模拟的I2C接口,可以根据需要随意选取普通GPIO连接AM2320的SCL和SDA引脚。只是需要注意所选引脚是否接有其它元器件。

用温湿度传感器AM2320测试模拟I2C接口

实物图

温湿度传感器AM2320占用的引脚
VDD ------------------ 3.3V
GND ------------------ GND
SCL ------------------ GPIO57
SDA ------------------ GPIO56
这里是用GPIO模拟的I2C,理论上所有GPIO都可以用作SCL和SDA。另外AM2320的芯片手册中推荐SCL和SDA接上拉电阻,实测不接也是可以的。

代码清单

main.c

#include "../lib/public.h"
#include "../lib/gpio.h"
#include "../lib/delay.h"
#include "../example/test_gpio.h"
#include "../example/test_pwm.h"
#include "../example/test_delay.h"
#include "../example/test_simulate_i2c.h"// pmon提供的打印接口
struct callvectors *callvec;int main(int argc, char **argv, char **env, struct callvectors *cv)
{callvec = cv;// -------------------------测试gpio----------------------/** 测试库中gpio作为输出时的相关接口* led闪烁10次*/
//    test_gpio_output();/** 测试库中gpio作为输入时的相关接口* 按键按下时,指示灯点亮,否则,熄灭*/
//    test_gpio_input();// ------------------------测试PWM--------------------------------    // 测试硬件pwm产生连续的pwm波形
//    test_pwm_normal();// 测试硬件pwm产生pwm脉冲
//    test_pwm_pulse();/** 测试gpio04复用为pwm,gpio06作为普通gpio使用* PWM0的默认引脚位GPIO06,但也可以复用为GPIO04* 当gpio06还是保持默认为pwm时,复用gpio04为pwm0,那么会同时在两个引脚输出相同的pwm波形* 本函数旨在证明可以在gpio04复用为pwm0时,还可以将(默认作为pwm0的)gpio06作为普通gpio使用*/
//    test_pwm_gpio04_gpio06();// 测试pwm最大周期
//    test_pwm_max_period();// ------------------------测试软件延时--------------------------------  // 测试延时函数delay_1ms()
//    test_delay_1ms();// 测试延时函数delay_1us()
//    test_delay_1us();// 测试延时函数delay_1s()
//    test_delay_1s();// ------------------------测试模拟I2C------------------------------  test_simulate_i2c_am2320();return(0);
}

test_simulate_i2c.c

// 测试模拟i2c#include "../lib/public.h"
#include "../lib/delay.h"
#include "../lib/simulate_i2c.h"// 接收缓存大小
#define RECV_BUFF_SIZE                (8)
// 读取的消息中每字节的含义
enum
{AM2320_RSP_FUNC_ID = 0,             // 功能码AM2320_RSP_LEN,                     // 数据长度AM2320_RSP_HUMI_HIGH,               // 湿度高位AM2320_RSP_HUMI_LOW,                // 湿度低位AM2320_RSP_TEMP_HIGH,               // 温度高位AM2320_RSP_TEMP_LOW,                // 温度低位AM2320_RSP_CRC_LOW,                 // CRC低位AM2320_RSP_CRC_HIGH,                // CRC高位
};// 温湿度传感器AM2320的引脚接线信息
simulate_i2c_t am2320_info = { 57, 56, 10 };        // SCL=gpio57, SDA=gpio56, delay_time=10us// 温湿度传感器AM2320初始化
void am2320_init(void)
{// 模拟i2c初始化simulate_i2c_init(&am2320_info);return ;
}/** 计算crc* @ptr 待计算crc的数据的首地址* @len 数据长度*/
unsigned short am2320_crc16(unsigned char *ptr, unsigned char len)
{unsigned short crc = 0xFFFF;unsigned char i;while (len--){crc ^= *ptr++;for (i=0; i<8; i++){if (crc & 0x01){crc >>= 1;crc ^= 0xA001;}else{crc >>= 1;}}}return crc;
}// 从AM2320读取温湿度信息
void am2320_get_temp_humi(void)
{const unsigned char addr = 0xB8; unsigned char recv_buff[RECV_BUFF_SIZE] = {0};unsigned short recved_crc, calced_crc;int temp, humi;int i;// 唤醒AM2320simulate_i2c_start(&am2320_info);simulate_i2c_write_byte(&am2320_info, addr);simulate_i2c_read_ack(&am2320_info);delay_ms(1);simulate_i2c_stop(&am2320_info);// 发送读指令simulate_i2c_start(&am2320_info);simulate_i2c_write_byte(&am2320_info, addr);simulate_i2c_read_ack(&am2320_info);simulate_i2c_write_byte(&am2320_info, 0x03);simulate_i2c_read_ack(&am2320_info);simulate_i2c_write_byte(&am2320_info, 0x00);simulate_i2c_read_ack(&am2320_info);simulate_i2c_write_byte(&am2320_info, 0x04);simulate_i2c_read_ack(&am2320_info);simulate_i2c_stop(&am2320_info);// 读回数据delay_ms(2);simulate_i2c_start(&am2320_info);simulate_i2c_write_byte(&am2320_info, addr | 0x01);simulate_i2c_read_ack(&am2320_info);delay_us(50);for (i=0; i<RECV_BUFF_SIZE; i++){recv_buff[i] = simulate_i2c_read_byte(&am2320_info);simulate_i2c_send_ack(&am2320_info);}simulate_i2c_stop(&am2320_info);recved_crc = (recv_buff[AM2320_RSP_CRC_HIGH] << 8) + recv_buff[AM2320_RSP_CRC_LOW];calced_crc = am2320_crc16(recv_buff, 6);if (recved_crc != calced_crc){myprintf("[%s] crc error! recved_crc=0x%x, calced_crc=0x%x\n", __FUNCTION__, recved_crc, calced_crc);myprintf("[%s] recved data: func_id=%d, len=%d, humi[1]=0x%x, humi[0]=0x%x, temp[1]=0x%x, temp[0]=0x%x, crc[0]=0x%x, crc[1]=0x%x\n",__FUNCTION__, recv_buff[AM2320_RSP_FUNC_ID],recv_buff[AM2320_RSP_LEN],recv_buff[AM2320_RSP_HUMI_HIGH],recv_buff[AM2320_RSP_HUMI_LOW],recv_buff[AM2320_RSP_TEMP_HIGH],recv_buff[AM2320_RSP_TEMP_LOW],recv_buff[AM2320_RSP_CRC_LOW],recv_buff[AM2320_RSP_CRC_HIGH]);return ;}  humi = (recv_buff[AM2320_RSP_HUMI_HIGH] * 0xff + recv_buff[AM2320_RSP_HUMI_LOW]) / 10;temp = (recv_buff[AM2320_RSP_TEMP_HIGH] * 0xff + recv_buff[AM2320_RSP_TEMP_LOW]) / 10;myprintf("[%s] humi=%d, temp=%d\n", __FUNCTION__, humi, temp);return ;
}// 用模拟i2c接口与温湿度传感器AM2320通信,读取温湿度信息
void test_simulate_i2c_am2320(void)
{// 温湿度传感器AM2320初始化am2320_init();while (1){// 从AM2320读取温湿度信息am2320_get_temp_humi();// 等待3sdelay_s(3);}
}

test_simulate_i2c.h

// 测试模拟i2c#ifndef __OPENLOONGSON_TEST_SIMULATE_I2C_H
#define __OPENLOONGSON_TEST_SIMULATE_I2C_H// 用模拟i2c接口与温湿度传感器AM2320通信,读取温湿度信息
void test_simulate_i2c_am2320(void);#endif

代码很简单,调用函数test_simulate_i2c_am2320()读取一次温湿度信息,间隔3s读一次。读取一次温湿度信息需要先唤醒AM2320,然后发送读指令,最后才读温湿度数据。

测试效果

这是获取一次温湿度信息的时序图。首先是唤醒AM2320,延时,再发送读温湿度的命令,延时,最后才是读取温湿度值。把波形放大后,如下

唤醒AM2320的时序,开始信号后,发送地址0xB8,然后延时1ms,再发结束信号

读命令的时序。开始信号,发地址0xB8,发发功能码0x3,发起始地址0x0,发寄存器长度0x4,结束信号

把读取温湿度信息的时序分为两个图,这样看得更清楚一些。

时序为:开始信号,发送地址,延时50us后,读取8字节的数据(其中包括温度,湿度和CRC),最后发送结束信号。

最后来看下串口打印信息

封装模拟I2C接口

接口要点

模拟I2C其实不难,就是根据I2C协议的时序,将普通GPIO拉低拉高,然后延时,再拉低拉高,再延时,再拉低拉高……
程序写好后,上电运行,对照芯片手册的时序图,接上示波器一看就知道对不对,哪里延时时间长了,哪里又短了,找着芯片手册中的时序图修改,直到满意为止。
只是在使用这些接口时,要仔细看芯片手册,改读I2C时,不要写I2C。假设发送地址后,需要连续读n个数据,如果在发生地址后,多写了一个数据后,再读I2C时,可能读到的是全0xff,并且没有ack。那是因为通信的对方已经放弃本次I2C通信,I2C变成空闲状态(SCL=1,SDA=1),所以读到的是0xff,并且没有ack。

代码清单

simulate_i2c.c

// 模拟i2c的源文件#include "public.h"
#include "gpio.h"
#include "delay.h"
#include "simulate_i2c.h"/** 配置SCL所在gpio引脚为输出模式* @i2c_info i2c接口信息*/
void simulate_i2c_config_scl_out(simulate_i2c_t *i2c_info)
{gpio_init(i2c_info->scl_gpio, gpio_mode_output);return ;
}/** 配置SDA所在gpio引脚为输出模式* @i2c_info i2c接口信息*/
void simulate_i2c_config_sda_out(simulate_i2c_t *i2c_info)
{gpio_init(i2c_info->sda_gpio, gpio_mode_output);return ;
}/** 配置SDA所在gpio引脚为输入模式* @i2c_info i2c接口信息*/
void simulate_i2c_config_sda_in(simulate_i2c_t *i2c_info)
{gpio_init(i2c_info->sda_gpio, gpio_mode_input);return ;
}/** SCL引脚输出高电平* @i2c_info i2c接口信息*/
void simulate_i2c_scl_out_high(simulate_i2c_t *i2c_info)
{gpio_set(i2c_info->scl_gpio, gpio_level_high);return ;
}/** SCL引脚输出低电平* @i2c_info i2c接口信息*/
void simulate_i2c_scl_out_low(simulate_i2c_t *i2c_info)
{gpio_set(i2c_info->scl_gpio, gpio_level_low);return ;
}/** SDA引脚输出高电平* @i2c_info i2c接口信息*/
void simulate_i2c_sda_out_high(simulate_i2c_t *i2c_info)
{gpio_set(i2c_info->sda_gpio, gpio_level_high);return ;
}/** SDA引脚输出低电平* @i2c_info i2c接口信息*/
void simulate_i2c_sda_out_low(simulate_i2c_t *i2c_info)
{gpio_set(i2c_info->sda_gpio, gpio_level_low);return ;
}/** 读取SDA引脚* @i2c_info i2c接口信息* @ret SDA引脚的电平值*/
unsigned int simulate_i2c_sda_in(simulate_i2c_t *i2c_info)
{return gpio_get(i2c_info->sda_gpio);
}/** 模拟i2c初始化* @i2c_info i2c的接口信息*/
void simulate_i2c_init(simulate_i2c_t *i2c_info)
{// SCL输出高电平simulate_i2c_config_scl_out(i2c_info);simulate_i2c_scl_out_high(i2c_info);return ;
}/** 模拟I2C的开始* @i2c_info i2c接口信息*/
void simulate_i2c_start(simulate_i2c_t *i2c_info)
{// SDA输出模式simulate_i2c_config_sda_out(i2c_info);// 这里可能需要一个stopsimulate_i2c_scl_out_high(i2c_info);delay_us(i2c_info->delay_time);simulate_i2c_sda_out_high(i2c_info);delay_us(2 * i2c_info->delay_time);// startsimulate_i2c_sda_out_low(i2c_info);delay_us(i2c_info->delay_time);simulate_i2c_scl_out_low(i2c_info);delay_us(i2c_info->delay_time);return ;
}/** 模拟I2C的停止* @i2c_info i2c接口信息*/
void simulate_i2c_stop(simulate_i2c_t *i2c_info)
{// SDA输出模式simulate_i2c_config_sda_out(i2c_info);// 先把SCL和SDA拉低simulate_i2c_scl_out_low(i2c_info);delay_us(i2c_info->delay_time);simulate_i2c_sda_out_low(i2c_info);delay_us(i2c_info->delay_time);// stopsimulate_i2c_scl_out_high(i2c_info);delay_us(i2c_info->delay_time);simulate_i2c_sda_out_high(i2c_info);delay_us(2 * i2c_info->delay_time);return ;
}/** 给从设备发送一个ack应答信号* @i2c_info i2c接口信息*/
void simulate_i2c_send_ack(simulate_i2c_t *i2c_info)
{// SDA输出模式simulate_i2c_config_sda_out(i2c_info);// SDA=0simulate_i2c_sda_out_low(i2c_info);delay_us(i2c_info->delay_time);// SCL发送一个脉冲simulate_i2c_scl_out_high(i2c_info);delay_us(i2c_info->delay_time);simulate_i2c_scl_out_low(i2c_info);delay_us(i2c_info->delay_time);return ;
}/** 给从设备发送一个no ack非应答信号* @i2c_info i2c接口信息*/
void simulate_i2c_send_no_ack(simulate_i2c_t *i2c_info)
{// SDA输出模式simulate_i2c_config_sda_out(i2c_info);// SDA=1simulate_i2c_sda_out_high(i2c_info);delay_us(i2c_info->delay_time);// SCL发送一个脉冲simulate_i2c_scl_out_high(i2c_info);delay_us(i2c_info->delay_time);simulate_i2c_scl_out_low(i2c_info);delay_us(i2c_info->delay_time);return ;
}/** 读取从设备的ack应答信号* @i2c_info i2c接口信息* @ret 读取到的信号。0表示应答,1表示非应答*/
unsigned int simulate_i2c_read_ack(simulate_i2c_t *i2c_info)
{unsigned int ack = 1;// SDA输入模式,释放SDAsimulate_i2c_config_sda_in(i2c_info);delay_us(i2c_info->delay_time);simulate_i2c_scl_out_high(i2c_info);delay_us(i2c_info->delay_time);ack = simulate_i2c_sda_in(i2c_info);simulate_i2c_scl_out_low(i2c_info);delay_us(i2c_info->delay_time);return ack;
}/** 主设备从从设备那里读取一个8bit数据* @i2c_info i2c接口信息* @ret 读取的数据*/
unsigned char simulate_i2c_read_byte(simulate_i2c_t *i2c_info)
{int i;unsigned char data = 0;// SDA输入模式simulate_i2c_config_sda_in(i2c_info);for (i=0; i<8; i++){delay_us(i2c_info->delay_time);simulate_i2c_scl_out_high(i2c_info);delay_us(i2c_info->delay_time);// 读取一个bitdata <<= 1;if (gpio_level_high == simulate_i2c_sda_in(i2c_info))data |= 0x01;simulate_i2c_scl_out_low(i2c_info);}return data;
}/** 主设备写8bit数据到从设备* @i2c_info i2c接口信息* @data 待写数据*/
void simulate_i2c_write_byte(simulate_i2c_t *i2c_info, unsigned char data)
{int i;// SDA输出模式simulate_i2c_config_sda_out(i2c_info);for (i=0; i<8; i++){delay_us(i2c_info->delay_time);// 写一个bitif (data & 0x80)simulate_i2c_sda_out_high(i2c_info);elsesimulate_i2c_sda_out_low(i2c_info);delay_us(i2c_info->delay_time);simulate_i2c_scl_out_high(i2c_info);delay_us(i2c_info->delay_time);simulate_i2c_scl_out_low(i2c_info);data <<= 1;}delay_us(i2c_info->delay_time);return ;
}

simulate_i2c.h

// 模拟i2c的头文件#ifndef __OPENLOONGSON_SIMULATE_H
#define __OPENLOONGSON_SIMULATE_H// 模拟i2c的接口信息
typedef struct
{unsigned int scl_gpio;          // SCL所在gpio引脚unsigned int sda_gpio;          // SDA所在gpio引脚int delay_time;                 // 周期的1/2,单位us
}simulate_i2c_t;/** 模拟i2c初始化* @i2c_info i2c的接口信息*/
void simulate_i2c_init(simulate_i2c_t *i2c_info);/** 模拟I2C的开始* @i2c_info i2c接口信息*/
void simulate_i2c_start(simulate_i2c_t *i2c_info);/** 模拟I2C的停止* @i2c_info i2c接口信息*/
void simulate_i2c_stop(simulate_i2c_t *i2c_info);/** 给从设备发送一个ack应答信号* @i2c_info i2c接口信息*/
void simulate_i2c_send_ack(simulate_i2c_t *i2c_info);/** 给从设备发送一个no ack非应答信号* @i2c_info i2c接口信息*/
void simulate_i2c_send_no_ack(simulate_i2c_t *i2c_info);/** 读取从设备的ack应答信号* @i2c_info i2c接口信息* @ret 读取到的信号。0表示应答,1表示非应答*/
unsigned int simulate_i2c_read_ack(simulate_i2c_t *i2c_info);/** 主设备从从设备那里读取一个8bit数据* @i2c_info i2c接口信息* @ret 读取的数据*/
unsigned char simulate_i2c_read_byte(simulate_i2c_t *i2c_info);/** 主设备写8bit数据到从设备* @i2c_info i2c接口信息* @data 待写数据*/
void simulate_i2c_write_byte(simulate_i2c_t *i2c_info, unsigned char data);#endif

完整的代码请到git查看,谢谢欣赏。

【龙芯1c库】封装模拟I2C接口和使用示例相关推荐

  1. 【龙芯1c库】封装硬件定时器接口和使用示例

    龙芯1c库是把龙芯1c的常用外设的常用功能封装为一个库,类似于STM32库.完整源码请移步到https://gitee.com/caogos/OpenLoongsonLib1c 龙芯1c库中硬件定时器 ...

  2. 用龙芯1c库实现无源蜂鸣器唱歌《送别》

    龙芯1c库是把龙芯1c的常用外设的常用功能封装为一个库,可用于裸机编程和实时系统,类似于STM32库.Git地址:http://git.oschina.NET/caogos/OpenLoongsonL ...

  3. 【龙芯1c库】封装硬件pwm接口和使用示例

    龙芯1c库是把龙芯1c的常用外设的常用功能封装为一个库,类似于STM32库.Git地址:https://gitee.com/caogos/OpenLoongsonLib1c 本文通过"龙芯1 ...

  4. 【龙芯1c库】移植硬浮点FPU

    龙芯1c库是把龙芯1c的常用外设的常用功能封装为一个库,类似于STM32库.完整源码请移步到https://gitee.com/caogos/OpenLoongsonLib1c 龙芯1C上有硬浮点协处 ...

  5. 【龙芯1c库】封装CAN接口和使用示例

    can使用还是比较广泛的,之前有网友在龙芯1b和龙芯1c上已经测试过了在裸机编程中使用CAN接口,这里把他们分享的程序贴上来,供大家参考. 龙芯1b上的测试程序在https://gitee.com/c ...

  6. 在龙芯1C单片机上使用ESP8266 wifi透传模块

    龙芯1C既可以运行linux,也可以当作单片机用.当用作linux时,可以通过USB wifi模块RTL8192C,RTL8188ETV等,当作单片机用时,可以像STM32那样使用串口透传wifi模块 ...

  7. Buildroot 龙芯1C支持指南

    本文转载自:https://github.com/pengphei/smartloong-sphinx/blob/master/source/cn/loongson1c_buildroot_guide ...

  8. 龙芯处理器可以适配鸿蒙os吗,SylixOS龙芯1C适配总结

    1.龙芯1C简介 1.1龙芯1C简介 龙芯 1C300(以下简称 1C)芯片是基于 LS232 处理器核的高性价比单芯片系统,可应用于指纹生物识别.物联传感等领域.1C 包含浮点处理单元,可以有效增强 ...

  9. 【龙印】用龙芯1c的硬件pwm产生单个脉冲来驱动步进电机

    本文为在用龙芯1c做3D打印机过程中的笔记.龙芯1c做的3d打印机简称"龙印",Git地址"http://git.oschina.NET/caogos/marlin_ls ...

最新文章

  1. 1. 批量梯度下降法BGD 2. 随机梯度下降法SGD 3. 小批量梯度下降法MBGD
  2. Coursera公开课笔记: 斯坦福大学机器学习第七课“正则化(Regularization)”
  3. 人工机器:深度学习CNN到底实现了什么
  4. [react] React的isMounted有什么作用?
  5. Maven Web项目配置Mybatis出现SqlSessionFactory错误的解决方案
  6. JS 回调(CallBack)
  7. 利用Python绘制图案——七色花子
  8. Halcon识别激光雕刻二维码_ZCTMV
  9. openpyxl自动设置列宽
  10. 让css固定定位占据其位置
  11. CSS中media的最全用法格式总结!
  12. springboot 定时器使用方法之并行
  13. 第四十一章 SQL命令 DROP VIEW
  14. Spring学习-Spring核心技术(九)
  15. 网易我的世界服务器正在维护,网易《我的世界》Hypixel中国版服务器将停止运营...
  16. “超越巴菲特计划“之股市小知识常用的术语笔记
  17. 企业级监控服务器构建
  18. 计算机网络第一二章多选题,计算机网络概论第一章练习题
  19. 长城宽带网线--有线路由器--无线路由器(手机wifi上网)
  20. 三维目标检测新SOTA---ADFDetV2论文解读

热门文章

  1. 最有用的p d f 格式转换软件
  2. 我和我目前的测试状态
  3. Web系统常见安全漏洞介绍及解决方案-sql注入
  4. 注册中心对比Zookeeper、Eureka、Nacos、Consul和Etcd
  5. 百度网盘加速无限试用_单次付费来了!百度网盘推出3元/5分钟加速下载服务
  6. Verilog基础知识
  7. 【华人学者风采】程学旗 中国科学院
  8. Cookie、Session、Token、JWT 看一篇就够了
  9. 开源音乐_与这位开源音乐老师一起学习乐器
  10. Master in Vocab -- Day Six