STM32 설계 기반 인체 건강 감지기

1. 프로젝트 소개

현재 기사에서는 STM32 설계를 기반으로 하는 인체 건강 감지기를 소개합니다. 이 장치는 STM32 시리즈 MCU를 메인 제어 칩으로 사용하며 혈중 산소 농도 센서(MAX30102 혈중 산소 농도 감지 센서 사용), OLED 화면 및 배터리 전원 공급 장치 및 기타 주변 모듈을 갖추고 있습니다. 이 장치는 의료, 건강 및 기타 분야에서 널리 사용될 수 있습니다. 그것은 의사와 환자가 자신의 상태 변화를 더 잘 이해하고 치료 효과와 삶의 질을 향상시키는 데 도움이 될 수 있습니다. 또한 이 장치는 건강 관리, 운동 모니터링 및 기타 시나리오에서 사용되어 사용자가 자신의 신체 상태를 이해하고 건강한 라이프스타일을 유지하는 데 도움이 될 수 있습니다.

프로젝트에서 KEIL은 개발 플랫폼 및 도구로 사용되었으며, 혈중 산소 모듈을 통해 인체의 심장 박동 및 혈중 산소 농도 매개 변수를 수집하고 현재 심장 박동 및 혈중 산소 농도를 OLED 화면에 표시했습니다. 동시에 지표 분석을 통해 수집된 데이터를 정상 지표와 비교하여 검사자의 건강 상태를 분석합니다. 수집된 데이터는 블루투스 또는 WIFI를 통해 처리하기 위해 휴대폰 APP로 전송될 수 있으므로 사용자는 언제든지 자신의 신체 상태를 알 수 있습니다.

이 디자인은 STM32를 주요 제어 칩으로 사용하고 혈중 산소 농도 센서와 OLED 화면을 통해 인체 건강 데이터의 수집 및 표시를 실현하고 수집된 데이터를 분석하여 검사자의 건강 상태를 판단합니다. 동시에 디자인은 처리를 위해 수집된 데이터를 모바일 앱으로 전송하기 위해 블루투스 또는 WiFi를 사용합니다.

이미지-20230618132149185

이미지-20230618132108207

2. 프로젝트 설계 아이디어

2.1 하드웨어 설계

(1) 주 제어 칩: STM32 시리즈 MCU, 기타 주변 모듈 구동 담당

(2) 혈중 산소 농도 센서: MAX30102 혈중 산소 농도 감지 센서는 인체의 심장 박동 및 혈중 산소 농도 매개변수를 수집하는 데 사용됩니다.

(3) OLED 화면: 현재 심박수 및 혈중 산소 농도를 표시하는 데 사용됩니다.

2.2 소프트웨어 설계

(1) 혈액 산소 모듈을 통해 인체의 심장 박동 및 혈액 산소 농도 매개변수를 수집합니다.

(2) OLED 화면을 통해 현재 심장 박동과 혈중 산소 농도를 표시합니다.

(3) 수집된 데이터에 대한 지표 분석을 수행하고 수집된 데이터를 정상 지표와 비교하며 검사 대상자의 건강 상태를 분석합니다.

(4) 수집된 데이터는 블루투스 또는 WiFi를 통해 모바일 APP으로 전송되어 처리될 수 있습니다.

2.3 기술적 실현

(1) 이 설계는 AD8232 심전도(ECG) 모듈과 MAX30102 혈액 산소 모듈을 사용하여 심장 박동 및 혈액 산소 농도 매개변수를 수집하고 I2C 인터페이스를 통해 주 제어 칩 STM32에 연결합니다.

(2) OLED 화면은 I2C 인터페이스를 사용하여 메인 제어 칩 STM32와 연결합니다.

(3) 수집된 데이터는 알고리즘에 의해 분석되고 수집된 데이터는 정상 지표와 비교되어 검사자의 건강 상태를 판단합니다.

(4) 장치는 블루투스 또는 WiFi를 통해 처리하기 위해 수집된 데이터를 모바일 앱으로 전송합니다.

3. 코드 설계

3.1 MAX30102 혈액 산소 모듈 코드

I2C 프로토콜 코드:

#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의 초기화 기능 및 데이터 수집 기능:

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;
}

데이터 처리 기능:

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 디스플레이 드라이버 코드

I2C 프로토콜 코드:

#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의 초기화 기능 및 데이터 업데이트 기능:

#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));
}
}

4. 요약

이 디자인은 STM32를 주요 제어 칩으로 채택하고 혈중 산소 농도 센서 및 OLED 화면과 협력하여 인체 건강 데이터의 수집 및 표시를 실현하고 알고리즘을 통해 수집된 데이터를 분석하여 테스트 대상자의 건강 상태를 판단합니다. 동시에 디자인은 처리를 위해 수집된 데이터를 모바일 앱으로 전송하기 위해 블루투스 또는 WiFi를 사용합니다. 디자인은 기본적으로 인체 건강 감지기의 기술 요구 사항 및 환경 요구 사항을 충족합니다.

Supongo que te gusta

Origin blog.csdn.net/xiaolong1126626497/article/details/131979504
Recomendado
Clasificación