mirror of
https://github.com/qmk/qmk_firmware.git
synced 2026-09-30 13:58:57 +02:00
merge from master
This commit is contained in:
+30
-19
@@ -42,43 +42,54 @@ static const I2CConfig i2cconfig = {
|
||||
0
|
||||
};
|
||||
|
||||
static i2c_status_t chibios_to_qmk(const msg_t* status) {
|
||||
switch (*status) {
|
||||
case I2C_NO_ERROR:
|
||||
return I2C_STATUS_SUCCESS;
|
||||
case I2C_TIMEOUT:
|
||||
return I2C_STATUS_TIMEOUT;
|
||||
// I2C_BUS_ERROR, I2C_ARBITRATION_LOST, I2C_ACK_FAILURE, I2C_OVERRUN, I2C_PEC_ERROR, I2C_SMB_ALERT
|
||||
default:
|
||||
return I2C_STATUS_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__ ((weak))
|
||||
void i2c_init(void)
|
||||
{
|
||||
//palSetGroupMode(GPIOB, GPIOB_PIN6 | GPIOB_PIN7, 0, PAL_MODE_INPUT);
|
||||
|
||||
// Try releasing special pins for a short time
|
||||
palSetPadMode(GPIOB, 6, PAL_MODE_INPUT);
|
||||
palSetPadMode(GPIOB, 7, PAL_MODE_INPUT);
|
||||
palSetPadMode(I2C1_BANK, I2C1_SCL, PAL_MODE_INPUT);
|
||||
palSetPadMode(I2C1_BANK, I2C1_SDA, PAL_MODE_INPUT);
|
||||
|
||||
chThdSleepMilliseconds(10);
|
||||
|
||||
palSetPadMode(GPIOB, 6, PAL_MODE_ALTERNATE(4) | PAL_STM32_OTYPE_OPENDRAIN | PAL_STM32_PUPDR_PULLUP);
|
||||
palSetPadMode(GPIOB, 7, PAL_MODE_ALTERNATE(4) | PAL_STM32_OTYPE_OPENDRAIN | PAL_STM32_PUPDR_PULLUP);
|
||||
palSetPadMode(I2C1_BANK, I2C1_SCL, PAL_MODE_ALTERNATE(4) | PAL_STM32_OTYPE_OPENDRAIN);
|
||||
palSetPadMode(I2C1_BANK, I2C1_SDA, PAL_MODE_ALTERNATE(4) | PAL_STM32_OTYPE_OPENDRAIN);
|
||||
|
||||
//i2cInit(); //This is invoked by halInit() so no need to redo it.
|
||||
}
|
||||
|
||||
// This is usually not needed
|
||||
uint8_t i2c_start(uint8_t address)
|
||||
i2c_status_t i2c_start(uint8_t address)
|
||||
{
|
||||
i2c_address = address;
|
||||
i2cStart(&I2C_DRIVER, &i2cconfig);
|
||||
return 0;
|
||||
return I2C_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
uint8_t i2c_transmit(uint8_t address, uint8_t* data, uint16_t length, uint16_t timeout)
|
||||
i2c_status_t i2c_transmit(uint8_t address, const uint8_t* data, uint16_t length, uint16_t timeout)
|
||||
{
|
||||
i2c_address = address;
|
||||
i2cStart(&I2C_DRIVER, &i2cconfig);
|
||||
return i2cMasterTransmitTimeout(&I2C_DRIVER, (i2c_address >> 1), data, length, 0, 0, MS2ST(timeout));
|
||||
msg_t status = i2cMasterTransmitTimeout(&I2C_DRIVER, (i2c_address >> 1), data, length, 0, 0, MS2ST(timeout));
|
||||
return chibios_to_qmk(&status);
|
||||
}
|
||||
|
||||
uint8_t i2c_receive(uint8_t address, uint8_t* data, uint16_t length, uint16_t timeout)
|
||||
i2c_status_t i2c_receive(uint8_t address, uint8_t* data, uint16_t length, uint16_t timeout)
|
||||
{
|
||||
i2c_address = address;
|
||||
i2cStart(&I2C_DRIVER, &i2cconfig);
|
||||
return i2cMasterReceiveTimeout(&I2C_DRIVER, (i2c_address >> 1), data, length, MS2ST(timeout));
|
||||
msg_t status = i2cMasterReceiveTimeout(&I2C_DRIVER, (i2c_address >> 1), data, length, MS2ST(timeout));
|
||||
return chibios_to_qmk(&status);
|
||||
}
|
||||
|
||||
uint8_t i2c_transmit_receive(uint8_t address, uint8_t * tx_body, uint16_t tx_length, uint8_t * rx_body, uint16_t rx_length) {
|
||||
@@ -97,19 +108,19 @@ uint8_t i2c_writeReg(uint8_t devaddr, uint8_t regaddr, uint8_t* data, uint16_t l
|
||||
}
|
||||
complete_packet[0] = regaddr;
|
||||
|
||||
return i2cMasterTransmitTimeout(&I2C_DRIVER, (i2c_address >> 1), complete_packet, length + 1, 0, 0, MS2ST(timeout));
|
||||
msg_t status = i2cMasterTransmitTimeout(&I2C_DRIVER, (i2c_address >> 1), complete_packet, length + 1, 0, 0, MS2ST(timeout));
|
||||
return chibios_to_qmk(&status);
|
||||
}
|
||||
|
||||
uint8_t i2c_readReg(uint8_t devaddr, uint8_t* regaddr, uint8_t* data, uint16_t length, uint16_t timeout)
|
||||
i2c_status_t i2c_readReg(uint8_t devaddr, uint8_t* regaddr, uint8_t* data, uint16_t length, uint16_t timeout)
|
||||
{
|
||||
i2c_address = devaddr;
|
||||
i2cStart(&I2C_DRIVER, &i2cconfig);
|
||||
return i2cMasterTransmitTimeout(&I2C_DRIVER, (i2c_address >> 1), regaddr, 1, data, length, MS2ST(timeout));
|
||||
msg_t status = i2cMasterTransmitTimeout(&I2C_DRIVER, (i2c_address >> 1), regaddr, 1, data, length, MS2ST(timeout));
|
||||
return chibios_to_qmk(&status);
|
||||
}
|
||||
|
||||
// This is usually not needed. It releases the driver to allow pins to become GPIO again.
|
||||
uint8_t i2c_stop(uint16_t timeout)
|
||||
void i2c_stop(void)
|
||||
{
|
||||
i2cStop(&I2C_DRIVER);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -26,15 +26,31 @@
|
||||
#include "ch.h"
|
||||
#include <hal.h>
|
||||
|
||||
#ifndef I2C1_BANK
|
||||
#define I2C1_BANK GPIOB
|
||||
#endif
|
||||
#ifndef I2C1_SCL
|
||||
#define I2C1_SCL 6
|
||||
#endif
|
||||
#ifndef I2C1_SDA
|
||||
#define I2C1_SDA 7
|
||||
#endif
|
||||
|
||||
#ifndef I2C_DRIVER
|
||||
#define I2C_DRIVER I2CD1
|
||||
#endif
|
||||
|
||||
typedef int16_t i2c_status_t;
|
||||
|
||||
#define I2C_STATUS_SUCCESS (0)
|
||||
#define I2C_STATUS_ERROR (-1)
|
||||
#define I2C_STATUS_TIMEOUT (-2)
|
||||
|
||||
void i2c_init(void);
|
||||
uint8_t i2c_start(uint8_t address);
|
||||
uint8_t i2c_transmit(uint8_t address, uint8_t* data, uint16_t length, uint16_t timeout);
|
||||
uint8_t i2c_receive(uint8_t address, uint8_t* data, uint16_t length, uint16_t timeout);
|
||||
uint8_t i2c_transmit_receive(uint8_t address, uint8_t * tx_body, uint16_t tx_length, uint8_t * rx_body, uint16_t rx_length);
|
||||
uint8_t i2c_writeReg(uint8_t devaddr, uint8_t regaddr, uint8_t* data, uint16_t length, uint16_t timeout);
|
||||
uint8_t i2c_readReg(uint8_t devaddr, uint8_t* regaddr, uint8_t* data, uint16_t length, uint16_t timeout);
|
||||
uint8_t i2c_stop(uint16_t timeout);
|
||||
i2c_status_t i2c_start(uint8_t address);
|
||||
i2c_status_t i2c_transmit(uint8_t address, const uint8_t* data, uint16_t length, uint16_t timeout);
|
||||
i2c_status_t i2c_receive(uint8_t address, uint8_t* data, uint16_t length, uint16_t timeout);
|
||||
i2c_status_t i2c_transmit_receive(uint8_t address, uint8_t * tx_body, uint16_t tx_length, uint8_t * rx_body, uint16_t rx_length);
|
||||
i2c_status_t i2c_writeReg(uint8_t devaddr, uint8_t regaddr, const uint8_t* data, uint16_t length, uint16_t timeout);
|
||||
i2c_status_t i2c_readReg(uint8_t devaddr, uint8_t* regaddr, uint8_t* data, uint16_t length, uint16_t timeout);
|
||||
void i2c_stop(void);
|
||||
|
||||
Executable
+101
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* APA102 lib V1.0a
|
||||
*
|
||||
* Controls APA102 RGB-LEDs
|
||||
* Author: Mikkel (Duckle29 on github)
|
||||
*
|
||||
* Dec 22th, 2017 v1.0a Initial Version
|
||||
*
|
||||
* 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 "apa102.h"
|
||||
#include <avr/interrupt.h>
|
||||
#include <avr/io.h>
|
||||
#include <util/delay.h>
|
||||
#include "debug.h"
|
||||
|
||||
// Setleds for standard RGB
|
||||
void inline apa102_setleds(LED_TYPE *ledarray, uint16_t leds){
|
||||
apa102_setleds_pin(ledarray,leds, _BV(RGB_DI_PIN & 0xF), _BV(RGB_CLK_PIN & 0xF));
|
||||
}
|
||||
|
||||
void static inline apa102_setleds_pin(LED_TYPE *ledarray, uint16_t leds, uint8_t pinmask_DI, uint8_t pinmask_CLK){
|
||||
pinMode(RGB_DI_PIN, PinDirectionOutput);
|
||||
pinMode(RGB_CLK_PIN, PinDirectionOutput);
|
||||
|
||||
apa102_send_array((uint8_t*)ledarray,leds)
|
||||
}
|
||||
|
||||
void apa102_send_array(uint8_t *data, uint16_t leds){ // Data is struct of 3 bytes. RGB - leds is number of leds in data
|
||||
apa102_start_frame();
|
||||
while(leds--){
|
||||
apa102_send_frame(0xFF000000 | (data->b << 16) | (data->g << 8) | data->r);
|
||||
data++;
|
||||
}
|
||||
apa102_end_frame(leds);
|
||||
}
|
||||
|
||||
void apa102_send_frame(uint32_t frame){
|
||||
for(uint32_t i=0xFF; i>0;){
|
||||
apa102_send_byte(frame & i);
|
||||
i = i << 8;
|
||||
}
|
||||
}
|
||||
|
||||
void apa102_start_frame(){
|
||||
apa102_send_frame(0);
|
||||
}
|
||||
|
||||
void apa102_end_frame(uint16_t leds)
|
||||
{
|
||||
// This function has been taken from: https://github.com/pololu/apa102-arduino/blob/master/APA102.h
|
||||
// and adapted. The code is MIT licensed. I think thats compatible?
|
||||
|
||||
// We need to send some more bytes to ensure that all the LEDs in the
|
||||
// chain see their new color and start displaying it.
|
||||
//
|
||||
// The data stream seen by the last LED in the chain will be delayed by
|
||||
// (count - 1) clock edges, because each LED before it inverts the clock
|
||||
// line and delays the data by one clock edge. Therefore, to make sure
|
||||
// the last LED actually receives the data we wrote, the number of extra
|
||||
// edges we send at the end of the frame must be at least (count - 1).
|
||||
// For the APA102C, that is sufficient.
|
||||
//
|
||||
// The SK9822 only updates after it sees 32 zero bits followed by one more
|
||||
// rising edge. To avoid having the update time depend on the color of
|
||||
// the last LED, we send a dummy 0xFF byte. (Unfortunately, this means
|
||||
// that partial updates of the beginning of an LED strip are not possible;
|
||||
// the LED after the last one you are trying to update will be black.)
|
||||
// After that, to ensure that the last LED in the chain sees 32 zero bits
|
||||
// and a rising edge, we need to send at least 65 + (count - 1) edges. It
|
||||
// is sufficent and simpler to just send (5 + count/16) bytes of zeros.
|
||||
//
|
||||
// We are ignoring the specification for the end frame in the APA102/SK9822
|
||||
// datasheets because it does not actually ensure that all the LEDs will
|
||||
// start displaying their new colors right away.
|
||||
|
||||
apa102_send_byte(0xFF);
|
||||
for (uint16_t i = 0; i < 5 + leds / 16; i++){
|
||||
apa102_send_byte(0);
|
||||
}
|
||||
}
|
||||
|
||||
void apa102_send_byte(uint8_t byte){
|
||||
uint8_t i;
|
||||
for (i = 0; i < 8; i++){
|
||||
digitalWrite(RGB_DI_PIN, !!(byte & (1 << (7-i)));
|
||||
digitalWrite(RGB_CLK_PIN, PinLevelHigh);
|
||||
}
|
||||
}
|
||||
Executable
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* light weight WS2812 lib include
|
||||
*
|
||||
* Version 2.3 - Nev 29th 2015
|
||||
* Author: Tim (cpldcpu@gmail.com)
|
||||
*
|
||||
* Please do not change this file! All configuration is handled in "ws2812_config.h"
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <avr/io.h>
|
||||
#include <avr/interrupt.h>
|
||||
|
||||
#include "rgblight_types.h"
|
||||
|
||||
|
||||
/* User Interface
|
||||
*
|
||||
* Input:
|
||||
* ledarray: An array of GRB data describing the LED colors
|
||||
* number_of_leds: The number of LEDs to write
|
||||
* pinmask (optional): Bitmask describing the output bin. e.g. _BV(PB0)
|
||||
*
|
||||
* The functions will perform the following actions:
|
||||
* - Set the data-out pin as output
|
||||
* - Send out the LED data
|
||||
* - Wait 50�s to reset the LEDs
|
||||
*/
|
||||
|
||||
void apa102_setleds (LED_TYPE *ledarray, uint16_t number_of_leds);
|
||||
void apa102_setleds_pin (LED_TYPE *ledarray, uint16_t number_of_leds,uint8_t pinmask);
|
||||
void apa102_setleds_rgbw(LED_TYPE *ledarray, uint16_t number_of_leds);
|
||||
+80
-91
@@ -7,43 +7,56 @@
|
||||
|
||||
#include "i2c_master.h"
|
||||
#include "timer.h"
|
||||
#include "wait.h"
|
||||
|
||||
#ifndef F_SCL
|
||||
#define F_SCL 400000UL // SCL frequency
|
||||
# define F_SCL 400000UL // SCL frequency
|
||||
#endif
|
||||
#define Prescaler 1
|
||||
#define TWBR_val ((((F_CPU / F_SCL) / Prescaler) - 16 ) / 2)
|
||||
#define TWBR_val ((((F_CPU / F_SCL) / Prescaler) - 16) / 2)
|
||||
|
||||
void i2c_init(void)
|
||||
{
|
||||
TWSR = 0; /* no prescaler */
|
||||
void i2c_init(void) {
|
||||
TWSR = 0; /* no prescaler */
|
||||
TWBR = (uint8_t)TWBR_val;
|
||||
|
||||
#ifdef __AVR_ATmega32A__
|
||||
// set pull-up resistors on I2C bus pins
|
||||
PORTC |= 0b11;
|
||||
|
||||
// enable TWI (two-wire interface)
|
||||
TWCR |= (1 << TWEN);
|
||||
|
||||
// enable TWI interrupt and slave address ACK
|
||||
TWCR |= (1 << TWIE);
|
||||
TWCR |= (1 << TWEA);
|
||||
#endif
|
||||
}
|
||||
|
||||
i2c_status_t i2c_start(uint8_t address, uint16_t timeout)
|
||||
{
|
||||
i2c_status_t i2c_start(uint8_t address, uint16_t timeout) {
|
||||
// reset TWI control register
|
||||
TWCR = 0;
|
||||
// transmit START condition
|
||||
TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN);
|
||||
TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
|
||||
|
||||
uint16_t timeout_timer = timer_read();
|
||||
while( !(TWCR & (1<<TWINT)) ) {
|
||||
while (!(TWCR & (1 << TWINT))) {
|
||||
if ((timeout != I2C_TIMEOUT_INFINITE) && ((timer_read() - timeout_timer) >= timeout)) {
|
||||
return I2C_STATUS_TIMEOUT;
|
||||
}
|
||||
}
|
||||
|
||||
// check if the start condition was successfully transmitted
|
||||
if(((TW_STATUS & 0xF8) != TW_START) && ((TW_STATUS & 0xF8) != TW_REP_START)){ return I2C_STATUS_ERROR; }
|
||||
if (((TW_STATUS & 0xF8) != TW_START) && ((TW_STATUS & 0xF8) != TW_REP_START)) {
|
||||
return I2C_STATUS_ERROR;
|
||||
}
|
||||
|
||||
// load slave address into data register
|
||||
TWDR = address;
|
||||
// start transmission of address
|
||||
TWCR = (1<<TWINT) | (1<<TWEN);
|
||||
TWCR = (1 << TWINT) | (1 << TWEN);
|
||||
|
||||
timeout_timer = timer_read();
|
||||
while( !(TWCR & (1<<TWINT)) ) {
|
||||
while (!(TWCR & (1 << TWINT))) {
|
||||
if ((timeout != I2C_TIMEOUT_INFINITE) && ((timer_read() - timeout_timer) >= timeout)) {
|
||||
return I2C_STATUS_TIMEOUT;
|
||||
}
|
||||
@@ -51,38 +64,39 @@ i2c_status_t i2c_start(uint8_t address, uint16_t timeout)
|
||||
|
||||
// check if the device has acknowledged the READ / WRITE mode
|
||||
uint8_t twst = TW_STATUS & 0xF8;
|
||||
if ( (twst != TW_MT_SLA_ACK) && (twst != TW_MR_SLA_ACK) ) return I2C_STATUS_ERROR;
|
||||
if ((twst != TW_MT_SLA_ACK) && (twst != TW_MR_SLA_ACK)) {
|
||||
return I2C_STATUS_ERROR;
|
||||
}
|
||||
|
||||
return I2C_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
i2c_status_t i2c_write(uint8_t data, uint16_t timeout)
|
||||
{
|
||||
i2c_status_t i2c_write(uint8_t data, uint16_t timeout) {
|
||||
// load data into data register
|
||||
TWDR = data;
|
||||
// start transmission of data
|
||||
TWCR = (1<<TWINT) | (1<<TWEN);
|
||||
TWCR = (1 << TWINT) | (1 << TWEN);
|
||||
|
||||
uint16_t timeout_timer = timer_read();
|
||||
while( !(TWCR & (1<<TWINT)) ) {
|
||||
while (!(TWCR & (1 << TWINT))) {
|
||||
if ((timeout != I2C_TIMEOUT_INFINITE) && ((timer_read() - timeout_timer) >= timeout)) {
|
||||
return I2C_STATUS_TIMEOUT;
|
||||
}
|
||||
}
|
||||
|
||||
if( (TW_STATUS & 0xF8) != TW_MT_DATA_ACK ){ return I2C_STATUS_ERROR; }
|
||||
if ((TW_STATUS & 0xF8) != TW_MT_DATA_ACK) {
|
||||
return I2C_STATUS_ERROR;
|
||||
}
|
||||
|
||||
return I2C_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
int16_t i2c_read_ack(uint16_t timeout)
|
||||
{
|
||||
|
||||
int16_t i2c_read_ack(uint16_t timeout) {
|
||||
// start TWI module and acknowledge data after reception
|
||||
TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWEA);
|
||||
TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWEA);
|
||||
|
||||
uint16_t timeout_timer = timer_read();
|
||||
while( !(TWCR & (1<<TWINT)) ) {
|
||||
while (!(TWCR & (1 << TWINT))) {
|
||||
if ((timeout != I2C_TIMEOUT_INFINITE) && ((timer_read() - timeout_timer) >= timeout)) {
|
||||
return I2C_STATUS_TIMEOUT;
|
||||
}
|
||||
@@ -92,14 +106,12 @@ int16_t i2c_read_ack(uint16_t timeout)
|
||||
return TWDR;
|
||||
}
|
||||
|
||||
int16_t i2c_read_nack(uint16_t timeout)
|
||||
{
|
||||
|
||||
int16_t i2c_read_nack(uint16_t timeout) {
|
||||
// start receiving without acknowledging reception
|
||||
TWCR = (1<<TWINT) | (1<<TWEN);
|
||||
TWCR = (1 << TWINT) | (1 << TWEN);
|
||||
|
||||
uint16_t timeout_timer = timer_read();
|
||||
while( !(TWCR & (1<<TWINT)) ) {
|
||||
while (!(TWCR & (1 << TWINT))) {
|
||||
if ((timeout != I2C_TIMEOUT_INFINITE) && ((timer_read() - timeout_timer) >= timeout)) {
|
||||
return I2C_STATUS_TIMEOUT;
|
||||
}
|
||||
@@ -109,112 +121,89 @@ int16_t i2c_read_nack(uint16_t timeout)
|
||||
return TWDR;
|
||||
}
|
||||
|
||||
i2c_status_t i2c_transmit(uint8_t address, uint8_t* data, uint16_t length, uint16_t timeout)
|
||||
{
|
||||
i2c_status_t i2c_transmit(uint8_t address, const uint8_t* data, uint16_t length, uint16_t timeout) {
|
||||
i2c_status_t status = i2c_start(address | I2C_WRITE, timeout);
|
||||
if (status) return status;
|
||||
|
||||
for (uint16_t i = 0; i < length; i++) {
|
||||
for (uint16_t i = 0; i < length && status >= 0; i++) {
|
||||
status = i2c_write(data[i], timeout);
|
||||
if (status) return status;
|
||||
}
|
||||
|
||||
status = i2c_stop(timeout);
|
||||
if (status) return status;
|
||||
i2c_stop();
|
||||
|
||||
return I2C_STATUS_SUCCESS;
|
||||
return status;
|
||||
}
|
||||
|
||||
i2c_status_t i2c_receive(uint8_t address, uint8_t* data, uint16_t length, uint16_t timeout)
|
||||
{
|
||||
i2c_status_t i2c_receive(uint8_t address, uint8_t* data, uint16_t length, uint16_t timeout) {
|
||||
i2c_status_t status = i2c_start(address | I2C_READ, timeout);
|
||||
if (status) return status;
|
||||
|
||||
for (uint16_t i = 0; i < (length-1); i++) {
|
||||
for (uint16_t i = 0; i < (length - 1) && status >= 0; i++) {
|
||||
status = i2c_read_ack(timeout);
|
||||
if (status >= 0) {
|
||||
data[i] = status;
|
||||
} else {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
status = i2c_read_nack(timeout);
|
||||
if (status >= 0 ) {
|
||||
data[(length-1)] = status;
|
||||
} else {
|
||||
return status;
|
||||
if (status >= 0) {
|
||||
status = i2c_read_nack(timeout);
|
||||
if (status >= 0) {
|
||||
data[(length - 1)] = status;
|
||||
}
|
||||
}
|
||||
|
||||
status = i2c_stop(timeout);
|
||||
if (status) return status;
|
||||
i2c_stop();
|
||||
|
||||
return I2C_STATUS_SUCCESS;
|
||||
return (status < 0) ? status : I2C_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
i2c_status_t i2c_writeReg(uint8_t devaddr, uint8_t regaddr, uint8_t* data, uint16_t length, uint16_t timeout)
|
||||
{
|
||||
i2c_status_t i2c_writeReg(uint8_t devaddr, uint8_t regaddr, const uint8_t* data, uint16_t length, uint16_t timeout) {
|
||||
i2c_status_t status = i2c_start(devaddr | 0x00, timeout);
|
||||
if (status) return status;
|
||||
if (status >= 0) {
|
||||
status = i2c_write(regaddr, timeout);
|
||||
|
||||
status = i2c_write(regaddr, timeout);
|
||||
if (status) return status;
|
||||
|
||||
for (uint16_t i = 0; i < length; i++) {
|
||||
status = i2c_write(data[i], timeout);
|
||||
if (status) return status;
|
||||
for (uint16_t i = 0; i < length && status >= 0; i++) {
|
||||
status = i2c_write(data[i], timeout);
|
||||
}
|
||||
}
|
||||
|
||||
status = i2c_stop(timeout);
|
||||
if (status) return status;
|
||||
i2c_stop();
|
||||
|
||||
return I2C_STATUS_SUCCESS;
|
||||
return status;
|
||||
}
|
||||
|
||||
i2c_status_t i2c_readReg(uint8_t devaddr, uint8_t regaddr, uint8_t* data, uint16_t length, uint16_t timeout)
|
||||
{
|
||||
i2c_status_t i2c_readReg(uint8_t devaddr, uint8_t regaddr, uint8_t* data, uint16_t length, uint16_t timeout) {
|
||||
i2c_status_t status = i2c_start(devaddr, timeout);
|
||||
if (status) return status;
|
||||
if (status < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
status = i2c_write(regaddr, timeout);
|
||||
if (status) return status;
|
||||
if (status < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
status = i2c_start(devaddr | 0x01, timeout);
|
||||
if (status) return status;
|
||||
|
||||
for (uint16_t i = 0; i < (length-1); i++) {
|
||||
for (uint16_t i = 0; i < (length - 1) && status >= 0; i++) {
|
||||
status = i2c_read_ack(timeout);
|
||||
if (status >= 0) {
|
||||
data[i] = status;
|
||||
} else {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
status = i2c_read_nack(timeout);
|
||||
if (status >= 0 ) {
|
||||
data[(length-1)] = status;
|
||||
} else {
|
||||
return status;
|
||||
if (status >= 0) {
|
||||
status = i2c_read_nack(timeout);
|
||||
if (status >= 0) {
|
||||
data[(length - 1)] = status;
|
||||
}
|
||||
}
|
||||
|
||||
status = i2c_stop(timeout);
|
||||
if (status) return status;
|
||||
error:
|
||||
i2c_stop();
|
||||
|
||||
return I2C_STATUS_SUCCESS;
|
||||
return (status < 0) ? status : I2C_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
i2c_status_t i2c_stop(uint16_t timeout)
|
||||
{
|
||||
void i2c_stop(void) {
|
||||
// transmit STOP condition
|
||||
TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTO);
|
||||
|
||||
uint16_t timeout_timer = timer_read();
|
||||
while(TWCR & (1<<TWSTO)) {
|
||||
if ((timeout != I2C_TIMEOUT_INFINITE) && ((timer_read() - timeout_timer) >= timeout)) {
|
||||
return I2C_STATUS_TIMEOUT;
|
||||
}
|
||||
}
|
||||
|
||||
return I2C_STATUS_SUCCESS;
|
||||
}
|
||||
TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
|
||||
}
|
||||
|
||||
@@ -22,10 +22,10 @@ i2c_status_t i2c_start(uint8_t address, uint16_t timeout);
|
||||
i2c_status_t i2c_write(uint8_t data, uint16_t timeout);
|
||||
int16_t i2c_read_ack(uint16_t timeout);
|
||||
int16_t i2c_read_nack(uint16_t timeout);
|
||||
i2c_status_t i2c_transmit(uint8_t address, uint8_t* data, uint16_t length, uint16_t timeout);
|
||||
i2c_status_t i2c_transmit(uint8_t address, const uint8_t* data, uint16_t length, uint16_t timeout);
|
||||
i2c_status_t i2c_receive(uint8_t address, uint8_t* data, uint16_t length, uint16_t timeout);
|
||||
i2c_status_t i2c_writeReg(uint8_t devaddr, uint8_t regaddr, uint8_t* data, uint16_t length, uint16_t timeout);
|
||||
i2c_status_t i2c_writeReg(uint8_t devaddr, uint8_t regaddr, const uint8_t* data, uint16_t length, uint16_t timeout);
|
||||
i2c_status_t i2c_readReg(uint8_t devaddr, uint8_t regaddr, uint8_t* data, uint16_t length, uint16_t timeout);
|
||||
i2c_status_t i2c_stop(uint16_t timeout);
|
||||
void i2c_stop(void);
|
||||
|
||||
#endif // I2C_MASTER_H
|
||||
#endif // I2C_MASTER_H
|
||||
|
||||
+15
-12
@@ -9,23 +9,26 @@
|
||||
|
||||
#include "i2c_slave.h"
|
||||
|
||||
void i2c_init(uint8_t address){
|
||||
volatile uint8_t i2c_slave_reg[I2C_SLAVE_REG_COUNT];
|
||||
|
||||
static volatile uint8_t buffer_address;
|
||||
static volatile bool slave_has_register_set = false;
|
||||
|
||||
void i2c_slave_init(uint8_t address){
|
||||
// load address into TWI address register
|
||||
TWAR = (address << 1);
|
||||
TWAR = address;
|
||||
// set the TWCR to enable address matching and enable TWI, clear TWINT, enable TWI interrupt
|
||||
TWCR = (1 << TWIE) | (1 << TWEA) | (1 << TWINT) | (1 << TWEN);
|
||||
}
|
||||
|
||||
void i2c_stop(void){
|
||||
void i2c_slave_stop(void){
|
||||
// clear acknowledge and enable bits
|
||||
TWCR &= ~((1 << TWEA) | (1 << TWEN));
|
||||
}
|
||||
|
||||
ISR(TWI_vect){
|
||||
uint8_t ack = 1;
|
||||
// temporary stores the received data
|
||||
//uint8_t data;
|
||||
|
||||
|
||||
switch(TW_STATUS){
|
||||
case TW_SR_SLA_ACK:
|
||||
// The device is now a slave receiver
|
||||
@@ -38,13 +41,13 @@ ISR(TWI_vect){
|
||||
if(!slave_has_register_set){
|
||||
buffer_address = TWDR;
|
||||
|
||||
if (buffer_address >= RX_BUFFER_SIZE){ // address out of bounds dont ack
|
||||
ack = 0;
|
||||
buffer_address = 0;
|
||||
if (buffer_address >= I2C_SLAVE_REG_COUNT) { // address out of bounds dont ack
|
||||
ack = 0;
|
||||
buffer_address = 0;
|
||||
}
|
||||
slave_has_register_set = true; // address has been receaved now fill in buffer
|
||||
} else {
|
||||
rxbuffer[buffer_address] = TWDR;
|
||||
i2c_slave_reg[buffer_address] = TWDR;
|
||||
buffer_address++;
|
||||
}
|
||||
break;
|
||||
@@ -52,7 +55,7 @@ ISR(TWI_vect){
|
||||
case TW_ST_SLA_ACK:
|
||||
case TW_ST_DATA_ACK:
|
||||
// This device is a slave transmitter and master has requested data
|
||||
TWDR = txbuffer[buffer_address];
|
||||
TWDR = i2c_slave_reg[buffer_address];
|
||||
buffer_address++;
|
||||
break;
|
||||
|
||||
@@ -63,6 +66,6 @@ ISR(TWI_vect){
|
||||
break;
|
||||
}
|
||||
|
||||
// Reset i2c state mahcine to be ready for next interrupt
|
||||
// Reset i2c state machine to be ready for next interrupt
|
||||
TWCR |= (1 << TWIE) | (1 << TWINT) | (ack << TWEA) | (1 << TWEN);
|
||||
}
|
||||
@@ -8,16 +8,11 @@
|
||||
#ifndef I2C_SLAVE_H
|
||||
#define I2C_SLAVE_H
|
||||
|
||||
#define TX_BUFFER_SIZE 30
|
||||
#define RX_BUFFER_SIZE 30
|
||||
#define I2C_SLAVE_REG_COUNT 30
|
||||
|
||||
volatile uint8_t buffer_address;
|
||||
static volatile bool slave_has_register_set = false;
|
||||
volatile uint8_t txbuffer[TX_BUFFER_SIZE];
|
||||
volatile uint8_t rxbuffer[RX_BUFFER_SIZE];
|
||||
extern volatile uint8_t i2c_slave_reg[I2C_SLAVE_REG_COUNT];
|
||||
|
||||
void i2c_init(uint8_t address);
|
||||
void i2c_stop(void);
|
||||
ISR(TWI_vect);
|
||||
void i2c_slave_init(uint8_t address);
|
||||
void i2c_slave_stop(void);
|
||||
|
||||
#endif // I2C_SLAVE_H
|
||||
+27
-2
@@ -27,6 +27,12 @@
|
||||
#include <util/delay.h>
|
||||
#include "debug.h"
|
||||
|
||||
#if !defined(LED_ARRAY) && defined(RGB_MATRIX_ENABLE)
|
||||
// LED color buffer
|
||||
LED_TYPE led[DRIVER_LED_TOTAL];
|
||||
#define LED_ARRAY led
|
||||
#endif
|
||||
|
||||
#ifdef RGBW_BB_TWI
|
||||
|
||||
// Port for the I2C
|
||||
@@ -128,11 +134,11 @@ unsigned char I2C_Write(unsigned char c)
|
||||
c <<= 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
I2C_WriteBit(0);
|
||||
_delay_us(I2C_DELAY);
|
||||
_delay_us(I2C_DELAY);
|
||||
|
||||
|
||||
// _delay_us(I2C_DELAY);
|
||||
//return I2C_ReadBit();
|
||||
return 0;
|
||||
@@ -141,6 +147,25 @@ unsigned char I2C_Write(unsigned char c)
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef RGB_MATRIX_ENABLE
|
||||
// Set an led in the buffer to a color
|
||||
void inline ws2812_setled(int i, uint8_t r, uint8_t g, uint8_t b)
|
||||
{
|
||||
led[i].r = r;
|
||||
led[i].g = g;
|
||||
led[i].b = b;
|
||||
}
|
||||
|
||||
void ws2812_setled_all (uint8_t r, uint8_t g, uint8_t b)
|
||||
{
|
||||
for (int i = 0; i < sizeof(led)/sizeof(led[0]); i++) {
|
||||
led[i].r = r;
|
||||
led[i].g = g;
|
||||
led[i].b = b;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Setleds for standard RGB
|
||||
void inline ws2812_setleds(LED_TYPE *ledarray, uint16_t leds)
|
||||
{
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
|
||||
#include "rgblight_types.h"
|
||||
|
||||
|
||||
/* User Interface
|
||||
*
|
||||
* Input:
|
||||
@@ -43,6 +42,10 @@
|
||||
* - Send out the LED data
|
||||
* - Wait 50�s to reset the LEDs
|
||||
*/
|
||||
#ifdef RGB_MATRIX_ENABLE
|
||||
void ws2812_setled (int index, uint8_t r, uint8_t g, uint8_t b);
|
||||
void ws2812_setled_all (uint8_t r, uint8_t g, uint8_t b);
|
||||
#endif
|
||||
|
||||
void ws2812_setleds (LED_TYPE *ledarray, uint16_t number_of_leds);
|
||||
void ws2812_setleds_pin (LED_TYPE *ledarray, uint16_t number_of_leds,uint8_t pinmask);
|
||||
|
||||
+12
-12
@@ -21,7 +21,7 @@
|
||||
#include <math.h>
|
||||
|
||||
|
||||
uint8_t DRV2605L_transfer_buffer[20];
|
||||
uint8_t DRV2605L_transfer_buffer[2];
|
||||
uint8_t DRV2605L_tx_register[0];
|
||||
uint8_t DRV2605L_read_buffer[0];
|
||||
uint8_t DRV2605L_read_register;
|
||||
@@ -34,6 +34,11 @@ void DRV_write(uint8_t drv_register, uint8_t settings) {
|
||||
}
|
||||
|
||||
uint8_t DRV_read(uint8_t regaddress) {
|
||||
#ifdef __AVR__
|
||||
i2c_readReg(DRV2605L_BASE_ADDRESS << 1,
|
||||
regaddress, DRV2605L_read_buffer, 1, 100);
|
||||
DRV2605L_read_register = (uint8_t)DRV2605L_read_buffer[0];
|
||||
#else
|
||||
DRV2605L_tx_register[0] = regaddress;
|
||||
if (MSG_OK != i2c_transmit_receive(DRV2605L_BASE_ADDRESS << 1,
|
||||
DRV2605L_tx_register, 1,
|
||||
@@ -42,14 +47,13 @@ uint8_t DRV_read(uint8_t regaddress) {
|
||||
printf("err reading reg \n");
|
||||
}
|
||||
DRV2605L_read_register = (uint8_t)DRV2605L_read_buffer[0];
|
||||
#endif
|
||||
return DRV2605L_read_register;
|
||||
}
|
||||
|
||||
void DRV_init(void)
|
||||
{
|
||||
i2c_init();
|
||||
i2c_start(DRV2605L_BASE_ADDRESS);
|
||||
|
||||
/* 0x07 sets DRV2605 into calibration mode */
|
||||
DRV_write(DRV_MODE,0x07);
|
||||
|
||||
@@ -104,21 +108,17 @@ void DRV_init(void)
|
||||
C4_SET.Bits.C4_AUTO_CAL_TIME = AUTO_CAL_TIME;
|
||||
DRV_write(DRV_CTRL_4, (uint8_t) C4_SET.Byte);
|
||||
DRV_write(DRV_LIB_SELECTION,LIB_SELECTION);
|
||||
//start autocalibration
|
||||
|
||||
DRV_write(DRV_GO, 0x01);
|
||||
|
||||
/* 0x00 sets DRV2605 out of standby and to use internal trigger
|
||||
* 0x01 sets DRV2605 out of standby and to use external trigger */
|
||||
DRV_write(DRV_MODE,0x00);
|
||||
|
||||
/* 0x06: LRA library */
|
||||
DRV_write(DRV_WAVEFORM_SEQ_1, 0x01);
|
||||
|
||||
/* 0xB9: LRA, 4x brake factor, medium gain, 7.5x back EMF
|
||||
* 0x39: ERM, 4x brake factor, medium gain, 1.365x back EMF */
|
||||
|
||||
/* TODO: setup auto-calibration as part of initiation */
|
||||
|
||||
//Play greeting sequence
|
||||
DRV_write(DRV_GO, 0x00);
|
||||
DRV_write(DRV_WAVEFORM_SEQ_1, DRV_GREETING);
|
||||
DRV_write(DRV_GO, 0x01);
|
||||
}
|
||||
|
||||
void DRV_pulse(uint8_t sequence)
|
||||
|
||||
@@ -31,13 +31,6 @@
|
||||
#define FB_LOOPGAIN 1 /* For Low:0, Medium:1, High:2, Very High:3 */
|
||||
#endif
|
||||
|
||||
#ifndef RATED_VOLTAGE
|
||||
#define RATED_VOLTAGE 2 /* 2v as safe range in case device voltage is not set */
|
||||
#ifndef V_PEAK
|
||||
#define V_PEAK 2.8
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* LRA specific settings */
|
||||
#if FB_ERM_LRA == 1
|
||||
#ifndef V_RMS
|
||||
@@ -49,6 +42,16 @@
|
||||
#ifndef F_LRA
|
||||
#define F_LRA 205
|
||||
#endif
|
||||
#ifndef RATED_VOLTAGE
|
||||
#define RATED_VOLTAGE 2 /* 2v as safe range in case device voltage is not set */
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef RATED_VOLTAGE
|
||||
#define RATED_VOLTAGE 2 /* 2v as safe range in case device voltage is not set */
|
||||
#endif
|
||||
#ifndef V_PEAK
|
||||
#define V_PEAK 2.8
|
||||
#endif
|
||||
|
||||
/* Library Selection */
|
||||
@@ -60,6 +63,13 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef DRV_GREETING
|
||||
#define DRV_GREETING alert_750ms
|
||||
#endif
|
||||
#ifndef DRV_MODE_DEFAULT
|
||||
#define DRV_MODE_DEFAULT strong_click1
|
||||
#endif
|
||||
|
||||
/* Control 1 register settings */
|
||||
#ifndef DRIVE_TIME
|
||||
#define DRIVE_TIME 25
|
||||
@@ -162,7 +172,6 @@ void DRV_write(const uint8_t drv_register, const uint8_t settings);
|
||||
uint8_t DRV_read(const uint8_t regaddress);
|
||||
void DRV_pulse(const uint8_t sequence);
|
||||
|
||||
|
||||
typedef enum DRV_EFFECT{
|
||||
clear_sequence = 0,
|
||||
strong_click = 1,
|
||||
@@ -288,6 +297,7 @@ typedef enum DRV_EFFECT{
|
||||
smooth_hum3_30 = 121,
|
||||
smooth_hum4_20 = 122,
|
||||
smooth_hum5_10 = 123,
|
||||
drv_effect_max = 124,
|
||||
} DRV_EFFECT;
|
||||
|
||||
/* Register bit array unions */
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
/* Copyright 2019 ishtob
|
||||
* Driver for haptic feedback written for QMK
|
||||
*
|
||||
* 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 "haptic.h"
|
||||
#include "eeconfig.h"
|
||||
#include "progmem.h"
|
||||
#include "debug.h"
|
||||
#ifdef DRV2605L
|
||||
#include "DRV2605L.h"
|
||||
#endif
|
||||
#ifdef SOLENOID_ENABLE
|
||||
#include "solenoid.h"
|
||||
#endif
|
||||
|
||||
haptic_config_t haptic_config;
|
||||
|
||||
void haptic_init(void) {
|
||||
debug_enable = 1; //Debug is ON!
|
||||
if (!eeconfig_is_enabled()) {
|
||||
eeconfig_init();
|
||||
}
|
||||
haptic_config.raw = eeconfig_read_haptic();
|
||||
if (haptic_config.mode < 1){
|
||||
haptic_config.mode = 1;
|
||||
}
|
||||
if (!haptic_config.mode){
|
||||
dprintf("No haptic config found in eeprom, setting default configs\n");
|
||||
haptic_reset();
|
||||
}
|
||||
#ifdef SOLENOID_ENABLE
|
||||
solenoid_setup();
|
||||
dprintf("Solenoid driver initialized\n");
|
||||
#endif
|
||||
#ifdef DRV2605L
|
||||
DRV_init();
|
||||
dprintf("DRV2605 driver initialized\n");
|
||||
#endif
|
||||
eeconfig_debug_haptic();
|
||||
}
|
||||
|
||||
void haptic_task(void) {
|
||||
#ifdef SOLENOID_ENABLE
|
||||
solenoid_check();
|
||||
#endif
|
||||
}
|
||||
|
||||
void eeconfig_debug_haptic(void) {
|
||||
dprintf("haptic_config eprom\n");
|
||||
dprintf("haptic_config.enable = %d\n", haptic_config.enable);
|
||||
dprintf("haptic_config.mode = %d\n", haptic_config.mode);
|
||||
}
|
||||
|
||||
void haptic_enable(void) {
|
||||
haptic_config.enable = 1;
|
||||
xprintf("haptic_config.enable = %u\n", haptic_config.enable);
|
||||
eeconfig_update_haptic(haptic_config.raw);
|
||||
}
|
||||
|
||||
void haptic_disable(void) {
|
||||
haptic_config.enable = 0;
|
||||
xprintf("haptic_config.enable = %u\n", haptic_config.enable);
|
||||
eeconfig_update_haptic(haptic_config.raw);
|
||||
}
|
||||
|
||||
void haptic_toggle(void) {
|
||||
if (haptic_config.enable) {
|
||||
haptic_disable();
|
||||
} else {
|
||||
haptic_enable();
|
||||
}
|
||||
eeconfig_update_haptic(haptic_config.raw);
|
||||
}
|
||||
|
||||
void haptic_feedback_toggle(void){
|
||||
haptic_config.feedback++;
|
||||
if (haptic_config.feedback >= HAPTIC_FEEDBACK_MAX)
|
||||
haptic_config.feedback = KEY_PRESS;
|
||||
xprintf("haptic_config.feedback = %u\n", !haptic_config.feedback);
|
||||
eeconfig_update_haptic(haptic_config.raw);
|
||||
}
|
||||
|
||||
void haptic_buzz_toggle(void) {
|
||||
bool buzz_stat = !haptic_config.buzz;
|
||||
haptic_config.buzz = buzz_stat;
|
||||
haptic_set_buzz(buzz_stat);
|
||||
}
|
||||
|
||||
void haptic_mode_increase(void) {
|
||||
uint8_t mode = haptic_config.mode + 1;
|
||||
#ifdef DRV2605L
|
||||
if (haptic_config.mode >= drv_effect_max) {
|
||||
mode = 1;
|
||||
}
|
||||
#endif
|
||||
haptic_set_mode(mode);
|
||||
}
|
||||
|
||||
void haptic_mode_decrease(void) {
|
||||
uint8_t mode = haptic_config.mode -1;
|
||||
#ifdef DRV2605L
|
||||
if (haptic_config.mode < 1) {
|
||||
mode = (drv_effect_max - 1);
|
||||
}
|
||||
#endif
|
||||
haptic_set_mode(mode);
|
||||
}
|
||||
|
||||
void haptic_dwell_increase(void) {
|
||||
uint8_t dwell = haptic_config.dwell + 1;
|
||||
#ifdef SOLENOID_ENABLE
|
||||
if (haptic_config.dwell >= SOLENOID_MAX_DWELL) {
|
||||
dwell = 1;
|
||||
}
|
||||
solenoid_set_dwell(dwell);
|
||||
#endif
|
||||
haptic_set_dwell(dwell);
|
||||
}
|
||||
|
||||
void haptic_dwell_decrease(void) {
|
||||
uint8_t dwell = haptic_config.dwell -1;
|
||||
#ifdef SOLENOID_ENABLE
|
||||
if (haptic_config.dwell < SOLENOID_MIN_DWELL) {
|
||||
dwell = SOLENOID_MAX_DWELL;
|
||||
}
|
||||
solenoid_set_dwell(dwell);
|
||||
#endif
|
||||
haptic_set_dwell(dwell);
|
||||
}
|
||||
|
||||
void haptic_reset(void){
|
||||
haptic_config.enable = true;
|
||||
uint8_t feedback = HAPTIC_FEEDBACK_DEFAULT;
|
||||
haptic_config.feedback = feedback;
|
||||
#ifdef DRV2605L
|
||||
uint8_t mode = HAPTIC_MODE_DEFAULT;
|
||||
haptic_config.mode = mode;
|
||||
#endif
|
||||
#ifdef SOLENOID_ENABLE
|
||||
uint8_t dwell = SOLENOID_DEFAULT_DWELL;
|
||||
haptic_config.dwell = dwell;
|
||||
#endif
|
||||
eeconfig_update_haptic(haptic_config.raw);
|
||||
xprintf("haptic_config.feedback = %u\n", haptic_config.feedback);
|
||||
xprintf("haptic_config.mode = %u\n", haptic_config.mode);
|
||||
}
|
||||
|
||||
void haptic_set_feedback(uint8_t feedback) {
|
||||
haptic_config.feedback = feedback;
|
||||
eeconfig_update_haptic(haptic_config.raw);
|
||||
xprintf("haptic_config.feedback = %u\n", haptic_config.feedback);
|
||||
}
|
||||
|
||||
void haptic_set_mode(uint8_t mode) {
|
||||
haptic_config.mode = mode;
|
||||
eeconfig_update_haptic(haptic_config.raw);
|
||||
xprintf("haptic_config.mode = %u\n", haptic_config.mode);
|
||||
}
|
||||
|
||||
void haptic_set_buzz(uint8_t buzz) {
|
||||
haptic_config.buzz = buzz;
|
||||
eeconfig_update_haptic(haptic_config.raw);
|
||||
xprintf("haptic_config.buzz = %u\n", haptic_config.buzz);
|
||||
}
|
||||
|
||||
void haptic_set_dwell(uint8_t dwell) {
|
||||
haptic_config.dwell = dwell;
|
||||
eeconfig_update_haptic(haptic_config.raw);
|
||||
xprintf("haptic_config.dwell = %u\n", haptic_config.dwell);
|
||||
}
|
||||
|
||||
uint8_t haptic_get_mode(void) {
|
||||
if (!haptic_config.enable){
|
||||
return false;
|
||||
}
|
||||
return haptic_config.mode;
|
||||
}
|
||||
|
||||
uint8_t haptic_get_feedback(void) {
|
||||
if (!haptic_config.enable){
|
||||
return false;
|
||||
}
|
||||
return haptic_config.feedback;
|
||||
}
|
||||
|
||||
uint8_t haptic_get_dwell(void) {
|
||||
if (!haptic_config.enable){
|
||||
return false;
|
||||
}
|
||||
return haptic_config.dwell;
|
||||
}
|
||||
|
||||
void haptic_play(void) {
|
||||
#ifdef DRV2605L
|
||||
uint8_t play_eff = 0;
|
||||
play_eff = haptic_config.mode;
|
||||
DRV_pulse(play_eff);
|
||||
#endif
|
||||
#ifdef SOLENOID_ENABLE
|
||||
solenoid_fire();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool process_haptic(uint16_t keycode, keyrecord_t *record) {
|
||||
if (keycode == HPT_ON && record->event.pressed) { haptic_enable(); }
|
||||
if (keycode == HPT_OFF && record->event.pressed) { haptic_disable(); }
|
||||
if (keycode == HPT_TOG && record->event.pressed) { haptic_toggle(); }
|
||||
if (keycode == HPT_RST && record->event.pressed) { haptic_reset(); }
|
||||
if (keycode == HPT_FBK && record->event.pressed) { haptic_feedback_toggle(); }
|
||||
if (keycode == HPT_BUZ && record->event.pressed) { haptic_buzz_toggle(); }
|
||||
if (keycode == HPT_MODI && record->event.pressed) { haptic_mode_increase(); }
|
||||
if (keycode == HPT_MODD && record->event.pressed) { haptic_mode_decrease(); }
|
||||
if (keycode == HPT_DWLI && record->event.pressed) { haptic_dwell_increase(); }
|
||||
if (keycode == HPT_DWLD && record->event.pressed) { haptic_dwell_decrease(); }
|
||||
if (haptic_config.enable) {
|
||||
if ( record->event.pressed ) {
|
||||
// keypress
|
||||
if (haptic_config.feedback < 2) {
|
||||
haptic_play();
|
||||
}
|
||||
} else {
|
||||
//keyrelease
|
||||
if (haptic_config.feedback > 0) {
|
||||
haptic_play();
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void haptic_shutdown(void) {
|
||||
#ifdef SOLENOID_ENABLE
|
||||
solenoid_shutdown();
|
||||
#endif
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/* Copyright 2019 ishtob
|
||||
* Driver for haptic feedback written for QMK
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "quantum.h"
|
||||
#ifdef DRV2605L
|
||||
#include "DRV2605L.h"
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef HAPTIC_FEEDBACK_DEFAULT
|
||||
#define HAPTIC_FEEDBACK_DEFAULT 0
|
||||
#endif
|
||||
#ifndef HAPTIC_MODE_DEFAULT
|
||||
#define HAPTIC_MODE_DEFAULT DRV_MODE_DEFAULT
|
||||
#endif
|
||||
|
||||
/* EEPROM config settings */
|
||||
typedef union {
|
||||
uint32_t raw;
|
||||
struct {
|
||||
bool enable :1;
|
||||
uint8_t feedback :2;
|
||||
uint8_t mode :7;
|
||||
bool buzz :1;
|
||||
uint8_t dwell :7;
|
||||
uint16_t reserved :16;
|
||||
};
|
||||
} haptic_config_t;
|
||||
|
||||
typedef enum HAPTIC_FEEDBACK{
|
||||
KEY_PRESS,
|
||||
KEY_PRESS_RELEASE,
|
||||
KEY_RELEASE,
|
||||
HAPTIC_FEEDBACK_MAX,
|
||||
} HAPTIC_FEEDBACK;
|
||||
|
||||
bool process_haptic(uint16_t keycode, keyrecord_t *record);
|
||||
void haptic_init(void);
|
||||
void haptic_task(void);
|
||||
void eeconfig_debug_haptic(void);
|
||||
void haptic_enable(void);
|
||||
void haptic_disable(void);
|
||||
void haptic_toggle(void);
|
||||
void haptic_feedback_toggle(void);
|
||||
void haptic_mode_increase(void);
|
||||
void haptic_mode_decrease(void);
|
||||
void haptic_mode(uint8_t mode);
|
||||
void haptic_reset(void);
|
||||
void haptic_set_feedback(uint8_t feedback);
|
||||
void haptic_set_mode(uint8_t mode);
|
||||
void haptic_set_dwell(uint8_t dwell);
|
||||
void haptic_set_buzz(uint8_t buzz);
|
||||
void haptic_buzz_toggle(void);
|
||||
uint8_t haptic_get_mode(void);
|
||||
uint8_t haptic_get_feedback(void);
|
||||
void haptic_dwell_increase(void);
|
||||
void haptic_dwell_decrease(void);
|
||||
|
||||
void haptic_play(void);
|
||||
void haptic_shutdown(void);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/* Copyright 2018 mtdjr - modified by ishtob
|
||||
* Driver for solenoid written for QMK
|
||||
*
|
||||
* 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 <timer.h>
|
||||
#include "solenoid.h"
|
||||
#include "haptic.h"
|
||||
|
||||
bool solenoid_on = false;
|
||||
bool solenoid_buzzing = false;
|
||||
uint16_t solenoid_start = 0;
|
||||
uint8_t solenoid_dwell = SOLENOID_DEFAULT_DWELL;
|
||||
|
||||
extern haptic_config_t haptic_config;
|
||||
|
||||
|
||||
void solenoid_buzz_on(void) {
|
||||
haptic_set_buzz(1);
|
||||
}
|
||||
|
||||
void solenoid_buzz_off(void) {
|
||||
haptic_set_buzz(0);
|
||||
}
|
||||
|
||||
void solenoid_set_buzz(int buzz) {
|
||||
haptic_set_buzz(buzz);
|
||||
}
|
||||
|
||||
|
||||
void solenoid_dwell_minus(uint8_t solenoid_dwell) {
|
||||
if (solenoid_dwell > 0) solenoid_dwell--;
|
||||
}
|
||||
|
||||
void solenoid_dwell_plus(uint8_t solenoid_dwell) {
|
||||
if (solenoid_dwell < SOLENOID_MAX_DWELL) solenoid_dwell++;
|
||||
}
|
||||
|
||||
void solenoid_set_dwell(uint8_t dwell) {
|
||||
solenoid_dwell = dwell;
|
||||
}
|
||||
|
||||
void solenoid_stop(void) {
|
||||
writePinLow(SOLENOID_PIN);
|
||||
solenoid_on = false;
|
||||
solenoid_buzzing = false;
|
||||
}
|
||||
|
||||
void solenoid_fire(void) {
|
||||
if (!haptic_config.buzz && solenoid_on) return;
|
||||
if (haptic_config.buzz && solenoid_buzzing) return;
|
||||
|
||||
solenoid_on = true;
|
||||
solenoid_buzzing = true;
|
||||
solenoid_start = timer_read();
|
||||
writePinHigh(SOLENOID_PIN);
|
||||
}
|
||||
|
||||
void solenoid_check(void) {
|
||||
uint16_t elapsed = 0;
|
||||
|
||||
if (!solenoid_on) return;
|
||||
|
||||
elapsed = timer_elapsed(solenoid_start);
|
||||
|
||||
//Check if it's time to finish this solenoid click cycle
|
||||
if (elapsed > solenoid_dwell) {
|
||||
solenoid_stop();
|
||||
return;
|
||||
}
|
||||
|
||||
//Check whether to buzz the solenoid on and off
|
||||
if (haptic_config.buzz) {
|
||||
if (elapsed / SOLENOID_MIN_DWELL % 2 == 0){
|
||||
if (!solenoid_buzzing) {
|
||||
solenoid_buzzing = true;
|
||||
writePinHigh(SOLENOID_PIN);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (solenoid_buzzing) {
|
||||
solenoid_buzzing = false;
|
||||
writePinLow(SOLENOID_PIN);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void solenoid_setup(void) {
|
||||
setPinOutput(SOLENOID_PIN);
|
||||
solenoid_fire();
|
||||
}
|
||||
|
||||
void solenoid_shutdown(void) {
|
||||
writePinLow(SOLENOID_PIN);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/* Copyright 2018 mtdjr - modified by ishtob
|
||||
* Driver for solenoid written for QMK
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef SOLENOID_DEFAULT_DWELL
|
||||
#define SOLENOID_DEFAULT_DWELL 12
|
||||
#endif
|
||||
|
||||
#ifndef SOLENOID_MAX_DWELL
|
||||
#define SOLENOID_MAX_DWELL 100
|
||||
#endif
|
||||
|
||||
#ifndef SOLENOID_MIN_DWELL
|
||||
#define SOLENOID_MIN_DWELL 4
|
||||
#endif
|
||||
|
||||
#ifndef SOLENOID_ACTIVE
|
||||
#define SOLENOID_ACTIVE false
|
||||
#endif
|
||||
|
||||
#ifndef SOLENOID_PIN
|
||||
#define SOLENOID_PIN F6
|
||||
#endif
|
||||
|
||||
void solenoid_buzz_on(void);
|
||||
void solenoid_buzz_off(void);
|
||||
void solenoid_set_buzz(int buzz);
|
||||
|
||||
void solenoid_dwell_minus(uint8_t solenoid_dwell);
|
||||
void solenoid_dwell_plus(uint8_t solenoid_dwell);
|
||||
void solenoid_set_dwell(uint8_t dwell);
|
||||
|
||||
void solenoid_stop(void);
|
||||
void solenoid_fire(void);
|
||||
|
||||
void solenoid_check(void);
|
||||
|
||||
void solenoid_setup(void);
|
||||
void solenoid_shutdown(void);
|
||||
@@ -0,0 +1,246 @@
|
||||
/* Copyright 2017 Jason Williams
|
||||
* Copyright 2018 Jack Humbert
|
||||
* Copyright 2019 Clueboard
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifdef __AVR__
|
||||
#include <avr/interrupt.h>
|
||||
#include <avr/io.h>
|
||||
#include <util/delay.h>
|
||||
#else
|
||||
#include "wait.h"
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include "is31fl3731-simple.h"
|
||||
#include "i2c_master.h"
|
||||
#include "progmem.h"
|
||||
#include "print.h"
|
||||
|
||||
// This is a 7-bit address, that gets left-shifted and bit 0
|
||||
// set to 0 for write, 1 for read (as per I2C protocol)
|
||||
// The address will vary depending on your wiring:
|
||||
// 0b1110100 AD <-> GND
|
||||
// 0b1110111 AD <-> VCC
|
||||
// 0b1110101 AD <-> SCL
|
||||
// 0b1110110 AD <-> SDA
|
||||
#define ISSI_ADDR_DEFAULT 0x74
|
||||
|
||||
#define ISSI_REG_CONFIG 0x00
|
||||
#define ISSI_REG_CONFIG_PICTUREMODE 0x00
|
||||
#define ISSI_REG_CONFIG_AUTOPLAYMODE 0x08
|
||||
#define ISSI_REG_CONFIG_AUDIOPLAYMODE 0x18
|
||||
|
||||
#define ISSI_CONF_PICTUREMODE 0x00
|
||||
#define ISSI_CONF_AUTOFRAMEMODE 0x04
|
||||
#define ISSI_CONF_AUDIOMODE 0x08
|
||||
|
||||
#define ISSI_REG_PICTUREFRAME 0x01
|
||||
|
||||
#define ISSI_REG_SHUTDOWN 0x0A
|
||||
#define ISSI_REG_AUDIOSYNC 0x06
|
||||
|
||||
#define ISSI_COMMANDREGISTER 0xFD
|
||||
#define ISSI_BANK_FUNCTIONREG 0x0B // helpfully called 'page nine'
|
||||
|
||||
#ifndef ISSI_TIMEOUT
|
||||
#define ISSI_TIMEOUT 100
|
||||
#endif
|
||||
|
||||
#ifndef ISSI_PERSISTENCE
|
||||
#define ISSI_PERSISTENCE 0
|
||||
#endif
|
||||
|
||||
// Transfer buffer for TWITransmitData()
|
||||
uint8_t g_twi_transfer_buffer[20];
|
||||
|
||||
// These buffers match the IS31FL3731 PWM registers 0x24-0xB3.
|
||||
// Storing them like this is optimal for I2C transfers to the registers.
|
||||
// We could optimize this and take out the unused registers from these
|
||||
// buffers and the transfers in IS31FL3731_write_pwm_buffer() but it's
|
||||
// probably not worth the extra complexity.
|
||||
uint8_t g_pwm_buffer[LED_DRIVER_COUNT][144];
|
||||
bool g_pwm_buffer_update_required = false;
|
||||
|
||||
/* There's probably a better way to init this... */
|
||||
#if LED_DRIVER_COUNT == 1
|
||||
uint8_t g_led_control_registers[LED_DRIVER_COUNT][18] = {{0}};
|
||||
#elif LED_DRIVER_COUNT == 2
|
||||
uint8_t g_led_control_registers[LED_DRIVER_COUNT][18] = {{0}, {0}};
|
||||
#elif LED_DRIVER_COUNT == 3
|
||||
uint8_t g_led_control_registers[LED_DRIVER_COUNT][18] = {{0}, {0}, {0}};
|
||||
#elif LED_DRIVER_COUNT == 4
|
||||
uint8_t g_led_control_registers[LED_DRIVER_COUNT][18] = {{0}, {0}, {0}, {0}};
|
||||
#endif
|
||||
bool g_led_control_registers_update_required = false;
|
||||
|
||||
// This is the bit pattern in the LED control registers
|
||||
// (for matrix A, add one to register for matrix B)
|
||||
//
|
||||
// reg - b7 b6 b5 b4 b3 b2 b1 b0
|
||||
// 0x00 - R08,R07,R06,R05,R04,R03,R02,R01
|
||||
// 0x02 - G08,G07,G06,G05,G04,G03,G02,R00
|
||||
// 0x04 - B08,B07,B06,B05,B04,B03,G01,G00
|
||||
// 0x06 - - , - , - , - , - ,B02,B01,B00
|
||||
// 0x08 - - , - , - , - , - , - , - , -
|
||||
// 0x0A - B17,B16,B15, - , - , - , - , -
|
||||
// 0x0C - G17,G16,B14,B13,B12,B11,B10,B09
|
||||
// 0x0E - R17,G15,G14,G13,G12,G11,G10,G09
|
||||
// 0x10 - R16,R15,R14,R13,R12,R11,R10,R09
|
||||
|
||||
|
||||
void IS31FL3731_write_register(uint8_t addr, uint8_t reg, uint8_t data) {
|
||||
g_twi_transfer_buffer[0] = reg;
|
||||
g_twi_transfer_buffer[1] = data;
|
||||
|
||||
#if ISSI_PERSISTENCE > 0
|
||||
for (uint8_t i = 0; i < ISSI_PERSISTENCE; i++) {
|
||||
if (i2c_transmit(addr << 1, g_twi_transfer_buffer, 2, ISSI_TIMEOUT) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
#else
|
||||
i2c_transmit(addr << 1, g_twi_transfer_buffer, 2, ISSI_TIMEOUT);
|
||||
#endif
|
||||
}
|
||||
|
||||
void IS31FL3731_write_pwm_buffer(uint8_t addr, uint8_t *pwm_buffer) {
|
||||
// assumes bank is already selected
|
||||
|
||||
// transmit PWM registers in 9 transfers of 16 bytes
|
||||
// g_twi_transfer_buffer[] is 20 bytes
|
||||
|
||||
// iterate over the pwm_buffer contents at 16 byte intervals
|
||||
for (int i = 0; i < 144; i += 16) {
|
||||
// set the first register, e.g. 0x24, 0x34, 0x44, etc.
|
||||
g_twi_transfer_buffer[0] = 0x24 + i;
|
||||
// copy the data from i to i+15
|
||||
// device will auto-increment register for data after the first byte
|
||||
// thus this sets registers 0x24-0x33, 0x34-0x43, etc. in one transfer
|
||||
for (int j = 0; j < 16; j++) {
|
||||
g_twi_transfer_buffer[1 + j] = pwm_buffer[i + j];
|
||||
}
|
||||
|
||||
#if ISSI_PERSISTENCE > 0
|
||||
for (uint8_t i = 0; i < ISSI_PERSISTENCE; i++) {
|
||||
if (i2c_transmit(addr << 1, g_twi_transfer_buffer, 17, ISSI_TIMEOUT) == 0)
|
||||
break;
|
||||
}
|
||||
#else
|
||||
i2c_transmit(addr << 1, g_twi_transfer_buffer, 17, ISSI_TIMEOUT);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void IS31FL3731_init(uint8_t addr) {
|
||||
// In order to avoid the LEDs being driven with garbage data
|
||||
// in the LED driver's PWM registers, first enable software shutdown,
|
||||
// then set up the mode and other settings, clear the PWM registers,
|
||||
// then disable software shutdown.
|
||||
|
||||
// select "function register" bank
|
||||
IS31FL3731_write_register(addr, ISSI_COMMANDREGISTER, ISSI_BANK_FUNCTIONREG);
|
||||
|
||||
// enable software shutdown
|
||||
IS31FL3731_write_register(addr, ISSI_REG_SHUTDOWN, 0x00);
|
||||
// this delay was copied from other drivers, might not be needed
|
||||
wait_ms(10);
|
||||
|
||||
// picture mode
|
||||
IS31FL3731_write_register(addr, ISSI_REG_CONFIG, ISSI_REG_CONFIG_PICTUREMODE);
|
||||
// display frame 0
|
||||
IS31FL3731_write_register(addr, ISSI_REG_PICTUREFRAME, 0x00);
|
||||
// audio sync off
|
||||
IS31FL3731_write_register(addr, ISSI_REG_AUDIOSYNC, 0x00);
|
||||
|
||||
// select bank 0
|
||||
IS31FL3731_write_register(addr, ISSI_COMMANDREGISTER, 0);
|
||||
|
||||
// turn off all LEDs in the LED control register
|
||||
for (int i = 0x00; i <= 0x11; i++) {
|
||||
IS31FL3731_write_register(addr, i, 0x00);
|
||||
}
|
||||
|
||||
// turn off all LEDs in the blink control register (not really needed)
|
||||
for (int i = 0x12; i <= 0x23; i++) {
|
||||
IS31FL3731_write_register(addr, i, 0x00);
|
||||
}
|
||||
|
||||
// set PWM on all LEDs to 0
|
||||
for (int i = 0x24; i <= 0xB3; i++) {
|
||||
IS31FL3731_write_register(addr, i, 0x00);
|
||||
}
|
||||
|
||||
// select "function register" bank
|
||||
IS31FL3731_write_register(addr, ISSI_COMMANDREGISTER, ISSI_BANK_FUNCTIONREG);
|
||||
|
||||
// disable software shutdown
|
||||
IS31FL3731_write_register(addr, ISSI_REG_SHUTDOWN, 0x01);
|
||||
|
||||
// select bank 0 and leave it selected.
|
||||
// most usage after initialization is just writing PWM buffers in bank 0
|
||||
// as there's not much point in double-buffering
|
||||
IS31FL3731_write_register(addr, ISSI_COMMANDREGISTER, 0);
|
||||
|
||||
}
|
||||
|
||||
void IS31FL3731_set_value(int index, uint8_t value) {
|
||||
if (index >= 0 && index < LED_DRIVER_LED_COUNT) {
|
||||
is31_led led = g_is31_leds[index];
|
||||
|
||||
// Subtract 0x24 to get the second index of g_pwm_buffer
|
||||
g_pwm_buffer[led.driver][led.v - 0x24] = value;
|
||||
g_pwm_buffer_update_required = true;
|
||||
}
|
||||
}
|
||||
|
||||
void IS31FL3731_set_value_all(uint8_t value) {
|
||||
for (int i = 0; i < LED_DRIVER_LED_COUNT; i++) {
|
||||
IS31FL3731_set_value(i, value);
|
||||
}
|
||||
}
|
||||
|
||||
void IS31FL3731_set_led_control_register(uint8_t index, bool value) {
|
||||
is31_led led = g_is31_leds[index];
|
||||
|
||||
uint8_t control_register = (led.v - 0x24) / 8;
|
||||
uint8_t bit_value = (led.v - 0x24) % 8;
|
||||
|
||||
if (value) {
|
||||
g_led_control_registers[led.driver][control_register] |= (1 << bit_value);
|
||||
} else {
|
||||
g_led_control_registers[led.driver][control_register] &= ~(1 << bit_value);
|
||||
}
|
||||
|
||||
g_led_control_registers_update_required = true;
|
||||
}
|
||||
|
||||
void IS31FL3731_update_pwm_buffers(uint8_t addr, uint8_t index) {
|
||||
if (g_pwm_buffer_update_required) {
|
||||
IS31FL3731_write_pwm_buffer(addr, g_pwm_buffer[index]);
|
||||
g_pwm_buffer_update_required = false;
|
||||
}
|
||||
}
|
||||
|
||||
void IS31FL3731_update_led_control_registers(uint8_t addr, uint8_t index) {
|
||||
if (g_led_control_registers_update_required) {
|
||||
for (int i=0; i<18; i++) {
|
||||
IS31FL3731_write_register(addr, i, g_led_control_registers[index][i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
/* Copyright 2017 Jason Williams
|
||||
* Copyright 2018 Jack Humbert
|
||||
* Copyright 2019 Clueboard
|
||||
*
|
||||
* 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 IS31FL3731_DRIVER_H
|
||||
#define IS31FL3731_DRIVER_H
|
||||
|
||||
|
||||
typedef struct is31_led {
|
||||
uint8_t driver:2;
|
||||
uint8_t v;
|
||||
} __attribute__((packed)) is31_led;
|
||||
|
||||
extern const is31_led g_is31_leds[LED_DRIVER_LED_COUNT];
|
||||
|
||||
void IS31FL3731_init(uint8_t addr);
|
||||
void IS31FL3731_write_register(uint8_t addr, uint8_t reg, uint8_t data);
|
||||
void IS31FL3731_write_pwm_buffer(uint8_t addr, uint8_t *pwm_buffer);
|
||||
|
||||
void IS31FL3731_set_value(int index, uint8_t value);
|
||||
void IS31FL3731_set_value_all(uint8_t value);
|
||||
|
||||
void IS31FL3731_set_led_control_register(uint8_t index, bool value);
|
||||
|
||||
// This should not be called from an interrupt
|
||||
// (eg. from a timer interrupt).
|
||||
// Call this while idle (in between matrix scans).
|
||||
// If the buffer is dirty, it will update the driver with the buffer.
|
||||
void IS31FL3731_update_pwm_buffers(uint8_t addr, uint8_t index);
|
||||
void IS31FL3731_update_led_control_registers(uint8_t addr, uint8_t index);
|
||||
|
||||
#define C1_1 0x24
|
||||
#define C1_2 0x25
|
||||
#define C1_3 0x26
|
||||
#define C1_4 0x27
|
||||
#define C1_5 0x28
|
||||
#define C1_6 0x29
|
||||
#define C1_7 0x2A
|
||||
#define C1_8 0x2B
|
||||
|
||||
#define C1_9 0x2C
|
||||
#define C1_10 0x2D
|
||||
#define C1_11 0x2E
|
||||
#define C1_12 0x2F
|
||||
#define C1_13 0x30
|
||||
#define C1_14 0x31
|
||||
#define C1_15 0x32
|
||||
#define C1_16 0x33
|
||||
|
||||
#define C2_1 0x34
|
||||
#define C2_2 0x35
|
||||
#define C2_3 0x36
|
||||
#define C2_4 0x37
|
||||
#define C2_5 0x38
|
||||
#define C2_6 0x39
|
||||
#define C2_7 0x3A
|
||||
#define C2_8 0x3B
|
||||
|
||||
#define C2_9 0x3C
|
||||
#define C2_10 0x3D
|
||||
#define C2_11 0x3E
|
||||
#define C2_12 0x3F
|
||||
#define C2_13 0x40
|
||||
#define C2_14 0x41
|
||||
#define C2_15 0x42
|
||||
#define C2_16 0x43
|
||||
|
||||
#define C3_1 0x44
|
||||
#define C3_2 0x45
|
||||
#define C3_3 0x46
|
||||
#define C3_4 0x47
|
||||
#define C3_5 0x48
|
||||
#define C3_6 0x49
|
||||
#define C3_7 0x4A
|
||||
#define C3_8 0x4B
|
||||
|
||||
#define C3_9 0x4C
|
||||
#define C3_10 0x4D
|
||||
#define C3_11 0x4E
|
||||
#define C3_12 0x4F
|
||||
#define C3_13 0x50
|
||||
#define C3_14 0x51
|
||||
#define C3_15 0x52
|
||||
#define C3_16 0x53
|
||||
|
||||
#define C4_1 0x54
|
||||
#define C4_2 0x55
|
||||
#define C4_3 0x56
|
||||
#define C4_4 0x57
|
||||
#define C4_5 0x58
|
||||
#define C4_6 0x59
|
||||
#define C4_7 0x5A
|
||||
#define C4_8 0x5B
|
||||
|
||||
#define C4_9 0x5C
|
||||
#define C4_10 0x5D
|
||||
#define C4_11 0x5E
|
||||
#define C4_12 0x5F
|
||||
#define C4_13 0x60
|
||||
#define C4_14 0x61
|
||||
#define C4_15 0x62
|
||||
#define C4_16 0x63
|
||||
|
||||
#define C5_1 0x64
|
||||
#define C5_2 0x65
|
||||
#define C5_3 0x66
|
||||
#define C5_4 0x67
|
||||
#define C5_5 0x68
|
||||
#define C5_6 0x69
|
||||
#define C5_7 0x6A
|
||||
#define C5_8 0x6B
|
||||
|
||||
#define C5_9 0x6C
|
||||
#define C5_10 0x6D
|
||||
#define C5_11 0x6E
|
||||
#define C5_12 0x6F
|
||||
#define C5_13 0x70
|
||||
#define C5_14 0x71
|
||||
#define C5_15 0x72
|
||||
#define C5_16 0x73
|
||||
|
||||
#define C6_1 0x74
|
||||
#define C6_2 0x75
|
||||
#define C6_3 0x76
|
||||
#define C6_4 0x77
|
||||
#define C6_5 0x78
|
||||
#define C6_6 0x79
|
||||
#define C6_7 0x7A
|
||||
#define C6_8 0x7B
|
||||
|
||||
#define C6_9 0x7C
|
||||
#define C6_10 0x7D
|
||||
#define C6_11 0x7E
|
||||
#define C6_12 0x7F
|
||||
#define C6_13 0x80
|
||||
#define C6_14 0x81
|
||||
#define C6_15 0x82
|
||||
#define C6_16 0x83
|
||||
|
||||
#define C7_1 0x84
|
||||
#define C7_2 0x85
|
||||
#define C7_3 0x86
|
||||
#define C7_4 0x87
|
||||
#define C7_5 0x88
|
||||
#define C7_6 0x89
|
||||
#define C7_7 0x8A
|
||||
#define C7_8 0x8B
|
||||
|
||||
#define C7_9 0x8C
|
||||
#define C7_10 0x8D
|
||||
#define C7_11 0x8E
|
||||
#define C7_12 0x8F
|
||||
#define C7_13 0x90
|
||||
#define C7_14 0x91
|
||||
#define C7_15 0x92
|
||||
#define C7_16 0x93
|
||||
|
||||
#define C8_1 0x94
|
||||
#define C8_2 0x95
|
||||
#define C8_3 0x96
|
||||
#define C8_4 0x97
|
||||
#define C8_5 0x98
|
||||
#define C8_6 0x99
|
||||
#define C8_7 0x9A
|
||||
#define C8_8 0x9B
|
||||
|
||||
#define C8_9 0x9C
|
||||
#define C8_10 0x9D
|
||||
#define C8_11 0x9E
|
||||
#define C8_12 0x9F
|
||||
#define C8_13 0xA0
|
||||
#define C8_14 0xA1
|
||||
#define C8_15 0xA2
|
||||
#define C8_16 0xA3
|
||||
|
||||
#define C9_1 0xA4
|
||||
#define C9_2 0xA5
|
||||
#define C9_3 0xA6
|
||||
#define C9_4 0xA7
|
||||
#define C9_5 0xA8
|
||||
#define C9_6 0xA9
|
||||
#define C9_7 0xAA
|
||||
#define C9_8 0xAB
|
||||
|
||||
#define C9_9 0xAC
|
||||
#define C9_10 0xAD
|
||||
#define C9_11 0xAE
|
||||
#define C9_12 0xAF
|
||||
#define C9_13 0xB0
|
||||
#define C9_14 0xB1
|
||||
#define C9_15 0xB2
|
||||
#define C9_16 0xB3
|
||||
|
||||
|
||||
#endif // IS31FL3731_DRIVER_H
|
||||
@@ -24,10 +24,10 @@
|
||||
#include "wait.h"
|
||||
#endif
|
||||
|
||||
#include "is31fl3733.h"
|
||||
#include <string.h>
|
||||
#include "i2c_master.h"
|
||||
#include "progmem.h"
|
||||
#include "rgb_matrix.h"
|
||||
|
||||
// This is a 7-bit address, that gets left-shifted and bit 0
|
||||
// set to 0 for write, 1 for read (as per I2C protocol)
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
/* Copyright 2017 Jason Williams
|
||||
* Copyright 2018 Jack Humbert
|
||||
* Copyright 2018 Yiancar
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifdef __AVR__
|
||||
#include <avr/interrupt.h>
|
||||
#include <avr/io.h>
|
||||
#include <util/delay.h>
|
||||
#else
|
||||
#include "wait.h"
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
#include "i2c_master.h"
|
||||
#include "progmem.h"
|
||||
#include "rgb_matrix.h"
|
||||
|
||||
// This is a 7-bit address, that gets left-shifted and bit 0
|
||||
// set to 0 for write, 1 for read (as per I2C protocol)
|
||||
// The address will vary depending on your wiring:
|
||||
// 00 <-> GND
|
||||
// 01 <-> SCL
|
||||
// 10 <-> SDA
|
||||
// 11 <-> VCC
|
||||
// ADDR1 represents A1:A0 of the 7-bit address.
|
||||
// ADDR2 represents A3:A2 of the 7-bit address.
|
||||
// The result is: 0b101(ADDR2)(ADDR1)
|
||||
#define ISSI_ADDR_DEFAULT 0x50
|
||||
|
||||
#define ISSI_COMMANDREGISTER 0xFD
|
||||
#define ISSI_COMMANDREGISTER_WRITELOCK 0xFE
|
||||
#define ISSI_INTERRUPTMASKREGISTER 0xF0
|
||||
#define ISSI_INTERRUPTSTATUSREGISTER 0xF1
|
||||
|
||||
#define ISSI_PAGE_LEDCONTROL 0x00 //PG0
|
||||
#define ISSI_PAGE_PWM 0x01 //PG1
|
||||
#define ISSI_PAGE_AUTOBREATH 0x02 //PG2
|
||||
#define ISSI_PAGE_FUNCTION 0x03 //PG3
|
||||
|
||||
#define ISSI_REG_CONFIGURATION 0x00 //PG3
|
||||
#define ISSI_REG_GLOBALCURRENT 0x01 //PG3
|
||||
#define ISSI_REG_RESET 0x11// PG3
|
||||
#define ISSI_REG_SWPULLUP 0x0F //PG3
|
||||
#define ISSI_REG_CSPULLUP 0x10 //PG3
|
||||
|
||||
#ifndef ISSI_TIMEOUT
|
||||
#define ISSI_TIMEOUT 100
|
||||
#endif
|
||||
|
||||
#ifndef ISSI_PERSISTENCE
|
||||
#define ISSI_PERSISTENCE 0
|
||||
#endif
|
||||
|
||||
// Transfer buffer for TWITransmitData()
|
||||
uint8_t g_twi_transfer_buffer[20];
|
||||
|
||||
// These buffers match the IS31FL3737 PWM registers.
|
||||
// The control buffers match the PG0 LED On/Off registers.
|
||||
// Storing them like this is optimal for I2C transfers to the registers.
|
||||
// We could optimize this and take out the unused registers from these
|
||||
// buffers and the transfers in IS31FL3737_write_pwm_buffer() but it's
|
||||
// probably not worth the extra complexity.
|
||||
uint8_t g_pwm_buffer[DRIVER_COUNT][192];
|
||||
bool g_pwm_buffer_update_required = false;
|
||||
|
||||
uint8_t g_led_control_registers[DRIVER_COUNT][24] = { { 0 } };
|
||||
bool g_led_control_registers_update_required = false;
|
||||
|
||||
void IS31FL3737_write_register( uint8_t addr, uint8_t reg, uint8_t data )
|
||||
{
|
||||
g_twi_transfer_buffer[0] = reg;
|
||||
g_twi_transfer_buffer[1] = data;
|
||||
|
||||
#if ISSI_PERSISTENCE > 0
|
||||
for (uint8_t i = 0; i < ISSI_PERSISTENCE; i++) {
|
||||
if (i2c_transmit(addr << 1, g_twi_transfer_buffer, 2, ISSI_TIMEOUT) == 0)
|
||||
break;
|
||||
}
|
||||
#else
|
||||
i2c_transmit(addr << 1, g_twi_transfer_buffer, 2, ISSI_TIMEOUT);
|
||||
#endif
|
||||
}
|
||||
|
||||
void IS31FL3737_write_pwm_buffer( uint8_t addr, uint8_t *pwm_buffer )
|
||||
{
|
||||
// assumes PG1 is already selected
|
||||
|
||||
// transmit PWM registers in 12 transfers of 16 bytes
|
||||
// g_twi_transfer_buffer[] is 20 bytes
|
||||
|
||||
// iterate over the pwm_buffer contents at 16 byte intervals
|
||||
for ( int i = 0; i < 192; i += 16 ) {
|
||||
g_twi_transfer_buffer[0] = i;
|
||||
// copy the data from i to i+15
|
||||
// device will auto-increment register for data after the first byte
|
||||
// thus this sets registers 0x00-0x0F, 0x10-0x1F, etc. in one transfer
|
||||
for ( int j = 0; j < 16; j++ ) {
|
||||
g_twi_transfer_buffer[1 + j] = pwm_buffer[i + j];
|
||||
}
|
||||
|
||||
#if ISSI_PERSISTENCE > 0
|
||||
for (uint8_t i = 0; i < ISSI_PERSISTENCE; i++) {
|
||||
if (i2c_transmit(addr << 1, g_twi_transfer_buffer, 17, ISSI_TIMEOUT) == 0)
|
||||
break;
|
||||
}
|
||||
#else
|
||||
i2c_transmit(addr << 1, g_twi_transfer_buffer, 17, ISSI_TIMEOUT);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void IS31FL3737_init( uint8_t addr )
|
||||
{
|
||||
// In order to avoid the LEDs being driven with garbage data
|
||||
// in the LED driver's PWM registers, shutdown is enabled last.
|
||||
// Set up the mode and other settings, clear the PWM registers,
|
||||
// then disable software shutdown.
|
||||
|
||||
// Unlock the command register.
|
||||
IS31FL3737_write_register( addr, ISSI_COMMANDREGISTER_WRITELOCK, 0xC5 );
|
||||
|
||||
// Select PG0
|
||||
IS31FL3737_write_register( addr, ISSI_COMMANDREGISTER, ISSI_PAGE_LEDCONTROL );
|
||||
// Turn off all LEDs.
|
||||
for ( int i = 0x00; i <= 0x17; i++ )
|
||||
{
|
||||
IS31FL3737_write_register( addr, i, 0x00 );
|
||||
}
|
||||
|
||||
// Unlock the command register.
|
||||
IS31FL3737_write_register( addr, ISSI_COMMANDREGISTER_WRITELOCK, 0xC5 );
|
||||
|
||||
// Select PG1
|
||||
IS31FL3737_write_register( addr, ISSI_COMMANDREGISTER, ISSI_PAGE_PWM );
|
||||
// Set PWM on all LEDs to 0
|
||||
// No need to setup Breath registers to PWM as that is the default.
|
||||
for ( int i = 0x00; i <= 0xBF; i++ )
|
||||
{
|
||||
IS31FL3737_write_register( addr, i, 0x00 );
|
||||
}
|
||||
|
||||
// Unlock the command register.
|
||||
IS31FL3737_write_register( addr, ISSI_COMMANDREGISTER_WRITELOCK, 0xC5 );
|
||||
|
||||
// Select PG3
|
||||
IS31FL3737_write_register( addr, ISSI_COMMANDREGISTER, ISSI_PAGE_FUNCTION );
|
||||
// Set global current to maximum.
|
||||
IS31FL3737_write_register( addr, ISSI_REG_GLOBALCURRENT, 0xFF );
|
||||
// Disable software shutdown.
|
||||
IS31FL3737_write_register( addr, ISSI_REG_CONFIGURATION, 0x01 );
|
||||
|
||||
// Wait 10ms to ensure the device has woken up.
|
||||
#ifdef __AVR__
|
||||
_delay_ms( 10 );
|
||||
#else
|
||||
wait_ms(10);
|
||||
#endif
|
||||
}
|
||||
|
||||
void IS31FL3737_set_color( int index, uint8_t red, uint8_t green, uint8_t blue )
|
||||
{
|
||||
if ( index >= 0 && index < DRIVER_LED_TOTAL ) {
|
||||
is31_led led = g_is31_leds[index];
|
||||
|
||||
g_pwm_buffer[led.driver][led.r] = red;
|
||||
g_pwm_buffer[led.driver][led.g] = green;
|
||||
g_pwm_buffer[led.driver][led.b] = blue;
|
||||
g_pwm_buffer_update_required = true;
|
||||
}
|
||||
}
|
||||
|
||||
void IS31FL3737_set_color_all( uint8_t red, uint8_t green, uint8_t blue )
|
||||
{
|
||||
for ( int i = 0; i < DRIVER_LED_TOTAL; i++ )
|
||||
{
|
||||
IS31FL3737_set_color( i, red, green, blue );
|
||||
}
|
||||
}
|
||||
|
||||
void IS31FL3737_set_led_control_register( uint8_t index, bool red, bool green, bool blue )
|
||||
{
|
||||
is31_led led = g_is31_leds[index];
|
||||
|
||||
uint8_t control_register_r = led.r / 8;
|
||||
uint8_t control_register_g = led.g / 8;
|
||||
uint8_t control_register_b = led.b / 8;
|
||||
uint8_t bit_r = led.r % 8;
|
||||
uint8_t bit_g = led.g % 8;
|
||||
uint8_t bit_b = led.b % 8;
|
||||
|
||||
if ( red ) {
|
||||
g_led_control_registers[led.driver][control_register_r] |= (1 << bit_r);
|
||||
} else {
|
||||
g_led_control_registers[led.driver][control_register_r] &= ~(1 << bit_r);
|
||||
}
|
||||
if ( green ) {
|
||||
g_led_control_registers[led.driver][control_register_g] |= (1 << bit_g);
|
||||
} else {
|
||||
g_led_control_registers[led.driver][control_register_g] &= ~(1 << bit_g);
|
||||
}
|
||||
if ( blue ) {
|
||||
g_led_control_registers[led.driver][control_register_b] |= (1 << bit_b);
|
||||
} else {
|
||||
g_led_control_registers[led.driver][control_register_b] &= ~(1 << bit_b);
|
||||
}
|
||||
|
||||
g_led_control_registers_update_required = true;
|
||||
|
||||
}
|
||||
|
||||
void IS31FL3737_update_pwm_buffers( uint8_t addr1, uint8_t addr2 )
|
||||
{
|
||||
if ( g_pwm_buffer_update_required )
|
||||
{
|
||||
// Firstly we need to unlock the command register and select PG1
|
||||
IS31FL3737_write_register( addr1, ISSI_COMMANDREGISTER_WRITELOCK, 0xC5 );
|
||||
IS31FL3737_write_register( addr1, ISSI_COMMANDREGISTER, ISSI_PAGE_PWM );
|
||||
|
||||
IS31FL3737_write_pwm_buffer( addr1, g_pwm_buffer[0] );
|
||||
//IS31FL3737_write_pwm_buffer( addr2, g_pwm_buffer[1] );
|
||||
}
|
||||
g_pwm_buffer_update_required = false;
|
||||
}
|
||||
|
||||
void IS31FL3737_update_led_control_registers( uint8_t addr1, uint8_t addr2 )
|
||||
{
|
||||
if ( g_led_control_registers_update_required )
|
||||
{
|
||||
// Firstly we need to unlock the command register and select PG0
|
||||
IS31FL3737_write_register( addr1, ISSI_COMMANDREGISTER_WRITELOCK, 0xC5 );
|
||||
IS31FL3737_write_register( addr1, ISSI_COMMANDREGISTER, ISSI_PAGE_LEDCONTROL );
|
||||
for ( int i=0; i<24; i++ )
|
||||
{
|
||||
IS31FL3737_write_register(addr1, i, g_led_control_registers[0][i] );
|
||||
//IS31FL3737_write_register(addr2, i, g_led_control_registers[1][i] );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
/* Copyright 2017 Jason Williams
|
||||
* Copyright 2018 Jack Humbert
|
||||
* Copyright 2018 Yiancar
|
||||
*
|
||||
* 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 IS31FL3737_DRIVER_H
|
||||
#define IS31FL3737_DRIVER_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct is31_led {
|
||||
uint8_t driver:2;
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
} __attribute__((packed)) is31_led;
|
||||
|
||||
extern const is31_led g_is31_leds[DRIVER_LED_TOTAL];
|
||||
|
||||
void IS31FL3737_init( uint8_t addr );
|
||||
void IS31FL3737_write_register( uint8_t addr, uint8_t reg, uint8_t data );
|
||||
void IS31FL3737_write_pwm_buffer( uint8_t addr, uint8_t *pwm_buffer );
|
||||
|
||||
void IS31FL3737_set_color( int index, uint8_t red, uint8_t green, uint8_t blue );
|
||||
void IS31FL3737_set_color_all( uint8_t red, uint8_t green, uint8_t blue );
|
||||
|
||||
void IS31FL3737_set_led_control_register( uint8_t index, bool red, bool green, bool blue );
|
||||
|
||||
// This should not be called from an interrupt
|
||||
// (eg. from a timer interrupt).
|
||||
// Call this while idle (in between matrix scans).
|
||||
// If the buffer is dirty, it will update the driver with the buffer.
|
||||
void IS31FL3737_update_pwm_buffers( uint8_t addr1, uint8_t addr2 );
|
||||
void IS31FL3737_update_led_control_registers( uint8_t addr1, uint8_t addr2 );
|
||||
|
||||
#define A_1 0x00
|
||||
#define A_2 0x01
|
||||
#define A_3 0x02
|
||||
#define A_4 0x03
|
||||
#define A_5 0x04
|
||||
#define A_6 0x05
|
||||
#define A_7 0x08
|
||||
#define A_8 0x09
|
||||
#define A_9 0x0A
|
||||
#define A_10 0x0B
|
||||
#define A_11 0x0C
|
||||
#define A_12 0x0D
|
||||
|
||||
#define B_1 0x10
|
||||
#define B_2 0x11
|
||||
#define B_3 0x12
|
||||
#define B_4 0x13
|
||||
#define B_5 0x14
|
||||
#define B_6 0x15
|
||||
#define B_7 0x18
|
||||
#define B_8 0x19
|
||||
#define B_9 0x1A
|
||||
#define B_10 0x1B
|
||||
#define B_11 0x1C
|
||||
#define B_12 0x1D
|
||||
|
||||
#define C_1 0x20
|
||||
#define C_2 0x21
|
||||
#define C_3 0x22
|
||||
#define C_4 0x23
|
||||
#define C_5 0x24
|
||||
#define C_6 0x25
|
||||
#define C_7 0x28
|
||||
#define C_8 0x29
|
||||
#define C_9 0x2A
|
||||
#define C_10 0x2B
|
||||
#define C_11 0x2C
|
||||
#define C_12 0x2D
|
||||
|
||||
#define D_1 0x30
|
||||
#define D_2 0x31
|
||||
#define D_3 0x32
|
||||
#define D_4 0x33
|
||||
#define D_5 0x34
|
||||
#define D_6 0x35
|
||||
#define D_7 0x38
|
||||
#define D_8 0x39
|
||||
#define D_9 0x3A
|
||||
#define D_10 0x3B
|
||||
#define D_11 0x3C
|
||||
#define D_12 0x3D
|
||||
|
||||
#define E_1 0x40
|
||||
#define E_2 0x41
|
||||
#define E_3 0x42
|
||||
#define E_4 0x43
|
||||
#define E_5 0x44
|
||||
#define E_6 0x45
|
||||
#define E_7 0x48
|
||||
#define E_8 0x49
|
||||
#define E_9 0x4A
|
||||
#define E_10 0x4B
|
||||
#define E_11 0x4C
|
||||
#define E_12 0x4D
|
||||
|
||||
#define F_1 0x50
|
||||
#define F_2 0x51
|
||||
#define F_3 0x52
|
||||
#define F_4 0x53
|
||||
#define F_5 0x54
|
||||
#define F_6 0x55
|
||||
#define F_7 0x58
|
||||
#define F_8 0x59
|
||||
#define F_9 0x5A
|
||||
#define F_10 0x5B
|
||||
#define F_11 0x5C
|
||||
#define F_12 0x5D
|
||||
|
||||
#define G_1 0x60
|
||||
#define G_2 0x61
|
||||
#define G_3 0x62
|
||||
#define G_4 0x63
|
||||
#define G_5 0x64
|
||||
#define G_6 0x65
|
||||
#define G_7 0x68
|
||||
#define G_8 0x69
|
||||
#define G_9 0x6A
|
||||
#define G_10 0x6B
|
||||
#define G_11 0x6C
|
||||
#define G_12 0x6D
|
||||
|
||||
#define H_1 0x70
|
||||
#define H_2 0x71
|
||||
#define H_3 0x72
|
||||
#define H_4 0x73
|
||||
#define H_5 0x74
|
||||
#define H_6 0x75
|
||||
#define H_7 0x78
|
||||
#define H_8 0x79
|
||||
#define H_9 0x7A
|
||||
#define H_10 0x7B
|
||||
#define H_11 0x7C
|
||||
#define H_12 0x7D
|
||||
|
||||
#define I_1 0x80
|
||||
#define I_2 0x81
|
||||
#define I_3 0x82
|
||||
#define I_4 0x83
|
||||
#define I_5 0x84
|
||||
#define I_6 0x85
|
||||
#define I_7 0x88
|
||||
#define I_8 0x89
|
||||
#define I_9 0x8A
|
||||
#define I_10 0x8B
|
||||
#define I_11 0x8C
|
||||
#define I_12 0x8D
|
||||
|
||||
#define J_1 0x90
|
||||
#define J_2 0x91
|
||||
#define J_3 0x92
|
||||
#define J_4 0x93
|
||||
#define J_5 0x94
|
||||
#define J_6 0x95
|
||||
#define J_7 0x98
|
||||
#define J_8 0x99
|
||||
#define J_9 0x9A
|
||||
#define J_10 0x9B
|
||||
#define J_11 0x9C
|
||||
#define J_12 0x9D
|
||||
|
||||
#define K_1 0xA0
|
||||
#define K_2 0xA1
|
||||
#define K_3 0xA2
|
||||
#define K_4 0xA3
|
||||
#define K_5 0xA4
|
||||
#define K_6 0xA5
|
||||
#define K_7 0xA8
|
||||
#define K_8 0xA9
|
||||
#define K_9 0xAA
|
||||
#define K_10 0xAB
|
||||
#define K_11 0xAC
|
||||
#define K_12 0xAD
|
||||
|
||||
#define L_1 0xB0
|
||||
#define L_2 0xB1
|
||||
#define L_3 0xB2
|
||||
#define L_4 0xB3
|
||||
#define L_5 0xB4
|
||||
#define L_6 0xB5
|
||||
#define L_7 0xB8
|
||||
#define L_8 0xB9
|
||||
#define L_9 0xBA
|
||||
#define L_10 0xBB
|
||||
#define L_11 0xBC
|
||||
#define L_12 0xBD
|
||||
|
||||
#endif // IS31FL3737_DRIVER_H
|
||||
@@ -0,0 +1,240 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __AVR__
|
||||
#include <avr/io.h>
|
||||
#include <avr/pgmspace.h>
|
||||
#elif defined(ESP8266)
|
||||
#include <pgmspace.h>
|
||||
#else
|
||||
#define PROGMEM
|
||||
#endif
|
||||
|
||||
// Helidox 8x6 font with QMK Firmware Logo
|
||||
// Online editor: http://teripom.x0.com/
|
||||
|
||||
static 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, 0xA4, 0xA4, 0x9C, 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,
|
||||
0xFC, 0x18, 0x24, 0x24, 0x18, 0x00,
|
||||
0x18, 0x24, 0x24, 0x18, 0xFC, 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,
|
||||
0x40, 0x40, 0x40, 0xF0, 0xF8, 0xF8,
|
||||
0xFF, 0x38, 0xFF, 0xF8, 0xF8, 0x3F,
|
||||
0xF8, 0xF8, 0xFF, 0x38, 0xFF, 0xF8,
|
||||
0xF8, 0xF0, 0x40, 0x40, 0x40, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
|
||||
0xC0, 0xC0, 0xC0, 0x80, 0x00, 0x00,
|
||||
0xC0, 0xC0, 0x80, 0x00, 0x00, 0x00,
|
||||
0x80, 0xC0, 0xC0, 0x00, 0xC0, 0xC0,
|
||||
0x00, 0x00, 0x80, 0xC0, 0xC0, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xC0, 0xC0,
|
||||
0xC0, 0xC0, 0xC0, 0x00, 0xC0, 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, 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, 0xC0, 0xF0, 0xF8, 0xFC, 0x3E,
|
||||
0x1E, 0x06, 0x01, 0x00, 0x00, 0x00,
|
||||
0x7F, 0x41, 0x41, 0x41, 0x7F, 0x00,
|
||||
0x7F, 0x41, 0x41, 0x41, 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,
|
||||
0x49, 0x49, 0x49, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xE0, 0xDF, 0xBF, 0xBF, 0x00,
|
||||
0xBF, 0xBF, 0xDF, 0xE0, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0x49, 0x49, 0x49, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x1F, 0x3F,
|
||||
0x60, 0x60, 0xE0, 0xBF, 0x1F, 0x00,
|
||||
0x7F, 0x7F, 0x07, 0x1E, 0x38, 0x1E,
|
||||
0x07, 0x7F, 0x7F, 0x00, 0x7F, 0x7F,
|
||||
0x0E, 0x1F, 0x3B, 0x71, 0x60, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x7F, 0x7F,
|
||||
0x0C, 0x0C, 0x0C, 0x00, 0x7E, 0x7E,
|
||||
0x00, 0x7F, 0x7E, 0x03, 0x03, 0x00,
|
||||
0x7F, 0x7E, 0x03, 0x03, 0x7E, 0x7E,
|
||||
0x03, 0x03, 0x7F, 0x7E, 0x00, 0x0F,
|
||||
0x3E, 0x70, 0x3C, 0x06, 0x3C, 0x70,
|
||||
0x3E, 0x0F, 0x00, 0x32, 0x7B, 0x49,
|
||||
0x49, 0x3F, 0x7E, 0x00, 0x7F, 0x7E,
|
||||
0x03, 0x03, 0x00, 0x1E, 0x3F, 0x69,
|
||||
0x69, 0x6F, 0x26, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x03, 0x0F, 0x1F, 0x3F, 0x3C,
|
||||
0x78, 0x70, 0x60, 0x00, 0x00, 0x00,
|
||||
0x7F, 0x41, 0x41, 0x41, 0x7F, 0x00,
|
||||
0x7F, 0x41, 0x41, 0x41, 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,
|
||||
0x01, 0x01, 0x01, 0x07, 0x0F, 0x0F,
|
||||
0x7F, 0x0F, 0x7F, 0x0F, 0x0F, 0x7E,
|
||||
0x0F, 0x0F, 0x7F, 0x0F, 0x7F, 0x0F,
|
||||
0x0F, 0x07, 0x01, 0x01, 0x01, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01, 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, 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,
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
The Android robot is reproduced or modified from work created and shared by Google and used according to terms described in the Creative Commons 3.0 Attribution License.
|
||||
|
||||
|
||||
This is the Linux-penguin again...
|
||||
|
||||
Originally drewn by Larry Ewing (http://www.isc.tamu.edu/~lewing/)
|
||||
(with the GIMP) the Linux Logo has been vectorized by me (Simon Budig,
|
||||
http://www.home.unix-ag.org/simon/).
|
||||
|
||||
This happened quite some time ago with Corel Draw 4. But luckily
|
||||
meanwhile there are tools available to handle vector graphics with
|
||||
Linux. Bernhard Herzog (bernhard@users.sourceforge.net) deserves kudos
|
||||
for creating Sketch (http://sketch.sourceforge.net), a powerful free
|
||||
tool for creating vector graphics. He converted the Corel Draw file to
|
||||
the Sketch native format. Since I am unable to maintain the Corel Draw
|
||||
file any longer, the Sketch version now is the "official" one.
|
||||
|
||||
Anja Gerwinski (anja@gerwinski.de) has created an alternate version of
|
||||
the penguin (penguin-variant.sk) with a thinner mouth line and slightly
|
||||
altered gradients. It also features a nifty drop shadow.
|
||||
|
||||
The third bird (penguin-flat.sk) is a version reduced to three colors
|
||||
(black/white/yellow) for e.g. silk screen printing. I made this version
|
||||
for a mug, available at the friendly folks at
|
||||
http://www.kernelconcepts.de/ - they do good stuff, mail Petra
|
||||
(pinguin@kernelconcepts.de) if you need something special or don't
|
||||
understand the german :-)
|
||||
|
||||
These drawings are copyrighted by Larry Ewing and Simon Budig
|
||||
(penguin-variant.sk also by Anja Gerwinski), redistribution is free but
|
||||
has to include this README/Copyright notice.
|
||||
|
||||
The use of these drawings is free. However I am happy about a sample of
|
||||
your mug/t-shirt/whatever with this penguin on it...
|
||||
|
||||
Have fun
|
||||
Simon Budig
|
||||
|
||||
|
||||
Simon.Budig@unix-ag.org
|
||||
http://www.home.unix-ag.org/simon/
|
||||
|
||||
Simon Budig
|
||||
Am Hardtkoeppel 2
|
||||
D-61279 Graevenwiesbach
|
||||
@@ -0,0 +1,531 @@
|
||||
/*
|
||||
Copyright 2019 Ryan Caltabiano <https://github.com/XScorpion2>
|
||||
|
||||
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 "i2c_master.h"
|
||||
#include "oled_driver.h"
|
||||
#include OLED_FONT_H
|
||||
#include "timer.h"
|
||||
#include "print.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#if defined(__AVR__)
|
||||
#include <avr/io.h>
|
||||
#include <avr/pgmspace.h>
|
||||
#elif defined(ESP8266)
|
||||
#include <pgmspace.h>
|
||||
#else // defined(ESP8266)
|
||||
#define PROGMEM
|
||||
#define memcpy_P(des, src, len) memcpy(des, src, len)
|
||||
#endif // defined(__AVR__)
|
||||
|
||||
// Used commands from spec sheet: https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf
|
||||
// Fundamental Commands
|
||||
#define CONTRAST 0x81
|
||||
#define DISPLAY_ALL_ON 0xA5
|
||||
#define DISPLAY_ALL_ON_RESUME 0xA4
|
||||
#define NORMAL_DISPLAY 0xA6
|
||||
#define DISPLAY_ON 0xAF
|
||||
#define DISPLAY_OFF 0xAE
|
||||
|
||||
// Scrolling Commands
|
||||
#define ACTIVATE_SCROLL 0x2F
|
||||
#define DEACTIVATE_SCROLL 0x2E
|
||||
#define SCROLL_RIGHT 0x26
|
||||
#define SCROLL_LEFT 0x27
|
||||
#define SCROLL_RIGHT_UP 0x29
|
||||
#define SCROLL_LEFT_UP 0x2A
|
||||
|
||||
// Addressing Setting Commands
|
||||
#define MEMORY_MODE 0x20
|
||||
#define COLUMN_ADDR 0x21
|
||||
#define PAGE_ADDR 0x22
|
||||
|
||||
// Hardware Configuration Commands
|
||||
#define DISPLAY_START_LINE 0x40
|
||||
#define SEGMENT_REMAP 0xA0
|
||||
#define SEGMENT_REMAP_INV 0xA1
|
||||
#define MULTIPLEX_RATIO 0xA8
|
||||
#define COM_SCAN_INC 0xC0
|
||||
#define COM_SCAN_DEC 0xC8
|
||||
#define DISPLAY_OFFSET 0xD3
|
||||
#define COM_PINS 0xDA
|
||||
|
||||
// Timing & Driving Commands
|
||||
#define DISPLAY_CLOCK 0xD5
|
||||
#define PRE_CHARGE_PERIOD 0xD9
|
||||
#define VCOM_DETECT 0xDB
|
||||
|
||||
// Charge Pump Commands
|
||||
#define CHARGE_PUMP 0x8D
|
||||
|
||||
// Misc defines
|
||||
#define OLED_TIMEOUT 60000
|
||||
#define OLED_BLOCK_COUNT (sizeof(OLED_BLOCK_TYPE) * 8)
|
||||
#define OLED_BLOCK_SIZE (OLED_MATRIX_SIZE / OLED_BLOCK_COUNT)
|
||||
|
||||
// i2c defines
|
||||
#define I2C_CMD 0x00
|
||||
#define I2C_DATA 0x40
|
||||
#if defined(__AVR__)
|
||||
// already defined on ARM
|
||||
#define I2C_TIMEOUT 100
|
||||
#define I2C_TRANSMIT_P(data) i2c_transmit_P((OLED_DISPLAY_ADDRESS << 1), &data[0], sizeof(data), I2C_TIMEOUT)
|
||||
#else // defined(__AVR__)
|
||||
#define I2C_TRANSMIT_P(data) i2c_transmit((OLED_DISPLAY_ADDRESS << 1), &data[0], sizeof(data), I2C_TIMEOUT)
|
||||
#endif // defined(__AVR__)
|
||||
#define I2C_TRANSMIT(data) i2c_transmit((OLED_DISPLAY_ADDRESS << 1), &data[0], sizeof(data), I2C_TIMEOUT)
|
||||
#define I2C_WRITE_REG(mode, data, size) i2c_writeReg((OLED_DISPLAY_ADDRESS << 1), mode, data, size, I2C_TIMEOUT)
|
||||
|
||||
#define HAS_FLAGS(bits, flags) ((bits & flags) == flags)
|
||||
|
||||
// Display buffer's is the same as the OLED memory layout
|
||||
// this is so we don't end up with rounding errors with
|
||||
// parts of the display unusable or don't get cleared correctly
|
||||
// and also allows for drawing & inverting
|
||||
uint8_t oled_buffer[OLED_MATRIX_SIZE];
|
||||
uint8_t* oled_cursor;
|
||||
OLED_BLOCK_TYPE oled_dirty = 0;
|
||||
bool oled_initialized = false;
|
||||
bool oled_active = false;
|
||||
bool oled_scrolling = false;
|
||||
uint8_t oled_rotation = 0;
|
||||
uint8_t oled_rotation_width = 0;
|
||||
#if !defined(OLED_DISABLE_TIMEOUT)
|
||||
uint16_t oled_last_activity;
|
||||
#endif
|
||||
|
||||
// Internal variables to reduce math instructions
|
||||
|
||||
#if defined(__AVR__)
|
||||
// identical to i2c_transmit, but for PROGMEM since all initialization is in PROGMEM arrays currently
|
||||
// probably should move this into i2c_master...
|
||||
static i2c_status_t i2c_transmit_P(uint8_t address, const uint8_t* data, uint16_t length, uint16_t timeout) {
|
||||
i2c_status_t status = i2c_start(address | I2C_WRITE, timeout);
|
||||
|
||||
for (uint16_t i = 0; i < length && status >= 0; i++) {
|
||||
status = i2c_write(pgm_read_byte((const char*)data++), timeout);
|
||||
if (status) break;
|
||||
}
|
||||
|
||||
i2c_stop();
|
||||
|
||||
return status;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Flips the rendering bits for a character at the current cursor position
|
||||
static void InvertCharacter(uint8_t *cursor)
|
||||
{
|
||||
const uint8_t *end = cursor + OLED_FONT_WIDTH;
|
||||
while (cursor < end) {
|
||||
*cursor = ~(*cursor);
|
||||
cursor++;
|
||||
}
|
||||
}
|
||||
|
||||
bool oled_init(uint8_t rotation) {
|
||||
oled_rotation = oled_init_user(rotation);
|
||||
if (!HAS_FLAGS(oled_rotation, OLED_ROTATION_90)) {
|
||||
oled_rotation_width = OLED_DISPLAY_WIDTH;
|
||||
} else {
|
||||
oled_rotation_width = OLED_DISPLAY_HEIGHT;
|
||||
}
|
||||
i2c_init();
|
||||
|
||||
static const uint8_t PROGMEM display_setup1[] = {
|
||||
I2C_CMD,
|
||||
DISPLAY_OFF,
|
||||
DISPLAY_CLOCK, 0x80,
|
||||
MULTIPLEX_RATIO, OLED_DISPLAY_HEIGHT - 1,
|
||||
DISPLAY_OFFSET, 0x00,
|
||||
DISPLAY_START_LINE | 0x00,
|
||||
CHARGE_PUMP, 0x14,
|
||||
MEMORY_MODE, 0x00, }; // Horizontal addressing mode
|
||||
if (I2C_TRANSMIT_P(display_setup1) != I2C_STATUS_SUCCESS) {
|
||||
print("oled_init cmd set 1 failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!HAS_FLAGS(oled_rotation, OLED_ROTATION_180)) {
|
||||
static const uint8_t PROGMEM display_normal[] = {
|
||||
I2C_CMD,
|
||||
SEGMENT_REMAP_INV,
|
||||
COM_SCAN_DEC };
|
||||
if (I2C_TRANSMIT_P(display_normal) != I2C_STATUS_SUCCESS) {
|
||||
print("oled_init cmd normal rotation failed\n");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
static const uint8_t PROGMEM display_flipped[] = {
|
||||
I2C_CMD,
|
||||
SEGMENT_REMAP,
|
||||
COM_SCAN_INC };
|
||||
if (I2C_TRANSMIT_P(display_flipped) != I2C_STATUS_SUCCESS) {
|
||||
print("display_flipped failed\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static const uint8_t PROGMEM display_setup2[] = {
|
||||
I2C_CMD,
|
||||
COM_PINS, 0x02,
|
||||
CONTRAST, 0x8F,
|
||||
PRE_CHARGE_PERIOD, 0xF1,
|
||||
VCOM_DETECT, 0x40,
|
||||
DISPLAY_ALL_ON_RESUME,
|
||||
NORMAL_DISPLAY,
|
||||
DEACTIVATE_SCROLL,
|
||||
DISPLAY_ON };
|
||||
if (I2C_TRANSMIT_P(display_setup2) != I2C_STATUS_SUCCESS) {
|
||||
print("display_setup2 failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
oled_clear();
|
||||
oled_initialized = true;
|
||||
oled_active = true;
|
||||
oled_scrolling = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
__attribute__((weak))
|
||||
oled_rotation_t oled_init_user(oled_rotation_t rotation) {
|
||||
return rotation;
|
||||
}
|
||||
|
||||
void oled_clear(void) {
|
||||
memset(oled_buffer, 0, sizeof(oled_buffer));
|
||||
oled_cursor = &oled_buffer[0];
|
||||
oled_dirty = -1; // -1 will be max value as long as display_dirty is unsigned type
|
||||
}
|
||||
|
||||
static void calc_bounds(uint8_t update_start, uint8_t* cmd_array)
|
||||
{
|
||||
cmd_array[1] = OLED_BLOCK_SIZE * update_start % OLED_DISPLAY_WIDTH;
|
||||
cmd_array[4] = OLED_BLOCK_SIZE * update_start / OLED_DISPLAY_WIDTH;
|
||||
cmd_array[2] = (OLED_BLOCK_SIZE + OLED_DISPLAY_WIDTH - 1) % OLED_DISPLAY_WIDTH + cmd_array[1];
|
||||
cmd_array[5] = (OLED_BLOCK_SIZE + OLED_DISPLAY_WIDTH - 1) / OLED_DISPLAY_WIDTH - 1;
|
||||
}
|
||||
|
||||
static void calc_bounds_90(uint8_t update_start, uint8_t* cmd_array)
|
||||
{
|
||||
cmd_array[1] = OLED_BLOCK_SIZE * update_start / OLED_DISPLAY_HEIGHT * 8;
|
||||
cmd_array[4] = OLED_BLOCK_SIZE * update_start % OLED_DISPLAY_HEIGHT;
|
||||
cmd_array[2] = (OLED_BLOCK_SIZE + OLED_DISPLAY_HEIGHT - 1) / OLED_DISPLAY_HEIGHT * 8 - 1 + cmd_array[1];;
|
||||
cmd_array[5] = (OLED_BLOCK_SIZE + OLED_DISPLAY_HEIGHT - 1) % OLED_DISPLAY_HEIGHT / 8;
|
||||
}
|
||||
|
||||
uint8_t crot(uint8_t a, int8_t n)
|
||||
{
|
||||
const uint8_t mask = 0x7;
|
||||
n &= mask;
|
||||
return a << n | a >> (-n & mask);
|
||||
}
|
||||
|
||||
static void rotate_90(const uint8_t* src, uint8_t* dest)
|
||||
{
|
||||
for (uint8_t i = 0, shift = 7; i < 8; ++i, --shift) {
|
||||
uint8_t selector = (1 << i);
|
||||
for (uint8_t j = 0; j < 8; ++j) {
|
||||
dest[i] |= crot(src[j] & selector, shift - (int8_t)j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void oled_render(void) {
|
||||
// Do we have work to do?
|
||||
if (!oled_dirty || oled_scrolling) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find first dirty block
|
||||
uint8_t update_start = 0;
|
||||
while (!(oled_dirty & (1 << update_start))) { ++update_start; }
|
||||
|
||||
// Set column & page position
|
||||
static uint8_t display_start[] = {
|
||||
I2C_CMD,
|
||||
COLUMN_ADDR, 0, OLED_DISPLAY_WIDTH - 1,
|
||||
PAGE_ADDR, 0, OLED_DISPLAY_HEIGHT / 8 - 1 };
|
||||
if (!HAS_FLAGS(oled_rotation, OLED_ROTATION_90)) {
|
||||
calc_bounds(update_start, &display_start[1]); // Offset from I2C_CMD byte at the start
|
||||
} else {
|
||||
calc_bounds_90(update_start, &display_start[1]); // Offset from I2C_CMD byte at the start
|
||||
}
|
||||
|
||||
// Send column & page position
|
||||
if (I2C_TRANSMIT(display_start) != I2C_STATUS_SUCCESS) {
|
||||
print("oled_render offset command failed\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!HAS_FLAGS(oled_rotation, OLED_ROTATION_90)) {
|
||||
// Send render data chunk as is
|
||||
if (I2C_WRITE_REG(I2C_DATA, &oled_buffer[OLED_BLOCK_SIZE * update_start], OLED_BLOCK_SIZE) != I2C_STATUS_SUCCESS) {
|
||||
print("oled_render data failed\n");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Rotate the render chunks
|
||||
const static uint8_t source_map[] = OLED_SOURCE_MAP;
|
||||
const static uint8_t target_map[] = OLED_TARGET_MAP;
|
||||
|
||||
static uint8_t temp_buffer[OLED_BLOCK_SIZE];
|
||||
memset(temp_buffer, 0, sizeof(temp_buffer));
|
||||
for(uint8_t i = 0; i < sizeof(source_map); ++i) {
|
||||
rotate_90(&oled_buffer[OLED_BLOCK_SIZE * update_start + source_map[i]], &temp_buffer[target_map[i]]);
|
||||
}
|
||||
|
||||
// Send render data chunk after rotating
|
||||
if (I2C_WRITE_REG(I2C_DATA, &temp_buffer[0], OLED_BLOCK_SIZE) != I2C_STATUS_SUCCESS) {
|
||||
print("oled_render data failed\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Turn on display if it is off
|
||||
oled_on();
|
||||
|
||||
// Clear dirty flag
|
||||
oled_dirty &= ~(1 << update_start);
|
||||
}
|
||||
|
||||
void oled_set_cursor(uint8_t col, uint8_t line) {
|
||||
uint16_t index = line * oled_rotation_width + col * OLED_FONT_WIDTH;
|
||||
|
||||
// Out of bounds?
|
||||
if (index >= OLED_MATRIX_SIZE) {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
oled_cursor = &oled_buffer[index];
|
||||
}
|
||||
|
||||
void oled_advance_page(bool clearPageRemainder) {
|
||||
uint16_t index = oled_cursor - &oled_buffer[0];
|
||||
uint8_t remaining = oled_rotation_width - (index % oled_rotation_width);
|
||||
|
||||
if (clearPageRemainder) {
|
||||
// Remaining Char count
|
||||
remaining = remaining / OLED_FONT_WIDTH;
|
||||
|
||||
// Write empty character until next line
|
||||
while (remaining--)
|
||||
oled_write_char(' ', false);
|
||||
} else {
|
||||
// Next page index out of bounds?
|
||||
if (index + remaining >= OLED_MATRIX_SIZE) {
|
||||
index = 0;
|
||||
remaining = 0;
|
||||
}
|
||||
|
||||
oled_cursor = &oled_buffer[index + remaining];
|
||||
}
|
||||
}
|
||||
|
||||
void oled_advance_char(void) {
|
||||
uint16_t nextIndex = oled_cursor - &oled_buffer[0] + OLED_FONT_WIDTH;
|
||||
uint8_t remainingSpace = oled_rotation_width - (nextIndex % oled_rotation_width);
|
||||
|
||||
// Do we have enough space on the current line for the next character
|
||||
if (remainingSpace < OLED_FONT_WIDTH) {
|
||||
nextIndex += remainingSpace;
|
||||
}
|
||||
|
||||
// Did we go out of bounds
|
||||
if (nextIndex >= OLED_MATRIX_SIZE) {
|
||||
nextIndex = 0;
|
||||
}
|
||||
|
||||
// Update cursor position
|
||||
oled_cursor = &oled_buffer[nextIndex];
|
||||
}
|
||||
|
||||
// Main handler that writes character data to the display buffer
|
||||
void oled_write_char(const char data, bool invert) {
|
||||
// Advance to the next line if newline
|
||||
if (data == '\n') {
|
||||
// Old source wrote ' ' until end of line...
|
||||
oled_advance_page(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// copy the current render buffer to check for dirty after
|
||||
static uint8_t oled_temp_buffer[OLED_FONT_WIDTH];
|
||||
memcpy(&oled_temp_buffer, oled_cursor, OLED_FONT_WIDTH);
|
||||
|
||||
// set the reder buffer data
|
||||
uint8_t cast_data = (uint8_t)data; // font based on unsigned type for index
|
||||
if (cast_data < OLED_FONT_START || cast_data > OLED_FONT_END) {
|
||||
memset(oled_cursor, 0x00, OLED_FONT_WIDTH);
|
||||
} else {
|
||||
const uint8_t *glyph = &font[(cast_data - OLED_FONT_START) * OLED_FONT_WIDTH];
|
||||
memcpy_P(oled_cursor, glyph, OLED_FONT_WIDTH);
|
||||
}
|
||||
|
||||
// Invert if needed
|
||||
if (invert) {
|
||||
InvertCharacter(oled_cursor);
|
||||
}
|
||||
|
||||
// Dirty check
|
||||
if (memcmp(&oled_temp_buffer, oled_cursor, OLED_FONT_WIDTH)) {
|
||||
uint16_t index = oled_cursor - &oled_buffer[0];
|
||||
oled_dirty |= (1 << (index / OLED_BLOCK_SIZE));
|
||||
// Edgecase check if the written data spans the 2 chunks
|
||||
oled_dirty |= (1 << ((index + OLED_FONT_WIDTH) / OLED_BLOCK_SIZE));
|
||||
}
|
||||
|
||||
// Finally move to the next char
|
||||
oled_advance_char();
|
||||
}
|
||||
|
||||
void oled_write(const char *data, bool invert) {
|
||||
const char *end = data + strlen(data);
|
||||
while (data < end) {
|
||||
oled_write_char(*data, invert);
|
||||
data++;
|
||||
}
|
||||
}
|
||||
|
||||
void oled_write_ln(const char *data, bool invert) {
|
||||
oled_write(data, invert);
|
||||
oled_advance_page(true);
|
||||
}
|
||||
|
||||
#if defined(__AVR__)
|
||||
void oled_write_P(const char *data, bool invert) {
|
||||
uint8_t c = pgm_read_byte(data);
|
||||
while (c != 0) {
|
||||
oled_write_char(c, invert);
|
||||
c = pgm_read_byte(++data);
|
||||
}
|
||||
}
|
||||
|
||||
void oled_write_ln_P(const char *data, bool invert) {
|
||||
oled_write_P(data, invert);
|
||||
oled_advance_page(true);
|
||||
}
|
||||
#endif // defined(__AVR__)
|
||||
|
||||
bool oled_on(void) {
|
||||
#if !defined(OLED_DISABLE_TIMEOUT)
|
||||
oled_last_activity = timer_read();
|
||||
#endif
|
||||
|
||||
static const uint8_t PROGMEM display_on[] = { I2C_CMD, DISPLAY_ON };
|
||||
if (!oled_active) {
|
||||
if (I2C_TRANSMIT_P(display_on) != I2C_STATUS_SUCCESS) {
|
||||
print("oled_on cmd failed\n");
|
||||
return oled_active;
|
||||
}
|
||||
oled_active = true;
|
||||
}
|
||||
return oled_active;
|
||||
}
|
||||
|
||||
bool oled_off(void) {
|
||||
static const uint8_t PROGMEM display_off[] = { I2C_CMD, DISPLAY_OFF };
|
||||
if (oled_active) {
|
||||
if (I2C_TRANSMIT_P(display_off) != I2C_STATUS_SUCCESS) {
|
||||
print("oled_off cmd failed\n");
|
||||
return oled_active;
|
||||
}
|
||||
oled_active = false;
|
||||
}
|
||||
return !oled_active;
|
||||
}
|
||||
|
||||
bool oled_scroll_right(void) {
|
||||
// Dont enable scrolling if we need to update the display
|
||||
// This prevents scrolling of bad data from starting the scroll too early after init
|
||||
if (!oled_dirty && !oled_scrolling) {
|
||||
static const uint8_t PROGMEM display_scroll_right[] = {
|
||||
I2C_CMD, SCROLL_RIGHT, 0x00, 0x00, 0x00, 0x0F, 0x00, 0xFF, ACTIVATE_SCROLL };
|
||||
if (I2C_TRANSMIT_P(display_scroll_right) != I2C_STATUS_SUCCESS) {
|
||||
print("oled_scroll_right cmd failed\n");
|
||||
return oled_scrolling;
|
||||
}
|
||||
oled_scrolling = true;
|
||||
}
|
||||
return oled_scrolling;
|
||||
}
|
||||
|
||||
bool oled_scroll_left(void) {
|
||||
// Dont enable scrolling if we need to update the display
|
||||
// This prevents scrolling of bad data from starting the scroll too early after init
|
||||
if (!oled_dirty && !oled_scrolling) {
|
||||
static const uint8_t PROGMEM display_scroll_left[] = {
|
||||
I2C_CMD, SCROLL_LEFT, 0x00, 0x00, 0x00, 0x0F, 0x00, 0xFF, ACTIVATE_SCROLL };
|
||||
if (I2C_TRANSMIT_P(display_scroll_left) != I2C_STATUS_SUCCESS) {
|
||||
print("oled_scroll_left cmd failed\n");
|
||||
return oled_scrolling;
|
||||
}
|
||||
oled_scrolling = true;
|
||||
}
|
||||
return oled_scrolling;
|
||||
}
|
||||
|
||||
bool oled_scroll_off(void) {
|
||||
if (oled_scrolling) {
|
||||
static const uint8_t PROGMEM display_scroll_off[] = { I2C_CMD, DEACTIVATE_SCROLL };
|
||||
if (I2C_TRANSMIT_P(display_scroll_off) != I2C_STATUS_SUCCESS) {
|
||||
print("oled_scroll_off cmd failed\n");
|
||||
return oled_scrolling;
|
||||
}
|
||||
oled_scrolling = false;
|
||||
}
|
||||
return !oled_scrolling;
|
||||
}
|
||||
|
||||
uint8_t oled_max_chars(void) {
|
||||
if (!HAS_FLAGS(oled_rotation, OLED_ROTATION_90)) {
|
||||
return OLED_DISPLAY_WIDTH / OLED_FONT_WIDTH;
|
||||
}
|
||||
return OLED_DISPLAY_HEIGHT / OLED_FONT_WIDTH;
|
||||
}
|
||||
|
||||
uint8_t oled_max_lines(void) {
|
||||
if (!HAS_FLAGS(oled_rotation, OLED_ROTATION_90)) {
|
||||
return OLED_DISPLAY_HEIGHT / OLED_FONT_HEIGHT;
|
||||
}
|
||||
return OLED_DISPLAY_WIDTH / OLED_FONT_HEIGHT;
|
||||
}
|
||||
|
||||
void oled_task(void) {
|
||||
if (!oled_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
oled_set_cursor(0, 0);
|
||||
|
||||
oled_task_user();
|
||||
|
||||
// Smart render system, no need to check for dirty
|
||||
oled_render();
|
||||
|
||||
// Display timeout check
|
||||
#if !defined(OLED_DISABLE_TIMEOUT)
|
||||
if (oled_active && timer_elapsed(oled_last_activity) > OLED_TIMEOUT) {
|
||||
oled_off();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
__attribute__((weak))
|
||||
void oled_task_user(void) {
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
Copyright 2019 Ryan Caltabiano <https://github.com/XScorpion2>
|
||||
|
||||
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/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
|
||||
#if defined(OLED_DISPLAY_CUSTOM)
|
||||
// Expected user to implement the necessary defines
|
||||
#elif defined(OLED_DISPLAY_128X64)
|
||||
// Double height 128x64
|
||||
#define OLED_DISPLAY_WIDTH 128
|
||||
#define OLED_DISPLAY_HEIGHT 64
|
||||
#define OLED_MATRIX_SIZE (OLED_DISPLAY_HEIGHT / 8 * OLED_DISPLAY_WIDTH) // 1024 (compile time mathed)
|
||||
#define OLED_BLOCK_TYPE uint32_t
|
||||
#define OLED_BLOCK_COUNT (sizeof(OLED_BLOCK_TYPE) * 8) // 32 (compile time mathed)
|
||||
#define OLED_BLOCK_SIZE (OLED_MATRIX_SIZE / OLED_BLOCK_COUNT) // 32 (compile time mathed)
|
||||
|
||||
// For 90 degree rotation, we map our internal matrix to oled matrix using fixed arrays
|
||||
// The OLED writes to it's memory horizontally, starting top left, but our memory starts bottom left in this mode
|
||||
#define OLED_SOURCE_MAP { 32, 40, 48, 56 }
|
||||
#define OLED_TARGET_MAP { 24, 16, 8, 0 }
|
||||
// If OLED_BLOCK_TYPE is uint16_t, these tables would look like:
|
||||
// #define OLED_SOURCE_MAP { 0, 8, 16, 24, 32, 40, 48, 56 }
|
||||
// #define OLED_TARGET_MAP { 56, 48, 40, 32, 24, 16, 8, 0 }
|
||||
// If OLED_BLOCK_TYPE is uint8_t, these tables would look like:
|
||||
// #define OLED_SOURCE_MAP { 0, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120 }
|
||||
// #define OLED_TARGET_MAP { 56, 120, 48, 112, 40, 104, 32, 96, 24, 88, 16, 80, 8, 72, 0, 64 }
|
||||
#else // defined(OLED_DISPLAY_128X64)
|
||||
// Default 128x32
|
||||
#define OLED_DISPLAY_WIDTH 128
|
||||
#define OLED_DISPLAY_HEIGHT 32
|
||||
#define OLED_MATRIX_SIZE (OLED_DISPLAY_HEIGHT / 8 * OLED_DISPLAY_WIDTH) // 512 (compile time mathed)
|
||||
#define OLED_BLOCK_TYPE uint16_t // Type to use for segmenting the oled display for smart rendering, use unsigned types only
|
||||
#define OLED_BLOCK_COUNT (sizeof(OLED_BLOCK_TYPE) * 8) // 16 (compile time mathed)
|
||||
#define OLED_BLOCK_SIZE (OLED_MATRIX_SIZE / OLED_BLOCK_COUNT) // 32 (compile time mathed)
|
||||
|
||||
// For 90 degree rotation, we map our internal matrix to oled matrix using fixed arrays
|
||||
// The OLED writes to it's memory horizontally, starting top left, but our memory starts bottom left in this mode
|
||||
#define OLED_SOURCE_MAP { 0, 8, 16, 24 }
|
||||
#define OLED_TARGET_MAP { 24, 16, 8, 0 }
|
||||
// If OLED_BLOCK_TYPE is uint8_t, these tables would look like:
|
||||
// #define OLED_SOURCE_MAP { 0, 8, 16, 24, 32, 40, 48, 56 }
|
||||
// #define OLED_TARGET_MAP { 48, 32, 16, 0, 56, 40, 24, 8 }
|
||||
#endif // defined(OLED_DISPLAY_CUSTOM)
|
||||
|
||||
// Address to use for tthe i2d oled communication
|
||||
#if !defined(OLED_DISPLAY_ADDRESS)
|
||||
#define OLED_DISPLAY_ADDRESS 0x3C
|
||||
#endif
|
||||
|
||||
// Custom font file to use
|
||||
#if !defined(OLED_FONT_H)
|
||||
#define OLED_FONT_H "glcdfont.c"
|
||||
#endif
|
||||
// unsigned char value of the first character in the font file
|
||||
#if !defined(OLED_FONT_START)
|
||||
#define OLED_FONT_START 0
|
||||
#endif
|
||||
// unsigned char value of the last character in the font file
|
||||
#if !defined(OLED_FONT_END)
|
||||
#define OLED_FONT_END 224
|
||||
#endif
|
||||
// Font render width
|
||||
#if !defined(OLED_FONT_WIDTH)
|
||||
#define OLED_FONT_WIDTH 6
|
||||
#endif
|
||||
// Font render height
|
||||
#if !defined(OLED_FONT_HEIGHT)
|
||||
#define OLED_FONT_HEIGHT 8
|
||||
#endif
|
||||
|
||||
// OLED Rotation enum values are flags
|
||||
typedef enum {
|
||||
OLED_ROTATION_0 = 0,
|
||||
OLED_ROTATION_90 = 1,
|
||||
OLED_ROTATION_180 = 2,
|
||||
OLED_ROTATION_270 = 3, // OLED_ROTATION_90 | OLED_ROTATION_180
|
||||
} oled_rotation_t;
|
||||
|
||||
// Initialize the oled display, rotating the rendered output based on the define passed in.
|
||||
// Returns true if the OLED was initialized successfully
|
||||
bool oled_init(oled_rotation_t rotation);
|
||||
|
||||
// Called at the start of oled_init, weak function overridable by the user
|
||||
// rotation - the value passed into oled_init
|
||||
// Return new oled_rotation_t if you want to override default rotation
|
||||
oled_rotation_t oled_init_user(oled_rotation_t rotation);
|
||||
|
||||
// Clears the display buffer, resets cursor position to 0, and sets the buffer to dirty for rendering
|
||||
void oled_clear(void);
|
||||
|
||||
// Renders the dirty chunks of the buffer to oled display
|
||||
void oled_render(void);
|
||||
|
||||
// Moves cursor to character position indicated by column and line, wraps if out of bounds
|
||||
// Max column denoted by 'oled_max_chars()' and max lines by 'oled_max_lines()' functions
|
||||
void oled_set_cursor(uint8_t col, uint8_t line);
|
||||
|
||||
// Advances the cursor to the next page, writing ' ' if true
|
||||
// Wraps to the begining when out of bounds
|
||||
void oled_advance_page(bool clearPageRemainder);
|
||||
|
||||
// Moves the cursor forward 1 character length
|
||||
// Advance page if there is not enough room for the next character
|
||||
// Wraps to the begining when out of bounds
|
||||
void oled_advance_char(void);
|
||||
|
||||
// Writes a single character to the buffer at current cursor position
|
||||
// Advances the cursor while writing, inverts the pixels if true
|
||||
// Main handler that writes character data to the display buffer
|
||||
void oled_write_char(const char data, bool invert);
|
||||
|
||||
// Writes a string to the buffer at current cursor position
|
||||
// Advances the cursor while writing, inverts the pixels if true
|
||||
void oled_write(const char *data, bool invert);
|
||||
|
||||
// Writes a string to the buffer at current cursor position
|
||||
// Advances the cursor while writing, inverts the pixels if true
|
||||
// Advances the cursor to the next page, wiring ' ' to the remainder of the current page
|
||||
void oled_write_ln(const char *data, bool invert);
|
||||
|
||||
#if defined(__AVR__)
|
||||
// Writes a PROGMEM string to the buffer at current cursor position
|
||||
// Advances the cursor while writing, inverts the pixels if true
|
||||
// Remapped to call 'void oled_write(const char *data, bool invert);' on ARM
|
||||
void oled_write_P(const char *data, bool invert);
|
||||
|
||||
// Writes a PROGMEM string to the buffer at current cursor position
|
||||
// Advances the cursor while writing, inverts the pixels if true
|
||||
// Advances the cursor to the next page, wiring ' ' to the remainder of the current page
|
||||
// Remapped to call 'void oled_write_ln(const char *data, bool invert);' on ARM
|
||||
void oled_write_ln_P(const char *data, bool invert);
|
||||
#else
|
||||
// Writes a string to the buffer at current cursor position
|
||||
// Advances the cursor while writing, inverts the pixels if true
|
||||
#define oled_write_P(data, invert) oled_write(data, invert)
|
||||
|
||||
// Writes a string to the buffer at current cursor position
|
||||
// Advances the cursor while writing, inverts the pixels if true
|
||||
// Advances the cursor to the next page, wiring ' ' to the remainder of the current page
|
||||
#define oled_write_ln_P(data, invert) oled_write(data, invert)
|
||||
#endif // defined(__AVR__)
|
||||
|
||||
// Can be used to manually turn on the screen if it is off
|
||||
// Returns true if the screen was on or turns on
|
||||
bool oled_on(void);
|
||||
|
||||
// Can be used to manually turn off the screen if it is on
|
||||
// Returns true if the screen was off or turns off
|
||||
bool oled_off(void);
|
||||
|
||||
// Basically it's oled_render, but with timeout management and oled_task_user calling!
|
||||
void oled_task(void);
|
||||
|
||||
// Called at the start of oled_task, weak function overridable by the user
|
||||
void oled_task_user(void);
|
||||
|
||||
// Scrolls the entire display right
|
||||
// Returns true if the screen was scrolling or starts scrolling
|
||||
// NOTE: display contents cannot be changed while scrolling
|
||||
bool oled_scroll_right(void);
|
||||
|
||||
// Scrolls the entire display left
|
||||
// Returns true if the screen was scrolling or starts scrolling
|
||||
// NOTE: display contents cannot be changed while scrolling
|
||||
bool oled_scroll_left(void);
|
||||
|
||||
// Turns off display scrolling
|
||||
// Returns true if the screen was not scrolling or stops scrolling
|
||||
bool oled_scroll_off(void);
|
||||
|
||||
// Returns the maximum number of characters that will fit on a line
|
||||
uint8_t oled_max_chars(void);
|
||||
|
||||
// Returns the maximum number of lines that will fit on the oled
|
||||
uint8_t oled_max_lines(void);
|
||||
@@ -28,6 +28,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "micro_oled.h"
|
||||
#include <print.h>
|
||||
#include <stdlib.h>
|
||||
#include "util/font5x7.h"
|
||||
#include "util/font8x16.h"
|
||||
@@ -309,12 +310,11 @@ static uint8_t micro_oled_screen_buffer[] = {
|
||||
#else
|
||||
//catchall for custom screen szies
|
||||
static uint8_t micro_oled_screen_buffer[LCDWIDTH*LCDWIDTH/8] = {0};
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
void micro_oled_init(void) {
|
||||
|
||||
i2c_init();
|
||||
i2c_start(I2C_ADDRESS_SA0_1);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user