A quick word on ATtiny 1-series interrupts

The Atmel AVR 8-bit microcontrollers have always been a favourite for tinkering; and the massive popularity of the Arduino based on the ATmega 168 and 328 MCUs introduced a lot of hobbyists to this series. The companion ATtiny series from Atmel were the poor stepchildren of the ATmega controllers to an extent - useful for small projects but often quite limited. However, the acquisition of Atmel by Microchip Technology in 2016 ushered in a new series of MCUs bearing the same moniker of ATtiny, but much more capable and innovative. They have been around for a while now, but many hobbyists are just beginning to poke around with these new capable MCUs.

Some of the positives of the tinyAVR 1-series are:

  1. The introduction of the UPDI programming interface - moving away from ICSP is a major step forward. Not only does this redue the pin count required for programming from 4 to 1.
  2. Event System: The Event System allows for direct communication between peripherals without involving the CPU. This can lead to more efficient use of resources and faster response times in applications that require real-time processing or precise timing.
  3. Configurable Custom Logic (CCL): The CCL enables the creation of simple logic functions directly in hardware, reducing the need for external components and allowing for more compact and power-efficient designs.

Since, I’m just beginning to look into these MCUs, this post and likely others to follow are just to document some of the features and works in case learning about them is helpful to others.

Prerequisites

Since I’m on macOS and do not own a dedicated Microchip/Atmel branded UPDI programmer, using Microchip Studio with, say an Atmel ICE programmer is not in the cards. For the moment, I’m just working in the Arduino IDE. Not my favourite at all, but with some effort you can get the job done. To program these controllers you do need to set up both the software and hardware environment first.

Hardware setup

You will need a UPDI programmer. There are several options here:

  1. Buy a dedicated UPDI programmer. There are several on the market through Tindie and elsewhere. I have not evaluated them at all; but some seem to be quite capable, including the ability to mass-program devices without a computer.
  2. Convert an Arduino (Uno, Nano, etc.) to a dedicated programmer. This is what I did, using a 5V Nano. It’s a slight overkill but it works. Two modifications are necessary to make it work:
    • Add a 10 uF capacitor from RESET to GND
    • Add a 4K7 Ω resistor from D6 to the UPDI pin.
    • Upload jtag2updi sketch to the Arduino
  3. Convert a USB to serial adapter to a UPDI and use SerialUPDI to program the target.

Software setup

In the Arduino environment, you will need to add the megaTinyCore board library. The installation documentation is straightforward; but it involves a step which requires loading a library.json file from this URL: http://drazzy.com/package_drazzy.com_index.json. Unfortunately at the time of this writing the SSL certificate on this site has expired and Arduino IDE refused to load the library. When you are reading this, it may be fixed; but there is a workaround:

  • Navigate to the URL and when the browser complains about the security risk, just accept it and download the json file.
  • Provide the file URL to the Board Manager in the Arduino preferences. In my case it was: file:////Users/alan/Documents/dev/tinyMegaCore.json
  • Then install the library in Board Manager.

A few caveats:

  • You need to select the programmer in the Tools menu. It is jtag2updi.
  • The Upload button does not work. You need to use Sketch > Upload Using Programmer.
  • You may see the error avrdude: jtagmkII_initialize(): Cannot locate “flash” and “boot” memories in description. Apparently it is inconsequential. It certainly seems so.

Interrupts, finally

Now, finally the actual subject of the post - interrupts on the tinyAVR 1-series. I’m only concerned with pin interrupts here. They are easy to use and configure but there are a few things to be aware of:

  • Any pin can have an external interrupt
  • All interrupts on a port have a single vector. It is up to the developer to distinguish between the pins that could have generated the interrupt. For example PORTA_PORT_vect covers all of the interrupts on the PORTA pins.
  • The developer is responsible for clearing the interrupt flag in the interrupt service routine.
  • The Arduino function attachInterrupt() can be used, but it is not recommended. There are several reasons for this; but chiefly it imposes significant overhead.

The simple project I’m describing here uses one of those microwave presence detectors that you can find everywhere on AliExpress:

I’ve wired this up with an ATtiny 1614 on an SMD to DIP adapter board, with the microwave module connected to PA5 and an indicator LED to PA6. When presence is detected, the code should toggle the LED. Simple.

Set up interrupt

To set up the interrupt, we will just designate PA5 as an input pin and enable a rising signal interrupt on it:

void setup() {
    // Interrupt on PA5
    PORTA.DIRCLR = PIN5_bm;
    PORTA.PIN5CTRL = PORT_ISC_RISING_gc;
    
    // enable global interrupts
    sei();
}

Don’t forget to enable global interrupts with sei().

Interrupt service routine

As mentioned, all of the pins in a port are capable of using external interrupts, but they share a single vector. Our ISR needs to distinguish which pin generated the interrupt, do its work as quickly as possible and reset the interrupt flag, not necessarily in that order.

volatile bool didReceiveRadarPulse = false;

ISR(PORTA_PORT_vect) {
    // reset the PORTA.INTFLAGS - necessary on this series
    uint8_t portAFlags = PORTA.INTFLAGS;
    PORTA.INTFLAGS = portAFlags;
    if (portAFlags & PIN5_bm) {
        // Handle the interrupt for PA5
        didReceiveRadarPulse = true;
    }
}

The trick of reading the flags and writing the same value back to them works because of a hardware feature of this register:

So, we just read the value so we can use it to test whether our PA5 interrupt was triggered and then immediately write the value back to reset it. We could also just do the pin toggle in the ISR, but since we’re leaving the door open to doing more sophisticated things with this skeletal code, we will set a boolean didReceiveRadarPulse to true and then act on that in the loop().

Toggle a pin

In other MCUs toggling a pin often meant keeping track of the state yourself. Interestingly, the tinyAVR 1-series has a register just for toggling an output pin. So we can just PORTA.OUTTGL = PIN6_bm and it’s done!

Complete code

Here’s the full working code for ATtiny 1614.

/*

Test interrupts on ATtiny 1614

*/
#include <Arduino.h>
#include <avr/io.h>
#include <avr/interrupt.h>

volatile bool didReceiveRadarPulse = false;

ISR(PORTA_PORT_vect) {
    // reset the PORTA.INTFLAGS - necessary on this series
    uint8_t portAFlags = PORTA.INTFLAGS;
    PORTA.INTFLAGS = portAFlags;
    if (portAFlags & PIN5_bm) {
        // Handle the interrupt for PA5
        didReceiveRadarPulse = true;
    }
}

void setup() {
    PORTA.DIRSET = PIN6_bm;  // simple toggle
    PORTA.OUTCLR = PIN6_bm;  // turn off
    // Interrupt on PA5
    PORTA.DIRCLR = PIN5_bm;
    PORTA.PIN5CTRL = PORT_ISC_RISING_gc;
    // enable global interrupts
    sei();
}

// the loop routine runs over and over again forever:
void loop() {
    if (didReceiveRadarPulse) {
        PORTA.OUTTGL = PIN6_bm;
        didReceiveRadarPulse = false;  // reset our radar pulse flag
    }
}

If you have any difficulties, I can try to help. See my contact page.

References

FreeRTOS stack size on ESP32 - words or bytes?

Although FreeRTOS1 is an indispensible tool for working on anything more than the simplest application on ESP32, there are some difficulties to master, such as multitasking. Multitasking using FreeRTOS is accomplished by creating tasks with xTaskCreate() or xTaskCreatePinnedToCore(). In both of these calls, one of the parameters is uxStackDepth which is the allocated stack size for the task. The FreeRTOS documentation on the subject is clear about the units for uxStackDepth:

The number of words (not bytes!) to allocate for use as the task’s stack. For example, if the stack is 16-bits wide and uxStackDepth is 100, then 200 bytes will be allocated for use as the task’s stack. As another example, if the stack is 32-bits wide and uxStackDepth is 400 then 1600 bytes will be allocated for use as the task’s stack.

The FreeRTOS declaration of xTaskCreate() is:

BaseType_t xTaskCreate(    TaskFunction_t pvTaskCode,
                            const char * const pcName,
                            const configSTACK_DEPTH_TYPE uxStackDepth,
                            void *pvParameters,
                            UBaseType_t uxPriority,
                            TaskHandle_t *pxCreatedTask
                          );

where the units of uxStackDepth are words not bytes.

So that’s the story in vanilla FreeRTOS. Then we would expect on ESP32 with 4-byte wide words that if wanted to provide a 4096 byte deep stack, we would use a uxStackDepth value of 1024. But, the plot thickens. In the ESP-IDF implementation of FreeRTOS, the function declaration of xTaskCreate() (and friends) differs:

static inline BaseType_t xTaskCreate(
	TaskFunction_t pxTaskCode, 
	const char *const pcName, 
	const configSTACK_DEPTH_TYPE usStackDepth, 
	void *const pvParameters, 
	UBaseType_t uxPriority, 
	TaskHandle_t *const pxCreatedTask
	);

Notice that the the stack size parameter is now const configSTACK_DEPTH_TYPE usStackDepth and the documentation makes it clear:

IDF FreeRTOS also changes the units of ulStackDepth in the task creation functions. Task stack sizes in Vanilla FreeRTOS are specified in a number of words, whereas in IDF FreeRTOS, the task stack sizes are specified in bytes.2

So on ESP-IDF (and the ESP Arduino core), task creation is in bytes not words.


However, we also sometimes want to see how much of the stack allocation we’re using in order to fine-tune the allocation. In FreeRTOS, we can use the uxTaskGetStackHighWaterMark API to inspect the stack high water mark. For example:

void vTask1( void * pvParameters ) {
    UBaseType_t uxHighWaterMark;
    /* Inspect our own high water mark on entering the task. */
    uxHighWaterMark = uxTaskGetStackHighWaterMark( NULL );
    for( ;; )
    {
        /* Call any function. */
        vTaskDelay( 1000 );
        /* Calling the function will have used some stack space, we would 
        therefore now expect uxTaskGetStackHighWaterMark() to return a 
        value lower than when it was called on entering the task. */
        uxHighWaterMark = uxTaskGetStackHighWaterMark( NULL );
    }
}

So does uxTaskGetStackHighWaterMark() returns words or bytes? In the official FreeRTOS documentation for uxTaskGetStackHighWaterMark() we read:

The value returned is the high water mark in words (for example, on a 32 bit machine a return value of 1 would indicate that 4 bytes of stack were unused). If the return value is zero then the task has likely overflowed its stack. If the return value is close to zero then the task has come close to overflowing its stack.3

So, then the answer is words. But does the ESP-IDF implementation differ? Here’s what the documentation says about it:

So the type returned by uxTaskGetStackHighWaterMark() is determined by configSTACK_DEPTH_TYPE. Out-of-the box in the ESP-IDF flavour of FreeRTOS.h, this is defined in FreeRTOS.h as:

#ifndef configSTACK_DEPTH_TYPE

/* Defaults to uint16_t for backward compatibility, but can be overridden
 * in FreeRTOSConfig.h if uint16_t is too restrictive. */
    #define configSTACK_DEPTH_TYPE    uint16_t
#endif

Further, several comments on the official Espressif ESP32 forums posted by Espressif developers point to this function returning bytes not words. For example,

Stack is in bytes…4

and

Vanilla freertos is using dword for creating task and stack watermark. esp-idf is using bytes as base size in both functions.5

Conclusion

In the end, the short version of this story is that on ESP32, working in ESP-IDF and friends, that stack size is assigned and reported in bytes.

References


  1. FreeRTOS documentation ↩︎

  2. ESP-IDF documentation: FreeRTOS task creation ↩︎

  3. FreeRTOS documentation for uxTaskGetStackHighWaterMark ↩︎

  4. Comment on ESP32.com ↩︎

  5. Another comment about stack size type on ESP32.com ↩︎

Our vermiculture process: A sustainable contribution

Several people have asked me how we manage a very productive vegetable garden; so I’ve written this post as a brief description of one aspect our our approach - vermiculture. One of our overarching family goals is sustainable living. It’s basically about leaving a small footprint. A practical component of this philosophical stance is dealing with food waste. We deal with kitchen waste with a combination of bokashi composting and vermicomposting (also known as vermiculture) It’s not for the faint-of-heart and some are horrified to learn that I keep thousands - possibly hundreds of thousands - of worms in our basement.

An approach to interleaved and variable musical practice: Tools and techniques

“How do you get to Carnegie Hall” goes the old joke. “Practice, practice, practice.” But of course there’s no other way. If the science of talent development has taught us anything over the last fifty years, it’s that there is no substitute for strategic practice. Some even argue that innate musical abilities don’t exist. Whether it’s nature, nurture, or both, show me a top-notch musician and I’ll show you a person who has learned to practice well.

Telling Hazel not to match locked files

Hazel is a centrepiece of my automation suite on macOS. I rely on it to watch directories and take complex actions on files contained within them. Recently I discovered an issue with files that are locked in the Finder. If files that otherwise match all the rules are locked, then Hazel will attempt to execute the rules. But the locked status may preclude execution. For example, I began seeing frequent Hazel notifications popups such as:

Quickly change playlist view options on macOS

While Apple is slowly coming around to recognizing that some of its users listen to classical music, there is one quirk in the Music app on macOS that betrays its deep bias toward pop music. It’s this: when you create a new playlist, the application defaults to displaying the tracks in its “Playlist” view, which as far as I can tell serves no other function than to consume real-estate in the UI by displaying a thumbnail of the album art.

Obsidian file creation date debacle and a solution

Obsidian is pretty reckless with file creation dates. If you modify a note in Obsidian, it updates the file creation date. This renders Dataview queries that rely on it useless. For an introduction to this issue, see this lengthy thread on the Obsidian forums. Workarounds There are a several solutions to this problem.

  1. YAML-based dates One can include a cdate (or similar) field in the note’s front matter and just direct the Dataview query against that, e.

Changing the file creation date on macOS

If you modify a file in-place using sed with the -i option, you will get a file that has a new file creation date. On macOS 13.3.1, this is absolutely 100% true, although you will read claims otherwise. I ran into this problem while implementing a Hazel rule that updates YAML automatically in my Obsidian notes. Background I have use YAML frontmatter in my Obsidian notes. It looks like: —uid:20221120152124aliases:[20221120152124,AllAboutShell]cdate:2022-11-2015:21mdate:2023-05-1805:14type:zettel— My goal is to update the mdate field whenever the file changes.

Flatten airports in X-Plane

Some airports in X-Plane have terrain issues that can be quite entertaining. This Delta 737-800 got lost in the maze of cargo ramps at PANC and was trying to taxi back to the terminal when it encountered a steep icy taxiway. It required 65% N1 just to get up the slope. Clearly a fix is required. It turns out to be quite simple. In the global airports file apt.

Hazel deletes custom file icons, and a workaround

I use Hazel extensively for automating file management tasks on my macOS systems. Recently I found that Hazel aggressively matches an invisible system file that appears whenever you use a custom file or folder icon. I’ll describe the problem and present a workaround. In a handful of directories, I have a rule that prevents users (me) from adding certain file types. So the rule just matches any file that is not an image, for example, and deletes it.