제작된 폐활량 기기:
- 디자인은 개발시간 부족으로 신경쓰지 못했습니다....
폐활량 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 |
댓글