Page 1 of 1

TMK Firmware Question

Posted: 16 Dec 2015, 17:14
by breh
I have been using a fork of TMK's firmware (QMK - Jack's firmware) for a keypad (numberpad) PCB that I purchased. I am able to push a working keymapping to the device with the pinout that I was able to probe with a multimeter. Firmware pretty much worked out of the box (repo) once I remapped my pinout and set my col2row diode direction for my PCB.
QMK/TMK works very well and is very easy to setup for someone who has very little real programming experience. I plan to put out a build-log to help others who may want to try. It's been a real journey and I've learned a lot.
I had a question to anyone who has experience working with the TMK firmware code regarding LED control.

The PCB uses an ATMEGA32U4. The PCB has two sets of LEDs:
  • Numlock in-switch LED
  • All other in-switch LEDs and LEDs on the perimeter of the underside of the PCB
The "all other LEDs" are controlled via pin PB6 through a chip labeled "a6shb" which seems to be a transistor/mosfet. I am able to control these LEDs by modifying "led.c" code in the TMK firmware to write to PB6. I'm hoping to eventually use the backlight.c code to control this port, but I'm not able to find where in the code structure TMK designates the backlight LED control port.
Does anyone know how to designate a control pin for the backlight LED code in TMK?

The Numlock LED is controlled independently. I probed the path for the diode/resistor for the numlock key numerous times and I'm almost 100% this is correct: the signal for the numlock LED flows from the VBus (Pin 7 - "USB VBUS monitor input" according to spec. sheet) and ends at port PD6 (Pin 26). Can anyone explain why the designer would do this and how I could control the Numlock LED in this configuration?
How would you control an LED with power flowing from VBUS to PD6 pins on ATMEGA32u4?

I appreciate any and all help I can get from your experts! If I need to provide any additional details, I will be more than happy to!

Feel free to move this post to a more appropriate forum!

Thanks!

Posted: 16 Dec 2015, 18:15
by Halvar
Numlock LED: I don't know how it's done on your PCB, but on the Teensy, and I think that's how it's supposed to be, VBUS is directly connected to the +5V input of the USB port. So I'm pretty sure the designer just used it as +5V. If you use PD6 as an output, you can either set it to low (GND), in which case a current flows from +5v through the LED and the LED'S resistor to PD6 and the LED lights up, or you can set it to "high" (+5V), in which case no current can flow and the LED goes off. You can configure that to work by modifying led.c

Backlight: which backlight.c in a keyboard folder did you start with? The examples in the TMK code all have more than one backlight region, so you mainly need to simplify that code. If I'm looking at keyboard\lightsaber\backlight.c and backlight.h for example, I see a few parts for the different regions that all look like this:

Code: Select all

void backlight_set(uint8_t level)
{
...
    // Set as output.
    DDRE |= (1<<6);
...
    if (level & BACKLIGHT_FROW)
    {
        PORTE |= (1<<6);
    }
    else
    {
        PORTE &= ~(1<<6);
    }
...
}
So as you can see, this is for pin PE6, and it's easy to change for PB6:

Code: Select all

void backlight_set(uint8_t level)
{
...
    // Set as output.
    DDRB |= (1<<6);
...
    if (level & BACKLIGHT_FROW)
    {
        PORTB |= (1<<6);
    }
    else
    {
        PORTB &= ~(1<<6);
    }
...
}
You'll also need to change backlight.h if you only support one level of on or off, and config.h, and the fn_actions[] array in your keymap, and include backlight.c in your Makefile. Look at the lightsaber for an example on how to do it..

Posted: 17 Dec 2015, 05:23
by breh
Halvar, thanks! Everything you said was on point and got me going in the right direction.

Numlock LED
VUSB is indeed tied to VCC as the spec. sheet and ATMEL literature tends to point out. I reprobed the pins carefully with my bent/dulled Fluke probes and got proper contact. I modified led.c to do as you said and pulled "low" for on, but pulling "high" for off didn't work - I had to use matt3o's example for setting the high state by making the pin a high impedance input (I think at least). I don't know enough about coding with bit shifting and bitwise operators, so I just trusted the code comments and it worked out. http://deskthority.net/workshop-f7/how- ... t7177.html.

Code: Select all

void led_set(uint8_t usb_led)
{
    if (usb_led & (1<<USB_LED_CAPS_LOCK)) {
        // output low
        DDRD |= (1<<4);
        PORTD &= ~(1<<4);
    } else {
        // Hi-Z
        DDRD &= ~(1<<4);
        PORTD &= ~(1<<4);
    }
}
Backlight
I was using the backlight.c from TMK for the planck that had PWM control of the backlight pin. Luckily the PCB designer kept the LEDs on a PWM capable pin (PB6). The code is this one for the plank from JackHUmbert's TMK fork: https://github.com/jackhumbert/qmk_firm ... acklight.c I simply had to modify the pin number and COM address. This whole journey is well beyond any amount of coding I'm used to but it has been interesting.
It offers dimming via PWM control but at the end of the day I could have probably implemented the binary LED state code that you had posted.

Eeeeeverything is working now and I have fully controllable LEDs and programmable layers and keymaps. Now I just have to decide what I would possibly need on the extra keymap layers :)

Thanks again for the quick and concise reply.

http://www.gfycat.com/SilentDimGypsymoth

Posted: 17 Dec 2015, 12:53
by Halvar
Good to see, congrats for getting it to work! To have the backlights dimmable is probably quite useful.

About the Numlock LED and setting the pin to output/high vs. input/no pullup AFAIR both of them worked for me, but switching it to input mode like you did is the cleaner solution.

Posted: 18 Nov 2016, 01:01
by Cortes
breh wrote:
Backlight
I was using the backlight.c from TMK for the planck that had PWM control of the backlight pin. Luckily the PCB designer kept the LEDs on a PWM capable pin (PB6). The code is this one for the plank from JackHUmbert's TMK fork: https://github.com/jackhumbert/qmk_firm ... acklight.c I simply had to modify the pin number and COM address. This whole journey is well beyond any amount of coding I'm used to but it has been interesting.
It offers dimming via PWM control but at the end of the day I could have probably implemented the binary LED state code that you had posted.

Eeeeeverything is working now and I have fully controllable LEDs and programmable layers and keymaps. Now I just have to decide what I would possibly need on the extra keymap layers :)

Thanks again for the quick and concise reply.

http://www.gfycat.com/SilentDimGypsymoth
Hi, could you paste the backlight.c code?
The link you put did not work anymore.

Posted: 21 Nov 2016, 04:36
by breh
Bear with me as it has been a while since I've touched the code. I believe the following is what I had modified to work with my keypad.

Literally what I did was DMM the pins on the ATMEGA and find out how my PCB LEDs were wired (to which ports). Then I read the ATMEGA data sheet as the comments halfway through the code suggests. Then I changed the controlled port to PB6 as that is what my LED PWM is on and changed the timer controller to the appropriate one corresponding to the port, and also chose the correct COM port. (Basically read the spec sheet). I did not write 99% of this code I believe the version I have is from the author at the head of the code.

Code: Select all

/*
Copyright 2013 Mathias Andersson <wraul@dbox.se>

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 "backlight.h"
#include "eeconfig.h"
#include "debug.h"

backlight_config_t backlight_config;

void backlight_init(void)
{
    //check signature 
    if (!eeconfig_is_enabled()) {
        eeconfig_init();
    }
    backlight_config.raw = eeconfig_read_backlight();
    backlight_set(backlight_config.enable ? backlight_config.level : 0);
}

void backlight_increase(void)
{
    if(backlight_config.level < BACKLIGHT_LEVELS)
    {
        backlight_config.level++;
        backlight_config.enable = 1;
        eeconfig_write_backlight(backlight_config.raw);
    }
    dprintf("backlight increase: %u\n", backlight_config.level);
    backlight_set(backlight_config.level);
}

void backlight_decrease(void)
{
    if(backlight_config.level > 0)
    {
        backlight_config.level--;
        backlight_config.enable = !!backlight_config.level;
        eeconfig_write_backlight(backlight_config.raw);
    }
    dprintf("backlight decrease: %u\n", backlight_config.level);
    backlight_set(backlight_config.level);
}

void backlight_toggle(void)
{
    backlight_config.enable ^= 1;
    eeconfig_write_backlight(backlight_config.raw);
    dprintf("backlight toggle: %u\n", backlight_config.enable);
    backlight_set(backlight_config.enable ? backlight_config.level : 0);
}

void backlight_step(void)
{
    backlight_config.level++;
    if(backlight_config.level > BACKLIGHT_LEVELS)
    {
        backlight_config.level = 0;
    }
    backlight_config.enable = !!backlight_config.level;
    eeconfig_write_backlight(backlight_config.raw);
    dprintf("backlight step: %u\n", backlight_config.level);
    backlight_set(backlight_config.level);
}

void backlight_level(uint8_t level)
{
    backlight_config.level ^= level;
    backlight_config.enable = !!backlight_config.level;
    eeconfig_write_backlight(backlight_config.raw);
    backlight_set(backlight_config.level);
}

// Plank Code for Backlight

//#include <avr/io.h>
//#include "backlight.h"

#define CHANNEL OCR1B
// Plank is OCR1C
void backlight_init_ports()
{

    // Setup PB7 as output and output low.
    DDRB |= (1<<6);
    PORTB &= ~(1<<6);
    
    // Use full 16-bit resolution. 
    ICR1 = 0xFFFF;

    // I could write a wall of text here to explain... but TL;DW
    // Go read the ATmega32u4 datasheet.
    // And this: http://blog.saikoled.com/post/43165849837/secret-konami-cheat-code-to-high-resolution-pwm-on
    
    // Pin PB7 = OCR1C (Timer 1, Channel C)
	// Pin PB6 = OCR1B (Timer 1, Channel B)
    // Compare Output Mode = Clear on compare match, Channel C = COM1C1=1 COM1C0=0
	// Compare Output Mode = Clear on compare match, Channel C = COM1B1=1 COM1B0=0
    // (i.e. start high, go low when counter matches.)
    // WGM Mode 14 (Fast PWM) = WGM13=1 WGM12=1 WGM11=1 WGM10=0
    // Clock Select = clk/1 (no prescaling) = CS12=0 CS11=0 CS10=1
    
    TCCR1A = _BV(COM1B1) | _BV(WGM11); // = 0b00001010;
    TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS10); // = 0b00011001;

    backlight_init();
}

void backlight_set(uint8_t level)
{
    if ( level == 0 )
    {
        // Turn off PWM control on PB6, revert to output low.
        TCCR1A &= ~(_BV(COM1B1));
        CHANNEL = 0x0;
        // Prevent backlight blink on lowest level
        PORTB &= ~(_BV(PORTB6));
    }
    else if ( level == BACKLIGHT_LEVELS )
    {
        // Prevent backlight blink on lowest level
        PORTB &= ~(_BV(PORTB6));
        // Turn on PWM control of PB6
        TCCR1A |= _BV(COM1B1);
        // Set the brightness
        CHANNEL = 0xFFFF;
    }
    else        
    {
        // Prevent backlight blink on lowest level
        PORTB &= ~(_BV(PORTB6));
        // Turn on PWM control of PB6
        TCCR1A |= _BV(COM1B1);
        // Set the brightness
        CHANNEL = 0xFFFF >> ((BACKLIGHT_LEVELS - level) * ((BACKLIGHT_LEVELS + 1) / 2));
    }
}

Posted: 21 Nov 2016, 04:53
by breh
Ok I think I found out what I did, I combined the two following backlight.c codes into one so that I could get PWM control with adjustable brightness. The code authors are listed at the start of each block:

Portion from TMK firmware

Code: Select all

/*
Copyright 2013 Mathias Andersson <wraul@dbox.se>

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 "backlight.h"
#include "eeconfig.h"
#include "debug.h"

backlight_config_t backlight_config;

void backlight_init(void)
{
    /* check signature */
    if (!eeconfig_is_enabled()) {
        eeconfig_init();
    }
    backlight_config.raw = eeconfig_read_backlight();
    backlight_set(backlight_config.enable ? backlight_config.level : 0);
}

void backlight_increase(void)
{
    if(backlight_config.level < BACKLIGHT_LEVELS)
    {
        backlight_config.level++;
        backlight_config.enable = 1;
        eeconfig_write_backlight(backlight_config.raw);
    }
    dprintf("backlight increase: %u\n", backlight_config.level);
    backlight_set(backlight_config.level);
}

void backlight_decrease(void)
{
    if(backlight_config.level > 0)
    {
        backlight_config.level--;
        backlight_config.enable = !!backlight_config.level;
        eeconfig_write_backlight(backlight_config.raw);
    }
    dprintf("backlight decrease: %u\n", backlight_config.level);
    backlight_set(backlight_config.level);
}

void backlight_toggle(void)
{
    backlight_config.enable ^= 1;
    eeconfig_write_backlight(backlight_config.raw);
    dprintf("backlight toggle: %u\n", backlight_config.enable);
    backlight_set(backlight_config.enable ? backlight_config.level : 0);
}

void backlight_step(void)
{
    backlight_config.level++;
    if(backlight_config.level > BACKLIGHT_LEVELS)
    {
        backlight_config.level = 0;
    }
    backlight_config.enable = !!backlight_config.level;
    eeconfig_write_backlight(backlight_config.raw);
    dprintf("backlight step: %u\n", backlight_config.level);
    backlight_set(backlight_config.level);
}

void backlight_level(uint8_t level)
{
    backlight_config.level ^= level;
    backlight_config.enable = !!backlight_config.level;
    eeconfig_write_backlight(backlight_config.raw);
    backlight_set(backlight_config.level);
}
Portion of code from "Quantum" QMK firmware retrieved from JackHumbert's Git. No author in comments blocks.

Code: Select all

#include <avr/io.h>
#include "backlight.h"

#define CHANNEL OCR1C

void backlight_init_ports()
{

    // Setup PB7 as output and output low.
    DDRB |= (1<<7);
    PORTB &= ~(1<<7);
    
    // Use full 16-bit resolution. 
    ICR1 = 0xFFFF;

    // I could write a wall of text here to explain... but TL;DW
    // Go read the ATmega32u4 datasheet.
    // And this: http://blog.saikoled.com/post/43165849837/secret-konami-cheat-code-to-high-resolution-pwm-on
    
    // Pin PB7 = OCR1C (Timer 1, Channel C)
    // Compare Output Mode = Clear on compare match, Channel C = COM1C1=1 COM1C0=0
    // (i.e. start high, go low when counter matches.)
    // WGM Mode 14 (Fast PWM) = WGM13=1 WGM12=1 WGM11=1 WGM10=0
    // Clock Select = clk/1 (no prescaling) = CS12=0 CS11=0 CS10=1
    
    TCCR1A = _BV(COM1C1) | _BV(WGM11); // = 0b00001010;
    TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS10); // = 0b00011001;

    backlight_init();
}

void backlight_set(uint8_t level)
{
    if ( level == 0 )
    {
        // Turn off PWM control on PB7, revert to output low.
        TCCR1A &= ~(_BV(COM1C1));
        CHANNEL = 0x0;
        // Prevent backlight blink on lowest level
        PORTB &= ~(_BV(PORTB7));
    }
    else if ( level == BACKLIGHT_LEVELS )
    {
        // Prevent backlight blink on lowest level
        PORTB &= ~(_BV(PORTB7));
        // Turn on PWM control of PB7
        TCCR1A |= _BV(COM1C1);
        // Set the brightness
        CHANNEL = 0xFFFF;
    }
    else        
    {
        // Prevent backlight blink on lowest level
        PORTB &= ~(_BV(PORTB7));
        // Turn on PWM control of PB7
        TCCR1A |= _BV(COM1C1);
        // Set the brightness
        CHANNEL = 0xFFFF >> ((BACKLIGHT_LEVELS - level) * ((BACKLIGHT_LEVELS + 1) / 2));
    }
}
With these two comment blocks and the one I pasted before (the one I actually compiled) you should be able to see what I changed to get the firmware working for me.