新的生活的老合成器。第2部分

关于老烧,合成器,我试图通过完全替代铁呼吸新的生活故事,负责对软件合成器生成的声音,内置了迷你电脑EmbedSky E8使用Linux在船上。这是常有的情况下,该制品的第一和第二部分的出版物之间花更长的时间比预计的,但尽管如此,继续进行。





在选择过程中的早期描述的硬件平台,一个新的“大脑”合成与解决方案的技术特征的描述,简单地组装照明必要的库,并在过程中遇到的问题的过程。现在,作为铁,我们看到如何构建一个合成器键盘矩阵,然后会有更多的细节致力于软件。


键盘矩阵 H5>合成器键盘矩阵是非常相似的普通键盘矩阵,微控制器许多球迷可能已经连接到他们的Arduino。两个从一个设置在其上合成器(在最便宜的机型)每个键(在本体模式)交换机。使用两个相邻的开关,其中一个是通过按压一点点早于其他闭合时,微控制器可确定该条件的力,或者更确切地说,在该键随后向声音压下的速度被再现的音量。它看起来像这样:



在电路板的背面放置二极管防止“假”读书按键同时按下几个键。这里有一个片段概念键盘矩阵,这说明两个开关和二极管连接到他们:


扫描矩阵的微控制器系列拉动柱(示标记为N)的电源,并检查在线路电平(管脚标为B)。如果一行的水平将是高的,从而对应于当前有效组合“列线”键被按下。该图显示键盘的一部分 - 它只有76项(13行和6列×2,提供总共156个可能的变种扫描矩阵25和微控制器引脚使用)。扫描整个键盘进行了几十每秒次,不知道的是执行。

在我的合成器的微控制器负责扫描键盘原本是一个8位的一次性可编程微控制器日立HD63B05V0,主频为8 MHz并具有4 KB ROM和192字节的RAM内存。不幸的是,这个控制器事件与营养,在第一篇文章的开头所述后转身不起作用。不过,幸运的是,他几乎引脚尽我最大的控制器ATmega162的,我照做了,取而代之的是切割和焊接只有2轨道电路板上,其中一个兼容 - 结论RESET,竟然是完全错误的地方,像HD63B05V0。

由于这种包容性的控制器没让我拿内建UART(因为他也是在其他调查结果),则显示有关我用键盘输入的这个双面(只写)串行端口的实现。此外,微控制器已经坐满启动 TinySafeBoot ,还采用软件实现串口的今后可能的固件更新。由于为所有高层次软件合成的快速发展的语言,我选择Python的+ QT5,然后TinySafeBoot我也写为Python模块,使您可以读取和写入固件到微控制器AVR。本人AVR单片机连接于串行端口UART1板EmbedSky E8和由电压3.3V供电,以避免需要进行电平转换。

的固件AVR B> <前>&LT的源代码;类=&QUOT; CPP&QUOT;&GT;的#include&LT; AVR / io.h&GT; #包括LT&; AVR / interrupt.h&GT; #包括LT&; UTIL / delay.h&GT; #包括LT&;&string.h中GT;的#include&QUOT; dbg_putchar.h&QUOT; #定义MIDI_BASE 18#定义ZERO_BASE 28#定义KEYS_COUNT 76#定义HIZ(端口,DIR)做{\(DIR)= 0; \(端口)= 0; \}在(0)的#define alow(端口,DIR)做{\(DIR)= 0xFF的; \(端口)= 0; \}在(0)uint8_t有键[KEYS_COUNT] / *获取一排状态,其指数*开始从1到13 * / uint8_t的的getRow(uint8_t有IDX){如果(IDX&LT; = 8){回报(PINC及(1&LT;&LT;(8 - IDX) )); }否则如果(IDX&GT; = 9&安培;&安培; idx的&LT; = 11){返回(松树及(1&LT;≤(11 - IDX))); }否则如果(IDX == 12){返回(PINA及(1&LT;&LT; PIN6)); }否则如果(IDX == 13){返回(PINA及(1&LT;&LT; PIN4)); }返回0; }内嵌无效activateColumn1(uint8_t有IDX){PORTD = 0×00 |(1&LT;&LT;(8 - IDX)); PORTB = 0×00; }空隙activateColumn2(uint8_t有IDX){如果(IDX&LT; = 3){PORTB = 0×00 |(1&LT;≤(IDX + 4)); PORTD = 0×00; }否则,如果(IDX == 4){PORTB = 0×00 |(1&LT;&LT; PIN4); PORTD = 0×00; }否则如果(IDX == 5 || IDX == 6){PORTD = 0×00 |(1&LT;≤(IDX - 5)); PORTB = 0×00; }}内嵌无效deactivateColumns(无效){PORTD = 0×00; PORTB = 0×00; }内嵌无效initPorts(无效){HIZ(PORTA,DDRA); HIZ(PORTC,DDRC); HIZ(PORTE,DDRE); PORTB = 0×00; DDRB = 0xFE的; DDRD = 0xFF的; }虚空resetRows(无效){/ *输出低电平* / alow(PORTC,DDRC); alow(PORTE,DDRE); / *请勿触摸PA7和放大器; PA5 * / DDRA | = 0x5F的; PORTA和放大器; =〜0x5F的; _delay_us(10); / *返回浮动输入* / HIZ(PORTC,DDRC); HIZ(PORTE,DDRE); DDRA与放大器; =〜0x5F的; } / *基MIDI音符编号为25:C#0 * / INT主要(无效){uint8_t的行,列,层; uint8_t有笔记,抵消; initPorts(); memset的(键,0,的sizeof(键)); dbg_tx_init(); dbg_putchar('O'); dbg_putchar(“K”);而(1){对于(层= 0;&层2;一层++){对于(COL = 1; COL&LT = 6;山口++){如果(层!)activateColumn1(COL);否则activateColumn2(COL);对于(行= 1;行&LT; = 13;排++){注= 6 *行+山坳+ MIDI_BASE;偏移量=注 - ZERO_BASE; (!层),如果(的getRow(行)){如果{/ *增加速度计数器* /如果(键[失调] LT; 254&安培;&安培;!(键[失调]放; 0x80的))键[失调] + +; }其他{如果(!(键[失调]放; 0x80的)){/ *生成音通事件* / dbg_putchar(0x90处); dbg_putchar(注); / * Dbg_putchar(键[失调]); * / dbg_putchar(0x7F的); / *停止计数* /键[失调] | = 0x80的; }}}其他{如果(层)继续;如果(键[失调]放; 0x80的){/ *产生音符关事件* / dbg_putchar(0x90处); dbg_putchar(注); dbg_putchar(0×00); / *复位键状态* /键[抵销] = 0×00; }}} DeactivateColumns(); resetRows(); }}}返回0; }&中尉; /码&GT; PRE>
在Python模块TinySafeBoot B> <前>&LT;代码级=&QUOT;蟒蛇&QUOT;&GT;进口系列进口binascii进口结构进口intelhex进口SYS类TSB(对象):'!'CONFIRM = REQUEST ='?“高清__init __(个体经营,端口):self.port = serial.Serial(端口,波特率= 9600,超时= 1)self.flashsz = 0闪避检查(个体经营):如果不是self.flashsz:提高异常(&QUOT;未激活&QUOT ;)高清激活(个体经营):self.port.write(&QUOT; @&QUOT;)(self.tsb,self.version,self.status,self.sign,self.pagesz,self.flashsz,self.eepsz )= \ struct.unpack(QUOT;&LT; 3sHB3sBHH&QUOT;,self.port.read(14))self.port.read(2)self.pagesz * = 2 self.flashsz * = 2 self.eepsz + = 1断言(self.port.read()== self.CONFIRM)高清rflash(自我,进步=无,大小= 0):self.check()self.port.write(&QUOT; F&QUOT;)self.addr = 0的自.flash =&QUOT;&QUOT;大小= self.flashsz如果没有其他的大小尺寸,同时self.addr&LT;大小:如果进展不无!进度(QUOT;读&QUOT;,self.addr,大小)self.port.write(self.CONFIRM)页= self.port.read(self.pagesz)如果len(页)= self.pagesz:引发异常(&QUOT;接收页面太短:%D&QUOT;%LEN(页))self.addr + = LEN(页)self.flash + =页面返回self.flash.rstrip('\ XFF“)高清wflash(个体经营,数据,进度=无):如果len(数据)%self.pagesz = 0:数据=数据+&QUOT; \ XFF&QUOT; *(Self.pagesz - (LEN(数据)%self.pagesz))断言(LEN(数据)%self.pagesz == 0)self.check()self.port.write(&QUOT; F&QUOT;)self.addr = 0,断言(self.port.read()== self.REQUEST)而self.addr&LT; LEN(数据):若进展不无:前进(&QUOT;写&QUOT;,self.addr,LEN(数据))self.port.write(self.CONFIRM)self.port.write(数据[self.addr:自我.addr + self.pagesz])self.addr + = self.pagesz断言(self.port.read()== self.REQUEST)self.port.write(self.REQUEST)返回self.port.read()= = self.CONFIRM高清的vFlash(个体经营,数据,进度=无):FW = self.rflash(进度,LEN(数据))返回FW ==数据DEF信息(个体经营):打印&QUOT;微小的安全引导程序:%S&QUOT; %Self.tsb打印&QUOT;页面大​​小:%D&QUOT; %Self.pagesz打印&QUOT;闪存大小:%D&QUOT; %Self.flashsz打印&QUOT; EEPROM大小:%D&QUOT; %Self.eepsz如果__name__ ==&QUOT; __ __主要与QUOT;:进口argparse高清进步(OP,地址,总数):sys.stdout.write函数(&QUOT; \ r%s地址:$ 0.4倍%/ $%&0.4倍QUOT;% (OP,地址,总))sys.stdout.flush()语法分析器= argparse.ArgumentParser()parser.add_argument(&QUOT;文件名&QUOT;,帮助=&QUOT;而在Intel HEX格式&QUOT固件文件;)parser.add_argument(&QUOT; - -device&QUOT;,帮助=&QUOT;串口用于编程和QUOT;,默认=&QUOT;为/ dev / ttyUSB0&QUOT;)的args = parser.parse_args()TSB = TSB(args.device)tsb.activate()tsb.info( )FW = intelhex.IntelHex(args.filename)断言(tsb.wflash(fw.tobinstr(),进度))断言(tsb.vflash(fw.tobinstr(),进度))的打印和QUOT; \ NOK \ñ&QUOT;和中尉; /码&GT; PRE>
作为一个程序员的AVR我第一次使用基于MSP430的Launchpad 中的程序员,其中我可以在几件,然后这种自制的奇迹(正常工作,顺便说一句),让位给来自中国的程序员TL866CS MiniPro。新编程的感觉是非常积极的。

很详细的关于设备的键盘合成器,以及如何进行扫描,包括通过AVR单片机的接口为外部存储器芯片扫描一个非常原始的方式告诉在线<一href="http://www.openmusiclabs.com/learning/digital/input-matrix-scanning/xmem/">OpenMusicLabs


烹饪内核的实时抢占 H5>支持天色获得更多的控制权,调度和播放音频时减少延迟(潜伏期),部分为了好玩,我决定用一个内核的的патчемPREEPMT RT 时,主要功能,它的是,中断也变得“过程”,可推出通过基于优先级的调度器中的一个。原来的核心,由三星S5PV210处理器提供,在此基础上,系统构建,基于内核版本3.0.8,显然是从Android系统。无补丁RT_PREEMPT,可在项目网站,专为内核版本(3.0.8)的,不想没​​有冲突的根源上进行叠加,但最终,解决手工管理到应用补丁版本3.0.8-RT23所有冲突。

由于这样的事实,在修改,使得核也被修改的基本结构如螺旋锁和互斥,它不再与编译对象的形式提供的链接文件专有驱动一些外围设备:摄像机,电容式触摸屏控制器,以及,最糟糕的是音频编解​​码器。回到那里去以后,但现在禁用它们,并尝试以新鲜收获的内核实时运行的第一次董事会,并收到即时...内核崩溃。他来到即使启动调试器的kgdb前(其中,因为它横空出世,仍然是行不通的,即使开始),所以它不得不将调试的printf-S文件中的
 <代码>的init / main.c中代码> 预>,功能<预类=“prettyprint”> <代码>的start_kernel 代码>  PRE>,以确定这里的一切分崩离析的地方。因此,很显然,过去的事情了内核管理做的是调用函数<预类=“prettyprint”> <代码> hrtimers_init()代码>  PRE>,初始化高分辨率定时器和中断。此代码依赖于平台,并在我们的例子是在<预类=“prettyprint”> <代码>弓/ ARM /开发平台 -  S5P /小时时,rtc.c 代码>  PRE>。正如我刚才所说,用一个补丁PREEMPT RT内核的主要特点之一是,中断流量。这是可能的,在通常的内核,但PREEMPT RT默认内核试图使这些几乎全部中断。代码的进一步分析表明,这些流被使用的任务kthreadd_task,这是在功能的
 <代码>的结束初始化的start_kernel 代码>  PRE>  - 远远晚于初始化定时器。的减少,是因为还到一个事实,即一个定时器中断已试图使芯流,同时仍kthreadd_task NULL。我们解决这个问题通过设置单独的中断,你不应该做的流媒体,在任何情况下,它被添加到定时器中断标志在<预类=“prettyprint”> <代码> HR-时间rtc.c标志IRQF_NO_THREAD 代码> 预>。耶!内核启动,但是这仅仅是个开始...... 

正如我上面提到的,副作用之一是负责音频输入/输出模块,再与新的内核相连。这部分是因为内核的支持PREEMPT RT(版本3.0.8),只读存储器管理SLAB,并初步模块是与包容的机制竹节,这是不支持的新内核编译。不过,我很幸运地工作在卡巴斯基实验室,和我说服我的同事反编译驱动程序文件,并使用六角射线反编译为ARM编解码器,然后设法几乎完全重新创建的源代码。几乎 - 因为作为“新”的音频接口驱动的结果开始然而,应当确定,由于低级初始化过程中一些差异寄存器芯片WM8960播放声音用的工件。有一段时间,我试图调整你的驱动程序,但后来选择了一个更简单的方法 - 我给技术支持的中国企业EmbedSky技术,在那里买一个小型的电脑,与PREEMPT_RT补丁,并要求他们编译为我和发送音频驱动程序文件。这些家伙反应迅速,并寄给我的声音终于获得预期的文件。

顺便说一句,当我正忙着与他的反编译司机,我发现,调试器的kgdb不与我的工作,也不能与原来的内核。事实证明,对于需要他的工作,以支持同步(查询)查询串行端口,这是没有的串口设备驱动程序的三星(<预类=“prettyprint”> <代码>驱动器/ TTY /串口/ samsung.c 代码> 预>)。我添加所需的驱动程序支持的基础上, 这个补丁,然后获得一个调试器。

进一步挖掘。新的内核的另一个副作用是极其低,具有大的“滞后”时,所有四个串行端口患的SoC S5PV210,其结果是不是该终端的正常操作,通过一个串行端口,和不工作,因为它应该重写控制器的AVR的速度,询问键盘合成器。我想明白其中的道理,但我注意到的是,在终端的每个字符导致了数百万串口中断的产生 - 内核似乎并没有放慢来处理它们。最后,我的事实,由上述标志IRQF_NO_THREAD做所有中断串口非流解决了这个问题。这一决定并没有变成很漂亮,因为除了司机,三星不得不做出更改文件<预类=“prettyprint”> <代码> serial_core.c 代码> PRE>和<预类=“prettyprint”> <代码> serial_core.h 代码> PRE>,一般影响所有串行端口。因为与PREEMPT RT内核不能使用谁NO_THREAD spin_lock_t驱动程序,并且应使用raw_spinlock_t。

在原核,其中,因为我上面所说的,支持的512 MB的RAM的各种外围设备,例如摄像机,硬件编解码器,HDMI等的,是可利用的仅约390 MB,而其余的是保留给上述装置始终(即使在配置内核,他们已被禁用的过程)。非常浪费,特别是考虑到额外的120 MB RAM合成甚至没有防止样品的存储。记忆被保留在文件中的
 <代码>弓/ ARM /马赫S5PV210 /马赫tq210.c 代码>  PRE>,这是收集有关设备的配置和特定机器的所有信息的要点(在我们的例子 - 卡)。谈到内存的选择 - 函数调用<预类=“prettyprint”> <代码> s5p_reserve_bootmem 代码>  PRE>,我们得到120 MB的额外内存为合成

最后的更改,以在内核中,最小尺寸为音频缓冲器,这是等于原始一页存储器,以44100赫的采样率,2通道16比特,得到大约20毫秒 - 太多。此值已经在文件中被修改<预类=“prettyprint”> <代码>声音/ SOC /三星/ dma.c 代码> 预> 128字节,则最小缓冲区大小减少到几毫秒不影响稳定和性能。

内核源PREEMPT RT,并在GitHub上
所有修改
你如何与AVR LinuxSampler通讯 H5> AVR连接到微型计算机的串口,并在UART MIDI就绪消息软件吐奶。为了保存自己不必编写驱动程序,因此决定作为传输的所有音频和MIDI数据服务器JACK使用。小prilozhenitse对C连接到串行端口,将自己注册为JACK MIDI-OUT,并开始路线有所有接收到的MIDI-职位,JACK已经提供他们LinuxSampler。廉价和开朗。

作为串行端口和杰克之间的桥梁的源代码的应用程序 B> <前>&LT;代码级=&QUOT; CPP&QUOT;&GT;的#include&LT; stdio.h中&GT; #包括LT&;&stdlib.h中GT; #包括LT&; SYS / types.h中&GT; #包括LT&; SYS / time.h中&GT; #包括LT&;&unistd.h中GT; #包括LT&;&ASSERT.H GT; #包括LT&;&string.h中GT; #包括LT&; sysexits.h&GT; #包括LT&;&errno.h中GT; #包括LT&;&signal.h中GT; #包括LT&; fcntl.h&GT; #包括LT&; termios.h&GT; #包括LT&;插孔/ jack.h&GT; #包括LT&;插孔/ midiport.h&GT; #定义UART_SPEED B9600 jack_port_t * output_port; jack_client_t * jack_client = NULL; INT input_fd;无效init_serial(INT FD){结构termios的termios的;中期业绩; RES = tcgetattr(FD,与放,termios的);如果(RES&LT; 0){fprintf中(错误,&QUOT; termios的得到错误:%s \ñ&QUOT;,字符串错误(错误));退出(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的);如果(RES&LT; 0){fprintf中(错误,&QUOT; termios的设置错误:%s \ñ&QUOT;,字符串错误(错误));退出(EXIT_FAILURE); }}双GET_TIME(无效){双秒; INT RET; timeval结构电视; RET =函数gettimeofday(安培;电视,NULL);如果(RET){PERROR(&QUOT; gettimeofday的&QUOT;);退出(EX_OSERR); }秒= tv.tv_sec + tv.tv_usec / 1000000.0;返回秒; }双get_delta_time(无效){静态双重以前= -1.0;现在翻一番;双三角翼;现在= GET_TIME();如果(以前== -1.0){以前=现在;返回0; }三角洲=现在 - 以前;此前=现在;断言(增量&GT = 0.0);返回三角洲; }静态双nframes_to_ms(jack_nframes_t将为nframes){jack_nframes_t SR; SR = jack_get_sample_rate(jack_client);断言(SR大于0);返回(将为nframes * 1000.0)/(双)SR; }静态双nframes_to_seconds(jack_nframes_t将为nframes){返回nframes_to_ms(将为nframes)/ 1000.0; }静态jack_nframes_t ms_to_nframes(双毫秒){jack_nframes_t SR; SR = jack_get_sample_rate(jack_client);断言(SR大于0);返回((双)SR *毫秒)/ 1000.0; }静态jack_nframes_t seconds_to_nframes(双秒){返回ms_to_nframes(秒* 1000.0); }静态无效process_midi_output(jack_nframes_t将为nframes){INT吨,水库;无效* port_buffer;炭midi_buffer [3]; jack_nframes_t last_frame_time; port_buffer = jack_port_get_buffer(output_port,将为nframes);如果(port_buffer == NULL){printf的(&QUOT; jack_port_get_buffer失败,无法发送任何\ñ&QUOT;);返回; } Jack_midi_clear_buffer(port_buffer); last_frame_time = jack_last_frame_time(jack_client); T = seconds_to_nframes(get_delta_time()); RES =读取(input_fd,midi_buffer,的sizeof(midi_buffer));






标签

另请参见

新&值得注意