MustafaMulla29/wireless-mouse-pcb

A tscircuit package created by MustafaMulla29.

Version
1.0.16
License
unset
Stars
0

firmware/src/main.c

/* SPDX-License-Identifier: Apache-2.0 */

#include "hid_mouse.h"
#include "pmw3610.h"
#include "status_led.h"

#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <zephyr/bluetooth/services/bas.h>
#include <zephyr/device.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/util.h>

#define PIN_DPI      0
#define PIN_MIDDLE   3
#define PIN_ENC_B    5
#define PIN_ENC_A    7
#define PIN_RIGHT    8
#define PIN_LEFT     18
#define PIN_SIDE1    27
#define PIN_SIDE2    29

#define BUTTON_LEFT   BIT(0)
#define BUTTON_RIGHT  BIT(1)
#define BUTTON_MIDDLE BIT(2)
#define BUTTON_SIDE1  BIT(3)
#define BUTTON_SIDE2  BIT(4)

#define REPORT_PERIOD_MS 2
#define BATTERY_PERIOD_MS 60000
#define BATTERY_SETTLE_MS 250

static const struct device *const gpio0 = DEVICE_DT_GET(DT_NODELABEL(gpio0));
static const struct adc_dt_spec battery_adc =
	ADC_DT_SPEC_GET_BY_IDX(DT_PATH(zephyr_user), 0);

static uint8_t read_buttons(void)
{
	uint8_t buttons = 0;

	if (gpio_pin_get(gpio0, PIN_LEFT) == 0) {
		buttons |= BUTTON_LEFT;
	}
	if (gpio_pin_get(gpio0, PIN_RIGHT) == 0) {
		buttons |= BUTTON_RIGHT;
	}
	if (gpio_pin_get(gpio0, PIN_MIDDLE) == 0) {
		buttons |= BUTTON_MIDDLE;
	}
	if (gpio_pin_get(gpio0, PIN_SIDE1) == 0) {
		buttons |= BUTTON_SIDE1;
	}
	if (gpio_pin_get(gpio0, PIN_SIDE2) == 0) {
		buttons |= BUTTON_SIDE2;
	}
	return buttons;
}

static int8_t read_encoder(void)
{
	static uint8_t previous;
	static int8_t accumulator;
	static const int8_t transitions[16] = {
		 0, -1,  1,  0,
		 1,  0,  0, -1,
		-1,  0,  0,  1,
		 0,  1, -1,  0,
	};
	uint8_t current = ((gpio_pin_get(gpio0, PIN_ENC_A) > 0) << 1) |
			  (gpio_pin_get(gpio0, PIN_ENC_B) > 0);
	int8_t wheel = 0;

	accumulator += transitions[(previous << 2) | current];
	previous = current;
	if (accumulator >= 4) {
		wheel = 1;
		accumulator = 0;
	} else if (accumulator <= -4) {
		wheel = -1;
		accumulator = 0;
	}
	return wheel;
}

static uint8_t battery_percent(void)
{
	int16_t sample = 0;
	int32_t millivolts;
	struct adc_sequence sequence = {
		.buffer = &sample,
		.buffer_size = sizeof(sample),
	};

	if (!adc_is_ready_dt(&battery_adc) ||
	    adc_sequence_init_dt(&battery_adc, &sequence) != 0 ||
	    adc_read_dt(&battery_adc, &sequence) != 0) {
		return 0;
	}

	millivolts = sample;
	if (adc_raw_to_millivolts_dt(&battery_adc, &millivolts) != 0) {
		return 0;
	}
	millivolts *= 2; /* 1 MOhm / 1 MOhm divider. */

	if (millivolts <= 3200) {
		return 0;
	}
	if (millivolts >= 4200) {
		return 100;
	}
	return (uint8_t)((millivolts - 3200) / 10);
}

static int configure_inputs(void)
{
	const uint8_t pins[] = {
		PIN_DPI, PIN_MIDDLE, PIN_ENC_B, PIN_ENC_A,
		PIN_RIGHT, PIN_LEFT, PIN_SIDE1, PIN_SIDE2,
	};

	if (!device_is_ready(gpio0)) {
		return -ENODEV;
	}
	for (size_t i = 0; i < ARRAY_SIZE(pins); i++) {
		int err = gpio_pin_configure(gpio0, pins[i], GPIO_INPUT | GPIO_PULL_UP);
		if (err != 0) {
			return err;
		}
	}
	return 0;
}

int main(void)
{
	static const uint16_t cpi_steps[] = {800, 1200, 1600, 2400, 3200};
	struct pmw3610_motion motion;
	uint8_t cpi_index = 1;
	uint8_t last_buttons = 0;
	bool last_dpi = false;
	bool last_connected = false;
	bool battery_adc_ready;
	bool sensor_ok;
	int64_t next_battery_update;

	if (configure_inputs() != 0) {
		return 0;
	}
	battery_adc_ready = adc_channel_setup_dt(&battery_adc) == 0;

	(void)status_led_init();
	status_led_set(0, 0, 8); /* Advertising / startup: dim blue. */
	sensor_ok = (pmw3610_init() == 0);
	if (!sensor_ok) {
		status_led_set(12, 0, 0); /* Sensor fault: dim red. */
	}

	if (hid_mouse_init() != 0) {
		status_led_set(12, 0, 0);
		return 0;
	}

	if (battery_adc_ready) {
		/* The 1 MOhm / 1 MOhm divider and 100 nF filter have a 50 ms
		 * time constant. Wait five constants after switched power rises.
		 */
		k_msleep(BATTERY_SETTLE_MS);
		bt_bas_set_battery_level(battery_percent());
	}
	next_battery_update = k_uptime_get() + BATTERY_PERIOD_MS;

	for (;;) {
		uint8_t buttons = read_buttons();
		int8_t wheel = read_encoder();
		bool dpi_pressed = gpio_pin_get(gpio0, PIN_DPI) == 0;
		int16_t x = 0;
		int16_t y = 0;

		if (dpi_pressed && !last_dpi && sensor_ok) {
			cpi_index = (uint8_t)((cpi_index + 1) % ARRAY_SIZE(cpi_steps));
			(void)pmw3610_set_cpi(cpi_steps[cpi_index]);
		}
		last_dpi = dpi_pressed;

		if (sensor_ok && pmw3610_read_motion(&motion) == 0 &&
		    motion.valid && !motion.overflow) {
			x = motion.x;
			y = motion.y;
		}

		if (x != 0 || y != 0 || wheel != 0 || buttons != last_buttons) {
			(void)hid_mouse_send(buttons, x, y, wheel);
			last_buttons = buttons;
		}

		if (battery_adc_ready && k_uptime_get() >= next_battery_update) {
			bt_bas_set_battery_level(battery_percent());
			next_battery_update += BATTERY_PERIOD_MS;
		}

		bool connected = hid_mouse_is_connected();
		if (sensor_ok && connected != last_connected) {
			if (connected) {
				status_led_set(0, 6, 0); /* Connected: dim green. */
			} else {
				status_led_set(0, 0, 8); /* Advertising: dim blue. */
			}
			last_connected = connected;
		}

		k_msleep(REPORT_PERIOD_MS);
	}
	return 0;
}