--- /dev/null
+/*
+ * Basic firmware to emulate a USB TEMPer device (which uses an FM75 I2C
+ * sensor internally) using a Digispark + a DS18B20 1-wire sensor.
+ *
+ * Copyright 2018 Jonathan McDowell <noodles@earth.li>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <avr/eeprom.h>
+#include <avr/interrupt.h>
+#include <avr/io.h>
+#include <avr/wdt.h>
+#include <util/delay.h>
+
+#include <avr/pgmspace.h>
+#include "usbdrv.h"
+#include "libs-device/osccal.h"
+
+typedef struct {
+ uint8_t modifier;
+ uint8_t reserved;
+ uint8_t keycode[6];
+} keyboard_report_t;
+keyboard_report_t keyboard_report;
+
+uint8_t temp_report[8];
+bool have_temp_int = false;
+
+#define NUM_LOCK 1
+#define CAPS_LOCK 2
+#define SCROLL_LOCK 4
+volatile static uchar ledstate = 0xff;
+static uchar idlerate;
+
+/* We populate this with the 1-wire device ROM ID when we get it */
+int serno_str[] = {
+ USB_STRING_DESCRIPTOR_HEADER(16),
+ 'F', 'F', 'F', 'F', 'F', 'F', 'F', 'F',
+ 'F', 'F', 'F', 'F', 'F', 'F', 'F', 'F',
+};
+
+#define USB_INDEX_KEYBOARD 0
+#define USB_INDEX_MOUSE 1
+
+PROGMEM const char usbHidKeyboardReportDescriptor[] = {
+ 0x05, 0x01, // USAGE_PAGE (Generic Desktop)
+ 0x09, 0x06, // USAGE (Keyboard)
+ 0xa1, 0x01, // COLLECTION (Application)
+ 0x85, 0x01, // REPORT_ID (1)
+ 0x05, 0x07, // USAGE_PAGE (Keyboard) (Key Codes)
+ 0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)(224)
+ 0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)(231)
+ 0x15, 0x00, // LOGICAL_MINIMUM (0)
+ 0x25, 0x01, // LOGICAL_MAXIMUM (1)
+ 0x75, 0x01, // REPORT_SIZE (1)
+ 0x95, 0x08, // REPORT_COUNT (8)
+ 0x81, 0x02, // INPUT (Data,Var,Abs) ; Modifier byte
+ 0x95, 0x01, // REPORT_COUNT (1)
+ 0x75, 0x08, // REPORT_SIZE (8)
+ 0x81, 0x01, // INPUT (Cnst,Arr,Abs) ; Reserved byte
+ 0x95, 0x03, // REPORT_COUNT (3)
+ 0x75, 0x01, // REPORT_SIZE (1)
+ 0x05, 0x08, // USAGE_PAGE (LEDs)
+ 0x19, 0x01, // USAGE_MINIMUM (Num Lock)
+ 0x29, 0x03, // USAGE_MAXIMUM (Scroll Lock)
+ 0x91, 0x02, // OUTPUT (Data,Var,Abs) ; LED report
+ 0x95, 0x05, // REPORT_COUNT (5)
+ 0x75, 0x01, // REPORT_SIZE (1)
+ 0x91, 0x01, // OUTPUT (Cnst,Arr,Abs)
+ 0x95, 0x05, // REPORT_COUNT (5)
+ 0x75, 0x08, // REPORT_SIZE (8)
+ 0x15, 0x00, // LOGICAL_MINIMUM (0)
+ 0x25, 0xff, // LOGICAL_MAXIMUM (255)
+ 0x05, 0x07, // USAGE_PAGE (Keyboard)(Key Codes)
+ 0x19, 0x00, // USAGE_MINIMUM (Reserved (no event))(0)
+ 0x29, 0xff, // USAGE_MAXIMUM (Keyboard Application)(null)
+ 0x81, 0x00, // INPUT (Data,Ary,Abs)
+ 0xc0 // END_COLLECTION
+};
+
+PROGMEM const char usbHidMouseReportDescriptor[] = {
+ 0x06, 0x00, 0xff, // USAGE_PAGE (65280)
+ 0x09, 0x01, // USAGE (1)
+ 0xa1, 0x01, // COLLECTION (Application)
+ 0x09, 0x01, // USAGE (1)
+ 0x15, 0x00, // LOGICAL_MINIMUM (0)
+ 0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255)
+ 0x75, 0x08, // REPORT_SIZE (8)
+ 0x95, 0x08, // REPORT_COUNT (8)
+ 0x81, 0x02, // INPUT (Data,Var,Abs) ; Modifier byte
+ 0x09, 0x01, // USAGE (Pointer)
+ 0x95, 0x08, // REPORT_COUNT (8)
+ 0x91, 0x02, // OUTPUT (Data,Var,Abs) ; Modifier byte
+ 0x05, 0x0c, // USAGE_PAGE (Consumer)
+ 0x09, 0x00, // USAGE (0)
+ 0x15, 0x80, // LOGICAL_MINIMUM (128)
+ 0x25, 0x7f, // LOGICAL_MAXIMUM (127)
+ 0x75, 0x08, // REPORT_SIZE (8)
+ 0x95, 0x08, // REPORT_COUNT (8)
+ 0xb1, 0x02, // FEATURE (Data,Var,Abs)
+ 0xc0 // END_COLLECTION
+};
+
+/* USB configuration descriptor */
+PROGMEM const char usbDescriptorConfiguration[] = {
+ 9, /* sizeof(usbDescriptorConfiguration) */
+ USBDESCR_CONFIG, /* descriptor type */
+ /* total length of data returned (including inlined descriptors) */
+ 18 + 9 + 9 + 7 + 9 + 7, 0,
+ 2, /* number of interfaces in this cfg */
+ 1, /* index of this configuration */
+ 0, /* configuration name string index */
+ (1 << 7) | USBATTR_REMOTEWAKE, /* attributes */
+ USB_CFG_MAX_BUS_POWER/2, /* max USB current in 2mA units */
+
+ /* Keyboard interface descriptor follows inline: */
+
+ /* sizeof(usbDescrInterface): length of descriptor in bytes */
+ 9,
+ USBDESCR_INTERFACE, /* descriptor type */
+ 0, /* index of this interface */
+ 0, /* alternate setting for this interface */
+ /* endpoints excl 0: number of endpoint descriptors to follow */
+ 1,
+ 3, /* Interface class: HID */
+ 1, /* SubClass: Boot Interface */
+ 1, /* Interface protocol: Keyboard */
+ 0, /* string index for interface */
+ 9, /* sizeof(usbDescrHID) */
+ USBDESCR_HID, /* descriptor type: HID */
+ 0x10, 0x01, /* BCD representation of HID version */
+ 0x00, /* target country code */
+ /* number of HID Report (or other HID class) Descriptors to follow */
+ 0x01,
+ 0x22, /* descriptor type: report */
+ sizeof(usbHidKeyboardReportDescriptor),
+ 0, /* total length of report descriptor */
+
+ /* endpoint descriptor for endpoint 1 */
+ 7, /* sizeof(usbDescrEndpoint) */
+ USBDESCR_ENDPOINT, /* descriptor type = endpoint */
+ (char)0x81, /* IN endpoint number 1 */
+ 0x03, /* attrib: Interrupt endpoint */
+ 8, 0, /* maximum packet size */
+ USB_CFG_INTR_POLL_INTERVAL, /* in ms */
+
+ /* Mouse interface descriptor follows inline: */
+
+ /* sizeof(usbDescrInterface): length of descriptor in bytes */
+ 9,
+ USBDESCR_INTERFACE, /* descriptor type */
+ 1, /* index of this interface */
+ 0, /* alternate setting for this interface */
+ /* endpoints excl 0: number of endpoint descriptors to follow */
+ 1,
+ 3, /* Interface class: HID */
+ 1, /* SubClass: Boot Interface */
+ 2, /* Interface protocol: Mouse */
+ 0, /* string index for interface */
+ 9, /* sizeof(usbDescrHID) */
+ USBDESCR_HID, /* descriptor type: HID */
+ 0x10, 0x01, /* BCD representation of HID version */
+ 0x00, /* target country code */
+ /* number of HID Report (or other HID class) Descriptors to follow */
+ 0x01,
+ 0x22, /* descriptor type: report */
+ /* total length of report descriptor */
+ sizeof(usbHidMouseReportDescriptor), 0,
+
+ /* endpoint descriptor for endpoint 2 */
+ 7, /* sizeof(usbDescrEndpoint) */
+ USBDESCR_ENDPOINT, /* descriptor type = endpoint */
+ (char)0x82, /* IN endpoint number 2 */
+ 0x03, /* attrib: Interrupt endpoint */
+ 8, 0, /* maximum packet size */
+ USB_CFG_INTR_POLL_INTERVAL, /* in ms */
+};
+
+usbMsgLen_t usbFunctionDescriptor(usbRequest_t *rq)
+{
+ if (rq->wValue.bytes[1] == USBDESCR_STRING &&
+ rq->wValue.bytes[0] == 3) {
+ usbMsgPtr = (usbMsgPtr_t) serno_str;
+ return sizeof(serno_str);
+ } else if (rq->wValue.bytes[1] == USBDESCR_HID) {
+ if (rq->wIndex.word == USB_INDEX_MOUSE) {
+ /* "Mouse" */
+ usbMsgPtr = (usbMsgPtr_t)
+ &usbDescriptorConfiguration[43];
+ } else {
+ /* "Keyboard" */
+ usbMsgPtr = (usbMsgPtr_t)
+ &usbDescriptorConfiguration[18];
+ }
+ } else if (rq->wValue.bytes[1] == USBDESCR_HID_REPORT) {
+ if (rq->wIndex.word == USB_INDEX_MOUSE) {
+ usbMsgPtr = (usbMsgPtr_t)
+ &usbHidMouseReportDescriptor[0];
+ return sizeof(usbHidMouseReportDescriptor);
+ } else {
+ usbMsgPtr = (usbMsgPtr_t)
+ &usbHidKeyboardReportDescriptor[0];
+ return sizeof(usbHidKeyboardReportDescriptor);
+ }
+ }
+
+ return 0;
+}
+
+usbMsgLen_t usbFunctionSetup(uchar data[8])
+{
+ usbRequest_t *rq = (void *) data;
+
+ if ((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS) {
+ if (rq->bRequest == USBRQ_HID_GET_REPORT) {
+ /* No keys pressed, no mouse event */
+ usbMsgPtr = (void *) &keyboard_report;
+ keyboard_report.modifier = 0;
+ keyboard_report.reserved = 0;
+ keyboard_report.keycode[0] = 0;
+ return sizeof(keyboard_report);
+ } else if (rq->bRequest == USBRQ_HID_SET_REPORT) {
+ if (rq->wIndex.word == USB_INDEX_MOUSE) {
+ return (rq->wLength.word == 8) ?
+ USB_NO_MSG : 0;
+ } else {
+ /* Keyboard */
+ return (rq->wLength.word == 1) ?
+ USB_NO_MSG : 0;
+ }
+ } else if (rq->bRequest == USBRQ_HID_GET_IDLE) {
+ usbMsgPtr = &idlerate;
+ return 1;
+ } else if (rq->bRequest == USBRQ_HID_SET_IDLE) {
+ idlerate = rq->wValue.bytes[1];
+ }
+ }
+
+ return 0;
+}
+
+usbMsgLen_t usbFunctionWrite(uint8_t * data, uchar len)
+{
+ if (len == 1) {
+ if (data[0] != ledstate) {
+ ledstate = data[0];
+
+ if (ledstate & CAPS_LOCK) {
+ PORTB |= 1 << PB1; // LED on
+ } else {
+ PORTB &= ~(1 << PB1); // LED off
+ }
+ }
+ return 1;
+ } else if (len == 8) {
+ /* Silently consume if unexpected*/
+ if (data[0] != 1)
+ return 8;
+ if ((data[4] | data[5] | data[6] | data[7]) != 0)
+ return 8;
+
+ if (data[1] == 0x80 && data[2] == 0x33 && data[3] == 1) {
+ /* Temperature query */
+ have_temp_int = true;
+ temp_report[0] = 0x80;
+ temp_report[1] = 2;
+ /* Fake 15°C */
+ temp_report[2] = 0xF;
+ temp_report[3] = 0x0;
+
+ } else if (data[1] == 0x82 && data[2] == 0x77 &&
+ data[3] == 1) {
+ /* Initialisation Query #1 */
+ have_temp_int = true;
+ temp_report[0] = 0x82;
+ temp_report[1] = 1;
+ temp_report[2] = 0;
+ } else if (data[1] == 0x86 && data[2] == 0xff &&
+ data[3] == 1) {
+ /* Initialisation Query #2 */
+ have_temp_int = true;
+ memcpy(temp_report, "TEMPerF1", 8);
+ }
+
+ return 8;
+ }
+
+ return 0;
+}
+
+int main(void)
+{
+ unsigned char i;
+
+ wdt_enable(WDTO_1S);
+
+ usbInit();
+ usbDeviceDisconnect();
+
+ i = 0;
+ while (--i) {
+ wdt_reset();
+ _delay_ms(1);
+ }
+
+ usbDeviceConnect();
+
+ /* PB1 as output for LED */
+ DDRB |= 1 << PB1;
+
+ sei(); /* We're ready to go; enable interrupts */
+
+ keyboard_report.modifier = 0;
+ keyboard_report.reserved = 0;
+ keyboard_report.keycode[0] = 0;
+
+ while (1) {
+ wdt_reset();
+ usbPoll();
+
+ if (have_temp_int && usbInterruptIsReady3()) {
+ usbSetInterrupt3((void *) temp_report,
+ sizeof(temp_report));
+ if (temp_report[0] == 'T') {
+ /* Hack up second response for 0x86 query */
+ memcpy(temp_report, ".2", 2);
+ } else {
+ have_temp_int = false;
+ }
+ }
+ }
+}
/* --------------------------- Functional Range ---------------------------- */
-#define USB_CFG_HAVE_INTRIN_ENDPOINT 0
+#define USB_CFG_HAVE_INTRIN_ENDPOINT 1
/* Define this to 1 if you want to compile a version with two endpoints: The
* default control endpoint 0 and an interrupt-in endpoint (any other endpoint
* number).
*/
-#define USB_CFG_HAVE_INTRIN_ENDPOINT3 0
+#define USB_CFG_HAVE_INTRIN_ENDPOINT3 1
/* Define this to 1 if you want to compile a version with three endpoints: The
* default control endpoint 0, an interrupt-in endpoint 3 (or the number
* configured below) and a catch-all default interrupt-in endpoint as above.
* You must also define USB_CFG_HAVE_INTRIN_ENDPOINT to 1 for this feature.
*/
-#define USB_CFG_EP3_NUMBER 3
+#define USB_CFG_EP3_NUMBER 2
/* If the so-called endpoint 3 is used, it can now be configured to any other
* endpoint number (except 0) with this macro. Default if undefined is 3.
*/
* (e.g. HID), but never want to send any data. This option saves a couple
* of bytes in flash memory and the transmit buffers in RAM.
*/
-#define USB_CFG_INTR_POLL_INTERVAL 10
+#define USB_CFG_INTR_POLL_INTERVAL 20
/* If you compile a version with endpoint 1 (interrupt-in), this is the poll
* interval. The value is in milliseconds and must not be less than 10 ms for
* low speed devices.
* The value is in milliamperes. [It will be divided by two since USB
* communicates power requirements in units of 2 mA.]
*/
-#define USB_CFG_IMPLEMENT_FN_WRITE 0
+#define USB_CFG_IMPLEMENT_FN_WRITE 1
/* Set this to 1 if you want usbFunctionWrite() to be called for control-out
* transfers. Set it to 0 if you don't need it and want to save a couple of
* bytes.
/* define this macro to 1 if you want the function usbMeasureFrameLength()
* compiled in. This function can be used to calibrate the AVR's RC oscillator.
*/
-#define USB_USE_FAST_CRC 0
+#define USB_USE_FAST_CRC 1
/* The assembler module has two implementations for the CRC algorithm. One is
* faster, the other is smaller. This CRC routine is only used for transmitted
* messages where timing is not critical. The faster routine needs 31 cycles
/* -------------------------- Device Description --------------------------- */
-#define USB_CFG_VENDOR_ID 0xc0, 0x16 /* = 0x16c0 = 5824 = voti.nl */
+#define USB_CFG_VENDOR_ID 0x45, 0x0c
/* USB vendor ID for the device, low byte first. If you have registered your
* own Vendor ID, define it here. Otherwise you may use one of obdev's free
* shared VID/PID pairs. Be sure to read USB-IDs-for-free.txt for rules!
* with libusb: 0x16c0/0x5dc. Use this VID/PID pair ONLY if you understand
* the implications!
*/
-#define USB_CFG_DEVICE_ID 0xdc, 0x05 /* = 0x05dc = 1500 */
+#define USB_CFG_DEVICE_ID 0x01, 0x74
/* This is the ID of the product, low byte first. It is interpreted in the
* scope of the vendor ID. If you have registered your own VID with usb.org
* or if you have licensed a PID from somebody else, define it here. Otherwise
#define USB_CFG_DEVICE_VERSION 0x00, 0x01
/* Version number of the device: Minor number first, then major number.
*/
-#define USB_CFG_VENDOR_NAME 'o', 'b', 'd', 'e', 'v', '.', 'a', 't'
+#define USB_CFG_VENDOR_NAME 'e', 'a', 'r', 't', 'h', '.', 'l', 'i'
#define USB_CFG_VENDOR_NAME_LEN 8
/* These two values define the vendor name returned by the USB device. The name
* must be given as a list of characters under single quotes. The characters
* obdev's free shared VID/PID pair. See the file USB-IDs-for-free.txt for
* details.
*/
-#define USB_CFG_DEVICE_NAME 'T', 'e', 'm', 'p', 'l', 'a', 't', 'e'
-#define USB_CFG_DEVICE_NAME_LEN 8
+#define USB_CFG_DEVICE_NAME 'T', 'E', 'M', 'P', 'e', 'r', 'V', '1', '.', '2'
+#define USB_CFG_DEVICE_NAME_LEN 10
/* Same as above for the device name. If you don't want a device name, undefine
* the macros. See the file USB-IDs-for-free.txt before you assign a name if
* you use a shared VID/PID.
* to fine tune control over USB descriptors such as the string descriptor
* for the serial number.
*/
-#define USB_CFG_DEVICE_CLASS 0xff /* set to 0 if deferred to interface */
+#define USB_CFG_DEVICE_CLASS 0
#define USB_CFG_DEVICE_SUBCLASS 0
/* See USB specification if you want to conform to an existing device class.
* Class 0xff is "vendor specific".
*/
-#define USB_CFG_INTERFACE_CLASS 0 /* define class here if not at device level */
+#define USB_CFG_INTERFACE_CLASS 0 /* At device level */
#define USB_CFG_INTERFACE_SUBCLASS 0
#define USB_CFG_INTERFACE_PROTOCOL 0
/* See USB specification if you want to conform to an existing device class or
* };
*/
-#define CONFIG_DESCRIPTOR_SIZE 41
-
#define USB_CFG_DESCR_PROPS_DEVICE 0
-#define USB_CFG_DESCR_PROPS_CONFIGURATION 0
+#define USB_CFG_DESCR_PROPS_CONFIGURATION (9 + 2 * (9 + 9 + 7))
#define USB_CFG_DESCR_PROPS_STRINGS 0
#define USB_CFG_DESCR_PROPS_STRING_0 0
#define USB_CFG_DESCR_PROPS_STRING_VENDOR 0
#define USB_CFG_DESCR_PROPS_STRING_PRODUCT 0
-#define USB_CFG_DESCR_PROPS_STRING_SERIAL_NUMBER 0
+#define USB_CFG_DESCR_PROPS_STRING_SERIAL_NUMBER (USB_PROP_IS_DYNAMIC | USB_PROP_IS_RAM)
#define USB_CFG_DESCR_PROPS_HID 0
#define USB_CFG_DESCR_PROPS_HID_REPORT 0
-#define USB_CFG_DESCR_PROPS_UNKNOWN 0
+#define USB_CFG_DESCR_PROPS_UNKNOWN 0xFFFF
//#define usbMsgPtr_t unsigned short