Hiroh Satoh / keyboard Featured

Dependencies:   BLE_API mbed-dev nRF51822

main.cpp

Committer:
cho45
Date:
2016-08-26
Revision:
40:364deaa190fe
Parent:
39:b7889285c9ef
Child:
41:2b034f22b98f

File content as of revision 40:364deaa190fe:

/* mbed Microcontroller Library
 * Copyright (c) 2015 ARM Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <cmath>
#include "mbed.h"

#include "HIDController_BLE.h"

#include "mcp23017.h"
#include "keymap.h"


class KeyboardMatrixController {
	I2C& i2c;
	MCP23017 gpio1;
	MCP23017 gpio2;
	bool gpio1_ready;
	bool gpio2_ready;

	static const uint8_t GPIO1_SLAVE_ADDRESS = 0b0100000;
	static const uint8_t GPIO2_SLAVE_ADDRESS = 0b0100100;

	/**
	 * COL=GPIOA (output normaly positive)
	 * ROW=GPIOB (input pulled-up)
	 */

	bool setupGpio(MCP23017& gpio) {
		int ok;
		printf("SET IOCON\r\n");
		ok = gpio.write8(
			MCP23017::IOCON,
			0<<MCP23017::BANK |
			1<<MCP23017::MIRROR |
			1<<MCP23017::SEQOP |
			0<<MCP23017::DISSLW |
			1<<MCP23017::ODR // int pin is open drain
		);
		if (!ok) return false;

		// IODIR
		//   1: input
		//   0: output
		printf("SET IODIRA\r\n");
		ok = gpio.write16(
			MCP23017::IODIRA,
			0b0000000011111111
		);
		if (!ok) return false;

		// INPUT POLARITY
		//   1: inverse polarity
		//   0: raw
		printf("SET IPOLB\r\n");
		ok = gpio.write8(
			MCP23017::IPOLB,
			0b11111111
		);
		if (!ok) return false;

		// INTERRUPT-ON-CHANGE Enable
		printf("SET GPINTENB\r\n");
		ok = gpio.write8(
			MCP23017::GPINTENB,
			0b11111111
		);
		if (!ok) return false;
		
		// INTERRUPT-ON-CHANGE Control
		//   1: compared with DEFVAL
		//   0: compared to previous value
		printf("SET INTCONB\r\n");
		ok = gpio.write8(
			MCP23017::INTCONB,
			0b00000000
		);
		if (!ok) return false;
		
		// PULL-UP (for input pin)
		//   1: pull-up enabled
		//   0: pull-up disabled
		printf("SET GPPUB\r\n");
		ok = gpio.write8(
			MCP23017::GPPUB,
			0b11111111
		);
		if (!ok) return false;

		printf("SET GPIOA\r\n");
		ok = gpio1.write8(
			MCP23017::GPIOA,
			0b00000000
		);
		if (!ok) return false;

		return true;
	}

public:
	KeyboardMatrixController(I2C& _i2c) :
		i2c(_i2c),
		gpio1(i2c, GPIO1_SLAVE_ADDRESS),
		gpio2(i2c, GPIO2_SLAVE_ADDRESS)
	{
	}

	void init() {
		printf("init gpio1\r\n");
		gpio1_ready = setupGpio(gpio1);
		printf("gpio1 initialized: %s\r\n", gpio1_ready ? "success" : "failed");
		
		printf("init gpio2\r\n");
		gpio2_ready = setupGpio(gpio2);
		printf("gpio2 initialized: %s\r\n", gpio2_ready ? "success" : "failed");

	}

	// __attribute__((used, long_call, section(".data")))
	void scanKeyboard(uint8_t* keys) {
		int ok;
		
		disableInterrupt();

		if (gpio1_ready) {
			for (int i = 0; i < 8; i++) {
				ok = gpio1.write8(
					MCP23017::GPIOA,
					~(1<<i)
				);
				wait_us(1);
				keys[i] = gpio1.read8(MCP23017::GPIOB, ok);
			}
	
			// set all output to negative for interrupt
			ok = gpio1.write8(
				MCP23017::GPIOA,
				0b00000000
			);
		}


		if (gpio2_ready) {
			for (int i = 0; i < 8; i++) {
				ok = gpio2.write8(
					MCP23017::GPIOA,
					~(1<<i)
				);
				wait_us(1);
				keys[i+8] = gpio2.read8(MCP23017::GPIOB, ok);
			}
	
			// set all output to negative for interrupt
			ok = gpio2.write8(
				MCP23017::GPIOA,
				0b00000000
			);
		}
		
		enableInterrupt();
	}
	
	int disableInterrupt() {
		int ok;
		if (gpio1_ready) {
			// Disable interrupt
			ok = gpio1.write8(
				MCP23017::GPINTENB,
				0b00000000
			);
		}
		
		if (gpio2_ready) {
			// Disable interrupt
			ok = gpio2.write8(
				MCP23017::GPINTENB,
				0b00000000
			);
		}
		return ok;
	}

	int enableInterrupt() {
		int ok;
		if (gpio1_ready) {
			// Enable interrupt
			ok = gpio1.write8(
				MCP23017::GPINTENB,
				0b11111111
			);
		}
		
		if (gpio2_ready) {
			// Enable interrupt
			ok = gpio2.write8(
				MCP23017::GPINTENB,
				0b11111111
			);
		}

		// Clear interrupt
		// gpio1.read8(MCP23017::GPIOB, ok);
		return ok;
	}
};

I2C i2c(I2C_SDA0, I2C_SCL0);
// Serial serial(USBTX, USBRX);
KeyboardMatrixController keyboardMatrixController(i2c);
Keymap keymap;

// Interrupt from MCP23017
// (pulled-up and two MCP23017 is configured with open drain INT)
InterruptIn buttonInt(P0_5);

#define PIN_STATUS_LED P0_4
DigitalOut statusLed(PIN_STATUS_LED, 0);

// Unsed pins. Set to output for power consumption

// Pad pinout
/*
DigitalIn unused_p0_7(P0_7, PullUp);
DigitalIn unused_p0_6(P0_6, PullUp);
DigitalIn unused_p0_15(P0_15, PullUp);
DigitalIn unused_p0_29(P0_29, PullUp);
DigitalIn unused_p0_28(P0_28, PullUp);
*/

DigitalIn unused_p0_19(P0_19, PullUp); // This is on board LED which connected to VDD
DigitalIn unused_p0_11(P0_11, PullUp); // RXD

Timeout timeout;

// ROWS=8
// COLS=16
// 列ごとに1バイトにパックしてキーの状態を保持する
static uint8_t keysA[COLS];
static uint8_t keysB[COLS];
static bool state = 0;
#define is_pressed(keys, row, col) (!!(keys[col] & (1<<row)))

// delay for interrupt
static volatile int8_t pollCount = 50;

void buttonIntCallback() {
	// just for wakeup
	pollCount = 25;
}

void powerOff() {
	printf("power off\r\n");
	NRF_POWER->SYSTEMOFF = 1;
}

void tickerStatus() {
    statusLed = !statusLed;
}

static bool updateStatudLedEnabled = false;
void updateStatusLed() {
	switch (HIDController::status()) {
		case TIMEOUT:
		case DISCONNECTED:
		case CONNECTED:
			statusLed = 0;
			updateStatudLedEnabled = false;
			return;
		case ADVERTISING:
		case CONNECTING:
			statusLed = !statusLed;
			updateStatudLedEnabled = true;
			timeout.attach(updateStatusLed, statusLed ? 0.5 : 0.1);
			break;
	}
}

static volatile bool keyIntervalInterrupt = false;
void wakeupKeyIntervalSleep() {
	keyIntervalInterrupt = true;
}

class WatchDog {
	static const uint32_t RELOAD_VALUE = 0x6E524635;
	static const uint8_t WDT_TIMEOUT = 3; // sec
public:
	static void init() {
		// timeout [s] = (CRV + 1) / 32768;
		// crv = 32768 * timeout - 1
		NRF_WDT->CRV = 32768 * WDT_TIMEOUT - 1;
		NRF_WDT->RREN = WDT_RREN_RR0_Enabled << WDT_RREN_RR0_Pos;
		NRF_WDT->CONFIG = WDT_CONFIG_SLEEP_Pause << WDT_CONFIG_SLEEP_Pos;
		NRF_WDT->TASKS_START = 1;
	}
	
	static void reload() {
		NRF_WDT->RR[0] = RELOAD_VALUE;
	}
	
};

class Battery {

public:
	static const float BATTERY_MAX = 2.4;
	static const float REFERNECE = 1.2;
	static const float PRESCALE = 3;
	static const float BATTERY_LOW = 2.0;
	
	static uint8_t readBatteryPercentage(float voltage) {
		uint16_t percentage = (voltage - BATTERY_LOW) / (BATTERY_MAX - BATTERY_LOW) * 100;
		if (percentage > 100) percentage = 100;
		return percentage;
	}
	
	static float readBatteryVoltage() {
		NRF_ADC->ENABLE = ADC_ENABLE_ENABLE_Enabled;
	
		// Use internal 1.2V reference for batteryInput
		//	1/3 pre-scaled input and 1.2V internal band gap reference
		// ref. mbed-src/targets/hal/TARGET_NORDIC/TARGET_MCU_NRF51822/analogin_api.c
		NRF_ADC->CONFIG =
			(ADC_CONFIG_RES_10bit << ADC_CONFIG_RES_Pos) |
			// Use VDD 1/3 for input
			(ADC_CONFIG_INPSEL_SupplyOneThirdPrescaling << ADC_CONFIG_INPSEL_Pos) |
			// Use internal band gap for reference
			(ADC_CONFIG_REFSEL_VBG << ADC_CONFIG_REFSEL_Pos) |
			(ADC_CONFIG_EXTREFSEL_None << ADC_CONFIG_EXTREFSEL_Pos);
	
		// Start ADC
		NRF_ADC->TASKS_START = 1;
		while (((NRF_ADC->BUSY & ADC_BUSY_BUSY_Msk) >> ADC_BUSY_BUSY_Pos) == ADC_BUSY_BUSY_Busy) {
			// busy loop
		}
	
		// Read ADC result
		uint16_t raw10bit = static_cast<uint16_t>(NRF_ADC->RESULT);
		
		NRF_ADC->ENABLE = ADC_ENABLE_ENABLE_Disabled;
		
		float ratio = raw10bit / static_cast<float>(1<<10);
	
		float batteryVoltage = ratio * (REFERNECE * PRESCALE);
		return batteryVoltage;
	}
};

#define is_pressed(keys, row, col) (!!(keys[col] & (1<<row)))

int main(void) {
	{
		uint32_t reason = NRF_POWER->RESETREAS;
		NRF_POWER->RESETREAS = 0xffffffff; // clear reason
		printf("init [%x]\r\n", reason);
	}
	
	float battery = Battery::readBatteryVoltage();
	if (battery < Battery::BATTERY_LOW) {
		powerOff();
	}
			
 	// Enable Pin-reset on DEBUG mode
 	// This makes possiable booting without normal mode easily.
	NRF_POWER->RESET = 1;
	// Disable Internal DC/DC step down converter surely
	NRF_POWER->DCDCEN = 0;
	// Enable 2.1V brown out detection for avoiding over discharge of NiMH
	NRF_POWER->POFCON = 
		POWER_POFCON_POF_Enabled << POWER_POFCON_POF_Pos |
		POWER_POFCON_THRESHOLD_V21 << POWER_POFCON_THRESHOLD_Pos;
    
	// mbed's Serial of TARGET_RBLAB_BLENANO sucks
	// DO NOT CONNECT RTS/CTS WITHOUT PRIOR CONSENT!
	NRF_UART0->PSELRTS = 0xFFFFFFFFUL;
	NRF_UART0->PSELCTS = 0xFFFFFFFFUL;
	
	// Set LED Pin as HIGH Current mode
	NRF_GPIO->PIN_CNF[PIN_STATUS_LED] =
		(NRF_GPIO->PIN_CNF[PIN_STATUS_LED] & GPIO_PIN_CNF_DRIVE_Msk) |
		(GPIO_PIN_CNF_DRIVE_H0H1 << GPIO_PIN_CNF_DRIVE_Pos);

	WatchDog::init();
			    		
	// 100kHz
	i2c.frequency(250000);

	buttonInt.mode(PullUp);
	buttonInt.fall(buttonIntCallback);

	keyboardMatrixController.init();
	pollCount = 10;

	HIDController::init();
		
	// STOP UART RX for power consumption
	NRF_UART0->TASKS_STOPRX = 1;
	
	// Disable TWI by default.
	NRF_TWI0->ENABLE = TWI_ENABLE_ENABLE_Disabled << TWI_ENABLE_ENABLE_Pos;
		
	while (1) {
		WatchDog::reload();
		
		if (pollCount > 0) {
			printf("scan keys\r\n");
  		    
			while (pollCount-- > 0) {
				WatchDog::reload();
				
				uint8_t (&keysCurr)[COLS] = state ? keysA : keysB;
				uint8_t (&keysPrev)[COLS] = state ? keysB : keysA;
	
				NRF_TWI0->ENABLE = TWI_ENABLE_ENABLE_Enabled << TWI_ENABLE_ENABLE_Pos;
				keyboardMatrixController.scanKeyboard(keysCurr);
				NRF_TWI0->ENABLE = TWI_ENABLE_ENABLE_Disabled << TWI_ENABLE_ENABLE_Pos;
	
				bool queue = false;
	
				for (int col = 0; col < COLS; col++) {
					uint8_t changed = keysPrev[col] ^ keysCurr[col];
					if (changed) queue = true;
					for (int row = 0; row < ROWS; row++) {
						if (changed & (1<<row)) {
							bool pressed = keysCurr[col] & (1<<row);
							// printf("changed: col=%d, row=%d / pressed=%d\r\n", col, row, pressed);
							keymap.execute(col, row, pressed);
						}
					}
				}
				state = !state;
				
				if (HIDController::status() == DISCONNECTED ||
				    HIDController::status() == TIMEOUT) {
				    	
				    if (
				    	is_pressed(keysCurr, 0, 0) && // left top 1
				    	is_pressed(keysCurr, 1, 0) && // left top 2
				    	is_pressed(keysCurr, 0, 15)   // right top
				    ) {
				    	printf("re-init connection\r\n");
				    	HIDController::initializeConnection(true);
				    } else {
				    	if (HIDController::status() == TIMEOUT) {
					    	printf("wakeup\r\n");
					    	HIDController::initializeConnection(false);
				    	}
				    }
				}
	
				if (queue) HIDController::queueCurrentReportData();
				
				// wait_ms(5); is busy loop
				// use timer1 to use wait 5ms
				timeout.attach_us(wakeupKeyIntervalSleep, 5000);
				keyIntervalInterrupt = false;
				while (!keyIntervalInterrupt) sleep();
			}
		} else {
			if (!updateStatudLedEnabled) updateStatusLed();
			
			float batteryVoltage = Battery::readBatteryVoltage();
			uint8_t batteryPercentage = Battery::readBatteryPercentage(batteryVoltage);
			bool isLowBattery = batteryVoltage < Battery::BATTERY_LOW;

			printf("%d%% [%d:%s] %s\r\n",
				batteryPercentage,
				HIDController::status(),
				HIDController::statusString(),
				isLowBattery ? "LOWBAT" : "WFE"
			);
			
			HIDController::updateBatteryLevel(batteryPercentage);
			
			if (isLowBattery) {
				powerOff();
			}
    

			// disable internal HFCLK RC Clock surely. It consume 1mA constantly
			// TWI / SPI / UART must be disabled and boot without debug mode
			while (NRF_UART0->EVENTS_TXDRDY != 1);
			
			uint32_t tx = NRF_UART0->PSELTXD;
			
			NRF_UART0->TASKS_STOPTX = 1;
			NRF_UART0->ENABLE = (UART_ENABLE_ENABLE_Disabled << UART_ENABLE_ENABLE_Pos);

			HIDController::waitForEvent();
						
			NRF_UART0->ENABLE = (UART_ENABLE_ENABLE_Enabled << UART_ENABLE_ENABLE_Pos);
			NRF_UART0->TASKS_STARTTX = 1;
			// dummy send to wakeup...
			NRF_UART0->PSELTXD = 0xFFFFFFFF;
		    NRF_UART0->EVENTS_TXDRDY = 0;
		    NRF_UART0->TXD = 0;
		    while (NRF_UART0->EVENTS_TXDRDY != 1);
			NRF_UART0->PSELTXD = tx;

		}
	}
}