본문 바로가기
2023 상반기/캡스톤

폐활량계(spirometer) 개발3 (코드포함)

by concho 2023. 6. 5.

제작된 폐활량 기기:

- 디자인은 개발시간 부족으로 신경쓰지 못했습니다....

폐활량 app UI:


- VC data에 대한 그래프, FVC_value

- FEV1_value, VC_ratio  나타내는 화면1

- 측정된 값들이 저장되어 있는 화면2로 구성됨

- START&RESET 버튼을 통해 측정 시작 및 초기화

- BT_CONNECT 버튼을 통해 폐활량 측정 기기와 블루투스 연결

- NEXT 버튼을 통해 화면1 에서 화면2로 이동가능

 


MCU펌웨어(C) 코드:

/*
 * Capstone.c
 *
 * Created: 2023-01-06 오후 2:35:59
 * Author : Owner
 */ 

#define F_CPU	20000000UL

#include <avr/io.h>
#include <util/delay.h>
#include <inttypes.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/sfr_defs.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>

#include "spi.h"
#include "uart.h"

void putty_process( char ttyBuffer[], uint8_t ttyIndex );

#define		MAX_SIZE		1000
#define		USER_LED_ON		PORTF.OUTCLR = PIN5_bm
#define		USER_LED_OFF	PORTF.OUTSET = PIN5_bm
#define		USER_LED_TOGGLE	PORTF.OUTTGL = PIN5_bm

#define		USER_BUTTON		(PORTF.IN & PIN6_bm)

#define		YF_TAP	32

volatile	bool	buttonFlag = false;
volatile	bool	buttonUpFlag = false;
volatile	bool	yfFlag = false;
volatile	bool	buFlag = false;
volatile	bool	buFalseFlag = false;
volatile	bool	Flag100Hz = false;

typedef enum { KEY_IDLE, KEY_PRESSING, KEY_PRESSED, KEY_RELEASE } KeyState_t;

typedef struct qbuffer {
	int *buffer;
	int head;
	int tail;
	int size;
} QBuffer;

int PulseCnt = 0;

static uint16_t YF_Buffer[YF_TAP] = {0};
static uint32_t YF_Sum = 0;
static uint8_t YF_Index = 0;

QBuffer* init_qbuffer() {
	QBuffer* qbuf = (QBuffer*)malloc(sizeof(QBuffer));
	qbuf->buffer = (int*)malloc(MAX_SIZE * sizeof(int));
	qbuf->head = 0;
	qbuf->tail = 0;
	qbuf->size = 0;
	return qbuf;
}

void push(QBuffer* qbuf, int data) {
	if (qbuf->size == MAX_SIZE) {
		//printf("QBuffer is full.\n");
		return;
	}

	qbuf->buffer[qbuf->tail] = data;
	qbuf->tail = (qbuf->tail + 1) % MAX_SIZE;
	qbuf->size++;
}

int pop(QBuffer* qbuf) {
	if (qbuf->size == 0) {
		//printf("QBuffer is empty.\n");
		return -1;
	}

	int data = qbuf->buffer[qbuf->head];
	qbuf->head = (qbuf->head + 1) % MAX_SIZE;
	qbuf->size--;

	return data;
}

QBuffer* qbuf = NULL;

void ScanButtonISR( void ) {
	static KeyState_t KeyState = KEY_IDLE;
	
	switch ( KeyState ) {
		case KEY_IDLE :
			if ( !USER_BUTTON )  KeyState = KEY_PRESSING;
		break;
		case KEY_PRESSING :
			if ( USER_BUTTON ) KeyState = KEY_IDLE;
		else {
			KeyState = KEY_PRESSED;
			buttonFlag = true;
		}
		break;
		case KEY_PRESSED :
			USER_LED_ON;
			buFlag = true;
			if ( USER_BUTTON ) KeyState = KEY_RELEASE;
		break;
		case KEY_RELEASE :
			USER_LED_OFF;
			buFlag = false;
			buFalseFlag = true;
			KeyState = ( USER_BUTTON )? KEY_IDLE : KEY_PRESSED;
		break;
	}
}

// 1kHz
ISR( TCB0_INT_vect ) {
	static uint16_t	CntHz = 0, Cnt10Hz = 0;
	uint8_t Cnt200Hz;
	
	
	CntHz++;
	if(PulseCnt > 1) Cnt10Hz++;
	
	Cnt200Hz = CntHz % 5;
	if ( Cnt200Hz == 0 ) ScanButtonISR();
	else if ( Cnt200Hz == 3 ); //SendLCDbySpiISR();
	
	if ( Cnt10Hz >= 100 && buFlag){
		Flag100Hz = true;
		push(qbuf, PulseCnt);
		PulseCnt = 0;
		Cnt10Hz= 0;
	}
	
	TCB0.INTFLAGS |= TCB_CAPT_bm;
}

void TCA_initialize(void){
	TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV1024_gc | TCA_SPLIT_ENABLE_bm;
}

void TCB2_initialize(void){
	
	// TCB2 initialize without enable
	TCB2.CTRLB |= TCB_CNTMODE_FRQ_gc;
	TCB2.EVCTRL |= TCB_CAPTEI_bm;
	
	//20Mhz
	TCB2.CTRLA |= TCB_CLKSEL_CLKDIV1_gc;
	TCB2.INTCTRL |= TCB_CAPT_bm;
	TCB2.CCMP |= 5000;
	
	//TCB2 enable
	TCB2.CTRLA |= TCB_ENABLE_bm;
	
	// PD0 -> CHANNEL3 -> TCB2
	EVSYS.CHANNEL3 |= EVSYS_GENERATOR_PORT1_PIN0_gc;
	EVSYS.USERTCB2 |= EVSYS_CHANNEL_CHANNEL3_gc;
	
}
// 4e-7에 ccmp 1증가
ISR( TCB2_INT_vect ) {
	//yfFlag = true;
	PulseCnt++;
	TCB2.INTFLAGS |= TCB_CAPT_bm;
}

int main(void)
{
	qbuf = init_qbuffer();
	int cnt = 0;
	char  tBuffer[16] = { 0 };
	// Main clock 5MHz
	CCP = CCP_IOREG_gc;
	CLKCTRL.MCLKCTRLA |= CLKCTRL_CLKSEL_OSC20M_gc;
	//CLKCTRL.MCLKCTRLB = CLKCTRL_PDIV_2X_gc | CLKCTRL_PEN_bm;
	CCP = CCP_IOREG_gc;
	CLKCTRL.MCLKCTRLB &= ~CLKCTRL_PEN_bm;
	
	PORTF.DIRSET = PIN5_bm;		// PF5 Output mode for USER_LED
	USER_LED_OFF;
	PORTF.DIRCLR = PIN6_bm;		// PF6 Input mode for USER_BUTTON
	PORTF.PIN6CTRL |= PORT_PULLUPEN_bm;
	
	//inputMode
	PORTD.DIRCLR = PIN0_bm;
	
	InitializeUART( 9600 );
	
	sei();						// Enable global int
	
	TCA_initialize();
	
	TCB2_initialize();
	
	
	//InitializeIOX();
	// TCB0 Initialization for 1000 Hz periodic interrupt
	TCB0.CTRLA |= TCB_CLKSEL_CLKDIV2_gc; // 10Mhz
	TCB0.CCMP = 9999;			// TCB2 CLK 10000000 / 10000 =  1000Hz
	TCB0.CTRLA |= TCB_ENABLE_bm;
	TCB0.INTCTRL |= TCB_CAPT_bm;
	
	_delay_ms(1000);
	bool stopflag = true;
	int Totaldata = 0; 
	uint8_t cnt_10 = 0;
    /* Replace with your application code */
    while (1) 
    {
		if(buttonFlag){
			buttonFlag = false;
			USER_LED_TOGGLE;
			while(qbuf->size > 0) pop(qbuf);
			cnt = 0;
			printf("ASA");
		}
		
		if (qbuf->size > 0){ 
			Flag100Hz = false;
			
			printf("A%dA", pop(qbuf));
		}
		
		if (buFalseFlag == true){
			buFalseFlag = false;
			printf("A-A");
		}
	}
}

uart.c & uart.h

/*
 * uart.h
 *
 * Created: 2023-01-06 오후 2:37:08
 *  Author: Owner
 */ 


#ifndef UART_H_
#define UART_H_
#define F_CPUM	20000000UL

#define USART0_BAUD_RATE(BAUD_RATE)		((64.0 * F_CPUM / (16.0 * BAUD_RATE)) + 0.5)
#define USART0_BUFFER_SIZE				1024	// 2^n  <= 256
#define USART0_BUFFER_MASK				(USART0_BUFFER_SIZE - 1)

typedef struct {
	uint8_t RingBuffer[USART0_BUFFER_SIZE];
	uint8_t	HeadIndex, TailIndex;
	uint8_t NoElement;
} RingBuffer_t;

void InitializeUART( uint32_t baud );
uint8_t USART0_GetChar( void );
uint8_t USART0_PutChar( uint8_t dat );
bool USART0_CheckRxData( void );
int	StdIO_Get( FILE *stream );
int StdIO_Put( char d, FILE *stream );

#endif /* UART_H_ */


/*
 * uart.c
 *
 * Created: 2023-01-06 오후 2:36:55
 *  Author: Owner
 */ 
#define F_CPU	20000000UL

#include <avr/io.h>
#include <util/delay.h>
#include <inttypes.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/sfr_defs.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>

#include "uart.h"

RingBuffer_t RxBuffer, TxBuffer;

static  FILE OUTPUT_device = FDEV_SETUP_STREAM( StdIO_Put, NULL, _FDEV_SETUP_WRITE);
static  FILE INPPUT_device = FDEV_SETUP_STREAM( NULL, StdIO_Get, _FDEV_SETUP_READ);

void InitializeUART( uint32_t baud ) {
	USART0.BAUD = (uint16_t)USART0_BAUD_RATE(baud);
	PORTA.DIRSET = PIN0_bm;
	PORTA.OUTSET = PIN0_bm;		// TxD Outmode, TxD = '1'
	USART0.CTRLB |= USART_RXEN_bm + USART_TXEN_bm;
	USART0.CTRLA |= USART_RXCIE_bm;
	
	RxBuffer.HeadIndex = RxBuffer.TailIndex = 0;
	RxBuffer.NoElement = 0;
	TxBuffer.HeadIndex = TxBuffer.TailIndex = 0;
	TxBuffer.NoElement = 0;
	
	while ( USART0.STATUS & USART_RXCIF_bm ) USART0.RXDATAL;
	
	stdout = &OUTPUT_device;
	stdin  = &INPPUT_device;
}

ISR( USART0_RXC_vect ) {
	uint8_t	rxDat;
	
	rxDat = USART0.RXDATAL;
	if ( RxBuffer.NoElement < USART0_BUFFER_SIZE ) {
		RxBuffer.RingBuffer[RxBuffer.HeadIndex++] = rxDat;
		RxBuffer.HeadIndex &= USART0_BUFFER_MASK;
		RxBuffer.NoElement++;
	} else
	;
}

bool USART0_CheckRxData( void ) {
	return RxBuffer.NoElement > 0;
}

void USART0_Remove(void){
	for(int i =0; i < USART0_BUFFER_SIZE; i++) RxBuffer.RingBuffer[i] = 0;
}

uint8_t USART0_GetChar( void ) {
	uint8_t rxDat;
	
	while ( RxBuffer.NoElement == 0 ) ;
	rxDat = RxBuffer.RingBuffer[RxBuffer.TailIndex++];
	RxBuffer.TailIndex &= USART0_BUFFER_MASK;
	cli();  RxBuffer.NoElement--;  sei();
	return rxDat;
}

uint8_t USART0_PutChar( uint8_t dat ) {
	while (TxBuffer.NoElement >= USART0_BUFFER_SIZE) ;
	TxBuffer.RingBuffer[TxBuffer.HeadIndex++] = dat;
	TxBuffer.HeadIndex &= USART0_BUFFER_MASK;
	cli();  TxBuffer.NoElement++;  sei();
	
	USART0.CTRLA |= USART_DREIE_bm;			// local int enable
	
	return dat;
}

ISR( USART0_DRE_vect ) {
	if ( TxBuffer.NoElement > 0 ) {
		USART0.TXDATAL = TxBuffer.RingBuffer[TxBuffer.TailIndex++];
		TxBuffer.TailIndex &= USART0_BUFFER_MASK;
		TxBuffer.NoElement--;
		} else {
		USART0.CTRLA &= ~USART_DREIE_bm;	// local int disable
	}
}

// std I/O
int	StdIO_Get( FILE *stream ) {
	return (int)USART0_GetChar();
}

int StdIO_Put( char d, FILE *stream ) {
	USART0_PutChar( (uint8_t)d );
	return 0;
}

안드로이드(java) 코드:

package com.example.ecgtester;

import android.Manifest;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.components.LimitLine;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
import com.github.mikephil.charting.formatter.ValueFormatter;

import java.io.IOException;
import java.io.InputStream;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Set;
import java.util.UUID;

public class MainActivity extends AppCompatActivity {

    private static final float xLimit = 61f;
    private static final float yLimit = 8001f;
    int nonSignalCnt = 0;
    int cnt = 0;
    double total = 0;
    double _1sFVC = 0;
    double tmData = 0;
    double inertiaCnt = 0;
    private LineChart chart;
    private ArrayList<Entry> values = new ArrayList<>();

    private static final int REQUEST_ENABLE_BLUETOOTH_ADMIN = 4;
    private final BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    private BluetoothSocket bluetoothSocket = null;
    private static final int PERMISSION_REQUEST_COARSE_LOCATION = 456;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        chart = findViewById(R.id.chart);
        setupChart();

        // Bluetooth 권한 요청
        requestBluetoothPermission();

        connectButton = findViewById(R.id.connect_button);
        connectButton.setOnClickListener(view -> {
            connectButton.setEnabled(false);
            connectBluetooth();
        });

        Button nextButton = findViewById(R.id.next);
        nextButton.setOnClickListener(this::goToMainActivity2);
    }


    public void goToMainActivity2(View view) {
        Intent intent = new Intent(this, MainActivity2.class);
        startActivity(intent);
    }

    private void requestBluetoothPermission() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, PERMISSION_REQUEST_COARSE_LOCATION);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        try {
            bluetoothSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void requestBluetoothAdminPermission() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_ADMIN) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.BLUETOOTH_ADMIN}, REQUEST_ENABLE_BLUETOOTH_ADMIN);
        }
    }

    private void enableBluetooth() {
        if (bluetoothAdapter == null) {
            Toast.makeText(this, "Bluetooth not supported", Toast.LENGTH_SHORT).show();
        } else {
            if (bluetoothAdapter.isEnabled()) {
                Toast.makeText(this, "Bluetooth is already enabled", Toast.LENGTH_SHORT).show();
            } else {
                requestBluetoothAdminPermission();
                try {
                    bluetoothAdapter.enable();
                    Toast.makeText(this, "Bluetooth is enabled", Toast.LENGTH_SHORT).show();
                } catch (SecurityException e) {
                    Toast.makeText(this, "Bluetooth permission denied", Toast.LENGTH_SHORT).show();
                }
            }
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_ENABLE_BLUETOOTH_ADMIN) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                enableBluetooth();
            } else {
                Toast.makeText(this, "Bluetooth permission denied", Toast.LENGTH_SHORT).show();
            }
        }
    }


    private Button connectButton;

    private void connectBluetooth() {

        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
            Toast.makeText(this, "Bluetooth connection recast Failed", Toast.LENGTH_SHORT).show();
            return;
        }
        Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();
        if (pairedDevices.size() > 0) {
            for (BluetoothDevice device : pairedDevices) {
                if (device.getName().equals("HC-06")) {
                    UUID hc06Uuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
                    try {
                        bluetoothSocket = device.createRfcommSocketToServiceRecord(hc06Uuid);
                        bluetoothSocket.connect();
                        Toast.makeText(this, "Bluetooth connection successful", Toast.LENGTH_SHORT).show();
                        startThread();
                        connectButton.setEnabled(false);
                        break;
                    } catch (IOException e) {
                        e.printStackTrace();
                        Toast.makeText(this, "Bluetooth connection failed", Toast.LENGTH_SHORT).show();
                        connectButton.setEnabled(true);
                    }
                }
            }
        }
    }


    @SuppressLint("SetTextI18n")
    private void startThread() {
        TextView nonSignalCntTextView = findViewById(R.id.nonSignalCnt_text_view);
        TextView _1sFVCTextView = findViewById(R.id._1sFVC_text_view);
        TextView VCDevTextView = findViewById(R.id.VC_dev_text_view);
        Button resetButton = findViewById(R.id.reset_button);
        resetButton.performClick();

        resetButton.setOnClickListener(v -> {
            // 초기화
            values.clear(); // 차트 데이터 값 초기화
            _1sFVC = 0; // 1초 FVC 초기화
            nonSignalCnt = 0; //VC_ratio 초기화
            cnt = 0; // 측정 횟수 초기화
            total = 0; // 총값 초기화
            tmData = 0;
            inertiaCnt = 0;

            // TextView 초기화
            _1sFVCTextView.setText("FEV1_value(ml): " + _1sFVC);
            nonSignalCntTextView.setText("FVC_value(ml): 0");
            VCDevTextView.setText("VC_ratio(%): ");

            // 차트 업데이트
            chart.notifyDataSetChanged();
            chart.invalidate();
        });


        new Thread(() -> {

            try {
                InputStream inputStream = bluetoothSocket.getInputStream();
                byte[] buffer = new byte[1024];
                int bytes;

                // 초기화된 total 값을 Entry로 추가
                values.add(new Entry(0, (float) total));
                // 차트 데이터 갱신
                chart.notifyDataSetChanged();
                chart.invalidate();


                while (true) {
                    try {
                        bytes = inputStream.read(buffer);
                        String data = new String(buffer, 0, bytes);
                        String[] lines = data.split("A");

                        for (int i = 0; i < lines.length; i++) {
                            lines[i] = lines[i].replaceAll("[\\sA]", "");

                            if(lines[i].matches(".*-.*")){
                                SharedPreferences sharedPref = getSharedPreferences("myPrefs", MODE_PRIVATE);
                                int num = sharedPref.getInt("num", 0);  // default value: 0
                                num++;
                                if (num > 5) num = 0;
                                SharedPreferences.Editor editor = sharedPref.edit();
                                editor.putInt("total_" + num, (int)total);
                                editor.putInt("_1sFVC_" + num, (int)_1sFVC);
                                editor.putInt("num", num);
                                editor.apply();

                                values.add(new Entry(cnt, (float) total));
                                chart.notifyDataSetChanged();
                                chart.invalidate();

                                int tmVC_R  = (int)(100 * _1sFVC / nonSignalCnt);
                                runOnUiThread(() -> {
                                    VCDevTextView.setText("VC_ratio(%): " + tmVC_R);
                                    _1sFVCTextView.setText("FEV1_value(ml): " + (int)_1sFVC);
                                    nonSignalCntTextView.setText("FVC_value(ml): " + nonSignalCnt);
                                });
                            }
                            else if(!lines[i].isEmpty() && lines[i].matches("\\d+") && !lines[i].equals("0")){
                                double newData = calculateNewData(lines[i]);
                                if(cnt == 0){
                                    values.add(new Entry(0, (float) total));
                                }
                                if (cnt < xLimit) {
                                    /*
                                    if (tmData > newData + 5000){
                                        inertiaCnt++;
                                        newData = 2 * newData / (inertiaCnt + 1);
                                    }
                                    else{
                                        tmData = newData * newData * 3 / cnt;
                                    }
                                    */
                                    total += newData;
                                    //total += newData;
                                    cnt++;
                                }
                                if(cnt == 11){
                                    _1sFVC = total;
                                    _1sFVCTextView.setText("FEV1_value(ml): " + (int)_1sFVC);
                                }
                                if(cnt % 2 == 1) {
                                    nonSignalCnt = (int) (total);
                                    nonSignalCntTextView.setText("FVC_value(ml): " + nonSignalCnt);
                                }
                                if (cnt % 2 == 0){
                                    values.add(new Entry(cnt, (float) total));
                                    //total = 0;


                                    runOnUiThread(() -> {
                                        chart.notifyDataSetChanged();
                                        chart.invalidate();
                                    });
                                }
                            }
                            if(lines[i].matches(".*S.*")){
                                Arrays.fill(lines, "");
                                nonSignalCnt = 0;
                                cnt = 0;
                                total = 0;
                                _1sFVC = 0;
                            }

                        }
                    } catch (IOException e) {
                        nonSignalCnt = -100;
                        nonSignalCntTextView.setText("nonSignalCnt: " + nonSignalCnt);
                        break;
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
    }


    private void setupChart() {
        chart.getDescription().setEnabled(false);
        chart.setTouchEnabled(false);
        chart.setDragEnabled(false);
        chart.setScaleEnabled(false);
        chart.setDrawGridBackground(false);
        chart.getAxisRight().setEnabled(false);

        // X축 객체를 가져옵니다.
        XAxis xAxis = chart.getXAxis();
        // X축의 위치를 하단으로 설정합니다.
        xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
        // X축 그리드 선을 비활성화합니다.
        xAxis.setDrawGridLines(false);
        // X축의 격자 간격을 1f로 설정합니다.
        xAxis.setGranularity(1f);
        // X축에 표시될 레이블 수를 10개로 설정합니다.
        xAxis.setLabelCount(12);
        // X축 값의 형식을 설정하는 ValueFormatter 객체를 생성합니다.
        xAxis.setValueFormatter(new ValueFormatter() {
            @Override
            public String getFormattedValue(float value) {
                // 값을 "#" 형식으로 포매팅하여 반환합니다.
                return new DecimalFormat("#").format(value);
            }
        });

        // 그래프의 왼쪽 Y축 객체를 가져옵니다.
        YAxis yAxis = chart.getAxisLeft();

        // 그래프의 Y축에 격자 라인을 표시하지 않도록 설정합니다.
        yAxis.setDrawGridLines(false);

        // Y축의 최소값을 0으로 설정합니다.
        yAxis.setAxisMinimum(0f);

        // Y축의 최대값을 yLimit 변수로 설정합니다.
        yAxis.setAxisMaximum(yLimit);
        yAxis.setLabelCount(16);

        // Y축에 제한 범위를 표시하는 라인을 생성합니다.
        LimitLine limitLine = new LimitLine(5000, "normal VC_ratio(70%)");

        // 라인의 두께를 2로 설정합니다.
        limitLine.setLineWidth(2f);

        // 라인의 색상을 accent 색상으로 설정합니다.
        limitLine.setLineColor(ContextCompat.getColor(MainActivity.this, R.color.colorAccent));

        // 라인을 점선으로 설정합니다. 첫 번째 매개변수는 선의 길이, 두 번째 매개변수는 공백의 길이를 설정하며, 세 번째 매개변수는 오프셋을 설정합니다.
        limitLine.enableDashedLine(10f, 10f, 0f);

        // 라인 위 문자열의 위치를 설정합니다.
        limitLine.setLabelPosition(LimitLine.LimitLabelPosition.RIGHT_TOP);

        // 라인 위 문자열의 크기를 설정합니다.
        limitLine.setTextSize(10f);

        // Y축에 라인을 추가합니다.
        yAxis.addLimitLine(limitLine);

        // 데이터를 저장할 ArrayList를 생성합니다.
        values = new ArrayList<>();

        values.add(new Entry(0, 0));
        values.add(new Entry(61, 0));

        // 데이터를 그래프에 추가합니다.
        chart.setData(new LineData(getDataSet()));

        // X축 범위를 설정합니다.
        chart.setVisibleXRangeMaximum(xLimit);

    }


    // getDataSet() 메서드는 LineDataSet 객체를 생성하여 반환합니다.
    // LineDataSet 객체는 MPAndroidChart 라이브러리에서 사용되는 개체로, 그래프의 데이터를 관리합니다.
    private LineDataSet getDataSet() {
        // LineDataSet 객체를 생성합니다. values 리스트와 "Lung Capacity (VC) data" 라벨을 설정합니다.
        LineDataSet set = new LineDataSet(values, "Lung Capacity (VC) data");
        // 그래프의 색상을 R.color.colorAccent로 설정합니다.
        set.setColor(ContextCompat.getColor(MainActivity.this, R.color.colorAccent));
        // 그래프의 선 두께를 2.5f로 설정합니다.
        set.setLineWidth(2.5f);
        // 그래프의 데이터 값을 표시하지 않도록 설정합니다.
        set.setDrawValues(false);
        // 그래프의 모양을 CUBIC_BEZIER로 설정합니다.
        set.setMode(LineDataSet.Mode.CUBIC_BEZIER);
        // 생성된 LineDataSet 객체를 반환합니다.
        return set;
    }

    private double calculateNewData(String line) {
        return 2.64 * Double.parseDouble(line);
    }
}

'2023 상반기 > 캡스톤' 카테고리의 다른 글

폐활량계(spirometer) 개발2  (0) 2023.04.27
폐활량 진단 방법  (0) 2023.04.26
폐활량의 종류와 그에 관한 특성  (0) 2023.02.15
폐활량계(spirometer) 개발1  (0) 2023.01.10

댓글