Browse Source

firmware with USB CDC and HID. PMW3360 driver started, not working yet.

Thomas Buck 1 year ago
parent
commit
44da7888a4

+ 6
- 0
firmware/CMakeLists.txt View File

@@ -13,8 +13,12 @@ add_executable(trackball)
13 13
 
14 14
 target_sources(trackball PUBLIC
15 15
     src/main.c
16
+    src/log.c
17
+    src/pmw3360.c
16 18
     src/usb.c
19
+    src/usb_cdc.c
17 20
     src/usb_descriptors.c
21
+    src/usb_hid.c
18 22
 )
19 23
 
20 24
 # Make sure TinyUSB can find tusb_config.h
@@ -23,8 +27,10 @@ target_include_directories(trackball PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include)
23 27
 # pull in common dependencies
24 28
 target_link_libraries(trackball
25 29
     pico_stdlib
30
+    pico_unique_id
26 31
     tinyusb_device
27 32
     tinyusb_board
33
+    hardware_spi
28 34
 )
29 35
 
30 36
 # fix for Errata RP2040-E5 (the fix requires use of GPIO 15)

+ 10
- 2
firmware/README.md View File

@@ -2,7 +2,7 @@
2 2
 
3 3
 For use with Raspberry Pi Pico boards.
4 4
 
5
-Adapted from the `dev_hid_composite` pico-example [from GitHub](https://github.com/raspberrypi/pico-examples/tree/master/usb/device/dev_hid_composite).
5
+Adapted from the `dev_hid_composite` pico-example [from GitHub](https://github.com/raspberrypi/pico-examples/tree/master/usb/device/dev_hid_composite), as well as the tinyusb-cdc-example [from GitHub](https://github.com/hathach/tinyusb/blob/master/examples/device/cdc_msc/src/main.c).
6 6
 
7 7
 Supports PMW3360 optical mouse sensors.
8 8
 
@@ -21,4 +21,12 @@ Then do this to build.
21 21
     cmake ..
22 22
     make trackball
23 23
 
24
-And flash the resulting `trackball.uf2` file to your Pico.
24
+And flash the resulting `trackball.uf2` file to your Pico as usual.
25
+
26
+For convenience you can use the included `flash.sh`, as long as you flashed the binary manually once before.
27
+
28
+    make trackball
29
+    ../flash.sh trackball.uf2
30
+
31
+For debugging a serial port will be presented by the firmware.
32
+Open it using eg. `picocom`, or with the included `debug.sh` script.

+ 18
- 0
firmware/debug.sh View File

@@ -0,0 +1,18 @@
1
+#!/bin/bash
2
+set -euo pipefail
3
+
4
+SERIAL=/dev/serial/by-id/usb-xythobuz_Trackball_*
5
+
6
+echo -n Waiting for serial port to appear
7
+until [ -e $SERIAL ]
8
+do
9
+    echo -n .
10
+    sleep 1
11
+done
12
+echo
13
+
14
+echo Opening picocom terminal
15
+echo "[C-a] [C-x] to exit"
16
+echo
17
+
18
+picocom -q $SERIAL

+ 28
- 0
firmware/flash.sh View File

@@ -0,0 +1,28 @@
1
+#!/bin/bash
2
+set -euo pipefail
3
+
4
+SERIAL=/dev/serial/by-id/usb-xythobuz_Trackball_*
5
+DISK=/dev/disk/by-label/RPI-RP2
6
+MOUNT=/mnt/tmp
7
+
8
+if [ ! -e $DISK ]
9
+then
10
+    echo Resetting Raspberry Pi Pico
11
+    echo -n -e "\\x1b" > $SERIAL
12
+fi
13
+
14
+echo -n Waiting for disk to appear
15
+until [ -e $DISK ]
16
+do
17
+    echo -n .
18
+    sleep 1
19
+done
20
+echo
21
+
22
+echo Mounting bootloader disk
23
+sudo mount $DISK $MOUNT
24
+
25
+echo Copying binary
26
+sudo cp $1 $MOUNT
27
+
28
+echo Done

+ 18
- 0
firmware/include/log.h View File

@@ -0,0 +1,18 @@
1
+/*
2
+ * log.h
3
+ */
4
+
5
+#ifndef __LOG_H__
6
+#define __LOG_H__
7
+
8
+void debug_log(const char *format, ...) __attribute__((format(printf, 1, 2)));
9
+
10
+#define debug(fmt, ...) debug_log( \
11
+        "%08u %s: " fmt "\r\n", \
12
+        to_ms_since_boot(get_absolute_time()), \
13
+        __func__, \
14
+        ##__VA_ARGS__)
15
+
16
+void log_dump_to_usb(void);
17
+
18
+#endif // __LOG_H__

+ 10
- 0
firmware/include/pmw3360.h View File

@@ -0,0 +1,10 @@
1
+/*
2
+ * pmw3360.h
3
+ */
4
+
5
+#ifndef __PMW3360_H__
6
+#define __PMW3360_H__
7
+
8
+int pmw_init(void);
9
+
10
+#endif // __PMW3360_H__

+ 75
- 0
firmware/include/pmw3360_registers.h View File

@@ -0,0 +1,75 @@
1
+/*
2
+ * pmw3360_registers.h
3
+ */
4
+
5
+#ifndef __PMW3360_REGISTERS_H__
6
+#define __PMW3360_REGISTERS_H__
7
+
8
+#define REG_PRODUCT_ID                 0x00
9
+#define REG_REVISION_ID                0x01
10
+#define REG_MOTION                     0x02
11
+#define REG_DELTA_X_L                  0x03
12
+#define REG_DELTA_X_H                  0x04
13
+#define REG_DELTA_Y_L                  0x05
14
+#define REG_DELTA_Y_H                  0x06
15
+#define REG_SQUAL                      0x07
16
+#define REG_RAW_DATA_SUM               0x08
17
+#define REG_MAXIMUM_RAW_DATA           0x09
18
+#define REG_MINIMUM_RAW_DATA           0x0A
19
+#define REG_SHUTTER_LOWER              0x0B
20
+#define REG_SHUTTER_UPPER              0x0C
21
+#define REG_CONTROL                    0x0D
22
+#define REG_CONFIG1                    0x0F
23
+#define REG_CONFIG2                    0x10
24
+#define REG_ANGLE_TUNE                 0x11
25
+#define REG_FRAME_CAPTURE              0x12
26
+#define REG_SROM_ENABLE                0x13
27
+#define REG_RUN_DOWNSHIFT              0x14
28
+#define REG_REST1_RATE_LOWER           0x15
29
+#define REG_REST1_RATE_UPPER           0x16
30
+#define REG_REST1_DOWNSHIFT            0x17
31
+#define REG_REST2_RATE_LOWER           0x18
32
+#define REG_REST2_RATE_UPPER           0x19
33
+#define REG_REST2_DOWNSHIFT            0x1A
34
+#define REG_REST3_RATE_LOWER           0x1B
35
+#define REG_REST3_RATE_UPPER           0x1C
36
+#define REG_OBSERVATION                0x24
37
+#define REG_DATA_OUT_LOWER             0x25
38
+#define REG_DATA_OUT_UPPER             0x26
39
+#define REG_RAW_DATA_DUMP              0x29
40
+#define REG_SROM_ID                    0x2A
41
+#define REG_MIN_SQ_RUN                 0x2B
42
+#define REG_RAW_DATA_THRESHOLD         0x2C
43
+#define REG_CONFIG5                    0x2F
44
+#define REG_POWER_UP_RESET             0x3A
45
+#define REG_SHUTDOWN                   0x3B
46
+#define REG_INVERSE_PRODUCT_ID         0x3F
47
+#define REG_LIFTCUTOFF_TUNE3           0x41
48
+#define REG_ANGLE_SNAP                 0x42
49
+#define REG_LIFTCUTOFF_TUNE1           0x4A
50
+#define REG_MOTION_BURST               0x50
51
+#define REG_LIFTCUTOFF_TUNE_TIMEOUT    0x58
52
+#define REG_LIFTCUTOFF_TUNE_MIN_LENGTH 0x5A
53
+#define REG_SROM_LOAD_BURST            0x62
54
+#define REG_LIFT_CONFIG                0x63
55
+#define REG_RAW_DATA_BURST             0x64
56
+#define REG_LIFTCUTOFF_TUNE2           0x65
57
+
58
+#define WRITE_BIT 0x80
59
+
60
+struct pmw_motion_report {
61
+    uint8_t motion;
62
+    uint8_t observation;
63
+    uint8_t delta_x_l;
64
+    uint8_t delta_x_h;
65
+    uint8_t delta_y_l;
66
+    uint8_t delta_y_h;
67
+    uint8_t squal;
68
+    uint8_t raw_data_sum;
69
+    uint8_t maximum_raw_data;
70
+    uint8_t minimum_raw_data;
71
+    uint8_t shutter_upper;
72
+    uint8_t shutter_lower;
73
+} __attribute__((packed));
74
+
75
+#endif // __PMW3360_REGISTERS_H__

+ 290
- 0
firmware/include/pmw3360_srom.h View File

@@ -0,0 +1,290 @@
1
+/*
2
+ * pmw3360_srom.h
3
+ *
4
+ * https://github.com/mrjohnk/PMW3360DM-T2QU/blob/master/Arduino%20Examples/PMW3360DM-polling/SROM_0x04_Arduino.ino
5
+ * https://github.com/SunjunKim/PMW3360/blob/master/src/PMW3360.cpp
6
+ */
7
+
8
+#ifndef __PMW3360_SROM_H__
9
+#define __PMW3360_SROM_H__
10
+
11
+// Firmware "PMW3360DM_srom_0x04"
12
+
13
+static const uint16_t pmw_fw_length = 4094;
14
+static const uint8_t pmw_fw_data[] = {
15
+0x01, 0x04, 0x8e, 0x96, 0x6e, 0x77, 0x3e, 0xfe, 0x7e, 0x5f, 0x1d, 0xb8, 0xf2, 0x66, 0x4e,
16
+0xff, 0x5d, 0x19, 0xb0, 0xc2, 0x04, 0x69, 0x54, 0x2a, 0xd6, 0x2e, 0xbf, 0xdd, 0x19, 0xb0,
17
+0xc3, 0xe5, 0x29, 0xb1, 0xe0, 0x23, 0xa5, 0xa9, 0xb1, 0xc1, 0x00, 0x82, 0x67, 0x4c, 0x1a,
18
+0x97, 0x8d, 0x79, 0x51, 0x20, 0xc7, 0x06, 0x8e, 0x7c, 0x7c, 0x7a, 0x76, 0x4f, 0xfd, 0x59,
19
+0x30, 0xe2, 0x46, 0x0e, 0x9e, 0xbe, 0xdf, 0x1d, 0x99, 0x91, 0xa0, 0xa5, 0xa1, 0xa9, 0xd0,
20
+0x22, 0xc6, 0xef, 0x5c, 0x1b, 0x95, 0x89, 0x90, 0xa2, 0xa7, 0xcc, 0xfb, 0x55, 0x28, 0xb3,
21
+0xe4, 0x4a, 0xf7, 0x6c, 0x3b, 0xf4, 0x6a, 0x56, 0x2e, 0xde, 0x1f, 0x9d, 0xb8, 0xd3, 0x05,
22
+0x88, 0x92, 0xa6, 0xce, 0x1e, 0xbe, 0xdf, 0x1d, 0x99, 0xb0, 0xe2, 0x46, 0xef, 0x5c, 0x07,
23
+0x11, 0x5d, 0x98, 0x0b, 0x9d, 0x94, 0x97, 0xee, 0x4e, 0x45, 0x33, 0x6b, 0x44, 0xc7, 0x29,
24
+0x56, 0x27, 0x30, 0xc6, 0xa7, 0xd5, 0xf2, 0x56, 0xdf, 0xb4, 0x38, 0x62, 0xcb, 0xa0, 0xb6,
25
+0xe3, 0x0f, 0x84, 0x06, 0x24, 0x05, 0x65, 0x6f, 0x76, 0x89, 0xb5, 0x77, 0x41, 0x27, 0x82,
26
+0x66, 0x65, 0x82, 0xcc, 0xd5, 0xe6, 0x20, 0xd5, 0x27, 0x17, 0xc5, 0xf8, 0x03, 0x23, 0x7c,
27
+0x5f, 0x64, 0xa5, 0x1d, 0xc1, 0xd6, 0x36, 0xcb, 0x4c, 0xd4, 0xdb, 0x66, 0xd7, 0x8b, 0xb1,
28
+0x99, 0x7e, 0x6f, 0x4c, 0x36, 0x40, 0x06, 0xd6, 0xeb, 0xd7, 0xa2, 0xe4, 0xf4, 0x95, 0x51,
29
+0x5a, 0x54, 0x96, 0xd5, 0x53, 0x44, 0xd7, 0x8c, 0xe0, 0xb9, 0x40, 0x68, 0xd2, 0x18, 0xe9,
30
+0xdd, 0x9a, 0x23, 0x92, 0x48, 0xee, 0x7f, 0x43, 0xaf, 0xea, 0x77, 0x38, 0x84, 0x8c, 0x0a,
31
+0x72, 0xaf, 0x69, 0xf8, 0xdd, 0xf1, 0x24, 0x83, 0xa3, 0xf8, 0x4a, 0xbf, 0xf5, 0x94, 0x13,
32
+0xdb, 0xbb, 0xd8, 0xb4, 0xb3, 0xa0, 0xfb, 0x45, 0x50, 0x60, 0x30, 0x59, 0x12, 0x31, 0x71,
33
+0xa2, 0xd3, 0x13, 0xe7, 0xfa, 0xe7, 0xce, 0x0f, 0x63, 0x15, 0x0b, 0x6b, 0x94, 0xbb, 0x37,
34
+0x83, 0x26, 0x05, 0x9d, 0xfb, 0x46, 0x92, 0xfc, 0x0a, 0x15, 0xd1, 0x0d, 0x73, 0x92, 0xd6,
35
+0x8c, 0x1b, 0x8c, 0xb8, 0x55, 0x8a, 0xce, 0xbd, 0xfe, 0x8e, 0xfc, 0xed, 0x09, 0x12, 0x83,
36
+0x91, 0x82, 0x51, 0x31, 0x23, 0xfb, 0xb4, 0x0c, 0x76, 0xad, 0x7c, 0xd9, 0xb4, 0x4b, 0xb2,
37
+0x67, 0x14, 0x09, 0x9c, 0x7f, 0x0c, 0x18, 0xba, 0x3b, 0xd6, 0x8e, 0x14, 0x2a, 0xe4, 0x1b,
38
+0x52, 0x9f, 0x2b, 0x7d, 0xe1, 0xfb, 0x6a, 0x33, 0x02, 0xfa, 0xac, 0x5a, 0xf2, 0x3e, 0x88,
39
+0x7e, 0xae, 0xd1, 0xf3, 0x78, 0xe8, 0x05, 0xd1, 0xe3, 0xdc, 0x21, 0xf6, 0xe1, 0x9a, 0xbd,
40
+0x17, 0x0e, 0xd9, 0x46, 0x9b, 0x88, 0x03, 0xea, 0xf6, 0x66, 0xbe, 0x0e, 0x1b, 0x50, 0x49,
41
+0x96, 0x40, 0x97, 0xf1, 0xf1, 0xe4, 0x80, 0xa6, 0x6e, 0xe8, 0x77, 0x34, 0xbf, 0x29, 0x40,
42
+0x44, 0xc2, 0xff, 0x4e, 0x98, 0xd3, 0x9c, 0xa3, 0x32, 0x2b, 0x76, 0x51, 0x04, 0x09, 0xe7,
43
+0xa9, 0xd1, 0xa6, 0x32, 0xb1, 0x23, 0x53, 0xe2, 0x47, 0xab, 0xd6, 0xf5, 0x69, 0x5c, 0x3e,
44
+0x5f, 0xfa, 0xae, 0x45, 0x20, 0xe5, 0xd2, 0x44, 0xff, 0x39, 0x32, 0x6d, 0xfd, 0x27, 0x57,
45
+0x5c, 0xfd, 0xf0, 0xde, 0xc1, 0xb5, 0x99, 0xe5, 0xf5, 0x1c, 0x77, 0x01, 0x75, 0xc5, 0x6d,
46
+0x58, 0x92, 0xf2, 0xb2, 0x47, 0x00, 0x01, 0x26, 0x96, 0x7a, 0x30, 0xff, 0xb7, 0xf0, 0xef,
47
+0x77, 0xc1, 0x8a, 0x5d, 0xdc, 0xc0, 0xd1, 0x29, 0x30, 0x1e, 0x77, 0x38, 0x7a, 0x94, 0xf1,
48
+0xb8, 0x7a, 0x7e, 0xef, 0xa4, 0xd1, 0xac, 0x31, 0x4a, 0xf2, 0x5d, 0x64, 0x3d, 0xb2, 0xe2,
49
+0xf0, 0x08, 0x99, 0xfc, 0x70, 0xee, 0x24, 0xa7, 0x7e, 0xee, 0x1e, 0x20, 0x69, 0x7d, 0x44,
50
+0xbf, 0x87, 0x42, 0xdf, 0x88, 0x3b, 0x0c, 0xda, 0x42, 0xc9, 0x04, 0xf9, 0x45, 0x50, 0xfc,
51
+0x83, 0x8f, 0x11, 0x6a, 0x72, 0xbc, 0x99, 0x95, 0xf0, 0xac, 0x3d, 0xa7, 0x3b, 0xcd, 0x1c,
52
+0xe2, 0x88, 0x79, 0x37, 0x11, 0x5f, 0x39, 0x89, 0x95, 0x0a, 0x16, 0x84, 0x7a, 0xf6, 0x8a,
53
+0xa4, 0x28, 0xe4, 0xed, 0x83, 0x80, 0x3b, 0xb1, 0x23, 0xa5, 0x03, 0x10, 0xf4, 0x66, 0xea,
54
+0xbb, 0x0c, 0x0f, 0xc5, 0xec, 0x6c, 0x69, 0xc5, 0xd3, 0x24, 0xab, 0xd4, 0x2a, 0xb7, 0x99,
55
+0x88, 0x76, 0x08, 0xa0, 0xa8, 0x95, 0x7c, 0xd8, 0x38, 0x6d, 0xcd, 0x59, 0x02, 0x51, 0x4b,
56
+0xf1, 0xb5, 0x2b, 0x50, 0xe3, 0xb6, 0xbd, 0xd0, 0x72, 0xcf, 0x9e, 0xfd, 0x6e, 0xbb, 0x44,
57
+0xc8, 0x24, 0x8a, 0x77, 0x18, 0x8a, 0x13, 0x06, 0xef, 0x97, 0x7d, 0xfa, 0x81, 0xf0, 0x31,
58
+0xe6, 0xfa, 0x77, 0xed, 0x31, 0x06, 0x31, 0x5b, 0x54, 0x8a, 0x9f, 0x30, 0x68, 0xdb, 0xe2,
59
+0x40, 0xf8, 0x4e, 0x73, 0xfa, 0xab, 0x74, 0x8b, 0x10, 0x58, 0x13, 0xdc, 0xd2, 0xe6, 0x78,
60
+0xd1, 0x32, 0x2e, 0x8a, 0x9f, 0x2c, 0x58, 0x06, 0x48, 0x27, 0xc5, 0xa9, 0x5e, 0x81, 0x47,
61
+0x89, 0x46, 0x21, 0x91, 0x03, 0x70, 0xa4, 0x3e, 0x88, 0x9c, 0xda, 0x33, 0x0a, 0xce, 0xbc,
62
+0x8b, 0x8e, 0xcf, 0x9f, 0xd3, 0x71, 0x80, 0x43, 0xcf, 0x6b, 0xa9, 0x51, 0x83, 0x76, 0x30,
63
+0x82, 0xc5, 0x6a, 0x85, 0x39, 0x11, 0x50, 0x1a, 0x82, 0xdc, 0x1e, 0x1c, 0xd5, 0x7d, 0xa9,
64
+0x71, 0x99, 0x33, 0x47, 0x19, 0x97, 0xb3, 0x5a, 0xb1, 0xdf, 0xed, 0xa4, 0xf2, 0xe6, 0x26,
65
+0x84, 0xa2, 0x28, 0x9a, 0x9e, 0xdf, 0xa6, 0x6a, 0xf4, 0xd6, 0xfc, 0x2e, 0x5b, 0x9d, 0x1a,
66
+0x2a, 0x27, 0x68, 0xfb, 0xc1, 0x83, 0x21, 0x4b, 0x90, 0xe0, 0x36, 0xdd, 0x5b, 0x31, 0x42,
67
+0x55, 0xa0, 0x13, 0xf7, 0xd0, 0x89, 0x53, 0x71, 0x99, 0x57, 0x09, 0x29, 0xc5, 0xf3, 0x21,
68
+0xf8, 0x37, 0x2f, 0x40, 0xf3, 0xd4, 0xaf, 0x16, 0x08, 0x36, 0x02, 0xfc, 0x77, 0xc5, 0x8b,
69
+0x04, 0x90, 0x56, 0xb9, 0xc9, 0x67, 0x9a, 0x99, 0xe8, 0x00, 0xd3, 0x86, 0xff, 0x97, 0x2d,
70
+0x08, 0xe9, 0xb7, 0xb3, 0x91, 0xbc, 0xdf, 0x45, 0xc6, 0xed, 0x0f, 0x8c, 0x4c, 0x1e, 0xe6,
71
+0x5b, 0x6e, 0x38, 0x30, 0xe4, 0xaa, 0xe3, 0x95, 0xde, 0xb9, 0xe4, 0x9a, 0xf5, 0xb2, 0x55,
72
+0x9a, 0x87, 0x9b, 0xf6, 0x6a, 0xb2, 0xf2, 0x77, 0x9a, 0x31, 0xf4, 0x7a, 0x31, 0xd1, 0x1d,
73
+0x04, 0xc0, 0x7c, 0x32, 0xa2, 0x9e, 0x9a, 0xf5, 0x62, 0xf8, 0x27, 0x8d, 0xbf, 0x51, 0xff,
74
+0xd3, 0xdf, 0x64, 0x37, 0x3f, 0x2a, 0x6f, 0x76, 0x3a, 0x7d, 0x77, 0x06, 0x9e, 0x77, 0x7f,
75
+0x5e, 0xeb, 0x32, 0x51, 0xf9, 0x16, 0x66, 0x9a, 0x09, 0xf3, 0xb0, 0x08, 0xa4, 0x70, 0x96,
76
+0x46, 0x30, 0xff, 0xda, 0x4f, 0xe9, 0x1b, 0xed, 0x8d, 0xf8, 0x74, 0x1f, 0x31, 0x92, 0xb3,
77
+0x73, 0x17, 0x36, 0xdb, 0x91, 0x30, 0xd6, 0x88, 0x55, 0x6b, 0x34, 0x77, 0x87, 0x7a, 0xe7,
78
+0xee, 0x06, 0xc6, 0x1c, 0x8c, 0x19, 0x0c, 0x48, 0x46, 0x23, 0x5e, 0x9c, 0x07, 0x5c, 0xbf,
79
+0xb4, 0x7e, 0xd6, 0x4f, 0x74, 0x9c, 0xe2, 0xc5, 0x50, 0x8b, 0xc5, 0x8b, 0x15, 0x90, 0x60,
80
+0x62, 0x57, 0x29, 0xd0, 0x13, 0x43, 0xa1, 0x80, 0x88, 0x91, 0x00, 0x44, 0xc7, 0x4d, 0x19,
81
+0x86, 0xcc, 0x2f, 0x2a, 0x75, 0x5a, 0xfc, 0xeb, 0x97, 0x2a, 0x70, 0xe3, 0x78, 0xd8, 0x91,
82
+0xb0, 0x4f, 0x99, 0x07, 0xa3, 0x95, 0xea, 0x24, 0x21, 0xd5, 0xde, 0x51, 0x20, 0x93, 0x27,
83
+0x0a, 0x30, 0x73, 0xa8, 0xff, 0x8a, 0x97, 0xe9, 0xa7, 0x6a, 0x8e, 0x0d, 0xe8, 0xf0, 0xdf,
84
+0xec, 0xea, 0xb4, 0x6c, 0x1d, 0x39, 0x2a, 0x62, 0x2d, 0x3d, 0x5a, 0x8b, 0x65, 0xf8, 0x90,
85
+0x05, 0x2e, 0x7e, 0x91, 0x2c, 0x78, 0xef, 0x8e, 0x7a, 0xc1, 0x2f, 0xac, 0x78, 0xee, 0xaf,
86
+0x28, 0x45, 0x06, 0x4c, 0x26, 0xaf, 0x3b, 0xa2, 0xdb, 0xa3, 0x93, 0x06, 0xb5, 0x3c, 0xa5,
87
+0xd8, 0xee, 0x8f, 0xaf, 0x25, 0xcc, 0x3f, 0x85, 0x68, 0x48, 0xa9, 0x62, 0xcc, 0x97, 0x8f,
88
+0x7f, 0x2a, 0xea, 0xe0, 0x15, 0x0a, 0xad, 0x62, 0x07, 0xbd, 0x45, 0xf8, 0x41, 0xd8, 0x36,
89
+0xcb, 0x4c, 0xdb, 0x6e, 0xe6, 0x3a, 0xe7, 0xda, 0x15, 0xe9, 0x29, 0x1e, 0x12, 0x10, 0xa0,
90
+0x14, 0x2c, 0x0e, 0x3d, 0xf4, 0xbf, 0x39, 0x41, 0x92, 0x75, 0x0b, 0x25, 0x7b, 0xa3, 0xce,
91
+0x39, 0x9c, 0x15, 0x64, 0xc8, 0xfa, 0x3d, 0xef, 0x73, 0x27, 0xfe, 0x26, 0x2e, 0xce, 0xda,
92
+0x6e, 0xfd, 0x71, 0x8e, 0xdd, 0xfe, 0x76, 0xee, 0xdc, 0x12, 0x5c, 0x02, 0xc5, 0x3a, 0x4e,
93
+0x4e, 0x4f, 0xbf, 0xca, 0x40, 0x15, 0xc7, 0x6e, 0x8d, 0x41, 0xf1, 0x10, 0xe0, 0x4f, 0x7e,
94
+0x97, 0x7f, 0x1c, 0xae, 0x47, 0x8e, 0x6b, 0xb1, 0x25, 0x31, 0xb0, 0x73, 0xc7, 0x1b, 0x97,
95
+0x79, 0xf9, 0x80, 0xd3, 0x66, 0x22, 0x30, 0x07, 0x74, 0x1e, 0xe4, 0xd0, 0x80, 0x21, 0xd6,
96
+0xee, 0x6b, 0x6c, 0x4f, 0xbf, 0xf5, 0xb7, 0xd9, 0x09, 0x87, 0x2f, 0xa9, 0x14, 0xbe, 0x27,
97
+0xd9, 0x72, 0x50, 0x01, 0xd4, 0x13, 0x73, 0xa6, 0xa7, 0x51, 0x02, 0x75, 0x25, 0xe1, 0xb3,
98
+0x45, 0x34, 0x7d, 0xa8, 0x8e, 0xeb, 0xf3, 0x16, 0x49, 0xcb, 0x4f, 0x8c, 0xa1, 0xb9, 0x36,
99
+0x85, 0x39, 0x75, 0x5d, 0x08, 0x00, 0xae, 0xeb, 0xf6, 0xea, 0xd7, 0x13, 0x3a, 0x21, 0x5a,
100
+0x5f, 0x30, 0x84, 0x52, 0x26, 0x95, 0xc9, 0x14, 0xf2, 0x57, 0x55, 0x6b, 0xb1, 0x10, 0xc2,
101
+0xe1, 0xbd, 0x3b, 0x51, 0xc0, 0xb7, 0x55, 0x4c, 0x71, 0x12, 0x26, 0xc7, 0x0d, 0xf9, 0x51,
102
+0xa4, 0x38, 0x02, 0x05, 0x7f, 0xb8, 0xf1, 0x72, 0x4b, 0xbf, 0x71, 0x89, 0x14, 0xf3, 0x77,
103
+0x38, 0xd9, 0x71, 0x24, 0xf3, 0x00, 0x11, 0xa1, 0xd8, 0xd4, 0x69, 0x27, 0x08, 0x37, 0x35,
104
+0xc9, 0x11, 0x9d, 0x90, 0x1c, 0x0e, 0xe7, 0x1c, 0xff, 0x2d, 0x1e, 0xe8, 0x92, 0xe1, 0x18,
105
+0x10, 0x95, 0x7c, 0xe0, 0x80, 0xf4, 0x96, 0x43, 0x21, 0xf9, 0x75, 0x21, 0x64, 0x38, 0xdd,
106
+0x9f, 0x1e, 0x95, 0x16, 0xda, 0x56, 0x1d, 0x4f, 0x9a, 0x53, 0xb2, 0xe2, 0xe4, 0x18, 0xcb,
107
+0x6b, 0x1a, 0x65, 0xeb, 0x56, 0xc6, 0x3b, 0xe5, 0xfe, 0xd8, 0x26, 0x3f, 0x3a, 0x84, 0x59,
108
+0x72, 0x66, 0xa2, 0xf3, 0x75, 0xff, 0xfb, 0x60, 0xb3, 0x22, 0xad, 0x3f, 0x2d, 0x6b, 0xf9,
109
+0xeb, 0xea, 0x05, 0x7c, 0xd8, 0x8f, 0x6d, 0x2c, 0x98, 0x9e, 0x2b, 0x93, 0xf1, 0x5e, 0x46,
110
+0xf0, 0x87, 0x49, 0x29, 0x73, 0x68, 0xd7, 0x7f, 0xf9, 0xf0, 0xe5, 0x7d, 0xdb, 0x1d, 0x75,
111
+0x19, 0xf3, 0xc4, 0x58, 0x9b, 0x17, 0x88, 0xa8, 0x92, 0xe0, 0xbe, 0xbd, 0x8b, 0x1d, 0x8d,
112
+0x9f, 0x56, 0x76, 0xad, 0xaf, 0x29, 0xe2, 0xd9, 0xd5, 0x52, 0xf6, 0xb5, 0x56, 0x35, 0x57,
113
+0x3a, 0xc8, 0xe1, 0x56, 0x43, 0x19, 0x94, 0xd3, 0x04, 0x9b, 0x6d, 0x35, 0xd8, 0x0b, 0x5f,
114
+0x4d, 0x19, 0x8e, 0xec, 0xfa, 0x64, 0x91, 0x0a, 0x72, 0x20, 0x2b, 0xbc, 0x1a, 0x4a, 0xfe,
115
+0x8b, 0xfd, 0xbb, 0xed, 0x1b, 0x23, 0xea, 0xad, 0x72, 0x82, 0xa1, 0x29, 0x99, 0x71, 0xbd,
116
+0xf0, 0x95, 0xc1, 0x03, 0xdd, 0x7b, 0xc2, 0xb2, 0x3c, 0x28, 0x54, 0xd3, 0x68, 0xa4, 0x72,
117
+0xc8, 0x66, 0x96, 0xe0, 0xd1, 0xd8, 0x7f, 0xf8, 0xd1, 0x26, 0x2b, 0xf7, 0xad, 0xba, 0x55,
118
+0xca, 0x15, 0xb9, 0x32, 0xc3, 0xe5, 0x88, 0x97, 0x8e, 0x5c, 0xfb, 0x92, 0x25, 0x8b, 0xbf,
119
+0xa2, 0x45, 0x55, 0x7a, 0xa7, 0x6f, 0x8b, 0x57, 0x5b, 0xcf, 0x0e, 0xcb, 0x1d, 0xfb, 0x20,
120
+0x82, 0x77, 0xa8, 0x8c, 0xcc, 0x16, 0xce, 0x1d, 0xfa, 0xde, 0xcc, 0x0b, 0x62, 0xfe, 0xcc,
121
+0xe1, 0xb7, 0xf0, 0xc3, 0x81, 0x64, 0x73, 0x40, 0xa0, 0xc2, 0x4d, 0x89, 0x11, 0x75, 0x33,
122
+0x55, 0x33, 0x8d, 0xe8, 0x4a, 0xfd, 0xea, 0x6e, 0x30, 0x0b, 0xd7, 0x31, 0x2c, 0xde, 0x47,
123
+0xe3, 0xbf, 0xf8, 0x55, 0x42, 0xe2, 0x7f, 0x59, 0xe5, 0x17, 0xef, 0x99, 0x34, 0x69, 0x91,
124
+0xb1, 0x23, 0x8e, 0x20, 0x87, 0x2d, 0xa8, 0xfe, 0xd5, 0x8a, 0xf3, 0x84, 0x3a, 0xf0, 0x37,
125
+0xe4, 0x09, 0x00, 0x54, 0xee, 0x67, 0x49, 0x93, 0xe4, 0x81, 0x70, 0xe3, 0x90, 0x4d, 0xef,
126
+0xfe, 0x41, 0xb7, 0x99, 0x7b, 0xc1, 0x83, 0xba, 0x62, 0x12, 0x6f, 0x7d, 0xde, 0x6b, 0xaf,
127
+0xda, 0x16, 0xf9, 0x55, 0x51, 0xee, 0xa6, 0x0c, 0x2b, 0x02, 0xa3, 0xfd, 0x8d, 0xfb, 0x30,
128
+0x17, 0xe4, 0x6f, 0xdf, 0x36, 0x71, 0xc4, 0xca, 0x87, 0x25, 0x48, 0xb0, 0x47, 0xec, 0xea,
129
+0xb4, 0xbf, 0xa5, 0x4d, 0x9b, 0x9f, 0x02, 0x93, 0xc4, 0xe3, 0xe4, 0xe8, 0x42, 0x2d, 0x68,
130
+0x81, 0x15, 0x0a, 0xeb, 0x84, 0x5b, 0xd6, 0xa8, 0x74, 0xfb, 0x7d, 0x1d, 0xcb, 0x2c, 0xda,
131
+0x46, 0x2a, 0x76, 0x62, 0xce, 0xbc, 0x5c, 0x9e, 0x8b, 0xe7, 0xcf, 0xbe, 0x78, 0xf5, 0x7c,
132
+0xeb, 0xb3, 0x3a, 0x9c, 0xaa, 0x6f, 0xcc, 0x72, 0xd1, 0x59, 0xf2, 0x11, 0x23, 0xd6, 0x3f,
133
+0x48, 0xd1, 0xb7, 0xce, 0xb0, 0xbf, 0xcb, 0xea, 0x80, 0xde, 0x57, 0xd4, 0x5e, 0x97, 0x2f,
134
+0x75, 0xd1, 0x50, 0x8e, 0x80, 0x2c, 0x66, 0x79, 0xbf, 0x72, 0x4b, 0xbd, 0x8a, 0x81, 0x6c,
135
+0xd3, 0xe1, 0x01, 0xdc, 0xd2, 0x15, 0x26, 0xc5, 0x36, 0xda, 0x2c, 0x1a, 0xc0, 0x27, 0x94,
136
+0xed, 0xb7, 0x9b, 0x85, 0x0b, 0x5e, 0x80, 0x97, 0xc5, 0xec, 0x4f, 0xec, 0x88, 0x5d, 0x50,
137
+0x07, 0x35, 0x47, 0xdc, 0x0b, 0x3b, 0x3d, 0xdd, 0x60, 0xaf, 0xa8, 0x5d, 0x81, 0x38, 0x24,
138
+0x25, 0x5d, 0x5c, 0x15, 0xd1, 0xde, 0xb3, 0xab, 0xec, 0x05, 0x69, 0xef, 0x83, 0xed, 0x57,
139
+0x54, 0xb8, 0x64, 0x64, 0x11, 0x16, 0x32, 0x69, 0xda, 0x9f, 0x2d, 0x7f, 0x36, 0xbb, 0x44,
140
+0x5a, 0x34, 0xe8, 0x7f, 0xbf, 0x03, 0xeb, 0x00, 0x7f, 0x59, 0x68, 0x22, 0x79, 0xcf, 0x73,
141
+0x6c, 0x2c, 0x29, 0xa7, 0xa1, 0x5f, 0x38, 0xa1, 0x1d, 0xf0, 0x20, 0x53, 0xe0, 0x1a, 0x63,
142
+0x14, 0x58, 0x71, 0x10, 0xaa, 0x08, 0x0c, 0x3e, 0x16, 0x1a, 0x60, 0x22, 0x82, 0x7f, 0xba,
143
+0xa4, 0x43, 0xa0, 0xd0, 0xac, 0x1b, 0xd5, 0x6b, 0x64, 0xb5, 0x14, 0x93, 0x31, 0x9e, 0x53,
144
+0x50, 0xd0, 0x57, 0x66, 0xee, 0x5a, 0x4f, 0xfb, 0x03, 0x2a, 0x69, 0x58, 0x76, 0xf1, 0x83,
145
+0xf7, 0x4e, 0xba, 0x8c, 0x42, 0x06, 0x60, 0x5d, 0x6d, 0xce, 0x60, 0x88, 0xae, 0xa4, 0xc3,
146
+0xf1, 0x03, 0xa5, 0x4b, 0x98, 0xa1, 0xff, 0x67, 0xe1, 0xac, 0xa2, 0xb8, 0x62, 0xd7, 0x6f,
147
+0xa0, 0x31, 0xb4, 0xd2, 0x77, 0xaf, 0x21, 0x10, 0x06, 0xc6, 0x9a, 0xff, 0x1d, 0x09, 0x17,
148
+0x0e, 0x5f, 0xf1, 0xaa, 0x54, 0x34, 0x4b, 0x45, 0x8a, 0x87, 0x63, 0xa6, 0xdc, 0xf9, 0x24,
149
+0x30, 0x67, 0xc6, 0xb2, 0xd6, 0x61, 0x33, 0x69, 0xee, 0x50, 0x61, 0x57, 0x28, 0xe7, 0x7e,
150
+0xee, 0xec, 0x3a, 0x5a, 0x73, 0x4e, 0xa8, 0x8d, 0xe4, 0x18, 0xea, 0xec, 0x41, 0x64, 0xc8,
151
+0xe2, 0xe8, 0x66, 0xb6, 0x2d, 0xb6, 0xfb, 0x6a, 0x6c, 0x16, 0xb3, 0xdd, 0x46, 0x43, 0xb9,
152
+0x73, 0x00, 0x6a, 0x71, 0xed, 0x4e, 0x9d, 0x25, 0x1a, 0xc3, 0x3c, 0x4a, 0x95, 0x15, 0x99,
153
+0x35, 0x81, 0x14, 0x02, 0xd6, 0x98, 0x9b, 0xec, 0xd8, 0x23, 0x3b, 0x84, 0x29, 0xaf, 0x0c,
154
+0x99, 0x83, 0xa6, 0x9a, 0x34, 0x4f, 0xfa, 0xe8, 0xd0, 0x3c, 0x4b, 0xd0, 0xfb, 0xb6, 0x68,
155
+0xb8, 0x9e, 0x8f, 0xcd, 0xf7, 0x60, 0x2d, 0x7a, 0x22, 0xe5, 0x7d, 0xab, 0x65, 0x1b, 0x95,
156
+0xa7, 0xa8, 0x7f, 0xb6, 0x77, 0x47, 0x7b, 0x5f, 0x8b, 0x12, 0x72, 0xd0, 0xd4, 0x91, 0xef,
157
+0xde, 0x19, 0x50, 0x3c, 0xa7, 0x8b, 0xc4, 0xa9, 0xb3, 0x23, 0xcb, 0x76, 0xe6, 0x81, 0xf0,
158
+0xc1, 0x04, 0x8f, 0xa3, 0xb8, 0x54, 0x5b, 0x97, 0xac, 0x19, 0xff, 0x3f, 0x55, 0x27, 0x2f,
159
+0xe0, 0x1d, 0x42, 0x9b, 0x57, 0xfc, 0x4b, 0x4e, 0x0f, 0xce, 0x98, 0xa9, 0x43, 0x57, 0x03,
160
+0xbd, 0xe7, 0xc8, 0x94, 0xdf, 0x6e, 0x36, 0x73, 0x32, 0xb4, 0xef, 0x2e, 0x85, 0x7a, 0x6e,
161
+0xfc, 0x6c, 0x18, 0x82, 0x75, 0x35, 0x90, 0x07, 0xf3, 0xe4, 0x9f, 0x3e, 0xdc, 0x68, 0xf3,
162
+0xb5, 0xf3, 0x19, 0x80, 0x92, 0x06, 0x99, 0xa2, 0xe8, 0x6f, 0xff, 0x2e, 0x7f, 0xae, 0x42,
163
+0xa4, 0x5f, 0xfb, 0xd4, 0x0e, 0x81, 0x2b, 0xc3, 0x04, 0xff, 0x2b, 0xb3, 0x74, 0x4e, 0x36,
164
+0x5b, 0x9c, 0x15, 0x00, 0xc6, 0x47, 0x2b, 0xe8, 0x8b, 0x3d, 0xf1, 0x9c, 0x03, 0x9a, 0x58,
165
+0x7f, 0x9b, 0x9c, 0xbf, 0x85, 0x49, 0x79, 0x35, 0x2e, 0x56, 0x7b, 0x41, 0x14, 0x39, 0x47,
166
+0x83, 0x26, 0xaa, 0x07, 0x89, 0x98, 0x11, 0x1b, 0x86, 0xe7, 0x73, 0x7a, 0xd8, 0x7d, 0x78,
167
+0x61, 0x53, 0xe9, 0x79, 0xf5, 0x36, 0x8d, 0x44, 0x92, 0x84, 0xf9, 0x13, 0x50, 0x58, 0x3b,
168
+0xa4, 0x6a, 0x36, 0x65, 0x49, 0x8e, 0x3c, 0x0e, 0xf1, 0x6f, 0xd2, 0x84, 0xc4, 0x7e, 0x8e,
169
+0x3f, 0x39, 0xae, 0x7c, 0x84, 0xf1, 0x63, 0x37, 0x8e, 0x3c, 0xcc, 0x3e, 0x44, 0x81, 0x45,
170
+0xf1, 0x4b, 0xb9, 0xed, 0x6b, 0x36, 0x5d, 0xbb, 0x20, 0x60, 0x1a, 0x0f, 0xa3, 0xaa, 0x55,
171
+0x77, 0x3a, 0xa9, 0xae, 0x37, 0x4d, 0xba, 0xb8, 0x86, 0x6b, 0xbc, 0x08, 0x50, 0xf6, 0xcc,
172
+0xa4, 0xbd, 0x1d, 0x40, 0x72, 0xa5, 0x86, 0xfa, 0xe2, 0x10, 0xae, 0x3d, 0x58, 0x4b, 0x97,
173
+0xf3, 0x43, 0x74, 0xa9, 0x9e, 0xeb, 0x21, 0xb7, 0x01, 0xa4, 0x86, 0x93, 0x97, 0xee, 0x2f,
174
+0x4f, 0x3b, 0x86, 0xa1, 0x41, 0x6f, 0x41, 0x26, 0x90, 0x78, 0x5c, 0x7f, 0x30, 0x38, 0x4b,
175
+0x3f, 0xaa, 0xec, 0xed, 0x5c, 0x6f, 0x0e, 0xad, 0x43, 0x87, 0xfd, 0x93, 0x35, 0xe6, 0x01,
176
+0xef, 0x41, 0x26, 0x90, 0x99, 0x9e, 0xfb, 0x19, 0x5b, 0xad, 0xd2, 0x91, 0x8a, 0xe0, 0x46,
177
+0xaf, 0x65, 0xfa, 0x4f, 0x84, 0xc1, 0xa1, 0x2d, 0xcf, 0x45, 0x8b, 0xd3, 0x85, 0x50, 0x55,
178
+0x7c, 0xf9, 0x67, 0x88, 0xd4, 0x4e, 0xe9, 0xd7, 0x6b, 0x61, 0x54, 0xa1, 0xa4, 0xa6, 0xa2,
179
+0xc2, 0xbf, 0x30, 0x9c, 0x40, 0x9f, 0x5f, 0xd7, 0x69, 0x2b, 0x24, 0x82, 0x5e, 0xd9, 0xd6,
180
+0xa7, 0x12, 0x54, 0x1a, 0xf7, 0x55, 0x9f, 0x76, 0x50, 0xa9, 0x95, 0x84, 0xe6, 0x6b, 0x6d,
181
+0xb5, 0x96, 0x54, 0xd6, 0xcd, 0xb3, 0xa1, 0x9b, 0x46, 0xa7, 0x94, 0x4d, 0xc4, 0x94, 0xb4,
182
+0x98, 0xe3, 0xe1, 0xe2, 0x34, 0xd5, 0x33, 0x16, 0x07, 0x54, 0xcd, 0xb7, 0x77, 0x53, 0xdb,
183
+0x4f, 0x4d, 0x46, 0x9d, 0xe9, 0xd4, 0x9c, 0x8a, 0x36, 0xb6, 0xb8, 0x38, 0x26, 0x6c, 0x0e,
184
+0xff, 0x9c, 0x1b, 0x43, 0x8b, 0x80, 0xcc, 0xb9, 0x3d, 0xda, 0xc7, 0xf1, 0x8a, 0xf2, 0x6d,
185
+0xb8, 0xd7, 0x74, 0x2f, 0x7e, 0x1e, 0xb7, 0xd3, 0x4a, 0xb4, 0xac, 0xfc, 0x79, 0x48, 0x6c,
186
+0xbc, 0x96, 0xb6, 0x94, 0x46, 0x57, 0x2d, 0xb0, 0xa3, 0xfc, 0x1e, 0xb9, 0x52, 0x60, 0x85,
187
+0x2d, 0x41, 0xd0, 0x43, 0x01, 0x1e, 0x1c, 0xd5, 0x7d, 0xfc, 0xf3, 0x96, 0x0d, 0xc7, 0xcb,
188
+0x2a, 0x29, 0x9a, 0x93, 0xdd, 0x88, 0x2d, 0x37, 0x5d, 0xaa, 0xfb, 0x49, 0x68, 0xa0, 0x9c,
189
+0x50, 0x86, 0x7f, 0x68, 0x56, 0x57, 0xf9, 0x79, 0x18, 0x39, 0xd4, 0xe0, 0x01, 0x84, 0x33,
190
+0x61, 0xca, 0xa5, 0xd2, 0xd6, 0xe4, 0xc9, 0x8a, 0x4a, 0x23, 0x44, 0x4e, 0xbc, 0xf0, 0xdc,
191
+0x24, 0xa1, 0xa0, 0xc4, 0xe2, 0x07, 0x3c, 0x10, 0xc4, 0xb5, 0x25, 0x4b, 0x65, 0x63, 0xf4,
192
+0x80, 0xe7, 0xcf, 0x61, 0xb1, 0x71, 0x82, 0x21, 0x87, 0x2c, 0xf5, 0x91, 0x00, 0x32, 0x0c,
193
+0xec, 0xa9, 0xb5, 0x9a, 0x74, 0x85, 0xe3, 0x36, 0x8f, 0x76, 0x4f, 0x9c, 0x6d, 0xce, 0xbc,
194
+0xad, 0x0a, 0x4b, 0xed, 0x76, 0x04, 0xcb, 0xc3, 0xb9, 0x33, 0x9e, 0x01, 0x93, 0x96, 0x69,
195
+0x7d, 0xc5, 0xa2, 0x45, 0x79, 0x9b, 0x04, 0x5c, 0x84, 0x09, 0xed, 0x88, 0x43, 0xc7, 0xab,
196
+0x93, 0x14, 0x26, 0xa1, 0x40, 0xb5, 0xce, 0x4e, 0xbf, 0x2a, 0x42, 0x85, 0x3e, 0x2c, 0x3b,
197
+0x54, 0xe8, 0x12, 0x1f, 0x0e, 0x97, 0x59, 0xb2, 0x27, 0x89, 0xfa, 0xf2, 0xdf, 0x8e, 0x68,
198
+0x59, 0xdc, 0x06, 0xbc, 0xb6, 0x85, 0x0d, 0x06, 0x22, 0xec, 0xb1, 0xcb, 0xe5, 0x04, 0xe6,
199
+0x3d, 0xb3, 0xb0, 0x41, 0x73, 0x08, 0x3f, 0x3c, 0x58, 0x86, 0x63, 0xeb, 0x50, 0xee, 0x1d,
200
+0x2c, 0x37, 0x74, 0xa9, 0xd3, 0x18, 0xa3, 0x47, 0x6e, 0x93, 0x54, 0xad, 0x0a, 0x5d, 0xb8,
201
+0x2a, 0x55, 0x5d, 0x78, 0xf6, 0xee, 0xbe, 0x8e, 0x3c, 0x76, 0x69, 0xb9, 0x40, 0xc2, 0x34,
202
+0xec, 0x2a, 0xb9, 0xed, 0x7e, 0x20, 0xe4, 0x8d, 0x00, 0x38, 0xc7, 0xe6, 0x8f, 0x44, 0xa8,
203
+0x86, 0xce, 0xeb, 0x2a, 0xe9, 0x90, 0xf1, 0x4c, 0xdf, 0x32, 0xfb, 0x73, 0x1b, 0x6d, 0x92,
204
+0x1e, 0x95, 0xfe, 0xb4, 0xdb, 0x65, 0xdf, 0x4d, 0x23, 0x54, 0x89, 0x48, 0xbf, 0x4a, 0x2e,
205
+0x70, 0xd6, 0xd7, 0x62, 0xb4, 0x33, 0x29, 0xb1, 0x3a, 0x33, 0x4c, 0x23, 0x6d, 0xa6, 0x76,
206
+0xa5, 0x21, 0x63, 0x48, 0xe6, 0x90, 0x5d, 0xed, 0x90, 0x95, 0x0b, 0x7a, 0x84, 0xbe, 0xb8,
207
+0x0d, 0x5e, 0x63, 0x0c, 0x62, 0x26, 0x4c, 0x14, 0x5a, 0xb3, 0xac, 0x23, 0xa4, 0x74, 0xa7,
208
+0x6f, 0x33, 0x30, 0x05, 0x60, 0x01, 0x42, 0xa0, 0x28, 0xb7, 0xee, 0x19, 0x38, 0xf1, 0x64,
209
+0x80, 0x82, 0x43, 0xe1, 0x41, 0x27, 0x1f, 0x1f, 0x90, 0x54, 0x7a, 0xd5, 0x23, 0x2e, 0xd1,
210
+0x3d, 0xcb, 0x28, 0xba, 0x58, 0x7f, 0xdc, 0x7c, 0x91, 0x24, 0xe9, 0x28, 0x51, 0x83, 0x6e,
211
+0xc5, 0x56, 0x21, 0x42, 0xed, 0xa0, 0x56, 0x22, 0xa1, 0x40, 0x80, 0x6b, 0xa8, 0xf7, 0x94,
212
+0xca, 0x13, 0x6b, 0x0c, 0x39, 0xd9, 0xfd, 0xe9, 0xf3, 0x6f, 0xa6, 0x9e, 0xfc, 0x70, 0x8a,
213
+0xb3, 0xbc, 0x59, 0x3c, 0x1e, 0x1d, 0x6c, 0xf9, 0x7c, 0xaf, 0xf9, 0x88, 0x71, 0x95, 0xeb,
214
+0x57, 0x00, 0xbd, 0x9f, 0x8c, 0x4f, 0xe1, 0x24, 0x83, 0xc5, 0x22, 0xea, 0xfd, 0xd3, 0x0c,
215
+0xe2, 0x17, 0x18, 0x7c, 0x6a, 0x4c, 0xde, 0x77, 0xb4, 0x53, 0x9b, 0x4c, 0x81, 0xcd, 0x23,
216
+0x60, 0xaa, 0x0e, 0x25, 0x73, 0x9c, 0x02, 0x79, 0x32, 0x30, 0xdf, 0x74, 0xdf, 0x75, 0x19,
217
+0xf4, 0xa5, 0x14, 0x5c, 0xf7, 0x7a, 0xa8, 0xa5, 0x91, 0x84, 0x7c, 0x60, 0x03, 0x06, 0x3b,
218
+0xcd, 0x50, 0xb6, 0x27, 0x9c, 0xfe, 0xb1, 0xdd, 0xcc, 0xd3, 0xb0, 0x59, 0x24, 0xb2, 0xca,
219
+0xe2, 0x1c, 0x81, 0x22, 0x9d, 0x07, 0x8f, 0x8e, 0xb9, 0xbe, 0x4e, 0xfa, 0xfc, 0x39, 0x65,
220
+0xba, 0xbf, 0x9d, 0x12, 0x37, 0x5e, 0x97, 0x7e, 0xf3, 0x89, 0xf5, 0x5d, 0xf5, 0xe3, 0x09,
221
+0x8c, 0x62, 0xb5, 0x20, 0x9d, 0x0c, 0x53, 0x8a, 0x68, 0x1b, 0xd2, 0x8f, 0x75, 0x17, 0x5d,
222
+0xd4, 0xe5, 0xda, 0x75, 0x62, 0x19, 0x14, 0x6a, 0x26, 0x2d, 0xeb, 0xf8, 0xaf, 0x37, 0xf0,
223
+0x6c, 0xa4, 0x55, 0xb1, 0xbc, 0xe2, 0x33, 0xc0, 0x9a, 0xca, 0xb0, 0x11, 0x49, 0x4f, 0x68,
224
+0x9b, 0x3b, 0x6b, 0x3c, 0xcc, 0x13, 0xf6, 0xc7, 0x85, 0x61, 0x68, 0x42, 0xae, 0xbb, 0xdd,
225
+0xcd, 0x45, 0x16, 0x29, 0x1d, 0xea, 0xdb, 0xc8, 0x03, 0x94, 0x3c, 0xee, 0x4f, 0x82, 0x11,
226
+0xc3, 0xec, 0x28, 0xbd, 0x97, 0x05, 0x99, 0xde, 0xd7, 0xbb, 0x5e, 0x22, 0x1f, 0xd4, 0xeb,
227
+0x64, 0xd9, 0x92, 0xd9, 0x85, 0xb7, 0x6a, 0x05, 0x6a, 0xe4, 0x24, 0x41, 0xf1, 0xcd, 0xf0,
228
+0xd8, 0x3f, 0xf8, 0x9e, 0x0e, 0xcd, 0x0b, 0x7a, 0x70, 0x6b, 0x5a, 0x75, 0x0a, 0x6a, 0x33,
229
+0x88, 0xec, 0x17, 0x75, 0x08, 0x70, 0x10, 0x2f, 0x24, 0xcf, 0xc4, 0xe9, 0x42, 0x00, 0x61,
230
+0x94, 0xca, 0x1f, 0x3a, 0x76, 0x06, 0xfa, 0xd2, 0x48, 0x81, 0xf0, 0x77, 0x60, 0x03, 0x45,
231
+0xd9, 0x61, 0xf4, 0xa4, 0x6f, 0x3d, 0xd9, 0x30, 0xc3, 0x04, 0x6b, 0x54, 0x2a, 0xb7, 0xec,
232
+0x3b, 0xf4, 0x4b, 0xf5, 0x68, 0x52, 0x26, 0xce, 0xff, 0x5d, 0x19, 0x91, 0xa0, 0xa3, 0xa5,
233
+0xa9, 0xb1, 0xe0, 0x23, 0xc4, 0x0a, 0x77, 0x4d, 0xf9, 0x51, 0x20, 0xa3, 0xa5, 0xa9, 0xb1,
234
+0xc1, 0x00, 0x82, 0x86, 0x8e, 0x7f, 0x5d, 0x19, 0x91, 0xa0, 0xa3, 0xc4, 0xeb, 0x54, 0x0b,
235
+0x75, 0x68, 0x52, 0x07, 0x8c, 0x9a, 0x97, 0x8d, 0x79, 0x70, 0x62, 0x46, 0xef, 0x5c, 0x1b,
236
+0x95, 0x89, 0x71, 0x41, 0xe1, 0x21, 0xa1, 0xa1, 0xa1, 0xc0, 0x02, 0x67, 0x4c, 0x1a, 0xb6,
237
+0xcf, 0xfd, 0x78, 0x53, 0x24, 0xab, 0xb5, 0xc9, 0xf1, 0x60, 0x23, 0xa5, 0xc8, 0x12, 0x87,
238
+0x6d, 0x58, 0x13, 0x85, 0x88, 0x92, 0x87, 0x6d, 0x58, 0x32, 0xc7, 0x0c, 0x9a, 0x97, 0xac,
239
+0xda, 0x36, 0xee, 0x5e, 0x3e, 0xdf, 0x1d, 0xb8, 0xf2, 0x66, 0x2f, 0xbd, 0xf8, 0x72, 0x47,
240
+0xed, 0x58, 0x13, 0x85, 0x88, 0x92, 0x87, 0x8c, 0x7b, 0x55, 0x09, 0x90, 0xa2, 0xc6, 0xef,
241
+0x3d, 0xf8, 0x53, 0x24, 0xab, 0xd4, 0x2a, 0xb7, 0xec, 0x5a, 0x36, 0xee, 0x5e, 0x3e, 0xdf,
242
+0x3c, 0xfa, 0x76, 0x4f, 0xfd, 0x59, 0x30, 0xe2, 0x46, 0xef, 0x3d, 0xf8, 0x53, 0x05, 0x69,
243
+0x31, 0xc1, 0x00, 0x82, 0x86, 0x8e, 0x7f, 0x5d, 0x19, 0xb0, 0xe2, 0x27, 0xcc, 0xfb, 0x74,
244
+0x4b, 0x14, 0x8b, 0x94, 0x8b, 0x75, 0x68, 0x33, 0xc5, 0x08, 0x92, 0x87, 0x8c, 0x9a, 0xb6,
245
+0xcf, 0x1c, 0xba, 0xd7, 0x0d, 0x98, 0xb2, 0xe6, 0x2f, 0xdc, 0x1b, 0x95, 0x89, 0x71, 0x60,
246
+0x23, 0xc4, 0x0a, 0x96, 0x8f, 0x9c, 0xba, 0xf6, 0x6e, 0x3f, 0xfc, 0x5b, 0x15, 0xa8, 0xd2,
247
+0x26, 0xaf, 0xbd, 0xf8, 0x72, 0x66, 0x2f, 0xdc, 0x1b, 0xb4, 0xcb, 0x14, 0x8b, 0x94, 0xaa,
248
+0xb7, 0xcd, 0xf9, 0x51, 0x01, 0x80, 0x82, 0x86, 0x6f, 0x3d, 0xd9, 0x30, 0xe2, 0x27, 0xcc,
249
+0xfb, 0x74, 0x4b, 0x14, 0xaa, 0xb7, 0xcd, 0xf9, 0x70, 0x43, 0x04, 0x6b, 0x35, 0xc9, 0xf1,
250
+0x60, 0x23, 0xa5, 0xc8, 0xf3, 0x45, 0x08, 0x92, 0x87, 0x6d, 0x58, 0x32, 0xe6, 0x2f, 0xbd,
251
+0xf8, 0x72, 0x66, 0x4e, 0x1e, 0xbe, 0xfe, 0x7e, 0x7e, 0x7e, 0x5f, 0x1d, 0x99, 0x91, 0xa0,
252
+0xa3, 0xc4, 0x0a, 0x77, 0x4d, 0x18, 0x93, 0xa4, 0xab, 0xd4, 0x0b, 0x75, 0x49, 0x10, 0xa2,
253
+0xc6, 0xef, 0x3d, 0xf8, 0x53, 0x24, 0xab, 0xb5, 0xe8, 0x33, 0xe4, 0x4a, 0x16, 0xae, 0xde,
254
+0x1f, 0xbc, 0xdb, 0x15, 0xa8, 0xb3, 0xc5, 0x08, 0x73, 0x45, 0xe9, 0x31, 0xc1, 0xe1, 0x21,
255
+0xa1, 0xa1, 0xa1, 0xc0, 0x02, 0x86, 0x6f, 0x5c, 0x3a, 0xd7, 0x0d, 0x98, 0x93, 0xa4, 0xca,
256
+0x16, 0xae, 0xde, 0x1f, 0x9d, 0x99, 0xb0, 0xe2, 0x46, 0xef, 0x3d, 0xf8, 0x72, 0x47, 0x0c,
257
+0x9a, 0xb6, 0xcf, 0xfd, 0x59, 0x11, 0xa0, 0xa3, 0xa5, 0xc8, 0xf3, 0x45, 0x08, 0x92, 0x87,
258
+0x6d, 0x39, 0xf0, 0x43, 0x04, 0x8a, 0x96, 0xae, 0xde, 0x3e, 0xdf, 0x1d, 0x99, 0x91, 0xa0,
259
+0xc2, 0x06, 0x6f, 0x3d, 0xf8, 0x72, 0x47, 0x0c, 0x9a, 0x97, 0x8d, 0x98, 0x93, 0x85, 0x88,
260
+0x73, 0x45, 0xe9, 0x31, 0xe0, 0x23, 0xa5, 0xa9, 0xd0, 0x03, 0x84, 0x8a, 0x96, 0xae, 0xde,
261
+0x1f, 0xbc, 0xdb, 0x15, 0xa8, 0xd2, 0x26, 0xce, 0xff, 0x5d, 0x19, 0x91, 0x81, 0x80, 0x82,
262
+0x67, 0x2d, 0xd8, 0x13, 0xa4, 0xab, 0xd4, 0x0b, 0x94, 0xaa, 0xb7, 0xcd, 0xf9, 0x51, 0x20,
263
+0xa3, 0xa5, 0xc8, 0xf3, 0x45, 0xe9, 0x50, 0x22, 0xc6, 0xef, 0x5c, 0x3a, 0xd7, 0x0d, 0x98,
264
+0x93, 0x85, 0x88, 0x73, 0x64, 0x4a, 0xf7, 0x4d, 0xf9, 0x51, 0x20, 0xa3, 0xc4, 0x0a, 0x96,
265
+0xae, 0xde, 0x3e, 0xfe, 0x7e, 0x7e, 0x7e, 0x5f, 0x3c, 0xfa, 0x76, 0x4f, 0xfd, 0x78, 0x72,
266
+0x66, 0x2f, 0xbd, 0xd9, 0x30, 0xc3, 0xe5, 0x48, 0x12, 0x87, 0x8c, 0x7b, 0x55, 0x28, 0xd2,
267
+0x07, 0x8c, 0x9a, 0x97, 0xac, 0xda, 0x17, 0x8d, 0x79, 0x51, 0x20, 0xa3, 0xc4, 0xeb, 0x54,
268
+0x0b, 0x94, 0x8b, 0x94, 0xaa, 0xd6, 0x2e, 0xbf, 0xfc, 0x5b, 0x15, 0xa8, 0xd2, 0x26, 0xaf,
269
+0xdc, 0x1b, 0xb4, 0xea, 0x37, 0xec, 0x3b, 0xf4, 0x6a, 0x37, 0xcd, 0x18, 0x93, 0x85, 0x69,
270
+0x31, 0xc1, 0xe1, 0x40, 0xe3, 0x25, 0xc8, 0x12, 0x87, 0x8c, 0x9a, 0xb6, 0xcf, 0xfd, 0x59,
271
+0x11, 0xa0, 0xc2, 0x06, 0x8e, 0x7f, 0x5d, 0x38, 0xf2, 0x47, 0x0c, 0x7b, 0x74, 0x6a, 0x37,
272
+0xec, 0x5a, 0x36, 0xee, 0x3f, 0xfc, 0x7a, 0x76, 0x4f, 0x1c, 0x9b, 0x95, 0x89, 0x71, 0x41,
273
+0x00, 0x63, 0x44, 0xeb, 0x54, 0x2a, 0xd6, 0x0f, 0x9c, 0xba, 0xd7, 0x0d, 0x98, 0x93, 0x85,
274
+0x69, 0x31, 0xc1, 0x00, 0x82, 0x86, 0x8e, 0x9e, 0xbe, 0xdf, 0x3c, 0xfa, 0x57, 0x2c, 0xda,
275
+0x36, 0xee, 0x3f, 0xfc, 0x5b, 0x15, 0x89, 0x71, 0x41, 0x00, 0x82, 0x86, 0x8e, 0x7f, 0x5d,
276
+0x38, 0xf2, 0x47, 0xed, 0x58, 0x13, 0xa4, 0xca, 0xf7, 0x4d, 0xf9, 0x51, 0x01, 0x80, 0x63,
277
+0x44, 0xeb, 0x54, 0x2a, 0xd6, 0x2e, 0xbf, 0xdd, 0x19, 0x91, 0xa0, 0xa3, 0xa5, 0xa9, 0xb1,
278
+0xe0, 0x42, 0x06, 0x8e, 0x7f, 0x5d, 0x19, 0x91, 0xa0, 0xa3, 0xc4, 0x0a, 0x96, 0x8f, 0x7d,
279
+0x78, 0x72, 0x47, 0x0c, 0x7b, 0x74, 0x6a, 0x56, 0x2e, 0xde, 0x1f, 0xbc, 0xfa, 0x57, 0x0d,
280
+0x79, 0x51, 0x01, 0x61, 0x21, 0xa1, 0xc0, 0xe3, 0x25, 0xa9, 0xb1, 0xc1, 0xe1, 0x40, 0x02,
281
+0x67, 0x4c, 0x1a, 0x97, 0x8d, 0x98, 0x93, 0xa4, 0xab, 0xd4, 0x2a, 0xd6, 0x0f, 0x9c, 0x9b,
282
+0xb4, 0xcb, 0x14, 0xaa, 0xb7, 0xcd, 0xf9, 0x51, 0x20, 0xa3, 0xc4, 0xeb, 0x35, 0xc9, 0xf1,
283
+0x60, 0x42, 0x06, 0x8e, 0x7f, 0x7c, 0x7a, 0x76, 0x6e, 0x3f, 0xfc, 0x7a, 0x76, 0x6e, 0x5e,
284
+0x3e, 0xfe, 0x7e, 0x5f, 0x3c, 0xdb, 0x15, 0x89, 0x71, 0x41, 0xe1, 0x21, 0xc0, 0xe3, 0x44,
285
+0xeb, 0x54, 0x2a, 0xb7, 0xcd, 0xf9, 0x70, 0x62, 0x27, 0xad, 0xd8, 0x32, 0xc7, 0x0c, 0x7b,
286
+0x74, 0x4b, 0x14, 0xaa, 0xb7, 0xec, 0x3b, 0xd5, 0x28, 0xd2, 0x07, 0x6d, 0x39, 0xd1, 0x20,
287
+0xc2, 0xe7, 0x4c, 0x1a, 0x97, 0x8d, 0x98, 0xb2, 0xc7, 0x0c, 0x59, 0x28, 0xf3, 0x9b
288
+};
289
+
290
+#endif // __PMW3360_SROM_H__

+ 21
- 0
firmware/include/usb.h View File

@@ -0,0 +1,21 @@
1
+/*
2
+ * usb.h
3
+ */
4
+
5
+#ifndef __USB_H__
6
+#define __USB_H__
7
+
8
+enum  {
9
+    BLINK_NOT_MOUNTED = 250,
10
+    BLINK_MOUNTED = 1000,
11
+    BLINK_SUSPENDED = 2500,
12
+};
13
+
14
+extern uint32_t blink_interval_ms;
15
+
16
+void usb_init(void);
17
+void usb_run(void);
18
+
19
+void usb_cdc_write(const char *buf, uint32_t count);
20
+
21
+#endif // __USB_H__

+ 81
- 0
firmware/src/log.c View File

@@ -0,0 +1,81 @@
1
+/*
2
+ * log.c
3
+ */
4
+
5
+#include <stdarg.h>
6
+#include <stdio.h>
7
+#include "pico/stdlib.h"
8
+#include "usb.h"
9
+#include "log.h"
10
+
11
+char log_buff[4096];
12
+size_t head = 0, tail = 0;
13
+bool full = false;
14
+
15
+static void add_to_log(const char *buff, int len) {
16
+    for (int i = 0; i < len; i++) {
17
+        log_buff[head] = buff[i];
18
+
19
+        if (full && (++tail == sizeof(log_buff))) {
20
+            tail = 0;
21
+        }
22
+
23
+        if (++(head) == sizeof(log_buff)) {
24
+            head = 0;
25
+        }
26
+
27
+        full = (head == tail);
28
+    }
29
+}
30
+
31
+void log_dump_to_usb(void) {
32
+    if (head == tail) {
33
+        return;
34
+    }
35
+
36
+    char buff[32];
37
+    int l = snprintf(buff, sizeof(buff), "\r\n\r\nbuffered log output:\r\n");
38
+    if ((l > 0) && (l <= sizeof(buff))) {
39
+        usb_cdc_write(buff, l);
40
+    }
41
+
42
+    if (head > tail) {
43
+        usb_cdc_write(log_buff + tail, head - tail);
44
+    } else {
45
+        usb_cdc_write(log_buff + tail, sizeof(log_buff) - tail);
46
+        usb_cdc_write(log_buff, head);
47
+    }
48
+
49
+    l = snprintf(buff, sizeof(buff), "\r\n\r\nlive log:\r\n");
50
+    if ((l > 0) && (l <= sizeof(buff))) {
51
+        usb_cdc_write(buff, l);
52
+    }
53
+}
54
+
55
+static int format_debug_log(char *buff, size_t len, const char *format, va_list args) {
56
+    int l = vsnprintf(buff, len, format, args);
57
+
58
+    if (l < 0) {
59
+        // encoding error
60
+        l = snprintf(buff, len, "%s: encoding error\r\n", __func__);
61
+    } else if (l >= len) {
62
+        // not enough space for string
63
+        l = snprintf(buff, len, "%s: message too long (%d)\r\n", __func__, l);
64
+    }
65
+
66
+    return l;
67
+}
68
+
69
+void debug_log(const char* format, ...) {
70
+    static char line_buff[256];
71
+
72
+    va_list args;
73
+    va_start(args, format);
74
+    int l = format_debug_log(line_buff, sizeof(line_buff), format, args);
75
+    va_end(args);
76
+
77
+    if ((l > 0) && (l <= sizeof(line_buff))) {
78
+        usb_cdc_write(line_buff, l);
79
+        add_to_log(line_buff, l);
80
+    }
81
+}

+ 16
- 4
firmware/src/main.c View File

@@ -1,9 +1,21 @@
1
-/**
2
- * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
3
- *
4
- * SPDX-License-Identifier: BSD-3-Clause
1
+/*
2
+ * main.c
5 3
  */
6 4
 
7 5
 #include "pico/stdlib.h"
8 6
 
7
+#include "log.h"
8
+#include "usb.h"
9
+#include "pmw3360.h"
9 10
 
11
+int main(void) {
12
+    usb_init();
13
+    pmw_init();
14
+    debug("init done");
15
+
16
+    while (1) {
17
+        usb_run();
18
+    }
19
+
20
+    return 0;
21
+}

+ 209
- 0
firmware/src/pmw3360.c View File

@@ -0,0 +1,209 @@
1
+/*
2
+ * pmw3360.c
3
+ *
4
+ * Based on:
5
+ *  - PMW3360 Datasheet
6
+ *  - https://github.com/raspberrypi/pico-examples/blob/master/spi/bme280_spi/bme280_spi.c
7
+ *
8
+ * Pinout:
9
+ *   GPIO 16 (pin 21) MISO -> MISO on PMW3360 board
10
+ *   GPIO 17 (pin 22) CS   -> NCS on PMW3360 board
11
+ *   GPIO 18 (pin 24) SCK  -> SCK on PMW3360 board
12
+ *   GPIO 19 (pin 25) MOSI -> MOSI on PMW3360 board
13
+ *      3.3v (pin 36)      -> VCC on PMW3360 board
14
+ *       GND (pin 38)      -> GND on PMW3360 board
15
+ *
16
+ * NOTE: Ensure the PMW3360 breakout board is capable of being driven at 3.3v NOT 5v.
17
+ * The Pico GPIO (and therefore SPI) cannot be used at 5v.
18
+ */
19
+
20
+#include "pico/stdlib.h"
21
+#include "pico/binary_info.h"
22
+#include "hardware/spi.h"
23
+
24
+#include "log.h"
25
+#include "pmw3360_registers.h"
26
+#include "pmw3360_srom.h"
27
+#include "pmw3360.h"
28
+
29
+#if !defined(spi_default) || !defined(PICO_DEFAULT_SPI_SCK_PIN) || !defined(PICO_DEFAULT_SPI_TX_PIN) || !defined(PICO_DEFAULT_SPI_RX_PIN) || !defined(PICO_DEFAULT_SPI_CSN_PIN)
30
+#error PMW3360 API requires a board with SPI pins
31
+#endif
32
+
33
+#define PMW_MOTION_PIN 20
34
+
35
+static inline void pmw_cs_select() {
36
+    asm volatile("nop \n nop \n nop");
37
+    gpio_put(PICO_DEFAULT_SPI_CSN_PIN, 0); // Active low
38
+    asm volatile("nop \n nop \n nop");
39
+}
40
+
41
+static inline void pmw_cs_deselect() {
42
+    asm volatile("nop \n nop \n nop");
43
+    gpio_put(PICO_DEFAULT_SPI_CSN_PIN, 1);
44
+    asm volatile("nop \n nop \n nop");
45
+}
46
+
47
+static void pmw_write_register(uint8_t reg, uint8_t data) {
48
+    uint8_t buf[2];
49
+    buf[0] = reg | WRITE_BIT;
50
+    buf[1] = data;
51
+    pmw_cs_select();
52
+    spi_write_blocking(spi_default, buf, 2);
53
+    pmw_cs_deselect();
54
+    sleep_ms(10);
55
+}
56
+
57
+static uint8_t pmw_read_register(uint8_t reg) {
58
+    uint8_t buf = 0;
59
+    reg &= ~WRITE_BIT;
60
+    pmw_cs_select();
61
+    spi_write_blocking(spi_default, &reg, 1);
62
+    sleep_ms(10);
63
+    spi_read_blocking(spi_default, 0, &buf, 1);
64
+    pmw_cs_deselect();
65
+    sleep_ms(10);
66
+    return buf;
67
+}
68
+
69
+static void pmw_write_register_burst(uint8_t reg, uint8_t *buf, uint16_t len) {
70
+    reg |= WRITE_BIT;
71
+    pmw_cs_select();
72
+    spi_write_blocking(spi_default, &reg, 1);
73
+    sleep_us(15);
74
+    for (uint16_t i = 0; i < len; i++) {
75
+        spi_write_blocking(spi_default, buf + i, 1);
76
+        sleep_us(15);
77
+    }
78
+    pmw_cs_deselect();
79
+    sleep_us(1);
80
+}
81
+
82
+static void pmw_read_register_burst(uint8_t reg, uint8_t *buf, uint16_t len) {
83
+    reg &= ~WRITE_BIT;
84
+    pmw_cs_select();
85
+    spi_write_blocking(spi_default, &reg, 1);
86
+    sleep_us(15); // TODO tSRAD_MOTBR
87
+    spi_read_blocking(spi_default, 0x00, buf, len);
88
+    pmw_cs_deselect();
89
+    sleep_us(1); // TODO tBEXIT
90
+}
91
+
92
+static uint8_t pmw_srom_download(void) {
93
+    // Write 0 to Rest_En bit of Config2 register to disable Rest mode
94
+    pmw_write_register(REG_CONFIG2, 0x00);
95
+
96
+    // Write 0x1d to SROM_Enable register for initializing
97
+    pmw_write_register(REG_SROM_ENABLE, 0x1D);
98
+
99
+    // Wait for 10 ms
100
+    sleep_ms(10);
101
+
102
+    // Write 0x18 to SROM_Enable register again to start SROM Download
103
+    pmw_write_register(REG_SROM_ENABLE, 0x18);
104
+
105
+    // Write SROM file into SROM_Load_Burst register, 1st data must start with SROM_Load_Burst address.
106
+    pmw_write_register_burst(REG_SROM_LOAD_BURST, (uint8_t *)pmw_fw_data, pmw_fw_length);
107
+
108
+    // Read the SROM_ID register to verify the ID before any other register reads or writes
109
+    uint8_t srom_id = pmw_read_register(REG_SROM_ID);
110
+    return srom_id;
111
+}
112
+
113
+static uint8_t pmw_power_up(void) {
114
+    // Write 0x5A to Power_Up_Reset register
115
+    pmw_write_register(REG_POWER_UP_RESET, 0x5A);
116
+
117
+    // Wait for at least 50ms
118
+    sleep_ms(50);
119
+
120
+    // Read from registers 0x02, 0x03, 0x04, 0x05 and 0x06 one time
121
+    for (uint8_t reg = REG_MOTION; reg <= REG_DELTA_Y_H; reg++) {
122
+        pmw_read_register(reg);
123
+    }
124
+
125
+    // Perform SROM download
126
+    uint8_t srom_id = pmw_srom_download();
127
+    return srom_id;
128
+}
129
+
130
+static void pmw_motion_read(void) {
131
+    // Write any value to Motion_Burst register
132
+    pmw_write_register(REG_MOTION_BURST, 0x42);
133
+
134
+    // Start reading SPI Data continuously up to 12 bytes
135
+    struct pmw_motion_report motion_report;
136
+    pmw_read_register_burst(REG_MOTION_BURST, (uint8_t *)&motion_report, sizeof(motion_report));
137
+
138
+    // TODO do something with data
139
+}
140
+
141
+static uint16_t pmw_srom_checksum(void) {
142
+    pmw_write_register(REG_SROM_ENABLE, 0x15);
143
+
144
+    // Wait for at least 10 ms
145
+    sleep_ms(10);
146
+
147
+    uint16_t data = pmw_read_register(REG_DATA_OUT_LOWER);
148
+    data &= pmw_read_register(REG_DATA_OUT_UPPER) << 8;
149
+    return data;
150
+}
151
+
152
+static void pmw_spi_init(void) {
153
+    // Use SPI0 at 2MHz
154
+    spi_init(spi_default, 2 * 1000 * 1000);
155
+    gpio_set_function(PICO_DEFAULT_SPI_RX_PIN, GPIO_FUNC_SPI);
156
+    gpio_set_function(PICO_DEFAULT_SPI_SCK_PIN, GPIO_FUNC_SPI);
157
+    gpio_set_function(PICO_DEFAULT_SPI_TX_PIN, GPIO_FUNC_SPI);
158
+
159
+    // Chip select is active-low, so we'll initialise it to a driven-high state
160
+    gpio_init(PICO_DEFAULT_SPI_CSN_PIN);
161
+    gpio_set_dir(PICO_DEFAULT_SPI_CSN_PIN, GPIO_OUT);
162
+    gpio_put(PICO_DEFAULT_SPI_CSN_PIN, 1);
163
+
164
+    // Make the SPI pins available to picotool
165
+    bi_decl(bi_3pins_with_func(PICO_DEFAULT_SPI_RX_PIN, PICO_DEFAULT_SPI_TX_PIN, PICO_DEFAULT_SPI_SCK_PIN, GPIO_FUNC_SPI));
166
+    bi_decl(bi_1pin_with_name(PICO_DEFAULT_SPI_CSN_PIN, "SPI CS"));
167
+}
168
+
169
+static void pmw_motion_irq(void) {
170
+    if (gpio_get_irq_event_mask(PMW_MOTION_PIN) & GPIO_IRQ_EDGE_FALL) {
171
+        gpio_acknowledge_irq(PMW_MOTION_PIN, GPIO_IRQ_EDGE_FALL);
172
+
173
+        // TODO
174
+    }
175
+}
176
+
177
+int pmw_init(void) {
178
+    pmw_spi_init();
179
+
180
+    uint8_t srom_id = pmw_power_up();
181
+
182
+    uint8_t prod_id = pmw_read_register(REG_PRODUCT_ID);
183
+    uint8_t inv_prod_id = pmw_read_register(REG_INVERSE_PRODUCT_ID);
184
+    uint8_t rev_id = pmw_read_register(REG_REVISION_ID);
185
+    uint16_t srom_checksum = pmw_srom_checksum();
186
+
187
+    debug("SROM ID: 0x%02X", srom_id);
188
+    debug("Product ID: 0x%02X", prod_id);
189
+    debug("~Prod. ID: 0x%02X", inv_prod_id);
190
+    debug("Revision ID: 0x%02X", rev_id);
191
+    debug("SROM CRC: 0x%04X", srom_checksum);
192
+
193
+    if (prod_id != (~inv_prod_id)) {
194
+        debug("SPI communication error (0x%02X != ~0x%02X)", prod_id, inv_prod_id);
195
+        return -1;
196
+    }
197
+
198
+    // Write 0x00 to Config2 register for wired mouse or 0x20 for wireless mouse design
199
+#ifdef FEATURE_WIRELESS
200
+    pmw_write_register(REG_CONFIG2, 0x20);
201
+#else
202
+    pmw_write_register(REG_CONFIG2, 0x00);
203
+#endif
204
+
205
+    // TODO setup MOTION pin interrupt to handle reading data?
206
+    //gpio_add_raw_irq_handler(PMW_MOTION_PIN, pmw_motion_irq);
207
+
208
+    return 0;
209
+}

+ 32
- 244
firmware/src/usb.c View File

@@ -23,280 +23,68 @@
23 23
  *
24 24
  */
25 25
 
26
-#include <stdlib.h>
27
-#include <stdio.h>
28
-#include <string.h>
29
-
30 26
 #include "bsp/board.h"
31 27
 #include "tusb.h"
32 28
 
33 29
 #include "usb_descriptors.h"
34
-
35
-//--------------------------------------------------------------------+
36
-// MACRO CONSTANT TYPEDEF PROTYPES
37
-//--------------------------------------------------------------------+
38
-
39
-/* Blink pattern
40
- * - 250 ms  : device not mounted
41
- * - 1000 ms : device mounted
42
- * - 2500 ms : device is suspended
43
- */
44
-enum  {
45
-  BLINK_NOT_MOUNTED = 250,
46
-  BLINK_MOUNTED = 1000,
47
-  BLINK_SUSPENDED = 2500,
48
-};
49
-
50
-static uint32_t blink_interval_ms = BLINK_NOT_MOUNTED;
30
+#include "usb.h"
51 31
 
52 32
 void led_blinking_task(void);
33
+void usb_descriptor_init_id(void);
34
+void cdc_task(void);
53 35
 void hid_task(void);
54 36
 
55
-/*------------- MAIN -------------*/
56
-int main(void)
57
-{
58
-  board_init();
59
-  tusb_init();
37
+void usb_init(void) {
38
+    usb_descriptor_init_id();
60 39
 
61
-  while (1)
62
-  {
63
-    tud_task(); // tinyusb device task
40
+    board_init();
41
+    tusb_init();
42
+}
43
+
44
+void usb_run(void) {
45
+    tud_task();
64 46
     led_blinking_task();
65 47
 
48
+    cdc_task();
66 49
     hid_task();
67
-  }
68
-
69
-  return 0;
70 50
 }
71 51
 
72
-//--------------------------------------------------------------------+
73
-// Device callbacks
74
-//--------------------------------------------------------------------+
52
+uint32_t blink_interval_ms = BLINK_NOT_MOUNTED;
75 53
 
76 54
 // Invoked when device is mounted
77
-void tud_mount_cb(void)
78
-{
79
-  blink_interval_ms = BLINK_MOUNTED;
55
+void tud_mount_cb(void) {
56
+    blink_interval_ms = BLINK_MOUNTED;
80 57
 }
81 58
 
82 59
 // Invoked when device is unmounted
83
-void tud_umount_cb(void)
84
-{
85
-  blink_interval_ms = BLINK_NOT_MOUNTED;
60
+void tud_umount_cb(void) {
61
+    blink_interval_ms = BLINK_NOT_MOUNTED;
86 62
 }
87 63
 
88 64
 // Invoked when usb bus is suspended
89 65
 // remote_wakeup_en : if host allow us  to perform remote wakeup
90 66
 // Within 7ms, device must draw an average of current less than 2.5 mA from bus
91
-void tud_suspend_cb(bool remote_wakeup_en)
92
-{
93
-  (void) remote_wakeup_en;
94
-  blink_interval_ms = BLINK_SUSPENDED;
67
+void tud_suspend_cb(bool remote_wakeup_en) {
68
+    (void) remote_wakeup_en;
69
+    blink_interval_ms = BLINK_SUSPENDED;
95 70
 }
96 71
 
97 72
 // Invoked when usb bus is resumed
98
-void tud_resume_cb(void)
99
-{
100
-  blink_interval_ms = BLINK_MOUNTED;
101
-}
102
-
103
-//--------------------------------------------------------------------+
104
-// USB HID
105
-//--------------------------------------------------------------------+
106
-
107
-static void send_hid_report(uint8_t report_id, uint32_t btn)
108
-{
109
-  // skip if hid is not ready yet
110
-  if ( !tud_hid_ready() ) return;
111
-
112
-  switch(report_id)
113
-  {
114
-    case REPORT_ID_KEYBOARD:
115
-    {
116
-      // use to avoid send multiple consecutive zero report for keyboard
117
-      static bool has_keyboard_key = false;
118
-
119
-      if ( btn )
120
-      {
121
-        uint8_t keycode[6] = { 0 };
122
-        keycode[0] = HID_KEY_A;
123
-
124
-        tud_hid_keyboard_report(REPORT_ID_KEYBOARD, 0, keycode);
125
-        has_keyboard_key = true;
126
-      }else
127
-      {
128
-        // send empty key report if previously has key pressed
129
-        if (has_keyboard_key) tud_hid_keyboard_report(REPORT_ID_KEYBOARD, 0, NULL);
130
-        has_keyboard_key = false;
131
-      }
132
-    }
133
-    break;
134
-
135
-    case REPORT_ID_MOUSE:
136
-    {
137
-      int8_t const delta = 5;
138
-
139
-      // no button, right + down, no scroll, no pan
140
-      tud_hid_mouse_report(REPORT_ID_MOUSE, 0x00, delta, delta, 0, 0);
141
-    }
142
-    break;
143
-
144
-    case REPORT_ID_CONSUMER_CONTROL:
145
-    {
146
-      // use to avoid send multiple consecutive zero report
147
-      static bool has_consumer_key = false;
148
-
149
-      if ( btn )
150
-      {
151
-        // volume down
152
-        uint16_t volume_down = HID_USAGE_CONSUMER_VOLUME_DECREMENT;
153
-        tud_hid_report(REPORT_ID_CONSUMER_CONTROL, &volume_down, 2);
154
-        has_consumer_key = true;
155
-      }else
156
-      {
157
-        // send empty key report (release key) if previously has key pressed
158
-        uint16_t empty_key = 0;
159
-        if (has_consumer_key) tud_hid_report(REPORT_ID_CONSUMER_CONTROL, &empty_key, 2);
160
-        has_consumer_key = false;
161
-      }
162
-    }
163
-    break;
164
-
165
-    case REPORT_ID_GAMEPAD:
166
-    {
167
-      // use to avoid send multiple consecutive zero report for keyboard
168
-      static bool has_gamepad_key = false;
169
-
170
-      hid_gamepad_report_t report =
171
-      {
172
-        .x   = 0, .y = 0, .z = 0, .rz = 0, .rx = 0, .ry = 0,
173
-        .hat = 0, .buttons = 0
174
-      };
175
-
176
-      if ( btn )
177
-      {
178
-        report.hat = GAMEPAD_HAT_UP;
179
-        report.buttons = GAMEPAD_BUTTON_A;
180
-        tud_hid_report(REPORT_ID_GAMEPAD, &report, sizeof(report));
181
-
182
-        has_gamepad_key = true;
183
-      }else
184
-      {
185
-        report.hat = GAMEPAD_HAT_CENTERED;
186
-        report.buttons = 0;
187
-        if (has_gamepad_key) tud_hid_report(REPORT_ID_GAMEPAD, &report, sizeof(report));
188
-        has_gamepad_key = false;
189
-      }
190
-    }
191
-    break;
192
-
193
-    default: break;
194
-  }
195
-}
196
-
197
-// Every 10ms, we will sent 1 report for each HID profile (keyboard, mouse etc ..)
198
-// tud_hid_report_complete_cb() is used to send the next report after previous one is complete
199
-void hid_task(void)
200
-{
201
-  // Poll every 10ms
202
-  const uint32_t interval_ms = 10;
203
-  static uint32_t start_ms = 0;
204
-
205
-  if ( board_millis() - start_ms < interval_ms) return; // not enough time
206
-  start_ms += interval_ms;
207
-
208
-  uint32_t const btn = board_button_read();
209
-
210
-  // Remote wakeup
211
-  if ( tud_suspended() && btn )
212
-  {
213
-    // Wake up host if we are in suspend mode
214
-    // and REMOTE_WAKEUP feature is enabled by host
215
-    tud_remote_wakeup();
216
-  }else
217
-  {
218
-    // Send the 1st of report chain, the rest will be sent by tud_hid_report_complete_cb()
219
-    send_hid_report(REPORT_ID_KEYBOARD, btn);
220
-  }
221
-}
222
-
223
-// Invoked when sent REPORT successfully to host
224
-// Application can use this to send the next report
225
-// Note: For composite reports, report[0] is report ID
226
-void tud_hid_report_complete_cb(uint8_t instance, uint8_t const* report, uint8_t len)
227
-{
228
-  (void) instance;
229
-  (void) len;
230
-
231
-  uint8_t next_report_id = report[0] + 1;
232
-
233
-  if (next_report_id < REPORT_ID_COUNT)
234
-  {
235
-    send_hid_report(next_report_id, board_button_read());
236
-  }
237
-}
238
-
239
-// Invoked when received GET_REPORT control request
240
-// Application must fill buffer report's content and return its length.
241
-// Return zero will cause the stack to STALL request
242
-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)
243
-{
244
-  // TODO not Implemented
245
-  (void) instance;
246
-  (void) report_id;
247
-  (void) report_type;
248
-  (void) buffer;
249
-  (void) reqlen;
250
-
251
-  return 0;
252
-}
253
-
254
-// Invoked when received SET_REPORT control request or
255
-// received data on OUT endpoint ( Report ID = 0, Type = 0 )
256
-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)
257
-{
258
-  (void) instance;
259
-
260
-  if (report_type == HID_REPORT_TYPE_OUTPUT)
261
-  {
262
-    // Set keyboard LED e.g Capslock, Numlock etc...
263
-    if (report_id == REPORT_ID_KEYBOARD)
264
-    {
265
-      // bufsize should be (at least) 1
266
-      if ( bufsize < 1 ) return;
267
-
268
-      uint8_t const kbd_leds = buffer[0];
269
-
270
-      if (kbd_leds & KEYBOARD_LED_CAPSLOCK)
271
-      {
272
-        // Capslock On: disable blink, turn led on
273
-        blink_interval_ms = 0;
274
-        board_led_write(true);
275
-      }else
276
-      {
277
-        // Caplocks Off: back to normal blink
278
-        board_led_write(false);
279
-        blink_interval_ms = BLINK_MOUNTED;
280
-      }
281
-    }
282
-  }
73
+void tud_resume_cb(void) {
74
+    blink_interval_ms = BLINK_MOUNTED;
283 75
 }
284 76
 
285
-//--------------------------------------------------------------------+
286
-// BLINKING TASK
287
-//--------------------------------------------------------------------+
288
-void led_blinking_task(void)
289
-{
290
-  static uint32_t start_ms = 0;
291
-  static bool led_state = false;
77
+void led_blinking_task(void) {
78
+    static uint32_t start_ms = 0;
79
+    static bool led_state = false;
292 80
 
293
-  // blink is disabled
294
-  if (!blink_interval_ms) return;
81
+    // blink is disabled
82
+    if (!blink_interval_ms) return;
295 83
 
296
-  // Blink every interval ms
297
-  if ( board_millis() - start_ms < blink_interval_ms) return; // not enough time
298
-  start_ms += blink_interval_ms;
84
+    // Blink every interval ms
85
+    if ( board_millis() - start_ms < blink_interval_ms) return; // not enough time
86
+    start_ms += blink_interval_ms;
299 87
 
300
-  board_led_write(led_state);
301
-  led_state = 1 - led_state; // toggle
88
+    board_led_write(led_state);
89
+    led_state = 1 - led_state; // toggle
302 90
 }

+ 100
- 0
firmware/src/usb_cdc.c View File

@@ -0,0 +1,100 @@
1
+/* 
2
+ * The MIT License (MIT)
3
+ *
4
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in
14
+ * all copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ * THE SOFTWARE.
23
+ *
24
+ */
25
+
26
+#include "bsp/board.h"
27
+#include "tusb.h"
28
+#include "pico/bootrom.h"
29
+
30
+#include "log.h"
31
+#include "usb_descriptors.h"
32
+#include "usb.h"
33
+
34
+void usb_cdc_write(const char *buf, uint32_t count) {
35
+#ifndef DISABLE_CDC_DTR_CHECK
36
+    if (!tud_cdc_connected()) {
37
+        return;
38
+    }
39
+#endif // DISABLE_CDC_DTR_CHECK
40
+
41
+    uint32_t len = 0;
42
+
43
+    while (len < count) {
44
+        uint32_t n = count - len;
45
+        uint32_t available = tud_cdc_write_available();
46
+
47
+        if (n > available) {
48
+            n = available;
49
+        }
50
+
51
+        len += tud_cdc_write(buf + len, n);
52
+        tud_task();
53
+        tud_cdc_write_flush();
54
+    }
55
+}
56
+
57
+void cdc_task(void) {
58
+    if (tud_cdc_available()) {
59
+        char buf[64];
60
+        uint32_t count = tud_cdc_read(buf, sizeof(buf));
61
+
62
+        if ((count >= 1) && (buf[0] == 27)) {
63
+            // escape key
64
+            debug("switching to bootloader");
65
+
66
+#ifdef PICO_DEFAULT_LED_PIN
67
+            reset_usb_boot(1 << PICO_DEFAULT_LED_PIN, 0);
68
+#else
69
+            reset_usb_boot(0, 0);
70
+#endif
71
+        } else {
72
+            // echo back
73
+            usb_cdc_write(buf, count);
74
+
75
+            // TODO handle user input
76
+        }
77
+    }
78
+}
79
+
80
+// invoked when cdc when line state changed e.g connected/disconnected
81
+void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) {
82
+    (void) itf;
83
+    (void) rts;
84
+
85
+    static bool last_dtr = false;
86
+
87
+    if (dtr && !last_dtr) {
88
+        log_dump_to_usb();
89
+        debug("terminal connected");
90
+    } else if (!dtr && last_dtr) {
91
+        debug("terminal disconnected");
92
+    }
93
+
94
+    last_dtr = dtr;
95
+}
96
+
97
+// invoked when CDC interface received data from host
98
+void tud_cdc_rx_cb(uint8_t itf) {
99
+    (void) itf;
100
+}

+ 93
- 93
firmware/src/usb_descriptors.c View File

@@ -23,18 +23,22 @@
23 23
  *
24 24
  */
25 25
 
26
+#include "pico/unique_id.h"
26 27
 #include "tusb.h"
27 28
 #include "usb_descriptors.h"
28 29
 
29
-/* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug.
30
- * Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC.
30
+/*
31
+ * A combination of interfaces must have a unique product id,
32
+ * since PC will save device driver after the first plug.
33
+ * Same VID/PID with different interface e.g MSC (first),
34
+ * then CDC (later) will possibly cause system error on PC.
31 35
  *
32 36
  * Auto ProductID layout's Bitmap:
33 37
  *   [MSB]         HID | MSC | CDC          [LSB]
34 38
  */
35
-#define _PID_MAP(itf, n)  ( (CFG_TUD_##itf) << (n) )
36
-#define USB_PID           (0x4200 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \
37
-                           _PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4) )
39
+#define _PID_MAP(itf, n) ((CFG_TUD_##itf) << (n))
40
+#define USB_PID (0x4200 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) \
41
+    | _PID_MAP(HID, 2) | _PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4) )
38 42
 
39 43
 #define USB_VID   0xCafe
40 44
 #define USB_BCD   0x0200
@@ -42,14 +46,18 @@
42 46
 //--------------------------------------------------------------------+
43 47
 // Device Descriptors
44 48
 //--------------------------------------------------------------------+
49
+
45 50
 tusb_desc_device_t const desc_device =
46 51
 {
47 52
     .bLength            = sizeof(tusb_desc_device_t),
48 53
     .bDescriptorType    = TUSB_DESC_DEVICE,
49 54
     .bcdUSB             = USB_BCD,
50 55
 
51
-    // Use Interface Association Descriptor (IAD) for CDC
52
-    // As required by USB Specs IAD's subclass must be common class (2) and protocol must be IAD (1)
56
+    /*
57
+     * Use Interface Association Descriptor (IAD) for CDC
58
+     * As required by USB Specs IAD's subclass must be common class (2)
59
+     * and protocol must be IAD (1)
60
+     */
53 61
     .bDeviceClass       = TUSB_CLASS_MISC,
54 62
     .bDeviceSubClass    = MISC_SUBCLASS_COMMON,
55 63
     .bDeviceProtocol    = MISC_PROTOCOL_IAD,
@@ -69,42 +77,38 @@ tusb_desc_device_t const desc_device =
69 77
 
70 78
 // Invoked when received GET DEVICE DESCRIPTOR
71 79
 // Application return pointer to descriptor
72
-uint8_t const * tud_descriptor_device_cb(void)
73
-{
74
-  return (uint8_t const *) &desc_device;
80
+uint8_t const * tud_descriptor_device_cb(void) {
81
+    return (uint8_t const *) &desc_device;
75 82
 }
76 83
 
77 84
 //--------------------------------------------------------------------+
78 85
 // HID Report Descriptor
79 86
 //--------------------------------------------------------------------+
80 87
 
81
-uint8_t const desc_hid_report[] =
82
-{
83
-  TUD_HID_REPORT_DESC_KEYBOARD( HID_REPORT_ID(REPORT_ID_KEYBOARD         )),
84
-  TUD_HID_REPORT_DESC_MOUSE   ( HID_REPORT_ID(REPORT_ID_MOUSE            )),
85
-  TUD_HID_REPORT_DESC_CONSUMER( HID_REPORT_ID(REPORT_ID_CONSUMER_CONTROL )),
86
-  TUD_HID_REPORT_DESC_GAMEPAD ( HID_REPORT_ID(REPORT_ID_GAMEPAD          ))
88
+uint8_t const desc_hid_report[] = {
89
+    TUD_HID_REPORT_DESC_KEYBOARD( HID_REPORT_ID(REPORT_ID_KEYBOARD         )),
90
+    TUD_HID_REPORT_DESC_MOUSE   ( HID_REPORT_ID(REPORT_ID_MOUSE            )),
91
+    TUD_HID_REPORT_DESC_CONSUMER( HID_REPORT_ID(REPORT_ID_CONSUMER_CONTROL )),
92
+    TUD_HID_REPORT_DESC_GAMEPAD ( HID_REPORT_ID(REPORT_ID_GAMEPAD          ))
87 93
 };
88 94
 
89 95
 // Invoked when received GET HID REPORT DESCRIPTOR
90 96
 // Application return pointer to descriptor
91 97
 // Descriptor contents must exist long enough for transfer to complete
92
-uint8_t const * tud_hid_descriptor_report_cb(uint8_t instance)
93
-{
94
-  (void) instance;
95
-  return desc_hid_report;
98
+uint8_t const * tud_hid_descriptor_report_cb(uint8_t instance) {
99
+    (void) instance;
100
+    return desc_hid_report;
96 101
 }
97 102
 
98 103
 //--------------------------------------------------------------------+
99 104
 // Configuration Descriptor
100 105
 //--------------------------------------------------------------------+
101 106
 
102
-enum
103
-{
104
-  ITF_NUM_CDC = 0,
105
-  ITF_NUM_CDC_DATA,
106
-  ITF_NUM_HID,
107
-  ITF_NUM_TOTAL
107
+enum {
108
+    ITF_NUM_CDC = 0,
109
+    ITF_NUM_CDC_DATA,
110
+    ITF_NUM_HID,
111
+    ITF_NUM_TOTAL
108 112
 };
109 113
 
110 114
 #define  CONFIG_TOTAL_LEN  (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN + TUD_HID_DESC_LEN)
@@ -114,16 +118,15 @@ enum
114 118
 #define EPNUM_CDC_OUT   0x02
115 119
 #define EPNUM_CDC_IN    0x83
116 120
 
117
-uint8_t const desc_configuration[] =
118
-{
119
-  // Config number, interface count, string index, total length, attribute, power in mA
120
-  TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
121
+uint8_t const desc_configuration[] = {
122
+    // Config number, interface count, string index, total length, attribute, power in mA
123
+    TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
121 124
 
122
-  // Interface number, string index, EP notification address and size, EP data address (out, in) and size.
123
-  TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 64),
125
+    // Interface number, string index, EP notification address and size, EP data address (out, in) and size.
126
+    TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 64),
124 127
 
125
-  // Interface number, string index, protocol, report descriptor len, EP In address, size & polling interval
126
-  TUD_HID_DESCRIPTOR(ITF_NUM_HID, 0, HID_ITF_PROTOCOL_NONE, sizeof(desc_hid_report), EPNUM_HID, CFG_TUD_HID_EP_BUFSIZE, 5)
128
+    // Interface number, string index, protocol, report descriptor len, EP In address, size & polling interval
129
+    TUD_HID_DESCRIPTOR(ITF_NUM_HID, 0, HID_ITF_PROTOCOL_NONE, sizeof(desc_hid_report), EPNUM_HID, CFG_TUD_HID_EP_BUFSIZE, 5)
127 130
 };
128 131
 
129 132
 #if TUD_OPT_HIGH_SPEED
@@ -133,43 +136,40 @@ uint8_t const desc_configuration[] =
133 136
 uint8_t desc_other_speed_config[CONFIG_TOTAL_LEN];
134 137
 
135 138
 // device qualifier is mostly similar to device descriptor since we don't change configuration based on speed
136
-tusb_desc_device_qualifier_t const desc_device_qualifier =
137
-{
138
-  .bLength            = sizeof(tusb_desc_device_qualifier_t),
139
-  .bDescriptorType    = TUSB_DESC_DEVICE_QUALIFIER,
140
-  .bcdUSB             = USB_BCD,
139
+tusb_desc_device_qualifier_t const desc_device_qualifier = {
140
+    .bLength            = sizeof(tusb_desc_device_qualifier_t),
141
+    .bDescriptorType    = TUSB_DESC_DEVICE_QUALIFIER,
142
+    .bcdUSB             = USB_BCD,
141 143
 
142
-  .bDeviceClass       = TUSB_CLASS_MISC,
143
-  .bDeviceSubClass    = MISC_SUBCLASS_COMMON,
144
-  .bDeviceProtocol    = MISC_PROTOCOL_IAD,
144
+    .bDeviceClass       = TUSB_CLASS_MISC,
145
+    .bDeviceSubClass    = MISC_SUBCLASS_COMMON,
146
+    .bDeviceProtocol    = MISC_PROTOCOL_IAD,
145 147
 
146
-  .bMaxPacketSize0    = CFG_TUD_ENDPOINT0_SIZE,
147
-  .bNumConfigurations = 0x01,
148
-  .bReserved          = 0x00
148
+    .bMaxPacketSize0    = CFG_TUD_ENDPOINT0_SIZE,
149
+    .bNumConfigurations = 0x01,
150
+    .bReserved          = 0x00
149 151
 };
150 152
 
151 153
 // Invoked when received GET DEVICE QUALIFIER DESCRIPTOR request
152 154
 // Application return pointer to descriptor, whose contents must exist long enough for transfer to complete.
153 155
 // device_qualifier descriptor describes information about a high-speed capable device that would
154 156
 // change if the device were operating at the other speed. If not highspeed capable stall this request.
155
-uint8_t const* tud_descriptor_device_qualifier_cb(void)
156
-{
157
-  return (uint8_t const*) &desc_device_qualifier;
157
+uint8_t const* tud_descriptor_device_qualifier_cb(void) {
158
+    return (uint8_t const*) &desc_device_qualifier;
158 159
 }
159 160
 
160 161
 // Invoked when received GET OTHER SEED CONFIGURATION DESCRIPTOR request
161 162
 // Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
162 163
 // Configuration descriptor in the other speed e.g if high speed then this is for full speed and vice versa
163
-uint8_t const* tud_descriptor_other_speed_configuration_cb(uint8_t index)
164
-{
165
-  (void) index; // for multiple configurations
164
+uint8_t const* tud_descriptor_other_speed_configuration_cb(uint8_t index) {
165
+    (void) index; // for multiple configurations
166 166
 
167
-  // other speed config is basically configuration with type = OHER_SPEED_CONFIG
168
-  memcpy(desc_other_speed_config, desc_configuration, CONFIG_TOTAL_LEN);
169
-  desc_other_speed_config[1] = TUSB_DESC_OTHER_SPEED_CONFIG;
167
+    // other speed config is basically configuration with type = OHER_SPEED_CONFIG
168
+    memcpy(desc_other_speed_config, desc_configuration, CONFIG_TOTAL_LEN);
169
+    desc_other_speed_config[1] = TUSB_DESC_OTHER_SPEED_CONFIG;
170 170
 
171
-  // this example use the same configuration for both high and full speed mode
172
-  return desc_other_speed_config;
171
+    // this example use the same configuration for both high and full speed mode
172
+    return desc_other_speed_config;
173 173
 }
174 174
 
175 175
 #endif // highspeed
@@ -177,64 +177,64 @@ uint8_t const* tud_descriptor_other_speed_configuration_cb(uint8_t index)
177 177
 // Invoked when received GET CONFIGURATION DESCRIPTOR
178 178
 // Application return pointer to descriptor
179 179
 // Descriptor contents must exist long enough for transfer to complete
180
-uint8_t const * tud_descriptor_configuration_cb(uint8_t index)
181
-{
182
-  (void) index; // for multiple configurations
180
+uint8_t const * tud_descriptor_configuration_cb(uint8_t index) {
181
+    (void) index; // for multiple configurations
183 182
 
184
-  // This example use the same configuration for both high and full speed mode
185
-  return desc_configuration;
183
+    // This example use the same configuration for both high and full speed mode
184
+    return desc_configuration;
186 185
 }
187 186
 
188 187
 //--------------------------------------------------------------------+
189 188
 // String Descriptors
190 189
 //--------------------------------------------------------------------+
191 190
 
191
+char string_pico_serial[2 * PICO_UNIQUE_BOARD_ID_SIZE_BYTES + 1];
192
+
193
+void usb_descriptor_init_id(void) {
194
+    pico_get_unique_board_id_string(string_pico_serial, sizeof(string_pico_serial));
195
+}
196
+
192 197
 // array of pointer to string descriptors
193
-char const* string_desc_arr [] =
194
-{
195
-  (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409)
196
-  "xythobuz",                    // 1: Manufacturer
197
-  "Trackball",                   // 2: Product
198
-  "123456",                      // 3: Serials, should use chip ID
199
-  "Debug Serial",                // 4: CDC Interface
198
+char const* string_desc_arr [] = {
199
+    (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409)
200
+    "xythobuz",                    // 1: Manufacturer
201
+    "Trackball",                   // 2: Product
202
+    string_pico_serial,            // 3: Serials, should use chip ID
203
+    "Debug Serial",                // 4: CDC Interface
200 204
 };
201 205
 
202 206
 static uint16_t _desc_str[32];
203 207
 
204 208
 // Invoked when received GET STRING DESCRIPTOR request
205 209
 // Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
206
-uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid)
207
-{
208
-  (void) langid;
210
+uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
211
+    (void) langid;
209 212
 
210
-  uint8_t chr_count;
213
+    uint8_t chr_count;
211 214
 
212
-  if ( index == 0)
213
-  {
214
-    memcpy(&_desc_str[1], string_desc_arr[0], 2);
215
-    chr_count = 1;
216
-  }else
217
-  {
218
-    // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors.
219
-    // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors
215
+    if ( index == 0) {
216
+        memcpy(&_desc_str[1], string_desc_arr[0], 2);
217
+        chr_count = 1;
218
+    } else {
219
+        // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors.
220
+        // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors
220 221
 
221
-    if ( !(index < sizeof(string_desc_arr)/sizeof(string_desc_arr[0])) ) return NULL;
222
+        if ( !(index < sizeof(string_desc_arr)/sizeof(string_desc_arr[0])) ) return NULL;
222 223
 
223
-    const char* str = string_desc_arr[index];
224
+        const char* str = string_desc_arr[index];
224 225
 
225
-    // Cap at max char
226
-    chr_count = strlen(str);
227
-    if ( chr_count > 31 ) chr_count = 31;
226
+        // Cap at max char
227
+        chr_count = strlen(str);
228
+        if ( chr_count > 31 ) chr_count = 31;
228 229
 
229
-    // Convert ASCII string into UTF-16
230
-    for(uint8_t i=0; i<chr_count; i++)
231
-    {
232
-      _desc_str[1+i] = str[i];
230
+        // Convert ASCII string into UTF-16
231
+        for(uint8_t i=0; i<chr_count; i++) {
232
+            _desc_str[1+i] = str[i];
233
+        }
233 234
     }
234
-  }
235 235
 
236
-  // first byte is length (including header), second byte is string type
237
-  _desc_str[0] = (TUSB_DESC_STRING << 8 ) | (2*chr_count + 2);
236
+    // first byte is length (including header), second byte is string type
237
+    _desc_str[0] = (TUSB_DESC_STRING << 8 ) | (2*chr_count + 2);
238 238
 
239
-  return _desc_str;
239
+    return _desc_str;
240 240
 }

+ 191
- 0
firmware/src/usb_hid.c View File

@@ -0,0 +1,191 @@
1
+/* 
2
+ * The MIT License (MIT)
3
+ *
4
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in
14
+ * all copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ * THE SOFTWARE.
23
+ *
24
+ */
25
+
26
+#include "bsp/board.h"
27
+#include "tusb.h"
28
+
29
+#include "usb_descriptors.h"
30
+#include "usb.h"
31
+
32
+static void send_hid_report(uint8_t report_id, uint32_t btn) {
33
+    // skip if hid is not ready yet
34
+    if ( !tud_hid_ready() ) return;
35
+
36
+    switch(report_id) {
37
+        case REPORT_ID_KEYBOARD:
38
+        {
39
+            // use to avoid send multiple consecutive zero report for keyboard
40
+            static bool has_keyboard_key = false;
41
+
42
+            if ( btn ) {
43
+                uint8_t keycode[6] = { 0 };
44
+                keycode[0] = HID_KEY_A;
45
+
46
+                tud_hid_keyboard_report(REPORT_ID_KEYBOARD, 0, keycode);
47
+                has_keyboard_key = true;
48
+            } else {
49
+                // send empty key report if previously has key pressed
50
+                if (has_keyboard_key) tud_hid_keyboard_report(REPORT_ID_KEYBOARD, 0, NULL);
51
+                has_keyboard_key = false;
52
+            }
53
+        }
54
+        break;
55
+
56
+        case REPORT_ID_MOUSE:
57
+        {
58
+            int8_t const delta = 5;
59
+
60
+            // no button, right + down, no scroll, no pan
61
+            tud_hid_mouse_report(REPORT_ID_MOUSE, 0x00, delta, delta, 0, 0);
62
+        }
63
+        break;
64
+
65
+        case REPORT_ID_CONSUMER_CONTROL:
66
+        {
67
+            // use to avoid send multiple consecutive zero report
68
+            static bool has_consumer_key = false;
69
+
70
+            if (btn) {
71
+                // volume down
72
+                uint16_t volume_down = HID_USAGE_CONSUMER_VOLUME_DECREMENT;
73
+                tud_hid_report(REPORT_ID_CONSUMER_CONTROL, &volume_down, 2);
74
+                has_consumer_key = true;
75
+            } else {
76
+                // send empty key report (release key) if previously has key pressed
77
+                uint16_t empty_key = 0;
78
+                if (has_consumer_key) tud_hid_report(REPORT_ID_CONSUMER_CONTROL, &empty_key, 2);
79
+                has_consumer_key = false;
80
+            }
81
+        }
82
+        break;
83
+
84
+        case REPORT_ID_GAMEPAD:
85
+        {
86
+            // use to avoid send multiple consecutive zero report for keyboard
87
+            static bool has_gamepad_key = false;
88
+
89
+            hid_gamepad_report_t report = {
90
+                .x   = 0, .y = 0, .z = 0, .rz = 0, .rx = 0, .ry = 0,
91
+                .hat = 0, .buttons = 0
92
+            };
93
+
94
+            if (btn) {
95
+                report.hat = GAMEPAD_HAT_UP;
96
+                report.buttons = GAMEPAD_BUTTON_A;
97
+                tud_hid_report(REPORT_ID_GAMEPAD, &report, sizeof(report));
98
+
99
+                has_gamepad_key = true;
100
+            } else {
101
+                report.hat = GAMEPAD_HAT_CENTERED;
102
+                report.buttons = 0;
103
+                if (has_gamepad_key) tud_hid_report(REPORT_ID_GAMEPAD, &report, sizeof(report));
104
+                has_gamepad_key = false;
105
+            }
106
+        }
107
+        break;
108
+
109
+        default:
110
+        break;
111
+    }
112
+}
113
+
114
+// Every 10ms, we will sent 1 report for each HID profile (keyboard, mouse etc ..)
115
+// tud_hid_report_complete_cb() is used to send the next report after previous one is complete
116
+void hid_task(void) {
117
+    // Poll every 10ms
118
+    const uint32_t interval_ms = 10;
119
+    static uint32_t start_ms = 0;
120
+
121
+    if ( board_millis() - start_ms < interval_ms) return; // not enough time
122
+    start_ms += interval_ms;
123
+
124
+    uint32_t const btn = board_button_read();
125
+
126
+    // Remote wakeup
127
+    if ( tud_suspended() && btn ) {
128
+        // Wake up host if we are in suspend mode
129
+        // and REMOTE_WAKEUP feature is enabled by host
130
+        tud_remote_wakeup();
131
+    } else {
132
+        // Send the 1st of report chain, the rest will be sent by tud_hid_report_complete_cb()
133
+        send_hid_report(REPORT_ID_KEYBOARD, btn);
134
+    }
135
+}
136
+
137
+// Invoked when sent REPORT successfully to host
138
+// Application can use this to send the next report
139
+// Note: For composite reports, report[0] is report ID
140
+void tud_hid_report_complete_cb(uint8_t instance, uint8_t const* report, uint8_t len) {
141
+    (void) instance;
142
+    (void) len;
143
+
144
+    uint8_t next_report_id = report[0] + 1;
145
+
146
+    if (next_report_id < REPORT_ID_COUNT) {
147
+        send_hid_report(next_report_id, board_button_read());
148
+    }
149
+}
150
+
151
+// Invoked when received GET_REPORT control request
152
+// Application must fill buffer report's content and return its length.
153
+// Return zero will cause the stack to STALL request
154
+uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id,
155
+        hid_report_type_t report_type, uint8_t* buffer, uint16_t reqlen) {
156
+    // TODO not Implemented
157
+    (void) instance;
158
+    (void) report_id;
159
+    (void) report_type;
160
+    (void) buffer;
161
+    (void) reqlen;
162
+
163
+    return 0;
164
+}
165
+
166
+// Invoked when received SET_REPORT control request or
167
+// received data on OUT endpoint ( Report ID = 0, Type = 0 )
168
+void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id,
169
+        hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize) {
170
+    (void) instance;
171
+
172
+    if (report_type == HID_REPORT_TYPE_OUTPUT) {
173
+        // Set keyboard LED e.g Capslock, Numlock etc...
174
+        if (report_id == REPORT_ID_KEYBOARD) {
175
+            // bufsize should be (at least) 1
176
+            if ( bufsize < 1 ) return;
177
+
178
+            uint8_t const kbd_leds = buffer[0];
179
+
180
+            if (kbd_leds & KEYBOARD_LED_CAPSLOCK) {
181
+                // Capslock On: disable blink, turn led on
182
+                blink_interval_ms = 0;
183
+                board_led_write(true);
184
+            } else {
185
+                // Caplocks Off: back to normal blink
186
+                board_led_write(false);
187
+                blink_interval_ms = BLINK_MOUNTED;
188
+            }
189
+        }
190
+    }
191
+}

Loading…
Cancel
Save