AberLED shield library
Library for the bicolor LED (and TFT screen) shield used in CS12020
AberLED.cpp
Go to the documentation of this file.
1 /**
2  * @file AberLED.cpp
3  * @brief Implementation of the bicolor LED library.
4  * @author Jim Finnis (jcf12@aber.ac.uk)
5  * @version 3.3
6  * @date 6 November 2023
7  * @copyright Aberstwyth University
8  *
9  * This file implements the AberLED class. Stylistically it's somewhat horrific,
10  * using global variables all over the place. This is because (a) it keeps
11  * implementation details (private variables) out of the include file, and (b)
12  * the class is always used as a singleton.
13  */
14 
15 
16 
17 /*
18  * These two libraries are for the ST7735 TFT display. You'll also need the BusIO and Seesaw
19  * libraries installed.
20  */
21 
22 #include "TFT_ST7735.h"
23 #include <SPI.h>
24 
25 #include "AberLED.h"
26 
27 const char *AberLEDClass::version(){
28  // which line of the LED we'll be drawn on
29  // 00000000000000000000011111111
30  return "v3.4 FRIENDLY FROG 05-01-25";
31 }
32 
33 
34 // pin mappings - the actual refresh code uses direct
35 // port manipulation, so YOU MUST CHANGE THAT TOO IF YOU
36 // CHANGE THESE!
37 
38 // pins for the row shift register
39 #define RDATA 2
40 #define RLATCH 3
41 #define RCLOCK 4
42 
43 // pins for the column shift registers
44 #define CDATA 5
45 #define CCLOCK 6
46 #define CLATCH 7
47 
48 int UP, DOWN, LEFT, RIGHT, FIRE;
49 
50 // these are the two buffers.
51 static uint16_t bufferA[8];
52 static uint16_t bufferB[8];
53 
54 // these are pointers to the back and front buffers
55 static uint16_t *backBuffer;
56 static uint16_t *frontBuffer;
57 
58 // button variables
59 static byte buttonStates[] = {0, 0, 0, 0, 0}; // set in swap()
60 static byte debouncedButtonStates[] = {0, 0, 0, 0, 0}; // set in interrupt
61 static byte buttonDebounceCounters[] = {0, 0, 0, 0, 0};
62 static byte buttonWentDown[] = {0, 0, 0, 0, 0};
63 static byte buttonWentDownInLastLoop[] = {0, 0, 0, 0, 0};
64 static int boardRev = -1;
65 
66 int ticks = 0;
67 bool interruptRunning = false;
68 volatile int interruptTicks = 0;
69 bool isTFT;
70 
71 #define MAXTEXTLEN 32
72 // we only render text when it has changed, so we keep the previously rendered text
73 static char prevTxtBuffer[MAXTEXTLEN];
74 static char txtBuffer[MAXTEXTLEN];
75 
77 
78 /*
79  * This is stuff for the TFT - it should be OK to declare and even construct the TFT,
80  * provided you don't initialise it.
81  */
82 
83 
84 
86 
87 
89 {
90  boardRev = rev;
91  if (rev == REV01)
92  {
93  UP = 1;
94  DOWN = 2;
95  LEFT = 4;
96  RIGHT = 3;
97  FIRE = 5;
98  }
99  else
100  {
101  UP = 2;
102  DOWN = 4;
103  LEFT = 1;
104  RIGHT = 3;
105  FIRE = 5;
106  }
107 }
108 
110 {
111  return ticks;
112 }
113 
114 // these are faster routines for doing the shift register writes
115 
116 static void fastShiftOutRows(byte n)
117 {
118  PORTD &= ~(1 << 2); // data off
119  for (int i = 7; i >= 0; i--)
120  {
121  PORTD &= ~(1 << 4); // clock off
122  if (n & (1 << i))
123  PORTD |= 1 << 2; // data on if true
124  else
125  PORTD &= ~(1 << 2); // data off if true
126  PORTD |= 1 << 4; // clock on
127 
128  PORTD &= ~(1 << 2); // data off (to prevent bleed through)
129  }
130  PORTD &= ~(1 << 4); // clock off
131 }
132 
133 static void fastShiftOutCols(uint16_t n)
134 {
135  PORTD &= ~(1 << 5); // data off
136  for (int i = 15; i >= 0; --i)
137  {
138  PORTD &= ~(1 << 6); // clock off
139  if (n & (1 << i))
140  PORTD |= 1 << 5; // data on if true
141  else
142  PORTD &= ~(1 << 5); // data off if true
143  PORTD |= 1 << 6; // clock on
144  PORTD &= ~(1 << 5); // data off (to prevent bleed through)
145  }
146  PORTD &= ~(1 << 6); // clock off
147 }
148 
149 static void setupInterrupt();
150 
151 
152 // this is the colour map for the TFT display. It's an array of 16 bits per colour in a 565 format
153 // (5 bits red, 6 bits green, 5 bits blue). It is initialised with the colours black, green, red
154 // and yellow, but you can change it if you want to by calling AberLED.begin() with a colour map.
155 
156 static uint16_t cols[] = {ST7735_BLACK, ST7735_GREEN, ST7735_RED, ST7735_YELLOW};
157 
158 void AberLEDClass::begin(AberLEDFlags flags, uint8_t *colourMap){
160  isTFT = (flags & AF_TFTDISPLAY) != 0;
161 
162  // set all the shift register pins to output
163  if (isTFT) {
164  // Use this initializer if you're using a 1.44" TFT
165  tft.init();
166  tft.setRotation(2);
167  tft.fillScreen(TFT_BLACK);
168 
169  tft.setCursor(4, 4);
170  tft.setTextColor(TFT_WHITE);
171  tft.setTextWrap(true);
172  tft.print("Arduino LED\n\n");
173  tft.setTextColor(TFT_GREEN);
174  tft.print(version());
175  delay(2000);
176 
177  tft.fillScreen(TFT_BLACK);
178 
179  if(colourMap){
180  for(int i=0;i<4;i++){
181  uint16_t r = (*colourMap++ >> 3) & 0x1f; // chop down frop 8 bits to 5
182  r <<= 11; // put into the right place
183  uint16_t g = (*colourMap++ >> 2) & 0x3f; // chop down frop 8 bits to 6
184  g <<= 5; // put into the right place
185  uint16_t b = (*colourMap++ >> 3) & 0x1f; // chop down frop 8 bits to 5
186  cols[i] = r | g | b;
187  }
188  }
189 
190 
191  } else {
192  // this initialiser is used when using a bicolor LED display
193  pinMode(CLATCH, OUTPUT);
194  pinMode(CDATA, OUTPUT);
195  pinMode(CCLOCK, OUTPUT);
196  pinMode(RLATCH, OUTPUT);
197  pinMode(RDATA, OUTPUT);
198  pinMode(RCLOCK, OUTPUT);
199 
200  // clear the SRs
201 
202  digitalWrite(RLATCH, LOW);
203  fastShiftOutRows(0);
204  digitalWrite(RLATCH, HIGH);
205 
206  digitalWrite(CLATCH, LOW);
207  fastShiftOutCols(0);
208  digitalWrite(CLATCH, HIGH);
209  }
210 
211  // set up the switch inputs
212  pinMode(A0, INPUT_PULLUP);
213  pinMode(A1, INPUT_PULLUP);
214  pinMode(A2, INPUT_PULLUP);
215  pinMode(A3, INPUT_PULLUP);
216  pinMode(9, INPUT_PULLUP);
217  // and the default LED
218  pinMode(13, OUTPUT);
219 
220  // set up the initial buffers and clear them
221 
222  backBuffer = bufferA;
223  frontBuffer = bufferB;
224 
225  memset(backBuffer, 0, 16);
226  memset(frontBuffer, 0, 16);
227 
228  txtBuffer[0] = 0;
229  prevTxtBuffer[0] = 0;
230 
231 
232  if (!(flags & AF_NOINTERRUPT))
233  setupInterrupt();
234 }
235 
236 // render the text to the screen, if it is not the same text as was previously rendered.
237 static void renderText(){
238  if(strcmp(txtBuffer, prevTxtBuffer)){
239  Serial.println(txtBuffer);
240  strcpy(prevTxtBuffer, txtBuffer);
241  tft.fillRect(0, 150, 128, 16, TFT_BLACK);
242  // we display the text in the front buffer
243  if(txtBuffer[0]){
244  tft.setTextColor(TFT_WHITE);
245  tft.setCursor(4, 150);
246  tft.print(txtBuffer);
247  }
248  }
249 }
250 
251 
252 // The user calls this code when they have finished writing to
253 // the back buffer. It swaps the back and front buffer, so that
254 // the newly written buffer becomes the front buffer and is
255 // displayed. This is done by swapping the pointers, not the data.
256 // Interrupts are disabled to avoid the "tearing" effect produced
257 // by the buffers being swapped during redraw.
258 
260 {
261  uint16_t *t;
262 
263  cli();
264  for (int i = 0; i < 5; i++)
265  {
266  buttonWentDownInLastLoop[i] = buttonWentDown[i];
267  buttonWentDown[i] = 0;
268  buttonStates[i] = debouncedButtonStates[i];
269  }
270 
272  interruptTicks = 0;
273 
274  t = frontBuffer;
275  frontBuffer = backBuffer;
276  backBuffer = t;
277 
278  // render text to the screen if it has changed. Will do nothing if not using a TFT.
279  if(isTFT)
280  renderText();
281 
282  sei();
283  if (interruptRunning)
284  {
285  while (interruptTicks < 2)
286  {
287  }
288  }
289 }
290 
292  if(isTFT){
293  cli(); // disable interrupts
294  // clear the text buffer
295  txtBuffer[0] = 0;
296  sei(); // re-enable interrupts
297  }
298 }
299 
300 void AberLEDClass::addToText(const char *s){
301  if(isTFT){
302  cli();
303 
304  // is the length of this string, plus the length of the
305  // current string, plus the null terminator, less than the
306  // maximum length? If so, we can concatenate the strings.
307  if(strlen(txtBuffer) + strlen(s) + 1 < MAXTEXTLEN){
308  strcat(txtBuffer, s);
309  }
310  sei();
311  }
312 }
313 
315  // write the number to a temporary string, taking care not to overflow the buffer.
316  char tmp[16];
317  snprintf(tmp, 16, "%d", n);
318  addToText(tmp);
319 }
320 
321 void AberLEDClass::panic(int n, const char *msg){
322  bool showpattern = true;
323  for(;;){
324  AberLED.clear();
325  if(showpattern){
326  for(int i=0;i<8;i++){
327  AberLED.set(i, 2, RED);
328  AberLED.set(i, 4, RED);
329  if(n & (1 << (7-i))) // least significant bit on the right hand edge
330  AberLED.set(i, 3, YELLOW);
331  }
332  }
333  showpattern = !showpattern;
334  AberLED.clearText();
335  AberLED.addToText(msg);
336  AberLED.swap();
337  delay(300);
338  }
339 }
340 
341 int AberLEDClass::getButton(unsigned char c)
342 {
343  return buttonStates[c - 1];
344 }
345 
346 int AberLEDClass::getButtonDown(unsigned char c)
347 {
348  return buttonWentDownInLastLoop[c - 1];
349 }
350 
351 // The user calls this before writing to the back buffer, to
352 // get its pointer to write to.
353 
355 {
356  return backBuffer;
357 }
358 
359 // set a pixel in the back buffer to a given colour
360 
361 void AberLEDClass::set(int x, int y, unsigned char col)
362 {
363  if (x < 8 && y < 8 && x >= 0 && y >= 0)
364  { // check we're in range
365  uint16_t *p = backBuffer + y; // get row pointer
366  x *= 2; // double x to get the column bit index
367  // clear the bits first
368  *p &= ~(3 << x);
369  // then set the new colour
370  *p |= col << x;
371  }
372 }
373 
374 // sets the entire back buffer to zero
375 
377 {
378  memset(backBuffer, 0, 16);
379 }
380 
381 
382 // called periodically by the interrupt, this writes the front
383 // buffer data out to the screen. The front buffer is the one
384 // which is not being drawn to.
385 
386 static int refrow = 0;
387 inline void refreshNextRow()
388 {
389  if(isTFT) {
390  // using a TFT, so we draw rectangles instead of using the shift registers (which
391  // don't exist on the TFT boards)
392 
393  uint16_t v = *(frontBuffer + refrow); // get row pointer
394 // uint16_t vold = *(backBuffer + refrow); // get row pointer
395 
396  for(int x=0;x<8;x++){
397  uint16_t q = (v >> (x*2)) & 3;
398  tft.fillRect(16*x+2, 16*refrow+2, 12, 12, cols[q]);
399  }
400  } else {
401  // this code is used for the older LED boards, and use the shift registers -
402  // we directly manipulate the port registers for speed.
403 
404  if (!refrow)
405  PORTD |= 1 << 2; // turn on the row data line to get the first bit set
406 
407  // set latches low
408  PORTD &= ~((1 << 3) | (1 << 7));
409 
410  // tick the row clock to move the next bit in (high on
411  // the first row, low after that)
412  PORTD |= (1 << 4);
413  PORTD &= ~(1 << 4);
414 
415  // and turn off the row data line
416  PORTD &= ~(1 << 2);
417 
418  // now the appropriate row is set high, set the column
419  // bits low for the pixels we want.
420 
421  fastShiftOutCols(~(frontBuffer[refrow]));
422  // and latch the registers
423 
424  PORTD |= ((1 << 3) | (1 << 7));
425  }
426 
427  refrow = (refrow + 1) % 8;
428 }
429 
430 // refresh the entire display BY HAND. This IS NOT CALLED BY THE INTERRUPT!!!!
432 {
433  refrow = 0;
434  refreshNextRow();
435  refreshNextRow();
436  refreshNextRow();
437  refreshNextRow();
438  refreshNextRow();
439  refreshNextRow();
440  refreshNextRow();
441  refreshNextRow();
442 
443  if(!isTFT){
444  // hold the last line for a little while
445  for (int volatile i = 0; i < 30; i++)
446  {
447  __asm__ __volatile__("nop\n\t");
448  }
449 
450  // // latch off values into the columns, to avoid last row bright.
451  PORTD &= ~((1 << 3) | (1 << 7));
452  fastShiftOutCols(0xffff);
453  PORTD |= ((1 << 3) | (1 << 7));
454  }
455 }
456 
457 // this is the interrupt service routine for the timer interrupt
458 
459 ISR(TIMER1_COMPA_vect)
460 {
461  interruptTicks++;
462 
463  // draw the next row
464  refreshNextRow();
465 
466  static byte trueButtonStates[] = {0, 0, 0, 0, 0};
467  static byte button = 0;
468  // and process the next button
469 
470  byte bstate;
471  if (boardRev == REV00)
472  {
473  switch (button)
474  {
475  case 0:
476  bstate = PINC & 2;
477  break;
478  case 1:
479  bstate = PINC & 1;
480  break;
481  case 2:
482  bstate = PINC & 4;
483  break;
484  case 3:
485  bstate = PINC & 8;
486  break;
487  case 4:
488  bstate = PINB & 2;
489  break;
490  }
491  }
492  else
493  {
494  switch (button)
495  {
496  case 0:
497  bstate = PINC & 1;
498  break;
499  case 1:
500  bstate = PINC & 8;
501  break;
502  case 2:
503  bstate = PINC & 4;
504  break;
505  case 3:
506  bstate = PINC & 2;
507  break;
508  case 4:
509  bstate = PINB & 2;
510  break;
511  }
512  }
513 
514  bstate = bstate ? 0 : 1; // make booleanesque and invert
515 
516  if (bstate != trueButtonStates[button])
517  {
518  buttonDebounceCounters[button] = 0;
519  if (bstate)
520  buttonWentDown[button] = 1;
521  }
522  else if (++buttonDebounceCounters[button] == 4)
523  {
524  buttonDebounceCounters[button] = 0;
525  debouncedButtonStates[button] = bstate;
526  }
527  trueButtonStates[button] = bstate;
528 
529  button = (button + 1) % 5;
530 }
531 
532 // Set up a 1kHz interrupt handler - the code for the interrupt
533 // is in the TIMER1_COMPA_vect() function.
534 
535 static void setupInterrupt()
536 {
537  cli(); // disable all interrupts
538 
539  // some very hardware-specific code, setting registers
540  // inside the ATMega328p processor.
541 
542  // set timer1 interrupt at 500Hz or slower for the TFT
543  TCCR1A = 0; // set entire TCCR1A register to 0
544  TCCR1B = 0; // same for TCCR1B
545  TCNT1 = 0; // initialize counter value to 0
546  if(isTFT) {
547  // 200 Hz
548  OCR1A = 1249;
549  TCCR1B |= (1 << WGM12);
550  TCCR1B |= (1 << CS11) | (1 << CS10);
551  } else {
552  // set compare match register for correct frequency
553  // (16*10^6) / (500*8) - 1 gives 3999 (for 500Hz)
554  OCR1A = 3999;
555  // turn on CTC mode
556  TCCR1B |= (1 << WGM12);
557  // Set prescaler to 8
558  TCCR1B |= (1 << CS11);
559  }
560  // enable timer compare interrupt
561  TIMSK1 |= (1 << OCIE1A);
562  interruptRunning = true;
563  sei(); // re-enable interrupts
564 }
void clear()
Set all pixels in the back buffer to black.
Definition: AberLED.cpp:376
#define RLATCH
Definition: AberLED.cpp:40
#define ST7735_GREEN
Definition: TFT_ST7735.h:200
#define CDATA
Definition: AberLED.cpp:44
void setRotation(uint8_t r)
Use a TFT screen display (this is the default)
Definition: AberLED.h:53
int UP
the number for button S1, the "up" button
Definition: AberLED.cpp:48
void begin(AberLEDFlags flags=AF_TFTDISPLAY, uint8_t *colourMap=NULL)
Initialises all pin modes, clears the buffers, starts the interrupt and begins to output data to the ...
Definition: AberLED.cpp:158
void fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color)
int getButtonDown(unsigned char c)
Return nonzero if the button has been pressed since the last swap(). It&#39;s better to use the UP...
Definition: AberLED.cpp:346
bool interruptRunning
Definition: AberLED.cpp:67
static void setRevision(int rev)
Call this to set the revision, after AberLED.begin() (which will set it to REV01 by default)...
Definition: AberLED.cpp:88
AberLEDClass AberLED
this is the single instance of the LED class - for documentation see AberLEDClass.
Definition: AberLED.cpp:76
#define RDATA
Definition: AberLED.cpp:39
#define ST7735_YELLOW
Definition: TFT_ST7735.h:204
void clearText()
clear the string which is written to the text area on a TFT display.
Definition: AberLED.cpp:291
int ticks
Definition: AberLED.cpp:66
static void refresh()
Use only when interrupts are disabled - copies the front buffer to the display.
Definition: AberLED.cpp:431
void init(void)
Definition: TFT_ST7735.cpp:210
#define ST7735_BLACK
Definition: TFT_ST7735.h:190
void setCursor(int16_t x, int16_t y)
Definition: TFT_ST7735.cpp:808
The declaration for the AberLED class. Many implementation details are inside the AberLED implementat...
The class for the AberLED shield. One object of this class, called AberLED, is automatically created ...
Definition: AberLED.h:87
#define MAXTEXTLEN
Definition: AberLED.cpp:71
#define TFT_BLACK
Definition: TFT_ST7735.h:169
Do not set up the interrupt. The screen will not be refreshed automatically. You will need to do by c...
Definition: AberLED.h:56
int v
Definition: main.cpp:107
void fillScreen(uint16_t color)
Definition: TFT_ST7735.cpp:647
#define REV00
Definition: AberLED.h:32
ISR(TIMER1_COMPA_vect)
Definition: AberLED.cpp:459
#define RCLOCK
Definition: AberLED.cpp:41
int getButton(unsigned char c)
Return nonzero if a given switch is pressed - switches are numbered 1 to 5, which is against my Golde...
Definition: AberLED.cpp:341
#define TFT_WHITE
Definition: TFT_ST7735.h:184
bool isTFT
Definition: AberLED.cpp:69
static void panic(int n, const char *msg="PANIC")
Display a pattern of dots in binary, flashing on and off in yellow between two red lines...
Definition: AberLED.cpp:321
#define YELLOW
the yellow colour for pixels, used in set()
Definition: AberLED.h:27
#define ST7735_RED
Definition: TFT_ST7735.h:202
#define RED
the red colour for pixels, used in set()
Definition: AberLED.h:25
static const char * version()
return the version string
Definition: AberLED.cpp:27
uint16_t * getBuffer()
Call this to write to the back buffer directly. It returns a pointer to the buffer: a set of 8 16-bit...
Definition: AberLED.cpp:354
void setTextColor(uint16_t color)
Definition: TFT_ST7735.cpp:848
int FIRE
the number for button S5, the "action" or "fire" button
Definition: AberLED.cpp:48
int LEFT
the number for button S3, the "right" button
Definition: AberLED.cpp:48
#define REV01
Definition: AberLED.h:34
void addToText(const char *s)
Append a string to the text area on a TFT display. Does nothing if the display is not a TFT display...
Definition: AberLED.cpp:300
void swap()
Call this code to finish drawing operations. It swaps the back and front buffer, so that the newly wr...
Definition: AberLED.cpp:259
void setTextWrap(boolean wrap)
Definition: TFT_ST7735.cpp:869
volatile int interruptTicks
Definition: AberLED.cpp:68
#define CLATCH
Definition: AberLED.cpp:46
void refreshNextRow()
Definition: AberLED.cpp:387
int DOWN
the number for button S2, the "down" button
Definition: AberLED.cpp:48
void set(int x, int y, unsigned char col)
This sets the given pixel in the back buffer to the given value. The pixel values are 0 (off)...
Definition: AberLED.cpp:361
#define CCLOCK
Definition: AberLED.cpp:45
#define TFT_GREEN
Definition: TFT_ST7735.h:179
int getTicks()
Return the number of interrupt ticks which occurred in the last swap()-swap() cycle.
Definition: AberLED.cpp:109
AberLEDFlags
Definition: AberLED.h:49
TFT_ST7735 tft
Definition: AberLED.cpp:85
int RIGHT
the number for button S4, the "left" button
Definition: AberLED.cpp:48