Lesson 8: Part 1: PWM - LED Brightness Control

Introduction

PWM - namely Pulse-width modulation, enable us to output digital signal that toggle between high(3.3V) and low(0V) state in desire frequency. A example of signal will be as below:
Example of PWM waveform


3 terms that is use in PWM are below:
  1. Period : the time require to complete a cycle of high state and low state.
  2. Frequency: Inverse of period.  The frequency of the PWM waveform.
  3. Duty cycle: A measurement of the ratio of high state as compare to a period of PWM. A 50% of duty cycle meaning half of the period is at high state with another half of period at low state. Example above have a 33% duty cycle.
By applying capacitor at PWM pin, we can 'smoothen' the PWM and use it as DAC. For example using it to control the brightness of the LED. Increase LED brightness by increase duty cycle and vice versa.

Another important usage of PWM is in servo motor control. 

Example Code

In STM32F030, the PWM is generate using one of the MCU timer.

In the coming example, we will be using Timer14 and enable the PWM waveform output at pin PB1. And by connecting the PB1 into on-board LED-LD3, we can observe the PWM effect without any equipment.

As on board LED is at pin PC9, we need to first pull up the PC9 pin, and then physically connect the pin from PC9 to PB1 as shown below:
Short Between PC9 (LED-LD3) and PB1 (PWM output)


You can retrieve the sample code at github here.


In pwm.c source, there is additional define namely: PWM_PB1_BLINK. This define is use to demonstrate a PWM waveform with different frequency, thus enabling the LED blink at different rate.

When enable PWM_PB1_BLINK, LD3 will be blinking at rate of 0.5seconds, due to PWM generated have period of 0.5seconds.

When disable PWM_PB1_BLINK, the brightness of the LD3 will be reduce slightly. This is because the PWM is running at 50% duty cycle, thus reducing the overall brightness. But no blinking is observe because the PWM waveform is running at 10millisconds (refer below for the capture waveform on scope), which is too fast for our eyes to perceive. One exercise is you can try is change the value of PWM_DUTY_CYCLE. When PWM_DUTY_CYCLE is reduce, the brightness will be reduce.
Disable define PWM_PB1_BLINK Generate PWM Of 10mS Period

SFR Configurations

Key configurations of SFR is describes as below:
  1. Enable Timer 14 block
  2. Setting of PWM duty cycle
  3. Settings of PWM period or frequency
  4. Configurations of Timer 14
  5. Configurations of GPIO-PB1
  6. Configurations of GPIO-PC9

Lesson 7: Part 2: ADC - Reading Sensor Voltage Input

Previously we work on the ADC function in reading on chip temperature sensor. In this tutorial we will be working on external sensor.
 
To simplify the work require, we will be using a potentiometer to simulate the change of resistor. A sample of potentiometer is this. By applying the potentiometer in a voltage divider circuit, we will be getting an output voltage that change when potentiometer resistor is changing.

Tutorial Setup

This tutorial will be reading performing ADC read at channel ADC_IN0 every seconds (using systick), then output it through USART interface(USART) into laptop hyperterminal.

Below shown the setup require on ADC section


Using Vin=5V,  R1=6.8K Ohm, potentiometer at R2 (with resisitance range of 0Ohm ~ 10K Ohm). The voltage output sample is shown in below:

R2 Resistance              Vout
0 Ohm                                 0V
1K Ohm                         0.64V
2K Ohm                         1.13V
5K Ohm                         2.11V
10K Ohm                       2.98V


The Vout will be connect into PA0, correspond to ADC_IN0 pin.

Actual Setup

The MCU is actually running at 3V, the ADC will give maximum reading of 0xFFF when input voltage is 3V.


Source Code Descriptions

SFR Settings Descriptions

The following SFR setup is require to enable ADC_IN0

A) ADC function

Power up initialisation
  1.  Enable GPIO-A block
  2. Change PA0 pin function to alternate function (ADC function)
  3. Enable ADC block
  4. Perform ADC calibration and wait for completion
  5. PowerUp ADC block and wait for completion
During Run time on Reading ADC result
  1. Select ADC channel (in our case it would be ADC_IN0:Channel 0)
  2. Enable single conversion mode/
  3. Select conversion resolution (in our case we use 12 bits)
  4. Start ADC process and wait for completion
  5. Read the result
B) USART
  1. Enable GPIO-A block
  2. Change PA9, PA10 pin function to alternate function( USART)
  3. Enable USART block
  4. USART settings on the following: 8 databit, 115200 baud rate, 1 stop bit
  5. USART transmit/receive enable
  6. Enable USART interrrupt receive
  7. Enable USART
  8. * For more detail on USART, refer to tutorial here.
C) SysTick
  1.  Set SysTick timer reload value
  2. Clear SysTick timer current value to 0
  3. Select SysTick timer clock source
  4. Enable timeout interrupt, SysTick timer
  5. *For more detail on SysTick, refer to this tutorial.

Tutorial Source Code

The tutorial source code can get clone from here.
I have purposely combine the previous tutorial (USART, SysTick) to show how a source code can be 'expand' when more features is being added. Now each MCU peripheral will have its respective source file (e.g. gpio.c, systick.c, uart.c)

Even though we are using potentionmeter to simulate the voltage change, a real life example work similar way. For example, a temperature sensor of LM35, is giving out a voltage change base on temperature. And the above code can be use with some tweak on the ADC result, to translate the result into temperature.

Lesson 7: Part 1: ADC - Reading On Chip Temperature Sensor

MCU is consider the processing unit of the embedded system. In order to allow MCU 'process', certain data need to be collect and feed into the system. For example, for a temperature monitoring system, it may require to pickup the current temperature from a temperature sensor. Or a burglar system will require to read current door contact sensor and feed into MCU. Base on these input then MCU will act accordingly.

There are may method the data (or sensor data) can be receive by MCU such as: SPI bus, IIC bus, ADC. We will be touching on SPI/IIC in future, for this lesson, our focus will be on ADC, which stands for 'Analog to Digital Converter'. As the name imply, the functionality of ADC is to convert analog signal(in this case is voltage) to binary data.

Take temperature sensor as example, when the temperature is changing, the resistance of the sensor will varies. Using this characteristic and apply the sensor into voltage divider circuit, we will have a voltage output that change according to the temperature. By using ADC, MCU can know the current voltage level, thus enabling system to know current temperature.

After reading the ADC section in Reference Manual (RM0360), there is actually an internal temperature sensor (Vsense). This would be an good example to demonstrate ADC function in reading temperature.

p.s:
Despite the idea of demostrating tempeature reading, it appears that much more work is require in order to perform a proper temperature reading, as described in RM0360: Section 12.9. Thus, the example would only demonstrate a ADC value change (increase) correspond to temperature increase.

Controlling ADC

Key function to enabling ADC is summarizes as below:
  1. Enable ADC peripheral
  2. Configurations on ADC
  3. ADC channel selection
  4. Start ADC process
  5. Wait for ADC process complete, then read the result
The source code can get obtain from here.


Lesson 6: Part 3: USART - Transmit Using DMA

In previous USART example, both transmit and receive will requie significant MCU invovlment in moving data in and out the buffer. This is (receiving example) what we usually perform in old days.

But now with a DMA(Direct Memory Access), a lot of processing can be offload to hardware instead. Using DMA, automated the byte transfer between USART buffer to RAM.

This is what we will be doing in this tutorial, we will be sending data out(TX) using DMA.

The sample code will read character being received and retranmist received characters back to sender using DMA. The receiving section remain as previous example, using interrupt and MCU to read the bytes from USART receiving buffer and place into dedicated variable.

Please take note this example only use DMA for TX(transmit), and not RX(receive).

Source Code Explaination

The sample code can get obtain from here.

 Code Initialisation

During code initialisation, below peripheral has to be initialised.
  1. GPIOA - Enable USART Tx/Rx pin and use as USART function
  2. USART - Enable USART block and related USART settings (e.g. baud rate, stop bits)
  3. DMA - Enable DMA function in USART transmission
  4. At the end of initilisation code , a message " Hello World" will be transmit out.
One changes on USART SFR is to enable a DMA transmit mode as shown in below:
USART1->CR3 |= USART_CR3_DMAT;

Some of the key settings in DMA includes:
  1. Source address: when the data come from
  2. Destination address: when the data should transfer to
  3. Transfer bytes: number of data to transfer
  4. Data size: Data size for each transfer : 8/16/32bits
  5. Transfer type: Peripheral to memory or vice versa, memory to memory
  6. Interrupt type: Half transfer or complete transfer

 Main Loop

In the main loop, MCU will continue check if there is any data being received. If data has been recevied, then it will read the received data and perform data transmission using DMA.

The configuration of DMA block is perform in USART1_Tx().

To ensure next DMA sending is only start when last sending is complete, a variable TXBuffers.Complete is using. Before start of transmission, this flag will be turn to 0. Upon complete of data sending, an DMA interrupt will be generated, and a call back function will be called to turn this flag into 1. Before data transmission, this flag will be check and system will enter a while loop if this flag has value 0 (meaning previous transmission is still in progress).

**This may cause a problem if previous transmission has large number of data to be send while system waited and eventually timeout and discard current data. To solve this, change in such a way reading on received data only happen when last DMA send is complete.

Lastly, I found a similar tutorial on here. This tutorial is more detail with oscilloscope measurement to prove the advantage of using DMA.

Lesson 6: Part 2: USART - Receiving Data From PC

In previous tutorial I am showing STM32F030 sending data and receive by PC. This tutorial will be working on the other way: PC sending data and receive by STM32F030. As USART is full duplex, while PC is sending data, STM32F030 also can send data simultaneously.

I have expand the code from previous tutorial to include the receiving section. And in this tutorial, we will be perform a character echo. Characters receive from PC will be transmit back to PC. Thus, we should expect character that we type in transmit section will be appear on receiving window of the software.

SFR Configurations

Refer to source code here for SFR configurations, all the related SFR on receiving is grouped in function name IntUSARTInit_RX in source file name int_usart.c.

Source Code Explanation

To avoid a blocking read that would occupied the whole MCU cycles, this example demonstrate the usage of interrupt (without DMA) on character receiving. 

When character is being received, interrupt routine ISR name USART1_IRQHandler will be called and this routine will read the character from buffer (USART1->RDR) and save into local buffer. Then the pointer PtrProd will be increment. The method use here is the producer/consumer model.

In the main program, the program will constant check if a data is available in buffer. When there is data available, a read from buffer will perform, then data read will be transmit out (using past tutorial).

Below is the video showing the code is running


Lesson 6: Part 1: USART - Transmitting Data Out To PC

To expand the capability of the embedded system, a communication channel is always require. For example, to communicate with a PC, usually a Serial Port (RS232) will be use. Machine to machine communication usually is realise using RS232 or RS485/RS422 (longer distance). In embedded system, the peripheral that incharge of such function is UART(Universal asynchronous receiver transmitter) or USART (Universal synchronous asynchronous receiver transmitter)

As Debugging Tool

One useful usage of USART is in debugging. But inserting code that print debugging message, we can always know what is the value of variable, which part of the code MCU is running. Sometimes, it is more efficient by looking at the debug message instead of stepping through the program.

The down side of this is that a dedicated buffer has to be allocated to keep the debugging buffer(as sending USART speed is slow).

Physical Connection

USB To Serial Port < --> Max232 <--> USART/UART

In this tutorial, we will be looking at enabling communication between STM32F030 with PC through RS232. We will be enabling USART1 on MCU part. To enable communication with PC, an additional voltage converter(MAX232 chipset) circuit is require as shown below:

RS232 Circuit
The above image is take from here. It also include a good introduction of interfacing with PC.

Currently most of the PC/laptop no longer come with DB-9 Serial Port. And a replacement of this would be a USB to Serial converter as shown below:
USB to Serial Converter

USB To TTL

The easiest converter to use is shown below, which enabling direct interface into STM32F030, without requiring MAX232 as shown below:
USB to TTL
Enabling direct connection to STM32F030 USART

STM32F030 SFR Configurations

We will be using the following settings on STM32F030:
  • Baud Rate: 115200bps
  • Data bits: 8
  • Stop bits: 1
  • Parity : None
  • Handshake : None
On PC, you can use teraterm, hyperterminal or similar software to open the Serial Port and accept data send from STM32F030. The above settings must be configure in software to ensure proper communication between PC and STM board.

As for the SFR section, it would be too lengthy to go through each of the register. I have heavily comment on the source code instead to provide explanation of each SFR. Refer here for the source code.

Source Code Explained

Below are the key actions implemented in source code to enable data transmission.

1. SFR configurations
  • Enable GPIO block that contain USART TX/RX pn
  •  Configure TX/RX pin to use as USART (so that is will not use as general purpose input/output)
  • Enable USART block
  • USART settings (baud rate, data bits, stop bits, parity, handshake)
  • Enable USART function, and transmit, receive function
2.  Data Transmit Function (call by application)
  • Putting first byte into transmit buffer
  • Waiting first byte transmit complete
  • Putting subsequenct byte into transmit buffer and wait until byte is transmit out. Repeating this until all bytes has been transmit
The example will be continuing printing out message 'Hello World!" through USART1.

Below is the image of my connection, using an USB to TTL device, directly connect to STM32
  • White cable: 3V
  • Blue cable: GND
  • Grey cable: PA9, USART_TX
  • Purple cable: PA10:USART_RX
Physical Connection

Source Code Implementation

The above source code is implementing a blocking transmit. Which means that the MCU will be waiting(a while loop) when current chracter being transmit out. Thus when sending full length of 'Hello World!', no other process can be executed (except ISR). This is inefficient but this is the simpliest way to ensure character transmitting is working.

Apart from blocking transmit, we can also perform USART transmit using:
  • Interrupt: allow interrupt occur when current data has been transfer to transmit buffer(it would take some time to transmit out), so we can load next data into TDR. This is better than blocking, but still require ISR code to loadbuffer into TDR
  • DMA: enabling DMA auto transfer when current data has been transfer to transmit buffer. This is the most efficient method, but also more complex as it involve interrupt + DMA control.
Later stage we shall be looking for DMA method. For now, we should get things work in the simpliest method: blocking transmit/receive.

Lastly, I attached a short video on my capture on data transmit from STM32 on this tutorial.



Lesson 5: Part 2: SysTick Timer - Building Operating System

As promise, we will be doing some real work here, making our first Operating System - task scheduler function.

The task scheduler that perform round-robin with no priority. Each task will have a task timer count which get decrement. And when the timer count reach zero, the task is ready to be executed. There will be no preemptive function and thus each task that executed should not have an infinite loop that occupied CPU time indefinitely. This is just a brief explanation of the task scheduler in action as the main focus here is on the SysTick.

Below is the snapshot of the code on the task scheduler.

Structure Defining Task Called Interval and Task Pointer
SysTick Timer Interrupt That Decrement Task Delay


Called Task Upon Task Delay Timeout
Again, the above complete source code has been uploaded into github.

Lesson 5: Part 1: SysTick Timer - Running it

On ARM architecture, a special peripheral that I appreciated much is this:  a dedicated timer called system tick, which use as system tick for RTOS or task scheduler.

The register settings is much simpler as it is meant to be used as system tick in software. In this tutorial, again, we will be using this timer to toggle the LED. On second thought, lets have some fun instead, lets build a Operating System! Unfortunately, this operating system is only have a round-robin task scheduler.

I shall split this into 2 session, first is a guide to enabling the System Tick. The second tutorial will be putting System Tick into our task scheduler.

Do take note that the system tick description is in the programming manual. In here, I will be setting the system tick to timeout every 100ms. This is too big for normal application, but since I will be running tasks with 1seconds and 2 seconds timeout later on, it is good enough here.

After running so many tutorial, finally I saw some easy guide as below:
Guide On Setting Up SysTick
First is the program reload value. With system clock running at 48MHz, each clock running at 1/48MHz, which is 20.83nS. To get to 100ms, I will need 4800000 tick. This correspond to the reload value that I should use.
SysTick Reload Value:
STK_RVR : 4800000
Next I just need to clear the current value to zero
Clear SysTick Current Count
STK_CVR : 0
And finally configure program control and status register
Setting Control and Status Register
STK_CSR : 0x000007
That's it. A few SFR setting and we are good to go. Just go to github(Lesson5_SysTick_Blinking_LED) to grab the source code. And it seems that I still need the blinking LED in this and next (task scheduler) example. Below is the code snippet on SFR settings.
Source Code To Setup SysTick


Lesson 4: Part 1: Timer: Running A Timer

There is bunch of timer available in STM32F030, below is a snapshot from the F0 series datasheet.

Summary of Timers in ST F0 Series
This is tutorial we will be using TIM3 in our exercise. This timer is select as it is one of the timer that is available across all MCU. Initially I want to use TIM6 as example as it have simpler configurations, but only certain MCU part will this timer and thus I have drop the idea.

To understand TIM3 features and SFR control, refer to ReferenceManual at Chapter 14: General Purpose Timer (TIM3). Frankly speaking, I have quite a hard time in understanding the description, and also having difficulties in finding the register I need. I hope this is not a sign for I am too old for this job, :-)
Reference Manual on TIM3


Before we dive into  controlling the timer, we need some understanding on timers. Some reference is here.

Generally to use a timer, we will need to perform the following actions:
  1. Enable timer 3
  2. Timer interrupt duration calculation
  3. Enable timer interrupt
  4. Run timer
  5. Implement timer ISR

1. Enable Timer 3

This is done by enabling Timer3 block by supplying clock.
Enable Timer 3 Block
RCC_APB1ENR: TIM3_EN

2. Timer Interrupt Duration Calculation

2.1 Timer Prescaler

A timer is just a counter that keep running up/down (count up or count down) until overflow and interrupted is generated due to  the overflow. The counter is increment in a fix duration. A 48MHz system, each clock cycle can increment the counter, making the duration for counter 0->1 happen in 1/48MHz (208.3 nano-seconds)

STM32F030 is running at 48MHz, typically we want a timer to have duration of 1milliseconds. In order to 'scale down' the frequency, we will need the prescaler. Each MCU will have its own prescaler formula as describe in user manual.
Timer Prescaler
TIM3_PSC = 47999
As shown in above, CK_CNT is the frequency that we need, PSC is the prescaler value that we should set in order to obtain the frequence we want.
Said we need 1 micro-seconds interval, that would equal to 1MHz, thus the prescale value = 48MHz/1MHz - 1 = 47
However, in this tutorial I will created a 1 seconds timer, a more appropiate frequency would be 1KHz. Thus, the prescale value = 48MHz/1KHz - 1 = 47999.

As the prescale is only 16 bit, the slowest frequency we can have will be 732.4Hz.

2.2 Timer Auto Reload Value

After setting the prescaler 47999, we now have a counter(timer) that increment every 1milliseconds. We want to have a timer that interrupt at 1 second interval, thus we want the counter to count up to 1000 (1000*1milliseconds = 1 second) and generated an interrupt.

The SFR so use in determining the maximum count is Timer Auto Reload Value.
Timer Auto Reload
TIM3_ARR  = 1000
 We just need to set this register as 1000 to get a 1 seconds timer interrupted. The range we can have here is from 1mS to 65535 mS (base on prescale value of 47999).

By adjusting prescaler and auto-reload register, we can control timer interrupt period to a smaller/larger duration.

3. Enable timer interrupt

We need to enable the following SFR for interrupt generation upon timer overflow
Enable Timer 3 Overflow Generated Interrupt
TIM3_DIER : UIE
Interrupt Block : Enable Timer 3 Interrupt
NVIC_ISER : 16 (16 is ISR for Timer 3) as shown below
Vector Table Showing Timer 3 At Position 16

4. Run timer

Start timer with the following

Enable Timer Run
TIM_CR1 : CEN

5. Implement Timer ISR

Timer ISR is pretty simple, we just need to clear the interrupt flag, so that ISR is not called until next interrupt happen.

Then is our user code, here we just set a flag upon interrupt. In the main loop there will be code that trigger LED-LD3 base on this flag. Do notice the flag is declare as below:
volatile unsigned char LEDOnOff;

A 'volatile' keyword is require here to inform compiler that this variable can be change anytime in the program. This will avoid compiler optimizing the LED trigger code in main function. A detail explanation of these will be out of this tutorial scope. I will try explain on this in other post instead.

As a rule of thumb, variable use in the interrupt should always declare with 'volatile' keyword.

As usual, I have push the tutorial code into github.






Lesson 3: Part 2: Interrupt - More about it

Interrupt is important as it enable software to transfer works to hardware and get notified when the hardware has completed its works. A good example is our previous interrupt tutorial. By passing the input checking to hardware, software no longer require to constantly poll the pin state and check if there is state change.

However, interrupt does make the system more complex in the sense that now any code in the main() function can be interrupted and interrupt service routine(ISR) get executed. Refer below for more explanation of interrupt to avoid buggy software.
  1. Embedded.com

Lesson 3: Part 2: Interrupt - External Input


In this lesson we will be showing way to connect STM32 board with external push button. Do take note we are using the source code in previous lesson. The easier way of doing this is following using the same circuit in STM32 board as below:

On Board Push Button Circuit
 By implementing Push button, R28/R29, the actual image will be as below.
*At the time of writing I was unable to find a push button to use, thus I had replace the button with a white wire.
Blue: PA0, Red:3.3V, Green: Ground
White: Button
White Wire is Not Connect/Button is Release
LED is Off

White Wire is Connect/Button is Press
LED is On

To properly design a input push button detection, a debounce circuit or software has to be implement. I found a useful article discussing on push button(including debounce)  here. Do take note it is on MSP430 from TI, just understand the theory section, skip the MSP430 SFR settings.

Lesson 3: Part 1: Interrupt - On-Board Input

Now we are able to read input status from the input pin. In the previous lesson, we have to keep reading the input pin to detect for button press. In a DVD player example, it is very unlikely user will be pressing the button(e.g. play button). But in order to cater for this user interaction, system have to keep reading the input, which is quite inefficient.

The better method would be configure the MCU in such that any input pin change, notify to the program via interrupt. The function that will be called when interrupt occur is called Interrupt Service Routine(ISR).

The following settings will be require to get this function to work:
  1. Configure input (as shown in previous lesson here)
  2. Configure MCU such that the input state change, it will trigger an interrupt
  3. Select rising/falling interrupt
  4. Enable interrupt
  5. Created ISR source code
The above should serve a guidelines as different interrupt architecture would require different configurations.

For input interrupt, 2 configuration is possible, namely: falling interrupt or rising interrupt. This essentially represent the signal state changes. Taking button as example, idle button state will have voltage high, when user press the button, voltage will change from high to low - falling interrupt. Similarly, rising interrupt occur when user release the pressed button.

Configure MCU For Input Interrupt

We need to figure out the SFR that enable interrupt to be generated, when our input change. 
SYSCFG_EXTICR1:EXTI0 = 0000
Notifying MCU Tie PA0 To Interrupt Line 0
EXTI_IMR:MR0 = 1
Enable Line 0 Interrupt

Select Falling/Rising Interrupt

EXTI_FTSR:TR1
Falling Trigger Enable For Button Press
EXTI_RTSR: TR1
Rising Trigger Enable For Button Release

Enable Interrupt

This is the best part of writing this lesson. I do not found any description on the technical document.  Thus I have no choice but skip this section while writing the source code, and the result: things does not work.

Scanning the document up and down doesn't have much, expect I spot a section on NVIC (Nested Vector Interrupted Controller), but no SFR at end of section. And later on I discovered this in debugging session:
NVIC Discovery in Debugger Mode
With this, I believe a section of the NVIC description has left out in technical document. Further digging into Keil header file leads me to below:

/** \brief  Structure type to access the Nested Vectored Interrupt Controller (NVIC).
 */
typedef struct
{
  __IO uint32_t ISER[1];                 /*!< Offset: 0x000 (R/W)  Interrupt Set Enable Register           */
       uint32_t RESERVED0[31];
  __IO uint32_t ICER[1];                 /*!< Offset: 0x080 (R/W)  Interrupt Clear Enable Register          */
       uint32_t RSERVED1[31];
  __IO uint32_t ISPR[1];                 /*!< Offset: 0x100 (R/W)  Interrupt Set Pending Register           */
       uint32_t RESERVED2[31];
  __IO uint32_t ICPR[1];                 /*!< Offset: 0x180 (R/W)  Interrupt Clear Pending Register         */
       uint32_t RESERVED3[31];
       uint32_t RESERVED4[64];
  __IO uint32_t IP[8];                   /*!< Offset: 0x300 (R/W)  Interrupt Priority Register              */
}  NVIC_Type;

Above looks promising with SFR 'ISER', which finally lead to below line to enable interrupt line 0:

NVIC->ISER[0] |= (0x01<<EXTI0_1_IRQn);

And it works! The bad thing, I spend about an hour just because of the missing specification.

**After contacted with ST FAE, they have point me to the document that have description on above SFR register. I do not understand why it is in separate document, my guess is they use the same document for all M0 core, which may include other STM series.

Writing ISR

Writing ISR is a bit tedious as you have to know how to 'tell' your compiler this is an ISR. Thus it generally varies from one compiler to the other one. In our case here, the vector is define in startup file as below:
ISR in startup_stm32f030.s
What we need to do is just declare a function with same name to 'connect' into it.

Lastly, in our ISR we may need to include a checking to ensure the interrupt source is indeed from button, and not from others by checking SFR below:
EXTI_PR:PR0
Reading Interrupt Source, and clearing it
Each time ISR is being called, we also need to clear the bit so that next interrupt could happen.

The Source Code

The complete source code has been push into git.

Lesson 2: Part 2: Controlling GPIO - On-Board Input - Input Reading

 Recite from Part 1, 3 configurations is require as below:

  1. Enable GPIO section for respective pin
    • SFR: RCC_AHBENR , bit: IOPAEN, value: 1
  2. Configure pin to input function
    • SFR: GPIOA_MODER, bit: MODER0, value: 00
  3. Reading input status
    • SFR: GPIOA_IDR, bit: IDR0
The corresponding C code for 3 cases above are:
  1. Enable GPIO section for respective pin
    • RCC->AHBENR |= RCC_AHBENR_GPIOAEN;
  2. Configure pin to input function
    • GPIOA->MODER &= ~GPIO_MODER_MODER0;  //mask the bit to 00
    • GPIOA->MODER |= MODE_INPUT;         //set the bit
  3. Reading pin status into a variable
    • InputState = GPIOA->IDR & GPIO_IDR_0;
The main.c source code is shown as below, or retrieve from git:
#include "stm32f0xx.h"

void MCUInit(void)
{
    /* Input config */
    //Enable GPIO_A block
    RCC->AHBENR |= RCC_AHBENR_GPIOAEN;
    //Configure PA0 as Input
    GPIOA->MODER &= ~GPIO_MODER_MODER0;//mask the bit to 00
}

int main(void)
{
    unsigned char InputState;
   
    MCUInit();
   
    while(1)
    {
        //read input state
        InputState = GPIOA->IDR & GPIO_IDR_0;
    }
}

Real Life Application

  1. Pressing play button in DVD to start video playback process.
  2. Pressing the power button at hand phone to lit up screen for phone unlock process

Exercise

  1. Since reset setting on GPIOA->MODER is already as input, try remove the configurations and see if input detection still works.
  2. Base on USER button state, change the LD3 LED state, for example:
    • when USER button press, turn on LD3 LED
    • when USER button release, turn off LD3 LED

Lesson 2: Part 1: Controlling GPIO - On-Board Input - SFR Study

Introduction

Push button provide a method for human to give input to the system. It can be requesting a system to start a process, or requesting a system to provide system state and etc. For example, pressing a button in remote control to perform volume up action, pressing start button in washing machine to initiate wash status.

In this lesson, we are be using out board button as system input.

Working Principles

When MCU pin is configure as input pin, the pin state (High or Low) can be read from software by accessing SFR. Below schematic is the circuit design for USER button in STM32F030 Discovery board.
USER Button (B1) <-> PA0
Refer to above circuit, PA0 is the input pin to MCU. When USER button is not press, PA0 signal will be 0V (read by software as Low state). When USER button is press, VDD will be supply at point 3,4, thus enabling PA0 to have VDD voltage, which is 3.3V(read by software as High state)

We need the following configuration to enable an input pin
  1. Enable GPIO section for respective pin
  2. Enable pin to be input function
  3. Read the current input state

Step 1: Enable PA0 GPIO Function

  1. Similar to lesson 1 part 1, we use the same SFR(RCC_AHBENR) to enable GPIO function, but PA0 require us to enable bit (IOPAEN)
Enable Bit IOPAEN In Register RCC_AHBENR

Step 2: Configure Pin To Be Input Pin

  1. Similar to lesson 1 part 1, we use the same SFR(GPIOx_MODER). Since we are looking at Port A, the right register is GPIOA_MODER
  2. PA0 refer to bit 0, thus we should be configure MODER0 to '00'
  3. If we refer to below, the reset state of the pin is 00, that means after power up the pin has been configure to input state. Thus, we can actually skip this setting in our coding.
Set MODER0 To '00' In Register GPIOA_MODER

Step 3: Reading Input State

  1. By reading register GPIOA_IDR,  bit IDR0, we will know the current state of the pin.
Reading IDR0 In Register GPIOA_IDR

Lesson 1: Part 4: Controlling GPIO - External Output - LED Control

To control external LED, the circuit would be as below:
PC9 With External LED


PC9 -> Orange Wire -> 30 Ohm Resistor* -> LED -> Black Wire -> GND
*Note: Use 30 Ohm resistor, image shown is NOT 30 Ohm resistor
*The resistor is use to limit the current, thus it is OK to put put before or after the LED.


As the voltage line at PC9 is only 3.3V, the LED may not achieve maximum brightness. But this example is good enough to demonstrate how we can connect to external device(LED).

A transistor(2N3904) can be use in order to increase control voltage to 5V.






Lesson 1: Part 3: Controlling GPIO - On-Board Output - LED Control

In this section we will be using what we learn from Part 1 and write some C code. Recite from Part 1, 3 configurations is require as below:
  1. Enable GPIO section for respective pin
    • SFR: RCC_AHBENR , bit: IOPCEN, value: 1
  2. Pin to be output function
    • SFR: GPIOC_MODER, bit: MODER9, value: 01
  3. Control the pin to be high state or low state
    • High state: SFR: GPIOC_ODR, bit: ODR 9, value: 1
    • Low state:  SFR: GPIOC_ODR, bit: ODR 9, value: 0
In order to access SFR in C language, we need to include a header files :
  •  #include stm32f0xx.h
 Open content of stm32f0xx.h by right click on source code file name, select 'Open document' as shown below:
Open SFR Define File
Inside the SFR files define all the SFR that we use in source code below.

Then the corresponding C code for 3 cases above are:
  1. Enable GPIO section for respective pin
    • RCC->AHBENR |= RCC_AHBENR_GPIOCEN;
  2.  Pin to be output function
    • GPIOC->MODER &= ~GPIO_MODER_MODER9;  //mask the bit to 00
    • GPIOC->MODER |= MODE_OUTPUT;         //set the bit
  3.  Control the pin to be high state or low state
    • High state:
      • GPIOC->ODR |= GPIO_ODR_9;
    • Low state:
      • GPIOC->ODR &= ~GPIO_ODR_9;
Below is the image for whole source code. You can either type out or just use Git link here.

Full Source Code To Control LD3

Real life applications:

  1. With proper hardware circuit, instead of turn on/off LED, we can turn on/off fan, light.
  2. Replacing the LED with IR transmittor, and with correct timing on turning on/off, we can use it as remote control, to control electrical appliances: TV, DVD.

Exercise

  1. In the source code we have turn on LED and turn off LED, why the LED is constantly turn on when program is running?
  2. Repeating Part 1 to 3 process, this time try controlling LD4 LED






Lesson 1: Part 2: Controlling GPIO - On-Board Output - Create New Project

This section will be demonstrating step to create new project.

 











Project -> New uVision Project


Select Device
Select Project Folder, Add Project Name
Select Startup -> Resolve -> OK
Right Click 'Source Group 1' -> Select 'Add New Item...'
C File -> main.c -> Add

Edit main.c -> Click Build Button -> 0 Error, 0 Warning
Project build successful when message shown : 0 Error(s), 0 Warning(s)