-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Cleaned up and made more object orientated like
- Loading branch information
Jotham Gates
committed
Aug 19, 2021
1 parent
035d81c
commit 966f9d2
Showing
8 changed files
with
20,779 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
# LCDGraph API <!-- omit in toc --> | ||
## Table of Contents <!-- omit in toc --> | ||
- [Including](#including) | ||
- [Constructor](#constructor) | ||
- [Attributes](#attributes) | ||
- [Methods](#methods) | ||
- [`void begin(LiquidCrystal *lcd)`](#void-beginliquidcrystal-lcd) | ||
- [`void add(DataFormat value)`](#void-adddataformat-value) | ||
- [`void clear()`](#void-clear) | ||
- [`void setRegisters()`](#void-setregisters) | ||
- [`void display(uint8_t x, uint8_t y)`](#void-displayuint8_t-x-uint8_t-y) | ||
- [`void autoRescale(bool force0 = false, bool allowSmallerRange = true)`](#void-autorescalebool-force0--false-bool-allowsmallerrange--true) | ||
- [`uint8_t length()`](#uint8_t-length) | ||
- [`void end()`](#void-end) | ||
|
||
# Including | ||
Add these lines to the top of your main project file: | ||
```c++ | ||
// Libraries to include | ||
#include <LiquidCrystal.h> | ||
#include <LCDGraph.h> | ||
``` | ||
|
||
|
||
# Constructor | ||
```c++ | ||
LCDGraph<DataFormat>(uint8_t width, uint8_t height, uint8_t firstRegister) | ||
``` | ||
Initialises the class. | ||
Currently, the height must be 1 as vertical tiling is not yet implemented. | ||
- `DataFormat` is a template and is the data format that will be expected when calling `add` and setting and reading `yMax` and `yMin`. This can be changed to a smaller data type such as a `byte` to save memory if needed or a larger one such as an `unsigned long` or `float` if larger or floating point numbers need to be displayed. Because each data type will require its own copy of the class, it is recommended that if possible, only a single data type is used accross all instances to save program memory. | ||
- `width` is the width in characters. | ||
- `height` is the height in characters on the display, but should be 1 currently. | ||
- `firstRegister` is the first register to use in the display. As there are only 8, they may need to be shared around. | ||
|
||
For example, using 2 4 character wide graphs would be: | ||
```c++ | ||
LCDGraph<int> graph1(4, 1, 0); | ||
LCDGraph<int> graph2(4, 1, 4); | ||
``` | ||
An 8 char wide graph must start at register 0 in the display, a 7 char wide at 0 or 1, ... | ||
# Attributes | ||
| Name | Data Type | Comments | Default | | ||
| ------------ | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----: | | ||
| `yMin` | `DataFormat` (specified in constructor) | The current minimum of the Y axis. Can be written to to set it manually | 0 | | ||
| `yMax` | `DataFormat` (specified in constructor) | The current maximum of the Y axis. Can be written to to set it manually | 255 | | ||
| `filled` | `bool` | Whether everything under the line will be filled in | `true` | | ||
| `showXAxis` | `bool` | Whether to draw the X axis as a solid line. | `true` | | ||
| `showYAxis` | `bool` | Whether to draw the Y axis as a solid line. | `true` | | ||
| `intercepts` | `bool` | If `true`, makes any x and y axis intercepts display as an off pixel when axis are displayed so that points on axis do not disappear. Can be a bit misleading if the x axis is on the top or bottom of the display. | `false` | | ||
# Methods | ||
## `void begin(LiquidCrystal *lcd)` | ||
Gives the library the pointer to the lcd object to use. | ||
For example: | ||
```c++ | ||
// Set up the lcd | ||
lcd.begin(20, 4); | ||
graph.begin(&lcd); | ||
``` | ||
|
||
## `void add(DataFormat value)` | ||
Adds a data point to the internal graph circular buffer. | ||
Chucks out the earlist data point if the buffer is full. | ||
|
||
## `void clear()` | ||
Removes all data from the circular buffer | ||
|
||
## `void setRegisters()` | ||
Generates the custom characters from the data currently in the circular buffer and sends it to the custom character registers in the lcd. If any of the special characters or this graph are already on the display, they will be updated. Otherwise, `display(uint8_t x, uint8_t y);` needs to be called to draw the graph from the registers. | ||
|
||
## `void display(uint8_t x, uint8_t y)` | ||
Displays the graph in the correct location on the display. setRegisters needs to be called beforehand to display the latest data. In all displays I have used, `display` only needs to be called once if the graph is going to be continually be updated in the same location. | ||
|
||
## `void autoRescale(bool force0 = false, bool allowSmallerRange = true)` | ||
Rescales the graph to fit all data. | ||
- `force0` will make sure that 0 is included as either the minimum or maximum if true. 0 will also be included as a limit if there is only a single value. | ||
- `allowSmallerRange` will allow `yMin` to increase and `yMax` to decrease to fit all current data. If false, the range can only expand (`yMin` decrease and `yMax` increase). | ||
|
||
## `uint8_t length()` | ||
Returns the number of points in the circular buffer. | ||
|
||
## `void end()` | ||
Deallocates the internal circular buffer from memory. | ||
It is preferable to only create the instance and use it as global (with `clear()` if necessary) for the entire time the program is running to avoid memory fragmentation. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
/** | ||
* LCDGraph.cpp - Arduino library for drawing graphs on alphanumeric LCDs using custom chars. | ||
* Jotham Gates, 27/11/2020 | ||
*/ | ||
|
||
#include "Arduino.h" | ||
#include "LCDGraph.h" | ||
#include <LiquidCrystal.h> | ||
|
||
LCDGraph::LCDGraph(uint8_t width, uint8_t height, uint8_t firstRegister) { | ||
_width = width; | ||
_height = height; | ||
_firstRegister = firstRegister; | ||
} | ||
|
||
void LCDGraph::begin(LiquidCrystal *lcd) { | ||
_lcd = lcd; | ||
} | ||
|
||
/** Adds a data point to the graph circular buffer. | ||
* Chucks out the earlist data point if the buffer is full. | ||
*/ | ||
void LCDGraph::add(LCDGRAPH_DATA_TYPE value) { | ||
// Calculate the position for the new data point | ||
uint8_t position = _start + length; | ||
if(position >= LCDGRAPH_MAX_DATA) { | ||
position -= LCDGRAPH_MAX_DATA; | ||
} | ||
|
||
// Add the new data point | ||
_data[position] = value; | ||
|
||
// Either increase the length if not full or update the address of the first element to be the next as we will have just overwritten the first element if the buffer is at capacity. | ||
if(length < LCDGRAPH_MAX_DATA) { | ||
length++; | ||
} else { | ||
_start++; | ||
if(_start == LCDGRAPH_MAX_DATA) { // Wrap around if needed | ||
_start = 0; | ||
} | ||
} | ||
} | ||
void LCDGraph::clear() { | ||
length = 0; | ||
} | ||
|
||
void LCDGraph::setRegisters() { | ||
uint8_t accessed = 0; | ||
// For each character to draw | ||
for(uint8_t block = 0; block < _width; block++) { | ||
// Get the data from the stored array and map it to the available pixel height. | ||
uint8_t mappedData[LCDGRAPH_CHAR_WIDTH]; | ||
|
||
// Clear mappedData: | ||
for(uint8_t i = 0; i < LCDGRAPH_CHAR_WIDTH; i++) { | ||
mappedData[i] = 0; | ||
} | ||
|
||
uint8_t printMask = 0x00; // If a bit is 0, do not print anything in its column. | ||
uint8_t startPos = block * LCDGRAPH_CHAR_WIDTH; | ||
|
||
// Load data if available and map it to fit | ||
for(uint8_t i = 0; i < LCDGRAPH_CHAR_WIDTH && accessed < length; i++, accessed++) { | ||
mappedData[i] = map(_atPosition(i + startPos), yMax, yMin, 0, LCDGRAPH_CHAR_HEIGHT-1); | ||
printMask |= 1 << i; // Set the value as printable | ||
} | ||
|
||
// Generate the custom character for that block | ||
uint8_t xAxis = map(0, yMax, yMin, 0, LCDGRAPH_CHAR_HEIGHT-1); | ||
|
||
uint8_t character[LCDGRAPH_CHAR_HEIGHT]; | ||
for(uint8_t row = 0; row < LCDGRAPH_CHAR_HEIGHT; row++) { | ||
character[row] = 0; // Clear the row in case there is something left in memory | ||
// Draw the x axis if needed | ||
if(axis && (row == xAxis)) { | ||
character[row] = 0xff; // Use exclusive or so that any point on the x axis is now light to stand out. | ||
} | ||
// For each column, check if a point should be drawn in the row. | ||
for(uint8_t col = 0; col < LCDGRAPH_CHAR_WIDTH; col++) { | ||
// Draw the y axis if needed | ||
if(axis && !col && !block) { | ||
character[row] |= 1 << (LCDGRAPH_CHAR_WIDTH -1); | ||
} | ||
|
||
// Draw the point on the graph or point under it if shading | ||
if((printMask >> col) & 1) { // If there should be a point drawn on this column | ||
if(mappedData[col] == row) { // An actual point | ||
character[row] ^= 1 << (LCDGRAPH_CHAR_WIDTH -1 -col); // XOR so that any point on an axis appears white. | ||
} else if(filled && mappedData[col] < row) { // The shaded area below | ||
character[row] |= 1 << (LCDGRAPH_CHAR_WIDTH -1 -col); // Normal OR so the axis is not inverted | ||
} | ||
} | ||
} | ||
} | ||
|
||
_lcd->createChar(_firstRegister + block, character); // Send the character to the display. | ||
} | ||
} | ||
|
||
/* Draws the custom characters to the display */ | ||
void LCDGraph::display(uint8_t x, uint8_t y) { | ||
_lcd->setCursor(x, y); | ||
for(uint8_t i = _firstRegister; i < _width + _firstRegister; i++) { | ||
_lcd->write(uint8_t(i)); | ||
} | ||
} | ||
|
||
/** Rescales the graph to fit all data. If force0 is set to true, will make | ||
* sure that 0 is included as either the minimum or maximum. | ||
*/ | ||
void LCDGraph::autoRescale(bool force0) { | ||
// Find the minimum and maximum | ||
LCDGRAPH_DATA_TYPE value = _atPosition(0); | ||
yMin = value; | ||
yMax = value; | ||
for(uint8_t i = 1; i < length; i++) { | ||
LCDGRAPH_DATA_TYPE value = _atPosition(i); | ||
if(value < yMin) { | ||
yMin = value; | ||
} | ||
if(value > yMax) { | ||
yMax = value; | ||
} | ||
} | ||
|
||
// Make sure the x axis is displayed if required. | ||
if(force0 || yMin == yMax) { // yMin == yMax is for if there is only 1 point, include a 0 as another point. | ||
if(yMin > 0) { | ||
yMin = 0; | ||
} | ||
if(yMax < 0) { | ||
yMax = 0; | ||
} | ||
} | ||
} | ||
|
||
/* Translates the position in order into the physical address and returns the value at the address. */ | ||
inline LCDGRAPH_DATA_TYPE LCDGraph::_atPosition(uint8_t position) { | ||
position += _start; | ||
if(position >= LCDGRAPH_MAX_DATA) { | ||
position -= LCDGRAPH_MAX_DATA; | ||
} | ||
|
||
return _data[position]; | ||
} |
Oops, something went wrong.