Skip to content

Instantly share code, notes, and snippets.

@sonnny
Last active February 19, 2024 22:52
Show Gist options
  • Select an option

  • Save sonnny/06969a708c279418b9730cd486ff7a97 to your computer and use it in GitHub Desktop.

Select an option

Save sonnny/06969a708c279418b9730cd486ff7a97 to your computer and use it in GitHub Desktop.
pico pio test to decode/encode nmra dcc protocol, work in progress
//////////////readme.txt
when using flutter syntax view to view
source file, don't forget to replace dollar sign
so the code text will have multiple colors
nmra dcc decoder encoder
this is my project to generate and encode nmra
dcc pulses for model train. google nmra dcc
pulses to understand what it is. pulse one contains
58 microsecond high 58 microsecond low
pulse zero contains
100 microsecond high 100 microsecond low
this is tutorial for myself about the pico rp2040
pio and nmra dcc protocol
nmra dcc encoder I use the pico pio to jmp
to a pio offset to generate preamble, after the
offset to generate preamble, I generate the packet
data, so the offset is a good pio tutorial how
to execute a pio instruction from the c program
outside the pio program.
generating nmra dcc pulses:
what follows the preamble is the actual shifting
of bits from the input shift register, testing
the shifted bit if it's one or zero then generate
the pulses depending if it's a zero or one.
encoding nmra dcc pulses:
I wait for pulse rising edge, then delay
about 75 microsecond, check if pin is still high,
it it's still high then the bit must be zero,
because if it's a one then the pin must have been
low (because one pulse is 58 microsecond high
the 58 microsecond low) so in theory after
75 microsecond delay and the pin is still high
then it must be a zero pulse.
I look for a minimum of 12 consecutive one pulses, if in
between 12 pulses I see a 0 pulse then I
reset the a counter and start to look for
preamble again, preamble must have at least
a minimum of 12 one pulses and no zero pulses
in between those 12 pulses. also, preamble can have
more than 12 consecutive pulses, 12 is a minimum
recommendation only.
if i found a valid preamble, i look for a zero pulse,
per nmra dcc protocol after preamble there must
be a zero pulse before starting the 8 bit address,
so I keep looking for a zero after a minimum
of 12 1 pulses, remember that you can have more
than 12 1 pulses for preamble.
if a found a zero pulse after a valid preamble pattern
I decode 32 bits which should contain 8 bit address,
1 zero pulse separator, 8 bit data (direction and speed),
1 zero pulse separator, 8 bit checksum, 1 one pulse end of packet.
In the main program I extract address, data (speed direction),
checksum. I xor the address and data and compare to the
checksum byte, if it matches then the address and data byte
must be good.
one algorithm i found on the net that helped me with
this project. the assembling of bytes to generate
a dcc packet that I found on mbed.com.
another algorithm i found on the net is how to extract
bits from an unsigned 32 bit number, i found the code
in stackoverflow.com, first you generate a mask which
bit you want to extract by specipying start and end bit
of what bits you want extracted, then you use the mask
to and'ing the mask and the 32 bit number and the result
is the bits you extracted from the usigned 32 bits.
i eventually would like to build a dcc command station
and maybe an acessory decoder for model trains.
this is a work in progress and more to serve to keep
me busy since I'm retired and most of it is done
while vacationing in the Philippines. During slow
times while vacationing, I keep myself entertain
writing pico c code and flutter.
//
///////////////////////////////
//filename: blink_test.c
//////////////////////////////
//still testing... feb 20, 2024
//
//demo to generate nmra dcc pulse
//also to decode generated dcc pulse
//gpio pin 8 generates nmra dcc pulse
//gpio pin 7 decodes nmra dcc pulse
//pio clock set to 2.5us per pio instruction/cycle
//frequency calculation link below
//https://www.sensorsone.com/frequency-to-period-calculator/
//nmra dcc pulse one 58us high 58us low
//nmra dcc pulse zero 100us high 100us low
//
//i'm doing this project because i love model train
//and i love learning the pio on the rp2040
//
//todo, check for:
// idle packet
// reset packet
// 2 byte address
// etc.
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "hardware/clocks.h"
#include "blink.pio.h"
#include "pulsewidth.pio.h"
uint offset_0 = 0;
volatile uint32_t packet;
//assemble packet from mbed.com
//https://os.mbed.com/users/4180_1/notebook/controlling-a-model-railroad-using-mbed/
uint32_t assemble_packet(uint8_t address, uint8_t data){
uint8_t checksum = address ^ data;
packet = 0;
packet = (address << 23) | (data << 14) | (checksum << 5) | (0x1 << 4);
return packet;}
int main(){
stdio_init_all();
sleep_ms(5000); //give time to terminal program to launch
printf("starting program...\n");
sleep_ms(1000);
//init gpio output on pin 8
//clock 2.5us
PIO pio_0=pio0;
int sm_0=0;
offset_0=pio_add_program(pio_0,&blink_program);
pio_gpio_init(pio_0,8);
pio_sm_set_consecutive_pindirs(pio_0,sm_0,8,1,true);//output
pio_sm_config c0=blink_program_get_default_config(offset_0);
sm_config_set_clkdiv(&c0,clock_get_hz(clk_sys)/400000.0);
sm_config_set_set_pins(&c0,8,1);
sm_config_set_out_shift(&c0,
false, //shift left
true, //autopull
32);
pio_sm_init(pio_0,sm_0,offset_0,&c0);
pio_sm_clear_fifos(pio_0,sm_0);
pio_sm_set_enabled(pio_0,sm_0,true);
//init gpio input on pin 7
//clock 1 us
PIO pio_1=pio1;
int sm_1=0;
uint offset_1=pio_add_program(pio_1,&pulsewidth_program);
pio_sm_set_consecutive_pindirs(pio_1,sm_1,7,1,false);//input
pio_gpio_init(pio_1,7);
gpio_pull_up(7);
pio_sm_config c1=pulsewidth_program_get_default_config(offset_1);
sm_config_set_jmp_pin(&c1,7);
sm_config_set_in_pins(&c1,7);
sm_config_set_in_shift(&c1,false,false,0);
sm_config_set_clkdiv(&c1,clock_get_hz(clk_sys)/400000.0);
pio_sm_init(pio_1,sm_1,offset_1,&c1);
pio_sm_clear_fifos(pio_1,sm_1);
pio_sm_set_enabled(pio_1,sm_1,true);
uint32_t count=0;
uint32_t address_temp=0;
uint32_t address=0;
uint32_t data_temp=0;
uint32_t data=0;
uint32_t checksum_temp=0;
uint32_t checksum=0;
uint32_t temp=0;
for(;;){
//send preamble, address, and data
pio_sm_exec_wait_blocking(pio_0, sm_0, pio_encode_jmp(offset_0 + blink_offset_preamble));
pio_sm_put_blocking(pio_0, sm_0, assemble_packet(15, 77));
//get decoded packet 32 bits
count=pio_sm_get_blocking(pio_1,sm_1);
//extract address,data,checksum
address=(count & 0xFF000000)>>24;
data=(count & 0x007F8000)>>15;
checksum=(count & 0x00003FC0)>>6;
//check checksum of address and data
//if checksum matches, print address and data
if(checksum == (address ^ data)){
printf("address: %lu\n",address);
printf("data: %lu\n\n",data);}
//printf("checksum: %lu\n\n",checksum);}
else
printf("bad packet\n\n");
sleep_ms(1000);
} return 0;}
;;;;;;;;;;;;;;;;;;;;;;;;
;;filename: blink.pio
;;;;;;;;;;;;;;;;;;;;;;;
;
;generate nmra waveform
;clock setup 2.5us per pio instruction/delay
;
;pulse 1 58us high 58us low
;pulse 0 100us high 100us low
;preamble at least 14 consecutive 1 pulse
.program blink
PUBLIC preamble:
set x,17 ; at least 17 one bits
loop:
set pins, 1 [22] ;high 57.5us
;1 instruction + 22 delay
;23 x 2.5us = 57.5us
set pins, 0 [21] ;low 57.5us
jmp x--, loop ;2 instruction + 21 delay
;23 x 2.5us = 57.5us
.wrap_target
PUBLIC shift_data:
loop1:
set pins, 1 [20] ;pin high
out x,1 ;get 1 bit from OSR
jmp !x do_zero ;check if bit is 1 or zero
set pins, 0 [21] ;bit is 1, set pin to low
jmp loop1 ;do all 32 bits
do_zero: ;bit is zero
nop [16] ;delay pin high more for pulse zero
set pins, 0 [30] ;pin low for 100us
nop [8] ;2 instruction + 38 delay
;40 x 2.5us = 100us pin low
.wrap
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;filename: pulsewidth.pio
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;detect nmra dcc pulse
;pulse 1 58us high 58us low
;pulse 0 100us high 100us low
;
;clock setup 2.5us per pio instruction/delay
;
;look for rising edge
;delay 75us
; nop [29] = 1 + 29 instruction/cycle
; 30 x 2.5us = 75us
;
;if input pin is still high
; it's zero pulse
;
;if input pin is low
; it's one pulse
;
;preamble should be at least 14 ones or more
.program pulsewidth
preamble:
set x,14 ;minimum 14 one pulses
possible_preamble:
wait 0 pin 0 ;wait pin to go low
wait 1 pin 0 [30];wait pin to go hi
jmp pin preamble ;pin high must be zero start looking
;for one pulse again all over again
jmp x-- possible_preamble
;after preamble, there should be
;a zero pulse that signals start of
;packet
separator1:
wait 0 pin 0 ;wait input pin to go low
wait 1 pin 0 [30];wait input pin to go high
jmp pin packet ;pin still high found zero pulse
;proceed to packet decoding
jmp separator1 ;pin low must be one
;look for rising edge again
;still one might be leftover ones
;from preamble, remember that
;preamble only shows minimum one pulses
;but you can exceed more than minimum one pulses
;packet starts here
;8 bit address
;zero pulse separator
;8 bit speed/direction
;zero pulse separator
;checksum of address and speed/direction
;one pulse end of packet
packet:
set x,31 ;get 32 bits
loop1:
wait 0 pin 0 ;wait pin low
wait 1 pin 0 [30];wait pin high
in pins, 1 ;shift bit left in ISR
jmp x-- loop1 ;get all 32 bits
mov x isr ;copy ISR to x
mov isr ~x ;complement the bits
;we have to complement
;the bits because the
;dcc protocol longer pulse
;is zero bit and not 1 bit
;so the instruction in pins, 1
;will save 1 (high pin) instead
;of zero bit
push ;send to program
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment