Files
gd32e230_uart_ring_buffer/Src/i2c.c
2025-08-16 04:06:46 +08:00

664 lines
23 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// Created by dell on 24-12-20.
//
#include "i2c.h"
/*!
\brief configure the GPIO ports
\param[in] none
\param[out] none
\retval none
*/
void i2c_gpio_config(void) {
/* enable IIC GPIO clock */
rcu_periph_clock_enable(RCU_GPIO_I2C);
/* connect I2C_SCL_PIN to I2C_SCL */
gpio_af_set(I2C_SCL_PORT, I2C_GPIO_AF, I2C_SCL_PIN);
/* connect I2C_SDA_PIN to I2C_SDA */
gpio_af_set(I2C_SDA_PORT, I2C_GPIO_AF, I2C_SDA_PIN);
/* configure GPIO pins of I2C */
gpio_mode_set(I2C_SCL_PORT, GPIO_MODE_AF, GPIO_PUPD_PULLUP, I2C_SCL_PIN);
gpio_output_options_set(I2C_SCL_PORT, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, I2C_SCL_PIN);
gpio_mode_set(I2C_SDA_PORT, GPIO_MODE_AF, GPIO_PUPD_PULLUP, I2C_SDA_PIN);
gpio_output_options_set(I2C_SDA_PORT, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, I2C_SDA_PIN);
}
/*!
\brief configure the I2CX interface
\param[in] none
\param[out] none
\retval none
*/
i2c_result_t i2c_config(void) {
/* configure I2C GPIO */
i2c_gpio_config();
/* enable I2C clock */
rcu_periph_clock_enable(RCU_I2C);
/* configure I2C clock */
i2c_clock_config(I2C0, I2C_SPEED, I2C_DTCY_2);
/* configure I2C address */
i2c_mode_addr_config(I2C0, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, 0xA0);
/* enable I2CX */
i2c_enable(I2C0);
/* enable acknowledge */
i2c_ack_config(I2C0, I2C_ACK_ENABLE);
return I2C_RESULT_SUCCESS;
}
/* wait for SCL to go high, return true if successful, false if timeout */
static bool i2c_wait_scl_high(uint16_t max_wait_time) {
while (max_wait_time--) {
if (gpio_input_bit_get(I2C_SCL_PORT, I2C_SCL_PIN)) {
return true;
}
delay_10us(1);
}
return false;
}
/* generate one manual SCL pulse; return true if SCL observed high (no stuck/overstretch) */
static bool i2c_generate_scl_pulse(void) {
GPIO_BC(I2C_SCL_PORT) = I2C_SCL_PIN; /* drive SCL low */
delay_10us(1);
GPIO_BOP(I2C_SCL_PORT) = I2C_SCL_PIN; /* release SCL (open-drain -> high via pull-up) */
return i2c_wait_scl_high(200); /* wait up to ~2ms for clock stretching release */
}
/*!
\brief reset I2C bus
\param[in] none
\param[out] none
\retval none
*/
i2c_result_t i2c_bus_reset(void) {
/* 1. Disable & deinit peripheral so pins can be fully controlled */
i2c_disable(I2C0);
i2c_deinit(I2C0);
#ifdef DEBUG_VERBOSE
printf("I2C bus reset\r\n");
#endif
/* 2. Configure SCL/SDA as GPIO open-drain outputs with pull-up and release them */
gpio_mode_set(I2C_SCL_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, I2C_SCL_PIN);
gpio_mode_set(I2C_SDA_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, I2C_SDA_PIN);
gpio_output_options_set(I2C_SCL_PORT, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, I2C_SCL_PIN);
gpio_output_options_set(I2C_SDA_PORT, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, I2C_SDA_PIN);
gpio_bit_set(I2C_SCL_PORT, I2C_SCL_PIN); /* release SCL */
gpio_bit_set(I2C_SDA_PORT, I2C_SDA_PIN); /* release SDA */
#ifdef DEBUG_VERBOSE
printf("I2C bus reset: SCL = %d, SDA = %d\r\n", gpio_input_bit_get(I2C_SCL_PORT, I2C_SCL_PIN), gpio_input_bit_get(I2C_SDA_PORT, I2C_SDA_PIN));
#endif
/* 3. Double sample to confirm bus state */
delay_10us(1);
bool scl_value1 = gpio_input_bit_get(I2C_SCL_PORT, I2C_SCL_PIN);
bool sda_value1 = gpio_input_bit_get(I2C_SDA_PORT, I2C_SDA_PIN);
delay_10us(1);
bool scl_value2 = gpio_input_bit_get(I2C_SCL_PORT, I2C_SCL_PIN);
bool sda_value2 = gpio_input_bit_get(I2C_SDA_PORT, I2C_SDA_PIN);
/* 4. If SCL low -> stuck (cannot proceed) */
if (!scl_value2) {
#ifdef DEBUG_VERBOSE
printf("I2C bus reset: SCL stuck low\r\n");
#endif
return I2C_RECOVERY_SCL_STUCK_LOW;
}
/* 5. Fast path: bus idle */
if (scl_value1 && sda_value1 && scl_value2 && sda_value2) {
i2c_config();
#ifdef DEBUG_VERBOSE
printf("I2C bus reset: bus idle\r\n");
#endif
return I2C_RECOVERY_OK;
}
/* 6. SDA low: attempt to free by generating up to I2C_RECOVERY_CLOCKS pulses */
if (scl_value2 && !sda_value2) {
bool sda_released = false;
#ifdef DEBUG_VERBOSE
printf("I2C bus reset: SCL will try to free SDA\r\n");
#endif
for (uint8_t i = 0; i < I2C_RECOVERY_CLOCKS && !sda_released; i++) {
if (!i2c_generate_scl_pulse()) {
return I2C_RECOVERY_SCL_STUCK_LOW; /* SCL failed to go high */
}
if (gpio_input_bit_get(I2C_SDA_PORT, I2C_SDA_PIN)) {
sda_released = true;
}
}
if (!sda_released) {
return I2C_RECOVERY_SDA_STUCK_LOW;
}
/* 7. Generate a STOP condition to leave bus in idle state */
#ifdef DEBUG_VERBOSE
printf("I2C bus reset: generating STOP condition\r\n");
#endif
gpio_bit_reset(I2C_SDA_PORT, I2C_SDA_PIN); /* SDA low */
delay_10us(1);
gpio_bit_set(I2C_SCL_PORT, I2C_SCL_PIN); /* ensure SCL high */
delay_10us(1);
gpio_bit_set(I2C_SDA_PORT, I2C_SDA_PIN); /* SDA rising while SCL high -> STOP */
delay_10us(1);
}
#ifdef DEBUG_VERBOSE
printf("I2C bus reset: bus recovered\r\n");
#endif
/* 8. Reconfigure & enable peripheral */
i2c_config();
return I2C_RECOVERY_OK;
}
/**
* @brief 扫描I2C总线查找连接的设备
*
* 该函数会扫描I2C总线上的所有地址1到126并尝试与每个地址进行通信。
* 如果在某个地址上发现了设备,则会打印出该设备的地址。
* 最后会打印出找到的设备总数。
*/
void i2c_scan(void) {
uint32_t timeout;
uint8_t address;
int found_devices = 0;
// printf("Scanning I2C bus...\r\n");
const char* msg1 = "Scanning I2C bus...\r\n";
for (uint8_t i = 0; msg1[i] != '\0'; i++) {
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TBE) == RESET) {}
usart_data_transmit(I2C_DEBUG_UART, msg1[i]);
}
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TC) == RESET) {}
for (address = 1; address < 127; address++) {
timeout = 0;
// 生成起始条件
while (i2c_flag_get(I2C0, I2C_FLAG_I2CBSY) && (timeout < I2C_TIME_OUT))
timeout++;
if (timeout >= I2C_TIME_OUT) {
continue; // 超时,跳过该地址
}
i2c_start_on_bus(I2C0);
timeout = 0;
// 等待起始条件发送完成
while (!i2c_flag_get(I2C0, I2C_FLAG_SBSEND) && (timeout < I2C_TIME_OUT))
timeout++;
if (timeout >= I2C_TIME_OUT) {
continue; // 超时,跳过该地址
}
i2c_master_addressing(I2C0, (address << 1), I2C_TRANSMITTER);
timeout = 0;
// 等待地址发送完成
while (!i2c_flag_get(I2C0, I2C_FLAG_ADDSEND) && (timeout < I2C_TIME_OUT))
timeout++;
if (timeout < I2C_TIME_OUT) {
i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND);
// printf("Found device at 0x%02X\r\n", address);
const char* msg2_prefix = "Found device at 0x";
for (uint8_t i = 0; msg2_prefix[i] != '\0'; i++) {
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TBE) == RESET) {}
usart_data_transmit(I2C_DEBUG_UART, msg2_prefix[i]);
}
// 发送地址的十六进制表示
uint8_t hex_chars[] = "0123456789ABCDEF";
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TBE) == RESET) {}
usart_data_transmit(I2C_DEBUG_UART, hex_chars[(address >> 4) & 0x0F]);
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TBE) == RESET) {}
usart_data_transmit(I2C_DEBUG_UART, hex_chars[address & 0x0F]);
const char* msg2_suffix = "\r\n";
for (uint8_t i = 0; msg2_suffix[i] != '\0'; i++) {
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TBE) == RESET) {}
usart_data_transmit(I2C_DEBUG_UART, msg2_suffix[i]);
}
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TC) == RESET) {}
found_devices++;
}
// 生成停止条件
i2c_stop_on_bus(I2C0);
timeout = 0;
while (i2c_flag_get(I2C0, I2C_FLAG_STPDET) && (timeout < I2C_TIME_OUT))
timeout++;
}
if (found_devices == 0) {
// printf("No I2C devices found.\r\n");
const char* msg3 = "No I2C devices found.\r\n";
for (uint8_t i = 0; msg3[i] != '\0'; i++) {
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TBE) == RESET) {}
usart_data_transmit(I2C_DEBUG_UART, msg3[i]);
}
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TC) == RESET) {}
} else {
// printf("Total %d I2C devices found.\r\n", found_devices);
const char* msg4_prefix = "Total ";
for (uint8_t i = 0; msg4_prefix[i] != '\0'; i++) {
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TBE) == RESET) {}
usart_data_transmit(I2C_DEBUG_UART, msg4_prefix[i]);
}
// 发送设备数量
if (found_devices >= 10) {
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TBE) == RESET) {}
usart_data_transmit(I2C_DEBUG_UART, '0' + (found_devices / 10));
}
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TBE) == RESET) {}
usart_data_transmit(I2C_DEBUG_UART, '0' + (found_devices % 10));
const char* msg4_suffix = " I2C devices found.\r\n";
for (uint8_t i = 0; msg4_suffix[i] != '\0'; i++) {
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TBE) == RESET) {}
usart_data_transmit(I2C_DEBUG_UART, msg4_suffix[i]);
}
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TC) == RESET) {}
}
}
i2c_result_t i2c_write_16bits(uint8_t slave_addr, uint8_t reg_addr, uint8_t data[2]) {
uint8_t state = I2C_STATE_START;
uint16_t timeout = 0;
uint8_t i2c_timeout_flag = 0;
/* parameter validation */
if (data == NULL || slave_addr > 0x7F) {
return I2C_RESULT_INVALID_PARAM;
}
/* enable acknowledge */
i2c_ack_config(I2C0, I2C_ACK_ENABLE);
while (!(i2c_timeout_flag)) {
switch (state) {
case I2C_STATE_START:
/* i2c master sends start signal only when the bus is idle */
while (i2c_flag_get(I2C0, I2C_FLAG_I2CBSY) && (timeout < I2C_TIME_OUT)) {
timeout++;
}
if (timeout < I2C_TIME_OUT) {
i2c_start_on_bus(I2C0);
timeout = 0;
state = I2C_STATE_SEND_ADDRESS;
} else {
timeout = 0;
state = I2C_STATE_START;
#ifdef DEBUG_VERBOES
printf("i2c bus is busy in WRITE BYTE!\n");
#endif
}
break;
case I2C_STATE_SEND_ADDRESS:
/* i2c master sends START signal successfully */
while ((!i2c_flag_get(I2C0, I2C_FLAG_SBSEND)) && (timeout < I2C_TIME_OUT)) {
timeout++;
}
if (timeout < I2C_TIME_OUT) {
i2c_master_addressing(I2C0, slave_addr << 1, I2C_TRANSMITTER);
timeout = 0;
state = I2C_STATE_CLEAR_ADDRESS;
} else {
timeout = 0;
state = I2C_STATE_START;
#ifdef DEBUG_VERBOES
printf("i2c master sends start signal timeout in WRITE BYTE!\n");
#endif
}
break;
case I2C_STATE_CLEAR_ADDRESS:
/* address flag set means i2c slave sends ACK */
while ((!i2c_flag_get(I2C0, I2C_FLAG_ADDSEND)) && (timeout < I2C_TIME_OUT)) {
timeout++;
}
if (timeout < I2C_TIME_OUT) {
i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND);
timeout = 0;
state = I2C_STATE_TRANSMIT_DATA;
} else {
timeout = 0;
state = I2C_STATE_START;
#ifdef DEBUG_VERBOES
printf("i2c master clears address flag timeout in WRITE BYTE!\n");
#endif
}
break;
case I2C_STATE_TRANSMIT_DATA:
/* wait until the transmit data buffer is empty */
while ((!i2c_flag_get(I2C0, I2C_FLAG_TBE)) && (timeout < I2C_TIME_OUT)) {
timeout++;
}
if (timeout < I2C_TIME_OUT) {
/* send IIC register address */
i2c_data_transmit(I2C0, reg_addr);
timeout = 0;
} else {
timeout = 0;
state = I2C_STATE_START;
#ifdef DEBUG_VERBOES
printf("i2c master sends data timeout in WRITE BYTE!\n");
#endif
}
/* wait until BTC bit is set */
while ((!i2c_flag_get(I2C0, I2C_FLAG_BTC)) && (timeout < I2C_TIME_OUT)) {
timeout++;
}
if (timeout < I2C_TIME_OUT) {
/* send register MSB value */
i2c_data_transmit(I2C0, data[0]);
timeout = 0;
} else {
timeout = 0;
state = I2C_STATE_START;
#ifdef DEBUG_VERBOES
printf("i2c master sends MSB data timeout in WRITE BYTE!\n");
#endif
}
/* wait until BTC bit is set */
while ((!i2c_flag_get(I2C0, I2C_FLAG_BTC)) && (timeout < I2C_TIME_OUT)) {
timeout++;
}
if (timeout < I2C_TIME_OUT) {
/* send register LSB value */
i2c_data_transmit(I2C0, data[1]);
timeout = 0;
state = I2C_STATE_STOP;
} else {
timeout = 0;
state = I2C_STATE_START;
#ifdef DEBUG_VERBOES
printf("i2c master sends LSB data timeout in WRITE BYTE!\n");
#endif
}
/* wait until BTC bit is set */
while ((!i2c_flag_get(I2C0, I2C_FLAG_BTC)) && (timeout < I2C_TIME_OUT)) {
timeout++;
}
if (timeout < I2C_TIME_OUT) {
state = I2C_STATE_STOP;
timeout = 0;
} else {
timeout = 0;
state = I2C_STATE_START;
#ifdef DEBUG_VERBOES
printf("i2c master sends data timeout in WRITE BYTE!\n");
#endif
}
break;
case I2C_STATE_STOP:
/* send a stop condition to I2C bus */
i2c_stop_on_bus(I2C0);
/* i2c master sends STOP signal successfully */
while ((I2C_CTL0(I2C0) & I2C_CTL0_STOP) && (timeout < I2C_TIME_OUT)) {
timeout++;
}
if (timeout < I2C_TIME_OUT) {
timeout = 0;
state = I2C_STATE_END;
i2c_timeout_flag = I2C_OK;
} else {
timeout = 0;
state = I2C_STATE_START;
#ifdef DEBUG_VERBOES
printf("i2c master sends stop signal timeout in WRITE BYTE!\n");
#endif
}
break;
default:
state = I2C_STATE_START;
i2c_timeout_flag = I2C_OK;
timeout = 0;
#ifdef DEBUG_VERBOES
printf("i2c master sends start signal in WRITE BYTE.\n");
#endif
break;
}
}
return I2C_RESULT_SUCCESS;
}
i2c_result_t i2c_read_16bits(uint8_t slave_addr, uint8_t reg_addr, uint8_t *data) {
i2c_state_t state = I2C_STATE_START;
uint16_t timeout = 0;
uint8_t retry_count = 0;
bool write_phase = true;
uint8_t data_index = 0;
// 参数检查:防止空指针和非法地址
if (data == NULL || slave_addr > 0x7F) {
return I2C_RESULT_INVALID_PARAM;
}
/* enable acknowledge */
i2c_ack_config(I2C0, I2C_ACK_ENABLE);
while (retry_count < (uint8_t)I2C_MAX_RETRY) {
switch (state) {
case I2C_STATE_START:
timeout = 0;
// wait for bus to be idle
while (i2c_flag_get(I2C0, I2C_FLAG_I2CBSY) && (timeout < I2C_TIME_OUT)) {
timeout++;
}
if (timeout >= I2C_TIME_OUT) {
state = I2C_STATE_ERROR;
break;
}
// send start condition
i2c_start_on_bus(I2C0);
state = I2C_STATE_SEND_ADDRESS;
timeout = 0;
break;
case I2C_STATE_SEND_ADDRESS:
/* wait for start condition to be sent */
while ((!i2c_flag_get(I2C0, I2C_FLAG_SBSEND)) && (timeout < I2C_TIME_OUT)) {
timeout++;
}
if (timeout >= I2C_TIME_OUT) {
state = I2C_STATE_ERROR;
break;
}
// send slave address
if (write_phase) {
/* write phase: send address with write bit */
i2c_master_addressing(I2C0, (slave_addr << 1), I2C_TRANSMITTER);
} else {
/* read phase: send address with read bit */
i2c_master_addressing(I2C0, (slave_addr << 1) | 0x01, I2C_RECEIVER);
}
state = I2C_STATE_CLEAR_ADDRESS;
timeout = 0;
break;
case I2C_STATE_CLEAR_ADDRESS:
/* wait for address to be acknowledged */
while ((!i2c_flag_get(I2C0, I2C_FLAG_ADDSEND)) && (timeout < I2C_TIME_OUT)) {
timeout++;
}
if (timeout >= I2C_TIME_OUT) {
state = I2C_STATE_ERROR;
break;
}
/* clear address flag, address flag set means i2c slave sends ACK */
i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND);
if (write_phase) {
state = I2C_STATE_TRANSMIT_DATA;
} else {
/* 读阶段配置ACK位置和禁用ACK针对2字节读取 */
i2c_ackpos_config(I2C0, I2C_ACKPOS_NEXT);
i2c_ack_config(I2C0, I2C_ACK_DISABLE);
state = I2C_STATE_RECEIVE_DATA;
data_index = 0;
}
timeout = 0;
break;
case I2C_STATE_TRANSMIT_DATA:
/* wait for transmit buffer to be empty */
while ((!i2c_flag_get(I2C0, I2C_FLAG_TBE)) && (timeout < I2C_TIME_OUT)) {
timeout++;
}
if (timeout >= I2C_TIME_OUT) {
state = I2C_STATE_ERROR;
break;
}
/* send register address */
i2c_data_transmit(I2C0, reg_addr);
state = I2C_STATE_RESTART;
timeout = 0;
break;
case I2C_STATE_RESTART:
/* wait for byte transfer complete BTC: Bit Transfer Complete */
while ((!i2c_flag_get(I2C0, I2C_FLAG_BTC)) && (timeout < I2C_TIME_OUT)) {
timeout++;
}
if (timeout >= I2C_TIME_OUT) {
state = I2C_STATE_ERROR;
break;
}
/* generate repeated start condition */
i2c_start_on_bus(I2C0);
/* wait for repeated start condition to be sent */
timeout = 0;
while ((!i2c_flag_get(I2C0, I2C_FLAG_SBSEND)) && (timeout < I2C_TIME_OUT)) {
timeout++;
}
if (timeout >= I2C_TIME_OUT) {
state = I2C_STATE_ERROR;
break;
}
/* send slave address with read bit */
i2c_master_addressing(I2C0, (slave_addr << 1) | 0x01, I2C_RECEIVER);
/* switch to read phase */
write_phase = false;
state = I2C_STATE_CLEAR_ADDRESS;
timeout = 0;
break;
case I2C_STATE_RECEIVE_DATA:
/* 等待BTC标志表示第一个字节接收完成 */
while ((!i2c_flag_get(I2C0, I2C_FLAG_BTC)) && (timeout < I2C_TIME_OUT)) {
timeout++;
}
if (timeout >= I2C_TIME_OUT) {
state = I2C_STATE_ERROR;
break;
}
/* 发送STOP条件 */
i2c_stop_on_bus(I2C0);
/* 读取第一个字节 */
data[0] = i2c_data_receive(I2C0);
/* 等待第二个字节接收完成 */
timeout = 0;
while ((!i2c_flag_get(I2C0, I2C_FLAG_RBNE)) && (timeout < I2C_TIME_OUT)) {
timeout++;
}
if (timeout >= I2C_TIME_OUT) {
state = I2C_STATE_ERROR;
break;
}
/* 读取第二个字节 */
data[1] = i2c_data_receive(I2C0);
state = I2C_STATE_STOP;
break;
case I2C_STATE_STOP:
/* wait for stop condition to complete */
while ((I2C_CTL0(I2C0) & I2C_CTL0_STOP) && (timeout < I2C_TIME_OUT)) {
timeout++;
}
if (timeout >= I2C_TIME_OUT) {
state = I2C_STATE_ERROR;
break;
}
/* i2c master sends STOP signal successfully */
/* success */
return I2C_RESULT_SUCCESS;
case I2C_STATE_ERROR:
/* send stop condition to release bus */
i2c_stop_on_bus(I2C0);
retry_count++;
if (retry_count >= I2C_MAX_RETRY) {
#ifdef DEBUG_VERBOES
printf("IIC read failed after %d retries\n", I2C_RETRY_MAX);
#endif
return I2C_RESULT_ERROR;
}
/* reset state machine for retry */
state = I2C_STATE_START;
write_phase = true;
data_index = 0;
timeout = 0;
/* small delay before retry */
delay_10us(10);
break;
default:
state = I2C_STATE_START;
break;
}
}
return I2C_RESULT_TIMEOUT;
}
#ifdef DEBUG_VERBOSE
/*!
\brief get status string for debugging
\param[in] status: i2c_status_t value
\param[out] none
\retval const char* status string
*/
const char* i2c_get_status_string(i2c_result_t status) {
switch (status) {
case I2C_RESULT_SUCCESS:
return "SUCCESS";
case I2C_RESULT_TIMEOUT:
return "TIMEOUT";
case I2C_RESULT_NACK:
return "NACK";
case I2C_RESULT_BUS_BUSY:
return "BUS_BUSY";
case I2C_RESULT_ERROR:
return "ERROR";
case I2C_RESULT_INVALID_PARAM:
return "INVALID_PARAM";
default:
return "UNKNOWN";
}
}
#endif