製作モジュール一覧

ラベル PSoC 4 Pioneer Kit の投稿を表示しています。 すべての投稿を表示
ラベル PSoC 4 Pioneer Kit の投稿を表示しています。 すべての投稿を表示

2016年9月23日金曜日

PSoC 4 Pioneer Kitでサーミスタ(SEMITEC 103AT)を使ってみる。

素子の温度を測りたいのでサーミスタを使ってみた。

以前、I2C出力のHDC1000を使って温度湿度を測定する実験をしてみたが、形状が素子の温度測定には使いにくそうなので(貼りつけられなさそう)、部品箱で眠っていたサーミスタの103ATが使えるかどうか実験してみた。

要件としては

1.素子に貼りつけて、素子の温度を測定できる
2.素子が高温になる場合もあるので100℃ぐらいまでは測定できる
3.1秒ごと程度にサンプリングしてロギングできる
4.手持ちの部品を使ってなるべくお金をかけない

PSoC 4 Pioneer Kit


高機能テスタだと熱電対というセンサーを使って、PCにもロギングできるようだ。(OWON DMM B35など)。

いずれは買うかもしれないとして、なるべくお金をかけないように手持ちの部品で考えてみた。

ロギングで一番楽なのはPCとUART通信すること。UARTでCSV形式のデータを出力できれば、CPLT(http://www.datatecno.co.jp/cplt/cplt-download.htm)を使ってロギングやグラフ化が簡単にできる。

Arduinoがいちばん使い慣れてるが、UART通信しようと思うとArduinoをまるまる使わないといけない。aitendoのあちゃんでいいのは安いけどUART通信には別途それ用の回路が必要。

PSoC 4 Prototyping KitだとUARTも使えるし値段も安いのでPSoC 4を使ってみることにした。

が、Prototyping Kitは何かと実験しにくいのでまずはPioneer Kitでテストしてみた。

TopDesign

PSoCにはThermistorCalcというコンポーネントがあってサーミスタの抵抗値->温度を変換してくれるので使ってみた。ソースをチラ見するとSteinhart-Hart式の定数を生成して計算してくれるマクロのようだ。

Thermitor CalculatorのDATASHEETを見ると、抵抗値によってインスタンスを分割したほうがいいようなことが書いてあるのでlow、mid、highの3つに分けてみた。

D_1のZennerで2.5Vの定電圧源とし(ツェナーは正確な電圧を出力できないので、実際は基準電圧を作るのにTL431を使った)、Opamp_1のボルテージフォロワーで基準電圧の出力を増強。

R_2のサーミスタとR_refで分圧してそれぞれの電位差を差動モードのADCに読み込ませた。

ThermistorCalcのGetResitance()はこういう分圧と測定の仕方で電圧値を与えてサーミスタの抵抗値を算出しているようだ。

配線図


Github:
https://github.com/ryood/PSoC_4_Thermistor

2016年9月22日木曜日

PSoC 4でCharLCD(HD44780)を使う

HD44780互換の普通のキャラクタLCDをPSoC 5LPで使うとPinによって動いたり動かなかったりしたので、PSoC 4でも確認してみた。PSoC 4 Pioneer KitとPSoC 4 Prototyping Kitを持っているので両方試してみた。

PSoCは割と自由にPin配置が決められるが、完全に自由と言うわけではなく機能によって使えるPinが決まっている。

PSoC 4 Pioneer Kit(3000円@秋月)とPSoC 4 Prototyping Kit(600円@秋月)は同じCY8C4245AXI-483を使っている。

CharLCDの割り当て可能なPinは
P0[6:0]
P0[7:1]
P1[6:0]
P1[7:1]
P2[6:0]
P2[7:1]

PSoC 4 Pioneer Kit


配線図


プログラムはPSoC CreatorのExampleの「CharLCD_CustomFont」プロジェクトをベースに、DWRエディターで割り当てるPinを変更しながら実験した。


PSoC 4 Pioneer KitではP1[6]がヘッダに引き出されていないのでPort P1は使えないようだ。

PSoC 4 Prototyping Kit






Prototyping Kitの方はP1[6]もヘッダに引き出されているが、CharLCD用にP1[7:1]を割り当てるとちゃんと表示されなかった。


信号線にLEDがつながっているので何か影響があるのかもしれない。

PSoC 4 Prototyping Kitはまたまた認識されにくくて困った。

Windowsのデバイスマネージャーの「ポート(COMとLPT)」に現れず、「ユニバーサル シリアル バス コントローラー」にあやしいデバイスが表示される場合(プロパティを見ると、製造元がCypressになっている)、Windowsを再起動すると認識されるようだ。←今日の経験則

<追記:2016.09.25>

画像のように、ポートの項にCOMポートが追加されず、ユニバーサル シリアル バス コントローラーの項の囲みのような項目が表示される。

</追記>

Bootloader Hostで書き込む場合Baudが115200になっていないと書き込みに失敗する。

メモ:


PSoC 4ではPort[P1]を避ければCharLCDを使えそうだ。

PSoC 5LPでCharLCDがポートによってちゃんと使えなったのは実験ミスかもしれないしPSoCのバグかもしれない。PSoC 5LPをまた使う機会があれば調べてみたい。

2016年6月21日火曜日

NucleoシーケンサーとPSoC4 DCOとVCVS DCFの結合テスト

NucleoシーケンサーとPSoC4 DCO(「NucleoシーケンサーとPSoC4 DCOのテスト(その4)」)にVCVS DCFシールドを追加してテストしてみた。

配線図


<追記:2016.06.23> Nucleo→PSoC4へのSPI:CSのピンが違っていたので修正(D10→D9) </追記>


ぴゅんぴゅんコントローラー2号のジョイスティックでVCVS DCFのcutoffとresonanceをコントロールする。ジョイスティックの値はNucleoシーケンサーで受けて、Nucleoに載せたVCVS DCFシールドにSPIで値を送っている。

PSoC4 Pioneer KitとVCVS DCFシールドは同一のSPIバス(SPI1)を使用。グラフィックLCD(Nokia 5110)は別のSPIバス(SPI2)を使用。

Nucleoシーケンサー
https://developer.mbed.org/users/ryood/code/Nucleo_SPI_Sequencer/

PSoC4 DCO
https://github.com/ryood/PSoC4_DCO/tree/master/PSoC/PSoC4_DCO_for_NucleoF401RE_Interrupt_Test.cydsn

入出力波形


ch1:PSoC4 DCOからの出力 ch2:VCVS DCFからの出力

ジョイスティックがニュートラルの状態。cutoff:124、resonance:130


cutoff、resonanceを上げて発振させた状態。cutoff:255、resonance:232


cutoffだけ上げて発振させた状態。cutoff:255、resonance:140

PSoC4 DCOからの出力はIDACの出力を1kΩの抵抗で電流電圧変換していて、振幅は0V~612mV。

VCVS DCFからの出力は発振させると電源電圧の5V(p-p)付近になるが、AC結合しているのでパルス幅によっては0Vからの振幅になる。

->OTAはの信号入力は差動で100mV以内にしないといけないので1/50~1/5程度に減衰。もともと歪んでいる場合は除外して値を決める?

2016年6月12日日曜日

NucleoシーケンサーとPSoC4 DCOのテスト(その4)

以前やった「Nucleo F401RE(mbed)でシーケンサーを作るつもりで入出力デバイスのテスト。」にSPIでDCOにシーケンスを送信するSpiSequenceSenderクラスを組み込んでみた。

配線図


mbed repository:
https://developer.mbed.org/users/ryood/code/Nucleo_SPI_Sequencer/

Github(PSoC4 DCO):
https://github.com/ryood/PSoC4_DCO/tree/master/PSoC/PSoC4_DCO_for_NucleoF401RE_Interrupt_Test.cydsn

LCDの表示は文字のみだが、pitch、octave、tie、waveShape、BPM、pulse widthはリアルタイムに変更できた。

メモ:


LCDを表示させてロータリーエンコーダを回すとシーケンスが遅れる。LCDをオフにすると大丈夫なようだ。
→どこに原因があるか特定する。SPI信号を受け取ってダンプする、なんちゃってLogic AnalizerをArduinoあたりで作ってみる?

RTOSTimer::stop()が効かない
→単体テストしてみる。

シーケンスの組み方でたまに異音がする。どこかの計算を間違えていると思う。

再生中に現在位置をLCDに表示するために、SequenceSenderクラスにコールバック関数を作ってみる?

DCFやVCAにどのクラス/関数でSPI出力するかもう一度整理。

2016年6月10日金曜日

NucleoシーケンサーとPSoC4 DCOのテスト(その3)

Nucleoシーケンサーから周波数をSPIで出力してPSoC4で受信して波形を再生してみた。



mbed repository:
https://developer.mbed.org/users/ryood/code/SpiSequenceSender_Test/

Github:
https://github.com/ryood/PSoC4_DCO/tree/master/PSoC/PSoC4_DCO_for_NucleoF401RE_Interrupt_Test.cydsn

オシロでトリガをかけると未だ割り込みが効かなくなる時があるようだ(10分に1回ぐらい)。これはSPIの割り込みのタイミングの絡みかもしれない。

が、大してブツブツいわなくなったので、これで少し先に進んでみるつもり。

NucleoにUIを実装するとまた問題が発生しそうだし、DCOはまだやってみたいことがいろいろあるので。

2016年6月9日木曜日

PSoC4 DCOの単体テスト(割り込みがかからない現象を追い込む)

SPI受信の処理を外して、シーケンスをTimer割り込みで処理するようにしてみた。

単純にSPIの処理を外しただけだと割り込みがかからなく現象は改善されなかったが、波形が開始するタイミングでTimerのPeriodを変更するようにしたらゴソッと割り込みがかからなくなる現象は起こらなくなった。

PSoC CreatorのTopDesign

SPISはDisableしている。

単にSPIからTimerに周波数変更の割り込みを変更した場合


ch1:Pin_Check1 ch2:Pin_Check2

ch1のPin_Check1はシーケンス処理をするTimer1の割り込みを捕捉。ch2のPin_Check2は波形を生成するTimer_Samplingの割り込みを捕捉。Timer_Samplingの割り込みが2.8m秒程度かからなくなっている。


ch1:出力波形 ch2:Pin_Check2

Timer_Samplingの割り込みがかからない区間で波形が崩れる。

拡大

波形の開始地点でTimer_SamplingのPeriodを変更。5ミリ秒間隔で周波数を変更。

main.c

/* ========================================
 *
 * Copyright YOUR COMPANY, THE YEAR
 * All Rights Reserved
 * UNPUBLISHED, LICENSED SOFTWARE.
 *
 * CONFIDENTIAL AND PROPRIETARY INFORMATION
 * WHICH IS THE PROPERTY OF your company.
 *
 * ========================================
*/
#include <project.h>
#include <stdio.h>

#define UART_TRACE  (0)
#define LCD_DISPLAY (0)

#define TITLE_STR1  ("PSoC 4 DCO")
#define TITLE_STR2  ("20160609")

#define SAMPLING_CLOCK           (24000000)
#define SPIS_RX_PACKET_SIZE      (5)
#define SPIS_RX_PACKET_HEADER    (0x55)

#define WAVESHAPE_SQUARE    0
#define WAVESHAPE_SAW       1
#define WAVESHAPE_N         2

volatile uint8 count = 0;
volatile uint16 timerPeriod = 100;
volatile int toChangePeriod = 0;

uint8 waveShape   = WAVESHAPE_SAW;
uint8 squareDuty  = 127;
int32 frequency10 = 4400;

#if(LCD_DISPLAY)
void printLCD(uint8* rxBuffer)
{
    char strBuffer[20];
    sprintf(strBuffer, "%3u %3u %3u %3u",
    rxBuffer[0], rxBuffer[1], rxBuffer[2], rxBuffer[3]);
    LCD_Char_Position(0, 0);
    LCD_Char_PrintString(strBuffer);
    sprintf(strBuffer, "%3u %5ld", rxBuffer[4], frequency10);
    LCD_Char_Position(1, 0);
    LCD_Char_PrintString(strBuffer);
}
#endif

CY_ISR(ISR_Saw_handler)
{
    Pin_Check2_Write(1);
    
    count++;
    if (count == 0 && toChangePeriod) {
        Timer_Sampling_WritePeriod(timerPeriod);
        toChangePeriod = 0;
    }
        
    IDAC8_SetValue(count);
 
    Timer_Sampling_ClearInterrupt(Timer_Sampling_INTR_MASK_TC);
    
    Pin_Check2_Write(0);
}

CY_ISR(ISR_Square_handler)
{
    Pin_Check2_Write(1);
    
    count++;
    if (count == 0 && toChangePeriod) {
        Timer_Sampling_WritePeriod(timerPeriod);
        toChangePeriod = 0;
    }
    IDAC8_SetValue((count / squareDuty) ? 255 : 0);    
    
    Timer_Sampling_ClearInterrupt(Timer_Sampling_INTR_MASK_TC);

    Pin_Check2_Write(0);    
}

void doCommand(uint8 *rxBuffer)
{
    if (waveShape != rxBuffer[1]) {
        waveShape = rxBuffer[1];
        switch (waveShape) {
        case WAVESHAPE_SQUARE:
            ISR_Timer_Sampling_StartEx(ISR_Square_handler);
            break;
        case WAVESHAPE_SAW:
            ISR_Timer_Sampling_StartEx(ISR_Saw_handler);
            break;
        }
    }

    squareDuty = rxBuffer[2];
    frequency10 = ((uint16)rxBuffer[3] << 8) | rxBuffer[4];
    
    timerPeriod = (uint64)SAMPLING_CLOCK * 10 / (frequency10 * 256);
    toChangePeriod = 1;
}

#define CNT_MAX (125)

CY_ISR(ISR_Timer1_handler)
{
    const uint16 freq10Arr[] = { 654, 1308 };
    static int cnt = 0;
    static int idx = 0;
    static int f_delta = 0;
    static uint8 rxBuffer[] = {
        SPIS_RX_PACKET_HEADER, WAVESHAPE_SQUARE, 127, 0x00, 0x00
    };
    
    Pin_Check1_Write(1);

    cnt++;
    if (cnt == CNT_MAX) {
        cnt = 0;
        idx++;
        if (idx > 1) {
            idx = 0;
        }
        frequency10 = freq10Arr[idx];
        f_delta = (freq10Arr[(idx == 0) ? 1 : 0] - frequency10) / CNT_MAX;
    }
    frequency10 += f_delta;
    rxBuffer[3] = frequency10 >> 8;
    rxBuffer[4] = frequency10 & 0xff;
    #if(LCD_DISPLAY)
    printLCD(rxBuffer);
    #endif
    doCommand(rxBuffer);

    Timer1_ClearInterrupt(Timer_Sampling_INTR_MASK_TC);
    
    Pin_Check1_Write(0);
}

int main()
{
    CyGlobalIntEnable; /* Enable global interrupts. */

    #if(UART_TRACE)
    UART_Start();
    UART_UartPutString("PSoC 4 SPI Slave Test\r\n");
    #endif
    
    #if(LCD_DISPLAY)
    LCD_Char_Start();
    LCD_Char_ClearDisplay();
    LCD_Char_PrintString(TITLE_STR1);
    LCD_Char_Position(1, 0);
    LCD_Char_PrintString(TITLE_STR2);
    CyDelay(1000);
    #endif
    
    IDAC8_Start();
    Opamp_IV_Conv_Start();

    Timer_Sampling_Start();
    ISR_Timer_Sampling_StartEx(ISR_Saw_handler);
    
    Timer1_Start();
    ISR_Timer1_StartEx(ISR_Timer1_handler);

    for(;;)
    {
    }
}

/* [] END OF FILE */

BPM120/16Stepで4StepごとにC(ド)の音を1オクターブ上下させて、線形補間している。補完の間隔が5m秒。

オシロでトリガを掛けてしばらく様子を見てみたが、Timer_Samplingの割り込みが掛からなくなることはなくなったようだ。

※LCD表示を有効化すると割り込みが遅延する。

Github:
https://github.com/ryood/PSoC4_DCO/tree/master/PSoC/PSoC4_DCO_No_SPI_Interrupt_Test.cydsn

メモ:


うまくいった理由はよくわからないが、波形の開始地点でPeriodを更新するようにしたことよりもTimer_Sapmlingの割り込みのタイミングで更新しているからなのかもしれない。

波形の開始地点でTimer_SamplingのPeriod更新すると単波形の波形の崩れもなくなるので、これはこれでよさそう。

ToDo: SPIマスターからの命令で周波数を変更するようにする。
→これでまたおかしくなるようなら、ノコギリ波はDDS、矩形波はPWMで生成するように根本から変更。DDSのノコギリ波は波形がいまいちなのでできれば現状の方法でやりたい。

ノコギリ波ぐらいは、プログラムではなくてPSoCのUDB(プチFPGA?)で実装できないかと妄想中。

2016年6月6日月曜日

NucleoシーケンサーとPSoC4 DCOのテスト(その2)

PSoC4 DCOの波形が崩れる原因を追い込んでみたが、まだはっきりわからない。

SPIの受信をメインループ内でポーリングしていたのを、SPI(SCB)コンポーネントのInterruput SourceでFIFOにデータが5エントリー溜まったら割り込みが発生するようにして処理してみた。


main.c

/* ========================================
 *
 * Copyright YOUR COMPANY, THE YEAR
 * All Rights Reserved
 * UNPUBLISHED, LICENSED SOFTWARE.
 *
 * CONFIDENTIAL AND PROPRIETARY INFORMATION
 * WHICH IS THE PROPERTY OF your company.
 *
 * ========================================
*/
#include <project.h>
#include <stdio.h>

#define UART_TRACE  (0)
#define LCD_DISPLAY (0)

#define TITLE_STR1  ("PSoC 4 DCO")
#define TITLE_STR2  ("20160606")

#define SAMPLING_CLOCK           (24000000)
#define SPIS_RX_PACKET_SIZE      (5)
#define SPIS_RX_PACKET_HEADER    (0x55)

#define WAVESHAPE_SQUARE    0
#define WAVESHAPE_SAW       1
#define WAVESHAPE_N         2

volatile uint8 count;

uint8 waveShape   = WAVESHAPE_SQUARE;
uint8 squareDuty  = 127;
int32 frequency10 = 4400;

#if(LCD_DISPLAY)
void printLCD(uint8* rxBuffer)
{
    char strBuffer[20];
    sprintf(strBuffer, "%3u %3u %3u %3u",
    rxBuffer[0], rxBuffer[1], rxBuffer[2], rxBuffer[3]);
    LCD_Char_Position(0, 0);
    LCD_Char_PrintString(strBuffer);
    sprintf(strBuffer, "%3u %5ld", rxBuffer[4], frequency10);
    LCD_Char_Position(1, 0);
    LCD_Char_PrintString(strBuffer);
}
#endif

CY_ISR(ISR_Saw_handler)
{
    Pin_Check2_Write(1);
    
    count++;
    IDAC8_SetValue(count);
 
    Timer_Sampling_ClearInterrupt(Timer_Sampling_INTR_MASK_TC);
    
    Pin_Check2_Write(0);
}

CY_ISR(ISR_Square_handler)
{
    Pin_Check2_Write(1);
    
    count++;
    IDAC8_SetValue((count / squareDuty) ? 255 : 0);    
    
    Timer_Sampling_ClearInterrupt(Timer_Sampling_INTR_MASK_TC);

    Pin_Check2_Write(0);    
}

void doCommand(uint8 *rxBuffer)
{
    uint16 timerPeriod;
    
    if (waveShape != rxBuffer[1]) {
        waveShape = rxBuffer[1];
        switch (waveShape) {
        case WAVESHAPE_SQUARE:
            ISR_Timer_Sampling_StartEx(ISR_Square_handler);
            break;
        case WAVESHAPE_SAW:
            ISR_Timer_Sampling_StartEx(ISR_Saw_handler);
            break;
        }
    }

    squareDuty = rxBuffer[2];
    frequency10 = ((uint16)rxBuffer[3] << 8) | rxBuffer[4];
    
    timerPeriod = (uint64)SAMPLING_CLOCK * 10 / (frequency10 * 256);
    Timer_Sampling_WritePeriod(timerPeriod);
}

void SPI_RX_handler()
{
    uint8 rxBuffer[SPIS_RX_PACKET_SIZE];
    int i;
    
    Pin_Check1_Write(1);
    
    rxBuffer[0] = SPIS_SpiUartReadRxData();
    if (SPIS_RX_PACKET_HEADER == rxBuffer[0]) {
        for (i = 1; i < SPIS_RX_PACKET_SIZE; i++) {
            rxBuffer[i] = SPIS_SpiUartReadRxData();
        }
        doCommand(rxBuffer);
    }
    
    SPIS_ClearRxInterruptSource(SPIS_INTR_RX_FIFO_LEVEL);
    
    Pin_Check1_Write(0);
}

int main()
{
    CyGlobalIntEnable; /* Enable global interrupts. */

    #if(UART_TRACE)
    UART_Start();
    UART_UartPutString("PSoC 4 SPI Slave Test\r\n");
    #endif
    
    #if(LCD_DISPLAY)
    LCD_Char_Start();
    LCD_Char_ClearDisplay();
    LCD_Char_PrintString(TITLE_STR1);
    LCD_Char_Position(1, 0);
    LCD_Char_PrintString(TITLE_STR2);
    CyDelay(1000);
    #endif
    
    IDAC8_Start();
    Opamp_IV_Conv_Start();

    Timer_Sampling_Start();
    ISR_Timer_Sampling_StartEx(ISR_Saw_handler);
    
    SPIS_SetCustomInterruptHandler(SPI_RX_handler);
    SPIS_Start();

    for(;;)
    {
    }
}

/* [] END OF FILE */

SPI信号とタイマー割り込み


割り込みがかからない?

ch1:Pin_Check1 ch2:SPI_MOSI

拡大

ch1のPin_Check1はタイマー割り込みがかかるたびに割り込み処理ルーチンの最初と最後でH→Lさせている。

ch2はSPIのMOSIの信号を拾っている。1枚目の画像はch2が単にH→Lになっているように見えるが、拡大するとHの後にMOSIの信号が流れて最後はLになっている。MOSIの通信の前後のレベルは不定でよいと思うのでHでもLでもいいと思うけど、違うかな?

SPIのマスター(Nucleo)から音程が変更されるデータが送られてくるタイミングで、1枚目の画像のようにゴソッと割り込みがかからなくなる。

出力波形をWaveSpectraで取り込み

メモ:


いくつかバグや無駄な処理があったので修正してみたが、改善されない。

DWRエディターでSPIとタイマーの割り込みの優先度を変えてみたがかえって悪化する(ーー;

SPIとタイマー割り込みの絡みかと思っていたが、周期的に割り込みが発生している最中にPeriod(割り込みの周期)を変更しているのが原因のような気もする。

→SPIの処理を外して実験して見る。

mbed erepository:
https://developer.mbed.org/users/ryood/code/SpiSequenceSender_Test/

Github:
https://github.com/ryood/PSoC4_DCO/tree/master/PSoC/PSoC4_DCO_for_NucleoF401RE_Interrupt_Test.cydsn

2016年6月3日金曜日

NucleoシーケンサーとPSoC4 DCOのテスト(その1)

シーケンサーをArduino UnoからNucleo F401REに移行してベース・シンセをつくる方向で考えみた。

しばらくかかりそうなので分割して記事にしていくつもりです。

全体の構成案


アナログ・シンセと同じ様にVCO→VCF→VCAとアナログ波形を流して加工する(オレンジ色の部分)。音階やモジュレーションは、Nucleo SequencerからSPIで制御する。

Nucleo SequencerとPSoC4 DCOは現在テスト中のもの。SPI Digi POT + VCVS LPFはAruduinoのシールドとして製作した。

OTAはNJM13600/13700である程度テストしたがDACによるコントロールのテストはまだ。

NucleoシーケンサーとPSoC4 DCOのテスト


配線図


Nucleoシーケンサー


UI系は省いてプログラムに直書きしたパラメータを送るようにしてテスト中。

mbed repogitory:
https://developer.mbed.org/users/ryood/code/SpiSequenceSender_Test/

PSoC4 DCO


Arduino用に作ったもの(「Arduino UnoをシーケンサーにしてPSoC 4 DCOを制御する。」)を多少改変した。

矩形波、ノコギリ波を切り替えて出力できて、矩形波はPulth Widthが可変。

PSoC CreatorのTopDesign

Github:
https://github.com/ryood/PSoC4_DCO/tree/master/PSoC/PSoC4_DCO_for_NucleoF401RE.cydsn

問題点


出力波形がたまに遅れるようだ。


範囲選択したあたりで、シーケンス1周のうち1回か2回。UARTとLCDは無効化しても出るので、タイマー割り込みとSPI通信の絡みかな?地味にテストしていくしかないか(^q^;

NucleoはArduinoのシールドが載せられるが、VCVS LPFシールドが動作するか要テスト。


2016年5月19日木曜日

Nucleo F401RE(mbed)をMasterにしてPSoC4とSPI通信する。

ArduinoのSPIライブラリではSlaveになれないようなのでPSoC 4 Pioneer KitをSPI Slaveにして通信させてみた。

配線図

PSoC 4 Pioneer Kitのボード上のジャンパを3.3V駆動に設定して、信号レベルを両方とも3.3Vにした。

Nucleo F401RE(SPI Master)


SPI MasterのNucleo F401REはmbedのオンライン・コンパイラで、SPIクラスと割り込み処理にTickerクラスを使った。

mbedのmain.cpp


#include "mbed.h"

#define SPIM_TICKER_PERIOD  (0.01)
#define SPIM_RATE           (8000000)   // 8MHz
#define SPI_PACKET_HEADER   (0x55)

// ピン・アサイン
#define PSOC4_DCO_CS    PB_6

SPI spim(SPI_MOSI, SPI_MISO, SPI_SCK);
Ticker spim_write_ticker;

DigitalOut PSoC4_DCO_CS(PSOC4_DCO_CS);

void spim_tx() {
    static uint8_t i = 0;
    
    PSoC4_DCO_CS = 0;
    spim.write(SPI_PACKET_HEADER);
    spim.write(i);
    spim.write(i);
    spim.write(i);
    spim.write(i);
    wait_us(1);
    PSoC4_DCO_CS = 1;
    
    i++;
}
 
int main() {
    spim.format(8, 0);
    spim.frequency(SPIM_RATE);
    spim_write_ticker.attach(&spim_tx, SPIM_TICKER_PERIOD);
    for (;;) {
        //spim_tx();
        //wait(0.1);
    }
}
 

spim_tx()が割り込み処理ルーチンで、SPI Masterから送信している。
wait_us(1);
とPSoC4_DCO_CSをHにする前にウェイトをかけているのは、タイミングなどの条件によってSCKの送信が完了する前にCSがHになってしまう場合があったから。

また、mbedオフィシャルのTickerのページ

No blocking code in ISR
In ISR you should avoid any call to wait, infinitive while loop, or blocking calls in general.
No printf, malloc, or new in ISR
In ISR you should avoid any call to bulky library functions. In particular, certain library functions (like printf, malloc and new) are non re-entrant and their behaviour could be corrupted when called from an ISR.
RTOS Timer
Consider using the mbed RTOS Timer instead of a Ticker. In this way your periodic function will not be executed in a ISR, giving you more freedom and safety in your code.

とあるので、ほんとはwaitをかけるのは良くないのかもしれないし、SPIのコマンドを発行するのも良くないのかもしれない。RTOSクラスのTimerを使え、と書いてあるのでそうしするつもり。ただし、RTOS Timerの間隔指定はミリ秒単位のようだ(クロックで言うと1kHz)。

SPIの通信状態



ch1:MOSI ch2:SCK


ch1:CS ch2:SCK

mbedのプログラムでSPIのクロックは8MHzを指定しているが、実際は5MHz程度になっている。いくつか値を試してみたが設定通りのクロックになることはまずなかった。タイミングがシビアなときには確認が必要だと思う。

PSoC 4 Pioneer Kit(SPI Slave)


PSoCのTop Design

PWMコンポーネントは受け取った値でPulse Widthを変えて様子を見るために使っている。Master側のSPIの送信速度が速くなるとUARTでは処理落ちしてしまうため。

main.c


#define UART_TRACE  (1)

#include <project.h>
#include <stdio.h>
#define RX_PACKET_SIZE      (5)
#define RX_PACKET_HEADER    (0x55)

int main()
{
    uint8_t rxBuffer[RX_PACKET_SIZE];
    char strBuffer[100];
    int i;
    
    CyGlobalIntEnable; /* Enable global interrupts. */
    UART_Start();
    UART_UartPutString("PSoC 4 SPI Slave Test\r\n");
    
    SPIS_Start();
    PWM_Start();

    for(;;)
    {
        // Check Packet Header
        if (RX_PACKET_SIZE <= SPIS_SpiUartGetRxBufferSize()) {
            rxBuffer[0] = SPIS_SpiUartReadRxData();
            if (RX_PACKET_HEADER != rxBuffer[0]) {
                break;
            }
            for (i = 1; i < RX_PACKET_SIZE; i++) {
                rxBuffer[i] = SPIS_SpiUartReadRxData();
            }
            PWM_WriteCompare(rxBuffer[1]);
#if(UART_TRACE)
            sprintf(strBuffer, "%d\t%d\t%d\t%d\t%d\r\n",
                rxBuffer[0], rxBuffer[1], rxBuffer[2], rxBuffer[3], rxBuffer[4]);
            UART_UartPutString(strBuffer);
#endif     
        }
    }
}

SPIのパケットの先頭byteのヘッダ(0x55)を見て、パケットごとに処理するようにしている。

Github:
https://github.com/ryood/PSoC4_SPI_Slave_Test/tree/master/PSoC(Slave)/PSoC4_SPI_Slave_Test/PSoC4_SPI_Slave_Test_for_NucleoF401RE.cydsn

2016年5月17日火曜日

Arduino UnoをシーケンサーにしてPSoC 4 DCOを制御する。

音が出たので一旦まとめ。

配線図

シーケンサーの表示

Propellerhead ReasonのMatrixというシーケンサーを参考にした。

音出し

Tie(二つの音符の音程をなめらかにつなぐ)を使ってうにょうにょさせてみた。

シーケンサーとオシレーターだけなので情けないですが(^q^;

Arduino Sequencer
https://github.com/ryood/Arduino_Sequencer/tree/master/Arduino_Sequencer_Nokia5110

PSoC 4 DCO
https://github.com/ryood/PSoC4_DCO/tree/master/PSoC/PSoC4_DCO_for_Arduino_Sequencer_Nokia5110.cydsn

メモ:

PSoC 4のDCOの処理が追い付いていないのか、音が飛ぶときある。DCOをもう少し低スぺなプログラムにするかPSoC 5LPにするか。

シーケンサーのSPIにArduinoで作ったDCFとVCA用のDACをぶら下げる予定なので、Arduino UnoだとUI(ロータリーエンコーダやタクトスイッチ)のピン数が足りない。UI系だけ別にしてこれもSPIでつなげようか。

2016年5月12日木曜日

PSoC 4 Pioneer KitでSPI制御のDCOを作る。

アナログ・シンセのVCOが電圧で周波数を制御するのに対して、デジタル信号で周波数を制御するDCO(Digital Controled Oscillator)を作ってみた。

IC間通信で一般的な、I2CとSPIの実験をしてみたが、SPIの方が簡単そうなのでSPIでやってみた。

ブレッドボード図


接続は前回の「Arduino UnoをSPIのMasterにして、複数のSlaveを使う。」のPSoC 4 Pioneer Kitに、

  • IDAC(電流出力型DAC)の電流電圧変換用の1kΩの抵抗
  • ノコギリ波/矩形波切り替え用のタクトスイッチ

を追加した。

PSoCのTopDesign

IDAC8からの電流出力はR_1で電圧変換し、Opamp_IV_Convのボルテージフォロワーで強化して出力している。

IDAC8の電流出力は0~612uAに設定しているので、出力は0~612mVになる。

波形生成


<PSoC 4の割り込みルーチン>


CY_ISR(ISR_Saw_handler)
{
    Pin_Check2_Write(1);
    
    count++;
    IDAC8_SetValue(count);
 
    Timer_Sampling_ClearInterrupt(Timer_Sampling_INTR_MASK_TC);
    
    Pin_Check2_Write(0);
}

CY_ISR(ISR_Square_handler)
{
    Pin_Check2_Write(1);
    
    count++;
    IDAC8_SetValue((count / squareDuty) ? 255 : 0);    
    
    Timer_Sampling_ClearInterrupt(Timer_Sampling_INTR_MASK_TC);

    Pin_Check2_Write(0);    
}

今回はDDSではなくて、ノコギリ波と矩形波を単純な方法で生成してみた。

ISR_Saw_handlerがノコギリ波生成ルーチンで、符号なし8bit整数のcountを割り込みごとにインクリメントしているので0~255の値でノコギリ波形になるのでそのままDACに出力。

ISR_Square_handlerは矩形波生成ルーチンで、countをsquareDuty(デューティ比:符号なし8bit整数)で割り算して、デューティ比可変の0, 255の値で矩形波を生成している。

処理速度は結構シビアで、割り込みルーチン内で条件分岐してノコギリ波と矩形波を切り替えると割り込みのタイミングに追いつかなくなるので、波形ごとに割り込みルーチンを作って外部で切り替えるようにした。

ノコギリ波生成のタイミング

ch1:Pin_Check2 ch2:Pin_Check1

Pin_Check2は割り込みルーチン内の最初と最後でOn/Offしているのを捕捉。Pin_Check1はメインループの最初と最後でOn/Offしているものを補足している。

Pin_Check2のPW(正のパルス幅)を見ると割り込み処理に2.6us程度かかっている。

Pin_Check1のPWはメインループの処理時間で、波形生成の割り込みがかかっている時は処理時間が引き伸ばされている。

矩形波生成のタイミング

ch1:Pin_Check2 ch2:Pin_Check1

矩形波も同じだが、割り込みルーチン内で割り算等、処理が増えているのでPWが3.3usに増加している。

→矩形波はプログラムでの計算ではなく、TimerCounterの割り込み発生条件でうまく制御すればもっと処理が速くなると思う。

周波数の制御


TimerCounterのPeriodを変更してオーバーフローするタイミングを変えることで可変にしている。低い周波数でもなめらかに周波数を変更できるように0.1Hz単位で設定できるようにした。

符号なし16bit整数に周波数の10倍値を入れるようにした。最大値が65535なので0Hz~6553.5Hzの範囲で音程を設定できる。

再生可能周波数

実際のプログラムでは再生できる周波数はもう少し制限される。

TimerCounterを12MHz(SAMPLING_CLOCK)で駆動していて、TimerCounterのPeriodは
timerPeriod = SAMPLING_CLOCK * 10 / (frequency10 * 256);
になる。(frequency10は周波数の10倍値)

変形すると
frequency10 = SAMPLING_CLOCK * 10 / (timerPeriod * 256);
ノコギリ波の場合、波形生成の割り込みルーチンで2.6usかかっているので、timerPeriodの最小値は
2.6us / (1 / 12Mhz) = 31.2
これ以上timerPeriodを短くすると、割り込みルーチン内の処理が追いつかなくなる。
frequency10 < 12MHz * 10 / (31.2 * 256) ≒ 15024
で周波数に直すとおよそ1,502Hzになる。

音程でいうと真ん中のドより2オクターブ高いF#になる。

MIDIノートナンバーでうと0~90を(もう少し余裕をみる必要はあるが)再生できそうだ。

Github:
https://github.com/ryood/PSoC4_DCO

Arduino Master:
https://github.com/ryood/PSoC4_DCO/tree/master/Arduino/Sequencer_Test

PSoC 4 Pioneer Kit Slave:
https://github.com/ryood/PSoC4_DCO/tree/master/PSoC/PSoC4_DCO.cydsn

メモ:


割り込みのタイミングを可変にすることで周波数を制御してみたが、DDSとくらべて特にメリットはない気がする(^q^;

<追記:2016.06.09>

あ、いやDDSの矩形波やノコギリ波は波形が崩れるのでこれはこれでメリットがある。

</追記>

ARM Cortex-M0の48MHz駆動だとノコギリ波のような単純な波形でもリアルタイム生成はこのあたりが限度かも。

2016年5月9日月曜日

Arduino UnoをSPIのMasterにして、複数のSlaveを使う。

PSoC 4 Pioneer KitをSPIのスレーブとして動作させて、SPI LCDのNokia 5110も同じSPIバスを使って動作させてみた。

<追記:2017.08.30>

ArduinoはSPI Slaveにすることができないので、Arduinoのスケッチの中でAVRのレジスタを使って実験しました。

Arduino UnoをSPI Slaveとして使う。

</追記>


※実際の配線では「Arduino Uno」と「Nokia 5110」間のMISOはつながっていません。

SPIはスレーブの切り替えにSS(Slave Select。CS/Chip Select)を使う。信号線はMasterSlaveのMOSIとMasterSlaveのMISOの2本だがスレーブの数だけSSが必要になる。

<追記:2016.06.03>

書き忘れてましたが、SPI通信には信号線以外にクロック線のSCKが必要です(^q^;

</追記>

ブレッドボード図

動作としては、

1) MasterのArduinoが、繋がれているPOTの値を読み取り、SPIでSlaveのPSoC4に送信する。

2) SlaveのPSoC4はArduinoから送られてきたデータを受け取り、繋がれているロータリー・エンコーダーの値を読み取り、MasterのArduinoに送信する。

3) SlaveのPSoC4はArduinoから送られてきたデータの値(RX)と、Arduinoに送ったデータ(TX)をキャラクタLCD(パラレル接続)に表示する。

4) MasterのArduinoは1)で自分が送ったデータ(TX)と、SlaveのPSoC4から送られてきたデータ(RX)をSPI接続のLCDのNokia 5110に表示する。

したがって、POTを回しても、ロータリー・エンコーダーを回してもArduinoのLCDにもPSoC4のLCDにも同じデータが表示される。

Master側のArduinoのスケッチ


// PSoC 4 SPI Slave用のMasterプログラム
// 
// POTでTXデータを設定、NOKIA 5110でTX,RXデータを表示
//
// PSoC 4 Slave
//      SCK    13
//      MISO   12
//      MOSI   11
//      CS      9
//
// LCD5110 
//      SCK  - Pin 13
//      MOSI - Pin 11
//      DC   - Pin 2
//      RST  - Pin 3
//      CS   - Pin 10
//
// POT  A0
//
// 2016.05.09
#include <stdio.h>
#include <SPI.h>
#include <LCD5110_Graph.h>

#define PIN_PSOC_SS  9
#define PIN_POT      A0

#define TX_PACKET_SIZE  3
#define RX_PACKET_SIZE  3

LCD5110 myGLCD(13,11,2,3,10);  // SCK, MOSI, DC, RST, CS

extern uint8_t SmallFont[];

byte txBuffer[TX_PACKET_SIZE];
byte rxBuffer[RX_PACKET_SIZE];

char strBuffer[17];

void setup()
{
  myGLCD.InitLCD();
  myGLCD.setFont(SmallFont);
  
  myGLCD.clrScr();
  myGLCD.print("SPI Master", 0, 0);
  myGLCD.update();
  
  pinMode(PIN_PSOC_SS, OUTPUT);
}

void loop()
{
  int txData;
  int rxData;
  
  txData = analogRead(PIN_POT);
  rxData = sendRecieveData(txData);
  displayData(txData, rxData);
  delay(100);
}

int sendRecieveData(int txData) {
  static byte cnt;

  txBuffer[0] = cnt++;
  txBuffer[1] = txData >> 8;
  txBuffer[2] = txData & 0xff;

  SPI.begin();
  SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
  digitalWrite(PIN_PSOC_SS, LOW);
  rxBuffer[0] = SPI.transfer(txBuffer[0]);
  rxBuffer[1] = SPI.transfer(txBuffer[1]);
  rxBuffer[2] = SPI.transfer(txBuffer[2]);
  digitalWrite(PIN_PSOC_SS, HIGH);
  SPI.endTransaction();
  SPI.end();

  return ((int)rxBuffer[1] << 8) | (rxBuffer[2]);
}

void displayData(int txData, int rxData) {
  myGLCD.clrScr();
  myGLCD.print("SPI Master", 0, 0);
  
  sprintf(strBuffer, "TXDATA: %d", txData);
  myGLCD.print(strBuffer, 0, 10);
  
  sprintf(strBuffer, "TX:%03d %03d %03d", txBuffer[0], txBuffer[1], txBuffer[2]);
  myGLCD.print(strBuffer, 0, 20);

  sprintf(strBuffer, "RX:%03d %03d %03d", rxBuffer[0], rxBuffer[1], rxBuffer[2]);
  myGLCD.print(strBuffer, 0, 30);

  sprintf(strBuffer, "RXDATA: %d", rxData);
  myGLCD.print(strBuffer, 0, 40);

  myGLCD.update();
}

Nokia 5110の表示には「Nokia5110_Graph」ライブラリを使っている。(参考「ロジックICでSPIの5V->3.3Vレベルシフト Nokia LCD5110を動かす」)。

PSoC4とのSPI通信にはArduinoの「SPI」ライブラリを使った。「NOKIA5110_Graph」はArduinoのSPIライブラリを使用していない(ATMega328pのハードウェアSPIも使っていない)ので、PSoC4との通信の前後でSPIオブジェクトをbegin()、end()させている。こうしないとNokia 5110との通信がうまくいかない。

送受信データは、どちらも3byteにしていて、Masterが生成したカウンターの値(1byte)+POT/RotaryEncoderの16bitの読み取り値(2byte)。

Slave側のPSoC4のプロジェクト


TopDesign

SPISはSCBのSPIコンポーネントでスレーブに設定している。UARTはデバッグ用に使った。PIN_REというPINコンポーネントはロータリー・エンコーダーの読み取り用。

Github:
https://github.com/ryood/PSoC4_SPI_Slave_Test

Master側 (Arduino)
https://github.com/ryood/PSoC4_SPI_Slave_Test/tree/master/Arduino(Master)/SPI_Master_PSOC4_Slave_w_UI

Slave側 (PSoC4)
https://github.com/ryood/PSoC4_SPI_Slave_Test/tree/master/PSoC(Slave)/PSoC4_SPI_Slave_Test

メモ:


SPIは全二重なので送信だけ、受信だけという使い方にするには少し工夫が必要。普通のシリアル通信のようにプログラムを書くと電話でお互いが勝手に喋っべいるみたいな状態(自分の声でかき消されて相手が何を言っているか聞き取れない?)になってしまってハマってしまった。

SlaveからMasterに送信するには、Master側からSlaveに「どうぞ」と声をかけてからSlaveが送信するとか、Master側から制御するR/Wの信号線を追加するとかしないとダメかもしれない。

2016年5月7日土曜日

秋月のI2Cバス用双方向電圧レベル変換モジュール(PCA9306)を使ってみる。

PSoC 4 Pioneer KitとArduino UnoのI2S通信秋月のI2Cバス用双方向電圧レベル変換モジュール(PCA9306)を使って、ちゃんと5V系と3.3V系のレベルシフトをしてみた。

ブレッドボード図


変換前後のI2C信号


SCL

ch1:Arduino側 ch2:PSoC側

SDA

メモ:


LがGNDまでちゃんとおちてないかな?I2Cにぶら下がるデバイスが増えて通信がうまくいかない場合は使ってみるかも?