Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
GNU/Linux Rapid Embedded Programming

You're reading from   GNU/Linux Rapid Embedded Programming Your one-stop solution to embedded programming on GNU/Linux

Arrow left icon
Product type Paperback
Published in Mar 2017
Publisher Packt
ISBN-13 9781786461803
Length 732 pages
Edition 1st Edition
Languages
Arrow right icon
Author (1):
Arrow left icon
Rodolfo Giometti Rodolfo Giometti
Author Profile Icon Rodolfo Giometti
Rodolfo Giometti
Arrow right icon
View More author details
Toc

Table of Contents (20) Chapters Close

Preface 1. Installing the Developing System FREE CHAPTER 2. Managing the System Console 3. C Compiler, Device Drivers, and Useful Developing Techniques 4. Quick Programming with Scripts and System Daemons 5. Setting Up an Embedded OS 6. General Purposes Input Output signals – GPIO 7. Serial Ports and TTY Devices - TTY 8. Universal Serial Bus - USB 9. Inter-Integrated Circuits - I2C 10. Serial Peripheral Interface - SPI 11. 1-Wire - W1 12. Ethernet Network Device - ETH 13. Wireless Network Device - WLAN 14. Controller Area Network - CAN 15. Sound Devices - SND 16. Video devices - V4L 17. Analog-to-Digital Converters - ADC 18. Pulse-Width Modulation - PWM 19. Miscellaneous Devices

Setting up the developing system

Before ending the chapter, let's review each board in order to verify that our newly created operating systems have whatever we need to proceed further in the book.

BeagleBone Black - USB, networking, and overlays

As soon as we log in to our new system, we see that the prompt looks like this:

root@arm:~#

Maybe, we can customize it a bit by changing the hostname from the generic string arm into a more appropriate bbb (which stands for BeagleBone Black). The commands to do the job are shown here:

root@arm:~# echo bbb > /etc/hostname
root@arm:~# sed -i -e's/\<arm\>/bbb/g' /etc/hosts

Now, we have to reboot the system using the classic reboot command, and at the next login, we should get a welcome message:

Debian GNU/Linux 8 bbb ttyS0
default username:password is [debian:temppwd]
bbb login:

After the login, we will get the new prompt:

root@bbb:~#

Then, we will update the distribution repositories and install the aptitude tool as done for the host machine:

root@bbb:~# apt-get update
root@bbb:~# apt-get install aptitude

OK, now it is time to add a useful feature, that is, the possibility to establish a virtual Ethernet connection between our BeagleBone Black and the host PC over the USB cable connected with BeagleBone Black's USB device port and the host. To do this, we have first to install the udhcpd package using the following command:

root@bbb:~# aptitude install udhcpd

Then, we must add the following lines to the /etc/network/interfaces file:

allow-hotplug usb0 
iface usb0 inet static 
        address 192.168.7.2 
        netmask 255.255.255.252 
        network 192.168.7.0 

Note

Don't forget the tab character to indent the lines!

Then, restart the networking system as follows:

root@bbb:~# /etc/init.d/networking restart

IPv6: ADDRCONF(NETDEV_CHANGE): Black, we should see a message, as shown here, on the serial console:

g_ether gadget: high-speed config #1: CDC Ethernet (ECM)

IPv6: ADDRCONF(NETDEV_CHANGE): usb0: link becomes ready

A new Ethernet device should appear as reported here:

root@bbb:~# ifconfig usb0
usb0      Link encap:Ethernet  HWaddr 78:a5:04:ca:c9:f1  
         inet addr:192.168.7.2  Bcast:192.168.7.3  Mask:255.255.255.252
       inet6 addr: fe80::7aa5:4ff:feca:c9f1/64 Scope:Link
      UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
      RX packets:46 errors:0 dropped:0 overruns:0 frame:0
      TX packets:32 errors:0 dropped:0 overruns:0 carrier:0
       collisions:0 txqueuelen:1000
       RX bytes:7542 (7.3 KiB)  TX bytes:5525 (5.3 KiB)

OK, now, we have to configure the new Ethernet device on the host and then we can try an ssh connection as shown here:

$ ssh root@192.168.7.2

Note

On my host PC, which is Ubuntu based, before executing the ssh command, we had to properly configure the new Ethernet device by adding a new network connection in the entry Edit Connections... in the system settings menu.

The authenticity of host '192.168.7.2 (192.168.7.2)' can't be establis
hed.
ECDSA key fingerprint is SHA256:Iu23gb49VFKsFs+HMwjza1OzcpzRL/zxFxjFpF
EiDsg.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.7.2' (ECDSA) to the list of known 
hosts.
root@192.168.7.2's password:

Now we have to enter the root's password that is root and the trick is done:

The programs included with the Debian GNU/Linux system are free
softwa
re; 
the exact distribution terms for each program are
described in the 
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the
extent 
permitted by applicable law.
Last login: Sat Apr 2 18:28:44 2016
root@bbb:~#

Tip

We may need to modify the ssh daemon configuration if we cannot successfully log in to our system. In fact, the login by the root user may be disabled for security reasons. To enable the login, we have to modify the /etc/ssh/sshd_config file as follows:

    --- /etc/ssh/sshd_config.orig 2016-04-02 18:40:31.    120000086 +0000    +++ /etc/ssh/sshd_config 2016-04-02 18:40:46.05000    0088 +0000    @@ -25,7 +25,7 @@     # Authentication:     LoginGraceTime 120    -PermitRootLogin without-password    +PermitRootLogin yes     StrictModes yes     RSAAuthentication yes

Then, we have to restart the daemon using the following command:

    root@bbb:~# /etc/init.d/ssh restart     Restarting ssh (via systemctl): ssh.service.

Now, we have to install the overlay system, that is, the mechanism that allow us to load at run time a part of a new device tree binaries and then change our kernel settings to get access to the board's peripherals (this mechanism will be used in the upcoming chapters, and it'll be more clear to you when we'll start using it).

To install the overlay mechanism, we must clone its repository into our BeagleBone Black as follows:

bbb@arm:~# git clone https://github.com/beagleboard/bb.org-overlays

Then, we must update the device tree compiler (the dtc command) with the following commands:

root@arm:~# cd bb.org-overlays/
root@arm:~/bb.org-overlays# ./dtc-overlay.sh

Tip

This command may take a while to complete. Be patient.

Installing: bison build-essential flex
Get:1 http://repos.rcn-ee.com jessie InRelease [4,347 B]                       
Get:2 http://repos.rcn-ee.com jessie/main armhf Packages [370 kB]              
...
Installing into: /usr/local/bin/
       CHK version_gen.h
     INSTALL-BIN
    INSTALL-LIB
    INSTALL-INC
dtc: Version: DTC 1.4.1-g1e75ebc9

Then, we can install the dtbo files with the following command:

root@arm:~/bb.org-overlays# ./install.sh
 CLEAN   src/arm
  DTC     src/arm/BB-BONE-WTHR-01-00B0.dtbo
  DTC     src/arm/BB-BONE-LCD3-01-00A2.dtbo
  DTC     src/arm/BB-PWM2-00A0.dtbo
...
'src/arm/univ-hdmi-00A0.dtbo' -> '/lib/firmware/univ-hdmi-00A0.dtbo'
'src/arm/univ-nhdmi-00A0.dtbo' -> '/lib/firmware/univ-nhdmi-00A0.dtbo'
update-initramfs: Generating /boot/initrd.img-4.4.7-bone9
cape overlays have been built and added to /lib/firmware & /boot/initr
d.img-4.4.
7-bone9, please reboot

OK, now, we can safely reboot the system to test it.

After reboot, to display the current overlay configuration, we can use the following cat command:

root@arm:~# cat /sys/devices/platform/bone_capemgr/slots
0: PF----  -1
1: PF----  -1
2: PF----  -1
3: PF----  -1

Then, we can try to enable the second SPI bus char device using the following command:

root@bbb:~# echo BB-SPIDEV1 > /sys/devices/platform/bone_capemgr/slots
bone_capemgr bone_capemgr: part_number 'BB-SPIDEV1', version 'N/A'
bone_capemgr bone_capemgr: slot #4: override
bone_capemgr bone_capemgr: Using override eeprom data at slot 4
bone_capemgr bone_capemgr: slot #4: 'Override Board Name,00A0,Ove
rride
 Manuf,BB-SPIDEV1'
bone_capemgr bone_capemgr: slot #4: dtbo 'BB-SPIDEV1-00A0.dtbo' 
loaded; overlay id#0

Now, two new char devices should appear in the /dev directory:

root@bbb:~# ls -l /dev/spidev*
crw-rw---- 1 root spi 153, 0 Apr  2 19:27 /dev/spidev2.0
crw-rw---- 1 root spi 153, 1 Apr  2 19:27 /dev/spidev2.1

Also, the slots file in sysfs is updated accordingly:

root@bbb:~# cat /sys/devices/platform/bone_capemgr/slots
0: PF----  -1
 1: PF----  -1
 2: PF----  -1
 3: PF----  -1
 4: P-O-L-   0 Override Board Name,00A0,Override Manuf,BB-SPIDEV1

Now, everything should be in place. However, as the last step, we can decide to copy our new system from the microSD card to eMMC in order to boot directly from the on-board eMMC device, thus avoiding pressing the user button each time we power up the board.

To do this, we have to install three new packages (initramfs-tools, dosfstools, and rsync) and then use a script form the Robert C. Nelson archive:

root@bbb:~# wget https://raw.githubusercontent.com/RobertCNelson/boot-
scripts/master/tools/eMMC/bbb-eMMC-flasher-eewiki-ext4.sh

Then, we just need to execute it using the following two commands, and the BeagleBone Black will start rewriting the eMMC contents:

root@bbb:~# chmod +x bbb-eMMC-flasher-eewiki-ext4.sh
root@bbb:~# /bin/bash ./bbb-eMMC-flasher-eewiki-ext4.sh

SAMA5D3 Xplained - USB and networking

Even for the SAMA5D3 Xplained, we can have a pretty prompt. So, let's change it as done for the BeagleBone Black:

root@arm:~# echo a5d3 > /etc/hostname                                           
root@arm:~# sed -i -e's/\<arm\>/a5d3/g' /etc/hosts                              

Now, we can reboot, and we should get a new welcome message as shown here:

Debian GNU/Linux 8 a5d3 ttyS0
default username:password is [debian:temppwd]
a5d3 login:

Then, we will update the distribution repositories and install the aptitude tool as done for the host machine:

root@a5d3:~# apt-get update
root@a5d3:~# apt-get install aptitude

OK, now, we can try to replicate BeagleBone Black's configuration, allowing an ssh connection via the USB device port. However, this time, we'll do more. In fact, we will install two kinds of different virtual connections over the USB cable: an Ethernet and a serial connection.

Note

This configuration can be done on the BeagleBone Black too.

To do this, we need the USB gadget driver named CDC Composite Device (Ethernet and ACM) (see the kernel configuration settings done earlier for the SAMA5D3 Xplained):

root@a5d3:~# modprobe g_cdc host_addr=78:A5:04:CA:CB:01

The kernel messages we should see on the serial console are reported here, and they show that we have two new devices now:

using random self ethernet address
using random host ethernet address
using host ethernet address: 78:A5:04:CA:CB:01
usb0: HOST MAC 78:a5:04:ca:cb:01
usb0: MAC 22:6c:23:f0:10:62
g_cdc gadget: CDC Composite Gadget, version: King Kamehameha Day 2008
g_cdc gadget: g_cdc ready
g_cdc gadget: high-speed config #1: CDC Composite (ECM + ACM)

In fact, now. we should have a new Ethernet device named usb0:

root@a5d3:~# ifconfig usb0
usb0      Link encap:Ethernet  HWaddr da:a0:89:f9:a6:1d  
      BROADCAST MULTICAST  MTU:1500  Metric:1
      RX packets:0 errors:0 dropped:0 overruns:0 frame:0
      TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
      collisions:0 txqueuelen:1000
      RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

And , we should have a new serial port:

root@a5d3:~# ls -l /dev/ttyGS0
crw-rw---- 1 root dialout 250, 0 Apr  2 18:28 /dev/ttyGS0

To force this new setting at every boot, we can add the kernel module name to the auto-loading module system:

root@a5d3:~# echo "g_cdc" >> /etc/modules-load.d/modules.conf
root@a5d3:~# echo "options g_cdc host_addr=78:A5:04:CA:CB:01" >>
                  /etc/modprobe.d/modules.conf

Note

The host_addr parameter is needed to allow the host PC to recognize our device and then correctly configure it by forcing a well-known MAC address each time we start the board.

Then, we can reboot the board, and the kernel module should be already present:

root@a5d3:~# lsmod
Module                  Size  Used by
usb_f_acm               3680  1
u_serial                6214  3 usb_f_acm
usb_f_ecm               4430  1
g_cdc                   2165  0
u_ether                 6869  2 g_cdc,usb_f_ecm
libcomposite           26527  3 g_cdc,usb_f_acm,usb_f_ecm

Good! Now, we can start configuring them.

Regarding the Ethernet device, we can repeat what we did for the BeagleBone Black by adding the following lines to the /etc/network/interfaces file:

allow-hotplug usb0 
iface usb0 inet static 
        address 192.168.8.2 
        netmask 255.255.255.252 
        network 192.168.8.0 

Tip

In order to avoid conflicts with the BeagleBone Black setting, we used the 192.168.8.X subnetwork for this board instead of 192.168.7.X used for the BeagleBone Black.

Then, we've to restart the networking system as follows:

root@a5d3:~# /etc/init.d/networking restart

Tip

As we did earlier, we may need to enable the root login via ssh by modifying the PermitRootLogin setting in the /etc/ssh/sshd_config file and then restarting the daemon.

Then, we have to install the udhcpd daemon as we did earlier and then replace its current configuration in the /etc/udhcpd.conf file with the following settings:

start      192.168.8.1 
end        192.168.8.1 
interface  usb0 
max_leases 1 
option subnet 255.255.255.252 

Tip

We can save the daemon's old configuration with the following command:

    root@a5d3:~# mv /etc/udhcpd.conf                     /etc/udhcpd.conf.orig

Then, we must enable it by setting the DHCPD_ENABLED variable to yes in the /etc/default/udhcpd file. Then, restart the daemon:

root@a5d3:~# /etc/init.d/udhcpd restart

Now, regarding the serial connection, we can add the ability to do a serial login by adding a new getty service on it with the following commands:

root@a5d3:~# systemctl enable getty@ttyGS0.service
Created symlink from /etc/systemd/system/getty.target.wants/getty@ttyG
S0.service to /lib/systemd/system/getty@.service.
root@a5d3:~# systemctl start getty@ttyGS0.service

Now, we only need to add the following lines to the /etc/securetty file in order to allow the root user to login using this new communication channel:

# USB gadget 
ttyGS0 

OK, now, if we take a look at the host PC's kernel messages, we should see something as like this:

usb 1-1: new high-speed USB device number 3 using ehci-pci
usb 1-1: New USB device found, idVendor=0525, idProduct=a4aa
usb 1-1: New USB device strings: Mfr=1, Product=2, SerialNumber=0
usb 1-1: Product: CDC Composite Gadget
usb 1-1: Manufacturer: Linux 4.4.6-sama5-armv7-r5 with atmel_usba_udc
cdc_ether 1-1:1.0 eth0: register 'cdc_ether' at usb-0000:00:0b.0-1, 
CDC Ethernet Device, 78:a5:04:ca:cb:01
cdc_acm 1-1:1.2: ttyACM0: USB ACM device

Then, we can test the networking connection with the ssh command with the following command line on the host PC:

$ ssh root@192.168.8.2
The authenticity of host '192.168.8.2 (192.168.8.2)' can't be establis
hed.
ECDSA key fingerprint is SHA256:OduXLAPIYgNR7Xxh8XbhSum+zOKHBbgv/tnbeD
j2O30.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.8.2' (ECDSA) to the list of known 
hosts.
root@192.168.8.2's password:

Now, enter the root's password that is the root string and the job is done:

The programs included with the Debian GNU/Linux system are free
software;
the exact distribution terms for each program are
described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the
extent
permitted by applicable law.
Last login: Sat Apr  2 18:02:23 2016
root@a5d3:~#

Then, the serial connection can be tested using the minicom command as shown here:

$ sudo minicom  -o -D /dev/ttyACM0
Debian GNU/Linux 8 a5d3 ttyGS0
default username:password is [debian:temppwd]
a5d3 login:

Now, our SAMA5D3 Xplained is ready, and we can step next to the Wandboard.

Wandboard - USB and networking (wired and wireless)

Again, we like to have a pretty prompt. So, let's change it as we did for the BeagleBone Black:

root@arm:~# echo wb > /etc/hostname                                           
root@arm:~# sed -i -e's/\<wb\>/a5d3/g' /etc/hosts                              

Now, we can reboot the system, and we should get a new welcome message as shown here:

Debian GNU/Linux 8 wb ttymxc0
default username:password is [debian:temppwd]
wb login:

Then, we will update the distribution repositories and install the aptitude tool as done for the host machine:

root@wb:~# apt-get update
root@wb:~# apt-get install aptitude

OK, now, we can try to replicate BeagleBone Black's configuration by allowing an ssh connection via the USB device port. So, let's install the udhcpd package using the usual aptitude command:

root@wb:~# aptitude install udhcpd

Then, add the following lines to the /etc/network/interfaces file:

allow-hotplug usb0 
iface usb0 inet static 
        address 192.168.9.2 
        netmask 255.255.255.252 
        network 192.168.9.0 

Note

In order to avoid conflicts with the BeagleBone Black and SAMA5D3 Xplained settings, we used the subnetwork 192.168.9.X for this board instead of 192.168.7.X used for the BeagleBone Black or the 192.168.8.X used for the SAMA5D3 Xplained.

Then, restart the networking system as follows:

root@wb:~# /etc/init.d/networking restart

Note

As we did earlier, we may need to enable the root login via ssh by modifying the PermitRootLogin setting in the /etc/ssh/sshd_config file and then restarting the daemon.

Then, we have to install the udhcpd daemon as we did earlier and then replace its current configuration in /etc/udhcpd.conf file with the following settings:

start      192.168.9.1 
end        192.168.9.1 
interface  usb0 
max_leases 1 
option subnet 255.255.255.252 

Tip

We can save the daemon's old configuration with the following command:

    root@wb:~# mv /etc/udhcpd.conf /etc/udhcpd.conf.orig

Then, we must enable it by setting the DHCPD_ENABLED variable to yes in the /etc/default/udhcpd file. Then, restart the daemon:

root@wb:~# /etc/init.d/udhcpd restart

Now, if we try to connect to the host PC with our Wandboard, we should see the following message on the serial console:

g_ether gadget: high-speed config #1: CDC Ethernet (ECM)
IPv6: ADDRCONF(NETDEV_CHANGE): usb0: link becomes ready

A new Ethernet device should appear as reported here:

root@wb:~# ifconfig usb0
usb0      Link encap:Ethernet  HWaddr 62:1e:f6:88:9b:42  
      inet addr:192.168.9.2  Bcast:192.168.9.3  Mask:255.255.255.252
      inet6 addr: fe80::601e:f6ff:fe88:9b42/64 Scope:Link
      UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
      RX packets:0 errors:0 dropped:0 overruns:0 frame:0
      TX packets:30 errors:0 dropped:0 overruns:0 carrier:0
      collisions:0 txqueuelen:1000
      RX bytes:0 (0.0 B)  TX bytes:5912 (5.7 KiB)

OK, now, we have to configure the new Ethernet device on the host, and then, we can try an ssh connection as shown here:

$ ssh root@192.168.9.2

Tip

On my host PC that is Ubuntu based, before executing the ssh command earlier, we had to properly configure the new Ethernet device by adding a new network connection in the entry Edit Connections... in the system settings menu.

The authenticity of host '192.168.9.2 (192.168.9.2)' can't be establis
hed.
ECDSA key fingerprint is SHA256:Xp2Bf+YOWL0kDSm00GxXw9CY5wH+ECnPzp0EHp
3+GM8.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.9.2' (ECDSA) to the list of known 
hosts.
root@192.168.9.2's password:

Now, enter the root's password that is the root string and the job is done:

The programs included with the Debian GNU/Linux system are free
softwa
re; 
the exact distribution terms for each program are
described in the 
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the
extent 
permitted by applicable law.
Last login: Sat Apr 2 17:45:31 2016
root@wb:~#

Ok now, as last step, we have to set up the on-board Wi-Fi chip. To do this, we need to download the firmware. Here are the commands:

root@wb:~# mkdir -p /lib/firmware/brcm/
root@wb:~# cd /lib/firmware/brcm/
root@wb:/lib/firmware/brcm# wget -c
        https://git.kernel.org/cgit/linux/kernel/git/firmware/linux-fi
rmware.git/plain/brcm/brcmfmac4329-sdio.bin
root@wb:/lib/firmware/brcm# wget -c
        https://git.kernel.org/cgit/linux/kernel/git/firmware/linux-fi
rmware.git/plain/brcm/brcmfmac4330-sdio.bin
root@wb:/lib/firmware/brcm# wget -c
        https://rcn-ee.com/repos/git/meta-fsl-arm-extra/recipes-bsp/br
oadcom-nvram-config/files/wandboard/brcmfmac4329-sdio.txt
root@wb:/lib/firmware/brcm# wget -c
        https://rcn-ee.com/repos/git/meta-fsl-arm-extra/recipes-bsp/br
oadcom-nvram-config/files/wandboard/brcmfmac4330-sdio.txt

Then, we have to reboot the system with the usual reboot command. After reboot, if everything works well, we should see a new interface named wlan0 as shown here:

root@wb:~# ifconfig wlan0
wlan0     Link encap:Ethernet  HWaddr 44:39:c4:9a:96:24  
      BROADCAST MULTICAST  MTU:1500  Metric:1
      RX packets:0 errors:0 dropped:0 overruns:0 frame:0
      TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
      collisions:0 txqueuelen:1000
      RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

Now, we have to verify that it works. So, as the first step, let's try a wireless network scan:

root@wb:~# ifconfig wlan0 0.0.0.0
root@wb:~# iwlist wlan0 scan | grep ESSID
                ESSID:"EnneEnne"

Great, my home network has been recognized!

Note

We may need to connect the external antenna in order to correctly detect all wireless networks around. The external antenna connector is labeled as ANT near the Wi-Fi chip.

For the moment, we can stop the Wi-Fi setup here since it will be restarted later in this book in a proper chapter.

Common settings

Before ending this chapter, let me suggest that you install some basic and common tools we're going to use in this book. We can decide to install these tools now or, when needed, later during the reading of the book.

If we decide to perform this last step and then install these tools right now, we have to connect our boards to the Internet using, for example, an Ethernet cable, and then setting a suitable network configuration for it.

Let me remember that if our embedded kit doesn't automatically take a network configuration and we have a DHCP server in our LAN, we can force this behavior using the dhclient command:

# dhclient eth0

If we don't have a running DHCP service, we can manually set up a network configuration using the ifconfig and route commands as shown here:

# ifconfig eth0 <LOCAL-IP-ADDR>
# route add default gw <GATEWAY-IP-ADDR>

OK, now, to install our tools, we can use the aptitude command again and then wait for the complete installation:

# aptitude install autoconf git subversion make gcc libtool
pkg-config
                   bison build-essential flex
                   strace php5-cli python-pip libpython-dev
You have been reading a chapter from
GNU/Linux Rapid Embedded Programming
Published in: Mar 2017
Publisher: Packt
ISBN-13: 9781786461803
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime
Banner background image