Realtime MIDI Player
- Войдите на сайт для отправки комментариев
Режим самоизоляции позволил уделить время многим проектам, которые были в планах, но до которых хронически не доходили руки.
Хронологически первым оказался проект MIDI-box - устройства, позволяющего либо формировать поток MIDI команд, либо добавлять свои MIDI команды к существующему потоку. http://arduino.ru/forum/proekty/midi-boxmidi-sintezator
Вероятно, этот же проект окажется и хронологически последним. По крайней мере, в процессе его реализации возникали и возникают новые идеи и новые проекты, способствующие его продвижению либо достаточно обособленные части, допускающие самостоятельную публикацию.
Первая из таких публикаций (кроме основной) - разработанный для этого проекта класс меню:
http://arduino.ru/forum/proekty/menyu-dlya-dvukhstrochnogo-displeya
Следующим является настоящий проект.
Но в планах присутствует еще парочка.
Вопрос - как долго еще продлится самоизоляция...
В процессе работы над проектом MIDI-box, подразумевающем как принятие и анализ входного потока команд, так и формирование выходного, вдруг внезапно обнаружился недостаток устройств, которые могли бы создавать нужный входной поток либо принимать выходной.
Собственно, в наличии присутствовали только MIDI клавиатура и компьютер. Компьютер, конечно, устройство довольно универсальное. Особенно, если на нем не только пользоваться общеупотребительным софтом, но и писать свой. Но все равно не слишком удобное.
Поэтому возникла идея параллельной разработки еще двух устройств:
- автономного проигрывателя MIDI-файлов, которое, в отличие от аналогичных устройств, на выходе давало бы не Audio, а MIDI,
- автономного проигрывателя реалтаймового потока MIDI команд, которое на входе получало бы MIDI, а на выходе давало Audio.
Т.е. вместо стандартного одного устройства MIDI-файл->Audio для наладки аппаратуры должно быть два разных: MIDI-файл->Realtime MIDI и Realtime MIDI->Audio.
Прикинув, что второе намного проще, именно с него решил и начать.
За основу был выбран ардуиновский шилд MP3 проигрывателя на VS1053B. Собственно, особенностью данного контроллера является то, что он проектировался для проигрывания не только MP3, но и множества других, в числе которых *.mid. А если загрузить в него небольшую программу, то он способен воспроизводить и раелтаймовое MIDI. Эта программа (в двоичных кодах) в И-нете была найдена.
Реалтаймовый проигрыватель, собранный на макетке
Правда следует сказать, что конструкторы шилда разрабатывали его исключительно для MP3, поэтому даже не потрудились вывести наружу последовательный порт, по которому передается стандартный MIDI сигнал. Пришлось, так же как и программу в кодах, передавать его через SPI, причем, с некоторыми изменениями протокола.
Я думаю, каждый компьютерщик на том или ином отрезке своей бурной жизни предпринимал попытки разгона компьютера. Были модели, которые удавалось разгонять в полтора раза и более, но обычно эффект не превосходил 20-30%. А чип VS1053B допускает разгон штатными средствами в 5 раз, причем в 4.5 раза его допускается разгонять в долговременном режиме. А для MIDI это важно. Ведь декодирование MP3 - это всего два стереоканала. В терминологии музыки - две ноты. Ну, еще постобработка - пусть вдвое тяжелее декодирования, т.е. порядка 6 "голых" нот без постобработки. А если разогнать в 4.5 раза, то уже получится 27 "голых" нот или 23 ноты с постобработкой смикшированного сигнала (цифры, конечно, очень условные, верно показаны лишь порядки величин и общая тенденция). При простом же воспроизведении MP3 файлов нужды в разгоне нет, и чип VS1052B таким образом экономит питание, т.к. он предназначен для работы в том числе и в портативных проигрывателях.
Собственно, вопрос этот возник по попытке воспроизвести на VS1053B оркестрового MIDI файла: значительную часть нот синтезатор просто проглатывал. Разгон исправил ситуацию. Поэтому было решено режим разгона сделать основным режимом устройства, для чего найденную в И-нете последовательность команд пришлось дополнить еще и дополнительными командами разгона - от себя лично.
устройство было собрано на заготовке ардуиновского шилда и куске макетки для пайки
Ардуиновские шилды имеют ряд неприятных особенностей. Одну из них я уже упоминал - наружу выведены далеко не все нужные сигналы. Другая - все приводится к 5-вольтовым уровням. Т.е. если я работаю с 3.3-вольтовым контроллером, то должен заботиться о сопряжении с преобразователями уровня, которые стоят между 3.3-вольтовым ведомым устройством и внешним разъемом шилда, выдавая наружу 5 Вольт.
Собственно, для конструкции решено было использовать Blue Pill, т.к. при цене где-то между Arduino MINI и arduino NANO он имеет полноценные как USB, так и Serial. И уж намного дешевле Arduino Micro или Leonardo, имеющими нужные порты. Не говоря о Меге, которая озадачивает еще и размерами.
Так вот, только часть ножек Blue Pill толерантна к 5 В. А в проекте нужно было одновременно использовать Serial, SPI и I2C. Первый - для приема MIDI сигнала, второй - для управления VS1053B, а третий - для отображения на дисплее последних пришедших команд (тоже очень полезно для отладки MIDI аппаратуры). Так что выбор подходящей комбинации оказался не слишком широким.
Вот тут и пришла засада. Сначала собрал и заставил работать все на макетке. Потом - начал разводить плату. Когда я четверть века назад занимался аналоговой техникой, вполне спокойно разводил и травил однослойные платы. При первых же опытах с цифровой (еще в то время) обнаружил, что одного слоя для цифровых устройств явно недостаточно. Ну а с появлением современных мелковыводных деталей рисовать от руки дорожки лаком для ногтей и вовсе оказалось нереальным (с учетом "изменившегося" за последние четверть века зрения). Поэтому сейчас для всех конструкций использую исключиетльно макетки для пайки (или заготовки ардуиновских шилдов, без которых трудно, т.к. "отцы основатели" позаботились о нестандартном расположении контактов).
Так вот, о засаде: в процессе разводки платы оказалось, что гораздо удобнее изменить комбинацию интерфейсов. Хорошо, решил опробовать это, не разбирая макет, просто переткнув провода. Полдня бился, но так и не сумел заставить работать устройство в новой конфигурации. Решил, что утро вечера мудренее и отложил разбирательство на завтра. Конечно, быстрее, проще и дешевле было бы отказаться от идеи переназначения - распаять три лишних провода не весть какая работа, но тут - дело принципа: надо же разобраться!
Вечером перед сном имею тенденцию почитать, что пишут в И-нете. Ну и заодно решил провентилировать возникший вопрос. На одном из англоязычных форумов обнаружил ссылку на errata по похожему случаю, цитирую последнюю:
Да уж...
Предыдущий пост отправлен 8 мая, код дописан 9 мая, а до того, чтобы завершить публикацию, руки все не доходят.
Итак, схема:
AUDIO OUT - это 3.5 мм гнездо, расположенное на шилде аппаратного синтезатора VS1053B.
Код для последнего - в следующих двух файлах:
VS1053B_MIDI.h
#ifndef VS1053B_MIDI_H #define VS1053B_MIDI_H #include "Arduino.h" class VS1053B_device { public: VS1053B_device() {}; ~VS1053B_device() {}; void setup(bool PrintON); void talkMIDI(byte cmd, byte data1, byte data2); //Sends a MIDI command/data. Doesn't check to see that cmd is greater than 127, or that data values are less than 127 void noteOn(byte channel, byte note, byte attack_velocity); //Send a MIDI note-on message. Like pressing a piano key. channel ranges from 0-15 void noteOff(byte channel, byte note, byte release_velocity); //Send a MIDI note-off message. Like releasing a piano key void sendMIDI(byte data); private: void WriteRegister(unsigned char addressbyte, unsigned char highbyte, unsigned char lowbyte); //Write to VS10xx register. SCI: Data transfers are always 16bit. void LoadUserCode(void); // Plugin to put VS10XX into realtime MIDI mode. Originally from http://www.vlsi.fi/fileadmin/software/VS10XX/vs1053b-rtmidistart.zip }; extern VS1053B_device VS1053B; #endif // VS1053B_MIDI_Hи VS1053B_MIDI.cpp
#include "VS1053B_MIDI.h" #include <SPI.h> VS1053B_device VS1053B; SPIClass SPI_2(2); //Create an instance of the SPI Class called SPI_2 that uses the 2nd SPI Port #define VS_START_SPEED 1000000 // предел по дэйташиту CLKI/7 (=1.76 при 12.288 МГц) #define VS_SPEED 4000000 // предел по дэйташиту CLKI/7 (=7.9 при 55.3 МГц) // SCK2 - PB13 // 13 // MISO2 - PB14 // 12 // MOSI2 - PB15 // 11 // SD-CS - PB8 // 9 SD-card #define VS_RESET PB9 // 8 Reset is active low #define VS_XDCS PB3 // 7 Data Chip Select / BSYNC Pin #define VS_XCS PB4 // 6 Control Chip Select Pin (for accessing SPI Control/Status registers) #define VS_DREQ PA15 // 2 Data Request Pin: Player asks for more data SPISettings VS1053B_START(VS_START_SPEED, MSBFIRST, SPI_MODE0); SPISettings VS1053B_HiFreq(VS_SPEED, MSBFIRST, SPI_MODE0); //Write to VS10xx register //SCI: Data transfers are always 16bit. When a new SCI operation comes in DREQ goes low. We then have to wait for DREQ to go high again. //XCS should be low for the full duration of operation. void VS1053B_device::WriteRegister(unsigned char addressbyte, unsigned char highbyte, unsigned char lowbyte){ while(!digitalRead(VS_DREQ)) ; //Wait for DREQ to go high indicating IC is available SPI_2.beginTransaction (VS1053B_START); digitalWrite(VS_XCS, LOW); //Select control //SCI consists of instruction byte, address byte, and 16-bit data word. SPI_2.transfer(0x02); //Write instruction SPI_2.transfer(addressbyte); SPI_2.transfer(highbyte); SPI_2.transfer(lowbyte); while(!digitalRead(VS_DREQ)) ; //Wait for DREQ to go high indicating command is complete digitalWrite(VS_XCS, HIGH); //Deselect Control SPI_2.endTransaction (); } // Plugin to put VS10XX into realtime MIDI mode // Originally from http://www.vlsi.fi/fileadmin/software/VS10XX/vs1053b-rtmidistart.zip // Permission to reproduce here granted by VLSI solution. const PROGMEM unsigned short sVS1053b_Realtime_MIDI_Plugin[] = { /* [28] Compressed plugin */ 0x0007, 0x0001, 0x8050, 0x0006, 0x0014, 0x0030, 0x0715, 0xb080, /* 0 */ 0x3400, 0x0007, 0x9255, 0x3d00, 0x0024, 0x0030, 0x0295, 0x6890, /* 8 */ 0x3400, 0x0030, 0x0495, 0x3d00, 0x0024, 0x2908, 0x4d40, 0x0030, /* 10 */ 0x0200, 0x000a, 0x0001, 0x0050, 0x0003, 0x0001, 0xd800 // добавлена команда увел.частоты: d800->r3 (x4.5+1.5) }; // 0x9800: x3.5+1.5, 0xa00: x4.0+0.0, 0xb800: x4.0+1.5, 0xc00: x4.5+0.0, 0xe00: x5.0+0.0 void VS1053B_device::LoadUserCode(void) { int i = 0; while (i<sizeof(sVS1053b_Realtime_MIDI_Plugin)/sizeof(sVS1053b_Realtime_MIDI_Plugin[0])) { unsigned short addr, n, val; // pgm_read_word_near(&glideStepArray[glide]) addr = pgm_read_word_near(&sVS1053b_Realtime_MIDI_Plugin[i++]); n = pgm_read_word_near(&sVS1053b_Realtime_MIDI_Plugin[i++]); while (n--) { val = pgm_read_word_near(&sVS1053b_Realtime_MIDI_Plugin[i++]); WriteRegister(addr, val >> 8, val & 0xFF); } } } //Sends a MIDI command/data. Doesn't check to see that cmd is greater than 127, or that data values are less than 127 void VS1053B_device::talkMIDI(byte cmd, byte data1, byte data2) { while (!digitalRead(VS_DREQ)); // Wait for chip to be ready (Unlikely to be an issue with real time MIDI) SPI_2.beginTransaction (VS1053B_HiFreq); digitalWrite(VS_XDCS, LOW); SPI_2.transfer(0); SPI_2.transfer(cmd); if( (cmd & 0xF0) <= 0xB0 || (cmd & 0xF0) >= 0xE0) { SPI_2.transfer(0); SPI_2.transfer(data1); SPI_2.transfer(0); SPI_2.transfer(data2); } else { SPI_2.transfer(0); SPI_2.transfer(data1); } digitalWrite(VS_XDCS, HIGH); SPI_2.endTransaction (); } void VS1053B_device::setup(bool PrintON) { if (PrintON) Serial.println(F("VS1053B_setup Start")); digitalWrite(VS_XCS, HIGH); //Deselect Control pinMode(VS_XCS, OUTPUT); digitalWrite(VS_XDCS, HIGH); //Deselect Data pinMode(VS_XDCS, OUTPUT); digitalWrite(VS_RESET, LOW); //Put VS1053 into hardware reset pinMode(VS_RESET, OUTPUT); pinMode(VS_DREQ, INPUT); SPI_2.begin(); //From page 12 of datasheet, max SCI reads are CLKI/7. Input clock is 12.288MHz. //Internal clock multiplier is 1.0x after power up. //Therefore, max SPI speed is 1.75MHz. We will use 1MHz to be safe. SPI_2.transfer(0xFF); //Throw a dummy byte at the bus delayMicroseconds(1); digitalWrite(VS_RESET, HIGH); //Bring up VS1053 LoadUserCode(); // if USE_PATCH_INIT delay(1000); // (4) talkMIDI(0xB0, 0x79, 127); // Reset All Controllers talkMIDI(0xB0, 0x7B, 0); // All Notes Off talkMIDI(0xB0, 0x5B, 0x41); // External effects depth talkMIDI(0xB0, 0x5D, 0); // Chorus depth talkMIDI(0xB0, 0x40, 0); // Sustain pedal talkMIDI(0xB0, 0x00, 0); // Bank select talkMIDI(0xB0, 0x01, 0); // Modulation Wheel talkMIDI(0xB0, 0x0A, 0x37); // Pan talkMIDI(0xC0, 0x38, 0); // Program number talkMIDI(0xE0, 0x00, 0x40); // Pitch Wheel talkMIDI(0xB0, 0x07, 100); // Main Volume talkMIDI(0xB0, 0, 0x00); //Default bank GM1 if (PrintON) Serial.println("VS1053B_setup End"); } //Send a MIDI note-on message. Like pressing a piano key //channel ranges from 0-15 void VS1053B_device::noteOn(byte channel, byte note, byte attack_velocity) { talkMIDI( (0x90 | channel), note, attack_velocity); } //Send a MIDI note-off message. Like releasing a piano key void VS1053B_device::noteOff(byte channel, byte note, byte release_velocity) { talkMIDI( (0x80 | channel), note, release_velocity); } void VS1053B_device::sendMIDI(byte data) { while (!digitalRead(VS_DREQ)); // Wait for chip to be ready (Unlikely to be an issue with real time MIDI) SPI_2.beginTransaction (VS1053B_HiFreq); digitalWrite(VS_XDCS, LOW); SPI_2.transfer(0); SPI_2.transfer(data); digitalWrite(VS_XDCS, HIGH); SPI_2.endTransaction (); }О разгоне модуля я уже говорил, о том, что с ним, в отсутствие распаянного порта, приходится общаться по SPI, - тоже. Так вот, максимальная частота SPI сильно зависит от частоты работы самого модуля. Поэтому сначала в него на частоте SPI 1 MHz загружается программа, осуществляющая помимо прочего и разгон, а потом, с уже разогнанным, общение происходит на частоте SPI 4 MHz.
В "комплект" модулей входят также файлы MIDIfn.h и MIDIfn.cpp, опубликованные в 6-м сообщении связанного проекта: http://arduino.ru/forum/proekty/midi-boxmidi-sintezator , поэтому повторять здесь их не буду. Собственно, в этих файла - базовый класс MIDI, а его наследник, используемый в данном проекте выглядит так:
файл MIDIplay.h
#ifndef MIDIPLAY_H // производный от MIDIengine класс - добавлена работа с органами управления #define MIDIPLAY_H #include <Arduino.h> #include "MIDIfn.h" #define timeInterval 10 // периодичность между отправками значений изменяющегося контрола - 14 мс (реально получается на 3-5 мс больше установленной) #define Debug_MIDIbox_CTRL false // печать для отладки контроллеров class MIDIplay; extern MIDIplay MIDI; class MIDIplay : public MIDIengine { public: // команды реализации MIDIplay() : MIDIengine() {} // ~MIDIbox() : ~MIDIengine() {} // void Check(); // void AllNotesOff() {}; virtual void ControlChange(uint8_t control, uint8_t value, uint8_t channel); virtual void ProgramChange(uint8_t program, uint8_t channel); virtual void NoteOff(uint8_t note, uint8_t channel); virtual void NoteOn(uint8_t note, uint8_t vel, uint8_t channel); virtual void PitchWheel(uint8_t loByte, uint8_t hiByte, uint8_t channel); protected: // MIDI-команды (с номерами) // void KeyPressure(uint8_t note, uint8_t pressure, uint8_t channel); void AfterTouch(uint8_t value, uint8_t channel); // void QuarterFrame(uint8_t value); // void SongPointer(uint8_t hiByte, uint8_t loByte); // void SongSelect(uint8_t song); // void TuneRequest(); // void SystemExclusive(); // (буфер SysEx заполнен) вызывается по команде F7 и любой другой команде кроме RealTime virtual void ActSenAction(); // реакция на сообщение ActiveSensing - по умолчанию отсутствует // void AddSysExBuffer(uint8_t data) {}; }; #endif // MIDIBOX_Hи MIDIplay.cpp
#include "MIDIplay.h" #include "VS1053B_MIDI.h" #include "screen.h" #define DEBUG_1 MIDIplay MIDI; void MIDIplay::NoteOn(uint8_t note, uint8_t vel, uint8_t channel) { if (SerialON) Serial.println(F("MIDIplay::NoteOn")); VS1053B.noteOn(channel, note, vel); screen.addCommand(3, 0x90 | channel, note, vel); // MIDIport.write(0x90 | channel); // MIDIport.write(note); // MIDIport.write(vel); } void MIDIplay::NoteOff(uint8_t note, uint8_t channel) { // переделать на 3 параметра: release_velocity if (SerialON) Serial.println(F("MIDIplay::NoteOff")); VS1053B.noteOff(channel, note, 0); screen.addCommand(3, 0x80 | channel, note, 0); // MIDIport.write(0x80 | channel); // MIDIport.write(note); // MIDIport.write(0); } void MIDIplay::ControlChange(uint8_t control, uint8_t value, uint8_t channel) { if (SerialON) Serial.println(F("MIDIplay::ControlChange")); VS1053B.talkMIDI(0xB0 | channel, control, value); screen.addCommand(3, 0xB0 | channel, control, value); // MIDIport.write(0xB0 | channel); // MIDIport.write(control); // MIDIport.write(value); } void MIDIplay::ProgramChange(uint8_t program, uint8_t channel) { if (SerialON) Serial.println(F("MIDIplay::ProgramChange")); VS1053B.talkMIDI(0xC0 | channel, program, 0); screen.addCommand(2, 0xC0 | channel, program, 0); // MIDIport.write(0xC0 | channel); // MIDIport.write(program); } void MIDIplay::PitchWheel(uint8_t loByte, uint8_t hiByte, uint8_t channel) { // функция записывает в буфер событие изменения Pitch Bend if (SerialON) { Serial.print(F("MIDIplay::PitchWheel ch: ")); Serial.println(channel); } VS1053B.talkMIDI(0xE0 | channel, loByte, hiByte); screen.addCommand(3, 0xE0 | channel, loByte, hiByte); // MIDIport.write(0xE0 | channel); // MIDIport.write(loByte); // MIDIport.write(hiByte); } void MIDIplay::ActSenAction() { // реакция на сообщение ActiveSensing - по умолчанию отсутствует VS1053B.sendMIDI(0xFE); MIDIport.write(0xFE); } void MIDIplay::AfterTouch(uint8_t value, uint8_t channel) { if (SerialON) Serial.println(F("MIDIplay::ProgramChange")); VS1053B.talkMIDI(0xD0 | channel, value, 0); screen.addCommand(2, 0xD0 | channel, value, 0); // MIDIport.write(0xD0 | channel); // MIDIport.write(value); } #undef DEBUG_1В устройстве используется мой любимый OLED дисплей 128х64, для которого я еще в первые дни своего знакомства с Ардуино написал библиотечку, впоследствии опубликованную: http://arduino.ru/forum/proekty/asoled-kompaktnaya-biblioteka-dlya-oled-displeya-128kh64-s-kirillitsei-utf-8
Но в данном проекте от нее используется по сути только инициализация дисплея. Да и задача стояла простая: показывать 8 последних пришедших MIDI команд. Что удобнее в 16-чном виде. Поэтому и фонт нарисовал специально для этого проекта, состоящий всего из 16 цифр.
Но работа с растровым дисплеем - занятие довольно медленное. Особенно по последовательному интерфейсу. А еще более особенно, если ради экономии проводов протокол требует передавать дополнительную информацию, в частности, о номере устройства. А совсем уж особенно - если частота работы последовательной шины ограничена величиной 400 кГц. Кстати, последнее меня несколько озадачило: оказалось, библиотека I2C для STM32 поддерживает всего две частоты передачи 100кГц и 400 кГц, причем при любом значении кроме 400000 библиотека устанавливает частоту 100000. Даже на Uno I2C удавалось разогнать почти до 1 МГц, а на Due, я проверял, и сама библиотека и дисплей в частности устойчиво работают на 2 МГц. А вот на STM32 выше 400 кГц - никак. Ну, точнее, со стандартной для него библиотекой. А ковыряться с собственной реализацией I2C для STM32 мне как-то не хотелось - хотелось побыстрее доделать дивайс.
Так вот, о скорости: протокол MIDI сам не очень быстрый - задержка ноты составляет примерно 1 мс. При том, что величины порядка 7-10 мс уже различаются на слух. А вывод на экран - всего 400 Гц. А это порядка 50 мс. В общем, на экран пришлось выводить кусочками по 16 байт.
В программе предусмотрен текстовый экранный буфер, который поддерживается в актуальном состоянии обработчиком MIDI сообщений, благо, он всего 8 строк по 6 символов (MIDI команда - три 16-ричных числа). Затем в перемешку с опросом входящих MIDI команд происходит постепенное отображение на экран, разделенное на 65 этапов: один из которых - это преобразование текстового буфера в графический - объемом 1 кБайт, а остальные 64 - поблочная (длина блока 16 байт) пересылка этого экранного буфера собственно на дисплей. В результате максимальная дополнительная задержке MIDI команды не превосходит 0.6-0.8 мс, что вполне приемлемо. Ну и частота обновления экрана порядка 20 fps, что также не вызывает возражений.
Работа с экраном, как обычно, сосредоточена в двух файлах с оригинальными названиями:
screen.h
#ifndef SCREEN_H #define SCREEN_H #include <Arduino.h> class screen1306; extern screen1306 screen; class screen1306 { public: screen1306(){}; ~screen1306(){}; void init(); void clear(); void drawDigit(byte row, byte column, byte value); void draw(); void addCommand(byte len, byte cmd, byte parm1, byte parm2); void run(); // вызывать на каждом проходе цикла private: byte screenState; // переменная, управляющая выводом на экран }; #endifи screen.cpp
//#include <Arduino.h> #include <Wire.h> #include "comm_ctrl.h" #include "screen.h" #include "ASOLED.h" #include "VS1053B_MIDI.h" screen1306 screen; byte buffer[1028] = {0x80, 0x40, 0xFF, 0x01}; // заголовок буфера будет оставаться простоянным, а область изображения - меняться const byte digBMP[12*16] = { // таблица знакогенератора для цифр от "0" до "F" 0x7E, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x7E, // 0 0x0C, 0x0C, 0x1C, 0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, // 1 0x7E, 0xC3, 0xC3, 0xC3, 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC3, 0xFF, // 2 0x7E, 0xC3, 0xC3, 0xC3, 0x03, 0x0E, 0x03, 0x03, 0xC3, 0xC3, 0xC3, 0x7E, // 3 0x03, 0x07, 0x0F, 0x1B, 0x33, 0x63, 0xC3, 0xFF, 0x03, 0x03, 0x03, 0x03, // 4 0xFF, 0xC3, 0xC0, 0xC0, 0xC0, 0xFE, 0x03, 0x03, 0x03, 0xC3, 0xC3, 0x7E, // 5 0x7E, 0xC3, 0xC3, 0xC0, 0xC0, 0xFE, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x7E, // 6 0xFF, 0xC3, 0x03, 0x06, 0x06, 0x0C, 0x0C, 0x18, 0x18, 0x30, 0x30, 0x30, // 7 0x7E, 0xC3, 0xC3, 0xC3, 0xC3, 0x7E, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x7E, // 8 0x7E, 0xC3, 0xC3, 0xC3, 0xC3, 0x7F, 0x03, 0x03, 0x03, 0xC3, 0xC3, 0x7E, // 9 0x0F, 0x1B, 0x1B, 0x33, 0x33, 0x63, 0x63, 0xFF, 0xC3, 0xC3, 0xC3, 0xC3, // A 0x7E, 0xC3, 0xC3, 0xC3, 0xC6, 0xFC, 0xC6, 0xC3, 0xC3, 0xC3, 0xC3, 0x7E, // B 0x7E, 0xC3, 0xC3, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC3, 0xC3, 0x7E, // C 0xFC, 0xC6, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC6, 0xFC, // D 0xFF, 0xC3, 0xC0, 0xC0, 0xC0, 0xFC, 0xC0, 0xC0, 0xC0, 0xC0, 0xC3, 0xFF, // E 0xFF, 0xC3, 0xC0, 0xC0, 0xC0, 0xFC, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0}; // F const int dBank[6] = {7, 6, 5, 3, 2, 1}; // мультипликативное смещение для row const int dShift[6] = {8, 6, 1, 7, 2, 0}; // аддитивное смещение для row byte scrChars[48] = { 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, // массив байтов для отображения на экране (если байт >=16, он не рисуется) 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16}; // массив байтов для отображения на экране (если байт >=16, он не рисуется) void screen1306::init(){ MIDIport.begin(31500); LD.init(); //initialze OLED display LD.clearDisplay(); if (SerialON) Serial.println("Start"); delay(150); Serial.println(F(" before VS1053B_setup Start")); pinMode(LED_ACTSEN_PIN, OUTPUT); VS1053B.setup(SerialON); Serial.println(F(" after VS1053B_setup Start")); delay(200); Serial2.begin(31325); VS1053B.noteOn(0, 60, 64); VS1053B.noteOn(0, 63, 64); VS1053B.noteOn(0, 67, 64); delay(2000); VS1053B.noteOff(0, 60, 64); VS1053B.noteOff(0, 63, 64); VS1053B.noteOff(0, 67, 64); delay(100); VS1053B.talkMIDI(0xC0, 0x13, 0); // Program number // LD.SetNormalOrientation(); // pins on top LD.SetTurnedOrientation(); // pins on bottom } void screen1306::clear() { memset(&buffer[4], 0, 1024); }; void screen1306::draw() { LD.drawBitmap(buffer, 0, 0, 128, 8); }; void screen1306::drawDigit(byte row, byte column, byte value) { if (value < 16) { for (int i = 0; i < 12; i++) { // цикл по строкам сверху register uint16_t tmp = digBMP[i + value*12] << dShift[column]; register int addrL = 6 + dBank[column]*128 + 16*row + i; // 4 + dBank[column]*128 + 2 + 16*row + i buffer[addrL] |= highByte(tmp); buffer[addrL - 128] |= lowByte(tmp); } } } void screen1306::addCommand(byte len, byte cmd, byte parm1, byte parm2) { if (SerialON) Serial.println("addCommand "); for (int i = 0; i < 42; i++) scrChars[i] = scrChars[i+6]; // сдвигаем экран вверх scrChars[42] = cmd >> 4; scrChars[43] = cmd & 0x0F; if (1 < len) { scrChars[44] = parm1 >> 4; scrChars[45] = parm1 & 0x0F; } else { scrChars[44] = 16; scrChars[45] = 16; } if (2 < len) { scrChars[46] = parm2 >> 4; scrChars[47] = parm2 & 0x0F; } else { scrChars[46] = 16; scrChars[47] = 16; } } #define ASA_OLED_DATA_MODE 0x40 #define OLED_ADDRESS 0x3C void screen1306::run() { // вызывать на каждом проходе цикла for(int i = 0; i < 6; i++) for(int j = 0; j < 8; j++) drawDigit(j,i,scrChars[j*6 + i]); if(screenState < 64) { Wire.beginTransmission(OLED_ADDRESS); // begin transmitting Wire.write(ASA_OLED_DATA_MODE);//data mode for (byte i = 0; i < 16; i++){ Wire.write(buffer[4 + screenState*16 + i]); } Wire.endTransmission(); // stop transmitting///////////////////////////////////////////////////////////// } else { clear(); for(int i = 0; i < 6; i++) for(int j = 0; j < 8; j++) drawDigit(j,i,scrChars[j*6 + i]); } screenState++; if (screenState >=65) screenState = 0; }Ну и в заключение (какая же статья без картинок!) фото, на котором изображена совместная отладка блоков из двух проектов: MIDI-бокса (слева - поставлен на попа, т.к. снизу к нему подключен логический анализатор) и MIDI-плеера (справа, лежит на столе собранный на макетной плате):
Большое спасибо за библиотеку VS1053_MIDI!
Небольшое уточнение, на которое в связи со своей невнимательностью убил час. В файле VS1053_MIDI.cpp в 1-й строке стоит
#include "VS1053B_MIDI.h"
а поскольку и такая библиотека у меня стоит, то компилятор матно ругался на отсутствие объявлений функций. Исправить просто - написать там:
#include "VS1053_MIDI.h"
Это моя невнимательность: файлы на самом деле называются VS1053B_MIDI.*. В тексте поправил.
Приношу свои извинения за доставленные проблемы.
Это моя невнимательность: файлы на самом деле называются VS1053B_MIDI.*. В тексте поправил.
Приношу свои извинения за доставленные проблемы.
А мне как раз понравилось предыдущее название. Под ардуинку - VS1053B_MIDI.*, под STM32 - VS1053_MIDI.*
Хотя Ваша библиотека поинтереснее. Туда бы команды препроцессора на выбор варианта SPI в зависимости от контроллера - так и ей вообще цены бы не было. Сам в них, к сожалению, не силен.
Так наверное, основа у библиотек, как и название, одна и та же. Я ведь не с нуля библиотеку делал, а сначала скачал, что нашел, потом уже переделывал под свои нужды и, кстати, в предыдущем проекте ( http://arduino.ru/forum/proekty/analog-analogovogo-sintezatora ) эта библиотечка использовалась с Мегой 2560.
По поводу препроцессора, так там только с stm32 столько вариантов... Причем, как выяснилось, эти варианты могут еще и зависеть от прочей подключаемой аппаратуры (например, SPI1 на альтернативных пинах вместе с I2C не работает, причем узнать об этом, кроме, разумеется, практики, можно только из errata, - в дэйташите этого нет). В общем, описание универсального случая весьма проблематично.
И выбрать из трех возможных вариантов наиболее приемлемый вариант по умолчанию - тоже проблема, т.к. основные пины для SPI1 не толерантны к 5 Вольтам.
В общем, VS1053B не такое уж распространенное устройство, и если оно используется, то является одним из центральных в системе. А раз так, лучше не держать его в списке универсальных библиотек, а класть каждый раз в папочку конкретного проекта и рихтовать под каждый конкретный случай.
И еще: не люблю директивы препроцессора за то, что они здорово мешают читать исходник.
Ну и сегодня завершаю публикацию проекта в том виде, в каком он на сегодняшний день состоялся. В частности, он уже оказался очень полезен для реализации длящихся в настоящее время проектов, описание которых последует через некоторое время.
Осталось всего два файла, при этом размеры их таковы, что можно обойтись без их сворачивания.
Первый - основная программа. Расширение у нее .ino, а назвать ее может каждый, как душенька пожелает. Тем более с именами файлов я в этой теме уже облажался.
#define SPI_HAS_TRANSACTION #include <Wire.h> #include <SPI.h> #include "ASOLED.h" #include "VS1053B_MIDI.h" #include "MIDIplay.h" #include "screen.h" void setup() { if (SerialON) { Serial.begin(115200); while(!Serial); } screen.init(); } void loop() { MIDI.Check(); screen.run(); if (SerialON) { if(Serial.available()) { byte ch = Serial.read(); switch (ch) { case 't': MIDI.printStat(); break; } } } }Второй файл - настроечный, называется comm_ctrl.h
Само устройство выглядит так:
Светодиод показывает наличие сигнала Active Sensing, а на экране отображаются последние 8 MIDI команд.
У прибора есть еще одна особенность: при включении питания он воспроизводит органным звуком мажорный аккорд (чтобы продемонстрировать свою работоспособность), а затем НЕ возвращается к настройкам по умолчанию. С точки зрения настройки разрабатываемых устройств именно такое поведение оказалось удобным - есть контроль за тем, не "забывает" ли новое устройство послать команды, инициализирующие синтезатор.
Классно получилось! Корпус выше всяческих похвал! Респект и уважуха!!!
PS поинтересуюсь, аддон для блюпила какой используешь, в настроенной среде от DIMAX не компилируется
Но я там исправил немного ошибок, по большей части касающихся stm32f407, но, возможно, что-то еще.
Андриано, по картинке не ясно - это Кларковский аддон или СТМ?
Стыдно признаться, но - понятия не имею.
Втихаря надеялся, что те, кому нужно, сумеют опознать по картинке.
По каким критериям опознавать то? В тексте модулей что искать?
PS. Судя по ошибкам, которые были для f407, вряд ли это STM.
точно не STM...
а ASOLED.h откуда взять? На 4 версию ругается
А что за ошибки, если кратенько? Критичные?
критичные
Андриано, может строкой json сюда кинешься, так как через менеджер устанавливал она должна быть
Ua6em не знал что вы теперь спец по стм
шутить изволите, да ошибка:
D:\ARDUINO\arduino-1.8.9.new\portable\sketchbook\libraries\ASOLED\ASOLED.cpp:527:21: error: 'TWBR' was not declared in this scope
byte twbrbackup = TWBR;
а ASOLED.h откуда взять? На 4 версию ругается
D:\ARDUINO\arduino-1.8.9.new\portable\sketchbook\libraries\ASOLED\ASOLED.cpp:527:21: error: 'TWBR' was not declared in this scope
byte twbrbackup = TWBR;
Да, ASOLED чуть подрихтована под stm. Собственно, TWBR - это задание частоты i2c на AVR. Все, связанное с ним, убрать полностью, а вместо этого устанавливать частоту i2c стандартным для ARM способом (для чего есть специальная функция. Если не ошибаюсь, .setClock(400000);).
Последние ошибки, которые исправлял, были для 407 - там просто тупо были перенесены структуры из f103, а некоторые из них для f4 заметно отличаются. Но я устанавливал давно, может, сегодня уже и исправили.
Upd. Обнаружил, что все это не на том компе, за которым я сейчас сижу, так что подробности - не сегодня.
Upd2. Сейчас посмотрел, судя по датам, правка коснулась файлов: dac.h, timer.h и rcc4.h. В первых двух добавлены регистры, которых нет в f103, но есть в f407, а в последнем - добавлены устройства (в частности - таймеры), которых не было в f103. Возможно, где-то еще подправлены адреса регистров.
критичные
Андриано, может строкой json сюда кинешься, так как через менеджер устанавливал она должна быть
критичные
Андриано, может строкой json сюда кинешься, так как через менеджер устанавливал она должна быть
Да! Это оно!
Я вижу вы серьезно в теме. Подскажите пожалуйста, нужно на esp8266/32 проиграть midi-файл, через пассивный извещатель. То есть через ШИМ. Как из midi файла получить частоту и длительность нот?
Перерыл весь интернет. Всё как сговорились, показывают как через сайт переконвертировать midi-файл в серию tone() и delay (). Ну это же ерунда...