feat: 全量同步 254 个常用的 Arduino 扩展库文件

This commit is contained in:
yczpf2019
2026-01-24 16:05:38 +08:00
parent c665ba662b
commit 397b9a23a3
6878 changed files with 2732224 additions and 1 deletions

View File

@@ -0,0 +1,480 @@
/*
This is the core graphics library for all our displays, providing a common
set of graphics primitives (points, lines, circles, etc.). It needs to be
paired with a hardware-specific library for each display device we carry
(to handle the lower-level functions).
Adafruit invests time and resources providing this open source code, please
support Adafruit & open-source hardware by purchasing products from Adafruit!
Copyright (c) 2013 Adafruit Industries. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#include "Adafruit_GFX.h"
#include "glcdfont.c"
#ifdef __AVR__
#include <avr/pgmspace.h>
#else
#define pgm_read_byte(addr) (*(const unsigned char *)(addr))
#endif
Adafruit_GFX::Adafruit_GFX(int16_t w, int16_t h):
WIDTH(w), HEIGHT(h)
{
_width = WIDTH;
_height = HEIGHT;
rotation = 0;
cursor_y = cursor_x = 0;
textsize = 1;
textcolor = textbgcolor = 0xFFFF;
wrap = true;
}
// Draw a circle outline
void Adafruit_GFX::drawCircle(int16_t x0, int16_t y0, int16_t r,
uint16_t color) {
int16_t f = 1 - r;
int16_t ddF_x = 1;
int16_t ddF_y = -2 * r;
int16_t x = 0;
int16_t y = r;
drawPixel(x0 , y0+r, color);
drawPixel(x0 , y0-r, color);
drawPixel(x0+r, y0 , color);
drawPixel(x0-r, y0 , color);
while (x<y) {
if (f >= 0) {
y--;
ddF_y += 2;
f += ddF_y;
}
x++;
ddF_x += 2;
f += ddF_x;
drawPixel(x0 + x, y0 + y, color);
drawPixel(x0 - x, y0 + y, color);
drawPixel(x0 + x, y0 - y, color);
drawPixel(x0 - x, y0 - y, color);
drawPixel(x0 + y, y0 + x, color);
drawPixel(x0 - y, y0 + x, color);
drawPixel(x0 + y, y0 - x, color);
drawPixel(x0 - y, y0 - x, color);
}
}
void Adafruit_GFX::drawCircleHelper( int16_t x0, int16_t y0,
int16_t r, uint8_t cornername, uint16_t color) {
int16_t f = 1 - r;
int16_t ddF_x = 1;
int16_t ddF_y = -2 * r;
int16_t x = 0;
int16_t y = r;
while (x<y) {
if (f >= 0) {
y--;
ddF_y += 2;
f += ddF_y;
}
x++;
ddF_x += 2;
f += ddF_x;
if (cornername & 0x4) {
drawPixel(x0 + x, y0 + y, color);
drawPixel(x0 + y, y0 + x, color);
}
if (cornername & 0x2) {
drawPixel(x0 + x, y0 - y, color);
drawPixel(x0 + y, y0 - x, color);
}
if (cornername & 0x8) {
drawPixel(x0 - y, y0 + x, color);
drawPixel(x0 - x, y0 + y, color);
}
if (cornername & 0x1) {
drawPixel(x0 - y, y0 - x, color);
drawPixel(x0 - x, y0 - y, color);
}
}
}
void Adafruit_GFX::fillCircle(int16_t x0, int16_t y0, int16_t r,
uint16_t color) {
drawFastVLine(x0, y0-r, 2*r+1, color);
fillCircleHelper(x0, y0, r, 3, 0, color);
}
// Used to do circles and roundrects
void Adafruit_GFX::fillCircleHelper(int16_t x0, int16_t y0, int16_t r,
uint8_t cornername, int16_t delta, uint16_t color) {
int16_t f = 1 - r;
int16_t ddF_x = 1;
int16_t ddF_y = -2 * r;
int16_t x = 0;
int16_t y = r;
while (x<y) {
if (f >= 0) {
y--;
ddF_y += 2;
f += ddF_y;
}
x++;
ddF_x += 2;
f += ddF_x;
if (cornername & 0x1) {
drawFastVLine(x0+x, y0-y, 2*y+1+delta, color);
drawFastVLine(x0+y, y0-x, 2*x+1+delta, color);
}
if (cornername & 0x2) {
drawFastVLine(x0-x, y0-y, 2*y+1+delta, color);
drawFastVLine(x0-y, y0-x, 2*x+1+delta, color);
}
}
}
// Bresenham's algorithm - thx wikpedia
void Adafruit_GFX::drawLine(int16_t x0, int16_t y0,
int16_t x1, int16_t y1,
uint16_t color) {
int16_t steep = abs(y1 - y0) > abs(x1 - x0);
if (steep) {
swap(x0, y0);
swap(x1, y1);
}
if (x0 > x1) {
swap(x0, x1);
swap(y0, y1);
}
int16_t dx, dy;
dx = x1 - x0;
dy = abs(y1 - y0);
int16_t err = dx / 2;
int16_t ystep;
if (y0 < y1) {
ystep = 1;
} else {
ystep = -1;
}
for (; x0<=x1; x0++) {
if (steep) {
drawPixel(y0, x0, color);
} else {
drawPixel(x0, y0, color);
}
err -= dy;
if (err < 0) {
y0 += ystep;
err += dx;
}
}
}
// Draw a rectangle
void Adafruit_GFX::drawRect(int16_t x, int16_t y,
int16_t w, int16_t h,
uint16_t color) {
drawFastHLine(x, y, w, color);
drawFastHLine(x, y+h-1, w, color);
drawFastVLine(x, y, h, color);
drawFastVLine(x+w-1, y, h, color);
}
void Adafruit_GFX::drawFastVLine(int16_t x, int16_t y,
int16_t h, uint16_t color) {
// Update in subclasses if desired!
drawLine(x, y, x, y+h-1, color);
}
void Adafruit_GFX::drawFastHLine(int16_t x, int16_t y,
int16_t w, uint16_t color) {
// Update in subclasses if desired!
drawLine(x, y, x+w-1, y, color);
}
void Adafruit_GFX::fillRect(int16_t x, int16_t y, int16_t w, int16_t h,
uint16_t color) {
// Update in subclasses if desired!
for (int16_t i=x; i<x+w; i++) {
drawFastVLine(i, y, h, color);
}
}
void Adafruit_GFX::fillScreen(uint16_t color) {
fillRect(0, 0, _width, _height, color);
}
// Draw a rounded rectangle
void Adafruit_GFX::drawRoundRect(int16_t x, int16_t y, int16_t w,
int16_t h, int16_t r, uint16_t color) {
// smarter version
drawFastHLine(x+r , y , w-2*r, color); // Top
drawFastHLine(x+r , y+h-1, w-2*r, color); // Bottom
drawFastVLine(x , y+r , h-2*r, color); // Left
drawFastVLine(x+w-1, y+r , h-2*r, color); // Right
// draw four corners
drawCircleHelper(x+r , y+r , r, 1, color);
drawCircleHelper(x+w-r-1, y+r , r, 2, color);
drawCircleHelper(x+w-r-1, y+h-r-1, r, 4, color);
drawCircleHelper(x+r , y+h-r-1, r, 8, color);
}
// Fill a rounded rectangle
void Adafruit_GFX::fillRoundRect(int16_t x, int16_t y, int16_t w,
int16_t h, int16_t r, uint16_t color) {
// smarter version
fillRect(x+r, y, w-2*r, h, color);
// draw four corners
fillCircleHelper(x+w-r-1, y+r, r, 1, h-2*r-1, color);
fillCircleHelper(x+r , y+r, r, 2, h-2*r-1, color);
}
// Draw a triangle
void Adafruit_GFX::drawTriangle(int16_t x0, int16_t y0,
int16_t x1, int16_t y1,
int16_t x2, int16_t y2, uint16_t color) {
drawLine(x0, y0, x1, y1, color);
drawLine(x1, y1, x2, y2, color);
drawLine(x2, y2, x0, y0, color);
}
// Fill a triangle
void Adafruit_GFX::fillTriangle ( int16_t x0, int16_t y0,
int16_t x1, int16_t y1,
int16_t x2, int16_t y2, uint16_t color) {
int16_t a, b, y, last;
// Sort coordinates by Y order (y2 >= y1 >= y0)
if (y0 > y1) {
swap(y0, y1); swap(x0, x1);
}
if (y1 > y2) {
swap(y2, y1); swap(x2, x1);
}
if (y0 > y1) {
swap(y0, y1); swap(x0, x1);
}
if(y0 == y2) { // Handle awkward all-on-same-line case as its own thing
a = b = x0;
if(x1 < a) a = x1;
else if(x1 > b) b = x1;
if(x2 < a) a = x2;
else if(x2 > b) b = x2;
drawFastHLine(a, y0, b-a+1, color);
return;
}
int16_t
dx01 = x1 - x0,
dy01 = y1 - y0,
dx02 = x2 - x0,
dy02 = y2 - y0,
dx12 = x2 - x1,
dy12 = y2 - y1,
sa = 0,
sb = 0;
// For upper part of triangle, find scanline crossings for segments
// 0-1 and 0-2. If y1=y2 (flat-bottomed triangle), the scanline y1
// is included here (and second loop will be skipped, avoiding a /0
// error there), otherwise scanline y1 is skipped here and handled
// in the second loop...which also avoids a /0 error here if y0=y1
// (flat-topped triangle).
if(y1 == y2) last = y1; // Include y1 scanline
else last = y1-1; // Skip it
for(y=y0; y<=last; y++) {
a = x0 + sa / dy01;
b = x0 + sb / dy02;
sa += dx01;
sb += dx02;
/* longhand:
a = x0 + (x1 - x0) * (y - y0) / (y1 - y0);
b = x0 + (x2 - x0) * (y - y0) / (y2 - y0);
*/
if(a > b) swap(a,b);
drawFastHLine(a, y, b-a+1, color);
}
// For lower part of triangle, find scanline crossings for segments
// 0-2 and 1-2. This loop is skipped if y1=y2.
sa = dx12 * (y - y1);
sb = dx02 * (y - y0);
for(; y<=y2; y++) {
a = x1 + sa / dy12;
b = x0 + sb / dy02;
sa += dx12;
sb += dx02;
/* longhand:
a = x1 + (x2 - x1) * (y - y1) / (y2 - y1);
b = x0 + (x2 - x0) * (y - y0) / (y2 - y0);
*/
if(a > b) swap(a,b);
drawFastHLine(a, y, b-a+1, color);
}
}
void Adafruit_GFX::drawBitmap(int16_t x, int16_t y,
const uint8_t *bitmap, int16_t w, int16_t h,
uint16_t color) {
int16_t i, j, byteWidth = (w + 7) / 8;
for(j=0; j<h; j++) {
for(i=0; i<w; i++ ) {
if(pgm_read_byte(bitmap + j * byteWidth + i / 8) & (128 >> (i & 7))) {
drawPixel(x+i, y+j, color);
}
}
}
}
#if ARDUINO >= 100
size_t Adafruit_GFX::write(uint8_t c) {
#else
void Adafruit_GFX::write(uint8_t c) {
#endif
if (c == '\n') {
cursor_y += textsize*8;
cursor_x = 0;
} else if (c == '\r') {
// skip em
} else {
drawChar(cursor_x, cursor_y, c, textcolor, textbgcolor, textsize);
cursor_x += textsize*6;
if (wrap && (cursor_x > (_width - textsize*6))) {
cursor_y += textsize*8;
cursor_x = 0;
}
}
#if ARDUINO >= 100
return 1;
#endif
}
// Draw a character
void Adafruit_GFX::drawChar(int16_t x, int16_t y, unsigned char c,
uint16_t color, uint16_t bg, uint8_t size) {
if((x >= _width) || // Clip right
(y >= _height) || // Clip bottom
((x + 6 * size - 1) < 0) || // Clip left
((y + 8 * size - 1) < 0)) // Clip top
return;
for (int8_t i=0; i<6; i++ ) {
uint8_t line;
if (i == 5)
line = 0x0;
else
line = pgm_read_byte(font+(c*5)+i);
for (int8_t j = 0; j<8; j++) {
if (line & 0x1) {
if (size == 1) // default size
drawPixel(x+i, y+j, color);
else { // big size
fillRect(x+(i*size), y+(j*size), size, size, color);
}
} else if (bg != color) {
if (size == 1) // default size
drawPixel(x+i, y+j, bg);
else { // big size
fillRect(x+i*size, y+j*size, size, size, bg);
}
}
line >>= 1;
}
}
}
void Adafruit_GFX::setCursor(int16_t x, int16_t y) {
cursor_x = x;
cursor_y = y;
}
void Adafruit_GFX::setTextSize(uint8_t s) {
textsize = (s > 0) ? s : 1;
}
void Adafruit_GFX::setTextColor(uint16_t c) {
// For 'transparent' background, we'll set the bg
// to the same as fg instead of using a flag
textcolor = textbgcolor = c;
}
void Adafruit_GFX::setTextColor(uint16_t c, uint16_t b) {
textcolor = c;
textbgcolor = b;
}
void Adafruit_GFX::setTextWrap(boolean w) {
wrap = w;
}
uint8_t Adafruit_GFX::getRotation(void) {
return rotation;
}
void Adafruit_GFX::setRotation(uint8_t x) {
rotation = (x & 3);
switch(rotation) {
case 0:
case 2:
_width = WIDTH;
_height = HEIGHT;
break;
case 1:
case 3:
_width = HEIGHT;
_height = WIDTH;
break;
}
}
// Return the size of the display (per current rotation)
int16_t Adafruit_GFX::width(void) {
return _width;
}
int16_t Adafruit_GFX::height(void) {
return _height;
}
void Adafruit_GFX::invertDisplay(boolean i) {
// Do nothing, must be subclassed if supported
}

View File

@@ -0,0 +1,87 @@
#ifndef _ADAFRUIT_GFX_H
#define _ADAFRUIT_GFX_H
#if ARDUINO >= 100
#include "Arduino.h"
#include "Print.h"
#else
#include "WProgram.h"
#endif
#define swap(a, b) { int16_t t = a; a = b; b = t; }
class Adafruit_GFX : public Print {
public:
Adafruit_GFX(int16_t w, int16_t h); // Constructor
// This MUST be defined by the subclass:
virtual void drawPixel(int16_t x, int16_t y, uint16_t color) = 0;
// These MAY be overridden by the subclass to provide device-specific
// optimized code. Otherwise 'generic' versions are used.
virtual void
drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t color),
drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color),
drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color),
drawRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color),
fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color),
fillScreen(uint16_t color),
invertDisplay(boolean i);
// These exist only with Adafruit_GFX (no subclass overrides)
void
drawCircle(int16_t x0, int16_t y0, int16_t r, uint16_t color),
drawCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t cornername,
uint16_t color),
fillCircle(int16_t x0, int16_t y0, int16_t r, uint16_t color),
fillCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t cornername,
int16_t delta, uint16_t color),
drawTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1,
int16_t x2, int16_t y2, uint16_t color),
fillTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1,
int16_t x2, int16_t y2, uint16_t color),
drawRoundRect(int16_t x0, int16_t y0, int16_t w, int16_t h,
int16_t radius, uint16_t color),
fillRoundRect(int16_t x0, int16_t y0, int16_t w, int16_t h,
int16_t radius, uint16_t color),
drawBitmap(int16_t x, int16_t y, const uint8_t *bitmap,
int16_t w, int16_t h, uint16_t color),
drawChar(int16_t x, int16_t y, unsigned char c, uint16_t color,
uint16_t bg, uint8_t size),
setCursor(int16_t x, int16_t y),
setTextColor(uint16_t c),
setTextColor(uint16_t c, uint16_t bg),
setTextSize(uint8_t s),
setTextWrap(boolean w),
setRotation(uint8_t r);
#if ARDUINO >= 100
virtual size_t write(uint8_t);
#else
virtual void write(uint8_t);
#endif
int16_t
height(void),
width(void);
uint8_t getRotation(void);
protected:
const int16_t
WIDTH, HEIGHT; // This is the 'raw' display w/h - never changes
int16_t
_width, _height, // Display w/h as modified by current rotation
cursor_x, cursor_y;
uint16_t
textcolor, textbgcolor;
uint8_t
textsize,
rotation;
boolean
wrap; // If set, 'wrap' text at right edge of display
};
#endif // _ADAFRUIT_GFX_H

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,181 @@
/*--------------------------------------------------------------------
This file is part of the Adafruit NeoPixel library.
NeoPixel is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
NeoPixel is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with NeoPixel. If not, see
<http://www.gnu.org/licenses/>.
--------------------------------------------------------------------*/
#ifndef ADAFRUIT_NEOPIXEL_H
#define ADAFRUIT_NEOPIXEL_H
#if (ARDUINO >= 100)
#include <Arduino.h>
#else
#include <WProgram.h>
#include <pins_arduino.h>
#endif
// The order of primary colors in the NeoPixel data stream can vary
// among device types, manufacturers and even different revisions of
// the same item. The third parameter to the Adafruit_NeoPixel
// constructor encodes the per-pixel byte offsets of the red, green
// and blue primaries (plus white, if present) in the data stream --
// the following #defines provide an easier-to-use named version for
// each permutation. e.g. NEO_GRB indicates a NeoPixel-compatible
// device expecting three bytes per pixel, with the first byte
// containing the green value, second containing red and third
// containing blue. The in-memory representation of a chain of
// NeoPixels is the same as the data-stream order; no re-ordering of
// bytes is required when issuing data to the chain.
// Bits 5,4 of this value are the offset (0-3) from the first byte of
// a pixel to the location of the red color byte. Bits 3,2 are the
// green offset and 1,0 are the blue offset. If it is an RGBW-type
// device (supporting a white primary in addition to R,G,B), bits 7,6
// are the offset to the white byte...otherwise, bits 7,6 are set to
// the same value as 5,4 (red) to indicate an RGB (not RGBW) device.
// i.e. binary representation:
// 0bWWRRGGBB for RGBW devices
// 0bRRRRGGBB for RGB
// RGB NeoPixel permutations; white and red offsets are always same
// Offset: W R G B
#define NEO_RGB ((0 << 6) | (0 << 4) | (1 << 2) | (2))
#define NEO_RBG ((0 << 6) | (0 << 4) | (2 << 2) | (1))
#define NEO_GRB ((1 << 6) | (1 << 4) | (0 << 2) | (2))
#define NEO_GBR ((2 << 6) | (2 << 4) | (0 << 2) | (1))
#define NEO_BRG ((1 << 6) | (1 << 4) | (2 << 2) | (0))
#define NEO_BGR ((2 << 6) | (2 << 4) | (1 << 2) | (0))
// RGBW NeoPixel permutations; all 4 offsets are distinct
// Offset: W R G B
#define NEO_WRGB ((0 << 6) | (1 << 4) | (2 << 2) | (3))
#define NEO_WRBG ((0 << 6) | (1 << 4) | (3 << 2) | (2))
#define NEO_WGRB ((0 << 6) | (2 << 4) | (1 << 2) | (3))
#define NEO_WGBR ((0 << 6) | (3 << 4) | (1 << 2) | (2))
#define NEO_WBRG ((0 << 6) | (2 << 4) | (3 << 2) | (1))
#define NEO_WBGR ((0 << 6) | (3 << 4) | (2 << 2) | (1))
#define NEO_RWGB ((1 << 6) | (0 << 4) | (2 << 2) | (3))
#define NEO_RWBG ((1 << 6) | (0 << 4) | (3 << 2) | (2))
#define NEO_RGWB ((2 << 6) | (0 << 4) | (1 << 2) | (3))
#define NEO_RGBW ((3 << 6) | (0 << 4) | (1 << 2) | (2))
#define NEO_RBWG ((2 << 6) | (0 << 4) | (3 << 2) | (1))
#define NEO_RBGW ((3 << 6) | (0 << 4) | (2 << 2) | (1))
#define NEO_GWRB ((1 << 6) | (2 << 4) | (0 << 2) | (3))
#define NEO_GWBR ((1 << 6) | (3 << 4) | (0 << 2) | (2))
#define NEO_GRWB ((2 << 6) | (1 << 4) | (0 << 2) | (3))
#define NEO_GRBW ((3 << 6) | (1 << 4) | (0 << 2) | (2))
#define NEO_GBWR ((2 << 6) | (3 << 4) | (0 << 2) | (1))
#define NEO_GBRW ((3 << 6) | (2 << 4) | (0 << 2) | (1))
#define NEO_BWRG ((1 << 6) | (2 << 4) | (3 << 2) | (0))
#define NEO_BWGR ((1 << 6) | (3 << 4) | (2 << 2) | (0))
#define NEO_BRWG ((2 << 6) | (1 << 4) | (3 << 2) | (0))
#define NEO_BRGW ((3 << 6) | (1 << 4) | (2 << 2) | (0))
#define NEO_BGWR ((2 << 6) | (3 << 4) | (1 << 2) | (0))
#define NEO_BGRW ((3 << 6) | (2 << 4) | (1 << 2) | (0))
// Add NEO_KHZ400 to the color order value to indicate a 400 KHz
// device. All but the earliest v1 NeoPixels expect an 800 KHz data
// stream, this is the default if unspecified. Because flash space
// is very limited on ATtiny devices (e.g. Trinket, Gemma), v1
// NeoPixels aren't handled by default on those chips, though it can
// be enabled by removing the ifndef/endif below -- but code will be
// bigger. Conversely, can disable the NEO_KHZ400 line on other MCUs
// to remove v1 support and save a little space.
#define NEO_KHZ800 0x0000 // 800 KHz datastream
#ifndef __AVR_ATtiny85__
#define NEO_KHZ400 0x0100 // 400 KHz datastream
#endif
// If 400 KHz support is enabled, the third parameter to the constructor
// requires a 16-bit value (in order to select 400 vs 800 KHz speed).
// If only 800 KHz is enabled (as is default on ATtiny), an 8-bit value
// is sufficient to encode pixel color order, saving some space.
#ifdef NEO_KHZ400
typedef uint16_t neoPixelType;
#else
typedef uint8_t neoPixelType;
#endif
class Adafruit_NeoPixel {
public:
// Constructor: number of LEDs, pin number, LED type
Adafruit_NeoPixel(uint16_t n, uint8_t p=6, neoPixelType t=NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel(void);
~Adafruit_NeoPixel();
void
begin(void),
show(void),
setPin(uint8_t p),
setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b),
setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w),
setPixelColor(uint16_t n, uint32_t c),
setBrightness(uint8_t),
clear(),
updateLength(uint16_t n),
updateType(neoPixelType t);
uint8_t
*getPixels(void) const,
getBrightness(void) const;
int8_t
getPin(void) { return pin; };
uint16_t
numPixels(void) const;
static uint32_t
Color(uint8_t r, uint8_t g, uint8_t b),
Color(uint8_t r, uint8_t g, uint8_t b, uint8_t w);
uint32_t
getPixelColor(uint16_t n) const;
inline bool
canShow(void) { return (micros() - endTime) >= 50L; }
private:
boolean
#ifdef NEO_KHZ400 // If 400 KHz NeoPixel support enabled...
is800KHz, // ...true if 800 KHz pixels
#endif
begun; // true if begin() previously called
uint16_t
numLEDs, // Number of RGB LEDs in strip
numBytes; // Size of 'pixels' buffer below (3 or 4 bytes/pixel)
int8_t
pin; // Output pin number (-1 if not yet set)
uint8_t
brightness,
*pixels, // Holds LED color values (3 or 4 bytes each)
rOffset, // Index of red byte within each 3- or 4-byte pixel
gOffset, // Index of green byte
bOffset, // Index of blue byte
wOffset; // Index of white byte (same as rOffset if no white)
uint32_t
endTime; // Latch timing reference
#ifdef __AVR__
volatile uint8_t
*port; // Output PORT register
uint8_t
pinMask; // Output PORT bitmask
#endif
};
#endif // ADAFRUIT_NEOPIXEL_H

View File

@@ -0,0 +1,8 @@
#ifndef _YFROBOT_H_
#define _YFROBOT_H_
#include "Arduino.h"
#include "Adafruit_NeoPixel.h" //for RGB
#include "Adafruit_GFX.h" //for 88点阵
#endif

View File

@@ -0,0 +1,465 @@
#include "PS2X_lib.h"
#include <math.h>
#include <stdio.h>
#include <stdint.h>
#include <avr/io.h>
#if ARDUINO > 22
#include "Arduino.h"
#else
#include "WProgram.h"
#include "pins_arduino.h"
#endif
static byte enter_config[]={0x01,0x43,0x00,0x01,0x00};
static byte set_mode[]={0x01,0x44,0x00,0x01,0xEE,0x00,0x00,0x00,0x00}; // 通过MODE键可更改模式
// static byte set_mode[]={0x01,0x44,0x00,0x01,0x03,0x00,0x00,0x00,0x00}; // 通过MODE键不可更改模式
static byte set_bytes_large[]={0x01,0x4F,0x00,0xFF,0xFF,0x03,0x00,0x00,0x00};
static byte exit_config[]={0x01,0x43,0x00,0x00,0x5A,0x5A,0x5A,0x5A,0x5A};
static byte enable_rumble[]={0x01,0x4D,0x00,0x00,0x01};
static byte type_read[]={0x01,0x45,0x00,0x5A,0x5A,0x5A,0x5A,0x5A,0x5A};
/****************************************************************************************/
boolean PS2X::NewButtonState() {
return ((last_buttons ^ buttons) > 0);
}
/****************************************************************************************/
boolean PS2X::NewButtonState(unsigned int button) {
return (((last_buttons ^ buttons) & button) > 0);
}
/****************************************************************************************/
boolean PS2X::ButtonPressed(unsigned int button) {
return(NewButtonState(button) & Button(button));
}
/****************************************************************************************/
boolean PS2X::ButtonReleased(unsigned int button) {
return((NewButtonState(button)) & ((~last_buttons & button) > 0));
}
/****************************************************************************************/
boolean PS2X::Button(uint16_t button) {
return ((~buttons & button) > 0);
}
/****************************************************************************************/
unsigned int PS2X::ButtonDataByte() {
return (~buttons);
}
/****************************************************************************************/
byte PS2X::Analog(byte button) {
return PS2data[button];
}
/****************************************************************************************/
unsigned char PS2X::_gamepad_shiftinout (char byte) {
unsigned char tmp = 0;
for(unsigned char i=0;i<8;i++) {
if(CHK(byte,i)) CMD_SET();
else CMD_CLR();
CLK_CLR();
delayMicroseconds(CTRL_CLK);
//if(DAT_CHK()) SET(tmp,i);
if(DAT_CHK()) bitSet(tmp,i);
CLK_SET();
#if CTRL_CLK_HIGH
delayMicroseconds(CTRL_CLK_HIGH);
#endif
}
CMD_SET();
delayMicroseconds(CTRL_BYTE_DELAY);
return tmp;
}
/****************************************************************************************/
void PS2X::read_gamepad() {
read_gamepad(false, 0x00);
}
/****************************************************************************************/
boolean PS2X::read_gamepad(boolean motor1, byte motor2) {
double temp = millis() - last_read;
if (temp > 1500) //waited to long
reconfig_gamepad();
if(temp < read_delay) //waited too short
delay(read_delay - temp);
if(motor2 != 0x00)
motor2 = map(motor2,0,255,0x40,0xFF); //noting below 40 will make it spin
char dword[9] = {0x01,0x42,0,motor1,motor2,0,0,0,0};
byte dword2[12] = {0,0,0,0,0,0,0,0,0,0,0,0};
// Try a few times to get valid data...
for (byte RetryCnt = 0; RetryCnt < 5; RetryCnt++) {
CMD_SET();
CLK_SET();
ATT_CLR(); // low enable joystick
delayMicroseconds(CTRL_BYTE_DELAY);
//Send the command to send button and joystick data;
for (int i = 0; i<9; i++) {
PS2data[i] = _gamepad_shiftinout(dword[i]);
}
if(PS2data[1] == 0x79) { //if controller is in full data return mode, get the rest of data
for (int i = 0; i<12; i++) {
PS2data[i+9] = _gamepad_shiftinout(dword2[i]);
}
}
ATT_SET(); // HI disable joystick
// Check to see if we received valid data or not.
// We should be in analog mode for our data to be valid (analog == 0x7_)
if ((PS2data[1] & 0xf0) == 0x70)
break;
// If we got to here, we are not in analog mode, try to recover...
reconfig_gamepad(); // try to get back into Analog mode.
delay(read_delay);
}
// If we get here and still not in analog mode (=0x7_), try increasing the read_delay...
if ((PS2data[1] & 0xf0) != 0x70) {
if (read_delay < 10)
read_delay++; // see if this helps out...
}
#ifdef PS2X_COM_DEBUG
Serial.println("OUT:IN");
for(int i=0; i<9; i++){
Serial.print(dword[i], HEX);
Serial.print(":");
Serial.print(PS2data[i], HEX);
Serial.print(" ");
}
for (int i = 0; i<12; i++) {
Serial.print(dword2[i], HEX);
Serial.print(":");
Serial.print(PS2data[i+9], HEX);
Serial.print(" ");
}
Serial.println("");
#endif
last_buttons = buttons; //store the previous buttons states
#if defined(__AVR__)
buttons = *(uint16_t*)(PS2data+3); //store as one value for multiple functions
#else
buttons = (uint16_t)(PS2data[4] << 8) + PS2data[3]; //store as one value for multiple functions
#endif
last_read = millis();
return ((PS2data[1] & 0xf0) == 0x70); // 1 = OK = analog mode - 0 = NOK
}
/****************************************************************************************/
byte PS2X::config_gamepad(uint8_t clk, uint8_t cmd, uint8_t att, uint8_t dat) {
return config_gamepad(clk, cmd, att, dat, false, false);
}
/****************************************************************************************/
byte PS2X::config_gamepad(uint8_t clk, uint8_t cmd, uint8_t att, uint8_t dat, bool pressures, bool rumble) {
byte temp[sizeof(type_read)];
#ifdef __AVR__
_clk_mask = digitalPinToBitMask(clk);
_clk_oreg = portOutputRegister(digitalPinToPort(clk));
_cmd_mask = digitalPinToBitMask(cmd);
_cmd_oreg = portOutputRegister(digitalPinToPort(cmd));
_att_mask = digitalPinToBitMask(att);
_att_oreg = portOutputRegister(digitalPinToPort(att));
_dat_mask = digitalPinToBitMask(dat);
_dat_ireg = portInputRegister(digitalPinToPort(dat));
#else
uint32_t lport; // Port number for this pin
_clk_mask = digitalPinToBitMask(clk);
lport = digitalPinToPort(clk);
_clk_lport_set = portOutputRegister(lport) + 2;
_clk_lport_clr = portOutputRegister(lport) + 1;
_cmd_mask = digitalPinToBitMask(cmd);
lport = digitalPinToPort(cmd);
_cmd_lport_set = portOutputRegister(lport) + 2;
_cmd_lport_clr = portOutputRegister(lport) + 1;
_att_mask = digitalPinToBitMask(att);
lport = digitalPinToPort(att);
_att_lport_set = portOutputRegister(lport) + 2;
_att_lport_clr = portOutputRegister(lport) + 1;
_dat_mask = digitalPinToBitMask(dat);
_dat_lport = portInputRegister(digitalPinToPort(dat));
#endif
pinMode(clk, OUTPUT); //configure ports
pinMode(att, OUTPUT);
pinMode(cmd, OUTPUT);
pinMode(dat, INPUT);
#if defined(__AVR__)
digitalWrite(dat, HIGH); //enable pull-up
#endif
CMD_SET(); // SET(*_cmd_oreg,_cmd_mask);
CLK_SET();
//new error checking. First, read gamepad a few times to see if it's talking
read_gamepad();
read_gamepad();
//see if it talked - see if mode came back.
//If still anything but 41, 73 or 79, then it's not talking
if(PS2data[1] != 0x41 && PS2data[1] != 0x73 && PS2data[1] != 0x79){
#ifdef PS2X_DEBUG
Serial.println("Controller mode not matched or no controller found");
Serial.print("Expected 0x41, 0x73 or 0x79, but got ");
Serial.println(PS2data[1], HEX);
#endif
return 1; //return error code 1
}
//try setting mode, increasing delays if need be.
read_delay = 1;
for(int y = 0; y <= 10; y++) {
sendCommandString(enter_config, sizeof(enter_config)); //start config run
//read type
delayMicroseconds(CTRL_BYTE_DELAY);
CMD_SET();
CLK_SET();
ATT_CLR(); // low enable joystick
delayMicroseconds(CTRL_BYTE_DELAY);
for (int i = 0; i<9; i++) {
temp[i] = _gamepad_shiftinout(type_read[i]);
}
ATT_SET(); // HI disable joystick
controller_type = temp[3];
sendCommandString(set_mode, sizeof(set_mode));
if(rumble){ sendCommandString(enable_rumble, sizeof(enable_rumble)); en_Rumble = true; }
if(pressures){ sendCommandString(set_bytes_large, sizeof(set_bytes_large)); en_Pressures = true; }
sendCommandString(exit_config, sizeof(exit_config));
read_gamepad();
if(pressures){
if(PS2data[1] == 0x79)
break;
if(PS2data[1] == 0x73)
return 3;
}
if(PS2data[1] == 0x73)
break;
if(y == 10){
#ifdef PS2X_DEBUG
Serial.println("Controller not accepting commands");
Serial.print("mode stil set at");
Serial.println(PS2data[1], HEX);
#endif
return 2; //exit function with error
}
read_delay += 1; //add 1ms to read_delay
}
return 0; //no error if here
}
/****************************************************************************************/
void PS2X::sendCommandString(byte string[], byte len) {
#ifdef PS2X_COM_DEBUG
byte temp[len];
ATT_CLR(); // low enable joystick
delayMicroseconds(CTRL_BYTE_DELAY);
for (int y=0; y < len; y++)
temp[y] = _gamepad_shiftinout(string[y]);
ATT_SET(); //high disable joystick
delay(read_delay); //wait a few
Serial.println("OUT:IN Configure");
for(int i=0; i<len; i++) {
Serial.print(string[i], HEX);
Serial.print(":");
Serial.print(temp[i], HEX);
Serial.print(" ");
}
Serial.println("");
#else
ATT_CLR(); // low enable joystick
for (int y=0; y < len; y++)
_gamepad_shiftinout(string[y]);
ATT_SET(); //high disable joystick
delay(read_delay); //wait a few
#endif
}
/****************************************************************************************/
byte PS2X::readType() {
/*
byte temp[sizeof(type_read)];
sendCommandString(enter_config, sizeof(enter_config));
delayMicroseconds(CTRL_BYTE_DELAY);
CMD_SET();
CLK_SET();
ATT_CLR(); // low enable joystick
delayMicroseconds(CTRL_BYTE_DELAY);
for (int i = 0; i<9; i++) {
temp[i] = _gamepad_shiftinout(type_read[i]);
}
sendCommandString(exit_config, sizeof(exit_config));
if(temp[3] == 0x03)
return 1;
else if(temp[3] == 0x01)
return 2;
return 0;
*/
if(controller_type == 0x03)
return 1;
else if(controller_type == 0x01)
return 2;
else if(controller_type == 0x0C)
return 3; //2.4G Wireless Dual Shock PS2 Game Controller
return 0;
}
/****************************************************************************************/
void PS2X::enableRumble() {
sendCommandString(enter_config, sizeof(enter_config));
sendCommandString(enable_rumble, sizeof(enable_rumble));
sendCommandString(exit_config, sizeof(exit_config));
en_Rumble = true;
}
/****************************************************************************************/
bool PS2X::enablePressures() {
sendCommandString(enter_config, sizeof(enter_config));
sendCommandString(set_bytes_large, sizeof(set_bytes_large));
sendCommandString(exit_config, sizeof(exit_config));
read_gamepad();
read_gamepad();
if(PS2data[1] != 0x79)
return false;
en_Pressures = true;
return true;
}
/****************************************************************************************/
void PS2X::reconfig_gamepad(){
sendCommandString(enter_config, sizeof(enter_config));
sendCommandString(set_mode, sizeof(set_mode));
if (en_Rumble)
sendCommandString(enable_rumble, sizeof(enable_rumble));
if (en_Pressures)
sendCommandString(set_bytes_large, sizeof(set_bytes_large));
sendCommandString(exit_config, sizeof(exit_config));
}
/****************************************************************************************/
#ifdef __AVR__
inline void PS2X::CLK_SET(void) {
register uint8_t old_sreg = SREG;
cli();
*_clk_oreg |= _clk_mask;
SREG = old_sreg;
}
inline void PS2X::CLK_CLR(void) {
register uint8_t old_sreg = SREG;
cli();
*_clk_oreg &= ~_clk_mask;
SREG = old_sreg;
}
inline void PS2X::CMD_SET(void) {
register uint8_t old_sreg = SREG;
cli();
*_cmd_oreg |= _cmd_mask; // SET(*_cmd_oreg,_cmd_mask);
SREG = old_sreg;
}
inline void PS2X::CMD_CLR(void) {
register uint8_t old_sreg = SREG;
cli();
*_cmd_oreg &= ~_cmd_mask; // SET(*_cmd_oreg,_cmd_mask);
SREG = old_sreg;
}
inline void PS2X::ATT_SET(void) {
register uint8_t old_sreg = SREG;
cli();
*_att_oreg |= _att_mask ;
SREG = old_sreg;
}
inline void PS2X::ATT_CLR(void) {
register uint8_t old_sreg = SREG;
cli();
*_att_oreg &= ~_att_mask;
SREG = old_sreg;
}
inline bool PS2X::DAT_CHK(void) {
return (*_dat_ireg & _dat_mask) ? true : false;
}
#else
// On pic32, use the set/clr registers to make them atomic...
inline void PS2X::CLK_SET(void) {
*_clk_lport_set |= _clk_mask;
}
inline void PS2X::CLK_CLR(void) {
*_clk_lport_clr |= _clk_mask;
}
inline void PS2X::CMD_SET(void) {
*_cmd_lport_set |= _cmd_mask;
}
inline void PS2X::CMD_CLR(void) {
*_cmd_lport_clr |= _cmd_mask;
}
inline void PS2X::ATT_SET(void) {
*_att_lport_set |= _att_mask;
}
inline void PS2X::ATT_CLR(void) {
*_att_lport_clr |= _att_mask;
}
inline bool PS2X::DAT_CHK(void) {
return (*_dat_lport & _dat_mask) ? true : false;
}
#endif

View File

@@ -0,0 +1,236 @@
/******************************************************************
* Super amazing PS2 controller Arduino Library v1.8
* details and example sketch:
* http://www.billporter.info/?p=240
*
* Original code by Shutter on Arduino Forums
*
* Revamped, made into lib by and supporting continued development:
* Bill Porter
* www.billporter.info
*
* Contributers:
* Eric Wetzel (thewetzel@gmail.com)
* Kurt Eckhardt
*
* Lib version history
* 0.1 made into library, added analog stick support.
* 0.2 fixed config_gamepad miss-spelling
* added new functions:
* NewButtonState();
* NewButtonState(unsigned int);
* ButtonPressed(unsigned int);
* ButtonReleased(unsigned int);
* removed 'PS' from begining of ever function
* 1.0 found and fixed bug that wasn't configuring controller
* added ability to define pins
* added time checking to reconfigure controller if not polled enough
* Analog sticks and pressures all through 'ps2x.Analog()' function
* added:
* enableRumble();
* enablePressures();
* 1.1
* added some debug stuff for end user. Reports if no controller found
* added auto-increasing sentence delay to see if it helps compatibility.
* 1.2
* found bad math by Shutter for original clock. Was running at 50kHz, not the required 500kHz.
* fixed some of the debug reporting.
* 1.3
* Changed clock back to 50kHz. CuriousInventor says it's suppose to be 500kHz, but doesn't seem to work for everybody.
* 1.4
* Removed redundant functions.
* Fixed mode check to include two other possible modes the controller could be in.
* Added debug code enabled by compiler directives. See below to enable debug mode.
* Added button definitions for shapes as well as colors.
* 1.41
* Some simple bug fixes
* Added Keywords.txt file
* 1.5
* Added proper Guitar Hero compatibility
* Fixed issue with DEBUG mode, had to send serial at once instead of in bits
* 1.6
* Changed config_gamepad() call to include rumble and pressures options
* This was to fix controllers that will only go into config mode once
* Old methods should still work for backwards compatibility
* 1.7
* Integrated Kurt's fixes for the interrupts messing with servo signals
* Reorganized directory so examples show up in Arduino IDE menu
* 1.8
* Added Arduino 1.0 compatibility.
* 1.9
* Kurt - Added detection and recovery from dropping from analog mode, plus
* integreated Chipkit (pic32mx...) support
*
*
*
*This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
<http://www.gnu.org/licenses/>
*
******************************************************************/
// $$$$$$$$$$$$ DEBUG ENABLE SECTION $$$$$$$$$$$$$$$$
// to debug ps2 controller, uncomment these two lines to print out debug to uart
// #define PS2X_DEBUG
// #define PS2X_COM_DEBUG
#ifndef PS2X_lib_h
#define PS2X_lib_h
#if ARDUINO > 22
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
#include <math.h>
#include <stdio.h>
#include <stdint.h>
#ifdef __AVR__
// AVR
#include <avr/io.h>
#define CTRL_CLK 4
#define CTRL_BYTE_DELAY 3
#else
// Pic32...
#include <pins_arduino.h>
#define CTRL_CLK 5
#define CTRL_CLK_HIGH 5
#define CTRL_BYTE_DELAY 4
#endif
//These are our button constants
#define PSB_SELECT 0x0001
#define PSB_L3 0x0002
#define PSB_R3 0x0004
#define PSB_START 0x0008
#define PSB_PAD_UP 0x0010
#define PSB_PAD_RIGHT 0x0020
#define PSB_PAD_DOWN 0x0040
#define PSB_PAD_LEFT 0x0080
#define PSB_L2 0x0100
#define PSB_R2 0x0200
#define PSB_L1 0x0400
#define PSB_R1 0x0800
#define PSB_GREEN 0x1000
#define PSB_RED 0x2000
#define PSB_BLUE 0x4000
#define PSB_PINK 0x8000
#define PSB_TRIANGLE 0x1000
#define PSB_CIRCLE 0x2000
#define PSB_CROSS 0x4000
#define PSB_SQUARE 0x8000
//Guitar button constants
#define UP_STRUM 0x0010
#define DOWN_STRUM 0x0040
#define STAR_POWER 0x0100
#define GREEN_FRET 0x0200
#define YELLOW_FRET 0x1000
#define RED_FRET 0x2000
#define BLUE_FRET 0x4000
#define ORANGE_FRET 0x8000
#define WHAMMY_BAR 8
//These are stick values
#define PSS_RX 5
#define PSS_RY 6
#define PSS_LX 7
#define PSS_LY 8
//These are analog buttons
#define PSAB_PAD_RIGHT 9
#define PSAB_PAD_UP 11
#define PSAB_PAD_DOWN 12
#define PSAB_PAD_LEFT 10
#define PSAB_L2 19
#define PSAB_R2 20
#define PSAB_L1 17
#define PSAB_R1 18
#define PSAB_GREEN 13
#define PSAB_RED 14
#define PSAB_BLUE 15
#define PSAB_PINK 16
#define PSAB_TRIANGLE 13
#define PSAB_CIRCLE 14
#define PSAB_CROSS 15
#define PSAB_SQUARE 16
#define SET(x,y) (x|=(1<<y))
#define CLR(x,y) (x&=(~(1<<y)))
#define CHK(x,y) (x & (1<<y))
#define TOG(x,y) (x^=(1<<y))
class PS2X {
public:
boolean Button(uint16_t); //will be TRUE if button is being pressed
unsigned int ButtonDataByte();
boolean NewButtonState();
boolean NewButtonState(unsigned int); //will be TRUE if button was JUST pressed OR released
boolean ButtonPressed(unsigned int); //will be TRUE if button was JUST pressed
boolean ButtonReleased(unsigned int); //will be TRUE if button was JUST released
void read_gamepad();
boolean read_gamepad(boolean, byte);
byte readType();
byte config_gamepad(uint8_t, uint8_t, uint8_t, uint8_t);
byte config_gamepad(uint8_t, uint8_t, uint8_t, uint8_t, bool, bool);
void enableRumble();
bool enablePressures();
byte Analog(byte);
void reconfig_gamepad();
private:
inline void CLK_SET(void);
inline void CLK_CLR(void);
inline void CMD_SET(void);
inline void CMD_CLR(void);
inline void ATT_SET(void);
inline void ATT_CLR(void);
inline bool DAT_CHK(void);
unsigned char _gamepad_shiftinout (char);
unsigned char PS2data[21];
void sendCommandString(byte*, byte);
unsigned char i;
unsigned int last_buttons;
unsigned int buttons;
#ifdef __AVR__
uint8_t maskToBitNum(uint8_t);
uint8_t _clk_mask;
volatile uint8_t *_clk_oreg;
uint8_t _cmd_mask;
volatile uint8_t *_cmd_oreg;
uint8_t _att_mask;
volatile uint8_t *_att_oreg;
uint8_t _dat_mask;
volatile uint8_t *_dat_ireg;
#else
uint8_t maskToBitNum(uint8_t);
uint16_t _clk_mask;
volatile uint32_t *_clk_lport_set;
volatile uint32_t *_clk_lport_clr;
uint16_t _cmd_mask;
volatile uint32_t *_cmd_lport_set;
volatile uint32_t *_cmd_lport_clr;
uint16_t _att_mask;
volatile uint32_t *_att_lport_set;
volatile uint32_t *_att_lport_clr;
uint16_t _dat_mask;
volatile uint32_t *_dat_lport;
#endif
unsigned long last_read;
byte read_delay;
byte controller_type;
boolean en_Rumble;
boolean en_Pressures;
};
#endif

View File

@@ -0,0 +1,700 @@
#include "QTRSensors.h"
#include <Arduino.h>
void QTRSensors::setTypeRC()
{
_type = QTRType::RC;
_maxValue = _timeout;
}
void QTRSensors::setTypeAnalog()
{
_type = QTRType::Analog;
_maxValue = 1023; // Arduino analogRead() returns a 10-bit value by default
}
void QTRSensors::setSensorPins(const uint8_t * pins, uint8_t sensorCount)
{
if (sensorCount > QTRMaxSensors) { sensorCount = QTRMaxSensors; }
// (Re)allocate and initialize the array if necessary.
uint8_t * oldSensorPins = _sensorPins;
_sensorPins = (uint8_t *)realloc(_sensorPins, sizeof(uint8_t) * sensorCount);
if (_sensorPins == nullptr)
{
// Memory allocation failed; don't continue.
free(oldSensorPins); // deallocate any memory used by old array
return;
}
for (uint8_t i = 0; i < sensorCount; i++)
{
_sensorPins[i] = pins[i];
}
_sensorCount = sensorCount;
// Any previous calibration values are no longer valid, and the calibration
// arrays might need to be reallocated if the sensor count was changed.
calibrationOn.initialized = false;
calibrationOff.initialized = false;
}
void QTRSensors::setTimeout(uint16_t timeout)
{
if (timeout > 32767) { timeout = 32767; }
_timeout = timeout;
if (_type == QTRType::RC) { _maxValue = timeout; }
}
void QTRSensors::setSamplesPerSensor(uint8_t samples)
{
if (samples > 64) { samples = 64; }
_samplesPerSensor = samples;
}
void QTRSensors::setEmitterPin(uint8_t emitterPin)
{
releaseEmitterPins();
_oddEmitterPin = emitterPin;
pinMode(_oddEmitterPin, OUTPUT);
_emitterPinCount = 1;
}
void QTRSensors::setEmitterPins(uint8_t oddEmitterPin, uint8_t evenEmitterPin)
{
releaseEmitterPins();
_oddEmitterPin = oddEmitterPin;
_evenEmitterPin = evenEmitterPin;
pinMode(_oddEmitterPin, OUTPUT);
pinMode(_evenEmitterPin, OUTPUT);
_emitterPinCount = 2;
}
void QTRSensors::releaseEmitterPins()
{
if (_oddEmitterPin != QTRNoEmitterPin)
{
pinMode(_oddEmitterPin, INPUT);
_oddEmitterPin = QTRNoEmitterPin;
}
if (_evenEmitterPin != QTRNoEmitterPin)
{
pinMode(_evenEmitterPin, INPUT);
_evenEmitterPin = QTRNoEmitterPin;
}
_emitterPinCount = 0;
}
void QTRSensors::setDimmingLevel(uint8_t dimmingLevel)
{
if (dimmingLevel > 31) { dimmingLevel = 31; }
_dimmingLevel = dimmingLevel;
}
// emitters defaults to QTREmitters::All; wait defaults to true
void QTRSensors::emittersOff(QTREmitters emitters, bool wait)
{
bool pinChanged = false;
// Use odd emitter pin in these cases:
// - 1 emitter pin, emitters = all
// - 2 emitter pins, emitters = all
// - 2 emitter pins, emitters = odd
if (emitters == QTREmitters::All ||
(_emitterPinCount == 2 && emitters == QTREmitters::Odd))
{
// Check if pin is defined and only turn off if not already off
if ((_oddEmitterPin != QTRNoEmitterPin) &&
(digitalRead(_oddEmitterPin) == HIGH))
{
digitalWrite(_oddEmitterPin, LOW);
pinChanged = true;
}
}
// Use even emitter pin in these cases:
// - 2 emitter pins, emitters = all
// - 2 emitter pins, emitters = even
if (_emitterPinCount == 2 &&
(emitters == QTREmitters::All || emitters == QTREmitters::Even))
{
// Check if pin is defined and only turn off if not already off
if ((_evenEmitterPin != QTRNoEmitterPin) &&
(digitalRead(_evenEmitterPin) == HIGH))
{
digitalWrite(_evenEmitterPin, LOW);
pinChanged = true;
}
}
if (wait && pinChanged)
{
if (_dimmable)
{
// driver min is 1 ms
delayMicroseconds(1200);
}
else
{
delayMicroseconds(200);
}
}
}
void QTRSensors::emittersOn(QTREmitters emitters, bool wait)
{
bool pinChanged = false;
uint16_t emittersOnStart;
// Use odd emitter pin in these cases:
// - 1 emitter pin, emitters = all
// - 2 emitter pins, emitters = all
// - 2 emitter pins, emitters = odd
if (emitters == QTREmitters::All ||
(_emitterPinCount == 2 && emitters == QTREmitters::Odd))
{
// Check if pin is defined, and only turn on non-dimmable sensors if not
// already on, but always turn dimmable sensors off and back on because
// we might be changing the dimming level (emittersOnWithPin() should take
// care of this)
if ((_oddEmitterPin != QTRNoEmitterPin) &&
( _dimmable || (digitalRead(_oddEmitterPin) == LOW)))
{
emittersOnStart = emittersOnWithPin(_oddEmitterPin);
pinChanged = true;
}
}
// Use even emitter pin in these cases:
// - 2 emitter pins, emitters = all
// - 2 emitter pins, emitters = even
if (_emitterPinCount == 2 &&
(emitters == QTREmitters::All || emitters == QTREmitters::Even))
{
// Check if pin is defined, and only turn on non-dimmable sensors if not
// already on, but always turn dimmable sensors off and back on because
// we might be changing the dimming level (emittersOnWithPin() should take
// care of this)
if ((_evenEmitterPin != QTRNoEmitterPin) &&
(_dimmable || (digitalRead(_evenEmitterPin) == LOW)))
{
emittersOnStart = emittersOnWithPin(_evenEmitterPin);
pinChanged = true;
}
}
if (wait && pinChanged)
{
if (_dimmable)
{
// Make sure it's been at least 300 us since the emitter pin was first set
// high before returning. (Driver min is 250 us.) Some time might have
// already passed while we set the dimming level.
while ((uint16_t)(micros() - emittersOnStart) < 300)
{
delayMicroseconds(10);
}
}
else
{
delayMicroseconds(200);
}
}
}
// assumes pin is valid (not QTRNoEmitterPin)
// returns time when pin was first set high (used by emittersSelect())
uint16_t QTRSensors::emittersOnWithPin(uint8_t pin)
{
if (_dimmable && (digitalRead(pin) == HIGH))
{
// We are turning on dimmable emitters that are already on. To avoid messing
// up the dimming level, we have to turn the emitters off and back on. This
// means the turn-off delay will happen even if wait = false was passed to
// emittersOn(). (Driver min is 1 ms.)
digitalWrite(pin, LOW);
delayMicroseconds(1200);
}
digitalWrite(pin, HIGH);
uint16_t emittersOnStart = micros();
if (_dimmable && (_dimmingLevel > 0))
{
noInterrupts();
for (uint8_t i = 0; i < _dimmingLevel; i++)
{
delayMicroseconds(1);
digitalWrite(pin, LOW);
delayMicroseconds(1);
digitalWrite(pin, HIGH);
}
interrupts();
}
return emittersOnStart;
}
void QTRSensors::emittersSelect(QTREmitters emitters)
{
QTREmitters offEmitters;
switch (emitters)
{
case QTREmitters::Odd:
offEmitters = QTREmitters::Even;
break;
case QTREmitters::Even:
offEmitters = QTREmitters::Odd;
break;
case QTREmitters::All:
emittersOn();
return;
case QTREmitters::None:
emittersOff();
return;
default: // invalid
return;
}
// Turn off the off-emitters; don't wait before proceeding, but record the time.
emittersOff(offEmitters, false);
uint16_t turnOffStart = micros();
// Turn on the on-emitters and wait.
emittersOn(emitters);
if (_dimmable)
{
// Finish waiting for the off-emitters emitters to turn off: make sure it's been
// at least 1200 us since the off-emitters was turned off before returning.
// (Driver min is 1 ms.) Some time has already passed while we waited for
// the on-emitters to turn on.
while ((uint16_t)(micros() - turnOffStart) < 1200)
{
delayMicroseconds(10);
}
}
}
void QTRSensors::resetCalibration()
{
for (uint8_t i = 0; i < _sensorCount; i++)
{
if (calibrationOn.maximum) { calibrationOn.maximum[i] = 0; }
if (calibrationOff.maximum) { calibrationOff.maximum[i] = 0; }
if (calibrationOn.minimum) { calibrationOn.minimum[i] = _maxValue; }
if (calibrationOff.minimum) { calibrationOff.minimum[i] = _maxValue; }
}
}
void QTRSensors::calibrate(QTRReadMode mode)
{
// manual emitter control is not supported
if (mode == QTRReadMode::Manual) { return; }
if (mode == QTRReadMode::On ||
mode == QTRReadMode::OnAndOff)
{
calibrateOnOrOff(calibrationOn, QTRReadMode::On);
}
else if (mode == QTRReadMode::OddEven ||
mode == QTRReadMode::OddEvenAndOff)
{
calibrateOnOrOff(calibrationOn, QTRReadMode::OddEven);
}
if (mode == QTRReadMode::OnAndOff ||
mode == QTRReadMode::OddEvenAndOff ||
mode == QTRReadMode::Off)
{
calibrateOnOrOff(calibrationOff, QTRReadMode::Off);
}
}
void QTRSensors::calibrateOnOrOff(CalibrationData & calibration, QTRReadMode mode)
{
uint16_t sensorValues[QTRMaxSensors];
uint16_t maxSensorValues[QTRMaxSensors];
uint16_t minSensorValues[QTRMaxSensors];
// (Re)allocate and initialize the arrays if necessary.
if (!calibration.initialized)
{
uint16_t * oldMaximum = calibration.maximum;
calibration.maximum = (uint16_t *)realloc(calibration.maximum,
sizeof(uint16_t) * _sensorCount);
if (calibration.maximum == nullptr)
{
// Memory allocation failed; don't continue.
free(oldMaximum); // deallocate any memory used by old array
return;
}
uint16_t * oldMinimum = calibration.minimum;
calibration.minimum = (uint16_t *)realloc(calibration.minimum,
sizeof(uint16_t) * _sensorCount);
if (calibration.minimum == nullptr)
{
// Memory allocation failed; don't continue.
free(oldMinimum); // deallocate any memory used by old array
return;
}
// Initialize the max and min calibrated values to values that
// will cause the first reading to update them.
for (uint8_t i = 0; i < _sensorCount; i++)
{
calibration.maximum[i] = 0;
calibration.minimum[i] = _maxValue;
}
calibration.initialized = true;
}
for (uint8_t j = 0; j < 10; j++)
{
read(sensorValues, mode);
for (uint8_t i = 0; i < _sensorCount; i++)
{
// set the max we found THIS time
if ((j == 0) || (sensorValues[i] > maxSensorValues[i]))
{
maxSensorValues[i] = sensorValues[i];
}
// set the min we found THIS time
if ((j == 0) || (sensorValues[i] < minSensorValues[i]))
{
minSensorValues[i] = sensorValues[i];
}
}
}
// record the min and max calibration values
for (uint8_t i = 0; i < _sensorCount; i++)
{
// Update maximum only if the min of 10 readings was still higher than it
// (we got 10 readings in a row higher than the existing maximum).
if (minSensorValues[i] > calibration.maximum[i])
{
calibration.maximum[i] = minSensorValues[i];
}
// Update minimum only if the max of 10 readings was still lower than it
// (we got 10 readings in a row lower than the existing minimum).
if (maxSensorValues[i] < calibration.minimum[i])
{
calibration.minimum[i] = maxSensorValues[i];
}
}
}
void QTRSensors::read(uint16_t * sensorValues, QTRReadMode mode)
{
switch (mode)
{
case QTRReadMode::Off:
emittersOff();
// fall through
case QTRReadMode::Manual:
readPrivate(sensorValues);
return;
case QTRReadMode::On:
case QTRReadMode::OnAndOff:
emittersOn();
readPrivate(sensorValues);
emittersOff();
break;
case QTRReadMode::OddEven:
case QTRReadMode::OddEvenAndOff:
// Turn on odd emitters and read the odd-numbered sensors.
// (readPrivate takes a 0-based array index, so start = 0 to start with
// the first sensor)
emittersSelect(QTREmitters::Odd);
readPrivate(sensorValues, 0, 2);
// Turn on even emitters and read the even-numbered sensors.
// (readPrivate takes a 0-based array index, so start = 1 to start with
// the second sensor)
emittersSelect(QTREmitters::Even);
readPrivate(sensorValues, 1, 2);
emittersOff();
break;
default: // invalid - do nothing
return;
}
if (mode == QTRReadMode::OnAndOff ||
mode == QTRReadMode::OddEvenAndOff)
{
// Take a second set of readings and return the values (on + max - off).
uint16_t offValues[QTRMaxSensors];
readPrivate(offValues);
for (uint8_t i = 0; i < _sensorCount; i++)
{
sensorValues[i] += _maxValue - offValues[i];
if (sensorValues[i] > _maxValue)
{
// This usually doesn't happen, because the sensor reading should
// go up when the emitters are turned off.
sensorValues[i] = _maxValue;
}
}
}
}
void QTRSensors::readCalibrated(uint16_t * sensorValues, QTRReadMode mode)
{
// manual emitter control is not supported
if (mode == QTRReadMode::Manual) { return; }
// if not calibrated, do nothing
if (mode == QTRReadMode::On ||
mode == QTRReadMode::OnAndOff ||
mode == QTRReadMode::OddEvenAndOff)
{
if (!calibrationOn.initialized)
{
return;
}
}
if (mode == QTRReadMode::Off ||
mode == QTRReadMode::OnAndOff ||
mode == QTRReadMode::OddEvenAndOff)
{
if (!calibrationOff.initialized)
{
return;
}
}
// read the needed values
read(sensorValues, mode);
for (uint8_t i = 0; i < _sensorCount; i++)
{
uint16_t calmin, calmax;
// find the correct calibration
if (mode == QTRReadMode::On ||
mode == QTRReadMode::OddEven)
{
calmax = calibrationOn.maximum[i];
calmin = calibrationOn.minimum[i];
}
else if (mode == QTRReadMode::Off)
{
calmax = calibrationOff.maximum[i];
calmin = calibrationOff.minimum[i];
}
else // QTRReadMode::OnAndOff, QTRReadMode::OddEvenAndOff
{
if (calibrationOff.minimum[i] < calibrationOn.minimum[i])
{
// no meaningful signal
calmin = _maxValue;
}
else
{
// this won't go past _maxValue
calmin = calibrationOn.minimum[i] + _maxValue - calibrationOff.minimum[i];
}
if (calibrationOff.maximum[i] < calibrationOn.maximum[i])
{
// no meaningful signal
calmax = _maxValue;
}
else
{
// this won't go past _maxValue
calmax = calibrationOn.maximum[i] + _maxValue - calibrationOff.maximum[i];
}
}
uint16_t denominator = calmax - calmin;
int16_t value = 0;
if (denominator != 0)
{
value = (((int32_t)sensorValues[i]) - calmin) * 1000 / denominator;
}
if (value < 0) { value = 0; }
else if (value > 1000) { value = 1000; }
sensorValues[i] = value;
}
}
// Reads the first of every [step] sensors, starting with [start] (0-indexed, so
// start = 0 means start with the first sensor).
// For example, step = 2, start = 1 means read the *even-numbered* sensors.
// start defaults to 0, step defaults to 1
void QTRSensors::readPrivate(uint16_t * sensorValues, uint8_t start, uint8_t step)
{
if (_sensorPins == nullptr) { return; }
switch (_type)
{
case QTRType::RC:
for (uint8_t i = start; i < _sensorCount; i += step)
{
sensorValues[i] = _maxValue;
// make sensor line an output (drives low briefly, but doesn't matter)
pinMode(_sensorPins[i], OUTPUT);
// drive sensor line high
digitalWrite(_sensorPins[i], HIGH);
}
delayMicroseconds(10); // charge lines for 10 us
{
// disable interrupts so we can switch all the pins as close to the same
// time as possible
noInterrupts();
// record start time before the first sensor is switched to input
// (similarly, time is checked before the first sensor is read in the
// loop below)
uint32_t startTime = micros();
uint16_t time = 0;
for (uint8_t i = start; i < _sensorCount; i += step)
{
// make sensor line an input (should also ensure pull-up is disabled)
pinMode(_sensorPins[i], INPUT);
}
interrupts(); // re-enable
while (time < _maxValue)
{
// disable interrupts so we can read all the pins as close to the same
// time as possible
noInterrupts();
time = micros() - startTime;
for (uint8_t i = start; i < _sensorCount; i += step)
{
if ((digitalRead(_sensorPins[i]) == LOW) && (time < sensorValues[i]))
{
// record the first time the line reads low
sensorValues[i] = time;
}
}
interrupts(); // re-enable
}
}
return;
case QTRType::Analog:
// reset the values
for (uint8_t i = start; i < _sensorCount; i += step)
{
sensorValues[i] = 0;
}
for (uint8_t j = 0; j < _samplesPerSensor; j++)
{
for (uint8_t i = start; i < _sensorCount; i += step)
{
// add the conversion result
sensorValues[i] += analogRead(_sensorPins[i]);
}
}
// get the rounded average of the readings for each sensor
for (uint8_t i = start; i < _sensorCount; i += step)
{
sensorValues[i] = (sensorValues[i] + (_samplesPerSensor >> 1)) /
_samplesPerSensor;
}
return;
default: // QTRType::Undefined or invalid - do nothing
return;
}
}
uint16_t QTRSensors::readLinePrivate(uint16_t * sensorValues, QTRReadMode mode,
bool invertReadings)
{
bool onLine = false;
uint32_t avg = 0; // this is for the weighted total
uint16_t sum = 0; // this is for the denominator, which is <= 64000
// manual emitter control is not supported
if (mode == QTRReadMode::Manual) { return 0; }
readCalibrated(sensorValues, mode);
for (uint8_t i = 0; i < _sensorCount; i++)
{
uint16_t value = sensorValues[i];
if (invertReadings) { value = 1000 - value; }
// keep track of whether we see the line at all
if (value > 200) { onLine = true; }
// only average in values that are above a noise threshold
if (value > 50)
{
avg += (uint32_t)value * (i * 1000);
sum += value;
}
}
if (!onLine)
{
// If it last read to the left of center, return 0.
if (_lastPosition < (_sensorCount - 1) * 1000 / 2)
{
return 0;
}
// If it last read to the right of center, return the max.
else
{
return (_sensorCount - 1) * 1000;
}
}
_lastPosition = avg / sum;
return _lastPosition;
}
// the destructor frees up allocated memory
QTRSensors::~QTRSensors()
{
releaseEmitterPins();
if (_sensorPins) { free(_sensorPins); }
if (calibrationOn.maximum) { free(calibrationOn.maximum); }
if (calibrationOff.maximum) { free(calibrationOff.maximum); }
if (calibrationOn.minimum) { free(calibrationOn.minimum); }
if (calibrationOff.minimum) { free(calibrationOff.minimum); }
}

View File

@@ -0,0 +1,579 @@
/// \file QTRSensors.h
#pragma once
#include <stdint.h>
/// \brief Emitter behavior when taking readings.
///
/// Note that emitter control will only work if you specify a valid emitter pin
/// with setEmitterPin(), and the odd/even modes will only work if you are
/// using a second-generation QTR or QTRX sensor with two emitter control pins
/// and you specify both pins with setEmitterPins().
enum class QTRReadMode : uint8_t {
/// Each reading is made without turning on the infrared (IR) emitters. The
/// reading represents ambient light levels near the sensor.
Off,
/// Each reading is made with the emitters on. The reading is a measure of
/// reflectance.
On,
/// For each sensor, a reading is made in both the on and off states. The
/// value returned is **on + max &minus; off**, where **on** and **off** are
/// the reading with the emitters on and off, respectively, and **max** is
/// the maximum possible sensor reading. This mode can reduce the amount of
/// interference from uneven ambient lighting.
OnAndOff,
/// The odd-numbered sensors are read with the odd-numbered emitters on, then
/// the even-numbered sensors are read with the even-numbered emitters on.
/// This mode can reduce interference between adjacent sensors, especially on
/// QTRX sensor boards. It is only usable with second-generation QTR and QTRX
/// sensor arrays that have two emitter control pins.
OddEven,
/// The odd and even sensors are read separately with the respective emitters
/// on, then all sensors are read with emitters off and **on + max &minus;
/// off** is returned. (In other words, this mode combines OddEven and
/// OnAndOff.)
OddEvenAndOff,
/// Calling read() with this mode prevents it from automatically controlling
/// the emitters: they are left in their existing states, which allows manual
/// control of the emitters for testing and advanced use. Calibrating and
/// obtaining calibrated readings are not supported with this mode.
Manual
};
/// Sensor types.
enum class QTRType : uint8_t {
Undefined,
RC,
Analog
};
/// Emitters selected to turn on or off.
enum class QTREmitters : uint8_t {
All,
Odd,
Even,
None
};
/// Represents an undefined emitter control pin.
const uint8_t QTRNoEmitterPin = 255;
/// Default timeout for RC sensors (in microseconds).
const uint16_t QTRRCDefaultTimeout = 2500;
/// The maximum number of sensors supported by an instance of this class.
const uint8_t QTRMaxSensors = 31;
/// \brief Represents a QTR sensor array.
///
/// An instance of this class represents a QTR sensor array, consisting of one
/// or more sensors of the same type. This could be either a single QTR sensor
/// board or multiple boards controlled as a group.
///
/// See \ref md_usage for an overview of how this library can be used and some
/// example code.
class QTRSensors
{
public:
QTRSensors() = default;
~QTRSensors();
/// \brief Specifies that the sensors are RC.
///
/// Call this function to set up RC-type sensors.
void setTypeRC();
/// \brief Specifies that the sensor type is analog.
///
/// Call this function to set up A-type sensors.
void setTypeAnalog();
/// \brief Returns the type of the sensors.
///
/// \return The sensor type as a member of the ::QTRType enum.
///
/// See also setTypeRC() and setTypeAnalog().
QTRType getType() { return _type; }
/// \brief Sets the sensor pins.
///
/// \param[in] pins A pointer to an array containing the Arduino pins that
/// the sensors are connected to.
///
/// \param sensorCount The number of sensors, which should match the length
/// of the pins array.
///
/// Example usage:
/// ~~~{.cpp}
/// // Set pins for four RC sensors connected to pins 6, 7, A0, and A1.
/// // (Most analog pins can also be used as digital pins.)
/// qtr.setTypeRC();
/// qtr.setSensorPins((const uint8_t[]){6, 7, A0, A1}, 4);
/// ~~~
/// ~~~{.cpp}
/// // Set pins for four analog sensors connected to pins A2, A3, A4, and A5.
/// qtr.setTypeAnalog();
/// qtr.setSensorPins((const uint8_t[]){A2, A3, A4, A5}, 4);
/// ~~~
///
/// If \link CalibrationData calibration data \endlink has already been
/// stored, calling this method will force the storage for the calibration
/// values to be reallocated and reinitialized the next time calibrate() is
/// called (it sets `calibrationOn.initialized` and
/// `calibrationOff.initialized` to false).
void setSensorPins(const uint8_t * pins, uint8_t sensorCount);
/// \brief Sets the timeout for RC sensors.
///
/// \param timeout The length of time, in microseconds, beyond which you
/// consider the sensor reading completely black.
///
/// If the pulse length for a pin exceeds \p timeout, pulse timing will
/// stop and the reading for that pin will be considered full black. It is
/// recommended that you set \p timeout to be between 1000 and 3000
/// &micro;s, depending on factors like the height of your sensors and
/// ambient lighting. This allows you to shorten the duration of a
/// sensor-reading cycle while maintaining useful measurements of
/// reflectance. The default timeout is 2500 &micro;s.
///
/// The maximum allowed timeout is 32767.
/// (This prevents any possibility of an overflow when using
/// QTRReadMode::OnAndOff or QTRReadMode::OddEvenAndOff).
///
/// The timeout setting only applies to RC sensors.
void setTimeout(uint16_t timeout);
/// \brief Returns the timeout for RC sensors.
///
/// \return The RC sensor timeout in microseconds.
///
/// See also setTimeout().
uint16_t getTimeout() { return _timeout; }
/// \brief Sets the number of analog readings to average per analog sensor.
///
/// \param samples The number of 10-bit analog samples (analog-to-digital
/// conversions) to average per sensor each time it is read.
///
/// Increasing \p samples increases noise suppression at the cost of sample
/// rate. The maximum number of samples per sensor is 64; the default is 4.
///
/// The samples per sensor setting only applies to analog sensors.
void setSamplesPerSensor(uint8_t samples);
/// \brief Returns the number of analog readings to average per analog
/// sensor.
///
/// \return The samples per channel for analog sensors.
///
/// See also setSamplesPerSensor().
uint16_t getSamplesPerSensor() { return _samplesPerSensor; }
/// \brief Sets the emitter control pin for the sensors.
///
/// \param emitterPin The Arduino digital pin that controls whether the IR
/// LEDs are on or off.
///
/// Specifying an emitter pin is optional, and the pin is not present on
/// some QTR sensor boards. If a valid pin is connected and specified, the
/// emitters will only be turned on during a reading; otherwise, the IR
/// emitters will always be on. No emitter pin is specified by default.
///
/// With second-generation QTR or QTRX sensor arrays that have two emitter
/// control pins, you can control all of the emitters together by
/// specifying a single emitter pin connected to either the CTRL ODD or
/// CTRL EVEN pin on the sensor board. For independent control of the odd-
/// and even-numbered emitters, see setEmitterPins().
///
/// If you call this function after an emitter pin/pins have already been
/// specified, any existing emitter pins will be released; see also
/// releaseEmitterPins().
void setEmitterPin(uint8_t emitterPin);
/// \brief Sets separate odd and even emitter control pins for the sensors.
///
/// \param oddEmitterPin The Arduino digital pin that controls the
/// odd-numbered IR LEDs.
///
/// \param evenEmitterPin The Arduino digital pin that controls the
/// even-numbered IR LEDs.
///
/// This function only works with second-generation QTR or QTRX sensor
/// arrays that have two emitter control pins. To specify a single emitter
/// pin for all sensors, see setEmitterPin().
///
/// If you call this function after an emitter pin/pins have already been
/// specified, any existing emitter pins will be released; see also
/// releaseEmitterPins().
void setEmitterPins(uint8_t oddEmitterPin, uint8_t evenEmitterPin);
/// \brief Releases emitter pin/pins that have been set.
///
/// This function releases any emitter pins that were previously specified,
/// making them inputs and stopping further control of the emitters through
/// them.
///
/// See also setEmitterPin() and setEmitterPins().
void releaseEmitterPins();
/// \brief Returns the number of emitter control pins in use.
///
/// \return The number of emitter control pins previously specified (1 with
/// setEmitterPin() or 2 with setEmitterPins()). If no emitter pins have
/// been specified (the default), or if previously specified pins were
/// released with releaseEmitterPins(), this function returns 0.
uint8_t getEmitterPinCount() { return _emitterPinCount; }
/// \brief Returns the emitter control pin.
///
/// \return The Arduino digital pin number of the emitter control pin
/// (QTRNoEmitterPin if undefined).
///
/// This function is intended for use when there is a single emitter pin
/// specified; you can use getOddEmitterPin() and getEvenEmitterPin()
/// instead when two are specified.
///
/// See also setEmitterPin().
uint8_t getEmitterPin() { return _oddEmitterPin; }
/// \brief Returns the odd emitter control pin.
///
/// \return The Arduino digital pin number of the odd emitter control pin
/// (QTRNoEmitterPin if undefined).
///
/// This function is intended for use when there are separate odd and even
/// emitter pins specified; you can use getEmitterPin() instead when only
/// one is specified.
///
/// See also getEvenEmitterPin() and setEmitterPins().
uint8_t getOddEmitterPin() { return _oddEmitterPin; }
/// \brief Returns the even emitter control pin.
///
/// \return The Arduino digital pin number of the even emitter control pin
/// (QTRNoEmitterPin if undefined).
///
/// This function is intended for use when there are separate odd and even
/// emitter pins specified; you can use getEmitterPin() instead when only
/// one is specified.
///
/// See also getOddEmitterPin() and setEmitterPins().
uint8_t getEvenEmitterPin() { return _evenEmitterPin; }
/// \brief Specifies that the sensors are dimmable.
///
/// Calling this function is optional when setting up second-generation QTR
/// or QTRX sensors. By default, the library assumes the sensors are
/// dimmable.
///
/// For first-generation QTR sensors, see setNonDimmable().
void setDimmable() { _dimmable = true; }
/// \brief Specifies that the sensors are non-dimmable.
///
/// Call this function to set up first-generation QTR sensors and allow
/// them to be read slightly faster (since their emitters can be turned on
/// and off slightly more quickly than those on dimmable sensors).
///
/// See also setDimmable().
void setNonDimmable() { _dimmable = false; }
/// \brief Returns whether the sensors are dimmable.
///
/// \return True if this object is configured to treat the sensors as
/// dimmable, false otherwise.
///
/// See also setDimmable() and setNonDimmable().
bool getDimmable() { return _dimmable; }
/// \brief Sets the dimming level.
///
/// \param dimmingLevel The dimming level (0 to 31). A dimming level of 0
/// corresponds to full current and brightness, with higher dimming levels
/// meaning lower currents.
///
/// See your sensor board's product page or documentation for details on
/// the relationship of the dimming level to the LED current.
///
/// The dimming level will take effect the next time emittersOn() is called
/// (either from your own program or by one of the library's read methods),
/// and it will be applied again whenever the emitters are turned on after
/// that.
///
/// This setting is only used by dimmable sensors, and an emitter control
/// pin/pins must be connected and defined for dimming to be applied.
void setDimmingLevel(uint8_t dimmingLevel);
/// \brief Returns the dimming level.
///
/// \return The dimming level.
///
/// See also setDimmingLevel().
uint8_t getDimmingLevel() { return _dimmingLevel; }
/// \brief Turns the IR LEDs off.
///
/// \param emitters Which emitters to turn off, as a member of the
/// ::QTREmitters enum. The default is QTREmitters::All.
///
/// \param wait If true (the default), this function delays to give the
/// sensors time to turn off before returning. Otherwise, it returns
/// immediately.
///
/// This function is mainly for use by the read() method. Since read()
/// normally turns the emitters on and off automatically for each reading,
/// calling this function yourself will not affect the readings unless the
/// read mode is QTRReadMode::Manual, which tells read() to leave the
/// emitters alone.
void emittersOff(QTREmitters emitters = QTREmitters::All, bool wait = true);
/// \brief Turns the IR LEDs on.
///
/// \param emitters Which emitters to turn on, as a member of the
/// ::QTREmitters enum. The default is QTREmitters::All.
///
/// \param wait If true (the default), this function delays to give the
/// sensors time to turn on before returning. Otherwise, it returns
/// immediately.
///
/// If the sensors are dimmable and a dimming level is set, this function
/// will apply the dimming level after turning the emitters on.
///
/// This function is mainly for use by the read() method. Since read()
/// normally turns the emitters on and off automatically for each reading,
/// calling this function yourself will not affect the readings unless the
/// read mode is QTRReadMode::Manual, which tells read() to leave the
/// emitters alone.
void emittersOn(QTREmitters emitters = QTREmitters::All, bool wait = true);
/// \brief Turns on the selected emitters and turns off the other emitters
/// with optimized timing.
///
/// \param emitters Which emitters to turn on, as a member of the
/// ::QTREmitters enum. The other emitters will be turned off.
///
/// This function turns on the selected emitters while it waits for the
/// other emitters to turn off. For example,
/// `emittersSelect(QTREmitters::Odd)` turns on the odd-numbered emitters
/// while turning off the even-numbered emitters. Using this method avoids
/// unnecessary delays compared to calling emittersOff() and emittersOn()
/// separately, but it still waits for all emitters to be in the right
/// states before returning.
void emittersSelect(QTREmitters emitters);
/// \brief Reads the sensors for calibration.
///
/// \param mode The emitter behavior during calibration, as a member of the
/// ::QTRReadMode enum. The default is QTRReadMode::On. Manual emitter
/// control with QTRReadMode::Manual is not supported.
///
/// This method reads the sensors 10 times and uses the results for
/// calibration. The sensor values are not returned; instead, the maximum
/// and minimum values found over time are stored in #calibrationOn and/or
/// #calibrationOff for use by the readCalibrated() method.
///
/// If the storage for the calibration values has not been initialized,
/// this function will (re)allocate the arrays and initialize the maximum
/// and minimum values to 0 and the maximum possible sensor reading,
/// respectively, so that the very first calibration sensor reading will
/// update both of them.
///
/// Note that the `minimum` and `maximum` pointers in the CalibrationData
/// structs will point to arrays of length \p sensorCount, as specified in
/// setSensorPins(), and they will only be allocated when calibrate() is
/// called. If you only calibrate with the emitters on, the calibration
/// arrays that hold the off values will not be allocated (and vice versa).
///
/// See \ref md_usage for more information and example code.
void calibrate(QTRReadMode mode = QTRReadMode::On);
/// \brief Resets all calibration that has been done.
void resetCalibration();
/// \brief Reads the raw sensor values into an array.
///
/// \param[out] sensorValues A pointer to an array in which to store the
/// raw sensor readings. There **MUST** be space in the array for as many
/// values as there were sensors specified in setSensorPins().
///
/// \param mode The emitter behavior during the read, as a member of the
/// ::QTRReadMode enum. The default is QTRReadMode::On.
///
/// Example usage:
/// ~~~{.cpp}
/// uint16_t sensorValues[8];
/// qtr.read(sensorValues);
/// ~~~
///
/// The values returned are a measure of the reflectance in abstract units,
/// with higher values corresponding to lower reflectance (e.g. a black
/// surface or a void).
///
/// Analog sensors will return a raw value between 0 and 1023 (like
/// Arduino's `analogRead()` function).
///
/// RC sensors will return a raw value in microseconds between 0 and the
/// timeout setting configured with setTimeout() (the default timeout is
/// 2500 &micro;s).
///
/// See \ref md_usage for more information and example code.
void read(uint16_t * sensorValues, QTRReadMode mode = QTRReadMode::On);
/// \brief Reads the sensors and provides calibrated values between 0 and
/// 1000.
///
/// \param[out] sensorValues A pointer to an array in which to store the
/// calibrated sensor readings. There **MUST** be space in the array for
/// as many values as there were sensors specified in setSensorPins().
///
/// \param mode The emitter behavior during the read, as a member of the
/// ::QTRReadMode enum. The default is QTRReadMode::On. Manual emitter
/// control with QTRReadMode::Manual is not supported.
///
/// 0 corresponds to the minimum value stored in #calibrationOn or
/// #calibrationOff, depending on \p mode, and 1000 corresponds to the
/// maximum value. Calibration values are typically obtained by calling
/// calibrate(), and they are stored separately for each sensor, so that
/// differences in the sensors are accounted for automatically.
///
/// See \ref md_usage for more information and example code.
void readCalibrated(uint16_t * sensorValues, QTRReadMode mode = QTRReadMode::On);
/// \brief Reads the sensors, provides calibrated values, and returns an
/// estimated black line position.
///
/// \param[out] sensorValues A pointer to an array in which to store the
/// calibrated sensor readings. There **MUST** be space in the array for
/// as many values as there were sensors specified in setSensorPins().
///
/// \param mode The emitter behavior during the read, as a member of the
/// ::QTRReadMode enum. The default is QTRReadMode::On. Manual emitter
/// control with QTRReadMode::Manual is not supported.
///
/// \return An estimate of the position of a black line under the sensors.
///
/// The estimate is made using a weighted average of the sensor indices
/// multiplied by 1000, so that a return value of 0 indicates that the line
/// is directly below sensor 0, a return value of 1000 indicates that the
/// line is directly below sensor 1, 2000 indicates that it's below sensor
/// 2000, etc. Intermediate values indicate that the line is between two
/// sensors. The formula is (where \f$v_0\f$ represents the value from the
/// first sensor):
///
/// \f[
/// {(0 \times v_0) + (1000 \times v_1) + (2000 \times v_2) + \cdots
/// \over
/// v_0 + v_1 + v_2 + \cdots}
/// \f]
///
/// As long as your sensors arent spaced too far apart relative to the
/// line, this returned value is designed to be monotonic, which makes it
/// great for use in closed-loop PID control. Additionally, this method
/// remembers where it last saw the line, so if you ever lose the line to
/// the left or the right, its line position will continue to indicate the
/// direction you need to go to reacquire the line. For example, if sensor
/// 4 is your rightmost sensor and you end up completely off the line to
/// the left, this function will continue to return 4000.
///
/// This function is intended to detect a black (or dark-colored) line on a
/// white (or light-colored) background. For a white line, see
/// readLineWhite().
///
/// See \ref md_usage for more information and example code.
uint16_t readLineBlack(uint16_t * sensorValues, QTRReadMode mode = QTRReadMode::On)
{
return readLinePrivate(sensorValues, mode, false);
}
/// \brief Reads the sensors, provides calibrated values, and returns an
/// estimated white line position.
///
/// \param[out] sensorValues A pointer to an array in which to store the
/// calibrated sensor readings. There **MUST** be space in the array for
/// as many values as there were sensors specified in setSensorPins().
///
/// \param mode The emitter behavior during the read, as a member of the
/// ::QTRReadMode enum. The default is QTRReadMode::On. Manual emitter
/// control with QTRReadMode::Manual is not supported.
///
/// \return An estimate of the position of a white line under the sensors.
///
/// This function is intended to detect a white (or light-colored) line on
/// a black (or dark-colored) background. For a black line, see
/// readLineBlack().
///
/// See \ref md_usage for more information and example code.
uint16_t readLineWhite(uint16_t * sensorValues, QTRReadMode mode = QTRReadMode::On)
{
return readLinePrivate(sensorValues, mode, true);
}
/// \brief Stores sensor calibration data.
///
/// See calibrate() and readCalibrated() for details.
struct CalibrationData
{
/// Whether array pointers have been allocated and initialized.
bool initialized = false;
/// Lowest readings seen during calibration.
uint16_t * minimum = nullptr;
/// Highest readings seen during calibration.
uint16_t * maximum = nullptr;
};
/// \name Calibration data
///
/// See calibrate() and readCalibrated() for details.
///
/// These variables are made public so that you can use them for your own
/// calculations and do things like saving the values to EEPROM, performing
/// sanity checking, etc.
/// \{
/// Data from calibrating with emitters on.
CalibrationData calibrationOn;
/// Data from calibrating with emitters off.
CalibrationData calibrationOff;
/// \}
private:
uint16_t emittersOnWithPin(uint8_t pin);
// Handles the actual calibration, including (re)allocating and
// initializing the storage for the calibration values if necessary.
void calibrateOnOrOff(CalibrationData & calibration, QTRReadMode mode);
void readPrivate(uint16_t * sensorValues, uint8_t start = 0, uint8_t step = 1);
uint16_t readLinePrivate(uint16_t * sensorValues, QTRReadMode mode, bool invertReadings);
QTRType _type = QTRType::Undefined;
uint8_t * _sensorPins = nullptr;
uint8_t _sensorCount = 0;
uint16_t _timeout = QTRRCDefaultTimeout; // only used for RC sensors
uint16_t _maxValue = QTRRCDefaultTimeout; // the maximum value returned by readPrivate()
uint8_t _samplesPerSensor = 4; // only used for analog sensors
uint8_t _oddEmitterPin = QTRNoEmitterPin; // also used for single emitter pin
uint8_t _evenEmitterPin = QTRNoEmitterPin;
uint8_t _emitterPinCount = 0;
bool _dimmable = true;
uint8_t _dimmingLevel = 0;
uint16_t _lastPosition = 0;
};

View File

@@ -0,0 +1,218 @@
#ifndef __RTCDS1307_H__
#define __RTCDS1307_H__
#include <Arduino.h>
#include "RtcDateTime.h"
#include "RtcUtility.h"
//I2C Slave Address
const uint8_t DS1307_ADDRESS = 0x68;
//DS1307 Register Addresses
const uint8_t DS1307_REG_TIMEDATE = 0x00;
const uint8_t DS1307_REG_STATUS = 0x00;
const uint8_t DS1307_REG_CONTROL = 0x07;
const uint8_t DS1307_REG_RAMSTART = 0x08;
const uint8_t DS1307_REG_RAMEND = 0x3f;
const uint8_t DS1307_REG_RAMSIZE = DS1307_REG_RAMEND - DS1307_REG_RAMSTART;
//DS1307 Register Data Size if not just 1
const uint8_t DS1307_REG_TIMEDATE_SIZE = 7;
// DS1307 Control Register Bits
const uint8_t DS1307_RS0 = 0;
const uint8_t DS1307_RS1 = 1;
const uint8_t DS1307_SQWE = 4;
const uint8_t DS1307_OUT = 7;
// DS1307 Status Register Bits
const uint8_t DS1307_CH = 7;
enum DS1307SquareWaveOut
{
DS1307SquareWaveOut_1Hz = 0b00010000,
DS1307SquareWaveOut_4kHz = 0b00010001,
DS1307SquareWaveOut_8kHz = 0b00010010,
DS1307SquareWaveOut_32kHz = 0b00010011,
DS1307SquareWaveOut_High = 0b10000000,
DS1307SquareWaveOut_Low = 0b00000000,
};
template<class T_WIRE_METHOD> class RtcDS1307
{
public:
RtcDS1307(T_WIRE_METHOD& wire) :
_wire(wire)
{
}
void Begin()
{
_wire.begin();
}
bool IsDateTimeValid()
{
return GetIsRunning();
}
bool GetIsRunning()
{
uint8_t sreg = getReg(DS1307_REG_STATUS);
return !(sreg & _BV(DS1307_CH));
}
void SetIsRunning(bool isRunning)
{
uint8_t sreg = getReg(DS1307_REG_STATUS);
if (isRunning)
{
sreg &= ~_BV(DS1307_CH);
}
else
{
sreg |= _BV(DS1307_CH);
}
setReg(DS1307_REG_STATUS, sreg);
}
void SetDateTime(const RtcDateTime& dt)
{
// retain running state
uint8_t sreg = getReg(DS1307_REG_STATUS) & _BV(DS1307_CH);
// set the date time
_wire.beginTransmission(DS1307_ADDRESS);
_wire.write(DS1307_REG_TIMEDATE);
_wire.write(Uint8ToBcd(dt.Second()) | sreg);
_wire.write(Uint8ToBcd(dt.Minute()));
_wire.write(Uint8ToBcd(dt.Hour())); // 24 hour mode only
_wire.write(Uint8ToBcd(dt.DayOfWeek()));
_wire.write(Uint8ToBcd(dt.Day()));
_wire.write(Uint8ToBcd(dt.Month()));
_wire.write(Uint8ToBcd(dt.Year() - 2000));
_wire.endTransmission();
}
RtcDateTime GetDateTime()
{
_wire.beginTransmission(DS1307_ADDRESS);
_wire.write(DS1307_REG_TIMEDATE);
_wire.endTransmission();
_wire.requestFrom(DS1307_ADDRESS, DS1307_REG_TIMEDATE_SIZE);
uint8_t second = BcdToUint8(_wire.read() & 0x7F);
uint8_t minute = BcdToUint8(_wire.read());
uint8_t hour = BcdToBin24Hour(_wire.read());
_wire.read(); // throwing away day of week as we calculate it
uint8_t dayOfMonth = BcdToUint8(_wire.read());
uint8_t month = BcdToUint8(_wire.read());
uint16_t year = BcdToUint8(_wire.read()) + 2000;
return RtcDateTime(year, month, dayOfMonth, hour, minute, second);
}
void SetMemory(uint8_t memoryAddress, uint8_t value)
{
uint8_t address = memoryAddress + DS1307_REG_RAMSTART;
if (address <= DS1307_REG_RAMEND)
{
setReg(address, value);
}
}
uint8_t GetMemory(uint8_t memoryAddress)
{
uint8_t value = 0;
uint8_t address = memoryAddress + DS1307_REG_RAMSTART;
if (address <= DS1307_REG_RAMEND)
{
value = getReg(address);
}
return value;
}
uint8_t SetMemory(uint8_t memoryAddress, const uint8_t* pValue, uint8_t countBytes)
{
uint8_t address = memoryAddress + DS1307_REG_RAMSTART;
uint8_t countWritten = 0;
if (address <= DS1307_REG_RAMEND)
{
_wire.beginTransmission(DS1307_ADDRESS);
_wire.write(address);
while (countBytes > 0 && address <= DS1307_REG_RAMEND)
{
_wire.write(*pValue++);
address++;
countBytes--;
countWritten++;
}
_wire.endTransmission();
}
return countWritten;
}
uint8_t GetMemory(uint8_t memoryAddress, uint8_t* pValue, uint8_t countBytes)
{
uint8_t address = memoryAddress + DS1307_REG_RAMSTART;
uint8_t countRead = 0;
if (address <= DS1307_REG_RAMEND)
{
if (countBytes > DS1307_REG_RAMSIZE)
{
countBytes = DS1307_REG_RAMSIZE;
}
_wire.beginTransmission(DS1307_ADDRESS);
_wire.write(address);
_wire.endTransmission();
_wire.requestFrom(DS1307_ADDRESS, countBytes);
while (countBytes-- > 0)
{
*pValue++ = _wire.read();
countRead++;
}
}
return countRead;
}
void SetSquareWavePin(DS1307SquareWaveOut pinMode)
{
setReg(DS1307_REG_CONTROL, pinMode);
}
private:
T_WIRE_METHOD& _wire;
uint8_t getReg(uint8_t regAddress)
{
_wire.beginTransmission(DS1307_ADDRESS);
_wire.write(regAddress);
_wire.endTransmission();
// control register
_wire.requestFrom(DS1307_ADDRESS, (uint8_t)1);
uint8_t regValue = _wire.read();
return regValue;
}
void setReg(uint8_t regAddress, uint8_t regValue)
{
_wire.beginTransmission(DS1307_ADDRESS);
_wire.write(regAddress);
_wire.write(regValue);
_wire.endTransmission();
}
};
#endif // __RTCDS1307_H__

View File

@@ -0,0 +1,528 @@
#ifndef __RTCDS3231_H__
#define __RTCDS3231_H__
#include <Arduino.h>
#include "RtcDateTime.h"
#include "RtcTemperature.h"
#include "RtcUtility.h"
//I2C Slave Address
const uint8_t DS3231_ADDRESS = 0x68;
//DS3231 Register Addresses
const uint8_t DS3231_REG_TIMEDATE = 0x00;
const uint8_t DS3231_REG_ALARMONE = 0x07;
const uint8_t DS3231_REG_ALARMTWO = 0x0B;
const uint8_t DS3231_REG_CONTROL = 0x0E;
const uint8_t DS3231_REG_STATUS = 0x0F;
const uint8_t DS3231_REG_AGING = 0x10;
const uint8_t DS3231_REG_TEMP = 0x11;
//DS3231 Register Data Size if not just 1
const uint8_t DS3231_REG_TIMEDATE_SIZE = 7;
const uint8_t DS3231_REG_ALARMONE_SIZE = 4;
const uint8_t DS3231_REG_ALARMTWO_SIZE = 3;
const uint8_t DS3231_REG_TEMP_SIZE = 2;
// DS3231 Control Register Bits
const uint8_t DS3231_A1IE = 0;
const uint8_t DS3231_A2IE = 1;
const uint8_t DS3231_INTCN = 2;
const uint8_t DS3231_RS1 = 3;
const uint8_t DS3231_RS2 = 4;
const uint8_t DS3231_CONV = 5;
const uint8_t DS3231_BBSQW = 6;
const uint8_t DS3231_EOSC = 7;
const uint8_t DS3231_AIEMASK = (_BV(DS3231_A1IE) | _BV(DS3231_A2IE));
const uint8_t DS3231_RSMASK = (_BV(DS3231_RS1) | _BV(DS3231_RS2));
// DS3231 Status Register Bits
const uint8_t DS3231_A1F = 0;
const uint8_t DS3231_A2F = 1;
const uint8_t DS3231_BSY = 2;
const uint8_t DS3231_EN32KHZ = 3;
const uint8_t DS3231_OSF = 7;
const uint8_t DS3231_AIFMASK = (_BV(DS3231_A1F) | _BV(DS3231_A2F));
// seconds accuracy
enum DS3231AlarmOneControl
{
// bit order: A1M4 DY/DT A1M3 A1M2 A1M1
DS3231AlarmOneControl_HoursMinutesSecondsDayOfMonthMatch = 0x00,
DS3231AlarmOneControl_OncePerSecond = 0x17,
DS3231AlarmOneControl_SecondsMatch = 0x16,
DS3231AlarmOneControl_MinutesSecondsMatch = 0x14,
DS3231AlarmOneControl_HoursMinutesSecondsMatch = 0x10,
DS3231AlarmOneControl_HoursMinutesSecondsDayOfWeekMatch = 0x08,
};
class DS3231AlarmOne
{
public:
DS3231AlarmOne( uint8_t dayOf,
uint8_t hour,
uint8_t minute,
uint8_t second,
DS3231AlarmOneControl controlFlags) :
_flags(controlFlags),
_dayOf(dayOf),
_hour(hour),
_minute(minute),
_second(second)
{
}
uint8_t DayOf() const
{
return _dayOf;
}
uint8_t Hour() const
{
return _hour;
}
uint8_t Minute() const
{
return _minute;
}
uint8_t Second() const
{
return _second;
}
DS3231AlarmOneControl ControlFlags() const
{
return _flags;
}
bool operator == (const DS3231AlarmOne& other) const
{
return (_dayOf == other._dayOf &&
_hour == other._hour &&
_minute == other._minute &&
_second == other._second &&
_flags == other._flags);
}
bool operator != (const DS3231AlarmOne& other) const
{
return !(*this == other);
}
protected:
DS3231AlarmOneControl _flags;
uint8_t _dayOf;
uint8_t _hour;
uint8_t _minute;
uint8_t _second;
};
// minutes accuracy
enum DS3231AlarmTwoControl
{
// bit order: A2M4 DY/DT A2M3 A2M2
DS3231AlarmTwoControl_HoursMinutesDayOfMonthMatch = 0x00,
DS3231AlarmTwoControl_OncePerMinute = 0x0b,
DS3231AlarmTwoControl_MinutesMatch = 0x0a,
DS3231AlarmTwoControl_HoursMinutesMatch = 0x08,
DS3231AlarmTwoControl_HoursMinutesDayOfWeekMatch = 0x04,
};
class DS3231AlarmTwo
{
public:
DS3231AlarmTwo( uint8_t dayOf,
uint8_t hour,
uint8_t minute,
DS3231AlarmTwoControl controlFlags) :
_flags(controlFlags),
_dayOf(dayOf),
_hour(hour),
_minute(minute)
{
}
uint8_t DayOf() const
{
return _dayOf;
}
uint8_t Hour() const
{
return _hour;
}
uint8_t Minute() const
{
return _minute;
}
DS3231AlarmTwoControl ControlFlags() const
{
return _flags;
}
bool operator == (const DS3231AlarmTwo& other) const
{
return (_dayOf == other._dayOf &&
_hour == other._hour &&
_minute == other._minute &&
_flags == other._flags);
}
bool operator != (const DS3231AlarmTwo& other) const
{
return !(*this == other);
}
protected:
DS3231AlarmTwoControl _flags;
uint8_t _dayOf;
uint8_t _hour;
uint8_t _minute;
};
enum DS3231SquareWaveClock
{
DS3231SquareWaveClock_1Hz = 0b00000000,
DS3231SquareWaveClock_1kHz = 0b00001000,
DS3231SquareWaveClock_4kHz = 0b00010000,
DS3231SquareWaveClock_8kHz = 0b00011000,
};
enum DS3231SquareWavePinMode
{
DS3231SquareWavePin_ModeNone,
DS3231SquareWavePin_ModeBatteryBackup,
DS3231SquareWavePin_ModeClock,
DS3231SquareWavePin_ModeAlarmOne,
DS3231SquareWavePin_ModeAlarmTwo,
DS3231SquareWavePin_ModeAlarmBoth
};
enum DS3231AlarmFlag
{
DS3231AlarmFlag_Alarm1 = 0x01,
DS3231AlarmFlag_Alarm2 = 0x02,
DS3231AlarmFlag_AlarmBoth = 0x03,
};
template<class T_WIRE_METHOD> class RtcDS3231
{
public:
RtcDS3231(T_WIRE_METHOD& wire) :
_wire(wire)
{
}
void Begin()
{
_wire.begin();
}
bool IsDateTimeValid()
{
uint8_t status = getReg(DS3231_REG_STATUS);
return !(status & _BV(DS3231_OSF));
}
bool GetIsRunning()
{
uint8_t creg = getReg(DS3231_REG_CONTROL);
return !(creg & _BV(DS3231_EOSC));
}
void SetIsRunning(bool isRunning)
{
uint8_t creg = getReg(DS3231_REG_CONTROL);
if (isRunning)
{
creg &= ~_BV(DS3231_EOSC);
}
else
{
creg |= _BV(DS3231_EOSC);
}
setReg(DS3231_REG_CONTROL, creg);
}
void SetDateTime(const RtcDateTime& dt)
{
// clear the invalid flag
uint8_t status = getReg(DS3231_REG_STATUS);
status &= ~_BV(DS3231_OSF); // clear the flag
setReg(DS3231_REG_STATUS, status);
// set the date time
_wire.beginTransmission(DS3231_ADDRESS);
_wire.write(DS3231_REG_TIMEDATE);
_wire.write(Uint8ToBcd(dt.Second()));
_wire.write(Uint8ToBcd(dt.Minute()));
_wire.write(Uint8ToBcd(dt.Hour())); // 24 hour mode only
uint8_t year = dt.Year() - 2000;
uint8_t centuryFlag = 0;
if (year >= 100)
{
year -= 100;
centuryFlag = _BV(7);
}
_wire.write(Uint8ToBcd(dt.DayOfWeek()));
_wire.write(Uint8ToBcd(dt.Day()));
_wire.write(Uint8ToBcd(dt.Month()) | centuryFlag);
_wire.write(Uint8ToBcd(year));
_wire.endTransmission();
}
RtcDateTime GetDateTime()
{
_wire.beginTransmission(DS3231_ADDRESS);
_wire.write(DS3231_REG_TIMEDATE);
_wire.endTransmission();
_wire.requestFrom(DS3231_ADDRESS, DS3231_REG_TIMEDATE_SIZE);
uint8_t second = BcdToUint8(_wire.read() & 0x7F);
uint8_t minute = BcdToUint8(_wire.read());
uint8_t hour = BcdToBin24Hour(_wire.read());
_wire.read(); // throwing away day of week as we calculate it
uint8_t dayOfMonth = BcdToUint8(_wire.read());
uint8_t monthRaw = _wire.read();
uint16_t year = BcdToUint8(_wire.read()) + 2000;
if (monthRaw & _BV(7)) // century wrap flag
{
year += 100;
}
uint8_t month = BcdToUint8(monthRaw & 0x7f);
return RtcDateTime(year, month, dayOfMonth, hour, minute, second);
}
RtcTemperature GetTemperature()
{
_wire.beginTransmission(DS3231_ADDRESS);
_wire.write(DS3231_REG_TEMP);
_wire.endTransmission();
_wire.requestFrom(DS3231_ADDRESS, DS3231_REG_TEMP_SIZE);
int8_t degrees = _wire.read();
// fraction is just the upper bits
// representing 1/4 of a degree
uint8_t fract = (_wire.read() >> 6) * 25;
return RtcTemperature(degrees, fract);
}
void Enable32kHzPin(bool enable)
{
uint8_t sreg = getReg(DS3231_REG_STATUS);
if (enable == true)
{
sreg |= _BV(DS3231_EN32KHZ);
}
else
{
sreg &= ~_BV(DS3231_EN32KHZ);
}
setReg(DS3231_REG_STATUS, sreg);
}
void SetSquareWavePin(DS3231SquareWavePinMode pinMode)
{
uint8_t creg = getReg(DS3231_REG_CONTROL);
// clear all relevant bits to a known "off" state
creg &= ~(DS3231_AIEMASK | _BV(DS3231_BBSQW));
creg |= _BV(DS3231_INTCN); // set INTCN to disables SQW
switch (pinMode)
{
case DS3231SquareWavePin_ModeNone:
break;
case DS3231SquareWavePin_ModeBatteryBackup:
creg |= _BV(DS3231_BBSQW); // set battery backup flag
creg &= ~_BV(DS3231_INTCN); // clear INTCN to enable SQW
break;
case DS3231SquareWavePin_ModeClock:
creg &= ~_BV(DS3231_INTCN); // clear INTCN to enable SQW
break;
case DS3231SquareWavePin_ModeAlarmOne:
creg |= _BV(DS3231_A1IE);
break;
case DS3231SquareWavePin_ModeAlarmTwo:
creg |= _BV(DS3231_A2IE);
break;
case DS3231SquareWavePin_ModeAlarmBoth:
creg |= _BV(DS3231_A1IE) | _BV(DS3231_A2IE);
break;
}
setReg(DS3231_REG_CONTROL, creg);
}
void SetSquareWavePinClockFrequency(DS3231SquareWaveClock freq)
{
uint8_t creg = getReg(DS3231_REG_CONTROL);
creg &= ~DS3231_RSMASK; // Set to 0
creg |= (freq & DS3231_RSMASK); // Set freq bits
setReg(DS3231_REG_CONTROL, creg);
}
void SetAlarmOne(const DS3231AlarmOne& alarm)
{
_wire.beginTransmission(DS3231_ADDRESS);
_wire.write(DS3231_REG_ALARMONE);
_wire.write(Uint8ToBcd(alarm.Second()) | ((alarm.ControlFlags() & 0x01) << 7));
_wire.write(Uint8ToBcd(alarm.Minute()) | ((alarm.ControlFlags() & 0x02) << 6));
_wire.write(Uint8ToBcd(alarm.Hour()) | ((alarm.ControlFlags() & 0x04) << 5)); // 24 hour mode only
_wire.write(Uint8ToBcd(alarm.DayOf()) | ((alarm.ControlFlags() & 0x18) << 3));
_wire.endTransmission();
}
void SetAlarmTwo(const DS3231AlarmTwo& alarm)
{
_wire.beginTransmission(DS3231_ADDRESS);
_wire.write(DS3231_REG_ALARMTWO);
_wire.write(Uint8ToBcd(alarm.Minute()) | ((alarm.ControlFlags() & 0x01) << 7));
_wire.write(Uint8ToBcd(alarm.Hour()) | ((alarm.ControlFlags() & 0x02) << 6)); // 24 hour mode only
_wire.write(Uint8ToBcd(alarm.DayOf()) | ((alarm.ControlFlags() & 0x0c) << 4));
_wire.endTransmission();
}
DS3231AlarmOne GetAlarmOne()
{
_wire.beginTransmission(DS3231_ADDRESS);
_wire.write(DS3231_REG_ALARMONE);
_wire.endTransmission();
_wire.requestFrom(DS3231_ADDRESS, DS3231_REG_ALARMONE_SIZE);
uint8_t raw = _wire.read();
uint8_t flags = (raw & 0x80) >> 7;
uint8_t second = BcdToUint8(raw & 0x7F);
raw = _wire.read();
flags |= (raw & 0x80) >> 6;
uint8_t minute = BcdToUint8(raw & 0x7F);
raw = _wire.read();
flags |= (raw & 0x80) >> 5;
uint8_t hour = BcdToBin24Hour(raw & 0x7f);
raw = _wire.read();
flags |= (raw & 0xc0) >> 3;
uint8_t dayOf = BcdToUint8(raw & 0x3f);
return DS3231AlarmOne(dayOf, hour, minute, second, (DS3231AlarmOneControl)flags);
}
DS3231AlarmTwo GetAlarmTwo()
{
_wire.beginTransmission(DS3231_ADDRESS);
_wire.write(DS3231_REG_ALARMTWO);
_wire.endTransmission();
_wire.requestFrom(DS3231_ADDRESS, DS3231_REG_ALARMTWO_SIZE);
uint8_t raw = _wire.read();
uint8_t flags = (raw & 0x80) >> 7;
uint8_t minute = BcdToUint8(raw & 0x7F);
raw = _wire.read();
flags |= (raw & 0x80) >> 6;
uint8_t hour = BcdToBin24Hour(raw & 0x7f);
raw = _wire.read();
flags |= (raw & 0xc0) >> 4;
uint8_t dayOf = BcdToUint8(raw & 0x3f);
return DS3231AlarmTwo(dayOf, hour, minute, (DS3231AlarmTwoControl)flags);
}
// Latch must be called after an alarm otherwise it will not
// trigger again
DS3231AlarmFlag LatchAlarmsTriggeredFlags()
{
uint8_t sreg = getReg(DS3231_REG_STATUS);
uint8_t alarmFlags = (sreg & DS3231_AIFMASK);
sreg &= ~DS3231_AIFMASK; // clear the flags
setReg(DS3231_REG_STATUS, sreg);
return (DS3231AlarmFlag)alarmFlags;
}
void ForceTemperatureCompensationUpdate(bool block)
{
uint8_t creg = getReg(DS3231_REG_CONTROL);
creg |= _BV(DS3231_CONV); // Write CONV bit
setReg(DS3231_REG_CONTROL, creg);
while (block && (creg & _BV(DS3231_CONV)) != 0)
{
// Block until CONV is 0
creg = getReg(DS3231_REG_CONTROL);
}
}
int8_t GetAgingOffset()
{
return getReg(DS3231_REG_AGING);
}
void SetAgingOffset(int8_t value)
{
setReg(DS3231_REG_AGING, value);
}
private:
T_WIRE_METHOD& _wire;
uint8_t getReg(uint8_t regAddress)
{
_wire.beginTransmission(DS3231_ADDRESS);
_wire.write(regAddress);
_wire.endTransmission();
// control register
_wire.requestFrom(DS3231_ADDRESS, (uint8_t)1);
uint8_t regValue = _wire.read();
return regValue;
}
void setReg(uint8_t regAddress, uint8_t regValue)
{
_wire.beginTransmission(DS3231_ADDRESS);
_wire.write(regAddress);
_wire.write(regValue);
_wire.endTransmission();
}
};
#endif // __RTCDS3231_H__

View File

@@ -0,0 +1,111 @@
#include <Arduino.h>
#include "RtcDateTime.h"
const uint8_t c_daysInMonth[] PROGMEM = { 31,28,31,30,31,30,31,31,30,31,30,31 };
RtcDateTime::RtcDateTime(uint32_t secondsFrom2000)
{
_initWithSecondsFrom2000<uint32_t>(secondsFrom2000);
}
uint8_t StringToUint8(const char* pString)
{
uint8_t value = 0;
// skip leading 0 and spaces
while ('0' == *pString || *pString == ' ')
{
pString++;
}
// calculate number until we hit non-numeral char
while ('0' <= *pString && *pString <= '9')
{
value *= 10;
value += *pString - '0';
pString++;
}
return value;
}
RtcDateTime::RtcDateTime(const char* date, const char* time)
{
// sample input: date = "Dec 26 2009", time = "12:34:56"
_yearFrom2000 = StringToUint8(date + 9);
// Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
switch (date[0])
{
case 'J':
if ( date[1] == 'a' )
_month = 1;
else if ( date[2] == 'n' )
_month = 6;
else
_month = 7;
break;
case 'F':
_month = 2;
break;
case 'A':
_month = date[1] == 'p' ? 4 : 8;
break;
case 'M':
_month = date[2] == 'r' ? 3 : 5;
break;
case 'S':
_month = 9;
break;
case 'O':
_month = 10;
break;
case 'N':
_month = 11;
break;
case 'D':
_month = 12;
break;
}
_dayOfMonth = StringToUint8(date + 4);
_hour = StringToUint8(time);
_minute = StringToUint8(time + 3);
_second = StringToUint8(time + 6);
}
template <typename T> T DaysSinceFirstOfYear2000(uint16_t year, uint8_t month, uint8_t dayOfMonth)
{
T days = dayOfMonth;
for (uint8_t indexMonth = 1; indexMonth < month; ++indexMonth)
{
days += pgm_read_byte(c_daysInMonth + indexMonth - 1);
}
if (month > 2 && year % 4 == 0)
{
days++;
}
return days + 365 * year + (year + 3) / 4 - 1;
}
template <typename T> T SecondsIn(T days, uint8_t hours, uint8_t minutes, uint8_t seconds)
{
return ((days * 24L + hours) * 60 + minutes) * 60 + seconds;
}
uint8_t RtcDateTime::DayOfWeek() const
{
uint16_t days = DaysSinceFirstOfYear2000<uint16_t>(_yearFrom2000, _month, _dayOfMonth);
return (days + 6) % 7; // Jan 1, 2000 is a Saturday, i.e. returns 6
}
uint32_t RtcDateTime::TotalSeconds() const
{
uint16_t days = DaysSinceFirstOfYear2000<uint16_t>(_yearFrom2000, _month, _dayOfMonth);
return SecondsIn<uint32_t>(days, _hour, _minute, _second);
}
uint64_t RtcDateTime::TotalSeconds64() const
{
uint32_t days = DaysSinceFirstOfYear2000<uint32_t>(_yearFrom2000, _month, _dayOfMonth);
return SecondsIn<uint64_t>(days, _hour, _minute, _second);
}

View File

@@ -0,0 +1,146 @@
#ifndef __RTCDATETIME_H__
#define __RTCDATETIME_H__
// ESP32 complains if not included
#if defined(ARDUINO_ARCH_ESP32)
#include <inttypes.h>
#endif
const uint16_t c_OriginYear = 2000;
const uint32_t c_Epoch32OfOriginYear = 946684800;
extern const uint8_t c_daysInMonth[] PROGMEM;
class RtcDateTime
{
public:
RtcDateTime(uint32_t secondsFrom2000 = 0);
RtcDateTime(uint16_t year,
uint8_t month,
uint8_t dayOfMonth,
uint8_t hour,
uint8_t minute,
uint8_t second) :
_yearFrom2000((year >= c_OriginYear) ? year - c_OriginYear : year),
_month(month),
_dayOfMonth(dayOfMonth),
_hour(hour),
_minute(minute),
_second(second)
{
}
// RtcDateTime compileDateTime(__DATE__, __TIME__);
RtcDateTime(const char* date, const char* time);
uint16_t Year() const
{
return c_OriginYear + _yearFrom2000;
}
uint8_t Month() const
{
return _month;
}
uint8_t Day() const
{
return _dayOfMonth;
}
uint8_t Hour() const
{
return _hour;
}
uint8_t Minute() const
{
return _minute;
}
uint8_t Second() const
{
return _second;
}
uint8_t DayOfWeek() const;
// 32-bit times as seconds since 1/1/2000
uint32_t TotalSeconds() const;
uint64_t TotalSeconds64() const;
// add seconds
void operator += (uint32_t seconds)
{
RtcDateTime after = RtcDateTime( TotalSeconds() + seconds );
*this = after;
}
// remove seconds
void operator -= (uint32_t seconds)
{
RtcDateTime before = RtcDateTime( TotalSeconds() - seconds );
*this = before;
}
// allows for comparisons to just work (==, <, >, <=, >=, !=)
operator uint32_t() const
{
return TotalSeconds();
}
// Epoch32 support
uint32_t Epoch32Time() const
{
return TotalSeconds() + c_Epoch32OfOriginYear;
}
void InitWithEpoch32Time(uint32_t time)
{
_initWithSecondsFrom2000<uint32_t>(time - c_Epoch32OfOriginYear);
}
// Epoch64 support
uint64_t Epoch64Time() const
{
return TotalSeconds64() + c_Epoch32OfOriginYear;
}
void InitWithEpoch64Time(uint64_t time)
{
_initWithSecondsFrom2000<uint64_t>(time - c_Epoch32OfOriginYear);
}
protected:
uint8_t _yearFrom2000;
uint8_t _month;
uint8_t _dayOfMonth;
uint8_t _hour;
uint8_t _minute;
uint8_t _second;
template <typename T> void _initWithSecondsFrom2000(T secondsFrom2000)
{
_second = secondsFrom2000 % 60;
T timeFrom2000 = secondsFrom2000 / 60;
_minute = timeFrom2000 % 60;
timeFrom2000 /= 60;
_hour = timeFrom2000 % 24;
T days = timeFrom2000 / 24;
T leapDays;
for (_yearFrom2000 = 0;; ++_yearFrom2000)
{
leapDays = (_yearFrom2000 % 4 == 0) ? 1 : 0;
if (days < 365U + leapDays)
break;
days -= 365 + leapDays;
}
for (_month = 1;; ++_month)
{
uint8_t daysPerMonth = pgm_read_byte(c_daysInMonth + _month - 1);
if (leapDays && _month == 2)
daysPerMonth++;
if (days < daysPerMonth)
break;
days -= daysPerMonth;
}
_dayOfMonth = days + 1;
}
};
#endif // __RTCDATETIME_H__

View File

@@ -0,0 +1,38 @@
#ifndef __RTCTEMPERATURE_H__
#define __RTCTEMPERATURE_H__
class RtcTemperature
{
public:
RtcTemperature(int8_t degrees, uint8_t fraction) :
integerDegrees(degrees),
decimalFraction(fraction)
{
}
float AsFloat()
{
float degrees = (float)integerDegrees;
degrees += (float)decimalFraction / ((degrees < 0) ? -100.0f : 100.0f) ;
return degrees;
}
int8_t AsWholeDegrees()
{
return integerDegrees;
}
uint8_t GetFractional()
{
return decimalFraction;
}
protected:
int8_t integerDegrees;
uint8_t decimalFraction;
};
#endif // __RTCTEMPERATURE_H__

View File

@@ -0,0 +1,34 @@
#include <Arduino.h>
#include "RtcUtility.h"
uint8_t BcdToUint8(uint8_t val)
{
return val - 6 * (val >> 4);
}
uint8_t Uint8ToBcd(uint8_t val)
{
return val + 6 * (val / 10);
}
uint8_t BcdToBin24Hour(uint8_t bcdHour)
{
uint8_t hour;
if (bcdHour & 0x40)
{
// 12 hour mode, convert to 24
bool isPm = ((bcdHour & 0x20) != 0);
hour = BcdToUint8(bcdHour & 0x1f);
if (isPm)
{
hour += 12;
}
}
else
{
hour = BcdToUint8(bcdHour);
}
return hour;
}

View File

@@ -0,0 +1,19 @@
#ifndef __RTCUTILITY_H__
#define __RTCUTILITY_H__
// ESP32 complains if not included
#if defined(ARDUINO_ARCH_ESP32)
#include <inttypes.h>
#endif
// for some reason, the DUE board support does not define this, even though other non AVR archs do
#ifndef _BV
#define _BV(b) (1UL << (b))
#endif
extern uint8_t BcdToUint8(uint8_t val);
extern uint8_t Uint8ToBcd(uint8_t val);
extern uint8_t BcdToBin24Hour(uint8_t bcdHour);
#endif // __RTCUTILITY_H__

View File

@@ -0,0 +1,209 @@
/*
* Interrupt and PWM utilities for 16 bit Timer1 on ATmega168/328
* Original code by Jesse Tane for http://labs.ideo.com August 2008
* Modified March 2009 by Jérôme Despatis and Jesse Tane for ATmega328 support
* Modified June 2009 by Michael Polli and Jesse Tane to fix a bug in setPeriod() which caused the timer to stop
* Modified June 2011 by Lex Talionis to add a function to read the timer
* Modified Oct 2011 by Andrew Richards to avoid certain problems:
* - Add (long) assignments and casts to TimerOne::read() to ensure calculations involving tmp, ICR1 and TCNT1 aren't truncated
* - Ensure 16 bit registers accesses are atomic - run with interrupts disabled when accessing
* - Remove global enable of interrupts (sei())- could be running within an interrupt routine)
* - Disable interrupts whilst TCTN1 == 0. Datasheet vague on this, but experiment shows that overflow interrupt
* flag gets set whilst TCNT1 == 0, resulting in a phantom interrupt. Could just set to 1, but gets inaccurate
* at very short durations
* - startBottom() added to start counter at 0 and handle all interrupt enabling.
* - start() amended to enable interrupts
* - restart() amended to point at startBottom()
* Modiied 7:26 PM Sunday, October 09, 2011 by Lex Talionis
* - renamed start() to resume() to reflect it's actual role
* - renamed startBottom() to start(). This breaks some old code that expects start to continue counting where it left off
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* See Google Code project http://code.google.com/p/arduino-timerone/ for latest
*/
#ifndef TIMERONE_cpp
#define TIMERONE_cpp
#include "TimerOne.h"
TimerOne Timer1; // preinstatiate
ISR(TIMER1_OVF_vect) // interrupt service routine that wraps a user defined function supplied by attachInterrupt
{
Timer1.isrCallback();
}
void TimerOne::initialize(long microseconds)
{
TCCR1A = 0; // clear control register A
TCCR1B = _BV(WGM13); // set mode 8: phase and frequency correct pwm, stop the timer
setPeriod(microseconds);
}
void TimerOne::setPeriod(long microseconds) // AR modified for atomic access
{
long cycles = (F_CPU / 2000000) * microseconds; // the counter runs backwards after TOP, interrupt is at BOTTOM so divide microseconds by 2
if(cycles < RESOLUTION) clockSelectBits = _BV(CS10); // no prescale, full xtal
else if((cycles >>= 3) < RESOLUTION) clockSelectBits = _BV(CS11); // prescale by /8
else if((cycles >>= 3) < RESOLUTION) clockSelectBits = _BV(CS11) | _BV(CS10); // prescale by /64
else if((cycles >>= 2) < RESOLUTION) clockSelectBits = _BV(CS12); // prescale by /256
else if((cycles >>= 2) < RESOLUTION) clockSelectBits = _BV(CS12) | _BV(CS10); // prescale by /1024
else cycles = RESOLUTION - 1, clockSelectBits = _BV(CS12) | _BV(CS10); // request was out of bounds, set as maximum
oldSREG = SREG;
cli(); // Disable interrupts for 16 bit register access
ICR1 = pwmPeriod = cycles; // ICR1 is TOP in p & f correct pwm mode
SREG = oldSREG;
TCCR1B &= ~(_BV(CS10) | _BV(CS11) | _BV(CS12));
TCCR1B |= clockSelectBits; // reset clock select register, and starts the clock
}
void TimerOne::setPwmDuty(char pin, int duty)
{
unsigned long dutyCycle = pwmPeriod;
dutyCycle *= duty;
dutyCycle >>= 10;
oldSREG = SREG;
cli();
if(pin == 1 || pin == 9) OCR1A = dutyCycle;
else if(pin == 2 || pin == 10) OCR1B = dutyCycle;
SREG = oldSREG;
}
void TimerOne::pwm(char pin, int duty, long microseconds) // expects duty cycle to be 10 bit (1024)
{
if(microseconds > 0) setPeriod(microseconds);
if(pin == 1 || pin == 9) {
DDRB |= _BV(PORTB1); // sets data direction register for pwm output pin
TCCR1A |= _BV(COM1A1); // activates the output pin
}
else if(pin == 2 || pin == 10) {
DDRB |= _BV(PORTB2);
TCCR1A |= _BV(COM1B1);
}
setPwmDuty(pin, duty);
resume(); // Lex - make sure the clock is running. We don't want to restart the count, in case we are starting the second WGM
// and the first one is in the middle of a cycle
}
void TimerOne::disablePwm(char pin)
{
if(pin == 1 || pin == 9) TCCR1A &= ~_BV(COM1A1); // clear the bit that enables pwm on PB1
else if(pin == 2 || pin == 10) TCCR1A &= ~_BV(COM1B1); // clear the bit that enables pwm on PB2
}
void TimerOne::attachInterrupt(void (*isr)(), long microseconds)
{
if(microseconds > 0) setPeriod(microseconds);
isrCallback = isr; // register the user's callback with the real ISR
TIMSK1 = _BV(TOIE1); // sets the timer overflow interrupt enable bit
// might be running with interrupts disabled (eg inside an ISR), so don't touch the global state
// sei();
resume();
}
void TimerOne::detachInterrupt()
{
TIMSK1 &= ~_BV(TOIE1); // clears the timer overflow interrupt enable bit
// timer continues to count without calling the isr
}
void TimerOne::resume() // AR suggested
{
TCCR1B |= clockSelectBits;
}
void TimerOne::restart() // Depricated - Public interface to start at zero - Lex 10/9/2011
{
start();
}
void TimerOne::start() // AR addition, renamed by Lex to reflect it's actual role
{
unsigned int tcnt1;
TIMSK1 &= ~_BV(TOIE1); // AR added
GTCCR |= _BV(PSRSYNC); // AR added - reset prescaler (NB: shared with all 16 bit timers);
oldSREG = SREG; // AR - save status register
cli(); // AR - Disable interrupts
TCNT1 = 0;
SREG = oldSREG; // AR - Restore status register
resume();
do { // Nothing -- wait until timer moved on from zero - otherwise get a phantom interrupt
oldSREG = SREG;
cli();
tcnt1 = TCNT1;
SREG = oldSREG;
} while (tcnt1==0);
// TIFR1 = 0xff; // AR - Clear interrupt flags
// TIMSK1 = _BV(TOIE1); // sets the timer overflow interrupt enable bit
}
void TimerOne::stop()
{
TCCR1B &= ~(_BV(CS10) | _BV(CS11) | _BV(CS12)); // clears all clock selects bits
}
unsigned long TimerOne::read() //returns the value of the timer in microseconds
{ //rember! phase and freq correct mode counts up to then down again
unsigned long tmp; // AR amended to hold more than 65536 (could be nearly double this)
unsigned int tcnt1; // AR added
oldSREG= SREG;
cli();
tmp=TCNT1;
SREG = oldSREG;
char scale=0;
switch (clockSelectBits)
{
case 1:// no prescalse
scale=0;
break;
case 2:// x8 prescale
scale=3;
break;
case 3:// x64
scale=6;
break;
case 4:// x256
scale=8;
break;
case 5:// x1024
scale=10;
break;
}
do { // Nothing -- max delay here is ~1023 cycles. AR modified
oldSREG = SREG;
cli();
tcnt1 = TCNT1;
SREG = oldSREG;
} while (tcnt1==tmp); //if the timer has not ticked yet
//if we are counting down add the top value to how far we have counted down
tmp = ( (tcnt1>tmp) ? (tmp) : (long)(ICR1-tcnt1)+(long)ICR1 ); // AR amended to add casts and reuse previous TCNT1
return ((tmp*1000L)/(F_CPU /1000L))<<scale;
}
#endif

View File

@@ -0,0 +1,70 @@
/*
* Interrupt and PWM utilities for 16 bit Timer1 on ATmega168/328
* Original code by Jesse Tane for http://labs.ideo.com August 2008
* Modified March 2009 by Jérôme Despatis and Jesse Tane for ATmega328 support
* Modified June 2009 by Michael Polli and Jesse Tane to fix a bug in setPeriod() which caused the timer to stop
* Modified June 2011 by Lex Talionis to add a function to read the timer
* Modified Oct 2011 by Andrew Richards to avoid certain problems:
* - Add (long) assignments and casts to TimerOne::read() to ensure calculations involving tmp, ICR1 and TCNT1 aren't truncated
* - Ensure 16 bit registers accesses are atomic - run with interrupts disabled when accessing
* - Remove global enable of interrupts (sei())- could be running within an interrupt routine)
* - Disable interrupts whilst TCTN1 == 0. Datasheet vague on this, but experiment shows that overflow interrupt
* flag gets set whilst TCNT1 == 0, resulting in a phantom interrupt. Could just set to 1, but gets inaccurate
* at very short durations
* - startBottom() added to start counter at 0 and handle all interrupt enabling.
* - start() amended to enable interrupts
* - restart() amended to point at startBottom()
* Modiied 7:26 PM Sunday, October 09, 2011 by Lex Talionis
* - renamed start() to resume() to reflect it's actual role
* - renamed startBottom() to start(). This breaks some old code that expects start to continue counting where it left off
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* See Google Code project http://code.google.com/p/arduino-timerone/ for latest
*/
#ifndef TIMERONE_h
#define TIMERONE_h
#include <avr/io.h>
#include <avr/interrupt.h>
#define RESOLUTION 65536 // Timer1 is 16 bit
class TimerOne
{
public:
// properties
unsigned int pwmPeriod;
unsigned char clockSelectBits;
char oldSREG; // To hold Status Register while ints disabled
// methods
void initialize(long microseconds=1000000);
void start();
void stop();
void restart();
void resume();
unsigned long read();
void pwm(char pin, int duty, long microseconds=-1);
void disablePwm(char pin);
void attachInterrupt(void (*isr)(), long microseconds=-1);
void detachInterrupt();
void setPeriod(long microseconds);
void setPwmDuty(char pin, int duty);
void (*isrCallback)();
};
extern TimerOne Timer1;
#endif

View File

@@ -0,0 +1,478 @@
/*-------------------------------------------------------------------------------------
YFTM1650.h -
8-segment display driver of YFRobot 4-bit digital tube module based on YFTM1650 chip
Created by yfrobot,Released into the public domain.
Product: https://item.taobao.com/item.htm?id=561918482249
Changelog:
v1.0:
2017/12/25 - Initial release
v1.1:
2020/03/25 - 统一修改TM1650为YFTM1650避免与其他库混淆
新增显示float/double/int类型函数
新增显示滚动字符函数
----------------------------------------------------------------------------------------*/
#ifndef _TM1650_H_
#define _TM1650_H_
#include <Arduino.h>
#define TM1650_USE_PROGMEM
#ifdef TM1650_USE_PROGMEM
#include <avr/pgmspace.h>
#endif
/** Definitions **/
#define _8_SEGMENT_MODE 0x00 // 8段显示模式
#define _7_SEGMENT_MODE 0x08 // 7段显示模式
#define NORMAL_MODE 0x00 // 正常工作模式
#define STANDBY_MODE 0x04 // 待机工作模式
#define DISPLAY_ON 0x01
#define DISPLAY_OFF 0x00
#define CMD_SYSTEM_CONFIG 0x48
// #define CMD_READ_KEYPAD 0x4F
#define DIG1_ADDRESS 0x68
#define DIG2_ADDRESS 0x6A
#define DIG3_ADDRESS 0x6C
#define DIG4_ADDRESS 0x6E
#define TM1650_NUM_DIGITS 16 // max number of digits 最大数字位数
#define TM1650_MAX_STRING 128 // number of digits 位数
const unsigned char Brightness[9] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x00};
const unsigned char DisplayAddressArray[4] = {DIG1_ADDRESS,DIG2_ADDRESS,DIG3_ADDRESS,DIG4_ADDRESS};
const unsigned int iNumDigits = 4;
//number 0-9 code
//const unsigned char Number_arr[10] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f};
#ifndef TM1650_USE_PROGMEM
const byte TM1650_CDigits[128] {
#else
const PROGMEM byte TM1650_CDigits[128] {
#endif
//0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0A 0x0B 0x0C 0x0D 0x0E 0x0F
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x00
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x10
0x00, 0x82, 0x21, 0x00, 0x00, 0x00, 0x00, 0x02, 0x39, 0x0F, 0x00, 0x00, 0x00, 0x40, 0x80, 0x00, // 0x20
0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7f, 0x6f, 0x00, 0x00, 0x00, 0x48, 0x00, 0x53, // 0x30
0x00, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71, 0x6F, 0x76, 0x06, 0x1E, 0x00, 0x38, 0x00, 0x54, 0x3F, // 0x40
0x73, 0x67, 0x50, 0x6D, 0x78, 0x3E, 0x00, 0x00, 0x00, 0x6E, 0x00, 0x39, 0x00, 0x0F, 0x00, 0x08, // 0x50
0x63, 0x5F, 0x7C, 0x58, 0x5E, 0x7B, 0x71, 0x6F, 0x74, 0x02, 0x1E, 0x00, 0x06, 0x00, 0x54, 0x5C, // 0x60
0x73, 0x67, 0x50, 0x6D, 0x78, 0x1C, 0x00, 0x00, 0x00, 0x6E, 0x00, 0x39, 0x30, 0x0F, 0x00, 0x00 // 0x70
};
class YFTM1650
{
public:
YFTM1650(int pin_SCK,int pin_DIO);
void init();
void clear();
// void clearDot();
void displayString(char *aString);
void displayString(String sString);
void displayString(float value);
void displayString(double value);// 与float完全相同,arduino中double和float精度完全相同
void displayString(int value);
boolean displayOneDigi(unsigned char digi,unsigned char cha);
void displayOn();
void displayOff();
boolean displayOff(unsigned char lightLevel,unsigned char SegmentMode,unsigned char WorkMode);
boolean displayOn(unsigned char lightLevel,unsigned char SegmentMode,unsigned char WorkMode);
void setDot(unsigned int aPos, bool aState);
void setBrightness(unsigned int iBrightness);
int displayRunning(String aString);
int displayRunning(char *aString);
int displayRunningShift();
private:
int _pin_SCK; //clock in pin
int _pin_DIO; //data in and out pin
char *iPosition;
char iString[TM1650_MAX_STRING+1];
byte iBuffer_num[TM1650_NUM_DIGITS+1]; // 数字位
byte iBuffer_dot[TM1650_NUM_DIGITS+1]; // 小数点位
byte SegmentMode;
byte WorkMode;
byte DsplayONOFF;
void FrameStart_1650(void);
void FrameEnd_1650(void);
boolean FrameAck_1650(void);
boolean writeByte(unsigned char firstByte,unsigned char secondByte);
};
/*==================================================*/
/**
*/
YFTM1650::YFTM1650(int pin_SCK,int pin_DIO)
{
pinMode(pin_SCK, OUTPUT);
pinMode(pin_DIO, OUTPUT);
_pin_SCK = pin_SCK;
_pin_DIO = pin_DIO;
}
/** FrameStart_1650
*/
void YFTM1650::FrameStart_1650(void)
{
digitalWrite(_pin_DIO , HIGH);
digitalWrite(_pin_SCK , HIGH);
digitalWrite(_pin_DIO , LOW);
}
/** FrameEnd_1650
*/
void YFTM1650::FrameEnd_1650(void)
{
digitalWrite(_pin_DIO , LOW);
digitalWrite(_pin_SCK , HIGH);
digitalWrite(_pin_DIO , HIGH);
}
/** FrameAck_1650
*/
boolean YFTM1650::FrameAck_1650(void)
{
if(digitalRead(_pin_DIO) == LOW)
{
digitalWrite(_pin_SCK , HIGH);
digitalWrite(_pin_SCK , LOW);
return 0;
}
else
{
return 1;
}
}
/** writeByte
*/
boolean YFTM1650::writeByte(unsigned char firstByte,unsigned char secondByte)
{
unsigned char tmp;
unsigned char i=0;
boolean err=0;
tmp=firstByte;
FrameStart_1650();
for(i=0;i<8;i++)
{
if(tmp&0x80)
{
digitalWrite(_pin_DIO , HIGH);
}
else
{
digitalWrite(_pin_DIO , LOW);
}
digitalWrite(_pin_SCK , LOW);
digitalWrite(_pin_SCK , HIGH);
digitalWrite(_pin_SCK , LOW);
tmp=tmp<<1;
}
if(FrameAck_1650()==1)
{
err=1;
}
tmp=secondByte;
for(i=0;i<8;i++)
{
if(tmp&0x80)
{
digitalWrite(_pin_DIO , HIGH);
}
else
{
digitalWrite(_pin_DIO , LOW);
}
digitalWrite(_pin_SCK , LOW);
digitalWrite(_pin_SCK , HIGH);
digitalWrite(_pin_SCK , LOW);
tmp=tmp<<1;
}
if(FrameAck_1650()==1)
{
err=1;
}
FrameEnd_1650();
return err;
}
/** init 初始化
*/
void YFTM1650::init()
{
iPosition = NULL;
for (int i=0; i<iNumDigits; i++) {
// iCtrl[i] = 0;
iBuffer_num[i] = 0;
iBuffer_dot[i] = 0;
}
SegmentMode = _8_SEGMENT_MODE;
WorkMode = NORMAL_MODE;
DsplayONOFF = DISPLAY_ON;
clear();
displayOn();
}
/** display n --
*
*/
boolean YFTM1650::displayOneDigi(unsigned char digi,unsigned char cha)
{
unsigned char tmp = 0;
boolean err = 0;
err = writeByte(DisplayAddressArray[digi-1],cha);
return err;
}
/** displayString -- 显示字符串
*
*/
void YFTM1650::displayString(char *aString)
{
for (int i=0; i<iNumDigits; i++) {
byte a = ((byte) aString[i]) & 0b01111111;
#ifndef TM1650_USE_PROGMEM
iBuffer_num[i] = TM1650_CDigits[a];
#else
iBuffer_num[i] = pgm_read_byte_near(TM1650_CDigits + a);
#endif
if (a) {
writeByte(DisplayAddressArray[i],iBuffer_num[i] | iBuffer_dot[i]);
}
else break;
}
}
/** displayString -- 显示字符串
*
*/
void YFTM1650::displayString(String sString)
{
for (int i=0; i<iNumDigits; i++) {
byte a = ((byte) sString.c_str()[i]) & 0b01111111;
#ifndef TM1650_USE_PROGMEM
iBuffer_num[i] = TM1650_CDigits[a];
#else
iBuffer_num[i] = pgm_read_byte_near(TM1650_CDigits + a);
#endif
if (a) {
writeByte(DisplayAddressArray[i],iBuffer_num[i] | iBuffer_dot[i]);
}
else break;
}
}
/** displayString -- 显示float
* 保留两位小数
*/
void YFTM1650::displayString(float value)
{
int f_value = int(value*100); // float值 放大100倍并转换int类型
if(f_value > 9999){ // 当数字大于9999四位数则只显示后四位
f_value = f_value%10000;
}
if(f_value < -999){
iBuffer_dot[1] = 0; // 无法显示
}else{
iBuffer_dot[1] = 0b10000000; // 保留两位小数
}
displayString(f_value);
iBuffer_dot[1] = 0; // 数码管小数点位清除
}
/** displayString -- 显示double
* 保留两位小数
* 与float完全相同,arduino中double和float精度完全相同
*/
void YFTM1650::displayString(double value)
{
displayString(float(value));
}
/** displayString -- 显示int
* value 范围:-999 ~ 9999
* 超出显示范围,则不显示
*/
void YFTM1650::displayString(int value)
{
if(value > 9999 || value < -999){
// 超出显示范围,则不显示
}else{
String aString = String("") + value;
unsigned int slen = aString.length();
for (int i = 0; i < 4 - slen; i++)
aString = " " + aString;
for (int i = 0; i<iNumDigits; i++) {
byte a = ((byte)aString.charAt(i)) & 0b01111111;
#ifndef TM1650_USE_PROGMEM
iBuffer_num[i] = TM1650_CDigits[a];
#else
iBuffer_num[i] = pgm_read_byte_near(TM1650_CDigits + a);
#endif
if (a) {
writeByte(DisplayAddressArray[i],iBuffer_num[i] | iBuffer_dot[i]);
}
else break;
}
}
}
/** display off -- 关闭显示
* lightLevel 亮度等级
* SegmentMode 显示模式7/8段显示
* WorkMode 工作模式(待机/正常工作模式)
*/
boolean YFTM1650::displayOff(unsigned char lightLevel,unsigned char SegmentMode,unsigned char WorkMode)
{
unsigned char tmp = 0;
boolean err = 0;
tmp = lightLevel | SegmentMode | WorkMode | DISPLAY_OFF;
err = writeByte(CMD_SYSTEM_CONFIG,tmp);
if(err == 1)
{
return 1;
}
else
{
return 0;
}
}
/** display off -- 关闭显示
*/
void YFTM1650::displayOff(){
displayOff(Brightness[7], SegmentMode, WorkMode);
}
/** display on -- 打开显示
* lightLevel 亮度等级
* SegmentMode 显示模式7/8段显示
* WorkMode 工作模式(待机/正常工作模式)
*/
boolean YFTM1650::displayOn(unsigned char lightLevel,unsigned char SegmentMode,unsigned char WorkMode)
{
unsigned char tmp = 0;
boolean err = 0;
tmp = lightLevel | SegmentMode | WorkMode | DISPLAY_ON;
err = writeByte(CMD_SYSTEM_CONFIG,tmp);
if(err == 1)
{
return 1;
}
else
{
return 0;
}
}
/** display on -- 打开显示
*/
void YFTM1650::displayOn(){
displayOn(Brightness[7], SegmentMode, WorkMode);
}
/** clear -- 清除显示
*/
void YFTM1650::clear(){
for (int i=0; i<iNumDigits; i++) {
iBuffer_num[i] = 0;
iBuffer_dot[i] = 0;
writeByte(DisplayAddressArray[i],iBuffer_num[i] | iBuffer_dot[i]);
}
}
/** Directly set/clear a 'dot' next to a specific position 直接设置/清除特定位置旁边的“点”
* aPos = position to set/clear the dot for aPos =设置/清除点的位置
* aState = display the dot if true, clear if false aState =如果为真则显示点,否则清除
*
* Internal buffer is updated as well 内部缓冲区也被更新
*/
void YFTM1650::setDot(unsigned int aPos, bool aState) {
iBuffer_dot[aPos] = aState ? 0b10000000 : 0;
if (aPos < iNumDigits) {
writeByte(DisplayAddressArray[aPos] , iBuffer_num[aPos] | iBuffer_dot[aPos]);
}
}
/** Set brightness of all digits equally 平均设置所有数字的亮度
* aValue - brightness value with 1 being the lowest, and 8 being the brightest
* aValue - 亮度值1代表最低8代表最亮(实际设置值0~7)
*/
void YFTM1650::setBrightness(unsigned int aValue) {
unsigned int iBrightness = Brightness[((aValue > 8) ? 8 : (aValue < 1) ? 1 : aValue) - 1];
writeByte(CMD_SYSTEM_CONFIG, iBrightness | SegmentMode | WorkMode | DsplayONOFF);
}
/** Display string on the display in a running fashion
* aString = character array to be displayed
*
* Starts with first N positions of the string.
* Subsequent characters are displayed with 1 char shift each time displayRunningShift() is called
*
* returns: number of iterations remaining to display the whole string
*/
int YFTM1650::displayRunning(String aString){
strncpy(iString, aString.c_str(), TM1650_MAX_STRING+1);
iPosition = iString;
iString[TM1650_MAX_STRING] = '\0'; //just in case.
displayString(iPosition);
int l = strlen(iPosition);
if (l <= iNumDigits) return 0;
return (l - iNumDigits);
}
int YFTM1650::displayRunning(char *aString){
strncpy(iString, aString, TM1650_MAX_STRING+1);
iPosition = iString;
iString[TM1650_MAX_STRING] = '\0'; //just in case.
displayString(iPosition);
int l = strlen(iPosition);
if (l <= iNumDigits) return 0;
return (l - iNumDigits);
}
/** Display next segment (shifting to the left) of the string set by displayRunning()
* Starts with first N positions of the string.
* Subsequent characters are displayed with 1 char shift each time displayRunningShift is called
*
* returns: number of iterations remaining to display the whole string
*/
int YFTM1650::displayRunningShift() {
if (strlen(iPosition) <= iNumDigits) return 0;
displayString(++iPosition);
return (strlen(iPosition) - iNumDigits);
}
#endif

View File

@@ -0,0 +1,201 @@
//
// FILE: dht.cpp
// AUTHOR: Rob Tillaart
// VERSION: 0.1.22
// PURPOSE: DHT Temperature & Humidity Sensor library for Arduino
// URL: http://arduino.cc/playground/Main/DHTLib
//
// HISTORY:
// 0.1.22 undo delayMicroseconds() for wakeups larger than 8
// 0.1.21 replace delay with delayMicroseconds() + small fix
// 0.1.20 Reduce footprint by using uint8_t as error codes. (thanks to chaveiro)
// 0.1.19 masking error for DHT11 - FIXED (thanks Richard for noticing)
// 0.1.18 version 1.16/17 broke the DHT11 - FIXED
// 0.1.17 replaced micros() with adaptive loopcount
// removed DHTLIB_INVALID_VALUE
// added DHTLIB_ERROR_CONNECT
// added DHTLIB_ERROR_ACK_L DHTLIB_ERROR_ACK_H
// 0.1.16 masking unused bits (less errors); refactored bits[]
// 0.1.15 reduced # micros calls 2->1 in inner loop.
// 0.1.14 replace digital read with faster (~3x) code => more robust low MHz machines.
//
// 0.1.13 fix negative temperature
// 0.1.12 support DHT33 and DHT44 initial version
// 0.1.11 renamed DHTLIB_TIMEOUT
// 0.1.10 optimized faster WAKEUP + TIMEOUT
// 0.1.09 optimize size: timeout check + use of mask
// 0.1.08 added formula for timeout based upon clockspeed
// 0.1.07 added support for DHT21
// 0.1.06 minimize footprint (2012-12-27)
// 0.1.05 fixed negative temperature bug (thanks to Roseman)
// 0.1.04 improved readability of code using DHTLIB_OK in code
// 0.1.03 added error values for temp and humidity when read failed
// 0.1.02 added error codes
// 0.1.01 added support for Arduino 1.0, fixed typos (31/12/2011)
// 0.1.00 by Rob Tillaart (01/04/2011)
//
// inspired by DHT11 library
//
// Released to the public domain
//
#include "dht.h"
/////////////////////////////////////////////////////
//
// PUBLIC
//
int8_t dht::read11(uint8_t pin)
{
// READ VALUES
int8_t result = _readSensor(pin, DHTLIB_DHT11_WAKEUP, DHTLIB_DHT11_LEADING_ZEROS);
// these bits are always zero, masking them reduces errors.
bits[0] &= 0x7F;
bits[2] &= 0x7F;
// CONVERT AND STORE
humidity = bits[0]; // bits[1] == 0;
temperature = bits[2]; // bits[3] == 0;
// TEST CHECKSUM
// bits[1] && bits[3] both 0
uint8_t sum = bits[0] + bits[2];
if (bits[4] != sum)
{
return DHTLIB_ERROR_CHECKSUM;
}
return result;
}
int8_t dht::read(uint8_t pin)
{
// READ VALUES
int8_t result = _readSensor(pin, DHTLIB_DHT_WAKEUP, DHTLIB_DHT_LEADING_ZEROS);
// these bits are always zero, masking them reduces errors.
bits[0] &= 0x03;
bits[2] &= 0x83;
// CONVERT AND STORE
humidity = (bits[0]*256 + bits[1]) * 0.1;
temperature = ((bits[2] & 0x7F)*256 + bits[3]) * 0.1;
if (bits[2] & 0x80) // negative temperature
{
temperature = -temperature;
}
// TEST CHECKSUM
uint8_t sum = bits[0] + bits[1] + bits[2] + bits[3];
if (bits[4] != sum)
{
return DHTLIB_ERROR_CHECKSUM;
}
return result;
}
/////////////////////////////////////////////////////
//
// PRIVATE
//
int8_t dht::_readSensor(uint8_t pin, uint8_t wakeupDelay, uint8_t leadingZeroBits)
{
// INIT BUFFERVAR TO RECEIVE DATA
uint8_t mask = 128;
uint8_t idx = 0;
uint8_t data = 0;
uint8_t state = LOW;
uint8_t pstate = LOW;
uint16_t zeroLoop = DHTLIB_TIMEOUT;
uint16_t delta = 0;
leadingZeroBits = 40 - leadingZeroBits; // reverse counting...
// replace digitalRead() with Direct Port Reads.
// reduces footprint ~100 bytes => portability issue?
// direct port read is about 3x faster
uint8_t bit = digitalPinToBitMask(pin);
uint8_t port = digitalPinToPort(pin);
volatile uint8_t *PIR = portInputRegister(port);
// REQUEST SAMPLE
pinMode(pin, OUTPUT);
digitalWrite(pin, LOW); // T-be
if (wakeupDelay > 8) delay(wakeupDelay);
else delayMicroseconds(wakeupDelay * 1000UL);
digitalWrite(pin, HIGH); // T-go
pinMode(pin, INPUT);
uint16_t loopCount = DHTLIB_TIMEOUT * 2; // 200uSec max
// while(digitalRead(pin) == HIGH)
while ((*PIR & bit) != LOW )
{
if (--loopCount == 0) return DHTLIB_ERROR_CONNECT;
}
// GET ACKNOWLEDGE or TIMEOUT
loopCount = DHTLIB_TIMEOUT;
// while(digitalRead(pin) == LOW)
while ((*PIR & bit) == LOW ) // T-rel
{
if (--loopCount == 0) return DHTLIB_ERROR_ACK_L;
}
loopCount = DHTLIB_TIMEOUT;
// while(digitalRead(pin) == HIGH)
while ((*PIR & bit) != LOW ) // T-reh
{
if (--loopCount == 0) return DHTLIB_ERROR_ACK_H;
}
loopCount = DHTLIB_TIMEOUT;
// READ THE OUTPUT - 40 BITS => 5 BYTES
for (uint8_t i = 40; i != 0; )
{
// WAIT FOR FALLING EDGE
state = (*PIR & bit);
if (state == LOW && pstate != LOW)
{
if (i > leadingZeroBits) // DHT22 first 6 bits are all zero !! DHT11 only 1
{
zeroLoop = min(zeroLoop, loopCount);
delta = (DHTLIB_TIMEOUT - zeroLoop)/4;
}
else if ( loopCount <= (zeroLoop - delta) ) // long -> one
{
data |= mask;
}
mask >>= 1;
if (mask == 0) // next byte
{
mask = 128;
bits[idx] = data;
idx++;
data = 0;
}
// next bit
--i;
// reset timeout flag
loopCount = DHTLIB_TIMEOUT;
}
pstate = state;
// Check timeout
if (--loopCount == 0)
{
return DHTLIB_ERROR_TIMEOUT;
}
}
pinMode(pin, OUTPUT);
digitalWrite(pin, HIGH);
return DHTLIB_OK;
}
//
// END OF FILE
//

View File

@@ -0,0 +1,77 @@
//
// FILE: dht.h
// AUTHOR: Rob Tillaart
// VERSION: 0.1.22
// PURPOSE: DHT Temperature & Humidity Sensor library for Arduino
// URL: http://arduino.cc/playground/Main/DHTLib
//
// HISTORY:
// see dht.cpp file
//
#ifndef dht_h
#define dht_h
#if ARDUINO < 100
#include <WProgram.h>
#include <pins_arduino.h> // fix for broken pre 1.0 version - TODO TEST
#else
#include <Arduino.h>
#endif
#define DHT_LIB_VERSION "0.1.22"
#define DHTLIB_OK 0
#define DHTLIB_ERROR_CHECKSUM -1
#define DHTLIB_ERROR_TIMEOUT -2
#define DHTLIB_ERROR_CONNECT -3
#define DHTLIB_ERROR_ACK_L -4
#define DHTLIB_ERROR_ACK_H -5
#define DHTLIB_DHT11_WAKEUP 18
#define DHTLIB_DHT_WAKEUP 1
#define DHTLIB_DHT11_LEADING_ZEROS 1
#define DHTLIB_DHT_LEADING_ZEROS 6
// max timeout is 100 usec.
// For a 16 Mhz proc 100 usec is 1600 clock cycles
// loops using DHTLIB_TIMEOUT use at least 4 clock cycli
// so 100 us takes max 400 loops
// so by dividing F_CPU by 40000 we "fail" as fast as possible
#ifndef F_CPU
#define DHTLIB_TIMEOUT 1000 // ahould be approx. clock/40000
#else
#define DHTLIB_TIMEOUT (F_CPU/40000)
#endif
class dht
{
public:
dht() {};
// return values:
// DHTLIB_OK
// DHTLIB_ERROR_CHECKSUM
// DHTLIB_ERROR_TIMEOUT
// DHTLIB_ERROR_CONNECT
// DHTLIB_ERROR_ACK_L
// DHTLIB_ERROR_ACK_H
int8_t read11(uint8_t pin);
int8_t read(uint8_t pin);
inline int8_t read21(uint8_t pin) { return read(pin); };
inline int8_t read22(uint8_t pin) { return read(pin); };
inline int8_t read33(uint8_t pin) { return read(pin); };
inline int8_t read44(uint8_t pin) { return read(pin); };
double humidity;
double temperature;
private:
uint8_t bits[5]; // buffer to receive data
int8_t _readSensor(uint8_t pin, uint8_t wakeupDelay, uint8_t leadingZeroBits);
};
#endif
//
// END OF FILE
//

View File

@@ -0,0 +1,67 @@
// This is a mash-up of the Due show() code + insights from Michael Miller's
// ESP8266 work for the NeoPixelBus library: github.com/Makuna/NeoPixelBus
// Needs to be a separate .c file to enforce ICACHE_RAM_ATTR execution.
#ifdef ESP8266
#include <Arduino.h>
#include <eagle_soc.h>
static uint32_t _getCycleCount(void) __attribute__((always_inline));
static inline uint32_t _getCycleCount(void) {
uint32_t ccount;
__asm__ __volatile__("rsr %0,ccount":"=a" (ccount));
return ccount;
}
void ICACHE_RAM_ATTR espShow(
uint8_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz) {
#define CYCLES_800_T0H (F_CPU / 2500000) // 0.4us
#define CYCLES_800_T1H (F_CPU / 1250000) // 0.8us
#define CYCLES_800 (F_CPU / 800000) // 1.25us per bit
#define CYCLES_400_T0H (F_CPU / 2000000) // 0.5uS
#define CYCLES_400_T1H (F_CPU / 833333) // 1.2us
#define CYCLES_400 (F_CPU / 400000) // 2.5us per bit
uint8_t *p, *end, pix, mask;
uint32_t t, time0, time1, period, c, startTime, pinMask;
pinMask = _BV(pin);
p = pixels;
end = p + numBytes;
pix = *p++;
mask = 0x80;
startTime = 0;
#ifdef NEO_KHZ400
if(is800KHz) {
#endif
time0 = CYCLES_800_T0H;
time1 = CYCLES_800_T1H;
period = CYCLES_800;
#ifdef NEO_KHZ400
} else { // 400 KHz bitstream
time0 = CYCLES_400_T0H;
time1 = CYCLES_400_T1H;
period = CYCLES_400;
}
#endif
for(t = time0;; t = time0) {
if(pix & mask) t = time1; // Bit high duration
while(((c = _getCycleCount()) - startTime) < period); // Wait for bit start
GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, pinMask); // Set high
startTime = c; // Save start time
while(((c = _getCycleCount()) - startTime) < t); // Wait high duration
GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, pinMask); // Set low
if(!(mask >>= 1)) { // Next bit/byte
if(p >= end) break;
pix = *p++;
mask = 0x80;
}
}
while((_getCycleCount() - startTime) < period); // Wait for last bit
}
#endif // ESP8266

View File

@@ -0,0 +1,270 @@
#ifndef FONT5X7_H
#define FONT5X7_H
#ifdef __AVR__
#include <avr/io.h>
#include <avr/pgmspace.h>
#else
#define PROGMEM
#endif
// Standard ASCII 5x7 font
static const unsigned char font[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00,
0x3E, 0x5B, 0x4F, 0x5B, 0x3E,
0x3E, 0x6B, 0x4F, 0x6B, 0x3E,
0x1C, 0x3E, 0x7C, 0x3E, 0x1C,
0x18, 0x3C, 0x7E, 0x3C, 0x18,
0x1C, 0x57, 0x7D, 0x57, 0x1C,
0x1C, 0x5E, 0x7F, 0x5E, 0x1C,
0x00, 0x18, 0x3C, 0x18, 0x00,
0xFF, 0xE7, 0xC3, 0xE7, 0xFF,
0x00, 0x18, 0x24, 0x18, 0x00,
0xFF, 0xE7, 0xDB, 0xE7, 0xFF,
0x30, 0x48, 0x3A, 0x06, 0x0E,
0x26, 0x29, 0x79, 0x29, 0x26,
0x40, 0x7F, 0x05, 0x05, 0x07,
0x40, 0x7F, 0x05, 0x25, 0x3F,
0x5A, 0x3C, 0xE7, 0x3C, 0x5A,
0x7F, 0x3E, 0x1C, 0x1C, 0x08,
0x08, 0x1C, 0x1C, 0x3E, 0x7F,
0x14, 0x22, 0x7F, 0x22, 0x14,
0x5F, 0x5F, 0x00, 0x5F, 0x5F,
0x06, 0x09, 0x7F, 0x01, 0x7F,
0x00, 0x66, 0x89, 0x95, 0x6A,
0x60, 0x60, 0x60, 0x60, 0x60,
0x94, 0xA2, 0xFF, 0xA2, 0x94,
0x08, 0x04, 0x7E, 0x04, 0x08,
0x10, 0x20, 0x7E, 0x20, 0x10,
0x08, 0x08, 0x2A, 0x1C, 0x08,
0x08, 0x1C, 0x2A, 0x08, 0x08,
0x1E, 0x10, 0x10, 0x10, 0x10,
0x0C, 0x1E, 0x0C, 0x1E, 0x0C,
0x30, 0x38, 0x3E, 0x38, 0x30,
0x06, 0x0E, 0x3E, 0x0E, 0x06,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x5F, 0x00, 0x00,
0x00, 0x07, 0x00, 0x07, 0x00,
0x14, 0x7F, 0x14, 0x7F, 0x14,
0x24, 0x2A, 0x7F, 0x2A, 0x12,
0x23, 0x13, 0x08, 0x64, 0x62,
0x36, 0x49, 0x56, 0x20, 0x50,
0x00, 0x08, 0x07, 0x03, 0x00,
0x00, 0x1C, 0x22, 0x41, 0x00,
0x00, 0x41, 0x22, 0x1C, 0x00,
0x2A, 0x1C, 0x7F, 0x1C, 0x2A,
0x08, 0x08, 0x3E, 0x08, 0x08,
0x00, 0x80, 0x70, 0x30, 0x00,
0x08, 0x08, 0x08, 0x08, 0x08,
0x00, 0x00, 0x60, 0x60, 0x00,
0x20, 0x10, 0x08, 0x04, 0x02,
0x3E, 0x51, 0x49, 0x45, 0x3E,
0x00, 0x42, 0x7F, 0x40, 0x00,
0x72, 0x49, 0x49, 0x49, 0x46,
0x21, 0x41, 0x49, 0x4D, 0x33,
0x18, 0x14, 0x12, 0x7F, 0x10,
0x27, 0x45, 0x45, 0x45, 0x39,
0x3C, 0x4A, 0x49, 0x49, 0x31,
0x41, 0x21, 0x11, 0x09, 0x07,
0x36, 0x49, 0x49, 0x49, 0x36,
0x46, 0x49, 0x49, 0x29, 0x1E,
0x00, 0x00, 0x14, 0x00, 0x00,
0x00, 0x40, 0x34, 0x00, 0x00,
0x00, 0x08, 0x14, 0x22, 0x41,
0x14, 0x14, 0x14, 0x14, 0x14,
0x00, 0x41, 0x22, 0x14, 0x08,
0x02, 0x01, 0x59, 0x09, 0x06,
0x3E, 0x41, 0x5D, 0x59, 0x4E,
0x7C, 0x12, 0x11, 0x12, 0x7C,
0x7F, 0x49, 0x49, 0x49, 0x36,
0x3E, 0x41, 0x41, 0x41, 0x22,
0x7F, 0x41, 0x41, 0x41, 0x3E,
0x7F, 0x49, 0x49, 0x49, 0x41,
0x7F, 0x09, 0x09, 0x09, 0x01,
0x3E, 0x41, 0x41, 0x51, 0x73,
0x7F, 0x08, 0x08, 0x08, 0x7F,
0x00, 0x41, 0x7F, 0x41, 0x00,
0x20, 0x40, 0x41, 0x3F, 0x01,
0x7F, 0x08, 0x14, 0x22, 0x41,
0x7F, 0x40, 0x40, 0x40, 0x40,
0x7F, 0x02, 0x1C, 0x02, 0x7F,
0x7F, 0x04, 0x08, 0x10, 0x7F,
0x3E, 0x41, 0x41, 0x41, 0x3E,
0x7F, 0x09, 0x09, 0x09, 0x06,
0x3E, 0x41, 0x51, 0x21, 0x5E,
0x7F, 0x09, 0x19, 0x29, 0x46,
0x26, 0x49, 0x49, 0x49, 0x32,
0x03, 0x01, 0x7F, 0x01, 0x03,
0x3F, 0x40, 0x40, 0x40, 0x3F,
0x1F, 0x20, 0x40, 0x20, 0x1F,
0x3F, 0x40, 0x38, 0x40, 0x3F,
0x63, 0x14, 0x08, 0x14, 0x63,
0x03, 0x04, 0x78, 0x04, 0x03,
0x61, 0x59, 0x49, 0x4D, 0x43,
0x00, 0x7F, 0x41, 0x41, 0x41,
0x02, 0x04, 0x08, 0x10, 0x20,
0x00, 0x41, 0x41, 0x41, 0x7F,
0x04, 0x02, 0x01, 0x02, 0x04,
0x40, 0x40, 0x40, 0x40, 0x40,
0x00, 0x03, 0x07, 0x08, 0x00,
0x20, 0x54, 0x54, 0x78, 0x40,
0x7F, 0x28, 0x44, 0x44, 0x38,
0x38, 0x44, 0x44, 0x44, 0x28,
0x38, 0x44, 0x44, 0x28, 0x7F,
0x38, 0x54, 0x54, 0x54, 0x18,
0x00, 0x08, 0x7E, 0x09, 0x02,
0x18, 0xA4, 0xA4, 0x9C, 0x78,
0x7F, 0x08, 0x04, 0x04, 0x78,
0x00, 0x44, 0x7D, 0x40, 0x00,
0x20, 0x40, 0x40, 0x3D, 0x00,
0x7F, 0x10, 0x28, 0x44, 0x00,
0x00, 0x41, 0x7F, 0x40, 0x00,
0x7C, 0x04, 0x78, 0x04, 0x78,
0x7C, 0x08, 0x04, 0x04, 0x78,
0x38, 0x44, 0x44, 0x44, 0x38,
0xFC, 0x18, 0x24, 0x24, 0x18,
0x18, 0x24, 0x24, 0x18, 0xFC,
0x7C, 0x08, 0x04, 0x04, 0x08,
0x48, 0x54, 0x54, 0x54, 0x24,
0x04, 0x04, 0x3F, 0x44, 0x24,
0x3C, 0x40, 0x40, 0x20, 0x7C,
0x1C, 0x20, 0x40, 0x20, 0x1C,
0x3C, 0x40, 0x30, 0x40, 0x3C,
0x44, 0x28, 0x10, 0x28, 0x44,
0x4C, 0x90, 0x90, 0x90, 0x7C,
0x44, 0x64, 0x54, 0x4C, 0x44,
0x00, 0x08, 0x36, 0x41, 0x00,
0x00, 0x00, 0x77, 0x00, 0x00,
0x00, 0x41, 0x36, 0x08, 0x00,
0x02, 0x01, 0x02, 0x04, 0x02,
0x3C, 0x26, 0x23, 0x26, 0x3C,
0x1E, 0xA1, 0xA1, 0x61, 0x12,
0x3A, 0x40, 0x40, 0x20, 0x7A,
0x38, 0x54, 0x54, 0x55, 0x59,
0x21, 0x55, 0x55, 0x79, 0x41,
0x21, 0x54, 0x54, 0x78, 0x41,
0x21, 0x55, 0x54, 0x78, 0x40,
0x20, 0x54, 0x55, 0x79, 0x40,
0x0C, 0x1E, 0x52, 0x72, 0x12,
0x39, 0x55, 0x55, 0x55, 0x59,
0x39, 0x54, 0x54, 0x54, 0x59,
0x39, 0x55, 0x54, 0x54, 0x58,
0x00, 0x00, 0x45, 0x7C, 0x41,
0x00, 0x02, 0x45, 0x7D, 0x42,
0x00, 0x01, 0x45, 0x7C, 0x40,
0xF0, 0x29, 0x24, 0x29, 0xF0,
0xF0, 0x28, 0x25, 0x28, 0xF0,
0x7C, 0x54, 0x55, 0x45, 0x00,
0x20, 0x54, 0x54, 0x7C, 0x54,
0x7C, 0x0A, 0x09, 0x7F, 0x49,
0x32, 0x49, 0x49, 0x49, 0x32,
0x32, 0x48, 0x48, 0x48, 0x32,
0x32, 0x4A, 0x48, 0x48, 0x30,
0x3A, 0x41, 0x41, 0x21, 0x7A,
0x3A, 0x42, 0x40, 0x20, 0x78,
0x00, 0x9D, 0xA0, 0xA0, 0x7D,
0x39, 0x44, 0x44, 0x44, 0x39,
0x3D, 0x40, 0x40, 0x40, 0x3D,
0x3C, 0x24, 0xFF, 0x24, 0x24,
0x48, 0x7E, 0x49, 0x43, 0x66,
0x2B, 0x2F, 0xFC, 0x2F, 0x2B,
0xFF, 0x09, 0x29, 0xF6, 0x20,
0xC0, 0x88, 0x7E, 0x09, 0x03,
0x20, 0x54, 0x54, 0x79, 0x41,
0x00, 0x00, 0x44, 0x7D, 0x41,
0x30, 0x48, 0x48, 0x4A, 0x32,
0x38, 0x40, 0x40, 0x22, 0x7A,
0x00, 0x7A, 0x0A, 0x0A, 0x72,
0x7D, 0x0D, 0x19, 0x31, 0x7D,
0x26, 0x29, 0x29, 0x2F, 0x28,
0x26, 0x29, 0x29, 0x29, 0x26,
0x30, 0x48, 0x4D, 0x40, 0x20,
0x38, 0x08, 0x08, 0x08, 0x08,
0x08, 0x08, 0x08, 0x08, 0x38,
0x2F, 0x10, 0xC8, 0xAC, 0xBA,
0x2F, 0x10, 0x28, 0x34, 0xFA,
0x00, 0x00, 0x7B, 0x00, 0x00,
0x08, 0x14, 0x2A, 0x14, 0x22,
0x22, 0x14, 0x2A, 0x14, 0x08,
0xAA, 0x00, 0x55, 0x00, 0xAA,
0xAA, 0x55, 0xAA, 0x55, 0xAA,
0x00, 0x00, 0x00, 0xFF, 0x00,
0x10, 0x10, 0x10, 0xFF, 0x00,
0x14, 0x14, 0x14, 0xFF, 0x00,
0x10, 0x10, 0xFF, 0x00, 0xFF,
0x10, 0x10, 0xF0, 0x10, 0xF0,
0x14, 0x14, 0x14, 0xFC, 0x00,
0x14, 0x14, 0xF7, 0x00, 0xFF,
0x00, 0x00, 0xFF, 0x00, 0xFF,
0x14, 0x14, 0xF4, 0x04, 0xFC,
0x14, 0x14, 0x17, 0x10, 0x1F,
0x10, 0x10, 0x1F, 0x10, 0x1F,
0x14, 0x14, 0x14, 0x1F, 0x00,
0x10, 0x10, 0x10, 0xF0, 0x00,
0x00, 0x00, 0x00, 0x1F, 0x10,
0x10, 0x10, 0x10, 0x1F, 0x10,
0x10, 0x10, 0x10, 0xF0, 0x10,
0x00, 0x00, 0x00, 0xFF, 0x10,
0x10, 0x10, 0x10, 0x10, 0x10,
0x10, 0x10, 0x10, 0xFF, 0x10,
0x00, 0x00, 0x00, 0xFF, 0x14,
0x00, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0x00, 0x1F, 0x10, 0x17,
0x00, 0x00, 0xFC, 0x04, 0xF4,
0x14, 0x14, 0x17, 0x10, 0x17,
0x14, 0x14, 0xF4, 0x04, 0xF4,
0x00, 0x00, 0xFF, 0x00, 0xF7,
0x14, 0x14, 0x14, 0x14, 0x14,
0x14, 0x14, 0xF7, 0x00, 0xF7,
0x14, 0x14, 0x14, 0x17, 0x14,
0x10, 0x10, 0x1F, 0x10, 0x1F,
0x14, 0x14, 0x14, 0xF4, 0x14,
0x10, 0x10, 0xF0, 0x10, 0xF0,
0x00, 0x00, 0x1F, 0x10, 0x1F,
0x00, 0x00, 0x00, 0x1F, 0x14,
0x00, 0x00, 0x00, 0xFC, 0x14,
0x00, 0x00, 0xF0, 0x10, 0xF0,
0x10, 0x10, 0xFF, 0x10, 0xFF,
0x14, 0x14, 0x14, 0xFF, 0x14,
0x10, 0x10, 0x10, 0x1F, 0x00,
0x00, 0x00, 0x00, 0xF0, 0x10,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
0xFF, 0xFF, 0xFF, 0x00, 0x00,
0x00, 0x00, 0x00, 0xFF, 0xFF,
0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
0x38, 0x44, 0x44, 0x38, 0x44,
0x7C, 0x2A, 0x2A, 0x3E, 0x14,
0x7E, 0x02, 0x02, 0x06, 0x06,
0x02, 0x7E, 0x02, 0x7E, 0x02,
0x63, 0x55, 0x49, 0x41, 0x63,
0x38, 0x44, 0x44, 0x3C, 0x04,
0x40, 0x7E, 0x20, 0x1E, 0x20,
0x06, 0x02, 0x7E, 0x02, 0x02,
0x99, 0xA5, 0xE7, 0xA5, 0x99,
0x1C, 0x2A, 0x49, 0x2A, 0x1C,
0x4C, 0x72, 0x01, 0x72, 0x4C,
0x30, 0x4A, 0x4D, 0x4D, 0x30,
0x30, 0x48, 0x78, 0x48, 0x30,
0xBC, 0x62, 0x5A, 0x46, 0x3D,
0x3E, 0x49, 0x49, 0x49, 0x00,
0x7E, 0x01, 0x01, 0x01, 0x7E,
0x2A, 0x2A, 0x2A, 0x2A, 0x2A,
0x44, 0x44, 0x5F, 0x44, 0x44,
0x40, 0x51, 0x4A, 0x44, 0x40,
0x40, 0x44, 0x4A, 0x51, 0x40,
0x00, 0x00, 0xFF, 0x01, 0x03,
0xE0, 0x80, 0xFF, 0x00, 0x00,
0x08, 0x08, 0x6B, 0x6B, 0x08,
0x36, 0x12, 0x36, 0x24, 0x36,
0x06, 0x0F, 0x09, 0x0F, 0x06,
0x00, 0x00, 0x18, 0x18, 0x00,
0x00, 0x00, 0x10, 0x10, 0x00,
0x30, 0x40, 0xFF, 0x01, 0x01,
0x00, 0x1F, 0x01, 0x01, 0x1E,
0x00, 0x19, 0x1D, 0x17, 0x12,
0x00, 0x3C, 0x3C, 0x3C, 0x3C,
0x00, 0x00, 0x00, 0x00, 0x00
};
#endif // FONT5X7_H