The Tiny1616 has four timers: TCA, TCB0, TCB1, and TCD. TCA is a 16-bit auto-reload timer with three compare channel outputs that can be used for PWM, or for periodic interrupts. This timer can be configured to count up, down, or up/down, and can also count external events (and divide them down, too). The Tiny1616 has two TCB modules, each one can be configured independently as a normal 16-bit auto-reload timer, an input capture module, or an 8-bit PWM output.
TCD is a 12-bit timer that two independent capture/compare units. This timer has separate “set” and “clear” registers, allowing arbitrary-phase PWM signals to be generated. It also has two inputs that can be programmed to capture the PWM value asynchronously, as well as to do different command functions (blanking, resetting, jumping to next compare cycle, etc).
What they don’t tell you
Not quite Single-Cycle
AVR is somewhere in the middle of Microchip’s true single-cycle performance, and the 8051 CISC
By default, -O1 is enabled, which is needed to get any sort of performance out of the part. With it, a line like this:
DDRB |= 0x10
Gets compiled into:
0000001C LDI R24,0x37 Load immediate 0000001D LDI R25,0x00 Load immediate 0000001E LDI R18,0x37 Load immediate 0000001F LDI R19,0x00 Load immediate 00000020 MOVW R30,R18 Copy register pair 00000021 LDD R18,Z+0 Load indirect with displacement 00000022 ORI R18,0x10 Logical OR with immediate 00000023 MOVW R30,R24 Copy register pair 00000024 STD Z+0,R18 Store indirect with displacement
With the optimizer on, it gets compiled into:
SBI DDRB, 4 ; set bit in I/O register
The problem is that with the optimizer at -O1, the debugger often gets confused, and breakpoints don’t fire. This has to do with the RISC nature of the processor. A line like this:
PORTB ^= 0x10;
Is decomposed into:
- Load 0x10 into a register
- Load PORTB into another register
- XOR those two registers together
- Output that result to PORTB
- Jump back to the PORTB load.
Note that we only have to do the first operation once. The problem is that when we put a break-point on that line of C source code, it actually puts a breakpoint on the first instruction, i.e.:
But we’ll never execute that instruction again, thus our break-points won’t happen.
On a CISC architecture, we would have an instruction like:
XOR PORTB 0x01
Which directly translates to a line of C code. This is much easier for the debugger to handle.
Debugging is a bit of a mess on the AVR platform. Unlike Microchip and Silicon Labs, who both blur the line between in-circuit programming and in-circuit debugging, there’s a clear demarcation with the AVR: You’ll need the 3-wire SPI interface, plus the reset pin, just to get code on your chip or program the fuses. If you want to debug your code, this is done through the debugWIRE interface, shared with the reset pin. This is actually really cool – most MCU vendors require a two-wire or three-wire connection for debugging. Once you’re in debug-wire mode, you only need a single pin to upload code and debug.
The problems with debugWIRE are:
- The lowest-cost debugger out there is the AVR Dragon, which, at $60, is a considerable cost to consider if you’re a hobbyist or student. There are no low-cost clones, and Atmel keeps the design heavily guarded. This differs from Microchip and Silicon Labs, which have schematics freely available; low-cost clones are readily available online.
- debugWIRE can only be used to upload and debug code; not burn fuses. And AVR has a many system configuration settings in fuses — clock sources, clock dividers, watchdog timer settings, and brown-out detection.
- Chicken-and-the-egg problem: To use debugWIRE, the DWEN fuse must be set, which requires the SPI-based ISP method. So you end up having to hook up the ISP lines, burn the DWEN fuse, and then you can use debugWIRE. Of course, once you’re in debugWIRE, you can’t program fuses until you unset the DWEN fuse and go back to ISP mode. There’s a special debugWIRE message that unsets the DWEN fuse.