1
MSP430 複数ペリフェラルを並行動作させる方式 [ マルチ IO システム ]
Version3.0 2014.11.26 PIC 山内 一男
この方式では、複数の IO 処理を並行して実行できますので、MSP430 を幅広く利用できます。 OS を使用せずに、複数ペリフェラルを並行動作させて、IO 完了やソフトイベントなどを複数管理して通知することができま す。 これにより、イベントドリブン型の並行処理システムを構築できます。 この方式のライブラリ自体は、デバイスシリーズに依存していません。 ドライバーと ISR に特別なマクロコールを組み込んで ライブラリと連携して実現します。 ドライバーと ISR はデバイスシリーズに依存します。 現在は、MSP430G2553 と MSP430FR5969 に実装してあります。 別途マルチ IO システムとして example を提供 いたします。内容
1 機能概要 ... 2 1.1 従来方式との違い、改善点 ... 2 2 方式概要 ... 22.1 イベント定義テーブル ( the table of events ) ... 3
2.2 IO 制御テーブル (The common table for IO control) ... 3
2.2.1 IO 制御テーブルにアクセスするマクロ ... 3
2.2.2 LPM mode control ... 4
2.2.3 IO status and IO completed ... 4
2.3 Event Manager の動作 ... 4 2.4 Event Manager の関数例 ... 4 3 ドライバーと ISR へのインプリメント ... 5 3.1 ドライバー ... 5 3.2 ISR ... 5 4 アプリケーションでのイベント処理例 ... 5 4.1 イベント ハンドリング ... 5 4.2 リトライ例 ... 7
2
1 機能概要
複数ペリフェラル(デバイスと呼びます)を並行動作させて、その IO 完了などのイベントを1カ所で待ち合わせて、イベ ントドリブン処理を可能にします。 イベントは16個を扱えます。 シーケンス処理を組めるように、特定のイベントだけ選別して待ち合わせる機能があります。 待ち合わせるときに、適切な LPM0、LPM3、LPM4 を選んで wait します。 delay イベントを起こすことで、タイムアウト監視やリトライが可能です。1.1 従来方式との違い、改善点
複数のデバイスを動作させたとき、従来の __bis_SR_register(LPMx_bits+GIE); で待ち合わせる方法は、 wait が解除されたのはどのデバイスが完了したのか判断ができません。 LPM0/LPM3/LPM4 どれで待つかはアプリケーションが適切に判断する必要があります。 ISR が先に wait 解除し、アプリケーションが後から wait するクロス状態が起きると、永遠に wait することになります。 今回提案する方式は、ドライバーと ISR に所定の手続きを追加することで、 複数のペリフェラル IO の待ち合わせを可能にします。 複数同時の IO 完了もIOctl領域の bit にプールされますので、取りこぼしがありません。 ISR の wait 解除とアプリケーションの IO 完了のタイミングが、クロスしても正常に判定できます。 特定のペリフェラル IO 完了だけ選別することができますので、A->B->C のように IO 完了をシーケンスに組むこともで きます。 ドライバーが必要な LPMx を設定しますので、アプリケーションは LPM 制御の意識が不要になります。
2 方式概要
下記の構成要素が連携して動作します。3
driver(デバイスに必要な初期設定をして ISR を呼ぶもの)と ISR は、IO 情報を IOctl に書き込んで、wait と event を管理する event manager と相互連携して動きます。
AP は、IO 要求(サイズ、バッファアドレス)を IO_req にセットして、driver を呼びます。
driver は device に必要な設定をして IO を開始します。 ISR は device からの割り込みを処理して、IO 完了した ら AP を active にします。
driver と ISR は、IO 情報を IOctl に書き込んで、event manager に通知します。 event manager は IO 中 の LPMx wait の制御と、AP へ IO 完了の device_ID を通知します。
2.1 イベント定義テーブル ( the table of events )
IO 要求や完了などのイベントは device ID で識別します。 これは、ユーザが再定義可能です。 イベントは下記のように bit 対応で定義します。 max16 個です。 “Driver.h”で定義します。
/* ======== device ID(uint16_t), user need to define ============== */ // the fixed ID
#define Interval 0x01 // interval timer, timerA1
#define DelayMs 0x02 // for delay_ms, ACLK(LPM3)-> WDT // user need to define following IDs
#define Timer0 0x04 // ACLK,LPM3
//#define Timer1 0x08 // SMCLK,LPM0
#define SPI4 0x20 // SMCLK,LPM0 rev2.0
#define ADC 0x40 // ADC
#define UartA0 0x80 // SMCLK,LPM0
#define PIO1 0x0100 // Port 1,LPM4
#define PIO2 0x0200 // 2
#define PIO3 0x0400 // 3
#define PIO4 0x0800 // 4
2.2 IO 制御テーブル (The common table for IO control)
下記の構造体が Driver.h で定義されており、実体は IOctl_driver.c にあります。 driver と ISR がこのテーブルの device ID に該当する bit を on/off して制御します。
// common control structure for drivers and check_IOwait() struct IO_ctl {
uint16_t Rq_LPM0; // requested LPM0
uint16_t Rq_LPM3; // LPM3
uint16_t St_busy; // under IO executing
uint16_t St_endMK; // IO end mark
};
extern struct IO_ctl IOctl; // defined in IOctl_driver.c
2.2.1 IO 制御テーブルにアクセスするマクロ
アクセスの間違い防止と拡張性のため、下記のようなマクロを DriverG.h に用意してあります。 ドライバーと ISR はこのマ クロを使用して、IOctl table にアクセスします。 dev は device ID を表します。
例 IOctl_start(UartA0);
/* --- Macro define --- */
4
#define IOctl_LPM0off(dev) IOctl.Rq_LPM0 &= ~dev
#define IOctl_LPM3on(dev) IOctl.Rq_LPM3 |= dev
#define IOctl_LPM3off(dev) IOctl.Rq_LPM3 &= ~dev
#define IOctl_start(dev) IOctl.St_busy |=dev; IOctl.St_endMK &= ~dev;
#define IOctl_stop(dev) IOctl.St_busy &=~dev; IOctl.St_endMK |= dev;
2.2.2 LPM mode control
LPM0 の wait を必要なデバイスは、Rq_LPM0 の対応 bit を on にします。 IO 完了で off します。 LPM3 の wait を必要なデバイスは、Rq_LPM3 の対応 bit を on にします。 IO 完了で off します。 Event Manager は、wait するとき Rq_LPM0 と Rq_LPM3 を見て LPMx を判断して wait します。
2.2.3 IO status and IO completed
driver は、IO 開始するとき St_busy の device ID bit を on します。 IO 中か否かの識別に使います。 ISR は IO 完了(IO_completed)のとき、St_endMK の該当 bit を on にして、Active mode に復帰します。 この後、event manager が wake-up して、St_endMK: on の device ID を event として AP に返します。
2.3 Event Manager の動作
アプリケーションは、 __bis_SR_register(LPM0_bits+GIE); の代わりに Check_IOwait(); で IO 完了を待ちます。
event manager は、IO 完了デバイスを IOctl の St_endMK(LSB first)の on bit を探して見つけだし、その device ID(uint16_t)を返します。 その時 St_endMK の device ID の bit は off します。
複数 IO 完了しているときは、この IOctl:St_endMK にプールされていますので、Check_IOwait();を呼ぶこと で順次処理することができます。
もし IO 完了しているものがなければ(St_endMK=0)、Rq_LPM0、Rq_LPM3 の bit が on の device がある 場合は、LPM0(優先)か LPM3 で、無ければ LPM4 で wait します。
2.4 Event Manager の関数例
1) uint16_t Check_IOwait(uint8_t wait_type)
戻り値 dv_id IO 完了したデバイス識別子(Device ID)が戻ります。 wait_type: IOnowait wait しません、IO 完了なしは dv_id=0 で戻ります。
IOwait wait します、タイムアウトなしです。 2) uint16_t Check_onlyDEV(uint16_t dv_id, uint8_t wait_type)
5
3 ドライバーと ISR へのインプリメント
この方式では、ドライバーと ISR は IOctl を制御する所定の手続きの追加が必要です。3.1 ドライバー
ドライバーは、wait するときに必要な LPM0/LPM3 を設定し、IO の開始を設定します。 下記2行追加します。 IOctl_LPM3on(UartA0); IOctl_start(UartA0);3.2 ISR
ISR は、IO 完了時には LPMx 解除と IO 終了を設定して、active に復帰します。 IOctl_LPM3off(UartA0);
IOctl_stop(UartA0);
__bic_SR_register_on_exit(LPM_all); LPM_all = LPM4_bits+GIE
ISR は IO_req の U_status にエラー情報や IO 情報を返すことができます。 下記は timer が割り込み CCR 種 別を返す例です。 また、timer のように連続動作しながら、CCR 割り込みを通知する場合は、IOctl_stop(dev) ではなく IOctl_Mark_end(dev)により割り込みイベントの通知だけをすることができます。
switch ( _even_in_range( TA1IV, 0x0A) ){
case 0x02: // CCR1
P_tm1->U_status = 0x02; // CCR1 interrupt
break;
case 0x0A: // TAIFG
P_tm1->U_status = 0x01; break; default: break; } IOctl_Mark_end(Timer1);
__bic_SR_register_on_exit(LPM_all); // set active
4 アプリケーションでのイベント処理例
4.1 イベント ハンドリング
下記は、SPI スレーブのイベントドリブン処理の抜粋です。 前半 while までは、必要なデバイス(PIO、ADC、Timer0、SPI)の IO を開始しています。 while{}の中で各イベントの処理をしています。 switch(Dev_ID){}部分で、イベント(Dev_ID=PIO1、 ADC、SPI4)を case に区分して、個別の処理をしています。 // enable interrupt of P1.3, SW2PIOx_IntEnable_HtoL( Pio1, &SW_req ); Interval_set( &Interval_req );
6 // get my temperature TLV data
ADCtemp_getTLV( ADC_data ); ADCtemp_start( );
ADCtemp_IO_NoWait( &IO_ADC ); // enable ADC timerA0_IO_NoWait( &IO_timer0 ); // timer0 trigger ADC /* --- waiting event, endless loop --- */
/* exit by SPI received start command, _cmd_Start */ while(1)
{
uint8_t text, status;
/* --- SPI transferring --- */ // sending RQSS if some messages leaved
SPI_receive();
Dev_ID = Check_IOwait( IOwait );
switch ( Dev_ID ) {
/* --- PIO1: SW2-> P1.3 interrupt --- */ case PIO1: // P1.3: SW2 interrupt
if( SW_req.U_status & SWbit )
{
LEDstatus ^= 0x00FF; // toggle
if( LEDstatus ) { P1OUT |= BIT0; } // LED on
else { P1OUT &= ~BIT0; }
// start interval to prevent from chattering of switchs Interval_CCRx_start( _ccr1 );
Mess_set_cmdDATA( s_ID, Pio_ID, &PIO_event ); // send PIO event
}
break;
case Interval: // protecting from chattering if( Interval_req.U_status == fg_CCR1 )
{
Interval_CCRx_stop( _ccr1 ); PIOx_ReEnable_HtoL ( Pio1, SWbit ); }
break;
/* --- ADC --- */ case ADC:
Mess_set_cmdDATA( s_ID, ADC_ID, &ADC_str ); // send ADC data ADCtemp_IO_NoWait( &IO_ADC ); // restart ADC break;
/* --- SPI4 --- */
case SPI4:
status = Mess_get_CMD( &R_mess ); SPIs_stop();
if ( status != IO_completed ) { // IO error
P1OUT |= BIT0; // LED on
}
else
{ // IO completed
Mess_release_BUF(); // releasing the used buffer
switch ( R_mess.M_cmd ) {
case _cmd_Start: // restart
goto Start;
case _cmd_PIO:
7 // LED on/off
if( LEDstatus ) P1OUT |= BIT0; // LED on
else P1OUT &= ~BIT0;
break;
case _cmd_Test: status = 0;
for(cnt=4; cnt< R_mess.size_DT; cnt++) {
text = rvBuf[cnt] +1;
if ( text != rvBuf[cnt+1] ) { status++; break; } }
if ( status ) Mess_set_CMD( s_ID, sdBuf_ID, &NAK_str );
else Mess_set_CMD( s_ID, sdBuf_ID, &ACK_str );
break;
default:
break;
} // end of SPI4 switch } break; default: break; } /* --- switch end --- */