From 458e322fcf52e4ee3d1ee7c0aaf497997d418974 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Ruel Date: Sun, 27 May 2018 15:32:34 -0400 Subject: [PATCH] lepton: complete the lepton driver overhaul - New(): Remove cs argument. - Frame uses image14bit.Gray14 instead of image.Gray16. - ReadImg(): Removed in favor to NextFrame(). --- devices/lepton/lepton.go | 74 ++++++++--------------- devices/lepton/lepton_test.go | 108 ++++++++++------------------------ 2 files changed, 54 insertions(+), 128 deletions(-) diff --git a/devices/lepton/lepton.go b/devices/lepton/lepton.go index 99c5c3f..35b6abf 100644 --- a/devices/lepton/lepton.go +++ b/devices/lepton/lepton.go @@ -22,16 +22,15 @@ import ( "errors" "fmt" "image" - "image/color" "sync" "time" "periph.io/x/periph/conn" - "periph.io/x/periph/conn/gpio" "periph.io/x/periph/conn/i2c" "periph.io/x/periph/conn/physic" "periph.io/x/periph/conn/spi" "periph.io/x/periph/devices/lepton/cci" + "periph.io/x/periph/devices/lepton/image14bit" "periph.io/x/periph/devices/lepton/internal" ) @@ -53,51 +52,29 @@ type Metadata struct { } // Frame is a FLIR Lepton frame, containing 14 bits resolution intensity stored -// as image.Gray16. +// as image14bit.Gray14. // // Values centered around 8192 accorging to camera body temperature. Effective // range is 14 bits, so [0, 16383]. // // Each 1 increment is approximatively 0.025K. type Frame struct { - *image.Gray16 + *image14bit.Gray14 Metadata Metadata // Metadata that is sent along the pixels. } // New returns an initialized connection to the FLIR Lepton. // -// The CS line is manually managed by using mode spi.NoCS when calling -// Connect(). In this case pass nil for the cs parameter. Some spidev drivers -// refuse spi.NoCS, they do not implement proper support to not trigger the CS -// line so a manual CS (really, any GPIO pin) must be used instead. -// // Maximum SPI speed is 20Mhz. Minimum usable rate is ~2.2Mhz to sustain a 9hz // framerate at 80x60. // // Maximum I²C speed is 1Mhz. // // MOSI is not used and should be grounded. -// -// Deprecated: Argument cs will be removed in v3. -func New(p spi.Port, i i2c.Bus, cs gpio.PinOut) (*Dev, error) { - // Sadly the Lepton will unconditionally send 27fps, even if the effective - // rate is 9fps. - mode := spi.Mode3 - if cs == nil { - // Query the CS pin before disabling it. - pins, ok := p.(spi.Pins) - if !ok { - return nil, errors.New("lepton: require manual access to the CS pin") - } - cs = pins.CS() - if cs == gpio.INVALID { - return nil, errors.New("lepton: require manual access to a valid CS pin") - } - mode |= spi.NoCS - } +func New(p spi.Port, i i2c.Bus) (*Dev, error) { // TODO(maruel): Switch to 16 bits per word, so that big endian 16 bits word // decoding is done by the SPI driver. - s, err := p.Connect(20000000, mode, 8) + s, err := p.Connect(20000000, spi.Mode3, 8) if err != nil { return nil, err } @@ -114,10 +91,9 @@ func New(p spi.Port, i i2c.Bus, cs gpio.PinOut) (*Dev, error) { d := &Dev{ Dev: c, s: s, - cs: cs, w: w, h: h, - prevImg: image.NewGray16(image.Rect(0, 0, w, h)), + prevImg: image14bit.NewGray14(image.Rect(0, 0, w, h)), frameWidth: frameWidth, frameLines: frameLines, delay: time.Second, @@ -145,10 +121,9 @@ func New(p spi.Port, i i2c.Bus, cs gpio.PinOut) (*Dev, error) { type Dev struct { *cci.Dev s spi.Conn - cs gpio.PinOut w int h int - prevImg *image.Gray16 + prevImg *image14bit.Gray14 frameA, frameB []byte frameWidth int // in bytes frameLines int @@ -157,7 +132,7 @@ type Dev struct { } func (d *Dev) String() string { - return fmt.Sprintf("Lepton(%s/%s/%s)", d.Dev, d.s, d.cs) + return fmt.Sprintf("Lepton(%s/%s)", d.Dev, d.s) } // Halt implements conn.Resource. @@ -188,7 +163,9 @@ func (d *Dev) NextFrame(f *Frame) error { // the camera has a shutter. //go d.RunFFC() } - if !bytes.Equal(d.prevImg.Pix, f.Gray16.Pix) { + // Sadly the Lepton will unconditionally send 27fps, even if the effective + // rate is 9fps. + if !equalUint16(d.prevImg.Pix, f.Gray14.Pix) { break } // It also happen if the image is 100% static without noise. @@ -197,17 +174,6 @@ func (d *Dev) NextFrame(f *Frame) error { return nil } -// ReadImg reads an image. -// -// Deprecated: Use NextFrame() instead. -func (d *Dev) ReadImg() (*Frame, error) { - f := &Frame{Gray16: image.NewGray16(d.prevImg.Bounds())} - if err := d.NextFrame(f); err != nil { - return nil, err - } - return f, nil -} - // Private details. // stream reads continuously from the SPI connection. @@ -218,10 +184,6 @@ func (d *Dev) stream(done <-chan struct{}, c chan<- []byte) error { lines = l } } - if err := d.cs.Out(gpio.Low); err != nil { - return err - } - defer d.cs.Out(gpio.High) for { // TODO(maruel): Use a ring buffer to stop continuously allocating. buf := make([]byte, d.frameWidth*lines) @@ -317,7 +279,7 @@ func (d *Dev) readFrame(f *Frame) error { // Image. for x := 0; x < w; x++ { o := 4 + x*2 - f.SetGray16(x, sync-3, color.Gray16{internal.Big16.Uint16(l[o : o+2])}) + f.SetIntensity14(x, sync-3, image14bit.Intensity14(internal.Big16.Uint16(l[o:o+2]))) } } if sync++; sync == d.frameLines { @@ -484,5 +446,17 @@ func verifyCRC(d []byte) bool { return internal.CRC16(tmp) == internal.Big16.Uint16(d[2:]) } +func equalUint16(a, b []uint16) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} + var _ conn.Resource = &Dev{} var _ fmt.Stringer = &Dev{} diff --git a/devices/lepton/lepton_test.go b/devices/lepton/lepton_test.go index 90e7bb5..7cf4b86 100644 --- a/devices/lepton/lepton_test.go +++ b/devices/lepton/lepton_test.go @@ -7,20 +7,17 @@ package lepton import ( "bytes" "encoding/binary" - "encoding/hex" "errors" "image" - "image/color" "testing" "periph.io/x/periph/conn" "periph.io/x/periph/conn/conntest" - "periph.io/x/periph/conn/gpio" - "periph.io/x/periph/conn/gpio/gpiotest" "periph.io/x/periph/conn/i2c/i2ctest" "periph.io/x/periph/conn/physic" "periph.io/x/periph/conn/spi" "periph.io/x/periph/conn/spi/spitest" + "periph.io/x/periph/devices/lepton/image14bit" "periph.io/x/periph/devices/lepton/internal" ) @@ -35,11 +32,11 @@ func TestNew_cs(t *testing.T) { }...), } s := spitest.Playback{} - d, err := New(&s, &i, &gpiotest.Pin{N: "CS"}) + d, err := New(&s, &i) if err != nil { t.Fatal(err) } - if s := d.String(); s != "Lepton(playback(42)/playback/CS(0))" { + if s := d.String(); s != "Lepton(playback(42)/playback)" { t.Fatal(s) } if err := d.Halt(); err != nil { @@ -55,8 +52,8 @@ func TestNew_cs(t *testing.T) { func TestNew(t *testing.T) { i := i2ctest.Playback{Ops: initSequence()} - s := spitest.Playback{CSPin: &gpiotest.Pin{N: "CS"}} - _, err := New(&s, &i, nil) + s := spitest.Playback{} + _, err := New(&s, &i) if err != nil { t.Fatal(err) } @@ -72,8 +69,8 @@ func TestNew_Init_fail(t *testing.T) { // Strip off last command. ops := initSequence() i := i2ctest.Playback{Ops: ops[:len(ops)-1], DontPanic: true} - s := spitest.Playback{CSPin: &gpiotest.Pin{N: "CS"}} - if _, err := New(&s, &i, nil); err == nil { + s := spitest.Playback{} + if _, err := New(&s, &i); err == nil { t.Fatal("cci.Dev.Init() failed") } if err := i.Close(); err != nil { @@ -95,8 +92,8 @@ func TestNew_GetStatus_fail(t *testing.T) { }, DontPanic: true, } - s := spitest.Playback{CSPin: &gpiotest.Pin{N: "CS"}} - if _, err := New(&s, &i, nil); err == nil { + s := spitest.Playback{} + if _, err := New(&s, &i); err == nil { t.Fatal("cci.Dev.GetStatus() failed") } if err := i.Close(); err != nil { @@ -119,8 +116,8 @@ func TestNew_GetStatus_bad(t *testing.T) { }, DontPanic: true, } - s := spitest.Playback{CSPin: &gpiotest.Pin{N: "CS"}} - if _, err := New(&s, &i, nil); err == nil { + s := spitest.Playback{} + if _, err := New(&s, &i); err == nil { t.Fatal("cci.Dev.GetStatus() failed") } if err := i.Close(); err != nil { @@ -131,26 +128,10 @@ func TestNew_GetStatus_bad(t *testing.T) { } } -func TestNew_fail_invalid(t *testing.T) { - i := i2ctest.Record{} - s := spitest.Record{} - if _, err := New(&s, &i, nil); err == nil { - t.Fatal("spi.Pins.CS() returns INVALID") - } -} - -func TestNew_fail_no_Pins(t *testing.T) { - i := i2ctest.Record{} - s := spiStream{} - if _, err := New(&s, &i, nil); err == nil { - t.Fatal("no CS and no spi.Pins") - } -} - func TestNew_Connect(t *testing.T) { i := i2ctest.Record{} s := spiStream{err: errors.New("injected")} - if _, err := New(&s, &i, &gpiotest.Pin{N: "CS"}); err == nil { + if _, err := New(&s, &i); err == nil { t.Fatal("Connect failed") } } @@ -158,15 +139,15 @@ func TestNew_Connect(t *testing.T) { func TestNew_cci_New_fail(t *testing.T) { i := i2ctest.Playback{DontPanic: true} s := spitest.Record{} - if _, err := New(&s, &i, &gpiotest.Pin{N: "CS"}); err == nil { + if _, err := New(&s, &i); err == nil { t.Fatal("cci.New failed") } } func TestBounds(t *testing.T) { i := i2ctest.Playback{Ops: initSequence()} - s := spitest.Playback{CSPin: &gpiotest.Pin{N: "CS"}} - d, err := New(&s, &i, nil) + s := spitest.Playback{} + d, err := New(&s, &i) if err != nil { t.Fatal(err) } @@ -184,11 +165,11 @@ func TestBounds(t *testing.T) { func TestNextFrame(t *testing.T) { i := i2ctest.Playback{Ops: initSequence()} s := spiStream{data: prepareFrame(t)} - d, err := New(&s, &i, &gpiotest.Pin{N: "CS"}) + d, err := New(&s, &i) if err != nil { t.Fatal(err) } - f := Frame{Gray16: image.NewGray16(d.Bounds())} + f := Frame{Gray14: image14bit.NewGray14(d.Bounds())} if err := d.NextFrame(&f); err != nil { t.Fatal(err) } @@ -197,7 +178,7 @@ func TestNextFrame(t *testing.T) { } // Compare the frame with the reference image. It should match. ref := referenceFrame() - if !bytes.Equal(ref.Pix, f.Pix) { + if !equalUint16(ref.Pix, f.Pix) { offset := 0 for { if ref.Pix[offset] != f.Pix[offset] { @@ -205,7 +186,8 @@ func TestNextFrame(t *testing.T) { } offset++ } - t.Fatalf("different pixels at offset %d:\n%s\n%s", offset, hex.EncodeToString(ref.Pix[offset:]), hex.EncodeToString(f.Pix[offset:])) + t.Fatalf("different pixels at offset %d", offset) + //t.Fatalf("different pixels at offset %d:\n%s\n%s", offset, hex.EncodeToString(ref.Pix[offset:]), hex.EncodeToString(f.Pix[offset:])) } if err := i.Close(); err != nil { t.Fatal(err) @@ -215,11 +197,11 @@ func TestNextFrame(t *testing.T) { func TestNextFrame_invalid_bounds(t *testing.T) { i := i2ctest.Playback{Ops: initSequence()} s := spiStream{data: prepareFrame(t)} - d, err := New(&s, &i, &gpiotest.Pin{N: "CS"}) + d, err := New(&s, &i) if err != nil { t.Fatal(err) } - if err := d.NextFrame(&Frame{Gray16: image.NewGray16(image.Rect(0, 0, 1, 1))}); err == nil { + if err := d.NextFrame(&Frame{Gray14: image14bit.NewGray14(image.Rect(0, 0, 1, 1))}); err == nil { t.Fatal("invalid bounds") } if err := i.Close(); err != nil { @@ -230,11 +212,11 @@ func TestNextFrame_invalid_bounds(t *testing.T) { func TestNextFrame_fail_Tx(t *testing.T) { i := i2ctest.Playback{Ops: initSequence()} s := spitest.Playback{Playback: conntest.Playback{DontPanic: true}} - d, err := New(&s, &i, &gpiotest.Pin{N: "CS"}) + d, err := New(&s, &i) if err != nil { t.Fatal(err) } - if err := d.NextFrame(&Frame{Gray16: image.NewGray16(d.Bounds())}); err == nil { + if err := d.NextFrame(&Frame{Gray14: image14bit.NewGray14(d.Bounds())}); err == nil { t.Fatal("spi port Tx failed") } if err := i.Close(); err != nil { @@ -245,11 +227,11 @@ func TestNextFrame_fail_Tx(t *testing.T) { func TestNextFrame_fail_Out(t *testing.T) { i := i2ctest.Playback{Ops: initSequence()} s := spitest.Playback{Playback: conntest.Playback{DontPanic: true}} - d, err := New(&s, &i, &failPin{}) + d, err := New(&s, &i) if err != nil { t.Fatal(err) } - if err := d.NextFrame(&Frame{Gray16: image.NewGray16(d.Bounds())}); err == nil { + if err := d.NextFrame(&Frame{Gray14: image14bit.NewGray14(d.Bounds())}); err == nil { t.Fatal("spi port Tx failed") } if err := i.Close(); err != nil { @@ -257,28 +239,6 @@ func TestNextFrame_fail_Out(t *testing.T) { } } -func TestReadImg(t *testing.T) { - i := i2ctest.Playback{Ops: initSequence()} - s := spiStream{data: prepareFrame(t)} - d, err := New(&s, &i, &gpiotest.Pin{N: "CS"}) - if err != nil { - t.Fatal(err) - } - f, err := d.ReadImg() - if err != nil { - t.Fatal(err) - } - if f.Metadata.TempHousing != 2*physic.Celsius+physic.ZeroCelsius { - t.Fatal(f.Metadata.TempHousing) - } - if err := i.Close(); err != nil { - t.Fatal(err) - } - if _, err = d.ReadImg(); err == nil { - t.Fatal(err) - } -} - func TestParseTelemetry_fail(t *testing.T) { l := telemetryLine(t) m := Metadata{} @@ -384,12 +344,12 @@ func appendHeader(t *testing.T, i int, d []byte) []byte { return out } -func referenceFrame() *image.Gray16 { +func referenceFrame() *image14bit.Gray14 { r := image.Rect(0, 0, 80, 60) - img := image.NewGray16(r) + img := image14bit.NewGray14(r) for y := r.Min.Y; y < r.Max.Y; y++ { for x := r.Min.X; x < r.Max.X; x++ { - img.SetGray16(x, y, color.Gray16{uint16(8192 - 80 + (x * 2))}) + img.SetIntensity14(x, y, image14bit.Intensity14(uint16(8192-80+(x*2)))) } } return img @@ -405,7 +365,7 @@ func prepareFrame(t *testing.T) []byte { r := img.Bounds() for y := 0; y < r.Max.Y; y++ { for x := 0; x < r.Max.X; x++ { - internal.Big16.PutUint16(tmp[x*2:], img.Gray16At(x, y).Y) + internal.Big16.PutUint16(tmp[x*2:], uint16(img.Intensity14At(x, y))) } buf.Write(appendHeader(t, y+3, tmp)) } @@ -468,11 +428,3 @@ func (s *spiStream) Duplex() conn.Duplex { func (s *spiStream) MaxTxSize() int { return 7 * 164 } - -type failPin struct { - gpiotest.Pin -} - -func (f *failPin) Out(l gpio.Level) error { - return errors.New("injected") -}