reform

MNT Reform: Open Source Portable Computer
Log (Feed) | Files | Refs (Tags) | README

commit e85006fcbef44dfeb1df1b0800f72f38cdffd30d
parent e55f842adcccb6fc82e734d92e45db74441f523f
Author: mntmn <lukas@mntmn.com>
Date:   Wed, 20 Nov 2019 21:14:41 +0100

WIP keyboard2 test: add i2c and ssd1306 code

Diffstat:
Mreform-keyboard2-fw/Keyboard.c | 10+++++++++-
Mreform-keyboard2-fw/Makefile | 2+-
Areform-keyboard2-fw/i2c.c | 159+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Areform-keyboard2-fw/i2c.h | 46++++++++++++++++++++++++++++++++++++++++++++++
Areform-keyboard2-fw/ssd1306.c | 527+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Areform-keyboard2-fw/ssd1306.h | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 832 insertions(+), 2 deletions(-)

diff --git a/reform-keyboard2-fw/Keyboard.c b/reform-keyboard2-fw/Keyboard.c @@ -103,10 +103,12 @@ void SetupHardware() PORTF = 0b01111111; PORTC = 0b01000000; - // disable JTAG MCUCR |=(1<<JTD); MCUCR |=(1<<JTD); + + iota_gfx_init(); + iota_gfx_write("Hello."); } /** Event handler for the library USB Connection event. */ @@ -320,6 +322,7 @@ bool CALLBACK_HID_Device_CreateHIDReport(USB_ClassInfo_HID_Device_t* const HIDIn { USB_KeyboardReport_Data_t* KeyboardReport = (USB_KeyboardReport_Data_t*)ReportData; uint8_t metaPressedNow = 0; + uint8_t keyPressedNow = 0; // how many keys are pressed this round uint8_t usedKeyCodes = 0; @@ -370,6 +373,7 @@ bool CALLBACK_HID_Device_CreateHIDReport(USB_ClassInfo_HID_Device_t* const HIDIn metaPressedNow = 1; } else { KeyboardReport->KeyCode[usedKeyCodes++] = keycode; + keyPressedNow = keycode; } } } @@ -385,6 +389,10 @@ bool CALLBACK_HID_Device_CreateHIDReport(USB_ClassInfo_HID_Device_t* const HIDIn } metaPressed = metaPressedNow; + + if (keyPressedNow) { + iota_gfx_write_char('!'); + } *ReportSize = sizeof(USB_KeyboardReport_Data_t); return false; diff --git a/reform-keyboard2-fw/Makefile b/reform-keyboard2-fw/Makefile @@ -18,7 +18,7 @@ F_CPU = 16000000 F_USB = $(F_CPU) OPTIMIZATION = s TARGET = Keyboard -SRC = $(TARGET).c Descriptors.c $(LUFA_SRC_USB) $(LUFA_SRC_USBCLASS) +SRC = $(TARGET).c Descriptors.c i2c.c ssd1306.c $(LUFA_SRC_USB) $(LUFA_SRC_USBCLASS) LUFA_PATH = ./lufa-master/LUFA CC_FLAGS = -DUSE_LUFA_CONFIG_HEADER $(REFORM_KBD_OPTIONS) -IConfig/ LD_FLAGS = diff --git a/reform-keyboard2-fw/i2c.c b/reform-keyboard2-fw/i2c.c @@ -0,0 +1,159 @@ +#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" + +// 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 μC 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) + +volatile uint8_t i2c_slave_buffer[SLAVE_BUFFER_SIZE]; + +static volatile uint8_t slave_buffer_pos; +static volatile bool slave_has_register_set = false; + +// 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); +} + +// Setup twi to run at 100kHz or 400kHz (see ./i2c.h SCL_CLOCK) +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_master_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_master_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_master_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_master_read(int ack) { + TWCR = (1<<TWINT) | (1<<TWEN) | (ack<<TWEA); + + i2c_delay(); + return TWDR; +} + +void i2c_reset_state(void) { + TWCR = 0; +} + +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); +} diff --git a/reform-keyboard2-fw/i2c.h b/reform-keyboard2-fw/i2c.h @@ -0,0 +1,46 @@ +#pragma once + +#include <stdint.h> + +#ifndef F_CPU +#define F_CPU 16000000UL +#endif + +#define I2C_READ 1 +#define I2C_WRITE 0 + +#define I2C_ACK 1 +#define I2C_NACK 0 + +#define SLAVE_BUFFER_SIZE 0x10 + +// i2c SCL clock frequency 400kHz +#define SCL_CLOCK 400000L + +extern volatile uint8_t i2c_slave_buffer[SLAVE_BUFFER_SIZE]; + +void i2c_master_init(void); +uint8_t i2c_master_start(uint8_t address); +void i2c_master_stop(void); +uint8_t i2c_master_write(uint8_t data); +uint8_t i2c_master_read(int); +void i2c_reset_state(void); +void i2c_slave_init(uint8_t address); + + +static inline unsigned char i2c_start_read(unsigned char addr) { + return i2c_master_start((addr << 1) | I2C_READ); +} + +static inline unsigned char i2c_start_write(unsigned char addr) { + return i2c_master_start((addr << 1) | I2C_WRITE); +} + +// from SSD1306 scrips +extern unsigned char i2c_rep_start(unsigned char addr); +extern void i2c_start_wait(unsigned char addr); +extern unsigned char i2c_readAck(void); +extern unsigned char i2c_readNak(void); +extern unsigned char i2c_read(unsigned char ack); + +#define i2c_read(ack) (ack) ? i2c_readAck() : i2c_readNak(); diff --git a/reform-keyboard2-fw/ssd1306.c b/reform-keyboard2-fw/ssd1306.c @@ -0,0 +1,527 @@ +#include "ssd1306.h" +#include "i2c.h" +#include <string.h> + #include <avr/pgmspace.h> +//#include "print.h" +//#include "timer.h" + +const unsigned char font[] PROGMEM = { +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x3E, 0x5B, 0x4F, 0x5B, 0x3E, 0x00, +0x3E, 0x6B, 0x4F, 0x6B, 0x3E, 0x00, +0x1C, 0x3E, 0x7C, 0x3E, 0x1C, 0x00, +0x18, 0x3C, 0x7E, 0x3C, 0x18, 0x00, +0x1C, 0x57, 0x7D, 0x57, 0x1C, 0x00, +0x1C, 0x5E, 0x7F, 0x5E, 0x1C, 0x00, +0x00, 0x18, 0x3C, 0x18, 0x00, 0x00, +0xFF, 0xE7, 0xC3, 0xE7, 0xFF, 0x00, +0x00, 0x18, 0x24, 0x18, 0x00, 0x00, +0xFF, 0xE7, 0xDB, 0xE7, 0xFF, 0x00, +0x30, 0x48, 0x3A, 0x06, 0x0E, 0x00, +0x26, 0x29, 0x79, 0x29, 0x26, 0x00, +0x40, 0x7F, 0x05, 0x05, 0x07, 0x00, +0x40, 0x7F, 0x05, 0x25, 0x3F, 0x00, +0x5A, 0x3C, 0xE7, 0x3C, 0x5A, 0x00, +0x7F, 0x3E, 0x1C, 0x1C, 0x08, 0x00, +0x08, 0x1C, 0x1C, 0x3E, 0x7F, 0x00, +0x14, 0x22, 0x7F, 0x22, 0x14, 0x00, +0x5F, 0x5F, 0x00, 0x5F, 0x5F, 0x00, +0x06, 0x09, 0x7F, 0x01, 0x7F, 0x00, +0x00, 0x66, 0x89, 0x95, 0x6A, 0x00, +0x60, 0x60, 0x60, 0x60, 0x60, 0x00, +0x94, 0xA2, 0xFF, 0xA2, 0x94, 0x00, +0x08, 0x04, 0x7E, 0x04, 0x08, 0x00, +0x10, 0x20, 0x7E, 0x20, 0x10, 0x00, +0x08, 0x08, 0x2A, 0x1C, 0x08, 0x00, +0x08, 0x1C, 0x2A, 0x08, 0x08, 0x00, +0x1E, 0x10, 0x10, 0x10, 0x10, 0x00, +0x0C, 0x1E, 0x0C, 0x1E, 0x0C, 0x00, +0x30, 0x38, 0x3E, 0x38, 0x30, 0x00, +0x06, 0x0E, 0x3E, 0x0E, 0x06, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, +0x00, 0x07, 0x00, 0x07, 0x00, 0x00, +0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00, +0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x00, +0x23, 0x13, 0x08, 0x64, 0x62, 0x00, +0x36, 0x49, 0x56, 0x20, 0x50, 0x00, +0x00, 0x08, 0x07, 0x03, 0x00, 0x00, +0x00, 0x1C, 0x22, 0x41, 0x00, 0x00, +0x00, 0x41, 0x22, 0x1C, 0x00, 0x00, +0x2A, 0x1C, 0x7F, 0x1C, 0x2A, 0x00, +0x08, 0x08, 0x3E, 0x08, 0x08, 0x00, +0x00, 0x80, 0x70, 0x30, 0x00, 0x00, +0x08, 0x08, 0x08, 0x08, 0x08, 0x00, +0x00, 0x00, 0x60, 0x60, 0x00, 0x00, +0x20, 0x10, 0x08, 0x04, 0x02, 0x00, +0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00, +0x00, 0x42, 0x7F, 0x40, 0x00, 0x00, +0x72, 0x49, 0x49, 0x49, 0x46, 0x00, +0x21, 0x41, 0x49, 0x4D, 0x33, 0x00, +0x18, 0x14, 0x12, 0x7F, 0x10, 0x00, +0x27, 0x45, 0x45, 0x45, 0x39, 0x00, +0x3C, 0x4A, 0x49, 0x49, 0x31, 0x00, +0x41, 0x21, 0x11, 0x09, 0x07, 0x00, +0x36, 0x49, 0x49, 0x49, 0x36, 0x00, +0x46, 0x49, 0x49, 0x29, 0x1E, 0x00, +0x00, 0x00, 0x14, 0x00, 0x00, 0x00, +0x00, 0x40, 0x34, 0x00, 0x00, 0x00, +0x00, 0x08, 0x14, 0x22, 0x41, 0x00, +0x14, 0x14, 0x14, 0x14, 0x14, 0x00, +0x00, 0x41, 0x22, 0x14, 0x08, 0x00, +0x02, 0x01, 0x59, 0x09, 0x06, 0x00, +0x3E, 0x41, 0x5D, 0x59, 0x4E, 0x00, +0x7C, 0x12, 0x11, 0x12, 0x7C, 0x00, +0x7F, 0x49, 0x49, 0x49, 0x36, 0x00, +0x3E, 0x41, 0x41, 0x41, 0x22, 0x00, +0x7F, 0x41, 0x41, 0x41, 0x3E, 0x00, +0x7F, 0x49, 0x49, 0x49, 0x41, 0x00, +0x7F, 0x09, 0x09, 0x09, 0x01, 0x00, +0x3E, 0x41, 0x41, 0x51, 0x73, 0x00, +0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00, +0x00, 0x41, 0x7F, 0x41, 0x00, 0x00, +0x20, 0x40, 0x41, 0x3F, 0x01, 0x00, +0x7F, 0x08, 0x14, 0x22, 0x41, 0x00, +0x7F, 0x40, 0x40, 0x40, 0x40, 0x00, +0x7F, 0x02, 0x1C, 0x02, 0x7F, 0x00, +0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00, +0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00, +0x7F, 0x09, 0x09, 0x09, 0x06, 0x00, +0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00, +0x7F, 0x09, 0x19, 0x29, 0x46, 0x00, +0x26, 0x49, 0x49, 0x49, 0x32, 0x00, +0x03, 0x01, 0x7F, 0x01, 0x03, 0x00, +0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00, +0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00, +0x3F, 0x40, 0x38, 0x40, 0x3F, 0x00, +0x63, 0x14, 0x08, 0x14, 0x63, 0x00, +0x03, 0x04, 0x78, 0x04, 0x03, 0x00, +0x61, 0x59, 0x49, 0x4D, 0x43, 0x00, +0x00, 0x7F, 0x41, 0x41, 0x41, 0x00, +0x02, 0x04, 0x08, 0x10, 0x20, 0x00, +0x00, 0x41, 0x41, 0x41, 0x7F, 0x00, +0x04, 0x02, 0x01, 0x02, 0x04, 0x00, +0x40, 0x40, 0x40, 0x40, 0x40, 0x00, +0x00, 0x03, 0x07, 0x08, 0x00, 0x00, +0x20, 0x54, 0x54, 0x78, 0x40, 0x00, +0x7F, 0x28, 0x44, 0x44, 0x38, 0x00, +0x38, 0x44, 0x44, 0x44, 0x28, 0x00, +0x38, 0x44, 0x44, 0x28, 0x7F, 0x00, +0x38, 0x54, 0x54, 0x54, 0x18, 0x00, +0x00, 0x08, 0x7E, 0x09, 0x02, 0x00, +0x18, 0x24, 0x24, 0x1C, 0x78, 0x00, +0x7F, 0x08, 0x04, 0x04, 0x78, 0x00, +0x00, 0x44, 0x7D, 0x40, 0x00, 0x00, +0x20, 0x40, 0x40, 0x3D, 0x00, 0x00, +0x7F, 0x10, 0x28, 0x44, 0x00, 0x00, +0x00, 0x41, 0x7F, 0x40, 0x00, 0x00, +0x7C, 0x04, 0x78, 0x04, 0x78, 0x00, +0x7C, 0x08, 0x04, 0x04, 0x78, 0x00, +0x38, 0x44, 0x44, 0x44, 0x38, 0x00, +0x7C, 0x18, 0x24, 0x24, 0x18, 0x00, +0x18, 0x24, 0x24, 0x18, 0x7C, 0x00, +0x7C, 0x08, 0x04, 0x04, 0x08, 0x00, +0x48, 0x54, 0x54, 0x54, 0x24, 0x00, +0x04, 0x04, 0x3F, 0x44, 0x24, 0x00, +0x3C, 0x40, 0x40, 0x20, 0x7C, 0x00, +0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00, +0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00, +0x44, 0x28, 0x10, 0x28, 0x44, 0x00, +0x4C, 0x90, 0x90, 0x90, 0x7C, 0x00, +0x44, 0x64, 0x54, 0x4C, 0x44, 0x00, +0x00, 0x08, 0x36, 0x41, 0x00, 0x00, +0x00, 0x00, 0x77, 0x00, 0x00, 0x00, +0x00, 0x41, 0x36, 0x08, 0x00, 0x00, +0x02, 0x01, 0x02, 0x04, 0x02, 0x00, +0x3C, 0x26, 0x23, 0x26, 0x3C, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xC0, 0xE0, +0xF0, 0xF8, 0xF8, 0x18, 0x00, 0xC0, +0xF0, 0xFC, 0xFE, 0xFF, 0xFF, 0xFF, +0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x80, 0xC0, 0xE0, 0xE0, +0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, +0xC0, 0x80, 0x00, 0x00, 0x00, 0x00, +0x80, 0xC0, 0xE0, 0xE0, 0xE0, 0xE0, +0xE0, 0xE0, 0xE0, 0xE0, 0xC0, 0x80, +0x00, 0x00, 0x00, 0xE0, 0xE0, 0xC0, +0xC0, 0xE0, 0xE0, 0xE0, 0xE0, 0x00, +0x00, 0xE0, 0xE0, 0xC0, 0xC0, 0xE0, +0xE0, 0xE0, 0xE0, 0xE0, 0xC0, 0x80, +0x00, 0x00, 0x00, 0x00, 0x80, 0xC0, +0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, +0xE0, 0xE0, 0xC0, 0x80, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0xE0, 0xF0, 0xF0, 0xF0, 0xE0, 0xEC, +0xEE, 0xF7, 0xF3, 0x70, 0x20, 0x00, +0x7C, 0x7C, 0x7C, 0x7E, 0x00, 0x7E, +0x7E, 0x7E, 0x7F, 0x7F, 0x7F, 0x00, +0x00, 0x80, 0xC0, 0xE0, 0x7E, 0x5B, +0x4F, 0x5B, 0xFE, 0xC0, 0x00, 0x00, +0xC0, 0x00, 0xDC, 0xD7, 0xDE, 0xDE, +0xDE, 0xD7, 0xDC, 0x00, 0xC0, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0xF8, 0xFC, 0xFE, +0xFF, 0xE0, 0x00, 0xFF, 0xFF, 0xFF, +0xFF, 0xFF, 0xFF, 0x80, 0xFF, 0xFF, +0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +0xFF, 0x1F, 0x07, 0x01, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0xFF, 0xFF, 0xFF, 0x81, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x81, +0xC3, 0xC3, 0xC3, 0x00, 0x00, 0xFF, +0xFF, 0xFF, 0x81, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x81, 0xFF, 0xFF, +0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, +0x01, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0xFF, 0xFF, 0xFF, 0x01, 0x00, +0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, +0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, +0x9D, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, +0x1C, 0x9D, 0xDF, 0xDF, 0xDF, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x0F, 0x1F, 0x3F, 0x7F, 0x7F, 0x7F, +0x7F, 0x7F, 0x3F, 0x1E, 0x0C, 0x00, +0x1F, 0x1F, 0x1F, 0x3F, 0x00, 0x3F, +0x3F, 0x3F, 0x7F, 0x7F, 0x7F, 0x00, +0x30, 0x7B, 0x7F, 0x78, 0x30, 0x20, +0x20, 0x30, 0x78, 0x7F, 0x3B, 0x00, +0x03, 0x00, 0x0F, 0x7F, 0x0F, 0x0F, +0x0F, 0x7F, 0x0F, 0x00, 0x03, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x03, 0x0F, 0x1F, +0x3F, 0x3F, 0x3F, 0x3F, 0x1F, 0x1F, +0x3F, 0x3F, 0x7F, 0x7F, 0x7F, 0x3F, +0x3F, 0x1F, 0x3F, 0x7F, 0x7F, 0x7F, +0x7F, 0x7C, 0x78, 0x78, 0x38, 0x1C, +0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x01, 0x03, 0x07, 0x07, +0x07, 0x07, 0x07, 0x07, 0x07, 0x07, +0x03, 0x01, 0x00, 0x00, 0x00, 0x00, +0x01, 0x03, 0x07, 0x07, 0x07, 0x07, +0x07, 0x07, 0x07, 0x07, 0x03, 0x01, +0x00, 0x00, 0x00, 0x07, 0x07, 0x07, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x07, 0x07, 0x07, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x07, 0x07, +0x07, 0x00, 0x00, 0x00, 0x01, 0x03, +0x07, 0x07, 0x07, 0x07, 0x07, 0x07, +0x07, 0x07, 0x03, 0x01, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + + +//static uint16_t last_battery_update; +//static uint32_t vbat; +//#define BatteryUpdateInterval 10000 /* milliseconds */ + +// 'last_flush' is declared as uint16_t, +// so this must be less than 65535 +#define ScreenOffInterval 60000 /* milliseconds */ +static uint16_t last_flush; + +static bool force_dirty = true; + +// Write command sequence. +// Returns true on success. +static inline bool _send_cmd1(uint8_t cmd) { + bool res = false; + + if (i2c_start_write(SSD1306_ADDRESS)) { + //xprintf("failed to start write to %d\n", SSD1306_ADDRESS); + goto done; + } + + if (i2c_master_write(0x0 /* command byte follows */)) { + //print("failed to write control byte\n"); + + goto done; + } + + if (i2c_master_write(cmd)) { + //xprintf("failed to write command %d\n", cmd); + goto done; + } + res = true; +done: + i2c_master_stop(); + return res; +} + +// Write 2-byte command sequence. +// Returns true on success +static inline bool _send_cmd2(uint8_t cmd, uint8_t opr) { + if (!_send_cmd1(cmd)) { + return false; + } + return _send_cmd1(opr); +} + +// Write 3-byte command sequence. +// Returns true on success +static inline bool _send_cmd3(uint8_t cmd, uint8_t opr1, uint8_t opr2) { + if (!_send_cmd1(cmd)) { + return false; + } + if (!_send_cmd1(opr1)) { + return false; + } + return _send_cmd1(opr2); +} + +#define send_cmd1(c) if (!_send_cmd1(c)) {goto done;} +#define send_cmd2(c,o) if (!_send_cmd2(c,o)) {goto done;} +#define send_cmd3(c,o1,o2) if (!_send_cmd3(c,o1,o2)) {goto done;} + +static void clear_display(void) { + matrix_clear(&display); + + // Clear all of the display bits (there can be random noise + // in the RAM on startup) + send_cmd3(PageAddr, 0, (DisplayHeight / 8) - 1); + send_cmd3(ColumnAddr, 0, DisplayWidth - 1); + + if (i2c_start_write(SSD1306_ADDRESS)) { + goto done; + } + if (i2c_master_write(0x40)) { + // Data mode + goto done; + } + for (uint8_t row = 0; row < MatrixRows; ++row) { + for (uint8_t col = 0; col < DisplayWidth; ++col) { + i2c_master_write(0); + } + } + + display.dirty = false; + +done: + i2c_master_stop(); +} + +bool iota_gfx_init(bool rotate) { + bool success = false; + + i2c_master_init(); + send_cmd1(DisplayOff); + send_cmd2(SetDisplayClockDiv, 0x80); + send_cmd2(SetMultiPlex, DisplayHeight - 1); + + send_cmd2(SetDisplayOffset, 0); + + send_cmd1(SetStartLine | 0x0); + send_cmd2(SetChargePump, 0x14 /* Enable */); + send_cmd2(SetMemoryMode, 0 /* horizontal addressing */); + + if (rotate) { + // the following Flip the display orientation 180 degrees + send_cmd1(SegRemap); + send_cmd1(ComScanInc); + } else { + // Flips the display orientation 0 degrees + send_cmd1(SegRemap | 0x1); + send_cmd1(ComScanDec); + } + + send_cmd2(SetComPins, 0x2); + send_cmd2(SetContrast, 0x8f); + send_cmd2(SetPreCharge, 0xf1); + send_cmd2(SetVComDetect, 0x40); + send_cmd1(DisplayAllOnResume); + send_cmd1(NormalDisplay); + send_cmd1(DeActivateScroll); + send_cmd1(DisplayOn); + + send_cmd2(SetContrast, 0); // Dim + + clear_display(); + + success = true; + + iota_gfx_flush(); + +done: + return success; +} + +bool iota_gfx_off(void) { + bool success = false; + + send_cmd1(DisplayOff); + success = true; + +done: + return success; +} + +bool iota_gfx_on(void) { + bool success = false; + + send_cmd1(DisplayOn); + success = true; + +done: + return success; +} + +void matrix_write_char_inner(struct CharacterMatrix *matrix, uint8_t c) { + *matrix->cursor = c; + ++matrix->cursor; + + if (matrix->cursor - &matrix->display[0][0] == sizeof(matrix->display)) { + // We went off the end; scroll the display upwards by one line + memmove(&matrix->display[0], &matrix->display[1], + MatrixCols * (MatrixRows - 1)); + matrix->cursor = &matrix->display[MatrixRows - 1][0]; + memset(matrix->cursor, ' ', MatrixCols); + } +} + +void matrix_write_char(struct CharacterMatrix *matrix, uint8_t c) { + matrix->dirty = true; + + if (c == '\n') { + // Clear to end of line from the cursor and then move to the + // start of the next line + uint8_t cursor_col = (matrix->cursor - &matrix->display[0][0]) % MatrixCols; + + while (cursor_col++ < MatrixCols) { + matrix_write_char_inner(matrix, ' '); + } + return; + } + + matrix_write_char_inner(matrix, c); +} + +void iota_gfx_write_char(uint8_t c) { + matrix_write_char(&display, c); +} + +void matrix_write(struct CharacterMatrix *matrix, const char *data) { + const char *end = data + strlen(data); + while (data < end) { + matrix_write_char(matrix, *data); + ++data; + } +} + +void matrix_write_ln(struct CharacterMatrix *matrix, const char *data) { + char data_ln[strlen(data)+2]; + snprintf(data_ln, sizeof(data_ln), "%s\n", data); + matrix_write(matrix, data_ln); +} + +void iota_gfx_write(const char *data) { + matrix_write(&display, data); +} + +void matrix_write_P(struct CharacterMatrix *matrix, const char *data) { + while (true) { + uint8_t c = pgm_read_byte(data); + if (c == 0) { + return; + } + matrix_write_char(matrix, c); + ++data; + } +} + +void iota_gfx_write_P(const char *data) { + matrix_write_P(&display, data); +} + +void matrix_clear(struct CharacterMatrix *matrix) { + memset(matrix->display, ' ', sizeof(matrix->display)); + matrix->cursor = &matrix->display[0][0]; + matrix->dirty = true; +} + +void iota_gfx_clear_screen(void) { + matrix_clear(&display); +} + +void matrix_render(struct CharacterMatrix *matrix) { + //last_flush = timer_read(); + iota_gfx_on(); + + // Move to the home position + send_cmd3(PageAddr, 0, MatrixRows - 1); + send_cmd3(ColumnAddr, 0, (MatrixCols * FontWidth) - 1); + + if (i2c_start_write(SSD1306_ADDRESS)) { + goto done; + } + if (i2c_master_write(0x40)) { + // Data mode + goto done; + } + + for (uint8_t row = 0; row < MatrixRows; ++row) { + for (uint8_t col = 0; col < MatrixCols; ++col) { + const uint8_t *glyph = font + (matrix->display[row][col] * FontWidth); + + for (uint8_t glyphCol = 0; glyphCol < FontWidth; ++glyphCol) { + uint8_t colBits = pgm_read_byte(glyph + glyphCol); + i2c_master_write(colBits); + } + + // 1 column of space between chars (it's not included in the glyph) + //i2c_master_write(0); + } + } + + matrix->dirty = false; + +done: + i2c_master_stop(); +} + +void iota_gfx_flush(void) { + matrix_render(&display); +} + +__attribute__ ((weak)) +void iota_gfx_task_user(void) { +} + +void iota_gfx_task(void) { + iota_gfx_task_user(); + + if (display.dirty|| force_dirty) { + iota_gfx_flush(); + force_dirty = false; + } + + //if (timer_elapsed(last_flush) > ScreenOffInterval) { + // iota_gfx_off(); + //} +} + +//bool process_record_gfx(uint16_t keycode, keyrecord_t *record) { +// force_dirty = true; +// return true; +//} diff --git a/reform-keyboard2-fw/ssd1306.h b/reform-keyboard2-fw/ssd1306.h @@ -0,0 +1,90 @@ +#pragma once + +#include <stdbool.h> +#include <stdio.h> +//#include "action.h" + +enum ssd1306_cmds { + DisplayOff = 0xAE, + DisplayOn = 0xAF, + + SetContrast = 0x81, + DisplayAllOnResume = 0xA4, + + DisplayAllOn = 0xA5, + NormalDisplay = 0xA6, + InvertDisplay = 0xA7, + SetDisplayOffset = 0xD3, + SetComPins = 0xda, + SetVComDetect = 0xdb, + SetDisplayClockDiv = 0xD5, + SetPreCharge = 0xd9, + SetMultiPlex = 0xa8, + SetLowColumn = 0x00, + SetHighColumn = 0x10, + SetStartLine = 0x40, + + SetMemoryMode = 0x20, + ColumnAddr = 0x21, + PageAddr = 0x22, + + ComScanInc = 0xc0, + ComScanDec = 0xc8, + SegRemap = 0xa0, + SetChargePump = 0x8d, + ExternalVcc = 0x01, + SwitchCapVcc = 0x02, + + ActivateScroll = 0x2f, + DeActivateScroll = 0x2e, + SetVerticalScrollArea = 0xa3, + RightHorizontalScroll = 0x26, + LeftHorizontalScroll = 0x27, + VerticalAndRightHorizontalScroll = 0x29, + VerticalAndLeftHorizontalScroll = 0x2a, +}; + +// Controls the SSD1306 128x32 OLED display via i2c + +#ifndef SSD1306_ADDRESS +#define SSD1306_ADDRESS 0x3C +#endif + +#define DisplayHeight 32 +#define DisplayWidth 128 + +#define FontHeight 8 +#define FontWidth 6 + +#define MatrixRows (DisplayHeight / FontHeight) +#define MatrixCols (DisplayWidth / FontWidth) + +struct CharacterMatrix { + uint8_t display[MatrixRows][MatrixCols]; + uint8_t *cursor; + bool dirty; +}; + +struct CharacterMatrix display; + +bool iota_gfx_init(bool rotate); +void iota_gfx_task(void); +bool iota_gfx_off(void); +bool iota_gfx_on(void); +void iota_gfx_flush(void); +void iota_gfx_write_char(uint8_t c); +void iota_gfx_write(const char *data); +void iota_gfx_write_P(const char *data); +void iota_gfx_clear_screen(void); + +void iota_gfx_task_user(void); + +void matrix_clear(struct CharacterMatrix *matrix); +void matrix_write_char_inner(struct CharacterMatrix *matrix, uint8_t c); +void matrix_write_char(struct CharacterMatrix *matrix, uint8_t c); +void matrix_write(struct CharacterMatrix *matrix, const char *data); +void matrix_write_ln(struct CharacterMatrix *matrix, const char *data); +void matrix_write_P(struct CharacterMatrix *matrix, const char *data); +void matrix_render(struct CharacterMatrix *matrix); + +//bool process_record_gfx(uint16_t keycode, keyrecord_t *record);