Raspberry pi pico/pico2をVScode PlatformIOでC++開発② PIO/interrupt/周期回転計の製作

カテゴリー: シングルボードコンピュータ  タグ:

前記事「Raspberry pi pico/pico2をVScode PlatformIOでC++開発」の続き

前回、FreeRtOS/USBCDCを使ってテストプログラムを作りました。今回はさらに周期割り込みとPIOを使って周期回転計を製作しました

テストベンチの組立

上の写真は完成形ですが、まずはブレッドボードで実験ベンチを組んでみます

先に回路図。いつもは手書きですが、気まぐれで清書してみました

秋月販売の4桁LEDディスプレイOSL40391をダイナミック点灯させる。トリマを使ってベンチ内でテスト用のパルス発生(本チャン用は不要)

回路図とベンチ

その様子は

テストベンチ外観

①でもチラチラ外観出してましたがテストベンチの全景です。ベンチ内で簡易的に疑似回転信号を出してパルステストするときは、GP0とGP15を接続して、青色のトリマ抵抗で疑似回転数を調整して表示を確認します

PIO

サイト「Raspberry Pi Pico+Arduinoで周波数を測定する」を参考にさせていただきました。

参考サイトのPIOコードはHighの時間を測定する方式で、デューティ1:1の時に好結果がでますが、今回の応用ではデューティ1:1とは限らないので少し手を加えました

.pio_version 0 // only requires PIO version 0

.program periodic

begin:
	pull block			; FIFO からデータを OSR に読み込む
	mov x, osr			; x レジスタに初期カウント値を格納
	wait 0 pin 0		; pin 0 がローになるまで待機(ハイからロー)
	wait 1 pin 0		; pin 0 がハイになるまで待機(ローからハイ)

high_to_low:
	jmp pin count		; pin 0 がローなら count ラベルにジャンプ
	jmp low_to_high		; pin 0 がハイなら low_to_high ラベルにジャンプ

count:
	jmp x-- high_to_low	; x レジスタをデクリメントし、ゼロでない限り high_to_low に戻る

low_to_high:
	jmp pin exitloop	; pin 0 がローなら count ラベルにジャンプ
	jmp x-- low_to_high	; pin 0 がハイなら exitloop にジャンプ

exitloop:
	mov isr, x			; カウント結果を ISR に格納
	push block			; 結果を FIFO に送信

% c-sdk {
static inline void periodic_program_init(PIO pio, uint sm, uint offset, uint pin) {
	pio_sm_config c = periodic_program_get_default_config(offset);
//	sm_config_set_out_pins(&c, pin, 1);
	pio_gpio_init(pio, pin);
	// Set the pin direction to output at the PIO
	pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, false);
	sm_config_set_in_pins(&c, pin);
	sm_config_set_jmp_pin(&c, pin);
	// クロック分周なしでステートマシン実行
	sm_config_set_clkdiv(&c, 1.0f);
	pio_sm_init(pio, sm, offset, &c);
	// Set the state machine running
	pio_sm_set_enabled(pio, sm, true);
}
%}

PIOの条件分岐命令にはダウンカウントで飛ぶ命令しかないので、大きい数値を入れてダウンしていく方式がとられる

メインからFIFOに2^31をいれてPIOを呼ぶ。PIOではパルスのlow待ち・hight待ちを経て、hight期間+low期間のカウントをFIFOの値からダウンカウントして一周期分終了したらFIFOに格納してメインに戻る

一周期は(2^31-戻り値)/31.25MHzで得られる、という原理です

VScodeからpioasmを呼ぶ

拡張機能「Raspberry Pi Pico」ではbuildでpioasmも一括アセンブルできたのですが、さすがにplatformIOではスクリプトを組まないとできないらしいので、単純にVScodeとpioasmは別に配置して、ターミナルで実行する方式にしました。

以前picoSDKをインストールしてあるので、そこからpioasm.exeを取り出し、パスをシンプルにした「d:\pioasm\」フォルダを作成し格納しました

「cd src」は.pioソースファイルがプロジェクトフォルダ下の\srcフォルダにある前提です。VScodeで開いている間は一回チェンジしてしまえば、あとは「d:\pioasm\・・・」の繰り返しでアセンブルできるので、↑キーによるコマンドリピートでできます

一括buildでなくともターミナルでアセンブルできれば、まあ良しとします

「VScode内で完結」を目指した方法ですが、VScodeのターミナルではなくWindowsターミナルでもできるので、別ウィンドウでやれるほうが効率良いかもしれません

メインプログラム

arduinoとpicoSDK混在できる

LEDダイナミック点灯に周期割り込み「event_timer」を1msで駆動します。割り込みの中でLEDのdataとcolumnにそれぞれ8bit/4bitで出力しますが、picoSDKのバス出力の記述gpio_set_mask()で一気に出力しています。その他のGPIOはarduino記述のdigitalWrite()などです。arduinoとpicoSDK混在できるので便利です

周期割込みの宣言 add_repeating_timer_us()関数はpico専用の記述で、ESP32で使っていたxTimerCreate()がうまくいかないときはこちらで・・・すでに周知でしょうけど

メインプログラム

#include <Arduino.h>
#include <stdio.h>
#include <stdlib.h>
#include <FreeRTOS.h>
#include <task.h>
#include "periodic.pio.h"
#include "hardware/adc.h"
#include "hardware/pwm.h"

//	SEG A-DP -> GPIO2-9, COL 1-4 -> GPIO10-13
#define SEG_GPIO	2
#define	COL_GPIO	10
#define	MON			16
#define	PLSIN		15

PIO pio = pio0;
uint32_t sm;
uint32_t COUNT_START = 2147483648;
TaskHandle_t	mloopTaskHandle = NULL;
TaskHandle_t	blinkTaskHandle = NULL;

// 0-9 & blank 7segment pattern
uint8_t bits[11] = {
		0x3f,	// 0
		0x06,	// 1
		0x5b,	// 2
		0x4f,	// 3
		0x66,	// 4
		0x6d,	// 5
		0x7d,	// 6
		0x07,	// 7
		0x7f,	// 8
		0x67,	// 9
		0x00	// blank
};

uint32_t	col[4] = {0xe, 0xd, 0xb, 0x7};
volatile	uint8_t		ledbuf[4] = {0};
volatile int	timec;

bool event_timer(__unused struct repeating_timer *t) {
	static	uint8_t	scanc = 0;

//	gpio_put(MON,1);	// digitalWrite(MON,1);でも可
	uint32_t mask = 0b0011111111111100;	// GP2-13 clear
	gpio_clr_mask(mask);
	mask = (bits[ledbuf[scanc]] << SEG_GPIO) | (col[scanc] << COL_GPIO);
	gpio_set_mask(mask);
	scanc = ++scanc & 0x3;
//	gpio_put(MON,0);	// digitalWrite(MON,0);でも可
	return true;
}

void dispLED(int val) {
	ledbuf[3] = val % 10;
	ledbuf[2] = val / 10 % 10;
	ledbuf[1] = val / 100 % 10;
	ledbuf[0] = val / 1000 % 10;
	for (int i = 0; i < 3; i++) {	// 0サプレス処理(先頭の'0'をスペースに置き換え)
		if (ledbuf[i] == 0) {
			ledbuf[i] = 10; 	// 先頭の'0'をスペースに置換
		} else {
			break; // 最初に'0'でない桁が現れたら終了
		}
	}
}

uint slice_num;

void blink_task(void *pvParameters) {
	bool isOn = false;
	while (1) {
		digitalWrite(25, isOn = !isOn);
		vTaskDelay(500);
	}
}

void mloop_task(void *pvParameters) {
	while(1) {
		vTaskDelay(500);
		if (pio_sm_get_rx_fifo_level(pio, sm) > 0) {
			uint32_t count = pio_sm_get(pio, sm);
			pio_sm_set_enabled(pio, sm, false);	// ステートマシン停止
			float fval = round(1.0 / ((COUNT_START - count) * (1.0f / (double)clock_get_hz(clk_sys)) * 2.0f / 60.0f));
//			fval /= 20.0f;	// 12000rpmMax x2パルスのBLDM用
			Serial.printf("%5.0frpm ", fval);
			dispLED((int)fval);
			pio_sm_clear_fifos(pio, sm);		// ステートマシン再起動
			pio_sm_set_enabled(pio, sm, true);
			pio_sm_put(pio, sm, COUNT_START);
		} else {
			dispLED(0);		// パルスカウント無しのときは0表示
		}
		// adc値->10~200Hz
		uint16_t result = adc_read();
		float f = 10.0f + ((float)result-20.0f) / 4075.0f * 190.0f;
		uint32_t top = 125000000/(125*(uint32_t)f)-1;
		top /= 2;
		pwm_set_wrap(slice_num, top);	// 1/adc読取値を正規化してPWM出力(周期を調整する)
		pwm_set_chan_level(slice_num, PWM_CHAN_A, top / 2);
		Serial.printf("result:%d, wrap:%d chan:%d\r\n", result, top,  top/2);
	}
}

void setup() {
	uint32_t offset = pio_add_program(pio, &periodic_program);
	sm = pio_claim_unused_sm(pio, true);
	periodic_program_init(pio, sm, offset, PLSIN);
	// ステートマシン起動
	pio_sm_clear_fifos(pio, sm);
	pio_sm_set_enabled(pio, sm, true);
	pio_sm_put(pio, sm, COUNT_START);

	adc_init();
	adc_gpio_init(26);		// Make sure GPIO is high-impedance, no pullups etc
	adc_select_input(0);	// Select ADC input 0 (GPIO26)

	for (int gpio = SEG_GPIO; gpio < SEG_GPIO + 12; gpio++) {
		gpio_init(gpio);
		gpio_set_dir(gpio, GPIO_OUT);
		gpio_set_outover(gpio, GPIO_OVERRIDE_NORMAL);
	}

	gpio_set_function(0, GPIO_FUNC_PWM);
	slice_num = pwm_gpio_to_slice_num(0);
	pwm_set_clkdiv(slice_num, 255.0f);	// PWM周期を設定
	pwm_set_wrap(slice_num, 48921);
	pwm_set_chan_level(slice_num, PWM_CHAN_A, 24460);
	pwm_set_enabled(slice_num, true);	// PWM出力イネーブル

 	pinMode(25, OUTPUT);
	pinMode(MON, OUTPUT);
	gpio_init(PLSIN);
	gpio_set_dir(PLSIN,GPIO_IN);
	static repeating_timer_t timer;
	add_repeating_timer_us(1000, &event_timer, NULL, &timer);
	xTaskCreate(mloop_task, "mloop", 1024, NULL, 1, &mloopTaskHandle);
	xTaskCreate(blink_task, "blink", 256, NULL, 1, &blinkTaskHandle);
}

void wait_timec(unsigned int n) {
	timec = n;
	while(timec != 0) vTaskDelay(1);
}

void loop() {}

プログラムはpicoSDKのExample、platformIOのExample、参考サイトのプログラムをつぎはぎのようにくみ上げたものです。悪しからず

カウンタの精度

SGで確認しました

50.000Hz:3000rpm、400.000Hz:24000rpm表示はOK

ちょっとずらして下の写真を得る

50.01Hzで3001rpm

400.01Hzで24001rpm(4桁なので20000の桁は表示できていない)

モーター回転が目的なので、回転計としてはかなり満足な性能です

あとは、パルス入力のデバウンス除去などおいおい工夫していく予定です

お気軽にコメントをどうぞ。

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)