Skip to main content
YSall.1
Senior
June 17, 2022
Solved

How to run assembly code in stm32 cube ide? (NUCLEO F446RE)

  • June 17, 2022
  • 4 replies
  • 20011 views

I created a little assembly project on Uvision5 but I fastly reached the code size limit and I can't manage for my assembly code to run on stm32cubeide, do you know how to run assembly code please?

This topic has been closed for replies.
Best answer by Peter BENSCH

Yes, you can write assembler code with the STM32CubeIDE and compile it successfully. However, this is very rarely done because the effort required to write the program is much more complex and, above all, time-consuming compared to C programming.

In principle, you can insert assembler into any CubeIDE project, be it an existing one or a new one. However, it should be noted that the syntax of the GNU assembler probably differs in some respects from that of other assemblers.

Basically, you can add assembly code by creating it in STM32CubeIDE:

  • creating the header file e.g. assembler.h
/*
 * assembler.h
 *
 */
 
#ifndef APPLICATION_USER_CORE_ASSEMBLER_H_
#define APPLICATION_USER_CORE_ASSEMBLER_H_
 
extern void ASM_SystemInit(void);
extern void ASM_Function(void);
 
#endif /* APPLICATION_USER_CORE_ASSEMBLER_H_ */
  • adding it to main.c:
/* USER CODE BEGIN Includes */
#include "assembler.h"
/* USER CODE END Includes */
  • create the file assembler.s in the source code block

The content of assembler.s might look like this (this small example also contains an initialisation, as it was a pure ASM project):

/*
 * assembler.s
 *
 */
 
 .syntax unified
 
 .text
 .global ASM_SystemInit
 .global ASM_Function
 .thumb_func
 
 .equ RCC_BASE_ADDR, 0x40021000
 .equ o_RCC_CR, 0x00 // offset to RCC_CR
 .equ o_RCC_CFGR, 0x08 // offset to RCC_CFGR
 .equ o_RCC_PLLCFGR, 0x0C // offset to RCC_PLLCFGR
 .equ o_RCC_AHB2ENR, 0x4C // offset to RCC_AHB2ENR
 
 .equ GPIOA_BASE_ADDR,0x48000000
 .equ o_GPIOA_MODER, 0x00 // offset to GPIOA_MODER
 .equ o_GPIOA_OTYPER, 0x04 // offset for GPIOA Output PP/OD
 .equ o_GPIOA_OSPEEDR,0x08 // offset for GPIOA Output Speed
 .equ o_GPIOA_PUPDR, 0x0C // offset for GPIOA PU/PD
 
 .equ GPIOA_BSRR, 0x48000018 // GPIOA Bit set reset register
 
 .equ COUNTER, 10000
//------------------------------------------------------------------------------------------------
 ASM_SystemInit:
 
 // Clock configuration, 16 MHz HSI as PLL clock source
 LDR R1, =RCC_BASE_ADDR // Load RCC configuration register address in R1
 
 LDR R0, =0x06000802 // PLLPDIV=0, PLLR=8, PLLQ=2, PLLP=7, PLLN=16, PLLM=1, HSI16->PLL
 STR R0, [R1, o_RCC_PLLCFGR] // store into RCC_PLLCFGR
 
 ORR R0, #0x01000000 // set PLLREN (bit 24)
 STR R0, [R1, o_RCC_PLLCFGR] // store into RCC_PLLCFGR
 
 LDR R0, [R1, o_RCC_CR] // read RCC_CR
 ORR R0, #0x01000000 // set PLLON (bit 24)
 STR R0, [R1, o_RCC_CR] // store into RCC_CR
 
 ASM_SystemInit_1:
 LDR R0, [R1, o_RCC_CR] // read RCC_CR
 LSLS R0, #6 // Logical Shift Left by 6
 BPL.N ASM_SystemInit_1 // Branch if N flag = 0 (= positive or zero, see PM0214, table 24)
 
 [...]
 
 BX LR // Return from function
 
//------------------------------------------------------------------------------------------------
 ASM_Function:
 
 turnON:
 // Set output high
 LDR R1, =GPIOA_BSRR
 LDR R0, =0x00000020
 STR R0, [R1]
 
 LDR R2, =COUNTER
 delay1:
 SUBS R2, R2, #1 // R2 = R2 - 1, R2 = 0?
 BNE delay1 // stay in loop delay1 if not equal to zero
 
 turnOFF:
 // Set output low
 LDR R0, =0x00200000
 STR R0, [R1]
 
 LDR R2, =#COUNTER
 LSL R2, #6 // Logical Shift Left
 
 delay2:
 SUBS R2, R2, #1 // R2 = R2 - 1, R2 = 0?
 BNE delay2 // stay in loop delay1 if not equal to zero
 
 delayDone:
 B turnON // Jump to turnON
  • finally start this assembler routines e.g. in main.c with the following statements, while the first is a function that returns while the second is not exited (infinite loop):
int main(void)
{
 /* USER CODE BEGIN 1 */
 ASM_SystemInit();
 ASM_Function();
 /* USER CODE END 1 */
}

Did that give you some inspiration and answer your question?

Regards

/Peter

4 replies

Peter BENSCH
Peter BENSCHBest answer
Technical Moderator
June 17, 2022

Yes, you can write assembler code with the STM32CubeIDE and compile it successfully. However, this is very rarely done because the effort required to write the program is much more complex and, above all, time-consuming compared to C programming.

In principle, you can insert assembler into any CubeIDE project, be it an existing one or a new one. However, it should be noted that the syntax of the GNU assembler probably differs in some respects from that of other assemblers.

Basically, you can add assembly code by creating it in STM32CubeIDE:

  • creating the header file e.g. assembler.h
/*
 * assembler.h
 *
 */
 
#ifndef APPLICATION_USER_CORE_ASSEMBLER_H_
#define APPLICATION_USER_CORE_ASSEMBLER_H_
 
extern void ASM_SystemInit(void);
extern void ASM_Function(void);
 
#endif /* APPLICATION_USER_CORE_ASSEMBLER_H_ */
  • adding it to main.c:
/* USER CODE BEGIN Includes */
#include "assembler.h"
/* USER CODE END Includes */
  • create the file assembler.s in the source code block

The content of assembler.s might look like this (this small example also contains an initialisation, as it was a pure ASM project):

/*
 * assembler.s
 *
 */
 
 .syntax unified
 
 .text
 .global ASM_SystemInit
 .global ASM_Function
 .thumb_func
 
 .equ RCC_BASE_ADDR, 0x40021000
 .equ o_RCC_CR, 0x00 // offset to RCC_CR
 .equ o_RCC_CFGR, 0x08 // offset to RCC_CFGR
 .equ o_RCC_PLLCFGR, 0x0C // offset to RCC_PLLCFGR
 .equ o_RCC_AHB2ENR, 0x4C // offset to RCC_AHB2ENR
 
 .equ GPIOA_BASE_ADDR,0x48000000
 .equ o_GPIOA_MODER, 0x00 // offset to GPIOA_MODER
 .equ o_GPIOA_OTYPER, 0x04 // offset for GPIOA Output PP/OD
 .equ o_GPIOA_OSPEEDR,0x08 // offset for GPIOA Output Speed
 .equ o_GPIOA_PUPDR, 0x0C // offset for GPIOA PU/PD
 
 .equ GPIOA_BSRR, 0x48000018 // GPIOA Bit set reset register
 
 .equ COUNTER, 10000
//------------------------------------------------------------------------------------------------
 ASM_SystemInit:
 
 // Clock configuration, 16 MHz HSI as PLL clock source
 LDR R1, =RCC_BASE_ADDR // Load RCC configuration register address in R1
 
 LDR R0, =0x06000802 // PLLPDIV=0, PLLR=8, PLLQ=2, PLLP=7, PLLN=16, PLLM=1, HSI16->PLL
 STR R0, [R1, o_RCC_PLLCFGR] // store into RCC_PLLCFGR
 
 ORR R0, #0x01000000 // set PLLREN (bit 24)
 STR R0, [R1, o_RCC_PLLCFGR] // store into RCC_PLLCFGR
 
 LDR R0, [R1, o_RCC_CR] // read RCC_CR
 ORR R0, #0x01000000 // set PLLON (bit 24)
 STR R0, [R1, o_RCC_CR] // store into RCC_CR
 
 ASM_SystemInit_1:
 LDR R0, [R1, o_RCC_CR] // read RCC_CR
 LSLS R0, #6 // Logical Shift Left by 6
 BPL.N ASM_SystemInit_1 // Branch if N flag = 0 (= positive or zero, see PM0214, table 24)
 
 [...]
 
 BX LR // Return from function
 
//------------------------------------------------------------------------------------------------
 ASM_Function:
 
 turnON:
 // Set output high
 LDR R1, =GPIOA_BSRR
 LDR R0, =0x00000020
 STR R0, [R1]
 
 LDR R2, =COUNTER
 delay1:
 SUBS R2, R2, #1 // R2 = R2 - 1, R2 = 0?
 BNE delay1 // stay in loop delay1 if not equal to zero
 
 turnOFF:
 // Set output low
 LDR R0, =0x00200000
 STR R0, [R1]
 
 LDR R2, =#COUNTER
 LSL R2, #6 // Logical Shift Left
 
 delay2:
 SUBS R2, R2, #1 // R2 = R2 - 1, R2 = 0?
 BNE delay2 // stay in loop delay1 if not equal to zero
 
 delayDone:
 B turnON // Jump to turnON
  • finally start this assembler routines e.g. in main.c with the following statements, while the first is a function that returns while the second is not exited (infinite loop):
int main(void)
{
 /* USER CODE BEGIN 1 */
 ASM_SystemInit();
 ASM_Function();
 /* USER CODE END 1 */
}

Did that give you some inspiration and answer your question?

Regards

/Peter

In order to give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.
YSall.1
YSall.1Author
Senior
June 20, 2022

Thank you very much Peter for your accurate explanation, it worked for me!

Tesla DeLorean
Guru
June 17, 2022

Most projects have a startup.s file, start by adding your code in there. At some point you can trim other files, and remove, and alter the linker script.

Publicly (.global) export the symbols so you can call as C functions.

GNU/GAS syntax is markedly different from KEIL/MDK, but the examples in startup.s should give you a understanding/template to follow.

Watch how you do comments and labels, and how it does functions without PROC/ENDP

Tips, Buy me a coffee, or three.. PayPal VenmoUp vote any posts that you find helpful, it shows what's working..
YSall.1
YSall.1Author
Senior
June 20, 2022

Thank you

PhilBrown
Associate
January 30, 2023

The college has classes on assembler programming here, but they use keil.

that is pretty much windows only.

(yeah I know, "wine", but.. come on)

It would be SO NICE if someone could just set up and share a barebones assembly programming project in CUBE, that does nothing more than blink the lights, but then didnt have any of the extra stuff.

For example, showing off the SUPER_small code chunks in

https://www.mikrocontroller.net/articles/ARM-ASM-Tutorial

Pure assembly, with no #ifdefs or any C-like stuff.

I looked at the base GPIO example project for CUBE, and it has approximately a gazillion files, when all that is really needed is.. maybe 4, including a makefile?

Then more colleges could use CUBE for this sort of thing instead of keil.

Tesla DeLorean
Guru
January 30, 2023

One might hope college level professors could code a solution to the course they are teaching...

Peter has provided a good starting point.

Tips, Buy me a coffee, or three.. PayPal VenmoUp vote any posts that you find helpful, it shows what's working..
PhilBrown
Associate
February 5, 2023

no, he didnt. He replied to the actual question with basically, "I dont think you should do that, try this other way instead".

And he wrote 30 lines of code that were not asked for, instead of a much simpler output, that would actually answer the question, centered around something like

main.s

main:
 nop
 b main