Hiroh Satoh / keyboard Featured

Dependencies:   BLE_API mbed-dev nRF51822

main.cpp

Committer:
cho45
Date:
2016-07-20
Revision:
4:54cb552e50c4
Parent:
3:266dfa9f8709
Child:
5:65d4e94735b6

File content as of revision 4:54cb552e50c4:

/* 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 "mbed.h"
#include "BLE.h"
#include "KeyboardService.h"
#include "BatteryService.h"
#include "DeviceInformationService.h"
#include "mcp23017.h"

static const char MODEL_NAME[] = "keyboard";
static const char SERIAL_NUMBER[] = "X00000";
static const char HARDWARE_REVISION[] = "0.1";
static const char FIRMWARE_REVISION[] = "0.1";
static const char SOFTWARE_REVISION[] = "0.0";

static const uint8_t DEVICE_NAME[] = "my keyboard";
static const uint8_t SHORT_DEVICE_NAME[] = "kbd1";

static const bool ENABLE_BONDING = true;
static const bool REQUIRE_MITM = true;
static const uint8_t PASSKEY[6] = {'1','2','3','4','5','6'}; // must be 6-digits number

static const uint16_t uuid16_list[] =  {
	GattService::UUID_HUMAN_INTERFACE_DEVICE_SERVICE,
	GattService::UUID_DEVICE_INFORMATION_SERVICE,
	GattService::UUID_BATTERY_SERVICE
};

KeyboardService* keyboardService;
BatteryService* batteryService;
DeviceInformationService* deviceInformationService;

I2C i2c(I2C_SDA0, I2C_SCL0);
MCP23017 gpio1(i2c, 0b0100000);
InterruptIn buttonInt(P0_5);

void updateBatteryLevel() {
	if (!batteryService) return;
	static const float BATTERY_MAX = 2.4;
	static const float REFERNECE = 1.2;
	static const float PRESCALE = 3;

	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);
	float ratio = raw10bit / static_cast<float>(1<<10);

	float batteryVoltage = ratio * (REFERNECE * PRESCALE);
	float percentage = (batteryVoltage / BATTERY_MAX) * 100;
	if (percentage > 100) {
		percentage = 100;
	}
	printf("updateBatteryLevel %f V : %d/100\r\n", batteryVoltage, static_cast<uint8_t>(percentage));
	batteryService->updateBatteryLevel(static_cast<uint8_t>(percentage));
}


static void onConnect(const Gap::ConnectionCallbackParams_t *params) {
	printf("onConnect\r\n");
}

static void onDisconnect(const Gap::DisconnectionCallbackParams_t *params) {
	printf("onDisconnect\r\n");
	BLE::Instance(BLE::DEFAULT_INSTANCE).gap().startAdvertising();
}

static void onTimeout(const Gap::TimeoutSource_t source) {
	printf("onTimeout\r\n");
	BLE::Instance(BLE::DEFAULT_INSTANCE).gap().startAdvertising();
}

static void passkeyDisplayCallback(Gap::Handle_t handle, const SecurityManager::Passkey_t passkey) {
	printf("Input passKey: ");
	for (unsigned i = 0; i < Gap::ADDR_LEN; i++) {
		printf("%c", passkey[i]);
	}
	printf("\r\n");
}

static void securitySetupCompletedCallback(Gap::Handle_t handle, SecurityManager::SecurityCompletionStatus_t status) {
	if (status == SecurityManager::SEC_STATUS_SUCCESS) {
		printf("Security success %d\r\n", status);
	} else {
		printf("Security failed %d\r\n", status);
	}
}

static void securitySetupInitiatedCallback(Gap::Handle_t, bool allowBonding, bool requireMITM, SecurityManager::SecurityIOCapabilities_t iocaps) {
	printf("Security setup initiated\r\n");
}

static void bleInitComplete(BLE::InitializationCompleteCallbackContext *params) {
	// https://developer.mbed.org/compiler/#nav:/keyboard/BLE_API/ble/blecommon.h;
	ble_error_t error;
	BLE &ble          = params->ble;
	
	error = params->error;
	if (error != BLE_ERROR_NONE) {
		printf("error on ble.init() \r\n");
		goto return_error;
	}

	ble.gap().onDisconnection(onDisconnect);
	ble.gap().onConnection(onConnect);
	ble.gap().onTimeout(onTimeout);

	printf("setup ble security manager\r\n");
	ble.securityManager().onSecuritySetupInitiated(securitySetupInitiatedCallback);
	ble.securityManager().onPasskeyDisplay(passkeyDisplayCallback);
	ble.securityManager().onSecuritySetupCompleted(securitySetupCompletedCallback);
	// bonding with hard-coded passkey.
	error = ble.securityManager().init(ENABLE_BONDING, REQUIRE_MITM, SecurityManager::IO_CAPS_DISPLAY_ONLY, PASSKEY);
	if (error != BLE_ERROR_NONE) {
		printf("error on ble.securityManager().init()");
		goto return_error;
	}

	keyboardService = new KeyboardService(ble);
	deviceInformationService = new DeviceInformationService(ble, "lowreal.net", MODEL_NAME, SERIAL_NUMBER, HARDWARE_REVISION, FIRMWARE_REVISION, SOFTWARE_REVISION);
	batteryService = new BatteryService(ble, 100);
	updateBatteryLevel();

	printf("general setup\r\n");
	error = ble.gap().accumulateAdvertisingPayload(
		GapAdvertisingData::BREDR_NOT_SUPPORTED |
		GapAdvertisingData::LE_GENERAL_DISCOVERABLE
	);
	if (error != BLE_ERROR_NONE) goto return_error;

	printf("set COMPLETE_LIST_16BIT_SERVICE_IDS\r\n");
	error = ble.gap().accumulateAdvertisingPayload(
		GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS,
		(uint8_t*)uuid16_list, sizeof(uuid16_list)
	);
	if (error != BLE_ERROR_NONE) goto return_error;

	printf("set advertising\r\n");
	// see 5.1.2: HID over GATT Specification (pg. 25)
	ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);

	printf("set advertising interval\r\n");
	ble.gap().setAdvertisingInterval(50);
	// XXX
	// ble.gap().setAdvertisingTimeout(0);

	printf("set keyboard\r\n");
	error = ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::KEYBOARD);
	if (error != BLE_ERROR_NONE) goto return_error;

	printf("set complete local name\r\n");
	error = ble.gap().accumulateAdvertisingPayload(
		GapAdvertisingData::COMPLETE_LOCAL_NAME,
		DEVICE_NAME, sizeof(DEVICE_NAME)
	);
	if (error != BLE_ERROR_NONE) goto return_error;

	printf("set device name\r\n");
	error = ble.gap().setDeviceName(DEVICE_NAME);
	if (error != BLE_ERROR_NONE) goto return_error;
	// ble.gap().setTxPower(-12);


	printf("advertising\r\n");
	error = ble.gap().startAdvertising();
	if (error != BLE_ERROR_NONE) goto return_error;
	return;

return_error:
	printf("error with %d\r\n", error);
	return;
}

class MyKeyboardService : public HIDServiceBase {
	union {
		uint8_t raw[8];
		struct {
			uint8_t modifier;
			uint8_t padding;
			uint8_t keycode[6];
		} data;
	} inputReportData;

	union {
		uint8_t raw[1];
	} outputReportData;
public:
	MyKeyboardService(BLE& _ble) :
		HIDServiceBase(
			_ble,
			KEYBOARD_REPORT_MAP,
			sizeof(KEYBOARD_REPORT_MAP),
			inputReport         = inputReportData.raw,
			outputReport        = outputReportData.raw,
			featureReport       = NULL,
			inputReportLength   = sizeof(inputReportData),
			outputReportLength  = sizeof(outputReportData),
			featureReportLength = 0,
			reportTickerDelay   = 24
		)
	{
	}

	void appendReportData(uint8_t keycode) {
		for (int i = 0; i < 6; i++) {
			if (inputReportData.data.keycode[i] == 0) {
				inputReportData.data.keycode[i] = keycode;
				startReportTicker();
				return;
			}
		}

		// TODO: report data is full
	}

	void deleteReportData(uint8_t keycode) {
		for (int i = 0; i < 6; i++) {
			if (inputReportData.data.keycode[i] == keycode) {
				inputReportData.data.keycode[i] = 0;
				startReportTicker();
				return;
			}
		}
	}

	bool isKeyPressed() {
		for (int i = 0; i < 8; i++) {
			if (inputReportData.raw[i]) {
				return 1;
			}
		}
		return 0;
	}

	virtual void sendCallback(void) {
		ble_error_t error = HIDServiceBase::send(inputReportData.raw);
		if (error == BLE_STACK_BUSY) {
			// retry after
			return;
		}

		if (!isKeyPressed()) {
			stopReportTicker();
		}
	}
};

void buttonIntCallback() {
	int ok;
	printf("buttonInt!!\r\n");
	uint8_t read = gpio1.read8(MCP23017::GPIOA, ok);
	printf("read %x\r\n", read);
}

int main(void) {
	// printf("init\r\n");

	// mbed's Serial of TARGET_RBLAB_BLENANO sucks
	// DO NOT CONNECT RTS/CTS AUTOMATICALY!
	NRF_UART0->PSELRTS = 0xFFFFFFFFUL;
	NRF_UART0->PSELCTS = 0xFFFFFFFFUL;

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

	int ok;

	// 100kHz
	i2c.frequency(100000);

	ok = gpio1.write8(
		MCP23017::IOCON,
		0<<MCP23017::BANK |
		1<<MCP23017::MIRROR |
		1<<MCP23017::SEQOP |
		0<<MCP23017::DISSLW |
		1<<MCP23017::ODR
	);

	// IODIR
	//   1: input
	//   0: output
	ok = gpio1.write16(
		MCP23017::IODIRA,
		0b1111111100000000
	);

	// INPUT POLARITY
	ok = gpio1.write8(
		MCP23017::IPOLA,
		0b00000000
	);
	// INTERRUPT-ON-CHANGE Enable
	ok = gpio1.write8(
		MCP23017::GPINTENA,
		0b11111111
	);
	// INTERRUPT-ON-CHANGE Control
	//   1: compared with DEFVAL
	//   0: compared to previous value
	ok = gpio1.write8(
		MCP23017::INTCONA,
		0b00000000
	);
	// PULL-UP (for input pin)
	//   1: pull-up enabled
	//   0: pull-up disabled
	ok = gpio1.write8(
		MCP23017::GPPUA,
		0b11111111
	);

	if (!ok) {
		printf("failed to write to i2c bus\r\n");
	} else {
		printf("wrote to i2c bus\r\n");
	}

	uint8_t read = gpio1.read8(MCP23017::GPIOA, ok);
	printf("read %x\r\n", read);

	while (1) {}

	return;

	// https://github.com/jpbrucker/BLE_HID/blob/master/examples/examples_common.cpp

	printf("ble.init\r\n");
	BLE& ble = BLE::Instance(BLE::DEFAULT_INSTANCE);
	ble.init(bleInitComplete);

	while (!ble.hasInitialized()) { }

	printf("ble.hasIntialized\r\n");

	while (1) {
		ble.waitForEvent();
	}
}