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 (jcf1@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.3 ETERNAL EVENING 16-11-23";
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 int AberLEDClass::getButton(unsigned char c)
322 {
323  return buttonStates[c - 1];
324 }
325 
326 int AberLEDClass::getButtonDown(unsigned char c)
327 {
328  return buttonWentDownInLastLoop[c - 1];
329 }
330 
331 // The user calls this before writing to the back buffer, to
332 // get its pointer to write to.
333 
335 {
336  return backBuffer;
337 }
338 
339 // set a pixel in the back buffer to a given colour
340 
341 void AberLEDClass::set(int x, int y, unsigned char col)
342 {
343  if (x < 8 && y < 8 && x >= 0 && y >= 0)
344  { // check we're in range
345  uint16_t *p = backBuffer + y; // get row pointer
346  x *= 2; // double x to get the column bit index
347  // clear the bits first
348  *p &= ~(3 << x);
349  // then set the new colour
350  *p |= col << x;
351  }
352 }
353 
354 // sets the entire back buffer to zero
355 
357 {
358  memset(backBuffer, 0, 16);
359 }
360 
361 
362 // called periodically by the interrupt, this writes the front
363 // buffer data out to the screen. The front buffer is the one
364 // which is not being drawn to.
365 
366 static int refrow = 0;
367 inline void refreshNextRow()
368 {
369  if(isTFT) {
370  // using a TFT, so we draw rectangles instead of using the shift registers (which
371  // don't exist on the TFT boards)
372 
373  uint16_t v = *(frontBuffer + refrow); // get row pointer
374 // uint16_t vold = *(backBuffer + refrow); // get row pointer
375 
376  for(int x=0;x<8;x++){
377  uint16_t q = (v >> (x*2)) & 3;
378  tft.fillRect(16*x+2, 16*refrow+2, 12, 12, cols[q]);
379  }
380  } else {
381  // this code is used for the older LED boards, and use the shift registers -
382  // we directly manipulate the port registers for speed.
383 
384  if (!refrow)
385  PORTD |= 1 << 2; // turn on the row data line to get the first bit set
386 
387  // set latches low
388  PORTD &= ~((1 << 3) | (1 << 7));
389 
390  // tick the row clock to move the next bit in (high on
391  // the first row, low after that)
392  PORTD |= (1 << 4);
393  PORTD &= ~(1 << 4);
394 
395  // and turn off the row data line
396  PORTD &= ~(1 << 2);
397 
398  // now the appropriate row is set high, set the column
399  // bits low for the pixels we want.
400 
401  fastShiftOutCols(~(frontBuffer[refrow]));
402  // and latch the registers
403 
404  PORTD |= ((1 << 3) | (1 << 7));
405  }
406 
407  refrow = (refrow + 1) % 8;
408 }
409 
410 // refresh the entire display BY HAND. This IS NOT CALLED BY THE INTERRUPT!!!!
412 {
413  refrow = 0;
414  refreshNextRow();
415  refreshNextRow();
416  refreshNextRow();
417  refreshNextRow();
418  refreshNextRow();
419  refreshNextRow();
420  refreshNextRow();
421  refreshNextRow();
422 
423  if(!isTFT){
424  // hold the last line for a little while
425  for (int volatile i = 0; i < 30; i++)
426  {
427  __asm__ __volatile__("nop\n\t");
428  }
429 
430  // // latch off values into the columns, to avoid last row bright.
431  PORTD &= ~((1 << 3) | (1 << 7));
432  fastShiftOutCols(0xffff);
433  PORTD |= ((1 << 3) | (1 << 7));
434  }
435 }
436 
437 // this is the interrupt service routine for the timer interrupt
438 
439 ISR(TIMER1_COMPA_vect)
440 {
441  interruptTicks++;
442 
443  // draw the next row
444  refreshNextRow();
445 
446  static byte trueButtonStates[] = {0, 0, 0, 0, 0};
447  static byte button = 0;
448  // and process the next button
449 
450  byte bstate;
451  if (boardRev == REV00)
452  {
453  switch (button)
454  {
455  case 0:
456  bstate = PINC & 2;
457  break;
458  case 1:
459  bstate = PINC & 1;
460  break;
461  case 2:
462  bstate = PINC & 4;
463  break;
464  case 3:
465  bstate = PINC & 8;
466  break;
467  case 4:
468  bstate = PINB & 2;
469  break;
470  }
471  }
472  else
473  {
474  switch (button)
475  {
476  case 0:
477  bstate = PINC & 1;
478  break;
479  case 1:
480  bstate = PINC & 8;
481  break;
482  case 2:
483  bstate = PINC & 4;
484  break;
485  case 3:
486  bstate = PINC & 2;
487  break;
488  case 4:
489  bstate = PINB & 2;
490  break;
491  }
492  }
493 
494  bstate = bstate ? 0 : 1; // make booleanesque and invert
495 
496  if (bstate != trueButtonStates[button])
497  {
498  buttonDebounceCounters[button] = 0;
499  if (bstate)
500  buttonWentDown[button] = 1;
501  }
502  else if (++buttonDebounceCounters[button] == 4)
503  {
504  buttonDebounceCounters[button] = 0;
505  debouncedButtonStates[button] = bstate;
506  }
507  trueButtonStates[button] = bstate;
508 
509  button = (button + 1) % 5;
510 }
511 
512 // Set up a 1kHz interrupt handler - the code for the interrupt
513 // is in the TIMER1_COMPA_vect() function.
514 
515 static void setupInterrupt()
516 {
517  cli(); // disable all interrupts
518 
519  // some very hardware-specific code, setting registers
520  // inside the ATMega328p processor.
521 
522  // set timer1 interrupt at 500Hz or slower for the TFT
523  TCCR1A = 0; // set entire TCCR1A register to 0
524  TCCR1B = 0; // same for TCCR1B
525  TCNT1 = 0; // initialize counter value to 0
526  if(isTFT) {
527  // 200 Hz
528  OCR1A = 1249;
529  TCCR1B |= (1 << WGM12);
530  TCCR1B |= (1 << CS11) | (1 << CS10);
531  } else {
532  // set compare match register for correct frequency
533  // (16*10^6) / (500*8) - 1 gives 3999 (for 500Hz)
534  OCR1A = 3999;
535  // turn on CTC mode
536  TCCR1B |= (1 << WGM12);
537  // Set prescaler to 8
538  TCCR1B |= (1 << CS11);
539  }
540  // enable timer compare interrupt
541  TIMSK1 |= (1 << OCIE1A);
542  interruptRunning = true;
543  sei(); // re-enable interrupts
544 }
void clear()
Set all pixels in the back buffer to black.
Definition: AberLED.cpp:356
#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:326
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:411
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
void fillScreen(uint16_t color)
Definition: TFT_ST7735.cpp:647
#define REV00
Definition: AberLED.h:32
ISR(TIMER1_COMPA_vect)
Definition: AberLED.cpp:439
#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:321
#define TFT_WHITE
Definition: TFT_ST7735.h:184
bool isTFT
Definition: AberLED.cpp:69
#define ST7735_RED
Definition: TFT_ST7735.h:202
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:334
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:367
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:341
#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