From 00d0883507efdc17688abafa75e81bf62f83d777 Mon Sep 17 00:00:00 2001 From: Dean Camera Date: Sat, 20 Jun 2009 11:43:26 +0000 Subject: [PATCH] Added USE_INTERNAL_SERIAL compile time option to automatically read out the internal unique serial number as the device's serial number descriptor on supported AVR models. --- .../Device/LowLevel/USBtoSerial/USBtoSerial.c | 2 +- .../GenericHIDHost/ConfigDescriptor.c | 2 +- LUFA/Drivers/USB/Class/Device/CDC.c | 6 ++-- LUFA/Drivers/USB/Class/Device/CDC.h | 24 +++++++++----- LUFA/Drivers/USB/Class/Host/CDC.c | 28 +++++++++------- LUFA/Drivers/USB/Class/Host/CDC.h | 24 ++++++++++++-- LUFA/Drivers/USB/HighLevel/StdDescriptors.h | 8 ++++- LUFA/Drivers/USB/LowLevel/DevChapter9.c | 32 ++++++++++++++++++- LUFA/Drivers/USB/LowLevel/DevChapter9.h | 12 +++++++ LUFA/ManPages/ChangeLog.txt | 3 +- LUFA/ManPages/CompileTimeTokens.txt | 8 +++++ LUFA/ManPages/LUFAPoweredProjects.txt | 31 ++++++++++++++---- 12 files changed, 142 insertions(+), 38 deletions(-) diff --git a/Demos/Device/LowLevel/USBtoSerial/USBtoSerial.c b/Demos/Device/LowLevel/USBtoSerial/USBtoSerial.c index 3ed406db4f..444aa5e1ca 100644 --- a/Demos/Device/LowLevel/USBtoSerial/USBtoSerial.c +++ b/Demos/Device/LowLevel/USBtoSerial/USBtoSerial.c @@ -265,7 +265,7 @@ void CDC_Task(void) while (!(Endpoint_IsReadWriteAllowed())); /* Write the bytes from the buffer to the endpoint while space is available */ - while (Tx_Buffer.Elements && (Endpoint_BytesInEndpoint() < CDC_TXRX_EPSIZE)) + while (Tx_Buffer.Elements && Endpoint_IsReadWriteAllowed()) { /* Write each byte retreived from the buffer to the endpoint */ Endpoint_Write_Byte(Buffer_GetElement(&Tx_Buffer)); diff --git a/Demos/Host/LowLevel/GenericHIDHost/ConfigDescriptor.c b/Demos/Host/LowLevel/GenericHIDHost/ConfigDescriptor.c index 2a6e152569..26181c1461 100644 --- a/Demos/Host/LowLevel/GenericHIDHost/ConfigDescriptor.c +++ b/Demos/Host/LowLevel/GenericHIDHost/ConfigDescriptor.c @@ -134,7 +134,7 @@ uint8_t DComp_NextHIDInterface(void* CurrentDescriptor) /* Determine if the current descriptor is an interface descriptor */ if (DESCRIPTOR_TYPE(CurrentDescriptor) == DTYPE_Interface) { - /* Check the HID descriptor class and protocol, break out if correct class/protocol interface found */ + /* Check the HID descriptor class, break out if correct class/protocol interface found */ if (DESCRIPTOR_CAST(CurrentDescriptor, USB_Descriptor_Interface_t).Class == HID_CLASS) { /* Indicate that the descriptor being searched for has been found */ diff --git a/LUFA/Drivers/USB/Class/Device/CDC.c b/LUFA/Drivers/USB/Class/Device/CDC.c index cc8161e0b6..fc6ea93fb9 100644 --- a/LUFA/Drivers/USB/Class/Device/CDC.c +++ b/LUFA/Drivers/USB/Class/Device/CDC.c @@ -74,7 +74,7 @@ void CDC_Device_ProcessControlPacket(USB_ClassInfo_CDC_Device_t* CDCInterfaceInf { Endpoint_ClearSETUP(); - CDCInterfaceInfo->State.ControlLineState = USB_ControlRequest.wValue; + CDCInterfaceInfo->State.ControlLineStates.HostToDevice = USB_ControlRequest.wValue; EVENT_CDC_Device_ControLineStateChanged(CDCInterfaceInfo); @@ -178,7 +178,7 @@ uint8_t CDC_Device_ReceiveByte(USB_ClassInfo_CDC_Device_t* CDCInterfaceInfo) return DataByte; } -void CDC_Device_SendControlLineStateChange(USB_ClassInfo_CDC_Device_t* CDCInterfaceInfo, uint16_t LineStateMask) +void CDC_Device_SendControlLineStateChange(USB_ClassInfo_CDC_Device_t* CDCInterfaceInfo) { if (!(USB_IsConnected)) return; @@ -195,7 +195,7 @@ void CDC_Device_SendControlLineStateChange(USB_ClassInfo_CDC_Device_t* CDCInterf }; Endpoint_Write_Stream_LE(&Notification, sizeof(Notification), NO_STREAM_CALLBACK); - Endpoint_Write_Stream_LE(&LineStateMask, sizeof(LineStateMask), NO_STREAM_CALLBACK); + Endpoint_Write_Stream_LE(&CDCInterfaceInfo->State.ControlLineStates.DeviceToHost, sizeof(uint8_t), NO_STREAM_CALLBACK); Endpoint_ClearIN(); } diff --git a/LUFA/Drivers/USB/Class/Device/CDC.h b/LUFA/Drivers/USB/Class/Device/CDC.h index 13fa8d3b78..bcf8ff7b9e 100644 --- a/LUFA/Drivers/USB/Class/Device/CDC.h +++ b/LUFA/Drivers/USB/Class/Device/CDC.h @@ -71,7 +71,15 @@ /** Current State information structure for \ref USB_ClassInfo_CDC_Device_t CDC device interface structures. */ typedef struct { - uint8_t ControlLineState; /**< Current control line states, as set by the host */ + struct + { + uint8_t HostToDevice; /**< Control line states from the host to device, as a set of CDC_CONTROL_LINE_OUT_* + * masks. + */ + uint8_t DeviceToHost; /**< Control line states from the device to host, as a set of CDC_CONTROL_LINE_IN_* + * masks. + */ + } ControlLineStates; struct { @@ -143,8 +151,8 @@ /** CDC class driver event for a control line state change on a CDC interface. This event fires each time the host requests a * control line state change (containing the virtual serial control line states, such as DTR) and may be hooked in the * user program by declaring a handler function with the same name and parameters listed here. The new control line states - * are available in the ControlLineState value inside the CDC interface structure passed as a parameter, set as a mask of - * CDC_CONTROL_LINE_OUT_* masks. + * are available in the ControlLineStates.HostToDevice value inside the CDC interface structure passed as a parameter, set as + * a mask of CDC_CONTROL_LINE_OUT_* masks. * * \param CDCInterfaceInfo Pointer to a structure containing a CDC Class configuration and state. */ @@ -185,14 +193,14 @@ */ uint8_t CDC_Device_ReceiveByte(USB_ClassInfo_CDC_Device_t* CDCInterfaceInfo); - /** Sends a Serial Control Line State Change notification to the host. This should be called when the virtual serial control - * lines (DCD, DSR, etc.) have changed states, or to give BREAK notfications to the host. Line states persist until they are - * cleared via a second notification. + /** Sends a Serial Control Line State Change notification to the host. This should be called when the virtual serial + * control lines (DCD, DSR, etc.) have changed states, or to give BREAK notfications to the host. Line states persist + * until they are cleared via a second notification. This should be called each time the CDC class driver's + * ControlLineStates.DeviceToHost value is updated to push the new states to the USB host. * * \param CDCInterfaceInfo Pointer to a structure containing a CDC Class configuration and state. - * \param LineStateMask Mask of CDC_CONTROL_LINE_IN_* masks giving the current control line states */ - void CDC_Device_SendControlLineStateChange(USB_ClassInfo_CDC_Device_t* CDCInterfaceInfo, uint16_t LineStateMask); + void CDC_Device_SendControlLineStateChange(USB_ClassInfo_CDC_Device_t* CDCInterfaceInfo); /* Private Interface - For use in library only: */ #if !defined(__DOXYGEN__) diff --git a/LUFA/Drivers/USB/Class/Host/CDC.c b/LUFA/Drivers/USB/Class/Host/CDC.c index aef5b3539d..2d5ec0fb26 100644 --- a/LUFA/Drivers/USB/Class/Host/CDC.c +++ b/LUFA/Drivers/USB/Class/Host/CDC.c @@ -71,7 +71,6 @@ static uint8_t CDC_Host_ProcessConfigDescriptor(USB_ClassInfo_CDC_Host_t* CDCInt if (USB_GetNextDescriptorComp(&ConfigDescriptorSize, &ConfigDescriptorData, DComp_CDC_Host_NextCDCDataInterface) != DESCRIPTOR_SEARCH_COMP_Found) { - /* Descriptor not found, error out */ return CDC_ENUMERROR_NoCDCInterfaceFound; } } @@ -149,10 +148,12 @@ static uint8_t DComp_CDC_Host_NextCDCControlInterface(void* CurrentDescriptor) { if (DESCRIPTOR_TYPE(CurrentDescriptor) == DTYPE_Interface) { - /* Check the CDC descriptor class, subclass and protocol, break out if correct control interface found */ - if ((DESCRIPTOR_CAST(CurrentDescriptor, USB_Descriptor_Interface_t).Class == CDC_CONTROL_CLASS) && - (DESCRIPTOR_CAST(CurrentDescriptor, USB_Descriptor_Interface_t).SubClass == CDC_CONTROL_SUBCLASS) && - (DESCRIPTOR_CAST(CurrentDescriptor, USB_Descriptor_Interface_t).Protocol == CDC_CONTROL_PROTOCOL)) + USB_Descriptor_Interface_t* CurrentInterface = DESCRIPTOR_PCAST(CurrentDescriptor, + USB_Descriptor_Interface_t); + + if ((CurrentInterface->Class == CDC_CONTROL_CLASS) && + (CurrentInterface->SubClass == CDC_CONTROL_SUBCLASS) && + (CurrentInterface->Protocol == CDC_CONTROL_PROTOCOL)) { return DESCRIPTOR_SEARCH_Found; } @@ -165,10 +166,12 @@ static uint8_t DComp_CDC_Host_NextCDCDataInterface(void* CurrentDescriptor) { if (DESCRIPTOR_TYPE(CurrentDescriptor) == DTYPE_Interface) { - /* Check the CDC descriptor class, subclass and protocol, break out if correct data interface found */ - if ((DESCRIPTOR_CAST(CurrentDescriptor, USB_Descriptor_Interface_t).Class == CDC_DATA_CLASS) && - (DESCRIPTOR_CAST(CurrentDescriptor, USB_Descriptor_Interface_t).SubClass == CDC_DATA_SUBCLASS) && - (DESCRIPTOR_CAST(CurrentDescriptor, USB_Descriptor_Interface_t).Protocol == CDC_DATA_PROTOCOL)) + USB_Descriptor_Interface_t* CurrentInterface = DESCRIPTOR_PCAST(CurrentDescriptor, + USB_Descriptor_Interface_t); + + if ((CurrentInterface->Class == CDC_DATA_CLASS) && + (CurrentInterface->SubClass == CDC_DATA_SUBCLASS) && + (CurrentInterface->Protocol == CDC_DATA_PROTOCOL)) { return DESCRIPTOR_SEARCH_Found; } @@ -181,8 +184,10 @@ static uint8_t DComp_CDC_Host_NextInterfaceCDCDataEndpoint(void* CurrentDescript { if (DESCRIPTOR_TYPE(CurrentDescriptor) == DTYPE_Endpoint) { - uint8_t EndpointType = (DESCRIPTOR_CAST(CurrentDescriptor, - USB_Descriptor_Endpoint_t).Attributes & EP_TYPE_MASK); + USB_Descriptor_Endpoint_t* CurrentEndpoint = DESCRIPTOR_PCAST(CurrentDescriptor, + USB_Descriptor_Endpoint_t) + + uint8_t EndpointType = (CurrentEndpoint->Attributes & EP_TYPE_MASK); if ((EndpointType == EP_TYPE_BULK) || (EndpointType == EP_TYPE_INTERRUPT)) return DESCRIPTOR_SEARCH_Found; @@ -215,7 +220,6 @@ void CDC_Host_USBTask(USB_ClassInfo_CDC_Host_t* CDCInterfaceInfo) USB_HostState = HOST_STATE_Configured; break; case HOST_STATE_Configured: - USB_HostState = HOST_STATE_Ready; break; } diff --git a/LUFA/Drivers/USB/Class/Host/CDC.h b/LUFA/Drivers/USB/Class/Host/CDC.h index 859c41d57a..4054cf117b 100644 --- a/LUFA/Drivers/USB/Class/Host/CDC.h +++ b/LUFA/Drivers/USB/Class/Host/CDC.h @@ -68,8 +68,16 @@ uint16_t DataOUTPipeSize; /**< Size in bytes of the CDC interface's OUT data pipe */ uint16_t NotificationPipeSize; /**< Size in bytes of the CDC interface's IN notification endpoint, if used */ - uint8_t ControlLineState; /**< Current control line states */ - + struct + { + uint8_t HostToDevice; /**< Control line states from the host to device, as a set of CDC_CONTROL_LINE_OUT_* + * masks. + */ + uint8_t DeviceToHost; /**< Control line states from the device to host, as a set of CDC_CONTROL_LINE_IN_* + * masks. + */ + } ControlLineStates; + struct { uint32_t BaudRateBPS; /**< Baud rate of the virtual serial port, in bits per second */ @@ -80,7 +88,7 @@ * CDCDevice_LineCodingParity_t enum */ uint8_t DataBits; /**< Bits of data per character of the virtual serial port */ - } LineEncoding; + } LineEncoding; } USB_ClassInfo_CDC_Host_State_t; /** Class state structure. An instance of this structure should be made within the user application, @@ -138,6 +146,16 @@ static uint8_t DComp_CDC_Host_NextCDCDataInterface(void* CurrentDescriptor); static uint8_t DComp_CDC_Host_NextInterfaceCDCDataEndpoint(void* CurrentDescriptor); #endif + + void EVENT_CDC_Host_ControLineStateChanged(USB_ClassInfo_CDC_Host_t* CDCInterfaceInfo); + + uint8_t CDC_Host_SetLineEncoding(USB_ClassInfo_CDC_Host_t* CDCInterfaceInfo); + uint8_t CDC_Host_SendControlLineStateChange(USB_ClassInfo_CDC_Host_t* CDCInterfaceInfo); + + void CDC_Host_SendString(USB_ClassInfo_CDC_Host_t* CDCInterfaceInfo, char* Data, uint16_t Length); + void CDC_Host_SendByte(USB_ClassInfo_CDC_Host_t* CDCInterfaceInfo, uint8_t Data); + uint16_t CDC_Host_BytesReceived(USB_ClassInfo_CDC_Host_t* CDCInterfaceInfo); + uint8_t CDC_Host_ReceiveByte(USB_ClassInfo_CDC_Host_t* CDCInterfaceInfo); #endif diff --git a/LUFA/Drivers/USB/HighLevel/StdDescriptors.h b/LUFA/Drivers/USB/HighLevel/StdDescriptors.h index 7a0ca52b34..2ce75b2d9f 100644 --- a/LUFA/Drivers/USB/HighLevel/StdDescriptors.h +++ b/LUFA/Drivers/USB/HighLevel/StdDescriptors.h @@ -244,7 +244,13 @@ */ uint8_t SerialNumStrIndex; /**< String index for the product's globally unique hexadecimal * serial number, in uppercase Unicode ASCII. - * + * + * \note On some AVR models, there is an embedded serial number + * in the chip which can be used for the device serial number. + * To use this serial number, define USE_INTERNAL_SERIAL to a + * unique string index number in the project makefile and set + * this value to USE_INTERNAL_SERIAL. + * * \see ManufacturerStrIndex structure entry. */ diff --git a/LUFA/Drivers/USB/LowLevel/DevChapter9.c b/LUFA/Drivers/USB/LowLevel/DevChapter9.c index 829bd13e82..9acf300996 100644 --- a/LUFA/Drivers/USB/LowLevel/DevChapter9.c +++ b/LUFA/Drivers/USB/LowLevel/DevChapter9.c @@ -179,6 +179,36 @@ static void USB_Device_GetDescriptor(void) void* DescriptorPointer; uint16_t DescriptorSize; + #if defined(USE_INTERNAL_SERIAL) + if (USB_ControlRequest.wValue == ((DTYPE_String << 8) | USE_INTERNAL_SERIAL)) + { + uint8_t SignatureDescriptor[2 + (sizeof(int16_t) * 20)]; + + SignatureDescriptor[0] = sizeof(SignatureDescriptor); + SignatureDescriptor[1] = DTYPE_String; + + uint16_t* SigUnicodeChars = (uint16_t*)&SignatureDescriptor[2]; + + for (uint8_t SerialByteNum = 0; SerialByteNum < 10; SerialByteNum++) + { + char ConvSigString[3]; + + itoa(boot_signature_byte_get(0x0E + SerialByteNum), ConvSigString, 16); + + SigUnicodeChars[0] = toupper(ConvSigString[0]); + SigUnicodeChars[1] = toupper(ConvSigString[1]); + + SigUnicodeChars += 2; + } + + Endpoint_ClearSETUP(); + Endpoint_Write_Control_Stream_LE(SignatureDescriptor, sizeof(SignatureDescriptor)); + Endpoint_ClearOUT(); + + return; + } + #endif + if ((DescriptorSize = CALLBACK_USB_GetDescriptor(USB_ControlRequest.wValue, USB_ControlRequest.wIndex, &DescriptorPointer)) == NO_DESCRIPTOR) { @@ -186,7 +216,7 @@ static void USB_Device_GetDescriptor(void) } Endpoint_ClearSETUP(); - + #if defined(USE_RAM_DESCRIPTORS) Endpoint_Write_Control_Stream_LE(DescriptorPointer, DescriptorSize); #else diff --git a/LUFA/Drivers/USB/LowLevel/DevChapter9.h b/LUFA/Drivers/USB/LowLevel/DevChapter9.h index 68cb2c448d..65fa0418cd 100644 --- a/LUFA/Drivers/USB/LowLevel/DevChapter9.h +++ b/LUFA/Drivers/USB/LowLevel/DevChapter9.h @@ -35,6 +35,9 @@ #include #include #include + #include + #include + #include #include "../HighLevel/StdDescriptors.h" #include "../HighLevel/Events.h" @@ -42,6 +45,15 @@ #include "../HighLevel/USBTask.h" #include "LowLevel.h" + /* Preprocessor Checks: */ + #if defined(USE_INTERNAL_SERIAL) && !(defined(USB_SERIES_6_AVR) || defined(USB_SERIES_7_AVR)) + #error USE_INTERNAL_SERIAL invalid, the selected AVR model does not contain unique serial bytes. + #endif + + #if defined(USE_INTERNAL_SERIAL) && (USE_INTERNAL_SERIAL <= 1) + #error USE_INTERNAL_SERIAL must be defined to the string descriptor index chosen for the serial number descriptor. + #endif + /* Enable C linkage for C++ Compilers: */ #if defined(__cplusplus) extern "C" { diff --git a/LUFA/ManPages/ChangeLog.txt b/LUFA/ManPages/ChangeLog.txt index 464418140f..f7ae4c664a 100644 --- a/LUFA/ManPages/ChangeLog.txt +++ b/LUFA/ManPages/ChangeLog.txt @@ -8,7 +8,7 @@ * * \section Sec_ChangeLogXXXXXX Version XXXXXX * - * - Removed psuedo-scheduler, dynamic memory block allocator from the library (no longer needed and not used respectively) + * - Deprecated psuedo-scheduler and removed dynamic memory allocator from the library (first no longer needed and second unused) * - Added new class drivers and matching demos to the library for rapid application development * - Added incomplete device and host mode demos for later enhancement * - Changed bootloaders to use FLASHEND rather than the existence of RAMPZ to determine if far FLASH pointers are needed @@ -28,6 +28,7 @@ * cleared to prevent endpoint type corruption * - Fix documentation mentioning Pipe_GetCurrentToken() function when real name is Pipe_GetPipeToken() * - Extend USB_GetDeviceConfigDescriptor() routine to require the configuration number within the device to fetch + * - Added new USE_INTERNAL_SERIAL compile time option * * \section Sec_ChangeLog090605 Version 090605 * diff --git a/LUFA/ManPages/CompileTimeTokens.txt b/LUFA/ManPages/CompileTimeTokens.txt index d8a7cf74a7..e1f248d8fb 100644 --- a/LUFA/ManPages/CompileTimeTokens.txt +++ b/LUFA/ManPages/CompileTimeTokens.txt @@ -80,6 +80,14 @@ * compatibility. If this token is defined, the structure element names are switched to the LUFA-specific but more descriptive * names documented in the StdDescriptors.h source file. * + * USE_INTERNAL_SERIAL - ( \ref Group_Descriptors ) \n + * Some AVR models contain a unique 20-digit serial number which can be used as the device serial number, while in device mode. This + * allows the host to uniquely identify the device regardless of if it is moved between USB ports on the same computer, allowing + * allocated resources (such as drivers, COM Port number allocations) to be preserved. To make the library use this value for the + * device's serial number, define this token in the project makefile, set it to a unique string descriptor index (i.e. one not used + * elsewhere in the device for a string descriptor) and set the Device Descriptor's serial number descriptor index entry to the + * USE_INTERNAL_SERIAL value. + * * FIXED_CONTROL_ENDPOINT_SIZE - ( \ref Group_EndpointManagement ) \n * By default, the library determines the size of the control endpoint (when in device mode) by reading the device descriptor. * Normally this reduces the amount of configuration required for the library, allows the value to change dynamically (if diff --git a/LUFA/ManPages/LUFAPoweredProjects.txt b/LUFA/ManPages/LUFAPoweredProjects.txt index b93604e2fc..1f9a317e6a 100644 --- a/LUFA/ManPages/LUFAPoweredProjects.txt +++ b/LUFA/ManPages/LUFAPoweredProjects.txt @@ -13,20 +13,37 @@ * If you have a project that you would like to add to this list, please contact me via the details on the main page of this * documentation. * + * \section Sec_BoardsUsingLUFA AVR-USB Development Boards Using LUFA + * + * The following is a list of known AVR USB development boards, which recommend using LUFA for the USB stack. Some of these + * are open design, and all are available for purchase as completed development boards suitable for project development. + * + * - AVROpendous, an open design/source set of AVR USB development boards: http://avropendous.org/ + * - Teensy and Teensy++, two other AVR USB development boards: http://www.pjrc.com/teensy/index.html + * - USBFoo, an AT90USB162 based development board: http://shop.kernelconcepts.de/product_info.php?products_id=102 + * - USB10 AKA "The Ferret", a AT90USB162 development board: http://www.soc-machines.com + * + * \section Sec_LUFAProjects Projects Using LUFA (Hobbyist) + * + * The following are hobbyist projects using LUFA. Most are open source, and show off interesting ways that the LUFA library + * can be incorporated into many different applications. * - * - Benito #7, an AVR Programmer: http://www.dorkbotpdx.org/blog/feurig/benito_7_the_next_big_thing * - Stripe Snoop, a Magnetic Card reader: http://www.ossguy.com/ss_usb/ - * - USB10 AKA "The Ferret", a USB162 development board: http://www.soc-machines.com + * - Benito #7, an AVR Programmer: http://www.dorkbotpdx.org/blog/feurig/benito_7_the_next_big_thing * - Bicycle POV: http://www.code.google.com/p/bicycleledpov/ - * - Digital Survey Instruments Magnetometer and Pointer: http://www.digitalsurveyinstruments.com/ - * - ARPS Locator: http://la3t.hamradio.no/lab//?id=tracker_en - * - Lightweight CC110x USB dongle for 868MHz Protocols: http://busware.de/tiki-index.php?page=CUL - * - AVROpendous, an open design/source AT90USB162 development board: http://avropendous.org/ * - USB Interface for Playstation Portable Devices: http://forums.ps2dev.org/viewtopic.php?t=11001 * - USB to Serial Bridge, via SPI and I2C: http://www.tty1.net/userial/ - * - Teensy, another tiny AT90USB162 development board: http://www.pjrc.com/teensy/index.html * - SEGA Megadrive/Genesis Development Cartridge: http://www.spritesmind.net/_GenDev/forum/viewtopic.php?t=464 * - CAMTRIG, a remote Camera Trigger device: http://code.astraw.com/projects/motmot/camtrig * - Opendous-JTAG, an open source JTAG device: http://code.google.com/p/opendous-jtag/ * - Openkubus, an open source hardware-based authentication dongle: http://code.google.com/p/openkubus/ + * + * \section Sec_LUFACommercialProjects Projects Using LUFA (Commercial) + * + * The following is a list of known commercial products using LUFA. Some of these are open source, although many are "black-box" + * solutions with no source code given. + * + * - ARPS Locator: http://la3t.hamradio.no/lab//?id=tracker_en + * - Digital Survey Instruments Magnetometer and Pointer: http://www.digitalsurveyinstruments.com/ + * - Lightweight CC110x USB dongle for 868MHz Protocols: http://busware.de/tiki-index.php?page=CUL */ \ No newline at end of file