Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
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;
}
}
}