├── shims.d.ts ├── icon.png ├── .gitignore ├── tsconfig.json ├── jsconfig.js ├── package.json ├── pxt.json ├── .github └── workflows │ └── makecode.yml ├── LICENSE ├── test.ts ├── font.cpp ├── display.cpp ├── README.md └── inkybit.ts /shims.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace inkybit { 2 | } 3 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raster/pxt-inkybit/master/icon.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | built 2 | node_modules 3 | yotta_modules 4 | yotta_targets 5 | pxt_modules 6 | .vscode 7 | *.db 8 | *.tgz 9 | package-lock.json 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "noImplicitAny": true, 5 | "outDir": "built", 6 | "rootDir": "." 7 | }, 8 | "exclude": ["pxt_modules/**/*test.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /jsconfig.js: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "noImplicitAny": true, 5 | "outDir": "built", 6 | "rootDir": "." 7 | }, 8 | "exclude": ["pxt_modules/**/*test.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pxt-inkybit2", 3 | "version": "0.0.2", 4 | "dependencies": { 5 | "pxt": "^5.0.0" 6 | }, 7 | "devDependencies": { 8 | "pxt-core": "^5.0.0", 9 | "pxt-microbit": "^3.1.36" 10 | }, 11 | "scripts": { 12 | "pxt:init": "pxt target microbit", 13 | "pxt:install": "pxt install", 14 | "test": "pxt test" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /pxt.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "inkybit", 3 | "version": "0.0.1", 4 | "description": "raster Inky:Bit", 5 | "license": "MIT", 6 | "dependencies": { 7 | "core": "*" 8 | }, 9 | "files": [ 10 | "README.md", 11 | "display.cpp", 12 | "font.cpp", 13 | "shims.d.ts", 14 | "inkybit.ts" 15 | ], 16 | "testFiles": [ 17 | "test.ts" 18 | ], 19 | "public": true 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/makecode.yml: -------------------------------------------------------------------------------- 1 | name: MakeCode 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | node-version: [8.x] 13 | 14 | steps: 15 | - uses: actions/checkout@v1 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - name: npm install 21 | run: | 22 | npm install -g pxt 23 | pxt target microbit 24 | - name: build 25 | run: | 26 | pxt install 27 | pxt build --cloud 28 | env: 29 | CI: true 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Pimoroni Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /test.ts: -------------------------------------------------------------------------------- 1 | // Name tag 2 | inkybit.drawRectangle(0, 0, 249, 121, inkybit.Color.Accent, true) 3 | inkybit.drawRectangle(0, 30, 249, 60, inkybit.Color.White, true) 4 | 5 | inkybit.drawText("Hello, my name is:", 10, 10, inkybit.Color.White, inkybit.TextSize.Regular) 6 | 7 | let size: number = inkybit.measureText("Phillius", inkybit.TextSize.Large) 8 | let x: number = Math.floor((250 - size) / 2) 9 | inkybit.drawText("Phillius", x, 50, inkybit.Color.Black, inkybit.TextSize.Large) 10 | 11 | // Lines 12 | inkybit.drawLine(0, 0, 50, 50, inkybit.Color.Accent) 13 | inkybit.drawLine(50, 0, 0, 50, inkybit.Color.Black) 14 | inkybit.drawLine(0, 0, 0, 50, inkybit.Color.Black) 15 | 16 | inkybit.drawRectangle(55, 5, 50, 50, inkybit.Color.Accent, false) 17 | inkybit.drawRectangle(110, 5, 50, 50, inkybit.Color.Black, true) 18 | 19 | // Variable pixel size 20 | let n:number = 0; 21 | for (n = 0; n < 50; n++){ 22 | inkybit.setPixel(n, n, 1) 23 | } 24 | for (n = 0; n < 50; n++){ 25 | inkybit.setPixel(n, 50-n, 1) 26 | } 27 | 28 | inkybit.setPixelSize(inkybit.PixelSize.Normal) 29 | inkybit.drawText("Ahoy", 1, 1, inkybit.Color.Black, inkybit.TextSize.Regular) 30 | 31 | // Icon tokenization 32 | inkybit.drawText("Hello {Asleep}", 1, 22) 33 | 34 | // Icons 35 | inkybit.drawIcon(IconNames.Heart, 0, 1, inkybit.Color.Accent, inkybit.TextSize.Tiny) 36 | inkybit.drawIcon(IconNames.Heart, 0, 6, inkybit.Color.Accent, inkybit.TextSize.Regular) 37 | inkybit.drawIcon(IconNames.Heart, 0, 41, inkybit.Color.Accent, inkybit.TextSize.Medium) 38 | inkybit.drawIcon(IconNames.Heart, 41, 0, inkybit.Color.Accent, inkybit.TextSize.Large) 39 | 40 | inkybit.clear() 41 | 42 | inkybit.show() 43 | -------------------------------------------------------------------------------- /font.cpp: -------------------------------------------------------------------------------- 1 | #include "pxt.h" 2 | using namespace pxt; 3 | 4 | #ifndef PXT_CREATE_BUFFER 5 | #define PXT_CREATE_BUFFER(data, len) ManagedBuffer(data, len).leakData() 6 | #endif 7 | 8 | namespace inkybit { 9 | //% 10 | int getFontDataByte(int index) { 11 | if(index < 0 || index >= 475){ 12 | return 0; 13 | } 14 | #if MICROBIT_CODAL 15 | auto font = codal::BitmapFont::getSystemFont(); 16 | #else 17 | auto font = MicroBitFont::getSystemFont(); 18 | #endif 19 | return (char)*(font.characters + index); 20 | } 21 | //% 22 | int getCharWidth(int charCode) { 23 | if(charCode < MICROBIT_FONT_ASCII_START || charCode > MICROBIT_FONT_ASCII_END){ 24 | return 5; 25 | } 26 | #if MICROBIT_CODAL 27 | auto font = codal::BitmapFont::getSystemFont(); 28 | #else 29 | auto font = MicroBitFont::getSystemFont(); 30 | #endif 31 | int offset = (charCode - MICROBIT_FONT_ASCII_START) * 5; 32 | uint8_t width = (uint8_t)*(font.characters + offset) 33 | | (uint8_t)*(font.characters + offset + 1) 34 | | (uint8_t)*(font.characters + offset + 2) 35 | | (uint8_t)*(font.characters + offset + 3) 36 | | (uint8_t)*(font.characters + offset + 4); 37 | 38 | if (width & 1) { return 5; } 39 | if (width & 2) { return 4; } 40 | if (width & 4) { return 3; } 41 | return 2; 42 | } 43 | //% 44 | Buffer getFontData(int charCode) { 45 | if(charCode < MICROBIT_FONT_ASCII_START || charCode > MICROBIT_FONT_ASCII_END){ 46 | return PXT_CREATE_BUFFER(NULL, 5); 47 | } 48 | #if MICROBIT_CODAL 49 | auto font = codal::BitmapFont::getSystemFont(); 50 | #else 51 | auto font = MicroBitFont::getSystemFont(); 52 | #endif 53 | int offset = (charCode - MICROBIT_FONT_ASCII_START) * 5; 54 | 55 | return PXT_CREATE_BUFFER((uint8_t *)(font.characters + offset), 5); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /display.cpp: -------------------------------------------------------------------------------- 1 | #include "pxt.h" 2 | #include 3 | using namespace pxt; 4 | 5 | #ifndef PXT_CREATE_BUFFER 6 | #define PXT_CREATE_BUFFER(data, len) ManagedBuffer(data, len).leakData() 7 | #endif 8 | 9 | #define DC uBit.io.P12 // MICROBIT_PIN_P12 10 | #define CS uBit.io.P8 // MICROBIT_PIN_P8 11 | #define RESET uBit.io.P2 // MICROBIT_PIN_P2 12 | #define BUSY uBit.io.P11 // MICROBIT_PIN_P11 13 | 14 | #define DRIVER_CONTROL 0x01 15 | #define GATE_VOLTAGE 0x03 16 | #define SOURCE_VOLTAGE 0x04 17 | #define DISPLAY_CONTROL 0x07 18 | #define NON_OVERLAP 0x0B 19 | #define BOOSTER_SOFT_START 0x0C 20 | #define GATE_SCAN_START 0x0F 21 | #define DEEP_SLEEP 0x10 22 | #define DATA_MODE 0x11 23 | #define SW_RESET 0x12 24 | #define TEMP_WRITE 0x1A 25 | #define TEMP_READ 0x1B 26 | #define TEMP_CONTROL 0x1C 27 | #define TEMP_LOAD 0x1D 28 | #define MASTER_ACTIVATE 0x20 29 | #define DISP_CTRL1 0x21 30 | #define DISP_CTRL2 0x22 31 | #define WRITE_RAM 0x24 32 | #define WRITE_ALTRAM 0x26 33 | #define READ_RAM 0x25 34 | #define VCOM_SENSE 0x28 35 | #define VCOM_DURATION 0x29 36 | #define WRITE_VCOM 0x2C 37 | #define READ_OTP 0x2D 38 | #define WRITE_LUT 0x32 39 | #define WRITE_DUMMY 0x3A 40 | #define WRITE_GATELINE 0x3B 41 | #define WRITE_BORDER 0x3C 42 | #define SET_RAMXPOS 0x44 43 | #define SET_RAMYPOS 0x45 44 | #define SET_RAMXCOUNT 0x4E 45 | #define SET_RAMYCOUNT 0x4F 46 | 47 | #define CS_ACTIVE 0 48 | #define CS_INACTIVE 1 49 | #define DC_DATA 1 50 | #define DC_COMMAND 0 51 | 52 | constexpr uint8_t luts[30] = { 53 | 0x02, 0x02, 0x01, 0x11, 0x12, 0x12, 0x22, 0x22, 0x66, 0x69, 54 | 0x69, 0x59, 0x58, 0x99, 0x99, 0x88, 0x00, 0x00, 0x00, 0x00, 55 | 0xF8, 0xB4, 0x13, 0x51, 0x35, 0x51, 0x51, 0x19, 0x01, 0x00 56 | }; 57 | 58 | constexpr uint8_t WIDTH = 296; 59 | constexpr uint8_t HEIGHT = 128; 60 | 61 | constexpr uint8_t COLS = 136; 62 | constexpr uint8_t ROWS = 250; 63 | constexpr uint8_t OFFSET_X = 0; 64 | constexpr uint8_t OFFSET_Y = 6; 65 | 66 | uint8_t *buf_b; 67 | uint8_t *buf_r; 68 | 69 | SPI spi(MOSI, MISO, SCK); 70 | 71 | bool initialized = false; 72 | 73 | 74 | namespace inkybit { 75 | void busyWait() { 76 | while(BUSY.getDigitalValue()) { 77 | uBit.sleep(50); 78 | } 79 | } 80 | 81 | void spiCommand(uint8_t command, const uint8_t *data, int len) { 82 | CS.setDigitalValue(CS_ACTIVE); 83 | DC.setDigitalValue(DC_COMMAND); 84 | spi.write(command); 85 | if (len > 0) { 86 | DC.setDigitalValue(DC_DATA); 87 | for(auto x = 0; x < len; x++){ 88 | spi.write(data[x]); 89 | } 90 | } 91 | CS.setDigitalValue(CS_INACTIVE); 92 | } 93 | 94 | void spiCommand(uint8_t command) { 95 | spiCommand(command, NULL, 0); 96 | } 97 | 98 | void spiCommand(uint8_t command, std::initializer_list data) { 99 | CS.setDigitalValue(CS_ACTIVE); 100 | DC.setDigitalValue(DC_COMMAND); 101 | spi.write(command); 102 | DC.setDigitalValue(DC_DATA); 103 | for(auto c : data){ 104 | spi.write(c); 105 | } 106 | CS.setDigitalValue(CS_INACTIVE); 107 | } 108 | 109 | void spiData(uint8_t *data, int len) { 110 | CS.setDigitalValue(CS_ACTIVE); 111 | DC.setDigitalValue(DC_DATA); 112 | for(auto x = 0; x < len; x++){ 113 | spi.write(data[x]); 114 | } 115 | CS.setDigitalValue(CS_INACTIVE); 116 | } 117 | 118 | //% 119 | void clear() { 120 | memset(buf_b, 0xff, (COLS / 8) * ROWS); 121 | memset(buf_r, 0x00, (COLS / 8) * ROWS); 122 | } 123 | 124 | //% 125 | void setPixel(int x, int y, int color) { 126 | if(x >= WIDTH) return; 127 | if(y >= HEIGHT) return; 128 | y += OFFSET_Y; 129 | y = COLS - 1 - y; 130 | uint8_t shift = 7 - (y % 8); 131 | y /= 8; 132 | uint16_t offset = (x * (COLS / 8)) + y; 133 | 134 | uint8_t byte_b = buf_b[offset] | (0b1 << shift); 135 | uint8_t byte_r = buf_r[offset] & ~(0b1 << shift); 136 | 137 | if(color == 2) { 138 | byte_r |= 0b1 << shift; 139 | } 140 | if(color == 1) { 141 | byte_b &= ~(0b1 << shift); 142 | } 143 | 144 | buf_b[offset] = byte_b; 145 | buf_r[offset] = byte_r; 146 | } 147 | 148 | //% 149 | void show() { 150 | RESET.setDigitalValue(0); 151 | uBit.sleep(100); 152 | RESET.setDigitalValue(1); 153 | uBit.sleep(100); 154 | 155 | spiCommand(0x12); 156 | uBit.sleep(500); 157 | busyWait(); 158 | 159 | spiCommand(DRIVER_CONTROL, {ROWS - 1, (ROWS - 1) >> 8, 0x00}); 160 | spiCommand(WRITE_DUMMY, {0x1B}); 161 | spiCommand(WRITE_GATELINE, {0x0B}); 162 | spiCommand(DATA_MODE, {0x03}); 163 | spiCommand(SET_RAMXPOS, {0x00, COLS / 8 - 1}); 164 | spiCommand(SET_RAMYPOS, {0x00, 0x00, (ROWS - 1) & 0xFF, (ROWS - 1) >> 8}); 165 | spiCommand(WRITE_VCOM, {0x70}); 166 | spiCommand(WRITE_LUT, luts, sizeof(luts)); 167 | spiCommand(SET_RAMXCOUNT, {0x00}); 168 | spiCommand(SET_RAMYCOUNT, {0x00, 0x00}); 169 | 170 | spiCommand(WRITE_RAM); 171 | spiData(buf_b, (COLS / 8) * ROWS); 172 | spiCommand(WRITE_ALTRAM); 173 | spiData(buf_r, (COLS / 8) * ROWS); 174 | 175 | busyWait(); 176 | spiCommand(MASTER_ACTIVATE); 177 | } 178 | 179 | //% 180 | void init() { 181 | if(initialized) return; 182 | 183 | buf_b = (uint8_t *)malloc((COLS / 8) * ROWS); 184 | buf_r = (uint8_t *)malloc((COLS / 8) * ROWS); 185 | clear(); 186 | 187 | initialized = true; 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # inky:bit 2 | 3 | ![GitHub Workflow Status](https://img.shields.io/github/workflow/status/pimoroni/pxt-inkybit/MakeCode) 4 | 5 | A 250x122 pixel, tri-colour e-ink display for low frequency, high visibility status display. 6 | 7 | This extension adds support for the Pimoroni inky:bit to makecode.microbit.org. 8 | 9 | A inky:bit is required to use this extension, grab yours here: https://shop.pimoroni.com/products/inky-bit 10 | 11 | To use this extension, go to https://makecode.microbit.org/, click "Advanced" then "Add Package" and search for inky:bit. 12 | 13 | ## JavaScript Reference 14 | 15 | inky:bit has a 250x122 pixel display, each pixel can be set to white, black or red. 16 | 17 | ### Text Size 18 | 19 | There are four text sizes available, all using the built-in 5x5px micro:bit font: 20 | 21 | 1. `TextSize.Regular` - 2x - each font pixel is 2x2 real pixels 22 | 2. `TextSize.Tiny` - 1x - each font pixel is a single pixel 23 | 3. `TextSize.Medium` - 3x - each font pixel is 3x3 real pixels 24 | 4. `TextSize.Large` - 4x - each font pixel is 4x4 real pixels 25 | 26 | ### Get the size of your inky:bit 27 | 28 | The blocks `width` and `height` return the width and height of inky:bit in pixels respectively. 29 | 30 | Use these in your loops and flow control to draw amazing things! 31 | 32 | ### Set a single pixel 33 | 34 | Set a single pixel on inky:bit. Note, you must call `show` to display your changes. 35 | 36 | * `x` is the x position, from 0-295 37 | * `y` is the y position, from 0-127 38 | * `color` is the color, one of `inkybit.Color.White`, `inkybit.Color.Black`, `inkybit.Color.Accent` 39 | 40 | ``` 41 | inkybit.setPixel(x: number, y: number, color: number) 42 | ``` 43 | 44 | For example: 45 | 46 | ``` 47 | inkybit.setPixel(5, 5, inkybit.Color.Black) 48 | ``` 49 | 50 | ### Display your changes 51 | 52 | When you've finished setting pixels and drawing text, you must call `show` to display your changes. 53 | 54 | ``` 55 | inkybit.show() 56 | ``` 57 | 58 | ### Clear the display 59 | 60 | To clear the display, you can call `clear`, you must also call `show` if you want to display your changes. 61 | 62 | ``` 63 | inkybit.clear() 64 | inkybit.show() 65 | ``` 66 | 67 | ### Display an image 68 | 69 | To show an image on inky:bit, use `drawImage`: 70 | 71 | * `image` is the micro:bit image you want to display 72 | * `x` is the x position, from 0-295 73 | * `y` is the y position, from 0-127 74 | * `color` is the color, one of `inkybit.Color.White`, `inkybit.Color.Black`, `inkybit.Color.Accent` 75 | * `size` is the text size, one of `inkybit.TextSize.Tiny`, `inkybit.TextSize.Regular`, `inkybit.TextSize.Medium`, `inkybit.TextSize.Large` 76 | 77 | ``` 78 | inkybit.drawImage(image: Image, x: number, y: number, color: Color, size: TextSize) 79 | ``` 80 | 81 | ### Display a text string 82 | 83 | To show a string of text on inky:bit you should use `drawText`: 84 | 85 | * `text` is the text you want to show 86 | * `x` is the x position, from 0-295 87 | * `y` is the y position, from 0-127 88 | * `color` is the color, one of `inkybit.Color.White`, `inkybit.Color.Black`, `inkybit.Color.Accent` 89 | * `size` is the text size, one of `inkybit.TextSize.Tiny`, `inkybit.TextSize.Regular`, `inkybit.TextSize.Medium`, `inkybit.TextSize.Large` 90 | 91 | ``` 92 | inkybit.drawText(text: string, x: number, y: number, color: Color, size: TextSize) 93 | ``` 94 | 95 | For example: 96 | 97 | ``` 98 | inkybit.drawText(0, 1, "Hello World") 99 | ``` 100 | 101 | ### Measure a text string 102 | 103 | It can be useful to know how long a string of text might be, in pixels, on inky:bit. Use `measureText` to find out: 104 | 105 | ``` 106 | inkybit.measureText(text: string, size: TextSize) 107 | ``` 108 | 109 | For example: 110 | 111 | ``` 112 | let width: number = inkybit.measureText("Hello World") 113 | ``` 114 | 115 | This will return a number of pixels corresponding to the length of the text as it's displayed on inky:bit (using the built-in 5x5 micro:bit font at the size you specify). 116 | 117 | ### Icons & Arrows 118 | 119 | You can use icons and arrows in your text, just place their name in curly brackets like so: `"Hello {Heart} World"` or: `"Boo! Went the {Ghost}"` or: `"{Heart}{SmallHeart}{Heart} Happy Birthday! {Heart}{SmallHeart}{Heart}"` 120 | 121 | Here's a list of icons you can use: 122 | 123 | * Heart 124 | * SmallHeart 125 | * Yes 126 | * No 127 | * Happy 128 | * Sad 129 | * Confused 130 | * Angry 131 | * Asleep 132 | * Surprised 133 | * Silly 134 | * Fabulous 135 | * Meh 136 | * TShirt 137 | * Rollerskate 138 | * Duck 139 | * House 140 | * Tortoise 141 | * Butterfly 142 | * StickFigure 143 | * Ghost 144 | * Sword 145 | * Giraffe 146 | * Skull 147 | * Umbrella 148 | * Snake 149 | * Rabbit 150 | * Cow 151 | * QuarterNote 152 | * EigthNote 153 | * Pitchfork 154 | * Target 155 | * Triangle 156 | * LeftTriangle 157 | * Chessboard 158 | * Diamond 159 | * SmallDiamond 160 | * Square 161 | * SmallSquare 162 | * Scissors 163 | * North 164 | * NorthEast 165 | * East 166 | * SouthEast 167 | * South 168 | * SouthWest 169 | * West 170 | * NorthWest 171 | 172 | ## License 173 | 174 | MIT License 175 | 176 | Copyright (c) 2020 Pimoroni Ltd. 177 | 178 | Permission is hereby granted, free of charge, to any person obtaining a copy 179 | of this software and associated documentation files (the "Software"), to deal 180 | in the Software without restriction, including without limitation the rights 181 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 182 | copies of the Software, and to permit persons to whom the Software is 183 | furnished to do so, subject to the following conditions: 184 | 185 | The above copyright notice and this permission notice shall be included in all 186 | copies or substantial portions of the Software. 187 | 188 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 189 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 190 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 191 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 192 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 193 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 194 | SOFTWARE. 195 | 196 | ## Supported targets 197 | 198 | * for PXT/microbit 199 | 200 | ```package 201 | inkybit=github:raster/pxt-inkybit 202 | ``` 203 | -------------------------------------------------------------------------------- /inkybit.ts: -------------------------------------------------------------------------------- 1 | //% weight=100 color=#000000 icon="\uf043" block="Inky:Bit" 2 | namespace inkybit { 3 | 4 | const WIDTH: number = 296 5 | const HEIGHT: number = 128 6 | 7 | let UPSIDE_DOWN: boolean = false 8 | 9 | const ARROWOFFSET: number = 40 10 | 11 | const ICONS: string[] = [ 12 | "Heart", 13 | "SmallH", 14 | "Yes", 15 | "No", 16 | "Happy", 17 | "Sad", 18 | "Confus", 19 | "Angry", 20 | "Asleep", 21 | "Surpri", 22 | "Silly", 23 | "Fabulo", 24 | "Meh", 25 | "TShirt", 26 | "Roller", 27 | "Duck", 28 | "House", 29 | "Tortoi", 30 | "Butter", 31 | "StickF", 32 | "Ghost", 33 | "Sword", 34 | "Giraff", 35 | "Skull", 36 | "Umbrel", 37 | "Snake", 38 | "Rabbit", 39 | "Cow", 40 | "Quarte", 41 | "EigthN", 42 | "Pitchf", 43 | "Target", 44 | "Triang", 45 | "LeftTr", 46 | "Chessb", 47 | "Diamon", 48 | "SmallD", 49 | "Square", 50 | "SmallS", 51 | "Scisso", 52 | 53 | "North", 54 | "NorthE", 55 | "East", 56 | "SouthE", 57 | "South", 58 | "SouthW", 59 | "West", 60 | "NorthW" 61 | ] 62 | 63 | export enum Color { 64 | //% block="black" defl=True 65 | Black = 1, 66 | //% block="white" 67 | White = 0, 68 | //% block="accent" 69 | Accent = 2 70 | } 71 | 72 | export enum PixelSize { 73 | //% block="normal (x1)" defl=True 74 | Normal = 1, 75 | //% block="double (x2)" 76 | Double = 2, 77 | //% block="triple (x3)" 78 | Triple = 3, 79 | //% block="quad (x4)" 80 | Quad = 4 81 | } 82 | 83 | export enum TextSize { 84 | //% block="regular (x2)" defl=True 85 | Regular = 2, 86 | //% block="tiny (x1)" 87 | Tiny = 1, 88 | //% block="medium (x3)" 89 | Medium = 3, 90 | //% block="large (x4)" 91 | Large = 4 92 | } 93 | 94 | let _pixelSize: number = 1 95 | 96 | function tokenize(text: string): string { 97 | let result: string = "" 98 | let icon: string = "" 99 | 100 | for (let x = 0; x < text.length; x++){ 101 | let char: string = text.charAt(x) 102 | if (char == "}" && icon.length > 0) { 103 | let index: number = ICONS.indexOf(icon.substr(1,6)) 104 | icon += char 105 | 106 | if (index > -1) { 107 | icon = String.fromCharCode(DAL.MICROBIT_FONT_ASCII_END + 1 + index) 108 | } 109 | 110 | result += icon 111 | icon = "" 112 | continue 113 | } 114 | if (char == "{" || icon.length > 0) { 115 | icon += char 116 | continue 117 | } 118 | result += char 119 | } 120 | 121 | return result 122 | } 123 | 124 | /** 125 | * Display an icon on inky:bit 126 | * @param icon - icon to display 127 | * @param x - x position (0-295) 128 | * @param y - y position (0-127) 129 | * @param color - color to set (0-2) 130 | */ 131 | /* 132 | // blockId=inkybit_draw_icon 133 | // block="draw A icon %icon| at x %x| y %y| with color %color| and size %size" 134 | // icon.fieldEditor="gridpicker" 135 | // icon.fieldOptions.width="400" icon.fieldOptions.columns="5" 136 | // icon.fieldOptions.itemColour="black" icon.fieldOptions.tooltips="true" 137 | // x.min=0 x.max=295 138 | // y.min=0 y.max=127 139 | */ 140 | export function drawIcon(icon: IconNames, x: number, y: number, color: Color = Color.Black, size: TextSize = TextSize.Regular): void { 141 | let image: Image = images.iconImage(icon) 142 | drawImage(image, x, y, color, size) 143 | } 144 | 145 | /** 146 | * Display an arrow on inky:bit 147 | * @param arrow - arrow to display 148 | * @param x - x position (0-295) 149 | * @param y - y position (0-127) 150 | * @param color - color to set (0-2) 151 | */ 152 | /* 153 | // blockId=inkybit_draw_arrow 154 | // block="draw A arrow %arrow| at x %x| y %y| with color %color| and size %size" 155 | // x.min=0 x.max=295 156 | // y.min=0 y.max=127 157 | */ 158 | export function drawArrow(arrow: ArrowNames, x: number, y: number, color: Color = Color.Black, size: TextSize = TextSize.Regular): void { 159 | let image: Image = images.arrowImage(arrow) 160 | drawImage(image, x, y, color, size) 161 | } 162 | 163 | /** 164 | * Draw an image on inky:bit 165 | * @param image - image to display 166 | * @param x - x position (0-295) 167 | * @param y - y position (0-127) 168 | * @param color - color to set (0-2) 169 | */ 170 | //% blockId=inkybit_draw_image 171 | //% block="draw A image %image| at x %x| y %y| with color %color| and size %size" 172 | //% x.min=0 x.max=295 173 | //% y.min=0 y.max=127 174 | export function drawImage(image: Image, x: number, y: number, color: Color = Color.Black, size: TextSize = TextSize.Regular): void { 175 | let rows: number = 5 * size 176 | let cols: number = image.width() * size 177 | for (let c_row = 0; c_row < rows; c_row++) { 178 | let s_row: number = Math.floor(c_row / size) 179 | for (let c_col = 0; c_col < cols; c_col++) { 180 | let s_col: number = Math.floor(c_col / size) 181 | if (image.pixelBrightness(s_col, s_row)) { 182 | setPixel(x + c_col, y + c_row, color) 183 | } 184 | } 185 | } 186 | } 187 | 188 | /** 189 | * Set an pixel on inky:bit 190 | * @param x - x position (0-295) 191 | * @param y - y position (0-127) 192 | * @param color - color to set (0-2) 193 | */ 194 | //% blockId=inkybit_set_pixel 195 | //% block="set pixel at x %x| y %y| with color %color" 196 | //% x.min=0 x.max=295 197 | //% y.min=0 y.max=127 198 | export function setPixel(x: number, y: number, color: Color = Color.Black): void { 199 | x *= _pixelSize 200 | y *= _pixelSize 201 | let c: number = color 202 | let px: number = 0 203 | let py: number = 0 204 | for(py = 0; py < _pixelSize; py++) { 205 | for(px = 0; px < _pixelSize; px++) { 206 | _setPixel(x + px, y + py, c) 207 | } 208 | } 209 | return 210 | } 211 | 212 | /** 213 | * Draw a rect on inky:bit 214 | * @param x - x position (0-295) 215 | * @param y - y position (0-127) 216 | * @param color - color to set (0-2) 217 | * @param filled - whether to fill the rect with color 218 | */ 219 | //% blockId=inkybit_draw_rectangle 220 | //% block="draw A rect at x %x| y %y| width %width| height %height| color %color| filled %filled" 221 | //% x.min=0 x.max=295 222 | //% y.min=0 y.max=127 223 | export function drawRectangle(x: number, y: number, width: number, height: number, color: Color = Color.Black, filled: Boolean = false): void { 224 | let c: number = color 225 | let px: number = 0 226 | let py: number = 0 227 | /* 228 | x, y x+w, y 229 | 230 | x, y+h x+w, y+h 231 | */ 232 | drawLine(x, y, x + width, y, c) 233 | drawLine(x, y, x, y + height, c) 234 | drawLine(x + width, y, x + width, y + height, c) 235 | drawLine(x, y + height, x + width, y + height, c) 236 | 237 | if(filled) { 238 | x += 1 239 | y += 1 240 | width -= 2 241 | height -= 2 242 | for(py = y; py <= y + height; py++) { 243 | for(px = x; px <= x + width; px++) { 244 | _setPixel(px, py, c) 245 | } 246 | } 247 | } 248 | } 249 | 250 | /** 251 | * Draw a line on inky:bit 252 | * @param x0 - start x position (0-295) 253 | * @param y0 - start y position (0-127) 254 | * @param x1 - end x position (0-295) 255 | * @param y1 - end y position (0-127) 256 | * @param color - color to set (0-2) 257 | */ 258 | //% blockId=inkybit_draw_line 259 | //% block="draw A line from x %x0 y %y0| to x %x1 y %y1| color %color" 260 | //% x0.min=0 x0.max=295 261 | //% y0.min=0 y0.max=127 262 | //% x1.min=0 x1.max=295 263 | //% y1.min=0 y1.max=127 264 | export function drawLine(x0: number, y0: number, x1: number, y1: number, color: Color = Color.Black): void { 265 | let c: number = color 266 | let dx: number = Math.abs(x1 - x0) 267 | let sx: number = x0 < x1 ? 1 : -1 268 | let dy: number = -Math.abs(y1 - y0) 269 | let sy: number = y0 < y1 ? 1 : -1 270 | 271 | let err: number = dx + dy; /* error value e_xy */ 272 | while (true) { /* loop */ 273 | _setPixel(x0, y0, c) 274 | if (x0 == x1 && y0 == y1) break; 275 | let e2: number = 2 * err; 276 | if (e2 >= dy) { /* e_xy+e_x > 0 */ 277 | err += dy; 278 | x0 += sx; 279 | } 280 | if (e2 <= dx) { /* e_xy+e_y < 0 */ 281 | err += dx; 282 | y0 += sy; 283 | } 284 | } 285 | } 286 | 287 | /** 288 | * Set scroll:bit pixel size 289 | * @param size - pixel size (1 to 4) 290 | */ 291 | //% blockId=inkybit_set_pixel_size 292 | //% block="set pixel size to %size" 293 | //% advanced color=#220000 294 | //% size.defl=1 295 | export function setPixelSize(size: PixelSize = PixelSize.Normal): void { 296 | _pixelSize = size 297 | } 298 | 299 | /** 300 | * Get scroll:bit pixel size 301 | */ 302 | //% blockId=inkybit_get_pixel_size 303 | //% block="get pixel size" 304 | //% advanced color=#220000 305 | export function getPixelSize(): PixelSize { 306 | return _pixelSize 307 | } 308 | 309 | /** 310 | * Measure text, returns a length in pixels 311 | * @param text - text to measure 312 | */ 313 | //% blockId=inkybit_measure_text 314 | //% block="get length of %text in pixels| at size %size" 315 | //% advanced color=#554444 316 | export function measureText(text: string, size: TextSize = TextSize.Regular): number { 317 | let len: number = 0 318 | for (let x: number = 0; x < text.length; x++){ 319 | len += charWidth(text.charAt(x), size) + (1 * size) 320 | } 321 | return len 322 | } 323 | 324 | /** 325 | * Draw a single alphanumeric character. 326 | * @param char - character to display 327 | * @param x - x position (0-295) 328 | * @param y - y position (0-127) 329 | * @param color - color to set (0-2) 330 | */ 331 | export function drawChar(char: string, x: number, y: number, color: Color = Color.Black, size: TextSize = TextSize.Regular): void { 332 | let rows: number = 5 * size 333 | let cols: number = 5 * size 334 | 335 | if (char.charCodeAt(0) > DAL.MICROBIT_FONT_ASCII_END + ARROWOFFSET) { 336 | drawArrow(char.charCodeAt(0) - DAL.MICROBIT_FONT_ASCII_END - ARROWOFFSET - 1, x, y, color, size) 337 | return 338 | } 339 | if (char.charCodeAt(0) > DAL.MICROBIT_FONT_ASCII_END) { 340 | drawIcon(char.charCodeAt(0) - DAL.MICROBIT_FONT_ASCII_END - 1, x, y, color, size) 341 | return 342 | } 343 | let data: Buffer = getChar(char) 344 | for (let c_row = 0; c_row < rows; c_row++) { 345 | let s_row: number = Math.floor(c_row / size) 346 | for (let c_col = 0; c_col < cols; c_col++) { 347 | let s_col: number = Math.floor(c_col / size) 348 | if ((data[s_row] & (1 << (4 - s_col))) > 0) { 349 | setPixel(x + c_col, y + c_row, color) 350 | } 351 | } 352 | } 353 | } 354 | 355 | /** 356 | * Draw text on scroll:bit 357 | * @param col - column to set (0-16) 358 | * @param row - row to set (0-6) 359 | * @param text - text to show 360 | * @param brightness - brightness to set (0-255) 361 | */ 362 | //% blockId=inkybit_draw_text 363 | //% block="draw A text %text| at x %x| y %y| with color %color| and size %size" 364 | //% x.min=0 x.max=295 365 | //% y.min=0 y.max=127 366 | export function drawText(text: string, x: number, y: number, color: Color = Color.Black, size: TextSize = TextSize.Regular): void { 367 | text = tokenize(text) 368 | _drawText(text, x, y, color, size) 369 | } 370 | export function _drawText(text: string, x: number, y: number, color: Color = Color.Black, size: TextSize = TextSize.Regular): void { 371 | let o_x: number = x 372 | for (let char_index: number = 0; char_index < text.length; char_index++){ 373 | let width: number = charWidth(text.charAt(char_index), size) 374 | if ((x + width) * _pixelSize >= WIDTH) { 375 | y += 6 * size // New line, 5px tall + 1px gap 376 | x = o_x 377 | } 378 | drawChar(text.charAt(char_index), x, y, color, size) 379 | x += width + (1 * size) // 1px space between chars 380 | } 381 | } 382 | 383 | /** 384 | * Return the width of inky:bit 385 | */ 386 | //% blockId=scrollbit_cols 387 | //% block="width" 388 | //% color=#444444 389 | //% icon="" 390 | export function width(): number { 391 | return WIDTH 392 | } 393 | 394 | /** 395 | * Return the height of inky:bit 396 | */ 397 | //% blockId=inkybit_height 398 | //% block="height" 399 | //% color=#444444 400 | //% icon="" 401 | export function height(): number { 402 | return HEIGHT 403 | } 404 | 405 | /** 406 | * Update inky:bit, 407 | * update the e-ink display with your pretty pixels 408 | */ 409 | //% blockId=inkybit_show 410 | //% block="display the changes" 411 | export function show() { 412 | _show() 413 | } 414 | 415 | export function init() { 416 | _init() 417 | } 418 | 419 | //% shim=inkybit::show 420 | function _show(): void { 421 | return 422 | } 423 | 424 | //% shim=inkybit::clear 425 | function _clear(): void { 426 | return 427 | } 428 | 429 | //% shim=inkybit::setPixel 430 | function _setPixel(x: number, y: number, color: number): void { 431 | return 432 | } 433 | 434 | //% shim=inkybit::init 435 | function _init(): void { 436 | return 437 | } 438 | 439 | // Font bindings 440 | 441 | //% shim=inkybit::getFontDataByte 442 | function getFontDataByte(index: number): number { 443 | return 0 444 | } 445 | 446 | //% shim=inkybit::getFontData 447 | function getFontData(index: number): Buffer { 448 | return pins.createBuffer(5) 449 | } 450 | 451 | //% shim=inkybit::getCharWidth 452 | function getCharWidth(char: number): number { 453 | return 5 454 | } 455 | 456 | function getChar(character: string): Buffer { 457 | return getFontData(character.charCodeAt(0)) 458 | } 459 | 460 | function charWidth(character: string, size: TextSize = TextSize.Regular): number { 461 | let charcode: number = character.charCodeAt(0) 462 | if (charcode > DAL.MICROBIT_FONT_ASCII_END) { 463 | return 5 * size 464 | } 465 | return getCharWidth(charcode) * size 466 | } 467 | } 468 | 469 | inkybit.init() 470 | --------------------------------------------------------------------------------