Как генерировать музыкальные ноты с помощью uno без использования функции tone()? Может ли кто-нибудь поделиться кодом встроенного языка c для него?

Я также хочу знать, как мы можем обойтись без таких функций, как цифровая запись

, 👍0

Обсуждение

прочитайте это https://www.arduino.cc/en/Reference/PortManipulation, @jsotola

но тогда это не Ардуино, если вы не пользуетесь функциями Ардуино и вопрос не по теме, @Juraj

Я не вижу, что в этом не по теме, если у него есть Arduino и он хочет заставить ее что-то делать. Конечно, ему не обязательно использовать библиотеку Tone или обязательно использовать digitalWrite? В конечном счете каждая библиотека в конечном итоге будет использовать аппаратные регистры, и вы же не скажете, что библиотеки не по теме, не так ли?, @Nick Gammon

@NickGammon, OP хочет программу AVR C с main () (встроенный код языка C), и это похоже на школьное задание. Если я пишу низкоуровневую часть библиотеки Arduino, я использую базовую технологию, которая не является Arduino, и если это AVR, и у меня есть вопрос по этому поводу, я иду на форум avrfreaks., @Juraj

Возможно, вы правы насчет задания, но в том, чтобы помогать людям достичь просветления, нет ничего плохого по своей сути. :), @Nick Gammon


2 ответа


0

Я сделал здесь 13-нотный инструмент, похожий на фортепиано (ну, скорее, на орган)

https://forum.arduino.cc/index.php?topic=179761.0

Клип на YouTube, демонстрирующий это в действии

https://www.youtube.com/watch?v=4c8idXN4Pg0

Всего 8 нот в клипе, это все кнопки, которые у меня были на тот момент.

Я использовал процессор '1284P вместо '328P, чтобы он мог иметь 13 входов & 13 выходов, без перехода на 2560.

Нет ничего плохого в использовании Direct Port Manipulation в среде Arduino IDE. Arduino — это просто C++ с некоторыми упрощенными для пользователя функциями, макросами и т. д.

Вот код.

/* Перекресток, 26 июля 2013 г.
тест для создания нескольких одновременных заметок с использованием micros()
нажмите клавишу, получите записку. Используйте внешний аналоговый микшер (суммирующий операционный усилитель) для сведения звука к одному выходу на динамик.
требуется устройство с большим количеством контактов - этот код предназначен для '1284P - с определенными сопоставлениями контактов, не написанными для переносимости UC
сыграйте пока 13 нот, от C4 до C5
информация о заметках с http://www.sengpielaudio.com/calculator-notenames.htm
Клавиши ввода: pin2=C4, 3=C#4, 30=D4, 8=D#4, 9=E5, 31=F5, 4=F5#, 5=G5, 6=G5#, 7=A5, 10 =A5#, 11=B5, 12=C5
Выходные данные: контакт 16=C4, 17=C#4, 18=D4, 19=D#4, 20=E5, 21=F5, 22=F5#, 23=G5, 24=G5#, 25=A5, 26=А5#, 27=В5, 28=С5
Порядок забавен из-за отображения Bobuino, и когда выходные биты порта A следуют за входными битами порта D, выходные биты порта C следуют за входными битами порта B.
0,1 зарезервировано для серийного номера
13,14,15,29, неиспользуемые, может сделать выбор 1 из 9 октав? Фортепиано имеет 7 полных октав от C до B, 2 частичных
*/
// массив 1/2 периода нот в микросекундах
// таким образом, нота C8 будет 119 мкс High, например, 119 uS low
unsigned long noteArray[] = {
  119, // C8, самая высокая нота — нота 0
  127,134,142,150,159,169,179,190,201,213,225,239, // B7, A#7,A7, G#7, G7, F#7, F7, E7, D#7, D7, C#7, C7 - ноты 1-12
  253,268,284,301,318,338,356,379,402,426,451,478, // B6 вниз до C6 - примечания 13-24
  506,536,568,602,638,676,716,758,804,851,902,965, // B5 вниз до C5 - примечания 25-36
  1012,1073,1136,1204,1276,1351,1432,1517,1607,1703,1804,1911, // B4 вниз до C4 - примечания 37-48
  2025,2145,2272,2408,2551,2703,2863,3034,3214,3405,3607,3822, // B3 вниз до C3 - примечания 49-60
  4050,4290,4545,4816,5102,5405,5727,6067,6428,6810,7215,7645,  // B2 вниз до C2 - примечания 61-72
  8099,8581,9091,9631,10204,10811,11454,12135,12856,13621,14431,15289,  // B1 вниз до C1 - примечания -73-84
  16198,17161,18182, // B0, A#0, A0 - 85-86-87
};  // действительно нужно перевернуть все это так, чтобы 0 была самой низкой нотой & 87 самый высокий
byte keyArray[]={
  2,3,30,8,9,31,4,5,6,7,10,11,12,13,}; //Порты D2,D3,D4,D5,D6,D7,B0,B1,B2,B3,B4,B5,B6, добавлен b7 - примечания 48, 47,46,45,44,43,42,41,40 ,39,38,37,36
byte outputArray[] = {
  14,15,16,17,18,19,22,23,24,25,26,27,28, 21,22,29}; // Порты A0,A1,A2,A3,A4,A5,C0,C1,C2,C3,C4,C5,C6, добавлены a6, a7, c7
// порты, выбранные для согласованности битов, используемых на входе и выходе, от входа D к выходу A и от входа B к выходу C
byte keyActive[]= {
  0,0,0,0,0,0,0,0,0,0,0,0,0,}; // 1 указывает, что клавиша нажата
//byte octaveSelect[] = {
// 13,21,22,29}; // b7,a6,a7,c7 - от 0 до 9 - будущее использование
// ATMEL ATMEGA1284P на Бобуино
//
// +---\/---+
// (D 4) PB0 1 | | 40 ПА0 (Д 21) АИ 7
// (D 5) PB1 2 | | 39 ПА1 (Д 20) АИ 6
// INT2 (D 6) PB2 3 | | 38 ПА2 (Д 19) АИ 5
// ШИМ (D 7) PB3 4 | | 37 ПА3 (Д 18) АИ 4
// ШИМ/СС (D 10) PB4 5 | | 36 ПА4 (Д 17) АИ 3
// ДЫМ (Д 11) ПБ5 6 | | 35 ПА5 (Д 16) АИ
// ШИМ/MISO(D12) PB6 7 | | 34 ПА6 (Д 15) АИ
// ШИМ/СКК (D13) PB7 8 | | 33 ПА7 (Д 14) АИ
// РСТ 9 | | 32 АРЕФ
// ВКЦ 10 | | 31 земля
// ЗЕМЛЯ 11 | | 30 АВКК
// XTAL2 12 | | 29 ПК7 (Д 29)
// XTAL1 13 | | 28 ПК6 (Д 28)
// RX0(D0)PD0 14 | | 27 ПК5 (Д 27) ТДИ
// TX0(D1)PD1 15 | | 26 ПК4 (Д 26) ТДО
// INT0 RX1(D2)PD2 16 | | 25 ПК3 (Д 25) ТМС
// INT1 TX1(D3)PD3 17 | | 24 ПК2 (Д24) ТСК
// ШИМ(D30) PD4 18 | | 23 ПК1 (Д 23) ПДД
// ШИМ (D 8) PD5 19 | | 22 ПК0 (Д 22) СКЛ
// ШИМ (D 9) PD6 20 | | 21 ПД7 (Д 31) ШИМ
// +--------+
//
byte x;
byte portDkeys;
byte portBkeys;
byte portAnotes;
byte portCnotes;
unsigned long currentTime;
unsigned long changeTime[]= {
  0,0,0,0,0,0,0,0,0,0,0,0,0,}; // отслеживаем, когда переворачивать вывод
unsigned long duration;
/* consistency check
 INDEX, NOTE, port In, port Out
 0, C4,       D2, A2           read 04 write 04 FB 1111 1011 Middle C
 1, C#4,      D3, A3           read 08 write 08 F7 1111 0111
 2, D4,       D4, A4           read 10 write 10 EF 1110 1111
 3, D#4,      D5, A5           read 20 write 20 DF 1101 1111
 4, E4,       D6, A6           read 40 write 40 BF 1011 1111
 5, F4,       D7, A7           read 80 write 80 7F 0111 1111
 6, F#4,      B0, C0           read 01 write 01 FE 1111 1110
 7, G4,       B1, C1           read 02 write 02 FD 1111 1101 
 8, G#4,      B2, C2           read 04 write 04 FB 1111 1011
 9, A4,       B3, C3           read 08 write 08 F7 1111 0111  Concert A
 10, A#4,     B4, C4           read 10 write 10 EF 1110 1111
 11, B4,      B5, C5           read 20 write 20 DF 1101 1111
 12, C5,      B6, C6           read 40 write 40 BF 1011 1111
 */
/*************/
void setup(){
  //Серийный.начало (115200); // для отладочного тестирования
  for (x=0; x<14; x=x+1){ // входные контакты с подтяжкой
    pinMode(keyArray[x], INPUT);
    digitalWrite (keyArray[x], HIGH);
  }
  for (x=0; x<17; x=x+1){ 
    pinMode(outputArray[x], OUTPUT); // выходные контакты
  }
  // for (x=0; x< 4; x=x+1){ // возможно, в будущем
  // pinMode(octaveSelect[x], INPUT); // входные пины с подтягиваниями
  // digitalWrite (octaveSelect[x], HIGH);
  // }
} // конец настройки

/***********/
void loop(){
  //Серийный.принт(");
  // если клавиша нажата, определяем время следующего перехода
  // если клавиша не нажата, перехода нет, используйте связь по переменному току с суммирующим операционным усилителем для отсутствия выхода
  // идея состоит в том, чтобы разрешить одновременное создание нескольких заметок. Может ли чтение & если все сделать достаточно быстро?
  portDkeys = PIND; // Чтение входных ключей - использование прямой манипуляции с портом для увеличения скорости
  portBkeys = PINB; 
  currentTime = micros();

  // Не сделал эту часть более крупного поиска цикла/массива, потому что это добавляет 12-15 мкс для каждого прохода через цикл

  /* Check if key for note C4 is pressed, key0  */

  if ((portDkeys & 0x04) == 0){ // нажата клавиша D2 -> А2
     if (keyActive[0]==0){
       keyActive[0] = 1;
       changeTime[0] = currentTime;
     }
    // смотрим, пора ли менять привет на ло или ло на привет
    if ( (currentTime - changeTime[0])>=noteArray[48]){ // время для переключения периода?
      changeTime[0] = changeTime[0] + noteArray[48];    // время установки следующего переключателя
      PINA=0x04;  // запись 1 в PINx переключает выходной бит — NickGammon снова спешит на помощь!
    }
  }
  else{keyActive[0] = 0;}

  /* Check if key for note C#4 is pressed, key1  */

  if ((portDkeys & 0x08) == 0){ // нажата клавиша D3 -> gt; А3
       if (keyActive[1]==0){
       keyActive[1] = 1;
       changeTime[1] = currentTime;
     }
    // смотрим, пора ли менять привет на ло или ло на привет
    if ( (currentTime - changeTime[1])>=noteArray[47]){
      changeTime[1] = changeTime[1] + noteArray[47];
      PINA = 0x08;
    }
  }
  else{keyActive[1] = 0;}

  /* Check if key for note D4 is pressed, key2  */

  if ((portDkeys & 0x10) == 0){ // нажата клавиша D4 -> А4
       if (keyActive[2]==0){
       keyActive[2] = 1;
       changeTime[2] = currentTime;
     }
    // смотрим, пора ли менять привет на ло или ло на привет
    if ( (currentTime - changeTime[2])>=noteArray[46]){
      changeTime[2] = changeTime[2] + noteArray[46];
      PINA = 0x10;
    }
  }
  else{keyActive[2] = 0;}

  /* Check if key for note D#4 is pressed, key3  */

  if ((portDkeys & 0x20) == 0){ // нажата клавиша D5 -> gt; А5
       if (keyActive[3]==0){
       keyActive[3] = 1;
       changeTime[3] = currentTime;
     }
    // смотрим, пора ли менять привет на ло или ло на привет
    if ( (currentTime - changeTime[3])>=noteArray[45]){
      changeTime[3] = changeTime[3] + noteArray[45];
      PINA = 0x20;
    }
  }
  else{keyActive[3] = 0;}

  /* Check if key for note E4 is pressed, key4  */

  if ((portDkeys & 0x40) == 0){ // нажата клавиша D6 -> gt; А6
       if (keyActive[4]==0){
       keyActive[4] = 1;
       changeTime[4] = currentTime;
     }
    // смотрим, пора ли менять привет на ло или ло на привет
    if ( (currentTime - changeTime[4])>=noteArray[44]){
      changeTime[4] = changeTime[4] + noteArray[44];
      PINA = 0x40;
    }
  }
  else{keyActive[4] = 0;}

  /* Check if key for note F4 is pressed, key5  */

  if ((portDkeys & 0x80) == 0){ // нажата клавиша D7 -> gt; А7
       if (keyActive[5]==0){
       keyActive[5] = 1;
       changeTime[5] = currentTime;
     }
    // смотрим, пора ли менять привет на ло или ло на привет
    if ( (currentTime - changeTime[5])>=noteArray[43]){
      changeTime[5] = changeTime[5] + noteArray[43];
      PINA = 0x80;
    }
  }
  else{keyActive[5] = 0;}

  /*  Now Port B & Port C */
  /* Check if key for note F#4 is pressed, key6  */

  if ((portBkeys & 0x01) == 0){ // нажата клавиша B0 -> gt; С0
       if (keyActive[6]==0){
       keyActive[6] = 1;
       changeTime[6] = currentTime;
     }
    // смотрим, пора ли менять привет на ло или ло на привет
    if ( (currentTime - changeTime[6])>=noteArray[42]){
      changeTime[6] = changeTime[6] + noteArray[42];
      PINC = 0x01;
    }
  }
  else{keyActive[6] = 0;}

  /* Check if key for note G4 is pressed, key7  */

  if ((portBkeys & 0x02) == 0){ // нажата клавиша B1 -> gt; С1
       if (keyActive[7]==0){
       keyActive[7] = 1;
       changeTime[7] = currentTime;
     }
    // смотрим, пора ли менять привет на ло или ло на привет
    if ( (currentTime - changeTime[7])>=noteArray[41]){
      changeTime[7] = changeTime[7] + noteArray[41];
      PINC = 0x02;
    }
  }
  else{keyActive[7] = 0;}

  /* Check if key for note G#4 is pressed, key8  */

  if ((portBkeys & 0x04) == 0){ // нажата клавиша B2 -> gt; С2
       if (keyActive[8]==0){
       keyActive[8] = 1;
       changeTime[8] = currentTime;
     }
    // смотрим, пора ли менять привет на ло или ло на привет
    if ( (currentTime - changeTime[8])>=noteArray[40]){
      changeTime[8] = changeTime[8] + noteArray[40];
      PINC = 0x04;
    }
  }
  else{keyActive[8] = 0;}

  /* Check if key for note A4 is pressed, key9  */

  if ((portBkeys & 0x08) == 0){ // нажата клавиша B3 -> gt; С3
       if (keyActive[9]==0){
       keyActive[9] = 1;
       changeTime[9] = currentTime;
     }
    // смотрим, пора ли менять привет на ло или ло на привет
    if ( (currentTime - changeTime[9])>=noteArray[39]){
      changeTime[9] = changeTime[9] + noteArray[39];
      PINC = 0x08;
    }
  }
  else{keyActive[9] = 0;}

  /* Check if key for note A#4 is pressed, key10  */

  if ((portBkeys & 0x10) == 0){ // нажата клавиша B4 -> gt; С4
       if (keyActive[10]==0){
       keyActive[10] = 1;
       changeTime[10] = currentTime;
     }
    // смотрим, пора ли менять привет на ло или ло на привет
    if ( (currentTime - changeTime[10])>=noteArray[38]){
      changeTime[10] = changeTime[10] + noteArray[38];
      PINC = 0x10;
    }
  }
  else{keyActive[10] = 0;}

  /* Check if key for note B4 is pressed, key11  */

  if ((portBkeys & 0x20) == 0){ // нажата клавиша B5 -> gt; С5
       if (keyActive[11]==0){
       keyActive[11] = 1;
       changeTime[11] = currentTime;
     }
    // смотрим, пора ли менять привет на ло или ло на привет
    if ( (currentTime - changeTime[11])>=noteArray[37]){
      changeTime[11] = changeTime[11] + noteArray[37];
      PINC = 0x20;
    }
  }
  else{keyActive[11] = 0;}

  /* Check if key for note C5 is pressed, key12  */

  if ((portBkeys & 0x40) == 0){ // нажата клавиша B6 -> gt; С6
       if (keyActive[12]==0){
       keyActive[12] = 1;
       changeTime[12] = currentTime;
     }
    // смотрим, пора ли менять привет на ло или ло на привет
    if ( (currentTime - changeTime[12])>=noteArray[36]){
      changeTime[12] = changeTime[12] + noteArray[36];
      PINC = 0x40;
    }
  }
  else{keyActive[12] = 0;}

} // конец цикла
,

Классное видео! Вы можете опубликовать свой довольно короткий код в своем ответе, чтобы он не полностью зависел от ссылок. :) В конце концов, это совершенно альтернативный способ сделать это, чем тот, который я показал (который не позволяет делать несколько заметок одновременно)., @Nick Gammon

Просто выберите код, нажмите Ctrl+K (K для кода). Это делает отступ в 4 пробела и форматирует его для вас. Я сделал это для тебя. :), @Nick Gammon

Прохладный! Я не понимал, что это можно сделать. Спасибо, Ник!, @CrossRoads


1

Да, это достаточно просто сделать. Для этого я написал библиотеку, как описано здесь.

В принципе вы можете создать такой класс:

class TonePlayer
  {
  // адреса выходных портов - NULL, если не применимо
  volatile byte * const timerRegA_;
  volatile byte * const timerRegB_;
  volatile byte * const timerOCRH_;
  volatile byte * const timerOCRL_;
  volatile byte * const timerTCNTH_;
  volatile byte * const timerTCNTL_;

  public:
    // конструктор
    TonePlayer (
          // порты
          volatile byte & timerRegA, 
          volatile byte & timerRegB, 
          volatile byte & timerOCRH,
          volatile byte & timerOCRL, 
          volatile byte & timerTCNTH, 
          volatile byte & timerTCNTL)
       : 
         timerRegA_  (&timerRegA), 
         timerRegB_  (&timerRegB),
         timerOCRH_  (&timerOCRH), 
         timerOCRL_  (&timerOCRL), 
         timerTCNTH_ (&timerTCNTH), 
         timerTCNTL_ (&timerTCNTH)
  { }

    void tone (const unsigned int Hz);
    void noTone ();

  };  // конец TonePlayer

Затем реализуйте две функции следующим образом:

void TonePlayer::tone (const unsigned int Hz)
{
  // требуется два переключателя для одного "цикла"
  unsigned long ocr = F_CPU / Hz / 2;
  byte prescaler = _BV (CS10);  // начинаем с предделителя 1 (биты одинаковы для всех таймеров)

  // слишком большой? предварительно масштабировать это
  if (ocr > 0xFFFF)
    {
    prescaler |= _BV (CS11);    // теперь прескалер 64
    ocr /= 64;
    }

  // остановить таймер
  *timerRegA_ = 0;
  *timerRegB_ = 0;

  // сброс счетчика
  *timerTCNTH_ = 0;
  *timerTCNTL_ = 0;

  // до чего считать
  *timerOCRH_ = highByte (ocr);
  *timerOCRL_ = lowByte (ocr);

  *timerRegA_ = _BV (COM1A0);             // переключаем выходной пин
  *timerRegB_ = _BV (WGM12) | prescaler;  // СТС
  }  // конец TonePlayer::tone

void TonePlayer::noTone ()
  {
  // остановить таймер
  *timerRegA_ = 0;
  *timerRegB_ = 0;  
  } // конец TonePlayer::noTone

А теперь назовите это:

TonePlayer tone1 (TCCR1A, TCCR1B, OCR1AH, OCR1AL, TCNT1H, TCNT1L);  // контакт D9 (Уно), D11 (Мега)

void setup() 
  {
  pinMode (9, OUTPUT);  // выходной контакт фиксирован (OC1A)

  tone1.tone (220);  // 220 Гц
  delay (500);
  tone1.noTone ();

  tone1.tone (440);
  delay (500);
  tone1.noTone ();

  tone1.tone (880);
  delay (500);
  tone1.noTone ();
  }

void loop() { }

В этом примере вы подключаете динамик к D9 (цифровой контакт 9) на Uno, а другой контакт подключается к земле. Поскольку при этом проигрываются тоны с использованием аппаратных таймеров, вы можете выполнять другие действия в цикле, не влияя на тоны.

,

из таблицы данных Atmega328 TCNT1 = 0x1FF; "Обратите внимание, что при использовании "C" компилятор обрабатывает 16-битный доступ", @Juraj

Ах да, возможно, я был слишком осторожен., @Nick Gammon