added support for muliple i2c channels and TCS34725 colour sensor

main
Jake 2026-04-19 22:23:55 +08:00
parent 5a7cf002f5
commit 843ac9d202
7 changed files with 240 additions and 18 deletions

View File

@ -418,8 +418,8 @@ arduinoGenerator.forBlock['arduino_map'] = function (block) {
const unsupportedBlocks = [
'wifi_connect', 'wifi_get_ip',
'neopixel_init', 'neopixel_set_color', 'neopixel_show',
'colour_rgb', 'tuple_create_3',
'i2c_init', 'i2c_scan', 'i2c_writeto', 'i2c_readfrom',
'colour_rgb', 'tuple_create_3', 'tuple_index',
'i2c_init', 'i2c_scan', 'i2c_writeto', 'i2c_readfrom', 'tcs34725_read_colours',
'hid_key_press', 'hid_key_down', 'hid_key_up', 'hid_keyboard_type',
'hid_mouse_move', 'hid_mouse_click', 'hid_mouse_scroll',
'hid_gamepad_button', 'hid_gamepad_axis',

View File

@ -3,9 +3,17 @@ export const i2cCategory = {
name: 'I2C',
colour: '200',
contents: [
{ kind: 'block', type: 'i2c_init' },
{ kind: 'block', type: 'i2c_scan' },
{ kind: 'block', type: 'i2c_writeto' },
{ kind: 'block', type: 'i2c_readfrom' },
{ kind: 'block', type: 'i2c_init', fields: { CH: '0' } },
{ kind: 'block', type: 'i2c_scan', fields: { CH: '0' } },
{ kind: 'block', type: 'i2c_writeto', fields: { CH: '0' } },
{ kind: 'block', type: 'i2c_readfrom', fields: { CH: '0' } },
{
kind: 'block',
type: 'tcs34725_read_colours',
fields: {
CH: '0',
ADDR: 41,
},
},
],
};

View File

@ -22,6 +22,22 @@ export const neopixelCategory = {
C: { shadow: { type: 'math_number', fields: { NUM: 0 } } },
},
},
{
kind: 'block',
type: 'tuple_index',
inputs: {
TUPLE: {
shadow: {
type: 'tuple_create_3',
inputs: {
A: { shadow: { type: 'math_number', fields: { NUM: 255 } } },
B: { shadow: { type: 'math_number', fields: { NUM: 128 } } },
C: { shadow: { type: 'math_number', fields: { NUM: 0 } } },
},
},
},
},
},
{
kind: 'block',
type: 'neopixel_set_color',

View File

@ -11,5 +11,25 @@ export const sensorsCategory = {
ECHO: { shadow: { type: 'math_number', fields: { NUM: 3 } } },
},
},
{
kind: 'block',
type: 'tcs34725_read_colours',
fields: {
CH: '0',
ADDR: 41,
},
},
{
kind: 'block',
type: 'tuple_index',
inputs: {
TUPLE: {
shadow: {
type: 'tcs34725_read_colours',
fields: { CH: '0', ADDR: 41 },
},
},
},
},
],
};

View File

@ -276,6 +276,29 @@ Blockly.Blocks['tuple_create_3'] = {
},
};
Blockly.Blocks['tuple_index'] = {
init() {
this.appendValueInput('TUPLE')
.setCheck(null)
.appendField('item')
.appendField(
new Blockly.FieldDropdown([
['0', '0'],
['1', '1'],
['2', '2'],
]),
'I',
)
.appendField('of tuple');
this.setOutput(true, 'Number');
this.setInputsInline(true);
this.setColour(10);
this.setTooltip(
'Read one element from a tuple by index. Use with the TCS34725 block or any (a, b, c) value.',
);
},
};
Blockly.Blocks['neopixel_set_color'] = {
init() {
this.appendValueInput('INDEX')
@ -304,11 +327,18 @@ Blockly.Blocks['neopixel_show'] = {
};
// ─── I2C ──────────────────────────────────────────────────
// Hardware I2C peripheral id (0 or 1). Generated code uses i2c0 / i2c1 variables.
const I2C_CHANNEL_OPTIONS = [
['ch 0', '0'],
['ch 1', '1'],
];
Blockly.Blocks['i2c_init'] = {
init() {
this.appendDummyInput()
.appendField('init I2C SDA pin')
.appendField('init I2C')
.appendField(new Blockly.FieldDropdown(I2C_CHANNEL_OPTIONS), 'CH')
.appendField('SDA pin')
.appendField(new Blockly.FieldNumber(8, 0, 48, 1), 'SDA')
.appendField('SCL pin')
.appendField(new Blockly.FieldNumber(9, 0, 48, 1), 'SCL')
@ -317,17 +347,19 @@ Blockly.Blocks['i2c_init'] = {
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setColour(200);
this.setTooltip('Initialize I2C bus');
this.setTooltip('Initialize an I2C bus. ESP32/RP2040: channel 0 or 1 maps to I2C(0) or I2C(1).');
},
};
Blockly.Blocks['i2c_scan'] = {
init() {
this.appendDummyInput()
.appendField('I2C scan devices');
.appendField('I2C')
.appendField(new Blockly.FieldDropdown(I2C_CHANNEL_OPTIONS), 'CH')
.appendField('scan devices');
this.setOutput(true, 'Array');
this.setColour(200);
this.setTooltip('Scan for I2C devices, returns list of addresses');
this.setTooltip('Scan for I2C devices on the selected bus, returns list of addresses');
},
};
@ -335,26 +367,45 @@ Blockly.Blocks['i2c_writeto'] = {
init() {
this.appendValueInput('DATA')
.setCheck(null)
.appendField('I2C write to address')
.appendField('I2C')
.appendField(new Blockly.FieldDropdown(I2C_CHANNEL_OPTIONS), 'CH')
.appendField('write to address')
.appendField(new Blockly.FieldNumber(0, 0, 127, 1), 'ADDR')
.appendField('data');
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setColour(200);
this.setTooltip('Write data bytes to an I2C device');
this.setTooltip('Write data bytes to an I2C device on the selected bus');
},
};
Blockly.Blocks['i2c_readfrom'] = {
init() {
this.appendDummyInput()
.appendField('I2C read from address')
.appendField('I2C')
.appendField(new Blockly.FieldDropdown(I2C_CHANNEL_OPTIONS), 'CH')
.appendField('read from address')
.appendField(new Blockly.FieldNumber(0, 0, 127, 1), 'ADDR')
.appendField('bytes')
.appendField(new Blockly.FieldNumber(1, 1, 256, 1), 'NBYTES');
this.setOutput(true, null);
this.setColour(200);
this.setTooltip('Read N bytes from an I2C device');
this.setTooltip('Read N bytes from an I2C device on the selected bus');
},
};
Blockly.Blocks['tcs34725_read_colours'] = {
init() {
this.appendDummyInput()
.appendField('TCS34725 read RGB')
.appendField(new Blockly.FieldDropdown(I2C_CHANNEL_OPTIONS), 'CH')
.appendField('address')
.appendField(new Blockly.FieldNumber(41, 1, 127, 1), 'ADDR');
this.setOutput(true, null);
this.setColour(65);
this.setTooltip(
'Read raw RGB channel counts as (R, G, B), each 065535. If there is no init I2C block for this channel, the bus is set up on GPIO 21 (SDA) and 22 (SCL) at 400 kHz.',
);
},
};

View File

@ -256,6 +256,13 @@ pythonGenerator.forBlock['tuple_create_3'] = function (block) {
return [`(${a}, ${b}, ${c})`, Order.ATOMIC];
};
pythonGenerator.forBlock['tuple_index'] = function (block) {
const tupleCode =
pythonGenerator.valueToCode(block, 'TUPLE', Order.MEMBER) || '(0, 0, 0)';
const i = block.getFieldValue('I') ?? '0';
return [`${tupleCode}[${i}]`, Order.MEMBER];
};
pythonGenerator.forBlock['neopixel_set_color'] = function (block) {
const index = pythonGenerator.valueToCode(block, 'INDEX', Order.NONE) || '0';
const color = pythonGenerator.valueToCode(block, 'COLOR', Order.NONE) || '(0, 0, 0)';
@ -268,35 +275,145 @@ pythonGenerator.forBlock['neopixel_show'] = function () {
// ─── I2C ──────────────────────────────────────────────────
function i2cChannel(block) {
return block.getFieldValue('CH') === '1' ? '1' : '0';
}
function i2cBusName(ch) {
return ch === '1' ? 'i2c1' : 'i2c0';
}
function i2cHardwareId(ch) {
return ch === '1' ? 1 : 0;
}
/** True if an enabled "init I2C" block exists for this hardware channel (0 or 1). */
function workspaceHasI2cInitForChannel(block, ch) {
const ws = block.workspace;
if (!ws) return false;
const target = String(ch);
for (const b of ws.getAllBlocks(false)) {
if (!b.isEnabled()) continue;
if (b.type === 'i2c_init' && String(b.getFieldValue('CH') ?? '0') === target) {
return true;
}
}
return false;
}
/** Emit I2C bus init into definitions (runs before user code) when no init block is present. */
function ensureAutoI2cBusDefinition(block, ch, sda, scl, freq) {
if (DEVICE() === 'microbit') return;
if (workspaceHasI2cInitForChannel(block, ch)) return;
pythonGenerator.definitions_['import_machine'] = 'from machine import Pin, PWM, ADC, I2C';
const name = i2cBusName(ch);
const hw = i2cHardwareId(ch);
pythonGenerator.definitions_[`i2c_init_bus_${ch}`] =
`${name} = I2C(${hw}, sda=Pin(${sda}), scl=Pin(${scl}), freq=${freq})`;
}
const TCS34725_RGB_HELPER = `_tcs34725_inited = [False, False]
def _tcs34725_rgb(bus, ch_idx, addr):
import time
global _tcs34725_inited
ch_idx = int(ch_idx) & 1
addr = int(addr)
if not _tcs34725_inited[ch_idx]:
time.sleep_ms(10)
bus.writeto(addr, bytes([0x80, 0x01]))
time.sleep_ms(10)
bus.writeto(addr, bytes([0x80, 0x03]))
bus.writeto(addr, bytes([0x81, 0xFF]))
bus.writeto(addr, bytes([0x8F, 0x03]))
_tcs34725_inited[ch_idx] = True
time.sleep_ms(5)
bus.writeto(addr, bytes([0x94]))
d = bus.readfrom(addr, 8)
r = d[3] << 8 | d[2]
g = d[5] << 8 | d[4]
b = d[7] << 8 | d[6]
return (r, g, b)
`;
const TCS34725_RGB_HELPER_MB = `_tcs34725_inited_mb = False
def _tcs34725_rgb_mb(addr):
import time
from microbit import i2c
global _tcs34725_inited_mb
addr = int(addr)
if not _tcs34725_inited_mb:
time.sleep_ms(10)
i2c.write(addr, bytes([0x80, 0x01]))
time.sleep_ms(10)
i2c.write(addr, bytes([0x80, 0x03]))
i2c.write(addr, bytes([0x81, 0xFF]))
i2c.write(addr, bytes([0x8F, 0x03]))
_tcs34725_inited_mb = True
time.sleep_ms(5)
i2c.write(addr, bytes([0x94]))
d = i2c.read(addr, 8)
r = d[3] << 8 | d[2]
g = d[5] << 8 | d[4]
b = d[7] << 8 | d[6]
return (r, g, b)
`;
pythonGenerator.forBlock['i2c_init'] = function (block) {
if (DEVICE() === 'microbit') return ''; // micro:bit has built-in i2c
const ch = i2cChannel(block);
const sda = block.getFieldValue('SDA');
const scl = block.getFieldValue('SCL');
const freq = block.getFieldValue('FREQ');
pythonGenerator.definitions_['import_machine'] = 'from machine import Pin, PWM, ADC, I2C';
return `i2c = I2C(0, sda=Pin(${sda}), scl=Pin(${scl}), freq=${freq})\n`;
const name = i2cBusName(ch);
const hw = i2cHardwareId(ch);
return `${name} = I2C(${hw}, sda=Pin(${sda}), scl=Pin(${scl}), freq=${freq})\n`;
};
pythonGenerator.forBlock['i2c_scan'] = function (block) {
if (DEVICE() === 'microbit') {
return ['[]', Order.ATOMIC]; // stub
}
const ch = i2cChannel(block);
pythonGenerator.definitions_['import_machine'] = 'from machine import Pin, PWM, ADC, I2C';
return ['i2c.scan()', Order.FUNCTION_CALL];
const bus = i2cBusName(ch);
return [`${bus}.scan()`, Order.FUNCTION_CALL];
};
pythonGenerator.forBlock['i2c_writeto'] = function (block) {
if (DEVICE() === 'microbit') return '';
const ch = i2cChannel(block);
const addr = block.getFieldValue('ADDR');
const data = pythonGenerator.valueToCode(block, 'DATA', Order.NONE) || 'b""';
return `i2c.writeto(${addr}, ${data})\n`;
const bus = i2cBusName(ch);
return `${bus}.writeto(${addr}, ${data})\n`;
};
pythonGenerator.forBlock['i2c_readfrom'] = function (block) {
if (DEVICE() === 'microbit') return ['b""', Order.ATOMIC];
const ch = i2cChannel(block);
const addr = block.getFieldValue('ADDR');
const nbytes = block.getFieldValue('NBYTES');
return [`i2c.readfrom(${addr}, ${nbytes})`, Order.FUNCTION_CALL];
const bus = i2cBusName(ch);
return [`${bus}.readfrom(${addr}, ${nbytes})`, Order.FUNCTION_CALL];
};
pythonGenerator.forBlock['tcs34725_read_colours'] = function (block) {
const addr = Number(block.getFieldValue('ADDR')) || 41;
if (DEVICE() === 'microbit') {
pythonGenerator.definitions_['import_time'] = 'import time';
pythonGenerator.definitions_['tcs34725_rgb_mb'] = TCS34725_RGB_HELPER_MB;
return [`_tcs34725_rgb_mb(${addr})`, Order.FUNCTION_CALL];
}
const ch = i2cChannel(block);
ensureAutoI2cBusDefinition(block, ch, 21, 22, 400000);
pythonGenerator.definitions_['import_time'] = 'import time';
pythonGenerator.definitions_['tcs34725_rgb'] = TCS34725_RGB_HELPER;
const bus = i2cBusName(ch);
const chIdx = ch === '1' ? 1 : 0;
return [`_tcs34725_rgb(${bus}, ${chIdx}, ${addr})`, Order.FUNCTION_CALL];
};
// ─── Print ────────────────────────────────────────────────

View File

@ -65,6 +65,16 @@ export const ROBOT_TEMPLATE_LIST = [
blockType: 'i2c_init',
applyAs: 'statement',
fields: [
{
key: 'CH',
label: 'Channel',
type: 'dropdown',
options: [
['0', '0'],
['1', '1'],
],
default: '0',
},
{ key: 'SDA', label: 'SDA pin', type: 'number', min: 0, max: 48, default: 8 },
{ key: 'SCL', label: 'SCL pin', type: 'number', min: 0, max: 48, default: 9 },
{ key: 'FREQ', label: 'Frequency (Hz)', type: 'number', min: 1, max: 1_000_000, default: 400_000 },