Как генерировать музыкальные ноты с помощью uno без использования функции tone()? Может ли кто-нибудь поделиться кодом встроенного языка c для него?
Я также хочу знать, как мы можем обойтись без таких функций, как цифровая запись
2 ответа
Я сделал здесь 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
Да, это достаточно просто сделать. Для этого я написал библиотеку, как описано здесь.
В принципе вы можете создать такой класс:
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
- Как использовать SPI на Arduino?
- Как решить проблему «avrdude: stk500_recv(): programmer is not responding»?
- Как создать несколько запущенных потоков?
- Как подключиться к Arduino с помощью WiFi?
- avrdude ser_open() can't set com-state
- Как узнать частоту дискретизации?
- Что такое Serial.begin(9600)?
- Я закирпичил свой Arduino Uno? Проблемы с загрузкой скетчей на плату
прочитайте это 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