ARMマイコンで作る シンセサイザー
Naomasa Matsubayashi
ARMマイコンで作る シンセサイザー
Naomasa Matsubayashi
@fadis_Twitter
Github
slidesharehttp://www.slideshare.net/fadis
https://github.com/Fadis/
slidesharehttp://www.slideshare.net/fadis
世の中には色んな
ARM がある
でかいARM
ARM Cortex-A
ARM
BeagleBoard-X15 ARM Cortex-A15
1.5GHz x2http://www.elinux.org/Beagleboard:BeagleBoard-X15
Cortex-R
ARM Cortex-M
小さいARM
LPC812 ARM Cortex-M0+
30MHzhttp://www.nxp-lpc.com/lpc_micon/cortex-m0+/lpc800/
マイクロコントローラ向けARM Cortex-Mシリーズ
Cortex-M7 Cortex-M4 Cortex-M3 Cortex-M1 Cortex-M0+ CortexM0
http://arm.com/ja/products/processors/cortex-m/index.php
ARM命令には対応していない
Thumb2(+FPU)のみ Thumb2(+FPU)のみ
Thumb2のみ Thumb2の一部だけ Thumb2の一部だけ Thumb2の一部だけ
Cortex-M7 Cortex-M4 Cortex-M3 Cortex-M1 Cortex-M0+ CortexM0
Thumb2とは: ARMプロセッサのもう1つの命令セット。 命令が可変長でARM命令セットよりマシン語が小さくなる。
パイプラインが短いCortex-M7 Cortex-M4 Cortex-M3 Cortex-M1 Cortex-M0+ CortexM0
6段 投機的実行 3段 投機的実行
3段 3段 2段 3段
投機的実行とは: 処理が必要かどうか確定する前から 実行を始めておく事。必要なかったら実行結果を捨てる。
MMUが無い
基本的にOSを動かすことは想定されていない
MMUとは: メモリ管理ユニット。仮想アドレスと 物理アドレスの変換、及びメモリ保護を行うハードウェア。
Cortex-M7/M4/M3/M0+には メモリ保護ユニットだけは備わっている
http://www.linux-arm.org/LinuxKernel/LinuxM3ただしLinuxを動かしてしまった人は居る
仮想アドレスの変換は出来ない
ARMにしては遅い
DMIPS/MHzとは: Dhrystoneベンチマークを1秒間に実行できた回数を周波数で割ったもの。大きい程クロック辺りの性能が良い。速いARMだと15以上、x86_64だと30以上。
200-400MHz 100-300MHz 75-120MHz 30-50MHz 30-50MHz
Cortex-M7 Cortex-M4 Cortex-M3 Cortex-M0+ CortexM0
2.14 1.52 1.50 1.08 0.99
DMIPS/MHzクロック
安い
マルツとは: 秋葉原の本店をはじめ全国12カ所にある電子部品屋さん。入門キットから特殊用途の石まで幅広い品揃え。
NXP LPC812の場合マルツで1個
http://www.marutsu.co.jp/pc/i/226931/
110円(税抜)
低消費電力NXP LPC812の場合最大クロック(30MHz)時
10.89mW割り込み待機時
5.28µWクロックを低めに設定すれば コイン型リチウム電池でも動く
ARM Cortex-Mで動く バイナリを作ろう
mbed
色んなCortex-Mマイコン向けの バイナリを吐ける
mbed
色んなデバイスのドライバが 最初から用意されている
mbed
mbedUSBストレージをマウントイーサネットドライバ
HTTPサーバ
DHCPでアドレスを貰ってくるHTTPサーバを80番ポートにbind
驚異のレイヤーの高さ
ダメだ もっとレイヤーを下げるんだ
arm-none-eabi-g++
ローカルのgccでビルドしよう
CMSISCortex-M向けの
ハードウェア抽象化レイヤーARM社がインターフェースを定めて
マイコンのベンダが実装する
違うベンダのマイコンに移行しても 基本的な機能の使い方は一緒で習得が楽
Cortex-MマイコンCMSIS
mbedライブラリ
mbedコミュニティ ペリフェラルライブラリ
アプリケーション
mbedはCMSISの上で動く
イーサネット ドライバ とかはここ
HTTPサーバ とかはここ
デバイスの 初期化
とかはここここまでは使いたい
mbedのソースコードは githubで公開されている
各マイコン向けのCMSISの実装も入っているhttps://github.com/mbedmicro/mbed/
CMSIS
ldscript
mbedのヤツは ARM純正コンパイラ用に書かれているldscriptとは: リンカに渡す設定ファイル。バイナリイメージ
のどこに何が置かれるかを記述する。
MEMORY {! flash (rx) : ORIGIN = 0x00000000, LENGTH = 16K! ram (rwx) : ORIGIN = 0x10000000, LENGTH = 4K!}!ENTRY(Reset_Handler)!SECTIONS {! .text : {! KEEP(*(.isr_vector))! *(.text*)! KEEP(*(.init))! KEEP(*(.fini))! *crtbegin.o(.ctors)! *crtbegin?.o(.ctors)! *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors)! *(SORT(.ctors.*))! *(.ctors)! *crtbegin.o(.dtors)! *crtbegin?.o(.dtors)! *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors)! *(SORT(.dtors.*))! *(.dtors)! *(.rodata*)! KEEP(*(.eh_frame*))! } > flash! .ARM.extab : {! *(.ARM.extab* .gnu.linkonce.armextab.*)! } > flash! __exidx_start = .;! .ARM.exidx : {! *(.ARM.exidx* .gnu.linkonce.armexidx.*)! } > flash! __exidx_end = .;! __etext__ = .;! .data : AT(__etext__) {! __data_values__ = LOADADDR(.data);! __data_begin__ = .;! *(vtable)! *(.data*)! . = ALIGN(4);!
RAMとフラッシュメモリの 開始アドレスとサイズは
普通データシートに書いてある !
今回使うLPC812の情報は 以下のURLから辿れる
http://www.nxp-lpc.com/lpc_micon/cortex-m0+/lpc800/
ldscript
MEMORY {! flash (rx) : ORIGIN = 0x00000000, LENGTH = 16K! ram (rwx) : ORIGIN = 0x10000000, LENGTH = 4K!}!ENTRY(Reset_Handler)!SECTIONS {! .text : {! KEEP(*(.isr_vector))! *(.text*)! KEEP(*(.init))! KEEP(*(.fini))! *crtbegin.o(.ctors)! *crtbegin?.o(.ctors)! *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors)! *(SORT(.ctors.*))! *(.ctors)! *crtbegin.o(.dtors)! *crtbegin?.o(.dtors)! *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors)! *(SORT(.dtors.*))! *(.dtors)! *(.rodata*)! KEEP(*(.eh_frame*))! } > flash! .ARM.extab : {! *(.ARM.extab* .gnu.linkonce.armextab.*)! } > flash! __exidx_start = .;! .ARM.exidx : {! *(.ARM.exidx* .gnu.linkonce.armexidx.*)! } > flash! __exidx_end = .;! __etext__ = .;! .data : AT(__etext__) {! __data_values__ = LOADADDR(.data);! __data_begin__ = .;! *(vtable)! *(.data*)! . = ALIGN(4);! PROVIDE_HIDDEN (__preinit_array_start = .);!
フラッシュメモリには !
割り込みベクタ マシン語
書き変わらないデータ !
を置く
ldscript
__exidx_start = .;! .ARM.exidx : {! *(.ARM.exidx* .gnu.linkonce.armexidx.*)! } > flash! __exidx_end = .;! __etext__ = .;! .data : AT(__etext__) {! __data_values__ = LOADADDR(.data);! __data_begin__ = .;! *(vtable)! *(.data*)! . = ALIGN(4);! PROVIDE_HIDDEN (__preinit_array_start = .);! KEEP(*(.preinit_array))! PROVIDE_HIDDEN (__preinit_array_end = .);! . = ALIGN(4);! PROVIDE_HIDDEN (__init_array_start = .);! KEEP(*(SORT(.init_array.*)))! KEEP(*(.init_array))! PROVIDE_HIDDEN (__init_array_end = .);! . = ALIGN(4);! PROVIDE_HIDDEN (__fini_array_start = .);! KEEP(*(SORT(.fini_array.*)))! KEEP(*(.fini_array))! PROVIDE_HIDDEN (__fini_array_end = .);! . = ALIGN(4);! __data_end__ = .;! } > ram! .bss : {! __bss_begin__ = .;! *(.bss*)! *(COMMON)! __bss_end__ = .;! } > ram! __stack_begin__ = .;! .stack_dummy : {! *(.stack)! } > ram! __stack_end__ = .;!}
RAMには !
vtable 書き変わるデータ
スタック !
を置く
ldscript
プログラムが起動してから main関数に入るまでの処理を書いたもの
起動グローバル変数の初期値を設定
グローバル変数のコンストラクタを実行ハードウェアの初期化main()にジャンプ
スタートアップルーチン
mbedのヤツは例によって ARM純正コンパイラ用に書かれている
スタートアップルーチン
スタートアップルーチンReset_Handler:! ldr r1, =__etext__! ldr r2, =__data_begin__! ldr r3, =__data_end__! ldr r0, =init_data! blx r0! ldr r0, =init_bss! blx r0! ldr r0, =run_preinit! blx r0! ldr r0, =run_init! blx r0! ldr r0, =SystemInit! blx r0! ldr r0, =main! blx r0! .pool ! .size Reset_Handler, . - Reset_Handler
スタートアップルーチンReset_Handler:! ldr r1, =__etext__! ldr r2, =__data_begin__! ldr r3, =__data_end__! ldr r0, =init_data! blx r0! ldr r0, =init_bss! blx r0! ldr r0, =run_preinit! blx r0! ldr r0, =run_init! blx r0! ldr r0, =SystemInit! blx r0! ldr r0, =main! blx r0! .pool ! .size Reset_Handler, . - Reset_Handlerアセンブリで格闘したくないので
Cで実装した初期化コードに飛ぶ
スタートアップルーチンReset_Handler:! ldr r1, =__etext__! ldr r2, =__data_begin__! ldr r3, =__data_end__! ldr r0, =init_data! blx r0! ldr r0, =init_bss! blx r0! ldr r0, =run_preinit! blx r0! ldr r0, =run_init! blx r0! ldr r0, =SystemInit! blx r0! ldr r0, =main! blx r0! .pool ! .size Reset_Handler, . - Reset_Handler
デバイスの初期化は CMSISにおまかせ
void run_preinit(void) {! int *cur = &__preinit_array_start;! for( ; cur < &__preinit_array_end; cur++ ) {! void (*f)(void) = (void *)*cur;! (*f)();! }!}!void run_init(void) {! int *cur = &__init_array_start;! for( ; cur < &__init_array_end; cur++ ) {! void (*f)(void) = (void *)*cur;! (*f)();! }!}!void init_data(void) {! unsigned char *src = &__data_values__;! unsigned char *dest = &__data_begin__;! unsigned int len = &__data_end__ - &__data_begin__;! while( len-- ) *dest++ = *src++;!}!void init_bss(void) {! unsigned char *dest = &__bss_begin__;! unsigned int len = &__bss_end__ - &__bss_begin__;! while( len-- ) *dest++ = 0;!}
void run_preinit(void) {! int *cur = &__preinit_array_start;! for( ; cur < &__preinit_array_end; cur++ ) {! void (*f)(void) = (void *)*cur;! (*f)();! }!}!void run_init(void) {! int *cur = &__init_array_start;! for( ; cur < &__init_array_end; cur++ ) {! void (*f)(void) = (void *)*cur;! (*f)();! }!}!void init_data(void) {! unsigned char *src = &__data_values__;! unsigned char *dest = &__data_begin__;! unsigned int len = &__data_end__ - &__data_begin__;! while( len-- ) *dest++ = *src++;!}!void init_bss(void) {! unsigned char *dest = &__bss_begin__;! unsigned int len = &__bss_end__ - &__bss_begin__;! while( len-- ) *dest++ = 0;!}
RAMに置いておくデータを フラッシュメモリからコピー
void run_preinit(void) {! int *cur = &__preinit_array_start;! for( ; cur < &__preinit_array_end; cur++ ) {! void (*f)(void) = (void *)*cur;! (*f)();! }!}!void run_init(void) {! int *cur = &__init_array_start;! for( ; cur < &__init_array_end; cur++ ) {! void (*f)(void) = (void *)*cur;! (*f)();! }!}!void init_data(void) {! unsigned char *src = &__data_values__;! unsigned char *dest = &__data_begin__;! unsigned int len = &__data_end__ - &__data_begin__;! while( len-- ) *dest++ = *src++;!}!void init_bss(void) {! unsigned char *dest = &__bss_begin__;! unsigned int len = &__bss_end__ - &__bss_begin__;! while( len-- ) *dest++ = 0;!}
グローバルオブジェクトの コンストラクタとかを実行
LEDを点滅させてみよう
#include "LPC8xx.h"!extern "C"!__attribute__((interrupt("IRQ")))!void SysTick_Handler(void) {! LPC_GPIO_PORT->NOT0 = ( 1 << 16 );!}!int main() {! LPC_SYSCON->SYSAHBCLKCTRL |= (1 << 6);! LPC_GPIO_PORT->DIR0 |= ( 1 << 16 );! SysTick_Config( 6000000 );! while( 1 );!}
#include "LPC8xx.h"!extern "C"!__attribute__((interrupt("IRQ")))!void SysTick_Handler(void) {! LPC_GPIO_PORT->NOT0 = ( 1 << 16 );!}!int main() {! LPC_SYSCON->SYSAHBCLKCTRL |= (1 << 6);! LPC_GPIO_PORT->DIR0 |= ( 1 << 16 );! SysTick_Config( 6000000 );! while( 1 );!}
SysTickタイマーの割り込みが発生したら
16番目のGPIOの値を反転
__attribute__((interrupt(“IRQ”)))とは: これを指定して おくとgccが割り込みエントリルーチンを付けてくれる
#include "LPC8xx.h"!extern "C"!__attribute__((interrupt("IRQ")))!void SysTick_Handler(void) {! LPC_GPIO_PORT->NOT0 = ( 1 << 16 );!}!int main() {! LPC_SYSCON->SYSAHBCLKCTRL |= (1 << 6);! LPC_GPIO_PORT->DIR0 |= ( 1 << 16 );! SysTick_Config( 6000000 );! while( 1 );!}
GPIOへのクロックを有効にして 16番目を出力モードに切り替え
SysTickが6,000,000カウントする度に SysTick割り込みを発生させるようにする
LPC812は 起動時は12MHzで動いている
SysTickが 6,000,000カウントするには
0.5秒かかる0.5秒毎にGPIOの出力が
反転するはず
http://youtu.be/9GypdG2DmIc
第2回カーネル/VM関西で紹介したネタ
100MHzのCortex-M3マイコンを使って FM音源とかを作る話
シンセサイザー入門
30MHzのCortex-M0+でも シンセサイザーが作れるのでは
音を出せるようにしよう
マイコンにDACが無いので SPIにDACをぶら下げる
3.3V
ARMマイコン LPC812
SPI DAC MCP4821
アンプIC HT82V739
PIO0_8PIO0_9PIO0_1
VDDVSS
VDDAVSS
CSSCLKSDI
VREF
VOUTA
VSS100kΩ
1μF
1μF
47μF
VDDVSS
VREF
AUDINOUTNOUTP
void init_dac() {! NVIC_DisableIRQ( (IRQn_Type)( SPI0_IRQn ) );! NVIC_SetPriority(SPI0_IRQn, 0); ! do {! NVIC_ClearPendingIRQ(SPI0_IRQn); ! } while(NVIC_GetPendingIRQ(SPI0_IRQn) != 0);! LPC_SYSCON->SYSAHBCLKCTRL |= (1 << 11);! LPC_SPI0->DLY = 4;! LPC_SPI0->DIV = 2;! LPC_SPI0->TXCTRL = (16-1)<<24 | 1<<22 | ( 1 << 20 );! LPC_SPI0->CFG = (1<<5)|(1<<4)|(1<<2);! switch_matrix::bind_SPI0_SSEL_IO( 8 ); ! switch_matrix::bind_SPI0_SCK_IO( 9 );! switch_matrix::bind_SPI0_MOSI_IO( 1 );! LPC_SPI0->INTENSET = 0x0;! NVIC_EnableIRQ( (IRQn_Type)( SPI0_IRQn ) );! LPC_SPI0->TXDAT = 2048 | ( 1 << 13 )|( 1 << 12 );!}!void set_dac( uint16_t value ) {! value &= 4095;! LPC_SPI0->TXDAT = value | ( 1 << 13 )|( 1 << 12 );!}
void init_dac() {! NVIC_DisableIRQ( (IRQn_Type)( SPI0_IRQn ) );! NVIC_SetPriority(SPI0_IRQn, 0); ! do {! NVIC_ClearPendingIRQ(SPI0_IRQn); ! } while(NVIC_GetPendingIRQ(SPI0_IRQn) != 0);! LPC_SYSCON->SYSAHBCLKCTRL |= (1 << 11);! LPC_SPI0->DLY = 4;! LPC_SPI0->DIV = 2;! LPC_SPI0->TXCTRL = (16-1)<<24 | 1<<22 | ( 1 << 20 );! LPC_SPI0->CFG = (1<<5)|(1<<4)|(1<<2);! switch_matrix::bind_SPI0_SSEL_IO( 8 ); ! switch_matrix::bind_SPI0_SCK_IO( 9 );! switch_matrix::bind_SPI0_MOSI_IO( 1 );! LPC_SPI0->INTENSET = 0x0;! NVIC_EnableIRQ( (IRQn_Type)( SPI0_IRQn ) );! LPC_SPI0->TXDAT = 2048 | ( 1 << 13 )|( 1 << 12 );!}!void set_dac( uint16_t value ) {! value &= 4095;! LPC_SPI0->TXDAT = value | ( 1 << 13 )|( 1 << 12 );!}
LPC812のSPIは 一度に16bitまで送れる
MCP4821(今回使ったDAC)は 16bit送る毎に出力が更新される
送信完了時の割り込みが要らない 送信バッファも要らない
やったね!
void init_system_clock() {! LPC_SYSCON->SYSAHBCLKDIV = 2;! LPC_SYSCON->SYSPLLCTRL = 0x24;!}!int main() {! LPC_SYSCON->SYSAHBCLKCTRL |= (1 << 6);! LPC_SYSCON->SYSAHBCLKCTRL |= (1 << 18);! LPC_GPIO_PORT->DIR0 |= ( 1 << 8 );! LPC_GPIO_PORT->DIR0 |= ( 1 << 9 );! LPC_GPIO_PORT->DIR0 |= ( 1 << 1 );! init_system_clock();! init_dac();! SysTick_Config( 1875*2 );! while( 1 );!}
CPUのクロックを30MHzにして SysTickを8kHzに設定
uint16_t step = 0;!!
extern "C"!__attribute__((interrupt("IRQ")))!void SysTick_Handler(void) {! set_dac( step << 5 );! ++step;!}
SysTick割り込みが発生したら カウンタの値をDACに送る
これでノコギリ波が出るはず
http://youtu.be/fajL7klkpNI
音色を計算しよう
ダイナミックレンジ圧縮
オーディオバッファ
4オペレータ3和音FM音源
16.16の固定小数点数を使う浮動小数演算器が無い
整数部16bit符号付き整数 小数部16bit符号無し整数
整数から固定小数点数への変換は左に16bitシフト固定小数点数から整数への変換は右に16bitシフト
ブロック単位で計算PCMのサンプルは8kHzはないと
まともな音に聞こえない音量の上げ下げやキーの状態は もっと低い頻度でしか変化しない
1ブロックブロックの中では音量やキーの変化は
無視できるものとする
周波数の低い波や線形な値の変化は 間引いて計算出来る
補間 補間 補間 補間
必要なサンプル数 計算したサンプル数 ごまかしたサンプル数
32 4
24
ブロック単位で計算
http://youtu.be/OaUmnOxzUI0
計算を速くしよう
32bitの整数同士の乗算の結果は 最大で64bitの整数になる
FFFFFFFF!FFFFFFFF
32bit32bit64bitFFFFFFFE00000001
*
乗算命令mulは 32bitと32bitを掛けて 結果の下32bitを得る
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0204ij/Cihihggj.html
FFFF.FFFF!FFFF.FFFF
FFFFFFFE.00000001*
16.16の固定小数点数同士の 乗算の結果は
32.32の固定小数点数になる
結果を16.16で得るには 16bit目から47bit目までが欲しい
FFFF.FFFF!FFFF.FFFF
FFFFFFFE.00000001*
結果を16.16で得るには 16bit目から47bit目までが欲しい
乗算結果の32bit目以上の値が必要
32bitと32bitを掛けて 結果を64bitで得る命令
mull
inline constexpr self_type operator*(! const self_type &y!) const {! return self_type(! (! static_cast< double_type >( base ) *! static_cast< double_type >( y.base )! ) >> Shift::value, raw()! );!}
mullが使われることを期待して 乗算をこんな風に実装していた
…!00001374 <__aeabi_lmul>:!1374: 469c mov ip, r3!1376: 0403 lsls r3, r0, #16!1378: b5f0 push {r4, r5, r6, r7, lr}!137a: 0c1b lsrs r3, r3, #16!137c: 0417 lsls r7, r2, #16!137e: 0c3f lsrs r7, r7, #16!1380: 0c15 lsrs r5, r2, #16!1382: 1c1e adds r6, r3, #0!1384: 1c04 adds r4, r0, #0!1386: 0c00 lsrs r0, r0, #16!1388: 437e muls r6, r7!138a: 436b muls r3, r5!138c: 4347 muls r7, r0!138e: 4345 muls r5, r0!1390: 18fb adds r3, r7, r3!1392: 0c30 lsrs r0, r6, #16!1394: 1818 adds r0, r3, r0!1396: 4287 cmp r7, r0!1398: d902 bls.n 13a0 <__aeabi_lmul+0x2c>!139a: 2380 movs r3, #128 ; 0x80!139c: 025b lsls r3, r3, #9!
現実
1392: 0c30 lsrs r0, r6, #16!1394: 1818 adds r0, r3, r0!1396: 4287 cmp r7, r0!1398: d902 bls.n 13a0 <__aeabi_lmul+0x2c>!139a: 2380 movs r3, #128 ; 0x80!139c: 025b lsls r3, r3, #9!139e: 18ed adds r5, r5, r3!13a0: 0c03 lsrs r3, r0, #16!13a2: 18ed adds r5, r5, r3!13a4: 4663 mov r3, ip!13a6: 435c muls r4, r3!13a8: 434a muls r2, r1!13aa: 0436 lsls r6, r6, #16!13ac: 0c36 lsrs r6, r6, #16!13ae: 18a1 adds r1, r4, r2!13b0: 0400 lsls r0, r0, #16!13b2: 1980 adds r0, r0, r6!13b4: 1949 adds r1, r1, r5!13b6: bdf0 pop {r4, r5, r6, r7, pc}!…
現実
mullは使われていない
ARM命令には対応していない
Thumb2(+FPU)のみ Thumb2(+FPU)のみ
Thumb2のみ Thumb2の一部だけ Thumb2の一部だけ Thumb2の一部だけ
Cortex-M7 Cortex-M4 Cortex-M3 Cortex-M1 Cortex-M0+ CortexM0
Thumb2とは: ARMプロセッサのもう1つの命令セット。 命令が可変長でARM命令セットよりマシン語が小さくなる。
ARM命令には対応していない
Thumb2(+FPU)のみ Thumb2(+FPU)のみ
Thumb2のみ Thumb2の一部だけ Thumb2の一部だけ Thumb2の一部だけ
Cortex-M7 Cortex-M4 Cortex-M3 Cortex-M1 Cortex-M0+ CortexM0
Thumb2とは: ARMプロセッサのもう1つの命令セット。 命令が可変長でARM命令セットよりマシン語が小さくなる。
一部とは
mov movw movt add adc adr sub sbc rsb mul mla mls smull umull smlal umlal sdiv udiv ssat usat cmp cmn and eor orr orn bic mvn tst teq lsl lsr asr ror rrx clz ldr ldrh ldrb ldrsh ldrsb ldrt ldrht ldrbt ldrsht ldrsbt ldrd ldm str strh strb strsh strsb strt strht strbt strsht strsbt strd stm push pop ldrex ldrexh ldrexb strex strexh strexb clrex b bl bx blx cbz cbzn tbb tbh svc it cpsid cpsie mrs msr bkpt sxth sxtb uxth uxtb ubfx sbfx bfc bfi rev rev16 revsh rbit sev wef wfi nop isb dmb dsb
M7/M4/M3移動 加算 減算 乗算 除算
飽和演算 比較
論理演算 シフト ロード ストア
スタック操作 セマフォ 分岐
状態変更 型の拡張
ビットフィールド ビット列反転
ヒント バリア ARMv7m
mov movw movt add adc adr sub sbc rsb mul mla mls smull umull smlal umlal sdiv udiv ssat usat cmp cmn and eor orr orn bic mvn tst teq lsl lsr asr ror rrx clz ldr ldrh ldrb ldrsh ldrsb ldrt ldrht ldrbt ldrsht ldrsbt ldrd ldm str strh strb strsh strsb strt strht strbt strsht strsbt strd stm push pop ldrex ldrexh ldrexb strex strexh strexb clrex b bl bx blx cbz cbzn tbb tbh svc it cpsid cpsie mrs msr bkpt sxth sxtb uxth uxtb ubfx sbfx bfc bfi rev rev16 revsh rbit sev wef wfi yeild nop isb dmb dsb
移動 加算 減算 乗算 除算
飽和演算 比較
論理演算 シフト ロード ストア
スタック操作 セマフォ 分岐
状態変更 型の拡張
ビットフィールド ビット列反転
ヒント バリア
M1/M0+/M0
ARMv6m
M1/M0+/M0mov movw movt add adc adr sub sbc rsb mul mla mls smull umull smlal umlal sdiv udiv ssat usat cmp cmn and eor orr orn bic mvn tst teq lsl lsr asr ror rrx clz ldr ldrh ldrb ldrsh ldrsb ldrt ldrht ldrbt ldrsht ldrsbt ldrd ldm str strh strb strsh strsb strt strht strbt strsht strsbt strd stm push pop ldrex ldrexh ldrexb strex strexh strexb clrex b bl bx blx cbz cbzn tbb tbh svc it cpsid cpsie mrs msr bkpt sxth sxtb uxth uxtb ubfx sbfx bfc bfi rev rev16 revsh rbit sev wef wfi yeild nop isb dmb dsb
移動 加算 減算 乗算 除算
飽和演算 比較
論理演算 シフト ロード ストア
スタック操作 セマフォ 分岐
状態変更 型の拡張
ビットフィールド ビット列反転
ヒント バリア ARMv6m
mullは犠牲になったのだ
mul mla mls smull umull smlal umlal
mull無しでどうやって 結果が64bitの乗算をするか
a b
a*d b*d* c d
a*c b*c ((a*c)<<32)+((a*d+b*c)<<16)+b*d
abcdはそれぞれ16bit
筆算の要領で16bitづつ 計算していく
a b
a*d b*d* c d
a*c b*c ((a*c)<<32)+((a*d+b*c)<<16)+b*d
1サンプル毎にこんな計算を 何度もしていたら間に合わない
64bitの乗算を 回避せよ
-1<結果<1が明らかな場合
32bit目から47bit目は明らかなので 16bit目から31bit目までが欲しい
この計算はmul命令でできる
0000.FFFF!0000.FFFF
00000000.FFFE0001*
-1<結果<1が明らかな場合実はFM音源の計算中に現れる 乗算の多くにこれが成り立つ
ADSRFM変調
音量の時間変化 (最大1)
両辺共に1の場合のみ 注意すればmulでOK
実はFM音源の計算中に現れる 乗算の殆どにこれが成り立つ
ミキサーi1*l1+i2*l2+i3*l3+i4*l4
i1 i2 i3 i4
l1 l2 l3 l4
入力はFMオペレータの出力なので最大1 重みは全部あわせて最大1
両辺共に1の場合のみ注意すればmulでOK
-1<結果<1が明らかな場合
16.16固定小数点数と整数の乗算FFFF.FFFF!
FFFFFFFEFFFF.0001*
結果を16.16で得るには 0bit目から31bit目までが欲しいこの計算はmul命令でできる
0 311 2 … …
ブロックの中で何番目のサンプルかは 整数で表される
サンプルのインデックスと時刻の関係は線形
t = 0*fs + ss t= 31*fs + ss
時刻行列
時刻行列
サンプルのインデックスと時刻の関係は線形
2x2の行列で表現出来る
tはサンプルの時刻 iはサンプルのインデックス
fsはPCMの周波数 ssはこのブロックの再生が始まる時刻
トーン時間実際の時刻に対して
音の周波数倍で進行するクロック
トーン時間の小数部分だけを取り出すと 意図した周波数と位相のノコギリ波になる
トーン行列時刻とトーンクロックの関係は線形なので
2x2の行列で表現できる
この乗算は小数同士かつ 結果の絶対値が1以上になる
tはサンプルの時刻、ttはサンプルのトーン時刻 ftはノートの周波数、stはノートの位相
この部分が事前に求まっていれば インデックスは整数なので
トーンクロックは高速な乗算で求まる
トーン行列時刻とトーンクロックの関係は線形なので
2x2の行列で表現できる
ブロックの開始時刻がブロック毎に変わるので ブロック毎に
時刻行列とトーン行列の積を求める
行列の積の計算には遅い乗算が入るが ブロックにつき1回の遅い乗算で
サンプル単位の遅い乗算を回避出来る
結果
http://youtu.be/DDLSEirr8_A
まとめ
小さなARMには 大きな魅力が秘められている
Thank you for listening.