chakokuのブログ(rev4)

日々のごった煮ブログです

お越しいただきありがとうございます。

このブログでは主に以下の内容を記載しています。

  • 何か作った話(自分は何かを作るために生きている..できたらクスっと笑えるものや、アートと絡みたい)
  • 音楽(AerophoneやDTM(FL-Studio))
  • 趣味のロードバイク(坂道をひーこら走ると結構大変な人生(笑)と重なる。。仕事のストレスが軽いと走らなくなる。。いかん)

記事が渾然一体となってカオス状態ですが、マイコンのプログラムも、ちゃりで走るのも、料理するのも、自分の中ではそれぞれが繋がっているので、カオスな日常をそのまま反映させています。リンクや引用はご自由にどうぞ。質問等ありましたらコメントに書いてください。微力ながら自分の分かる範囲で回答します。
とにかく、、人に「あほやなぁ」と言われる事に精進するのが我が人生。

2019/1/26 追記
長らくお世話になった「はてな」様ですが、はてなダイアリーがサービス終了となり、はてなブログに移しましたが、宣伝がちょっと辛いのでbloggerに引っ越す予定です。引っ越し先は以下
https://chakoku.blogspot.com/

2019/7/20
ひっこしたbloggerですが、どうにもUIが使い慣れないのと、なんか寂しい。ネット空間で寂しいとはどういうことなのか?自分でもよくわからないが、bloggreはなんか寂しい。。記事を書く気がおきない。で、Hatena Blogは無料だと広告がかなり邪魔なんだけど*1長年使い慣れたHatenaに戻ることにしました。Hatenaはほっとする~


まとめ:https://sites.google.com/site/chakokumatome/

*1:有料が高すぎるので払えない

RP2040のPIOで2bitバスで入力を受け付ける

ロータリエンコーダのドライバをPIOで作ってみようと思い、まずは、2bitバスを入力できるように設定してみる。
IN命令により2bit分をPin6,Pin7から入力してISRに格納、PUSH命令でISRからRX FIFOにPUSHする。while文でgetメソッドでFIFOから読み取る。

            PUSH        IN                   2bitバス
[RX FIFO]<---[ISR]<-------(Pin6, Pin7) <-------/---- [ロータリエンコーダ]

以下がテストプログラム。

#
#  PIO  IN  test 
#

from machine import Pin
from rp2 import PIO, StateMachine, asm_pio

PIO_IN_PIN0  = Pin(6, Pin.IN, Pin.PULL_UP)
PIO_IN_PIN1  = Pin(7, Pin.IN, Pin.PULL_UP)

@asm_pio(in_shiftdir=PIO.SHIFT_LEFT)
def ingo():
    in_(pins, 2)
    push()

sm = StateMachine(0, ingo, freq = 2000, in_base=PIO_IN_PIN0)
sm.active(1)

while True:
   print(hex(sm.get()),end="")

実行結果は以下。GP6,GP7はPULL UPしているのでIOに何も接続していない時はHとなり、0x3が続く。ジャンパでGNDに接続するとLになり、0x2,0x1,0x0等に変化する。

0x30x30x30x30x30x30x30x30x30x30x30x30x30x30x30x30x30x30x20x20x20x20x20x20x20x20x20x20x20x20x20x20x20x20x20x20x20x20x20x20x20x20x20x20x20x20x20x00x00x0

RP2040のPIOを引き続き調べる

PIOでロータリエンコーダのドライバを作ってみたいと思い調査
使い慣れているMicroPythonでPIOを叩きたい。やりたいのは、エンコーダからの2本のパラレル信号を受けたいのだが。。簡単に処理するには、2つのIOを束ねてバス幅2bitとして扱いたい。英語表現では、multiple consecutive pins (複数の連続するピン)となって、RP2040のSDKではIOのbaseに加え、バス幅を指定できるようなのだが、、MicroPythonからバス幅を指定できるかどうか。。
MicroPython版のPIOライブラリはあまり仕様の説明がないような。。。
ソースを見ながら推測する(CによるAPI仕様があまり分かっておらず)
Gitのソース*1を参考にしながらrp2.StateMachineで指定可能な引数は以下だろう

prog, freq, jmp_pin, 
in_base, out_base,set_base, sideset_base,
in_shiftdir, out_shiftdir,
push_thresh, pull_thresh,    

かなりできなさそうな印象。

間違っているかもしれんが、Pi Pico C/C++SDKに、sm_config_set_out_pinsの仕様が書かれていて、out_baseがPin番号で、out_countがビット幅と思ってるのだが。。それとも、out_countとは、単にout_baseからのオフセット値??

4.1.15.3.13. sm_config_set_out_pins

static void sm_config_set_out_pins (pio_sm_config *c,
uint out_base, uint out_count)

Picoをロジアナにしてしまうサンプルがあり、ここでもバス幅をcountで指定しているので、入力のIOを束ねることができるようだ。

uint16_t capture_prog_instr = pio_encode_in(pio_pins, pin_count);

rp2040-logic-analyzer/rp2040-logic-analyzer.c at main · gamblor21/rp2040-logic-analyzer · GitHub

これをMicroPythonから設定したいのだが、、

多分無理そうなのでこれ以上は追わない。忘れるので覚書

smオブジェクトへの操作

put FIFO(TX FIFO)への書き込み
get FIFO(RX FIFO)からの読み込み

PIOアセンブラ内で使える命令(主に出力)

pull FIFO(TX FIFO)からシフトレジスタ(OSR)への読み込み
out シフトレジスタ(OSR)から任意のリソースへのシフト

PIOアセンブラ内で使える命令(主に入力)

in シフトレジスタ(ISR)をシフトしてから任意のリソースから読み込み
push シフトレジスタ(ISR)からFIFO(RX FIFO)への書き込み

データ操作

set 任意のリソースへの任意の値の書き込み
mov 任意のリソース間の値の移動

実行制御

jmp 条件付きjump

jmp命令で使える条件

"not_x" (!x) ,  "x_dec"  (x--),    "not_y" (!y),  "y_dec" (y--), "x_not_y"  ( x != y),
"pin":,  "not_osre"  (OSR),

設定できる4系統の入出力

  • in_base(アセンブラ内ではout pinsとして指定(例:in_(pins, 1)),
  • out_base(アセンブラ内ではin pinsとして指定(例:out(pins, 1)),,
  • set_base, (set命令により操作されるPIN (例:set(pins, 0)))
  • sideset_base,(アセンブラ内でsideset操作するとsideset_baseで指定したPINから出力(例:.side(0x1))

分からないのは、、movの時はどの設定が使われる??
仕様書を確認すると、movでsourceがpinsの場合はin_base、destがpinsの場合はout_baseとあった。合理的な設計だ。。

さらに調べていると、、出力はバスとして束ねられるが、入力は束ねられのか?

  • static void sm_config_set_out_pins (pio_sm_config *c, uint out_base, uint out_count)
  • static void sm_config_set_in_pins (pio_sm_config *c, uint in_base)

PIOレジスタ仕様書にも、OUT_COUNTのフィールドがあるけど、IN_COUNTというのがない。だから、、2本の信号を受け取りたかったら、ステートマシンを2つ動かす必要がある??本当??しかし、、ロジアナサンプルでも入力を束ねていたから、それができないはずはない。暗黙で入力はバス幅32なのでは??とも思えるのだが。。そんなはずはないか。

ロジアナサンプルで入力バスを指定しているのは以下の関数

 inline static uint pio_encode_in(enum pio_src_dest src, uint value) {
     valid_params_if(PIO_INSTRUCTIONS, !(src & _PIO_INVALID_IN_SRC));
     return _pio_encode_instr_and_src_dest(pio_instr_bits_in, src, value);
 }

inlineで展開されていて、最後は以下の関数のようだ

inline static uint _pio_encode_instr_and_args(enum pio_instr_bits instr_bits, uint arg1, uint arg2) {
    valid_params_if(PIO_INSTRUCTIONS, arg1 <= 0x7);
#if PARAM_ASSERTIONS_ENABLED(PIO_INSTRUCTIONS)
    uint32_t major = _pio_major_instr_bits(instr_bits);
    if (major == pio_instr_bits_in || major == pio_instr_bits_out) {
        assert(arg2 && arg2 <= 32);
    } else {
        assert(arg2 <= 31);
    }
#endif
    return instr_bits | (arg1 << 5u) | (arg2 & 0x1fu);
}

参照先
https://raspberrypi.github.io/pico-sdk-doxygen/pio__instructions_8h_source.html

これは、PIOアセンブラなのか??
あらためてソースを読み直すと、、下記となっており、PIO用の命令を生成して、.instructionsに渡している。だから、PIOの命令を使ってバス幅を指定しているようだ。そんな命令あったか?? それにしても興味深い。。PIO命令で入力のバス幅を指定する??そんなの可能か?

    uint16_t capture_prog_instr = pio_encode_in(pio_pins, pin_count);
    struct pio_program capture_prog = {
            .instructions = &capture_prog_instr,
            .length = 1,
            .origin = -1
    };
    uint offset = pio_add_program(pio, &capture_prog);

IN命令を読み直すと、Bit countフィールドを指定すると、in_baseで指定したPinから指定分をシフトしながら読み込んでくれると書かれていた。(in/outのbitってどういう意味かイマイチわからんと思って放置していた)結局そういうことか。

IN always uses the least significant Bit count bits of the source data. For example, if PINCTRL_IN_BASE is set to 5, the
instruction IN 3, PINS will take the values of pins 5, 6 and 7, and shift these into the ISR. First the ISR is shifted to the left
or right to make room for the new input data, then the input data is copied into the gap this leaves. The bit order of the
input data is not dependent on the shift direction.

これは実にありがたい仕様だ。。

IN命令は、常にソースデータの最下位ビットカウントビットを使用します。 たとえば、PINCTRL_IN_BASEが5に設定されている場合、IN 3, PINSの命令を実行すると、ピン5、6、および7の値を取得し、これらをISRにシフトします。 最初にISRが左または右にシフトされて新しい入力データ用のスペースが確保され、次に入力データがこの隙間にコピーされます。 入力データのビット順序はシフト方向に依存しません。

調べてないけど、outも同じような考え方だろうか。。あっちはOUT_COUNTがあるのだが。。
out命令はin命令とちょっと違うようだ。

A 32-bit value is written to Destination: the lower Bit count bits come from the OSR, and the remainder are zeroes.

初めにちゃんとマニュアル読まなかったから遠回りしたけど*2、なんとかドライバ開発ができそうな気がしてきた。応用の効く仕様で実装されていることに感謝!!

MicroPython PIOサンプル
https://github.com/micropython/micropython/tree/master/examples/rp2


安定版だと以下のエラーが出るので、unstable版で試す

=== @asm_pio(in_shiftdir=PIO.SHIFT_LEFT)
=== def ingo():
===     in_(pins, 2)
===     push()
===
===
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "rp2.py", line 228, in asm_pio
  File "rp2.py", line 36, in __init__
ImportError: no module named 'array'

*1:https://github.com/micropython/micropython/blob/master/ports/rp2/rp2_pio.c

*2:しかもなんとなく分からない仕様を放置していた

RP2040のPIOを使ってモールス符号を出力する(その2)

前回の試作では、ドット(・)とダッシュ(ー)のみPIOで出力して、スペースはtimerで待たせていた。このため、FIFOに先読みされてしまって、Pythonによるスペースの待ち時間のタイミングと、PIOによる・ーの発生タイミングがずれる問題があった。これを解消するには、スペースの待ち時間もPIOで処理させる必要があった。その問題を解決したサンプルが以下。ステートマシン内で、ドット(・)、ダッシュ(ー)、スペースを生成させることで、タイミングのずれを解消している。ステートマシンのコードは実質アセンブラなのでどうしてもスパゲッティになってしまう。

from machine import Pin
from rp2 import PIO, StateMachine, asm_pio
import utime

@asm_pio(set_init=PIO.OUT_LOW,out_shiftdir=PIO.SHIFT_RIGHT)
def cond_test():
    set(y, 0x00)
    pull()
    out(x, 4)          # mov 4bit
    jmp(x_not_y,"space")

    #
    label("cw_start")
    out(x,4)           # mov next 4bit to x
    label("dash")
    set(pins, 1) 
    label("dashloop")  
    set(y, 0xf)
    label("waitloop0")
    nop()                [0x1f]   # wait
    jmp(y_dec, "waitloop0")
    jmp(x_dec, "dashloop")
    set(x,0x1)
    jmp("space")

    # 
    label("space_asit")
    out(x,4)           # mov next 4bit to x

    # space
    label("space")
    set(pins, 0)   
    label("spaceloop")
    set(y, 0x14)
    label("waitloop1")
    nop()                [0x1f]   # wait
    jmp(y_dec, "waitloop1")
    jmp(x_dec, "spaceloop")

sm = StateMachine(0, cond_test, freq = 7000, set_base=Pin(25))
sm.active(1)

while True:
  sm.put(0x10)  # dot
  sm.put(0x10)  # dot
  sm.put(0x10)  # dot
  sm.put(0x03)  # space
  sm.put(0x50)  # dash
  sm.put(0x50)  # dash
  sm.put(0x50)  # dash
  sm.put(0x03)  # space

Pico(RP2040)のPIOでモールス点滅サンプルを作ってみた

RP2040のPIOを理解するためいろいろテストしていて、文字列を入れるとモールス符号にして点滅するサンプルを作ってみた。
問題点としては、、点滅パターンをFIFOに入れるのだけど、FIFOでバッファしてしまって先読みのような状態になってcharacter間の空白がうまく働かない。空白をメイン側でwaitしているのがまずい。waitもPIO内で実装するように変えるべき。

#
# CW by PIO of RP2040
#

from machine import Pin
from rp2 import PIO, StateMachine, asm_pio
import utime

LED = 25

CODE = { 'A' : ".-", 
         'B' : "-...",  
         'C' : "-.-.", 
         'D' : "-..", 
         'E' : ".", 
         'F' : "..-.", 
         'G' : "--.", 
         'H' : "....", 
         'I' : "..", 
         'J' : ".---", 
         'K' : "-.-", 
         'L' : ".-..", 
         'M' : "--", 
         'N' : "-.", 
         'O' : "---", 
         'P' : ".--.", 
         'Q' : "--..", 
         'R' : ".-.", 
         'S' : "...", 
         'T' : "-",          
         'U' : "..-", 
         'V' : "...-", 
         'W' : ".--", 
         'X' : "-..-", 
         'Y' : "-.--",  
         'Z' : "--..",  
}


@asm_pio(set_init=PIO.OUT_LOW)
def cw_out():
    pull()
    mov(x, osr)

    # signal (dot or dash)
    set(pins, 1) 
    label("dashloop")  
    set(y, 0xf)
    label("waitloop0")
    nop()                [0x1f]   # wait
    jmp(y_dec, "waitloop0")
    jmp(x_dec, "dashloop")

    # space
    set(pins, 0)   
    set(y, 0x14)
    label("waitloop1")
    nop()                [0x1f]   # wait
    jmp(y_dec, "waitloop1")

sm = StateMachine(0, cw_out, freq = 6000, set_base=Pin(25))
sm.active(1)

def morse(str):
    for ch in str:
        print(ch, end="")
        if ch == " ":
            utime.sleep(3)
        else:
            morse_chr(ch)
            utime.sleep(1.5)

def morse_chr(ch):
    for ptn in CODE[ch]:
        for ch in ptn:
            if ch == '.':
                sm.put(1)
            elif ch == '-':
                sm.put(4)


morse("A B C HELLO")

子供(大学生だが)のノートPCのHDDをSSDに載せ替え

子供のノートPCがHDDアクセス集中で??で動かなくなるとのことで、HDD->SSDの載せ替えをやってみる。
PCはThinkPadのX260で、入っているHDDは、WD5000LPLX 。いつものように、、SSDを買ってきて、外付けディスクとしてマウントしてボリュームコピーして入れ替える。以前載せ替えに使ったのと同じ、WDの2.5インチ500G SSDにする予定。

今入っているHDD:
WD HDD 内蔵ハードディスク 2.5インチ 500GBHDD( WD Black WD5000LPLX SATA3.0 7200rpm 32MB 7.0mm)

載せ替え予定のSSD
Western Digital SSD 500GB WD Blue PC PS4 2.5インチ 内蔵SSD WDS500G2B0A-EC

USB<->SATAの下駄をさしてSSDを接続したが、Dドライブに出てこない*1Windowsの「ディスクの管理」には初期化されてないDドライブが見えているので、ボリューム(パーティションだったか?)を作って、NTFSで初期化した(確か)。この操作により空のDドライブができた。
クローン操作については当初、EaseUSのサイトから無料版バックアップツールをDLしたが、クローン操作も無料では使えないようであった(操作ミスかもしれませんが)。窓の杜からDLすると同じV13.0だが、こちらはクローン操作にロックがかかっておらず問題なくクローン操作できた。
「EaseUS Todo Backup」無料のイメージバックアップソフト - 窓の杜

X260はオプションで内蔵バッテリーを付けたので、BIOSから内蔵バッテリーの電源をOFFにしてから蓋を開ける。確かに、バッテリー側に爪がかみ合わさっている部分がある。開け方としては、ノートの手前側の隙間から段々と広げてゆくように開ける必要あり。奥のバッテリー側から開けようとすると爪を飛ばしてしまうと思う。

X260の裏ブタをはずしたところ。

HDDをはずしてSSDに入れ替えた。

全部組み立ててから起動しないとなるのは嫌なので、仮配線でブートしてみた。エラーが出ることなくOSは起動できた。だが。。SSDなのにあんまり体感的に早くない。娘も、別に変わらんなーという始末。。確かに起動ももっさりしているし、ファイルのセーブも普通だし。クリーンインストールだと速さも違うのだろうけど、今までのHDDも結構いい性能だったということか。。あとは、固まらくなったという改善に期待したい。

というわけで、、ThinkPadの蓋を開けて中身を見ることができたのは良かったけど、SSDに載せ替えしてもあまり体感的なスピードアップは感じられず達成感が得られない結果となった。



■ご参考URL
HDD->SSDミラーリングは無料版のeaseusを使う予定(以下はクローン機能も有償かと)
EaseUS®完璧な無料データバックアップソフト - EaseUS Todo Backup Free
窓の杜からDLしたらクローン機能には鍵かかってなかった。
「EaseUS Todo Backup」無料のイメージバックアップソフト - 窓の杜

*1:前回どうだったか忘れた

Raspberry Pi Picoに搭載されているRP2040のPIOをMicroPythonから叩きたい・・が、、あまり仕様が分からない

RP2040はWiFi/BLEがないので、どう使ったらいいのやらと思っていたけど、プログラミングできるステートマシンが搭載されたPIOが熱いと思う。多分。
CからPIOを叩くのが本筋かもしれないが、コンパイルしないといかんので面倒で、Try&Errorをなんどもやりたい場合はMicroPytonが便利と思う。なんだけど、、MicroPython版でPIOを叩くための情報があまりない。サンプルソースもあるにはあるけど、説明がないので、細かい部分がどうなっているのかわからない。仕様もStateMachine用のアセンブラとちょっと違ってるように思えるのだけど。。Pythonでputを使ってるけど、アセンブラにはPULL/OUTはあるけどPutは無いのでは?? 勉強中だから勘違いかもしれんが。Raspberry Pi Pico Python SDKとかいろいろ仕様書を読むのだけど、MicroPythonの表記法とPIOのハードウエアアーキテクチャが頭の中でリンクしないというか。。

sm.put(ar,8) というのが、8bitを除いて残り24bit(単位がbitかbyteか正確には理解できていません)を渡すということらしいが、、putって一体何・・


■追記
putはTxFIFOへの書き込み操作らしい。RxFIFOからの読み込みは、getの様だ。FIFOのR/W時にどれだけのbit数が欲しいのかを引数で指定するのだろう。多分。(単位はbit? byte??) 該当のソースコードは多分以下なのだが、、Python->Cの呼び出しシーケンスが分かっておらず、読み解けない。。

micropython/rp2_pio.c at master · micropython/micropython · GitHub

// StateMachine.put(value, shift=0)
STATIC mp_obj_t rp2_state_machine_put(size_t n_args, const mp_obj_t *args) {
    rp2_state_machine_obj_t *self = MP_OBJ_TO_PTR(args[0]);
    uint32_t shift = 0;
    if (n_args > 2) {
        shift = mp_obj_get_int(args[2]);
    }
    uint32_t data;
    mp_buffer_info_t bufinfo;
    if (!mp_get_buffer(args[1], &bufinfo, MP_BUFFER_READ)) {
        data = mp_obj_get_int_truncated(args[1]);
        bufinfo.buf = &data;
        bufinfo.len = sizeof(uint32_t);
        bufinfo.typecode = 'I';
    }
    const uint8_t *src = bufinfo.buf;
    const uint8_t *src_top = src + bufinfo.len;
    while (src < src_top) {
        uint32_t value;
        if (bufinfo.typecode == 'B' || bufinfo.typecode == BYTEARRAY_TYPECODE) {
            value = *(uint8_t *)src;
            src += sizeof(uint8_t);
        } else if (bufinfo.typecode == 'H') {
            value = *(uint16_t *)src;
            src += sizeof(uint16_t);
        } else if (bufinfo.typecode == 'I') {
            value = *(uint32_t *)src;
            src += sizeof(uint32_t);
        } else {
            mp_raise_ValueError("unsupported buffer type");
        }
        while (pio_sm_is_tx_fifo_full(self->pio, self->sm)) {
            // This delay must be fast.
            mp_handle_pending(true);
            MICROPY_HW_USBDEV_TASK_HOOK
        }
        pio_sm_put(self->pio, self->sm, value << shift);
    }
    return mp_const_none;
}

■追記

jmp命令で使える条件

    # jmp condition constants
    "not_x": 1,
    "x_dec": 2,
    "not_y": 3,
    "y_dec": 4,
    "x_not_y": 5,
    "pin": 6,
    "not_osre": 7,

GitHubより
micropython/rp2.py at master · micropython/micropython · GitHub


■ご参考URL

なんとなく活動記録。様のPIOまとめ記事
Raspberry Pi Pico(RP2040)のPIOについて備忘録(MicroPython)

「Get started with MicroPython on Raspberry Pi Pico」
HackSpace magazine
一見初心者向けの入門書のようだけど、最後の付録の章ではPIOについてもかなり詳しく書いている。これを見ながら勉強したが、、、分かり切らない。