LPCマイコンとmbed関係の個人的メモ

Pocket

自分のためのメモです。今回はオーディオは全く関係ありません。基本的にmbedをLPCXpressoで使った時のTips的内容になる雰囲気です。何か気づいたことがあったら随時追加していきます。

LPC824のmbed-srcをLPCXpressoで使う時の問題

すごく限定条件なきがしますが初期状態ではタイマーが正しく動作しません。これの原因は初期設定の関数が呼び出されていないために、CPUクロックが規定の30Mhzより低い状態で動作してしまうからです。

1

デバッガで追いかけてみるとSystemInit();が呼び出されません。

該当部のソースを見ると、defineに__USE_CMSISと__USE_LPCOPENのどちらかがないと初期化関数が呼び出されないようになっていて、LPC824のmbed-srcをLPCXpressoにインポートした場合には、何故かデフォルトではこの部分が呼び出されない状態になっているようです。

2

正しく動作させるためにはこちらの画面で__USE_CMSISを追加するか、cr_startup_lpc82x.cppのdefine条件自体を削除することで対応できます。

LPC15XXで内蔵EEPROMを使う時にハマったこと

LPC1549等のLPC15XXシリーズで内蔵EEPROMをmbedライブラリと合わせて使おうと思った時に詰まるポイントが2箇所あったのでメモしておきます。ネット上では海外含めて具体的に解決策を解説しているサイトがなさそうなのでここにまとめておきます。

LPCシリーズの内蔵EEPROM書き込みには内蔵フラッシュと同じような手続き(IAP)が必要のようで、なかなか手数が多い印象でAVRの内蔵EEPより大分めんどくさい感じです。でもせっかく内蔵しているので使いたいところです。

まずEEPROM書き込みに使用するソースはこちら。

https://developer.mbed.org/users/okini3939/code/eeprom/

ただしこのソースは11シリーズ用なので書き込みアドレスが異なっており、1500シリーズ用に書き換える必要があります。あまり使用者の多くないCPUみたいなのでノウハウの情報が少ないのが難点ですが、LPC1549はUSBもスイッチマトリクスもついていてピン数もそこそこあってRAM容量も大きいという、お手軽ライブラリのmbedと相性がすごく良いと個人的に思っているところです。

該当部はこちら。eeprom.cppの中身。

enum command_code
{
	IAPCommand_EEPROM_Write = 61,
	IAPCommand_EEPROM_Read,
};

//#define     IAP_LOCATION    0x1fff1ff1
//ここのアドレスを変更!
#define		IAP_LOCATION    0x03000205
typedef     void (*IAP_call)(unsigned int [], unsigned int []);

IAP_call        iap_entry = reinterpret_cast<IAP_call>(IAP_LOCATION);
unsigned int    IAP_command[ 5 ];
unsigned int    IAP_result[ 5 ];
int             cclk_kHz = SystemCoreClock / 1000;

マニュアルと、オンライン検索で見つかる幾つかのソースでは、IAP_LOCATIONが0x3000200になっていますが、実は0x3000205が正しいので注意!最初デバッガで追いかけてみるとiap_entryのタイミングでHardfaultになってしまい何が原因か全くわからなかったのですが、ねむいさんのOpenOCDソース改定履歴にこの内容の指摘があって間違いに気付きました。まずはここがハマるポイントです。

http://openocd.zylin.com/#/c/2304/

ここを治すことでとりあえずHardFaultは起きなくなります。実は本家LPCOpenのLPC1500版にもEEPROMのライブラリが含まれていたのですが、ライブラリのファイルをみてもIAP_CALLの初期設定がどこに書いてあるのかなかなか発見できませんでした…。LPCOpenのライブラリは初期定義があちこち拡散していていったい何がどこで宣言されているのか探すのに苦労します。結局動いた後で記述を見つけたのですけどわかりにくすぎます。その点mbedのソースのほうが宣言と関数がよく整理されている印象です。mbedライブラリだとCPUの深層や固有機能に触れることは出来ませんが、SPIとかAD含めて汎用的な機能の制御ならmbedライブラリを使っていくほうが後から見て分かりやすいコードが書けるのが良いです。STMシリーズにあとから乗り換えとかしても出来るだけmbedで書いておけばプログラム書き直しもしなくてよいですし。

話がそれました。ということで一つ目はこれでOKなのですが、LPC1500シリーズでEEPROMを動かすために必要な手続きはまだ終わりではありません。もう一つ必要な初期化設定があります。必要な部分をLPCOpenのライブラリから抜粋します。

/**
 * Peripheral reset identifiers, not available on all devices
 */
typedef enum {
	/* PRESETCTRL0 resets */
	RESET_FLASH = 7,		/*!< FLASH controller reset control */
	RESET_EEPROM = 9,		/*!< EEPROM controller reset control */
	RESET_MUX = 11,			/*!< Input mux reset control */
	RESET_IOCON = 13,		/*!< IOCON reset control */
	RESET_PININT = 18,		/*!< Pin interrupt (PINT) reset reset control */
	RESET_GINT,				/*!< Grouped interrupt (GINT) reset control */
	RESET_DMA,				/*!< DMA reset control */
	RESET_CRC,				/*!< CRC reset control */
	RESET_ADC0 = 27,		/*!< ADC0 reset control */
	RESET_ADC1,				/*!< ADC1 reset control */
	RESET_ACMP = 30,		/*!< Analog Comparator (all 4 ACMP) reset control */
	RESET_MRT = 32 + 0,		/*!< Multi-rate timer (MRT) reset control */
	RESET_RIT,				/*!< Repetitive interrupt timer (RIT) reset control */
	RESET_SCT0,				/*!< State configurable timer 0 (SCT0) reset control */
	RESET_SCT1,				/*!< State configurable timer 1 (SCT1) reset control */
	RESET_SCT2,				/*!< State configurable timer 2 (SCT2) reset control */
	RESET_SCT3,				/*!< State configurable timer 3 (SCT3) reset control */
	RESET_SCTIPU,			/*!< State configurable timer IPU (SCTIPU) reset control */
	RESET_CAN,				/*!< CAN reset control */
	RESET_SPI0 = 32 + 9,	/*!< SPI0 reset control */
	RESET_SPI1,				/*!< SPI1 reset control */
	RESET_I2C0 = 32 + 13,	/*!< I2C0 reset control */
	RESET_UART0 = 32 + 17,	/*!< UART0 reset control */
	RESET_UART1,			/*!< UART1 reset control */
	RESET_UART2,			/*!< UART2 reset control */
	RESET_QEI0 = 32 + 21,	/*!< QEI0 reset control */
	RESET_USB = 32 + 23		/*!< USB reset control */
} CHIP_SYSCTL_PERIPH_RESET_T;

/**
 * System and peripheral clocks
 */
typedef enum CHIP_SYSCTL_CLOCK {
	/* Peripheral clock enables for SYSAHBCLKCTRL0 */
	SYSCTL_CLOCK_SYS = 0,				/*!< System clock */
	SYSCTL_CLOCK_ROM,					/*!< ROM clock */
	SYSCTL_CLOCK_SRAM1 = 3,				/*!< SRAM1 clock */
	SYSCTL_CLOCK_SRAM2,					/*!< SRAM2 clock */
	SYSCTL_CLOCK_FLASH = 7,				/*!< FLASH controller clock */
	SYSCTL_CLOCK_EEPROM = 9,			/*!< EEPROM controller clock */
	SYSCTL_CLOCK_MUX = 11,				/*!< Input mux clock */
	SYSCTL_CLOCK_SWM,					/*!< Switch matrix clock */
	SYSCTL_CLOCK_IOCON,					/*!< IOCON clock */
	SYSCTL_CLOCK_GPIO0,					/*!< GPIO0 clock */
	SYSCTL_CLOCK_GPIO1,					/*!< GPIO1 clock */
	SYSCTL_CLOCK_GPIO2,					/*!< GPIO2 clock */
	SYSCTL_CLOCK_PININT = 18,			/*!< PININT clock */
	SYSCTL_CLOCK_GINT,					/*!< grouped pin interrupt block clock */
	SYSCTL_CLOCK_DMA,					/*!< DMA clock */
	SYSCTL_CLOCK_CRC,					/*!< CRC clock */
	SYSCTL_CLOCK_WDT,					/*!< WDT clock */
	SYSCTL_CLOCK_RTC,					/*!< RTC clock */
	SYSCTL_CLOCK_ADC0 = 27,				/*!< ADC0 clock */
	SYSCTL_CLOCK_ADC1,					/*!< ADC1 clock */
	SYSCTL_CLOCK_DAC,					/*!< DAC clock */
	SYSCTL_CLOCK_ACMP,					/*!< ACMP clock */
	/* Peripheral clock enables for SYSAHBCLKCTRL1 */
	SYSCTL_CLOCK_MRT = 32,				/*!< multi-rate timer clock */
	SYSCTL_CLOCK_RIT,					/*!< repetitive interrupt timer clock */
	SYSCTL_CLOCK_SCT0,					/*!< SCT0 clock */
	SYSCTL_CLOCK_SCT1,					/*!< SCT1 clock */
	SYSCTL_CLOCK_SCT2,					/*!< SCT2 clock */
	SYSCTL_CLOCK_SCT3,					/*!< SCT3 clock */
	SYSCTL_CLOCK_SCTIPU,				/*!< SCTIPU clock */
	SYSCTL_CLOCK_CAN,					/*!< CAN clock */
	SYSCTL_CLOCK_SPI0 = 32 + 9,			/*!< SPI0 clock */
	SYSCTL_CLOCK_SPI1,					/*!< SPI1 clock */
	SYSCTL_CLOCK_I2C0 = 32 + 13,		/*!< I2C0 clock */
	SYSCTL_CLOCK_UART0 = 32 + 17,		/*!< UART0 clock */
	SYSCTL_CLOCK_UART1,					/*!< UART1 clock */
	SYSCTL_CLOCK_UART2,					/*!< UART2 clock */
	SYSCTL_CLOCK_QEI = 32 + 21,			/*!< QEI clock */
	SYSCTL_CLOCK_USB = 32 + 23,			/*!< USB clock */
} CHIP_SYSCTL_CLOCK_T;


/* De-assert reset for a peripheral */
void Chip_SYSCTL_AssertPeriphReset(CHIP_SYSCTL_PERIPH_RESET_T periph)
{
	if (periph >= 32) {
		LPC_SYSCON->PRESETCTRL1 |= (1 << ((uint32_t) periph - 32));
	}
	else {
		LPC_SYSCON->PRESETCTRL0 |= (1 << (uint32_t) periph);
	}
}

/* Assert reset for a peripheral */
void Chip_SYSCTL_DeassertPeriphReset(CHIP_SYSCTL_PERIPH_RESET_T periph)
{
	if (periph >= 32) {
		LPC_SYSCON->PRESETCTRL1 &= ~(1 << ((uint32_t) periph - 32));
	}
	else {
		LPC_SYSCON->PRESETCTRL0 &= ~(1 << (uint32_t) periph);
	}
}

inline void Chip_SYSCTL_PeriphReset(CHIP_SYSCTL_PERIPH_RESET_T periph)
{
	Chip_SYSCTL_AssertPeriphReset(periph);
	Chip_SYSCTL_DeassertPeriphReset(periph);
}

/* Enable a system or peripheral clock */
void Chip_Clock_EnablePeriphClock(CHIP_SYSCTL_CLOCK_T clk)
{
	uint32_t clkEnab = (uint32_t) clk;

	if (clkEnab >= 32) {
		LPC_SYSCON->SYSAHBCLKCTRL1 |= (1 << (clkEnab - 32));
	}
	else {
		LPC_SYSCON->SYSAHBCLKCTRL0 |= (1 << clkEnab);
	}
}

void Chip_EEPROM_init(void)
{
	Chip_Clock_EnablePeriphClock(SYSCTL_CLOCK_EEPROM);
	Chip_SYSCTL_PeriphReset(RESET_EEPROM);
}

こちらのコードをeepromのライブラリに追加するか、または別途定義すればOKです。

動作テストに成功したときのサンプルコードです。

#include "mbed.h"
#include "eeprom.h"

int main()
{
	char wbuf[256],rbuf[256];
	for(int i=0; i<256; i++) rbuf[i] = 0;

	__disable_irq();

	Chip_EEPROM_init();
	write_eeprom(wbuf, (char*)0, 256);
	read_eeprom((char*)0, rbuf, 256);

	__enable_irq();

これを実行すると無事rbufにwbufの内容がコピーされました。Chip_EEPROM_initの初期化なしだとrbufの中身は0のままです。

LPC1549でタッチパネルを読み取るときの問題と解決法

これは抵抗膜式タッチパネルをmbedのADCポートで読む時に起きる特有の問題です。しかもスイッチマトリクスではないCPU向けのmbedライブラリではおそらく問題になりません。

動作検証に使用したライブラリはこちら。

https://developer.mbed.org/users/king33jp/code/SX032QVGA008/

これをこのまま動かすとLPC1549ではアナログの値が全然取得できません。しかし自分でポートを固定定義してAD値を取得するとちゃんと取れます。なぜでしょうか?

問題の箇所はここです。

int SX032QVGA008::readTouch(PinName p, PinName m, PinName a, PinName i)
{
    DigitalOut _p(p);
    _p = 1;
    DigitalOut _m(m);
    _m = 0;
    AnalogIn   _a(a);
    AnalogIn   _i(i); // this pin has to be high Z (DigitalIn may also work)
    wait_us(10);
    return _a.read_u16();
}

SX032QVGA008::TOUCH SX032QVGA008::getTouch(point& p)
{
    int y2 = readTouch(_xp,_xm,_yp,_ym);
    int x2 = readTouch(_yp,_ym,_xp,_xm);
    int y1 = readTouch(_xp,_xm,_yp,_ym);
    int x1 = readTouch(_yp,_ym,_xp,_xm);

実はこの問題の原因はスイッチマトリクスでADCを定義した後にmbedライブラリではスイッチマトリクスの設定がクリアされないことにあります。mbedのAnalogInのソースを見ると初期定義は下記リンク内のanalogin_initが実体なのですが、使用後の開放処理がどこにも見当たらないので一度ADポートとして定義されると開放されないような雰囲気です。

https://github.com/mbedmicro/mbed/blob/master/libraries/mbed/targets/hal/TARGET_NXP/TARGET_LPC15XX/analogin_api.c

LPC_SWM->PINENABLE0 &= ~(1UL << obj->adc);

ここでスイッチマトリクスにADCの機能定義しているのですが、これの開放が見当たりませんでした。LPC15XXでのADC割り付けはリセットするまでピン設定はこのままということのようです。

だから実は最初だけ正しく値が取得できるのですが、ポート設定を入れ替えていく度にAD入力に切り替わっていき、最後には使用している全部のポートがAD入力になってどのピンからも抵抗膜に電圧をかけられない状態になっていたということのようです。

改良のためにはmbedライブラリを直接改造してもいいのですが、ライブラリがバージョンアップしたあとのことを考えるとライブラリ自体に変更はあまり加えたくありません。となるとタッチパネルのライブラリから手動で毎回開放するように改良したいところです。

ということで途中経過はバッサリ省いてうまく行ったコードを貼り付けておきます。

#include "mbed.h"
#include "pinmap.h"
#include "SX032QVGA008.h"

#ifdef LPC15XX_H
static const PinMap PinMap_ADC[] = {
    {P0_8 , ADC0_0, 0},
    {P0_7 , ADC0_1, 0},
    {P0_6 , ADC0_2, 0},
    {P0_5 , ADC0_3, 0},
    {P0_4 , ADC0_4, 0},
    {P0_3 , ADC0_5, 0},
    {P0_2 , ADC0_6, 0},
    {P0_1 , ADC0_7, 0},
    {P1_0 , ADC0_8, 0},
    {P0_31, ADC0_9, 0},
    {P0_0 , ADC0_10,0},
    {P0_30, ADC0_11,0},
    {P1_1 , ADC1_0, 0},
    {P0_9 , ADC1_1, 0},
    {P0_10, ADC1_2, 0},
    {P0_11, ADC1_3, 0},
    {P1_2 , ADC1_4, 0},
    {P1_3 , ADC1_5, 0},
    {P0_13, ADC1_6, 0},
    {P0_14, ADC1_7, 0},
    {P0_15, ADC1_8, 0},
    {P0_16, ADC1_9, 0},
    {P1_4 , ADC1_10,0},
    {P1_5 , ADC1_11,0},
};
#endif

SX032QVGA008::SX032QVGA008(PinName xp, PinName xm, PinName yp, PinName ym)
:AnalogIn(NC)
{
	// touch screen pins
    _xp = xp;
    _yp = yp;
    _xm = xm;
    _ym = ym;
    // default touch calibration
    // orientation     //      0      1      2      3
    x_off = 108000;  //  17252  16605 108755 108000
    y_off =  22000;  //  22330 105819  97167  22000
    pp_tx =   -291;  //    378    289   -390   -291
    pp_ty =    356;  //    261   -355   -239    356
}

void SX032QVGA008::analog_disable(analogin_t *obj, PinName pin)
{
#ifdef LPC15XX_H
	obj->adc = (ADCName)pinmap_peripheral(pin, PinMap_ADC);
	MBED_ASSERT(obj->adc != (ADCName)NC);
	// pin disable
	LPC_SWM->PINENABLE0 |= 1UL << obj->adc;
#endif
}

int SX032QVGA008::readTouch(PinName p, PinName m, PinName a, PinName i)
{
	int read_data;

    DigitalOut _p(p);
    _p = 1;
    DigitalOut _m(m);
    _m = 0;
    analogin_init(&_adc, a);
    DigitalIn   _i(i); // this pin has to be high Z (DigitalIn may also work)

    wait_us(10);
    read_data = analogin_read_u16(&_adc);
#ifdef LPC15XX_H
    analog_disable(&_adc, a);
#endif
    return read_data;
}

analogin_api.c内部でしか定義されていないピン情報はどうしても参照できないので仕方なく再定義していますが、AnalogInのクラス内にある内部データを格納する構造体は継承でアクセスしています。この構造体をprotected宣言にして最後のアクセス手段を残してくれたのはさすがmbedライブラリ、分かっているじゃないかと感じました。

補足ですが、このソースだけじゃなくてヘッダファイルのクラス定義にもclass SX032QVGA008: protected AnalogInのように追加するのを忘れないようにしてください。

Subscribe
Notify of
guest

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください

1 Comment
Inline Feedbacks
View all comments
trackback
3 years ago

[…] LPCマイコンとmbed関係の個人的メモ […]