pico program for dcc function decoder flutter program demo for connecting to bluetooth module and run the app for dcc function decoder youtube - https://www.youtube.com/watch?v=4MvgJCYxS-U /* // original source from https://github.com/gab-k/RP2040-Decoder // another dcc function decoder // this program only demo address 13 F5 function nothing else // when it received dcc message to turn on address 13 function 5 it will // turn on led connected to gpio 3 // when it received dcc message to turn off address 13 function 5 it will // turn off led connected to gpio 3 // // assumes F6, F7, and F8 are all off on startup // // from gab-k github wiki // The detection of the DCC signal works by looking // at every rising and falling edge and calculating the // time between them. When the time between rising and // falling edge is greater than 87us the this is equivalent // to "0" else "1" - this value then gets shifted // into a 64 bit variable. // // Decoding is done after every falling edge. It starts // with an error detection, when the detection fails // the received command is dismiss. If the error detection // passed the address will be decoded and compared to // the hardcoded address value. Then if the address matches // the command/instruction will be decoded. // // most important function - evaluate_message * * function evaluate_message * check if dcc message is valid * if valid then we get how many bytes in the message * split data into 8 bit array * check for error * check for address match * process instruction // // uses this board: https://ilabs.se/product/opendec02/ // // to compile (assumes pico-sdk 2.0): // mkdir build // cd build // cmake -DPICO_BOARD=ilabs_opendec02 .. // make // // flashing: // sudo openocd -f interface/cmsis-dap.cfg // -f target/rp2040.cfg -c "adapter speed 5000" // -c "program RP2040-Decoder.elf verify reset exit" // // note on flashing my setup: // unplug the usb with motor driver then plug back if // having problem with openocd // // on startup blink white led 3 times // show 55 on the two 7 segment led */ /* todo, need to delete not needed include files */ #include "string.h" #include "pico/float.h" #include "pico/stdlib.h" #include "pico/multicore.h" #include "pico/printf.h" #include "hardware/pwm.h" #include "hardware/adc.h" #include "hardware/flash.h" #include "hardware/i2c.h" /* forward declaration */ void track_signal_fall(unsigned int gpio, long unsigned int events); #define SIZE_BYTE_ARRAY 5 #define MESSAGE_3_BYTES 0b11111111110000000000000000000000000001 #define MESSAGE_MASK_3_BYTES 0b11111111111000000001000000001000000001 #define MESSAGE_4_BYTES 0b11111111110000000000000000000000000000000000001 #define MESSAGE_MASK_4_BYTES 0b11111111111000000001000000001000000001000000001 #define MESSAGE_5_BYTES 0b11111111110000000000000000000000000000000000000000000001 #define MESSAGE_MASK_5_BYTES 0b11111111111000000001000000001000000001000000001000000001 #define _125M 125000000 // Constant Value of 125 x 10^6 #define MY_ADDRESS 13 // hard code address #define DCC_INPUT_PIN 20u // ilabs decoder board #define F5_ON 0x20 // f5 on (only f5 not f6,f7,f8 uint64_t input_bit_buffer = 0; uint16_t level_table[32] = {0}; absolute_time_t falling_edge_time, rising_edge_time; uint8_t i2c_buffer[2]; /* buffer for 2 seven segment led i2c driver from electric dollar store */ /* * function get_direction speed * bit 7 is the speed direction */ bool get_direction_of_speed_step(uint8_t speed_step){return speed_step >> 7;} /* function error_detection * bitwise xor all bytes, result should be 0x00 * see nmra dcc packet error byte */ bool error_detection(const int8_t number_of_bytes, const uint8_t *const byte_array) { uint8_t xor_byte = 0; for (int i = 0; i < number_of_bytes; i++) { xor_byte = xor_byte ^ byte_array[i];} return (0 == xor_byte);} /* * function is_long_address */ bool is_long_address(uint8_t const number_of_bytes, const uint8_t *const byte_array) { if ((byte_array[number_of_bytes - 1] >> 6) == 0b00000011) { return true;} return false;} /* * function address_evaluation * match incoming address with hardcoded address (see define above) */ bool address_evaluation(const uint8_t number_of_bytes, const uint8_t *const byte_array) { if (byte_array[number_of_bytes - 1] == 255) { return false;} /* check for idle message */ uint16_t read_address; read_address = byte_array[number_of_bytes - 1]; return MY_ADDRESS == read_address; } /* function instruction_evaluation * check if instruction is speed, function, etc. * position of instruction depends on whether the address is long or short * function f5 - 0b0010 0000 * function f6 - 0b0100 0000 * function f7 - 0b1000 0000 * function f5 and f6 - 0b0110 0000 */ void instruction_evaluation(const uint8_t number_of_bytes, const uint8_t *const byte_array) { uint8_t command_byte_start_index; if (is_long_address(number_of_bytes, byte_array)) { command_byte_start_index = number_of_bytes - 3; } else { command_byte_start_index = number_of_bytes - 2;} const uint8_t command_byte_n = byte_array[command_byte_start_index]; if (command_byte_n >> 6 == 0b00000010) { // 10XX-XXXX function group instruction switch(command_byte_n >> 4) { case 0b00001011: // f5-f8 uint8_t val = command_byte_n << 5; if (val == F5_ON){ gpio_put(3,1); } // only F5 (not f6,f7,f8) else if (val == 0x00) { gpio_put(3,0); } i2c_buffer[1] = command_byte_n << 5; i2c_write_blocking(i2c0,0x14,i2c_buffer,2,true); break; default: break;}}} /* function verify_dcc_message * looking for pattern */ int8_t verify_dcc_message() { int8_t number_of_bytes = -1; const uint64_t masked_message_3_bytes = input_bit_buffer & MESSAGE_MASK_3_BYTES; if (masked_message_3_bytes == MESSAGE_3_BYTES) number_of_bytes = 3; const uint64_t masked_message_4_bytes = input_bit_buffer & MESSAGE_MASK_4_BYTES; if (masked_message_4_bytes == MESSAGE_4_BYTES) number_of_bytes = 4; const uint64_t masked_message_5_bytes = input_bit_buffer & MESSAGE_MASK_5_BYTES; if (masked_message_5_bytes == MESSAGE_5_BYTES) number_of_bytes = 5; return number_of_bytes;} /* * convert bits array to byte array */ void bits_to_byte_array(const int8_t number_of_bytes, uint8_t byte_array[]) { for (uint8_t i = 0; i < number_of_bytes; i++) { byte_array[i] = input_bit_buffer >> (i * 9 + 1);}} /* ************************************************************* * for me the most important function on this entire program ************************************************************* * function evaluate_message * check if dcc message is valid * if valid then we get how many bytes in the message * split data into 8 bit array * check for error * check for address match * process instruction */ void evaluate_message(){ const int8_t number_of_bytes = verify_dcc_message(); // verify dcc message if (number_of_bytes != -1) { // if number of bytes is not equal to -1 uint8_t byte_array[SIZE_BYTE_ARRAY] = {0}; bits_to_byte_array(number_of_bytes, byte_array); // split data into 8 bit array if (error_detection(number_of_bytes, byte_array)) { // check for error if (address_evaluation(number_of_bytes, byte_array)) { // check for address match instruction_evaluation(number_of_bytes, byte_array);}}}} // process instruction /* * function track_signal_rise * get time * disable rising irq and enable falling irq */ void track_signal_rise(const unsigned int gpio, const long unsigned int events) { rising_edge_time = get_absolute_time(); gpio_set_irq_enabled_with_callback(DCC_INPUT_PIN, GPIO_IRQ_EDGE_RISE, false, &track_signal_rise); gpio_set_irq_enabled_with_callback(DCC_INPUT_PIN, GPIO_IRQ_EDGE_FALL, true, &track_signal_fall);} /* * function track_signal_fall * get time difference in rising and falling * decide if bit is 0 or 1 using 87 microsecond as threshold * shift bit in the buffer * evaluate message * disable falling irq and enable rising irq */ void track_signal_fall( const unsigned int gpio, const long unsigned int events) { falling_edge_time = get_absolute_time(); const int64_t time_logical_high = absolute_time_diff_us(rising_edge_time, falling_edge_time); bool bit; if (time_logical_high > 87) bit = 0; else bit = 1; input_bit_buffer <<= 1; input_bit_buffer |= bit; evaluate_message(); gpio_set_irq_enabled_with_callback(DCC_INPUT_PIN, GPIO_IRQ_EDGE_FALL, false, &track_signal_fall); gpio_set_irq_enabled_with_callback(DCC_INPUT_PIN, GPIO_IRQ_EDGE_RISE, true, &track_signal_rise);} int main() { stdio_init_all(); gpio_init(3); gpio_set_dir(3,GPIO_OUT); /* blink led 5 times on start */ for(int i=0; i<5; i++){gpio_put(3,1); sleep_ms(200); gpio_put(3,0); sleep_ms(200);} /* init i2c 2 digit seven segment from electric dollar store */ i2c_init(i2c0,400*1000); gpio_set_function(4,GPIO_FUNC_I2C); gpio_set_function(5,GPIO_FUNC_I2C); gpio_pull_up(4); gpio_pull_up(5); i2c_buffer[0] = 1; // hex format; i2c_buffer[1] = 0x55; // write 0x55 on startup i2c_write_blocking(i2c0,0x14,i2c_buffer,2,true); sleep_ms(500); gpio_init(DCC_INPUT_PIN); gpio_set_dir(DCC_INPUT_PIN, GPIO_IN); gpio_pull_up(DCC_INPUT_PIN); gpio_set_irq_enabled_with_callback(DCC_INPUT_PIN, GPIO_IRQ_EDGE_RISE, true, &track_signal_rise); busy_wait_ms(200); while (true); } /*********************************** * CMakeLists.txt ***********************************/ cmake_minimum_required(VERSION 3.12) # Pull in SDK (must be before project) include(pico_sdk_import.cmake) project(RP2040-Decoder C CXX ASM) set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 17) set(PICO_DECODER_PATH DOLLAR_SIGN{PROJECT_SOURCE_DIR}) # Stop the compiler from inling method calls making it easier to debug set(PICO_DEOPTIMIZED_DEBUG 1) # Initialize the SDK pico_sdk_init() /* add_compile_options(-Wall -Wno-format # int != int32 _t as far as the compiler is concerned because gcc has int32_t as long int -Wno-unused-function # we have some for the docs that aren't called -Wno-maybe-uninitialized ) */ add_executable( RP2040-Decoder core0.c) # Multiplier to lengthen XOSC startup delay to accommodate slow-starting oscillator target_compile_definitions( RP2040-Decoder PUBLIC PICO_XOSC_STARTUP_DELAY_MULTIPLIER=64 ) # Add pico_multicore which is required for multicore functionality target_link_libraries( RP2040-Decoder pico_stdlib hardware_pwm hardware_adc hardware_flash hardware_i2c hardware_pio pico_multicore ) # disable usb stack pico_enable_stdio_usb(RP2040-Decoder 0) # disable uart output (to enable put a 1 instead of the 0) pico_enable_stdio_uart(RP2040-Decoder 0) # create map/bin/hex file etc. pico_add_extra_outputs(RP2040-Decoder) // // demo of dcc function using smartphone app // uses bluetooth hc19 module // uses flutter_reactive_ble plugin // don't forget to give this app location permission on android // use nrf connect to find out device id, service, and characteristic // press connect button to connect to hc19 bluetooth module // make sure button status is connected before continuing // uses github.com/pico-cs/firmware for dcc station // protocol: // +mte t \r - turn on track power // +mte f \r - turn off track power // +lf 13 5 t \r - turn on address 13 function 5 // +lf 13 5 f \r - turn off address 13 function 5 // import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; // needed for exit import 'package:get/get.dart'; import 'package:flutter_reactive_ble/flutter_reactive_ble.dart'; import 'dart:async'; import 'dart:convert'; // needed for utf8.encode final buttonStyle = ButtonStyle(backgroundColor: MaterialStateProperty.all(Colors.teal[100])); void main() {WidgetsFlutterBinding.ensureInitialized(); // without this, error tx has not been initialized runApp(MaterialApp(home:Home()));} class Home extends StatelessWidget{ final ble=FlutterReactiveBle(); late QualifiedCharacteristic tx; dynamic trackPower=false.obs; var status='connect to ble'.obs; void send(val) async { List data=utf8.encode(val); await ble.writeCharacteristicWithoutResponse(tx,value:data);} void ble_connect() async{ status.value='connecting...'; late StreamSubscription c; c=ble.connectToDevice(id:'A4:DA:32:55:06:1E').listen((s){ if (s.connectionState == DeviceConnectionState.connected){ status.value='connected'; tx=QualifiedCharacteristic(serviceId:Uuid.parse('0000ffe0-0000-1000-8000-00805f9b34fb'), characteristicId:Uuid.parse('0000ffe1-0000-1000-8000-00805f9b34fb'), deviceId:'A4:DA:32:55:06:1E');}});} @override Widget build(BuildContext c){ return Scaffold(appBar:AppBar(title:Text('dcc function bluetooth demo')), body:Column(children:[ SizedBox(height:40), ElevatedButton(onPressed:(){ble_connect();}, child: Obx(() =>Text('${status}')), style:buttonStyle), SizedBox(height:40), Padding(padding:EdgeInsets.all(16.0),child: Obx(() => Row(children:[Text('track power on: '), (Switch(value:trackPower.value, activeColor:Colors.green, onChanged:(bool v){send('+mte t\r'); trackPower.value=v;}))]))), SizedBox(height:40), ElevatedButton(child:Text('F5 on'), onPressed:(){send('+lf 13 5 t\r');}, style:buttonStyle), SizedBox(height:40), ElevatedButton(child:Text('F5 off'), onPressed:(){send('+lf 13 5 f\r');}, style:buttonStyle), SizedBox(height:40), //on exit turn off track power ElevatedButton(child:Text('Exit'), onPressed:(){ send('+mte f\r'); SystemNavigator.pop();})]));}}