ターゲットでのテストユニット実行
「テスト駆動開発による組み込みプログラミング」(#tdd4ec と呼ばれている?)の演習をターゲット用にビルド、実行してみる。ターゲットはTI社製Cortex-M4を搭載したStellaris Launchpad LM4F120(¥550で売ってたヤツ)で、開発環境はTI社謹製のCode Composer Studio v5でやってみる。
くどいようだけど、ここで出てくるUnityはゲームプラットフォームの方じゃなくて、テストハーネスのUnityってことで。
プロジェクトの作成
Code Composer Studioを起動し、Workspaceに適当な名前(ここではworkspace_LM4F120とした)を入れてOKを押下。
File => New => CCS Projectで新規プロジェクトを作成する。
Description | comment |
---|---|
Project name | 何でもいいと思うがここではLab1とした |
Output type | 実行形式なのでExecutableを選択 |
Use default location | 今回はソースコードを分離しないのでチェックしたまま |
Variant | 左に120hと入力で絞り込みでき、LM4F120H5QRを選択 |
Connection | Stellaris In-Circuit Debug Interfaceを選択 |
Project templates | Empty Project (with main.c)を選択 |
入力が完了したらFinishを押下。
Stellaris Launchpadでは標準出力や標準エラーを使ってもシリアルに吐いてくれないので、UARTを使うためにStellarisWareを利用する。StellarisWareはTI社のサイトから落として来て適当なディレクトリにインストールする。ここではC:\ti\StellarisWareにインストールしたものとして進める。
インクルードディレクトリの指定
プロジェクトを右クリックからpropertiesを選択し、右下のペインにあるAddボタンをクリックし、File systemボタンから参照してC:\ti\StellarisWareを指定する。
ライブラリの指定
同様にARM Linkerの中のFile Search Pathを選び、右上のペインから環境に合うライブラリを指定する。C:\ti\StellarisWare\driverlib\ccs-cm4f\Debug\driverlib-cm4f.libを選択する。OKを押下して確定。
UARTへの出力を確認
main.cを次のように書き換える。これはStellarisLaunchPadWorkbook.pdfを参照した。なお、ピン配置を決定するためにターゲットに合ったシンボル定義(ここではPART_LM4F120H5QRを指定)が必要となる。今回はmain.cで指定したが各所で使用するならプロジェクトで一括定義してもいい。
# define PART_LM4F120H5QR
# include "inc/hw_ints.h"
# include "inc/hw_memmap.h"
# include "inc/hw_types.h"
# include "driverlib/gpio.h"
# include "driverlib/interrupt.h"
# include "driverlib/pin_map.h"
# include "driverlib/sysctl.h"
# include "driverlib/uart.h"
void UARTIntHandler(void)
{
unsigned long ulStatus;
unsigned char received_character;
ulStatus = UARTIntStatus(UART0_BASE, true); //get interrupt status
UARTIntClear(UART0_BASE, ulStatus); //clear the asserted interrupts
while(UARTCharsAvail(UART0_BASE)) //loop while there are characters in the receive FIFO
{
received_character = UARTCharGet(UART0_BASE);
UARTCharPutNonBlocking(UART0_BASE, received_character); //echo character
if (received_character == 13) UARTCharPutNonBlocking(UART0_BASE, 10); //if CR received, issue LF as well
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_2, GPIO_PIN_2); //blink LED
SysCtlDelay(SysCtlClockGet() / (1000 * 3)); //delay ~1 msec
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_2, 0); //turn off LED
}
}
int main()
{
SysCtlClockSet(SYSCTL_SYSDIV_4 | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN | SYSCTL_XTAL_16MHZ);
SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
GPIOPinConfigure(GPIO_PA0_U0RX);
GPIOPinConfigure(GPIO_PA1_U0TX);
GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1);
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF); //enable GPIO port for LED
GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_2); //enable pin for LED PF2
UARTConfigSetExpClk(UART0_BASE, SysCtlClockGet(), 115200,
(UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE | UART_CONFIG_PAR_NONE));
UARTFIFOLevelSet(UART0_BASE, UART_FIFO_TX4_8, UART_FIFO_RX4_8); //set FIFO level to 8 characters
UARTFIFOEnable(UART0_BASE); //enable FIFOs
IntMasterEnable(); //enable processor interrupts
IntEnable(INT_UART0); //enable the UART interrupt
UARTIntEnable(UART0_BASE, UART_INT_RX | UART_INT_RT); //enable Receiver interrupts
// erase screen
UARTCharPut(UART0_BASE, '\033');
UARTCharPut(UART0_BASE, '[');
UARTCharPut(UART0_BASE, '2');
UARTCharPut(UART0_BASE, 'J');
// put cursor at home position (0,0)
UARTCharPut(UART0_BASE, '\033');
UARTCharPut(UART0_BASE, '[');
UARTCharPut(UART0_BASE, 'H');
for(;;); // Forever...
}
この時点で正しくStellarisWareが使えるかどうかビルドを試しておくとよい。
Unityテストハーネスの組み込み
プロジェクトを選択して右クリックからNew => Source Foldersでソース用のフォルダを新規作成する。ここではFolder nameをUnityとして作成、作成したフォルダを右クリックからImportを選び、General => File systemでunity.frameworkのsrc及びextras/fixture/srcの中のファイルを全て選択してFinishを押下する。取り込まれたファイルは次のようになっている。
インクルードパスを通す
取り込んだだけではビルドできないので、ヘッダファイルのある場所にパスを通す。プロジェクトのPropertiesを開いて次のように二つのパスを追加する。
ビルドして成功すればここまで完了している。
テストケースとプロダクトコードの作成
続いてテストケースとプロダクトコードを作成する。先ほどと同様、Source Foldersを新規作成して作成済みのコードを取り込む。テストケースはTestsフォルダ、プロダクトコードはSrcsフォルダにそれぞれImportした。なお、5章のまでの作業が完了していて5.9演習問題でターゲット用のユニットテストを実行する場面を想定しているので、ここではテストケース及びプロダクトコードはホスト環境で動作済みのものとする。
プロダクトコードへインクルードパスを通す
Importしたプロダクトコードへのインクルードパスを通す。プロジェクトを右クリックからPropertiesを開き、Build => ARM Compiler => Include Options で左下のペインにパスを追加する。ここではワークスペースからSrcs/Includeを選んだ。
ランナーの準備
今回はmain.cから直接起動するようにした。引数を指定できるようにしているが、この例では引数無しで起動している。
# include "unity_fixture.h"
/* 中略 */
static void RunAllTests(void)
{
RUN_TEST_GROUP(LedDriver);
}
int main()
{
/* 中略 */
static char *argv[] = { "main" };
UnityMain(sizeof(argv)/sizeof(*argv), argv, RunAllTests);
for(;;); // Forever...
}
テストハーネスからの出力をUARTに接続
テストハーネスからの出力は全てputchar()関数を通して行われるので、今回はmain.cでputchar()関数を実装し、UARTへの出力に変える。
int putchar(int c)
{
UARTCharPut(UART0_BASE, c);
return c;
}
テストの実行
Stellaris LaunchpadをUSB経由で接続し、CCSからデバッグ実行すると、ビルドしてターゲットへのダウンロードが完了し、main.cに突入するところでブレークした状態となる。ターミナルソフトを起動してシリアル接続(115200bpsに指定)した後、RUNする。実行後、ターミナルソフト上に次のように結果が表示される。
Unity test run 1 of 1
...........!......
-----------------------
18 Tests 0 Failures 1 Ignored
OK
一点問題があり、なぜかラインフィードはするけどキャリッジリターンしない状況になっているが、これはターミナルソフトが上手く設定できていないだけかもと、ちょっとペンディング。
あと、繰り返し書き込んで実行するのはやっぱりRAM上でやりたいよね、とか思っちゃうけどね。