// h-bridge Motor Driver addon // // Adds a "h-bridge Motor" toolbox category with: // - motor_init: set up a motor with IN1 and IN2 pins // - motor_speed: set speed from -255 (full reverse) to +255 (full forward) // - motor_stop: stop a motor // // Generates device-appropriate MicroPython: // - ESP32 / RP2040: machine.Pin + machine.PWM // - micro:bit: pinN.write_analog() // --- Block definitions --- api.Blockly.Blocks['hbridge_motor_init'] = { init() { this.appendDummyInput() .appendField('init motor') .appendField(new api.Blockly.FieldDropdown([ ['A', 'A'], ['B', 'B'], ]), 'MOTOR') .appendField('IN1 pin') .appendField(new api.Blockly.FieldNumber(2, 0, 48, 1), 'IN1') .appendField('IN2 pin') .appendField(new api.Blockly.FieldNumber(3, 0, 48, 1), 'IN2'); this.setPreviousStatement(true, null); this.setNextStatement(true, null); this.setColour(30); this.setTooltip('Initialize an h-bridge motor channel with two pins'); }, }; api.Blockly.Blocks['hbridge_motor_speed'] = { init() { this.appendDummyInput() .appendField('set motor') .appendField(new api.Blockly.FieldDropdown([ ['A', 'A'], ['B', 'B'], ]), 'MOTOR'); this.appendValueInput('SPEED') .setCheck('Number') .appendField('speed'); this.setInputsInline(true); this.setPreviousStatement(true, null); this.setNextStatement(true, null); this.setColour(30); this.setTooltip('Set motor speed: -255 (full reverse) to +255 (full forward)'); }, }; api.Blockly.Blocks['hbridge_motor_stop'] = { init() { this.appendDummyInput() .appendField('stop motor') .appendField(new api.Blockly.FieldDropdown([ ['A', 'A'], ['B', 'B'], ]), 'MOTOR'); this.setPreviousStatement(true, null); this.setNextStatement(true, null); this.setColour(30); this.setTooltip('Stop an h-bridge motor (brake)'); }, }; // --- Helpers used by generators --- function isMicrobit() { return api.getDeviceId() === 'microbit'; } function mbPin(pin) { var n = Math.max(0, Math.min(20, parseInt(pin, 10) || 0)); return 'pin' + n; } // --- Code generators --- api.pythonGenerator.forBlock['hbridge_motor_init'] = function (block) { var motor = block.getFieldValue('MOTOR'); var in1 = block.getFieldValue('IN1'); var in2 = block.getFieldValue('IN2'); var m = motor.toLowerCase(); if (isMicrobit()) { // micro:bit: no explicit init needed, just store pin numbers for later use. // We define variables so speed/stop blocks can reference them. api.pythonGenerator.definitions_['import_microbit'] = 'from microbit import *'; api.pythonGenerator.definitions_['hb_motor_' + m + '_pins'] = 'motor_' + m + '_in1 = ' + mbPin(in1) + '\n' + 'motor_' + m + '_in2 = ' + mbPin(in2); api.pythonGenerator.definitions_['hb_set_motor_mb'] = [ 'def _hb_set_motor(p1, p2, speed):', ' speed = max(-255, min(255, int(speed)))', ' duty = abs(speed) * 4', ' if speed > 0:', ' p1.write_analog(duty)', ' p2.write_analog(0)', ' elif speed < 0:', ' p1.write_analog(0)', ' p2.write_analog(duty)', ' else:', ' p1.write_analog(0)', ' p2.write_analog(0)', ].join('\n'); } else { // ESP32 / RP2040: machine.PWM (pins stored as numbers, PWM created on demand) api.pythonGenerator.definitions_['import_machine'] = 'from machine import Pin, PWM'; api.pythonGenerator.definitions_['hb_motor_' + m + '_in1'] = 'motor_' + m + '_in1 = ' + in1; api.pythonGenerator.definitions_['hb_motor_' + m + '_in2'] = 'motor_' + m + '_in2 = ' + in2; api.pythonGenerator.definitions_['hb_set_motor'] = [ 'def _hb_set_motor(p1, p2, speed):', ' speed = max(-255, min(255, int(speed)))', ' duty = abs(speed) * 4', ' if speed > 0:', ' try: PWM(Pin(p2)).deinit()', ' except: pass', ' Pin(p2, Pin.OUT).value(0)', ' PWM(Pin(p1), freq=1000, duty=duty)', ' elif speed < 0:', ' try: PWM(Pin(p1)).deinit()', ' except: pass', ' Pin(p1, Pin.OUT).value(0)', ' PWM(Pin(p2), freq=1000, duty=duty)', ' else:', ' try: PWM(Pin(p1)).deinit()', ' except: pass', ' try: PWM(Pin(p2)).deinit()', ' except: pass', ' Pin(p1, Pin.OUT).value(0)', ' Pin(p2, Pin.OUT).value(0)', ].join('\n'); } return ''; }; api.pythonGenerator.forBlock['hbridge_motor_speed'] = function (block) { var motor = block.getFieldValue('MOTOR'); var m = motor.toLowerCase(); var speed = api.pythonGenerator.valueToCode( block, 'SPEED', api.pythonGenerator.ORDER_NONE ) || '0'; return '_hb_set_motor(motor_' + m + '_in1, motor_' + m + '_in2, ' + speed + ')\n'; }; api.pythonGenerator.forBlock['hbridge_motor_stop'] = function (block) { var motor = block.getFieldValue('MOTOR'); var m = motor.toLowerCase(); return '_hb_set_motor(motor_' + m + '_in1, motor_' + m + '_in2, 0)\n'; }; // --- Dual-motor block definitions --- api.Blockly.Blocks['hbridge_dual_init'] = { init() { this.appendDummyInput().appendField('init dual motors'); this.appendDummyInput() .appendField('left IN1') .appendField(new api.Blockly.FieldNumber(2, 0, 48, 1), 'L_IN1') .appendField('IN2') .appendField(new api.Blockly.FieldNumber(3, 0, 48, 1), 'L_IN2'); this.appendDummyInput() .appendField('right IN1') .appendField(new api.Blockly.FieldNumber(4, 0, 48, 1), 'R_IN1') .appendField('IN2') .appendField(new api.Blockly.FieldNumber(5, 0, 48, 1), 'R_IN2'); this.setInputsInline(false); this.setPreviousStatement(true, null); this.setNextStatement(true, null); this.setColour(30); this.setTooltip('Initialize two h-bridge motors (left and right) with 4 pins.'); }, }; api.Blockly.Blocks['hbridge_dual_speed'] = { init() { this.appendDummyInput().appendField('set motors'); this.appendValueInput('LEFT') .setCheck('Number') .appendField('left'); this.appendValueInput('RIGHT') .setCheck('Number') .appendField('right'); this.setInputsInline(true); this.setPreviousStatement(true, null); this.setNextStatement(true, null); this.setColour(30); this.setTooltip('Set left and right motor speeds independently (-255 to 255).'); }, }; api.Blockly.Blocks['hbridge_dual_forward'] = { init() { this.appendDummyInput().appendField('drive forward'); this.setPreviousStatement(true, null); this.setNextStatement(true, null); this.setColour(30); this.setTooltip('Drive both motors forward using the speed from "set motors".'); }, }; api.Blockly.Blocks['hbridge_dual_backward'] = { init() { this.appendDummyInput().appendField('drive backward'); this.setPreviousStatement(true, null); this.setNextStatement(true, null); this.setColour(30); this.setTooltip('Drive both motors backward using the speed from "set motors".'); }, }; api.Blockly.Blocks['hbridge_dual_left'] = { init() { this.appendDummyInput().appendField('turn left'); this.setPreviousStatement(true, null); this.setNextStatement(true, null); this.setColour(30); this.setTooltip('Turn left (left motor backward, right motor forward).'); }, }; api.Blockly.Blocks['hbridge_dual_right'] = { init() { this.appendDummyInput().appendField('turn right'); this.setPreviousStatement(true, null); this.setNextStatement(true, null); this.setColour(30); this.setTooltip('Turn right (left motor forward, right motor backward).'); }, }; api.Blockly.Blocks['hbridge_dual_stop'] = { init() { this.appendDummyInput().appendField('stop motors'); this.setPreviousStatement(true, null); this.setNextStatement(true, null); this.setColour(30); this.setTooltip('Stop both motors.'); }, }; // --- Dual-motor generators --- api.pythonGenerator.forBlock['hbridge_dual_init'] = function (block) { var l1 = block.getFieldValue('L_IN1'); var l2 = block.getFieldValue('L_IN2'); var r1 = block.getFieldValue('R_IN1'); var r2 = block.getFieldValue('R_IN2'); if (isMicrobit()) { api.pythonGenerator.definitions_['import_microbit'] = 'from microbit import *'; api.pythonGenerator.definitions_['hb_dual_pins'] = 'motor_l_in1 = ' + mbPin(l1) + '\n' + 'motor_l_in2 = ' + mbPin(l2) + '\n' + 'motor_r_in1 = ' + mbPin(r1) + '\n' + 'motor_r_in2 = ' + mbPin(r2); api.pythonGenerator.definitions_['hb_set_motor_mb'] = [ 'def _hb_set_motor(p1, p2, speed):', ' speed = max(-255, min(255, int(speed)))', ' duty = abs(speed) * 4', ' if speed > 0:', ' p1.write_analog(duty)', ' p2.write_analog(0)', ' elif speed < 0:', ' p1.write_analog(0)', ' p2.write_analog(duty)', ' else:', ' p1.write_analog(0)', ' p2.write_analog(0)', ].join('\n'); } else { api.pythonGenerator.definitions_['import_machine'] = 'from machine import Pin, PWM'; api.pythonGenerator.definitions_['hb_motor_l_in1'] = 'motor_l_in1 = ' + l1; api.pythonGenerator.definitions_['hb_motor_l_in2'] = 'motor_l_in2 = ' + l2; api.pythonGenerator.definitions_['hb_motor_r_in1'] = 'motor_r_in1 = ' + r1; api.pythonGenerator.definitions_['hb_motor_r_in2'] = 'motor_r_in2 = ' + r2; api.pythonGenerator.definitions_['hb_set_motor'] = [ 'def _hb_set_motor(p1, p2, speed):', ' speed = max(-255, min(255, int(speed)))', ' duty = abs(speed) * 4', ' if speed > 0:', ' try: PWM(Pin(p2)).deinit()', ' except: pass', ' Pin(p2, Pin.OUT).value(0)', ' PWM(Pin(p1), freq=1000, duty=duty)', ' elif speed < 0:', ' try: PWM(Pin(p1)).deinit()', ' except: pass', ' Pin(p1, Pin.OUT).value(0)', ' PWM(Pin(p2), freq=1000, duty=duty)', ' else:', ' try: PWM(Pin(p1)).deinit()', ' except: pass', ' try: PWM(Pin(p2)).deinit()', ' except: pass', ' Pin(p1, Pin.OUT).value(0)', ' Pin(p2, Pin.OUT).value(0)', ].join('\n'); } return ''; }; api.pythonGenerator.forBlock['hbridge_dual_speed'] = function (block) { var left = api.pythonGenerator.valueToCode( block, 'LEFT', api.pythonGenerator.ORDER_NONE) || '0'; var right = api.pythonGenerator.valueToCode( block, 'RIGHT', api.pythonGenerator.ORDER_NONE) || '0'; return '_hb_set_motor(motor_l_in1, motor_l_in2, ' + left + ')\n' + '_hb_set_motor(motor_r_in1, motor_r_in2, ' + right + ')\n'; }; api.pythonGenerator.forBlock['hbridge_dual_forward'] = function () { return '_hb_set_motor(motor_l_in1, motor_l_in2, 255)\n' + '_hb_set_motor(motor_r_in1, motor_r_in2, 255)\n'; }; api.pythonGenerator.forBlock['hbridge_dual_backward'] = function () { return '_hb_set_motor(motor_l_in1, motor_l_in2, -255)\n' + '_hb_set_motor(motor_r_in1, motor_r_in2, -255)\n'; }; api.pythonGenerator.forBlock['hbridge_dual_left'] = function () { return '_hb_set_motor(motor_l_in1, motor_l_in2, -255)\n' + '_hb_set_motor(motor_r_in1, motor_r_in2, 255)\n'; }; api.pythonGenerator.forBlock['hbridge_dual_right'] = function () { return '_hb_set_motor(motor_l_in1, motor_l_in2, 255)\n' + '_hb_set_motor(motor_r_in1, motor_r_in2, -255)\n'; }; api.pythonGenerator.forBlock['hbridge_dual_stop'] = function () { return '_hb_set_motor(motor_l_in1, motor_l_in2, 0)\n' + '_hb_set_motor(motor_r_in1, motor_r_in2, 0)\n'; }; // --- Register toolbox category --- api.registerCategories([ { kind: 'category', name: 'H-Bridge Motor', colour: '30', contents: [ { kind: 'block', type: 'hbridge_motor_init' }, { kind: 'block', type: 'hbridge_motor_speed', inputs: { SPEED: { shadow: { type: 'math_number', fields: { NUM: 200 } } }, }, }, { kind: 'block', type: 'hbridge_motor_stop' }, { kind: 'block', type: 'hbridge_dual_init' }, { kind: 'block', type: 'hbridge_dual_speed', inputs: { LEFT: { shadow: { type: 'math_number', fields: { NUM: 200 } } }, RIGHT: { shadow: { type: 'math_number', fields: { NUM: 200 } } }, }, }, { kind: 'block', type: 'hbridge_dual_forward' }, { kind: 'block', type: 'hbridge_dual_backward' }, { kind: 'block', type: 'hbridge_dual_left' }, { kind: 'block', type: 'hbridge_dual_right' }, { kind: 'block', type: 'hbridge_dual_stop' }, ], }, ]);