From 92534b2170e6676a493b92a7d3cefb4e3d8173a5 Mon Sep 17 00:00:00 2001 From: Thomas Kohler Date: Thu, 16 Jan 2025 19:32:40 +0100 Subject: [PATCH] nanopct6: introduce adaptor for FriendlyELEC NanoPC-T6 --- MIGRATION.md | 17 ++ README.md | 3 +- examples/edison_grove_blink.go | 39 --- examples/edison_grove_button.go | 40 ---- examples/edison_grove_led.go | 39 --- examples/edison_grove_touch.go | 40 ---- examples/nanopct6_direct_pin.go | 84 +++++++ examples/nanopct6_ds18b20.go | 80 +++++++ examples/nanopct6_servo.go | 93 ++++++++ examples/nanopct6_thermalzone.go | 50 ++++ examples/nanopct6_yl40.go | 80 +++++++ examples/nanopi_button.go | 2 +- examples/nanopi_direct_pin.go | 2 +- examples/nanopi_direct_pin_event.go | 2 +- examples/nanopi_led_brightness.go | 2 +- examples/nanopi_pca9533.go | 2 +- platforms/friendlyelec/nanopct6/LICENSE | 13 + platforms/friendlyelec/nanopct6/README.md | 67 ++++++ platforms/friendlyelec/nanopct6/adaptor.go | 161 +++++++++++++ .../friendlyelec/nanopct6/adaptor_test.go | 224 ++++++++++++++++++ platforms/friendlyelec/nanopct6/doc.go | 7 + platforms/friendlyelec/nanopct6/pin_map.go | 85 +++++++ platforms/{ => friendlyelec}/nanopi/LICENSE | 0 platforms/{ => friendlyelec}/nanopi/README.md | 4 +- platforms/friendlyelec/nanopi/doc.go | 7 + .../nanopi/nanopi_adaptor.go | 2 +- .../nanopi/nanopi_adaptor_test.go | 110 +-------- .../nanopi/nanopineo_pin_map.go | 0 platforms/nanopi/doc.go | 7 - platforms/tinkerboard/adaptor_test.go | 96 +------- platforms/upboard/up2/adaptor_test.go | 7 +- 31 files changed, 990 insertions(+), 375 deletions(-) delete mode 100644 examples/edison_grove_blink.go delete mode 100644 examples/edison_grove_button.go delete mode 100644 examples/edison_grove_led.go delete mode 100644 examples/edison_grove_touch.go create mode 100644 examples/nanopct6_direct_pin.go create mode 100644 examples/nanopct6_ds18b20.go create mode 100644 examples/nanopct6_servo.go create mode 100644 examples/nanopct6_thermalzone.go create mode 100644 examples/nanopct6_yl40.go create mode 100644 platforms/friendlyelec/nanopct6/LICENSE create mode 100644 platforms/friendlyelec/nanopct6/README.md create mode 100644 platforms/friendlyelec/nanopct6/adaptor.go create mode 100644 platforms/friendlyelec/nanopct6/adaptor_test.go create mode 100644 platforms/friendlyelec/nanopct6/doc.go create mode 100644 platforms/friendlyelec/nanopct6/pin_map.go rename platforms/{ => friendlyelec}/nanopi/LICENSE (100%) rename platforms/{ => friendlyelec}/nanopi/README.md (92%) create mode 100644 platforms/friendlyelec/nanopi/doc.go rename platforms/{ => friendlyelec}/nanopi/nanopi_adaptor.go (98%) rename platforms/{ => friendlyelec}/nanopi/nanopi_adaptor_test.go (66%) rename platforms/{ => friendlyelec}/nanopi/nanopineo_pin_map.go (100%) delete mode 100644 platforms/nanopi/doc.go diff --git a/MIGRATION.md b/MIGRATION.md index 903d76126..88edea9fb 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -9,6 +9,23 @@ are listed here and a migration strategy is provided. ## Switch from version 2.4.0 (applications using the gpiod options affected) +### NanoPi NEO adaptor was moved to friendlyelec folder + +With introduce of FriendlyELEC NanoPC-T6 a second adaptor from FriendlyELEC (formerly friendlarm) now exists. Please +search and replace to change the import path as follows. + +```go +// old +... + "gobot.io/x/gobot/v2/platforms/nanopi" +... + +// new +... + "gobot.io/x/gobot/v2/platforms/friendlyelec/nanopi" +... +``` + ### The term gpiod was renamed to cdev Using the term "cdev" (short for character device Kernel ABI for GPIO access) is more suitable than using "gpiod" (the diff --git a/README.md b/README.md index 40846a9cf..ba94d5cb0 100644 --- a/README.md +++ b/README.md @@ -252,6 +252,8 @@ platforms are currently supported: - [DJI Tello](https://www.ryzerobotics.com/tello) <=> [Package](https://github.com/hybridgroup/gobot/blob/release/platforms/dji/tello) - [DragonBoard](https://developer.qualcomm.com/hardware/dragonboard-410c) <=> [Package](https://github.com/hybridgroup/gobot/blob/release/platforms/dragonboard) - [ESP8266](http://esp8266.net/) <=> [Package](https://github.com/hybridgroup/gobot/blob/release/platforms/firmata) +- [FriendlyELEC NanoPi NEO](https://wiki.friendlyelec.com/wiki/index.php/NanoPi_NEO) <=> [Package](https://github.com/hybridgroup/gobot/blob/release/platforms/friendlyelec/nanopi) +- [FriendlyELEC NanoPC-T6](https://wiki.friendlyelec.com/wiki/index.php/NanoPC-T6) <=> [Package](https://github.com/hybridgroup/gobot/blob/release/platforms/friendlyelec/nanopct6) - [GoPiGo 3](https://www.dexterindustries.com/gopigo3/) <=> [Package](https://github.com/hybridgroup/gobot/blob/release/platforms/dexter/gopigo3) - [Intel Curie](https://www.intel.com/content/www/us/en/products/boards-kits/curie.html) <=> [Package](https://github.com/hybridgroup/gobot/blob/release/platforms/intel-iot/curie) - [Intel Edison](http://www.intel.com/content/www/us/en/do-it-yourself/edison.html) <=> [Package](https://github.com/hybridgroup/gobot/blob/release/platforms/intel-iot/edison) @@ -264,7 +266,6 @@ platforms are currently supported: - [MegaPi](http://www.makeblock.com/megapi) <=> [Package](https://github.com/hybridgroup/gobot/blob/release/platforms/megapi) - [Microbit](http://microbit.org/) <=> [Package](https://github.com/hybridgroup/gobot/blob/release/platforms/microbit) - [MQTT](http://mqtt.org/) <=> [Package](https://github.com/hybridgroup/gobot/blob/release/platforms/mqtt) -- [NanoPi NEO](https://wiki.friendlyelec.com/wiki/index.php/NanoPi_NEO) <=> [Package](https://github.com/hybridgroup/gobot/blob/release/platforms/nanopi) - [NATS](http://nats.io/) <=> [Package](https://github.com/hybridgroup/gobot/blob/release/platforms/nats) - [Neurosky](http://neurosky.com/products-markets/eeg-biosensors/hardware/) <=> [Package](https://github.com/hybridgroup/gobot/blob/release/platforms/neurosky) - [OpenCV](http://opencv.org/) <=> [Package](https://github.com/hybridgroup/gobot/blob/release/platforms/opencv) diff --git a/examples/edison_grove_blink.go b/examples/edison_grove_blink.go deleted file mode 100644 index 54617b10a..000000000 --- a/examples/edison_grove_blink.go +++ /dev/null @@ -1,39 +0,0 @@ -//go:build example -// +build example - -// -// Do not build by default. - -package main - -import ( - "fmt" - "time" - - "gobot.io/x/gobot/v2" - "gobot.io/x/gobot/v2/drivers/gpio" - "gobot.io/x/gobot/v2/platforms/intel-iot/edison" -) - -func main() { - e := edison.NewAdaptor() - led := gpio.NewLedDriver(e, "13") - - work := func() { - gobot.Every(1*time.Second, func() { - if err := led.Toggle(); err != nil { - fmt.Println(err) - } - }) - } - - robot := gobot.NewRobot("blinkBot", - []gobot.Connection{e}, - []gobot.Device{led}, - work, - ) - - if err := robot.Start(); err != nil { - panic(err) - } -} diff --git a/examples/edison_grove_button.go b/examples/edison_grove_button.go deleted file mode 100644 index 7a86dc963..000000000 --- a/examples/edison_grove_button.go +++ /dev/null @@ -1,40 +0,0 @@ -//go:build example -// +build example - -// -// Do not build by default. - -package main - -import ( - "fmt" - - "gobot.io/x/gobot/v2" - "gobot.io/x/gobot/v2/drivers/gpio" - "gobot.io/x/gobot/v2/platforms/intel-iot/edison" -) - -func main() { - e := edison.NewAdaptor() - button := gpio.NewButtonDriver(e, "2") - - work := func() { - _ = button.On(gpio.ButtonPush, func(data interface{}) { - fmt.Println("On!") - }) - - _ = button.On(gpio.ButtonRelease, func(data interface{}) { - fmt.Println("Off!") - }) - } - - robot := gobot.NewRobot("bot", - []gobot.Connection{e}, - []gobot.Device{button}, - work, - ) - - if err := robot.Start(); err != nil { - panic(err) - } -} diff --git a/examples/edison_grove_led.go b/examples/edison_grove_led.go deleted file mode 100644 index 2c11fadbf..000000000 --- a/examples/edison_grove_led.go +++ /dev/null @@ -1,39 +0,0 @@ -//go:build example -// +build example - -// -// Do not build by default. - -package main - -import ( - "fmt" - "time" - - "gobot.io/x/gobot/v2" - "gobot.io/x/gobot/v2/drivers/gpio" - "gobot.io/x/gobot/v2/platforms/intel-iot/edison" -) - -func main() { - e := edison.NewAdaptor() - led := gpio.NewLedDriver(e, "4") - - work := func() { - gobot.Every(1*time.Second, func() { - if err := led.Toggle(); err != nil { - fmt.Println(err) - } - }) - } - - robot := gobot.NewRobot("blinkBot", - []gobot.Connection{e}, - []gobot.Device{led}, - work, - ) - - if err := robot.Start(); err != nil { - panic(err) - } -} diff --git a/examples/edison_grove_touch.go b/examples/edison_grove_touch.go deleted file mode 100644 index c8b7f7632..000000000 --- a/examples/edison_grove_touch.go +++ /dev/null @@ -1,40 +0,0 @@ -//go:build example -// +build example - -// -// Do not build by default. - -package main - -import ( - "fmt" - - "gobot.io/x/gobot/v2" - "gobot.io/x/gobot/v2/drivers/gpio" - "gobot.io/x/gobot/v2/platforms/intel-iot/edison" -) - -func main() { - e := edison.NewAdaptor() - touch := gpio.NewButtonDriver(e, "2") - - work := func() { - _ = touch.On(gpio.ButtonPush, func(data interface{}) { - fmt.Println("On!") - }) - - _ = touch.On(gpio.ButtonRelease, func(data interface{}) { - fmt.Println("Off!") - }) - } - - robot := gobot.NewRobot("blinkBot", - []gobot.Connection{e}, - []gobot.Device{touch}, - work, - ) - - if err := robot.Start(); err != nil { - panic(err) - } -} diff --git a/examples/nanopct6_direct_pin.go b/examples/nanopct6_direct_pin.go new file mode 100644 index 000000000..abf398572 --- /dev/null +++ b/examples/nanopct6_direct_pin.go @@ -0,0 +1,84 @@ +//go:build example +// +build example + +// +// Do not build by default. + +package main + +import ( + "fmt" + "time" + + "gobot.io/x/gobot/v2" + "gobot.io/x/gobot/v2/drivers/gpio" + "gobot.io/x/gobot/v2/platforms/adaptors" + "gobot.io/x/gobot/v2/platforms/friendlyelec/nanopct6" +) + +// Wiring +// PWR : 1, 17 (+3.3V, VCC), 2, 4 (+5V), 6, 9, 14, 20, 25, 30, 34, 39 (GND) +// GPIO : header pin 36 is input, pin 37 used as normal output, pin 38 used as inverted output +// Button: the input pin is wired with a button to GND, the internal pull up resistor is used +// LED's: the output pins are wired to the cathode of the LED, the anode is wired with a resistor (70-130Ohm for 20mA) +// to VCC +// Expected behavior: always one LED is on, the other in opposite state, if button is pressed for >2 seconds the state +// changes +func main() { + const ( + inPinNum = "36" + outPinNum = "37" + outPinInvertedNum = "38" + debounceTime = 2 * time.Second + ) + // note: WithGpiosOpenDrain() is optional, if using WithGpiosOpenSource() the LED's will not light up + board := nanopct6.NewAdaptor(adaptors.WithGpiosActiveLow(outPinInvertedNum), + adaptors.WithGpiosOpenDrain(outPinNum, outPinInvertedNum), + adaptors.WithGpiosPullUp(inPinNum), + adaptors.WithGpioDebounce(inPinNum, debounceTime)) + + inPin := gpio.NewDirectPinDriver(board, inPinNum) + outPin := gpio.NewDirectPinDriver(board, outPinNum) + outPinInverted := gpio.NewDirectPinDriver(board, outPinInvertedNum) + + work := func() { + level := byte(1) + + gobot.Every(500*time.Millisecond, func() { + read, err := inPin.DigitalRead() + fmt.Printf("pin %s state is %d\n", inPinNum, read) + if err != nil { + fmt.Println(err) + if level == 1 { + level = 0 + } else { + level = 1 + } + } else { + level = byte(read) + } + + err = outPin.DigitalWrite(level) + fmt.Printf("pin %s is now %d\n", outPinNum, level) + if err != nil { + fmt.Println(err) + } + + err = outPinInverted.DigitalWrite(level) + fmt.Printf("pin %s is now not %d\n", outPinInvertedNum, level) + if err != nil { + fmt.Println(err) + } + }) + } + + robot := gobot.NewRobot("pinBot", + []gobot.Connection{board}, + []gobot.Device{inPin, outPin, outPinInverted}, + work, + ) + + if err := robot.Start(); err != nil { + panic(err) + } +} diff --git a/examples/nanopct6_ds18b20.go b/examples/nanopct6_ds18b20.go new file mode 100644 index 000000000..0c76d51a4 --- /dev/null +++ b/examples/nanopct6_ds18b20.go @@ -0,0 +1,80 @@ +//go:build example +// +build example + +// +// Do not build by default. + +package main + +import ( + "fmt" + "log" + "time" + + "gobot.io/x/gobot/v2" + "gobot.io/x/gobot/v2/drivers/onewire" + "gobot.io/x/gobot/v2/platforms/friendlyelec/nanopct6" +) + +// Preparation: see /gobot/system/ONEWIRE.md +// +// Wiring: +// PWR : 1, 17 (+3.3V, VCC), 6, 9, 14, 20, 25, 30, 34, 39 (GND) +// 1-wire : 16 (DQ) - resistor to VCC, ~1.5kOhm ... 5kOhm +// DS18B20: 1 (GND), 2 (DQ), 3 (VDD, +3 ... 5.5V) for local power mode +func main() { + adaptor := nanopct6.NewAdaptor() + // resolution change not supported by all devices + temp0 := onewire.NewDS18B20Driver(adaptor, 0xde5e710a6461, onewire.WithResolution(12)) + temp1 := onewire.NewDS18B20Driver(adaptor, 0x1e40710a6461, onewire.WithFahrenheit(), onewire.WithConversionTime(500)) + + work := func() { + time0, err := temp0.ConversionTime() + if err != nil { + log.Printf("Err CT0: %v\n", err) + } + res0, err := temp0.Resolution() + if err != nil { + log.Printf("Err R0: %v\n", err) + } + log.Printf("Conversion time @%d bit for Temp 0: %d ms\n", res0, time0) + + time1, err := temp1.ConversionTime() + if err != nil { + log.Printf("Err CT1: %v\n", err) + } + res1, err := temp1.Resolution() + if err != nil { + log.Printf("Err R1: %v\n", err) + } + log.Printf("Conversion time @%d bit for Temp 0: %d ms\n", res1, time1) + + gobot.Every(10*(time.Duration(time0))*time.Millisecond, func() { + t0, err := temp0.Temperature() + if err != nil { + log.Printf("Err Temp 0: %v\n", err) + } + + fmt.Printf("Temp 0: %2.1f °C\n", t0) + }) + + gobot.Every(10*(time.Duration(time1))*time.Millisecond, func() { + t1, err := temp1.Temperature() + if err != nil { + log.Printf("Err Temp 1: %v\n", err) + } + + fmt.Printf("Temp 1: %2.3f °F\n", t1) + }) + } + + robot := gobot.NewRobot("onewireBot", + []gobot.Connection{adaptor}, + []gobot.Device{temp0, temp1}, + work, + ) + + if err := robot.Start(); err != nil { + panic(err) + } +} diff --git a/examples/nanopct6_servo.go b/examples/nanopct6_servo.go new file mode 100644 index 000000000..37b5e499e --- /dev/null +++ b/examples/nanopct6_servo.go @@ -0,0 +1,93 @@ +//go:build example +// +build example + +// Do not build by default. + +package main + +import ( + "fmt" + "log" + "time" + + "gobot.io/x/gobot/v2" + "gobot.io/x/gobot/v2/drivers/gpio" + "gobot.io/x/gobot/v2/platforms/adaptors" + "gobot.io/x/gobot/v2/platforms/friendlyelec/nanopct6" +) + +// Wiring +// PWR: 1, 17 (+3.3V, VCC), 2, 4 (+5V), 6, 9, 14, 20, 25, 30, 34, 39 (GND) +// PWM: header pin 11 (pwm14-m0), 13 (pwm15-m0), 29 (pwm12-m0), 31 (pwm13-m0), 35 (pwm10-m0) +// Servo SG90: red (+5V), brown (GND), orange (PWM) +func main() { + const ( + pwmPin = "35" + wait = 3 * time.Second + + fiftyHzNanos = 20 * 1000 * 1000 // 50Hz = 0.02 sec = 20 ms + ) + // usually a frequency of 50Hz is used for servos, most servos have 0.5 ms..2.5 ms for 0-180°, + // however the mapping can be changed with options: + adaptor := nanopct6.NewAdaptor( + adaptors.WithPWMDefaultPeriodForPin(pwmPin, fiftyHzNanos), + adaptors.WithPWMServoDutyCycleRangeForPin(pwmPin, 500*time.Microsecond, 2500*time.Microsecond), + adaptors.WithPWMServoAngleRangeForPin(pwmPin, 0, 180), + ) + servo := gpio.NewServoDriver(adaptor, pwmPin) + + work := func() { + fmt.Printf("first move to minimal position for %s...\n", wait) + if err := servo.ToMin(); err != nil { + log.Println(err) + } + + time.Sleep(wait) + + fmt.Printf("second move to center position for %s...\n", wait) + if err := servo.ToCenter(); err != nil { + log.Println(err) + } + + time.Sleep(wait) + + fmt.Printf("third move to maximal position for %s...\n", wait) + if err := servo.ToMax(); err != nil { + log.Println(err) + } + + time.Sleep(wait) + + fmt.Println("finally move 0-180° (or what your servo do for the new mapping) and back forever...") + angle := 0 + fadeAmount := 45 + + gobot.Every(time.Second, func() { + if err := servo.Move(byte(angle)); err != nil { + log.Println(err) + } + angle = angle + fadeAmount + if angle < 0 || angle > 180 { + if angle < 0 { + angle = 0 + } + if angle > 180 { + angle = 180 + } + // change direction and recalculate + fadeAmount = -fadeAmount + angle = angle + fadeAmount + } + }) + } + + robot := gobot.NewRobot("motorBot", + []gobot.Connection{adaptor}, + []gobot.Device{servo}, + work, + ) + + if err := robot.Start(); err != nil { + panic(err) + } +} diff --git a/examples/nanopct6_thermalzone.go b/examples/nanopct6_thermalzone.go new file mode 100644 index 000000000..337f980c6 --- /dev/null +++ b/examples/nanopct6_thermalzone.go @@ -0,0 +1,50 @@ +//go:build example +// +build example + +// +// Do not build by default. + +package main + +import ( + "fmt" + "log" + "time" + + "gobot.io/x/gobot/v2" + "gobot.io/x/gobot/v2/drivers/aio" + "gobot.io/x/gobot/v2/platforms/friendlyelec/nanopct6" +) + +// Wiring: no wiring needed +func main() { + adaptor := nanopct6.NewAdaptor() + therm0 := aio.NewThermalZoneDriver(adaptor, "soc_thermal") + therm1 := aio.NewThermalZoneDriver(adaptor, "npu_thermal", aio.WithFahrenheit()) + + work := func() { + gobot.Every(500*time.Millisecond, func() { + t0, err := therm0.Read() + if err != nil { + log.Println(err) + } + + t1, err := therm1.Read() + if err != nil { + log.Println(err) + } + + fmt.Printf("SOC: %2.3f °C, NPU: %2.3f °F\n", t0, t1) + }) + } + + robot := gobot.NewRobot("thermalBot", + []gobot.Connection{adaptor}, + []gobot.Device{therm0, therm1}, + work, + ) + + if err := robot.Start(); err != nil { + panic(err) + } +} diff --git a/examples/nanopct6_yl40.go b/examples/nanopct6_yl40.go new file mode 100644 index 000000000..2695eb145 --- /dev/null +++ b/examples/nanopct6_yl40.go @@ -0,0 +1,80 @@ +//go:build example +// +build example + +// +// Do not build by default. + +package main + +import ( + "fmt" + "log" + "time" + + "gobot.io/x/gobot/v2" + "gobot.io/x/gobot/v2/drivers/i2c" + "gobot.io/x/gobot/v2/platforms/friendlyelec/nanopct6" +) + +func main() { + // Wiring + // PWR : 1, 17 (+3.3V, VCC), 6, 9, 14, 20, 25, 30, 34, 39 (GND) + // I2C8: 3 (SDA), 5 (SCL) + // YL-40 module: wire AOUT --> AIN2 for this example, set all jumpers for temp, LDR and variable resistor + // + // Note: temperature measurement is often buggy, because sensor is not properly grounded + // fix it by soldering a small bridge to the adjacent ground pin of brightness sensor + board := nanopct6.NewAdaptor() + yl := i2c.NewYL40Driver(board, i2c.WithBus(8)) + + work := func() { + // the LED light is visible above ~1.7V + writeVal, _ := yl.AOUT() + + gobot.Every(1000*time.Millisecond, func() { + if err := yl.Write(writeVal); err != nil { + fmt.Println(err) + } else { + log.Printf(" %.1f V written", writeVal) + writeVal = writeVal + 0.1 + if writeVal > 3.3 { + writeVal = 0 + } + } + + if brightness, err := yl.ReadBrightness(); err != nil { + fmt.Println(err) + } else { + log.Printf("Brightness: %.0f [0..1000]", brightness) + } + + if temperature, err := yl.ReadTemperature(); err != nil { + fmt.Println(err) + } else { + log.Printf("Temperature: %.1f °C", temperature) + } + + if ain2, err := yl.ReadAIN2(); err != nil { + fmt.Println(err) + } else { + log.Printf("Read back AOUT: %.1f [0..3.3]", ain2) + } + + if potiState, err := yl.ReadPotentiometer(); err != nil { + fmt.Println(err) + } else { + log.Printf("Resistor: %.0f %% [-100..+100]", potiState) + } + }) + } + + robot := gobot.NewRobot("yl40Bot", + []gobot.Connection{board}, + []gobot.Device{yl}, + work, + ) + + if err := robot.Start(); err != nil { + panic(err) + } +} diff --git a/examples/nanopi_button.go b/examples/nanopi_button.go index 7ea2d6da5..11302e62d 100644 --- a/examples/nanopi_button.go +++ b/examples/nanopi_button.go @@ -12,7 +12,7 @@ import ( "gobot.io/x/gobot/v2" "gobot.io/x/gobot/v2/drivers/gpio" "gobot.io/x/gobot/v2/platforms/adaptors" - "gobot.io/x/gobot/v2/platforms/nanopi" + "gobot.io/x/gobot/v2/platforms/friendlyelec/nanopi" ) // PWR NanoPi: 1, 17 (+3.3V, VCC); 2, 4 (+5V, VDD); 6, 9, 14, 20 (GND) diff --git a/examples/nanopi_direct_pin.go b/examples/nanopi_direct_pin.go index ce7da612c..565735854 100644 --- a/examples/nanopi_direct_pin.go +++ b/examples/nanopi_direct_pin.go @@ -13,7 +13,7 @@ import ( "gobot.io/x/gobot/v2" "gobot.io/x/gobot/v2/drivers/gpio" "gobot.io/x/gobot/v2/platforms/adaptors" - "gobot.io/x/gobot/v2/platforms/nanopi" + "gobot.io/x/gobot/v2/platforms/friendlyelec/nanopi" ) // Wiring diff --git a/examples/nanopi_direct_pin_event.go b/examples/nanopi_direct_pin_event.go index 8a898a4b2..c2b61fdbd 100644 --- a/examples/nanopi_direct_pin_event.go +++ b/examples/nanopi_direct_pin_event.go @@ -11,7 +11,7 @@ import ( "time" "gobot.io/x/gobot/v2" - "gobot.io/x/gobot/v2/platforms/nanopi" + "gobot.io/x/gobot/v2/platforms/friendlyelec/nanopi" "gobot.io/x/gobot/v2/system" ) diff --git a/examples/nanopi_led_brightness.go b/examples/nanopi_led_brightness.go index d9ad9ac77..e64042ba7 100644 --- a/examples/nanopi_led_brightness.go +++ b/examples/nanopi_led_brightness.go @@ -12,7 +12,7 @@ import ( "gobot.io/x/gobot/v2" "gobot.io/x/gobot/v2/drivers/gpio" - "gobot.io/x/gobot/v2/platforms/nanopi" + "gobot.io/x/gobot/v2/platforms/friendlyelec/nanopi" ) // Wiring diff --git a/examples/nanopi_pca9533.go b/examples/nanopi_pca9533.go index 844f75cea..46edcb638 100644 --- a/examples/nanopi_pca9533.go +++ b/examples/nanopi_pca9533.go @@ -12,7 +12,7 @@ import ( "gobot.io/x/gobot/v2" "gobot.io/x/gobot/v2/drivers/i2c" - "gobot.io/x/gobot/v2/platforms/nanopi" + "gobot.io/x/gobot/v2/platforms/friendlyelec/nanopi" ) // Wiring diff --git a/platforms/friendlyelec/nanopct6/LICENSE b/platforms/friendlyelec/nanopct6/LICENSE new file mode 100644 index 000000000..f4448d985 --- /dev/null +++ b/platforms/friendlyelec/nanopct6/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2025 The Hybrid Group + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/platforms/friendlyelec/nanopct6/README.md b/platforms/friendlyelec/nanopct6/README.md new file mode 100644 index 000000000..3db8f6f2f --- /dev/null +++ b/platforms/friendlyelec/nanopct6/README.md @@ -0,0 +1,67 @@ +# FriendlyELEC NanoPC-T6 + +The FriendlyELEC NanoPC-T6 is a single board SoC computer based on the Rockchip RK3588 arm64 processor. It has built-in +GPIO, I2C, PWM, SPI, 1-Wire, MIPI CSI and MIPI DSI interfaces. + +For more info about the FriendlyELEC NanoPC-T6, go to [https://wiki.friendlyelec.com/wiki/index.php/NanoPC-T6](https://wiki.friendlyelec.com/wiki/index.php/NanoPC-T6). + +## How to Install + +Please refer to the main [README.md](https://github.com/hybridgroup/gobot/blob/release/README.md) + +Tested OS: + +* [armbian](https://www.armbian.com/nanopct6/) with "Armbian 24.11.1 Bookworm Minimal / IOT" + +## Configuration steps for the OS + +### System access and configuration basics + +Please follow the instructions of the OS provider. A ssh access is used in this guide. + +```sh +ssh @192.168.1.xxx +``` + +### Enabling hardware drivers + +Not all drivers are enabled by default. You can have a look at the configuration file, to find out what is enabled at +your system: + +```sh +cat /boot/armbianEnv.txt +``` + +```sh +sudo apt install armbian-config +sudo armbian-config +``` + +## How to Use + +The pin numbering used by your Gobot program should match the way your board is labeled right on the board itself. + +```go +r := nanopct6.NewAdaptor() +led := gpio.NewLedDriver(r, "7") +``` + +## How to Connect + +### Compiling + +Compile your Gobot program on your workstation like this: + +```sh +GOARCH=arm64 GOOS=linux go build -o output/ examples/nanopct6_blink.go +``` + +Once you have compiled your code, you can upload your program and execute it on the board from your workstation +using the `scp` and `ssh` commands like this: + +```sh +scp nanopct6_blink @192.168.1.xxx:~ +ssh -t @192.168.1.xxx "./nanopct6_blink" +``` + +## Troubleshooting diff --git a/platforms/friendlyelec/nanopct6/adaptor.go b/platforms/friendlyelec/nanopct6/adaptor.go new file mode 100644 index 000000000..6248e3472 --- /dev/null +++ b/platforms/friendlyelec/nanopct6/adaptor.go @@ -0,0 +1,161 @@ +package nanopct6 + +import ( + "fmt" + "sync" + + multierror "github.com/hashicorp/go-multierror" + + "gobot.io/x/gobot/v2" + "gobot.io/x/gobot/v2/platforms/adaptors" + "gobot.io/x/gobot/v2/system" +) + +const ( + defaultI2cBusNumber = 8 // on 40 pin header + + defaultSpiBusNumber = 0 + defaultSpiChipNumber = 0 + defaultSpiMode = 0 + defaultSpiBitsNumber = 8 + defaultSpiMaxSpeed = 500000 +) + +// Adaptor represents a Gobot Adaptor for the FriendlyELEC NanoPC-T6 +type Adaptor struct { + name string + sys *system.Accesser // used for unit tests only + mutex *sync.Mutex + *adaptors.AnalogPinsAdaptor + *adaptors.DigitalPinsAdaptor + *adaptors.PWMPinsAdaptor + *adaptors.I2cBusAdaptor + *adaptors.SpiBusAdaptor + *adaptors.OneWireBusAdaptor +} + +// NewAdaptor creates a NanoPC-T6 Adaptor +// +// Optional parameters: +// +// adaptors.WithGpioSysfsAccess(): use legacy sysfs driver instead of default character device driver +// adaptors.WithSpiGpioAccess(sclk, ncs, sdo, sdi): use GPIO's instead of /dev/spidev#.# +// adaptors.WithGpiosActiveLow(pin's): invert the pin behavior +// adaptors.WithGpiosPullUp/Down(pin's): sets the internal pull resistor +// adaptors.WithGpiosOpenDrain/Source(pin's): sets the output behavior +// adaptors.WithGpioDebounce(pin, period): sets the input debouncer +// adaptors.WithGpioEventOnFallingEdge/RaisingEdge/BothEdges(pin, handler): activate edge detection +// +// Optional parameters for PWM, see [adaptors.NewPWMPinsAdaptor] +func NewAdaptor(opts ...interface{}) *Adaptor { + sys := system.NewAccesser() + a := &Adaptor{ + name: gobot.DefaultName("NanoPC-T6"), + sys: sys, + mutex: &sync.Mutex{}, + } + + var digitalPinsOpts []adaptors.DigitalPinsOptionApplier + var pwmPinsOpts []adaptors.PwmPinsOptionApplier + var spiBusOpts []adaptors.SpiBusOptionApplier + for _, opt := range opts { + switch o := opt.(type) { + case adaptors.DigitalPinsOptionApplier: + digitalPinsOpts = append(digitalPinsOpts, o) + case adaptors.PwmPinsOptionApplier: + pwmPinsOpts = append(pwmPinsOpts, o) + case adaptors.SpiBusOptionApplier: + spiBusOpts = append(spiBusOpts, o) + default: + panic(fmt.Sprintf("'%s' can not be applied on adaptor '%s'", opt, a.name)) + } + } + + analogPinTranslator := adaptors.NewAnalogPinTranslator(sys, analogPinDefinitions) + digitalPinTranslator := adaptors.NewDigitalPinTranslator(sys, gpioPinDefinitions) + pwmPinTranslator := adaptors.NewPWMPinTranslator(sys, pwmPinDefinitions) + // Valid bus numbers are [3,4,5,7,8] which corresponds to /dev/i2c-3, /dev/i2c-4 ... + // needs to be enabled by DT-overlay: i2c3-m0, i2c4-m3, i2c5-m0, i2c8-m2 + // i2c7-m0 maybe shared with sound, so 0x?? is in use + // We don't support /dev/i2c-0 (voltage regulator), /dev/i2c-1 (?), /dev/i2c-2 (voltage regulator), + // /dev/i2c-6 (RTC, USB-C, EEPROM 24c02), /dev/i2c-9 (ddc), /dev/i2c-10 (ddc), /dev/i2c-11 (fde50000.dp). + i2cBusNumberValidator := adaptors.NewBusNumberValidator([]int{3, 4, 5, 7, 8}) + // Valid bus numbers are [0,4] which corresponds to /dev/spidev0.x, /dev/spidev4.x + // x is the chip number <255 + spiBusNumberValidator := adaptors.NewBusNumberValidator([]int{0, 4}) + + a.AnalogPinsAdaptor = adaptors.NewAnalogPinsAdaptor(sys, analogPinTranslator.Translate) + a.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, digitalPinTranslator.Translate, digitalPinsOpts...) + a.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, pwmPinTranslator.Translate, pwmPinsOpts...) + a.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, i2cBusNumberValidator.Validate, defaultI2cBusNumber) + a.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, spiBusNumberValidator.Validate, defaultSpiBusNumber, + defaultSpiChipNumber, defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed, a.DigitalPinsAdaptor, spiBusOpts...) + // pin 16 needs to be activated by DT-overlay w1-gpio3-b3 + a.OneWireBusAdaptor = adaptors.NewOneWireBusAdaptor(sys) + + return a +} + +// Name returns the name of the Adaptor +func (a *Adaptor) Name() string { return a.name } + +// SetName sets the name of the Adaptor +func (a *Adaptor) SetName(n string) { a.name = n } + +// Connect create new connection to board and pins. +func (a *Adaptor) Connect() error { + a.mutex.Lock() + defer a.mutex.Unlock() + + if err := a.OneWireBusAdaptor.Connect(); err != nil { + return err + } + + if err := a.SpiBusAdaptor.Connect(); err != nil { + return err + } + + if err := a.I2cBusAdaptor.Connect(); err != nil { + return err + } + + if err := a.AnalogPinsAdaptor.Connect(); err != nil { + return err + } + + if err := a.PWMPinsAdaptor.Connect(); err != nil { + return err + } + + return a.DigitalPinsAdaptor.Connect() +} + +// Finalize closes connection to board, pins and bus +func (a *Adaptor) Finalize() error { + a.mutex.Lock() + defer a.mutex.Unlock() + + err := a.DigitalPinsAdaptor.Finalize() + + if e := a.PWMPinsAdaptor.Finalize(); e != nil { + err = multierror.Append(err, e) + } + + if e := a.AnalogPinsAdaptor.Finalize(); e != nil { + err = multierror.Append(err, e) + } + + if e := a.I2cBusAdaptor.Finalize(); e != nil { + err = multierror.Append(err, e) + } + + if e := a.SpiBusAdaptor.Finalize(); e != nil { + err = multierror.Append(err, e) + } + + if e := a.OneWireBusAdaptor.Finalize(); e != nil { + err = multierror.Append(err, e) + } + + return err +} diff --git a/platforms/friendlyelec/nanopct6/adaptor_test.go b/platforms/friendlyelec/nanopct6/adaptor_test.go new file mode 100644 index 000000000..def6b3b12 --- /dev/null +++ b/platforms/friendlyelec/nanopct6/adaptor_test.go @@ -0,0 +1,224 @@ +package nanopct6 + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "gobot.io/x/gobot/v2" + "gobot.io/x/gobot/v2/drivers/aio" + "gobot.io/x/gobot/v2/drivers/gpio" + "gobot.io/x/gobot/v2/drivers/i2c" + "gobot.io/x/gobot/v2/platforms/adaptors" + "gobot.io/x/gobot/v2/system" +) + +const ( + pwmDir = "/sys/devices/platform/febf0030.pwm/pwm/pwmchip7/" //nolint:gosec // false positive + pwmExportPath = pwmDir + "export" + pwmUnexportPath = pwmDir + "unexport" + pwmPwmDir = pwmDir + "pwm0/" + pwmEnablePath = pwmPwmDir + "enable" + pwmPeriodPath = pwmPwmDir + "period" + pwmDutyCyclePath = pwmPwmDir + "duty_cycle" + pwmPolarityPath = pwmPwmDir + "polarity" + + pwmInvertedIdentifier = "inversed" +) + +var pwmMockPaths = []string{ + pwmExportPath, + pwmUnexportPath, + pwmEnablePath, + pwmPeriodPath, + pwmDutyCyclePath, + pwmPolarityPath, +} + +// make sure that this Adaptor fulfills all the required interfaces +var ( + _ gobot.Adaptor = (*Adaptor)(nil) + _ gobot.DigitalPinnerProvider = (*Adaptor)(nil) + _ gobot.PWMPinnerProvider = (*Adaptor)(nil) + _ gpio.DigitalReader = (*Adaptor)(nil) + _ gpio.DigitalWriter = (*Adaptor)(nil) + _ aio.AnalogReader = (*Adaptor)(nil) + _ i2c.Connector = (*Adaptor)(nil) +) + +func preparePwmFs(fs *system.MockFilesystem) { + fs.Files[pwmEnablePath].Contents = "0" + fs.Files[pwmPeriodPath].Contents = "0" + fs.Files[pwmDutyCyclePath].Contents = "0" + fs.Files[pwmPolarityPath].Contents = pwmInvertedIdentifier +} + +func initConnectedTestAdaptorWithMockedFilesystem(mockPaths []string) (*Adaptor, *system.MockFilesystem) { + a := initConnectedTestAdaptor() + fs := a.sys.UseMockFilesystem(mockPaths) + return a, fs +} + +func initConnectedTestAdaptor() *Adaptor { + a := NewAdaptor() + if err := a.Connect(); err != nil { + panic(err) + } + return a +} + +func TestNewAdaptor(t *testing.T) { + // arrange & act + a := NewAdaptor() + // assert + assert.IsType(t, &Adaptor{}, a) + assert.True(t, strings.HasPrefix(a.Name(), "NanoPC-T6")) + assert.NotNil(t, a.sys) + assert.NotNil(t, a.mutex) + assert.NotNil(t, a.AnalogPinsAdaptor) + assert.NotNil(t, a.DigitalPinsAdaptor) + assert.NotNil(t, a.PWMPinsAdaptor) + assert.NotNil(t, a.I2cBusAdaptor) + assert.NotNil(t, a.SpiBusAdaptor) + assert.True(t, a.sys.HasDigitalPinCdevAccess()) + // act & assert + a.SetName("NewName") + assert.Equal(t, "NewName", a.Name()) +} + +func TestNewAdaptorWithOption(t *testing.T) { + // arrange & act + a := NewAdaptor(adaptors.WithGpiosActiveLow("1"), adaptors.WithGpioSysfsAccess()) + // assert + require.NoError(t, a.Connect()) + assert.True(t, a.sys.HasDigitalPinSysfsAccess()) +} + +func TestDigitalIO(t *testing.T) { + // some basic tests, further tests are done in "digitalpinsadaptor.go" + // arrange + a := initConnectedTestAdaptor() + dpa := a.sys.UseMockDigitalPinAccess() + require.True(t, a.sys.HasDigitalPinCdevAccess()) + // act & assert write + err := a.DigitalWrite("7", 1) + require.NoError(t, err) + assert.Equal(t, []int{1}, dpa.Written("gpiochip3", "10")) + // arrange, act & assert read + dpa.UseValues("gpiochip0", "20", []int{3}) + i, err := a.DigitalRead("10") + require.NoError(t, err) + assert.Equal(t, 3, i) + // act and assert unknown pin + require.ErrorContains(t, a.DigitalWrite("99", 1), "'99' is not a valid id for a digital pin") + // act and assert finalize + require.NoError(t, a.Finalize()) + assert.Equal(t, 0, dpa.Exported("gpiochip3", "10")) + assert.Equal(t, 0, dpa.Exported("gpiochip0", "20")) +} + +func TestDigitalIOSysfs(t *testing.T) { + // some basic tests, further tests are done in "digitalpinsadaptor.go" + // arrange + a := NewAdaptor(adaptors.WithGpioSysfsAccess()) + require.NoError(t, a.Connect()) + dpa := a.sys.UseMockDigitalPinAccess() + require.True(t, a.sys.HasDigitalPinSysfsAccess()) + // act & assert write + err := a.DigitalWrite("7", 1) + require.NoError(t, err) + assert.Equal(t, []int{1}, dpa.Written("", "106")) + // arrange, act & assert read + dpa.UseValues("", "20", []int{4}) + i, err := a.DigitalRead("10") + require.NoError(t, err) + assert.Equal(t, 4, i) + // act and assert unknown pin + require.ErrorContains(t, a.DigitalWrite("99", 1), "'99' is not a valid id for a digital pin") + // act and assert finalize + require.NoError(t, a.Finalize()) + assert.Equal(t, 0, dpa.Exported("", "106")) + assert.Equal(t, 0, dpa.Exported("", "20")) +} + +func TestAnalogRead(t *testing.T) { + mockPaths := []string{ + "/sys/class/thermal/thermal_zone0/temp", + } + + a, fs := initConnectedTestAdaptorWithMockedFilesystem(mockPaths) + + fs.Files["/sys/class/thermal/thermal_zone0/temp"].Contents = "567\n" + got, err := a.AnalogRead("soc_thermal") + require.NoError(t, err) + assert.Equal(t, 567, got) + + _, err = a.AnalogRead("thermal_zone10") + require.ErrorContains(t, err, "'thermal_zone10' is not a valid id for an analog pin") + + fs.WithReadError = true + _, err = a.AnalogRead("soc_thermal") + require.ErrorContains(t, err, "read error") + fs.WithReadError = false + + require.NoError(t, a.Finalize()) +} + +func TestFinalizeErrorAfterGPIO(t *testing.T) { + // arrange + a := initConnectedTestAdaptor() + dpa := a.sys.UseMockDigitalPinAccess() + require.True(t, a.sys.HasDigitalPinCdevAccess()) + require.NoError(t, a.DigitalWrite("7", 1)) + dpa.UseUnexportError("gpiochip3", "10") + // act + err := a.Finalize() + // assert + require.ErrorContains(t, err, "unexport error") +} + +func TestFinalizeErrorAfterPWM(t *testing.T) { + // indirect test for PWM.Finalize() is called for the adaptor + // arrange + a, fs := initConnectedTestAdaptorWithMockedFilesystem(pwmMockPaths) + preparePwmFs(fs) + require.NoError(t, a.PwmWrite("13", 1)) + fs.WithWriteError = true + // act + err := a.Finalize() + // assert + require.ErrorContains(t, err, "write error") +} + +func TestSpiDefaultValues(t *testing.T) { + a := NewAdaptor() + + assert.Equal(t, 0, a.SpiDefaultBusNumber()) + assert.Equal(t, 0, a.SpiDefaultChipNumber()) + assert.Equal(t, 0, a.SpiDefaultMode()) + assert.Equal(t, 8, a.SpiDefaultBitCount()) + assert.Equal(t, int64(500000), a.SpiDefaultMaxSpeed()) +} + +func TestI2cDefaultBus(t *testing.T) { + a := NewAdaptor() + assert.Equal(t, 8, a.DefaultI2cBus()) +} + +func TestI2cFinalizeWithErrors(t *testing.T) { + // arrange + a := initConnectedTestAdaptor() + a.sys.UseMockSyscall() + fs := a.sys.UseMockFilesystem([]string{"/dev/i2c-4"}) + con, err := a.GetI2cConnection(0xff, 4) + require.NoError(t, err) + _, err = con.Write([]byte{0xbf}) + require.NoError(t, err) + fs.WithCloseError = true + // act + err = a.Finalize() + // assert + require.ErrorContains(t, err, "close error") +} diff --git a/platforms/friendlyelec/nanopct6/doc.go b/platforms/friendlyelec/nanopct6/doc.go new file mode 100644 index 000000000..9a76ecb33 --- /dev/null +++ b/platforms/friendlyelec/nanopct6/doc.go @@ -0,0 +1,7 @@ +/* +Package nanopct6 contains the Gobot adaptor for the FriendlyELEC NanoPC-T6. + +For further information refer to the boards README: +https://github.com/hybridgroup/gobot/blob/release/platforms/friendlyelec/nanopct6/README.md +*/ +package nanopct6 // import "gobot.io/x/gobot/v2/platforms/friendlyelec/nanopct6" diff --git a/platforms/friendlyelec/nanopct6/pin_map.go b/platforms/friendlyelec/nanopct6/pin_map.go new file mode 100644 index 000000000..34222d36a --- /dev/null +++ b/platforms/friendlyelec/nanopct6/pin_map.go @@ -0,0 +1,85 @@ +package nanopct6 + +import "gobot.io/x/gobot/v2/platforms/adaptors" + +// notes for character device +// sysfs: Chip*32 + (A=0, B=8, C=16) + Nr +// tested with cdev on a NanoPC-T6 2301 board: armbian Linux, OK: works, ?: unknown, NOK: not working +// IN: works only as input, PU: if used as input, external pullup resistor needed +var gpioPinDefinitions = adaptors.DigitalPinDefinitions{ + "10": {Sysfs: 20, Cdev: adaptors.CdevPin{Chip: 0, Line: 20}}, // GPIO0_C4_UART0_RX_M0 - OK + "8": {Sysfs: 21, Cdev: adaptors.CdevPin{Chip: 0, Line: 21}}, // GPIO0_C5_UART0_TX_M0_PWM4_M0 - OK + "32": {Sysfs: 22, Cdev: adaptors.CdevPin{Chip: 0, Line: 22}}, // GPIO0_C6_PWM5_M1 - OK + "27": {Sysfs: 32, Cdev: adaptors.CdevPin{Chip: 1, Line: 0}}, // GPIO1_A0_UART6_RX_M1 - OK + "28": {Sysfs: 33, Cdev: adaptors.CdevPin{Chip: 1, Line: 1}}, // GPIO1_A1_UART6_TX_M1 - OK (UP) + "15": {Sysfs: 39, Cdev: adaptors.CdevPin{Chip: 1, Line: 7}}, // GPIO1_A7 - OK + "26": {Sysfs: 40, Cdev: adaptors.CdevPin{Chip: 1, Line: 8}}, // GPIO1_B0 - OK + "21": {Sysfs: 41, Cdev: adaptors.CdevPin{Chip: 1, Line: 9}}, // GPIO1_B1_SPI0_MISO_M2 - OK (UP) + "19": {Sysfs: 42, Cdev: adaptors.CdevPin{Chip: 1, Line: 10}}, // GPIO1_B2_SPI0_MOSI_M2_UART4_RX_M2 - OK + "23": {Sysfs: 43, Cdev: adaptors.CdevPin{Chip: 1, Line: 11}}, // GPIO1_B3_SPI0_CLK_M2_UART4_TX_M2 - OK + "24": {Sysfs: 44, Cdev: adaptors.CdevPin{Chip: 1, Line: 12}}, // GPIO1_B4_SPI0_CS0_M2_UART7_RX_M2 - OK + "22": {Sysfs: 45, Cdev: adaptors.CdevPin{Chip: 1, Line: 13}}, // GPIO1_B5_SPI0_CS1_M0_UART7_TX_M2 - OK + "5": {Sysfs: 62, Cdev: adaptors.CdevPin{Chip: 1, Line: 30}}, // GPIO1_D6_I2C8_SCL_M2 - OK + "3": {Sysfs: 63, Cdev: adaptors.CdevPin{Chip: 1, Line: 31}}, // GPIO1_D7_I2C8_SDA_M2 - OK + "CSI1_11": {Sysfs: 81, Cdev: adaptors.CdevPin{Chip: 2, Line: 17}}, // GPIO2_C1 - ? + "CSI1_12": {Sysfs: 82, Cdev: adaptors.CdevPin{Chip: 2, Line: 18}}, // GPIO2_C2 - ? + "35": {Sysfs: 96, Cdev: adaptors.CdevPin{Chip: 3, Line: 0}}, // GPIO3_A0_SPI4_MISO_M1_I2S3_MCLK_PWM10_M0 - OK + "38": {Sysfs: 97, Cdev: adaptors.CdevPin{Chip: 3, Line: 1}}, // GPIO3_A1_SPI4_MOSI_M1_I2S3_SCLK - OK + "40": {Sysfs: 98, Cdev: adaptors.CdevPin{Chip: 3, Line: 2}}, // GPIO3_A2_SPI4_CLK_M1_UART8_TX_M1_I2S3_LRCK - OK + "36": {Sysfs: 99, Cdev: adaptors.CdevPin{Chip: 3, Line: 3}}, // GPIO3_A3_SPI4_CS0_M1_UART8_RX_M1_I2S3_SDO - OK + "37": {Sysfs: 100, Cdev: adaptors.CdevPin{Chip: 3, Line: 4}}, // GPIO3_A4_SPI4_CS1_M1_I2S3_SDI - OK + "DSI0_12": {Sysfs: 102, Cdev: adaptors.CdevPin{Chip: 3, Line: 6}}, // GPIO3_A6 - ? + "33": {Sysfs: 104, Cdev: adaptors.CdevPin{Chip: 3, Line: 8}}, // GPIO3_B0_PWM9_M0 - OK + "DSI0_10": {Sysfs: 105, Cdev: adaptors.CdevPin{Chip: 3, Line: 9}}, // GPIO3_B1_PWM2_M1 - ? + "7": {Sysfs: 106, Cdev: adaptors.CdevPin{Chip: 3, Line: 10}}, // GPIO3_B2_I2S2_SDI_M1 - OK + "16": {Sysfs: 107, Cdev: adaptors.CdevPin{Chip: 3, Line: 11}}, // GPIO3_B3_I2S2_SDO_M1 - OK + "18": {Sysfs: 108, Cdev: adaptors.CdevPin{Chip: 3, Line: 12}}, // GPIO3_B4_I2S2_MCLK_M1 - OK + "29": {Sysfs: 109, Cdev: adaptors.CdevPin{Chip: 3, Line: 13}}, // GPIO3_B5_UART3_TX_M1_I2S2_SCLK_M1_PWM12_M0 - OK + "31": {Sysfs: 110, Cdev: adaptors.CdevPin{Chip: 3, Line: 14}}, // GPIO3_B6_UART3_RX_M1_I2S2_LRCK_M1_PWM13_M0 - OK + "12": {Sysfs: 111, Cdev: adaptors.CdevPin{Chip: 3, Line: 15}}, // GPIO3_B7 - OK (UP) + "DSI0_8": {Sysfs: 112, Cdev: adaptors.CdevPin{Chip: 3, Line: 16}}, // GPIO3_C0 - ? + "DSI0_14": {Sysfs: 113, Cdev: adaptors.CdevPin{Chip: 3, Line: 17}}, // GPIO3_C1 - ? + "11": {Sysfs: 114, Cdev: adaptors.CdevPin{Chip: 3, Line: 18}}, // GPIO3_C2_PWM14_M0 - OK + "13": {Sysfs: 115, Cdev: adaptors.CdevPin{Chip: 3, Line: 19}}, // GPIO3_C3_PWM15_IR_M0 - OK + "DSI1_10": {Sysfs: 125, Cdev: adaptors.CdevPin{Chip: 3, Line: 29}}, // GPIO3_D5_PWM11_M3 - ? + "DSI1_8": {Sysfs: 128, Cdev: adaptors.CdevPin{Chip: 4, Line: 0}}, // GPIO4_A0 - ? + "DSI1_14": {Sysfs: 129, Cdev: adaptors.CdevPin{Chip: 4, Line: 1}}, // GPIO4_A1 - ? + "DSI1_12": {Sysfs: 131, Cdev: adaptors.CdevPin{Chip: 4, Line: 3}}, // GPIO4_A3 - ? + "CSI0_11": {Sysfs: 148, Cdev: adaptors.CdevPin{Chip: 4, Line: 20}}, // GPIO4_C4 - ? + "CSI0_12": {Sysfs: 149, Cdev: adaptors.CdevPin{Chip: 4, Line: 21}}, // GPIO4_C5 - ? +} + +var pwmPinDefinitions = adaptors.PWMPinDefinitions{ + // needs to be enabled by DT-overlay pwm2-m1 (pwm2 = "/pwm@fd8b0020";) + "DSI0_10": {Dir: "/sys/devices/platform/fd8b0020.pwm/pwm/", DirRegexp: "pwmchip[0|1|2|3|4|5|6|7]$", Channel: 0}, + // needs to be enabled by DT-overlay pwm4-m0 (pwm4 = "/pwm@febd0000";) + "8": {Dir: "/sys/devices/platform/febd0000.pwm/pwm/", DirRegexp: "pwmchip[0|1|2|3|4|5|6|7]$", Channel: 0}, + // needs to be enabled by DT-overlay pwm5-m1 (pwm5 = "/pwm@febd0010";) + "32": {Dir: "/sys/devices/platform/febd0010.pwm/pwm/", DirRegexp: "pwmchip[0|1|2|3|4|5|6|7]$", Channel: 0}, + // needs to be enabled by DT-overlay pwm9-m0 (pwm9 = "/pwm@febe0010";) + "33": {Dir: "/sys/devices/platform/febe0010.pwm/pwm/", DirRegexp: "pwmchip[0|1|2|3|4|5|6|7]$", Channel: 0}, + // needs to be enabled by DT-overlay pwm10-m0 (pwm10 = "/pwm@febe0020";) + "35": {Dir: "/sys/devices/platform/febe0020.pwm/pwm/", DirRegexp: "pwmchip[0|1|2|3|4|5|6|7]$", Channel: 0}, + // needs to be enabled by DT-overlay pwm11-m3 (pwm11 = "/pwm@febe0030";) + "DSI1_10": {Dir: "/sys/devices/platform/febe0030.pwm/pwm/", DirRegexp: "pwmchip[0|1|2|3|4|5|6|7]$", Channel: 0}, + // needs to be enabled by DT-overlay pwm12-m0 (pwm12 = "/pwm@febf0000";) + "29": {Dir: "/sys/devices/platform/febf0000.pwm/pwm/", DirRegexp: "pwmchip[0|1|2|3|4|5|6|7]$", Channel: 0}, + // needs to be enabled by DT-overlay pwm13-m0 (pwm13 = "/pwm@febf0010";) + "31": {Dir: "/sys/devices/platform/febf0010.pwm/pwm/", DirRegexp: "pwmchip[0|1|2|3|4|5|6|7]$", Channel: 0}, + // needs to be enabled by DT-overlay pwm14-m0 (pwm14 = "/pwm@febf0020";) + "11": {Dir: "/sys/devices/platform/febf0020.pwm/pwm/", DirRegexp: "pwmchip[0|1|2|3|4|5|6|7]$", Channel: 0}, + // needs to be enabled by DT-overlay pwm15-m0 (pwm15 = "/pwm@febf0030";) + "13": {Dir: "/sys/devices/platform/febf0030.pwm/pwm/", DirRegexp: "pwmchip[0|1|2|3|4|5|6|7]$", Channel: 0}, +} + +var analogPinDefinitions = adaptors.AnalogPinDefinitions{ + // +/-273.200 °C need >=7 characters to read: +/-273200 millidegree Celsius + // names equals /sys/class/thermal/thermal_zone*/hwmon*/name + "soc_thermal": {Path: "/sys/class/thermal/thermal_zone0/temp", W: false, ReadBufLen: 7}, + "bigcore0_thermal": {Path: "/sys/class/thermal/thermal_zone1/temp", W: false, ReadBufLen: 7}, + "bigcore1_thermal": {Path: "/sys/class/thermal/thermal_zone2/temp", W: false, ReadBufLen: 7}, + "littlecore_thermal": {Path: "/sys/class/thermal/thermal_zone3/temp", W: false, ReadBufLen: 7}, + "center_thermal": {Path: "/sys/class/thermal/thermal_zone4/temp", W: false, ReadBufLen: 7}, + "gpu_thermal": {Path: "/sys/class/thermal/thermal_zone5/temp", W: false, ReadBufLen: 7}, + "npu_thermal": {Path: "/sys/class/thermal/thermal_zone6/temp", W: false, ReadBufLen: 7}, +} diff --git a/platforms/nanopi/LICENSE b/platforms/friendlyelec/nanopi/LICENSE similarity index 100% rename from platforms/nanopi/LICENSE rename to platforms/friendlyelec/nanopi/LICENSE diff --git a/platforms/nanopi/README.md b/platforms/friendlyelec/nanopi/README.md similarity index 92% rename from platforms/nanopi/README.md rename to platforms/friendlyelec/nanopi/README.md index 2e7c61798..4be4899fe 100644 --- a/platforms/nanopi/README.md +++ b/platforms/friendlyelec/nanopi/README.md @@ -1,9 +1,9 @@ # NanoPi Boards -The FriendlyARM NanoPi Boards are single board SoC computers with different hardware design. It has built-in GPIO, PWM, +The FriendlyELEC NanoPi Boards are single board SoC computers with different hardware design. It has built-in GPIO, PWM, SPI, and I2C interfaces. -For more info about the NanoPi Boards, go to [https://wiki.friendlyelec.com/wiki/index.php/Main_Page](https://wiki.friendlyelec.com/wiki/index.php/Main_Page). +For more info about the NanoPi Boards, go to [https://wiki.friendlyelec.com/wiki/index.php/NanoPi_NEO](https://wiki.friendlyelec.com/wiki/index.php/NanoPi_NEO). ## How to Install diff --git a/platforms/friendlyelec/nanopi/doc.go b/platforms/friendlyelec/nanopi/doc.go new file mode 100644 index 000000000..368440fd0 --- /dev/null +++ b/platforms/friendlyelec/nanopi/doc.go @@ -0,0 +1,7 @@ +/* +Package nanopi contains the Gobot adaptor for the FriendlyELEC NanoPi Boards. + +For further information refer to nanopi README: +https://github.com/hybridgroup/gobot/blob/release/platforms/friendlyelec/nanopi/README.md +*/ +package nanopi // import "gobot.io/x/gobot/v2/platforms/friendlyelec/nanopi" diff --git a/platforms/nanopi/nanopi_adaptor.go b/platforms/friendlyelec/nanopi/nanopi_adaptor.go similarity index 98% rename from platforms/nanopi/nanopi_adaptor.go rename to platforms/friendlyelec/nanopi/nanopi_adaptor.go index 5bc06ce0d..f487d2257 100644 --- a/platforms/nanopi/nanopi_adaptor.go +++ b/platforms/friendlyelec/nanopi/nanopi_adaptor.go @@ -21,7 +21,7 @@ const ( defaultSpiMaxSpeed = 500000 ) -// Adaptor represents a Gobot Adaptor for the FriendlyARM NanoPi Boards +// Adaptor represents a Gobot Adaptor for the FriendlyELEC NanoPi Boards type Adaptor struct { name string sys *system.Accesser // used for unit tests only diff --git a/platforms/nanopi/nanopi_adaptor_test.go b/platforms/friendlyelec/nanopi/nanopi_adaptor_test.go similarity index 66% rename from platforms/nanopi/nanopi_adaptor_test.go rename to platforms/friendlyelec/nanopi/nanopi_adaptor_test.go index 03e9a1c93..4d1ac29b7 100644 --- a/platforms/nanopi/nanopi_adaptor_test.go +++ b/platforms/friendlyelec/nanopi/nanopi_adaptor_test.go @@ -1,8 +1,6 @@ package nanopi import ( - "fmt" - "strconv" "strings" "testing" @@ -169,107 +167,6 @@ func TestAnalog(t *testing.T) { require.NoError(t, a.Finalize()) } -func TestInvalidPWMPin(t *testing.T) { - a, fs := initConnectedTestAdaptorWithMockedFilesystem(pwmMockPaths) - preparePwmFs(fs) - - err := a.PwmWrite("666", 42) - require.ErrorContains(t, err, "'666' is not a valid id for a PWM pin") - - err = a.ServoWrite("666", 120) - require.ErrorContains(t, err, "'666' is not a valid id for a PWM pin") - - err = a.PwmWrite("3", 42) - require.ErrorContains(t, err, "'3' is not a valid id for a PWM pin") - - err = a.ServoWrite("3", 120) - require.ErrorContains(t, err, "'3' is not a valid id for a PWM pin") -} - -func TestPwmWrite(t *testing.T) { - // arrange - a, fs := initConnectedTestAdaptorWithMockedFilesystem(pwmMockPaths) - preparePwmFs(fs) - // act - err := a.PwmWrite("PWM", 100) - // assert - require.NoError(t, err) - assert.Equal(t, "0", fs.Files[pwmExportPath].Contents) - assert.Equal(t, "1", fs.Files[pwmEnablePath].Contents) - assert.Equal(t, strconv.Itoa(10000000), fs.Files[pwmPeriodPath].Contents) - assert.Equal(t, "3921568", fs.Files[pwmDutyCyclePath].Contents) - assert.Equal(t, "normal", fs.Files[pwmPolarityPath].Contents) - - require.NoError(t, a.Finalize()) -} - -func TestServoWrite(t *testing.T) { - // arrange: prepare 50Hz for servos - const ( - pin = "PWM" - fiftyHzNano = 20000000 - ) - a := NewNeoAdaptor(adaptors.WithPWMDefaultPeriodForPin(pin, fiftyHzNano)) - fs := a.sys.UseMockFilesystem(pwmMockPaths) - preparePwmFs(fs) - require.NoError(t, a.Connect()) - // act & assert for 0° (min default value) - err := a.ServoWrite(pin, 0) - require.NoError(t, err) - assert.Equal(t, strconv.Itoa(fiftyHzNano), fs.Files[pwmPeriodPath].Contents) - assert.Equal(t, "500000", fs.Files[pwmDutyCyclePath].Contents) - // act & assert for 180° (max default value) - err = a.ServoWrite(pin, 180) - require.NoError(t, err) - assert.Equal(t, strconv.Itoa(fiftyHzNano), fs.Files[pwmPeriodPath].Contents) - assert.Equal(t, "2500000", fs.Files[pwmDutyCyclePath].Contents) - // act & assert invalid pins - err = a.ServoWrite("3", 120) - require.ErrorContains(t, err, "'3' is not a valid id for a PWM pin") - - require.NoError(t, a.Finalize()) -} - -func TestSetPeriod(t *testing.T) { - // arrange - a, fs := initConnectedTestAdaptorWithMockedFilesystem(pwmMockPaths) - preparePwmFs(fs) - - newPeriod := uint32(2550000) - // act - err := a.SetPeriod("PWM", newPeriod) - // assert - require.NoError(t, err) - assert.Equal(t, "0", fs.Files[pwmExportPath].Contents) - assert.Equal(t, "1", fs.Files[pwmEnablePath].Contents) - assert.Equal(t, fmt.Sprintf("%d", newPeriod), fs.Files[pwmPeriodPath].Contents) //nolint:perfsprint // ok here - assert.Equal(t, "0", fs.Files[pwmDutyCyclePath].Contents) - assert.Equal(t, "normal", fs.Files[pwmPolarityPath].Contents) - - // arrange test for automatic adjustment of duty cycle to lower value - err = a.PwmWrite("PWM", 127) // 127 is a little bit smaller than 50% of period - require.NoError(t, err) - assert.Equal(t, strconv.Itoa(1270000), fs.Files[pwmDutyCyclePath].Contents) - newPeriod = newPeriod / 10 - - // act - err = a.SetPeriod("PWM", newPeriod) - - // assert - require.NoError(t, err) - assert.Equal(t, strconv.Itoa(127000), fs.Files[pwmDutyCyclePath].Contents) - - // arrange test for automatic adjustment of duty cycle to higher value - newPeriod = newPeriod * 20 - - // act - err = a.SetPeriod("PWM", newPeriod) - - // assert - require.NoError(t, err) - assert.Equal(t, strconv.Itoa(2540000), fs.Files[pwmDutyCyclePath].Contents) -} - func TestFinalizeErrorAfterGPIO(t *testing.T) { // arrange a := initConnectedTestAdaptor() @@ -284,14 +181,15 @@ func TestFinalizeErrorAfterGPIO(t *testing.T) { } func TestFinalizeErrorAfterPWM(t *testing.T) { + // indirect test for PWM.Finalize() is called for the adaptor + // arrange a, fs := initConnectedTestAdaptorWithMockedFilesystem(pwmMockPaths) preparePwmFs(fs) - require.NoError(t, a.PwmWrite("PWM", 1)) - fs.WithWriteError = true - + // act err := a.Finalize() + // assert require.ErrorContains(t, err, "write error") } diff --git a/platforms/nanopi/nanopineo_pin_map.go b/platforms/friendlyelec/nanopi/nanopineo_pin_map.go similarity index 100% rename from platforms/nanopi/nanopineo_pin_map.go rename to platforms/friendlyelec/nanopi/nanopineo_pin_map.go diff --git a/platforms/nanopi/doc.go b/platforms/nanopi/doc.go deleted file mode 100644 index 072c4871e..000000000 --- a/platforms/nanopi/doc.go +++ /dev/null @@ -1,7 +0,0 @@ -/* -Package nanopi contains the Gobot adaptor for the FriendlyARM NanoPi Boards. - -For further information refer to nanopi README: -https://github.com/hybridgroup/gobot/blob/release/platforms/nanopi/README.md -*/ -package nanopi // import "gobot.io/x/gobot/v2/platforms/nanopi" diff --git a/platforms/tinkerboard/adaptor_test.go b/platforms/tinkerboard/adaptor_test.go index ba6b6c809..ff2988d60 100644 --- a/platforms/tinkerboard/adaptor_test.go +++ b/platforms/tinkerboard/adaptor_test.go @@ -1,8 +1,6 @@ package tinkerboard import ( - "fmt" - "strconv" "strings" "testing" @@ -171,93 +169,6 @@ func TestAnalogRead(t *testing.T) { require.NoError(t, a.Finalize()) } -func TestPwmWrite(t *testing.T) { - // arrange - a, fs := initConnectedTestAdaptorWithMockedFilesystem(pwmMockPaths) - preparePwmFs(fs) - // act - err := a.PwmWrite("33", 100) - // assert - require.NoError(t, err) - assert.Equal(t, "0", fs.Files[pwmExportPath].Contents) - assert.Equal(t, "1", fs.Files[pwmEnablePath].Contents) - assert.Equal(t, "10000000", fs.Files[pwmPeriodPath].Contents) - assert.Equal(t, "3921568", fs.Files[pwmDutyCyclePath].Contents) - assert.Equal(t, "normal", fs.Files[pwmPolarityPath].Contents) - // act & assert invalid pin - err = a.PwmWrite("666", 42) - require.ErrorContains(t, err, "'666' is not a valid id for a PWM pin") - - require.NoError(t, a.Finalize()) -} - -func TestServoWrite(t *testing.T) { - // arrange: prepare 50Hz for servos - const ( - pin = "33" - fiftyHzNano = 20000000 - ) - a := NewAdaptor(adaptors.WithPWMDefaultPeriodForPin(pin, fiftyHzNano)) - fs := a.sys.UseMockFilesystem(pwmMockPaths) - preparePwmFs(fs) - require.NoError(t, a.Connect()) - // act & assert for 0° (min default value) - err := a.ServoWrite(pin, 0) - require.NoError(t, err) - assert.Equal(t, strconv.Itoa(fiftyHzNano), fs.Files[pwmPeriodPath].Contents) - assert.Equal(t, "500000", fs.Files[pwmDutyCyclePath].Contents) - // act & assert for 180° (max default value) - err = a.ServoWrite(pin, 180) - require.NoError(t, err) - assert.Equal(t, strconv.Itoa(fiftyHzNano), fs.Files[pwmPeriodPath].Contents) - assert.Equal(t, "2500000", fs.Files[pwmDutyCyclePath].Contents) - // act & assert invalid pins - err = a.ServoWrite("3", 120) - require.ErrorContains(t, err, "'3' is not a valid id for a PWM pin") - - require.NoError(t, a.Finalize()) -} - -func TestSetPeriod(t *testing.T) { - // arrange - a, fs := initConnectedTestAdaptorWithMockedFilesystem(pwmMockPaths) - preparePwmFs(fs) - - newPeriod := uint32(2550000) - // act - err := a.SetPeriod("33", newPeriod) - // assert - require.NoError(t, err) - assert.Equal(t, "0", fs.Files[pwmExportPath].Contents) - assert.Equal(t, "1", fs.Files[pwmEnablePath].Contents) - assert.Equal(t, fmt.Sprintf("%d", newPeriod), fs.Files[pwmPeriodPath].Contents) //nolint:perfsprint // ok here - assert.Equal(t, "0", fs.Files[pwmDutyCyclePath].Contents) - assert.Equal(t, "normal", fs.Files[pwmPolarityPath].Contents) - - // arrange test for automatic adjustment of duty cycle to lower value - err = a.PwmWrite("33", 127) // 127 is a little bit smaller than 50% of period - require.NoError(t, err) - assert.Equal(t, strconv.Itoa(1270000), fs.Files[pwmDutyCyclePath].Contents) - newPeriod = newPeriod / 10 - - // act - err = a.SetPeriod("33", newPeriod) - - // assert - require.NoError(t, err) - assert.Equal(t, strconv.Itoa(127000), fs.Files[pwmDutyCyclePath].Contents) - - // arrange test for automatic adjustment of duty cycle to higher value - newPeriod = newPeriod * 20 - - // act - err = a.SetPeriod("33", newPeriod) - - // assert - require.NoError(t, err) - assert.Equal(t, strconv.Itoa(2540000), fs.Files[pwmDutyCyclePath].Contents) -} - func TestFinalizeErrorAfterGPIO(t *testing.T) { // arrange a := initConnectedTestAdaptor() @@ -272,14 +183,15 @@ func TestFinalizeErrorAfterGPIO(t *testing.T) { } func TestFinalizeErrorAfterPWM(t *testing.T) { + // indirect test for PWM.Finalize() is called for the adaptor + // arrange a, fs := initConnectedTestAdaptorWithMockedFilesystem(pwmMockPaths) preparePwmFs(fs) - require.NoError(t, a.PwmWrite("33", 1)) - fs.WithWriteError = true - + // act err := a.Finalize() + // assert require.ErrorContains(t, err, "write error") } diff --git a/platforms/upboard/up2/adaptor_test.go b/platforms/upboard/up2/adaptor_test.go index c1a233fd3..32eaf0dc5 100644 --- a/platforms/upboard/up2/adaptor_test.go +++ b/platforms/upboard/up2/adaptor_test.go @@ -161,15 +161,16 @@ func TestFinalizeErrorAfterGPIO(t *testing.T) { } func TestFinalizeErrorAfterPWM(t *testing.T) { + // indirect test for PWM.Finalize() is called for the adaptor + // arrange a, fs := initConnectedTestAdaptorWithMockedFilesystem(pwmMockPaths) fs.Files[pwmDutyCyclePath].Contents = "0" fs.Files[pwmPeriodPath].Contents = "0" - require.NoError(t, a.PwmWrite("32", 1)) - fs.WithWriteError = true - + // act err := a.Finalize() + // assert require.ErrorContains(t, err, "write error") }