yoneda 1 ano atrás
commit
1d8bad88bc
15 arquivos alterados com 880 adições e 0 exclusões
  1. 1 0
      .gitignore
  2. 18 0
      .vscode/c_cpp_properties.json
  3. 7 0
      .vscode/cmake-kits.json
  4. 9 0
      .vscode/extensions.json
  5. 58 0
      .vscode/launch.json
  6. 26 0
      .vscode/settings.json
  7. 29 0
      .vscode/tasks.json
  8. 31 0
      CMakeLists.txt
  9. 69 0
      display.cpp
  10. 46 0
      display.h
  11. 145 0
      font8x8.h
  12. 137 0
      main.cpp
  13. 73 0
      pico_sdk_import.cmake
  14. 196 0
      sd1306.cpp
  15. 35 0
      sd1306.h

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+build/*

+ 18 - 0
.vscode/c_cpp_properties.json

@@ -0,0 +1,18 @@
+{
+  "configurations": [
+    {
+      "name": "Pico",
+      "includePath": [
+        "${workspaceFolder}/**",
+        "${env:PICO_SDK_PATH}/**"
+      ],
+      "defines": [],
+      "compilerPath": "${env:PICO_INSTALL_PATH}/gcc-arm-none-eabi/bin/arm-none-eabi-gcc.exe",
+      "cStandard": "c11",
+      "cppStandard": "c++11",
+      "intelliSenseMode": "linux-gcc-arm",
+      "configurationProvider": "ms-vscode.cmake-tools"
+    }
+  ],
+  "version": 4
+}

+ 7 - 0
.vscode/cmake-kits.json

@@ -0,0 +1,7 @@
+[
+  {
+    "name": "Pico ARM GCC",
+    "description": "Pico SDK Toolchain with GCC arm-none-eabi",
+    "toolchainFile": "${env:PICO_SDK_PATH}/cmake/preload/toolchains/pico_arm_gcc.cmake"
+  }
+]

+ 9 - 0
.vscode/extensions.json

@@ -0,0 +1,9 @@
+{
+  "recommendations": [
+    "marus25.cortex-debug",
+    "ms-vscode.cmake-tools",
+    "ms-vscode.cpptools",
+    "ms-vscode.cpptools-extension-pack",
+    "ms-vscode.vscode-serial-monitor"
+  ]
+}

+ 58 - 0
.vscode/launch.json

@@ -0,0 +1,58 @@
+{
+  "version": "0.2.0",
+  "configurations": [
+    {
+      "name": "Pico Debug (Cortex-Debug)",
+      "cwd": "${workspaceFolder}",
+      "executable": "${command:cmake.launchTargetPath}",
+      "request": "launch",
+      "type": "cortex-debug",
+      "servertype": "openocd",
+      "gdbPath": "arm-none-eabi-gdb",
+      "device": "RP2040",
+      "configFiles": [
+        "interface/cmsis-dap.cfg",
+        "target/rp2040.cfg"
+      ],
+      "svdFile": "${env:PICO_SDK_PATH}/src/rp2040/hardware_regs/rp2040.svd",
+      "runToEntryPoint": "main",
+      "openOCDLaunchCommands": [
+        "adapter speed 1000"
+      ]
+    },
+    {
+      "name": "Pico Debug (Cortex-Debug with external OpenOCD)",
+      "cwd": "${workspaceFolder}",
+      "executable": "${command:cmake.launchTargetPath}",
+      "request": "launch",
+      "type": "cortex-debug",
+      "servertype": "external",
+      "gdbTarget": "localhost:3333",
+      "gdbPath": "arm-none-eabi-gdb",
+      "device": "RP2040",
+      "svdFile": "${env:PICO_SDK_PATH}/src/rp2040/hardware_regs/rp2040.svd",
+      "runToEntryPoint": "main"
+    },
+    {
+      "name": "Pico Debug (C++ Debugger)",
+      "type": "cppdbg",
+      "request": "launch",
+      "cwd": "${workspaceFolder}",
+      "program": "${command:cmake.launchTargetPath}",
+      "MIMode": "gdb",
+      "miDebuggerPath": "arm-none-eabi-gdb",
+      "miDebuggerServerAddress": "localhost:3333",
+      "debugServerPath": "openocd",
+      "debugServerArgs": "-f interface/cmsis-dap.cfg -f target/rp2040.cfg -c \"adapter speed 1000\"",
+      "serverStarted": "Listening on port .* for gdb connections",
+      "filterStderr": true,
+      "stopAtEntry": true,
+      "hardwareBreakpoints": {
+        "require": true,
+        "limit": 4
+      },
+      "preLaunchTask": "Flash",
+      "svdPath": "${env:PICO_SDK_PATH}/src/rp2040/hardware_regs/rp2040.svd"
+    }
+  ]
+}

+ 26 - 0
.vscode/settings.json

@@ -0,0 +1,26 @@
+{
+    // These settings tweaks to the cmake plugin will ensure
+    // that you debug using cortex-debug instead of trying to launch
+    // a Pico binary on the host
+    "cmake.statusbar.advanced": {
+        "debug": {
+            "visibility": "hidden"
+        },
+        "launch": {
+            "visibility": "hidden"
+        },
+        "build": {
+            "visibility": "hidden"
+        },
+        "buildTarget": {
+            "visibility": "hidden"
+        }
+    },
+    "cmake.buildBeforeRun": true,
+    "cmake.configureOnOpen": true,
+    "cmake.configureSettings": {
+      "CMAKE_MODULE_PATH": "${env:PICO_INSTALL_PATH}/pico-sdk-tools"
+    },
+    "cmake.generator": "Ninja",
+    "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools"
+}

+ 29 - 0
.vscode/tasks.json

@@ -0,0 +1,29 @@
+{
+  "version": "2.0.0",
+  "tasks": [
+    {
+      "label": "Flash",
+      "type": "shell",
+      "command": "openocd",
+      "args": [
+        "-f",
+        "interface/cmsis-dap.cfg",
+        "-f",
+        "target/rp2040.cfg",
+        "-c",
+        "adapter speed 1000; program {${command:cmake.launchTargetPath}} verify reset exit"
+      ],
+      "problemMatcher": []
+    },
+    {
+      "label": "Build",
+      "type": "cmake",
+      "command": "build",
+      "problemMatcher": "$gcc",
+      "group": {
+        "kind": "build",
+        "isDefault": true
+      }
+    }
+  ]
+}

+ 31 - 0
CMakeLists.txt

@@ -0,0 +1,31 @@
+cmake_minimum_required(VERSION 3.13)
+
+# initialize the SDK based on PICO_SDK_PATH
+# note: this must happen before project()
+include(pico_sdk_import.cmake)
+
+project(HeartbeatPi)
+
+# initialize the Raspberry Pi Pico SDK
+pico_sdk_init()
+
+add_executable(${PROJECT_NAME}
+	display.cpp
+	main.cpp
+	sd1306.cpp
+)
+
+# Add pico_stdlib library which aggregates commonly used features
+target_link_libraries(${PROJECT_NAME}
+	pico_stdlib
+	hardware_i2c
+	hardware_adc
+	hardware_irq
+	pico_multicore
+)
+
+pico_enable_stdio_usb(${PROJECT_NAME} 1)
+pico_enable_stdio_uart(${PROJECT_NAME} 0)
+
+# create map/bin/hex/uf2 file in addition to ELF.
+pico_add_extra_outputs(${PROJECT_NAME})

+ 69 - 0
display.cpp

@@ -0,0 +1,69 @@
+#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, 0x00);
+    while(1) {
+        render(display_buffer, &frame_area);
+        sleep_us(6*1000);
+    }
+}

+ 46 - 0
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
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

+ 137 - 0
main.cpp

@@ -0,0 +1,137 @@
+#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用リングバッファ
+volatile uint16_t read_pointer = 0;          // バッファ用ポインタ
+volatile 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;
+
+    stdio_init_all();
+
+    // 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 = 1;
+            // ドット位置の計算
+            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;
+            }
+        }
+    }
+}
+

+ 73 - 0
pico_sdk_import.cmake

@@ -0,0 +1,73 @@
+# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
+
+# This can be dropped into an external project to help locate this SDK
+# It should be include()ed prior to project()
+
+if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
+    set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
+    message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
+    set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
+    message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
+    set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
+    message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
+endif ()
+
+set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
+set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
+set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
+
+if (NOT PICO_SDK_PATH)
+    if (PICO_SDK_FETCH_FROM_GIT)
+        include(FetchContent)
+        set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
+        if (PICO_SDK_FETCH_FROM_GIT_PATH)
+            get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
+        endif ()
+        # GIT_SUBMODULES_RECURSE was added in 3.17
+        if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
+            FetchContent_Declare(
+                    pico_sdk
+                    GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+                    GIT_TAG master
+                    GIT_SUBMODULES_RECURSE FALSE
+            )
+        else ()
+            FetchContent_Declare(
+                    pico_sdk
+                    GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+                    GIT_TAG master
+            )
+        endif ()
+
+        if (NOT pico_sdk)
+            message("Downloading Raspberry Pi Pico SDK")
+            FetchContent_Populate(pico_sdk)
+            set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
+        endif ()
+        set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
+    else ()
+        message(FATAL_ERROR
+                "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
+                )
+    endif ()
+endif ()
+
+get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
+if (NOT EXISTS ${PICO_SDK_PATH})
+    message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
+endif ()
+
+set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
+if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
+    message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
+endif ()
+
+set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
+
+include(${PICO_SDK_INIT_CMAKE_FILE})

+ 196 - 0
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
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