-
Notifications
You must be signed in to change notification settings - Fork 2
Software
The Flight Software on the Raspberry Pi Pico is written in pure C with the Pico C/C++ SDK.
Code Repo: https://github.com/MotionStudioCornell/BritneyDrone/tree/master/software
The parts being directed connected to the Pico are:
(The schematics is not yet updated, thus does not reflect the current build)
The current state of the flight software is a one-core, single-threading system with interrupts. It takes input from the handheld controller, sends data to the radio receiver, decodes the throttle information, converts to motor control PWM, collects IMU data, and sends the control signal to the ESC, which generates a three-phase voltage to control the speed of the motors.
The flight control system can be divided into two subsystems: State Estimation and State Control.
State Estimation
The controller system has its own de-noise filter implemented, namely the "leaky filter."
Which is a simple low-pass filter with one hyper-parameter
And we use it to do a simple low-pass for gyroscope and accelerometer data.
With the gyroscope and accelerometer values denoised. We use complementary filters to estimate the drone's roll, pitch, and yaw.
The specifies of complementary filters can be found on Hunter's website. However, due to its limitations, we can only estimate the roll and pitch. Thus, yaw is just estimated by integrating the gyro z value.
State Control
We implement a simple PID controller, which can be orthogonalized into PID roll, PID pitch, and PID yaw control.
Each of them is a 2D PID controller.
For roll, we are controlling the rpm of the left and right side propellers.
For pitch, we are controlling the rpm of the front and back side propellers.
For yaw, we are controlling the rpm of the diagonal propellers.
What is worth noting is we can use the gyro readings directly as the values needed for the derivative term computation.
An example to calculate the roll contribution to the final thrust:
float roll_P = my_controller->Kp * (error.roll);
float roll_I = my_controller->Ki * (my_controller->integral_error.roll);
float roll_D = my_controller->Kd * (d_error.roll);
motor1 += -roll_P - roll_I - roll_D;
motor2 += -roll_P - roll_I - roll_D;
motor3 += roll_P + roll_I + roll_D;
motor4 += roll_P + roll_I + roll_D;
The pseudocode for the flight software entry point P2.c
can be expressed as:
radio_interrput(){
throttle = new_throttle
}
setup(){
setup_serial()
setup_LED()
setup_radio()
setup_IMU()
calibrate_IMU()
setup_ESC()
calibrate_ESC()
arm_ESC()
}
while(1){
update_imu()
control_motor(throttle)
}
- controller/
- build/
- src/
- P2.c
- CMakelists.txt
- lib/
- ESC/
- ESC.h
- ESC.c
- MPU9250/
- mpu9250.h
- mpu9250.c
P2.c
is the main entry point of the flight software. Under lib/
, we have two main libraries we have developed for the ESC and the IMU.
Requirement:
Raspberry Pi Pico C/C++ SDK installed.
In controller/src/
cd build
cmake ../src -DPICO_BOARD=pico_w
make
Find P2.uf2
in the build/
dir, and drag it onto the Pico MSD(Or any methods you prefer)
Related files: esc.h
, esc.c
As for our ESC, which is loaded with the BLHeli32 firmware(Datasheet), its control signal to the motors is represented by a three-phase voltage signal.
To command the ESC from Pico, we need to send PWM signals with different duty cycles to represent 0%-100% throttle. Turns out, it expects a
50Hz
PWM signal, with a 5% duty-cycle
representing min throttle(0%)
and a 10% duty-cycle
representing max throttle(100%)
.
The ESC library consists of a struct ESC
to store information and several functions:
-
esc_setup()
takes in anESC
struct, PWM pin information, and other PWM characteristics to initialize the corresponding Pin with the desired frequency/wrap/slice/level and enables the PWM. -
cali_motor()
calibrates the ESC to determine the min and max throttle. It should be done on an occasional basis; every time the ESC is calibrated, the calibration information is stored on the ESC memory. -
arm_motor()
initiates the arming sequence PWM to the ESC to start taking in motor control commands. In our case, it sends the 0% throttle(10% duty-cycle) PWM signal until two beeps are heard. -
motor_control()
takes in anESC
struct, apercent_throttle
variable in[0,1]
, and aMOTOR_NUM
variable. It converts the throttle signal to the corresponding PWM signal and sends it to the desired motor.
Related files: mpu9250.h
, mpu9250.c
We chose the MPU9250 IMU for its SPI connection, which enables a higher data transmission frequency than a regular I2C protocol. This library is universal for any generic MPU9250s.
Data Sheet: https://invensense.tdk.com/wp-content/uploads/2015/02/PS-MPU-9250A-01-v1.1.pdf
Register Map: https://invensense.tdk.com/wp-content/uploads/2015/02/MPU-9250-Register-Map.pdf
(This library is quite low-level, as it involves register-level interaction.)
The MPU9250 library consists of a struct mpu9250
to store sensor data(raw, bias, calibrated) and SPI pin.
Low-Level Functions:
-
cs_select()
clears the assembly withnops
and actives the chip select pin -
cs_select()
clears the assembly withnops
and deactivates the chip select pin -
read_registers()
is a helper function that uses SPI to read imu register values -
convert()
is a helper function that converts the raw sensor data from the register to correct data using the corresponding sensor sensitivity.
High-Level Functions:
-
mpu9250_setup()
takes a set of SPI pins and properly sets up. Then, it starts the SPI connection by sending reg0x6B
data0x01
. -
gyro_cal()
takes in abuffer_size
variable, collects that many gyroscope data and calculates a bias by taking the average, then finally stores it in thempu9250
struct -
acc_cal()
takes in abuffer_size
variable, collects that many accelerometer data and calculates a bias by taking the average, then finally stores it in thempu9250
struct -
mpu9250_update()
reads raw data from the register, converts it to correct data depending on the sensor sensitivity, and applies the offsets, finally stores it in thempu9250
struct.
Related files: P2.c
The related code for the Radio receiver is not yet to be organized as a full-blown library, but its functionality is almost complete.
-
radio_setup()
set up the UART connection with the radio receiver using uart1 -
radio_irq_handler()
is the interrupt handler for the radio UART. It reads the UART character-by-character, stores them in a moving buffer, and converts the corresponding character to a global variablethrottle
that is being used to control the motor by callingmotor_control()