Browse Source

halfhalf

Half and Half split keyboard
master
di0ib 2 years ago
parent
commit
4dab703eb2

+ 171
- 0
keyboard/halfhalf/Makefile View File

@@ -0,0 +1,171 @@
#----------------------------------------------------------------------------
# On command line:
#
# make all = Make software.
#
# make clean = Clean out built project files.
#
# make coff = Convert ELF to AVR COFF.
#
# make extcoff = Convert ELF to AVR Extended COFF.
#
# make program = Download the hex file to the device.
# Please customize your programmer settings(PROGRAM_CMD)
#
# make teensy = Download the hex file to the device, using teensy_loader_cli.
# (must have teensy_loader_cli installed).
#
# make dfu = Download the hex file to the device, using dfu-programmer (must
# have dfu-programmer installed).
#
# make flip = Download the hex file to the device, using Atmel FLIP (must
# have Atmel FLIP installed).
#
# make dfu-ee = Download the eeprom file to the device, using dfu-programmer
# (must have dfu-programmer installed).
#
# make flip-ee = Download the eeprom file to the device, using Atmel FLIP
# (must have Atmel FLIP installed).
#
# make debug = Start either simulavr or avarice as specified for debugging,
# with avr-gdb or avr-insight as the front end for debugging.
#
# make filename.s = Just compile filename.c into the assembler code only.
#
# make filename.i = Create a preprocessed source file for use in submitting
# bug reports to the GCC project.
#
# To rebuild project do "make clean" then "make all".
#----------------------------------------------------------------------------

# Target file name (without extension).
TARGET = halfhalf

# Directory common source filess exist
TMK_DIR = ../../tmk_core

# Directory keyboard dependent files exist
TARGET_DIR = .

# project specific files
SRC = matrix.c \
i2c.c \
serial.c \
split-util.c \
led.c

CONFIG_H = config.h

# MCU name
MCU = atmega32u4

COM_PORT=/dev/ttyACM0
PROGRAM_CMD=sleep 3; avrdude -p atmega32u4 -P $(COM_PORT) -c avr109 -U flash:w:$(TARGET).hex

# Processor frequency.
# This will define a symbol, F_CPU, in all source code files equal to the
# processor frequency in Hz. You can then use this symbol in your source code to
# calculate timings. Do NOT tack on a 'UL' at the end, this will be done
# automatically to create a 32-bit value in your source code.
#
# This will be an integer division of F_USB below, as it is sourced by
# F_USB after it has run through any CPU prescalers. Note that this value
# does not *change* the processor frequency - it should merely be updated to
# reflect the processor speed set externally so that the code can use accurate
# software delays.
F_CPU = 16000000


#
# LUFA specific
#
# Target architecture (see library "Board Types" documentation).
ARCH = AVR8

# Input clock frequency.
# This will define a symbol, F_USB, in all source code files equal to the
# input clock frequency (before any prescaling is performed) in Hz. This value may
# differ from F_CPU if prescaling is used on the latter, and is required as the
# raw input clock is fed directly to the PLL sections of the AVR for high speed
# clock generation for the USB and other AVR subsections. Do NOT tack on a 'UL'
# at the end, this will be done automatically to create a 32-bit value in your
# source code.
#
# If no clock division is performed on the input clock inside the AVR (via the
# CPU clock adjust registers or the clock division fuses), this will be equal to F_CPU.
F_USB = $(F_CPU)

# Interrupt driven control endpoint task(+60)
OPT_DEFS += -DINTERRUPT_CONTROL_ENDPOINT


# Boot Section Size in *bytes*
# Teensy halfKay 512
# Teensy++ halfKay 1024
# Atmel DFU loader 4096
# LUFA bootloader 4096
# USBaspLoader 2048
OPT_DEFS += -DBOOTLOADER_SIZE=4096

# Changes some bootmagic settings for when the space is on the left half
OPT_DEFS += -DSPACE_ON_LEFT_HALF

# # Use I2C for communication between the halves of the keyboard
# OPT_DEFS += -DUSE_I2C

# Build Options
# comment out to disable the options.
#
BOOTMAGIC_ENABLE = yes # Virtual DIP switch configuration(+1000)
MOUSEKEY_ENABLE = yes # Mouse keys(+4700)
EXTRAKEY_ENABLE = yes # Audio control and System control(+450)
CONSOLE_ENABLE = yes # Console for debug(+400)
COMMAND_ENABLE = yes # Commands for debug and configuration
SLEEP_LED_ENABLE = yes # Breathing sleep LED during USB suspend
NKRO_ENABLE = yes # USB Nkey Rollover
ACTIONMAP_ENABLE = yes # Use 16bit action codes in keymap instead of 8bit keycodes


ifdef ACTIONMAP_ENABLE
KEYMAP_FILE = actionmap
else
KEYMAP_FILE = keymap
SRC := keymap_common.c $(SRC)
endif
ifdef KEYMAP
SRC := $(KEYMAP_FILE)_$(KEYMAP).c $(SRC)
else
SRC := $(KEYMAP_FILE)_plain.c $(SRC)
endif


#PS2_MOUSE_ENABLE = yes # PS/2 mouse(TrackPoint) support
#PS2_USE_BUSYWAIT = yes # uses primitive reference code
#PS2_USE_INT = yes # uses external interrupt for falling edge of PS/2 clock pin
#PS2_USE_USART = yes # uses hardware USART engine for PS/2 signal receive(recomened)


# Search Path
VPATH += $(TARGET_DIR)
VPATH += $(TMK_DIR)

# Un comment this line if you want to use pjrc protocol
# include $(TMK_DIR)/protocol/pjrc.mk

include $(TMK_DIR)/protocol/lufa.mk
include $(TMK_DIR)/common.mk
include $(TMK_DIR)/rules.mk


debug-on: EXTRAFLAGS += -DDEBUG -DDEBUG_ACTION
debug-on: all

debug-off: EXTRAFLAGS += -DNO_DEBUG -DNO_PRINT
debug-off: OPT_DEFS := $(filter-out -DCONSOLE_ENABLE,$(OPT_DEFS))
debug-off: all

eeprom-left:
sleep 3; avrdude -p atmega32u4 -P $(COM_PORT) -c avr109 -U eeprom:w:eeprom-lefthand.eep

eeprom-right:
sleep 3; avrdude -p atmega32u4 -P $(COM_PORT) -c avr109 -U eeprom:w:eeprom-righthand.eep

+ 104
- 0
keyboard/halfhalf/README.md View File

@@ -0,0 +1,104 @@
This is a modification of the TMK firmware by ahtn found here https://github.com/ahtn/tmk_keyboard/tree/master/keyboard/split_keyboard

COL_PINS { F4, F5, F6, F7, B1, B3, B2 }

ROW_PINS { D4, C6, D7, E6 }


Custom split keyboard firmware
======

Split keyboard firmware for Arduino Pro Micro or other ATmega32u4
based boards.


Features
--------

Some features supported by the firmware:

* Either half can connect to the computer via USB, or both halves can be used
independently.
* You only need 3 wires to connect the two halves. Two for VCC and GND and one
for serial communication.
* Optional support for I2C connection between the two halves if for some
reason you require a faster connection between the two halves. Note this
requires an extra wire between halves and pull-up resistors on the data lines.

Required Hardware
-----------------

Apart from diodes and key switches for the keyboard matrix in each half, you
will need:

* 2 Arduino Pro Micro's. You can find theses on aliexpress for ≈3.50USD each.
* 2 TRS sockets
* 1 TRS cable.

Alternatively, you can use any sort of cable and socket that has at least 3
wires. If you want to use I2C to communicate between halves, you will need a
cable with at least 4 wires and 2x 4.7kΩ pull-up resistors


Wiring
------

The 3 wires of the TRS cable need to connect GND, VCC, and digital pin 3 (i.e.
`PD0` on the ATmega32u4) between the two Pro Micros.

Then wire your key matrix to any of the remaining 17 IO pins of the pro micro
and modify the `MATRIX_COL_PINS` and `MATRIX_ROW_PINS` in `config.h` accordingly.

The wiring for serial:

![serial wiring](imgs/split-keyboard-serial-schematic.png)

The wiring for i2c:

![i2c wiring](imgs/split-keyboard-i2c-schematic.png)

The pull-up resistors may be placed on either half. It is also possible
to use 4 resistors and have the pull-ups in both halves, but this is
unnecessary in simple use cases.

Notes on Software Configuration
-------------------------------

Configuring the firmware is similar to any other TMK project. One thing
to note is that `MATIX_ROWS` in `config.h` is the total number of rows between
the two halves, i.e. if your split keyboard has 4 rows in each half, then
`MATRIX_ROWS=8`.

Also the current implementation assumes a maximum of 8 columns, but it would
not be very difficult to adapt it to support more if required.


Flashing
--------

Before you go to flash the program memory for the first time, you will need to
EEPROM for the left and right halves. The EEPROM is used to store whether the
half is left handed or right handed. This makes it so that the same firmware
file will run on both hands instead of having to flash left and right handed
versions of the firmware to each half. To flash the EEPROM file for the left
half run:
```
make eeprom-left
```
and similarly for right half
```
make eeprom-right
```

After you have flashed the EEPROM for the first time, you then need to program
the flash memory:
```
make program
```
Note that you need to program both halves, but you have the option of using
different keymaps for each half. You could program the left half with a QWERTY
layout and the right half with a Colemak layout. Then if you connect the left
half to a computer by USB the keyboard will use QWERTY and Colemak when the
right half is connected.



+ 25
- 0
keyboard/halfhalf/actionmap_common.h View File

@@ -0,0 +1,25 @@
#ifndef ACTIONMAP_COMMON_H
#define ACTIONMAP_COMMON_H

#define ACTIONMAP( \
K00, K01, K02, K03, K04, K05, K06, \
K10, K11, K12, K13, K14, K15, K16, \
K20, K21, K22, K23, K24, K25, K26, \
K34, \
\
K50, K51, K52, K53, K54, K55, K56, \
K60, K61, K62, K63, K64, K65, K66, \
K70, K71, K72, K73, K74, K75, K76, \
K82 \
) { \
{ AC_##K00, AC_##K01, AC_##K02, AC_##K03, AC_##K04, AC_##K05, AC_##K06 }, \
{ AC_##K10, AC_##K11, AC_##K12, AC_##K13, AC_##K14, AC_##K15, AC_##K16 }, \
{ AC_##K20, AC_##K21, AC_##K22, AC_##K23, AC_##K24, AC_##K25, AC_##K26 }, \
{ KC_NO, KC_NO, KC_NO, KC_NO, AC_##K34, KC_NO, KC_NO }, \
\
{ AC_##K56, AC_##K55, AC_##K54, AC_##K53, AC_##K52, AC_##K51, AC_##K50 }, \
{ AC_##K66, AC_##K65, AC_##K64, AC_##K63, AC_##K62, AC_##K61, AC_##K60 }, \
{ AC_##K76, AC_##K75, AC_##K74, AC_##K73, AC_##K72, AC_##K71, AC_##K70 }, \
{ KC_NO, KC_NO, KC_NO, KC_NO, AC_##K82, KC_NO, KC_NO } \
}
#endif

+ 23
- 0
keyboard/halfhalf/actionmap_plain.c View File

@@ -0,0 +1,23 @@
#include "actionmap.h"
#include "action_code.h"
#include "actionmap_common.h"


/*
* Actions
*/

const action_t PROGMEM actionmaps[][MATRIX_ROWS][MATRIX_COLS] = {
[0] = ACTIONMAP(
ESC, Q, W, E, R, T, LCTL,
TAB, A, S, D, F, G, LALT,
LSFT, Z, X, C, V, B, LGUI,
SPC,

RCTL, Y, U, I, O, P, BSPC,
RALT, H, J, K, L, SCLN, QUOT,
APP, N, M, COMM, DOT, SLSH, ENT,
SPC),
};


+ 113
- 0
keyboard/halfhalf/config.h View File

@@ -0,0 +1,113 @@
/*
Copyright 2012 Jun Wako <wakojun@gmail.com>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef CONFIG_H
#define CONFIG_H


/* USB Device descriptor parameter */

#define VENDOR_ID 0xFEED
#define PRODUCT_ID 0x0A0C
#define DEVICE_VER 0x4A1F
#define MANUFACTURER di0ib
#define PRODUCT The Half and Half Keyboard
#define DESCRIPTION A split keyboard

/* key matrix size */
#define ROWS_PER_HAND 4
#define MATRIX_COLS 7

#define MATRIX_ROWS ROWS_PER_HAND*2

#define MATRIX_COL_PINS { F4, F5, F6, F7, B1, B3, B2 }
#define MATRIX_ROW_PINS { D4, C6, D7, E6 }


/* use i2c instead of serial */
//#define USE_I2C

//#define I2C_WRITE_TEST_CODE

/* define if matrix has ghost */
//#define MATRIX_HAS_GHOST

/* Set 0 if debouncing isn't needed */
#define DEBOUNCE 5

/* Mechanical locking support. Use KC_LCAP, KC_LNUM or KC_LSCR instead in keymap */
#define LOCKING_SUPPORT_ENABLE
/* Locking resynchronize hack */
#define LOCKING_RESYNC_ENABLE

/*
* Feature disable options
* These options are also useful to firmware size reduction.
*/

/* disable debug print */
//#define NO_DEBUG

/* disable print */
//#define NO_PRINT

/* disable action features */
//#define NO_ACTION_LAYER
//#define NO_ACTION_TAPPING
//#define NO_ACTION_ONESHOT
//#define NO_ACTION_MACRO
//#define NO_ACTION_FUNCTION

/* key combination for command */
#define IS_COMMAND() ( \
keyboard_report->mods == (MOD_BIT(KC_LCTL) | MOD_BIT(KC_LALT) | MOD_BIT(KC_LGUI)) \
)

/* boot magic key */
#define BOOTMAGIC_KEY_SALT KC_Q

#ifdef SPACE_ON_LEFT_HALF
#define BOOTMAGIC_KEY_DEFAULT_LAYER_0 KC_Z
#define BOOTMAGIC_KEY_DEFAULT_LAYER_1 KC_X
#define BOOTMAGIC_KEY_DEFAULT_LAYER_2 KC_C
#define BOOTMAGIC_HOST_NKRO KC_V
#else
#define BOOTMAGIC_KEY_DEFAULT_LAYER_0 KC_M
#define BOOTMAGIC_KEY_DEFAULT_LAYER_1 KC_COMM
#define BOOTMAGIC_KEY_DEFAULT_LAYER_2 KC_DOT
#define BOOTMAGIC_HOST_NKRO KC_N
#endif

/* Mousekey settings */
#define MOUSEKEY_MOVE_MAX 127 // default 127
#define MOUSEKEY_WHEEL_MAX 127 // default 127
#define MOUSEKEY_MOVE_DELTA 5 // default 5
#define MOUSEKEY_WHEEL_DELTA 1 // default 1
#define MOUSEKEY_DELAY 300 // default 300
#define MOUSEKEY_INTERVAL 50 // default 50
#define MOUSEKEY_MAX_SPEED 5 // default 10
#define MOUSEKEY_TIME_TO_MAX 10 // default 20
#define MOUSEKEY_WHEEL_MAX_SPEED 8 // default 8
#define MOUSEKEY_WHEEL_TIME_TO_MAX 40 // default 40

/* Action tapping settings */
#define TAPPING_TERM 200 // default 200
/* #define TAPPING_TOGGLE 2 // default 5 */
/* #define ONESHOT_TIMEOUT 5000 // default undefined */
#define ONESHOT_TAP_TOGGLE 2

#endif

+ 223
- 0
keyboard/halfhalf/i2c.c View File

@@ -0,0 +1,223 @@
#include <util/twi.h>
#include <avr/io.h>
#include <stdlib.h>
#include <avr/interrupt.h>
#include <util/twi.h>
#include <stdbool.h>
#include "i2c.h"

#define I2C_READ 1
#define I2C_WRITE 0

#define I2C_ACK 1
#define I2C_NACK 0

// Limits the amount of we wait for any one i2c transaction.
// Since were running SCL line 100kHz (=> 10μs/bit), and each transactions is
// 9 bits, a single transaction will take around 90μs to complete.
//
// (F_CPU/SCL_CLOCK) => # of mcu cycles to transfer a bit
// poll loop takes at least 8 clock cycles to execute
#define I2C_LOOP_TIMEOUT (9+1)*(F_CPU/SCL_CLOCK)/8

#define BUFFER_POS_INC() (slave_buffer_pos = (slave_buffer_pos+1)%SLAVE_BUFFER_SIZE)

static volatile uint8_t i2c_slave_buffer[SLAVE_BUFFER_SIZE] = {0};

static volatile uint8_t slave_buffer_pos;
static volatile bool slave_has_register_set = false;

static uint8_t i2c_start(uint8_t address);
static void i2c_stop(void);
static uint8_t i2c_write(uint8_t data);
static uint8_t i2c_read(uint8_t ack);

// Wait for an i2c operation to finish
inline static
void i2c_delay(void) {
uint16_t lim = 0;
while(!(TWCR & (1<<TWINT)) && lim < I2C_LOOP_TIMEOUT)
lim++;

// easier way, but will wait slightly longer
// _delay_us(100);
}

// i2c_device_addr: the i2c device to communicate with
// addr: the memory address to read from the i2c device
// dest: pointer to where read data is saved
// len: the number of bytes to read
//
// NOTE: on error, the data in dest may have been modified
bool i2c_master_read(uint8_t i2c_device_addr, uint8_t addr, uint8_t *dest, uint8_t len) {
bool err;
if (len == 0) return 0;

err = i2c_start(i2c_device_addr + I2C_WRITE);
if (err) return err;
err = i2c_write(addr);
if (err) return err;

err = i2c_start(i2c_device_addr + I2C_READ);
if (err) return err;

for (uint8_t i = 0; i < len-1; ++i) {
dest[i] = i2c_read(I2C_ACK);
}
dest[len-1] = i2c_read(I2C_NACK);
i2c_stop();

return 0;
}

// i2c_device_addr: the i2c device to communicate with
// addr: the memory address at which to write in the i2c device
// data: the data to be written
// len: the number of bytes to write
bool i2c_master_write(uint8_t i2c_device_addr, uint8_t addr, uint8_t *data, uint8_t len) {
bool err;

if (len == 0) return 0;

err = i2c_start(i2c_device_addr + I2C_WRITE);
if (err) return err;
err = i2c_write(addr);
if (err) return err;

for (uint8_t i = 0; i < len; ++i) {
err = i2c_write(data[i]);
if (err) return err;
}
i2c_stop();
return 0;
}

void i2c_slave_write(uint8_t addr, uint8_t data) {
i2c_slave_buffer[addr] = data;
}

uint8_t i2c_slave_read(uint8_t addr) {
return i2c_slave_buffer[addr];
}

// Setup twi to run at 100kHz
void i2c_master_init(void) {
// no prescaler
TWSR = 0;
// Set TWI clock frequency to SCL_CLOCK. Need TWBR>10.
// Check datasheets for more info.
TWBR = ((F_CPU/SCL_CLOCK)-16)/2;
}

// Start a transaction with the given i2c slave address. The direction of the
// transfer is set with I2C_READ and I2C_WRITE.
// returns: 0 => success
// 1 => error
uint8_t i2c_start(uint8_t address) {
TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTA);

i2c_delay();

// check that we started successfully
if ( (TW_STATUS != TW_START) && (TW_STATUS != TW_REP_START))
return 1;

TWDR = address;
TWCR = (1<<TWINT) | (1<<TWEN);

i2c_delay();

if ( (TW_STATUS != TW_MT_SLA_ACK) && (TW_STATUS != TW_MR_SLA_ACK) )
return 1; // slave did not acknowledge
else
return 0; // success
}


// Finish the i2c transaction.
void i2c_stop(void) {
TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTO);

uint16_t lim = 0;
while(!(TWCR & (1<<TWSTO)) && lim < I2C_LOOP_TIMEOUT)
lim++;
}

// Write one byte to the i2c slave.
// returns 0 => slave ACK
// 1 => slave NACK
uint8_t i2c_write(uint8_t data) {
TWDR = data;
TWCR = (1<<TWINT) | (1<<TWEN);

i2c_delay();

// check if the slave acknowledged us
return (TW_STATUS == TW_MT_DATA_ACK) ? 0 : 1;
}

// Read one byte from the i2c slave. If ack=1 the slave is acknowledged,
// if ack=0 the acknowledge bit is not set.
// returns: byte read from i2c device
uint8_t i2c_read(uint8_t ack) {
TWCR = (1<<TWINT) | (1<<TWEN) | (ack<<TWEA);

i2c_delay();
return TWDR;
}

void i2c_slave_init(uint8_t address) {
TWAR = address << 0; // slave i2c address
// TWEN - twi enable
// TWEA - enable address acknowledgement
// TWINT - twi interrupt flag
// TWIE - enable the twi interrupt
TWCR = (1<<TWIE) | (1<<TWEA) | (1<<TWINT) | (1<<TWEN);
}

ISR(TWI_vect);

ISR(TWI_vect) {
uint8_t ack = 1;
switch(TW_STATUS) {
case TW_SR_SLA_ACK:
// this device has been addressed as a slave receiver
slave_has_register_set = false;
break;

case TW_SR_DATA_ACK:
// this device has received data as a slave receiver
// The first byte that we receive in this transaction sets the location
// of the read/write location of the slaves memory that it exposes over
// i2c. After that, bytes will be written at slave_buffer_pos, incrementing
// slave_buffer_pos after each write.
if(!slave_has_register_set) {
slave_buffer_pos = TWDR;
// don't acknowledge the master if this memory loctaion is out of bounds
if ( slave_buffer_pos >= SLAVE_BUFFER_SIZE ) {
ack = 0;
slave_buffer_pos = 0;
}
slave_has_register_set = true;
} else {
i2c_slave_buffer[slave_buffer_pos] = TWDR;
BUFFER_POS_INC();
}
break;

case TW_ST_SLA_ACK:
case TW_ST_DATA_ACK:
// master has addressed this device as a slave transmitter and is
// requesting data.
TWDR = i2c_slave_buffer[slave_buffer_pos];
BUFFER_POS_INC();
break;

case TW_BUS_ERROR: // something went wrong, reset twi state
TWCR = 0;
default:
break;
}
// Reset everything, so we are ready for the next TWI interrupt
TWCR |= (1<<TWIE) | (1<<TWINT) | (ack<<TWEA) | (1<<TWEN);
}

+ 21
- 0
keyboard/halfhalf/i2c.h View File

@@ -0,0 +1,21 @@
#ifndef I2C_H
#define I2C_H

#include <stdint.h>

#define SLAVE_BUFFER_SIZE 0x40

// i2c SCL clock frequency
#define SCL_CLOCK 100000L


void i2c_master_init(void);
void i2c_slave_init(uint8_t address);

bool i2c_master_write(uint8_t i2c_device_addr, uint8_t addr, uint8_t *dest, uint8_t len);
bool i2c_master_read(uint8_t i2c_device_addr, uint8_t addr, uint8_t *data, uint8_t len);

void i2c_slave_write(uint8_t addr, uint8_t data);
uint8_t i2c_slave_read(uint8_t addr);

#endif

BIN
keyboard/halfhalf/imgs/split-keyboard-i2c-schematic.png View File


BIN
keyboard/halfhalf/imgs/split-keyboard-serial-schematic.png View File


+ 24
- 0
keyboard/halfhalf/led.c View File

@@ -0,0 +1,24 @@
/*
Copyright 2012 Jun Wako <wakojun@gmail.com>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include <avr/io.h>
#include "stdint.h"
#include "led.h"

void led_set(uint8_t usb_led)
{
}

+ 298
- 0
keyboard/halfhalf/matrix.c View File

@@ -0,0 +1,298 @@
/*
Copyright 2012 Jun Wako <wakojun@gmail.com>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

/*
* scan matrix
*/
#include <stdint.h>
#include <stdbool.h>
#include <avr/io.h>
#include <avr/wdt.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include "print.h"
#include "debug.h"
#include "util.h"
#include "timer.h"
#include "matrix.h"
#include "i2c.h"
#include "serial.h"
#include "split-util.h"
#include "pro-micro.h"
#include "config.h"

#include "pin_defs.h"

#ifndef DEBOUNCE
# define DEBOUNCE 5
#endif

#define ERROR_DISCONNECT_COUNT 5

#define I2C_MATRIX_ADDR 0x00
#define I2C_LED_ADDR ROWS_PER_HAND

static uint8_t debouncing = DEBOUNCE;
static uint8_t error_count = 0;

/* matrix state(1:on, 0:off) */
static matrix_row_t matrix[MATRIX_ROWS];
static matrix_row_t matrix_debouncing[MATRIX_ROWS];

static const uint8_t row_pins[MATRIX_ROWS] = MATRIX_ROW_PINS;
static const uint8_t col_pins[MATRIX_COLS] = MATRIX_COL_PINS;

static matrix_row_t read_cols(void);
static void init_cols(void);
static void unselect_rows(void);
static void select_row(uint8_t row);

inline
uint8_t matrix_rows(void)
{
return MATRIX_ROWS;
}

inline
uint8_t matrix_cols(void)
{
return MATRIX_COLS;
}

void matrix_init(void)
{
// To use PORTF disable JTAG with writing JTD bit twice within four cycles.
MCUCR |= (1<<JTD);
MCUCR |= (1<<JTD);

debug_enable = true;
debug_matrix = true;
debug_mouse = true;
// initialize row and col
unselect_rows();
init_cols();

TX_RX_LED_INIT;
//Turn LEDs off by default
RXLED0;
TXLED0;

// initialize matrix state: all keys off
for (uint8_t i=0; i < MATRIX_ROWS; i++) {
matrix[i] = 0;
matrix_debouncing[i] = 0;
}
}

uint8_t _matrix_scan(void)
{
// Right hand is stored after the left in the matirx so, we need to offset it
int offset = isLeftHand ? 0 : (ROWS_PER_HAND);

for (uint8_t i = 0; i < ROWS_PER_HAND; i++) {
select_row(i);
_delay_us(30); // without this wait read unstable value.
matrix_row_t cols = read_cols();
if (matrix_debouncing[i+offset] != cols) {
matrix_debouncing[i+offset] = cols;
debouncing = DEBOUNCE;
}
unselect_rows();
}

if (debouncing) {
if (--debouncing) {
_delay_ms(1);
} else {
for (uint8_t i = 0; i < ROWS_PER_HAND; i++) {
matrix[i+offset] = matrix_debouncing[i+offset];
}
}
}

return 1;
}

// Get rows from other half over i2c
int i2c_transaction(void) {
bool err = false;
int slaveOffset = (isLeftHand) ? (ROWS_PER_HAND) : 0;

err = i2c_master_read(
SLAVE_I2C_ADDRESS, // i2c address of other half
I2C_MATRIX_ADDR, // read the slaves matrix data
matrix+slaveOffset, // store in correct position in master's matrix
ROWS_PER_HAND // number of bytes to read
);

#ifdef I2C_WRITE_TEST_CODE
// controls the RX led on the slave and toggles it every second
uint8_t test_data = (timer_read() / 1000) % 2;

err |= i2c_master_write(
SLAVE_I2C_ADDRESS, // i2c address of other half
I2C_LED_ADDR, // address for led control
&test_data, // data to send
sizeof(test_data) // size of test data
);
#endif

return err;
}

int serial_transaction(void) {
int slaveOffset = (isLeftHand) ? (ROWS_PER_HAND) : 0;

if (serial_update_buffers()) {
return 1;
}

for (int i = 0; i < ROWS_PER_HAND; ++i) {
matrix[slaveOffset+i] = serial_slave_buffer[i];
}
return 0;
}

uint8_t matrix_scan(void)
{
int ret = _matrix_scan();



#ifdef USE_I2C
if( i2c_transaction() ) {
#else
if( serial_transaction() ) {
#endif
// turn on the indicator led when halves are disconnected
TXLED1;

error_count++;

if (error_count > ERROR_DISCONNECT_COUNT) {
// reset other half if disconnected
int slaveOffset = (isLeftHand) ? (ROWS_PER_HAND) : 0;
for (int i = 0; i < ROWS_PER_HAND; ++i) {
matrix[slaveOffset+i] = 0;
}
}
} else {
// turn off the indicator led on no error
TXLED0;
error_count = 0;
}

return ret;
}

void matrix_slave_scan(void) {
_matrix_scan();

int offset = (isLeftHand) ? 0 : (MATRIX_ROWS / 2);

#ifdef USE_I2C
for (int i = 0; i < ROWS_PER_HAND; ++i) {
i2c_slave_write(I2C_MATRIX_ADDR+i, matrix[offset+i]);
}

#ifdef I2C_WRITE_TEST_CODE
// control the pro micro RX LED based on what the
// i2c master has sent us
uint8_t led_state = i2c_slave_read(I2C_LED_ADDR);
if (led_state == 1) {
RXLED1;
} else if(led_state == 0) {
RXLED0;
}
#endif

#else
for (int i = 0; i < ROWS_PER_HAND; ++i) {
serial_slave_buffer[i] = matrix[offset+i];
}
#endif

}

bool matrix_is_modified(void)
{
if (debouncing) return false;
return true;
}

inline
bool matrix_is_on(uint8_t row, uint8_t col)
{
return (matrix[row] & ((matrix_row_t)1<<col));
}

inline
matrix_row_t matrix_get_row(uint8_t row)
{
return matrix[row];
}

void matrix_print(void)
{
print("\nr/c 0123456789ABCDEF\n");
for (uint8_t row = 0; row < MATRIX_ROWS; row++) {
phex(row); print(": ");
pbin_reverse16(matrix_get_row(row));
print("\n");
}
}

uint8_t matrix_key_count(void)
{
uint8_t count = 0;
for (uint8_t i = 0; i < MATRIX_ROWS; i++) {
count += bitpop16(matrix[i]);
}
return count;
}


static void init_cols(void)
{
for(int x = 0; x < MATRIX_COLS; x++) {
_SFR_IO8((col_pins[x] >> 4) + 1) &= ~_BV(col_pins[x] & 0xF);
_SFR_IO8((col_pins[x] >> 4) + 2) |= _BV(col_pins[x] & 0xF);
}
}

static matrix_row_t read_cols(void)
{
matrix_row_t result = 0;
for(int x = 0; x < MATRIX_COLS; x++) {
result |= (_SFR_IO8(col_pins[x] >> 4) & _BV(col_pins[x] & 0xF)) ? 0 : (1 << x);
}
return result;
}

static void unselect_rows(void)
{
for(int x = 0; x < ROWS_PER_HAND; x++) {
_SFR_IO8((row_pins[x] >> 4) + 1) &= ~_BV(row_pins[x] & 0xF);
_SFR_IO8((row_pins[x] >> 4) + 2) |= _BV(row_pins[x] & 0xF);
}
}

static void select_row(uint8_t row)
{
_SFR_IO8((row_pins[row] >> 4) + 1) |= _BV(row_pins[row] & 0xF);
_SFR_IO8((row_pins[row] >> 4) + 2) &= ~_BV(row_pins[row] & 0xF);
}

+ 58
- 0
keyboard/halfhalf/pin_defs.h View File

@@ -0,0 +1,58 @@
#ifndef PIN_DEFS_H
#define PIN_DEFS_H

/* diode directions */
#define COL2ROW 0
#define ROW2COL 1

/* I/O pins */
#define B0 0x30
#define B1 0x31
#define B2 0x32
#define B3 0x33
#define B4 0x34
#define B5 0x35
#define B6 0x36
#define B7 0x37
#define C0 0x60
#define C1 0x61
#define C2 0x62
#define C3 0x63
#define C4 0x64
#define C5 0x65
#define C6 0x66
#define C7 0x67
#define D0 0x90
#define D1 0x91
#define D2 0x92
#define D3 0x93
#define D4 0x94
#define D5 0x95
#define D6 0x96
#define D7 0x97
#define E0 0xC0
#define E1 0xC1
#define E2 0xC2
#define E3 0xC3
#define E4 0xC4
#define E5 0xC5
#define E6 0xC6
#define E7 0xC7
#define F0 0xF0
#define F1 0xF1
#define F2 0xF2
#define F3 0xF3
#define F4 0xF4
#define F5 0xF5
#define F6 0xF6
#define F7 0xF7
#define A0 0x00
#define A1 0x01
#define A2 0x02
#define A3 0x03
#define A4 0x04
#define A5 0x05
#define A6 0x06
#define A7 0x07

#endif

+ 362
- 0
keyboard/halfhalf/pro-micro.h View File

@@ -0,0 +1,362 @@
/*
pins_arduino.h - Pin definition functions for Arduino
Part of Arduino - http://www.arduino.cc/

Copyright (c) 2007 David A. Mellis

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General
Public License along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 02111-1307 USA

$Id: wiring.h 249 2007-02-03 16:52:51Z mellis $
*/

#ifndef Pins_Arduino_h
#define Pins_Arduino_h

#include <avr/pgmspace.h>

// Workaround for wrong definitions in "iom32u4.h".
// This should be fixed in the AVR toolchain.
#undef UHCON
#undef UHINT
#undef UHIEN
#undef UHADDR
#undef UHFNUM
#undef UHFNUML
#undef UHFNUMH
#undef UHFLEN
#undef UPINRQX
#undef UPINTX
#undef UPNUM
#undef UPRST
#undef UPCONX
#undef UPCFG0X
#undef UPCFG1X
#undef UPSTAX
#undef UPCFG2X
#undef UPIENX
#undef UPDATX
#undef TCCR2A
#undef WGM20
#undef WGM21
#undef COM2B0
#undef COM2B1
#undef COM2A0
#undef COM2A1
#undef TCCR2B
#undef CS20
#undef CS21
#undef CS22
#undef WGM22
#undef FOC2B
#undef FOC2A
#undef TCNT2
#undef TCNT2_0
#undef TCNT2_1
#undef TCNT2_2
#undef TCNT2_3
#undef TCNT2_4
#undef TCNT2_5
#undef TCNT2_6
#undef TCNT2_7
#undef OCR2A
#undef OCR2_0
#undef OCR2_1
#undef OCR2_2
#undef OCR2_3
#undef OCR2_4
#undef OCR2_5
#undef OCR2_6
#undef OCR2_7
#undef OCR2B
#undef OCR2_0
#undef OCR2_1
#undef OCR2_2
#undef OCR2_3
#undef OCR2_4
#undef OCR2_5
#undef OCR2_6
#undef OCR2_7

#define NUM_DIGITAL_PINS 30
#define NUM_ANALOG_INPUTS 12

#define TX_RX_LED_INIT DDRD |= (1<<5), DDRB |= (1<<0)
#define TXLED0 PORTD |= (1<<5)
#define TXLED1 PORTD &= ~(1<<5)
#define RXLED0 PORTB |= (1<<0)
#define RXLED1 PORTB &= ~(1<<0)

static const uint8_t SDA = 2;
static const uint8_t SCL = 3;
#define LED_BUILTIN 13

// Map SPI port to 'new' pins D14..D17
static const uint8_t SS = 17;
static const uint8_t MOSI = 16;
static const uint8_t MISO = 14;
static const uint8_t SCK = 15;

// Mapping of analog pins as digital I/O
// A6-A11 share with digital pins
static const uint8_t A0 = 18;
static const uint8_t A1 = 19;
static const uint8_t A2 = 20;
static const uint8_t A3 = 21;
static const uint8_t A4 = 22;
static const uint8_t A5 = 23;
static const uint8_t A6 = 24; // D4
static const uint8_t A7 = 25; // D6
static const uint8_t A8 = 26; // D8
static const uint8_t A9 = 27; // D9
static const uint8_t A10 = 28; // D10
static const uint8_t A11 = 29; // D12

#define digitalPinToPCICR(p) ((((p) >= 8 && (p) <= 11) || ((p) >= 14 && (p) <= 17) || ((p) >= A8 && (p) <= A10)) ? (&PCICR) : ((uint8_t *)0))
#define digitalPinToPCICRbit(p) 0
#define digitalPinToPCMSK(p) ((((p) >= 8 && (p) <= 11) || ((p) >= 14 && (p) <= 17) || ((p) >= A8 && (p) <= A10)) ? (&PCMSK0) : ((uint8_t *)0))
#define digitalPinToPCMSKbit(p) ( ((p) >= 8 && (p) <= 11) ? (p) - 4 : ((p) == 14 ? 3 : ((p) == 15 ? 1 : ((p) == 16 ? 2 : ((p) == 17 ? 0 : (p - A8 + 4))))))

// __AVR_ATmega32U4__ has an unusual mapping of pins to channels
extern const uint8_t PROGMEM analog_pin_to_channel_PGM[];
#define analogPinToChannel(P) ( pgm_read_byte( analog_pin_to_channel_PGM + (P) ) )

#define digitalPinToInterrupt(p) ((p) == 0 ? 2 : ((p) == 1 ? 3 : ((p) == 2 ? 1 : ((p) == 3 ? 0 : ((p) == 7 ? 4 : NOT_AN_INTERRUPT)))))

#ifdef ARDUINO_MAIN

// On the Arduino board, digital pins are also used
// for the analog output (software PWM). Analog input
// pins are a separate set.

// ATMEL ATMEGA32U4 / ARDUINO LEONARDO
//
// D0 PD2 RXD1/INT2
// D1 PD3 TXD1/INT3
// D2 PD1 SDA SDA/INT1
// D3# PD0 PWM8/SCL OC0B/SCL/INT0
// D4 A6 PD4 ADC8
// D5# PC6 ??? OC3A/#OC4A
// D6# A7 PD7 FastPWM #OC4D/ADC10
// D7 PE6 INT6/AIN0
//
// D8 A8 PB4 ADC11/PCINT4
// D9# A9 PB5 PWM16 OC1A/#OC4B/ADC12/PCINT5
// D10# A10 PB6 PWM16 OC1B/0c4B/ADC13/PCINT6
// D11# PB7 PWM8/16 0C0A/OC1C/#RTS/PCINT7
// D12 A11 PD6 T1/#OC4D/ADC9
// D13# PC7 PWM10 CLK0/OC4A
//
// A0 D18 PF7 ADC7
// A1 D19 PF6 ADC6
// A2 D20 PF5 ADC5
// A3 D21 PF4 ADC4
// A4 D22 PF1 ADC1
// A5 D23 PF0 ADC0
//
// New pins D14..D17 to map SPI port to digital pins
//
// MISO D14 PB3 MISO,PCINT3
// SCK D15 PB1 SCK,PCINT1
// MOSI D16 PB2 MOSI,PCINT2
// SS D17 PB0 RXLED,SS/PCINT0
//
// Connected LEDs on board for TX and RX
// TXLED D24 PD5 XCK1
// RXLED D17 PB0
// HWB PE2 HWB

// these arrays map port names (e.g. port B) to the
// appropriate addresses for various functions (e.g. reading
// and writing)
const uint16_t PROGMEM port_to_mode_PGM[] = {
NOT_A_PORT,
NOT_A_PORT,
(uint16_t) &DDRB,
(uint16_t) &DDRC,
(uint16_t) &DDRD,
(uint16_t) &DDRE,
(uint16_t) &DDRF,
};

const uint16_t PROGMEM port_to_output_PGM[] = {
NOT_A_PORT,
NOT_A_PORT,
(uint16_t) &PORTB,
(uint16_t) &PORTC,
(uint16_t) &PORTD,
(uint16_t) &PORTE,
(uint16_t) &PORTF,
};

const uint16_t PROGMEM port_to_input_PGM[] = {
NOT_A_PORT,
NOT_A_PORT,
(uint16_t) &PINB,
(uint16_t) &PINC,
(uint16_t) &PIND,
(uint16_t) &PINE,
(uint16_t) &PINF,
};

const uint8_t PROGMEM digital_pin_to_port_PGM[] = {
PD, // D0 - PD2
PD, // D1 - PD3
PD, // D2 - PD1
PD, // D3 - PD0
PD, // D4 - PD4
PC, // D5 - PC6
PD, // D6 - PD7
PE, // D7 - PE6
PB, // D8 - PB4
PB, // D9 - PB5
PB, // D10 - PB6
PB, // D11 - PB7
PD, // D12 - PD6
PC, // D13 - PC7
PB, // D14 - MISO - PB3
PB, // D15 - SCK - PB1
PB, // D16 - MOSI - PB2
PB, // D17 - SS - PB0
PF, // D18 - A0 - PF7
PF, // D19 - A1 - PF6
PF, // D20 - A2 - PF5
PF, // D21 - A3 - PF4
PF, // D22 - A4 - PF1
PF, // D23 - A5 - PF0
PD, // D24 - PD5
PD, // D25 / D6 - A7 - PD7
PB, // D26 / D8 - A8 - PB4
PB, // D27 / D9 - A9 - PB5
PB, // D28 / D10 - A10 - PB6
PD, // D29 / D12 - A11 - PD6
};

const uint8_t PROGMEM digital_pin_to_bit_mask_PGM[] = {
_BV(2), // D0 - PD2
_BV(3), // D1 - PD3
_BV(1), // D2 - PD1
_BV(0), // D3 - PD0
_BV(4), // D4 - PD4
_BV(6), // D5 - PC6
_BV(7), // D6 - PD7
_BV(6), // D7 - PE6
_BV(4), // D8 - PB4
_BV(5), // D9 - PB5
_BV(6), // D10 - PB6
_BV(7), // D11 - PB7
_BV(6), // D12 - PD6
_BV(7), // D13 - PC7
_BV(3), // D14 - MISO - PB3
_BV(1), // D15 - SCK - PB1
_BV(2), // D16 - MOSI - PB2
_BV(0), // D17 - SS - PB0
_BV(7), // D18 - A0 - PF7
_BV(6), // D19 - A1 - PF6
_BV(5), // D20 - A2 - PF5
_BV(4), // D21 - A3 - PF4
_BV(1), // D22 - A4 - PF1
_BV(0), // D23 - A5 - PF0
_BV(5), // D24 - PD5
_BV(7), // D25 / D6 - A7 - PD7
_BV(4), // D26 / D8 - A8 - PB4
_BV(5), // D27 / D9 - A9 - PB5
_BV(6), // D28 / D10 - A10 - PB6
_BV(6), // D29 / D12 - A11 - PD6
};

const uint8_t PROGMEM digital_pin_to_timer_PGM[] = {
NOT_ON_TIMER,
NOT_ON_TIMER,
NOT_ON_TIMER,
TIMER0B, /* 3 */
NOT_ON_TIMER,
TIMER3A, /* 5 */
TIMER4D, /* 6 */
NOT_ON_TIMER,
NOT_ON_TIMER,
TIMER1A, /* 9 */
TIMER1B, /* 10 */
TIMER0A, /* 11 */
NOT_ON_TIMER,
TIMER4A, /* 13 */
NOT_ON_TIMER,
NOT_ON_TIMER,
NOT_ON_TIMER,
NOT_ON_TIMER,
NOT_ON_TIMER,
NOT_ON_TIMER,

NOT_ON_TIMER,
NOT_ON_TIMER,
NOT_ON_TIMER,
NOT_ON_TIMER,
NOT_ON_TIMER,
NOT_ON_TIMER,
NOT_ON_TIMER,
NOT_ON_TIMER,
NOT_ON_TIMER,
NOT_ON_TIMER,
};

const uint8_t PROGMEM analog_pin_to_channel_PGM[] = {
7, // A0 PF7 ADC7
6, // A1 PF6 ADC6
5, // A2 PF5 ADC5
4, // A3 PF4 ADC4
1, // A4 PF1 ADC1
0, // A5 PF0 ADC0
8, // A6 D4 PD4 ADC8
10, // A7 D6 PD7 ADC10
11, // A8 D8 PB4 ADC11
12, // A9 D9 PB5 ADC12
13, // A10 D10 PB6 ADC13
9 // A11 D12 PD6 ADC9
};

#endif /* ARDUINO_MAIN */

// These serial port names are intended to allow libraries and architecture-neutral
// sketches to automatically default to the correct port name for a particular type
// of use. For example, a GPS module would normally connect to SERIAL_PORT_HARDWARE_OPEN,
// the first hardware serial port whose RX/TX pins are not dedicated to another use.
//
// SERIAL_PORT_MONITOR Port which normally prints to the Arduino Serial Monitor
//
// SERIAL_PORT_USBVIRTUAL Port which is USB virtual serial
//
// SERIAL_PORT_LINUXBRIDGE Port which connects to a Linux system via Bridge library
//
// SERIAL_PORT_HARDWARE Hardware serial port, physical RX & TX pins.
//
// SERIAL_PORT_HARDWARE_OPEN Hardware serial ports which are open for use. Their RX & TX
// pins are NOT connected to anything by default.
#define SERIAL_PORT_MONITOR Serial
#define SERIAL_PORT_USBVIRTUAL Serial
#define SERIAL_PORT_HARDWARE Serial1
#define SERIAL_PORT_HARDWARE_OPEN Serial1

#endif /* Pins_Arduino_h */

+ 225
- 0
keyboard/halfhalf/serial.c View File

@@ -0,0 +1,225 @@
/*
* WARNING: be careful changing this code, it is very timing dependent
*/

#ifndef F_CPU
#define F_CPU 16000000
#endif

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdbool.h>

#include "serial.h"

// Serial pulse period in microseconds. Its probably a bad idea to lower this
// value.
#define SERIAL_DELAY 24

uint8_t volatile serial_slave_buffer[SERIAL_SLAVE_BUFFER_LENGTH] = {0};
uint8_t volatile serial_master_buffer[SERIAL_MASTER_BUFFER_LENGTH] = {0};

#define SLAVE_DATA_CORRUPT (1<<0)
volatile uint8_t status = 0;

inline static
void serial_delay(void) {
_delay_us(SERIAL_DELAY);
}

inline static
void serial_output(void) {
SERIAL_PIN_DDR |= SERIAL_PIN_MASK;
}

// make the serial pin an input with pull-up resistor
inline static
void serial_input(void) {
SERIAL_PIN_DDR &= ~SERIAL_PIN_MASK;
SERIAL_PIN_PORT |= SERIAL_PIN_MASK;
}

inline static
uint8_t serial_read_pin(void) {
return !!(SERIAL_PIN_INPUT & SERIAL_PIN_MASK);
}

inline static
void serial_low(void) {
SERIAL_PIN_PORT &= ~SERIAL_PIN_MASK;
}

inline static
void serial_high(void) {
SERIAL_PIN_PORT |= SERIAL_PIN_MASK;
}

void serial_master_init(void) {
serial_output();
serial_high();
}

void serial_slave_init(void) {
serial_input();

// Enable INT0
EIMSK |= _BV(INT0);
// Trigger on falling edge of INT0
EICRA &= ~(_BV(ISC00) | _BV(ISC01));
}

// Used by the master to synchronize timing with the slave.
static
void sync_recv(void) {
serial_input();
// This shouldn't hang if the slave disconnects because the
// serial line will float to high if the slave does disconnect.
while (!serial_read_pin());
serial_delay();
}

// Used by the slave to send a synchronization signal to the master.
static
void sync_send(void) {
serial_output();

serial_low();
serial_delay();

serial_high();
}

// Reads a byte from the serial line
static
uint8_t serial_read_byte(void) {
uint8_t byte = 0;
serial_input();
for ( uint8_t i = 0; i < 8; ++i) {
byte = (byte << 1) | serial_read_pin();
serial_delay();
_delay_us(1);
}

return byte;
}

// Sends a byte with MSB ordering
static
void serial_write_byte(uint8_t data) {
uint8_t b = 8;
serial_output();
while( b-- ) {
if(data & (1 << b)) {
serial_high();
} else {
serial_low();
}
serial_delay();
}
}

// interrupt handle to be used by the slave device
ISR(SERIAL_PIN_INTERRUPT) {
sync_send();

uint8_t checksum = 0;
for (int i = 0; i < SERIAL_SLAVE_BUFFER_LENGTH; ++i) {
serial_write_byte(serial_slave_buffer[i]);
sync_send();
checksum += serial_slave_buffer[i];
}
serial_write_byte(checksum);
sync_send();

// wait for the sync to finish sending
serial_delay();

// read the middle of pulses
_delay_us(SERIAL_DELAY/2);

uint8_t checksum_computed = 0;
for (int i = 0; i < SERIAL_MASTER_BUFFER_LENGTH; ++i) {
serial_master_buffer[i] = serial_read_byte();
sync_send();
checksum_computed += serial_master_buffer[i];
}
uint8_t checksum_received = serial_read_byte();
sync_send();

serial_input(); // end transaction

if ( checksum_computed != checksum_received ) {
status |= SLAVE_DATA_CORRUPT;
} else {
status &= ~SLAVE_DATA_CORRUPT;
}
}

inline
bool serial_slave_DATA_CORRUPT(void) {
return status & SLAVE_DATA_CORRUPT;
}

// Copies the serial_slave_buffer to the master and sends the
// serial_master_buffer to the slave.
//
// Returns:
// 0 => no error
// 1 => slave did not respond
int serial_update_buffers(void) {
// this code is very time dependent, so we need to disable interrupts
cli();

// signal to the slave that we want to start a transaction
serial_output();
serial_low();
_delay_us(1);

// wait for the slaves response
serial_input();
serial_high();
_delay_us(SERIAL_DELAY);

// check if the slave is present
if (serial_read_pin()) {
// slave failed to pull the line low, assume not present
sei();
return 1;
}

// if the slave is present syncronize with it
sync_recv();

uint8_t checksum_computed = 0;
// receive data from the slave
for (int i = 0; i < SERIAL_SLAVE_BUFFER_LENGTH; ++i) {
serial_slave_buffer[i] = serial_read_byte();
sync_recv();
checksum_computed += serial_slave_buffer[i];
}
uint8_t checksum_received = serial_read_byte();
sync_recv();

if (checksum_computed != checksum_received) {
sei();
return 1;
}

uint8_t checksum = 0;
// send data to the slave
for (int i = 0; i < SERIAL_MASTER_BUFFER_LENGTH; ++i) {
serial_write_byte(serial_master_buffer[i]);
sync_recv();
checksum += serial_master_buffer[i];
}
serial_write_byte(checksum);
sync_recv();

// always, release the line when not in use
serial_output();
serial_high();

sei();
return 0;
}

+ 26
- 0
keyboard/halfhalf/serial.h View File

@@ -0,0 +1,26 @@
#ifndef MY_SERIAL_H
#define MY_SERIAL_H

#include "config.h"
#include <stdbool.h>

/* TODO: some defines for interrupt setup */
#define SERIAL_PIN_DDR DDRD
#define SERIAL_PIN_PORT PORTD
#define SERIAL_PIN_INPUT PIND
#define SERIAL_PIN_MASK _BV(PD0)
#define SERIAL_PIN_INTERRUPT INT0_vect

#define SERIAL_SLAVE_BUFFER_LENGTH MATRIX_ROWS/2
#define SERIAL_MASTER_BUFFER_LENGTH 1

// Buffers for master - slave communication
extern volatile uint8_t serial_slave_buffer[SERIAL_SLAVE_BUFFER_LENGTH];
extern volatile uint8_t serial_master_buffer[SERIAL_MASTER_BUFFER_LENGTH];

void serial_master_init(void);
void serial_slave_init(void);
int serial_update_buffers(void);
bool serial_slave_data_corrupt(void);

#endif

+ 68
- 0
keyboard/halfhalf/split-util.c View File

@@ -0,0 +1,68 @@
#include <avr/io.h>
#include <avr/wdt.h>
#include <avr/power.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <avr/eeprom.h>
#include "split-util.h"
#include "matrix.h"
#include "i2c.h"
#include "serial.h"
#include "keyboard.h"
#include "config.h"

volatile bool isLeftHand = true;

static void setup_handedness(void) {
isLeftHand = eeprom_read_byte(EECONFIG_HANDEDNESS);
}

static void keyboard_master_setup(void) {
#ifdef USE_I2C
i2c_master_init();
#else
serial_master_init();
#endif
}

static void keyboard_slave_setup(void) {
#ifdef USE_I2C
i2c_slave_init(SLAVE_I2C_ADDRESS);
#else
serial_slave_init();
#endif
}

bool has_usb(void) {
USBCON |= (1 << OTGPADE); //enables VBUS pad
_delay_us(5);
return (USBSTA & (1<<VBUS)); //checks state of VBUS
}

void split_keyboard_setup(void) {
setup_handedness();

if (has_usb()) {
keyboard_master_setup();
} else {
keyboard_slave_setup();
}
sei();
}

void keyboard_slave_loop(void) {
matrix_init();

while (1) {
matrix_slave_scan();
}
}

// this code runs before the usb and keyboard is initialized
void matrix_setup(void) {
split_keyboard_setup();

if (!has_usb()) {
keyboard_slave_loop();
}
}

+ 20
- 0
keyboard/halfhalf/split-util.h View File

@@ -0,0 +1,20 @@
#ifndef SPLIT_KEYBOARD_UTIL_H
#define SPLIT_KEYBOARD_UTIL_H

#include <stdbool.h>

#define EECONFIG_BOOTMAGIC_END (uint8_t *)7
#define EECONFIG_HANDEDNESS EECONFIG_BOOTMAGIC_END

#define SLAVE_I2C_ADDRESS 0x32

extern volatile bool isLeftHand;

// slave version of matix scan, defined in matrix.c
void matrix_slave_scan(void);

void split_keyboard_setup(void);
bool has_usb(void);
void keyboard_slave_loop(void);

#endif

+ 226
- 0
keyboard/halfhalf/uno-slave/Makefile View File

@@ -0,0 +1,226 @@
# Hey Emacs, this is a -*- makefile -*-

# AVR-GCC Makefile template, derived from the WinAVR template (which
# is public domain), believed to be neutral to any flavor of "make"
# (GNU make, BSD make, SysV make)


MCU = atmega328p
FORMAT = ihex
TARGET = keyboard-i2c-slave
SRC = \
$(TARGET).c \
uno-matrix.c \
../serial.c \
../i2c-slave.c

ASRC =
OPT = s

# Programming support using avrdude. Settings and variables.

AVRDUDE_PROGRAMMER = arduino
AVRDUDE_PORT = /dev/ttyACM0

# Name of this Makefile (used for "make depend").
MAKEFILE = Makefile

# Debugging format.
# Native formats for AVR-GCC's -g are stabs [default], or dwarf-2.
# AVR (extended) COFF requires stabs, plus an avr-objcopy run.
DEBUG = stabs

# Compiler flag to set the C Standard level.
# c89 - "ANSI" C
# gnu89 - c89 plus GCC extensions
# c99 - ISO C99 standard (not yet fully implemented)
# gnu99 - c99 plus GCC extensions
CSTANDARD = -std=gnu99

# Place -D or -U options here
CDEFS =

# Place -I options here
CINCS =


CDEBUG = -g$(DEBUG)
CWARN = -Wall -Wstrict-prototypes
CTUNING = -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums
#CEXTRA = -Wa,-adhlns=$(<:.c=.lst)
CFLAGS = $(CDEBUG) $(CDEFS) $(CINCS) -O$(OPT) $(CWARN) $(CSTANDARD) $(CEXTRA) \
-fno-aggressive-loop-optimizations

#ASFLAGS = -Wa,-adhlns=$(<:.S=.lst),-gstabs


#Additional libraries.

# Minimalistic printf version
PRINTF_LIB_MIN = -Wl,-u,vfprintf -lprintf_min

# Floating point printf version (requires MATH_LIB = -lm below)
PRINTF_LIB_FLOAT = -Wl,-u,vfprintf -lprintf_flt

PRINTF_LIB =

# Minimalistic scanf version
SCANF_LIB_MIN = -Wl,-u,vfscanf -lscanf_min

# Floating point + %[ scanf version (requires MATH_LIB = -lm below)
SCANF_LIB_FLOAT = -Wl,-u,vfscanf -lscanf_flt

SCANF_LIB =

MATH_LIB = -lm

# External memory options

# 64 KB of external RAM, starting after internal RAM (ATmega128!),
# used for variables (.data/.bss) and heap (malloc()).
#EXTMEMOPTS = -Wl,--section-start,.data=0x801100,--defsym=__heap_end=0x80ffff

# 64 KB of external RAM, starting after internal RAM (ATmega128!),
# only used for heap (malloc()).
#EXTMEMOPTS = -Wl,--defsym=__heap_start=0x801100,--defsym=__heap_end=0x80ffff

EXTMEMOPTS =

#LDMAP = $(LDFLAGS) -Wl,-Map=$(TARGET).map,--cref
LDFLAGS = $(EXTMEMOPTS) $(LDMAP) $(PRINTF_LIB) $(SCANF_LIB) $(MATH_LIB)


AVRDUDE_WRITE_FLASH = -U flash:w:$(TARGET).hex
#AVRDUDE_WRITE_EEPROM = -U eeprom:w:$(TARGET).eep


# Uncomment the following if you want avrdude's erase cycle counter.
# Note that this counter needs to be initialized first using -Yn,
# see avrdude manual.
#AVRDUDE_ERASE_COUNTER = -y

# Uncomment the following if you do /not/ wish a verification to be
# performed after programming the device.
#AVRDUDE_NO_VERIFY = -V

# Increase verbosity level. Please use this when submitting bug
# reports about avrdude. See <http://savannah.nongnu.org/projects/avrdude>
# to submit bug reports.
#AVRDUDE_VERBOSE = -v -v

AVRDUDE_BASIC = -p $(MCU) -P $(AVRDUDE_PORT) -c $(AVRDUDE_PROGRAMMER)
AVRDUDE_FLAGS = $(AVRDUDE_BASIC) $(AVRDUDE_NO_VERIFY) $(AVRDUDE_VERBOSE) $(AVRDUDE_ERASE_COUNTER)


CC = avr-gcc
OBJCOPY = avr-objcopy
OBJDUMP = avr-objdump
SIZE = avr-size
NM = avr-nm
AVRDUDE = avrdude
REMOVE = rm -f
MV = mv -f

# Define all object files.
OBJ = $(SRC:.c=.o) $(ASRC:.S=.o)

# Define all listing files.
LST = $(ASRC:.S=.lst) $(SRC:.c=.lst)

# Combine all necessary flags and optional flags.
# Add target processor to flags.
ALL_CFLAGS = -mmcu=$(MCU) -I. $(CFLAGS)
ALL_ASFLAGS = -mmcu=$(MCU) -I. -x assembler-with-cpp $(ASFLAGS)


# Default target.
all: build

build: elf hex eep

elf: $(TARGET).elf
hex: $(TARGET).hex
eep: $(TARGET).eep
lss: $(TARGET).lss
sym: $(TARGET).sym


# Program the device.
program: $(TARGET).hex $(TARGET).eep
$(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_FLASH) $(AVRDUDE_WRITE_EEPROM)


# Convert ELF to COFF for use in debugging / simulating in AVR Studio or VMLAB.
COFFCONVERT=$(OBJCOPY) --debugging \
--change-section-address .data-0x800000 \
--change-section-address .bss-0x800000 \
--change-section-address .noinit-0x800000 \
--change-section-address .eeprom-0x810000


coff: $(TARGET).elf
$(COFFCONVERT) -O coff-avr $(TARGET).elf $(TARGET).cof


extcoff: $(TARGET).elf
$(COFFCONVERT) -O coff-ext-avr $(TARGET).elf $(TARGET).cof


.SUFFIXES: .elf .hex .eep .lss .sym

.elf.hex:
$(OBJCOPY) -O $(FORMAT) -R .eeprom $< $@

.elf.eep:
-$(OBJCOPY) -j .eeprom --set-section-flags=.eeprom="alloc,load" \
--change-section-lma .eeprom=0 -O $(FORMAT) $< $@

# Create extended listing file from ELF output file.
.elf.lss:
$(OBJDUMP) -h -S $< > $@

# Create a symbol table from ELF output file.
.elf.sym:
$(NM) -n $< > $@



# Link: create ELF output file from object files.
$(TARGET).elf: $(OBJ)
$(CC) $(ALL_CFLAGS) $(OBJ) --output $@ $(LDFLAGS)


# Compile: create object files from C source files.
.c.o:
$(CC) -c $(ALL_CFLAGS) $< -o $@


# Compile: create assembler files from C source files.
.c.s:
$(CC) -S $(ALL_CFLAGS) $< -o $@


# Assemble: create object files from assembler source files.
.S.o:
$(CC) -c $(ALL_ASFLAGS) $< -o $@



# Target: clean project.
clean:
$(REMOVE) $(TARGET).hex $(TARGET).eep $(TARGET).cof $(TARGET).elf \
$(TARGET).map $(TARGET).sym $(TARGET).lss \
$(OBJ) $(LST) $(SRC:.c=.s) $(SRC:.c=.d)

depend:
if grep '^# DO NOT DELETE' $(MAKEFILE) >/dev/null; \
then \
sed -e '/^# DO NOT DELETE/,$$d' $(MAKEFILE) > \
$(MAKEFILE).$$$$ && \
$(MV) $(MAKEFILE).$$$$ $(MAKEFILE); \
fi
echo '# DO NOT DELETE THIS LINE -- make depend depends on it.' \
>> $(MAKEFILE); \
$(CC) -M -mmcu=$(MCU) $(CDEFS) $(CINCS) $(SRC) $(ASRC) >> $(MAKEFILE)

.PHONY: all build elf hex eep lss sym program coff extcoff clean depend

+ 42
- 0
keyboard/halfhalf/uno-slave/keyboard-i2c-slave.c View File

@@ -0,0 +1,42 @@
#include "../i2c-slave.h"
#include "../serial.h"
#include "uno-matrix.h"

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

void setup(void) {
// give some time for noise to clear
_delay_us(1000);

// turn off arduino uno's led on pin 13
DDRB |= (1 << 5);
PORTB &= ~(1 << 5);

matrix_init();
/* i2c_slave_init(0x32); */
serial_slave_init();

/* serial_slave_buffer[0] = 0xa1; */
/* serial_slave_buffer[1] = 0x52; */
/* serial_slave_buffer[2] = 0xa2; */
/* serial_slave_buffer[3] = 0x67; */

// need interrupts for i2c slave code to work
sei();
}

void loop(void) {
matrix_scan();
for(int i=0; i<MATRIX_ROWS; ++i) {
slaveBuffer[i] = matrix_get_row(i);
serial_slave_buffer[i] = slaveBuffer[i];
}
}

int main(int argc, char *argv[]) {
setup();
while (1)
loop();
}

+ 1
- 0
keyboard/halfhalf/uno-slave/readme.md View File

@@ -0,0 +1 @@
Code for Arduino uno (atmega328p) slave used for testing.

+ 160
- 0
keyboard/halfhalf/uno-slave/uno-matrix.c View File

@@ -0,0 +1,160 @@
#define F_CPU 16000000UL

#include <util/delay.h>
#include <avr/io.h>
#include <stdlib.h>

#include "uno-matrix.h"

#define debug(X) NULL
#define debug_hex(X) NULL

#ifndef DEBOUNCE
# define DEBOUNCE 5
#endif

static uint8_t debouncing = DEBOUNCE;

/* matrix state(1:on, 0:off) */
static matrix_row_t matrix[MATRIX_ROWS];
static matrix_row_t matrix_debouncing[MATRIX_ROWS];

static matrix_row_t read_cols(void);
static void init_cols(void);
static void unselect_rows(void);
static void select_row(uint8_t row);

inline
uint8_t matrix_rows(void)
{
return MATRIX_ROWS;
}

inline
uint8_t matrix_cols(void)
{
return MATRIX_COLS;
}

void matrix_init(void)
{
//debug_enable = true;
//debug_matrix = true;
//debug_mouse = true;
// initialize row and col
unselect_rows();
init_cols();

// initialize matrix state: all keys off
for (uint8_t i=0; i < MATRIX_ROWS; i++) {
matrix[i] = 0;
matrix_debouncing[i] = 0;
}
}

uint8_t matrix_scan(void)
{
for (uint8_t i = 0; i < MATRIX_ROWS; i++) {
select_row(i);
_delay_us(30); // without this wait read unstable value.
matrix_row_t cols = read_cols();
//Serial.println(cols, BIN);
if (matrix_debouncing[i] != cols) {
matrix_debouncing[i] = cols;
if (debouncing) {
debug("bounce!: "); debug_hex(debouncing); debug("\n");
}
debouncing = DEBOUNCE;
}
unselect_rows();
}

if (debouncing) {
if (--debouncing) {
_delay_ms(1);
} else {
for (uint8_t i = 0; i < MATRIX_ROWS; i++) {
matrix[i] = matrix_debouncing[i];
}
}
}

return 1;
}

bool matrix_is_modified(void)
{
if (debouncing) return false;
return true;
}

inline
bool matrix_is_on(uint8_t row, uint8_t col)
{
return (matrix[row] & ((matrix_row_t)1<<col));
}

inline
matrix_row_t matrix_get_row(uint8_t row)
{
return matrix[row];
}

// TODO update this comment
/* Column pin configuration
* col: 0 1 2 3 4 5
* pin: D3 D4 D5 D6 D7 B0
*/
static void init_cols(void)
{
// Input with pull-up(DDR:0, PORT:1)
DDRD &= ~(1<<3 | 1<<4 | 1<<5 | 1<<6 | 1<<7);
PORTD |= (1<<3 | 1<<4 | 1<<5 | 1<<6 | 1<<7);

DDRB &= ~(1<<0);
PORTB |= (1<<0);
}

static matrix_row_t read_cols(void)
{
return (PIND&(1<<3) ? 0 : (1<<0)) |
(PIND&(1<<4) ? 0 : (1<<1)) |
(PIND&(1<<5) ? 0 : (1<<2)) |
(PIND&(1<<6) ? 0 : (1<<3)) |
(PIND&(1<<7) ? 0 : (1<<4)) |
(PINB&(1<<0) ? 0 : (1<<5));
}

/* Row pin configuration
* row: 0 1 2 3