URCUTE

An eldercare management system on LPC1769. For EE2024.

View the Project on GitHub

URCUTE

Unnecessarily Redundant Care Unit for The Elderly (CUTE) is a variation of EE2024 CUTE, built for EE2024 by Justin Ng and Lim Hong Wei as Assignment 2. It is strictly for educational purposes and not to be used on a real elderly patient. Get an Apple Watch for them or something.

About

URCUTE is designed for the elderly person who wholly entrusts their care to a a Central Elderly Monitoring System (CEMS).

The ease of understanding lies in the fact that URCUTE only runs from a single file. No more complicated header structures and variables that seem to come out of nowhere. All global flags are declared up top, and all timer variables are declared at the top of void main().

For detailed assignment requirements, visit the EE2024 wiki.

Modes

URCUTE has two basic modes: STABLE and MONITOR, two enhanced modes: NIGHT and EMERGENCY, and one quasi-mode: CEMS OVERRIDE.

To view use cases, teleport there.

Stable Mode

Stable mode is to be used in the presence of a caretaker. There is no environment sampling, no information displayed on the screen, and no emergency warnings.

Stable mode is also the first mode that URCUTE boots into. The boot process is highlighted below. Click here to skip to Monitor Mode.

Initialization

The system initializes the clock, I2C, SPI/SSP and GPIO.

I2C is used for the LED array, accelerometer and light sensor. As per EA’s specifications, the system uses the I2C2 interface, which takes P0.10 and P0.11 as SDA2 and SCL2 respectively. For this, we set them to use Funcnum = 2, which activates I2C.

static void init_i2c(void)
{
	PINSEL_CFG_Type PinCfg;
	PinCfg.Funcnum = 2; // when 10
	PinCfg.Pinnum = 10;
	PinCfg.Portnum = 0;
	PINSEL_ConfigPin(&PinCfg);
	PinCfg.Pinnum = 11;
	PINSEL_ConfigPin(&PinCfg);
	I2C_Init(LPC_I2C2, 100000); // clock rate 100000
	I2C_Cmd(LPC_I2C2, ENABLE);
}

GPIO is used for SW4, SW3 buttons, the buzzer and the temperature sensor. The following sections of code initializes the buttons SW3 (P2.10) and SW4 (P1.31), and the buzzer (P2.13).

static void init_GPIO(void) {
	//Initialize button sw4 (GPIO interrupt)
	PINSEL_CFG_Type PinCfg;
	PinCfg.Funcnum = 0; // Using normal GPIO setting for P1.31 when 00.
	PinCfg.OpenDrain = 0; // PUN
	PinCfg.Pinmode = 0; // PUN
	PinCfg.Portnum = 1;
	PinCfg.Pinnum = 31;
	PINSEL_ConfigPin(&PinCfg);
	GPIO_SetDir(1, 1<<31, 0); // Set input mode

SW4 is activated upon regular polling, with debounce time of 500ms.

if (timesUpOrNot(modeTime, 500)) {
  mode_button = GPIO_ReadValue(1) >> 31 & 0x01;
  if (mode_button == 0) {
    if (mode == MODE_STABLE) {
      reset();
      mode = MODE_MONITOR;
    } else if (mode == MODE_MONITOR) {
      printf("Entering STABLE mode.\n");
      mode = MODE_STABLE;
    }
    mode_button = 1;
  }
  modeTime = getTicks();
}

SW3 employs EINT0 (P2.10) instead of polling, unlike SW4. (This is because SW4 is on Port 1 where EINT0 is not supported.) To do so we must set FuncNum = 1.

	//Initialize button sw3 (Interrupt)
	PinCfg.Funcnum = 1; // Using EINT0: P2.10 is EINT0 when 01.
	PinCfg.OpenDrain = 0; // PUN
	PinCfg.Pinmode = 0; // PUN
	PinCfg.Portnum = 2;
	PinCfg.Pinnum = 10;
	PINSEL_ConfigPin(&PinCfg);
	GPIO_SetDir(2, 1 << 10, 0); // Set input mode

Of note is that the buzzer (LM4811-shutdown) and the RGB BLUE LED share the same pin P2.13.

	//Initialize buzzer (Speaker-Amplifier PWMs)
	GPIO_SetDir(0, 1<<27, 1); // SP-CLK / LM4811-clk
	GPIO_SetDir(0, 1<<28, 1); // SP-UP/DOWN / LM4811-up/dn
	GPIO_SetDir(2, 1<<13, 1); // RGB BLUE or LM4811-shutdn
	GPIO_ClearValue(0, 1<<27); // Set output mode
	GPIO_ClearValue(0, 1<<28);
	GPIO_ClearValue(2, 1<<13);
}

We also initialize SPI/SSP for 7-segment display and OLED, but its initialization is trivial and will not be explained here.

URCUTE then polls for the initial x, y, z accelerometer values and calculates its offsets. This ensures that x,y,z are relative to the position URCUTE was first initialized in.

acc_init();
acc_read(&x, &y, &z);
xoff = 0 - x; // Calculate offsets
yoff = 0 - y;
zoff = 0 - z;

URCUTE then sets up its light sensors in light_enable() (provided in Lib_EaBaseBoard) and config_light(). Of note is that we are setting up a light IRQ (P2.5) as falling edge interrupt. So when the reading falls below or above the specified threshold (in our case, 50 and 3891* respectively), an interrupt flag is written to P2.5.

static void config_light(void) {
	light_setRange(LIGHT_RANGE_4000);
	light_setLoThreshold(lightLoLimit);
	light_setHiThreshold(lightHiLimit);
	light_setIrqInCycles(LIGHT_CYCLE_16); // Used 16 for 4 sec activation time.
	light_clearIrqStatus();
	LPC_GPIOINT->IO2IntClr = 1 << 5;
	LPC_GPIOINT->IO2IntEnF |= 1 << 5; // enable falling edge interrupt for P2.5 (irq_out for light sensor)
}

The system uses an EINT3 interrupt handler for this purpose. It will first check P2.5 for the interrupt flag and set light_flag accordingly.

void EINT3_IRQHandler(void) {
	// Determine whether GPIO Interrupt P2.5 has occurred (LIGHT SENSOR) by checking pin
	if ((LPC_GPIOINT->IO2IntStatF>>5)& 0x1) {
		light_flag = LIGHT_LOW;
		LPC_GPIOINT->IO2IntClr = (1<<5); // Clear the interrupt register
		light_clearIrqStatus(); // Clear IRQ otherwise the interrupt will never be issued again.
	}
}

Further research finds that a max value of 972 for LIGHT_RANGE_1000 (essentially 3892/4 - 1) applies. This has to do with an overflowing bit issue with the light sensor and will not be explained in the scope of this project. Do remember to take this in mind when setting lightHiLimit. The effect does not apply to lightLoLimit.

Note on interrupts

There are a total of 3 interrupts used in URCUTE, in order of priority:

Their priorities are set by NVIC_SetPriority(), as below

void eint_init(void) {
	NVIC_SetPriority(SysTick_IRQn, 1); // Timer has the highest priority.

	// Enable EINT3 interrupt
	NVIC_ClearPendingIRQ(EINT3_IRQn);
	NVIC_SetPriority(EINT3_IRQn, 2); // Light has higher priority than button.
	NVIC_EnableIRQ(EINT3_IRQn);

	// Enable EINT0 interrupt with SW3
	LPC_SC->EXTINT = 1; // Clear existing interrupts.
	NVIC_ClearPendingIRQ(EINT0_IRQn);
	NVIC_SetPriority(EINT0_IRQn, 3);
	NVIC_EnableIRQ(EINT0_IRQn);
}

Note that lower number corresponds to higher priority.

Sending to CEMS via XBee

CEMS is simply a UART Serial terminal on a computer with a corresponding XBee pair connected to it. The following code sets it up on URCUTE, with baud rate at 115200.

void pinsel_uart3(void){
	// P0.0: uart1tx
	// P0.1: uart1rx
	PINSEL_CFG_Type PinCfg;
	PinCfg.Funcnum = 2;
	PinCfg.Pinnum = 0;
	PinCfg.Portnum = 0;
	PINSEL_ConfigPin(&PinCfg);
	PinCfg.Pinnum = 1;
	PINSEL_ConfigPin(&PinCfg);
}

void init_uart(void){
	UART_CFG_Type uartCfg;
	uartCfg.Baud_rate = 115200;
	uartCfg.Databits = UART_DATABIT_8;
	uartCfg.Parity = UART_PARITY_NONE;
	uartCfg.Stopbits = UART_STOPBIT_1;
	pinsel_uart3();	//pin select for uart3
	UART_Init(LPC_UART3, &uartCfg); //supply power & setup working parameters for uart3
	UART_TxCmd(LPC_UART3, ENABLE); //enable transmit for uart3
}

The remaining code initializes the various peripherals on the EaBaseBoard URCUTE is built on. There is nothing significant about them, and we will not be elaborating in detail here.

temp_init(getTicks);
pca9532_init();
joystick_init();
rgb_init(); // Since green LED interferes with OLED, do not use.
oled_init();
led7seg_init();
RTC_Init(LPC_RTC);
eint_init();
rotary_init();

Monitor Mode

Monitor mode is used when no caretaker is available. It samples

  1. the ambient temperature in deg C, every 5 seconds,
  2. the movement of the wearer with coordinates x,y,z, every 1 second,
  3. the ambient light in lux, every 5 seconds

and sends these to CEMS with format NNN_-_T*****_L*****_AX*****_AY*****_AZ*****\r\n every 5 seconds (or when the seven-segment display shows ‘5’, ‘A’, or ‘F’).

static void sendToCems(unsigned char *string) {
	UART_Send(LPC_UART3, (uint8_t *) string, strlen(string), BLOCKING);
}

In addition to the above factors, the following alarms are activated upon

  1. Increase in ambient temperature beyond a threshold of 45 deg C
    float temperature  = temp_read()/10.0;
    if (temperature >= 45)
      blink_red = 1;
    
  2. Movement is detected in the dark (below 50 lux)
    void isMovementInDarkDetected(uint8_t threshold) {
     if (sqrt(x*x+y*y+z*z) >= threshold && light_flag == LIGHT_LOW) {
         blink_blue = 1;
     }
    }
    
  3. Serious fall has occurred
    void isFallDetected(uint8_t threshold) {
     if (abs(y - yLast) >= threshold) {
         fall = FALL_DETECTED;
     }
    }
    

The appropriate LEDs blink according to the emergency accorded:

RED during a fire,

if (blink_red == 1) { // Blink RED LED for fire detection.
				if (timesUpOrNot(rgbTime, 333))
					GPIO_SetValue( 2, 1);
				if (timesUpOrNot(rgbTime, 666)) {
					GPIO_ClearValue( 2, 1);
					rgbTime = getTicks();
				}

BLUE during movement in the dark (The 16-LEDs also all turn on)

if (blink_blue == 1) { // Blink BLUE LED for movement in the dark.
				pca9532_setLeds(0xffff, 0); // On floodlights
				if (timesUpOrNot(rgbTime, 333))
					GPIO_SetValue( 0, (1<<26));
				if (timesUpOrNot(rgbTime, 666)) {
					GPIO_ClearValue( 0, (1<<26));
					rgbTime = getTicks();
				}

and both RED and BLUE when both have occurred (Also, the 16-LEDs all blink)

if (blink_red == 1 && blink_blue == 1) {
				if (timesUpOrNot(rgbTime, 333)) {
					pca9532_setLeds(0xffff, 0); // Flash floodlights on
					GPIO_SetValue( 2, 1); // Red first
					GPIO_ClearValue( 0, (1<<26)); // Clear blue
				}
				if (timesUpOrNot(rgbTime, 666)) {
					pca9532_setLeds(0, 0xffff); // Flash floodlights off
					GPIO_ClearValue( 2, 1); // Clear red
					GPIO_SetValue( 0, (1<<26)); // Set blue
					rgbTime = getTicks();
				}

The LEDs do not turn off until a caretaker is present and MODE change SW4 is pressed.

Pages

URCUTE supports three different pages in MONITOR mode:

  1. Main: Displays sampled environment information, updated every 5s.
    • MONITOR 0 is displayed at the top at all times.
  2. Information
    • 1 is displayed at the top at all times.
  3. Nightmode
    • NIGHT 2 is displayed at the top at all times.
static void changePage(uint8_t joyState)
{
	// Ignore up, down joystates
	if ((joyState & JOYSTICK_UP) != 0 || (joyState & JOYSTICK_DOWN) != 0) {
		return;
	}
	oled_clearScreen(OLED_COLOR_BLACK);
	int maxIndex = 3;

	// Reset
	if ((joyState & JOYSTICK_CENTER) != 0) {
		pageIndex = 0;
	} else if ((joyState & JOYSTICK_RIGHT) != 0) {
		pageIndex = (pageIndex+1)%3;
	} else if ((joyState & JOYSTICK_LEFT) != 0) {
		pageIndex = (pageIndex+5)%3;
	}

  if (pageIndex == 0) {
  reading_mode = 0;
  sampleEnvironmentAnd(PRINT);
} else if (pageIndex == 1) {
  reading_mode = 0;
  oled_putString(0,10,"URCUTE v0.1", OLED_COLOR_WHITE, OLED_COLOR_BLACK);
  oled_putString(0,30,"For licensing", OLED_COLOR_WHITE, OLED_COLOR_BLACK);
  oled_putString(0,40,"contact Arcana.", OLED_COLOR_WHITE, OLED_COLOR_BLACK);
} else if (pageIndex == 2) {
  reading_mode = 1;
  oled_putString(0,0, "NIGHT", OLED_COLOR_WHITE, OLED_COLOR_BLACK);
  oled_fillRect(0, 10, xW, OLED_DISPLAY_HEIGHT, OLED_COLOR_WHITE);
}

Night Mode

At any time, URCUTE is able to act as a nightlight to facilitate safe movement in the dark. The nightlight is able to be adjusted using the included rotary dial.

Furthermore, turning the rotary adjusts the brightness of the OLED accordingly:

if (reading_mode == 1 && rotaryState != ROTARY_WAIT) {
  if (rotaryState == ROTARY_RIGHT) {
    xW = xW + 6;
    if (xW > OLED_DISPLAY_WIDTH) {
      xW = OLED_DISPLAY_WIDTH;
    }
  } else if (rotaryState == ROTARY_LEFT) {
    xW = xW - 6;
    if (xW < 6) {
      xW = 6;
    }
  }
  oled_clearScreen(OLED_COLOR_BLACK);
  oled_putString(0,0,"READING", OLED_COLOR_WHITE, OLED_COLOR_BLACK);
  oled_fillRect(0, 10, xW, OLED_DISPLAY_HEIGHT, OLED_COLOR_WHITE);
}

Emergency Mode

URCUTE is able to interface with the CEMS HQ in case the user needs urgent assistance. When SW3 is pressed, it sends a message to CEMS. Once CEMS receives it successfully, the screen shows the message HELP REQUESTED.

if (emergency_flag == EMER_RAISED) {
  sendToCems("[MANUAL OVERRIDE] EMERGENCY ASSISTANCE REQUESTED!\r\n");
  emergency_flag = EMER_WAIT;
} else if (emergency_flag == EMER_WAIT) {
  oled_putString(0,55, "HELP REQUESTED.", OLED_COLOR_WHITE, OLED_COLOR_BLACK);
}

Until the request is acknowledged, the buzzer buzzes for half a second per second. Note that in this mode, the RED led does not blink, while the BLUE led does not blink normally, even if there are pending FIRE or DARK flags.

// The blinking of blink_blue and blink_red ceases in this mode
// and page defaults to 0.
if (timesUpOrNot(buzzerTime, 1000) && emergency_flag == EMER_WAIT) {
  buzz_first = 0;
  blink_red = 0;
  blink_blue = 0;
  playNote(pitch, 500);
  Timer0_Wait(1);
  buzzerTime = getTicks();
}

playNote() simply uses PWM to turn the BLUE led on and off in a fixed periodic duration, varying the tone and pitch of the buzzer. The lower the note ‘frequency’, the shorter each of its on/off periods (inversely proportional to frequency) and its the more times it modulates (proportional to duration), resulting in a higher note.

#define NOTE_PIN_HIGH() GPIO_SetValue(0, 1<<26)
#define NOTE_PIN_LOW() GPIO_ClearValue(0, 1<<26)

static void playNote(uint32_t note, uint32_t durationMs) {
	uint32_t t = 0;
	if (note>0) {
		while(t<(durationMs*1000)) {
			NOTE_PIN_HIGH();
			Timer0_us_Wait(note/2);
			NOTE_PIN_LOW();
			Timer0_us_Wait(note/2);
			t+=note;
		}
	} else {
		Timer0_Wait(durationMs);
	}
}

The pitch of the buzzer may be adjusted by varying the pitch variable being passed into the playNote(pitch, duration) method, through turning the rotary left or right. pitch here corresponds to the variable note, and defaults to 2000.

uint8_t rotaryState = rotary_read();
if (reading_mode == 0 && rotaryState!=ROTARY_WAIT) {
  if (rotaryState == ROTARY_RIGHT) {
    pitch -= 300;
    if (pitch <= 100) {
      pitch = 100;
    }
  } else if (rotaryState == ROTARY_LEFT) {
    pitch += 300;
    if (pitch >= 3000) {
      pitch = 3000;
    }
  }
}

Whenever this mode is triggered, the page shown on the OLED returns to Page 0, and the joystick, rotary is deactivated for Page 2 (NIGHTLIGHT).

if ((pageIndex == 1 || pageIndex == 2) && emergency_flag == EMER_WAIT) {
  reading_mode = 0;
  changePage(0x01);
}

When the request is acknowledged by CEMS, the display shows HELP IS COMING, and the buzzer switches off.

CEMS Override Mode

URCUTE may be remotely controlled via an authorized attendant at the CEMS HQ.

s: Change to STABLE mode.
m: Change to MONITOR mode.
e: Acknowledge EMERGENCY mode. Informs URCUTE that help is on its way, so it silences its alarms.

The following hunk of code, using UART_Receive library, enables this incoming mode of communication.

UART_Receive(LPC_UART3, &data, 1, NONE_BLOCKING);
		if (data == 's') {
			mode = MODE_STABLE;
			data = 0;
			sendToCems("\r\nEntering STABLE mode by CEMS.\r\n");
		} else if (data == 'm') {
			mode = MODE_MONITOR;
			data = 0;
			sendToCems("\r\nEntering MONITOR mode by CEMS.\r\n");
		} else if (data == 'e' && mode == MODE_MONITOR && emergency_flag == EMER_WAIT) {
			emergency_flag = EMER_RESOLVED;
			data = 0;
			sendToCems("\r\nCEMS responding to emergency...\r\n");
		}

Use Cases: Elderly Person A

0. Caretaker sets up URCUTE and heads back to CEMS HQ.

1. A’s wig is on fire

2. A is caught moving to the kitchen at night for his milk.

2. (a) A does not want to be caught during his milk run this time.

3. A is sleepwalking and knocks over a candle. The carpet catches fire.

4. A has fallen off his bed and is unconscious.

5. A notices a stranger at his door. The stranger seems to be holding a suspicious box and a knife.

5. (a) A caretaker has arrived, only to discover that it was another caretaker holding a birthday cake and a plastic knife. It is A’s birthday!

5. (b) A’s neighbor has a hearing aid which only picks up high pitch sounds.

License

Code released under the MIT License.

Copyright (c) [2017] [Justin Ng] [Lim Hong Wei], ECE (CEG2) @ NUS

Stay hungry, stay foolish.