// ATmega328P Arduino addon // // Demonstrates how an addon can register an entirely new microcontroller. // Adds the ATmega328P (Arduino Uno / Nano) as a selectable device with: // - Curated Pin I/O, PWM, ADC, Time, and Serial categories (existing blocks) // - Arduino-specific blocks: built-in LED, analog write, servo // // Pin constraints: digital 0-13, analog A0-A5 (14-19), PWM on 3/5/6/9/10/11 // Generates MicroPython (machine module) since the IDE's code pipeline is // MicroPython-based. Users can flash MicroPython firmware onto compatible // ATmega328P boards to use this. // ─── Arduino-specific block definitions ─────────────────── api.Blockly.Blocks['arduino_builtin_led'] = { init() { this.appendDummyInput() .appendField('set built-in LED') .appendField(new api.Blockly.FieldDropdown([ ['ON', '1'], ['OFF', '0'], ]), 'STATE'); this.setPreviousStatement(true, null); this.setNextStatement(true, null); this.setColour(230); this.setTooltip('Turn the built-in LED (pin 13) on or off'); }, }; api.Blockly.Blocks['arduino_analog_write'] = { init() { this.appendValueInput('VALUE') .setCheck('Number') .appendField('analog write pin') .appendField(new api.Blockly.FieldDropdown([ ['3', '3'], ['5', '5'], ['6', '6'], ['9', '9'], ['10', '10'], ['11', '11'], ]), 'PIN') .appendField('value'); this.appendDummyInput().appendField('(0-255)'); this.setInputsInline(true); this.setPreviousStatement(true, null); this.setNextStatement(true, null); this.setColour(160); this.setTooltip('Write a PWM value (0-255) to a PWM-capable pin'); }, }; api.Blockly.Blocks['arduino_analog_read'] = { init() { this.appendDummyInput() .appendField('analog read') .appendField(new api.Blockly.FieldDropdown([ ['A0', '0'], ['A1', '1'], ['A2', '2'], ['A3', '3'], ['A4', '4'], ['A5', '5'], ]), 'CHANNEL'); this.setOutput(true, 'Number'); this.setColour(30); this.setTooltip('Read analog value from an ADC channel (0-1023)'); }, }; api.Blockly.Blocks['arduino_servo_attach'] = { init() { this.appendDummyInput() .appendField('attach servo on pin') .appendField(new api.Blockly.FieldDropdown([ ['9', '9'], ['10', '10'], ['11', '11'], ['3', '3'], ['5', '5'], ['6', '6'], ]), 'PIN'); this.setPreviousStatement(true, null); this.setNextStatement(true, null); this.setColour(260); this.setTooltip('Attach a servo motor to a PWM pin'); }, }; api.Blockly.Blocks['arduino_servo_write'] = { init() { this.appendValueInput('ANGLE') .setCheck('Number') .appendField('set servo on pin') .appendField(new api.Blockly.FieldDropdown([ ['9', '9'], ['10', '10'], ['11', '11'], ['3', '3'], ['5', '5'], ['6', '6'], ]), 'PIN') .appendField('to'); this.appendDummyInput().appendField('degrees'); this.setInputsInline(true); this.setPreviousStatement(true, null); this.setNextStatement(true, null); this.setColour(260); this.setTooltip('Set servo angle (0-180 degrees)'); }, }; api.Blockly.Blocks['arduino_tone'] = { init() { this.appendDummyInput() .appendField('play tone on pin') .appendField(new api.Blockly.FieldNumber(8, 0, 13, 1), 'PIN'); this.appendValueInput('FREQ') .setCheck('Number') .appendField('freq'); this.appendDummyInput().appendField('Hz'); this.appendValueInput('DURATION') .setCheck('Number') .appendField('for'); this.appendDummyInput().appendField('ms'); this.setInputsInline(true); this.setPreviousStatement(true, null); this.setNextStatement(true, null); this.setColour(300); this.setTooltip('Play a tone on a digital pin using PWM'); }, }; api.Blockly.Blocks['arduino_no_tone'] = { init() { this.appendDummyInput() .appendField('stop tone on pin') .appendField(new api.Blockly.FieldNumber(8, 0, 13, 1), 'PIN'); this.setPreviousStatement(true, null); this.setNextStatement(true, null); this.setColour(300); this.setTooltip('Stop any tone playing on a digital pin'); }, }; api.Blockly.Blocks['arduino_map'] = { init() { this.appendValueInput('VALUE').setCheck('Number').appendField('map'); this.appendValueInput('FROM_LOW').setCheck('Number').appendField('from'); this.appendValueInput('FROM_HIGH').setCheck('Number').appendField('-'); this.appendValueInput('TO_LOW').setCheck('Number').appendField('to'); this.appendValueInput('TO_HIGH').setCheck('Number').appendField('-'); this.setInputsInline(true); this.setOutput(true, 'Number'); this.setColour(200); this.setTooltip('Re-map a number from one range to another'); }, }; // ─── Code generators ────────────────────────────────────── var gen = api.pythonGenerator; gen.forBlock['arduino_builtin_led'] = function (block) { var state = block.getFieldValue('STATE'); gen.definitions_['import_machine'] = 'from machine import Pin, PWM, ADC'; gen.definitions_['led_pin13'] = 'led = Pin(13, Pin.OUT)'; return 'led.value(' + state + ')\n'; }; gen.forBlock['arduino_analog_write'] = function (block) { var pin = block.getFieldValue('PIN'); var value = gen.valueToCode(block, 'VALUE', gen.ORDER_NONE) || '0'; gen.definitions_['import_machine'] = 'from machine import Pin, PWM, ADC'; gen.definitions_['pwm_aw_' + pin] = 'pwm_' + pin + ' = PWM(Pin(' + pin + '), freq=490)'; // ATmega328P analogWrite is 0-255, MicroPython PWM duty is 0-1023 return 'pwm_' + pin + '.duty(int(' + value + ' * 4))\n'; }; gen.forBlock['arduino_analog_read'] = function (block) { var ch = block.getFieldValue('CHANNEL'); var pin = parseInt(ch, 10) + 14; gen.definitions_['import_machine'] = 'from machine import Pin, PWM, ADC'; gen.definitions_['adc_a' + ch] = 'adc_a' + ch + ' = ADC(Pin(' + pin + '))'; return ['adc_a' + ch + '.read()', gen.ORDER_FUNCTION_CALL]; }; var SERVO_HELPER = [ 'def _servo_write(pwm, angle):', ' angle = max(0, min(180, int(angle)))', ' duty = int(26 + (angle / 180) * 102)', ' pwm.duty(duty)', ].join('\n'); gen.forBlock['arduino_servo_attach'] = function (block) { var pin = block.getFieldValue('PIN'); gen.definitions_['import_machine'] = 'from machine import Pin, PWM, ADC'; gen.definitions_['servo_' + pin] = 'servo_' + pin + ' = PWM(Pin(' + pin + '), freq=50)'; gen.definitions_['servo_helper'] = SERVO_HELPER; return ''; }; gen.forBlock['arduino_servo_write'] = function (block) { var pin = block.getFieldValue('PIN'); var angle = gen.valueToCode(block, 'ANGLE', gen.ORDER_NONE) || '90'; gen.definitions_['import_machine'] = 'from machine import Pin, PWM, ADC'; gen.definitions_['servo_' + pin] = 'servo_' + pin + ' = PWM(Pin(' + pin + '), freq=50)'; gen.definitions_['servo_helper'] = SERVO_HELPER; return '_servo_write(servo_' + pin + ', ' + angle + ')\n'; }; gen.forBlock['arduino_tone'] = function (block) { var pin = block.getFieldValue('PIN'); var freq = gen.valueToCode(block, 'FREQ', gen.ORDER_NONE) || '440'; var duration = gen.valueToCode(block, 'DURATION', gen.ORDER_NONE) || '500'; gen.definitions_['import_machine'] = 'from machine import Pin, PWM, ADC'; gen.definitions_['import_time'] = 'import time'; var v = 'buzzer_' + pin; gen.definitions_['buzzer_' + pin] = v + ' = PWM(Pin(' + pin + '))'; return v + '.freq(' + freq + ')\n' + v + '.duty(512)\n' + 'time.sleep_ms(' + duration + ')\n' + v + '.duty(0)\n'; }; gen.forBlock['arduino_no_tone'] = function (block) { var pin = block.getFieldValue('PIN'); gen.definitions_['import_machine'] = 'from machine import Pin, PWM, ADC'; var v = 'buzzer_' + pin; gen.definitions_['buzzer_' + pin] = v + ' = PWM(Pin(' + pin + '))'; return v + '.duty(0)\n'; }; gen.forBlock['arduino_map'] = function (block) { var value = gen.valueToCode(block, 'VALUE', gen.ORDER_NONE) || '0'; var fromLow = gen.valueToCode(block, 'FROM_LOW', gen.ORDER_NONE) || '0'; var fromHigh = gen.valueToCode(block, 'FROM_HIGH', gen.ORDER_NONE) || '1023'; var toLow = gen.valueToCode(block, 'TO_LOW', gen.ORDER_NONE) || '0'; var toHigh = gen.valueToCode(block, 'TO_HIGH', gen.ORDER_NONE) || '255'; gen.definitions_['_arduino_map'] = 'def _map(x, in_min, in_max, out_min, out_max):\n' + ' return int((x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min)'; return [ '_map(' + value + ', ' + fromLow + ', ' + fromHigh + ', ' + toLow + ', ' + toHigh + ')', gen.ORDER_FUNCTION_CALL, ]; }; // ─── Register device ────────────────────────────────────── api.registerDevice({ id: 'atmega328p', label: 'ATmega328P (Arduino)', firmware: { label: 'MicroPython (ATmega328P)', url: 'https://micropython.org/download/', canFlashInBrowser: false, instructions: 'Flash MicroPython firmware via your preferred tool, then connect here.', }, categories: [ { kind: 'category', name: 'Pin I/O', colour: '230', contents: [ { kind: 'block', type: 'pin_set_mode' }, { kind: 'block', type: 'pin_digital_write' }, { kind: 'block', type: 'pin_digital_read' }, { kind: 'block', type: 'arduino_builtin_led' }, ], }, { kind: 'category', name: 'Analog', colour: '30', contents: [ { kind: 'block', type: 'arduino_analog_read' }, { kind: 'block', type: 'arduino_analog_write', inputs: { VALUE: { shadow: { type: 'math_number', fields: { NUM: 128 } } }, }, }, { kind: 'block', type: 'arduino_map', inputs: { VALUE: { shadow: { type: 'math_number', fields: { NUM: 512 } } }, FROM_LOW: { shadow: { type: 'math_number', fields: { NUM: 0 } } }, FROM_HIGH: { shadow: { type: 'math_number', fields: { NUM: 1023 } } }, TO_LOW: { shadow: { type: 'math_number', fields: { NUM: 0 } } }, TO_HIGH: { shadow: { type: 'math_number', fields: { NUM: 255 } } }, }, }, ], }, { kind: 'category', name: 'Servo', colour: '260', contents: [ { kind: 'block', type: 'arduino_servo_attach' }, { kind: 'block', type: 'arduino_servo_write', inputs: { ANGLE: { shadow: { type: 'math_number', fields: { NUM: 90 } } }, }, }, ], }, { kind: 'category', name: 'Sound', colour: '300', contents: [ { kind: 'block', type: 'arduino_tone', inputs: { FREQ: { shadow: { type: 'math_number', fields: { NUM: 440 } } }, DURATION: { shadow: { type: 'math_number', fields: { NUM: 500 } } }, }, }, { kind: 'block', type: 'arduino_no_tone' }, ], }, { kind: 'category', name: 'Time', colour: '120', contents: [ { kind: 'block', type: 'sleep_seconds', inputs: { SECONDS: { shadow: { type: 'math_number', fields: { NUM: 1 } } }, }, }, { kind: 'block', type: 'sleep_ms', inputs: { MS: { shadow: { type: 'math_number', fields: { NUM: 100 } } }, }, }, { kind: 'block', type: 'ticks_ms' }, ], }, { kind: 'category', name: 'Serial / Print', colour: '60', contents: [ { kind: 'block', type: 'print_text', inputs: { TEXT: { shadow: { type: 'text', fields: { TEXT: 'Hello!' } } }, }, }, ], }, ], });