- ARM Assembly
- Introduction
- Disclaimer
- Prerequisites
- Code Structure
- Data Types in ARM Assembly
- Important Notations
- ARM Instructions
- Differences Between ARM and x86 Assembly
- Addressing Modes
- General Code Structure
- Bit Operations and Bitmasking in ARM Assembly
- Understanding Bit Operations
- Bitmasking
- Bitmask
- 1. ORR (Logical OR) - Setting a Bit
- 2. BIC (Bit Clear) - Clearing a Bit
- 3. BFI (Bit Field Insert) - Inserting a Bit Field (ARMv7 and Later)
- 4. BFC (Bit Field Clear) - Clearing a Bit Field (ARMv7 and Later)
- 5. TST (Test Bit) - Checking if a Bit is Set
- 6. LSR (Logical Shift Right) - Reading a Specific Bit
- Summary of Bit Operations
- Hardware Refresher
- Hardware Introduction
- Microcontrollers
- Initialization
- GPIO Registers in STM32F407VG
- GPIO Registers in STM32F103C6
- Clock
- TFT LCDs
- Interfacing Between STM32 and TFT Display
- 8080 Parallel Interface
- Display Initialization
- Drawing
- Preparing Simulation Environment
- Preparing Hardware Environment
- Examples
In this guide, I aim to explain how to write, compile, and test ARM Assembly code. I am writing code using Keil MDK (of course, you can use STM32CubeIDE
or any other IDE). I am testing the code using simulation first (with Proteus
), then on hardware (using EasyMX Pro v7 for STM
).
I am writing for both the BluePill STM32F103C6
and the EasyMX STM32F407VG
interchangeably, as they are quite similar. I chose the BluePill STM32F103C6
because it is the most popular STM32
microcontroller and the EasyMX STM32F407VG
because I have it.
Proteus is a commercial software that requires a valid license for use. It is not free and must be purchased from the official vendors. I do not support or encourage software piracy or any unethical methods to acquire or use Proteus illegally. Please ensure you are using a legitimate copy of the software in compliance with its licensing terms.
Using Proteus is not mandatory; it is just an option for simulation. If you are unable to purchase Proteus, you can consider buying an STM32 Blue Pill development board, which is an affordable alternative for testing and running STM32 projects on real hardware.
I assume you have prior experience with x86 Assembly programming. You may need the following datasheets:
and the ARM Cortex M3 Instructions List:
Directive | Size | Writable? | Description |
---|---|---|---|
EQU |
N/A | No | Defines a constant value |
DCB |
1B | Depends on Area | Define a Byte |
DCW |
2B | Depends on Area | Define a Word |
DCD |
4B | Depends on Area | Define a Doubleword |
DCQ |
8B | Depends on Area | Define a Quadword |
SPACE |
N | Depends on Area | Reserves N bytes of memory without initialization. |
FILL |
N | Depends on Area | Reserves N bytes and initializes them with a specified value. |
-
Parentheses (
()
)- Used for syntax readability. Neglected by the compiler.
-
Immediate Values (
#
)-
#
is used for immediate values and constants in instructions likeMOV
,ADD
, etc. -
Example:
MOV R0, #2 ; Move the immediate value 2 into R0 MOV R0, #(1 << 4) ; Move 16 (1 shifted left by 4) into R0 myConstant EQU 4 MOV R0, #myConstant
-
-
Variables with
LDR
(=
)-
LDR
with=
allows loading (large immediate values - constant values - address of a variable). -
Example:
LDR R0, =0xFFFFFF ; Load the large immediate value 0xFFFFFF into R0 LDR R0, =0xFFF + 0x5 ; Load 0x1004 into R0 myConstant EQU 4 LDR R0, =myConstant ; Loads 4 into R0 myVariable DCB 5 LDR R0, =myVariable ; Loads address of myVariable into R0 (needs another load to get 5)
-
-
Dereferencing with
[...]
-
Used for accessing memory via registers.
-
Example:
STR R0, [R1] ; Store R0 at the address in R1 LDR R0, [R1, #4] ; Load R0 from memory at R1+4
-
-
Auto-Update (
!
and Post-Increment/Decrement)-
!
updates the base register immediately. -
Example:
LDR R0, [R1, #4]! ; Load R0 from R1+4, then update R1 to R1+4 STR R0, [R1], #4 ; Store R0 at R1, then increment R1 by 4
-
-
Comments (
;
or@
)-
;
for comments (or@
in unified syntax). -
Example:
MOV R0, #5 ; This is a comment MOV R1, #10 @ Another comment in unified syntax
-
<imm/cnst>
= immediate values or constants
<var>
= variable
Operation | ARM Assembly | x86 Assembly | Description |
---|---|---|---|
Move Data | MOV R0, R1 |
MOV AX, BX |
Move data between registers |
Load Immediate or Constant | MOV R0, #<imm/cnst> |
MOV AX, <imm/cnst> |
Load an immediate or a Constant |
Load Immediate or Constant | LDR R0, =<imm/cnst> |
MOV AX, <imm/cnst> |
Load an immediate or a Constant to register |
Load Address of Variable | LDR R0, =<var> |
LEA AX, <var> |
Load address of a variable to register |
Load from Memory | LDR R0, [R1] |
MOV AX, [BX] |
Load a word from memory |
Store to Memory | STR R0, [R1] |
MOV [BX], AX |
Store a word to memory |
Addition | ADD R0, R1, R2 |
ADD AX, BX |
Add two registers |
Addition | ADD R0, R1, #<imm/cnst> |
ADD AX, <imm/cnst> |
Add using Immediate or Constant |
Subtraction | SUB R0, R1, R2 |
SUB AX, BX |
Subtract two registers |
Subtraction | SUB R0, R1, #<imm/cnst> |
SUB AX, <imm/cnst> |
Subtract using Immediate or Constant |
Multiplication | MUL R0, R1, R2 |
MUL BX |
Multiply (ARM: 3-operand, x86: implicit AX ) |
Bitwise AND | AND R0, R1, R2 |
AND AX, BX |
Logical AND |
Bitwise AND | AND R0, R1, #<imm/cnst> |
AND AX, <imm/cnst> |
Logical AND |
Bitwise OR | ORR R0, R1, R2 |
OR AX, BX |
Logical OR |
Bitwise OR | ORR R0, R1, #<imm/cnst> |
OR AX, <imm/cnst> |
Logical OR |
Bitwise XOR | EOR R0, R1, R2 |
XOR AX, BX |
Logical XOR |
Bitwise XOR | EOR R0, R1, #<imm/cnst> |
XOR AX, <imm/cnst> |
Logical XOR |
Bit clear | BIC R0, R1, #<imm/cnst> |
- | Clear bits in Op1 in 1s locations in Op2 |
Shift Left | LSL R0, R1, R2 |
SHL AX, BX |
Logical shift left |
Shift Left | LSL R0, R1, #<imm/cnst> |
SHL AX, <imm/cnst> |
Logical shift left |
Shift Right | LSR R0, R1, R2 |
SHR AX, BX |
Logical shift right |
Shift Right | LSR R0, R1, #<imm/cnst> |
SHR AX, <imm/cnst> |
Logical shift right |
Branch (Jump) | B label |
JMP label |
Unconditional jump |
Branch if Zero | BEQ label |
JE label |
Jump if equal (zero flag set) |
Binary Test | TST R0, R1 |
TEST AX, BX |
Compare two registers (with AND) |
Compare | CMP R0, R1 |
CMP AX, BX |
Compare two registers (with subtraction) |
Function Call | BL function |
CALL function |
Call a function |
Function Return | BX LR |
RET |
Return from function |
Push to Stack | PUSH {R0} |
PUSH AX |
Push register to stack |
Push All to Stack | PUSH {R0-R12, LR} |
PUSHA |
Push all registers to stack |
Pop from Stack | POP {R0} |
POP AX |
Pop register from stack |
Pop All from Stack | POP {R0-R12, LR} |
POPA |
Pop all registers from stack |
No Operation | NOP |
NOP |
No operation |
-
Register Naming
- ARM does not have named registers like x86 (
EAX
,EBX
, etc.). Instead, it uses numbered registers:R0
→R15
.
- ARM does not have named registers like x86 (
-
Operand Order
-
In x86, the first operand is the destination.
-
In ARM, the destination must be explicitly written, as instructions use three operands:
{Instruction} {destination} {op1} {op2}
-
-
Memory Access
- ARM does not support direct
MOV
between registers and memory. - Instead, it uses Load (
LDR
) and Store (STR
) instructions.
- ARM does not support direct
-
Data, Code, and Stack Sections
-
In x86 Assembly, sections are declared as
.data .code .stack
. -
In ARM, they are defined using the
AREA
directive:AREA <Area Name>, [CODE/DATA], [READONLY/READWRITE]
- ARM assembly can have multiple code areas but always starts from
main
. - Some assemblers allow writing data inside code sections.
READONLY
areas are stored in flash memory (non-writable at runtime).READWRITE
areas are stored in RAM.
- ARM assembly can have multiple code areas but always starts from
-
-
Return Address in ARM
- In ARM, the return address is stored in the
Link Register (LR)
, also called Link. - Unlike x86,
LR
is not the instruction pointer. Instead:PC (Program Counter)
is the actual instruction pointer.LR (Link Register)
stores the return address when a function is called.
- In ARM, the return address is stored in the
-
Function Calls and Returns
-
x86 has
CALL
andRET
instructions. -
ARM does not have
CALL
andRET
. Instead, it uses:BL
(Branch and Link) for function calls.BX LR
(Branch Exchange) for returning.
BL my_function ; Calls my_function, storing return address in LR BX LR ; Returns to the caller
-
-
Procedures and Functions in ARM
-
In ARM Assembly:
- Procedures perform actions but may not return a value.
- Functions return a value.
- All functions are procedures, but not all procedures are functions.
Example: Procedure (No return value)
procedure_example: PUSH {R4, LR} ; Save R4 and LR MOV R0, #5 ; Example operation MOV R1, #10 ; Another operation POP {R4, LR} ; Restore R4 and LR BX LR ; Return to caller
Example: Function (Returns a value)
function_example: PUSH {R4, LR} ; Save R4 and LR MOV R4, #20 ADD R0, R0, R4 ; Modify R0 MOV R0, #10 ; Return value POP {R4, LR} ; Restore R4 and LR BX LR ; Return to caller
-
-
Case Sensitivity and Indentation
- ARM Assembly is case-sensitive.
- Unlike x86, labels in ARM do not use colons (
:
) but must start at the beginning of a line.
-
Using External Functions in ARM
-
To use functions from another file:
EXPORT <function_name> ; Export function for external use INCLUDE <filename> ; Include another assembly file
-
-
Program Termination
END MAIN
in x86 Assembly is simply written asEND
in ARM.
-
Unified Syntax
- ARM supports an alternative syntax called
.syntax unified
, which is different from traditional ARM assembly.
- ARM supports an alternative syntax called
Addressing Mode | Example |
---|---|
Immediate Addressing | MOV r0, #10 (Load immediate value 10 into r0 ). |
Register Addressing | MOV r1, r2 (Copy value of r2 into r1 ). |
Register Indirect Addressing | LDR r0, [r1] (Load value from memory at address in r1 into r0 ). |
Pre-Indexed Addressing | LDR r0, [r1, #4] (Load value from r1 + 4 into r0 ). |
Post-Indexed Addressing | LDR r0, [r1], #4 (Load value from r1 , then increment r1 by 4). |
Scaled Register Addressing | LDR r0, [r1, r2, LSL #2] (Load from r1 + (r2 × 4) ). |
PC-Relative Addressing | LDR r0, =variable (Assembler generates LDR r0, [PC, #offset] ). |
Stack Addressing (Push/Pop) | PUSH {r0, r1} (Store r0 and r1 on stack), POP {r0} (Restore r0 from stack). |
ARM's startup code (provided by STMicroelectronics) calls the __main
function, which should contain all your code.
EXPORT __main
AREA MYDATA, DATA, READWRITE ; Read-Write data section (written to RAM)
; Data
AREA MYCODE, CODE, READONLY ; Read-only data section (written to flash or ROM)
__main FUNCTION
; Code
ENDFUNC
END
Bit operations allow us to manipulate individual bits in registers, which is essential in embedded programming when dealing with hardware registers, flags, and memory-mapped I/O.
Each bit in a register can represent an ON (1) or OFF (0) state. We use bitwise operations to modify specific bits without affecting others.
Bitmasking is a technique where we use a binary pattern (mask) to set, clear, or test specific bits in a value while leaving others unchanged.
For example:
- Setting a bit → Force a bit to 1.
- Clearing a bit → Force a bit to 0.
- Testing a bit → Test if a specific bit is set or cleared.
A bitmask is a constant used to modify specific bits in a register.
; define bitmask as 1 shifted to the required bit location
TFT_WR EQU (1 << 3) ; equivalent to 0x08
; use bitmask
ORR R2, R2, #TFT_WR ; sets bit 3 in R2
ARM provides several instructions for bit manipulation:
The ORR
(Logical OR) instruction is used to set (turn ON) specific bits while keeping others unchanged.
Syntax:
ORR Rd, Rn, #bit_mask
Rd
→ Destination register.Rn
→ Source register.bit_mask
→ Bit pattern to set.
Example: Set bit 3 in R0
MOV R0, #0x00 ; R0 = 0b00000000
ORR R0, R0, #(1 << 3) ; R0 = 0b00001000 (bit 3 set)
The BIC
(Bit Clear) instruction is used to clear (turn OFF) specific bits.
Syntax:
BIC Rd, Rn, #bit_mask
bit_mask
→ Defines which bits to clear (set to 0).
Example: Clear bit 3 in R0
MOV R0, #0xFF ; R0 = 0b11111111
BIC R0, R0, #(1 << 3) ; R0 = 0b11110111 (bit 3 cleared)
The BFI
(Bit Field Insert) instruction allows inserting a value into specific bit positions.
Note: This instruction is available only in ARMv7 (Cortex-M3 and later). If targeting older cores (e.g., Cortex-M0), use BIC
and ORR
instead.
Syntax:
BFI Rd, Rn, #lsb, #width
Rd
→ Destination register.Rn
→ Source value.lsb
→ Least Significant Bit position to insert the value.width
→ Number of bits to insert.
Example: Insert 3-bit value (0b101) at bit position 4
MOV R0, #0x00 ; R0 = 0b00000000
MOV R1, #0x05 ; R1 = 0b00000101 (value 5)
BFI R0, R1, #4, #3 ; R0 = 0b00010100 (insert at bit 4)
The BFC
(Bit Field Clear) instruction clears a range of bits.
Note: This instruction is available only in ARMv7 (Cortex-M3 and later). If targeting older cores, use BIC
instead.
Syntax:
BFC Rd, #lsb, #width
lsb
→ Least Significant Bit position to clear.width
→ Number of bits to clear.
Example: Clear 3 bits starting from bit 4
MOV R0, #0xFF ; R0 = 0b11111111
BFC R0, #4, #3 ; R0 = 0b11100011 (cleared bits 4-6)
The TST
(Test) instruction is used to check if a specific bit is set.
Syntax:
TST Rn, #bit_mask
Rn
→ Register to test.bit_mask
→ Bit pattern to test.
Example: Check if bit 3 is set in R0
AND R0, #(1 << 3) ; Clear all bit but bit 3
TST R0, #(1 << 3) ; Check if bit 3 is set
BEQ bit_not_set ; If zero flag is set, bit 3 is not set
B bit_set ; Otherwise, bit 3 is set
The LSR
(Logical Shift Right) instruction can be used to extract a specific bit by shifting it to the least significant position.
Syntax:
LSR Rd, Rn, #shift_amount
Rd
→ Destination register.Rn
→ Source register.shift_amount
→ Number of bits to shift right.
Example: Read bit 3 of R0 and store it in R1 (0 or 1)
LSR R1, R0, #3 ; Shift bit 3 to position 0
AND R1, R1, #1 ; Clear other bits, keeping only bit 3
Instruction | Purpose | Example |
---|---|---|
ORR |
Set a bit | ORR R0, R0, #(1 << 3) (Set bit 3) |
BIC |
Clear a bit | BIC R0, R0, #(1 << 3) (Clear bit 3) |
BFI |
Insert a value into a bit field (ARMv7+) | BFI R0, R1, #4, #3 (Insert 3-bit value at bit 4) |
BFC |
Clear a range of bits (ARMv7+) | BFC R0, #4, #3 (Clear 3 bits starting from bit 4) |
TST |
Test if a bit is set | TST R0, #(1 << 3) (Check bit 3) |
LSR |
Extract a specific bit | LSR R1, R0, #3 (Extract bit 3) |
Most microcontrollers, such as STM32
, ESP
, and AVR (Arduino)
, operate with similar concepts. Here, we will review some of these concepts to help you understand how our code will work.
Most microcontroller boards have external pins to connect them to other devices. To use these pins, we must:
- Configure which pins are used as outputs and which as inputs.
- Configure whether input pins have pull-up or pull-down resistors.
- Write to output pins.
- Read from input pins.
- Apply any other needed configurations.
All these configurations are stored in registers. Every group of pins is called a Port, and each port has a set of registers for configuring its pins.
For example, the STM32F407VG
has five ports while STM32F103C6
has two ports. Each port has control registers and data registers.
Control Registers:
GPIOx_MODER
– Selects the I/O direction.GPIOx_OTYPER
– Selects the output type (push-pull or open-drain).GPIOx_OSPEEDR
– Selects the pin speed.GPIOx_PUPDR
– Selects the pull-up/pull-down configuration.
Data Registers:
GPIOx_IDR
– Stores input data (read-only).GPIOx_ODR
– Stores output data (read/write).
Each port pin is associated with two bits in the mode register:
00
– Input mode01
– Output mode10
– Alternate function mode11
– Analog mode
This register configures output pins as either push-pull or open-drain:
0
– Push-pull1
– Open-drain
This register determines the maximum switching speed of the port pins:
00
– Low speed (2 MHz to 8 MHz)01
– Medium speed (12.5 MHz to 50 MHz)10
– High speed (25 MHz to 100 MHz)11
– Very high speed (50 MHz to 180 MHz)
This register configures the internal pull-up or pull-down resistors for each pin:
00
– No pull-up / pull-down01
– Pull-up10
– Pull-down11
– Reserved
These registers store the state of the GPIO pins, where:
1
– ON (High)0
– OFF (Low)
Control Registers:
GPIOx_CRL
– Selects the I/O direction.GPIOx_CRH
– Selects the output type (push-pull or open-drain).
Data Registers:
GPIOx_IDR
– Stores input data (read-only).GPIOx_ODR
– Stores output data (read/write).
GPIOx_CRL
controls the configuration of pins 0 to 7 of the corresponding GPIO port.
GPIOx_CRH
controls the configuration of pins 8 to 15 of the corresponding GPIO port.
Each pin is associated with four bits in these register:
-
Lower two bits MODE[1:0] defines the operating mode (and speed) of the pin:
00
– Input mode01
– Output mode (Max speed 10 MHz)10
– Output mode (Max speed 2 MHz)11
– Output mode (Max speed 50 MHz)
-
Higher two bits CNF[1:0] configures the function of the pin based on the mode:
- Input mode (
MODE = 00
):00
– Analog input01
– Floating input10
– Input with pull-up/pull-down resistors (GPIOx_ODR
is used in this case:0
for pull-down,1
for pull-up)11
– Reserved
- Output mode (
MODE ≠ 00
):00
– General-purpose push-pull01
– General-purpose open-drain10
– Alternate function push-pull11
– Alternate function open-drain
- Input mode (
These registers store the state of the GPIO pins, where:
1
– ON (High)0
– OFF (Low)
All registers are memory-mapped (accessed using LDR
and STR
).
Note: In ARM Assembly, we can easily add base to offset to get register address using +
sign:
; Define register base addresses
GPIOA_BASE EQU 0x40010800
; Define register offsets
GPIO_ODR EQU 0x0C
; Code
LDR R1, =GPIOA_BASE + GPIO_ODR
Instead of
; define base and offset
GPIOB_BASE EQU 0x40010C00
GPIO_ODR EQU 0x0C
; read register
LDR R1, =GPIOB_BASE
ADD R1, R1, #GPIO_ODR
; Define register base addresses
RCC_BASE EQU 0x40023800
GPIOA_BASE EQU 0x40020000
GPIOB_BASE EQU 0x40020400
GPIOC_BASE EQU 0x40020800
GPIOD_BASE EQU 0x40020C00
GPIOE_BASE EQU 0x40021000
; Define register offsets
RCC_AHB1ENR EQU 0x30
GPIO_MODER EQU 0x00
GPIO_OTYPER EQU 0x04
GPIO_OSPEEDR EQU 0x08
GPIO_PUPDR EQU 0x0C
GPIO_IDR EQU 0x10
GPIO_ODR EQU 0x14
; Define register base addresses
RCC_BASE EQU 0x40021000
GPIOA_BASE EQU 0x40010800
GPIOB_BASE EQU 0x40010C00
; Define register offsets
RCC_APB2ENR EQU 0x18
GPIO_CRL EQU 0x00
GPIO_CRH EQU 0x04
GPIO_IDR EQU 0x08
GPIO_ODR EQU 0x0C
To use any register, we must first enable the clock for the needed port. For STM32F407VG
we need to enable corresponding port clock on register RCC_AHB1ENR
:
while for STM32F103C6
we need to enable corresponding port clock on register RCC_APB2ENR
:
LDR R1, =RCC_BASE + RCC_AHB1ENR ; Load address of clock register
LDR R0, [R1] ; Load value from clock register
ORR R0, R0, #(1 << 3)
STR R0, [R1] ; Store updated value back to register
TFT LCDs are graphical displays capable of rendering colored frames pixel by pixel. In our examples, we will use the ILI9341
TFT display because:
- It supports multiple interface modes.
- It is available in Proteus' default library.
- It is widely available in the market.
- It is included in the
EasyMX
kit.
ILI9341 connections on EasyMX board:
+--------- TFT ---------+
| D0 ← PE0 |
| D1 ← PE1 |
| D2 ← PE2 |
| D3 ← PE3 |
| D4 ← PE4 |
| D5 ← PE5 |
| D6 ← PE6 |
| D7 ← PE7 |
|-----------------------|
| RST ← PE8 |
| BCK ← PE9 |
| RD ← PE10 |
| WR ← PE11 |
| RS ← PE12 |
| CS ← PE15 |
+-----------------------+
TFT with EasyMX
The way devices communicate with each other through pins (or wires) is called interfacing. Different displays support different interface modes.
Most displays support SPI
(Serial Peripheral Interface), which is widely used in electronics. However, since we are writing ARM Assembly, implementing SPI
without libraries can be complex.
Instead, we will use the 8080 Parallel Interface, which is also supported by the ILI9341
display. Although the ILI9341
also supports SPI
, we will not use it in this guide.
The 8080 protocol uses:
- 8 data pins:
D0 - D7
- 1 read pin:
RD
- 1 write pin:
WR
- 1 chip select pin:
CS
- 1 reset pin:
RST
- 1 data/control selection pin:
RS
(also calledD/C
)
Note:
IM2/IM1/IM0
pins are used to set the interface mode in chips that support multiple interfaces.
-
Data is read on the rising edge of the write signal (
WR
). -
Commands are sent when
D/C
is low, and data is sent whenD/C
is high. -
The write cycle works as follows:
- Set
CS
low. - If sending a command, set
D/C
low. - Write data on
D0-D7
. - Set
WR
low. - If needed, add a delay.
- Set
WR
high. - If needed, add a delay.
- If a command was sent, set
D/C
high. - Set
CS
high.
- Set
The read cycle is similar to the write cycle.
To initialize the display:
- Reset the display by setting the reset pin
RST
low. - Hold (delay).
- Set the reset pin high.
- Write the soft reset command
0x01
(following the write cycle). - Hold (delay).
- Write the display off command
0x28
(following the write cycle). - Set the pixel format to 16-bit by writing command
0x3A
, then writing data0x55
. - Send the sleep out command
0x11
. - Hold (delay).
- Send the display on command
0x29
.
In 16-bit pixel mode, each pixel is represented by 16 bits (R:5-bit, G:6-bit, B:5-bit), allowing 65,536 colors. Each pixel's data is sent in two 8-bit transfers.
-
Set the drawing window from
(x1, y1)
to(x2, y2)
:- Write the set X range command
0x2A
(column address). - Send the start X coordinate (write data twice, higher byte then lower byte).
- Send the end X coordinate (write data twice, higher byte then lower byte).
- Write the set Y range command
0x2B
(row/page address). - Send the start Y coordinate (write data twice, higher byte then lower byte).
- Send the end Y coordinate (write data twice, higher byte then lower byte).
- Write the set X range command
-
Send the pixel colors for the window:
- Write the memory write command
0x2C
(indicates pixel data transmission). - Loop through all pixels and send their color data (write two times per pixel, higher byte then lower byte).
- Write the memory write command
Note: To set the color for a single pixel, set the window size to cover only that pixel’s dimensions.
For simulation, we use Proteus V8.16
. No extra libraries are needed, as it already includes STM32
parts, buttons, LEDs, and TFT LCDs.
Install Proteus
as usual, ensure it is working properly, and verify that it contains the required components.
- Create New Project with
Default Schematic
template but with PCB Layout or Firmware - Add
Power
andGround
fromTerminals
list on the side bar- Set
Power
value by double click and writing label as+3.3v
for 3.3 volts
- Set
- Open parts library (press
p
or openLibrary
>Pick Parts
) and search for any needed parts - Connect parts by extending wires using mouse dragging
- If your simulation contains Micro-controllers
- Double click the Micro-controllers
- Check that
Crystal Frequency
is set. For example:16MHz
or16M
- Choose code file as the HEX file generated from the compiler
- Run the simulation
Keil can simulate and flash code to STM chips (if a hardware programmer is available). It also provides startup code (bootstrap) for STM32 chips and supports different compilers for C++, C, and Assembly.
- Download Keil uVision (also called MDK-ARM) from the official website.
- For BluePill: Download and install the
STM32F103C6
package from the Packs Installer in Keil. Only the DFP pack is needed. - For EasyMX: Download and install the
STM32F407VG
package (v2.17.1) from this link.
- Open Keil uVision.
- Create a new project (Project > Create New uVision Project).
- In the Packs section, select
STM32F407VG
orSTM32F103C6
as the target. - In the Runtime Environment, select:
- CMSIS > Core
- Device > Startup
- For Hardware: Click on Options for Target > Debugger > select ST-Link Debugger.
- For Simulation: Click on Options for Target > Output > check "Create HEX File".
- Start coding, compiling, and building.
Note: Code can be simulated at the register level by debugging in Keil.
- Install the STM driver (
ST-Link
), a general driver for any STM debugger, using Mikro Website or ARM Website. - Install
MikroProg Suite
for flashing code. - Connect the board and check the connection using
MikroProg Suite
.
Note: The board has multiple USB ports. Connect to the one closest to the debugger (MikroProg port).
To flash the code onto the target:
- Ensure the target is connected via
MikroProg Suite
. - Flash the code using Keil uVision.
Note: After flashing, if the code does not start, reset the board.
You can see the code Here.
You can see the code Here.
You can see the code Here. This code is explained in the Interfacing Between STM32 and TFT Display part.
You can see the code Here. This image data is generated using the python script here.
You can see the code Here
- Enable GPIOD clock
- Configure pins PD0-PD3 as output mode
- loop
- Turn on LEDs using Output port
- Delay
- Turn off LEDs using Output port
- Delay
You can see the code Here
- Enable GPIOD clock
- Configure pins PD0-PD3 as output mode
- Configure pin PD11 as input mode
- Enable pulldown resistor on pin PD11 (Button)
- Loop
- Read Button from Input Port
- Turn On LEDs using output port
- Turn Off LEDs using output port
You can see the code Here
You can see the code Here