2013/11/02からのアクセス回数 6967
ここでは、LM4F120 LaunchPadを使っていろいろな実験をして、lbedの使い方を説明します。
LM4F120 LaunchPadには、2個のLM4F120が搭載されており、1つはデバッガ兼シリアル通信用、 もう一つがターゲットのLM4F120と豪勢な構成となっています。
このようにデバッグと通信に専用にCPUが割り当てられているとUSBケーブルに接続しただけで パソコンの通信ソフトに接続できるので、CDCのようにシリアルの通信のテスト毎に接続が切れ てしまうようなことがなく、とても自然に通信とデバッグができます。
LM4F120 LaunchPadのStellarisWareライブラリとサンプルプログラムを使うことで、簡単にSerialクラスを 実装することができます。
Serial.cppは次のようになっています。
#include "Serial.h" #include "PinNames.h" #include "inc/hw_memmap.h" #include "inc/hw_types.h" #include "driverlib/sysctl.h" #include "driverlib/uart.h" #include "driverlib/gpio.h" Serial::Serial() : _tx(-1) , _rx(-1) { setup(PA_1, PA_0, "default"); } Serial::Serial(PinName tx, PinName rx, const char *name) : _tx(-1) , _rx(-1) { setup(tx, rx, name); } void Serial::setup(PinName tx, PinName rx, const char *name) { _tx = tx; _rx = rx; _available = false; 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); } void Serial::baud(unsigned int baudrate) { unsigned long sysclock = SysCtlClockGet(); UARTConfigSetExpClk(UART0_BASE, sysclock, baudrate, (UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE | UART_CONFIG_PAR_NONE)); _available = true; } void Serial::begin(unsigned int baudrate) { baud(baudrate); } int Serial::write(const char c) { UARTCharPut(UART0_BASE, c); return 1; } int Serial::read() { return UARTCharGet(UART0_BASE); } int Serial::available() { return _available ? 1 : 0; }
実際にSerialクラスを使ってシリアル通信を行ってみます。
テストプログラムは、以下の様になります。
#include"lbed.h" DigitalOut myled(LEDG); int main(void) { Serial pc(PA_1, PA_0); pc.baud(19200); pc.println("Hello"); while (1) { char c = pc.read(); pc.write(c + 1); myled = !myled; } return 0; }
シリアル通信には、やはりArduinoのシリアルモニターを使いました。 これなら、どんなPCでも同じように使えるので、便利です。
最初にHelloと出力して入力を待ちます。 ここで、abcと入力すると一つ後の文字bcdを出力します。
DegialInのクラスを使ってSW1を押したときに、LEDBが点灯するプログラムを作ってみましょう。
ポイントは以下の2つです。
プログラムは、とても簡単です。
#include "lbed.h" int main(void) { DigitalIn sw1(SW1); sw1.mode(PullUp); DigitalOut myled(LEDG); while(1) { myled = !sw1; // SW1を押すとLow=0になるので、押したときにLEDを付けるために!を付ける。 wait_ms(200); } }
デバッガを起動して、プログラムをLM4F120 LaunchPadに書き込み、Resumeメニューを選択、または三角の青いアイコンをクリックするとmain関数の先頭で停まります。
ここで、もう一度Resumeを実行して、SW1を押したり、離したりしてみて下さい。
I2Cインターフェースを持った温度センサーLM73B*2を使って、温度を測ってみましょう。
LM73は、白の三角がついたところが、1番ピンで反時計回りにピン番号が割り振られています。
となっています。SCL, SDAは、プルアップ抵抗が必要で、ここでは手持ちの4.7KΩを使用しました。
LM4F120 LaunchPadの接続は、以下の4本を使用します。
テストプログラムTestLM73.cppは、以下の様になります。 *3
どうもDegitalOutは、シリアルクラスの影響を受けるみたいで、pcの後に型宣言しています。
#include"lbed.h" #include "LM73.h" int main(void) { LM73 lm73(PB_3, PB_2); Serial pc(PA_1, PA_0); pc.baud(19200); // 注意)Serialの影響を受けるので、最後にLEDを生成した DigitalOut myled(LEDG); while (1) { float t = lm73.read(); pc.printf("temp=%d.%02d\n", int(t), (int(t*100)%100)); myled = !myled; wait_ms(1000); } }
実際に動かしてシリアルモニターに出力させてみました。
StellarisWareライブラリで、I2Cを利用する例題はいくつか見つかりましたが、2バイト以上を送る例が少なく、
LM4F120 LaunchPad特有のI2C初期設定が分からず、動作するまでかなり時間が掛かりました。 *4
I2Cクラスは、以下の様に作成しました。
#include "platform.h" #include "PinNames.h" #include "I2C.h" #include "inc/hw_memmap.h" #include "inc/hw_types.h" #include "inc/hw_i2c.h" #include "driverlib/i2c.h" #include "driverlib/sysctl.h" #include "driverlib/gpio.h" I2C::I2C(PinName sda, PinName scl, const char *name) { _name = (char *) name; // The I2C0 peripheral must be enabled before use. SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C0); // For this example I2C0 is used with PortB[3:2]. SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB); // Select the I2C function for these pins. GPIOPinTypeI2CSCL(GPIO_PORTB_BASE, GPIO_PIN_2); // I2CSCL GPIOPinTypeI2C(GPIO_PORTB_BASE, GPIO_PIN_3); // I2CSDA // Enable and initialize the I2C0 master module. True=400Kbps, False=100Kbps I2CMasterInitExpClk(I2C0_MASTER_BASE, SysCtlClockGet(), false); } int I2C::read(int address, char *data, int length, bool repeated) { unsigned char addr = (unsigned char)address>>1; I2CMasterSlaveAddrSet( I2C0_MASTER_BASE, addr, true); // false = write, true = read if (length == 1) { I2CMasterControl( I2C0_MASTER_BASE, I2C_MASTER_CMD_SINGLE_RECEIVE); // Wait until done transmitting while( I2CMasterBusy(I2C0_MASTER_BASE)); *data = I2CMasterDataGet(I2C0_MASTER_BASE); } else { for (int i = 0; i < length; i++) { if (i == 0) I2CMasterControl( I2C0_MASTER_BASE, I2C_MASTER_CMD_BURST_RECEIVE_START); else if (i == length-1) I2CMasterControl( I2C0_MASTER_BASE, I2C_MASTER_CMD_BURST_RECEIVE_FINISH); else I2CMasterControl( I2C0_MASTER_BASE, I2C_MASTER_CMD_BURST_RECEIVE_CONT); // Wait until done transmitting while( I2CMasterBusy(I2C0_MASTER_BASE)); *data++ = I2CMasterDataGet(I2C0_MASTER_BASE); } } return length; } int I2C::write(int address, const char *data, int length, bool repeated) { unsigned char addr = (unsigned char)address>>1; I2CMasterSlaveAddrSet( I2C0_MASTER_BASE, addr, false); // false = write, true = read if (length == 1) { I2CMasterDataPut( I2C0_MASTER_BASE, *data); I2CMasterControl( I2C0_MASTER_BASE, I2C_MASTER_CMD_SINGLE_SEND); // Wait until done transmitting while( I2CMasterBusy(I2C0_MASTER_BASE)); } else { for (int i = 0; i < length; i++) { I2CMasterDataPut( I2C0_MASTER_BASE, *data++); if (i == 0) I2CMasterControl( I2C0_MASTER_BASE, I2C_MASTER_CMD_BURST_SEND_START); else if (i == length-1) I2CMasterControl( I2C0_MASTER_BASE, I2C_MASTER_CMD_BURST_SEND_FINISH); else I2CMasterControl( I2C0_MASTER_BASE, I2C_MASTER_CMD_BURST_SEND_CONT); // Wait until done transmitting while( I2CMasterBusy(I2C0_MASTER_BASE)); } } return length; } // これらの関数の使い方がよく分からないので、ダミー関数 int I2C::read(int ack) { return -1; } int I2C::write(int ack) { return -1; }
これまで自分が作ってきたユーザライブラリを使っていましたが、mbedの既存のユーザ ライブラリをlbedに移植した場合の変更点と問題点を整理します。
勝純一さんが公開されているI2cLCDを例にlbedへの移植をしてみます。
wait関数の変更は、wait_msとwait_usを使って置き換えます。
wait_ms(1); wait_us(640); //wait(0.00164f); // This command takes 1.64 ms
I2cLCD.hとI2cLCD.cppのメンバー変数の順序を以下の様に変更します。
// DigitalOut, I2Cの順で宣言されているのをI2C, DigitalOutに変更 // DigitalOut _rs; // I2C _i2c; I2C _i2c; DigitalOut _rs;
// _rs, _i2cの順に初期化しているのを_i2c, _rsに変更 // I2cLCD::I2cLCD(PinName sda, PinName scl, PinName rp) : _rs( rp ), _i2c( sda , scl ) { I2cLCD::I2cLCD(PinName sda, PinName scl, PinName rp) : _i2c( sda , scl ), _rs( rp ) {
staticな配列icon_dataの初期化をauto変数に変更します。
static unsigned char icon_data[]= { 0x00, 0x10, 途中省略 0x0F, 0x10, };
をputiconメンバ関数のなかで設定するように変更 *6
void I2cLCD::puticon(int flg) { unsigned char icon_data[] = { 0x00, 0x10, 途中省略 0x0F, 0x10, };
このように手順が分かれば、ある程度スムーズに移植できると思います。 *7
アナログ入力は、StellarisWareのライブラリを使用するため、 StellarisLaunchPadWorkbook を参考にAnalogInクラスを作成してみます。
LM4F120には、12bit 1MサンプリングのADCモジュール*8が2個内蔵され、12個のアナログ入力チャネルで共有されています。*9
LM4F120 LaunchPad UserManual Table 2-5によると、12個のアナログ入力チャネルは、以下の様にピン配置されています。
アナログ入力クラスAnalogInを使って可変抵抗の電圧を測ってみます。
可変抵抗の両端を3.3VとGNDに接続し、真ん中のピンをJ3の9番目のピンPE_3に接続します。
テストプログラムTestAnalogIn.cppは、以下の様にします。
#include"lbed.h" int main(void) { Serial pc(PA_1, PA_0); AnalogIn in(PE_3); DigitalOut myled(LEDG); pc.baud(19200); pc.println("Hello"); while (1) { unsigned short val = in.read_u16(); pc.printf("Sensor = %d\n", (int)val); myled = !myled; wait_ms(1000); } return 0; }
いつものようにArudino IDEのシリアルモニターを起動して実行すると、以下のように可変抵抗を回すと 1から4095の範囲の値が表示されます。
AnalogInのsetupをPE_3に対してのみ表すと以下の様になります。
void AnalogIn::setup(PinName pin, const char* name) { SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC0); SysCtlADCSpeedSet(SAMPLE_SPS); ADCSequenceDisable(ADC0_BASE, SEQUENCE_NUM); if (pin >= PE_0 && pin <= PE_5) { _gpio = GPIO_PORTE_BASE; SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOE); switch (pin) { case PE_3: _channel = ADC_CTL_CH0; _pin = 3; break; // 他のピンも同様に設定 } AnalogIn::_e_mask |= 1 << _pin; // ピンタイプをADCにセット GPIOPinTypeADC(_gpio, AnalogIn::_e_mask); } // PB, PDは省略 // ADC0をシーケンス3を割り込み不可にする ADCSequenceDisable(ADC0_BASE, SEQUENCE_NUM); // ADC0のシーケンスを最大優先にセット ADCSequenceConfigure(ADC0_BASE, SEQUENCE_NUM, ADC_TRIGGER_PROCESSOR, 0); // 1ステップで1サンプリングにセット ADCSequenceStepConfigure(ADC0_BASE, SEQUENCE_NUM, 0, _channel | ADC_CTL_IE | ADC_CTL_END); // ADC0をシーケンス3を割り込み可能にする ADCSequenceEnable(ADC0_BASE, SEQUENCE_NUM); // ADC0をシーケンス3の割り込みフラグをクリア ADCIntClear(ADC0_BASE, SEQUENCE_NUM); }
アナログ値の読み込みは、以下の様にしました。
unsigned short AnalogIn::read_u16() { if (_gpio) { ADCIntClear(ADC0_BASE, SEQUENCE_NUM); ADCProcessorTrigger(ADC0_BASE, SEQUENCE_NUM); while(!ADCIntStatus(ADC0_BASE, SEQUENCE_NUM, false)) { } ADCSequenceDataGet(ADC0_BASE, SEQUENCE_NUM, &_value); return ((short)(_value&0x0FFF)); } else return 0; }
皆様のご意見、ご希望をお待ちしております。