mirror of
https://github.com/compute-blade-community/compute-blade-agent.git
synced 2026-04-25 03:10:45 +02:00
781ded8e43
This PR introduces a comprehensive set of new subcommands to bladectl, expanding its capabilities for querying and managing compute blade state. It also includes an internal refactor to simplify interface management across the gRPC API.
* `get`
* `fan`: Returns current fan speed.
* `identify`: Indicates whether the identify mode is active.
* `stealth`: Shows if stealth mode is currently enabled.
* `status`: Prints a full blade status report.
* `temperature`: Retrieves current SoC temperature.
* `critical`: Shows whether critical mode is active.
* `power`: Reports the current power source (e.g., PoE+ or USB).
* `set`
* `stealth`: Enables stealth mode.
* `remove`
* `stealth`: Disables stealth mode.
* `describe`
* `fan`: Outputs the current fan curve configuration.
* `monitor`: plot some charts about the state of the compute-blade-agent
* **gRPC API refactor**: The gRPC service definitions previously located in `internal/api` have been folded into `internal/agent`. This eliminates redundant interface declarations and ensures that all ComputeBladeAgent implementations are directly compatible with the gRPC API.
This reduces duplication and improves long-term maintainability and clarity of the interface contract.
```bash
bladectl set fan --percent 90 --blade 1 --blade 2
bladectl unset identify --blade 1 --blade 2 --blade 3 --blade 4
bladectl set stealth --blade 1 --blade 2 --blade 3 --blade 4
bladectl get status --blade 1 --blade 2 --blade 3 --blade 4
┌───────┬─────────────┬────────────────────┬───────────────┬──────────────┬──────────┬───────────────┬──────────────┐
│ BLADE │ TEMPERATURE │ FAN SPEED OVERRIDE │ FAN SPEED │ STEALTH MODE │ IDENTIFY │ CRITICAL MODE │ POWER STATUS │
├───────┼─────────────┼────────────────────┼───────────────┼──────────────┼──────────┼───────────────┼──────────────┤
│ 1 │ 50°C │ 90% │ 5825 RPM(90%) │ Active │ Off │ Off │ poe+ │
│ 2 │ 48°C │ 90% │ 5825 RPM(90%) │ Active │ Off │ Off │ poe+ │
│ 3 │ 49°C │ Not set │ 4643 RPM(56%) │ Active │ Off │ Off │ poe+ │
│ 4 │ 49°C │ Not set │ 4774 RPM(58%) │ Active │ Off │ Off │ poe+ │
└───────┴─────────────┴────────────────────┴───────────────┴──────────────┴──────────┴───────────────┴──────────────┘
bladectl rm stealth --blade 1 --blade 2 --blade 3 --blade 4
bladectl rm fan --blade 1 --blade 2 --blade 3 --blade 4
bladectl get status --blade 1 --blade 2 --blade 3 --blade 4
┌───────┬─────────────┬────────────────────┬───────────────┬──────────────┬──────────┬───────────────┬──────────────┐
│ BLADE │ TEMPERATURE │ FAN SPEED OVERRIDE │ FAN SPEED │ STEALTH MODE │ IDENTIFY │ CRITICAL MODE │ POWER STATUS │
├───────┼─────────────┼────────────────────┼───────────────┼──────────────┼──────────┼───────────────┼──────────────┤
│ 1 │ 51°C │ Not set │ 5177 RPM(66%) │ Off │ Off │ Off │ poe+ │
│ 2 │ 49°C │ Not set │ 5177 RPM(58%) │ Off │ Off │ Off │ poe+ │
│ 3 │ 50°C │ Not set │ 4659 RPM(60%) │ Off │ Off │ Off │ poe+ │
│ 4 │ 48°C │ Not set │ 4659 RPM(54%) │ Off │ Off │ Off │ poe+ │
└───────┴─────────────┴────────────────────┴───────────────┴──────────────┴──────────┴───────────────┴──────────────┘
```
when having multiple compute-blades in your bladeconfig:
```yaml
blades:
- name: 1
blade:
server: blade-pi1:8081
cert:
certificate-authority-data: <redacted>
client-certificate-data: <redacted>
client-key-data: <redacted>
- name: 2
blade:
server: blade-pi2:8081
cert:
certificate-authority-data: <redacted>
client-certificate-data: <redacted>
client-key-data: <redacted>
- name: 3
blade:
server: blade-pi3:8081
cert:
certificate-authority-data: <redacted>
client-certificate-data: <redacted>
client-key-data: <redacted>
- name: 4
blade:
server: blade-pi4:8081
cert:
certificate-authority-data: <redacted>
client-certificate-data: <redacted>
client-key-data: <redacted>
- name: 4
blade:
server: blade-pi4:8081
cert:
certificate-authority-data: <redacted>
client-certificate-data: <redacted>
client-key-data: <redacted>
current-blade: 1
```
Fixes #4, #9 (partially), should help with #5
* test: improve unit-testing
* fix: pin github.com/warthog618/gpiod
---------
Co-authored-by: Cedric Kienzler <cedric@specht-labs.de>
510 lines
15 KiB
Go
510 lines
15 KiB
Go
//go:build linux && !tinygo
|
|
|
|
package hal
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/compute-blade-community/compute-blade-agent/pkg/hal/led"
|
|
"github.com/compute-blade-community/compute-blade-agent/pkg/log"
|
|
"github.com/warthog618/gpiod"
|
|
"github.com/warthog618/gpiod/device/rpi"
|
|
"go.uber.org/zap"
|
|
"golang.org/x/sync/errgroup"
|
|
)
|
|
|
|
const (
|
|
bcm2711PeripheryBaseAddr = 0xFE000000
|
|
bcm2711RegPwmAddr = bcm2711PeripheryBaseAddr + 0x20C000
|
|
bcm2711GpioAddr = bcm2711PeripheryBaseAddr + 0x200000
|
|
bcm2711ClkAddr = bcm2711PeripheryBaseAddr + 0x101000
|
|
bcm2711ClkManagerPwd = (0x5A << 24) //(31 - 24) on CM_GP0CTL/CM_GP1CTL/CM_GP2CTL regs
|
|
bcm2711PageSize = 4096 // theoretical page size
|
|
|
|
// FIXME: no dead code
|
|
//bcm2711FrontButtonPin = 20
|
|
//bcm2711StealthPin = 21
|
|
//bcm2711RegPwmTachPin = 13
|
|
|
|
bcm2711RegGpfsel1 = 0x01
|
|
|
|
bcm2711RegPwmCtl = 0x00
|
|
bcm2711RegPwmRng1 = 0x04
|
|
bcm2711RegPwmFif1 = 0x06
|
|
|
|
bcm2711RegPwmCtlBitPwen2 = 8 // Enable (pwm2)
|
|
bcm2711RegPwmCtlBitClrf1 = 6 // Clear FIFO
|
|
bcm2711RegPwmCtlBitUsef1 = 5 // Use FIFO
|
|
bcm2711RegPwmCtlBitSbit1 = 3 // Line level when not transmitting
|
|
bcm2711RegPwmCtlBitRptl1 = 2 // Repeat last data when FIFO is empty
|
|
bcm2711RegPwmCtlBitMode1 = 1 // Mode; 0: PWM, 1: Serializer
|
|
bcm2711RegPwmCtlBitPwen1 = 0 // Enable (pwm1)
|
|
|
|
bcm2711RegPwmclkCntrl = 0x28
|
|
bcm2711RegPwmclkDiv = 0x29
|
|
bcm2711RegPwmclkCntrlBitSrcOsc = 0
|
|
bcm2711RegPwmclkCntrlBitEnable = 4
|
|
|
|
bcm2711DebounceInterval = 100 * time.Millisecond
|
|
|
|
bcm2711ThermalZonePath = "/sys/class/thermal/thermal_zone0/temp"
|
|
|
|
smartFanUnitDev = "/dev/ttyAMA5" // UART5
|
|
)
|
|
|
|
type bcm2711 struct {
|
|
// Config options
|
|
opts ComputeBladeHalOpts
|
|
|
|
wrMutex sync.Mutex
|
|
|
|
// Keep track of the currently set fanspeed so it can later be restored after setting the ws281x LEDs
|
|
currFanSpeed uint8
|
|
|
|
devmem *os.File
|
|
gpioMem8 []uint8
|
|
gpioMem []uint32
|
|
pwmMem8 []uint8
|
|
pwmMem []uint32
|
|
clkMem8 []uint8
|
|
clkMem []uint32
|
|
gpioChip0 *gpiod.Chip
|
|
|
|
// Save LED colors so the pixels can be updated individually
|
|
leds [2]led.Color
|
|
|
|
// Stealth mode output
|
|
stealthModeLine *gpiod.Line
|
|
|
|
// Edge button input
|
|
edgeButtonLine *gpiod.Line
|
|
edgeButtonDebounceChan chan struct{}
|
|
edgeButtonWatchChan chan struct{}
|
|
|
|
// PoE detection input
|
|
poeLine *gpiod.Line
|
|
|
|
// Fan unit
|
|
fanUnit FanUnit
|
|
}
|
|
|
|
func NewCm4Hal(ctx context.Context, opts ComputeBladeHalOpts) (ComputeBladeHal, error) {
|
|
// /dev/gpiomem doesn't allow complex operations for PWM fan control or WS281x
|
|
devmem, err := os.OpenFile("/dev/mem", os.O_RDWR|os.O_SYNC, os.ModePerm)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
gpioChip0, err := gpiod.NewChip("gpiochip0")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Setup memory mappings
|
|
gpioMem, gpioMem8, err := mmap(devmem, bcm2711GpioAddr, bcm2711PageSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pwmMem, pwmMem8, err := mmap(devmem, bcm2711RegPwmAddr, bcm2711PageSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
clkMem, clkMem8, err := mmap(devmem, bcm2711ClkAddr, bcm2711PageSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
bcm := &bcm2711{
|
|
devmem: devmem,
|
|
gpioMem: gpioMem,
|
|
gpioMem8: gpioMem8,
|
|
pwmMem: pwmMem,
|
|
pwmMem8: pwmMem8,
|
|
clkMem: clkMem,
|
|
clkMem8: clkMem8,
|
|
gpioChip0: gpioChip0,
|
|
opts: opts,
|
|
edgeButtonDebounceChan: make(chan struct{}, 1),
|
|
edgeButtonWatchChan: make(chan struct{}),
|
|
}
|
|
|
|
computeModule.WithLabelValues("cm4").Set(1)
|
|
|
|
log.FromContext(ctx).Info("starting hal setup", zap.String("hal", "bcm2711"))
|
|
err = bcm.setup(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return bcm, nil
|
|
}
|
|
|
|
// Close cleans all memory mappings
|
|
func (bcm *bcm2711) Close() error {
|
|
errs := errors.Join(
|
|
bcm.fanUnit.Close(),
|
|
syscall.Munmap(bcm.gpioMem8),
|
|
syscall.Munmap(bcm.pwmMem8),
|
|
syscall.Munmap(bcm.clkMem8),
|
|
bcm.devmem.Close(),
|
|
bcm.gpioChip0.Close(),
|
|
bcm.poeLine.Close(),
|
|
bcm.stealthModeLine.Close(),
|
|
)
|
|
|
|
return errs
|
|
}
|
|
|
|
// Init initialises GPIOs and sets sane defaults
|
|
func (bcm *bcm2711) setup(ctx context.Context) error {
|
|
var err error
|
|
|
|
// Register edge event handler for edge button
|
|
bcm.edgeButtonLine, err = bcm.gpioChip0.RequestLine(
|
|
rpi.GPIO20, gpiod.WithEventHandler(bcm.handleEdgeButtonEdge),
|
|
gpiod.WithFallingEdge, gpiod.WithPullUp, gpiod.WithDebounce(50*time.Millisecond))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Register input for PoE detection
|
|
bcm.poeLine, err = bcm.gpioChip0.RequestLine(rpi.GPIO23, gpiod.AsInput, gpiod.WithPullUp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Register output for stealth mode
|
|
bcm.stealthModeLine, err = bcm.gpioChip0.RequestLine(rpi.GPIO21, gpiod.AsOutput(1))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Setup correct fan unit
|
|
log.FromContext(ctx).Info("detecting fan unit")
|
|
detectCtx, cancel := context.WithTimeout(ctx, 3*time.Second) // temp events are sent every 2 seconds
|
|
defer cancel()
|
|
|
|
if smartFanUnitPresent, err := SmartFanUnitPresent(detectCtx, smartFanUnitDev); err == nil && smartFanUnitPresent {
|
|
log.FromContext(ctx).Info("detected smart fan unit")
|
|
bcm.fanUnit, err = NewSmartFanUnit(smartFanUnitDev)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
log.FromContext(ctx).WithError(err).Info("no smart fan unit detected, assuming standard fan unit")
|
|
// FAN PWM output for standard fan unit (GPIO 12)
|
|
// -> bcm2711RegGpfsel1 8:6, alt0
|
|
bcm.gpioMem[bcm2711RegGpfsel1] = (bcm.gpioMem[bcm2711RegGpfsel1] &^ (0b111 << 6)) | (0b100 << 6)
|
|
bcm.fanUnit = &standardFanUnitBcm2711{
|
|
GpioChip0: bcm.gpioChip0,
|
|
DisableRpmReporting: !bcm.opts.RpmReportingStandardFanUnit,
|
|
SetFanSpeedPwmFunc: func(speed uint8) error {
|
|
bcm.setFanSpeedPWM(speed)
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (bcm *bcm2711) Run(parentCtx context.Context) error {
|
|
ctx, cancel := context.WithCancel(parentCtx)
|
|
defer cancel()
|
|
|
|
group := errgroup.Group{}
|
|
|
|
group.Go(func() error {
|
|
defer cancel()
|
|
return bcm.fanUnit.Run(ctx)
|
|
})
|
|
|
|
return group.Wait()
|
|
}
|
|
|
|
func (bcm *bcm2711) handleEdgeButtonEdge(evt gpiod.LineEvent) {
|
|
// Despite debouncing, we still get multiple events for a single button press
|
|
// -> This is an in-software debounce to ensure we only get one event per button press
|
|
select {
|
|
case bcm.edgeButtonDebounceChan <- struct{}{}:
|
|
go func() {
|
|
// Manually debounce the button
|
|
<-bcm.edgeButtonDebounceChan
|
|
time.Sleep(bcm2711DebounceInterval)
|
|
edgeButtonEventCount.Inc()
|
|
close(bcm.edgeButtonWatchChan)
|
|
bcm.edgeButtonWatchChan = make(chan struct{})
|
|
}()
|
|
default:
|
|
// noop
|
|
return
|
|
}
|
|
}
|
|
|
|
// WaitForEdgeButtonPress blocks until the edge button has been pressed
|
|
func (bcm *bcm2711) WaitForEdgeButtonPress(parentCtx context.Context) error {
|
|
ctx, cancel := context.WithCancel(parentCtx)
|
|
defer cancel()
|
|
|
|
fanUnitChan := make(chan struct{})
|
|
go func() {
|
|
err := bcm.fanUnit.WaitForButtonPress(ctx)
|
|
if err != nil && err != context.Canceled {
|
|
log.FromContext(ctx).WithError(err).Error("failed to wait for button press")
|
|
} else {
|
|
close(fanUnitChan)
|
|
}
|
|
}()
|
|
|
|
// Either wait for the context to be cancelled or the edge button to be pressed
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
case <-bcm.edgeButtonWatchChan:
|
|
return nil
|
|
case <-fanUnitChan:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (bcm *bcm2711) GetFanRPM() (float64, error) {
|
|
rpm, err := bcm.fanUnit.FanSpeedRPM(context.TODO())
|
|
return float64(rpm), err
|
|
}
|
|
|
|
func (bcm *bcm2711) GetPowerStatus() (PowerStatus, error) {
|
|
// GPIO 23 is used for PoE detection
|
|
val, err := bcm.poeLine.Value()
|
|
if err != nil {
|
|
return PowerPoeOrUsbC, err
|
|
}
|
|
|
|
if val > 0 {
|
|
powerStatus.WithLabelValues(fmt.Sprint(PowerPoe802at)).Set(1)
|
|
powerStatus.WithLabelValues(fmt.Sprint(PowerPoeOrUsbC)).Set(0)
|
|
return PowerPoe802at, nil
|
|
}
|
|
powerStatus.WithLabelValues(fmt.Sprint(PowerPoe802at)).Set(0)
|
|
powerStatus.WithLabelValues(fmt.Sprint(PowerPoeOrUsbC)).Set(1)
|
|
return PowerPoeOrUsbC, nil
|
|
}
|
|
|
|
func (bcm *bcm2711) setPwm0Freq(targetFrequency uint64) error {
|
|
// Calculate PWM divisor based on target frequency
|
|
divisor := 54000000 / targetFrequency
|
|
realDivisor := divisor & 0xfff // 12 bits
|
|
if divisor != realDivisor {
|
|
return fmt.Errorf("invalid frequency, max divisor is 4095, calculated divisor is %d", divisor)
|
|
}
|
|
|
|
// Stop pwm for both channels; this is required to set the new configuration
|
|
bcm.pwmMem[bcm2711RegPwmCtl] &^= (1 << bcm2711RegPwmCtlBitPwen1) | (1 << bcm2711RegPwmCtlBitPwen2)
|
|
time.Sleep(time.Microsecond * 10)
|
|
|
|
// Stop clock w/o any changes, they cannot be made in the same step
|
|
bcm.clkMem[bcm2711RegPwmclkCntrl] = bcm2711ClkManagerPwd | (bcm.clkMem[bcm2711RegPwmclkCntrl] &^ (1 << 4))
|
|
time.Sleep(time.Microsecond * 10)
|
|
|
|
// Wait for the clock to not be busy so we can perform the changes
|
|
for bcm.clkMem[bcm2711RegPwmclkCntrl]&(1<<7) != 0 {
|
|
time.Sleep(time.Microsecond * 10)
|
|
}
|
|
|
|
// passwd, disabled, source (oscillator)
|
|
bcm.clkMem[bcm2711RegPwmclkCntrl] = bcm2711ClkManagerPwd | (0 << bcm2711RegPwmclkCntrlBitEnable) | (1 << bcm2711RegPwmclkCntrlBitSrcOsc)
|
|
time.Sleep(time.Microsecond * 10)
|
|
|
|
bcm.clkMem[bcm2711RegPwmclkDiv] = bcm2711ClkManagerPwd | (uint32(divisor) << 12)
|
|
time.Sleep(time.Microsecond * 10)
|
|
|
|
// Start clock (passwd, enable, source)
|
|
bcm.clkMem[bcm2711RegPwmclkCntrl] = bcm2711ClkManagerPwd | (1 << bcm2711RegPwmclkCntrlBitEnable) | (1 << bcm2711RegPwmclkCntrlBitSrcOsc)
|
|
time.Sleep(time.Microsecond * 10)
|
|
|
|
// Start pwm for both channels again
|
|
bcm.pwmMem[bcm2711RegPwmCtl] &= (1 << bcm2711RegPwmCtlBitPwen1)
|
|
time.Sleep(time.Microsecond * 10)
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetFanSpeed sets the fanspeed of a blade in percent (standard fan unit)
|
|
func (bcm *bcm2711) SetFanSpeed(speed uint8) error {
|
|
fanTargetPercent.Set(float64(speed))
|
|
return bcm.fanUnit.SetFanSpeedPercent(context.TODO(), speed)
|
|
}
|
|
|
|
func (bcm *bcm2711) setFanSpeedPWM(speed uint8) {
|
|
// Noctua fans are expecting a 25khz signal, where duty cycle controls fan on/speed/off
|
|
// With the usage of the FIFO, we can alter the duty cycle by the number of bits set in the FIFO, maximum of 32.
|
|
// We therefore need a frequency of 32*25khz = 800khz, which is a divisor of 67.5 (thus we'll use 68).
|
|
// This results in an actual period frequency of 24.8khz, which is within the specifications of Noctua fans.
|
|
err := bcm.setPwm0Freq(800000)
|
|
if err != nil {
|
|
// we know it produces a valid divisor, so this should never happen
|
|
panic(err)
|
|
}
|
|
|
|
// Using hardware ticks would offer a better resultion, but this works for now.
|
|
var targetvalue uint32 = 0
|
|
if speed == 0 {
|
|
targetvalue = 0
|
|
} else if speed <= 100 {
|
|
for i := 0; i <= int((float64(speed)/100.0)*32.0); i++ {
|
|
targetvalue |= (1 << i)
|
|
}
|
|
} else {
|
|
targetvalue = ^(uint32(0))
|
|
}
|
|
|
|
// Use fifo, repeat, ...
|
|
bcm.pwmMem[bcm2711RegPwmCtl] = (1 << bcm2711RegPwmCtlBitPwen1) | (1 << bcm2711RegPwmCtlBitMode1) | (1 << bcm2711RegPwmCtlBitRptl1) | (1 << bcm2711RegPwmCtlBitUsef1)
|
|
time.Sleep(10 * time.Microsecond)
|
|
bcm.pwmMem[bcm2711RegPwmRng1] = 32
|
|
time.Sleep(10 * time.Microsecond)
|
|
bcm.pwmMem[bcm2711RegPwmFif1] = targetvalue
|
|
|
|
// Store fan speed for later use
|
|
bcm.currFanSpeed = speed
|
|
}
|
|
|
|
func (bcm *bcm2711) SetStealthMode(enable bool) error {
|
|
if enable {
|
|
stealthModeEnabled.Set(1)
|
|
return bcm.stealthModeLine.SetValue(1)
|
|
} else {
|
|
stealthModeEnabled.Set(0)
|
|
return bcm.stealthModeLine.SetValue(0)
|
|
}
|
|
}
|
|
|
|
func (bcm *bcm2711) StealthModeActive() bool {
|
|
val, err := bcm.stealthModeLine.Value()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return val > 0
|
|
}
|
|
|
|
// serializePwmDataFrame converts a byte to a 24 bit PWM data frame for WS281x LEDs
|
|
func serializePwmDataFrame(data uint8) uint32 {
|
|
var result uint32 = 0
|
|
for i := 7; i >= 0; i-- {
|
|
if i != 7 {
|
|
result <<= 3
|
|
}
|
|
if (uint32(data)&(1<<i))>>i == 0 {
|
|
result |= 0b100 // -__
|
|
} else {
|
|
result |= 0b110 // --_
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (bcm *bcm2711) SetLed(idx LedIndex, color led.Color) error {
|
|
if idx >= 2 {
|
|
return fmt.Errorf("invalid led index %d, supported: [0, 1]", idx)
|
|
}
|
|
|
|
// Update the fan unit LED if the index is the same as the fan unit LED index
|
|
if idx == LedEdge {
|
|
if err := bcm.fanUnit.SetLed(context.TODO(), color); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
bcm.leds[idx] = color
|
|
|
|
return bcm.updateLEDs()
|
|
}
|
|
|
|
// updateLEDs sets the color of the WS281x LEDs
|
|
func (bcm *bcm2711) updateLEDs() error {
|
|
bcm.wrMutex.Lock()
|
|
defer bcm.wrMutex.Unlock()
|
|
|
|
ledColorChangeEventCount.Inc()
|
|
|
|
// Set frequency to 3*800khz.
|
|
// we'll bit-bang the data, so we'll need to send 3 bits per one bit of data.
|
|
if err := bcm.setPwm0Freq(3 * 800000); err != nil {
|
|
return err
|
|
}
|
|
time.Sleep(10 * time.Microsecond)
|
|
|
|
// WS281x Output (GPIO 18)
|
|
// -> bcm2711RegGpfsel1 24:26, regular output; it's configured as alt5 whenever pixel data is sent.
|
|
// This is not optimal but required as the pwm0 peripheral is shared between fan and data line for the LEDs.
|
|
time.Sleep(10 * time.Microsecond)
|
|
bcm.gpioMem[bcm2711RegGpfsel1] = (bcm.gpioMem[bcm2711RegGpfsel1] &^ (0b111 << 24)) | (0b010 << 24)
|
|
time.Sleep(10 * time.Microsecond)
|
|
defer func() {
|
|
// Set to regular output again so the PWM signal doesn't confuse the WS2812
|
|
bcm.gpioMem[bcm2711RegGpfsel1] = (bcm.gpioMem[bcm2711RegGpfsel1] &^ (0b111 << 24)) | (0b001 << 24)
|
|
bcm.setFanSpeedPWM(bcm.currFanSpeed)
|
|
}()
|
|
|
|
bcm.pwmMem[bcm2711RegPwmCtl] = (1 << bcm2711RegPwmCtlBitMode1) | (1 << bcm2711RegPwmCtlBitRptl1) | (0 << bcm2711RegPwmCtlBitSbit1) | (1 << bcm2711RegPwmCtlBitUsef1) | (1 << bcm2711RegPwmCtlBitClrf1)
|
|
time.Sleep(10 * time.Microsecond)
|
|
// bcm.pwmMem[bcm2711RegPwmRng1] = 32
|
|
bcm.pwmMem[bcm2711RegPwmRng1] = 24 // we only need 24 bits per LED
|
|
time.Sleep(10 * time.Microsecond)
|
|
|
|
// Add sufficient padding to clear 50us of silence with ~412.5ns per bit -> at least 121 bits -> let's be safe and send 6*24=144 bits of silence
|
|
bcm.pwmMem[bcm2711RegPwmFif1] = 0
|
|
bcm.pwmMem[bcm2711RegPwmFif1] = 0
|
|
bcm.pwmMem[bcm2711RegPwmFif1] = 0
|
|
bcm.pwmMem[bcm2711RegPwmFif1] = 0
|
|
bcm.pwmMem[bcm2711RegPwmFif1] = 0
|
|
bcm.pwmMem[bcm2711RegPwmFif1] = 0
|
|
// Write top LED data
|
|
bcm.pwmMem[bcm2711RegPwmFif1] = serializePwmDataFrame(bcm.leds[0].Red) << 8
|
|
bcm.pwmMem[bcm2711RegPwmFif1] = serializePwmDataFrame(bcm.leds[0].Green) << 8
|
|
bcm.pwmMem[bcm2711RegPwmFif1] = serializePwmDataFrame(bcm.leds[0].Blue) << 8
|
|
// Write edge LED data
|
|
bcm.pwmMem[bcm2711RegPwmFif1] = serializePwmDataFrame(bcm.leds[1].Red) << 8
|
|
bcm.pwmMem[bcm2711RegPwmFif1] = serializePwmDataFrame(bcm.leds[1].Green) << 8
|
|
bcm.pwmMem[bcm2711RegPwmFif1] = serializePwmDataFrame(bcm.leds[1].Blue) << 8
|
|
// make sure there's >50us of silence
|
|
bcm.pwmMem[bcm2711RegPwmFif1] = 0 // auto-repeated, so no need to feed the FIFO further.
|
|
|
|
bcm.pwmMem[bcm2711RegPwmCtl] = (1 << bcm2711RegPwmCtlBitPwen1) | (1 << bcm2711RegPwmCtlBitMode1) | (1 << bcm2711RegPwmCtlBitRptl1) | (0 << bcm2711RegPwmCtlBitSbit1) | (1 << bcm2711RegPwmCtlBitUsef1)
|
|
// sleep for 4*50us to ensure the data is sent. This is probably a bit too gracious but does not have a significant impact, so let's be safe data gets out.
|
|
time.Sleep(200 * time.Microsecond)
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetTemperature returns the current temperature of the SoC
|
|
func (bcm *bcm2711) GetTemperature() (float64, error) {
|
|
// Read temperature
|
|
|
|
f, err := os.Open(bcm2711ThermalZonePath)
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
raw, err := io.ReadAll(f)
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
|
|
cpuTemp, err := strconv.Atoi(strings.TrimSpace(string(raw)))
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
|
|
temp := float64(cpuTemp) / 1000.0
|
|
socTemperature.Set(temp)
|
|
|
|
return temp, nil
|
|
}
|