mrrwa / NmraDcc

NMRA Digital Command Control (DCC) Library
GNU Lesser General Public License v2.1
137 stars 53 forks source link

Question: Can DCC reading be paused temporarily? #77

Closed reduzent closed 7 months ago

reduzent commented 7 months ago

(It feels a bit awkward to ask questions in GitHub. If there is a better place, please let me know.)

I'm trying to build a turn-out decoder with an ATTiny85. I couldn't find a servo library that doesn't interfere with NmraDcc. So I decided to create the pulses in the code. However, while there is a DCC signal on the tracks, the servo becomes jittery. I guess that is because the interrupt has more priority than the delayMicroseconds() function. So, i thought about turning DCC reading off while the servo is moving. My idea basically is:

  1. Read DCC
  2. When a packet with the correct address is received, turn of DCC reading
  3. Perform action that requires precise timing (like controlling a servo)
  4. When action has finished, turn back on DCC reading.

I'm aware that I would miss packets during the time DCC reading is turned off, but I don't have a need to switch turn-outs in quick succession.

So, is there a way to disable DCC reading termporarily? Or is there another way to get jitterless servo control?

This is the code I'm currently using, which disables the pulse after switching so that the servo stops trembling. It works, but it would be nice if I could move the servo slowly and steadily:

#include "NmraDcc.h"
NmraDcc  Dcc ;
DCC_MSG  Packet ;

const int adresse = 25; 

const int LED = 4;
const int relay = 3;
const int servo = 1;
int pos = 90;
unsigned long last_update;
unsigned long now;

void servo_pulse(int pin, int pos){
  int pulse = map(pos,0, 180, 850, 2050);
  digitalWrite(pin, HIGH);
  delayMicroseconds(pulse);
  digitalWrite(pin, LOW);
  delay(20);
}

void notifyDccAccTurnoutOutput( uint16_t Addr, uint8_t Direction, uint8_t OutputPower )
{ 
  switch(Addr){
    case adresse:
      if(Direction < 1){
        digitalWrite(LED, LOW);
        digitalWrite(relay, LOW);
        pos = 65;
      }else{
        digitalWrite(LED, HIGH);
        digitalWrite(relay, HIGH);
        pos = 115;
      }
      last_update = millis();
      break;
      default:
      break;
  }
}

void setup()
{
  Dcc.pin(0, 2, 1);
  Dcc.init( MAN_ID_DIY, 10, CV29_ACCESSORY_DECODER | CV29_OUTPUT_ADDRESS_MODE, 0 );
  pinMode(LED, OUTPUT);
  pinMode(relay, OUTPUT);
  pinMode(servo, OUTPUT);
}

void loop()
{
  Dcc.process();
  now = millis();
  if ((now - last_update) < 65){
    servo_pulse(servo, pos);
  } 
}
reduzent commented 7 months ago

Hm.. now while reading the code, I wonder whether it is possible to perform the NmraDcc.init() function in the main loop, setting it to a pin with no DCC signal, and then later back to pin 2.

reduzent commented 7 months ago

It worked by detaching the interrupt while the servo is moving (see line 59):

#include <EEPROM.h>
NmraDcc  Dcc ;
DCC_MSG  Packet ;

const int adresse = 25; 

const int LED = 4;
const int relay = 3;
const int servo = 1;
const int pos_straight = 93;
const int pos_turn = 156;

bool state = LOW;
bool got_packet = false;

void servo_pulse(int pin, int pos){
  int pulse = map(pos,0, 180, 850, 2050);
  digitalWrite(pin, HIGH);
  delayMicroseconds(pulse);
  digitalWrite(pin, LOW);
  delay(21);
}

void notifyDccAccTurnoutOutput( uint16_t Addr, uint8_t Direction, uint8_t OutputPower )
{ 
  switch(Addr){
    case adresse:
      if(Direction < 1){
        state = LOW;
      }else{
        state = HIGH;
      }
      got_packet = true;
      EEPROM.write(adresse, state);
      break;
    default:
      break;
  }
}

void setup()
{
  Dcc.pin(0, 2, 1);
  Dcc.init( MAN_ID_DIY, 10, CV29_ACCESSORY_DECODER | CV29_OUTPUT_ADDRESS_MODE, 0 );
  pinMode(LED, OUTPUT);
  pinMode(relay, OUTPUT);
  pinMode(servo, OUTPUT);
  state = EEPROM.read(adresse);
  digitalWrite(LED, state);
  digitalWrite(relay, state);

}

void loop()
{
  Dcc.process();
  if ( got_packet ) {
    detachInterrupt(0);
    if (state) {
      for (int i = pos_straight; i <= pos_turn; i++) {
        servo_pulse(servo, i);
      }
      digitalWrite(LED, HIGH);
      digitalWrite(relay, HIGH);
      state = false;
    } else {
      for (int i = pos_turn; i >= pos_straight; i--) {
        servo_pulse(servo, i);
      }
      digitalWrite(LED, LOW);
      digitalWrite(relay, LOW);
      state = true;
    }
    got_packet = false;
    Dcc.pin(0,2,1);
    Dcc.init( MAN_ID_DIY, 10, CV29_ACCESSORY_DECODER | CV29_OUTPUT_ADDRESS_MODE, 0 );
  }

}
reduzent commented 7 months ago

closed

kiwi64ajs commented 7 months ago

Hi Roman,

Sorry, I’ve been away on holiday, but I see you have figured out how to achieve what you needed to do.

We’d have to add pause()/resume() methods to do what you want, but your solution is probably ok.

I don’t think I’ve tried to drive servos from a Tiny85, but I’m a bit surprised you had issues as the AVRTinyCore has Tiny-specific support for servos, but obviously, it's still CPU load dependant…

Regards

Alex Shepherd

On 14/02/2024, at 12:30 AM, Roman Haefeli @.***> wrote:

It worked by detaching the interrupt while the servo is moving (see line 59):

include

NmraDcc Dcc ; DCC_MSG Packet ;

const int adresse = 25;

const int LED = 4; const int relay = 3; const int servo = 1; const int pos_straight = 93; const int pos_turn = 156;

bool state = LOW; bool got_packet = false;

void servo_pulse(int pin, int pos){ int pulse = map(pos,0, 180, 850, 2050); digitalWrite(pin, HIGH); delayMicroseconds(pulse); digitalWrite(pin, LOW); delay(21); }

void notifyDccAccTurnoutOutput( uint16_t Addr, uint8_t Direction, uint8_t OutputPower ) { switch(Addr){ case adresse: if(Direction < 1){ state = LOW; }else{ state = HIGH; } got_packet = true; EEPROM.write(adresse, state); break; default: break; } }

void setup() { Dcc.pin(0, 2, 1); Dcc.init( MAN_ID_DIY, 10, CV29_ACCESSORY_DECODER | CV29_OUTPUT_ADDRESS_MODE, 0 ); pinMode(LED, OUTPUT); pinMode(relay, OUTPUT); pinMode(servo, OUTPUT); state = EEPROM.read(adresse); digitalWrite(LED, state); digitalWrite(relay, state);

}

void loop() { Dcc.process(); if ( got_packet ) { detachInterrupt(0); if (state) { for (int i = pos_straight; i <= pos_turn; i++) { servo_pulse(servo, i); } digitalWrite(LED, HIGH); digitalWrite(relay, HIGH); state = false; } else { for (int i = pos_turn; i >= pos_straight; i--) { servo_pulse(servo, i); } digitalWrite(LED, LOW); digitalWrite(relay, LOW); state = true; } got_packet = false; Dcc.pin(0,2,1); Dcc.init( MAN_ID_DIY, 10, CV29_ACCESSORY_DECODER | CV29_OUTPUT_ADDRESS_MODE, 0 ); }

} `` — Reply to this email directly, view it on GitHub https://github.com/mrrwa/NmraDcc/issues/77#issuecomment-1941286138, or unsubscribe https://github.com/notifications/unsubscribe-auth/AB5Y53ID3PTTGBUTJDLG3IDYTNFEDAVCNFSM6AAAAABDDBHLWKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTSNBRGI4DMMJTHA. You are receiving this because you are subscribed to this thread.