Show Posts
Pages: [1] 2
1  Using Arduino / Programming Questions / Re: How adding delay to a Time type variable on: April 03, 2013, 02:39:42 am
No not that Im aware of, unless you can employ a manual reset of the Arduino, but im not even sure if that will reset the millis function. Of course, the maximum number is known, so if the second number is smaller than the first, you could still calculate the delay. Or take care of it if youre running out of millis().

But it will eventually rollover yes. 50 days is not enough for your intentions?

EDIT: Its 49 days smiley. But look here, I think youre answer might be in it:

http://www.cmiyc.com/blog/2012/07/16/arduino-how-do-you-reset-millis/

Important question is whether you really need a 'date' type of time keeping, or a general delay which can be kept with millis(). So, simply time passed, or the actual day/month etc as well?
2  Using Arduino / Programming Questions / Re: How adding delay to a Time type variable on: April 03, 2013, 02:29:38 am
No clue exactly what you want to do, but couldnt you time a delay by recording and comparing to instances of millis()?

EDIT: I see youre still editing the post. Ill be back in a  minute than
3  Using Arduino / Programming Questions / Re: How to generically pass parameters to a function. General C++ question on: April 03, 2013, 02:26:59 am
A macro is simply a text-replacement mechanism, so yes, it can take any type as argument.

Ok thanks....Im now at a level that I can program virtually anything I want on Arduino, but it is things like this I still have to read up about. My JAVA-only background doesnt help.... smiley-wink
4  Using Arduino / Programming Questions / Re: How to generically pass parameters to a function. General C++ question on: April 03, 2013, 02:15:02 am
Code:
#define MY_LCD_PRINT(x) do {if (condition) lcd.print (x);  } while (0)


This seems a lot like what OP already gave, so I guess the difference is is that this macro can take any argument? (string byte int)
5  Using Arduino / Programming Questions / Re: How to generically pass parameters to a function. General C++ question on: April 03, 2013, 02:00:46 am
Not familiar with the LCD screen, but as far as my (limited) knowledge of Arduino programming goes, its rather impossible to define an argument for a method that can take multiple or all forms. I would suggest to either store the to-be-printed material in global variables, and handle the different formats with different methods, or use an identifier in the argument to tell the method what kind of data is being printed. Perhaps with a switch case or something.

Code:
int a
string b
byte c

int identifier

void print(identifier){
   switch(identifier)
     case 1:  print a
     case 2:  print b
     case 3:  print c

}

I have no clue exactly what the extend of the expected data formats is, but this is a general solution which should be easily implementable

EDIT: perhaps you can set up 'condition' as to take care of selecting the correct method to handle the data. Some actual code would be useful, primarily the part which fills/selects the data for the final printing. How do you decide which variables are printed? The casting question I cannot answer, but would be useful if the method would simply print what you feed it...
6  Using Arduino / Programming Questions / Re: WDT+switch case error; Strangest behavior ever on: March 27, 2013, 02:59:05 am
Thanks for the reply and the testing! If indeed the problem is somewhere deep in the compiler creating the machine code, I guess there is nothing left to do but work with other solutions and perhaps notify the Arduino developers of this problem. Too bad it turned out that way.

I will come back as soon as possible with the testing results of the WDT lib. I will try to use the exact same setup as described, but simply switch the register flipping with the lib function calls and see if that works.
7  Using Arduino / Programming Questions / Re: Arduino reset issue on: March 26, 2013, 01:14:39 pm
Probably the way you have it now, but swap the battery for a better pack. Like mentioned, servo's have current demands which probably cannot be met by your current 9v battery. If im correct, Ni based batteries are good for bursts of current, which is why they are used in cameras for example.

Point is, each battery has a different chemistry and capacity, which in turn will feed specific needs. For example, Im working on a sensor node which measures periodically, possibly for a number of years on one battery. Average current is very low, so I use an alkaline battery, which are known for flat discharge curves and self-discharge just a few percent per year, whereas for example Li based batteries will self-discharge up to 10% a month! (but can meet higher current demands again!)

In short, map your current needs, read up on battery types and find the right kind for your project. Ill see if I can find some links in my bookmarks...

good luck!
8  Using Arduino / Programming Questions / Re: WDT+switch case error; Strangest behavior ever on: March 26, 2013, 12:36:11 pm
I suggest you start by replacing your DIY implementation with the Arduino standard function, and see if that works correctly.

If it does, then compare the Arduino implementation against your one and see if you can spot your mistake.

The line "...and see if you can spot your mistake." could sound like you know what might or is the problem? If so, could you elaborate?

As for your first point. I wouldnt have any problems switching to higher level implementations. The thing is, there shouldnt theoretically be anything wrong with it, im just following the datasheets. Many people have done it like I did (WDT part at least) and it also works for me...UNLESS its in that dreaded switch case. Besides, the libraries, again in theory because I didnt go through the WDT lib yet, SHOULD at its core do the exact same thing im doing.

If there were grave mistakes, the problem should occur more. Right now, the behavior is highly unpredictable....it might be some deeper lying error in the Arduino architecture for all I know....thats what Im trying to figure out. So, do you see, at the moment, any reason why my method doesnt work and behaves as described??

In my free time, Ill get to implementing the wdt lib and check back here.

Quote
I don't have an FIO available to test your code but I see no reason it would work any better for me than it does for you. Since your code accesses the hardware directly it's not inherently portable and I don't think that tests on a different Arduino variant will prove anything useful.

If you have an UNO around, it has the same ATMEGA chip (and thus watchdog version), maybe try that? Thats why you asked for the code right? Even though I expect the code to not work with your setup as well, what if it does? At least it will be a clue as to what is causing the problem. Maybe my hardware is busted afterall, even though I tested it on four FIO's, including out-of-the-box ones.

In short, Ill come back with results of the WDT lib, but in the meantime, if you or someone else could please comment on, or test my implementation, that would be great!

Thanks in advance!

9  Using Arduino / Programming Questions / Re: WDT+switch case error; Strangest behavior ever on: March 26, 2013, 10:55:44 am
Why aren't you using wdt_enable()?

No real reason. I assume btw you are talking about the given library function, not my own function for it? When I got into making the FIO as energy-efficient as possible, I learned a lot about the deeper coding, behind the methods. I looked a lot into the 328P datasheet, which is very extensive, and some very nice project examples utilizing all possible power saving options. I think I implemented all but the wasteful standard voltage regulator on the FIO.

Anyway, this was the way I learned it myself, and it helps to understand what is actually being done and why. I also think that it should work as expected, given that I am pretty sure im coding things as supposed to be coded. Running into problems isnt a problem itself and can be fun and challenging, but this kind of totally unexpected/unpredictable behavior is annoying.

So while we are on the topic, did you have any luck putting it on a FIO and running the code?
10  Using Arduino / Programming Questions / Re: Problem with file "pitches.h" in tutorial sketch "Melody". on: March 26, 2013, 07:54:37 am
As far as I know, the line should be:

Code:
#include <pitches.h>

without any """""" 's Or am I misreading the problem?

EDIT: Btw might be an issue indeed that the 'library' only has an .h file. All libraries Ive worked with consisted of a map with at least .h and .cpp file....
11  Using Arduino / Programming Questions / Re: WDT+switch case error; Strangest behavior ever on: March 26, 2013, 03:03:38 am
Sick of it all, I replaced the entire switch case in the give program with four separate voids to set each timer setting, per:

Code:
  void WDTset1(){
        WDTCSR &= ~_BV(WDIE);                         
        MCUSR  &= ~(1<<WDRF);                         
        WDTCSR |=  (1<<WDCE) | (1<<WDE);               
        WDTCSR  =   B00000110;                             //Slightly different way of doing it, though no difference
  }
  void WDTset2(){
        WDTCSR &= ~_BV(WDIE);                         
        MCUSR  &= ~(1<<WDRF);                         
        WDTCSR |=  (1<<WDCE) | (1<<WDE);               
        WDTCSR  =   B00000111;
  }
  void WDTset4(){
        WDTCSR &= ~_BV(WDIE);                       
        MCUSR  &= ~(1<<WDRF);                       
        WDTCSR |=  (1<<WDCE) | (1<<WDE);   
        WDTCSR  =   B00100000;
  }
  void WDTset8(){
        WDTCSR &= ~_BV(WDIE);                         
        MCUSR  &= ~(1<<WDRF);                         
        WDTCSR |=  (1<<WDCE) | (1<<WDE);               
        WDTCSR  =   B00100001;
  } 
 
(Note that I changed the method of coding the register settings for the WDP bits. But dont worry, I also already did that earlier on and did NOT influence the errors.)

And then of course replaced the function calls in the main program.  This solved the problem. I can pick any combination for the 2 calls and it works.

So for now, the problem must indeed be sought with the case switch itself it seems.

Coming half hour I will try to break the program again with delays and Serialprints....wish me luck  smiley-sweat

EDIT: No luck in breaking the separate methods version. Going back to the switch-case version, the following way of coding the ConfigureWDT(int seconds) method works:

Code:
void configureWDT(int seconds){
 
   int testInt = seconds;
 
  switch (testInt) {
    case 1:
    Serial.println("setting 1 second");
    Serial.println(WDTCSR);
        WDTCSR &= ~_BV(WDIE);                         
        MCUSR  &= ~(1<<WDRF);                         
        WDTCSR |=  (1<<WDCE) | (1<<WDE);             
        WDTCSR  =   B00000110;
        Serial.println(WDTCSR);
      break;
    case 2:
    Serial.println("setting 2 second");
    Serial.println(WDTCSR);
        WDTCSR &= ~_BV(WDIE);                         
        MCUSR  &= ~(1<<WDRF);                         
        WDTCSR |=  (1<<WDCE) | (1<<WDE);               
        WDTCSR  =   B00000111;
        Serial.println(WDTCSR);
       break;
    case 4:
    Serial.println("setting 4 second");
    Serial.println(WDTCSR);
        WDTCSR &= ~_BV(WDIE);                       
        MCUSR  &= ~(1<<WDRF);                       
        WDTCSR |=  (1<<WDCE) | (1<<WDE);   
        WDTCSR  =   B00100000;
        Serial.println(WDTCSR);
      break;
    case 8:
    Serial.println("setting 8 second");
    Serial.println(WDTCSR);
        WDTCSR &= ~_BV(WDIE);                         
        MCUSR  &= ~(1<<WDRF);                         
        WDTCSR |=  (1<<WDCE) | (1<<WDE);               
        WDTCSR  =   B00100001;
        Serial.println(WDTCSR);
      break; 
    default:
    ;
   
       
  }
 
  }

The difference being here that the default case is emptied. Substituting the case 8 code in the default case, which I would like to have, breaks it again.
12  Using Arduino / Programming Questions / Re: WDT+switch case error; Strangest behavior ever on: March 26, 2013, 02:10:24 am
Just to be absolutely sure, I uploaded above code on a pristine, out-of-the-box Arduino FIO, to rule out hardware failure, but the problem still persisted.

Code as given runs, when changing case 2 it goes into reset-loop.....pfff

And sorry about not posting code sooner. I somehow didnt see the first message and was doing the code while the second was posted...Guess I was just hoping I made some dumb typo in the code somewhere...

EDIT: Ok I guess Ill edit this post with more results.

The code above: I upload it with 'case 2' as it should be, which results in the crash. Now, like I said in my opening post(s), adding a small delay to 'case 4' (the case being called upon), removes the problem. I just found out that the same delay put in 'case 2' also prevents the reset-loop.
13  Using Arduino / Programming Questions / Re: WDT+switch case error; Strangest behavior ever on: March 25, 2013, 11:34:33 am
Sorry I missed your earlier message. Here is a VERY VERY barebones version of my complete program (No XBEE/I2C/sensors/anything). Basically I stripped everything but what I mentioned in my pseudo code:
Code:

#include <avr/interrupt.h>        // interrupt routines
#include <avr/wdt.h>              // watchdogtimer functionality

const int measuring_interval = 1;              //Defines the number of minutes between each measurement
const int WDTInterval        = 4 ;             //Preferred number of seconds between WD interrupts
int WDTInterruptsNeeded;                       //The number of calibrated interrupts to achieve the approximate minutes interval of 'measuring_interval'
                                               //Established through calibrateAndSetWDT()
                                               
const int WDTCalInterval = 4;                  //Number of seconds between WDT interrupts during calibration of the WDT
const int WDTCalCycles = 2;                    //The number of WDT interrupts (during calibration), to base calibration ratios upon


volatile int WDTCounter;                       //Keep track of number of WDT interrupts. Reset after measurement
volatile int WDTCalCount;                      //Keep track of larger number of interrupts to signal WDT recalibration
volatile boolean calibrating;                  //Flag which tells the WDT interrupt whether calibration is being done


int caliInt;                                   //Stores number of interrupts while calibrating WDT
int calibrateTimeHours;                        //Time in hours between calibrating the WDT timer
int calTimeHoursToInterrupt;                   //Number of interrupts to reach calibrateTimeHours



  void setup(){ 
   
    WDTCounter           = 0;                          //Initialize the interrupt counter for measurements
    WDTCalCount          = 0;                          //Initialize WDT recalibration interrupt counter
    caliInt              = 0;                          //Initialize WDT calibration interrupt counter
   
    Serial.begin(9600);                                                //Enable serial communication at 9600 BAUD rate

   

    calibrateAndSetWDT();                         //Initial calibration and configuration of the WDT


   
  }

  void loop(){ }


  void calibrateAndSetWDT(){
   
    delay(10000);
    configureWDT(WDTCalInterval);
    delay(10000);
   
    configureWDT(WDTInterval);
    Serial.println(WDTCSR);
    delay(100);
  }
 
 
 
 void configureWDT(int seconds){
 
   int testInt = seconds;
 
  switch (testInt) {
    case 1:
        WDTCSR &= ~_BV(WDIE);                         //Disable WDT before setting to proper value again
        MCUSR  &= ~(1<<WDRF);                         //Clear the WDT reset flag from the MCU register
        WDTCSR |=  (1<<WDCE) | (1<<WDE);              //Prepare register for changing WDT prescaler
        WDTCSR  =   1<<WDP1  |  1<<WDP2;
      break;
    case 2:
        WDTCSR &= ~_BV(WDIE);                         
        MCUSR  &= ~(1<<WDRF);                         
        WDTCSR |=  (1<<WDCE) | (1<<WDE);               
        WDTCSR  =   1<<WDP3; //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<CHANGE HIS TO: 1<<WDP0 | 1<<WDP1  |  1<<WDP2;  (normal setting for 2 seconds)
       break;
    case 4:
        WDTCSR &= ~_BV(WDIE);                       
        MCUSR  &= ~(1<<WDRF);                       
        WDTCSR |=  (1<<WDCE) | (1<<WDE);   
        WDTCSR  =   1<<WDP3; 

      break;
    case 8:

        WDTCSR &= ~_BV(WDIE);                         
        MCUSR  &= ~(1<<WDRF);                         
        WDTCSR |=  (1<<WDCE) | (1<<WDE);               
        WDTCSR  =   1<<WDP3  |  1<<WDP0;

      break; 
    default:
        WDTCSR &= ~_BV(WDIE);                         
        MCUSR  &= ~(1<<WDRF);                         
        WDTCSR |=  (1<<WDCE) | (1<<WDE);               
        WDTCSR  =   1<<WDP3  |  1<<WDP0;
        Serial.println("error");
  }
  delay(100);
  }
 
 
 

  ISR(WDT_vect){
   
    if(calibrating==false){
     
      WDTCounter++;
      WDTCalCount++;
    }else{
     
      caliInt++;
     
      if(caliInt>=WDTCalCycles){
        calibrating=false;
      }
     
    }
   
  }   
 
 
 
  void enableWDT(){
        WDTCSR |= _BV(WDIE);             
 
  }   

                                                 
  void disableWDT(){                                     
        WDTCSR &= ~_BV(WDIE);                       
        MCUSR  &= ~(1<<WDRF);                       
  }   



When I upload this, this sucker works (ARDUINO FIO). When I change case 2 as described....resetloop...goodluck and Im very curious!
14  Using Arduino / Programming Questions / Re: WDT+switch case error; Strangest behavior ever on: March 25, 2013, 10:55:08 am
Ok, so here is another perfect example of the problem. Remember the basic program from above (even more stripped down, minor changes):
Code:
const int WDTInterval     = 4; //WDT timer settings in seconds during normal operation
const int WDTCalInterval = 4; //WDT timer settings in seconds during calibration

setup(){
  ...
  calibrateWDT();
}

calibrateWDT(){
  delay(10000);                                    //Added this delay
  setWDT( WDTCalInterval );
  delay(10000);
  ....                                                 //Crash has already occurred here
 setWDT( WDTInterval );
}

setWDT(int seconds){
   ...switch statement....                  //Sets the WDT settings according to the value given. See above to notice that both ints are set to 4;
}

If I write the switch statement as follows, the program works as intended. Note that case 4 is the only case being used here:
Code:
setWDT( int seconds){
  int testInt = seconds;
 
  switch (testInt) {
    case 1:
        WDTCSR &= ~_BV(WDIE);                         //Disable WDT before setting to proper value again
        MCUSR  &= ~(1<<WDRF);                         //Clear the WDT reset flag from the MCU register
        WDTCSR |=  (1<<WDCE) | (1<<WDE);              //Prepare register for changing WDT prescaler
        WDTCSR  =   1<<WDP1  |  1<<WDP2;
      break;
    case 2:
        WDTCSR &= ~_BV(WDIE);                         
        MCUSR  &= ~(1<<WDRF);                         
        WDTCSR |=  (1<<WDCE) | (1<<WDE);               
        WDTCSR  =   1<<WDP3;                                         //THIS CASE IS HERE CONFIGURED WRONG, IT ACTUALLY CONFIGURES THE WDT FOR FOUR SECONDS
       break;
    case 4:
        WDTCSR &= ~_BV(WDIE);                       
        MCUSR  &= ~(1<<WDRF);                       
        WDTCSR |=  (1<<WDCE) | (1<<WDE);   
        WDTCSR  =   1<<WDP3; 

      break;
    case 8:

        WDTCSR &= ~_BV(WDIE);                         
        MCUSR  &= ~(1<<WDRF);                         
        WDTCSR |=  (1<<WDCE) | (1<<WDE);               
        WDTCSR  =   1<<WDP3  |  1<<WDP0;

      break; 
    default:
        WDTCSR &= ~_BV(WDIE);                         
        MCUSR  &= ~(1<<WDRF);                         
        WDTCSR |=  (1<<WDCE) | (1<<WDE);               
        WDTCSR  =   1<<WDP3  |  1<<WDP0;
        Serial.println("error");
  }

  delay(100);
  }

IF, however, I fix case 2 so that it configures the WDT for 2 seconds (as it should), like this:

Code:
setWDT( int seconds){
  int testInt = seconds;
 
  switch (testInt) {
    case 1:
        WDTCSR &= ~_BV(WDIE);                         //Disable WDT before setting to proper value again
        MCUSR  &= ~(1<<WDRF);                         //Clear the WDT reset flag from the MCU register
        WDTCSR |=  (1<<WDCE) | (1<<WDE);              //Prepare register for changing WDT prescaler
        WDTCSR  =   1<<WDP1  |  1<<WDP2;
      break;
    case 2:
        WDTCSR &= ~_BV(WDIE);                         
        MCUSR  &= ~(1<<WDRF);                         
        WDTCSR |=  (1<<WDCE) | (1<<WDE);               
        WDTCSR  =   1<<WDP1  |  1<<WDP2 | 1<<WDP0;               //<<<<<<<<<<<<<<<<<<<<The ONLY thing that has changed!!!
       break;
    case 4:
        WDTCSR &= ~_BV(WDIE);                       
        MCUSR  &= ~(1<<WDRF);                       
        WDTCSR |=  (1<<WDCE) | (1<<WDE);   
        WDTCSR  =   1<<WDP3; 

      break;
    case 8:

        WDTCSR &= ~_BV(WDIE);                         
        MCUSR  &= ~(1<<WDRF);                         
        WDTCSR |=  (1<<WDCE) | (1<<WDE);               
        WDTCSR  =   1<<WDP3  |  1<<WDP0;

      break; 
    default:
        WDTCSR &= ~_BV(WDIE);                         
        MCUSR  &= ~(1<<WDRF);                         
        WDTCSR |=  (1<<WDCE) | (1<<WDE);               
        WDTCSR  =   1<<WDP3  |  1<<WDP0;
        Serial.println("error");
  }

  delay(100);
  }

The program breaks again, going into reset right after the first 10 second delay. So once again, changing what seems to be irrelevant pieces of code, in a switch statement case which is NOT USED OR CALLED, EVER, affects the behavior or outcome of another case statement........I cannot describe this any more clearer...quickly loosing sanity
15  Using Arduino / Programming Questions / Re: WDT+switch case error; Strangest behavior ever on: March 25, 2013, 09:40:47 am
My experiences:
Now, most importantly, my experiences and observations of the behavior:

STEP A-The problems started when I used 8 secs and 4 secs for the settings. Using 8 for calibration and 4 for normal operation (testing purposes), the WDE bit got set, but the program would still continue on. When using 4 sec for the calibration: immediate crash on first call to setWDT();. Same when setting both calls to 4 secs. (This last one turned out very interesting later on)

STEP B-Noticing the 4 second setting seemed to give the problems I decided to change the WDP bit settings in the statements of the switch case. (I did try more things but to no avail)
These are some of the results:
    -Making all statements/cases program the bits to 2 seconds  :  Works every time, no matter what x or y (see code)
    -Making all statements/cases program the bits to 8 seconds  :  Works every time, no matter what x or y (see code)
    -Making all statements/cases program the bits to 4 seconds  :  Works every time, no matter what x or y (see code)
 
STEP C-Building on the above results, I decided to return ONLY THE STATEMENT FOR 8 SECONDS to its original code (WDP3 and WDP0 =1). So all other statements still set the WDT to 4 seconds. Results as follows:
   case:                         (a)(b)(c)(d)
   calibrationInterval(y)   = 8  8  4  4
   normalInterval(x)        = 8  4  8  4
   result                       = V  X  X  X

Only case (a) performed as intended. Case (b) is most interesting. During this test, the WDE bit apparently got set wrongfully (seen by printing WDTCSR a number of times throughout). The program would however continue operating as intended. This changed when I added a print of WDTCSR in the switch statement for 4 seconds, after setting the bits. This resulted in an immediate crash.
Cases (c) and (d) also crashed immediately.

STEP D-Not knowing the exact reasons yet, I started adding delays() in the code at certain points. This was mainly because following the execution without delays is impossible. Few second delays made it possible to see whether the program would hang before or after a block of code. Normally, printlns would also do that job, but as they return immediately, they arent reliable at ms timing.
  -The first major delay was the 10 second delay after setting the WDT for the first time for calibration (see code). It didnt solve the problem, but helped diagnosing cases b,c and d from step C.
 - Next I added a small delay(100) IN the switch statement case for setting it to four seconds, AFTER setting the bits. HOORAY, for some reason this worked, solving the wrongful setting of the WDE bit.
 -Following the above result, I thought it might be useful to pull the delay out of the four second case, and put it after the switch statement, so that all cases might benefit....You guessed it, the problem returned.... *sigh*

STEP E -I tried using an if/else cascade instead of a switch case.....didnt help...

CONCLUSIONS(apparent or otherwise)
-From step A: The problem seems to be caused by configuring the WDT to four second intervals.
-From step B:  The conclusion is that the problem cannot be caused by the WDT code itself it seems here. Every possibility seems to work. This conflicts with the conclusion from A, which seems to point to the WDT code of four seconds. Instead, the suspect now appears to be the SWITCH CASE ITSELF!
-From step C: Conclusion seems to be that somehow, SOMEHOW, the code in the last case block (8 seconds, default not included) is interfering with the 4 second block!! Extremely weird is case (d) from step C, which (setting wise) worked in step B (4 secs for both x and y). So by changing the code in the block for 8 seconds, a case WHICH IS NOT CALLED, the program broke.
-From step D: Adding a small delay right after setting the bits for four second interval seemed to help. What is important is to realize that not only should the delay be directly sequential in time after setting the bits, IT MUST be written directly after setting the bits IN THE CODE. This seems evident from the fact that adding the small delay after the entire switch statement, as well as the large delay following setWDT() in calibrateWDT() didnt help at all.
-From step E: From this it seems the problem might not even be the SWITCH CASE ITSELF, but more the flow of the program following the setting of the WDT.



                                                            SO...LIKE....WHAT IS GOING ON HERE!!!!!!!!????????
Please let there be someone who can point me in the right direction. I simply can NOT find ANY reasonable explanation for this behavior. Especially the conclusion from step C above is making me doubt my insanity. Could it be that the code is written to memory in a wrong way? I dont know....but I hope some of you will. Im even hopeful at the moment I made a GIANT GIANT N00b error....I can live with the shame, not with this ridiculous behavior....
Pages: [1] 2