Electronic – ATTiny414 inline assembly operand constraints for IO pin parameter

assemblyattinyavravr-gcc

I'm trying to use the light_ws2812 library to drive WS2812 LEDs from an ATTiny414.
The core of that library is an inline assembly snippet that bitbangs the serial line. here it is with the timing-nops stripped out:

void inline ws2812_sendarray_mask(uint8_t *data,uint16_t datlen,uint8_t maskhi)
{
  uint8_t curbyte,ctr,masklo;
  uint8_t sreg_prev;

  ws2812_DDRREG |= maskhi; // Enable output

  masklo  =~maskhi&ws2812_PORTREG;
  maskhi |=        ws2812_PORTREG;

  sreg_prev=SREG;
  cli();  

  while (datlen--) {
    curbyte=*data++;

    asm volatile(
    "       ldi   %0,8  \n\t"
    "loop%=:            \n\t"
    "       out   %2,%3 \n\t"    //  '1' [01] '0' [01] - re
    "       sbrs  %1,7  \n\t"    //  '1' [03] '0' [02]
    "       out   %2,%4 \n\t"    //  '1' [--] '0' [03] - fe-low
    "       lsl   %1    \n\t"    //  '1' [04] '0' [04]
    "       out   %2,%4 \n\t"    //  '1' [+1] '0' [+1] - fe-high

    "       dec   %0    \n\t"    //  '1' [+2] '0' [+2]
    "       brne  loop%=\n\t"    //  '1' [+3] '0' [+4]
    :  "=&d" (ctr)
    :  "r" (curbyte), "I" (_SFR_IO_ADDR(ws2812_PORTREG)), "r" (maskhi), "r" (masklo)
    );
  }

  SREG=sreg_prev;
}

An obvious difference between the 'regular AVR' and the ATTiny*14 series
C definitions is that the IO registers aren't called DDRx and PORTx but PORTx.DIR and PORTx. I already fixed that in the light_ws2812 header and it seems to work fine.

After this I am however getting an error with the assembly operand 2 ("I" (_SFR_IO_ADDR(ws2812_PORTREG))):

lib/light_ws2812.c: In function 'ws2812_sendarray_mask':
lib/light_ws2812.c:119:5: warning: asm operand 2 probably doesn't match constraints
     asm volatile(
     ^~~
lib/light_ws2812.c:119:5: error: impossible constraint in 'asm'
make: *** [Makefile:28: lib/light_ws2812.o] Error 1

I also tried using a lowercase i as the constraint instead of the I, which changes the error to

/tmp/ccBR4JKE.s:48: Error: operand out of range: 1028

Looking at the datasheet this value makes sense, PORTA starts at 0x400 (1024) and the PORTx.OUT register has an additional offset of 4 bytes, placing PORTA.OUT at 0x404 = 1028.

with a 16-bit register constraint like x or w it compiles but then I get a linker error:

avr-ld: lib/light_ws2812.o: in function `loop32':
light_ws2812.c:(.text+0x38): undefined reference to `r30'

I am compiling this using avr-gcc and avr-ld, with a current avr-libc that I manually added the relevant files from the Atmel ATtiny Series Device Support package to.

Best Answer

The ATTiny 1-series has the GPIO registers mapped outside of the io port register space, so they cannot be accessed using out or in. You will either need to use the VPORT registers, or load the address of the io port into a register and use an st instruction to write the data. If you decide to use a regular store instruction, it may change the timing of the loop so you may have to adjust the number of nops.