tinygo.org/x/drivers@v0.27.1-0.20240509133757-7dbca2a54349/microbitmatrix/microbitmatrix.go (about)

     1  package microbitmatrix // import "tinygo.org/x/drivers/microbitmatrix"
     2  
     3  import (
     4  	"image/color"
     5  	"time"
     6  )
     7  
     8  type Config struct {
     9  	// Rotation of the LED matrix.
    10  	//
    11  	// Valid values:
    12  	//
    13  	//     0: regular orientation, (0 degree rotation)
    14  	//     1: 90 degree rotation clock wise
    15  	//     2: 180 degree rotation clock wise
    16  	//     3: 270 degree rotation clock wise
    17  	Rotation uint8
    18  }
    19  
    20  const (
    21  	RotationNormal = 0
    22  	Rotation90     = 1
    23  	Rotation180    = 2
    24  	Rotation270    = 3
    25  )
    26  
    27  // New returns a new microbitmatrix driver.
    28  func New() Device {
    29  	return Device{}
    30  }
    31  
    32  // Configure sets up the device.
    33  func (d *Device) Configure(cfg Config) {
    34  	d.SetRotation(cfg.Rotation)
    35  
    36  	d.assignPins()
    37  
    38  	d.ClearDisplay()
    39  	d.DisableAll()
    40  }
    41  
    42  // SetRotation changes the rotation of the LED matrix.
    43  //
    44  // Valid values for rotation:
    45  //
    46  //	0: regular orientation, (0 degree rotation)
    47  //	1: 90 degree rotation clock wise
    48  //	2: 180 degree rotation clock wise
    49  //	3: 270 degree rotation clock wise
    50  func (d *Device) SetRotation(rotation uint8) {
    51  	d.rotation = rotation % 4
    52  }
    53  
    54  // Source:
    55  // https://github.com/bbcmicrobit/micropython/blob/1252f887ddc790676bf9314a136bd17650b9c36c/source/microbit/microbitdisplay.cpp#L282
    56  var renderTimings = []time.Duration{
    57  	0,  // Bright, Ticks Duration, Relative power
    58  	2,  //   1,   2,     32µs,     inf
    59  	2,  //   2,   4,     64µs,     200%
    60  	4,  //   3,   8,     128µs,    200%
    61  	7,  //   4,   15,    240µs,    187%
    62  	13, //   5,   28,    448µs,    187%
    63  	25, //   6,   53,    848µs,    189%
    64  	49, //   7,   102,   1632µs,   192%
    65  	97, //   8,   199,   3184µs,   195%
    66  }
    67  
    68  // Source:
    69  // https://github.com/bbcmicrobit/micropython/blob/1252f887ddc790676bf9314a136bd17650b9c36c/source/microbit/microbitdisplay.cpp#L368
    70  const tickDuration = 16 * time.Microsecond
    71  
    72  const (
    73  	rowIdx = 0
    74  	colIdx = 1
    75  )
    76  
    77  // SetPixel modifies the internal buffer in a single pixel.
    78  //
    79  // The alpha channel of the RGBA is used to control the brightness of the LED
    80  // in 9 different levels.
    81  //
    82  //	alpha channel, brightness level
    83  //	  0 -  27, 9 (no transparency = highest brightness)
    84  //	 28 -  55, 8
    85  //	 56 -  83, 7
    86  //	 84 - 111, 6
    87  //	112 - 139, 5
    88  //	140 - 167, 4
    89  //	168 - 195, 3
    90  //	196 - 223, 2
    91  //	224 - 251, 1 (very high transparency = lowest brightness)
    92  //	252 - 255, 0 (full transparency = off)
    93  func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
    94  	if x < 0 || x >= 5 || y < 0 || y >= 5 {
    95  		return
    96  	}
    97  	col := x
    98  	row := y
    99  	if c.R != 0 || c.G != 0 || c.B != 0 {
   100  		d.buffer[matrixRotations[d.rotation][row][col][rowIdx]][matrixRotations[d.rotation][row][col][colIdx]] = brightness(c.A)
   101  	} else {
   102  		d.buffer[matrixRotations[d.rotation][row][col][rowIdx]][matrixRotations[d.rotation][row][col][colIdx]] = 0
   103  	}
   104  }
   105  
   106  const (
   107  	brightnessLevels  = 9
   108  	brightnessDivider = int8(255 / brightnessLevels)
   109  )
   110  
   111  var (
   112  	Brightness0 = color.RGBA{R: 0, G: 0, B: 0, A: 0}
   113  	Brightness1 = color.RGBA{R: 255, G: 255, B: 255, A: 255 - uint8(brightnessDivider)*1}
   114  	Brightness2 = color.RGBA{R: 255, G: 255, B: 255, A: 255 - uint8(brightnessDivider)*2}
   115  	Brightness3 = color.RGBA{R: 255, G: 255, B: 255, A: 255 - uint8(brightnessDivider)*3}
   116  	Brightness4 = color.RGBA{R: 255, G: 255, B: 255, A: 255 - uint8(brightnessDivider)*4}
   117  	Brightness5 = color.RGBA{R: 255, G: 255, B: 255, A: 255 - uint8(brightnessDivider)*5}
   118  	Brightness6 = color.RGBA{R: 255, G: 255, B: 255, A: 255 - uint8(brightnessDivider)*6}
   119  	Brightness7 = color.RGBA{R: 255, G: 255, B: 255, A: 255 - uint8(brightnessDivider)*7}
   120  	Brightness8 = color.RGBA{R: 255, G: 255, B: 255, A: 255 - uint8(brightnessDivider)*8}
   121  	Brightness9 = color.RGBA{R: 255, G: 255, B: 255, A: 255 - uint8(brightnessDivider)*9}
   122  
   123  	BrightnessOff  = Brightness0
   124  	BrightnessFull = Brightness9
   125  )
   126  
   127  func brightness(alpha uint8) int8 {
   128  	return brightnessLevels - int8(alpha/uint8(brightnessDivider))
   129  }
   130  
   131  // GetPixel returns if the specific pixels is enabled.
   132  func (d *Device) GetPixel(x int16, y int16) bool {
   133  	if x < 0 || x >= 5 || y < 0 || y >= 5 {
   134  		return false
   135  	}
   136  	col := x
   137  	row := y
   138  	return d.buffer[matrixRotations[d.rotation][row][col][rowIdx]][matrixRotations[d.rotation][row][col][colIdx]] > 0
   139  }
   140  
   141  const displayRefreshDelay = 8 * time.Millisecond
   142  
   143  // Display sends the buffer (if any) to the screen.
   144  func (d *Device) Display() error {
   145  	var displayBuffer [ledRows][ledCols]int8
   146  	for row := 0; row < ledRows; row++ {
   147  		for col := 0; col < ledCols; col++ {
   148  			displayBuffer[row][col] = d.buffer[row][col]
   149  		}
   150  	}
   151  
   152  	for row := 0; row < ledRows; row++ {
   153  		d.DisableAll()
   154  		d.pin[ledCols+row].High()
   155  
   156  		for col := 0; col < ledCols; col++ {
   157  			if displayBuffer[row][col] > 0 {
   158  				d.pin[col].Low()
   159  			}
   160  		}
   161  
   162  		then := time.Now()
   163  		var offset time.Duration = 0
   164  		for _, ticks := range renderTimings {
   165  			for time.Since(then).Nanoseconds() < int64(ticks*tickDuration+offset) {
   166  				time.Sleep(offset / 10)
   167  			}
   168  			offset += ticks + tickDuration
   169  			for col := 0; col < ledCols; col++ {
   170  				displayBuffer[row][col]--
   171  				if displayBuffer[row][col] <= 0 {
   172  					d.pin[col].High()
   173  				}
   174  			}
   175  		}
   176  	}
   177  	time.Sleep(displayRefreshDelay)
   178  	return nil
   179  }
   180  
   181  // ClearDisplay erases the internal buffer.
   182  func (d *Device) ClearDisplay() {
   183  	for row := 0; row < ledRows; row++ {
   184  		for col := 0; col < ledCols; col++ {
   185  			d.buffer[row][col] = 0
   186  		}
   187  	}
   188  }
   189  
   190  // DisableAll disables all the LEDs without modifying the buffer.
   191  func (d *Device) DisableAll() {
   192  	for i := 0; i < ledCols; i++ {
   193  		d.pin[i].High()
   194  	}
   195  	for i := 0; i < ledRows; i++ {
   196  		d.pin[ledCols+i].Low()
   197  	}
   198  }
   199  
   200  // EnableAll enables all the LEDs without modifying the buffer.
   201  func (d *Device) EnableAll() {
   202  	for i := 0; i < ledCols; i++ {
   203  		d.pin[i].Low()
   204  	}
   205  	for i := 0; i < ledRows; i++ {
   206  		d.pin[ledCols+i].High()
   207  	}
   208  }
   209  
   210  // Size returns the current size of the display.
   211  func (d *Device) Size() (w, h int16) {
   212  	return 5, 5
   213  }