Hiroh Satoh / keyboard Featured

Dependencies:   BLE_API mbed-dev nRF51822

main.cpp

Committer:
cho45
Date:
2016-08-30
Revision:
58:64df960619ce
Parent:
50:c1382a0ff066
Child:
59:2d6c0bff2151

File content as of revision 58:64df960619ce:

/* 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);

static I2C i2c(I2C_SDA0, I2C_SCL0);
static KeyboardMatrixController keyboardMatrixController(i2c);
static Keymap keymap;

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

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

// timeout for status led and wakeup from sleep
static 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;

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

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

static bool updateStatudLedEnabled = false;
static 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.1 : 0.5);
			break;
	}
}

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

int main(void) {
	{
		const uint32_t reason = NRF_POWER->RESETREAS;
		NRF_POWER->RESETREAS = 0xffffffff; // clear reason
		// reset cause should be shown everytime
		serial.printf("init [%x]\r\n", reason);
	}

	{
		const 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;

	// 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);

	keyboardInterruptIn.mode(PullUp);
	keyboardInterruptIn.fall(keyboardInterrupt);

	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++) {
					const 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(row, col, 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();

				// use timer to use wait 5ms
				timeout.detach();
				keyIntervalInterrupt = false;
				timeout.attach_us(wakeupKeyIntervalSleep, 5000);
				while (!keyIntervalInterrupt) HIDController::waitForEvent();
			}
		} else {
			if (!updateStatudLedEnabled) updateStatusLed();

			{
				const float batteryVoltage = BatteryLevel::readBatteryVoltage();
				const uint8_t batteryPercentage = BatteryLevel::readBatteryPercentage(batteryVoltage);
				const 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, batteryVoltage * 1000);
	
				if (isLowBattery) {
					powerOff();
				}
			}
			
			if (HIDController::status() == DISCONNECTED) {
				HIDController::initializeConnection(false);
			}
			
			HIDController::waitForEvent();
		}
	}
}