(blinky is a common name for a program that only blinks an LED, a “hello world” of the embedded world)
A couple of months back, I thought that it would finally be fun to do some experiments using an embedded SoC I’m already familiar with, in order to finally test out various ideas that have been floating around my head, but without pressure of actually getting anything delivered (like in Real Life™). In any proper embedded project, the first step is always getting an LED to blink (due to software control). Little did I know that I would end up falling so far in the rabbit hole, but hopefully this article will succeed in documenting some of the lessons that I learned.
Please note that this is not meant to be read as educational material. There are undoubtedly acronyms and technical terms that might not be familiar to people who have never worked at bare metal before or not worked with Cortex series.
I feel that it is fair to mention that before writing this article, I’ve had multiple years of experience with working with embedded SoCs, with and without operating systems or RTOSes, and for the past two years I’ve been working with Kinetis daily. I also have a background in networking, infosec, protocol design and Linux.
My employer (Rakettitiede Oy) has graciously sponsored the hardware for this project, for which I am grateful. However, my opinions are my own.
I will try to use the vendor names of the chips from the time of their release to the market (can’t be bothered to rename everything each time someone merges with Philips semi, NXP, Motorola, Freescale and/or Qualcomm, apologies in advance). Using the original names normally gives better search results in search engines anyway.
All trademarked names are trademarked by the organizations that trademarked them in the jurisdictions where the aforementioned trademarks are valid.
Besides the normal toolset, the following tools were used for this project/article:
- GNU make (for now, might replace it with cmake or meson or something else later, the current build system is quite horrific)
- Baremetal ARM Cortex gcc/binutils ( https://launchpad.net/gcc-arm-embedded )
- Segger j-link EDU & JLink commander (this is a non-profit project)
- JTAG-20 to MIPI-10 (“ARM Cortex Debug”/SWD without Trace)-adapter
- Freescale FRDM-K64F and one USB cable to power it
In this age of abundant development board choices, one might ask, “Why did I settle on this exact board?”
There are many reasons, but normally I go for hardware that is both cheap and contains things that I haven’t worked with before. Bonus for actually containing chips and parts that are documented.
It is certainly cheap(ful) compared to other boards with fixed Ethernet access and it has a uSD, USB and also an accelerometer. The accelerometer seems to feature in all of the FRDM-series boards, so if you’re just looking for that, you might want to grab one of the cheaper FRDMs.
The FRDM boards (similar to what Freescale did with TWR-boards), contain a separate CPU to provide for the programming and debugging needs of developers who can’t or won’t use a hardware debugger. This is a common trend, and there at seems to be some consensus that not every vendor needs to re-invent this separately (explaining CMSIS-DAP). The secondary chip is a smaller and older variant of the Kinetis K series, namely, K20 (K20_50 to be more precise).
So, for the price of one devkit, you get all of this:
- A Cortex-M4F and while the maxfreq (120MHz) is not the highest around, has adequate oomph and is compatible with the faster ones like K26 and K65
- Rich set of peripherals (including a RNG, a not very modern crypto-accelerator, a CRC accelerator, none of which I’ve used yet with Kinetises)
- Acceleration sensor over I2C and couple of interrupt lines back
- uSD capability (quad-wide)
- With a known PHY and transformers and RJ45 for playing with Ethernet
- Triple LED (not on PWM-capable pins unfortunately)
- Two micro-switches
- A lot of pins to play with and buses exposed to attach signal analyzers and external chips and boards
- USB device (and partial device via OTG)
- Low frequency 32k768 crystal (quite useful for playing with low-power and wall clock time)
- An MIPI-10 SWD connector to support hardware debuggers
- K20_50 (normally runs the OpenSDA firmware):
- A Cortex-M4 at maxfreq (50MHz)
- A single red LED (on a PWM capable pin)
- An 8MHz crystal for oscillator (required for stable USB clock most likely, since this is an older generation Kinetis)
- USB device support (although there’s not a lot of SRAM to play around with, making any work with USB somewhat challenging)
- Bidirectional UART to K64 (allowing playing with serial protocols), only RX-TX though
- Pins that would normally be used for SWD can be used for GPIO “communication” between processors
- Can cause reset of K64 (and in fact can reprogram the K64 if required)
- An MIPI-10 SWD connector to support hardware debuggers (this is very nice, since normally a vendor wouldn’t care much to provide you with the means to also use the “programming helper” chip to run your own code)
So, in many ways this is like a bare-minimum board, but least the USB connector is populated, there are LEDs to control, there are a couple of user switches and most importantly there’s something to talk with (the acceleration sensor).
Now, I know what you’re thinking: “this FRDM-K64F sure sounds like an excellent board! I want it!”. At least Farnell in Finland has dropped the board (as obsolete). You might still get it from Digikey. Take note that a lot of the open source stuff around the net (different RTOSs for example) already have some support for FRDM-K64F because it was/is so popular. I’m sad to say that the replacement board with a newer K6x-series is already somewhat more expensive, although the contents are pretty much the same. There also seems to be a tendency for the new boards to target wireless communication, making wired Ethernet stuff more niche.
Down the rabbit hole we go!
So, for part 1, I started by dreaming about a USB stack that would work and then downgraded it a bit into “How to blink an LED in 21 hard steps.” Lessons learned are listed at the end.
Step 1: Dream about a better world
I’ve now spent quite a bit of my limited lifespan working with various USB stack issues and bugs and am mentally ready to attempt to write my own. A nice one. Suitable for embedded devices and such. Starting with something simple like implementing CDC/ACM for example.
Step 2: Not so fast!
Having a working
printf would be nice though. While using
printf from within the
time-critical parts of a USB stack is folly, having
general would be nice. Now, people quite often build and bundle gcc against/with
newlib which is an open source portable “minimal C standard library” implementation, or at least most of the minimal stdio stuff. And while newlib is
awesome for what it was designed for, it doesn’t really fit well into SoCs. So,
one might end up with huge 20 KiB executables that just print “hello world” before
even writing proper proper programs. Not to mention SRAM reservations and what not. Sadly, I have no
good solution at the moment for a decent and small “libc” for baremetal-like use
for small SoCs. avr-libc is an example of something that might work well with
So, I decided to write my own
printf. There are small ones floating around
the Internets (with no, or dubious licenses). There’s also musl’s
printf implementation and while quite feature complete, it perhaps contains too much
of a good thing.
Step 3: No really, stop!
Getting stuff out with
printf actually already implies that quite a bit of stuff
has happened in the system and/or software. Someone has configured the UART with
sane speeds and there is some sane binding between the stdio minimal API down to
the UART level. Someone has also arranged for the clocks within the SoC to be
set up correctly and according to design requirements. All of these things will
also vary according to the actual board design, so all needs to be board design
specific, since different boards will use different crystals and use different
pins for connecting to the outside world.
So, what if instead I could concentrate on the pure minimum starting point. Blink an LED! You have to understand, blinking an LED is never an end objective. However, once it blinks, it is a proof that something that you wrote is actually alive in the target device and you can see the warm glow of the LED as a symbolic beating heart (assuming it blinks of course). Never design hardware without at least one software controllable LED. It makes the person doing the initial porting and board bring-up very happy. You can DNP it later if you’re worried about the cost and/or light leakage.
So, to get back to the basics, this is pretty much minimum that you’d have to do in order to blink an LED:
- Make current flow through an LED.
- Wait for a while (do nothing for a while).
- Stop current flow through an LED.
- Wait for a while again.
- Go to step 1.
Converting this into actions done in software with the board design of FRDM-K64F, we then have:
- Make low voltage level appear on the SoC pin that is connected to the LED.
- Do something N times to keep the CPU busy for a specific duration.
- Make high voltage level appear on the SoC pin that is connected to the LED.
- Do something N times again to keep the CPU busy for a specific duration.
- Go to step 1.
Now, since we want to minimize the amount of code that we’ll have to test and maintain, we can combine steps 1-2 and 3-4 like this:
- Toggle the voltage level that appears on the pin that is connected to the LED.
- Do something N times to keep the CPU busy for a specific duration.
- Go to step 1.
Toggle above means “inverting” the bit that controls which voltage is driven out.
So, three simple steps, how hard could this be then? (Somewhat obviously, if you see a scrollbar at the side of this page, you will know that objects are sometimes closer and/or farther away than they appear in the mirror.)
Step 4: Before we can start…
It normally takes some time before one realizes that all the easy stuff that is
doable in C, does not come free, nor automatically. One of the things that is
hidden away quite effectively is the things that are required to happen so that
the environment is ready to execute the C code. One doesn’t just start
The magic is commonly implemented and hidden by your toolchain and/or C library. Some vendors also want to be “helpful” and implement it for you. Which of course is great fun when the said vendor code does not know or care about the startup code that also ships with your toolchain.
So, some moments later, somewhat generic startup code written in pure C was implemented with some semi-portable linker script as well. All Cortex-M SoCs that I know of map their primary SRAM to start from 0x20000000, and internal flash at 0x0, so by restricting to suitably small text and data areas, it’s possible to make a linker script that covers a wide range of Cortex-M SoCs.
Resulting binary was paper-tested, as in since the build system produces
hexdump -C is a friend, one can see whether everything builds and
links to proper places. Qemu could’ve also been perhaps used.
Step 5: Download what now? (longish rant, apologies)
So, linker script and initial C runtime setup routine and build system ready, the next step is to find a way to describe registers to the compiler in order to write the code that flips the LED on and off.
There is a possibility in side-stepping this problem completely, by using a method similar to good old “POKE & PEEK” (as in Commodore 64 et al). Meaning that since all of the SoC peripherals map into internal memory regions and addresses, it is perfectly valid to just use pointer casting and write to specific memory addresses to cause change in the system. This is in fact what all of the register structures and macros from vendor SDKs do as well (in the end, from compiler’s standpoint).
So, at this point the available choices are pretty bleak. A lot of vendors seem to have a conception that the more the software environment for their SoC looks and feels like Arduino, the better. While a valid perspective in terms of increasing accessibility for a lot of people, the approach does abstract a lot of things and simple things no longer are simple at all. Switching an LED should be simple.
A good example is what happens with Freescale. Historically, they distributed software support in “peripheral support packages” or similar. Basically ZIP files that contained all structure definitions for the peripherals, perhaps some very simple utility functions to abstract the hardware a bit, and some examples for each peripheral (if you were lucky). Using a low-level support package like this is quite hard for Arduino-targeted people.
Next logical step is to add a some kind of RTOS in the mix, or at least support them. FreeRTOS support is fairly common, as well as Keil’s RTX (more on RTX later in a separate article perhaps). Once you start thinking about how to represent problems with an RTOS, you quickly realize that there are in fact close to infinite different ways of modeling the API. The decisions taken at API design will define what will be easy to do with package, and what will be harder to do than before (this assumes that the lower level access method is deprecated at the same time). So, by making the SoC support software easier to use in general, it becomes more difficult to use for people who know what they’re doing.
The ultimate conclusion with this is of course mbed, with which ARM is finally reaching “parity” of ease compared to Arduino. It will never reach it, for the obvious reason that Arduino operates in a much simpler environment and is not as open-ended as mbed.
So, back to switching an LED on Kinetis. The K64 is too new to have peripheral support packages. Or if they existed, they are no longer available on the NXP site (which itself has gone through several facelifts). A couple of years back, Freescale’s higher level package was called KSDK (Kinetis SDK) and was available relatively easily by downloading some 100 MiB ZIP (could’ve been smaller, can’t recall specifically). Note that this package contains support for exactly one Kinetis K-chip. 99% of internal resources in all K series Kinetis processors are shared. The difference is mainly in how many of each peripheral is included and some times specific peripherals are left out (Ethernet MAC is a good example). Some of you are hopefully thinking at this point, “What the heck? Couldn’t they just make one ZIP that supported all of the K’s?” Sadly, this is still the norm of the industry.
KSDKv1 came and went, being replaced by KSDKv2. Now KSDKv2 was special in that there was a rather short PDF document explaining how to port software from the KSDKv1 APIs to KSDKv2, but the document quite strongly worded that “it will be hard”. Indeed, it was close to impossible in some cases where the API turned from a blocking + callback based into only blocking based. Once you have your software stack sitting on a specific “call model”, it generally becomes impossible to change this call model later without affecting your software at architectural level. Plainly, if blocking model is the only thing that a vendor’s SDK support, you will have to use threads. If you have to use threads, you will have to solve the problem of multi-stack allocation and probably go with over-committing of resources. This is the decision that the vendor did for you.
KSDKv2 weighed at about 170 MiB the last time I let the very ‘impressive’ NXP web site generate it for me. Granted it now packaged more middleware in, stuff like lwIP, FatFS, etc. Now, somewhere in the depths of that web site machinery are the original files from which all if this is created, with the masters and files encoding the differences between the SoCs (in one way or another). However you’ll have to download the ZIP for each SoC model still separately. 200 MiBs per Kinetis K. NXP and Freescale merged at this point, so after a rather obvious web facelift, KSDK is now being called as MCUXpresso (the name comes from NXP which used something similarly named for their SoCs). The contents for Kinetis devices is still the same as before.
So, let’s return to the core question: in order to blink an LED, do I really want to download 100-200 MiB ZIP packages for each of the processors that I want to the blinky to work on? What about change management? What are the odds that the support packages for all of the chips will be updated at the same time from vendor’s side? You might think this is a problem, but most of the time there is no communication about the exact changes between versions by the vendor anyway, and if you’re in a happy place, support for your SoC might be dropped completely after a time (vendor doesn’t want to invest in back-porting support for “legacy” products).
A good example is the K20_50 SoC on the FRDM-K64F. There is no support for it in the MCUXpresso. There is no trace for the peripheral package on the NXP site for it either. If you feel lucky, you might find some source code floating around the Internet (it’s amazing what Github is used for nowadays).
Also, please don’t misunderstand the (rather long) raging above. This is an industry wide problem, not just somehow restricted to Freescale. People doing cross-SoC work are not seen as a useful market to optimize for. “Arduino-makers” are. And this is perfectly alright, a lot of cool stuff is being done with easy to use and simple frameworks. However, it doesn’t mean that there is no need for parallel and complementary approach as well. If I’d have to guess, then perhaps there’s a 1:100-1:1000 ratio between cross-SoC’ers and “Arduïnists”, although I’m pulling this number out of my current source.
For this project, I decided to throw the vendor SDKs away. It’s too bothersome to attempt to track them or try use them in any useful way. And it would be nice if the code would work in 5 years from now and could be located in a known place then as well. Also, if this was a project for a customer, I’d probably never do this. This is not at all an efficient way of using someone else’s money. However, this is a pet-project, and what a wonderfully scruffy pet it will be!
Step 6: Add some XML!
Bet you didn’t see that title coming. ARM has been trying to steer ARM licensees into working with the CMSIS framework that they control (and possibly Keil before it was snarfed into ARM). Within CMSIS exists the SVD (System View Description) which is an XML schema for describing and documenting the programmer visible aspects of an SoC integrating an ARM supplied core design. In theory, one could expect to find an SVD file for each SoC model that a vendor sells, but this is sometimes quite difficult. They’re normally not distributed separately by vendor, but embedded in those megalarge blobs. Some enterprising people have extracted them and put them into separate public repositories as well, and finally, Keil used to have their own SVD for chips too (which might differ from the vendor ones).
For Freescale, if there’s an KSDK package for your chip, then it will include the SVD file somewhere inside (it’s several megabytes of XML). If there’s no KSDK package, your best bet is to hunt for the SVD from Keil’s support website. And finally, the very nice collections like cmsis-svd. Please make sure to check the licenses on the respective SVD files before using them for your work, to make sure they’re compatible with what you’re doing.
In all cases the SVDs contain bugs. They are not the source that is used to synthesize designs, so reader beware.
There are two main uses for SVD files:
They can be used as input for
SVDConv.exe, which is ARM’s tool to take SVD files in, and generate C structures and macros out (describing the peripherals). This is something that the vendor would use when preparing their “SDK” package.
They can be used as inputs to interactive debuggers so that register contents of peripherals can be decoded at bit field level when attached to the target. (Most commercial Cortex-M debuggers should support this, and there are versions of Eclipse debugger that support them as well). Depending on the level of work you do, these might become very useful indeed although much has been achieved without these as well.
Now, this doesn’t mean that there can’t be more uses for the files. I’ve never particularly liked how vendors generate the structure/macro files so that they result in one huge blob file instead of more modular and fine-grained file structure that could contain parts that can be re-used between compatible SoCs.
This is one of the reasons why I started to work on a custom generator tool and some of the results of that tool can be seen in the code repository later. There are also other people who’ve had the same idea, especially if they need the data suitable to a non-C language (that still allows hardware level access, like Rust).
I’ll try to cover the generator later perhaps, and it also quite seriously needs much more work (since it’s not “just” an “SVD to structures” tool).
With the GPIO and PORT blocks adequately described with structures, we’re done!
Step 7: Freeze!
Since we’re bringing the SoCs from reset to the world without any existing code, we’ll need to make sure that we enable access to the peripherals and enable power to them. Most modern(ish) SoCs have at least clock gating available in them so that clock delivery can be stopped to the peripheral when it’s not being used (to stop it from flipping its flops and wasting energy). Most SoCs come up from reset with just the minimum clocks enabled.
Kinetis internal clocking model is somewhat complicated because of the number of different clocking setups that can be used, but for switching an LED we only need to know how to enable the peripherals and that is done via the SIM peripheral (System Integration Module, and by System, Freescale means their SoC only, not your system).
Step 8: We have lift… misfire!
The blinky doesn’t work on the K64. It works when the core is started via j-link, but not if j-link is removed and the board is reset. What is odd at this point is that the reset LED (D1) is “not at full brightness” (semi-half-brightness state). Deduced that OpenSDA must be doing something over the SWD programming lines to keep the K64 from starting. The programming of the K64 happens over the SWD lines coming (also) from the K20.
This was the first mistake. (Other than not starting with STM32 perhaps?)
Slightly difficult to see from the picture, but the red “RST” led is much more faint than the green one:
Blinky on the K64 should’ve started blinking the circled LED above (with blue color, since it’s the most annoying).
Step 9: Oh yeah?
I then proceed to attach j-link to the K20, and use it to store the flash contents for later use (the SoC flash was not locked). So that the K20 doesn’t interfere with the K64, I decide to erase the flash (very easy and efficient with j-link).
Reconnect the board to test without debugger and things are now worse. The D1 doesn’t go on, no LEDs are on.
Decided that it must be because the K20 now has no code, and one needs to drive some of the K20 pins to specific state in the board, so that the K64 will run. Considered writing a program for the K20 in order to set up the pins.
Step 10: Where am I even?
I need to understand the board schema properly in order to know which settings will have to be applied to each pin to bring the K64 up. I downloaded a schema and started to investige it in the evenings. The FRDM-K64F is not a simple design, mainly because the board supports so many different powering options.
Step 11: This map is all wrong!
Realized that the schema I’m reading is not actually for the same revision as the board is. The board has gone through quite a number of changes. Having previously worked with Freescale boards I should’ve anticipated this.
Hunted for the correct version of schema (and found one close enough).
Step 12: Sir, you don’t have any clothes on
Realized that I in order to run anything on the K20, I first need an LED toggling program to verify that any code runs at all, before bringing in the pin configuration. Luckily, I already had an LED blinking program! So, some changes and generalization later…
Step 13: There are no limits to the different kinds of Fail you can
The blinky on the K20 results in no LEDs going on. Unless started from debugger. At this point I question my original analysis of possible root cause.
Step 14: But there certainly are many different and colorful ways of Fail
If I changed the order of things a bit, I could get the K20 LED to turn on and then remain in a “half-live” state. Very similar to what I had before with the K64’s reset-indicating LED. So, perhaps I hadn’t configured the pin correctly for the way that the LED is connected on the board? In retrospective, a mistake on my part, but one that has a logical reason to exist: I have quite a lot of experience with dealing with digital electronics and schemas, but as soon as we start talking about open collector or junction currents, I cover my ears and start saying “lalala”. Some things I just don’t want to know about (limited time). So, since I don’t know enough about electronics, the problem must be in electronics.
Again, the contrast captured by the camera can’t really communicate the brightness properly, so you’ll just have to trust me on this one: the green LED is quite faint compared to “full on”-state:
Step 15: Impedance is futile!
While the topic of bus and drive impedance, and cat-eye pictures is fascinating, changing drive strength, slew rate, or turning the pin into open collector setup did nothing to help. This also confirms that perhaps I do know a bit about electronics to start with.
Step 16: Just steal the code already!
I sink into a slavic melancholy that can only be fixed by reading existing code written by other people. Specifically, code that is run by other bare-metal initializing software that is known to work with FRDM-K64F. Having previously run an mbed generated “hello world” on the K64, I knew that the board works electronically. Having had OpenSDA executing on the K20 was also a proof that the board worked on the K20 side. So, something I was doing was wrong or different from what the others were doing. My code was special.
Step 17: No interrupts please
Verified that my problem is not caused by unwanted NMI launching
immediately post reset, nor is the chip enslaved to EzPort-based parallel
flashing operation. However, seems that enough people have had problems with
both, so the FOPT bytes in
flashconfig.c are modified to disable both features.
Step 18: I smell a rat
Most of the code that people use to bring up the system is using assembler. While I love assembler, I’d actually like to complete this project without writing any, but perhaps there is a technical reason for the assembler? Perhapsies indeed.
Step 19: Heureka! A Rat!
Going back to ze Internets, and reading reference manuals, and interesting old forum notes, and some more setup code, I finally understand what is going on.
Post-reset, Kinetis SoCs have their integrated watchdog active immediately. There’s no way to disable it at reset directly, like on AVR and many others. Instead, you have 20 bus cycles to disable it, or it will trip. Tripping causes a reset and the flow restarts.
Using the FOPT byte, it is possible to select whether Kinetis will boot in “normal speed” or “low speed” mode. This affects the clock dividers between the system clock (at which core runs) and other clocks in the system, like the bus clock. The default divisor is 1 for the bus, which means that we have exactly 20 core clocks to unlock and disable the watchdog coming out of a reset. This also explains why many people opt to arrange the startup code so that it is written in assembler (in order to guarantee that the reset path is something that compiler cannot muck up, no matter how good it is at optimizing).
The irony here is that I’ve actually read through the early setup assembler code that comes with KSDKv1, and I’ve actually written mental notes about checking out how the hardware watchdog really works, since a lot of effort is being spent on disabling it at the start. Purists will point out that this is not what irony actually means, and I agree.
Step 20: It doesn’t work and yet it works?
The only way I find to explain why the debugger was able to start the program(s) is that j-link probably writes to the watchdog registers automatically and immediately post-reset, so that the watchdog doesn’t trip. Or perhaps the watchdog is not enabled when a connection to CoreSight is active (haven’t checked).
Step 21: BLINKYYYYYYYYY!!1one
By re-arranging code so that the watchdog is unlocked and turned off as soon as possible, the code now starts properly without a debugger and the danged LED blinks!
So, these three little things were missing:
WDOG->UNLOCK = 0xC520; WDOG->UNLOCK = 0xD928; WDOG->STCTRLH = 0x01D2;
And finally: Blinky running on the K20_50, without the debugger:
By the way, if you’re wondering why I’ve twisted a pin on both of the SWD connectors (what a keen eye you have!), then wonder no longer: pin 7 on both the MIPI-10 and MIPI-20 connectors when used with SWD is the “KEY” pin, which is sometimes “stuffed” on the cable side, so that you won’t attach the cable inadvertently the wrong way around.
There were several midway steps as well:
- Trying different reset sequences (j-link knows several)
- Re-verifying a lot of the register specs
- Thinking about decoding the SWD traffic between j-link and SoC to figure out which reset strategy it employs and whether that’s what’s causing the different behaviour. I’m pretty happy I didn’t start on this path
- Ensuring that the vector table section actually covers the first 1K. This is not a requirement by the way, but I’m keeping the current linker script for now, since it’s closer to the norm of existing code
- Figuring out whether it was more useful to immediately turn the LED on or off, and whether configuring pinmux before GPIO direction made any difference
End result is now this:
- Blinky on the K20 works perfectly (it blinks an LED) and also starts without
a debugger (see
- Blinky on the K64 does not work if the K20 is running its blinky as well.
- Blinky on the K64 works perfectly if the K20 is running OpenSDA. Also without a debugger.
So, what is missing at this point are the pin configurations for the K20 so that it will allow the K64 to execute. Configuring pinmux using direct structures is rather tedious (common problem with most SoCs with more than one GPIO block), so the next article will present design rationale for a better design and also fix the K20 program so that it will allow the K64 to execute.
- Make sure your schema revision matches your board revision.
- Do not trust vendor’s DNP (do-not-populate) markings in schema. Components might be populated anyway. If you can find a component layout/silk print images, grab them to help you find components on the board. The FRDM-K64F was especially dense here, although still not close to miniaturization seen in wearable consumer electronics.
- Make sure to start with the simplest possible assumptions and tests.
- Reserve a lot of coffee.
- You never can know how much time root cause analysis will take (I certainly didn’t plan on using so many evenings on getting a danged LED to blink).
- On the path to root cause lie many side paths that contain a lot of interesting things to learn and to trip over.
- You cannot distinguish an LED at low current from an LED that blinks fast without help. An oscilloscope or digital signal analyzer would’ve helped. The rather obvious challenge in this case however is that neither of the LEDs are very easily accessible on any of the headers on FRDM-K64F, so that would’ve required very tiny probes or soldering which is outside the scope of my spare-time tinkering.
- Your debugging environment does magical things to your target behind your back. Obviously to make life easier for you, but still. Attaching/connecting a hardware debugger changes the environment where the code executes.
- Default state for blinky output should be “LED on” as fast as possible.
Please find this delightful code dump for your enjoyment and joke material: https://github.com/majava3000/slib/tree/2017-02-25
I do plan on making a proper license and changelog and whatnot in the future, but wanted to get something out immediately to go with this article, so there.