program dsPIC33EP_NMRAnetCommandStation; // ****************************************************************************** // // * Copyright: // (c) Mustangpeak Software 2012. // // The contents of this file are subject to the GNU GPL v3 licence/ you maynot use // this file except in compliance with the License. You may obtain a copy of the // License at http://www.gnu.org/licenses/gpl.html // // * Revision History: // 2012-02-01: Created // 2012-11-13: Converted to the dsPIC33EP // 2012-11-13: Updated to v2 of the NMRAbus Library // 2013-3-31 : Service Mode completed // // * Description: // Implements a NMRABus based Command Station // // //* TODO // - Packet Refresh // - Traction Protocol Results: implement // - Implement "stupid throttle" (Lenz/NCE) // - When in Service Mode it is blocked and will cause issues with the node CAN if other chatter is on the bus // - Can't detect no loco on the track in Byte by Bit mode because a lack of an ACK is interperted as it "must be a 0" // - Operations Service Mode needs implementing // // // * Improvements // - Optimize how Service Mode looks for values (check 0, 255, 64, 192 first) // // * Fixes // - 128/14 Step Mode 4/1/13: Fixed 128 step mode not working with short address // - Auto Allocate vTrainNodes 4/1/13: Added configuration option to turn this on off. // - Reworked the Overcurrent to use ADC Interrupts. Much faster and less overhead needed (12us every 5.5ms with 16x oversampling) 4/13/2013 // - Reworked statemachine to allow Binary Searching of Nodes for fast access of vNodes 4/13/2013 // - Reworked Free Node to release vNodes when Train is released; 4/20/13 // // ****************************************************************************** {$I Options.inc} uses dsPIC33_Traps, {$IFDEF CAN_BUS} dsPIC33_CAN, {$ENDIF} NMRAnetStateMachine, NMRAnetDefines, NMRAnetAppCallbacks, NMRAnetDCC, MCU_Setup_dsPIC33EP64GP502, _25AAxxxx, NMRAnetBufferPools, NMRAnetNode, HelperFunctions, NodeIDs, NMRAnetServiceMode, TractionProtocol, ServiceModeDefines; var // EEPROM SPI PINS CS_Bank_0 : sbit at LATB6_bit; CS_Bank_0_Direction : sbit at TRISB6_bit; EE_PROM_Hold : sbit at LATB5_bit; EEPROM_Hold_Direction : sbit at TRISB5_bit; // DCC SIGNAL PINS H_Bridge_A_Lo : sbit at LATA1_bit; H_Bridge_A_Hi : sbit at LATB0_bit; H_Bridge_B_Lo : sbit at LATB1_bit; H_Bridge_B_Hi : sbit at LATB4_bit; H_Bridge_A_Lo_Direction : sbit at TRISA1_bit; H_Bridge_A_Hi_Direction : sbit at TRISB0_bit; H_Bridge_B_Lo_Direction : sbit at TRISB1_bit; H_Bridge_B_Hi_Direction : sbit at TRISB4_bit; DCC_Programming_ACK_Pin : sbit at RB2_bit; // Input so use the port and not the latch DCC_Programming_ACK_Direction : sbit at TRISB2_bit; DCC_NMRA_Tranmitter_Lo : sbit at LATB15_bit; // DCC Bus Transmitter DCC_NMRA_Tranmitter_Hi : sbit at LATB14_bit; // DCC Bus Transmitter DCC_NMRA_Tranmitter_Lo_Direction : sbit at TRISB15_bit; // DCC Bus Transmitter DCC_NMRA_Tranmitter_Hi_Direction : sbit at TRISB14_bit; // DCC Bus Transmitter Ack_TimerEnabled_Bit : sbit at TON_T5CON_bit; // DCC Service Mode ACK Timer Dcc_Timer_PR : Word at PR1; // DCC Interrupt Rollover TrapFlagPin : sbit at LATB3_bit; // OVERLOADING THE NMRA DCC TRANSMITTER CURRENT SENSE // ***************************************************************************** // INTERRUPTS // ***************************************************************************** DCCTime: Word; procedure RunServiceMode; begin // Toggle the Bridge off H_Bridge_A_Lo := 0; // Bridge Off H_Bridge_A_Hi := 0; // Bridge Off H_Bridge_B_Lo := 0; // Bridge Off H_Bridge_B_Hi := 0; // Bridge Off if ServiceModeInfo.iStateMachine > STATE_SERVICEMODE_IDLE then begin if Programming.TX_Flags.TRANSMITTING_FLAG_DCC_PIN_BIT = 1 then begin H_Bridge_A_Lo := 1; H_Bridge_B_Hi := 1; end else begin H_Bridge_A_Hi := 1; H_Bridge_B_Lo := 1; end; // Now we can update the xxxx_DCC_PIN_BIT flags for the next 56us time slot ServiceMode_56us_TimeTick; NMRA_DCC_TransmitterStateMachine(@Programming, True, False); ServiceMode_StateMachine(@Programming); end end; procedure RunNmraDccTranmitter; begin DCC_NMRA_Tranmitter_Lo := 0; // DCC Bus Transmitter DCC_NMRA_Tranmitter_Hi := 0; // DCC Bus Transmitter if Track.TX_Flags.TRANSMITTING_FLAG_DCC_PIN_BIT = 1 then DCC_NMRA_Tranmitter_Lo := 1 else DCC_NMRA_Tranmitter_Hi := 1; end; procedure ToggleBridge; begin H_Bridge_A_Lo := 0; // Bridge Off H_Bridge_A_Hi := 0; // Bridge Off H_Bridge_B_Lo := 0; // Bridge Off H_Bridge_B_Hi := 0; // Bridge Off if Track.TX_Flags.TRANSMITTING_FLAG_DCC_PIN_BIT = 1 then begin H_Bridge_A_Lo := 1; H_Bridge_B_Hi := 1; end else begin H_Bridge_A_Hi := 1; H_Bridge_B_Lo := 1; end; end; procedure INTERRUPT_DCC_Timer(); iv IVT_ADDR_T1INTERRUPT; begin T1IF_bit := 0; // Clear the Flag if CommandStationRamData.OverloadDetected then begin // Toggle the Bridge off H_Bridge_A_Lo := 0; // Bridge Off H_Bridge_A_Hi := 0; // Bridge Off H_Bridge_B_Lo := 0; // Bridge Off H_Bridge_B_Hi := 0; // Bridge Off end else begin if CommandStationConfigurationShadowRam.OutputMode = CONFIG_OUTPUTMODE_SERVICE_MODE then begin RunServiceMode // Command Station is in Service Mode end else begin // Command Station is in Main Line Mode if CommandStationConfigurationShadowRam.DccBusMode = CONFIG_NMRA_DCC_TRANSMITTER then // If we are a DCC Transmitter then handle that RunNmraDccTranmitter; if CommandStationConfigurationShadowRam.RailComEnable = CONFIG_RAILCOM_ENABLED then begin if Track.TX_Flags.TRANSMITTING_FLAG_RAIL_COM_CUTOUT_BIT = 1 then begin H_Bridge_A_Lo := 1; // Short the Rails H_Bridge_B_Lo := 1; end else ToggleBridge end else ToggleBridge; // Now we can update the xxxx_DCC_PIN_BIT flags for the next 56us time slot NMRA_DCC_58us_TimeTick(@Track); // < 1us NMRA_DCC_TransmitterStateMachine(@Track, False, CommandStationConfigurationShadowRam.RailComEnable = CONFIG_RAILCOM_ENABLED); // < 5us NMRA_DCC_LoadPacketIntoTransmitterStateMachine(@Track, PREAMBLE_BIT_COUNT_NORMAL); // < 11us Max end end end; procedure INTERRUPT_100ms_Timer(); iv IVT_ADDR_T2INTERRUPT; // Called once every 100m var i: Integer; begin T2IF_bit := 0; // Clear the Flag for i := 0 to Nodes.AllocatedCount - 1 do NMRAnetStateMachine_100ms_Timer(Nodes.AllocatedList[i]); NMRAnetBufferPools_100ms_TimeTick; ServiceMode_100ms_TimeTick end; procedure INTERRUPT_5ms_Timer(); iv IVT_ADDR_T5INTERRUPT; // Called once every 5m during Service MOde begin T5IF_bit := 0; // Clear the Flag ServiceMode_5ms_TimeTick end; procedure INTERRUPT_ADC1(); iv IVT_ADDR_AD1INTERRUPT; ics ICS_AUTO; const CLAMP_UPPER_LIMIT = 7; CLAMP_LOWER_LIMIT = 0; var i: Integer; begin AD1IF_bit := 0; case OverCurrent.iStateMachine of STATE_OVERCURRENT_NORMAL : begin NMRAnetTractionProtocol_OvercurrentUpdateCountersWithAdcBuffers(@ADC1BUF0, 8, CLAMP_LOWER_LIMIT, CLAMP_UPPER_LIMIT); if OverCurrent.Counter = CLAMP_UPPER_LIMIT then begin // Potential OC detected in this Sampling time frame if OverCurrent.SampleTimeStepLimitCounter = OverCurrent.SampleTimeStepLimitShutDown then Inc(OverCurrent.iStateMachine) // Have found N Sampling frames that all said an overcurrent occured, start the fault sequence else Inc(OverCurrent.SampleTimeStepLimitCounter); // Have not found N sampling frames that have all said an overcurrent occured, increase the sampling counter end else OverCurrent.SampleTimeStepLimitCounter := 0; // If we get one Sampling time frame that is not an OC then reset the timer end; STATE_OVERCURRENT_DETECTED : begin i := 1; CommandStationRamData.OverloadDetected := True; OverCurrent.Counter := CLAMP_LOWER_LIMIT; // Reset the Counters OverCurrent.SampleTimeStepLimitCounter := 0; Inc(OverCurrent.iStateMachine); end; STATE_OVERCURRENT_OFF : begin NMRAnetTractionProtocol_OvercurrentUpdateCountersWithAdcBuffers(@ADC1BUF0, 8, CLAMP_LOWER_LIMIT, CLAMP_UPPER_LIMIT); if OverCurrent.Counter = CLAMP_LOWER_LIMIT then begin // Potential recovery detected in this Sampling time frame if OverCurrent.SampleTimeStepLimitCounter = OverCurrent.SampleTimeStepLimitRestart then Inc(OverCurrent.iStateMachine) // Have found N Sampling frames that all said an overcurrent occured, start the fault sequence else Inc(OverCurrent.SampleTimeStepLimitCounter); // Have not found N sampling frames that have all said an overcurrent occured, increase the sampling counter end else OverCurrent.SampleTimeStepLimitCounter := 0; // If we get one Sampling time frame that is an OC then reset the timer end; STATE_OVERCURRENT_REMOVED : begin i := 0; CommandStationRamData.OverloadDetected := False; OverCurrent.Counter := CLAMP_LOWER_LIMIT; // Reset the Counters OverCurrent.SampleTimeStepLimitCounter := 0; OverCurrent.iStateMachine := STATE_OVERCURRENT_NORMAL; end; end; end; procedure INTERRUPT_Timer_3(); iv IVT_ADDR_T3INTERRUPT; begin T3IF_bit := 0; // Clear the flag // Unused end; function IsPrintableChar(C: Char): Boolean; begin Result := ((Ord( C) >= 32) and (Ord( C) <= 126)) or ((Ord( C) >= 128) and (Ord( C) <= 255)) end; procedure DumpEEProm(ASCII: Boolean); var Buffer: array[0..15] of byte; i, j, Offset: Integer; s: string[64]; Output: string[256]; begin i := 0; Offset := 0; while i < 1024 do // Max is 8192 but that is too much begin _25AAxxxx_Read(EEPROM_BANK_0, Offset, 16, @Buffer[0]); WordToHex(Offset, s); Output := s + ' | '; for j := 0 to 15 do begin s := '' ; if ASCII then s := s + Char(Buffer[j]) else ByteToHex(Buffer[j], s); Output := Output + s + ' '; end; UART1_Write_Text(Output + LF); Offset := Offset + 16; Inc(i); end; end; // ******************* // MAIN LOOP // ******************* var ActiveNode, TempNode: PNMRAnetNode; i: Integer; Cmd: Char; str: string[6]; ProxyData: PTrainProxyRamData; begin SR := SR and $FF1F; // Bug in silicon? Clear the IPL bits (CPU Priority is lowest possible) ANSELA := 0; ANSELB := 0; TRISA4_bit := 0; LATA4_bit := 1; // Output _25AAxxxx_Initialize; NMRAnetStateMachine_Initialize(MUSTANGPEAK_ID_0_HI, {MUSTANGPEAK_SERVICETRACK_NODE_ID_0_LO} MUSTANGPEAK_COMMANDSTATION_ID_0_LO); MCU_Setup_Initialize; // Start the timers and perpherials last NMRA_DCC_Initialize; ServiceMode_Initialize; AppCallback_Configuration_Zeroize(False); AppCallback_Sync_Config_Ram_with_EEPROM; H_Bridge_A_Lo := 0; // Bridge Off H_Bridge_A_Hi := 0; // Bridge Off H_Bridge_B_Lo := 0; // Bridge Off H_Bridge_B_Hi := 0; // Bridge Off H_Bridge_A_Lo_Direction := 0; // Output H_Bridge_A_Hi_Direction := 0; // Output H_Bridge_B_Lo_Direction := 0; // Output H_Bridge_B_Hi_Direction := 0; // Output DCC_NMRA_Tranmitter_Lo := 1; DCC_NMRA_Tranmitter_Hi := 0; DCC_NMRA_Tranmitter_Lo_Direction := 0; // Output DCC_NMRA_Tranmitter_Hi_Direction := 0; // Output UART1_Write_Text(LF+'Mustangpeak Engineering OpenLCB CommandStation Prototype v0.5'+LF); // TON_T3CON_bit := 1; // Turn on current limit AD1IE_bit := 1; // Turn on ADC1 interrupt TON_T2CON_bit := 1; // Turn on the 100ms timer MCU_Setup_Enable_OlcbBus; UART1_Write_Text('Sending Initialization DCC Packets to Rails'+LF); {$IFNDEF DCCTIMER_DISABLE} TON_T1CON_bit := 1; // Start the DCC Timer Delay_ms(10); NMRA_DCC_Packet_Init; // Send our 20 Idle Packets per the spec, note we are not on the OLCB bus yet so this will block until done. {$ENDIF} UART1_Write_Text('Layout is ready to run'+LF); if CommandStationRamData.EnableAutoAllocateProxy = 0 then UART1_Write_Text(LF+'AutoAllocate virtual train nodes enabled (Change in Command Station Configuration)'+LF) else UART1_Write_Text(LF+'AutoAllocate virtual train nodes disabled (Change in Command Station Configuration)'+LF); WordToStr(MAX_NODE_COUNT - 1, s1); UART1_Write_Text(LF+s1+' Train Nodes supported in this build.'+LF); UART1_Write_Text(LF+'***************************************************'+LF); UART1_Write_Text('Menu:'+LF); UART1_Write_Text('X : Send Global Node Verify message'+LF); UART1_Write_Text('A (a) : Force allocation of a Train Node'+LF); UART1_Write_Text('D (d) : Force deallocation of the first Train Node in the list'+LF); UART1_Write_Text('Z (z) : Erase the EEPROM (looses all configruation customization'+LF); UART1_Write_Text('V : Dump EEPROM in ASCII (THIS CAN TAKE A LONG TIME)'+LF); UART1_Write_Text('v : Dump EEPROM in hex (THIS CAN TAKE A LONG TIME)'+LF); UART1_Write_Text('M (m) : Dump RAM and Configuration Offsets for all Nodes'+LF); UART1_Write_Text('3 : Dump allocated Node record data and RAM data '+LF); UART1_Write_Text('4 : Dump all Node record data (THIS CAN TAKE A LONG TIME)'+LF); UART1_Write_Text('***************************************************'+LF); while (TRUE) do begin // Global updates // Allcoate a proxy if needed if (CommandStationRamData.EnableAutoAllocateProxy = 0) and (CommandStationRamData.AllocateProxy > 0) then begin LockCANInterrupt; // There may be dangling nodes that have been allocated then released if NMRAnetTractionProtocol_FindFirstNonDCCAllocatedNode = nil then begin NMRAnetNode_Allocate; end; Dec(CommandStationRamData.AllocateProxy); UnLockCANInterrupt; end; ActiveNode := NMRAnetNode_NextNode; if ActiveNode <> PNMRAnetNode( nil) then begin NMRAnetStateMachine_Process(ActiveNode); if UART1_Data_Ready then begin Cmd := UART1_Read; case Cmd of 'X' : begin NMRAnetStateMachine_TrySendVerifyNodeID(ActiveNode, 0); end; 'A', 'a' : begin LockCANInterrupt; NMRAnetNode_Allocate; UnLockCANInterrupt; end; 'D', 'd' : begin LockCANInterrupt; TempNode := NMRAnetNode_FindFirstVirtualNode; if TempNode <> nil then begin ProxyData := NMRAnetTractionProtocol_ExtractTrainProxyRamData( TempNode); ProxyData^.State := ProxyData^.State and not PS_DCC_ADDRESS_ALLOCATED; end; UnLockCANInterrupt end; 'F', 'f' : begin LockCANInterrupt; TempNode := NMRAnetNode_FindFirstVirtualNode; if TempNode <> nil then NMRAnetNode_MarkForRelease(TempNode); UnLockCANInterrupt end; 'Z', 'z' : begin _25AAxxxx_Erase(EEPROM_BANK_0); end; 'V' : begin DumpEEProm(True); // Print in ASCII end; 'v' : begin DumpEEProm(False); // Print in Hex end; 'M', 'm' : begin for i := 0 to MAX_NODE_COUNT - 1 do begin LongWordToStr(Nodes.RawList[i].ConfigurationAddress, s1); UART1_Write_Text('Offset: ' + s1 +LF); end; for i := 0 to MAX_NODE_COUNT - 1 do begin LongWordToStr(Nodes.RawList[i].RAMAddress, s1); UART1_Write_Text('RAM Offset: ' + s1 +LF); end; end; '9' : begin ByteToStr(CommandStationConfigurationShadowRam.OutputMode, s1); UART1_Write_Text('Output Mode: ' + s1 + LF); ByteToStr(CommandStationConfigurationShadowRam.ProgrammingMode, s1); UART1_Write_Text('Programming Mode: ' + s1 + LF); ByteToStr(CommandStationConfigurationShadowRam.DccBusMode, s1); UART1_Write_Text('DCC Bus Mode: ' + s1 + LF); end; '1' : ServiceMode_Print; '2' : begin IntToStr(OverCurrent.SampleTimeStepLimitShutDown, s1); UART1_Write_Text('SampleTimeStepLimitShutDown: ' + s1 + LF); IntToStr(OverCurrent.SampleTimeStepLimitRestart, s1); UART1_Write_Text('SampleTimeStepLimitRestart: ' + s1 + LF); IntToStr(OverCurrent.SampleTimeStepLimitCounter, s1); UART1_Write_Text('SampleTimeStepLimitCounter: ' + s1 + LF); IntToStr(OverCurrent.Limit, s1); UART1_Write_Text('Over Current Limit: ' + s1 + LF); IntToStr(OverCurrent.Counter, s1); UART1_Write_Text('Over Current Counter: ' + s1 + LF); IntToStr(OverCurrent.BitValue, s1); UART1_Write_Text('BitValue: ' + s1 + LF); FloatToStr(Double( OverCurrent.BitValue) * 1.9230769 * 0.00322266, s1); // .00322266 V/Bit * 1.9230769A/V * Bits = UART1_Write_Text('Measured Current: ' + s1 + 'A' + LF); WordToStr(CommandStationConfigurationShadowRam.OverCurrentLevel, s1); UART1_Write_Text('I Level: ' + s1 + LF); WordToStr(CommandStationConfigurationShadowRam.OverCurrentTime, s1); UART1_Write_Text('I Time: ' + s1 + LF); WordToStr(CommandStationRamData.OverloadDetected, s1); UART1_Write_Text('OverloadDetected: ' + s1 + LF); end; '3' : begin for i := 0 to Nodes.AllocatedCount - 1 do begin NMRAnetNode_PrintNodeData( i, Nodes.AllocatedList[i]); NMRAnetTractionProtocol_PrintNodeRamData(Nodes.AllocatedList[i], False); UART1_Write_Text(LF + LF); end; end; '4' : begin NMRAnetNode_PrintRawNodeData end; '5' : begin end; end; end; end; if C1IE_bit = 0 then UART1_Write_Text('CAN Disabled!'); end; end.