Доступ к большим массивам данных в программной памяти

edisson77
Offline
Зарегистрирован: 04.11.2015

Приветствую всех! Прошу помочь разобраться с проблемой считывания массива данных (размер более 64К) из PROGMEM. Пытаюсь сделать электронную ударную установку (барабаны) на Ардуино Mega 2560 для маленького внука (ну, и самому молодость вспомнить :)). Изначально проект делал для 8-битного звука (а еще раньше для MIDI, но малышу сложно подключать), объем сэмплов не превышал 64 кбайта и все работало замечательно c функцией pgm_read_byte, но качество звука не устроило. Как вариант, решил увеличить разрядность звука, и теперь в программной памяти располагается 8 звуковых сэмплов 16 бит 22 кHz (8 массивов данных int около 80 кбайт). Функция pgm_read_word_far упорно не хочет считывать нужные данные, вместо сэмплов считывает куски кода, соответственно, вместо звука треск и шум. Если использовать просто pgm_read_word, то все работает корректно пока не возникнет необходимости "дальний" сэмпл считать - вместо нужного звука считывается кусочек "ближнего", что , в принципе, и правильно. Перепробовал все сочетания присвоения указателей для pgm_read_word_far, но так ничего и не получилось. Пожалуста, помогите решить проблему. На кусочки от миди не обращайте внимание,по итогу будет поддерживаться и миди и wavetable

 

#include <Timer1.h>
#include <MIDI.h>
#include <avr/pgmspace.h>

// размещаем сэмплы в программной памяти
const char bassdrum[ /*36-6970 */ ] PROGMEM = {
 0x01, 0x00, 0xFF, 0xFF, 0x01, 0x00, 0xFF, 0xFF, 0x01, 0x00, 0xFF, 0xFF, 0x01, 0x00, 0xFF, 0xFF, 0x01, 0x00, 0xFF, 0xFF, 0x01, 0x00, 0xFF, 0xFF, 
//....... и т.д.
};


const char pedalhat [ /* 44-4264 */ ] PROGMEM = {
 0xC2, 0x01, 0xDB, 0x01, 0x61, 0x01, 0x83, 0x00, 0xE3, 0x00, 0x8C, 0x00, 0x87, 0x01, 0x08, 0x01, 0x42, 0x00, 0x94, 0x00, 0xEC, 0x01, 0x3F, 0x01, 
//....... и т.д.
};


const char closehat [ /* 42-5386 */ ] PROGMEM = {

 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
 0x42, 0xFF, 0xD3, 0xFE, 0xB9, 0xFF, 0x75, 0xFF, 0xC5, 0xFF                                                                                      
//....... и т.д.
};


const char tomlow [ /* 43-18250 */ ] PROGMEM = {
 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
 0xC8, 0xFF, 0xC0, 0xFF, 0xC1, 0xFF, 0xC1, 0xFF, 0xBC, 0xFF                                                                                      
//....... и т.д.
};

const char tommid[ /* 47-12934 */ ] PROGMEM = {
 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xFF, 0xFF, 0x01, 0x00, 0xFF, 0xFF, 0x01, 0x00, 0xFE, 0xFF, 0x02, 0x00, 0xFD, 0xFF, 0x04, 0x00, 
 0x80, 0x01, 0x57, 0x01, 0x5B, 0x01, 0x2E, 0x01, 0x0D, 0x01, 0x00, 0x01, 0xD6, 0x00, 0xCF, 0x00, 0x9E, 0x00, 0x9C, 0x00, 0x8F, 0x00              
//....... и т.д.
};
const char tomhi[ /* 50_13274 */ ] PROGMEM = {
 0xFD, 0xFF, 0x04, 0x00, 0xFA, 0xFF, 0x08, 0x00, 0xF6, 0xFF, 0x0B, 0x00, 0xF4, 0xFF, 0x0C, 0x00, 0xF6, 0xFF, 0x08, 0x00, 0xFB, 0xFF, 0x02, 0x00, 
//....... и т.д.
};                                                                                                                                    

const unsigned char snare [/* 38-16346 */] PROGMEM  = {
 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x01, 0x00, 0xFF, 0xFF, 0x02, 0x00, 0xFE, 0xFF, 0x03, 0x00, 0xFC, 0xFF, 0x04, 0x00, 
//....... и т.д.                                                                                                                                      
 
};



const char crash [/* 49-60380 */] PROGMEM = {
 0x03, 0x00, 0xFC, 0xFF, 0x04, 0x00, 0xFB, 0xFF, 0x05, 0x00, 0xFA, 0xFF, 0x06, 0x00, 0xF9, 0xFF, 0x07, 0x00, 0xF9, 0xFF, 0x06, 0x00, 0xFA, 0xFF, 
//....... и т.д.
}; 




int porog = 200;//порог для датчиков, ниже не учитываем
boolean beat[] = {0,0,0,0,0,0,0,0}; // удар или нет
int vol[] = {0,0,0,0,0,0,0,0}; //(МИДИ)громкость полученная с датчиков
int volset[] = {600,720,600,1000,1000,1000,520,600};//(МИДИ)минимальный уровень громкости, если на датчиках меньше
int anPin[] = {A0,A1,A2,A3,A4,A5,A6,A7};// входы датчиков
//const int dcnt = 8; //количество барабанов
int drums[]={47,43,50,44,36,42,38,57};//(МИДИ)номер ноты барабана или тарелки
//36 bass 3485 байт в сэмпле
//38 snare 8173
//42 close hat 2693
//43 low tom 9125
//44 pedal hat 2132
//47 mid tom 6467
//49 crash 30190
//50 hi tom 6637



unsigned long *ptr [8];// массив указателей на сэмплы
volatile uint16_t ln [] = {6465, 9123, 6635, 2130, 3483,  2691, 8171, 15000}; //длина каждого сэмпла в word (2байта)

//текущее положение в сэмпле. Изначально указаны, чтобы при первом включении не воспроизводились
volatile uint16_t cn [] = {6465, 9123, 6635, 2130, 3483,  2691, 8171, 15000};

unsigned int sd[] = {0,0,0,0,0,0,0,0}; //текущее считанное значение word из сэмпла

volatile uint16_t snd; //сумма данных из 8 сэмплов для вывода в порт

MIDI_CREATE_DEFAULT_INSTANCE();
void setup()
{
  //  MIDI.begin();
 disableMillis();// на всякий случай, для увеличения быстродействия отключаем (библиотека Timer1);

//настройка портов и установка звукового 0(0x8000) для 16 разрядного ЦАП (R2R)
  DDRC = B11111111;
  PORTC = B00000000; //младший
  DDRA = B11111111; 
  PORTA = B10000000;//старший


//заполняем массив указателей:
ptr[0]=(unsigned long*)&tommid[0];
ptr[1]=(unsigned long*)&tomlow[0];
ptr[2]=(unsigned long*)&tomhi[0];
ptr[3]=(unsigned long*)&pedalhat[0];
ptr[4]=(unsigned long*)&bassdrum[0];
ptr[5]=(unsigned long*)&closehat[0];
ptr[6]=(unsigned long*)&snare[0];
ptr[7]=(unsigned long*)&crash[0];



  // Prepare Timer1 to send notifications every 1000000us (1s)
  // On 16 MHz Arduino boards, this function has a resolution of 4us for intervals <= 260000,
  // and a resolution of 16us for other intervals
  // On 8 MHz Arduino boards, this function has a resolution of 8us for intervals <= 520000,
  // and a resolution of 32us for other intervals
 // cn = 0;
  startTimer1(45L);// включаем таймер (22 кГц)
  
}

void loop()
{
  
  for (int i=0;i < 8; i++) {
  int sensorValue = analogRead(anPin[i]); //читаем информацию с датчиков
  
  if (sensorValue > porog) {
    if (!beat[i]){ // новый удар или хвост от датчика?
  
      if (cn[i] > 1000){ // если время прошло, то не хвост, а новый удар
      beat[i] = true;
    // vol[i] = sensorValue; для МИДИ
      } 
    //vol = max(vol,sensorValue);  для МИДИ  
    }
  
  // vol[i] = max(vol[i],sensorValue);для МИДИ
  }
  else
  {if ((beat[i])&& (sensorValue == 0) ) // был удар и первая волна закончилась
      { 
      //if (volset[i]>vol[i]){vol[i]=volset[i];} 
      // Для миди режима
      //  MIDI.sendNoteOn(drums[i],vol[i]/8,10);
    //для wav режима
    cn[i] = 0; //начинаем воспроизводить сэмпл сначала
      beat[i] = false; //готовимся к следующему удару
      }
    

  }
 }

 
}

// обработчик прерывания
ISR(timer1Event)
{
  
 resetTimer1(); сбрасываем таймер
 
  snd =0x8000; // звуковой 0

// пробегаемся по всем сэмплам
// если текущее положение в сэмпле меньшн его длины, то воспроизводим, иначе нет
  for (int i=0; i<8; i++){
  if (cn[i]<ln[i]) {
  
    //Здесь начинаются проблемы если сэмплы превышают 64к "окно"
  // - конец одного сэмпла может переплестись с началом другого.
    // Использование указателей на long и макроса pgm_read_word_far не помогло, так как не знаю,
    // как получить "длинный" адрес на сэмпл :(((
    sd[i]=pgm_read_word_far((unsigned long*)ptr[i]+cn[i]);//считываем данные сэмпла из программной памяти
  
    sd[i]=0x8000 - sd[i]; //переводим считанный данные из сэмпла знаковые в беззнаковые (0..FFFF)
    sd[i]= sd[i] >> 2; // чтобы не превысить 16 разрядов в ЦАП,делим на четыре из расчета,
                        // что одновременно максимум 4 сэмпла будет звучать
    snd=snd+sd[i]-0x2000; //суммируем и корректируем звуковой 0, на 1/4 часть, чтобы не было щелчков и помех
    ++cn[i];
 
    }
  }
  //выводим в ЦАП
  PORTC = snd;
  PORTA = snd>>8;


}
toc
Offline
Зарегистрирован: 09.02.2013

1. вы это читали наверное http://www.nongnu.org/avr-libc/user-manual/group__avr__pgmspace.html
2. раз не даёте полный текст вашей программы, подготовьте и дайте короткие примеры, которые хорошо иллюстиируют рабочий вариант, который вас уже не устраивает, и целевой вариант, который пока не работает как надо

офтоп
3. что за цап у вас?
4. обработчик прерывания, по-возможности, должен быть коротким и быстрым

edisson77
Offline
Зарегистрирован: 04.11.2015

Спасибо за отклик!

1.Да, это читал и много другого. Попадалось дополнение к pgmspace, в котором подправлена работа с 32-битными адресами.Сразу страничку не сохранил и теперь не могу найти :((.

2.Я приложил полный работоспособный листинг программы, только сократил массивы сэмплов.

Вот из 8-битного рабочего варианта (в обработчике прерывания):

 

  for (int i=0; i<8; i++){
  if (cn[i]<ln[i]) {
        sd[i]=pgm_read_byte(ptr[i]+cn[i]);
        snd=snd+sd[i]; 
     ++cn[i];
 
  }
  else
  {snd = snd+128;}
  }
   PORTC = snd;
  PORTA = snd>>8;

И указатели другого типа: ptr[0]=(char*)&crash[0];ptr[1]=(char*)&tomlow[0];и т.д.

3. ЦАП в комментариях к листингу есть - это линейка из резисторов R2R

4. Обработчик настолько короткий и быстрый, что можно и 44кГц 16 бит сразу 3 сэмпла одновременно воспроизводить и 8 ударов по барабанам обрабатывать(проверено с сэмплами по 20 кбайт).Возможно, и 8 можно, но только в памяти не помещаются.

edisson77
Offline
Зарегистрирован: 04.11.2015

Уважаемый toc!

Большое Вам спасибо! Благодаря Вам более внимательно рассмотрел morepgmspace.h, и это оказалось то что нужно.

В код добавил и поменял:

#include "morepgmspace.h"

unsigned long ptr [8];

ptr[0]=GET_FAR_ADDRESS(tommid[0]);  ptr[1]=GET_FAR_ADDRESS(tomlow[0]);  ptr[2]=GET_FAR_ADDRESS(tomhi[0]);  ptr[3]=GET_FAR_ADDRESS(pedalhat[0]);  ptr[4]=GET_FAR_ADDRESS(bassdrum[0]);  ptr[5]=GET_FAR_ADDRESS(closehat[0]);  ptr[6]=GET_FAR_ADDRESS(snare[0]);  ptr[7]=GET_FAR_ADDRESS(crash[0]); 

sd[i]=pgm_read_word_far(ptr[i]+cn[i]*2);

И все заработало!

Еще раз спасибо от меня и внука!