Ported to C++ for the ATMega328P

Added fancier display elements now that we've got room to spare
This commit is contained in:
Mark van Renswoude 2018-11-19 21:03:04 +01:00
parent 5f89b3efd2
commit a5eab1427d
33 changed files with 2252 additions and 1921 deletions

View File

@ -1,3 +1,3 @@
REM Internal 8 Mhz oscillator, no clock division
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

View File

@ -13,7 +13,11 @@ env_default = board
[env:board]
platform = atmelavr
board = attiny4313
framework = arduino
board = 328p8m
board_build.f_cpu = 8000000L
upload_speed = 115200
upload_protocol = usbtiny
upload_protocol = usbtiny
lib_deps =
Adafruit GFX Library

BIN
module/res/CommIcon.psd Normal file

Binary file not shown.

BIN
module/res/WaitCursor.psd Normal file

Binary file not shown.

View File

@ -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(&currentX, firstStep);
ssd1306_drawminus_32px(currentX);
currentX += CharOuterWidth;
display_drawNumber(&currentX, 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;
}

197
module/src/display.cpp Normal file
View File

@ -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();
}

View File

@ -7,8 +7,41 @@
#ifndef __display
#define __display
void display_init(void);
void display_drawIndex(void);
void display_drawWait(void);
#include <Arduino.h>
#include <Wire.h>
#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

View File

@ -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);
}

29
module/src/global.cpp Normal file
View File

@ -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;

View File

@ -7,13 +7,14 @@
#ifndef __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
void global_init(void);
uint8_t global_getModuleIndex(void);
void global_setModuleIndex(uint8_t index);
extern Settings settings;
extern Display display;
extern RS485 comm;
extern PCA9685 ledDriver;
#endif

28
module/src/icons.h Normal file
View File

@ -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

View File

@ -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_

127
module/src/lib/PCA9685.cpp Normal file
View File

@ -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();
}

48
module/src/lib/PCA9685.h Normal file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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;
}

56
module/src/main.cpp Normal file
View File

@ -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;
}
}

56
module/src/settings.cpp Normal file
View File

@ -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);
}

27
module/src/settings.h Normal file
View File

@ -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