Pulling Rotary Encoder State

When it comes to microcontrollers reading a rotary encoder, I often see it being done using interrupts. While that is not necessarily the wrong thing to do, it can crowd the interrupt handler with something that's not really critical functionality, especially if microcontroller can handle it within its base loop just as effectively.

To start with, the code is available here and consists of two exported functions:

void rotary_init(void);
enum ROTARY_DIRECTION rotary_getDirection(void);

If we disregards rotary_init which just initializes a few variables, all the significant code is in rotary_getDirection function. To use it, simply call this function once in a while from the main loop, and it will return one of three values (ROTARY_DIRECTION_NONE, ROTARY_DIRECTION_LEFT, or ROTARY_DIRECTION_RIGHT) corresponding to the detected movement.

The heart of the functionality is in the following code:

    uint8_t currRotState = getRotaryState();
    if (currRotState != lastRotState) {
        histRotState = (uint8_t)(histRotState << 2) | currRotState;
        lastRotState = currRotState;
        uint8_t filteredRotState = histRotState & 0x3F;
        if (filteredRotState == 0x07) { return ROTARY_DIRECTION_LEFT; }
        if (filteredRotState == 0x0B) { return ROTARY_DIRECTION_RIGHT; }
    }
    return ROTARY_DIRECTION_NONE;

First, we get the state in binary for each of the 2 contacts available. Thus, the output can be either 0x00, 0x01, 0b10, or 0b11, depending on which contacts are closed. If the state has changed compared to the previous reading, we append it to the variable used to store previous states. As each state is 2 bits long, this means we can easily fit all 4 states in a single 8-bit value by shifting it 2 bits at a time.

If your switch is new and shiny, this miis where you might stop. However, in the real world, switches are dirty and thus can skip a state or two. For example, one of my rotary encoders would skip a state every once in a while. This means that a proper code would simply make experience worse as time goes by.

However, due to redundancies in how the encoder functions, detecting three neighboring states still gives you plenty of information to go by without misidentifying the direction of scrolling. In my code, detection starts when both encoder contacts are active (i.e. 0b11 state). On all encoders I worked with (admitely, mostly PEC12R series from Bourns), this actually nicely aligns with steps and almost perfectly filters wear-induced noise.

And yes, you can adjust the code slightly to make it run in an interrupt routine if you are so inclined.

Different DHCP for Windows/Linux Dual Boot

While I am using Linux as my daily driver, I still need to use Windows from time to time. This means that I have both systems to back up. As I try to be a good boy, all my backup scripts are running on my server. However, due to how they are written, it's rather hard to distinguish which system is running at what time. One way to solve this would be to rewrite the scripts to enable auto-detection, but a simpler alternative would be to assign different IP addresses to Windows and Linux thus leaving working scripts alone. Using a static IP could be a simple solution, but where's the fun in that? I decided to make my Mikrotik router assign a different DHCP address depending on which system I boot.

When it comes to Mikrotik routers, the configuration options for delivering DHCP are quite rich but not very flexible when matching inputs. Yes, there is an option to recognize the OS (vendor-class), but this requires a different pool for those devices. If you want to match inputs without managing multiple pools, mac-address and client-id are only differentiators. Since I don't want to mess with MAC address, choice is clear.

In Windows, it is hard to alter client identifier, so I decided not to alter its values at all. But Linux, as always, is a land of opportunity. So, to change the class identifier, it's enough to just tell the network manager you want to do so. In the example below, I wanted to change the client identifier on a wireless network named Ursa Major:

nmcli con modify "Ursa Major" ipv4.dhcp-client-id "Ubuntu"

Alternatively, you can edit a file in /etc/netplan (the exact name varies per system; it was /etc/netplan/90-NM-c70925a8-cdca-491a-a6b3-db2263db425a.yaml for me). There, just add the ipv4.dhcp-client-id: "<whatever>" setting under the networkmanager key. My file looked something like this (some sections omitted):

network:
  version: 2
  wifis:
    NM-c70925a8-cdca-491a-a6b3-db2263db425a:
      access-points:
        "Ursa Major":
          networkmanager:
            uuid: "c70925a8-cdca-491a-a6b3-db2263db425a"
            name: "Ursa Major"
            passthrough:
              ipv4.dhcp-client-id: "Ubuntu"

Combine either of these changes with RouterOS client-id matching, and you have the same machine using different IPs, depending on the OS it booted in.

Powering Mikrotik Router from Netgear GS305EP

Occasionally I find myself with piece of information that is unlikely to help anybody other than me in future. But that never prevented me from making a post before, so why should it now. In the left corner, we have my existing Mikrotik Audience router that yearns for some power via passive PoE. And in the right corner we have Netgear GS305EP PoE switch that'll give its best using 802.3af/at. Can we make them play together?

Short answer is no - you cannot power router that uses passive PoE from a switch that provides only 802.3af/at. But... Depending on both the exact passive PoE device and how the active PoE switch is doing detection, you might get lucky and things will work.

First prerequisite is for client device to support 56V on PoE input. Anything lower than that and you'll probably fry your device if you attempt powering it. Second prerequisite is that active PoE device supports legacy mode where resistors are used to determine power level. And lastly, your client device needs to have those resistors even though it doesn't support active PoE as such. In Settings this would look like this:

Power Mode .....: Pre-802.3at
Power Limit Type: User
Power Limit (W) : 30.0
Detection Type .: 4pt 802.3af + Legacy

If you are lucky with all that, you might have power flowing between two officially incompatible devices.

Caveat emptor!

Repurposing Airplane Mode

Having QMK based keyboard on Framework 16 gives quite a lot of flexibility to change keyboard mapping to whatever suits you. The only problem is that the default layout is as good as it gets considering the key count. So, what can we even improve? Well, how about using Airplane Mode key for something useful? Well, that actually isn't as straightforward as it could be.

Due to how ISO keyboard definitions are made, airplane mode key gets processed before it hits keymap.c. So, we can go a bit deeper in quantum definitions and edit keymap_common.c. Default definition is:

case KC_AIRPLANE_MODE:
    action.code = ACTION_USAGE_RADIO;

To make it do something else (for example, start file manager), we just give it the correct code. In given example that would be KC_MY_COMPUTER:

case KC_AIRPLANE_MODE:
    action.code = ACTION_USAGE_CONSUMER(KEYCODE2CONSUMER(KC_MY_COMPUTER));

Compile and flash, and you can enjoy additional macro key instead of accidentally killing your network.