Lab 1 Introduction To Stm32F103 and Ide: St-Link V2 and Keil Uvision5
Lab 1 Introduction To Stm32F103 and Ide: St-Link V2 and Keil Uvision5
Before you can create a new project in Keil, you must install the Software Packs. You can install it by
clicking Pack Installer icon on toolbar. In the Pack Installer dialog you can select
menu File → Import, and then select the Software Pack (the .pack file).
To create a new project, go to Project → New uVision Project, give a name for that project, and
then select your microcontroller (STM32F103C8). After that, you can select which peripheral library
that you want to use, then click the Resolve button.
Build the Project
In the project window, you can rename the Target 1 and Source Group 1 folder
to STM32F103C8 and Source respectively. Right click on Source folder then click Add New Item
to Group menu. Select the .c file and give a name for that file (for example main.c). You can write
your code in main.c file. You can build the project by clicking Build icon from toolbar or press F7.
Your Built Output should contain zero error.
Download the Code
After you build the project successfully, then you can download the code into the microcontroller. To
do that, you need to configure the ST-Link settings by clicking Options for Target icon. In
the Debug tab, select ST-Link Debugger and click Settings button. Set the properties in Cortex-M
Target Driver Setup dialog like this:
Check the Reset and Run option, so the microcontroller will run the program after the download
process finished. If you not check this option, then you have to press the reset button to run the
program.
After the settings for ST-Link have been configured properly, you can start flash the microcontroller,
by pressing Download icon from toolbar. You can also debug the program from menu Debug →
Start/Stop Debug Session.
LAB 2
STM32F103 GPIO
STM32F103 GPIO are generic pins that can be configured as input or output. In this tutorial, I will
explain how to use STM32F103 GPIO for controlling an LED on/off. STM32F103 GPIO can be
configured in 4 different modes (input mode, output mode, analog input mode, and alternate function
mode). For controlling an LED on/off, we need to configure a GPIO pin in output mode.
There are 2 output modes for GPIO, output open drain and output push-pull. The logic voltage of
STM32F103 GPIO is 3.3V, so the pin output voltage is 3.3V. This is the characteristic of a GPIO pin
when it is configured in output mode:
The output driver is enabled.
In open drain mode, a “0” in the output data register activates the N-MOS while a “1” in the output
data register leaves the port in Hi-Z. (the P-MOS is never activated in open drain mode).
In push-pull mode, a “0” in the output data register activates the N-MOS while a “1” in the output data
register activates the P-MOS.
The schmitt trigger input is activated.
The weak pull-up and pull-down resistors are disabled.
The data present on the GPIO pin is sampled into the input data register every APB2 clock cycle.
A read access to the input data register gets the GPIO state in open drain mode.
A read access to the output data register gets the last written value in push-pull mode.
LED Circuit
This is the LED circuit that I use in this tutorial. The LED configuration is active low. When the PA0 is
low or “0”, the LED will turn on. When the PA0 is high or “1”, the LED will turn off. The output mode
that I use is output push-pull mode so, when I write “0” to the output data register, the N-MOS will
turn on, therefore the LED will turn on (current can flow from Vdd to Vss through LED and resistor).
When I write “1” to the output data register, the P-MOS will turn on, therefore the LED will turn off
(current can‟t flow from Vdd to Vdd).
Example Code
Enable the peripheral clock for GPIOA. GPIOA is connected to APB2 bus.
This the code to make an LED blinking, for the project file you can get it from here.
#include "stm32f10x.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
while (1)
{
/* Toggle LED on PA0 */
// Reset bit will turn on LED (because the logic is interved)
GPIO_ResetBits(GPIOA, GPIO_Pin_0);
delay(1000);
// Set bit will turn off LED (because the logic is interved)
GPIO_SetBits(GPIOA, GPIO_Pin_0);
delay(1000);
}
}
// Delay function
void delay(unsigned int nCount)
{
unsigned int i, j;
It is also possible to use output open drain mode to control the LED. The LED circuit is the same.
When I write “0” to output data register, the N-MOS will turn on, therefore the LED will turn on
(current can flow from Vdd to Vss through LED and resistor). When I write “1” to output data register,
PA0 is in Hi-Z (current can‟t flow because PA0 is Hi-Z/floating), therefore the LED will turn off.
STM32F103 GPIO
In this tutorial, I will explain how to use STM32F103 GPIO for reading a push button. STM32F103
GPIO can be configured in 4 different modes (input mode, output mode, analog input mode, and
alternate function mode). For reading a button, we need to configure a GPIO pin in digital input
mode.
This is the button circuit for this tutorial. I also add an LED in this circuit just for showing the state of
the button (is pressed or not). For the GPIO input mode, I use the input mode with internal pull-up,
therefore the button circuit is active low (when the button is pressed, the logic in input data register is
“0”). The GPIO output mode for the LED is open-drain mode and also active low.
Example Code
This is the example code. The purpose of this code is to turn on the LED when the button is pressed
and turn off the LED when it is not pressed
#include "stm32f10x.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
GPIO_InitTypeDef GPIO_InitStruct;
int main(void)
{
// Enable clock for GPIOA
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
while (1)
{
// If button on PA1 is pressed (button circuit is active low)
if (!(GPIO_ReadInputData(GPIOA) & GPIO_Pin_1))
{
// Turn on LED on PA0 (LED circuit is active low)
GPIO_ResetBits(GPIOA, GPIO_Pin_0);
}
else
{
// Turn off LED on PA0
GPIO_SetBits(GPIOA, GPIO_Pin_0);
}
}
}
You can use other GPIO input modes, which are input with internal pull-down or input floating. For
input floating, you must add an external pull-up or pull-down resistor. This is the example circuit for
input with internal pull-down and input floating with external pull-up. When you use a pull-down
resistor, the logic is active high (when the button is pressed, the logic in input data register is “1”).
LAB 3
STM32F103 System Timer
Create a Delay Function with System Timer
STM32F103 System Timer or SysTick is a timer inside the CPU. SysTick is a basic countdown timer.
SysTick can be polled by software or can be configured to generate an interrupt. To use SysTick, we
must load a value to the reload value register. The width of reload value register is 24-bit, so it can
counts from 0x00FFFFFF to 0. In this tutorial, I will explain how to use SysTick for creating a delay
function. SysTick can be configured through the registers below.
To easily configure SysTick, we can use SysTick_Config() function. This function is defined in
core_cm3.h. This function will initialize SysTick and its interrupt, then start the SysTick. The counter
is in free running mode to generate periodic interrupts. The input parameter of this function is the
number of ticks between two interrupts.
In this tutorial, I will create 2 delay functions (DelayUs() and DelayMs()). To create DelayUs()
function, we should configure the SysTick interrupt to be triggered every 1 us by using
SysTick_Config() function. There is a variable called usTicks that holds the value of ticks in us.
Every time we call DelayUs() function, we should load this variable with the delay value in us and
then we poll this variable until reach 0. This variable will be decremented by 1 every 1 us by
SysTick_Handler() ISR. To create DelayMs() function, we should load the delay value in ms, then it
will be decremented by 1 every 1000 us using DelayUs() function. This is the code for delay library.
To use the delay functions, you should call the DelayInit() function first.
#ifdef __cplusplus
extern "C" {
#endif
#include "stm32f10x.h"
void DelayInit(void);
void DelayUs(uint32_t us);
void DelayMs(uint32_t ms);
#ifdef __cplusplus
}
#endif
#endif
-------delay.c-----------
#include "delay.h"
void DelayInit()
{
// Update SystemCoreClock value
SystemCoreClockUpdate();
// Configure the SysTick timer to overflow every 1 us
SysTick_Config(SystemCoreClock / 1000000);
}
SPI Protocol
SPI communication is different from other serial communication especially on data transfer. There is
no concept like transmit and receive data, but there is a data trading concept. When data trading
occurs, the data bits in master register is traded with the data bits in slave register on every clock
from master (one data bit per clock tick). You can think SPI is like shift registers. There are 2 shift
registers, one in master device and another in slave device. Each input of shift register is connected
to the output of the other through MOSI and MISO lines, so that they form a ring.
The figure above illustrates the bit trading from master to slave. Master register contain data 0xFF
and slave register contain data 0x00. After one clock tick, the master is left with seven of its original
bits and the first one that is come in from the slave, and vice versa. After a total of eight clock ticks,
all eight bits of each byte have traded place. Sometimes not all data byte come from slave or sent to
slave is meaningful. This happen because probably slave device has not received any command
yet, so the data in slave register is not meaningful. Another case, if we just want to take data from
slave, but don‟t want to send any command to slave, we can place dummy byte on master register
and then give eight clock ticks for trading with data bits in slave register.
The figure above is the timing diagram of SPI protocol. We know that to communicate to the slave
device, the slave select pin should be activated (active low). The data is sampled every rising edge
of clock. We can also sample data on falling edge of clock, this setting can be configured depending
on the feature of the hardware SPI that you use. In this example, master is send data byte 0x53 to
slave and then slave send data byte 0x46 to master. The order of the data is LSB first, but it can also
MSB first depending on the configuration.
Example Code
In this tutorial, I will explain how to use SPI in STM32F103 as a master, and for the slave I will use
Arduino. We can send data char „1‟ from SPI master to turn on LED blinking on Arduino. To turn off
LED blinking, we can send „0‟ from SPI master. Master can also read LED blinking status (off/on)
from Arduino by sending „?‟ first, then read the LED blinking status which will return 0 or 1. This is
the Arduino code for SPI slave device.
1 #define LED_PIN 9
2
3 volatile uint8_t led_blink = 0;
4
5 ISR(SPI_STC_vect)
6 {
7 uint8_t data_byte = SPDR;
8
9 switch (data_byte)
10 {
11 case '0':
12 led_blink = 0;
13 SPDR = 0;
14 break;
15 case '1':
16 led_blink = 1;
17 SPDR = 0;
18 break;
19 case '?':
20 // Place LED blinking status in SPDR register for next transfer
21 SPDR = led_blink;
22 break;
23 }
24 }
25
26 void setup()
27 {
28 pinMode(LED_PIN, OUTPUT);
29
30 // Set MISO pin as output
31 pinMode(MISO, OUTPUT);
32 // Turn on SPI in slave mode
33 SPCR |= (1 << SPE);
34 // Turn on interrupt
35 SPCR |= (1 << SPIE);
36 }
37
38 void loop()
39 {
40 // If LED blink status is on, then blink LED for 250ms
41 if (led_blink == 1)
42 {
43 digitalWrite(LED_PIN, HIGH);
44 delay(250);
45 digitalWrite(LED_PIN, LOW);
46 delay(250);
47 }
48 else if (led_blink == 0)
49 {
50 digitalWrite(LED_PIN, LOW);
51 }
52 }
For the SPI on STM32F103, I create several functions such as for initialize SPI, SPI data transfer,
and enable/disable slave device. In main function, I write codes to turn on and off the LED blinking
on Arduino, then ask the current LED blinking status every 2500 ms. The LED blinking status will be
displayed on LCD.
#include "stm32f10x.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_spi.h"
#include "delay.h"
#include "lcd16x2.h"
void SPIx_Init(void);
uint8_t SPIx_Transfer(uint8_t data);
void SPIx_EnableSlave(void);
void SPIx_DisableSlave(void);
uint8_t receivedByte;
int main(void)
{
DelayInit();
lcd16x2_init(LCD16X2_DISPLAY_ON_CURSOR_OFF_BLINK_OFF);
SPIx_Init();
while (1)
{
// Enable slave
SPIx_EnableSlave();
// Write command to slave to turn on LED blinking
SPIx_Transfer((uint8_t) '1');
DelayUs(10);
// Write command to slave for asking LED blinking status
SPIx_Transfer((uint8_t) '?');
DelayUs(10);
// Read LED blinking status (off/on) from slave by transmitting
// dummy byte
receivedByte = SPIx_Transfer(0);
// Disable slave
SPIx_DisableSlave();
// Display LED blinking status
lcd16x2_clrscr();
if (receivedByte == 0)
{
lcd16x2_puts("LED Blinking Off");
}
else if (receivedByte == 1)
{
lcd16x2_puts("LED Blinking On");
}
DelayMs(2500);
// Enable slave
SPIx_EnableSlave();
// Write command to slave to turn off LED blinking
SPIx_Transfer((uint8_t) '0');
DelayUs(10);
// Write command to slave for asking LED blinking status
SPIx_Transfer((uint8_t) '?');
DelayUs(10);
// Read LED blinking status (off/on) from slave by transmitting
// dummy byte
receivedByte = SPIx_Transfer(0);
// Disable slave
SPIx_DisableSlave();
// Display LED blinking status
lcd16x2_clrscr();
if (receivedByte == 0)
{
lcd16x2_puts("LED Blinking Off");
}
else if (receivedByte == 1)
{
lcd16x2_puts("LED Blinking On");
}
DelayMs(2500);
}
}
void SPIx_Init()
{
// Initialization struct
SPI_InitTypeDef SPI_InitStruct;
GPIO_InitTypeDef GPIO_InitStruct;
void SPIx_EnableSlave()
{
// Set slave SS pin low
SPI_GPIO->BRR = SPI_PIN_SS;
}
void SPIx_DisableSlave()
{
// Set slave SS pin high
SPI_GPIO->BSRR = SPI_PIN_SS;
}
LAB 5
STM32F103 SPL - Interfacing LCD16x2
LCD16x2
LCD16x2 is a popular display module and commonly used in various devices. In this tutorial, I will
explain about how to use LCD16x2 with STM32F103 microcontroller. If you want to know the detail
how LCD16x2 works, you can follow this tutorial. To interface the LCD with STM32F103, you need
the library files (lcd16x2.h and lcd16x2.c). You can get the library files and from here.
LCD16x2 Library
In this library, we can configure the GPIO for LCD in lcd16x2.h file. This definitions (in source code
below) in lcd16x2.h contain the GPIO configuration for the LCD. In this definitions, you can configure
the GPIO for LCD control lines (RS, RW, EN) and LCD data lines (D4-D7). This library only support
4-bit data mode.
Example Code
To use this library, in the main.c file, you must call lcd16x2_init() function. There are 5 parameters
for this function that can be used, depending on what type of cursor you want to use:
LCD16X2_DISPLAY_OFF_CURSOR_OFF_BLINK_OFF
LCD16X2_DISPLAY_ON_CURSOR_OFF_BLINK_OFF
LCD16X2_DISPLAY_ON_CURSOR_OFF_BLINK_ON
LCD16X2_DISPLAY_ON_CURSOR_ON_BLINK_OFF
LCD16X2_DISPLAY_ON_CURSOR_ON_BLINK_ON
This library is also support custom character display. This code below is an example how to use the
library for displaying a string and also a custom character. The custom character is created by
defining the pattern. In this example, I use the pattern for battery indicator.
LCD16x2
1 #include "stm32f10x.h"
2 #include "stm32f10x_rcc.h"
3 #include "stm32f10x_gpio.h"
4 #include "delay.h"
5 #include "lcd16x2.h"
6
7 // Custom char data (battery symbol)
8 uint8_t custom_char[] = { 0x0E, 0x1B, 0x11, 0x11, 0x11, 0x11, 0x1F, 0x1F };
9
10 int main(void)
11 {
12 // Delay initialization
13 DelayInit();
14
15 // LCD initialization
16 lcd16x2_init(LCD16X2_DISPLAY_ON_CURSOR_OFF_BLINK_OFF);
17
18 // Create custom char
19 lcd16x2_create_custom_char(0, custom_char);
20
21 while (1)
22 {
23 // Display custom char
24 lcd16x2_put_custom_char(0, 0, 0);
25 lcd16x2_puts(" Battery Low");
26 DelayMs(500);
27 // Clear display
28 lcd16x2_clrscr();
29 DelayMs(500);
30 }
31 }
LAB 6
STM32F103 SPL– Interfacing Unipolar
Stepper Motor
Stepper Motor
Stepper motor is an electromechanical device that converts electrical pulses into discrete
mechanical movements. In this tutorial, I will explain how to control a unipolar stepper motor using
STM32F103 microcontroller. If you don‟t know the basic of the stepper motor, I suggest you to read
this post. To interface a stepper motor from a microcontroller, we can‟t directly drive it with GPIO
pins because GPIO pins have maximum current that can sink or source from it. To overcome this
problem, we can use driver circuit. The driver circuit for unipolar stepper motor can be built by using
4 transistors to drive large current to the 4 wires of a stepper motor. It also can be built with
ULN2003 IC. This is the circuit for driving a unipolar stepper motor from STM32F103 by using
ULN2003 IC.
In this tutorial, I will use the 28BYJ-48 stepper motor. This motor is very cheap and it also comes
with driver module based on ULN2003 IC. This motor runs with 5V supply and has gear inside. The
gear reduction ratio is approximately 64:1. If you search from the internet, other people say that the
gear reduction ratio is actually 63.68395:1.
There are 2 common modes that can be used for controlling stepper motor, full step and half step.
Actually there is 1 more mode that can be used for controlling stepper motor in more advanced
ways. The mode is called micro step, but in this tutorial, I will explain only the full step and half step.
Full Step
The stepper motor is controlled by giving a sequence of electrical pulses. That electrical pulses are
applied to the 4 wires of stepper motor. Each electrical pulse is consist of 4-bits. We can applied
these pulses to the 4 wires by using 4 GPIO pins. The full step mode has a sequence of electrical
pulses that consist of 4 different pulses. Every 1 sequence is equivalent to 4 steps of movement, so
1 pulse is equivalent to 1 step movement. The following table is the sequence of full step mode.
One step movement of this motor in full step mode is 11.25°. By applying these sequence once, the
internal shaft of stepper motor will move 4 x 11.25° = 45°. To rotate the internal shaft of stepper
motor 1 revolution, we need 360° / 45° = 8 sequences (32 steps). The actual shaft of this motor is
geared with 64:1 gear ratio. That means every actual shaft rotates 1 revolution, the internal shaft
must rotate 64 revolutions, so to rotate the actual shaft 1 revolution, we need 32 x 64 = 2048 steps.
To move stepper motor 2048 steps, we can applied the sequence 512 times (because 1 sequence is
4 step). The following code is for rotating the actual shaft of the stepper motor 1 clockwise
revolution.
Between each pulse in a sequence, we need to add delay, because the stepper motor works much
slower compared to the execution time of a microcontroller to execute 1 line of code. This delay
allows stepper to move 1 step before we apply next pulse. This delay is directly related to rotation
speed of the shaft. The smaller delay value will give faster rotation speed. We can reverse the
direction of the shaft rotation by reversing the applied sequence.
This is the full code for rotating stepper motor one revolution clockwise and one revolution counter-
clockwise.
#include "stm32f10x.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
int main(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
delay(1000);
while (1)
{
}
}
Half step mode consists of 8 electrical pulse for every 1 sequence. The sequence for half step mode
is shown in the following table. The different between full step and half step is the step resolution.
When you use half step, you will get more smaller degree per step movement. In this motor, if we
use half step, you will get 5.625° per 1 step, but the total number of degree per 1 sequence is the
same (5.625° x 8 = 45°). The effect is the shaft rotates more smooth.
This is the code for rotating stepper motor one revolution clockwise and the one revolution counter-
clockwise using half step mode.