STM32 驱动 TSL2591 光照传感器
文章目录
一、简介
TSL2591 是一款高灵敏度的光到数字转换器,可将光强转换为数字信号输出,并支持直接通过 I²C 接口进行通信。该器件在一个 CMOS 集成电路上集成了一个宽带光电二极管(可见光加红外)和一个对红外有响应的光电二极管。两个集成式 ADC(模数转换器)将光电二极管的电流转换为数字输出,该输出表示每个通道上测量的辐照度。这一数字输出可输入到微处理器中,并通过经验公式将其转换为照度(环境光水平,单位为lux),以近似人眼的响应。
该设备通过标准的两线I²C串行总线进行通信。因此,TSL2591可以很容易地连接到微控制器或嵌入式控制器。无需外部电路进行信号调理。由于该设备输出的是数字信号,与模拟信号相比,其输出对噪声具有很强的抗干扰能力。
TSL2591还支持中断功能,这有助于简化系统并提高系统效率,从而无需持续轮询传感器以获取光强值。中断功能的主要目的是检测光强的显著变化。用户可以通过设置光强变化的程度和持续时间来定义“显著变化”的概念。该设备能够设置两组阈值,分别对应于当前光强的上限和下限。当转换结果超出这些阈值之一时,将产生中断。一组阈值可以配置为仅在环境光持续超过某个可配置时间(即持续性)时触发中断,而另一组阈值则可以配置为在立即超过阈值时触发中断。

二、模组引脚功能
| VIN | 电源 5V |
|---|---|
| GND | 电源地 |
| 3VO | 电源 3.3V |
| INT | 中断输出引脚 低电平有效 |
| SCL | 时钟线 |
| SDA | 数据线 |
三、寄存器介绍
1.Enable Register (0x00)
控制 TSL2591 启动、关闭、功能启用
| Bit 位 | 名称 | 说明 |
|---|---|---|
| 7 | NPIEN | 当该位被置位(为1)时,只要光照超过中断阈值,就会立刻触发中断,忽略“持久性过滤器” |
| 6 | SAI | 当该位被置位(为1)时,一旦生成了中断,设备将在当前 ALS(光强采样)周期结束后自动进入休眠(省电)模式 |
| 5 | Reserved* | 保留,0 |
| 4 | AIEN | 当该位被置位(设为1)时,允许产生 ALS 中断,受“持久性过滤器(Persist Filter)”限制 |
| 3:2 | Reserved | 保留,0 |
| 1 | AEN | 使能 ALS(光强感应)功能,1=开启,0=关闭 |
| 0 | PON | 打开内部振荡器(总开关),使计时器和 ADC 通道工作,1=上电,0=下电 |
2.Control Register (0x01)
| 位 | 名称 | 说明 |
|---|---|---|
| 7 | SRESET | 系统复位位,写1触发复位,自动清零 |
| 6 | 保留位 | 保留,0 |
| 5:4 | AGAIN | 增益设置: |
| 00 - 低增益 | ||
| 01 - 中增益 | ||
| 10 - 高增益 | ||
| 11 - 最大增益 | ||
| 3 | 保留位 | 保留,0 |
| 2:0 | ATIME | 采样时间设置: |
| 000 - 100 ms(最大计数36863) | ||
| 001 - 200 ms(最大计数65535) | ||
| 010 - 300 ms(最大计数65535) | ||
| 011 - 400 ms(最大计数65535) | ||
| 100 - 500 ms(最大计数65535) | ||
| 101 - 600 ms(最大计数65535) |
3.ALS Interrupt Threshold Register (0x04 − 0x0B)
ALS 中断阈值寄存器提供用于比较功能的高低触发点,以生成中断。当通道 0 数据(C0DATA)低于设定的低阈值,或高于设定的高阈值时,INT 引脚上会触发中断信号。
如果 C0DATA 超过了带持久阈值(寄存器地址 0x04 至 0x07)并且持续的周期数达到了 PERSIST 寄存器中配置的持久周期数,则会触发中断。
如果 C0DATA 超过了无持久阈值(寄存器地址 0x08 至 0x0B),则会在当前积分周期结束后立即触发中断。
中断状态虽然可以在 STATUS 寄存器(0x13)中查看,但只有当 ENABLE 寄存器(0x00)中的 AIEN(ALS 中断使能)或 NPIEN(无持久中断使能)被启用时,INT 引脚才会输出中断信号。
设备上电后,中断阈值寄存器的默认值为 0x00
| 寄存器地址 | 名称 | 位范围 | 说明 |
|---|---|---|---|
| 0x04 | AILTL | 7:0 | ALS低阈值的低字节 |
| 0x05 | AILTH | 7:0 | ALS低阈值的高字节 |
| 0x06 | AIHTL | 7:0 | ALS高阈值的低字节 |
| 0x07 | AIHTH | 7:0 | ALS高阈值的高字节 |
| 0x08 | NPAILTL | 7:0 | 无持久ALS低阈值的低字节 |
| 0x09 | NPAILTH | 7:0 | 无持久ALS低阈值的高字节 |
| 0x0A | NPAIHTL | 7:0 | 无持久ALS高阈值的低字节 |
| 0x0B | NPAIHTH | 7:0 | 无持久ALS高阈值的高字节 |
4.PERSIST Register (0x0C)
中断持久过滤器用于设置连续多少个超出范围的 ALS 周期数,才会触发中断。
超出范围的判断依据是将通道0数据(C0DATA,寄存器地址 0x14 和 0x15)与中断阈值寄存器(0x04 至 0x07)进行比较。无持久(No Persist)ALS中断不受此持久过滤器影响。
设备上电时,中断持久过滤器寄存器的默认值为 0x00。
| 位范围 | 名称 | 说明 | APERS(二进制) | 说明 |
|---|---|---|---|---|
| 7:4 | Reserved | 保留,0 | ||
| 3:0 | APERS | ALS中断持久过滤器,设置连续超限次数 | 0000 (0x0) | 每个ALS周期都会触发中断 |
| 0001 (0x1) | 任意一个值超出阈值即可触发中断 | |||
| 0010 (0x2) | 连续2次超出阈值触发 | |||
| 0011 (0x3) | 连续3次超出阈值触发 | |||
| 0100 (0x4) | 连续5次超出阈值触发 | |||
| 0101 (0x5) | 连续10次超出阈值触发 | |||
| 0110 (0x6) | 连续15次超出阈值触发 | |||
| 0111 (0x7) | 连续20次超出阈值触发 | |||
| 1000 (0x8) | 连续25次超出阈值触发 | |||
| 1001 (0x9) | 连续30次超出阈值触发 | |||
| 1010 (0xA) | 连续35次超出阈值触发 | |||
| 1011 (0xB) | 连续40次超出阈值触发 | |||
| 1100 (0xC) | 连续45次超出阈值触发 | |||
| 1101 (0xD) | 连续50次超出阈值触发 | |||
| 1110 (0xE) | 连续55次超出阈值触发 | |||
| 1111 (0xF) | 连续60次超出阈值触发 |
5.ALS Data Register (0x14 - 0x17)
ALS(环境光)数据以两个 16 位的值存储,每个通道各占一个 16 位数值。
当读取任一通道的低字节时,该通道的高字节会被锁存到一个影子寄存器(shadow register)中。这个影子寄存器确保读取的高、低字节来自于同一个 ALS 积分周期,即使在读取这两个字节之间发生了新的积分周期,数据仍然保持一致。
每个通道的影子寄存器是独立工作的。因此,为了最小化通道 CH0 和 CH1 之间的不同步(数据偏差),建议按顺序连续读取全部四个 ADC 字节。
| 寄存器地址 | 位范围 | 字段名 | 说明 |
|---|---|---|---|
| 0x14 | 7:0 | C0DATAL | ALS 通道 0(CH0)数据低字节 |
| 0x15 | 7:0 | C0DATAH | ALS 通道 0(CH0)数据高字节 |
| 0x16 | 7:0 | C1DATAL | ALS 通道 1(CH1)数据低字节 |
| 0x17 | 7:0 | C1DATAH | ALS 通道 1(CH1)数据高字节 |
四、程序
TSL2591.C
#include "DEV_Config.h"
#include "TSL2591.h"
UBYTE TSL2591_Gain, TSL2591_Time;
/******************************************************************************
function: Read one byte of data to TSL2591 via I2C
parameter:
Addr: Register address
Info:
******************************************************************************/
static UBYTE TSL2591_Read_Byte(UBYTE Addr)
{
Addr = Addr | COMMAND_BIT;
return I2C_Read_Byte(Addr);
}
/******************************************************************************
function: Read one word of data to TSL2591 via I2C
parameter:
Addr: Register address
Info:
******************************************************************************/
static UWORD TSL2591_Read_Word(UBYTE Addr)
{
Addr = Addr | COMMAND_BIT;
return I2C_Read_Word(Addr);
}
/******************************************************************************
function: Send one byte of data to TSL2591 via I2C
parameter:
Addr: Register address
Value: Write to the value of the register
Info:
******************************************************************************/
static void TSL2591_Write_Byte(UBYTE Addr, UBYTE Value)
{
Addr = Addr | COMMAND_BIT;
I2C_Write_Byte(Addr, Value);
}
/******************************************************************************
function: Enable TSL2591
parameter:
Info:
******************************************************************************/
void TSL2591_Enable(void)
{
TSL2591_Write_Byte(ENABLE_REGISTER, \
ENABLE_AIEN | ENABLE_POWERON | ENABLE_AEN | ENABLE_NPIEN);
}
/******************************************************************************
function: Disable TSL2591
parameter:
Info:
******************************************************************************/
void TSL2591_Disable(void)
{
TSL2591_Write_Byte(ENABLE_REGISTER, \
ENABLE_POWEROFF);
}
/******************************************************************************
function: Read TSL2591 gain
parameter:
Info:
******************************************************************************/
UBYTE TSL2591_Get_Gain(void)
{
/*************************************************
LOW_AGAIN = (0X00) (1x)
MEDIUM_AGAIN = (0X10) (25x)
HIGH_AGAIN = (0X20) (428x)
MAX_AGAIN = (0x30) (9876x)
*************************************************/
UBYTE data;
data = TSL2591_Read_Byte(CONTROL_REGISTER);
TSL2591_Gain = data & 0x30;
return data & 0x30;
}
/******************************************************************************
function: Set the TSL2591 gain
parameter:
Info:
******************************************************************************/
void TSL2591_Set_Gain(UBYTE Gain)
{
UBYTE control=0;
if(Gain == LOW_AGAIN || Gain == MEDIUM_AGAIN \
|| Gain == HIGH_AGAIN || Gain == MAX_AGAIN){
control = TSL2591_Read_Byte(CONTROL_REGISTER);
control &= 0xCf; //0b11001111
control |= Gain;
TSL2591_Write_Byte(CONTROL_REGISTER, control);
TSL2591_Gain = Gain;
}else{
printf("Gain Parameter Error\r\n");
}
}
/******************************************************************************
function: Get the TSL2591 Integral Time
parameter:
Info:
******************************************************************************/
UBYTE TSL2591_Get_IntegralTime()
{
UBYTE control=0;
/************************************************************
ATIME_100MS = (0x00) 100 millis MAX COUNT 36863
ATIME_200MS = (0x01) 200 millis MAX COUNT 65535
ATIME_300MS = (0x02) 300 millis MAX COUNT 65535
ATIME_400MS = (0x03) 400 millis MAX COUNT 65535
ATIME_500MS = (0x04) 500 millis MAX COUNT 65535
ATIME_600MS = (0x05) 600 millis MAX COUNT 65535
************************************************************/
control = TSL2591_Read_Byte(CONTROL_REGISTER);
TSL2591_Time = control & 0x07;
return control & 0x07; //0b00000111
}
/******************************************************************************
function: Set the TSL2591 Integral Time
parameter:
Info:
******************************************************************************/
void TSL2591_Set_IntegralTime(UBYTE Time)
{
UBYTE control=0;
if(Time < 0x06){
control = TSL2591_Read_Byte(CONTROL_REGISTER);
control &= 0xf8; //0b11111000
control |= Time;
TSL2591_Write_Byte(CONTROL_REGISTER, control);
TSL2591_Time = Time;
}else{
printf("Integral Time Parameter Error\r\n");
}
}
/******************************************************************************
function: Read channel data
parameter:
Info:
******************************************************************************/
UWORD TSL2591_Read_Channel0(void)
{
return TSL2591_Read_Word(CHAN0_LOW);
}
UWORD TSL2591_Read_Channel1(void)
{
return TSL2591_Read_Word(CHAN1_LOW);
}
/******************************************************************************
function: TSL2591 Initialization
parameter:
Info:
******************************************************************************/
UBYTE TSL2591_Init(void)
{
DEV_I2C_Init(TSL2591_ADDRESS<<1);//8-bit address
printf("ID = 0x%X \r\n",TSL2591_Read_Byte(ID_REGISTER));
TSL2591_Enable();
TSL2591_Set_Gain(MEDIUM_AGAIN);//25X GAIN
TSL2591_Set_IntegralTime(ATIME_200MS);//200ms Integration time
TSL2591_Write_Byte(PERSIST_REGISTER, 0x01);//filter
TSL2591_Disable();
return 0;
}
/******************************************************************************
function: Read TSL2591 data to convert to Lux value
parameter:
Info:
******************************************************************************/
UWORD TSL2591_Read_Lux(void)
{
UWORD atime, max_counts,channel_0,channel_1;
TSL2591_Enable();
for(UBYTE i=0; i<TSL2591_Time+2; i++){
DEV_Delay_ms(100);
}
// if(DEV_Digital_Read(INT_PIN) == 1)
// printf("INT 0\r\n");
// else
// printf("INT 1\r\n");
channel_0 = TSL2591_Read_Channel0();
channel_1 = TSL2591_Read_Channel1();
TSL2591_Disable();
TSL2591_Enable();
TSL2591_Write_Byte(0xE7, 0x13);
TSL2591_Disable();
atime = 100 * TSL2591_Time + 100;
if(TSL2591_Time == ATIME_100MS){
max_counts = MAX_COUNT_100MS;
}else{
max_counts = MAX_COUNT;
}
UBYTE gain_t;
if (channel_0 >= max_counts || channel_1 >= max_counts){
gain_t = TSL2591_Get_Gain();
if(gain_t != LOW_AGAIN){
gain_t = ((gain_t>>4)-1)<<4;
TSL2591_Set_Gain(gain_t);
channel_0 = 0;
channel_1 = 0;
while(channel_0 <= 0 || channel_1 <=0){
channel_0 = TSL2591_Read_Channel0();
channel_1 = TSL2591_Read_Channel1();
}
DEV_Delay_ms(100);
}else{
printf("Numerical overflow!/r/n");
return 0;
}
}
double again;
again = 1.0;
if(TSL2591_Gain == MEDIUM_AGAIN){
again = 25.0;
}else if(TSL2591_Gain == HIGH_AGAIN){
again = 428.0;
}else if(TSL2591_Gain == MAX_AGAIN){
again = 9876.0;
}
double Cpl;
UWORD lux1,lux2=0;
Cpl = (atime * again) / LUX_DF;
lux1 = (int)((channel_0 - (2 * channel_1)) / Cpl);
// lux2 = ((0.6 * channel_0) - (channel_1)) / Cpl;
// This is a two segment lux equation where the first
// segment (Lux1) covers fluorescent and incandescent light
// and the second segment (Lux2) covers dimmed incandescent light
if(lux1>lux2){
return lux1;
}else{
return lux2;
}
}
/******************************************************************************
function: Set the TSL2591 interrupt
parameter:
SET_LOW : Interrupt low threshold
SET_HIGH: Interrupt high threshold
Info: Is the channel 0 AD data as a comparison
******************************************************************************/
void TSL2591_SET_InterruptThreshold(UWORD SET_LOW, UWORD SET_HIGH)
{
TSL2591_Enable();
TSL2591_Write_Byte(AILTL_REGISTER, SET_LOW & 0xFF);
TSL2591_Write_Byte(AILTH_REGISTER, SET_LOW >> 8);
TSL2591_Write_Byte(AIHTL_REGISTER, SET_HIGH & 0xFF);
TSL2591_Write_Byte(AIHTH_REGISTER, SET_HIGH >> 8);
TSL2591_Write_Byte(NPAILTL_REGISTER, 0 );
TSL2591_Write_Byte(NPAILTH_REGISTER, 0 );
TSL2591_Write_Byte(NPAIHTL_REGISTER, 0xff );
TSL2591_Write_Byte(NPAIHTH_REGISTER, 0xff );
TSL2591_Disable();
}
/******************************************************************************
function: Set the TSL2591 interrupt
parameter:
SET_LOW : Interrupt low threshold
SET_HIGH: Interrupt high threshold
Info: Based on Lux as a comparison
Need to use the function TSL2591_Read_Lux() to update the data
******************************************************************************/
void TSL2591_SET_LuxInterrupt(UWORD SET_LOW, UWORD SET_HIGH)
{
double Cpl;
double again;
UWORD atime, channel_1;
atime = 100 * TSL2591_Time + 100;
again = 1.0;
if(TSL2591_Gain == MEDIUM_AGAIN){
again = 25.0;
}else if(TSL2591_Gain == HIGH_AGAIN){
again = 428.0;
}else if(TSL2591_Gain == MAX_AGAIN){
again = 9876.0;
}
Cpl = (atime * again) / LUX_DF;
channel_1 = TSL2591_Read_Channel1();
SET_HIGH = (int)(Cpl * SET_HIGH)+ 2*channel_1-1;
SET_LOW = (int)(Cpl * SET_LOW)+ 2*channel_1+1;
TSL2591_Enable();
TSL2591_Write_Byte(AILTL_REGISTER, SET_LOW & 0xFF);
TSL2591_Write_Byte(AILTH_REGISTER, SET_LOW >> 8);
TSL2591_Write_Byte(AIHTL_REGISTER, SET_HIGH & 0xFF);
TSL2591_Write_Byte(AIHTH_REGISTER, SET_HIGH >> 8);
TSL2591_Write_Byte(NPAILTL_REGISTER, 0 );
TSL2591_Write_Byte(NPAILTH_REGISTER, 0 );
TSL2591_Write_Byte(NPAIHTL_REGISTER, 0xff );
TSL2591_Write_Byte(NPAIHTH_REGISTER, 0xff );
TSL2591_Disable();
}
UDOUBLE TSL2591_Read_FullSpectrum(void)
{
UDOUBLE data;
//Read the full spectrum (IR + visible) light and return its value
TSL2591_Enable();
// for(UBYTE i=0; i<TSL2591_Time+2; i++){
// DEV_Delay_ms(100);
// }
data = (TSL2591_Read_Channel1() << 16) | TSL2591_Read_Channel0();
TSL2591_Disable();
return data;
}
UWORD TSL2591_Read_Infrared()
{
UWORD data;
//Read the infrared light and return its value as a 16-bit unsigned number
TSL2591_Enable();
// for(UBYTE i=0; i<TSL2591_Time+2; i++){
// DEV_Delay_ms(100);
// }
data = TSL2591_Read_Channel1();
TSL2591_Disable();
return data;
}
UDOUBLE TSL2591_Read_Visible()
{
UDOUBLE full;
UWORD Ch1,Ch0;
TSL2591_Enable();
// for(UBYTE i=0; i<TSL2591_Time+2; i++){
// DEV_Delay_ms(100);
// }
Ch1 = TSL2591_Read_Channel1();
Ch0 = TSL2591_Read_Channel0();
TSL2591_Disable();
// full = (Ch1 << 16) | Ch0;
full = Ch0 - Ch1;
return full;
}
main.C
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "i2c.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "TSL2591.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_I2C1_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
printf("TSL2591_Light_Sensor Code\r\n");
DEV_ModuleInit();
TSL2591_Init();
//TSL2591_SET_InterruptThreshold(0x000f, 0xff00);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
printf("Lux = %d\r\n",TSL2591_Read_Lux());
TSL2591_SET_LuxInterrupt(50,200);
printf("Infrared light: %d\r\n", TSL2591_Read_Infrared());
printf("Visible light: %d\r\n", TSL2591_Read_Visible());
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the CPU, AHB and APB busses clocks
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB busses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
五、实验现象
室内光照情况下输出:

传感模块黑暗情况下输出:

小光源照射情况下输出:

注意该程序由互联网移植修改得出,实验数据未经过仪器验证,数据不建议作为环境标准使用,仅供参考,需要资料与工程可以留言邮箱。
更多推荐
所有评论(0)