499 lines
14 KiB
C
499 lines
14 KiB
C
/* ----------------------------------------------------------------------------
|
|
* ergoDOX : controller specific code
|
|
* ----------------------------------------------------------------------------
|
|
* Copyright (c) 2012 Ben Blazak <benblazak.dev@gmail.com>
|
|
* Released under The MIT License (MIT) (see "license.md")
|
|
* Project located at <https://github.com/benblazak/ergodox-firmware>
|
|
* ------------------------------------------------------------------------- */
|
|
|
|
#include "./controller.h"
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
/* returns
|
|
* - success: 0
|
|
* - error: number of the function that failed
|
|
*/
|
|
uint8_t kb_init(void) {
|
|
if (teensy_init()) // must be first
|
|
return 1;
|
|
if (mcp23018_init()) // must be second
|
|
return 2;
|
|
|
|
return 0; // success
|
|
}
|
|
|
|
/* returns
|
|
* - success: 0
|
|
* - error: number of the function that failed
|
|
*/
|
|
uint8_t kb_update_matrix(bool matrix[KB_ROWS][KB_COLUMNS]) {
|
|
if (teensy_update_matrix(matrix))
|
|
return 1;
|
|
if (mcp23018_update_matrix(matrix))
|
|
return 2;
|
|
|
|
return 0; // success
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// check options
|
|
#if (MCP23018__DRIVE_ROWS && MCP23018__DRIVE_COLUMNS) \
|
|
|| !(MCP23018__DRIVE_ROWS || MCP23018__DRIVE_COLUMNS)
|
|
#error "See 'Pin drive direction' in 'options.h'"
|
|
#endif
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// register addresses (see "mcp23018.md")
|
|
#define IODIRA 0x00 // i/o direction register
|
|
#define IODIRB 0x01
|
|
#define GPPUA 0x0C // GPIO pull-up resistor register
|
|
#define GPPUB 0x0D
|
|
#define GPIOA 0x12 // general purpose i/o port register (write modifies OLAT)
|
|
#define GPIOB 0x13
|
|
#define OLATA 0x14 // output latch register
|
|
#define OLATB 0x15
|
|
|
|
// TWI aliases
|
|
#define TWI_ADDR_WRITE ( (MCP23018_TWI_ADDRESS<<1) | TW_WRITE )
|
|
#define TWI_ADDR_READ ( (MCP23018_TWI_ADDRESS<<1) | TW_READ )
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
/* returns:
|
|
* - success: 0
|
|
* - failure: twi status code
|
|
*
|
|
* notes:
|
|
* - `twi_stop()` must be called *exactly once* for each twi block, the way
|
|
* things are currently set up. this may change in the future.
|
|
*/
|
|
uint8_t mcp23018_init(void) {
|
|
uint8_t ret;
|
|
|
|
// set pin direction
|
|
// - unused : input : 1
|
|
// - input : input : 1
|
|
// - driving : output : 0
|
|
twi_start();
|
|
ret = twi_send(TWI_ADDR_WRITE);
|
|
if (ret) goto out; // make sure we got an ACK
|
|
twi_send(IODIRA);
|
|
#if MCP23018__DRIVE_ROWS
|
|
twi_send(0b11111111); // IODIRA
|
|
twi_send(0b11000000); // IODIRB
|
|
#elif MCP23018__DRIVE_COLUMNS
|
|
twi_send(0b10000000); // IODIRA
|
|
twi_send(0b11111111); // IODIRB
|
|
#endif
|
|
twi_stop();
|
|
|
|
// set pull-up
|
|
// - unused : on : 1
|
|
// - input : on : 1
|
|
// - driving : off : 0
|
|
twi_start();
|
|
ret = twi_send(TWI_ADDR_WRITE);
|
|
if (ret) goto out; // make sure we got an ACK
|
|
twi_send(GPPUA);
|
|
#if MCP23018__DRIVE_ROWS
|
|
twi_send(0b11111111); // GPPUA
|
|
twi_send(0b11000000); // GPPUB
|
|
#elif MCP23018__DRIVE_COLUMNS
|
|
twi_send(0b10000000); // GPPUA
|
|
twi_send(0b11111111); // GPPUB
|
|
#endif
|
|
twi_stop();
|
|
|
|
// set logical value (doesn't matter on inputs)
|
|
// - unused : hi-Z : 1
|
|
// - input : hi-Z : 1
|
|
// - driving : hi-Z : 1
|
|
twi_start();
|
|
ret = twi_send(TWI_ADDR_WRITE);
|
|
if (ret) goto out; // make sure we got an ACK
|
|
twi_send(OLATA);
|
|
twi_send(0b11111111); //OLATA
|
|
twi_send(0b11111111); //OLATB
|
|
|
|
out:
|
|
twi_stop();
|
|
return ret;
|
|
}
|
|
|
|
/* returns:
|
|
* - success: 0
|
|
* - failure: twi status code
|
|
*/
|
|
#if KB_ROWS != 6 || KB_COLUMNS != 14
|
|
#error "Expecting different keyboard dimensions"
|
|
#endif
|
|
|
|
uint8_t mcp23018_update_matrix(bool matrix[KB_ROWS][KB_COLUMNS]) {
|
|
uint8_t ret, data;
|
|
|
|
// initialize things, just to make sure
|
|
// - it's not appreciably faster to skip this, and it takes care of the
|
|
// case when the i/o expander isn't plugged in during the first
|
|
// init()
|
|
ret = mcp23018_init();
|
|
|
|
// if there was an error
|
|
if (ret) {
|
|
// clear our part of the matrix
|
|
for (uint8_t row=0; row<=5; row++)
|
|
for (uint8_t col=0; col<=6; col++)
|
|
matrix[row][col] = 0;
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
// --------------------------------------------------------------------
|
|
// update our part of the matrix
|
|
|
|
#if MCP23018__DRIVE_ROWS
|
|
for (uint8_t row=0; row<=5; row++) {
|
|
// set active row low : 0
|
|
// set other rows hi-Z : 1
|
|
twi_start();
|
|
twi_send(TWI_ADDR_WRITE);
|
|
twi_send(GPIOB);
|
|
twi_send( 0xFF & ~(1<<(5-row)) );
|
|
twi_stop();
|
|
|
|
// read column data
|
|
twi_start();
|
|
twi_send(TWI_ADDR_WRITE);
|
|
twi_send(GPIOA);
|
|
twi_start();
|
|
twi_send(TWI_ADDR_READ);
|
|
twi_read(&data);
|
|
twi_stop();
|
|
|
|
// update matrix
|
|
for (uint8_t col=0; col<=6; col++) {
|
|
matrix[row][col] = !( data & (1<<col) );
|
|
}
|
|
}
|
|
|
|
// set all rows hi-Z : 1
|
|
twi_start();
|
|
twi_send(TWI_ADDR_WRITE);
|
|
twi_send(GPIOB);
|
|
twi_send(0xFF);
|
|
twi_stop();
|
|
|
|
#elif MCP23018__DRIVE_COLUMNS
|
|
for (uint8_t col=0; col<=6; col++) {
|
|
// set active column low : 0
|
|
// set other columns hi-Z : 1
|
|
twi_start();
|
|
twi_send(TWI_ADDR_WRITE);
|
|
twi_send(GPIOA);
|
|
twi_send( 0xFF & ~(1<<col) );
|
|
twi_stop();
|
|
|
|
// read row data
|
|
twi_start();
|
|
twi_send(TWI_ADDR_WRITE);
|
|
twi_send(GPIOB);
|
|
twi_start();
|
|
twi_send(TWI_ADDR_READ);
|
|
twi_read(&data);
|
|
twi_stop();
|
|
|
|
// update matrix
|
|
for (uint8_t row=0; row<=5; row++) {
|
|
matrix[row][col] = !( data & (1<<(5-row)) );
|
|
}
|
|
}
|
|
|
|
// set all columns hi-Z : 1
|
|
twi_start();
|
|
twi_send(TWI_ADDR_WRITE);
|
|
twi_send(GPIOA);
|
|
twi_send(0xFF);
|
|
twi_stop();
|
|
|
|
#endif
|
|
|
|
// /update our part of the matrix
|
|
// --------------------------------------------------------------------
|
|
|
|
return ret; // success
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// check options
|
|
#if (TEENSY__DRIVE_ROWS && TEENSY__DRIVE_COLUMNS) \
|
|
|| !(TEENSY__DRIVE_ROWS || TEENSY__DRIVE_COLUMNS)
|
|
#error "See 'Pin drive direction' in 'options.h'"
|
|
#endif
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// processor frequency (from <http://www.pjrc.com/teensy/prescaler.html>)
|
|
#define CPU_PRESCALE(n) (CLKPR = 0x80, CLKPR = (n))
|
|
#define CPU_16MHz 0x00
|
|
#define CPU_8MHz 0x01
|
|
#define CPU_4MHz 0x02
|
|
#define CPU_2MHz 0x03
|
|
#define CPU_1MHz 0x04
|
|
#define CPU_500kHz 0x05
|
|
#define CPU_250kHz 0x06
|
|
#define CPU_125kHz 0x07
|
|
#define CPU_62kHz 0x08
|
|
|
|
|
|
/*
|
|
* pin macros
|
|
* - note: you can move the `UNUSED`, `ROW`, and `COLUMN` pins around, but be
|
|
* sure to keep the set of all the pins listed constant. other pins are not
|
|
* movable, and either are referenced explicitly or have macros defined for
|
|
* them elsewhere.
|
|
* - note: if you change pin assignments, please be sure to update
|
|
* "teensy-2-0.md", and the '.svg' circuit diagram.
|
|
*/
|
|
|
|
// --- unused
|
|
#define UNUSED_0 C, 7
|
|
#define UNUSED_1 D, 7
|
|
#define UNUSED_2 D, 4 // hard to use with breadboard (on the end)
|
|
#define UNUSED_3 D, 5 // hard to use with breadboard (on the end)
|
|
#define UNUSED_4 E, 6 // hard to use with breadboard (internal)
|
|
|
|
// --- rows
|
|
#define ROW_0 F, 7
|
|
#define ROW_1 F, 6
|
|
#define ROW_2 F, 5
|
|
#define ROW_3 F, 4
|
|
#define ROW_4 F, 1
|
|
#define ROW_5 F, 0
|
|
|
|
// --- columns
|
|
#define COLUMN_7 B, 0
|
|
#define COLUMN_8 B, 1
|
|
#define COLUMN_9 B, 2
|
|
#define COLUMN_A B, 3
|
|
#define COLUMN_B D, 2
|
|
#define COLUMN_C D, 3
|
|
#define COLUMN_D C, 6
|
|
|
|
// --- helpers
|
|
#define SET |=
|
|
#define CLEAR &=~
|
|
|
|
#define _teensypin_write(register, operation, pin_letter, pin_number) \
|
|
do { \
|
|
((register##pin_letter) operation (1<<(pin_number))); \
|
|
_delay_us(1); /* allow pins time to stabilize */ \
|
|
} while(0)
|
|
#define teensypin_write(register, operation, pin) \
|
|
_teensypin_write(register, operation, pin)
|
|
|
|
#define _teensypin_read(pin_letter, pin_number) \
|
|
((PIN##pin_letter) & (1<<(pin_number)))
|
|
#define teensypin_read(pin) \
|
|
_teensypin_read(pin)
|
|
|
|
#define teensypin_write_all_unused(register, operation) \
|
|
do { \
|
|
teensypin_write(register, operation, UNUSED_0); \
|
|
teensypin_write(register, operation, UNUSED_1); \
|
|
teensypin_write(register, operation, UNUSED_2); \
|
|
teensypin_write(register, operation, UNUSED_3); \
|
|
teensypin_write(register, operation, UNUSED_4); } \
|
|
while(0)
|
|
|
|
#define teensypin_write_all_row(register, operation) \
|
|
do { \
|
|
teensypin_write(register, operation, ROW_0); \
|
|
teensypin_write(register, operation, ROW_1); \
|
|
teensypin_write(register, operation, ROW_2); \
|
|
teensypin_write(register, operation, ROW_3); \
|
|
teensypin_write(register, operation, ROW_4); \
|
|
teensypin_write(register, operation, ROW_5); } \
|
|
while(0)
|
|
|
|
#define teensypin_write_all_column(register, operation) \
|
|
do { \
|
|
teensypin_write(register, operation, COLUMN_7); \
|
|
teensypin_write(register, operation, COLUMN_8); \
|
|
teensypin_write(register, operation, COLUMN_9); \
|
|
teensypin_write(register, operation, COLUMN_A); \
|
|
teensypin_write(register, operation, COLUMN_B); \
|
|
teensypin_write(register, operation, COLUMN_C); \
|
|
teensypin_write(register, operation, COLUMN_D); } \
|
|
while(0)
|
|
|
|
|
|
/*
|
|
* update macros
|
|
*/
|
|
#define update_rows_for_column(matrix, column) \
|
|
do { \
|
|
/* set column low (set as output) */ \
|
|
teensypin_write(DDR, SET, COLUMN_##column); \
|
|
/* read rows 0..5 and update matrix */ \
|
|
matrix[0x0][0x##column] = ! teensypin_read(ROW_0); \
|
|
matrix[0x1][0x##column] = ! teensypin_read(ROW_1); \
|
|
matrix[0x2][0x##column] = ! teensypin_read(ROW_2); \
|
|
matrix[0x3][0x##column] = ! teensypin_read(ROW_3); \
|
|
matrix[0x4][0x##column] = ! teensypin_read(ROW_4); \
|
|
matrix[0x5][0x##column] = ! teensypin_read(ROW_5); \
|
|
/* set column hi-Z (set as input) */ \
|
|
teensypin_write(DDR, CLEAR, COLUMN_##column); \
|
|
} while(0)
|
|
|
|
#define update_columns_for_row(matrix, row) \
|
|
do { \
|
|
/* set row low (set as output) */ \
|
|
teensypin_write(DDR, SET, ROW_##row); \
|
|
/* read columns 7..D and update matrix */ \
|
|
matrix[0x##row][0x7] = ! teensypin_read(COLUMN_7); \
|
|
matrix[0x##row][0x8] = ! teensypin_read(COLUMN_8); \
|
|
matrix[0x##row][0x9] = ! teensypin_read(COLUMN_9); \
|
|
matrix[0x##row][0xA] = ! teensypin_read(COLUMN_A); \
|
|
matrix[0x##row][0xB] = ! teensypin_read(COLUMN_B); \
|
|
matrix[0x##row][0xC] = ! teensypin_read(COLUMN_C); \
|
|
matrix[0x##row][0xD] = ! teensypin_read(COLUMN_D); \
|
|
/* set row hi-Z (set as input) */ \
|
|
teensypin_write(DDR, CLEAR, ROW_##row); \
|
|
} while(0)
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
/* returns
|
|
* - success: 0
|
|
*/
|
|
uint8_t teensy_init(void) {
|
|
// CPU speed : should match F_CPU in makefile
|
|
#if F_CPU != 16000000
|
|
#error "Expecting different CPU frequency"
|
|
#endif
|
|
CPU_PRESCALE(CPU_16MHz);
|
|
|
|
// onboard LED
|
|
// (tied to GND for hardware convenience)
|
|
DDRD &= ~(1<<6); // set D(6) as input
|
|
PORTD &= ~(1<<6); // set D(6) internal pull-up disabled
|
|
|
|
// (tied to Vcc for hardware convenience)
|
|
DDRB &= ~(1<<4); // set B(4) as input
|
|
PORTB &= ~(1<<4); // set B(4) internal pull-up disabled
|
|
|
|
// keyboard LEDs (see "PWM on ports OC1(A|B|C)" in "teensy-2-0.md")
|
|
_kb_led_all_off(); // (just to put the pins in a known state)
|
|
TCCR1A = 0b10101001; // set and configure fast PWM
|
|
TCCR1B = 0b00001001; // set and configure fast PWM
|
|
|
|
// I2C (TWI)
|
|
twi_init(); // on pins D(1,0)
|
|
|
|
// unused pins
|
|
teensypin_write_all_unused(DDR, CLEAR); // set as input
|
|
teensypin_write_all_unused(PORT, SET); // set internal pull-up enabled
|
|
|
|
// rows and columns
|
|
teensypin_write_all_row(DDR, CLEAR); // set as input (hi-Z)
|
|
teensypin_write_all_column(DDR, CLEAR); // set as input (hi-Z)
|
|
#if TEENSY__DRIVE_ROWS
|
|
teensypin_write_all_row(PORT, CLEAR); // pull-up disabled
|
|
teensypin_write_all_column(PORT, SET); // pull-up enabled
|
|
#elif TEENSY__DRIVE_COLUMNS
|
|
teensypin_write_all_row(PORT, SET); // pull-up enabled
|
|
teensypin_write_all_column(PORT, CLEAR); // pull-up disabled
|
|
#endif
|
|
|
|
return 0; // success
|
|
}
|
|
|
|
/* returns
|
|
* - success: 0
|
|
*/
|
|
#if KB_ROWS != 6 || KB_COLUMNS != 14
|
|
#error "Expecting different keyboard dimensions"
|
|
#endif
|
|
|
|
uint8_t teensy_update_matrix(bool matrix[KB_ROWS][KB_COLUMNS]) {
|
|
#if TEENSY__DRIVE_ROWS
|
|
update_columns_for_row(matrix, 0);
|
|
update_columns_for_row(matrix, 1);
|
|
update_columns_for_row(matrix, 2);
|
|
update_columns_for_row(matrix, 3);
|
|
update_columns_for_row(matrix, 4);
|
|
update_columns_for_row(matrix, 5);
|
|
#elif TEENSY__DRIVE_COLUMNS
|
|
update_rows_for_column(matrix, 7);
|
|
update_rows_for_column(matrix, 8);
|
|
update_rows_for_column(matrix, 9);
|
|
update_rows_for_column(matrix, A);
|
|
update_rows_for_column(matrix, B);
|
|
update_rows_for_column(matrix, C);
|
|
update_rows_for_column(matrix, D);
|
|
#endif
|
|
|
|
return 0; // success
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void twi_init(void) {
|
|
// set the prescaler value to 0
|
|
TWSR &= ~( (1<<TWPS1)|(1<<TWPS0) );
|
|
// set the bit rate
|
|
// - TWBR should be 10 or higher (datasheet section 20.5.2)
|
|
// - TWI_FREQ should be 400000 (400kHz) max (datasheet section 20.1)
|
|
TWBR = ((F_CPU / TWI_FREQ) - 16) / 2;
|
|
}
|
|
|
|
uint8_t twi_start(void) {
|
|
// send start
|
|
TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTA);
|
|
// wait for transmission to complete
|
|
while (!(TWCR & (1<<TWINT)));
|
|
// if it didn't work, return the status code (else return 0)
|
|
if ( (TW_STATUS != TW_START) &&
|
|
(TW_STATUS != TW_REP_START) )
|
|
return TW_STATUS; // error
|
|
return 0; // success
|
|
}
|
|
|
|
void twi_stop(void) {
|
|
// send stop
|
|
TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTO);
|
|
// wait for transmission to complete
|
|
while (TWCR & (1<<TWSTO));
|
|
}
|
|
|
|
uint8_t twi_send(uint8_t data) {
|
|
// load data into the data register
|
|
TWDR = data;
|
|
// send data
|
|
TWCR = (1<<TWINT)|(1<<TWEN);
|
|
// wait for transmission to complete
|
|
while (!(TWCR & (1<<TWINT)));
|
|
// if it didn't work, return the status code (else return 0)
|
|
if ( (TW_STATUS != TW_MT_SLA_ACK) &&
|
|
(TW_STATUS != TW_MT_DATA_ACK) &&
|
|
(TW_STATUS != TW_MR_SLA_ACK) )
|
|
return TW_STATUS; // error
|
|
return 0; // success
|
|
}
|
|
|
|
uint8_t twi_read(uint8_t * data) {
|
|
// read 1 byte to TWDR, send ACK
|
|
TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWEA);
|
|
// wait for transmission to complete
|
|
while (!(TWCR & (1<<TWINT)));
|
|
// set data variable
|
|
*data = TWDR;
|
|
// if it didn't work, return the status code (else return 0)
|
|
if (TW_STATUS != TW_MR_DATA_ACK)
|
|
return TW_STATUS; // error
|
|
return 0; // success
|
|
}
|