LPC810メモ:pattern match engine
pattern match engineを試す。
pattern match engineは、前に試したpin interruptsを高度化したような機能であり、単独のpinでなく複数pinの状態の組み合わせで割り込みを発生させる。
前提とする環境はこちら→LPC810メモ 共通の準備
- pattern match engineは概略こんな感じ。
- 0-7番の8つの「slice」を定義できる。
- 各sliceでは、のpinから入力するか、どういう条件に反応するか(rising edgeとか、HIGH levelである、とか)を設定する。
- 連続した複数のsliceの条件が、endpointのsliceまですべて成立すれば、割り込み発生。
- このイメージは、「UM10601 8.5.2 Pattern match engine」のFig8,Fig9あたりをじっくり眺めていると理解できる。
- 例) slice0を1番pinのrising edge、slice1を2番pinのrising edgeに設定して、slice1がendpointとすると、「1番pinと2番pinのrising edgeがそれぞれ1回づつあったら」割り込み発生。
- 本例の動作
- 1,2,3,4番pinに、押すとLOWになるpushボタンがそれぞれついている。それぞれボタン1,2,3,4と呼ぶ。
- ボタン1,2,3,4を任意の順番で1回づつ押すと、LED点灯。これをslice0,1,2,3で実現。
- ボタン1,3,を押下、ボタン2,4を離した状態になると、LED消灯。これをslice4,5,6,7で実現。
サンプルコード
#include "LPC8xx.h" int main(void) { // GPIOの設定 8番pinを出力に設定して消灯しておく // (pattern matchとは直接関係ない) LPC_SYSCON->SYSAHBCLKCTRL |= (1<<6); LPC_GPIO_PORT->DIR0 |= (1<<0); LPC_GPIO_PORT->CLR0 |= (1<<0); // SWMの設定 // 固定pin機能をreset含めて無効化 // (pattern matchとは直接関係ない) LPC_SYSCON->SYSAHBCLKCTRL |= (1<<7); LPC_SWM->PINENABLE0 = 0xffffffffUL; // (pin interruptではなく)pattern matchを利用する旨の設定 // UM10601 8.6.11 Pattern Match Interrupt Control Register LPC_PIN_INT->PMCTRL |= (1 << 0); // 各pinをpattern matchの入力に設定 // 4.6.27 Pin interrupt select registers LPC_SYSCON->PINTSEL[0] = 5; // PIO0_5(1番pin)をPINTSEL0に設定 LPC_SYSCON->PINTSEL[1] = 4; // PIO0_4(2番pin)をPINTSEL1に設定 LPC_SYSCON->PINTSEL[2] = 3; // PIO0_3(3番pin)をPINTSEL2に設定 LPC_SYSCON->PINTSEL[3] = 2; // PIO0_2(4番pin)をPINTSEL3に設定 // 各sliceにどのPINTSELを割り当てるか設定 // 8.6.12 Pattern Match Interrupt Bit-Slice Source register LPC_PIN_INT->PMSRC = (0 << 8) // slice0にPINTSEL0(1番pin)を設定 | (1 << 11) // slice1にPINTSEL1(2番pin)を設定 | (2 << 14) // slice2にPINTSEL2(3番pin)を設定 | (3 << 17) // slice3にPINTSEL3(4番pin)を設定 | (0 << 20) // slice4にPINTSEL0(1番pin)を設定 | (1 << 23) // slice5にPINTSEL1(2番pin)を設定 | (2 << 26) // slice6にPINTSEL2(3番pin)を設定 | (3 << 29) // slice7にPINTSEL3(4番pin)を設定 ; // 各sliceの条件を設定 // 8.6.13 Pattern Match Interrupt Bit Slice Configuration register LPC_PIN_INT->PMCFG = (0x2 << 8) // CFG0 = 0x2 slice0(1番pin)のfalling edge | (0x2 << 11) // CFG1 = 0x2 slice1(2番pin)のfalling edge | (0x2 << 14) // CFG2 = 0x2 slice2(3番pin)のfalling edge | (0x2 << 17) // CFG3 = 0x2 slice3(4番pin)のfalling edge | (1 << 3) // PROD_ENDPTS3 = 1 slice3をendpointとする。 // 以上合わせて、「1,2,3,4番pinの押下(falling edge)が各1回づつあったら割り込み発生」となる。 | (0x5 << 20) // CFG4 = 0x5 slice4(1番pin)がLow leel | (0x4 << 23) // CFG5 = 0x4 slice5(2番pin)がHigh leel | (0x5 << 26) // CFG6 = 0x5 slice6(3番pin)がLow leel | (0x4 << 29) // CFG7 = 0x4 slice7(4番pin)がHigh leel // 以上合わせて、「1,3番pinが押された状態(Low)、2,4番pinが押されてない状態(High)で // 割り込み発生」となる。 // ちなみに、slice7はデフォルトでend pointなので、end pointの設定は不要 ; // slice3と7の割り込み有効化。 // UM10601 3.3.1 Interrupt sources NVIC->ISER[0] = (0x1 << 27) | (0x1 << 31); return 0 ; } extern "C" { // slice3に対応する割り込みハンドラ。 // cr_startup_lpc8xx.cpp で定義されている名称。 void PININT3_IRQHandler(void) { // 割り込みをクリア // UM10601 8.6.13 Pattern Match Interrupt Bit Slice Configuration register // のRemarkより、SEL_PMATCHをclear(して、再度set) LPC_PIN_INT->PMCTRL &= ~0x1; LPC_PIN_INT->PMCTRL |= 0x1; // LED(8番pin)点灯 LPC_GPIO_PORT->SET0 |= (1<<0); } // slice7に対応する割り込みハンドラ。 void PININT7_IRQHandler(void) { // 割り込みをクリア LPC_PIN_INT->PMCTRL &= ~0x1; LPC_PIN_INT->PMCTRL |= 0x1; // LED(8番pin)消灯 LPC_GPIO_PORT->CLR0 |= (1<<0); } }
接続
- 1,2,3,4番pinはそれぞれとりあえず3kΩでpull upしてあり、それぞれプッシュボタンを押すとLOWになるようにつないである
- LEDは8番pin
- 6,7番pin(Vdd,GND)は0.1μFのコンデンサ。
補足/雑感
- UM10601の「8.5.2 Pattern match engine」とか「8.7.2 Pattern Match engine example」とかを最初に読んだ時に、かなり柔軟なbool式を構成できるのかと誤解。なぜそんな事ができるのかわからず混乱。
- 「UM10601 8.5.2 Pattern match engine」のFig8,Fig9を眺めて、基本的には連続するsliceをAND接続するだけということに気づいてからは理解早かった。
- これをもって、当初試したいと思っていたLPC810の機能は全部試せた。3ヶ月くらいかかった。まだ初心者脱却を達成したとは思わないが、かなり勉強にはなったと思う。
- せっかくなので成果をまとめたいところ。
LPC810メモ:SPI interface(SDカードを読み書き)
SDカードに書き込みを行うサンプルですが、ファイルシステムは無視して書き込むので、使ったカードは再フォーマットしないとPCからアクセスできなくなると思われます。もし試そうとしてくれる方がいましたら、必ず不要なSDカードを使ってください。
SPI通信の例として、SDカードを読み書きする例。
前提とする環境はこちら→LPC810メモ 共通の準備
- 本例の動作。電源を入れると、以下を順に行う。
- SDカードの最後のブロックに、1,2,3,1,2,3...というデータを書き込む。
- 同じブロックからデータを読み込む
- 読み込んだ値に応じた回数のLEDを点滅させる。つまり、1回、2回、3回、1回、2回、3回...と点滅する。
サンプルコード
#include "LPC8xx.h" // 成功するまで粘るコマンドのための回数定義 #define TIMEOUT 5000 // 失敗したここに飛ばす。 // 引数の回数だけLED点滅させておく。 static void error(uint8_t e) { while(1) { for(uint8_t i = 0; i < e * 2; i++) { LPC_GPIO_PORT->NOT0 |= (1<<0); for(volatile int j = 0; j < 100000; j++) {} } for(volatile int i = 0; i < 300000; i++) {} } } static bool sdhc; // SDHC/XCの場合にtrue(途中で設定) static uint32_t sdBlocks; // ブロックの数(途中で設定) // read/write時のアドレス指定(sdhcだとblock単位) static uint32_t sdAdress(uint32_t block) { return block * (sdhc ? 1 : 512); } // CSにつなぐ1番pin(GPIO5)をHIGHにする、LOWにする static void csSet() { LPC_GPIO_PORT->SET0 |= (1<<5); }; static void csClear() { LPC_GPIO_PORT->CLR0 |= (1<<5); }; // 1byte読み書き // UM10601 29.3.5 Transmit and receive a byte to/from slave 0 // の例を真似。 static uint8_t spiWrite(uint8_t arg = 0xff) { // UM10601 17.6.3 SPI Status register // TXRDY(Transmitter Ready flag)が立つまで待つ while(~LPC_SPI0->STAT & (1 << 1)); // UM10601 17.6.7 SPI Transmitter Data and Control register // 1バイト書き込み LPC_SPI0->TXDATCTL = arg // TXDAT = 送信するデータ | (8 - 1) << 24 // LEN = 8bit送信 | (1 << 20); // EOT = 1 (data stallの発生を防ぐのに必要だとか) // UM10601 17.6.3 SPI Status register // RXRDY(Receiver Ready flag)が立つまで待つ while(~LPC_SPI0->STAT & (1 << 0)); // UM10601 17.6.6 SPI Receiver Data register // 読み込み結果を返す return LPC_SPI0->RXDAT; } // SDカードにコマンド送信 // skipはR1byteの後に何バイト読むか(R7なら4byteなど)。ただし読むだけで無視する。 static uint8_t sdCmd(uint8_t cmd, uint32_t arg = 0, int skip = 0, uint8_t crc=0) { csClear(); spiWrite(0x40 | cmd); spiWrite(arg >> 24); spiWrite(arg >> 16); spiWrite(arg >> 8); spiWrite(arg >> 0); spiWrite(crc); for (int i = 0; i < TIMEOUT; i++) { int response = spiWrite(); if (!(response & 0x80)) { for (int j = 0; j < skip; j++) { spiWrite(); } csSet(); spiWrite(); return response; } } csSet(); spiWrite(); return 0xff; // タイムアウト } // データパケット読み込み static void sdReadPacket(uint8_t *buffer, int length) { csClear(); // データトークンを待つ for (int i = 0;; i++) { if(i > TIMEOUT) { error(5); }; if(spiWrite() == 0xFE) { break; }; } for (int i = 0; i < length; i++) { buffer[i] = spiWrite(); } // データ読み込み spiWrite(); // CRC spiWrite(); csSet(); spiWrite(); } // 指定されたブロックから512byte読み込み static void sdRead(uint8_t *buffer, uint32_t block_number) { // CMD17 if (sdCmd(17, sdAdress(block_number)) != 0) { error(6); } sdReadPacket(buffer, 512); } // 指定されたブロックに512byte書き込み static void sdWrite(uint8_t *buffer, uint32_t block_number) { // CMD24 if (sdCmd(24, sdAdress(block_number)) != 0) { return; } csClear(); spiWrite(0xFE); // データトークン for (int i = 0; i < 512; i++) { spiWrite(buffer[i]); } // データ読み込み spiWrite(); // CRC spiWrite(); // レスポンストークン if ((spiWrite() & 0x1F) != 0x05) { // 成功 csSet(); spiWrite(); return; } // なんらか失敗(0以外が戻るまで待つ) for (int i = 0;; i++) { if(i > TIMEOUT) { error(7); }; if(spiWrite() != 0) { break; }; } csSet(); spiWrite(); } // SDカード初期化 void sdInitialize() { // SDカードが立ち上がってないかもしれないので少しだけ待つ for(volatile int i = 0; i < 5000; i++) {} // UM10601 17.6.10 SPI Divider register LPC_SPI0->DIV = 120 - 1; // 12MHz / 120 = 100KHzに設定 // SPIをmasterとして有効化 // UM10601 17.6.1 SPI Configuration register LPC_SPI0->CFG = (1 << 0) // ENABLE = 1 | (1 << 2); // MASTER = 1 // SDカード初期化のためにCSをHIGHにして0xffを送信(クロック74回以上) csSet(); for(int i = 0; i < 16; i++) { spiWrite(); } // 16byte送って、都合128クロック if(sdCmd(0,0,0,0x95) != 1) { error(2); }; // CMD0(リセット) // CMD8の結果が1(idle state, illegal commandではない)ならSDHC/XCとみなす。 // 0x01 << 8は3.3v、0xaaはcheck pattern(なぜ0xaaなのかは知らない) sdhc = (sdCmd(8, 0x01aa, 4, 0x87) == 1) ; // ACMD41(初期化) for (int i = 0;; i++) { if(i > TIMEOUT) { error(3); }; sdCmd(55); if (sdCmd(41, sdhc ? (1 << 30) : 0) == 0) { break; }; // 成功 } sdCmd(16, 512); // CMD16(ブロック長を512byteに設定。なくても良い気がする。) LPC_SPI0->DIV = 12 - 1; // 12MHz / 12 = 1MHzに設定 // CMD9(CSDを読んで、カードのブロック(セクタ)の数を求める) if (sdCmd(9) != 0) { error(4); } uint8_t csd[16]; sdReadPacket(csd, 16); if(sdhc) { // CSD Version 2.0 uint32_t c_size = (csd[7] & 0x3f) << 16 | csd[8] << 8 | csd[9]; // [69:48] device size sdBlocks = (c_size + 1) * 1024; } else { // CSD Version 1.0 uint32_t c_size = (csd[6] & 0x3) << 10 | (csd[7] << 2) | (csd[8] & 0xc0) >> 6; // [73:62] device size uint32_t c_size_mult = (csd[9] & 0x3) << 1 | (csd[10] & 0x80) >> 7; // [49:47] device size multiplier uint32_t read_bl_len = (csd[5] & 0xf); // [83:80] max. read data block length uint32_t block_len = 1 << read_bl_len; uint32_t mult = 1 << (c_size_mult + 2); uint32_t blocknr = (c_size + 1) * mult; uint32_t capacity = blocknr * block_len; sdBlocks = capacity / 512; } } int main(void) { // GPIOの設定 1,8番pinを出力に設定しておく // 1番はSDカードのCSにつなぐ // 8番はLEDにつなぐn LPC_SYSCON->SYSAHBCLKCTRL |= (1<<6); LPC_GPIO_PORT->DIR0 |= (1<<0) | (1<<5); // LED消灯 LPC_GPIO_PORT->CLR0 |= (1<<0); // 「UM10601 17.3 Basic configuration」の手順で初期化 // SPI0に対してclockを有効化(bit11。bit12はspi1なので関係ない) // In the SYSAHBCLKCTRL register, set bit 11 and 12 (Table 30) to // enable the clock to the register interface. LPC_SYSCON->SYSAHBCLKCTRL |= (1<<11); // Clear the SPI0/1 peripheral resets using the PRESETCTRL register (Table 19). LPC_SYSCON->PRESETCTRL &= ~(0x1<<0); LPC_SYSCON->PRESETCTRL |= (0x1<<0); // SWMの設定 LPC_SYSCON->SYSAHBCLKCTRL |= (1<<7); /* Pin Assign 8 bit Configuration */ LPC_SWM->PINASSIGN3 = 0x04ffffffUL; // SPI0_SCK ->2番pin LPC_SWM->PINASSIGN4 = 0xffff0302UL; // SPI0_MISO->3番、SPI0_MOSI->4番 LPC_SWM->PINENABLE0 = 0xffffffffUL; // 固定pin機能はreset含めて無効化(1番使いたいから) // SDカード初期化 sdInitialize(); // 読み書き用のbufferを用意。データを適当に入れる(頭から順に1,2,3,1,2,3....) uint8_t buffer[512]; for(int x = 0; x < 512; x++) { buffer[x] = x % 3 + 1; } // SDカードの最後のセクタにbufferの内容を書き込む sdWrite(buffer, sdBlocks-1); // 読み込み用のデータをゼロ初期化 for(int x = 0; x < 512; x++) { buffer[x] = 0; } // SDカードの最後のセクタからbufferの内容を読み込む sdRead(buffer, sdBlocks-1); // buffer[x]の値に応じた回数だけLED点滅 for(int x = 0; x < 512; x++) { for(int j = 0; j < buffer[x] * 2; j++) { LPC_GPIO_PORT->NOT0 |= (1<<0); for(volatile int i = 0; i < 200000; i++) {} } for(volatile int i = 0; i < 1000000; i++) {} } return 0 ; }
接続
- 左型の緑色のは、秋月で買ったSDカードスロットDIP化モジュール(250円)
- SDカードとの結線は下記
LPC810 | SDカード |
---|---|
1番pin(GPIO5) | CS |
2番pin(SPI0_SCK) | CLK |
3番pin(SPI0_MISO) | SDO |
4番pin(SPI0_MOSI) | SDI |
- LEDは8番pin
- 6,7番pin(Vdd,GND)は0.1μFのコンデンサ。
- 実行後のSDカードの中身をHxDで確認した結果
- 8MBのSDカード
- 最後のSector(=Block)13278に、01,02,03...と書き込まれている。
- 8MBのSDカード
-
- 8GBのSDHCカード
- 最後のSector(=Block)15278079に、01,02,03...と書き込まれている。
- 8GBのSDHCカード
LPC810メモ:SysTick Timer
SysTick Timerを試す。
前提とする環境はこちら→LPC810メモ 共通の準備
- SysTick Timer
- 指定した周期で割り込みを発生させるタイマ。
- counterは24bit。1clockで1countか、2clockで1countか選択できる。
- LPCの機能というよりは、Cortex-M0+.の機能らしい。
- この例の動作
- 1秒ごとに割り込みを発生させて、LEDの点灯/消灯を切替える。
サンプルコード
#include "LPC8xx.h" int main(void) { // LED点滅用に、8番pinを出力に設定しておく LPC_SYSCON->SYSAHBCLKCTRL |= (1<<6); LPC_GPIO_PORT->DIR0 |= (1<<0); // SysTick Timer関連のレジスタの構造体は、LPC8xx.hからincludeされる // core_cm0plus.h で定義されている。 // UM10601 14.6.2 System Timer Reload value register // タイマの周期を1秒に設定。 // デフォルトでは2clockで1countなので、12MHz/2で6000000。 SysTick->LOAD = 6000000; // タイマの初期値をゼロにする。 // UM10601 14.6.3 System Timer Current value register SysTick->VAL = 0; // タイマを有効化 // UM10601 14.6.1 System Timer Control and status register SysTick->CTRL = (1 << 0) // ENABLE = 1 | (1 << 1) // TICKINT = 1 .. 割り込み有効化 ; return 0 ; } // 割り込みハンドラの定義 extern "C" { void SysTick_Handler(void) { // 8番pinの出力を反転 // (LED点滅) LPC_GPIO_PORT->NOT0 |= (1<<0); } }
補足/雑感
- MRT(Multi-Rate Timer)やSCT(SCTimer/PWM)と比べればかなりシンプル。コードの量も少ない。
- ただしあまり周期は長くとれない。
- 2clockで1countに設定して、24bitの最大値(16777215?)にしても、12MHzだと2.7秒くらい。
LPC810メモ:Flash IAP
UM10601「Chapter 22: LPC81x Flash ISP and IAP programming」の、IAPを使う例。
前提とする環境はこちら→LPC810メモ 共通の準備
- Flash IAP
- 本来はプログラムを格納するためのflash romの領域を操作する機能(In-Application programming)
- 電源をoffにしても保持したい情報の記録に利用できる。
- この例の内容
- 電源onすると、flashの領域にあるアドレス'0x00000FC0'に記載された値を1増やす。
- その値の回数だけLEDを点滅させる(他は何もしない)。
- 結果として、電源off/onを繰り返す毎に、'0x00000FC0'の値が1づつ増え、LEDの点滅回数が増える。
- 毎回、対象のセクタ(sector3、0x00000C00 - 0x00000FFF)はまるごと消去されるので、他のアドレスの値は0xffになる。
サンプルコード
#include "LPC8xx.h" // UM10601 22.5.2 IAP commands // に従った宣言 typedef void (*IAP)(unsigned int [],unsigned int[]); IAP iap_entry = (IAP)0X1FFF1FF1UL; int main(void) { // 引数を渡す配列と、結果を受けとる配列を準備 // UM10601 22.5.2 IAP commands unsigned int command_param[5]; unsigned int status_result[4]; // sector3、page63の先頭アドレス、0x00000FC0の値を読み込む uint8_t value = *((uint8_t *)0x00000FC0UL); // 書き込むための領域(最低1page、すなわち64byte)を準備。 uint8_t buffer[64] = {0}; // 0byte目に、読んだ値 + 1 を格納しておく。 // (この値が、後のAPIでsector3、page63の先頭アドレス、0x00000FC0で書き込まれる) buffer[0] = value+ 1; // 割り込み禁止 // (割り込み禁止にしないで使う方法もあるようだが面倒なので調べない) __disable_irq(); // ここからAPIを呼ぶ。 // 呼び方は、「UM10601 22.5.2 IAP commands」を見る。 // command_param[]に引数をいれて、 // result_param[]で結果を受け取る。 // (本例では結果は全く使ってない) // 消去の準備 // UM10601 22.5.2.1 Prepare sector(s) for write operation (IAP) command_param[0] = 50; // Command code command_param[1] = 3; // sector 3から、 command_param[2] = 3; // sector 3までを対象とする。 iap_entry(command_param, status_result); // API呼び出し // 消去 // UM10601 22.5.2.3 Erase Sector(s) (IAP) command_param[0] = 52; // Command code command_param[1] = 3; // sector 3から、 command_param[2] = 3; // sector 3までを対象とする。 command_param[3] = 12000; // 動作クロックは12MHz(12000KHz) iap_entry(command_param, status_result); // API呼び出し // 書き込みの準備(消去の準備と同じ) // UM10601 22.5.2.1 Prepare sector(s) for write operation (IAP) command_param[0] = 50; command_param[1] = 3; command_param[2] = 3; iap_entry(command_param, status_result); // 書き込み // UM10601 22.5.2.2 Copy RAM to flash (IAP) command_param[0] = 51; // Command code command_param[1] = 0x00000FC0UL; // 書き込み先の先頭アドレス(sector3, page63) command_param[2] = (unsigned int)buffer; // 書き込み元の先頭アドレス command_param[3] = 64; // 64byte書き込む(これが最低値)。 command_param[4] = 12000; // 動作クロックは12MHz(12000KHz) iap_entry(command_param, status_result); // 割り込み有効化 __enable_irq(); // 0x00000FC0ULの値の数だけ、8番pinのLEDを点滅 LPC_SYSCON->SYSAHBCLKCTRL |= (1<<6); LPC_GPIO_PORT->DIR0 |= (1<<0); for(uint32_t i = 0; i < *((uint8_t *)0x00000FC0UL) * 2; i++) { LPC_GPIO_PORT->NOT0 |= (1<<0); for(volatile int i = 0; i < 200000; i++) {} } return 0 ; }
補足/雑感
- flashについての知識、多分常識なんだろうけど知らなかったために、それなりにハマった。
- flashは基本、消去してからじゃないと書き込めない。
- 正確には、消去しなくても、各bit毎に1を0にすることはできる。しかし逆はできない。つまり、0xff->0xfeにすることはできるが(最下位bitを0にする)、0x00を0x01にすることはできない。
- 消去した時の値は0xffになる。
- 書き込みでは0を1にする事しかできないんだから当たり前だ。でも、最初は0になるっていう先入観でハマった。
- flashは基本、消去してからじゃないと書き込めない。
- flashに書き込まれている値は、flash magicのメニュー、[ISP]->[Display Memory]で参照可能。
- 便利だけど、自分の環境だと、これを使うと頻繁にflash magicごと落ちる。
- 本例では完全無視だが、APIの成否はresult[0]に入っているstatus codeを見る。
- UM10601 22.5.2.11 IAP Status codes
- セクタやページとアドレスの対応表はここ
- UM10601 22.4.1 Flash configuration
- セクタは1KB単位で、ページは64byte単位。
- 書き込みはページ単位(64byte)からできるが、消去は実質セクタ単位かもしれない。
LPC810メモ:deep power-downモード(Self wake-up timerで復帰)
前回と同様、deep power-downモードの実験。
ただし、WAKEUP pinではなく、Self wake-up timer(WKT)で定期的にwake upする例。
前提とする環境はこちら→LPC810メモ 共通の準備
- 電源onすると、LED一回点滅してdeep power-down
- 3秒経過するとwake up、LEDを2回点滅してdeep power-down
- 以降、3秒経過する度にwake up、点滅回数が一回ずつ増える。
サンプルコード
#include "LPC8xx.h" int main(void) { // 何回目の起動かを判別用 uint32_t count = 1; // wake up時の手順、deep power-downに入る手順は、それぞれ // UM10601の5.7.7.4、5.7.7.5 // wake upかどうか判定 // UM10601 5.6.1 Power control register if(LPC_PMU->PCON & (1 << 11)) { // DPDFLAG(11)が立ってるので、deep power-downからの復帰である。 // 同フラグをクリア LPC_PMU->PCON &= ~(1 << 11); // Deep power-downする前に記録した汎用レジスタから値を読み込み // UM10601 5.6.2 General purpose registers 0 to 3 count = LPC_PMU->GPREG0; } // countの回数分、8番pinのLEDを点滅 LPC_SYSCON->SYSAHBCLKCTRL |= (1<<6); LPC_GPIO_PORT->DIR0 |= (1<<0); for(uint32_t i = 0; i < (count * 2); i++) { LPC_GPIO_PORT->NOT0 |= (1<<0); for(volatile int i = 0; i < 200000; i++) {} } /// Self wake-up timer (WKT)の有効化 // UM10601 13.3 Basic configuration LPC_SYSCON->SYSAHBCLKCTRL |= (1<<9); LPC_SYSCON->PRESETCTRL &= ~(0x1<<9); LPC_SYSCON->PRESETCTRL |= (0x1<<9); // Deep power-downの処理 // UM10601 5.6.3 Deep power-down control register LPC_PMU->DPDCTRL |= (1 << 1) // WAKEPAD_DISABLE = 1(今回はWAKEUP pinは使わない) | (1 << 2) // LPOSCEN = 1 | (1 << 3); // LPOSCDPDEN = 1 // UM10601 5.6.1 Power control register LPC_PMU->PCON &= ~(1 << 3); // NODPD = 0 LPC_PMU->PCON |= 0x3; // PM = 0x3 // UM10601 5.6.2 General purpose registers 0 to 3 LPC_PMU->GPREG0 = count + 1; // 次回のために汎用レジスタをカウントアップ // UM10601 5.3.1.1 System control register SCB->SCR |= (1 << 2); // SLEEPDEEP = 1 /// Self wake-up timer (WKT)設定 // 13.6.1 Control register LPC_WKT->CTRL |= (1 << 0); // CLKSEL = 1 10KHzのlow power clockを利用 LPC_WKT->COUNT = 30000; // 10KHzなので、3秒に設定 // Use the ARM WFI instruction. __WFI(); return 0 ; }
補足/雑感
- WAKEUP pin使わないから、UM10601「5.4 Pin description」の記載を無視して1,2番をpull upしてないが、問題ないのか不明。1番(RESET)はpull upした方が良い気もする。
- この機能って何に使うんだろう。1時間毎に温度記録する、とか?
LPC810メモ:deep power-downモード(wake up pinで復帰)
reduced power modes(省電力モード?)のうち、最も簡単そうで、かつ最も低消費電力らしい、「deep power-downモード」の実験。
前提とする環境はこちら→LPC810メモ 共通の準備
- 消費電力順にならべると以下のとおりらしい。数値は電流、データシートLPC81xMの「11. Static characteristics」のTypicalの数値から。
-
- active mode(1.4mA)
- sleep mode(0.8mA)
- deep-sleep mode(150μA)
- powor-down mode(0.9μA)
- deep powor-down mode(170nA)
-
- deep power-downモード特徴
- この例の内容
- 電源onすると、LED一回点滅してdeep power-down
- ボタンを押す(wake up pinがLOWになる)とwake up、LEDを2回点滅してdeep power-down
- 以降、ボタン押す度にwake up、点滅回数が一回ずつ増える。
サンプルコード
#include "LPC8xx.h" int main(void) { // 何回目の起動かを判別用 uint32_t count = 1; // wake up時の手順、deep power-downに入る手順は、それぞれ // UM10601の5.7.7.2、5.7.7.3 // wake upかどうか判定 // UM10601 5.6.1 Power control register if(LPC_PMU->PCON & (1 << 11)) { // DPDFLAG(11)が立ってるので、deep power-downからの復帰である。 // 同フラグをクリア LPC_PMU->PCON &= ~(1 << 11); // Deep power-downする前に記録した汎用レジスタから値を読み込み // UM10601 5.6.2 General purpose registers 0 to 3 count = LPC_PMU->GPREG0; } // countの回数分、8番pinのLEDを点滅 LPC_SYSCON->SYSAHBCLKCTRL |= (1<<6); LPC_GPIO_PORT->DIR0 |= (1<<0); for(uint32_t i = 0; i < (count * 2); i++) { LPC_GPIO_PORT->NOT0 |= (1<<0); for(volatile int i = 0; i < 200000; i++) {} } // Deep power-downの処理 // UM10601 5.6.3 Deep power-down control register LPC_PMU->DPDCTRL |= (1 << 0); // Hysteresis for WAKEUP pin enabled // UM10601 5.6.1 Power control register LPC_PMU->PCON &= ~(1 << 3); // NODPD = 0 LPC_PMU->PCON |= 0x3; // PM = 0x3 // UM10601 5.6.2 General purpose registers 0 to 3 LPC_PMU->GPREG0 = count + 1; // 次回のために汎用レジスタをカウントアップ // UM10601 5.3.1.1 System control register SCB->SCR |= (1 << 2); // SLEEPDEEP = 1 // Use the ARM WFI instruction. __WFI(); return 0 ; }
接続
- ボタンは4番pin、LEDは8番pin
- 電池はいつもの3本じゃなくて2本にしてる。2.5vだけど一応動いている。
- 1,2番をpull upしてるのは、UM10601「5.4 Pin description」の記載による。
- WAKEUPとRESETは安定化のために外部でpull upせよとか。
- 6,7番pin(Vdd,GND)は0.1μFのコンデンサ。
補足/雑感
- 精度は怪しいけど、テスターで電流測ってみた。
- LED点滅中(active mode) -> 2mA前後
- deep power-down mode -> 0.1-0.3μAくらい
- データシートの数値とわりと近い。
- 0.3μAとかだと、ボタン電池一つで何年も持ちそう。誕生日カードとかで押すと音楽が鳴るやつ、ああいうの作ってみたい。
LPC810メモ:I2C-bus ROM API、同じLPC810でI2C slaveとmasterの両機能を使う
master送信、slave受信の例や、master受信、slave送信の例と同様、LPC810を2個使って、それぞれmaster/slaveとして機能させる例。
前回までは役割固定で、I2C slaveとなったLPC810はslaveとしてのみ機能(masterも同様)としていたが、今回はmasterとslaveが動的に入れ替わるようにする実験。
前提とする環境はこちら→LPC810メモ 共通の準備
- 通信の内容と仕掛けは、master送信、slave受信の例と同じ。文字列("led off", "led on")を送受信する。
- LPC810を2個使うのも同じ。ただし、それぞれ以下のように設定される。
- 電源on時に8番がHIGH(ボタン押下)の方が、I2c slaveアドレス0xc0になる。
- 電源on時に8番がLOWの方が、I2c slaveアドレス0xc2になる。
- 従って、電源onの時、どちらか片方のボタンを押しておく必要あり。
- プッシュボタンを押す度に、自分のLEDの点灯/消灯をトグルしつつ、I2C masterとして、もう片方のLPC810に"led on","led off"を送信する。
- もう片方のLPC810は、I2C slaveとしてそれを受け取り、LEDを点灯/消灯する。
- 結果として、どちらのボタンを押しても、I2C slave/master双方のLEDが点灯/消灯がトグルする。
サンプルコード
#include "LPC8xx.h" #include<string.h> #define I2C_BUFF_SZ 16 // UM10601 24.4 API description // に相当すると思われるプロトタイプ宣言を、 // LPCOpenのヘッダから持ってきた。 extern "C" { typedef int ErrorCode_t; // 本当はenumだけど今回は値は見ない typedef int CHIP_I2C_MODE_T; // これも本当はenum typedef void *I2C_HANDLE_T; typedef void (*I2C_CALLBK_T)(uint32_t err_code, uint32_t n); typedef struct I2C_PARAM { uint32_t num_bytes_send; uint32_t num_bytes_rec; uint8_t *buffer_ptr_send; uint8_t *buffer_ptr_rec; I2C_CALLBK_T func_pt; uint8_t stop_flag; uint8_t dummy[3]; } I2C_PARAM_T; typedef struct I2C_RESULT { uint32_t n_bytes_sent; uint32_t n_bytes_recd; } I2C_RESULT_T; typedef struct I2CD_API { void (*i2c_isr_handler)(I2C_HANDLE_T *handle); ErrorCode_t (*i2c_master_transmit_poll)(I2C_HANDLE_T *handle, I2C_PARAM_T *param, I2C_RESULT_T *result); ErrorCode_t (*i2c_master_receive_poll)(I2C_HANDLE_T *handle, I2C_PARAM_T *param, I2C_RESULT_T *result); ErrorCode_t (*i2c_master_tx_rx_poll)(I2C_HANDLE_T *handle, I2C_PARAM_T *param, I2C_RESULT_T *result); ErrorCode_t (*i2c_master_transmit_intr)(I2C_HANDLE_T *handle, I2C_PARAM_T *param, I2C_RESULT_T *result); ErrorCode_t (*i2c_master_receive_intr)(I2C_HANDLE_T *handle, I2C_PARAM_T *param, I2C_RESULT_T *result); ErrorCode_t (*i2c_master_tx_rx_intr)(I2C_HANDLE_T *handle, I2C_PARAM_T *param, I2C_RESULT_T *result); ErrorCode_t (*i2c_slave_receive_poll)(I2C_HANDLE_T *handle, I2C_PARAM_T *param, I2C_RESULT_T *result); ErrorCode_t (*i2c_slave_transmit_poll)(I2C_HANDLE_T *handle, I2C_PARAM_T *param, I2C_RESULT_T *result); ErrorCode_t (*i2c_slave_receive_intr)(I2C_HANDLE_T *handle, I2C_PARAM_T *param, I2C_RESULT_T *result); ErrorCode_t (*i2c_slave_transmit_intr)(I2C_HANDLE_T *handle, I2C_PARAM_T *param, I2C_RESULT_T *result); ErrorCode_t (*i2c_set_slave_addr)(I2C_HANDLE_T *handle, uint32_t slave_addr_0_3, uint32_t slave_mask_0_3); uint32_t (*i2c_get_mem_size)(void); I2C_HANDLE_T * (*i2c_setup)( uint32_t i2c_base_addr, uint32_t * start_of_ram); ErrorCode_t (*i2c_set_bitrate)(I2C_HANDLE_T *handle, uint32_t p_clk_in_hz, uint32_t bitrate_in_bps); uint32_t (*i2c_get_firmware_version)(void); CHIP_I2C_MODE_T (*i2c_get_status)(I2C_HANDLE_T *handle); ErrorCode_t (*i2c_set_timeout)(I2C_HANDLE_T *handle, uint32_t timeout); } I2CD_API_T; // 「UM10601 21.5.2 ROM-based APIs」に相当するstruct // 本当はI2C以外も定義されているが今回は省略 typedef struct ROM_API { const uint32_t dummy1[5]; const I2CD_API_T *pI2CD; const uint32_t dummy2[4]; } LPC_ROM_API_T; // UM10601 24.3 General description // より、アドレスの設定 #define LPC_I2CD_API ((*(LPC_ROM_API_T * *) 0x1FFF1FF8UL)->pI2CD) } // slave側の記述ここから // APIを使う前提となる変数を用意 static I2C_HANDLE_T *i2cHandleSlave; static uint32_t i2cSlaveHandleMEM[0x20]; // 受信用のバッファ static uint8_t rxBuff[I2C_BUFF_SZ]; // 割り込みを使う都合上、APIで使うパラメータをファイルスコープで用意 static I2C_PARAM_T slaveParam; static I2C_RESULT_T slaveResult; // 何文字読んだか、rom apiの外側で記録するカウンタ static uint32_t slaveRecvCounter = 0; // 受信の準備 // 最初の1回だけは、slaveのアドレスとコールバックを設定して呼び出すようにした。 static void slaveSetupRecv(uint8_t addr = 0, I2C_CALLBK_T func = 0) { if(addr != 0) { // slaveのアドレスを設定 // UM10601 24.4.12 I2C Set Slave Address LPC_I2CD_API->i2c_set_slave_addr(i2cHandleSlave, addr, 0); } // コールバック関数を設定 if(func != 0) { slaveParam.func_pt = func; } // 受信用のバッファを設定 slaveParam.buffer_ptr_rec = rxBuff; // 最大受信バイト数 slaveParam.num_bytes_rec = sizeof(rxBuff); // 読み込んだ文字数をリセット slaveResult.n_bytes_recd = 0; slaveRecvCounter = 0; // 読み込んだ文字数を、rom apiの外側でも記録 // なぜかrom apiが使わない、Slave Deselectの割り込みを有効化 // 16.6.3 Interrupt Enable Set and read register LPC_I2C->INTENSET |= (1 << 15); // i2c slaveの受信、割り込みモード // 完了したらコールバックが呼ばれる仕組み // UM10601 24.4.10 I2C Slave Receive Interrupt LPC_I2CD_API->i2c_slave_receive_intr(i2cHandleSlave, &slaveParam, &slaveResult); } // 受信完了時に呼ばれるコールバック static void slaveCompleteRecv(uint32_t err_code, uint32_t n) { static char buffer[I2C_BUFF_SZ]; if (slaveResult.n_bytes_recd > 1) { // 1byteより多く読み込まれた場合 // なお、0byte目はslave addressが入るので、1byteジャストだと // 実質読み込まれてない。 // slave addressを除いたrxBuff[1] 以降を取り出す strncpy(buffer, (char *)(&rxBuff[1]), sizeof(buffer)); buffer[sizeof(buffer) - 1] = 0; // null終端の保証 // masterから送信された文字列が・・・ if(strcmp(buffer, "led on") == 0) { // "led on"なら、2番pinをHIGHに(slave側のledを点灯) LPC_GPIO_PORT->SET0 |= (1 << 4); } else if(strcmp(buffer, "led off") == 0) { // "led on"なら、2番pinをLOWに(slave側のledを点灯) LPC_GPIO_PORT->CLR0 |= (1 << 4); } } // 次回の受信準備 slaveSetupRecv(); } // I2C割り込みハンドラ extern "C" { void I2C_IRQHandler(void) { // slave pending状態で、slave receive.の場合、読み込み文字数を1増やす // UM10601 16.6.7 I2C Interrupt Status register // UM10601 16.6.2 I2C Status register if( (LPC_I2C->INTSTAT & (1 << 8)) && ((LPC_I2C->STAT & (0x3 << 9)) == (0x1 << 9))) { slaveRecvCounter++; } // 処理をrom apiに委譲 LPC_I2CD_API->i2c_isr_handler(i2cHandleSlave); // Slave Deselectedの割り込みを処理 // UM10601 16.6.7 I2C Interrupt Status register if(LPC_I2C->INTSTAT & (1 << 15)) { if(slaveRecvCounter > 0) { // 1byte以上読み込んでいたら、rom apiに代わってコールバック関数を呼ぶ slaveResult.n_bytes_recd = slaveRecvCounter; slaveCompleteRecv(0,0); } LPC_I2C->STAT |= (1 << 15); } } } static void slaveMain(uint8_t addr) { // I2C-bus ROM APIを使って初期設定 // UM10601 24.4.14 I2C Setup i2cHandleSlave =LPC_I2CD_API->i2c_setup(LPC_I2C_BASE, i2cSlaveHandleMEM); // タイムアウト設定 // UM10601 24.4.18 I2C time-out value LPC_I2CD_API->i2c_set_timeout(i2cHandleSlave, 100000); // Enable/disable the I2C interrupt in interrupt slots #8 in the NVIC. NVIC->ISER[0] = (0x1 << 8); // 受信の準備 slaveSetupRecv(addr, slaveCompleteRecv); } // slave側の記述ここまで // master側の記述ここから // APIを使う前提となる変数を用意 static I2C_HANDLE_T *i2cHandleMaster; static uint32_t i2cMasterHandleMEM[0x20]; // num byteのデータを送信 static void masterSendData(uint8_t addr, uint8_t data[], uint32_t num) { uint8_t d[I2C_BUFF_SZ]; d[0] = addr; // 0byte目はslaveのアドレスを入れる memcpy(&d[1], data, num); // [1]から先がデータ本体 I2C_PARAM_T param; I2C_RESULT_T result; param.num_bytes_send = num + 1; // addrの分を含めてnum + 1 byte送信 param.buffer_ptr_send = &d[0]; param.num_bytes_rec = 0; // 受信はしない(byte) param.stop_flag = 1; // 送信終わったらstop // タイムアウト設定 // UM10601 24.4.18 I2C time-out value LPC_I2CD_API->i2c_set_timeout(i2cHandleMaster, 100000); // 送信 // UM10601 24.4.2 I2C Master Transmit Polling LPC_I2CD_API->i2c_master_transmit_poll(i2cHandleMaster, ¶m, &result); } // ボタン押されたときの処理 // (初回呼び出し時はslaveアドレスの設定) static void masterHandlePushDown(uint32_t addr = 0) { static uint8_t slaveAddr = 0; static bool flag = true; if(addr != 0) { slaveAddr = addr; } else { if(flag) { // slaveに文字列"led on"を送信 uint8_t data[] = "led on"; masterSendData(slaveAddr, data, sizeof(data)); // 2番pinをHIGHに(master側のledを点灯) LPC_GPIO_PORT->SET0 |= (1 << 4); } else { // 8番pinがHIGH // slaveに文字列"led off"を送信 uint8_t data[] = "led off"; masterSendData(slaveAddr, data, sizeof(data)); // 2番pinをLOWに(master側のledを消灯) LPC_GPIO_PORT->CLR0 |= (1 << 4); } flag = !flag; // ★slaveとしての受信を再設定 // ★一回masterとして送信してしまったので、待ち受け状態はキャンセル // ★されている模様。従って再設定する。 slaveSetupRecv(); } } extern "C" { // ボタン押したときのハンドラ // cr_startup_lpc8xx.cpp で定義されている名称。 void PININT0_IRQHandler(void) { // 割り込みをクリア LPC_PIN_INT->IST = (1<<0); // ボタンが押された場合の処理 masterHandlePushDown(); } } // master側の初期設定 static void masterMain(uint8_t addr) { // I2C-bus ROM APIを使って初期設定 // UM10601 24.4.14 I2C Setup i2cHandleMaster = LPC_I2CD_API->i2c_setup(LPC_I2C_BASE, i2cMasterHandleMEM); // UM10601 24.4.15 I2C Set Bit Rate LPC_I2CD_API->i2c_set_bitrate(i2cHandleMaster, 12000000, 100000); // 速度100KHz // ボタン押下に対する割り込み設定 LPC_SYSCON->PINTSEL[0] = 0; // PIO0_0(8番pin)をpin interruptの0番に NVIC->ISER[0] = (0x1 << 24); // Pin interrupt 0の割り込み有効化 LPC_PIN_INT->SIENR = (1 << 0); // rising edge(本例では押した時)に割り込み発生 // ボタンが押された時の処理を初期化(slaveアドレスの設定) masterHandlePushDown(addr); } // master側の記述ここまで int main(void) { // SWMのclockを有効化。 // UM10601 4.6.13 System clock control register // → SWMは7bit目 LPC_SYSCON->SYSAHBCLKCTRL |= (1<<7); // SDAを4番pinに // UM10601 9.5.8 Pin assign register 7 LPC_SWM->PINASSIGN7 = 0x02ffffffUL; // SCLを3番pinに // UM10601 9.5.9 Pin assign register 8 LPC_SWM->PINASSIGN8 = 0xffffff03UL; // fixed pin functionはreset以外全部disable // UM10601 9.5.10 Pin enable register 0 LPC_SWM->PINENABLE0 = 0xffffffbfUL; // UM10601 16.3 Basic configuration // にしたがってI2Cの準備 // In the SYSAHBCLKCTRL register, set bit 5 (Table 30) to enable the clock to theregister interface. LPC_SYSCON->SYSAHBCLKCTRL |= (0x1<<5); // Clear the I2C peripheral reset using the PRESETCTRL register (Table 19). LPC_SYSCON->PRESETCTRL &= ~(0x1<<6); LPC_SYSCON->PRESETCTRL |= (0x1<<6); // GPIO clockを有効化 // UM10601 4.6.13 System clock control register LPC_SYSCON->SYSAHBCLKCTRL |= (1<<6); // 2番pinを出力に設定 LPC_GPIO_PORT->DIR0 |= (1<<4); if(LPC_GPIO_PORT->PIN0 & (1<<0)) { // 開始時点で5番がHIGH -> slave address 0xc0で動作 masterMain(0xc2); // 0xc2のslaveに送信するmasterとして機能 slaveMain(0xc0); // 0xc0のslaveとして機能 } else { // 開始時点で5番がLOW -> slave address 0xc2で動作 masterMain(0xc0); // 0xc0のslaveに送信するmasterとして機能 slaveMain(0xc2); // 0xc2のslaveとして機能 } return 0 ; }
接続
補足/雑感
- http://d.hatena.ne.jp/mikkabo/20150509/1431123991:pin interruptを早速利用。
- master受信、slave送信の例を試した時に、slave側から送信する手順は少し面倒な気がしたので、どうせLPC810同士(どちらもI2C slaveにもmasterになれる)なら「常に受ける方がslave」でも良いのでは、と思って実験。
- サンプルコード中の「★」の行について。それまでi2c_slave_receive_intrでslave側として割り込み待ちをしていたとしても、master側のAPI(i2c_master_transmit_poll)を使ったら無効になってしまうようなので、再度i2c_slave_receive_intrを呼んでおく必要があるようだ。
- これまでの例でパスコンとか付けてなかったが、原因不明でうまく動かないことがあり、気休めとしてつけたら安定するようになった気がする。効いたんだろうか。