Last active
February 19, 2024 22:52
-
-
Save sonnny/06969a708c279418b9730cd486ff7a97 to your computer and use it in GitHub Desktop.
pico pio test to decode/encode nmra dcc protocol, work in progress
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //////////////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