Monday, May 28, 2012

[Quadcopter] How to design?

Now included in the expanding build log

FM Radio Aux Input Mod.

The car i currently drive is a Ford 92' Taurus. The radio sucks; its as simple as that. It has a broken tape player, but the radio and the coordinating buttons still work. Sometimes i wanted to listen to my iPod and not the radio, so I decided a few weeks ago that i wanted AUX input.

First, of course, i took the radio out of the car. Ford wants you to buy some stupid tool to get it out, but instead i used a clothes hanger :P. Basically, you push some long metal rods into the 1/8in holes(four of them, but you can do one side at a time, so two at a time.) on the front of the radio and this pushes in the springs and releases the radio.

On the back it has two harnesses and one-wire interface that runs to Volume, Preset, and Seek buttons next to the steering wheel(very useful, I think :)). One harness is for the power and other stuff like that, and the other goes to the speakers(the amp is built into the radio).


To take the top cover off of the radio, it was just some Torx screw and i easily got them out with this handy tool set.

Taking out the tape deck to get to the PCB.

Some nice little solenoids and a motor in there.

Radio top PCB

Radio bottom PCB

Audio amps on the heatsink

The Radio IC decodes the FM signal to audio, i think. Tape decoding IC does similar. Mixer IC takes the Tape and FM audio signals and switches between them/adds EQ/balance/fade etc. The main IC controls all of the other ICs/LCD/buttons/

Now comes the real reverse engineering. At first i was going to find the tape audio line, as it didn't work anyways, and inject aux audio there. As i was trying to find this tape audio line, i found it very difficult; I had narrowed it down to a few wires coming form the tape IC area going to the mixer, but i didn't know for sure. So i connected a little speaker(GND and the other wire free) and probed around on the mixer IC to find the audio lines. With this method i found both the FM and tape audio lines. Since the volume is adjusted by the buttons in the mixer IC and not in the tape decoding area on the PCB, i cut the tape audio trace and soldered in the headphone jack. If i didn't cut the trace, the mixer IC would receive noise from the tape decoding ICs and the aux audio at the same time which would be bad. I threw in a tape(so the radio changes to that channel in the mixer IC) and hoped for the best. Surprise, it didn't work very well! The audio was only playing on the left side, and sounded really distorted. After i thought about it, i found it strange that i only found one input line from the tape ICs to the mixer chip when it is stereo audio. I went back to the PCB and found another trace that ran form the tape ICs to the mixer, but when i played a tape, this line pure noise; strange, right? I went through all the pins on the mixer IC with the little speaker and i could still only find one audio channel for both the tape and FM that sounded like audio and not distortions. Maybe the tape and FM ICs put out a LR mixed audio line that is later split? I dunno. So that plan didn't work.

Next i though maybe i should bypass the mixer IC all together and inject the aux audio into the amps directly. I could turn the radio volume all the way down so nothing was coming out of the mixer IC(since that is where the volume is adjusted) to the the amps and so the aux input shouldn't have any noise issues. But first i had to find the audio line inputs to the amps. 

I knew that harness A(look at the pic below) was for power etc, so mostly ignored that, but harness B is for the speakers. Every other pin on B is GND and between the GNDs are the speaker pins. From looking inside i already knew where the speaker amps were, but i couldn't find ANY info on them because they are propitiatory. So i had to use a DMM(multimeter) and its continuity function to trace backwards. I forgot that i cant test continuity through capacitors, so that plan didn't work until i tested for continuity until the caps, and then jumped to the other side of the cap and continued testing. I drew a little pinout on paper of the IC and used process of elimination to find the audio inputs to the amps. I started by using harness B to find the out put pins on the amp ICs and crossing those out on my pinout, and then i found GND, VCC, and any other pins that were linked together(if they were linked together, it wouldn't make sense that they were inputs, would they?) In the end, it was pretty obvious; four small traces(for each speaker) running to the mixer IC.

Since the audio jacks on pretty much any Mp3 player has only left and right, i soldered the front/back left speaker amp inputs together and connected that to the left pin of the audio jack and did the same for the right. I turned down the volume to 0 on the radio, connected the iPod to the audio jack and it worked! Crisp, clean and on demand music! To use the FM radio, i just unplug the iPod and turn the volume back up on the radio
Up close on the aux input mod


But what would be any Aux input without a charger? I guessed, and confirmed with a DMM, that the ICs ran on 5v. Then i connected a USB female plug to the GND and +5v pins i found in the radio to create a charger. This doesn't work for iPod because Apple is annoying and certain voltages have to be on D+ and the D- pins of the USB for it to start charging. With older generations like my iPod Touch 1G, the voltages are less picky, but the voltages have to be more precise for newer generations. If D+ and D- are both 2v it charges at about 500ma(like a computer).With 2.8v on D- and 2v on D+ it charges at about 1A. Pinout below:
But, how do i get these voltages? A voltage divider, of course. Below, V1 is 5v, 0V is GND, V2 is  the output voltage, in my case. R1 and R2 are resistors and depending on their values/ratio, the voltage output on V2 varies. This is a great calculator for finding what resistors you need. i found that with R1 being 68kohms and R2 being 47kohms, i get 2v on V2. With R1 being 22kohms and R2 being 27kohms, i get 2.75v on V2. But the radio voltage wasn't exactly 5c; more like 4.91, so that changes the values a bit.
One thing that i found strange was that the voltage from the divider on both pins was .2v lower than the calculator said it would be. Even though the Radio was putting out 4.91v, that still isnt enough to drop the voltage .2v. So i choose resistors that would give me 3v theoretically on D-(2.8v really) and 2.2v on D+(2v really). It worked great; I got almost exactly 2.8 and 2v.

Adding in the USB and audio jack.

Used some Sugru to hold the connectors in.

Still, i have one last issue. If i plug in both the charger and the audio cable in at once, i get this strange ticking sound through the speakers, but not when i have just the audio cable. Maybe add a cap to the usb plug? But, overall, this mod worked out quite well! 

Sunday, May 13, 2012

er9x Quadcopter Setup

Now that i have the er9x firmware on the Turnigy 9x, i have to change some of the settings so that it works with MWC. I'd recommend you read through the er9x manual here. It tells you how to navigate the menus and pretty much anything you need to do to get started.

First, you need to calibrate the sticks so that the TX knows how far the sticks can move. My values were completely wrong when i first flashed the er9x firmware, so this is required.
1. Press and hold the + button for a second to get into the radio setup menu.
2. Press + again to get to screen 6/6
3. Follow onscreen instructions.

Next, I changed some cosmetic settings.
1. go to the Radio Setup menu again and go to 1/6, Use the UP/DN buttons to move between options.
2. Set the Owner Name by moving to it and then pressing Menu. Now use UP/DN to change the character and +/- to change position in the name.
3. I also changed the Beeper(how loud it is), Battery Warning(good for use with LiPos), and Inactivity Alarm(if i forget to turn it off :)) options here. You can change the TX Mode at the very last option(default is 2).

Now for the more useful mixing, trim, channel setup.
1. From the home screen, press and hold the - button for a second to get into the Model Setup screen.
2. All of the lines should be blank and the first one having a * next to it meaning it is selected.
3. Now press - to go to 2/10; Name, Trim Inc, Proto PPM, E. Limits, and Trainer are the settings you might have to change.
4. Go to the 10/10 menu and select the Simple 4Ch Template; its a good way to start.
5. Go to 5/10; this has tons of features, but all we want to do is change which channels output what signal. Move down until Ch1 is selected, then press menu. The top option of the menu is the only one that needs to be changed. I choose AIL(roll) because the Ch1 port on the RX is connected to the roll pin on the MWC board. Once you have the correct setting, press exit. Now go down until Ch2 is underlined and do the same as you did for Ch1. And then for Ch3, Ch4, Ch5(AUX 1 for me), Ch6(AUX 2 for me).
AIL is roll, ELE is pitch, THR is throttle, RUD is yaw, P1 is the Hov.Pit pot, P2 is the Gear pot, P3 is the Pit. Trim pot.
6. Go to 6/10; this allows you to change Subtrim(what the center value of the stick is when centered), and End Points(what the values are for when the stick is all the way UP/DN or L/R.) Subtrim for each channel needs to be at 1500, and the low point needs to be 1000, high point 2000. They wont be perfect, but get them as close as possible(you can set a dead zone with the MWC code if you need too). The first column is Subtrim, next is low point, last is high point. The row is for each channel that you just set is step 5. So, use UN/DN to move between channels, and +/- to move between Subtrim/low/high, Menu to select value to change, then +/- to change value, Menu to finalize value. This is where the MWC computer GUI comes in; it allows you to see in live time where the center is of each channel and how high/low they go. So, get your quad all powered up and connected to the GUI.
a. Move all sticks(and AUX pots) to center, and then set the subtrim with +/- for each channel so that all values in the GUI are very close to 1500.
b. Setting low/high points; for Ch1 I choose AIL(roll), so ill select the low point for Ch1, move the AIL(roll) stick all the way to the left, then adjust the vlaue with +/- until it is at 1000 int the GUI for roll. Then I'll select the high point and move the stick all the way to the right and then adjust the vlaue with +/- until it is at 2000 int the GUI for roll.

Aux Switches/Channels
Here i will go through how to setup two Aux channels(5+6) for the 3pos F.MODE switch and the 2Pos AIL switch.
1. Go to the Mixer(5/10) and go down so that your first Aux channel(most likely CH5) is underlined and hold menu(this creates a MIX in CH5).
2. Source should be Half, Weight -100, then go down to Switch, and change it to ID0. Go back to the previous menu(EXIT button).
3. Make sure CH5 is underlined again and hold menu. This creates another MIX in CH5.
4. Source should be Half, Weight 100, then go down to Switch, and change it to ID2. Go back to the previous menu.
5. Done with 3Pos.
6. Move the cursor so that CH6 is underlined and hold menu.
7.  Source should be FULL, Weight 100, then go down to Switch, and change it to AIL. This is not the AIL  stick, it is the AIL switch; this also applies for the ELE, THR, RUD switches. Look at page 9 of the manual PDF to see the names of the switches(or just look on the TX). Go back to the previous menu.
8. Done! Now go to the main screen and press DN until you get to the screen that shows you the current values of the channels. Test the switches. Now you can go into the MitiWii config GUI and change the function of the switches.

What about Trainer mode?
1. After i got all of the stuff setup for regular control, i went back to 1/10 in the Model Setup menu, moved to the model I made, and held down Menu(this duplicates the model). Then i changed the name to Trainer or something so i could distinguish between the two. Then i went to the Radio Setup 1/6, and turned on trainer mode.
2. 2/6 of Radio Setup is the Trainer menu. The THR on the left stands for your THR channel. Change OFF to := if you want the trainer to have complete control of the channel, += means they have partial control. Change the % to the percentage of the channel you want the trainer to control. Then change the ch1 to the channel of the trainer that is THR(or AIL or whatever).

HUURAY YOUR DONE!

Turnigy 9x TX Mods

The Turnigy 9x is a great beginner TX, but it definitely needs some adjustments to make it better. I first added a reliable battery, then fixed the trainer mode, and finally, put the er9x firmware on it.

Battery Mod
This is almost a must do because the stock AA holder is very unreliable and can become disconnected during flight(yikes, quad on the loose...). I've seen two main fixes for this; Li-Ion batteries, or Li-Fe/Po transmitter packs.


Trainer Mode Fix
I did this mod so that i don't have to unplug the transmitter module in the TX every time I want to do trainer mode or use the 9x for a simulator.
First, take out all the screws in the back of the case 
and unplug the cable connecting the two halves.
Look at the back half and find the trace pointed to in the picture.
Cut the trace with an knife and make sure that it is cut with a multimeter. Now, you have two options; you can put a 1k resistor in between the two blue circled solder points, or, you can scrape off the light green part   above and below the trace until you hit copper, and then solder the 1k resistor on the two scraped off places.
Done!


er9x Firmware
This mod replaces the stock Turnigy firmware and replaces it with the er9x firmware which has way more features, and maybe even the best part; it disables the beeper if you want! This requires a AVR ISP of some sort; i used a Arduino, but those other fancy ones work too :). You''ll obviously need to crack open your case, so you might as well do the trainer mode fix at the same time.

Inside the TX:
Once you have your TX open, solder wires onto the labeled solder pads. 
Then, label the other ends of the wires so you don't mix them up later(that would be bad). 
You can either just have a cable that comes out(through a pre-made hole in the battery compartment) like i did, or you can cut a little hole in the case a put a standard ISP header in there(probably better).

Now you need to get the Arduino ready. ...And this is where it went all wrong. Apparently the Arduino ISP sketch that is provided with the IDE isn't fully working in 1.0, so i had to change a few things to get it to work.
1. The serial buffer size needs to be changed so that the Arduino can keep up with the data(er9x firmware) being sent to it over serial. To do this, find your arduino directory(arduino-1.0 somewhere), then go to: arduino-1.0\hardware\arduino\cores\arduino\ and open HardwareSerial.cpp  in a text editor. Search for #define SERIAL_BUFFER_SIZE and change its value from 64 to 128.

2. The current Arduino ISP sketch cant handle the large EEPROM, so use this sketch instead. Upload it now:
// this sketch turns the Arduino into a AVRISP
// using the following pins:
// 10: slave reset
// 11: MOSI
// 12: MISO
// 13: SCK

// Put an LED (with resistor) on the following pins:
// 9: Heartbeat - shows the programmer is running
// 8: Error - Lights up if something goes wrong (use red if that makes sense)
// 7: Programming - In communication with the slave
//
// October 2010 by Randall Bohn
// - Write to EEPROM > 256 bytes
// - Better use of LEDs:
// -- Flash LED_PMODE on each flash commit
// -- Flash LED_PMODE while writing EEPROM (both give visual feedback of writing progress)
// - Light LED_ERR whenever we hit a STK_NOSYNC. Turn it off when back in sync.
//
// October 2009 by David A. Mellis
// - Added support for the read signature command
//
// February 2009 by Randall Bohn
// - Added support for writing to EEPROM (what took so long?)
// Windows users should consider WinAVR's avrdude instead of the
// avrdude included with Arduino software.
//
// January 2008 by Randall Bohn
// - Thanks to Amplificar for helping me with the STK500 protocol
// - The AVRISP/STK500 (mk I) protocol is used in the arduino bootloader
// - The SPI functions herein were developed for the AVR910_ARD programmer
// - More information at http://code.google.com/p/mega-isp
#include "pins_arduino.h"  // defines SS,MOSI,MISO,SCK
#define SCK 13
#define MISO 12
#define MOSI 11
#define RESET 10

#define LED_HB 9
#define LED_ERR 8
#define LED_PMODE 7
#define PROG_FLICKER true

#define HWVER 2
#define SWMAJ 1
#define SWMIN 18

// STK Definitions
#define STK_OK 0x10
#define STK_FAILED 0x11
#define STK_UNKNOWN 0x12
#define STK_INSYNC 0x14
#define STK_NOSYNC 0x15
#define CRC_EOP 0x20 //ok it is a space...

void pulse(int pin, int times);

void setup() {
  Serial.begin(19200);
  pinMode(LED_PMODE, OUTPUT);
  pulse(LED_PMODE, 2);
  pinMode(LED_ERR, OUTPUT);
  pulse(LED_ERR, 2);
  pinMode(LED_HB, OUTPUT);
  pulse(LED_HB, 2);
}

int error=0;
int pmode=0;
// address for reading and writing, set by 'U' command
int here;
uint8_t buff[256]; // global block storage

#define beget16(addr) (*addr * 256 + *(addr+1) )
typedef struct param {
  uint8_t devicecode;
  uint8_t revision;
  uint8_t progtype;
  uint8_t parmode;
  uint8_t polling;
  uint8_t selftimed;
  uint8_t lockbytes;
  uint8_t fusebytes;
  int flashpoll;
  int eeprompoll;
  int pagesize;
  int eepromsize;
  int flashsize;
}
parameter;

parameter param;

// this provides a heartbeat on pin 9, so you can tell the software is running.
uint8_t hbval=128;
int8_t hbdelta=8;
void heartbeat() {
  if (hbval > 192) hbdelta = -hbdelta;
  if (hbval < 32) hbdelta = -hbdelta;
  hbval += hbdelta;
  analogWrite(LED_HB, hbval);
  delay(40);
}


void loop(void) {
  // is pmode active?
  if (pmode) digitalWrite(LED_PMODE, HIGH);
  else digitalWrite(LED_PMODE, LOW);
  // is there an error?
  if (error) digitalWrite(LED_ERR, HIGH);
  else digitalWrite(LED_ERR, LOW);

  // light the heartbeat LED
  heartbeat();
  if (Serial.available()) {
    avrisp();
  }
}

uint8_t getch() {
  while(!Serial.available());
  return Serial.read();
}
void fill(int n) {
  for (int x = 0; x < n; x++) {
    buff[x] = getch();
  }
}

#define PTIME 30
void pulse(int pin, int times) {
  do {
    digitalWrite(pin, HIGH);
    delay(PTIME);
    digitalWrite(pin, LOW);
    delay(PTIME);
  }
  while (times--);
}

void prog_lamp(int state) {
  if (PROG_FLICKER)
    digitalWrite(LED_PMODE, state);
}

void spi_init() {
  uint8_t x;
  SPCR = 0x53;
  x=SPSR;
  x=SPDR;
}

void spi_wait() {
  do {
  }
  while (!(SPSR & (1 << SPIF)));
}

uint8_t spi_send(uint8_t b) {
  uint8_t reply;
  SPDR=b;
  spi_wait();
  reply = SPDR;
  return reply;
}

uint8_t spi_transaction(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
  uint8_t n;
  spi_send(a);
  n=spi_send(b);
  //if (n != a) error = -1;
  n=spi_send(c);
  return spi_send(d);
}

void empty_reply() {
  if (CRC_EOP == getch()) {
    Serial.print((char)STK_INSYNC);
    Serial.print((char)STK_OK);
  } else {
    error++;
    Serial.print((char)STK_NOSYNC);
  }
}

void breply(uint8_t b) {
  if (CRC_EOP == getch()) {
    Serial.print((char)STK_INSYNC);
    Serial.print((char)b);
    Serial.print((char)STK_OK);
  }
  else {
    error++;
    Serial.print((char)STK_NOSYNC);
  }
}

void get_version(uint8_t c) {
  switch(c) {
  case 0x80:
    breply(HWVER);
    break;
  case 0x81:
    breply(SWMAJ);
    break;
  case 0x82:
    breply(SWMIN);
    break;
  case 0x93:
    breply('S'); // serial programmer
    break;
  default:
    breply(0);
  }
}

void set_parameters() {
  // call this after reading paramter packet into buff[]
  param.devicecode = buff[0];
  param.revision = buff[1];
  param.progtype = buff[2];
  param.parmode = buff[3];
  param.polling = buff[4];
  param.selftimed = buff[5];
  param.lockbytes = buff[6];
  param.fusebytes = buff[7];
  param.flashpoll = buff[8];
  // ignore buff[9] (= buff[8])
  // following are 16 bits (big endian)
  param.eeprompoll = beget16(&buff[10]);
  param.pagesize = beget16(&buff[12]);
  param.eepromsize = beget16(&buff[14]);

  // 32 bits flashsize (big endian)
  param.flashsize = buff[16] * 0x01000000
    + buff[17] * 0x00010000
    + buff[18] * 0x00000100
    + buff[19];

}

void start_pmode() {
  spi_init();
  // following delays may not work on all targets...
  pinMode(RESET, OUTPUT);
  digitalWrite(RESET, HIGH);
  pinMode(SCK, OUTPUT);
  digitalWrite(SCK, LOW);
  delay(50);
  digitalWrite(RESET, LOW);
  delay(50);
  pinMode(MISO, INPUT);
  pinMode(MOSI, OUTPUT);
  spi_transaction(0xAC, 0x53, 0x00, 0x00);
  pmode = 1;
}

void end_pmode() {
  pinMode(MISO, INPUT);
  pinMode(MOSI, INPUT);
  pinMode(SCK, INPUT);
  pinMode(RESET, INPUT);
  pmode = 0;
}

void universal() {
  int w;
  uint8_t ch;

  fill(4);
  ch = spi_transaction(buff[0], buff[1], buff[2], buff[3]);
  breply(ch);
}

void flash(uint8_t hilo, int addr, uint8_t data) {
  spi_transaction(0x40+8*hilo,
  addr>>8 & 0xFF,
  addr & 0xFF,
  data);
}
void commit(int addr) {
  if (PROG_FLICKER) prog_lamp(LOW);
  spi_transaction(0x4C, (addr >> 8) & 0xFF, addr & 0xFF, 0);
  if (PROG_FLICKER) {
    delay(PTIME);
    prog_lamp(HIGH);
  }
}

//#define _current_page(x) (here & 0xFFFFE0)
int current_page(int addr) {
  if (param.pagesize == 32) return here & 0xFFFFFFF0;
  if (param.pagesize == 64) return here & 0xFFFFFFE0;
  if (param.pagesize == 128) return here & 0xFFFFFFC0;
  if (param.pagesize == 256) return here & 0xFFFFFF80;
  return here;
}


void write_flash(int length) {
  fill(length);
  if (CRC_EOP == getch()) {
    Serial.print((char) STK_INSYNC);
    Serial.print((char) write_flash_pages(length));
  } else {
    error++;
    Serial.print((char) STK_NOSYNC);
  }
}

uint8_t write_flash_pages(int length) {
  int x = 0;
  int page = current_page(here);
  while (x < length) {
    if (page != current_page(here)) {
      commit(page);
      page = current_page(here);
    }
    flash(LOW, here, buff[x++]);
    flash(HIGH, here, buff[x++]);
    here++;
  }

  commit(page);

  return STK_OK;
}

#define EECHUNK (32)
uint8_t write_eeprom(int length) {
  // here is a word address, get the byte address
  int start = here * 2;
  int remaining = length;
  if (length > param.eepromsize) {
    error++;
    return STK_FAILED;
  }
  while (remaining > EECHUNK) {
    write_eeprom_chunk(start, EECHUNK);
    start += EECHUNK;
    remaining -= EECHUNK;
  }
  write_eeprom_chunk(start, remaining);
  return STK_OK;
}
// write (length) bytes, (start) is a byte address
uint8_t write_eeprom_chunk(int start, int length) {
  // this writes byte-by-byte,
  // page writing may be faster (4 bytes at a time)
  fill(length);
  prog_lamp(LOW);
  for (int x = 0; x < length; x++) {
    int addr = start+x;
    spi_transaction(0xC0, (addr>>8) & 0xFF, addr & 0xFF, buff[x]);
    delay(45);
  }
  prog_lamp(HIGH);
  return STK_OK;
}

void program_page() {
  char result = (char) STK_FAILED;
  int length = 256 * getch() + getch();
  char memtype = getch();
  // flash memory @here, (length) bytes
  if (memtype == 'F') {
    write_flash(length);
    return;
  }
  if (memtype == 'E') {
    result = (char)write_eeprom(length);
    if (CRC_EOP == getch()) {
      Serial.print((char) STK_INSYNC);
      Serial.print(result);
    } else {
      error++;
      Serial.print((char) STK_NOSYNC);
    }
    return;
  }
  Serial.print((char)STK_FAILED);
  return;
}

uint8_t flash_read(uint8_t hilo, int addr) {
  return spi_transaction(0x20 + hilo * 8,
    (addr >> 8) & 0xFF,
    addr & 0xFF,
    0);
}

char flash_read_page(int length) {
  for (int x = 0; x < length; x+=2) {
    uint8_t low = flash_read(LOW, here);
    Serial.print((char) low);
    uint8_t high = flash_read(HIGH, here);
    Serial.print((char) high);
    here++;
  }
  return STK_OK;
}

char eeprom_read_page(int length) {
  // here again we have a word address
  int start = here * 2;
  for (int x = 0; x < length; x++) {
    int addr = start + x;
    uint8_t ee = spi_transaction(0xA0, (addr >> 8) & 0xFF, addr & 0xFF, 0xFF);
    Serial.print((char) ee);
  }
  return STK_OK;
}

void read_page() {
  char result = (char)STK_FAILED;
  int length = 256 * getch() + getch();
  char memtype = getch();
  if (CRC_EOP != getch()) {
    error++;
    Serial.print((char) STK_NOSYNC);
    return;
  }
  Serial.print((char) STK_INSYNC);
  if (memtype == 'F') result = flash_read_page(length);
  if (memtype == 'E') result = eeprom_read_page(length);
  Serial.print(result);
  return;
}

void read_signature() {
  if (CRC_EOP != getch()) {
    error++;
    Serial.print((char) STK_NOSYNC);
    return;
  }
  Serial.print((char) STK_INSYNC);
  uint8_t high = spi_transaction(0x30, 0x00, 0x00, 0x00);
  Serial.print((char) high);
  uint8_t middle = spi_transaction(0x30, 0x00, 0x01, 0x00);
  Serial.print((char) middle);
  uint8_t low = spi_transaction(0x30, 0x00, 0x02, 0x00);
  Serial.print((char) low);
  Serial.print((char) STK_OK);
}
//////////////////////////////////////////
//////////////////////////////////////////


////////////////////////////////////
////////////////////////////////////
int avrisp() {
  uint8_t data, low, high;
  uint8_t ch = getch();
  switch (ch) {
  case '0': // signon
    error = 0;
    empty_reply();
    break;
  case '1':
    if (getch() == CRC_EOP) {
      Serial.print((char) STK_INSYNC);
      Serial.print("AVR ISP");
      Serial.print((char) STK_OK);
    }
    break;
  case 'A':
    get_version(getch());
    break;
  case 'B':
    fill(20);
    set_parameters();
    empty_reply();
    break;
  case 'E': // extended parameters - ignore for now
    fill(5);
    empty_reply();
    break;

  case 'P':
    start_pmode();
    empty_reply();
    break;
  case 'U': // set address (word)
    here = getch() + 256 * getch();
    empty_reply();
    break;

  case 0x60: //STK_PROG_FLASH
    low = getch();
    high = getch();
    empty_reply();
    break;
  case 0x61: //STK_PROG_DATA
    data = getch();
    empty_reply();
    break;

  case 0x64: //STK_PROG_PAGE
    program_page();
    break;
 
  case 0x74: //STK_READ_PAGE 't'
    read_page();  
    break;

  case 'V': //0x56
    universal();
    break;
  case 'Q': //0x51
    error=0;
    end_pmode();
    empty_reply();
    break;
 
  case 0x75: //STK_READ_SIGN 'u'
    read_signature();
    break;

  // expecting a command, not CRC_EOP
  // this is how we can get back in sync
  case CRC_EOP:
    error++;
    Serial.print((char) STK_NOSYNC);
    break;
 
  // anything else we will return STK_UNKNOWN
  default:
    error++;
    if (CRC_EOP == getch())
      Serial.print((char)STK_UNKNOWN);
    else
      Serial.print((char)STK_NOSYNC);
  }
}

Now for the Arduino hardware side:
1. Unplug your Arduino form the computer and connect the wires from the TX that you just soldered as follows:
Arduino Pin: 9x transmitter
10: RST
11: MOSI
12: MISO
13: SCK
5v: 5v
Gnd: Gnd
2. Put a 120ohm resistor between +5v and Reset on the Arduino. This stops the Arduino from rebooting in the ISP process.

Next up is the er9x firmware buring software.
2. When you open it, it should ask if you want to download the latest er9x firmware, click yes.
3. Go to burn->configure in the eepe software
select the avrisp programmer.
select m64 mcu.
select port your Arduino is connected to.
type -b 19200 in extra arguments.
4. You should probably backup the firmware, memory and EEPROM using the options in the burn menu( i didn't...oops) in case of an issue with the er9x firmware.
5. Go to burn->Flash Firmware to TX and select the firmware you saved from step 2. 
6. Should say complete after a minute or two!!

Joystick PPM Interface
This isn't really a mod, but it should make flying easier by hooking up a PC gaming joystick. Nothing too fancy, just a old game port joystick that is connected to a Arduino which sends PPM signals to the TX trainer port.......later

Tuesday, May 1, 2012

Arduino Composite TV out

I've been wanting to interface my Arduino with a monitor of some sort with RGB, but it didn't seem to be much of a possibility for such a memory limited device. But then i found this tvout library that uses composite video! It is only in black and white, but still pretty awesome. The first thing i thought of to do was make a space invaders type game with that Parallax joystick from some time ago...and its coming along quite well.

I have created a "starry" background and a ship bitmap so far(its a simple monochrome bitmap made in paint that can be converted to code and displayed using the library). Basically how it works: the loop reads the stick values, changes the X/Y coordinates of where the ship will be, then draws the "stars" and then the ship. This obviously happens many times a second so it looks fairly fluid on screen. So, currently I can move the ship around on a starry background and i'm starting to work on projectiles.

Here's some code in progress.
////////////////////BITMAPS!/////////////////////////////////////
//120 pixels wide,96 pixels high
PROGMEM const unsigned char BckGnd[] = {
  120,96,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x20,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x10,0x10,0x00,0x00,0x00,0x40,0x00,0x40,0x20,0x04,0x08,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x20,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x40,0x00,0x00,0x00,
  0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x08,0x00,0x00,0x00,0x40,0x00,
  0x04,0x02,0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,
  0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,
  0x00,0x00,0x00,0x80,0x00,0x04,0x00,0x08,0x00,0x00,0x00,0x00,0x40,0x00,0x00,
  0x20,0x02,0x00,0x00,0x10,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x10,
  0x00,0x40,0x00,0x80,0x00,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x40,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x00,
  0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,
  0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,
  0x20,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x10,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x02,0x00,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x10,
  0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,
  0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x08,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00,
  0x00,0x40,0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x00,
  0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,
  0x20,0x00,0x40,0x00,0x08,0x10,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x02,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,
  0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x04,0x00,0x00,0x80,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x08,0x10,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x04,0x00,
  0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x10,0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x04,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x04,
  0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x02,0x00,
  0x10,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x80,
  0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x02,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,
  0x04,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,
  0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x08,0x00,
  0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x40,0x00,0x00,0x10,
  0x00,0x00,0x00,0x08,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00,
  0x10,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x80,0x04,0x00,0x00,0x08,0x00,0x01,0x00,
  0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};

PROGMEM const unsigned char Ship[] = {
  8,7,
  0x18,
  0x18,
  0x3C,
  0xBD,
  0xBD,
  0xFF,
  0xFF};

////////////////////END OF BITMAPS!/////////////////////////////////////

/*
RCA video plug:
 Sync(Pin 9) -> 1k -> center prong
 Video(pin 7) -> 470ohm -> center prong
 Gnd -> outside casing
 */

#include <TVout.h>
#include <fontALL.h>

TVout TV; //init the TV object

//resolution of output
#define TVresW 120
#define TVresH 96

// x/y limits for ship
#define MaxRight (TVresW-8)
#define MaxDown (TVresH -7)

//this program uses a lot of memory, so i use bytes here because they are large enough for this purpose.
//where the ship is on screen
byte CurXPos = (TVresW-8)/2; //centers ship horizontally
byte CurYPos = TVresH -7; //(total height) - 7(ship height), moves ship to bottom

#define XaxisPin A2
int Xval=0; //raw stick data, x axis
#define YaxisPin A1
int Yval=0; //raw stick data, y axis

#define FireBtn 0 //aka digital 2 interrupt
volatile long lastDebounceTime = 0;   // the last time the interrupt was triggered
#define debounceDelay  10    // the debounce time; decrease if quick button presses are ignored
volatile int fireState = LOW; //initial button state
volatile long lastFire = 0;
byte FireY =0;
byte FireX = 0;

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

  attachInterrupt(FireBtn,FireEvent, RISING); //fire button interrupt

  TV.begin(NTSC,TVresW,TVresH); //initialize TV
  delay(1000); //wait for TV to detect input

  TV.bitmap(0,0,BckGnd); //displays the "stars"
  TV.select_font(font8x8); //each char is 8x8 pixels
  TV.print((TVresW-80)/2,(TVresH-8)/2,"Ship Test!"); //80 pixel long message, 8 tall
  TV.bitmap(CurXPos,CurYPos,Ship); //displays the ship

  delay(250);
}
void loop()
{
  GetSticks(); //get raw data
  MoveShip(); //move ship accordingly on screen
  CheckFire(); //testing
}

void GetSticks()
{
  Yval = analogRead(YaxisPin);
  Xval = analogRead(XaxisPin);
  /*
  Serial.print(Xval);
   Serial.print(" ");
   Serial.println(Yval);
   */
  if(Xval < 512) //move left
  {
    Xval = map(Xval,512,0,0,-4);
  }
  else//its >= 512
  {
    Xval = map(Xval,512,1023,0,4); //move right
  }

  if(Yval < 512) //move down
  {
    Yval = map(Yval,512,0,0,4);
  }
  else//its >= 512 //move up
  {
    Yval = map(Yval,512,1023,0,-4);
  }
}

void MoveShip()
{
  //get new pos
  CurXPos += Xval;
  CurYPos += Yval;

  if (CurXPos > MaxRight) //check horizontal limits
  {
    CurXPos = 0;
  }
  else if(CurXPos <= 0)
  {
    CurXPos = MaxRight;
  }

  if (CurYPos > MaxDown) //check vertical limits
  {
    CurYPos = 0;
  }
  else if(CurYPos <= 0)
  {
    CurYPos = MaxDown;
  }
  //clear screen + display new ship
  TV.bitmap(0,0,BckGnd); // used to be "TV.clear_screen();", but the BckGnd bitmap covers the enite screen, so not needed.
  TV.bitmap(CurXPos,CurYPos,Ship);
  /*
  Serial.print(CurXPos);
   Serial.print(",");
   Serial.println(CurYPos);
   */
}
/*
void DrawFire() //works well at drawing a bullet, but stops the program whiles its doing it because of the for loop.
{
  byte FireX = CurXPos + 4;
  byte FireY = CurYPos;

  for(byte i = FireY; i>=0; i--)
  {
    TV.set_pixel(FireX,i+1,0);
    delay(2);
    TV.set_pixel(FireX,i,1);
    delay(2);
  }
}
*/


//untested attempt at "drawing" fire(a bullet)
void FireEvent()
{
  if ((millis() - lastDebounceTime) > debounceDelay)
  {
    lastDebounceTime = millis();
 
    FireX = CurXPos + 4;
    FireY = CurYPos;
    fireState = true;
 
    TV.set_pixel(FireX,FireY+1,0);
    TV.set_pixel(FireX,FireY,1);
  }
}

void CheckFire()
{
  if(fireState && millis() > (lastFire + 2) && FireY >0)
  {
    FireY--;
    lastFire = millis();
 
    TV.set_pixel(FireX,FireY+1,0);
    TV.set_pixel(FireX,FireY,1);
  }
}