]> the.earth.li Git - riso-kagaku-clone.git/blobdiff - main.c
Add initial Riso Kagaku Webmail Notifier ATTiny clone
[riso-kagaku-clone.git] / main.c
diff --git a/main.c b/main.c
new file mode 100644 (file)
index 0000000..2308d55
--- /dev/null
+++ b/main.c
@@ -0,0 +1,279 @@
+/*
+ * Basic firmware for an RGB LED notifier clone of the Riso Kaguku
+ * Webmail Notifier.
+ *
+ * Copyright 2016 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 <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"
+
+#define RED_BIT    8 /* Bit 3 on port B */
+#define GREEN_BIT 16 /* Bit 4 on port B */
+#define BLUE_BIT  32 /* Bit 5 on port B */
+#define ALL_BITS (RED_BIT | GREEN_BIT | BLUE_BIT)
+#define CMD_SET_SERIAL 0xfa
+
+uchar serno_read = 0;
+int serno_str[] = {
+       USB_STRING_DESCRIPTOR_HEADER(8),
+       'U', 'N', 'S', 'E', 'T', 'X', 'X', 'X',
+};
+
+PROGMEM const char usbDescriptorConfiguration[CONFIG_DESCRIPTOR_SIZE] = {    /* USB configuration descriptor */
+       9,          /* sizeof(usbDescriptorConfiguration): length of descriptor in bytes */
+       USBDESCR_CONFIG,    /* descriptor type */
+       18 + 7 + 7 + 9, 0,
+       /* total length of data returned (including inlined descriptors) */
+       1,          /* number of interfaces in this configuration */
+       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 */
+       /* interface descriptor follows inline: */
+       9,          /* sizeof(usbDescrInterface): length of descriptor in bytes */
+       USBDESCR_INTERFACE, /* descriptor type */
+       0,          /* index of this interface */
+       0,          /* alternate setting for this interface */
+       2,          /* endpoints excl 0: number of endpoint descriptors to follow */
+       USB_CFG_INTERFACE_CLASS,
+       USB_CFG_INTERFACE_SUBCLASS,
+       USB_CFG_INTERFACE_PROTOCOL,
+       0,          /* string index for interface */
+       9,          /* sizeof(usbDescrHID): length of descriptor in bytes */
+       USBDESCR_HID,   /* descriptor type: HID */
+       0x01, 0x01, /* BCD representation of HID version */
+       0x00,       /* target country code */
+       0x01,       /* number of HID Report (or other HID class) Descriptor infos to follow */
+       0x22,       /* descriptor type: report */
+       USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH, 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 */
+       /* endpoint descriptor for endpoint 2 */
+       7,          /* sizeof(usbDescrEndpoint) */
+       USBDESCR_ENDPOINT,  /* descriptor type = endpoint */
+       2,                      /* OUT endpoint number 2 */
+       0x03,       /* attrib: Interrupt endpoint */
+       5, 0,       /* maximum packet size */
+       USB_CFG_INTR_POLL_INTERVAL, /* in ms */
+};
+
+PROGMEM const char usbHidReportDescriptor[22] = {
+       0x06, 0x00, 0xff,               /* USAGE PAGE (Generic Desktop) */
+       0x09, 0x01,                     /* USAGE (Vendor Usage 1) */
+       0xa1, 0x01,                     /* COLLECTION (Application) */
+       0x15, 0x00,                     /*   LOGICAL_MINIMUM (0) */
+       0x26, 0xff, 0x00,               /*   LOGICAL_MAXIMUM (255) */
+       0x75, 0x08,                     /*   REPORT_SIZE (8 bits) */
+       0x95, 0x06,                     /*   REPORT_COUNT (6 elements) */
+       0x09, 0x00,                     /*   USAGE (Undefined) */
+       0xb2, 0x02, 0x01,               /*   FEATURE (Data, Var, Abs, Buf) */
+       0xc0                            /* END_COLLECTION */
+};
+
+inline char hexdigit(int i)
+{
+       return (i < 10) ? ('0' + i) : ('A' - 10 + i);
+}
+
+void fetch_serno(void)
+{
+       uint32_t serno;
+
+       if (!serno_read) {
+               eeprom_read_block(&serno, 0, 4);
+               if (serno == 0xffffffff) {
+                       serno_str[1] = 'U';
+                       serno_str[2] = 'N';
+                       serno_str[3] = 'S';
+                       serno_str[4] = 'E';
+                       serno_str[5] = 'T';
+                       serno_str[6] = 'X';
+                       serno_str[7] = 'X';
+                       serno_str[8] = 'X';
+               } else {
+                       serno_str[1] = hexdigit((serno >> 28));
+                       serno_str[2] = hexdigit((serno >> 24) & 0xF);
+                       serno_str[3] = hexdigit((serno >> 20) & 0xF);
+                       serno_str[4] = hexdigit((serno >> 16) & 0xF);
+                       serno_str[5] = hexdigit((serno >> 12) & 0xF);
+                       serno_str[6] = hexdigit((serno >>  8) & 0xF);
+                       serno_str[7] = hexdigit((serno >>  4) & 0xF);
+                       serno_str[8] = hexdigit( serno        & 0xF);
+               }
+               serno_read = 1;
+       }
+}
+
+void update_serno(uint32_t serno)
+{
+       eeprom_write_block(&serno, 0x00, 4);
+
+       serno_str[1] = hexdigit((serno >> 28));
+       serno_str[2] = hexdigit((serno >> 24) & 0xF);
+       serno_str[3] = hexdigit((serno >> 20) & 0xF);
+       serno_str[4] = hexdigit((serno >> 16) & 0xF);
+       serno_str[5] = hexdigit((serno >> 12) & 0xF);
+       serno_str[6] = hexdigit((serno >>  8) & 0xF);
+       serno_str[7] = hexdigit((serno >>  4) & 0xF);
+       serno_str[8] = hexdigit( serno        & 0xF);
+}
+
+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) ||
+                               (rq->bRequest == USBRQ_HID_SET_REPORT)) {
+                       return 0xFF;
+               }
+       }
+
+       return 0;
+}
+
+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);
+       }
+
+       return 0;
+}
+
+uchar usbFunctionRead(uchar *data, uchar len)
+{
+       uint8_t colour_map[8] = { 0, 2, 1, 5, 3, 6, 4, 7 };
+       uint8_t status;
+
+       if (len != 0) {
+               status = 0;
+               if (PORTB & RED_BIT)
+                       status |= 1;
+               if (PORTB & GREEN_BIT)
+                       status |= 2;
+               if (PORTB & BLUE_BIT)
+                       status |= 4;
+               /* Map RGB back to Riso Kagaku colour code. */
+               data[0] = colour_map[status];
+               return len;
+       }
+
+       return 0;
+}
+
+uchar usbFunctionWrite(uchar *data, uchar len)
+{
+       /*
+        * The Riso Kagaku has an internal colour table, map it back to an
+        * RGB bitmap.
+        */
+       uint8_t colour_map[8] = { 0, 2, 1, 4, 6, 3, 5, 7 };
+       uint8_t rgb_val, port_val;
+
+       if (data[0] < 8) {
+               rgb_val = colour_map[data[0]];
+
+               port_val = PORTB & ~ALL_BITS;
+
+               if (rgb_val & 1)
+                       port_val |= RED_BIT;
+               if (rgb_val & 2)
+                       port_val |= GREEN_BIT;
+               if (rgb_val & 4)
+                       port_val |= BLUE_BIT;
+
+               PORTB = port_val;
+       } else if (data[0] == CMD_SET_SERIAL) {
+               update_serno(*(uint32_t *) &data[1]);
+       }
+
+       return len;
+}
+
+void usbFunctionWriteOut(uchar *data, uchar len)
+{
+       /*
+        * The Riso Kagaku has an internal colour table, map it back to an
+        * RGB bitmap.
+        */
+       uint8_t colour_map[8] = { 0, 2, 1, 4, 6, 3, 5, 7 };
+       uint8_t rgb_val, port_val;
+
+       if (!len)
+               return;
+
+       if (data[0] < 8) {
+               rgb_val = colour_map[data[0]];
+
+               port_val = PORTB & ~ALL_BITS;
+
+               if (rgb_val & 1)
+                       port_val |= RED_BIT;
+               if (rgb_val & 2)
+                       port_val |= GREEN_BIT;
+               if (rgb_val & 4)
+                       port_val |= BLUE_BIT;
+
+               PORTB = port_val;
+       } else if (len == 5 && data[0] == CMD_SET_SERIAL) {
+               update_serno(*(uint32_t *) &data[1]);
+       }
+}
+
+int __attribute__((noreturn)) main(void)
+{
+       unsigned char i;
+
+       wdt_enable(WDTO_1S);
+
+       usbInit();
+       usbDeviceDisconnect();
+
+       i = 0;
+       while (--i) {
+               wdt_reset();
+               _delay_ms(1);
+       }
+
+       usbDeviceConnect();
+
+       /* Set the LED bits to output mode */
+       DDRB |= ALL_BITS;
+       /* Turn them off */
+       PORTB &= ~ALL_BITS;
+
+       sei(); /* We're ready to go; enable interrupts */
+
+       while (1) {
+               wdt_reset();
+               usbPoll();
+       }
+}