module main author unknown version 1 0 description '' variables buffer_tmp host_ip host_port thread_manager mqtt_topic spec ' ' 'main' 'main' spec ' ' 'mqtt_handle_msg' 'mqtt_handle_msg' spec ' ' 'send_position' 'send_position' spec ' ' 'move_to_landmark' 'move_to_landmark _' 'num' 10 spec ' ' 'move_to_road' 'move_to_road _ _' 'auto auto' '10' '10' spec ' ' 'move_to_target' 'move_to_target _ _ _' 'num auto auto' 10 '10' '10' spec ' ' '右电机 速度(-255~255)' '右电机 速度(-100~100) _' 'num' 10 spec ' ' '小车右转 速度' '小马右转 速度 _' 'auto' '10' spec ' ' '左电机 方向' '左电机 速度(-100~100) _' 'num' 0 to main { mqtt_topic = 'AABB' wifiConnect 'AGILE-3D2' '99998888' repeatUntil ('MQTT connected') { 'MQTT connect to' 'broker.emqx.io' } 'MQTT sub' ('[data:join]' '/' mqtt_topic '/control') sendBroadcast 'start_receive_message' sendBroadcast 'start_mqtt' } to move_to_landmark idx { if (idx == ('[data:toString]' '1')) { move_to_target 150 125 90 } (idx == ('[data:toString]' '2')) { move_to_target 150 125 270 } (idx == ('[data:toString]' '3')) { move_to_target 150 275 90 } (idx == ('[data:toString]' '4')) { move_to_target 150 275 270 } else { } 'CoCube move by step' 'cocube;forward' 40 20 } to move_to_road x y { local 'angle' ('[misc:atan2]' (('CoCube position_Y') - 200) (('CoCube position_X') - 150)) if (and (angle < 4500) (angle > -4500)) { 'CoCube move to' ('CoCube position_X') 350 40 'CoCube move to' 150 350 40 } (and (angle < 13500) (angle > 4500)) { 'CoCube move to' 275 ('CoCube position_Y') 40 'CoCube move to' 275 200 40 } (and (angle < -4500) (angle > -13500)) { 'CoCube move to' 25 ('CoCube position_Y') 40 'CoCube move to' 25 200 40 } else { 'CoCube move to' ('CoCube position_X') 50 40 'CoCube move to' 150 50 40 } if (((absoluteValue (('CoCube position_X') - x)) + (absoluteValue (('CoCube position_Y') - y))) > 165) { 'CoCube move to' 150 200 40 } 'CoCube move to' x y 40 } to move_to_target x y direction { if (and (('CoCube position_X') >= 137) (('CoCube position_X') <= 163)) { 'CoCube move to' x y 40 } (and (('CoCube position_Y') >= 187) (('CoCube position_Y') <= 213)) { 'CoCube move to' 150 200 40 'CoCube move to' x y 40 } else { move_to_road x y } 'CoCube rotate to angle' direction 30 } to mqtt_handle_msg { local 'Msg' buffer_tmp Msg = ('[data:split]' Msg ',') local 'cmd_name' (at 1 Msg) local 'cmd_args' ('[data:copyFromTo]' Msg 2) sayIt cmd_name callCustomCommand cmd_name cmd_args } to send_position { if ('CoCube on the mat') {'MQTT pub' ('[data:join]' '/' mqtt_topic '/position') ('[data:join]' ('CoCube position_X') ',' ('CoCube position_Y') ',' ('CoCube direction'))} } to '右电机 速度(-255~255)' 速度 { digitalWriteOp 17 false local 'pwm' 0 if (速度 > 100) { 速度 = 100 } if (速度 < -100) { 速度 = -100 } if (速度 >= 0) { digitalWriteOp 16 true pwm = ('[misc:rescale]' 速度 0 100 1023 0) } else { digitalWriteOp 16 false pwm = ('[misc:rescale]' 速度 0 -100 0 1023) } analogWriteOp 33 pwm } to '小车右转 速度' 速度 { '左电机 方向' (0 - 速度) '右电机 速度(-255~255)' 速度 } to '左电机 方向' 速度 { digitalWriteOp 27 false local 'pwm' 0 if (速度 > 100) { 速度 = 100 } if (速度 < -100) { 速度 = -100 } if (速度 >= 0) { digitalWriteOp 26 true pwm = ('[misc:rescale]' 速度 0 100 1023 0) } else { digitalWriteOp 26 false pwm = ('[misc:rescale]' 速度 0 -100 0 1023) } analogWriteOp 33 pwm } script 55 49 { whenBroadcastReceived 'start_receive_message' forever { buffer_tmp = ('MQTT event payload' ('[net:MQTTLastEvent]')) if ((size buffer_tmp) > 1) { local 'Msg' buffer_tmp Msg = ('[data:split]' Msg ',') local 'cmd_name' (at 1 Msg) local 'cmd_args' ('[data:copyFromTo]' Msg 2) sayIt buffer_tmp sayIt cmd_name } if (cmd_name == 'CoCube move for msecs') { sendBroadcast 'up' } (cmd_name == 'CoCube wheels stop') { sendBroadcast 'stop' } else { } } } script 559 72 { whenBroadcastReceived 'up' sendBroadcast 'birthday' analogWriteOp 18 800 OLEDclear OLEDwrite 'Forward' 0 0 false 2 waitMillis 2000 analogWriteOp 18 0 } script 1418 73 { whenStarted 'MIDI_set MIDI pin' 26 '[ble:radioSetGroup]' 100 OLEDInit_I2C 'OLED_0.96in' '3C' 0 false OLEDwrite 'AI Horse ' 0 0 false 2 digitalWriteOp 18 false digitalWriteOp 19 false main } script 981 76 { whenBroadcastReceived 'start_mqtt' forever { if (not ('MQTT connected')) { 'MQTT connect to' 'broker.emqx.io' 'MQTT sub' ('[data:join]' '/' mqtt_topic '/control') sendBroadcast 'start_send_position' sendBroadcast 'start_receive_message' } waitMillis 1000 } } script 567 324 { whenBroadcastReceived 'start_send_position' local 'var' 0 sayIt 123 forever { send_position waitMillis 500 } } script 1041 400 { whenCondition (bleRadio_messageReceived) if ((bleRadio_receivedString) == 'up') { sendBroadcast 'up' } ((bleRadio_receivedString) == 'down') { sendBroadcast 'down' } ((bleRadio_receivedString) == 'stop') { sendBroadcast 'stop' } ((bleRadio_receivedString) == 1) { sendBroadcast '1' } ((bleRadio_receivedString) == 2) { sendBroadcast '2' } ((bleRadio_receivedString) == 3) { sendBroadcast '3' } ((bleRadio_receivedString) == 4) { sendBroadcast '4' } else { } } script 1462 428 { to main {} } script 556 550 { whenBroadcastReceived 'stop' digitalWriteOp 18 false digitalWriteOp 19 false OLEDclear OLEDwrite 'Stop' 0 0 false 2 } script 574 752 { whenBroadcastReceived 'down' analogWriteOp 19 800 OLEDclear OLEDwrite 'Back' 0 0 false 2 } script 88 770 { whenCondition (not (digitalReadOp 23 'up')) sendBroadcast 'stop' } script 193 862 { whenCondition (not (digitalReadOp 14 'up')) sendBroadcast 'up' } script 159 1013 { whenCondition (not (digitalReadOp 25 'up')) sendBroadcast 'down' } module 'Analog Synthesis' Operators author MicroBlocks version 1 14 choices outputTypes 'asy;pin' 'asy;MCP4725' description 'Generate analog waves that you can output to a DAC or analog pin to produce sound. You can create your own instruments by adding waves together, and you can define envelopes for gates and trigger them at will, or create sound textures by mixing waves. Amplitude range: -32768 to 32767 (16 bits signed) ' variables _asynth_trigger_active _asynth_trigger_times _asynth_frequencies _asynth_frequency_table _asynth_amplitudes _asynth_attacks _asynth_holds _asynth_releases _asynth_total_durations _asynth_ramp_starts _asynth_ramp_ends '_mcp4725 out' _asynth_ramp_frequencies spec ' ' 'asynth output' 'output _ to _ : number/address _ : asynth;resolution _' 'num menu.outputTypes num num' 1024 'asy;pin' 25 10 space spec 'r' 'asynth sin' '#SVG#wave-sine _ Hz _ #SVG#double-arrow-v' 'num num' 440 10000 spec 'r' 'asynth square' '#SVG#wave-square _ Hz _ #SVG#double-arrow-v' 'num num' 185 8000 spec 'r' 'asynth tri' '#SVG#wave-triangle _ Hz _ #SVG#double-arrow-v' 'num num' 392 10000 spec 'r' 'asynth saw' '#SVG#wave-sawtooth _ Hz _ #SVG#double-arrow-v' 'num num' 196 8000 spec 'r' 'asynth noise' '#SVG#wave-noise _ Hz _ #SVG#double-arrow-v' 'num num' 500 4000 space spec 'r' 'asynth sin gate' '#SVG#wave-sine gate _' 'menu.range:1-8' 1 spec 'r' 'asynth square gate' '#SVG#wave-square gate _' 'menu.range:1-8' 1 spec 'r' 'asynth tri gate' '#SVG#wave-triangle gate _' 'menu.range:1-8' 1 spec 'r' 'asynth saw gate' '#SVG#wave-sawtooth gate _' 'menu.range:1-8' 1 spec 'r' 'asynth noise gate' '#SVG#wave-noise gate _' 'menu.range:1-8' 1 space spec ' ' 'asynth envelope' 'set amplitude _ attack _ hold _ release _ for gate _' 'num num num num menu.range:1-8' 10000 10 250 100 1 spec ' ' 'asynth ramp' 'set frequency ramp from _ % to _ % for gate _' 'num num menu.range:1-8' 100 10 1 spec ' ' 'asynth trigger gate' 'trigger gate _ MIDI key _' 'menu.range:1-8 num' 1 60 space spec 'r' 'asynth gate amplitude' 'amplitude for gate _' 'menu.range:1-8' 1 spec 'r' 'asynth gate frequency' 'frequency for gate _' 'menu.range:1-8' 1 space spec ' ' '_asynth init' '_asynth init' spec 'r' '_asynth MIDI to freq' '_asynth MIDI note _ to frequency' 'auto' 60 to '_asynth MIDI to freq' key { local 'semitone' ((key % 12) + 1) local 'octave' ((key / 12) - 5) if (_asynth_frequency_table == 0) { _asynth_frequency_table = ('[data:makeList]' 261626 277183 293665 311127 329628 349228 369994 391995 415305 440000 466164 493883 246942 277183 277183 311127 311127 349228 329628 369994 369994 415305 415305 466164 466164 523252) } if (and (1 <= semitone) (semitone <= (size _asynth_frequency_table))) { local 'freq' (at semitone _asynth_frequency_table) if (octave < 0) { repeat (absoluteValue octave) { freq = (freq / 2) } } repeat octave { freq = (freq * 2) } return (freq / 1000) } else { return 10000 } } to '_asynth init' { if (_asynth_trigger_times != 0) {return 0} _asynth_trigger_active = (newList 8 (booleanConstant false)) _asynth_trigger_times = (newList 8) _asynth_frequencies = (newList 8) _asynth_amplitudes = (newList 8) _asynth_attacks = (newList 8) _asynth_holds = (newList 8) _asynth_releases = (newList 8) _asynth_total_durations = (newList 8) _asynth_ramp_starts = (newList 8 -1) _asynth_ramp_ends = (newList 8) _asynth_ramp_frequencies = (newList 8) } to 'asynth envelope' amp att hold release gate { '_asynth init' atPut gate _asynth_amplitudes amp atPut gate _asynth_attacks att atPut gate _asynth_holds hold atPut gate _asynth_releases release atPut gate _asynth_total_durations (sum att hold release) } to 'asynth gate amplitude' gate { if (not (at gate _asynth_trigger_active)) {return 0} local 'start time' (at gate _asynth_trigger_times) local 'amp' (at gate _asynth_amplitudes) local 'att' (maximum 1 (at gate _asynth_attacks)) local 'hold' (at gate _asynth_holds) local 'release' (maximum 1 (at gate _asynth_releases)) if ((millisOp) < (v 'start time')) { return 0 } ((millisOp) > (((v 'start time') + att) + (hold + release))) { atPut gate _asynth_trigger_active (booleanConstant false) return 0 } ((millisOp) > (((v 'start time') + att) + hold)) { return (amp - ((amp * ((millisOp) - (((v 'start time') + att) + hold))) / release)) } ((millisOp) > ((v 'start time') + att)) { return amp } else { return ((amp * ((millisOp) - (v 'start time'))) / att) } } to 'asynth gate frequency' gate { local 'base' (at gate _asynth_frequencies) if ((at gate _asynth_ramp_starts) == -1) { return base } local 'freq1' (base * (at gate _asynth_ramp_starts)) local 'freq2' (base * (at gate _asynth_ramp_ends)) local 'freq' (at gate _asynth_ramp_frequencies) local 'new freq' ((freq1 + (((freq2 - freq1) * ((millisOp) - (at gate _asynth_trigger_times))) / (maximum 1 (at gate _asynth_total_durations)))) / 100) if ((absoluteValue (((microsOp) % (1000000 / (maximum 1 freq))) - ((microsOp) % (1000000 / (maximum 1 (v 'new freq')))))) < 250) { freq = (v 'new freq') atPut gate _asynth_ramp_frequencies (v 'new freq') } return freq } to 'asynth noise' freq amp { if (amp < 1) {return 0} return (ifExpression (((microsOp) % (1000000 / (maximum 1 freq))) < 100) (random (0 - amp) amp) 0) } to 'asynth noise gate' gate { '_asynth init' if (at gate _asynth_trigger_active) { return ('asynth noise' ('asynth gate frequency' gate) ('asynth gate amplitude' gate)) } return 0 } to 'asynth output' sample 'output type' destination bits { if ((v 'output type') == 'asy;pin') { local 'res' (argOrDefault 4 10) analogWriteOp (ifExpression (and ((argOrDefault 3 -1) == -1) ((boardType) == 'micro:bit v2')) 27 destination) (maximum 0 (minimum (((1 << res) / 2) + (sample >> (16 - res))) ((1 << res) - 1))) } else { if ((v '_mcp4725 out') == 0) {'_mcp4725 out' = (newList 3 64)} sample = (maximum 0 (minimum (2048 + (sample >> 4)) 4095)) atPut 2 (v '_mcp4725 out') ((sample >> 8) & 15) atPut 3 (v '_mcp4725 out') (sample & 255) '[sensors:i2cWrite]' (argOrDefault 3 96) (v '_mcp4725 out') } } to 'asynth ramp' start end gate { '_asynth init' atPut gate _asynth_ramp_starts start atPut gate _asynth_ramp_ends end } to 'asynth saw' freq amp { if (or (amp < 1) (freq < 1)) {return 0} local 'ms' (1000000 / freq) return ((((2 * amp) * ((microsOp) % ms)) / ms) - amp) } to 'asynth saw gate' gate { '_asynth init' if (at gate _asynth_trigger_active) { return ('asynth saw' ('asynth gate frequency' gate) ('asynth gate amplitude' gate)) } return 0 } to 'asynth sin' freq amp { if (or (amp < 1) (freq < 1)) {return 0} local 'ms' (1000000 / freq) return (((2 * amp) * ('[misc:sin]' ((3600 * ((microsOp) % ms)) / (ms / 10)))) >> 15) } to 'asynth sin gate' gate { '_asynth init' if (at gate _asynth_trigger_active) { return ('asynth sin' ('asynth gate frequency' gate) ('asynth gate amplitude' gate)) } return 0 } to 'asynth square' freq amp { if (or (amp < 1) (freq < 1)) {return 0} local 'ms' (1000000 / freq) if (ms < 1) { return 0 } return (ifExpression (((microsOp) % ms) > (ms / 2)) amp (0 - amp)) } to 'asynth square gate' gate { '_asynth init' if (at gate _asynth_trigger_active) { return ('asynth square' ('asynth gate frequency' gate) ('asynth gate amplitude' gate)) } return 0 } to 'asynth tri' freq amp { if (or (amp < 1) (freq < 1)) {return 0} local 'ms' (1000000 / freq) return ((((4 * amp) * (absoluteValue (((microsOp) % ms) - (ms / 2)))) / ms) - amp) } to 'asynth tri gate' gate { '_asynth init' if (at gate _asynth_trigger_active) { return ('asynth tri' ('asynth gate frequency' gate) ('asynth gate amplitude' gate)) } return 0 } to 'asynth trigger gate' index key { '_asynth init' local 'base' ('_asynth MIDI to freq' key) atPut index _asynth_frequencies base atPut index _asynth_trigger_times (millisOp) atPut index _asynth_trigger_active (booleanConstant true) atPut index _asynth_ramp_frequencies ((base * (at index _asynth_ramp_starts)) / 100) } module 'BLE Radio' Comm author MicroBlocks version 1 1 description 'This library supports easy message exchange among boards that support Bluetooth Low Energy (BLE). A message can contain a string, a number, or both. Messages are received by all boards in the same BLE radio group as the sender. This library is similar to the Radio library, but messages are sent via BLE advertisements instead of the Nordic radio system. ' variables _bleRadio_lastNumber _bleRadio_lastString spec ' ' 'bleRadio_sendNumber' 'ble send number _' 'num' 123 spec ' ' 'bleRadio_sendString' 'ble send string _' 'str' 'Hello!' spec ' ' 'bleRadio_sendPair' 'ble send pair _ = _' 'str num' 'light' 10 space spec 'r' 'bleRadio_messageReceived' 'ble message received?' spec 'r' 'bleRadio_receivedInteger' 'ble last number' spec 'r' 'bleRadio_receivedString' 'ble last string' space spec ' ' '[ble:radioSetGroup]' 'ble set group _ (0-255)' 'num' 0 to bleRadio_messageReceived { local 'msg' ('[ble:radioReceive]') if ((size msg) < 4) { return (booleanConstant false) } _bleRadio_lastNumber = ((at 1 msg) | (((at 2 msg) << 8) | (((at 3 msg) << 16) | ((at 4 msg) << 24)))) local 'highByte' (at 4 msg) if (and (64 <= highByte) (highByte <= 127)) { _bleRadio_lastNumber = 'out of range number (positve)' } if (and (128 <= highByte) (highByte <= 191)) { _bleRadio_lastNumber = 'out of range number (negative)' } _bleRadio_lastString = ('[data:join]' '' ('[data:copyFromTo]' msg 5)) return (booleanConstant true) } to bleRadio_receivedInteger { return _bleRadio_lastNumber } to bleRadio_receivedString { if (_bleRadio_lastString == 0) {_bleRadio_lastString = ''} return _bleRadio_lastString } to bleRadio_sendNumber n { bleRadio_sendPair '' n } to bleRadio_sendPair s n { local 'msg' ('[data:newByteArray]' 4) comment 'MicroBlocks BLE Radio message format: MicroBlocks integer (4 bytes, LSB first) string (UTF8, variable length)' atPut 1 msg (n & 255) atPut 2 msg ((n >> 8) & 255) atPut 3 msg ((n >> 16) & 255) atPut 4 msg ((n >> 24) & 255) msg = ('[data:join]' msg ('[data:asByteArray]' s)) '[ble:radioStartBeam]' msg waitMillis 500 '[ble:radioStopBeam]' } to bleRadio_sendString s { bleRadio_sendPair s 0 } module 'Distance (HC-SR04)' Input author 'Joan Guillén & Josep Ferràndiz' version 1 4 tags sensor 'hc-sr04' distance ultrasound description 'Support for the HC-SR04 ultrasound distance sensor.' variables _sr04_last spec 'r' 'distance (cm)' 'distance (cm) trigger _ echo _' 'num num' 2 4 spec 'r' 'sr04_distanceOnePin' 'distance (cm) pin _' 'num' 0 to 'distance (cm)' trig ech { comment 'Contributed by Joan Guillén and Josep Ferràndiz' digitalWriteOp trig false waitMicros 2 digitalWriteOp trig true waitMicros 50 digitalWriteOp trig false local 'start' (microsOp) waitUntil (or (not (digitalReadOp ech)) (((microsOp) - start) > 23320)) waitUntil (or (digitalReadOp ech) (((microsOp) - start) > 23320)) if (((microsOp) - start) > 23320) { comment 'Distance sensor not ready; return the last distance reading' return _sr04_last } comment 'Pulse sent. Measure time until echo is detected.' start = (microsOp) waitUntil (or (not (digitalReadOp ech)) (((microsOp) - start) > 23320)) _sr04_last = ((10 * ((microsOp) - start)) / 583) comment 'Leave some time for reverberations to die away.' waitMillis 10 return _sr04_last } to sr04_distanceOnePin pin { return ('distance (cm)' pin pin) } module 'LED Display' Output author MicroBlocks version 1 15 choices led_imageMenu heart 'small heart' yes no happy sad confused angry asleep surprised silly fabulous meh 't-shirt' 'roller skate' duck house tortoise butterfly 'stick figure' ghost sword giraffe skull umbrella snake rabbit cow 'quarter note' 'eight note' pitchfork target triangle 'left triangle' 'chess board' diamond 'small diamond' square 'small square' scissors description 'Display primitives for the 5x5 LED display on the BBC micro:bit, Calliope mini and M5Atom Matrix. Boards with TFT displays (such as the Citilab ED1 or the M5Stack family) support these primitives with a simulated "fat pixel" display. ' variables _stop_scrolling_text spec ' ' '[display:mbDisplay]' 'display _' 'microbitDisplay' 15237440 spec ' ' 'led_displayImage' 'display image _ : x _ y _' 'menu.led_imageMenu num num' 'happy' 1 1 spec ' ' '[display:mbDisplayOff]' 'clear display' space spec ' ' '[display:mbPlot]' 'plot x _ y _' 'num num' 3 3 spec ' ' '[display:mbUnplot]' 'unplot x _ y _' 'num num' 3 3 space spec ' ' 'displayCharacter' 'display character _' 'str' 'A' spec ' ' 'scroll_text' 'scroll text _ : pausing _ ms' 'str num' 'HELLO ROSA!' 100 spec ' ' 'stopScrollingText' 'stop scrolling' advanced spec ' ' 'set display color' 'set display color _' 'color' spec 'r' 'led_image' 'LED image #BR# _' 'microbitDisplay' 15237440 space spec 'r' '_led_namedImage' '_led_namedImage _' 'menu.led_imageMenu' 'happy' spec 'r' '_led_imageData' '_led_imageData' spec 'r' '_led_image' 'obsolete led image _' 'microbitDisplay' 15237440 to '_led_imageData' { return 'heart:4685802,small heart:145728,yes:2269696,no:18157905,happy:15237440,sad:18284864,confused:22348096,angry:23036241,asleep:459616,surprised:4526090,silly:25984017,fabulous:15008639,meh:2236443,t-shirt:15154043,roller skate:11534104,duck:489702,house:10976708,tortoise:359872,butterfly:29332475,stick figure:18158564,ghost:23068334,sword:4657284,giraffe:10946627,skull:15171246,umbrella:6460398,snake:469859,rabbit:16104613,cow:4685361,quarter note:7573636,eight note:7590276,pitchfork:4357813,target:4681156,triangle:1026176,left triangle:32805985,chess board:11184810,diamond:4539716,small diamond:141440,square:33080895,small square:469440,scissors:20287859,' } to '_led_namedImage' name { local 'data' ('_led_imageData') local 'i' ('[data:find]' name data) if (i == -1) { comment 'Name not found' return 0 } local 'start' (('[data:find]' ':' data i) + 1) local 'end' (('[data:find]' ',' data i) - 1) return ('[data:convertType]' ('[data:copyFromTo]' data start end) 'number') } to displayCharacter s { s = ('[data:join]' '' s) if ((size s) == 0) { '[display:mbDisplayOff]' return 0 } '[display:mbDrawShape]' ('[display:mbShapeForLetter]' (at 1 s)) 1 1 } to led_displayImage imageName optionalX optionalY { local 'image' imageName if (isType image 'string') { image = ('_led_namedImage' imageName) } '[display:mbDrawShape]' image (argOrDefault 2 1) (argOrDefault 3 1) } to led_image twentyFiveBitInt { comment 'An LED image is a 25-bit integer' return twentyFiveBitInt } to scroll_text text optionalDelay { text = ('[data:join]' '' text) local 'delay' 100 if ((pushArgCount) > 1) { delay = optionalDelay } _stop_scrolling_text = (booleanConstant false) if ('Pico:ed' == (boardType)) { for position (((size text) * 6) + 18) { if _stop_scrolling_text {return 0} '[display:mbDisplayOff]' '[tft:text]' text (17 - position) 0 (colorSwatch 125 125 125 255) 1 true waitMillis (delay / 2) } } (or ('KidsIOT' == (boardType)) ('CodingBox' == (boardType))) { for position (((size text) * 6) + 21) { if _stop_scrolling_text {return 0} '[tft:deferUpdates]' '[tft:clear]' '[tft:text]' text (128 - (6 * position)) 6 (colorSwatch 255 255 255 255) 6 false '[tft:resumeUpdates]' waitMillis (delay / 8) } } else { for position (((size text) * 6) + 6) { if _stop_scrolling_text {return 0} for i (size text) { '[display:mbDrawShape]' ('[display:mbShapeForLetter]' ('[data:unicodeAt]' i text)) (((i * 6) + 2) - position) 1 } waitMillis delay } } } to 'set display color' color { '[display:mbSetColor]' color } to stopScrollingText { _stop_scrolling_text = (booleanConstant true) waitMillis 10 '[display:mbDisplayOff]' } module MIDI Output author MicroBlocks version 1 20 choices druminst 'Acoustic Bass Drum' 'Bass Drum 1' 'Side Stick' 'Acoustic Snare' 'Hand Clap' 'Electric Snare' 'Low Floor Tom' 'Closed Hi-Hat' 'High Floor Tom' 'Pedal Hi-Hat' 'Low Tom' 'Open Hi-Hat' 'Low-Mid Tom' 'Hi-Mid Tom' 'Crash Cymbal 1' 'High Tom' 'Ride Cymbal 1' 'Chinese Cymbal' 'Ride Bell' Tambourine 'Splash Cymbal' Cowbell 'Crash Cymbal 2' Vibraslap 'Ride Cymbal 2' 'Hi Bongo' 'Low Bongo' 'Mute Hi Conga' 'Open Hi Conga' 'Low Conga' 'High Timbale' 'Low Timbale' 'High Agogo' 'Low Agogo' Cabasa Maracas 'Short Whistle' 'Long Whistle' 'Short Guiro' 'Long Guiro' Claves 'Hi Wood Block' 'Low Wood Block' 'Mute Cuica' 'Open Cuica' 'Mute Triangle' 'Open Triangle' choices _midiPin USB choices instrument piano 'chromatic percussion' organ guitar bass strings ensemble brass reed pipe 'synth lead' 'synth pad' 'synth SFX' ethnic percussive 'gm;SFX' choices sfx 'guitar fret noise' 'guitar cut noise' 'string slap' 'flute key click' seashore rain thunder wind stream bubble bird dog horse 'telephone 1' 'telephone 2' 'door creaking' door scratch windchime helicopter 'car engine' 'car stop' 'car pass' 'car crash' siren train 'jet plane' starship 'burst noise' applause laughing screaming punch 'heart beat' footstep 'gun shot' 'machine gun' 'laser gun' explosion choices drumkits 'Standard 1' 'Standard 2' 'Standard L/R' Room 'Hip Hop' Jungle Techno 'Room L/R' House Power Electronic 'TR-808' Dance 'CR-78' 'TR-606' 'TR-707' 'TR-909' Jazz 'Jazz L/R' Brush 'Brush 2' 'Brush 2 L/R' Orchestra Ethnic 'Kick & Snare' 'Kick & Snare 2' Asia 'Cymbal&claps' 'Gamelan 1' 'Gamelan 2' SFX 'Rhythm FX' 'Rhythm FX 2' 'Rhythm FX 3' 'SFX 2' Voice 'Cym&Claps 2' choices controls 'bank select 1' 'bank select 2' modulation 'portamento time' volume pan expression hold portamento sostenuto soft resonance 'release time' 'attack time' cutoff 'decay time' 'vibrato rate' 'vibrato depth' 'vibrato delay' 'portamento control' 'effect 1' 'effect 2' description 'Control MIDI devices. By default, it interfaces the hardware serial port, but it also supports software serial and USB MIDI. Software Serial Ports --------------------- You can specify a software serial port using the `set MIDI pin` block. On most boards, however, the default serial pin is going to be pin 1, so there is no need to use the `set MIDI pin` block. Connect pin 5 of the MIDI DIN connector to the serial pin, and pin 4 of the MIDI DIN connector to 3.3v or 5v. USB MIDI -------- Alternatively, if you want to use the MIDI library to control a USB MIDI device -or a virtual synth running in your PC, even while MicroBlocks is running- you can do so by setting the `use USB MIDI` flag to true in the `set MIDI pin` block. ' variables _MIDI_initialized _MIDI_pin _MIDI_drums _MIDI_clock_on spec ' ' 'MIDI_play MIDI note' 'play MIDI note _ for _ ms on channel _ volume _' 'auto auto auto auto' 60 500 1 127 spec ' ' 'MIDI_send note' 'set MIDI note _ to _ on channel _ volume _' 'auto bool auto auto' 60 true 1 127 spec ' ' 'MIDI_stop all notes in channel' 'stop all notes on channel _ in _ ms' 'auto auto' 1 1000 space spec 'r' 'MIDI_note to MIDI' 'note _ octave _ to MIDI key' 'auto auto' 'c' 0 space spec ' ' 'MIDI_select MIDI instrument' 'select MIDI instrument _ number _ for channel _ : variation _' 'menu.instrument menu.range:1-8 num num' 'bass' 2 1 127 spec ' ' 'MIDI_raw select MIDI instrument' 'select MIDI instrument _ for channel _ : variation _' 'num num num' 23 2 8 spec 'r' 'MIDI_instrument' 'instrument _ number _' 'menu.instrument menu.range:1-8' 'piano' 1 spec ' ' 'MIDI_select MIDI drum kit' 'select MIDI drum kit _' 'menu.drumkits' 'Techno' space spec ' ' 'MIDI_play MIDI drum' 'play MIDI drum _ for _ ms volume _' 'menu.druminst num num' 'Acoustic Snare' 100 80 spec 'r' 'MIDI_drum note' 'drum _' 'menu.druminst' 'Ride Cymbal 2' space spec ' ' 'MIDI_change MIDI control' 'change MIDI control _ on channel _ to _' 'menu.controls auto auto' 'modulation' 1 127 spec ' ' 'MIDI_pitch bend channel' 'pitch bend MIDI channel _ to _ %' 'auto auto' 1 -50 space spec ' ' 'MIDI_send MIDI reset' 'send MIDI reset' space spec ' ' 'MIDI_send MIDI start playing' 'send MIDI start playing' spec ' ' 'MIDI_send MIDI stop playing' 'send MIDI stop playing' spec ' ' 'MIDI_send MIDI continue playing' 'send MIDI continue playing' spec ' ' 'MIDI_start MIDI clock' 'start MIDI clock' spec ' ' 'MIDI_stop MIDI clock' 'stop MIDI clock' space spec ' ' 'MIDI_set MIDI pin' 'set MIDI signal to pin _' 'auto._midiPin' 8 space spec ' ' '_MIDI init' '_MIDI init' space spec ' ' '_MIDI command' '_MIDI command _ channel _ arg1 _ : arg2 _' 'num num num num' 9 1 60 127 spec ' ' '_MIDI send bytes' '_MIDI send bytes _' 'auto' '' spec ' ' '_MIDI_NRPN' '_set MIDI NRPN MSB _ LSB _ on channel _ to _' 'auto auto auto auto auto auto' 1 33 1 100 spec ' ' '_MIDI_Roland_control' '_set Roland control _ to _ on channel _' 'auto auto auto' 72 64 1 spec ' ' '_MIDI clock loop' '_MIDI clock loop' space spec 'r' '_trimmedLowercase' '_trimmedLowercase _' 'str' 'A. b C...' to 'MIDI_change MIDI control' control channel value { local 'controlChangeCmd' 11 local 'controller' (at (ifExpression (isType control 'number') control ('[data:find]' control ('[data:makeList]' 'bank select 1' 'bank select 2' 'modulation' 'portamento time' 'volume' 'pan' 'expression' 'hold' 'portamento' 'sostenuto' 'soft' 'resonance' 'release time' 'attack time' 'cutoff' 'decay time' 'vibrato rate' 'vibrato depth' 'vibrato delay' 'portamento control' 'effect 1' 'effect 2'))) ('[data:makeList]' 0 32 1 5 7 10 11 64 65 66 67 71 72 73 74 75 76 77 78 84 91 93)) comment 'Make sure Roland synths get these as NRPN messages' '_MIDI_Roland_control' controller value channel '_MIDI command' controlChangeCmd channel controller value } to 'MIDI_drum note' instrument { if (not (isType _MIDI_drums 'list')) {_MIDI_drums = ('[data:split]' 'Acoustic Bass Drum,Bass Drum 1,Side Stick,Acoustic Snare,Hand Clap,Electric Snare,Low Floor Tom,Closed Hi-Hat,High Floor Tom,Pedal Hi-Hat,Low Tom,Open Hi-Hat,Low-Mid Tom,Hi-Mid Tom,Crash Cymbal 1,High Tom,Ride Cymbal 1,Chinese Cymbal,Ride Bell,Tambourine,Splash Cymbal,Cowbell,Crash Cymbal 2,Vibraslap,Ride Cymbal 2,Hi Bongo,Low Bongo,Mute Hi Conga,Open Hi Conga,Low Conga,High Timbale,Low Timbale,High Agogo,Low Agogo,Cabasa,Maracas,Short Whistle,Long Whistle,Short Guiro,Long Guiro,Claves,Hi Wood Block,Low Wood Block,Mute Cuica,Open Cuica,Mute Triangle,Open Triangle' ',')} return (('[data:find]' instrument _MIDI_drums) + 34) } to MIDI_instrument instrument number { local 'categories' ('[data:split]' 'piano,chromatic percussion,organ,guitar,bass,strings,ensemble,brass,reed,pipe,synth lead,synth pad,synth SFX,ethnic,percussive,gm;SFX' ',') return (((('[data:find]' instrument categories) - 1) * 8) + number) } to 'MIDI_note to MIDI' note octave { local 'noteNames' ('[data:makeList]' 'c' 'c#' 'd' 'd#' 'e' 'f' 'f#' 'g' 'g#' 'a' 'a#' 'b' 'c_' 'db' 'd_' 'eb' 'e_' 'e#' 'f_' 'gb' 'g_' 'ab' 'a_' 'bb' 'b_' 'b#') local 'midiKeys' ('[data:makeList]' 60 61 62 63 64 65 66 67 68 69 70 71 59 61 61 63 63 65 64 66 66 68 68 70 70 72) return ((at ('[data:find]' ('_trimmedLowercase' note) noteNames) midiKeys) + (octave * 12)) } to 'MIDI_pitch bend channel' channel percent { local 'pitchBendCmd' 14 local 'bendMSB' (maximum 0 (minimum (((64 * percent) / 100) + 64) 127)) '_MIDI command' pitchBendCmd channel 0 bendMSB } to 'MIDI_play MIDI drum' drumName duration volume { if (isType drumName 'number') { 'MIDI_send note' drumName true 10 volume } else { 'MIDI_send note' ('MIDI_drum note' drumName) true 10 volume } waitMillis (duration - 3) } to 'MIDI_play MIDI note' 'midi note' duration channel volume { if (isType (v 'midi note') 'string') { waitMillis duration } else { 'MIDI_send note' (v 'midi note') true channel volume waitMillis (duration - 3) 'MIDI_send note' (v 'midi note') false channel 0 } } to 'MIDI_raw select MIDI instrument' 'instrument number' channel variation { local 'programChangeCmd' 12 'MIDI_change MIDI control' 'bank select 1' channel variation '_MIDI command' programChangeCmd channel (v 'instrument number') } to 'MIDI_select MIDI drum kit' drumkit { local 'programChangeCmd' 12 '_MIDI command' programChangeCmd 10 ((at ('[data:find]' drumkit ('[data:split]' 'Standard 1,Standard 2,Standard L/R,Room,Hip Hop,Jungle,Techno,Room L/R,House,Power,Electronic,TR-808,Dance,CR-78,TR-606,TR-707,TR-909,Jazz,Jazz L/R,Brush,Brush 2,Brush 2 L/R,Orchestra,Ethnic,Kick & Snare,Kick & Snare 2,Asia,Cymbal&claps,Gamelan 1,Gamelan 2,SFX,Rhythm FX,Rhythm FX 2,Rhythm FX 3,SFX 2,Voice,Cym&Claps 2' ',')) ('[data:makeList]' 1 2 3 9 10 11 12 13 14 17 25 26 27 28 29 30 31 33 34 41 42 43 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63)) - 1) } to 'MIDI_select MIDI instrument' 'instrument type' 'instrument number' channel variation { local 'programChangeCmd' 12 'MIDI_change MIDI control' 'bank select 1' channel (argOrDefault 4 0) '_MIDI command' programChangeCmd channel ((MIDI_instrument (v 'instrument type') (v 'instrument number')) - 1) } to 'MIDI_send MIDI continue playing' { '_MIDI send bytes' (hexToInt 'FB') } to 'MIDI_send MIDI reset' { '_MIDI send bytes' (hexToInt 'FF') '_MIDI send bytes' ('[data:makeList]' (hexToInt 'F0') (hexToInt '7E') (hexToInt '7F') (hexToInt '09') (hexToInt '01') (hexToInt 'F7')) '_MIDI send bytes' ('[data:makeList]' (hexToInt 'F0') (hexToInt '41') (hexToInt '10') (hexToInt '42') (hexToInt '12') (hexToInt '40') (hexToInt '00') (hexToInt '7F') (hexToInt '00') (hexToInt '41') (hexToInt 'F7')) comment 'Turn off all notes on all channels' for chan 16 { for key 128 { 'MIDI_send note' (key - 1) false chan 0 } } } to 'MIDI_send MIDI start playing' { '_MIDI send bytes' (hexToInt 'FA') } to 'MIDI_send MIDI stop playing' { '_MIDI send bytes' (hexToInt 'FC') } to 'MIDI_send note' 'midi note' on/off channel volume { if (isType (v 'midi note') 'list') { for note (v 'midi note') { 'MIDI_send note' note on/off channel volume } } else { local 'noteOnMsg' 9 if on/off { '_MIDI command' noteOnMsg channel (v 'midi note') volume } else { '_MIDI command' noteOnMsg channel (v 'midi note') 0 } } } to 'MIDI_set MIDI pin' pin { '[serial:close]' if ('USB' == pin) { _MIDI_pin = 'USB' } else { if (or (isType pin 'list') ((boardType) == 'Boardie')) { _MIDI_pin = pin } else { _MIDI_pin = ('[data:makeList]' ('[data:makeList]' pin ('[data:range]' 1 16))) } } _MIDI_initialized = (booleanConstant false) '_MIDI init' } to 'MIDI_start MIDI clock' { if (_rhy_beat_duration == 0) {'rhy_set tempo' 250 4 4} _MIDI_clock_on = (booleanConstant true) sendBroadcast '_MIDI clock loop' } to 'MIDI_stop MIDI clock' { _MIDI_clock_on = (booleanConstant false) } to 'MIDI_stop all notes in channel' channel ms { for v 9 { 'MIDI_change MIDI control' 'volume' channel (100 - (10 * v)) waitMillis ((ms - 50) / (1 << v)) } 'MIDI_change MIDI control' 'volume' channel 0 waitMillis ((ms - 50) / (1 << 8)) '_MIDI command' 11 channel 123 0 waitMillis 50 'MIDI_change MIDI control' 'volume' channel 100 } to '_MIDI clock loop' { repeatUntil (_MIDI_clock_on != (booleanConstant true)) { 'rhy wait until beat' (('rhy_current beat') + 1) repeat 24 { 'rhy_wait until next' (('rhy_quarter duration') / 24) '_MIDI send bytes' 248 } } } to '_MIDI command' cmd channel arg1 arg2 { '_MIDI init' local 'cmdByte' ((cmd << 4) | ((channel - 1) & 15)) local 'oneArgByte' ((pushArgCount) < 4) if (or (_MIDI_pin == 'USB') ((boardType) == 'Boardie')) { if oneArgByte { '[serial:midiSend]' cmdByte arg1 } else { '[serial:midiSend]' cmdByte arg1 arg2 } } (_MIDI_pin == 0) { if oneArgByte { '[serial:write]' ('[data:makeList]' cmdByte arg1) } else { '[serial:write]' ('[data:makeList]' cmdByte arg1 arg2) } } else { for map _MIDI_pin { if (('[data:find]' channel (at 2 map)) > 0) { '[io:softWriteByte]' cmdByte (at 1 map) 31250 '[io:softWriteByte]' arg1 (at 1 map) 31250 if (not oneArgByte) { '[io:softWriteByte]' arg2 (at 1 map) 31250 } } } } } to '_MIDI init' { if (not _MIDI_initialized) { if (and ((boardType) == 'micro:bit v2') (_MIDI_pin == 0)) { 'MIDI_set MIDI pin' 0 } ((boardType) != 'Boardie') { if (_MIDI_pin == 0) { '[serial:open]' 31250 } else { '[serial:close]' } } else { callCustomCommand '[serial:setPins]' ('[data:makeList]' _MIDI_pin) } _MIDI_initialized = (booleanConstant true) for i 16 { 'MIDI_change MIDI control' 'volume' i 100 } } } to '_MIDI send bytes' byteList { '_MIDI init' if (isType byteList 'number') { byteList = ('[data:makeList]' byteList) } for byte byteList { if (_MIDI_pin == 0) { '[serial:write]' byte } (_MIDI_pin == 'USB') { '[serial:midiSend]' byte } else { for map _MIDI_pin { '[io:softWriteByte]' byte (at 1 map) 31250 } } } } to '_MIDI_NRPN' msb lsb channel value { '_MIDI command' 11 channel 99 msb '_MIDI command' 11 channel 98 lsb '_MIDI command' 11 channel 6 value } to '_MIDI_Roland_control' control value channel { if (control == 71) { '_MIDI_NRPN' 1 33 channel value } (control == 72) { '_MIDI_NRPN' 1 102 channel value } (control == 73) { '_MIDI_NRPN' 1 99 channel value } (control == 74) { '_MIDI_NRPN' 1 32 channel value } (control == 75) { '_MIDI_NRPN' 1 100 channel value } } module MQTT Comm author wwj718 version 1 7 depends WiFi tags mqtt network description 'A simple MQTT client. ' spec ' ' 'MQTT connect to' 'MQTT connect to broker _ : buffer sizes _ client id _ : username _ password _' 'str num str str str' '' 128 '' '' '' spec 'r' 'MQTT connected' 'MQTT connected' spec ' ' 'MQTT disconnect' 'MQTT disconnect' spec ' ' 'MQTT sub' 'MQTT sub _ : QoS _' 'str num' 'testTopic' 0 spec ' ' 'MQTT unsub' 'MQTT unsub _' 'str' 'testTopic' spec ' ' 'MQTT pub' 'MQTT pub topic _ payload _ : retain _ QoS _' 'str auto bool num' 'testTopic' 'Hello!' false 0 spec 'r' '[net:MQTTLastEvent]' 'MQTT event : binary _' 'bool' false spec 'r' 'MQTT event topic' 'topic for MQTT event _' 'str' '' spec 'r' 'MQTT event payload' 'payload for MQTT event _' 'str' '' spec ' ' '_MQTT set will' 'MQTT set will topic _ payload _ : retain _ QoS _ : buffer sizes _' 'str auto bool num num' 'testWill' 'Last will!' false 0 128 to 'MQTT connect to' broker buffer_sizes client_id username password { if ((getIPAddress) != '0.0.0.0') { if ((pushArgCount) == 1) { callCustomCommand '[net:MQTTConnect]' ('[data:makeList]' broker 128 ('[net:myMAC]')) } ((pushArgCount) == 3) { callCustomCommand '[net:MQTTConnect]' ('[data:makeList]' broker buffer_sizes client_id) } else { callCustomCommand '[net:MQTTConnect]' ('[data:makeList]' broker buffer_sizes client_id username password) } if ('MQTT connected') { sayIt 'Connected!' } else { sayIt 'Not Connected' } } else { sayIt 'Not Connected' } } to 'MQTT connected' { return (callCustomReporter '[net:MQTTIsConnected]') } to 'MQTT disconnect' { callCustomCommand '[net:MQTTDisconnect]' } to 'MQTT event payload' event { if (and (isType event 'list') ((size event) > 1)) { comment 'Join to empty string converts byte array to string' return ('[data:join]' '' (at 2 event)) } else { return '' } } to 'MQTT event topic' event { if (and (isType event 'list') ((size event) > 1)) { return (at 1 event) } else { return '' } } to 'MQTT pub' topic payload retain qos { if ((pushArgCount) < 4) { okay = (callCustomReporter '[net:MQTTPub]' ('[data:makeList]' topic payload)) } else { okay = (callCustomReporter '[net:MQTTPub]' ('[data:makeList]' topic payload retain qos)) } if (not okay) { sayIt 'pub failure!' } } to 'MQTT sub' topic qos { if ((pushArgCount) < 2) { okay = (callCustomReporter '[net:MQTTSub]' ('[data:makeList]' topic)) } else { okay = (callCustomReporter '[net:MQTTSub]' ('[data:makeList]' topic qos)) } if (not okay) { sayIt 'sub failure!' } } to 'MQTT unsub' topic { if ((callCustomReporter '[net:MQTTUnsub]' ('[data:makeList]' topic)) != (booleanConstant true)) {sayIt 'unsub failure!'} } to '_MQTT set will' topic payload retain qos buffer_sizes { if ('MQTT connected') { sayIt 'set will has to be called before calling connect' return '' } if ((pushArgCount) < 4) { okay = (callCustomReporter '[net:MQTTSetWill]' ('[data:makeList]' topic payload)) } ((pushArgCount) == 4) { okay = (callCustomReporter '[net:MQTTSetWill]' ('[data:makeList]' topic payload retain qos)) } else { okay = (callCustomReporter '[net:MQTTSetWill]' ('[data:makeList]' topic payload retain qos buffer_sizes)) } if (not okay) { sayIt 'set will failure!' } } module NeoPixel Output author MicroBlocks version 1 15 description 'Control NeoPixel (WS2812) RGB LED strips and rings. ' variables _np_pixels _np_pin _np_haswhite spec ' ' 'neoPixelAttach' 'attach _ LED NeoPixel strip to pin _ : has white _' 'num auto bool' 10 '' false spec ' ' 'setNeoPixelColors10' 'set NeoPixels _ _ _ _ _ _ _ _ _ _' 'color color color color color color color color color color' spec ' ' 'setNeoPixelColors25' 'set NeoPixels #BR# _ _ _ _ _ #BR# _ _ _ _ _ #BR# _ _ _ _ _ #BR# _ _ _ _ _ #BR# _ _ _ _ _' 'color color color color color color color color color color color color color color color color color color color color color color color color color' spec ' ' 'clearNeoPixels' 'clear NeoPixels' spec ' ' 'neoPixelSetAllToColor' 'set all NeoPixels color _' 'color' spec ' ' 'setNeoPixelColor' 'set NeoPixel _ color _' 'num color' 1 space spec 'r' 'neoPixel_colorSwatch' '_' 'color' spec 'r' 'colorFromRGB' 'color r _ g _ b _ (0-255)' 'num num num' 0 100 100 space spec ' ' 'rotateNeoPixelsBy' 'rotate NeoPixels by _' 'auto' 1 space spec ' ' 'NeoPixel_brighten' 'brighten NeoPixel _ by _' 'num num' 1 10 spec ' ' 'NeoPixel_brighten_all' 'brighten all NeoPixels by _' 'num' 10 spec ' ' 'NeoPixel_shift_color' 'shift NeoPixel _ color by _' 'num num' 1 10 spec ' ' 'NeoPixel_shift_all_colors' 'shift all NeoPixel colors by _' 'num' 10 space spec ' ' '_NeoPixel_ensureInitialized' '_NeoPixel_ensureInitialized' spec ' ' '_NeoPixel_increaseRGB' '_NeoPixel_increaseRGB of _ by _' 'num num' 1 10 spec ' ' '_NeoPixel_rotate' '_NeoPixel_rotate_left _' 'bool' true spec ' ' '_NeoPixel_update' '_NeoPixel_update' spec ' ' '_NeoPixel_shift_hue' '_NeoPixel_shift_hue of _ by _' 'auto auto' '10' '10' to NeoPixel_brighten i delta { '_NeoPixel_increaseRGB' i delta '_NeoPixel_update' } to NeoPixel_brighten_all delta { for i (size _np_pixels) { '_NeoPixel_increaseRGB' i delta } '_NeoPixel_update' } to NeoPixel_shift_all_colors delta { for i (size _np_pixels) { '_NeoPixel_shift_hue' i delta } '_NeoPixel_update' } to NeoPixel_shift_color i delta { '_NeoPixel_shift_hue' i delta '_NeoPixel_update' } to '_NeoPixel_ensureInitialized' { if (_np_pixels == 0) {if (or ((boardType) == 'M5Atom-Matrix') (or ((boardType) == 'Mbits') ((boardType) == 'micro:STEAMakers'))) { neoPixelAttach 25 '' false } ((boardType) == 'D1-Mini') { comment 'D1 mini kit' neoPixelAttach 7 15 false } ((boardType) == 'Foxbit') { neoPixelAttach 35 '' false } ((boardType) == 'CodingBox') { neoPixelAttach 35 '' false } else { neoPixelAttach 10 '' false }} } to '_NeoPixel_increaseRGB' i delta { if (or (i < 1) (i > (size _np_pixels))) {return} local 'rgb' (at i _np_pixels) if (rgb != 0) { local 'h' ('[misc:hue]' rgb) local 's' ('[misc:saturation]' rgb) local 'v' (('[misc:brightness]' rgb) + delta) v = (maximum 20 (minimum v 100)) atPut i _np_pixels ('[misc:hsvColor]' h s v) } } to '_NeoPixel_rotate' left { '_NeoPixel_ensureInitialized' local 'length' (size _np_pixels) if left { local 'first' (at 1 _np_pixels) for i (length - 1) { atPut i _np_pixels (at (i + 1) _np_pixels) } atPut length _np_pixels first } else { local 'last' (at length _np_pixels) for i (length - 1) { atPut ((length - i) + 1) _np_pixels (at (length - i) _np_pixels) } atPut 1 _np_pixels last } } to '_NeoPixel_shift_hue' i delta { if (or (i < 1) (i > (size _np_pixels))) {return} local 'rgb' (at i _np_pixels) if (rgb != 0) { local 'h' ((('[misc:hue]' rgb) + delta) % 360) local 's' ('[misc:saturation]' rgb) local 'v' ('[misc:brightness]' rgb) atPut i _np_pixels ('[misc:hsvColor]' h s v) } } to '_NeoPixel_update' { comment 'NeoPixel pin and hasWhite may have been changed by another library.' '[display:neoPixelSetPin]' _np_pin _np_hasWhite '[display:neoPixelSend]' _np_pixels waitMicros 300 } to clearNeoPixels { '_NeoPixel_ensureInitialized' atPut 'all' _np_pixels 0 '_NeoPixel_update' } to colorFromRGB r g b { r = (maximum 0 (minimum r 255)) g = (maximum 0 (minimum g 255)) b = (maximum 0 (minimum b 255)) return (((r << 16) | (g << 8)) | b) } to neoPixelAttach number pinNumber optionalHasWhite { _np_pin = pinNumber _np_hasWhite = false if ((pushArgCount) > 2) { _np_hasWhite = optionalHasWhite } if (or (_np_pixels == 0) (number != (size _np_pixels))) { _np_pixels = (newList number) } atPut 'all' _np_pixels 0 '[display:neoPixelSetPin]' _np_pin _np_hasWhite } to neoPixelSetAllToColor color { '_NeoPixel_ensureInitialized' atPut 'all' _np_pixels color '_NeoPixel_update' } to neoPixel_colorSwatch color { return color } to rotateNeoPixelsBy n { '_NeoPixel_ensureInitialized' local 'rotateLeft' (n < 0) if (or ((boardType) == 'CircuitPlayground') ((boardType) == 'CircuitPlayground Bluefruit')) { rotateLeft = (n > 0) } repeat (absoluteValue n) { '_NeoPixel_rotate' rotateLeft } '_NeoPixel_update' } to setNeoPixelColor i color { '_NeoPixel_ensureInitialized' if (and (1 <= i) (i <= (size _np_pixels))) { atPut i _np_pixels color '_NeoPixel_update' } } to setNeoPixelColors10 c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 { '_NeoPixel_ensureInitialized' for i (minimum (size _np_pixels) (pushArgCount)) { atPut i _np_pixels (getArg i) } '_NeoPixel_update' } to setNeoPixelColors25 c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12 c13 c14 c15 c16 c17 c18 c19 c20 c21 c22 c23 c24 c25 { '_NeoPixel_ensureInitialized' for i (minimum (size _np_pixels) (pushArgCount)) { atPut i _np_pixels (getArg i) } '_NeoPixel_update' } module 'OLED Graphics' Output author 'Turgut Guneysu and Miroslav Klimos' version 1 22 choices ModeMenu Horizontal Vertical choices OnOffMenu On Off choices VideoMenu Inverse Normal choices DispTypeMenu 'OLED_0.96in' 'OLED_1.3in' 'OLED_1.54in' 'OLED_2.42in' description 'Supports 0.96in and 1.54in Displays with SSD1306 and 2.42in OLED with SSD1309 chipsets, also 1.3in with SHT1106 chipset. Comm mode is I2C or SPI. Changes: - always display buffer - defer display updates block - remove reveal - switch to x (0-127), y (0-63) - Pruned commands and vars - textX and textY - Text at any x and y - handles newLines - removed dependencies - unified data format for chars, sprites, and images - image draw at any x,y - filled rectangle - image draw bug fix - stack overflow fix - improve conversion ops, i2c io, rect fill, img processing, and buffer displays - reduce i2c buffer to 64 bytes - correct internal draws, clean-up vars, restore _clearDisplay Miroslav Klimos - added Zoom option for text - added support for 1.3in OLED (1106) - thanks to KwyjiboYue ' variables OLEDReady OLEDi2cAddr _GDBuffer _cDecTBL _cTABLE _comMode _dcPin _delayGDUpd _displayType _eol _resetPin _textX _textY _dataPrefix _byteCount _imgData _imgWidth _imgHeight _ColumnOffset spec ' ' 'OLEDInit_I2C_auto' 'initialize i2c(auto) reset pin# _ flip _' 'auto bool' '-' false spec ' ' 'OLEDInit_I2C' 'initialize i2c _ address(hex) _ reset pin# _ flip _' 'str.DispTypeMenu auto auto bool' 'OLED_0.96in' '3C' '-' false spec ' ' 'OLEDInit_SPI' 'initialize spi _ d/c pin# _ reset pin# _ flip _' 'str.DispTypeMenu auto auto bool' 'OLED_2.42in' 16 8 false spec ' ' 'OLEDwrite' 'write _ at x _ y _ inverse _ : scale _' 'auto num num bool num' 'Hello!' 0 0 false 1 spec ' ' 'OLEDshowGDBuffer' 'show display buffer' spec ' ' 'OLEDclear' 'clear' spec ' ' 'OLEDcontrast' 'set contrast (1-4) _' 'auto' 2 spec ' ' 'OLEDdrawCircle' 'draw circle at x _ y _ radius _ erase _ : filled _' 'auto auto auto bool bool' 64 32 '10' false false spec ' ' 'OLEDdrawImage' 'draw image _ at x _ y _' 'auto auto auto' 0 0 0 spec ' ' 'OLEDdrawLine' 'draw line from x _ y _ to x _ y _ erase _' 'auto auto auto auto bool any' 0 0 127 63 false spec ' ' 'OLEDdrawRect' 'draw rectangle x _ y _ w _ h _ erase _ rounding(3-15) _ : filled _' 'auto auto auto auto bool auto bool' 0 0 127 63 false 0 false spec ' ' 'OLEDfillRect' 'fill rectangle x _ y _ w _ h _ erase _' 'auto auto auto auto bool' 20 20 30 20 false spec ' ' '_OLEDflip' '_flip display top _' 'bool' false spec 'r' 'OLEDmakeImage' 'make image _' 'microbitDisplay' 33084991 spec ' ' 'OLEDpixel' 'set pixel x _ y _ erase _' 'auto auto bool' 0 0 false spec ' ' 'OLEDsetVideo' 'set video _' 'str.VideoMenu' 'Inverse' spec 'r' 'OLEDwru' 'cursor location' spec ' ' 'defer display updates' 'defer display updates' spec ' ' '_GDDRAMoff' '_GDDRAMoff' spec ' ' '_GDDRAMon' '_GDDRAMon' spec ' ' '_OLEDcursorReset' '_cursor reset' spec ' ' '_OLEDsetDisplay' '_set display _' 'str.OnOffMenu' 'On' spec ' ' '_OLEDreset' '_set reset Pin# _' 'auto' '0' spec ' ' '_SPIWriteCmd' '_SPIWriteCmd' spec ' ' '_SPIWriteData' '_SPIWriteData' spec ' ' '_clearDisplay' '_clearDisplay' spec ' ' '_corner' '_corner _ _ _ _ _ _' 'auto auto auto auto bool bool' 'TL' 32 1 0 false false spec 'r' '_dec2hex' '_dec2hex _' 'auto' 0 spec ' ' '_initCheck' '_initCheck' spec ' ' '_initDisplayHW' '_initDisplayHW' spec ' ' '_initLibrary' '_initLibrary' spec ' ' '_process image data' '_process image data _' 'auto' ' ' spec ' ' '_sendCmd' '_sendCmd _' 'auto' '' spec ' ' '_verifyXY' '_verifyXY _ _' 'auto auto' 0 0 spec ' ' '_OLEDwriteChar' '_write char _ at x _ y _ inverse _ scale _' 'auto auto auto bool num' 'A' 1 1 false 1 to OLEDInit_I2C dispType i2cAddr resetPin flip { comment 'Sets display type and interface and initializes HW settings. Do NOT set a ResetPin# unless one exists on the display hardware. NOTE: Displays supported: - OLED 0.96in (SD1306 chip - displayType=3) and - OLED 1.3in (SH1106 chip- displayType=8) and - OLED 1.54in (SD1309 chip - displayType=6) and - OLED 2.42" (SD1309 chip - displayType=6 ??? ) All displays are 128x64 pixels or 16x8 characters in size. Displays come in dual mode version (i2c and spi) and pure i2c. Pure i2c models do not have a Reset pin. flip setting controls display hardware write direction: - false: top to bottom - true: bottom to top' OLEDInit_I2C_auto resetPin flip } to OLEDInit_I2C_auto resetPin flip { comment 'Sets display type and interface and initializes HW settings. I2C addresss is checked for 0x3C and 0x3D automatically. Display typ is read from status register' _resetPin = resetPin _comMode = 'i2c' _ColumnOffset = '0x00' OLEDi2cAddr = 60 if (not ('[sensors:i2cExists]' OLEDi2cAddr)) { OLEDi2cAddr += 1 if (not ('[sensors:i2cExists]' OLEDi2cAddr)) { sayIt 'I2C OLED Display not found' waitMillis 2000 return 0 } } if (_displayType != 0) { return } local '_statusreg' 0 _statusreg = (newList 1 0) '[sensors:i2cWrite]' OLEDi2cAddr ('[data:newByteArray]' 1 0) '[sensors:i2cRead]' OLEDi2cAddr _statusreg _displayType = ((at 1 _statusreg) & 15) sayIt 'Display found at address: ' OLEDi2cAddr '- type:' _displayType if (_displayType == 8) { _ColumnOffset = '0x02' } '_initDisplayHW' if flip { '_OLEDflip' true } '_initLibrary' OLEDclear OLEDcontrast 4 } to OLEDInit_SPI dispType dcPin resetPin flip { comment 'Sets display type and interface and initializes HW settings. SPI four wire in Mode-0 is supported. Max speed=10000000. Do NOT set a ResetPin# unless one exists on the display hardware. NOTE: Displays supported: - OLED 0.96in (SD1306 chip) and - OLED 2.42" (SD1309 chip) Both displays are 128x64 pixels or 16x8 characters in size. Displays come in dual mode version (i2c and spi) and pure i2c. Pure i2c models do not have a Reset pin. flip setting controls display hardware write direction: - false: top to bottom - true: bottom to top' _comMode = 'spi' _dcPin = dcPin if (_displayType != 0) { return } _resetPin = resetPin if (dispType == 'OLED_0.96in') { _displayType = 3 } (dispType == 'OLED_1.3in') { _displayType = 8 } else { _displayType = 6 } '[sensors:spiSetup]' 10000000 '_initDisplayHW' if flip { '_OLEDflip' true } '_initLibrary' OLEDclear OLEDcontrast 4 } to OLEDclear { comment 'Set bounds to FullScreen and CLEARs display and GDBuffer.' _GDBuffer = ('[data:newByteArray]' 1024) '_initCheck' '_OLEDcursorReset' OLEDshowGDBuffer } to OLEDcontrast contrast { comment 'Sets the brightness control of the display to one of four values. 1 is the least bright, 4 is the brightest setting.' '_initCheck' local 'cLevels' ('[data:makeList]' 0 '1F' '2F' 'F0') if (and (contrast >= 1) (contrast <= 4)) { local 'i2cCmd' ('[data:join]' '81,' (at contrast cLevels)) '_sendCmd' i2cCmd } else { sayIt 'Error in CONTRAST Level' } } to OLEDdrawCircle cx cy r erase optionalFilled { comment 'Bresenham Circle: Draws circles, even partially bigger than the display. x: 0-127 y: 0-63 void plotCircle(int xm, int ym, int r) { int x = -r, y = 0, err = 2-2*r; /* II. Quadrant */ do { setPixel(xm-x, ym+y); /* I. Quadrant */ setPixel(xm-y, ym-x); /* II. Quadrant */ setPixel(xm+x, ym-y); /* III. Quadrant */ setPixel(xm+y, ym+x); /* IV. Quadrant */ r = err; if (r <= y) err += ++y*2+1; /* e_xy+e_y < 0 */ if (r > x || err > y) err += ++x*2+1; /* e_xy+e_x > 0 or no 2nd y-step */ } while (x < 0); }' '_initCheck' local 'oldDelayGDUpd' _delayGDUpd _delayGDUpd = (booleanConstant true) local 'filled' (argOrDefault 5 (booleanConstant false)) local 'x' (-1 * r) local 'y' 0 local 'err' (2 - (2 * r)) repeatUntil (x >= 0) { if filled { OLEDdrawLine (cx - x) (cy + y) (cx + x) (cy + y) erase OLEDdrawLine (cx + y) (cy + x) (cx - y) (cy + x) erase } else { OLEDpixel (cx - x) (cy + y) erase OLEDpixel (cx - y) (cy - x) erase OLEDpixel (cx + x) (cy - y) erase OLEDpixel (cx + y) (cy + x) erase } r = err if (r <= y) { y += 1 err = (err + ((y * 2) + 1)) } if (or (r > x) (err > y)) { x += 1 err = (err + ((x * 2) + 1)) } } _delayGDUpd = oldDelayGDUpd if (not _delayGDUpd) { OLEDshowGDBuffer } } to OLEDdrawImage image x y { comment '_imgHeight has to be mod8 or 1-8' if (0 == image) { return 0 } local 'oldDelayGDUpd' _delayGDUpd _delayGDUpd = (booleanConstant true) for row# (maximum 1 ((minimum _imgHeight (64 - y)) / 8)) { local 'GDidx' ((((y / 8) * 128) + x) + 1) local 'imgDispl' ((row# - 1) * _imgWidth) for byte# (minimum _imgWidth (128 - x)) { comment 'If y is on page boundary, just copy' if (0 == (y % 8)) { atPut GDidx _GDBuffer (at (imgDispl + byte#) image) GDidx += 1 if (GDidx > 1024) { _delayGDUpd = oldDelayGDUpd if (not _delayGDUpd) { OLEDshowGDBuffer } return 0 } } else { comment 'Else, copy GDLowBits + shiftedByte + GDHiBits to GDBuffer and GDBuffer +128' local 'shiftedByte' ('_dec2hex' ((at (imgDispl + byte#) image) << (y % 8))) local 'temp' 0 local 'tempHI' ('[data:copyFromTo]' shiftedByte 1 2) local 'tempLO' ('[data:copyFromTo]' shiftedByte 3 4) local 'GDLowBits' ((1 << (y % 8)) - 1) local 'GDHiBits' (255 - GDLowBits) tempLO = ((hexToInt tempLO) | ((at GDidx _GDBuffer) & GDLowBits)) atPut GDidx _GDBuffer tempLO if ((GDidx + 128) <= 1024) { tempHI = ((hexToInt tempHI) | ((at (GDidx + 128) _GDBuffer) & GDHiBits)) atPut (GDidx + 128) _GDBuffer tempHI } GDidx += 1 if (GDidx > 1024) { _delayGDUpd = oldDelayGDUpd if (not _delayGDUpd) { OLEDshowGDBuffer } return 0 } } waitMillis 0 } y += 8 } _delayGDUpd = oldDelayGDUpd if (not _delayGDUpd) { OLEDshowGDBuffer } } to OLEDdrawLine x0 y0 x1 y1 erase { comment 'Draws a line from x0,y0 to x1,y1 using the Bresenham Algorithm x: 0-127 y: 0-63 plotLine(int x0, int y0, int x1, int y1) dx = abs(x1-x0); sx = x0= dy) /* e_xy+e_x > 0 */ err += dy; x0 += sx; end if if (e2 <= dx) /* e_xy+e_y < 0 */ err += dx; y0 += sy; end if end while' '_initCheck' local 'oldDelayGDUpd' _delayGDUpd _delayGDUpd = (booleanConstant true) results = ('[data:makeList]') local 'dx' (absoluteValue (x1 - x0)) local 'dy' (-1 * (absoluteValue (y1 - y0))) local 'err' (dx + dy) local 'e2' 0 local 'done' (booleanConstant false) if (x0 < x1) { local 'sx' 1 } else { local 'sx' -1 } if (y0 < y1) { local 'sy' 1 } else { local 'sy' -1 } repeatUntil done { OLEDpixel x0 y0 erase if (and (x0 == x1) (y0 == y1)) { done = (booleanConstant true) } e2 = (2 * err) if (e2 >= dy) { err += dy x0 += sx } if (e2 <= dx) { err += dx y0 += sy } } _delayGDUpd = oldDelayGDUpd if (not _delayGDUpd) { OLEDshowGDBuffer } } to OLEDdrawRect TLx TLy width height erase cornerRad optionalFilled { comment 'Draw Rectangle with optional rounded corners with radius R. Does not check for reversed rectangle coordinates for round corners.' '_initCheck' local 'oldDelayGDUpd' _delayGDUpd _delayGDUpd = (booleanConstant true) local 'BRx' (TLx + width) local 'BRy' (TLy + height) local 'filled' (argOrDefault 7 (booleanConstant false)) if (cornerRad < 3) { cornerRad = 0 } else { '_corner' 'TL' TLx TLy cornerRad erase filled '_corner' 'TR' BRx TLy cornerRad erase filled '_corner' 'BR' BRx BRy cornerRad erase filled '_corner' 'BL' TLx BRy cornerRad erase filled } comment 'TOP' for f ('[data:range]' TLy (ifExpression filled (TLy + cornerRad) TLy)) { OLEDdrawLine (TLx + cornerRad) f (BRx - cornerRad) f erase } comment 'MIDDLE' if filled { for f ('[data:range]' ((TLy + 1) + cornerRad) ((BRy - 1) - cornerRad)) { OLEDdrawLine TLx f BRx f erase } } else { OLEDdrawLine TLx ((BRy - 1) - cornerRad) TLx ((TLy + 1) + cornerRad) erase OLEDdrawLine BRx ((TLy + 1) + cornerRad) BRx ((BRy - 1) - cornerRad) erase } comment 'BOTTOM' for f ('[data:range]' (ifExpression filled (BRy - cornerRad) BRy) BRy) { OLEDdrawLine (TLx + cornerRad) f (BRx - cornerRad) f erase } _delayGDUpd = oldDelayGDUpd if (not _delayGDUpd) { OLEDshowGDBuffer } } to OLEDfillRect x y w h erase { local 'oldDelayGDUpd' _delayGDUpd _delayGDUpd = (booleanConstant true) for row ('[data:range]' 0 (h - 1)) { for col ('[data:range]' 0 (w - 1)) { OLEDpixel (x + col) (y + row) erase } } _delayGDUpd = oldDelayGDUpd if (not _delayGDUpd) { OLEDshowGDBuffer } } to OLEDmakeImage spr# { comment 'Receives a spr# representing 5x5 image matrix. Converts it to a HOR array of 5 VERT bitmap numbers. This can be displayed with the draw image block. sprNum max is 33554431' '_initCheck' local 'spriteList' ('[data:newByteArray]' 5) local 'val' 0 for col 5 { for row ('[data:asByteArray]' ('[data:makeList]' 0 5 10 15 20)) { local 'bit' (col + row) local 'vertBit#' (bit / 5) if ((spr# & (1 << (bit - 1))) != 0) { if (col != 5) { val += (1 << vertBit#) } else { val += (1 << (vertBit# - 1)) } } } atPut col spriteList val val = 0 } _imgWidth = 5 _imgHeight = 5 return spriteList } to OLEDpixel x y erase { comment 'Places a pixel at x,y in the virtual GDBuffer Use OLEDshowGDBuffer to display it 8192 Pixels over 1024 Bytes x: 0-127 y: 0-63 page#: 0-7 pagePixel#: 0-7 GDIndex: 1-1024 RangeCheck: verify x and y are in range of display limits if X <= num <= Y' '_initCheck' local 'pagePixel#' (y % 8) local 'GDIndex' ((x + ((y / 8) * 128)) + 1) if (GDIndex <= 1024) { local 'byteBMP' (at GDIndex _GDBuffer) byteBMP = (ifExpression (not erase) (byteBMP | (1 << pagePixel#)) (byteBMP & ('~' (1 << pagePixel#)))) atPut GDIndex _GDBuffer byteBMP } } to OLEDsetVideo videoMode { comment 'Switches the entire display: Inverse: inverse video mode (bit 0 = on) Normal: normal video mode (bit 1 = on). Any image on the display will be preserved when mode changes.' '_initCheck' if (videoMode == 'Inverse') { local 'i2cCmd' 'A7' } else { local 'i2cCmd' 'A6' } '_sendCmd' i2cCmd } to OLEDshowGDBuffer { comment 'Copies contents of virtual _GDBuffer to display i2c: in 64 byte chunks for speed. page selection is used to work on OLED with SH1106 cipset spi: fastest is dump buffer _GDBuffer is in decimal' '_initCheck' '_sendCmd' '40' if (_comMode == 'i2c') { local 'idx' 1 for j 8 { '[sensors:i2cWrite]' OLEDi2cAddr ('[data:makeList]' (hexToInt '80') ((hexToInt 'B0') + (j - 1))) '_sendCmd' _ColumnOffset '_sendCmd' '10' '[sensors:i2cWrite]' OLEDi2cAddr ('[data:join]' _dataPrefix ('[data:copyFromTo]' _GDBuffer idx (idx + 63))) idx += 64 '[sensors:i2cWrite]' OLEDi2cAddr ('[data:join]' _dataPrefix ('[data:copyFromTo]' _GDBuffer idx (idx + 63))) idx += 64 } } else { '_SPIWriteData' '[sensors:spiExchange]' ('[data:copyFromTo]' _GDBuffer 1) } _delayGDUpd = (booleanConstant false) } to OLEDwrite string x y invFlag optionalScale { comment 'Writes strings to display at any x,y; processing CR LF and wrapping at col x. _textX and _textY are next write locations.' '_initCheck' local 'oldDelayGDUpd' _delayGDUpd _delayGDUpd = true '_verifyXY' x 0 local 'origX' x local 'scale' (argOrDefault 5 1) if (not (isType string 'string')) { string = ('[data:join]' '' string) } for char string { local 'unicode' ('[data:unicodeAt]' 1 char) if (13 == unicode) { noop } (10 == unicode) { x = origX y += (8 * scale) if (y > 63) { y = 0 } } (and (unicode > 31) (unicode < 128)) { '_OLEDwriteChar' char x y invFlag scale x += (7 * scale) if (x > 127) { x = origX y += (8 * scale) if (y > 63) { y = 0 } } } else { comment 'bad char - STOP' sayIt 'Invalid CHAR value:' char 'uniCode:' unicode stopTask } } _textX = x _textY = y _delayGDUpd = oldDelayGDUpd if (not _delayGDUpd) { OLEDshowGDBuffer } } to OLEDwru { comment 'Next Row and Column to print _textY, _textX' return ('[data:asByteArray]' ('[data:makeList]' _textY _textX)) } to '_GDDRAMoff' { comment 'Displays a full empty screen of reverse video. It disengages the hardware GDBuffer. ' local 'i2cCmd' 'A5' '_sendCmd' i2cCmd } to '_GDDRAMon' { comment 'It disables the GDDRAMoff mode and engages the hardware GDBuffer for display content.' local 'i2cCmd' 'A4' '_sendCmd' i2cCmd } to '_OLEDcursorReset' { comment 'Sets display bounds to full range and resets cursor to the origin top-left (0,0) Rows: 0-7 Cols: 0-127 Cursor position is affected by any display operation and also by OLEDColMode block.' '_initCheck' local 'cmdString' '22,00,07,21,00,7F' '_sendCmd' cmdString _textX = 0 _textY = 0 } to '_OLEDflip' flip { comment 'Flips the display initialization horizontally or vertically. Horizontal or vertical is based on the pin connector location.' if flip { '_sendCmd' 'A0,C0' } else { '_sendCmd' 'A1,C8' } } to '_OLEDreset' pin { comment 'Does a power off and on on the display, thus forcing a hardware initialization.' digitalWriteOp pin false waitMillis 1 digitalWriteOp pin true waitMillis 1 } to '_OLEDsetDisplay' onoff { comment 'Puts the display into Off:SLEEP On:WAKE mode. Images on display are preserved.' '_initCheck' if (onoff == 'On') { local 'i2cCmd' 'AF' } else { local 'i2cCmd' 'AE' } '_sendCmd' i2cCmd } to '_OLEDwriteChar' char x y inverse scale { local 'shape' ('[misc:shapeforChar]' char) if (shape == 0) { return 0 } for row ('[data:range]' 0 ((9 * scale) - 1)) { for col ('[data:range]' 0 ((7 * scale) - 1)) { OLEDpixel (x + col) (y + row) (not inverse) } } x += scale y += scale local 'y0' y for c 5 { local 'mask' 1 y = y0 repeat 8 { repeat scale { repeat scale { if (((at c shape) & mask) != 0) { OLEDpixel x y inverse } else { } x += 1 } x += (0 - scale) y += 1 } mask = (mask << 1) } x += scale } } to '_SPIWriteCmd' { comment 'In SPI mode, we send either a write command or write data control code. Then follow it with the appropriate command/data bundle.' digitalWriteOp _dcPin false } to '_SPIWriteData' { comment 'In SPI mode, we send either a write command or write data control code. Then follow it with the appropriate command/data bundle.' digitalWriteOp _dcPin true } to '_clearDisplay' { comment 'Clear Screen without initializing _GDBuffer' '_initCheck' '_OLEDcursorReset' local 'clearBuff' ('[data:newByteArray]' 128 0) '_sendCmd' '40' for j 8 { '[sensors:i2cWrite]' OLEDi2cAddr ('[data:makeList]' (hexToInt '80') ((hexToInt 'B0') + (j - 1))) '_sendCmd' '01' '_sendCmd' '10' '[sensors:i2cWrite]' OLEDi2cAddr ('[data:join]' _dataPrefix clearBuff) } for j 8 { '[sensors:i2cWrite]' OLEDi2cAddr ('[data:makeList]' (hexToInt '80') ((hexToInt 'B0') + (j - 1))) '_sendCmd' '03' '_sendCmd' '10' '[sensors:i2cWrite]' OLEDi2cAddr ('[data:join]' _dataPrefix clearBuff) } } to '_corner' loc cx cy r erase filled { comment 'Calculates and displays the rounded corners for the rectangles. loc is one of TL, TR, BL, BR ... topLeft, topright, bottomleft, bottomright cx,cy are the corner coordinates for the rounded corner. r is the radius in pixels of the arc to be calculated.' local 'x' (-1 * r) local 'y' 0 local 'err' (2 - (2 * r)) if (loc == 'TL') { cx += r cy += r } (loc == 'TR') { cx += (0 - r) cy += r } (loc == 'BL') { cx += r cy += (0 - r) } (loc == 'BR') { cx += (0 - r) cy += (0 - r) } repeatUntil (x > 0) { if filled { if (loc == 'TL') { OLEDdrawLine (cx + x) (cy - y) cx (cy - y) erase } (loc == 'TR') { OLEDdrawLine cx (cy + x) (cx + y) (cy + x) erase } (loc == 'BL') { OLEDdrawLine (cx - y) (cy - x) cx (cy - x) erase } (loc == 'BR') { OLEDdrawLine cx (cy + y) (cx - x) (cy + y) erase } } else { if (loc == 'TL') { OLEDpixel (cx + x) (cy - y) erase } (loc == 'TR') { OLEDpixel (cx + y) (cy + x) erase } (loc == 'BL') { OLEDpixel (cx - y) (cy - x) erase } (loc == 'BR') { OLEDpixel (cx - x) (cy + y) erase } } r = err if (r <= y) { y += 1 err = (err + ((y * 2) + 1)) } if (or (r > x) (err > y)) { x += 1 err = (err + ((x * 2) + 1)) } } } to '_dec2hex' num { comment 'Fast version w/o inversion. (~115uSecs)' local '_hexTbl' '0123456789ABCDEF' local 'hexNum' '' repeatUntil (num < 0) { local 'temp' ('[data:join]' (at ((num % 16) + 1) _hexTbl) hexNum) hexNum = temp num = (ifExpression ((num / 16) != 0) (num / 16) -1) } comment 'If not half-byte length, pad it.' hexNum = ('[data:join]' ('[data:copyFromTo]' '000' 1 (4 - (size hexNum))) hexNum) return hexNum } to '_initCheck' { comment 'Verifies Library initialization status.' if (not OLEDReady) { sayIt 'Display needs to be initialized before using the library blocks.' stopTask } } to '_initDisplayHW' { comment '*** DO NOT CHANGE THESE WITHOUT UNDERSTANDING THEIR MEANINGS *** *** IT CAN MAKE THE DISPLAY NOT OPERATE CORRECTLY *** Initializes display HW timings OLED INITIALIZATION STRINGS: 1306: pins are on top 1309: pins are on the right A8 3F - Multiplex Ratio 0F-3F (16mux to 64mux) D3 00 - Display Offset (vertical) 00-63 40 - Display Start Line 40-7F (40:0) A0 - Set Segment Remap A0: Remap 0:0, A1: 127:0 [A0] C0 - Scan Direction C0:0-127, C8:127-0 [C0] DA 12 - COM pin hw config 02:double-high 12:normal bits 81 7F - Set contrast 00-FF (00:dimmest, FF:brightest) A4 - A4:display shows GDRAM, A5:display all ON pixels A6 - A6:Normal video, A7:Inverse video D5 F0 - Set display clock HB:OSC freq / LB:divide ratio (00-FF) D9 22 - Set pre-charge period HB:Phase2 / LB:Phase1 (1-15 DCLK) DB 20 - Set Vcomh deselect level 20:1306(~0.77xVcc), 34:1309(~0.78xVcc) 8D 14 - Charge Pump Setting 10:disable CP, 14:enable CP 20 00 - Set HORIZONTAL mode AF - Display ON/OFF AE:off (sleep mode), AF:on' if (_resetPin != '-') { '_OLEDreset' _resetPin } local 'initCmd' 'A8,3F,D3,00,40,A1,C8,DA,12,81,7F,A4,A6,D5,70,D9,22,DB,34,8D,14,20,00,AF' '_sendCmd' initCmd } to '_initLibrary' { comment 'Sets all Library variables and initializes the display hardware. It also initializes the virtual GDBuffer, and clears the display. NOTE: Displays supported: OLED1306 (0.96in) and OLED1309 (2.42"), selected via boolean position. OLED1309 requires the connection of RESET pin to a digital pin. Both displays are 128x64 pixels or 16x8 characters in size. Make sure the character hex tables are \n (LF) terminated. Otherwise, extra lines are added in between and it gets messed up. eg: A: 00,7C,12,11,11,12,7C,00\n Max i2c IO is 64 bytes: buffered writes need to be max that size - 16 x 64' _eol = ('[data:unicodeString]' 10) comment 'Col starts are in HEX' _dataPrefix = ('[data:newByteArray]' 1 (hexToInt '40')) comment 'There is no FF/255 in th cHexTBL. this value is used to build the byteArray.' _cDecTBL = ('[data:newByteArray]' 768 255) _GDBuffer = ('[data:newByteArray]' 1024) _textX = 0 _textY = 0 _delayGDUpd = (booleanConstant false) OLEDReady = (booleanConstant true) '_OLEDsetDisplay' 'On' repeat 2 { setUserLED true waitMillis 50 setUserLED false waitMillis 50 } } to '_process image data' image { comment 'Processes the new hex image data format. Each _imgHex gets added to _imgData in DEC byteArray format. _byteCount keeps track of total bytes processed. _imgWidth and _imgHeight (mod8) are dimensions.' local '_ptr' 1 comment '_imgData is not initialized yet.' if (or (0 == _imgData) (not (isType _imgData 'byte array'))) { _imgWidth = (hexToInt ('[data:copyFromTo]' image 1 2)) _imgHeight = (hexToInt ('[data:copyFromTo]' image 3 4)) image = ('[data:copyFromTo]' image 5) comment 'If _imgHeight is partial byte, adjust to full byte' if (not (0 == (_imgHeight % 8))) { _imgHeight += (8 - (_imgHeight % 8)) } _imgData = ('[data:newByteArray]' ((_imgWidth * _imgHeight) / 8)) } for byte ('[data:range]' 1 (size image) 2) { atPut (_byteCount + _ptr) _imgData (hexToInt ('[data:copyFromTo]' image byte (byte + 1))) _ptr += 1 } _byteCount += (_ptr - 1) } to '_sendCmd' cmdString { comment 'Input is a comma sep. STRING. MAKE SURE ALL INPUT PARAMETERS ARE HEX' local '_cList' ('[data:split]' cmdString ',') local 'cmdPrefix' (hexToInt '80') if ('i2c' == _comMode) { for cmd _cList { '[sensors:i2cWrite]' OLEDi2cAddr ('[data:asByteArray]' ('[data:makeList]' cmdPrefix (hexToInt cmd))) } } else { '_SPIWriteCmd' for cmd _cList { spiSend (hexToInt cmd) } } } to '_verifyXY' x y { if (and (and (x >= 0) (x <= 127)) (and (y >= 0) (y <= 63))) { return 0 } else { sayIt 'x or y value error:' ('[data:unicodeString]' 10) 'x:' x ' y:' y stopTask } } to 'defer display updates' { _delayGDUpd = (booleanConstant true) } module PID Operators author 'Russell Owen' version 0 9 description 'PID Control Loop' variables _pid__initialized _pid__numPIDs _pid__prevError _pid__prevMicros _pid__prevIntegral spec 'r' 'pid_computePID' 'compute pid at index _ error _ p coeff _ i coeff _ d coeff _ max integral _ (ignored if 0)' 'num num num num num num' 1 0 1 0 0 0 spec ' ' 'pid_resetPID' 'reset pid at index _' 'num' 1 space spec 'r' 'pid_constrainValue' 'constrain value _ deadband _ minimum _ maximum _' 'num num num num' 0 0 0 1000 spec 'r' 'pid_applySign' 'apply sign of _ to value _' 'num num' 1 0 space spec ' ' '_pid_extendPIDLists' '_extend pid lists; index _' 'num' 1 spec ' ' '_pid_initLibrary' '_init pid library' to '_pid_extendPIDLists' index { comment 'Extend global list variables, if needed, so that they are large enough to use the specified index.' '_pid_initLibrary' repeatUntil ((size _pid__prevError) >= index) { '[data:addLast]' 0 _pid__prevError '[data:addLast]' -1 _pid__prevMicros '[data:addLast]' 0 _pid__prevIntegral } comment 'Set _pid__numPIDs so pid_computePID can efficiently decide if the list needs to be extended' _pid__numPIDs = (size _pid__prevError) } to '_pid_initLibrary' { comment 'Create global lists.' if (not _pid__initialized) { _pid__prevError = (newList 0) _pid__prevMicros = (newList 0) _pid__prevIntegral = (newList 0) _pid__initialized = (booleanConstant true) } } to pid_applySign sign value { comment 'If sign < 0 return -value else return value' return (ifExpression (sign >= 0) value (0 - value)) } to pid_computePID index error pCoeff iCoeff dCoeff maxIntegral { comment 'Compute the next PID value value, using inputs: * index: index of the PID loop * error: error to correct * pCoeff: proportional coefficient (corr/error) * iCoeff: integral coefficient (corr-msec/error) * dCoeff: derivitive coefficient (corr/error-msec) * maxIntegral: maximum absolute value of the integrated error; ignored if 0' if (_pid__numPIDs < index) { '_pid_extendPIDLists' index } local 'pValue' 0 local 'iValue' 0 local 'dValue' 0 local 'integral' 0 local 'currMicros' (microsOp) local 'prevMicros' (at index _pid__prevMicros) pValue = (pCoeff * error) if (prevMicros >= 0) { comment 'We have old data for this PID loop, so compute derivitive and integral contributions.' local 'deltaMicros' (microsSince prevMicros currMicros) local 'deltaErrorr' (error - (at index _pid__prevError)) dValue = (((dCoeff * deltaErrorr) * 1000) / deltaMicros) integral = (((error * deltaMicros) / 1000) + (at index _pid__prevIntegral)) if (and (maxIntegral > 0) ((absoluteValue integral) > maxIntegral)) { integral = (pid_applySign integral maxIntegral) } iValue = (iCoeff * integral) } atPut index _pid__prevMicros currMicros atPut index _pid__prevError error atPut index _pid__prevIntegral integral return (pValue + (iValue + dValue)) } to pid_constrainValue value deadband minimum maximum { comment 'Constrain a value as follows: If |value| < deadband: return 0. If |value| < minimum: return minimum with sign of value. If |value| > maximum: return maximum with sign of value.' local 'absValue' (absoluteValue value) if (absValue < deadband) { return 0 } (absValue < minimum) { return (pid_applySign value minimum) } (absValue > maximum) { return (pid_applySign value maximum) } else { return value } } to pid_resetPID index { comment 'Zero the recorded error data for a specific PID loop. Call this before starting each move, to avoid unwanted values from the integral and derivitive terms.' if (_pid__numPIDs < index) { '_pid_extendPIDLists' index } atPut index _pid__prevError 0 atPut index _pid__prevMicros -1 atPut index _pid__prevIntegral 0 } module Rhythm Control author MicroBlocks version 2 7 description 'Keeps time according to a tempo and time signature. Useful to create musical patterns. Includes abstractions for note durations, bars and beats. ' variables _rhy_tempo _rhy_bars _rhy_beat_duration _rhy_measures _rhy_reference_note _rhy_start_time spec ' ' 'rhy_set tempo' 'set tempo to _ bpm at _ / _ signature' 'num num num' 120 4 4 spec ' ' 'rhy_set bars' 'set pattern to _ bars' 'num' 16 space spec 'r' 'rhy_current bar' 'current bar' spec 'r' 'rhy_current beat' 'current beat' space spec ' ' 'rhy_wait until next' 'wait until next _ duration' 'num' spec ' ' 'rhy wait until beat' 'wait until beat _' 'num' 3 spec ' ' 'rhy wait until bar' 'wait until bar _' 'num' 2 space spec 'r' 'rhy_whole duration' '#SVG#note-whole' spec 'r' 'rhy_half duration' '#SVG#note-half' spec 'r' 'rhy_quarter duration' '#SVG#note-quarter' spec 'r' 'rhy_eighth duration' '#SVG#note-eighth' spec 'r' 'rhy_sixteenth duration' '#SVG#note-sixteenth' space spec 'r' 'rhy_dotted' '_ .' 'auto' '' spec 'r' 'rhy_triplet' '‿³ _' 'auto' '' space spec 'r' '_rhy duration of note value' '_rhy duration of note value _' 'auto' 4 spec 'r' '_rhy 2 ^' '_rhy 2 ^ _' 'auto' '10' spec 'r' '_rhy between' '_rhy _ between _ and _' 'auto auto auto' '10' '10' '10' to '_rhy 2 ^' n { local 'result' 1 repeat n { result = (result * 2) } return result } to '_rhy between' foo a b { return (and (foo >= a) (foo <= b)) } to '_rhy duration of note value' 'note value' { if (_rhy_beat_duration == 0) {'rhy_set tempo' 120 4 4} return ((_rhy_beat_duration * _rhy_reference_note) / (v 'note value')) } to 'rhy wait until bar' 'bar number' { if (_rhy_beat_duration == 0) {'rhy_set tempo' 120 4 4} if (_rhy_bars == 0) {'rhy_set bars' 16} local 'bar duration' (_rhy_measures * _rhy_beat_duration) local 'round duration' ((v 'bar duration') * _rhy_bars) local 'wakeTime' ((((v 'round duration') * (((millisOp) / (v 'round duration')) + 1)) + (((v 'bar number') - 1) * (v 'bar duration'))) - _rhy_start_time) waitMillis ((wakeTime - (millisOp)) % (v 'round duration')) } to 'rhy wait until beat' 'beat number' { if (_rhy_beat_duration == 0) {'rhy_set tempo' 120 4 4} local 'bar duration' (_rhy_measures * _rhy_beat_duration) local 'wakeTime' ((((v 'bar duration') * (((millisOp) / (v 'bar duration')) + 1)) + (((v 'beat number') - 1) * _rhy_beat_duration)) - _rhy_start_time) waitMillis ((wakeTime - (millisOp)) % (v 'bar duration')) } to 'rhy_current bar' { local 'bar duration' (_rhy_measures * _rhy_beat_duration) local 'round duration' ((v 'bar duration') * _rhy_bars) return (((((millisOp) - _rhy_start_time) % (v 'round duration')) / (v 'bar duration')) + 1) } to 'rhy_current beat' { local 'bar duration' (_rhy_measures * _rhy_beat_duration) return (((((millisOp) - _rhy_start_time) % (v 'bar duration')) / _rhy_beat_duration) + 1) } to rhy_dotted duration { return (duration + (duration / 2)) } to 'rhy_eighth duration' { return ('_rhy duration of note value' 8) } to 'rhy_half duration' { return ('_rhy duration of note value' 2) } to 'rhy_quarter duration' { return ('_rhy duration of note value' 4) } to 'rhy_set bars' bars { if (_rhy_measures == 0) {'rhy_set tempo' 240 4 4} _rhy_bars = bars _rhy_start_time = (millisOp) } to 'rhy_set tempo' tempo measures 'note value' { _rhy_tempo = tempo _rhy_reference_note = (v 'note value') _rhy_beat_duration = (60000 / _rhy_tempo) _rhy_measures = measures _rhy_start_time = (millisOp) } to 'rhy_sixteenth duration' { return ('_rhy duration of note value' 16) } to rhy_triplet duration { return ((duration * 2) / 3) } to 'rhy_wait until next' duration { local 'wakeTime' ((duration * (((millisOp) / duration) + 1)) - _rhy_start_time) waitMillis ((wakeTime - (millisOp)) % duration) } to 'rhy_whole duration' { return ('_rhy duration of note value' 1) } module 'Scales & Chords' Data author MicroBlocks version 2 12 depends MIDI Rhythm Tone 'Analog Synthesis' choices sound_dst 'MIDI channel' 'tone pin' 'gate trigger' choices scales ionian dorian phrygian lydian mixolydian aeolian locrian chromatic tonal choices pentatonics major minor choices chords major minor diminished 'major 7th' 'minor 7th' Maj7 description 'Lists of relative semitone intervals for all Western diatonic scales plus the major and minor pentatonic scales. It also includes intervals for the most common chords and progressions, an abstraction of the circle of fifths, an arpeggiator and an improviser. ' variables _scales_degrees _scales_circle5ths _scales_delay spec 'r' 'scales_scale' '_ scale root _ : steps _' 'menu.scales num num' 'ionian' 60 7 spec 'r' 'scales_pentatonic scale' 'pentatonic _ scale root _ : steps _' 'menu.pentatonics num num' 'major' 60 7 space spec 'r' 'scales_chord' '_ chord root _ : steps _' 'menu.chords num num' 'major' 60 4 spec 'r' 'scales_chord with roman numeral' '_ chord for root _ : steps _' 'auto auto auto' 'IV' 60 4 space spec ' ' 'scales_arpeggiate' 'arpeggiate _ in order _ duration _ and volume _ on _ _' 'str str str num menu.sound_dst auto' '60-63' '1,3,1,2' '4,8' 100 'MIDI channel' 1 spec 'r' 'scales_improvisation' 'improvised arpeggio over _ for _ bars with durations _' 'auto num str' 'aListOfNotes' 2 '4,4,8' spec ' ' 'scales_improv' 'improvise over _ for _ bars with durations _ on _ _ volume _' 'auto auto str menu.sound_dst auto auto' 'aListOfNotes' '10' '4,4,8' 'MIDI channel' 1 100 space spec 'r' 'scales_transposed' '_ transposed _ semitones' 'auto auto' 60 12 space spec 'r' 'scales_progression' 'chord progression _ with root _ : and _ steps per chord' 'auto auto auto' 'I,IV,viiº,iii,vi,ii,V,I' 60 3 spec 'r' 'scales_circle rotation' 'rotation _ of circle of 5ths _ M _ m _ º' 'auto str str str' 0 '-1,0,1' '-1,0,1' '0' space spec 'r' '_scales intervals' '_scales intervals _ with root _ and _ steps' 'auto auto auto' 'aList' 60 10 spec 'r' '_scales degrees' '_scales degrees' spec ' ' '_scales_init_circle' '_scales_init_circle' spec 'r' '_scales circular item' '_scales circular item _ of _' 'auto auto' '10' '' spec 'r' '_scales parsed durations' '_scales parsed durations _' 'str' '4,4,8' spec 'r' '_scales parsed shorthand' '_scales parsed shorthand _' 'str' '1,3-5,32' to '_scales circular item' n list { if (n < 1) { return (at ((size list) - ((absoluteValue n) % (size list))) list) } else { return (at (((n - 1) % (size list)) + 1) list) } } to '_scales degrees' { if (_scales_degrees == 0) {_scales_degrees = ('[data:makeList]' 'I' 'II' 'III' 'IV' 'V' 'VI' 'VII' 'i' 'ii' 'iii' 'iv' 'v' 'vi' 'vii')} return _scales_degrees } to '_scales intervals' intervals root steps { local 'notes' ('[data:makeList]') for step steps { '[data:addLast]' ((at (((step - 1) % (size intervals)) + 1) intervals) + (root + (12 * ((step - 1) / (size intervals))))) notes } return notes } to '_scales parsed durations' 'duration string' { local 'duration' ('_scales parsed shorthand' (v 'duration string')) for idx (size duration) { if ((at idx duration) < 33) { local 'ms' ('_rhy duration of note value' ('[data:convertType]' (at idx duration) 'number')) if ((at 'last' (at idx duration)) == '.') { ms += (ms / 2) } ((at 'last' (at idx duration)) == ')') { ms = ((ms * 2) / 3) } atPut idx duration ms } } return duration } to '_scales parsed shorthand' string { local 'list' ('[data:makeList]') local 'item' '' local 'i' 1 repeatUntil (i > (size string)) { repeatUntil (or (i > (size string)) (or ((at i string) == ',') ((at i string) == '-'))) { item = ('[data:join]' item (at i string)) i += 1 } '[data:addLast]' item list item = '' if (i <= (size string)) { if ((at i string) == '-') { repeatUntil (or ((at i string) == ',') (i == (size string))) { i += 1 item = ('[data:join]' item (at i string)) } list = ('[data:join]' ('[data:copyFromTo]' list 1 ((size list) - 1)) ('[data:range]' (at 'last' list) item)) number = '' } i += 1 } } return list } to '_scales_init_circle' { if (_scales_circle5ths == 0) {_scales_circle5ths = ('[data:makeList]' ('[data:makeList]' 'I' 'V' 'II' 'VI' 'III' 'VII' 'bV' 'bII' 'bVI' 'bIII' 'bVII' 'IV') ('[data:makeList]' 'vi' 'iii' 'vii' 'bv' 'bii' 'bvi' 'biii' 'bvii' 'iv' 'i' 'v' 'ii') ('[data:makeList]' 'VIIº' 'bVº' 'bIIº' 'bVIº' 'bIIIº' 'bVIIº' 'IVº' 'Iº' 'Vº' 'IIº' 'VIº' 'IIIº'))} } to scales_arpeggiate 'note list' order duration volume destination channel { if (_scales_delay == 0) {if ((boardType) == 'Boardie') { _scales_delay = 12 } ((boardType) == 'micro:bit v2') { _scales_delay = 5 } (destination == 'gate trigger') { _scales_delay = 4 }} if (isType (v 'note list') 'string') {'note list' = ('_scales parsed shorthand' (v 'note list'))} if (order == '') { order = ('[data:range]' 1 (size (v 'note list'))) } (isType order 'string') { order = ('_scales parsed shorthand' order) } if (isType duration 'string') { duration = ('_scales parsed durations' duration) } for i (size order) { local 'start' (millisOp) if (isType duration 'list') { local 'dur' ('_scales circular item' i duration) } else { local 'dur' duration } if (('[data:convertType]' (at i order) 'number') == 0) { waitMillis ((dur - ((millisOp) - start)) - _scales_delay) } else { if (destination == 'MIDI channel') { 'MIDI_play MIDI note' ('[data:convertType]' (at (at i order) (v 'note list')) 'number') ((dur - ((millisOp) - start)) - _scales_delay) channel volume } (destination == 'gate trigger') { 'asynth trigger gate' channel ('[data:convertType]' (at (at i order) (v 'note list')) 'number') waitMillis ((dur - ((millisOp) - start)) - _scales_delay) } else { if (channel != '') {_tonePin = channel} playMIDIKey ('[data:convertType]' (at (at i order) (v 'note list')) 'number') ((dur - ((millisOp) - start)) - 1) } } } } to scales_chord which root 'optional steps' { local 'steps' (argOrDefault 3 4) return ('_scales intervals' (at ('[data:find]' which ('[data:makeList]' 'major' 'minor' 'diminished' 'major 7th' 'minor 7th' 'Maj7')) ('[data:makeList]' ('[data:makeList]' 0 4 7) ('[data:makeList]' 0 3 7) ('[data:makeList]' 0 3 6) ('[data:makeList]' 0 4 7 10) ('[data:makeList]' 0 3 7 10) ('[data:makeList]' 0 4 7 11))) root steps) } to 'scales_chord with roman numeral' degree root 'optional steps' { local 'steps' (argOrDefault 3 3) local 'add 7th' (('[data:find]' '7' degree) > 0) local 'diminished' ((('[data:find]' 'o' degree) + ('[data:find]' 'º' degree)) > 0) local 'chord name' '' local 'flat' (('[data:find]' 'b' degree) == 1) if flat { degree = ('[data:copyFromTo]' degree 2 (size degree)) root += -1 } if (v 'add 7th') { degree = ('[data:copyFromTo]' degree 1 ((size degree) - 1)) } if diminished { degree = ('[data:copyFromTo]' degree 1 ((size degree) - 1)) } local 'index' ('[data:find]' degree ('_scales degrees')) if (index > 7) { 'chord name' = 'minor' } else { 'chord name' = 'major' } if (v 'add 7th') { 'chord name' = ('[data:join]' (v 'chord name') ' 7th') } if diminished { 'chord name' = 'diminished' } return (scales_chord (v 'chord name') (at (((index - 1) % 7) + 1) ('[data:makeList]' (root + 0) (root + 2) (root + 4) (root + 5) (root + 7) (root + 9) (root + 11))) steps) } to 'scales_circle rotation' n majors minors dims { '_scales_init_circle' local 'degrees' ('[data:makeList]') if (majors != '') { for index ('[data:split]' majors ',') { '[data:addLast]' ('_scales circular item' ((index + n) + 1) (at 1 _scales_circle5ths)) degrees } } if (minors != '') { for index ('[data:split]' minors ',') { '[data:addLast]' ('_scales circular item' ((index + n) + 1) (at 2 _scales_circle5ths)) degrees } } if (dims != '') { for index ('[data:split]' dims ',') { '[data:addLast]' ('_scales circular item' ((index + n) + 1) (at 3 _scales_circle5ths)) degrees } } return ('[data:joinStrings]' degrees ',') } to scales_improv scale 'bar count' 'possible durations' destination channel volume { if (_rhy_beat_duration == 0) {'rhy_set tempo' 120 4 4} local 'next note' (at 'random' scale) local 'max jump' 4 if (isType (v 'possible durations') 'string') { 'possible durations' = ('_scales parsed durations' (v 'possible durations')) } local 'next duration' (at 'random' (v 'possible durations')) local 'total duration' ((v 'bar count') * (_rhy_beat_duration * _rhy_measures)) local 'next vol' volume repeatUntil ((v 'total duration') <= 0) { 'total duration' += (0 - (v 'next duration')) if (((v 'total duration') - (v 'next duration')) >= 0) { local 'index' (('[data:find]' (v 'next note') scale) + (random (random 0 1) (v 'max jump'))) if (or (index > (size scale)) (index < 1)) { 'max jump' = (0 - (v 'max jump')) 'next note' = ('_scales circular item' (index + (v 'max jump')) scale) } else { 'next note' = (at index scale) } if ((random 1 3) < 3) { 'next duration' = (at 'random' (v 'possible durations')) } if (destination == 'MIDI channel') { if (isType volume 'list') {'next vol' = (random (at 1 volume) (at 2 volume))} 'MIDI_play MIDI note' (v 'next note') (v 'next duration') channel (v 'next vol') } (destination == 'gate trigger') { 'asynth trigger gate' channel (v 'next note') } else { if (channel != '') {_tonePin = channel} playMIDIKey (v 'next note') (v 'next duration') } if ((random 1 3) == 3) { waitMillis (v 'next duration') } } } } to scales_improvisation scale 'bar count' 'possible durations' { local 'impro' ('[data:makeList]' ('[data:makeList]') ('[data:makeList]')) if (_rhy_beat_duration == 0) { 'rhy_set tempo' 120 4 4 } local 'next note' (at 'random' scale) local 'max jump' 4 if (isType (v 'possible durations') 'string') { 'possible durations' = ('_scales parsed durations' (v 'possible durations')) } local 'next duration' (at 'random' (v 'possible durations')) local 'total duration' ((v 'bar count') * (_rhy_beat_duration * _rhy_measures)) repeatUntil ((v 'total duration') <= 0) { 'total duration' += (0 - (v 'next duration')) if (((v 'total duration') - (v 'next duration')) >= 0) { local 'index' (('[data:find]' (v 'next note') scale) + (random (random 0 1) (v 'max jump'))) if (or (index > (size scale)) (index < 1)) { 'max jump' = (0 - (v 'max jump')) 'next note' = ('_scales circular item' (index + (v 'max jump')) scale) } else { 'next note' = (at index scale) } if ((random 1 3) < 3) { 'next duration' = (at 'random' (v 'possible durations')) } '[data:addLast]' (v 'next note') (at 1 impro) '[data:addLast]' (v 'next duration') (at 2 impro) if ((random 1 3) == 3) { '[data:addLast]' 0 (at 1 impro) '[data:addLast]' (v 'next duration') (at 2 impro) } } } return impro } to 'scales_pentatonic scale' which root 'optional steps' { local 'steps' (argOrDefault 3 5) return ('_scales intervals' (at ('[data:find]' which ('[data:makeList]' 'major' 'minor')) ('[data:makeList]' ('[data:makeList]' 0 2 4 7 9) ('[data:makeList]' 0 3 5 7 10))) root steps) } to scales_progression degrees root 'optional steps' { degrees = ('[data:split]' degrees ',') local 'steps' (argOrDefault 3 3) local 'chords' ('[data:makeList]') for i (size degrees) { if (isType steps 'list') { local 'chord steps' (at i steps) } else { local 'chord steps' steps } '[data:addLast]' ('scales_chord with roman numeral' (at i degrees) root (v 'chord steps')) chords } return chords } to scales_scale which root 'optional steps' { local 'steps' (argOrDefault 3 7) return ('_scales intervals' (at ('[data:find]' which ('[data:makeList]' 'ionian' 'dorian' 'phrygian' 'lydian' 'mixolydian' 'aeolian' 'locrian' 'chromatic' 'tonal')) ('[data:makeList]' ('[data:makeList]' 0 2 4 5 7 9 11) ('[data:makeList]' 0 2 3 5 7 9 10) ('[data:makeList]' 0 1 3 5 7 8 10) ('[data:makeList]' 0 2 4 6 7 9 11) ('[data:makeList]' 0 2 4 5 7 9 10) ('[data:makeList]' 0 2 3 5 7 8 10) ('[data:makeList]' 0 1 3 5 6 8 10) ('[data:range]' 0 11) ('[data:range]' 0 11 2))) root steps) } to scales_transposed 'note or chord' semitones { if (isType (v 'note or chord') 'list') { local 'notes' (newList (size (v 'note or chord'))) for i (size (v 'note or chord')) { atPut i notes ((at i (v 'note or chord')) + semitones) } return notes } else { return ((v 'note or chord') + semitones) } } module Servo Output author MicroBlocks version 1 4 tags servo motor angle rotation position description 'Control both positional (angle) and rotational servo motors. ' variables _servoPin _servoPulseWidth spec ' ' 'setServoAngle' 'set servo _ to _ degrees (-90 to 90)' 'num num' 1 90 spec ' ' 'setServoSpeed' 'set servo _ to speed _ (-100 to 100)' 'num num' 1 100 spec ' ' 'stopServo' 'stop servo _' 'num' 1 spec 'r' '_servoIndex' '_servoIndex _' 'num' 1 spec ' ' '_servoPulse' '_servoPulse pin _ usecs _' 'num num' 1 1500 spec ' ' '_servoUpdateLoop' '_servoUpdateLoop' to '_servoIndex' which { if (_servoPin == 0) { _servoPin = ('[data:makeList]') _servoPulseWidth = ('[data:makeList]') sendBroadcast '_servoUpdateLoop' } local 'i' ('[data:find]' which _servoPin) if (i < 0) { comment 'Add new pin' '[data:addLast]' which _servoPin '[data:addLast]' 1500 _servoPulseWidth i = (size _servoPin) } return i } to '_servoPulse' pin usecs { if (usecs == 0) { comment 'Servo stopped; do nothing' return 0 } usecs = (maximum 500 (minimum usecs 2900)) comment 'Split wait into a long wait followed by a wait of <= 30 usecs for greater accuracy' local 'endTime' ((microsOp) + usecs) digitalWriteOp pin true waitMicros (usecs - 30) waitMicros (endTime - (microsOp)) digitalWriteOp pin false } to '_servoUpdateLoop' { forever { if (_servoPin != 0) { comment 'If the _servoPin list is not 0, update the servos' for i (size _servoPin) { local 'pin' (at i _servoPin) local 'usecs' (at i _servoPulseWidth) if (and (pin >= 0) (usecs != 0)) { '_servoPulse' pin usecs } } waitMillis 15 } } } to setServoAngle which degrees optionalReverse { local 'reversed' false if ((pushArgCount) > 2) { reversed = optionalReverse } if reversed { degrees = (0 - degrees) } local 'pulseWidth' (1500 - (10 * degrees)) if ('[io:hasServo]') { '[io:setServo]' which pulseWidth } else { atPut ('_servoIndex' which) _servoPulseWidth pulseWidth } } to setServoSpeed which speed optionalReverse { local 'reversed' false if ((pushArgCount) > 2) { reversed = optionalReverse } if reversed { speed = (0 - speed) } local 'pulseWidth' (1500 - (10 * speed)) if ((absoluteValue speed) < 2) { pulseWidth = 0 } if ('[io:hasServo]') { '[io:setServo]' which pulseWidth } else { atPut ('_servoIndex' which) _servoPulseWidth pulseWidth } } to stopServo which { if ('[io:hasServo]') { '[io:setServo]' which 0 } else { atPut ('_servoIndex' which) _servoPulseWidth 0 } } module Tone Output author MicroBlocks version 1 10 tags tone sound music audio note speaker choices tone_NoteName 'nt;c' 'nt;c#' 'nt;d' 'nt;d#' 'nt;e' 'nt;f' 'nt;f#' 'nt;g' 'nt;g#' 'nt;a' 'nt;a#' 'nt;b' description 'Audio tone generation. Make music with MicroBlocks! ' variables _tonePin _toneInitalized _toneLoopOverhead _toneNoteNames _toneArezzoNotes _toneFrequencies spec ' ' 'play tone' 'play note _ octave _ for _ ms' 'str.tone_NoteName num num' 'nt;c' 0 500 spec ' ' 'playMIDIKey' 'play midi key _ for _ ms' 'num num' 60 500 spec ' ' 'play frequency' 'play frequency _ for _ ms' 'num num' 261 500 space spec ' ' 'startTone' 'start tone _ Hz' 'num' 440 spec ' ' 'stopTone' 'stop tone' space spec ' ' 'attach buzzer to pin' 'attach buzzer to pin _' 'auto' '' space spec 'r' '_measureLoopOverhead' '_measureLoopOverhead' spec 'r' '_baseFreqForNote' '_baseFreqForNote _' 'auto' 'c' spec 'r' '_baseFreqForSemitone' '_baseFreqForSemitone _' 'num' 0 spec ' ' '_toneLoop' '_toneLoop _ for _ ms' 'num num' 440000 100 spec 'r' '_trimmedLowercase' '_trimmedLowercase _' 'str' 'A. b C...' spec ' ' '_tone init note names' '_tone init note names' to '_baseFreqForNote' note { comment 'Return the frequency for the given note in the middle-C octave scaled by 1000. For example, return 440000 (440Hz) for A. Note names may be upper or lower case. Note names may be followed by # for a sharp or b for a flat.' local 'normalized note' ('_trimmedLowercase' note) 'normalized note' = (ifExpression ((at 1 (v 'normalized note')) == 'n') (v 'normalized note') ('[data:join]' 'nt;' (v 'normalized note'))) '_tone init note names' if (('[data:find]' (v 'normalized note') _toneArezzoNotes) > 0) { return ('_baseFreqForSemitone' ('[data:find]' (v 'normalized note') _toneArezzoNotes)) } else { return ('_baseFreqForSemitone' ('[data:find]' (v 'normalized note') _toneNoteNames)) } } to '_baseFreqForSemitone' semitone { if (_toneFrequencies == 0) {_toneFrequencies = ('[data:makeList]' 261626 277183 293665 311127 329628 349228 369994 391995 415305 440000 466164 493883 246942 277183 277183 311127 311127 349228 329628 369994 369994 415305 415305 466164 466164 523252)} if (and (1 <= semitone) (semitone <= (size _toneFrequencies))) { return (at semitone _toneFrequencies) } else { comment 'Bad note name; return 10 Hz' return 10000 } } to '_measureLoopOverhead' { comment 'Measure the loop overhead on this device' local 'halfCycle' 100 local 'startT' (microsOp) repeat 100 { digitalWriteOp _tonePin false waitMicros halfCycle digitalWriteOp _tonePin false waitMicros halfCycle } local 'usecs' ((microsOp) - startT) return ((usecs - 20000) / 200) } to '_tone init note names' { if (_toneNoteNames == 0) { _toneNoteNames = ('[data:makeList]' 'nt;c' 'nt;c#' 'nt;d' 'nt;d#' 'nt;e' 'nt;f' 'nt;f#' 'nt;g' 'nt;g#' 'nt;a' 'nt;a#' 'nt;b' 'nt;c_' 'nt;db' 'nt;d_' 'nt;eb' 'nt;e_' 'nt;e#' 'nt;f_' 'nt;gb' 'nt;g_' 'nt;ab' 'nt;a_' 'nt;bb' 'nt;b_' 'nt;b#') _toneArezzoNotes = ('[data:makeList]' 'nt;do' 'nt;do#' 'nt;re' 'nt;re#' 'nt;mi' 'nt;fa' 'nt;fa#' 'nt;sol' 'nt;sol#' 'nt;la' 'nt;la#' 'nt;si' 'nt;do_' 'nt;dob' 'nt;re_' 'nt;reb' 'nt;mi_' 'nt;mi#' 'nt;fa_' 'nt;solb' 'nt;sol_' 'nt;lab' 'nt;la_' 'nt;sib' 'nt;si_' 'nt;si#') } } to '_toneLoop' scaledFreq ms { if (_toneInitalized == 0) {'attach buzzer to pin' ''} if ('[io:hasTone]') { '[io:playTone]' _tonePin (scaledFreq / 1000) waitMillis ms '[io:playTone]' _tonePin 0 } else { local 'halfCycle' ((500000000 / scaledFreq) - _toneLoopOverhead) local 'cycles' ((ms * 500) / halfCycle) repeat cycles { digitalWriteOp _tonePin true waitMicros halfCycle digitalWriteOp _tonePin false waitMicros halfCycle } } } to '_trimmedLowercase' s { comment 'Return a copy of the given string without whitespace or periods and all lowercase.' local 'result' (newList (size s)) '[data:delete]' 'all' result for i (size s) { local 'ch' ('[data:unicodeAt]' i s) if (and (ch > 32) (ch != 46)) { if (and (65 <= ch) (ch <= 90)) {ch = (ch + 32)} '[data:addLast]' ch result } } return ('[data:unicodeString]' result) } to 'attach buzzer to pin' pinNumber { if (pinNumber == '') { comment 'Pin number not specified; use default pin for this device' if ((boardType) == 'Citilab ED1') { _tonePin = 26 } ((boardType) == 'M5Stack-Core') { _tonePin = 25 } ((boardType) == 'M5StickC') { _tonePin = 26 } ((boardType) == 'Calliope') { digitalWriteOp 23 true digitalWriteOp 24 true _tonePin = 25 } ((boardType) == 'D1-Mini') { _tonePin = 12 } ((boardType) == 'CodingBox') { _tonePin = 32 } else { _tonePin = -1 } } else { _tonePin = pinNumber } _toneLoopOverhead = ('_measureLoopOverhead') _toneInitalized = (booleanConstant true) } to 'play frequency' freq ms { '_toneLoop' (freq * 1000) ms } to 'play tone' note octave ms { local 'freq' ('_baseFreqForNote' note) if (freq <= 10000) { waitMillis ms return 0 } if (octave < 0) { repeat (absoluteValue octave) { freq = (freq / 2) } } repeat octave { freq = (freq * 2) } '_toneLoop' freq ms } to playMIDIKey key ms { local 'freq' ('_baseFreqForSemitone' ((key % 12) + 1)) local 'octave' ((key / 12) - 5) if (octave < 0) { repeat (absoluteValue octave) { freq = (freq / 2) } } repeat octave { freq = (freq * 2) } '_toneLoop' freq ms } to startTone freq { if (_toneInitalized == 0) {'attach buzzer to pin' ''} if ('[io:hasTone]') {'[io:playTone]' _tonePin freq} } to stopTone { startTone 0 } module WiFi Comm author MicroBlocks version 1 10 tags communication network description 'Connect to a WiFi network. Used in conjunction with other network libraries, such as HTTP client, HTTP server or Web Thing. ' spec ' ' 'wifiConnect' 'wifi connect to _ password _ : IP _ gateway _ subnet _' 'str str auto auto auto' 'Network_Name' '' '192.168.1.42' '192.168.1.1' '255.255.255.0' spec ' ' 'wifiCreateHotspot' 'wifi create hotspot _ password _' 'str str' 'Network_Name' 'Network_Password' spec ' ' '[net:stopWiFi]' 'stop wifi' space spec ' ' '[net:setDomainName]' 'wifi set local domain name _ .local' 'str' 'microblocks' space spec 'r' 'getIPAddress' 'IP address' spec 'r' '[net:myMAC]' 'MAC address' space spec ' ' '[net:allowWiFiAndBLE]' 'allow WiFi while using BLE _' 'bool' true to getIPAddress { return ('[net:myIPAddress]') } to wifiConnect ssid password fixedIP gatewayIP subnetIP { if (not ('[net:hasWiFi]')) {return} '[net:stopWiFi]' if ((pushArgCount) < 5) { '[net:startWiFi]' ssid password } else { '[net:startWiFi]' ssid password false fixedIP gatewayIP subnetIP } local 'startMSecs' (millisOp) repeatUntil (('[net:myIPAddress]') != '0.0.0.0') { comment 'Timeout after N seconds' if (((millisOp) - startMSecs) > 30000) { sayIt 'Could not connect' return 0 } comment 'Slow blink while trying to connect' setUserLED true waitMillis 300 setUserLED false waitMillis 300 } repeat 6 { comment 'Quick blinks when connected' setUserLED true waitMillis 50 setUserLED false waitMillis 50 } '[net:setDomainName]' 'microblocks' sayIt 'My IP address is:' ('[net:myIPAddress]') } to wifiCreateHotspot ssid password { if (not ('[net:hasWiFi]')) {return} if ((size password) < 8) { sayIt 'Password must be at least 8 characters' return 0 } '[net:startWiFi]' ssid password true if ('Connected' != ('[net:wifiStatus]')) { sayIt 'Could not create hotspot' return 0 } repeat 6 { comment 'Quick blinks when connected' setUserLED true waitMillis 50 setUserLED false waitMillis 50 } '[net:setDomainName]' 'microblocks' sayIt 'My IP address is:' ('[net:myIPAddress]') }