Hello,
the documentation has some information on cross-compiling for embedded systems. I'd like to use nim on an STM32 ARM Cortex-M4 microcontroller. It's not entirely clear to me how to configure nim to cross-compile using arm-none-eabi-gcc.
Is there some up to date information for cross-compiling nim like this for a cortex-m4 cpu with arm-none-eabi-gcc?
Here is my nim.cfg
os = "standalone"
cpu = "arm"
gc = "arc"
define = "useMalloc"
define = "release"
opt = "size"
passC = "-mcpu=cortex-m4 -march=armv7e-m -mtune=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -mthumb -ffunction-sections -fdata-sections -flto"
passL = "-mcpu=cortex-m4 -march=armv7e-m -mtune=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -mthumb -ffunction-sections -fdata-sections -flto"
nimcache=nimcache
arm.standalone.gcc.path = "/usr/bin"
arm.standalone.gcc.exe = "arm-none-eabi-gcc"
arm.standalone.gcc.linkerexe = "arm-none-eabi-gcc"
I'm overriding the -mtune and -march directive to gcc with the --passC option because nim somehow includes -mtune=generic and -march=x86-64 even though I chose standalone and arm for compilation.
The above config manages to create the object files in nimcache, however linking fails because nim invokes the gcc linker and can't find libdl. It seems that this is configured by the global /etc/nim/nim.cfg.
Would it be easier to let nim generate the C code only and then use a more traditional Makefile based C compilation & linking workflow?
Have you taken a look at https://github.com/elcritch/nesper config files ?
Here's how it compile :
--gc:arc
--os:freertos
-d:use_malloc
-d:no_signal_handler
--debugger:native
--threads:on
--tls_emulation:off
--hint:conf:off
--hint:SuccessX:off
It is possible to override the cflags/ldflags set by the global nim.cfg, using arm.standalone.gcc.options.linker and arm.standalone.gcc.options.always
Here's an example of how I do it for the GBA. (well I use a less messy script nowadays but this one has all the flags in one place so it's easier to show people)
Personally I've found that "--os:standalone --gc:none" or "--os:any --gc:arc" are the two combinations that work for me with Nim 1.4, though --os:standalone --gc:arc would be ideal so certainly worth giving it a try (I think it's fixed in devel?)
Hi, thanks for the replies. I didn't realize the default flags could be easily overridden.
How do you handle interrupts in nim? Say my startup code file defines a symbol called UART_InterruptHandler which is placed at a certain hardware memory address corresponding to that interrupt handler.
Is it enough to have something this?
proc UART_InterruptHandler() : =
echo "Hi"
Thanks for the reply! I've tried to implement some STM32F4 startup code in nim but alas the generated code doesn't quite work out.
In this snippet I'm trying to import the linker defined symbol "_estack" (the highest stack address i.e. end of RAM) and make the compiler place it in the .isp linker section with the annotation. This works in the C code that I'm envisioning, unfortunately the nim compiler doesn't output the correct C code.
var estack {.importc: "_estack".} : cuint
let stacktop {.exportc, codegenDecl: "__attribute__ ((used, section (\".isp\"))) $# $#".} : ptr cuint = unsafeAddr(estack)
The generated C code from the nim snippet above is
extern unsigned int _estack;
__attribute__ ((used, section (".isp"))) unsigned int* stacktop;
N_LIB_PRIVATE N_NIMCALL(void, playground_startupInit000)(void) {
{
stacktop = (&_estack);
}
}
The C code that I'd like to generate is this
extern unsigned int _estack;
__attribute__ ((used, section (".isp"))) unsigned int *stacktop = &_estack;
i.e. have the value assignment happen directly at declaration instead of in some startup routine.
Is there a way to do this?
Unfortunately it's impossible to do this without emit right now.
I pushed for this kind of thing a couple of years ago, some progress was made on it (some basic types such as array of int may now be initialised statically) but it's still not really usable for many purposes - dispatch tables in ROM being my top use case. See:
For now you can do something like this:
{.emit:"""
extern unsigned int _estack;
__attribute__ ((used, section (".isp"))) unsigned int *stacktop = &_estack;
""".}
let stacktop {.importc, nodecl.}: ptr cuint
This should be fine as long as you don't need to export stacktop for use in other modules. If you do, then you may need a workaround like this.
Thanks again for the great reply. I'd come across those github issues before and wasn't sure if anything had improved yet.
Compiling the startup code as a separate C file indeed seems like the best option, it would be pointless to have something like this
# startup.nim
{.emit """
// contents of startup.c
""".}
I was just interested if there was a good way to do it in nim, as that would allow the entire application (startup code, peripheral drivers, business logic) to be written in the same language and seemed like a neat goal.
Compiling the startup code as a separate C file indeed seems like the best option, it would be pointless to have something like this
There's some benefit of having an emit in Nim as it generally makes re-using it in other Nim programs easier. E.g. if you put it in a startup.nim it's easier to move to a Nim support library later.
Likely dealing with estack/stacktop in pure Nim might still be possible. Maybe try a const instead of let? I've had good luck defining things from C as a var or function. Depending on what you need, you could create a Nim (inline) function that returns the address of estack? That'd work if you don't need to modify stacktop during C initialization.
You will also need you to call nimMain() to ensure Nim init functions are called. That playground_Init000 code would generally called in that function. See: https://github.com/elcritch/nesper/blob/f8e6e93649061e3a5d8af75b0e61ce08e21ad815/src/nesper/general.nim#L21
Personally I've moved to compiling Nim to C and letting the MCU build system take it from there, but that's best for for esp32 like targets that do a bunch of obscure init code. Hopefully you can get it all working!
This might be useful too: https://gcc.gnu.org/onlinedocs/gccint/Initialization.html
The C compiler might turn the stacktop code into a C initialization function too. I'm not sure if the compiler can set up the pointer address during linking.