diff --git a/microbitmatrix/microbitmatrix-v1.go b/microbitmatrix/microbitmatrix-v1.go index f93ad25..b682848 100644 --- a/microbitmatrix/microbitmatrix-v1.go +++ b/microbitmatrix/microbitmatrix-v1.go @@ -52,7 +52,7 @@ const ( type Device struct { pin [ledCols + ledRows]machine.Pin - buffer [ledRows][ledCols]bool + buffer [ledRows][ledCols]int8 rotation uint8 } diff --git a/microbitmatrix/microbitmatrix-v2.go b/microbitmatrix/microbitmatrix-v2.go index dc7fad7..3f18249 100644 --- a/microbitmatrix/microbitmatrix-v2.go +++ b/microbitmatrix/microbitmatrix-v2.go @@ -52,7 +52,7 @@ const ( type Device struct { pin [ledCols + ledRows]machine.Pin - buffer [ledRows][ledCols]bool + buffer [ledRows][ledCols]int8 rotation uint8 } diff --git a/microbitmatrix/microbitmatrix.go b/microbitmatrix/microbitmatrix.go index 748e55a..e9db240 100644 --- a/microbitmatrix/microbitmatrix.go +++ b/microbitmatrix/microbitmatrix.go @@ -43,20 +43,53 @@ func (d *Device) Configure(cfg Config) { // // Valid values for rotation: // -// 0: regular orientation, (0 degree rotation) -// 1: 90 degree rotation clock wise -// 2: 180 degree rotation clock wise -// 3: 270 degree rotation clock wise +// 0: regular orientation, (0 degree rotation) +// 1: 90 degree rotation clock wise +// 2: 180 degree rotation clock wise +// 3: 270 degree rotation clock wise func (d *Device) SetRotation(rotation uint8) { d.rotation = rotation % 4 } +// Source: +// https://github.com/bbcmicrobit/micropython/blob/1252f887ddc790676bf9314a136bd17650b9c36c/source/microbit/microbitdisplay.cpp#L282 +var renderTimings = []time.Duration{ + 0, // Bright, Ticks Duration, Relative power + 2, // 1, 2, 32µs, inf + 2, // 2, 4, 64µs, 200% + 4, // 3, 8, 128µs, 200% + 7, // 4, 15, 240µs, 187% + 13, // 5, 28, 448µs, 187% + 25, // 6, 53, 848µs, 189% + 49, // 7, 102, 1632µs, 192% + 97, // 8, 199, 3184µs, 195% +} + +// Source: +// https://github.com/bbcmicrobit/micropython/blob/1252f887ddc790676bf9314a136bd17650b9c36c/source/microbit/microbitdisplay.cpp#L368 +const tickDuration = 16 * time.Microsecond + const ( rowIdx = 0 colIdx = 1 ) // SetPixel modifies the internal buffer in a single pixel. +// +// The alpha channel of the RGBA is used to control the brightness of the LED +// in 9 different levels. +// +// alpha channel, brightness level +// 0 - 27, 9 (no transparency = highest brightness) +// 28 - 55, 8 +// 56 - 83, 7 +// 84 - 111, 6 +// 112 - 139, 5 +// 140 - 167, 4 +// 168 - 195, 3 +// 196 - 223, 2 +// 224 - 251, 1 (very high transparency = lowest brightness) +// 252 - 255, 0 (full transparency = off) func (d *Device) SetPixel(x int16, y int16, c color.RGBA) { if x < 0 || x >= 5 || y < 0 || y >= 5 { return @@ -64,12 +97,37 @@ func (d *Device) SetPixel(x int16, y int16, c color.RGBA) { col := x row := y if c.R != 0 || c.G != 0 || c.B != 0 { - d.buffer[matrixRotations[d.rotation][row][col][rowIdx]][matrixRotations[d.rotation][row][col][colIdx]] = true + d.buffer[matrixRotations[d.rotation][row][col][rowIdx]][matrixRotations[d.rotation][row][col][colIdx]] = brightness(c.A) } else { - d.buffer[matrixRotations[d.rotation][row][col][rowIdx]][matrixRotations[d.rotation][row][col][colIdx]] = false + d.buffer[matrixRotations[d.rotation][row][col][rowIdx]][matrixRotations[d.rotation][row][col][colIdx]] = 0 } } +const ( + brightnessLevels = 9 + brightnessDivider = int8(255 / brightnessLevels) +) + +var ( + Brightness0 = color.RGBA{R: 0, G: 0, B: 0, A: 0} + Brightness1 = color.RGBA{R: 255, G: 255, B: 255, A: 255 - uint8(brightnessDivider)*1} + Brightness2 = color.RGBA{R: 255, G: 255, B: 255, A: 255 - uint8(brightnessDivider)*2} + Brightness3 = color.RGBA{R: 255, G: 255, B: 255, A: 255 - uint8(brightnessDivider)*3} + Brightness4 = color.RGBA{R: 255, G: 255, B: 255, A: 255 - uint8(brightnessDivider)*4} + Brightness5 = color.RGBA{R: 255, G: 255, B: 255, A: 255 - uint8(brightnessDivider)*5} + Brightness6 = color.RGBA{R: 255, G: 255, B: 255, A: 255 - uint8(brightnessDivider)*6} + Brightness7 = color.RGBA{R: 255, G: 255, B: 255, A: 255 - uint8(brightnessDivider)*7} + Brightness8 = color.RGBA{R: 255, G: 255, B: 255, A: 255 - uint8(brightnessDivider)*8} + Brightness9 = color.RGBA{R: 255, G: 255, B: 255, A: 255 - uint8(brightnessDivider)*9} + + BrightnessOff = Brightness0 + BrightnessFull = Brightness9 +) + +func brightness(alpha uint8) int8 { + return brightnessLevels - int8(alpha/uint8(brightnessDivider)) +} + // GetPixel returns if the specific pixels is enabled. func (d *Device) GetPixel(x int16, y int16) bool { if x < 0 || x >= 5 || y < 0 || y >= 5 { @@ -77,26 +135,46 @@ func (d *Device) GetPixel(x int16, y int16) bool { } col := x row := y - return d.buffer[matrixRotations[d.rotation][row][col][rowIdx]][matrixRotations[d.rotation][row][col][colIdx]] + return d.buffer[matrixRotations[d.rotation][row][col][rowIdx]][matrixRotations[d.rotation][row][col][colIdx]] > 0 } const displayRefreshDelay = 8 * time.Millisecond // Display sends the buffer (if any) to the screen. func (d *Device) Display() error { + var displayBuffer [ledRows][ledCols]int8 + for row := 0; row < ledRows; row++ { + for col := 0; col < ledCols; col++ { + displayBuffer[row][col] = d.buffer[row][col] + } + } + for row := 0; row < ledRows; row++ { d.DisableAll() - d.pin[ledCols+row].Low() + d.pin[ledCols+row].High() for col := 0; col < ledCols; col++ { - if d.buffer[row][col] { - d.pin[col].High() - } else { + if displayBuffer[row][col] > 0 { d.pin[col].Low() } } - time.Sleep(time.Millisecond * 4) + + then := time.Now() + var offset time.Duration = 0 + for _, ticks := range renderTimings { + for time.Since(then).Nanoseconds() < int64(ticks*tickDuration+offset) { + time.Sleep(offset / 10) + } + offset += ticks + tickDuration + for col := 0; col < ledCols; col++ { + displayBuffer[row][col]-- + if displayBuffer[row][col] <= 0 { + d.pin[col].High() + } + } + } } + time.Sleep(displayRefreshDelay) return nil } @@ -104,7 +182,7 @@ func (d *Device) Display() error { func (d *Device) ClearDisplay() { for row := 0; row < ledRows; row++ { for col := 0; col < ledCols; col++ { - d.buffer[row][col] = false + d.buffer[row][col] = 0 } } }