Created by Scott Schmit, last modified on Sep 18, 2014
Purpose
This page is meant to help you get started with Giant Gecko microcontrollers which are part of the EFM32 device family from Silicon Labs. The EFM32 family of microcontrollers are some of the lowest power 32-bit microcontrollers available. The Giant Gecko device family implements an ARM Cortex-M3 core and is available with up to 1024KB program (flash) memory, up to 128KB RAM, and CPU speeds up to 48MHz. This page will help you start a blank Giant Gecko project using IAR Embedded Workbench and provide some example code to familiarize you with the various peripherals available with this microcontroller at the register level. It also provides example code using emlib functions. The EFM32GG-STK3700 was used as the development platform for these examples.
Quick Links
Purchase Links
- EFM32GGxxx ARM Cortex-M3 devices
- EFM32GG-STK3700 - Giant Gecko Starter Kit
Reference Documentation
- Splash Page for ARM microcontrollers from SiLabs - Provides overview of the entire ARM portfolio from Silicon Labs.
- EFM32GG Reference Manual EFM32GG-RefMan.pdf (11.1 MB) - Provides register details and descriptions for each peripheral.
- EFM32GG990 Datasheet EFM32GG990_Datasheet.pdf (1.7 MB) - Provides electrical characteristics, pin diagrams/descriptions, and memory map for the EFM32GG990 device specifically.
- EFM32 Giant Gecko Starter Kit Board Schematic BRD2200A_A03_schematic.pdf (506.7 KB)
- EFM32 emlib API - Online documentation for emlib functions
- AN0009 EFM32 Getting Started AN0009_EFM32_Getting_Started.pdf (783.7 KB) - App note that provides background on EFM32 devices, Simplicity Studio, and how to use the emlib libraries.
Downloads
- Simplicity Studio - Provides extensive documentation for all EFM32 devices and example code using emlib libraries. Also performs energy analysis. Latest version of Simplicity Studio includes a code development environment.
- IAR Embedded Workbench - Firmware development environment (CD also included with kit). Time-limited licenses, or code-limited licenses are available for free. The 32KB code-limited version can be used for the examples found on this page.
- Keil uVision5 - Firmware development environment. Similar free licenses are also available for this software.
Create an IAR Template Project for EFM32
Silicon Labs recommends using Simplicity Studio to locate example code and documentation. They recommend cloning existing projects for IAR instead of starting a blank IAR project from scratch. This is because all of the App Notes and example code written for these micros use a hierarchy that is non-typical for IAR projects. The examples also setup your include file paths and setup your debug link for you. You can then use CMSIS and em_lib libraries to call functions from your main.c file to develop your application.
Although it’s not necessarily recommended, you can start a blank IAR project by using the following procedure. This procedure parallels the example found here. However, I setup my project in a unique project directory rather than the default location used by Simplicity Studio.
Procedure for Starting a Template IAR Project for EFM32
This procedure outlines how to start a blank IAR project and setup the necessary project settings. However, we will use this process to setup a template project. Once we have a template created, we can simply copy&paste and rename the project whenever we wish to start a new EFM32 Giant Gecko project. Even if you don’t want to use the emlib libraries, you should still follow all of these steps to properly setup your project.
-
Open IAR and navigate to Project → Create New Project
-
In the “Create New Project” dialog box, ensure ‘ARM’ is selected as the Toolchain. Select ‘Empty Project’ and click ‘OK’.
-
Now a “Save As” dialog should pop up and you can select a location for your project.
Silicon Labs recommends placing each of your projects in the application note (an) directory that gets created when you install Simplicity Studio. However, we are going to create a unique location for all of our EFM32 projects.
For my personal use, I created a “My_EFM32_Projects” folder within an “Energy Micro” folder on my desktop. This is where I store all of my new EFM32 projects. For this demo, since we are using a Giant Gecko microcontroller, inside the “My_EFM32_Projects” folder, I’ve created a folder called “Template_EFM32GG”. You can name it whatever you wish. However, I like to be able to sort my projects by micro type. It’s an easy identifier for me to recognize which specific micro I used for that project if I include it in the name of the project folder. Alternatively, you could create another folder for individual micros if you wanted, but I like to keep my folder count down if possible. My project directory now has the following hierarchy:
→ “Energy Micro” → “My_EFM32_Projects” → “Template_EFM32GG”
Once you have your project directory setup, create an “iar” folder and save the project file as “Template_EFM32GG.ewp”.
-
Now that your project has been created, you can add some groups to the project. Right-click the project in the workspace browser and select ‘Add Group’.
-
Let’s name the first group ‘source’. This is standard for all EFM32 projects.
-
Now we can create our ‘main.c’ file. Navigate to File → New → File
-
An untitled file should open. Select File → Save As
-
Navigate to your project directory (one level above the ‘iar’ folder you saved the project file in) and save the file as ‘main.c’. In my case the project directory is: → “Energy Micro” → “My_EFM32_Projects” → “Template_EFM32GG”
-
Now that the ‘main.c’ file has been created, we have to actually add it to our workspace. Right-click on the ‘source’ group you created and select Add → Add Files.
-
Select the ‘main.c’ file you just created.
-
Add two more groups to your workspace and call them ‘CMSIS’ and ‘emlib’. This is standard for EFM32 projects.
-
Right-click the ‘CMSIS’ group and select Add → Add File.
-
When Simplicity Studio is installed, it saves all documentation as well as CMSIS, emlib, and device files in: C:\Users<user name>\AppData\Roaming\energymicro
In the ‘energymicro’ folder, navigate to Device → EnergyMicro → EFM32GG → Source (if you aren’t using a Giant Gecko, navigate to the appropriate EFM32xx folder).Select the ‘system_efm32gg.c’ file and click ‘Open’ to add the file to your workspace. This file is included for all standard EFM32 projects.
-
Right-click the ‘CMSIS’ group and again select Add → Add File. Navigate to C:\Users<user name>\App Data\Roaming\energymicro\Device\EnergyMicro\EFM32GG\Source\IAR and open the ‘startup_efm32gg.s’ file to add it to your workspace. This file is needed in order to properly program the device.
-
Now you can add source files to the ‘emlib’ group that you previously created. You can add only the ones you need, or add all of them if you wish. Navigate to the ‘emlib’ folder which is located at C:\Users<user name>\AppData\Roaming\energymicro.
Inside the ‘emlib’ folder you can open the ‘src’ folder and add any or all of the peripheral emlib libraries. I would recommend adding all of them to your workspace for you to easily browse through the available functions. That way you can view any file without having to navigate to each one using windows explorer. Use ‘Ctrl’+‘left mouse button’ to select individual files and click ‘Open’ to add them to your workspace.
Note: Even if you don’t want to use the emlib libraries, you should still add the ‘em_system.c’ file to the emlib group so that you can call the Chip_Init() function at the beginning of your ‘main.c’ file. Chip_Init() addresses any known errata for your device and applies any known software fixes.
-
In order to use the functions located in these emlib files, you need to add #include for the respective header file in your ‘main.c’ file. For example, if you wanted to use any of the prewritten ADC functions in your ‘main.c’ file, you would need to include the ‘em_adc.h’ header file as shown in the following example. ‘em_system.h’ and ‘em_chip.h’ should be added to every EFM32 project. You can also add a blank main() function to your ‘main.c’ file.
-
Now let’s setup the project options. Right-click on “Template_EFM32GG.ewp” in the workspace explorer and select ‘Options’.
-
In the ‘General Options’ category, under the ‘Target’ tab, make sure the ‘Device’ button is selected and click the ‘list’ icon on the right. Navigate to EnergyMicro and select your specific device. For this demo, I selected EFM32GG990F1024 which is loaded on the EFM32GG Starter Kit.
-
In the ‘C/C++ Compiler Settings’ category, under the ‘Preprocessor’ tab, you can add include directories for your device. You will need to add the paths to the include files for the CMSIS library and the emlib library if you wish to use the emlib API (recommended). You will also need to link to the ‘Include’ folder for the specific device. Example projects found in Energy Micro app notes use relative paths from the project directory to link to these directories. You can use hard-coded paths if you know the location of these won’t change. For my personal use, I hard code the paths directly in the Project Options (rather than using relative path). Silicon Labs recommends you use relative paths for portability. It’s completely up to you, but you need to always be aware of where the files are located and how you are linking to them.
In the ‘Defined symbols’ box, you also need to define your device symbol. For my project, I’ve defined the symbol EFM32GG990F1024. Here’s an example of hard-coding the path to the default install directory.
-
In the ‘Output Converter’ category, check ‘Generate additional output’ and select ‘binary’ for output format.
-
In the ‘Linker’ category, select ‘Override default’ and navigate to the .icf file for your device. The file should be located in the IAR install directory, not the Simplicity Studio install directory:
C:\Program Files (x86)\IAR Systems\Embedded Workbench 6.x\arm\config\linker\EnergyMicro.
-
In the ‘Debugger’ category, under the ‘Setup’ tab, select ‘J-Link/J-Trace’ as the Driver since we are using the J-link debugger included on the EFM32GG Starter Kit. If you are using a different debugger, these settings may change.
-
Still in the ‘Debugger’ category, under the ‘Download’ tab, check the ‘Verify download’ and ‘Use flash loader(s)’ boxes.
-
In the ‘J-Link’ category, under the ‘Connection’ tab, select ‘SWD’ as the Interface. Click ‘OK’ to apply the project options.
25. Select File → Save All. Save the workspace in the ‘iar’ folder within your project directory.
-
Right click on the project and click ‘Make’ to build the project
-
Now we have a template project with all of the necessary project settings. Whenever we wish to start a new EFM32 project for Giant Gecko, we can simply copy&paste the template project in whatever location we desire because we used hard-coded include paths. Once you paste a copy of the template, simply rename the project folder to whatever you wish. Also, I would recommend renaming the workspace file to match the project name. All other files can keep “Template” in the file name and the project will still build properly.
You are on your way! Now let’s look at some code examples…
Code Examples
The following examples were written using IAR embedded work bench with a 32KB size-limited free license. They are meant to help familiarize you with basic functionality of EFM32 Giant Gecko microcontrollers. For all of the following projects, I followed the procedure above for creating a new project (copy&paste created template). The examples are presented in order of difficulty and some build on previous examples. If you are brand new to EFM32 microcontrollers, I would recommend starting from the beginning and going through each of the examples in order. If you already have some experience with EFM32 microcontrollers, feel free to jump to any example you wish.
GPIO Example
This example demonstrates how to set pins as inputs with pull-up resistors enabled, as well as how to set and clear digital outputs. It also demonstrates how to adjust the drive strength of digital outputs. The EFM32GG allows the user to set the drive strength of a digital output with the following values:
STANDARD - 6mA drive current
LOWEST - 0.5mA drive current
HIGH - 20mA drive current
LOW - 2mA drive current
However, the EFM32GG Starter Kit uses 3 kOhm resistors which limits LED current to a max of about 1mA. Therefore, the HIGH and LOW drive settings show no effect. The LOWEST drive mode does enable the user to see visible change in LED intensity as a direct result of the drive mode setting. Therefore, LOWEST was used in the following example.
In the following example, LED0 is on by default with a standard drive strength of 6mA. LED1 is off by default. Pressing PB0 toggles LED1 and pressing PB1 toggles LED0. LED1 uses the lowest drive mode setting of 0.5mA. Even though the drive strength of LED0 is set to 6mA, the current limiting resistors reduce the current to approximately 1mA through LED0.
GPIO Example Using Direct Register Access
- The following code demonstrates how basic GPIO functionality using direct register access. For the following code to work, the ‘em_system.c’ file needs to be added to the workspace.
#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
}
}
}
GPIO Example Using emlib API
- The following code demonstrates how the emlib libraries can be used to implement the same GPIO example. For the following code to work, the following emlib source files must be added to the workspace:
- 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
}
}
}
Timer with Interrupt
The following code uses a timer with interrupt to blink an LED. It uses the default clock source which is the internal high speed RC oscillator. It sets the RC oscillator to 1MHz and uses a timer to interrupt every millisecond. After 500 ms, it toggles LED0.
- Timer with Interrupt Using Direct Register Access
The following code demonstrates how a basic timer can be implemented using direct register access. For the following code to work, the ‘em_system.c’ file needs to be added to the workspace.
#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
}
}
}
- Timer with Interrupt Using emlib API
The following code demonstrates how the emlib libraries can be used to implement the same Timer with Interrupt example. For the following code to work, the following emlib source files must be added to the workspace:
- 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
}
}
}
Using the HF XTAL Oscillator
The following example is similar to the Timer with Interrupt example shown above. However, this example uses the on-board 48MHz crystal as the High Frequency Clock source. The code uses the HF clock divider to set the HF clock rate to 24MHz. The High Frequency Peripheral clock and the High Frequency Core clock prescalers were left at /1. Therefore, the Timer, GPIO peripherals along with the Core clocks all run at 24MHz in this example. The TOP value of the counter was set to 24000 so that the overflow interrupt occurs every millisecond. Every 500 ms, LED0 toggles.
HF XTAL Example Using Direct Register Access
- The following code block shows exactly which registers to write to in order to properly configure and use the HF XTAL oscillator. For the following code to work, the ‘em_system.c’ file needs to be added to the workspace.
#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
}
}
}
HF XTAL Example Using emlib API
- The following code block achieves the exact same functionality as the above example using emlib functions. The following files need to be added to the workspace for the code to work:
- 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 Example
This example demonstrates how to setup PWM output on the EFM32 Giant Gecko. It uses Capture/Compare Channel 2 of Timer3 which is available on PortE, Pin2 of the microcontroller. You may remember from the “GPIO Example” above, PE2 is connected externally to LED0 on the EFM32 Giant Gecko Starter Kit. Therefore, we can use PWM output to control the intensity of LED0.
This example uses 2 different timers. Timer3 was used as the PWM generator. The PWM period was set to 1ms (frequency of 1kHz) and the duty cycle was adjusted by updating the output compare register at a defined UPDATE_PERIOD. The second timer – Timer0 – was used to generate the update period of 250ms. This example uses Timer0 with its overflow interrupt just like in the “Timer with Interrupt” example above.
Whenever the Update Period is reached the intensity of LED0 is updated with the new compare value and LED1 toggles.
The system clock for this example was the High Frequency RC Oscillator, set to 1MHz. The High Frequency Peripheral clock was used as the source for Timer0 and Timer3 with a prescaler of /1 for each timer. Therefore, Timer0, Timer3, and the System Clock all share the same frequency. The TOP value of each timer was set to 1000, so the overflow interrupt period for Timer0 and the PWM period for Timer3 were both 1ms (1kHz freq).
PWM Example Using Direct Register Access
The following code block demonstrates how to achieve LED intensity control using PWM output functionality of Timer3 with direct register access. For the following code to work, the ‘em_system.c’ file needs to be added to the workspace.
#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
}
}
PWM Example Using emlib API
The following code block demonstrates LED0 intensity control using PWM output capability of Timer3 (channel 2). The following files need to be added to the workspace for the code to work:
- 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 Example
This example shows how to setup the USART in asynchronous mode (essentially a UART) for basic serial communication. UART communication is very popular in embedded designs due to the low number of required pins (Transmit Data line, Receive Data line, and Ground). There are even some “single-wire” UARTs available (shared TX/RX line), however these still require a common ground to operate properly. UARTs work asynchronously, meaning there is no dedicated clock line between the 2 devices. Therefore, each device needs to have a predefined matching data rate and frame format to actually communicate.
The data rate is defined by the clock for each individual device. Any error in the clock rate will effect the baud rate of the UART and could cause loss of sync between the devices. Because of this, it’s normally recommended to use a high accuracy clock source such as a crystal oscillator. An RC oscillator may work, although it should involve some sort of calibration process to increase accuracy.
For this demo, the Giant Gecko was setup to use the 48MHz high frequency crystal oscillator. The HF clock devider was set to /2 to achieve a system clock of 24 MHz just like in the “HF XTAL Oscillator” example above.
For this demo, the Giant Gecko UART port was setup with the following serial communication settings:
- Baud Rate (bps): 38400
- Data Bits: 8
- Parity: none
- Stop Bits: 1
The code examples found below were tested using a custom expansion board which has USART1 available on an external connector. The custom expansion board was connected to a PC using the TTL-232R-3V3 smart cable from FTDI. A terminal emulator program (Tera Term) was used to display characters transmitted to and from the Giant Gecko. The serial port of the terminal program was setup with the following settings:
- Baud Rate (bps): 38400
- Data Bits: 8
- Parity: none
- Stop Bits: 1
- Flow Control: none
The following figure shows the printed characters in the terminal window while running the applications found below.
UART Example Using Direct Register Access
- The following example code shows how to use direct register access to implement this simple UART application. For the following code to work, the ‘em_system.c’ file needs to be added to the workspace.
#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
}
}
}
}
UART Example Using emlib API
- The following example code shows how to implement the UART example using emlib functions. For the following code to work, the following files need to be added to the workspace:
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 Example
This example shows how to setup the Analog to Digital Converter (ADC) in the Giant Gecko using a custom expansion board that interfaces with the STK3700.
A potentiometer loaded on the expansion board was used as the analog source. The potentiometer provided a controllable analog voltage source varying between 0~3.3V with the center tap connected to PD6 (ADC0 Channel 6) of the Giant Gecko. Therefore, VDD (3.3V) was used as the reference voltage of the ADC. The ADC was setup in single ended mode and provided single conversions at a predefined interval. The results were set to 12-bit, right-justified within the 16-bit result register. The ADC clock was set to 12MHz with x2 oversampling enabled. An input filter was not implemented for this demo.
After the ADC completed each conversion, USART1 was used to transmit the results to a terminal program on a PC. The USART was setup in asynchronous mode with the same serial communication settings as the “UART Example” shown above.
To provide a high accuracy clock for UART communication, the 48MHz crystal oscillator was used with a /2 prescaler to generate a 24MHz System Clock, just like the “HF XTAL Oscillator” example above.
Timer0 was used as a general purpose timer to trigger single ADC conversions every 500ms, similar to the “Timer with Interrupt” example above. The potentiometer value was updated onscreen every half second.
Below is a screenshot of the terminal window while running this demo application.
ADC Example Using Direct Register Access
- The following example code demonstrate how to setup the ADC in single conversion mode using direct register access. For the following code to work, the ‘em_system.c’ file needs to be added to the workspace.
#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 Example
This example shows how to setup the Serial Peripheral Interface (SPI) in the Giant Gecko using a custom expansion board that interfaces with the STK3700.
A flash memory IC (W25X20CL) loaded on the expansion board was used as the SPI target. The flash IC SPI port (MOSI, MISO, SCLK, CS) was connected to USART1 (Location #1) through PortD - PD[3:0]. USART1 was configured in synchronous (SPI) master mode with the SCLK idle low, data setup on trailing edge of SCLK, and data sampled on the rising edge of SCLK. The USART1 clock divider was left at 0 which generated a SCLK frequency of 12MHz. Due to the requirements of the flash IC, USART1 was setup to transmit msb first and the CS pin was handled manually (although the Giant Gecko does offer automatic CS functionality). The flash IC also has active low “Hold” and “Write Protect” pins connected to the Giant Gecko. However, these pins are not used in this example, except to drive the “Hold” pin high and the “Write Protect” pin low.
For this demo, a simple ‘Device ID’ command was sent to the flash IC which returns a 2-byte response – the Manufacturer ID and the Device ID. For the W24X20CL, these values were 0xEF and 0x11, respectively.
After the response was received, USART0 was used to transmit the response to a terminal program on a PC. USART0 was setup in asynchronous mode with the same serial communication settings as the “UART Example” shown above. However, for this example, PC0 (USART0 Location #5) was used as the transmit pin. The receive pin at Location #5 is not available through the expansion port of the STK3700 while in full duplex mode. This isn’t a problem since we are simply transmitting results to the PC, not entering values from the PC. USART0 TX pin and GND were connected to the RX and GND pins of TTL-232R-3V3 smart cable from FTDI which was then connected to the USB port of the PC.
To provide a high accuracy clock for UART communication, the 48MHz crystal oscillator was used with a /2 prescaler to generate a 24MHz System Clock, just like the “HF XTAL Oscillator” example above.
Below is a screenshot of the terminal window while running this demo application.
SPI Example Using Direct Register Access
- The following example code demonstrate how to setup SPI communication using direct register access. For the following code to work, the ‘em_system.c’ file needs to be added to the workspace.
#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 Example
This example shows how to setup the Inter-Integrated Circuit (I2C) peripheral in the Giant Gecko using a custom expansion board that interfaces with the STK3700.
An EEPROM memory IC loaded on the expansion board was used as the I2C slave device. The EEPROM was connected to I2C1 (Location #0) through PortC - PC[5:4]. I2C1 was configured in master mode using standard speed (100kHz). The auto-acknowledge feature of the Giant Gecko I2C was used for this demo to simplify the code. The EEPROM also has an active high “Write Protect” pin connected to PC3 which needs to be driven low to enable memory writes.
The example code below includes multiple functions involving interaction with the EEPROM:
- read_byte() - reads the contents of the specified address in memory
- write_byte() - writes a byte of data to a specific address in memory
- read_block() - performs a sequential read of an entire block (256 bytes) of memory
- write_page() - writes up to 16 bytes of memory in a single I2C transaction
- chip_is_busy() - checks if the EEPROM is able to communicate
USART1 was setup to print responses in a terminal program. USART1 was setup in asynchronous mode at Location #1 with the same serial settings as the examples above. USART1 was connected to the PC using TTL-232R-3V3 smart cable from FTDI.
To provide a high accuracy clock for UART communication, the 48MHz crystal oscillator was used with a /2 prescaler to generate a 24MHz System Clock, just like the “HF XTAL Oscillator” example above.
Below is a screenshot of the terminal window while running this demo application. The following screenshot shows a portion of Block 1 after the write_byte() function was used to write 0x23 to address 0x02 of Block 1. The page_write() function was used to write 0x00 to each byte of Page 1 as seen in the terminal window below.
I2C Example Using Direct Register Access
- The following code block demonstrates I2C communication using direct register access. For the following code to work, the ‘em_system.c’ file needs to be added to the workspace.
// 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;
}
}
Comments from the Author
The code found in the examples above can be used freely. I encourage you to build on the examples presented on this page and add functionality to the applications. Take the Timer example code and try cascading multiple timers together. See if you can combine the GPIO example and the Timer example to create a debounce function for the user buttons. Try configuring different baud rates or setting up a software buffer for the UART example. The goal of this page is to familiarize you with basic functions of EFM32 Giant Gecko microcontrollers so that you can apply each peripheral in more complex applications. I hope you find it useful. I hope you are successful with Giant Gecko microcontrollers.
- Scott
Questions/Comments
Any questions or comments please go to DigiKey’s TechForum