Multiple button groups piping to HID

This commit is contained in:
Justin C. Miller
2025-02-23 17:21:09 -08:00
parent 3a8fa7d08f
commit eac47947cd
10 changed files with 364 additions and 149 deletions

View File

@@ -3,8 +3,6 @@
#include <string.h>
#include "pico/stdlib.h"
#include "pico/binary_info.h"
#include "hardware/i2c.h"
#include "tusb_config.h"
#include "bsp/board_api.h"
@@ -14,8 +12,13 @@
#include "blink.hh"
#include "hid.hh"
#include "i2c.hh"
#include "mcp23017.hh"
using mcp23017::reg;
static constexpr unsigned i2c_baud = 1200 * 1000;
extern "C" {
// TinyUSB callbacks
void tud_mount_cb();
@@ -24,73 +27,47 @@ extern "C" {
void tud_resume_cb();
}
// I2C reserves some addresses for special purposes. We exclude these from the scan.
// These are any addresses of the form 000 0xxx or 111 1xxx
bool reserved_addr(uint8_t addr) {
return (addr & 0x78) == 0 || (addr & 0x78) == 0x78;
}
static constexpr unsigned bank_leds = 0;
static constexpr unsigned bank_buttons = 1;
void setup_i2c()
{
i2c_init(i2c_default, 100 * 1000);
gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C);
gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C);
gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN);
gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN);
static constexpr unsigned hid_update_ms = 1000/20; // 20Hz
// Make the I2C pins available to picotool
bi_decl(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C));
}
static constexpr unsigned buttons_per_group = 5;
static constexpr unsigned button_group_count = 2;
static const unsigned button_group_irqs[] = {5, 6};
mcp23017::extender button_groups[] ={ {0x20}, {0x21} };
uint8_t button_states[] = {0, 0};
uint8_t button_leds[] = {0, 0};
int write_mcp(uint8_t addr, uint8_t reg, uint8_t value, const char *regname)
{
uint8_t txdata[2] = { reg, value };
int ret = i2c_write_blocking(i2c_default, addr, txdata, 2, false);
if (ret)
printf("Error %02x writing %02x to %02x:%s", ret, value, addr, regname);
else
printf("Wrote %02x to %02x:%s", value, addr, regname);
static_assert(sizeof(button_group_irqs) == button_group_count * sizeof(*button_group_irqs));
static_assert(sizeof(button_groups) == button_group_count * sizeof(*button_groups));
return ret;
}
void setup_mcp()
{
// A7 is button, B0 is LED
static constexpr uint8_t addr = 0x20;
if (write_mcp(addr, 0x00, 0xff, "IODIRA")) return;
if (write_mcp(addr, 0x10, 0x00, "IODIRB")) return;
if (write_mcp(addr, 0x02, 0xff, "GPINTENA")) return;
if (write_mcp(addr, 0x06, 0xff, "GPPUA")) return;
}
void i2c_scan()
{
printf("\nI2C Bus Scan\n");
printf(" 0 1 2 3 4 5 6 7 8 9 A B C D E F\n");
for (int addr = 0; addr < (1 << 7); ++addr) {
if (addr % 16 == 0) {
printf("%02x ", addr);
}
// Perform a 1-byte dummy read from the probe address. If a slave
// acknowledges this address, the function returns the number of bytes
// transferred. If the address byte is ignored, the function returns
// -1.
// Skip over any reserved addresses.
int ret;
uint8_t rxdata;
if (reserved_addr(addr))
ret = PICO_ERROR_GENERIC;
else
ret = i2c_read_blocking(i2c_default, addr, &rxdata, 1, false);
printf(ret < 0 ? "." : "@");
printf(addr % 16 == 15 ? "\n" : " ");
void irq_handler(unsigned pin, uint32_t events) {
for (unsigned i = 0; i < button_group_count; ++i) {
if (button_group_irqs[i] != pin) continue;
button_states[i] = button_groups[i].get_gpios(bank_buttons);
break;
}
}
bool hid_update_callback(repeating_timer *timer) {
uint32_t buttons = 0;
for (unsigned i = 0; i < button_group_count; ++i)
buttons |= uint32_t(button_states[i]) << (i*8);
blink::task();
hid::task(buttons);
return true;
}
void set_leds_callback(uint32_t leds) {
for (unsigned i = 0; i < button_group_count; ++i) {
uint8_t desired = (leds >> (i*8)) & 0xff;
if (desired != button_leds[i]) {
button_groups[i].set_gpios(bank_leds, desired);
button_leds[i] = desired;
}
}
printf("Done.\n");
}
int main(void)
@@ -111,27 +88,50 @@ int main(void)
board_init_after_tusb();
log::info("tinyusb initialized");
setup_i2c();
log::info("i2c initialized");
i2c::init(i2c_baud);
using mcp23017::reg;
mcp23017::gpios gpios {0x20};
// set up interrupt handlers
for (int pin : button_group_irqs) {
gpio_init(pin);
gpio_pull_up(pin);
gpio_set_irq_enabled_with_callback(pin, GPIO_IRQ_EDGE_FALL, true, irq_handler);
}
uint8_t iocon = 0xba;
gpios.read(reg::iocona, &iocon);
gpios.read(reg::iodira, &iocon);
bool have_any_buttons = false;
for (unsigned i = 0; i < button_group_count; ++i) {
if (!button_groups[i].init()) {
log::warn("failed to initialize button group %d", i);
continue;
}
log::info("initializing button group %d", i);
/*
gpios.write(reg::iodira, 0xff);
gpios.write(reg::gpintena, 0xff);
gpios.write(reg::gppua, 0xff);
*/
have_any_buttons = true;
mcp23017::extender &buttons = button_groups[i];
using mcp23017::direction;
for (unsigned i = 0; i < buttons_per_group; ++i)
buttons.set_direction(0, i, direction::out);
buttons.set_direction(1, 0, direction::in);
buttons.set_polarity(1, 0, true);
buttons.set_pullup(1, 0, true);
buttons.set_irq(1, 0, true);
}
if (!have_any_buttons) {
i2c::scan();
return 1;
}
hid::init(set_leds_callback);
repeating_timer hid_timer;
add_repeating_timer_ms(hid_update_ms, hid_update_callback, nullptr, &hid_timer);
// Let timers and iterrupts handle most things
while (1) {
uint32_t time = board_millis();
tud_task(); // tinyusb device task
blink::task(time);
hid::task(time);
sleep_ms(1);
}
}