Studio7 ATtiny5 pgm_read_byte issue

Go To Last Post
8 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi folks, newbie Karl here.

 

I'm not having much luck reading data stored on program memory in my ATtiny5. It works great on an ATtiny85.

 

I define an array of data in Program Memory:

 

const uint8_t  sinewave[] PROGMEM= //64 values
{
    0x80,0x83,0x86,0x89,0x8c,0x8f,0x92,0x95,0x98,0x9c,0x9f,0xa2,0xa5,0xa8,0xab,0xae,
    0xb0,0xb3,0xb6,0xb9,0xbc,0xbf,0xc1,0xc4,0xc7,0xc9,0xcc,0xce,0xd1,0xd3,0xd5,0xd8,
    0xda,0xdc,0xde,0xe0,0xe2,0xe4,0xe6,0xe8,0xea,0xec,0xed,0xef,0xf0,0xf2,0xf3,0xf5,
    0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfc,0xfd,0xfe,0xfe,0xff,0xff,0xff,0xff,0xff
};

 

I set up timer0 and enter a simple delay loop:

 

int main(void)
{
    int i;
    
    // PB1 output
    DDRB = (1<<PB1);

    // Timer0 in mode 14, fast PWM with ICR0 as top.
    // Enable OC0A and OC0B, lower mode bits
    TCCR0A = (1<<COM0A1) | (1<<COM0B1) | (1<<WGM01);
    // Set top to 1000
    ICR0 = 1000;
    // Start timer with prescaler 1:8 and set upper mode bits
    TCCR0B = (1<<CS01)  | (1<<WGM03) | (1<<WGM02);

 

    while(1)
    {
            OCR0A = pgm_read_byte(&sinewave[i]);
            i++;
            i %= 64;
            _delay_ms(1);

    }
}
 

Here is code from the disassembler windows:

 

Code generated for ATTiny85 :

    OCR0A = pgm_read_byte(&sinewave[i]);
000000CA  LDS R30,0x0066        Load direct from data space 
000000CC  LDI R31,0x00        Load immediate 
000000CD  SUBI R30,0xE2        Subtract immediate 
000000CE  SBCI R31,0xFF        Subtract immediate with carry 
000000CF  LPM R30,Z        Load program memory 
000000D0  OUT 0x29,R30        Out to I/O location 

 

 

Code generated for ATTiny5 :

            OCR0A = pgm_read_byte(&sinewave[i]);
00000073  MOV R30,R20        Copy register 
00000074  MOV R31,R21        Copy register 
00000075  SUBI R30,0xEA        Subtract immediate 
00000076  SBCI R31,0x7F        Subtract immediate with carry 
00000077  LDD R30,Z+0        Load indirect with displacement 
00000078  LDI R31,0x00        Load immediate 
00000079  OUT 0x27,R31        Out to I/O location 
0000007A  OUT 0x26,R30        Out to I/O location 

 

 

My Observations on the ATTiny5 code:

sinewave[] starts at address 0x0016. 

"i" seems to be held in pair R20,21.

"Z" register sems to be held in pair R30,31.

If "i"=0, then after step 75, the Z register value is 0x0016. After step 76, the Z register value is 0x8016.  Then the LDD command retruns 0x00.

 

 

So, that is my problem, as I understand it. Any insight would be much appreciated. Thanks!

 

 

Last Edited: Mon. Feb 18, 2019 - 09:48 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Which version of the toolchain?  AVR GCC version?  What was your build command?

 

The t4/5/9/10 are somewhat different beasts.  No LPM instruction (which is used by PROGMEM et al), but flash is mapped into SRAM at 0x4000.

 

Note:

00000075  SUBI R30,0xEA        Subtract immediate
00000076  SBCI R31,0x7F        Subtract immediate with carry

This subtracts 0x7FEA from Z, which is like adding (0x10000 - 0x7FEA) or 0x8016 to Z.  So in effect, Z is loaded with the address of sinewave[i], assuming that the sinewave table starts at 0x8016.  That does seem to be incorrect, as I would expect it to be at 0x4016.  That's because (in my build of your test code) it ends up at flash byte address 0x0016, which is mapped to SRAM address 0x4016.

 

Support for PROGMEM seems to be a bit broken for the t4/5/9/10.  I would have suggested __flash instead, but that won't work because it expects an AVR which supports LPM and r0, neither of which is available on that family.

 

A workaround is to continue to use pgm_read_byte(), but to declare the table as __flash instead of PROGMEM:

const __flash uint8_t  sinewave[] = //64 values

As I say, this should allow access without the need for the pgm_() functions:

            OCR0A = sinewave[i];

... but:

$ avr-gcc -W -Wall -Werror -O3 -g -mmcu=attiny5 -DF_CPU=1000000 -save-temps foo.c -o foo.elf && avr-objdump -S foo.elf > foo.lss
foo.s: Assembler messages:
foo.s:71: Error: illegal opcode elpm for mcu avrtiny
foo.s:72: Error: register name or number from 16 to 31 required

... which is complaining about:

	lpm
	mov r22,r0

 

However, if we stick with pgm_read_byte(), it compiles fine, and we get:

            OCR0A = pgm_read_byte(&sinewave[i]);
  80:	e4 2f       	mov	r30, r20
  82:	f5 2f       	mov	r31, r21
  84:	ea 5e       	subi	r30, 0xEA	; 234
  86:	ff 4b       	sbci	r31, 0xBF	; 191
  88:	e0 81       	ld	r30, Z
  8a:	f0 e0       	ldi	r31, 0x00	; 0
  8c:	f7 bd       	out	0x27, r31	; 39
  8e:	e6 bd       	out	0x26, r30	; 38

... which is what we want.  Note that subtracting 0xBFEA is like adding (0x10000 - 0xBFEA) or 0x4016, the correct SRAM-mapped address of the table.

 

I don't know if there is a less kludgey way of doing this.  Perhaps I've missed the 'right' way to do it with this family of AVR.  I haven't spent time searching for a bug report on the AVR GCC or AVR Libc bugzillas.

 

EDIT:  copy/paste error in .s extract

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

Last Edited: Tue. Feb 19, 2019 - 01:28 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

After you get that sorted, you'll want to pay some attention to the fact that TIMER0 on the t5 is 16-bits.  Also, 'i' need not be int, but could instead be the narrower uint8_t.

 

Not sure why you've set TOP to 1000.  Since the waveform table is 8-bit, would be better to set it to 255, no?

 

Or, you could just use mode 5, which has a fixed top of 0xFF.  Then you need only write to OCR0AL.

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

That's a lot of good information, and a lot of food for thought for this newbie. I'm new to this entire family of chips. I'm up to my ears in datasheets and and Studio 7 tutorials!

I found vague references to a reduced instruction set for this tiny4/5/9/10 group, but no real listing. Thanks for clearing up why the same code complies so differently.

Yes, I had set TOP to 255 in another version of the code, but that didn't work either. A work mate had written the code for a tiny85, and I'm trying to get it into a tiny5. When it failed miserably, I found a simple pwm LED fade demo, copied it into Studio 7, and it worked, so I started with that code. That's where the TOP of 1000 originated. Upon investigating my work mate's data, I realized I could use 255 for TOP, but as I say that didn't work.

So, if I follow your explanation correctly, all I need to do is to declare my data as flash, then all the address math will work and I will get my data. Cool.

I'm also going to investigate mode 5. Sounds interesting.

I admit I don't even know enough about the real nuts and bolts of this to answer your first few questions. I just click on icons to build, no real understanding of the process at this point. This is my Hello World project, I'm learning the device and the devopment process. It's been about 30 years since I wrote any assembly code, and about six years since I've done any coding at all.

But I'm having fun learning. Thanks for your help.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Karl,

I have not looked at this particular datasheet, but normally at the end of the datasheet there is an overview of all the supported instructions. Then on the Microchip site there should be a full AVR8 instruction set document were all is described in more detail.

 

and reading and re-reading a datasheet is never a bad thing.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
            i %= 64;

You may be lucky and the compiler may be smart enough to spot that a divide by a multiple of 2 can be done more easily but note that % is really a divide and on a brain dead chip like these very limited tiny's that can be a large library function that could eat a lot of your flash so when you program these extremely resource limited things you need to be thinking all the time about the implications on resource usage. In this case:

i &= 0x3F;

is a much "cheaper" (one instruction) method to keep the variable in the 0..63 range.

 

EDIT: OK so I built your code (-01 optimisation) and I get this for the handling of "i":

		i++;
  8c:	4f 5f       	subi	r20, 0xFF	; 255
  8e:	5f 4f       	sbci	r21, 0xFF	; 255
		i %= 64;
  90:	4f 73       	andi	r20, 0x3F	; 63
  92:	50 78       	andi	r21, 0x80	; 128
  94:	55 23       	and	r21, r21
  96:	34 f4       	brge	.+12     	; 0xa4 <main+0x3c>
  98:	41 50       	subi	r20, 0x01	; 1
  9a:	51 0b       	sbc	r21, r17
  9c:	40 6c       	ori	r20, 0xC0	; 192
  9e:	5f 6f       	ori	r21, 0xFF	; 255
  a0:	4f 5f       	subi	r20, 0xFF	; 255
  a2:	5f 4f       	sbci	r21, 0xFF	; 255

So there's good news and there's bad news. The good news is that the compiler was smart enough to spot it was effectively an AND 0x3F. The bad news is that (as Joey pointed out) that "int" was completely the wrong choice of variable type. It's 2 bytes wide when to count 0..63 it only needs to be 1 byte and "int" by default is signed so this adds signed number handling. If I change "i" to be the uint8_t it should have been in the first place I get:

		i++;
  8c:	4f 5f       	subi	r20, 0xFF	; 255
		i %= 64;
  8e:	4f 73       	andi	r20, 0x3F	; 63

As I say, because you picked the worst kind of AVR to learn on you have to be thinking about this stuff all the time. It's using 1 less byte of the 16 registers and just 4 bytes of code to increment/limit rather than 24 (!) bytes previously.

Last Edited: Tue. Feb 19, 2019 - 09:18 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Update: I got it working, using MODE5. To conserve data space, only 1/4 of a sine wave is represented in the data, and the program handles reading back and forth thru the data and performing math to create the negative half. 

 

I even had enough space left over to get the ADC to read a pot and control the delay setting, thus controlling the frequency of the output sine wave. WooHoo!

 

Thanks for the help, fellas! 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

KarlFrick54 wrote:
WooHoo!
Glad you got it sorted.

 

joeymorin wrote:
This subtracts 0x7FEA from Z, which is like adding (0x10000 - 0x7FEA) or 0x8016 to Z. So in effect, Z is loaded with the address of sinewave[i], assuming that the sinewave table starts at 0x8016. That does seem to be incorrect, as I would expect it to be at 0x4016. That's because (in my build of your test code) it ends up at flash byte address 0x0016, which is mapped to SRAM address 0x4016.
This was still bothering me, so I tried to find a bug report:

 

https://www.google.com/search?q="avr+libc"+"bugzilla"+"0x4000"+"progmem"

 

Found this:

https://www.mikrocontroller.net/articles/Avr-gcc_Bugs

Leading to this:

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=71948

 

Reported 2016-07-20, solved 2016-08-01.

 

So it seems I got it backwards.  The reduced core tinies are intended to be used like this:

const uint8_t  sinewave[] PROGMEM = //64 values
{
    0x80,0x83,0x86,0x89,0x8c,0x8f,0x92,0x95,0x98,0x9c,0x9f,0xa2,0xa5,0xa8,0xab,0xae,
    0xb0,0xb3,0xb6,0xb9,0xbc,0xbf,0xc1,0xc4,0xc7,0xc9,0xcc,0xce,0xd1,0xd3,0xd5,0xd8,
    0xda,0xdc,0xde,0xe0,0xe2,0xe4,0xe6,0xe8,0xea,0xec,0xed,0xef,0xf0,0xf2,0xf3,0xf5,
    0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfc,0xfd,0xfe,0xfe,0xff,0xff,0xff,0xff,0xff
};
            OCR0A = sinewave[i];

This gives:

            OCR0A = sinewave[i];
  82:	70 e0       	ldi	r23, 0x00	; 0
  84:	77 bd       	out	0x27, r23	; 39
  86:	66 bd       	out	0x26, r22	; 38

Which at first seems confusing, but when we look a bit closer:

  7c:	60 e8       	ldi	r22, 0x80	; 128



int main(void)
{
    int i = 0;
  7e:	40 e0       	ldi	r20, 0x00	; 0
  80:	50 e0       	ldi	r21, 0x00	; 0
    TCCR0B = (1<<CS01)  | (1<<WGM03) | (1<<WGM02);


    while(1)
    {
            OCR0A = sinewave[i];
  82:	70 e0       	ldi	r23, 0x00	; 0
  84:	77 bd       	out	0x27, r23	; 39
  86:	66 bd       	out	0x26, r22	; 38
            i++;
  88:	4f 5f       	subi	r20, 0xFF	; 255
  8a:	5f 4f       	sbci	r21, 0xFF	; 255
            i %= 64;
  8c:	4f 73       	andi	r20, 0x3F	; 63
  8e:	55 27       	eor	r21, r21
	#else
		//round up by default
		__ticks_dc = (uint32_t)(ceil(fabs(__tmp)));
	#endif

	__builtin_avr_delay_cycles(__ticks_dc);
  90:	69 ef       	ldi	r22, 0xF9	; 249
  92:	70 e0       	ldi	r23, 0x00	; 0
  94:	61 50       	subi	r22, 0x01	; 1
  96:	70 40       	sbci	r23, 0x00	; 0
  98:	e9 f7       	brne	.-6      	; 0x94 <main+0x2c>
  9a:	00 c0       	rjmp	.+0      	; 0x9c <main+0x34>
  9c:	00 00       	nop
  9e:	e4 2f       	mov	r30, r20
  a0:	f5 2f       	mov	r31, r21
  a2:	ea 5e       	subi	r30, 0xEA	; 234
  a4:	ff 4b       	sbci	r31, 0xBF	; 191
  a6:	60 81       	ld	r22, Z
  a8:	ec cf       	rjmp	.-40     	; 0x82 <main+0x1a>

... it becomes a bit clearer.  The correct offset of 0x4000 is used, just as when we used __flash and pgm_read_byte().

 

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"We see a lot of arses on handlebars around here." - [J Ekdahl]