Accessing to program memmory pgmstate / lpm

Hi

I’m writing a simple synthesizer and to do so I’m using interruption (timer2 with fast pwm, the OVF interupt, at 62,5kHz).

Since the code is used a lots of time in a second, it should be quick (less than 200 cycles) and I was unable tu fit all the stuff using C, so I rewrite the code in assembly, and it’s working fine (I was able to ad one more channel, and there is still free time for a noise channel!).

But to generate noise, I’m using a 4096 char table with random numbers stored in program memory. I tried to look at GCC generated assembly for program memory access. I write some test code like

#include <avr/pgmspace.h>
#include <avr/io.h>
#include <avr/interrupt.h>

#include "white_noise.h"
/*
char white_noise[4096] PROGMEM = {
70, ...
*/

void foo(void)
{
	char bar;
	int k = 3;
	
	bar = white_noise[k];

}

And compiling with

avr-g++.exe -mmcu=atmega328p -S -c file.cpp

But :

  1. I get a warning : only initialized variables can be placed into program memory area (Of course, the table is initialized!)
  2. There is no lpm instruction wich should be used, if I look at the Atmel 8bit Assembly documentation.
//The function (g++ generated assembly, obviously not optimised...)
	ldi r24,lo8(3)
	ldi r25,hi8(3)
	std Y+2,r25
	std Y+1,r24
	ldd r24,Y+1
	ldd r25,Y+2
	movw r30,r24
	subi r30,lo8(-(white_noise))
	sbci r31,hi8(-(white_noise))
	ld r24,Z
	std Y+3,r24
  1. Not completely sure the table will really be placed in progmem, although it’s placed in “progmem.data” ;
	.global	white_noise
	.section	.progmem.data,"a",@progbits
	.type	white_noise, @object
	.size	white_noise, 4096
white_noise:
	.byte	70
...

In my own assembly code, I use something like :

    "lds r26, noise_id\n"
    "lds r27, (noise_id)+1\n"
    
    "ldi r30, lo8(white_noise)\n"
    "ldi r31, hi8(white_noise)\n"

    "add r30, r26\n"
    "adc r31, r27\n"
    
    "lpm r28, Z\n"

Is that the right way to do it?

Thx

 bar = white_noise[k];

You must use program memory access macros to access program memory, this won't work.

According to AVR-LIBC documentation, you’r right ( avr-libc: Data in Program Space )
(And also http://arduino.cc/en/Reference/PROGMEM )

I’m a bit surprised that the compiler isn’t able to manage all that alone :frowning:

Well, when read_far should be used? And what about the warning?

1) I get a warning : only initialized variables can be placed into program memory area (Of course, the table is initialized!)

I think the warning is bogus. If I enable warnings for code that uses program memory I get it too.

Look in pgmspace.h for macros for accessing program memory. Although I don't think, as a general rule, that using assembler gains you anything. The code generated from C is usually just as good.

Well, when read_far should be used?

It looks like, if you have more than 64 Kb of flash.

Zenol: I'm a bit surprised that the compiler isn't able to manage all that alone :(

I believe it is an attribute, not a type.

[quote author=Nick Gammon link=topic=106147.msg796560#msg796560 date=1337207545]

Zenol: I'm a bit surprised that the compiler isn't able to manage all that alone :(

I believe it is an attribute, not a type. [/quote]

I mean, understanding itself that the c code ask data located in progmem and it should use the good lpm instructions.

About assembly, I need it because it's a quite critical interrupt, called each 255 clock cycles, so i have to do all my work and let some cycles for the main code really quickly.

In fact, I first wrote all that in C, and tried to add 4 channel, but it was too much, the function took more than 255 cycles and so, the output frequency was bad (something like divided by 2, because one interrupt was "lost"...).

I rewrote it in assembly because I had no choice, and in fact, an empty interrupt generated by avr-G++ with standard prologue/epilogue take more than 30 cycles!! I also won time using tricks (like only working on the high part of an integer if I know that the result of the binary and will let the lower part unchanged), so that i was able to add one square wave channel, and a noise channel. (And there is still time left, because for testing purpose I do not optimized the use of register to minimize push&pop).

So, actually, there is a great difference in space/time between C code and assembly. (But who want to write a library in assembly if not required? )

Edit : The read_word_near is in fact this assembly macro : http://www.nongnu.org/avr-libc/user-manual/pgmspace_8h.html - LPM_dword_classic(addr) So they use the Z register and lpm...

I mean, understanding itself that the c code ask data located in progmem and it should use the good lpm instructions.

Consider the following:

void foo (char* string)
{
  for (; *string; string++) {
    doSomething (*string);
  }
}

char whiteNoiseInROM[4096] PROGMEM = {70..0};
char whitePunksOnDope [40] = {60..0};

...
...
void loop ()
{
  foo (whiteNoiseInROM);
  foo (whitePunksOnDope);
}

How does the compiler know how to create the correct code for "foo()"?

(It can't. Both "whiteNoiseInROM" and "whitePunksOnDope" are both valid pointers, in that they contain the address of a section of memory. But they're in completely different memory spaces.

That's what I meant by it being an attribute and not a type. :)

I mean, understanding itself that the c code ask data located in progmem and it should use the good lpm instructions.

Once you introduce pointers you start hiding the attributes.

In fact, I first wrote all that in C, and tried to add 4 channel, but it was too much, the function took more than 255 cycles and so, the output frequency was bad (something like divided by 2, because one interrupt was "lost"...).

I had similar problems when trying to output VGA video information, which is highly timing-critical. But by analyzing the generated code I was able to see how to tweak the C code to minimize the amount of code the compiler generates.