4 ms·
Programming for Arduino in C/C++ has been the worst most unproductive endeavor I ever undertook as a software developer. I have no idea how people write whole b
by nikitaga 5y ago
Programming for Arduino in C/C++ has been the worst most unproductive endeavor I ever undertook as a software developer. I have no idea how people write whole browsers and operating systems in this language.
Rewrote everything in CircuitPython after 1500 loc of cpp madness, and living happily thereafter. I had to switch to a more powerful board though, it would be great to have lower requirements. Adafruit's M0 adalogger board has been perfect for me in every way except it does not have enough flash for CircuitPython.
Toit looks funky, but nice. They seem to already have a decent start at the ecosystem, with very basic libraries like drivers for HD44780 already made. Doesn't have everything that I need yet, but looks very promising.
I'm working on a flight controller, not IoT, so I don't need their serviceability API, but that's an interesting way to monetize. If I needed that, I can definitely see it being cheaper to pay the fee than to develop stuff like a reliable OTA update system myself.
- PMunch 5y agoAgreed, C/C++ can sometimes be a real pain to work with. I went a slightly different route however after seeing how much less I could do with an equally powerful board running MicroPython and settled on Nim. It has a syntax that looks familiar to Python, but it's compiled through C/C++ so every board I could run C/C++ on I could run Nim on. It also meant I didn't have to rewrite all the libraries. Recently I've been digging into creating a pure Nim ecosystem for microcontrollers after discovering just how much overhead Arduino and other generic C approaches are. The benefit Nim has here is that it is incredibly strong at compile-time execution. This means that I can write succinct Python-looking code which compiles to tiny binaries with great performance. My biggest project in it so far is a keyboard firmware for a keyboard split in half. The whole project compiles down to about 4Kb with all the code for the port expanders and the layouts and special key macros I use. For comparison the Arduino code to blink an LED over one such port expander was about 3.8Kb and MicroPython can't even compile a hello world for the board I'm using.
- nyanpasu64 5y agoI wonder if Nim is better or worse suited for low-powered boards than TinyGo. Zig is probably not mature enough and insufficiently safer than C++ to satisfy you and your parent post's requirements though.
- PMunch 5y agoTinyGo has a list of explicitly supported devices. Nim can run on anything you can compile C for. I also can't see some of the more low-powered hobby devices such as the Digispark (Attiny85) or similar. Not sure if that's because TinyGo can't be run on them, or whether or not it's just no-one who has written a target for it yet. One huge benefit Nim has in this category is that it compiles to C. If your board can run C, it can run Nim. And don't let the Nim GC fool you, it can be disabled, or switched to the more modern ARC version which runs fine on microcontrollers (and if you don't use garbage collected memory it doesn't add any code to your project). Since it compiles to C you can easily wrap libraries and compile for pretty much anything. Zig can at least also wrap C code super easily, but I'm not sure how the overhead and targeting is for it.
- Chris2048 5y agoSounds great, are there any project pages towards this initiative? I'm also sceptical of HHLs on restricted devices i.e. mcus, but Arduino seems like the only game in town atm.
- PMunch 5y agoNothing about the entire ecosystem I was talking about. But my initial work on the keyboard firmware can be found here: https://github.com/PMunch/badger/tree/final https://github.com/PMunch/badger/tree/final. There are many different projects in Nim running on microcontrollers though, but not something on a common ecosystem. HHL?
- deleted 5y ago[deleted]
- okamiueru 5y ago> I had to switch to a more powerful board though And therein lies the rub. If you can throw more computational power and resources at your problem, then you should absolutely go for something with simpler abstractions that make better use of your time. The problem is however not with C or C++, but rather inexperience in how to manage the control/flexibility these languages allow. You now have 50 ways to shoot yourself in the foot, instead of the otherwise handful. And, you also get the choice of a few bazookas. > I have no idea how people write whole browsers and operating systems in this language. They use best practices that avoid all of the aforementioned 50 ways.
- klohto 5y agoOh, look! Another comment about how C is great and it’s the user’s fault. Yea, no shit Sherlock. This is the reason why we have Rust now and what’s possible is migrated into it.
- nikitaga 5y agoCan confirm, foot gunned myself over and over even though I'm not doing anything complicated. Couldn't figure out the last memory corruption trap I fell into, had to abandon ship. I'm honestly surprised how well regarded Arduino software stack is given that its target audience is not exactly seasoned C++ developers. I guess most casuals have very simple programs, or just go straight for micropython / circuitpython.
- nyanpasu64 5y agoC++ memory management is hard, and best practices don't solve all the problems you encounter. (Note that I have more experience with desktop programs, like "browsers and operating systems", than embedded boards.) Managing tree-shaped allocation trees is possible but mistake-prone, and Rust includes a sound checker to catch mistakes. (Rust pushes users towards using "writable xor aliased" pointers which is integral to this sound checker. "Writable xor aliased" is one of Rust's biggest strengths and weaknesses, and differs from both C++ and GC'd languages.) Managing intrusive linked lists is common in C (I have less C experience than C++) and probably tractable as long as you don't carry around pointers which may dangle. Rust doesn't help write correct intrusive code, and worse yet Stacked Borrows (a proposed set of rules for pointer aliasing) interferes with attempts to write intrusive collections in unsafe code (https://gist.github.com/Darksonn/1567538f56af1a8038ecc3c664a42462 https://gist.github.com/Darksonn/1567538f56af1a8038ecc3c664a...). Maintaining intrusive sorted-tree maps and such is probably possible in C, but perhaps even trickier to get right (note that I've never built one myself). I try to avoid aliased object graphs (multiple objects hold pointers to another object) when architecting greenfield projects, and it's often (but not always) possible. When I encounter pointer aliasing anyway (often in existing codebases), I usually fall back to shared_ptr (reference counting) because manual memory management requires borderline-intractable global reasoning to make sure every possible codepath never uses an aliasing pointer after the object has been freed through another pointer. (This is especially difficult on code I'm not wholly familiar with, because I either didn't build it myself or forgot the details.) And if you have reference cycles, shared_ptr leaks memory, so you need to manually clear shared_ptr on destruction (error-prone and risks leaking), use weak_ptr (runtime overhead and boilerplate on every read, not just creation/destruction), or use raw pointers (error-prone and risks memory unsafety). Rust doesn't help you with reference cycles either, sadly (Rust makes mutating shared references painful, Rc leaks memory, and Weak has runtime overhead and boilerplate, but at least Rc/Weak aren't atomic like shared_ptr). A garbage collector makes your life a lot easier, at the cost of runtime pauses (often higher throughput than refcounting, but slowdown occurs in bursts). ---- C++ is intractable to write correct threaded code in. The language doesn't tell you whether a T& is visible from one or multiple threads (which determines whether you need to acquire a mutex to access it). And if 1 object has methods A and B both exposed through a public API (accessible from multiple threads), but method A (which acquires a mutex) calls method B, then method B must acquire the mutex when called from outside, but cannot acquire the mutex when called by method A unless it's a recursive mutex. The alternative is to make neither A nor B acquire the mutex, and require callers to acquire the mutex beforehand (which they may forget to do). I've written more about this at https://write.as/nyanpasu64/c-mutexes-are-easy-to-misuse-and-terrifying-to-properly-use https://write.as/nyanpasu64/c-mutexes-are-easy-to-misuse-and.... I honestly don't think Python and Java are that much more tractable to write correct threaded code (note that Python has a global interpreter lock, and I have limited experience with Java threading). Neither distinguishes references to an object visible to one thread, from references to an object visible to multiple threads. And neither can force users to acquire a mutex before accessing an object visible to multiple threads, though Java's synchronized methods use recursive locks which can avoid the method A/B problem above. Also the consequences for threading errors are slightly less severe (corrupted data and exceptions instead of segfaults). Rust is tractable to write correct threaded code. Its type system distinguishes &T (sometimes shared between threads, usually read-only), &Mutex<T> (cannot access T without taking a lock), and MutexGuard<T> and &mut T (obtained when you acquire a lock, provides unique access to T while you hold the mutex). This ensures you'll never forget to acquire a mutex, and have to go out of your way to try to acquire a second lock to a mutex you already own.
- mattgreenrocks 5y agoC++ is a weapon of last resort. It’s very powerful if you need it, but if you don’t, it’s a lot more cognitive complexity for little gain.
- pif 5y ago> I have no idea how people write whole browsers and operating systems in this language. They are software developers.
- deleted 5y ago[deleted]
- the__alchemist 5y agoHave you can confirmed circuit python is suitable for the real-time control and latency requirements of this use? I'd assumed it wouldn't be suitable due to speed etc.
- nikitaga 5y agoI checked it in practice, and it seems fine. Reading and parsing a 115200 baud telemetry stream from the ESC is the most expensive part of the whole thing, but even still, I split it up in a way that caps the amount of work that this subsystem is allowed to perform per loop, so all other parts of the loop get a chance to execute very often. The GC by default only collects garbage once you don't have enough memory for the next allocation, I thought I might need to adjust that, but even though it triggers regularly in my code, it never seems to cause a noticeable slowdown. Despite "flight controller" sounding realtimey, the code is resilient to some reasonable latency. One potential concern is that CircuitPython does not support user defined interrupts, but I haven't needed that personally. And I think you can still have those if you drop into C? Not sure. At any rate, if it does get slower as I add more code, for me it's easier (and safer) to deal with that, than with subtle memory corruption foot guns in cpp.
- justin66 5y ago> Programming for Arduino in C/C++ has been the worst most unproductive endeavor I ever undertook as a software developer. I have no idea how people write whole browsers and operating systems in this language. It would be more straightforward to say "I have no idea how people write whole browsers and operating systems."
- eternityforest 5y agoProgramming Arduino is the only time I willingly use C++... it's a lot more annoying in other contexts. But still better than some languages.