Saturday, October 6, 2012

HefnyCopter2 Gyro Noise Filtering


HefnyCopter


HefnyCopter is a firmware specially designed for KK boards. There are two types of this firmware.

HefnyCopter


                This version targets the green and blue KK boards from HobbyKing. The main feature of this version is that it allows awitching your Quadcopter from X to PLUS mode without the need to reorient the board. The main concept of this feature explained in this topic “quadcopter-stabilization-control-system”.
                The hex & sourcecode is available here . Please make sure that you read the manual well before flying as POTS functions are not the same as the original firmware of KK boards.

HefnyCopter2


                This is a complete new version and goes in parallel with HefnyCopter, i.e. there are still updates on HefnyCopter so they are two parallel tracks.
                This firmware targets KK2 board. The board has LCD, Accelerometers, and Gyro. As well as a beter processor ATMega324. This means you have better capabilities here to help you to build a better firmware.
                HefnyCopter2 still has the feature of mode switching from X to Plus. But now you can connect your board via XBEE to your PC and receive telemetry data, you can work better on you stabilization algorithm and enhance your quadcopter stability.
                HefnyCopter2 also has the feature of supporting two TX, that means you can fly it with a friend and train him/her how to fly the quad.


How HefnyCopter2 Helps to Improve Stabilization



Because of the ability of sending data to PC using XBEE, I was able to visualize the data and try different approaches to filter out the noise.




The Windows application that connects to XBEE has nice GUI interface for realtime display, however the more useful part is the CSV file that it dumps for the received data. In the below figure we can see the received data. Columns in order are:

Gyro_X, Gyro_Y, GyroX, Acc_X, Acc_Y, Acc_Z, M1, M2, M3, M4
Raw Data as received from XBEE in csv format
Raw Data as received from XBEE in csv format

Please note that Acc_X represent the pitch so it goes with Gyro_Y, also Gyro_Y increase when quadcopter rotates forward, while Acc_X increase while quadcopter rotates backward. i.e. Acc_X is related to Gyro_Y and with negative sign. Same applied for Acc_Y & Gyro_Y.

Acc_Z represents the gravity effects that is why it is not 0, it is 100 which is equivalent to 9.8 m/s, Gyro_Z is used to detect YAW so it has nothing to do with Acc_Z.

Values ranges from 500 to -500, while motor ranges from 0 to 1000. To keep logged data at reasonable size data is only transmitted when quadcopter is armed and motors are on. that is why the minimum values of motors Ms is 100 here in the sheet as below this values quadcopter ignores TX signal and send Zero to motors.

Let now study Gyro_Y values and apply some math on it.

Gyro_Y data only
Gyro_Y Data received from XBEE

as we can see above this is Gyro_Y data, I added three more columns:

Move Avg
   
        This is a moving average column, which means each value is added to the total and then divide the total over two.
       Gyro_Y_Total = (Gyro_Y_Total + Gyro_Y ) /2

LPF

    LPF stands for Low-Pass-Filter, here i used a complementary filter to reduce noise. Complementary filter is not a complex math at all, at least if we focus on the formula not the derivation, and try to understand it from the simple view point. The formula is as follows:

     Gyro_Y_Total = (0.95 * Gyro_Y_Total) + (0.05 * Gyro_Y)

   Each time we take small part of the signal and sum it to the total. assuming that the signal has high frequency noise, so we need to smooth it by taking part of the signal and accumulate it, to avoid taking peaks that represents high frequency.

  Yes there are better ways to filter noise and at specific frequencies -as we can find in sound equalizer-, but computation is difficult and time consuming. If you are interested for more complex algorithms try Kalman Filter, or read topics about Fast Fourier Transform.

LPF2

    This is the same as LPF. I only changed the factors, and took double the amount in LPF.

     Gyro_Y_Total = (0.90 * Gyro_Y_Total) + (0.10 * Gyro_Y)


Gyro_Y under different filters
Gyro_Y under different filters


You can see clearly from above how complementary filter does remove noise from signal. There are two lines red & blue in the complementary filter the one with some noise is LPF2 and the red smoother one is LPF where we take only 0.05 of the signal value.

HefnyCopter2 now implements complementary filters for Gyro_X, Gyro_Y & Gyro_Z and the result was a much smooth flying.

I am still working on studying different behaviors and values received from my quadcopter. I believe there is a much yet to be discovered -at least for me-. 


Data Analysis Files can be downloaded from here.


Please follow up with us on HefnyCopter.net

Tuesday, September 4, 2012

QuadCopter Stabilization & Control System "X & Plus Configuration"


QuadCopter Balancing & Controlling Separation


This topic discusses how to separate between balancing quadcopter and controlling it.

First let us discuss the balancing control techniques in different quadcopter modes. Quadcopter can fly in (+) configuration and (X) configuration. The only difference between these modes is where the front of the quad is.

The following diagram illustrates the difference.

Quadcopter Configuration "X & Plus"

Plus (+) Configuration Control


In this configuration the control as follows:
// Pitch Control
M1 = M1 + RX(Elevator);
M4 = M4 - RX(Elevator);
// Roll Control
M2 = M2 + RX(Aileron);
M3 = M3 - RX(Aileron);

This is very simple control and is found in all quadcopter code. Sure there are some checks here and there to avoid motor stopping or saturating.  But the flying logic is always as above. There is also a scaling factor that is used to determine the sensitivity of the sticks and a Divide factor that limits the value range.


X Configuration Control


In this configuration the control as follows:
// Pitch Control
M1 = M1 + RX(Elevator) /2;
M3 = M3 + RX(Elevator) /2;
M4 = M4 - RX(Elevator) /2;
M2 = M2 - RX(Elevator) /2;

// Roll Control 
M1 = M1 + RX(Aileron)/2;
M2 = M2 + RX(Aileron) /2;
M3 = M3 - RX(Aileron) /2;
M4 = M4 - RX(Aileron) /2;


As we can see this is the same logic, we only assumes that M1 & M3 together acts as a single virtual motor in the front, and M2 & M4 together acts as a virtual motor in the rear. Other than that it is the very same logic.
Same considerations and factors applied in the PLUS configuration go here as well.

Stabilization System


Now let us see the stabilization system. The idea is simple, if the quad is falling to the right then speed up the right motor and slow the left one with the same amount and vice versa. Also if the quad is falling down from the front arm then speed up the M1 motor and slow down  M4. The rule is written as follows.

M1 = M1 + PitchAmount  *  PitchGain
M4 = M4 -  PitchAmount  *  PitchGain
M2 = M2 + RollAmount   *   RollGain
M3 = M3  - RollAmount   *   RollGain

This is the basic rule that should be found in all quadcopter programs. The main difference between the different firmware approaches is how to calculate PitchAmount and RollAmount. This can simply be read from a Gyro sensor and multiply it by a constant factor to adjust the range, or we can use PID approach or even Kalman filter and combine Gyro with Acc sensors to get the exact degree.
The Gain factor is used to determine the sensitivity. Other checks such as trimming are common in quadcopter code.

Do we need Different Stabilization for X-Configuration?


Well in almost all quadcopter programs you will find that stabilization and control calculation follows the same concept. i.e. if we use X-Quadcopter then we add the PitchAmount to both motors M1 & M2. Also RollAmount is added to motors M1 & M3. This is valid approach but in fact it is not necessary at all, also there is a major drawback here.
It is not necessary because the stabilization control of Plus configuration can stabilize quadcopter if there is a simultaneous picth & roll forces, that means if the Control System is Plus of X the Stabilization System can stabilize both using the same PLUS configuration.
The main drawback is that Stabilization control requires the orient the control board so that its onboard gyro and acc are in the right directions, so you cannot switch between PLUS configuration to X-Configuration without reorient the board. So if we can keep the Stabilization System and only change the Control System we don’t need to reorient the board as Control System has nothing to do with the board sensors it only change the signal values sent to different motors.

HefnyCopter has implemented this idea successfully as in the below video.



Also please read my other topic about Gyro Noise Filtering.

See newest article Quadcopter Flight-Control  Framework

Tuesday, July 31, 2012

My X-525 QuadCopter

This is my second trial of building a quadcopter. The first quadcopter -is the one we can see below- was totally homemade, the arms were too thin and U-shape so motors were twisting the bars and it was unstable.

early steps in building frame

My hand-made frame
My second quadcopter uses exactly the same motors and board, I decided to use Hobby King X-525 Quadcopter Frame.


Building the body was easy it took 40 min to assemble it. and another 4 hours in mounting motors and soldering connections.

rubber to reduce vibrations
Flying results was different, QuadCopter is much stable now.

X-525 Frame with folding bars
Still I need training, below was my first trial, now I fly it much better however still a beginner.



If you are interested in building your quad, below is a complete list of items you need to buy. Building a quadcopter is an interesting task, although it does not require as high skills as building a scale RC plane or a helicopter where mechanics are too complex. Quadcopter are very easy to balance as well, they mainly depends on their control board for balancing so minor mismatch in CG is not an issue at all.


Bill of Materials:
   
       - Quadcopter Frame Hobbyking X666 Glass Fiber Quadcopter Frame 666mm
   
       - Propellers you need two propellers CW & two propellers CCW. I tried both GWS 9x5 CW & CCW as well as SF 10x45 CW & CCW. if you are a newbie here, then you need to get at least extra 4 propeller of each type i.e. CW & CCW as you should expect that the first thing you will break here are propellers.

       - 4 x Brushless FC28-22

       - 4 x ESC HobbyKing 20A BlueSeries Brushless Speed Controller or Mystery 30A BEC Brushless Speed Controller (Blue Series)

       - Hobby King Multi-Rotor Control Board V3.0 (Atmega 328 PA) or the newer Hobbyking KK2.0 Multi-rotor LCD Flight Control Board with LCD and additional accelerometer sensors.

       - 1 x LIPO Battery Turnigy 2200mAh 3S 40C Lipo Pack . The quad flies for +10 min.

      - 1 x Receiver 6CH such as OrangeRx R610 Spektrum DSM2 6Ch 2.4Ghz Receiver (w/ Sat Port) it is fully compatible with Spektrum & JR yet it costs lest that 1/10 of the price of original receivers.



Other Misc. Stuff are:

       - 1 x 2mm Gold Connectors 10 pairs (20pc) These connectors are slim and very suitable for your FC 28-22 wires. You will need them to connect motor to ESC.

       - 1 x 10CM Male to Male Servo Lead (JR) 26AWG (10pcs/set) the set contains 10 pieces which are enough for the quadcopter . . . actually you will need four or five based on the control board you choose.

       - 1 x Hobby King Quadcopter Power Distribution Board This links your power wires from ESCs together to connect them to the battery. You can also use Single Male to 4 x 3.5mm Female adapter instead based on your quadcopter size.

      - 1 x Turnigy 5mm Heat Shrink Tube - BLACK (1mtr) This is important and easy to use as well. It keeps your connections tight and clean. you need a simple air drier to shrink them. It is great to isolate connectors and keep them together.

     - 1 x HobbyKing Programming card for BlueSeries Brushless Speed Controller This is important to avoid the hassle of programming your ESC using transmitters and magical beeps. The quad has four ESC, you need easy and accurate programming tool to make your life easier.
 
Extra Stuff:

      - 1 x On Board Lipoly Low Voltage Alarm (2s~4s) actually I would not say it is all extra, it is important if you plan to fly your quad high. . . you need an alarm to tell you that your battery is about to finish and you need to bring your quad down before it gets down by itself :)

    - LEDs exists in different types  RED - GREEN - BLUE they are great if you plan to fly quad at night



HefnyCopter Code

  The video below show my quad flying using my own Hefny Copter Firmware. The new feature in this firmware is that your ability to switch between X-Quad configuration and + Quad Configuration using your remote TX only. no need to reorient the board or recompile the code.





Wednesday, July 18, 2012

N-Channel Switch


What is N-Channel Switch



N-Channel Switch is a hardware device that is very much like multiplexer that takes input from two sources and based on a select signal it outputs on of these two signals.

General Function


Domains of Application

     This is a general purpose device, so it can be used in different fields; however it was specifically designed to target RC planes. 

     In UAV planes you may need to take-off and land using your own skills not autonomously using plane computer. You can easily and instantly take control from your plan. Same need when testing your UAV you need to make sure that if something went wrong you are still able to get it back again. The idea here is that this switch is completely isolated from your complex UAV logic, so if main processor hangs due to software bug you can still access this switch and get your plan back.

Another use of this switch is when trainer a new pilot on RC Planes. You no longer need to have the same type of transmitters; you can even use your FM together with his 2.4 GHz in a master slave combination.  All you need to do is to put two receivers on that plane, connect them to the switch and you can start your training session safely.


Features and Specification


  • Takes Throttle, Elevator, Rudder & Aileron input from two sources. This can be two receivers or a receiver and a UAV board.
  • Use PWM signal to switch from first input to the second. You can use gear, Aux1, Aux2 …etc. to make switching.
  • Two digital output ports can be used to turn lights or other devices on and off by using a control signal.
  • Failsafe Switching. In case one source stops sending valid data the switch automatically switches to the other source.



Pin Assignment

Input Ports

Output Ports

Circuit Diagram


Circuit Diagram

Source Code & Hex

HEX file is here
Also Source Code is here

Wednesday, June 20, 2012

Handmade Arduino

When I first read about Arduino, I liked how compact it is, and how much processing power I can get. Also I loved the library and the idea of standard pins. I decided to buy a Mini-Arduino to play with. It costs me 160 L.E. in Egypt, that is about 27 USD. I decided to buy an ATMega 328 chip and try to imitate the board. I bought a crystal and couple of ceramic capacitors and some resistors, and started to build up an identical circuit.


Circuit Diagram using Fritzing 






My Actual Circuit identical to the diagram


 [extra wires you can see connected to Arduino are for AVR programming]



The end result is using ATMEGA-326 alone with nothing connecting to it except a resistor in the reset pin.

to make sure that they run at the same speed I developed a simple application that blinks the LED on a certain rate, and I uploaded the application to both chips using AVR. LEDs started to blink on the same rate.


Application Source Code



The good side is that the handmade version costed me only 40 L.E. which 6.67 USD only.
Application can be downloaded from here.

Monday, June 11, 2012

What is Inside Spektrum DX7 Receiver


In this document I try to go through my findings and my expectation of board structure and behavior. 




Overview
My Tiger Trainer plane was completely destroyed in a crash. Even the muffler and the receiver were destroyed. I took this as an opportunity to know what is inside Receiver DX7, and how much is it different than much cheaper receivers such as Orange Receiver.



Boards StructureAfter some investigation I came out with the following outputs:
1- The receiver of DX7 consists of two main boards attached to each other as in figure 1

Figure 1: DX7 Receiver Board

The two boards are:

a. The smaller upper board is the actual receiving board and has two antenna attached to it – one of them was detached in my plane crash-.
b. The bottom bigger board is the main board. It takes receiver output and decodes it and sends output signals through output pins to servos.




Figure 2: Block diagram of the main board of DX7


Things to notice on this board:


1- The first thing I noticed is that the satellite external circuit is identical to the receiver circuit –smaller upper board- inside the DX7 body. To prove that I took the receiver out and solder satellite circuit wires to pins where the receiver board was connected. When I turned on the receiver and the transmitter they could connect to each other.


2- There is three receiver points on this board. One in the middle where the main board connects, one on the left where the satellite socket exists, and another one on the right that is active but cannot be used unless you take the cover and solder your wires there.

I expect this will give extra coverage because you will be able to a receiver board for each dimension and you will cover reliably 3D, so whatever the position of your plane the link will be very reliable.


Figure 3: Main Board, and pin assignment of each receiver.



Figure #3 shows how receivers are connected to CY8C27443 microcontroller. 


Datasheet for this microcontroller can be found here 


Output Pins:

Throttle:   Pin 26     P0[6]

Aileron: Pin 4 -    P0[1]
Elevator: Pin 1 -    P0[7]
Rudder: Pin 24 -  P0[0]
Gear: Pin 25 -  P0[2]
Aux 1: Pin 3 -    P0[3]
Aux 2:      Pin 6 –   P2[5] 

What is can we conclude here is that the microcontroller receive signals from multiple receivers and determine which signal is valid. Then it simply converts serial data to PWM signal for each output. I found interesting topic here discussing details of this point by connecting satellite to Arduino http://www.dogfight.no/2011/01/spectrum-receiver-satellite-to-arduino.html


Orange Receiver


Although Orange receiver looks much more simple than DX7, but infact we can see many commons here. There is only two ICs the receiver, we should expect three here, but it seems that the second IC performs the decoding function as well, because at the end of the day they are microcontrollers and it is a matter of software updates.


The regulator is another difference, we have only one capacitor and a small regulator which gives an indication of the maximum current they can stand.



Another interested part I noticed is the signals that you receive from channels. Signals are originally send and received in series, then these signals are demultiplexed into their original channels. This means we never get any overlap between any two PWM signals   if we measure them from RX output channels. Channels can never overlap.
Throttle - ALI"green-red"
The above figure shows how signals Throttle and ALI are adjacent signals, but they never over lap, as we can see in the next figure
No Signal Overlapping
The same characteristics appears between ELE & Rudder signals, if you choose THR & ELE or rudder you will notice that the distance between these to signals are far.


If you are interested in more details about signal handling in your firmware and how to protect your quadcopter from losing TX signal while armed please check this link Receiver Handling and Signal Lost Detection Explained


Saturday, April 7, 2012

Controlling your RC Plane from Computer using SoundCard


Controlling your RC Plane using your PC is surprisingly easy

The main idea is that RC Transmitter generates audio signals that are carried by the Radio signals using FM or spread spectrum. The receiver decode these signals and output audio signals PMM that servo understands and moves accordingly.

This link explains every thing in good details.

However the sample is for Linux, another person who helped to develop a window version in this link


What is New ?


Well the C# code above reads from a joystick and generates an audio signal. My application is basically depends on the SoundPlay.cs but allows you to control your plane from the screen directly.

I tested it on my Spektrum DX7, and an Orange RX and it worked great.

Main Things to Remember:

1- Remember to set your voice setting to 192000 Hz in your Windows settings as in the image below.
2- Set your TX to in the Trainer menu P-Link.
3- Change Windows sound volume till DX7 detects that it is connected to another Remote.


Then you can enjoy controlling your RC



You can download source code from here

and binaries



Thanks for the guys who wrote the original linux and windows version.

Sunday, March 25, 2012

Quadrocopter Firmware (4 Rotor) Explained

I just downloaded the code for my QuadCopter with hobbyking board from http://www.kkmulticopter.com .
I went through the code, and tried to understand each line as this is my first experience with ATmega micro controllers.

I simply downloaded a data sheet for ATmega here and went through the code line by line, it took me a while but I could understand it, it is not that complex.

The code is the same as the original I only added comments -alot of comments - so that the reader can understand what happens. a minor change i did is adding a function FlashLEDnTimes that is a replacement of code in many places.

If you need to know what items exactly you need to build a quadcopter please check here

ADC routine after adding comments.
ADC Routine Description


I also reviewed the board looking for extensions that we can connect  extra sensors to hobbyking board and I found the following ports are free:
                  Port B:  PB3,PB4, PB5.
                  Port D: PD0, PD4, PD5, PD6

Below is the complete code:
;-*- Quadrocopter (Quadrotor) Controller V4.5 -*-
;-*-     All Code And Hardware Design         -*-
;-*-      By Rolf R Bakke,  April, juli 2010  -*-

; (I have not peeked in others projects or code :-)

; Code is best viewed in monospace font and tab = 8


; Added gyro direction reversing.

; Added calibration delay.

; Added potmeter reversing

; Added stick scaling

; Added Yaw command limiting

; Changed gain pot scaling

; Added arming/disarming




;   View from above

;      Forward

;       M1,CW
;         *
;         |
; M2,CCW  |   M3,CCW
;   *-----+-----*
;         |
;         |
;         *
;       M4,CW





;******************* SETTINGS **************************

; This one determines the range of the gain potmeters.
; Reducing the value by one doubles the range.
; Increasing the value by one halves the range.

.equ    PotScaleRoll    = 10
.equ    PotScalePitch    = 10
.equ    PotScaleYaw    = 10


; This one determines the stick sensitivity.
; Reducing the value by one doubles the sensitivity.
; Increasing the value by one halves the sensitivity.

.equ    StickScaleRoll    = 11
.equ    StickScalePitch    = 11
.equ    StickScaleYaw    = 11


; This one determines the maximum Yaw command applied, in percent.
; Less gives less yaw autority, but also less possibility of motor saturation.
; More gives more yaw autority, but also more possibility of motor saturation during full rudder stick.

.equ    YawLimit    = 30


; This one should be set to the chip you are using.
; Atmega48  = "m48def.inc"
; Atmega88  = "m88def.inc"
; Atmega168 = "m168def.inc"
; Atmega328 = "m328def.inc"

.include "m48def.inc"    

;*******************************************************
    


;---  16.16 bit signed registers ---

.equ    Temp                =0

.equ    RxInRoll            =1
.equ    RxInPitch            =2
.equ    RxInYaw                =3
.equ    RxInCollective        =4

.equ    RxChannel            =5

.equ    GyroZeroRoll        =9
.equ    GyroZeroPitch        =10
.equ    GyroZeroYaw            =11

.equ    MotorOut1            =12
.equ    MotorOut2            =13
.equ    MotorOut3            =14
.equ    MotorOut4            =15

.equ    GyroRoll            =17
.equ    GyroPitch            =18
.equ    GyroYaw                =19

.equ    GainInRoll            =26
.equ    GainInPitch            =27
.equ    GainInYaw            =28



.equ    B16_RegAdd=0x0100    ;base address for the math library register array

.equ    B16_WorkAdd=0x0200    ;base address for the math library work area


.def    RxChannel1StartL    =r0
.def    RxChannel1StartH    =r1

.def    RxChannel2StartL    =r2
.def    RxChannel2StartH    =r3

.def    RxChannel3StartL    =r4
.def    RxChannel3StartH    =r5
    
.def    RxChannel4StartL    =r6
.def    RxChannel4StartH    =r7

.def    RxChannel1L            =r8
.def    RxChannel1H            =r9

.def    RxChannel2L            =r10
.def    RxChannel2H            =r11

.def    RxChannel3L            =r12
.def    RxChannel3H            =r13

.def    RxChannel4L            =r14
.def    RxChannel4H            =r15



.equ     FlagGyroCalibrated    =b16_workadd+24

.equ    RollGyroDirection    =b16_workadd+26
.equ    PitchGyroDirection    =b16_workadd+27
.equ    YawGyroDirection    =b16_workadd+28

.equ    CalibrationDelayCounter    =b16_workadd+29

.equ    PotReverser            =b16_workadd+30

.equ    FlagFcArmed            =b16_workadd+31
.equ    CounterFcArm        =b16_workadd+32
.equ    CounterFcDisarm        =b16_workadd+33

.equ    FlagCollectiveZero    =b16_workadd+34



.def    t=r16                ;Main temporary register

;r16,r17,r18,r19 is destroyed by the math lib


.def    counterl                =r20
.def    counterh                =r21

.def    RxChannelsUpdatingFlag    =r22    ;ISR (do not read channels while this is true) 

.def    tisp                    =r23            ;ISR temporary register

                    ;the registers marked ISR is not to be used for any other purpose 

    


.macro led0_on            ;macros for the LED's
    sbi portb,6
.endmacro
.macro led0_off
    cbi portb,6
.endmacro
.macro led2_on
    sbi portd,5
.endmacro
.macro led2_off
    cbi portd,5
.endmacro
.macro led3_on
    sbi portd,6
.endmacro
.macro led3_off
    cbi portd,6
.endmacro


; Motor-Out Ports/Pins
#define motor_out_pin_1 portb,2    ;motor output pin assignments
#define motor_out_pin_2 portb,1
#define motor_out_pin_3 portb,0
#define motor_out_pin_4 portd,7


.include "1616mathlib_ram_macros_v1.asm"

.org 0

; ------------------------------------- Interrupt Table
#if defined(__ATmega48__) || defined(__ATmega88__)
#message "rjmp"
    rjmp reset
    rjmp RxChannel2
    rjmp RxChannel3
    rjmp RxChannel4
    rjmp unused
    rjmp RxChannel1
    rjmp unused
    rjmp unused
    rjmp unused
    rjmp unused
    rjmp unused
    rjmp unused
    rjmp unused
    rjmp unused
    rjmp unused
    rjmp unused
    rjmp unused
    rjmp unused
    rjmp unused
    rjmp unused
    rjmp unused
    rjmp unused
    rjmp unused
    rjmp unused
    rjmp unused
    rjmp unused

#elif defined(__ATmega168__) || defined(__ATmega328__)
#message "jmp"
    jmp reset
    jmp RxChannel2        ;IRQ 0 Handler
    jmp RxChannel3        ;IRQ 1 Handler
    jmp RxChannel4        ;PCINT0 Handler: will be triggered if any enabled PCINT07..0 pin toggles.
    jmp unused            ;PCINT1 Handler: will be triggered if any enabled PCINT14..8 pin toggles.
    jmp RxChannel1        ;PCINT2 Handler: will be triggered if any enabled PCINT23..16 pin toggles.
    jmp unused
    jmp unused
    jmp unused
    jmp unused
    jmp unused
    jmp unused
    jmp unused
    jmp unused
    jmp unused
    jmp unused
    jmp unused
    jmp unused
    jmp unused
    jmp unused
    jmp unused
    jmp unused
    jmp unused
    jmp unused
    jmp unused
    jmp unused
#else
#error "Unsupported part:" __PART_NAME__
#endif
; ------------------------------------- Interrupt Table END

; ------------------------------------- Interrupt Handling Functions
; -- Handle unused Intrrupts
unused:    
    reti

; -- Handle Reset Intrrupt    
reset:    
    ; -- Initialize Stack Pointer
    ldi t,low(ramend)    ;initalize stack pointer
    out spl,t
    ldi t,high(ramend)
    out sph,t


    ; ------------ Setup IO
    ;
    ; ------- Port B
    ; Motor 3    :    PB0-w
    ; Motor 2    :    PB1-w
    ; Motor 1    :    PB2-w
    ; LED        :    PB6-w
    ; Rudder    :    PB7-r
    ;       76543210
    ldi t,0b01111111
    out ddrb,t 
    
    
    ; ------- Port C
    ; Gyro-Yaw    :    PC0-r ADC
    ; Gyro-Pitch:    PC1-r ADC        
    ; Gyro-Roll    :    PC2-r ADC
    ; Gain-Roll    :    PC3-r ADC
    ; Gain-Pitch:    PC4-r ADC
    ; Gain-Yaw    :    PC5-r ADC
    ;       76543210
    ldi t,0b11000000
    out ddrc,t
    
    ; ------- Port D
    ; Motor 4    :    PD7-w
    ; Roll        :     PD1-r
    ; Pitch        :     PD2-r
    ; Throttle    :     PD3-r
    ;       76543210
    ldi t,0b11110001
    out ddrd,t    
    
    
    
    
    ; ------------ EOF Setup IO

    
    ;-- Setup pin change interrupt on PD1, PD2, PD3, PB4
    ;
    
    ; Bit(0) = 1 : Pin Change Interrupt is enabled [PCINT07..0 pin will cause an interrupt.]
    ; Bit(2) = 1 : Pin Change Interrupt is enabled [PCINT23..16 pin will cause an interrupt.]
    ; PCMSK0 enables those pins individually.
    ;       76543210
    ldi t,0b00000101    ;PB7, PB1
    sts pcicr,t

    ; PCMSK0 enables those pins individually. [only PB7 makes interrupt]
    ;       76543210    
    ldi t,0b10000000    ;PB7
    sts pcmsk0,t
    
    ; PCMSK1 enables those pins individually. [only PD1 makes interrupt]
    ; please note that PD2 & PD3 is handled throuhj INT0 & INT1 directly.
    ;       76543210
    ldi t,0b00000010    ;PD1
    sts pcmsk2,t

    ; for INT0 bit [1,0]: value = 01: any logical change generate interrupt
    ; either from high to low or from low to high. This is done to detect rising and falling edges.
    ; for INT0 bit [3,2]: value = 01: any logical change generate interrupt
    ;       76543210
    ldi t,0b00000101    ;PD2, PD3
    sts eicra,t
    
    ; Enable both INT0 & INT1
    ;       76543210
    ldi t,0b00000011    ;PD2, PD3
    out eimsk,t        ;STS? OUT?  Come on!



    ;-- Setup timer1 to run at 1MHz ----
    ; bits:     7        6      5        4        3        2       1      0
    ;        ICNC1 - ICES1 - NA - WGM13 - WGM12 - CS12 - CS11 - CS10
    ; clkIO 1/8 From Prescaler
    ;       76543210
    ldi t,0b00000010
    sts tccr1b,t

    
    ;-- Initalize variables ---

    clr t
    sts FlagGyroCalibrated,t    ;FlagGyroCalibrated = false
    sts FlagFcArmed,t            ;FlagFcArmed = false

    sts CounterFcArm,t
    sts CounterFcDisarm,t
    
    clr RxChannelsUpdatingFlag
    
    ldi xl,low(1520)        ;prime the channels
    ldi xh,high(1520)

    mov RxChannel1L,xl
    mov RxChannel1H,xh
    mov RxChannel2L,xl
    mov RxChannel2H,xh
    mov RxChannel3L,xl
    mov RxChannel3H,xh
    mov RxChannel4L,xl
    mov RxChannel4H,xh


    sei ; enable interrupt

    led0_on                ;Flash LED once, I am alive!

    ldi zl,0
    rcall waitms

    led0_off

    ldi zh,100            ;2 second delay
ca6:    ldi zl,200        ;delay of waitms = zl * 0.1ms 
    rcall waitms
    dec zh
    brne ca6





;---- Gyro direction reversing ----
;---- 1: Set roll gain pot to zero.
;---- 2: Turn on flight controller.
;---- 3: LED flashes rapidly 10 times.
;---- 4: Move the stick for the gyro you want to reverse.
;---- 5: LED will blink continually.
;---- 6: Turn off flight controller.
;---- 7: If there is more gyros to be reversed, goto step 2, else set roll gain pot back. 

    rcall ReadGainPots
    rcall ReadGainPots

    b16ldi Temp, 51
    b16cmp GainInRoll,Temp
    brlt GyroDirectionReversing            ;enter gyro direction reversing?
    rjmp ca1

GyroDirectionReversing:
    ldi counterl,10            ;yes, flash led 10 times
    rcall FlashLEDnTimes




ca5:    ldi zl,180
    rcall waitms

    rcall RxGetChannels

    b16ldi Temp, 30

    b16cmp RxInRoll,Temp        ;Roll TX stick moved?
    ldi zl,0
    brge ca8

    b16cmp RxInPitch,Temp        ;Pitch TX stick moved?
    ldi zl,1
    brge ca8

    b16cmp RxInYaw,Temp            ;Yaw TX stick moved?
    ldi zl,2
    brge ca8

    b16cmp RxInCollective,Temp    ;Throttle TX stick moved?
    ldi zl,3
    brge ca8

    rjmp ca5            ;no

ca8:    ldi zh,0            ;yes, reverse direction
    rcall ReadEeprom

    ldi xl,0x80
    eor t,xl                    

    rcall WriteEeprom        ;Store gyro direction to EEPROM

ca4:    led0_on                ;flash LED
    ldi zl,0
    rcall waitms
    led0_off
    ldi zl,0
    rcall waitms
    rjmp ca4



;---- ESC Throttle range calibration. This outputs collective input to all motor outputs ---
;---- This mode is entered by turning yaw gain pot to zero and turning on the flight controller. ---
    
ca1:    b16ldi Temp, 51
    b16cmp GainInYaw,Temp
    brge Mainloop            ;enter ESC throttle range calibration?

    ldi counterl,10            ;yes, flash led 10 times
    rcall FlashLEDnTimes

ca3:    ldi zl,180
    rcall waitms

    rcall RxGetChannels

    b16mov MotorOut1,RxInCollective        ;output collective to all ESC's
    b16mov MotorOut2,RxInCollective
    b16mov MotorOut3,RxInCollective
    b16mov MotorOut4,RxInCollective
    rcall output_motor_ppm
    rjmp ca3                 ;do until the cows come home.




    ;--- Main loop ---

Mainloop:    rcall GetGyroDirections

    rcall ReadGainPots

    rcall RxGetChannels


    ;--- Arming/Disarming ---


    lds t,FlagCollectiveZero
    tst t
    breq ma80

    b16ldi Temp, -50        ;Disarm?
    b16cmp RxInYaw,Temp
    brge ma81

    lds t,CounterFcDisarm
    inc t
    sts CounterFcDisarm,t
    cpi t, 50
    brne ma82

    clr t
    sts FlagFcArmed,t
    rjmp ma80

ma81:    b16ldi Temp, 50            ;Arm?
    b16cmp RxInYaw,Temp
    brlt ma80

    lds t,CounterFcArm
    inc t
    sts CounterFcArm,t
    cpi t, 50
    brne ma82

    ser t
    sts FlagFcArmed,t

ma80:    clr t
    sts CounterFcDisarm, t
    sts CounterFcArm,t

ma82:    


    ;--- Calibrate gyro when collective below 1% ---

    lds t,FlagCollectiveZero
    tst t
    brne ma12                    ;collective below 1% ?
    rjmp ma4

ma12:    lds t,CalibrationDelayCounter            ;yes, increase delay counter
    inc t
    breq ma50                    ;delay reached 256?
    
    sts CalibrationDelayCounter,t            ;no, skip calibration.
    rjmp ma51

ma50:    b16clr GyroZeroRoll                ;yes, set gyro zero value (average of 16 readings)
    b16clr GyroZeroPitch
    b16clr GyroZeroYaw

    ldi counterl,16

ma20:    rcall ReadGyros

    b16add GyroZeroRoll, GyroRoll
    b16add GyroZeroPitch, GyroPitch
    b16add GyroZeroYaw, GyroYaw

    dec counterl
    brne ma20

    b16load GyroZeroRoll                ;divide by 16
    ldi t,4
    rcall FastDivide
    b16store GyroZeroRoll

    b16load GyroZeroPitch                ;divide by 16
    ldi t,4
    rcall FastDivide
    b16store GyroZeroPitch

    b16load GyroZeroYaw                ;divide by 16
    ldi t,4
    rcall FastDivide
    b16store GyroZeroYaw

    ser t
    sts FlagGyroCalibrated,t            ;FlagGyroCalibrated = true
    
    sts CalibrationDelayCounter,t    

    rjmp ma51
            
ma4:    clr t                        ;no, skip calibration, reset calibration delay
    sts CalibrationDelayCounter,t

ma51:




    ;--- Read gyros ---


    rcall ReadGyros
    
    b16sub GyroRoll, GyroZeroRoll            ;remove offset from gyro output
    b16sub GyroPitch, GyroZeroPitch
    b16sub GyroYaw, GyroZeroYaw
 

;rcall ShowChannels
;rcall ShowGyros


    ;--- Start mixing by setting collective to motor input 1,2,3 and 4 ---

    b16mov MotorOut1,RxInCollective
    b16mov MotorOut2,RxInCollective
    b16mov MotorOut3,RxInCollective
    b16mov MotorOut4,RxInCollective



    ;--- Calculate roll command output ---

    b16load GainInRoll                ;scale gyro output
    ldi t, PotScaleRoll
    rcall FastDivide                ;  divide by 2^t
    
    lds t, RollGyroDirection
    tst t
    brpl ma60
    
    rcall NegateXY

ma60:    b16store Temp

    b16mul GyroRoll, Temp    

    
    b16load GainInRoll                ;scale stick output
    ldi t, StickScaleRoll
    rcall FastDivide                ;  divide by 2^t
    b16store Temp

    b16mul RxInRoll, Temp


    b16add RxInRoll, GyroRoll            ;add gyro output to stick output


    
    ;--- Add roll command output to motor 2 and 3 ---

    b16add MotorOut2, RxInRoll
    b16sub MotorOut3, RxInRoll



    ;--- Calculate pitch command output ---

    b16load GainInPitch                ;scale gyro output
    ldi t, PotScalePitch
    rcall FastDivide                ;  divide by 2^t
    
    lds t, PitchGyroDirection
    tst t
    brpl ma61
    
    rcall NegateXY

ma61:    b16store Temp

    b16mul GyroPitch, Temp    

    
    b16load GainInPitch                ;scale stick output
    ldi t, StickScalePitch
    rcall FastDivide                ;  divide by 2^t
    b16store Temp

    b16mul RxInPitch, Temp


    b16add RxInPitch, GyroPitch            ;add gyro output to stick output


    
    ;--- Add pitch command output to motor 1 and 4 ---

    b16add MotorOut1, RxInPitch
    b16sub MotorOut4, RxInPitch



    ;--- Calculate yaw command output ---

    b16load GainInYaw                ;scale gyro output
    ldi t, PotScaleYaw
    rcall FastDivide                ;  divide by 2^t
    
    lds t, YawGyroDirection
    tst t
    brpl ma62
    
    rcall NegateXY

ma62:    b16store Temp

    b16mul GyroYaw, Temp    

    
    b16load GainInYaw                ;scale stick output
    ldi t, StickScaleYaw
    rcall FastDivide                ;  divide by 2^t
    b16store Temp

    b16mul RxInYaw, Temp


    b16add RxInYaw, GyroYaw                ;add gyro output to stick output


    
    b16ldi Temp, -YawLimit                ;limit Yaw command to -YawLimit and YawLimit
    b16cmp RxInYaw, Temp
    brge ma90
    b16mov RxInYaw, Temp

ma90:    b16ldi Temp, Yawlimit
    b16cmp RxInYaw, Temp
    brlt ma91
    b16mov RxInYaw, Temp
ma91:

    ;--- Add yaw command output to motor 1,2,3 and 4 ---

    b16sub MotorOut1, RxInYaw
    b16add MotorOut2, RxInYaw
    b16add MotorOut3, RxInYaw
    b16sub MotorOut4, RxInYaw



    ;--- Limit the lowest value to avoid stopping of motor if motor value is under-saturated ---

    b16ldi Temp, 10       ;this is the motor idle level

    b16cmp MotorOut1, Temp
    brge ma40
    b16mov MotorOut1, Temp

ma40:    b16cmp MotorOut2, Temp
    brge ma41
    b16mov MotorOut2, Temp

ma41:    b16cmp MotorOut3, Temp
    brge ma42
    b16mov MotorOut3, Temp

ma42:    b16cmp MotorOut4, Temp
    brge ma43
    b16mov MotorOut4, Temp

ma43:


    ;---- Update Status LED ----

    lds xl, FlagGyroCalibrated        ;LED on if (FlagGyroCalibrated && FlagFcArmed) true
    lds yl, FlagFcArmed
    and xl, yl
    brne ma7
    led0_off
    rjmp OutputToMotorESC
ma7:    led0_on        


;--- Output to motor ESC's ---

OutputToMotorESC:


    lds t,FlagCollectiveZero        ;turn on motors if (FlagGyroCalibrated && FlagFcArmed && !FlagCollectiveZero) true
    com t
    and xl,t
    brne ma6

    b16ldi Temp, 0                ;set motor 1-4 to zero
    b16mov MotorOut1,Temp
    b16mov MotorOut2,Temp
    b16mov MotorOut3,Temp
    b16mov MotorOut4,Temp

ma6:    rcall output_motor_ppm            ;output ESC signal

    rjmp Mainloop


    ;--- End of main loop ---


    ;--- Subroutines ---


;--- Output motor ppm channels 1-4 in parallell --- 
;--- Input is 0 to 100 part #1
;--- Trim if greater than 100 ot less than 0.

output_motor_ppm:

    b16ldi Temp, 0            ;limit to zero
    b16cmp MotorOut1,Temp
    brge ou1
    b16mov MotorOut1,Temp
ou1:
    b16cmp MotorOut2,Temp
    brge ou2
    b16mov MotorOut2,Temp
ou2:
    b16cmp MotorOut3,Temp
    brge ou3
    b16mov MotorOut3,Temp
ou3:
    b16cmp MotorOut4,Temp
    brge ou4
    b16mov MotorOut4,Temp
ou4:

    b16ldi Temp, 100        ;limit to 100
    b16cmp MotorOut1,Temp
    brlt ou5
    b16mov MotorOut1,Temp
ou5:
    b16cmp MotorOut2,Temp
    brlt ou6
    b16mov MotorOut2,Temp
ou6:
    b16cmp MotorOut3,Temp
    brlt ou7
    b16mov MotorOut3,Temp
ou7:
    b16cmp MotorOut4,Temp
    brlt ou8
    b16mov MotorOut4,Temp
ou8:

    
    b16ldi Temp, 100        ;add 1ms to all channels
    b16add MotorOut1,Temp
    b16add MotorOut2,Temp
    b16add MotorOut3,Temp
    b16add MotorOut4,Temp


    b16load MotorOut1        ;scale from 0-200 to 0-800 (multiply by 4)
    ldi t,2
    rcall FastMultiply
    b16store MotorOut1

    b16load MotorOut2
    ldi t,2
    rcall FastMultiply
    b16store MotorOut2

    b16load MotorOut3
    ldi t,2
    rcall FastMultiply
    b16store MotorOut3

    b16load MotorOut4
    ldi t,2
    rcall FastMultiply
    b16store MotorOut4


    b16load MotorOut4        ;transfer to 16bit counters
    push xl
    push xh
    b16load MotorOut3        
    push xl
    push xh
    b16load MotorOut2        
    push xl
    push xh
    b16load MotorOut1        
    movw r25:r24,xh:xl

    pop r27
    pop r26

    pop r29
    pop r28

    pop r31
    pop r30

    ;--- Output Signals to ESC actual part #2
    ;--- Send "1s"  to all ESC.
    sbi motor_out_pin_1        ;turn on pins
    sbi motor_out_pin_2
    sbi motor_out_pin_3
    sbi motor_out_pin_4

    clr t
    ;--- This is the total length of  square pulse sent to ESC.
    ldi counterl,low(801)
    ldi counterh,high(801)

    ;--- Output Signals to ESC actual part #3
    ;--- This is the monitor and control part: it keep watching the length of the "on" signal for each motor.
    ;--- The main logic is that there is a big loop that includes all motors.
    ;--- All pins are on, and each pin "motor" has its own Registry [R25,R26,R27,R28,R29,R30,R31] that 
    ;--- defines the positive duration of the pulse "duty cycle".

lbl_MotorOutPin1:    
    subi r24,1                ;20 cycles (1ms = 400 counts)
    sbc r25,t
    brcc lbl_MotorOutPin2    ; Have we reached the end of the "on" part; no then check the next ESC.
    cbi motor_out_pin_1        ; if yes then output "0".

lbl_MotorOutPin2:    
    subi r26,1
    sbc r27,t
    brcc lbl_MotorOutPin3
    cbi motor_out_pin_2
lbl_MotorOutPin3:
    subi r28,1
    sbc r29,t
    brcc lbl_MotorOutPin4
    cbi motor_out_pin_3
lbl_MotorOutPin4:    
    subi r30,1
    sbc r31,t
    brcc lbl_MotorOutPin5
    cbi motor_out_pin_4
lbl_MotorOutPin5:    
    subi counterl,1
    sbc counterh,t
    brcc lbl_MotorOutPin1    ; hefny:L continue go to first motor.

    ret    





;--- function: Read ADC's ---
ReadGyros:
    ; Digital Input Disable Registers (DIDR)
    ; To reduce power by diable Digital Inpurt Register.
    ; Correspondent digital pins will read Zero when diable DIR.
    ; Bits:  7 - 6  -   5    -    4    -    3    -    2    -    1    -    0
    ;        NA - NA - ADC5D - ADC4D - ADC3D - ADC2D - ADC1D - ADC0D
    ;       76543210
    ldi t,0b00111111
    sts didr0,t ; Digital Input Disable Registers (DIDR)

    ; ADCSRB: ADC Control & Status Register B
    ; Enable ADC0
    ; Bits:        7  -   6  -  5 - 4 -  3  -   2   -   1   -   0
    ;            NA - ACME - NA - NA - NA - ADTS2 - ADTS1 - ADTS0
    ;     ACME [0]: select the negative input of the Analog Comparator.
    ;    ADTS2 - ADTS1 - ADTS0 [0,0,0]: means Free Running Mode.
    ;                                 however [ADATE] is clearned so they have no effect here.
    ;       76543210
    ldi t,0b00000000
    sts adcsrb,t

    ; Enable ADC2
    ;       76543210    ;read roll
    ldi t,0b00000010
    rcall read_adc
    b16store GyroRoll
    
    ; Enable ADC1
    ;       76543210    ;read pitch
    ldi t,0b00000001
    rcall read_adc
    b16store GyroPitch

    ; Enable ADC0
    ;       76543210    ;read yaw
    ldi t,0b00000000
    rcall read_adc
    b16store GyroYaw

    ret



ReadGainPots:

    ldi zl,3        ;get PotReverser from EEPROM
    ldi zh,0
    rcall ReadEeprom
    sts PotReverser,t


    ; Digital Input Disable Registers (DIDR)
    ; To reduce power by diable Digital Inpurt Register.
    ; Correspondent digital pins will read Zero when diable DIR.
    ; Bits:  7 - 6  -   5    -    4    -    3    -    2    -    1    -    0
    ;        NA - NA - ADC5D - ADC4D - ADC3D - ADC2D - ADC1D - ADC0D
    ;       76543210
    ldi t,0b00111111
    sts didr0,t

    ; ADCSRB: ADC Control & Status Register B
    ; Enable ADC0
    ; Bits:        7  -   6  -  5 - 4 -  3  -   2   -   1   -   0
    ;            NA - ACME - NA - NA - NA - ADTS2 - ADTS1 - ADTS0
    ;     ACME [0]: select the negative input of the Analog Comparator.
    ;    ADTS2 - ADTS1 - ADTS0 [0,0,0]: means Free Running Mode.
    ;                                 however [ADATE] is clearned so they have no effect here.
    ;       76543210
    ldi t,0b00000000
    sts adcsrb,t


    ; Enable ADC3
    ;       76543210    ;read roll gain
    ldi t,0b00000011
    rcall read_adc

    lds t,PotReverser
    tst t
    brmi ga1
    rcall invert
ga1:    b16store GainInRoll

    ;       76543210    ;read pitch gain
    ldi t,0b00000100
    rcall read_adc

    lds t,PotReverser
    tst t
    brmi ga2
    rcall invert
ga2:    b16store GainInPitch

    ;       76543210    ;read yaw gain
    ldi t,0b00000101
    rcall read_adc

    lds t,PotReverser
    tst t
    brmi ga3
    rcall invert
ga3:    b16store GainInYaw
    
    ret

invert:    ldi t,0x03        ;Invert X
    eor xh,t
    ldi t,0xff
    eor xl,t
    ret


read_adc:            ;x = adc    y = 0
    ; ADMUX: ADC Multiplexer Selection Register.
    ; Bits:
    ;        REFS1 - REFS0 - ADLAR - NA - MUX3 - MUX2 - MUX1 - MUX0
    ; for REFS1 - REFS0 [0,0]: means use AREF, Internal Vref & AVCC both are turned off.
    ; MUX2..0: selects ADC number.
    sts admux,t        ;set ADC channel
    
    ; ADCSRA: ADC Control & Status Register.
    ; Bits:
    ;    ADEN - ADSC - ADATE - ADIF - ADIE - ADPES2 - ADPS1 - ADPS0
    ;   ADCEnable : on
    ;    ADSC: on - start reading . if done with ADEN then it takes 25 cycle instead of 13 
    ;          and first conversion pefromes initialization of the ADC.
    ;    ADATE "auto trigger enables" : off 
    ;   ADIF "ADC interrupt Flag" : off
    ;   ADIE "ADC Interrupt Enable" : off
    ;   ADPS2..0: "ADC Prescale Select Bits" : division factor = 64.
    ;       76543210
    ldi t,0b11000110    ;start ADC
    sts adcsra,t

re1:    
    ; ADSC turns back to zero when reading is finished.
    ; Setting ADEN to zero while this loop will terminate the conversion process.
    lds t,adcsra        ;wait for ADC to complete.
    sbrc t,6            ;is ADSC is ZERO
    rjmp re1

    lds xl,adcl        ;read ADC Data High & Low ADC Data Register.
    lds xh,adch

    clr yl
    clr yh

    ret





;--- function: wait for n ms.
;--- wait time = zl * 0.1 ms
waitms:            ;wait zl * 0.1ms
    ldi t,199
wa1:    nop
    dec t
    brne wa1
    dec zl
    brne waitms
    ret





    ;--- Read RX channels 1-4, pin change interrupt driven ---
    ;these have to be fast as possible to minimize jitter in loop timed events

RxChannel1:
;led2_on
    ser RxChannelsUpdatingFlag

    in tisp, sreg

    sbis pind,1                ;rising or falling?
    rjmp rx1m1


    lds RxChannel1StartL, tcnt1l        ;rising, store the start value
    lds RxChannel1StartH, tcnt1h

    clr RxChannelsUpdatingFlag

    out sreg,tisp                ;exit    
;led2_off
    reti


rx1m1:    lds RxChannel1L, tcnt1l            ;falling, calculate the pulse length
    lds RxChannel1H, tcnt1h

    sub RxChannel1L, RxChannel1StartL
    sbc RxChannel1H, RxChannel1StartH

    clr RxChannelsUpdatingFlag

    out sreg,tisp                ;exit    
;led2_off
    reti


RxChannel2:
;led2_on
    ser RxChannelsUpdatingFlag

    in tisp, sreg

    sbis pind,2                ;rising or falling?
    rjmp rx2m1


    lds RxChannel2StartL, tcnt1l        ;rising, store the start value
    lds RxChannel2StartH, tcnt1h

    clr RxChannelsUpdatingFlag

    out sreg,tisp                ;exit    
;led2_off
    reti


rx2m1:    lds RxChannel2L, tcnt1l            ;falling, calculate the pulse length
    lds RxChannel2H, tcnt1h

    sub RxChannel2L, RxChannel2StartL
    sbc RxChannel2H, RxChannel2StartH

    clr RxChannelsUpdatingFlag

    out sreg,tisp                ;exit    
;led2_off
    reti



RxChannel3:
;led2_on
    ser RxChannelsUpdatingFlag

    in tisp, sreg

    sbis pind,3                ;rising or falling?
    rjmp rx3m1


    lds RxChannel3StartL, tcnt1l        ;rising, store the start value
    lds RxChannel3StartH, tcnt1h

    clr RxChannelsUpdatingFlag

    out sreg,tisp                ;exit    
;led2_off
    reti


rx3m1:    lds RxChannel3L, tcnt1l            ;falling, calculate the pulse length
    lds RxChannel3H, tcnt1h

    sub RxChannel3L, RxChannel3StartL
    sbc RxChannel3H, RxChannel3StartH

    clr RxChannelsUpdatingFlag

    out sreg,tisp                ;exit    
;led2_off
    reti



RxChannel4:
;led2_on
    ser RxChannelsUpdatingFlag

    in tisp, sreg

    sbis pinb,7                ;rising or falling?
    rjmp rx4m1


    lds RxChannel4StartL, tcnt1l        ;rising, store the start value
    lds RxChannel4StartH, tcnt1h

    clr RxChannelsUpdatingFlag

    out sreg,tisp                ;exit    
;led2_off
    reti

; ------------------------------------- EOF Interrupt Handling Functions


rx4m1:    lds RxChannel4L, tcnt1l            ;falling, calculate the pulse length
    lds RxChannel4H, tcnt1h

    sub RxChannel4L, RxChannel4StartL
    sbc RxChannel4H, RxChannel4StartH

    clr RxChannelsUpdatingFlag

    out sreg,tisp                ;exit    
;led2_off
    reti




    
    ;--- Get and scale RX channel inputs ---


RxGetChannels:
    tst RxChannelsUpdatingFlag    ;channel 1 (Roll)    
    brne RxGetChannels

    mov xl,RxChannel1L
    mov xh,RxChannel1H

    rcall XAbs

    clr yl
    clr yh
    b16store RxChannel

    rcall ScaleMinus100To100

    b16mov RxInRoll,RxChannel



c2:    tst RxChannelsUpdatingFlag    ;channel 2 (Pitch)    
    brne c2

    mov xl,RxChannel2L
    mov xh,RxChannel2H

    rcall XAbs

    clr yl
    clr yh
    b16store RxChannel

    rcall ScaleMinus100To100

    b16mov RxInPitch,RxChannel



c3:    tst RxChannelsUpdatingFlag    ;channel 3 (Collective)    
    brne c3

    mov xl,RxChannel3L
    mov xh,RxChannel3H

    rcall XAbs

    clr yl
    clr yh
    b16store RxChannel

    rcall Scale0To100

    b16mov RxInCollective,RxChannel



c4:    tst RxChannelsUpdatingFlag    ;channel 4 (Yaw)    
    brne c4

    mov xl,RxChannel4L
    mov xh,RxChannel4H

    rcall XAbs

    clr yl
    clr yh
    b16store RxChannel

    rcall ScaleMinus100To100

    b16mov RxInYaw,RxChannel

    

    clr t                ;Set FlagCollectiveZero true if collective is < 1
    sts FlagCollectiveZero,t

    b16ldi Temp, 1
    b16cmp RxInCollective,Temp
    brge c5

    ser t
    sts FlagCollectiveZero,t

c5:    ret



XAbs:    tst xh        ;X = ABS(X)
    brpl xa1

    ldi t,0xff
    eor xl,t
    eor xh,t
    
    ldi t,1
    add xl,t
    clr t
    adc xh,t

xa1:    ret


ScaleMinus100To100:
    b16ldi Temp, 1520
    b16sub RxChannel, Temp
    b16load RxChannel    ;divide RxChannel by 4
    ldi t,2
    rcall FastDivide
    b16store RxChannel
    ret


Scale0To100:
    b16ldi Temp, 1120
    b16sub RxChannel, Temp
    brcc sc1
    b16ldi RxChannel, 0
sc1:    b16load RxChannel    ;divide RxChannel by 8
    ldi t,3
    rcall FastDivide
    b16store RxChannel
    
    ret



FastDivide:
    asr xh        ;X.Y Fast divide by 2n
    ror xl
    ror yh
    ror yl
    dec t
    brne Fastdivide
    ret


FastMultiply:
    lsl yl
    rol yh
    rol xl
    rol xh
    dec t
    brne FastMultiply
    ret



ReadEeprom:
    out eearl,zl    ;(Z) -> t
    out 0x22,zh

    ldi t,0x01
    out eecr,t

    in t, eedr
    ret


WriteEeprom:
    cli        ;t -> (Z)

wr1:    sbic eecr,1
    rjmp wr1

    out eearl,zl
    out 0x22,zh

    out eedr,t

    ;       76543210
    ldi t,0b00000100
    out eecr,t

    ;       76543210
    ldi t,0b00000010
    out eecr,t

    sei

    ret


GetGyroDirections:
    clr zl                ;Get roll gyro directions from EEPROM
    clr zh
    rcall ReadEeprom
    sts RollGyroDirection,t

    ldi zl,1            ;Get pitch gyro directions from EEPROM
    clr zh
    rcall ReadEeprom
    sts PitchGyroDirection,t

    ldi zl,2            ;Get yaw gyro directions from EEPROM
    clr zh
    rcall ReadEeprom
    sts YawGyroDirection,t

    ret


NegateXY: clr t                ;Negate X:Y
    sub t, yl
    mov yl, t

    clr t
    sbc t, yh
    mov yh, t

    clr t
    sbc t, xl
    mov xl, t

    clr t
    sbc t, xh
    mov xh, t

    ret

; -- function: flash LED n times.    
; -- counterl is number of flashes
FlashLEDnTimes:

lblFlashLEDnTimes:    
    led0_on
    ldi zl,0
    rcall waitms
    led0_off
    ldi zl,0
    rcall waitms
    dec counterl
    brne lblFlashLEDnTimes
    ret

    ;--- Debug: ----
/*
ShowGyros:
    ldi xl,0x0d
    rcall SerbyteOut

    ldi xl,0x0a
    rcall SerbyteOut


    b16load GyroRoll
    rcall B1616Out
    ldi xl,' '
    rcall SerByteOut

    b16load GyroPitch
    rcall B1616Out
    ldi xl,' '
    rcall SerByteOut

    b16load GyroYaw
    rcall B1616Out
    ldi xl,' '
    rcall SerByteOut

    ret



ShowChannels:
    
    ldi xl,0x0d
    rcall SerbyteOut

    ldi xl,0x0a
    rcall SerbyteOut


    b16load RxInRoll
    rcall B1616Out
    ldi xl,' '
    rcall SerByteOut

    b16load RxInPitch
    rcall B1616Out
    ldi xl,' '
    rcall SerByteOut

    b16load RxInCollective
    rcall B1616Out
    ldi xl,' '
    rcall SerByteOut

    b16load RxInYaw
    rcall B1616Out
    ldi xl,' '
    rcall SerByteOut

    ret




B1616Out:
    push yl
    push yh
    push xl

    mov xl,xh
    rcall SerByteAsciiOut
    pop xl
    rcall SerByteAsciiOut
    ldi xl,'.'
    rcall SerByteOut
    pop xl
    rcall SerByteAsciiOut
    pop xl
    rcall SerByteAsciiOut
    ret


B16Out:
    push xl

    mov xl,xh
    rcall SerByteAsciiOut
    pop xl
    rcall SerByteAsciiOut
    ret



    ;--- Debug: Output byte xl (ASCII) to serial port pin at 115200 8N1 ----

SerByteAsciiOut:


    push xl
    swap xl
    rcall su1        ;high nibble
    pop xl
    rjmp su1        ;low nibble

su1:    andi xl,0x0f
    ldi zl,low(su2*2)    ;output one nibble in ASCII
    add zl,xl
    ldi zh,high(su2*2)
    clr xl
    adc zh,xl
    lpm xl,z
    rjmp SerByteOut

su2:    .db "0123456789ABCDEF"


        



    ;--- Debug: Output byte xl (binary) to serial port pin at 115200 8N1 ----

SerByteOut:
    led3_off        ;startbit
    nop
    nop
    nop

    rcall BaudRateDelay    

    ldi xh,8        ;databits

se3:    ror xl

    brcc se1
    nop
    led3_on
    rjmp se2
se1:    led3_off
    nop
    nop

se2:    rcall BaudRateDelay

    dec xh
    brne se3

    nop
    nop
    nop
    nop

    led3_on            ;stopbit
    nop 
    nop
    nop
    rcall BaudRateDelay

    ret

BaudRatedelay:

    ldi t,17
ba1:    dec t
    brne ba1
    ret
    

*/


.undef t

.include "1616mathlib_ram_v1.asm"


I hope you find this is useful.



Months Later I built my second -and first truly flying- quadcopter I also wrote a new code with a new features that allows you to switch between +Quad  & X-Quad using your remote control, without the need to recompile the code or orient the board.