[HN Gopher] MicroZig: Unified abstraction layer and HAL for Zig ...
       ___________________________________________________________________
        
       MicroZig: Unified abstraction layer and HAL for Zig on several
       microcontrollers
        
       Author : homarp
       Score  : 237 points
       Date   : 2024-02-28 17:14 UTC (1 days ago)
        
 (HTM) web link (github.com)
 (TXT) w3m dump (github.com)
        
       | nippoo wrote:
       | The fragmented state of the microcontroller market is pretty
       | dire. Especially as there's more higher-level features being
       | baked in to modern micros, every different manufacturer is
       | implementing different APIs to do the same basic thing (whether
       | that be I2C or things like MIPI-CSI). Having a unified API would
       | really help portability (and would enable people to build
       | workable emulators!)
        
         | ComputerGuru wrote:
         | fyi since you seem to be interested in this: rust has a few
         | options including the embedded_hal crate with impls for
         | everything from various microcontrollers to desktop linux.
         | 
         | https://docs.rs/embedded-hal/latest/embedded_hal/
        
           | the__alchemist wrote:
           | Sounds great in theory (writing MCU-agnostic libraries for
           | peripherals), but I've found it unusable in practice due to
           | the clunky API and incompatibility with DMA, interrupt-based-
           | flow, least-common-denominator functionality etc.
           | 
           | The new direction that library and related is Async with
           | Embassy, which... is also not something I want to use.
        
           | KallDrexx wrote:
           | Despite my day job involving rust, I struggled pretty hard
           | getting Rust working adequately for my little GPU project on
           | both pi pico and an esp32s3. Both had debugg-ability issues,
           | missing features that would have required advanced FFI
           | integrations to be added, and things just wouldn't even work.
           | I spent a week on it making no progress, and with only an
           | hour an evening I had to do anything it was frustrating.
           | 
           | I then spent 2 days brushing up on C and got things up and
           | running with ESP-IDF painlessly. I've been iterating really
           | fast on C just fine, and the few times I wish I had Rust's
           | features have been eclipsed by the advanced ESP-IDF api's
           | I've needed to use that didn't have support in the Rust HALs.
        
             | ComputerGuru wrote:
             | My experience wasn't stellar but it wasn't like yours.
             | There's of course no comparison between the speed of
             | getting up and running with the official sdk and toolchain,
             | but it wasn't _so_ miserable.
        
         | petra wrote:
         | Modm.io is a a unified API for microcontrollers.
        
       | MatthiasPortzel wrote:
       | I started a new project with MicroZig just the other week. It's
       | working (with the RP2040), and that alone is a huge deal, since
       | writing Zig is much more ergonomic than C with Arduino. It makes
       | cross-compiling for testing in simulation locally trivial as
       | well.
       | 
       | However, the project is still not very mature. They just
       | refactored into the repo you see since I started using it.
       | Support for RP2040 is good, but the HAL for other
       | microcontrollers seems less supported. (And it seems to be
       | written in such a way that code isn't shared between HALs for
       | different microcontrollers.) It's targeting stable Zig (instead
       | of HEAD), which is an unusual choice for Zig projects, since Zig
       | is also in an immature state and updates rapidly. There's zero
       | documentation except for the source, but that's par for Zig
       | projects.
       | 
       | Zig has the potential to be huge for embedded, once these APIs
       | are fleshed out and stabilize. It's already more enjoyable than
       | writing C.
        
         | wredue wrote:
         | I just recently switched off stable to 0.12 and I honestly
         | didn't run in to too much in the way of changes. It is a bit
         | unusual for zig projects currently BUT zig changes are slowing.
         | 
         | Additionally, Andrew himself has stated he wants 0.12 to be a
         | version where zig devs should feel comfortable pinning to
         | stable. I know stable now is 0.11, but the changes are not
         | egregious at the moment. Not sure if that's still the deal.
        
         | mikepurvis wrote:
         | I'm interested in this too, though I've had mixed experiences
         | in the past using third party HALs for microcontrollers only to
         | discover that they have gaps in API coverage or can only expose
         | the most common use-cases. There was always lot of examples and
         | documentation for the "easy" stuff like GPIOs, timers, ADCs,
         | serial port, etc, but once you got into USB, Ethernet, or RTOS
         | integration then things rapidly got much scarier.
        
         | tredre3 wrote:
         | Arduino is C++, not C! Of course that changes nothing to what
         | you said :) .
        
         | zamalek wrote:
         | > Support for RP2040 is good,
         | 
         | I've done a fair bit of stuff with embedded Rust and the main
         | reason for this is manufacturer documentation. The RP2040 is
         | really in its own tier of documentation quality, nothing comes
         | close. Other chipsets are so bad that the Rust SVD codegen has
         | a way for maintainers to fix errata.
         | 
         | I've concluded that I'm only going to use RP2040s going
         | forward.
        
       | Cyberdog wrote:
       | I got this working on a RPi 2040 during my most recent
       | microcontroller phase several months back. I know Zig can be
       | oddly divisive here on HN but can we at least agree that it's a
       | good thing to have alternatives to Python in this space?
        
         | nrclark wrote:
         | In the microcontroller space, I think Zig is more of a
         | replacement for C/C++. It's a compiled language, intended to
         | produce optimized code that hits cost/performance targets.
         | 
         | Micropython and friends are well-suited for education/hobby
         | use, protoptyping, and maybe some commercial applications that
         | aren't sensitive to cost or realtime characteristics. But since
         | they don't really compete with C/C++, they also don't really
         | compete with Zig.
        
           | throwawaymaths wrote:
           | it's true that micropython doesn't compete with zig, but zig
           | is simple enough/no-fuss enough (imagine deployment
           | strategies baked with one line in a build.zig) that it might
           | compete with micropython.
        
           | Cyberdog wrote:
           | You're right of course, but if you pick up a 2040 and you're
           | a newbie to microcontrollers (as I am), all of the
           | documentation and first-party tools push you towards using
           | Python, which I've never been too fond of and I started
           | looking for alternatives as soon as I finished the last
           | chapter of the manual. The alternatives are always there but
           | they presume a level of experience (and C skills) that I just
           | don't have. Having experimented with Zig I was glad to
           | eventually find MicroZig. I remember that using it wasn't
           | quite as easy as "push button, watch LED blink" as it was
           | when I was using Python and Thonny but if a n00b like me
           | could figure it out, anyone can.
        
           | pjmlp wrote:
           | BASIC, Pascal have been doing it for decades already, to the
           | point the likes of Mikroe and Parallax don't have any issue
           | staying in business selling compilers.
        
         | jakewins wrote:
         | Agreed! I've similarly had good experiences with Rust on the
         | rp2040, using embassy which builds on the rust HALs.
        
         | KerrAvon wrote:
         | God, yes. Ruby is also at least somewhat usable in the space,
         | FWIW; I've done RPi hacking with it in the past.
        
       | cnln wrote:
       | I literally just posted on my blog about my adventures writing an
       | OLED driver in MicroZig, it was a really fun little project
       | 
       | https://news.ycombinator.com/item?id=39540761
        
         | dang wrote:
         | That's great and if you email hn@ycombinator.com in, say, a
         | couple months*, we can send you a repost invite for it.
         | 
         | * i.e. enough time to flush the hivemind caches so the thread
         | won't feel repetitive
        
           | cnln wrote:
           | Great, thanks!
        
             | unwind wrote:
             | Meta: you have typod the name of the microcontroller as
             | _RP4020_ twice, might be worth fixing. Nice post,
             | otherwise!
             | 
             | Edit: typos, ironically. Mobile phone text editing ~ftw.
        
               | cnln wrote:
               | Thanks, I'll fix that
        
           | sgt wrote:
           | Isn't it better just to repost with a query parameter like
           | ?foobar ?
        
             | dang wrote:
             | Oh the issue isn't a technical one. It's that once there's
             | been a big discussion about a topic (in this case MicroZig)
             | the community appetite for a second thread about roughly
             | the same thing (in this case, another MicroZig thread)
             | drops off sharply. However, if you wait long enough then it
             | becomes fresh again and a new thread is ok. That's what I
             | facetiously call letting the hivemind caches clear.
        
       | spiritplumber wrote:
       | You know what you doing! Move Zig for great justice.
        
         | just_steve_h wrote:
         | YOU HAVE NO CHANCE TO SURVIVE MAKE YOUR TIME
        
       | spiritplumber wrote:
       | Are there any plans to put this on the Parallax Propeller as
       | well?
        
       | UncleOxidant wrote:
       | Are there similar efforts for other languages? Or could this be a
       | starting point for a language-neutral, multi-
       | board/microcontroller HAL?
        
         | skybrian wrote:
         | There are lots of similar efforts, but they tend to be half-
         | finished on many platforms that they supposedly support.
         | 
         | (Also, how is this one language-neutral?)
        
           | UncleOxidant wrote:
           | It's not langauge-neutral. But I suppose one could imagine
           | zig at the bottom level on the bare metal and other languages
           | being able to call into that zig layer.
        
       | TimSchumann wrote:
       | Word on the street is the badges at the next Software You Can
       | Love conference will be running this.
        
       | jeff_ciesielski wrote:
       | I used Zig (not MicroZig, just rolled my own HAL) for the
       | bootloader and firmware on a soft RISC-V SOC + custom peripherals
       | recently and had somewhat mixed, though positive feelings about
       | it.
       | 
       | On the positive side:
       | 
       | - As a 'safer c', getting things up and running was a breeze,
       | writing code largely felt intuitive.
       | 
       | - The additions to C (slices/iterators, enhanced structs,
       | arbitrarily sized integers) are excellent
       | 
       | - It produces fairly small firmware images (useful when stuffing
       | a boot rom in logic/EBRAM)
       | 
       | - Easier (than C IMO) to get up and running with formatted IO vs
       | retargeting libc
       | 
       | - Comptime is neat, and you can build some decent low-cost
       | abstractions with it (ex: I built a comptime heavy write-through
       | cache for key-value storage that required very little overhead
       | and largely self-generated based on a simple struct)
       | 
       | - I really enjoy the use of structs for function+data
       | organization. It maps well to hardware instances, giving you an
       | 'object' like feeling without OOP ick.
       | 
       | On the negative side:
       | 
       | - The compiler is still a seriously moving target. Upgrading
       | sometimes meant rather large refactors.
       | 
       | - Documentation is somewhat poor IMO.
       | 
       | - As a long time user of Nim (including on really lean embedded
       | targets), compared to hygienic macros, comptime falls way short.
       | 
       | - The lack of first class interfaces/traits/typeclasses is not my
       | favorite. The currently suggested alternatives are so un-
       | ergonomic I'd almost call them hostile.
       | 
       | All-in-all, I'm excited to see where Zig ends up. After nearly 20
       | years writing embedded code I'm really (really really really)
       | tired of C. The embedded systems community really needs to
       | embrace better tools.
        
         | AceJohnny2 wrote:
         | > _After nearly 20 years writing embedded code I 'm really
         | (really really really) tired of C. The embedded systems
         | community really needs to embrace better tools._
         | 
         | Hear hear, brother!
         | 
         | I get particularly frustrated about the second point, and I've
         | made it my career goal to get my teams to update their tools
         | and processes. For example, when I joined my current team,
         | _they didn 't compile debug symbols and didn't know how to use
         | a debugger on our system!_ Hell, in 2024 I still have
         | colleagues who prefer to use a .dis and .map file than leverage
         | the debug symbols... "What is this "mixed code and disassembly"
         | display you speak of"
        
           | staunton wrote:
           | I know people who insist on putting register addresses (in
           | hex) in their code rather than make a variable because "it's
           | easier to debug by comparing to the user guide. If it was a
           | variable, you would have to look up its value every time". It
           | looks somewhat like this:                  *(int*)(0xf003) |=
           | 39;
        
             | AceJohnny2 wrote:
             | Oh deer god.
             | 
             | We've never been _that_ bad, but we are still using macros
             | for registers instead of block structure pointers, which we
             | have access to...
             | 
             | (although I'll grant that structures can be risky due to
             | undefined packing rules)
        
             | the__alchemist wrote:
             | So, my favorite pattern in Rust for this is to use a u8,
             | (or u16 etc)-repr Enum for register addresses and values.
             | So, you'd do something like, assuming a direct register API
             | vice a wrapper:                 write_register(Reg::Config
             | as u8, value)
             | 
             | Where `value` may be constructed from variables or, wait
             | for it... Might be a binary literal because it's easier to
             | compare to the datasheet if it's a one-off vice a general
             | API. If it's a general API, it is probably handled with a
             | config struct etc, where each field is a u8-repr enum.
             | 
             | Code like this should IMO always have a reference to the
             | relevant DS table in comments, and probably an explanation
             | of why you're setting the bits that way.
        
         | cgh wrote:
         | > The lack of first class interfaces/traits/typeclasses is not
         | my favorite. The currently suggested alternatives are so un-
         | ergonomic I'd almost call them hostile.
         | 
         | The use of anytype as a sort of universal interface is my least
         | favourite part of Zig. I've seen enough griping about it that
         | I'm hopeful something happens here.
        
         | wredue wrote:
         | >- As a long time user of Nim (including on really lean
         | embedded targets), compared to hygienic macros, comptime falls
         | way short.
         | 
         | In what way? Comptime should be generally capable of anything
         | macros are.
         | 
         | >- The lack of first class interfaces/traits/typeclasses is not
         | my favorite. The currently suggested alternatives are so un-
         | ergonomic I'd almost call them hostile.
         | 
         | Terribly difficult to implement without breaking a major
         | language tenant of "no hidden control flow".
         | 
         | But I found that once I left behind OO style of thinking, I
         | haven't missed this all that much. For the rare time I do
         | generalize like this, you can literally just check at comptime
         | that the passed in type provides the necessary decls. It's not
         | terribly complex or hostile (although tooling could use some
         | work around it)
        
           | jeff_ciesielski wrote:
           | > In what way? Comptime should be generally capable of
           | anything macros are.
           | 
           | I started to reply to this with 'Comptime is generally
           | capable of doing anything that Nim's templates can accomplish
           | (but not it's macros)', but I stopped myself because even
           | though Zig's comptime is more akin to Nim's templates than
           | its macros (in my opinion), Nim templates are more powerful
           | as they allow you to embed arbitrary blocks of code to
           | implement constructs similar to python's context managers
           | (which I'm fairly certain you can't do with comptime).
           | 
           | W/R/T Macros vs Comptime, you can't create arbitrarily
           | complex DSLs with comptime the way you can with a Nim's
           | macros as you don't have full control over AST generation.
           | 
           | All that said, the power you'd get from a Macro or Template
           | system like Nim's don't really jive with Zig's whole "no
           | hidden control flow" thing.
           | 
           | >Terribly difficult to implement without breaking a major
           | language tenant of "no hidden control flow".
           | 
           | I dunno if I agree with that, even very simple rust-like
           | traits that simply enforce that a struct implemented a given
           | interface at compile time (static dispatch only) would go a
           | long way without compromising obvious control flow IMO.
           | 
           | >But I found that once I left behind OO style of thinking, I
           | haven't missed this all that much. For the rare time I do
           | generalize like this, you can literally just check at
           | comptime that the passed in type provides the necessary
           | decls. It's not terribly complex or hostile (although tooling
           | could use some work around it)
           | 
           | Respectfully, I don't view it as OO thinking (I think
           | typeclasses come from SML...). Making polymorphism reasonably
           | ergonomic goes a long way towards code reuse and (again, only
           | my humble opinion here) would help with what some of the
           | folks in this comment section are talking about w/r/t code
           | re-use and generalizing a HAL layer in a consistent way
           | without forcing users (or library authors) to write a bunch
           | of ad-hoc code to check that functions exist on a given
           | struct, or manually implementing dispatch tables.
        
             | naasking wrote:
             | I would generally agree that polymorphism is necessary,
             | except maybe for embedded systems. You want more of a
             | closed world system there, not an open world system that
             | permits arbitrary extensions.
             | 
             | By open world, I'm thinking of abstractions like closures
             | and interfaces which permit arbitrary extension and require
             | dynamic dispatch. Given any fixed set of such abstractions,
             | you can simulate in a closed world system via something
             | like defunctionalization during whole program compilation,
             | which is what you do on embedded systems. You can probably
             | do a defunctionalization transformation with comptime, and
             | that gets you better visibility on the state of the system
             | in a way that's not possible with a truly open world
             | system.
        
             | renox wrote:
             | > All that said, the power you'd get from a Macro or
             | Template system like Nim's don't really jive with Zig's
             | whole "no hidden control flow" thing.
             | 
             | Only if you do it wrong.. It's quite simple to mandate a
             | special character which indicates that "something's
             | happening here", for example you could have y = a #+ b with
             | + being a function operating on matrixes, the # indicating
             | that this is a function called not the regular operator.
        
         | bsder wrote:
         | > - As a long time user of Nim (including on really lean
         | embedded targets)
         | 
         | Could you expound on how you used Nim on "really lean embedded
         | targets"?
        
           | girvo wrote:
           | Nim is "just C" at the end of the day, so by leveraging the
           | ---compileOnly flag you can use Nim anywhere you can use C
           | 
           | Now that arc/orc are the default memory management strategy,
           | it's even possible to keep some of the niceties from the
           | standard library when doing so -- but that will depend on
           | your target of course.
           | 
           | Even going the "no heap allocation" route is totally
           | feasible, you sort of end up using Nim as a nicer C syntax
           | with extra features. All of the libraries we write for work
           | have two interfaces, one that returns (possible heap
           | allocated) results, and one that takes a buffer pointer (as a
           | var openArray[T] param) in instead.
        
         | vrinsd wrote:
         | Hey Jeff,
         | 
         | Can you share what your experience has been like with Nim on
         | embedded targets? Both Nim and Zig are on my wishlist to try
         | out for embedded but I'm doing C and RTOSes for the next few
         | projects.
         | 
         | Ada with Ravenscar seems like another "seems like it solves a
         | lot of common problems intelligently" but I haven't had much
         | time to try it being a simple proof of concept.
        
           | girvo wrote:
           | I use Nim at work for embedded firmware development right
           | now, and we evaluated Zig but it was a year and a half ago
           | when we started the project, Zig just wasn't as far along as
           | it is now.
           | 
           | I'm currently in the process of writing a nice HAL/dev
           | framework agnostic FreeRTOS binding in Nim, which maybe
           | you'll find useful once we can post it?
        
             | SV_BubbleTime wrote:
             | Do neither of these languages have better concurrency tools
             | than porting FreeRTOS?
        
               | davidzweig wrote:
               | iirc the os needs to have processor specific task
               | switching code in assembly, Freertos must have tons of
               | ports already. Nim supposedly has nice FFI for C. The
               | niceness of freertos is then mostly about it's.. system
               | call design. If it's good, then you could probably
               | progressively rewrite freertos in nim.
               | 
               | Never used nim and been a while since I did any embedded.
        
               | bsder wrote:
               | OS is not the same level of abstraction as a language.
               | 
               | This is an active problem in embedded. Cheap
               | microcontrollers can have multiple cores that are not
               | even the same _architecture_.
               | 
               | This means that just to get "blinky" running, you need to
               | choose a (language, OS) tuple. And, given that "language"
               | is generally "C", that means that your abstraction
               | choices for OS are lousy.
               | 
               | Side question: last I checked, FreeRTOS didn't do a great
               | job when multiple microcontrollers were involved--
               | especially if the communication channels or
               | synchronization were hardware-based. Has this changed?
        
           | jeff_ciesielski wrote:
           | Like any time you get off the beaten path, there are rough
           | patches, but to paraphrase another comment in this thread
           | "Nim is just C", so anything that felt a little awkward just
           | meant using an `importc` pragma and doing whatever I needed
           | to do in an environment I felt more comfortable (you can also
           | have C code compiled with a {.compile: "foo.c"} pragma, so if
           | a module required something be done in C, you didn't have to
           | monkey about with the build system to include it, it 'just
           | worked')
           | 
           | The biggest negative I ever hit was that early on, Nim didn't
           | have support for `volatile`, which meant it was a non-starter
           | for doing anything with MMIO (I ended up being the one who
           | added volatileLoad/volatileStore to Nim's stdlib so I could
           | use it on a Cortex-M without having to drop into C so much).
           | 
           | For the most part though, if you're reasonably comfortable
           | with embedded toolchains (i.e. you understand how to write
           | linker scripts, understand what happens between a reset and
           | actually getting into `main()`, etc), it's not much of a
           | hurdle to set up a simple build system to compile your Nim
           | code to C, link appropriately, and then sort of forget about
           | it.
           | 
           | It's been a while, but IIRC I also got step-through debugging
           | working with OpenOCD by having the nim compiler generate
           | `#line` pragmas and including debug symbols, which was pretty
           | neat.
           | 
           | This was all pre ARC/ORC, so I did have to make sure to be
           | careful not to use ref objects, but ultimately it felt pretty
           | seamless. I still tend towards fully manually managed memory
           | on embedded projects, but I'd be curious to give it a go.
        
         | GuestHNUser wrote:
         | > - The lack of first class interfaces/traits/typeclasses is
         | not my favorite. The currently suggested alternatives are so
         | un-ergonomic I'd almost call them hostile.
         | 
         | Personally, I don't see this as a problem. I like implementing
         | an interface in Zig the same way I implement one in C -- I use
         | a function pointer that takes a packet that defines the
         | operation to execute with that packet data. Zig's exhaustive
         | switch statements make these even better to work with.
         | 
         | Here is some pseudocode to illustrate what I mean:
         | 
         | fn doThing(ThingPacket p) switch(p.Opcode)
         | MyInterfaceFunc1_Opcode => return
         | MyInterfaceFunc1(p.Func1Params); MyInterfaceFunc2_Opcode =>
         | return MyInterfaceFunc2(p.Func2Param); ... default =>
         | error.InvalidOpcode;
        
       | 22c wrote:
       | ESP32 and STM32 support is very welcome!
       | 
       | I have been following https://github.com/ziglang/zig/issues/5467
       | for a while and progress seemed to have slowed significantly
        
         | kassane wrote:
         | Unfortunately, the upstream zig toolchain (LLVM codegen) does
         | not support the Xtensa architecture (opaque CPU features), but
         | it is possible to use riscv32 for esp32 from the C2/C3/C6, H2
         | and the new P4 release.
         | 
         | I'm currently working on a fork of the zig toolchain with
         | espressif-LLVM to support Xtensa.[1] It is now possible to test
         | with esp-idf instead of microzig for baremetal (Blink) yet.
         | 
         | [1]
         | https://gist.github.com/kassane/7bdb782a1984d0c6581ae7b44e1f...
        
           | 22c wrote:
           | Yeah I remember reading about it's relation to LLVM issues
           | etc. I also think there was another (languishing) fork of the
           | entire zig repo which had some support for Xtensa which I
           | couldn't find at the time, but I found it.
           | 
           | https://github.com/ominitay/zig/tree/xtensa
           | 
           | Edit: I see you are the same kassane from the GitHub thread.
           | Thank you for your efforts on Xtensa support! Would be great
           | to have it out-of-the-box at some point.
        
       | roland35 wrote:
       | I appreciate that zig is an easily searchable name for a
       | programming language. You'd think after Java and python that'd
       | we'd have better options, but no! Now there is Go, Rust, etc etc
        
         | Ar-Curunir wrote:
         | is it that much more difficult to append a `lang` after go,
         | rust etc?
        
           | roland35 wrote:
           | That works... sometimes. Just doing a quick search in the
           | Rust book, 0 results for "rustlang", 30+ results for "rust".
        
       | extraduder_ire wrote:
       | I do hope that someone balls this up into an arduino core so it
       | can be used with the arduino IDEs, it even supports the
       | atmega328P already.
       | 
       | Purely for making it easy to try out without too much setup. e.g.
       | click a button in the boards manager, and everything should just
       | work.
       | 
       | This saved my ass when I was able to switch to a third-party AVR
       | core which allowed changing the clockspeed so I could have
       | working serial on a chip running slower than 16mhz and get a demo
       | out of an otherwise busted board.
        
       ___________________________________________________________________
       (page generated 2024-02-29 23:02 UTC)