Skip to content

Notes for using the ATtiny10 with the open source AVR toolchain

Notifications You must be signed in to change notification settings

valerionew/attiny10-notes

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 

Repository files navigation

ATtiny10-notes

These notes refer to the usage of the USBasp programmer with avr-gcc, avr-libc and avrdude on a linux system to compile and upload programs to an ATtiny10 microcontroller. With little modifications, the same notes will probably apply also to ATtiny4 and ATtiny5.

Setting up the USBasp programmer

ATtinys use the TPI interface for programming. Make sure to have your usbasp updated to the latest firmware, otherwise TPI is not supported.
At the first connection of the USBasp programmer to your computer you won't have the permissions to use it.
The way i solved this is to add an udev rule for the USBasp.

Navigate to the /etc/udev/rules.d/ folder. If you already have a 50-embedded_devices.rules file open it (with sudo).
Otherwise create it: sudo touch /etc/udev/rules.d/50-embedded_devices.rules.

Add:

# USBasp Programmer rules http://www.fischl.de/usbasp/
SUBSYSTEMS=="usb", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="05dc", GROUP="users", MODE="0666"

Now it should be mounted correctly.

Sources: [1], [2].

Compiling

The most basic command you can compile with is:

avr-gcc -mmcu=attiny10 $MYPROGRAM -o $MYPROGRAM.elf

Where $MYPROGRAM is your source file name, such as blink.c.

This probably will work for the most basic programs, but soon you'll get in trouble. See the Setting the prescaler section for example.
At some point, probably you'll want at least to add some optimization. Given the limited memory of the tiny10, -Os (optimize level 2, but perform only the options that don't increase the size) would be the best optimization type. This is not something specific to avr-gcc, it is simply from the GNU C compiler.

avr-gcc -Os -mmcu=attiny10 $MYPROGRAM -o $MYPROGRAM.elf

If you want to get more information about warnings, you can turn all on with:

avr-gcc -Wall -Wextra -Wpedantic -Werror -Os -mmcu=attiny10 $MYPROGRAM -o $MYPROGRAM.elf

See The GCC reference for warning options to select which ones you want to enable.

Check the assembly output

At some point in spacetime you will at least want to check what's happening under the hood. You can do this in multiple ways. I found that the way that i prefer is:

avr-gcc -Os -g -Wa,-adhln -mmcu=attiny10 $MYPROGRAM

It meshes the assembly lines with the c lines, so that you can see what's being compiled to what.
Source [4]

Setting the prescaler

At first, setting the prescaler wasn't an easy task.
The datasheet [3] states:

To avoid unintentional changes of clock frequency, a protected change sequence must be followed to change the CLKPS bits:

  1. Write the signature for change enable of protected I/O register to register CCP
  2. Within four instruction cycles, write the desired value to CLKPS bits

Where "the signature" is 0xD8. It is a neat safety feature, but it can get you in trouble. The first thing i did was:

  char oldsreg = SREG;   // save the interrupt setting register to oldsreg
  cli();                 // disable all the interrupts (SREG = 0)
  CCP = 0xD8;            // signature to CCP
  CLKMSR = 0;            // use clock 00: Calibrated Internal 8 MHzOscillator  
  CCP = 0xD8;            // signature
  CLKPSR = 0;            // set prescaler to :1 (0x00)
  SREG = oldsreg;        // restore the sreg, enabling the interrupts

Compiling without optimization didn't work. If you check the assembly output, you'll get:

  10:blink-prescaler-register.c ****   CCP = 0xD8;       // signature to CCP
  54               		.loc 1 10 0
  55               	/* #NOAPP */
  56 0022 4CE3      		ldi r20,lo8(60)
  57 0024 50E0      		ldi r21,0
  58 0026 68ED      		ldi r22,lo8(-40)
  59 0028 E42F      		mov r30,r20
  60 002a F52F      		mov r31,r21
  61 002c 6083      		st Z,r22
  11:blink-prescaler-register.c ****   CLKMSR = 0;       // use clock 00: Internal 8 MHz
  62               		.loc 1 11 0
  63 002e 47E3      		ldi r20,lo8(55)
  64 0030 50E0      		ldi r21,0
  65 0032 E42F      		mov r30,r20
  66 0034 F52F      		mov r31,r21
  67 0036 1083      		st Z,__zero_reg__

If you consider, for example from the instruction at memory addres 002c (signature loaded into CCP) to instruction at 0036, when 0 is loaded into CLKMSR, there are obviously more than 4 instructions.
The compiler is doing something weird to my eyes. Of course, to an expert, this would seem totally expected.
At first loads the immediate 60 in r20. 60 is the address of the CCP register. Then loads zero into r21.
At this point loads the immediate -40 (which is 0xD8 in two's complement) into r22.
Then moves r20 and r21 to r30 and r31. These two destination registers are at least a bit special, because they are address registers. r31 is the high byte, and r30 is the low byte of an address. Toghether, the asm refers to them with "Z".
Then, loads 0xD8 (stored in r22) to the CCP register, addressed by the Z register.

Why not loading everything directly to the Z register, or even directly to CCP?
Remember that this mcu is 8bit, with 8bit datapath and 16bit address. this means that to store a value at a generic address, we can't build the destination address -the content of the Z register - directly. so we construct it piecewise,one byte at the time, and then use the result for an indirect addressing store.
The extra steps are due to the optimization level: at no optimization, the compiler is doing a 1 to 1 translation of the code, and in the code we are specifying a 16 bit constant for the address (hidden in the macros defining CLKMSR and CCP), so this needs to be constructed. But the routine that generate the code to load 16bit does not know that the routine to generate a store prefer to use the Z register, so this sequence of code is generated.

If you take the same code and compile it with the -Os option, you get:

  10:blink-prescaler-register.c ****   CCP = 0xD8;       // signature to CCP
  31               		.loc 1 10 0
  32               	/* #NOAPP */
  33 0004 48ED      		ldi r20,lo8(-40)
  34 0006 4CBF      		out __CCP__,r20
  11:blink-prescaler-register.c ****   CLKMSR = 0;       // use clock 00: Internal 8 MHz
  35               		.loc 1 11 0
  36 0008 17BF      		out 0x37,__zero_reg__

Now we're talking! Loading the value into CLKMSR is the very next operation. The prescaler is working.

If you prefer, you can take two alternative ways:

  • Embed a piece of assembler directly into the c code
  • Use the clock_prescale_set(clock_div_N) from <avr/power.h>, where N is the desired prescaler. But there's no fun or learning in this option.

Sources

[1]: How to fix device permissions for the USBasp programmer - Andreas Rohner
[2]: gist: arturcieslak/50-embedded_devices.rules
[3]: ATtiny10 Datasheet
[4]: SYSTutorials - Generating Mixed Source and Assembly List using GCC

About

Notes for using the ATtiny10 with the open source AVR toolchain

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages