cancel
Showing results for 
Search instead for 
Did you mean: 

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

YSall.1
Senior

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?

1 ACCEPTED SOLUTION

Accepted Solutions
Peter BENSCH
ST Employee

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.

View solution in original post

9 REPLIES 9
Peter BENSCH
ST Employee

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.

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 Venmo
Up vote any posts that you find helpful, it shows what's working..

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

Thank you

PhilBrown
Associate II

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.

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 Venmo
Up vote any posts that you find helpful, it shows what's working..
PhilBrown
Associate II

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

Great work, Peter!

In my eyes this solution works for a STM32F4xx - are there similar solutions for a earlier stm32f1xx?

Ok, it is a similar work for me ... very good solution also for the connection to c-code-applications.

 

But what is the basic-idea of my question:

Are there also more basically solutions in the integration of assembler files for a final binary upload on a stm32?

 

I work with a x86_64-Linux-system...

You can use startup.s or simply ADD foo.s to your project, or in the directory for make to process. The calling conventions are pretty simple, up to four parameters in registers, and subsequent ones passed on the stack frame. ARM defines their ABI (application binary interface).

You can generate example assembler for given C code using the disassemblers in objcopy or fromelf.

The Cortex-M3 and M4 code should be very similar, the latter allowing for FPU instructions and registers when enabled, and several more advanced commands. The Cortex-M0(+) parts add a little complication as they have a more restrictive instruction set where some operations now might take two operations, and are less tolerant of misaligned memory accesses.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..