Thursday, December 18, 2014

Real Time Controls with ATMEGA1284 using Interrupts

Few months ago, I had to create a real-time signal generator using a microcontroller unit (MCU) that would control the trajectory of a multi-kilowatt laser. As the process involved is for large-scale laser welding, it was requested that the system be highly reliable.
I was given an ATMEGA1284P with an external 16MHz quartz. (To see how to set up the external clock, click here)
The signal being generated was between 5-30Hz (which is quite slow), so I tried using time-based Sleep() instructions. This resulted in a terribly incorrect frequency.

The solution was to use interruptsInterrupts are used everywhere. In software, they can be used to handle errors that pop up during program execution. In electronics, a button press can generate an interrupt, leading to a function execution.

In our case, the interrupt is generated from the MCU itself. 
In short, the quartz --which is a crystal oscillator-- 'ticks' (hopefully) at a constant frequency. The MCU is able to count these ticks. At every certain number of ticks (which you assign), the MCU can call a function called an Interrupt Service Routine (ISR).

The benefit of this method is that as long as the quartz is reliable, the MCU will reliably run the ISR at every few instances in time.
The biggest drawback -- and something to watch out for -- is that the ISR must be very concise (a loop of any kind is a big no-no).

Example Problem

Say we want to generate a sine signal to an analog-out pin at a certain frequency.

We can use interrupts to output a certain value of the sine wave at a given time. Unfortunately, depending on the MCU (and true for ATMEGA1284P), calculating sin() within the ISR takes a long time -- long enough to cause delays in the signal. Since sine is a periodic function, it is much better to pre-compute the values with a given temporal resolution and call them from an array during the ISR. 


Initialization

First, we need to declare a few variables.

#define RESOLUTION 250

volatile unsigned int counterMaxValue;
const int clkspeed = 16000000; 
const int prescaler = 64;
volatile int frequency = 5; // Default freq. is 5Hz

// for pre-computed sine table.
int table_index = 0;  
double sin_table[RESOLUTION];
float increment;

const int outportB = 2; // PortB
const int outportC = 1; // PortC

counterMaxValue is the number of 'ticks' of the quartz at which the ISR should be called.
clkspeed is the frequency of our quartz, in Hz.
prescaler is the number at which to divide the number of 'ticks,' and it allows us to use a certain range of frequencies. Read more about prescaler here.

The Interrupt

Now, we must configure the hardware. We are using Timer 1 of the 1284P, in the Clear Timer on Compare (CTC) mode. When I was first learning about CTC, I used this resource.
void initInterrupt() {
  // INTERRUPT SET UP
  cli();//stop interrupts

  TCCR1A = 0;// set entire TCCR1A register to 0
  TCCR1B = 0;// same for TCCR1B

  /* 
   CTC mode activation:
   The counter value (TCNT1) is incremented from 0 to the value specified in OCR1A. 
   Once it hits that value, the interrupt is generated, and the interrupt service routine (ISR) is called.
   The below setting for TCCR1B enables this to happen. See the datasheet for more details.
   */
  TCCR1B |= (1 << WGM12)|(1 << CS11)|(1 << CS10); 

  // set compare match register for freq * n increments
  counterMaxValue = (unsigned int) (clkspeed/(frequeny*n*prescaler) - 1);
  OCR1A = counterMaxValue;//eg:(16*10^6)/(2000*64)-1 (must be <256 for 8 bit counter.); 124 for 2kHz

  //initialize counter value to 0
  TCNT1  = 0; 
  
  // enable timer compare interrupt
  TIMSK1 |= (1 << OCIE1A);

  //enable interrupts
  sei();
}

After this function is run, the interrupts will begin. But first, we must set up a pre-computed sine table; and above all, we must define the ISR.

The setup()

void setup()
{
  ...

  // Pre-compute sine table
  increment = 6.283185/(RESOLUTION-1);
  for (int index = 0; index < RESOLUTION; index++)
  {
    sin_table[index] = sin(x);
    x = x + increment;
  }

  initInterrupt();
}

The ISR


I was using Wiring to program the MCU, and there was an error when I tried to use any of the timers. The problem was solved by commenting out the definition set out by wiring.
// Had to comment out line 77-86 of ...\wiring-0100\wiring-0100\cores\AVR8Bit\WHardwareTimer.cpp
// in order to 're-define' TIMER1_COMPA_vect
ISR(TIMER1_COMPA_vect) {
  // NO LOOPS / WAITING / DELAYS IN THE ISR!!
   
  // Traverse through the table. This is much faster than computing sine every time! :)
  // Equivalent to: 
  // y = sin(x)*amplitude+1638+offset; // Based at 5V, up to 4V peak to peak; offset +/- 1V. 2047 - 409 = 1638; 5V - 1V to account for offset
  // x = x + increment;
  if (table_index > RESOLUTION-1) // loop back to beginning
  {  
    table_index = 0;
  }
  y = sin_table[table_index]*amplitude+1638+offset;
  table_index++;
  output = word(y);
  portWrite(outportB, int (lowByte(output)));
  portWrite(outportC, int (highByte(output))); 
}

Just a quick note on outputting the signal: I am using a 12-bit digital to analog converter (DAC). Two ports on the MCU are used, each outputting half of the word.
The DAC I used allowed two modes of output: unipolar (0V to 10V), or bipolar (-5V to 5V). I used the latter, which means that when I send (1111 1111 1111)2 to the DAC, the output value will be (+Vref)*(2047/2048).
In short, output value of '409' from the MCU corresponds to 1V output from the DAC.

Now everything is ready! An empty loop() suffices to run this on the MCU.

The loop()

void loop(){}

After uploading the program to your 1284P, it should start sending out sine signals in digital form to the DAC, which it can then transform to the analog signal.

Conclusion

Establishing accurate real time controls on a microcontroller requires us to use timer-based interrupts. There are many benefits to this method, including parallelization of tasks for the MCU.

In other words, the MCU can do other things, such as polling for user input in the main loop(), while ISR takes care of the timing-based tasks. This would not be possible using the sleep() function, which simply waits for a certain amount of time until moving on to the next instruction.

Lastly, the most important thing to consider when using timer-based interrupts is to keep the ISR as simple and short as possible!

Monday, December 15, 2014

[Solidworks] Tube Routing without Solidworks Premium


Without a doubt, Solidworks Routing package within the premium version of SW can be quite useful when creating electrical connections, pipes, or tubes. Unfortunately, a lot of us have to make-do without such handy tools. In this tutorial, I will cover how to create flexible tubings with just a few loft tools. The example model I am using is a minimal working example (MWE) so please excuse the crude design.

Problem

- Route a flexible tube between two connections that updates with the varying position of the end connections.
A flexible tube must be connected between the two highlighted profiles.

Solution


1. First, create a cross-sectional sketch of the tube. Save the part.

Cross-sectional sketch of the tube
2. Create a new assembly, and import the assembly with the original parts. This will enable the tube route to be automatically updated. (Do not import by parts!)
3. Import the sketch, and mate it to one end of the connection. Make sure it is fully defined.
Mate the sketch to one end of the tube connection.

4. Go into “Edit Component” mode for the tube, and click on any of the planes.


5. CTRL+DRAG the plane to create a new plane.


Define the plane to be coincident to the other end of the tube connection.


6. Make a sketch on this new plane of the tube cross section. Exit sketch.

7. While still in Edit Component mode, go to Features -> Lofted Boss/Base.
8. First we have to create the outer wall. So, designate the outer closed loop as the end geometries.
It will create the shortest-path connection, which ends up looking quite strange!



9. This can be easily fixed by clicking on Start/End Constraints, and setting them to be “Normal To Profile.” The orthogonal distance to profile can be edited here as well, but remember these values because we will need it soon.


10. We have to now cut out the inside of the tube. To do this, first make the sketches in loft visible. (Click on the + sign beside Loft, right click on the sketches and click “Show”)

11. Go to Features -> Lofted Cut. Now click on the inner closed loops.
Set the same Start/End Constraints as before (do you remember those numbers? :O)

Now your tube is done!

You can open the original assembly without the tube, change the positions, and the assembly with the tube should update on its own.


Ta-da!

Saturday, December 13, 2014

Budapest for 1.5 days




If I could describe Budapest using two words, it would be 'grey' and 'mysterious.' The weather wasn't so great during the 1.5 days; it was raining, cloudy and foggy -- definitely not the best time to take photographs. Still, they turned out alright after some post-processing. :)

My travel buddy this time was Alvin, and despite our super uncomfortable wet shoes, we tried to make the best of the trip. Alvin's friend, Albert from Canada met up with us and showed us around despite his busy study schedule for med school finals.

The Chain Bridge
The city is divided into two parts by the river Danube; Buda on the west bank and Pest on the east.
Hungarian Parliament Building


Walking around the city, I noticed a lot of Roman-esque buildings. It turns out, Romans declared this city the capital of Pannonia Inferior.

At the centre of Chain Bridge, overlooking the Statue of Liberty on the left and Pest on the right.

Friday, December 12, 2014

[Jam Sesh] Girl from Ipamena

This week, Alvin and I covered a classic Bossa nova song called Girl from Ipamena.
For some reason, the piano went completely out of tune on certain notes since last week :(
To spice up the video, I added random pictures/videos from my trips :)


Wednesday, December 10, 2014

Homeless in Dortmund

Alvin and I bought our plane tickets to Budapest, leaving from Dortmund airport two weeks before the trip. Unfortunately, the only available times for the flights were early morning; which meant we had to be at Dortmund the night before.

Well, it turns out Dortmund airport closes between midnight and 3am, and the city has exactly one hostel, and it was all booked out. Hotels were out of our price range and couchsurfing failed us, so we decided to YOLO it. We brainstormed a few ideas:

  1. Bring a sleeping bag, set up a tent near the airport, and camp out for the night. It turns out wild-camping is illegal in Germany. 
  2. Find a 24/7 McDonalds, sleep there. There was no 24/7 McD's in Dortmund.
  3. Sleep on the street. No problem here.
Clearly, the only option we had was sleeping on the street. 
So we arrived in Dortmund around 8pm, had dinner, and set out to see the city for a bit. We found a Christmas market, and Germany's biggest Christmas tree. Impressive!! Except it was about -5 degrees outside.

As we walked around the city, we looked for bushes near big buildings. We found a super nice hidden place, but we had to sleep on concrete floor. It was away from the city centre, and not a lot of people walked by (even if they did, they couldn't see anything). I didn't take a picture of the place, since it was night time :(

After about an hour of trying to fall asleep in freezing conditions, I gave up. Alvin and I decided to just hang out at the Hauptbahnhoff (main train staition), which is supposedly open 24/7. 
Then came our savior, KFC. This KFC by the HBf was the warmest, and the tastiest-smelling place we were at all night. So, along with 10 other travellers cozied up inside, we took a short nap.
At 4am, we headed to DTM airport.

Off to Budapest! <<-- will be linked to a new post soon.

After Budapest, we came back to Dortmund airport, and stayed at the most beautiful McD's I have ever witnessed until our car-share ride home arrived.

The only good thing about Dortmund was this McDonalds. (Partly because I planned my trip terribly) Dortmund gets 5 McBreakfast out of 10. 

Wednesday, December 3, 2014

[R] Caret training error

Error: 

When I was training a neural network with R using 'nnet' method, I got the following error:
Error in model.frame.default(Terms, newdata, na.action = na.omit, xlev = object$xlevels) : 
  invalid type (list) for variable 'x'

Reason:

It turns out, predict.train function in the {caret} package uses the model.frame function in {stats} package to extract the formula from the trained network.

This error is received when you are trying to train a single-input network (1-n-k network).

Solution:

Unfortunately, I haven't found way to train single-input networks using caret and nnet. You CAN, however, use 'nnet' by itself to train your network. If you wanted to optimize your network, you would have to iteratively change the desired hyperparameters (such as weight decay, size of hidden layer, etc).


I will post an update if it is fixed in the future.