Saturday, December 28, 2013

Working like a regular car

EEPROM
In order to be practical I need the final version of my ECU to work with no thought or effort from the driver (me). I want to simply turn the key and have my car start. For the ECU to work it must have the engine tune data available. Where it previously was only provided by my laptop over a serial interface, it can now be written and read from the built in EEPROM memory. EEPROM memory will hold data even when power is disconnected from the ECU, but is much slower than RAM. When power is applied to the ECU, the data in the EEPROM is loaded into RAM so it can be quickly accessed when needed.

This works fine for now, but there is only 1KB of storage and I'm already using half. Later I'll need more data recorded, which might not fit in 1KB. The only other non volatile storage in the chip is the Flash program memory. It's much bigger at 64KB. The Flash storage is where the program is recorded, but I should be able record tune data into the unused areas.

Closed Loop Mode
Nearly all fuel injected cars will use a slightly rich air/fuel ratio while accelerating, under heavy load, or high RPM. This is based on values recorded in a fuel map and is called open loop mode. This allows for fast adjustments as engine load and speed changes, gives better power and helps prevent pinging(knocking). The disadvantage here is decreased fuel efficiency and more pollution.

While under light, steady load, such as when idling or cruising at a constant speed, closed loop mode is used. In closed loop mode the air/fuel ratio is continuously adjusted as close as possible to the ideal ratio. Using an oxygen sensor, the current ratio is determined by measuring the amount of oxygen in the exhaust gases. Voltage goes up when the engine is running rich, and down when lean. Small adjustments are made to the injector duty cycle based on measurements of this voltage. The end result is that less fuel is required and the function of the catalytic converter is improved. Unfortunately it does not function if it's cold, so cannot be used as soon as the car is started. And compared to a MAP sensor is much slower to respond to changes, which makes it unsuitable for situations where a sudden change in engine speed or load occurs.

http://www.youtube.com/watch?v=XHYJ5v8_8fs

Wednesday, December 4, 2013

When I grow up, I want to be a real ECU.

Mmmm sparodic updates...

I've made a lot of changes since my last post and I'm much closer to having something I can drive daily.

MAP Sensor
I've ditched the hotwire setup for a 2 bar MAP sensor. What this means is that instead of measuring the air flow, I am measuring the air pressure. It complicates things, but there's a few reasons I went for the MAP sensor. With the hotwire sensor I had trouble figuring out how it'd be properly mounted and how I'd fit the existing (or make a replacement) air filter box. I also had the problem of very high air flows not being measured accurately. Higher voltage from the hotwire signal = more airflow and I was getting close to 5v (max voltage) before the turbo was even fitted. The MAP sensor can be anywhere as long as I can reach a vacuum line to it. It's also much smaller and easier to mount.
MAP sensor

The difficult part comes in software. In order to properly calculate the injector pulse width, you need to know how much air is flowing into the engine (the total mass of air). The MAP sensor only gives me the manifold air pressure. An increase in air pressure does mean more air is flowing into the engine, but an increase in RPM also means more air is flowing into the engine. Now instead of a 1D fuel map, I have a 2D fuel map. The first dimension related to air pressure and the 2nd related to RPM. I now have 256 data points where I used to have 16. The code needs to calculate the injector pulse width based on how far the current values (pressure and RPM) are from their nearest data points. It does a sort of "proportional averaging" or "local linearisation" (seriously I don't know what to call it) to get the final result. I'm using a lot of floating point numbers and there are a lot of calculations to be done!
Small portion of tuning data (top/left numbers). Also, yes! I know RPM isn't a percentage!

I'll also be doing the same thing for ignition timing. More advance as RPM increases, and less advance as air pressure increases.

Processing time
My complicated fuel map gave me a new problem. And that's required processing time. Engine peaked at 2200 rpm now, not because the tuning was poor, but because the next injector pulse width could not be calculated in time. I ended up confirming the duration of processing time by outputting the timer values at the beginning and end up the interrupt handler. The difference was the total processing time.

I'm using the free XC8 compiler http://www.microchip.com/pagehandler/en_us/devtools/mplabxc/ to compile my C code. The free version has "limited optimisations" which result in the compiled code being slow to execute. The "standard" and "pro" editions are far too expensive for me to buy. Finally, there is SDCC http://sdcc.sourceforge.net/ a free and open source C compiler for PIC (and other) microcontollers, but is appears that my micro PIC18F46K80 is not properly supported yet. That leaves the following options:
  • Write optimised in-line assembly for the slowest sections of code
  • Crank up the MHz
I took the easy way out. Using the 4x PLL clock multiplier i'm now running the clock at 64MHz from 16MHz. This had the side effect of speeding up the timing for all the mcu peripherals (timers, USART). But I could fix this by adjusting some of the timer clock dividers and switching to the now 4x faster baud rate. Finally, in the case of the timer user for cam position timing, I used a seperate microcontroller PIC16F88 who's sole purpose was to provide a 500kHz signal for just that timer. Otherwise, only a 32.768 kHz watch crystal can be used (too slow). There's probably a way to provide a 500kHz clock with a few discreet components as well but it's much easier to write a couple of lines of code than it is to make up a new circuit.

The second micro could also be used for for secondary functions like turbo boost controller, or controlling the EGR valve. By removing these functions away from the main mcu I can simplify the code that it runs. Simpler code generally runs faster and is less bug-prone.

Veroboard
I've also moved the entire circuit from the broadboard to a veroboard. Soldered connections seem to be more reliable, except kynar wire is frikin fragile! There were some changes to the electrical characteristics of the circuit, especially with the 7805 regulator. I had to add a cap on the input side to smooth out the supply voltage. I think this has to do with the relatively high capacitance of the breadboard.
Messy Breadboard

I wasn't really sure of the best way to lay out the board, I just tried to keep the wires short. I made a couple of mistakes (miscounting pins) so I had to make some additional changes and the final result is a little messier than I wanted. Still, it is much better than the breadboard setup.
mmm blobbly solder



I also learned a very important lesson about proper connections. I spent a full day trying to fix an issue with serial communication between the mcu and my laptop. I thought the USB>serial adapter had died. I spent the night configuring a spare old laptop that had a serial port on it, when I should have realised the 4 pin header connecting to the MAX232 chip was damaged!! I would've liked to watch the signals coming in and out of the MAX232 chip with a scope, but I can't tell much with my analog scope and my cheapo USB digital scope is screwed.

So now I can start the car
Nope! for some reason the micro is acting a bit weird. I wasn't sure what version of my program was on it at the time, but I can't reprogram it. I know my programmer (PICkit 3) is working because I can program other chips. I'm hoping it's just a connection issue with the ICSP interface, but it looks OK. Could I have damaged the micro with either heat from the soldering iron or ESD? I'm not looking forward to replacing this micro on the board. So many pins. If only I had gone with a ZIF socket!

Edit on 2013-12-04: mcu is deaded. I can program a spare mcu on a breadboard, but if I replace it with the one desoldered from the veroboard, it will fail to program.

Friday, October 18, 2013

I swear I haven't forgotten this project!

Same old story. Been busy with uni and now work(which is awesome!)
Reeeaaallly busy. In fact, I should go back to my mechanics assignment.

What little work i've done has been on the software side to make it compatible with a MAP sensor... which I just bought of ebay :D

hopefully i'll have something to post about an a few weeks.

-fuzzymonkey

Saturday, September 7, 2013

The Maiden Voyage!

(to the end of my street)

Took me a while to get this far since, of course, uni work takes up my time. A valid reason of course :)

Only have video of the end after i return to my driveway. Much too difficult monitor the laptop and a/f ratio displays and hold a phone AND drive the car without something really bad happening.



I decided to test it out on the road after creating a 16 point fuel map and making further improvements to the injector pulse timing. It actually ran pretty well, but not quite good enough to drive daily. I expect it wouldn't pass any EPA tests either.

The fuel map is made of 16 data points that correspond to a particular level of air flow into the engine. The reason it exists is that you can't just deliver more fuel when the voltage from the hotwire sensor (measures mass of air flowing into engine) increases.  The correlation is not 1-1 matched, so a fuel map has to be created. The data points are arranged in order with the first one sets the fuel to be delivered at lowest air flow and the last one sets the fuel to be delivered at the highest air flow. Because it's not ideal to have 16 discreet steps that determine the amount of fuel to be delivered, the final value that is used is calculated based on how far the measured hotwire value is from each of the two nearest data points.


I was able to improve the injector timing in two ways. First, I have some code which will determine when the next cam signal pulse will occur. This is used to determine when the next combustion stroke will start. That code has been altered to use a new algorithm, which is a continuously corrected estimation. In practice the new method seems to be no better or worse than the old method, but it requires less memory and fewer instruction to complete, which means more time can be spent processing other stuff.
It basically works like this:
  1. Cam pulse occurs (record timer value)
  2. Note the difference between recorded timer value and the previous estimation. Record this as an estimation error.
  3. Make a new estimation (also take into account previous error in estimation)
  4. repeat
Once I had those estimations I could determine when to start and stop each injector pulse. In theory, if your engine does not have direct injection, then it's best to start each injector pulse as late as possible during the intake stroke of the cylinder. The result is that the injected fuel is mostly in a mist form and does not have time to settle completely into a liquid at the bottom (someone correct me if i'm wrong!!). The evenly spread fuel mist should burn much more uniformly than a pool of liquid, resulting in improved power and efficiency. My old method was to look at when the next cam pulse would occur (using the previously mentioned estimation) and if my pulse was, for example, 300 timer ticks wide, then I would start my pulse 300 ticks before the estimation. The problem with this method was that i was ending the pulse when the actual cam pulse occurred. The difference between the estimation and the actual cam pulse meant that the duration of the injector pulse was not exactly what it should be.


The second improvement I made was to use an additional CCP module (remember, a CCP module can be used to execute some program code at a precise time), so that I had one CCP for the injector pulse start, and one CCP for the injector pulse end. This meant that the injector pulse was always exactly for the duration intended duration. The final change to make was to have it occur in the middle of the intake stroke instead of the end. While I still prefer it to happen right at the end of the intake stroke, the reality is that it's not possible to determine exactly when the end would occur. If I started, and ended the injector pulse too late, then the cylinder intake valve would close before all the fuel was delivered. That of course, would be bad! Additionally when more fuel is required than can be delivered in a single intake stroke, the injector pulse is started before the beginning of the intake stroke. When the intake valve is opened (approx at the beginning of the intake stroke), all the fuel is pulled in from the already-started-but-not-yet-completed injector pulse. The pulse continues (delivering more fuel) all the way to the end of the intake stroke.



Now that I have these two changes implemented and working well I can move on to the next tasks. Firstly, that extra CCP module was taken from part of the code used to detect a stalled engine. I'll have to come up with a new way to ensure injector and ignition pulses stop happening if the engine stops running. I'll also improve and make better use of the fuel map and create a usable ignition map to get better spark timing.

Thursday, July 18, 2013

Success: smooth and stable operation

Firstly, I would like to say that my head hurts from all the rich MX5 exhaust smoke... Yeah, that's probably not healthy.

In my last post I mentioned that there was a problem with missed injector and spark events. The problem was caused by my old program code not being able to compensate fast enough for changing engine speed. If the engine was speeding up rapidly, then the calculated values could be so far out that they don't have a chance to complete before the next combustion cycle.

To remedy this my new program also takes into account the rate of change. By determining the rate at which the speed changes (acceleration) and then applying that rate of change to all the calculated timing values, my code can get near perfect timing with fewer or no missed events.

Next thing to do is tying the injector pulse width to the measured air flow.

WOOO!! PROGRESS!!! :D



Sunday, July 7, 2013

Progress!!

I've moved from "shaking and shuddering at 800 rpm" to "smokey smooth-ish revving at 3000 rpm". I got this far by tweaking some code and default values to minimise missed spark and injector pulse events. Not a true solution, but it made things a lot better!

Saturday, July 6, 2013

Running the engine from my ECU

This will be more of an update than anything else. I've created the circuitry and wiring for a fully sequential fuel injection setup. Once I had the spark and injection system working I configured them to mimic the timing and pulse width of the original ecu at idle speed.

It does not measure air flow or air temp at all. Everything is completely static at this stage (injector pulse width, ignition advance), but it's enough to get the car started and run at idle....kinda. I had some success last night running for about 20 seconds before it stalls. By tweaking a few things I got it to idle for a little longer, but it's still not stable.

Pretty excited to get this far and discovered a few things to improve. 1. If the rpm increases rapidly, then an injector pulse or spark event can be skipped. 2. Cam sensor pulse does not exactly correspond to expected piston position. There is an adjustment, but there may be more to it that i'm missing.

Saturday, June 1, 2013

Reading the Cam Angle - Part 2 (Filtering MX5 cam sensor signals)

About a week ago I was successful in properly firing the ignition coils based on the cam sensor signal.

Up until then I could fire a coil by sending a command directly to the micro and could also determine cam position based on the square wave signals from the cam sensor. The next step was bringing the two together and use the cam signal as a trigger for firing the coils. Unfortunately, because the cam sensor is located close to the ignition coils, the signal it was sending would be altered by the interference generated by the coils. This resulted in out of order firing and sometimes repeated firing even when the cam sensor wasn't turning!

To filter out the interference I did 2 things. The first was to use a low pass filter circuit and the second was to set up a voltage comparator with a 2.5v reference (half the supply voltage).

The low pass filter circuit will allow low frequency signals to pass through relatively unchanged, while high frequency signals are attenuated. Because the noise generated by the interference consisted of short duration pulses (can be thought of as high frequency) compared to the longer pulses of the cam sensor signal, a low pass filter of the correct value should remove/decrease only the parts that I don't want to measure. 

Low pass filter pic stolen from wikipedia :)

Imagine a high frequency signal on the Vin input. The capacitor will be charged to that voltage, but will take some time (depending on the resistor and capacitor values) to match the input voltage. If the signal is oscillating between -10v and +10v very quickly, then the slow charging capacitor may only get up to +2v before the input switches to -10v, then the cap tries to match that new input... but again may only get as far as -2v before the input switches back to +10v. In this way rapidly changing voltages (interference) are a much smaller part of the signal. You can also see how if a signal changes slowly (the part we want to measure), then the capacitor will have time to charge up to a matching voltage and the output will be nearly the same as the input.

To choose the right resistor and capacitor values you can use the formula 1/(2𝝿RC) to get your cutoff frequency in Hz. The "R" part is your resistor value and the "C" part is your capacitor value. The higher the frequency, the more it will be attenuated. The cutoff frequency is defined as the frequency where a signal is attenuated to 70.7%. If the cutoff value chosen is far enough lower than the interference signal, then it can be removed almost completely.
In the picture above the red shows the original signal and the blue shows the filtered signal. I'm not sure why it doesn't return exactly to the voltage before the interference spike, maybe the output needs a little more current draw to bring the voltage down, or maybe I had something setup wrong. Still, you can see that filtered signal is much cleaner.

At this point I had much more reliable operation, but still with the occasional glitch. What I could see was that even with the filtering I wasn't getting an ideal square wave. The top and bottom edges of the wave form weren’t always close to the ideal 0v and 5v, and could get a bit wavy. To fix this I used an opamp configured as a voltage comparator. An op amp has two inputs ("inverting" - and "non-inverting" +) and will try to amplify the difference between those two inputs as much as it can unless there is some kind of feedback. For example, if the - input is at 1.2v and the + input is at 1.3v, then the difference of 0.1v will be amplified all the way up to the maximum output voltage. Swap around those two values and the difference of -0.1v will be amplified to minimum output voltage.

In my case I have a 5v system where the square wave high(5v) and low(0v) represents two discreet logic levels. Stray too far from those values and you can't reliably interpret the signal. In my circuit I had one input connected to a 2.5v reference and the other input connected to the low pass filtered signal. Now if the input signal is anywhere above the reference, then it will be amplified as much as possible and the output voltage will be pushed to one extreme. Anywhere below the reference and the output is pushed to the other extreme. Also, by choosing which of the inputs - or + is the reference and which is the cam signal, I have the option of inverting the signal (output is opposite of input). So I could have the 2.5v reference on the + input and the signal on the - input. When the signal is low (say 1v), then difference of 1.5v will be amplified to the maximum value 5v (or just under 5v for the opamp i'm using). When it is high (say 3v) then a difference of -0.5v will be amplified to the lowest voltage which is 0v in this case.

Using these two filtering methods I was able to reliably fire the ignition coils based on the rotation of the cam sensor. I think working out how to clean up the signals has taught me a lot and helped me make a lot of progress with this project :D I guess there could be other more suitable filtering methods too. And as I learn about them I could incorporate them in this project.

Final exams are coming soon, so this project has been paused yet again. When I have time, the next step is to build up the circuitry for the injectors, fuel pump and hot wire air flow sensor. Then find a suitable, more powerful microcontroller, write up some code, and do a test run of the engine!

Edit on 2013-06-03: Added youtube video.

Sunday, May 12, 2013

Microcontrollers Are Slow

Efficient code
When writing program code for modern computers typically you need to focus on program correctness and reliability. In other words, the program shouldn't do crazy stuff and shouldn't crash. It's a good idea, but you might end up with code that uses more computer resources than it needs to. Usually that doesn't matter. Who cares if your sort method has 5 times as many move operations. Even a crappy 2GB RAM, 2GHz dual core box will take 3ms instead of 800ns. Extra 2.2 ms doesn't matter!

A microcontroller works on a different scale. The PIC16F1519 has 1KB of RAM and can run at 20MHz. Or 0.00000069GB and 0.02GHz. Frequency isn't a direct indication of processing speed, but it's still far slower than the modern CPUs i'm used to! In addition I have to be very sure the code is executed at the correct time. If I switch a coil or injector on/off at the wrong time I'll ruin the efficiency and power output and could even damage the engine!

Firstly a quick explanation of some of the terms i use for the microcontroller.
1. Interrupt: A signal that will interrupt the processor from what it is doing so that something urgent can be executed immediately. After the interrupt is done the processor can continue with what it was doing before.
2. Timer: A module that counts at a specified rate. Some can count to a higher number which makes them more precise.
3. CCP: A module that can generate an interrupt at a specified timer value.

I had initially wanted to use a method I called "action queue" where each "action" would turn on, turn off, or pulse (using a built in timer module) an injector or coil at the correct time. When each action would occur is based on the current cam position, air flow and other variables. When the action (along with its activation time) was determined, it was added to the action queue, sometimes with an activation time earlier than the existing items in the queue. Once the activation time of the soonest-to-be-activated action was reached, it would be run and the next item would be read from the queue. I thought it was clever since it provides plenty of flexibility. I was definitely wrong! Unfortunately I had exhausted the resources of the PIC16F1519 pretty quickly. In the space of less than a second I was trying to get the chip to determine the next action(s) to be run, add them to the queue, determine the next to be processed (from an unsorted list!), read the struct information with the function pointer (what to set), parameter value (how to set it) and the timer value (when to set it). THEN set the one available CCP module with the correct value so that the function is executed on time. AND have enough processing power to run any CURRENTLY executing action while also continuing secondary functions!

It was never going to work.

The main problem was that I couldn't set the single available CCP value before the intended timer value was reached. It was already to late to execute the action by the time I had set it. I potentially could have improved it by programming it entirely in assembly, but i would still exhaust the available resources when everything was implemented. I don't think the compiled C code is really inefficient anyway. I'm working on a more sensible solution that makes better use of the built in timer, CCP and Port change interrupts. This allows me to set actions to run at specific times without consuming as much of the available processing power. I'll also change to a more powerful microcontroller

He's dead, Jim
This is the device I use to program and debug the microcontrollers that I use. Unfortunately it decided to die.
Super awesome blurry photgraphy
Microchip, the company that created it, is sending out a replacement for me. I don't know how long it's going to take, so until then... Uni Studies!

Ignition coils and Electromagnetic Interference


A simplified way to think of ignition coils (and most transformers) is to imagine them like mechanical gear boxes. For example; you can have a gear ratio that increases your output torque at the cost of decreasing your output speed. In the case of transformers and coils you are trading voltage and current. Basic operation of a coil works like this:
1. 12v is applied to the primary winding for a few milliseconds. This produces a magnetic field around both the primary and secondary coils (they are really close together). An electrical current produces a magnetic field.
2. The voltage is suddenly shut off and the magnetic field collapses. The changing magnetic field will induce a voltage across the output coil. It has many more windings of wire than the input coil which means a few thousand volts to produce a really strong spark (at fewer amps compared to input).

I found three challenges while powering ignition coils. The first thing is that the coil will produce a voltage spike on the input winding that is opposite to the voltage used to power it. This is called Back EMF or Counter EMF. The problem is that the negative voltage can damage any connected electronic components or cause them to malfunction. It can be solved with the use of diodes and IGBT transistors designed specifically for powering ignition coils. Even easier is to use the existing ignition module in the car which was designed specifically to switch ignition coils and isolate the ECU providing the on/off signal from any back emf. Secondly, ignition coils require a fair bit of current which means the power supply voltage could dip low if it wasn't powerful enough. In order have a power supply that can supply enough current for the coils and provide a stable voltage for the microcontroller I used a car battery in parallel with a bench power supply at 13v.

The third problem was by far the most challenging for me. Even while subbing the back emf spike I found that the microcontroller would reset itself. I wrongly assumed that it was due to either the power supply becoming unstable or the back emf not being completely removed. I tried all kinds of filter and isolation methods on the power and signal lines of the microcontroller. In the end I realised that I could see a fluctuating voltage everywhere in the circuit, on every microcontroller pin, even those pulled direct to ground! (thanks to Maverick for letting me borrow his Rigol scope). It turns out that the high voltage spark creates strong electromagnetic interference. With the coil 1m away it was still enough to cause problems. Once I discovered the problem it was pretty easy to solve. Typically you would have some shielding in the form of a metal case. I just installed everything back into the MX5 and set my project onto passenger side floor. The metal firewall between the ignition coil and microcontroller was enough to shield from the interference. I'm now able to repeatedly fire the ignition coil without interfering with the operation of the microcontroller :)

This was an interesting learning process for me and I'm really excited to learn more about these kinds of effects. Can't wait till I can start my physics classes and continue with the next electronics classes :D

Wednesday, March 6, 2013

It begins!

..slowly

I've mostly worked out everything that needs to be done for the engine swap. How to route the radiator hoses is the trickiest problem so far.

The most exciting part is finding a MAF sensor that I can use. http://en.wikipedia.org/wiki/Mass_flow_sensor#Hot_wire_sensor_.28MAF.29 I've taken it from a '96 Ford Laser. The same sensor is also fitted to Mazda MPVs. Since they have a bigger 2.5L V6 engine, I expect the sensor to cope well with large air volumes.

Test wiring was a little scary since the wiring diagrams I found aren't 100% clear on what wire does what. Don't want to break it! I guess it's because it's aimed at diagnostics and repair. For instance it shows/says things like "terminal 1A should be 2v" or "check for continuity between x and y". Following the diagram lines doesn't give much more information. Without knowing for sure what terminals were pull-up or pull-down and what voltages they worked at, the best i could do was take an educated guess. To be safe I started by feeding small voltages through various terminals and probing the others at the same time. Keeping an eye on any sudden current dips/ spikes or burning smells/ magic smoke/ audible pops may or may not have helped avoid damage. They're more like "You broke it", than early warning signs.

I'd check to see if blowing through the sensor would cause any measurable change in voltage on the wire that was being probed. Eventually I found the right combination for the hot wire. The response was initially very slow, but became faster after blasting it with a leaf blower. I guess the dirt added some thermal mass? Or maybe it was acting like an insulator?

Either way, an ADC working on the right voltage range should be all I need to read the signal :) Now, does anyone have any suggestions on how to properly clean a MAF sensor?

Thursday, January 24, 2013

Infrared Sonic Screwdriver completed

Finally I've been able to finish this project. I've been spending time on uni work, but session 3 final exams will be coming up soon. After that I'll have more free time! I scratched up the plastic case while pulling apart and reassembling, but it functions exactly as I want. Overall I'm pretty happy with the result.

Design choices and restrictions
My choices of where to fit all the components were limited to either the handle or the cylinder near the top. The handle has more space (even with the batteries), but would be way to awkward to fit everything and required more work with excess bits of plastic and springy bits to cut off. The top cylinder piece contained the original electronics and speaker to flash LEDs and make sonic screwdriver type noises. It would be cool to keep, but there wasn't enough space for that.

After carving out all the excess bits of plastic I had just enough space for a DIP8 chip and a few other components. From my breadboard prototype I could easily use everything except for the giant 40pin microcontroller. The best uC I could find was the PIC12F1840 from Element14. There actually wasn't a lot of chips that met my requirements. Most didn't have enough memory or had crap timer resolution. I think the limiting factor was my choice of package. Seems more pins = more cool stuff and I'm not able to use anything smaller than DIP style chips.

Next, was the problem of power consumption. The uC could be set to an ultra low power mode and I could switch the IR receiver on only when needed, but that adds complexity and possibly components. To keep things simple I added a power switch. I have no idea how long the batteries will last though!

For input I have just 1 button and for output there is the status and infrared LEDs. Since both playback and recording mode need to be accessible from the 1 button I decided that the screwdriver should be used like this:
1. Power switch on = pulse from status LED.
2. click button = pulse status LED & transmit infrared data in EEPROM.
3a. hold button = status LED rapid flash, then status LED steady on. (ready to begin recording)
3b(i). Aim a remote at the sensor and press a button. The signal will be written to EEPROM and the status LED will go out.
3b(ii). Or click the button to clear the memory & cancel recording. Status LED will go out.

I've also changed the way the infrared LED is being powered. I'm controlling both PWM and IR signal timing on the positive side instead of separating between positive and negative sides of the LED. This means I only need 1 pin from the microcontroller and it means I only need 1 transistor to avoid pulling big current directly through the microntroller pin. It's probably the way I should've done it from the start, but it didn't occur to me until later on.
Circuit diagram, but i've left out the power switch.

Playback and recording
To record the signal I'm still using the method counting the time passed each time the IR signal flips between 1 and 0. Since i'm using a 16bit timer I'm writing the data to a 200 byte SignalData array to record a maximum of 100 bit flips. After the signal ends, the data is copied from the array to the EEPROM. It would be simpler to write directly to EEPROM, but found that the write speed was too slow and the signal data would change multiple times before each byte was written.

Playback is simply a matter of iterating through all the bytes recorded in the EEPROM and copying them back to the SignalData array. Once there I can playback the signal. The playback method is similar to before, but slightly more complicated. The IR LED has to switched on/off at intervals matching the recorded signal data, but while the LED is on it has to be modulated at 38kHz. I chose to drive the LED (via NPN transistor) from the PWM output pin (pin RA5). Now I only have to turn PWM on and off with the IR timing data. I had originally thought to switch between RA5 input and output modes. That would turn the signal on in output mode and turn it off in input mode. I think it switches too slow between input and output because the transmitted signal looked wrong on my oscilloscope unless I slowed the signal timer way down. In the end I turned the timer that drove the PWM on and off. The only down side is that sometimes the end of the transmission will result in the IR LED being stuck on. I guess it depends at what point in the PWM cycle the timer is switched off. To fix this problem I still switch RA5 between input and output mode, but only once at the beginning and end.

Assembly
Not much to say here. Just a bunch of epoxy glue, hot glue, soldering and patience. I guess my soldering could be better, but soldering components directly together without overheating them is very tricky!






Video

MPLABX Project
2013-05-12 edit: There are improvements to made in the program code especially in regards to variable naming and referencing individual bits. Didn't know "PORTxbits.x" reference existed at the time! But I won't be making any changes since the compiled code would give the same functionality as what is being used now.