Help needed

Arduino projects on the go
MaxZ
Posts: 330
Joined: 31 Jan 2019, 11:48
Location: Boskoop, Netherlands

Help needed

Post by MaxZ »

Hi guys,

I am writing some code for the Digispark. Actually it is a modified version of Phil's digispark_3ch_propo_sc_v2, intended for conversion of a WebraPicco Tx , see viewtopic.php?f=25&t=1168
Actually it was working very well, just as I intended it to be. Then I decided I wanted to add a buzzer and to have it sound two pips just before it enters the loop. I have been spending about 4 hours now to get it to work, without success. All it wants to do is show a single short flash (I have connected a led instead of the beeper for now, I am doing this in the living room and don't want to drive my wife nuts.....).

It's probably something simple, but I cannot work out what I am doing wrong, hence this cry for help.
Here is that part of the code:
Schermafbeelding 2021-01-04 om 16.17.23.png
Schermafbeelding 2021-01-04 om 16.17.23.png (27.9 KiB) Viewed 2020 times
Cheers,
Max.

Edit: and yes, buzzer is defined as OUTPUT, on P2. And the Digispark has been de-fluffed.
User avatar
Mike_K
Posts: 669
Joined: 16 Feb 2018, 06:35
Location: Hertfordshire

Re: Help needed

Post by Mike_K »

Hi Max

You'll probably need to post the complete modified sketch (as a .ino) to get help. I've only briefly looked at Phil's Digispark encoder, mainly as I'm an ATtiny85 fan, but I thought the buzzer was on P1?? Or have you done away with the elevator pot?

Cheers Mike
MaxZ
Posts: 330
Joined: 31 Jan 2019, 11:48
Location: Boskoop, Netherlands

Re: Help needed

Post by MaxZ »

Phil_G wrote: 04 Jan 2021, 18:08 Your modification prevents any ppm generation for four seconds Max
You need to do it on a frame-gated basis which is how the inactivity warning works.
It's further complicated by the multiplexed buzzer and button input...
I have posted 2, 3 and 4 channel versions, including rudder/throttle... all work perfectly
Cheers
Phil
Hi Mike,

I revised the entire sketch for my specific purpose, one of the things I did was to split the combined buzzer and s/c button, P1 is now the button and P2 the buzzer.
I did test the whole thing without a buzzer installed (or its led replacement), and all was well. I did not test the inactivity timer, so I don't know if that works or not. But that part is exactly the same as Phil's original (yes Phil, I will add a source reference.....), except maybe for the channel assignment.

This is my code. I commented the part about the reversing check out since I did revise that to add an audible signal (the Webra has a non-centering lever for rudder, so you could be reversing inadvertently), and I wanted to be sure that it is not the culprit of my current trauma..

Cheers,
Max.

Code: Select all

// Simple 2 channel propo encoder for DigiSpark ATTiny85, for Rudder&Throttle channels in a converted Webra Picco transmitter 
// A2=throttle, A3=rudder
// Calibrate button is push-to-make in this version, alternate use is to set/reset throttle timeout.
// REMOVE THE SCHOTTKY DIODES ON D3 & D4, the pullup on P3 and either LED or LED Resistor (they flick off easily with a watchmakers screwdriver)
// Connections:
// Pots wired between ground and regulated 5v from the Digispark
// A2(P4)=throttle wiper, A3(P3)=rudder wiper, P2=buzzer, P1=calibrate button, P0=PPM out.
// Rudder pot calibration, hold button in, switch on, still holding button move pot to extremes, hold button.
// Centralise spot including throttle, release button.
// Servo reversing by moving Rudder pot to any end on power up (saved to flash).
// Adapted for Gerard's Webra Picco Tx conversion. Positive mark pulses ppm for Frsky DHT module.


static int ppm = 0, button = 1, buzzer = 2;       // D1 is calibrate & timeout set/reset button
int ch, ppmPulse = 300, neutralPulse, raw, calibrated = 1, RudHi = 0, RudLo = 1023, RudMid = 512;
int chtemp, ch0val, channel[] = {0, 0, 0, 0, 0, 0, 8000}; // last is used for sync
byte tlock = 1, RudReverse = 0;
unsigned int inact = 0;

#define INACTFRAMES 30000  // inactivity alarm, 30,000 x 20ms frames is 10 minutes
#define full_n -500 // full negative deflection of sticks
#define full_p +500 // full positive deflection of sticks

#include <EEPROM.h>

void setup()  {
  OSCCAL = 96; // Digisparks can vary in speed, make larger if timings too long.
  noInterrupts();
  pinMode(button, INPUT_PULLUP);
  pinMode(buzzer, OUTPUT);
  pinMode(ppm, OUTPUT);




  while (digitalRead(button) == 0) {  // calibrate sticks by holding button, (make-contact to GND)
    calibrated = 0;
      raw = analogRead(3); // read rudder (A3) input
      if (raw > RudHi) RudHi = raw;
      if (raw < RudLo) RudLo = raw;
      RudMid = raw;  // save neutral of rudder stick as button is released
  } // calibrate button released

  if (calibrated == 0) {
  // save the calibration values
      EEPROMWriteInt(0, RudLo);     // eeprom location  0 (decimal)
      EEPROMWriteInt(2, RudMid);    // eeprom location  2 (decimal)
      EEPROMWriteInt(4, RudHi);     // eeprom location  4 (decimal)
    calibrated = 1;                 // flag as calibrated
  }

  // load the saved calibration values
    RudLo = EEPROMReadInt(0);       // eeprom location  0  (decimal)
    RudMid = EEPROMReadInt(2);      // eeprom location  2  (decimal)
    RudHi = EEPROMReadInt(4);       // eeprom location  4  (decimal)
    
  // load saved rudder channel reversal from eeprom location 6 (decimal), only last bit is considered
    RudReverse = EEPROM.read(6) & 1; // (bitwise AND with B00000001: set bits 1-7 to 0, leave bit0 as is)  

  // check for rudder reversing, stick over on power-up.
/*    channel[1] = map(analogRead(3), RudLo, RudHi, full_n, full_p);
    if (channel[1] > full_p - 50 || channel[1] < full_n + 50) {
      while (digitalRead (button) == 1) digitalWrite (buzzer,HIGH); // buzzer sounds to avoid inadvertent reversal,
      digitalWrite (buzzer,LOW);                      // continue by pressing button, switch Tx off to abort
      RudReverse ^= B0000001; // flip bit0 of byte 

      EEPROM.write(6, RudReverse);
    }
*/
  neutralPulse = 1500 - ppmPulse;
  
  for (int x = 0; x<2; x++) {
    delay (1000);
    digitalWrite (buzzer,HIGH);
    delay (1000);
    digitalWrite (buzzer,LOW);
  }

}
 
void loop() {

  raw = analogRead(2); // read throttle pot wiper to A2
  channel[0] = map (raw, 0, 1023, full_n, full_p);

  raw = analogRead(3); // read rudder pot wiper to A3
  if (raw > RudMid) channel[1] = map (raw, RudMid, RudHi, 0, full_p);
  else channel[1] = map (raw, RudLo, RudMid, full_n, 0);



    

  ch0val = channel[1];                  // used by inactivity timer
  
  channel[2] = 0;                       // channel 2 neutral
  channel[3] = 0;                       // channel 3 neutral
  channel[4] = 0;                       // channel 5 neutral
  channel[5] = 0;                       // channel 6 neutral
  channel[6] = 0;                       // 7th element is sync

  // soft throttle lock, throttle can't be opened until it has been closed first
  if (channel[0] < full_n + 50) tlock = 0;
  if (tlock) channel[0] = full_n;

  // rudder channel reversal
  if (RudReverse == 1) channel[1] = - channel[1];

  // frame formatting
  for (int ch = 0; ch < 6; ch++) {
    channel[ch] = constrain(channel[ch], full_n, full_p);   
    channel[ch] += neutralPulse;
    channel[6] += channel[ch];
  }

  channel[6] = 15000 - channel[6];  // CHECK, SHOULD BE 20000 - 7*PPMPULSE - CHANNEL [6]

  //send ppm frame, last channel holds sync value
  for (int ch = 0; ch < 7; ch++) {
    digitalWrite(ppm, HIGH);
    delayMicroseconds(ppmPulse);
    digitalWrite(ppm, LOW);
    delayMicroseconds(channel[ch]);
  }

  // inactivity timer. 10 mins is approx 30000 frames.
  if (ch0val < -100 || ch0val > 100) { // moving rudder resets timer start
    inact = 0;
  }
  if (++inact > INACTFRAMES) {
    if (bitRead(inact, 3) == 1 && bitRead(inact, 5) == 1) { 
      digitalWrite(buzzer, HIGH);
    }
    else {
      digitalWrite(buzzer, LOW);
      if (digitalRead(button) == 0) inact = 0; // Push button to stop alarm
    }
  }
}

// This function will write a 2 byte integer to the eeprom at the specified address and address + 1
void EEPROMWriteInt(int p_address, int p_value)
{
  byte lowByte = p_value % 256;
  byte highByte = p_value / 256;
  EEPROM.write(p_address, lowByte);
  EEPROM.write(p_address + 1, highByte);
}

//This function will read a 2 byte integer from the eeprom at the specified address and address + 1
unsigned int EEPROMReadInt(int p_address)
{
  byte lowByte = EEPROM.read(p_address);
  byte highByte = EEPROM.read(p_address + 1);
  return lowByte + highByte * 256;
}
MaxZ
Posts: 330
Joined: 31 Jan 2019, 11:48
Location: Boskoop, Netherlands

Re: Help needed

Post by MaxZ »

Phil_G wrote: 04 Jan 2021, 18:08 Your modification prevents any ppm generation for four seconds Max
You need to do it on a frame-gated basis which is how the inactivity warning works.
It's further complicated by the multiplexed buzzer and button input...
Cheers
Phil
I know Phil, but it happens during Setup, not in the loop. And the millisecs shown are just for testing, I will change them to more appropriate values later.
Martin
Posts: 744
Joined: 16 Feb 2018, 14:11
Location: Warwickshire

Re: Help needed

Post by Martin »

You've got a noInterrupts(); statement at line 28.

That stops all interrupts, including the timer0 interrupt which the Arduino uses to keep track of milliseconds. So delay() won't work.

In general, you don't want to leave interrupts disabled for very long in a normal Arduino sketch. I don't see a good reason for disabling them in your setup() routine.
MaxZ
Posts: 330
Joined: 31 Jan 2019, 11:48
Location: Boskoop, Netherlands

Re: Help needed

Post by MaxZ »

Ah, thanks Martin. But it does not seem to affect the delayMicroseconds() used to construct the ppm stream.
Martin
Posts: 744
Joined: 16 Feb 2018, 14:11
Location: Warwickshire

Re: Help needed

Post by Martin »

Yeah delayMicroseconds() just uses the timer0 hardware. There is a hardware register, TCNT0, that is set to count once every 4 microseconds. It counts up to 255 and then resets back to zero. It does this regardless of the interrupts. delayMicroseconds() just sits looking at this changing register to keep track of time, so it works even when interrupts are switched off. Note that delayMicroseconds() and micros() only have a resolution of 4. If you write a loop to just Serial.println(micros()); you'll see that all the numbers printed are multiples of 4.

Technical details follow (most sane people will skip the rest of this).

Every time that timer/counter wraps round, and interrupts aren't turned off, then the milliseconds count is increased by at least one. Note that the timer wraps every 256 x 4 microseconds which is 1.024 milliseconds, so there is a fudge factor built in to millis() that keeps track of the fractional error and increases milliseconds by 2 instead of 1 roughly every 43 milliseconds: internally, it keeps track of the 'fractional parts' of a millisecond so it's not exactly every 43 counts that this happens. If you have a crystal and not a resonator (and don't have interrupts disabled), then it's very accurate long term, but it stutters/jitters roughly every 43 counts.

Note that it would be perfectly possible to set Timer0 to reset back to 0 at a count of 249 instead of 255 - and this would make millis() dead accurate without needing the fudge - but the Arduino also uses Timer0 for micros() and for driving some PWM pins - resetting it every 250 counts would make those functions more complex and the PWM would have lower resolution.

If you're not using Timer2 for anything else then you can use that to get more accurate timing of your pulses (one microsecond resolution instead of four). I posted a thread somewhere on here that shows you how to get accurateMicros() - it works just like micros() except that it has four times the resolution.

And by careful manipulation of the timers, you can get timings accurate to one-sixteenth of a microsecond. You'll need to read and understand the datasheet if you want to exploit the hardware fully.
MaxZ
Posts: 330
Joined: 31 Jan 2019, 11:48
Location: Boskoop, Netherlands

Re: Help needed

Post by MaxZ »

Thanks again Martin. I can't say I can follow everything you wrote (in the bit where sane people shouldn't go :mrgreen: ) but I appreciate your efforts nevertheless.
I've had enough for today, I will continue testing/developing tomorrow.

Cheers,
Max.
User avatar
Wayne_H
Posts: 809
Joined: 17 Feb 2018, 05:26
Location: Temora, NSW. Australia
Contact:

Re: Help needed

Post by Wayne_H »

Martin wrote: 04 Jan 2021, 21:16 ...Technical details follow (most sane people will skip the rest of this)....
I confess I too read it, ergo I'm not "most people" and/or not sane - in reality, it's probably both, but hey, we're happy :shock: :lol: :oops:
Cheers,

Wayne
Once a Retrobate, always a Retrobate............ ;)
User avatar
Mike_K
Posts: 669
Joined: 16 Feb 2018, 06:35
Location: Hertfordshire

Re: Help needed

Post by Mike_K »

Hi Martin

I was always under the impression that delayMicroseconds() used TCNT0 as well, until I had to read the source code for another issue. it doesn't use TCNT0, in fact, it doesn't use any timers at all and the pause is based on looping avrlib's delay_us() function. If you want to see this for yourself, look in wiring.c where you'll see the assembler calls, basically just loops of 1uS assembler delay. So its resolution is actually 1uS, not 4uS and the maximum value that works reliably is 16383. Your description is how micros() work.

If you want to prove it doesn't use interrupts or TCNT0 just try the following sketch which disables interrupts and TCNT0 and then flashes the built-in LED every second.

Cheers Mike
Attachments
delayMicroseconds.ino
(742 Bytes) Downloaded 110 times
Post Reply