The prospect of having high speed communication channel between an embedded project and outer world is very tempting. The challenge can be addressed by using a WiFi module in those areas with WiFi coverage being available but what if it is not an option? Then there is no other choice but GSM (also referred as 2G) module which substantially elevates the cost of overall solution and yet it is incapable of providing communication speed high enough for many nowdays needs. For example, prices for GSM Arduino shields currently available at Australian Little Bird Electronics are ranging between $71 and $118! The actual numbers for data exchange rate, however, are not that high and typically are in the vicinity of 9.6 Kbps for 2G networks. On the other hand, there are mobile broadband options like Vodafone USB Extreme 3G+ which can deliver data at up to 42 Mbps (download) and up to 5.76 Mbps (upload). Add to this virtually symbolic price tag for modem – Vodafone 3G+ USB stick would cost you $9(!) and it starts sounding like a fairy tale. The table below represents dramatic difference between the two solutions:
Modem type | Communication speed | Price, AU$ |
---|---|---|
GSM Arduino Shield | 9.6Kbps | 71-118 |
Vodafone K4606 3G HSDPA USB Modem | 42Mbps/5.76Mbps | 9 |
Impressed by the inequality we were wondering whether it would be possible to use one of those USB broadband modems for embedded projects and eventually decided to pick Vodafone 3G+ modem to give it a try.
Acknowledging the fact that USB modems are more demanding to host controller’s resources, we were aware that it would be highly unlikely to make even top end Arduino board talk to the Vodafone dongle. So we turned our attention to ARM based solutions like one of mbed boards and finally chose the one with USB host functionality.
The mbed source code repository even had a fully functional library for interfacing with Vodafone mode; however, we didn’t have high hopes (and this turned to be the case shortly after) that it would immediately work with our modem. But before using USB port a few extra components including USB connector need to be wired. This is described in detail at VodafoneUSMModem Cookbook. Given that we were working on a prototype we took the risk of not using two pulldown resistors and to be honest haven’t noticed any detrimental effects during our experiments. Ignoring the advice to power USB modem separately resulted in spurious resets of mbed board so eventually we derived +5V
from the second USB port. The final prototype ready for experiments is given below.
As a next step, VodafoneUSBModemHTTPClientTest - main.cpp
along with the VodafoneUSBLibrary has been imported into mbed development environment. Subsequently going through all project’s folders and subfolders the underlying set of libraries has been updated to accommodate the latest changes made since the release of VodafoneUSBLibrary as show on the picture below.
An interesting observation: updating the parent folder doesn’t cause children folders to be updated as well – it has to be done manually as shown on the picture below.
Eventually the code got into compilable state. It is fair to say that USB modem requires quite substantial bit of functionality on host controller. It includes USB Host and TCP stacks, PPP client along with CHAP/PAP authentications, DHCP client, TCP/UDP socket implementation, network streams and so on.
Before running the code APN was replaced with Australian Vodafone settings in Main.cpp
int ret = modem.connect("live.vodafone.com");
The compiled code then was uploaded to mbed board for the first time. As expected, nothing worked. There was nothing happening in terminal application so we added one more line in VodafoneUSBModem/core/dbg.h
:
... #ifdef __cplusplus extern "C" { #endif #define __DEBUG__ 4 void debug_init(void); ...
… and immediately got plenty of useful information:
The most interesting output is given below. The driver was trying to detect modem type by discovered device’s VID and PID and obviously it didn’t know anything about our modem model. What has also become obvious after quick analysis of the output and source code was that the driver was designed to retrieve information about USB interfaces and endpoints slightly different depending on the modem type. In addition, upon being powered up, each modem initially works as USB mass storage device allowing to install device drivers and then it is switched to modem mode by a unique byte sequence. So as a bare minimum for our dongle it was necessary to implement its own initialiser and find out its unique combination of bytes to make it act as a modem.
[START] ... [WARN] Module USBHALHost.cpp - Line 351: Device connected!! ... [DBG] Module USBHost.cpp - Line 621: CLASS: 00 VID: 12D1 PID: 1F19 [DBG] Module WANDongle.cpp - Line 195: *initializer=100005e0 [DBG] Module WANDongle.cpp - Line 196: (*initializer)->getSerialVid()=12d1 [DBG] Module WANDongle.cpp - Line 197: (*initializer)->getSerialPid()=14c9 [DBG] Module WANDongle.cpp - Line 195: *initializer=100005f4 [DBG] Module WANDongle.cpp - Line 196: (*initializer)->getSerialVid()=19d2 [DBG] Module WANDongle.cpp - Line 197: (*initializer)->getSerialPid()=1181 [DBG] Module WANDongle.cpp - Line 195: *initializer=10000608 [DBG] Module WANDongle.cpp - Line 196: (*initializer)->getSerialVid()=12d1 [DBG] Module WANDongle.cpp - Line 197: (*initializer)->getSerialPid()=14cf [DBG] Module WANDongle.cpp - Line 195: *initializer=1000061c [DBG] Module WANDongle.cpp - Line 196: (*initializer)->getSerialVid()=12d1 [DBG] Module WANDongle.cpp - Line 197: (*initializer)->getSerialPid()=1506 [DBG] Module WANDongle.cpp - Line 195: *initializer=10000630 [DBG] Module WANDongle.cpp - Line 196: (*initializer)->getSerialVid()=12d1 [DBG] Module WANDongle.cpp - Line 197: (*initializer)->getSerialPid()=1001 [DBG] Module WANDongle.cpp - Line 195: *initializer=10000640 [DBG] Module WANDongle.cpp - Line 196: (*initializer)->getSerialVid()=1546 [DBG] Module WANDongle.cpp - Line 197: (*initializer)->getSerialPid()=1102 ... [DBG] Module USBHost.cpp - Line 645: device enumerated!!!! [DBG] Module WANDongle.cpp - Line 80: Device has VID:12d1 PID:1f19 [DBG] Module VodafoneUSBModem.cpp - Line 527: Trying to connect the dongle ...
Searching through the internet we found that people had already discovered what the magic byte sequence was. They also provided dongles’s PID
and VID
for modem mode:
daxzor:
“The K4606 can also be switched to a regular modem by sending it:
55534243123456780000000000000011060000000000000000000000000000
The new id will be 12d1:1001 so the TargetProduct should be 0x1001”
Then we enhanced WANDongleInitializer.cpp
with a new VodafoneK4606Initializer
class taking K3773 as a base. Apart from just adding new initialiser there was one more problem to mitigate – a Huawei MU509
modem which had the same Vid:Pid
combination in serial mode (12d1:1001
) so after switching to serial mode the driver didn’t keep track on what was discovered before and simply kept picking up wrong initialiser. The issue was addressed by adding m_lastDongle
in WANDongle.cpp
, the modified files are available here. With the two enhancements the output became as shown on the video below:
Newly generated debug output (shown below) suggested that this time the modem was successfully switched to serial mode, correctly detected as K4606 so that its Vid:Pid
pair become 12D1:1001
as expected, then the driver discovered three interfaces but skipped all endpoints that is why when it tried to connect serial ports both endpoints were returned as ‘Ep 00000000’ – means NULL pointers or, in other words, not initialised.
[START] ... [DBG] Module USBHost.cpp - Line 621: CLASS: 00 VID: 12D1 PID: 1F19 [DBG] Module WANDongle.cpp - Line 204: *initializer=100021f4 [DBG] Module WANDongle.cpp - Line 205: (*initializer)->getSerialVid()=12d1 [DBG] Module WANDongle.cpp - Line 206: (*initializer)->getSerialPid()=14c9 [DBG] Module WANDongle.cpp - Line 204: *initializer=10002208 [DBG] Module WANDongle.cpp - Line 205: (*initializer)->getSerialVid()=19d2 [DBG] Module WANDongle.cpp - Line 206: (*initializer)->getSerialPid()=1181 [DBG] Module WANDongle.cpp - Line 204: *initializer=1000221c [DBG] Module WANDongle.cpp - Line 205: (*initializer)->getSerialVid()=12d1 [DBG] Module WANDongle.cpp - Line 206: (*initializer)->getSerialPid()=14cf [DBG] Module WANDongle.cpp - Line 204: *initializer=10002230 [DBG] Module WANDongle.cpp - Line 205: (*initializer)->getSerialVid()=12d1 [DBG] Module WANDongle.cpp - Line 206: (*initializer)->getSerialPid()=1506 [DBG] Module WANDongle.cpp - Line 204: *initializer=10002244 [DBG] Module WANDongle.cpp - Line 205: (*initializer)->getSerialVid()=12d1 [DBG] Module WANDongle.cpp - Line 206: (*initializer)->getSerialPid()=1001 [DBG] Module WANDongle.cpp - Line 204: *initializer=10002254 [DBG] Module WANDongle.cpp - Line 205: (*initializer)->getSerialVid()=1546 [DBG] Module WANDongle.cpp - Line 206: (*initializer)->getSerialPid()=1102 [DBG] Module WANDongle.cpp - Line 204: *initializer=10002268 [DBG] Module WANDongle.cpp - Line 205: (*initializer)->getSerialVid()=12d1 [DBG] Module WANDongle.cpp - Line 206: (*initializer)->getSerialPid()=1001 ... [DBG] Module USBHost.cpp - Line 776: !!!!!!!!!!!!! bulkwrite finished [DBG] Module WANDongle.cpp - Line 120: Switched OK [DBG] Module VodafoneUSBModem.cpp - Line 527: Trying to connect the dongle [DBG] Module WANDongle.cpp - Line 48: Trying to connect device [WARN] Module USBHALHost.cpp - Line 351: Device connected!! ... [DBG] Module USBHost.cpp - Line 621: CLASS: 00 VID: 12D1 PID: 1001 [DBG] Module WANDongle.cpp - Line 198: Initializer has been already detected in previous step ... [DBG] Module WANDongleInitializer.cpp - Line 755: *K4606 parsing intf 0, intf_class 2, m_currentSerialIntf 0 [DBG] Module USBHost.cpp - Line 687: Ep DESC [DBG] Module USBHost.cpp - Line 687: Ep DESC [DBG] Module USBHost.cpp - Line 687: Ep DESC [DBG] Module WANDongleInitializer.cpp - Line 755: *K4606 parsing intf 0, intf_class 2, m_currentSerialIntf 0 [DBG] Module USBHost.cpp - Line 687: Ep DESC [DBG] Module USBHost.cpp - Line 687: Ep DESC [DBG] Module WANDongleInitializer.cpp - Line 755: *K4606 parsing intf 0, intf_class 2, m_currentSerialIntf 0 [DBG] Module USBHost.cpp - Line 687: Ep DESC [DBG] Module USBHost.cpp - Line 687: Ep DESC ... [DBG] Module WANDongle.cpp - Line 81: Device has VID:12d1 PID:1001 [DBG] Module WANDongle.cpp - Line 85: m_pInitializer=10002268 [DBG] Module WANDongle.cpp - Line 86: m_pInitializer->getSerialVid()=12d1 [DBG] Module WANDongle.cpp - Line 87: m_pInitializer->getSerialPid()=1001 [DBG] Module WANDongle.cpp - Line 90: The dongle is in virtual serial mode [DBG] Module WANDongle.cpp - Line 93: Num serial ports: 2 [DBG] Module WANDongle.cpp - Line 101: Connecting serial port #1 [DBG] Module WANDongle.cpp - Line 102: Ep 00000000 [DBG] Module WANDongle.cpp - Line 103: Ep 00000000
At this stage it is, perhaps, worthwhile to give a bit of theory. Normally, a generic modem has just one serial port. However, this modem had two serial ports, each one was represented by one interface, each interface, in its own turn, had two endpoints, inbound and outbound datastreams. One serial port was intended for use by AT commands and the other one was for PPP transport. As we already noticed, this modem had three interfaces, not just two, and most probably the third one was for uploading firmware updates or something like that, the good news was that we wouldn’t need it. So our next task was to decide which two interfaces out of three to pick, which endpoints are for writing data and which are for reading data. Eventually we found (as we thought) the right combination, the changes are highlighted below:
bool VodafoneK4606Initializer::parseInterface(uint8_t intf_nb, uint8_t intf_class, uint8_t intf_subclass, uint8_t intf_protocol) //Must return true if the interface should be parsed { if( m_hasSwitched ) { DBG("*K4606 parsing intf %d, intf_class %d, m_currentSerialIntf %d", intf_nb, intf_class, m_currentSerialIntf); if( intf_class == 0xFF ) { if( (m_currentSerialIntf == 0) || (m_currentSerialIntf == 4) ) { m_currentSerialIntf++; return true; } m_currentSerialIntf++; } } else { if( (intf_nb == 0) && (intf_class == MSD_CLASS) ) { return true; } } return false; }
It also has been noticed that with debug output enabled application kept stopping working at some arbitrary point in time displaying ‘blue lights of death’. With the baudrate ramped up from 9600bps
to 460800bps
the problem went away and never occured again. Here are the changes than needed to be done in order to charge baudrate:
#include "mbed.h" #include "VodafoneUSBModem.h" #include "HTTPClient.h" #define DBG_PORT_BAUD 460800 Serial pc(USBTX, USBRX); void test(void const*) { ... } int main() { pc.baud (DBG_PORT_BAUD); Thread testTask(test, NULL, osPriorityNormal, 1024 * 4); DigitalOut led(LED1); while(1) { led=!led; Thread::wait(1000); } return 0; }
Resulted debug output of the third experiment with correctly parsed and used interfaces and endpoints is given below:
After filtering important lines from the third experiment debug we got the following list. It is clearly seen that interfaces and their endpoints were parsed, both serial ports were successfully opened, modem was initialised, APN was set but for some reasons PPP failed to open. Given that the host controller didn’t receive a single byte in reply to the very first PPP packet there were really two potential reasons for that: either nobody was listening on a remote side or communication channel just didn’t work.
[START] ... [DBG] Module WANDongleInitializer.cpp - Line 717: Vodafone K4606 MSD descriptor found on device 100039b0, intf 0, will now try to switch into serial mode ... [DBG] Module WANDongle.cpp - Line 120: Switched OK ... [DBG] Module WANDongleInitializer.cpp - Line 755: *K4606 parsing intf 0, intf_class 2, m_currentSerialIntf 0 [DBG] Module USBHost.cpp - Line 675: ADD INTF 0 on device 100039b0: class: 2, subclass: 2, proto: 255 ... [DBG] Module USBHost.cpp - Line 311: USBEndpoint created (10003870): type: 2, dir: 2, size: 64, addr: 2 [DBG] Module USBHost.cpp - Line 698: ADD USBEndpoint 10003870, on interf 0 on device 100039b0 [DBG] Module USBHost.cpp - Line 337: New ep 10003870 ... [DBG] Module USBHost.cpp - Line 311: USBEndpoint created (100038b8): type: 2, dir: 1, size: 64, addr: 1 [DBG] Module USBHost.cpp - Line 698: ADD USBEndpoint 100038b8, on interf 0 on device 100039b0 [DBG] Module USBHost.cpp - Line 337: New ep 100038b8 [DBG] Module WANDongleInitializer.cpp - Line 755: *K4606 parsing intf 1, intf_class 2, m_currentSerialIntf 1 ... [DBG] Module WANDongleInitializer.cpp - Line 755: *K4606 parsing intf 1, intf_class 2, m_currentSerialIntf 2 [DBG] Module USBHost.cpp - Line 675: ADD INTF 1 on device 100039b0: class: 2, subclass: 2, proto: 255 ... [DBG] Module USBHost.cpp - Line 311: USBEndpoint created (10003900): type: 2, dir: 2, size: 64, addr: 4 [DBG] Module USBHost.cpp - Line 698: ADD USBEndpoint 10003900, on interf 1 on device 100039b0 [DBG] Module USBHost.cpp - Line 337: New ep 10003900 ... [DBG] Module USBHost.cpp - Line 311: USBEndpoint created (10003948): type: 2, dir: 1, size: 64, addr: 3 [DBG] Module USBHost.cpp - Line 698: ADD USBEndpoint 10003948, on interf 1 on device 100039b0 [DBG] Module USBHost.cpp - Line 337: New ep 10003948 ... [DBG] Module USBHost.cpp - Line 645: device enumerated!!!! [DBG] Module WANDongle.cpp - Line 81: Device has VID:12d1 PID:1001 ... [DBG] Module WANDongle.cpp - Line 90: The dongle is in virtual serial mode [DBG] Module WANDongle.cpp - Line 93: Num serial ports: 2 [DBG] Module WANDongle.cpp - Line 101: Connecting serial port #1 [DBG] Module WANDongle.cpp - Line 102: Ep 10003900 [DBG] Module WANDongle.cpp - Line 103: Ep 10003948 ... [DBG] Module USBHost.cpp - Line 480: Now do queue transfer on ep 10003900 ... [DBG] Module WANDongle.cpp - Line 101: Connecting serial port #2 [DBG] Module WANDongle.cpp - Line 102: Ep 10003870 [DBG] Module WANDongle.cpp - Line 103: Ep 100038b8 ... [DBG] Module USBHost.cpp - Line 480: Now do queue transfer on ep 10003870 ... [DBG] Module VodafoneUSBModem.cpp - Line 531: Great the dongle is connected - I've tried 5 times to connect ... [DBG] Module ATCommandsInterface.cpp - Line 69: AT interface opened [DBG] Module VodafoneUSBModem.cpp - Line 572: Sending initialisation commands [DBG] Module ATCommandsInterface.cpp - Line 77: Sending ATZ E1 V1 ... [DBG] Module ATCommandsInterface.cpp - Line 625: Processing read line [OK] [DBG] Module SMSInterface.cpp - Line 327: AT code is OK ... [DBG] Module ATCommandsInterface.cpp - Line 224: Executing command AT+CNMI=2,1,0,0,0 [DBG] Module ATCommandsInterface.cpp - Line 625: Processing read line [OK] ... [DBG] Module ATCommandsInterface.cpp - Line 224: Executing command AT+CREG? [DBG] Module ATCommandsInterface.cpp - Line 625: Processing read line [+CREG: 2,1,"00F7","04C7AFF2"] [DBG] Module ATCommandsInterface.cpp - Line 677: OK result received ... [DBG] Module ATCommandsInterface.cpp - Line 224: Executing command AT+CGDCONT=1,"IP","live.vodafone.com" [DBG] Module ATCommandsInterface.cpp - Line 625: Processing read line [AT+CGDCONT=1,"IP","live.vodafone.com"] [DBG] Module ATCommandsInterface.cpp - Line 677: OK result received ... [DBG] Module VodafoneUSBModem.cpp - Line 245: APN set to live.vodafone.com [DBG] Module VodafoneUSBModem.cpp - Line 251: Connecting [DBG] Module VodafoneUSBModem.cpp - Line 270: Connecting PPP ... [DBG] Module PPPIPInterface.cpp - Line 239: Sending ATH [DBG] Module PPPIPInterface.cpp - Line 273: Got ATH [DBG] Module PPPIPInterface.cpp - Line 287: Got OK ... [DBG] Module PPPIPInterface.cpp - Line 108: Sending ATD *99# [DBG] Module PPPIPInterface.cpp - Line 134: Got ATD *99# CONNECT ... [DBG] Module PPPIPInterface.cpp - Line 145: Transport link open ... [DBG] Module USBSerialStream.cpp - Line 140: Trying to write 46 chars [DBG] Module USBSerialStream.cpp - Line 50: Trying to read at most 1504 chars ... [DBG] Module USBSerialStream.cpp - Line 140: Trying to write 46 chars ... [DBG] Module USBSerialStream.cpp - Line 140: Trying to write 46 chars ... [DBG] Module USBSerialStream.cpp - Line 140: Trying to write 46 chars ... [DBG] Module USBSerialStream.cpp - Line 140: Trying to write 46 chars ... [DBG] Module USBSerialStream.cpp - Line 140: Trying to write 46 chars ... [DBG] Module USBSerialStream.cpp - Line 140: Trying to write 46 chars ... [DBG] Module USBSerialStream.cpp - Line 140: Trying to write 46 chars ... [DBG] Module USBSerialStream.cpp - Line 151: Writing 46 chars [DBG] Module USBSerialStream.cpp - Line 99: Aborted ... [DBG] Module VodafoneUSBModem.cpp - Line 273: Result of connect: Err code=15 Could not connect [DBG] Module USBSerialStream.cpp - Line 94: Timeout [DBG] Module ATCommandsInterface.cpp - Line 344: Nothing read ...
At that point it was really unclear why PPP was unable to start so we decided to plug the modem into Linux machine and using Wireshark sniff USB traffic in order to find what we were doing differently. We didn’t realise how much there was yet to be discovered and how simple the final change would be.
USB traffic captured by Wireshark is shown below. The dongle was plugged into USB port shortly after the start of the capturing application. The modem successfully connected to the Internet and Kubuntu recognised the hardware without need to install drivers or doing something manually. However, two strange things have been discovered. First of all, from OS point of view, the hardware was not a modem but rather a wired Enternet adapter. Secondly, after filtering and analysing USB packets it turned out that the very last packet when Kubuntu was talking to the dongle as to a Mass Storage Device (MSD) was a byte sequence… slightly different from the one we used in order to switch the dongle to modem mode. By the look of things, the dongle could pretend to be not only Mass Storage Device or modem but also as an Ethernet adapter. And if so, we needed to use the other byte sequence in order to force the dongle to switch to modem mode and only then try to establish Internet connection again.
What was happening on a Linux machine after plugging in Vodafone modem with default settings is shown in video below. lsusb -t
command indicated that the dongle was recognised as 'cdc_ether'
. Shortly after ifconfig
displayed one more additional Ethernet interface eth1
. dmesg
suggested that after mode switch the dongle had Vid:Pid
combination set to 12d1:157b
.
By forcing the dongle to switch to modem mode result was quite different. lsusb -t
command indicated that the dongle was recognised but no appropriate driver was loaded. No additional interface appeared in the list generated by ifconfig
. dmesg
suggested that after mode switch the dongle had Vid:Pid
combination set to 12d1:1001
(just what we had during our experiments with mbed board!)
Then it took us some time to understand how to load correct driver for modem mode. We were stuck until we found this discussion and an original way of solving the problem:
bmork:
which is good in the sense that it confirms that no driver has captured any of the interfaces. If the modem really has vid:pid = 12d1:1c05, then something *should* happen when you type:Code:
echo 12d1 1c05 >/sys/bus/usb-serial/drivers/option1/new_idIf you have Linux v3.5 or later, then you can verify the list of dynamically added IDs by doing
Code:
cat /sys/bus/usb-serial/drivers/option1/new_id
Finally Linux loaded the driver and assigned three interfaces to ttyUSB0
, ttyUSB1
and ttyUSB2
. After that the only remaining part was to configuire wvdial
and run it watching how PPP
interface was being established. Then by opening new terminal it became obvious that ifconfig
reported one additional ppp0
network interface and the Internet connection was working again, this time via the dongle in modem mode.
Remarkably, that wvdial
used only one ttyUSB0
port for AT commands and PPP transport. Suddenly we were stunned – what if iterfaces for AT and PPP are not interchangeable and we inadvertently swapped them, could it explain why our mbed board kept failing to open PPP? All we needed to do was to replace just one line with the following content:
USBEndpoint* VodafoneK4606Initializer::getEp(USBDeviceConnected* pDev, int serialPortNumber, bool tx) { return pDev->getEndpoint(serialPortNumber, BULK_ENDPOINT, tx?OUT:IN, 0); }
Before compiling the code we disabled all debug output but from ATCommandsInterface.cpp
to make it less extensive…and modem successfully connected to the Internet! The was one more thing to look at as HTTPClient failed to get expected results from both GET and POST HTTP requests but regardless the link was up!
After disabling debug output from ATCommandsInterface.cpp
and enabling it from HTTPClient.cpp
we got the following result:
There were actually two issues with HTTPClient.cpp
– first of all, it didn’t process correctly HTTP redirects and in our case http://mbed.org/media/uploads/donatien/hello.txt doesn’t exist anymore instead our request gets redirected to http:///developer.mbed.org/media/uploads/donatien/hello.txt so there would be a trivial change in main.cpp
:
void test(void const*) { ... //GET data printf("Trying to fetch page...\n"); ret = http.get("http://developer.mbed.org/media/uploads/donatien/hello.txt", str, 128); ... }
And secondly, there was a limit for maximum HTTP header length in HTTPClient.cpp
which was set to 32
while some header values received in our experiment were much longer. Strictly speaking the logic should be modified to avoid using intermediate buffer for parsing header’s values and therefore become bullet proof should we encounter super long headers. In fact, the whole HTTPClient had to be redesigned. But in our case for simplicity sake we just added extra definition which replaced all the hardcoded values for buffer length:
#define KEY_VAL_BUF_SIZE 60
…and run the code again:
… and finally, it worked! At that stage we had broadband connectivity ready for real projects.
Downloads:
1. VodafoneUSBModem Libarary with K4606 support;
Conclusion. This article was intended to serve as a guide to help in solving similar challenges with interfacing embedded projects and broadband modems. We hope that someone may find this information useful and worth reading.