サンプルプログラムは以下のURLにZIP形式の圧縮ファイルで設置。適宜アーカイバにて解凍して使用の事。
GCC Developer Liteで提供される専用のライブラリを使用してPIOの初期化を行い、LEDの明滅を行う。
PIO関連ライブラリのプロトタイプ宣言はpiocfg.hとして提供され、ソースにインクルードする事で参照される。
#include <piocfg.h> // PIO関連ライブラリ
まずTPin構造体で各端子の初期化情報を列挙しておき、その構造体をPIO_Configureに与えると初期化が行われる。
void main (void) { TPin pins[] = { { uint32_t(0 << 1), &PIOA, PIOA_PID, PIO_OUTPUT_1, PIO_DEFAULT}, }; // PIO初期化 PIO_Configure (pins, PIO_LISTSIZE (pins)); ... }
後はコードを参照の事。
smpl1ではLEDを点灯させるために出力端子として構成していたが、こちらはプッシュボタンの入力端子も追加。更にプッシュボタンの押下にて割り込みを発生させ、割り込みルーチンにてLEDの点灯状況を変化させる。
割り込みにはAICを使用し適宜PIOからの割り込みを受け付ける様に設定する。
// PIOA割り込みを禁止 AIC.IDCR = 1 << PIOA_PID; // AICに割り込みベクタ設定 AIC.SVR[PIOA_PID] = PIOA_irq_handler; AIC.SMR[PIOA_PID] = _AIC_SRCTYPE_INT_HIGH_LEVEL | _AIC_PRIOR_LOWEST; // PIOA割り込みクリア AIC.ICCR = 1 << PIOA_PID; // PIOA割り込み許可 AIC.IECR = 1 << PIOA_PID;
割り込みの許可や禁止を行うためのヘッダも用意されている。
#include <vic.h> // 割り込み関連ライブラリ
デフォルトではIRQ割り込みが禁止されており、enableIRQでIRQ割り込みを許可する事でAICやPIOの割り込み関連の設定生きる。
DBGU・UART0・UART1・UDPを使ったシリアル通信を行う。シリアル通信はGCC Developer Liteで提供される専用のライブラリを直接使用し、文字及び文字列の送受信や書式化文字列の送信を行う。
シリアル通信ライブラリのプロトタイプ宣言はus.hとして提供され、ソースにインクルードする事で参照される。
#include <us.h> // UART通信ライブラリ
各シリアルポートで使用する送受信端子は現在公開されているAPIでは決め打ちになっているため、他の端子にアサインしなおす際は注意が必要である。
以下にDBGUを割り込みを使用しないで使用するAPIを抜粋する。他のペリフェラルでは若干異なるのでus.hを参照の事。
// ポートの初期化 uint32_t dbgu_init (uint32_t baudrate); // ボーレートのみの変更 uint32_t dbgu_baudrate (uint32_t baudrate); // 1文字送信 void dbgu_putc (char); // 1文字取得 char dbgu_getc (void); // 受信バッファデータ数取得 int dbgu_rx_buff (void); // 受信バッファデータクリア void dbgu_rx_purge (void); // 文字列送信 void dbgu_puts (char *); // int dbgu_sys_puts (char *, int); // int dbgu_sys_gets (char *, int); // 書式化文字列送信 int dbgu_printf (const char *, ...); // 書式化文字列取得 int dbgu_scanf(const char *, void *);
なお、本サンプルではマクロとus.cにて使用するポートを任意に選択してコンパイルできる様になっている。直接インクルードして構わない。
#define __UDP__ #include "share/us.c" // uart切替用ソース
本インクルードの直前に__UDP__が定義されているが、この宣言によりUSBによるCDCエミュレーションにて仮想シリアルポートが提供される。この部分をソースのコメントに従って変更する事で任意のポートに変更できる。
なおus.cを使用すれば、どのポートを使ったとしてもユーザプログラム中では以下の名称で各機能が利用できる。
// ポートの初期化 uint32_t _us_init (uint32_t baudrate, char *txbuf, uint16_t txl, char *rxbuf, uint16_t rxl); // 受信バッファデータ数チェック uint16_t _us_rx_buff (void); // 送信バッファデータ数チェック uint16_t _us_tx_buff (void); // 1文字送信 void _us_putc (char c); // 1文字受信 char _us_getc (void); // 文字列送信 void _us_puts (char *s); // 指定バイト数送信 int _us_putb (char *s, int len); // 書式化文字列送信 int _us_printf (const char *, ...); // 書式化文字列受信 int _us_scanf (const char *, void *); // 受信バッファデータクリア void _us_rx_purge (void);
また、割り込みやPDCを使用したポートの場合は送受信用にバッファを必要とするが、簡便のためus.c内で定義済みのものをがあるのでそれを使用するとよい。
// 送受信用バッファ char txb[100], rxb[100];
UDPを選択した場合、USBのリセット信号発行のためAT91SAM7S128/256ではPA16端子が、AT91SAM7S512, AT91SAM7X512においてはNRST端子が使用される。これらの端子を他の目的に使用する場合は注意が必要である。
US0を半二重モード(RS485)にし、Dynamixel AX-12+とシリアル通信を行う。
Dynamixelは通信速度の範囲が広く1Mbpsまで対応させる必要があるので、PDCを使用してデータの取りこぼしが極力無いようにしている。またus0_pdc_initでUS0を初期化した後、RS485モードを追加指定している。
us0_pdc_init (baud, us0tbuf, sizeof(us0tbuf), us0rbuf, sizeof(us0rbuf)); USART0.MR = (_US_CLKS_CLOCK | _US_CHRL_8_BITS | _US_PAR_NONE | _US_NBSTOP_1_BIT | _US_USMODE_RS485 | _US_OVER);
Dynamixelのパケット処理に関しては、GCC Developer Liteに収録されたDynamixelライブラリからの移植。
US1で自動的にハードフロー制御を行う様に構成し、ZEAL-C01(Bluetoothモジュール)とシリアル通信を行う。
USARTでハードフロー制御を行うにはPDCで通信する必要がある。us1_pdc_initでMRがノーマルモードで初期化された部分をハードフロー制御を行う様に設定しなおしている。
us1_pdc_init (9600, txb1, sizeof (txb1), rxb1, sizeof (rxb1)); USART1.CR = _US_RSTRX | _US_RSTTX | _US_RXDIS | _US_TXDIS; USART1.MR = (USART1.MR & ~_US_USMODE) | _US_USMODE_HWHSH; USART1.CR = _US_TXEN | _US_RXEN;
なお、ZEAL-C01は内部的に大きめの受信バッファを備えているため、高いボーレートでかつ数百バイトを超えるデータを連続的に送信させない限り一生懸命フロー制御させるまでもない場合もある。
AD0~AD7の端子に入力されるDC0~3.3Vの電圧を計測し、シリアル通信で送信する。
A/D変換器の初期化は以下の通り。時間等は精度等の必要に応じて設定する。
// ADC初期化 ADC_CfgTimings ( 5000000, /*[Hz] ADCクロック */ 20, /*[uSec] スタートアップ時間 */ 600, /*[nSec] サンプルホールド時間 */ );
EFCによるFLASHへの書き込みや消去操作等を行う。
EFCに制御コマンドを投げると、処理が終わるまでFLASH上にあるプログラムコードはコアから見えなくなる。何があろうと動いている最中のプログラムコードを見失う訳にはいかないため、書き込み操作を行う部分の関数は以下の様に宣言して見失われないRAM上に配置される様に仕向けている。
__attribute__((section(".ramfunc"))) int FlashReady (void) { ...
また、FLASHの最後尾あたりであればプログラムコードと干渉しないつもりで諸々割り当てているが、プログラムコードが肥大化すれば干渉しないとも限らないためマップファイル等でこまめに確認しておく必要がある。
それが面倒なら予めリンカスクリプトファイルで必要な領域をコードエリアから除外しておけば良いまでである。
なお、書き込みや消去を行う場合はアンロックしておかないと書き換わらない。
SPI接続された3軸加速度センサLIS331DLと通信を行う。
LIS331DLのCSはNPCS1に接続されていることを想定。またSPIはPDCを介し指定されたバッファのデータを送ったり受信するといった操作を基本的に自動的に行わせている。
SPI接続されたデバイス固有の設定はSPI_Init内の以下の部分で行われている。
// LIS3LV02DL SPI.CSR[1] = _SPI_CPOL | SPI_CSAAT | _SPI_BITS_8 | ((uint32_t)14 << 8);
送受信はPDCベースのSPI_SendReceiveで行うが、一応送信前と送信後にポーリングで通信状態を監視して完了復帰としている。
LIS331DLが持つメモリへのアクセスはSPI_LIS3_WriteRegないしSPI_LIS3_ReadRegを介して行う。
SPI接続された3chハーフブリッジドライバATA6831と通信を行う。
ATA6831のCSはNPCS2に接続されていることを想定。それ以外はほぼsmpl9と同等。
SPI接続されたSDカードI/Fを介してFATファイルシステムで初期化されたSDカードへのアクセスを行う。
SDカード用のライブラリとしてChaN氏のfatfsを使用させていただいた。
fatfsフォルダに一連のライブラリに関するファイルを同梱しているので、このサンプルプログラムに限り手動でコンパイルオプションにfatfsライブラリを追記する等の操作が必要である。
タイマカウンタ0/1のRCのコンペアマッチを使用して一定間隔で割り込みを励起する。
タイマカウンタ0のRCのコンペアマッチ割り込みを許可するのは以下の通り。
TC.CHANNEL[0].IER = _TC_CPCS;
タイマカウンタ0をCLK/128(48MHz/128=375kHz)で標準動作をさせるには以下の通り。
TC.CHANNEL[0].CMR = _TC_CLKS_MCK_128 | _TC_WAVESEL_UP_AUTO;
後はRCでさらに分周し丁度1msでコンペアマッチ割り込みが発生するように調整。
TC.CHANNEL[0].RC = 37500 - 1;
このような感じで両タイマの設定を行った後、TC0の割り込みルーチンでLEDの点滅を、TC1の割り込みルーチンでカウント変数のインクリメントを行わせている。
TC2を波形出力モードにし、RCのコンペアマッチでTIOA2の出力をトグル動作させる。そのポートにはブザーが接続されているので、トグル動作させる周波数を変化させて音階を奏でる。
先のインターバルタイマに対して
TC.CHANNEL[2].CMR |= _TC_WAVE | _TC_WAVESEL_UP_AUTO | _TC_ACPC_TOGGLE;
といった初期化を行うと波形出力モードになる。ポートの構成をTIOA2にすれば、RCのコンペアマッチ毎に出力論理が自動的に反転し続ける。
また、RCの値をコンソールから変更する事でトグル動作させるタイミグを変更する。
TC.CHANNEL[2].RC += 100; TC.CHANNEL[2].CCR = _TC_SWTRG; // これをやらないと即時反映されない
TC0~2の3つのタイマカウンタをキャプチャモードにし、TIOA0~2に入力された信号のパルス幅を自動計測する。
以下の様な設定を行うと、カウンタクロック6MHzで最大16bitまでのパルス幅(約10ms)がRAに自動的に保存される。
TC.CHANNEL[0].CMR = _TC_CLKS_MCK_8 // クロック分解能 48MHz/8=6MHz | _TC_LDRB_RISING // 立ち上がりエッジでRBキャプチャ | _TC_LDRA_FALLING // 立ち下がりエッジでRAキャプチャ | _TC_ETRGEDG_RISING // 立ち上がりエッジでカウンタクリア | _TC_ABETRG; // TIOBを外部トリガに
TC0を波形出力モードにし、TIOA0からPWMを出力する。デューティー比を変更してその結果を確認するためにTIOA0に接続したLEDの輝度を見る。
周期をRC、デューティーをRAに設定するものとした場合はCMRを以下の様に初期化する。
TC.CHANNEL[0].CMR |= _TC_WAVE | _TC_WAVESEL_UP_AUTO | _TC_ACPC_SET | _TC_ACPA_CLEAR;
PITによるインターバルタイマで割り込みを励起し、LEDを点滅させる。
PITはSYSC_PIDとして他のペリフェラルと割り込みを共有する都合から、SYSC_PIDに含まれる複数のペリフェラルからの割り込み処理を同時に処理させる場合はsysc.hで定義されるAPIを使用すると便利。
#include <sysc.h>
初期化は
sysc_init (); sysc_register(_SYSC_PIT_IRQ, &PIT_irq_handler);
といった具合に少ないコードで記述できる。
単一のペリフェラルであれば通常通りAICに直接設定すればよい。
PWMにてPWM波形を3ch出力し、各々の周期割り込みでデューティー比をスイープさせる。PWM0~2の各端子にはLEDを接続し、輝度と周期の違いを見る。
PWMの初期化痔と初期化後において扱うレジスタが異なる部分があるので、詳細はソースを参照の事。また、0ないし1をデューティーとして与えると出力がおかしくなるので注意。