Skip to content

Instantly share code, notes, and snippets.

@ksasao
Last active June 8, 2025 14:46
Show Gist options
  • Save ksasao/c74f8dfdca14e6acc21d989f89ec6e66 to your computer and use it in GitHub Desktop.
Save ksasao/c74f8dfdca14e6acc21d989f89ec6e66 to your computer and use it in GitHub Desktop.
8pin RISC-V マイコン CH32V003J4M6 で気圧変化を可視化。https://x.com/ksasao/status/1918255371950670254
// 8pin RISC-V マイコン CH32V003J4M6 で気圧変化を可視化します
// 気圧センサの生データを直接参照し温度補正等をしていないことに注意してください
// https://x.com/ksasao/status/1918255371950670254
//
// ■ 開発環境
// Arduino IDE 2.3.6
// Boards Manager: CH32 MCU EVT Boards 1.0.4
// https://github.com/openwch/board_manager_files/raw/main/package_ch32v_index.json
// Board: CH32V00x
// ■ パーツ
// CH32V003J4M6 https://akizukidenshi.com/catalog/g/g118062/
// マイコン内蔵RGBLEDモジュール WS2812B (NeoPixel) https://akizukidenshi.com/catalog/g/g108414/
// 絶対圧センサ DPS310 https://www.switch-science.com/products/6286
//
// 参考:
// amanoya3: ArduinoでRISC-VマイコンCH32V003にNeoPixelのLEDをつないでみた
// https://ameblo.jp/pta55/entry-12813320408.html
//
// ■ 配線
// CH32V003J4M6 のピンから見た接続
//---------------------------------------------
// 1: RGBLED の DI
// 2: WCH-Linkエミュレータ と RGBLEDのGND
// 3:
// 4: WCH-Linkエミュレータの3V3 とRGBLEDのVDD
// 5: 2SMPBのSDA
// 6: 2SMPBのSDL
// 7:
// 8: WCH-LinkエミュレータのSWDIOおよびRX
//---------------------------------------------
#include <Wire.h>
// PORTD レジスタへの直接アクセス
// CH32V003RM.PDF p.57-58 参照
// https://www.wch-ic.com/downloads/CH32V003RM_PDF.html
#define R32_GPIOD_BSHR (*(volatile uint32_t *)0x40011410)
#define DPS310_ADDR 0x77
#include <ch32v00x.h>
// フラッシュメモリの設定
#define FLASH_PAGE_SIZE 64 // CH32V003のページサイズは64バイト
#define DATA_FLASH_ADDR 0x08003FC0 // 最後のページの開始
#define CONFIG_ABORTED (DATA_FLASH_ADDR+0)
#define CONFIG_SETTING (DATA_FLASH_ADDR+2)
#define CONFIG_PRE_SETTING (DATA_FLASH_ADDR+4)
#define FLASH_CTLR_PAGE_PG ((uint32_t)0x00010000) /* Page Programming 64Byte */
#define FLASH_CTLR_PAGE_ER ((uint32_t)0x00020000) /* Page Erase 64Byte */
#define FLASH_CTLR_BUF_LOAD ((uint32_t)0x00040000) /* Buffer Load */
#define FLASH_CTLR_BUF_RST ((uint32_t)0x00080000) /* Buffer Reset */
// ページバックアップ用バッファ
uint8_t pageBuffer[FLASH_PAGE_SIZE];
// センサ表示の感度設定
const int32_t _limit = 50; // limitより変動が小さい場合はOFF
int32_t _range = 400; // absmaxより変動が大きい場合は最大値でクリッピング(setupConfig()で再設定される)
// ■ 気圧センサDPS310
int32_t readRawPressure()
{
delay(10);
// 測定開始
Wire.beginTransmission(DPS310_ADDR);
Wire.write(0x08);
Wire.write(0x01);
Wire.endTransmission();
// データ読み取り
Wire.beginTransmission(DPS310_ADDR);
Wire.write(0x00);
Wire.endTransmission();
Wire.requestFrom(DPS310_ADDR, 3);
int32_t raw = (Wire.read() << 16) | (Wire.read() << 8) | Wire.read();
return raw;
}
void configDPS310(){
delay(500);
// prs_cfg 圧力測定レート32Hz(5 << 4)、オーバーサンプリング2回 (1)
// https://www.infineon.com/dgdl/Infineon-DPS310-DS-v01_00-EN.pdf?fileId=5546d462576f34750157750826c42242
// p.29
byte prs_cfg_val = (5 << 4) | 1;
Wire.beginTransmission(DPS310_ADDR);
Wire.write(0x06); // PRS_CFG_ADDR
Wire.write(prs_cfg_val);
Wire.endTransmission();
}
int initDPS310(){
// チップID読み取り
Wire.beginTransmission(DPS310_ADDR);
Wire.write(0x0D); // チップIDレジスタ
Wire.endTransmission(false);
Wire.requestFrom(DPS310_ADDR, 1, true);
if (Wire.available()) {
uint8_t chip_id = Wire.read();
if (chip_id == 0x10) {
configDPS310();
return 0; // 正常に初期化済み
} else {
return -1; // DPS310が見つからない
}
}
return -2; // I2Cが応答しない
}
// ■ LED
void initLED(){
pinMode(PD6, OUTPUT);
// LEDを初期化するためのダミー書込み
for(int i=0;i<3;i++){
setColor(0,0,0);
delay(10);
}
}
void setColor(byte r, byte g, byte b){
byte aData[3] = {g,r,b};
noInterrupts();
volatile uint32_t d6High = R32_GPIOD_BSHR | 0x0040; // PortD 6bit目を1
for(byte rgbLed=0; rgbLed < 3;rgbLed++){
byte a=aData[rgbLed];
for(byte i=0;i<8;i++){
byte hl=a & 0x80;
if(hl>0){ // H: 600ns, L: 500ns
R32_GPIOD_BSHR = d6High; // High - 100ns
digitalWriteFast(PD_6, HIGH); // High - 500ns
digitalWriteFast(PD_6, LOW); // Low - 500ns
}else{ // H: 200ns, L: 500ns
R32_GPIOD_BSHR |= d6High; // High - 200ns
digitalWriteFast(PD_6, LOW); // Low - 500ns
}
a=a*2;
}
}
interrupts();
}
// ■ ハイパスフィルタ (固定小数点演算)
// フィルタのカットオフ特性を決定するシフト量
// 値が大きいほど、より低い周波数をカットオフします(時定数が大きくなる)。
const int32_t FILTER_STRENGTH_SHIFT = 9;
// 内部計算の精度を向上させるための追加のシフト量。桁あふれに注意。
const int32_t INTERNAL_PRECISION_SHIFT = 8;
// ハイパスフィルタ関数
// input_x: 現在の入力値
// 戻り値: フィルタリングされた値
int32_t getHPFValue(long input_x) {
// 内部計算ではシフトした量を利用します
static int64_t internal_prev_x = (int64_t)input_x << INTERNAL_PRECISION_SHIFT; // 前回の入力値
static int64_t internal_y = 0; // フィルタの出力状態, 0で初期化
// (input_x を INTERNAL_PRECISION_SHIFT ビット左シフト)
int64_t current_internal_x = (int64_t)input_x << INTERNAL_PRECISION_SHIFT;
// 入力値の差分を計算
// diff = x[n] - x[n-1]
int64_t internal_diff = current_internal_x - internal_prev_x;
// 減衰項 (decay) の計算
// decay = internal_y / (2^FILTER_STRENGTH_SHIFT) を丸め処理を加えて計算します。
// この internal_decay は internal_y と同じスケール(内部スケール)の値を持ちます。
int64_t internal_decay;
if (internal_y == 0) {
internal_decay = 0;
} else {
// 絶対値に対して丸め処理を行う (round half away from zero)
// (value + divisor/2) / divisor に相当する計算をビットシフトで行う
int64_t abs_internal_y = (internal_y > 0) ? internal_y : -internal_y;
internal_decay = (abs_internal_y + (1LL << (FILTER_STRENGTH_SHIFT - 1))) >> FILTER_STRENGTH_SHIFT;
// 元の値が負だった場合は減衰項も負にする
if (internal_y < 0) {
internal_decay = -internal_decay;
}
}
// フィルタの更新式
// y[n] = y[n-1] + diff[n] - decay_of_y[n-1]
// = y[n-1] * (1 - 1/(2^FILTER_STRENGTH_SHIFT)) + (x[n] - x[n-1]) (近似的に)
internal_y = internal_y + internal_diff - internal_decay;
// 次の計算のために、現在の入力値を保存 (内部スケール)
internal_prev_x = current_internal_x;
// フィルタの出力値を元のスケールに戻す
// (internal_y を INTERNAL_PRECISION_SHIFT ビット右シフトして丸める)
// 丸め方式: round half away from zero
int32_t output_y;
if (internal_y == 0) {
output_y = 0;
} else {
int64_t abs_val_for_output = (internal_y > 0) ? internal_y : -internal_y;
// (value + divisor/2) / divisor
int64_t rounded_abs_scaled_output = (abs_val_for_output + (1LL << (INTERNAL_PRECISION_SHIFT - 1))) >> INTERNAL_PRECISION_SHIFT;
if (internal_y > 0) {
output_y = (int32_t)rounded_abs_scaled_output;
} else {
output_y = -(int32_t)rounded_abs_scaled_output;
}
}
// デバッグ用のシリアル出力 (必要に応じて有効化)
Serial.print(input_x); // センサー生値
Serial.print(" ");
Serial.println(output_y); // HPF出力
return output_y;
}
// フラッシュメモリから1バイト読み込み
uint8_t readFlashByte(uint32_t address) {
uint8_t val = *(volatile uint8_t*)address;
uint8_t inv = ~(*(volatile uint8_t*)(address+1));
if(val!=inv){
Serial.print("Flash read error: ");
Serial.println(val);
}else{
Serial.print("Flash read ok: ");
Serial.println(val);
}
return val;
}
void writeFlashByte(uint32_t address, uint8_t data) {
// ページの開始アドレスを計算
uint32_t pageAddr = address & ~(FLASH_PAGE_SIZE - 1);
uint32_t byteOffset = address - pageAddr;
// 現在のページ内容をバックアップ
backupPage(pageAddr);
// バッファ内の目的のバイトを更新
pageBuffer[byteOffset] = data;
pageBuffer[byteOffset+1] = ~data;
// ページ全体を書き戻し
writePage(pageAddr, pageBuffer);
}
// ページ全体をバックアップ
void backupPage(uint32_t pageAddr) {
for (uint8_t i = 0; i < FLASH_PAGE_SIZE; i++) {
pageBuffer[i] = *(volatile uint8_t*)(pageAddr + i);
}
}
// ページ全体を書き込み
void writePage(uint32_t pageAddr, uint8_t* data) {
FLASH_Unlock();
// ページ消去
FLASH_ErasePage(pageAddr);
// データを16ビット単位で書き込み
for (uint8_t i = 0; i < FLASH_PAGE_SIZE; i += 2) {
uint16_t halfWord;
if (i + 1 < FLASH_PAGE_SIZE) {
halfWord = data[i] | (data[i + 1] << 8);
} else {
halfWord = data[i] | 0xFF00;
}
FLASH_ProgramHalfWord(pageAddr + i, halfWord);
}
FLASH_Lock();
}
void setupConfig(){
// 前回起動中に中断されたかをチェック
uint8_t aborted = readFlashByte(CONFIG_ABORTED);
if(CONFIG_ABORTED > 0){
uint8_t preSetting = readFlashByte(CONFIG_PRE_SETTING);
preSetting = preSetting < 2 ? preSetting : 0;
// 中断された場合には PRE_SETTINGの値をSETTINGにコピー
writeFlashByte(CONFIG_SETTING, preSetting);
}else{
// 中断フラグを1に
writeFlashByte(CONFIG_ABORTED, 1);
}
// 暗い水色、PRE_SETTING=0
setColor(0,32,32);
writeFlashByte(CONFIG_PRE_SETTING,0);
delay(1000);
// 明るい水色、PRE_SETTING=1
setColor(0,255,255);
writeFlashByte(CONFIG_PRE_SETTING,1);
delay(1000);
// 中断されずに起動したので中断フラグを0に
writeFlashByte(CONFIG_ABORTED,0);
// 設定値に応じて_rangeを設定
uint8_t setting = readFlashByte(CONFIG_SETTING);
switch(setting){
case 0:
_range = 4000;
break;
case 1:
_range = 100;
break;
default:
_range = 4000;
}
}
void setup(){
delay(1000); // 少し待つとArduinoの文字化けが減らせる
Serial.begin(115200);
initLED();
Wire.begin();
setupConfig();
// DPS310初期化
switch(initDPS310()){
case 0:
setColor(0,0,127); // 正常に初期化(青)
break;
case -1:
setColor(127,127,0); // DPS310ではない(橙)
break;
case -2:
setColor(127,0,127); // I2Cアドレスエラー(紫)
}
delay(500);
// 最初はセンサ値が安定していないため読み飛ばす
for(int i=0;i<20;i++){
readRawPressure();
}
}
void setMonitor(int32_t val,int32_t limit, int32_t absmax){
const int32_t palettes = 3;
const uint8_t pCol[][palettes] = {{0,0,1},{0,48,128},{64,192,255}}; // val>0 の時のグラデーション
const uint8_t mCol[][palettes] = {{1,0,0},{128,48,0},{255,192,40}}; // val<0 の時のグラデーション
// 正負の最大値でクリッピング
val = val > absmax ? absmax : val;
val = val < -absmax ? -absmax : val;
// limit以下なら黒
int32_t absval = val > 0 ? val : -val;
if(absval < limit){
setColor(0,0,0);
return;
}
// 中間値ならグラデーション
byte r,g,b;
int32_t width = (absmax-limit)/(palettes-1);
int32_t index = (absval-limit)/width;
int32_t midval = (absval-index*width-limit);
if(val > 0){
r = (byte)((pCol[index][0] * (width-midval) + pCol[index+1][0] * midval)/width);
g = (byte)((pCol[index][1] * (width-midval) + pCol[index+1][1] * midval)/width);
b = (byte)((pCol[index][2] * (width-midval) + pCol[index+1][2] * midval)/width);
}else{
r = (byte)((mCol[index][0] * (width-midval) + mCol[index+1][0] * midval)/width);
g = (byte)((mCol[index][1] * (width-midval) + mCol[index+1][1] * midval)/width);
b = (byte)((mCol[index][2] * (width-midval) + mCol[index+1][2] * midval)/width);
}
setColor(r,g,b);
}
void loop(){
// 気圧センサの生値は気圧の上昇とともに減少するようなので変化量に-をつける
int32_t delta = -getHPFValue(readRawPressure());
setMonitor(delta,_limit,_range);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment