avr-rust / avrd

AVR device definitions
MIT License
46 stars 5 forks source link

Support for peripheral-organized chips (newer ATinys) is broken #23

Open hansSchall opened 1 year ago

hansSchall commented 1 year ago

Some AVRs (for example 1-series) have a different register documentation scheme. The address of a register is the specified register offset PLUS the peripheral device address. This way one peripheral can be used multiple times without defining the same registers multiple times.

Example ATiny 1-series: PORTA.OUTTGL

Datasheet: https://ww1.microchip.com/downloads/en/DeviceDoc/ATtiny212-214-412-414-416-DataSheet-DS40002287A.pdf

Why OUTTGL, not OUT (equivalent to PORTx on ATmega)? The register OUT in the current version does not even reference the Port I/O peripheral. It is a wrong reference to another peripheral (writing to OUT currently results in writing VPORTA.DIR, this is the single-cycle access register to PORTA.DIR, equivalent to DDRA on ATmega). When comparing this might be confusing, i would recommend using PORTx.OUTTGL as example.

The offset of register OUTTGL in the PORT peripheral is 0x7 (see page 140), but the address of PORTA.OUTTGL is not 0x7, instead it is 0x407. The actual address is the peripheral address of PORTA (defined in the peripheral address map, see page 45) plus the register offset defined in the register description.

A quick look to the current definition clarifies the problem: The peripheral address isn't taken into account. Multiple registers share the same address (all registers whose offset inside the peripheral is 0x7) and OUTTGL isn't assigned to any specific port.

// https://github.com/avr-rust/avrd/blob/master/src/gen/attiny212.rs
...

/// Application Code Section End.
pub const APPEND: *mut u8 = 0x7 as *mut u8;

/// LUT Control 0 C.
pub const LUT0CTRLC: *mut u8 = 0x7 as *mut u8;

/// User Row Byte 7.
pub const USERROW7: *mut u8 = 0x7 as *mut u8;

/// Control F Set.
pub const CTRLFSET: *mut u8 = 0x7 as *mut u8;

/// Serial Number Byte 4.
pub const SERNUM4: *mut u8 = 0x7 as *mut u8;

/// Control C.
pub const CTRLC: *mut u8 = 0x7 as *mut u8;

/// Master Address.
pub const MADDR: *mut u8 = 0x7 as *mut u8;

/// Clock Select.
pub const CLKSEL: *mut u8 = 0x7 as *mut u8;

/// Output Value Toggle.
pub const OUTTGL: *mut u8 = 0x7 as *mut u8;
...

Instead, the definition of PORTA has to look like this:

// this works, tested on binary compatible Tiny204 (see PS)
pub mod PORTA {
    const OFFSET: usize = 0x400;
    pub const DIR: *mut u8 = (OFFSET + 0x0) as *mut u8;
    pub const DIRSET: *mut u8 = (OFFSET + 0x1) as *mut u8;
    pub const DIRCLR: *mut u8 = (OFFSET + 0x2) as *mut u8;
    pub const DIRTGL: *mut u8 = (OFFSET + 0x3) as *mut u8;
    pub const OUT: *mut u8 = (OFFSET + 0x4) as *mut u8;
    pub const OUTSET: *mut u8 = (OFFSET + 0x5) as *mut u8;
    pub const OUTCLR: *mut u8 = (OFFSET + 0x6) as *mut u8;
    pub const OUTTGL: *mut u8 = (OFFSET + 0x7) as *mut u8;
    pub const IN: *mut u8 = (OFFSET + 0x8) as *mut u8;
    pub const INT: *mut u8 = (OFFSET + 0x9) as *mut u8;

    pub const PINCTRL0: *mut u8 = (OFFSET + 0x10) as *mut u8;
    pub const PINCTRL1: *mut u8 = (OFFSET + 0x11) as *mut u8;
    pub const PINCTRL2: *mut u8 = (OFFSET + 0x12) as *mut u8;
    pub const PINCTRL3: *mut u8 = (OFFSET + 0x13) as *mut u8;
    pub const PINCTRL4: *mut u8 = (OFFSET + 0x14) as *mut u8;
    pub const PINCTRL5: *mut u8 = (OFFSET + 0x15) as *mut u8;
    pub const PINCTRL6: *mut u8 = (OFFSET + 0x16) as *mut u8;
    pub const PINCTRL7: *mut u8 = (OFFSET + 0x17) as *mut u8;
}

// Usage:
volatile_store(PORTA::OUTTGL, 0x0f); // toggles pins PA0 - PA3

PS: Attiny 0-series (202,204,402,404,...) could also be supported by this lib, as they are binary compatible. Microchip says: "Register names, bit names, and bit group names can be expected to be the same for both families, when present." (https://ww1.microchip.com/downloads/en/Appnotes/Migration-Between-tinyAVR-1-and-0-series-00002636B.pdf). When using only peripheral modules present on all devices, one program can be used on all 0- and 1-series devices without recompiling(!).

Currently 0-series devices are supported (if there weren't this bug) by replacing the 0 in the part name with 1 and making sure, that only existent peripheral modules are used.