目录

参考调试MPU6050与EEPROM的经验,整合了目标内存/寄存器地址是否为16位的情况,合并了单字节与多字节间的操作,添加了返回值与读写超时功能:硬件IIC的7位从机地址查询方式读写参考代码 - JayWell - 博客园 (cnblogs.com)

很久以前听说过硬件IIC的“难用”,也在其他单片机上尝试过调试硬件IIC,调“通”过,却很容易卡死,于是默默给硬件IIC贴上个“不稳定”的标签,之后就一直用软件模拟IIC。

CH582这块单片机主攻蓝牙相关的功能,也有硬件IIC这个模块。考虑到低功耗蓝牙对时间的把控比较严格,相比软件IIC,硬件IIC能够帮助节省模拟时序代码中“无意义”的等待,且时序更精准,能达到更高的通信速率,更契合在蓝牙方面的应用,再加上有中断功能,还是有不错的应用价值的。

以下代码以MPU6050外设为例,简单验证了三个方向的加速度读取数值——转动MPU6050,各方向读取的数据有相应变化。

先贴一波MPU6050寄存器相关的宏定义:

#define SMPLRT_DIV              0x19 //陀螺仪采样率典型值为0x07 1000/(1+7)=125HZ
#define CONFIG 0x1A //低通滤波器 典型值0x06 5hz
#define GYRO_CONFIG 0x1B //陀螺仪测量范围 0x18正负2000度
#define ACCEL_CONFIG 0x1C //加速度计测量范围 0x18正负16g
#define ACCEL_CONFIG2 0x1D //加速度计低通滤波器 0x06 5hz #define USER_CTRL 0x6A //用户配置当为0x10时使用SPI模式
#define PWR_MGMT_1 0x6B //电源管理1典型值为0x00
#define PWR_MGMT_2 0x6C //电源管理2典型值为0x00 #define WHO_AM_I 0x75 //器件ID MPU6050默认返回值为0x68 #define I2C_ADDR_MPU6050 0xD0 //左移一位后的地址
//0x68 //没有左移一位的7位地址 #define ACCEL_XOUT_H 0x3B //加速度计输出数据
#define ACCEL_XOUT_L 0x3C
#define ACCEL_YOUT_H 0x3D
#define ACCEL_YOUT_L 0x3E
#define ACCEL_ZOUT_H 0x3F
#define ACCEL_ZOUT_L 0x40 #define TEMP_OUT_H 0x41 //温度计输出数据
#define TEMP_OUT_L 0x42 #define GYRO_XOUT_H 0x43 //陀螺仪输出数据
#define GYRO_XOUT_L 0x44
#define GYRO_YOUT_H 0x45
#define GYRO_YOUT_L 0x46
#define GYRO_ZOUT_H 0x47
#define GYRO_ZOUT_L 0x48

硬件IIC的“难用”,一部分归“功”于标志位之多——保证了其可靠性,可也限制了其稳健性,错过一个ACK就有可能导致卡死,导致下次判忙一直为Busy等问题。以下贴出相关标志位,具体描述详见CH58x系列手册和EVT包中的IIC接口使用指南。

/*各标志位
* AF: 应答失败
* BUSY: 总线忙标志位,该位在检测到一个停止位时会被清零
* MSL:   主从模式指示位,当接口处于主模式时(SB=1),硬件将该位置位
* SB:    起始位发送标志位,读取状态寄存器 1 后写数据寄存器的操作将清除该位
* ADDR: 地址被发送/地址匹配标志位,用户读取状态寄存器 1后,对状态寄存器 2的读操作将会清除此位
* TxE:   数据寄存器为空标志位,向数据寄存器写数据可以清除,或者产生一个起始或者停止位后由硬件自动清除。TxE=0表示非空
* TRA:    发送 /接收标志位 ,在检测到停止事件(STOPF=1),重复的起始条件时由硬件清零。该位根据地址字节的 R/W位来决定
* BTF:   字节发送结束标志位,用户读取状态寄存器 1后,对数据寄存器的读写将清除此位;在传输中,发起一个起始或者停止事件后,由硬件清除次位
*/

IIC的读写时序和协议特性这里就不再介绍了,网上能找到很多资料。

和单片机上地其他硬件资源一样,使用前记得先初始化。我使用的MPU6050模块上有时钟线和数据线的上拉电阻,自己制板或是焊接,一定要在这两根信号线上加上拉。

GPIOB_ModeCfg(GPIO_Pin_12 | GPIO_Pin_13, GPIO_ModeIN_PU);   //PB12:SDA,PB13:SCL     内部上拉较弱,可能需要外部上拉
I2C_Init(I2C_Mode_I2C, 400000, I2C_DutyCycle_16_9, I2C_Ack_Enable, I2C_AckAddr_7bit, Host_No_Addr);
    //选择IIC模式,400k速率,选择占空比,默认开启ACK(接收模式必须开启),作从机时使用地址位数为7位,作从机时的地址(本测试中单片机为主机模式,后面两个参数不起作用)

习惯性地,我在使用MPU6050之前会“问一问它是谁”(只连一个MPU6050的话,不识别设备ID也行,直接初始化)。MPU6050模块上一般有个AD0引脚或焊盘,悬空或者接地,读WHO_AM_I寄存器,会返回0x68;接上高电平,会返回0x69,此时I2C_ADDR_MPU6050这个宏也就不要设为0x68左移1位了,也要改为0x69左移一位。

temp_data = IIC_read_reg(I2C_ADDR_MPU6050, (WHO_AM_I | 0x80));      //获取器件ID
PRINT("0x%2x\n", temp_data);

这就需要读寄存器的代码,下面是返回一个字节的读代码↓

//从从机的某寄存器,读取一个字节的数据
uint8_t IIC_read_reg(uint8_t addr, uint8_t reg)
{
uint8_t data = 0; //主机通知从机要读取它的哪个寄存器
while(I2C_GetFlagStatus(I2C_FLAG_BUSY)); //IIC主机判忙
I2C_GenerateSTART(ENABLE); //起始信号
while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT)); //判断BUSY, MSL and SB flags
I2C_Send7bitAddress(addr, I2C_Direction_Transmitter); //发送地址+最低位0表示为“写”
while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); //判断BUSY, MSL, ADDR, TXE and TRA flags
I2C_SendData(reg); //发送寄存器的地址
while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //判断TRA, BUSY, MSL, TXE and BTF flags //直接产生一个重起始信号即可开始读的过程
I2C_GenerateSTART(ENABLE); //重起始信号
while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT)); //判断BUSY, MSL and SB flags
I2C_Send7bitAddress(addr, I2C_Direction_Receiver); //发送地址+最低位1表示为“读”
while(!I2C_CheckEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); //判断BUSY, MSL and ADDR flags I2C_GenerateSTOP(DISABLE); //关闭停止信号使能 I2C_AcknowledgeConfig(DISABLE); //关闭ACK使能,接收一个字节数据后,主机就回NACK表示不再接收数据 while(!I2C_GetFlagStatus(I2C_FLAG_RXNE)); //获取RxEN的状态,等待收到数据
data = I2C_ReceiveData(); //获得从机的寄存器中的数据 I2C_GenerateSTOP(ENABLE); //停止信号
I2C_AcknowledgeConfig(ENABLE); //传输完毕,再次打开ACK使能 return data;
}

在识别了设备ID之后,就可以对其进行初始化。

void MPU6050Init()
{
IIC_write_reg(I2C_ADDR_MPU6050, USER_CTRL, 0x00); //配置为0x10时为SPI通信模式
IIC_write_reg(I2C_ADDR_MPU6050, PWR_MGMT_1, 0x00); //解除休眠状态 IIC_write_reg(I2C_ADDR_MPU6050, SMPLRT_DIV, 0x02); //333HZ采样率
IIC_write_reg(I2C_ADDR_MPU6050, CONFIG, 0x03); //设置低通滤波,频率见手册 1K/ 1+2 = 333HZ
IIC_write_reg(I2C_ADDR_MPU6050, GYRO_CONFIG, 0x18); //角速度计2000°/s量程
IIC_write_reg(I2C_ADDR_MPU6050, ACCEL_CONFIG, 0x10); //加速度计8g量程
}

这又需要写寄存器的代码↓

//向从机的某寄存器,写入一个字节的数据
void IIC_write_reg(uint8_t addr, uint8_t reg, uint8_t data)
{
//主机通知从机要写它的哪个寄存器
while(I2C_GetFlagStatus(I2C_FLAG_BUSY)); //IIC主机判忙
I2C_GenerateSTART(ENABLE); //起始信号
while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT)); //判断BUSY, MSL and SB flags
I2C_Send7bitAddress(addr, I2C_Direction_Transmitter); //发送地址+最低位0表示为“写”
while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); //判断BUSY, MSL, ADDR, TXE and TRA flags
I2C_SendData(reg);    //发送寄存器的地址 //ACK之后直接写入数据
while(!I2C_GetFlagStatus(I2C_FLAG_TXE)); //获取TxE的状态 数据寄存器为空标志位,可以向其中写数据
I2C_SendData(data); //发送寄存器的地址
while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //判断TRA, BUSY, MSL, TXE and BTF flags
I2C_GenerateSTOP(ENABLE); //停止信号
}

至此,就可以读取传感器相应的寄存器的数值了。MPU6050返回的主要数值大多是16位的数据,使用以下函数一次读两个字节数据↓(一个字节一个字节读也行,慢点罢了)

//从从机的某寄存器起始,读取并返回16位数据
uint16_t IIC_read_reg_2Bytes(uint8_t addr, uint8_t reg)
{
uint8_t dataH = 0, dataL = 0;
uint16_t data = 0; //主机通知从机要读取它的哪个寄存器
while(I2C_GetFlagStatus(I2C_FLAG_BUSY)); //IIC主机判忙
I2C_GenerateSTART(ENABLE); //起始信号
while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT)); //判断BUSY, MSL and SB flags
I2C_Send7bitAddress(addr, I2C_Direction_Transmitter); //发送地址+最低位0表示为“写”
while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); //判断BUSY, MSL, ADDR, TXE and TRA flags
I2C_SendData(reg); //发送寄存器的地址
while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //判断TRA, BUSY, MSL, TXE and BTF flags //直接产生一个重起始信号即可开始读的过程
I2C_GenerateSTART(ENABLE); //重起始信号
while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT)); //判断BUSY, MSL and SB flags
I2C_Send7bitAddress(addr, I2C_Direction_Receiver); //发送地址+最低位1表示为“读”
while(!I2C_CheckEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); //判断BUSY, MSL and ADDR flags I2C_GenerateSTOP(DISABLE); //关闭停止信号使能 while(!I2C_GetFlagStatus(I2C_FLAG_RXNE)); //获取RxEN的状态,等待收到数据
dataH = I2C_ReceiveData(); //获得从机的寄存器中的数据 I2C_AcknowledgeConfig(DISABLE); //清除ACK位 主设备为了能在收到最后一个字节后产生一个NACK脉冲,
//必须在读取倒数第二个字节之后(倒数第二个RxNE 事件之后)清除ACK位(ACK=0) while(!I2C_GetFlagStatus(I2C_FLAG_RXNE)); //获取RxEN的状态
dataL = I2C_ReceiveData(); //获得从机地址的寄存器地址中的数据 I2C_GenerateSTOP(ENABLE); //使能停止信号
I2C_AcknowledgeConfig(ENABLE); //传输完毕,再次打开ACK使能 data = (uint16_t)(dataH<<8) + dataL;
return data;
}

为了验证代码运行的稳定性,加上随机延时,将下列代码跑了一个下午,也没有卡死(在上面的函数中我一直用while(判断标志)的死循环来等待,一旦标志位不对就会卡死),动一动传感器有相应的数据变化,就算成功了。

while(1)
{
DelayMs(100+rand()%300);
temp_data = IIC_read_reg(I2C_ADDR_MPU6050, (WHO_AM_I | 0x80)); //获取器件ID
PRINT("0x%2x\n", temp_data); DelayMs(100+rand()%300);
MPU6050Init(); DelayMs(50+rand()%300);
mpu_acc_x = IIC_read_reg_2Bytes(I2C_ADDR_MPU6050, ACCEL_XOUT_H); //获取X轴的加速度
PRINT("X:0x%4x\n",mpu_acc_x); DelayMs(50+rand()%300);
mpu_acc_y = IIC_read_reg_2Bytes(I2C_ADDR_MPU6050, ACCEL_YOUT_H); //获取Y轴的加速度
PRINT("Y:0x%4x\n",mpu_acc_y); DelayMs(50+rand()%300);
mpu_acc_z = IIC_read_reg_2Bytes(I2C_ADDR_MPU6050, ACCEL_ZOUT_H); //获取Z轴的加速度
PRINT("Z:0x%4x\n",mpu_acc_z);
}

IIC通信协议中是可以连续读/写n个字节的数据的,手头的这块MPU6050也支持这样的操作,代码如下↓

//从从机的某寄存器起始,连续读取n个字节的数据
void IIC_read_reg_nBytes(uint8_t addr, uint8_t reg, uint8_t *des, uint8_t len)
{
//主机通知从机要读取它的哪个寄存器
while(I2C_GetFlagStatus(I2C_FLAG_BUSY)); //IIC主机判忙
I2C_GenerateSTART(ENABLE); //起始信号
while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT)); //判断BUSY, MSL and SB flags
I2C_Send7bitAddress(addr, I2C_Direction_Transmitter); //发送地址+最低位0表示为“写”
while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); //判断BUSY, MSL, ADDR, TXE and TRA flags
I2C_SendData(reg); //发送寄存器的地址
while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //判断TRA, BUSY, MSL, TXE and BTF flags //直接产生一个重起始信号即可开始读的过程
I2C_GenerateSTART(ENABLE); //重起始信号
while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT)); //判断BUSY, MSL and SB flags
I2C_Send7bitAddress(addr, I2C_Direction_Receiver); //发送地址+最低位1表示为“读”
while(!I2C_CheckEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); //判断BUSY, MSL and ADDR flags I2C_GenerateSTOP(DISABLE); //关闭停止信号使能 for(uint8_t i=0; i<len; i++)
{
if(i == len-1)
I2C_AcknowledgeConfig(DISABLE); //清除ACK位 主设备为了能在收到最后一个字节后产生一个NACK脉冲,
//必须在读取倒数第二个字节之后(倒数第二个RxNE 事件之后)清除ACK位(ACK=0)
while(!I2C_GetFlagStatus(I2C_FLAG_RXNE)); //获取RxEN的状态,等待收到数据
*(des+i) = I2C_ReceiveData(); //获得从机的寄存器中的数据
} I2C_GenerateSTOP(ENABLE); //使能停止信号
I2C_AcknowledgeConfig(ENABLE); //传输完毕,再次打开ACK使能
}
//向从机某寄存器起始,连续写入n个字节的数据
void IIC_write_reg_nBytes(uint8_t addr, uint8_t reg, uint8_t *src, uint8_t len)
{
//主机通知从机要写它的哪个寄存器
while(I2C_GetFlagStatus(I2C_FLAG_BUSY)); //IIC主机判忙
I2C_GenerateSTART(ENABLE); //起始信号
while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT)); //判断BUSY, MSL and SB flags
I2C_Send7bitAddress(addr, I2C_Direction_Transmitter); //发送地址+最低位0表示为“写”
while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); //判断BUSY, MSL, ADDR, TXE and TRA flags
I2C_SendData(reg); //发送寄存器的地址 //ACK之后直接写入数据
for(uint8_t i=0; i<len; i++)
{
while(!I2C_GetFlagStatus(I2C_FLAG_TXE)); //获取TxE的状态 数据寄存器为空标志位,可以向其中写数据
I2C_SendData(*(src+i)); //发送寄存器的地址
} while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //判断TRA, BUSY, MSL, TXE and BTF flags
I2C_GenerateSTOP(ENABLE); //停止信号
}

使用连续读/写的代码后,初始化和读传感器数值,就变成下面这个样子↓

void MPU6050Init()
{
uint8_t temp_str1[2] = {0};
IIC_write_reg_nBytes(I2C_ADDR_MPU6050, USER_CTRL, temp_str1, 2);
uint8_t temp_str2[4] = {0x02, 0x03, 0x18, 0x10};
IIC_write_reg_nBytes(I2C_ADDR_MPU6050, SMPLRT_DIV, temp_str2, 4);
}
while(1)
{
DelayMs(300+rand()%300);
MPU6050Init();
PRINT("Init_OK\n"); DelayMs(300+rand()%300);
IIC_read_reg_nBytes(I2C_ADDR_MPU6050, ACCEL_XOUT_H,acc_str,6);
for(uint8_t i=0; i<6; i++)
PRINT("0x%2x ",acc_str[i]);
PRINT("\n");
}

又挂着跑了三个多小时,也没有出现卡死的情况,传感器数据变化正常。

回过头来看,使用硬件IIC去写数据,没有磕绊,但在读数据时,要注意两点:

①在读最后一个字节的数据前关闭ACK使能,即使是只读一个数据,也要在读取前关闭。我的理解是ACK使能后,主机会自动拉低数据线以回复ACK,若没有及时关闭,最好的情况是多收一个字节数据,通信照常进行,最坏的情况就是卡死了。

②在读数据之前关闭STOP使能。很奇怪,在读之前关闭停止信号使能,在读完数据之后再使能,产生一个停止信号,可以“增强”程序的稳定性;若缺少关闭使能的代码,程序会在读取或多或少的传感器数值后卡死。解释或猜测还需要笔者继续深入了解硬件IIC。

另外,如果想加强代码的健壮性,可以在读/写函数的判断标志等待中加上计数来判断等待超时,超时则直接返回错误代码,根据错误代码选择重新读写或是重新初始化硬件IIC模块或外设。比如说↓

//向从机的某寄存器写入一个字节的数据
uint8_t IIC_write_reg(uint8_t addr, uint8_t reg, uint8_t data)
{
uint16_t i=1000;
//主机通知从机要写它的哪个寄存器
while(I2C_GetFlagStatus(I2C_FLAG_BUSY) && i--) //IIC主机判忙
{
if(!i)
return 1;
}
i = 1000; I2C_GenerateSTART(ENABLE);    //起始信号 while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT) && i--) //判断BUSY, MSL and SB flags
{
if(!i)
return 2;
}
i = 1000; I2C_Send7bitAddress(addr, I2C_Direction_Transmitter);    //发送地址+最低位0表示为“写”
//略
}

以MPU6050为例的硬件IIC的使用的更多相关文章

  1. MPU6050首例整合性6轴的姿态模块(转)

    源:MPU6050首例整合性6轴的姿态模块 Mpu6050为全球首例整合3轴陀螺仪.3轴加速器.含9轴融合演:MPU-6000为全球首例整合性6轴运动处理组件,相较于多组件方案,免除了组合陀螺仪与加速 ...

  2. STM32 IIC双机通信—— HAL库硬件IIC版

    参考传送门 关于IIC的原理这里我就不多说了,网上有很多很好的解析,如果要看我个人对IIC的理解的话,可以点击查看,这里主要讲一下怎样利用STM32CubeMx实现IIC的通讯,经过个人实践,感觉HA ...

  3. STM32硬件IIC操作

    Stm32具有IIC接口,接口有以下主要特性 多主机功能:该模块既可做主设备也可做从设备 主设备功能 C地址检测 位/10位地址和广播呼叫 支持不同的通讯速度 状态标志: 发送器/接收器模式标志 字节 ...

  4. STM32硬件IIC (转)

    源: STM32硬件IIC

  5. STM32硬件IIC驱动设计(转)

    源: STM32硬件IIC驱动设计 参考: STM32—硬件IIC主机通信 STM32’s I2C 硬件BUG引发的血案(qzm) 解决STM32 I2C接口死锁在BUSY状态的方法讨论

  6. STM32硬件IIC

    /** * @brief 写一个字节到I2C设备中 * @param * @arg pBuffer:缓冲区指针 * @arg WriteAddr:写地址 * @retval 正常返回1,异常返回0 * ...

  7. STM32读取bq33100数据——硬件IIC

    背景:拟采用bq33100超级电容管理芯片,实现自动的超级电容组的均压任务.需监控芯片的工作情况,以及电容组的均压情况. 平台: 硬件:STM32F103C8T6 通信:SMBus(低速IIC) 目标 ...

  8. iic通讯 FPGA实现 mpu6050为例

    IIC最常用的通讯协议,但普遍用于单片机.arm这些,用FPGA实现大材小用,但对于菜鸡水平练练手很不错,考验串并转换和时序的控制.今天我就以mpu6050陀螺仪为例,实现FPGA的iic通信. 1. ...

  9. LPC2478的硬件IIC使用

    LPC2478的IIC使用 LPC2478带有三个IIC接口,每个IIC都可以工作在主机或者从机模式下,LPC的IIC的架构是一种状态机的形式,在不同的的时间做不同的工作之后有不同的状态来表示, 简单 ...

  10. 硬件IIC驱动原理

    1.IIC物理层 IIC通信属于同步半双工通信,IIC总线由两根信号线组成.一根是数据线SDA,一根是时钟线SCL,时钟线只能由主机发送给从机,数据线可以双向进行通信,总线上可挂载多个设备,挂载数量受 ...

随机推荐

  1. Codeforces Round #844 (Div. 1 + Div. 2, based on VK Cup 2022 - Elimination Round) A-D

    比赛链接 A 题意 设计一条线路要贴着6个墙面走,从 \((a,b)\) 到 \((f,g)\) ,线路长度最短. 题解 知识点:模拟. 分类取最短即可. 时间复杂度 \(O(1)\) 空间复杂度 \ ...

  2. MySQL 留存率和复购率的场景分析

    实际工作中常见的业务场景是求次日留存率,还有一些会对次日留存率增加限制,例如求新用户的次日留存率或者求活跃用户留存率.另外,留存率和复购率看起来都是统计重复出现的概率,但实际求解方法是不一样的. [场 ...

  3. SpringCloud NetFlix学习

    SpringCloud NetFlix 遇到记录不完全的可以看看这个人的博客 学相伴SpringCloud 微服务架构的4个核心问题? 服务很多,客户端该怎么访问? 负载均衡.反向代理,用户请求的永远 ...

  4. TCP/IP RTT算法比较

    TCP重传机制Timeout特点: 设长了,重发就慢,效率和性能差: 设短了,重发就快,可能导致没有丢就重发,增加网络拥塞,导致更多的超时,更多的超时导致更多的重发. TCP协议引入2个概念: RTT ...

  5. CSS常用属性(2)

    (4) position(定位) fixed 一般用来写网页顶端的固定导航条,或者两侧的菜单. <!--对于块级标签来说加上position:fixed之后,该div就不会占一整行,一般需要手动 ...

  6. C语言-补漏 -内存管理

    8. 全局就是简简单单编写的 静态函数--只能在当前文件内使用的函数

  7. Openfoam UPstream类探索

    前言 上篇文章我们大体捋顺了Pstream类,但上篇没有讲到的是Pstream类很多东西是从UPstream类继承来的 这次我们大体看一下UPstream类,以避免Pstream类内很多继承来的东西不 ...

  8. IDEA插件Apifox,一键自动生成接口文档!

    有关Apifox软件之前写过一篇文章: 接口测试神器Apifox,亲测好用! 如何一键自动生成数据库文档之前也写过一篇文章: 数据库界的Swagger:一键生成数据库文档! 一.Apifox插件的优势 ...

  9. Java中的源码,反码和补码

    一.在Java中所有数据都是以补码的形式表示的,原码即数字的二进制表示加符号位,反码即将原码按位取反,补码简单来说即反码加1 二.Java中数据的表示方式 1.正数:正数的原码.补码.反码都相同,正数 ...

  10. 动手写了个简单版的ChatGPT的Java版客户端

    最近ChatGpt大火,我在年前申请过账号忘了下确实强大. 作为Java程序猿社畜就尝试写了个Java版本的简易版客户端. 源码地址:https://github.com/Grt1228/chatgp ...