不確定特異点

広く深く、ところどころ超深く

STM32F7 Discovery の開発環境 (2)

LED チカチカプログラム

まずは、お決まりの LED を点滅させるプログラムを作ります。回路図を見ると、STM32F456 の PI1 端子は LD1 という緑色の LED に接続されてます。PI1 は GPIOI で制御可能なので、GPIOI を 0/1 制御することで LED を点滅させてみます。

#include "stm32f7xx.h"

#define BIT(x, n) ((x) & (1 << (n)))

#define COUNTER_BIT 17

void Reset_Handler()
{
    int i = 0;

    SystemInit();
    
    /* Enable clock for each peripherals */
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOIEN;
    
    /* PI1 pin is assigned for GPIO output */
    GPIOI->MODER = 0x00000004;
    
    while (1) {
        if (i++ == (1<<COUNTER_BIT) - 1)
            i = 0;
        GPIOI->ODR = BIT(i,COUNTER_BIT-1) >> (COUNTER_BIT-2);
    }
}

リセットハンドラに実行後のミニマムの設定は、

  • RCC の AHB1ENR レジスタにより GPIOI に対してクロックを供給する
  • GPIOI の MODER レジスタにより出力設定にする

の 2 点です。あとは、カウンタ(i)の上位ビットを GPIOI の ODR レジスタから出力させているだけです。カウンタを 0 クリアするタイミングは、点滅が目視できる値に適当に調整します。

ROM のプログラム方法

Flash が 0x08000000 番地にマッピングされているため、この番地を ROM 領域として使うようにリンカスクリプトを設定して ELF ファイルを作成します。ちなみに Flash は 0x00200000 番地からの ITCM 経由と 0x08000000 番地からの AXIM 経由の 2 通りのバスによるマッピングがなされており、リンカスクリプトで ROM の開始アドレスを設定することにより、どちらのバスを使ってアクセスするかを選択できます。両方試してみましたが、ITCM の方が高速でした。命令フェッチにバスを占有できるからでしょうか。(謎な ART とかいうのもあったけど)

参考

image.elf という ELF ファイルを OpenOCD で転送するには次のように実行します。このとき、ROM を書き込むのはあくまで 0x08000000 番地を指定する、ということのようです。

$ arm-linux-gnueabi-objcopy -O binary image.elf image
$ openocd -s /usr/local/share/openocd/scripts/ -f board/stm32f7discovery.cfg -c "program image verify reset exit 0x08000000"
Open On-Chip Debugger 0.10.0-dev-00026-g97a8b08-dirty (2015-10-23-18:37)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
adapter speed: 2000 kHz
adapter_nsrst_delay: 100
srst_only separate srst_nogate srst_open_drain connect_deassert_srst
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
Info : clock speed 1800 kHz
Info : STLINK v2 JTAG v24 API v2 SWIM v11 VID 0x0483 PID 0x374B
Info : using stlink api v2
Info : Target voltage: 3.211002
Info : stm32f7x.cpu: hardware has 8 breakpoints, 4 watchpoints
target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x0800025c msp: 0x20020000
** Programming Started **
auto erase enabled
Info : device id = 0x10016449
Info : flash size = 1024kbytes
wrote 32768 bytes from file image in 1.673457s (19.122 KiB/s)
** Programming Finished **
** Verify Started **
verified 740 bytes in 0.224078s (3.225 KiB/s)
** Verified OK **
** Resetting Target **
shutdown command invoked

LED がチカチカしました。

PLL を使って 216 MHz までクロックアップ

正直、STM32CubeF7 のドライバを使った方が手早くて確実なのですが、スタートアップに必要な処理を理解するために自力で設定してみます。STM32F4 Discovery の開発環境 (3) で実装した EnablePLL() のコードを応用して、STM32F7 対応させてみます。変更点はそれほど多くはなく、

  • PLL の逓倍設定
    • HSE=16MHz ⇒ 168MHz 設定から HSE=25MHz ⇒ 216MHz 設定に変更
  • オーバードライブモードへの移行を追加
    • STM32F4 Discovery の時は電源系は Voltage Scaling = 1 のみしか設定しなかった。
  • Flash レイテンシの設定を変更
    • 5WS ⇒ 7WS へ変更

を追加変更しました。(※以下のプログラムでは端折ってますが、レジスタ設定はレジスタへの Write が反映されていることを確認するため、Read チェックを入れた方が確実かもしれません。)

例によって、EnablePLL() により所望の周波数設定になっていることを確認するため、タイマ割り込みを使って GPIOI 端子をトグルさせて周期を測定してみます。

#include "stm32f7xx.h"

#define PLL_M 25
#define PLL_N 432
#define PLL_P 2
#define PLL_Q 9

void EnablePLL()
{
    RCC->CR |= RCC_CR_HSEON;
    while (!(RCC->CR & RCC_CR_HSERDY))
        continue;
    
    /* Power supply setup */
    RCC->APB1ENR |= RCC_APB1ENR_PWREN;
    PWR->CR1 |= PWR_CR1_VOS;
    
    /* Enable Over-drive mode */
    PWR->CR1 |= PWR_CR1_ODEN;
    while (!(PWR->CSR1 & PWR_CSR1_ODRDY))
        continue;

    /* Switch to Over-drive mode */
    PWR->CR1 |= PWR_CR1_ODSWEN;
    while (!(PWR->CSR1 & PWR_CSR1_ODSWRDY))
        continue;

    /*
     * PLL settings
     * AHB  = SYSCLK / 1 = 216MHz
     * APB1 = SYSCLK / 4 = 54MHz
     * APB2 = SYSCLK / 2 = 108MHz
     */
    RCC->CFGR |= RCC_CFGR_HPRE_DIV1  |
                 RCC_CFGR_PPRE2_DIV2 |
                 RCC_CFGR_PPRE1_DIV4;
    
    /* PLLCLK = HSI(16 MHz) / M * N / P */
    RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) |
                   RCC_PLLCFGR_PLLSRC_HSE | (PLL_Q << 24);

    /* PLL ON */
    RCC->CR |= RCC_CR_PLLON;

    /* Wait until PLL is locked up */
    while (!(RCC->CR & RCC_CR_PLLRDY))
        continue;
    
    /* Increase flash latency */
    FLASH->ACR = FLASH_ACR_LATENCY_7WS;

    RCC->CFGR &= ~RCC_CFGR_SW;
    RCC->CFGR |= RCC_CFGR_SW_PLL;
}

void Reset_Handler()
{
    SystemInit();
    
    /* Enable clock for each peripherals */
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOIEN;
    RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
    
    /* PI1 pin is assigned for GPIO output */
    GPIOI->MODER = 0x00000004;
    
    EnablePLL();
    
    /* Timer interrupting at regular interval */
    TIM2->PSC  = 0;            /* Timer clock  108 MHz / PSC = 104 KHz */
    TIM2->ARR  = 54*2*1000-1;  /* Auto reload: 108 KHz / ARR = 1 Hz */
    TIM2->DIER = TIM_DIER_UIE; /* Enable update interrupt */
    TIM2->EGR  = TIM_EGR_UG;   /* Initialize counter */
    TIM2->SR   = 0;            /* Clear all interrupts */
    TIM2->CR1 |= TIM_CR1_CEN;  /* Enable counter */

    NVIC_SetPriority(TIM2_IRQn, 2);
    NVIC_EnableIRQ(TIM2_IRQn);

    while (1)
        continue;
}

void TIM2_IRQHandler()
{
    if (TIM2->SR & TIM_SR_UIF) {
        TIM2->SR = ~TIM_SR_UIF;   /* Clear update interrupt */
        GPIOI->ODR ^= 0x00000002; /* Toggle GPIO output */
    }
}

108 MHz(SYSCLK/2) をタイマのオートリロード機能で分周して 1 KHz まで落とし、その 1 KHz 周期でかかる割り込み処理の中で GPIOI をトグルさせるので、PI1 端子上に 500 Hz が観測されるはずです。というわけで、GPIOI の出力をオシロで測定してみます。

f:id:tanakahx:20151026003750j:plainf:id:tanakahx:20151026011858p:plain

500 Hz ピッタリです。