budget dcc youtube --> https://www.youtube.com/watch?v=9-TJmxLq8jw ble $2.95 - https://www.seeedstudio.com/HM-BT4502-Bluetooth-Low-Energy-BLE-Pass-through-Module-p-4440.html booster $4.95 - https://www.pololu.com/product/2136 booster can be wired parallel to increase drive current see datasheet pinout connections bluetooth ble vdd - pico pin 36 3.3v ble gnd - pico any ground pins ble rxd - pico pin 6 gp4 ble txd - pico pin 7 gp5 booster drv8801 drv8801 vdd - pico pin 36 3.3v drv8801 gnd - pico any ground pins drv8801 pwm(enable) - pico pin 36 3.3v drv8801 dir(phase) - pico pin 20 gp15 drv8801 vmm - power supply positive 12v - 15v drv8801 gnd - power supply negative drv8801 out + - track rail (can be interchange with out -) drv8801 out - - track rail (can be interchange with out +) #############pico micropython code #dcc demo from machine import UART, Pin from micropython import const import time, rp2, array, struct l0=Pin(25,Pin.OUT) l1=Pin(16,Pin.OUT) l2=Pin(17,Pin.OUT) uart=UART(1,115200) l0.on() time.sleep(2) l0.off() time.sleep(2) l0.on() ADDRESS = const(20) #santa fe speed = 0 isForward = True data = 0 dir = 0x60 packet = bytearray(b'\xff\xff\xfe\xff\xff\xff\xff\xff') idle = bytearray(b'\xff\xfd\xfe\x00\x7f\xc0\x00\x00') def init(): global packet packet = bytearray(b'\xff\xff\xfe\xff\xff\xff\xff\xff') def assemble(addr, d): global packet checksum = addr ^ d packet[3] = addr packet[4] = d >> 1 packet[5] = checksum >> 2 if not((d >> 7) & 1): packet[5] |= 1 << 7 temp = checksum << 6 packet[6] = temp | 0x3f @rp2.asm_pio(set_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT, autopull=True) def dcc(): label("bitloop") set(pins,1) [20] out(x,1) jmp(not_x,"do_zero") set(pins,0) [21] jmp("bitloop") label("do_zero") nop() [16] set(pins,0) [30] nop() [8] # freq = 400_000 2.5us clock cycle sm = rp2.StateMachine(0, dcc, freq=400000, set_base=Pin(15)) sm.active(1) while 1: if uart.any() > 0: c = uart.read(1) cc = c.decode('UTF-8') if cc == '$': if uart.any() > 0: d = uart.read(1) dd = d.decode('UTF-8') if dd == 'd': #direction isForward = not isForward if isForward: data = 0x61 dir = 0x60 else: data = 0x41 dir = 0x40 if dd == 's': data = 0x41 #stop if dd == 'F': data = dir + 0x0f #FAST if dd == 'M': data = dir + 0x09 #MEDIUM if dd == 'S': data = dir + 0x05 #SLOW #print(hex(data)) init() #reset packet assemble(ADDRESS,data) w1,w2=struct.unpack('>II',packet) sm.put(w1) sm.put(w2) time.sleep_ms(5) #nmra specs else: #send idle packets w1,w2=struct.unpack('>II',idle) sm.put(w1) sm.put(w2) time.sleep_ms(5) #nmra specs // flutter files // make sure change build.gradle to minsdk to atleast 21 // part of pubspec.yaml dependencies: flutter: sdk: flutter get: flutter_reactive_ble: group_button: get_storage: /////////////////////filename: main.dart import './listing.dart'; import 'package:group_button/group_button.dart'; import './blecontroller.dart'; import './controller.dart'; void main() async { await GetStorage.init(); runApp(MyApp());} class MyApp extends StatelessWidget { final appdata = GetStorage(); TextStyle myStyle = TextStyle(fontSize: 20); final BleController ble = Get.put(BleController()); final Controller c = Get.put(Controller()); void processButton(int i){ switch(i){ case 0:{c.toggleDirection();}break; case 1:{c.setSpeed(i);}break; case 2:{c.setSpeed(i);}break; case 3:{c.setSpeed(i);}break; case 4:{c.setSpeed(i);}break;} ble.send(i);} @override Widget build(BuildContext context){ appdata.writeIfNull('darkmode',false); return SimpleBuilder(builder:(_){ bool isDarkMode = appdata.read('darkmode'); return GetMaterialApp( debugShowCheckedModeBanner:false, theme: isDarkMode ? ThemeData.dark() : ThemeData.light(), home: Scaffold( appBar:AppBar(title: Text('DCC BLE'), actions:[Switch(value:isDarkMode, onChanged:(value)=>appdata.write('darkmode',value))]), body: Column(children: [ SizedBox(height:50), Obx(() => Text('${ble.status}', style:myStyle)), SizedBox(height:40), Row( mainAxisAlignment:MainAxisAlignment.center, children:[ Text('direction: ',style:myStyle), Obx(()=>Icon(c.direction.value == 'forward' ?Icons.arrow_back_rounded : Icons.arrow_forward_rounded)), Text('speed: ',style:myStyle), Obx(()=>Text('${c.speed.value}',style:myStyle))]), SizedBox(height:40), GroupButton( isRadio: true, spacing: 10, onSelected: (index, isSelected) => processButton(index), buttons: ["direction","fast", "medium", "slow", "stop"], borderRadius: BorderRadius.circular(5.0),), SizedBox(height:40), Obx(() => Container( child: ble.status != 'connected!' ? ElevatedButton(onPressed: ble.connect, child: Text('connect', style:myStyle)) : null)) ])),);});}} ///////////////////////// filename: blecontroller.dart import './listing.dart'; import 'package:flutter_reactive_ble/flutter_reactive_ble.dart'; import 'dart:async'; class BleController { final frb = FlutterReactiveBle(); late StreamSubscription c; late QualifiedCharacteristic tx; RxString status = 'not connected'.obs; List values = [0x24,0,0x00]; void send(int i) async { switch(i){ case 0: {values[1]=0x64;}break; case 1: {values[1]=0x46;}break; case 2: {values[1]=0x4d;}break; case 3: {values[1]=0x53;}break; case 4: {values[1]=0x73;}break;} await frb.writeCharacteristicWithoutResponse(tx,value:values);} void connect() async { status.value = 'connecting...'; c = frb.connectToDevice(id: '62:00:A1:1C:46:54').listen((state){ if (state.connectionState == DeviceConnectionState.connected){ status.value = 'connected!'; tx = QualifiedCharacteristic( serviceId: Uuid.parse("ffe0"), characteristicId: Uuid.parse("ffe9"), deviceId:'62:00:A1:1C:46:54');}});}} ////////////////// filename: listing.dart export 'package:flutter/material.dart'; export 'package:get/get.dart'; export 'package:get_storage/get_storage.dart'; /////////////////filename: controller.dart import 'package:flutter/material.dart'; import 'package:get/get.dart'; class Controller{ var direction = 'forward'.obs; var speed = 'stop'.obs; toggleDirection(){ if (direction.value == 'forward') direction.value = 'backward'; else direction.value = 'forward';} setSpeed(int s){ switch(s){ case 1:{speed.value = 'fast';}break; case 2:{speed.value = 'medium';}break; case 3:{speed.value = 'slow';}break; case 4:{speed.value = 'stop';}break; }}}