Saturday, November 26, 2016

Arduino Interrupt Stepper Driver - CTC Mode

Introduction to the Problem

This tutorial will show how to drive a Pololu style stepper (A4988) driver using a timer interrupt. This method is non blocking, efficient, and as far as I know is pretty much what most 3D printer firmwares use.

The idea is this. A pololu style stepper driver (the kind that plugs into the RAMPS board) only requires two inputs from the Arduino. One is a direction pin. The other is a pulse train. One rising edge equals one step (or micro step, depending on how the set pins are wired). Most people when they first get going with steppers probably do one of two things. 1) They use some library that does all this for them (I don't know if one exists, but maybe it does) or 2) They just throw a digitalWrite in the loop() and pulse it that way. The problem with that is that it is dependent on the speed with which the loop runs. Enter Timer Interrupts

Interrupts - Conceptually

The timer interrupt is a low level feature of the ATmega family. It is not something that is provided by Arduino, and in fact functions such as millis() and delay() are based on them. I have always been a bit surprised that Arduino does not break timer interrupts out a little. They are really pretty easy to use but are very powerful. I am not going to go into great detail on the specifics of timer interrupts because there are other sources out there. The best of which is the ATmega datasheet.

The idea is this - the ATmega CPU is sitting there executing your code, pulling commands off of the stack. It does this in the same order each time. On another part of the chip there is this thing called a timer. It is counting up from 0 to some value over and over again incrementing at a set frequency. When it reaches the target value it sets a flag and goes back to 0. When that flag is set, the ATmega chip sees it and says, "it is time to execute a special piece of code. Drop everything and do it." What ever it was doing before goes back on the stack and what you put in the "interrupt service routine (ISR)" gets executed. Then it goes back to its normal business. We want to put our "pulse stepper driver" code in the ISR.

There are a couple of dangers with this, but I will just leave you with this. Keep the ISR short. Don't do any serial prints or heavy computations (floating point math) in there. Calculate those ahead of time and pull them in as compile time constants ideally.

Solving the Problem

Now I actually came up with 3 ways of solving this problem
  1. Using a fixed rate Timer Interrupt and only pulsing on some of the ISRs
  2. Using CTC mode and pulsing inside the ISR
  3. Using a special PWM mode 
This tutorial covers method 2. It uses Timer5 in Clear Timer on Compare (CTC) Mode. This allows you to call an interrupt at whatever frequency you want. If you're familiar with timer interrupts  the picture below might help. Again, I will not take the time to go into that much detail on that in this post. For now, I will point you to the ATMega datasheet which covers all of this stuff and THIS post by maxembedded.
CTC Mode - From ATMega Datasheet

Another important point is that I use direct port manipulation in the interrupt. I will not cover that here, but there are numerous examples online of how that works in addition to the ATMega datasheet. HERE is one example. I use direct port manipulation because it is much faster. As stated above, the ISR should execute as quickly as possible.

The Practical Stuff

Copy the code below. Wire it according to pins set in the code. Change the pulses per second calculation based on your setup (change it in the ISR Location calculation too). Set targSpeed in mm/s. Then set the Z_DIR_PIN and DirFlag based on the direction you want to drive. Test your code.

I hope this is helpful to someone. If it is, please let me know in the comments. If anyone that reads this has any insight into libraries available or other methods, comment those too. Good luck!

-Matthew



/*
 * Drives stepper using a pololu stepper driver and timer interrupts
 * 
 * This example uses pinouts associated with RAMPS 1.4 z-axis
 * 
 * Last edited by Matthew 11/14/2016 Arduino 1.6.7
 *                projectsfromtech.blogspot.com
 * TRCCR1A/B               
 * COM1A = 0b00 - disconnect OCR
 * WGM1 = 0b0100 - Fast PWM with the top value at compare match
 * CS1  = 0b001 - no prescaling               
 * ICNC1 = ICES = 0b0 - doesn't apply
 * 
 * */

const byte Z_STEP_PIN    =     46;
const byte Z_DIR_PIN     =     48;
const byte Z_ENABLE_PIN  =     62;  //62

//Interrupt Variables
volatile uint16_t PulseOnISRNum = 0;
volatile uint16_t isrSincePulse = 0;

//============================================================================
void setup() {
  Serial.begin(115200);

  pinMode(Z_STEP_PIN, OUTPUT);
  pinMode(Z_ENABLE_PIN,OUTPUT);
  pinMode(Z_DIR_PIN, OUTPUT);

  //setup Timer1
  TCCR5A = 0b00000000;
  TCCR5B = 0b00001001;
  TIMSK5 |= 0b00000010;       //set for output compare interrupt
  sei();                      //enables interrups. Use cli() to turn them off
}

float targSpeed = 2.5;       // mm/s
float PPS = 0;               // Pulses Per Second
int8_t DirFlag = 1;          // Direction flag. Set this to keep track of location
int32_t Location = 0;        // nanometers (m*10^-9) scaled by 10^-6 to avoid floating point math in interrupt

long clk = micros();


//============================================================================
void loop() {
  //Set Direction
  digitalWrite(Z_DIR_PIN, LOW);     // Low is forward   (based on setup)
  DirFlag = 1;
//  digitalWrite(Z_DIR_PIN,HIGH);       // High is backward (based on setup)
//  DirFlag = -1;
  digitalWrite(Z_ENABLE_PIN , LOW);   // Active Low

  // Set Speed - these calculation are based on your harware setup
  //           - Mine are for 1/16 microstepping and an m5 threaded rod driving the stage
  //------------------------
  for(float ind = 0 ; ind <3.0 ; ind = ind+0.0005)
  {
  targSpeed = ind;            // mm/s
  PPS = targSpeed * 4000;     //Pulses/s
  OCR5A = 16000000/PPS - 1;   //equation from pg 146 in datasheet- removed factor of 2 b/c I am manually pulsing in an interrupt every time
  Serial.print("Speed (mm/s): ");
  Serial.print(targSpeed);
  Serial.print("  Loop Time (ms): ");
  Serial.print(micros()-clk);
  Serial.print("  Location (mm): ");
  Serial.println(Location/1000000.);
  clk=micros();
  // Input other code here! Stepper driver will run even if this code is blocking!


}}

//================================================================================




ISR(TIMER5_COMPA_vect) {
//    digitalWrite(46, HIGH);       // Driver only looks for rising edge
//    digitalWrite(46, LOW);        //  DigitalWrite executes in 16 us  
    //Generate Rising Edge
    PORTL =  PORTL |= 0b00001000;   //Direct Port manipulation executes in 450 ns  => 16x faster!
    PORTL =  PORTL &= 0b11110111;
    Location = Location + 250 * DirFlag ;  //Updates Location (based on 4000 Pulses/mm)
}
    

Saturday, November 19, 2016

Raspian ROS on Raspberry PI B+ with Downloadable Image

I wanted to share my image of ROS Indigo for a Raspberry Pi B+. When I tried a couple of images I found online, none of them seemed to work, so I had to make my own. I will warn you that it is pretty slow, but ROS is installed. I was originally going to use this onboard a turtlebot, but I decided against it after seeing how slow it was. As far as I know it would work, but the extent of my testing was launching roscore. Here is the process I went through to create the image.

  1. Install Raspian Jessie with Pixel version September 2016
  2. Follow instructions on the ROS wiki for the ROS-Comm version on Indigo
  3. Pull Image using Win32DiskImager

Download the image here: Raspian_PiBPlus_ROS_Indigo_11_17_2016.7z


Notes:

  • It would probably be better to use the minimal version of Raspian. Like I commented above, it is pretty slow
  • I had issues building the catkin workspace. I think it was RAM related as the wiki suggests. I ended up closing everything that was open and running with the default -j4 option, and it worked.
  • Contrary to seemingly popular belief, ROS can be installed on an original Raspberry Pi. It is just painful. My install took several hours.
  • The image is 2.287 Gb compressed and 31.260 Gb uncompressed. 
I hope this is useful to someone.
-Matthew

Saturday, November 12, 2016

Replacing a Burnt Out MOSFET in an AR6400

This is a quick post about changing the MOSFET in an AR6400. This appears to be a a fairly common problem people have with RC airplanes such as the Hobbyzone Champ or the Parkzone P51 Mustang. Symptoms include elevator or other control surface only moving in one direction,  elevator or other control surface not moving at all, or a distinct electronics burning smell emanating from the control board. This appears to be usually caused by plugging in the battery backwards.

My AR6400L with MOSFET Desoldered

I don't really have a whole lot in the way of new information on the subject. I am just going to compile some of the necessary resources here for reference.

The replacement part is the FDG6322C. When I considered shipping, the cheapest place I could find in a short search was DigiKey. I would buy extras. They are extremely tiny. Here is the datasheet.
https://www.fairchildsemi.com/datasheets/FD/FDG6322C.pdf

One useful piece of information is that the two sides are wired the same, so if one is still intact you can use that side as reference. Here is a schematic taken from an rcgroups forum post that I found helpful.

Photo Credit: RCgroups.com member greghol

Here are the forum posts I used for reference. Really, the only pieces of information I would say you will need is the schematic and part number above.

https://www.rcgroups.com/forums/showthread.php?t=960011&page=3

https://www.rcgroups.com/forums/showthread.php?t=1221866#post14761707

https://www.rcgroups.com/forums/showthread.php?t=1837318

One last word of encouragement. I was able to do this with a pretty standard Weller SPG40 soldering iron. My burnt out MOSFET actually disentegrated while I was trying to remove it, but after replacing it the board works fine. It will fly again.

Notes:
At the time of this writing, these are some prices if you can't fix it.

  •  AR6400L - $60 (Ebay)
  • Hobbyzone Champ - $89.99 (Your local hobby shop)
  • HobbyKing SuperMicro control board - $22.68 (HobbyKing)

-Matthew

Wednesday, September 28, 2016

Combining and Trimming Videos Without Re-encoding Using FMPEG


Recently I was tasked with trimming and combining some videos for work. We had filmed 16 hours of lectures and needed to make them ready to be published. Unfortunately the cameras we used split the video into 19 minute segments that then had to be recombined into one long video. I got to work.

It was recommended to me that I just use Windows Movie Maker. I was skeptical, but I decided to give it a try. I copied the first 3 videos into the Windows Movie Maker workspace and waited. After waiting several minutes for the files to import and seeing the progress bar barely move, I decided there had to be a better way. I had used FFMPEG in the past in the form of SUPER media encoder. However, this time I decided to go directly to the source and use FFMPEG from the command line. Since Windows 7 (64 bit) is my primary operating system, I would use that. Likewise, my videos are mp4 files, so these instructions are written for those.

Disclaimer: I am not an FFMPEG expert. This is just what I found that worked for me. No guarantees that it is the most efficient way or that it will even work for you.

1) Download FFMPEG - https://www.ffmpeg.org/download.html

FFMEG can be found at the link above. Select your operating system and put it in a convenient folder (I used a Windows static version). Unzip it, and it is ready to use.

2) Separate Files into Directories

The first thing you are going to want to do is separate each set of files that you want to join into directories (ie. folders). One thing to make your life easier, make the name short and leave out any spaces. For example, "JohnsBirthday" instead of "Johns Birthday May 22 1934"

3) Run Batch File and Navigate to Directory

Run the batch file in the folder where you unzipped FFMPEG. It should be called "ff-prompt.bat". This will launch FFMPEG in a command prompt in the directory where it is located. 

Now you need to navigate in the command prompt to the directory (the folder) where you put the videos. If you know how to do this, use your favorite method. If you don't, here are some basic instructions. "cd" is the command to change directory. The usage is below.
  • Use "cd .." to go up one level
  • Use "cd folderName" to navigate to a folder in your current directory
If you get lost, you could always put the videos you are working on in the folder with the batch file. Then you would not have to navigate anywhere.

4) Combine Videos Using FFMPEG


To combine a video we are going to "concatenate" them. If you are familiar with programming, it is the same concept as concatenating a string. You are taking one and sticking on the end of the other. HERE is the link to the ffmpeg wiki page with the documentation, but I will copy the steps below that I used.

1) Copy the command below into the command line you have open. Replace "mp4" with the file type of your videos (.wav, .mov, etc.). Then run the command. It should execute very quickly.
(for %i in (*.mp4) do @echo file '%i') > mylist.txt
This creates a list of the files to be concatenated. Alternatively, you can create the file by hand. One note on this, I am not sure how it determines the order. It seems to be by the name. Also, spaces in the name cause problems, so make sure no file names have spaces. 

2) Now copy this into the command line you have open. Note that the ".mp4" was added by me to get it to work. The wiki implies you shouldn't need it.
ffmpeg -f concat -i mylist.txt -c copy output.mp4
This will take some time to execute, but it will still be much faster than re-encoding the video. I was combining about 1.5Gb of video and it took 3 minutes.

Command Line at the End of Successful Execution

3) Verify video. I went through and double checked at the points were it transitioned to make sure everything worked out. This step is obviously optional

5) Trim Videos Using FFMPEG

Trimming is a bit more complicated. "Why does my audio get desynced when trimming with FMPEG?" This is because of something called "key frames" that mp4 files use. I did not take the time to research those all that much, but the long and short seems to be that you have to trim at a key frame if you want to be able to trim a video without re-encoding or desyncing the audio. This is the command line instruction.
ffmpeg -ss 00:03:35 -i output.mp4 -t 00:57:04 -c copy outputtrimmed.mp4
As best I understand it, here is what is happening - the -ss "searches" for the closest key frame to the time 00:03:35. This will happen at the speed of re-encoding (slow). It will then pull in the file "output.mp4" starting at that key frame and will copy from that frame until time 00:57:04 into the file "outputtrimmed.mp4". This part will happen without re-encoding and will be fairly fast. This does not allow to trim at exact points, but it allows you to do it quickly and without losing audio sync (both important in my book).

6) Repeat

Do this for all of your videos.

Conclusion

So that is how I combined and trimmed a bunch of 19 minute segments into hour long blocks totaling about 16 hours quickly and without re-encoding the video. No guarantees it will work for you. I just had a hard time finding detailed instructions on this, so I thought I would post. One note, I imagine trimming before you combine would speed up the process a bit. For me it just made sense to do it after so I would not have to juggle so many files. The process isn't that long anyway.

That's it! If you have any questions or comments post those in the comments below. If you have any other methods to try or ways to make this better, post those too!

Until later,
-Matthew