• 方案介紹
    • 一、項(xiàng)目介紹
    • 二、項(xiàng)目設(shè)計思路
    • 三、代碼設(shè)計
    • 四、總結(jié)
  • 附件下載
  • 相關(guān)推薦
申請入駐 產(chǎn)業(yè)圖譜

基于STM32設(shè)計的人體健康檢測儀

04/14 09:06
1125
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

更多詳細(xì)資料請聯(lián)系.docx

共1個文件

一、項(xiàng)目介紹

當(dāng)前文章介紹基于STM32設(shè)計的人體健康檢測儀。設(shè)備采用STM32系列MCU作為主控芯片,配備血氧濃度傳感器(使用MAX30102血氧濃度檢測傳感器)、OLED屏幕和電池供電等外設(shè)模塊。設(shè)備可以廣泛應(yīng)用于醫(yī)療、健康等領(lǐng)域??梢詭椭t(yī)生和病人更好地了解病情變化,提高治療效果和生活質(zhì)量。設(shè)備也可以用于健康管理、運(yùn)動監(jiān)測等場景,幫助用戶了解自己的身體狀況,保持健康的生活方式。

在項(xiàng)目中,使用了KEIL作為開發(fā)平臺和工具,通過血氧模塊采集人體的心跳和血氧濃度參數(shù),并通過OLED屏幕顯示現(xiàn)在的心跳和血氧濃度。同時,通過指標(biāo)分析,提供采集到的數(shù)據(jù)與正常指標(biāo)比對,分析被檢測人員的健康狀態(tài)。采集的數(shù)據(jù)可通過藍(lán)牙或者WIFI傳遞給手機(jī)APP進(jìn)行處理,方便用戶隨時了解自己的身體狀況。

本設(shè)計采用STM32為主控芯片,搭配血氧濃度傳感器和OLED屏幕,實(shí)現(xiàn)了人體健康數(shù)據(jù)的采集和展示,并對采集到的數(shù)據(jù)進(jìn)行分析,判斷被檢測人員的健康狀態(tài)。同時,設(shè)計使用藍(lán)牙或WiFi將采集到的數(shù)據(jù)傳遞給手機(jī)APP進(jìn)行處理。

image-20230618132149185

image-20230618132108207

二、項(xiàng)目設(shè)計思路

2.1 硬件設(shè)計

(1)主控芯片:STM32系列MCU,負(fù)責(zé)驅(qū)動其他外設(shè)模塊;

(2)血氧濃度傳感器:使用MAX30102血氧濃度檢測傳感器,用于采集人體的心跳和血氧濃度參數(shù);

(3)OLED屏:用于顯示現(xiàn)在的心跳和血氧濃度;

2.2 軟件設(shè)計

(1) 通過血氧模塊采集人體的心跳和血氧濃度參數(shù);

(2) 通過OLED屏顯示現(xiàn)在的心跳和血氧濃度;

(3) 對采集到的數(shù)據(jù)進(jìn)行指標(biāo)分析,將采集到的數(shù)據(jù)與正常指標(biāo)比對,分析被檢測人員的健康狀態(tài);

(4) 采集的數(shù)據(jù)可通過藍(lán)牙或WiFi傳遞給手機(jī)APP進(jìn)行處理。

2.3 技術(shù)實(shí)現(xiàn)

(1)設(shè)計采用AD8232心電圖(ECG)模塊和MAX30102血氧模塊采集心跳和血氧濃度參數(shù),并通過I2C接口連接主控芯片STM32。

(2)OLED屏使用I2C接口與主控芯片STM32連接。

(3)采集到的數(shù)據(jù)通過算法進(jìn)行指標(biāo)分析,將采集到的數(shù)據(jù)與正常指標(biāo)比對,判斷被檢測人員的健康狀態(tài)。

(4)設(shè)備通過藍(lán)牙或WiFi將采集到的數(shù)據(jù)傳遞給手機(jī)APP進(jìn)行處理。

三、代碼設(shè)計

3.1 MAX30102血氧模塊代碼

I2C協(xié)議代碼:

#define MAX30102_I2C_ADDR 0xAE

void MAX30102_I2C_Init(void)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    I2C_InitTypeDef   I2C_InitStructure;

    /* Enable GPIOB clock */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    /* Enable I2C1 and I2C2 clock */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1 | RCC_APB1Periph_I2C2, ENABLE);

    // Configure I2C SCL and SDA pins
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // Open-drain output
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    // Configure I2C parameters
    I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
    I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
    I2C_InitStructure.I2C_OwnAddress1 = 0x00;
    I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
    I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    I2C_InitStructure.I2C_ClockSpeed = 100000; // 100KHz
    I2C_Init(I2C1, &I2C_InitStructure);

    // Enable I2C
    I2C_Cmd(I2C1, ENABLE);
}

void MAX30102_I2C_WriteReg(uint8_t reg, uint8_t value)
{
    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));

    I2C_GenerateSTART(I2C1, ENABLE);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));

    I2C_Send7bitAddress(I2C1, MAX30102_I2C_ADDR, I2C_Direction_Transmitter);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

    I2C_SendData(I2C1, reg);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

    I2C_SendData(I2C1, value);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

    I2C_GenerateSTOP(I2C1, ENABLE);
}

uint8_t MAX30102_I2C_ReadReg(uint8_t reg)
{
    uint8_t value;

    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));

    I2C_GenerateSTART(I2C1, ENABLE);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));

    I2C_Send7bitAddress(I2C1, MAX30102_I2C_ADDR, I2C_Direction_Transmitter);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

    I2C_SendData(I2C1, reg);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

    I2C_GenerateSTART(I2C1, ENABLE);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));

    I2C_Send7bitAddress(I2C1, MAX30102_I2C_ADDR, I2C_Direction_Receiver);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));

    I2C_AcknowledgeConfig(I2C1, DISABLE);
    value = I2C_ReceiveData(I2C1);

    I2C_GenerateSTOP(I2C1, ENABLE);

    return value;
}

void MAX30102_I2C_ReadArray(uint8_t reg, uint8_t* data, uint8_t len)
{
    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));

    I2C_GenerateSTART(I2C1, ENABLE);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));

    I2C_Send7bitAddress(I2C1, MAX30102_I2C_ADDR, I2C_Direction_Transmitter);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

    I2C_SendData(I2C1, reg);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

    I2C_GenerateSTART(I2C1, ENABLE);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));

    I2C_Send7bitAddress(I2C1, MAX30102_I2C_ADDR, I2C_Direction_Receiver);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));

    while(len > 1)
    {
        I2C_AcknowledgeConfig(I2C1, ENABLE);
        while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED));
        *data++ = I2C_ReceiveData(I2C1);
        len--;
    }

    I2C_AcknowledgeConfig(I2C1, DISABLE);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED));
    *data++ = I2C_ReceiveData(I2C1);

    I2C_GenerateSTOP(I2C1, ENABLE);
}

MAX30102的初始化函數(shù)和數(shù)據(jù)獲取函數(shù):

void MAX30102_Init(void)
{
    MAX30102_I2C_Init();

    // Reset the device
    MAX30102_I2C_WriteReg(0x09, 0x40);
    HAL_Delay(100);
    MAX30102_I2C_WriteReg(0x09, 0x00);

    // Set FIFO average to 4 samples
    MAX30102_I2C_WriteReg(0x08, 0x03);

    // Set LED pulse amplitude
    MAX30102_I2C_WriteReg(0x0C, 0x1F);
    MAX30102_I2C_WriteReg(0x0D, 0x1F);

    // Set sample rate to 100Hz
    MAX30102_I2C_WriteReg(0x0F, 0x04);

    // Enable the red LED only
    MAX30102_I2C_WriteReg(0x11, 0x02);

    // Read the temperature value to start a reading
    MAX30102_I2C_ReadReg(0x1F);
}

uint32_t MAX30102_GetHeartRate(void)
{
    uint8_t buffer[MAX30102_FIFO_DEPTH*4];
    MAX30102_Data sensor_data = {0};
    uint16_t ir_value;
    uint16_t red_value;
    uint8_t byte_count, fifo_overflow;

    // Check if any data is available in FIFO
    byte_count = MAX30102_I2C_ReadReg(0x06) - MAX30102_I2C_ReadReg(0x04);
    if(byte_count > 0)
    {
        fifo_overflow = MAX30102_I2C_ReadReg(0x09) & 0x80;

        // Read the data from FIFO
        MAX30102_I2C_ReadArray(0x07, buffer, byte_count);

        // Parse the data
        for(int i=0; i<byte_count; i+=4)
        {
            ir_value = ((uint16_t)buffer[i] << 8) | buffer[i+1];
            red_value = ((uint16_t)buffer[i+2] << 8) | buffer[i+3];

            // Update the sensor data
            MAX30102_UpdateData(&sensor_data, ir_value, red_value);
        }

        if(!fifo_overflow && MAX30102_CheckForBeat(sensor_data.IR_AC_Signal_Current))
        {
            return MAX30102_HeartRate(sensor_data.IR_AC_Signal_Previous, 16);
        }
    }

    return 0;
}

數(shù)據(jù)處理函數(shù):

void MAX30102_UpdateData(MAX30102_Data* data, uint16_t ir_value, uint16_t red_value)
{
    int32_t ir_val_diff = ir_value - data->IR_AC_Signal_Current;
    int32_t red_val_diff = red_value - data->Red_AC_Signal_Current;

    // Update IR AC and DC signals
    data->IR_AC_Signal_Current = (ir_val_diff + (7 * data->IR_AC_Signal_Previous)) / 8;
    data->IR_DC_Signal_Current = (ir_value + data->IR_AC_Signal_Current + (2 * data->IR_DC_Signal_Current)) / 4;
    data->IR_AC_Signal_Previous = data->IR_AC_Signal_Current;

    // Update Red AC and DC signals
    data->Red_AC_Signal_Current = (red_val_diff + (7 * data->Red_AC_Signal_Previous)) / 8;
    data->Red_DC_Signal_Current = (red_value + data->Red_AC_Signal_Current + (2 * data->Red_DC_Signal_Current)) / 4;
    data->Red_AC_Signal_Previous = data->Red_AC_Signal_Current;

    // Update IR and Red AC signal peak-to-peak values
    if(data->IR_AC_Signal_Current > data->IR_AC_Max)
        data->IR_AC_Max = data->IR_AC_Signal_Current;
    else if(data->IR_AC_Signal_Current < data->IR_AC_Min)
        data->IR_AC_Min = data->IR_AC_Signal_Current;

    if(data->Red_AC_Signal_Current > data->Red_AC_Max)
        data->Red_AC_Max = data->Red_AC_Signal_Current;
    else if(data->Red_AC_Signal_Current < data->Red_AC_Min)
        data->Red_AC_Min = data->Red_AC_Signal_Current;
}

uint8_t MAX30102_CheckForBeat(int32_t ir_val)
{
    static uint8_t beat_detection_enabled = 1;
    static uint32_t last_beat_time = 0;
    static int32_t threshold = 0x7FFFFF;

    uint32_t delta_time;
    int32_t beat_amplitude;

    if(beat_detection_enabled)
    {
        // Increment the beat counter
        MAX30102_beat_counter++;

        // Calculate the threshold value
        threshold += (ir_val - threshold) / 8;

        // Check if a beat has occurred
        if(ir_val > threshold && MAX30102_beat_counter > 20)
        {
            delta_time = micros() - last_beat_time;
            last_beat_time = micros();
            beat_amplitude = ir_val - threshold;
            if(delta_time < 1000 || delta_time > 2000 || beat_amplitude < 20 ||
            beat_amplitude > 1000) { return 0; }
                   // Reset the beat counter and set the threshold value
        MAX30102_beat_counter = 0;
        threshold = ir_val;

        return 1;
    }
}

return 0;
}

uint32_t MAX30102_HeartRate(int32_t ir_val, uint8_t samples) { int32_t ir_val_sum = 0;
// Calculate the sum of IR values
for(int i=0; i<samples; i++)
{
    ir_val_sum += MAX30102_IR_Sample_Buffer[i];
}

// Calculate the average IR value
ir_val_sum /= samples;

// Calculate the heart rate
return (uint32_t)(60 * MAX30102_SAMPLING_FREQUENCY / (ir_val - ir_val_sum));
}

3.2 OLED顯示屏驅(qū)動代碼

I2C協(xié)議代碼:

#define SSD1306_I2C_ADDR 0x78

void SSD1306_I2C_Init(void)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    I2C_InitTypeDef   I2C_InitStructure;

    /* Enable GPIOB clock */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    /* Enable I2C1 and I2C2 clock */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1 | RCC_APB1Periph_I2C2, ENABLE);

    // Configure I2C SCL and SDA pins
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // Open-drain output
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    // Configure I2C parameters
    I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
    I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
    I2C_InitStructure.I2C_OwnAddress1 = 0x00;
    I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
    I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    I2C_InitStructure.I2C_ClockSpeed = 100000; // 100KHz
    I2C_Init(I2C1, &I2C_InitStructure);

    // Enable I2C
    I2C_Cmd(I2C1, ENABLE);
}

void SSD1306_I2C_WriteReg(uint8_t reg, uint8_t value)
{
    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));

    I2C_GenerateSTART(I2C1, ENABLE);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));

    I2C_Send7bitAddress(I2C1, SSD1306_I2C_ADDR, I2C_Direction_Transmitter);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

    I2C_SendData(I2C1, 0x00);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

    I2C_SendData(I2C1, reg);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

    I2C_SendData(I2C1, value);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

    I2C_GenerateSTOP(I2C1, ENABLE);
}

void SSD1306_I2C_WriteArray(uint8_t* data, uint16_t len)
{
    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));

    I2C_GenerateSTART(I2C1, ENABLE);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));

    I2C_Send7bitAddress(I2C1, SSD1306_I2C_ADDR, I2C_Direction_Transmitter);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

    while(len--)
    {
        I2C_SendData(I2C1, *data++);
        while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
    }

    I2C_GenerateSTOP(I2C1, ENABLE);
}

SSD1306的初始化函數(shù)和數(shù)據(jù)更新函數(shù):

#define SSD1306_WIDTH 128
#define SSD1306_HEIGHT 64
#define SSD1306_BUFFER_SIZE (SSD1306_WIDTH*SSD1306_HEIGHT/8)

uint8_t SSD1306_Buffer[SSD1306_BUFFER_SIZE];

void SSD1306_Init(void)
{
    SSD1306_I2C_Init();

    // Turn display off
    SSD1306_DisplayOff();

    // Set the clock to a high value for faster data transfer
    SSD1306_I2C_WriteReg(0x0F, 0x80);

    // Set multiplex ratio to default value (63)
    SSD1306_I2C_WriteReg(0xA8, 0x3F);

    // Set the display offset to 0
    SSD1306_I2C_WriteReg(0xD3, 0x00);

    // Display start line is 0
    SSD1306_I2C_WriteReg(0x40, 0x00);

    // Set segment remap to inverted
    SSD1306_I2C_WriteReg(0xA1, 0xC0);

    // Set COM output scan direction to inverted
    SSD1306_I2C_WriteReg(0xC8, 0xC0);

    // Disable display offset shift
    SSD1306_I2C_WriteReg(0xD7, 0x9F);

    // Set display clock divide ratio/oscillator frequency to default value (8/0xF0)
    SSD1306_I2C_WriteReg(0xD5, 0xF0);

    // Enable charge pump regulator
    SSD1306_I2C_WriteReg(0x8D, 0x14);

    // Set memory addressing mode
    // Set the display to normal mode (not inverted)
SSD1306_I2C_WriteReg(0xA6, 0xA6);

// Set the contrast to a default value of 127
SSD1306_I2C_WriteReg(0x81, 0x7F);

// Turn the display back on
SSD1306_DisplayOn();

// Clear the display buffer
SSD1306_ClearBuffer();

// Update the display with the cleared buffer
SSD1306_UpdateDisplay();
}

void SSD1306_UpdateDisplay(void) { uint8_t column, page;
}for(page=0; page<8; page++)
{
    SSD1306_I2C_WriteReg(0xB0+page, 0x00);
    SSD1306_I2C_WriteReg(0x10, 0x00);
    SSD1306_I2C_WriteReg(0x00, 0x00);

    for(column=0; column<SSD1306_WIDTH; column++)
    {
        SSD1306_I2C_WriteArray(&SSD1306_Buffer[column + page*SSD1306_WIDTH], 1);
    }
}
}
void SSD1306_ClearBuffer(void) { memset(SSD1306_Buffer, 0x00, sizeof(SSD1306_Buffer)); }

void SSD1306_SetPixel(uint8_t x, uint8_t y, uint8_t color) { if(x >= SSD1306_WIDTH || y >= SSD1306_HEIGHT) { return; }
}if(color)
{
    SSD1306_Buffer[x + (y/8)*SSD1306_WIDTH] |= (1 << (y%8));
}
else
{
    SSD1306_Buffer[x + (y/8)*SSD1306_WIDTH] &= ~(1 << (y%8));
}
}

四、總結(jié)

本設(shè)計采用STM32為主控芯片,配合血氧濃度傳感器和OLED屏幕,實(shí)現(xiàn)了人體健康數(shù)據(jù)的采集和展示,并通過算法對采集到的數(shù)據(jù)進(jìn)行分析,判斷被檢測人員的健康狀態(tài)。同時,設(shè)計使用藍(lán)牙或WiFi將采集到的數(shù)據(jù)傳遞給手機(jī)APP進(jìn)行處理。設(shè)計基本滿足了人體健康檢測儀的技術(shù)要求和環(huán)境要求。

  • 更多詳細(xì)資料請聯(lián)系.docx
    下載
意法半導(dǎo)體

意法半導(dǎo)體

意法半導(dǎo)體(ST)集團(tuán)于1987年6月成立,是由意大利的SGS微電子公司和法國Thomson半導(dǎo)體公司合并而成。1998年5月,SGS-THOMSON Microelectronics將公司名稱改為意法半導(dǎo)體有限公司。意法半導(dǎo)體是世界最大的半導(dǎo)體公司之一,公司銷售收入在半導(dǎo)體工業(yè)五大高速增長市場之間分布均衡(五大市場占2007年銷售收入的百分比):通信(35%),消費(fèi)(17%),計算機(jī)(16%),汽車(16%),工業(yè)(16%)。 據(jù)最新的工業(yè)統(tǒng)計數(shù)據(jù),意法半導(dǎo)體是全球第五大半導(dǎo)體廠商,在很多市場居世界領(lǐng)先水平。例如,意法半導(dǎo)體是世界第一大專用模擬芯片和電源轉(zhuǎn)換芯片制造商,世界第一大工業(yè)半導(dǎo)體和機(jī)頂盒芯片供應(yīng)商,而且在分立器件、手機(jī)相機(jī)模塊和車用集成電路領(lǐng)域居世界前列.

意法半導(dǎo)體(ST)集團(tuán)于1987年6月成立,是由意大利的SGS微電子公司和法國Thomson半導(dǎo)體公司合并而成。1998年5月,SGS-THOMSON Microelectronics將公司名稱改為意法半導(dǎo)體有限公司。意法半導(dǎo)體是世界最大的半導(dǎo)體公司之一,公司銷售收入在半導(dǎo)體工業(yè)五大高速增長市場之間分布均衡(五大市場占2007年銷售收入的百分比):通信(35%),消費(fèi)(17%),計算機(jī)(16%),汽車(16%),工業(yè)(16%)。 據(jù)最新的工業(yè)統(tǒng)計數(shù)據(jù),意法半導(dǎo)體是全球第五大半導(dǎo)體廠商,在很多市場居世界領(lǐng)先水平。例如,意法半導(dǎo)體是世界第一大專用模擬芯片和電源轉(zhuǎn)換芯片制造商,世界第一大工業(yè)半導(dǎo)體和機(jī)頂盒芯片供應(yīng)商,而且在分立器件、手機(jī)相機(jī)模塊和車用集成電路領(lǐng)域居世界前列.收起

查看更多

相關(guān)推薦