KEMBAR78
STM32Lab Version2024 1 | PDF | Arduino | Computing
100% found this document useful (2 votes)
133 views46 pages

STM32Lab Version2024 1

Uploaded by

t
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
100% found this document useful (2 votes)
133 views46 pages

STM32Lab Version2024 1

Uploaded by

t
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 46

MICROPROCESSOR LABORATORY

THÍ NGHIỆM KỸ THUẬT VI XỬ LÝ


Advisor: Tran Van Lic
Email: tvlic@dut.udn.vn
Version: 2024
LAB 1
INTRODUCTION TO STM32F103 AND IDE
(Blue Pill) STM32F103 is an STMicroelectronics’s 32-bit microcontroller based on the ARM Cortex-
M3 processor. The processor clock speed is up to 72MHz. It has 64 Kbytes of flash memory and 20
Kbytes of SRAM. There are several development boards for this microcontroller available on the
market. In this tutorial, I will use this development board. It has 40 pins that fit into breadboard. It has
2× 12-bit ADCs, 7× timers, and Up to 9 communication interfaces (3× USART, 2× I2C, 2× SPI, 1×
CAN, 1× USB).

ST-Link v2 and Keil uVision5


ST-Link v2 is an in-circuit debugger and programmer for the STM8 and STM32 microcontroller families.
There are 2 types of ST-Link, the original version and the clone version. For the IDE, I will use Keil uVision5.
The evaluation version of Keil uVision5 has 32 Kbytes code limitation, but for learning purpose it is enough.
You can download Keil uVision5 for MDK ARM from here. After that, you have to download MDK5
Software Packs (libraries) for this microcontroller from here.

STM32 IDE
Why should not use Arduino IDE for this Lab ?

Most example projects and how to's describe programming the Blue Pill board using the Arduino
environment. While this works and is a way to get started it has its limitations. The Arduino
environment shelters you a bit from the underlying hardware - that's its design goal. Because of this
you will not be able to take advantage of all the features the processor offers, and integrating a real
time operating system is not really supported. This means that the Arduino environment is not widely
used in industry. If you are wanting to make a career in embedded software development, Arduino is
a good starting place, but you need to move on and use a development environment that is used
industrially. ST helpfully provide a completely free development environment suite for their
processors called STM32CubeIDE. This is widely used in industry, so it's a good one to move on to.

However, and this is the big however, STM32CubeIDE is fearsomely complicated and is a daunting
piece of software to use. It supports all features of all ST's processors and allows them to be
intimately configured, which you don't come across in the Arduino IDE because it's all done for you.

You need to set up your board as a first step in STM32CubeIDE. The IDE knows about ST's own
development boards and sets them up for you, but the Blue Pill, while using a ST processor, it is not
an ST product, so you are on your own here.

This instructable takes you through the process of setting up your Blue Pill board, enabling a serial
port, and writing out some text. It's not much, but it's an important first step.

LAB PREPARING
1. STM32CubeIDE - download from ST's website. You need to register and it takes a while
to download (https://www.st.com/en/development-tools/stm32cubeide.html )

2. A Blue Pill board. You can get them from shopee or anywhere. You need one that has a
genuine ST processor on it as some don't.
3. A ST-LINK v2 debugger/programmer

4. A TTL to USB 3.3V serial cable for output and 2 male to female header wires to connect
it.
5. A serial terminal program like PuTTY. (https://www.putty.org/ )
GETTING START

Step 1: Creating a New Project

Start STM32CubeIDE and then from the menu choose File|New|STM32 Project.

In the Part Number Search box enter STM32F103C8.

In the MCUs/MPUs List you should see STM32F103C8. Select this line as in the image above.

Click Next.

In the Project Setup dialog give you project a name.

Leave everything else as it is and click Finish. Your project will appear to the left in the Project
Explorer panel.

Step 2: Configuring the Processor


In the Project Explorer pane open up your project and double click the .ioc file.

On the Project & Configuration tab expand System Core then select SYS.

Under SYS Mode and Configuration in the Debug drop-down choose Serial Wire.

Now select RCC in the System Core list just above SYS you selected above.

Under RCC Mode & Configuration from the High Speed Clock (HSE) drop-down select
Crystal/Ceramic Resonator.

Now under Categories again, open up Connectivity and select USART2.

Under USART2 Mode and Configuration from the Mode drop-down select Asynchronous.

Now select the Clock Configuration tab and go to the next step.

Step 3: Configuring the Clocks


Configuring the Clocks

You can now see a rather daunting clock diagram, but it only needs setting up once. This the
hardest to describe here as the diagram is complex. All the things you need to change are
highlighted in the image above.

The Blue Pill board comes with an 8 MHz crystal on the board and that is what the clock
configuration diagram defaults to, so we don't need to change that.

Under PLL Source Mux select the lower choice, HSE.

Just to the right set PLLMul to X9.

To the right again under System Clock Mux select PLLCLK.

To the right again under APB1 Prescalar select /2.

That's it. If you see any parts of the diagram highlighted in purple you've done something wrong.
Step 4: Save and Build

Save the .ioc configuration with Ctrl-S. When you are asked if you want to generate code select Yes
(and tick Remember my decision so that you are not asked every time). You can close the .ioc file.

Now do a build from the menu Project|Build Project.

Step 5: Adding Some Code

Now we'll add some code to use the serial port we configured.

In Project Explorer open up Core\Src and double click main.c to edit it.

Scroll down until you find the main() function and add the code shown below just below the comment
/* USER CODE BEGIN 3 */ then do a build again.

HAL_UART_Transmit(&huart2, (uint8_t *)"Hello, world!\r\n", 15U, 100U);<br>

Next it's connect the up hardware and give it a go.


Step 6: Connecting Up the Hardware

Connecting the ST-LINK v2

The ST-LINK v2 should have come with a 4 wire female to female header ribbon cable. You need to
make the following connections:

Blue Pill to ST-LINK v2

GND to GND

CLK to SWCLK

DIO to SWDIO

3.3 to 3.3V

Connecting the Serial Cable


If you go back to the .ioc file and look at the chip diagram on the right you'll see that UART2's Tx line
is on pin PA2. Therefor connect the pin labelled PA2 on the Blue Pill board to the connection with
the yellow wire on the FTDI Serial cable. Also connect one of the Blue Pill's ground pins (labelled G)
to the black wire on the FTDI serial cable.

Step 7: Debugging

Plug in you FTDI Serial cable and fire up a serial terminal at 115200 baud. Then plug in your ST-
LINK v2 and you're ready to go.

From STM32CubeIDE choose Run|Debug. When a Debug as dialog pops up choose STM32
Cortex-M C/C++ Application and OK.

When a Edit Configuration dialog pops up just press OK.

The debugger will break on the first line of main(). From the menu choose Run|Resume and check
for messages in the serial terminal.
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.

GPIO 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

To configure PA0 in output push-pull mode, we must do this steps:

Enable the peripheral clock for GPIOA. GPIOA is connected to APB2 bus.

Initialize GPIOA by using GPIO_InitTypeDef struct.

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"

void delay(unsigned int nCount);


GPIO_InitTypeDef GPIO_InitStruct;

int main (void)


{
// Enable clock for GPIOA
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

// Configure PA0 as push-pull output


GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &GPIO_InitStruct);

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;

for (i = 0; i < nCount; i++)


for (j = 0; j < 0x2AFF; 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.

GPIO Read Button

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.

GPIO Input Mode


STM32F103 GPIO has 3 digital input mode: input with internal pull-up, input with internal pull-down,
and input floating. The logic voltage of STM32 is 3.3V, so the logic voltage for GPIO input pins are
also 3.3V, but there are several pins that 5V tolerant. We can use 5V logic level to this 5V tolerant
input pins. The table below shows I/O voltage level on STM32F103 input pins. The characteristic of
a GPIO pin when it is configured in input mode is shown in figure below.
• The output driver is disabled.
• The schmitt trigger input is activated.
• The weak pull-up and pull-down resistors are activated or not depending on the input configuration
(pull-up, pull-down, or floating).
• 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 obtains the GPIO pin state.
Button Circuit

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);

// Cofigure PA0 as open-drain output


GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_Init(GPIOA, &GPIO_InitStruct);

// Cofigure PA1 as input with internal pull-up resistor


GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStruct);

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.

Create A Delay Function

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.

---------Include file delay.h…………….


#ifndef __DELAY_H
#define __DELAY_H

#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"

// For store tick counts in us


static __IO uint32_t usTicks;

// SysTick_Handler function will be called every 1 us


void SysTick_Handler()
{
if (usTicks != 0)
{
usTicks--;
}
}

void DelayInit()
{
// Update SystemCoreClock value
SystemCoreClockUpdate();
// Configure the SysTick timer to overflow every 1 us
SysTick_Config(SystemCoreClock / 1000000);
}

void DelayUs(uint32_t us)


{
// Reload us value
usTicks = us;
// Wait until usTick reach zero
while (usTicks);
}

void DelayMs(uint32_t ms)


{
// Wait until ms reach zero
while (ms--)
{
// Delay 1ms
DelayUs(1000);
}
}

Lab 3 assignment:
Create a counter with timer in 1 second (1s , 2s, 3s, ….)

Use putty to show your counter in your computer by connecting BluePill with your
computer via USB TTL (Transistor-Transistor Logic) (USB to UART)

Hint for this assignment:


int main(void)
{
uint8_t message[35] = {'\0'};

HAL_Init();

SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();

while (1)
{
message = “Hello wordl”;
HAL_UART_Transmit(&huart1, message, sizeof(message), 100);
HAL_Delay(500);

Note: Go Device Manager and check Port number. Install driver for
CP210x if window do not recognize the driver.

Finally, Open Putty or Shell Console (STM32IDE) to see the results.


Shell Console (STM32IDE)
Or Putty
LAB 4
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.

1 // LCD control lines (must be on the same port)


2 #define LCD16X2_RCC_GPIO_CONTROL RCC_APB2Periph_GPIOB
3 #define LCD16X2_GPIO_CONTROL GPIOB
4 #define LCD16X2_GPIO_RS GPIOB
5 #define LCD16X2_GPIO_RW GPIOB
6 #define LCD16X2_GPIO_EN GPIOB
7 // LCD data lines (must be on the same port)
8 #define LCD16X2_RCC_GPIO_DATA RCC_APB2Periph_GPIOA
9 #define LCD16X2_GPIO_DATA GPIOA
10 #define LCD16X2_GPIO_D4 GPIOA
11 #define LCD16X2_GPIO_D5 GPIOA
12 #define LCD16X2_GPIO_D6 GPIOA
13 #define LCD16X2_GPIO_D7 GPIOA
14 // Pin definition
15 #define LCD16X2_PIN_RS GPIO_Pin_12
16 #define LCD16X2_PIN_RW GPIO_Pin_13
17 #define LCD16X2_PIN_EN GPIO_Pin_14
18 #define LCD16X2_PIN_D4 GPIO_Pin_8 // 4-bit mode LSB
19 #define LCD16X2_PIN_D5 GPIO_Pin_9
20 #define LCD16X2_PIN_D6 GPIO_Pin_10
21 #define LCD16X2_PIN_D7 GPIO_Pin_11 // 4-bit mode MSB

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 4 assignment:
4.1 Implement Lab 3 assignment with this LCD

4.2 Run STM32 PWM Example – Timer PWM Mode Tutorial in this website below

https://deepbluembedded.com/stm32-pwm-example-timer-pwm-mode-tutorial/
LAB 5
STM32F103 INTERFACE - SPI
STM32F103 SPI (Serial Peripheral Interface) is a synchronous serial communication protocol. In this
interface, in addition to transmit and receive lines, there is a third line that is used for clock line. Each
slave device also has a chip select (enable) pin, that is used for activating the device. So to use SPI,
we need 2 wires for data lines (MOSI, MISO), 1 wire for clock line, and 1 wire per device for chip
select line. MOSI (Master Out Slave In) is used for data transfer from master device to slave device.
MISO (Master In Slave Out) is used for data transfer from slave device to master device.

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"

#define SPIx_RCC RCC_APB2Periph_SPI1


#define SPIx SPI1
#define SPI_GPIO_RCC RCC_APB2Periph_GPIOA
#define SPI_GPIO GPIOA
#define SPI_PIN_MOSI GPIO_Pin_7
#define SPI_PIN_MISO GPIO_Pin_6
#define SPI_PIN_SCK GPIO_Pin_5
#define SPI_PIN_SS GPIO_Pin_4

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;

// Step 1: Initialize SPI


RCC_APB2PeriphClockCmd(SPIx_RCC, ENABLE);
SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;
SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStruct.SPI_Mode = SPI_Mode_Master;
SPI_InitStruct.SPI_NSS = SPI_NSS_Soft | SPI_NSSInternalSoft_Set;
SPI_Init(SPIx, &SPI_InitStruct);
SPI_Cmd(SPIx, ENABLE);

// Step 2: Initialize GPIO


RCC_APB2PeriphClockCmd(SPI_GPIO_RCC, ENABLE);
// GPIO pins for MOSI, MISO, and SCK
GPIO_InitStruct.GPIO_Pin = SPI_PIN_MOSI | SPI_PIN_MISO | SPI_PIN_SCK;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(SPI_GPIO, &GPIO_InitStruct);
// GPIO pin for SS
GPIO_InitStruct.GPIO_Pin = SPI_PIN_SS;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(SPI_GPIO, &GPIO_InitStruct);

// Disable SPI slave device


SPIx_DisableSlave();
}

uint8_t SPIx_Transfer(uint8_t data)


{
// Write data to be transmitted to the SPI data register
SPIx->DR = data;
// Wait until transmit complete
while (!(SPIx->SR & (SPI_I2S_FLAG_TXE)));
// Wait until receive complete
while (!(SPIx->SR & (SPI_I2S_FLAG_RXNE)));
// Wait until SPI is not busy anymore
while (SPIx->SR & (SPI_I2S_FLAG_BSY));
// Return received data from SPI data register
return SPIx->DR;
}

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 assignment:
Read data from any SPI or I2C sensors (For example: BME280 Humidity
Temperature Pressure Sensor-SPI protocol) and show it on LCD or Putty.
LAB 6 (Optional)
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.

1 // One revolution CW using full step mode


2 for (cycle = 0; cycle < 512; cycle++)
3 {
4 GPIO_Write(GPIOB, 0x1000);
5 delay(5);
6 GPIO_Write(GPIOB, 0x2000);
7 delay(5);
8 GPIO_Write(GPIOB, 0x4000);
9 delay(5);
10 GPIO_Write(GPIOB, 0x8000);
11 delay(5);
12 }

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.

Full step counterclockwise


1 // One revolution CCW using full step mode
2 for (cycle = 0; cycle < 512; cycle++)
3 {
4 GPIO_Write(GPIOB, 0x8000);
5 delay(5);
6 GPIO_Write(GPIOB, 0x4000);
7 delay(5);
8 GPIO_Write(GPIOB, 0x2000);
9 delay(5);
10 GPIO_Write(GPIOB, 0x1000);
11 delay(5);
12 }

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"

void delay(unsigned int nCount);


GPIO_InitTypeDef GPIO_InitStruct;
int cycle = 0;

int main(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 |


GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOB, &GPIO_InitStruct);
// One revolution CW using full step mode
for (cycle = 0; cycle < 512; cycle++)
{
GPIO_Write(GPIOB, 0x1000);
delay(5);
GPIO_Write(GPIOB, 0x2000);
delay(5);
GPIO_Write(GPIOB, 0x4000);
delay(5);
GPIO_Write(GPIOB, 0x8000);
delay(5);
}

delay(1000);

// One revolution CCW using full step mode


for (cycle = 0; cycle < 512; cycle++)
{
GPIO_Write(GPIOB, 0x8000);
delay(5);
GPIO_Write(GPIOB, 0x4000);
delay(5);
GPIO_Write(GPIOB, 0x2000);
delay(5);
GPIO_Write(GPIOB, 0x1000);
delay(5);
}

while (1)
{
}
}

void delay(unsigned int nCount)


{
unsigned int i, j;

for (i = 0; i < nCount; i++)


for (j = 0; j < 0x2AFF; j++);
}
Half Step

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.

// One revolution CW using half step mode


2 for (cycle = 0; cycle < 512; cycle++)
3 {
4 GPIO_Write(GPIOB, 0x9000);
5 delay(5);
6 GPIO_Write(GPIOB, 0x1000);
7 delay(5);
8 GPIO_Write(GPIOB, 0x3000);
9 delay(5);
10 GPIO_Write(GPIOB, 0x2000);
11 delay(5);
12 GPIO_Write(GPIOB, 0x6000);
13 delay(5);
14 GPIO_Write(GPIOB, 0x4000);
15 delay(5);
16 GPIO_Write(GPIOB, 0xC000);
17 delay(5);
18 GPIO_Write(GPIOB, 0x8000);
19 delay(5);
20 }
21
22 delay(1000);
23
24 // One revolution CCW using half step mode
25 for (cycle = 0; cycle < 512; cycle++)
26 {
27 GPIO_Write(GPIOB, 0x8000);
28 delay(5);
29 GPIO_Write(GPIOB, 0xC000);
30 delay(5);
31 GPIO_Write(GPIOB, 0x4000);
32 delay(5);
33 GPIO_Write(GPIOB, 0x6000);
34 delay(5);
35 GPIO_Write(GPIOB, 0x2000);
36 delay(5);
37 GPIO_Write(GPIOB, 0x3000);
38 delay(5);
39 GPIO_Write(GPIOB, 0x1000);
40 delay(5);
41 GPIO_Write(GPIOB, 0x9000);
42 delay(5);
43 }

You might also like