EFM32 Giant Gecko ARM Cortex-M3入門

作成者:Scott Schmit、最終更新日:2014年9月18日

image

目的

このページは、Silicon LabsのEFM32デバイスファミリの一部であるGiant Geckoマイクロコントローラの初心者向け入門ガイドです。EFM32マイクロコントローラファミリは、最も低消費電力の32bitマイクロコントローラの1つです。Giant Geckoデバイスファミリは、ARM Cortex-M3コアを実装し、最大1024KBのプログラム(フラッシュ)メモリ、最大128KBのRAM、最大48MHzのCPU速度で利用可能です。このページは、IAR Embedded Workbenchを使用して一からGiant Geckoプロジェクトを始めるためのガイドで、このマイクロコントローラで使用可能な様々なペリフェラルをレジスタレベルで理解するためのサンプルコードを提供します。また、emlib関数を使用したサンプルコードも提供します。開発プラットフォームは、EFM32GG-STK3700を使用しました。

クイックリンク

購入リンク
参考資料
ダウンロード
  • Simplicity Studio - すべてのEFM32デバイスに関する豊富なドキュメントとemlibライブラリを使用したサンプルコードを提供します。また、エネルギー解析も実行します。Simplicity Studioの最新バージョンにはコード開発環境が含まれています。
  • IAR Embedded Workbench - ファームウェア開発環境(CDもキットに付属)。期間限定ライセンス、またはコード限定ライセンスが無償で利用できます。32KBのコード限定バージョンは、このページのサンプルに使用できます。
  • Keil uVision5 - ファームウェア開発環境。このソフトウェアにも同様の無償ライセンスがあります。

EFM32用テンプレートIARプロジェクト開始手順

Silicon Labsは、サンプルコードとドキュメントを探し出すためにSimplicity Studioを使用し、ゼロからIARプロジェクトを始める代わりに、既存の IARプロジェクトをそのまま使用することを推奨しています。というのも、これらのマイクロコントローラ用に書かれたアプリノートとサンプルコードはすべて、IARプロジェクトでは一般的でない階層を使用しているからです。サンプルはまた、インクルードファイルのパスを設定し、デバッグリンクを設定します。その後、CMSISとem_libライブラリを使用して、main.cファイルから関数を呼び出し、アプリケーションを開発することができます。

必ずしも推奨されるわけではありませんが、以下の手順で空白のIARプロジェクトを開始することができます。この手順は、ここにある例と類似しています。ただし、私はSimplicity Studioが使用するデフォルトの場所ではなく、独自のプロジェクトディレクトリにプロジェクトをセットアップしました。

EFM32用テンプレートIARプロジェクト開始手順
この手順では、空白のIARプロジェクトを起動し、必要なプロジェクト設定を行います。しかし、ここではテンプレートプロジェクトをセットアップするためにこの手順を使用します。一旦テンプレートが作成されれば、新しい EFM32 Giant Geckoプロジェクトを開始したいときはいつでも、単純にコピー&ペーストしてプロジェクト名を変更することができます。emlibライブラリを使用したくない場合でも、プロジェクトを適切にセットアップするために、これらの手順をすべて実行する必要があります。

  1. IARを開き、Project → Create New Projectを選択します。
    image

  2. 「Create New Project」ダイアログボックスで、「ARM」がツールチェーンとして選択されている ことを確認します。「Empty Project」を選択し、「OK」をクリックします。
    image

  3. 「Save As」ダイアログが表示されるので、プロジェクトの保存場所を選択してください。

Silicon Labsは、Simplicity Studioをインストールしたときに作成されるアプリケーションノートディレクトリに各プロジェクトを配置することを推奨しています。しかし、ここではすべてのEFM32プロジェクトのためのユニークな場所を作成するつもりです。

個人的に使用するため、デスクトップの「Energy Micro」フォルダの中に「My_EFM32_Projects」フォルダを作成しました。ここに新しいEFM32プロジェクトをすべて保存しています。このデモではGiant Geckoマイクロコントローラを使うので、「My_EFM32_Projects」フォルダの中に「Template_EFM32GG」というフォルダを作りました。好きな名前をつけても構いません。しかし、私はマイクロコントローラタイプによってプロジェクトを整理しやすくしたいと考えています。プロジェクトフォルダの名前にそのマイコンを含めると、そのプロジェクトにどのマイコンを使ったかを簡単に識別することができます。また、必要であれば、個々のマイコン用に別のフォルダを作ることもできますが、私はできればフォルダの数を減らしたいのです。現在、私のプロジェクトディレクトリは以下のような階層になっています。

→ 「Energy Micro」 → 「My_EFM32_Projects」 → 「Template_EFM32GG」 です。

プロジェクトディレクトリを設定したら、「iar」フォルダを作成し、プロジェクトファイルを 「Template_EFM32MGG.ewp」として保存します。
image

  1. プロジェクトが作成されたので、プロジェクトにグループを追加することができます。ワークスペースブラウザでプロジェクトを右クリックし、「Add Group」を選択します。
    image

  2. 最初のグループを「source」と名付けます。これはすべてのEFM32プロジェクトの標準です。
    image

  3. これで「main.c」ファイルを作成できます。File → New → Fileに移動します。
    image

  4. 無題のファイルが開きます。File → Save Asを選択します。
    image

  5. プロジェクトディレクトリ(プロジェクトファイルを保存した「iar」フォルダの1つ上の階層)に移動し、ファイルを「main.c」として保存します。私の場合、プロジェクトディレクトリは次のとおりです。 → 「Energy Micro」→ 「My_EFM32_Projects」 → 「Template_EFM32GG」
    image

  6. 「main.c」ファイルができたので、実際にワークスペースに追加します。作成した「source」グループを右クリックし、Add → Add Filesを選択します。
    image

  7. 先ほど作成した「main.c」ファイルを選択します。
    image

  8. ワークスペースにさらに2つのグループを追加し、「CMSIS」と「emlib」と名付けます。これは EFM32プロジェクトの標準です。

  9. 「CMSIS」グループを右クリックし、Add → Add Fileを選択します。
    image

  10. Simplicity Studioがインストールされると、CMSIS、emlib、デバイスファイルだけでなく、すべてのドキュメントがC:\Users<ユーザー名>\AppData\Roaming\energymicroに保存されます。この「energymicro」フォルダにおいて、Device → EnergyMicro → EFM32GG → Sourceへと進みます。(もし「Giant Gecko」を使用していない場合には適宜、EFM32xxフォルダに進んで下さい。) 「system_efm32gg.c」ファイルを選択し、「Open」をクリックしてファイルをワークスペースに追加します。このファイルはすべての標準EFM32プロジェクトに含まれています。
    image

  11. 「CMSIS」グループを右クリックし、再度Add → Add Fileを選択します。C:\Users<ユーザー名>\App Data\Roaming\energymicro\Device\EnergyMicro\EFM32GG\Source\IAR に移動し、「startup_efm32gg.s」ファイルを開いてワークスペースに追加します。このファイルは、デバイスを正しくプログラムするために必要です。
    image

  12. これで、以前に作成した「emlib」グループにソースファイルを追加することができます。必要なものだけを追加することもできますし、すべてのファイルを追加することもできます。
    C:\Users<ユーザー名>\AppData\Roaming\energymicroフォルダに移動します。
    「emlib」フォルダの中にある「src」フォルダを開き、周辺機器のemlibライブラリを追加してください。利用可能な関数を簡単にブラウズできるように、ワークスペースにすべてのライブラリを追加することをお勧めします。そうすることで、Windowsエクスプローラを使ってそれぞれのファイルに移動しなくても、どのファイルでも見ることができます。「Ctrl」+「マウスの左ボタン」で個々のファイルを選択し、「Open」をクリックしてワークスペースに追加します。
    注:emlibライブラリを使用しない場合でも、emlibグループに「em_system.c」ファイルを追加して、「main.c」ファイルの先頭で Chip_Init() 関数を呼び出せるようにしてください。Chip_Init() は、デバイスの既知のエラッタに対応し、既知のソフトウェア修正を適用します。
    image

  13. これらのemlibファイルにある関数を使用するには、「main.c」ファイルにそれぞれのヘッダファイルの#includeを追加する必要があります。例えば、「main.c」ファイルにあらかじめ書き込まれているADC関数を使用する場合、次の例のように「em_adc.h」ヘッダファイルをインクルードする必要があります。「em_system.h」 と「em_chip.h」はすべての EFM32プロジェクトに追加する必要があります。また、「main.c」ファイルに空白の main() 関数を追加することもできます。
    image

  14. それでは、プロジェクトのオプションを設定しましょう。ワークスペースエクスプローラの 「Template_EFM32GG.ewp」を右クリックし、「Options」を選択します。
    image

  15. 「General Options」カテゴリの「Target」タブで、「Device」ボタンが選択されていることを確認し、右側の「list」アイコンをクリックします。EnergyMicroに移動し、特定のデバイスを選択します。このデモでは、EFM32GGスターターキットに搭載されているEFM32GG990F1024を選択しました。

  16. 「C/C++ Compiler Settings」カテゴリの「Preprocessor」タブで、デバイス用のインクルードディレクトリを追加できます。emlib APIを使用する場合は、CMSIS ライブラリとemlibライブラリのインクルードファイルへのパスを追加する必要があります(推奨)。また、特定のデバイスの「インクルード」フォルダにリンクする必要があります。Energy Micro app notesにあるサンプルプロジェクトでは、プロジェクトディレクトリからの相対パスを使用してこれらのディレクトリにリンクしています。これらの場所が変更されないことが分かっていれば、ハードコードされたパスを使用することができます。個人的な使用では、(相対パスを使用するのではなく)プロジェクトオプションで直接パスをハードコードしています。Silicon Labsは移植性のために相対パスを使うことを推奨しています。完全にあなた次第ですが、ファイルがどこにあり、どのようにリンクしているかを常に意識する必要があります。

「Defined symbols」ボックスで、デバイスシンボルを定義する必要があります。私のプロジェクトでは、EFM32GG990F1024というシンボルを定義しました。以下は、デフォルトのインストールディレクトリへのパスをハードコーディングした例です。
image

  1. 「Output Converter」カテゴリで、「Generate additional output」をチェックし、出力形式として「binary」を選択します。
    image

  2. 「Linker」カテゴリで「Override default」を選択し、デバイスの .icf ファイルに移動します。このファイルは、Simplicity Studioのインストールディレクトリではなく、IARのインストールディレクトリにあるはずです。
    C:\Program Files (x86)\IAR Systems\Embedded Workbench 6.x\arm\config\linker\EnergyMicro.

  3. 「Debugger」カテゴリの「Setup」タブでは、EFM32GG Starter Kitに同梱されているJ-linkデバッガを使用するため、Driverとして「J-Link/J-Trace」を選択します。他のデバッガを使用している場合は、これらの設定が変わる可能性があります。
    image

  4. 引き続き、「Debugger」カテゴリにある「Download」タブで、「Verify download」と「Use flash loader(s)」のボックスをチェックします。
    image

  5. 「J-Link」カテゴリの「Connection」タブで、インターフェースとして「SWD」を選択します。つぎに、「OK」をクリックしてプロジェクトオプションを適用します。

  6. File → Save Allを選択します。プロジェクトディレクトリ内の「iar」フォルダにワークスペースを保存します。
    image

  7. プロジェクトを右クリックし、「Make」をクリックしてプロジェクトをビルドします。
    image

  8. これで、必要なプロジェクト設定をすべて含むテンプレートプロジェクトができました。ハードコードされたインクルードパスを使用しているため、Giant Gecko用の新しいEFM32プロジェクトを開始したいときはいつでも、好きな場所にテンプレートプロジェクトをコピー&ペーストするだけです。テンプレートのコピーを貼り付けたら、プロジェクトフォルダの名前を好きなものに変更してください。また、ワークスペースファイルの名前もプロジェクト名に一致するように変更することをお勧めします。他のすべてのファイルはファイル名に「Template」をつけたままでも、プロジェクトは正しくビルドされます。
    image
    image

もう準備は整いました!では、いくつかのコード例を見てみましょう…

コード例

以下のサンプルコードは、IAR embedded work benchを使用し、32KBサイズ制限のフリーライセンスで書かれています。これらは、EFM32 Giant Geckoマイクロコントローラの基本的な機能に慣れるためのものです。以下のプロジェクトはすべて、上記の新規プロジェクト作成手順(作成したテンプレートのコピー&ペースト)に従いました。例題は難易度の順番に紹介しており、前の例題を基にしたものもあります。もしあなたがEFM32マイクロコントローラの初心者であれば、最初から順番にサンプルを見ていくことをお勧めします。すでにEFM32マイクロコントローラの経験がある方は、お好きな例にジャンプしてください。

GPIOの例
この例では、ピンをプルアップ抵抗を有効にした入力として設定する方法と、デジタル出力をセットおよびクリアする方法を示します。また、デジタル出力の駆動強度を調整する方法を示します。EFM32GGでは、デジタル出力のドライブ強度を以下の値で設定できます。

標準 - 6mA ドライブ電流
LOWEST - 0.5mA ドライブ電流
HIGH - 20mA ドライブ電流
LOW - 2mA ドライブ電流
しかし、EFM32GGスターターキットはLED電流を最大約1mAに制限する3kΩ抵抗を使用しています。そのため、HIGHとLOWの駆動設定は効果を示しません。LOWESTドライブモードは、ドライブモード設定の直接的な結果としてLED強度の変化を目に見える形で確認することができます。したがって、以下の例ではLOWESTを使用しました。

以下の例では、LED0はデフォルトで点灯しており、駆動強度は標準の6mAです。LED1はデフォルトで消灯しています。PB0を押すとLED1が切り替わり、PB1を押すとLED0が切り替わります。LED1は0.5mAの最低駆動モード設定を使用します。LED0の駆動強度が6mAに設定されていても、電流制限抵抗によってLED0を流れる電流は約1mAに減少します。

ダイレクトレジスタアクセスを使用したGPIOの例

  • 以下のコードは、ダイレクトレジスタアクセスを使用した基本的なGPIO機能のデモンストレーショ ンです。以下のコードを動作させるには、「em_system.c」ファイルをワークスペースに追加する必要があります。
#include "efm32gg990f1024.h"
#include "em_chip.h"    // required for CHIP_Init() function
 
// LEDs on EFM32GG Starter Kit are connected to PortE, pin2 (LED0) and PortE, pin3 (LED1)
// Push-buttons are connected to PortB, pin9 (Push-button 0) and PortB, pin10 (Push-button 1)
 
enum {
  A,
  B,
  C,
  D,
  E
};
 
#define LED_PORT E
#define BUTTON_PORT B
#define LED0 2
#define LED1 3
#define PB0 9
#define PB1 10
 
int main() {
  CHIP_Init();                                         // This function addresses some chip errata and should be called at the start of every EFM32 application (need em_system.c)
   
  CMU->HFPERCLKEN0 = (1 << 13);                        // Enable GPIO clock
   
  // Configure LED0 pin as digital output (push-pull)
  // Configure LED1 pin as digital output (push-pull) with drive-strength set by DRIVEMODE
  GPIO->P[LED_PORT].MODEL = (5 << 12) | (4 << 8);        
                              
  GPIO->P[LED_PORT].CTRL = 1;                          // Set DRIVEMODE to lowest setting (0.5 mA) for all LEDs configured with alternate drive strength
  GPIO->P[BUTTON_PORT].MODEH = (2 << 4) | (2 << 8);    // Configure PB0 and PB1 as inputs
  GPIO->P[BUTTON_PORT].DOUT = (1 << PB1) | (1 << PB0); // Enable Pull-ups on PB0 and PB1
   
  while(1) {
    if(! (GPIO->P[BUTTON_PORT].DIN & (1 << PB0)) ) {   // If PB0 is pressed
      GPIO->P[LED_PORT].DOUTSET = 1 << LED1;           // Turn on LED1
    }else{                                             // If PB0 is released
      GPIO->P[LED_PORT].DOUTCLR = 1 << LED1;           // Turn off LED1
    }
    if(! (GPIO->P[BUTTON_PORT].DIN & (1 << PB1)) ) {   // If PB1 is pressed
      GPIO->P[LED_PORT].DOUTCLR = 1 << LED0;           // Turn off LED0
    }else{                                             // If PB1 is released
      GPIO->P[LED_PORT].DOUTSET = 1 << LED0;           // Turn on LED0
    }
  }
}

emlib APIを使用したGPIOの例

  • 以下のコードは、emlibライブラリを使用して同じGPIO例を実装する方法を示しています。以下のコードを動作させるには、以下のemlibソースファイルをワークスペースに追加する必要があります。
  • em_cmu.c
  • em_gpio.c
  • em_system.c
#include "em_device.h"
#include "em_cmu.h"
#include "em_gpio.h"
#include "em_system.h"
#include "em_chip.h"    // required for CHIP_Init() function
 
#define LED_PORT gpioPortE
#define BUTTON_PORT gpioPortB
#define LED0 2
#define LED1 3
#define PB0 9
#define PB1 10
 
int main() {
  CHIP_Init();                                               // This function addresses some chip errata and should be called at the start of every EFM32 application (need em_system.c)
   
  CMU_ClockEnable(cmuClock_GPIO, true);                      // Enable GPIO peripheral clock
   
  GPIO_PinModeSet(LED_PORT, LED0, gpioModePushPull, 0);      // Configure LED0 pin as digital output (push-pull)
  GPIO_PinModeSet(LED_PORT, LED1, gpioModePushPullDrive, 1); // Configure LED1 pin as digital output (push-pull) with drive-strength to lowest setting
  GPIO_DriveModeSet(LED_PORT, gpioDriveModeLowest);          // Set DRIVEMODE to lowest setting (0.5 mA) for all LEDs configured with alternate drive strength
  GPIO_PinModeSet(BUTTON_PORT, PB0, gpioModeInputPull, 1);   // Configure PB0 as input with pull-up enabled
  GPIO_PinModeSet(BUTTON_PORT, PB1, gpioModeInputPull, 1);   // Configure PB1 as input with pull-up enabled
   
  while(1) {
    if(! (GPIO_PinInGet(BUTTON_PORT, PB0)) ) {               // If PB0 is pressed
      GPIO_PinOutSet(LED_PORT, LED1);                        // Turn on LED1
    }else{
      GPIO_PinOutClear(LED_PORT, LED1);                      // Turn off LED1
    }
    if(! (GPIO_PinInGet(BUTTON_PORT, PB1)) ) {               // If PB1 is pressed
      GPIO_PinOutClear(LED_PORT, LED0);                      // Turn off LED0
    }else{
      GPIO_PinOutSet(LED_PORT, LED0);                        // Turn on LED0
    }
  }
}

タイマー割り込み
以下のコードでは、割り込み付きタイマーを使ってLEDを点滅させています。デフォルトのクロック源は内蔵の高速RC発振器です。RC発振器を1MHzに設定し、タイマーを使って1msごとに割り込みをかけ、500ms後にLED0をトグルさせます。

  • ダイレクトレジスタアクセスを使用したタイマー割り込み
    次のコードは、ダイレクトレジスタアクセスを使って基本的なタイマーを実装する方法を示しています。以下のコードを実行するには、ワークスペースに「em_system.c」ファイルを追加する必要があります。
#include "efm32gg990f1024.h"
#include "em_chip.h" // required for CHIP_Init() function
 
#define LED_PORT 4 // gpioPortE
#define LED_PIN1 2
#define LED_PIN2 3
 
uint16_t ms_counter = 0;
 
void TIMER0_IRQHandler(void) {
  TIMER0->IFC = 1;                              // Clear overflow flag
  ms_counter++;                                 // Increment counter
}
 
int main() {
  CHIP_Init();                                  // This function addresses some chip errata and should be called at the start of every EFM32 application (need em_system.c)
   
  CMU->HFRCOCTRL = 0x8;                         // Set High Freq. RC Osc. to 1 MHz
  CMU->HFPERCLKEN0 = (1 << 13) | (1 << 5);      // Enable GPIO and Timer0 peripheral clocks
   
  GPIO->P[LED_PORT].MODEL = 4 << 8;             // Configure LED pin as digital output (push-pull)
   
  TIMER0->TOP = 1000;                           // Set TOP value for Timer0
  TIMER0->IEN = 1;                              // Enable Timer0 overflow interrupt
   
  NVIC_EnableIRQ(TIMER0_IRQn);                  // Enable TIMER0 interrupt vector in NVIC
   
  TIMER0->CMD = 0x1;                            // Start timer0
   
  while(1) {
    if(ms_counter == 500) {
      GPIO->P[LED_PORT].DOUTTGL = 1 << LED_PIN1;// Toggle LED
      ms_counter = 0;                           // Reset counter
    }
  }
}
  • emlib APIを使用したタイマー割り込み
    次のコードは、emlibライブラリを使用して、同じインタラプト付きタイマーの例を実装する方法を示しています。以下のコードを実行するには、以下のemlibソースファイルをワークスペースに追加する必要があります。
  • em_cmu.c
  • em_gpio.c
  • em_timer.c
  • em_system.c
#include "em_device.h"
#include "em_cmu.h"
#include "em_gpio.h"
#include "em_system.h"
#include "em_timer.h"
#include "em_chip.h"
  
#define LED_PORT gpioPortE
#define LED_PIN  2
  
uint16_t ms_counter = 0;
  
void TIMER0_IRQHandler(void)
{
  TIMER_IntClear(TIMER0, TIMER_IF_OF);      // Clear overflow flag
  ms_counter++;                             // Increment counter
}
  
int main() {
  
 CHIP_Init();                               // This function addresses some chip errata and should be called at the start of every EFM32 application (need em_system.c)
  
  CMU_HFRCOBandSet(cmuHFRCOBand_1MHz);      // Set High Freq. RC Osc. to 1 MHz
  CMU_ClockEnable(cmuClock_GPIO, true);     // Enable GPIO peripheral clock
  CMU_ClockEnable(cmuClock_TIMER0, true);   // Enable TIMER0 peripheral clock
  
  GPIO_PinModeSet(LED_PORT, LED_PIN, gpioModePushPullDrive, 0); // Configure LED0 pin as digital output (push-pull)
    
  TIMER_TopSet(TIMER0, 1000);               // Set timer TOP value
  TIMER_Init_TypeDef timerInit =            // Setup Timer initialization
  {
    .enable     = true,                     // Start timer upon configuration
    .debugRun   = true,                     // Keep timer running even on debug halt
    .prescale   = timerPrescale1,           // Use /1 prescaler...timer clock = HF clock = 1 MHz
    .clkSel     = timerClkSelHFPerClk,      // Set HF peripheral clock as clock source
    .fallAction = timerInputActionNone,     // No action on falling edge
    .riseAction = timerInputActionNone,     // No action on rising edge
    .mode       = timerModeUp,              // Use up-count mode
    .dmaClrAct  = false,                    // Not using DMA
    .quadModeX4 = false,                    // Not using quad decoder
    .oneShot    = false,                    // Using continuous, not one-shot
    .sync       = false,                    // Not synchronizing timer operation off of other timers
  };
  TIMER_IntEnable(TIMER0, TIMER_IF_OF);     // Enable Timer0 overflow interrupt
  NVIC_EnableIRQ(TIMER0_IRQn);              // Enable TIMER0 interrupt vector in NVIC
  TIMER_Init(TIMER0, &timerInit);           // Configure and start Timer0
  
  while(1)
  {
    if(ms_counter == 500) {
      GPIO_PinOutToggle(LED_PORT, LED_PIN); // Toggle LED
      ms_counter = 0;                       // Reset counter
    }
  }
}

HF XTALオシレータの使用
次の例は上に示した「タイマー割り込み」の例と似ています。しかし、この例では高周波クロック源としてオンボード48MHzクリスタルを使用します。コードは高周波クロック分周器を使用して高周波クロックレートを24MHzに設定します。高周波ペリフェラルクロックと高周波コアクロックのプリスケーラは1/1のままであるため、この例ではタイマー、GPIOペリフェラル、コアクロックはすべて24MHzで動作します。オーバーフロー割り込みが1msごとに発生するように、カウンタのTOP値を24000に設定しました。LEDは500msごとに点滅します。

ダイレクトレジスタアクセスを使用したHF XTALの例

  • 以下のコードブロックは、HF XTAL発振器を適切に設定し使用するために、どのレジスタに書き 込むかを示しています。以下のコードを動作させるには、「em_system.c」ファイルをワークスペースに追加する必要があります。
#include "efm32gg990f1024.h"
#include "em_chip.h"    // required for CHIP_Init() function
 
#define LED_PORT 4 // gpioPortE
#define LED_PIN1 2
#define LED_PIN2 3
 
uint16_t ms_counter = 0;
 
void TIMER0_IRQHandler(void) {
  TIMER0->IFC = 1;                              // Clear overflow flag
  ms_counter++;                                 // Increment counter
}
 
int main() {
  CHIP_Init();                                  // This function addresses some chip errata and should be called at the start of every EFM32 application (need em_system.c)
   
  CMU->CTRL |= (1 << 14);                       // Set HF clock divider to /2 to keep core frequency <32MHz
  CMU->OSCENCMD |= 0x4;                         // Enable XTAL Oscillator
  while(! (CMU->STATUS & 0x8) );                // Wait for XTAL osc to stabilize
  CMU->CMD = 0x2;                               // Select HF XTAL osc as system clock source. 48MHz XTAL, but we divided the system clock by 2, therefore our HF clock should be 24MHz
   
  CMU->HFPERCLKEN0 = (1 << 13) | (1 << 5);      // Enable GPIO and Timer0 peripheral clocks
   
  GPIO->P[LED_PORT].MODEL = 4 << 8;             // Configure LED pin as digital output (push-pull)
   
  TIMER0->TOP = 24000;                          // Set TOP value for Timer0
  TIMER0->IEN = 1;                              // Enable Timer0 overflow interrupt
   
  NVIC_EnableIRQ(TIMER0_IRQn);                  // Enable TIMER0 interrupt vector in NVIC
   
  TIMER0->CMD = 0x1;                            // Start timer0
   
  while(1) {
    if(ms_counter == 500) {
      GPIO->P[LED_PORT].DOUTTGL = 1 << LED_PIN1;// Toggle LED
      ms_counter = 0;                           // Reset counter
    }
  }
}

emlib APIを使用したHF XTALの例

次のコードブロックは、emlib 関数を使用して、上記の例とまったく同じ機能を実現します。このコードを動作させるには、以下のファイルをワークスペースに追加する必要があります。

  • em_cmu.c
  • em_emu.c
  • em_gpio.c
  • em_timer.c
  • em_system.c
#include "em_device.h"
#include "em_cmu.h"
#include "em_gpio.h"
#include "em_timer.h"
#include "em_system.h"
#include "em_chip.h"  // required for CHIP_Init() function
 
#define LED_PORT gpioPortE
#define LED_PIN 2
 
uint16_t ms_counter = 0;
 
void TIMER0_IRQHandler(void)
{
  TIMER_IntClear(TIMER0, TIMER_IF_OF);  // Clear overflow flag
  ms_counter++;                         // Increment counter
}
 
int main() {
  CHIP_Init(); // This function addresses some chip errata and should be called at the start of every EFM32 application (need em_system.c)
   
  CMU_ClockDivSet(cmuClock_HF, cmuClkDiv_2);               // Set HF clock divider to /2 to keep core frequency <32MHz
  CMU_OscillatorEnable(cmuOsc_HFXO, true, true);           // Enable XTAL Osc and wait to stabilize
  CMU_ClockSelectSet(cmuClock_HF, cmuSelect_HFXO);         // Select HF XTAL osc as system clock source. 48MHz XTAL, but we divided the system clock by 2, therefore our HF clock will be 24MHz
   
  CMU_ClockEnable(cmuClock_GPIO, true);                    // Enable GPIO peripheral clock
  CMU_ClockEnable(cmuClock_TIMER0, true);                  // Enable TIMER0 peripheral clock
   
  GPIO_PinModeSet(LED_PORT, LED_PIN, gpioModePushPull, 0); // Configure LED0 pin as digital output (push-pull)
  GPIO_PinOutClear(LED_PORT, LED_PIN);                     // Turn off LED0
   
  TIMER_TopSet(TIMER0, 24000);                             // Set timer TOP value
  TIMER_Init_TypeDef timerInit =                           // Setup Timer initialization
  {
    .enable     = true,
    .debugRun   = true,
    .prescale   = timerPrescale1,
    .clkSel     = timerClkSelHFPerClk,
    .fallAction = timerInputActionNone,
    .riseAction = timerInputActionNone,
    .mode       = timerModeUp,
    .dmaClrAct  = false,
    .quadModeX4 = false,
    .oneShot    = false,
    .sync       = false,
  };
  TIMER_IntEnable(TIMER0, TIMER_IF_OF);     // Enable Timer0 overflow interrupt
  NVIC_EnableIRQ(TIMER0_IRQn);              // Enable TIMER0 interrupt vector in NVIC
  TIMER_Init(TIMER0, &timerInit);           // Configure and start Timer0
   
  while(1) {
    if(ms_counter == 500) {
      GPIO_PinOutToggle(LED_PORT, LED_PIN); // Toggle LED
      ms_counter = 0;                       // Reset counter
    }
  }
}

PWM の例
この例では、EFM32 Giant GeckoでPWM出力を設定する方法を示します。マイクロコントローラのPortE、Pin2で利用可能なタイマー3のキャプチャ/コンペアチャンネル2を使用します。上記の「GPIOの例」を覚えているかもしれませんが、PE2はEFM32 Giant GeckoスターターキットのLED0に外部接続されています。したがって、PWM出力を使ってLED0の輝度を制御することができます。

この例では2つの異なるタイマーを使用しています。タイマー3はPWMジェネレータとして使用されています。PWM周期は1ms(周波数1kHz)に設定され、デューティサイクルは定義されたUPDATE_PERIODで出力コンペアレジスタを更新することで調整しています。250msの更新周期を生成するために、2番目のタイマー(タイマー0)が使用されています。この例では、上記の「タイマー割り込み」の例と同様に、タイマー0のオーバーフロー割り込みを使用しています。

設定された更新期間に達すると、LED0の輝度が新しい比較値で更新され、LED1がトグルします。

この例のシステムクロックは高周波RC発振器で、1MHzに設定しました。高周波ペリフェラルクロックはタイマー0とタイマー3のソースとして使用され、各タイマーのプリスケーラは/1に設定されています。したがって、タイマー0、タイマー3、およびシステムクロックはすべて同じ周波数を共有しています。各タイマーのTOP値は1000に設定されているため、タイマー0のオーバーフロー割り込み周期とタイマー3のPWM周期はともに1ms(周波数1kHz)となっています。

ダイレクトレジスタアクセスを使用したPWMの例
以下のコードブロックは、ダイレクトレジスタアクセスを使用して、タイマー3のPWM出力機能を使用してLEDの輝度制御を実現する方法を示しています。以下のコードを動作させるには、「em_system.c」ファイルをワークスペースに追加する必要があります。

#include "efm32gg990f1024.h"
#include "em_chip.h"            // required for CHIP_Init() function
 
#define LED_PORT 4              // gpioPortE
#define LED_PIN0 2              // LED0 is connected to PortE pin2
#define LED_PIN1 3              // LED1 is connected to PortE pin3
#define TOP_VAL_PWM 1000        // sets PWM frequency to 1kHz (1MHz timer clock)
#define TOP_VAL_GP_TIMER 1000   // sets general purpose timer overflow frequency to 1kHz (1MHz timer clock)
#define UPDATE_PERIOD 250       // update compare value and toggle LED1 every 250ms
#define INC_VAL (TOP_VAL_PWM/4) // adjust compare value amount
 
uint16_t ms_counter = 0;        // global variable to count general purpose timer overflow events
 
// TIMER ISR, executes every ms
void TIMER0_IRQHandler(void) {
  TIMER0->IFC = 1;              // Clear source of interrupt
  ms_counter++;                 // Increment Counter
}
 
int main() {
   
  uint16_t compare_val = 0; // Initial PWM duty cycle is 0% (LED0 off)
  uint8_t inc = 1;          // Increment = true
   
  CHIP_Init();              // This function addresses some chip errata and should be called at the start of every EFM32 application (need em_system.c)
    
  CMU->HFRCOCTRL = 0x8;                               // Set High Freq. RC Osc. to 1 MHz and use as system source
  CMU->HFPERCLKEN0 = (1 << 13) | (1 << 8) | (1 << 5); // Enable GPIO, Timer0, and Timer3 peripheral clocks
  GPIO->P[LED_PORT].MODEL = (4 << 12) | (4 << 8);     // Configure LED0 and LED1 pins as digital outputs (push-pull)
  GPIO->P[LED_PORT].DOUTSET = (1 << LED_PIN1);        // Turn on LED1 (PE3)
   
  TIMER0->TOP = TOP_VAL_GP_TIMER;                     // GP Timer period will be 1ms = 1kHz freq
  TIMER3->TOP = TOP_VAL_PWM;                          // PWM period will be 1ms = 1kHz freq
   
  TIMER0->CNT = 0;                                    // Start counter at 0 (up-count mode)
  TIMER3->CNT = 0;                                    // Start counter at 0 (up-count mode)
   
  TIMER3->CC[2].CCV = compare_val;                    // Set CC2 compare value (0% duty)
  TIMER3->CC[2].CCVB = compare_val;                   // Set CC2 compare buffer value (0% duty)
   
  TIMER0->IEN = 1;                                    // Enable Timer0 overflow interrupt
  NVIC_EnableIRQ(TIMER0_IRQn);                        // Enable TIMER0 interrupt vector in NVIC
   
  TIMER3->CC[2].CTRL = 0x3;                           // Put Timer3 CC channel 2 in PWM mode
  TIMER3->ROUTE = (1 << 16) | (1 << 2);               // Connect PWM output (timer3, channel 2) to PE2 (LED0). See EFM32GG990 datasheet for details.
   
  TIMER0->CTRL = (1 << 6);                            // Allow timer to run while in debug mode
  TIMER3->CTRL = (1 << 6);                            // Allow timer to run while in debug mode
   
  TIMER0->CMD = 0x1;                                  // Start Timer0
  TIMER3->CMD = 0x1;                                  // Start Timer3
   
  while(1) {
    if(ms_counter == UPDATE_PERIOD) {
      if(inc) {                                    // If increment = true
        compare_val += INC_VAL;                    // Increase the compare value
      }else{                                       // If increment = false
        compare_val -= INC_VAL;                    // Decrease the compare value
      }
      TIMER3->CC[2].CCVB = compare_val;            // Write new value to compare buffer
      GPIO->P[LED_PORT].DOUTTGL = (1 << LED_PIN1); // Toggle LED1
      ms_counter = 0;                              // Reset counter
    }
    if(compare_val > (TOP_VAL_PWM-1)) { inc = 0; } // If compare value is at max, start decrementing
    if(compare_val < 1) { inc = 1; }               // If compare value is at min, start incrementing
  }
}

emlib APIを使用したPWMの例
次のコードブロックは、タイマー3(チャネル 2)のPWM出力機能を使用したLED0の輝度制御を示しています。このコードを動作させるには、以下のファイルをワークスペースに追加する必要があります。

  • em_cmu.c
  • em_gpio.c
  • em_timer.c
  • em_system.c
#include "em_device.h"
#include "em_cmu.h"
#include "em_chip.h"            // required for CHIP_Init() function
#include "em_gpio.h"
#include "em_timer.h"
 
#define TOP_VAL_PWM 1000        // sets PWM frequency to 1kHz (1MHz timer clock)
#define TOP_VAL_GP_TIMER 1000   // sets general purpose timer overflow frequency to 1kHz (1MHz timer clock)
#define UPDATE_PERIOD 250       // update compare value, toggle LED1 every 1/4 second (250ms)
#define INC_VAL (TOP_VAL_PWM/4) // adjust compare value amount
 
uint16_t ms_counter = 0;        // global variable to count general purpose timer overflow events
 
// TIMER ISR executes every ms
void TIMER0_IRQHandler(void) {
  TIMER_IntClear(TIMER0, TIMER_IF_OF); // Clear interrupt source
  ms_counter++;                        // Increment counter
}
 
int main() {
   
  uint32_t compare_val = 0;               // Initial PWM duty cycle is 0% (LED0 off)
  uint8_t inc = 1;                        // Increment = true
   
  CHIP_Init();                            // This function addresses some chip errata and should be called at the start of every EFM32 application (need em_system.c)
  CMU_HFRCOBandSet(cmuHFRCOBand_1MHz);    // Set HF oscillator to 1MHz and use as system source
  CMU_ClockEnable(cmuClock_GPIO, true);   // Start GPIO peripheral clock
  CMU_ClockEnable(cmuClock_TIMER0, true); // Start TIMER0 peripheral clock
  CMU_ClockEnable(cmuClock_TIMER3, true); // Start TIMER3 peripheral clock
   
  GPIO_PinModeSet(gpioPortE, 2, gpioModePushPull, 0); // set LED0 pin as push-pull output
  GPIO_PinModeSet(gpioPortE, 3, gpioModePushPull, 1); // set LED0 pin as push-pull output
   
  // Setup Timer Channel Configuration for PWM
  TIMER_InitCC_TypeDef timerCCInit = {
    .eventCtrl  = timerEventEveryEdge,    // This value will be ignored since we aren't using input capture
    .edge       = timerEdgeNone,          // This value will be ignored since we aren't using input capture
    .prsSel     = timerPRSSELCh0,         // This value will be ignored since we aren't using PRS
    .cufoa      = timerOutputActionNone,  // No action on underflow (up-count mode)
    .cofoa      = timerOutputActionSet,   // On overflow, we want the output to go high, but in PWM mode this should happen automatically
    .cmoa       = timerOutputActionClear, // On compare match, we want output to clear, but in PWM mode this should happen automatically
    .mode       = timerCCModePWM,         // Set timer channel to run in PWM mode
    .filter     = false,                  // Not using input, so don't need a filter
    .prsInput   = false,                  // Not using PRS
    .coist      = false,                  // Initial state for PWM is high when timer is enabled
    .outInvert  = false,                  // non-inverted output
  };
   
  // Setup Timer Configuration for PWM
  TIMER_Init_TypeDef timerPWMInit =
  {
    .enable     = true,                 // start timer upon configuration
    .debugRun   = true,                 // run timer in debug mode
    .prescale   = timerPrescale1,       // set prescaler to /1
    .clkSel     = timerClkSelHFPerClk,  // set clock source as HFPERCLK
    .fallAction = timerInputActionNone, // no action from inputs
    .riseAction = timerInputActionNone, // no action from inputs
    .mode       = timerModeUp,          // use up-count mode
    .dmaClrAct  = false,                // not using DMA
    .quadModeX4 = false,                // not using Quad Dec. mode
    .oneShot    = false,                // not using one shot mode
    .sync       = false,                // not syncronizing timer3 with other timers
  };
   
  //Setup Timer Configuration for general purpose use
  TIMER_Init_TypeDef timerGPInit =
  {
    .enable     = true,                 // start timer upon configuration
    .debugRun   = true,                 // run timer in debug mode
    .prescale   = timerPrescale1,       // set prescaler to /1
    .clkSel     = timerClkSelHFPerClk,  // set clock source as HFPERCLK
    .fallAction = timerInputActionNone, // no action from inputs
    .riseAction = timerInputActionNone, // no action from inputs
    .mode       = timerModeUp,          // use up-count mode
    .dmaClrAct  = false,                // not using DMA
    .quadModeX4 = false,                // not using Quad Dec. mode
    .oneShot    = false,                // not using one shot mode
    .sync       = false,                // not syncronizing timer3 with other timers
  };
   
  TIMER_TopSet(TIMER0, TOP_VAL_CU_TIMER);      // GP Timer period will be 1ms = 1kHz freq
  TIMER_TopSet(TIMER3, TOP_VAL_PWM);           // PWM period will be 1ms = 1kHz freq
   
  TIMER_CounterSet(TIMER0, 0);                 // Start counter at 0 (up-count mode)
  TIMER_CounterSet(TIMER3, 0);                 // Start counter at 0 (up-count mode)
   
  TIMER_CompareSet(TIMER3, 2, compare_val);    // Set CC2 compare value (0% duty)
  TIMER_CompareBufSet(TIMER3, 2, compare_val); // Set CC2 compare buffer value (0% duty)
   
  TIMER_IntEnable(TIMER0, TIMER_IF_OF);        // Enable Timer0 overflow interrupt
  NVIC_EnableIRQ(TIMER0_IRQn);                 // Enable Timer0 interrupt vector in NVIC
   
  TIMER_InitCC(TIMER3, 2, &timerCCInit);       // apply channel configuration to Timer3 channel 2
  TIMER3->ROUTE = (1 << 16) |(1 << 2);         // connect PWM output (timer3, channel 2) to PE2 (LED0). See EFM32GG990 datasheet for details.
     
  TIMER_Init(TIMER0, &timerGPInit);            // apply general purpose configuration to timer0
  TIMER_Init(TIMER3, &timerPWMInit);           // apply PWM configuration to timer3
   
  while(1) {
    if(ms_counter == UPDATE_PERIOD) {
      if(inc) {                                    // If increment = true
        compare_val += INC_VAL;                    // Increase the compare value
      }else{                                       // increment = false
        compare_val -= INC_VAL;                    // Decrease the compare value
      }
      TIMER_CompareBufSet(TIMER3, 2, compare_val); // Write new value to compare buffer
      GPIO_PinOutToggle(gpioPortE, 3);             // Toggle LED1
      ms_counter = 0;                              // Reset counter
    }
    if(compare_val > (TOP_VAL_PWM-1)) { inc = 0; } // If compare value is at max, start decrementing
    if(compare_val < 1) { inc = 1; }               // If compare value is at min, start incrementing
  }
}

UARTの例
この例は基本的なシリアル通信のために非同期モード(本質的にUART)でUSARTを設定する方法を示します。UART通信は必要なピン数(送信データ線、受信データ線、グランド)が少ないため、組み込み設計で非常に人気があります。TX/RXラインを共有する「シングルワイヤ」UARTもありますが、適切に動作させるには共通のグランドが必要です。UARTは非同期で動作し、2つのデバイス間には専用のクロックラインがありません。したがって、各デバイスが実際に通信するには、事前に定義された一致するデータレートとフレームフォーマットが必要です。

データレートは各デバイスのクロックによって定義されます。クロックレートに誤差があると、UARTのボーレートに影響し、デバイス間の同期が失われる可能性があります。このため、通常は水晶発振器のような高精度のクロック源を使用することが推奨されます。RC発振器でも動作するかもしれませんが、精度を上げるために何らかの校正プロセスが必要です。

このデモでは、Giant Geckoは48MHzの高周波水晶発振器を使用するようにセットアップされています。ここでは、「HF XTAL Oscillator」の例と同様に、24MHzのシステムクロックを得るために、HFクロックデバイダは1/2に設定されています。

このデモのために、Giant Gecko UARTポートは以下のシリアル通信設定でセットアップされています。

  • ボーレート(bps):38400
  • データ・ビット:8
  • パリティ:なし
  • ストップビット:1

以下のコード例は、外部コネクタでUSART1が利用可能なカスタム拡張ボード を使用してテストされています。カスタム拡張ボードは、FTDIのTTL-232R-3V3スマートケーブルを使用してPCに接続されています。Giant Geckoとの間で送受信される文字を表示するために、ターミナルエミュレータプログラム(Tera Term)を使用しています。ターミナルプログラムのシリアルポートは以下のように設定しています。

  • ボーレート(bps):38400
  • データ・ビット:8
  • パリティ:なし
  • ストップビット:1
  • フロー制御:なし

次の図は、以下のアプリケーションを実行中に、ターミナルウィンドウに表示される文字を示したものです。
image

ダイレクトレジスタアクセスを使用したUARTの例

  • 以下のサンプルコードは、ダイレクトレジスタアクセスを使用して、この簡単なUARTアプリケーションを実装する方法を示しています。以下のコードを実行するには、「em_system.c」ファイルをワークスペースに追加する必要があります。
#include "efm32gg990f1024.h"
#include "em_chip.h"    // required for CHIP_Init() function
#include <string.h>
 
#define COM_PORT 3 // gpioPortD (USART location #1: PD0 and PD1)
#define UART_TX_pin 0
 
int main() {
  CHIP_Init();                                   // This function addresses some chip errata and should be called at the start of every EFM32 application (need em_system.c)
  uint8_t i;
  char test_string[] = "\n\rHello World!\n\r";
  char rx_char = 0;                              // Temp variable for storing received characters
   
  CMU->CTRL |= (1 << 14);                         // Set HF clock divider to /2 to keep core frequency <32MHz
  CMU->OSCENCMD |= 0x4;                           // Enable XTAL Oscillator
  while(! (CMU->STATUS & 0x8) );                  // Wait for XTAL osc to stabilize
  CMU->CMD = 0x2;                                 // Select HF XTAL osc as system clock source. 48MHz XTAL, but we divided the system clock by 2, therefore our HF clock should be 24MHz
   
  CMU->HFPERCLKEN0 = (1 << 13) | (1 << 1);        // Enable GPIO, and USART1 peripheral clocks
    
  GPIO->P[COM_PORT].MODEL = (1 << 4) | (4 << 0);  // Configure PD0 as digital output and PD1 as input
  GPIO->P[COM_PORT].DOUTSET = (1 << UART_TX_pin); // Initialize PD0 high since UART TX idles high (otherwise glitches can occur)
   
  // Use default value for USART1->CTRL: asynch mode, x16 OVS, lsb first, CLK idle low
  // Default frame options: 8-none-1-none
  USART1->CLKDIV = (152 << 6);                               // 152 will give 38400 baud rate (using 16-bit oversampling with 24MHz peripheral clock)
  USART1->CMD = (1 << 11) | (1 << 10) | (1 << 2) | (1 << 0); // Clear RX/TX buffers and shif regs, Enable Transmitter and Receiver
  USART1->IFC = 0x1FF9;                                      // clear all USART interrupt flags
  USART1->ROUTE = 0x103;                                     // Enable TX and RX pins, use location #1 (UART TX and RX located at PD0 and PD1, see EFM32GG990 datasheet for details)
   
  // Print test string
  for(i=0; i<strlen(test_string); i++) {
    while( !(USART1->STATUS & (1 << 6)) ); // wait for TX buffer to empty
    USART1->TXDATA = test_string[i];       // print each character of the test string
  }
   
  while(1) {
    if(USART1->STATUS & (1 << 7)) {   // if RX buffer contains valid data
      rx_char = USART1->RXDATA;       // store the data
    }
    if(rx_char) {                     // if we have a valid character
      if(USART1->STATUS & (1 << 6)) { // check if TX buffer is empty
        USART1->TXDATA = rx_char;     // echo received char
        rx_char = 0;                  // reset temp variable
      }
    }
  }
}

emlib APIを使用したUARTの例

  • 以下のサンプルコードは、emlib関数を使用したUARTサンプルの実装方法を示しています。以下のコードを実行するには、ワークスペースに以下のファイルを追加する必要があります。

em_cmu.c
em_emu.c
em_gpio.c
em_system.c
em_usart.c

#include "em_device.h"
#include "em_cmu.h"
#include "em_gpio.h"
#include "em_usart.h"
#include "em_system.h"
#include "em_chip.h"    // required for CHIP_Init() function
#include <string.h>     // required for strlen() function
 
#define COM_PORT gpioPortD // USART location #1: PD0 and PD1
#define UART_TX_pin 0      // PD0
#define UART_RX_pin 1      // PD1
 
int main() {
  CHIP_Init(); // This function addresses some chip errata and should be called at the start of every EFM32 application (need em_system.c)
   
  uint8_t i;
  char test_string[] = "\n\rHello World!\n\r";     // Test string
  char rx_char = 0;                                // Temp variable for storing received characters
   
  CMU_ClockDivSet(cmuClock_HF, cmuClkDiv_2);       // Set HF clock divider to /2 to keep core frequency < 32MHz
  CMU_OscillatorEnable(cmuOsc_HFXO, true, true);   // Enable XTAL Osc and wait to stabilize
  CMU_ClockSelectSet(cmuClock_HF, cmuSelect_HFXO); // Select HF XTAL osc as system clock source. 48MHz XTAL, but we divided the system clock by 2, therefore our HF clock will be 24MHz
   
  CMU_ClockEnable(cmuClock_GPIO, true);            // Enable GPIO peripheral clock
  CMU_ClockEnable(cmuClock_USART1, true);          // Enable USART1 peripheral clock
   
  GPIO_PinModeSet(COM_PORT, UART_TX_pin, gpioModePushPull, 1); // Configure UART TX pin as digital output, initialize high since UART TX idles high (otherwise glitches can occur)
  GPIO_PinModeSet(COM_PORT, UART_RX_pin, gpioModeInput, 0);    // Configure UART RX pin as input (no filter)
   
  USART_InitAsync_TypeDef uartInit =
  {
    .enable       = usartDisable,   // Wait to enable the transmitter and receiver
    .refFreq      = 0,              // Setting refFreq to 0 will invoke the CMU_ClockFreqGet() function and measure the HFPER clock
    .baudrate     = 38400,          // Desired baud rate
    .oversampling = usartOVS16,     // Set oversampling value to x16
    .databits     = usartDatabits8, // 8 data bits
    .parity       = usartNoParity,  // No parity bits
    .stopbits     = usartStopbits1, // 1 stop bit
    .mvdis        = false,          // Use majority voting
    .prsRxEnable  = false,          // Not using PRS input
    .prsRxCh      = usartPrsRxCh0,  // Doesn't matter which channel we select
  };
   
  USART_InitAsync(USART1, &uartInit);                                              // Apply configuration struct to USART1
  USART1->ROUTE = UART_ROUTE_RXPEN | UART_ROUTE_TXPEN | _UART_ROUTE_LOCATION_LOC1; // Clear RX/TX buffers and shift regs, enable transmitter and receiver pins
   
  USART_IntClear(USART1, _UART_IF_MASK); // Clear any USART interrupt flags
  NVIC_ClearPendingIRQ(UART1_RX_IRQn);   // Clear pending RX interrupt flag in NVIC
  NVIC_ClearPendingIRQ(UART1_TX_IRQn);   // Clear pending TX interrupt flag in NVIC
   
  USART_Enable(USART1, usartEnable);     // Enable transmitter and receiver
   
  // Print test string
  for(i=0; i<strlen(test_string); i++) {
    while( !(USART1->STATUS & (1 << 6)) ); // wait for TX buffer to empty
    USART1->TXDATA = test_string[i];       // print each character of the test string
  }
   
  while(1) {
    if(USART1->STATUS & (1 << 7)) {   // if RX buffer contains valid data
      rx_char = USART1->RXDATA;       // store the data
    }
    if(rx_char) {                     // if we have a valid character
      if(USART1->STATUS & (1 << 6)) { // check if TX buffer is empty
        USART1->TXDATA = rx_char;     // echo received char
        rx_char = 0;                  // reset temp variable
      }
    }
  }
}

ADCの例
この例では、STK3700とインターフェースするカスタム拡張ボードを使用して、Giant Geckoのアナログデジタルコンバータ(ADC)をセットアップする方法を示します。

拡張ボードに搭載されているポテンショメータがアナログソースとして使用されています。ポテンショメータは、0~3.3Vの間で変化する制御可能なアナログ電圧源を提供し、センタータップはGiant GeckoのPD6(ADC0チャンネル6)に接続されています。したがって、VDD(3.3V)がADCの基準電圧として使用されています。ADCはシングルエンドモードでセットアップされ、あらかじめ定義された間隔でシングル変換を行っています。結果は16ビット結果レジスタに右詰めの12ビットで格納されます。ADCクロックは12MHzに設定され、x2オーバーサンプリン グを有効にしています。このデモでは入力フィルタはインプリメントしていません。

ADCが各変換を完了した後、USART1がPC上のターミナルプログラムに結果を送信するために使用されます。USARTは上記の「UARTの例」と同じシリアル通信設定で非同期モードでセットアップされています。

UART通信用の高精度クロックを供給するため、48MHzの水晶発振器に1/2のプリスケーラを使用し、上記の「HF XTAL発振器」の例と同様に24MHzのシステムクロックを生成しています。

Timer0は、上記の「タイマー割り込み」の例と同様に、500ms毎にシングルADC変換をトリガする汎用タイマーとして使用されています。電位差計の値は0.5秒ごとに画面上で更新されます。

以下は、このデモアプリケーションを実行しているときのターミナルウィンドウのスクリーンショットです。
image

ダイレクトレジスタアクセスを使用するADCの例

  • 以下のコード例は、ダイレクトレジスタアクセスを使用してシングル変換モー ドでADCをセットアップする方法を示しています。以下のコードを動作させるには、「em_system.c」 ファイルをワークスペースに追加する必要があります。
#include "efm32gg990f1024.h"
#include "em_chip.h"    // required for CHIP_Init() function
  
#define COM_PORT 3 // gpioPortD (USART location #1: PD0 and PD1)
#define ADC_PORT 3 // gpioPortD (ADC Channel 6 location #0: PD6)
#define TX_pin 0
#define ADC_pin 6 // ADC Channel 6
 
void set_decade(uint16_t val);
uint8_t conv_ascii(uint16_t val);
  
uint16_t ms_counter = 0;
char header[] = "\n\rEFM32 Giant Gecko - ADC Example\n\r";
uint8_t digit_array[7] = { 0x20, 0x20, 0x20, 0x20, 0x20, 'm', 'V' }; // Array for displaying ADC result, initialize to "     mV"
  
void TIMER0_IRQHandler(void) {
  TIMER0->IFC = 1; // Clear overflow flag
  ms_counter++;    // Increment counter
}
 
int main() {
  CHIP_Init();                   // This function addresses some chip errata and should be called at the start of every EFM32 application (need em_system.c)
   
  uint8_t i;
  uint16_t adc_result = 0;       // Temp variable for storing ADC conversion results
  uint32_t temp;
   
  // Initialize Clock Tree
  CMU->CTRL |= (1 << 14);        // Set HF clock divider to /2 to keep core frequency <32MHz
  CMU->OSCENCMD |= 0x4;          // Enable XTAL Oscillator
  while(! (CMU->STATUS & 0x8) ); // Wait for XTAL osc to stabilize
  CMU->CMD = 0x2;                // Select HF XTAL osc as system clock source. 48MHz XTAL, but we divided the system clock by 2, therefore our HF clock should be 24MHz
  CMU->HFPERCLKEN0 = (1 << 16) | (1 << 13) | (1 << 5) | (1 << 1); // Enable GPIO, TIMER0, USART1, and ADC0 peripheral clocks
   
  // Initialize GPIO 
  GPIO->P[COM_PORT].MODEL = (1 << 24) | (1 << 4) | (4 << 0); // Configure PD0 as digital output, PD1 and PD6 as input
  GPIO->P[COM_PORT].DOUTSET = (1 << TX_pin);                 // Initialize PD0 high since UART TX idles high (otherwise glitches can occur)
   
  // Setup UART Port for asynch mode, frame format 8-none-1-none
  USART1->CLKDIV = (152 << 6);                               // 152 will give 38400 baud rate (using 16-bit oversampling with 24MHz peripheral clock)
  USART1->CMD = (1 << 11) | (1 << 10) | (1 << 2) | (1 << 0); // Clear RX/TX buffers and shif regs, Enable Transmitter and Receiver
  USART1->IFC = 0x1FF9;                                      // clear all USART interrupt flags
  USART1->ROUTE = 0x103;                                     // Enable TX and RX pins, use location #1 (UART TX and RX located at PD0 and PD1, see EFM32GG990 datasheet for details)
    
  // Setup ADC
  // Timebase bit field = 24, defines ADC warm up period (must be greater than or equal to number of clock cycles in 1us)
  // Prescaler setting = 1: ADC clock = HFPERCLK/2 = 12MHz (ADC clock should be between 32kHz and 13MHz)
  // Oversampling set to 2, no input filter, no need for Conversion Tailgating
  // Warm-up mode = NORMAL (ADC is not kept warmed up between conversions)
  ADC0->CTRL = (24 << 16) | (1 << 8);
   
  // Don't use PRS as input
  // Can use single-cycle acquisition time since we are spacing out our conversions using a timer
  // Use buffered Vdd as reference, use Channel 6 as input to single conversion
  // 12-bit resolution, right-justified, single-ended input, single conversion
  ADC0->SINGLECTRL = (2 << 16) | (6 << 8);
  ADC0->IEN = 0x0; // Disable ADC interrupts
   
  // Setup Timer to trigger conversions
  TIMER0->TOP = 24000;                          // Set TOP value for Timer0
  TIMER0->IEN = 1;                              // Enable Timer0 overflow interrupt
  NVIC_EnableIRQ(TIMER0_IRQn);                  // Enable TIMER0 interrupt vector in NVIC
  TIMER0->CMD = 0x1;                            // Start timer0
   
  // Print Startup Header
  i=0;
  while(header[i] != 0) {
    while( !(USART1->STATUS & (1 << 6)) ); // wait for TX buffer to empty
    USART1->TXDATA = header[i++];            // print each character of the header
  }
   
  while(1) {
    if(ms_counter == 500) {
       
      // Move cursor to start of line
      while( !(USART1->STATUS & (1 << 6)) ); // wait for TX buffer to empty
      USART1->TXDATA = 0x0D;                 // send carriage return
      ADC0->CMD = 0x1;                       // Start Single Conversion
      while(!(ADC0->STATUS & (1 << 16)));    // Wait for single conversion data to become valid
      adc_result = ADC0->SINGLEDATA;         // Store conversion result
       
      // Change hex result to decimal result in mV
      temp = adc_result*3300;
      adc_result = temp/4095;
      set_decade(adc_result); // Divide result into individual characters to be transmitted over UART
       
      // Transmit result characters over UART
      for(i=0; i<7; i++) {
        while( !(USART1->STATUS & (1 << 6)) ); // wait for TX buffer to empty
        USART1->TXDATA = digit_array[i];       // print each character of the test string
      }
       
      ms_counter = 0; // reset counter
    }
  }
}
 
// This function is used to divide a 16-bit value into 5 individual digits
void set_decade(uint16_t val)
{
  uint16_t tmp;
  tmp = val / 10000;
  if (tmp) {
    digit_array[0] = conv_ascii(tmp);
  } else
  digit_array[0] = ' ';
  val = val - tmp*10000;
  tmp = val / 1000;
  if (tmp) {
    digit_array[1] = conv_ascii(tmp);
  } else
  digit_array[1] = ' ';
  val = val - tmp*1000;
  tmp = val / 100;
  if (tmp || digit_array[1]!=' ') {
    digit_array[2] = conv_ascii(tmp);
  } else digit_array[2] = ' ';
  val = val - tmp*100;
  tmp = val / 10;
  if (tmp  || digit_array[2]!=' ' || digit_array[1]!=' ') {
    digit_array[3] = conv_ascii(tmp);
  } else digit_array[3] = ' ';
  val = val - tmp*10;
  digit_array[4] = conv_ascii(val);
}
 
// This function is used to covert the character to its ASCII value.
uint8_t conv_ascii(uint16_t val)
{
  if (val<= 0x09) {
    val = val + 0x30;
  }
  if ((val >= 0x0A) && (val<= 0x0F)) {
    val = val + 0x37;
  }
  return val;
}

SPIの例
この例では、STK3700とインターフェースするカスタム拡張ボード使用して、Giant Geckoでシリアルペリフェラルインターフェース(SPI)を設定する方法を示します。

SPIターゲットには拡張ボードに搭載されたフラッシュメモリIC(W25X20CL)を使用しました。フラッシュICのSPIポート(MOSI、 MISO、 SCLK、 CS)はPortD - PD[3:0]を通してUSART1(位置#1)に接続されています。USART1は同期(SPI)マスターモードに設定され、SCLKはアイドルLow、SCLKの立ち下がりエッジでデータがセットアップされ、SCLKの立ち上がりエッジでデータがサンプリングされます。USART1のクロック分周は0のままで、12MHzのSCLK周波数を生成します。フラッシュICの要件のため、USART1はMSBを最初に送信するように設定され、CSピンはマニュアルで処理しました。(Giant Geckoは自動CS機能を提供します)。フラッシュICはGiant Geckoに接続されたアクティブLowの「Hold」ピンと「Write Protect」ピンも持っています。しかし、これらのピンは、「Hold」ピンをHighに、「Write Protect」ピンをLowに駆動する以外、この例では使用されません。

このデモでは、単純な「Device ID」コマンドがフラッシュICに送信され、メーカーIDとデバイスIDの2バイトの応答が返されます。W24X20CLでは、これらの値はそれぞれ0xEFと0x11でした。

応答受信後、USART0はPC上の端末プログラムへ応答を送信するために使用されます。USART0は上記の「UARTの例」と同じシリアル通信設定で非同期モードに設定されています。しかしこの例ではPC0(USART0位置#5)が送信ピンとして使用されました。ロケーション#5の受信ピンは全二重モードの間、STK3700の拡張ポートを通して利用できません。PCから値を入力するのではなく、結果をPCに送信するだけなので、これは問題ではありません。USART0のTXピンとGNDは、FTDI製TTL-232R-3V3スマートケーブルのRXピンとGNDピンに接続され、PCのUSBポートに接続されています。

UART通信用の高精度クロックを提供するため、48MHzの水晶発振器に1/2のプリスケーラを使用し、上記の「HF XTAL発振器」の例と同様に24MHzのシステムクロックを生成しています。

以下は、このデモアプリケーションを実行しているときのターミナルウィンドウのスクリーンショットです。
image

ダイレクトレジスタアクセスを使用したSPI例

  • 以下のコード例は、ダイレクトレジスタアクセスを使用してSPI通信をセットアップする方法を示しています。以下のコードを実行するには、「em_system.c」ファイルをワークスペースに追加する必要があります。
#include "efm32gg990f1024.h"
#include "em_chip.h"    // required for CHIP_Init() function
 
#define SPI_USART USART1
#define SPI_COM_PORT 3 // gpioPortD (USART1 location #1)
#define SPI_TX_pin 0   // PD0 - MOSI
#define SPI_RX_pin 1   // PD1 - MISO
#define SPI_CS_pin 3   // PD3 - Chip Select
 
#define COM_USART USART0
#define EXT_COM_PORT 2 // gpioPortC (USART0 location #5)
#define EXT_TX_pin 0   // PC0 - TX
 
#define RX_BUFFER_SIZE 2
 
// SPI functions
void CS_pin_set(void);
void CS_pin_clr(void);
void SPI_WriteByte(uint8_t byte);
uint8_t SPI_ReadByte(void);
 
// UART function
void print_byte(uint8_t byte);
 
// Misc. functions
void get_device_id(uint8_t storage_array[], uint8_t start_index); // requires at least a 2-element storage array
void print_byte_setup(uint8_t byte);
 
// Global variable
uint8_t print_byte_array[4] = { 0x30, 0x78, 0x20, 0x20 }; // array for displaying bytes one nibble at a time
 
int main() {
  CHIP_Init(); // This function addresses some chip errata and should be called at the start of every EFM32 application (need em_system.c)
   
  uint8_t i;
  char test_string[] = "\n\rManuf/Device ID\n\r";
  uint8_t rx_buffer[RX_BUFFER_SIZE]; // array for storing the 2-byte 'Device ID' response
   
  // Clear rx buffer
  for(i=0; i<RX_BUFFER_SIZE; i++) {
    rx_buffer[i] = 0;
  }
   
  // Setup Clock Tree
  CMU->CTRL |= (1 << 14);                             // Set HF clock divider to /2 to keep core frequency <32MHz
  CMU->OSCENCMD |= 0x4;                               // Enable XTAL Oscillator
  while(! (CMU->STATUS & 0x8) );                      // Wait for XTAL osc to stabilize
  CMU->CMD = 0x2;                                     // Select HF XTAL osc as system clock source. 48MHz XTAL, but we divided the system clock by 2, therefore our HF clock should be 24MHz
  CMU->HFPERCLKEN0 = (1 << 13) | (1 << 1) | (1 << 0); // Enable GPIO, USART0, and USART1 peripheral clocks
   
  // Configure GPIO
  GPIO->P[SPI_COM_PORT].MODEL = (4 << 20) | (4 << 16) | (4 << 12) | (4 << 8) | (1 << 4) | (4 << 0); // Configure PD0 (MOSI), PD2 (SCK), PD3 (CS), PD4 (nHLD), and PD5 (nWP) as digital outputs and PD1 as input
  GPIO->P[SPI_COM_PORT].DOUTSET = (1 << 4) | (1 << 3); // Initialize CS and nHLD high, nWP low
  GPIO->P[EXT_COM_PORT].MODEL = (4 << 0);              // Configure PC0 as digital output
  GPIO->P[EXT_COM_PORT].DOUTSET = (1 << EXT_TX_pin);   // Initialize PC0 high since UART TX idles high (otherwise glitches can occur)
   
  // Configure SPI Port (USART1 in sync mode)
  SPI_USART->CTRL = (1 << 30) | (1 << 10) | (1 << 0);           // Transmit MSB first, use SPI mode 0 (0,0), Disable majority voting, use manual chip-select
  SPI_USART->CLKDIV = (0 << 6);                                 // Leave CLKDIV set to 0, leads to a baud rate of 12Mbps
  SPI_USART->CMD = (1 << 11) | (1 << 10) | (1 << 4) | (1 << 2) | (1 << 0); // Clear RX and TX buffers and shift regs, enable master mode, enable transmitter/receiver
  SPI_USART->IFC = 0x1FF9;                                      // Clear USART1 interrupt flags
  SPI_USART->ROUTE = (1 << 8) | (1 << 3) | (1 << 1) | (1 << 0); // Use USART1 location #1, enable CLK, MISO, and MOSI pins
   
  // Configure COM Port (USART0 in async mode)
  COM_USART->CLKDIV = (152 << 6);         // 152 will give 38400 baud rate (using 16-bit oversampling with 24MHz peripheral clock)
  COM_USART->CMD = (1 << 10) | (1 << 2);  // Clear TX buffer and shif reg, Enable Master mode, Enable Transmitter
  COM_USART->IFC = 0x1FF9;                // clear all USART interrupt flags
  COM_USART->ROUTE = (5 << 8) | (1 << 1); // Enable TX pin, use location #5 (UART TX located at PC0, see EFM32GG990 datasheet for details)
   
  // Print test string
  i=0;
  while(test_string[i] != 0){
    print_byte(test_string[i++]);
  }
   
  // Send 'Device ID' command over SPI
  get_device_id(rx_buffer, 0);       // store result in rx_buffer starting at element 0
   
  // Display Manufacturer ID
  print_byte_setup(rx_buffer[0]);    // break up byte into individual digits (nibbles)
  for(i=0; i<4; i++) {
    print_byte(print_byte_array[i]); // Transmit high and low nibbles over UART
  }
   
  print_byte(0x20); // send whitespace 
   
  // Display Device ID
  print_byte_setup(rx_buffer[1]);    // break up byte into individual digits (nibbles)
  for(i=0; i<4; i++) {
    print_byte(print_byte_array[i]); // Transmit high and low nibbles over UART
  }
   
  print_byte('\r'); // send CR
  print_byte('\n'); // send LF    
   
  while(1);
}
 
// This function is used to display bytes over UART
void print_byte(uint8_t byte) {
  while( !(COM_USART->STATUS & (1 << 6)) ); // wait for TX buffer to empty
  COM_USART->TXDATA = byte;                 // send byte over UART
}
 
// This function drives the CS pin low
void CS_pin_clr(void) {
  GPIO->P[SPI_COM_PORT].DOUTCLR = (1 << SPI_CS_pin);
}
 
// This function drives the CS pin high
void CS_pin_set(void) {
  GPIO->P[SPI_COM_PORT].DOUTSET = (1 << SPI_CS_pin);
}
 
// This function writes a byte to the TX buffer (waits for buffer to clear)
void SPI_WriteByte(uint8_t byte) {
  while( !(SPI_USART->STATUS & (1 << 6)) ); // wait for tx buffer to empty
  SPI_USART->TXDATA = byte;                
}
 
// This function returns the value of the RX buffer (waits for valid RX data)
uint8_t SPI_ReadByte(void) {
  while(! (SPI_USART->STATUS & (1 << 7)) ); // wait for valid RX data
  return(SPI_USART->RXDATA);         // clear RX buffer
}
 
// This function gets the manuf/device ID from the flash IC
void get_device_id(uint8_t storage_array[], uint8_t start_index) {
  uint8_t i;
   
  // drive CS pin low
  CS_pin_clr();  
   
  // send 'Device ID' command
  SPI_WriteByte(0x90);          
  storage_array[start_index] = SPI_ReadByte(); // clear RX buffer
   
  // send 3 '00' bytes
  for(i=0; i<3; i++) {
    SPI_WriteByte(0x00);          
    storage_array[start_index] = SPI_ReadByte(); // clear RX buffer each time  
  }
   
  // read manufacturer id
  SPI_WriteByte(0xFF);           // send dummy byte to receive manuf. id
  storage_array[start_index] = SPI_ReadByte(); // store manuf. id
  SPI_WriteByte(0xFF);           // send dummy byte to receive device id
  storage_array[start_index+1] = SPI_ReadByte(); // store device id     
   
  // drive CS pin high
  CS_pin_set();
}
 
// This function breaks up 'byte' into high and low nibbles (ASCII characters) to be transmitted over UART
void print_byte_setup(uint8_t byte) {
                
  if (((byte & 0xF0) >> 4) <= 0x09) {                      // if high nibble is less than 0xA
    print_byte_array[2] = ((byte & 0xF0) >> 4) + 0x30;     // store ASCII char
  }
  if ((((byte & 0xF0) >> 4) >= 0x0A) && (((byte & 0xF0) >> 4)<= 0x0F)) { // if high nibble is between 0xA and 0xF
    print_byte_array[2] = ((byte & 0xF0) >> 4) + 0x37;     // store ASCII char
  }
  if ((byte & 0x0F) <= 0x09) {                             // if low nibble is less than 0xA
    print_byte_array[3] = (byte & 0x0F) + 0x30;            // store ASCII char
  }
  if (((byte & 0x0F) >= 0x0A) && ((byte & 0x0F)<= 0x0F)) { // if low nibble is between 0xA and 0xF
    print_byte_array[3] = (byte & 0x0F) + 0x37;            // store ASCII char
  }
}

I2Cの例
この例では、STK3700 とインターフェースするスタム拡張ボードを使用して、Giant GeckoでI2C(Inter-Integrated Circuit)ペリフェラルをセットアップする方法を示します。

拡張ボードに搭載されているEEPROMメモリICをI2Cスレーブデバイスとして使用しました。EEPROMは、PortC - PC[5:4]を介してI2C1(ロケーション#0)に接続されています。I2C1は、標準速度(100kHz)を使用してマスターモードで設定されています。Giant Gecko I2Cの自動認識機能は、コードを単純化するためにこのデモで使用されています。EEPROMはまた、PC3に接続されたアクティブハイの「Write Protect」ピンを持っており、メモリ書き込みを有効にするには、このピンをローに駆動する必要があります。

以下のコード例には、EEPROMとのやりとりに関連する複数の関数が含まれています。

  • read_byte() - メモリ内の指定されたアドレスの内容を読み取る
  • write_byte() - メモリの特定のアドレスにバイトデータを書き込む
  • read_block() - メモリのブロック(256 バイト)全体を順次読み出す
  • write_page() - 1 回のI2Cトランザクションで、最大16バイトのメモリを書き込む
  • chip_is_busy() - EEPROMが通信可能かどうかをチェックする

USART1はターミナルプログラムで応答をプリントするように設定され、また、上記の例と同じシリアル設定でロケーション#1の非同期モードでセットアップされました。USART1はFTDIのTTL-232R-3V3スマートケーブルを使用してPCに接続されています。

UART通信用の高精度クロックを提供するため、48MHzの水晶発振器に1/2のプリスケーラを使用し、上記の「HF XTAL発振器」の例と同様に24MHzのシステムクロックを生成しています。

以下は、このデモアプリケーションを実行しているときのターミナルウィンドウのスクリーンショットです。以下のスクリーンショットは、write_byte() 関数を使用してブロック1のアドレス0x02に0x23を書き込んだ後のブロック1の1部を示しています。page_write() 関数を使用し、ページ1の各バイトに0x00を書き込んでいます。

ダイレクトレジスタアクセスを使用したI2Cの例

  • 次のコードブロックは、ダイレクトレジスタアクセスを使用したI2C通信の例です。次のコードを実行するには、「em_system.c」 ファイルをワークスペースに追加する必要があります。
// All of the functions involving direct I2C communication with the EEPROM wait
// for the I2C bus to become idle and then wait until the EEPROM is ready to
// communicate. All "write" functions wait until the EEPROM completes the memory
// write before returning.
 
#include  "efm32gg990f1024.h"
#include  "em_chip.h"        // required for CHIP_Init() function
 
#define   I2C_PORT     2     // gpioPortC
#define   I2C_SDA      4     // PC4
#define   I2C_SCLK     5     // PC5
#define   I2C_WP       3     // PC3 - Write protect pin for EEPROM IC
#define   UART_PORT    3     // gpioPortD (USART location #1: PD0 and PD1)
#define   UART_TX      0     // UART TX: PD0
#define   LED_PORT     1     // gpioPortB
#define   LED_PIN0     11    // LED0: PB11
#define   LED_PIN1     12    // LED1: PB12
#define   BLOCK_SIZE   256   // 256 bytes per block
#define   PAGE_SIZE    16    // 16 bytes per page
 
// Functions involving direct I2C communication with EEPROM
uint8_t read_byte(uint8_t memory_block, uint8_t address, uint8_t* response);
uint8_t write_byte(uint8_t memory_block, uint8_t address, uint8_t data);
uint8_t read_block(uint8_t memory_block, uint8_t buffer[], uint16_t buffer_size);
uint8_t write_page(uint8_t memory_block, uint8_t page_num, uint8_t data[], uint8_t page_size);
uint8_t chip_is_busy(void);
 
// Higher level functions that implement the I2C functions above
void display_block(uint8_t memory_block);
void block_erase(uint8_t memory_block);
void check_error_status(uint8_t status);
 
// Functions involving UART communication with terminal (PC) program
void print_byte(uint8_t byte);
void print_byte_setup(uint8_t byte);
 
// Global variable
uint8_t print_byte_array[4] = { 0x30, 0x78, 0x20, 0x20 }; // array for displaying bytes one nibble at a time
 
// Error strings
char error_message1[] = "Error - unsupported block number\n\r";
char error_message2[] = "Error - buffer/array size unacceptable\n\r";
char error_message3[] = "Error - unsupported page number\n\r";
char error_message4[] = "Error - transmitted/received NACK, expected ACK\n\r";
char error_message5[] = "Error - unkown cause of error\n\r";
 
int main() {
  CHIP_Init(); // This function addresses some chip errata and should be called at the start of every EFM32 application (need em_system.c)
   
  char header[] = "\n\rEFM32 Giant Gecko - I2C Example\n\r";
  uint8_t response_byte;
  uint8_t status;
  uint8_t page_data[PAGE_SIZE];
  uint8_t i;
   
  // Clear the page_data array
  for(i=0; i<sizeof(page_data); i++) {
    page_data[i] = 0;
  }
   
  // Setup Clock Tree
  CMU->CTRL |= (1 << 14);                              // Set HF clock divider to /2 to keep core frequency <32MHz
  CMU->OSCENCMD |= 0x4;                                // Enable XTAL Oscillator
  while(! (CMU->STATUS & 0x8) );                       // Wait for XTAL osc to stabilize
  CMU->CMD = 0x2;                                      // Select HF XTAL osc as system clock source. 48MHz XTAL, but we divided the system clock by 2, therefore our HF clock should be 24MHz
  CMU->HFPERCLKEN0 = (1 << 13) | (1 << 12) | (1 << 1); // Enable GPIO, USART1, and I2C1 peripheral clocks
   
  // Configure GPIO
  GPIO->P[I2C_PORT].MODEL = (11 << 20) | (11 << 16) | (4 << 12);                // Configure PC4 and PC5 as open drain output, PC3 as push-pull output
  GPIO->P[I2C_PORT].DOUTSET = (1 << I2C_SCLK) | (1 << I2C_SDA) | (1 << I2C_WP); // Initialize PC3, PC4, and PC5 high
  GPIO->P[UART_PORT].MODEL = (1 << 4) | (4 << 0);                               // Configure PD0 as push-pull output, PD1 as input
  GPIO->P[UART_PORT].DOUTSET = (1 << UART_TX);                                  // Initialize PD0 high since UART TX idles high (otherwise glitches can occur)
  GPIO->P[LED_PORT].MODEH = (4 << 16) | (4 << 12);                              // Configure PB11 and PB12 as push-pull outputs
   
  // Configure UART
  // Setup UART Port for asynch mode, frame format 8-none-1-none
  USART1->CLKDIV = (152 << 6);                               // 152 will give 38400 baud rate (using 16-bit oversampling with 24MHz peripheral clock)
  USART1->CMD = (1 << 11) | (1 << 10) | (1 << 2) | (1 << 0); // Clear RX/TX buffers and shif regs, Enable Transmitter and Receiver
  USART1->IFC = 0x1FF9;                                      // clear all USART interrupt flags
  USART1->ROUTE = 0x103;                                     // Enable TX and RX pins, use location #1 (UART TX and RX located at PD0 and PD1, see EFM32GG990 datasheet for details)
   
  // Configure I2C
  // Standard speed, Master mode
  I2C1->CTRL = (1 << 2) | (1 << 0);           // auto acknowledge, enable I2C module
  I2C1->CMD = (1 << 7) | (1 << 6) | (1 << 5); // Clear pending commands, clear TX buffer and shift reg, issue abort command to clear bus busy bit
  I2C1->CLKDIV = 29;                          // allows for 100kHz I2C clock
  I2C1->ROUTE = 0x3;                          // use location #0, enable SDA and SCK pins
   
  // Print Startup Header
  for(i=0; i<sizeof(header); i++) {
    print_byte(header[i]);
  }
   
  block_erase(1);                              // Erase entire block of memory
  display_block(1);                            // Dump contents of block into terminal window
   
  status = write_byte(1, 0x02, 0x23);          // Write a data byte to a specific address in memory
  check_error_status(status);                  // Check for errors
   
  status = read_byte(1, 0x02, &response_byte); // Read a specific address location, store in 'response_byte'
  print_byte_setup(response_byte);             // Print the response in the terminal window
  for(i=0; i<4; i++) {
    print_byte(print_byte_array[i]);
  }
   
  status = write_page(1, 0x1, page_data, sizeof(page_data)); // Write an entire page of memory
  check_error_status(status);                                // Check for errors
   
  display_block(1);                            // Dump contents of block into terminal window
   
  while(1);
}
 
// This function writes a single byte of 'data' to the specified 'address' of the
// specified memory block. Returns 0 if successful, 1 if unsupported memory block.
uint8_t write_byte(uint8_t memory_block, uint8_t address, uint8_t data) {
  // Initial parameter check
  if(memory_block > 3) { return 1; } // unsupported block number
   
  // Control variable
  uint8_t block_mask = 0x03; // 24AA08 only has 4 block to choose from
  // Build control byte (see 24AA08 datasheet for details)
  uint8_t control_byte = (0xA0 | ((memory_block & block_mask) << 1));
   
  // Drive WP pin low to allow memory write
  GPIO->P[I2C_PORT].DOUTCLR = (1 << I2C_WP);
   
  while(chip_is_busy());               // wait until EEPROM is ready to communicate
  while(I2C1->STATE & 0x1);            // wait for bus to become idle
  I2C1->CMD |= 0x1;                    // send START command
  while(!(I2C1->STATUS & (1 << 7)));   // wait for TX buffer to empty
  I2C1->TXDATA = control_byte;         // write control byte to TX buffer
  while(!(I2C1->STATUS & (1 << 7)));   // wait for TX buffer to empty
  while(!((I2C1->STATE >> 4) == 0x9)); // wait for address ACK/NACK and bushold
  if(I2C1->STATE & 0x8) { return 4; }  // NACK: lost communication
  I2C1->TXDATA = address;              // write address to TX buffer
  while(!(I2C1->STATUS & (1 << 7)));   // wait for TX buffer to empty
  while(!((I2C1->STATE >> 4) == 0xD)); // wait for data ACK/NACK and bushold
  if(I2C1->STATE & 0x8) { return 4; }  // NACK: lost communication
  I2C1->TXDATA = data;                 // write data to TX buffer
  while(!(I2C1->STATUS & (1 << 7)));   // wait for TX buffer to empty
  while(!((I2C1->STATE >> 4) == 0xD)); // wait for data ACK/NACK and bushold
  if(I2C1->STATE & 0x8) { return 4; }  // NACK: lost communication
  I2C1->CMD |= 0x2;                    // send STOP command
   
  while(chip_is_busy());               // wait until EEPROM has completed the memory write
   
  GPIO->P[I2C_PORT].DOUTSET = (1 << I2C_WP); // drive WP pin high to protect memory
   
  return 0;                            // successful transfer
}
 
// This function writes up to 16 bytes of memory with the data contained in the 'data[]'
// array. Returns 0 if successful, 1 if unsupported memory block, 2 if 'data[]' array is
// too large, 3 if the number of specified pages exceeds the number available in the block.
uint8_t write_page(uint8_t memory_block, uint8_t page_num, uint8_t data[], uint8_t page_size) {
  // Initial parameter check
  if(memory_block > 3) { return 1; } // unsupported memory block
  if(page_size > 16) { return 2; }   // data[] array is too large
  if(page_num > 0x0F) { return 3; }  // only 16 pages available per block
   
  // Control variables
  uint8_t block_mask = 0x03;
  uint8_t i;
   
  // Build control byte
  uint8_t control_byte = (0xA0 | ((memory_block & block_mask) << 1));
   
  // Locate page offset
  uint8_t address = 16*page_num;
   
  // Drive WP pin low to allow memory write
  GPIO->P[I2C_PORT].DOUTCLR = (1 << I2C_WP);
   
  while(chip_is_busy());               // wait until EEPROM is ready to communicate
  while(I2C1->STATE & 0x1);            // wait for bus to become idle
  I2C1->CMD |= 0x1;                    // send START command
  while(!(I2C1->STATUS & (1 << 7)));   // wait for TX buffer to empty
  I2C1->TXDATA = control_byte;         // write control byte to TX buffer
  while(!(I2C1->STATUS & (1 << 7)));   // wait for TX buffer to empty
  while(!((I2C1->STATE >> 4) == 0x9)); // wait for address ACK/NACK and bushold
  if(I2C1->STATE & 0x8) { return 4; }  // NACK: lost communication
  I2C1->TXDATA = address;              // write address to TX buffer
  while(!(I2C1->STATUS & (1 << 7)));   // wait for TX buffer to empty
  while(!((I2C1->STATE >> 4) == 0xD)); // wait for data ACK/NACK and bushold
  if(I2C1->STATE & 0x8) { return 4; }  // NACK: lost communication
   
  for(i=0; i<page_size; i++) {
    I2C1->TXDATA = data[i];              // write data to TX buffer
    while(!(I2C1->STATUS & (1 << 7)));   // wait for TX buffer to empty
    while(!((I2C1->STATE >> 4) == 0xD)); // wait for data ACK/NACK and bushold
    if(I2C1->STATE & 0x8) { return 4; }  // NACK: lost communication
  }
  I2C1->CMD |= 0x2;                      // send STOP command
   
  while(chip_is_busy()); // wait until EEPROM has completed the memory write
   
  GPIO->P[I2C_PORT].DOUTSET = (1 << I2C_WP); // drive WP pin high to protect memory
   
  return 0;                              // successful transfer
}
 
// This function reads the contents of 'address' within the specified memory block.
// The contents are stored in 'response'. Returns 0 if successful, 1 if unsupported
// memory block.
uint8_t read_byte(uint8_t memory_block, uint8_t address, uint8_t* response) {
  // Initial parameter check
  if(memory_block > 3) { return 1; } // unsupported block number
   
  // Control variable
  uint8_t block_mask = 0x03; // 24AA08 only has 4 block to choose from
     
  // Build control bytes (2 for reading a specific address)
  uint8_t control_byte[2] = { (0xA0 | ((memory_block & block_mask) << 1)),
                              (0xA0 | ((memory_block & block_mask) << 1) | 0x1) };
   
  I2C1->CMD &= ~(0x4);                 // clear TX buffer
  *response = I2C1->RXDATA;            // flush receive buffer
   
  while(chip_is_busy());               // wait until EEPROM is ready to communicate
  while(I2C1->STATE & 0x1);            // wait for bus to become idle
  I2C1->CMD |= 0x1;                    // send START command
  while(!(I2C1->STATUS & (1 << 7)));   // wait for TX buffer to empty
  I2C1->TXDATA = control_byte[0];      // write control byte[0] to TX buffer
  while(!(I2C1->STATUS & (1 << 7)));   // wait for TX buffer to empty
  while(!((I2C1->STATE >> 4) == 0x9)); // wait for address ACK/NACK and bushold
  if(I2C1->STATE & 0x8) { return 4; }  // NACK: lost communication
  I2C1->TXDATA = address;              // write address to TX buffer
  while(!(I2C1->STATUS & (1 << 7)));   // wait for TX buffer to empty
  while(!((I2C1->STATE >> 4) == 0xD)); // wait for data ACK/NACK and bushold
  if(I2C1->STATE & 0x8) { return 4; }  // NACK: lost communication
  I2C1->CMD |= 0x1;                    // send Repeated START command
  I2C1->TXDATA = control_byte[1];      // write control byte[1] to TX buffer
  while(!(I2C1->STATUS & (1 << 7)));   // wait for TX buffer to empty
  while(!((I2C1->STATE >> 4) == 0x8)); // wait for address ACK/NACK (no bushold)
  if(I2C1->STATE & 0x8) { return 4; }  // NACK: lost communication
  while(!(I2C1->STATUS & (1 << 8)));   // wait for data to arrive
  *response = I2C1->RXDATA;            // read rx buffer and store in 'response'
  I2C1->CMD |= 0x8;                    // send NACK
  while(!((I2C1->STATE >> 4) == 0xD)); // wait for data ACK/NACK and bushold
  I2C1->CMD |= 0x2;                    // send STOP command
   
  return 0;                            // successful transfer
}
 
// This function reads the specified memory block and stores the contents in 'buffer[]'.
// Returns 0 if successful, 1 if unsupported memory block, 2 if buffer is not large
// enough to store entire contents of the block.
uint8_t read_block(uint8_t memory_block, uint8_t buffer[], uint16_t buffer_size) {
    // Initial parameter check
  if(memory_block > 3) { return 1; }  // unsupported block number
  if(buffer_size < 256) { return 2; } // buffer is not large enough to store entire contents
   
  // Control variables
  uint8_t block_mask = 0x03; // 24AA08 only has 4 block to choose from
  uint8_t i;
   
  // build control bytes (2 for reading specific address)
  uint8_t control_byte[2] = { (0xA0 | ((memory_block & block_mask) << 1)),
                              (0xA0 | ((memory_block & block_mask) << 1) | 0x1) };
   
  I2C1->CMD &= ~(0x4);                 // clear TX buffer
  buffer[0] = I2C1->RXDATA;            // flush receive buffer
   
  while(chip_is_busy());               // wait until EEPROM is ready to communicate
  while(I2C1->STATE & 0x1);            // wait for bus to become idle
  I2C1->CMD |= 0x1;                    // send START command
  while(!(I2C1->STATUS & (1 << 7)));   // wait for TX buffer to empty
  I2C1->TXDATA = control_byte[0];      // write control byte[0] to TX buffer
  while(!(I2C1->STATUS & (1 << 7)));   // wait for TX buffer to empty
  while(!((I2C1->STATE >> 4) == 0x9)); // wait for address ACK/NACK and bushold
  if(I2C1->STATE & 0x8) { return 4; }  // NACK: lost communication
  I2C1->TXDATA = 0x00;                 // write address to TX buffer
  while(!(I2C1->STATUS & (1 << 7)));   // wait for TX buffer to empty
  while(!((I2C1->STATE >> 4) == 0xD)); // wait for data ACK/NACK and bushold
  if(I2C1->STATE & 0x8) { return 4; }  // NACK: lost communication
  I2C1->CMD |= 0x1;                    // send Repeated START command
  I2C1->TXDATA = control_byte[1];      // write control byte[1] to TX buffer
  while(!(I2C1->STATUS & (1 << 7)));   // wait for TX buffer to empty
  while(!((I2C1->STATE >> 4) == 0x8)); // wait for address ACK/NACK (no bushold)
  for(i=0; i<(buffer_size-1); i++) {
    while(!(I2C1->STATUS & (1 << 8)));   // wait for data to arrive
    buffer[i] = I2C1->RXDATA;            // read rx data and store in 'buffer'
    I2C1->CMD |= (1 << 2);               // send ACK
    while(!((I2C1->STATE >> 4) == 0xC)); // wait for data ACK/NACK (no bushold)
  }
   
  while(!(I2C1->STATUS & (1 << 8)));    // wait for final byte to arrive
  buffer[buffer_size-1] = I2C1->RXDATA; // read rx data and store in 'buffer'
  I2C1->CMD |= 0x8;                     // send NACK
  while(!((I2C1->STATE >> 4) == 0xD));  // wait for data ACK/NACK and bushold
  I2C1->CMD |= 0x2;                     // send STOP command
  return 0;                             // successful transfer
}
 
// This function is used to check if the EEPROM is currently performing an
// internal write process. Returns 1 for busy, 0 for ready
uint8_t chip_is_busy(void) {
   
  while(I2C1->STATE & 0x1);            // wait for bus to become idle
  I2C1->CMD |= 0x1;                    // send START command
  while(!(I2C1->STATUS & (1 << 7)));   // wait for TX buffer to empty
  I2C1->TXDATA = 0xA0;                 // write control byte to TX buffer
  while(!(I2C1->STATUS & (1 << 7)));   // wait for TX buffer to empty
  while(!((I2C1->STATE >> 4) == 0x9)); // wait for address ACK/NACK and bushold
  if(I2C1->STATE & 0x8){ // check for ACK/NACK (only valid if BUSHOLD is set)
    I2C1->CMD |= 0x2;    // send STOP command
    return 1;            // NACK received
  }else{
    I2C1->CMD |= 0x2;    // send STOP command
    return 0;            // ACK received
  }
}
 
// This function erases a page (16 bytes) of memory at a time by writing each byte '0xFF'.
void block_erase(uint8_t memory_block) {
  uint8_t j;
  uint8_t page_data[16];
   
  // load array with 0xFF
  for(j=0; j<sizeof(page_data); j++) {
    page_data[j] = 0xFF;
  }
   
  // write the array to all 16 pages of the block
  for(j=0; j<16; j++) {
      write_page(memory_block, j, page_data, sizeof(page_data));
    }
}
 
// This function reads the specified memory block and displays the contents in the terminal window
void display_block(uint8_t memory_block) {
   
  // Header Strings
  char block_header[]   = "\n\rBlock ";
  char mem_data_header[] = "| Page | Address | Data |\n\r";
  char dash_line[]        = " ------ --------- ------\n\r";
   
  // Control variables
  uint8_t rx_buffer[BLOCK_SIZE];
  uint16_t i;
  uint8_t j;
  uint8_t k=16;
   
  uint8_t status = read_block(memory_block, rx_buffer, sizeof(rx_buffer));
  check_error_status(status);
   
  // Display contents of Block
  // Print Headers ///////////////////////////////////////////
  for(i=0; i<sizeof(block_header); i++) {
      print_byte(block_header[i]);
  }
  print_byte(memory_block + 0x30); // specify which block is being dumped
  print_byte('\n');
  print_byte('\r');
  for(i=0; i<sizeof(dash_line); i++) {
      print_byte(dash_line[i]);
  }
  for(i=0; i<sizeof(mem_data_header); i++) {
      print_byte(mem_data_header[i]);
  }
  for(i=0; i<sizeof(dash_line); i++) {
      print_byte(dash_line[i]);
  }
  ////////////////////////////////////////////////////////////
   
  for(i=0; i<(sizeof(rx_buffer)); i++) { // display block data
     
    print_byte('|');
    if(k == 16) {
      for(j=0; j<3; j++) {   // print 3 whitespaces
        print_byte(' ');
      }
      print_byte_setup(i/16); // Print page number
      for(j=2; j<4; j++) {
        print_byte(print_byte_array[j]);
      }
      print_byte(' ');
      k = 0; // reset k
    }else{
      for(j=0; j<6; j++) {   // print 6 whitespaces
        print_byte(' ');
      }
    }
     
    print_byte('|');
    for(j=0; j<6; j++) {   // print 6 whitespaces
      print_byte(' ');
    }
    print_byte_setup(i); // print address of memory
    for(j=2; j<4; j++) {
      print_byte(print_byte_array[j]);
    }
    print_byte(' ');
    print_byte('|');       // separate address and data
    for(j=0; j<3; j++) {   // print 3 whitespaces
      print_byte(' ');
    }
    print_byte_setup(rx_buffer[i]); // print data located at given address
    for(j=2; j<4; j++) {
      print_byte(print_byte_array[j]);
    }
    print_byte(' ');
    print_byte('|');
    print_byte('\n'); // print newline
    print_byte('\r');
    
    k++;
  }
  // close the table
  for(i=0; i<sizeof(dash_line); i++) {
      print_byte(dash_line[i]);
  }
}
 
// This function is used to display bytes over UART
void print_byte(uint8_t byte) {
  while( !(USART1->STATUS & (1 << 6)) ); // wait for TX buffer to empty
  USART1->TXDATA = byte;                 // send byte over UART
}
 
// This function breaks up 'byte' into high and low nibbles (ASCII characters) to be transmitted over UART
void print_byte_setup(uint8_t byte) {
                
  if (((byte & 0xF0) >> 4) <= 0x09) {                      // if high nibble is less than 0xA
    print_byte_array[2] = ((byte & 0xF0) >> 4) + 0x30;     // store ASCII char
  }
  if ((((byte & 0xF0) >> 4) >= 0x0A) && (((byte & 0xF0) >> 4)<= 0x0F)) { // if high nibble is between 0xA and 0xF
    print_byte_array[2] = ((byte & 0xF0) >> 4) + 0x37;     // store ASCII char
  }
  if ((byte & 0x0F) <= 0x09) {                             // if low nibble is less than 0xA
    print_byte_array[3] = (byte & 0x0F) + 0x30;            // store ASCII char
  }
  if (((byte & 0x0F) >= 0x0A) && ((byte & 0x0F)<= 0x0F)) { // if low nibble is between 0xA and 0xF
    print_byte_array[3] = (byte & 0x0F) + 0x37;            // store ASCII char
  }
}
 
void check_error_status(uint8_t status) {
  uint8_t i;
   
  switch(status) {
    case 0:
      // successful operation
      break;
    case 1:
      // unsupported block number
      for(i=0; i<sizeof(error_message1); i++) {
        print_byte(error_message1[i]);
      }
      break;
    case 2:
      // array size error
      for(i=0; i<sizeof(error_message2); i++) {
        print_byte(error_message2[i]);
      }
      break;
    case 3:
      // unsupported page number
      for(i=0; i<sizeof(error_message3); i++) {
        print_byte(error_message3[i]);
      }
      break;
    case 4:
      // NACK transmitted/received when expected ACK
      for(i=0; i<sizeof(error_message4); i++) {
        print_byte(error_message4[i]);
      }
      break;
    default:
      // unknown error
      for(i=0; i<sizeof(error_message5); i++) {
        print_byte(error_message5[i]);
      }
      break;
  }
}

作者からのコメント

上記の例にあるコードは自由に使用できます。このページで紹介されている例を基に、アプリケーションに機能を追加することをお勧めします。タイマーの例のコードを使って、複数のタイマーをカスケード接続してみてください。GPIOの例とタイマーの例を組み合わせて、ユーザーボタンのデバウンス機能を作ってみてください。異なるボーレートを設定したり、UART サンプルのソフトウェアバッファを設定してみてください。このページの目的は、EFM32 Giant Geckoマイクロコントローラの基本的な機能に慣れ親しみ、より複雑なアプリケーションに各ペリフェラルを適用できるようにすることです。お役に立てれば幸いです。皆さんがGiant Geckoマイクロコントローラを使いこなせるようになることを願っています。
Scott

ご質問/コメント

ご質問やご意見は、DigiKeyのTechForumまでお寄せください。




オリジナル・ソース(English)