mirror of
https://github.com/betaflight/betaflight.git
synced 2025-07-14 20:10:18 +03:00
Merge branch 'master' into serial-cleanup
Conflicts: src/main/blackbox/blackbox_io.c src/main/config/config.c
This commit is contained in:
commit
b6509dd1eb
60 changed files with 2471 additions and 386 deletions
8
Makefile
8
Makefile
|
@ -199,6 +199,7 @@ COMMON_SRC = build_config.c \
|
||||||
common/maths.c \
|
common/maths.c \
|
||||||
common/printf.c \
|
common/printf.c \
|
||||||
common/typeconversion.c \
|
common/typeconversion.c \
|
||||||
|
common/encoding.c \
|
||||||
main.c \
|
main.c \
|
||||||
mw.c \
|
mw.c \
|
||||||
flight/altitudehold.c \
|
flight/altitudehold.c \
|
||||||
|
@ -206,6 +207,7 @@ COMMON_SRC = build_config.c \
|
||||||
flight/pid.c \
|
flight/pid.c \
|
||||||
flight/imu.c \
|
flight/imu.c \
|
||||||
flight/mixer.c \
|
flight/mixer.c \
|
||||||
|
flight/lowpass.c \
|
||||||
drivers/bus_i2c_soft.c \
|
drivers/bus_i2c_soft.c \
|
||||||
drivers/serial.c \
|
drivers/serial.c \
|
||||||
drivers/sound_beeper.c \
|
drivers/sound_beeper.c \
|
||||||
|
@ -293,6 +295,8 @@ NAZE_SRC = startup_stm32f10x_md_gcc.S \
|
||||||
drivers/system_stm32f10x.c \
|
drivers/system_stm32f10x.c \
|
||||||
drivers/timer.c \
|
drivers/timer.c \
|
||||||
drivers/timer_stm32f10x.c \
|
drivers/timer_stm32f10x.c \
|
||||||
|
drivers/flash_m25p16.c \
|
||||||
|
io/flashfs.c \
|
||||||
hardware_revision.c \
|
hardware_revision.c \
|
||||||
$(HIGHEND_SRC) \
|
$(HIGHEND_SRC) \
|
||||||
$(COMMON_SRC)
|
$(COMMON_SRC)
|
||||||
|
@ -396,6 +400,8 @@ CJMCU_SRC = \
|
||||||
drivers/timer.c \
|
drivers/timer.c \
|
||||||
drivers/timer_stm32f10x.c \
|
drivers/timer_stm32f10x.c \
|
||||||
hardware_revision.c \
|
hardware_revision.c \
|
||||||
|
blackbox/blackbox.c \
|
||||||
|
blackbox/blackbox_io.c \
|
||||||
$(COMMON_SRC)
|
$(COMMON_SRC)
|
||||||
|
|
||||||
CC3D_SRC = \
|
CC3D_SRC = \
|
||||||
|
@ -499,6 +505,8 @@ SPRACINGF3_SRC = \
|
||||||
drivers/barometer_ms5611.c \
|
drivers/barometer_ms5611.c \
|
||||||
drivers/compass_hmc5883l.c \
|
drivers/compass_hmc5883l.c \
|
||||||
drivers/display_ug2864hsweg01.h \
|
drivers/display_ug2864hsweg01.h \
|
||||||
|
drivers/flash_m25p16.c \
|
||||||
|
io/flashfs.c \
|
||||||
$(HIGHEND_SRC) \
|
$(HIGHEND_SRC) \
|
||||||
$(COMMON_SRC)
|
$(COMMON_SRC)
|
||||||
|
|
||||||
|
|
163
docs/Blackbox.md
163
docs/Blackbox.md
|
@ -5,7 +5,7 @@
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
This feature transmits your flight data information on every control loop iteration over a serial port to an external
|
This feature transmits your flight data information on every control loop iteration over a serial port to an external
|
||||||
logging device to be recorded.
|
logging device to be recorded, or to a dataflash chip which is present on some flight controllers.
|
||||||
|
|
||||||
After your flight, you can process the resulting logs on your computer to either turn them into CSV (comma-separated
|
After your flight, you can process the resulting logs on your computer to either turn them into CSV (comma-separated
|
||||||
values) or render your flight log as a video using the tools `blackbox_decode` and `blackbox_render`. Those tools can be
|
values) or render your flight log as a video using the tools `blackbox_decode` and `blackbox_render`. Those tools can be
|
||||||
|
@ -21,20 +21,17 @@ https://github.com/cleanflight/blackbox-log-viewer
|
||||||
The blackbox records flight data on every iteration of the flight control loop. It records the current time in
|
The blackbox records flight data on every iteration of the flight control loop. It records the current time in
|
||||||
microseconds, P, I and D corrections for each axis, your RC command stick positions (after applying expo curves),
|
microseconds, P, I and D corrections for each axis, your RC command stick positions (after applying expo curves),
|
||||||
gyroscope data, accelerometer data (after your configured low-pass filtering), barometer readings, 3-axis magnetometer
|
gyroscope data, accelerometer data (after your configured low-pass filtering), barometer readings, 3-axis magnetometer
|
||||||
readings, raw VBAT measurements, and the command being sent to each motor speed controller. This is all stored without
|
readings, raw VBAT and current measurements, and the command being sent to each motor speed controller. This is all
|
||||||
any approximation or loss of precision, so even quite subtle problems should be detectable from the fight data log.
|
stored without any approximation or loss of precision, so even quite subtle problems should be detectable from the fight
|
||||||
|
data log.
|
||||||
|
|
||||||
Currently, the blackbox attempts to log GPS data whenever new GPS data is available, but this has not been tested yet.
|
GPS data is logged whenever new GPS data is available. Although the CSV decoder will decode this data, the video
|
||||||
The CSV decoder and video renderer do not yet show any of the GPS data (though this will be added). If you have a working
|
renderer does not yet show any of the GPS information (this will be added later).
|
||||||
GPS, please send in your logs so I can get the decoding implemented.
|
|
||||||
|
|
||||||
The data rate for my quadcopter using a looptime of 2400 is about 10.25kB/s. This allows about 18 days of flight logs
|
|
||||||
to fit on a 16GB MicroSD card, which ought to be enough for anybody :).
|
|
||||||
|
|
||||||
## Supported configurations
|
## Supported configurations
|
||||||
|
|
||||||
The maximum data rate for the flight log is fairly restricted, so anything that increases the load can cause the flight
|
The maximum data rate that can be recorded to the flight log is fairly restricted, so anything that increases the load
|
||||||
log to drop frames and contain errors.
|
can cause the flight log to drop frames and contain errors.
|
||||||
|
|
||||||
The Blackbox was developed and tested on a quadcopter. It has also been tested on a tricopter. It should work on
|
The Blackbox was developed and tested on a quadcopter. It has also been tested on a tricopter. It should work on
|
||||||
hexacopters or octocopters, but as they transmit more information to the flight log (due to having more motors), the
|
hexacopters or octocopters, but as they transmit more information to the flight log (due to having more motors), the
|
||||||
|
@ -49,10 +46,11 @@ details.
|
||||||
|
|
||||||
## Hardware
|
## Hardware
|
||||||
|
|
||||||
The blackbox software is designed to be used with an [OpenLog serial data logger][] and a microSDHC card. You need a
|
There are two options for storing your flight logs. You can either transmit the log data over a serial port to an
|
||||||
little prep to get the OpenLog ready for use, so here are the details:
|
external logging device like the [OpenLog serial data logger][] to be recorded to a microSDHC card, or if you have a
|
||||||
|
compatible flight controller you can store the logs on the onboard dataflash storage instead.
|
||||||
|
|
||||||
### Firmware
|
### OpenLog serial data logger
|
||||||
|
|
||||||
The OpenLog ships with standard OpenLog 3 firmware installed. However, in order to reduce the number of dropped frames,
|
The OpenLog ships with standard OpenLog 3 firmware installed. However, in order to reduce the number of dropped frames,
|
||||||
it should be reflashed with the [OpenLog Light firmware][] or the special [OpenLog Blackbox firmware][] . The Blackbox
|
it should be reflashed with the [OpenLog Light firmware][] or the special [OpenLog Blackbox firmware][] . The Blackbox
|
||||||
|
@ -66,18 +64,18 @@ along with instructions for installing it onto your OpenLog.
|
||||||
[OpenLog Light firmware]: https://github.com/sparkfun/OpenLog/tree/master/firmware/OpenLog_v3_Light
|
[OpenLog Light firmware]: https://github.com/sparkfun/OpenLog/tree/master/firmware/OpenLog_v3_Light
|
||||||
[OpenLog Blackbox firmware]: https://github.com/cleanflight/blackbox-firmware
|
[OpenLog Blackbox firmware]: https://github.com/cleanflight/blackbox-firmware
|
||||||
|
|
||||||
### microSDHC
|
#### microSDHC
|
||||||
|
|
||||||
Your choice of microSDHC card is very important to the performance of the system. The OpenLog relies on being able to
|
Your choice of microSDHC card is very important to the performance of the system. The OpenLog relies on being able to
|
||||||
make many small writes to the card with minimal delay, which not every card is good at. A faster SD-card speed rating is
|
make many small writes to the card with minimal delay, which not every card is good at. A faster SD-card speed rating is
|
||||||
not a guarantee of better performance.
|
not a guarantee of better performance.
|
||||||
|
|
||||||
#### microSDHC cards known to have poor performance
|
##### microSDHC cards known to have poor performance
|
||||||
|
|
||||||
- Generic 4GB Class 4 microSDHC card - the rate of missing frames is about 1%, and is concentrated around the most
|
- Generic 4GB Class 4 microSDHC card - the rate of missing frames is about 1%, and is concentrated around the most
|
||||||
interesting parts of the log!
|
interesting parts of the log!
|
||||||
|
|
||||||
#### microSDHC cards known to have good performance
|
##### microSDHC cards known to have good performance
|
||||||
|
|
||||||
- Transcend 16GB Class 10 UHS-I microSDHC (typical error rate < 0.1%)
|
- Transcend 16GB Class 10 UHS-I microSDHC (typical error rate < 0.1%)
|
||||||
- Sandisk Extreme 16GB Class 10 UHS-I microSDHC (typical error rate < 0.1%)
|
- Sandisk Extreme 16GB Class 10 UHS-I microSDHC (typical error rate < 0.1%)
|
||||||
|
@ -87,7 +85,7 @@ the best chance of writing at high speed. You must format it with either FAT, or
|
||||||
|
|
||||||
[SD Association's special formatting tool]: https://www.sdcard.org/downloads/formatter_4/
|
[SD Association's special formatting tool]: https://www.sdcard.org/downloads/formatter_4/
|
||||||
|
|
||||||
### OpenLog configuration
|
#### OpenLog configuration
|
||||||
|
|
||||||
This section applies only if you are using the OpenLog or OpenLog Light original firmware on the OpenLog. If you flashed
|
This section applies only if you are using the OpenLog or OpenLog Light original firmware on the OpenLog. If you flashed
|
||||||
it with the special OpenLog Blackbox firmware, you don't need to configure it further.
|
it with the special OpenLog Blackbox firmware, you don't need to configure it further.
|
||||||
|
@ -105,42 +103,16 @@ of the MicroSD card:
|
||||||
baud,escape,esc#,mode,verb,echo,ignoreRX
|
baud,escape,esc#,mode,verb,echo,ignoreRX
|
||||||
```
|
```
|
||||||
|
|
||||||
## Enabling this feature (CLI)
|
#### Serial port
|
||||||
|
|
||||||
Enable the Blackbox feature by typing in `feature BLACKBOX` and pressing enter. You also need to assign the Blackbox to
|
A 115200 baud serial port is required to connect the OpenLog to your flight controller (such as `serial_port_1` on the
|
||||||
one of [your serial ports][] . A 115200 baud port is required (such as serial_port_1 on the Naze32, the two-pin Tx/Rx
|
Naze32, the two-pin Tx/Rx header in the center of the board). The Blackbox can not be used with softserial ports as they
|
||||||
header in the center of the board).
|
are too slow.
|
||||||
|
|
||||||
For example, use `set serial_port_1_scenario=11` to switch the main serial port to MSP, CLI, Blackbox and GPS Passthrough.
|
|
||||||
|
|
||||||
Enter `save`. Your configuration should be saved and the flight controller will reboot. You're ready to go!
|
|
||||||
|
|
||||||
[your serial ports]: https://github.com/cleanflight/cleanflight/blob/master/docs/Serial.md
|
|
||||||
[Cleanflight Configurator]: https://chrome.google.com/webstore/detail/cleanflight-configurator/enacoimjcgeinfnnnpajinjgmkahmfgb?hl=en
|
|
||||||
|
|
||||||
## Configuring this feature
|
|
||||||
|
|
||||||
The Blackbox currently provides two settings (`blackbox_rate_num` and `blackbox_rate_denom`) that allow you to control
|
|
||||||
the rate at which data is logged. These two together form a fraction (`blackbox_rate_num / blackbox_rate_denom`) which
|
|
||||||
decides what portion of the flight controller's control loop iterations should be logged. The default is 1/1 which logs
|
|
||||||
every iteration.
|
|
||||||
|
|
||||||
If you are using a short looptime like 2000 or faster, or you're using a slower MicroSD card, you will need to reduce
|
|
||||||
this rate to reduce the number of corrupted logged frames. A rate of 1/2 is likely to work for most craft.
|
|
||||||
|
|
||||||
You can change these settings by entering the CLI tab in the Cleanflight Configurator and using the `set` command, like so:
|
|
||||||
|
|
||||||
```
|
|
||||||
set blackbox_rate_num = 1
|
|
||||||
set blackbox_rate_denom = 2
|
|
||||||
```
|
|
||||||
|
|
||||||
### Serial port
|
|
||||||
|
|
||||||
Connect the "TX" pin of the serial port you've chosen to the OpenLog's "RXI" pin. Don't connect the serial port's RX
|
Connect the "TX" pin of the serial port you've chosen to the OpenLog's "RXI" pin. Don't connect the serial port's RX
|
||||||
pin to the OpenLog.
|
pin to the OpenLog.
|
||||||
|
|
||||||
### Protection
|
#### Protection
|
||||||
|
|
||||||
The OpenLog can be wrapped in black electrical tape or heat-shrink in order to insulate it from conductive frames (like
|
The OpenLog can be wrapped in black electrical tape or heat-shrink in order to insulate it from conductive frames (like
|
||||||
carbon fiber), but this makes its status LEDs impossible to see. I recommend wrapping it with some clear heatshrink
|
carbon fiber), but this makes its status LEDs impossible to see. I recommend wrapping it with some clear heatshrink
|
||||||
|
@ -148,30 +120,93 @@ tubing instead.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
### Onboard dataflash storage
|
||||||
|
The full version of the Naze32 has an onboard "m25p16" 2 megayte dataflash storage chip which can be used to store
|
||||||
|
flight logs instead of using an OpenLog. This is the small chip at the base of the Naze32's direction arrow.
|
||||||
|
This chip is not present on the "Acro" version of the Naze32.
|
||||||
|
|
||||||
|
## Enabling the Blackbox (CLI)
|
||||||
|
In the [Cleanflight Configurator][] , enter the CLI tab. Enable the Blackbox feature by typing in `feature BLACKBOX` and
|
||||||
|
pressing enter. Now choose the device that you want to log to:
|
||||||
|
|
||||||
|
### OpenLog serial data logger
|
||||||
|
Enter `set blackbox_device=0` to switch to logging to a serial port (this is the default).
|
||||||
|
|
||||||
|
You then need to let Cleanflight know which of [your serial ports][] you connected the OpenLog to. A 115200 baud port
|
||||||
|
is required (such as `serial_port_1` on the Naze32, the two-pin Tx/Rx header in the center of the board).
|
||||||
|
|
||||||
|
For example, use `set serial_port_1_scenario=11` to switch the main serial port to MSP, CLI, Blackbox and GPS
|
||||||
|
Passthrough. You can also use the GUI to configure a port for the Blackbox feature on the Ports tab.
|
||||||
|
|
||||||
|
### Onboard dataflash
|
||||||
|
Enter `set blackbox_device=1` to switch to logging to an onboard dataflash chip, if your flight controller has one.
|
||||||
|
|
||||||
|
[your serial ports]: https://github.com/cleanflight/cleanflight/blob/master/docs/Serial.md
|
||||||
|
[Cleanflight Configurator]: https://chrome.google.com/webstore/detail/cleanflight-configurator/enacoimjcgeinfnnnpajinjgmkahmfgb?hl=en
|
||||||
|
|
||||||
|
## Configuring the Blackbox
|
||||||
|
|
||||||
|
The Blackbox currently provides two settings (`blackbox_rate_num` and `blackbox_rate_denom`) that allow you to control
|
||||||
|
the rate at which data is logged. These two together form a fraction (`blackbox_rate_num / blackbox_rate_denom`) which
|
||||||
|
decides what portion of the flight controller's control loop iterations should be logged. The default is 1/1 which logs
|
||||||
|
every iteration.
|
||||||
|
|
||||||
|
If you are using a short looptime like 2000 or faster, or you're using a slower MicroSD card, you will need to reduce
|
||||||
|
this rate to reduce the number of corrupted logged frames that `blackbox_decode` complains about. A rate of 1/2 is likely
|
||||||
|
to work for most craft.
|
||||||
|
|
||||||
|
You can change these settings by entering the CLI tab in the [Cleanflight Configurator][] and using the `set` command, like so:
|
||||||
|
|
||||||
|
```
|
||||||
|
set blackbox_rate_num = 1
|
||||||
|
set blackbox_rate_denom = 2
|
||||||
|
```
|
||||||
|
|
||||||
|
The data rate for my quadcopter using a looptime of 2400 and a rate of 1/1 is about 10.25kB/s. This allows about 18
|
||||||
|
days of flight logs to fit on my OpenLog's 16GB MicroSD card, which ought to be enough for anybody :).
|
||||||
|
|
||||||
|
If you're logging to an onboard dataflash chip instead of an OpenLog, be aware that the 2MB of storage space it offers
|
||||||
|
is pretty small. At the default 1/1 logging rate, and a 2400 looptime, this is only enough for about 3 minutes of
|
||||||
|
flight. This could be long enough for you to investigate some flying problem with your craft, but you may want to reduce
|
||||||
|
the logging rate in order to extend your recording time.
|
||||||
|
|
||||||
|
To maximize your recording time, you could drop the rate way down to 1/32 (the smallest possible rate) which would
|
||||||
|
result in a logging rate of about 10-20Hz and about 650 bytes/second of data. At that logging rate, the 2MB flash chip
|
||||||
|
can store around 50 minutes of flight data, though the level of detail is severely reduced and you could not diagnose
|
||||||
|
flight problems like vibration or PID setting issues.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
The Blackbox starts recording data as soon as you arm your craft, and stops when you disarm. Each time the OpenLog is
|
The Blackbox starts recording data as soon as you arm your craft, and stops when you disarm.
|
||||||
power-cycled, it begins a fresh new log file. If you arm and disarm several times without cycling the power (recording
|
|
||||||
several flights), those logs will be combined together into one file. The command line tools will ask you to pick which
|
|
||||||
one of these flights you want to display/decode.
|
|
||||||
|
|
||||||
If your craft has a buzzer attached, a short beep will be played when you arm. You can later use this beep to
|
If your craft has a buzzer attached, a short beep will be played when you arm and recording begins. You can later use
|
||||||
synchronize your recorded flight video with the rendered flight data log (the beep is shown as a blue line in the flight
|
this beep to synchronize your recorded flight video with the rendered flight data log (the beep is shown as a blue line
|
||||||
data log, which you can sync against the beep in your recorded audio track).
|
in the flight data log, which you can sync against the beep in your recorded audio track).
|
||||||
|
|
||||||
The OpenLog requires a couple of seconds of delay after connecting the battery before it's ready to record, so don't
|
You should wait a few seconds after disarming your craft to allow the Blackbox to finish saving its data.
|
||||||
arm your craft immediately after connecting the battery (you'll probably be waiting for the flight controller to become
|
|
||||||
ready during that time anyway!)
|
|
||||||
|
|
||||||
You should also wait a few seconds after disarming the quad to allow the OpenLog to finish saving its data.
|
### Usage - OpenLog
|
||||||
|
Each time the OpenLog is power-cycled, it begins a fresh new log file. If you arm and disarm several times without
|
||||||
|
cycling the power (recording several flights), those logs will be combined together into one file. The command line
|
||||||
|
tools will ask you to pick which one of these flights you want to display/decode.
|
||||||
|
|
||||||
Don't insert or remove the SD card while the OpenLog is powered up.
|
Don't insert or remove the SD card while the OpenLog is powered up.
|
||||||
|
|
||||||
|
### Usage - Dataflash chip
|
||||||
|
After your flights, you can use the [Cleanflight Configurator][] to download the contents of the dataflash to your
|
||||||
|
computer. Go to the "dataflash" tab and click the "save flash to file..." button. Saving the log can take 2 or 3
|
||||||
|
minutes.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
After downloading the log, be sure to erase the chip to make it ready for reuse by clicking the "erase flash" button.
|
||||||
|
If you try to start recording a new flight when the dataflash is already full, the Blackbox will not make its regular
|
||||||
|
arming beep and nothing will be recorded.
|
||||||
|
|
||||||
## Converting logs to CSV or PNG
|
## Converting logs to CSV or PNG
|
||||||
After your flights, you'll have a series of files labeled "LOG00001.TXT" etc. on the microSD card. You'll need to
|
After your flights, you'll have a series of flight log files with a .TXT extension. You'll need to decode these with
|
||||||
decode these with the `blackbox_decode` tool to create a CSV (comma-separated values) file for analysis, or render them
|
the `blackbox_decode` tool to create CSV (comma-separated values) files for analysis, or render them into a series of PNG
|
||||||
into a series of PNG frames with `blackbox_render` tool, which you could then convert into a video using another
|
frames with `blackbox_render` tool, which you could then convert into a video using another software package.
|
||||||
software package.
|
|
||||||
|
|
||||||
You'll find those tools along with instructions for using them in this repository:
|
You'll find those tools along with instructions for using them in this repository:
|
||||||
|
|
||||||
|
|
|
@ -201,7 +201,7 @@ Re-apply any new defaults as desired.
|
||||||
| failsafe_min_usec | | 100 | 2000 | 985 | Profile | UINT16 |
|
| failsafe_min_usec | | 100 | 2000 | 985 | Profile | UINT16 |
|
||||||
| failsafe_max_usec | | 100 | 3000 | 2115 | Profile | UINT16 |
|
| failsafe_max_usec | | 100 | 3000 | 2115 | Profile | UINT16 |
|
||||||
| gimbal_flags | When feature SERVO_TILT is enabled, this can be a combination of the following numbers: 1=normal gimbal (default), 2=tiltmix gimbal, 4=in PPM (or SERIALRX) input mode, this will forward AUX1..4 RC inputs to PWM5..8 pins | 0 | 255 | 1 | Profile | UINT8 |
|
| gimbal_flags | When feature SERVO_TILT is enabled, this can be a combination of the following numbers: 1=normal gimbal (default), 2=tiltmix gimbal, 4=in PPM (or SERIALRX) input mode, this will forward AUX1..4 RC inputs to PWM5..8 pins | 0 | 255 | 1 | Profile | UINT8 |
|
||||||
| acc_hardware | This is used to suggest which accelerometer driver should load, or to force no accelerometer in case gyro-only flight is needed. Default (0) will attempt to auto-detect among enabled drivers. Otherwise, to force a particular device, set it to 1 for ADXL345, 2 for MPU6050 integrated accelerometer, 3 for MMA8452, 4 for BMA280, or 5 to disable accelerometer alltogether - resulting in gyro-only operation. | 0 | 9 | 0 | Master | UINT8 |
|
| acc_hardware | This is used to suggest which accelerometer driver should load, or to force no accelerometer in case gyro-only flight is needed. Default (0) will attempt to auto-detect among enabled drivers. Otherwise, to force a particular device, set it to 2 for ADXL345, 3 for MPU6050 integrated accelerometer, 4 for MMA8452, 5 for BMA280, 6 for LSM303DLHC, 7 for SPI_MPU6000, 8 for SPI_MPU6500 or 1 to disable accelerometer alltogether - resulting in gyro-only operation. | 0 | 9 | 0 | Master | UINT8 |
|
||||||
| acc_lpf_factor | | 0 | 250 | 4 | Profile | UINT8 |
|
| acc_lpf_factor | | 0 | 250 | 4 | Profile | UINT8 |
|
||||||
| accxy_deadband | | 0 | 100 | 40 | Profile | UINT8 |
|
| accxy_deadband | | 0 | 100 | 40 | Profile | UINT8 |
|
||||||
| accz_deadband | | 0 | 100 | 40 | Profile | UINT8 |
|
| accz_deadband | | 0 | 100 | 40 | Profile | UINT8 |
|
||||||
|
|
|
@ -28,6 +28,8 @@ HIGH - the channel value for the mapped channel input is around 2000
|
||||||
| Trim Acc Right | HIGH | CENTER | CENTER | HIGH |
|
| Trim Acc Right | HIGH | CENTER | CENTER | HIGH |
|
||||||
| Trim Acc Forwards | HIGH | CENTER | HIGH | CENTER |
|
| Trim Acc Forwards | HIGH | CENTER | HIGH | CENTER |
|
||||||
| Trim Acc Backwards | HIGH | CENTER | LOW | CENTER |
|
| Trim Acc Backwards | HIGH | CENTER | LOW | CENTER |
|
||||||
|
| Disable LCD Page Cycling | LOW | CENTER | HIGH | LOW |
|
||||||
|
| Enable LCD Page Cycling | LOW | CENTER | HIGH | HIGH |
|
||||||
| Save setting | LOW | LOW | LOW | HIGH |
|
| Save setting | LOW | LOW | LOW | HIGH |
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -23,14 +23,12 @@ Likewise, support for more than 32 LEDs is possible, it just requires additional
|
||||||
|
|
||||||
## Supported hardware
|
## Supported hardware
|
||||||
|
|
||||||
Only strips of 32 WS2812 LEDs are supported currently. If the strip is longer than 32 LEDs it does not matter,
|
Only strips of 32 WS2811/WS2812 LEDs are supported currently. If the strip is longer than 32 LEDs it does not matter,
|
||||||
but only the first 32 are used.
|
but only the first 32 are used.
|
||||||
|
|
||||||
WS2812 LEDs require an 800khz signal and precise timings and thus requires the use of a dedicated hardware timer.
|
WS2812 LEDs require an 800khz signal and precise timings and thus requires the use of a dedicated hardware timer.
|
||||||
|
|
||||||
Note: The initial code may work with WS2801 + External LEDs since the protocol is the same, WS2811/WS2812B should also work but
|
Note: Not all WS2812 ICs use the same timings, some batches use different timings.
|
||||||
may require very simple timing adjustments to be made in the source.
|
|
||||||
Not all WS2812 ICs use the same timings, some batches use different timings.
|
|
||||||
|
|
||||||
It could be possible to be able to specify the timings required via CLI if users request it.
|
It could be possible to be able to specify the timings required via CLI if users request it.
|
||||||
|
|
||||||
|
@ -63,14 +61,11 @@ Since RC5 is also used for SoftSerial on the Naze/Olimexino it means that you ca
|
||||||
Additionally, since RC5 is also used for Parallel PWM RC input on both the Naze, Chebuzz and STM32F3Discovery targets, led strips
|
Additionally, since RC5 is also used for Parallel PWM RC input on both the Naze, Chebuzz and STM32F3Discovery targets, led strips
|
||||||
can not be used at the same time at Parallel PWM.
|
can not be used at the same time at Parallel PWM.
|
||||||
|
|
||||||
Ensure that your 5V supply is not too high, if the voltage at the input to the LED strip is to high then the LEDs may not light; The
|
If you have LEDs that are intermittent, flicker or show the wrong colors then drop the VIN to less than 4.7v, e.g. by using an inline
|
||||||
problem occurs because of the difference in voltage between the data signal and the power signal.
|
diode on the VIN to the LED strip. The problem occurs because of the difference in voltage between the data signal and the power
|
||||||
|
signal. The WS2811 LED's require the data signal (Din) to be MAX 0.7 * VIN. Data In on the LEDs will allways be around ~3.3v,
|
||||||
|
so the Vin MAX should be 4.7v (3.3v / 0.7 = 4.71v). Some LEDs are more tolerant of this than others.
|
||||||
|
|
||||||
If you are using an BEC from an ESC to for the 5v supply check the output is as close to 5v as possible.
|
|
||||||
|
|
||||||
It was observed that a 5.4v supply from a BEC was fine for powering the FC and other devices but the LED strip would not light until
|
|
||||||
the voltage was reduced, this was acheived by placing an IN4007 diode between the BEC 5v output and the 5V input pin of the LED strip.
|
|
||||||
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
|
@ -411,7 +406,7 @@ led 27 2,9:S:FWT:0
|
||||||
```
|
```
|
||||||
16-18 9-11
|
16-18 9-11
|
||||||
19-21 \ / 6-8
|
19-21 \ / 6-8
|
||||||
\ 13-16 /
|
\ 12-15 /
|
||||||
\ FRONT /
|
\ FRONT /
|
||||||
/ BACK \
|
/ BACK \
|
||||||
/ \
|
/ \
|
||||||
|
@ -433,7 +428,4 @@ This also means that you can make sure that each R,G and B LED in each LED modul
|
||||||
|
|
||||||
After a short delay the LEDs will show the unarmed color sequence and or low-battery warning sequence.
|
After a short delay the LEDs will show the unarmed color sequence and or low-battery warning sequence.
|
||||||
|
|
||||||
If the LEDs flash intermittently or do not show the correct colors verify all connections and check the specifications of the
|
|
||||||
LEDs you have against the supported timings (for now, you'll have to look in the source).
|
|
||||||
|
|
||||||
Also check that the feature `LED_STRIP` was correctly enabled and that it does not conflict with other features, as above.
|
Also check that the feature `LED_STRIP` was correctly enabled and that it does not conflict with other features, as above.
|
||||||
|
|
|
@ -45,14 +45,12 @@ You can also use the Command Line Interface (CLI) to set the mixer type:
|
||||||
|
|
||||||
A low-pass filter can be enabled for the servos. It may be useful for avoiding structural modes in the airframe, for example. Currently it can only be configured via the CLI:
|
A low-pass filter can be enabled for the servos. It may be useful for avoiding structural modes in the airframe, for example. Currently it can only be configured via the CLI:
|
||||||
|
|
||||||
1. Use `set servo_lowpass_freq_idx = nn` to select the cutoff frequency. Valid values range from 0 to 99.
|
1. Use `set servo_lowpass_freq = nnn` to select the cutoff frequency. Valid values range from 10 to 400. This is a fraction of the loop frequency in 1/1000ths. For example, `40` means `0.040`.
|
||||||
2. Use `set servo_lowpass_enable = 1` to enable filtering.
|
2. Use `set servo_lowpass_enable = 1` to enable filtering.
|
||||||
|
|
||||||
The actual cutoff frequency is determined by the value of the `looptime` variable and the selected index.
|
The cutoff frequency can be determined by the following formula:
|
||||||
The formula is:
|
`Frequency = 1000 * servo_lowpass_freq / looptime`
|
||||||
`Frequency = 1000000 * (servo_lowpass_freq_idx + 1)*0.0025 / looptime )`
|
|
||||||
|
For example, if `servo_lowpass_freq` is set to 40, and looptime is set to the default of 3500 us, the cutoff frequency will be 11.43 Hz.
|
||||||
|
|
||||||
For example, if `servo_lowpass_freq_idx` is set to 40, and looptime is set to the default of 3500 us (0.0035 s), the cutoff frequency will be 29.3 Hz.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
BIN
docs/Screenshots/blackbox-dataflash.png
Normal file
BIN
docs/Screenshots/blackbox-dataflash.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 53 KiB |
|
@ -21,9 +21,12 @@
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
|
||||||
|
#ifdef BLACKBOX
|
||||||
|
|
||||||
#include "common/maths.h"
|
#include "common/maths.h"
|
||||||
#include "common/axis.h"
|
#include "common/axis.h"
|
||||||
#include "common/color.h"
|
#include "common/color.h"
|
||||||
|
#include "common/encoding.h"
|
||||||
|
|
||||||
#include "drivers/gpio.h"
|
#include "drivers/gpio.h"
|
||||||
#include "drivers/sensor.h"
|
#include "drivers/sensor.h"
|
||||||
|
@ -77,6 +80,7 @@
|
||||||
#include "blackbox_io.h"
|
#include "blackbox_io.h"
|
||||||
|
|
||||||
#define BLACKBOX_I_INTERVAL 32
|
#define BLACKBOX_I_INTERVAL 32
|
||||||
|
#define BLACKBOX_SHUTDOWN_TIMEOUT_MILLIS 200
|
||||||
|
|
||||||
#define ARRAY_LENGTH(x) (sizeof((x))/sizeof((x)[0]))
|
#define ARRAY_LENGTH(x) (sizeof((x))/sizeof((x)[0]))
|
||||||
|
|
||||||
|
@ -130,11 +134,17 @@ static const char* const blackboxGPSHHeaderNames[] = {
|
||||||
/* All field definition structs should look like this (but with longer arrs): */
|
/* All field definition structs should look like this (but with longer arrs): */
|
||||||
typedef struct blackboxFieldDefinition_t {
|
typedef struct blackboxFieldDefinition_t {
|
||||||
const char *name;
|
const char *name;
|
||||||
|
// If the field name has a number to be included in square brackets [1] afterwards, set it here, or -1 for no brackets:
|
||||||
|
int8_t fieldNameIndex;
|
||||||
|
|
||||||
|
// Each member of this array will be the value to print for this field for the given header index
|
||||||
uint8_t arr[1];
|
uint8_t arr[1];
|
||||||
} blackboxFieldDefinition_t;
|
} blackboxFieldDefinition_t;
|
||||||
|
|
||||||
typedef struct blackboxMainFieldDefinition_t {
|
typedef struct blackboxMainFieldDefinition_t {
|
||||||
const char *name;
|
const char *name;
|
||||||
|
int8_t fieldNameIndex;
|
||||||
|
|
||||||
uint8_t isSigned;
|
uint8_t isSigned;
|
||||||
uint8_t Ipredict;
|
uint8_t Ipredict;
|
||||||
uint8_t Iencode;
|
uint8_t Iencode;
|
||||||
|
@ -145,6 +155,8 @@ typedef struct blackboxMainFieldDefinition_t {
|
||||||
|
|
||||||
typedef struct blackboxGPSFieldDefinition_t {
|
typedef struct blackboxGPSFieldDefinition_t {
|
||||||
const char *name;
|
const char *name;
|
||||||
|
int8_t fieldNameIndex;
|
||||||
|
|
||||||
uint8_t isSigned;
|
uint8_t isSigned;
|
||||||
uint8_t predict;
|
uint8_t predict;
|
||||||
uint8_t encode;
|
uint8_t encode;
|
||||||
|
@ -159,73 +171,76 @@ typedef struct blackboxGPSFieldDefinition_t {
|
||||||
*/
|
*/
|
||||||
static const blackboxMainFieldDefinition_t blackboxMainFields[] = {
|
static const blackboxMainFieldDefinition_t blackboxMainFields[] = {
|
||||||
/* loopIteration doesn't appear in P frames since it always increments */
|
/* loopIteration doesn't appear in P frames since it always increments */
|
||||||
{"loopIteration", UNSIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(UNSIGNED_VB), .Ppredict = PREDICT(INC), .Pencode = FLIGHT_LOG_FIELD_ENCODING_NULL, CONDITION(ALWAYS)},
|
{"loopIteration",-1, UNSIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(UNSIGNED_VB), .Ppredict = PREDICT(INC), .Pencode = FLIGHT_LOG_FIELD_ENCODING_NULL, CONDITION(ALWAYS)},
|
||||||
/* Time advances pretty steadily so the P-frame prediction is a straight line */
|
/* Time advances pretty steadily so the P-frame prediction is a straight line */
|
||||||
{"time", UNSIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(UNSIGNED_VB), .Ppredict = PREDICT(STRAIGHT_LINE), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
|
{"time", -1, UNSIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(UNSIGNED_VB), .Ppredict = PREDICT(STRAIGHT_LINE), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
|
||||||
{"axisP[0]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
|
{"axisP", 0, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
|
||||||
{"axisP[1]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
|
{"axisP", 1, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
|
||||||
{"axisP[2]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
|
{"axisP", 2, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
|
||||||
/* I terms get special packed encoding in P frames: */
|
/* I terms get special packed encoding in P frames: */
|
||||||
{"axisI[0]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG2_3S32), CONDITION(ALWAYS)},
|
{"axisI", 0, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG2_3S32), CONDITION(ALWAYS)},
|
||||||
{"axisI[1]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG2_3S32), CONDITION(ALWAYS)},
|
{"axisI", 1, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG2_3S32), CONDITION(ALWAYS)},
|
||||||
{"axisI[2]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG2_3S32), CONDITION(ALWAYS)},
|
{"axisI", 2, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG2_3S32), CONDITION(ALWAYS)},
|
||||||
{"axisD[0]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(NONZERO_PID_D_0)},
|
{"axisD", 0, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(NONZERO_PID_D_0)},
|
||||||
{"axisD[1]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(NONZERO_PID_D_1)},
|
{"axisD", 1, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(NONZERO_PID_D_1)},
|
||||||
{"axisD[2]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(NONZERO_PID_D_2)},
|
{"axisD", 2, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(NONZERO_PID_D_2)},
|
||||||
/* rcCommands are encoded together as a group in P-frames: */
|
/* rcCommands are encoded together as a group in P-frames: */
|
||||||
{"rcCommand[0]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_4S16), CONDITION(ALWAYS)},
|
{"rcCommand", 0, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_4S16), CONDITION(ALWAYS)},
|
||||||
{"rcCommand[1]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_4S16), CONDITION(ALWAYS)},
|
{"rcCommand", 1, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_4S16), CONDITION(ALWAYS)},
|
||||||
{"rcCommand[2]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_4S16), CONDITION(ALWAYS)},
|
{"rcCommand", 2, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_4S16), CONDITION(ALWAYS)},
|
||||||
/* Throttle is always in the range [minthrottle..maxthrottle]: */
|
/* Throttle is always in the range [minthrottle..maxthrottle]: */
|
||||||
{"rcCommand[3]", UNSIGNED, .Ipredict = PREDICT(MINTHROTTLE), .Iencode = ENCODING(UNSIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_4S16), CONDITION(ALWAYS)},
|
{"rcCommand", 3, UNSIGNED, .Ipredict = PREDICT(MINTHROTTLE), .Iencode = ENCODING(UNSIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_4S16), CONDITION(ALWAYS)},
|
||||||
|
|
||||||
|
{"vbatLatest", -1, UNSIGNED, .Ipredict = PREDICT(VBATREF), .Iencode = ENCODING(NEG_14BIT), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_8SVB), FLIGHT_LOG_FIELD_CONDITION_VBAT},
|
||||||
|
{"amperageLatest",-1, UNSIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(UNSIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_8SVB), FLIGHT_LOG_FIELD_CONDITION_AMPERAGE},
|
||||||
|
|
||||||
{"vbatLatest", UNSIGNED, .Ipredict = PREDICT(VBATREF), .Iencode = ENCODING(NEG_14BIT), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_8SVB), FLIGHT_LOG_FIELD_CONDITION_VBAT},
|
|
||||||
{"amperageLatest",UNSIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(UNSIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_8SVB), FLIGHT_LOG_FIELD_CONDITION_AMPERAGE},
|
|
||||||
#ifdef MAG
|
#ifdef MAG
|
||||||
{"magADC[0]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_8SVB), FLIGHT_LOG_FIELD_CONDITION_MAG},
|
{"magADC", 0, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_8SVB), FLIGHT_LOG_FIELD_CONDITION_MAG},
|
||||||
{"magADC[1]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_8SVB), FLIGHT_LOG_FIELD_CONDITION_MAG},
|
{"magADC", 1, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_8SVB), FLIGHT_LOG_FIELD_CONDITION_MAG},
|
||||||
{"magADC[2]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_8SVB), FLIGHT_LOG_FIELD_CONDITION_MAG},
|
{"magADC", 2, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_8SVB), FLIGHT_LOG_FIELD_CONDITION_MAG},
|
||||||
#endif
|
#endif
|
||||||
#ifdef BARO
|
#ifdef BARO
|
||||||
{"BaroAlt", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_8SVB), FLIGHT_LOG_FIELD_CONDITION_BARO},
|
{"BaroAlt", -1, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_8SVB), FLIGHT_LOG_FIELD_CONDITION_BARO},
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Gyros and accelerometers base their P-predictions on the average of the previous 2 frames to reduce noise impact */
|
/* Gyros and accelerometers base their P-predictions on the average of the previous 2 frames to reduce noise impact */
|
||||||
{"gyroData[0]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
|
{"gyroData", 0, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
|
||||||
{"gyroData[1]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
|
{"gyroData", 1, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
|
||||||
{"gyroData[2]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
|
{"gyroData", 2, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
|
||||||
{"accSmooth[0]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
|
{"accSmooth", 0, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
|
||||||
{"accSmooth[1]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
|
{"accSmooth", 1, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
|
||||||
{"accSmooth[2]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
|
{"accSmooth", 2, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
|
||||||
/* Motors only rarely drops under minthrottle (when stick falls below mincommand), so predict minthrottle for it and use *unsigned* encoding (which is large for negative numbers but more compact for positive ones): */
|
/* Motors only rarely drops under minthrottle (when stick falls below mincommand), so predict minthrottle for it and use *unsigned* encoding (which is large for negative numbers but more compact for positive ones): */
|
||||||
{"motor[0]", UNSIGNED, .Ipredict = PREDICT(MINTHROTTLE), .Iencode = ENCODING(UNSIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_1)},
|
{"motor", 0, UNSIGNED, .Ipredict = PREDICT(MINTHROTTLE), .Iencode = ENCODING(UNSIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_1)},
|
||||||
/* Subsequent motors base their I-frame values on the first one, P-frame values on the average of last two frames: */
|
/* Subsequent motors base their I-frame values on the first one, P-frame values on the average of last two frames: */
|
||||||
{"motor[1]", UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_2)},
|
{"motor", 1, UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_2)},
|
||||||
{"motor[2]", UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_3)},
|
{"motor", 2, UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_3)},
|
||||||
{"motor[3]", UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_4)},
|
{"motor", 3, UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_4)},
|
||||||
{"motor[4]", UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_5)},
|
{"motor", 4, UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_5)},
|
||||||
{"motor[5]", UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_6)},
|
{"motor", 5, UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_6)},
|
||||||
{"motor[6]", UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_7)},
|
{"motor", 6, UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_7)},
|
||||||
{"motor[7]", UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_8)},
|
{"motor", 7, UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_8)},
|
||||||
{"servo[5]", UNSIGNED, .Ipredict = PREDICT(1500), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(TRICOPTER)}
|
|
||||||
|
/* Tricopter tail servo */
|
||||||
|
{"servo", 5, UNSIGNED, .Ipredict = PREDICT(1500), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(TRICOPTER)}
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef GPS
|
#ifdef GPS
|
||||||
// GPS position/vel frame
|
// GPS position/vel frame
|
||||||
static const blackboxGPSFieldDefinition_t blackboxGpsGFields[] = {
|
static const blackboxGPSFieldDefinition_t blackboxGpsGFields[] = {
|
||||||
{"time", UNSIGNED, PREDICT(LAST_MAIN_FRAME_TIME), ENCODING(UNSIGNED_VB), CONDITION(NOT_LOGGING_EVERY_FRAME)},
|
{"time", -1, UNSIGNED, PREDICT(LAST_MAIN_FRAME_TIME), ENCODING(UNSIGNED_VB), CONDITION(NOT_LOGGING_EVERY_FRAME)},
|
||||||
{"GPS_numSat", UNSIGNED, PREDICT(0), ENCODING(UNSIGNED_VB), CONDITION(ALWAYS)},
|
{"GPS_numSat", -1, UNSIGNED, PREDICT(0), ENCODING(UNSIGNED_VB), CONDITION(ALWAYS)},
|
||||||
{"GPS_coord[0]", SIGNED, PREDICT(HOME_COORD), ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
|
{"GPS_coord", 0, SIGNED, PREDICT(HOME_COORD), ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
|
||||||
{"GPS_coord[1]", SIGNED, PREDICT(HOME_COORD), ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
|
{"GPS_coord", 1, SIGNED, PREDICT(HOME_COORD), ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
|
||||||
{"GPS_altitude", UNSIGNED, PREDICT(0), ENCODING(UNSIGNED_VB), CONDITION(ALWAYS)},
|
{"GPS_altitude", -1, UNSIGNED, PREDICT(0), ENCODING(UNSIGNED_VB), CONDITION(ALWAYS)},
|
||||||
{"GPS_speed", UNSIGNED, PREDICT(0), ENCODING(UNSIGNED_VB), CONDITION(ALWAYS)},
|
{"GPS_speed", -1, UNSIGNED, PREDICT(0), ENCODING(UNSIGNED_VB), CONDITION(ALWAYS)},
|
||||||
{"GPS_ground_course",UNSIGNED, PREDICT(0), ENCODING(UNSIGNED_VB), CONDITION(ALWAYS)}
|
{"GPS_ground_course", -1, UNSIGNED, PREDICT(0), ENCODING(UNSIGNED_VB), CONDITION(ALWAYS)}
|
||||||
};
|
};
|
||||||
|
|
||||||
// GPS home frame
|
// GPS home frame
|
||||||
static const blackboxGPSFieldDefinition_t blackboxGpsHFields[] = {
|
static const blackboxGPSFieldDefinition_t blackboxGpsHFields[] = {
|
||||||
{"GPS_home[0]", SIGNED, PREDICT(0), ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
|
{"GPS_home", 0, SIGNED, PREDICT(0), ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
|
||||||
{"GPS_home[1]", SIGNED, PREDICT(0), ENCODING(SIGNED_VB), CONDITION(ALWAYS)}
|
{"GPS_home", 1, SIGNED, PREDICT(0), ENCODING(SIGNED_VB), CONDITION(ALWAYS)}
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -282,6 +297,7 @@ static uint32_t blackboxPFrameIndex, blackboxIFrameIndex;
|
||||||
* to encode:
|
* to encode:
|
||||||
*/
|
*/
|
||||||
static uint16_t vbatReference;
|
static uint16_t vbatReference;
|
||||||
|
|
||||||
static gpsState_t gpsHistory;
|
static gpsState_t gpsHistory;
|
||||||
|
|
||||||
// Keep a history of length 2, plus a buffer for MW to store the new values into
|
// Keep a history of length 2, plus a buffer for MW to store the new values into
|
||||||
|
@ -602,13 +618,23 @@ static void validateBlackboxConfig()
|
||||||
masterConfig.blackbox_rate_num = 1;
|
masterConfig.blackbox_rate_num = 1;
|
||||||
masterConfig.blackbox_rate_denom = 1;
|
masterConfig.blackbox_rate_denom = 1;
|
||||||
} else {
|
} else {
|
||||||
|
/* Reduce the fraction the user entered as much as possible (makes the recorded/skipped frame pattern repeat
|
||||||
|
* itself more frequently)
|
||||||
|
*/
|
||||||
div = gcd(masterConfig.blackbox_rate_num, masterConfig.blackbox_rate_denom);
|
div = gcd(masterConfig.blackbox_rate_num, masterConfig.blackbox_rate_denom);
|
||||||
|
|
||||||
masterConfig.blackbox_rate_num /= div;
|
masterConfig.blackbox_rate_num /= div;
|
||||||
masterConfig.blackbox_rate_denom /= div;
|
masterConfig.blackbox_rate_denom /= div;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (masterConfig.blackbox_device >= BLACKBOX_DEVICE_END) {
|
||||||
|
masterConfig.blackbox_device = BLACKBOX_DEVICE_SERIAL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start Blackbox logging if it is not already running. Intended to be called upon arming.
|
||||||
|
*/
|
||||||
void startBlackbox(void)
|
void startBlackbox(void)
|
||||||
{
|
{
|
||||||
if (blackboxState == BLACKBOX_STATE_STOPPED) {
|
if (blackboxState == BLACKBOX_STATE_STOPPED) {
|
||||||
|
@ -640,6 +666,9 @@ void startBlackbox(void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begin Blackbox shutdown.
|
||||||
|
*/
|
||||||
void finishBlackbox(void)
|
void finishBlackbox(void)
|
||||||
{
|
{
|
||||||
if (blackboxState == BLACKBOX_STATE_RUNNING) {
|
if (blackboxState == BLACKBOX_STATE_RUNNING) {
|
||||||
|
@ -747,8 +776,10 @@ static void loadBlackboxState(void)
|
||||||
blackboxCurrent->BaroAlt = BaroAlt;
|
blackboxCurrent->BaroAlt = BaroAlt;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_SERVOS
|
||||||
//Tail servo for tricopters
|
//Tail servo for tricopters
|
||||||
blackboxCurrent->servo[5] = servo[5];
|
blackboxCurrent->servo[5] = servo[5];
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -787,7 +818,8 @@ static bool sendFieldDefinition(const char * const *headerNames, unsigned int he
|
||||||
|
|
||||||
charsWritten = blackboxPrint("H Field ");
|
charsWritten = blackboxPrint("H Field ");
|
||||||
charsWritten += blackboxPrint(headerNames[xmitState.headerIndex]);
|
charsWritten += blackboxPrint(headerNames[xmitState.headerIndex]);
|
||||||
charsWritten += blackboxPrint(":");
|
blackboxWrite(':');
|
||||||
|
charsWritten++;
|
||||||
|
|
||||||
xmitState.u.fieldIndex++;
|
xmitState.u.fieldIndex++;
|
||||||
needComma = false;
|
needComma = false;
|
||||||
|
@ -808,6 +840,15 @@ static bool sendFieldDefinition(const char * const *headerNames, unsigned int he
|
||||||
// The first header is a field name
|
// The first header is a field name
|
||||||
if (xmitState.headerIndex == 0) {
|
if (xmitState.headerIndex == 0) {
|
||||||
charsWritten += blackboxPrint(def->name);
|
charsWritten += blackboxPrint(def->name);
|
||||||
|
|
||||||
|
// Do we need to print an index in brackets after the name?
|
||||||
|
if (def->fieldNameIndex != -1) {
|
||||||
|
blackboxWrite('[');
|
||||||
|
// Assume the field index is a single digit:
|
||||||
|
blackboxWrite(def->fieldNameIndex + '0');
|
||||||
|
blackboxWrite(']');
|
||||||
|
charsWritten += 3;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
//The other headers are integers
|
//The other headers are integers
|
||||||
if (def->arr[xmitState.headerIndex - 1] >= 10) {
|
if (def->arr[xmitState.headerIndex - 1] >= 10) {
|
||||||
|
@ -838,11 +879,6 @@ static bool sendFieldDefinition(const char * const *headerNames, unsigned int he
|
||||||
*/
|
*/
|
||||||
static bool blackboxWriteSysinfo()
|
static bool blackboxWriteSysinfo()
|
||||||
{
|
{
|
||||||
union floatConvert_t {
|
|
||||||
float f;
|
|
||||||
uint32_t u;
|
|
||||||
} floatConvert;
|
|
||||||
|
|
||||||
if (xmitState.headerIndex == 0) {
|
if (xmitState.headerIndex == 0) {
|
||||||
xmitState.u.serialBudget = 0;
|
xmitState.u.serialBudget = 0;
|
||||||
xmitState.headerIndex = 1;
|
xmitState.headerIndex = 1;
|
||||||
|
@ -861,26 +897,24 @@ static bool blackboxWriteSysinfo()
|
||||||
//Shouldn't ever get here
|
//Shouldn't ever get here
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
blackboxPrintf("H Firmware type:Cleanflight\n");
|
xmitState.u.serialBudget -= blackboxPrint("H Firmware type:Cleanflight\n");
|
||||||
|
|
||||||
xmitState.u.serialBudget -= strlen("H Firmware type:Cleanflight\n");
|
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
blackboxPrintf("H Firmware revision:%s\n", shortGitRevision);
|
blackboxPrintf("H Firmware revision:%s\n", shortGitRevision);
|
||||||
|
|
||||||
/* Don't need to be super exact about the budget so don't mind the fact that we're including the length of
|
xmitState.u.serialBudget -= strlen("H Firmware revision:\n") + strlen(shortGitRevision);
|
||||||
* the placeholder "%s"
|
|
||||||
*/
|
|
||||||
xmitState.u.serialBudget -= strlen("H Firmware revision:%s\n") + strlen(shortGitRevision);
|
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
blackboxPrintf("H Firmware date:%s %s\n", buildDate, buildTime);
|
blackboxPrintf("H Firmware date:%s %s\n", buildDate, buildTime);
|
||||||
|
|
||||||
xmitState.u.serialBudget -= strlen("H Firmware date:%s %s\n") + strlen(buildDate) + strlen(buildTime);
|
xmitState.u.serialBudget -= strlen("H Firmware date: \n") + strlen(buildDate) + strlen(buildTime);
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
blackboxPrintf("H P interval:%d/%d\n", masterConfig.blackbox_rate_num, masterConfig.blackbox_rate_denom);
|
blackboxPrintf("H P interval:%d/%d\n", masterConfig.blackbox_rate_num, masterConfig.blackbox_rate_denom);
|
||||||
|
|
||||||
|
/* Don't need to be super exact about the budget so don't mind the fact that we're using the length of
|
||||||
|
* the placeholder "%d" instead of the actual integer size.
|
||||||
|
*/
|
||||||
xmitState.u.serialBudget -= strlen("H P interval:%d/%d\n");
|
xmitState.u.serialBudget -= strlen("H P interval:%d/%d\n");
|
||||||
break;
|
break;
|
||||||
case 5:
|
case 5:
|
||||||
|
@ -899,10 +933,9 @@ static bool blackboxWriteSysinfo()
|
||||||
xmitState.u.serialBudget -= strlen("H maxthrottle:%d\n");
|
xmitState.u.serialBudget -= strlen("H maxthrottle:%d\n");
|
||||||
break;
|
break;
|
||||||
case 8:
|
case 8:
|
||||||
floatConvert.f = gyro.scale;
|
blackboxPrintf("H gyro.scale:0x%x\n", castFloatBytesToInt(gyro.scale));
|
||||||
blackboxPrintf("H gyro.scale:0x%x\n", floatConvert.u);
|
|
||||||
|
|
||||||
xmitState.u.serialBudget -= strlen("H gyro.scale:0x%x\n") + 6;
|
xmitState.u.serialBudget -= strlen("H gyro.scale:0x\n") + 6;
|
||||||
break;
|
break;
|
||||||
case 9:
|
case 9:
|
||||||
blackboxPrintf("H acc_1G:%u\n", acc_1G);
|
blackboxPrintf("H acc_1G:%u\n", acc_1G);
|
||||||
|
@ -1002,6 +1035,9 @@ static void blackboxPlaySyncBeep()
|
||||||
blackboxLogEvent(FLIGHT_LOG_EVENT_SYNC_BEEP, (flightLogEventData_t *) &eventData);
|
blackboxLogEvent(FLIGHT_LOG_EVENT_SYNC_BEEP, (flightLogEventData_t *) &eventData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call each flight loop iteration to perform blackbox logging.
|
||||||
|
*/
|
||||||
void handleBlackbox(void)
|
void handleBlackbox(void)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
@ -1074,6 +1110,9 @@ void handleBlackbox(void)
|
||||||
loadBlackboxState();
|
loadBlackboxState();
|
||||||
writeIntraframe();
|
writeIntraframe();
|
||||||
} else {
|
} else {
|
||||||
|
/* Adding a magic shift of "masterConfig.blackbox_rate_num - 1" in here creates a better spread of
|
||||||
|
* recorded / skipped frames when the I frame's position is considered:
|
||||||
|
*/
|
||||||
if ((blackboxPFrameIndex + masterConfig.blackbox_rate_num - 1) % masterConfig.blackbox_rate_denom < masterConfig.blackbox_rate_num) {
|
if ((blackboxPFrameIndex + masterConfig.blackbox_rate_num - 1) % masterConfig.blackbox_rate_denom < masterConfig.blackbox_rate_num) {
|
||||||
loadBlackboxState();
|
loadBlackboxState();
|
||||||
writeInterframe();
|
writeInterframe();
|
||||||
|
@ -1118,7 +1157,7 @@ void handleBlackbox(void)
|
||||||
*
|
*
|
||||||
* Don't wait longer than it could possibly take if something funky happens.
|
* Don't wait longer than it could possibly take if something funky happens.
|
||||||
*/
|
*/
|
||||||
if (millis() > xmitState.u.startTime + 200 || isBlackboxDeviceIdle()) {
|
if (millis() > xmitState.u.startTime + BLACKBOX_SHUTDOWN_TIMEOUT_MILLIS || blackboxDeviceFlush()) {
|
||||||
blackboxDeviceClose();
|
blackboxDeviceClose();
|
||||||
blackboxSetState(BLACKBOX_STATE_STOPPED);
|
blackboxSetState(BLACKBOX_STATE_STOPPED);
|
||||||
}
|
}
|
||||||
|
@ -1126,6 +1165,11 @@ void handleBlackbox(void)
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Did we run out of room on the device? Stop!
|
||||||
|
if (isBlackboxDeviceFull()) {
|
||||||
|
blackboxSetState(BLACKBOX_STATE_STOPPED);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool canUseBlackboxWithCurrentConfiguration(void)
|
static bool canUseBlackboxWithCurrentConfiguration(void)
|
||||||
|
@ -1133,6 +1177,9 @@ static bool canUseBlackboxWithCurrentConfiguration(void)
|
||||||
return feature(FEATURE_BLACKBOX);
|
return feature(FEATURE_BLACKBOX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call during system startup to initialize the blackbox.
|
||||||
|
*/
|
||||||
void initBlackbox(void)
|
void initBlackbox(void)
|
||||||
{
|
{
|
||||||
if (canUseBlackboxWithCurrentConfiguration()) {
|
if (canUseBlackboxWithCurrentConfiguration()) {
|
||||||
|
@ -1141,3 +1188,5 @@ void initBlackbox(void)
|
||||||
blackboxSetState(BLACKBOX_STATE_DISABLED);
|
blackboxSetState(BLACKBOX_STATE_DISABLED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
#include "blackbox_io.h"
|
#include "blackbox_io.h"
|
||||||
|
|
||||||
#include "platform.h"
|
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
#include "build_config.h"
|
||||||
|
|
||||||
#include "common/maths.h"
|
#include "common/maths.h"
|
||||||
#include "common/axis.h"
|
#include "common/axis.h"
|
||||||
#include "common/color.h"
|
#include "common/color.h"
|
||||||
|
#include "common/encoding.h"
|
||||||
|
|
||||||
#include "drivers/gpio.h"
|
#include "drivers/gpio.h"
|
||||||
#include "drivers/sensor.h"
|
#include "drivers/sensor.h"
|
||||||
|
@ -56,6 +58,10 @@
|
||||||
#include "config/config_profile.h"
|
#include "config/config_profile.h"
|
||||||
#include "config/config_master.h"
|
#include "config/config_master.h"
|
||||||
|
|
||||||
|
#include "io/flashfs.h"
|
||||||
|
|
||||||
|
#ifdef BLACKBOX
|
||||||
|
|
||||||
#define BLACKBOX_BAUDRATE 115200
|
#define BLACKBOX_BAUDRATE 115200
|
||||||
#define BLACKBOX_INITIAL_PORT_MODE MODE_TX
|
#define BLACKBOX_INITIAL_PORT_MODE MODE_TX
|
||||||
|
|
||||||
|
@ -68,7 +74,17 @@ static portSharing_e blackboxPortSharing;
|
||||||
|
|
||||||
void blackboxWrite(uint8_t value)
|
void blackboxWrite(uint8_t value)
|
||||||
{
|
{
|
||||||
serialWrite(blackboxPort, value);
|
switch (masterConfig.blackbox_device) {
|
||||||
|
#ifdef USE_FLASHFS
|
||||||
|
case BLACKBOX_DEVICE_FLASH:
|
||||||
|
flashfsWriteByte(value); // Write byte asynchronously
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
case BLACKBOX_DEVICE_SERIAL:
|
||||||
|
default:
|
||||||
|
serialWrite(blackboxPort, value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _putc(void *p, char c)
|
static void _putc(void *p, char c)
|
||||||
|
@ -89,14 +105,31 @@ void blackboxPrintf(char *fmt, ...)
|
||||||
// Print the null-terminated string 's' to the serial port and return the number of bytes written
|
// Print the null-terminated string 's' to the serial port and return the number of bytes written
|
||||||
int blackboxPrint(const char *s)
|
int blackboxPrint(const char *s)
|
||||||
{
|
{
|
||||||
const char *pos = s;
|
int length;
|
||||||
|
const uint8_t *pos;
|
||||||
|
|
||||||
while (*pos) {
|
switch (masterConfig.blackbox_device) {
|
||||||
blackboxWrite(*pos);
|
|
||||||
pos++;
|
#ifdef USE_FLASHFS
|
||||||
|
case BLACKBOX_DEVICE_FLASH:
|
||||||
|
length = strlen(s);
|
||||||
|
flashfsWrite((const uint8_t*) s, length, false); // Write asynchronously
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
case BLACKBOX_DEVICE_SERIAL:
|
||||||
|
default:
|
||||||
|
pos = (uint8_t*) s;
|
||||||
|
while (*pos) {
|
||||||
|
serialWrite(blackboxPort, *pos);
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
length = pos - (uint8_t*) s;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return pos - s;
|
return length;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -118,7 +151,7 @@ void blackboxWriteUnsignedVB(uint32_t value)
|
||||||
void blackboxWriteSignedVB(int32_t value)
|
void blackboxWriteSignedVB(int32_t value)
|
||||||
{
|
{
|
||||||
//ZigZag encode to make the value always positive
|
//ZigZag encode to make the value always positive
|
||||||
blackboxWriteUnsignedVB((uint32_t)((value << 1) ^ (value >> 31)));
|
blackboxWriteUnsignedVB(zigzagEncode(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
void blackboxWriteS16(int16_t value)
|
void blackboxWriteS16(int16_t value)
|
||||||
|
@ -375,11 +408,25 @@ void blackboxWriteTag8_8SVB(int32_t *values, int valueCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If there is data waiting to be written to the blackbox device, attempt to write that now.
|
* If there is data waiting to be written to the blackbox device, attempt to write (a portion of) that now.
|
||||||
|
*
|
||||||
|
* Returns true if all data has been flushed to the device.
|
||||||
*/
|
*/
|
||||||
void blackboxDeviceFlush(void)
|
bool blackboxDeviceFlush(void)
|
||||||
{
|
{
|
||||||
//Presently a no-op on serial
|
switch (masterConfig.blackbox_device) {
|
||||||
|
case BLACKBOX_DEVICE_SERIAL:
|
||||||
|
//Nothing to speed up flushing on serial, as serial is continuously being drained out of its buffer
|
||||||
|
return isSerialTransmitBufferEmpty(blackboxPort);
|
||||||
|
|
||||||
|
#ifdef USE_FLASHFS
|
||||||
|
case BLACKBOX_DEVICE_FLASH:
|
||||||
|
return flashfsFlushAsync();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -387,18 +434,6 @@ void blackboxDeviceFlush(void)
|
||||||
*/
|
*/
|
||||||
bool blackboxDeviceOpen(void)
|
bool blackboxDeviceOpen(void)
|
||||||
{
|
{
|
||||||
serialPortConfig_t *portConfig = findSerialPortConfig(FUNCTION_BLACKBOX);
|
|
||||||
if (!portConfig) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
blackboxPortSharing = determinePortSharing(portConfig, FUNCTION_BLACKBOX);
|
|
||||||
|
|
||||||
|
|
||||||
blackboxPort = openSerialPort(portConfig->identifier, FUNCTION_BLACKBOX, NULL, BLACKBOX_BAUDRATE, BLACKBOX_INITIAL_PORT_MODE, SERIAL_NOT_INVERTED);
|
|
||||||
if (!blackboxPort) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We want to write at about 7200 bytes per second to give the OpenLog a good chance to save to disk. If
|
* We want to write at about 7200 bytes per second to give the OpenLog a good chance to save to disk. If
|
||||||
* about looptime microseconds elapse between our writes, this is the budget of how many bytes we should
|
* about looptime microseconds elapse between our writes, this is the budget of how many bytes we should
|
||||||
|
@ -408,24 +443,73 @@ bool blackboxDeviceOpen(void)
|
||||||
*/
|
*/
|
||||||
blackboxWriteChunkSize = MAX((masterConfig.looptime * 9) / 1250, 4);
|
blackboxWriteChunkSize = MAX((masterConfig.looptime * 9) / 1250, 4);
|
||||||
|
|
||||||
return true;
|
switch (masterConfig.blackbox_device) {
|
||||||
}
|
case BLACKBOX_DEVICE_SERIAL:
|
||||||
|
{
|
||||||
|
serialPortConfig_t *portConfig = findSerialPortConfig(FUNCTION_BLACKBOX);
|
||||||
|
if (!portConfig) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
blackboxPortSharing = determinePortSharing(portConfig, FUNCTION_BLACKBOX);
|
||||||
|
|
||||||
void blackboxDeviceClose(void)
|
blackboxPort = openSerialPort(portConfig->identifier, FUNCTION_BLACKBOX, NULL, BLACKBOX_BAUDRATE, BLACKBOX_INITIAL_PORT_MODE, SERIAL_NOT_INVERTED);
|
||||||
{
|
return blackboxPort != NULL;
|
||||||
closeSerialPort(blackboxPort);
|
}
|
||||||
blackboxPort = NULL;
|
break;
|
||||||
|
#ifdef USE_FLASHFS
|
||||||
/*
|
case BLACKBOX_DEVICE_FLASH:
|
||||||
* Normally this would be handled by mw.c, but since we take an unknown amount
|
if (flashfsGetSize() == 0 || isBlackboxDeviceFull()) {
|
||||||
* of time to shut down asynchronously, we're the only ones that know when to call it.
|
return false;
|
||||||
*/
|
}
|
||||||
if (blackboxPortSharing == PORTSHARING_SHARED) {
|
|
||||||
mspAllocateSerialPorts(&masterConfig.serialConfig);
|
return true;
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isBlackboxDeviceIdle(void)
|
/**
|
||||||
|
* Close the Blackbox logging device immediately without attempting to flush any remaining data.
|
||||||
|
*/
|
||||||
|
void blackboxDeviceClose(void)
|
||||||
{
|
{
|
||||||
return isSerialTransmitBufferEmpty(blackboxPort);
|
switch (masterConfig.blackbox_device) {
|
||||||
|
case BLACKBOX_DEVICE_SERIAL:
|
||||||
|
closeSerialPort(blackboxPort);
|
||||||
|
blackboxPort = NULL;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Normally this would be handled by mw.c, but since we take an unknown amount
|
||||||
|
* of time to shut down asynchronously, we're the only ones that know when to call it.
|
||||||
|
*/
|
||||||
|
if (blackboxPortSharing == PORTSHARING_SHARED) {
|
||||||
|
mspAllocateSerialPorts(&masterConfig.serialConfig);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#ifdef USE_FLASHFS
|
||||||
|
case BLACKBOX_DEVICE_FLASH:
|
||||||
|
// No-op since the flash doesn't have a "close" and there's nobody else to hand control of it to.
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isBlackboxDeviceFull(void)
|
||||||
|
{
|
||||||
|
switch (masterConfig.blackbox_device) {
|
||||||
|
case BLACKBOX_DEVICE_SERIAL:
|
||||||
|
return false;
|
||||||
|
|
||||||
|
#ifdef USE_FLASHFS
|
||||||
|
case BLACKBOX_DEVICE_FLASH:
|
||||||
|
return flashfsIsEOF();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
|
@ -20,6 +20,18 @@
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "platform.h"
|
||||||
|
|
||||||
|
typedef enum BlackboxDevice {
|
||||||
|
BLACKBOX_DEVICE_SERIAL = 0,
|
||||||
|
|
||||||
|
#ifdef USE_FLASHFS
|
||||||
|
BLACKBOX_DEVICE_FLASH,
|
||||||
|
#endif
|
||||||
|
|
||||||
|
BLACKBOX_DEVICE_END
|
||||||
|
} BlackboxDevice;
|
||||||
|
|
||||||
uint8_t blackboxWriteChunkSize;
|
uint8_t blackboxWriteChunkSize;
|
||||||
|
|
||||||
void blackboxWrite(uint8_t value);
|
void blackboxWrite(uint8_t value);
|
||||||
|
@ -34,8 +46,8 @@ void blackboxWriteTag2_3S32(int32_t *values);
|
||||||
void blackboxWriteTag8_4S16(int32_t *values);
|
void blackboxWriteTag8_4S16(int32_t *values);
|
||||||
void blackboxWriteTag8_8SVB(int32_t *values, int valueCount);
|
void blackboxWriteTag8_8SVB(int32_t *values, int valueCount);
|
||||||
|
|
||||||
void blackboxDeviceFlush(void);
|
bool blackboxDeviceFlush(void);
|
||||||
bool blackboxDeviceOpen(void);
|
bool blackboxDeviceOpen(void);
|
||||||
void blackboxDeviceClose(void);
|
void blackboxDeviceClose(void);
|
||||||
|
|
||||||
bool isBlackboxDeviceIdle(void);
|
bool isBlackboxDeviceFull(void);
|
||||||
|
|
31
src/main/common/encoding.c
Normal file
31
src/main/common/encoding.c
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
#include "encoding.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cast the in-memory representation of the given float directly to an int.
|
||||||
|
*
|
||||||
|
* This is useful for printing the hex representation of a float number (which is considerably cheaper
|
||||||
|
* than a full decimal float formatter, in both code size and output length).
|
||||||
|
*/
|
||||||
|
uint32_t castFloatBytesToInt(float f)
|
||||||
|
{
|
||||||
|
union floatConvert_t {
|
||||||
|
float f;
|
||||||
|
uint32_t u;
|
||||||
|
} floatConvert;
|
||||||
|
|
||||||
|
floatConvert.f = f;
|
||||||
|
|
||||||
|
return floatConvert.u;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ZigZag encoding maps all values of a signed integer into those of an unsigned integer in such
|
||||||
|
* a way that numbers of small absolute value correspond to small integers in the result.
|
||||||
|
*
|
||||||
|
* (Compared to just casting a signed to an unsigned which creates huge resulting numbers for
|
||||||
|
* small negative integers).
|
||||||
|
*/
|
||||||
|
uint32_t zigzagEncode(int32_t value)
|
||||||
|
{
|
||||||
|
return (uint32_t)((value << 1) ^ (value >> 31));
|
||||||
|
}
|
23
src/main/common/encoding.h
Normal file
23
src/main/common/encoding.h
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Cleanflight.
|
||||||
|
*
|
||||||
|
* Cleanflight is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Cleanflight is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
uint32_t castFloatBytesToInt(float f);
|
||||||
|
uint32_t zigzagEncode(int32_t value);
|
|
@ -74,9 +74,17 @@
|
||||||
#define BRUSHLESS_MOTORS_PWM_RATE 400
|
#define BRUSHLESS_MOTORS_PWM_RATE 400
|
||||||
|
|
||||||
void setPIDController(int type); // FIXME PID code needs to be in flight_pid.c/h
|
void setPIDController(int type); // FIXME PID code needs to be in flight_pid.c/h
|
||||||
void mixerUseConfigs(servoParam_t *servoConfToUse, flight3DConfig_t *flight3DConfigToUse,
|
void mixerUseConfigs(
|
||||||
escAndServoConfig_t *escAndServoConfigToUse, mixerConfig_t *mixerConfigToUse,
|
#ifdef USE_SERVOS
|
||||||
airplaneConfig_t *airplaneConfigToUse, rxConfig_t *rxConfig, gimbalConfig_t *gimbalConfigToUse);
|
servoParam_t *servoConfToUse,
|
||||||
|
gimbalConfig_t *gimbalConfigToUse,
|
||||||
|
#endif
|
||||||
|
flight3DConfig_t *flight3DConfigToUse,
|
||||||
|
escAndServoConfig_t *escAndServoConfigToUse,
|
||||||
|
mixerConfig_t *mixerConfigToUse,
|
||||||
|
airplaneConfig_t *airplaneConfigToUse,
|
||||||
|
rxConfig_t *rxConfig
|
||||||
|
);
|
||||||
void useRcControlsConfig(modeActivationCondition_t *modeActivationConditions, escAndServoConfig_t *escAndServoConfigToUse, pidProfile_t *pidProfileToUse);
|
void useRcControlsConfig(modeActivationCondition_t *modeActivationConditions, escAndServoConfig_t *escAndServoConfigToUse, pidProfile_t *pidProfileToUse);
|
||||||
|
|
||||||
#define FLASH_TO_RESERVE_FOR_CONFIG 0x800
|
#define FLASH_TO_RESERVE_FOR_CONFIG 0x800
|
||||||
|
@ -111,7 +119,7 @@ profile_t *currentProfile;
|
||||||
static uint8_t currentControlRateProfileIndex = 0;
|
static uint8_t currentControlRateProfileIndex = 0;
|
||||||
controlRateConfig_t *currentControlRateProfile;
|
controlRateConfig_t *currentControlRateProfile;
|
||||||
|
|
||||||
static const uint8_t EEPROM_CONF_VERSION = 92;
|
static const uint8_t EEPROM_CONF_VERSION = 93;
|
||||||
|
|
||||||
static void resetAccelerometerTrims(flightDynamicsTrims_t *accelerometerTrims)
|
static void resetAccelerometerTrims(flightDynamicsTrims_t *accelerometerTrims)
|
||||||
{
|
{
|
||||||
|
@ -310,7 +318,10 @@ static void setControlRateProfile(uint8_t profileIndex)
|
||||||
static void resetConf(void)
|
static void resetConf(void)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
int8_t servoRates[8] = { 30, 30, 100, 100, 100, 100, 100, 100 };
|
#ifdef USE_SERVOS
|
||||||
|
int8_t servoRates[MAX_SUPPORTED_SERVOS] = { 30, 30, 100, 100, 100, 100, 100, 100 };
|
||||||
|
;
|
||||||
|
#endif
|
||||||
|
|
||||||
// Clear all configuration
|
// Clear all configuration
|
||||||
memset(&masterConfig, 0, sizeof(master_t));
|
memset(&masterConfig, 0, sizeof(master_t));
|
||||||
|
@ -425,8 +436,9 @@ static void resetConf(void)
|
||||||
currentProfile->failsafeConfig.failsafe_min_usec = 985; // any of first 4 channels below this value will trigger failsafe
|
currentProfile->failsafeConfig.failsafe_min_usec = 985; // any of first 4 channels below this value will trigger failsafe
|
||||||
currentProfile->failsafeConfig.failsafe_max_usec = 2115; // any of first 4 channels above this value will trigger failsafe
|
currentProfile->failsafeConfig.failsafe_max_usec = 2115; // any of first 4 channels above this value will trigger failsafe
|
||||||
|
|
||||||
|
#ifdef USE_SERVOS
|
||||||
// servos
|
// servos
|
||||||
for (i = 0; i < 8; i++) {
|
for (i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
|
||||||
currentProfile->servoConf[i].min = DEFAULT_SERVO_MIN;
|
currentProfile->servoConf[i].min = DEFAULT_SERVO_MIN;
|
||||||
currentProfile->servoConf[i].max = DEFAULT_SERVO_MAX;
|
currentProfile->servoConf[i].max = DEFAULT_SERVO_MAX;
|
||||||
currentProfile->servoConf[i].middle = DEFAULT_SERVO_MIDDLE;
|
currentProfile->servoConf[i].middle = DEFAULT_SERVO_MIDDLE;
|
||||||
|
@ -436,9 +448,12 @@ static void resetConf(void)
|
||||||
|
|
||||||
currentProfile->mixerConfig.yaw_direction = 1;
|
currentProfile->mixerConfig.yaw_direction = 1;
|
||||||
currentProfile->mixerConfig.tri_unarmed_servo = 1;
|
currentProfile->mixerConfig.tri_unarmed_servo = 1;
|
||||||
|
currentProfile->mixerConfig.servo_lowpass_freq = 400;
|
||||||
|
currentProfile->mixerConfig.servo_lowpass_enable = 0;
|
||||||
|
|
||||||
// gimbal
|
// gimbal
|
||||||
currentProfile->gimbalConfig.gimbal_flags = GIMBAL_NORMAL;
|
currentProfile->gimbalConfig.gimbal_flags = GIMBAL_NORMAL;
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef GPS
|
#ifdef GPS
|
||||||
resetGpsProfile(¤tProfile->gpsProfile);
|
resetGpsProfile(¤tProfile->gpsProfile);
|
||||||
|
@ -454,6 +469,7 @@ static void resetConf(void)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef BLACKBOX
|
#ifdef BLACKBOX
|
||||||
|
masterConfig.blackbox_device = 0;
|
||||||
masterConfig.blackbox_rate_num = 1;
|
masterConfig.blackbox_rate_num = 1;
|
||||||
masterConfig.blackbox_rate_denom = 1;
|
masterConfig.blackbox_rate_denom = 1;
|
||||||
#endif
|
#endif
|
||||||
|
@ -595,6 +611,8 @@ void activateConfig(void)
|
||||||
|
|
||||||
activateControlRateConfig();
|
activateControlRateConfig();
|
||||||
|
|
||||||
|
resetAdjustmentStates();
|
||||||
|
|
||||||
useRcControlsConfig(
|
useRcControlsConfig(
|
||||||
currentProfile->modeActivationConditions,
|
currentProfile->modeActivationConditions,
|
||||||
&masterConfig.escAndServoConfig,
|
&masterConfig.escAndServoConfig,
|
||||||
|
@ -618,13 +636,15 @@ void activateConfig(void)
|
||||||
setAccelerationTrims(&masterConfig.accZero);
|
setAccelerationTrims(&masterConfig.accZero);
|
||||||
|
|
||||||
mixerUseConfigs(
|
mixerUseConfigs(
|
||||||
|
#ifdef USE_SERVOS
|
||||||
currentProfile->servoConf,
|
currentProfile->servoConf,
|
||||||
|
¤tProfile->gimbalConfig,
|
||||||
|
#endif
|
||||||
&masterConfig.flight3DConfig,
|
&masterConfig.flight3DConfig,
|
||||||
&masterConfig.escAndServoConfig,
|
&masterConfig.escAndServoConfig,
|
||||||
¤tProfile->mixerConfig,
|
¤tProfile->mixerConfig,
|
||||||
&masterConfig.airplaneConfig,
|
&masterConfig.airplaneConfig,
|
||||||
&masterConfig.rxConfig,
|
&masterConfig.rxConfig
|
||||||
¤tProfile->gimbalConfig
|
|
||||||
);
|
);
|
||||||
|
|
||||||
imuRuntimeConfig.gyro_cmpf_factor = masterConfig.gyro_cmpf_factor;
|
imuRuntimeConfig.gyro_cmpf_factor = masterConfig.gyro_cmpf_factor;
|
||||||
|
|
|
@ -88,6 +88,7 @@ typedef struct master_t {
|
||||||
#ifdef BLACKBOX
|
#ifdef BLACKBOX
|
||||||
uint8_t blackbox_rate_num;
|
uint8_t blackbox_rate_num;
|
||||||
uint8_t blackbox_rate_denom;
|
uint8_t blackbox_rate_denom;
|
||||||
|
uint8_t blackbox_device;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
uint8_t magic_ef; // magic number, should be 0xEF
|
uint8_t magic_ef; // magic number, should be 0xEF
|
||||||
|
|
|
@ -47,8 +47,12 @@ typedef struct profile_s {
|
||||||
uint16_t throttle_correction_angle; // the angle when the throttle correction is maximal. in 0.1 degres, ex 225 = 22.5 ,30.0, 450 = 45.0 deg
|
uint16_t throttle_correction_angle; // the angle when the throttle correction is maximal. in 0.1 degres, ex 225 = 22.5 ,30.0, 450 = 45.0 deg
|
||||||
uint8_t throttle_correction_value; // the correction that will be applied at throttle_correction_angle.
|
uint8_t throttle_correction_value; // the correction that will be applied at throttle_correction_angle.
|
||||||
|
|
||||||
|
#ifdef USE_SERVOS
|
||||||
// Servo-related stuff
|
// Servo-related stuff
|
||||||
servoParam_t servoConf[MAX_SUPPORTED_SERVOS]; // servo configuration
|
servoParam_t servoConf[MAX_SUPPORTED_SERVOS]; // servo configuration
|
||||||
|
// gimbal-related configuration
|
||||||
|
gimbalConfig_t gimbalConfig;
|
||||||
|
#endif
|
||||||
|
|
||||||
// Failsafe related configuration
|
// Failsafe related configuration
|
||||||
failsafeConfig_t failsafeConfig;
|
failsafeConfig_t failsafeConfig;
|
||||||
|
@ -56,9 +60,6 @@ typedef struct profile_s {
|
||||||
// mixer-related configuration
|
// mixer-related configuration
|
||||||
mixerConfig_t mixerConfig;
|
mixerConfig_t mixerConfig;
|
||||||
|
|
||||||
// gimbal-related configuration
|
|
||||||
gimbalConfig_t gimbalConfig;
|
|
||||||
|
|
||||||
#ifdef GPS
|
#ifdef GPS
|
||||||
gpsProfile_t gpsProfile;
|
gpsProfile_t gpsProfile;
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -68,39 +68,12 @@ extern int16_t debug[4];
|
||||||
|
|
||||||
#define BOOT ((uint8_t)0x80)
|
#define BOOT ((uint8_t)0x80)
|
||||||
|
|
||||||
#define SPI1_GPIO GPIOA
|
|
||||||
#define SPI1_SCK_PIN GPIO_Pin_5
|
|
||||||
#define SPI1_SCK_PIN_SOURCE GPIO_PinSource5
|
|
||||||
#define SPI1_SCK_CLK RCC_AHBPeriph_GPIOA
|
|
||||||
#define SPI1_MISO_PIN GPIO_Pin_6
|
|
||||||
#define SPI1_MISO_PIN_SOURCE GPIO_PinSource6
|
|
||||||
#define SPI1_MISO_CLK RCC_AHBPeriph_GPIOA
|
|
||||||
#define SPI1_MOSI_PIN GPIO_Pin_7
|
|
||||||
#define SPI1_MOSI_PIN_SOURCE GPIO_PinSource7
|
|
||||||
#define SPI1_MOSI_CLK RCC_AHBPeriph_GPIOA
|
|
||||||
|
|
||||||
static void l3gd20SpiInit(SPI_TypeDef *SPIx)
|
static void l3gd20SpiInit(SPI_TypeDef *SPIx)
|
||||||
{
|
{
|
||||||
GPIO_InitTypeDef GPIO_InitStructure;
|
GPIO_InitTypeDef GPIO_InitStructure;
|
||||||
SPI_InitTypeDef SPI_InitStructure;
|
SPI_InitTypeDef SPI_InitStructure;
|
||||||
|
|
||||||
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
|
RCC_AHBPeriphClockCmd(L3GD20_CS_GPIO_CLK_PERIPHERAL, ENABLE);
|
||||||
RCC_AHBPeriphClockCmd(SPI1_SCK_CLK | SPI1_MISO_CLK | SPI1_MOSI_CLK, ENABLE);
|
|
||||||
|
|
||||||
GPIO_PinAFConfig(SPI1_GPIO, SPI1_SCK_PIN_SOURCE, GPIO_AF_5);
|
|
||||||
GPIO_PinAFConfig(SPI1_GPIO, SPI1_MISO_PIN_SOURCE, GPIO_AF_5);
|
|
||||||
GPIO_PinAFConfig(SPI1_GPIO, SPI1_MOSI_PIN_SOURCE, GPIO_AF_5);
|
|
||||||
|
|
||||||
// Init pins
|
|
||||||
GPIO_InitStructure.GPIO_Pin = SPI1_SCK_PIN | SPI1_MISO_PIN | SPI1_MOSI_PIN;
|
|
||||||
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
|
|
||||||
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
|
|
||||||
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
|
|
||||||
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
|
|
||||||
|
|
||||||
GPIO_Init(SPI1_GPIO, &GPIO_InitStructure);
|
|
||||||
|
|
||||||
RCC_AHBPeriphClockCmd(L3GD20_CS_GPIO_CLK, ENABLE);
|
|
||||||
|
|
||||||
GPIO_InitStructure.GPIO_Pin = L3GD20_CS_PIN;
|
GPIO_InitStructure.GPIO_Pin = L3GD20_CS_PIN;
|
||||||
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
|
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
|
||||||
|
@ -112,28 +85,11 @@ static void l3gd20SpiInit(SPI_TypeDef *SPIx)
|
||||||
|
|
||||||
GPIO_SetBits(L3GD20_CS_GPIO, L3GD20_CS_PIN);
|
GPIO_SetBits(L3GD20_CS_GPIO, L3GD20_CS_PIN);
|
||||||
|
|
||||||
SPI_I2S_DeInit(SPIx);
|
spiSetDivisor(L3GD20_SPI, SPI_9MHZ_CLOCK_DIVIDER);
|
||||||
|
|
||||||
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
|
|
||||||
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
|
|
||||||
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
|
|
||||||
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
|
|
||||||
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
|
|
||||||
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
|
|
||||||
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; // 36/4 = 9 MHz SPI Clock
|
|
||||||
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
|
|
||||||
SPI_InitStructure.SPI_CRCPolynomial = 7;
|
|
||||||
|
|
||||||
SPI_Init(SPIx, &SPI_InitStructure);
|
|
||||||
|
|
||||||
SPI_RxFIFOThresholdConfig(SPIx, SPI_RxFIFOThreshold_QF);
|
|
||||||
|
|
||||||
SPI_Cmd(SPIx, ENABLE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void l3gd20GyroInit(void)
|
void l3gd20GyroInit(void)
|
||||||
{
|
{
|
||||||
|
|
||||||
l3gd20SpiInit(L3GD20_SPI);
|
l3gd20SpiInit(L3GD20_SPI);
|
||||||
|
|
||||||
GPIO_ResetBits(L3GD20_CS_GPIO, L3GD20_CS_PIN);
|
GPIO_ResetBits(L3GD20_CS_GPIO, L3GD20_CS_PIN);
|
||||||
|
|
|
@ -17,10 +17,4 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define L3GD20_SPI SPI1
|
|
||||||
|
|
||||||
#define L3GD20_CS_GPIO GPIOE
|
|
||||||
#define L3GD20_CS_PIN GPIO_Pin_3
|
|
||||||
#define L3GD20_CS_GPIO_CLK RCC_AHBPeriph_GPIOE
|
|
||||||
|
|
||||||
bool l3gd20Detect(gyro_t *gyro, uint16_t lpf);
|
bool l3gd20Detect(gyro_t *gyro, uint16_t lpf);
|
||||||
|
|
|
@ -33,22 +33,56 @@ static volatile uint16_t spi3ErrorCount = 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_SPI_DEVICE_1
|
#ifdef USE_SPI_DEVICE_1
|
||||||
|
|
||||||
|
#define SPI1_GPIO GPIOA
|
||||||
|
#define SPI1_GPIO_PERIPHERAL RCC_AHBPeriph_GPIOB
|
||||||
|
#define SPI1_SCK_PIN GPIO_Pin_5
|
||||||
|
#define SPI1_SCK_PIN_SOURCE GPIO_PinSource5
|
||||||
|
#define SPI1_SCK_CLK RCC_AHBPeriph_GPIOA
|
||||||
|
#define SPI1_MISO_PIN GPIO_Pin_6
|
||||||
|
#define SPI1_MISO_PIN_SOURCE GPIO_PinSource6
|
||||||
|
#define SPI1_MISO_CLK RCC_AHBPeriph_GPIOA
|
||||||
|
#define SPI1_MOSI_PIN GPIO_Pin_7
|
||||||
|
#define SPI1_MOSI_PIN_SOURCE GPIO_PinSource7
|
||||||
|
#define SPI1_MOSI_CLK RCC_AHBPeriph_GPIOA
|
||||||
|
|
||||||
void initSpi1(void)
|
void initSpi1(void)
|
||||||
{
|
{
|
||||||
// Specific to the STM32F103
|
// Specific to the STM32F103
|
||||||
// SPI1 Driver
|
// SPI1 Driver
|
||||||
// PA7 17 SPI1_MOSI
|
|
||||||
// PA6 16 SPI1_MISO
|
|
||||||
// PA5 15 SPI1_SCK
|
|
||||||
// PA4 14 SPI1_NSS
|
// PA4 14 SPI1_NSS
|
||||||
|
// PA5 15 SPI1_SCK
|
||||||
|
// PA6 16 SPI1_MISO
|
||||||
|
// PA7 17 SPI1_MOSI
|
||||||
|
|
||||||
gpio_config_t gpio;
|
|
||||||
SPI_InitTypeDef spi;
|
SPI_InitTypeDef spi;
|
||||||
|
|
||||||
// Enable SPI1 clock
|
// Enable SPI1 clock
|
||||||
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
|
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
|
||||||
RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1, ENABLE);
|
RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1, ENABLE);
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef STM32F303xC
|
||||||
|
GPIO_InitTypeDef GPIO_InitStructure;
|
||||||
|
|
||||||
|
RCC_AHBPeriphClockCmd(SPI1_GPIO_PERIPHERAL, ENABLE);
|
||||||
|
|
||||||
|
GPIO_PinAFConfig(SPI1_GPIO, SPI1_SCK_PIN_SOURCE, GPIO_AF_5);
|
||||||
|
GPIO_PinAFConfig(SPI1_GPIO, SPI1_MISO_PIN_SOURCE, GPIO_AF_5);
|
||||||
|
GPIO_PinAFConfig(SPI1_GPIO, SPI1_MOSI_PIN_SOURCE, GPIO_AF_5);
|
||||||
|
|
||||||
|
// Init pins
|
||||||
|
GPIO_InitStructure.GPIO_Pin = SPI1_SCK_PIN | SPI1_MISO_PIN | SPI1_MOSI_PIN;
|
||||||
|
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
|
||||||
|
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
|
||||||
|
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
|
||||||
|
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
|
||||||
|
|
||||||
|
GPIO_Init(SPI1_GPIO, &GPIO_InitStructure);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef STM32F10X
|
||||||
|
gpio_config_t gpio;
|
||||||
// MOSI + SCK as output
|
// MOSI + SCK as output
|
||||||
gpio.mode = Mode_AF_PP;
|
gpio.mode = Mode_AF_PP;
|
||||||
gpio.pin = Pin_7 | Pin_5;
|
gpio.pin = Pin_7 | Pin_5;
|
||||||
|
@ -62,8 +96,9 @@ void initSpi1(void)
|
||||||
gpio.pin = Pin_4;
|
gpio.pin = Pin_4;
|
||||||
gpio.mode = Mode_Out_PP;
|
gpio.mode = Mode_Out_PP;
|
||||||
gpioInit(GPIOA, &gpio);
|
gpioInit(GPIOA, &gpio);
|
||||||
|
#endif
|
||||||
|
|
||||||
// Init SPI2 hardware
|
// Init SPI hardware
|
||||||
SPI_I2S_DeInit(SPI1);
|
SPI_I2S_DeInit(SPI1);
|
||||||
|
|
||||||
spi.SPI_Mode = SPI_Mode_Master;
|
spi.SPI_Mode = SPI_Mode_Master;
|
||||||
|
@ -76,60 +111,116 @@ void initSpi1(void)
|
||||||
spi.SPI_CPHA = SPI_CPHA_2Edge;
|
spi.SPI_CPHA = SPI_CPHA_2Edge;
|
||||||
spi.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;
|
spi.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;
|
||||||
|
|
||||||
|
#ifdef STM32F303xC
|
||||||
|
// Configure for 8-bit reads.
|
||||||
|
SPI_RxFIFOThresholdConfig(SPI1, SPI_RxFIFOThreshold_QF);
|
||||||
|
#endif
|
||||||
|
|
||||||
SPI_Init(SPI1, &spi);
|
SPI_Init(SPI1, &spi);
|
||||||
SPI_Cmd(SPI1, ENABLE);
|
SPI_Cmd(SPI1, ENABLE);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_SPI_DEVICE_2
|
#ifdef USE_SPI_DEVICE_2
|
||||||
|
|
||||||
|
#ifndef SPI2_GPIO
|
||||||
|
#define SPI2_GPIO GPIOB
|
||||||
|
#define SPI2_GPIO_PERIPHERAL RCC_AHBPeriph_GPIOB
|
||||||
|
#define SPI2_NSS_PIN GPIO_Pin_12
|
||||||
|
#define SPI2_NSS_PIN_SOURCE GPIO_PinSource12
|
||||||
|
#define SPI2_SCK_PIN GPIO_Pin_13
|
||||||
|
#define SPI2_SCK_PIN_SOURCE GPIO_PinSource13
|
||||||
|
#define SPI2_MISO_PIN GPIO_Pin_14
|
||||||
|
#define SPI2_MISO_PIN_SOURCE GPIO_PinSource14
|
||||||
|
#define SPI2_MOSI_PIN GPIO_Pin_15
|
||||||
|
#define SPI2_MOSI_PIN_SOURCE GPIO_PinSource15
|
||||||
|
#endif
|
||||||
|
|
||||||
void initSpi2(void)
|
void initSpi2(void)
|
||||||
{
|
{
|
||||||
|
// Specific to the STM32F103 / STM32F303 (AF5)
|
||||||
// Specific to the STM32F103
|
|
||||||
// SPI2 Driver
|
// SPI2 Driver
|
||||||
// PB15 28 SPI2_MOSI
|
|
||||||
// PB14 27 SPI2_MISO
|
|
||||||
// PB13 26 SPI2_SCK
|
|
||||||
// PB12 25 SPI2_NSS
|
// PB12 25 SPI2_NSS
|
||||||
|
// PB13 26 SPI2_SCK
|
||||||
|
// PB14 27 SPI2_MISO
|
||||||
|
// PB15 28 SPI2_MOSI
|
||||||
|
|
||||||
gpio_config_t gpio;
|
|
||||||
SPI_InitTypeDef spi;
|
SPI_InitTypeDef spi;
|
||||||
|
|
||||||
// Enable SPI2 clock
|
// Enable SPI2 clock
|
||||||
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
|
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
|
||||||
RCC_APB1PeriphResetCmd(RCC_APB1Periph_SPI2, ENABLE);
|
RCC_APB1PeriphResetCmd(RCC_APB1Periph_SPI2, ENABLE);
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef STM32F303xC
|
||||||
|
GPIO_InitTypeDef GPIO_InitStructure;
|
||||||
|
|
||||||
|
RCC_AHBPeriphClockCmd(SPI2_GPIO_PERIPHERAL, ENABLE);
|
||||||
|
|
||||||
|
GPIO_PinAFConfig(SPI2_GPIO, SPI2_SCK_PIN_SOURCE, GPIO_AF_5);
|
||||||
|
GPIO_PinAFConfig(SPI2_GPIO, SPI2_MISO_PIN_SOURCE, GPIO_AF_5);
|
||||||
|
GPIO_PinAFConfig(SPI2_GPIO, SPI2_MOSI_PIN_SOURCE, GPIO_AF_5);
|
||||||
|
|
||||||
|
GPIO_InitStructure.GPIO_Pin = SPI2_SCK_PIN | SPI2_MISO_PIN | SPI2_MOSI_PIN;
|
||||||
|
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
|
||||||
|
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
|
||||||
|
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
|
||||||
|
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
|
||||||
|
GPIO_Init(SPI2_GPIO, &GPIO_InitStructure);
|
||||||
|
|
||||||
|
GPIO_InitStructure.GPIO_Pin = SPI2_NSS_PIN;
|
||||||
|
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
|
||||||
|
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
|
||||||
|
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
|
||||||
|
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
|
||||||
|
|
||||||
|
GPIO_Init(SPI2_GPIO, &GPIO_InitStructure);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef STM32F10X
|
||||||
|
gpio_config_t gpio;
|
||||||
|
|
||||||
// MOSI + SCK as output
|
// MOSI + SCK as output
|
||||||
gpio.mode = Mode_AF_PP;
|
gpio.mode = Mode_AF_PP;
|
||||||
gpio.pin = Pin_13 | Pin_15;
|
gpio.pin = SPI2_SCK_PIN | SPI2_MOSI_PIN;
|
||||||
gpio.speed = Speed_50MHz;
|
gpio.speed = Speed_50MHz;
|
||||||
gpioInit(GPIOB, &gpio);
|
gpioInit(SPI2_GPIO, &gpio);
|
||||||
// MISO as input
|
// MISO as input
|
||||||
gpio.pin = Pin_14;
|
gpio.pin = SPI2_MISO_PIN;
|
||||||
gpio.mode = Mode_IN_FLOATING;
|
gpio.mode = Mode_IN_FLOATING;
|
||||||
gpioInit(GPIOB, &gpio);
|
gpioInit(SPI2_GPIO, &gpio);
|
||||||
// NSS as gpio slave select
|
|
||||||
gpio.pin = Pin_12;
|
|
||||||
gpio.mode = Mode_Out_PP;
|
|
||||||
gpioInit(GPIOB, &gpio);
|
|
||||||
|
|
||||||
GPIO_SetBits(GPIOB, GPIO_Pin_12);
|
// NSS as gpio slave select
|
||||||
|
gpio.pin = SPI2_NSS_PIN;
|
||||||
|
gpio.mode = Mode_Out_PP;
|
||||||
|
gpioInit(SPI2_GPIO, &gpio);
|
||||||
|
#endif
|
||||||
|
|
||||||
// Init SPI2 hardware
|
// Init SPI2 hardware
|
||||||
SPI_I2S_DeInit(SPI2);
|
SPI_I2S_DeInit(SPI2);
|
||||||
|
|
||||||
spi.SPI_Mode = SPI_Mode_Master;
|
|
||||||
spi.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
|
spi.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
|
||||||
|
spi.SPI_Mode = SPI_Mode_Master;
|
||||||
spi.SPI_DataSize = SPI_DataSize_8b;
|
spi.SPI_DataSize = SPI_DataSize_8b;
|
||||||
spi.SPI_NSS = SPI_NSS_Soft;
|
|
||||||
spi.SPI_FirstBit = SPI_FirstBit_MSB;
|
|
||||||
spi.SPI_CRCPolynomial = 7;
|
|
||||||
spi.SPI_CPOL = SPI_CPOL_High;
|
spi.SPI_CPOL = SPI_CPOL_High;
|
||||||
spi.SPI_CPHA = SPI_CPHA_2Edge;
|
spi.SPI_CPHA = SPI_CPHA_2Edge;
|
||||||
|
spi.SPI_NSS = SPI_NSS_Soft;
|
||||||
spi.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;
|
spi.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;
|
||||||
|
spi.SPI_FirstBit = SPI_FirstBit_MSB;
|
||||||
|
spi.SPI_CRCPolynomial = 7;
|
||||||
|
|
||||||
|
#ifdef STM32F303xC
|
||||||
|
// Configure for 8-bit reads.
|
||||||
|
SPI_RxFIFOThresholdConfig(SPI2, SPI_RxFIFOThreshold_QF);
|
||||||
|
#endif
|
||||||
SPI_Init(SPI2, &spi);
|
SPI_Init(SPI2, &spi);
|
||||||
SPI_Cmd(SPI2, ENABLE);
|
SPI_Cmd(SPI2, ENABLE);
|
||||||
|
|
||||||
|
// Drive NSS high to disable connected SPI device.
|
||||||
|
GPIO_SetBits(SPI2_GPIO, SPI2_NSS_PIN);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -198,7 +289,7 @@ uint8_t spiTransferByte(SPI_TypeDef *instance, uint8_t data)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool spiTransfer(SPI_TypeDef *instance, uint8_t *out, uint8_t *in, int len)
|
bool spiTransfer(SPI_TypeDef *instance, uint8_t *out, const uint8_t *in, int len)
|
||||||
{
|
{
|
||||||
uint16_t spiTimeout = 1000;
|
uint16_t spiTimeout = 1000;
|
||||||
|
|
||||||
|
@ -210,8 +301,9 @@ bool spiTransfer(SPI_TypeDef *instance, uint8_t *out, uint8_t *in, int len)
|
||||||
if ((spiTimeout--) == 0)
|
if ((spiTimeout--) == 0)
|
||||||
return spiTimeoutUserCallback(instance);
|
return spiTimeoutUserCallback(instance);
|
||||||
}
|
}
|
||||||
#ifdef STM32F303
|
#ifdef STM32F303xC
|
||||||
SPI_I2S_SendData16(instance, b);
|
SPI_SendData8(instance, b);
|
||||||
|
//SPI_I2S_SendData16(instance, b);
|
||||||
#endif
|
#endif
|
||||||
#ifdef STM32F10X
|
#ifdef STM32F10X
|
||||||
SPI_I2S_SendData(instance, b);
|
SPI_I2S_SendData(instance, b);
|
||||||
|
@ -220,8 +312,9 @@ bool spiTransfer(SPI_TypeDef *instance, uint8_t *out, uint8_t *in, int len)
|
||||||
if ((spiTimeout--) == 0)
|
if ((spiTimeout--) == 0)
|
||||||
return spiTimeoutUserCallback(instance);
|
return spiTimeoutUserCallback(instance);
|
||||||
}
|
}
|
||||||
#ifdef STM32F303
|
#ifdef STM32F303xC
|
||||||
b = SPI_I2S_ReceiveData16(instance);
|
b = SPI_ReceiveData8(instance);
|
||||||
|
//b = SPI_I2S_ReceiveData16(instance);
|
||||||
#endif
|
#endif
|
||||||
#ifdef STM32F10X
|
#ifdef STM32F10X
|
||||||
b = SPI_I2S_ReceiveData(instance);
|
b = SPI_I2S_ReceiveData(instance);
|
||||||
|
|
|
@ -19,12 +19,13 @@
|
||||||
|
|
||||||
#define SPI_0_5625MHZ_CLOCK_DIVIDER 128
|
#define SPI_0_5625MHZ_CLOCK_DIVIDER 128
|
||||||
#define SPI_18MHZ_CLOCK_DIVIDER 2
|
#define SPI_18MHZ_CLOCK_DIVIDER 2
|
||||||
|
#define SPI_9MHZ_CLOCK_DIVIDER 4
|
||||||
|
|
||||||
bool spiInit(SPI_TypeDef *instance);
|
bool spiInit(SPI_TypeDef *instance);
|
||||||
void spiSetDivisor(SPI_TypeDef *instance, uint16_t divisor);
|
void spiSetDivisor(SPI_TypeDef *instance, uint16_t divisor);
|
||||||
uint8_t spiTransferByte(SPI_TypeDef *instance, uint8_t in);
|
uint8_t spiTransferByte(SPI_TypeDef *instance, uint8_t in);
|
||||||
|
|
||||||
bool spiTransfer(SPI_TypeDef *instance, uint8_t *out, uint8_t *in, int len);
|
bool spiTransfer(SPI_TypeDef *instance, uint8_t *out, const uint8_t *in, int len);
|
||||||
|
|
||||||
uint16_t spiGetErrorCounter(SPI_TypeDef *instance);
|
uint16_t spiGetErrorCounter(SPI_TypeDef *instance);
|
||||||
void spiResetErrorCounter(SPI_TypeDef *instance);
|
void spiResetErrorCounter(SPI_TypeDef *instance);
|
||||||
|
|
31
src/main/drivers/flash.h
Normal file
31
src/main/drivers/flash.h
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Cleanflight.
|
||||||
|
*
|
||||||
|
* Cleanflight is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Cleanflight is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
typedef struct flashGeometry_t {
|
||||||
|
uint8_t sectors;
|
||||||
|
|
||||||
|
uint16_t pagesPerSector;
|
||||||
|
uint16_t pageSize;
|
||||||
|
|
||||||
|
uint32_t sectorSize;
|
||||||
|
|
||||||
|
uint32_t totalSize;
|
||||||
|
} flashGeometry_t;
|
289
src/main/drivers/flash_m25p16.c
Normal file
289
src/main/drivers/flash_m25p16.c
Normal file
|
@ -0,0 +1,289 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Cleanflight.
|
||||||
|
*
|
||||||
|
* Cleanflight is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Cleanflight is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "platform.h"
|
||||||
|
|
||||||
|
#include "drivers/flash_m25p16.h"
|
||||||
|
#include "drivers/bus_spi.h"
|
||||||
|
#include "drivers/system.h"
|
||||||
|
|
||||||
|
#define M25P16_INSTRUCTION_RDID 0x9F
|
||||||
|
#define M25P16_INSTRUCTION_READ_BYTES 0x03
|
||||||
|
#define M25P16_INSTRUCTION_READ_STATUS_REG 0x05
|
||||||
|
#define M25P16_INSTRUCTION_WRITE_STATUS_REG 0x01
|
||||||
|
#define M25P16_INSTRUCTION_WRITE_ENABLE 0x06
|
||||||
|
#define M25P16_INSTRUCTION_WRITE_DISABLE 0x04
|
||||||
|
#define M25P16_INSTRUCTION_PAGE_PROGRAM 0x02
|
||||||
|
#define M25P16_INSTRUCTION_SECTOR_ERASE 0xD8
|
||||||
|
#define M25P16_INSTRUCTION_BULK_ERASE 0xC7
|
||||||
|
|
||||||
|
#define M25P16_STATUS_FLAG_WRITE_IN_PROGRESS 0x01
|
||||||
|
#define M25P16_STATUS_FLAG_WRITE_ENABLED 0x02
|
||||||
|
|
||||||
|
#define DISABLE_M25P16 GPIO_SetBits(M25P16_CS_GPIO, M25P16_CS_PIN)
|
||||||
|
#define ENABLE_M25P16 GPIO_ResetBits(M25P16_CS_GPIO, M25P16_CS_PIN)
|
||||||
|
|
||||||
|
// The timeout we expect between being able to issue page program instructions
|
||||||
|
#define DEFAULT_TIMEOUT_MILLIS 6
|
||||||
|
|
||||||
|
// These take sooooo long:
|
||||||
|
#define SECTOR_ERASE_TIMEOUT_MILLIS 5000
|
||||||
|
#define BULK_ERASE_TIMEOUT_MILLIS 21000
|
||||||
|
|
||||||
|
static flashGeometry_t geometry;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Whether we've performed an action that could have made the device busy for writes.
|
||||||
|
*
|
||||||
|
* This allows us to avoid polling for writable status when it is definitely ready already.
|
||||||
|
*/
|
||||||
|
static bool couldBeBusy = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the given command byte to the device.
|
||||||
|
*/
|
||||||
|
static void m25p16_performOneByteCommand(uint8_t command)
|
||||||
|
{
|
||||||
|
ENABLE_M25P16;
|
||||||
|
|
||||||
|
spiTransferByte(M25P16_SPI_INSTANCE, command);
|
||||||
|
|
||||||
|
DISABLE_M25P16;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The flash requires this write enable command to be sent before commands that would cause
|
||||||
|
* a write like program and erase.
|
||||||
|
*/
|
||||||
|
static void m25p16_writeEnable()
|
||||||
|
{
|
||||||
|
m25p16_performOneByteCommand(M25P16_INSTRUCTION_WRITE_ENABLE);
|
||||||
|
|
||||||
|
// Assume that we're about to do some writing, so the device is just about to become busy
|
||||||
|
couldBeBusy = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t m25p16_readStatus()
|
||||||
|
{
|
||||||
|
uint8_t command[2] = {M25P16_INSTRUCTION_READ_STATUS_REG, 0};
|
||||||
|
uint8_t in[2];
|
||||||
|
|
||||||
|
ENABLE_M25P16;
|
||||||
|
|
||||||
|
spiTransfer(M25P16_SPI_INSTANCE, in, command, sizeof(command));
|
||||||
|
|
||||||
|
DISABLE_M25P16;
|
||||||
|
|
||||||
|
return in[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool m25p16_isReady()
|
||||||
|
{
|
||||||
|
// If couldBeBusy is false, don't bother to poll the flash chip for its status
|
||||||
|
couldBeBusy = couldBeBusy && ((m25p16_readStatus() & M25P16_STATUS_FLAG_WRITE_IN_PROGRESS) != 0);
|
||||||
|
|
||||||
|
return !couldBeBusy;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool m25p16_waitForReady(uint32_t timeoutMillis)
|
||||||
|
{
|
||||||
|
uint32_t time = millis();
|
||||||
|
while (!m25p16_isReady()) {
|
||||||
|
if (millis() - time > timeoutMillis) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read chip identification and geometry information (into global `geometry`).
|
||||||
|
*
|
||||||
|
* Returns true if we get valid ident, false if something bad happened like there is no M25P16.
|
||||||
|
*/
|
||||||
|
static bool m25p16_readIdentification()
|
||||||
|
{
|
||||||
|
uint8_t out[] = { M25P16_INSTRUCTION_RDID, 0, 0, 0};
|
||||||
|
uint8_t in[4];
|
||||||
|
|
||||||
|
delay(50); // short delay required after initialisation of SPI device instance.
|
||||||
|
|
||||||
|
/* Just in case transfer fails and writes nothing, so we don't try to verify the ID against random garbage
|
||||||
|
* from the stack:
|
||||||
|
*/
|
||||||
|
in[1] = 0;
|
||||||
|
|
||||||
|
ENABLE_M25P16;
|
||||||
|
|
||||||
|
spiTransfer(M25P16_SPI_INSTANCE, in, out, sizeof(out));
|
||||||
|
|
||||||
|
// Clearing the CS bit terminates the command early so we don't have to read the chip UID:
|
||||||
|
DISABLE_M25P16;
|
||||||
|
|
||||||
|
// Check manufacturer, memory type, and capacity
|
||||||
|
if (in[1] == 0x20 && in[2] == 0x20 && in[3] == 0x15) {
|
||||||
|
// In the future we can support other chip geometries here:
|
||||||
|
geometry.sectors = 32;
|
||||||
|
geometry.pagesPerSector = 256;
|
||||||
|
geometry.pageSize = 256;
|
||||||
|
|
||||||
|
geometry.sectorSize = geometry.pagesPerSector * geometry.pageSize;
|
||||||
|
geometry.totalSize = geometry.sectorSize * geometry.sectors;
|
||||||
|
|
||||||
|
couldBeBusy = true; // Just for luck we'll assume the chip could be busy even though it isn't specced to be
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
geometry.sectors = 0;
|
||||||
|
geometry.pagesPerSector = 0;
|
||||||
|
geometry.pageSize = 0;
|
||||||
|
|
||||||
|
geometry.sectorSize = 0;
|
||||||
|
geometry.totalSize = 0;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the driver, must be called before any other routines.
|
||||||
|
*
|
||||||
|
* Attempts to detect a connected m25p16. If found, true is returned and device capacity can be fetched with
|
||||||
|
* m25p16_getGeometry().
|
||||||
|
*/
|
||||||
|
bool m25p16_init()
|
||||||
|
{
|
||||||
|
//Maximum speed for standard READ command is 20mHz, other commands tolerate 25mHz
|
||||||
|
spiSetDivisor(M25P16_SPI_INSTANCE, SPI_18MHZ_CLOCK_DIVIDER);
|
||||||
|
|
||||||
|
return m25p16_readIdentification();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erase a sector full of bytes to all 1's at the given byte offset in the flash chip.
|
||||||
|
*/
|
||||||
|
void m25p16_eraseSector(uint32_t address)
|
||||||
|
{
|
||||||
|
uint8_t out[] = { M25P16_INSTRUCTION_SECTOR_ERASE, (address >> 16) & 0xFF, (address >> 8) & 0xFF, address & 0xFF};
|
||||||
|
|
||||||
|
m25p16_waitForReady(SECTOR_ERASE_TIMEOUT_MILLIS);
|
||||||
|
|
||||||
|
m25p16_writeEnable();
|
||||||
|
|
||||||
|
ENABLE_M25P16;
|
||||||
|
|
||||||
|
spiTransfer(M25P16_SPI_INSTANCE, NULL, out, sizeof(out));
|
||||||
|
|
||||||
|
DISABLE_M25P16;
|
||||||
|
}
|
||||||
|
|
||||||
|
void m25p16_eraseCompletely()
|
||||||
|
{
|
||||||
|
m25p16_waitForReady(BULK_ERASE_TIMEOUT_MILLIS);
|
||||||
|
|
||||||
|
m25p16_writeEnable();
|
||||||
|
|
||||||
|
m25p16_performOneByteCommand(M25P16_INSTRUCTION_BULK_ERASE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void m25p16_pageProgramBegin(uint32_t address)
|
||||||
|
{
|
||||||
|
uint8_t command[] = { M25P16_INSTRUCTION_PAGE_PROGRAM, (address >> 16) & 0xFF, (address >> 8) & 0xFF, address & 0xFF};
|
||||||
|
|
||||||
|
m25p16_waitForReady(DEFAULT_TIMEOUT_MILLIS);
|
||||||
|
|
||||||
|
m25p16_writeEnable();
|
||||||
|
|
||||||
|
ENABLE_M25P16;
|
||||||
|
|
||||||
|
spiTransfer(M25P16_SPI_INSTANCE, NULL, command, sizeof(command));
|
||||||
|
}
|
||||||
|
|
||||||
|
void m25p16_pageProgramContinue(const uint8_t *data, int length)
|
||||||
|
{
|
||||||
|
spiTransfer(M25P16_SPI_INSTANCE, NULL, data, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
void m25p16_pageProgramFinish()
|
||||||
|
{
|
||||||
|
DISABLE_M25P16;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write bytes to a flash page. Address must not cross a page boundary.
|
||||||
|
*
|
||||||
|
* Bits can only be set to zero, not from zero back to one again. In order to set bits to 1, use the erase command.
|
||||||
|
*
|
||||||
|
* Length must be smaller than the page size.
|
||||||
|
*
|
||||||
|
* This will wait for the flash to become ready before writing begins.
|
||||||
|
*
|
||||||
|
* Datasheet indicates typical programming time is 0.8ms for 256 bytes, 0.2ms for 64 bytes, 0.05ms for 16 bytes.
|
||||||
|
* (Although the maximum possible write time is noted as 5ms).
|
||||||
|
*
|
||||||
|
* If you want to write multiple buffers (whose sum of sizes is still not more than the page size) then you can
|
||||||
|
* break this operation up into one beginProgram call, one or more continueProgram calls, and one finishProgram call.
|
||||||
|
*/
|
||||||
|
void m25p16_pageProgram(uint32_t address, const uint8_t *data, int length)
|
||||||
|
{
|
||||||
|
m25p16_pageProgramBegin(address);
|
||||||
|
|
||||||
|
m25p16_pageProgramContinue(data, length);
|
||||||
|
|
||||||
|
m25p16_pageProgramFinish();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read `length` bytes into the provided `buffer` from the flash starting from the given `address` (which need not lie
|
||||||
|
* on a page boundary).
|
||||||
|
*
|
||||||
|
* Waits up to DEFAULT_TIMEOUT_MILLIS milliseconds for the flash to become ready before reading.
|
||||||
|
*
|
||||||
|
* The number of bytes actually read is returned, which can be zero if an error or timeout occurred.
|
||||||
|
*/
|
||||||
|
int m25p16_readBytes(uint32_t address, uint8_t *buffer, int length)
|
||||||
|
{
|
||||||
|
uint8_t command[] = { M25P16_INSTRUCTION_READ_BYTES, (address >> 16) & 0xFF, (address >> 8) & 0xFF, address & 0xFF};
|
||||||
|
|
||||||
|
if (!m25p16_waitForReady(DEFAULT_TIMEOUT_MILLIS)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ENABLE_M25P16;
|
||||||
|
|
||||||
|
spiTransfer(M25P16_SPI_INSTANCE, NULL, command, sizeof(command));
|
||||||
|
spiTransfer(M25P16_SPI_INSTANCE, buffer, NULL, length);
|
||||||
|
|
||||||
|
DISABLE_M25P16;
|
||||||
|
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch information about the detected flash chip layout.
|
||||||
|
*
|
||||||
|
* Can be called before calling m25p16_init() (the result would have totalSize = 0).
|
||||||
|
*/
|
||||||
|
const flashGeometry_t* m25p16_getGeometry()
|
||||||
|
{
|
||||||
|
return &geometry;
|
||||||
|
}
|
39
src/main/drivers/flash_m25p16.h
Normal file
39
src/main/drivers/flash_m25p16.h
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Cleanflight.
|
||||||
|
*
|
||||||
|
* Cleanflight is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Cleanflight is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "flash.h"
|
||||||
|
|
||||||
|
bool m25p16_init();
|
||||||
|
|
||||||
|
void m25p16_eraseSector(uint32_t address);
|
||||||
|
void m25p16_eraseCompletely();
|
||||||
|
|
||||||
|
void m25p16_pageProgram(uint32_t address, const uint8_t *data, int length);
|
||||||
|
|
||||||
|
void m25p16_pageProgramBegin(uint32_t address);
|
||||||
|
void m25p16_pageProgramContinue(const uint8_t *data, int length);
|
||||||
|
void m25p16_pageProgramFinish();
|
||||||
|
|
||||||
|
int m25p16_readBytes(uint32_t address, uint8_t *buffer, int length);
|
||||||
|
|
||||||
|
bool m25p16_isReady();
|
||||||
|
bool m25p16_waitForReady(uint32_t timeoutMillis);
|
||||||
|
|
||||||
|
const flashGeometry_t* m25p16_getGeometry();
|
|
@ -419,6 +419,7 @@ pwmOutputConfiguration_t *pwmInit(drv_pwm_config_t *init)
|
||||||
if (type == MAP_TO_PPM_INPUT && !init->usePPM)
|
if (type == MAP_TO_PPM_INPUT && !init->usePPM)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
#ifdef USE_SERVOS
|
||||||
if (init->useServos && !init->airplane) {
|
if (init->useServos && !init->airplane) {
|
||||||
#if defined(NAZE)
|
#if defined(NAZE)
|
||||||
// remap PWM9+10 as servos
|
// remap PWM9+10 as servos
|
||||||
|
@ -455,6 +456,7 @@ pwmOutputConfiguration_t *pwmInit(drv_pwm_config_t *init)
|
||||||
if (timerIndex >= PWM5 && timerIndex <= PWM8)
|
if (timerIndex >= PWM5 && timerIndex <= PWM8)
|
||||||
type = MAP_TO_SERVO_OUTPUT;
|
type = MAP_TO_SERVO_OUTPUT;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef CC3D
|
#ifdef CC3D
|
||||||
if (init->useParallelPWM) {
|
if (init->useParallelPWM) {
|
||||||
|
@ -489,8 +491,10 @@ pwmOutputConfiguration_t *pwmInit(drv_pwm_config_t *init)
|
||||||
}
|
}
|
||||||
pwmOutputConfiguration.motorCount++;
|
pwmOutputConfiguration.motorCount++;
|
||||||
} else if (type == MAP_TO_SERVO_OUTPUT) {
|
} else if (type == MAP_TO_SERVO_OUTPUT) {
|
||||||
|
#ifdef USE_SERVOS
|
||||||
pwmServoConfig(timerHardwarePtr, pwmOutputConfiguration.servoCount, init->servoPwmRate, init->servoCenterPulse);
|
pwmServoConfig(timerHardwarePtr, pwmOutputConfiguration.servoCount, init->servoPwmRate, init->servoCenterPulse);
|
||||||
pwmOutputConfiguration.servoCount++;
|
pwmOutputConfiguration.servoCount++;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,14 +49,16 @@ typedef struct drv_pwm_config_t {
|
||||||
bool useOneshot;
|
bool useOneshot;
|
||||||
bool useSoftSerial;
|
bool useSoftSerial;
|
||||||
bool useLEDStrip;
|
bool useLEDStrip;
|
||||||
|
#ifdef USE_SERVOS
|
||||||
bool useServos;
|
bool useServos;
|
||||||
bool extraServos; // configure additional 4 channels in PPM mode as servos, not motors
|
bool extraServos; // configure additional 4 channels in PPM mode as servos, not motors
|
||||||
|
uint16_t servoPwmRate;
|
||||||
|
uint16_t servoCenterPulse;
|
||||||
|
#endif
|
||||||
bool airplane; // fixed wing hardware config, lots of servos etc
|
bool airplane; // fixed wing hardware config, lots of servos etc
|
||||||
uint16_t motorPwmRate;
|
uint16_t motorPwmRate;
|
||||||
uint16_t servoPwmRate;
|
|
||||||
uint16_t idlePulse; // PWM value to use when initializing the driver. set this to either PULSE_1MS (regular pwm),
|
uint16_t idlePulse; // PWM value to use when initializing the driver. set this to either PULSE_1MS (regular pwm),
|
||||||
// some higher value (used by 3d mode), or 0, for brushed pwm drivers.
|
// some higher value (used by 3d mode), or 0, for brushed pwm drivers.
|
||||||
uint16_t servoCenterPulse;
|
|
||||||
} drv_pwm_config_t;
|
} drv_pwm_config_t;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -43,8 +43,10 @@ typedef struct {
|
||||||
static pwmOutputPort_t pwmOutputPorts[MAX_PWM_OUTPUT_PORTS];
|
static pwmOutputPort_t pwmOutputPorts[MAX_PWM_OUTPUT_PORTS];
|
||||||
|
|
||||||
static pwmOutputPort_t *motors[MAX_PWM_MOTORS];
|
static pwmOutputPort_t *motors[MAX_PWM_MOTORS];
|
||||||
static pwmOutputPort_t *servos[MAX_PWM_SERVOS];
|
|
||||||
|
|
||||||
|
#ifdef USE_SERVOS
|
||||||
|
static pwmOutputPort_t *servos[MAX_PWM_SERVOS];
|
||||||
|
#endif
|
||||||
#define PWM_BRUSHED_TIMER_MHZ 8
|
#define PWM_BRUSHED_TIMER_MHZ 8
|
||||||
|
|
||||||
static uint8_t allocatedOutputPortCount = 0;
|
static uint8_t allocatedOutputPortCount = 0;
|
||||||
|
@ -160,13 +162,6 @@ void pwmCompleteOneshotMotorUpdate(uint8_t motorCount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void pwmWriteServo(uint8_t index, uint16_t value)
|
|
||||||
{
|
|
||||||
if (servos[index] && index < MAX_SERVOS)
|
|
||||||
*servos[index]->ccr = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void pwmBrushedMotorConfig(const timerHardware_t *timerHardware, uint8_t motorIndex, uint16_t motorPwmRate, uint16_t idlePulse)
|
void pwmBrushedMotorConfig(const timerHardware_t *timerHardware, uint8_t motorIndex, uint16_t motorPwmRate, uint16_t idlePulse)
|
||||||
{
|
{
|
||||||
uint32_t hz = PWM_BRUSHED_TIMER_MHZ * 1000000;
|
uint32_t hz = PWM_BRUSHED_TIMER_MHZ * 1000000;
|
||||||
|
@ -187,7 +182,15 @@ void pwmOneshotMotorConfig(const timerHardware_t *timerHardware, uint8_t motorIn
|
||||||
motors[motorIndex]->pwmWritePtr = pwmWriteStandard;
|
motors[motorIndex]->pwmWritePtr = pwmWriteStandard;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_SERVOS
|
||||||
void pwmServoConfig(const timerHardware_t *timerHardware, uint8_t servoIndex, uint16_t servoPwmRate, uint16_t servoCenterPulse)
|
void pwmServoConfig(const timerHardware_t *timerHardware, uint8_t servoIndex, uint16_t servoPwmRate, uint16_t servoCenterPulse)
|
||||||
{
|
{
|
||||||
servos[servoIndex] = pwmOutConfig(timerHardware, PWM_TIMER_MHZ, 1000000 / servoPwmRate, servoCenterPulse);
|
servos[servoIndex] = pwmOutConfig(timerHardware, PWM_TIMER_MHZ, 1000000 / servoPwmRate, servoCenterPulse);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void pwmWriteServo(uint8_t index, uint16_t value)
|
||||||
|
{
|
||||||
|
if (servos[index] && index < MAX_SERVOS)
|
||||||
|
*servos[index]->ccr = value;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
117
src/main/flight/lowpass.c
Executable file
117
src/main/flight/lowpass.c
Executable file
|
@ -0,0 +1,117 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Cleanflight.
|
||||||
|
*
|
||||||
|
* Cleanflight is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Cleanflight is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "flight/lowpass.h"
|
||||||
|
|
||||||
|
//#define DEBUG_LOWPASS
|
||||||
|
|
||||||
|
void generateLowpassCoeffs2(int16_t freq, lowpass_t *filter)
|
||||||
|
{
|
||||||
|
float fixedScaler;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
// generates coefficients for a 2nd-order butterworth low-pass filter
|
||||||
|
float freqf = (float)freq*0.001f;
|
||||||
|
float omega = tanf((float)M_PI*freqf/2.0f);
|
||||||
|
float scaling = 1.0f / (omega*omega + 1.4142136f*omega + 1.0f);
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(UNIT_TEST) && defined(DEBUG_LOWPASS)
|
||||||
|
printf("lowpass cutoff: %f, omega: %f\n", freqf, omega);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
filter->bf[0] = scaling * omega*omega;
|
||||||
|
filter->bf[1] = 2.0f * filter->bf[0];
|
||||||
|
filter->bf[2] = filter->bf[0];
|
||||||
|
|
||||||
|
filter->af[0] = 1.0f;
|
||||||
|
filter->af[1] = scaling * (2.0f * omega*omega - 2.0f);
|
||||||
|
filter->af[2] = scaling * (omega*omega - 1.4142136f * omega + 1.0f);
|
||||||
|
|
||||||
|
// Scale for fixed-point
|
||||||
|
filter->input_bias = 1500; // Typical servo range is 1500 +/- 500
|
||||||
|
filter->input_shift = 16;
|
||||||
|
filter->coeff_shift = 24;
|
||||||
|
fixedScaler = (float)(1ULL << filter->coeff_shift);
|
||||||
|
for (i = 0; i < LOWPASS_NUM_COEF; i++) {
|
||||||
|
filter->a[i] = LPF_ROUND(filter->af[i] * fixedScaler);
|
||||||
|
filter->b[i] = LPF_ROUND(filter->bf[i] * fixedScaler);
|
||||||
|
#if defined(UNIT_TEST) && defined(DEBUG_LOWPASS)
|
||||||
|
printf("(%d) bf: %f af: %f b: %ld a: %ld\n", i,
|
||||||
|
filter->bf[i], filter->af[i], filter->b[i], filter->a[i]);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
filter->freq = freq;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t lowpassFixed(lowpass_t *filter, int32_t in, int16_t freq)
|
||||||
|
{
|
||||||
|
int16_t coefIdx;
|
||||||
|
int64_t out;
|
||||||
|
int32_t in_s;
|
||||||
|
|
||||||
|
// Check to see if cutoff frequency changed
|
||||||
|
if (freq != filter->freq) {
|
||||||
|
filter->init = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize if needed
|
||||||
|
if (!filter->init) {
|
||||||
|
generateLowpassCoeffs2(freq, filter);
|
||||||
|
for (coefIdx = 0; coefIdx < LOWPASS_NUM_COEF; coefIdx++) {
|
||||||
|
filter->x[coefIdx] = (in - filter->input_bias) << filter->input_shift;
|
||||||
|
filter->y[coefIdx] = (in - filter->input_bias) << filter->input_shift;
|
||||||
|
}
|
||||||
|
filter->init = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unbias input and scale
|
||||||
|
in_s = (in - filter->input_bias) << filter->input_shift;
|
||||||
|
|
||||||
|
// Delays
|
||||||
|
for (coefIdx = LOWPASS_NUM_COEF-1; coefIdx > 0; coefIdx--) {
|
||||||
|
filter->x[coefIdx] = filter->x[coefIdx-1];
|
||||||
|
filter->y[coefIdx] = filter->y[coefIdx-1];
|
||||||
|
}
|
||||||
|
filter->x[0] = in_s;
|
||||||
|
|
||||||
|
// Accumulate result
|
||||||
|
out = filter->x[0] * filter->b[0];
|
||||||
|
for (coefIdx = 1; coefIdx < LOWPASS_NUM_COEF; coefIdx++) {
|
||||||
|
out -= filter->y[coefIdx] * filter->a[coefIdx];
|
||||||
|
out += filter->x[coefIdx] * filter->b[coefIdx];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scale output by coefficient shift
|
||||||
|
out >>= filter->coeff_shift;
|
||||||
|
filter->y[0] = (int32_t)out;
|
||||||
|
|
||||||
|
// Scale output by input shift and round
|
||||||
|
out = (out + (1 << (filter->input_shift-1))) >> filter->input_shift;
|
||||||
|
|
||||||
|
// Reapply bias
|
||||||
|
out += filter->input_bias;
|
||||||
|
|
||||||
|
return (int32_t)out;
|
||||||
|
}
|
||||||
|
|
41
src/main/flight/lowpass.h
Normal file
41
src/main/flight/lowpass.h
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Cleanflight.
|
||||||
|
*
|
||||||
|
* Cleanflight is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Cleanflight is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
|
#define LOWPASS_NUM_COEF 3
|
||||||
|
#define LPF_ROUND(x) (x < 0 ? (x - 0.5f) : (x + 0.5f))
|
||||||
|
|
||||||
|
typedef struct lowpass_t {
|
||||||
|
bool init;
|
||||||
|
int16_t freq; // Normalized freq in 1/1000ths
|
||||||
|
float bf[LOWPASS_NUM_COEF];
|
||||||
|
float af[LOWPASS_NUM_COEF];
|
||||||
|
int64_t b[LOWPASS_NUM_COEF];
|
||||||
|
int64_t a[LOWPASS_NUM_COEF];
|
||||||
|
int16_t coeff_shift;
|
||||||
|
int16_t input_shift;
|
||||||
|
int32_t input_bias;
|
||||||
|
float xf[LOWPASS_NUM_COEF];
|
||||||
|
float yf[LOWPASS_NUM_COEF];
|
||||||
|
int32_t x[LOWPASS_NUM_COEF];
|
||||||
|
int32_t y[LOWPASS_NUM_COEF];
|
||||||
|
} lowpass_t;
|
||||||
|
|
||||||
|
void generateLowpassCoeffs2(int16_t freq, lowpass_t *filter);
|
||||||
|
int32_t lowpassFixed(lowpass_t *filter, int32_t in, int16_t freq);
|
154
src/main/flight/mixer.c
Normal file → Executable file
154
src/main/flight/mixer.c
Normal file → Executable file
|
@ -26,6 +26,7 @@
|
||||||
#include "common/axis.h"
|
#include "common/axis.h"
|
||||||
#include "common/maths.h"
|
#include "common/maths.h"
|
||||||
|
|
||||||
|
#include "drivers/system.h"
|
||||||
#include "drivers/gpio.h"
|
#include "drivers/gpio.h"
|
||||||
#include "drivers/timer.h"
|
#include "drivers/timer.h"
|
||||||
#include "drivers/pwm_output.h"
|
#include "drivers/pwm_output.h"
|
||||||
|
@ -45,6 +46,7 @@
|
||||||
#include "flight/mixer.h"
|
#include "flight/mixer.h"
|
||||||
#include "flight/pid.h"
|
#include "flight/pid.h"
|
||||||
#include "flight/imu.h"
|
#include "flight/imu.h"
|
||||||
|
#include "flight/lowpass.h"
|
||||||
|
|
||||||
#include "config/runtime_config.h"
|
#include "config/runtime_config.h"
|
||||||
#include "config/config.h"
|
#include "config/config.h"
|
||||||
|
@ -54,26 +56,32 @@
|
||||||
|
|
||||||
#define AUX_FORWARD_CHANNEL_TO_SERVO_COUNT 4
|
#define AUX_FORWARD_CHANNEL_TO_SERVO_COUNT 4
|
||||||
|
|
||||||
|
//#define MIXER_DEBUG
|
||||||
|
|
||||||
|
extern int16_t debug[4];
|
||||||
|
|
||||||
uint8_t motorCount = 0;
|
uint8_t motorCount = 0;
|
||||||
int16_t motor[MAX_SUPPORTED_MOTORS];
|
int16_t motor[MAX_SUPPORTED_MOTORS];
|
||||||
int16_t motor_disarmed[MAX_SUPPORTED_MOTORS];
|
int16_t motor_disarmed[MAX_SUPPORTED_MOTORS];
|
||||||
int16_t servo[MAX_SUPPORTED_SERVOS] = { 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500 };
|
|
||||||
|
|
||||||
static int useServo;
|
|
||||||
|
|
||||||
static uint8_t servoCount;
|
|
||||||
|
|
||||||
static servoParam_t *servoConf;
|
|
||||||
static mixerConfig_t *mixerConfig;
|
static mixerConfig_t *mixerConfig;
|
||||||
static flight3DConfig_t *flight3DConfig;
|
static flight3DConfig_t *flight3DConfig;
|
||||||
static escAndServoConfig_t *escAndServoConfig;
|
static escAndServoConfig_t *escAndServoConfig;
|
||||||
static airplaneConfig_t *airplaneConfig;
|
static airplaneConfig_t *airplaneConfig;
|
||||||
static rxConfig_t *rxConfig;
|
static rxConfig_t *rxConfig;
|
||||||
static gimbalConfig_t *gimbalConfig;
|
|
||||||
|
|
||||||
static motorMixer_t currentMixer[MAX_SUPPORTED_MOTORS];
|
static motorMixer_t currentMixer[MAX_SUPPORTED_MOTORS];
|
||||||
static mixerMode_e currentMixerMode;
|
static mixerMode_e currentMixerMode;
|
||||||
|
|
||||||
|
#ifdef USE_SERVOS
|
||||||
|
static gimbalConfig_t *gimbalConfig;
|
||||||
|
int16_t servo[MAX_SUPPORTED_SERVOS];
|
||||||
|
static int useServo;
|
||||||
|
static uint8_t servoCount;
|
||||||
|
static servoParam_t *servoConf;
|
||||||
|
static lowpass_t lowpassFilters[MAX_SUPPORTED_SERVOS];
|
||||||
|
#endif
|
||||||
|
|
||||||
static const motorMixer_t mixerQuadX[] = {
|
static const motorMixer_t mixerQuadX[] = {
|
||||||
{ 1.0f, -1.0f, 1.0f, -1.0f }, // REAR_R
|
{ 1.0f, -1.0f, 1.0f, -1.0f }, // REAR_R
|
||||||
{ 1.0f, -1.0f, -1.0f, 1.0f }, // FRONT_R
|
{ 1.0f, -1.0f, -1.0f, 1.0f }, // FRONT_R
|
||||||
|
@ -224,17 +232,31 @@ const mixer_t mixers[] = {
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void mixerUseConfigs(servoParam_t *servoConfToUse, flight3DConfig_t *flight3DConfigToUse, escAndServoConfig_t *escAndServoConfigToUse, mixerConfig_t *mixerConfigToUse, airplaneConfig_t *airplaneConfigToUse, rxConfig_t *rxConfigToUse, gimbalConfig_t *gimbalConfigToUse)
|
static motorMixer_t *customMixers;
|
||||||
|
|
||||||
|
void mixerUseConfigs(
|
||||||
|
#ifdef USE_SERVOS
|
||||||
|
servoParam_t *servoConfToUse,
|
||||||
|
gimbalConfig_t *gimbalConfigToUse,
|
||||||
|
#endif
|
||||||
|
flight3DConfig_t *flight3DConfigToUse,
|
||||||
|
escAndServoConfig_t *escAndServoConfigToUse,
|
||||||
|
mixerConfig_t *mixerConfigToUse,
|
||||||
|
airplaneConfig_t *airplaneConfigToUse,
|
||||||
|
rxConfig_t *rxConfigToUse)
|
||||||
{
|
{
|
||||||
|
#ifdef USE_SERVOS
|
||||||
servoConf = servoConfToUse;
|
servoConf = servoConfToUse;
|
||||||
|
gimbalConfig = gimbalConfigToUse;
|
||||||
|
#endif
|
||||||
flight3DConfig = flight3DConfigToUse;
|
flight3DConfig = flight3DConfigToUse;
|
||||||
escAndServoConfig = escAndServoConfigToUse;
|
escAndServoConfig = escAndServoConfigToUse;
|
||||||
mixerConfig = mixerConfigToUse;
|
mixerConfig = mixerConfigToUse;
|
||||||
airplaneConfig = airplaneConfigToUse;
|
airplaneConfig = airplaneConfigToUse;
|
||||||
rxConfig = rxConfigToUse;
|
rxConfig = rxConfigToUse;
|
||||||
gimbalConfig = gimbalConfigToUse;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_SERVOS
|
||||||
int16_t determineServoMiddleOrForwardFromChannel(int nr)
|
int16_t determineServoMiddleOrForwardFromChannel(int nr)
|
||||||
{
|
{
|
||||||
uint8_t channelToForwardFrom = servoConf[nr].forwardFromChannel;
|
uint8_t channelToForwardFrom = servoConf[nr].forwardFromChannel;
|
||||||
|
@ -263,9 +285,7 @@ int servoDirection(int nr, int lr)
|
||||||
else
|
else
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
static motorMixer_t *customMixers;
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef USE_QUAD_MIXER_ONLY
|
#ifndef USE_QUAD_MIXER_ONLY
|
||||||
void mixerInit(mixerMode_e mixerMode, motorMixer_t *initialCustomMixers)
|
void mixerInit(mixerMode_e mixerMode, motorMixer_t *initialCustomMixers)
|
||||||
|
@ -279,6 +299,11 @@ void mixerInit(mixerMode_e mixerMode, motorMixer_t *initialCustomMixers)
|
||||||
// if we want camstab/trig, that also enables servos, even if mixer doesn't
|
// if we want camstab/trig, that also enables servos, even if mixer doesn't
|
||||||
if (feature(FEATURE_SERVO_TILT))
|
if (feature(FEATURE_SERVO_TILT))
|
||||||
useServo = 1;
|
useServo = 1;
|
||||||
|
|
||||||
|
// give all servos a default command
|
||||||
|
for (uint8_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
|
||||||
|
servo[i] = DEFAULT_SERVO_MIDDLE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void mixerUsePWMOutputConfiguration(pwmOutputConfiguration_t *pwmOutputConfiguration)
|
void mixerUsePWMOutputConfiguration(pwmOutputConfiguration_t *pwmOutputConfiguration)
|
||||||
|
@ -350,15 +375,15 @@ void mixerInit(mixerMode_e mixerMode, motorMixer_t *initialCustomMixers)
|
||||||
currentMixerMode = mixerMode;
|
currentMixerMode = mixerMode;
|
||||||
|
|
||||||
customMixers = initialCustomMixers;
|
customMixers = initialCustomMixers;
|
||||||
|
|
||||||
useServo = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void mixerUsePWMOutputConfiguration(pwmOutputConfiguration_t *pwmOutputConfiguration)
|
void mixerUsePWMOutputConfiguration(pwmOutputConfiguration_t *pwmOutputConfiguration)
|
||||||
{
|
{
|
||||||
UNUSED(pwmOutputConfiguration);
|
UNUSED(pwmOutputConfiguration);
|
||||||
motorCount = 4;
|
motorCount = 4;
|
||||||
|
#ifdef USE_SERVOS
|
||||||
servoCount = 0;
|
servoCount = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
uint8_t i;
|
uint8_t i;
|
||||||
for (i = 0; i < motorCount; i++) {
|
for (i = 0; i < motorCount; i++) {
|
||||||
|
@ -377,6 +402,7 @@ void mixerResetMotors(void)
|
||||||
motor_disarmed[i] = feature(FEATURE_3D) ? flight3DConfig->neutral3d : escAndServoConfig->mincommand;
|
motor_disarmed[i] = feature(FEATURE_3D) ? flight3DConfig->neutral3d : escAndServoConfig->mincommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_SERVOS
|
||||||
static void updateGimbalServos(void)
|
static void updateGimbalServos(void)
|
||||||
{
|
{
|
||||||
pwmWriteServo(0, servo[0]);
|
pwmWriteServo(0, servo[0]);
|
||||||
|
@ -437,6 +463,7 @@ void writeServos(void)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void writeMotors(void)
|
void writeMotors(void)
|
||||||
{
|
{
|
||||||
|
@ -461,6 +488,13 @@ void writeAllMotors(int16_t mc)
|
||||||
writeMotors();
|
writeMotors();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void stopMotors(void)
|
||||||
|
{
|
||||||
|
writeAllMotors(escAndServoConfig->mincommand);
|
||||||
|
|
||||||
|
delay(50); // give the timers and ESCs a chance to react.
|
||||||
|
}
|
||||||
|
|
||||||
#ifndef USE_QUAD_MIXER_ONLY
|
#ifndef USE_QUAD_MIXER_ONLY
|
||||||
static void airplaneMixer(void)
|
static void airplaneMixer(void)
|
||||||
{
|
{
|
||||||
|
@ -511,7 +545,6 @@ static void airplaneMixer(void)
|
||||||
|
|
||||||
void mixTable(void)
|
void mixTable(void)
|
||||||
{
|
{
|
||||||
int16_t maxMotor;
|
|
||||||
uint32_t i;
|
uint32_t i;
|
||||||
|
|
||||||
if (motorCount > 3) {
|
if (motorCount > 3) {
|
||||||
|
@ -520,15 +553,17 @@ void mixTable(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
// motors for non-servo mixes
|
// motors for non-servo mixes
|
||||||
if (motorCount > 1)
|
if (motorCount > 1) {
|
||||||
for (i = 0; i < motorCount; i++)
|
for (i = 0; i < motorCount; i++) {
|
||||||
motor[i] =
|
motor[i] =
|
||||||
rcCommand[THROTTLE] * currentMixer[i].throttle +
|
rcCommand[THROTTLE] * currentMixer[i].throttle +
|
||||||
axisPID[PITCH] * currentMixer[i].pitch +
|
axisPID[PITCH] * currentMixer[i].pitch +
|
||||||
axisPID[ROLL] * currentMixer[i].roll +
|
axisPID[ROLL] * currentMixer[i].roll +
|
||||||
-mixerConfig->yaw_direction * axisPID[YAW] * currentMixer[i].yaw;
|
-mixerConfig->yaw_direction * axisPID[YAW] * currentMixer[i].yaw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#ifndef USE_QUAD_MIXER_ONLY
|
#if !defined(USE_QUAD_MIXER_ONLY) || defined(USE_SERVOS)
|
||||||
// airplane / servo mixes
|
// airplane / servo mixes
|
||||||
switch (currentMixerMode) {
|
switch (currentMixerMode) {
|
||||||
case MIXER_BI:
|
case MIXER_BI:
|
||||||
|
@ -586,7 +621,6 @@ void mixTable(void)
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
// do camstab
|
// do camstab
|
||||||
if (feature(FEATURE_SERVO_TILT)) {
|
if (feature(FEATURE_SERVO_TILT)) {
|
||||||
|
@ -626,37 +660,75 @@ void mixTable(void)
|
||||||
pwmWriteServo(firstServo + servoOffset, rcData[channelOffset++]);
|
pwmWriteServo(firstServo + servoOffset, rcData[channelOffset++]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
maxMotor = motor[0];
|
if (ARMING_FLAG(ARMED)) {
|
||||||
for (i = 1; i < motorCount; i++)
|
|
||||||
if (motor[i] > maxMotor)
|
int16_t maxMotor = motor[0];
|
||||||
maxMotor = motor[i];
|
for (i = 1; i < motorCount; i++) {
|
||||||
for (i = 0; i < motorCount; i++) {
|
if (motor[i] > maxMotor) {
|
||||||
if (maxMotor > escAndServoConfig->maxthrottle) // this is a way to still have good gyro corrections if at least one motor reaches its max.
|
maxMotor = motor[i];
|
||||||
motor[i] -= maxMotor - escAndServoConfig->maxthrottle;
|
|
||||||
if (feature(FEATURE_3D)) {
|
|
||||||
if ((rcData[THROTTLE]) > rxConfig->midrc) {
|
|
||||||
motor[i] = constrain(motor[i], flight3DConfig->deadband3d_high, escAndServoConfig->maxthrottle);
|
|
||||||
} else {
|
|
||||||
motor[i] = constrain(motor[i], escAndServoConfig->mincommand, flight3DConfig->deadband3d_low);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
motor[i] = constrain(motor[i], escAndServoConfig->minthrottle, escAndServoConfig->maxthrottle);
|
|
||||||
if ((rcData[THROTTLE]) < rxConfig->mincheck) {
|
|
||||||
if (!feature(FEATURE_MOTOR_STOP))
|
|
||||||
motor[i] = escAndServoConfig->minthrottle;
|
|
||||||
else
|
|
||||||
motor[i] = escAndServoConfig->mincommand;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!ARMING_FLAG(ARMED)) {
|
|
||||||
|
for (i = 0; i < motorCount; i++) {
|
||||||
|
if (maxMotor > escAndServoConfig->maxthrottle) {
|
||||||
|
// this is a way to still have good gyro corrections if at least one motor reaches its max.
|
||||||
|
motor[i] -= maxMotor - escAndServoConfig->maxthrottle;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (feature(FEATURE_3D)) {
|
||||||
|
if ((rcData[THROTTLE]) > rxConfig->midrc) {
|
||||||
|
motor[i] = constrain(motor[i], flight3DConfig->deadband3d_high, escAndServoConfig->maxthrottle);
|
||||||
|
} else {
|
||||||
|
motor[i] = constrain(motor[i], escAndServoConfig->mincommand, flight3DConfig->deadband3d_low);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
motor[i] = constrain(motor[i], escAndServoConfig->minthrottle, escAndServoConfig->maxthrottle);
|
||||||
|
if ((rcData[THROTTLE]) < rxConfig->mincheck) {
|
||||||
|
if (!feature(FEATURE_MOTOR_STOP))
|
||||||
|
motor[i] = escAndServoConfig->minthrottle;
|
||||||
|
else
|
||||||
|
motor[i] = escAndServoConfig->mincommand;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
for (i = 0; i < motorCount; i++) {
|
||||||
motor[i] = motor_disarmed[i];
|
motor[i] = motor_disarmed[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_SERVOS
|
||||||
bool isMixerUsingServos(void)
|
bool isMixerUsingServos(void)
|
||||||
{
|
{
|
||||||
return useServo;
|
return useServo;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void filterServos(void)
|
||||||
|
{
|
||||||
|
#ifdef USE_SERVOS
|
||||||
|
int16_t servoIdx;
|
||||||
|
|
||||||
|
#if defined(MIXER_DEBUG)
|
||||||
|
uint32_t startTime = micros();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (mixerConfig->servo_lowpass_enable) {
|
||||||
|
for (servoIdx = 0; servoIdx < MAX_SUPPORTED_SERVOS; servoIdx++) {
|
||||||
|
servo[servoIdx] = (int16_t)lowpassFixed(&lowpassFilters[servoIdx], servo[servoIdx], mixerConfig->servo_lowpass_freq);
|
||||||
|
|
||||||
|
// Sanity check
|
||||||
|
servo[servoIdx] = constrain(servo[servoIdx], servoConf[servoIdx].min, servoConf[servoIdx].max);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#if defined(MIXER_DEBUG)
|
||||||
|
debug[0] = (int16_t)(micros() - startTime);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,11 @@ typedef struct mixer_t {
|
||||||
|
|
||||||
typedef struct mixerConfig_s {
|
typedef struct mixerConfig_s {
|
||||||
int8_t yaw_direction;
|
int8_t yaw_direction;
|
||||||
|
#ifdef USE_SERVOS
|
||||||
uint8_t tri_unarmed_servo; // send tail servo correction pulses even when unarmed
|
uint8_t tri_unarmed_servo; // send tail servo correction pulses even when unarmed
|
||||||
|
int16_t servo_lowpass_freq; // lowpass servo filter frequency selection; 1/1000ths of loop freq
|
||||||
|
int8_t servo_lowpass_enable; // enable/disable lowpass filter
|
||||||
|
#endif
|
||||||
} mixerConfig_t;
|
} mixerConfig_t;
|
||||||
|
|
||||||
typedef struct flight3DConfig_s {
|
typedef struct flight3DConfig_s {
|
||||||
|
@ -82,6 +86,7 @@ typedef struct airplaneConfig_t {
|
||||||
|
|
||||||
#define CHANNEL_FORWARDING_DISABLED (uint8_t)0xFF
|
#define CHANNEL_FORWARDING_DISABLED (uint8_t)0xFF
|
||||||
|
|
||||||
|
#ifdef USE_SERVOS
|
||||||
typedef struct servoParam_t {
|
typedef struct servoParam_t {
|
||||||
int16_t min; // servo min
|
int16_t min; // servo min
|
||||||
int16_t max; // servo max
|
int16_t max; // servo max
|
||||||
|
@ -90,14 +95,18 @@ typedef struct servoParam_t {
|
||||||
int8_t forwardFromChannel; // RX channel index, 0 based. See CHANNEL_FORWARDING_DISABLED
|
int8_t forwardFromChannel; // RX channel index, 0 based. See CHANNEL_FORWARDING_DISABLED
|
||||||
} servoParam_t;
|
} servoParam_t;
|
||||||
|
|
||||||
|
extern int16_t servo[MAX_SUPPORTED_SERVOS];
|
||||||
|
bool isMixerUsingServos(void);
|
||||||
|
void writeServos(void);
|
||||||
|
void filterServos(void);
|
||||||
|
#endif
|
||||||
|
|
||||||
extern int16_t motor[MAX_SUPPORTED_MOTORS];
|
extern int16_t motor[MAX_SUPPORTED_MOTORS];
|
||||||
extern int16_t motor_disarmed[MAX_SUPPORTED_MOTORS];
|
extern int16_t motor_disarmed[MAX_SUPPORTED_MOTORS];
|
||||||
extern int16_t servo[MAX_SUPPORTED_SERVOS];
|
|
||||||
|
|
||||||
bool isMixerUsingServos(void);
|
|
||||||
void writeAllMotors(int16_t mc);
|
void writeAllMotors(int16_t mc);
|
||||||
void mixerLoadMix(int index, motorMixer_t *customMixers);
|
void mixerLoadMix(int index, motorMixer_t *customMixers);
|
||||||
void mixerResetMotors(void);
|
void mixerResetMotors(void);
|
||||||
void mixTable(void);
|
void mixTable(void);
|
||||||
void writeServos(void);
|
|
||||||
void writeMotors(void);
|
void writeMotors(void);
|
||||||
|
void stopMotors(void);
|
||||||
|
|
|
@ -539,8 +539,8 @@ rollAndPitchTrims_t *angleTrim, rxConfig_t *rxConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (axis = 0; axis < 2; axis++) {
|
for (axis = 0; axis < 2; axis++) {
|
||||||
int32_t tmp = (int32_t)((float)gyroData[axis] * 0.3125f); // Multiwii masks out the last 2 bits, this has the same idea
|
int32_t tmp = (int32_t)((float)gyroData[axis] * 0.3125f); // Multiwii masks out the last 2 bits, this has the same idea
|
||||||
gyroDataQuant = (float)tmp * 3.2f; // but delivers more accuracy and also reduces jittery flight
|
gyroDataQuant = (float)tmp * 3.2f; // but delivers more accuracy and also reduces jittery flight
|
||||||
rcCommandAxis = (float)rcCommand[axis]; // Calculate common values for pid controllers
|
rcCommandAxis = (float)rcCommand[axis]; // Calculate common values for pid controllers
|
||||||
if (FLIGHT_MODE(ANGLE_MODE) || FLIGHT_MODE(HORIZON_MODE)) {
|
if (FLIGHT_MODE(ANGLE_MODE) || FLIGHT_MODE(HORIZON_MODE)) {
|
||||||
#ifdef GPS
|
#ifdef GPS
|
||||||
|
@ -557,7 +557,7 @@ rollAndPitchTrims_t *angleTrim, rxConfig_t *rxConfig)
|
||||||
PTermACC = error * (float)pidProfile->P8[PIDLEVEL] * 0.008f;
|
PTermACC = error * (float)pidProfile->P8[PIDLEVEL] * 0.008f;
|
||||||
float limitf = (float)pidProfile->D8[PIDLEVEL] * 5.0f;
|
float limitf = (float)pidProfile->D8[PIDLEVEL] * 5.0f;
|
||||||
PTermACC = constrain(PTermACC, -limitf, +limitf);
|
PTermACC = constrain(PTermACC, -limitf, +limitf);
|
||||||
errorAngleIf[axis] = constrain(errorAngleIf[axis] + error * ACCDeltaTimeINS, -30.0f, +30.0f);
|
errorAngleIf[axis] = constrainf(errorAngleIf[axis] + error * ACCDeltaTimeINS, -30.0f, +30.0f);
|
||||||
ITermACC = errorAngleIf[axis] * (float)pidProfile->I8[PIDLEVEL] * 0.08f;
|
ITermACC = errorAngleIf[axis] * (float)pidProfile->I8[PIDLEVEL] * 0.08f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -578,7 +578,12 @@ void displaySetNextPageChangeAt(uint32_t futureMicros)
|
||||||
void displayEnablePageCycling(void)
|
void displayEnablePageCycling(void)
|
||||||
{
|
{
|
||||||
pageState.pageFlags |= PAGE_STATE_FLAG_CYCLE_ENABLED;
|
pageState.pageFlags |= PAGE_STATE_FLAG_CYCLE_ENABLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
void displayResetPageCycling(void)
|
||||||
|
{
|
||||||
pageState.cycleIndex = CYCLE_PAGE_ID_COUNT - 1; // start at first page
|
pageState.cycleIndex = CYCLE_PAGE_ID_COUNT - 1; // start at first page
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void displayDisablePageCycling(void)
|
void displayDisablePageCycling(void)
|
||||||
|
|
|
@ -35,4 +35,5 @@ void displayShowFixedPage(pageId_e pageId);
|
||||||
|
|
||||||
void displayEnablePageCycling(void);
|
void displayEnablePageCycling(void);
|
||||||
void displayDisablePageCycling(void);
|
void displayDisablePageCycling(void);
|
||||||
|
void displayResetPageCycling(void);
|
||||||
void displaySetNextPageChangeAt(uint32_t futureMicros);
|
void displaySetNextPageChangeAt(uint32_t futureMicros);
|
||||||
|
|
563
src/main/io/flashfs.c
Normal file
563
src/main/io/flashfs.c
Normal file
|
@ -0,0 +1,563 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Cleanflight.
|
||||||
|
*
|
||||||
|
* Cleanflight is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Cleanflight is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This provides a stream interface to a flash chip if one is present.
|
||||||
|
*
|
||||||
|
* On statup, call flashfsInit() after initialising the flash chip in order to init the filesystem. This will
|
||||||
|
* result in the file pointer being pointed at the first free block found, or at the end of the device if the
|
||||||
|
* flash chip is full.
|
||||||
|
*
|
||||||
|
* Note that bits can only be set to 0 when writing, not back to 1 from 0. You must erase sectors in order
|
||||||
|
* to bring bits back to 1 again.
|
||||||
|
*
|
||||||
|
* In future, we can add support for multiple different flash chips by adding a flash device driver vtable
|
||||||
|
* and make calls through that, at the moment flashfs just calls m25p16_* routines explicitly.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "drivers/flash_m25p16.h"
|
||||||
|
#include "flashfs.h"
|
||||||
|
|
||||||
|
static uint8_t flashWriteBuffer[FLASHFS_WRITE_BUFFER_SIZE];
|
||||||
|
|
||||||
|
/* The position of our head and tail in the circular flash write buffer.
|
||||||
|
*
|
||||||
|
* The head is the index that a byte would be inserted into on writing, while the tail is the index of the
|
||||||
|
* oldest byte that has yet to be written to flash.
|
||||||
|
*
|
||||||
|
* When the circular buffer is empty, head == tail
|
||||||
|
*/
|
||||||
|
static uint8_t bufferHead = 0, bufferTail = 0;
|
||||||
|
|
||||||
|
// The position of the buffer's tail in the overall flash address space:
|
||||||
|
static uint32_t tailAddress = 0;
|
||||||
|
// The index of the tail within the flash page it is inside
|
||||||
|
static uint16_t tailIndexInPage = 0;
|
||||||
|
|
||||||
|
static bool shouldFlush = false;
|
||||||
|
|
||||||
|
static void flashfsClearBuffer()
|
||||||
|
{
|
||||||
|
bufferTail = bufferHead = 0;
|
||||||
|
shouldFlush = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool flashfsBufferIsEmpty()
|
||||||
|
{
|
||||||
|
return bufferTail == bufferHead;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void flashfsSetTailAddress(uint32_t address)
|
||||||
|
{
|
||||||
|
tailAddress = address;
|
||||||
|
|
||||||
|
if (m25p16_getGeometry()->pageSize > 0) {
|
||||||
|
tailIndexInPage = tailAddress % m25p16_getGeometry()->pageSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void flashfsEraseCompletely()
|
||||||
|
{
|
||||||
|
m25p16_eraseCompletely();
|
||||||
|
|
||||||
|
flashfsClearBuffer();
|
||||||
|
|
||||||
|
flashfsSetTailAddress(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start and end must lie on sector boundaries, or they will be rounded out to sector boundaries such that
|
||||||
|
* all the bytes in the range [start...end) are erased.
|
||||||
|
*/
|
||||||
|
void flashfsEraseRange(uint32_t start, uint32_t end)
|
||||||
|
{
|
||||||
|
const flashGeometry_t *geometry = m25p16_getGeometry();
|
||||||
|
|
||||||
|
if (geometry->sectorSize <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Round the start down to a sector boundary
|
||||||
|
int startSector = start / geometry->sectorSize;
|
||||||
|
|
||||||
|
// And the end upward
|
||||||
|
int endSector = end / geometry->sectorSize;
|
||||||
|
int endRemainder = end % geometry->sectorSize;
|
||||||
|
|
||||||
|
if (endRemainder > 0) {
|
||||||
|
endSector++;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = startSector; i < endSector; i++) {
|
||||||
|
m25p16_eraseSector(i * geometry->sectorSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the flash is not currently occupied with an operation.
|
||||||
|
*/
|
||||||
|
bool flashfsIsReady()
|
||||||
|
{
|
||||||
|
return m25p16_isReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t flashfsGetSize()
|
||||||
|
{
|
||||||
|
return m25p16_getGeometry()->totalSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
const flashGeometry_t* flashfsGetGeometry()
|
||||||
|
{
|
||||||
|
return m25p16_getGeometry();
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t flashfsTransmitBufferUsed()
|
||||||
|
{
|
||||||
|
if (bufferHead >= bufferTail)
|
||||||
|
return bufferHead - bufferTail;
|
||||||
|
|
||||||
|
return FLASHFS_WRITE_BUFFER_SIZE - bufferTail + bufferHead;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the given buffers to flash sequentially at the current tail address, advancing the tail address after
|
||||||
|
* each write.
|
||||||
|
*
|
||||||
|
* In synchronous mode, waits for the flash to become ready before writing so that every byte requested can be written.
|
||||||
|
*
|
||||||
|
* In asynchronous mode, if the flash is busy, then the write is aborted and the routine returns immediately.
|
||||||
|
* In this case the returned number of bytes written will be less than the total amount requested.
|
||||||
|
*
|
||||||
|
* Modifies the supplied buffer pointers and sizes to reflect how many bytes remain in each of them.
|
||||||
|
*
|
||||||
|
* bufferCount: the number of buffers provided
|
||||||
|
* buffers: an array of pointers to the beginning of buffers
|
||||||
|
* bufferSizes: an array of the sizes of those buffers
|
||||||
|
* sync: true if we should wait for the device to be idle before writes, otherwise if the device is busy the
|
||||||
|
* write will be aborted and this routine will return immediately.
|
||||||
|
*
|
||||||
|
* Returns the number of bytes written
|
||||||
|
*/
|
||||||
|
static uint32_t flashfsWriteBuffers(uint8_t const **buffers, uint32_t *bufferSizes, int bufferCount, bool sync)
|
||||||
|
{
|
||||||
|
const flashGeometry_t *geometry = m25p16_getGeometry();
|
||||||
|
|
||||||
|
uint32_t bytesTotal = 0;
|
||||||
|
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < bufferCount; i++) {
|
||||||
|
bytesTotal += bufferSizes[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sync && !m25p16_isReady()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t bytesTotalRemaining = bytesTotal;
|
||||||
|
|
||||||
|
while (bytesTotalRemaining > 0) {
|
||||||
|
uint32_t bytesTotalThisIteration;
|
||||||
|
uint32_t bytesRemainThisIteration;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Each page needs to be saved in a separate program operation, so
|
||||||
|
* if we would cross a page boundary, only write up to the boundary in this iteration:
|
||||||
|
*/
|
||||||
|
if (tailIndexInPage + bytesTotalRemaining > geometry->pageSize) {
|
||||||
|
bytesTotalThisIteration = geometry->pageSize - tailIndexInPage;
|
||||||
|
} else {
|
||||||
|
bytesTotalThisIteration = bytesTotalRemaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Are we at EOF already? Abort.
|
||||||
|
if (flashfsIsEOF()) {
|
||||||
|
// May as well throw away any buffered data
|
||||||
|
flashfsClearBuffer();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
m25p16_pageProgramBegin(tailAddress);
|
||||||
|
|
||||||
|
bytesRemainThisIteration = bytesTotalThisIteration;
|
||||||
|
|
||||||
|
for (i = 0; i < bufferCount; i++) {
|
||||||
|
if (bufferSizes[i] > 0) {
|
||||||
|
// Is buffer larger than our write limit? Write our limit out of it
|
||||||
|
if (bufferSizes[i] >= bytesRemainThisIteration) {
|
||||||
|
m25p16_pageProgramContinue(buffers[i], bytesRemainThisIteration);
|
||||||
|
|
||||||
|
buffers[i] += bytesRemainThisIteration;
|
||||||
|
bufferSizes[i] -= bytesRemainThisIteration;
|
||||||
|
|
||||||
|
bytesRemainThisIteration = 0;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// We'll still have more to write after finishing this buffer off
|
||||||
|
m25p16_pageProgramContinue(buffers[i], bufferSizes[i]);
|
||||||
|
|
||||||
|
bytesRemainThisIteration -= bufferSizes[i];
|
||||||
|
|
||||||
|
buffers[i] += bufferSizes[i];
|
||||||
|
bufferSizes[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m25p16_pageProgramFinish();
|
||||||
|
|
||||||
|
bytesTotalRemaining -= bytesTotalThisIteration;
|
||||||
|
|
||||||
|
// Advance the cursor in the file system to match the bytes we wrote
|
||||||
|
flashfsSetTailAddress(tailAddress + bytesTotalThisIteration);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We'll have to wait for that write to complete before we can issue the next one, so if
|
||||||
|
* the user requested asynchronous writes, break now.
|
||||||
|
*/
|
||||||
|
if (!sync)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytesTotal - bytesTotalRemaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Since the buffered data might wrap around the end of the circular buffer, we can have two segments of data to write,
|
||||||
|
* an initial portion and a possible wrapped portion.
|
||||||
|
*
|
||||||
|
* This routine will fill the details of those buffers into the provided arrays, which must be at least 2 elements long.
|
||||||
|
*/
|
||||||
|
static void flashfsGetDirtyDataBuffers(uint8_t const *buffers[], uint32_t bufferSizes[])
|
||||||
|
{
|
||||||
|
buffers[0] = flashWriteBuffer + bufferTail;
|
||||||
|
buffers[1] = flashWriteBuffer + 0;
|
||||||
|
|
||||||
|
if (bufferHead >= bufferTail) {
|
||||||
|
bufferSizes[0] = bufferHead - bufferTail;
|
||||||
|
bufferSizes[1] = 0;
|
||||||
|
} else {
|
||||||
|
bufferSizes[0] = FLASHFS_WRITE_BUFFER_SIZE - bufferTail;
|
||||||
|
bufferSizes[1] = bufferHead;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current offset of the file pointer within the volume.
|
||||||
|
*/
|
||||||
|
uint32_t flashfsGetOffset()
|
||||||
|
{
|
||||||
|
uint8_t const * buffers[2];
|
||||||
|
uint32_t bufferSizes[2];
|
||||||
|
|
||||||
|
// Dirty data in the buffers contributes to the offset
|
||||||
|
|
||||||
|
flashfsGetDirtyDataBuffers(buffers, bufferSizes);
|
||||||
|
|
||||||
|
return tailAddress + bufferSizes[0] + bufferSizes[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after bytes have been written from the buffer to advance the position of the tail by the given amount.
|
||||||
|
*/
|
||||||
|
static void flashfsAdvanceTailInBuffer(uint32_t delta)
|
||||||
|
{
|
||||||
|
bufferTail += delta;
|
||||||
|
|
||||||
|
// Wrap tail around the end of the buffer
|
||||||
|
if (bufferTail >= FLASHFS_WRITE_BUFFER_SIZE) {
|
||||||
|
bufferTail -= FLASHFS_WRITE_BUFFER_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flashfsBufferIsEmpty()) {
|
||||||
|
flashfsClearBuffer(); // Bring buffer pointers back to the start to be tidier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the flash is ready to accept writes, flush the buffer to it, otherwise schedule
|
||||||
|
* a flush for later and return immediately.
|
||||||
|
*
|
||||||
|
* Returns true if all data in the buffer has been flushed to the device.
|
||||||
|
*/
|
||||||
|
bool flashfsFlushAsync()
|
||||||
|
{
|
||||||
|
if (flashfsBufferIsEmpty()) {
|
||||||
|
shouldFlush = false;
|
||||||
|
return true; // Nothing to flush
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t const * buffers[2];
|
||||||
|
uint32_t bufferSizes[2];
|
||||||
|
uint32_t bytesWritten;
|
||||||
|
|
||||||
|
flashfsGetDirtyDataBuffers(buffers, bufferSizes);
|
||||||
|
bytesWritten = flashfsWriteBuffers(buffers, bufferSizes, 2, false);
|
||||||
|
flashfsAdvanceTailInBuffer(bytesWritten);
|
||||||
|
|
||||||
|
shouldFlush = !flashfsBufferIsEmpty();
|
||||||
|
|
||||||
|
return flashfsBufferIsEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for the flash to become ready and begin flushing any buffered data to flash.
|
||||||
|
*
|
||||||
|
* The flash will still be busy some time after this sync completes, but space will
|
||||||
|
* be freed up to accept more writes in the write buffer.
|
||||||
|
*/
|
||||||
|
void flashfsFlushSync()
|
||||||
|
{
|
||||||
|
if (flashfsBufferIsEmpty()) {
|
||||||
|
shouldFlush = false;
|
||||||
|
return; // Nothing to flush
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t const * buffers[2];
|
||||||
|
uint32_t bufferSizes[2];
|
||||||
|
|
||||||
|
flashfsGetDirtyDataBuffers(buffers, bufferSizes);
|
||||||
|
flashfsWriteBuffers(buffers, bufferSizes, 2, true);
|
||||||
|
|
||||||
|
// We've written our entire buffer now:
|
||||||
|
flashfsClearBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void flashfsSeekAbs(uint32_t offset)
|
||||||
|
{
|
||||||
|
flashfsFlushSync();
|
||||||
|
|
||||||
|
flashfsSetTailAddress(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void flashfsSeekRel(int32_t offset)
|
||||||
|
{
|
||||||
|
flashfsFlushSync();
|
||||||
|
|
||||||
|
flashfsSetTailAddress(tailAddress + offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the given byte asynchronously to the flash. If the buffer overflows, data is silently discarded.
|
||||||
|
*/
|
||||||
|
void flashfsWriteByte(uint8_t byte)
|
||||||
|
{
|
||||||
|
flashWriteBuffer[bufferHead++] = byte;
|
||||||
|
|
||||||
|
if (bufferHead >= FLASHFS_WRITE_BUFFER_SIZE) {
|
||||||
|
bufferHead = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldFlush || flashfsTransmitBufferUsed() >= FLASHFS_WRITE_BUFFER_AUTO_FLUSH_LEN) {
|
||||||
|
flashfsFlushAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the given buffer to the flash either synchronously or asynchronously depending on the 'sync' parameter.
|
||||||
|
*
|
||||||
|
* If writing asynchronously, data will be silently discarded if the buffer overflows.
|
||||||
|
* If writing synchronously, the routine will block waiting for the flash to become ready so will never drop data.
|
||||||
|
*/
|
||||||
|
void flashfsWrite(const uint8_t *data, unsigned int len, bool sync)
|
||||||
|
{
|
||||||
|
uint8_t const * buffers[3];
|
||||||
|
uint32_t bufferSizes[3];
|
||||||
|
|
||||||
|
// There could be two dirty buffers to write out already:
|
||||||
|
flashfsGetDirtyDataBuffers(buffers, bufferSizes);
|
||||||
|
|
||||||
|
// Plus the buffer the user supplied:
|
||||||
|
buffers[2] = data;
|
||||||
|
bufferSizes[2] = len;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Would writing this data to our buffer cause our buffer to reach the flush threshold? If so try to write through
|
||||||
|
* to the flash now
|
||||||
|
*/
|
||||||
|
if (shouldFlush || bufferSizes[0] + bufferSizes[1] + bufferSizes[2] >= FLASHFS_WRITE_BUFFER_AUTO_FLUSH_LEN) {
|
||||||
|
uint32_t bytesWritten;
|
||||||
|
|
||||||
|
// Attempt to write all three buffers through to the flash asynchronously
|
||||||
|
bytesWritten = flashfsWriteBuffers(buffers, bufferSizes, 3, false);
|
||||||
|
|
||||||
|
if (bufferSizes[0] == 0 && bufferSizes[1] == 0) {
|
||||||
|
// We wrote all the data that was previously buffered
|
||||||
|
flashfsClearBuffer();
|
||||||
|
|
||||||
|
if (bufferSizes[2] == 0) {
|
||||||
|
// And we wrote all the data the user supplied! Job done!
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We only wrote a portion of the old data, so advance the tail to remove the bytes we did write from the buffer
|
||||||
|
flashfsAdvanceTailInBuffer(bytesWritten);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is the remainder of the data to be written too big to fit in the buffers?
|
||||||
|
if (bufferSizes[0] + bufferSizes[1] + bufferSizes[2] > FLASHFS_WRITE_BUFFER_USABLE) {
|
||||||
|
if (sync) {
|
||||||
|
// Write it through synchronously
|
||||||
|
flashfsWriteBuffers(buffers, bufferSizes, 3, true);
|
||||||
|
flashfsClearBuffer();
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* Silently drop the data the user asked to write (i.e. no-op) since we can't buffer it and they
|
||||||
|
* requested async.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall through and add the remainder of the incoming data to our buffer
|
||||||
|
data = buffers[2];
|
||||||
|
len = bufferSizes[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffer up the data the user supplied instead of writing it right away
|
||||||
|
|
||||||
|
// First write the portion before we wrap around the end of the circular buffer
|
||||||
|
unsigned int bufferBytesBeforeWrap = FLASHFS_WRITE_BUFFER_SIZE - bufferHead;
|
||||||
|
|
||||||
|
unsigned int firstPortion = len < bufferBytesBeforeWrap ? len : bufferBytesBeforeWrap;
|
||||||
|
|
||||||
|
memcpy(flashWriteBuffer + bufferHead, data, firstPortion);
|
||||||
|
|
||||||
|
bufferHead += firstPortion;
|
||||||
|
|
||||||
|
data += firstPortion;
|
||||||
|
len -= firstPortion;
|
||||||
|
|
||||||
|
// If we wrap the head around, write the remainder to the start of the buffer (if any)
|
||||||
|
if (bufferHead == FLASHFS_WRITE_BUFFER_SIZE) {
|
||||||
|
memcpy(flashWriteBuffer + 0, data, len);
|
||||||
|
|
||||||
|
bufferHead = len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read `len` bytes from the given address into the supplied buffer.
|
||||||
|
*
|
||||||
|
* Returns the number of bytes actually read which may be less than that requested.
|
||||||
|
*/
|
||||||
|
int flashfsReadAbs(uint32_t address, uint8_t *buffer, unsigned int len)
|
||||||
|
{
|
||||||
|
int bytesRead;
|
||||||
|
|
||||||
|
// Did caller try to read past the end of the volume?
|
||||||
|
if (address + len > flashfsGetSize()) {
|
||||||
|
// Truncate their request
|
||||||
|
len = flashfsGetSize() - address;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since the read could overlap data in our dirty buffers, force a sync to clear those first
|
||||||
|
flashfsFlushSync();
|
||||||
|
|
||||||
|
bytesRead = m25p16_readBytes(address, buffer, len);
|
||||||
|
|
||||||
|
return bytesRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the offset of the start of the free space on the device (or the size of the device if it is full).
|
||||||
|
*/
|
||||||
|
int flashfsIdentifyStartOfFreeSpace()
|
||||||
|
{
|
||||||
|
/* Find the start of the free space on the device by examining the beginning of blocks with a binary search,
|
||||||
|
* looking for ones that appear to be erased. We can achieve this with good accuracy because an erased block
|
||||||
|
* is all bits set to 1, which pretty much never appears in reasonable size substrings of blackbox logs.
|
||||||
|
*
|
||||||
|
* To do better we might write a volume header instead, which would mark how much free space remains. But keeping
|
||||||
|
* a header up to date while logging would incur more writes to the flash, which would consume precious write
|
||||||
|
* bandwidth and block more often.
|
||||||
|
*/
|
||||||
|
|
||||||
|
enum {
|
||||||
|
/* We can choose whatever power of 2 size we like, which determines how much wastage of free space we'll have
|
||||||
|
* at the end of the last written data. But smaller blocksizes will require more searching.
|
||||||
|
*/
|
||||||
|
FREE_BLOCK_SIZE = 65536,
|
||||||
|
|
||||||
|
/* We don't expect valid data to ever contain this many consecutive uint32_t's of all 1 bits: */
|
||||||
|
FREE_BLOCK_TEST_SIZE_INTS = 4, // i.e. 16 bytes
|
||||||
|
FREE_BLOCK_TEST_SIZE_BYTES = FREE_BLOCK_TEST_SIZE_INTS * sizeof(uint32_t),
|
||||||
|
};
|
||||||
|
|
||||||
|
union {
|
||||||
|
uint8_t bytes[FREE_BLOCK_TEST_SIZE_BYTES];
|
||||||
|
uint32_t ints[FREE_BLOCK_TEST_SIZE_INTS];
|
||||||
|
} testBuffer;
|
||||||
|
|
||||||
|
int left = 0;
|
||||||
|
int right = flashfsGetSize() / FREE_BLOCK_SIZE;
|
||||||
|
int mid, result = right;
|
||||||
|
int i;
|
||||||
|
bool blockErased;
|
||||||
|
|
||||||
|
while (left < right) {
|
||||||
|
mid = (left + right) / 2;
|
||||||
|
|
||||||
|
m25p16_readBytes(mid * FREE_BLOCK_SIZE, testBuffer.bytes, FREE_BLOCK_TEST_SIZE_BYTES);
|
||||||
|
|
||||||
|
// Checking the buffer 4 bytes at a time like this is probably faster than byte-by-byte, but I didn't benchmark it :)
|
||||||
|
blockErased = true;
|
||||||
|
for (i = 0; i < FREE_BLOCK_TEST_SIZE_INTS; i++) {
|
||||||
|
if (testBuffer.ints[i] != 0xFFFFFFFF) {
|
||||||
|
blockErased = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blockErased) {
|
||||||
|
/* This erased block might be the leftmost erased block in the volume, but we'll need to continue the
|
||||||
|
* search leftwards to find out:
|
||||||
|
*/
|
||||||
|
result = mid;
|
||||||
|
|
||||||
|
right = mid;
|
||||||
|
} else {
|
||||||
|
left = mid + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result * FREE_BLOCK_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the file pointer is at the end of the device.
|
||||||
|
*/
|
||||||
|
bool flashfsIsEOF() {
|
||||||
|
return tailAddress >= flashfsGetSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call after initializing the flash chip in order to set up the filesystem.
|
||||||
|
*/
|
||||||
|
void flashfsInit()
|
||||||
|
{
|
||||||
|
// If we have a flash chip present at all
|
||||||
|
if (flashfsGetSize() > 0) {
|
||||||
|
// Start the file pointer off at the beginning of free space so caller can start writing immediately
|
||||||
|
flashfsSeekAbs(flashfsIdentifyStartOfFreeSpace());
|
||||||
|
}
|
||||||
|
}
|
52
src/main/io/flashfs.h
Normal file
52
src/main/io/flashfs.h
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Cleanflight.
|
||||||
|
*
|
||||||
|
* Cleanflight is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Cleanflight is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "drivers/flash.h"
|
||||||
|
|
||||||
|
#define FLASHFS_WRITE_BUFFER_SIZE 128
|
||||||
|
#define FLASHFS_WRITE_BUFFER_USABLE (FLASHFS_WRITE_BUFFER_SIZE - 1)
|
||||||
|
|
||||||
|
// Automatically trigger a flush when this much data is in the buffer
|
||||||
|
#define FLASHFS_WRITE_BUFFER_AUTO_FLUSH_LEN 64
|
||||||
|
|
||||||
|
void flashfsEraseCompletely();
|
||||||
|
void flashfsEraseRange(uint32_t start, uint32_t end);
|
||||||
|
|
||||||
|
uint32_t flashfsGetSize();
|
||||||
|
uint32_t flashfsGetOffset();
|
||||||
|
int flashfsIdentifyStartOfFreeSpace();
|
||||||
|
const flashGeometry_t* flashfsGetGeometry();
|
||||||
|
|
||||||
|
void flashfsSeekAbs(uint32_t offset);
|
||||||
|
void flashfsSeekRel(int32_t offset);
|
||||||
|
|
||||||
|
void flashfsWriteByte(uint8_t byte);
|
||||||
|
void flashfsWrite(const uint8_t *data, unsigned int len, bool sync);
|
||||||
|
|
||||||
|
int flashfsReadAbs(uint32_t offset, uint8_t *data, unsigned int len);
|
||||||
|
|
||||||
|
bool flashfsFlushAsync();
|
||||||
|
void flashfsFlushSync();
|
||||||
|
|
||||||
|
void flashfsInit();
|
||||||
|
|
||||||
|
bool flashfsIsReady();
|
||||||
|
bool flashfsIsEOF();
|
|
@ -47,6 +47,8 @@
|
||||||
#include "io/rc_controls.h"
|
#include "io/rc_controls.h"
|
||||||
#include "io/rc_curves.h"
|
#include "io/rc_curves.h"
|
||||||
|
|
||||||
|
#include "io/display.h"
|
||||||
|
|
||||||
#include "flight/pid.h"
|
#include "flight/pid.h"
|
||||||
#include "flight/navigation.h"
|
#include "flight/navigation.h"
|
||||||
|
|
||||||
|
@ -247,6 +249,17 @@ void processRcStickPositions(rxConfig_t *rxConfig, throttleStatus_e throttleStat
|
||||||
rcDelayCommand = 0; // allow autorepetition
|
rcDelayCommand = 0; // allow autorepetition
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef DISPLAY
|
||||||
|
if (rcSticks == THR_LO + YAW_CE + PIT_HI + ROL_LO) {
|
||||||
|
displayDisablePageCycling();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rcSticks == THR_LO + YAW_CE + PIT_HI + ROL_HI) {
|
||||||
|
displayEnablePageCycling();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isRangeActive(uint8_t auxChannelIndex, channelRange_t *range) {
|
bool isRangeActive(uint8_t auxChannelIndex, channelRange_t *range) {
|
||||||
|
@ -580,3 +593,9 @@ void useRcControlsConfig(modeActivationCondition_t *modeActivationConditions, es
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void resetAdjustmentStates(void)
|
||||||
|
{
|
||||||
|
memset(adjustmentStates, 0, sizeof(adjustmentStates));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -214,6 +214,7 @@ typedef struct adjustmentState_s {
|
||||||
|
|
||||||
#define MAX_ADJUSTMENT_RANGE_COUNT 12 // enough for 2 * 6pos switches.
|
#define MAX_ADJUSTMENT_RANGE_COUNT 12 // enough for 2 * 6pos switches.
|
||||||
|
|
||||||
|
void resetAdjustmentStates(void);
|
||||||
void configureAdjustment(uint8_t index, uint8_t auxChannelIndex, const adjustmentConfig_t *adjustmentConfig);
|
void configureAdjustment(uint8_t index, uint8_t auxChannelIndex, const adjustmentConfig_t *adjustmentConfig);
|
||||||
void updateAdjustmentStates(adjustmentRange_t *adjustmentRanges);
|
void updateAdjustmentStates(adjustmentRange_t *adjustmentRanges);
|
||||||
void processRcAdjustments(controlRateConfig_t *controlRateConfig, rxConfig_t *rxConfig);
|
void processRcAdjustments(controlRateConfig_t *controlRateConfig, rxConfig_t *rxConfig);
|
||||||
|
|
|
@ -52,6 +52,7 @@
|
||||||
#include "io/rc_controls.h"
|
#include "io/rc_controls.h"
|
||||||
#include "io/serial.h"
|
#include "io/serial.h"
|
||||||
#include "io/ledstrip.h"
|
#include "io/ledstrip.h"
|
||||||
|
#include "io/flashfs.h"
|
||||||
|
|
||||||
#include "rx/rx.h"
|
#include "rx/rx.h"
|
||||||
#include "rx/spektrum.h"
|
#include "rx/spektrum.h"
|
||||||
|
@ -121,6 +122,13 @@ static void cliColor(char *cmdline);
|
||||||
static void cliMixer(char *cmdline);
|
static void cliMixer(char *cmdline);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_FLASHFS
|
||||||
|
static void cliFlashInfo(char *cmdline);
|
||||||
|
static void cliFlashErase(char *cmdline);
|
||||||
|
static void cliFlashWrite(char *cmdline);
|
||||||
|
static void cliFlashRead(char *cmdline);
|
||||||
|
#endif
|
||||||
|
|
||||||
// signal that we're in cli mode
|
// signal that we're in cli mode
|
||||||
uint8_t cliMode = 0;
|
uint8_t cliMode = 0;
|
||||||
|
|
||||||
|
@ -155,13 +163,11 @@ static const char * const sensorTypeNames[] = {
|
||||||
"GYRO", "ACC", "BARO", "MAG", "SONAR", "GPS", "GPS+MAG", NULL
|
"GYRO", "ACC", "BARO", "MAG", "SONAR", "GPS", "GPS+MAG", NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
// FIXME the next time the EEPROM is bumped change the order of acc and gyro names so that "None" is second.
|
|
||||||
|
|
||||||
#define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG)
|
#define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG)
|
||||||
|
|
||||||
static const char * const sensorHardwareNames[4][11] = {
|
static const char * const sensorHardwareNames[4][11] = {
|
||||||
{ "", "None", "MPU6050", "L3G4200D", "MPU3050", "L3GD20", "MPU6000", "MPU6500", "FAKE", NULL },
|
{ "", "None", "MPU6050", "L3G4200D", "MPU3050", "L3GD20", "MPU6000", "MPU6500", "FAKE", NULL },
|
||||||
{ "", "ADXL345", "MPU6050", "MMA845x", "BMA280", "LSM303DLHC", "MPU6000", "MPU6500", "FAKE", "None", NULL },
|
{ "", "None", "ADXL345", "MPU6050", "MMA845x", "BMA280", "LSM303DLHC", "MPU6000", "MPU6500", "FAKE", NULL },
|
||||||
{ "", "None", "BMP085", "MS5611", NULL },
|
{ "", "None", "BMP085", "MS5611", NULL },
|
||||||
{ "", "None", "HMC5883", "AK8975", NULL }
|
{ "", "None", "HMC5883", "AK8975", NULL }
|
||||||
};
|
};
|
||||||
|
@ -185,6 +191,12 @@ const clicmd_t cmdTable[] = {
|
||||||
{ "dump", "dump configuration", cliDump },
|
{ "dump", "dump configuration", cliDump },
|
||||||
{ "exit", "", cliExit },
|
{ "exit", "", cliExit },
|
||||||
{ "feature", "list or -val or val", cliFeature },
|
{ "feature", "list or -val or val", cliFeature },
|
||||||
|
#ifdef USE_FLASHFS
|
||||||
|
{ "flash_erase", "erase flash chip", cliFlashErase },
|
||||||
|
{ "flash_info", "get flash chip details", cliFlashInfo },
|
||||||
|
{ "flash_read", "read text from the given address", cliFlashRead },
|
||||||
|
{ "flash_write", "write text to the given address", cliFlashWrite },
|
||||||
|
#endif
|
||||||
{ "get", "get variable value", cliGet },
|
{ "get", "get variable value", cliGet },
|
||||||
#ifdef GPS
|
#ifdef GPS
|
||||||
{ "gpspassthrough", "passthrough gps to serial", cliGpsPassthrough },
|
{ "gpspassthrough", "passthrough gps to serial", cliGpsPassthrough },
|
||||||
|
@ -201,7 +213,7 @@ const clicmd_t cmdTable[] = {
|
||||||
{ "profile", "index (0 to 2)", cliProfile },
|
{ "profile", "index (0 to 2)", cliProfile },
|
||||||
{ "rateprofile", "index (0 to 2)", cliRateProfile },
|
{ "rateprofile", "index (0 to 2)", cliRateProfile },
|
||||||
{ "save", "save and reboot", cliSave },
|
{ "save", "save and reboot", cliSave },
|
||||||
#ifndef CJMCU
|
#ifdef USE_SERVOS
|
||||||
{ "servo", "servo config", cliServo },
|
{ "servo", "servo config", cliServo },
|
||||||
#endif
|
#endif
|
||||||
{ "set", "name=value or blank or * for list", cliSet },
|
{ "set", "name=value or blank or * for list", cliSet },
|
||||||
|
@ -355,8 +367,11 @@ const clivalue_t valueTable[] = {
|
||||||
|
|
||||||
{ "yaw_control_direction", VAR_INT8 | MASTER_VALUE, &masterConfig.yaw_control_direction, -1, 1 },
|
{ "yaw_control_direction", VAR_INT8 | MASTER_VALUE, &masterConfig.yaw_control_direction, -1, 1 },
|
||||||
{ "yaw_direction", VAR_INT8 | PROFILE_VALUE, &masterConfig.profile[0].mixerConfig.yaw_direction, -1, 1 },
|
{ "yaw_direction", VAR_INT8 | PROFILE_VALUE, &masterConfig.profile[0].mixerConfig.yaw_direction, -1, 1 },
|
||||||
|
#ifdef USE_SERVOS
|
||||||
{ "tri_unarmed_servo", VAR_INT8 | PROFILE_VALUE, &masterConfig.profile[0].mixerConfig.tri_unarmed_servo, 0, 1 },
|
{ "tri_unarmed_servo", VAR_INT8 | PROFILE_VALUE, &masterConfig.profile[0].mixerConfig.tri_unarmed_servo, 0, 1 },
|
||||||
|
{ "servo_lowpass_freq", VAR_INT16 | PROFILE_VALUE, &masterConfig.profile[0].mixerConfig.servo_lowpass_freq, 10, 400},
|
||||||
|
{ "servo_lowpass_enable", VAR_INT8 | PROFILE_VALUE, &masterConfig.profile[0].mixerConfig.servo_lowpass_enable, 0, 1 },
|
||||||
|
#endif
|
||||||
{ "default_rate_profile", VAR_UINT8 | PROFILE_VALUE , &masterConfig.profile[0].defaultRateProfileIndex, 0, MAX_CONTROL_RATE_PROFILE_COUNT - 1 },
|
{ "default_rate_profile", VAR_UINT8 | PROFILE_VALUE , &masterConfig.profile[0].defaultRateProfileIndex, 0, MAX_CONTROL_RATE_PROFILE_COUNT - 1 },
|
||||||
{ "rc_rate", VAR_UINT8 | CONTROL_RATE_VALUE, &masterConfig.controlRateProfiles[0].rcRate8, 0, 250 },
|
{ "rc_rate", VAR_UINT8 | CONTROL_RATE_VALUE, &masterConfig.controlRateProfiles[0].rcRate8, 0, 250 },
|
||||||
{ "rc_expo", VAR_UINT8 | CONTROL_RATE_VALUE, &masterConfig.controlRateProfiles[0].rcExpo8, 0, 100 },
|
{ "rc_expo", VAR_UINT8 | CONTROL_RATE_VALUE, &masterConfig.controlRateProfiles[0].rcExpo8, 0, 100 },
|
||||||
|
@ -373,7 +388,9 @@ const clivalue_t valueTable[] = {
|
||||||
{ "failsafe_min_usec", VAR_UINT16 | PROFILE_VALUE, &masterConfig.profile[0].failsafeConfig.failsafe_min_usec, 100, PWM_RANGE_MAX },
|
{ "failsafe_min_usec", VAR_UINT16 | PROFILE_VALUE, &masterConfig.profile[0].failsafeConfig.failsafe_min_usec, 100, PWM_RANGE_MAX },
|
||||||
{ "failsafe_max_usec", VAR_UINT16 | PROFILE_VALUE, &masterConfig.profile[0].failsafeConfig.failsafe_max_usec, 100, PWM_RANGE_MAX + (PWM_RANGE_MAX - PWM_RANGE_MIN) },
|
{ "failsafe_max_usec", VAR_UINT16 | PROFILE_VALUE, &masterConfig.profile[0].failsafeConfig.failsafe_max_usec, 100, PWM_RANGE_MAX + (PWM_RANGE_MAX - PWM_RANGE_MIN) },
|
||||||
|
|
||||||
|
#ifdef USE_SERVOS
|
||||||
{ "gimbal_flags", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].gimbalConfig.gimbal_flags, 0, 255},
|
{ "gimbal_flags", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].gimbalConfig.gimbal_flags, 0, 255},
|
||||||
|
#endif
|
||||||
|
|
||||||
{ "acc_hardware", VAR_UINT8 | MASTER_VALUE, &masterConfig.acc_hardware, 0, ACC_NONE },
|
{ "acc_hardware", VAR_UINT8 | MASTER_VALUE, &masterConfig.acc_hardware, 0, ACC_NONE },
|
||||||
{ "acc_lpf_factor", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].acc_lpf_factor, 0, 250 },
|
{ "acc_lpf_factor", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].acc_lpf_factor, 0, 250 },
|
||||||
|
@ -433,6 +450,7 @@ const clivalue_t valueTable[] = {
|
||||||
#ifdef BLACKBOX
|
#ifdef BLACKBOX
|
||||||
{ "blackbox_rate_num", VAR_UINT8 | MASTER_VALUE, &masterConfig.blackbox_rate_num, 1, 32 },
|
{ "blackbox_rate_num", VAR_UINT8 | MASTER_VALUE, &masterConfig.blackbox_rate_num, 1, 32 },
|
||||||
{ "blackbox_rate_denom", VAR_UINT8 | MASTER_VALUE, &masterConfig.blackbox_rate_denom, 1, 32 },
|
{ "blackbox_rate_denom", VAR_UINT8 | MASTER_VALUE, &masterConfig.blackbox_rate_denom, 1, 32 },
|
||||||
|
{ "blackbox_device", VAR_UINT8 | MASTER_VALUE, &masterConfig.blackbox_device, 0, 1 },
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -752,7 +770,7 @@ static void cliColor(char *cmdline)
|
||||||
|
|
||||||
static void cliServo(char *cmdline)
|
static void cliServo(char *cmdline)
|
||||||
{
|
{
|
||||||
#ifdef CJMCU
|
#ifndef USE_SERVOS
|
||||||
UNUSED(cmdline);
|
UNUSED(cmdline);
|
||||||
#else
|
#else
|
||||||
enum { SERVO_ARGUMENT_COUNT = 6 };
|
enum { SERVO_ARGUMENT_COUNT = 6 };
|
||||||
|
@ -822,6 +840,88 @@ static void cliServo(char *cmdline)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_FLASHFS
|
||||||
|
|
||||||
|
static void cliFlashInfo(char *cmdline)
|
||||||
|
{
|
||||||
|
const flashGeometry_t *layout = flashfsGetGeometry();
|
||||||
|
|
||||||
|
UNUSED(cmdline);
|
||||||
|
|
||||||
|
printf("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u, usedSize=%u\r\n",
|
||||||
|
layout->sectors, layout->sectorSize, layout->pagesPerSector, layout->pageSize, layout->totalSize, flashfsGetOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cliFlashErase(char *cmdline)
|
||||||
|
{
|
||||||
|
UNUSED(cmdline);
|
||||||
|
|
||||||
|
printf("Erasing, please wait...\r\n");
|
||||||
|
flashfsEraseCompletely();
|
||||||
|
|
||||||
|
while (!flashfsIsReady()) {
|
||||||
|
delay(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Done.\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cliFlashWrite(char *cmdline)
|
||||||
|
{
|
||||||
|
uint32_t address = atoi(cmdline);
|
||||||
|
char *text = strchr(cmdline, ' ');
|
||||||
|
|
||||||
|
if (!text) {
|
||||||
|
printf("Missing text to write.\r\n");
|
||||||
|
} else {
|
||||||
|
flashfsSeekAbs(address);
|
||||||
|
flashfsWrite((uint8_t*)text, strlen(text), true);
|
||||||
|
flashfsFlushSync();
|
||||||
|
|
||||||
|
printf("Wrote %u bytes at %u.\r\n", strlen(text), address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cliFlashRead(char *cmdline)
|
||||||
|
{
|
||||||
|
uint32_t address = atoi(cmdline);
|
||||||
|
uint32_t length;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
uint8_t buffer[32];
|
||||||
|
|
||||||
|
char *nextArg = strchr(cmdline, ' ');
|
||||||
|
|
||||||
|
if (!nextArg) {
|
||||||
|
printf("Missing length argument.\r\n");
|
||||||
|
} else {
|
||||||
|
length = atoi(nextArg);
|
||||||
|
|
||||||
|
printf("Reading %u bytes at %u:\r\n", length, address);
|
||||||
|
|
||||||
|
while (length > 0) {
|
||||||
|
int bytesRead;
|
||||||
|
|
||||||
|
bytesRead = flashfsReadAbs(address, buffer, length < sizeof(buffer) ? length : sizeof(buffer));
|
||||||
|
|
||||||
|
for (i = 0; i < bytesRead; i++) {
|
||||||
|
cliWrite(buffer[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
length -= bytesRead;
|
||||||
|
address += bytesRead;
|
||||||
|
|
||||||
|
if (bytesRead == 0) {
|
||||||
|
//Assume we reached the end of the volume or something fatal happened
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printf("\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
static void dumpValues(uint16_t mask)
|
static void dumpValues(uint16_t mask)
|
||||||
{
|
{
|
||||||
uint32_t i;
|
uint32_t i;
|
||||||
|
@ -1238,6 +1338,7 @@ static void cliRateProfile(char *cmdline)
|
||||||
static void cliReboot(void) {
|
static void cliReboot(void) {
|
||||||
cliPrint("\r\nRebooting");
|
cliPrint("\r\nRebooting");
|
||||||
waitForSerialPortToFinishTransmitting(cliPort);
|
waitForSerialPortToFinishTransmitting(cliPort);
|
||||||
|
stopMotors();
|
||||||
systemReset();
|
systemReset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,7 @@
|
||||||
#include "io/gimbal.h"
|
#include "io/gimbal.h"
|
||||||
#include "io/serial.h"
|
#include "io/serial.h"
|
||||||
#include "io/ledstrip.h"
|
#include "io/ledstrip.h"
|
||||||
|
#include "io/flashfs.h"
|
||||||
|
|
||||||
#include "telemetry/telemetry.h"
|
#include "telemetry/telemetry.h"
|
||||||
|
|
||||||
|
@ -232,6 +233,10 @@ const char *boardIdentifier = TARGET_BOARD_IDENTIFIER;
|
||||||
// DEPRECATED - Use MSP_BUILD_INFO instead
|
// DEPRECATED - Use MSP_BUILD_INFO instead
|
||||||
#define MSP_BF_BUILD_INFO 69 //out message build date as well as some space for future expansion
|
#define MSP_BF_BUILD_INFO 69 //out message build date as well as some space for future expansion
|
||||||
|
|
||||||
|
#define MSP_DATAFLASH_SUMMARY 70 //out message - get description of dataflash chip
|
||||||
|
#define MSP_DATAFLASH_READ 71 //out message - get content of dataflash chip
|
||||||
|
#define MSP_DATAFLASH_ERASE 72 //in message - erase dataflash chip
|
||||||
|
|
||||||
//
|
//
|
||||||
// Multwii original MSP commands
|
// Multwii original MSP commands
|
||||||
//
|
//
|
||||||
|
@ -435,24 +440,24 @@ static uint32_t read32(void)
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void headSerialResponse(uint8_t err, uint8_t s)
|
static void headSerialResponse(uint8_t err, uint8_t responseBodySize)
|
||||||
{
|
{
|
||||||
serialize8('$');
|
serialize8('$');
|
||||||
serialize8('M');
|
serialize8('M');
|
||||||
serialize8(err ? '!' : '>');
|
serialize8(err ? '!' : '>');
|
||||||
currentPort->checksum = 0; // start calculating a new checksum
|
currentPort->checksum = 0; // start calculating a new checksum
|
||||||
serialize8(s);
|
serialize8(responseBodySize);
|
||||||
serialize8(currentPort->cmdMSP);
|
serialize8(currentPort->cmdMSP);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void headSerialReply(uint8_t s)
|
static void headSerialReply(uint8_t responseBodySize)
|
||||||
{
|
{
|
||||||
headSerialResponse(0, s);
|
headSerialResponse(0, responseBodySize);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void headSerialError(uint8_t s)
|
static void headSerialError(uint8_t responseBodySize)
|
||||||
{
|
{
|
||||||
headSerialResponse(1, s);
|
headSerialResponse(1, responseBodySize);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void tailSerialReply(void)
|
static void tailSerialReply(void)
|
||||||
|
@ -532,6 +537,46 @@ reset:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void serializeDataflashSummaryReply(void)
|
||||||
|
{
|
||||||
|
headSerialReply(1 + 3 * 4);
|
||||||
|
#ifdef USE_FLASHFS
|
||||||
|
const flashGeometry_t *geometry = flashfsGetGeometry();
|
||||||
|
serialize8(flashfsIsReady() ? 1 : 0);
|
||||||
|
serialize32(geometry->sectors);
|
||||||
|
serialize32(geometry->totalSize);
|
||||||
|
serialize32(flashfsGetOffset()); // Effectively the current number of bytes stored on the volume
|
||||||
|
#else
|
||||||
|
serialize8(0);
|
||||||
|
serialize32(0);
|
||||||
|
serialize32(0);
|
||||||
|
serialize32(0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef USE_FLASHFS
|
||||||
|
static void serializeDataflashReadReply(uint32_t address, uint8_t size)
|
||||||
|
{
|
||||||
|
uint8_t buffer[128];
|
||||||
|
int bytesRead;
|
||||||
|
|
||||||
|
if (size > sizeof(buffer)) {
|
||||||
|
size = sizeof(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
headSerialReply(4 + size);
|
||||||
|
|
||||||
|
serialize32(address);
|
||||||
|
|
||||||
|
// bytesRead will be lower than that requested if we reach end of volume
|
||||||
|
bytesRead = flashfsReadAbs(address, buffer, size);
|
||||||
|
|
||||||
|
for (int i = 0; i < bytesRead; i++) {
|
||||||
|
serialize8(buffer[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static void resetMspPort(mspPort_t *mspPortToReset, serialPort_t *serialPort, mspPortUsage_e usage)
|
static void resetMspPort(mspPort_t *mspPortToReset, serialPort_t *serialPort, mspPortUsage_e usage)
|
||||||
{
|
{
|
||||||
memset(mspPortToReset, 0, sizeof(mspPort_t));
|
memset(mspPortToReset, 0, sizeof(mspPort_t));
|
||||||
|
@ -783,6 +828,7 @@ static bool processOutCommand(uint8_t cmdMSP)
|
||||||
for (i = 0; i < 3; i++)
|
for (i = 0; i < 3; i++)
|
||||||
serialize16(magADC[i]);
|
serialize16(magADC[i]);
|
||||||
break;
|
break;
|
||||||
|
#ifdef USE_SERVOS
|
||||||
case MSP_SERVO:
|
case MSP_SERVO:
|
||||||
s_struct((uint8_t *)&servo, 16);
|
s_struct((uint8_t *)&servo, 16);
|
||||||
break;
|
break;
|
||||||
|
@ -801,6 +847,7 @@ static bool processOutCommand(uint8_t cmdMSP)
|
||||||
serialize8(currentProfile->servoConf[i].forwardFromChannel);
|
serialize8(currentProfile->servoConf[i].forwardFromChannel);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
#endif
|
||||||
case MSP_MOTOR:
|
case MSP_MOTOR:
|
||||||
s_struct((uint8_t *)motor, 16);
|
s_struct((uint8_t *)motor, 16);
|
||||||
break;
|
break;
|
||||||
|
@ -1136,6 +1183,21 @@ static bool processOutCommand(uint8_t cmdMSP)
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
case MSP_DATAFLASH_SUMMARY:
|
||||||
|
serializeDataflashSummaryReply();
|
||||||
|
break;
|
||||||
|
|
||||||
|
#ifdef USE_FLASHFS
|
||||||
|
case MSP_DATAFLASH_READ:
|
||||||
|
{
|
||||||
|
uint32_t readAddress = read32();
|
||||||
|
|
||||||
|
serializeDataflashReadReply(readAddress, 128);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
|
||||||
case MSP_BF_BUILD_INFO:
|
case MSP_BF_BUILD_INFO:
|
||||||
headSerialReply(11 + 4 + 4);
|
headSerialReply(11 + 4 + 4);
|
||||||
for (i = 0; i < 11; i++)
|
for (i = 0; i < 11; i++)
|
||||||
|
@ -1304,6 +1366,7 @@ static bool processInCommand(void)
|
||||||
motor_disarmed[i] = read16();
|
motor_disarmed[i] = read16();
|
||||||
break;
|
break;
|
||||||
case MSP_SET_SERVO_CONF:
|
case MSP_SET_SERVO_CONF:
|
||||||
|
#ifdef USE_SERVOS
|
||||||
for (i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
|
for (i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
|
||||||
currentProfile->servoConf[i].min = read16();
|
currentProfile->servoConf[i].min = read16();
|
||||||
currentProfile->servoConf[i].max = read16();
|
currentProfile->servoConf[i].max = read16();
|
||||||
|
@ -1317,11 +1380,14 @@ static bool processInCommand(void)
|
||||||
}
|
}
|
||||||
currentProfile->servoConf[i].rate = read8();
|
currentProfile->servoConf[i].rate = read8();
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
break;
|
break;
|
||||||
case MSP_SET_CHANNEL_FORWARDING:
|
case MSP_SET_CHANNEL_FORWARDING:
|
||||||
|
#ifdef USE_SERVOS
|
||||||
for (i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
|
for (i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
|
||||||
currentProfile->servoConf[i].forwardFromChannel = read8();
|
currentProfile->servoConf[i].forwardFromChannel = read8();
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
break;
|
break;
|
||||||
case MSP_RESET_CONF:
|
case MSP_RESET_CONF:
|
||||||
if (!ARMING_FLAG(ARMED)) {
|
if (!ARMING_FLAG(ARMED)) {
|
||||||
|
@ -1345,6 +1411,13 @@ static bool processInCommand(void)
|
||||||
writeEEPROM();
|
writeEEPROM();
|
||||||
readEEPROM();
|
readEEPROM();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
#ifdef USE_FLASHFS
|
||||||
|
case MSP_DATAFLASH_ERASE:
|
||||||
|
flashfsEraseCompletely();
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef GPS
|
#ifdef GPS
|
||||||
case MSP_SET_RAW_GPS:
|
case MSP_SET_RAW_GPS:
|
||||||
if (read8()) {
|
if (read8()) {
|
||||||
|
@ -1609,6 +1682,7 @@ void mspProcess(void)
|
||||||
while (!isSerialTransmitBufferEmpty(candidatePort->port)) {
|
while (!isSerialTransmitBufferEmpty(candidatePort->port)) {
|
||||||
delay(50);
|
delay(50);
|
||||||
}
|
}
|
||||||
|
stopMotors();
|
||||||
systemReset();
|
systemReset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,10 +46,12 @@
|
||||||
#include "drivers/bus_i2c.h"
|
#include "drivers/bus_i2c.h"
|
||||||
#include "drivers/bus_spi.h"
|
#include "drivers/bus_spi.h"
|
||||||
#include "drivers/inverter.h"
|
#include "drivers/inverter.h"
|
||||||
|
#include "drivers/flash_m25p16.h"
|
||||||
|
|
||||||
#include "rx/rx.h"
|
#include "rx/rx.h"
|
||||||
|
|
||||||
#include "io/serial.h"
|
#include "io/serial.h"
|
||||||
|
#include "io/flashfs.h"
|
||||||
#include "io/gps.h"
|
#include "io/gps.h"
|
||||||
#include "io/escservo.h"
|
#include "io/escservo.h"
|
||||||
#include "io/rc_controls.h"
|
#include "io/rc_controls.h"
|
||||||
|
@ -127,11 +129,20 @@ void SetSysClock(void);
|
||||||
void SetSysClock(bool overclock);
|
void SetSysClock(bool overclock);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
SYSTEM_STATE_INITIALISING = 0,
|
||||||
|
SYSTEM_STATE_CONFIG_LOADED = (1 << 0),
|
||||||
|
SYSTEM_STATE_SENSORS_READY = (1 << 1),
|
||||||
|
SYSTEM_STATE_MOTORS_READY = (1 << 2),
|
||||||
|
SYSTEM_STATE_READY = (1 << 7)
|
||||||
|
} systemState_e;
|
||||||
|
|
||||||
|
static uint8_t systemState = SYSTEM_STATE_INITIALISING;
|
||||||
|
|
||||||
void init(void)
|
void init(void)
|
||||||
{
|
{
|
||||||
uint8_t i;
|
uint8_t i;
|
||||||
drv_pwm_config_t pwm_params;
|
drv_pwm_config_t pwm_params;
|
||||||
bool sensorsOK = false;
|
|
||||||
|
|
||||||
printfSupportInit();
|
printfSupportInit();
|
||||||
|
|
||||||
|
@ -140,6 +151,8 @@ void init(void)
|
||||||
ensureEEPROMContainsValidData();
|
ensureEEPROMContainsValidData();
|
||||||
readEEPROM();
|
readEEPROM();
|
||||||
|
|
||||||
|
systemState |= SYSTEM_STATE_CONFIG_LOADED;
|
||||||
|
|
||||||
#ifdef STM32F303
|
#ifdef STM32F303
|
||||||
// start fpu
|
// start fpu
|
||||||
SCB->CPACR = (0x3 << (10*2)) | (0x3 << (11*2));
|
SCB->CPACR = (0x3 << (10*2)) | (0x3 << (11*2));
|
||||||
|
@ -199,18 +212,22 @@ void init(void)
|
||||||
&& masterConfig.batteryConfig.currentMeterType == CURRENT_SENSOR_ADC;
|
&& masterConfig.batteryConfig.currentMeterType == CURRENT_SENSOR_ADC;
|
||||||
pwm_params.useLEDStrip = feature(FEATURE_LED_STRIP);
|
pwm_params.useLEDStrip = feature(FEATURE_LED_STRIP);
|
||||||
pwm_params.usePPM = feature(FEATURE_RX_PPM);
|
pwm_params.usePPM = feature(FEATURE_RX_PPM);
|
||||||
pwm_params.useOneshot = feature(FEATURE_ONESHOT125);
|
|
||||||
pwm_params.useSerialRx = feature(FEATURE_RX_SERIAL);
|
pwm_params.useSerialRx = feature(FEATURE_RX_SERIAL);
|
||||||
|
|
||||||
|
#ifdef USE_SERVOS
|
||||||
pwm_params.useServos = isMixerUsingServos();
|
pwm_params.useServos = isMixerUsingServos();
|
||||||
pwm_params.extraServos = currentProfile->gimbalConfig.gimbal_flags & GIMBAL_FORWARDAUX;
|
pwm_params.extraServos = currentProfile->gimbalConfig.gimbal_flags & GIMBAL_FORWARDAUX;
|
||||||
pwm_params.motorPwmRate = masterConfig.motor_pwm_rate;
|
pwm_params.servoCenterPulse = masterConfig.escAndServoConfig.servoCenterPulse;
|
||||||
pwm_params.servoPwmRate = masterConfig.servo_pwm_rate;
|
pwm_params.servoPwmRate = masterConfig.servo_pwm_rate;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
pwm_params.useOneshot = feature(FEATURE_ONESHOT125);
|
||||||
|
pwm_params.motorPwmRate = masterConfig.motor_pwm_rate;
|
||||||
pwm_params.idlePulse = PULSE_1MS; // standard PWM for brushless ESC (default, overridden below)
|
pwm_params.idlePulse = PULSE_1MS; // standard PWM for brushless ESC (default, overridden below)
|
||||||
if (feature(FEATURE_3D))
|
if (feature(FEATURE_3D))
|
||||||
pwm_params.idlePulse = masterConfig.flight3DConfig.neutral3d;
|
pwm_params.idlePulse = masterConfig.flight3DConfig.neutral3d;
|
||||||
if (pwm_params.motorPwmRate > 500)
|
if (pwm_params.motorPwmRate > 500)
|
||||||
pwm_params.idlePulse = 0; // brushed motors
|
pwm_params.idlePulse = 0; // brushed motors
|
||||||
pwm_params.servoCenterPulse = masterConfig.escAndServoConfig.servoCenterPulse;
|
|
||||||
|
|
||||||
pwmRxInit(masterConfig.inputFilteringMode);
|
pwmRxInit(masterConfig.inputFilteringMode);
|
||||||
|
|
||||||
|
@ -218,6 +235,8 @@ void init(void)
|
||||||
|
|
||||||
mixerUsePWMOutputConfiguration(pwmOutputConfiguration);
|
mixerUsePWMOutputConfiguration(pwmOutputConfiguration);
|
||||||
|
|
||||||
|
systemState |= SYSTEM_STATE_MOTORS_READY;
|
||||||
|
|
||||||
#ifdef BEEPER
|
#ifdef BEEPER
|
||||||
beeperConfig_t beeperConfig = {
|
beeperConfig_t beeperConfig = {
|
||||||
.gpioPin = BEEP_PIN,
|
.gpioPin = BEEP_PIN,
|
||||||
|
@ -296,11 +315,12 @@ void init(void)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
sensorsOK = sensorsAutodetect(&masterConfig.sensorAlignmentConfig, masterConfig.gyro_lpf, masterConfig.acc_hardware, masterConfig.mag_hardware, currentProfile->mag_declination);
|
if (!sensorsAutodetect(&masterConfig.sensorAlignmentConfig, masterConfig.gyro_lpf, masterConfig.acc_hardware, masterConfig.mag_hardware, currentProfile->mag_declination)) {
|
||||||
|
// if gyro was not detected due to whatever reason, we give up now.
|
||||||
// if gyro was not detected due to whatever reason, we give up now.
|
|
||||||
if (!sensorsOK)
|
|
||||||
failureMode(3);
|
failureMode(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
systemState |= SYSTEM_STATE_SENSORS_READY;
|
||||||
|
|
||||||
LED1_ON;
|
LED1_ON;
|
||||||
LED0_OFF;
|
LED0_OFF;
|
||||||
|
@ -328,7 +348,9 @@ void init(void)
|
||||||
cliInit(&masterConfig.serialConfig);
|
cliInit(&masterConfig.serialConfig);
|
||||||
|
|
||||||
failsafe = failsafeInit(&masterConfig.rxConfig);
|
failsafe = failsafeInit(&masterConfig.rxConfig);
|
||||||
|
|
||||||
beepcodeInit(failsafe);
|
beepcodeInit(failsafe);
|
||||||
|
|
||||||
rxInit(&masterConfig.rxConfig, failsafe);
|
rxInit(&masterConfig.rxConfig, failsafe);
|
||||||
|
|
||||||
#ifdef GPS
|
#ifdef GPS
|
||||||
|
@ -364,6 +386,18 @@ void init(void)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_FLASHFS
|
||||||
|
#ifdef NAZE
|
||||||
|
if (hardwareRevision == NAZE32_REV5) {
|
||||||
|
m25p16_init();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef SPRACINGF3
|
||||||
|
m25p16_init();
|
||||||
|
#endif
|
||||||
|
flashfsInit();
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef BLACKBOX
|
#ifdef BLACKBOX
|
||||||
initBlackbox();
|
initBlackbox();
|
||||||
#endif
|
#endif
|
||||||
|
@ -404,6 +438,7 @@ void init(void)
|
||||||
#ifdef USE_OLED_GPS_DEBUG_PAGE_ONLY
|
#ifdef USE_OLED_GPS_DEBUG_PAGE_ONLY
|
||||||
displayShowFixedPage(PAGE_GPS);
|
displayShowFixedPage(PAGE_GPS);
|
||||||
#else
|
#else
|
||||||
|
displayResetPageCycling();
|
||||||
displayEnablePageCycling();
|
displayEnablePageCycling();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -412,6 +447,8 @@ void init(void)
|
||||||
#ifdef CJMCU
|
#ifdef CJMCU
|
||||||
LED2_ON;
|
LED2_ON;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
systemState |= SYSTEM_STATE_READY;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef SOFTSERIAL_LOOPBACK
|
#ifdef SOFTSERIAL_LOOPBACK
|
||||||
|
@ -440,6 +477,9 @@ int main(void) {
|
||||||
void HardFault_Handler(void)
|
void HardFault_Handler(void)
|
||||||
{
|
{
|
||||||
// fall out of the sky
|
// fall out of the sky
|
||||||
writeAllMotors(masterConfig.escAndServoConfig.mincommand);
|
uint8_t requiredState = SYSTEM_STATE_CONFIG_LOADED | SYSTEM_STATE_MOTORS_READY;
|
||||||
|
if ((systemState & requiredState) == requiredState) {
|
||||||
|
stopMotors();
|
||||||
|
}
|
||||||
while (1);
|
while (1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -725,7 +725,12 @@ void loop(void)
|
||||||
);
|
);
|
||||||
|
|
||||||
mixTable();
|
mixTable();
|
||||||
|
|
||||||
|
#ifdef USE_SERVOS
|
||||||
|
filterServos();
|
||||||
writeServos();
|
writeServos();
|
||||||
|
#endif
|
||||||
|
|
||||||
writeMotors();
|
writeMotors();
|
||||||
|
|
||||||
#ifdef BLACKBOX
|
#ifdef BLACKBOX
|
||||||
|
|
|
@ -20,15 +20,15 @@
|
||||||
// Type of accelerometer used/detected
|
// Type of accelerometer used/detected
|
||||||
typedef enum {
|
typedef enum {
|
||||||
ACC_DEFAULT = 0,
|
ACC_DEFAULT = 0,
|
||||||
ACC_ADXL345 = 1,
|
ACC_NONE = 1,
|
||||||
ACC_MPU6050 = 2,
|
ACC_ADXL345 = 2,
|
||||||
ACC_MMA8452 = 3,
|
ACC_MPU6050 = 3,
|
||||||
ACC_BMA280 = 4,
|
ACC_MMA8452 = 4,
|
||||||
ACC_LSM303DLHC = 5,
|
ACC_BMA280 = 5,
|
||||||
ACC_SPI_MPU6000 = 6,
|
ACC_LSM303DLHC = 6,
|
||||||
ACC_SPI_MPU6500 = 7,
|
ACC_SPI_MPU6000 = 7,
|
||||||
ACC_FAKE = 8,
|
ACC_SPI_MPU6500 = 8,
|
||||||
ACC_NONE = 9
|
ACC_FAKE = 9,
|
||||||
} accelerationSensor_e;
|
} accelerationSensor_e;
|
||||||
|
|
||||||
extern sensor_align_e accAlign;
|
extern sensor_align_e accAlign;
|
||||||
|
|
|
@ -110,7 +110,7 @@ const mpu6050Config_t *selectMPU6050Config(void)
|
||||||
#ifdef USE_FAKE_GYRO
|
#ifdef USE_FAKE_GYRO
|
||||||
static void fakeGyroInit(void) {}
|
static void fakeGyroInit(void) {}
|
||||||
static void fakeGyroRead(int16_t *gyroData) {
|
static void fakeGyroRead(int16_t *gyroData) {
|
||||||
UNUSED(gyroData);
|
memset(gyroData, 0, sizeof(int16_t[XYZ_AXIS_COUNT]));
|
||||||
}
|
}
|
||||||
static void fakeGyroReadTemp(int16_t *tempData) {
|
static void fakeGyroReadTemp(int16_t *tempData) {
|
||||||
UNUSED(tempData);
|
UNUSED(tempData);
|
||||||
|
@ -129,7 +129,7 @@ bool fakeGyroDetect(gyro_t *gyro, uint16_t lpf)
|
||||||
#ifdef USE_FAKE_ACC
|
#ifdef USE_FAKE_ACC
|
||||||
static void fakeAccInit(void) {}
|
static void fakeAccInit(void) {}
|
||||||
static void fakeAccRead(int16_t *accData) {
|
static void fakeAccRead(int16_t *accData) {
|
||||||
UNUSED(accData);
|
memset(accData, 0, sizeof(int16_t[XYZ_AXIS_COUNT]));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fakeAccDetect(acc_t *acc)
|
bool fakeAccDetect(acc_t *acc)
|
||||||
|
@ -267,14 +267,6 @@ retry:
|
||||||
switch (accHardwareToUse) {
|
switch (accHardwareToUse) {
|
||||||
case ACC_DEFAULT:
|
case ACC_DEFAULT:
|
||||||
; // fallthrough
|
; // fallthrough
|
||||||
case ACC_FAKE:
|
|
||||||
#ifdef USE_FAKE_ACC
|
|
||||||
if (fakeAccDetect(&acc)) {
|
|
||||||
accHardware = ACC_FAKE;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
; // fallthrough
|
|
||||||
case ACC_ADXL345: // ADXL345
|
case ACC_ADXL345: // ADXL345
|
||||||
#ifdef USE_ACC_ADXL345
|
#ifdef USE_ACC_ADXL345
|
||||||
acc_params.useFifo = false;
|
acc_params.useFifo = false;
|
||||||
|
@ -365,6 +357,14 @@ retry:
|
||||||
accHardware = ACC_SPI_MPU6500;
|
accHardware = ACC_SPI_MPU6500;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
; // fallthrough
|
||||||
|
case ACC_FAKE:
|
||||||
|
#ifdef USE_FAKE_ACC
|
||||||
|
if (fakeAccDetect(&acc)) {
|
||||||
|
accHardware = ACC_FAKE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
; // fallthrough
|
; // fallthrough
|
||||||
case ACC_NONE: // disable ACC
|
case ACC_NONE: // disable ACC
|
||||||
|
|
|
@ -92,6 +92,7 @@
|
||||||
#define GPS
|
#define GPS
|
||||||
//#define DISPLAY
|
//#define DISPLAY
|
||||||
#define AUTOTUNE
|
#define AUTOTUNE
|
||||||
|
#define USE_SERVOS
|
||||||
|
|
||||||
|
|
||||||
#define SPEKTRUM_BIND
|
#define SPEKTRUM_BIND
|
||||||
|
|
|
@ -106,6 +106,7 @@
|
||||||
#define TELEMETRY
|
#define TELEMETRY
|
||||||
#define SERIAL_RX
|
#define SERIAL_RX
|
||||||
#define AUTOTUNE
|
#define AUTOTUNE
|
||||||
|
#define USE_SERVOS
|
||||||
|
|
||||||
#define SPEKTRUM_BIND
|
#define SPEKTRUM_BIND
|
||||||
// USART3, PB11 (Flexport)
|
// USART3, PB11 (Flexport)
|
||||||
|
|
|
@ -35,10 +35,18 @@
|
||||||
|
|
||||||
#define USABLE_TIMER_CHANNEL_COUNT 18
|
#define USABLE_TIMER_CHANNEL_COUNT 18
|
||||||
|
|
||||||
|
#define USE_SPI
|
||||||
|
#define USE_SPI_DEVICE_1
|
||||||
|
|
||||||
#define GYRO
|
#define GYRO
|
||||||
#define USE_GYRO_L3GD20
|
#define USE_GYRO_L3GD20
|
||||||
#define USE_GYRO_MPU6050
|
#define USE_GYRO_MPU6050
|
||||||
|
|
||||||
|
#define L3GD20_SPI SPI1
|
||||||
|
#define L3GD20_CS_GPIO_CLK_PERIPHERAL RCC_AHBPeriph_GPIOE
|
||||||
|
#define L3GD20_CS_GPIO GPIOE
|
||||||
|
#define L3GD20_CS_PIN GPIO_Pin_3
|
||||||
|
|
||||||
#define GYRO_L3GD20_ALIGN CW90_DEG
|
#define GYRO_L3GD20_ALIGN CW90_DEG
|
||||||
#define GYRO_MPU6050_ALIGN CW0_DEG
|
#define GYRO_MPU6050_ALIGN CW0_DEG
|
||||||
|
|
||||||
|
@ -114,3 +122,4 @@
|
||||||
#define TELEMETRY
|
#define TELEMETRY
|
||||||
#define SERIAL_RX
|
#define SERIAL_RX
|
||||||
#define AUTOTUNE
|
#define AUTOTUNE
|
||||||
|
#define USE_SERVOS
|
||||||
|
|
|
@ -61,6 +61,8 @@
|
||||||
// #define SOFT_I2C_PB67
|
// #define SOFT_I2C_PB67
|
||||||
|
|
||||||
#define SERIAL_RX
|
#define SERIAL_RX
|
||||||
|
//#define BLACKBOX
|
||||||
|
//#define USE_SERVOS
|
||||||
|
|
||||||
#define SPEKTRUM_BIND
|
#define SPEKTRUM_BIND
|
||||||
// USART2, PA3
|
// USART2, PA3
|
||||||
|
|
|
@ -124,6 +124,7 @@
|
||||||
#define TELEMETRY
|
#define TELEMETRY
|
||||||
#define SERIAL_RX
|
#define SERIAL_RX
|
||||||
#define AUTOTUNE
|
#define AUTOTUNE
|
||||||
|
#define USE_SERVOS
|
||||||
|
|
||||||
#define SPEKTRUM_BIND
|
#define SPEKTRUM_BIND
|
||||||
// USART2, PA3
|
// USART2, PA3
|
||||||
|
|
|
@ -58,7 +58,7 @@ void detectHardwareRevision(void)
|
||||||
#define SPI_DEVICE_MPU (2)
|
#define SPI_DEVICE_MPU (2)
|
||||||
|
|
||||||
#define M25P16_INSTRUCTION_RDID 0x9F
|
#define M25P16_INSTRUCTION_RDID 0x9F
|
||||||
#define FLASH_M25P16 (0x202015)
|
#define FLASH_M25P16_ID (0x202015)
|
||||||
|
|
||||||
uint8_t detectSpiDevice(void)
|
uint8_t detectSpiDevice(void)
|
||||||
{
|
{
|
||||||
|
@ -74,7 +74,7 @@ uint8_t detectSpiDevice(void)
|
||||||
DISABLE_SPI_CS;
|
DISABLE_SPI_CS;
|
||||||
|
|
||||||
flash_id = in[1] << 16 | in[2] << 8 | in[3];
|
flash_id = in[1] << 16 | in[2] << 8 | in[3];
|
||||||
if (flash_id == FLASH_M25P16)
|
if (flash_id == FLASH_M25P16_ID)
|
||||||
return SPI_DEVICE_FLASH;
|
return SPI_DEVICE_FLASH;
|
||||||
|
|
||||||
// try autodetect MPU
|
// try autodetect MPU
|
||||||
|
|
|
@ -56,10 +56,19 @@
|
||||||
#define NAZE_SPI_CS_GPIO GPIOB
|
#define NAZE_SPI_CS_GPIO GPIOB
|
||||||
#define NAZE_SPI_CS_PIN GPIO_Pin_12
|
#define NAZE_SPI_CS_PIN GPIO_Pin_12
|
||||||
|
|
||||||
|
// We either have this 16mbit flash chip on SPI or the MPU6500 acc/gyro depending on board revision:
|
||||||
|
#define M25P16_CS_GPIO NAZE_SPI_CS_GPIO
|
||||||
|
#define M25P16_CS_PIN NAZE_SPI_CS_PIN
|
||||||
|
#define M25P16_SPI_INSTANCE NAZE_SPI_INSTANCE
|
||||||
|
|
||||||
#define MPU6500_CS_GPIO NAZE_SPI_CS_GPIO
|
#define MPU6500_CS_GPIO NAZE_SPI_CS_GPIO
|
||||||
#define MPU6500_CS_PIN NAZE_SPI_CS_PIN
|
#define MPU6500_CS_PIN NAZE_SPI_CS_PIN
|
||||||
#define MPU6500_SPI_INSTANCE NAZE_SPI_INSTANCE
|
#define MPU6500_SPI_INSTANCE NAZE_SPI_INSTANCE
|
||||||
|
|
||||||
|
#define USE_FLASHFS
|
||||||
|
|
||||||
|
#define USE_FLASH_M25P16
|
||||||
|
|
||||||
#define GYRO
|
#define GYRO
|
||||||
#define USE_GYRO_MPU3050
|
#define USE_GYRO_MPU3050
|
||||||
#define USE_GYRO_MPU6050
|
#define USE_GYRO_MPU6050
|
||||||
|
@ -144,6 +153,7 @@
|
||||||
#define TELEMETRY
|
#define TELEMETRY
|
||||||
#define SERIAL_RX
|
#define SERIAL_RX
|
||||||
#define AUTOTUNE
|
#define AUTOTUNE
|
||||||
|
#define USE_SERVOS
|
||||||
|
|
||||||
#define SPEKTRUM_BIND
|
#define SPEKTRUM_BIND
|
||||||
// USART2, PA3
|
// USART2, PA3
|
||||||
|
|
|
@ -45,6 +45,7 @@
|
||||||
#define TELEMETRY
|
#define TELEMETRY
|
||||||
#define SERIAL_RX
|
#define SERIAL_RX
|
||||||
#define AUTOTUNE
|
#define AUTOTUNE
|
||||||
|
#define USE_SERVOS
|
||||||
|
|
||||||
#define SPEKTRUM_BIND
|
#define SPEKTRUM_BIND
|
||||||
// USART2, PA3
|
// USART2, PA3
|
||||||
|
|
|
@ -111,3 +111,4 @@
|
||||||
#define SERIAL_RX
|
#define SERIAL_RX
|
||||||
#define AUTOTUNE
|
#define AUTOTUNE
|
||||||
#define BLACKBOX
|
#define BLACKBOX
|
||||||
|
#define USE_SERVOS
|
||||||
|
|
|
@ -135,4 +135,4 @@
|
||||||
#define TELEMETRY
|
#define TELEMETRY
|
||||||
#define SERIAL_RX
|
#define SERIAL_RX
|
||||||
#define AUTOTUNE
|
#define AUTOTUNE
|
||||||
|
#define USE_SERVOS
|
||||||
|
|
|
@ -90,6 +90,7 @@
|
||||||
#define SERIAL_RX
|
#define SERIAL_RX
|
||||||
#define GPS
|
#define GPS
|
||||||
#define DISPLAY
|
#define DISPLAY
|
||||||
|
#define USE_SERVOS
|
||||||
|
|
||||||
#define LED_STRIP
|
#define LED_STRIP
|
||||||
#if 1
|
#if 1
|
||||||
|
|
|
@ -45,6 +45,9 @@
|
||||||
#define USE_MAG_HMC5883
|
#define USE_MAG_HMC5883
|
||||||
#define MAG_HMC5883_ALIGN CW270_DEG
|
#define MAG_HMC5883_ALIGN CW270_DEG
|
||||||
|
|
||||||
|
#define USE_FLASHFS
|
||||||
|
#define USE_FLASH_M25P16
|
||||||
|
|
||||||
#define BEEPER
|
#define BEEPER
|
||||||
#define LED0
|
#define LED0
|
||||||
|
|
||||||
|
@ -81,8 +84,23 @@
|
||||||
#define USE_I2C
|
#define USE_I2C
|
||||||
#define I2C_DEVICE (I2CDEV_1) // PB6/SCL, PB7/SDA
|
#define I2C_DEVICE (I2CDEV_1) // PB6/SCL, PB7/SDA
|
||||||
|
|
||||||
//#define USE_SPI
|
#define USE_SPI
|
||||||
//#define USE_SPI_DEVICE_2 // PB12,13,14,15 on AF5
|
#define USE_SPI_DEVICE_2 // PB12,13,14,15 on AF5
|
||||||
|
|
||||||
|
#define SPI2_GPIO GPIOB
|
||||||
|
#define SPI2_GPIO_PERIPHERAL RCC_AHBPeriph_GPIOB
|
||||||
|
#define SPI2_NSS_PIN Pin_12
|
||||||
|
#define SPI2_NSS_PIN_SOURCE GPIO_PinSource12
|
||||||
|
#define SPI2_SCK_PIN Pin_13
|
||||||
|
#define SPI2_SCK_PIN_SOURCE GPIO_PinSource13
|
||||||
|
#define SPI2_MISO_PIN Pin_14
|
||||||
|
#define SPI2_MISO_PIN_SOURCE GPIO_PinSource14
|
||||||
|
#define SPI2_MOSI_PIN Pin_15
|
||||||
|
#define SPI2_MOSI_PIN_SOURCE GPIO_PinSource15
|
||||||
|
|
||||||
|
#define M25P16_CS_GPIO GPIOB
|
||||||
|
#define M25P16_CS_PIN GPIO_Pin_12
|
||||||
|
#define M25P16_SPI_INSTANCE SPI2
|
||||||
|
|
||||||
#define USE_ADC
|
#define USE_ADC
|
||||||
|
|
||||||
|
@ -126,3 +144,4 @@
|
||||||
#define SERIAL_RX
|
#define SERIAL_RX
|
||||||
#define AUTOTUNE
|
#define AUTOTUNE
|
||||||
#define DISPLAY
|
#define DISPLAY
|
||||||
|
#define USE_SERVOS
|
||||||
|
|
|
@ -36,9 +36,17 @@
|
||||||
|
|
||||||
#define BEEPER_INVERTED
|
#define BEEPER_INVERTED
|
||||||
|
|
||||||
|
#define USE_SPI
|
||||||
|
#define USE_SPI_DEVICE_1
|
||||||
|
|
||||||
#define GYRO
|
#define GYRO
|
||||||
#define USE_GYRO_L3GD20
|
#define USE_GYRO_L3GD20
|
||||||
|
|
||||||
|
#define L3GD20_SPI SPI1
|
||||||
|
#define L3GD20_CS_GPIO_CLK_PERIPHERAL RCC_AHBPeriph_GPIOE
|
||||||
|
#define L3GD20_CS_GPIO GPIOE
|
||||||
|
#define L3GD20_CS_PIN GPIO_Pin_3
|
||||||
|
|
||||||
#define GYRO_L3GD20_ALIGN CW90_DEG
|
#define GYRO_L3GD20_ALIGN CW90_DEG
|
||||||
|
|
||||||
#define ACC
|
#define ACC
|
||||||
|
@ -88,3 +96,4 @@
|
||||||
#define TELEMETRY
|
#define TELEMETRY
|
||||||
#define SERIAL_RX
|
#define SERIAL_RX
|
||||||
#define AUTOTUNE
|
#define AUTOTUNE
|
||||||
|
#define USE_SERVOS
|
||||||
|
|
|
@ -52,7 +52,9 @@ TESTS = \
|
||||||
telemetry_hott_unittest \
|
telemetry_hott_unittest \
|
||||||
rc_controls_unittest \
|
rc_controls_unittest \
|
||||||
ledstrip_unittest \
|
ledstrip_unittest \
|
||||||
ws2811_unittest
|
ws2811_unittest \
|
||||||
|
encoding_unittest \
|
||||||
|
lowpass_unittest
|
||||||
|
|
||||||
# All Google Test headers. Usually you shouldn't change this
|
# All Google Test headers. Usually you shouldn't change this
|
||||||
# definition.
|
# definition.
|
||||||
|
@ -129,6 +131,25 @@ battery_unittest : \
|
||||||
$(OBJECT_DIR)/gtest_main.a
|
$(OBJECT_DIR)/gtest_main.a
|
||||||
|
|
||||||
$(CXX) $(CXX_FLAGS) -lpthread $^ -o $(OBJECT_DIR)/$@
|
$(CXX) $(CXX_FLAGS) -lpthread $^ -o $(OBJECT_DIR)/$@
|
||||||
|
|
||||||
|
$(OBJECT_DIR)/common/encoding.o : $(USER_DIR)/common/encoding.c $(USER_DIR)/common/encoding.h $(GTEST_HEADERS)
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(CC) $(C_FLAGS) $(TEST_CFLAGS) -c $(USER_DIR)/common/encoding.c -o $@
|
||||||
|
|
||||||
|
$(OBJECT_DIR)/encoding_unittest.o : \
|
||||||
|
$(TEST_DIR)/encoding_unittest.cc \
|
||||||
|
$(USER_DIR)/common/encoding.h \
|
||||||
|
$(GTEST_HEADERS)
|
||||||
|
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(CXX) $(CXX_FLAGS) $(TEST_CFLAGS) -c $(TEST_DIR)/encoding_unittest.cc -o $@
|
||||||
|
|
||||||
|
encoding_unittest : \
|
||||||
|
$(OBJECT_DIR)/common/encoding.o \
|
||||||
|
$(OBJECT_DIR)/encoding_unittest.o \
|
||||||
|
$(OBJECT_DIR)/gtest_main.a
|
||||||
|
|
||||||
|
$(CXX) $(CXX_FLAGS) -lpthread $^ -o $(OBJECT_DIR)/$@
|
||||||
|
|
||||||
$(OBJECT_DIR)/flight/imu.o : \
|
$(OBJECT_DIR)/flight/imu.o : \
|
||||||
$(USER_DIR)/flight/imu.c \
|
$(USER_DIR)/flight/imu.c \
|
||||||
|
@ -319,6 +340,31 @@ ws2811_unittest : \
|
||||||
|
|
||||||
$(CXX) $(CXX_FLAGS) -lpthread $^ -o $(OBJECT_DIR)/$@
|
$(CXX) $(CXX_FLAGS) -lpthread $^ -o $(OBJECT_DIR)/$@
|
||||||
|
|
||||||
|
|
||||||
|
$(OBJECT_DIR)/flight/lowpass.o : \
|
||||||
|
$(USER_DIR)/flight/lowpass.c \
|
||||||
|
$(USER_DIR)/flight/lowpass.h \
|
||||||
|
$(GTEST_HEADERS)
|
||||||
|
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(CC) $(C_FLAGS) $(TEST_CFLAGS) -c $(USER_DIR)/flight/lowpass.c -o $@
|
||||||
|
|
||||||
|
$(OBJECT_DIR)/lowpass_unittest.o : \
|
||||||
|
$(TEST_DIR)/lowpass_unittest.cc \
|
||||||
|
$(USER_DIR)/flight/lowpass.h \
|
||||||
|
$(GTEST_HEADERS)
|
||||||
|
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(CXX) $(CXX_FLAGS) $(TEST_CFLAGS) -c $(TEST_DIR)/lowpass_unittest.cc -o $@
|
||||||
|
|
||||||
|
lowpass_unittest : \
|
||||||
|
$(OBJECT_DIR)/flight/lowpass.o \
|
||||||
|
$(OBJECT_DIR)/lowpass_unittest.o \
|
||||||
|
$(OBJECT_DIR)/gtest_main.a
|
||||||
|
|
||||||
|
$(CXX) $(CXX_FLAGS) -lpthread $^ -o $(OBJECT_DIR)/$@
|
||||||
|
|
||||||
|
|
||||||
test: $(TESTS)
|
test: $(TESTS)
|
||||||
set -e && for test in $(TESTS) ; do \
|
set -e && for test in $(TESTS) ; do \
|
||||||
$(OBJECT_DIR)/$$test; \
|
$(OBJECT_DIR)/$$test; \
|
||||||
|
|
84
src/test/unit/encoding_unittest.cc
Normal file
84
src/test/unit/encoding_unittest.cc
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Cleanflight.
|
||||||
|
*
|
||||||
|
* Cleanflight is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Cleanflight is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include "common/encoding.h"
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "unittest_macros.h"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
typedef struct zigzagEncodingExpectation_t {
|
||||||
|
int32_t input;
|
||||||
|
uint32_t expected;
|
||||||
|
} zigzagEncodingExpectation_t;
|
||||||
|
|
||||||
|
typedef struct floatToIntEncodingExpectation_t {
|
||||||
|
float input;
|
||||||
|
uint32_t expected;
|
||||||
|
} floatToIntEncodingExpectation_t;
|
||||||
|
|
||||||
|
TEST(EncodingTest, ZigzagEncodingTest)
|
||||||
|
{
|
||||||
|
// given
|
||||||
|
zigzagEncodingExpectation_t expectations[] = {
|
||||||
|
{ 0, 0},
|
||||||
|
{-1, 1},
|
||||||
|
{ 1, 2},
|
||||||
|
{-2, 3},
|
||||||
|
{ 2, 4},
|
||||||
|
|
||||||
|
{ 2147483646, 4294967292},
|
||||||
|
{-2147483647, 4294967293},
|
||||||
|
{ 2147483647, 4294967294},
|
||||||
|
{-2147483648, 4294967295},
|
||||||
|
};
|
||||||
|
int expectationCount = sizeof(expectations) / sizeof(expectations[0]);
|
||||||
|
|
||||||
|
// expect
|
||||||
|
|
||||||
|
for (int i = 0; i < expectationCount; i++) {
|
||||||
|
zigzagEncodingExpectation_t *expectation = &expectations[i];
|
||||||
|
|
||||||
|
EXPECT_EQ(expectation->expected, zigzagEncode(expectation->input));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EncodingTest, FloatToIntEncodingTest)
|
||||||
|
{
|
||||||
|
// given
|
||||||
|
floatToIntEncodingExpectation_t expectations[] = {
|
||||||
|
{0.0, 0x00000000},
|
||||||
|
{2.0, 0x40000000}, // Exponent should be in the top bits
|
||||||
|
{4.5, 0x40900000}
|
||||||
|
};
|
||||||
|
int expectationCount = sizeof(expectations) / sizeof(expectations[0]);
|
||||||
|
|
||||||
|
// expect
|
||||||
|
|
||||||
|
for (int i = 0; i < expectationCount; i++) {
|
||||||
|
floatToIntEncodingExpectation_t *expectation = &expectations[i];
|
||||||
|
|
||||||
|
EXPECT_EQ(expectation->expected, castFloatBytesToInt(expectation->input));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// STUBS
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
}
|
128
src/test/unit/lowpass_unittest.cc
Normal file
128
src/test/unit/lowpass_unittest.cc
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Cleanflight.
|
||||||
|
*
|
||||||
|
* Cleanflight is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Cleanflight is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include "flight/lowpass.h"
|
||||||
|
}
|
||||||
|
|
||||||
|
static lowpass_t lowpassFilterReference;
|
||||||
|
static lowpass_t lowpassFilter;
|
||||||
|
|
||||||
|
#include "unittest_macros.h"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
static float lowpassRef(lowpass_t *filter, float in, int16_t freq)
|
||||||
|
{
|
||||||
|
int16_t coefIdx;
|
||||||
|
float out;
|
||||||
|
|
||||||
|
// Check to see if cutoff frequency changed
|
||||||
|
if (freq != filter->freq) {
|
||||||
|
filter->init = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize if needed
|
||||||
|
if (!filter->init) {
|
||||||
|
generateLowpassCoeffs2(freq, filter);
|
||||||
|
for (coefIdx = 0; coefIdx < LOWPASS_NUM_COEF; coefIdx++) {
|
||||||
|
filter->xf[coefIdx] = in;
|
||||||
|
filter->yf[coefIdx] = in;
|
||||||
|
}
|
||||||
|
filter->init = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delays
|
||||||
|
for (coefIdx = LOWPASS_NUM_COEF-1; coefIdx > 0; coefIdx--) {
|
||||||
|
filter->xf[coefIdx] = filter->xf[coefIdx-1];
|
||||||
|
filter->yf[coefIdx] = filter->yf[coefIdx-1];
|
||||||
|
}
|
||||||
|
filter->xf[0] = in;
|
||||||
|
|
||||||
|
// Accumulate result
|
||||||
|
out = filter->xf[0] * filter->bf[0];
|
||||||
|
for (coefIdx = 1; coefIdx < LOWPASS_NUM_COEF; coefIdx++) {
|
||||||
|
out += filter->xf[coefIdx] * filter->bf[coefIdx];
|
||||||
|
out -= filter->yf[coefIdx] * filter->af[coefIdx];
|
||||||
|
}
|
||||||
|
filter->yf[0] = out;
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LowpassTest, Lowpass)
|
||||||
|
{
|
||||||
|
int16_t servoCmds[3000];
|
||||||
|
int16_t expectedOut[3000];
|
||||||
|
int16_t referenceOut;
|
||||||
|
int16_t filterOut;
|
||||||
|
uint16_t sampleIdx;
|
||||||
|
int16_t freq;
|
||||||
|
|
||||||
|
uint16_t sampleCount = sizeof(servoCmds) / sizeof(int16_t);
|
||||||
|
|
||||||
|
// generate inputs and expecteds
|
||||||
|
for (sampleIdx = 0; sampleIdx < sampleCount; sampleIdx++) {
|
||||||
|
if (sampleIdx < 1000) {
|
||||||
|
servoCmds[sampleIdx] = 500;
|
||||||
|
} else if (sampleIdx >= 1000 && sampleIdx < 2000) {
|
||||||
|
servoCmds[sampleIdx] = 2500;
|
||||||
|
} else {
|
||||||
|
servoCmds[sampleIdx] = 1500;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((sampleIdx >= 900 && sampleIdx < 1000) ||
|
||||||
|
(sampleIdx >= 1900 && sampleIdx < 2000)||
|
||||||
|
(sampleIdx >= 2900 && sampleIdx < 3000)) {
|
||||||
|
expectedOut[sampleIdx] = servoCmds[sampleIdx];
|
||||||
|
} else {
|
||||||
|
expectedOut[sampleIdx] = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test all frequencies
|
||||||
|
for (freq = 10; freq <= 400; freq++) {
|
||||||
|
printf("*** Testing freq: %d (%f)\n", freq, ((float)freq * 0.001f));
|
||||||
|
|
||||||
|
// Run tests
|
||||||
|
for (sampleIdx = 0; sampleIdx < sampleCount; sampleIdx++)
|
||||||
|
{
|
||||||
|
// Filter under test
|
||||||
|
filterOut = (int16_t)lowpassFixed(&lowpassFilter, servoCmds[sampleIdx], freq);
|
||||||
|
|
||||||
|
// Floating-point reference
|
||||||
|
referenceOut = (int16_t)(lowpassRef(&lowpassFilterReference, (float)servoCmds[sampleIdx], freq) + 0.5f);
|
||||||
|
|
||||||
|
if (expectedOut[sampleIdx] >= 0) {
|
||||||
|
EXPECT_EQ(filterOut, expectedOut[sampleIdx]);
|
||||||
|
}
|
||||||
|
// Some tolerance
|
||||||
|
// TODO adjust precision to remove the need for this?
|
||||||
|
EXPECT_LE(filterOut, referenceOut + 1);
|
||||||
|
EXPECT_GE(filterOut, referenceOut - 1);
|
||||||
|
} // for each sample
|
||||||
|
} // for each freq
|
||||||
|
}
|
||||||
|
|
||||||
|
// STUBS
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
#define GPS
|
#define GPS
|
||||||
#define TELEMETRY
|
#define TELEMETRY
|
||||||
#define LED_STRIP
|
#define LED_STRIP
|
||||||
|
#define USE_SERVOS
|
||||||
|
|
||||||
#define SERIAL_PORT_COUNT 4
|
#define SERIAL_PORT_COUNT 4
|
||||||
|
|
||||||
|
|
|
@ -705,6 +705,9 @@ void mwArm(void) {}
|
||||||
void feature(uint32_t) {}
|
void feature(uint32_t) {}
|
||||||
void sensors(uint32_t) {}
|
void sensors(uint32_t) {}
|
||||||
void mwDisarm(void) {}
|
void mwDisarm(void) {}
|
||||||
|
void displayDisablePageCycling() {}
|
||||||
|
void displayEnablePageCycling() {}
|
||||||
|
|
||||||
uint8_t getCurrentControlRateProfile(void) {
|
uint8_t getCurrentControlRateProfile(void) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue