parent
bc2516607e
commit
3b6cc9f0b9
@ -1,112 +1,53 @@
|
|||||||
//#define DEBUG_SERIAL false
|
//#define DEBUG_SERIAL false
|
||||||
|
#ifndef KEYBOARD_CONFIG_H
|
||||||
|
#define KEYBOARD_CONFIG_H
|
||||||
|
|
||||||
|
#include "WS2812.h"
|
||||||
#define EEPROM_KEYMAP_LOCATION 0
|
#define EEPROM_KEYMAP_LOCATION 0
|
||||||
|
|
||||||
#define MODEL01keytest true
|
#define MODEL01 true
|
||||||
|
|
||||||
#ifdef MODEL00
|
#ifdef SYMMETRIC60
|
||||||
#define COLS 14
|
|
||||||
#define ROWS 5
|
#define ROWS 5
|
||||||
static const byte colPins[COLS] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, A0 };
|
#define COLS 14
|
||||||
static const byte rowPins[ROWS] = { A5, A4, A3, A2, A1 };
|
static const byte colPins[COLS] = {
|
||||||
|
A0, 3,13, 5, 10, 9, 8, 6, 12, 4, 11, 1, 0, 2 };
|
||||||
|
|
||||||
|
static const byte rowPins[ROWS] = { A5,A4,A3,A2,A1};
|
||||||
|
#define LED_DATA_PIN 7
|
||||||
|
#define LED_COUNT 0
|
||||||
|
static const int key_led_map[ROWS][COLS] = {};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef MODEL00bis
|
#ifdef MODEL01
|
||||||
#define COLS 14
|
|
||||||
#define ROWS 5
|
|
||||||
static const byte colPins[COLS] = { 0,1, 2, 3, 4, 5,6,7, 8, 9, 10,11,12, A0};
|
|
||||||
|
|
||||||
static const byte rowPins[ROWS] = { A1,A2,A3,A4,A5};
|
|
||||||
|
|
||||||
#endif
|
int RIGHT_COLS=8;
|
||||||
|
int RIGHT_ROWS=4;
|
||||||
|
|
||||||
#ifdef MODEL00piersjesse
|
|
||||||
#define COLS 16
|
|
||||||
#define ROWS 4
|
|
||||||
|
|
||||||
static const byte colPins[COLS] = {
|
int LEFT_COLS=8;
|
||||||
MOSI,
|
int LEFT_ROWS=4;
|
||||||
SCK,
|
int left_colpins[]={7,6,5,4,3,2,1,0};
|
||||||
1,
|
int left_rowpins[]={8,9,10,11};
|
||||||
0,
|
|
||||||
2,
|
|
||||||
3,
|
|
||||||
4,
|
|
||||||
9,
|
|
||||||
8,
|
|
||||||
5,
|
|
||||||
6,
|
|
||||||
7,
|
|
||||||
MISO,
|
|
||||||
12,
|
|
||||||
A5,
|
|
||||||
A4
|
|
||||||
|
|
||||||
};
|
|
||||||
static const byte rowPins[ROWS] = { A0, A1,A2,A3 };
|
|
||||||
|
|
||||||
#endif
|
int right_colpins[]={0,1,2,3,4,5,6,7};
|
||||||
|
int right_rowpins[]={8,9,10,11};
|
||||||
|
|
||||||
#ifdef MODEL01keytest
|
|
||||||
#define COLS 16
|
|
||||||
#define ROWS 4
|
|
||||||
|
|
||||||
static const byte colPins[COLS] = {
|
|
||||||
1,
|
|
||||||
2,
|
|
||||||
3,
|
|
||||||
4,
|
|
||||||
5,
|
|
||||||
6,
|
|
||||||
7,
|
|
||||||
8,
|
|
||||||
9,
|
|
||||||
10,
|
|
||||||
11,
|
|
||||||
12,
|
|
||||||
SCK,
|
|
||||||
MISO,
|
|
||||||
A5,
|
|
||||||
A4,
|
|
||||||
|
|
||||||
};
|
|
||||||
static const byte rowPins[ROWS] = { A0, A1,A2,A3 };
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
||||||
#ifdef MODEL00piers
|
|
||||||
#define COLS 16
|
#define COLS 16
|
||||||
#define ROWS 5
|
#define ROWS 4
|
||||||
|
|
||||||
static const byte colPins[COLS] = {
|
|
||||||
|
|
||||||
3,
|
|
||||||
A4,
|
|
||||||
A5,
|
|
||||||
MISO,
|
|
||||||
SCK,
|
|
||||||
1,
|
|
||||||
MOSI,
|
|
||||||
2,
|
|
||||||
4,
|
|
||||||
5,
|
|
||||||
7,
|
|
||||||
8,
|
|
||||||
9,
|
|
||||||
10,
|
|
||||||
0,
|
|
||||||
12,
|
|
||||||
};
|
|
||||||
static const byte rowPins[ROWS] = { A0, A1,A2,A3 };
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
//#static const byte colPins[COLS] = { 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
|
|
||||||
//#static const byte rowPins[ROWS] = { A2, A3, A4, A5, 15 };
|
|
||||||
|
|
||||||
|
|
||||||
// if we're sticking to boot protocol, these could all be 6 + mods
|
// if we're sticking to boot protocol, these could all be 6 + mods
|
||||||
// but *mumble*
|
// but *mumble*
|
||||||
|
|
||||||
#define KEYS_HELD_BUFFER 12
|
#define KEYS_HELD_BUFFER 12
|
||||||
|
|
||||||
|
#endif
|
||||||
|
@ -0,0 +1,572 @@
|
|||||||
|
/*
|
||||||
|
sx1509_library.cpp
|
||||||
|
Code file for the SX1509 Arduino library.
|
||||||
|
|
||||||
|
by: Jim Lindblom
|
||||||
|
SparkFun Electronics
|
||||||
|
date: December 13, 2012
|
||||||
|
|
||||||
|
license: Beerware. Feel free to use it, with or without attribution, in
|
||||||
|
your own projects. If you find it helpful, buy me a beer next time you
|
||||||
|
see me at the local pub.
|
||||||
|
|
||||||
|
In here you'll find the Arduino code used to interface with the SX1509 I2C
|
||||||
|
16 I/O expander. There are functions to take advantage of everything the
|
||||||
|
SX1509 provides - input/output setting, writing pins high/low, reading
|
||||||
|
the input value of pins, LED driver utilities (blink, breath, pwm), and
|
||||||
|
keypad engine utilites.
|
||||||
|
|
||||||
|
See the header file (sx1509_library.h) for detailed descriptions of each of
|
||||||
|
the sx1509Class methods.
|
||||||
|
|
||||||
|
For example uses of these functions, see the Arduino example codes in the
|
||||||
|
./examples/ folder.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Wire.h>
|
||||||
|
#include "Arduino.h"
|
||||||
|
#include "sx1509_library.h"
|
||||||
|
#include "sx1509_registers.h"
|
||||||
|
|
||||||
|
sx1509Class::sx1509Class(byte address, byte resetPin, byte interruptPin, byte oscillatorPin)
|
||||||
|
{
|
||||||
|
// Store the received parameters into member variables
|
||||||
|
deviceAddress = address;
|
||||||
|
pinInterrupt = interruptPin;
|
||||||
|
pinOscillator = oscillatorPin;
|
||||||
|
pinReset = resetPin;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte sx1509Class::init(void)
|
||||||
|
{
|
||||||
|
if (pinInterrupt != 255)
|
||||||
|
{
|
||||||
|
pinMode(pinInterrupt, INPUT_PULLUP);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin I2C
|
||||||
|
Wire.begin();
|
||||||
|
|
||||||
|
// If the reset pin is connected
|
||||||
|
if (pinReset != 255)
|
||||||
|
reset(1);
|
||||||
|
else
|
||||||
|
reset(0);
|
||||||
|
|
||||||
|
// Communication test. We'll read from two registers with different
|
||||||
|
// default values to verify communication.
|
||||||
|
unsigned int testRegisters = 0;
|
||||||
|
testRegisters = readWord(REG_INTERRUPT_MASK_A); // This should return 0xFF00
|
||||||
|
// Then read a byte that should be 0x00
|
||||||
|
if (testRegisters == 0xFF00)
|
||||||
|
return 1;
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sx1509Class::reset(bool hardware)
|
||||||
|
{
|
||||||
|
// if hardware bool is set
|
||||||
|
if (hardware)
|
||||||
|
{
|
||||||
|
// Check if bit 2 of REG_MISC is set
|
||||||
|
// if so nReset will not issue a POR, we'll need to clear that bit first
|
||||||
|
byte regMisc = readByte(REG_MISC);
|
||||||
|
if (regMisc & (1<<2))
|
||||||
|
{
|
||||||
|
regMisc &= ~(1<<2);
|
||||||
|
writeByte(REG_MISC, regMisc);
|
||||||
|
}
|
||||||
|
// Reset the SX1509, the pin is active low
|
||||||
|
pinMode(pinReset, OUTPUT); // set reset pin as output
|
||||||
|
digitalWrite(pinReset, LOW); // pull reset pin low
|
||||||
|
delay(1); // Wait for the pin to settle
|
||||||
|
digitalWrite(pinReset, HIGH); // pull reset pin back high
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Software reset command sequence:
|
||||||
|
writeByte(REG_RESET, 0x12);
|
||||||
|
writeByte(REG_RESET, 0x34);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sx1509Class::pinDir(byte pin, byte inOut)
|
||||||
|
{
|
||||||
|
unsigned int tempRegDir = readWord(REG_DIR_B);
|
||||||
|
// The SX1509 RegDir registers: REG_DIR_B, REG_DIR_A
|
||||||
|
// 0: IO is configured as an output
|
||||||
|
// 1: IO is configured as an input
|
||||||
|
// Flip inOut, in arduino.h INPUT = 0, OUTPUT = 1
|
||||||
|
if (!inOut) tempRegDir |= (1<<pin);
|
||||||
|
else tempRegDir &= ~(1<<pin);
|
||||||
|
|
||||||
|
writeWord(REG_DIR_B, tempRegDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void sx1509Class::rawWritePin(byte pin, byte highLow)
|
||||||
|
{
|
||||||
|
if (highLow)
|
||||||
|
writeWord(REG_DATA_B, readWord(REG_DATA_B) | (1 <<pin));
|
||||||
|
else
|
||||||
|
writeWord(REG_DATA_B, readWord(REG_DATA_B) & ~(1<<pin));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void sx1509Class::writePin(byte pin, byte highLow)
|
||||||
|
{
|
||||||
|
|
||||||
|
if ((0xFFFF^readWord(REG_DIR_B))&(1<<pin)) // If the pin is an output, write high/low
|
||||||
|
{
|
||||||
|
unsigned int tempRegData = readWord(REG_DATA_B);
|
||||||
|
if (highLow) tempRegData |= (1<<pin);
|
||||||
|
else tempRegData &= ~(1<<pin);
|
||||||
|
writeWord(REG_DATA_B, tempRegData);
|
||||||
|
}
|
||||||
|
else // Otherwise the pin is an input, pull-up/down
|
||||||
|
{
|
||||||
|
unsigned int tempPullUp = readWord(REG_PULL_UP_B);
|
||||||
|
unsigned int tempPullDown = readWord(REG_PULL_DOWN_B);
|
||||||
|
|
||||||
|
if (highLow) // if HIGH, do pull-up, disable pull-down
|
||||||
|
{
|
||||||
|
tempPullUp |= (1<<pin);
|
||||||
|
tempPullDown &= ~(1<<pin);
|
||||||
|
writeWord(REG_PULL_UP_B, tempPullUp);
|
||||||
|
writeWord(REG_PULL_DOWN_B, tempPullDown);
|
||||||
|
}
|
||||||
|
else // If LOW do pull-down, disable pull-up
|
||||||
|
{
|
||||||
|
tempPullDown |= (1<<pin);
|
||||||
|
tempPullUp &= ~(1<<pin);
|
||||||
|
writeWord(REG_PULL_UP_B, tempPullUp);
|
||||||
|
writeWord(REG_PULL_DOWN_B, tempPullDown);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
byte sx1509Class::rawReadPin(byte pin)
|
||||||
|
{
|
||||||
|
return (readWord(REG_DATA_B) & (1<<pin));
|
||||||
|
}
|
||||||
|
|
||||||
|
byte sx1509Class::readPin(byte pin)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (readWord(REG_DIR_B) & (1<<pin)) // If the pin is an input
|
||||||
|
{
|
||||||
|
unsigned int tempRegData = readWord(REG_DATA_B);
|
||||||
|
if (tempRegData & (1<<pin))
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void sx1509Class::ledDriverInit(byte pin, byte freq, bool log)
|
||||||
|
{
|
||||||
|
unsigned int tempWord;
|
||||||
|
byte tempByte;
|
||||||
|
// Disable input buffer
|
||||||
|
// Writing a 1 to the pin bit will disable that pins input buffer
|
||||||
|
tempWord = readWord(REG_INPUT_DISABLE_B);
|
||||||
|
tempWord |= (1<<pin);
|
||||||
|
writeWord(REG_INPUT_DISABLE_B, tempWord);
|
||||||
|
|
||||||
|
// Disable pull-up
|
||||||
|
// Writing a 0 to the pin bit will disable that pull-up resistor
|
||||||
|
tempWord = readWord(REG_PULL_UP_B);
|
||||||
|
tempWord &= ~(1<<pin);
|
||||||
|
writeWord(REG_PULL_UP_B, tempWord);
|
||||||
|
|
||||||
|
// Enable open-drain
|
||||||
|
// Writing a 1 to the pin bit will enable open drain on that pin
|
||||||
|
tempWord = readWord(REG_OPEN_DRAIN_B);
|
||||||
|
tempWord |= (1<<pin);
|
||||||
|
writeWord(REG_OPEN_DRAIN_B, tempWord);
|
||||||
|
|
||||||
|
// Set direction to output (REG_DIR_B)
|
||||||
|
pinDir(pin, OUTPUT);
|
||||||
|
|
||||||
|
// Enable oscillator (REG_CLOCK)
|
||||||
|
tempByte = readByte(REG_CLOCK);
|
||||||
|
tempByte |= (1<<6); // Internal 2MHz oscillator part 1 (set bit 6)
|
||||||
|
tempByte &= ~(1<<5); // Internal 2MHz oscillator part 2 (clear bit 5)
|
||||||
|
writeByte(REG_CLOCK, tempByte);
|
||||||
|
|
||||||
|
// Configure LED driver clock and mode (REG_MISC)
|
||||||
|
tempByte = readByte(REG_MISC);
|
||||||
|
if (log)
|
||||||
|
{
|
||||||
|
tempByte |= (1<<7); // set logarithmic mode bank B
|
||||||
|
tempByte |= (1<<3); // set logarithmic mode bank A
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tempByte &= ~(1<<7); // set linear mode bank B
|
||||||
|
tempByte &= ~(1<<3); // set linear mode bank A
|
||||||
|
}
|
||||||
|
if (freq == 0) // don't want it to be 0, that'll disable all led drivers
|
||||||
|
freq = 1;
|
||||||
|
freq = (freq & 0x07) << 4; // freq should only be 3 bits from 6:4
|
||||||
|
tempByte |= freq;
|
||||||
|
writeByte(REG_MISC, tempByte);
|
||||||
|
|
||||||
|
// Enable LED driver operation (REG_LED_DRIVER_ENABLE)
|
||||||
|
tempWord = readWord(REG_LED_DRIVER_ENABLE_B);
|
||||||
|
tempWord |= (1<<pin);
|
||||||
|
writeWord(REG_LED_DRIVER_ENABLE_B, tempWord);
|
||||||
|
|
||||||
|
// Set REG_DATA bit low ~ LED driver started
|
||||||
|
tempWord = readWord(REG_DATA_B);
|
||||||
|
tempWord &= ~(1<<pin);
|
||||||
|
writeWord(REG_DATA_B, tempWord);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sx1509Class::pwm(byte pin, byte iOn)
|
||||||
|
{
|
||||||
|
// Write the on intensity of pin
|
||||||
|
// Linear mode: Ion = iOn
|
||||||
|
// Log mode: Ion = f(iOn)
|
||||||
|
writeByte(REG_I_ON[pin], iOn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sx1509Class::blink(byte pin, byte tOn, byte tOff,
|
||||||
|
byte offIntensity, byte onIntensity,
|
||||||
|
byte tRise, byte tFall)
|
||||||
|
{
|
||||||
|
// Keep parameters within their limits:
|
||||||
|
tOn &= 0x1F; // tOn should be a 5-bit value
|
||||||
|
tOff &= 0x1F; // tOff should be a 5-bit value
|
||||||
|
offIntensity &= 0x07;
|
||||||
|
// Write the time on
|
||||||
|
// 1-15: TON = 64 * tOn * (255/ClkX)
|
||||||
|
// 16-31: TON = 512 * tOn * (255/ClkX)
|
||||||
|
writeByte(REG_T_ON[pin], tOn);
|
||||||
|
|
||||||
|
|
||||||
|
// Write the time/intensity off register
|
||||||
|
// 1-15: TOFF = 64 * tOff * (255/ClkX)
|
||||||
|
// 16-31: TOFF = 512 * tOff * (255/ClkX)
|
||||||
|
// linear Mode - IOff = 4 * offIntensity
|
||||||
|
// log mode - Ioff = f(4 * offIntensity)
|
||||||
|
writeByte(REG_OFF[pin], (tOff<<3) | offIntensity);
|
||||||
|
|
||||||
|
// Write the on intensity:
|
||||||
|
writeByte(REG_I_ON[pin], onIntensity);
|
||||||
|
|
||||||
|
// Prepare tRise and tFall
|
||||||
|
tRise &= 0x1F; // tRise is a 5-bit value
|
||||||
|
tFall &= 0x1F; // tFall is a 5-bit value
|
||||||
|
|
||||||
|
|
||||||
|
// Write regTRise
|
||||||
|
// 0: Off
|
||||||
|
// 1-15: TRise = (regIOn - (4 * offIntensity)) * tRise * (255/ClkX)
|
||||||
|
// 16-31: TRise = 16 * (regIOn - (4 * offIntensity)) * tRise * (255/ClkX)
|
||||||
|
if (REG_T_RISE[pin] != 0xFF)
|
||||||
|
writeByte(REG_T_RISE[pin], tRise);
|
||||||
|
// Write regTFall
|
||||||
|
// 0: off
|
||||||
|
// 1-15: TFall = (regIOn - (4 * offIntensity)) * tFall * (255/ClkX)
|
||||||
|
// 16-31: TFall = 16 * (regIOn - (4 * offIntensity)) * tFall * (255/ClkX)
|
||||||
|
if (REG_T_FALL[pin] != 0xFF)
|
||||||
|
writeByte(REG_T_FALL[pin], tFall);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sx1509Class::keypad(byte rows, byte columns, byte sleepTime, byte scanTime)
|
||||||
|
{
|
||||||
|
unsigned int tempWord;
|
||||||
|
byte tempByte;
|
||||||
|
|
||||||
|
// Set regDir 0:7 outputs, 8:15 inputs:
|
||||||
|
tempWord = readWord(REG_DIR_B);
|
||||||
|
for (int i=0; i<rows; i++)
|
||||||
|
tempWord &= ~(1<<i);
|
||||||
|
for (int i=8; i<(columns * 2); i++)
|
||||||
|
tempWord |= (1<<i);
|
||||||
|
writeWord(REG_DIR_B, tempWord);
|
||||||
|
|
||||||
|
// Set regOpenDrain on 0:7:
|
||||||
|
tempByte = readByte(REG_OPEN_DRAIN_A);
|
||||||
|
for (int i=0; i<rows; i++)
|
||||||
|
tempByte |= (1<<i);
|
||||||
|
writeByte(REG_OPEN_DRAIN_A, tempByte);
|
||||||
|
|
||||||
|
// Set regPullUp on 8:15:
|
||||||
|
tempByte = readByte(REG_PULL_UP_B);
|
||||||
|
for (int i=0; i<columns; i++)
|
||||||
|
tempByte |= (1<<i);
|
||||||
|
writeByte(REG_PULL_UP_B, tempByte);
|
||||||
|
|
||||||
|
// Enable and configure debouncing on 8:15:
|
||||||
|
tempByte = readByte(REG_DEBOUNCE_ENABLE_B);
|
||||||
|
for (int i=0; i<columns; i++)
|
||||||
|
tempByte |= (1<<i);
|
||||||
|
writeByte(REG_DEBOUNCE_ENABLE_B, tempByte);
|
||||||
|
writeByte(REG_DEBOUNCE_CONFIG, (scanTime & 0b111)); // Debounce must be less than scan time
|
||||||
|
|
||||||
|
// RegKeyConfig1 sets the auto sleep time and scan time per row
|
||||||
|
sleepTime = (sleepTime & 0b111)<<4;
|
||||||
|
scanTime &= 0b111; // Scan time is bits 2:0
|
||||||
|
tempByte = sleepTime | scanTime;
|
||||||
|
writeByte(REG_KEY_CONFIG_1, tempByte);
|
||||||
|
|
||||||
|
// RegKeyConfig2 tells the SX1509 how many rows and columns we've got going
|
||||||
|
rows = (rows - 1) & 0b111; // 0 = off, 0b001 = 2 rows, 0b111 = 8 rows, etc.
|
||||||
|
columns = (columns - 1) & 0b111; // 0b000 = 1 column, ob111 = 8 columns, etc.
|
||||||
|
writeByte(REG_KEY_CONFIG_2, (rows << 3) | columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int sx1509Class::readKeyData()
|
||||||
|
{
|
||||||
|
return (0xFFFF ^ readWord(REG_KEY_DATA_1));
|
||||||
|
}
|
||||||
|
|
||||||
|
void sx1509Class::sync(void)
|
||||||
|
{
|
||||||
|
// First check if nReset functionality is set
|
||||||
|
byte regMisc = readByte(REG_MISC);
|
||||||
|
if (!(regMisc & 0x04))
|
||||||
|
{
|
||||||
|
regMisc |= (1<<2);
|
||||||
|
writeByte(REG_MISC, regMisc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle nReset pin to sync LED timers
|
||||||
|
pinMode(pinReset, OUTPUT); // set reset pin as output
|
||||||
|
digitalWrite(pinReset, LOW); // pull reset pin low
|
||||||
|
delay(1); // Wait for the pin to settle
|
||||||
|
digitalWrite(pinReset, HIGH); // pull reset pin back high
|
||||||
|
|
||||||
|
// Return nReset to POR functionality
|
||||||
|
writeByte(REG_MISC, (regMisc & ~(1<<2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void sx1509Class::debounceConfig(byte configValue)
|
||||||
|
{
|
||||||
|
// First make sure clock is configured
|
||||||
|
byte tempByte = readByte(REG_MISC);
|
||||||
|
if ((tempByte & 0x70) == 0)
|
||||||
|
{
|
||||||
|
tempByte |= (1<<4); // Just default to no divider if not set
|
||||||
|
writeByte(REG_MISC, tempByte);
|
||||||
|
}
|
||||||
|
tempByte = readByte(REG_CLOCK);
|
||||||
|
if ((tempByte & 0x60) == 0)
|
||||||
|
{
|
||||||
|
tempByte |= (1<<6); // default to internal osc.
|
||||||
|
writeByte(REG_CLOCK, tempByte);
|
||||||
|
}
|
||||||
|
|
||||||
|
configValue &= 0b111; // 3-bit value
|
||||||
|
writeByte(REG_DEBOUNCE_CONFIG, configValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sx1509Class::debounceEnable(byte pin)
|
||||||
|
{
|
||||||
|
unsigned int debounceEnable = readWord(REG_DEBOUNCE_ENABLE_B);
|
||||||
|
debounceEnable |= (1<<pin);
|
||||||
|
writeWord(REG_DEBOUNCE_ENABLE_B, debounceEnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sx1509Class::enableInterrupt(byte pin, byte riseFall)
|
||||||
|
{
|
||||||
|
// Set REG_INTERRUPT_MASK
|
||||||
|
unsigned int tempWord = readWord(REG_INTERRUPT_MASK_B);
|
||||||
|
tempWord &= ~(1<<pin); // 0 = event on IO will trigger interrupt
|
||||||
|
writeWord(REG_INTERRUPT_MASK_B, tempWord);
|
||||||
|
|
||||||
|
byte sensitivity = 0;
|
||||||
|
switch (riseFall)
|
||||||
|
{
|
||||||
|
case CHANGE:
|
||||||
|
sensitivity = 0b11;
|
||||||
|
break;
|
||||||
|
case FALLING:
|
||||||
|
sensitivity = 0b10;
|
||||||
|
break;
|
||||||
|
case RISING:
|
||||||
|
sensitivity = 0b01;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set REG_SENSE_XXX
|
||||||
|
// Sensitivity is set as follows:
|
||||||
|
// 00: None
|
||||||
|
// 01: Rising
|
||||||
|
// 10: Falling
|
||||||
|
// 11: Both
|
||||||
|
byte pinMask = (pin & 0x07) * 2;
|
||||||
|
byte senseRegister;
|
||||||
|
|
||||||
|
// Need to select between two words. One for bank A, one for B.
|
||||||
|
if (pin >= 8) senseRegister = REG_SENSE_HIGH_B;
|
||||||
|
else senseRegister = REG_SENSE_HIGH_A;
|
||||||
|
|
||||||
|
tempWord = readWord(senseRegister);
|
||||||
|
tempWord &= ~(0b11<<pinMask); // Mask out the bits we want to write
|
||||||
|
tempWord |= (sensitivity<<pinMask); // Add our new bits
|
||||||
|
writeWord(senseRegister, tempWord);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int sx1509Class::interruptSource(void)
|
||||||
|
{
|
||||||
|
unsigned int intSource = readWord(REG_INTERRUPT_SOURCE_B);
|
||||||
|
writeWord(REG_INTERRUPT_SOURCE_B, 0xFFFF); // Clear interrupts
|
||||||
|
return intSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sx1509Class::configClock(byte oscSource, byte oscPinFunction, byte oscFreqOut, byte oscDivider)
|
||||||
|
{
|
||||||
|
// RegClock constructed as follows:
|
||||||
|
// 6:5 - Oscillator frequency souce
|
||||||
|
// 00: off, 01: external input, 10: internal 2MHz, 1: reserved
|
||||||
|
// 4 - OSCIO pin function
|
||||||
|
// 0: input, 1 ouptut
|
||||||
|
// 3:0 - Frequency of oscout pin
|
||||||
|
// 0: LOW, 0xF: high, else fOSCOUT = FoSC/(2^(RegClock[3:0]-1))
|
||||||
|
oscSource = (oscSource & 0b11)<<5; // 2-bit value, bits 6:5
|
||||||
|
oscPinFunction = (oscPinFunction & 1)<<4; // 1-bit value bit 4
|
||||||
|
oscFreqOut = (oscFreqOut & 0b1111); // 4-bit value, bits 3:0
|
||||||
|
byte regClock = oscSource | oscPinFunction | oscFreqOut;
|
||||||
|
writeByte(REG_CLOCK, regClock);
|
||||||
|
|
||||||
|
// Config RegMisc[6:4] with oscDivider
|
||||||
|
// 0: off, else ClkX = fOSC / (2^(RegMisc[6:4] -1))
|
||||||
|
oscDivider = (oscDivider & 0b111)<<4; // 3-bit value, bits 6:4
|
||||||
|
byte regMisc = readByte(REG_MISC);
|
||||||
|
regMisc &= ~(0b111<<4);
|
||||||
|
regMisc |= oscDivider;
|
||||||
|
writeByte(REG_MISC, regMisc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// readByte(byte registerAddress)
|
||||||
|
// This function reads a single byte located at the registerAddress register.
|
||||||
|
// - deviceAddress should already be set by the constructor.
|
||||||
|
// - Return value is the byte read from registerAddress
|
||||||
|
// - Currently returns 0 if communication has timed out
|
||||||
|
byte sx1509Class::readByte(byte registerAddress)
|
||||||
|
{
|
||||||
|
byte readValue;
|
||||||
|
unsigned int timeout = RECEIVE_TIMEOUT_VALUE;
|
||||||
|
|
||||||
|
Wire.beginTransmission(deviceAddress);
|
||||||
|
Wire.write(registerAddress);
|
||||||
|
Wire.endTransmission();
|
||||||
|
Wire.requestFrom(deviceAddress, (byte) 1);
|
||||||
|
|
||||||
|
while ((Wire.available() < 1) && (timeout != 0))
|
||||||
|
timeout--;
|
||||||
|
|
||||||
|
if (timeout == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
readValue = Wire.read();
|
||||||
|
|
||||||
|
return readValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// readWord(byte registerAddress)
|
||||||
|
// This function will read a two-byte word beginning at registerAddress
|
||||||
|
// - A 16-bit unsigned int will be returned.
|
||||||
|
// - The msb of the return value will contain the value read from registerAddress
|
||||||
|
// - The lsb of the return value will contain the value read from registerAddress + 1
|
||||||
|
unsigned int sx1509Class::readWord(byte registerAddress)
|
||||||
|
{
|
||||||
|
unsigned int readValue;
|
||||||
|
unsigned int msb, lsb;
|
||||||
|
unsigned int timeout = RECEIVE_TIMEOUT_VALUE * 2;
|
||||||
|
|
||||||
|
Wire.beginTransmission(deviceAddress);
|
||||||
|
Wire.write(registerAddress);
|
||||||
|
Wire.endTransmission();
|
||||||
|
Wire.requestFrom(deviceAddress, (byte) 2);
|
||||||
|
|
||||||
|
while ((Wire.available() < 2) && (timeout != 0))
|
||||||
|
timeout--;
|
||||||
|
|
||||||
|
if (timeout == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
msb = (Wire.read() & 0x00FF) << 8;
|
||||||
|
lsb = (Wire.read() & 0x00FF);
|
||||||
|
readValue = msb | lsb;
|
||||||
|
|
||||||
|
return readValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// readBytes(byte firstRegisterAddress, byte * destination, byte length)
|
||||||
|
// This function reads a series of bytes incrementing from a given address
|
||||||
|
// - firstRegsiterAddress is the first address to be read
|
||||||
|
// - destination is an array of bytes where the read values will be stored into
|
||||||
|
// - length is the number of bytes to be read
|
||||||
|
// - No return value.
|
||||||
|
void sx1509Class::readBytes(byte firstRegisterAddress, byte * destination, byte length)
|
||||||
|
{
|
||||||
|
byte readValue;
|
||||||
|
|
||||||
|
Wire.beginTransmission(deviceAddress);
|
||||||
|
Wire.write(firstRegisterAddress);
|
||||||
|
Wire.endTransmission();
|
||||||
|
Wire.requestFrom(deviceAddress, length);
|
||||||
|
|
||||||
|
while (Wire.available() < length)
|
||||||
|
;
|
||||||
|
|
||||||
|
for (int i=0; i<length; i++)
|
||||||
|
{
|
||||||
|
destination[i] = Wire.read();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeByte(byte registerAddress, byte writeValue)
|
||||||
|
// This function writes a single byte to a single register on the SX509.
|
||||||
|
// - writeValue is written to registerAddress
|
||||||
|
// - deviceAddres should already be set from the constructor
|
||||||
|
// - No return value.
|
||||||
|
void sx1509Class::writeByte(byte registerAddress, byte writeValue)
|
||||||
|
{
|
||||||
|
Wire.beginTransmission(deviceAddress);
|
||||||
|
Wire.write(registerAddress);
|
||||||
|
Wire.write(writeValue);
|
||||||
|
Wire.endTransmission();
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeWord(byte registerAddress, ungisnged int writeValue)
|
||||||
|
// This function writes a two-byte word to registerAddress and registerAddress + 1
|
||||||
|
// - the upper byte of writeValue is written to registerAddress
|
||||||
|
// - the lower byte of writeValue is written to registerAddress + 1
|
||||||
|
// - No return value.
|
||||||
|
void sx1509Class::writeWord(byte registerAddress, unsigned int writeValue)
|
||||||
|
{
|
||||||
|
Wire.beginTransmission(deviceAddress);
|
||||||
|
Wire.write(registerAddress);
|
||||||
|
Wire.write((writeValue & 0xFF00) >> 8);
|
||||||
|
Wire.write(writeValue & 0x00FF);
|
||||||
|
Wire.endTransmission();
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeBytes(byte firstRegisterAddress, byte * writeArray, byte length)
|
||||||
|
// This function writes an array of bytes, beggining at a specific adddress
|
||||||
|
// - firstRegisterAddress is the initial register to be written.
|
||||||
|
// - All writes following will be at incremental register addresses.
|
||||||
|
// - writeArray should be an array of byte values to be written.
|
||||||
|
// - length should be the number of bytes to be written.
|
||||||
|
// - no return value.
|
||||||
|
void sx1509Class::writeBytes(byte firstRegisterAddress, byte * writeArray, byte length)
|
||||||
|
{
|
||||||
|
Wire.beginTransmission(deviceAddress);
|
||||||
|
Wire.write(firstRegisterAddress);
|
||||||
|
for (int i=0; i<length; i++)
|
||||||
|
{
|
||||||
|
Wire.write(writeArray[i]);
|
||||||
|
}
|
||||||
|
Wire.endTransmission();
|
||||||
|
}
|
@ -0,0 +1,337 @@
|
|||||||
|
/*
|
||||||
|
sx1509_library.h
|
||||||
|
Header file for the SX1509 Arduino library.
|
||||||
|
|
||||||
|
by: Jim Lindblom
|
||||||
|
SparkFun Electronics
|
||||||
|
date: December 13, 2012
|
||||||
|
|
||||||
|
license: Beerware. Feel free to use it, with or without attribution, in
|
||||||
|
your own projects. If you find it helpful, buy me a beer next time you
|
||||||
|
see me at the local pub.
|
||||||
|
|
||||||
|
In here you'll find the Arduino code used to interface with the SX1509 I2C
|
||||||
|
16 I/O expander. There are functions to take advantage of everything the
|
||||||
|
SX1509 provides - input/output setting, writing pins high/low, reading
|
||||||
|
the input value of pins, LED driver utilities (blink, breath, pwm), and
|
||||||
|
keypad engine utilites.
|
||||||
|
|
||||||
|
This file includes detailed descriptions of each of the sx1509Class's
|
||||||
|
public methods.
|
||||||
|
|
||||||
|
For example uses of these functions, see the Arduino example codes in the
|
||||||
|
./examples/ folder.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Arduino.h"
|
||||||
|
|
||||||
|
#ifndef sx1509_library_H
|
||||||
|
#define sx1509_library_H
|
||||||
|
|
||||||
|
#define RECEIVE_TIMEOUT_VALUE 1000 // Timeout for I2C receive
|
||||||
|
|
||||||
|
// These are used for setting LED driver to linear or log mode:
|
||||||
|
#define LINEAR 0
|
||||||
|
#define LOGARITHMIC 1
|
||||||
|
|
||||||
|
// These are used for clock config:
|
||||||
|
#define INTERNAL_CLOCK 2
|
||||||
|
#define EXTERNAL_CLOCK 1
|
||||||
|
|
||||||
|
class sx1509Class
|
||||||
|
{
|
||||||
|
private: // These private functions are not available to Arduino sketches.
|
||||||
|
// If you need to read or write directly to registers, consider
|
||||||
|
// putting the writeByte, readByte functions in the public section
|
||||||
|
byte deviceAddress; // I2C Address of SX1509
|
||||||
|
// Pin definitions:
|
||||||
|
byte pinInterrupt;
|
||||||
|
byte pinOscillator;
|
||||||
|
byte pinReset;
|
||||||
|
// Read Functions:
|
||||||
|
byte readByte(byte registerAddress);
|
||||||
|
unsigned int readWord(byte registerAddress);
|
||||||
|
void readBytes(byte firstRegisterAddress, byte * destination, byte length);
|
||||||
|
// Write functions:
|
||||||
|
void writeByte(byte registerAddress, byte writeValue);
|
||||||
|
void writeWord(byte registerAddress, unsigned int writeValue);
|
||||||
|
void writeBytes(byte firstRegisterAddress, byte * writeArray, byte length);
|
||||||
|
|
||||||
|
public:
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Constructor - sx1509Class: This function sets up the pins connected to the
|
||||||
|
// SX1509, and sets up the private deviceAddress variable.
|
||||||
|
//
|
||||||
|
// Inputs:
|
||||||
|
// - address: should be the 7-bit address of the SX1509. This should be
|
||||||
|
// one of four values - 0x3E, 0x3F, 0x70, 0x71 - all depending on what the
|
||||||
|
// ADDR0 and ADDR1 pins ar se to. This variable is required.
|
||||||
|
// - resetPin: This is the Arduino pin tied to the SX1509 RST pin. This
|
||||||
|
// pin is optional. If not declared, the library will attempt to
|
||||||
|
// software reset the SX1509.
|
||||||
|
// - interruptPin: This is the Arduino pin tied to the SX1509 active-low
|
||||||
|
// interrupt output. Only necessary if you're planning on using
|
||||||
|
// the interrupt capabilities.
|
||||||
|
// - oscillatorPin: This is the Arduino pin tied to the SX1509's OSCIO
|
||||||
|
// pin. This pin can be an output or an input. This parameter is optional.
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
sx1509Class(byte address, byte resetPin = 255,
|
||||||
|
byte interruptPin = 255, byte oscillatorPin = 255);
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// init(void): This function initializes the SX1509. It begins the Wire
|
||||||
|
// library, resets the IC, and tries to read some registers to prove it's
|
||||||
|
// connected.
|
||||||
|
//
|
||||||
|
// Output: Returns a 1 if communication is successful, 0 on error.
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
byte init(void);
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// reset(bool hardware): This function resets the SX1509 - either a hardware
|
||||||
|
// reset or software. A hardware reset (hardware parameter = 1) pulls the
|
||||||
|
// reset line low, pausing, then pulling the reset line high. A software
|
||||||
|
// reset writes a 0x12 then 0x34 to the REG_RESET as outlined in the
|
||||||
|
// datasheet.
|
||||||
|
//
|
||||||
|
// Input:
|
||||||
|
// - hardware: 0 executes a software reset, 1 executes a hardware reset
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
void reset(bool hardware);
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// pinDir(byte pin, byte inOut): This function sets one of the SX1509's 16
|
||||||
|
// outputs to either an INPUT or OUTPUT.
|
||||||
|
//
|
||||||
|
// Inputs:
|
||||||
|
// - pin: should be a value between 0 and 15
|
||||||
|
// - inOut: The Arduino INPUT and OUTPUT constants should be used for the
|
||||||
|
// inOut parameter. They do what they say!
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
void pinDir(byte pin, byte inOut);
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// writePin(byte pin, byte highLow): This function writes a pin to either high
|
||||||
|
// or low if it's configured as an OUTPUT. If the pin is configured as an
|
||||||
|
// INPUT, this method will activate either the PULL-UP or PULL-DOWN
|
||||||
|
// resistor (HIGH or LOW respectively).
|
||||||
|
//
|
||||||
|
// Inputs:
|
||||||
|
// - pin: The SX1509 pin number. Should be a value between 0 and 15.
|
||||||
|
// - highLow: should be Arduino's defined HIGH or LOW constants.
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
void writePin(byte pin, byte highLow);
|
||||||
|
void rawWritePin(byte pin, byte highLow);
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// readPin(byte pin): This function reads the HIGH/LOW status of a pin.
|
||||||
|
// The pin should be configured as an INPUT, using the pinDir function.
|
||||||
|
//
|
||||||
|
// Inputs:
|
||||||
|
// - pin: The SX1509 pin to be read. should be a value between 0 and 15.
|
||||||
|
// Outputs:
|
||||||
|
// This function returns a 1 if HIGH, 0 if LOW
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
byte readPin(byte pin);
|
||||||
|
byte rawReadPin(byte pin);
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// ledDriverInit(byte pin, byte freq, bool log): This function initializes LED
|
||||||
|
// driving on a pin. It must be called if you want to use the pwm or blink
|
||||||
|
// functions on that pin.
|
||||||
|
//
|
||||||
|
// Inputs:
|
||||||
|
// - pin: The SX1509 pin connected to an LED. Should be 0-15.
|
||||||
|
// - freq: decides ClkX, and should be a value between 1-7
|
||||||
|
// - ClkX = 2MHz / (2^(freq - 1)
|
||||||
|
// - freq defaults to 1, which makes ClkX = 2MHz
|
||||||
|
// - log: selects either linear or logarithmic mode on the LED drivers
|
||||||
|
// - log defaults to 0, linear mode
|
||||||
|
// - currently log sets both bank A and B to the same mode
|
||||||
|
// Note: this function automatically decides to use the internal 2MHz osc.
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
void ledDriverInit(byte pin, byte freq = 1, bool log = 0);
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// pwm(byte pin, byte iOn): This function can be used to control the intensity
|
||||||
|
// of an output pin connected to an LED.
|
||||||
|
//
|
||||||
|
// Inputs:
|
||||||
|
// - pin: The SX1509 pin connecte to an LED.Should be 0-15.
|
||||||
|
// - iOn: should be a 0-255 value setting the intensity of the LED
|
||||||
|
// - 0 is completely off, 255 is 100% on.
|
||||||
|
//
|
||||||
|
// Note: ledDriverInit should be called on the pin before calling this.
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
void pwm(byte pin, byte iOn);
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// blink(byte pin, byte tOn, byte tOff, byte offIntensity, byte tRise, byte
|
||||||
|
// tFall): blink performs both the blink and breath LED driver functions.
|
||||||
|
//
|
||||||
|
// Inputs:
|
||||||
|
// - pin: the SX1509 pin (0-15) you want to set blinking/breathing.
|
||||||
|
// - tOn: the amount of time the pin is HIGH
|
||||||
|
// - This value should be between 1 and 31. 0 is off.
|
||||||
|
// - tOff: the amount of time the pin is at offIntensity
|
||||||
|
// - This value should be between 1 and 31. 0 is off.
|
||||||
|
// - offIntensity: How dim the LED is during the off period.
|
||||||
|
// - This value should be between 0 and 7. 0 is completely off.
|
||||||
|
// - onIntensity: How bright the LED will be when completely on.
|
||||||
|
// - This value can be between 0 (0%) and 255 (100%).
|
||||||
|
// - tRise: This sets the time the LED takes to fade in.
|
||||||
|
// - This value should be between 1 and 31. 0 is off.
|
||||||
|
// - This value is used with tFall to make the LED breath.
|
||||||
|
// - tFall: This sets the time the LED takes to fade out.
|
||||||
|
// - This value should be between 1 and 31. 0 is off.
|
||||||
|
// Notes:
|
||||||
|
// - The breathable pins are 4, 5, 6, 7, 12, 13, 14, 15 only. If tRise and
|
||||||
|
// tFall are set on 0-3 or 8-11 those pins will still only blink.
|
||||||
|
// - ledDriverInit should be called on the pin to be blinked before this.
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
void blink(byte pin, byte tOn, byte toff, byte offIntensity = 0,
|
||||||
|
byte onIntensity = 255, byte tRise = 0, byte tFall = 0);
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// keypad(byte rows, byte columns, byte sleepTime, byte scanTime): This function
|
||||||
|
// will initialize the keypad function on the SX1509.
|
||||||
|
//
|
||||||
|
// Inputs:
|
||||||
|
// - rows: The number of rows in the button matrix.
|
||||||
|
// - This value must be between 1 and 7. 0 will turn it off.
|
||||||
|
// - eg: 1 = 2 rows, 2 = 3 rows, 7 = 8 rows, etc.
|
||||||
|
// - columns: The number of columns in the button matrix
|
||||||
|
// - This value should be between 0 and 7.
|
||||||
|
// - 0 = 1 column, 7 = 8 columns, etc.
|
||||||
|
// - sleepTime: Sets the auto-sleep time of the keypad engine. 3-bit value:
|
||||||
|
// 0 : OFF
|
||||||
|
// 1 : 128ms x 2MHz/fOSC
|
||||||
|
// 2 : 256ms x 2MHz/fOSC
|
||||||
|
// 3 : 512ms x 2MHz/fOSC
|
||||||
|
// 4 : 1sec x 2MHz/fOSC
|
||||||
|
// 5 : 2sec x 2MHz/fOSC
|
||||||
|
// 6 : 4sec x 2MHz/fOSC
|
||||||
|
// 7 : 8sec x 2MHz/fOSC
|
||||||
|
// - scanTime: Sets the scan time per row. Must be set above debounce
|
||||||
|
// time. 3-bit value:
|
||||||
|
// 0 : 1ms x 2MHz/fOSC
|
||||||
|
// 1 : 2ms x 2MHz/fOSC
|
||||||
|
// 2 : 4ms x 2MHz/fOSC
|
||||||
|
// 3 : 8ms x 2MHz/fOSC
|
||||||
|
// 4 : 16ms x 2MHz/fOSC
|
||||||
|
// 5 : 32ms x 2MHz/fOSC
|
||||||
|
// 6 : 64ms x 2MHz/fOSC
|
||||||
|
// 7 : 128ms x 2MHz/fOSC
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
void keypad(byte rows, byte columns, byte sleepTime = 0, byte scanTime = 0);
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// readKeyData(): This function returns a 16-bit value containing the status of
|
||||||
|
// keypad engine.
|
||||||
|
//
|
||||||
|
// Output:
|
||||||
|
// A 16-bit value is returned. The lower 8 bits represent the up-to 8 rows,
|
||||||
|
// while the MSB represents the up-to 8 columns. Bit-values of 1 indicate a
|
||||||
|
// button in that row or column is being pressed. As such, at least two
|
||||||
|
// bits should be set.
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
unsigned int readKeyData();
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// sync(void): this function resets the PWM/Blink/Fade counters, syncing any
|
||||||
|
// blinking LEDs. Bit 2 of REG_MISC is set, which alters the functionality
|
||||||
|
// of the nReset pin. The nReset pin is toggled low->high, which should
|
||||||
|
// reset all LED counters. Bit 2 of REG_MISC is again cleared, returning
|
||||||
|
// nReset pin to POR functionality
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
void sync(void);
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// debounceConfig(byte configValue): This method configures the debounce time of
|
||||||
|
// every input.
|
||||||
|
//
|
||||||
|
// Input:
|
||||||
|
// - configValue: A 3-bit value configuring the debounce time.
|
||||||
|
// 000: 0.5ms * 2MHz/fOSC
|
||||||
|
// 001: 1ms * 2MHz/fOSC
|
||||||
|
// 010: 2ms * 2MHz/fOSC
|
||||||
|
// 011: 4ms * 2MHz/fOSC
|
||||||
|
// 100: 8ms * 2MHz/fOSC
|
||||||
|
// 101: 16ms * 2MHz/fOSC
|
||||||
|
// 110: 32ms * 2MHz/fOSC
|
||||||
|
// 111: 64ms * 2MHz/fOSC
|
||||||
|
//
|
||||||
|
// Note: fOSC is set with the configClock function. It defaults to 2MHz.
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
void debounceConfig(byte configVaule);
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// debounceEnable(byte pin): This method enables debounce on SX1509 input pin.
|
||||||
|
//
|
||||||
|
// Input:
|
||||||
|
// - pin: The SX1509 pin to be debounced. Should be between 0 and 15.
|
||||||
|
//
|
||||||
|
// Note: debounceConfig() should be called before this, to configure the clock
|
||||||
|
// and other debounce parameters.
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
void debounceEnable(byte pin);
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// enableInterrupt(byte pin, byte riseFall): This function sets up an interrupt
|
||||||
|
// on a pin. Interrupts can occur on all SX1509 pins, and can be generated
|
||||||
|
// on rising, falling, or both.
|
||||||
|
//
|
||||||
|
// Inputs:
|
||||||
|
// -pin: SX1509 input pin that will generate an input. Should be 0-15.
|
||||||
|
// -riseFall: Configures if you want an interrupt generated on rise fall or
|
||||||
|
// both. For this param, send the pin-change values previously defined
|
||||||
|
// by Arduino:
|
||||||
|
// #define CHANGE 1 <-Both
|
||||||
|
// #define FALLING 2 <- Falling
|
||||||
|
// #define RISING 3 <- Rising
|
||||||
|
//
|
||||||
|
// Note: This function does not set up a pin as an input, or configure its
|
||||||
|
// pull-up/down resistors! Do that before (or after).
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
void enableInterrupt(byte pin, byte riseFall);
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// interruptSource(void): Returns an unsigned int representing which pin caused
|
||||||
|
// an interrupt.
|
||||||
|
//
|
||||||
|
// Output: 16-bit value, with a single bit set representing the pin(s) that
|
||||||
|
// generated an interrupt. E.g. a return value of 0x0104 would mean pins 8
|
||||||
|
// and 3 (bits 8 and 3) have generated an interrupt.
|
||||||
|
//
|
||||||
|
// Note: This function also clears all interrupts
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
unsigned int interruptSource(void);
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// configClock(byte oscSource, byte oscPinFunction, byte oscFreqOut,
|
||||||
|
// byte oscDivider): This function configures the oscillator source/speed
|
||||||
|
// and the clock, which is used to drive LEDs and time debounces.
|
||||||
|
//
|
||||||
|
// Inputs:
|
||||||
|
// - oscSource: Choose either internal 2MHz oscillator or an external signal
|
||||||
|
// applied to the OSCIO pin.
|
||||||
|
// - INTERNAL_CLOCK and EXTERNAL_CLOCK are defined in the header file.
|
||||||
|
// Use those.
|
||||||
|
// - This value defaults to internal.
|
||||||
|
// - oscPinFunction: Allows you to set OSCIO as an input or output.
|
||||||
|
// - You can use Arduino's INPUT, OUTPUT defines for this value
|
||||||
|
// - This value defaults to input
|
||||||
|
// - oscFreqOut: If oscio is configured as an output, this will set the output
|
||||||
|
// frequency
|
||||||
|
// - This should be a 4-bit value. 0=0%, 0xF=100%, else
|
||||||
|
// fOSCOut = FOSC / (2^(RegClock[3:0]-1))
|
||||||
|
// - This value defaults to 0.
|
||||||
|
// - oscDivider: Sets the clock divider in REG_MISC.
|
||||||
|
// - ClkX = fOSC / (2^(RegMisc[6:4] -1))
|
||||||
|
// - This value defaults to 1.
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
void configClock(byte oscSource = 2, byte oscPinFunction = 0,
|
||||||
|
byte oscFreqOut = 0, byte oscDivider = 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SX1509_library_H
|
@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
sx1509_registers.h
|
||||||
|
Register definitions for SX1509.
|
||||||
|
|
||||||
|
by: Jim Lindblom
|
||||||
|
SparkFun Electronics
|
||||||
|
date: December 13, 2012
|
||||||
|
|
||||||
|
license: Beerware. Feel free to use it, with or without attribution, in
|
||||||
|
your own projects. If you find it helpful, buy me a beer next time you
|
||||||
|
see me at the local pub.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define REG_INPUT_DISABLE_B 0x00 // RegInputDisableB Input buffer disable register _ I/O[15_8] (Bank B) 0000 0000
|
||||||
|
#define REG_INPUT_DISABLE_A 0x01 // RegInputDisableA Input buffer disable register _ I/O[7_0] (Bank A) 0000 0000
|
||||||
|
#define REG_LONG_SLEW_B 0x02 // RegLongSlewB Output buffer long slew register _ I/O[15_8] (Bank B) 0000 0000
|
||||||
|
#define REG_LONG_SLEW_A 0x03 // RegLongSlewA Output buffer long slew register _ I/O[7_0] (Bank A) 0000 0000
|
||||||
|
#define REG_LOW_DRIVE_B 0x04 // RegLowDriveB Output buffer low drive register _ I/O[15_8] (Bank B) 0000 0000
|
||||||
|
#define REG_LOW_DRIVE_A 0x05 // RegLowDriveA Output buffer low drive register _ I/O[7_0] (Bank A) 0000 0000
|
||||||
|
#define REG_PULL_UP_B 0x06 // RegPullUpB Pull_up register _ I/O[15_8] (Bank B) 0000 0000
|
||||||
|
#define REG_PULL_UP_A 0x07 // RegPullUpA Pull_up register _ I/O[7_0] (Bank A) 0000 0000
|
||||||
|
#define REG_PULL_DOWN_B 0x08 // RegPullDownB Pull_down register _ I/O[15_8] (Bank B) 0000 0000
|
||||||
|
#define REG_PULL_DOWN_A 0x09 // RegPullDownA Pull_down register _ I/O[7_0] (Bank A) 0000 0000
|
||||||
|
#define REG_OPEN_DRAIN_B 0x0A // RegOpenDrainB Open drain register _ I/O[15_8] (Bank B) 0000 0000
|
||||||
|
#define REG_OPEN_DRAIN_A 0x0B // RegOpenDrainA Open drain register _ I/O[7_0] (Bank A) 0000 0000
|
||||||
|
#define REG_POLARITY_B 0x0C // RegPolarityB Polarity register _ I/O[15_8] (Bank B) 0000 0000
|
||||||
|
#define REG_POLARITY_A 0x0D // RegPolarityA Polarity register _ I/O[7_0] (Bank A) 0000 0000
|
||||||
|
#define REG_DIR_B 0x0E // RegDirB Direction register _ I/O[15_8] (Bank B) 1111 1111
|
||||||
|
#define REG_DIR_A 0x0F // RegDirA Direction register _ I/O[7_0] (Bank A) 1111 1111
|
||||||
|
#define REG_DATA_B 0x10 // RegDataB Data register _ I/O[15_8] (Bank B) 1111 1111*
|
||||||
|
#define REG_DATA_A 0x11 // RegDataA Data register _ I/O[7_0] (Bank A) 1111 1111*
|
||||||
|
#define REG_INTERRUPT_MASK_B 0x12 // RegInterruptMaskB Interrupt mask register _ I/O[15_8] (Bank B) 1111 1111
|
||||||
|
#define REG_INTERRUPT_MASK_A 0x13 // RegInterruptMaskA Interrupt mask register _ I/O[7_0] (Bank A) 1111 1111
|
||||||
|
#define REG_SENSE_HIGH_B 0x14 // RegSenseHighB Sense register for I/O[15:12] 0000 0000
|
||||||
|
#define REG_SENSE_LOW_B 0x15 // RegSenseLowB Sense register for I/O[11:8] 0000 0000
|
||||||
|
#define REG_SENSE_HIGH_A 0x16 // RegSenseHighA Sense register for I/O[7:4] 0000 0000
|
||||||
|
#define REG_SENSE_LOW_A 0x17 // RegSenseLowA Sense register for I/O[3:0] 0000 0000
|
||||||
|
#define REG_INTERRUPT_SOURCE_B 0x18 // RegInterruptSourceB Interrupt source register _ I/O[15_8] (Bank B) 0000 0000
|
||||||
|
#define REG_INTERRUPT_SOURCE_A 0x19 // RegInterruptSourceA Interrupt source register _ I/O[7_0] (Bank A) 0000 0000
|
||||||
|
#define REG_EVENT_STATUS_B 0x1A // RegEventStatusB Event status register _ I/O[15_8] (Bank B) 0000 0000
|
||||||
|
#define REG_EVENT_STATUS_A 0x1B // RegEventStatusA Event status register _ I/O[7_0] (Bank A) 0000 0000
|
||||||
|
#define REG_LEVEL_SHIFTER_1 0x1C // RegLevelShifter1 Level shifter register 0000 0000
|
||||||
|
#define REG_LEVEL_SHIFTER_2 0x1D // RegLevelShifter2 Level shifter register 0000 0000
|
||||||
|
#define REG_CLOCK 0x1E // RegClock Clock management register 0000 0000
|
||||||
|
#define REG_MISC 0x1F // RegMisc Miscellaneous device settings register 0000 0000
|
||||||
|
#define REG_LED_DRIVER_ENABLE_B 0x20 // RegLEDDriverEnableB LED driver enable register _ I/O[15_8] (Bank B) 0000 0000
|
||||||
|
#define REG_LED_DRIVER_ENABLE_A 0x21 // RegLEDDriverEnableA LED driver enable register _ I/O[7_0] (Bank A) 0000 0000
|
||||||
|
// Debounce and Keypad Engine
|
||||||
|
#define REG_DEBOUNCE_CONFIG 0x22 // RegDebounceConfig Debounce configuration register 0000 0000
|
||||||
|
#define REG_DEBOUNCE_ENABLE_B 0x23 // RegDebounceEnableB Debounce enable register _ I/O[15_8] (Bank B) 0000 0000
|
||||||
|
#define REG_DEBOUNCE_ENABLE_A 0x24 // RegDebounceEnableA Debounce enable register _ I/O[7_0] (Bank A) 0000 0000
|
||||||
|
#define REG_KEY_CONFIG_1 0x25 // RegKeyConfig1 Key scan configuration register 0000 0000
|
||||||
|
#define REG_KEY_CONFIG_2 0x26 // RegKeyConfig2 Key scan configuration register 0000 0000
|
||||||
|
#define REG_KEY_DATA_1 0x27 // RegKeyData1 Key value (column) 1111 1111
|
||||||
|
#define REG_KEY_DATA_2 0x28 // RegKeyData2 Key value (row) 1111 1111
|
||||||
|
// LED Driver (PWM, blinking, breathing)
|
||||||
|
#define REG_T_ON_0 0x29 // RegTOn0 ON time register for I/O[0] 0000 0000
|
||||||
|
#define REG_I_ON_0 0x2A // RegIOn0 ON intensity register for I/O[0] 1111 1111
|
||||||
|
#define REG_OFF_0 0x2B // RegOff0 OFF time/intensity register for I/O[0] 0000 0000
|
||||||
|
#define REG_T_ON_1 0x2C // RegTOn1 ON time register for I/O[1] 0000 0000
|
||||||
|
#define REG_I_ON_1 0x2D // RegIOn1 ON intensity register for I/O[1] 1111 1111
|
||||||
|
#define REG_OFF_1 0x2E // RegOff1 OFF time/intensity register for I/O[1] 0000 0000
|
||||||
|
#define REG_T_ON_2 0x2F // RegTOn2 ON time register for I/O[2] 0000 0000
|
||||||
|
#define REG_I_ON_2 0x30 // RegIOn2 ON intensity register for I/O[2] 1111 1111
|
||||||
|
#define REG_OFF_2 0x31 // RegOff2 OFF time/intensity register for I/O[2] 0000 0000
|
||||||
|
#define REG_T_ON_3 0x32 // RegTOn3 ON time register for I/O[3] 0000 0000
|
||||||
|
#define REG_I_ON_3 0x33 // RegIOn3 ON intensity register for I/O[3] 1111 1111
|
||||||
|
#define REG_OFF_3 0x34 // RegOff3 OFF time/intensity register for I/O[3] 0000 0000
|
||||||
|
#define REG_T_ON_4 0x35 // RegTOn4 ON time register for I/O[4] 0000 0000
|
||||||
|
#define REG_I_ON_4 0x36 // RegIOn4 ON intensity register for I/O[4] 1111 1111
|
||||||
|
#define REG_OFF_4 0x37 // RegOff4 OFF time/intensity register for I/O[4] 0000 0000
|
||||||
|
#define REG_T_RISE_4 0x38 // RegTRise4 Fade in register for I/O[4] 0000 0000
|
||||||
|
#define REG_T_FALL_4 0x39 // RegTFall4 Fade out register for I/O[4] 0000 0000
|
||||||
|
#define REG_T_ON_5 0x3A // RegTOn5 ON time register for I/O[5] 0000 0000
|
||||||
|
#define REG_I_ON_5 0x3B // RegIOn5 ON intensity register for I/O[5] 1111 1111
|
||||||
|
#define REG_OFF_5 0x3C // RegOff5 OFF time/intensity register for I/O[5] 0000 0000
|
||||||
|
#define REG_T_RISE_5 0x3D // RegTRise5 Fade in register for I/O[5] 0000 0000
|
||||||
|
#define REG_T_FALL_5 0x3E // RegTFall5 Fade out register for I/O[5] 0000 0000
|
||||||
|
#define REG_T_ON_6 0x3F // RegTOn6 ON time register for I/O[6] 0000 0000
|
||||||
|
#define REG_I_ON_6 0x40 // RegIOn6 ON intensity register for I/O[6] 1111 1111
|
||||||
|
#define REG_OFF_6 0x41 // RegOff6 OFF time/intensity register for I/O[6] 0000 0000
|
||||||
|
#define REG_T_RISE_6 0x42 // RegTRise6 Fade in register for I/O[6] 0000 0000
|
||||||
|
#define REG_T_FALL_6 0x43 // RegTFall6 Fade out register for I/O[6] 0000 0000
|
||||||
|
#define REG_T_ON_7 0x44 // RegTOn7 ON time register for I/O[7] 0000 0000
|
||||||
|
#define REG_I_ON_7 0x45 // RegIOn7 ON intensity register for I/O[7] 1111 1111
|
||||||
|
#define REG_OFF_7 0x46 // RegOff7 OFF time/intensity register for I/O[7] 0000 0000
|
||||||
|
#define REG_T_RISE_7 0x47 // RegTRise7 Fade in register for I/O[7] 0000 0000
|
||||||
|
#define REG_T_FALL_7 0x48 // RegTFall7 Fade out register for I/O[7] 0000 0000
|
||||||
|
#define REG_T_ON_8 0x49 // RegTOn8 ON time register for I/O[8] 0000 0000
|
||||||
|
#define REG_I_ON_8 0x4A // RegIOn8 ON intensity register for I/O[8] 1111 1111
|
||||||
|
#define REG_OFF_8 0x4B // RegOff8 OFF time/intensity register for I/O[8] 0000 0000
|
||||||
|
#define REG_T_ON_9 0x4C // RegTOn9 ON time register for I/O[9] 0000 0000
|
||||||
|
#define REG_I_ON_9 0x4D // RegIOn9 ON intensity register for I/O[9] 1111 1111
|
||||||
|
#define REG_OFF_9 0x4E // RegOff9 OFF time/intensity register for I/O[9] 0000 0000
|
||||||
|
#define REG_T_ON_10 0x4F // RegTOn10 ON time register for I/O[10] 0000 0000
|
||||||
|
#define REG_I_ON_10 0x50 // RegIOn10 ON intensity register for I/O[10] 1111 1111
|
||||||
|
#define REG_OFF_10 0x51 // RegOff10 OFF time/intensity register for I/O[10] 0000 0000
|
||||||
|
#define REG_T_ON_11 0x52 // RegTOn11 ON time register for I/O[11] 0000 0000
|
||||||
|
#define REG_I_ON_11 0x53 // RegIOn11 ON intensity register for I/O[11] 1111 1111
|
||||||
|
#define REG_OFF_11 0x54 // RegOff11 OFF time/intensity register for I/O[11] 0000 0000
|
||||||
|
#define REG_T_ON_12 0x55 // RegTOn12 ON time register for I/O[12] 0000 0000
|
||||||
|
#define REG_I_ON_12 0x56 // RegIOn12 ON intensity register for I/O[12] 1111 1111
|
||||||
|
#define REG_OFF_12 0x57 // RegOff12 OFF time/intensity register for I/O[12] 0000 0000
|
||||||
|
#define REG_T_RISE_12 0x58 // RegTRise12 Fade in register for I/O[12] 0000 0000
|
||||||
|
#define REG_T_FALL_12 0x59 // RegTFall12 Fade out register for I/O[12] 0000 0000
|
||||||
|
#define REG_T_ON_13 0x5A // RegTOn13 ON time register for I/O[13] 0000 0000
|
||||||
|
#define REG_I_ON_13 0x5B // RegIOn13 ON intensity register for I/O[13] 1111 1111
|
||||||
|
#define REG_OFF_13 0x5C // RegOff13 OFF time/intensity register for I/O[13] 0000 0000
|
||||||
|
#define REG_T_RISE_13 0x5D // RegTRise13 Fade in register for I/O[13] 0000 0000
|
||||||
|
#define REG_T_FALL_13 0x5E // RegTFall13 Fade out register for I/O[13] 0000 0000
|
||||||
|
#define REG_T_ON_14 0x5F // RegTOn14 ON time register for I/O[14] 0000 0000
|
||||||
|
#define REG_I_ON_14 0x60 // RegIOn14 ON intensity register for I/O[14] 1111 1111
|
||||||
|
#define REG_OFF_14 0x61 // RegOff14 OFF time/intensity register for I/O[14] 0000 0000
|
||||||
|
#define REG_T_RISE_14 0x62 // RegTRise14 Fade in register for I/O[14] 0000 0000
|
||||||
|
#define REG_T_FALL_14 0x63 // RegTFall14 Fade out register for I/O[14] 0000 0000
|
||||||
|
#define REG_T_ON_15 0x64 // RegTOn15 ON time register for I/O[15] 0000 0000
|
||||||
|
#define REG_I_ON_15 0x65 // RegIOn15 ON intensity register for I/O[15] 1111 1111
|
||||||
|
#define REG_OFF_15 0x66 // RegOff15 OFF time/intensity register for I/O[15] 0000 0000
|
||||||
|
#define REG_T_RISE_15 0x67 // RegTRise15 Fade in register for I/O[15] 0000 0000
|
||||||
|
#define REG_T_FALL_15 0x68 // RegTFall15 Fade out register for I/O[15] 0000 0000
|
||||||
|
// Miscellaneous
|
||||||
|
#define REG_HIGH_INPUT_B 0x69 // RegHighInputB High input enable register _ I/O[15_8] (Bank B) 0000 0000
|
||||||
|
#define REG_HIGH_INPUT_A 0x6A // RegHighInputA High input enable register _ I/O[7_0] (Bank A) 0000 0000
|
||||||
|
// Software Reset
|
||||||
|
#define REG_RESET 0x7D // RegReset Software reset register 0000 0000
|
||||||
|
#define REG_TEST_1 0x7E // RegTest1 Test register 0000 0000
|
||||||
|
#define REG_TEST_2 0x7F // RegTest2 Test register 0000 0000
|
||||||
|
|
||||||
|
byte REG_I_ON[16] = {REG_I_ON_0, REG_I_ON_1, REG_I_ON_2, REG_I_ON_3,
|
||||||
|
REG_I_ON_4, REG_I_ON_5, REG_I_ON_6, REG_I_ON_7,
|
||||||
|
REG_I_ON_8, REG_I_ON_9, REG_I_ON_10, REG_I_ON_11,
|
||||||
|
REG_I_ON_12, REG_I_ON_13, REG_I_ON_14, REG_I_ON_15};
|
||||||
|
|
||||||
|
byte REG_T_ON[16] = {REG_T_ON_0, REG_T_ON_1, REG_T_ON_2, REG_T_ON_3,
|
||||||
|
REG_T_ON_4, REG_T_ON_5, REG_T_ON_6, REG_T_ON_7,
|
||||||
|
REG_T_ON_8, REG_T_ON_9, REG_T_ON_10, REG_T_ON_11,
|
||||||
|
REG_T_ON_12, REG_T_ON_13, REG_T_ON_14, REG_T_ON_15};
|
||||||
|
|
||||||
|
byte REG_OFF[16] = {REG_OFF_0, REG_OFF_1, REG_OFF_2, REG_OFF_3,
|
||||||
|
REG_OFF_4, REG_OFF_5, REG_OFF_6, REG_OFF_7,
|
||||||
|
REG_OFF_8, REG_OFF_9, REG_OFF_10, REG_OFF_11,
|
||||||
|
REG_OFF_12, REG_OFF_13, REG_OFF_14, REG_OFF_15};
|
||||||
|
|
||||||
|
byte REG_T_RISE[16] = {0xFF, 0xFF, 0xFF, 0xFF,
|
||||||
|
REG_T_RISE_4, REG_T_RISE_5, REG_T_RISE_6, REG_T_RISE_7,
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF,
|
||||||
|
REG_T_RISE_12, REG_T_RISE_13, REG_T_RISE_14, REG_T_RISE_15};
|
||||||
|
|
||||||
|
byte REG_T_FALL[16] = {0xFF, 0xFF, 0xFF, 0xFF,
|
||||||
|
REG_T_FALL_4, REG_T_FALL_5, REG_T_FALL_6, REG_T_FALL_7,
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF,
|
||||||
|
REG_T_FALL_12, REG_T_FALL_13, REG_T_FALL_14, REG_T_FALL_15};
|
Loading…
Reference in new issue