ble nano hid over gatt

Dependencies:   BLE_API mbed-dev nRF51822

main.cpp

Committer:
cho45
Date:
2016-09-15
Revision:
86:e0fab77e669d
Parent:
77:d4be914fb87c

File content as of revision 86:e0fab77e669d:

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

static volatile bool shouldUpdateBatteryLevel = true;
static void wakeupIgnoreLongSleep() {
	// just wakeup for reloading WDT
	shouldUpdateBatteryLevel = true;
}

void setupIO () {
	// 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);

}

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] sp:%x\r\n", reason, GET_SP());
	}

	float batteryVoltage = BatteryLevel::readBatteryVoltage();
	if (batteryVoltage < 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;

	setupIO();

	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;

	{
		// init battery level
		const uint8_t batteryPercentage = BatteryLevel::readBatteryPercentage(batteryVoltage);
		HIDController::updateBatteryLevel(batteryPercentage, batteryVoltage * 1000);
	}

	while (1) {
		if (pollCount > 0) {
			DEBUG_PRINTF("scan keys\r\n");

			while (pollCount-- > 0) {
				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();

			if (shouldUpdateBatteryLevel) {
				shouldUpdateBatteryLevel = false;
				float batteryVoltage = BatteryLevel::readBatteryVoltage();
				const uint8_t batteryPercentage = BatteryLevel::readBatteryPercentage(batteryVoltage);
				const bool isLowBattery = batteryVoltage < BatteryLevel::BATTERY_LOW;

				HIDController::updateBatteryLevel(batteryPercentage, batteryVoltage * 1000);

				if (isLowBattery) {
					DEBUG_PRINTF("LOWBAT %f %d%%", batteryVoltage, batteryPercentage);
					powerOff();
				}
			}

			if (HIDController::status() == DISCONNECTED) {
				HIDController::initializeConnection(false);
			}

			DEBUG_PRINTF("WFE [%d:%s]\r\n",
				HIDController::status(),
				HIDController::statusString()
			);

			if (!updateStatudLedEnabled) {
				timeout.detach();
				timeout.attach(wakeupIgnoreLongSleep, 60);
			}
			HIDController::waitForEvent();
		}
	}
}