Hiroh Satoh / keyboard Featured

Dependencies:   BLE_API mbed-dev nRF51822

main.cpp

Committer:
cho45
Date:
2016-08-27
Revision:
42:2c3be8694896
Parent:
41:2b034f22b98f
Child:
43:4de3870b39cb

File content as of revision 42:2c3be8694896:

/* BLE Keyboard Implementation for mbed
 * Copyright (c) 2016 cho45 <cho45@lowreal.net>
 *
 * 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 "KeyboardMatrixController.h"
#include "BatteryLevel.h"
#include "WatchDog.h"

#include "keymap.h"
#include "config.h"

RawSerial serial(USBTX, USBRX);
I2C i2c(I2C_SDA0, I2C_SCL0);
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);

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() {
	DEBUG_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;
}

#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
		serial.printf("init [%x]\r\n", reason);
	}
	
	float battery = BatteryLevel::readBatteryVoltage();
	if (battery < BatteryLevel::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_S0S1 << GPIO_PIN_CNF_DRIVE_Pos);

	// Unsed pins.
	// Set as PullUp for power consumption
	// Use GPIO registers directly for saving ram
	// Pad pinouts
	/* 
	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);
	*/
	NRF_GPIO->PIN_CNF[P0_7] =
		(GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos) |
		(GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos) |
		(GPIO_PIN_CNF_PULL_Pullup << GPIO_PIN_CNF_PULL_Pos) |
		(GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos) |
		(GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos);
	NRF_GPIO->PIN_CNF[P0_6] =
		(GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos) |
		(GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos) |
		(GPIO_PIN_CNF_PULL_Pullup << GPIO_PIN_CNF_PULL_Pos) |
		(GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos) |
		(GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos);
	NRF_GPIO->PIN_CNF[P0_15] =
		(GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos) |
		(GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos) |
		(GPIO_PIN_CNF_PULL_Pullup << GPIO_PIN_CNF_PULL_Pos) |
		(GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos) |
		(GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos);
	NRF_GPIO->PIN_CNF[P0_29] =
		(GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos) |
		(GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos) |
		(GPIO_PIN_CNF_PULL_Pullup << GPIO_PIN_CNF_PULL_Pos) |
		(GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos) |
		(GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos);
	NRF_GPIO->PIN_CNF[P0_28] =
		(GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos) |
		(GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos) |
		(GPIO_PIN_CNF_PULL_Pullup << GPIO_PIN_CNF_PULL_Pos) |
		(GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos) |
		(GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos);
	/*
	DigitalIn unused_p0_19(P0_19, PullUp); // This is on board LED which connected to VDD
	DigitalIn unused_p0_11(P0_11, PullUp); // RXD
	*/
	NRF_GPIO->PIN_CNF[P0_19] = // This is on board LED which connected to VDD
		(GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos) |
		(GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos) |
		(GPIO_PIN_CNF_PULL_Pullup << GPIO_PIN_CNF_PULL_Pos) |
		(GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos) |
		(GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos);
	NRF_GPIO->PIN_CNF[P0_11] = // RXD
		(GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos) |
		(GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos) |
		(GPIO_PIN_CNF_PULL_Pullup << GPIO_PIN_CNF_PULL_Pos) |
		(GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos) |
		(GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos);

	WatchDog::init();

	// only 100kHz/250khz/400khz
	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) {
			DEBUG_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);
							// DEBUG_KEYEVENT("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
				    ) {
				    	DEBUG_PRINTF("re-init connection\r\n");
				    	HIDController::initializeConnection(true);
				    } else {
				    	if (HIDController::status() == TIMEOUT) {
					    	DEBUG_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 = BatteryLevel::readBatteryVoltage();
			uint8_t batteryPercentage = BatteryLevel::readBatteryPercentage(batteryVoltage);
			bool isLowBattery = batteryVoltage < BatteryLevel::BATTERY_LOW;

			DEBUG_PRINTF("%d%% [%d:%s] %s\r\n",
				batteryPercentage,
				HIDController::status(),
				HIDController::statusString(),
				isLowBattery ? "LOWBAT" : "WFE"
			);
			
			HIDController::updateBatteryLevel(batteryPercentage);
			
			if (isLowBattery) {
				powerOff();
			}
    
			
			if (DEBUG_BLE_INTERRUPT) {
				HIDController::waitForEvent();
			} else {
				// 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;
			}
		}
	}
}