Last active
June 8, 2025 14:46
-
-
Save ksasao/c74f8dfdca14e6acc21d989f89ec6e66 to your computer and use it in GitHub Desktop.
8pin RISC-V マイコン CH32V003J4M6 で気圧変化を可視化。https://x.com/ksasao/status/1918255371950670254
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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