MBR3 をホストから使ってみた (3) [PSoC]
今回は、ちょっと横道へ。 MBR3110 では、 LED は5本しか扱えません。 もっと、多くの LED たとえば7セグメント LED などを扱いたい時には、 PSoC 40xx を使えば解決しできます。
7セグメント LED をつなぎたい
SOIC16 パッケージの MBR3110 は、プログラムを作りたくない人には便利なのだけど、代わりに自由度が大幅に制限されてしまい、7セグメント LED を接続することもできません。 でも、同じパッケージの CY8C401x であれば、対応可能です。
そこで、設計してみたのが、このような基板です。 MBR3 と同じように I2C で制御できます。
中身は簡単です。 ホストと通信を行う EZI2C コンポーネントと7セグメント LED を制御するための出力端子が並んでいるだけです。 これに、 Watchdog タイマと Systick タイマを組み合わせて、ソフトウェアで LED を制御します。
ファームウェアは、こうなった
ファームウェアですべての制御を行っています。
#define MAX_DIGIT (16) #define DUTY_BASE (16) #define DUTY_FREQ (100) // frame frequency #define DUTY_CLOCK (DUTY_FREQ*DUTY_BASE) #define SYSTICK_PERIOD (12000000/DUTY_CLOCK) #define NDIGIT_DEFAULT (0) #define DUTY_DEFAULT (4) #define SPEED_DEFAULT (8) #define INTERVAL_DEFAULT (2) struct i2c_frame { uint8 ndigit; uint8 digit[MAX_DIGIT]; uint8 duty; uint8 speed; uint8 interval; };
EZI2C コンポーネントで送受信するデータは、仮想レジスタとして実装されています。 レジスタの中身を表しているのが、 struct i2c_frame 構造体です。 それぞれのレジスタは、以下のような意味を持っています。
- ndigit
-
LED で表示する桁数を表します。
- 0 の時 LED は消灯します。
- 1 の時 LED は設定されたパターンにしたがって連続して点灯します。
- 2 以上の時 LED は設定されたパターンを順に表示していきます。このとき、一桁目を表示する前に消灯期間を設けて一桁目を表現します。
- digit[MAX_DIGIT]
- 表示するパターンを格納します。 最大桁数は、 MAX_DIGIT であらわされる 16 ケタです。
- duty
- LED の明るさを 0 から 16 までの値で示します。 最大値は DUTY_BASE=16 で、初期値は DUTY_DEFAULT=4 です。 LED は、ソフトウェア PWM で制御されています。 キャリア周波数は DUTY_FREQ であらわされており、 100Hz となっています。 電流制限抵抗を適当に 220Ω にしたら意外に明るかったので、明度を落とす目的で入れてあります。
- speed
- 2ケタ以上の表示を行う時の一桁あたりの表示時間を示します。 初期値は SPEED_DEFAULT=8 です。 単位は、 Watchdog タイマの周期で、このプロジェクトでは 102.4ms に設定されています。
- interval
- 2ケタ以上の表示を行う時の桁と桁の間に設ける消灯時間を示します。 初期値は INTERVAL_DEFAULT=2 です。 LED に同じパターンを連続して表示させると、桁と桁の区切りが分からなくなります。 これを防ぐために、桁と桁の間に消灯時間を設けて、区切りを表現しています。
void Led_Write(uint8 seg) { (seg & 0x01)?CyPins_ClearPin(Seg_A ):CyPins_SetPin(Seg_A ); (seg & 0x02)?CyPins_ClearPin(Seg_B ):CyPins_SetPin(Seg_B ); (seg & 0x04)?CyPins_ClearPin(Seg_C ):CyPins_SetPin(Seg_C ); (seg & 0x08)?CyPins_ClearPin(Seg_D ):CyPins_SetPin(Seg_D ); (seg & 0x10)?CyPins_ClearPin(Seg_E ):CyPins_SetPin(Seg_E ); (seg & 0x20)?CyPins_ClearPin(Seg_F ):CyPins_SetPin(Seg_F ); (seg & 0x40)?CyPins_ClearPin(Seg_G ):CyPins_SetPin(Seg_G ); (seg & 0x80)?CyPins_ClearPin(Seg_DP):CyPins_SetPin(Seg_DP); }
LED に表示するパターンは、8ビットの値で表現されます。 Led_Write 関数では、この8ビットのパターンを使って LED のそれぞれのセグメントを制御しています。
static struct i2c_frame i2c_buffer; static struct i2c_frame digit_buffer;
レジスタの値を保持するバッファを二組宣言します。 i2c_buffer は、 EZI2C コンポーネントから受信した値を格納します。 受信した値は、通信直後に digit_buffer にコピーして表示に使用する事で安全に表示を行うことができます。
static uint8 segment = 0; static uint8 tick = 0; CY_ISR(duty_isr) { if (digit_buffer.duty >= DUTY_BASE) { // 100% tick = DUTY_BASE; Led_Write(segment); } else if (tick == 0) { // turn OFF tick = DUTY_BASE; Led_Write(0); } else if (tick == digit_buffer.duty) { // turn ON Led_Write(segment); } tick--; } void duty_init(void) { CyIntSetSysVector((SysTick_IRQn + 16), duty_isr); SysTick_Config(SYSTICK_PERIOD); }
これらのコードで、ソフトウェア PWM の制御を行います。 ソフトウェア PWM には、 SysTick タイマを使用しています。
static uint8 position = 0; static uint8 phase = 0; void wdt_init(void) { CySysWdtDisable(); memcpy(&digit_buffer, &i2c_buffer, sizeof i2c_buffer); position = 0; CySysWdtEnable(); } void wdt_isr(void) { switch (digit_buffer.ndigit) { case 0: // BLANK segment = 0; phase = 0; break; case 1: // STATIC segment = digit_buffer.digit[position]; phase = 0; break; default: // Multiple digits if (phase == 0) { if (position == 0) { // Blank digit segment = 0; } else { segment = digit_buffer.digit[position - 1]; } position++; if (position > digit_buffer.ndigit) { position = 0; } phase = digit_buffer.speed - 1; } else { if (phase == digit_buffer.interval) { // Interval phase segment = 0; } phase--; } break; } }
これらのコードで、複数桁の表現の制御を行っています。
int main() { uint8 status; CyGlobalIntEnable; /* Enable global interrupts. */ /* Place your initialization/startup code here (e.g. MyInst_Start()) */ EZI2C_Start(); i2c_buffer.ndigit = 0; i2c_buffer.duty = 4; i2c_buffer.speed = 8; i2c_buffer.interval = 2; memcpy(&digit_buffer, &i2c_buffer, sizeof i2c_buffer); EZI2C_EzI2CSetBuffer1(sizeof i2c_buffer, sizeof i2c_buffer, (uint8*)&i2c_buffer); CySysWdtDisable(); CySysWdtSetInterruptCallback(wdt_isr); duty_init(); for(;;) { /* Place your application code here. */ status = EZI2C_EzI2CGetActivity(); if (status & EZI2C_EZI2C_STATUS_WRITE1) { wdt_init(); } CySysPmSleep(); } }
EZI2C コンポーネントは、すべてのレジスタをホストから書き込み可能にしています。 メインループでは、 I2C インターフェイスから書き込み通信を待ち、 wdt_init 関数で書き込まれた値を使った表示を開始させる仕組みになっています。
ホスト側のソフトウェア
ホスト側には、以下のようなファームウェアを書き込みました。
#include "mbed.h" // One I2C port is used to connect to MBR3. // DIP9 and DIP10 are used for I2C but ports. I2C i2c(p9, p10); // Signal device register map const uint8_t SIGNAL_NDIGIT = 0x00; static const uint8_t seg_digit[] = { 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71, }; // Node "Signal" // Device: CY8C4014 // Function: 7-segment LED const uint8_t signalAddr = 0x68 << 1; // 8bit I2C address int main(void) { uint8_t cmd[8]; uint32_t count = 0; for (;;) { // Put COUNT status to Signal cmd[0] = SIGNAL_NDIGIT; // NDIGIT cmd[1] = 1; // 1 column cmd[2] = seg_digit[count & 0x0000000Fu]; i2c.write(signalAddr, (char *)cmd, 3); count++; // Wait for while wait(1); } }
一秒ごとに表示パターンを送り込んで、カウンタの値を表示させています。 特に有意義な事はしていません。
プロジェクトアーカイブ
この記事を書くために作成した PSoC 4 のプロジェクトは、このファイルの拡張子を ZIP に変更して展開すると再現できます。
コメント 0