Child pages
  • Getting started with Freescale's Freedom KL46Z Development Board
Skip to end of metadata
Go to start of metadata

Table of Contents

 

Introduction

This page will go over the process of starting a bare-metal project for Freescale's Freedom KL46Z Development (FRDM-KL46Z) board.  You are free to use any compiler, but we will use KEIL µVision version 5.18a (downloadable here).  This project will attempt to be as simple as possible, using very little outside code and is intended to be a basis for learning how to access and manage the peripherals of the board through register manipulation.  The end result will be a blinking LED.

For reference I will be using certain fonts or styles to denote specific things, shown in the following table

StyleMeaningExample
BoldExact wording or button

Click on Next to continue

ItalicsFilename or PathNow open My_File.c
MonospaceRegister namesSet the 9th bit in SIM_SCGC5

Helpful Links

Reference Information

Kinetis KL46 Reference Manual (this is the Microcontroller (MCU) that is on the dev board) KL46P121M48SF4RM.pdf

FRDM-KL46Z User's Manual FRDM-KL46Z_UM.pdf

FRDM-KL46Z Schematic FRDM-KL46Z_SCH.pdf

Tools

Freescale Freedom KL46Z (purchasable from Digi-Key)

MiniUSB cable (purchasable from Digi-Key)

Software

MDK-ARM which has Keil µVision version 5.18a from KEIL's download page

KL46_SC and FRDM-KL46Z_QSP from Freescale's Download page for the FRDM-KL46Z

P&E OpenSDA drivers from Pemicro

Installing FRDM-KL46Z driver interface

Start by plugging in the FRDM-KL46Z to the computer via the OpenSDA Mini-B connector (See to right, click to enlarge).  The FRDM-KL46Z will be powered by this.  The board comes with a mass-storage device Flash Programmer OpenSDA Application installed and will have a volume label FRDM-KL46Z.  Windows should install the Serial Port drivers automatically.  If it does not, open Device Manager, Locate and right-click on OpenSDA – CDC Serial Port.  Then select Update Driver Software, Browse and select the FRDM-KL46Z drive.  Click Next to complete the installation.

Last, drag and drop CMSIS-DAP_OpenSDA.S19 from FRDM_KL46Z_QSP/OpenSDA Applications into the FDRM-KL46Z drive in Bootloader mode.  To access the bootloader, unplug the usb from the board.  Hold down the reset button as you plug it back in.  After dragging in the file in.  Once that has copied, unplug the usb, and plug it back in without holding down the reset.  The device is now ready to run the CMSIS-DAP debugger.

Starting our project

We'll start this section by opening KEIL µVision5.  Our first order of business is to start a new project.  Go to Project>New µVision Project... then choose a directory and a name for your project.  I will be placing mine in C:\Users\Ethan Hettwer\Example_Project\Example_Project.uvproj.  I strongly recommend making a new folder for the project.

Next we need to choose a target device.  We are using the Freescale MKL46Z256xxx4 (in our case xxx = VLL, or the 100 pin package).  This can be found in Freescale Semiconductor > MKL46Z256xxx4.  Click OK, a list of software components will appear.  Check CMSIS > CORE and DEVICE > STARTUP, Click Resolve, and then OK.

Next we will need to add a c file.  To do this, right click on the Source Group 1 folder and click Add New Item to Group 'Source Group 1'....  This will allow us to create a new file to be included in the project.  As this file is intended to be holding code, it needs to be a C file.  Select this, then name it, I will be using main.c.  To enable components of the MCU we will be manipulating base registers.  To help with this, right click in the code window and select Insert '#include <MKL46Z4.H>.  This header file holds register names and "masks" which allow more intuitive use of those registers.  If you wish to look at the entirety of the file, right click on <MKL46Z4.H> and select Open Document <MKL46Z4.H>.  For the most part, the masks are named based on the KL46 reference manual in the form 'CHAPTER NAME'_'REGISTER NAME'_'BIT NAME'_MASK, all in caps.  An example of using masks to modify a register is shown below.  There are many ways of doing this both with and without masks, I will show a few and explain why some might be better than others.

 Example

Note the use of |= in this section.  It is a useful command that takes the first argument, logical ORs it with the second, then returns the value to the original.  This is used to set bits in a register without changing others.

//In order to allow PortA to be used we must first enable the clock to it.  The PortA clock enable is bit 9 of SIM_SCGC5.  All of the following lines of code do this, but it is much easier to see what is happening with some of them.
SIM->SCGC5 |= SIM_SCGC5_PORTA_MASK; //MKL46Z4.h defines SIM_SCGC5_PORTA_MASK as 0x200u, or 1 left shifted by 9.  Using this line makes it clear that we are enabling PORTA as we are setting the PORTA mask.
SIM->SCGC5 |= (1u << SIM_SCGC5_PORTA_SHIFT);  //This is very similar to the previous line, but it defines SIM_SCGC_PORTA_SHIFT as 9, or the number of the bit in the SIM_SCGC5 register.
SIM->SCGC5 |= (1u << 9);  //This one is harder to follow.  All we know is that we are setting the 9th bit of SIM_SCGC5, and an outside reader (or possibly you when you proofread your code) will have a hard time discerning what this line means.
SIM->SCGC5 |= (0x200u);  //This works, but it doesn't even tell what bit you're setting immediately.  It's hex, so it's in groups of 4 bits, which is its one saving grace.
SIM->SCGC5 |= (512u);  //This also works, but makes no sense.  Using a decimal number to describe which bit to set is unintuitive and almost impossible to read unless you're the author or the code is very well commented (at which point it's easier to just use the mask)

Masks can be useful for a multitude of reasons.  The first (as shown in the example) is readability.  It allows the reader to quickly understand what's happening.  By using a common naming scheme you can see what register and which bit in that register you are setting.  A second reason is portability.  Assuming perfectly written c code, by changing the header file you could port to another device seamlessly.

Messing with registers

Now we're going to start getting into the nitty gritty of the code. 

As I prefer to make my code modular and would rather make function calls than do it all line by line I will make a function to initialize my LEDs.  This can all be done in main, but leaves slightly more cluttered code.  To use one of the LEDs we only need a GPIO (General Purpose Input/Output) pin.  This involves using a couple of different registers, listed below (from the KL46 Reference Manual). 

The FRDM-KL46z User Manual is incorrect on which LED is on which port.  We will be blinking the green LED which is on PTD5.

  • System Integration Module (SIM) - This gives control of which peripherals are clocked.  By leaving them without access to the clock we save power, but leave them inoperable.
    • System Clock Gating Control Register 5 (SIM_SCGC5) - This register holds the clock enable for the various ports on the MCU.  IN ORDER TO USE ANY PINS ON A PORT THIS MUST BE ENABLED FOR THAT PORT.  For example, to use the red LED on pin PTE29 we must first enable the clock to Port E in SCGC5.
  • Port Control and Interrupts (PORT) - This register determines which pins are used for which peripheral.  Some of the power of the MCU is customization, and this is where that is done.  The pinout is located on page 172 of the KL46 Reference manual and shows what mode must be selected for a specific purpose.
    • Pin Control Register (PORTD_PCR5) - We will be using the green LED on the FRDM-KL46, so we will need PORTD (the port we're on), PCR5 (the pin we're on) and configure it to GPIO.
  • Gerneral-Purpose Input/Output (GPIO) - Once a pin is configured as GPIO this register determines whether it is an input or an output.  This register is also used to read from and write to GPIO pins.
    • Port Data Direction Register (GPIO_PDDR) - This register configures GPIO as inputs or outputs.  Pin number is the same as bit number,  0 is defined as input, 1 as output.  So to define PTD5 as an output we would set bit 5 of PTD.PDDR to 1.
    • Port Data Output Register (GPIO_PDOR) - This register will change the output of a pin to the value given.  So an input of 0 will change the bit to a 0, an input of 1 will change the bit to a 1.
    • Port Data Set Register (GPIO_PSOR) - This register will set the output of a pin.  An input of 0 will not change the current value of the pin, while a 1 will set the bit to a 1.
    • Port Data Clear Register (GPIO_PCOR) - This register will clear the output of a pin.  An input of 0 will not change the current value of the pin, while a 1 will clear the bit to a 0.
    • Port Data Toggle Register (GPIO_PTOR) - This register will toggle the output of a pin.  An input of 0 will not change the current value of the pin, while a 1 will change the bit to the inverse of its current state.
    • Port Data Input Register (GPIO_PDIR) - While we are not using this, it will return the value of an input port in a 32 bit format.  The value of a single pin can be obtained by taking that bit from this register.

So now that we have that, I'll throw some example code to show how I configured each of these registers.

LED Configuration
void InitLED(void){ //This function enables the red LED on the FRDM-KL46Z development board
//Note, each chapter from the Reference Manual is a structure, while each register is a member of that structure, so we address them by Chapter -> Register
	SIM->SCGC5 |= SIM_SCGC5_PORTD_MASK; //This enables the clock to PORTD
	PORTD->PCR[5] = PORT_PCR_MUX(1u); //This sets the Mux control of PTD5 to 001, or GPIO
	PTD->PDDR |= (1u<<5); //This sets PTD5 as an output.  There are no masks defined for each pin so we have to do it by hand and just comment well.
}

Now we can write the function to implement the blinking.  This can be done in a multitude of ways, which I will be showing below

3000000 is an arbitrary number for the wait loop.  You can make this larger or smaller to control blink interval.

Shown here is a picture of how how the LED is wired from the schematic.  This means that when the MCU pin goes logic low, the LED is on.  When trying to make the LED blink this doesn't really matter, as all we want is for it to switch, but this could be important for other applications.
Blink Method 1
void BlinkLED1(void){//This method turns the LED off, then back on using two separate commands.
	uint32_t i = 0; //Create a loop variable
	
	PTD->PSOR = (1u<<5); //Set PTD5 = 1, turns LED OFF (Cathode connected to PTD5)
	for(i; i < 3000000; i++){}; //Burn some time
	PTD->PCOR = (1u<<5); //Clear PTD5 = 0, turns LED ON
	for(i; i < 3000000; i++){}; //Burn some time
}
Blink Method 2
void BlinkLED2(void){//This method turns the LED off, then back on using PDOR
	//Note the use of |= and &=.  This allows you to change the 5th bit of PDOR without disturbing the other bits.  The other methods do not require this as setting PSOR/PCOR/PTOR = 0 does not change the output.
	uint32_t i = 0; //Create a loop variable
	
	PTD->PDOR |= (1u<<5); //Change PTD5 to 1, turns LED OFF (Cathode connected to PTD5)
	for(i; i < 3000000; i++){}; //Burn some time
	PTD->PDOR &= ~((uint32_t)(1u<<5)); //Change PTD5 to 0, turns LED ON.  This line is a bit weird.  First it takes 1u<<5 and typecasts ( http://en.wikipedia.org/wiki/Type_conversion ) it to a 32 bit value.  It then takes the inverse of it, so all bits are 1 except bit 5.  It then logical ands it with the current PDOR, which will retain all values except bit 5 which will be changed to 0.
	for(i; i < 3000000; i++){}; //Burn some time
}
Blink Method 3
void BlinkLED3(void){//This method turns the LED off, then back on using a single command.  It must be run twice in order to blink the LED.
	uint32_t i = 0; //Create a loop variable
	
	PTD->PTOR = (1u<<5); //Toggles PTD5 and the LED
	for(i; i < 3000000; i++){}; //Burn some time
}

The last thing we need for code is an actual main function.  We will keep everything in a while loop to make sure we never leave main as that could cause undefined behavior.

Main
int main(void){
	InitLED(); //Initialize LED for use
	while(1){//Main loop
		BlinkLED3(); //I prefer the third method.  All the command does is toggle the LED, which is exactly what we want to do.  All of the above methods will work.
	} //End main loop
}//End main

This is it for the code that we need.

Compiling, programming and debugging

To program our MCU we need to choose our debug option.  To do this, click on the Options for Target... button in the menu bar, go to the Debug tab and select CMSIS-DAP Debugger, then OK.

To compile our program and load it we click on Rebuild in the top menu bar then the Download button.  The green status light on the FRDM-KL46Z should light up, then go dark, and your LED should be blinking. 

That's it!  If something isn't working there are a few places to look. first, check your registry initialization.  Double check to make sure you enabled the clock to Port D, set PTD5 MUX to GPIO, then set the GPIO pin to output

Next, check the blink function you've chosen.  Remember that clearing the Port Data Output Register (PDOR) turns the LED ON.  If you're just testing and trying to turn the LED on, make sure you're setting the register to 0, not 1.  If you are using the PCOR command you must place a 1 in the bit you want to clear.  This seems counterintuitive, but it's just telling the computer which bit to clear, not what to put there.

Another issue could be based upon the MKL46Z4.h file.  KEIL MDK-ARM V5 comes with one version of MKL46Z4.h, while the FRDM starter code comes with another.  This tutorial was written for the version that comes default with KEIL. In order to make it compatible with the version in the starter code, the way we access registers changes.  The new revision of MKL46Z4.h targets registers differently, and I'll list some of the changes below:

In general, we replace -> with an underscore.  This changes SIM->SCGC5 to SIM_SCGC5.  For all GPIO changes, such as PTD->PSOR we change it to GPIOD_PSOR.  It also allows new variable masks.  For example, when we wanted to clear PTD5, we would have used GPIOD_PCOR = (1u << 5) before.  Now we can use GPIO_PCOR_PTCO(x), where x is the bit we want to clear.  This does not change the functionality of the code, but can make it more readable and portable (see above for use of masks).

What now?

This is a start for getting used to register manipulation on the KL46Z, and is just the beginning.  I recommend you try to get the red LED on PTE20 working in a similar fashion, to test the understanding of the tutorial here.  It involves the same steps, but using Port E instead of Port D, and pin 20 instead of pin 5.

Another tutorial is available for getting the LCD Display working available here Using the Segment LCD Controller on the Kinetis KL46.

Some other general tips for coding

  • Use comments liberally!  The compiler removes them all, so it doesn't increase the size of your code, but drastically improves readability.  Label what each function does, or what a line of code is accomplishing.  It also helps in debugging to go through and check what you want the line of code to do vs. what it actually does.  It's very easy to make a mistake like expecting GPIOD_PSOR to turn an LED on, as we're setting the pin high. 
  • USE THE DEBUGGER.  Often setting breakpoints in code is very helpful to find where an error is!  If the code locks up, using the debugger to find the error is often key, and very helpful.
  • Try to avoid hardcoding.  Above where I've used something like "PTD->PTOR = (1u<<5)" I might be better served making a variable at the start of my code called "Green_LED_Pin" and setting it to 5.  In this way I can make the line of code "PTD->PTOR = (1u<<Green_LED_Pin)".  By doing this I 1) Increase readability (are you sensing a theme?) and 2)  allow a rapid change.  In this application the pin we're using is static, but in other applications we can change which pin we use.  If we use a variable, all we have to do is change the value of that variable and we change the pin we're on instead of having to go through and check every line of code.

Contact and Other Info

 I hope you found this article helpful and insightful.  If you have any issues or think something should be added to this article please contact me at eewiki@digikey.com

A thank you to our sponsor at digikey.com.  If you want to help out the EE wiki, click a link or two to the digikey website or just browse around other EEwiki articles.  Continued interest helps show that the EEwiki is a valuable tool and worth supporting. 

 -Ethan

 

 

 

  • No labels