Продолжение истории про старый сгоревший синтезатор, в который я пытаюсь вдохнуть новую жизнь путем полной замены железа, отвечающего за генерацию звука, на программный синтезатор, построенный на базе мини-компьютера EmbedSky E8 с Linux на борту. Как это часто бывает, между публикацией первой и второй части статьи прошло гораздо больше времени, чем планировалось, но, тем не менее, продолжим.



В предыдущей части был изложен процесс выбора аппаратной платформы для нового «мозга» синтезатора с описанием технических характеристик решения, кратко освещен процесс сборки необходимых библиотек и проблем, с которыми пришлось столкнуться в процессе. Теперь же что касается железа, то мы посмотрим как устроена клавиатурная матрица синтезатора, а дальше будет больше деталей посвященных софтовой части.


Клавиатурная матрица

Клавиатурная матрица синтезатора очень похожа на обычную клавиатурную матрицу, которые многие любители микроконтроллеров уже наверняка подключали к своим Arduino. Для каждой клавиши синтезатора на ней предусмотрено от одного (в наиболее дешевых моделях) до двух (в основной массе моделей) переключателей. С помощью двух расположенных рядом переключателей, один из которых при нажатии клавиши замыкается немного раньше другого, микроконтроллер может определить условную силу, а точнее скорость, с которой клавиша была нажата, чтобы впоследствии был воспроизведен звук соответствующей громкости. Выглядит это так:


На обратной стороне платы размещены диоды, которые предотвращают «ложное» считывание нажатых клавиш при одновременном нажатии нескольких клавиш. Вот фрагмент принципиальной схемы клавиатурной матрицы, на которой видны эти два переключателя и подсоединенные к ним диоды:


Чтобы просканировать матрицу, микроконтроллер последовательно подтягивает столбцы (выводы, помеченные как N) к питанию, и проверяет уровень на строках (выводы, помеченные как B). Если уровень какой-либо строки окажется высоким, значит соответствующая активному в данный момент сочетанию «столбец-строка» клавиша нажата. На схеме показана лишь часть клавиатуры — всего на ней 76 клавиш (13 строк и 6 х 2 колонок, что дает в сумме 156 возможных вариантов при сканировании матрицы и 25 используемых выводов микроконтроллера). Сканирование всей клавиатуры осуществляется несколько десятков раз в секунду и незаметно для исполнителя.

В моем синтезаторе микроконтроллером, ответственным за сканирование клавиатуры, изначально был 8-битный однократно программируемый микроконтроллер Hitachi HD63B05V0, работающий на частоте 8 МГц и имеющий 4 КБ ROM и 192 байта RAM памяти. К сожалению, данный контроллер оказался нерабочим после инцидента с питанием, описанного в начале первой статьи. Зато, к счастью, он оказался почти совместим по выводам с имеющимся у меня контроллером ATmega162, на который я его и заменил, перерезав и перепаяв всего лишь 2 дорожки на плате, одна из которых — это вывод RESET, оказавшийся совсем не в том месте, как у HD63B05V0.

Поскольку такое включение контроллера не позволяло мне воспользоваться встроенным UART (так как он тоже был на других выводах), то для вывода информации о нажатых клавишах я воспользовался этой односторонней (только запись) реализацией последовательного порта. Также в микроконтроллер был залит загрузчик TinySafeBoot, также использующий программную реализацию последовательного порта, для возможности будущего обновления прошивки. Поскольку в качестве языка для быстрой разработки всего высокоуровневого ПО синтезатора я выбрал Python + Qt5, то для TinySafeBoot я также написал модуль на Python, который позволяет считывать и записывать прошивку в микроконтроллер AVR. Сам микроконтроллер AVR подключен к последовательному порту UART1 на плате EmbedSky E8 и питается от напряжения 3.3V, чтобы избежать необходимости в преобразовании уровней.

Исходный код прошивки для AVR
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <string.h>
#include "dbg_putchar.h"

#define MIDI_BASE			18
#define ZERO_BASE			28
#define KEYS_COUNT			76

#define hiz(port, dir) do { \
		(dir) = 0; \
		(port) = 0; \
	} while(0)

#define alow(port, dir) do { \
		(dir) = 0xff; \
		(port) = 0; \
	} while(0)

uint8_t keys[KEYS_COUNT];

/* Get state of a row by its index
 * starting from 1 to 13 */
uint8_t getRow(uint8_t idx)
{
	if (idx <= 8) {
		return (PINC & (1 << (8 - idx)));
	} else if (idx >= 9 && idx <= 11) {
		return (PINE & (1 << (11 - idx)));
	} else if (idx == 12) {
		return (PINA & (1 << PIN6));
	} else if (idx == 13) {
		return (PINA & (1 << PIN4));
	}

	return 0;
}

inline void activateColumn1(uint8_t idx)
{
	PORTD = 0x00 | (1 << (8 - idx));
	PORTB = 0x00;
}

void activateColumn2(uint8_t idx)
{
	if (idx <= 3) {
		PORTB = 0x00 | (1 << (idx + 4));
		PORTD = 0x00;
	} else if (idx == 4) {
		PORTB = 0x00 | (1 << PIN4);
		PORTD = 0x00;
	} else if (idx == 5 || idx == 6) {
		PORTD = 0x00 | (1 << (idx - 5));
		PORTB = 0x00;
	}
}

inline void deactivateColumns(void)
{
	PORTD = 0x00;
	PORTB = 0x00;
}

inline void initPorts(void)
{
	hiz(PORTA, DDRA);
	hiz(PORTC, DDRC);
	hiz(PORTE, DDRE);
	PORTB = 0x00;
	DDRB = 0xfe;
	DDRD = 0xff;
}

void resetRows(void)
{
	/* output low */
	alow(PORTC, DDRC);
	alow(PORTE, DDRE);

	/* don't touch PA7 & PA5 */
	DDRA |= 0x5f;
	PORTA &= ~0x5f;

	_delay_us(10);

	/* back to floating input */
	hiz(PORTC, DDRC);
	hiz(PORTE, DDRE);
	DDRA &= ~0x5f;
}

/* base MIDI note number is 25: C#0 */

int main(void)
{
	uint8_t row, col, layer;
	uint8_t note, offset;

	initPorts();
	memset(keys, 0, sizeof(keys));
	dbg_tx_init();
	dbg_putchar('O');
	dbg_putchar('K');

	while(1)
	{
		for (layer = 0; layer < 2; layer++)
		{
			for (col = 1; col <= 6; col++)
			{
				if (!layer)
					activateColumn1(col);
				else
					activateColumn2(col);

				for (row = 1; row <= 13; row++)
				{
					note = 6 * row + col + MIDI_BASE;
					offset = note - ZERO_BASE;

					if (getRow(row))
					{
						if (!layer)
						{
							/* increase velocity counter */
							if (keys[offset] < 254 && !(keys[offset] & 0x80))
								keys[offset]++;
						}
						else
						{
							if (!(keys[offset] & 0x80))
							{
								/* generate note-on event */
								dbg_putchar(0x90);
								dbg_putchar(note);
								/*dbg_putchar(keys[offset]);*/
								dbg_putchar(0x7f);
								/* stop counting */
								keys[offset] |= 0x80;
							}
						}
					}
					else
					{
						if (layer)
							continue;

						if (keys[offset] & 0x80)
						{
							/* generate note off event */
							dbg_putchar(0x90);
							dbg_putchar(note);
							dbg_putchar(0x00);
							/* reset key state */
							keys[offset] = 0x00;
						}
					}
				}

				deactivateColumns();
				resetRows();
			}
		}
	}

	return 0;
}


Модуль на Python для TinySafeBoot
import serial
import binascii
import struct
import intelhex
import sys

class TSB(object):
	CONFIRM = '!'
	REQUEST = '?'

	def __init__(self, port):
		self.port = serial.Serial(port, baudrate=9600, timeout=1)
		self.flashsz = 0

	def check(self):
		if not self.flashsz:
			raise Exception("Not activated")

	def activate(self):
		self.port.write("@@@")
		(self.tsb, self.version, self.status, self.sign, self.pagesz, self.flashsz, self.eepsz) = \
			struct.unpack("<3sHB3sBHH", self.port.read(14))
		self.port.read(2)
		self.pagesz *= 2
		self.flashsz *= 2
		self.eepsz += 1
		assert(self.port.read() == self.CONFIRM)

	def rflash(self, progress=None, size=0):
		self.check()
		self.port.write("f")
		self.addr = 0
		self.flash = ""
		size = self.flashsz if not size else size
		while self.addr < size:
			if progress is not None:
				progress("read", self.addr, size)
			self.port.write(self.CONFIRM)
			page = self.port.read(self.pagesz)
			if len(page) != self.pagesz:
				raise Exception("Received page too short: %d" % len(page))
			self.addr += len(page)
			self.flash += page
		return self.flash.rstrip('\xff')

	def wflash(self, data, progress=None):
		if len(data) % self.pagesz != 0:
			data = data + "\xff" * (self.pagesz - (len(data) % self.pagesz))
		assert(len(data) % self.pagesz == 0)
		self.check()
		self.port.write("F")
		self.addr = 0
		assert(self.port.read() == self.REQUEST)
		while self.addr < len(data):
			if progress is not None:
				progress("write", self.addr, len(data))
			self.port.write(self.CONFIRM)
			self.port.write(data[self.addr:self.addr + self.pagesz])
			self.addr += self.pagesz
			assert(self.port.read() == self.REQUEST)
		self.port.write(self.REQUEST)
		return self.port.read() == self.CONFIRM

	def vflash(self, data, progress=None):
		fw = self.rflash(progress, len(data))
		return fw == data

	def info(self):
		print "Tiny Safe Bootloader: %s" % self.tsb
		print "Page size:   %d" % self.pagesz
		print "Flash size:  %d" % self.flashsz
		print "EEPROM size: %d" % self.eepsz

if __name__ == "__main__":
	import argparse

	def progress(op, addr, total):
		sys.stdout.write("\r%s address: $%0.4x/$%0.4x" % (op, addr, total))
		sys.stdout.flush()

	parser = argparse.ArgumentParser()
	parser.add_argument("filename", help="firmware file in Intel HEX format")
	parser.add_argument("--device", help="Serial port to use for programming", default="/dev/ttyUSB0")
	args = parser.parse_args()

	tsb = TSB(args.device)
	tsb.activate()
	tsb.info()
	fw = intelhex.IntelHex(args.filename)
	assert(tsb.wflash(fw.tobinstr(), progress))
	assert(tsb.vflash(fw.tobinstr(), progress))
	print "\nOK\n"


В качестве программатора для AVR я сначала использовал программатор на базе Launchpad MSP430, коих у меня имеется в наличии несколько штук, а затем это самодельное чудо (неплохо работающее, кстати), уступило место прибывшему из Китая программатору TL866CS MiniPro. Ощущения от нового программатора крайне положительные.

Очень подробно про устройство клавиатуры синтезатора и способы ее сканирования, включая один очень оригинальный способ скан��рования через интерфейс микроконтроллера AVR для подключения внешней микросхемы ОЗУ рассказывается на сайте OpenMusicLabs

Приготовление ядра с поддержкой Realtime Preemption

Отчасти для получения большего контроля над планировщиком и снижения задержки (latency) при проигрывании звука, а отчасти из спортивного интереса, я решил использовать ядро с патчем PREEPMT RT, одной из основных особенностей которого является то, что прерывания также становятся «процессами», которые могут быть вытеснены планировщиком с учетом приоритета. Оригинальное ядро, поставляемое Samsung для процессора S5PV210, на базе которого построена система, базируется на ядре версии 3.0.8, судя по всему от Android. Ни один из патчей RT_PREEMPT, имеющихся на сайте проекта, предназначенных для данной версии ядра (3.0.8), не хотел накладываться на исходники без конфликтов, но в конце концов, разрешив все конфликты вручную, удалось наложить патч версии 3.0.8-rt23.

Из-за того, что в модифицированном таким образом ядре модифицированными также оказались такие базовые структуры, как spinlock и mutex, с ним перестали линковаться поставляемые в виде скомпилированных объектных файлов проприетарные драйверы некоторых периферийных устройств: видеокамер, контроллера ёмкостного тачскрина, и, что самое ужасное, аудиокодека. Вернемся к ним позже, а сейчас отключим их и попытаемся первый раз запустить плату со свежесобранным ядром реального времени и… получим моментальный kernel panic. Происходил он еще до запуска отладчика kgdb (который, как выяснил��сь позже, все равно не работал бы, даже если бы запустился), так что для отладки пришлось вставлять printf-ы в файл init/main.c, функцию start_kernel, чтобы определить место, в котором все рушится. Таким образом выяснилось, что последнее, что успевало сделать ядро, это вызвать функцию hrtimers_init(), инициализирующую таймеры высокого разрешения и их прерывания. Этот код зависит от конкретной платформы, и в нашем случае находится в arch/arm/plat-s5p/hr-time-rtc.c. Как я уже говорил, одной из основных особенностей ядра с патчем PREEMPT RT является то, что прерывания становятся потоками. Это возможно и в обычном ядре, но ядро с PREEMPT RT по-умолчанию пытается сделать таковыми почти все прерывания. Дальнейший анализ кода показал, что для работы этих потоков используется задача kthreadd_task, которая инициализируется в самом конце функции start_kernel — гораздо позже, чем происходит инициализация таймеров. Падение же происходило из-за того, что прерывание таймера ядро пыталось сделать потоковым, в то время как kthreadd_task еще NULL. Решается это установкой для отдельных прерываний, которые не стоит делать потоковыми ни при каких обстоятельствах, флага IRQF_NO_THREAD который и был добавлен к флагам прерывания таймера в hr-time-rtc.c. Ура! Ядро загрузилось, но это еще только начало…

Как я уже упоминал выше, одним из побочных эффектов стало то, что модуль, отвечающий за аудио ввод/вывод, перестал линковаться с новым ядром. Отчасти это было тем, что ядро с PREEMPT RT поддерживает (в версии 3.0.8) только механизм управления памятью SLAB, а изначально модуль был скомпилирован с включенным механизмом SLUB, который не поддерживается новым ядром. Однако, мне посчастливилось работать в Лаборатории Касперского, и я уговорил коллегу декомпилировать для меня файлы драйвера и кодека с помощью декомпилятора Hex-Rays для ARM, после чего удалось практически полностью воссоздать их исходный код. Практически — потому что в результате с «новым» драйвером аудиоинтерфейс стал определяться, однако из-за каких-то различий в низкоуровневой процедуре инициализации регистров микросхемы WM8960 звук проигрывался с артефактами. Какое-то время я пытался подправить свой драйвер, но потом выбрал более легкий путь — я отправил в техподдержку китайской компании EmbedSky Tech, где покупал мини-компьютер, свой патч с PREEMPT_RT, и попросил их скомпилировать для меня и выслать файлы аудиодрайвера. Ребята быстро откликнулись и прислали мне файлы, с которым звук, наконец, заработал как положено.

Кстати, пока я возился со своим декомпилированным драйвером, я обнаружил, что отладчик kgdb не работает ни с моим, ни с оригинальным ядром. Как выяснилось, для его работы требуется поддержка синхронного (polling) опроса последовательного порта, которая отсутствовала в драйвере последовательного порта Samsung (drivers/tty/serial/samsung.c). Я добавил в драйвер требуемую поддержку, основанную на этом патче, после чего отладчик заработал.

Копаем дальше. Вторым побочным эффектом нового ядра оказалась крайне низкая, с большими «лагами», скорость работы всех четырех многострадальных последовательных портов системы на кристалле S5PV210, в результате чего была невозможна нормальная работа в терминале через последовательный порт, а также не работала как положено перепрошивка контроллера AVR, опрашивающего клавиатуру синтезатора. Я долго пытался понять в чем причина, но заметил лишь то, что ввод каждого символа в терминале приводил к генерации нескольких миллионов прерываний последовательного порта — ядро, похоже, не спешило их обрабатывать. В итоге я решил эту проблему тем, что с помощью вышеупомянутого флага IRQF_NO_THREAD сделал все прерывания последовательных портов непотоковыми. Это решение вышло не очень красивым, потому что помимо драйвера Samsung пришлось внести изменения в файлы serial_core.c и serial_core.h, затрагивающие вообще все последовательные порты. Потому что в ядре с PREEMPT RT нельзя использовать spin_lock_t в драйверах, которые NO_THREAD, а нужно использовать raw_spinlock_t.

В оригинальном ядре, которое, как я говорил выше, поддерживает различные периферийные устройства, такие как видеокамеры, аппаратные кодеки, HDMI и т.д., из 512 МБ оперативной памяти было доступно лишь около 390 МБ, а остальное было зарезервировано для работы вышеуказанных устройств, причем всегда (даже если в процессе конфигурирования ядра они были отключены). Очень расточительно, особенно учитывая, что лишние 120 МБ оперативной памяти синтезатору очень даже не помешают для хранения сэмплов. Память резервировалась в файле arch/arm/mach-s5pv210/mach-tq210.c, который является главной точкой сбора всей информации о конфигурации и устройствах конкретной машины (в нашем случае — платы). Комментируем выделение памяти — вызов функции s5p_reserve_bootmem, и получаем 120 МБ дополнительной памяти для работы синтезатора.

Последнее изменение, которое было внесено в ядро, касалось минимального размера буфера для аудиоданных, который в оригинале был равен одной странице памяти, что при частоте дискретизации 44100 Гц, 2 канала по 16 бит давало примерно 20 мс — многовато. Это значение было изменено в файле sound/soc/samsung/dma.c на 128 байт, после чего минимальный размер буфера уменьшился до нескольких миллисекунд без ущерба стабильности и работоспособности.

Исходный код ядра с PREEMPT RT и всеми модификациями на GitHub

Как происходит общение микроконтроллера AVR с LinuxSampler

AVR подключен к последовательному порту платы мини-компьютера, и выплевывает в свой софтверный UART готовые MIDI-сообщения. Дабы избавить себя от необходимости писать драйверы, было принято решение использовать в качестве транспорта для всех аудио и MIDI-данных сервер JACK. Небольшое приложеньице на C подключается к последовательному порту, регистрирует себя в JACK как MIDI-OUT и начинает перенаправлять туда все полученные MIDI-сообщения, а JACK уже доставляет их в LinuxSampler. Дешево и сердито.

Исходный код приложения-моста между последовательным портом и JACK
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <sysexits.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <termios.h>
#include <jack/jack.h>
#include <jack/midiport.h>

#define UART_SPEED	B9600

jack_port_t	*output_port;
jack_client_t *jack_client = NULL;
int input_fd;

void init_serial(int fd)
{
    struct termios termios;
    int res;

    res = tcgetattr (fd, &termios);
    if (res < 0) {
        fprintf (stderr, "Termios get error: %s\n", strerror(errno));
        exit (EXIT_FAILURE);
    }

    cfsetispeed (&termios, UART_SPEED);
    cfsetospeed (&termios, UART_SPEED);

    termios.c_iflag &= ~(IGNPAR | IXON | IXOFF);
    termios.c_iflag |= IGNPAR;

    termios.c_cflag &= ~(CSIZE | PARENB | CSTOPB | CREAD | CLOCAL);
    termios.c_cflag |= CS8;
    termios.c_cflag |= CREAD;
    termios.c_cflag |= CLOCAL;

    termios.c_lflag &= ~(ICANON | ECHO);
    termios.c_cc[VMIN] = 3;
    termios.c_cc[VTIME] = 0;

    res = tcsetattr (fd, TCSANOW, &termios);
    if (res < 0) {
        fprintf (stderr, "Termios set error: %s\n", strerror(errno));
        exit (EXIT_FAILURE);
    }
}

double
get_time(void)
{
    double		seconds;
    int		ret;
    struct timeval	tv;

    ret = gettimeofday(&tv, NULL);

    if (ret) {
        perror("gettimeofday");
        exit(EX_OSERR);
    }

    seconds = tv.tv_sec + tv.tv_usec / 1000000.0;

    return seconds;
}

double
get_delta_time(void)
{
    static double	previously = -1.0;
    double		now;
    double		delta;

    now = get_time();

    if (previously == -1.0) {
        previously = now;

        return 0;
    }

    delta = now - previously;
    previously = now;

    assert(delta >= 0.0);

    return delta;
}

static double
nframes_to_ms(jack_nframes_t nframes)
{
    jack_nframes_t sr;

    sr = jack_get_sample_rate(jack_client);

    assert(sr > 0);

    return (nframes * 1000.0) / (double)sr;
}

static double
nframes_to_seconds(jack_nframes_t nframes)
{
    return nframes_to_ms(nframes) / 1000.0;
}

static jack_nframes_t
ms_to_nframes(double ms)
{
    jack_nframes_t sr;

    sr = jack_get_sample_rate(jack_client);

    assert(sr > 0);

    return ((double)sr * ms) / 1000.0;
}

static jack_nframes_t
seconds_to_nframes(double seconds)
{
    return ms_to_nframes(seconds * 1000.0);
}

static void
process_midi_output(jack_nframes_t nframes)
{
    int t, res;
    void *port_buffer;
    char midi_buffer[3];
    jack_nframes_t	last_frame_time;

    port_buffer = jack_port_get_buffer(output_port, nframes);
    if (port_buffer == NULL) {
        printf("jack_port_get_buffer failed, cannot send anything.\n");
        return;
    }

    jack_midi_clear_buffer(port_buffer);

    last_frame_time = jack_last_frame_time(jack_client);
    t = seconds_to_nframes(get_delta_time());

    res = read(input_fd, midi_buffer, sizeof(midi_buffer));
    if (res < 0 && errno == EAGAIN)
        return;
    res = jack_midi_event_write(port_buffer, t, midi_buffer, 3);

    if (res != 0) {
        printf("jack_midi_event_write failed, NOTE LOST.");
    }
}

static int
process_callback(jack_nframes_t nframes, void *notused)
{
    if (nframes <= 0) {
        printf("Process callback called with nframes = 0; bug in JACK?");
        return 0;
    }

    process_midi_output(nframes);
    return 0;
}

int
connect_to_input_port(const char *port)
{
    int ret;

    ret = jack_port_disconnect(jack_client, output_port);

    if (ret) {
        printf("Cannot disconnect MIDI port.");

        return -3;
    }

    ret = jack_connect(jack_client, jack_port_name(output_port), port);

    if (ret) {
        printf("Cannot connect to %s.", port);

        return -4;
    }

    printf("Connected to %s.", port);

    return 0;
}

static void
init_jack(void)
{
    int i, err;

    jack_client = jack_client_open("midibridge", JackNullOption, NULL);

    if (jack_client == NULL) {
        printf("Could not connect to the JACK server; run jackd first?");
        exit(EXIT_FAILURE);
    }

    err = jack_set_process_callback(jack_client, process_callback, 0);
    if (err) {
        printf("Could not register JACK process callback.");
        exit(EXIT_FAILURE);
    }

    char port_name[32];
    snprintf(port_name, sizeof(port_name), "midi_out");
    output_port = jack_port_register(jack_client, port_name, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0);

    if (output_port == NULL) {
        printf("Could not register JACK output port '%s'.", port_name);
        exit(EXIT_FAILURE);
    }

    if (jack_activate(jack_client)) {
        printf("Cannot activate JACK client.");
        exit(EXIT_FAILURE);
    }
}

static void
usage(void)
{
    fprintf(stderr, "usage: midibridge -a <input port>\n");
    exit(EXIT_FAILURE);
}

int
main(int argc, char *argv[])
{
    int		ch;
    char	*autoconnect_port_name = NULL;

    while ((ch = getopt(argc, argv, "a:")) != -1) {
        switch (ch) {
        case 'a':
            autoconnect_port_name = strdup(optarg);
            break;
        default:
            usage();
        }
    }

    input_fd = open("/dev/ttySAC1", O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK);
    if (input_fd < 0) {
        fprintf(stderr, "Cannot open serial port %s\n", strerror(errno));
        return EXIT_FAILURE;
    }

    init_serial (input_fd);

    init_jack();

    if (autoconnect_port_name) {
        if (connect_to_input_port(autoconnect_port_name)) {
            printf("Couldn't connect to '%s', exiting.", autoconnect_port_name);
            exit(EXIT_FAILURE);
        }
    }

    getc(stdin);

    return 0;
}



Такое решение также позволяет проигрывать MIDI-файлы чере�� JACK с помощью jack-smf-player, который я скомпилировал для ARM и WAV/MP3 через mplayer с поддержкой вывода звука в JACK.

Бонус

Благодаря комментарию nefelim4ag к предыдущему посту, я узнал про существование libhybris — библиотеки, которая позволяет использовать Android-драйвера в обычной Linux-системе. После некоторых танцев с бубнами, всех подробностей которых я, к сожалению, уже не помню, мне удалось завести libhybris в своей системе и пересобрать Qt 5 и PyQt5 с поддержкой OpenGL ES 2.0, EGLFS и Qt Quick 2.0. Теперь мой пользовательский интерфейс использует Qt Quick и выглядит в соответствии с последними модными тенденциями косит под Android 4.0.



Напоследок

Небольшое демо — пока только аудио, так как синтезатор сейчас находится в наполовину разобранном состоянии. Видео же будет в следующем посте, который родится скорее всего в августе, после того как приедет заказанная в Китае плата, соединяющая воедино все части синтезатора. Кроме того, следующий пост будет, скорее всего, посвящен уже не таким низкоуровневым манипуляциям с ядром, а процессу доведения до ума пользовательской части софта на PyQt5 и QtQuick и, конечно, демонстрации получившегося

Если кому-то интересно:
Список всего ПО, которое было кросс-компилировано для ARM
  • alsa-lib-1.0.27.2
  • alsa-utils-1.0.27.2
  • libaudiofile-0.3.6
  • dbus-1.8.0
  • dropbear-2014.63
  • fftw-3.3.3
  • fluidsynth-1.1.6
  • fontconfig-2.11.0
  • freetype-2.5.3
  • glib-2.34.3
  • libicu-52.1
  • jack-audio-connection-kit-0.121.3
  • jack-smf-utils-1.0
  • libffi-3.0.13
  • libgig-3.3.0
  • libgig-svn
  • libhybris
  • libsamplerate-0.1.8
  • libsndfile-1.0.25
  • linuxsampler-1.0.0
  • linuxsampler-svn
  • mplayer SVN-r36900-4.4.6
  • openssl-1.0.0l
  • psutil-1.2.1
  • pyjack-0.5.2
  • PyQt-gpl-5.2
  • pyserial-2.7
  • Python-2.7.6
  • strace-4.8
  • tslib-1.4.1


Если вам потребуется собрать что-то из этого списка и возникнут проблемы, я с удовольствием поделюсь опытом. Кроме того, многое из сказанного здесь справедливо для другой популярной платформы под названием FriendlyARM Tiny210, которая построена на базе того же самого процессора S5PV210 и, возможно, кому-то понадобится использовать с ней ядро реального времени.