Although Moe123 has been kind enough to suggest that I could write a tutorial/guide for some of the features of the ATtiny 0 series, I don't yet have enough information gathered to produce one, neat topic. I also need to make progress on the current projects before I can gather my thoughts into anything useful as learning material.
So I thought I'd start a thread with perhaps notes on what I have found using my ATtiny402 that may not be obvious on first read of the datasheet (which is already ranking low in our trust because there's been such an attempt at common data for common parts that we're not sure what's been copy-pasted and is wrong!). If this is not the right place to record these notes, please mods feel free to move it and tell me. Hopefully one day I'll have the makings of a tutorial from all the snippets here. At the very least, they may be examples of easy pitfalls for novice users of AVRs in general and the tiny 0 and 1 series specifically.
TCB0 interrupt effect on other output pins
Datasheet references: 21.3 [TCB0] Functional Description
The first one I wanted to mention that wasn't obvious to me as a new user - I inadvertently (as in "write 1 to anything which might trigger a pulse"!) set the TCB0 interrupt to fire when I configured it to output its waveform pulse on the WO pin (PA6) in Single Shot mode (Figure 21-8 in the 202/402 datasheet). Here's the timer setup.
TCB0.CCMP = 0x0001; /* Compare or Capture: 0x0 0x7FFF; */ TCB0.CNT = 0x0001; /* Count: 0x0 */ TCB0.CTRLB = 0 << TCB_ASYNC_bp /* Asynchronous Enable: disabled */ | 1 << TCB_CCMPEN_bp /* Pin Output Enable: enabled */ // | 0 << TCB_CCMPINIT_bp /* Pin Initial State: disabled */ | TCB_CNTMODE_SINGLE_gc; /* Single Shot */ // TCB0.DBGCTRL = 0 << TCB_DBGRUN_bp; /* Debug Run: disabled */ TCB0.EVCTRL = 1 << TCB_CAPTEI_bp; /* Event Input Enable: enabled */ // | 0 << TCB_EDGE_bp /* Event Edge: disabled */ // | 0 << TCB_FILTER_bp; /* Input Capture Noise Cancellation Filter: disabled */ TCB0.INTCTRL = 1 << TCB_CAPT_bp; /* Capture or Timeout: enabled */ TCB0.CTRLA = TCB_CLKSEL_CLKDIV1_gc /* CLK_PER (No Prescaling) */ | 1 << TCB_ENABLE_bp; /* Enable: enabled */ // | 0 << TCB_RUNSTDBY_bp /* Run Standby: disabled */ // | 0 << TCB_SYNCUPD_bp; /* Synchronize Update: disabled */
Bear in mind that I have not set up an ISR to handle this timer interrupt! After initialising some other pins as output, PA7 as an event input and the event system to fire TCB0, based on PA7, I couldn't get any of the other pins to act as digital outputs. I wanted to use one to tell whether the PAn event generator was doing just rising edges or falling edges too. I had to comment out this line to get the other pins to output OUTSET commands:
//TCB0.INTCTRL = 1 << TCB_CAPT_bp; /* Capture or Timeout: enabled */
I haven't dug into the datasheet but it seems funny to me that the GPIOs are affected by the TCB0 flag being raised but not handled in the ISR, however the TCB0 single shot kept functioning perfectly!
Event Generator: I/O Port pins
Datasheet references: 14.5.3 Asynchronous Channel n Generator Selection , 14.5.5 Asynchronous User Channel n Input Selection, 16.5.11 Pin n Control
I wanted to generate an event to trigger TCB0 in single shot mode. Because the event is a falling edge, I tried to use a connection to PA7, configured as an input, as an Event Generator. Here's the code which sets PA7 as the event generator for Asynchronous Event Channel 0 and connects TCB0 as an Event User of that Event Channel:
EVSYS.ASYNCUSER0 = EVSYS_ASYNCUSER0_ASYNCCH0_gc; //enable TCB0 input to take channel 0 EVSYS.ASYNCCH0 = EVSYS_ASYNCCH0_PORTA_PIN7_gc; //enable event sys to take PA7 logic edge as an input
When I connected up to my oscilloscope, the pulse generated by TCB0 on PA6 is only on a rising edge of PA7's input. I couldn't see anywhere that this was recorded in the event system section of the datasheet, so I thought it worth observing in public. Please open up the discussion here if I have missed something like taking advantage of INVEN in the PORT configuration to make the PA7 Event occur on a falling edge. I may try some of those PORT settings just to explore a bit. EDIT: It absolutely can be configured in the INVEN bit setting for the pin. You can ALSO get single shot events generated on either edge based on the TCB0.EVCTRL register but I had unexpected results with this - read on for more details.
PORTA.PIN7CTRL = PORT_INVEN_bm; // invert the logic of the input (0 = 1 and 1 = 0)
This will now generate the ASYNCCH0 event on a falling edge rather than a rising edge. The datasheet is quite clear now I have looked in the PORT configuration section, especially the helpful PORT block diagram. Here it is with the config used to generate a falling edge event:
Note: I have not set any of the ISC bits in the PINCTRL register - these seem ONLY for interrupts, not events. This makes me think that only RISING and FALLING edges will generate Pin I/O events. Interrupts appear to be able to trigger on RISING, FALLING, BOTH and LOW (but not high - maybe INVEN can sort that for us?!).
The next discovery I made was that you can apparently change the event edge which the single shot pulse responds to from within the TCB0 (rather than PORT) settings:
From this I understand that the following set to 1 or 0 will give EITHER a pulse on the rising edge, OR a pulse on the falling edge of the event (PA7 input, in this case). I tried but it appeared to pulse on BOTH the rising AND falling edges of the input pulse. I admit there is some ripple in the blue (PA7 input) trace but to trigger the pulse on both edges seems like unexpected/undocumented behaviour. What do you think? (BTW, PORT INVEN has no effect now both edges generate an event) Here are traces from the same configuration of:
TCB0.EVCTRL = 1 << TCB_EDGE_bp;
So my experience is that TCB_EDGE = 1 leads to BOTH rising and falling edges being responded to by the single shot pulse. Thoughts? I appreciate that you can't see all the code here and I can post it if that would help.
TCB0 Single Shot: Asynchronous versus Synchronous
Datasheet references: 21.3.3.2 [TCB0] Output
There's an option to drive the Waveform Output (WO) to a pin in the new ATtinys. This is similar to functionality of this nature in older microcontrollers. I have never really used it before. In TCB0, on the ATtiny402, WO only goes to one pin - PA6. So if you want this feature, you can't use PA6 for something else at the same time. For example, PA6 is also one of the two fully asynchronous pins on this 5 GPIO (effectively, if you reserve RESET/UPDI) microcontroller. Don't confuse asynchronous pins with asynchronous operation of WO - it's not quite the same. Anyway, when you set up TCB0, you have three key settings that will determine when your pulse starts and finishes (aside from the TCB0.CCMP value, that is). I find the table confusing:
What does CTRLB = '0' or '1' mean? This is an 8 bit register as far as I can tell:
So I have not attempted to test the difference between two alleged synchronous modes. I have instead tried with CTRLB.ASYNC = 0 and 1.
TCB0.CTRLB = 0 << TCB_ASYNC_bp; // OR TCB0.CTRLB = 1 << TCB_ASYNC_bp;
The traces here are for a 5MHz ATtiny402. The blue trace is my GPIO toggling as an input into my event generator (set to generate event on rising edge in this case) and the yellow trace is my Event User, TCB0, operating in single shot mode for a TCB0.CCMP = 0x0001, so only 1 count in there.
Here is the trace in synchronous mode, where the pulse starts in time with the clock cycles and finishes in time with the clock cycles:
Note there appears to be an 800ns delay before the pulse begins. It lasts 400ns and ends 1.2us after the rising edge of the event generator pin.
Now here's the asynchronous version, where the pulse starts as soon as the event system generates the event and ends when TCB0 has responded to the event, counted up to TCB0.CCMP and ends the pulse in line with clock cycles:
Note that the pulse starts the instant that the event generator pin edge rises but lasts 800ns longer and finishes at exactly the same time as the synchronous version.
Perhaps the main takeaway here is that if pulse length control is critical and you want short pulses, go synchronous but if you want to respond as fast as you can to an event and the pulse duration is less critical, go asynchronous. I would be interested if anyone has any ideas about if there is a better interpretation of Table 21-3 than I can make!
TCB0: Asynch + Edge = bouncy?
When I tried:
TCB0.EVCTRL = 1 << TCB_EDGE_bp; TCB0.CTRLB = 1 << TCB_ASYNC_bp;
I got these traces:
It looks like we're getting an Asynchronous single shot pulse, followed 600ns (3 clock cycles) later by a synchronous (in duration terms) single shot pulse. By the time the second pulse has finished, we're 2.2us from the rising/falling edges that generated the event. The datasheet talks about a noise canceller. I'm not sure it's describing my fairly tidy 0.5Hz square wave but you can make your own mind up:
So, just to see what it did, I enabled it like this:
TCB0.EVCTRL = 1 << TCB_FILTER_bp;
And these are the traces I got:
Now our pulse is back down to 1.8us, so rather than saying we have just filled in the gap between two pulses, it looks like the claim that the noise canceller adds four clock cycles to the pulse before changing state may be true (although 1200ns + 4 * 200ns = 2000ns, not 1800ns - more deviation from the datasheet or is it just me?!).
Finally, to see if my measurement setup was a significant contributory factor, I changed some things. Firstly, the device under test is a bare ATtiny402 sitting on a SOIC8 breakout board, stuffed in a breadboard with a 3.3V supply, GND and UPDI (via resistor) to an Arduino Nano clone in jtag2updi role. The 3.3V rail is the quieter of the two, I understand. I placed a 0.1uF ceramic cap between 402's VCC and GND. No change to the double pulse when I turned the noise filter off. I also changed my oscilloscope probes from 1x to 10x, just in case that helped. No change. Finally, I moved the capacitor between PA1, which was generating the square wave, and GND. Although the size of the cap is comically large for this timescale, I think it nicely shows the trigger thresholds for rising/falling edges on the PA7 input. But still no effect on the unfiltered double async pulse.
Fortunately for me, I don't need to capture both edges (even though that isn't a feature that is apparently available!) and I can avoid setting TCB_EDGE by using PORT_INVEN to select which edge I want to pulse off.
RTC CMP Interrupt doesn't work, at least in Standby?
Datasheet references: 22.3 RTC Functional Description, 22.11.3 Interrupt Control,
This is not going to be as fleshed out as some of the other observations for now but I can't get seem to get the RTC interrupt to work. I'm using Standby mode, internal 32kHz osc, a range of prescalers, no BOD, 4.7V supply. Bare 402 with an LED and resistor on PA2 to show output. Measurements are being made with a Joulescope (which is awesome BTW) and I can definitely see the 402 entering and leaving sleep. What's confusing me is that I can get the RTC interrupt on OVF and the CMP interrupt is acting like OVF and running until the RTC.PER value, regardless of what I set in RTC.CMP. Writing different values to RTC.PER seems to change the duration between interrupts firing, even with a solely CMP-based interrupt implemented (no OVF involved) and I suppose I could live with this but I wanted to see if someone else can spot what's going on here (datasheet, me, my code)...
Here's key elements of code, for use with megaTinyCore, which uses the Arduino IDE but calls the iotn402.h and hasn't had an issue with register addresses on anything else I have tried:
#include <avr/sleep.h> #include <avr/interrupt.h> void RTC_init(void) { while (RTC.STATUS > 0) { ; /* Wait for all register to be synchronized */ } RTC.CLKSEL = RTC_CLKSEL_INT32K_gc; /* 32.768kHz Internal Crystal Oscillator (XOSC32K) */ while (RTC.STATUS > 0) { ; /* Wait for all register to be synchronized */ } RTC.CTRLA = RTC_PRESCALER_DIV1_gc | RTC_RTCEN_bm | RTC_RUNSTDBY_bm; // RTC.INTCTRL |= RTC_OVF_bm; RTC.INTCTRL |= RTC_CMP_bm; while (RTC.STATUS > 0) { ; /* Wait for all register to be synchronized */ } RTC.CMP = 0x000F; //RTC.PER = 0xFF; sei(); } ISR(RTC_CNT_vect) { // RTC.INTFLAGS = RTC_OVF_bm; RTC.INTFLAGS = RTC_CMP_bm; } void setup() { RTC_init(); /* Initialize the RTC timer */ PORTA.DIRCLR = PIN2_bm; PORTA.DIRCLR = PIN1_bm; PORTA.DIRCLR = PIN0_bm; PORTA.DIRCLR = PIN3_bm; PORTA.DIRCLR = PIN6_bm; PORTA.DIRCLR = PIN7_bm; PORTA.PIN1CTRL = PORT_PULLUPEN_bm; PORTA.PIN3CTRL = PORT_PULLUPEN_bm; //PORTA.PIN6CTRL = PORT_PULLUPEN_bm; // ignore, this is for power saving in full code // PORTA.PIN2CTRL = PORT_PULLUPEN_bm; // ignore, this is for power saving in full code PORTA.PIN7CTRL = PORT_PULLUPEN_bm; // set_sleep_mode(SLEEP_MODE_PWR_DOWN); set_sleep_mode(SLEEP_MODE_STANDBY); sleep_enable(); } void loop() { PORTA.DIRSET = PIN2_bm; PORTA.OUTSET = PIN2_bm; // LED on sleep_cpu(); PORTA.OUTCLR = PIN2_bm; // LED off PORTA.DIRCLR = PIN2_bm; sleep_cpu(); }
More observations to come...