[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)