351 lines
12 KiB
JavaScript
351 lines
12 KiB
JavaScript
// 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!' } } },
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|