mirror of
https://github.com/justinian/edmfd_firmware.git
synced 2025-12-09 16:24:31 -08:00
Multiple button groups piping to HID
This commit is contained in:
@@ -49,6 +49,7 @@ add_executable(edmfd)
|
|||||||
target_sources(edmfd PUBLIC
|
target_sources(edmfd PUBLIC
|
||||||
src/edmfd/blink.cc
|
src/edmfd/blink.cc
|
||||||
src/edmfd/hid.cc
|
src/edmfd/hid.cc
|
||||||
|
src/edmfd/i2c.cc
|
||||||
src/edmfd/main.cc
|
src/edmfd/main.cc
|
||||||
src/edmfd/mcp23017.cc
|
src/edmfd/mcp23017.cc
|
||||||
src/edmfd/screen.cc
|
src/edmfd/screen.cc
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ void set_pattern(pattern pat) {
|
|||||||
board_led_write(led_state);
|
board_led_write(led_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
void task(uint32_t time) {
|
void task() {
|
||||||
|
uint32_t time = board_millis();
|
||||||
if (current_pattern == pattern::off || time < next_change)
|
if (current_pattern == pattern::off || time < next_change)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,6 @@ enum class pattern : uint8_t {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void set_pattern(pattern pat);
|
void set_pattern(pattern pat);
|
||||||
void task(uint32_t time);
|
void task();
|
||||||
|
|
||||||
} // namespace blink
|
} // namespace blink
|
||||||
@@ -12,8 +12,12 @@ extern "C" {
|
|||||||
void tud_hid_set_report_cb(uint8_t, uint8_t, hid_report_type_t, uint8_t const*, uint16_t);
|
void tud_hid_set_report_cb(uint8_t, uint8_t, hid_report_type_t, uint8_t const*, uint16_t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
namespace hid {
|
namespace hid {
|
||||||
|
|
||||||
|
static callback set_report_callback = nullptr;
|
||||||
|
|
||||||
|
|
||||||
void send_report(descriptor desc, uint32_t btn)
|
void send_report(descriptor desc, uint32_t btn)
|
||||||
{
|
{
|
||||||
// skip if hid is not ready yet
|
// skip if hid is not ready yet
|
||||||
@@ -48,30 +52,21 @@ void send_report(descriptor desc, uint32_t btn)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Every 10ms, we will sent 1 report for each HID profile (keyboard, mouse etc ..)
|
void init(callback cb)
|
||||||
// tud_hid_report_complete_cb() is used to send the next report after previous one is complete
|
|
||||||
void task(uint32_t time)
|
|
||||||
{
|
{
|
||||||
// Poll every 10ms
|
set_report_callback = cb;
|
||||||
static constexpr uint32_t interval_ms = 10;
|
}
|
||||||
static uint32_t next_update = 0;
|
|
||||||
|
|
||||||
if (time < next_update) return; // not enough time
|
void task(uint32_t inputs)
|
||||||
next_update = time + interval_ms;
|
{
|
||||||
|
// Remote wakeup if host is suspended. Requires host
|
||||||
uint32_t const btn = board_button_read();
|
// to have enabled REMOTE_WAKEUP feature for this device.
|
||||||
|
if (tud_suspended() && inputs) {
|
||||||
// Remote wakeup
|
|
||||||
if ( tud_suspended() && btn )
|
|
||||||
{
|
|
||||||
// Wake up host if we are in suspend mode
|
|
||||||
// and REMOTE_WAKEUP feature is enabled by host
|
|
||||||
tud_remote_wakeup();
|
tud_remote_wakeup();
|
||||||
}else
|
return;
|
||||||
{
|
|
||||||
// Send the 1st of report chain, the rest will be sent by tud_hid_report_complete_cb()
|
|
||||||
send_report(descriptor::gamepad, btn);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
send_report(descriptor::gamepad, inputs);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace hid
|
} // namespace hid
|
||||||
@@ -93,23 +88,26 @@ void tud_hid_report_complete_cb(uint8_t instance, uint8_t const* report, uint16_
|
|||||||
// Invoked when received GET_REPORT control request
|
// Invoked when received GET_REPORT control request
|
||||||
// Application must fill buffer report's content and return its length.
|
// Application must fill buffer report's content and return its length.
|
||||||
// Return zero will cause the stack to STALL request
|
// Return zero will cause the stack to STALL request
|
||||||
uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t* buffer, uint16_t reqlen)
|
uint16_t tud_hid_get_report_cb(
|
||||||
|
uint8_t instance,
|
||||||
|
uint8_t report_id,
|
||||||
|
hid_report_type_t report_type,
|
||||||
|
uint8_t* buffer,
|
||||||
|
uint16_t reqlen)
|
||||||
{
|
{
|
||||||
// TODO not Implemented
|
// TODO not Implemented
|
||||||
(void) instance;
|
|
||||||
(void) report_id;
|
|
||||||
(void) report_type;
|
|
||||||
(void) buffer;
|
|
||||||
(void) reqlen;
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invoked when received SET_REPORT control request or
|
// Invoked when received SET_REPORT control request or
|
||||||
// received data on OUT endpoint ( Report ID = 0, Type = 0 )
|
// received data on OUT endpoint ( Report ID = 0, Type = 0 )
|
||||||
void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize)
|
void tud_hid_set_report_cb(
|
||||||
|
uint8_t instance,
|
||||||
|
uint8_t report_id,
|
||||||
|
hid_report_type_t report_type,
|
||||||
|
uint8_t const* buffer,
|
||||||
|
uint16_t bufsize)
|
||||||
{
|
{
|
||||||
(void) instance;
|
|
||||||
const auto desc = descriptor(report_id);
|
const auto desc = descriptor(report_id);
|
||||||
|
|
||||||
if (report_type == HID_REPORT_TYPE_OUTPUT) {
|
if (report_type == HID_REPORT_TYPE_OUTPUT) {
|
||||||
@@ -118,14 +116,12 @@ void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_
|
|||||||
// bufsize should be (at least) 1
|
// bufsize should be (at least) 1
|
||||||
if ( bufsize < 1 ) return;
|
if ( bufsize < 1 ) return;
|
||||||
|
|
||||||
uint8_t const btn_leds = buffer[0];
|
uint32_t btn_leds = 0;
|
||||||
if (btn_leds & 1) {
|
for (unsigned i = 0; i < bufsize; ++i)
|
||||||
// Capslock On: disable blink, turn led on
|
btn_leds = uint32_t(buffer[i]) << (i*8);
|
||||||
blink::set_pattern(blink::pattern::on);
|
|
||||||
} else {
|
if (hid::set_report_callback)
|
||||||
// Caplocks Off: back to normal blink
|
hid::set_report_callback(btn_leds);
|
||||||
blink::set_pattern(blink::pattern::mounted);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
namespace hid {
|
namespace hid {
|
||||||
|
|
||||||
void task(uint32_t time);
|
using callback = void (*)(uint32_t);
|
||||||
|
|
||||||
|
void init(callback cb);
|
||||||
|
void task(uint32_t inputs);
|
||||||
|
|
||||||
} // namespace hid
|
} // namespace hid
|
||||||
65
src/edmfd/i2c.cc
Normal file
65
src/edmfd/i2c.cc
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include "hardware/i2c.h"
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "pico/binary_info.h"
|
||||||
|
|
||||||
|
#include "logging/log.hh"
|
||||||
|
|
||||||
|
#include "i2c.hh"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace i2c {
|
||||||
|
|
||||||
|
void init(unsigned baud)
|
||||||
|
{
|
||||||
|
baud = i2c_init(i2c_default, baud);
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
|
||||||
|
log::info("waiting for i2c...");
|
||||||
|
while(!i2c_get_write_available(i2c_default));
|
||||||
|
log::info("i2c initialized at %.1fkHz", baud/1000.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void 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" : " ");
|
||||||
|
}
|
||||||
|
printf("Done.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace i2c
|
||||||
8
src/edmfd/i2c.hh
Normal file
8
src/edmfd/i2c.hh
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace i2c {
|
||||||
|
|
||||||
|
void init(unsigned baud);
|
||||||
|
void scan();
|
||||||
|
|
||||||
|
} // namespace i2c
|
||||||
@@ -3,8 +3,6 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/binary_info.h"
|
|
||||||
#include "hardware/i2c.h"
|
|
||||||
|
|
||||||
#include "tusb_config.h"
|
#include "tusb_config.h"
|
||||||
#include "bsp/board_api.h"
|
#include "bsp/board_api.h"
|
||||||
@@ -14,8 +12,13 @@
|
|||||||
|
|
||||||
#include "blink.hh"
|
#include "blink.hh"
|
||||||
#include "hid.hh"
|
#include "hid.hh"
|
||||||
|
#include "i2c.hh"
|
||||||
#include "mcp23017.hh"
|
#include "mcp23017.hh"
|
||||||
|
|
||||||
|
using mcp23017::reg;
|
||||||
|
|
||||||
|
static constexpr unsigned i2c_baud = 1200 * 1000;
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
// TinyUSB callbacks
|
// TinyUSB callbacks
|
||||||
void tud_mount_cb();
|
void tud_mount_cb();
|
||||||
@@ -24,73 +27,47 @@ extern "C" {
|
|||||||
void tud_resume_cb();
|
void tud_resume_cb();
|
||||||
}
|
}
|
||||||
|
|
||||||
// I2C reserves some addresses for special purposes. We exclude these from the scan.
|
static constexpr unsigned bank_leds = 0;
|
||||||
// These are any addresses of the form 000 0xxx or 111 1xxx
|
static constexpr unsigned bank_buttons = 1;
|
||||||
bool reserved_addr(uint8_t addr) {
|
|
||||||
return (addr & 0x78) == 0 || (addr & 0x78) == 0x78;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup_i2c()
|
static constexpr unsigned hid_update_ms = 1000/20; // 20Hz
|
||||||
{
|
|
||||||
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);
|
|
||||||
|
|
||||||
// Make the I2C pins available to picotool
|
static constexpr unsigned buttons_per_group = 5;
|
||||||
bi_decl(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C));
|
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)
|
static_assert(sizeof(button_group_irqs) == button_group_count * sizeof(*button_group_irqs));
|
||||||
{
|
static_assert(sizeof(button_groups) == button_group_count * sizeof(*button_groups));
|
||||||
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);
|
|
||||||
|
|
||||||
return ret;
|
void irq_handler(unsigned pin, uint32_t events) {
|
||||||
}
|
for (unsigned i = 0; i < button_group_count; ++i) {
|
||||||
|
if (button_group_irqs[i] != pin) continue;
|
||||||
void setup_mcp()
|
button_states[i] = button_groups[i].get_gpios(bank_buttons);
|
||||||
{
|
break;
|
||||||
// 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
|
bool hid_update_callback(repeating_timer *timer) {
|
||||||
// acknowledges this address, the function returns the number of bytes
|
uint32_t buttons = 0;
|
||||||
// transferred. If the address byte is ignored, the function returns
|
for (unsigned i = 0; i < button_group_count; ++i)
|
||||||
// -1.
|
buttons |= uint32_t(button_states[i]) << (i*8);
|
||||||
|
|
||||||
// Skip over any reserved addresses.
|
blink::task();
|
||||||
int ret;
|
hid::task(buttons);
|
||||||
uint8_t rxdata;
|
return true;
|
||||||
if (reserved_addr(addr))
|
}
|
||||||
ret = PICO_ERROR_GENERIC;
|
|
||||||
else
|
|
||||||
ret = i2c_read_blocking(i2c_default, addr, &rxdata, 1, false);
|
|
||||||
|
|
||||||
printf(ret < 0 ? "." : "@");
|
void set_leds_callback(uint32_t leds) {
|
||||||
printf(addr % 16 == 15 ? "\n" : " ");
|
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)
|
int main(void)
|
||||||
@@ -111,27 +88,50 @@ int main(void)
|
|||||||
board_init_after_tusb();
|
board_init_after_tusb();
|
||||||
log::info("tinyusb initialized");
|
log::info("tinyusb initialized");
|
||||||
|
|
||||||
setup_i2c();
|
i2c::init(i2c_baud);
|
||||||
log::info("i2c initialized");
|
|
||||||
|
|
||||||
using mcp23017::reg;
|
// set up interrupt handlers
|
||||||
mcp23017::gpios gpios {0x20};
|
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;
|
bool have_any_buttons = false;
|
||||||
gpios.read(reg::iocona, &iocon);
|
for (unsigned i = 0; i < button_group_count; ++i) {
|
||||||
gpios.read(reg::iodira, &iocon);
|
if (!button_groups[i].init()) {
|
||||||
|
log::warn("failed to initialize button group %d", i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
log::info("initializing button group %d", i);
|
||||||
|
|
||||||
/*
|
have_any_buttons = true;
|
||||||
gpios.write(reg::iodira, 0xff);
|
mcp23017::extender &buttons = button_groups[i];
|
||||||
gpios.write(reg::gpintena, 0xff);
|
|
||||||
gpios.write(reg::gppua, 0xff);
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
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) {
|
while (1) {
|
||||||
uint32_t time = board_millis();
|
|
||||||
tud_task(); // tinyusb device task
|
tud_task(); // tinyusb device task
|
||||||
blink::task(time);
|
sleep_ms(1);
|
||||||
hid::task(time);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include "hardware/i2c.h"
|
#include "hardware/i2c.h"
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
|
#include "pico/time.h"
|
||||||
|
|
||||||
#include "logging/log.hh"
|
#include "logging/log.hh"
|
||||||
#include "mcp23017.hh"
|
#include "mcp23017.hh"
|
||||||
@@ -21,40 +22,158 @@ static const char * regnames[] = {
|
|||||||
"olata", "olatb",
|
"olata", "olatb",
|
||||||
};
|
};
|
||||||
|
|
||||||
gpios::gpios(uint8_t addr) :
|
inline reg bank_reg(reg base, unsigned bank) { return reg(uint8_t(base)+bank); }
|
||||||
|
|
||||||
|
extender::extender(uint8_t addr) :
|
||||||
m_addr {addr}
|
m_addr {addr}
|
||||||
{}
|
{}
|
||||||
|
|
||||||
int
|
bool
|
||||||
gpios::read(reg r, uint8_t *value)
|
extender::init()
|
||||||
{
|
{
|
||||||
uint8_t txdata = uint8_t(r);
|
unsigned count = 0;
|
||||||
int ret = i2c_write_burst_blocking(i2c_default, m_addr, &txdata, 1);
|
while (1) {
|
||||||
if (ret < 0) {
|
uint8_t value = 0xba;
|
||||||
log::error("writing read address {%02x:%s}: %d", m_addr, regnames[uint8_t(r)], ret);
|
int ret = i2c_read_blocking(i2c_default, m_addr, &value, 1, false);
|
||||||
return ret;
|
if (ret >= 0) break;
|
||||||
|
|
||||||
|
//log::error("reading %02x: %d", m_addr, ret);
|
||||||
|
if (count++ > 10) return false;
|
||||||
|
sleep_ms(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = i2c_read_blocking(i2c_default, m_addr, value, 1, false);
|
if (read(reg::iodira, m_direction, 2) > 0) {
|
||||||
if (ret < 0) {
|
//log::info("initialized directions to %02x %02x", m_direction[0], m_direction[1]);
|
||||||
log::error("reading {%02x:%s}: %d", m_addr, regnames[uint8_t(r)], ret);
|
return true;
|
||||||
} else {
|
|
||||||
log::info("read {%02x:%s}: 0x%02x", m_addr, regnames[uint8_t(r)], *value);
|
|
||||||
}
|
}
|
||||||
return ret;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<reg base_reg, typename Value>
|
||||||
|
void extender::set_field(unsigned bank, unsigned pin, Value value, uint8_t (¤t)[2])
|
||||||
|
{
|
||||||
|
if (bank > 1 || pin > 7) {
|
||||||
|
log::error("tried to configure pin %d:%d", bank, pin);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value current_value = Value((current[bank] >> pin) & 1);
|
||||||
|
if (value == current_value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
uint8_t intval = uint8_t(value);
|
||||||
|
if (intval)
|
||||||
|
current[bank] |= 1 << pin;
|
||||||
|
else
|
||||||
|
current[bank] &= ~(1 << pin);
|
||||||
|
|
||||||
|
reg r = bank_reg(base_reg, bank);
|
||||||
|
write(r, current[bank]);
|
||||||
|
//log::info("configured bank %d %s: %02x", bank, regnames[int(r)], current[bank]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
extender::set_direction(unsigned bank, unsigned pin, direction dir)
|
||||||
|
{
|
||||||
|
set_field<reg::iodira>(bank, pin, dir, m_direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
extender::set_polarity(unsigned bank, unsigned pin, bool reversed)
|
||||||
|
{
|
||||||
|
set_field<reg::ipola>(bank, pin, reversed, m_polarity);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
extender::set_pullup(unsigned bank, unsigned pin, bool enabled)
|
||||||
|
{
|
||||||
|
set_field<reg::gppua>(bank, pin, enabled, m_pullup);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
extender::set_irq(unsigned bank, unsigned pin, bool enabled)
|
||||||
|
{
|
||||||
|
set_field<reg::gpintena>(bank, pin, enabled, m_irq);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
extender::set_gpio(unsigned bank, unsigned pin, bool on)
|
||||||
|
{
|
||||||
|
if (bank > 1 || pin > 7) {
|
||||||
|
log::error("tried to set pin %d:%d", bank, pin);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t current = get_gpios(bank);
|
||||||
|
bool pin_current = (current >> pin) & 1;
|
||||||
|
if (on == pin_current)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (on)
|
||||||
|
current |= 1 << pin;
|
||||||
|
else
|
||||||
|
current &= ~(1 << pin);
|
||||||
|
|
||||||
|
set_gpios(bank, current);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
extender::set_gpios(unsigned bank, uint8_t values)
|
||||||
|
{
|
||||||
|
if (bank > 1) {
|
||||||
|
log::error("tried to set bank %d", bank);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reg r = bank_reg(reg::gpioa, bank);
|
||||||
|
int ret = write(r, values);
|
||||||
|
if (ret < 0) {
|
||||||
|
log::error("writing {%02x:%s}: %d", m_addr, regnames[uint8_t(r)], ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
extender::get_gpio(unsigned bank, unsigned pin)
|
||||||
|
{
|
||||||
|
if (bank > 1 || pin > 7) {
|
||||||
|
log::error("tried to get pin %d:%d", bank, pin);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (get_gpios(bank) >> pin) & 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t
|
||||||
|
extender::get_gpios(unsigned bank)
|
||||||
|
{
|
||||||
|
if (bank > 1) {
|
||||||
|
log::error("tried to get bank %d", bank);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t values = 0;
|
||||||
|
reg r = bank_reg(reg::gpioa, bank);
|
||||||
|
int ret = read(r, &values, 1);
|
||||||
|
if (ret < 0)
|
||||||
|
log::error("reading {%02x:%s}: %d", m_addr, regnames[uint8_t(r)], ret);
|
||||||
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
gpios::write(reg r, uint8_t value)
|
extender::read(reg r, uint8_t *values, unsigned count)
|
||||||
|
{
|
||||||
|
uint8_t txdata = uint8_t(r);
|
||||||
|
int ret = i2c_write_blocking(i2c_default, m_addr, &txdata, 1, true);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
return i2c_read_blocking(i2c_default, m_addr, values, count, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
extender::write(reg r, uint8_t value)
|
||||||
{
|
{
|
||||||
uint8_t txdata[2] = { uint8_t(r), value };
|
uint8_t txdata[2] = { uint8_t(r), value };
|
||||||
int ret = i2c_write_blocking(i2c_default, m_addr, txdata, sizeof(txdata), false);
|
return i2c_write_blocking(i2c_default, m_addr, txdata, sizeof(txdata), false);
|
||||||
if (ret != PICO_OK) {
|
|
||||||
log::error("writing {%02x:%s}: %d", m_addr, regnames[uint8_t(r)], ret);
|
|
||||||
} else {
|
|
||||||
log::info("wrote {%02x:%s}: 0x%02x", m_addr, regnames[uint8_t(r)], value);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace mcp23017
|
} // namespace mcp23017
|
||||||
|
|||||||
@@ -16,16 +16,38 @@ enum class reg : uint8_t {
|
|||||||
olata, olatb,
|
olata, olatb,
|
||||||
};
|
};
|
||||||
|
|
||||||
class gpios
|
enum class direction { out, in };
|
||||||
|
|
||||||
|
class extender
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
gpios(uint8_t addr);
|
extender(uint8_t addr);
|
||||||
|
|
||||||
int read(reg r, uint8_t *value);
|
bool init();
|
||||||
int write(reg r, uint8_t value);
|
|
||||||
|
void set_direction(unsigned bank, unsigned pin, direction dir);
|
||||||
|
void set_polarity(unsigned bank, unsigned pin, bool reversed);
|
||||||
|
void set_pullup(unsigned bank, unsigned pin, bool enabled);
|
||||||
|
void set_irq(unsigned bank, unsigned pin, bool enabled);
|
||||||
|
|
||||||
|
void set_gpio(unsigned bank, unsigned pin, bool on);
|
||||||
|
void set_gpios(unsigned bank, uint8_t values);
|
||||||
|
bool get_gpio(unsigned bank, unsigned pin);
|
||||||
|
uint8_t get_gpios(unsigned bank);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
template<reg base_reg, typename Value>
|
||||||
|
void set_field(unsigned bank, unsigned pin, Value value, uint8_t (¤t)[2]);
|
||||||
|
|
||||||
|
int write(reg r, uint8_t value);
|
||||||
|
int read(reg r, uint8_t *value, unsigned count);
|
||||||
|
|
||||||
uint8_t m_addr;
|
uint8_t m_addr;
|
||||||
|
|
||||||
|
uint8_t m_direction[2];
|
||||||
|
uint8_t m_polarity[2];
|
||||||
|
uint8_t m_pullup[2];
|
||||||
|
uint8_t m_irq[2];
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace mcp23017
|
} // namespace mcp23017
|
||||||
|
|||||||
Reference in New Issue
Block a user