Espruino/libs/microbit/jswrap_microbit.c
2020-10-02 12:36:30 +01:00

440 lines
12 KiB
C

/*
* This file is part of Espruino, a JavaScript interpreter for Microcontrollers
*
* Copyright (C) 2013 Gordon Williams <gw@pur3.co.uk>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* ----------------------------------------------------------------------------
* This file is designed to be parsed during the build process
*
* Contains JavaScript interface to micro:bit
* ----------------------------------------------------------------------------
*/
#include "jswrap_microbit.h"
#include "jswrapper.h"
#include "jstimer.h"
#include "jsparse.h"
#include "jsvariterator.h"
#include "nrf_gpio.h" // just go direct
#ifdef MICROBIT2
#include "jsi2c.h" // accelerometer/etc
// we use software I2C
JshI2CInfo i2cInfo;
// All microbit 2's have the new mmagnetometer
const bool microbitLSM303 = true;
#else
// 32 means not used
static const uint8_t MB_LED_MAPPING[] = {
0, 2, 4, 19, 18, 17, 16, 15, 11,
14, 10, 12, 1, 3, 23, 21, 32, 32,
22, 24, 20, 5, 6, 7, 8, 9, 13,
};
const int MMA8652_ADDR = 0x1D;
const int MAG3110_ADDR = 0x0E;
// Do we have the new version with the different magnetometer?
bool microbitLSM303;
#endif
const int LSM303_ACC_ADDR = 0b0011001;
const int LSM303_MAG_ADDR = 0b0011110;
uint32_t microbitLEDState = 0;
uint8_t microbitRow = 0;
// called on a timer to scan rows out
void jswrap_microbit_display_callback() {
#ifdef MICROBIT2
microbitRow++;
if (microbitRow>5) microbitRow=0;
uint32_t s = (~microbitLEDState) >> microbitRow*5;
nrf_gpio_pin_clear(MB_LED_ROW1);
nrf_gpio_pin_clear(MB_LED_ROW2);
nrf_gpio_pin_clear(MB_LED_ROW3);
nrf_gpio_pin_clear(MB_LED_ROW4);
nrf_gpio_pin_clear(MB_LED_ROW5);
nrf_gpio_pin_write(MB_LED_COL1, s & 1);
nrf_gpio_pin_write(MB_LED_COL2, s & 2);
nrf_gpio_pin_write(MB_LED_COL3, s & 4);
nrf_gpio_pin_write(MB_LED_COL4, s & 8);
nrf_gpio_pin_write(MB_LED_COL5, s & 16);
nrf_gpio_pin_write(MB_LED_ROW1, microbitRow==0);
nrf_gpio_pin_write(MB_LED_ROW2, microbitRow==1);
nrf_gpio_pin_write(MB_LED_ROW3, microbitRow==2);
nrf_gpio_pin_write(MB_LED_ROW4, microbitRow==3);
nrf_gpio_pin_write(MB_LED_ROW5, microbitRow==4);
#else
microbitRow++;
if (microbitRow>2) microbitRow=0;
int n = microbitRow*9;
uint32_t s = ~microbitLEDState;
nrf_gpio_pin_clear(MB_LED_ROW1);
nrf_gpio_pin_clear(MB_LED_ROW2);
nrf_gpio_pin_clear(MB_LED_ROW3);
nrf_gpio_pin_write(MB_LED_COL1, s & (1 << MB_LED_MAPPING[n++]));
nrf_gpio_pin_write(MB_LED_COL2, s & (1 << MB_LED_MAPPING[n++]));
nrf_gpio_pin_write(MB_LED_COL3, s & (1 << MB_LED_MAPPING[n++]));
nrf_gpio_pin_write(MB_LED_COL4, s & (1 << MB_LED_MAPPING[n++]));
nrf_gpio_pin_write(MB_LED_COL5, s & (1 << MB_LED_MAPPING[n++]));
nrf_gpio_pin_write(MB_LED_COL6, s & (1 << MB_LED_MAPPING[n++]));
nrf_gpio_pin_write(MB_LED_COL7, s & (1 << MB_LED_MAPPING[n++]));
nrf_gpio_pin_write(MB_LED_COL8, s & (1 << MB_LED_MAPPING[n++]));
nrf_gpio_pin_write(MB_LED_COL9, s & (1 << MB_LED_MAPPING[n++]));
nrf_gpio_pin_write(MB_LED_ROW1, microbitRow==0);
nrf_gpio_pin_write(MB_LED_ROW2, microbitRow==1);
nrf_gpio_pin_write(MB_LED_ROW3, microbitRow==2);
#endif
}
void jswrap_microbit_stopDisplay() {
if (microbitLEDState) {
jstStopExecuteFn(jswrap_microbit_display_callback, 0);
microbitLEDState = 0;
nrf_gpio_cfg_default(MB_LED_COL1);
nrf_gpio_cfg_default(MB_LED_COL2);
nrf_gpio_cfg_default(MB_LED_COL3);
nrf_gpio_cfg_default(MB_LED_COL4);
nrf_gpio_cfg_default(MB_LED_COL5);
#ifdef MICROBIT2
nrf_gpio_cfg_default(MB_LED_ROW4);
nrf_gpio_cfg_default(MB_LED_ROW5);
#else
nrf_gpio_cfg_default(MB_LED_COL6);
nrf_gpio_cfg_default(MB_LED_COL7);
nrf_gpio_cfg_default(MB_LED_COL8);
nrf_gpio_cfg_default(MB_LED_COL9);
#endif
nrf_gpio_cfg_default(MB_LED_ROW1);
nrf_gpio_cfg_default(MB_LED_ROW2);
nrf_gpio_cfg_default(MB_LED_ROW3);
}
}
void mb_i2c_write(unsigned int addr, int count, const unsigned char *data) {
#ifdef MICROBIT2
jsi2cWrite(&i2cInfo, addr, count, data, true);
#else
jshI2CWrite(EV_I2C1, addr, count, data, true);
#endif
}
void mb_i2c_read(unsigned int addr, int count, unsigned char *data) {
#ifdef MICROBIT2
jsi2cRead(&i2cInfo, addr, count, data, true);
#else
jshI2CRead(EV_I2C1, addr, count, data, true);
#endif
}
/*JSON{
"type" : "init",
"generate" : "jswrap_microbit_init"
}*/
void jswrap_microbit_init() {
// enable I2C (for accelerometers, etc)
#ifndef MICROBIT2
JshI2CInfo i2cInfo;
#endif
jshI2CInitInfo(&i2cInfo);
#ifdef MICROBIT2
i2cInfo.bitrate = 0x7FFFFFFF; // make it as fast as we can go
i2cInfo.clockStretch = false;
#endif
i2cInfo.pinSCL = INTERNAL_I2C_SCL_PIN;
i2cInfo.pinSDA = INTERNAL_I2C_SDA_PIN;
jshI2CSetup(EV_I2C1, &i2cInfo);
unsigned char d[2];
#ifndef MICROBIT2
d[0] = 0x07; // WHO_AM_I
mb_i2c_write(MAG3110_ADDR, 1, d);
mb_i2c_read(MAG3110_ADDR, 1, d);
jsvUnLock(jspGetException());
if (d[0]==0xC4) {
microbitLSM303 = false;
// Enable MMA8652 Accelerometer
d[0] = 0x2A; d[1] = 0x19; // CTRL_REG1, 100Hz, turn on
mb_i2c_write(MMA8652_ADDR, 2, d);
// Enable MAG3110 magnetometer, 80Hz
d[0] = 0x11; d[1] = 0x80; // CTRL_REG2, AUTO_MRST_EN
mb_i2c_write(MAG3110_ADDR, 2, d);
d[0] = 0x10; d[1] = 0x01; // CTRL_REG1, active mode 80 Hz ODR with OSR = 1
mb_i2c_write(MAG3110_ADDR, 2, d);
#else
if (false) {
#endif
} else {
#ifndef MICROBIT2
microbitLSM303 = true;
#endif
// LSM303_ACC_ADDR,0x0F => 51 // WHO_AM_I
// Init accelerometer
d[0] = 0x20; d[1] = 0b00110111; // CTRL_REG1_A, 25Hz
mb_i2c_write(LSM303_ACC_ADDR, 2, d);
d[0] = 0x22; d[1] = 0x10; // CTRL_REG3_A
mb_i2c_write(LSM303_ACC_ADDR, 2, d);
d[0] = 0x23; d[1] = 0b11011000; // CTRL_REG4_A - 4g range, MSB at low address, high res
mb_i2c_write(LSM303_ACC_ADDR, 2, d);
// Init magnetometer
d[0] = 0x60; d[1] = 0x04; // CFG_REG_A_M, 20Hz
mb_i2c_write(LSM303_MAG_ADDR, 2, d);
d[0] = 0x62; d[1] = 0b00001001; // CFG_REG_C_M - enable data ready IRQ (not that we use this), swap block order to match MAG3110
mb_i2c_write(LSM303_MAG_ADDR, 2, d);
}
}
/*JSON{
"type" : "kill",
"generate" : "jswrap_microbit_kill"
}*/
void jswrap_microbit_kill() {
jswrap_microbit_stopDisplay();
}
/*JSON{
"type" : "function",
"name" : "show",
"generate" : "jswrap_microbit_show",
"params" : [
["image","JsVar","The image to show"]
],
"ifdef" : "MICROBIT"
}
**Note:** This function is only available on the [BBC micro:bit](/MicroBit) board
Show an image on the in-built 5x5 LED screen.
Image can be:
* A number where each bit represents a pixel (so 25 bits). eg. `5` or `0x1FFFFFF`
* A string, eg: `show("10001")`. Newlines are ignored, and anything that is not
a space or `0` is treated as a 1.
* An array of 4 bytes (more will be ignored), eg `show([1,2,3,0])`
For instance the following works for images:
```
show("# #"+
" # "+
" # "+
"# #"+
" ### ")
```
This means you can also use Espruino's graphics library:
```
var g = Graphics.createArrayBuffer(5,5,1)
g.drawString("E",0,0)
show(g.buffer)
```
*/
void jswrap_microbit_show_raw(uint32_t newState) {
if ((newState!=0) && (microbitLEDState==0)) {
// we want to display something but we don't have an interval
JsSysTime period = jshGetTimeFromMilliseconds(5);
jstExecuteFn(jswrap_microbit_display_callback, 0, jshGetSystemTime()+period, (uint32_t)period);
// and also set pins to outputs
nrf_gpio_cfg_output(MB_LED_COL1);
nrf_gpio_cfg_output(MB_LED_COL2);
nrf_gpio_cfg_output(MB_LED_COL3);
nrf_gpio_cfg_output(MB_LED_COL4);
nrf_gpio_cfg_output(MB_LED_COL5);
#ifdef MICROBIT2
nrf_gpio_cfg_output(MB_LED_ROW4);
nrf_gpio_cfg_output(MB_LED_ROW5);
#else
nrf_gpio_cfg_output(MB_LED_COL6);
nrf_gpio_cfg_output(MB_LED_COL7);
nrf_gpio_cfg_output(MB_LED_COL8);
nrf_gpio_cfg_output(MB_LED_COL9);
#endif
nrf_gpio_cfg_output(MB_LED_ROW1);
nrf_gpio_cfg_output(MB_LED_ROW2);
nrf_gpio_cfg_output(MB_LED_ROW3);
} else if ((newState==0) && (microbitLEDState!=0)) {
jswrap_microbit_stopDisplay();
}
microbitLEDState = newState;
}
void jswrap_microbit_show(JsVar *image) {
uint32_t newState = 0;
if (jsvIsIterable(image)) {
bool str = jsvIsString(image);
JsvIterator it;
jsvIteratorNew(&it, image, JSIF_EVERY_ARRAY_ELEMENT);
int n = 0;
while (jsvIteratorHasElement(&it)) {
int ch = jsvIteratorGetIntegerValue(&it);
if (str) {
if (ch!='\n' && ch!='\r') {
if (ch!=' ' && ch!='0')
newState |= 1<<n;
n++;
}
} else {
newState |= (unsigned int)ch << n;
n+=8;
}
jsvIteratorNext(&it);
}
jsvIteratorFree(&it);
} else if (jsvIsNumeric(image)) {
newState = jsvGetInteger(image);
} else {
jsError("Expecting a number, got %t\n", image);
return;
}
jswrap_microbit_show_raw(newState);
}
/*JSON{
"type" : "function",
"name" : "acceleration",
"generate" : "jswrap_microbit_acceleration",
"return" : ["JsVar", "An object with x, y, and z fields in it"],
"ifdef" : "MICROBIT"
}
**Note:** This function is only available on the [BBC micro:bit](/MicroBit) board
Get the current acceleration of the micro:bit from the on-board accelerometer
*/
JsVar *jswrap_microbit_acceleration() {
unsigned char d[7];
JsVarFloat range;
if (microbitLSM303) {
d[0] = 0x28 | 0x80;
mb_i2c_write(LSM303_ACC_ADDR, 1, d);
mb_i2c_read(LSM303_ACC_ADDR, 6, &d[1]);
range = 8192;
} else {
#ifndef MICROBIT2
d[0] = 1;
mb_i2c_write(MMA8652_ADDR, 1, d);
mb_i2c_read(MMA8652_ADDR, 7, d);
range = 16384;
#endif
}
JsVar *xyz = jsvNewObject();
if (xyz) {
int x = (d[1]<<8) | d[2];
if (x>>15) x-=65536;
int y = (d[3]<<8) | d[4];
if (y>>15) y-=65536;
int z = (d[5]<<8) | d[6];
if (z>>15) z-=65536;
// something is very broken here - why doesn't this work?
jsvObjectSetChildAndUnLock(xyz, "x", jsvNewFromFloat(((JsVarFloat)x) / range));
jsvObjectSetChildAndUnLock(xyz, "y", jsvNewFromFloat(((JsVarFloat)y) / range));
jsvObjectSetChildAndUnLock(xyz, "z", jsvNewFromFloat(((JsVarFloat)z) / range));
}
return xyz;
}
/*JSON{
"type" : "function",
"name" : "compass",
"generate" : "jswrap_microbit_compass",
"return" : ["JsVar", "An object with x, y, and z fields in it"],
"ifdef" : "MICROBIT"
}
**Note:** This function is only available on the [BBC micro:bit](/MicroBit) board
Get the current compass position for the micro:bit from the on-board magnetometer
*/
JsVar *jswrap_microbit_compass() {
unsigned char d[6];
if (microbitLSM303) {
d[0] = 0x68 | 0x80;
mb_i2c_write(LSM303_MAG_ADDR, 1, d);
mb_i2c_read(LSM303_MAG_ADDR, 6, d);
} else {
#ifndef MICROBIT2
d[0] = 1;
mb_i2c_write(MAG3110_ADDR, 1, d);
mb_i2c_read(MAG3110_ADDR, 6, d);
#endif
}
JsVar *xyz = jsvNewObject();
if (xyz) {
int x = (d[0]<<8) | d[1];
if (x>>15) x-=65536;
int y = (d[2]<<8) | d[3];
if (y>>15) y-=65536;
int z = (d[4]<<8) | d[5];
if (z>>15) z-=65536;
jsvObjectSetChildAndUnLock(xyz, "x", jsvNewFromInteger(x));
jsvObjectSetChildAndUnLock(xyz, "y", jsvNewFromInteger(y));
jsvObjectSetChildAndUnLock(xyz, "z", jsvNewFromInteger(z));
}
return xyz;
}
/*JSON{
"type" : "variable",
"name" : "SPEAKER",
"generate_full" : "SPEAKER_PIN",
"ifdef" : "MICROBIT2",
"return" : ["pin",""]
}
The micro:bit's speaker
*/
/*JSON{
"type" : "variable",
"name" : "MIC",
"generate_full" : "MIC_PIN",
"ifdef" : "MICROBIT2",
"return" : ["pin",""]
}
The micro:bit's microphone
`MIC_ENABLE` should be set to 1 before using this
*/
/*JSON{
"type" : "variable",
"name" : "MIC_ENABLE",
"generate_full" : "MIC_ENABLE_PIN",
"ifdef" : "MICROBIT2",
"return" : ["pin",""]
}
The micro:bit's microphone enable pin
*/
//------------------------ virtuial pins allow us to have a LED1
void jshVirtualPinInitialise() {
}
void jshVirtualPinSetValue(Pin pin, bool state) {
jswrap_microbit_show_raw(state ? 0x1FFFFFF : 0);
}
bool jshVirtualPinGetValue(Pin pin) {
return 0;
}
JsVarFloat jshVirtualPinGetAnalogValue(Pin pin) {
return NAN;
}
void jshVirtualPinSetState(Pin pin, JshPinState state) {
}
JshPinState jshVirtualPinGetState(Pin pin) {
return JSHPINSTATE_UNDEFINED;
}