STM32开发项目:ADS1115的驱动与使用

2022-11-17 17:58:50 浏览数 (3)

日期

作者

版本

说明

2020.09.24

Tao

V1.0

发布第一版文档

2020.10.20

Tao

V1.1

改进了void ADS1115_RefreshAllChannel()函数的实现,增加了出错检查机制

2020.11.30

Tao

V1.2

增加了使用RTOS时的ADS1115应用说明

目录

  • ADS1115介绍
  • 驱动源码
    • 头文件
    • 源文件
  • 使用指南
    • 基本步骤
    • 注意事项
      • 不使用实时操作系统
      • 使用实时操作系统

ADS1115介绍

ADS1115是具有 PGA、振荡器、电压基准、比较器的 16 位、860SPS、4 通道 Δ-Σ ADC,数据通过一个 I2C 兼容型串行接口进行传输。有关它的详细说明可以参考官方数据手册。

驱动源码

头文件

代码语言:javascript复制
#ifndef __ADS1115_H__
#define __ADS1115_H__
#include "stm32f4xx.h"
#include "stm32f4xx_conf.h"
#ifdef ADS1115
#include "i2c_virtual.h"
#define ADS1115_SCL_PORT 'A'
#define ADS1115_SCL_PIN 6
#define ADS1115_SDA_PORT 'A'
#define ADS1115_SDA_PIN 5
#define Accuracy 32768 //ADC单端输入15位精度
#define ADS1115_ADDRESS_0 0x90 //ADDR PIN ->GND
#define ADS1115_ADDRESS_1 0x92 //ADDR PIN ->VDD
#define ADS1115_ADDRESS_2 0x94 //ADDR PIN ->SDA
#define ADS1115_ADDRESS_3 0x96 //ADDR PIN ->SCL
//*************ADDR Initial********************/
#define ADS1115_ADDRESS ADS1115_ADDRESS_0 //ADDR PIN ->GND
#define ADS1115_ADDRESS_W ADS1115_ADDRESS|0x00 //write address
#define ADS1115_ADDRESS_R ADS1115_ADDRESS|0x01 //read address
/************POINTER REGISTER*****************/
#define ADS1115_Pointer_ConverReg 0x00 //Convertion register
#define ADS1115_Pointer_ConfigReg 0x01 //Config register
#define ADS1115_Pointer_LoThreshReg 0x02 //Lo_thresh register
#define ADS1115_Pointer_HiThreshReg 0x03 //Hi_thresh register
/************CONFIG REGISTER*****************/
//Bit[15]
#define ADS1115_OS_OperationalStatus 0x0000 //No Effect
#define ADS1115_OS_SingleConverStart 0x8000 //Begin a single conversion
//Bits[14:12]
#define ADS1115_MUX_Differ_01 0x0000 //AINp=AIN0, AINn=AIN1(default)
#define ADS1115_MUX_Differ_03 0x1000 //AINp=AIN0, AINn=AIN3
#define ADS1115_MUX_Differ_13 0x2000 //AINp=AIN1, AINn=AIN3
#define ADS1115_MUX_Differ_23 0x3000 //AINp=AIN2, AINn=AIN3
#define ADS1115_MUX_Channel_0 0x4000 //AINp=AIN0, AINn=GND
#define ADS1115_MUX_Channel_1 0x5000 //AINp=AIN1, AINn=GND
#define ADS1115_MUX_Channel_2 0x6000 //AINp=AIN2, AINn=GND
#define ADS1115_MUX_Channel_3 0x7000 //AINp=AIN3, AINn=GND
//Bits[11:9]
#define ADS1115_PGA_6144 0x0000 //FS=6.144V
#define ADS1115_PGA_4096 0x0200 //FS=4.096V
#define ADS1115_PGA_2048 0x0400 //FS=2.048V(default)
#define ADS1115_PGA_1024 0x0600 //FS=1.024V
#define ADS1115_PGA_0512 0x0800 //FS=0.512V
#define ADS1115_PGA_0256 0x0A00 //FS=0.256V
//Bit[8]
#define ADS1115_MODE_ContinuConver 0x0000 //Continuous conversion mode
#define ADS1115_MODE_SingleConver 0x0100 //Power-down single-shot mode(default)
//Bits[7:5]
#define ADS1115_DataRate_8 0x0000 //Data Rate = 8
#define ADS1115_DataRate_16 0x0020 //Data Rate = 16
#define ADS1115_DataRate_32 0x0040 //Data Rate = 32
#define ADS1115_DataRate_64 0x0060 //Data Rate = 64
#define ADS1115_DataRate_128 0x0080 //Data Rate = 128(default)
#define ADS1115_DataRate_250 0x00A0 //Data Rate = 250
#define ADS1115_DataRate_475 0x00C0 //Data Rate = 475
#define ADS1115_DataRate_860 0x00E0 //Data Rate = 860
//Bit[4]
#define ADS1115_COMP_MODE_0 0x0000 //Traditional comparator with hysteresis
#define ADS1115_COMP_MODE_1 0x0010 //Window comparator
//Bit[3]
#define ADS1115_COMP_POL_0 0x0000 //Active low
#define ADS1115_COMP_POL_1 0x0008 //Active high
//Bit[2]
#define ADS1115_COMP_LAT_0 0x0000 //Non-latching comparator
#define ADS1115_COMP_LAT_1 0x0004 //Latching comparator
//Bits[1:0]
#define ADS1115_COMP_QUE_0 0x0000 //Assert after one conversion
#define ADS1115_COMP_QUE_1 0x0001 //Assert after two conversion
#define ADS1115_COMP_QUE_2 0x0002 //Assert after four conversion
#define ADS1115_COMP_QUE_3 0x0003 //Disable Comparator
typedef struct
{ 

u16 OS;
u16 MUX;
u16 PGA;
u16 MODE;
u16 DataRate;
u16 COMP_MODE;
u16 COMP_POL;
u16 COMP_LAT;
u16 COMP_QUE;
} ADS1115_InitTypeDefine;
extern int16_t ADS1115_RawData[4];
void ADS1115_Init();
void ADS1115_UserConfig1();
void ADS1115_UserConfig2();
u8 ADS1115_Config(ADS1115_InitTypeDefine* ADS1115_InitStruct);
u8 ADS1115_ReadRawData(int16_t* rawData);
void ADS1115_ScanChannel(uint8_t channel);
float ADS1115_RawDataToVoltage(int16_t rawData);
float ADS1115_GetVoltage();
float ADS1115_GetAverageVoltage(uint16_t num);
void ADS1115_RefreshAllChannel();
#endif
#endif

Jetbrains全家桶1年46,售后保障稳定

源文件

代码语言:javascript复制
#include "ads1115.h"
#ifdef ADS1115
static ADS1115_InitTypeDefine ADS1115_InitType;
/** * 可供外部调用的全局变量,记录了ADS1115采样的原始16位数据。调用void ADS1115_RefreshAllChannel( )函数可以刷新这个变量。 * 通过定义宏ADS1115_USE_FILTER,可以将ADS1115的轮询采样数据经过滑动滤波后,保存到ADS1115_RawData[]中。 * 通过float ADS1115_RawDataToVoltage(int16_t rawData)函数可以将ADS1115_RawData[]换算成对应的电压值。 */
int16_t ADS1115_RawData[4] = { 
0};
/** * @brief 完成芯片控制端口初始化,并设置初始状态 */
void ADS1115_Init()
{ 

I2C_Virtual_ConfigPort(ADS1115_SDA_PORT, ADS1115_SDA_PIN, ADS1115_SCL_PORT, ADS1115_SCL_PIN);
}
/** * @brief Configuration of ADS1115, single-shot */
void ADS1115_UserConfig1()
{ 

ADS1115_InitType.COMP_LAT = ADS1115_COMP_LAT_0;
ADS1115_InitType.COMP_MODE = ADS1115_COMP_MODE_0;
ADS1115_InitType.COMP_POL = ADS1115_COMP_POL_0;
ADS1115_InitType.DataRate = ADS1115_DataRate_475;
ADS1115_InitType.MODE = ADS1115_MODE_SingleConver;
ADS1115_InitType.MUX = ADS1115_MUX_Channel_0;
ADS1115_InitType.OS = ADS1115_OS_SingleConverStart;
ADS1115_InitType.PGA = ADS1115_PGA_4096;
ADS1115_Config(&ADS1115_InitType);
}
/** * @brief Configuration of ADS1115, continuous conversion */
void ADS1115_UserConfig2()
{ 

ADS1115_InitType.COMP_LAT = ADS1115_COMP_LAT_0;
ADS1115_InitType.COMP_MODE = ADS1115_COMP_MODE_0;
ADS1115_InitType.COMP_POL = ADS1115_COMP_POL_0;
ADS1115_InitType.DataRate = ADS1115_DataRate_475;
ADS1115_InitType.MODE = ADS1115_MODE_ContinuConver;
ADS1115_InitType.MUX = ADS1115_MUX_Channel_0;
ADS1115_InitType.OS = ADS1115_OS_OperationalStatus;
ADS1115_InitType.PGA = ADS1115_PGA_4096;
ADS1115_Config(&ADS1115_InitType);
}
/** * @brief 配置ADS1115 * @param ADS1115_InitStruct: 用来配置ADS1115的结构体变量指针 * @return 配置结果 * @arg: fail * @arg: success */
u8 ADS1115_Config(ADS1115_InitTypeDefine *ADS1115_InitStruct)
{ 

u16 Config;
u8 Writebuff[2];
Config = ADS1115_InitStruct->OS   ADS1115_InitStruct->MUX   ADS1115_InitStruct->PGA   ADS1115_InitStruct->MODE
  ADS1115_InitStruct->DataRate   ADS1115_InitStruct->COMP_MODE   ADS1115_InitStruct->COMP_POL
  ADS1115_InitStruct->COMP_LAT   ADS1115_InitStruct->COMP_QUE;
Writebuff[0] = (unsigned char) ((Config >> 8) & 0xFF);
Writebuff[1] = (unsigned char) (Config & 0xFF);
I2C_Virtual_Start();					//启动总线
I2C_Virtual_SendByte(ADS1115_ADDRESS_W);		//发送器件地址(写)
if (I2C_Virtual_ack == 0)
return (0);
I2C_Virtual_SendByte(ADS1115_Pointer_ConfigReg);		//发送寄存器地址
if (I2C_Virtual_ack == 0)
return (0);
I2C_Virtual_SendByte(Writebuff[0]);		//发送数据
if (I2C_Virtual_ack == 0)
return (0);
I2C_Virtual_SendByte(Writebuff[1]);		//发送数据
if (I2C_Virtual_ack == 0)
return (0);
I2C_Virtual_Stop();
return (1);
}
/** * @brief 读取ADS1115当前通道下的原始数据 * @param rawData: 传入一个int16_t整型变量的指针,ADS1115的原始数据将保存在这个变量中 * @return 读取结果 * @arg 0: fail * @arg 1: success */
uint8_t ADS1115_ReadRawData(int16_t *rawData)
{ 

unsigned char Result[2];
I2C_Virtual_Start();															//启动总线
I2C_Virtual_SendByte(ADS1115_ADDRESS_W);					//发送器件地址(写)
if (I2C_Virtual_ack == 0)
return (0);
I2C_Virtual_SendByte(ADS1115_Pointer_ConverReg); 	//发送寄存器地址
if (I2C_Virtual_ack == 0)
return (0);
I2C_Virtual_Stop();
delay_us(10);
I2C_Virtual_Start();											//写寄存器之后需要重新启动总线
I2C_Virtual_SendByte(ADS1115_ADDRESS_R);						//发送器件地址(读)
if (I2C_Virtual_ack == 0)
return (0);
Result[0] = I2C_Virtual_RcvByte();		//接收数据
I2C_Virtual_Ack();			//发送就答位
Result[1] = I2C_Virtual_RcvByte();
I2C_Virtual_NoAck();			//发送非应位
I2C_Virtual_Stop(); 			//结束总线
*rawData = (int16_t) (((Result[0] << 8) & 0xFF00) | (Result[1] & 0xFF));
return 1;
}
/** * @brief Switch the channel of ADS1115 * @param channel */
void ADS1115_ScanChannel(uint8_t channel)
{ 

switch (channel)
{ 

case 0:
ADS1115_InitType.MUX = ADS1115_MUX_Channel_0;
break;
case 1:
ADS1115_InitType.MUX = ADS1115_MUX_Channel_1;
break;
case 2:
ADS1115_InitType.MUX = ADS1115_MUX_Channel_2;
break;
case 3:
ADS1115_InitType.MUX = ADS1115_MUX_Channel_3;
break;
default:
break;
}
ADS1115_Config(&ADS1115_InitType);
}
/** * @brief 将传感器的原始采样数据转化为电压数据, * 根据ADS1115_InitType结构体中包含的增益信息计算 * @param rawData: 待转换的原始数据 * @retval 返回经过计算的电压值 */
float ADS1115_RawDataToVoltage(int16_t rawData)
{ 

float voltage;
switch (ADS1115_InitType.PGA)
{ 

case ADS1115_PGA_0256:
voltage = rawData * 0.0078125;
break;
case ADS1115_PGA_0512:
voltage = rawData * 0.015625;
break;
case ADS1115_PGA_1024:
voltage = rawData * 0.03125;
break;
case ADS1115_PGA_2048:
voltage = rawData * 0.0625;
break;
case ADS1115_PGA_4096:
voltage = rawData * 0.125;
break;
case ADS1115_PGA_6144:
voltage = rawData * 0.1875;
break;
default:
voltage = 0;
break;
}
return voltage;
}
/** * @brief 直接获取ADS1115当前通道的电压采样值 * @return 电压采样值 */
float ADS1115_GetVoltage()
{ 

int16_t rawData;
ADS1115_ReadRawData(&rawData);
return ADS1115_RawDataToVoltage(rawData);
}
/** * @brief 获取并计算ADC采样的平均电压值 * @param num: 计算平均值的数量 * @retval 电压采样的平均值 */
float ADS1115_GetAverageVoltage(uint16_t num)
{ 

int32_t sum = 0;
int16_t rawData;
if(num == 0)
{ 

return ADS1115_GetVoltage( );
}
for(uint16_t i =0; i< num;i  )
{ 

ADS1115_ReadRawData(&rawData);
sum  = rawData;
}
return ADS1115_RawDataToVoltage(sum/num);
}
/** * @brief 刷新ADS1115全部通道的采样数据 * 由于ADS1115通道切换后需要等待较长时间数据才能够稳定, * 在进行多路数据采集的时候,切换通道后延时阻塞等待切换完成会占用过多的系统时间, * 因此需要在一个定时器中轮询采集ADS1115数据,每次采集完成后,切换到下一个通道 * 大幅度提高了系统工作的效率。 * * 调用此函数可以刷新全局变量ADS1115_RawData[4]的值。 * * 应当在一个定时器更新中断服务函数中周期性的调用此函数,更新周期最好小于200Hz */
void ADS1115_RefreshAllChannel()
{ 

static uint8_t channel = 0;
int16_t adcDataTemp = 0;
//通道切换时可能有不确定的数据读出,因此需要将前1~2次读出的数据舍弃
ADS1115_ReadRawData(&adcDataTemp);
ADS1115_ReadRawData(&adcDataTemp);
//读取数据返回正确,则将读到的数据写入ADS1115_RawData数组中
if( ADS1115_ReadRawData(&adcDataTemp) !=0 )
{ 

ADS1115_RawData[channel] = adcDataTemp;
}
//ADS1115总共4个通道
channel  ;
if(channel>ADS1115_MAX_CHANNEL-1)
channel = 0;
//结束采样后切换至下一通道
ADS1115_ScanChannel(channel);
}
#endif

使用指南

基本步骤

  1. 初始化软件模拟I2C或者硬件I2C外设(以笔者编写的软件模拟I2C库为例)
代码语言:javascript复制
	I2C_Virtual_ConfigPort(ADS1115_SDA_PORT, ADS1115_SDA_PIN, ADS1115_SCL_PORT, ADS1115_SCL_PIN);
I2C_Virtual_SwitchBus(ADS1115_SDA_PORT, ADS1115_SDA_PIN, ADS1115_SCL_PORT, ADS1115_SCL_PIN);
  1. 初始化ADS1115芯片的配置。笔者在驱动库中提供了两种初始化配置函数void ADS1115_UserConfig1()void ADS1115_UserConfig2(),可以修改这两个函数中的配置参数后直接调用。
代码语言:javascript复制
	ADS1115_UserConfig2();
  1. 设置或者切换ADS1115的通道(配置完ADS1115之后,可以设置一个初始通道)。
代码语言:javascript复制
	ADS1115_ScanChannel(1);
  1. 根据项目的需要,选择采集原始数据然后在其他地方换算成电压数据,或者直接采集电压数据以及滤波后的电压数据。
代码语言:javascript复制
	int16_t rawData = 0;
float voltage = 0;
//获取ADS1115采集的原始16位数据
ADS1115_ReadRawData(&rawDAta);
//将ADS1115采集的原始16位数据换算为实际电压值
voltage = ADS1115_RawDataToVoltage(rawData);
//直接获取ADS1115采集的电压数据
voltage = ADS1115_GetVoltage();
//直接获取ADS1115采集的电压数据(经过多次采样计算平均值)
voltage = ADS1115_GetAverageVoltage(10);

注意事项

不使用实时操作系统

  • 对于多通道采样,每次通道切换时,应当等待几毫秒的时间后再进行采样,否则采样的数据可能不稳定或者发生通道间干扰。
代码语言:javascript复制
	int32_t ADC_Temp_Filter[4] = { 
0};
float ADC_Temp[4] = { 
0};
for (uint8_t chan = 0; chan < 4; chan  )
{ 

ADS1115_ScanChannel(chan);
delay_ms(5);
ADS1115_ReadRawData(&dataTemp);
//Keep 3 decimal places and filter
ADC_Temp_Filter[chan] = (int32_t)(ADS1115_GetVoltage()*1000);
ADC_Temp[chan] = Filter_MovingAverage(ADC_Temp_Filter[chan],chan)/1000.0;
}
  • 由于多通道切换的通道稳定等待时间的存在,上面的代码耗时将会很长(>20ms)。如果在中断服务函数中执行了这些代码,将会导致单片机的性能严重下降。因此,需要考虑在定时器更新中断服务函数中周期性的调用void ADS1115_RefreshAllChannel()。每次调用将刷新一个通道的数据,四次调用便可以将全部通道的数据都刷新一遍,大大提高了代码的运行效率。示例如下:
    • 软件定时器1以40Hz的频率周期性的调用void ADS1115_RefreshAllChannel(),以获得10Hz的全通道数据刷新速度。
    • 软件定时器0以10Hz的频率处理ADS1115的原始采样数据(换算成电压、滤波、校正等)与其他传感器的数据。
    • 通过多个更新周期不同的定时器的配合,可以在无代码延时的情况下解决不同传感器数据采集速度不一致的问题,周期性的调用代替了延时等待。
代码语言:javascript复制
/** * @brief 设置软件定时器,主要包括初始化与开启各个软件定时器 */
void User_SetupTimer()
{ 

//Poll frequency of software timer is 1KHz
//SW_Timer0 for refresh sensor data and modbus action. Freq. = 10Hz
SoftwareTimer_Init(0, 100, User_SoftwareTimer0_Handler, -1);
//SW_Timer1 for ads1115 data acquisition. Freq. = 40Hz
SoftwareTimer_Init(1, 25, User_SoftwareTimer1_Handler, -1);
//SW_Timer2 for RS485 scan. Freq. = 10Hz
SoftwareTimer_Init(2, 100, User_SoftwareTimer2_Handler, -1);
//SW_Timer3 for MTSICS. Freq. = 10Hz
SoftwareTimer_Init(3, 100, User_SoftwareTimer3_Handler, -1);
//SW_Timer4 for RS100 (Printer has not been used). Freq. = 10Hz
SoftwareTimer_Init(4, 100, User_SoftwareTimer4_Handler, -1);
SoftwareTimer_Enable(0);
SoftwareTimer_Enable(1);
SoftwareTimer_Enable(2);
SoftwareTimer_Enable(3);
SoftwareTimer_Enable(4);
}
/** * @brief 软件定时器1的更新服务函数 */
void User_SoftwareTimer0_Handler()
{ 

User_RefreshData();
User_RefreshAction();
}
/** * @brief 软件定时器2的更新服务函数 */
void User_SoftwareTimer1_Handler()
{ 

ADS1115_RefreshAllChannel();
}
/** * @brief This function is called by timer update interrupt handler, which will be executed periodically. */
void User_RefreshData()
{ 

for (uint8_t chan = 0; chan < 4; chan  )
{ 

ADC_Voltage[chan] = Filter_MovingAverage(ADS1115_Data[chan]*10,chan)/10.0;			//unit: mV, resolution: 0.1 mV
}
SensorData[0].value = OmronEC55_PV;
SensorData[1].value = SGDAQ_PV;
SensorData[2].value = (float)RS100_Count*0.0005;
SensorData[3].value = MTSICS_Weight;
//Pt100 - 1 PV (℃)
SensorData[4].value = Pt100_RtoT(User_VoltageToResistance(ADC_Voltage[0]));
SensorData[5].value = ADC_Voltage[1];
SensorData[6].value = ADC_Voltage[2];
SensorData[7].value = ADC_Voltage[3];
//校正后的SGDAQ_PV
SensorData[8].value = SensorData[1].value * HoldingReg_GetData(30)   HoldingReg_GetData(31);
}
  • 在两个或者多个ADS1115组合使用的情况下,对于多通道数据的采样处理应该遵循与上面相似的策略。以两个ADS1115通过软件模拟I2C组成的8通道数据采集功能为例:
代码语言:javascript复制
volatile int16_t User_ADS1115Data[8];
/** * @brief 软件定时器2的服务函数 * 主要实现了两个ADS1115(总共8路)的数据采集功能。 */
void User_SoftwareTimer2_Handler()
{ 

static uint8_t chan = 0;
static uint8_t isData1Copyed = 0;
static uint8_t isData2Copyed = 0;
//Operate ADS1115-1
if(chan < 4)
{ 

if(isData1Copyed == 0)
{ 

//将ADS1115-2采样的数据复制到User_ADS1115Data[]中
for(uint8_t i = 0; i <4; i  )
{ 

User_ADS1115Data[i 4] = ADS1115_RawData[i];
}
isData1Copyed = 1;
isData2Copyed = 0;
}
//切换I2C总线至ADS1115-1
I2C_Virtual_SwitchBus(ADS1115_SDA_PORT, ADS1115_SDA_PIN, ADS1115_SCL_PORT, ADS1115_SCL_PIN);
ADS1115_RefreshAllChannel();
}
//Operate ADS1115-2
else
{ 

if(isData2Copyed == 0)
{ 

//将ADS1115-1采样的数据复制到User_ADS1115Data[]中
for(uint8_t i = 0; i <4; i  )
{ 

User_ADS1115Data[i] = ADS1115_RawData[i];
}
isData2Copyed = 1;
isData1Copyed = 0;
}
//切换I2C总线至ADS1115-2
I2C_Virtual_SwitchBus(ADS1115_SDA_PORT_2, ADS1115_SDA_PIN_2, ADS1115_SCL_PORT_2, ADS1115_SCL_PIN_2);
ADS1115_RefreshAllChannel();
}
chan  ;
if (chan > 7)
{ 

chan = 0;
}
}

使用实时操作系统

在实时操作系统中使用延时函数时,任务调度器会自动切换执行低优先级任务,因此延时函数(由操作系统提供的API)不存在浪费CPU资源的问题。此时一般的做法是建立一个ADS1115的数据采集任务,在这个任务中轮询采集需要的数据。

代码语言:javascript复制
void ADS1115Daq_task(void *pvParameters)
{ 

int16_t adcDataTemp[4] = { 
0};
TickType_t ticks = xTaskGetTickCount();
//配置ADS1115端口,由于采用了软件I2C通讯,因此可以直接调用虚拟I2C中的配置函数完成配置
I2C_Virtual_ConfigPort(ADS1115_SDA_PORT, ADS1115_SDA_PIN, ADS1115_SCL_PORT, ADS1115_SCL_PIN);
I2C_Virtual_SwitchBus(ADS1115_SDA_PORT, ADS1115_SDA_PIN, ADS1115_SCL_PORT, ADS1115_SCL_PIN);
//使用内置的快速配置模板完成ADS1115的配置
ADS1115_UserConfig2();
while (1)
{ 

for (uint8_t chan = 0; chan < 4; chan  )
{ 

//设置ADS1115的采样通道
ADS1115_ScanChannel(chan);
//调用RTOS提供的API延时10ms
vTaskDelay(10);
if(ADS1115_ReadRawData(&adcDataTemp[chan])!=0)
{ 

//保留小数点后三位精度
SensorData[chan].value = (float)(ADS1115_RawDataToVoltage(adcDataTemp[chan])*1000)/1000.0;
}
}
//100ms 一个处理周期
vTaskDelayUntil( &ticks, 100);
}
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/222887.html原文链接:https://javaforall.cn

0 人点赞