yoneda преди 1 година
ревизия
b1ca1e3507
променени са 15 файла, в които са добавени 816 реда и са изтрити 0 реда
  1. 5 0
      .gitignore
  2. 10 0
      .vscode/extensions.json
  3. 39 0
      include/README
  4. 5 0
      include/pico/config_autogen.h
  5. 18 0
      include/tusb_config.h
  6. 27 0
      include/vfs_config.h
  7. 46 0
      lib/README
  8. 25 0
      platformio.ini
  9. 73 0
      src/display.cpp
  10. 46 0
      src/display.h
  11. 145 0
      src/font8x8.h
  12. 135 0
      src/main.cpp
  13. 196 0
      src/sd1306.cpp
  14. 35 0
      src/sd1306.h
  15. 11 0
      test/README

+ 5 - 0
.gitignore

@@ -0,0 +1,5 @@
+.pio
+.vscode/.browse.c_cpp.db*
+.vscode/c_cpp_properties.json
+.vscode/launch.json
+.vscode/ipch

+ 10 - 0
.vscode/extensions.json

@@ -0,0 +1,10 @@
+{
+    // See http://go.microsoft.com/fwlink/?LinkId=827846
+    // for the documentation about the extensions.json format
+    "recommendations": [
+        "platformio.platformio-ide"
+    ],
+    "unwantedRecommendations": [
+        "ms-vscode.cpptools-extension-pack"
+    ]
+}

+ 39 - 0
include/README

@@ -0,0 +1,39 @@
+
+This directory is intended for project header files.
+
+A header file is a file containing C declarations and macro definitions
+to be shared between several project source files. You request the use of a
+header file in your project source file (C, C++, etc) located in `src` folder
+by including it, with the C preprocessing directive `#include'.
+
+```src/main.c
+
+#include "header.h"
+
+int main (void)
+{
+ ...
+}
+```
+
+Including a header file produces the same results as copying the header file
+into each source file that needs it. Such copying would be time-consuming
+and error-prone. With a header file, the related declarations appear
+in only one place. If they need to be changed, they can be changed in one
+place, and programs that include the header file will automatically use the
+new version when next recompiled. The header file eliminates the labor of
+finding and changing all the copies as well as the risk that a failure to
+find one copy will result in inconsistencies within a program.
+
+In C, the usual convention is to give header files names that end with `.h'.
+It is most portable to use only letters, digits, dashes, and underscores in
+header file names, and at most one dot.
+
+Read more about using header files in official GCC documentation:
+
+* Include Syntax
+* Include Operation
+* Once-Only Headers
+* Computed Includes
+
+https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

+ 5 - 0
include/pico/config_autogen.h

@@ -0,0 +1,5 @@
+
+/* SELECT BOARD */
+#include "boards/pico.h" 
+
+

+ 18 - 0
include/tusb_config.h

@@ -0,0 +1,18 @@
+/*
+    default config for printf
+ */
+
+#ifndef _PICO_STDIO_USB_TUSB_CONFIG_H
+#define _PICO_STDIO_USB_TUSB_CONFIG_H
+
+#include "pico/stdio_usb.h"
+
+#define CFG_TUSB_RHPORT0_MODE   (OPT_MODE_DEVICE)
+
+#define CFG_TUD_CDC             (1)
+#define CFG_TUD_CDC_RX_BUFSIZE  (256)
+#define CFG_TUD_CDC_TX_BUFSIZE  (256)
+
+// We use a vendor specific interface but with our own driver
+#define CFG_TUD_VENDOR            (0)
+#endif

+ 27 - 0
include/vfs_config.h

@@ -0,0 +1,27 @@
+/* 
+
+WIKI:
+        https://github.com/Wiz-IO/wizio-pico/wiki/ARDUINO#vfs--file-system
+
+OTHER USER CONFIG KEYS
+
+LFS:    https://github.com/littlefs-project/littlefs   
+
+        https://github.com/Wiz-IO/framework-wizio-pico/blob/main/arduino/libraries/RP2040/VFS/VFS_LFS.h
+
+FATFS:  http://elm-chan.org/fsw/ff/00index_e.html
+
+        https://github.com/Wiz-IO/framework-wizio-pico/blob/main/arduino/libraries/RP2040/VFS/VFS_FATFS.h
+
+*/
+
+#define MAX_OPEN_FILES  4
+
+#define USE_FATFS       /* Enable FatFS                     0:/file_path */
+#define FATFS_SPI       spi1
+#define FATFS_SPI_BRG           10000000
+#define FATFS_SPI_SCK           10 /* SPI1_SCK  */
+#define FATFS_SPI_MOSI          11 /* SPI1_TX   */
+#define FATFS_SPI_MISO          12 /* SPI1_RX   */
+#define FATFS_CS_PIN            13 /* SPI1_CSn  */
+

+ 46 - 0
lib/README

@@ -0,0 +1,46 @@
+
+This directory is intended for project specific (private) libraries.
+PlatformIO will compile them to static libraries and link into executable file.
+
+The source code of each library should be placed in a an own separate directory
+("lib/your_library_name/[here are source files]").
+
+For example, see a structure of the following two libraries `Foo` and `Bar`:
+
+|--lib
+|  |
+|  |--Bar
+|  |  |--docs
+|  |  |--examples
+|  |  |--src
+|  |     |- Bar.c
+|  |     |- Bar.h
+|  |  |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
+|  |
+|  |--Foo
+|  |  |- Foo.c
+|  |  |- Foo.h
+|  |
+|  |- README --> THIS FILE
+|
+|- platformio.ini
+|--src
+   |- main.c
+
+and a contents of `src/main.c`:
+```
+#include <Foo.h>
+#include <Bar.h>
+
+int main (void)
+{
+  ...
+}
+
+```
+
+PlatformIO Library Dependency Finder will find automatically dependent
+libraries scanning project source files.
+
+More information about PlatformIO Library Dependency Finder
+- https://docs.platformio.org/page/librarymanager/ldf.html

+ 25 - 0
platformio.ini

@@ -0,0 +1,25 @@
+; PlatformIO Project Configuration File
+;
+;   Build options: build flags, source filter
+;   Upload options: custom upload port, speed and extra flags
+;   Library options: dependencies, extra library storages
+;   Advanced options: extra scripting
+;
+; Please visit documentation for the other options and examples
+; https://docs.platformio.org/page/projectconf.html
+
+[env:raspberry-pi-pico]
+platform = wizio-pico
+board = raspberry-pi-pico
+framework = baremetal
+
+upload_protocol = picoprobe
+debug_tool = picoprobe
+;monitor_port = SERIAL_PORT
+;monitor_speed = 115200
+
+board_build.nano = disable
+
+;lib_deps = 
+
+;build_flags = 

+ 73 - 0
src/display.cpp

@@ -0,0 +1,73 @@
+#include "sd1306.h"
+#include "display.h"
+#include <stdlib.h>
+#include "pico/stdlib.h"
+#include "pico/binary_info.h"
+#include "hardware/i2c.h"
+#include "pico/util/queue.h"
+#include "pico/multicore.h"
+
+#include "font8x8.h"
+
+
+
+
+
+uint8_t display_buffer[OLED_BUF_LEN];
+
+void display_putchar(char c, uint8_t col_x, uint8_t col_y, bool reverse)
+{
+    uint32_t offset = col_x * FONT_WIDTH + OLED_WIDTH * col_y + FONT_WIDTH;
+
+    if( c < 0 ) return;
+    if(col_x > 15 || col_y > 3) return;
+    // フォントの縦横変換
+    for(int i = 0; i < 8 ; i++ ) {
+        volatile uint8_t f;
+        f  = ((font8x8[(int)c][reverse ? 0 : 7] << (reverse ? 7 - i : i)) & 0x80) >> 0;
+        f |= ((font8x8[(int)c][reverse ? 1 : 6] << (reverse ? 7 - i : i)) & 0x80) >> 1;
+        f |= ((font8x8[(int)c][reverse ? 2 : 5] << (reverse ? 7 - i : i)) & 0x80) >> 2;
+        f |= ((font8x8[(int)c][reverse ? 3 : 4] << (reverse ? 7 - i : i)) & 0x80) >> 3;
+        f |= ((font8x8[(int)c][reverse ? 4 : 3] << (reverse ? 7 - i : i)) & 0x80) >> 4;
+        f |= ((font8x8[(int)c][reverse ? 5 : 2] << (reverse ? 7 - i : i)) & 0x80) >> 5;
+        f |= ((font8x8[(int)c][reverse ? 6 : 1] << (reverse ? 7 - i : i)) & 0x80) >> 6;
+        f |= ((font8x8[(int)c][reverse ? 7 : 0] << (reverse ? 7 - i : i)) & 0x80) >> 7;
+        display_buffer[offset - i] = f;
+    }
+}
+
+void display_putstr(char *str, uint8_t col_x, uint8_t col_y, bool reverse)
+{
+    int i = 0;
+    while(str[i] != '\0') {
+        display_putchar(str[i++], col_x++, col_y, reverse);
+    }
+}
+
+
+void display(void)
+{
+    // I2C初期化
+    i2c_init(i2c_default, 800 * 1000);
+    // I2Cピン初期化
+    gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C);
+    gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C);
+    gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN);
+    gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN);
+
+    // OLED初期化
+    oled_init();
+
+    struct render_area frame_area = {start_col: 0, 
+                                    end_col : OLED_WIDTH - 1, 
+                                    start_page : 0, 
+                                    end_page : OLED_NUM_PAGES - 1, 
+                                    buflen : 0 }; 
+    calc_render_area_buflen(&frame_area);
+    // ディスプレイクリア
+    fill(display_buffer, 0x0);
+    while(1) {
+        render(display_buffer, &frame_area);
+        sleep_us(6*1000);
+    }
+}

+ 46 - 0
src/display.h

@@ -0,0 +1,46 @@
+#ifndef _DISPLAY_H_
+#define _DISPLAY_H_
+
+#include <stdlib.h>
+
+typedef enum {
+    GCMD_CLEAR,
+    GCMD_HSCROLL,
+    GCMD_VSCROLL,
+    GCMD_BITBLT,
+    GCMD_DRAWBITMAP,
+    GCMD_SETPIXEL,
+    GCMD_DRAWLINE,
+    GCMD_DRAWCIRCLE,
+    GCMD_DRAWRECT,
+    GCMD_PUTCHR,
+    GCMD_PUTSTR,
+} gcmd;
+
+typedef struct {
+    uint8_t x;
+    uint8_t y;
+} coord;
+
+typedef struct {
+    uint8_t left, right, top, bottom;
+} rect;
+
+typedef struct {
+    uint8_t width, height;
+    uint8_t data[1];
+} gbmp;
+
+typedef struct {
+    gcmd c;
+    void *params;
+} gparam;
+
+
+
+extern uint8_t display_buffer[];
+void display(void);
+void display_putchar(char c, uint8_t col_x, uint8_t col_y, bool reverse);
+void display_putstr(char *str, uint8_t col_x, uint8_t col_y, bool reverse);
+
+#endif

+ 145 - 0
src/font8x8.h

@@ -0,0 +1,145 @@
+#ifndef _FONT8X8_H
+#define _FONT8X8_H
+
+#include "pico/stdlib.h"
+
+#define FONT_HEIGHT _u(8)
+#define FONT_WIDTH  _u(8)
+
+// 8x8 font
+// https://github.com/dhepper/font8x8
+// Author: Daniel Hepper <daniel@hepper.net>
+// License: Public Domain
+static uint8_t font8x8[128][8] = {
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0000 (nul)
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0001
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0002
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0003
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0004
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0005
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0006
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0007
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0008
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0009
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+000A
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+000B
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+000C
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+000D
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+000E
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+000F
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0010
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0011
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0012
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0013
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0014
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0015
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0016
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0017
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0018
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0019
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+001A
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+001B
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+001C
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+001D
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+001E
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+001F
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0020 (space)
+    { 0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00},   // U+0021 (!)
+    { 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0022 (")
+    { 0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00},   // U+0023 (#)
+    { 0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00},   // U+0024 ($)
+    { 0x00, 0x63, 0x33, 0x18, 0x0C, 0x66, 0x63, 0x00},   // U+0025 (%)
+    { 0x1C, 0x36, 0x1C, 0x6E, 0x3B, 0x33, 0x6E, 0x00},   // U+0026 (&)
+    { 0x06, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0027 (')
+    { 0x18, 0x0C, 0x06, 0x06, 0x06, 0x0C, 0x18, 0x00},   // U+0028 (()
+    { 0x06, 0x0C, 0x18, 0x18, 0x18, 0x0C, 0x06, 0x00},   // U+0029 ())
+    { 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00},   // U+002A (*)
+    { 0x00, 0x0C, 0x0C, 0x3F, 0x0C, 0x0C, 0x00, 0x00},   // U+002B (+)
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x06},   // U+002C (,)
+    { 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00},   // U+002D (-)
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00},   // U+002E (.)
+    { 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00},   // U+002F (/)
+    { 0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00},   // U+0030 (0)
+    { 0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00},   // U+0031 (1)
+    { 0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00},   // U+0032 (2)
+    { 0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00},   // U+0033 (3)
+    { 0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00},   // U+0034 (4)
+    { 0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00},   // U+0035 (5)
+    { 0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00},   // U+0036 (6)
+    { 0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00},   // U+0037 (7)
+    { 0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00},   // U+0038 (8)
+    { 0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00},   // U+0039 (9)
+    { 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00},   // U+003A (:)
+    { 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x06},   // U+003B (;)
+    { 0x18, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x18, 0x00},   // U+003C (<)
+    { 0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00},   // U+003D (=)
+    { 0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00},   // U+003E (>)
+    { 0x1E, 0x33, 0x30, 0x18, 0x0C, 0x00, 0x0C, 0x00},   // U+003F (?)
+    { 0x3E, 0x63, 0x7B, 0x7B, 0x7B, 0x03, 0x1E, 0x00},   // U+0040 (@)
+    { 0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00},   // U+0041 (A)
+    { 0x3F, 0x66, 0x66, 0x3E, 0x66, 0x66, 0x3F, 0x00},   // U+0042 (B)
+    { 0x3C, 0x66, 0x03, 0x03, 0x03, 0x66, 0x3C, 0x00},   // U+0043 (C)
+    { 0x1F, 0x36, 0x66, 0x66, 0x66, 0x36, 0x1F, 0x00},   // U+0044 (D)
+    { 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x46, 0x7F, 0x00},   // U+0045 (E)
+    { 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x0F, 0x00},   // U+0046 (F)
+    { 0x3C, 0x66, 0x03, 0x03, 0x73, 0x66, 0x7C, 0x00},   // U+0047 (G)
+    { 0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00},   // U+0048 (H)
+    { 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00},   // U+0049 (I)
+    { 0x78, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E, 0x00},   // U+004A (J)
+    { 0x67, 0x66, 0x36, 0x1E, 0x36, 0x66, 0x67, 0x00},   // U+004B (K)
+    { 0x0F, 0x06, 0x06, 0x06, 0x46, 0x66, 0x7F, 0x00},   // U+004C (L)
+    { 0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x00},   // U+004D (M)
+    { 0x63, 0x67, 0x6F, 0x7B, 0x73, 0x63, 0x63, 0x00},   // U+004E (N)
+    { 0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00},   // U+004F (O)
+    { 0x3F, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0F, 0x00},   // U+0050 (P)
+    { 0x1E, 0x33, 0x33, 0x33, 0x3B, 0x1E, 0x38, 0x00},   // U+0051 (Q)
+    { 0x3F, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x67, 0x00},   // U+0052 (R)
+    { 0x1E, 0x33, 0x07, 0x0E, 0x38, 0x33, 0x1E, 0x00},   // U+0053 (S)
+    { 0x3F, 0x2D, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00},   // U+0054 (T)
+    { 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x00},   // U+0055 (U)
+    { 0x33, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00},   // U+0056 (V)
+    { 0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00},   // U+0057 (W)
+    { 0x63, 0x63, 0x36, 0x1C, 0x1C, 0x36, 0x63, 0x00},   // U+0058 (X)
+    { 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x0C, 0x1E, 0x00},   // U+0059 (Y)
+    { 0x7F, 0x63, 0x31, 0x18, 0x4C, 0x66, 0x7F, 0x00},   // U+005A (Z)
+    { 0x1E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x1E, 0x00},   // U+005B ([)
+    { 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00},   // U+005C (\)
+    { 0x1E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1E, 0x00},   // U+005D (])
+    { 0x08, 0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00},   // U+005E (^)
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF},   // U+005F (_)
+    { 0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0060 (`)
+    { 0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x6E, 0x00},   // U+0061 (a)
+    { 0x07, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00},   // U+0062 (b)
+    { 0x00, 0x00, 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x00},   // U+0063 (c)
+    { 0x38, 0x30, 0x30, 0x3e, 0x33, 0x33, 0x6E, 0x00},   // U+0064 (d)
+    { 0x00, 0x00, 0x1E, 0x33, 0x3f, 0x03, 0x1E, 0x00},   // U+0065 (e)
+    { 0x1C, 0x36, 0x06, 0x0f, 0x06, 0x06, 0x0F, 0x00},   // U+0066 (f)
+    { 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x1F},   // U+0067 (g)
+    { 0x07, 0x06, 0x36, 0x6E, 0x66, 0x66, 0x67, 0x00},   // U+0068 (h)
+    { 0x0C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00},   // U+0069 (i)
+    { 0x30, 0x00, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E},   // U+006A (j)
+    { 0x07, 0x06, 0x66, 0x36, 0x1E, 0x36, 0x67, 0x00},   // U+006B (k)
+    { 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00},   // U+006C (l)
+    { 0x00, 0x00, 0x33, 0x7F, 0x7F, 0x6B, 0x63, 0x00},   // U+006D (m)
+    { 0x00, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x33, 0x00},   // U+006E (n)
+    { 0x00, 0x00, 0x1E, 0x33, 0x33, 0x33, 0x1E, 0x00},   // U+006F (o)
+    { 0x00, 0x00, 0x3B, 0x66, 0x66, 0x3E, 0x06, 0x0F},   // U+0070 (p)
+    { 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x78},   // U+0071 (q)
+    { 0x00, 0x00, 0x3B, 0x6E, 0x66, 0x06, 0x0F, 0x00},   // U+0072 (r)
+    { 0x00, 0x00, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x00},   // U+0073 (s)
+    { 0x08, 0x0C, 0x3E, 0x0C, 0x0C, 0x2C, 0x18, 0x00},   // U+0074 (t)
+    { 0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00},   // U+0075 (u)
+    { 0x00, 0x00, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00},   // U+0076 (v)
+    { 0x00, 0x00, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00},   // U+0077 (w)
+    { 0x00, 0x00, 0x63, 0x36, 0x1C, 0x36, 0x63, 0x00},   // U+0078 (x)
+    { 0x00, 0x00, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x1F},   // U+0079 (y)
+    { 0x00, 0x00, 0x3F, 0x19, 0x0C, 0x26, 0x3F, 0x00},   // U+007A (z)
+    { 0x38, 0x0C, 0x0C, 0x07, 0x0C, 0x0C, 0x38, 0x00},   // U+007B ({)
+    { 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00},   // U+007C (|)
+    { 0x07, 0x0C, 0x0C, 0x38, 0x0C, 0x0C, 0x07, 0x00},   // U+007D (})
+    { 0x6E, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+007E (~)
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}    // U+007F
+};
+
+
+#endif

+ 135 - 0
src/main.cpp

@@ -0,0 +1,135 @@
+#include <stdio.h>
+#include <pico.h>
+#include "pico/stdlib.h"
+#include "hardware/clocks.h"
+#include "pico/stdlib.h"
+#include "pico/binary_info.h"
+#include "hardware/i2c.h"
+#include "pico/multicore.h"
+#include "hardware/irq.h"
+#include "hardware/adc.h"
+#include "hardware/clocks.h"
+#include "display.h"
+#include "sd1306.h"
+
+#define ADC_BUF_LEN 0x100
+#define ADC_SAMPLE_CLOCK   3000     // 3000 sps
+#define HEARTBEAT_SAMPLE_CLOCK  50  // 50Hz
+
+uint16_t adc_buffer[ADC_BUF_LEN];   // ADC用リングバッファ
+uint16_t read_pointer = 0;          // バッファ用ポインタ
+uint16_t write_pointer = 0;
+
+// ADC割り込み
+void adc_handler(void)
+{
+    #define LOCAL_BUFFER_SIZE   (ADC_SAMPLE_CLOCK / HEARTBEAT_SAMPLE_CLOCK)
+    static int local_counter = 0;
+    static uint16_t local_buffer[LOCAL_BUFFER_SIZE];
+
+    while(! adc_fifo_is_empty()) {
+        local_buffer[local_counter++] = (adc_fifo_get() & 0xFFF);
+        if(local_counter >= LOCAL_BUFFER_SIZE) {
+            uint32_t total = 0;
+
+            // 平均を取る
+            for(int i = 0 ; i < local_counter; i++ )
+                total += local_buffer[i];
+            adc_buffer[write_pointer++] = (uint16_t)(total / local_counter);
+            write_pointer &= ADC_BUF_LEN - 1;
+            local_counter = 0;
+        }
+    }
+}
+
+int main(void)
+{
+    uint32_t adc_clk;
+
+    // Display初期化
+    multicore_reset_core1();
+    multicore_launch_core1(display);
+
+    // ADC初期化
+    adc_init();
+    adc_gpio_init(26);
+    adc_select_input(0);
+    adc_fifo_setup(true, false, 1, true, false);
+    // ADCサンプリングクロック設定
+    adc_clk = clock_get_hz(clk_adc);
+    adc_set_clkdiv(adc_clk/ADC_SAMPLE_CLOCK);
+    // ADC割り込み
+	irq_set_exclusive_handler(ADC_IRQ_FIFO, adc_handler);
+	irq_set_enabled(ADC_IRQ_FIFO, true);
+    // ADC割り込み有効化&ADCスタート
+    adc_irq_set_enabled(true);
+    adc_run(true);
+
+    // メインループ
+    #define HEARTBEAT_THRESHOLD    1430    // 心拍のしきい値
+    #define HYSTERESIS  50                  // ヒステリシス
+    #define HEARTBEAT_COUNT 10              // 基準心拍数
+    #define LOWEST_VALUE    1240            // 約1V
+
+    uint32_t last_time = 0;                 // 前回の時間
+    uint32_t heartbeat_counter = 0;         // 脈拍カウンタ
+    bool ex_threshold = false;              // しきい値フラグ
+
+    while(true) {
+        if(write_pointer != read_pointer) {
+            uint16_t pos_y = 0;
+            uint8_t pixel;
+            uint16_t offset;
+            uint16_t val = adc_buffer[read_pointer++] & 0xFFF;
+            read_pointer &= ADC_BUF_LEN - 1;
+
+            // 心拍センサー用の調整
+            if( val > LOWEST_VALUE ) {      // ADCの値から約1Vを切り捨てる
+                val -= LOWEST_VALUE;
+            }
+            else val = 0;
+            // ドット位置の計算
+            pos_y = (val * OLED_HEIGHT) / (4096 - LOWEST_VALUE) ;
+            offset = (pos_y / 8) * OLED_WIDTH;
+            pixel = 0x01 << (pos_y % 8);
+            // ディスプレイバッファ横スクロール
+            memmove(&(display_buffer[1]),&(display_buffer[0]), OLED_BUF_LEN-(1+24));
+            display_buffer[0] = 0x0;
+            display_buffer[OLED_WIDTH] = 0x0;
+            display_buffer[OLED_WIDTH*2] = 0x0;
+            display_buffer[OLED_WIDTH*3] = 0x0;
+            // ドットを打つ
+            display_buffer[offset] = pixel;
+
+            // 心拍カウンタ
+            if((val > HEARTBEAT_THRESHOLD) && !ex_threshold)
+                ex_threshold = true;
+            if(val < (HEARTBEAT_THRESHOLD - HYSTERESIS)){
+                if(ex_threshold) {
+                    heartbeat_counter++;
+                    if(heartbeat_counter >= HEARTBEAT_COUNT) {
+                        // 現在時刻をミリ秒で得る
+                        uint32_t current_time =  to_ms_since_boot(get_absolute_time());
+                        if(last_time == 0) {
+                            last_time = current_time;
+                        }
+                        else {
+                            // 脈拍数を3桁の数で表示する
+                            uint32_t past_ms = current_time - last_time;
+                            uint32_t heartrate = 60 * 1000 * HEARTBEAT_COUNT / past_ms;
+                            char s[16];
+                            sprintf(s, "%3d", (int)heartrate);
+                            display_putchar(s[0], 15, 3, true);
+                            display_putchar(s[1], 14, 3, true);
+                            display_putchar(s[2], 13, 3, true);
+                            last_time = current_time;
+                        }
+                        heartbeat_counter = 0;
+                    }
+                }
+                ex_threshold = false;
+            }
+        }
+    }
+}
+

+ 196 - 0
src/sd1306.cpp

@@ -0,0 +1,196 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include "pico/stdlib.h"
+#include "pico/binary_info.h"
+#include "hardware/i2c.h"
+#include "sd1306.h"
+
+// commands (see datasheet)
+#define OLED_SET_CONTRAST _u(0x81)
+#define OLED_SET_ENTIRE_ON _u(0xA4)
+#define OLED_SET_NORM_INV _u(0xA6)
+#define OLED_SET_DISP _u(0xAE)
+#define OLED_SET_MEM_ADDR _u(0x20)
+#define OLED_SET_COL_ADDR _u(0x21)
+#define OLED_SET_PAGE_ADDR _u(0x22)
+#define OLED_SET_DISP_START_LINE _u(0x40)
+#define OLED_SET_SEG_REMAP _u(0xA0)
+#define OLED_SET_MUX_RATIO _u(0xA8)
+#define OLED_SET_COM_OUT_DIR _u(0xC0)
+#define OLED_SET_DISP_OFFSET _u(0xD3)
+#define OLED_SET_COM_PIN_CFG _u(0xDA)
+#define OLED_SET_DISP_CLK_DIV _u(0xD5)
+#define OLED_SET_PRECHARGE _u(0xD9)
+#define OLED_SET_VCOM_DESEL _u(0xDB)
+#define OLED_SET_CHARGE_PUMP _u(0x8D)
+#define OLED_SET_HORIZ_SCROLL _u(0x26)
+#define OLED_SET_SCROLL _u(0x2E)
+
+#define OLED_ADDR _u(0x3C)
+
+#define OLED_WRITE_MODE _u(0xFE)
+#define OLED_READ_MODE _u(0xFF)
+
+
+
+void fill(uint8_t buf[], uint8_t fill) {
+    // fill entire buffer with the same byte
+    for (int i = 0; i < OLED_BUF_LEN; i++) {
+        buf[i] = fill;
+    }
+};
+
+void fill_page(uint8_t *buf, uint8_t fill, uint8_t page) {
+    // fill entire page with the same byte
+    memset(buf + (page * OLED_WIDTH), fill, OLED_WIDTH);
+};
+
+// convenience methods for printing out a buffer to be rendered
+// mostly useful for debugging images, patterns, etc
+
+void print_buf_page(uint8_t buf[], uint8_t page) {
+    // prints one page of a full length (128x4) buffer
+    for (int j = 0; j < OLED_PAGE_HEIGHT; j++) {
+        for (int k = 0; k < OLED_WIDTH; k++) {
+            printf("%u", (buf[page * OLED_WIDTH + k] >> j) & 0x01);
+        }
+        printf("\n");
+    }
+}
+
+void print_buf_pages(uint8_t buf[]) {
+    // prints all pages of a full length buffer
+    for (int i = 0; i < OLED_NUM_PAGES; i++) {
+        printf("--page %d--\n", i);
+        print_buf_page(buf, i);
+    }
+}
+
+void print_buf_area(uint8_t *buf, struct render_area *area) {
+    // print a render area of generic size
+    int area_width = area->end_col - area->start_col + 1;
+    int area_height = area->end_page - area->start_page + 1; // in pages, not pixels
+    for (int i = 0; i < area_height; i++) {
+        for (int j = 0; j < OLED_PAGE_HEIGHT; j++) {
+            for (int k = 0; k < area_width; k++) {
+                printf("%u", (buf[i * area_width + k] >> j) & 0x01);
+            }
+            printf("\n");
+        }
+    }
+}
+
+void calc_render_area_buflen(struct render_area *area) {
+    // calculate how long the flattened buffer will be for a render area
+    area->buflen = (area->end_col - area->start_col + 1) * (area->end_page - area->start_page + 1);
+}
+
+#ifdef i2c_default
+
+void oled_send_cmd(uint8_t cmd) {
+    // I2C write process expects a control byte followed by data
+    // this "data" can be a command or data to follow up a command
+
+    // Co = 1, D/C = 0 => the driver expects a command
+    uint8_t buf[2] = {0x80, cmd};
+    i2c_write_blocking(i2c_default, (OLED_ADDR & OLED_WRITE_MODE), buf, 2, false);
+}
+
+void oled_send_buf(uint8_t buf[], int buflen) {
+    // in horizontal addressing mode, the column address pointer auto-increments
+    // and then wraps around to the next page, so we can send the entire frame
+    // buffer in one gooooooo!
+
+    // copy our frame buffer into a new buffer because we need to add the control byte
+    // to the beginning
+
+    // TODO find a more memory-efficient way to do this..
+    // maybe break the data transfer into pages?
+    uint8_t *temp_buf = (uint8_t *)malloc(buflen + 1);
+
+    for (int i = 1; i < buflen + 1; i++) {
+        temp_buf[i] = buf[i - 1];
+    }
+    // Co = 0, D/C = 1 => the driver expects data to be written to RAM
+    temp_buf[0] = 0x40;
+    i2c_write_blocking(i2c_default, (OLED_ADDR & OLED_WRITE_MODE), temp_buf, buflen + 1, false);
+
+    free(temp_buf);
+}
+
+void oled_init(void) {
+    // some of these commands are not strictly necessary as the reset
+    // process defaults to some of these but they are shown here
+    // to demonstrate what the initialization sequence looks like
+
+    // some configuration values are recommended by the board manufacturer
+
+    oled_send_cmd(OLED_SET_DISP | 0x00); // set display off
+
+    /* memory mapping */
+    oled_send_cmd(OLED_SET_MEM_ADDR); // set memory address mode
+    oled_send_cmd(0x00); // horizontal addressing mode
+
+    /* resolution and layout */
+    oled_send_cmd(OLED_SET_DISP_START_LINE); // set display start line to 0
+
+    oled_send_cmd(OLED_SET_SEG_REMAP | 0x01); // set segment re-map
+    // column address 127 is mapped to SEG0
+
+    oled_send_cmd(OLED_SET_MUX_RATIO); // set multiplex ratio
+    oled_send_cmd(OLED_HEIGHT - 1); // our display is only 32 pixels high
+
+    oled_send_cmd(OLED_SET_COM_OUT_DIR | 0x08); // set COM (common) output scan direction
+    // scan from bottom up, COM[N-1] to COM0
+
+    oled_send_cmd(OLED_SET_DISP_OFFSET); // set display offset
+    oled_send_cmd(0x00); // no offset
+
+    oled_send_cmd(OLED_SET_COM_PIN_CFG); // set COM (common) pins hardware configuration
+    oled_send_cmd(0x02); // manufacturer magic number
+
+    /* timing and driving scheme */
+    oled_send_cmd(OLED_SET_DISP_CLK_DIV); // set display clock divide ratio
+    oled_send_cmd(0x80); // div ratio of 1, standard freq
+
+    oled_send_cmd(OLED_SET_PRECHARGE); // set pre-charge period
+    oled_send_cmd(0xF1); // Vcc internally generated on our board
+
+    oled_send_cmd(OLED_SET_VCOM_DESEL); // set VCOMH deselect level
+    oled_send_cmd(0x30); // 0.83xVcc
+
+    /* display */
+    oled_send_cmd(OLED_SET_CONTRAST); // set contrast control
+    oled_send_cmd(0xFF);
+
+    oled_send_cmd(OLED_SET_ENTIRE_ON); // set entire display on to follow RAM content
+
+    oled_send_cmd(OLED_SET_NORM_INV); // set normal (not inverted) display
+
+    oled_send_cmd(OLED_SET_CHARGE_PUMP); // set charge pump
+    oled_send_cmd(0x14); // Vcc internally generated on our board
+
+    oled_send_cmd(OLED_SET_SCROLL | 0x00); // deactivate horizontal scrolling if set
+    // this is necessary as memory writes will corrupt if scrolling was enabled
+
+    oled_send_cmd(OLED_SET_DISP | 0x01); // turn display on
+}
+
+void render(uint8_t *buf, struct render_area *area) {
+    // update a portion of the display with a render area
+    oled_send_cmd(OLED_SET_COL_ADDR);
+    oled_send_cmd(area->start_col);
+    oled_send_cmd(area->end_col);
+
+    oled_send_cmd(OLED_SET_PAGE_ADDR);
+    oled_send_cmd(area->start_page);
+    oled_send_cmd(area->end_page);
+
+    oled_send_buf(buf, area->buflen);
+}
+
+#endif
+
+
+

+ 35 - 0
src/sd1306.h

@@ -0,0 +1,35 @@
+#ifndef _SD1306_H_
+#define _SD1306_H_
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#define OLED_HEIGHT _u(32)
+#define OLED_WIDTH _u(128)
+#define OLED_PAGE_HEIGHT _u(8)
+#define OLED_NUM_PAGES OLED_HEIGHT / OLED_PAGE_HEIGHT
+#define OLED_BUF_LEN (OLED_NUM_PAGES * OLED_WIDTH)
+
+struct render_area {
+    uint8_t start_col;
+    uint8_t end_col;
+    uint8_t start_page;
+    uint8_t end_page;
+
+    int buflen;
+};
+
+void fill(uint8_t buf[], uint8_t fill);
+void fill_page(uint8_t *buf, uint8_t fill, uint8_t page);
+void print_buf_page(uint8_t buf[], uint8_t page);
+void print_buf_pages(uint8_t buf[]);
+void print_buf_area(uint8_t *buf, struct render_area *area);
+void calc_render_area_buflen(struct render_area *area);
+void oled_send_cmd(uint8_t cmd);
+void oled_send_buf(uint8_t buf[], int buflen);
+void oled_init(void);
+void render(uint8_t *buf, struct render_area *area);
+
+
+#endif

+ 11 - 0
test/README

@@ -0,0 +1,11 @@
+
+This directory is intended for PlatformIO Unit Testing and project tests.
+
+Unit Testing is a software testing method by which individual units of
+source code, sets of one or more MCU program modules together with associated
+control data, usage procedures, and operating procedures, are tested to
+determine whether they are fit for use. Unit testing finds problems early
+in the development cycle.
+
+More information about PlatformIO Unit Testing:
+- https://docs.platformio.org/page/plus/unit-testing.html