Ported to C++ for the ATMega328P
Added fancier display elements now that we've got room to spare
This commit is contained in:
parent
5f89b3efd2
commit
a5eab1427d
|
@ -1,3 +1,3 @@
|
||||||
REM Internal 8 Mhz oscillator, no clock division
|
REM Internal 8 Mhz oscillator, no clock division
|
||||||
REM http://www.engbedded.com/fusecalc/
|
REM http://www.engbedded.com/fusecalc/
|
||||||
@avrdude -c usbtiny -p t2313 -e -U lfuse:w:0xe4:m -U hfuse:w:0xdf:m -U efuse:w:0xff:m
|
@avrdude -c usbtiny -p m328p -e -U lfuse:w:0xE2:m -U hfuse:w:0xd9:m -U efuse:w:0x07:m
|
|
@ -13,7 +13,11 @@ env_default = board
|
||||||
|
|
||||||
[env:board]
|
[env:board]
|
||||||
platform = atmelavr
|
platform = atmelavr
|
||||||
board = attiny4313
|
framework = arduino
|
||||||
|
board = 328p8m
|
||||||
board_build.f_cpu = 8000000L
|
board_build.f_cpu = 8000000L
|
||||||
upload_speed = 115200
|
upload_speed = 115200
|
||||||
upload_protocol = usbtiny
|
upload_protocol = usbtiny
|
||||||
|
|
||||||
|
lib_deps =
|
||||||
|
Adafruit GFX Library
|
Binary file not shown.
Binary file not shown.
|
@ -1,126 +0,0 @@
|
||||||
/*
|
|
||||||
* Stairs lighting
|
|
||||||
* Copyright 2017 (c) Mark van Renswoude
|
|
||||||
*
|
|
||||||
* https://git.x2software.net/pub/Stairs
|
|
||||||
*/
|
|
||||||
#include "display.h"
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <avr/pgmspace.h>
|
|
||||||
#include "lib/VeryTinySSD1306.h"
|
|
||||||
#include "lib/VeryTinyBigChars.h"
|
|
||||||
#include "lib/TinyMillis.h"
|
|
||||||
#include "global.h"
|
|
||||||
|
|
||||||
|
|
||||||
#define CharSpacing 4
|
|
||||||
#define CharOuterWidth (SSD1306_CharWidth_32px + CharSpacing)
|
|
||||||
#define SingleCharCenteredX ((SSD1306_DisplayWidth - SSD1306_CharWidth_32px) / 2)
|
|
||||||
#define WaitAnimationInterval 250
|
|
||||||
|
|
||||||
|
|
||||||
#define WaitAnimationMaxStep 3
|
|
||||||
const uint16_t WaitAnimation [] PROGMEM = {
|
|
||||||
0b0000000000000100, // Left
|
|
||||||
0b0000000010000000, // Middle
|
|
||||||
0b0001000000000000, // Right
|
|
||||||
0b0000000010000000 // Middle
|
|
||||||
};
|
|
||||||
/*
|
|
||||||
#define WaitAnimationMaxStep 7
|
|
||||||
const uint16_t WaitAnimation [] PROGMEM = {
|
|
||||||
0b0000000001000000, // Top
|
|
||||||
0b0000100000000000, // Top-right
|
|
||||||
0b0001000000000000, // Right
|
|
||||||
0b0010000000000000, // Bottom-right
|
|
||||||
0b0000000100000000, // Bottom
|
|
||||||
0b0000000000001000, // Bottom-left
|
|
||||||
0b0000000000000100, // Left
|
|
||||||
0b0000000000000010 // Top-left
|
|
||||||
};
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
bool isWait = false;
|
|
||||||
uint16_t lastWait = 0;
|
|
||||||
uint8_t waitAnimationStep = 0;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
inline void display_drawNumber(uint8_t* x, uint8_t value)
|
|
||||||
{
|
|
||||||
if (value > 9)
|
|
||||||
{
|
|
||||||
ssd1306_drawdigit_32px(*x, value / 10);
|
|
||||||
*x += CharOuterWidth;
|
|
||||||
ssd1306_drawdigit_32px(*x, value % 10);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
ssd1306_drawdigit_32px(*x, value);
|
|
||||||
|
|
||||||
*x += CharOuterWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void display_init(void)
|
|
||||||
{
|
|
||||||
ssd1306_begin_default();
|
|
||||||
ssd1306_clear();
|
|
||||||
ssd1306_on();
|
|
||||||
ssd1306_switchRenderFrame();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void display_drawIndex(void)
|
|
||||||
{
|
|
||||||
ssd1306_clear();
|
|
||||||
|
|
||||||
uint8_t moduleIndex = global_getModuleIndex();
|
|
||||||
|
|
||||||
if (moduleIndex == ModuleIndexUndefined)
|
|
||||||
{
|
|
||||||
ssd1306_drawminus_32px(SingleCharCenteredX);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
uint8_t firstStep = (moduleIndex * 2) + 1;
|
|
||||||
uint8_t totalWidth = 3 * CharOuterWidth;
|
|
||||||
|
|
||||||
if (firstStep == 9)
|
|
||||||
totalWidth += CharOuterWidth;
|
|
||||||
else if (firstStep > 9)
|
|
||||||
totalWidth += 2 * CharOuterWidth;
|
|
||||||
|
|
||||||
|
|
||||||
uint8_t currentX = (SSD1306_DisplayWidth - totalWidth) / 2;
|
|
||||||
display_drawNumber(¤tX, firstStep);
|
|
||||||
|
|
||||||
ssd1306_drawminus_32px(currentX);
|
|
||||||
currentX += CharOuterWidth;
|
|
||||||
|
|
||||||
display_drawNumber(¤tX, firstStep + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
ssd1306_switchFrame();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void display_drawWait(void)
|
|
||||||
{
|
|
||||||
uint16_t currentTime = millis();
|
|
||||||
|
|
||||||
if (isWait && currentTime - lastWait < WaitAnimationInterval)
|
|
||||||
return;
|
|
||||||
|
|
||||||
ssd1306_clear();
|
|
||||||
ssd1306_drawchar_32px(SingleCharCenteredX, pgm_read_word(&WaitAnimation[waitAnimationStep]));
|
|
||||||
ssd1306_switchFrame();
|
|
||||||
|
|
||||||
waitAnimationStep++;
|
|
||||||
if (waitAnimationStep > WaitAnimationMaxStep)
|
|
||||||
waitAnimationStep = 0;
|
|
||||||
|
|
||||||
isWait = true;
|
|
||||||
lastWait = currentTime;
|
|
||||||
}
|
|
|
@ -0,0 +1,197 @@
|
||||||
|
/*
|
||||||
|
* Stairs lighting
|
||||||
|
* Copyright 2017 (c) Mark van Renswoude
|
||||||
|
*
|
||||||
|
* https://git.x2software.net/pub/Stairs
|
||||||
|
*/
|
||||||
|
#include "display.h"
|
||||||
|
#include <Fonts/FreeSans12pt7b.h>
|
||||||
|
#include "global.h"
|
||||||
|
#include "icons.h"
|
||||||
|
|
||||||
|
#define WaitAnimationInterval 150
|
||||||
|
|
||||||
|
|
||||||
|
void Display::init()
|
||||||
|
{
|
||||||
|
mDisplay = new Adafruit_SSD1306(128, 32, &Wire, -1);
|
||||||
|
mDisplay->begin(SSD1306_SWITCHCAPVCC, 0x3C);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Display::show(Screen screen)
|
||||||
|
{
|
||||||
|
switch (screen)
|
||||||
|
{
|
||||||
|
case Screen::Blank:
|
||||||
|
off();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Screen::WaitingForComm:
|
||||||
|
drawWaitingForComm();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Screen::ModuleIndex:
|
||||||
|
drawModuleIndex();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Display::drawWaitingForComm()
|
||||||
|
{
|
||||||
|
uint32_t currentTime = millis();
|
||||||
|
if (mLastScreen == Screen::WaitingForComm && currentTime - mLastWaiting < WaitAnimationInterval)
|
||||||
|
return;
|
||||||
|
|
||||||
|
checkOn();
|
||||||
|
mDisplay->clearDisplay();
|
||||||
|
drawTitle("Waiting for signal");
|
||||||
|
|
||||||
|
uint8_t xOffset = (mWaitAnimationStep == 1 || mWaitAnimationStep == 2) ? WaitCursorSegmentWidth : 0;
|
||||||
|
uint8_t yOffset = (mWaitAnimationStep == 2 || mWaitAnimationStep == 3) ? WaitCursorSegmentHeight : 0;
|
||||||
|
drawRotatedBitmap(xOffset, mDisplay->height() - (WaitCursorSegmentHeight * 2) + yOffset, WaitCursorSegment, WaitCursorSegmentWidth, WaitCursorSegmentHeight, mWaitAnimationStep);
|
||||||
|
|
||||||
|
mDisplay->display();
|
||||||
|
|
||||||
|
mWaitAnimationStep++;
|
||||||
|
if (mWaitAnimationStep > 3)
|
||||||
|
mWaitAnimationStep = 0;
|
||||||
|
|
||||||
|
mLastScreen = Screen::WaitingForComm;
|
||||||
|
mLastWaiting = currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Display::drawModuleIndex()
|
||||||
|
{
|
||||||
|
uint8_t moduleIndex = settings.getModuleIndex();
|
||||||
|
if (mLastScreen == Screen::ModuleIndex && mLastModuleIndex == moduleIndex)
|
||||||
|
return;
|
||||||
|
|
||||||
|
checkOn();
|
||||||
|
mDisplay->clearDisplay();
|
||||||
|
drawTitle("Steps");
|
||||||
|
drawCommIcon();
|
||||||
|
|
||||||
|
mDisplay->setFont(&FreeSans12pt7b);
|
||||||
|
mDisplay->setCursor(0, mDisplay->height());
|
||||||
|
|
||||||
|
// ToDo show numbers based on module index
|
||||||
|
uint8_t firstStep = (moduleIndex * 2) + 1;
|
||||||
|
mDisplay->print(firstStep);
|
||||||
|
mDisplay->print(" - ");
|
||||||
|
mDisplay->print(firstStep + 1);
|
||||||
|
|
||||||
|
mDisplay->display();
|
||||||
|
|
||||||
|
mLastScreen = Screen::ModuleIndex;
|
||||||
|
mLastModuleIndex = moduleIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Display::off()
|
||||||
|
{
|
||||||
|
if (mLastScreen != Screen::Blank)
|
||||||
|
{
|
||||||
|
mDisplay->ssd1306_command(SSD1306_DISPLAYOFF);
|
||||||
|
mLastScreen = Screen::Blank;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Display::checkOn()
|
||||||
|
{
|
||||||
|
if (mLastScreen == Screen::Blank)
|
||||||
|
mDisplay->ssd1306_command(SSD1306_DISPLAYON);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Display::drawTitle(const char* title)
|
||||||
|
{
|
||||||
|
mDisplay->setTextSize(1);
|
||||||
|
mDisplay->setTextColor(WHITE);
|
||||||
|
|
||||||
|
mDisplay->setFont(NULL);
|
||||||
|
mDisplay->setCursor(0, 0);
|
||||||
|
mDisplay->print(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Display::drawCommIcon()
|
||||||
|
{
|
||||||
|
// ToDo show actual state depending on last received message (should never be more than half a second ago)
|
||||||
|
mDisplay->drawBitmap(mDisplay->width() - IconCommWidth, 0, IconCommOff, IconCommWidth, IconCommHeight, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
typedef void (*WritePixelProc)(Adafruit_SSD1306* display, int8_t boundsX, int8_t boundsY, int8_t boundsW, int8_t boundsH, int8_t relativeX, int8_t relativeY);
|
||||||
|
|
||||||
|
|
||||||
|
void writePixel0(Adafruit_SSD1306* display, int8_t boundsX, int8_t boundsY, int8_t boundsW, int8_t boundsH, int8_t relativeX, int8_t relativeY)
|
||||||
|
{
|
||||||
|
display->writePixel(boundsX + relativeX, boundsY + relativeY, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void writePixel90(Adafruit_SSD1306* display, int8_t boundsX, int8_t boundsY, int8_t boundsW, int8_t boundsH, int8_t relativeX, int8_t relativeY)
|
||||||
|
{
|
||||||
|
display->writePixel(boundsX + boundsW - relativeY, boundsY + relativeX, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void writePixel180(Adafruit_SSD1306* display, int8_t boundsX, int8_t boundsY, int8_t boundsW, int8_t boundsH, int8_t relativeX, int8_t relativeY)
|
||||||
|
{
|
||||||
|
display->writePixel(boundsX + boundsW - relativeX, boundsY + boundsH - relativeY, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void writePixel270(Adafruit_SSD1306* display, int8_t boundsX, int8_t boundsY, int8_t boundsW, int8_t boundsH, int8_t relativeX, int8_t relativeY)
|
||||||
|
{
|
||||||
|
display->writePixel(boundsX + relativeY, boundsY + boundsH - relativeX, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Display::drawRotatedBitmap(int8_t x, int8_t y, const uint8_t bitmap[], int8_t w, int8_t h, uint8_t rotation)
|
||||||
|
{
|
||||||
|
int16_t byteWidth = (w + 7) / 8;
|
||||||
|
int8_t byte = 0;
|
||||||
|
|
||||||
|
mDisplay->startWrite();
|
||||||
|
|
||||||
|
WritePixelProc writePixel;
|
||||||
|
switch (rotation)
|
||||||
|
{
|
||||||
|
case 1: // 90 degrees
|
||||||
|
writePixel = writePixel90;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2: // 180 degrees
|
||||||
|
writePixel = writePixel180;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3: // 270 degrees
|
||||||
|
writePixel = writePixel270;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: // 0 degrees
|
||||||
|
writePixel = writePixel0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int8_t relativeY = 0; relativeY < h; relativeY++)
|
||||||
|
{
|
||||||
|
for(int8_t relativeX = 0; relativeX < w; relativeX++)
|
||||||
|
{
|
||||||
|
if(relativeX & 7)
|
||||||
|
byte <<= 1;
|
||||||
|
else
|
||||||
|
byte = pgm_read_byte(&bitmap[relativeY * byteWidth + relativeX / 8]);
|
||||||
|
|
||||||
|
if (byte & 0x80)
|
||||||
|
writePixel(mDisplay, x, y, w, h, relativeX, relativeY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mDisplay->endWrite();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,41 @@
|
||||||
#ifndef __display
|
#ifndef __display
|
||||||
#define __display
|
#define __display
|
||||||
|
|
||||||
void display_init(void);
|
#include <Arduino.h>
|
||||||
void display_drawIndex(void);
|
#include <Wire.h>
|
||||||
void display_drawWait(void);
|
#include <Adafruit_GFX.h>
|
||||||
|
#include "lib/Adafruit_SSD1306.h"
|
||||||
|
|
||||||
|
|
||||||
|
enum struct Screen: uint8_t
|
||||||
|
{
|
||||||
|
Blank,
|
||||||
|
WaitingForComm,
|
||||||
|
ModuleIndex
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class Display
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
Adafruit_SSD1306* mDisplay;
|
||||||
|
Screen mLastScreen = Screen::Blank;
|
||||||
|
uint8_t mLastModuleIndex;
|
||||||
|
uint32_t mLastWaiting;
|
||||||
|
uint8_t mWaitAnimationStep = 0;
|
||||||
|
|
||||||
|
void drawRotatedBitmap(int8_t x, int8_t y, const uint8_t bitmap[], int8_t w, int8_t h, uint8_t rotation);
|
||||||
|
|
||||||
|
void checkOn();
|
||||||
|
void drawTitle(const char* title);
|
||||||
|
void drawCommIcon();
|
||||||
|
|
||||||
|
void drawWaitingForComm();
|
||||||
|
void drawModuleIndex();
|
||||||
|
public:
|
||||||
|
void init();
|
||||||
|
void show(Screen screen);
|
||||||
|
void off();
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
|
@ -1,59 +0,0 @@
|
||||||
/*
|
|
||||||
* Stairs lighting
|
|
||||||
* Copyright 2017 (c) Mark van Renswoude
|
|
||||||
*
|
|
||||||
* https://git.x2software.net/pub/Stairs
|
|
||||||
*/
|
|
||||||
#include "global.h"
|
|
||||||
#include <avr/eeprom.h>
|
|
||||||
|
|
||||||
|
|
||||||
// EEPROM
|
|
||||||
// First byte is 0xAA to recognize uninitialised EEPROM
|
|
||||||
#define EEPROMHeader 0xAA
|
|
||||||
|
|
||||||
// Second byte is a version indicator
|
|
||||||
#define EEPROMCurrentVersion 1
|
|
||||||
|
|
||||||
// Version 1 fields
|
|
||||||
// index: 2
|
|
||||||
uint8_t moduleIndex = ModuleIndexUndefined;
|
|
||||||
|
|
||||||
|
|
||||||
void global_init(void)
|
|
||||||
{
|
|
||||||
uint8_t address = 0;
|
|
||||||
if (eeprom_read_byte(&address) != EEPROMHeader)
|
|
||||||
{
|
|
||||||
eeprom_write_byte(&address, EEPROMHeader); address++;
|
|
||||||
eeprom_write_byte(&address, EEPROMCurrentVersion); address++;
|
|
||||||
eeprom_write_byte(&address, moduleIndex); address++;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
address++;
|
|
||||||
uint8_t version = eeprom_read_byte(&address); address++;
|
|
||||||
if (version >= 1)
|
|
||||||
{
|
|
||||||
moduleIndex = eeprom_read_byte(&address); address++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
uint8_t global_getModuleIndex(void)
|
|
||||||
{
|
|
||||||
return moduleIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void global_setModuleIndex(uint8_t index)
|
|
||||||
{
|
|
||||||
if (index == moduleIndex)
|
|
||||||
return;
|
|
||||||
|
|
||||||
moduleIndex = index;
|
|
||||||
|
|
||||||
uint8_t address = 2;
|
|
||||||
eeprom_write_byte(&address, moduleIndex);
|
|
||||||
}
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Stairs lighting
|
||||||
|
* Copyright 2017 (c) Mark van Renswoude
|
||||||
|
*
|
||||||
|
* https://git.x2software.net/pub/Stairs
|
||||||
|
*/
|
||||||
|
#include "global.h"
|
||||||
|
|
||||||
|
|
||||||
|
int serialRead()
|
||||||
|
{
|
||||||
|
return Serial.read();
|
||||||
|
}
|
||||||
|
|
||||||
|
int serialAvailable()
|
||||||
|
{
|
||||||
|
return Serial.available();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t serialWrite(const byte what)
|
||||||
|
{
|
||||||
|
return Serial.write(what);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Settings settings;
|
||||||
|
Display display;
|
||||||
|
RS485 comm(serialRead, serialAvailable, serialWrite, 20);
|
||||||
|
PCA9685 ledDriver;
|
|
@ -7,13 +7,14 @@
|
||||||
#ifndef __global
|
#ifndef __global
|
||||||
#define __global
|
#define __global
|
||||||
|
|
||||||
#include <stdint.h>
|
#include "lib/RS485_non_blocking.h"
|
||||||
|
#include "lib/PCA9685.h"
|
||||||
|
#include "settings.h"
|
||||||
|
#include "display.h"
|
||||||
|
|
||||||
#define ModuleIndexUndefined 0xff
|
extern Settings settings;
|
||||||
|
extern Display display;
|
||||||
void global_init(void);
|
extern RS485 comm;
|
||||||
|
extern PCA9685 ledDriver;
|
||||||
uint8_t global_getModuleIndex(void);
|
|
||||||
void global_setModuleIndex(uint8_t index);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Stairs lighting
|
||||||
|
* Copyright 2017 (c) Mark van Renswoude
|
||||||
|
*
|
||||||
|
* https://git.x2software.net/pub/Stairs
|
||||||
|
*/
|
||||||
|
|
||||||
|
// http://javl.github.io/image2cpp/
|
||||||
|
// Draw mode: horizontal
|
||||||
|
const uint8_t IconCommWidth = 12;
|
||||||
|
const uint8_t IconCommHeight = 8;
|
||||||
|
|
||||||
|
const uint8_t PROGMEM IconCommOff[] = {
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||||
|
};
|
||||||
|
|
||||||
|
const uint8_t PROGMEM IconCommOn[] = {
|
||||||
|
0x40, 0x20, 0x40, 0x20, 0x90, 0x90, 0xa6, 0x50, 0xa6, 0x50, 0x90, 0x90, 0x40, 0x20, 0x40, 0x20
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const uint8_t WaitCursorSegmentWidth = 10;
|
||||||
|
const uint8_t WaitCursorSegmentHeight = 10;
|
||||||
|
|
||||||
|
const uint8_t PROGMEM WaitCursorSegment[] = {
|
||||||
|
0x07, 0x80, 0x1f, 0x80, 0x3e, 0x00, 0x78, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xe0, 0x00, 0xc0, 0x00,
|
||||||
|
0xc0, 0x00, 0x00, 0x00
|
||||||
|
};
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,185 @@
|
||||||
|
/*!
|
||||||
|
* @file Adafruit_SSD1306.h
|
||||||
|
*
|
||||||
|
* This is part of for Adafruit's SSD1306 library for monochrome
|
||||||
|
* OLED displays: http://www.adafruit.com/category/63_98
|
||||||
|
*
|
||||||
|
* These displays use I2C or SPI to communicate. I2C requires 2 pins
|
||||||
|
* (SCL+SDA) and optionally a RESET pin. SPI requires 4 pins (MOSI, SCK,
|
||||||
|
* select, data/command) and optionally a reset pin. Hardware SPI or
|
||||||
|
* 'bitbang' software SPI are both supported.
|
||||||
|
*
|
||||||
|
* Adafruit invests time and resources providing this open source code,
|
||||||
|
* please support Adafruit and open-source hardware by purchasing
|
||||||
|
* products from Adafruit!
|
||||||
|
*
|
||||||
|
* Written by Limor Fried/Ladyada for Adafruit Industries, with
|
||||||
|
* contributions from the open source community.
|
||||||
|
*
|
||||||
|
* BSD license, all text above, and the splash screen header file,
|
||||||
|
* must be included in any redistribution.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Modifications for Stairs project:
|
||||||
|
- Removed the Adafruit splash screen. I'm not going to show it anyways, so all it
|
||||||
|
does is take up valuable program space.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _Adafruit_SSD1306_H_
|
||||||
|
#define _Adafruit_SSD1306_H_
|
||||||
|
|
||||||
|
// ONE of the following three lines must be #defined:
|
||||||
|
//#define SSD1306_128_64 ///< DEPRECTAED: old way to specify 128x64 screen
|
||||||
|
#define SSD1306_128_32 ///< DEPRECATED: old way to specify 128x32 screen
|
||||||
|
//#define SSD1306_96_16 ///< DEPRECATED: old way to specify 96x16 screen
|
||||||
|
// This establishes the screen dimensions in old Adafruit_SSD1306 sketches
|
||||||
|
// (NEW CODE SHOULD IGNORE THIS, USE THE CONSTRUCTORS THAT ACCEPT WIDTH
|
||||||
|
// AND HEIGHT ARGUMENTS).
|
||||||
|
|
||||||
|
#if defined(ARDUINO_STM32_FEATHER)
|
||||||
|
typedef class HardwareSPI SPIClass;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <Wire.h>
|
||||||
|
#include <SPI.h>
|
||||||
|
#include <Adafruit_GFX.h>
|
||||||
|
|
||||||
|
#if defined(__AVR__)
|
||||||
|
typedef volatile uint8_t PortReg;
|
||||||
|
typedef uint8_t PortMask;
|
||||||
|
#define HAVE_PORTREG
|
||||||
|
#elif defined(__SAM3X8E__)
|
||||||
|
typedef volatile RwReg PortReg;
|
||||||
|
typedef uint32_t PortMask;
|
||||||
|
#define HAVE_PORTREG
|
||||||
|
#elif defined(__arm__) || defined(ARDUINO_FEATHER52)
|
||||||
|
typedef volatile uint32_t PortReg;
|
||||||
|
typedef uint32_t PortMask;
|
||||||
|
#define HAVE_PORTREG
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define BLACK 0 ///< Draw 'off' pixels
|
||||||
|
#define WHITE 1 ///< Draw 'on' pixels
|
||||||
|
#define INVERSE 2 ///< Invert pixels
|
||||||
|
|
||||||
|
#define SSD1306_MEMORYMODE 0x20 ///< See datasheet
|
||||||
|
#define SSD1306_COLUMNADDR 0x21 ///< See datasheet
|
||||||
|
#define SSD1306_PAGEADDR 0x22 ///< See datasheet
|
||||||
|
#define SSD1306_SETCONTRAST 0x81 ///< See datasheet
|
||||||
|
#define SSD1306_CHARGEPUMP 0x8D ///< See datasheet
|
||||||
|
#define SSD1306_SEGREMAP 0xA0 ///< See datasheet
|
||||||
|
#define SSD1306_DISPLAYALLON_RESUME 0xA4 ///< See datasheet
|
||||||
|
#define SSD1306_DISPLAYALLON 0xA5 ///< Not currently used
|
||||||
|
#define SSD1306_NORMALDISPLAY 0xA6 ///< See datasheet
|
||||||
|
#define SSD1306_INVERTDISPLAY 0xA7 ///< See datasheet
|
||||||
|
#define SSD1306_SETMULTIPLEX 0xA8 ///< See datasheet
|
||||||
|
#define SSD1306_DISPLAYOFF 0xAE ///< See datasheet
|
||||||
|
#define SSD1306_DISPLAYON 0xAF ///< See datasheet
|
||||||
|
#define SSD1306_COMSCANINC 0xC0 ///< Not currently used
|
||||||
|
#define SSD1306_COMSCANDEC 0xC8 ///< See datasheet
|
||||||
|
#define SSD1306_SETDISPLAYOFFSET 0xD3 ///< See datasheet
|
||||||
|
#define SSD1306_SETDISPLAYCLOCKDIV 0xD5 ///< See datasheet
|
||||||
|
#define SSD1306_SETPRECHARGE 0xD9 ///< See datasheet
|
||||||
|
#define SSD1306_SETCOMPINS 0xDA ///< See datasheet
|
||||||
|
#define SSD1306_SETVCOMDETECT 0xDB ///< See datasheet
|
||||||
|
|
||||||
|
#define SSD1306_SETLOWCOLUMN 0x00 ///< Not currently used
|
||||||
|
#define SSD1306_SETHIGHCOLUMN 0x10 ///< Not currently used
|
||||||
|
#define SSD1306_SETSTARTLINE 0x40 ///< See datasheet
|
||||||
|
|
||||||
|
#define SSD1306_EXTERNALVCC 0x01 ///< External display voltage source
|
||||||
|
#define SSD1306_SWITCHCAPVCC 0x02 ///< Gen. display voltage from 3.3V
|
||||||
|
|
||||||
|
#define SSD1306_RIGHT_HORIZONTAL_SCROLL 0x26 ///< Init rt scroll
|
||||||
|
#define SSD1306_LEFT_HORIZONTAL_SCROLL 0x27 ///< Init left scroll
|
||||||
|
#define SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL 0x29 ///< Init diag scroll
|
||||||
|
#define SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL 0x2A ///< Init diag scroll
|
||||||
|
#define SSD1306_DEACTIVATE_SCROLL 0x2E ///< Stop scroll
|
||||||
|
#define SSD1306_ACTIVATE_SCROLL 0x2F ///< Start scroll
|
||||||
|
#define SSD1306_SET_VERTICAL_SCROLL_AREA 0xA3 ///< Set scroll range
|
||||||
|
|
||||||
|
// Deprecated size stuff for backwards compatibility with old sketches
|
||||||
|
#if defined SSD1306_128_64
|
||||||
|
#define SSD1306_LCDWIDTH 128 ///< DEPRECATED: width w/SSD1306_128_64 defined
|
||||||
|
#define SSD1306_LCDHEIGHT 64 ///< DEPRECATED: height w/SSD1306_128_64 defined
|
||||||
|
#endif
|
||||||
|
#if defined SSD1306_128_32
|
||||||
|
#define SSD1306_LCDWIDTH 128 ///< DEPRECATED: width w/SSD1306_128_32 defined
|
||||||
|
#define SSD1306_LCDHEIGHT 32 ///< DEPRECATED: height w/SSD1306_128_32 defined
|
||||||
|
#endif
|
||||||
|
#if defined SSD1306_96_16
|
||||||
|
#define SSD1306_LCDWIDTH 96 ///< DEPRECATED: width w/SSD1306_96_16 defined
|
||||||
|
#define SSD1306_LCDHEIGHT 16 ///< DEPRECATED: height w/SSD1306_96_16 defined
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Class that stores state and functions for interacting with
|
||||||
|
SSD1306 OLED displays.
|
||||||
|
*/
|
||||||
|
class Adafruit_SSD1306 : public Adafruit_GFX {
|
||||||
|
public:
|
||||||
|
// NEW CONSTRUCTORS -- recommended for new projects
|
||||||
|
Adafruit_SSD1306(uint8_t w, uint8_t h, TwoWire *twi=&Wire, int8_t rst_pin=-1,
|
||||||
|
uint32_t clkDuring=400000UL, uint32_t clkAfter=100000UL);
|
||||||
|
Adafruit_SSD1306(uint8_t w, uint8_t h, int8_t mosi_pin, int8_t sclk_pin,
|
||||||
|
int8_t dc_pin, int8_t rst_pin, int8_t cs_pin);
|
||||||
|
Adafruit_SSD1306(uint8_t w, uint8_t h, SPIClass *spi,
|
||||||
|
int8_t dc_pin, int8_t rst_pin, int8_t cs_pin, uint32_t bitrate=8000000UL);
|
||||||
|
|
||||||
|
// DEPRECATED CONSTRUCTORS - for back compatibility, avoid in new projects
|
||||||
|
Adafruit_SSD1306(int8_t mosi_pin, int8_t sclk_pin, int8_t dc_pin,
|
||||||
|
int8_t rst_pin, int8_t cs_pin);
|
||||||
|
Adafruit_SSD1306(int8_t dc_pin, int8_t rst_pin, int8_t cs_pin);
|
||||||
|
Adafruit_SSD1306(int8_t rst_pin = -1);
|
||||||
|
|
||||||
|
~Adafruit_SSD1306(void);
|
||||||
|
|
||||||
|
boolean begin(uint8_t switchvcc=SSD1306_SWITCHCAPVCC,
|
||||||
|
uint8_t i2caddr=0, boolean reset=true,
|
||||||
|
boolean periphBegin=true);
|
||||||
|
void display(void);
|
||||||
|
void clearDisplay(void);
|
||||||
|
void invertDisplay(boolean i);
|
||||||
|
void dim(boolean dim);
|
||||||
|
void drawPixel(int16_t x, int16_t y, uint16_t color);
|
||||||
|
virtual void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color);
|
||||||
|
virtual void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color);
|
||||||
|
void startscrollright(uint8_t start, uint8_t stop);
|
||||||
|
void startscrollleft(uint8_t start, uint8_t stop);
|
||||||
|
void startscrolldiagright(uint8_t start, uint8_t stop);
|
||||||
|
void startscrolldiagleft(uint8_t start, uint8_t stop);
|
||||||
|
void stopscroll(void);
|
||||||
|
void ssd1306_command(uint8_t c);
|
||||||
|
boolean getPixel(int16_t x, int16_t y);
|
||||||
|
uint8_t *getBuffer(void);
|
||||||
|
|
||||||
|
private:
|
||||||
|
inline void SPIwrite(uint8_t d) __attribute__((always_inline));
|
||||||
|
void drawFastHLineInternal(int16_t x, int16_t y, int16_t w,
|
||||||
|
uint16_t color);
|
||||||
|
void drawFastVLineInternal(int16_t x, int16_t y, int16_t h,
|
||||||
|
uint16_t color);
|
||||||
|
void ssd1306_command1(uint8_t c);
|
||||||
|
void ssd1306_commandList(const uint8_t *c, uint8_t n);
|
||||||
|
|
||||||
|
SPIClass *spi;
|
||||||
|
TwoWire *wire;
|
||||||
|
uint8_t *buffer;
|
||||||
|
int8_t i2caddr, vccstate, page_end;
|
||||||
|
int8_t mosiPin , clkPin , dcPin , csPin, rstPin;
|
||||||
|
#ifdef HAVE_PORTREG
|
||||||
|
PortReg *mosiPort , *clkPort , *dcPort , *csPort;
|
||||||
|
PortMask mosiPinMask, clkPinMask, dcPinMask, csPinMask;
|
||||||
|
#endif
|
||||||
|
#if defined(SPI_HAS_TRANSACTION)
|
||||||
|
SPISettings spiSettings;
|
||||||
|
#endif
|
||||||
|
#if ARDUINO >= 157
|
||||||
|
uint32_t wireClk; // Wire speed for SSD1306 transfers
|
||||||
|
uint32_t restoreClk; // Wire speed following SSD1306 transfers
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // _Adafruit_SSD1306_H_
|
|
@ -0,0 +1,127 @@
|
||||||
|
/*
|
||||||
|
* PCA9685 library for Arduino
|
||||||
|
* Copyright 2017 (c) Mark van Renswoude
|
||||||
|
*/
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "./PCA9685.h"
|
||||||
|
#include <Wire.h>
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
|
||||||
|
void PCA9685::setAddress(uint8_t address)
|
||||||
|
{
|
||||||
|
this->mAddress = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t PCA9685::read(uint8_t registerAddress)
|
||||||
|
{
|
||||||
|
uint8_t result = 0;
|
||||||
|
|
||||||
|
Wire.beginTransmission(this->mAddress);
|
||||||
|
Wire.write(registerAddress);
|
||||||
|
Wire.endTransmission();
|
||||||
|
|
||||||
|
Wire.requestFrom(this->mAddress, (uint8_t)1);
|
||||||
|
if (Wire.available())
|
||||||
|
result = Wire.read();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void PCA9685::write(uint8_t registerAddress, uint8_t value)
|
||||||
|
{
|
||||||
|
Wire.beginTransmission(this->mAddress);
|
||||||
|
Wire.write(registerAddress);
|
||||||
|
Wire.write(value);
|
||||||
|
Wire.endTransmission();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
inline void PCA9685::write(uint8_t data)
|
||||||
|
{
|
||||||
|
Wire.write(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static const float prescaleValue = 25000000 / 4096;
|
||||||
|
|
||||||
|
void PCA9685::setPWMFrequency(float frequency)
|
||||||
|
{
|
||||||
|
// Credit to the Adafruit PWM Servo Driver library for these calculations
|
||||||
|
// https://github.com/adafruit/Adafruit-PWM-Servo-Driver-Library/
|
||||||
|
uint8_t prescale = floor(prescaleValue / (frequency * 0.9) - 0.5);
|
||||||
|
|
||||||
|
// Sleep while changing the frequency
|
||||||
|
uint8_t oldMode = this->read(PCA9685::RegisterMode1) & !PCA9685::Mode1Sleep;
|
||||||
|
uint8_t newMode = (oldMode & !PCA9685::Mode1Restart) | PCA9685::Mode1Sleep;
|
||||||
|
|
||||||
|
this->write(PCA9685::RegisterMode1, newMode);
|
||||||
|
this->write(PCA9685::RegisterPrescale, prescale);
|
||||||
|
this->write(PCA9685::RegisterMode1, oldMode);
|
||||||
|
|
||||||
|
// According to the datasheet:
|
||||||
|
// It takes 500 us max. for the oscillator to be up and running once
|
||||||
|
// SLEEP bit has been set to logic 0
|
||||||
|
//
|
||||||
|
// The Adafruit library uses 5 milliseconds, so I'll stick to that as well.
|
||||||
|
delay(5);
|
||||||
|
|
||||||
|
// Restart and turn on auto-increment (required for SetPWM)
|
||||||
|
this->write(PCA9685::RegisterMode1, oldMode | PCA9685::Mode1Restart | PCA9685::Mode1AI | PCA9685::Mode1AllCall);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void PCA9685::setPWM(uint8_t pin, uint16_t value)
|
||||||
|
{
|
||||||
|
if (value < 0) value = 0;
|
||||||
|
if (value > 4095) value = 4095;
|
||||||
|
|
||||||
|
if (value == 4095)
|
||||||
|
this->setPWM(pin, 4096, 0);
|
||||||
|
else if (value == 0)
|
||||||
|
this->setPWM(pin, 0, 4096);
|
||||||
|
else
|
||||||
|
this->setPWM(pin, 0, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void PCA9685::setPWM(uint8_t pin, uint16_t on, uint16_t off)
|
||||||
|
{
|
||||||
|
Wire.beginTransmission(this->mAddress);
|
||||||
|
this->write(PCA9685::RegisterLED0OnL + (4 * pin));
|
||||||
|
this->write(on);
|
||||||
|
this->write(on >> 8);
|
||||||
|
this->write(off);
|
||||||
|
this->write(off >> 8);
|
||||||
|
Wire.endTransmission();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void PCA9685::setAll(uint16_t value)
|
||||||
|
{
|
||||||
|
if (value < 0) value = 0;
|
||||||
|
if (value > 4095) value = 4095;
|
||||||
|
|
||||||
|
if (value == 4095)
|
||||||
|
this->setAll(4096, 0);
|
||||||
|
else if (value == 0)
|
||||||
|
this->setAll(0, 4096);
|
||||||
|
else
|
||||||
|
this->setAll(0, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void PCA9685::setAll(uint16_t on, uint16_t off)
|
||||||
|
{
|
||||||
|
Wire.beginTransmission(this->mAddress);
|
||||||
|
this->write(PCA9685::RegisterAllLEDOnL);
|
||||||
|
this->write(on);
|
||||||
|
this->write(on >> 8);
|
||||||
|
this->write(off);
|
||||||
|
this->write(off >> 8);
|
||||||
|
Wire.endTransmission();
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* PCA9685 library for Arduino
|
||||||
|
* Copyright 2017 (c) Mark van Renswoude
|
||||||
|
*/
|
||||||
|
#ifndef __PCA9685
|
||||||
|
#define __PCA9685
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
|
||||||
|
class PCA9685
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
uint8_t mAddress;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint8_t read(uint8_t registerAddress);
|
||||||
|
void write(uint8_t registerAddress, uint8_t value);
|
||||||
|
inline void write(uint8_t data);
|
||||||
|
|
||||||
|
public:
|
||||||
|
static const uint16_t Off = 0;
|
||||||
|
static const uint16_t On = 4095;
|
||||||
|
|
||||||
|
static const uint8_t RegisterMode1 = 0x0;
|
||||||
|
static const uint8_t RegisterPrescale = 0xFE;
|
||||||
|
|
||||||
|
static const uint8_t RegisterLED0OnL = 0x6;
|
||||||
|
static const uint8_t RegisterAllLEDOnL = 0xFA;
|
||||||
|
|
||||||
|
static const uint8_t Mode1Restart = 0x80;
|
||||||
|
static const uint8_t Mode1AI = 0x20;
|
||||||
|
static const uint8_t Mode1Sleep = 0x10;
|
||||||
|
static const uint8_t Mode1AllCall = 0x01;
|
||||||
|
|
||||||
|
// Call this if you already initialized the I2C library
|
||||||
|
void setAddress(uint8_t address);
|
||||||
|
|
||||||
|
void setPWMFrequency(float frequency);
|
||||||
|
|
||||||
|
void setPWM(uint8_t pin, uint16_t value);
|
||||||
|
void setPWM(uint8_t pin, uint16_t on, uint16_t off);
|
||||||
|
|
||||||
|
void setAll(uint16_t value);
|
||||||
|
void setAll(uint16_t on, uint16_t off);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,232 @@
|
||||||
|
/*
|
||||||
|
RS485 protocol library - non-blocking.
|
||||||
|
|
||||||
|
Devised and written by Nick Gammon.
|
||||||
|
Date: 4 December 2012
|
||||||
|
Version: 1.0
|
||||||
|
|
||||||
|
Can send from 1 to 255 bytes from one node to another with:
|
||||||
|
|
||||||
|
* Packet start indicator (STX)
|
||||||
|
* Each data byte is doubled and inverted to check validity
|
||||||
|
* Packet end indicator (ETX)
|
||||||
|
* Packet CRC (checksum)
|
||||||
|
|
||||||
|
|
||||||
|
To allow flexibility with hardware (eg. Serial, SoftwareSerial, I2C)
|
||||||
|
you provide three "callback" functions which send or receive data. Examples are:
|
||||||
|
|
||||||
|
size_t fWrite (const byte what)
|
||||||
|
{
|
||||||
|
return Serial.write (what);
|
||||||
|
}
|
||||||
|
|
||||||
|
int fAvailable ()
|
||||||
|
{
|
||||||
|
return Serial.available ();
|
||||||
|
}
|
||||||
|
|
||||||
|
int fRead ()
|
||||||
|
{
|
||||||
|
return Serial.read ();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
PERMISSION TO DISTRIBUTE
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||||
|
and associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||||
|
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
|
||||||
|
LIMITATION OF LIABILITY
|
||||||
|
|
||||||
|
The software is provided "as is", without warranty of any kind, express or implied,
|
||||||
|
including but not limited to the warranties of merchantability, fitness for a particular
|
||||||
|
purpose and noninfringement. In no event shall the authors or copyright holders be liable
|
||||||
|
for any claim, damages or other liability, whether in an action of contract,
|
||||||
|
tort or otherwise, arising from, out of or in connection with the software
|
||||||
|
or the use or other dealings in the software.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "RS485_non_blocking.h"
|
||||||
|
|
||||||
|
// allocate the requested buffer size
|
||||||
|
void RS485::begin ()
|
||||||
|
{
|
||||||
|
data_ = (byte *) malloc (bufferSize_);
|
||||||
|
reset ();
|
||||||
|
errorCount_ = 0;
|
||||||
|
} // end of RS485::begin
|
||||||
|
|
||||||
|
// get rid of the buffer
|
||||||
|
void RS485::stop ()
|
||||||
|
{
|
||||||
|
reset ();
|
||||||
|
free (data_);
|
||||||
|
data_ = NULL;
|
||||||
|
} // end of RS485::stop
|
||||||
|
|
||||||
|
// called after an error to return to "not in a packet"
|
||||||
|
void RS485::reset ()
|
||||||
|
{
|
||||||
|
haveSTX_ = false;
|
||||||
|
available_ = false;
|
||||||
|
inputPos_ = 0;
|
||||||
|
startTime_ = 0;
|
||||||
|
} // end of RS485::reset
|
||||||
|
|
||||||
|
// calculate 8-bit CRC
|
||||||
|
byte RS485::crc8 (const byte *addr, byte len)
|
||||||
|
{
|
||||||
|
byte crc = 0;
|
||||||
|
while (len--)
|
||||||
|
{
|
||||||
|
byte inbyte = *addr++;
|
||||||
|
for (byte i = 8; i; i--)
|
||||||
|
{
|
||||||
|
byte mix = (crc ^ inbyte) & 0x01;
|
||||||
|
crc >>= 1;
|
||||||
|
if (mix)
|
||||||
|
crc ^= 0x8C;
|
||||||
|
inbyte >>= 1;
|
||||||
|
} // end of for
|
||||||
|
} // end of while
|
||||||
|
return crc;
|
||||||
|
} // end of RS485::crc8
|
||||||
|
|
||||||
|
// send a byte complemented, repeated
|
||||||
|
// only values sent would be (in hex):
|
||||||
|
// 0F, 1E, 2D, 3C, 4B, 5A, 69, 78, 87, 96, A5, B4, C3, D2, E1, F0
|
||||||
|
void RS485::sendComplemented (const byte what)
|
||||||
|
{
|
||||||
|
byte c;
|
||||||
|
|
||||||
|
// first nibble
|
||||||
|
c = what >> 4;
|
||||||
|
fWriteCallback_ ((c << 4) | (c ^ 0x0F));
|
||||||
|
|
||||||
|
// second nibble
|
||||||
|
c = what & 0x0F;
|
||||||
|
fWriteCallback_ ((c << 4) | (c ^ 0x0F));
|
||||||
|
|
||||||
|
} // end of RS485::sendComplemented
|
||||||
|
|
||||||
|
// send a message of "length" bytes (max 255) to other end
|
||||||
|
// put STX at start, ETX at end, and add CRC
|
||||||
|
void RS485::sendMsg (const byte * data, const byte length)
|
||||||
|
{
|
||||||
|
// no callback? Can't send
|
||||||
|
if (fWriteCallback_ == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
fWriteCallback_ (STX); // STX
|
||||||
|
for (byte i = 0; i < length; i++)
|
||||||
|
sendComplemented (data [i]);
|
||||||
|
fWriteCallback_ (ETX); // ETX
|
||||||
|
sendComplemented (crc8 (data, length));
|
||||||
|
} // end of RS485::sendMsg
|
||||||
|
|
||||||
|
// called periodically from main loop to process data and
|
||||||
|
// assemble the finished packet in 'data_'
|
||||||
|
|
||||||
|
// returns true if packet received.
|
||||||
|
|
||||||
|
// You could implement a timeout by seeing if isPacketStarted() returns
|
||||||
|
// true, and if too much time has passed since getPacketStartTime() time.
|
||||||
|
|
||||||
|
bool RS485::update ()
|
||||||
|
{
|
||||||
|
// no data? can't go ahead (eg. begin() not called)
|
||||||
|
if (data_ == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// no callbacks? Can't read
|
||||||
|
if (fAvailableCallback_ == NULL || fReadCallback_ == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
while (fAvailableCallback_ () > 0)
|
||||||
|
{
|
||||||
|
byte inByte = fReadCallback_ ();
|
||||||
|
|
||||||
|
switch (inByte)
|
||||||
|
{
|
||||||
|
|
||||||
|
case STX: // start of text
|
||||||
|
haveSTX_ = true;
|
||||||
|
haveETX_ = false;
|
||||||
|
inputPos_ = 0;
|
||||||
|
firstNibble_ = true;
|
||||||
|
startTime_ = millis ();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ETX: // end of text (now expect the CRC check)
|
||||||
|
haveETX_ = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// wait until packet officially starts
|
||||||
|
if (!haveSTX_)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// check byte is in valid form (4 bits followed by 4 bits complemented)
|
||||||
|
if ((inByte >> 4) != ((inByte & 0x0F) ^ 0x0F) )
|
||||||
|
{
|
||||||
|
reset ();
|
||||||
|
errorCount_++;
|
||||||
|
break; // bad character
|
||||||
|
} // end if bad byte
|
||||||
|
|
||||||
|
// convert back
|
||||||
|
inByte >>= 4;
|
||||||
|
|
||||||
|
// high-order nibble?
|
||||||
|
if (firstNibble_)
|
||||||
|
{
|
||||||
|
currentByte_ = inByte;
|
||||||
|
firstNibble_ = false;
|
||||||
|
break;
|
||||||
|
} // end of first nibble
|
||||||
|
|
||||||
|
// low-order nibble
|
||||||
|
currentByte_ <<= 4;
|
||||||
|
currentByte_ |= inByte;
|
||||||
|
firstNibble_ = true;
|
||||||
|
|
||||||
|
// if we have the ETX this must be the CRC
|
||||||
|
if (haveETX_)
|
||||||
|
{
|
||||||
|
if (crc8 (data_, inputPos_) != currentByte_)
|
||||||
|
{
|
||||||
|
reset ();
|
||||||
|
errorCount_++;
|
||||||
|
break; // bad crc
|
||||||
|
} // end of bad CRC
|
||||||
|
|
||||||
|
available_ = true;
|
||||||
|
return true; // show data ready
|
||||||
|
} // end if have ETX already
|
||||||
|
|
||||||
|
// keep adding if not full
|
||||||
|
if (inputPos_ < bufferSize_)
|
||||||
|
data_ [inputPos_++] = currentByte_;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
reset (); // overflow, start again
|
||||||
|
errorCount_++;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
} // end of switch
|
||||||
|
} // end of while incoming data
|
||||||
|
|
||||||
|
return false; // not ready yet
|
||||||
|
} // end of RS485::update
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
/*
|
||||||
|
RS485 protocol library - non-blocking.
|
||||||
|
|
||||||
|
Devised and written by Nick Gammon.
|
||||||
|
Date: 4 December 2012
|
||||||
|
Version: 1.0
|
||||||
|
|
||||||
|
Licence: Released for public use.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Arduino.h"
|
||||||
|
|
||||||
|
|
||||||
|
class RS485
|
||||||
|
{
|
||||||
|
|
||||||
|
typedef size_t (*WriteCallback) (const byte what); // send a byte to serial port
|
||||||
|
typedef int (*AvailableCallback) (); // return number of bytes available
|
||||||
|
typedef int (*ReadCallback) (); // read a byte from serial port
|
||||||
|
|
||||||
|
enum {
|
||||||
|
STX = '\2', // start of text
|
||||||
|
ETX = '\3' // end of text
|
||||||
|
}; // end of enum
|
||||||
|
|
||||||
|
// callback functions to do reading/writing
|
||||||
|
ReadCallback fReadCallback_;
|
||||||
|
AvailableCallback fAvailableCallback_;
|
||||||
|
WriteCallback fWriteCallback_;
|
||||||
|
|
||||||
|
// where we save incoming stuff
|
||||||
|
byte * data_;
|
||||||
|
|
||||||
|
// how much data is in the buffer
|
||||||
|
const int bufferSize_;
|
||||||
|
|
||||||
|
// this is true once we have valid data in buf
|
||||||
|
bool available_;
|
||||||
|
|
||||||
|
// an STX (start of text) signals a packet start
|
||||||
|
bool haveSTX_;
|
||||||
|
|
||||||
|
// count of errors
|
||||||
|
unsigned long errorCount_;
|
||||||
|
|
||||||
|
// variables below are set when we get an STX
|
||||||
|
bool haveETX_;
|
||||||
|
byte inputPos_;
|
||||||
|
byte currentByte_;
|
||||||
|
bool firstNibble_;
|
||||||
|
unsigned long startTime_;
|
||||||
|
|
||||||
|
// helper private functions
|
||||||
|
byte crc8 (const byte *addr, byte len);
|
||||||
|
void sendComplemented (const byte what);
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
// constructor
|
||||||
|
RS485 (ReadCallback fReadCallback,
|
||||||
|
AvailableCallback fAvailableCallback,
|
||||||
|
WriteCallback fWriteCallback,
|
||||||
|
const byte bufferSize) :
|
||||||
|
fReadCallback_ (fReadCallback),
|
||||||
|
fAvailableCallback_ (fAvailableCallback),
|
||||||
|
fWriteCallback_ (fWriteCallback),
|
||||||
|
data_ (NULL),
|
||||||
|
bufferSize_ (bufferSize)
|
||||||
|
{}
|
||||||
|
|
||||||
|
// destructor - frees memory used
|
||||||
|
~RS485 () { stop (); }
|
||||||
|
|
||||||
|
// allocate memory for buf_
|
||||||
|
void begin ();
|
||||||
|
|
||||||
|
// free memory in buf_
|
||||||
|
void stop ();
|
||||||
|
|
||||||
|
// handle incoming data, return true if packet ready
|
||||||
|
bool update ();
|
||||||
|
|
||||||
|
// reset to no incoming data (eg. after a timeout)
|
||||||
|
void reset ();
|
||||||
|
|
||||||
|
// send data
|
||||||
|
void sendMsg (const byte * data, const byte length);
|
||||||
|
|
||||||
|
// returns true if packet available
|
||||||
|
bool available () const { return available_; };
|
||||||
|
|
||||||
|
// once available, returns the address of the current message
|
||||||
|
byte * getData () const { return data_; }
|
||||||
|
byte getLength () const { return inputPos_; }
|
||||||
|
|
||||||
|
// return how many errors we have had
|
||||||
|
unsigned long getErrorCount () const { return errorCount_; }
|
||||||
|
|
||||||
|
// return when last packet started
|
||||||
|
unsigned long getPacketStartTime () const { return startTime_; }
|
||||||
|
|
||||||
|
// return true if a packet has started to be received
|
||||||
|
bool isPacketStarted () const { return haveSTX_; }
|
||||||
|
|
||||||
|
}; // end of class RS485
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
/*
|
|
||||||
* Stairs lighting
|
|
||||||
* Copyright 2017 (c) Mark van Renswoude
|
|
||||||
*
|
|
||||||
* https://git.x2software.net/pub/Stairs
|
|
||||||
*/
|
|
||||||
#include "TinyMillis.h"
|
|
||||||
#include <avr/io.h>
|
|
||||||
#include <util/atomic.h>
|
|
||||||
#include <avr/interrupt.h>
|
|
||||||
|
|
||||||
|
|
||||||
volatile uint16_t tickCount;
|
|
||||||
|
|
||||||
|
|
||||||
ISR(TIMER1_COMPA_vect)
|
|
||||||
{
|
|
||||||
tickCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void millis_init(void)
|
|
||||||
{
|
|
||||||
uint32_t overflow;
|
|
||||||
|
|
||||||
overflow = ((F_CPU / 1000) / 8);
|
|
||||||
|
|
||||||
TCCR1B |= (1 << WGM12) | (1 << CS11);
|
|
||||||
OCR1AH = (overflow >> 8);
|
|
||||||
OCR1AL = overflow;
|
|
||||||
|
|
||||||
TIMSK |= (1 << OCIE1A);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
uint16_t millis(void)
|
|
||||||
{
|
|
||||||
uint16_t value;
|
|
||||||
|
|
||||||
ATOMIC_BLOCK(ATOMIC_FORCEON)
|
|
||||||
{
|
|
||||||
value = tickCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
/*
|
|
||||||
* Stairs lighting
|
|
||||||
* Copyright 2017 (c) Mark van Renswoude
|
|
||||||
*
|
|
||||||
* https://git.x2software.net/pub/Stairs
|
|
||||||
*
|
|
||||||
* Counts the elapsed milliseconds similar to Arduino's millis(void).
|
|
||||||
* Accuracy is not assumed to be important, and assumes no long-running
|
|
||||||
* checks are done so it can get away with 16 bits (65 seconds max).
|
|
||||||
*/
|
|
||||||
#ifndef __TinyMillis
|
|
||||||
#define __TinyMillis
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
// call sei(void) afterwards
|
|
||||||
void millis_init(void);
|
|
||||||
|
|
||||||
uint16_t millis(void);
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -1,262 +0,0 @@
|
||||||
/*
|
|
||||||
* Stairs lighting
|
|
||||||
* Copyright 2017 (c) Mark van Renswoude
|
|
||||||
*
|
|
||||||
* https://git.x2software.net/pub/Stairs
|
|
||||||
*
|
|
||||||
* Straight up port of Nick Gammon's non-blocking
|
|
||||||
* RS485 library to C and removing the Arduino
|
|
||||||
* framework dependency.
|
|
||||||
*
|
|
||||||
* Source:
|
|
||||||
* https://www.gammon.com.au/forum/?id=11428
|
|
||||||
* Devised and written by Nick Gammon.
|
|
||||||
* Date: 4 December 2012
|
|
||||||
* Version: 1.0
|
|
||||||
*/
|
|
||||||
#include "TinyRS485.h"
|
|
||||||
#include "TinyMillis.h"
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
|
|
||||||
ReadCallback doRead;
|
|
||||||
AvailableCallback doAvailable;
|
|
||||||
WriteCallback doWrite;
|
|
||||||
|
|
||||||
|
|
||||||
// where we save incoming stuff
|
|
||||||
uint8_t* data;
|
|
||||||
|
|
||||||
// how much data is in the buffer
|
|
||||||
uint8_t bufferSize;
|
|
||||||
|
|
||||||
// this is true once we have valid data in buf
|
|
||||||
bool available;
|
|
||||||
|
|
||||||
// an STX (start of text) signals a packet start
|
|
||||||
bool haveSTX;
|
|
||||||
|
|
||||||
// count of errors
|
|
||||||
uint16_t errorCount;
|
|
||||||
|
|
||||||
// variables below are set when we get an STX
|
|
||||||
bool haveETX;
|
|
||||||
uint8_t inputPos;
|
|
||||||
uint8_t currentByte;
|
|
||||||
bool firstNibble;
|
|
||||||
uint16_t startTime;
|
|
||||||
|
|
||||||
|
|
||||||
// helper private functions
|
|
||||||
uint8_t crc8 (uint8_t* addr, uint8_t len)
|
|
||||||
{
|
|
||||||
uint8_t crc = 0;
|
|
||||||
while (len--)
|
|
||||||
{
|
|
||||||
uint8_t inbyte = *addr++;
|
|
||||||
for (uint8_t i = 8; i; i--)
|
|
||||||
{
|
|
||||||
uint8_t mix = (crc ^ inbyte) & 0x01;
|
|
||||||
crc >>= 1;
|
|
||||||
if (mix)
|
|
||||||
crc ^= 0x8C;
|
|
||||||
|
|
||||||
inbyte >>= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return crc;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// send a byte complemented, repeated
|
|
||||||
// only values sent would be (in hex):
|
|
||||||
// 0F, 1E, 2D, 3C, 4B, 5A, 69, 78, 87, 96, A5, B4, C3, D2, E1, F0
|
|
||||||
void sendComplemented(uint8_t what)
|
|
||||||
{
|
|
||||||
uint8_t c;
|
|
||||||
|
|
||||||
// first nibble
|
|
||||||
c = what >> 4;
|
|
||||||
doWrite((c << 4) | (c ^ 0x0F));
|
|
||||||
|
|
||||||
// second nibble
|
|
||||||
c = what & 0x0F;
|
|
||||||
doWrite((c << 4) | (c ^ 0x0F));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void rs485_begin(ReadCallback readCallback,
|
|
||||||
AvailableCallback availableCallback,
|
|
||||||
WriteCallback writeCallback,
|
|
||||||
uint8_t bufferSize)
|
|
||||||
{
|
|
||||||
doRead = readCallback;
|
|
||||||
doAvailable = availableCallback;
|
|
||||||
doWrite = writeCallback;
|
|
||||||
data = (uint8_t*)malloc(bufferSize);
|
|
||||||
rs485_reset();
|
|
||||||
errorCount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void rs485_stop(void)
|
|
||||||
{
|
|
||||||
rs485_reset();
|
|
||||||
free(data);
|
|
||||||
data = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void rs485_reset(void)
|
|
||||||
{
|
|
||||||
haveSTX = false;
|
|
||||||
available = false;
|
|
||||||
inputPos = 0;
|
|
||||||
startTime = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// send a message of "length" bytes (max 255) to other end
|
|
||||||
// put STX at start, ETX at end, and add CRC
|
|
||||||
void rs485_sendMsg(uint8_t* data, uint8_t length)
|
|
||||||
{
|
|
||||||
doWrite(STX); // STX
|
|
||||||
for (uint8_t i = 0; i < length; i++)
|
|
||||||
sendComplemented(data[i]);
|
|
||||||
|
|
||||||
doWrite(ETX); // ETX
|
|
||||||
sendComplemented(crc8(data, length));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool rs485_available(void)
|
|
||||||
{
|
|
||||||
return available;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
uint8_t* rs485_getData(void)
|
|
||||||
{
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
uint8_t rs485_getLength(void)
|
|
||||||
{
|
|
||||||
return inputPos;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
uint16_t rs485_getErrorCount(void)
|
|
||||||
{
|
|
||||||
return errorCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
uint16_t rs485_getPacketStartTime(void)
|
|
||||||
{
|
|
||||||
return startTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool rs485_isPacketStarted(void)
|
|
||||||
{
|
|
||||||
return haveSTX;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// called periodically from main loop to process data and
|
|
||||||
// assemble the finished packet in 'data_'
|
|
||||||
|
|
||||||
// returns true if packet received.
|
|
||||||
|
|
||||||
// You could implement a timeout by seeing if isPacketStarted() returns
|
|
||||||
// true, and if too much time has passed since getPacketStartTime() time.
|
|
||||||
bool rs485_update(void)
|
|
||||||
{
|
|
||||||
// no data? can't go ahead (eg. begin() not called)
|
|
||||||
if (data == NULL)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// no callbacks? Can't read
|
|
||||||
if (doAvailable == NULL || doRead == NULL)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
while (doAvailable() > 0)
|
|
||||||
{
|
|
||||||
uint8_t inByte = doRead();
|
|
||||||
|
|
||||||
switch (inByte)
|
|
||||||
{
|
|
||||||
case STX: // start of text
|
|
||||||
haveSTX = true;
|
|
||||||
haveETX = false;
|
|
||||||
inputPos = 0;
|
|
||||||
firstNibble = true;
|
|
||||||
startTime = millis();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ETX: // end of text (now expect the CRC check)
|
|
||||||
haveETX = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
// wait until packet officially starts
|
|
||||||
if (!haveSTX)
|
|
||||||
break;
|
|
||||||
|
|
||||||
// check byte is in valid form (4 bits followed by 4 bits complemented)
|
|
||||||
if ((inByte >> 4) != ((inByte & 0x0F) ^ 0x0F))
|
|
||||||
{
|
|
||||||
rs485_reset();
|
|
||||||
errorCount++;
|
|
||||||
break; // bad character
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert back
|
|
||||||
inByte >>= 4;
|
|
||||||
|
|
||||||
// high-order nibble?
|
|
||||||
if (firstNibble)
|
|
||||||
{
|
|
||||||
currentByte = inByte;
|
|
||||||
firstNibble = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// low-order nibble
|
|
||||||
currentByte <<= 4;
|
|
||||||
currentByte |= inByte;
|
|
||||||
firstNibble = true;
|
|
||||||
|
|
||||||
// if we have the ETX this must be the CRC
|
|
||||||
if (haveETX)
|
|
||||||
{
|
|
||||||
if (crc8(data, inputPos) != currentByte)
|
|
||||||
{
|
|
||||||
rs485_reset();
|
|
||||||
errorCount++;
|
|
||||||
break; // bad crc
|
|
||||||
}
|
|
||||||
|
|
||||||
available = true;
|
|
||||||
return true; // show data ready
|
|
||||||
}
|
|
||||||
|
|
||||||
// keep adding if not full
|
|
||||||
if (inputPos < bufferSize)
|
|
||||||
data[inputPos++] = currentByte;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
rs485_reset(); // overflow, start again
|
|
||||||
errorCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false; // not ready yet
|
|
||||||
}
|
|
|
@ -1,69 +0,0 @@
|
||||||
/*
|
|
||||||
* Stairs lighting
|
|
||||||
* Copyright 2017 (c) Mark van Renswoude
|
|
||||||
*
|
|
||||||
* https://git.x2software.net/pub/Stairs
|
|
||||||
*
|
|
||||||
* Straight up port of Nick Gammon's non-blocking
|
|
||||||
* RS485 library to C and removing the Arduino
|
|
||||||
* framework dependency.
|
|
||||||
*
|
|
||||||
* Source:
|
|
||||||
* https://www.gammon.com.au/forum/?id=11428
|
|
||||||
* Devised and written by Nick Gammon.
|
|
||||||
* Date: 4 December 2012
|
|
||||||
* Version: 1.0
|
|
||||||
*/
|
|
||||||
#ifndef __TinyRS485
|
|
||||||
#define __TinyRS485
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
|
|
||||||
typedef void (*WriteCallback) (uint8_t what); // send a byte to serial port
|
|
||||||
typedef uint8_t (*AvailableCallback) (void); // return number of bytes available
|
|
||||||
typedef uint8_t (*ReadCallback) (void); // read a byte from serial port
|
|
||||||
|
|
||||||
|
|
||||||
enum {
|
|
||||||
STX = '\2', // start of text
|
|
||||||
ETX = '\3' // end of text
|
|
||||||
}; // end of enum
|
|
||||||
|
|
||||||
|
|
||||||
// allocate the requested buffer size
|
|
||||||
void rs485_begin(ReadCallback readCallback,
|
|
||||||
AvailableCallback availableCallback,
|
|
||||||
WriteCallback writeCallback,
|
|
||||||
uint8_t bufferSize);
|
|
||||||
|
|
||||||
// get rid of the buffer
|
|
||||||
void rs485_stop(void);
|
|
||||||
|
|
||||||
// handle incoming data, return true if packet ready
|
|
||||||
bool rs485_update(void);
|
|
||||||
|
|
||||||
// reset to no incoming data (eg. after a timeout)
|
|
||||||
void rs485_reset(void);
|
|
||||||
|
|
||||||
// send data
|
|
||||||
void rs485_sendMsg(uint8_t* data, uint8_t length);
|
|
||||||
|
|
||||||
// returns true if packet available
|
|
||||||
bool rs485_available(void);
|
|
||||||
|
|
||||||
// once available, returns the address of the current message
|
|
||||||
uint8_t* rs485_getData(void);
|
|
||||||
uint8_t rs485_getLength(void);
|
|
||||||
|
|
||||||
// return how many errors we have had
|
|
||||||
uint16_t rs485_getErrorCount(void);
|
|
||||||
|
|
||||||
// return when last packet started
|
|
||||||
uint16_t rs485_getPacketStartTime(void);
|
|
||||||
|
|
||||||
// return true if a packet has started to be received
|
|
||||||
bool rs485_isPacketStarted(void);
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -1,161 +0,0 @@
|
||||||
/*
|
|
||||||
* Stairs lighting
|
|
||||||
* Copyright 2017 (c) Mark van Renswoude
|
|
||||||
*
|
|
||||||
* https://git.x2software.net/pub/Stairs
|
|
||||||
*
|
|
||||||
* Source:
|
|
||||||
* https://github.com/akafugu/helloworld/blob/master/attiny2313/uart.c
|
|
||||||
*/
|
|
||||||
#include "TinyUART.h"
|
|
||||||
#include <avr/interrupt.h>
|
|
||||||
#include <avr/io.h>
|
|
||||||
#include <avr/pgmspace.h>
|
|
||||||
|
|
||||||
|
|
||||||
volatile static uint8_t rx_buffer[BUFFER_SIZE] = "xxxxxxxxxxxxxxxx";
|
|
||||||
volatile static uint8_t tx_buffer[BUFFER_SIZE] = "xxxxxxxxxxxxxxxx";
|
|
||||||
volatile static uint8_t rx_head = 0;
|
|
||||||
volatile static uint8_t rx_tail = 0;
|
|
||||||
volatile static uint8_t tx_head = 0;
|
|
||||||
volatile static uint8_t tx_tail = 0;
|
|
||||||
|
|
||||||
|
|
||||||
void uart_init(void)
|
|
||||||
{
|
|
||||||
// set baud rate
|
|
||||||
UBRRH = (uint8_t)(MYUBBR >> 8);
|
|
||||||
UBRRL = (uint8_t)(MYUBBR);
|
|
||||||
// enable receive and transmit
|
|
||||||
UCSRB = (1 << RXEN) | (1 << TXEN) | (1 << RXCIE);
|
|
||||||
// set frame format
|
|
||||||
UCSRC = (1 << USBS) | (3 << UCSZ0); // asynchron 8n1
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void uart_send(uint8_t c)
|
|
||||||
{
|
|
||||||
// wait for empty data register
|
|
||||||
while (!(UCSRA & (1<<UDRE)));
|
|
||||||
// set data into data register
|
|
||||||
UDR = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
uint8_t uart_available(void)
|
|
||||||
{
|
|
||||||
return rx_head - rx_tail;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
uint8_t uart_receive(void)
|
|
||||||
{
|
|
||||||
while (!(UCSRA & (1<<RXC)))
|
|
||||||
;
|
|
||||||
return UDR;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
uint16_t uart_getc(void)
|
|
||||||
{
|
|
||||||
uint8_t c = 0;
|
|
||||||
uint8_t tmp_tail = 0;
|
|
||||||
if (rx_head == rx_tail)
|
|
||||||
return UART_NO_DATA;
|
|
||||||
|
|
||||||
tmp_tail = (rx_tail + 1) % BUFFER_SIZE;
|
|
||||||
c = rx_buffer[rx_tail];
|
|
||||||
rx_tail = tmp_tail;
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
uint8_t uart_getc_wait(void)
|
|
||||||
{
|
|
||||||
uint16_t c;
|
|
||||||
while ((c = uart_getc()) == UART_NO_DATA) {}
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void uart_putc(uint8_t c)
|
|
||||||
{
|
|
||||||
uint8_t tmp_head = (tx_head + 1) % BUFFER_SIZE;
|
|
||||||
// wait for space in buffer
|
|
||||||
while (tmp_head == tx_tail)
|
|
||||||
;
|
|
||||||
|
|
||||||
tx_buffer[tx_head] = c;
|
|
||||||
tx_head = tmp_head;
|
|
||||||
// enable uart data interrupt (send data)
|
|
||||||
UCSRB |= (1<<UDRIE);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void uart_puts(const char *s)
|
|
||||||
{
|
|
||||||
while (*s)
|
|
||||||
{
|
|
||||||
uart_putc(*s);
|
|
||||||
s++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void uart_puts_P(const char *s)
|
|
||||||
{
|
|
||||||
while (pgm_read_byte(s) != 0x00)
|
|
||||||
{
|
|
||||||
uart_putc(pgm_read_byte(s++));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ISR User Data Regiser Empty
|
|
||||||
* Send a char out of buffer via UART. If sending is complete, the
|
|
||||||
* interrupt gets disabled.
|
|
||||||
*/
|
|
||||||
ISR(USART_UDRE_vect)
|
|
||||||
{
|
|
||||||
uint8_t tmp_tail = 0;
|
|
||||||
if (tx_head != tx_tail)
|
|
||||||
{
|
|
||||||
tmp_tail = (tx_tail + 1) % BUFFER_SIZE;
|
|
||||||
UDR = tx_buffer[tx_tail];
|
|
||||||
tx_tail = tmp_tail;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// disable this interrupt if nothing more to send
|
|
||||||
UCSRB &= ~(1 << UDRIE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ISR RX complete
|
|
||||||
* Receives a char from UART and stores it in ring buffer.
|
|
||||||
*/
|
|
||||||
ISR(USART_RX_vect)
|
|
||||||
{
|
|
||||||
uint8_t tmp_head = 0;
|
|
||||||
tmp_head = (rx_head + 1) % BUFFER_SIZE;
|
|
||||||
if (tmp_head == rx_tail)
|
|
||||||
{
|
|
||||||
// buffer overflow error!
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
rx_buffer[rx_head] = UDR;
|
|
||||||
rx_head = tmp_head;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,86 +0,0 @@
|
||||||
/*
|
|
||||||
* Stairs lighting
|
|
||||||
* Copyright 2017 (c) Mark van Renswoude
|
|
||||||
*
|
|
||||||
* https://git.x2software.net/pub/Stairs
|
|
||||||
*
|
|
||||||
* Source:
|
|
||||||
* https://github.com/akafugu/helloworld/blob/master/attiny2313/uart.c
|
|
||||||
*/
|
|
||||||
#ifndef __TinyUART
|
|
||||||
#define __TinyUART
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#define UART_NO_DATA 0x0100
|
|
||||||
|
|
||||||
#define BAUD 38400
|
|
||||||
// 4.096MHz
|
|
||||||
// 4800: 52.3333333
|
|
||||||
// 9600: 25.6666667
|
|
||||||
// 14400: 16.7777778
|
|
||||||
// 19600: 12.06
|
|
||||||
// 28800: 7.8889
|
|
||||||
// 38400: 5.6667
|
|
||||||
|
|
||||||
#define MYUBBR ((F_CPU / (BAUD * 16L)) - 1)
|
|
||||||
#define BUFFER_SIZE 16
|
|
||||||
|
|
||||||
|
|
||||||
void uart_init(void);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* uart_send
|
|
||||||
* Sends a single char to UART without ISR
|
|
||||||
*/
|
|
||||||
void uart_send(uint8_t c);
|
|
||||||
|
|
||||||
|
|
||||||
uint8_t uart_available(void);
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* uart_receive
|
|
||||||
* Receives a single char without ISR
|
|
||||||
*/
|
|
||||||
uint8_t uart_receive(void);
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* uart_getc
|
|
||||||
* Gets a single char from the receive buffer.
|
|
||||||
* return uint16_r the received char or UART_NO_DATA
|
|
||||||
*/
|
|
||||||
uint16_t uart_getc(void);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* uart_getc_wait
|
|
||||||
* Blocking call to getc. Will not return until a char is received.
|
|
||||||
*/
|
|
||||||
uint8_t uart_getc_wait(void);
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* uart_putc
|
|
||||||
* Puts a single char. Will block until there is enough space in the
|
|
||||||
* send buffer.
|
|
||||||
*/
|
|
||||||
void uart_putc(uint8_t c);
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* uart_puts
|
|
||||||
* Sends a string.
|
|
||||||
*/
|
|
||||||
void uart_puts(const char *s);
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* uart_puts_P
|
|
||||||
* Sends a PROGMEM string.
|
|
||||||
*/
|
|
||||||
void uart_puts_P(const char *s);
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -1,134 +0,0 @@
|
||||||
/*
|
|
||||||
* Stairs lighting
|
|
||||||
* Copyright 2017 (c) Mark van Renswoude
|
|
||||||
*
|
|
||||||
* https://git.x2software.net/pub/Stairs
|
|
||||||
*/
|
|
||||||
#include "VeryTinyBigChars.h"
|
|
||||||
#include "VeryTinySSD1306.h"
|
|
||||||
#include <avr/pgmspace.h>
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Each digit is composed of a 3x5 grid where each bit represents
|
|
||||||
* whether or not to draw that area. It is then stretched.
|
|
||||||
*
|
|
||||||
* Example:
|
|
||||||
*
|
|
||||||
* XXX
|
|
||||||
* X
|
|
||||||
* XXX
|
|
||||||
* X
|
|
||||||
* XXX
|
|
||||||
*
|
|
||||||
* The least significant bit is the top left corner, going down
|
|
||||||
* after that before wrapping to the middle column.
|
|
||||||
*/
|
|
||||||
const uint16_t ssd1306_digits_32px [] PROGMEM = {
|
|
||||||
0b0111111000111111, // 0
|
|
||||||
0b0100001111110010, // 1
|
|
||||||
0b0101111010111101, // 2
|
|
||||||
0b0111111010110101, // 3
|
|
||||||
0b0111110010000111, // 4
|
|
||||||
0b0111011010110111, // 5
|
|
||||||
0b0111011010111111, // 6
|
|
||||||
0b0111110000100001, // 7
|
|
||||||
0b0111111010111111, // 8
|
|
||||||
0b0111111010110111 // 9
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
void ssd1306_drawchar_32px(uint8_t x, uint16_t matrix)
|
|
||||||
{
|
|
||||||
uint8_t column[4];
|
|
||||||
|
|
||||||
// Vertical mode
|
|
||||||
ssd1306_setMemoryAddressingMode(0b01);
|
|
||||||
|
|
||||||
if (ssd1306_currentRenderFrame() == 1)
|
|
||||||
{
|
|
||||||
ssd1306_setPageStartAddress(4);
|
|
||||||
ssd1306_setPageAddress(4, 7);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ssd1306_setPageStartAddress(0);
|
|
||||||
ssd1306_setPageAddress(0, 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
ssd1306_setCursor(x, 0);
|
|
||||||
|
|
||||||
ssd1306_startData();
|
|
||||||
|
|
||||||
// Three columns, each shifted 5 bits
|
|
||||||
for (uint8_t shift = 0; shift < 3; shift++)
|
|
||||||
{
|
|
||||||
uint16_t columnValue = matrix >> (shift * 5);
|
|
||||||
|
|
||||||
// It's a bit rough, but it works! First, third and fifth "row" are 6 bits, in between are 7 bits = 32 total
|
|
||||||
column[0] = 0;
|
|
||||||
column[1] = 0;
|
|
||||||
column[2] = 0;
|
|
||||||
column[3] = 0;
|
|
||||||
|
|
||||||
// 0 = 0b0111111000111111
|
|
||||||
|
|
||||||
if (columnValue & 0b1)
|
|
||||||
column[0] |= 0b00111111;
|
|
||||||
|
|
||||||
if (columnValue & 0b10) {
|
|
||||||
column[0] |= 0b11000000;
|
|
||||||
column[1] |= 0b00011111;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (columnValue & 0b100) {
|
|
||||||
column[1] |= 0b11100000;
|
|
||||||
column[2] |= 0b00000111;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (columnValue & 0b1000) {
|
|
||||||
column[2] |= 0b11111000;
|
|
||||||
column[3] |= 0b00000011;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (columnValue & 0b10000)
|
|
||||||
column[3] |= 0b11111100;
|
|
||||||
|
|
||||||
// Each column is 6 pixels wide
|
|
||||||
for (uint8_t repeat = 0; repeat < SSD1306_CharColumnWidth_32px; repeat++)
|
|
||||||
{
|
|
||||||
ssd1306_sendData(column[0]);
|
|
||||||
ssd1306_sendData(column[1]);
|
|
||||||
ssd1306_sendData(column[2]);
|
|
||||||
ssd1306_sendData(column[3]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ssd1306_endData();
|
|
||||||
|
|
||||||
// Default: horizontal mode
|
|
||||||
ssd1306_setMemoryAddressingMode(0b00);
|
|
||||||
ssd1306_setPageStartAddress(0);
|
|
||||||
ssd1306_setPageAddress(0, 7);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void ssd1306_drawdigit_32px(uint8_t x, uint8_t value)
|
|
||||||
{
|
|
||||||
if (value < 0 || value > 9)
|
|
||||||
return;
|
|
||||||
|
|
||||||
ssd1306_drawchar_32px(x, pgm_read_word(&ssd1306_digits_32px[value]));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void ssd1306_drawplus_32px(uint8_t x)
|
|
||||||
{
|
|
||||||
ssd1306_drawchar_32px(x, 0b0001000111000100);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void ssd1306_drawminus_32px(uint8_t x)
|
|
||||||
{
|
|
||||||
ssd1306_drawchar_32px(x, 0b0001000010000100);
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
/*
|
|
||||||
* Stairs lighting
|
|
||||||
* Copyright 2017 (c) Mark van Renswoude
|
|
||||||
*
|
|
||||||
* https://git.x2software.net/pub/Stairs
|
|
||||||
*/
|
|
||||||
#ifndef __VeryTinyBigChars
|
|
||||||
#define __VeryTinyBigChars
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
|
|
||||||
#define SSD1306_CharColumnWidth_32px 6
|
|
||||||
#define SSD1306_CharWidth_32px (3 * SSD1306_CharColumnWidth_32px)
|
|
||||||
|
|
||||||
void ssd1306_drawchar_32px(uint8_t x, uint16_t matrix);
|
|
||||||
|
|
||||||
|
|
||||||
// Draws a digit from 0 to 9, 32 pixels tall
|
|
||||||
void ssd1306_drawdigit_32px(uint8_t x, uint8_t value);
|
|
||||||
void ssd1306_drawplus_32px(uint8_t x);
|
|
||||||
void ssd1306_drawminus_32px(uint8_t x);
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -1,153 +0,0 @@
|
||||||
/*
|
|
||||||
* Stairs lighting
|
|
||||||
* Copyright 2017 (c) Mark van Renswoude
|
|
||||||
*
|
|
||||||
* https://git.x2software.net/pub/Stairs
|
|
||||||
*
|
|
||||||
* Minimalistic I2C library for the ATTiny2313's limited space.
|
|
||||||
* Based on David Johnson-Davies' TinyI2C:
|
|
||||||
* https://github.com/technoblogy/tiny-i2c
|
|
||||||
*/
|
|
||||||
#include "VeryTinyI2C.h"
|
|
||||||
|
|
||||||
|
|
||||||
// Constants
|
|
||||||
// Prepare register value to: Clear flags, and set USI to shift 8 bits i.e. count 16 clock edges.
|
|
||||||
const unsigned char USISR_8bit = 1<<USISIF | 1<<USIOIF | 1<<USIPF | 1<<USIDC | 0x0<<USICNT0;
|
|
||||||
// Prepare register value to: Clear flags, and set USI to shift 1 bit i.e. count 2 clock edges.
|
|
||||||
const unsigned char USISR_1bit = 1<<USISIF | 1<<USIOIF | 1<<USIPF | 1<<USIDC | 0xE<<USICNT0;
|
|
||||||
|
|
||||||
|
|
||||||
int32_t I2Ccount;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
uint8_t i2c_transfer(uint8_t data)
|
|
||||||
{
|
|
||||||
USISR = data; // Set USISR according to data.
|
|
||||||
// Prepare clocking.
|
|
||||||
data = 0<<USISIE | 0<<USIOIE | // Interrupts disabled
|
|
||||||
1<<USIWM1 | 0<<USIWM0 | // Set USI in Two-wire mode.
|
|
||||||
1<<USICS1 | 0<<USICS0 | 1<<USICLK | // Software clock strobe as source.
|
|
||||||
1<<USITC; // Toggle Clock Port.
|
|
||||||
do {
|
|
||||||
DELAY_T2TWI;
|
|
||||||
USICR = data; // Generate positive SCL edge.
|
|
||||||
while (!(PIN_USI_CL & 1<<PIN_USI_SCL)); // Wait for SCL to go high.
|
|
||||||
DELAY_T4TWI;
|
|
||||||
USICR = data; // Generate negative SCL edge.
|
|
||||||
} while (!(USISR & 1<<USIOIF)); // Check for transfer complete.
|
|
||||||
|
|
||||||
DELAY_T2TWI;
|
|
||||||
data = USIDR; // Read out data.
|
|
||||||
USIDR = 0xFF; // Release SDA.
|
|
||||||
DDR_USI |= (1<<PIN_USI_SDA); // Enable SDA as output.
|
|
||||||
|
|
||||||
return data; // Return the data from the USIDR
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void i2c_init(void)
|
|
||||||
{
|
|
||||||
PORT_USI |= 1<<PIN_USI_SDA; // Enable pullup on SDA.
|
|
||||||
PORT_USI_CL |= 1<<PIN_USI_SCL; // Enable pullup on SCL.
|
|
||||||
|
|
||||||
DDR_USI_CL |= 1<<PIN_USI_SCL; // Enable SCL as output.
|
|
||||||
DDR_USI |= 1<<PIN_USI_SDA; // Enable SDA as output.
|
|
||||||
|
|
||||||
USIDR = 0xFF; // Preload data register with "released level" data.
|
|
||||||
USICR = 0<<USISIE | 0<<USIOIE | // Disable Interrupts.
|
|
||||||
1<<USIWM1 | 0<<USIWM0 | // Set USI in Two-wire mode.
|
|
||||||
1<<USICS1 | 0<<USICS0 | 1<<USICLK | // Software stobe as counter clock source
|
|
||||||
0<<USITC;
|
|
||||||
USISR = 1<<USISIF | 1<<USIOIF | 1<<USIPF | 1<<USIDC | // Clear flags,
|
|
||||||
0x0<<USICNT0; // and reset counter.
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
uint8_t i2c_read(void)
|
|
||||||
{
|
|
||||||
if ((I2Ccount != 0) && (I2Ccount != -1)) I2Ccount--;
|
|
||||||
|
|
||||||
/* Read a byte */
|
|
||||||
DDR_USI &= ~1<<PIN_USI_SDA; // Enable SDA as input.
|
|
||||||
uint8_t data = i2c_transfer(USISR_8bit);
|
|
||||||
|
|
||||||
/* Prepare to generate ACK (or NACK in case of End Of Transmission) */
|
|
||||||
if (I2Ccount == 0) USIDR = 0xFF; else USIDR = 0x00;
|
|
||||||
i2c_transfer(USISR_1bit); // Generate ACK/NACK.
|
|
||||||
|
|
||||||
return data; // Read successfully completed
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
uint8_t i2c_readLast(void)
|
|
||||||
{
|
|
||||||
I2Ccount = 0;
|
|
||||||
return i2c_read();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool i2c_write(uint8_t data)
|
|
||||||
{
|
|
||||||
/* Write a byte */
|
|
||||||
PORT_USI_CL &= ~(1<<PIN_USI_SCL); // Pull SCL LOW.
|
|
||||||
USIDR = data; // Setup data.
|
|
||||||
i2c_transfer(USISR_8bit); // Send 8 bits on bus.
|
|
||||||
|
|
||||||
/* Clock and verify (N)ACK from slave */
|
|
||||||
DDR_USI &= ~(1<<PIN_USI_SDA); // Enable SDA as input.
|
|
||||||
if (i2c_transfer(USISR_1bit) & 1<<TWI_NACK_BIT) return false;
|
|
||||||
|
|
||||||
return true; // Write successfully completed
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool i2c_start(uint8_t address, int readcount)
|
|
||||||
{
|
|
||||||
if (readcount != 0) { I2Ccount = readcount; readcount = 1; }
|
|
||||||
uint8_t addressRW = address<<1 | readcount;
|
|
||||||
|
|
||||||
/* Release SCL to ensure that (repeated) Start can be performed */
|
|
||||||
PORT_USI_CL |= 1<<PIN_USI_SCL; // Release SCL.
|
|
||||||
while (!(PIN_USI_CL & 1<<PIN_USI_SCL)); // Verify that SCL becomes high.
|
|
||||||
#ifdef TWI_FAST_MODE
|
|
||||||
DELAY_T4TWI;
|
|
||||||
#else
|
|
||||||
DELAY_T2TWI;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Generate Start Condition */
|
|
||||||
PORT_USI &= ~(1<<PIN_USI_SDA); // Force SDA LOW.
|
|
||||||
DELAY_T4TWI;
|
|
||||||
PORT_USI_CL &= ~(1<<PIN_USI_SCL); // Pull SCL LOW.
|
|
||||||
PORT_USI |= 1<<PIN_USI_SDA; // Release SDA.
|
|
||||||
|
|
||||||
if (!(USISR & 1<<USISIF)) return false;
|
|
||||||
|
|
||||||
/*Write address */
|
|
||||||
PORT_USI_CL &= ~(1<<PIN_USI_SCL); // Pull SCL LOW.
|
|
||||||
USIDR = addressRW; // Setup data.
|
|
||||||
i2c_transfer(USISR_8bit); // Send 8 bits on bus.
|
|
||||||
|
|
||||||
/* Clock and verify (N)ACK from slave */
|
|
||||||
DDR_USI &= ~(1<<PIN_USI_SDA); // Enable SDA as input.
|
|
||||||
if (i2c_transfer(USISR_1bit) & 1<<TWI_NACK_BIT) return false; // No ACK
|
|
||||||
|
|
||||||
return true; // Start successfully completed
|
|
||||||
}
|
|
||||||
|
|
||||||
bool i2c_restart(uint8_t address, int readcount)
|
|
||||||
{
|
|
||||||
return i2c_start(address, readcount);
|
|
||||||
}
|
|
||||||
|
|
||||||
void i2c_stop(void)
|
|
||||||
{
|
|
||||||
PORT_USI &= ~(1<<PIN_USI_SDA); // Pull SDA low.
|
|
||||||
PORT_USI_CL |= 1<<PIN_USI_SCL; // Release SCL.
|
|
||||||
while (!(PIN_USI_CL & 1<<PIN_USI_SCL)); // Wait for SCL to go high.
|
|
||||||
DELAY_T4TWI;
|
|
||||||
PORT_USI |= 1<<PIN_USI_SDA; // Release SDA.
|
|
||||||
DELAY_T2TWI;
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
/*
|
|
||||||
* Stairs lighting
|
|
||||||
* Copyright 2017 (c) Mark van Renswoude
|
|
||||||
*
|
|
||||||
* https://git.x2software.net/pub/Stairs
|
|
||||||
*
|
|
||||||
* Straight up port of David Johnson-Davies' TinyI2C to C
|
|
||||||
* and without the Arduino framework dependency.
|
|
||||||
* https://github.com/technoblogy/tiny-i2c
|
|
||||||
*/
|
|
||||||
#ifndef __VeryTinyI2C
|
|
||||||
#define __VeryTinyI2C
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <avr/io.h>
|
|
||||||
#include <util/delay.h>
|
|
||||||
|
|
||||||
// Defines
|
|
||||||
//#define TWI_FAST_MODE
|
|
||||||
|
|
||||||
#ifdef TWI_FAST_MODE // TWI FAST mode timing limits. SCL = 100-400kHz
|
|
||||||
#define DELAY_T2TWI (_delay_us(2)) // >1.3us
|
|
||||||
#define DELAY_T4TWI (_delay_us(1)) // >0.6us
|
|
||||||
#else // TWI STANDARD mode timing limits. SCL <= 100kHz
|
|
||||||
#define DELAY_T2TWI (_delay_us(5)) // >4.7us
|
|
||||||
#define DELAY_T4TWI (_delay_us(4)) // >4.0us
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define TWI_NACK_BIT 0 // Bit position for (N)ACK bit.
|
|
||||||
|
|
||||||
|
|
||||||
// These are valid for the ATTiny2313
|
|
||||||
#define DDR_USI DDRB
|
|
||||||
#define DDR_USI_CL DDR_USI
|
|
||||||
#define PORT_USI PORTB
|
|
||||||
#define PORT_USI_CL PORT_USI
|
|
||||||
#define PIN_USI PINB
|
|
||||||
#define PIN_USI_CL PIN_USI
|
|
||||||
#define PORT_USI_SDA PB5
|
|
||||||
#define PORT_USI_SCL PB7
|
|
||||||
#define PIN_USI_SDA PINB5
|
|
||||||
#define PIN_USI_SCL PINB7
|
|
||||||
|
|
||||||
|
|
||||||
void i2c_init(void);
|
|
||||||
uint8_t i2c_read(void);
|
|
||||||
uint8_t i2c_readLast(void);
|
|
||||||
bool i2c_write(uint8_t data);
|
|
||||||
bool i2c_start(uint8_t address, int readcount);
|
|
||||||
bool i2c_restart(uint8_t address, int readcount);
|
|
||||||
void i2c_stop(void);
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -1,517 +0,0 @@
|
||||||
/*
|
|
||||||
* Stairs lighting
|
|
||||||
* Copyright 2017 (c) Mark van Renswoude
|
|
||||||
*
|
|
||||||
* https://git.x2software.net/pub/Stairs
|
|
||||||
*
|
|
||||||
* Port of Stephen Denne's Tiny4kOLED to C
|
|
||||||
* and without the Arduino framework dependency.
|
|
||||||
* https://github.com/technoblogy/tiny-i2c
|
|
||||||
*/
|
|
||||||
#include "VeryTinySSD1306.h"
|
|
||||||
#include <avr/pgmspace.h>
|
|
||||||
#include "VeryTinyI2C.h"
|
|
||||||
|
|
||||||
#define SSD1306_PAGES 4
|
|
||||||
|
|
||||||
#define SSD1306_COMMAND 0x00
|
|
||||||
#define SSD1306_DATA 0x40
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Some code based on "IIC_without_ACK" by http://www.14blog.com/archives/1358
|
|
||||||
|
|
||||||
const uint8_t ssd1306_init_sequence [] PROGMEM = { // Initialization Sequence
|
|
||||||
// 0xAE, // Display OFF (sleep mode)
|
|
||||||
// 0x20, 0b10, // Set Memory Addressing Mode
|
|
||||||
// 00=Horizontal Addressing Mode; 01=Vertical Addressing Mode;
|
|
||||||
// 10=Page Addressing Mode (RESET); 11=Invalid
|
|
||||||
// 0xB0, // Set Page Start Address for Page Addressing Mode, 0-7
|
|
||||||
0xC8, // Set COM Output Scan Direction
|
|
||||||
// 0x00, // ---set low column address
|
|
||||||
// 0x10, // ---set high column address
|
|
||||||
// 0x40, // --set start line address
|
|
||||||
// 0x81, 0x7F, // Set contrast control register
|
|
||||||
0xA1, // Set Segment Re-map. A0=address mapped; A1=address 127 mapped.
|
|
||||||
// 0xA6, // Set display mode. A6=Normal; A7=Inverse
|
|
||||||
0xA8, 0x1F, // Set multiplex ratio(1 to 64)
|
|
||||||
// 0xA4, // Output RAM to Display
|
|
||||||
// 0xA4=Output follows RAM content; 0xA5,Output ignores RAM content
|
|
||||||
// 0xD3, 0x00, // Set display offset. 00 = no offset
|
|
||||||
// 0xD5, 0x80, // --set display clock divide ratio/oscillator frequency
|
|
||||||
// 0xD9, 0x22, // Set pre-charge period
|
|
||||||
0xDA, 0x02, // Set com pins hardware configuration
|
|
||||||
// 0xDB, 0x20, // --set vcomh 0x20 = 0.77xVcc
|
|
||||||
0x8D, 0x14 // Set DC-DC enable
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const DCfont *oledFont = 0;
|
|
||||||
uint8_t oledX = 0, oledY = 0;
|
|
||||||
uint8_t renderingFrame = 0xB0, drawingFrame = 0x40;
|
|
||||||
|
|
||||||
|
|
||||||
void ssd1306_send_command_start(void)
|
|
||||||
{
|
|
||||||
i2c_start(SSD1306, 0);
|
|
||||||
i2c_write(SSD1306_COMMAND);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_send_data_start(void)
|
|
||||||
{
|
|
||||||
i2c_start(SSD1306, 0);
|
|
||||||
i2c_write(SSD1306_DATA);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_send_command_byte(uint8_t byte)
|
|
||||||
{
|
|
||||||
if (i2c_write(byte) == 0)
|
|
||||||
{
|
|
||||||
i2c_stop();
|
|
||||||
ssd1306_send_command_start();
|
|
||||||
i2c_write(byte);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_send_data_byte(uint8_t byte)
|
|
||||||
{
|
|
||||||
if (i2c_write(byte) == 0)
|
|
||||||
{
|
|
||||||
i2c_stop();
|
|
||||||
ssd1306_send_data_start();
|
|
||||||
i2c_write(byte);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_send_command(uint8_t command)
|
|
||||||
{
|
|
||||||
ssd1306_send_command_start();
|
|
||||||
i2c_write(command);
|
|
||||||
i2c_stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_send_command2(uint8_t command1, uint8_t command2)
|
|
||||||
{
|
|
||||||
ssd1306_send_command_start();
|
|
||||||
i2c_write(command1);
|
|
||||||
i2c_write(command2);
|
|
||||||
i2c_stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_send_command3(uint8_t command1, uint8_t command2, uint8_t command3)
|
|
||||||
{
|
|
||||||
ssd1306_send_command_start();
|
|
||||||
i2c_write(command1);
|
|
||||||
i2c_write(command2);
|
|
||||||
i2c_write(command3);
|
|
||||||
i2c_stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_send_command6(uint8_t command1, uint8_t command2, uint8_t command3, uint8_t command4, uint8_t command5, uint8_t command6)
|
|
||||||
{
|
|
||||||
ssd1306_send_command_start();
|
|
||||||
i2c_write(command1);
|
|
||||||
i2c_write(command2);
|
|
||||||
i2c_write(command3);
|
|
||||||
i2c_write(command4);
|
|
||||||
i2c_write(command5);
|
|
||||||
i2c_write(command6);
|
|
||||||
i2c_stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_send_command7(uint8_t command1, uint8_t command2, uint8_t command3, uint8_t command4, uint8_t command5, uint8_t command6, uint8_t command7)
|
|
||||||
{
|
|
||||||
ssd1306_send_command_start();
|
|
||||||
i2c_write(command1);
|
|
||||||
i2c_write(command2);
|
|
||||||
i2c_write(command3);
|
|
||||||
i2c_write(command4);
|
|
||||||
i2c_write(command5);
|
|
||||||
i2c_write(command6);
|
|
||||||
i2c_write(command7);
|
|
||||||
i2c_stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_begin_default(void)
|
|
||||||
{
|
|
||||||
ssd1306_begin(sizeof(ssd1306_init_sequence), ssd1306_init_sequence);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_begin(uint8_t init_sequence_length, const uint8_t init_sequence [])
|
|
||||||
{
|
|
||||||
i2c_init();
|
|
||||||
|
|
||||||
ssd1306_send_command_start();
|
|
||||||
for (uint8_t i = 0; i < init_sequence_length; i++)
|
|
||||||
{
|
|
||||||
ssd1306_send_command_byte(pgm_read_byte(&init_sequence[i]));
|
|
||||||
}
|
|
||||||
i2c_stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_setFont(const DCfont *font)
|
|
||||||
{
|
|
||||||
oledFont = font;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_setCursor(uint8_t x, uint8_t y)
|
|
||||||
{
|
|
||||||
ssd1306_send_command3(renderingFrame | (y & 0x07), 0x10 | ((x & 0xf0) >> 4), x & 0x0f);
|
|
||||||
oledX = x;
|
|
||||||
oledY = y;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_clear(void)
|
|
||||||
{
|
|
||||||
ssd1306_fill(0x00);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_fill(uint8_t fill)
|
|
||||||
{
|
|
||||||
for (uint8_t m = 0; m < SSD1306_PAGES; m++)
|
|
||||||
{
|
|
||||||
ssd1306_setCursor(0, m);
|
|
||||||
ssd1306_fillToEOL(fill);
|
|
||||||
}
|
|
||||||
ssd1306_setCursor(0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_newLine_height(uint8_t fontHeight)
|
|
||||||
{
|
|
||||||
oledY+=fontHeight;
|
|
||||||
if (oledY > SSD1306_PAGES - fontHeight)
|
|
||||||
{
|
|
||||||
oledY = SSD1306_PAGES - fontHeight;
|
|
||||||
}
|
|
||||||
ssd1306_setCursor(0, oledY);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_newLine(void)
|
|
||||||
{
|
|
||||||
ssd1306_newLine_height(oledFont->height);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t ssd1306_write(uint8_t c)
|
|
||||||
{
|
|
||||||
if (!oledFont)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
if (c == '\r')
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
uint8_t h = oledFont->height;
|
|
||||||
|
|
||||||
if (c == '\n')
|
|
||||||
{
|
|
||||||
ssd1306_newLine_height(h);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t w = oledFont->width;
|
|
||||||
|
|
||||||
if (oledX > ((uint8_t)128 - w))
|
|
||||||
{
|
|
||||||
ssd1306_newLine_height(h);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t offset = ((uint16_t)c - oledFont->first) * w * h;
|
|
||||||
uint8_t line = h;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
ssd1306_send_data_start();
|
|
||||||
for (uint8_t i = 0; i < w; i++)
|
|
||||||
{
|
|
||||||
ssd1306_send_data_byte(pgm_read_byte(&(oledFont->bitmap[offset++])));
|
|
||||||
}
|
|
||||||
i2c_stop();
|
|
||||||
if (h == 1)
|
|
||||||
{
|
|
||||||
oledX+=w;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (line > 1)
|
|
||||||
{
|
|
||||||
ssd1306_setCursor(oledX, oledY + 1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ssd1306_setCursor(oledX + w, oledY - (h - 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (--line);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_bitmap(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, const uint8_t bitmap[])
|
|
||||||
{
|
|
||||||
uint16_t j = 0;
|
|
||||||
for (uint8_t y = y0; y < y1; y++)
|
|
||||||
{
|
|
||||||
ssd1306_setCursor(x0,y);
|
|
||||||
ssd1306_send_data_start();
|
|
||||||
for (uint8_t x = x0; x < x1; x++)
|
|
||||||
{
|
|
||||||
ssd1306_send_data_byte(pgm_read_byte(&bitmap[j++]));
|
|
||||||
}
|
|
||||||
i2c_stop();
|
|
||||||
}
|
|
||||||
ssd1306_setCursor(0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_clearToEOL(void)
|
|
||||||
{
|
|
||||||
ssd1306_fillToEOL(0x00);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_fillToEOL(uint8_t fill)
|
|
||||||
{
|
|
||||||
ssd1306_fillLength(fill, 128 - oledX);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_fillLength(uint8_t fill, uint8_t length)
|
|
||||||
{
|
|
||||||
oledX += length;
|
|
||||||
ssd1306_send_data_start();
|
|
||||||
do
|
|
||||||
{
|
|
||||||
ssd1306_send_data_byte(fill);
|
|
||||||
}
|
|
||||||
while (--length);
|
|
||||||
i2c_stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_startData(void)
|
|
||||||
{
|
|
||||||
ssd1306_send_data_start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_sendData(const uint8_t data)
|
|
||||||
{
|
|
||||||
ssd1306_send_data_byte(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_endData(void)
|
|
||||||
{
|
|
||||||
i2c_stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Double Buffering Commands
|
|
||||||
|
|
||||||
void ssd1306_switchRenderFrame(void)
|
|
||||||
{
|
|
||||||
renderingFrame ^= 0x04;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_switchDisplayFrame(void)
|
|
||||||
{
|
|
||||||
drawingFrame ^= 0x20;
|
|
||||||
ssd1306_send_command(drawingFrame);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_switchFrame(void)
|
|
||||||
{
|
|
||||||
ssd1306_switchDisplayFrame();
|
|
||||||
ssd1306_switchRenderFrame();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t ssd1306_currentRenderFrame(void)
|
|
||||||
{
|
|
||||||
return (renderingFrame >> 2) & 0x01;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t ssd1306_currentDisplayFrame(void)
|
|
||||||
{
|
|
||||||
return (drawingFrame >> 5) & 0x01;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. Fundamental Command Table
|
|
||||||
|
|
||||||
void ssd1306_setContrast(uint8_t contrast)
|
|
||||||
{
|
|
||||||
ssd1306_send_command2(0x81,contrast);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_setEntireDisplayOn(bool enable)
|
|
||||||
{
|
|
||||||
if (enable)
|
|
||||||
ssd1306_send_command(0xA5);
|
|
||||||
else
|
|
||||||
ssd1306_send_command(0xA4);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_setInverse(bool enable)
|
|
||||||
{
|
|
||||||
if (enable)
|
|
||||||
ssd1306_send_command(0xA7);
|
|
||||||
else
|
|
||||||
ssd1306_send_command(0xA6);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_off(void)
|
|
||||||
{
|
|
||||||
ssd1306_send_command(0xAE);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_on(void)
|
|
||||||
{
|
|
||||||
ssd1306_send_command(0xAF);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Scrolling Command Table
|
|
||||||
|
|
||||||
void ssd1306_scrollRight(uint8_t startPage, uint8_t interval, uint8_t endPage)
|
|
||||||
{
|
|
||||||
ssd1306_send_command7(0x26, 0x00, startPage, interval, endPage, 0x00, 0xFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_scrollLeft(uint8_t startPage, uint8_t interval, uint8_t endPage)
|
|
||||||
{
|
|
||||||
ssd1306_send_command7(0x27, 0x00, startPage, interval, endPage, 0x00, 0xFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_scrollRightOffset(uint8_t startPage, uint8_t interval, uint8_t endPage, uint8_t offset)
|
|
||||||
{
|
|
||||||
ssd1306_send_command6(0x29, 0x00, startPage, interval, endPage, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_scrollLeftOffset(uint8_t startPage, uint8_t interval, uint8_t endPage, uint8_t offset)
|
|
||||||
{
|
|
||||||
ssd1306_send_command6(0x2A, 0x00, startPage, interval, endPage, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_deactivateScroll(void)
|
|
||||||
{
|
|
||||||
ssd1306_send_command(0x2E);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_activateScroll(void)
|
|
||||||
{
|
|
||||||
ssd1306_send_command(0x2F);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_setVerticalScrollArea(uint8_t top, uint8_t rows)
|
|
||||||
{
|
|
||||||
ssd1306_send_command3(0xA3, top, rows);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Addressing Setting Command Table
|
|
||||||
|
|
||||||
void ssd1306_setColumnStartAddress(uint8_t startAddress)
|
|
||||||
{
|
|
||||||
ssd1306_send_command2(startAddress & 0x0F, startAddress >> 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_setMemoryAddressingMode(uint8_t mode)
|
|
||||||
{
|
|
||||||
ssd1306_send_command2(0x20, mode & 0x03);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_setColumnAddress(uint8_t startAddress, uint8_t endAddress)
|
|
||||||
{
|
|
||||||
ssd1306_send_command3(0x21, startAddress & 0x7F, endAddress & 0x7F);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_setPageAddress(uint8_t startPage, uint8_t endPage)
|
|
||||||
{
|
|
||||||
ssd1306_send_command3(0x22, startPage & 0x07, endPage & 0x07);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_setPageStartAddress(uint8_t startPage)
|
|
||||||
{
|
|
||||||
ssd1306_send_command(0xB0 | (startPage & 0x07));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Hardware Configuration (Panel resolution and layout related) Command Table
|
|
||||||
|
|
||||||
void ssd1306_setDisplayStartLine(uint8_t startLine)
|
|
||||||
{
|
|
||||||
ssd1306_send_command(0x40 | (startLine & 0x3F));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_setSegmentRemap(uint8_t remap)
|
|
||||||
{
|
|
||||||
ssd1306_send_command(0xA0 | (remap & 0x01));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_setMultiplexRatio(uint8_t mux)
|
|
||||||
{
|
|
||||||
ssd1306_send_command2(0xA8, (mux - 1) & 0x3F);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_setComOutputDirection(uint8_t direction)
|
|
||||||
{
|
|
||||||
ssd1306_send_command(0xC0 | ((direction & 0x01)<<3));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_setDisplayOffset(uint8_t offset)
|
|
||||||
{
|
|
||||||
ssd1306_send_command2(0xD3, offset & 0x3F);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_setComPinsHardwareConfiguration(uint8_t alternative, uint8_t enableLeftRightRemap)
|
|
||||||
{
|
|
||||||
ssd1306_send_command2(0xDA, ((enableLeftRightRemap & 0x01) << 5) | ((alternative & 0x01) << 4) | 0x02 );
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. Timing and Driving Scheme Setting Command table
|
|
||||||
|
|
||||||
void ssd1306_setDisplayClock(uint8_t divideRatio, uint8_t oscillatorFrequency)
|
|
||||||
{
|
|
||||||
ssd1306_send_command2(0xD5, ((oscillatorFrequency & 0x0F) << 4) | ((divideRatio -1) & 0x0F));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_setPrechargePeriod(uint8_t phaseOnePeriod, uint8_t phaseTwoPeriod)
|
|
||||||
{
|
|
||||||
ssd1306_send_command2(0xD9, ((phaseTwoPeriod & 0x0F) << 4) | (phaseOnePeriod & 0x0F));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_setVcomhDeselectLevel(uint8_t level)
|
|
||||||
{
|
|
||||||
ssd1306_send_command2(0xDB, (level & 0x07) << 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_nop(void)
|
|
||||||
{
|
|
||||||
ssd1306_send_command(0xE3);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. Advance Graphic Command table
|
|
||||||
|
|
||||||
void ssd1306_fadeOut(uint8_t interval)
|
|
||||||
{
|
|
||||||
ssd1306_send_command2(0x23, (0x20 | (interval & 0x0F)));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_blink(uint8_t interval)
|
|
||||||
{
|
|
||||||
ssd1306_send_command2(0x23, (0x30 | (interval & 0x0F)));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_disableFadeOutAndBlinking(void)
|
|
||||||
{
|
|
||||||
ssd1306_send_command2(0x23, 0x00);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_enableZoomIn(void)
|
|
||||||
{
|
|
||||||
ssd1306_send_command2(0xD6, 0x01);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_disableZoomIn(void)
|
|
||||||
{
|
|
||||||
ssd1306_send_command2(0xD6, 0x00);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Charge Pump Settings
|
|
||||||
|
|
||||||
void ssd1306_enableChargePump(void)
|
|
||||||
{
|
|
||||||
ssd1306_send_command2(0x8D, 0x14);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssd1306_disableChargePump(void)
|
|
||||||
{
|
|
||||||
ssd1306_send_command2(0x8D, 0x10);
|
|
||||||
}
|
|
|
@ -1,113 +0,0 @@
|
||||||
/*
|
|
||||||
* Stairs lighting
|
|
||||||
* Copyright 2017 (c) Mark van Renswoude
|
|
||||||
*
|
|
||||||
* https://git.x2software.net/pub/Stairs
|
|
||||||
*
|
|
||||||
* Port of Stephen Denne's Tiny4kOLED to C,
|
|
||||||
* without the Arduino framework dependency but with
|
|
||||||
* a hardcoded dependency on VeryTinyI2C.
|
|
||||||
*
|
|
||||||
* Original:
|
|
||||||
* https://github.com/technoblogy/tiny-i2c
|
|
||||||
*/
|
|
||||||
#ifndef __VeryTinySSD1306
|
|
||||||
#define __VeryTinySSD1306
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint8_t *bitmap; // character bitmaps data
|
|
||||||
uint8_t width; // character width in pixels
|
|
||||||
uint8_t height; // character height in pages (8 pixels)
|
|
||||||
uint8_t first, last; // ASCII extents
|
|
||||||
} DCfont;
|
|
||||||
|
|
||||||
|
|
||||||
#define SSD1306_DisplayWidth 128
|
|
||||||
#define SSD1306_DisplayHeight 32
|
|
||||||
|
|
||||||
#ifndef SSD1306
|
|
||||||
#define SSD1306 0x3C // Slave address
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
void ssd1306_begin_default(void);
|
|
||||||
void ssd1306_begin(uint8_t init_sequence_length, const uint8_t init_sequence []);
|
|
||||||
void ssd1306_switchRenderFrame(void);
|
|
||||||
void ssd1306_switchDisplayFrame(void);
|
|
||||||
void ssd1306_switchFrame(void);
|
|
||||||
uint8_t ssd1306_currentRenderFrame(void);
|
|
||||||
uint8_t ssd1306_currentDisplayFrame(void);
|
|
||||||
void ssd1306_setFont(const DCfont *font);
|
|
||||||
void ssd1306_setCursor(uint8_t x, uint8_t y);
|
|
||||||
void ssd1306_newLine();
|
|
||||||
void ssd1306_fill(uint8_t fill);
|
|
||||||
void ssd1306_fillToEOL(uint8_t fill);
|
|
||||||
void ssd1306_fillLength(uint8_t fill, uint8_t length);
|
|
||||||
void ssd1306_clear(void);
|
|
||||||
void ssd1306_clearToEOL(void);
|
|
||||||
void ssd1306_bitmap(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, const uint8_t bitmap[]);
|
|
||||||
void ssd1306_startData(void);
|
|
||||||
void ssd1306_sendData(const uint8_t data);
|
|
||||||
void ssd1306_endData(void);
|
|
||||||
|
|
||||||
// 1. Fundamental Command Table
|
|
||||||
|
|
||||||
void ssd1306_setContrast(uint8_t contrast);
|
|
||||||
void ssd1306_setEntireDisplayOn(bool enable);
|
|
||||||
void ssd1306_setInverse(bool enable);
|
|
||||||
void ssd1306_off(void);
|
|
||||||
void ssd1306_on(void);
|
|
||||||
|
|
||||||
// 2. Scrolling Command Table
|
|
||||||
|
|
||||||
void ssd1306_scrollRight(uint8_t startPage, uint8_t interval, uint8_t endPage);
|
|
||||||
void ssd1306_scrollLeft(uint8_t startPage, uint8_t interval, uint8_t endPage);
|
|
||||||
void ssd1306_scrollRightOffset(uint8_t startPage, uint8_t interval, uint8_t endPage, uint8_t offset);
|
|
||||||
void ssd1306_scrollLeftOffset(uint8_t startPage, uint8_t interval, uint8_t endPage, uint8_t offset);
|
|
||||||
void ssd1306_deactivateScroll(void);
|
|
||||||
void ssd1306_activateScroll(void);
|
|
||||||
void ssd1306_setVerticalScrollArea(uint8_t top, uint8_t rows);
|
|
||||||
|
|
||||||
// 3. Addressing Setting Command Table
|
|
||||||
void ssd1306_setColumnStartAddress(uint8_t startAddress);
|
|
||||||
void ssd1306_setMemoryAddressingMode(uint8_t mode);
|
|
||||||
void ssd1306_setColumnAddress(uint8_t startAddress, uint8_t endAddress);
|
|
||||||
void ssd1306_setPageAddress(uint8_t startPage, uint8_t endPage);
|
|
||||||
void ssd1306_setPageStartAddress(uint8_t startPage);
|
|
||||||
|
|
||||||
// 4. Hardware Configuration (Panel resolution and layout related) Command Table
|
|
||||||
|
|
||||||
void ssd1306_setDisplayStartLine(uint8_t startLine);
|
|
||||||
void ssd1306_setSegmentRemap(uint8_t remap);
|
|
||||||
void ssd1306_setMultiplexRatio(uint8_t mux);
|
|
||||||
void ssd1306_setComOutputDirection(uint8_t direction);
|
|
||||||
void ssd1306_setDisplayOffset(uint8_t offset);
|
|
||||||
void ssd1306_setComPinsHardwareConfiguration(uint8_t alternative, uint8_t enableLeftRightRemap);
|
|
||||||
|
|
||||||
// 5. Timing and Driving Scheme Setting Command table
|
|
||||||
|
|
||||||
void ssd1306_setDisplayClock(uint8_t divideRatio, uint8_t oscillatorFrequency);
|
|
||||||
void ssd1306_setPrechargePeriod(uint8_t phaseOnePeriod, uint8_t phaseTwoPeriod);
|
|
||||||
void ssd1306_setVcomhDeselectLevel(uint8_t level);
|
|
||||||
void ssd1306_nop(void);
|
|
||||||
|
|
||||||
// 6. Advance Graphic Command table
|
|
||||||
|
|
||||||
void ssd1306_fadeOut(uint8_t interval);
|
|
||||||
void ssd1306_blink(uint8_t interval);
|
|
||||||
void ssd1306_disableFadeOutAndBlinking(void);
|
|
||||||
void ssd1306_enableZoomIn(void);
|
|
||||||
void ssd1306_disableZoomIn(void);
|
|
||||||
|
|
||||||
// Charge Pump Settings
|
|
||||||
|
|
||||||
void ssd1306_enableChargePump(void);
|
|
||||||
void ssd1306_disableChargePump(void);
|
|
||||||
|
|
||||||
uint8_t ssd1306_write(uint8_t c);
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -1,83 +0,0 @@
|
||||||
/*
|
|
||||||
* Stairs lighting
|
|
||||||
* Copyright 2017 (c) Mark van Renswoude
|
|
||||||
*
|
|
||||||
* https://git.x2software.net/pub/Stairs
|
|
||||||
*/
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <avr/interrupt.h>
|
|
||||||
#include <util/delay.h>
|
|
||||||
#include "lib/TinyMillis.h"
|
|
||||||
#include "lib/TinyUART.h"
|
|
||||||
#include "lib/TinyRS485.h"
|
|
||||||
#include "global.h"
|
|
||||||
#include "display.h"
|
|
||||||
|
|
||||||
#define STEPS_DELAY_SHORT 200
|
|
||||||
#define STEPS_DELAY 800
|
|
||||||
#define STEPS_DELAY_LONG 2000
|
|
||||||
|
|
||||||
|
|
||||||
uint8_t uartRead(void)
|
|
||||||
{
|
|
||||||
return uart_getc();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t uartAvailable(void)
|
|
||||||
{
|
|
||||||
return uart_available();
|
|
||||||
}
|
|
||||||
|
|
||||||
void uartWrite(uint8_t what)
|
|
||||||
{
|
|
||||||
uart_putc(what);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
typedef enum
|
|
||||||
{
|
|
||||||
WaitingForComm,
|
|
||||||
Connected
|
|
||||||
} State;
|
|
||||||
|
|
||||||
|
|
||||||
State currentState = WaitingForComm;
|
|
||||||
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
// Allow peripherals to start up before sending messages
|
|
||||||
_delay_ms(40);
|
|
||||||
|
|
||||||
global_init();
|
|
||||||
millis_init();
|
|
||||||
sei();
|
|
||||||
|
|
||||||
uart_init();
|
|
||||||
rs485_begin(uartRead, uartAvailable, uartWrite, 20);
|
|
||||||
|
|
||||||
display_init();
|
|
||||||
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
if (rs485_update())
|
|
||||||
{
|
|
||||||
// TODO get data
|
|
||||||
currentState = Connected;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (currentState)
|
|
||||||
{
|
|
||||||
case WaitingForComm:
|
|
||||||
display_drawWait();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Connected:
|
|
||||||
display_drawIndex();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* Stairs lighting
|
||||||
|
* Copyright 2017 (c) Mark van Renswoude
|
||||||
|
*
|
||||||
|
* https://git.x2software.net/pub/Stairs
|
||||||
|
*/
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
#include "global.h"
|
||||||
|
#include "display.h"
|
||||||
|
|
||||||
|
|
||||||
|
enum struct State: uint8_t
|
||||||
|
{
|
||||||
|
WaitingForComm,
|
||||||
|
Connected
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
State currentState = State::WaitingForComm;
|
||||||
|
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
settings.init();
|
||||||
|
|
||||||
|
// Set up I2C devices: the SSD1306 OLED display and PCA9685 LED PWM driver
|
||||||
|
Wire.begin();
|
||||||
|
display.init();
|
||||||
|
ledDriver.setAddress(0x40);
|
||||||
|
|
||||||
|
// At 16 Mhz we pick a baud rate with a very acceptable 0.2% error rate
|
||||||
|
// Source: http://www.robotroom.com/Asynchronous-Serial-Communication-2.html
|
||||||
|
Serial.begin(76800);
|
||||||
|
comm.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
if (comm.update())
|
||||||
|
{
|
||||||
|
// TODO get data
|
||||||
|
currentState = State::Connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (currentState)
|
||||||
|
{
|
||||||
|
case State::WaitingForComm:
|
||||||
|
display.show(Screen::WaitingForComm);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case State::Connected:
|
||||||
|
display.show(Screen::ModuleIndex);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* Stairs lighting
|
||||||
|
* Copyright 2017 (c) Mark van Renswoude
|
||||||
|
*
|
||||||
|
* https://git.x2software.net/pub/Stairs
|
||||||
|
*/
|
||||||
|
#include "settings.h"
|
||||||
|
#include <EEPROM.h>
|
||||||
|
|
||||||
|
|
||||||
|
const uint8_t ModuleIndexUndefined = 0xff;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// First byte is 0xAA to recognize uninitialised EEPROM
|
||||||
|
const uint8_t EEPROMHeader = 0xAA;
|
||||||
|
const uint8_t EEPROMCurrentVersion = 1;
|
||||||
|
|
||||||
|
const uint8_t EEPROMAddressHeader = 0;
|
||||||
|
const uint8_t EEPROMAddressVersion = 1;
|
||||||
|
const uint8_t EEPROMAddressModuleIndex = 2;
|
||||||
|
|
||||||
|
|
||||||
|
void Settings::init()
|
||||||
|
{
|
||||||
|
if (EEPROM.read(EEPROMAddressHeader) != EEPROMHeader)
|
||||||
|
{
|
||||||
|
EEPROM.put(EEPROMAddressHeader, EEPROMHeader);
|
||||||
|
EEPROM.put(EEPROMAddressVersion, EEPROMCurrentVersion);
|
||||||
|
EEPROM.put(EEPROMAddressModuleIndex, mModuleIndex);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
uint8_t version = EEPROM.read(EEPROMAddressVersion);
|
||||||
|
if (version >= 1)
|
||||||
|
{
|
||||||
|
mModuleIndex = EEPROM.read(EEPROMAddressModuleIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t Settings::getModuleIndex(void)
|
||||||
|
{
|
||||||
|
return mModuleIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Settings::setModuleIndex(uint8_t index)
|
||||||
|
{
|
||||||
|
if (index == mModuleIndex)
|
||||||
|
return;
|
||||||
|
|
||||||
|
mModuleIndex = index;
|
||||||
|
EEPROM.put(EEPROMAddressModuleIndex, mModuleIndex);
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Stairs lighting
|
||||||
|
* Copyright 2017 (c) Mark van Renswoude
|
||||||
|
*
|
||||||
|
* https://git.x2software.net/pub/Stairs
|
||||||
|
*/
|
||||||
|
#ifndef __settings
|
||||||
|
#define __settings
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
extern const uint8_t ModuleIndexUndefined;
|
||||||
|
|
||||||
|
|
||||||
|
class Settings
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
uint8_t mModuleIndex = ModuleIndexUndefined;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void init();
|
||||||
|
|
||||||
|
uint8_t getModuleIndex();
|
||||||
|
void setModuleIndex(uint8_t index);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
Loading…
Reference in New Issue