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;
}