Hello,
I am a young embedded software engineer, small microcontrollers almost exclusively, C exclusively. I'm very excited about what I see in Nim, but I am having trouble translating some concepts that I could do in my sleep in embedded C or assembly. I wonder if I'm just trying to shoehorn in what I'm comfortable with in C. I want to create a statically allocated ring buffer using a template/generic/whatever so that I can instantiate it in the file in question and then use a handle to it for add and remove operations and have full/empty status covered in the add/remove routines.
I realize a ring buffer is simple, but as a first library type of exercise I would like to be able to use the implementation over and over again by including the file in other projects. I guess this means it would need two parameters if it was a generic like so maybe:
type
RingBuffer*[N,T] = ref object #N=size, T=type
buf: array[N,T]
head, tail: int
I think ref force allocates something on the heap at runtime and I'm fine with that if static allocation isn't possible but at least it can't be resizable. I'm particularly confused about how to do an add method though. Here's where I'm stuck:
proc add*[T](self: var RingBuffer[N,T], data: T) =
# some stuff
I am totally confused as to how to do this. I haven't seen any examples like this either. I've seen the one involving linked lists and plenty of examples using seqs.
Further, I would like the head and tail index variables to be the smallest width they can be and still point to anywhere in the array. I think that can be done at compile time, maybe with a process? It would be even better if the smallest power of 2 was selected automatically, provided Nim and/or the C compiler were smart enough to use bitwise and for bounds checking.
Thank you very much in advance for your help. I typically learn by reading everything I can find on a subject but there just isn't an exhaustive list of material out there for Nim yet.
Maybe I'm just too tired to think and the answer is obvious! I need to get that book... A few examples that target the use cases of embedded software would really help. I think embedded could be a breakthrough arena for Nim. I was particularly excited about Nim when I started thinking of how great it would be if the Nim compiler could target FreeRTOS or something similar using a flag like --os=freertos. That would be a game changer. Of course I'm aware you can wrap libraries, but to have it built into the language would rock. Maybe when I get the hang of this I'll try to tackle that.
I'm running Nim code on a medium size MSP430 32K Flash, 4K RAM. The MSP430F5510-STK board from Olimex.
type
RingBuffer*[N: static[int],T] = ref object #N=size, T=type
buf: array[N,T]
head, tail: int
proc add*[N: static[int],T](self: var RingBuffer[N,T], data: T) =
# some stuff
and I don't think you need ref object for your use case.
running Nim code on a medium size MSP430 32K Flash, 4K RAM. The MSP430F5510-STK board from Olimex.
Interesting. Can you access all the registers already? Do you use GC? Your project based on https://github.com/lonetech/nim-msp430 ?
Circular buffer should be your smallest problem I guess :-)
Note, Dom's book has no microcontroller chapter, it is more intended for PC programming.
Thank you for the reply. My concern was that the method has the N generic parameter in it. Does the compiler just figure that out and so the usage is as simple as something like:
#----------- circBuffer.nim
import ringbuffer
var myBuf*: RingBuffer[N: 8, T: uint8]
proc someProcess*() =
#some stuff
myBuf.add(0x03)
#----------- main.nim
import circBuffer
proc main =
while true:
circBuffer.someProcess()
circBuffer.myBuf.add(0x04)
Stefan,
I think I can access all the registers. I'm in the early stages. I did start off with that repo, but have modified it quite a bit. There was a problem with the header file for the device and it was making zero initialized pointers to registers, I think. The bss section had a bunch of symbols that were meant to be the same as #defined pointers to memory locations. I haven't committed it or anything yet as I'm not very far along. Basically I got it to compile, read the assembly to see that it was doing what I wanted, particularly in regards to an interrupt routine, and then loaded it onto the board and made sure nothing exploded. There's not even a blinking light yet. The assembly looks good except that the entry into my main routine has several steps. It goes from a main routine, which calls a NimMain() routine, which then calls my main, main_818410831058, which loops in place. That seems unnecessary to me, but not a huge deal I guess.
I am using the MSPGCC compiler, which I think is intentionally neutered as they want you to buy their compiler. The assembly looks good though. Accessing the P2IV register looks correct in the assembly. I don't know how to force Nim to tell the C compiler to make a jump table, which is what I would do. There are compiler intrinsics for that sort of thing, so I could use the FFI. The number of options is limited and a case statement would only have to handle powers of 2.
As for garbage collection... at my workplace we don't use any dynamic memory allocation. Everything is statically allocated. We don't make products that are classified as "safety critical" but they are high reliability devices and there is liability if a device doesn't work when it's supposed to. Static allocation makes sense for many of the embedded systems that I work on at least. Everything is bare metal. It seems like the concerns in the embedded world are quite different from those of the PC world. :) That's alright though. I'll slog away at it with some help from kind people like you and then there will be more information out there for the masses. I might even try and resurrect my blog haha. Also, allocating memory on a heap is non-deterministic I think in terms of time. That makes it harder to reason about systems in which I literally have to count cycles sometimes to meet execution deadlines.
Also, I was talking to a coworker about the nature of special function registers. In C, the macros for registers in the header files expand to dereferences of pointers to volatile memory. Nim has the volatileLoad and volatileStore functions instead. I'm not sure how I feel about that. I think I like it better actually. Time will tell.
Maybe I just don't know about it, but the option of using the language features that use the heap like seq but in a statically allocated memory way would be nice. It would just prevent the memory allocated for the seq from being resized.
Yes, N is automatically inferred at compile-time. I also don't think you need to use the heap but a heap object initialized once and never deallocated works fine too I guess.
Here you can find a definition from scratch of a custom static array type but that works like a seq from the outside. I needed it to improve the performance of my tensor library (heap allocations in loop are performance killers): https://github.com/mratsim/Arraymancer/blob/master/src/tensor/backend/metadataArray.nim#L17