Use RY835AI or RY836AI; add more fields to GDL90 report

pull/578/head
Eric Westphal 2016-07-14 21:38:15 -04:00
rodzic ddaf673006
commit d91a2a10e7
6 zmienionych plików z 328 dodań i 48 usunięć

Wyświetl plik

@ -163,8 +163,8 @@ func prepareMessage(data []byte) []byte {
// Compute CRC before modifying the message. // Compute CRC before modifying the message.
crc := crcCompute(data) crc := crcCompute(data)
// Add the two CRC16 bytes before replacing control characters. // Add the two CRC16 bytes before replacing control characters.
data = append(data, byte(crc&0xFF)) data = append(data, byte(crc & 0xFF))
data = append(data, byte(crc>>8)) data = append(data, byte((crc >> 8) & 0xFF))
tmp := []byte{0x7E} // Flag start. tmp := []byte{0x7E} // Flag start.
@ -426,8 +426,8 @@ func makeStratuxStatus() []byte {
// Connected hardware: number of radios. // Connected hardware: number of radios.
msg[15] = msg[15] | (byte(globalStatus.Devices) & 0x3) msg[15] = msg[15] | (byte(globalStatus.Devices) & 0x3)
// Connected hardware: RY835AI. // Connected hardware: RY83XAI.
if globalStatus.RY835AI_connected { if globalStatus.RY83XAI_connected {
msg[15] = msg[15] | (1 << 2) msg[15] = msg[15] | (1 << 2)
} }
@ -975,7 +975,7 @@ type status struct {
GPS_satellites_tracked uint16 GPS_satellites_tracked uint16
GPS_connected bool GPS_connected bool
GPS_solution string GPS_solution string
RY835AI_connected bool RY83XAI_connected bool
Uptime int64 Uptime int64
Clock time.Time Clock time.Time
UptimeClock time.Time UptimeClock time.Time
@ -1290,7 +1290,7 @@ func main() {
//FIXME: Only do this if data logging is enabled. //FIXME: Only do this if data logging is enabled.
initDataLog() initDataLog()
initRY835AI() initRY83XAI()
// Start the heartbeat message loop in the background, once per second. // Start the heartbeat message loop in the background, once per second.
go heartBeatSender() go heartBeatSender()

Wyświetl plik

@ -135,7 +135,10 @@ func sendToAllConnectedClients(msg networkMessage) {
if sleepFlag { if sleepFlag {
continue continue
} }
netconn.Conn.Write(msg.msg) // Write immediately. _, err := netconn.Conn.Write(msg.msg) // Write immediately.
if err != nil {
log.Printf("GDL Message error: %s\n", err.Error())
}
totalNetworkMessagesSent++ totalNetworkMessagesSent++
globalStatus.NetworkDataMessagesSent++ globalStatus.NetworkDataMessagesSent++
globalStatus.NetworkDataMessagesSentNonqueueable++ globalStatus.NetworkDataMessagesSentNonqueueable++

Wyświetl plik

@ -4,7 +4,7 @@
that can be found in the LICENSE file, herein included that can be found in the LICENSE file, herein included
as part of this header. as part of this header.
ry835ai.go: GPS functions, GPS init, AHRS status messages, other external sensor monitoring. ry83Xai.go: GPS functions, GPS init, AHRS status messages, other external sensor monitoring.
*/ */
package main package main
@ -28,6 +28,7 @@ import (
"os/exec" "os/exec"
"../mpu" "../mpu"
"errors"
) )
const ( const (
@ -37,6 +38,7 @@ const (
SAT_TYPE_GALILEO = 3 // GAxxx; NMEA IDs unknown SAT_TYPE_GALILEO = 3 // GAxxx; NMEA IDs unknown
SAT_TYPE_BEIDOU = 4 // GBxxx; NMEA IDs 201-235 SAT_TYPE_BEIDOU = 4 // GBxxx; NMEA IDs 201-235
SAT_TYPE_SBAS = 10 // NMEA IDs 33-54 SAT_TYPE_SBAS = 10 // NMEA IDs 33-54
MPURETRYNUM = 5 // Number of times to retry connecting to MPU
) )
type SatelliteInfo struct { type SatelliteInfo struct {
@ -432,9 +434,9 @@ func validateNMEAChecksum(s string) (string, bool) {
// changes while on the ground and "movement" is really only changes in GPS fix as it settles down. // changes while on the ground and "movement" is really only changes in GPS fix as it settles down.
//TODO: Some more robust checking above current and last speed. //TODO: Some more robust checking above current and last speed.
//TODO: Dynamic adjust for gain based on groundspeed //TODO: Dynamic adjust for gain based on groundspeed
//westphae: Do I need to do anything here? //TODO westphae: Do I need to do anything here?
func setTrueCourse(groundSpeed uint16, trueCourse float64) { func setTrueCourse(groundSpeed uint16, trueCourse float64) {
if myMPU != nil && globalStatus.RY835AI_connected && globalSettings.AHRS_Enabled { if myMPU != nil && globalStatus.RY83XAI_connected && globalSettings.AHRS_Enabled {
if mySituation.GroundSpeed >= 7 && groundSpeed >= 7 { if mySituation.GroundSpeed >= 7 && groundSpeed >= 7 {
myMPU.ResetHeading(trueCourse, 0.10) myMPU.ResetHeading(trueCourse, 0.10)
} }
@ -942,7 +944,7 @@ func processNMEALine(l string) (sentenceUsed bool) {
tmpSituation := mySituation // If we decide to not use the data in this message, then don't make incomplete changes in mySituation. tmpSituation := mySituation // If we decide to not use the data in this message, then don't make incomplete changes in mySituation.
//$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A //$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A
/* check RY835 man for NMEA version, if >2.2, add mode field /* check RY83XAI man for NMEA version, if >2.2, add mode field
Where: Where:
RMC Recommended Minimum sentence C RMC Recommended Minimum sentence C
123519 Fix taken at 12:35:19 UTC 123519 Fix taken at 12:35:19 UTC
@ -1363,10 +1365,31 @@ func initBMP180() error {
return nil return nil
} }
//TODO westphae: set up myMPU as MPU6050 or MPU9250, depending on which exists
func initMPU() error { func initMPU() error {
myMPU, _ = mpu.NewMPU6050() //TODO: error checking. var err error
return nil
for i:=0; i < MPURETRYNUM; i++ {
myMPU, err = mpu.NewMPU9250()
if err != nil {
time.Sleep(100 * time.Millisecond)
} else {
log.Println("AHRS: Successfully initialized MPU9250")
return nil
}
}
for i := 0; i < MPURETRYNUM; i++ {
myMPU, err = mpu.NewMPU6050()
if err != nil {
time.Sleep(100 * time.Millisecond)
} else {
log.Println("AHRS: Successfully initialized MPU6050")
return nil
}
}
log.Println("AHRS Error: couldn't initialize MPU9250 or MPU6050")
return errors.New("AHRS Error: couldn't initialize MPU9250 or MPU6050")
} }
func initI2C() error { func initI2C() error {
@ -1377,21 +1400,21 @@ func initI2C() error {
// Unused at the moment. 5 second update, since read functions in bmp180 are slow. // Unused at the moment. 5 second update, since read functions in bmp180 are slow.
func tempAndPressureReader() { func tempAndPressureReader() {
timer := time.NewTicker(5 * time.Second) timer := time.NewTicker(5 * time.Second)
for globalStatus.RY835AI_connected && globalSettings.AHRS_Enabled { for globalStatus.RY83XAI_connected && globalSettings.AHRS_Enabled {
<-timer.C <-timer.C
// Read temperature and pressure altitude. // Read temperature and pressure altitude.
temp, alt, err_bmp180 := readBMP180() temp, alt, err_bmp180 := readBMP180()
// Process. // Process.
if err_bmp180 != nil { if err_bmp180 != nil {
log.Printf("readBMP180(): %s\n", err_bmp180.Error()) log.Printf("readBMP180(): %s\n", err_bmp180.Error())
globalStatus.RY835AI_connected = false globalStatus.RY83XAI_connected = false
} else { } else {
mySituation.Temp = temp mySituation.Temp = temp
mySituation.Pressure_alt = alt mySituation.Pressure_alt = alt
mySituation.LastTempPressTime = stratuxClock.Time mySituation.LastTempPressTime = stratuxClock.Time
} }
} }
globalStatus.RY835AI_connected = false globalStatus.RY83XAI_connected = false
} }
func makeFFAHRSSimReport() { func makeFFAHRSSimReport() {
@ -1401,19 +1424,23 @@ func makeFFAHRSSimReport() {
} }
func makeAHRSGDL90Report() { func makeAHRSGDL90Report() {
msg := make([]byte, 16) msg := make([]byte, 24)
msg[0] = 0x4c msg[0] = 0x4c
msg[1] = 0x45 msg[1] = 0x45
msg[2] = 0x01 msg[2] = 0x01
msg[3] = 0x00 msg[3] = 0x01
pitch := int16(float64(mySituation.Pitch) * float64(10.0)) roll := int16(mySituation.Roll*10)
roll := int16(float64(mySituation.Roll) * float64(10.0)) pitch := int16(mySituation.Pitch*10)
hdg := uint16(float64(mySituation.Gyro_heading) * float64(10.0)) yaw := uint16(mySituation.Gyro_heading*10)
slip_skid := int16(float64(mySituation.SlipSkid) * float64(10.0)) slip_skid := int16(mySituation.SlipSkid*10)
turn_rate := int16(float64(mySituation.RateOfTurn) * float64(10.0)) turn_rate := int16(mySituation.RateOfTurn*10)
g := int16(float64(mySituation.GLoad) * float64(10.0)) g := int16(mySituation.GLoad*10)
airspeed := 0x7FFF // Can add this once we can read airspeed
palt := uint16(mySituation.Pressure_alt+5000)
vs := int16(mySituation.GPSVertVel) //TODO: record BMP rate of climb
//TODO westphae: invalidate each with 0x7FFF when data is invalid
// Roll. // Roll.
msg[4] = byte((roll >> 8) & 0xFF) msg[4] = byte((roll >> 8) & 0xFF)
msg[5] = byte(roll & 0xFF) msg[5] = byte(roll & 0xFF)
@ -1423,14 +1450,14 @@ func makeAHRSGDL90Report() {
msg[7] = byte(pitch & 0xFF) msg[7] = byte(pitch & 0xFF)
// Heading. // Heading.
msg[8] = byte((hdg >> 8) & 0xFF) msg[8] = byte((yaw >> 8) & 0xFF)
msg[9] = byte(hdg & 0xFF) msg[9] = byte(yaw & 0xFF)
// Slip/skid. // Slip/skid.
msg[10] = byte((slip_skid >> 8) & 0xFF) msg[10] = byte((slip_skid >> 8) & 0xFF)
msg[11] = byte(slip_skid & 0xFF) msg[11] = byte(slip_skid & 0xFF)
// Yaw rate. // Turn rate.
msg[12] = byte((turn_rate >> 8) & 0xFF) msg[12] = byte((turn_rate >> 8) & 0xFF)
msg[13] = byte(turn_rate & 0xFF) msg[13] = byte(turn_rate & 0xFF)
@ -1438,33 +1465,78 @@ func makeAHRSGDL90Report() {
msg[14] = byte((g >> 8) & 0xFF) msg[14] = byte((g >> 8) & 0xFF)
msg[15] = byte(g & 0xFF) msg[15] = byte(g & 0xFF)
// Indicated Airspeed
msg[16] = byte((airspeed >> 8) & 0xFF)
msg[17] = byte(airspeed & 0xFF)
// Pressure Altitude
//TODO westphae: this is just for testing; 0x7FFF it until BMP280 is working
msg[18] = byte((palt >> 8) & 0xFF)
msg[19] = byte(palt & 0xFF)
// Vertical Speed
msg[20] = byte((vs >> 8) & 0xFF)
msg[21] = byte(vs & 0xFF)
// Reserved
msg[22] = 0x7F
msg[23] = 0xFF
sendMsg(prepareMessage(msg), NETWORK_AHRS_GDL90, false) sendMsg(prepareMessage(msg), NETWORK_AHRS_GDL90, false)
} }
func attitudeReaderSender() { func attitudeReaderSender() {
timer := time.NewTicker(100 * time.Millisecond) // ~10Hz update. timer := time.NewTicker(100 * time.Millisecond) // ~10Hz update.
for globalStatus.RY835AI_connected && globalSettings.AHRS_Enabled { for globalStatus.RY83XAI_connected && globalSettings.AHRS_Enabled {
<-timer.C <-timer.C
// Read pitch and roll. // Read pitch and roll.
pitch, err_pitch := myMPU.Pitch() pitch, err_pitch := myMPU.Pitch()
if err_pitch != nil { if err_pitch != nil {
log.Printf("readMPU6050(): %s\n", err_pitch.Error()) log.Printf("AHRS MPU Error: %s\n", err_pitch.Error())
globalStatus.RY835AI_connected = false globalStatus.RY83XAI_connected = false
break break
} }
roll, err_roll := myMPU.Roll() roll, err_roll := myMPU.Roll()
if err_roll != nil { if err_roll != nil {
log.Printf("readMPU6050(): %s\n", err_roll.Error()) log.Printf("AHRS MPU Error: %s\n", err_roll.Error())
globalStatus.RY835AI_connected = false globalStatus.RY83XAI_connected = false
break break
} }
heading, err_heading := myMPU.Heading() //FIXME. Experimental. heading, err_heading := myMPU.Heading() //FIXME. Experimental.
if err_heading != nil { if err_heading != nil {
log.Printf("readMPU6050(): %s\n", err_heading.Error()) log.Printf("AHRS MPU Error: %s\n", err_heading.Error())
globalStatus.RY835AI_connected = false globalStatus.RY83XAI_connected = false
break
}
headingMag, err_headingMag := myMPU.MagHeading()
if err_headingMag != nil {
log.Printf("AHRS MPU Error: %s\n", err_headingMag.Error())
globalStatus.RY83XAI_connected = false
break
}
slipSkid, err_slipSkid := myMPU.SlipSkid()
if err_slipSkid != nil {
log.Printf("AHRS MPU Error: %s\n", err_slipSkid.Error())
globalStatus.RY83XAI_connected = false
break
}
turnRate, err_turnRate := myMPU.RateOfTurn()
if err_turnRate != nil {
log.Printf("AHRS MPU Error: %s\n", err_turnRate.Error())
globalStatus.RY83XAI_connected = false
break
}
gLoad, err_gLoad := myMPU.GLoad()
if err_gLoad != nil {
log.Printf("AHRS MPU Error: %s\n", err_gLoad.Error())
globalStatus.RY83XAI_connected = false
break break
} }
@ -1473,6 +1545,10 @@ func attitudeReaderSender() {
mySituation.Pitch = pitch mySituation.Pitch = pitch
mySituation.Roll = roll mySituation.Roll = roll
mySituation.Gyro_heading = heading mySituation.Gyro_heading = heading
mySituation.Mag_heading = headingMag
mySituation.SlipSkid = slipSkid
mySituation.RateOfTurn = turnRate
mySituation.GLoad = gLoad
mySituation.LastAttitudeTime = stratuxClock.Time mySituation.LastAttitudeTime = stratuxClock.Time
// Send, if valid. // Send, if valid.
@ -1483,7 +1559,7 @@ func attitudeReaderSender() {
mySituation.mu_Attitude.Unlock() mySituation.mu_Attitude.Unlock()
} }
globalStatus.RY835AI_connected = false globalStatus.RY83XAI_connected = false
} }
/* /*
@ -1559,27 +1635,29 @@ func isTempPressValid() bool {
func initAHRS() error { func initAHRS() error {
if err := initI2C(); err != nil { // I2C bus. if err := initI2C(); err != nil { // I2C bus.
log.Println("AHRS Error: Couldn't initialize i2c bus")
return err return err
} }
if err := initBMP180(); err != nil { // I2C temperature and pressure altitude. if err := initBMP180(); err != nil { // I2C temperature and pressure altitude.
log.Println("AHRS Error: No BMP180, Closing i2c bus")
i2cbus.Close() i2cbus.Close()
return err return err
} }
// TODO westphae: Initialize MPU9250 here
if err := initMPU(); err != nil { // I2C accel/gyro. if err := initMPU(); err != nil { // I2C accel/gyro.
log.Println("AHRS Error: Couldn't init MPU, closing i2c bus")
i2cbus.Close() i2cbus.Close()
myBMP180.Close() myBMP180.Close()
return err return err
} }
globalStatus.RY835AI_connected = true globalStatus.RY83XAI_connected = true
go attitudeReaderSender() go attitudeReaderSender()
go tempAndPressureReader() //go tempAndPressureReader()
return nil return nil
} }
func pollRY835AI() { func pollRY83XAI() {
readyToInitGPS = true //TO-DO: Implement more robust method (channel control) to kill zombie serial readers readyToInitGPS = true //TODO: Implement more robust method (channel control) to kill zombie serial readers
timer := time.NewTicker(4 * time.Second) timer := time.NewTicker(4 * time.Second)
for { for {
<-timer.C <-timer.C
@ -1590,22 +1668,22 @@ func pollRY835AI() {
go gpsSerialReader() go gpsSerialReader()
} }
} }
// RY835AI I2C enabled, was not connected previously? // RY83XAI I2C enabled, was not connected previously?
if globalSettings.AHRS_Enabled && !globalStatus.RY835AI_connected { if globalSettings.AHRS_Enabled && !globalStatus.RY83XAI_connected {
err := initAHRS() err := initAHRS()
if err != nil { if err != nil {
log.Printf("initAHRS(): %s\ndisabling AHRS sensors.\n", err.Error()) log.Printf("initAHRS(): %s\ndisabling AHRS sensors.\n", err.Error())
globalStatus.RY835AI_connected = false globalStatus.RY83XAI_connected = false
} }
} }
} }
} }
func initRY835AI() { func initRY83XAI() {
mySituation.mu_GPS = &sync.Mutex{} mySituation.mu_GPS = &sync.Mutex{}
mySituation.mu_Attitude = &sync.Mutex{} mySituation.mu_Attitude = &sync.Mutex{}
satelliteMutex = &sync.Mutex{} satelliteMutex = &sync.Mutex{}
Satellites = make(map[string]SatelliteInfo) Satellites = make(map[string]SatelliteInfo)
go pollRY835AI() go pollRY83XAI()
} }

1
mpu/bmp.go 100644
Wyświetl plik

@ -0,0 +1 @@
package mpu

Wyświetl plik

@ -7,6 +7,7 @@ import (
"log" "log"
"math" "math"
"time" "time"
"errors"
) )
//https://www.olimex.com/Products/Modules/Sensors/MOD-MPU6050/resources/RM-MPU-60xxA_rev_4.pdf //https://www.olimex.com/Products/Modules/Sensors/MOD-MPU6050/resources/RM-MPU-60xxA_rev_4.pdf
@ -42,14 +43,19 @@ type MPU6050 struct {
// New returns a handle to a MPU6050 sensor. // New returns a handle to a MPU6050 sensor.
func NewMPU6050() (*MPU6050, error) { func NewMPU6050() (*MPU6050, error) {
n := &MPU6050{poll: pollDelay} n := &MPU6050{poll: pollDelay}
n.startUp() if err := n.startUp(); err != nil {
return nil, err
}
return n, nil return n, nil
} }
func (d *MPU6050) startUp() error { func (d *MPU6050) startUp() error {
mpu_sample_rate := 10 // 10 Hz read rate of hardware IMU mpu_sample_rate := 10 // 10 Hz read rate of hardware IMU
yaw_mix_factor := 0 // must be zero if no magnetometer yaw_mix_factor := 0 // must be zero if no magnetometer
mpu.InitMPU(mpu_sample_rate, yaw_mix_factor) err := mpu.InitMPU(mpu_sample_rate, yaw_mix_factor)
if err != 0 {
return errors.New("MPU6050 Error: couldn't start MPU")
}
d.pitch_history = make([]float64, 0) d.pitch_history = make([]float64, 0)
d.roll_history = make([]float64, 0) d.roll_history = make([]float64, 0)

192
mpu/mpu9250.go 100644
Wyświetl plik

@ -0,0 +1,192 @@
// Package MPU9250 provides a stratux interface to the MPU9250 IMU
package mpu
import (
"errors"
"github.com/westphae/goflying/mpu9250"
"math"
"time"
"log"
)
const (
DECAY = 0.98
GYRORANGE = 250
ACCELRANGE = 4
UPDATEFREQ = 100
CALIBTIME int64 = 5*60*1000000000
)
type MPU9250 struct {
mpu *mpu9250.MPU9250
pitch, roll, heading float64
headingMag float64
slipSkid float64
turnRate float64
gLoad float64
T int64
valid bool
nextCalibrateT int64
quit chan struct{}
}
func NewMPU9250() (*MPU9250, error) {
var (
m MPU9250
mpu *mpu9250.MPU9250
err error
)
mpu, err = mpu9250.NewMPU9250(GYRORANGE, ACCELRANGE, UPDATEFREQ, false)
if err != nil {
log.Println("AHRS Error: couldn't initialize MPU9250")
return nil, err
}
err = mpu.CalibrateGyro(1)
if err != nil {
log.Printf("AHRS: Gyro calibration failed: %s\n", err.Error())
} else {
log.Println("AHRS: Gyro calibration successful")
m.nextCalibrateT = time.Now().UnixNano() + CALIBTIME
}
m.mpu = mpu
m.valid = true
time.Sleep(100 * time.Millisecond)
m.run()
return &m, nil
}
func (m *MPU9250) run() {
time.Sleep(100 * time.Millisecond)
go func() {
m.quit = make(chan struct{})
clock := time.NewTicker(100 * time.Millisecond)
for {
select {
case <-clock.C:
Ts, Gx, Gy, Gz, Ax, Ay, Az, Mx, My, _, gaError, magError := m.mpu.Read()
if gaError == nil {
m.T = Ts
smooth(&m.turnRate, Gz)
smooth(&m.gLoad, math.Sqrt(Ax * Ax + Ay * Ay + Az * Az))
smooth(&m.slipSkid, Ay / Az)
// Quick and dirty calcs just to test - these are no good for pitch >> 0
m.pitch += 0.1 * Gx
m.roll += 0.1 * Gy
m.heading -= 0.1 * Gz
if m.pitch > 90 {
m.pitch = 180-m.pitch
}
if m.pitch < -90 {
m.pitch = -180 - m.pitch
}
if (m.roll > 180) || (m.roll < -180) {
m.roll = -m.roll
}
if m.heading > 360 {
m.heading -= 360
}
if m.heading < 0 {
m.heading += 360
}
}
if magError == nil {
smooth(&m.headingMag, math.Atan2(My, Mx))
}
// Calibrate if past-due
if time.Now().UnixNano() > m.nextCalibrateT {
err := m.mpu.CalibrateGyro(1)
if err != nil {
log.Printf("AHRS: Error calibrating gyro, %s\n", err)
} else {
log.Println("AHRS: Gyro calibration successful")
m.nextCalibrateT = time.Now().UnixNano() + CALIBTIME
}
}
case <-m.quit:
m.mpu.CloseMPU()
return
}
}
}()
}
func smooth(val *float64, new float64) {
*val = DECAY * *val + (1-DECAY)*new
}
func (m *MPU9250) ResetHeading(newHeading float64, gain float64) {
m.heading = newHeading
}
func (m *MPU9250) Pitch() (float64, error) {
if m.valid {
return m.pitch, nil
} else {
return 0, errors.New("MPU error: data not available")
}
}
func (m *MPU9250) Roll() (float64, error) {
if m.valid {
return m.roll, nil
} else {
return 0, errors.New("MPU error: data not available")
}
}
func (m *MPU9250) Heading() (float64, error) {
if m.valid {
return m.heading, nil
} else {
return 0, errors.New("MPU error: data not available")
}
}
func (m *MPU9250) MagHeading() (float64, error) {
if m.valid {
return m.headingMag, nil
} else {
return 0, errors.New("MPU error: data not available")
}
}
func (m *MPU9250) SlipSkid() (float64, error) {
if m.valid {
return m.slipSkid, nil
} else {
return 0, errors.New("MPU error: data not available")
}
}
func (m *MPU9250) RateOfTurn() (float64, error) {
if m.valid {
return m.turnRate, nil
} else {
return 0, errors.New("MPU error: data not available")
}
}
func (m *MPU9250) GLoad() (float64, error) {
if m.valid {
return m.gLoad, nil
} else {
return 0, errors.New("MPU error: data not available")
}
}
func (m *MPU9250) Close() {
if m.quit != nil {
m.quit <- struct{}{}
}
}