diff --git a/README.md b/README.md index cb1de64..cac73c4 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ D-pad, see arrow buttons). Blocks for the shield are under the toolbox categories `Controller` and `Drawing` and are described further below. -![MakeCode with Arcade Shield Simulator](./assets/shieldSim.png) +![MakeCode with Arcade Shield Simulator](https://microbit-apps.github.io/pxt-arcadeshield/assets/shieldSim.png) ## Blocks @@ -101,9 +101,384 @@ Display present/absent ```blocks ``` -### Drawing with bitmaps +### Drawing into the screen bitmap -## TypeScript APIs +#### Screen coordinates and lines + +The screen bitmap is 160 pixels wide and 120 pixels high. +The upper left of the screen is coordinate (0,0); The lower +right of the screen is coordinate (159,119). We draw two lines +to the screen bitmap to show the four corners of the screen: + +```blocks +screen().drawLine(0, 0, 159, 119, 2) +screen().drawLine(159, 0, 0, 119, 5) +``` +The first two parameters to the function are the (x,y) +coordinate where the line should start, while the next +two parameters are the (x',y') coordinate where the line +should end. The final parameter is the color to draw. + +### Drawing out of bounds + +You don't need to worry (for any drawing command) about drawing off +the screen. So, for example, +```blocks +screen().drawLine(-10,-10,10,10,2) +``` +has the same effect as +```blocks +screen().drawLine(0,0,10,10,2) +``` +While the following code won't display anything on the screen at all: +```blocks +screen().drawLine(-1,-1,-10,-10,2) +``` + +#### Screen center, setting a pixel, and floating point + +Since the width and height of the screen are both even, the center of +the screen is bounded by these four pixels, as shown by the following +four commands that each draw a single pixel: +``` +screen().setPixel(79, 59, 1) +screen().setPixel(80, 59, 1) +screen().setPixel(79, 60, 1) +screen().setPixel(80, 60, 1) +``` + +You can pass floating point numbers to any drawing command that takes +a coordinate. For efficiency, the underlying representation is fixed point +in the MakeCode runtime. Fractional values are ignored when setting a pixel in a bitmap, so +```blocks +screen().setPixel(79.6, 59.6, 1) +``` +has the same effect as +```blocks +screen().setPixel(79.4, 59.4, 1) +``` +which has the same effect as +```blocks +screen().setPixel(79, 59, 1) +``` +#### Getting a pixel's (color index) value + +The following code will show a 2 on the micro:bit screen, as this is the color index +stored in the pixel: +```blocks +screen().setPixel(80, 60, 2) +basic.showNumber(screen().getPixel(80, 60)) +``` +So we can see that each pixel is like a variable that stores a value (in the range 0-15) +that can later be retrieved. + +#### Drawing shapes + +You can draw a recentangle by first specifying the upper left +corner with the first two parameters to the `drawRect` +function, followed by the width and the height of the +rectangle, and ending with the draw color: + +```blocks +screen().drawRect(0, 0, 10, 10, 1) +screen().drawRect(10, 10, 20, 20, 2) +screen().drawRect(0, 10, 10, 20, 3) +screen().drawRect(10, 0, 20, 10, 4) +``` + +You can have the rectangle filled with the specified color instead: +```blocks +screen().fillRect(0, 0, 10, 10, 1) +screen().fillRect(10, 10, 20, 20, 2) +screen().fillRect(0, 10, 10, 20, 3) +screen().fillRect(10, 0, 20, 10, 4) +``` + +To draw a circle, first specify the coordinate +of the center of the circle, followed by the radius +of the circle and the draw color. Again, you can choose +to fill the circle or just draw its outline: +```blocks +screen().fillCircle(10, 10, 10, 2) +screen().drawCircle(10, 10, 10, 5) +``` +### Bitmap + +Let's dig into bitmaps, which you can create yourself (the screen is represented by a bitmap, as we have seen already). + +A bitmap is some number of rows and columns of color pixels that make up rectangular picture. A _pixel_ is a single point of color inside the bitmap. + +Bitmaps are two-dimensional so they have a known height and width. When a bitmap is declared, or created, the height and width are specified either by the _layout_ of the bitmap or as parameters to it's `create` method. + +#### Bitmap layout + +You _declare_ a bitmap by creating a layout. This is done in JavaScript with the ``bmp'...'`` string declaration. The pixels are single characters inside the string. + +##### Zero size bitmap + +An zero size bitmap has no height or width and has no pixels, so the **bmp** string is just ``bmp''``. + +```typescript +let emptyBitmap = bmp`` +``` + +You can also use the `create` function and make another zero size bitmap with no pixels. + +```blocks +let emptyBitmap1 = bmp`` +let emptyBitmap2 = bitmaps.create(0, 0) +``` + +A zero size bitmap isn't really useful so MakeCode actually makes it have some size if you declare it without any. + +#### Bitmaps with size + +To make a bitmap with some size, just set the pixel characters in the rows of the **bmp** string. A bitmap that is 1 pixel high by 1 pixel wide (1 x 1) is: + +```typescript +let oneByOne = bmp`.` +``` + +A bitmap that is 2 x 2 is declared like this: + +```typescript +let twoBytwo = bmp` +. . +. . +` +``` + +Here they are in blocks: + +```blocks +let oneByOne = bmp`.` +let twoBytwo = bmp` +. . +. . +` +``` + +You'll notice that they look the same. That's because the pixel colors are not set so the bitmaps are empty. + +Bitmaps don't have to be exactly square. The height and width can be different. Here's a 6 x 2 bitmap: + +```typescript +let sixByTwo = bmp` +. . . . . . +. . . . . . +` +``` + +#### Setting pixels + +##### Transparent pixels + +A pixel value of `.` means an empty pixel. This pixel has no color and that pixel _location_ in the bitmap is _transparent_. Being transparent means that if this bitmap is on top of another bitmap (overlapping) that has some pixel color, then the color of the pixel in the bitmap underneath shows through to the bitmap above it. + +##### Pixel colors + +Besides the empty, or transparent pixel `.`, there are 16 color pixels you can use. These are matched to colors in a _palette_. A palette is a group of colors you can choose from. The colors are selected by using a single number or letter to match them. The default palette, for example, uses these colors: + +* `.`: empty or transparent +* `0`: transparent +* `1`: white +* `2`: red +* `3`: pink +* `4`: orange +* `5`: yellow +* `6`: blue-green +* `7`: green +* `8`: dark blue +* `9`: light blue +* `a`: purple +* `b`: dark grey +* `c`: dark purple +* `d`: beige +* `e`: brown +* `f`: black + +A 1 x 1 bitmap with a red pixel is declared as: + +```typescript +let oneRed = bmp`2` +``` + +As a block it looks like this: + +```block +let oneRed = bmp`2` +``` + +We can make 4 x 4 bitmap that uses all of the colors: + +```typescript +let allColors = bmp` +0 1 2 3 +4 5 6 7 +8 9 a b +c d e f +` +``` + +This the same bitmap as a block: + +```block +let allColors = bmp` +0 1 2 3 +4 5 6 7 +8 9 a b +c d e f +` +``` + +#### Transparency and overlap + +Let's see how transparency works with bitmaps. A `.` means that a pixel is transparent. Only the pixels with a color will show in a bitmap and any pixels with color in a bitmap below it will show through. So, to demonstrate, let's make two bitmaps that are the same size and put one that has some transparent pixels on top of one that doesn't. + +Our first bitmap is a green circle inside a 8 x 8 rectangle. All of the pixels around the circle are transparent. + +```typescript +let greenBall = bmp` +. . . . . . . . +. . . 6 6 . . . +. . 6 6 6 6 . . +. 6 6 6 6 6 6 . +. 6 6 6 6 6 6 . +. . 6 6 6 6 . . +. . . 6 6 . . . +. . . . . . . . +` +``` + +The other bitmap is the same size but with all yellow pixels. + +```blocks +let greenBall = bmp` +. . . . . . . . +. . . 6 6 . . . +. . 6 6 6 6 . . +. 6 6 6 6 6 6 . +. 6 6 6 6 6 6 . +. . 6 6 6 6 . . +. . . 6 6 . . . +. . . . . . . . +` + +let yellowSquare = bmp` +e e e e e e e e +e e e e e e e e +e e e e e e e e +e e e e e e e e +e e e e e e e e +e e e e e e e e +e e e e e e e e +e e e e e e e e +e e e e e e e e +` +``` + +Putting the green circle bitmap exactly over the yellow square, you see that the yellow from the bitmap below isn't blocked out by the transparent pixels from the bitmap on top. + +```sim +let greenBall = bmp` +. . . . . . . . . . . . . . . . +. . . . . . . . . . . . . . . . +. . . . . . 6 6 6 6 . . . . . . +. . . . 6 6 6 6 6 6 6 6 . . . . +. . . 6 6 6 6 6 6 6 6 6 6 . . . +. . . 6 6 6 6 6 6 6 6 6 6 . . . +. . 6 6 6 6 6 6 6 6 6 6 6 6 . . +. . 6 6 6 6 6 6 6 6 6 6 6 6 . . +. . 6 6 6 6 6 6 6 6 6 6 6 6 . . +. . 6 6 6 6 6 6 6 6 6 6 6 6 . . +. . . 6 6 6 6 6 6 6 6 6 6 . . . +. . . 6 6 6 6 6 6 6 6 6 6 . . . +. . . . 6 6 6 6 6 6 6 6 . . . . +. . . . . . 6 6 6 6 . . . . . . +. . . . . . . . . . . . . . . . +. . . . . . . . . . . . . . . . +` +let yellowSquare = bitmaps.create(16, 16) +yellowSquare.fill(5) + +screen().drawBitmap(yellowSquare, 0, 0) +screeb().drawTransparentBitmap(greenBall, 0, 0) +``` + +#### Setting pixels at locations + +You can create your bitmaps while your program is running too (dynamically). To make a bitmap this way, you set the color of a pixel at its location with code. Pixels are addressed by their row (``x`` value) and column (``y`` value) inside the bitmap. You could create an empty bitmap and make some or all of the bitmap by setting pixel colors in your code. Let's make a 32 x 32 box by creating an empty bitmap and then draw an orange border in it. + +```blocks +let orangeBox = bitmaps.create(32, 32) +for (let i = 0; i <= 31; i++) { + orangeBox.setPixel(0, i, 4) + orangeBox.setPixel(i, 0, 4) + orangeBox.setPixel(i, 31, 4) + orangeBox.setPixel(31, i, 4) +} +screen().drawTransparentBitmap(orangeBox, 0, 0) +``` + +#### Creating your own bitmaps + +There are two ways you can create your own bitmap. + +##### Bitmap editor and hex literal + +The first way to create a bitmap is with the bitmap editor, +which represents the bitmap in code as a hex literal that +must be assigned to a program variable. The bitmap won't appear +on the screen unless it is draw using the function below: +``` +```block +let bitmap = bmp` + . . . . . . . . . . . . . . . . + . 2 2 2 2 2 2 2 2 2 2 2 2 2 2 . + . 2 . . . . . . . . . . . . 2 . + . 2 . 2 2 2 2 2 2 2 2 2 2 . 2 . + . 2 . 2 . . . . . . . . 2 . 2 . + . 2 . 2 . . . . . . . . 2 . 2 . + . 2 . 2 . . 5 5 5 5 . . 2 . 2 . + . 2 . 2 . . 5 5 5 5 . . 2 . 2 . + . 2 . 2 . . 5 5 5 5 . . 2 . 2 . + . 2 . 2 . . 5 5 5 5 . . 2 . 2 . + . 2 . 2 . . . . . . . . 2 . 2 . + . 2 . 2 . . . . . . . . 2 . 2 . + . 2 . 2 2 2 2 2 2 2 2 2 2 . 2 . + . 2 . . . . . . . . . . . . 2 . + . 2 2 2 2 2 2 2 2 2 2 2 2 2 2 . + . . . . . . . . . . . . . . . . + ` + screen().fill(5) + screen().drawBitmap(0,0,bitmap) +``` +The `.` in string representation of the bitmap represents the +color value 0, which is interpreted as `transparent` by the following +function: +```blocks + screen().fill(5) + screen().drawTransparentBitmap(0,0,bitmap) +``` + +##### Bitmap create function + +The other way to create a bitmap is with the `create` function, as shown above. + +#### Drawing to a bitmap + +All the functions we previously reviewed for drawing to the +screen can also be applied to a bitmap. For example, the orange border in a bitmap can be created as followsL + +```blocks +let orangeBox = bitmaps.create(32, 32) +orangeBox.drawLine(0, 0, 31, 0, 4) +orangeBox.drawLine(0, 0, 0, 31, 4) +orangeBox.drawLine(0, 31, 31, 31, 4) +orangeBox.drawLine(31, 0, 31, 31, 4) +screen().drawTransparentBitmap(orangeBox, 0, 0) +screen().drawTransparentBitmap(orangeBox, 32, 32) +``` # Supported targets diff --git a/cpp/arcadegamepad.h b/cpp/arcadegamepad.h new file mode 100644 index 0000000..bcfe457 --- /dev/null +++ b/cpp/arcadegamepad.h @@ -0,0 +1,49 @@ +// Autogenerated C header file for Arcade Gamepad +#ifndef _JACDAC_SPEC_ARCADE_GAMEPAD_H +#define _JACDAC_SPEC_ARCADE_GAMEPAD_H 1 + +#define JD_SERVICE_CLASS_ARCADE_GAMEPAD 0x1deaa06e + +// enum Button (uint8_t) +#define JD_ARCADE_GAMEPAD_BUTTON_LEFT 0x1 +#define JD_ARCADE_GAMEPAD_BUTTON_UP 0x2 +#define JD_ARCADE_GAMEPAD_BUTTON_RIGHT 0x3 +#define JD_ARCADE_GAMEPAD_BUTTON_DOWN 0x4 +#define JD_ARCADE_GAMEPAD_BUTTON_A 0x5 +#define JD_ARCADE_GAMEPAD_BUTTON_B 0x6 +#define JD_ARCADE_GAMEPAD_BUTTON_MENU 0x7 +#define JD_ARCADE_GAMEPAD_BUTTON_SELECT 0x8 +#define JD_ARCADE_GAMEPAD_BUTTON_RESET 0x9 +#define JD_ARCADE_GAMEPAD_BUTTON_EXIT 0xa + +/** + * Indicates which buttons are currently active (pressed). + * `pressure` should be `0xff` for digital buttons, and proportional for analog ones. + */ +#define JD_ARCADE_GAMEPAD_REG_BUTTONS JD_REG_READING +typedef struct jd_arcade_gamepad_buttons { + uint8_t button; // Button + uint8_t pressure; // ratio u0.8 +} jd_arcade_gamepad_buttons_t; + + +/** + * Constant. Indicates number of players supported and which buttons are present on the controller. + */ +#define JD_ARCADE_GAMEPAD_REG_AVAILABLE_BUTTONS 0x180 +typedef struct jd_arcade_gamepad_available_buttons { + uint8_t button[0]; // Button +} jd_arcade_gamepad_available_buttons_t; + + +/** + * Argument: button Button (uint8_t). Emitted when button goes from inactive to active. + */ +#define JD_ARCADE_GAMEPAD_EV_DOWN JD_EV_ACTIVE + +/** + * Argument: button Button (uint8_t). Emitted when button goes from active to inactive. + */ +#define JD_ARCADE_GAMEPAD_EV_UP JD_EV_INACTIVE + +#endif diff --git a/cpp/arcadesound.h b/cpp/arcadesound.h new file mode 100644 index 0000000..7b47d36 --- /dev/null +++ b/cpp/arcadesound.h @@ -0,0 +1,31 @@ +// Autogenerated C header file for Arcade sound +#ifndef _JACDAC_SPEC_ARCADE_SOUND_H +#define _JACDAC_SPEC_ARCADE_SOUND_H 1 + +#define JD_SERVICE_CLASS_ARCADE_SOUND 0x1fc63606 + +/** + * Argument: samples bytes. Play samples, which are single channel, signed 16-bit little endian values. + */ +#define JD_ARCADE_SOUND_CMD_PLAY 0x80 + +/** + * Read-write Hz u22.10 (uint32_t). Get or set playback sample rate (in samples per second). + * If you set it, read it back, as the value may be rounded up or down. + */ +#define JD_ARCADE_SOUND_REG_SAMPLE_RATE 0x80 + +/** + * Constant B uint32_t. The size of the internal audio buffer. + */ +#define JD_ARCADE_SOUND_REG_BUFFER_SIZE 0x180 + +/** + * Read-only B uint32_t. How much data is still left in the buffer to play. + * Clients should not send more data than `buffer_size - buffer_pending`, + * but can keep the `buffer_pending` as low as they want to ensure low latency + * of audio playback. + */ +#define JD_ARCADE_SOUND_REG_BUFFER_PENDING 0x181 + +#endif diff --git a/cpp/config_nrf.h b/cpp/config_nrf.h new file mode 100644 index 0000000..8c8a531 --- /dev/null +++ b/cpp/config_nrf.h @@ -0,0 +1,69 @@ +// here we hard code the dependence on micro:bit V2 +// and Arcade Shield, to avoid potential conflicts with pxt-microbit + +#include "NRF52Pin.h" +#include "NRF52SPI.h" + +#define CODAL_PIN NRF52Pin +#define CODAL_SPI NRF52SPI + +#define MY_DISPLAY_TYPE 4242 // smart shield +#define MY_DISPLAY_CFG0 0x02000080 // allow execution without shield plugged in +#define MY_DISPLAY_CFG1 0x00000603 +#define MY_DISPLAY_CFG2 8 // maximum SPI frequency for smart shield + +#define MY_PIN_BTNMX_LATCH &uBit.io.P9 // DAL.P0_9 +#define MY_PIN_BTNMX_CLOCK &uBit.io.P20 // DAL.P1_0 +#define MY_PIN_BTNMX_DATA &uBit.io.P14 // DAL.P0_1 + +#define MY_PIN_DISPLAY_SCK &uBit.io.P13 // DAL.P0_17 +#define MY_PIN_DISPLAY_MOSI &uBit.io.P15 // DAL.P0_13 +#define MY_PIN_DISPLAY_MISO &uBit.io.P14 // DAL.P0_1 +#define MY_PIN_DISPLAY_BL &uBit.io.P19 // DAL.P0_26 +#define MY_PIN_DISPLAY_DC &uBit.io.P8 // DAL.P0_10 +#define MY_PIN_DISPLAY_RST &uBit.io.P16 // DAL.P1_2 +#define MY_PIN_DISPLAY_CS ((CODAL_PIN*)NULL) // not connected +#define MY_PIN_LED ((CODAL_PIN*)NULL) // not connected + +#undef DEV_NUM_PINS +#define DEV_NUM_PINS 48 +#define DEVICE_ID_IO_P0 100 + +#define DEV_PWM_PINS 0x0000ffffffffULL // all pins are PWM pins it seems +#define DEV_AIN_PINS 0x0000f000001fULL + +// Codal doesn't yet distinguish between PWM and AIN +#define DEV_ANALOG_PINS (DEV_PWM_PINS | DEV_AIN_PINS) + +#ifndef IS_ANALOG_PIN +#define IS_ANALOG_PIN(id) ((DEV_ANALOG_PINS >> (id)) & 1) +#endif + +typedef CODAL_PIN DevicePin; + +typedef DevicePin *DigitalInOutPin; +typedef DevicePin *AnalogInOutPin; +typedef DevicePin *AnalogInPin; +typedef DevicePin *AnalogOutPin; +typedef DevicePin *PwmPin; +typedef DevicePin *PwmOnlyPin; + +// remove the indirection through configuration +#undef PIN +#undef LOOKUP_PIN +#define PIN(name) MY_PIN_##name +#define LOOKUP_PIN(name) PIN(name) // pxt::myLookupPin(PIN(name)) + +#define PXT_INTERNAL_KEY_UP 2050 +#define PXT_INTERNAL_KEY_DOWN 2051 +#define DEVICE_ID_FIRST_BUTTON 4000 +#define BUTTON_ACTIVE_LOW_PULL_UP 32 + +namespace pxt { + uint32_t readButtonMultiplexer(int bits); + void disableButtonMultiplexer(); + DevicePin *myLookupPin(int pinName); + CodalComponent *lookupComponent(int id); + int pressureLevelByButtonId(int btnId, int codalId); +} + diff --git a/cpp/controllerbuttons.cpp b/cpp/controllerbuttons.cpp new file mode 100644 index 0000000..bea8b4e --- /dev/null +++ b/cpp/controllerbuttons.cpp @@ -0,0 +1,168 @@ +#include "pxt.h" + +#undef Button + +#include "Pin.h" +#define PinCompat codal::Pin + +#include "config_nrf.h" + +namespace pxt { + + +class PressureButton : public codal::Button { + public: + PressureButton(Pin &pin, uint16_t id, + ButtonEventConfiguration eventConfiguration = DEVICE_BUTTON_ALL_EVENTS, + ButtonPolarity polarity = ACTIVE_LOW, PullMode mode = PullMode::None) + : Button(pin, id, eventConfiguration, polarity, mode) {} + + virtual int pressureLevel() { return isPressed() ? 512 : 0; } +}; + +struct AnalogCache { + AnalogCache *next; + Pin *pin; + uint32_t lastMeasureMS; + uint16_t lastMeasure; + AnalogCache(Pin *pin) : pin(pin) { + next = NULL; + lastMeasureMS = 0; + lastMeasure = pin->getAnalogValue(); + } + uint16_t read(); +}; + +uint16_t AnalogCache::read() { + uint32_t now = current_time_ms(); + if (now - lastMeasureMS < 50) + return lastMeasure; + lastMeasureMS = now; + lastMeasure = pin->getAnalogValue(); + return lastMeasure; +} + +static AnalogCache *analogCache; + +class AnalogButton : public PressureButton { + public: + AnalogCache *cache; + int16_t threshold; + bool state; + + AnalogButton(AnalogCache *cache, uint16_t id, int threshold) + : PressureButton(*cache->pin, id), cache(cache), threshold(threshold), state(false) {} + + protected: + virtual int pressureLevel() override { + int v = cache->read() - 512; + if (threshold < 0) + v = -v; + int vmin = getConfig(CFG_ANALOG_JOYSTICK_MIN, 50); + int vmax = getConfig(CFG_ANALOG_JOYSTICK_MAX, 500); + v = (v - vmin) * 512 / (vmax - vmin); + if (v < 0) + v = 0; + if (v > 512) + v = 512; + return v; + } + + virtual int buttonActive() override { + int v = cache->read() - 512; + int thr = threshold; + + if (thr < 0) { + v = -v; + thr = -thr; + } + + if (v > thr) + state = true; + else if (state && v > thr * 3 / 4) + state = true; + else + state = false; + + return state; + } +}; + +AnalogCache *lookupAnalogCache(Pin *pin) { + for (auto c = analogCache; c; c = c->next) + if (c->pin == pin) + return c; + auto c = new AnalogCache(pin); + c->next = analogCache; + analogCache = c; + return c; +} + +int multiplexedButtonIsPressed(int btnId); +int registerMultiplexedButton(int pin, int buttonId); + +//% expose +int pressureLevelByButtonId(int btnId, int codalId) { + if (codalId <= 0) + codalId = DEVICE_ID_FIRST_BUTTON + btnId; + auto btn = (PressureButton *)lookupComponent(codalId); + if (!btn) { + return multiplexedButtonIsPressed(btnId) ? 512 : 0; + } + return btn->pressureLevel(); +} + +static void sendBtnDown(Event ev) { + Event(PXT_INTERNAL_KEY_DOWN, ev.source - DEVICE_ID_FIRST_BUTTON); +} + +static void sendBtnUp(Event ev) { + Event(PXT_INTERNAL_KEY_UP, ev.source - DEVICE_ID_FIRST_BUTTON); +} + +//% expose +void setupButton(int buttonId, int key) { + int pin = getConfig(key); + if (pin == -1) + return; + + unsigned highflags = (unsigned)pin >> 16; + int flags = BUTTON_ACTIVE_LOW_PULL_UP; + if (highflags & 0xff) + flags = highflags & 0xff; + + pin &= 0xffff; + + auto cpid = DEVICE_ID_FIRST_BUTTON + buttonId; + auto btn = (PressureButton *)lookupComponent(cpid); + if (btn == NULL) { + if (registerMultiplexedButton(pin, buttonId)) + return; + + if (1100 <= pin && pin < 1300) { + pin -= 1100; + int thr = getConfig(CFG_ANALOG_BUTTON_THRESHOLD, 300); + if (pin >= 100) { + thr = -thr; + pin -= 100; + } + btn = new AnalogButton(lookupAnalogCache(myLookupPin(pin)), cpid, thr); + } else { + auto pull = PullMode::None; + if ((flags & 0xf0) == 0x10) + pull = PullMode::Down; + else if ((flags & 0xf0) == 0x20) + pull = PullMode::Up; + else if ((flags & 0xf0) == 0x30) + pull = PullMode::None; + else + oops(3); + btn = new PressureButton(*myLookupPin(pin), cpid, DEVICE_BUTTON_ALL_EVENTS, + (ButtonPolarity)(flags & 0xf), pull); + } + EventModel::defaultEventBus->listen(btn->id, DEVICE_BUTTON_EVT_DOWN, sendBtnDown); + EventModel::defaultEventBus->listen(btn->id, DEVICE_BUTTON_EVT_UP, sendBtnUp); + } +} + +} // namespace pxt diff --git a/cpp/indexedscreen.h b/cpp/indexedscreen.h new file mode 100644 index 0000000..1cc0005 --- /dev/null +++ b/cpp/indexedscreen.h @@ -0,0 +1,85 @@ +// Autogenerated C header file for Indexed screen +#ifndef _JACDAC_SPEC_INDEXED_SCREEN_H +#define _JACDAC_SPEC_INDEXED_SCREEN_H 1 + +#define JD_SERVICE_CLASS_INDEXED_SCREEN 0x16fa36e5 + +/** + * Sets the update window for subsequent `set_pixels` commands. + */ +#define JD_INDEXED_SCREEN_CMD_START_UPDATE 0x81 +typedef struct jd_indexed_screen_start_update { + uint16_t x; // px + uint16_t y; // px + uint16_t width; // px + uint16_t height; // px +} jd_indexed_screen_start_update_t; + + +/** + * Argument: pixels bytes. Set pixels in current window, according to current palette. + * Each "line" of data is aligned to a byte. + */ +#define JD_INDEXED_SCREEN_CMD_SET_PIXELS 0x83 + +/** + * Read-write ratio u0.8 (uint8_t). Set backlight brightness. + * If set to `0` the display may go to sleep. + */ +#define JD_INDEXED_SCREEN_REG_BRIGHTNESS JD_REG_INTENSITY + +/** + * The current palette. + * The color entry repeats `1 << bits_per_pixel` times. + * This register may be write-only. + */ +#define JD_INDEXED_SCREEN_REG_PALETTE 0x80 +typedef struct jd_indexed_screen_palette { + uint8_t blue; + uint8_t green; + uint8_t red; + uint8_t padding; +} jd_indexed_screen_palette_t; + + +/** + * Constant bit uint8_t. Determines the number of palette entries. + * Typical values are 1, 2, 4, or 8. + */ +#define JD_INDEXED_SCREEN_REG_BITS_PER_PIXEL 0x180 + +/** + * Constant px uint16_t. Screen width in "natural" orientation. + */ +#define JD_INDEXED_SCREEN_REG_WIDTH 0x181 + +/** + * Constant px uint16_t. Screen height in "natural" orientation. + */ +#define JD_INDEXED_SCREEN_REG_HEIGHT 0x182 + +/** + * Read-write bool (uint8_t). If true, consecutive pixels in the "width" direction are sent next to each other (this is typical for graphics cards). + * If false, consecutive pixels in the "height" direction are sent next to each other. + * For embedded screen controllers, this is typically true iff `width < height` + * (in other words, it's only true for portrait orientation screens). + * Some controllers may allow the user to change this (though the refresh order may not be optimal then). + * This is independent of the `rotation` register. + */ +#define JD_INDEXED_SCREEN_REG_WIDTH_MAJOR 0x81 + +/** + * Read-write px uint8_t. Every pixel sent over wire is represented by `up_sampling x up_sampling` square of physical pixels. + * Some displays may allow changing this (which will also result in changes to `width` and `height`). + * Typical values are 1 and 2. + */ +#define JD_INDEXED_SCREEN_REG_UP_SAMPLING 0x82 + +/** + * Read-write ° uint16_t. Possible values are 0, 90, 180 and 270 only. + * Write to this register do not affect `width` and `height` registers, + * and may be ignored by some screens. + */ +#define JD_INDEXED_SCREEN_REG_ROTATION 0x83 + +#endif diff --git a/cpp/jddisplay.cpp b/cpp/jddisplay.cpp new file mode 100644 index 0000000..4d444bb --- /dev/null +++ b/cpp/jddisplay.cpp @@ -0,0 +1,359 @@ +#include "pxt.h" + +#include "jddisplay.h" + +#include "config_nrf.h" + +#define VLOG NOLOG +//#define VLOG DMESG + +namespace pxt { + +codal::CodalDevice device; + +#define ALIGN(x) (((x) + 3) & ~3) + +static void jd_panic(void) { + target_panic(121); // PANIC_SCREEN_ERROR +} + +static int jd_shift_frame(jd_frame_t *frame) { + int psize = frame->size; + jd_packet_t *pkt = (jd_packet_t *)frame; + int oldsz = pkt->service_size + 4; + if (ALIGN(oldsz) >= psize) + return 0; // nothing to shift + + int ptr; + if (frame->data[oldsz] == 0xff) { + ptr = frame->data[oldsz + 1]; + if (ptr >= psize) + return 0; // End-of-frame + if (ptr <= oldsz) { + DMESG("invalid super-frame %d %d", ptr, oldsz); + return 0; // don't let it go back, must be some corruption + } + } else { + ptr = ALIGN(oldsz); + } + + // assume the first one got the ACK sorted + frame->flags &= ~JD_FRAME_FLAG_ACK_REQUESTED; + + uint8_t *src = &frame->data[ptr]; + int newsz = *src + 4; + if (ptr + newsz > psize) { + DMESG("invalid super-frame %d %d %d", ptr, newsz, psize); + return 0; + } + uint32_t *dst = (uint32_t *)frame->data; + uint32_t *srcw = (uint32_t *)src; + // don't trust memmove() + for (int i = 0; i < newsz; i += 4) + *dst++ = *srcw++; + // store ptr + ptr += ALIGN(newsz); + frame->data[newsz] = 0xff; + frame->data[newsz + 1] = ptr; + + return 1; +} + +static void *jd_push_in_frame(jd_frame_t *frame, unsigned service_num, unsigned service_cmd, + unsigned service_size) { + if (service_num >> 8) + jd_panic(); + if (service_cmd >> 16) + jd_panic(); + uint8_t *dst = frame->data + frame->size; + unsigned szLeft = (uint8_t *)frame + sizeof(*frame) - dst; + if (service_size + 4 > szLeft) + return NULL; + *dst++ = service_size; + *dst++ = service_num; + *dst++ = service_cmd & 0xff; + *dst++ = service_cmd >> 8; + frame->size += ALIGN(service_size + 4); + return dst; +} + +JDDisplay::JDDisplay(SPI *spi, Pin *cs, Pin *flow) : spi(spi), cs(cs), flow(flow) { + inProgress = false; + stepWaiting = false; + displayServiceNum = 0; + controlsStartServiceNum = 0; + controlsEndServiceNum = 0; + soundServiceNum = 0; + buttonState = 0; + brightness = 100; + soundBufferPending = 0; + soundSampleRate = 44100; + avgFrameTime = 26300; // start with a reasonable default + lastFrameTimestamp = 0; + + EventModel::defaultEventBus->listen(DEVICE_ID_DISPLAY, 4243, this, &JDDisplay::sendDone); + + flow->getDigitalValue(PullMode::Down); + EventModel::defaultEventBus->listen(flow->id, DEVICE_PIN_EVENT_ON_EDGE, this, + &JDDisplay::onFlowHi, MESSAGE_BUS_LISTENER_IMMEDIATE); + flow->eventOn(DEVICE_PIN_EVT_RISE); +} + +void JDDisplay::waitForSendDone() { + if (inProgress) + fiber_wait_for_event(DEVICE_ID_DISPLAY, 4242); +} + +void JDDisplay::sendDone(Event) { + inProgress = false; + Event(DEVICE_ID_DISPLAY, 4242); +} + +void *JDDisplay::queuePkt(uint32_t service_num, uint32_t service_cmd, uint32_t size) { + void *res = jd_push_in_frame(&sendFrame, service_num, service_cmd, size); + if (res == NULL) + target_panic(122); // PANIC_SCREEN_ERROR + return res; +} + +void JDDisplay::flushSend() { + if (cs) + cs->setDigitalValue(0); + spi->startTransfer((uint8_t *)&sendFrame, sizeof(sendFrame), (uint8_t *)&recvFrame, + sizeof(recvFrame), &JDDisplay::stepStatic, this); +} + +void JDDisplay::stepStatic(void *p) { + ((JDDisplay *)p)->step(); +} + +// We assume EIC IRQ pre-empts SPI/DMA IRQ (that is the numerical priority value of EIC is lower) +// This is true for codal STM32, SAMD, and NRF52 +void JDDisplay::onFlowHi(Event) { + if (stepWaiting) + step(); +} + +void JDDisplay::handleIncoming(jd_packet_t *pkt) { + if (pkt->service_number == JD_SERVICE_NUMBER_CTRL && + pkt->service_command == JD_CMD_ADVERTISEMENT_DATA) { + uint32_t *servptr = (uint32_t *)pkt->data; + int numServ = pkt->service_size >> 2; + for (uint8_t servIdx = 1; servIdx < numServ; ++servIdx) { + uint32_t service_class = servptr[servIdx]; + if (service_class == JD_SERVICE_CLASS_INDEXED_SCREEN) { + displayServiceNum = servIdx; + VLOG("JDA: found screen, serv=%d", servIdx); + } else if (service_class == JD_SERVICE_CLASS_ARCADE_GAMEPAD) { + if (!controlsStartServiceNum) + controlsStartServiceNum = servIdx; + controlsEndServiceNum = servIdx; + VLOG("JDA: found controls, serv=%d", servIdx); + } else if (service_class == JD_SERVICE_CLASS_ARCADE_SOUND) { + soundServiceNum = servIdx; + VLOG("JDA: found sound, serv=%d", servIdx); + } else { + VLOG("JDA: unknown service: %x", service_class); + } + } + } else if (pkt->service_number == JD_SERVICE_NUMBER_CTRL && + pkt->service_command == JD_CMD_CTRL_NOOP) { + // do nothing + } else if (pkt->service_number == soundServiceNum) { + switch (pkt->service_command) { + case JD_GET(JD_ARCADE_SOUND_REG_BUFFER_PENDING): + soundBufferPending = *(uint32_t *)pkt->data; + break; + case JD_GET(JD_ARCADE_SOUND_REG_SAMPLE_RATE): + soundSampleRate = *(uint32_t *)pkt->data >> 10; + break; + } + } else if (pkt->service_number == displayServiceNum) { + switch (pkt->service_command) { + case JD_GET(JD_INDEXED_SCREEN_REG_HEIGHT): + screenHeight = *(uint16_t *)pkt->data; + break; + case JD_GET(JD_INDEXED_SCREEN_REG_WIDTH): + screenWidth = *(uint16_t *)pkt->data; + break; + } + } else if (controlsStartServiceNum <= pkt->service_number && + pkt->service_number <= controlsEndServiceNum && + pkt->service_command == (JD_CMD_GET_REG | JD_REG_READING)) { + auto report = (jd_arcade_gamepad_buttons_t *)pkt->data; + auto endp = pkt->data + pkt->service_size; + uint32_t state = 0; + + while ((uint8_t *)report < endp) { + int idx = 0; + int b = report->button; + + if (report->pressure < 0x20) + continue; + + if (b == JD_ARCADE_GAMEPAD_BUTTON_SELECT) + b = JD_ARCADE_GAMEPAD_BUTTON_MENU; + + if (b == JD_ARCADE_GAMEPAD_BUTTON_RESET || b == JD_ARCADE_GAMEPAD_BUTTON_EXIT) + target_reset(); + + if (1 <= b && b <= 7) { + idx = b + 7 * (pkt->service_number - controlsStartServiceNum); + } + + if (idx > 0) + state |= 1 << idx; + + report++; + } + + if (state != buttonState) { + for (int i = 0; i < 32; ++i) { + if ((state & (1 << i)) && !(buttonState & (1 << i))) + Event(PXT_INTERNAL_KEY_DOWN, i); + if (!(state & (1 << i)) && (buttonState & (1 << i))) + Event(PXT_INTERNAL_KEY_UP, i); + } + buttonState = state; + } + } else { + // TODO remove later + VLOG("JDA: unknown packet for %d (cmd=%x)", pkt->service_number, pkt->service_command); + } +} + +void JDDisplay::step() { + if (cs) + cs->setDigitalValue(1); + + target_disable_irq(); + if (!flow->getDigitalValue()) { + stepWaiting = true; + target_enable_irq(); + return; + } else { + stepWaiting = false; + } + target_enable_irq(); + + memset(&sendFrame, 0, JD_SERIAL_FULL_HEADER_SIZE); + sendFrame.crc = JDSPI_MAGIC; + sendFrame.device_identifier = device.getSerialNumber(); + + if (recvFrame.crc == JDSPI_MAGIC_NOOP) { + // empty frame, skip + } else if (recvFrame.crc != JDSPI_MAGIC) { + DMESG("JDA: magic mismatch %x", (int)recvFrame.crc); + } else if (recvFrame.size == 0) { + // empty frame, skip + } else { + for (;;) { + handleIncoming((jd_packet_t *)&recvFrame); + if (!jd_shift_frame(&recvFrame)) + break; + } + } + + if (displayServiceNum == 0) { + // poke the control service to enumerate + queuePkt(JD_SERVICE_NUMBER_CTRL, JD_CMD_ADVERTISEMENT_DATA, 0); + flushSend(); + return; + } + + if (palette) { + { +#define PALETTE_SIZE (16 * 4) + auto cmd = + queuePkt(displayServiceNum, JD_SET(JD_INDEXED_SCREEN_REG_PALETTE), PALETTE_SIZE); + memcpy(cmd, palette, PALETTE_SIZE); + palette = NULL; + } + { + auto cmd = (jd_indexed_screen_start_update_t *)queuePkt( + displayServiceNum, JD_INDEXED_SCREEN_CMD_START_UPDATE, + sizeof(jd_indexed_screen_start_update_t)); + *cmd = this->addr; + } + { + auto cmd = + (uint8_t *)queuePkt(displayServiceNum, JD_SET(JD_INDEXED_SCREEN_REG_BRIGHTNESS), 1); + *cmd = this->brightness * 0xff / 100; + } + + if (soundServiceNum) { + // we only need this for sending sound + uint32_t now = (uint32_t)(pxt::current_time_ms()); + if (lastFrameTimestamp) { + uint32_t thisFrame = now - lastFrameTimestamp; + avgFrameTime = (avgFrameTime * 15 + thisFrame) >> 4; + } + lastFrameTimestamp = now; + // send around 2 frames of sound; typically around 60ms, so ~3000 samples + soundBufferDesiredSize = + sizeof(int16_t *) * ((((avgFrameTime * 2) >> 10) * soundSampleRate) >> 10); + } + + flushSend(); + return; + } + + if (dataLeft > 0) { + uint32_t transfer = bytesPerTransfer; + if (dataLeft < transfer) + transfer = dataLeft; + auto pixels = queuePkt(displayServiceNum, JD_INDEXED_SCREEN_CMD_SET_PIXELS, transfer); + memcpy(pixels, dataPtr, transfer); + dataPtr += transfer; + dataLeft -= transfer; + flushSend(); + } else if (soundServiceNum && soundBufferPending < soundBufferDesiredSize) { + int bytesLeft = soundBufferDesiredSize - soundBufferPending; + if (bytesLeft > bytesPerTransfer) + bytesLeft = bytesPerTransfer; + auto samples = (int16_t *)queuePkt(soundServiceNum, JD_ARCADE_SOUND_CMD_PLAY, bytesLeft); + if (pxt::redirectSamples(samples, bytesLeft >> 1, soundSampleRate)) { + soundBufferPending += bytesLeft; + } else { + // no sound generated, fill with 0 and stop + memset(samples, 0, bytesLeft); + soundBufferDesiredSize = 0; + } + flushSend(); + } else { + // trigger sendDone(), which executes outside of IRQ context, so there + // is no race with waitForSendDone + Event(DEVICE_ID_DISPLAY, 4243); + } +} + +int JDDisplay::sendIndexedImage(const uint8_t *src, unsigned width, unsigned height, + uint32_t *palette) { + if (height & 1 || !height || !width) + target_panic(123); // PANIC_SCREEN_ERROR + if (width != addr.width || height != addr.height) + target_panic(124); // PANIC_SCREEN_ERROR + if (inProgress) + target_panic(125); // PANIC_SCREEN_ERROR + + if (addr.y && addr.y >= screenHeight) + return 0; // out of range + + inProgress = true; + + int numcols = JD_SERIAL_PAYLOAD_SIZE / (height / 2); + + bytesPerTransfer = numcols * (height / 2); + dataLeft = (height / 2) * width; + dataPtr = src; + + this->palette = palette; + + memset(&sendFrame, 0, sizeof(sendFrame)); + + step(); + + return 0; +} + +} // namespace pxt \ No newline at end of file diff --git a/cpp/jddisplay.h b/cpp/jddisplay.h new file mode 100644 index 0000000..f9626d3 --- /dev/null +++ b/cpp/jddisplay.h @@ -0,0 +1,67 @@ +#ifndef __JDDISPLAY_H +#define __JDDISPLAY_H + +#include "pxt.h" + +#include "Pin.h" +#define PinCompat codal::Pin + +#undef SPI +#include "jdprotocol.h" +#include "arcadegamepad.h" +#include "indexedscreen.h" +#include "arcadesound.h" + +namespace pxt { + +class JDDisplay { + jd_indexed_screen_start_update_t addr; + SPI *spi; + Pin *cs; + Pin *flow; + uint32_t dataLeft; + const uint8_t *dataPtr; + uint32_t *palette; + jd_frame_t sendFrame; + jd_frame_t recvFrame; + uint8_t bytesPerTransfer; + bool inProgress; + volatile bool stepWaiting; + uint8_t displayServiceNum; + uint8_t controlsStartServiceNum; + uint8_t controlsEndServiceNum; + uint8_t soundServiceNum; + uint16_t screenWidth, screenHeight; + uint32_t buttonState; + uint32_t avgFrameTime; // in us + uint32_t lastFrameTimestamp; + + uint32_t soundBufferDesiredSize; + uint32_t soundBufferPending; + uint16_t soundSampleRate; + + void *queuePkt(uint32_t service_num, uint32_t service_cmd, uint32_t size); + void flushSend(); + void step(); + void sendDone(Event); + static void stepStatic(void *); + void onFlowHi(Event); + void handleIncoming(jd_packet_t *pkt); + + public: + uint8_t brightness; + JDDisplay(SPI *spi, Pin *cs, Pin *flow); + void setAddrWindow(int x, int y, int w, int h) { + addr.x = x; + addr.y = y; + addr.width = w; + addr.height = h; + } + void waitForSendDone(); + + int sendIndexedImage(const uint8_t *src, unsigned width, unsigned height, uint32_t *palette); +}; + +} // namespace pxt + +#endif diff --git a/cpp/jdprotocol.h b/cpp/jdprotocol.h new file mode 100644 index 0000000..8d81c48 --- /dev/null +++ b/cpp/jdprotocol.h @@ -0,0 +1,125 @@ +#ifndef __JDPROTOCOL_H +#define __JDPROTOCOL_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// 255 minus size of the serial header, rounded down to 4 +#define JD_SERIAL_PAYLOAD_SIZE 236 +#define JD_SERIAL_FULL_HEADER_SIZE 16 + +#define JD_SERVICE_CLASS_CTRL 0x00000000 + +#define JD_SERVICE_NUMBER_CTRL 0x00 +#define JD_SERVICE_NUMBER_MASK 0x3f +#define JD_SERVICE_NUMBER_CRC_ACK 0x3f + +// the COMMAND flag signifies that the device_identifier is the recipent +// (i.e., it's a command for the peripheral); the bit clear means device_identifier is the source +// (i.e., it's a report from peripheral or a broadcast message) +#define JD_FRAME_FLAG_COMMAND 0x01 +// an ACK should be issued with CRC of this package upon reception +#define JD_FRAME_FLAG_ACK_REQUESTED 0x02 +// the device_identifier contains target service class number +#define JD_FRAME_FLAG_IDENTIFIER_IS_SERVICE_CLASS 0x04 + +#define JD_FRAME_SIZE(pkt) ((pkt)->size + 12) + +// Registers 0x001-0x07f - r/w common to all services +// Registers 0x080-0x0ff - r/w defined per-service +// Registers 0x100-0x17f - r/o common to all services +// Registers 0x180-0x1ff - r/o defined per-service +// Registers 0x200-0xeff - custom, defined per-service +// Registers 0xf00-0xfff - reserved for implementation, should not be on the wire + +// this is either binary (0 or non-zero), or can be gradual (eg. brightness of neopixel) +#define JD_REG_INTENSITY 0x01 +// the primary value of actuator (eg. servo angle) +#define JD_REG_VALUE 0x02 +// enable/disable streaming +#define JD_REG_IS_STREAMING 0x03 +// streaming interval in miliseconds +#define JD_REG_STREAMING_INTERVAL 0x04 +// for analog sensors +#define JD_REG_LOW_THRESHOLD 0x05 +#define JD_REG_HIGH_THRESHOLD 0x06 +// limit power drawn; in mA +#define JD_REG_MAX_POWER 0x07 + +// eg. one number for light sensor, all 3 coordinates for accelerometer +#define JD_REG_READING 0x101 + +#define JD_CMD_GET_REG 0x1000 +#define JD_CMD_SET_REG 0x2000 + +#define JD_GET(reg) (JD_CMD_GET_REG | (reg)) +#define JD_SET(reg) (JD_CMD_SET_REG | (reg)) + +// Commands 0x000-0x07f - common to all services +// Commands 0x080-0xeff - defined per-service +// Commands 0xf00-0xfff - reserved for implementation +// enumeration data for CTRL, ad-data for other services +#define JD_CMD_ADVERTISEMENT_DATA 0x00 +// event from sensor or on broadcast service +#define JD_CMD_EVENT 0x01 +// request to calibrate sensor +#define JD_CMD_CALIBRATE 0x02 +// request human-readable description of service +#define JD_CMD_GET_DESCRIPTION 0x03 + +// Commands specific to control service +// do nothing +#define JD_CMD_CTRL_NOOP 0x80 +// blink led or otherwise draw user's attention +#define JD_CMD_CTRL_IDENTIFY 0x81 +// reset device +#define JD_CMD_CTRL_RESET 0x82 +// identifies the type of hardware (eg., ACME Corp. Servo X-42 Rev C) +#define JD_REG_CTRL_DEVICE_DESCRIPTION 0x180 +// a numeric code for the string above; used to mark firmware images +#define JD_REG_CTRL_DEVICE_CLASS 0x181 +// MCU temperature in Celsius +#define JD_REG_CTRL_TEMPERATURE 0x182 +// this is very approximate; ADC reading from backward-biasing the identification LED +#define JD_REG_CTRL_LIGHT_LEVEL 0x183 +// typically the same as JD_REG_CTRL_DEVICE_CLASS; the bootloader will respond to that code +#define JD_REG_CTRL_BL_DEVICE_CLASS 0x184 + +struct _jd_packet_t { + uint16_t crc; + uint8_t _size; // of frame data[] + uint8_t flags; + + uint64_t device_identifier; + + uint8_t service_size; + uint8_t service_number; + uint16_t service_command; + + uint8_t data[0]; +} __attribute__((__packed__, aligned(4))); +typedef struct _jd_packet_t jd_packet_t; + +struct _jd_frame_t { + uint16_t crc; + uint8_t size; + uint8_t flags; + + uint64_t device_identifier; + + uint8_t data[JD_SERIAL_PAYLOAD_SIZE + 4]; +} __attribute__((__packed__, aligned(4))); +typedef struct _jd_frame_t jd_frame_t; + +#define JDSPI_MAGIC 0x7ACD +#define JDSPI_MAGIC_NOOP 0xB3CD + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cpp/pins.cpp b/cpp/pins.cpp new file mode 100644 index 0000000..01289ad --- /dev/null +++ b/cpp/pins.cpp @@ -0,0 +1,71 @@ +#include "pxt.h" + +#include "Pin.h" +#define PinCompat codal::Pin + +#include "config_nrf.h" + +// #undef Button + +namespace pxt { +static DevicePin **pinPtrs; +static uint8_t numPinPtrs; +static uint8_t pinPos[DEV_NUM_PINS]; + +DevicePin *myGetPin(int id) { + + id &= CFG_PIN_NAME_MSK; + + if (id >= DEV_NUM_PINS) + soft_panic(PANIC_NO_SUCH_PIN); + + // we could use lookupComponent() here - it would be slightly slower + + int ptr = pinPos[id]; + if (ptr == 0) { + pinPtrs = (DevicePin **)realloc(pinPtrs, (numPinPtrs + 1) * sizeof(void *)); + bool isAnalog = IS_ANALOG_PIN(id); + // GCTODO + pinPtrs[numPinPtrs++] = + new DevicePin(DEVICE_ID_IO_P0 + id, (PinName)id, + isAnalog ? PIN_CAPABILITY_AD : PIN_CAPABILITY_DIGITAL); + ptr = numPinPtrs; + pinPos[id] = ptr; + } + return pinPtrs[ptr - 1]; +} + +DevicePin *getPinCfg(int key) { + int p = getConfig(key, -1); + if (p == -1) + DMESG("no pin cfg: %d", key); + return myGetPin(p); +} + +void linkPin(int from, int to) { + if (from < 0 || from >= DEV_NUM_PINS) + soft_panic(PANIC_NO_SUCH_PIN); + myGetPin(to); + pinPos[from] = pinPos[to]; +} + +DevicePin *myLookupPin(int pinName) { + if (pinName < 0 || pinName == 0xff) + return NULL; + pinName &= CFG_PIN_NAME_MSK; + return myGetPin(pinName); +} + +DevicePin *lookupPinCfg(int key) { + return myLookupPin(getConfig(key)); +} + +CodalComponent *lookupComponent(int id) { + for (int i = 0; i < DEVICE_COMPONENT_COUNT; ++i) { + if (CodalComponent::components[i] && CodalComponent::components[i]->id == id) + return CodalComponent::components[i]; + } + return NULL; +} + +} // namespace pxt diff --git a/cpp/pinsDigital.cpp b/cpp/pinsDigital.cpp new file mode 100644 index 0000000..042a986 --- /dev/null +++ b/cpp/pinsDigital.cpp @@ -0,0 +1,142 @@ +#include "pxt.h" + +#include "Pin.h" +#define PinCompat codal::Pin + +#include "config_nrf.h" + +namespace pxt { + +static void waitABit() { + // for (int i = 0; i < 10; ++i) + // asm volatile("nop"); +} + +class ButtonMultiplexer : public CodalComponent { + public: + Pin &latch; + Pin &clock; + Pin &data; + uint32_t state; + uint32_t invMask; + uint16_t buttonIdPerBit[8]; + bool enabled; + + ButtonMultiplexer(uint16_t id) + : latch(uBit.io.P9), + clock(uBit.io.P20), + data((uBit.io.P14)) { + this->id = id; + this->status |= DEVICE_COMPONENT_STATUS_SYSTEM_TICK; + + state = 0; + invMask = 0; + enabled = true; + + memset(buttonIdPerBit, 0, sizeof(buttonIdPerBit)); + + data.setPull(PullMode::Down); + data.getDigitalValue(); + latch.setDigitalValue(1); + clock.setDigitalValue(1); + } + + void disable() { + data.getDigitalValue(PullMode::None); + latch.getDigitalValue(PullMode::None); + clock.getDigitalValue(PullMode::None); + enabled = false; + } + + bool isButtonPressed(int id) { + for (int i = 0; i < 8; ++i) { + if (buttonIdPerBit[i] == id) + return (state & (1 << i)) != 0; + } + return false; + } + + uint32_t readBits(int bits) { + latch.setDigitalValue(0); + waitABit(); + latch.setDigitalValue(1); + waitABit(); + + uint32_t state = 0; + for (int i = 0; i < bits; i++) { + state <<= 1; + if (data.getDigitalValue()) + state |= 1; + + clock.setDigitalValue(0); + waitABit(); + clock.setDigitalValue(1); + waitABit(); + } + + return state; + } + + virtual void periodicCallback() override { + if (!enabled) + return; + + uint32_t newState = readBits(8); + newState ^= invMask; + if (newState == state) + return; + + for (int i = 0; i < 8; ++i) { + uint32_t mask = 1 << i; + if (!buttonIdPerBit[i]) + continue; + int ev = 0; + if (!(state & mask) && (newState & mask)) + ev = PXT_INTERNAL_KEY_DOWN; + else if ((state & mask) && !(newState & mask)) + ev = PXT_INTERNAL_KEY_UP; + if (ev) { + Event(ev, buttonIdPerBit[i]); + Event(ev, 0); // any key + } + } + + state = newState; + } +}; + +static ButtonMultiplexer *btnMultiplexer; +ButtonMultiplexer *getMultiplexer() { + if (!btnMultiplexer) + btnMultiplexer = new ButtonMultiplexer(DEVICE_ID_FIRST_BUTTON); + return btnMultiplexer; +} + +int registerMultiplexedButton(int pin, int buttonId) { + if (1050 <= pin && pin < 1058) { + pin -= 50; + getMultiplexer()->invMask |= 1 << (pin - 1000); + } + if (1000 <= pin && pin < 1008) { + getMultiplexer()->buttonIdPerBit[pin - 1000] = buttonId; + return 1; + } + return 0; +} + +int multiplexedButtonIsPressed(int btnId) { + if (btnMultiplexer) + return btnMultiplexer->isButtonPressed(btnId) ? 512 : 0; + return 0; +} + +uint32_t readButtonMultiplexer(int bits) { + return getMultiplexer()->readBits(bits); +} + +void disableButtonMultiplexer() { + getMultiplexer()->disable(); +} + +} + diff --git a/cpp/pxt.json b/cpp/pxt.json new file mode 100644 index 0000000..fac8df7 --- /dev/null +++ b/cpp/pxt.json @@ -0,0 +1,39 @@ +{ + "name": "arcadeshield", + "description": "Support for the Arcade Shield. micro:bit (V2) only.", + "dependencies": { + "core": "file:../core", + "bitmap": "file:../bitmap", + "fonts": "file:../fonts" + }, + "files": [ + "config_nrf.h", + "arcadegamepad.h", + "indexedscreen.h", + "arcadesound.h", + "jdprotocol.h", + "jddisplay.h", + "jddisplay.cpp", + "pins.cpp", + "pinsDigital.cpp", + "controllerbuttons.cpp", + "screen.cpp", + "screenhelpers.d.ts", + "shims.d.ts" + ], + "test": [ + "test.ts" + ], + "public": true, + "hidden": true, + "preferredEditor": "tsprj", + "disablesVariants": [ + "mbdal" + ], + "yotta": { + "config": { + "DEVICE_BLE": 0, + "DMESG_SERIAL_DEBUG": 1 + } + } +} diff --git a/cpp/screen.cpp b/cpp/screen.cpp new file mode 100644 index 0000000..7307847 --- /dev/null +++ b/cpp/screen.cpp @@ -0,0 +1,348 @@ +#include "pxt.h" + +#include "Pin.h" +#define PinCompat codal::Pin + +#include "ST7735.h" +#include "ILI9341.h" + +// this is a hack because someone (don't know where) #defined SPI to be NRF52SPI, +// which messes with the include file below the #undef +#undef SPI +#include "SPIScreenIO.h" + +#include "jddisplay.h" + +#include "config_nrf.h" + +typedef RefImage *Bitmap_; + +namespace pxt { + +class WDisplay { + public: + ScreenIO *io; + ST7735 *lcd; + JDDisplay *smart; + + uint32_t currPalette[16]; + bool present; + bool newPalette; + bool inUpdate; + uint8_t *screenBuf; + + uint16_t width, height; + uint16_t displayHeight; + uint8_t offX, offY; + bool doubleSize; + uint32_t palXOR; + + WDisplay() { + uint32_t cfg2 = MY_DISPLAY_CFG2; + + uint32_t cfg0 = MY_DISPLAY_CFG0; + uint32_t frmctr1 = MY_DISPLAY_CFG1; + + int dispTp = MY_DISPLAY_TYPE; + + doubleSize = false; + smart = NULL; + + auto miso = LOOKUP_PIN(DISPLAY_MISO); + dispTp = smartConfigure(&cfg0, &frmctr1, &cfg2); + + if (dispTp != DISPLAY_TYPE_SMART) + miso = NULL; // only JDDisplay needs MISO, otherwise leave free + + SPI *spi = new CODAL_SPI(*LOOKUP_PIN(DISPLAY_MOSI), *miso, *LOOKUP_PIN(DISPLAY_SCK)); + io = new SPIScreenIO(*spi); + + if (dispTp == DISPLAY_TYPE_ST7735) { + width = 160; + height = 128; + lcd = new ST7735(*io, *LOOKUP_PIN(DISPLAY_CS), *LOOKUP_PIN(DISPLAY_DC)); + } else if (dispTp == DISPLAY_TYPE_SMART) { + lcd = NULL; + width = 160; + height = 120; + smart = new JDDisplay(spi, LOOKUP_PIN(DISPLAY_CS), LOOKUP_PIN(DISPLAY_DC)); + } else + target_panic(128); // PANIC_SCREEN_ERROR + + palXOR = (cfg0 & 0x1000000) ? 0xffffff : 0x000000; + auto madctl = cfg0 & 0xff; + offX = (cfg0 >> 8) & 0xff; + offY = (cfg0 >> 16) & 0xff; + + DMESG("configure screen: FRMCTR1=%p MADCTL=%p type=%d", frmctr1, madctl, dispTp); + + if (spi) { + auto freq = (cfg2 & 0xff); + if (!freq) + freq = 15; + spi->setFrequency(freq * 1000000); + spi->setMode(0); + // make sure the SPI peripheral is initialized before toggling reset + spi->write(0); + } + + auto rst = LOOKUP_PIN(DISPLAY_RST); + if (rst) { + rst->setDigitalValue(0); + fiber_sleep(20); + rst->setDigitalValue(1); + fiber_sleep(20); + } + + if (lcd) { + auto bl = LOOKUP_PIN(DISPLAY_BL); + if (bl) { + bl->setDigitalValue(1); + } + + lcd->init(); + lcd->configure(madctl, frmctr1); + } + + displayHeight = height; + setAddrMain(); + DMESG("screen: %d x %d, off=%d,%d", width, height, offX, offY); + int sz = doubleSize ? (width >> 1) * (height >> 1) : width * height; + screenBuf = (uint8_t *)app_alloc(sz / 2 + 20); + inUpdate = false; + } + + uint32_t smartConfigure(uint32_t *cfg0, uint32_t *cfg1, uint32_t *cfg2) { + uint32_t hc; + present = false; + + DMESG("74HC: waiting..."); + + // wait while nothing is connected + for (;;) { + auto rst = LOOKUP_PIN(DISPLAY_RST); + if (rst) { + rst->setDigitalValue(0); + target_wait_us(10); + rst->setDigitalValue(1); + fiber_sleep(3); // in reality we need around 1.2ms + } + + hc = readButtonMultiplexer(17); + if (hc != 0) + break; + + fiber_sleep(100); + + // the device will run without shield when the following is specified in user program: + // namespace userconfig { export const DISPLAY_CFG0 = 0x02000080 } + if (*cfg0 & 0x2000000) { + DMESG("74HC: no wait requested"); + return DISPLAY_TYPE_ST7735; + } + } + present = true; + + DMESG("74HC: %x", hc); + + // is the line forced up? if so, assume JDDisplay + if (hc == 0x1FFFF) { + disableButtonMultiplexer(); + return DISPLAY_TYPE_SMART; + } + + hc = hc >> 1; + + // SER pin (or first bit of second HC) is orientation + if (hc & 0x0010) + *cfg0 = 0x80; + else + *cfg0 = 0x40; + + uint32_t configId = (hc & 0xe0) >> 5; + + + switch (configId) { + case 1: + *cfg1 = 0x0603; // ST7735 + break; + case 2: + *cfg1 = 0xe14ff; // ILI9163C + *cfg0 |= 0x08; // BGR colors + break; + case 3: + *cfg1 = 0x0603; // ST7735 + *cfg0 |= 0x1000000; // inverted colors + break; + default: + target_panic(129); // PANIC_SCREEN_ERROR + break; + } + + DMESG("config type: %d; cfg0=%x cfg1=%x", configId, *cfg0, *cfg1); + + // for some reason, setting SPI frequency to 32 doesn't + // work with ST77735 in pxt-microbit + *cfg2 = 16; // Damn the torpedoes! 32MHz + + return DISPLAY_TYPE_ST7735; + } + + void setAddrMain() { + if (lcd) + lcd->setAddrWindow(offX, offY, width, displayHeight); + else + smart->setAddrWindow(offX, offY, width, displayHeight); + } + void waitForSendDone() { + if (lcd) + lcd->waitForSendDone(); + else + smart->waitForSendDone(); + } + int sendIndexedImage(const uint8_t *src, unsigned width, unsigned height, uint32_t *palette) { + if (lcd) + return lcd->sendIndexedImage(src, width, height, palette); + else + return smart->sendIndexedImage(src, width, height, palette); + } +}; + +SINGLETON_IF_PIN(WDisplay, DISPLAY_MOSI); + +//% +int setScreenBrightnessSupported() { + auto display = getWDisplay(); + if (display && display->smart) + return 1; + + auto bl = LOOKUP_PIN(DISPLAY_BL); + if (!bl) + return 0; +#ifdef SAMD51 + if (bl->name == PA06) + return 0; +#endif +#ifdef NRF52_SERIES + // PWM not implemented yet + return 0; +#else + return 1; +#endif +} + +//% +void setScreenBrightness(int level) { + if (level < 0) + level = 0; + if (level > 100) + level = 100; + + auto display = getWDisplay(); + if (display && display->smart) { + display->smart->brightness = level; + return; + } + + auto bl = LOOKUP_PIN(DISPLAY_BL); + if (!bl) + return; + + if (level == 0) + bl->setDigitalValue(0); + else if (level == 100) + bl->setDigitalValue(1); + else { + if (setScreenBrightnessSupported()) { + bl->setAnalogPeriodUs(1000); + bl->setAnalogValue(level * level * 1023 / 10000); + } + } +} + +//% +void setPalette(Buffer buf) { + auto display = getWDisplay(); + if (!display) + return; + + if (48 != buf->length) + target_panic(130); // PANIC_SCREEN_ERROR + for (int i = 0; i < 16; ++i) { + display->currPalette[i] = + (buf->data[i * 3] << 16) | (buf->data[i * 3 + 1] << 8) | (buf->data[i * 3 + 2] << 0); + display->currPalette[i] ^= display->palXOR; + } + display->newPalette = true; +} + +//% +bool displayPresent() { + auto display = getWDisplay(); + if (!display) + return false; + return display->present; +} + +//% +int displayHeight() { + auto display = getWDisplay(); + if (!display) + return -1; + return display->displayHeight; +} + +//% +int displayWidth() { + auto display = getWDisplay(); + if (!display) + return -1; + return display->width; +} + +//% +void updateScreen(Bitmap_ img) { + auto display = getWDisplay(); + if (!display) + return; + + if (display->inUpdate) + return; + + display->inUpdate = true; + + auto mult = display->doubleSize ? 2 : 1; + + if (img) { + if (img->bpp() != 4 || img->width() * mult != display->width || + img->height() * mult != display->displayHeight) + target_panic(131); // PANIC_SCREEN_ERROR + + // DMESG("wait for done"); + display->waitForSendDone(); + + auto palette = display->currPalette; + + if (display->newPalette) { + display->newPalette = false; + } else { + // smart mode always sends palette + if (!display->smart) + palette = NULL; + } + + memcpy(display->screenBuf, img->pix(), img->pixLength()); + + // DMESG("send"); + display->sendIndexedImage(display->screenBuf, img->width(), img->height(), palette); + } + + display->inUpdate = false; +} + +//% +void updateStats(String msg) { + // ignore... +} + +} // namespace pxt \ No newline at end of file diff --git a/cpp/screenhelpers.d.ts b/cpp/screenhelpers.d.ts new file mode 100644 index 0000000..7495f5d --- /dev/null +++ b/cpp/screenhelpers.d.ts @@ -0,0 +1,14 @@ +declare namespace __screenhelpers { + //% shim=pxt::displayPresent + function displayPresent(): boolean; + //% shim=pxt::setPalette + function setPalette(buf: Buffer): void; + //% shim=pxt::displayWidth + function displayWidth(): number; + //% shim=pxt::displayHeight + function displayHeight(): number; + //% shim=pxt::setScreenBrightness + function setScreenBrightness(b: number): void; + //% shim=pxt::updateScreen + function updateScreen(bmp: Bitmap): void; +} diff --git a/cpp/shims.d.ts b/cpp/shims.d.ts new file mode 100644 index 0000000..3c20787 --- /dev/null +++ b/cpp/shims.d.ts @@ -0,0 +1,159 @@ +// Auto-generated. Do not edit. + + +declare interface Bitmap { + /** + * Get underlying buffer + */ + //% property shim=BitmapMethods::__buffer + __buffer: Buffer; + + /** + * Get the width of the bitmap + */ + //% property shim=BitmapMethods::width + width: int32; + + /** + * Get the height of the bitmap + */ + //% property shim=BitmapMethods::height + height: int32; + + /** + * True if the bitmap is monochromatic (black and white) + */ + //% property shim=BitmapMethods::isMono + isMono: boolean; + + /** + * Sets all pixels in the current bitmap from the other bitmap, which has to be of the same size and + * bpp. + */ + //% shim=BitmapMethods::copyFrom + copyFrom(from: Bitmap): void; + + /** + * Set pixel color + */ + //% shim=BitmapMethods::setPixel + setPixel(x: int32, y: int32, c: int32): void; + + /** + * Get a pixel color + */ + //% shim=BitmapMethods::getPixel + getPixel(x: int32, y: int32): int32; + + /** + * Fill entire bitmap with a given color + */ + //% shim=BitmapMethods::fill + fill(c: int32): void; + + /** + * Copy row(s) of pixel from bitmap to buffer (8 bit per pixel). + */ + //% shim=BitmapMethods::getRows + getRows(x: int32, dst: Buffer): void; + + /** + * Copy row(s) of pixel from buffer to bitmap. + */ + //% shim=BitmapMethods::setRows + setRows(x: int32, src: Buffer): void; + + /** + * Return a copy of the current bitmap + */ + //% shim=BitmapMethods::clone + clone(): Bitmap; + + /** + * Flips (mirrors) pixels horizontally in the current bitmap + */ + //% shim=BitmapMethods::flipX + flipX(): void; + + /** + * Flips (mirrors) pixels vertically in the current bitmap + */ + //% shim=BitmapMethods::flipY + flipY(): void; + + /** + * Returns a transposed bitmap (with X/Y swapped) + */ + //% shim=BitmapMethods::transposed + transposed(): Bitmap; + + /** + * Every pixel in bitmap is moved by (dx,dy) + */ + //% shim=BitmapMethods::scroll + scroll(dx: int32, dy: int32): void; + + /** + * Stretches the bitmap horizontally by 100% + */ + //% shim=BitmapMethods::doubledX + doubledX(): Bitmap; + + /** + * Stretches the bitmap vertically by 100% + */ + //% shim=BitmapMethods::doubledY + doubledY(): Bitmap; + + /** + * Replaces one color in an bitmap with another + */ + //% shim=BitmapMethods::replace + replace(from: int32, to: int32): void; + + /** + * Stretches the bitmap in both directions by 100% + */ + //% shim=BitmapMethods::doubled + doubled(): Bitmap; + + /** + * Draw given bitmap on the current bitmap + */ + //% shim=BitmapMethods::drawBitmap + drawBitmap(from: Bitmap, x: int32, y: int32): void; + + /** + * Draw given bitmap with transparent background on the current bitmap + */ + //% shim=BitmapMethods::drawTransparentBitmap + drawTransparentBitmap(from: Bitmap, x: int32, y: int32): void; + + /** + * Check if the current bitmap "collides" with another + */ + //% shim=BitmapMethods::overlapsWith + overlapsWith(other: Bitmap, x: int32, y: int32): boolean; +} +declare namespace bitmaps { + + /** + * Create new bitmap with given content + */ + //% shim=bitmaps::ofBuffer + function ofBuffer(buf: Buffer): Bitmap; + + /** + * Create new empty (transparent) bitmap + */ + //% shim=bitmaps::create + function create(width: int32, height: int32): Bitmap; + + /** + * Double the size of an icon + */ + //% shim=bitmaps::doubledIcon + function doubledIcon(icon: Buffer): Buffer; +} + +// Auto-generated. Do not edit. Really. diff --git a/cpp/test.ts b/cpp/test.ts new file mode 100644 index 0000000..de610e9 --- /dev/null +++ b/cpp/test.ts @@ -0,0 +1,29 @@ +// tests go here; this will not be compiled when this package is used as an extension. + +const present = __screenhelpers.displayPresent(); +basic.showNumber(present ? 1 : 0) + +// set palette before creating screen, which initializes the display +__screenhelpers.setPalette(hex`000000ffffffff2121ff93c4ff8135fff609249ca378dc52003fad87f2ff8e2ec4a4839f5c406ce5cdc491463d000000`) + +const screen = bitmaps.create( + __screenhelpers.displayWidth(), + __screenhelpers.displayHeight() +) + +input.onButtonPressed(Button.A, () => { + screen.fill(2) + screen.drawLine(0,0,100,100,0) + __screenhelpers.updateScreen(screen) +}) + +input.onButtonPressed(Button.B, () => { + screen.fill(4) + screen.drawLine(100,0,0,100,0) + __screenhelpers.updateScreen(screen) +}) + +input.onButtonPressed(Button.AB, () => { + screen.fill(0) + __screenhelpers.updateScreen(screen) +}) diff --git a/docs/bitmap-type.md b/docs/bitmap-type.md deleted file mode 100644 index 5a4bbeb..0000000 --- a/docs/bitmap-type.md +++ /dev/null @@ -1,222 +0,0 @@ -# Bitmap - -A bitmap is some number of rows and columns of color pixels that make up rectangular picture. A _pixel_ is a single point of color inside the bitmap. - -Bitmaps are two-dimensional so they have a known height and width. When a bitmap is declared, or created, the height and width are specified either by the _layout_ of the bitmap or as parameters to it's [create](/reference/bitmaps/create) method. - -## Bitmap layout - -You _declare_ a bitmap by creating a layout. This is done in JavaScript with the ``bmp'...'`` string declaration. The pixels are single characters inside the string. - -### Zero size bitmap - -An zero size bitmap has no height or width and has no pixels, so the **bmp** string is just ``bmp''``. - -```typescript -let emptyBitmap = bmp`` -``` - -You can also use [create](/reference/bitmaps/create) and make another zero size bitmap with no pixels. - -```blocks -let emptyBitmap1 = bmp`` -let emptyBitmap2 = bitmaps.create(0, 0) -``` - -A zero size bitmap isn't really useful so MakeCode actually makes it have some size if you declare it without any. - -### Bitmaps with size - -To make a bitmap with some size, just set the pixel characters in the rows of the **bmp** string. A bitmap that is 1 pixel high by 1 pixel wide (1 x 1) is: - -```typescript -let oneByOne = bmp`.` -``` - -A bitmap that is 2 x 2 is declared like this: - -```typescript -let twoBytwo = bmp` -. . -. . -` -``` - -Here they are in blocks: - -```blocks -let oneByOne = bmp`.` -let twoBytwo = bmp` -. . -. . -` -``` - -You'll notice that they look the same. That's because the pixel colors are not set so the bitmaps are empty. - -Bitmaps don't have to be exactly square. The height and width can be different. Here's a 6 x 2 bitmap: - -```typescript -let sixByTwo = bmp` -. . . . . . -. . . . . . -` -``` - -## Setting pixels - -### Transparent pixels - -A pixel value of `.` means an empty pixel. This pixel has no color and that pixel _location_ in the bitmap is _transparent_. Being transparent means that if this bitmap is on top of another bitmap (overlapping) that has some pixel color, then the color of the pixel in the bitmap underneath shows through to the bitmap above it. - -### Pixel colors - -Besides the empty, or transparent pixel `.`, there are 16 color pixels you can use. These are matched to colors in a _palette_. A palette is a group of colors you can choose from. The colors are selected by using a single number or letter to match them. The default palette, for example, uses these colors: - -* `.`: empty or transparent -* `0`: transparent -* `1`: white -* `2`: light blue -* `3`: medium blue -* `4`: dark blue -* `5`: violet -* `6`: lime -* `7`: olive -* `8`: brown -* `9`: cyan -* `a`: red -* `b`: purple -* `c`: pink -* `d`: orange -* `e`: yellow -* `f`: black - -A 1 x 1 bitmap with a red pixel is declared as: - -```typescript -let oneRed = bmp`a` -``` - -As a block it looks like this: - -```block -let oneRed = bmp`a` -``` - -We can make 4 x 4 bitmap that uses all of the colors: - -```typescript -let allColors = bmp` -0 1 2 3 -4 5 6 7 -8 9 a b -c d e f -` -``` - -This the same bitmap as a block: - -```block -let allColors = bmp` -0 1 2 3 -4 5 6 7 -8 9 a b -c d e f -` -``` - -## Transparency and overlap - -Let's see how transparency works with bitmaps. A `.` means that a pixel is transparent. Only the pixels with a color will show in a bitmap and any pixels with color in a bitmap below it will show through. So, to demonstrate, let's make two bitmaps that are the same size and put one that has some transparent pixels on top of one that doesn't. - -Our first bitmap is a green circle inside a 8 x 8 rectangle. All of the pixels around the circle are transparent. - -```typescript -let greenBall = bmp` -. . . . . . . . -. . . 6 6 . . . -. . 6 6 6 6 . . -. 6 6 6 6 6 6 . -. 6 6 6 6 6 6 . -. . 6 6 6 6 . . -. . . 6 6 . . . -. . . . . . . . -` -``` - -The other bitmap is the same size but with all yellow pixels. - -```blocks -let greenBall = bmp` -. . . . . . . . -. . . 6 6 . . . -. . 6 6 6 6 . . -. 6 6 6 6 6 6 . -. 6 6 6 6 6 6 . -. . 6 6 6 6 . . -. . . 6 6 . . . -. . . . . . . . -` - -let yellowSquare = bmp` -e e e e e e e e -e e e e e e e e -e e e e e e e e -e e e e e e e e -e e e e e e e e -e e e e e e e e -e e e e e e e e -e e e e e e e e -e e e e e e e e -` -``` - -Putting the green circle bitmap exactly over the yellow square, you see that the yellow from the bitmap below isn't blocked out by the transparent pixels from the bitmap on top. - -```sim -let greenBall = bmp` -. . . . . . . . . . . . . . . . -. . . . . . . . . . . . . . . . -. . . . . . 6 6 6 6 . . . . . . -. . . . 6 6 6 6 6 6 6 6 . . . . -. . . 6 6 6 6 6 6 6 6 6 6 . . . -. . . 6 6 6 6 6 6 6 6 6 6 . . . -. . 6 6 6 6 6 6 6 6 6 6 6 6 . . -. . 6 6 6 6 6 6 6 6 6 6 6 6 . . -. . 6 6 6 6 6 6 6 6 6 6 6 6 . . -. . 6 6 6 6 6 6 6 6 6 6 6 6 . . -. . . 6 6 6 6 6 6 6 6 6 6 . . . -. . . 6 6 6 6 6 6 6 6 6 6 . . . -. . . . 6 6 6 6 6 6 6 6 . . . . -. . . . . . 6 6 6 6 . . . . . . -. . . . . . . . . . . . . . . . -. . . . . . . . . . . . . . . . -` -let yellowSquare = bitmaps.create(16, 16) -yellowSquare.fill(14) - -let yellowSprite = sprites.create(yellowSquare) -let greenSprite = sprites.create(greenBall) -``` - -## Setting pixels at locations - -You can create your bitmaps while your program is running too (dynamically). To make a bitmap this way, you set the color of a pixel at its location with code. Pixels are addressed by their row (``x`` value) and column (``y`` value) inside the bitmap. You could create and empty bitmap and make some or all of the bitmap by setting pixel colors in your code. Let's make a 32 x 32 box by creating an empty bitmap and then draw an orange border around it. - -```blocks -let orangeBox = bitmaps.create(32, 32) -for (let i = 0; i < 32; i++) { - orangeBox.setPixel(0, i, 13) - orangeBox.setPixel(i, 0, 13) - orangeBox.setPixel(i, 31, 13) - orangeBox.setPixel(31, i, 13) -} -let boxSprite = sprites.create(orangeBox) -``` - -The [bitmap](/reference/bitmaps) functions let you do more complex pixel operations like filling and drawing shapes. - - -```package -pxt-arcadeshield=github:microbit-apps/pxt-arcadeshield -``` diff --git a/docs/draw-bitmap.md b/docs/draw-bitmap.md new file mode 100644 index 0000000..6013b76 --- /dev/null +++ b/docs/draw-bitmap.md @@ -0,0 +1,6 @@ +# Draw bitmap + + +```package +pxt-arcadeshield=github:microbit-apps/pxt-arcadeshield +``` diff --git a/pxt.json b/pxt.json index 0583c8d..be4d0c4 100644 --- a/pxt.json +++ b/pxt.json @@ -6,7 +6,7 @@ "core": "*", "radio": "*", "microphone": "*", - "arcadeshield": "*", + "bitmap": "*", "fonts": "*" }, "files": [ @@ -24,6 +24,18 @@ "ns.ts", "shieldhelpers.ts", "text.ts", + "cpp/config_nrf.h", + "cpp/arcadegamepad.h", + "cpp/indexedscreen.h", + "cpp/arcadesound.h", + "cpp/jdprotocol.h", + "cpp/jddisplay.h", + "cpp/jddisplay.cpp", + "cpp/pins.cpp", + "cpp/pinsDigital.cpp", + "cpp/controllerbuttons.cpp", + "cpp/screen.cpp", + "cpp/screenhelpers.d.ts", "docs/clone.md", "docs/draw-line.md", "docs/draw-rect.md", @@ -41,7 +53,7 @@ "docs/screen-bitmap.md", "docs/on-button-event.md", "docs/is-pressed.md", - "docs/bitmap-type.md" + "docs/draw-bitmap.md" ], "testFiles": [ "test.ts" @@ -54,6 +66,15 @@ "supportedTargets": [ "microbit" ], + "disablesVariants": [ + "mbdal" + ], + "yotta": { + "config": { + "DEVICE_BLE": 0, + "DMESG_SERIAL_DEBUG": 1 + } + }, "preferredEditor": "tsprj", "tests": [ "test.ts" @@ -75,8 +96,5 @@ "#e5cdc4", "#91463d", "#000000" - ], - "disablesVariants": [ - "mbdal" ] } diff --git a/simx/public/assets/shieldSim.png b/simx/public/assets/shieldSim.png new file mode 100644 index 0000000..31ca930 Binary files /dev/null and b/simx/public/assets/shieldSim.png differ