pgm_read_和PROGMEM - 意外行为

时间:2017-10-30 01:30:53

标签: c arduino avr atmega

我制作了一个播放保存在数组中的歌曲(使用蜂鸣器)的程序:

(...)
//function which plays a single note
void playNote(int8 wavelength, int duration) {
    if (wavelength != 1) {
        OCR0A = wavelength; /* set pitch */
        SPEAKER_DDR |= (1 << SPEAKER); /* enable output on speaker */
    }
    while (duration) { /* Variable delay */
        _delay_ms(1);
        duration--;
    }
    SPEAKER_DDR &= ~(1 << SPEAKER); /* turn speaker off */    

//function which plays song from array
void playSong( int8 *song,int tempo){
    int length_16_note = tempo;
    for( int8 i = 0;song[i]) ; i += 2){
        playNote(song[i],song[i+1]*length_16_note);
        _delay_ms(15);
    }
}
int main(void){
//array of macros and lenghts of notes
int8 song1[] = {
    C,4, E1,4, F1,3, E1,3, F1,2, 
    F1,2, F1, 2, B1,2, A1,2, G1,1, F1,2, G1,5,
    G1,4, B1,4, C1,3, F1,3, E1,2,
    B1,2, B1,2, G1,2, B1,2, 
    B1,3, C1,13, 0};
//initialize a timer
initTimer();
//play song
playSong(song1, 150)
}

我省略了它的一些部分,但这段代码工作得很好。现在我想将我的歌曲保存到程序存储器中,所以我改变了:

#include <avr/pgmspace.h>
(...)      
int8 song1[] PROGMEM = {...}

void playSong( int8 *song, int tempo){
    int length_16_note = tempo;
    for( int8 i = 0; pgm_read_byte(&(song[i])) ; i += 2){
        playNote(pgm_read_byte(&(song[i])), pgm_read_byte(&(song[i+1]))*length_16_note);
        _delay_ms(15);
    }
}

当我在Arduino上运行该代码时,我会随机持续 beeps (比预期的长得多)。看起来pgm_read_byte(&(song[i]))会返回随机值。

我尝试将函数playSong中的代码提取到main,以便不将数组作为参数传递给函数,并且没有任何更改。那么这段代码有什么问题呢?

2 个答案:

答案 0 :(得分:0)

我不确定这是原因,但我的第一个猜测是你的持续时间比预期的要长,因为你的for循环看起来像这样:

for( int8 i = 0; pgm_read_byte(&(song[i])) ; i += 2)

这意味着每次执行for循环时,程序都会检查程序空间中的数据,因为它是循环条件。

avr-libc用户手册中有一个警告,说明在循环和函数中使用程序存储器读取,见下文。

  

用于从程序空间检索数据的宏和函数必须生成一些额外的代码,以便从程序空间实际加载数据。这会在代码空间(额外的操作码)和执行时间方面产生一些额外的开销。 (...)但是你应该意识到这一点,这样你就可以最大限度地减少从程序空间获取相同数据的单个函数中的调用次数。

此外,您还可以在for循环内对程序闪存进行两次额外调用。所有这些意味着,要更改音符,程序需要等待程序存储器的3字节读取和额外的15 ms延迟。因此,票据比预期的要长得多。

我建议程序首先应将整首歌读入RAM中的某种缓冲区,然后直接从RAM中播放。

答案 1 :(得分:0)

问题解决了。这里的问题是数组song1main()函数中声明的事实。我必须在函数定义之前将数组声明为全局数据:

#include (...)
#define (...)

const int8 song1[] PROGMEM = {
C,4, E1,4, F1,3, E1,3, F1,2,
F1,2, F1, 2, B1,2, A1,2, G1,1, F1,2, G1,5,
G1,4, B1,4, C1,3, F1,3, E1,2,
B1,2, B1,2, G1,2, B1,2,
B1,3, C1,13, 0};

void playNote(int8 wavelength, int16 duration){...}
void playSong( int8 *song, int16 tempo){...}
int main(void){...}

在我上面的问题中很难看到问题,因为我以误导的方式编写代码(在第二个引用中我在函数定义之前编写了数组 - 但在原始代码中它与之前的位置相同)。对不起,我很抱歉。