2020年12月31日木曜日

空調服を生体計測的にhack

  ほい。お久しぶりです。

今年はいろいろとありました。会社クビになったり、拾ってくれる会社があったり、拾われた所ではエンジニアのプライドがめっきり折られたりと…散々な感じですね。多分ですけど、1月末には契約が満期になって、また次の会社を探さないといけないですねぇ…。このご時世でどう探せばいいのやら…。


PSoCに逃げ込むぞ!!!


さて。


夏頃に「暑いけど、空調服は空調服のファンが回りっぱなしで身体が冷えすぎて良くないしなんかないかなー」と考えていました。思いつきでは「あ。生体計測していたし、体内調節機能と連動した空調服の制御が出来ないかな?」っと考えを広げていました。空調服で体内調節機能を連動したものは当時(2020年6月)ありませんでした。生体計測とは何かというと非侵襲、つまりは人体を切り開いたりせずに人体の様子を観察できるようにするという医療工学です。昔はこの分野に関わっていたので「どこ」を「どう」測れば「何が分かる」というのは分かりました。ただ今回のような空調服だとかなり難しいのです。なぜかと言うと次のような理由です。

  • 空調服は運動中に着用している。
  • 実験環境のような密室じゃないのでケーブルを吊り下げておけない
  • 野外に出るのであまりの重装備は怪しまれる。
  • 心電計は使えない(ディスポーザブル電極が手に入らない)
だからと言って雑な測定の仕方では生体情報の取得ができないので測定する範囲を絞ることで実現しようと考えました。空調服を制御する方針は次の通りです。
  • 脈拍を測定して運動負荷をみるようにする。
  • 脈拍数と体温は相関するはずなので、体温は測らない。
  • ボルグスケールに応じて空調服を制御する。
  • 外気の温度と湿度を測定して不快係数に応じて空調服を制御する。
このなかで難しいのは脈波測定です。アナログ回路から作る方法もあれば、センサーやモジュールを使うという方法もあります。アナログ回路から作ると大風呂敷になり過ぎるので脈波センサーを探すことにしました。探しているのは耳の血管で測定できるタイプの脈波センサーです。なぜ指ではダメかというときちんとした理由があります。安静時には心拍と末端の脈波はほぼ同じです。しかし運動時は心拍と末端の脈波は全く異なります。これは手先を動かすこともそうですが重力影響や血圧が変化するためです。末端の脈波の中で影響が比較的少ないのは耳や体幹で測ることの出来る脈波になります。
 おおよその方針が出来たので部品探しをしました。ただお目当ての「耳で測るタイプの脈波センサー」がありませんでした。基板から作ればいいのでしょうがそこまで行うエネルギーもなかったので指で測るタイプで妥協することにしました。


さて、使うセンサーの選定も行ったので全体設計を行いました。



メインはPSoC62xAでFreeRTOSが走っている状態です。(FreeRTOSが機能がシンプルで設計も実装も楽です…。)ユーザーボタンは割り込みで検知するようにしてvTaskNotifyGiveFromISR() でFreeRTOS内のタスクへ通知するようにします。そのほかはソフトウェアタイマーで定期的にセンサー値を取得してqueueに詰めて各タスクで使うような作りにしています。LCDならqueueでもらったデータを表示用のメモリに入れる。ファン制御の所はqueueでもらったデータを使ってファンのスピードを変更するというような切り分けです。私の図の書き方はFPGA屋さんのそれと同じなのでタスクはFPGA屋さんで言う所のモジュールみたいな立ち位置になっています。階層化したものを上から見るか横から見るかの差なだけでほとんどやっていることは同じです。

回路やら体表面用のメカ部品やらを作って、実装をするとこんな感じです。





今回は腕に着けられるようにしましたが、これだけでも怪しさが爆発ですね…。
あまりよろしくないです……。
すべてを組み立てた状態はこのような感じです。



プログラムの構造もすごくシンプルなのでかなり堅い作りかなと思います。
プラスアルファでフレームワークとかも入れていないのでメンテナンス性も良いコードになったと思います。

総括
良い感じ。ただ脈波センサーは変えないとかなり残念な感じになりそう。
今回の改造空調服は「バリアスーツ」と呼んで置きましょう。
今後はリュックに制御装置やらバッテリーが入るようにしようと思います。
どんどんプレデターに出てくるようなスーツのようになりそうです。
いっそ、肩からBB弾とかミサイルを発射出来るようにしてしまうのが
面白いかもしれませんね(当面その予定はないですが…)。
ではまたー。

2020年5月28日木曜日

PSoC6向けI2C_CharLCDライブラリを作ってみた。

今回はPSoC6向けにI2CでつながるキャラクタLCD用ライブラリを作ってみました。
PSoC5LPとかでちゃちゃっと表示器が欲しい時にはキャラクタLCDのコンポーネントを使って表示させることが出来ます。PSoC5LPやPSoC4だとキャラクタLCDのコンポーネントはパラレル接続とI2C接続の2つから選ぶことが出来ます。PSoC6でもちゃちゃっと表示器が欲しい所なのですが、リソースがたくさんあるにもかかわらずキャラクタLCDのコンポーネントはありません。パラレルTFTのコンポーネントがあるにはあるのですが毎回持ち出すのはちょっと大げさですし、一部のPSoC6には対応できません。なので今回はPSoC6向けにI2Cで接続するキャラクタLCD用のライブラリを作ることにしました。

 まず、このライブラリについての説明をする前にPSoC6で使われるライブラリを知る必要があります。PSoC6のライブラリはPDLとHAL(以降はCYHALとします)の2つがあります。PDLはペリフェラルドライバーライブラリーの略で文字通りPSoC6のペリフェラルを使うためライブラリーです。CYHALはPDLを元に作られています。おそらく、PDLのバージョンを切り替えても動作することを目的にしているのだと思われます。PSoC6の開発環境はPSoC Creator と modus ToolBoxの2つがあるのですが、PDLはどちらの環境でも使われていますがCYHALライブラリはmodus ToolBoxにだけ使われています。CYHALベースでI2C_CharLCDライブラリを作ってしまうと、PSoC Creatorで使うことが出来ません。なのでI2C_CharLCDライブラリはPDLをベースに作ることにしました。また、PSoC6ではRTOSを多用することも多いのでRTOSを使っても問題が起きにくいようにしました。PDLの厄介な所は構造体やポインタを多用するのでちょっとメモリー周りに意識が向いていないと使いこなせない所ですね。きちんとC言語が分かっている、C言語向けのオブジェクト指向を知っている必要があります。

さて、I2C_CharLCDライブラリの説明に入ろうと思います。AQM0802Aで動作確認をしていますが、コントローラICがST7032であればI2C_CharLCDライブラリが適応できると考えられます。I2C_CharLCDライブラリはmodus ToolBox、PSoC Creatorの両方の環境で動作できます。I2C_CharLCDライブラリの関数は5つと構造体が1つあります。それぞれの関数と構造体は次の通りです。

void I2C_CharLCD_Init(I2C_CHAR_LCD_HANDLE *handle, CySCB_Type *base,cy_stc_scb_i2c_context_t *context)
I2C_CharLCD_InitはI2C_CharLCDで使うI2C_CHAR_LCD_HANDLE構造体の初期化、キャラクタLCDそのものを初期化をします。

void I2C_CharLCD_Clear(I2C_CHAR_LCD_HANDLE *handle)
I2C_CharLCD_Clear()はキャラクタLCDの表示を消します。

bool I2C_CharLCD_Set_Cursor(I2C_CHAR_LCD_HANDLE *handle, uint8_t col, uint8_t row)
I2C_CharLCD_Set_Cursor()は表示のどこから表示をするかを設定します。

bool I2C_CharLCD_PutString(I2C_CHAR_LCD_HANDLE *handle, uint8_t *string, uint16_t length)
I2C_CharLCD_PutString()は文字列を表示します。

void I2C_CharLCD_PutChar(I2C_CHAR_LCD_HANDLE *handle, uint8_t putstr)
I2C_CharLCD_PutChar()は文字を1字だけ表示します。

I2C_CHAR_LCD_HANDLE
I2C_CHAR_LCD_HANDLE構造体はI2C_CharLCDライブラリのすべてで使います。

RTOSを使わない場合はI2CCharLCDConfig.hの#define Free_RTOS_ONをコメントアウトします。RTOSを使う場合はI2CCharLCDConfig.hの#define Free_RTOS_ONと書きます。

サンプルプログラムは次の通りです。
サンプルプログラムmodusToolboxで書きました。

#include "cy_pdl.h"
#include "cyhal.h"
#include "cybsp.h"
#include "FreeRTOS.h"
#include "task.h"
#include "I2CCharLCD.h"
#include "stdio.h"

void ledTask(void* prameter);
TaskHandle_t ledtask_h;

void lcdTask(void* prameter);
TaskHandle_t lcdtask_h;

int uxTopUsedPriority;
cy_stc_i2s_context_t i2cContext;
void I2C_ISR(void);

const cy_stc_sysint_t i2cIntrConfig =
{
    .intrSrc      = I2C_0_IRQ,
    .intrPriority = 6
};

int main(void)
{
    cy_rslt_t result;

    /* Initialize the device and board peripherals */
    result = cybsp_init() ;
    if (result != CY_RSLT_SUCCESS)
    {
        CY_ASSERT(0);
    }
    uxTopUsedPriority = configMAX_PRIORITIES - 1;
    xTaskCreate(ledTask, "LEDTASK", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, &ledtask_h);
    xTaskCreate(lcdTask, "LCDTASK", 256, NULL, tskIDLE_PRIORITY + 2, &lcdtask_h);
    vTaskStartScheduler();

    __enable_irq();

    for (;;)
    {
    }
}

void ledTask(void* prameter)
{

for(;;)
{
Cy_GPIO_Set(LED0_PORT, LED0_PIN);
vTaskDelay(250);
Cy_GPIO_Clr(LED0_PORT, LED0_PIN);
vTaskDelay(250);
}
}


void lcdTask(void* prameter)
{
Cy_SCB_I2C_Init(I2C_0_HW,  &I2C_0_config, &i2cContext);
Cy_SCB_I2C_SetDataRate(I2C_0_HW, 100000, Cy_SysClk_PeriphGetFrequency(CY_SYSCLK_DIV_16_BIT, 0U));
/* Hook interrupt service routine and enable interrupt */
Cy_SysInt_Init(&i2cIntrConfig, &I2C_ISR);
NVIC_EnableIRQ(I2C_0_IRQ);
Cy_SCB_I2C_Enable(I2C_0_HW);

    I2C_CHAR_LCD_HANDLE aqm;

    I2C_CharLCD_Init(&aqm, I2C_0_HW, &i2cContext);
    I2C_CharLCD_PutString(&aqm, (uint8_t*) "count", 5);
    I2C_CharLCD_Set_Cursor(&aqm, 0, 1);

    uint8_t count = 0;
    uint8_t strCount[5] = {};
for(;;)
{
snprintf((char*) strCount, 5, "%d  ", count);
I2C_CharLCD_PutString(&aqm, strCount, 5);
count++;
if(count > 255)
{
count = 0;
}
vTaskDelay(250);
//I2C_CharLCD_Clear(&aqm);
}
}

void I2C_ISR(void)
{
    Cy_SCB_I2C_Interrupt(I2C_0_HW, &i2cContext);
}
/* [] END OF FILE */

サンプルプログラムが正しく動くと次のようになります。



一応ですが、I2C_CharLCDライブラリのソースファイルを置いておきます。

2020年5月8日金曜日

PSoC5LPでDMA転送をする

しばらくぶりです。
世間も世間で大変なことになっておりますが、私も色々と大変なことになってなってきてしまいました。
おし!!PSoCに逃げ込むぞ!!

今回はPSoC5LPでDMA転送をしようと思います。
DMA転送なんじゃいなって言うところに触れる前にペリフェラルへのデータのアクセスについてちょっとおさらい。
 通常のペリフェラルのデータレジスタにアクセスする場合、CPUがペリフェラルのレジスタ番号を使ってデータを取ってきていました。端的に言ってしまえばCPUがレジスタ番号の0x0000番地にあるデータを取得するとかですね。また「ペリフェラル側でデータの用意出来たら、CPUにお知らせしてね。そうしたらCPUがペリフェラルのデータレジスタにアクセスするよ。」っていう割り込み処理(IRQ)を使ったデータレジスタへのアクセスがあります。どちらともペリフェラルからデータを取得する際に「CPUがデータを取得する」という事が発生します。
 「CPUがデータを取得する」というのは時として難しいケースもあります。例えばADCからの連続データを取得したいけど、表示や計算にもCPUパワーを使いたい時。ほかにもありますねSPI通信で大容量のデータをセンサーやモジュールに流し込みたい時等々。こういうケースは結構あります。そこで「CPUを使わないでデータの移し替えが出来ないかしら…。」って登場するのがDMA転送です。

DMA転送についてを触れようと思うと、話が膨らみすぎるのでポイントを絞って進めようと思います。
① DMAコントローラーの行えることについて
② DMAコントローラーが何を手がかりデータを転送しているかについて
③ DMAコントローラーはどのタイミングでデータの転送をするかについて
の3つで考えておくと良いと思います。あえてCPUとペリフェラル間の通信バスについては触れないです。優先順位や通信バスの規格の話になってややこしいので。

① DMAコントローラーの行えることについて
DMAコントローラーの行えることは4つあります。どれもデータを送ったり、受け取ったりすることです。
・メモリー(flashも含め)からペリフェラルにデータを送る。
・ペリフェラルにあるデータをメモリーへ送る。
・メモリーからメモリーへデータを送る(移す)。
・ペリフェラルからペリフェラルへデータを送る。

② DMAコントローラーは何を手がかりにしてデータの転送をしているか
これは3つあるのですがちょっと複雑です。
・ペリフェラルやメモリーに割り当てられているアドレス
・送るデータの総数
・1個当たりのデータ長さ
これらを頼りにDMAコントローラーはデータ転送をします。
これだけだとピンと来ないと思うので簡単に例を挙げようと思います。


ペリフェラル(送り元)からメモリー(送り先)にDMAが転送するとします。
送るデータの総数は32byte、1個当たりのデータは1byteとします。
送り先のメモリーはuint8_tで32byte分の配列になっていてます。(送り先のメモリーは配列じゃないと送れないです。)
DMAコントローラーはペリフェラルのアドレスは分かっているので、ペリフェラルから1個目のデータを取得します。DMAコントローラーは取得したデータをメモリーへ1個目格納します。格納が終わったらまたDMAコントローラーはペリフェラルからデータを取得してメモリーへ格納するわけですがちょっと問題ですね。メモリーのアドレスが分かっている1個目の配列にはもうデータが入っています。そこでDMAコントローラーは転送したデータの個数を数えることで次に格納するメモリーのアドレスを特定することが出来ます。
メモリー側の配列の1番目のアドレスが0x0001であれば、カウンタの値と合わせて2番目は0x0002、3番目のアドレス0x0003というようになります。

③ DMAコントローラーはどのタイミングでデータの取得を行うか
DMAコントローラーにデータの取得を伝えることを「DMA request」といいます。
このDMA requestはデータの送り元がDMAコントローラーに伝えることでDMA転送が開始されます。ペリフェラルでは内部割込みのピンがDMA requestを操作したり、メモリ間での転送はCPUがDMA requestを操作することもあります。
DMAコントローラーへDMA requestを伝えるタイミングはケースバイケースなのです。例を挙げておくと、通信系のペリフェラルで受信時にペリフェラル側のデータが溜まった時にDMA requestを行います。送信時ではペリフェラル側の送るデータが無い時にDMA requestを行います。

ここからは実際にPSoC5LPでDMA転送を行います。
今回はUARTの受信をDMA転送でSRAMに移すことをします。
コンポーネントの接続は次の通りです。


DMA requestは次の通りです。
UARTの場合Level edge設定じゃないとDMA requestがかかりません。


プログラムはFreeRTOSが入った状態で書きました。
現状のコードだとデバッガを当てないと通信テストが出来ないです。

void vGPStask()
{
    uint8_t gpsbuff[32];
   
    /* Defines for DMA_rx */
    #define DMA_rx_BYTES_PER_BURST 1 //1回のデータの長さ(byte単位)
    #define DMA_rx_REQUEST_PER_BURST 1 //request毎に転送
    #define DMA_rx_SRC_BASE (CYDEV_PERIPH_BASE) //転送元
    #define DMA_rx_DST_BASE (CYDEV_SRAM_BASE) //転送先
   
    /* Variable declarations for DMA_rx */
    /* Move these variable declarations to the top of the function */
    uint8 DMA_rx_Chan;
    uint8 DMA_rx_TD[1];
   
    /* DMA Configuration for DMA_rx */
    DMA_rx_Chan = DMA_rx_DmaInitialize(DMA_rx_BYTES_PER_BURST, DMA_rx_REQUEST_PER_BURST,
        HI16(DMA_rx_SRC_BASE), HI16(DMA_rx_DST_BASE));
    DMA_rx_TD[0] = CyDmaTdAllocate();
    CyDmaTdSetConfiguration(DMA_rx_TD[0], 32, DMA_rx_TD[0], CY_DMA_TD_INC_DST_ADR);//DMAのデータのカウントと送る設定
    CyDmaTdSetAddress(DMA_rx_TD[0], LO16((uint32)UART_1_RXDATA_PTR), LO16((uint32)gpsbuff));// 転送元と転送先のアドレス
    CyDmaChSetInitialTd(DMA_rx_Chan, DMA_rx_TD[0]);   
    CyDmaChEnable(DMA_rx_Chan, 1);//DMAの有効化
    UART_1_Start();//UARTの有効化

    for(;;)
    {
        vTaskDelay(250);
    }
}

転送テストを行った結果
tera tarmから文字で「123」と入力しました。
バッファの先頭から1(0x31)、2(0x32)、3(0x33)が入っています。

注意事項
PSoC5LPのDMAは最大で8chまでです。複数のDMAを使う場合はDMA同士の優先順位を決める必要があります。またDMAのカウンタは4095なのでそれを超えるデータの量を取り扱う場合はDMAの設定を組み替える必要があります。DMAと割り込みを組み合わせるケースもあります。DMAのコードジェネレータ(DMA Wizard)の使い方や詳細はアプリケーションマニュアルAN52705 - PSoC® 3 and PSoC 5LP - Getting Started with DMAを読んでください。

2020年1月19日日曜日

PSoC5LPでFreeRTOSを動かす。

今回はPSoC5LPでFreeRTOS V10を動かすことを行った。
以前はCypressのアプリケーションノートの中にFreeRTOSへ適用の仕方が書かれていたと記憶していた。今回調べたところ、アプリケーションノートがそのものがサイト等から消えてしまっていた。FreeRTOSのサイトにはPSoC5LPのサンプルコードがあるため、それをたたき台としてFreeRTOS V10をPSoC5LPへ適用することとした。
 サンプルコードは余計な内容が書かれていた。このままの状態では動作ができないので動作に必要となるコードを取り出して、適用することとした。必須となるスーパーバイザーコールとシスティックの部分はmain.c内に書かれていたので、新しい適用もこれに沿えば大丈夫だと思われる。使い方はスレッドスタートやタスクの登録以前に実行する。
下記はスーパーバイザーコールとシスティックのプログラム部分となる。
extern void xPortPendSVHandler( void );
extern void xPortSysTickHandler( void );
extern void vPortSVCHandler( void );
extern cyisraddress CyRamVectors[];

/* Install the OS Interrupt Handlers. */
CyRamVectors[ 11 ] = ( cyisraddress ) vPortSVCHandler;
CyRamVectors[ 14 ] = ( cyisraddress ) xPortPendSVHandler;
CyRamVectors[ 15 ] = ( cyisraddress ) xPortSysTickHandler;

あとはFreeRTOSでダウンロードしたソースファイルからCortexM3用のFreeRTOSデータをまとめれば、FreeRTOSに必要なデータがそろう。注意が必要な点はportableディレクトリ内のデータとなる。このディレクトリはCPUのコアとコンパイラ毎に異なったシスティックのプログラムとスーパーバイザーコールのプログラムが保存されている。さらにFreeRTOSが使うメモリー管理アルゴリズムのプログラムもここに格納されている。システィックのプログラムとスーパーバイザーコールのプログラムが書かれたファイルは、すべて同じファイル名となっている。つまり、ファイルの操作を間違えるとプログラムが変質することになる。
下記は今回参照したファイルのディレクトリ構造
FrerRTOSv10.2.1
┣FreeRTOS
┃┣Demo(MCUごとに作られたデモプログラムの一式)
┃┣License
┃┗Source(FreeRTOSの本体)
┃ ┣include
┃ ┗portable
┃  ┣MemMang(heap_1 heap_2 heap_3 heap_4 heap_5のいずれかが必要)
┃  ┣GCC
┃  ┃┣ARM_CM3(port.c portmacro.hがある。)
┃  ┃┗そのほかのCPU用ディレクトリ
┃  ┗そのほかのコンパイラ用ディレクトリ
┗FreeRTOS+Plus

下記はFreeRTOSのソースファイルから構成したファイル
freertos
┣inc
┃┗


┣src
┃┗

freeRTOSConfig.h

構成したソースファイルはエクステンディングアイテムを用いてPSoCCreatorに導入した。ビルドはFreeRTOSを入れたのでそれらを参照できるように設定を行った。

動作確認はLEDの点滅を行った。下記はそのプログラムとなる。
#include "project.h"

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

void vLEDtask();
xTaskHandle xLEDtask;
uint8_t ucParameterLED;

int main(void)
{
    CyGlobalIntEnable; /* Enable global interrupts. */
    /* Port layer functions that need to be copied into the vector table. */

    extern void xPortPendSVHandler( void );
    extern void xPortSysTickHandler( void );
    extern void vPortSVCHandler( void );
    extern cyisraddress CyRamVectors[];

/* Install the OS Interrupt Handlers. */
CyRamVectors[ 11 ] = ( cyisraddress ) vPortSVCHandler;
CyRamVectors[ 14 ] = ( cyisraddress ) xPortPendSVHandler;
CyRamVectors[ 15 ] = ( cyisraddress ) xPortSysTickHandler;
 
    xTaskCreate(vLEDtask,"ledtask",configMINIMAL_STACK_SIZE,&ucParameterLED,tskIDLE_PRIORITY + 1,&xLEDtask);
 
    /* Place your initialization/startup code here (e.g. MyInst_Start()) */
 
    vTaskStartScheduler();
    for(;;)
    {
        /* Place your application code here. */
    }
}

void vLEDtask()
{

    for(;;)
    {
        Pin_1_Write(1);
        vTaskDelay(250);
        Pin_1_Write(0);
        vTaskDelay(250);
    }
}

void vApplicationStackOverflowHook( TaskHandle_t pxTask, char *pcTaskName )
{
/* The stack space has been execeeded for a task, considering allocating more. */
taskDISABLE_INTERRUPTS();
for( ;; );
}
/*---------------------------------------------------------------------------*/

void vApplicationMallocFailedHook( void )
{
/* The heap space has been execeeded. */
taskDISABLE_INTERRUPTS();
for( ;; );
}
/*---------------------------------------------------------------------------*/



PSoC5LPはPSoC6で使われているPDLが使われていない。PLDがあるとFreeRTOSの適用が楽なのだが、それらしいオプションも見当たらなかった。PSoC5LPでFreeRTOSの開発を進める場合はこれを参照に進めればよいと思われる。