Tuesday, 8 April 2014

STM32F100RBT6 (ARM Cortex M3) Programming Tutorial - Blinking LED

We want to blink LED LD4, which are connected to PC8 pin of STM32F100RBT6. So we need to configure this pin as output. At this moment, You need to download from STMicro website document called RM0041. In this Reference Manual are described all peripherals of STM32F100 microcontrollers. Because this document has over 650 pages it impossible to copy all informations to this website. So I will reference to this document and suitable chapters concerning peripherals used in this tutorial. I will not reference to exact page, because after updating document by ST Micro page numbers can be not actual. I will be referenced to sections, figures and tables.
RCC configuration

Look at section 6 of 
RM0041, that concern about RCC (Reset and Clock Control) peripheral. In section 6.2 Figure 8 shows clock tree on STM32F100 low and medium density microcontrollers. Looks complicated, but almost all configurations are done by SystemInit function from system_stm32f10x.c file. Clocks will be configured to obtain specified core speed. Remember that - after reset all peripherals have disabled clock! So we need enable clock for every used peripheral. Go to section 6.3.7 of Reference Manual. This section describes APB2ENR register of RCC peripheral. This register is responsible for enabling clock signal for peripherals working on APB2 bus. All GPIO peripherals works on APB2 bus, so this register are interesting for us. Bits 2 to 8 are used to enable clock for each GPIO port (form GPIOA to GPIOG). We ned use only GPIOC port, so we need set bit number 4 of APB2ENR register. How do it? Probably You think about (1 << 4)? Right? That is wrong! Open document stm32f10x.h and search for bit name - "IOPCEN". You should find some macro definitions, which one from them is:
#define RCC_APB2ENR_IOPCEN ((uint32_t)0x00000010) /*!< I/O port C clock enable */
What is it? It's a bitmask for IOPCEN bit! So we don't need bitwise shifting one by pin number, we can use suitable bitmask! So, how will be looks first code line of us application? Probably something like that:
#include "stm32f10x.h"
int main(void)
{
RCC_APB2ENR  | = RCC_APB2ENR_IOPCEN;
do{
}while(1);
}
But this is wrong. After compiling application, compiler returns following error:
main.c(14): error: #20: identifier "RCC_APB2ENR" is undefined
Why? We use register name from Reference Manual, so why it is wrong? This is wrong, because all peripherals are divided into structures that hold peripherals registers. These structures are defined in stm32f10x.h file. So go to this file and search for "Peripheral_registers_structures" string. You will see some of typedefs describing structures for all of peripherals. Each structure holds all registers of peripheral. Naming scheme of peripheral structures are following: PeriphName_TypeDef. So for RCC peripheral structure definitions with registers are "RCC_TypeDef". So let's search for "RCC_TypeDef" string. We see, that registers name are slightly different from names from Reference Manual. Names in structure are without part of peripheral name. Register described in Reference Manual as RCC_APB2ENR in structure has name APB2ENR. OK, now we need name of structure variable. So let search again for RCC_TypeDef string. You should find following line:
#define RCC ((RCC_TypeDef *) RCC_BASE)
Now all are clear! In stm32f10x.h file is defined macro RCC that in fact are pointer dereference to our RCC_TypeDef structure that resides at RCC_BASE address. If you search for RCC_BASE string, you find another macro, that defines RCC_BASE as base address of RCC peripheral in STM32F100 memory space. So now, we can use RCC macro as pointer to structure, that holds all register of RCC peripheral. Now we can write correct code for set IOPCEN bit in APB2ENR register of RCC peripheral:
#include "stm32f10x.h"
int main(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
do{
}while(1);
}

Try to build application. Hooray! No errors! We have enabled clock for GPIOC peripheral and we can configure GPIO for driving LED.
GPIO configuration

Now, go to Section 7 of RM0041. As you can read, each GPIO port has several registers to configure and control input and output state of microcontroller pins. Most important information for us is that each GPIO port has two 32-bit configuration registers: CRL and CRH. CRL register is responsible for configuration of pins from 0 to 7 and CRH register is responsible for configuration of pins form 8 to 15. So for each GPIO pin we have four configuration bits. Table 16 from RM0041 shows all possible configurations of GPIO pins. Because configuration registers are slightly complicated, I prepared some macros for easier configuring GPIO ports on STM32 microcontrollers. With this macros pin configuration are easy and clear to read. These macros are in antilib_gpio.h file. Download this file, and save into folder with your application project. Example configuration for PC8 pin that works as output looks like that:
GPIOC->CRH = (GPIOC->CRH & CONFMASKH(8)) | GPIOPINCONFH(8, GPIOCONF(GPIO_MODE_OUTPUT2MHz, GPIO_CNF_OUTPUT_PUSHPULL));

Let me describe each element of this code line. GPIO->CRH mean of course access to GPIOC_CRH register. We want change configuration only for PC8, so remaining bits of this register should be unchanged. We realize that by reading contents of GPIO_CRH register and clear bits responsible for PC8 configuration. Macro CONFMASKH (8) give bitmask for clearing 4 lowest bits: 0xFFFFFFF0. Clearing this 4 bits will be done by bitwise AND of register with this bitmask. Next, we need to set configuration bits for PC8 suitable for driving LED. Output should be work as push-pull output. And what is this megahertz? This specified slew rate on outputs pin. For driving LED we don’t need fast edges of driving signals, so we use slowest slew ratio for PC8.
Controlling output state of GPIO pins can be realized on two ways: by writing port value to ODR (Output Data Register) register or by writing to BSRR (Bit Set/Reset Register) or BRR (Bit Reset Register). Writing to ODR register modifies value of all pins of given GPIO port. Writing to BSRR/BRR registers modifies state only this bits, that writing value has ones on bits position. For example to set bit PC8 we can use following code:
GPIOC->BSRR = (1 << 8);
After execution of this line bit number 8 (and only this bit) of GPIOC will be set. Other bits remain unchanged. Upper 16 bits of BSRR register can be used to clearing pin state:
GPIOC->BSRR = (1 << 24);
After execution of this line bit number 8 of GPIOC will be cleared. To resetting pin state we can use BRR register too:
GPIOC->BRR = (1 << 8);
After execution of this line, bit number 8 will be cleared. If we want clear single bit using ODR register, we must perform bitwise logical operations in Read-Modify-Write scheme:
GPIOC->ODR = GPIOC->ODR | (1 << 8);
but this operation is not atomic! After reading ODR state, his value can be changed (in interrupt for example), and after write modified value we can lose this change. So better use atomic operations with BSRR / BRR register.

Finally, let's blink the LED!
After getting all informations from above discussion, we can write our blinking LED application.
How to make a delay?
Simplest way to make a delay (let's call 'monkey delay') is loop counting some many times. Time of this delay it's hard to define. Especially on ARM devices counting how CPU cycles loop will be executed is difficult. Let's define variable named dly:
volatile uint32_t dly;
Short explain about two keywords before variable name. An 'volatile' keyword says to compiler "Don't optimize access to this variable. Its value can change in any time of execution of program". Second keyword defines size of variable - 32-bit unsigned integer. Now, let's do a simple loop using this variable:
for(dly = 0; dly < 500000; dly++);
This code give us 500.000 loop cycles, that in effect give us some time of CPU spending in the loop. Without keyword 'volatile' this code probably gives us no cycles and no time delay.
Complete code of blink LED application:
//=============================================================================
// STM32VLDISCOVERY tutorial
// Lesson 1. Blinking the LED.
// Copyright : Gaurav Verma, Assistant Professor, ECE Department.
// Jaypee Institute of Information Technology, Sector-62, Noida.
// e-mail : gaurav.iitkg@gmail.com
// Mobile No.: 9811506739
//=============================================================================
#include "stm32f10x.h"
#include "antilib_gpio.h"
//=============================================================================
// main function
//=============================================================================
int main(void)
{
volatile uint32_t dly;
RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
GPIOC->CRH = (GPIOC->CRH & CONFMASKH(8)) | GPIOPINCONFH(8, GPIOCONF(GPIO_MODE_OUTPUT2MHz, GPIO_CNF_OUTPUT_PUSHPULL));
while (1)
{
for(dly = 0; dly < 500000; dly++);
GPIOC->BSRR = (1 << 8);
for(dly = 0; dly < 500000; dly++);
GPIOC->BRR = (1 << 8);
}
}
//=============================================================================
// End of file
//=============================================================================

How to make this code more universal? When LED diode will be connected to other pin, we need modify in code all references to them. So let's use the preprocessor to define some macros describing connected LED diode:
#define LED_BLUE_GPIO  GPIOC
#define LED_BLUE_PIN   8

Now we can use defined macros in place to direct GPIO and pin number, but in case of configure GPIO there is need to specify configuration register (low or high). We can do this like in example:
#if (LED_BLUE_PIN > 7)
LED_BLUE_GPIO->CRH = (LED_BLUE_GPIO->CRH & CONFMASKH(LED_BLUE_PIN)) |GPIOPINCONFH(LED_BLUE_PIN,GPIOCONF(GPIO_MODE_OUTPUT2MHz, GPIO_CNF_OUTPUT_PUSHPULL));
#else
    LED_BLUE_GPIO->CRL = (LED_BLUE_GPIO->CRL & CONFMASKL(LED_BLUE_PIN)) |       GPIOPINCONFL(LED_BLUE_PIN, GPIOCONF(GPIO_MODE_OUTPUT2MHz, GPIO_CNF_OUTPUT_PUSHPULL));
#endif
The #if is preprocessor conditional directive, not the C language conditional instruction, so before compilation the preprocessor depending on value of LED_BLUE_PIN choose proper source code line and place them on source code file that will be compiled. Now, to turn LED on we can use following sequence:
GPIOC->BSRR = (1 << LED_BLUE_PIN);
and for turn led OFF :
GPIOC->BRR = (1 << LED_BLUE_PIN);
Complete source code :
//=============================================================================
// STM32VLDISCOVERY tutorial
// Lesson 1. Blinking the LED.
// Copyright : Gaurav Verma, Assistant Professor, ECE Department.
// Jaypee Institute of Information Technology, Sector-62, Noida.
// e-mail : gaurav.iitkg@gmail.com
// Mobile No.: 9811506739
//=============================================================================
#include "stm32f10x.h"
#include "antilib_gpio.h"
//=============================================================================
// Defines
//=============================================================================
#define LED_BLUE_GPIO GPIOC
#define LED_BLUE_PIN  8
//=============================================================================
// main function

//=============================================================================
int main(void)
{
volatile uint32_t dly;
RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
#if (LED_BLUE_PIN > 7)
  LED_BLUE_GPIO->CRH = (LED_BLUE_GPIO->CRH & CONFMASKH(LED_BLUE_PIN)) |
 GPIOPINCONFH(LED_BLUE_PIN,     GPIOCONF(GPIO_MODE_OUTPUT2MHz,
 GPIO_CNF_OUTPUT_PUSHPULL));
#else
  LED_BLUE_GPIO->CRL = (LED_BLUE_GPIO->CRL & CONFMASKL(LED_BLUE_PIN))
| GPIOPINCONFL(LED_BLUE_PIN,     GPIOCONF(GPIO_MODE_OUTPUT2MHz,
 GPIO_CNF_OUTPUT_PUSHPULL));
#endif
while (1)
 {
  for(dly = 0; dly < 300000; dly++);
  LED_BLUE_GPIO->BSRR = (1 << LED_BLUE_PIN);
  for(dly = 0; dly < 300000; dly++);
  LED_BLUE_GPIO->BRR = (1 << LED_BLUE_PIN);
}
}
//=============================================================================
// End of file
//=============================================================================


No comments:

Post a Comment