diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e26983..a34e098 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,7 @@ include(pico_sdk_import.cmake) project(EDMFD C CXX) set(CMAKE_C_STANDARD 11) -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS 3000) pico_sdk_init() @@ -35,18 +35,36 @@ add_compile_options( if (CMAKE_C_COMPILER_ID STREQUAL "GNU") add_compile_options(-Wno-maybe-uninitialized) + add_compile_options(-ffile-prefix-map=${CMAKE_SOURCE_DIR}/src/=) endif() +include_directories(src) + +add_library(logging STATIC + src/logging/log.cc + ) +target_include_directories(logging PRIVATE src) + add_executable(edmfd) -target_include_directories(edmfd PUBLIC src) target_sources(edmfd PUBLIC - src/blink.cc - src/hid.cc - src/main.cc - src/usb_descriptors.cc - src/vendor.cc + src/edmfd/blink.cc + src/edmfd/hid.cc + src/edmfd/main.cc + src/edmfd/mcp23017.cc + src/edmfd/screen.cc + src/edmfd/usb_descriptors.cc + src/edmfd/vendor.cc ) -pico_enable_stdio_usb(edmfd 1) +pico_enable_stdio_uart(edmfd 1) +pico_enable_stdio_uart(edmfd 0) pico_add_extra_outputs(edmfd) -target_link_libraries(edmfd PUBLIC pico_stdlib pico_unique_id tinyusb_device tinyusb_board) \ No newline at end of file +target_link_libraries(edmfd PUBLIC + logging + pico_stdlib + pico_stdio_uart + pico_unique_id + hardware_i2c + tinyusb_device + tinyusb_board + ) diff --git a/src/blink.cc b/src/edmfd/blink.cc similarity index 100% rename from src/blink.cc rename to src/edmfd/blink.cc diff --git a/src/blink.hh b/src/edmfd/blink.hh similarity index 100% rename from src/blink.hh rename to src/edmfd/blink.hh diff --git a/src/hid.cc b/src/edmfd/hid.cc similarity index 100% rename from src/hid.cc rename to src/edmfd/hid.cc diff --git a/src/hid.hh b/src/edmfd/hid.hh similarity index 100% rename from src/hid.hh rename to src/edmfd/hid.hh diff --git a/src/edmfd/main.cc b/src/edmfd/main.cc new file mode 100644 index 0000000..3fdb51a --- /dev/null +++ b/src/edmfd/main.cc @@ -0,0 +1,163 @@ +#include +#include +#include + +#include "pico/stdlib.h" +#include "pico/binary_info.h" +#include "hardware/i2c.h" + +#include "tusb_config.h" +#include "bsp/board_api.h" +#include "tusb.h" + +#include "logging/log.hh" + +#include "blink.hh" +#include "hid.hh" +#include "mcp23017.hh" + +extern "C" { + // TinyUSB callbacks + void tud_mount_cb(); + void tud_unmount_cb(); + void tud_suspend_cb(bool); + 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; +} + +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); + + // 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)); +} + +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); + + 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" : " "); + } + printf("Done.\n"); +} + +int main(void) +{ + setup_default_uart(); + stdio_init_all(); + puts("\n"); + + gpio_init(PICO_DEFAULT_LED_PIN); + gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT); + + board_init(); + log::info("board initialized"); + + tud_init(BOARD_TUD_RHPORT); + + if (board_init_after_tusb) + board_init_after_tusb(); + log::info("tinyusb initialized"); + + setup_i2c(); + log::info("i2c initialized"); + + using mcp23017::reg; + mcp23017::gpios gpios {0x20}; + + uint8_t iocon = 0xba; + gpios.read(reg::iocona, &iocon); + gpios.read(reg::iodira, &iocon); + + /* + gpios.write(reg::iodira, 0xff); + gpios.write(reg::gpintena, 0xff); + gpios.write(reg::gppua, 0xff); + */ + + while (1) { + uint32_t time = board_millis(); + tud_task(); // tinyusb device task + blink::task(time); + hid::task(time); + } +} + +//--------------------------------------------------------------------+ +// Device callbacks +//--------------------------------------------------------------------+ + +// Invoked when device is mounted +void tud_mount_cb() { + blink::set_pattern(blink::pattern::mounted); +} + +// Invoked when device is unmounted +void tud_umount_cb() { + blink::set_pattern(blink::pattern::not_mounted); +} + +// Invoked when usb bus is suspended +// argumen : if host allow us to perform remote wakeup +// Within 7ms, device must draw an average of current less than 2.5 mA from bus +void tud_suspend_cb(bool) { + blink::set_pattern(blink::pattern::suspended); +} + +// Invoked when usb bus is resumed +void tud_resume_cb() { + blink::pattern pat = tud_mounted() ? blink::pattern::mounted : blink::pattern::not_mounted; + blink::set_pattern(pat); +} diff --git a/src/edmfd/mcp23017.cc b/src/edmfd/mcp23017.cc new file mode 100644 index 0000000..e210464 --- /dev/null +++ b/src/edmfd/mcp23017.cc @@ -0,0 +1,60 @@ +#include +#include "hardware/i2c.h" +#include "pico/stdlib.h" + +#include "logging/log.hh" +#include "mcp23017.hh" + +namespace mcp23017 { + +static const char * regnames[] = { + "iodira", "iodirb", + "ipola", "ipolb", + "gpintena", "gpintenb", + "defvala", "defvalb", + "intcona", "intconb", + "iocona", "ioconb", + "gppua", "gppub", + "intfa", "intfb", + "intcapa", "intcapb", + "gpioa", "gpiob", + "olata", "olatb", +}; + +gpios::gpios(uint8_t addr) : + m_addr {addr} +{} + +int +gpios::read(reg r, uint8_t *value) +{ + uint8_t txdata = uint8_t(r); + int ret = i2c_write_burst_blocking(i2c_default, m_addr, &txdata, 1); + if (ret < 0) { + log::error("writing read address {%02x:%s}: %d", m_addr, regnames[uint8_t(r)], ret); + return ret; + } + + ret = i2c_read_blocking(i2c_default, m_addr, value, 1, false); + if (ret < 0) { + log::error("reading {%02x:%s}: %d", m_addr, regnames[uint8_t(r)], ret); + } else { + log::info("read {%02x:%s}: 0x%02x", m_addr, regnames[uint8_t(r)], *value); + } + return ret; +} + +int +gpios::write(reg r, uint8_t value) +{ + uint8_t txdata[2] = { uint8_t(r), value }; + int ret = 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 diff --git a/src/edmfd/mcp23017.hh b/src/edmfd/mcp23017.hh new file mode 100644 index 0000000..3d11266 --- /dev/null +++ b/src/edmfd/mcp23017.hh @@ -0,0 +1,31 @@ +#pragma once + +namespace mcp23017 { + +enum class reg : uint8_t { + iodira, iodirb, + ipola, ipolb, + gpintena, gpintenb, + defvala, defvalb, + intcona, intconb, + iocona, ioconb, + gppua, gppub, + intfa, intfb, + intcapa, intcapb, + gpioa, gpiob, + olata, olatb, +}; + +class gpios +{ +public: + gpios(uint8_t addr); + + int read(reg r, uint8_t *value); + int write(reg r, uint8_t value); + +private: + uint8_t m_addr; +}; + +} // namespace mcp23017 diff --git a/src/edmfd/proto.cc b/src/edmfd/proto.cc new file mode 100644 index 0000000..a1e3673 --- /dev/null +++ b/src/edmfd/proto.cc @@ -0,0 +1,9 @@ +#include "proto.hh" + +namespace proto { + +void parse(const uint8_t *buffer, unsigned len) { + +} + +} // namespace proto \ No newline at end of file diff --git a/src/edmfd/proto.hh b/src/edmfd/proto.hh new file mode 100644 index 0000000..e5bc7d7 --- /dev/null +++ b/src/edmfd/proto.hh @@ -0,0 +1,8 @@ +#pragma once +#include + +namespace proto { + +void parse(const uint8_t *buffer, unsigned len); + +} // namespace proto \ No newline at end of file diff --git a/src/edmfd/screen.cc b/src/edmfd/screen.cc new file mode 100644 index 0000000..14ef6bd --- /dev/null +++ b/src/edmfd/screen.cc @@ -0,0 +1,14 @@ +#include "screen.hh" + +namespace screen { + +palette g_palette = {0}; + +void set_color(unsigned index, color_t color) { + if (index >= num_colors) return; + g_palette[index] = color; +} + +const palette & get_colors() { return g_palette; } + +} // namespace screen \ No newline at end of file diff --git a/src/edmfd/screen.hh b/src/edmfd/screen.hh new file mode 100644 index 0000000..b039e2a --- /dev/null +++ b/src/edmfd/screen.hh @@ -0,0 +1,15 @@ +#pragma once +#include +#include + +namespace screen { + +inline constexpr unsigned color_bits = 4; +inline constexpr unsigned num_colors = 1 << color_bits; +using color_t = uint16_t; +using palette = std::array; + +void set_color(unsigned index, color_t color); +const palette & get_colors(); + +} // namepsace screen \ No newline at end of file diff --git a/src/usb_descriptors.cc b/src/edmfd/usb_descriptors.cc similarity index 99% rename from src/usb_descriptors.cc rename to src/edmfd/usb_descriptors.cc index e4a68ac..ffa9afc 100644 --- a/src/usb_descriptors.cc +++ b/src/edmfd/usb_descriptors.cc @@ -11,7 +11,7 @@ * [MSB] HID | MSC | CDC [LSB] */ #define _PID_MAP(itf, n) ( (CFG_TUD_##itf) << (n) ) -#define USB_PID (0x0000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ +#define USB_PID (0x0200 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ _PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4) ) #define USB_VID 0x4a33 // J3 in ascii @@ -123,12 +123,12 @@ uint8_t const desc_configuration[config_total_len] = { // Config number, interface count, string index, total length, attribute, power in mA TUD_CONFIG_DESCRIPTOR(1, uint8_t(interface::_count), 0, config_total_len, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), - // Interface number, string index, EP Out & IN address, EP size - TUD_VENDOR_DESCRIPTOR(uint8_t(interface::vendor), uint8_t(strings::interface), uint8_t(endpoint::vendor_out), uint8_t(endpoint::vendor_in), 64), - // Interface number, string index, protocol, report descriptor len, EP In address, size & polling interval TUD_HID_DESCRIPTOR(uint8_t(interface::HID), 0, HID_ITF_PROTOCOL_NONE, sizeof(desc_hid_report), uint8_t(endpoint::HID), CFG_TUD_HID_EP_BUFSIZE, 5), + // Interface number, string index, EP Out & IN address, EP size + TUD_VENDOR_DESCRIPTOR(uint8_t(interface::vendor), uint8_t(strings::interface), uint8_t(endpoint::vendor_out), uint8_t(endpoint::vendor_in), 64), + // CDC: Interface number, string index, EP notification address and size, EP data address (out, in) and size. //TUD_CDC_DESCRIPTOR(uint8_t(interface::CDC), 4, uint8_t(endpoint::CDCnotify), 8, uint8_t(endpoint::CDCout), uint8_t(endpoint::CDCin), 64) }; diff --git a/src/usb_descriptors.hh b/src/edmfd/usb_descriptors.hh similarity index 74% rename from src/usb_descriptors.hh rename to src/edmfd/usb_descriptors.hh index 1566d61..89cbd7f 100644 --- a/src/usb_descriptors.hh +++ b/src/edmfd/usb_descriptors.hh @@ -14,8 +14,8 @@ enum class strings : uint8_t { _count }; -enum class interface : uint8_t { vendor, HID, _count }; -enum class endpoint : uint8_t { HID = 0x81, vendor_out = 0x02, vendor_in = 0x82 }; +enum class interface : uint8_t { HID, vendor, _count }; +enum class endpoint : uint8_t { HID = 0x81, vendor_out = 0x02, vendor_in = 0x83 }; enum class vendor_req : uint8_t { microsoft = 1, diff --git a/src/vendor.cc b/src/edmfd/vendor.cc similarity index 66% rename from src/vendor.cc rename to src/edmfd/vendor.cc index 228a3a0..639b244 100644 --- a/src/vendor.cc +++ b/src/edmfd/vendor.cc @@ -2,6 +2,7 @@ #include "class/vendor/vendor_device.h" #include "blink.hh" +#include "screen.hh" #include "usb_descriptors.hh" extern "C" { @@ -30,11 +31,34 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_requ // Invoked when received new data void tud_vendor_rx_cb(uint8_t, uint8_t const* buffer, uint16_t bufsize) { - tud_vendor_write(buffer, bufsize); + if (!bufsize) return; + + + switch (buffer[0]) { + case 0x02: { // set colors + for (unsigned i = 1; i < bufsize-3u; i += 3u) { + unsigned index = buffer[i]; + screen::color_t color = + *reinterpret_cast(&buffer[i+1]); + screen::set_color(index, color); + } + break; + } + + case 0x03: {// get colors + const screen::palette &pal = screen::get_colors(); + tud_vendor_write(&pal, sizeof(pal)); + tud_vendor_write_flush(); + break; + } + + default: + break; + } + tud_vendor_read_flush(); } // Invoked when last rx (tx?) transfer finished void tud_vendor_tx_cb(uint8_t itf, uint32_t sent_bytes) { - tud_vendor_write_flush(); } diff --git a/src/logging/log.cc b/src/logging/log.cc new file mode 100644 index 0000000..bebb99c --- /dev/null +++ b/src/logging/log.cc @@ -0,0 +1,26 @@ +#include +#include +#include "log.hh" + +namespace logging { +namespace { + const char *level_strings[] = { + nullptr, + "\e[90mTRACE\e[0m", + "\e[94mDEBUG\e[0m", + "\e[92mINFO\e[0m", + "\e[93mWARN\e[0m", + "\e[91mERROR\e[0m", + }; +} + +void log(level lv, const format &fmt, ...) { + printf("%20s:%-3d: %5s ", fmt.loc.file_name(), fmt.loc.line(), level_strings[int(lv)]); + va_list args; + va_start(args, fmt); + vprintf(fmt.fmt, args); + va_end(args); + putchar('\n'); +} + +} // namespace log diff --git a/src/logging/log.hh b/src/logging/log.hh new file mode 100644 index 0000000..8adc4ce --- /dev/null +++ b/src/logging/log.hh @@ -0,0 +1,26 @@ +#pragma once +#include + +namespace logging { + +enum class level { none, trace, debug, info, warn, error }; + +class format +{ +public: + format() = default; + format(const char *fmt, const std::source_location &loc = std::source_location::current()) : + fmt {fmt}, loc {loc} {} + const char *fmt; + const std::source_location &loc; +}; + +void log(level lv, const format &fmt, ...); +inline void trace(const format &fmt, auto&&... args) { log(level::trace, fmt, args...); } +inline void debug(const format &fmt, auto&&... args) { log(level::debug, fmt, args...); } +inline void info (const format &fmt, auto&&... args) { log(level::info, fmt, args...); } +inline void warn (const format &fmt, auto&&... args) { log(level::warn, fmt, args...); } +inline void error(const format &fmt, auto&&... args) { log(level::error, fmt, args...); } + +} // namespace logging +namespace log = logging; diff --git a/src/main.cc b/src/main.cc deleted file mode 100644 index d1ddb1a..0000000 --- a/src/main.cc +++ /dev/null @@ -1,88 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 Ha Thach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - */ - -#include -#include -#include - -#include "tusb_config.h" -#include "bsp/board_api.h" -#include "tusb.h" - -#include "blink.hh" -#include "hid.hh" - -extern "C" { - // TinyUSB callbacks - void tud_mount_cb(); - void tud_unmount_cb(); - void tud_suspend_cb(bool); - void tud_resume_cb(); -} - -extern "C" -int main(void) -{ - board_init(); - - tud_init(BOARD_TUD_RHPORT); - - if (board_init_after_tusb) - board_init_after_tusb(); - - while (1) { - uint32_t time = board_millis(); - tud_task(); // tinyusb device task - blink::task(time); - hid::task(time); - } -} - -//--------------------------------------------------------------------+ -// Device callbacks -//--------------------------------------------------------------------+ - -// Invoked when device is mounted -void tud_mount_cb() { - blink::set_pattern(blink::pattern::mounted); -} - -// Invoked when device is unmounted -void tud_umount_cb() { - blink::set_pattern(blink::pattern::not_mounted); -} - -// Invoked when usb bus is suspended -// argumen : if host allow us to perform remote wakeup -// Within 7ms, device must draw an average of current less than 2.5 mA from bus -void tud_suspend_cb(bool) { - blink::set_pattern(blink::pattern::suspended); -} - -// Invoked when usb bus is resumed -void tud_resume_cb() { - blink::pattern pat = tud_mounted() ? blink::pattern::mounted : blink::pattern::not_mounted; - blink::set_pattern(pat); -} \ No newline at end of file