I can setup an esp-idf framework ready to go, but lots of work and pitfall are hidden. It's like with Arduino, it's very fast to code a temp sensor pushing data over wifi in few line of code. But when you want to play with a peripherial at full extent, you feel rapidly limited with such env.
Very true.
That's why I've been mentioning the ATmega32u4 so often. On one hand, it is simple and one can basically grasp all of it. On the other hand, because it does have native USB support (12 Mbit/s), it can appear as whatever USB device you want, including USB HID devices like keyboards and joysticks and gamepads, as an USB Serial device, or even some native USB device (like slower USB audio devices – note the 12 Mbit/s limitation).
Starting from those (well, actually AT90USB1280 aka Teensy 2.0++, then 2.0, then to Teensy 3.0, a diversion back to ATmega32u4 via cheap Pro Micro clones using Arduino Leonardo bootloader, and so on), I could grow my understanding and capabilities step by step, without encountering a sheer vertical wall at any point.
You can even look at the intermediate files the Arduino environment generates, and explore the C runtime (
crtmcu.o object files linked with Arduino code for each MCU), to see exactly how stuff gets implemented.
Most of the technical details I've mentioned in this thread are advanced stuff, in the hopes that you and others reading this thread will someday reach the point where they can make informed choices, and remember this thread and my points; and make stuff that Is Just Better than what we have now.
It is perfectly acceptable – and personally recommended by me – to start with the "easy" environments like Arduino, as getting stuff working gives that delicious boost of success and keeps ones motivation up; and while the environments are less than perfect, they are still quite usable, and very good for learning (as one quickly gets visible feedback, instead of just a laconic "Ok, that worked", and can
play with stuff). Just keep in mind that it is just a step along the ladder – optional, though; not everybody needs to create stuff for others, it is perfectly okay to just play with stuff for fun – towards better control and mastery of the subject domain.
So where is it possible to find the fundamentals of MCU development from how to start with an empty host os (which tool to install) an empty directory (which file are mandatory and for what) to produce a working firmware ?
I wish I had a real answer to that!
I
think –– but I seriously hope others will pipe in, because I'm not at all sure about this –– that the best approach is to start in the Arduino or PlatformIO environment with preferably a simple microcontroller, and learn to write code in this funky environment (while being aware of what are quirks of a particular environment, what is hardware-specific, and what is C or C++), and when you have something working (even a blinky one), in parallel start to examine the build process and intermediate files part by part. If the microcontroller is suitable for bare metal development, then the "advanced" step would be to recreate something you did in the Arduino/PlatformIO environment in "bare metal".
I do definitely recommend getting development experience in Arduino/PlatformIO first with different microcontrollers and hardware types. Not only is it different, but you'll find you yourself will shift paradigms based on the task at hand. Something like an USB arcade controller with joystick and buttons is nice to do in freestanding C, but a complex configurable robot firmware with different compile-time selectable modules can be much easier to write using the C++ subset. It is not just the language, but also how you go about looking for a way to implement something.
The best example I can think of is how the different display module libraries work. Some of them have a full frame buffer, others only have a partial buffer, and you need to repeat the drawing commands (they only capture/rasterize to the current buffer slice) until the entire buffer has been generated and sent to the device. So, for small memory devices you prefer to deal with the drawing primitives, and for larger memory devices you can operate on the entire frame buffer and can track changes (so that unnecessary parts are not updated, and the overall updates are faster – especially useful with eInk/e-paper displays). It does not matter what language you use, but for efficient implementation, your approach will differ based on whether you expect to have SRAM for a full framebuffer or only a part of one.
Even though I think Arduino could be better, I still think it is a very good environment to learn, and to do interesting stuff and gadgets in.
For example, I designed a
Pro Micro Gamepad so that you could solder a Pro Micro clone on top of the (bottom board) upside-down, add an 128×32 (0.91") I2C OLED display, and program the entire thing in the Arduino environment (treating it as an Arduino Leonardo there, but using the Pro Micro pinout to see how the hardware pins are actually connected to the gamepads).
There is absolutely no need to do this in bare metal; the Arduino version will work just fine.
(The idea is that the gamepad is actually a gamepad + keyboard combination, and generates keypresses when the buttons are pressed, so that one can play Online games with it using standard keyboard controls. The two smaller tactile buttons are intended to switch between mappings, with the OLED display showing the currently selected mapping for a few seconds after the most recent small tactile button press. It's a laughably simple design, so I designed another based on
CH551g, but haven't built that yet either.)
linker script
Linker script is a linker configuration file. You can find these in various projects in a
ldscripts directory. The suffix determines the set of linker parameters used;
For both gcc and clang, you can specify a custom linker script using
-Wl,-T -Wl,filename .
The default ones for avr-binutils are created by a shell script named
avr.sc in the binutils sources.
You can find a quick synopsis at
OSDev Wiki, and full details at the
binutils ld scripts documentation.
This is what determines where code starts, where the fuse bits are, where data will be put, and so on.
vector table
Vector table is either an array of code addresses, or an array of jump instructions to code addresses. When the microcontroller starts up, or a hardware interrupt occurs, it will use a specific entry in the vector table.
For ATmega32u4 (avr5), the vector table is in Flash, four bytes per entry (
0x0c 0x94 0xLL 0xHH for a jump to address
0xHHLL), with the first (zeroth) one being the init/reset vector, that the MCU starts with when it powers on or resets.
boot
When a microcontroller starts up or resets, it is in a very hardware specific state. To make firmware upgrading easy, the very first code it executes is a bootloader. Essentially, its purpose is to decide whether the microcontroller should expect a new firmware to be uploaded, or just jump to currently existing firmware.
Arduino uses its own bootloaders, so do Teensies. There is an USB standard, Device Firmware Upgrade or DFU, that microcontrollers with native USB interfaces can support for updating the device firmware via USB. In many cases, the bootloader will not replace itself, only the rest of the Flash contents, and to replace the bootloader, one needs to use the in-system programming interface (JTAG, for example) appropriate for the hardware. While the bootloader is running, it can use the entire SRAM as it wishes, since once it hands control over to the existing firmware, the firmware will be in full control. So, the only limited resource bootloaders consume, really is just the Flash they take up.
Therefore, "booting" in this context means the phase in microcontroller startup and reset sequences, where the "bootloader" code checks whether a new firmware should be uploaded to that microcontroller, or whether it should just hand over the reigns to the currently installed firmware.
Interestingly, the proprietary bootloader for Teensies, Halfkay, uses the HID protocol (and therefore needs no special OS drivers!), and the 8-bit AVR implementation (for Teensy 2.0 and 2.0++) takes less than half a kilobyte of Flash. This made early Teensies
so much nicer to develop on than e.g. Arduinos.