Portenta Hat Carrier User Manual

Learn about the hardware and software features of the Arduino® Portenta Hat Carrier.

Overview

The user manual offers a detailed guide on the Arduino Portenta Hat Carrier, consolidating all its features for easy reference. It will show how to set up, adjust, and assess its main functionalities.

Portenta Hat Carrier Overview

This manual will show the user how to proficiently operate the Portenta Hat Carrier, making it suitable for project developments related to industrial automation, manufacturing automation, robotics, and prototyping.

Hardware and Software Requirements

Hardware Requirements

To use the Portenta Hat Carrier, it is necessary to attach one of the boards from the Portenta Family:

Additionally, the following accessories are needed:

  • USB-C® cable (USB-C® to USB-A cable) (x1)
  • Wi-Fi® Access Point or Ethernet with Internet access (x1)

Software Requirements

If you want to use the Portenta Hat Carrier with a Portenta X8, check the following bullet points:

  • Make sure you have the latest Linux image. Refer to this section to confirm that your Portenta X8 is up-to-date.

To ensure a stable operation of the Portenta Hat Carrier with Portenta X8, the minimum Linux image version required for Portenta X8 is 746. To flash the latest image on your board, you can use the Portenta X8 Out-of-the-box or flash it manually downloading the latest version directly from this link.

In case you want to use the Portenta Hat Carrier with a Portenta H7/C33:

Product Overview

The Portenta Hat Carrier offers a platform for developing a variety of robotics and building automation applications. It provides access to multiple peripherals, including CAN FD, Ethernet, microSD, and USB, as well as a camera interface via MIPI and 8x analog pins. On the other hand, the dedicated debug pins for JTAG and the PWM fan connector help simplify your Portenta applications.

Portenta Hat Carrier Stacked Form

The carrier is adaptable, pairing seamlessly with Portenta X8 and converting it into an industrial Linux platform compatible with Raspberry Pi® Hats. It also works with Portenta H7 and Portenta C33, providing versatile solutions to meet demands across various requirements.

Compatible Portenta boards
Compatible Portenta boards

Carrier Architecture Overview

The Portenta Hat Carrier, designed for Portenta SOM boards like the Portenta X8, H7, and C33, offers a diverse power supply range:

  • 7-32V through its screw terminal
  • USB-C®
  • 5V pin on the 40-pin header

This versatility extends to its connectivity: a USB-A for peripherals, 1000 Mbit Base-T Ethernet, SPI, I2C, I2S, and UART interfaces accessible via a 40-pin male header, and MIPI camera support exclusive for the Portenta X8.

It integrates a microSD slot for storage and data logging, broad interface options through its 40-pin and 16-pin headers, JTAG pins for debugging, and a PWM fan connector for cooling. The Ethernet speed control is intuitive with a two-position DIP switch, allowing various profiles based on the paired Portenta board.

Portenta Hat Carrier board overview
Portenta Hat Carrier board overview

The Portenta Hat Carrier has the following characteristics:

  • Compatible SOM boards: The carrier is compatible with: Portenta X8 (ABX00049), Portenta H7 (ABX00042/ABX00045/ABX00046) and Portenta C33 (ABX00074).

  • Power management: The board can be powered up from different sources. The onboard screw terminal block allows a 7-32 V power supply to power the Portenta board and the carrier, and a 5 V power supply.

    The USB-C® interface of the Portenta X8, H7, and C33 can supply the needed power to the board. Alternatively, the 5V pin from the 40-pin male header can be used to power the board. The carrier can deliver a maximum current of 1.5 A.

  • USB connectivity: A USB-A female connector is used for data logging and the connection of external peripherals like keyboards, mice, hubs, and similar devices.

  • Communication: The carrier supports Ethernet interface (X1) via RJ45 connector 1000 Base-T connected to High-Density pins (J1). If paired with Portenta H7 or C33, the maximum speed is limited to 100 Mbit Ethernet.

    The SPI (X1), I2C (x2), I2S (x1), and UART (x2) are accessible via a 40-pin male header connector. The I2C1 is already dedicated to the EEPROM memory but is accessible through a 40-pin male header connector on SCL2 and SDA2.

    The UARTs do not have flow control, and UART1 and UART3 can be accessed via a 40-pin connector while UART2 can be accessed via a 16-pin connector. The CAN (x1) bus is available with an onboard transceiver. The MIPI camera is also available but only when the Portenta X8 is attached. Examples of compatible devices include the OmniVision OV5647 and the Sony IMX219 sensors.

  • Storage: The board has a microSD card slot for data logging operation and bootloading operation from external memory.

  • Ethernet connectivity: The carrier offers a Gigabit Ethernet interface through an RJ45 connector 1000 Base-T. If the carrier is paired with Portenta H7 or C33, the maximum speed is limited to 100 Mbit Ethernet.

  • 40-pin male header connector: The connector allows for SPI (x1), I2S (x1), SAI (x1), 5V power pin (x2), 3V3 power pin (x2), I2C (x2), UART (x2), PWM pins (x7), GND (x8), and GPIO (X26). The I2C count includes the one that is dedicated to EEPROM. UARTs do not have flow control. The GPIO pins are shared with different functionalities.

  • 16-pin male header connector: The connector allows analog pins (x8), PWM (x2), LICELL (x1), GPIO (x1), 3V3 (x1), GND (x1), serial TX (x1), and serial RX (x1).

  • Screw terminal block: The terminal block allows power supply line feed for the carrier and bus ports. It consists of VIN 7 ~ 32 VDC (x1), VIN 5 V (x1), CANH (x1), CANL (x1), and GND (x2).

  • Debug interface: The carrier features an onboard 10x pin 1.27mm JTAG connector.

  • PWM fan connector: The board has an onboard PWM fan connector (x1) compatible with a 5 V fan with a PWM signal for speed regulation.

  • DIP switch: The carrier features a DIP switch with two positions, allowing for different profiles depending on the paired Portenta board. This DIP switch includes both the ETH CENTER TAP and BTSEL switches.

    The ETH CENTER TAP controls the Ethernet interface. The OFF position enables Ethernet for the Portenta X8. Conversely, the ON position enables Ethernet for the Portenta H7/C33.

    The BTSEL switch can be used to set the Portenta X8 into Flashing Mode when the switch is set to the ON position.

Carrier Topology

Portenta Hat Carrier Topology
Portenta Hat Carrier Topology

ItemOnboard modules
J1, J2High-Density connectors for Portenta boards
J3JTAG male connector for debugging
J4USB-A female connector for data logging and external devices
J540-pin male header compatible with Raspberry Pi® Hats
J616-pin male header for analog, GPIOs, UART and RTC battery pins
J7MicroSD slot for data logging and media purposes
J8RJ45 connector for Ethernet
J9Screw terminal for power supply and CAN FD support
J10MIPI camera connector, exclusive for Portenta X8
J11PWM male header connector to control fan speed
SW2DIP switch with two sliders: ETH CENTER TAP and BTSEL
PB1User Button

Pinout

Portenta Hat Carrier Pinout

Datasheet

The full datasheet is available and downloadable as PDF from the link below:

Schematics

The full schematics are available and downloadable as PDF from the link below:

STEP Files

The full STEP files are available and downloadable from the link below:

Mechanical Information

In this section, you can find mechanical information about the Portenta Hat Carrier. The dimensions of the board are all specified here, within top and bottom views, including the placements of the components onboard.

If you desire to design and manufacture a custom mounting device or create a custom enclosure for your carrier, the following image shows the dimensions for the mounting holes and general board layout. The given dimensions are all in millimeters [mm].

You can also access the STEP files which are also available here.

Portenta Hat Carrier Overall Dimensions
Portenta Hat Carrier Overall Dimensions

First Use Of Your Portenta Hat Carrier

Stack The Carrier

The Portenta Hat Carrier design allows the user to easily stack the preferred Portenta board. The following figure shows how the Portenta Hat Carrier pairs with the Portenta boards via High-Density connectors.

Portenta Hat Carrier Stack Orientation

With the Portenta mounted to the carrier, you can proceed to power the carrier and start prototyping.

Power The Board

The Portenta Hat Carrier can be powered according to one of the following methods:

  • Using an external 7 to 32 V power supply connected to the VIN pin available on the screw terminal block of the board is the most recommended method. It ensures that the Portenta Hat Carrier, the SOM, and any connected hat receive power.

    For clarity on the connection points, please refer to the board pinout section of the user manual. Ensure the supplied current meets the specification for all components, as shown in the operating conditions table reported later on.

  • Using an external 5 V power supply to:

  • The 5V pin located on the 40-pin male header connector pins

  • The 5V pin located on the screw terminal of the board

    You can effectively power the Portenta Hat Carrier, the SOM, and any connected hat.

    For more details on this connection, kindly consult the board pinout section of the user manual. Again, ensure that the power supply's maximum current respects all components' specifications.

  • Using a USB-C® cable (not included) connected to the Portenta core board of your choice powers not only the selected core board, like the Portenta X8, H7, or C33, but also the Portenta Hat Carrier, and any connected hat that does not require a dedicated external power supply.

The Portenta Hat Carrier can deliver a maximum of 1.5 A.

Portenta Hat Carrier Power Connection Overview
Portenta Hat Carrier Power Connection Overview

The image below magnifies the location of the terminal block for the 7 - 32 V and 5 V power source of the Portenta Hat Carrier:

Portenta Hat Carrier Power Source within Terminal Block
Portenta Hat Carrier Power Source within Terminal Block

Subsequently, you can check how the Portenta Hat Carrier distributes power resources with the power tree diagram.

Portenta Hat Carrier Power Tree Diagram
Portenta Hat Carrier Power Tree Diagram

To ensure the safety and longevity of the board, it is essential to understand the carrier's operating conditions. The table below provides the recommended operating conditions for the carrier:

ParameterMinTypMaxUnit
VIN from onboard screw terminal* of the Carrier7.0-32.0V
USB-C® input from the connected Portenta family board-5.0-V
+5 VDC from the 40-pin header connector on the carrier-5.0-V
+5 VDC from the carrier's onboard screw terminal-5.0-V
Current supplied by the carrier--1.5A
Ambient operating temperature-40-85°C

The onboard screw terminal powers both the carrier and any connected Portenta board. Additionally, this terminal connector includes reverse polarity protection for enhanced safety.

Carrier Characteristics Highlight

The Portenta Hat Carrier provides different functionalities based on the connected Portenta family board, as shown in the table below:

FeaturesPortenta X8Portenta H7Portenta 33
40-Pin HeaderCompatibleCompatibleCompatible
16-Pin HeaderCompatibleCompatibleCompatible
MIPI CameraCompatibleIncompatibleIncompatible
Ethernet1 Gbit (DIP: OFF)100 Mbit (DIP: ON)100 Mbit (DIP: ON)
PWM FanAvailableAvailableAvailable
JTAG DebugAvailableAvailableAvailable
Storage Exp.MicroSD slotMicroSD slotMicroSD slot
CAN FDAvailableAvailableAvailable
USB-A SupportAvailableAvailableAvailable

The Portenta X8 is the specific Portenta family board that offers compatibility with Raspberry Pi® Hats on the 40-pin Header.

This provides a general idea of how the Portenta Hat Carrier will perform depending on the paired Portenta board. Each feature is explained in the following section after a quick guide covering how to properly interface the Portenta boards.

Hello World Carrier

Hello World Using Linux

Using Portenta X8 with Linux


To use the Portenta Hat Carrier with the Portenta X8, you will have to align the High-Density connectors along with the USB-C® port. The following diagram shows how the board stacks on the carrier.

Portenta Hat Carrier with X8
Portenta Hat Carrier with X8

Portenta Hat Carrier with X8
Portenta Hat Carrier with X8

For the stable functionality of the Portenta Hat Carrier when used with Portenta X8, it is crucial to have at least version 746 of the Linux image on the Portenta X8. Access and download the latest version directly through this link.

Hello World With Portenta X8 Shell


A series of Hello World examples will be used to ensure the Portenta Hat Carrier is correctly operating with the paired Portenta X8. These examples, using Linux commands, Python® scripts, and the Arduino IDE, aim to trigger the user-programmable LED connected to GPIO3 leveraging different methods and platforms.

We will begin with a Hello World example using Linux commands. The user-programmable LED can be controlled using commands within the Portenta X8's shell. Learn how to connect with the Portenta X8 shell here.

The following commands will help you set and control the GPIO3, which connects to the user-programmable LED.

Let us begin with the commands to access the Portenta X8's shell:

1adb shell
2sudo su -

When you execute the sudo su - command, you will be prompted for a password:

The default password is

fio

This command grants you access as the root user, loading the root user's environment settings such as

$HOME
and
$PATH
.

The aforementioned commands allow you to access the Portenta X8's shell with elevated privileges. This allows you to modify system configurations that require administrative permissions.

Subsequently, use the following command to export the gpio device located under

/sys/class/
. In this context, GPIO3 corresponds to GPIO 163, which is associated with the user-programmable LED we aim to access.

1echo 163 > /sys/class/gpio/export

Using the following commands will help you verify the available GPIO elements.

1ls /sys/class/gpio

It lists all the GPIOs previously initialized by the system. Meanwhile, the following command lists the details of GPIO 163, corresponding to GPIO3, which was previously imported:

1ls /sys/class/gpio/gpio163

The GPIO can now be configured verifying that GPIO3 elements were successfully exported. The following command with the

I/O
field will set the I/O state of the pin. The pin can be set either as Input using
in
or Output using
out
value.

1echo <I/O> >/sys/class/gpio/gpio163/direction

For this example, we will replace the

<I/O>
field with
out
value within the following command:

1echo out >/sys/class/gpio/gpio163/direction

To verify the pin setting, use the

cat
command. If correctly configured, this command will display the set value:

1cat /sys/class/gpio/gpio163/direction

The GPIO is now set as an output, thus it can now be controlled by setting its state.

To set the pin High, you need to assign the value

1
, or
0
to set the pin
HIGH
or
LOW
. The command will require the
value
at the end to ensure the pin's state is controlled.

To set the pin to

HIGH
:

1echo 1 >/sys/class/gpio/gpio163/value

To set the pin to

LOW
:

1echo 0 >/sys/class/gpio/gpio163/value

If you have finished controlling the GPIO, you can use the following command to unexport it, ensuring it no longer appears in the userspace:

1echo 163 >/sys/class/gpio/unexport

To confirm that the specified GPIO has been properly unexported, you can use the following command:

1ls /sys/class/gpio

This step helps you to prevent unintentional modifications to the element configuration.

Hello World Using Linux and Python® Scripts


Previously, we manually toggled the LED linked to GPIO3 on the Portenta X8 via the command line. However, to automate this process and potentially extend our control logic, we can employ a Python® script for this purpose.

The script below is compatible with the ADB shell on the Portenta X8:

1#!/usr/bin/env python3
2import time
3
4class GPIOController:
5 def __init__(self, gpio_number):
6 self.gpio_number = gpio_number
7 self.gpio_path = f"/sys/class/gpio/gpio{gpio_number}/"
8
9 def export(self):
10 with open("/sys/class/gpio/export", "w") as f:
11 f.write(str(self.gpio_number))
12
13 def unexport(self):
14 with open("/sys/class/gpio/unexport", "w") as f:
15 f.write(str(self.gpio_number))
16
17 def set_direction(self, direction):
18 with open(f"{self.gpio_path}direction", "w") as f:
19 f.write(direction)
20
21 def read_direction(self):
22 with open(f"{self.gpio_path}direction", "r") as f:
23 return f.read().strip()
24
25 def set_value(self, value):
26 with open(f"{self.gpio_path}value", "w") as f:
27 f.write(str(value))
28
29 def read_value(self):
30 with open(f"{self.gpio_path}value", "r") as f:
31 return int(f.read().strip())
32
33def main():
34 print("============================================")
35 print("Hello World PHC!")
36 print("============================================")
37
38 gpio = GPIOController(163)
39
40 # Export GPIO
41 gpio.export()
42
43 # Set as output
44 gpio.set_direction("out")
45 if gpio.read_direction() == "out":
46 print("GPIO set as output.")
47 print("User LED Blinking 20 times")
48
49
50 # Turn on (set to 1) and then off (set to 0)
51 for i in range(1,20,1):
52 gpio.set_value(1)
53 time.sleep(1)
54 gpio.set_value(0)
55 time.sleep(1)
56
57
58 print("GPIO Unexport")
59 gpio.unexport()
60 print("End of the program")
61 exit()
62
63if __name__ == "__main__":
64 main()

The script can be named

hello_world_python.py
for example. It can then be pushed to Portenta X8 using the following command on a computer terminal:

1adb push hello_world_python.py /home/fio

The file is uploaded to the

/home/fio
directory. Navigate to the directory using ADB shell:

1cd python3 hello_world_python.py

Now use the following command to run the script:

1python3 hello_world_python.py

Portenta Hat Carrier's user-programmable LED will start blinking whenever the script is running.

Please check out the Portenta X8 user manual to learn how the board operates, and maximize its potential when paired with the Portenta Hat Carrier. The Portenta Hat Carrier supports the Portenta X8 via High-Density connectors.

The Portenta X8 has the capability to operate in a Linux environment and it is based on Yocto Linux distribution. It is recommendable to read how the Portenta X8 works in terms of Linux environment here.

Hello World Using Arduino

Using Portenta X8 / H7 / C33 with Arduino


The Portenta X8 is also capable of operating within the Arduino environment and retains the same hardware setup as explained here.

The Portenta H7 and C33 boards have hardware setups similar to the Portenta X8. To mount them on the Hat Carrier, please align the High-Density connectors along with USB-C® port orientation.

The diagrams below show how the Portenta H7 and C33 stack on the carrier:

  • Portenta H7

Portenta Hat Carrier with H7
Portenta Hat Carrier with H7

Portenta Hat Carrier with H7
Portenta Hat Carrier with H7

  • Portenta C33

Portenta Hat Carrier with C33
Portenta Hat Carrier with C33

Portenta Hat Carrier with C33
Portenta Hat Carrier with C33

Hello World With Arduino


In this section, you will learn how to use the Portenta X8, Portenta H7, or Portenta C33 with the Portenta Hat Carrier. You will interact with the user-configurable LED connected to GPIO3, but this time within the Arduino environment.

Once any compatible Portenta board is connected to the Portenta Hat Carrier, launch the Arduino IDE 2 and set up the subsequent sketch:

1// the setup function runs once when you press reset or power the board
2void setup() {
3 // Initialize the digital pin of the chosen SOM as an output
4 pinMode(<DIGITAL_PIN>, OUTPUT);
5}
6
7// the loop function runs over and over again forever
8void loop() {
9 digitalWrite(<DIGITAL_PIN>, HIGH); // turn the LED on (HIGH is the voltage level)
10 delay(1000); // wait for a second
11 digitalWrite(<DIGITAL_PIN>, LOW); // turn the LED off by making the voltage LOW
12 delay(1000); // wait for a second
13}

Make sure to replace

<DIGITAL_PIN>
with the appropriate value for your chosen Portenta board:

  • Portenta X8: PF_4
  • Portenta H7: PD_5
  • Portenta C33: 30

For example, when using the Portenta X8, your script should look like this:

1// the setup function runs once when you press reset or power the board
2void setup() {
3 // Initialize the digital pin of the chosen SOM as an output
4 pinMode(PF_4, OUTPUT);
5}
6
7// the loop function runs over and over again forever
8void loop() {
9 digitalWrite(PF_4, HIGH); // turn the LED on (HIGH is the voltage level)
10 delay(1000); // wait for a second
11 digitalWrite(PF_4, LOW); // turn the LED off by making the voltage LOW
12 delay(1000); // wait for a second
13}

After successfully uploading the sketch, the user-configurable LED will start blinking. The following clip illustrates the expected LED blink pattern.

Portenta Hat Carrier Hello World Blink

Please check out the following documentation to learn more about each board and maximize its potential when paired with the Portenta Hat Carrier:

Please note that the Ethernet connectivity speed is limited to 100 Mbit when used with the Portenta H7 or C33.

For up-to-date performance of the Portenta X8 on the Portenta Hat Carrier, ensure you update to the latest Portenta X8 OS image. You can check here for more details.

Carrier Features and Interfaces

The carrier offers a diverse range of features and interfaces to cater to a variety of user requirements and applications. This section provides an overview of the main hardware interfaces, storage options, and configuration mechanisms integrated into the carrier.

Each sub-section further delves into the specifications of each feature, ensuring users will get comprehensive information for optimal utilization.

Hardware Interfaces

This sub-section introduces the essential hardware connection points and interfaces present on the Portenta Hat Carrier. Ranging from connectors and camera interfaces to fan control, you will be able to explore the physical interaction points of the carrier.

High-Density Connectors

The Portenta X8, H7, and C33 enhance functionality through High-Density connectors. For a comprehensive understanding of these connectors, please refer to the complete pinout documentation for each Portenta model.

USB Interface

The Portenta Hat Carrier features a USB interface suitable for data logging and connecting external devices.

Portenta Hat Carrier USB-A Port
Portenta Hat Carrier USB-A Port

If you are interested in the USB-A port pinout, the following table may serve to understand its connection distribution:

Pin numberPower NetPortenta HD Standard PinHigh-Density PinInterface
1+5VVIN / USB0_VBUSJ1-21, J1-24, J1-32, J1-41, J1-48
2USB0_D_NJ1-28USB D-
3USB0_D_PJ1-26USB D+
4GNDGNDJ1-22, J1-31, J1-42, J1-47, J1-54, J2-24, J2-33, J2-44, J2-57, J2-70

Devices with a USB-A interface, such as storage drives, can be used for logging data. External devices include peripherals like keyboards, mouses, webcams, and hubs.

Using Linux


As an example, the following command on Portenta X8's shell can be used to test a write command with a USB memory drive. To write a file, the following sequence of commands can help you to accomplish such task.

1sudo su -

First of all, let's enter root mode to have the right permissions to mount and unmount related peripherals like our USB memory drive.

1lsblk

The

lsblk
command lists all available block devices, such as hard drives and USB drives. It helps in identifying the device name, like
/dev/sda1
which will be probably the partition designation of the USB drive you just plugged in. A common trick to identify and check the USB drive connected is to execute the
lsblk
command twice; once with the USB disconnected and the next one to the USB connected, to compare both results and spot easily the newly connected USB drive. Additionally, the command
lsusb
can be used to gather more information about the connected USB drive.

1mkdir -p /mnt/USBmount

The

mkdir -p
command creates the directory
/mnt/USBmount
. This directory will be used as a mount point for the USB drive.

1mount -t vfat /dev/sda1 /mnt/USBmount

This mount command mounts the USB drive, assumed to have a FAT filesystem (

vfat
), located at
/dev/sda1
to the directory
/mnt/USBmount
. Once mounted, the content of the USB drive can be accessed from the
/mnt/USBmount
directory with
cd
:

1cd /mnt/USBmount

Now if you do an

ls
you can see the actual content of the connected USB Drive.

1ls

Let's create a simple text file containing the message

Hello, World!
in the already connected USB memory drive using the following command:

1dd if=<(echo -n "Hello, World!") of=/mnt/USBmount/helloworld.txt

This command uses the

dd
utility, combined with process substitution. Specifically, it seizes the output of the
echo
command, responsible for generating the
Hello, World!
message, and channels it as an input stream to
dd
.

Subsequently, the message gets inscribed into a file named helloworld.txt situated in the

/mnt/USBmount
directory.

After creating the file, if you wish to retrieve its contents and display them on the shell, you can use:

1cat helloworld.txt

This command

cat
prompts in the terminal the content of a file, in this case the words
Hello, World!
.

Now that you know how to locate, mount, write and read information from an external USB stick or hard drive you can expand the possibilities of your solution with the additional storage connected to the Portenta Hat Carrier.

Using Arduino IDE


The following example demonstrates how to use the USB interface of the Portenta Hat Carrier with the Portenta C33 to mount a Mass Storage Device (MSD).

Through this code, users will be able to effectively connect to, read from, and write to a USB storage device, making it easier to interact with external storage via the USB interface.

1#include <vector>
2#include <string>
3#include "UsbHostMsd.h"
4#include "FATFileSystem.h"
5
6#define TEST_FS_NAME "usb"
7#define TEST_FOLDER_NAME "TEST_FOLDER"
8#define TEST_FILE "test.txt"
9#define DELETE_FILE_DIMENSION 150
10
11
12USBHostMSD block_device;
13FATFileSystem fs(TEST_FS_NAME);
14
15std::string root_folder = std::string("/") + std::string(TEST_FS_NAME);
16std::string folder_test_name = root_folder + std::string("/") + std::string(TEST_FOLDER_NAME);
17std::string file_test_name = folder_test_name + std::string("/") + std::string(TEST_FILE);
18
19/* this callback will be called when a Mass Storage Device is plugged in */
20void device_attached_callback(void) {
21 Serial.println();
22 Serial.println("++++ Mass Storage Device detected ++++");
23 Serial.println();
24}
25
26void setup() {
27 /*
28 * SERIAL INITIALIZATION
29 */
30 Serial.begin(9600);
31 while(!Serial) {
32
33 }
34
35 Serial.println();
36 Serial.println("*** USB HOST Mass Storage Device example ***");
37 Serial.println();
38
39 /* attached the callback so that when the device is inserted the device_attached_callback
40 will be automatically called */
41 block_device.attach_detected_callback(device_attached_callback);
42 /* list to store all directory in the root */
43 std::vector<std::string> dir_list;
44
45 /*
46 * Check for device to be connected
47 */
48
49 int count = 0;
50 while (!block_device.connect()) {
51 if(count == 0) {
52 Serial.println("Waiting for Mass Storage Device");
53 }
54 else {
55 Serial.print(".");
56 if(count % 30 == 0) {
57 Serial.println();
58 }
59 }
60 count++;
61 delay(1000);
62 }
63
64 Serial.println("Mass Storage Device connected.");
65
66 /*
67 * MOUNTIN SDCARD AS FATFS filesystem
68 */
69
70 Serial.println("Mounting Mass Storage Device...");
71 int err = fs.mount(&block_device);
72 if (err) {
73 // Reformat if we can't mount the filesystem
74 // this should only happen on the first boot
75 Serial.println("No filesystem found, formatting... ");
76 err = fs.reformat(&block_device);
77 }
78
79 if (err) {
80 Serial.println("Error formatting USB Mass Storage Device");
81 while(1);
82 }
83
84 /*
85 * READING root folder
86 */
87
88 DIR *dir;
89 struct dirent *ent;
90 int dirIndex = 0;
91
92 Serial.println("*** List USB Mass Storage Device content: ");
93 if ((dir = opendir(root_folder.c_str())) != NULL) {
94 while ((ent = readdir (dir)) != NULL) {
95 if(ent->d_type == DT_REG) {
96 Serial.print("- [File]: ");
97 }
98 else if(ent->d_type == DT_DIR) {
99 Serial.print("- [Fold]: ");
100 if(ent->d_name[0] != '.') { /* avoid hidden folders (.Trash might contain a lot of files) */
101 dir_list.push_back(ent->d_name);
102 }
103 }
104 Serial.println(ent->d_name);
105 dirIndex++;
106 }
107 closedir (dir);
108 }
109 else {
110 // Could not open directory
111 Serial.println("Error opening USB Mass Storage Device\n");
112 while(1);
113 }
114
115 if(dirIndex == 0) {
116 Serial.println("Empty SDCARD");
117 }
118
119 bool found_test_folder = false;
120
121 /*
122 * LISTING CONTENT of the first level folders (the one immediately present in root folder)
123 */
124
125 if(dir_list.size()) {
126 Serial.println();
127 Serial.println("Listing content of folders in root: ");
128 }
129 for(unsigned int i = 0; i < dir_list.size(); i++) {
130 if(dir_list[i] == TEST_FOLDER_NAME) {
131 found_test_folder = true;
132 }
133 Serial.print("- ");
134 Serial.print(dir_list[i].c_str());
135 Serial.println(":");
136
137 std::string d = root_folder + std::string("/") + dir_list[i];
138 if ((dir = opendir(d.c_str())) != NULL) {
139 while ((ent = readdir (dir)) != NULL) {
140 if(ent->d_type == DT_REG) {
141 Serial.print(" - [File]: ");
142 }
143 else if(ent->d_type == DT_DIR) {
144 Serial.print(" - [Fold]: ");
145 }
146 Serial.println(ent->d_name);
147 }
148 closedir (dir);
149 }
150 else {
151 Serial.print("ERROR OPENING SUB-FOLDER ");
152 Serial.println(d.c_str());
153 }
154 }
155
156 /*
157 * CREATING TEST FOLDER (if does not exist already)
158 */
159
160 err = 0;
161 if(!found_test_folder) {
162 Serial.println("TEST FOLDER NOT FOUND... creating folder test");
163 err = mkdir(folder_test_name.c_str(), S_IRWXU | S_IRWXG | S_IRWXO);
164 if(err != 0) {
165 Serial.print("FAILED folder creation with error ");
166 Serial.println(err);
167 }
168 }
169
170 /*
171 * READING TEST FILE CONTENT
172 */
173
174 if(err == 0) {
175 int file_dimension = 0;
176 FILE* fp = fopen(file_test_name.c_str(), "r");
177 if(fp != NULL) {
178 Serial.print("Opened file: ");
179 Serial.print(file_test_name.c_str());
180 Serial.println(" for reading");
181
182 fseek(fp, 0L, SEEK_END);
183 int numbytes = ftell(fp);
184 fseek(fp, 0L, SEEK_SET);
185
186 Serial.print("Bytes in the file: ");
187 Serial.println(numbytes);
188 file_dimension = numbytes;
189
190 if(numbytes > 0) {
191 Serial.println();
192 Serial.println("-------------------- START FILE CONTENT --------------------");
193 }
194
195 for(int i = 0; i < numbytes; i++) {
196 char ch;
197 fread(&ch, sizeof(char), 1, fp);
198 Serial.print(ch);
199 }
200
201 if(numbytes > 0) {
202 Serial.println("--------------------- END FILE CONTENT ---------------------");
203 Serial.println();
204 }
205 else {
206 Serial.println("File is EMPTY!");
207 Serial.println();
208 }
209
210 fclose(fp);
211 }
212 else {
213 Serial.print("FAILED open file ");
214 Serial.println(file_test_name.c_str());
215 }
216
217 /*
218 * DELETE FILE IF THE File dimension is greater than 150 bytes
219 */
220
221 if(file_dimension > DELETE_FILE_DIMENSION) {
222 Serial.println("Test file reached the delete dimension... deleting it!");
223 if(remove(file_test_name.c_str()) == 0) {
224 Serial.println("TEST FILE HAS BEEN DELETED!");
225 }
226 }
227
228 /*
229 * APPENDING SOMETHING TO FILE
230 */
231
232 fp = fopen(file_test_name.c_str(), "a");
233 if(fp != NULL) {
234 Serial.print("Opened file: ");
235 Serial.print(file_test_name.c_str());
236 Serial.println(" for writing (append)");
237 char text[] = "This line has been appended to file!\n";
238 fwrite(text, sizeof(char), strlen(text), fp);
239 fclose(fp);
240 }
241 else {
242 Serial.print("FAILED open file for appending ");
243 Serial.println(file_test_name.c_str());
244 }
245
246 /*
247 * READING AGAIN FILE CONTENT
248 */
249
250 fp = fopen(file_test_name.c_str(), "r");
251 if(fp != NULL) {
252 Serial.print("Opened file: ");
253 Serial.print(file_test_name.c_str());
254 Serial.println(" for reading");
255
256 fseek(fp, 0L, SEEK_END);
257 int numbytes = ftell(fp);
258 fseek(fp, 0L, SEEK_SET);
259
260 Serial.print("Bytes in the file: ");
261 Serial.println(numbytes);
262
263 if(numbytes > 0) {
264 Serial.println();
265 Serial.println("-------------------- START FILE CONTENT --------------------");
266 }
267
268 for(int i = 0; i < numbytes; i++) {
269 char ch;
270 fread(&ch, sizeof(char), 1, fp);
271 Serial.print(ch);
272 }
273
274 if(numbytes > 0) {
275 Serial.println("--------------------- END FILE CONTENT ---------------------");
276 Serial.println();
277 }
278 else {
279 Serial.println("File is EMPTY!");
280 Serial.println();
281 }
282
283 fclose(fp);
284
285 }
286 else {
287 Serial.print("FAILED open file for appending ");
288 Serial.println(file_test_name.c_str());
289 }
290 }
291
292}
293
294void loop() {
295 // Empty
296}

Analog Pins

The 16-pin header connector of the Portenta Hat Carrier integrates the analog channels. The analog

A0
,
A1
,
A2
,
A3
,
A4
,
A5
,
A6
, and
A7
are accessible through these pins.

Portenta Hat Carrier Analog Pins
Portenta Hat Carrier Analog Pins

Pin numberSilkscreenPortenta HD Standard PinHigh-Density Pin
1A0ANALOG_A0J2-73
2A1ANALOG_A1J2-75
3A2ANALOG_A2J2-77
4A3ANALOG_A3J2-79
5A4ANALOG_A4J2-74
6A5ANALOG_A5J2-76
7A6ANALOG_A6J2-78
8A7ANALOG_A7J2-80

The built-in features of the Arduino programming language (

analogRead()
function) can be used to access the eight analog input pins on the Arduino IDE.

Please, refer to the board pinout section of the user manual to find the analog pins on the board.

Using Linux


Using the Portenta X8, you can obtain a voltage reading that falls within a 0 - 65535 range. This reading corresponds to a voltage between 0 and 3.3 V. To fetch this reading, use the command:

1cat /sys/bus/iio/devices/iio\:device0/in_voltage<adc_pin>_raw

Where

<adc_pin>
is the number of the analog pin to read. For example, in the case of
A0
:

1cat /sys/bus/iio/devices/iio\:device0/in_voltage0_raw

If you are working in Python®, the command can be implemented as shown in the script below:

1def read_adc_value(adc_pin):
2 try:
3 with open(f'/sys/bus/iio/devices/iio:device0/in_voltage{adc_pin}_raw', 'r') as file:
4 return int(file.read().strip())
5 except FileNotFoundError:
6 print(f"ADC pin {adc_pin} not found!")
7 return None
8
9if __name__ == "__main__":
10 adc_pin = input("Enter ADC pin number: ")
11 value = read_adc_value(adc_pin)
12
13 if value is not None:
14 print(f"Value from ADC pin {adc_pin}: {value}")
15
16 # Mapping between 0-3.3 V
17 new_value = (float) (value/65535)*3.3
18 print(f"Value mapped between 0-3.3 V: {new_value}")

Using Arduino IDE


The following example snippet, compatible with Portenta H7, shows how to read the voltage value from a potentiometer on

A0
. It will then display the readings on the Arduino IDE Serial Monitor.

1// Define the potentiometer pin and variable to store its value
2int potentiometerPin = A0;
3int potentiometerValue = 0;
4
5void setup() {
6 // Initialize Serial communication
7 Serial.begin(9600);
8}
9
10void loop() {
11 // Read the voltage value from the potentiometer
12 potentiometerValue = analogRead(potentiometerPin);
13
14 // Print the potentiometer voltage value to the Serial Monitor
15 Serial.print("- Potentiometer voltage value: ");
16 Serial.println(potentiometerValue);
17
18 // Wait for 1000 milliseconds
19 delay(1000);
20}

The following example can be considered for Portenta C33:

1#include "analogWave.h" // Include the library for analog waveform generation
2
3analogWave wave(DAC); // Create an instance of the analogWave class, using the DAC pin
4
5int freq = 10; // in hertz, change accordingly
6
7void setup() {
8 Serial.begin(115200); // Initialize serial communication at a baud rate of 115200
9 wave.sine(freq); // Generate a sine wave with the initial frequency
10}
11
12void loop() {
13 // Read an analog value from pin XX and map it to a frequency range
14 freq = map(analogRead(XX), 0, 1024, 0, 10000);
15
16 // Print the updated frequency to the serial monitor
17 Serial.println("Frequency is now " + String(freq) + " hz");
18
19 wave.freq(freq); // Set the frequency of the waveform generator to the updated value
20 delay(1000); // Delay for one second before repeating
21}

CAN FD (Onboard Transceiver)

The Portenta Hat Carrier features a dedicated CAN bus connected to a screw terminal block. It uses the TJA1049 module, a high-speed CAN FD transceiver integrated within the Portenta Hat Carrier.

The TJA1049 module supports ISO 11898-2:2016, SAE J2284-1, and SAE J2284-5 standards over the CAN physical layer, guaranteeing stable communication during the CAN FD fast phase.

Portenta Hat Carrier CAN Interface
Portenta Hat Carrier CAN Interface

Since CAN FD is part of the screw terminal block, we have highlighted the CAN bus ports within the screw terminal block pinout for reference.

Pin numberSilkscreenPower NetPortenta HD Standard PinHigh-Density PinInterface
1VIN 7-32VDCINPUT_7V-32V
2GNDGNDGNDJ1-22, J1-31, J1-42, J1-47, J1-54, J2-24, J2-33, J2-44, J2-57, J2-70
3GNDGNDGNDJ1-22, J1-31, J1-42, J1-47, J1-54, J2-24, J2-33, J2-44, J2-57, J2-70
45V+5VVINJ1-21, J1-24, J1-32, J1-41, J1-48
5CANHJ1-49 (Through U1)CAN BUS - CANH
6CANLJ1-51 (Through U1)CAN BUS - CANL

For stable CAN bus communication, it is recommended to install a 120 Ω termination resistor between CANH and CANL lines.

More information on how to use the CAN Bus protocol can be found within CAN Bus section under Pins chapter.

MIPI Camera

The Portenta X8 can interact with MIPI cameras through the dedicated camera connector. As a quick note, the out-of-the-box Alpine shell does not support certain commands directly through the ADB shell.

On the other hand, the Portenta H7 and C33 have no MIPI interface, so they cannot use the camera connector.

Portenta Hat Carrier MIPI Camera
Portenta Hat Carrier MIPI Camera

The MIPI connector is distributed as follows:

Pin numberPower NetPortenta HD Standard PinHigh-Density PinInterface
1GNDGNDJ1-22, J1-31, J1-42, J1-47, J1-54
2CAM_D0_D0_NJ2-16, J2-24, J2-33, J2-44, J2-57, J2-70
3CAM_D1_D0_PJ2-14
4GNDGNDJ1-22, J1-31, J1-42, J1-47, J1-54, J2-24, J2-33, J2-44, J2-57, J2-70
5CAM_D2_D1_NJ2-12
6CAM_D3_D1_PJ2-10
7GNDGNDJ1-22, J1-31, J1-42, J1-47, J1-54, J2-24, J2-33, J2-44, J2-57, J2-70
8CAM_CK_CK_NJ2-20
9CAM_VS_CK_PJ2-18
10GNDGNDJ1-22, J1-31, J1-42, J1-47, J1-54, J2-24, J2-33, J2-44, J2-57, J2-70
11GPIO_5J2-56
12NCNC
13I2C1_SCLJ1-45I2C 1 SCL
14I2C1_SDAJ1-43I2C 1 SDA
15+3V3_PORTENTAVCCJ2-23, J2-34, J2-43, J2-69

As mentioned before, the Portenta Hat Carrier supports the MIPI camera if paired with the Portenta X8. The flex cable can be used to interface a compatible camera with the platform. Compatible camera devices are as follows:

  • OmniVision OV5647 sensor (Raspberry Pi® Camera Module 1)
  • Sony IMX219 sensor (Raspberry Pi® Camera Module 2)

Portenta Hat Carrier MIPI Camera Mount

Using Linux


The following commands, using the Portenta X8 environment, allow you to capture a single frame and stream video at 30 FPS (Frames per Second) for 10 seconds from the Raspberry Pi Camera v1.3, which is based on the OV5647 CMOS sensor.

First, we need to set environment variables and specify the overlays for our camera and board setup:

1fw_setenv carrier_custom 1
2fw_setenv overlays ov_som_lbee5kl1dx ov_som_x8h7 ov_carrier_rasptenta_base ov_carrier_rasptenta_ov5647_camera_mipi

The U-Boot environment variables are modified with the above commands and

fw_setenv
sets the changes which are already persistent. The following command sequences can be used on the U-boot shell.

1setenv carrier_custom 1
2setenv overlays ov_som_lbee5kl1dx ov_som_x8h7 ov_carrier_rasptenta_base ov_carrier_rasptenta_ov5647_camera_mipi
3saveenv

Define the runtime directory for

Wayland
and load the necessary module for the OV5647 camera:

1export XDG_RUNTIME_DIR=/run # location of wayland-0 socket
2modprobe ov5647_mipi

Before capturing or streaming, we need to check the supported formats and controls of the connected video device:

1v4l2-ctl --list-formats-ext --device /dev/video0
2v4l2-ctl -d /dev/video0 --list-ctrls

Using

GStreamer
, capture a single frame in
JPEG
format:

1export GST_DEBUG=3
2gst-top-1.0 gst-launch-1.0 -v v4l2src device=/dev/video0 num-buffers=1 ! "video/x-bayer, format=bggr, width=640, height=480, bpp=8, framerate=30/1" ! bayer2rgbneon reduce-bpp=t ! jpegenc ! filesink location=/tmp/test.jpg

This command allows the user to capture one frame and save it as

/tmp/test.jpg
. The following command is used to stream video at 30FPS for approximately 10 seconds using
GStreamer
:

1gst-top-1.0 gst-launch-1.0 -v v4l2src device=/dev/video0 num-buffers=300 ! "video/x-bayer, format=bggr, width=640, height=480, bpp=8, framerate=30/1" ! bayer2rgbneon reduce-bpp=t ! queue ! waylandsink

This command allows the user to capture 300 frames at 30 FPS, which equals 10 seconds of video, and displays them using the

waylandsink
.

Following these steps, you will be able to successfully capture and stream video from the Raspberry Pi Camera v1.3 based on the OV5647 sensor.

For enhanced image quality, we recommend using a MIPI camera module with an integrated Image Signal Processor (ISP).

PWM Fan Control

The Portenta Hat Carrier is designed to be a thermal dissipation reference carrier for Portenta X8, including dedicated Pulse Width Modulation (PWM) pins for external fan control. The principle of PWM involves varying the width of the pulses sent to the device, in this case, a fan, to control its speed or position.

Portenta Hat Carrier Fan Mount

The fan can be connected via PWM pins available on the Portenta Hat Carrier. The connector has the following structure:

Pin numberSilkscreenPower NetPortenta HD Standard PinHigh-Density Pin
1PWM9PWM_9J2-68
2N/A
35V+5VVINJ1-21, J1-24, J1-32, J1-41, J1-48
4GNDGNDGNDJ1-22, J1-31, J1-42, J1-47, J1-54, J2-24, J2-33, J2-44, J2-57, J2-70

Using Linux


The fan's speed can be controlled using the following code sequence when you are using the Portenta X8 within the Linux environment.

Export the PWM channel:

1echo 9 > /sys/class/pwm/pwmchip0/export

Set the PWM period. By defining the period, you determine the duration of one PWM "cycle". Here, we set it to 100,000, representing 100,000 nanoseconds or 100 microseconds:

1echo 100000 > /sys/class/pwm/pwmchip0/pwm9/period

The following command sets the "ON" duration within the given period. A 50% duty cycle, for instance, means the signal is on for half the period and off for the other half:

1echo 50000 > /sys/class/pwm/pwmchip0/pwm9/duty_cycle #50% duty

We will then enable the PWM channel exported previously:

1echo 1 > /sys/class/pwm/pwmchip0/pwm9/enable

You can use the following command if you want to monitor the temperature of the device or environment (optional step):

1cat /sys/devices/virtual/thermal/thermal_zone0/temp

It can be translated into a Python® script to automate the command sequence:

1def setup_pwm(pwm_chip, pwm_channel, period, duty_cycle):
2 base_path = f"/sys/class/pwm/pwmchip{pwm_chip}"
3
4 # Export the PWM channel
5 with open(f"{base_path}/export", "w") as f:
6 f.write(str(pwm_channel))
7
8 # Set period
9 with open(f"{base_path}/pwm{pwm_channel}/period", "w") as f:
10 f.write(str(period))
11
12 # Set duty cycle
13 with open(f"{base_path}/pwm{pwm_channel}/duty_cycle", "w") as f:
14 f.write(str(duty_cycle))
15
16 # Enable the PWM channel
17 with open(f"{base_path}/pwm{pwm_channel}/enable", "w") as f:
18 f.write("1")
19
20def get_thermal_temperature(zone=0):
21 with open(f"/sys/devices/virtual/thermal/thermal_zone{zone}/temp", "r") as f:
22 return int(f.read().strip())
23
24
25if __name__ == "__main__":
26 # Set up PWM
27 setup_pwm(0, 9, 100000, 50000) # 50% duty
28
29 # Read and print thermal temperature
30 temperature = get_thermal_temperature()
31 print(f"Thermal Temperature (thermal_zone0): {temperature}")

If you are logged in with normal privileges, the speed of the fan can be controlled using the following instruction sequence. Export the PWM channel using the command below:

1echo 9 | sudo tee /sys/class/pwm/pwmchip0/export

Set the PWM period:

1echo 100000 | sudo tee /sys/class/pwm/pwmchip0/pwm9/period

Determine the duty cycle at 50%:

1echo 50000 | sudo tee /sys/class/pwm/pwmchip0/pwm9/duty_cycle #50% duty

And activate the PWM channel:

1echo 1 | sudo tee /sys/class/pwm/pwmchip0/pwm9/enable

Consider the following Python® script if you would like to automate the command sequence:

1import subprocess
2
3def setup_pwm(pwm_chip, pwm_channel, period, duty_cycle):
4 base_path = f"/sys/class/pwm/pwmchip{pwm_chip}"
5
6 # Export the PWM channel
7 subprocess.run(f"echo {pwm_channel} | sudo tee {base_path}/export", shell=True)
8
9 # Set period
10 subprocess.run(f"echo {period} | sudo tee {base_path}/pwm{pwm_channel}/period", shell=True)
11
12 # Set duty cycle
13 subprocess.run(f"echo {duty_cycle} | sudo tee {base_path}/pwm{pwm_channel}/duty_cycle", shell=True)
14
15 # Enable the PWM channel
16 subprocess.run(f"echo 1 | sudo tee {base_path}/pwm{pwm_channel}/enable", shell=True)
17
18if __name__ == "__main__":
19 setup_pwm(0, 9, 100000, 50000) # 50% duty

Portenta Hat Carrier Fan Operation

By understanding the fundamentals of PWM and leveraging the capabilities of the Portenta Hat Carrier, you can effectively regulate fan speed as part of the main feature, ensuring optimal cooling performance and longevity of the device of interest.

Storage and Boot Options

Storage and boot-related options are provided to manage the device's data storage and control its operational sequences. Dive into this sub-section to understand the onboard storage options and boot initialization mechanisms with user-programmable actuators.

User-Programmable Push Button

The Portenta Hat Carrier boasts a streamlined, user-centric design with its multifunctional push button. The button is designed for general user-programmable functions.

A single button press can be customized according to the application's needs. Whether you need to start a specific event, switch between various states, or execute a particular action, this button is equipped for diverse implementations.

Portenta Hat Carrier Onboard Buttons
Portenta Hat Carrier Onboard Buttons

MicroSD Storage

The available microSD card slot offers the advantage of expanded storage. This is especially beneficial for processing large volumes of log data, whether from sensors or the onboard computer registry.

Portenta Hat Carrier microSD Expansion Slot
Portenta Hat Carrier microSD Expansion Slot

The following table shows an in-depth connector designation:

Pin numberSilkscreenPower NetPortenta HD Standard PinHigh-Density Pin
1N/ASDC_D2J1-63
2N/ASDC_D3J1-65
3N/ASDC_CMDJ1-57
4N/AVDD_SDCARDVSDJ1-72
5N/ASDC_CLKJ1-55
6N/AGNDGNDJ1-22, J1-31, J1-42, J1-47, J1-54, J2-24, J2-33, J2-44, J2-57, J2-70
7N/ASDC_D0J1-59
8N/ASDC_D1J1-61
CD1N/ASDC_CDJ1-67
CD2N/AGNDGNDJ1-22, J1-31, J1-42, J1-47, J1-54, J2-24, J2-33, J2-44, J2-57, J2-70

Using Linux


To begin using a microSD card with Portenta X8, please use the following command to pull a Docker container that assists in setting up the necessary elements for interacting with the microSD card:

1docker run -it --cap-add SYS_ADMIN --device /dev/mmcblk1p1 debian:stable-slim bash

The command above will run the image immediately after the container image has been successfully pulled. You will find yourself inside the container once it is ready for use.

You will need to identify the partition scheme where the microSD card is located. If a partition table does not exist for the microSD card, you will have to use the

fdisk
command to create its partitions.

Inside the container, you can use the following commands.

To determine if the Portenta X8 has recognized the microSD card, you can use one of the following commands:

1lsblk
2
3# or
4fdisk -l

The microSD card usually appears as

/dev/mmcblk0
or
/dev/sdX
. Where X can be a, b, c, etc. depending on other connected storage devices.

Before accessing the contents of the microSD card, it needs to be mounted. For convenient operation, create a directory that will serve as the mount point:

1mkdir -p /tmp/sdcard

Use the following command to mount the microSD card to the previously created directory. Ensure you replace

XX
with the appropriate partition number (e.g., p1 for the first partition):

1mount /dev/mmcblk1p1 /tmp/sdcard

Navigate to the mount point and list the contents of the SD card:

1cd /tmp/sdcard
2ls

To write data to the microSD card, you can use the

echo
command. For example, type the following code to create a file named
hello.txt
with the content
"Hello World Carrier!"
:

1echo "Hello World Carrier!" > hello.txt

To read the contents of the file you have just created:

1cat hello.txt

This will print on your shell the contents that were saved to the

hello.txt
file.

Once you are done with the operations related to the microSD card, it is important to unmount it properly:

1umount /tmp/sdcard

If you need to format the SD card to the ext4 filesystem, use the following command. Please be cautious, since this command will erase all the existing data on the microSD card.

1mkfs.ext4 /dev/mmcblk1p1

Using Arduino IDE


To learn how to use the microSD card slot for enhanced storage with the Arduino IDE, please follow this guide.

For Portenta H7, you can use the following Arduino IDE script to test the mounted SD card within the Portenta Hat Carrier:

1#include "SDMMCBlockDevice.h"
2#include "FATFileSystem.h"
3
4SDMMCBlockDevice block_device;
5mbed::FATFileSystem fs("fs");
6
7void setup() {
8 Serial.begin(9600);
9 while (!Serial);
10
11 Serial.println("Mounting SDCARD...");
12 int err = fs.mount(&block_device);
13 if (err) {
14 // Reformat if we can't mount the filesystem
15 // this should only happen on the first boot
16 Serial.println("No filesystem found, formatting... ");
17 err = fs.reformat(&block_device);
18 }
19 if (err) {
20 Serial.println("Error formatting SDCARD ");
21 while(1);
22 }
23
24 DIR *dir;
25 struct dirent *ent;
26 int dirIndex = 0;
27
28 Serial.println("List SDCARD content: ");
29 if ((dir = opendir("/fs")) != NULL) {
30 // Print all the files and directories within directory (not recursively)
31 while ((ent = readdir (dir)) != NULL) {
32 Serial.println(ent->d_name);
33 dirIndex++;
34 }
35 closedir (dir);
36 } else {
37 // Could not open directory
38 Serial.println("Error opening SDCARD\n");
39 while(1);
40 }
41 if(dirIndex == 0) {
42 Serial.println("Empty SDCARD");
43 }
44}
45
46void loop() {
47 // Empty
48}

For Portenta C33, consider the following script for testing a mounted SD card.

1#include <vector>
2#include <string>
3#include "SDCardBlockDevice.h"
4#include "FATFileSystem.h"
5
6#define TEST_FS_NAME "fs"
7#define TEST_FOLDER_NAME "TEST_FOLDER"
8#define TEST_FILE "test.txt"
9#define DELETE_FILE_DIMENSION 150
10
11
12SDCardBlockDevice block_device(PIN_SDHI_CLK, PIN_SDHI_CMD, PIN_SDHI_D0, PIN_SDHI_D1, PIN_SDHI_D2, PIN_SDHI_D3, PIN_SDHI_CD, PIN_SDHI_WP);
13FATFileSystem fs(TEST_FS_NAME);
14
15std::string root_folder = std::string("/") + std::string(TEST_FS_NAME);
16std::string folder_test_name = root_folder + std::string("/") + std::string(TEST_FOLDER_NAME);
17std::string file_test_name = folder_test_name + std::string("/") + std::string(TEST_FILE);
18
19void setup() {
20 /*
21 * SERIAL INITIALIZATION
22 */
23 Serial.begin(9600);
24 while(!Serial) {
25
26 }
27
28 /* list to store all directory in the root */
29 std::vector<std::string> dir_list;
30
31 Serial.println();
32 Serial.println("##### TEST SD CARD with FAT FS");
33 Serial.println();
34
35 /*
36 * MOUNTING SDCARD AS FATFS filesystem
37 */
38 Serial.println("Mounting SDCARD...");
39 int err = fs.mount(&block_device);
40 if (err) {
41 // Reformat if we can't mount the filesystem
42 // this should only happen on the first boot
43 Serial.println("No filesystem found, formatting... ");
44 err = fs.reformat(&block_device);
45 }
46 if (err) {
47 Serial.println("Error formatting SDCARD ");
48 while(1);
49 }
50
51 /*
52 * READING root folder
53 */
54
55 DIR *dir;
56 struct dirent *ent;
57 int dirIndex = 0;
58
59 Serial.println("*** List SD CARD content: ");
60 if ((dir = opendir(root_folder.c_str())) != NULL) {
61 while ((ent = readdir (dir)) != NULL) {
62
63 if(ent->d_type == DT_REG) {
64 Serial.print("- [File]: ");
65 }
66
67 else if(ent->d_type == DT_DIR) {
68 Serial.print("- [Fold]: ");
69 dir_list.push_back(ent->d_name);
70 }
71 Serial.println(ent->d_name);
72 dirIndex++;
73 }
74 closedir (dir);
75 }
76 else {
77 // Could not open directory
78 Serial.println("Error opening SDCARD\n");
79 while(1);
80 }
81
82 if(dirIndex == 0) {
83 Serial.println("Empty SDCARD");
84 }
85
86 bool found_test_folder = false;
87
88 /*
89 * LISTING CONTENT of the first level folders (the one immediately present in root folder)
90 */
91
92 if(dir_list.size()) {
93 Serial.println();
94 Serial.println("Listing content of folders in root: ");
95 }
96 for(unsigned int i = 0; i < dir_list.size(); i++) {
97 if(dir_list[i] == TEST_FOLDER_NAME) {
98 found_test_folder = true;
99 }
100 Serial.print("- ");
101 Serial.print(dir_list[i].c_str());
102 Serial.println(":");
103
104 std::string d = root_folder + std::string("/") + dir_list[i];
105 if ((dir = opendir(d.c_str())) != NULL) {
106 while ((ent = readdir (dir)) != NULL) {
107 if(ent->d_type == DT_REG) {
108 Serial.print(" - [File]: ");
109 }
110 else if(ent->d_type == DT_DIR) {
111 Serial.print(" - [Fold]: ");
112 }
113 Serial.println(ent->d_name);
114 }
115 closedir (dir);
116 }
117 else {
118 Serial.print("ERROR OPENING SUB-FOLDER ");
119 Serial.println(d.c_str());
120 }
121 }
122
123 /*
124 * CREATING TEST FOLDER (if does not exist already)
125 */
126
127 err = 0;
128 if(!found_test_folder) {
129 Serial.println("TEST FOLDER NOT FOUND... creating folder test");
130 err = mkdir(folder_test_name.c_str(), S_IRWXU | S_IRWXG | S_IRWXO);
131 if(err != 0) {
132 Serial.print("FAILED folder creation with error ");
133 Serial.println(err);
134 }
135 }
136
137 /*
138 * READING TEST FILE CONTENT
139 */
140
141 if(err == 0) {
142 int file_dimension = 0;
143 FILE* fp = fopen(file_test_name.c_str(), "r");
144 if(fp != NULL) {
145 Serial.print("Opened file: ");
146 Serial.print(file_test_name.c_str());
147 Serial.println(" for reading");
148
149 fseek(fp, 0L, SEEK_END);
150 int numbytes = ftell(fp);
151 fseek(fp, 0L, SEEK_SET);
152
153 Serial.print("Bytes in the file: ");
154 Serial.println(numbytes);
155 file_dimension = numbytes;
156
157 if(numbytes > 0) {
158 Serial.println();
159 Serial.println("-------------------- START FILE CONTENT --------------------");
160 }
161
162 for(int i = 0; i < numbytes; i++) {
163 char ch;
164 fread(&ch, sizeof(char), 1, fp);
165 Serial.print(ch);
166 }
167
168 if(numbytes > 0) {
169 Serial.println("--------------------- END FILE CONTENT ---------------------");
170 Serial.println();
171 }
172 else {
173 Serial.println("File is EMPTY!");
174 Serial.println();
175 }
176
177 fclose(fp);
178 }
179 else {
180 Serial.print("FAILED open file ");
181 Serial.println(file_test_name.c_str());
182 }
183
184 /*
185 * DELETE FILE IF THE File dimension is greater than 150 bytes
186 */
187
188 if(file_dimension > DELETE_FILE_DIMENSION) {
189 Serial.println("Test file reached the delete dimension... deleting it!");
190 if(remove(file_test_name.c_str()) == 0) {
191 Serial.println("TEST FILE HAS BEEN DELETED!");
192 }
193 }
194
195 /*
196 * APPENDING SOMETHING TO FILE
197 */
198
199 fp = fopen(file_test_name.c_str(), "a");
200 if(fp != NULL) {
201 Serial.print("Opened file: ");
202 Serial.print(file_test_name.c_str());
203 Serial.println(" for writing (append)");
204 char text[] = "This line has been appended to file!\n";
205 fwrite(text, sizeof(char), strlen(text), fp);
206 fclose(fp);
207 }
208 else {
209 Serial.print("FAILED open file for appending ");
210 Serial.println(file_test_name.c_str());
211 }
212
213 /*
214 * READING AGAIN FILE CONTENT
215 */
216
217 fp = fopen(file_test_name.c_str(), "r");
218 if(fp != NULL) {
219 Serial.print("Opened file: ");
220 Serial.print(file_test_name.c_str());
221 Serial.println(" for reading");
222
223 fseek(fp, 0L, SEEK_END);
224 int numbytes = ftell(fp);
225 fseek(fp, 0L, SEEK_SET);
226
227 Serial.print("Bytes in the file: ");
228 Serial.println(numbytes);
229
230 if(numbytes > 0) {
231 Serial.println();
232 Serial.println("-------------------- START FILE CONTENT --------------------");
233 }
234
235 for(int i = 0; i < numbytes; i++) {
236 char ch;
237 fread(&ch, sizeof(char), 1, fp);
238 Serial.print(ch);
239 }
240
241 if(numbytes > 0) {
242 Serial.println("--------------------- END FILE CONTENT ---------------------");
243 Serial.println();
244 }
245 else {
246 Serial.println("File is EMPTY!");
247 Serial.println();
248 }
249
250 fclose(fp);
251
252 }
253 else {
254 Serial.print("FAILED open file for appending ");
255 Serial.println(file_test_name.c_str());
256 }
257 }
258
259}
260
261void loop() {
262 // Empty
263}

Once the script has successfully compiled, the result should resemble the following image:

Portenta Hat Carrier with Portenta C33 SD Card Test
Portenta Hat Carrier with Portenta C33 SD Card Test

Configuration and Control

Configuration and control features allow the user to customize the device's behavior for their specific needs. If you are interested in learning how to set up network connectivity or adjust switch configurations, follow the section below.

DIP Switch Configuration

The Portenta Hat Carrier incorporates a DIP switch, giving users the ability to manage the behavior of the board. The configuration parameters of this switch differ based on which Portenta board it is paired with.

Portenta Hat Carrier DIP switch
Portenta Hat Carrier DIP switch

For configurations when the Portenta Hat Carrier is combined with the Portenta X8, the DIP switch governs these settings:

DIP Switch DesignationPosition: ONPosition: OFF
ETH CENTER TAPEthernet DisabledEthernet Enabled
BTSELFlashing Mode (ON)-

Setting the BTSEL switch to the

ON
position will place the board in Flashing Mode, allowing to update the OS Image of the Portenta X8.

When the Portenta Hat Carrier is combined with either the Portenta H7 or C33, the DIP switch adjustments are as follows:

DIP Switch DesignationPosition: ONPosition: OFF
ETH CENTER TAPEthernet EnabledEthernet Disabled
BTSELNot usedNot used

This flexibility ensures that the Portenta Hat Carrier remains adaptable to the unique needs of each paired Portenta board.

Network Connectivity

The Portenta Hat Carrier significantly augments the networking functionalities of the devices within the Portenta family through its integrated Ethernet port. Additionally, the Portenta devices destined for pairing with this carrier are inherently equipped with Wi-Fi® and Bluetooth® capabilities.

Thus, when conceptualizing and executing project developments, the user can proficiently exploit both the wired and wireless communication capabilities. The inherent wireless attributes of the Portenta devices, combined with the carrier's sophisticated onboard components and adaptable protocol choices, enable a comprehensive suite of communication solutions ideal for a wide range of applications.

Ethernet


The Portenta HAT Carrier features a gigabit Ethernet port with an RJ45 connector model TRJG16414AENL with integrated magnetics. These magnetics are crucial for voltage isolation, noise suppression, signal quality maintenance, and rejecting common mode noise, ensuring adherence to waveform standards.

The connector supports the 1000BASE-T standard, complying with IEEE 802.3ab, guaranteeing high-speed, reliable network connections for data-intensive industrial applications.

Portenta Hat Carrier Ethernet Port
Portenta Hat Carrier Ethernet Port

The following table shows an in-depth connector designation:

Pin numberSilkscreenPower NetPortenta HD Standard PinHigh-Density Pin
1N/AGNDGNDJ1-22, J1-31, J1-42, J1-47, J1-54, J2-24, J2-33, J2-44, J2-57, J2-70
2ETH CENTER TAP
3N/AETH_D_PJ1-13
4N/AETH_D_NJ1-15
5N/AETH_C_PJ1-9
6N/AETH_C_NJ1-11
7N/AETH_B_PJ1-5
8N/AETH_B_NJ1-7
9N/AETH_A_PJ1-1
10N/AETH_A_NJ1-3
11N/AETH_LED2J1-19
12N/AGNDGNDJ1-22, J1-31, J1-42, J1-47, J1-54, J2-24, J2-33, J2-44, J2-57, J2-70
13N/AN/A
14N/AETH_LED1J1-17

Ethernet connection speeds differ based on the associated Portenta board:

  • With the Portenta X8: The system supports 1 Gbit Ethernet.
  • When combined with the Portenta H7 or C33: The performance is limited to 100 Mbit Ethernet.

To configure the Ethernet settings, depending on the paired Portenta board, one must use the provided DIP switch on the Portenta Hat Carrier. The following table shows the specific DIP switch configuration needed to enable Ethernet on the carrier:

Mounted Portenta DeviceETH CENTER TAP DIP SWITCH
Portenta X8Position: OFF
Portenta H7/C33Position: ON

For an in-depth understanding of the DIP switch, kindly refer to this section.

It is advisable to connect the Portenta X8 through the Portenta HAT Carrier to a device with DHCP server capabilities, such as a network router, to ease the automatic assignment of an IP address. DHCP will allow the Portenta X8 to communicate with other devices on the network without manual IP configuration. Employing DHCP simplifies device management, supports dynamic reconfiguration, and provides an advantage for applications involving many devices.

In case you want to assign a manual IP to your device, or even create a direct network between your computer and your board, you can follow the multiple procedures available depending on your network devices and operating system.

Ethernet Interface With Linux


Using the Portenta X8 in combination with the Hat Carrier allows you to evaluate the Ethernet speed between your device and your computer in your network. First, ensure that the Portenta X8 is mounted on the Hat Carrier, and then connect them using an RJ45 LAN cable to your local network. Be sure that your computer and your devices are connected to the same network and are on the same IP range, been capable of seeing each other.

Subsequently, open a terminal to access the shell of the Portenta X8 with admin (root) privileges.

1adb shell
2sudo su -

When prompted, enter the password

fio
. To measure the bandwidth, we will use the iperf3 tool, which is available here.

To use the iperf3 tool, we will set the Portenta X8 and Hat Carrier as the Server and the controlling computer as the Client. The commands will measure the bandwidth between the Portenta Hat Carrier with Portenta X8 and the computer. For a deeper understanding of iperf3, refer to its official documentation.

Begin by setting up the Portenta Hat Carrier with Portenta X8 as the Server. For the configuration of the necessary files to establish iperf3 on the device, follow the steps for Linux and Cygwin under General Build Instructions available here. In this case, we need aarch64 / arm64 version, thus we need to execute the following commands:

1mkdir -p ~/bin && source ~/.profile
1wget -qO ~/bin/iperf3 https://github.com/userdocs/iperf3-static/releases/latest/download/iperf3-arm64v8
1chmod 700 ~/bin/iperf3

Once installed, iperf3 will be ready on your device. To ensure it operates without issues, run:

1chmod +x iperf3

By following the provided instructions, the tool should be located in the Linux shell at:

1# ~bin/

Check the tool's version using the following command:

1./iperf3 -v

It should display the version information for the iperf3 tool.

Activate the Portenta Hat Carrier with Portenta X8 as a Server using the command:

1./iperf3 -s

This will set the Server to wait for connections via its IP address. It listens on port

5201
by default. To use an alternative port, append
-p
and your chosen port number:

1./iperf3 -s -p <Port Number>

To identify the IP address of the Portenta X8, you can use either of the following commands and search for

eth0
which provides the network information related to the Ethernet connection:

1ifconfig
2
3# Or
4ip addr show

Next, set up your computer as a Client. In this shared repository, select and download a version suitable for your system, like Windows x64.

Once you extract the content, you will notice the iperf3 file structure as follows:

1iperf3
2 |___bin
3 |___include
4 |___lib
5 |___share

Navigate to bin and launch a terminal to prepare to use the tool. You can now begin a simple speed test using the following command.

1# For Linux shell
2iperf3 -c <Server IP>
3
4# For Windows
5.\iperf3.exe -c <Server IP>

This will set the computer as a Client and connect to the configured IP address. If a specific Port needs to be assigned, the following command will allow you to make such a configuration:

1.\iperf3.exe -c <Server IP> -p <Port Number>

Upon starting the test, you will see the amount of data transferred and the corresponding bandwidth rate. With this, you will be able to verify the Ethernet connection and its performance.

Going forward, we can use the following examples to test out Ethernet connectivity.

If you desire to use Portenta X8 paired with Portenta Hat Carrier, please consider following Python® scripts. These scripts use the

socket
library used to create the socket and establish a computer network.

The below script would be used for Server side (TCP/IP) operations:

1#!/usr/bin/env python3
2
3import socket
4
5def start_server():
6 HOST = '127.0.0.1' # Localhost
7 PORT = 65432 # Port to listen on
8
9 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
10 s.bind((HOST, PORT))
11 s.listen()
12 print('Server is listening...')
13 conn, addr = s.accept()
14 with conn:
15 print('Connected by', addr)
16 while True:
17 data = conn.recv(1024)
18 if not data:
19 break
20 conn.sendall(data)
21
22if __name__ == "__main__":
23 start_server()

The Server-side script is set to wait for incoming connections on

127.0.0.1
(localhost) at port
65432
. These two properties can be modified later at your preference. When a Client connects, the server waits for incoming data and simply sends back whatever it receives, behaving as an echo server.

The script below will be used for Client side (TCP/IP) operations:

1#!/usr/bin/env python3
2
3import socket
4
5def start_client():
6 HOST = '127.0.0.1' # The server's hostname or IP address
7 PORT = 65432 # The port used by the server
8
9 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
10 s.connect((HOST, PORT))
11 s.sendall(b'Hello, server!')
12 data = s.recv(1024)
13
14 print('Received', repr(data))
15
16if __name__ == "__main__":
17 start_client()

The Client-side script connects to the server specified by the

HOST
and
PORT
. These are properties that you change to your preferences. Once connected, it sends a message
"Hello, server!"
and waits for a response.

If you would like to have a single script running both instances, the following script can perform the task using Python®'s built-in

threading
component.

1import socket
2import threading
3
4def server_function():
5 HOST = '127.0.0.1'
6 PORT = 65432
7
8 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
9 s.bind((HOST, PORT))
10 s.listen()
11 print('Server is listening...')
12 conn, addr = s.accept()
13 with conn:
14 print('Connected by', addr)
15 data = conn.recv(1024)
16 if data:
17 print('Server received:', repr(data))
18 conn.sendall(data)
19
20def client_function():
21 HOST = '127.0.0.1'
22 PORT = 65432
23
24 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
25 s.connect((HOST, PORT))
26 s.sendall(b'Hello, server!')
27 data = s.recv(1024)
28 print('Client received:', repr(data))
29
30if __name__ == "__main__":
31 # Start server thread
32 server_thread = threading.Thread(target=server_function)
33 server_thread.start()
34
35 # Wait a bit to ensure server is up before starting client
36 threading.Event().wait(1)
37
38 # Start client function
39 client_function()
40
41 # Join server thread
42 server_thread.join()

The script makes the server start in a separate thread, adding a brief pause using

threading.Event().wait(1)
to confirm it successfully started. It ensures the server is ready to accept connections before the client attempts to connect and send any data.

The client runs on the main thread. Using

server_thread.join()
, the main script waits for the server thread to finish its tasks before exiting.

Ethernet Interface With Arduino IDE


Below is a 'WebClient' example that can be used to test Ethernet connectivity with Portenta H7.

1#include <PortentaEthernet.h>
2#include <Ethernet.h>
3#include <SPI.h>
4
5// Enter a MAC address for your controller below.
6// Newer Ethernet shields have a MAC address printed on a sticker on the shield
7// byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
8
9// if you don't want to use DNS (and reduce your sketch size)
10// use the numeric IP instead of the name for the server:
11//IPAddress server(74,125,232,128); // numeric IP for Google (no DNS)
12char server[] = "www.google.com"; // name address for Google (using DNS)
13
14// Set the static IP address to use if the DHCP fails to assign
15IPAddress ip(192, 168, 2, 177);
16IPAddress myDns(192, 168, 2, 1);
17
18// Initialize the Ethernet client library
19// with the IP address and port of the server
20// that you want to connect to (port 80 is default for HTTP):
21EthernetClient client;
22
23// Variables to measure the speed
24unsigned long beginMicros, endMicros;
25unsigned long byteCount = 0;
26bool printWebData = true; // set to false for better speed measurement
27
28void setup()
29{
30
31 // Open serial communications and wait for port to open:
32 Serial.begin(9600);
33 while (!Serial) {
34 ; // wait for serial port to connect. Needed for native USB port only
35 }
36
37 // start the Ethernet connection:
38 Serial.println("Initialize Ethernet with DHCP:");
39 if (Ethernet.begin() == 0) {
40 Serial.println("Failed to configure Ethernet using DHCP");
41 // Check for Ethernet hardware present
42 if (Ethernet.hardwareStatus() == EthernetNoHardware) {
43 Serial.println("Ethernet shield was not found. Sorry, can't run without hardware");
44 while (true) {
45 delay(1); // do nothing, no point running without Ethernet hardware
46 }
47 }
48 if (Ethernet.linkStatus() == LinkOFF) {
49 Serial.println("Ethernet cable is not connected.");
50 }
51 // try to configure using IP address instead of DHCP:
52 Ethernet.begin(ip, myDns);
53 } else {
54 Serial.print(" DHCP assigned IP ");
55 Serial.println(Ethernet.localIP());
56 }
57 // give the Ethernet shield a second to initialize:
58 delay(1000);
59 Serial.print("connecting to ");
60 Serial.print(server);
61 Serial.println("...");
62
63 // if you get a connection, report back via serial:
64 if (client.connect(server, 80)) {
65 Serial.print("connected to ");
66 Serial.println(client.remoteIP());
67 // Make a HTTP request:
68 client.println("GET /search?q=arduino HTTP/1.1");
69 client.println("Host: www.google.com");
70 client.println("Connection: close");
71 client.println();
72 } else {
73 // if you didn't get a connection to the server:
74 Serial.println("connection failed");
75 }
76 beginMicros = micros();
77}
78
79void loop()
80{
81 // if there are incoming bytes available
82 // from the server, read them and print them:
83 int len = client.available();
84 if (len > 0) {
85 byte buffer[80];
86 if (len > 80)
87 len = 80;
88 client.read(buffer, len);
89 if (printWebData) {
90 Serial.write(buffer, len); // show in the serial monitor (slows some boards)
91 }
92 byteCount = byteCount + len;
93 }
94
95 // if the server's disconnected, stop the client:
96 if (!client.connected()) {
97 endMicros = micros();
98 Serial.println();
99 Serial.println("disconnecting.");
100 client.stop();
101 Serial.print("Received ");
102 Serial.print(byteCount);
103 Serial.print(" bytes in ");
104 float seconds = (float)(endMicros - beginMicros) / 1000000.0;
105 Serial.print(seconds, 4);
106 float rate = (float)byteCount / seconds / 1000.0;
107 Serial.print(", rate = ");
108 Serial.print(rate);
109 Serial.print(" kbytes/second");
110 Serial.println();
111
112 // do nothing forevermore:
113 while (true) {
114 delay(1);
115 }
116 }
117}

The following

Web Client
example can be considered for Portenta C33:

1#include <EthernetC33.h>
2
3// if you don't want to use DNS (and reduce your sketch size)
4// use the numeric IP instead of the name for the server:
5//IPAddress server(74,125,232,128); // numeric IP for Google (no DNS)
6char server[] = "www.google.com"; // name address for Google (using DNS)
7
8// Set the static IP address to use if the DHCP fails to assign
9IPAddress ip(10, 130, 22, 84);
10
11// Initialize the Ethernet client library
12// with the IP address and port of the server
13// that you want to connect to (port 80 is default for HTTP):
14EthernetClient client;
15
16void setup() {
17 // Open serial communications and wait for port to open:
18 Serial.begin(115200);
19
20 while (!Serial) {
21 ; // wait for serial port to connect. Needed for native USB port only
22 }
23
24 bool use_dns = true;
25
26 // start the Ethernet connection:
27 if (Ethernet.begin() == 0) {
28 Serial.println("Failed to configure Ethernet using DHCP");
29 // try to configure using IP address instead of DHCP:
30 // IN THAT CASE YOU SHOULD CONFIGURE manually THE DNS or USE the IPAddress Server variable above
31 // that is what is automatically done here...
32 Ethernet.begin(ip);
33 use_dns = false;
34 }
35 // give the Ethernet shield a second to initialize:
36 delay(2000);
37 Serial.println("connecting...");
38
39 Serial.print("Your DNS server is: ");
40 Serial.println(Ethernet.dnsServerIP());
41
42 bool connect_result = false;
43
44 if(use_dns) {
45 connect_result = client.connect(server, 80);
46 }
47 else {
48 connect_result = client.connect(IPAddress(74,125,232,128), 80);
49 }
50
51 // if you get a connection, report back via serial:
52 if (client.connect(server, 80)) {
53 Serial.println("connected");
54 // Make a HTTP request:
55 client.println("GET /search?q=arduino HTTP/1.1");
56 client.println("Host: www.google.com");
57 client.println("Connection: close");
58 client.println();
59 } else {
60 // if you didn't get a connection to the server:
61 Serial.println("connection failed");
62 }
63}
64
65/* just wrap the received data up to 80 columns in the serial print*/
66void read_request() {
67 uint32_t received_data_num = 0;
68 while (client.available()) {
69 /* actual data reception */
70 char c = client.read();
71 /* print data to serial port */
72 Serial.print(c);
73 /* wrap data to 80 columns*/
74 received_data_num++;
75 if(received_data_num % 80 == 0) {
76 Serial.println();
77 }
78 }
79}
80
81void loop() {
82
83 read_request();
84
85 // if the server's disconnected, stop the client:
86 if (!client.connected()) {
87 Serial.println();
88 Serial.println("disconnecting.");
89 client.stop();
90
91 // do nothing forevermore:
92 while (true);
93 }
94}

Wi-Fi® & Bluetooth®


The Portenta Hat Carrier is designed to work flawlessly with wireless features. Among its numerous advantages is its capacity to use Wi-Fi® and Bluetooth® technologies present in the Portenta models like X8, H7, or C33. When these wireless options are activated, they can be effectively combined with the intrinsic capabilities and features that the carrier offers. This combination makes this solution more versatile and powerful for many different projects.

This integration not only broadens the spectrum of use cases for the Portenta Hat Carrier but also ensures that developers can use robust wireless communications in their applications. The effectiveness of onboard capabilities with these wireless features makes the Portenta Hat Carrier an indispensable tool for developers looking for versatile and powerful connectivity solutions.

For a comprehensive understanding of these connectivity options, kindly refer to the specific documentation for each Portenta model.

Pins

The Portenta Hat Carrier is a versatile platform, and a significant feature of this carrier is its extensive pin availability. These pins provide a range of functionalities, including power, I/Os, communication, and more.

Portenta Hat Carrier Back Side
Portenta Hat Carrier Back Side

In this section we will examine the 40 pin and 16 pin headers of the Portenta Hat Carrier. These headers are integral to the carrier's interfacing capabilities, providing diverse connectivity options for various applications.

40-Pin Header

The Portenta Hat Carrier provides a 40 pin header that serves as an important interface for numerous applications.

Portenta Hat Carrier 40-Pin Header
Portenta Hat Carrier 40-Pin Header

To make it easier for developers, here is a comprehensive breakdown of the 40-pin header:

Pin DescriptionPinPinPin Description
VCC (+3V3_PORTENTA)12VIN (+5V)
I2C2_SDA (I2C 2 SDA)34VIN (+5V)
I2C2_SCL (I2C 2 SCL)56GND
PWM0 (PWM_0)78SERIAL3_TX (TX3 - UART 3 TX)
GND910SERIAL3_RX (RX3 - UART 3 RX)
GPIO21112I2S_CK
GPIO61314GND
SAI_D01516SAI_CK
VCC (+3V3_PORTENTA)1718SAI_FS
SPI1_MOSI (SPI1 COPI)1920GND
SPI1_MISO (SPI1 CIPO)2122PWM1 (PWM_1)
SPI1_CK (SPI1 SCK)2324SPI1_CS (SPI1 CE)
GND2526PWM2 (PWM_2)
I2C0_SDA (I2C 0 SDA)2728I2C0_SCL (I2C 0 SCL)
SERIAL1_RX (RX1- UART 1 RX)2930GND
PWM3 (PWM_3)3132SERIAL1_TX (TX1- UART 1 TX)
PWM4 (PWM_4)3334GND
I2S_WS (I2S WS)3536PWM5 (PWM_5)
PWM6 (PWM_6)3738I2S_SDI (I2S SDI)
GND3940I2S_SDO (I2S SDO)

This layout is designed to ensure that developers have a clear understanding of each pin and its function.

16-Pin Header

The Portenta Hat Carrier features a 16-pin male header connector dedicated to analog input but also offers a variety of other functionalities. The table below provides a detailed mapping:

Pin DescriptionPinsPinsPin Description
ANALOG_A0 (A0)12ANALOG_A1 (A1)
ANALOG_A2 (A2)34ANALOG_A3 (A3)
ANALOG_A4 (A4)56ANALOG_A5 (A5)
ANALOG_A6 (A6)78ANALOG_A7 (A7)
PWM7 (PWM_7)910PWM8 (PWM_8)
LICELL (RTC Power Source)1112GPIO0 (PWM4)
VCC (+3V3_PORTENTA)1314SERIAL2_TX (TX2 - UART 2 TX)
GND1516SERIAL2_RX (RX2 - UART 2 RX)

A visual representation of the header can be seen in the image below.

Portenta Hat Carrier 16-Pin Header
Portenta Hat Carrier 16-Pin Header

It is characterized as follows:

  • Analog Pins: It integrates eight dedicated pins for analog channels. It ranges from A0 ~ A7, and each of these pins serves a unique analog channel, facilitating a range of analog signal measurements.

  • PWM Pins: Integrates dedicated PWM pins within the header. Pin 9 is labeled PWM7 (PWM_7), and Pin 10 is identified as PWM8 (PWM_8).

    Additionally, Pin 12, although a General-Purpose Input/Output (GPIO0), also supports PWM and is labeled as PWM4.

  • Serial Pins: It integrates UART 2 functionalities. Pin 14 is the transmit function, identified as SERIAL2_TX or TX2, while Pin 16 is dedicated to the receive function, labeled as SERIAL2_RX or RX2.

  • Power and Grounding: Pin 11, labeled as LICELL, serves as the Real Time Clock (RTC) power source.

    For providing a voltage source, Pin 13 offers a 3.3 V output, specifically for the Portenta module, and is marked as VCC (+3V3_PORTENTA). The Ground for this header is accessible via Pin 15, designated simply as GND.

GPIO Pins

Understanding and managing the General-Purpose Input/Output (GPIO) pins on your device can be crucial for many applications. The following script is designed to display all the GPIOs available on the 40-pin connector of the Portenta Hat Carrier paired with Portenta X8.

Using Linux


Next conditions will help you properly set the hardware to test GPIO controls:

  1. Begin by positioning the Portenta-X8 securely onto the Portenta Hat Carrier. Make sure the High-Density connectors are securely connected.

  2. Each GPIO on the Portenta Hat Carrier is versatile and robust, designed to safely accept input voltages ranging between 0.0 V and 3.3 V. This input range ensures compatibility with an array of sensors and external devices.

  3. To prevent floating states and offer a consistent and predictable behavior, internal pull-ups are automatically enabled on all input pins. This default configuration means that, unless actively driven low, the pins will naturally read as high (or 3.3 V).

When all conditions are set and in place, access the Portenta X8's shell with admin (root) access as follows:

1adb shell
2sudo su -

Enter the password

fio
when prompted. Next, access the
x8-devel
Docker container with the command:

1docker exec -it x8-devel sh

Navigate to the directory containing the GPIO example, named

gpios.py
:

1cd root/examples/portenta-hat-carrier

Run the

gpios.py
script to read the status of all available GPIOs on the 40-pin header:

1#!/usr/bin/env python3
2
3# created 12 October 2023
4# by Riccardo Mereu & Massimo Pennazio
5
6import os
7
8if os.environ['CARRIER_NAME'] != "rasptenta":
9 print("This script requires Portenta HAT carrier")
10 exit(1)
11
12import Portenta.GPIO as GPIO
13
14GPIO.setmode(GPIO.BCM)
15
16all_pins_in_header = [2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27]
17
18GPIO.setup(all_pins_in_header, GPIO.IN)
19
20for pin in all_pins_in_header:
21 try:
22 print(GPIO.input(pin))
23 except RuntimeError as e:
24 print("Error reading GPIO %s: %s" % (str(pin), str(e)))
25
26GPIO.cleanup()

This script will help you verify the following considerations:

  • Avoid manually checking each pin by having a consolidated overview of all GPIOs' statuses.

  • By staying within the specified voltage range, you ensure the longevity of your device and prevent potential damages.

  • With the default pull-ups, you can be confident in your readings, knowing that unintentional fluctuations are minimized.

By employing this script, not only do you gain a deeper insight into the state of your GPIOs, but you also save valuable time and reduce the margin for error.

Whether you are debugging, prototyping, or setting up a new project, this script is an invaluable tool for all Portenta Hat Carrier users.

You can also retrieve information about the available GPIOs on the Portenta X8's shell using the following command:

1cat /sys/kernel/debug/gpio

Using Arduino IDE


If Portenta Hat Carrier is paired with Portenta H7 or Portenta C33, consider using the following example:

1const int actPin = 2; // the number of the activation pin (GPIO)
2const int ledPin = <PD_5/30>; // User programmable LED GPIO3 corresponding to paired Portenta board
3int actState = 0; // variable for reading the activation status
4
5void setup() {
6 pinMode(ledPin, OUTPUT); // initialize the User programmable LED of Portenta Hat Carrier
7 pinMode(actPin, INPUT); // initialize the activation pin as an input
8}
9
10void loop() {
11 actState = digitalRead(actPin); // read the state of the activation value
12
13 if (actState == HIGH) { // if the GPIO pin has feedback
14 digitalWrite(ledPin, HIGH);
15 } else {
16 digitalWrite(ledPin, LOW);
17 }
18}

This example uses a designated GPIO pin to set the user-programmable LED on the Portenta Hat Carrier to either a HIGH or LOW state.

Alternatively, the following example controls the user-programmable LED on the Portenta Hat Carrier based on potentiometer input:

1const int potPin = A0; // the number of the potentiometer pin (16-Pin header)
2const int ledPin = <PD_5/30>; // User programmable LED GPIO3 corresponding to paired Portenta board
3
4int potValue = 0; // value read from the potentiometer
5int ledThreshold = 0; // PWM value for the LED brightness (0 to 255)
6
7void setup() {
8 pinMode(ledPin, OUTPUT); // initialize the User programmable LED of Portenta Hat Carrier
9 // choose either PD_5 for H7, or 30 for C33
10}
11
12void loop() {
13 potValue = analogRead(potPin); // read the pot value (0 to 1023)
14 ledThreshold = map(potValue, 0, 1023, 0, 255); // scale it for user programmable LED GPIO3 activation threshold
15
16 if (ledThreshold >= 128){
17 digitalWrite(ledPin, HIGH); // set the GPIO3 LED High if potentiometer is mapped above 128 value
18 } else {
19 digitalWrite(ledPin, LOW); // set the GPIO3 LED Low if potentiometer is mapped below 128 value
20 }
21 delay(10);
22}

PWM Pins

The Portenta Hat Carrier has 10 digital pins with PWM functionality, mapped as follows:

Pin numberSilkscreenPortenta HD Standard PinHigh-Density Pin
40-Pin Header
7PWM0PWM_0J2-59
22PWM1PWM_1J2-61
26PWM2PWM_2J2-63
31PWM3PWM_3J2-65
33PWM4PWM_4J2-67
36PWM5PWM_5J2-60
37PWM6PWM_6J2-62
16-Pin Header
9PWM7PWM_7J2-64
10PWM8PWM_8J2-66
12PWM4GPIO_0J2-46

Please, refer to the board pinout section of the user manual to find them on the board. All these pins must be configured on the corresponding Portenta.

Using Linux


The following Python® script is designed to control the brightness of a device, such as an LED, by varying the duty cycle of a PWM signal in a Linux environment on Portenta X8.

The script sets up the PWM channel, defines its period, and then, within a loop, modulates the brightness by adjusting the duty cycle. Consider the script below as an example:

1#!/usr/bin/env python3
2
3import time
4import subprocess
5
6# Define the PWM chip, channel, and other parameters
7PWM_CHIP = 0
8PWM_CHANNEL = 9 # Replace with the correct channel if necessary
9BASE_PATH = f"/sys/class/pwm/pwmchip{PWM_CHIP}/pwm{PWM_CHANNEL}"
10PERIOD = 1000000 # 1 second in nanoseconds
11FADE_DURATION = 0.03 # 30 milliseconds
12
13# Define brightness and fade amount variables
14brightness = 0
15fadeAmount = 5 * (PERIOD // 255) # Scale fadeAmount for our period
16
17def setup_pwm():
18 subprocess.run(f"echo {PWM_CHANNEL} | sudo tee /sys/class/pwm/pwmchip{PWM_CHIP}/export", shell=True)
19 subprocess.run(f"echo {PERIOD} | sudo tee {BASE_PATH}/period", shell=True)
20 subprocess.run(f"echo 0 | sudo tee {BASE_PATH}/duty_cycle", shell=True)
21 subprocess.run(f"echo 1 | sudo tee {BASE_PATH}/enable", shell=True)
22
23def set_pwm_brightness(brightness_value):
24 duty_cycle = brightness_value * (PERIOD // 255)
25 subprocess.run(f"echo {duty_cycle} | sudo tee {BASE_PATH}/duty_cycle", shell=True)
26
27if __name__ == "__main__":
28 setup_pwm()
29 try:
30 while True:
31 set_pwm_brightness(brightness)
32 brightness += fadeAmount
33 if brightness <= 0 or brightness >= 255:
34 fadeAmount = -fadeAmount
35 time.sleep(FADE_DURATION)
36 except KeyboardInterrupt:
37 print("Exiting")

Using Arduino IDE


The [

analogWrite()
function included in the Arduino programming language can be used to access the PWM pins.

The example code shown below grabs a pin compatible with PWM functionality to control the brightness of an LED connected to it:

1const int ledPin = <PWM_X>; // Use a pin that supports PWM
2
3void setup() {
4 pinMode(ledPin, OUTPUT); // Configure the pin as OUTPUT
5}
6
7void loop() {
8 // Increase brightness
9 for (int brightness = 0; brightness <= 255; brightness++) {
10 analogWrite(ledPin, brightness);
11 delay(10);
12 }
13
14 // Decrease brightness
15 for (int brightness = 255; brightness >= 0; brightness--) {
16 analogWrite(ledPin, brightness);
17 delay(10);
18 }
19}

JTAG Pins

For developers aiming to investigate and understand the intricate details of development, the Portenta Hat Carrier features a built-in JTAG interface. This tool is crucial for hardware debugging, offering real-time observation. Through the JTAG pins, users can smoothly debug and program, guaranteeing accurate and optimal device performance.

Portenta Hat Carrier onboard JTAG pin
Portenta Hat Carrier onboard JTAG pin

The pins used for the JTAG debug port on the Portenta Hat Carrier are the following:

Pin numberPower NetPortenta HD Standard PinHigh-Density PinInterface
1+3V3_PORTENTAVCCJ2-23, J2-34, J2-43, J2-69
2JTAG_SWDJ1-75JTAG SWD
3GNDGNDJ1-22, J1-31, J1-42, J1-47, J1-54, J2-24, J2-33, J2-44, J2-57, J2-70
4JTAG_SCKJ1-77JTAG SCK
5GNDGNDJ1-22, J1-31, J1-42, J1-47, J1-54, J2-24, J2-33, J2-44, J2-57, J2-70
6JTAG_SWOJ1-79JTAG SWO
7NCNC
8JTAG_TDIJ1-78JTAG TDI
9JTAG_TRSTJ1-80JTAG TRST
10JTAG_RSTJ1-73JTAG RST

Understanding Device Tree Blobs (DTB) Overlays

Device Tree Blobs (DTB) And DTB Overlays

In the world of embedded systems, U-boot and the Linux kernel use a concept called Device Tree Blobs (DTB) to describe a board's hardware configuration. This approach allows for a unified main source tree to be used across different board configurations, ensuring consistency.

The boards, acting as carriers, allow various peripherals to be connected, such as temperature sensors or accelerometers. These carriers serve as expansion connectors. You might want to connect various peripherals and be able to add or remove them easily.

The concept of modularity is applied to the DTB, resulting in DTB overlays. The hardware configuration is split into multiple small files, each representing a different peripheral or function in the form of a DTB overlay.

During the early boot stage, these overlays are merged together into a single DTB and loaded into RAM. This approach enables users to select and change configurations with ease. However, it is important to note that changing the hardware configuration requires a system reboot to maintain system stability.

Handling DTB Overlays

To modify and maintain the Device Tree Blob (DTB) overlays of your Portenta X8 so it can support different hardware and devices, please read and execute the following steps.

Custom DTB Overlays

In cases where the required DTB overlay is not readily available and a specific configuration that is not part of the pre-compiled set is needed, it is possible to create customized DTB overlays.

DTB overlays originate from readable source files known as DTS files. Users with the respective experience can modify these DTS files and cross-compile them to create tailored overlays suited to their needs.

Automated Load And Carrier Detection

U-boot can be configured to automatically load specific DTB overlays based on the carrier board it detects, in this case the Portenta Hat Carrier, either by probing specific hardware or by reading an identification ID from an EEPROM.

For instance, for a Portenta-X8 placed on a Portenta HAT Carrier, upon logging into the board and executing subsequent commands on the shell, the expected output is as follows:

1fw_printenv overlays
2overlays=ov_som_lbee5kl1dx ov_som_x8h7 ov_carrier_rasptenta_base
3
4fw_printenv carrier_name
5carrier_name=rasptenta
6
7fw_printenv is_on_carrier
8is_on_carrier=yes

This information is written by U-boot during boot in a step referred to as auto carrier detection. You can modify the variables from user space, but after a reboot, they revert to their default state unless the

carrier_custom
variable is set to
1
.

1fw_setenv carrier_custom 1

This serves as an escape mechanism to enable user-based configurations.

1fw_setenv carrier_custom 1
2fw_setenv carrier_name rasptenta
3fw_setenv is_on_carrier yes
4fw_setenv overlays "ov_som_lbee5kl1dx ov_som_x8h7 ov_carrier_rasptenta_base ov_carrier_rasptenta_pwm_fan ov_carrier_rasptenta_ov5647_camera_mipi ov_rasptenta_iqaudio_codec"

The commands above enable functionalities such as a speed-controlled fan connector, an OV5647 based RPi v1.3 camera, and an IQ Audio Codec Zero audio HAT.

Hardware Configuration Layers

Hardware configuration is divided into the following layers:

  • Layer 0: System on Module (SoM), prefixed with
    ov_som_
    .
  • Layer 1: Carrier boards, prefixed with
    ov_carrier_
    .
  • Layer 2: HATs and Cameras, which is usually a concatenation of the carrier name and the hat name or functionality.

EEPROMs, which store identification IDs, are typically defined on Layer 1 and accessible on I2C1. Some HATs may also have EEPROMs according to the Raspberry Pi® standard (ID_SD, ID_SC), accessible on I2C0.

There are some overlays which add specific functionalities. For example:

  • ov_som_lbee5kl1dx
    : Adds Wi-Fi®
  • ov_som_x8h7
    : Adds the H7 external microcontroller
  • ov_carrier_rasptenta_base
    : Base support for Portenta Hat Carrier

When no known carrier is detected and the Portenta X8 is mounted as the main board, the first two overlays mentioned above are applied by default.

Distinction Between System And Hardware Configuration


The distinction between system and hardware configuration is crucial. System configuration includes settings such as user creation and Wi-Fi® passwords, whereas hardware configuration is explicitly defined through the device tree.

In production environments, the addition of custom compiled device tree overlays is restricted to maintain system integrity and security.

Raspberry Pi® HAT

The Portenta Hat Carrier is notable for its compatibility with Hardware Attached on Top (HAT) add-on boards.

A Hardware Attached on Top (HAT) is known as a standardized add-on module designed to be interfaced with compatible host systems. The HAT concept can be understood as a modular approach to hardware extension.

Following certain design rules to specific mechanical and electronic design criteria, a HAT usually has a built-in memory chip (EEPROM). This allows the host system to automatically recognize and potentially configure itself corresponding to the attached module.

The main objective of a HAT is to augment the functionalities of the host device, allowing for capabilities ranging from sensor integration and display enhancements to advanced audio processing and communication features.

The standardized design of HATs ensures they are compatible and easy to use with various devices. This makes them suitable for both seasoned professionals and enthusiasts.

Portenta Hat Carrier with Audio Hat

These is the officially compatible list of HATs:

  • Stepper Motor HAT: it is a HAT that drives stepper motors, enabling precise control of rotation direction, angle, speed, and steps, suitable for projects like CNC, 3D printers, and robotics. It uses a DRV8825 dual H-bridge motor driver.

  • RPi Relay Board: it is a HAT that eases the control of high-voltage devices, featuring three channels, and photo coupling isolation. It helps provide safe device switching for various applications.

The example scripts are located within the Docker container. To access these scripts and test them with the Hat mounted, execute the following command:

1docker exec -it x8-devel sh

Upon gaining access to the container, all relevant Portenta Hat Carrier scripts are conveniently located in the

root/examples/portenta-hat-carrier
directory. Alternatively, you also have the option to manually dockerize the scripts to run them within ADB shell of the Portenta X8.

To use the example script, a series of commands can be used. These will help you define and export necessary modules before executing the desired example. To start, the Python® shell can be launched within the container using:

1python3

To use modules like smbus2, the following sequence of commands will help you import such modules:

1import smbus2

To use a specific class or function from the module:

1from smbus2 import SMBus

Setting variables is straightforward. Depending on your requirement, assign values in either HEX or DEC:

1# Value can be in HEX or DEC
2variable_name = Value

To run an example script within the Python® shell, consider using the following command:

1with open('example_script.py', 'r') as file:
2... exec(file.read())

If you prefer traditional methods of execution, the following command can be used:

1python3 example_script.py

This last command for example is also applicable within ADB shell of the Portenta X8.

The following sections will help you become familiar with the examples found within the

root/examples/portenta-hat-carrier
directory. This directory contains both the RPi Relay Board and the Stepper Motor HAT. These examples are used within the Linux environment.

RPi Relay Board

The RPi Relay Board is a dynamic board that consists of three channels: a high-level trigger and two low-level triggers. With the capability to manage up to 250V AC or 30V DC with a current rating of 2A, it operates using the I2C interface, renowned for its high-quality relays.

The continuing script offers an interface for interaction with the Seeed Studio Raspberry Pi Relay Board. Authored by John M. Wargo, it is a refined version of the sample code available on the Seeed Studio Wiki. The script uses the smbus library to interface with the relay board and has a default I2C address set to

0x20
.

It can interact with up to four relay ports on the board. Among its various features, it can turn a specific relay on or off, toggle all relays simultaneously, toggle a particular relay's state, and retrieve the status of any relay. Furthermore, it has built-in error handling to ensure that a valid integer relay number is specified.

1from __future__ import print_function
2
3from smbus2 import SMBus
4
5# The number of relay ports on the relay board.
6# This value should never change!
7NUM_RELAY_PORTS = 4
8
9# Change the following value if your Relay board uses a different I2C address.
10DEVICE_ADDRESS = 0x20 # 7 bit address (will be left shifted to add the read write bit)
11
12# Don't change the values, there's no need for that.
13DEVICE_REG_MODE1 = 0x06
14DEVICE_REG_DATA = 0xff
15
16bus = SMBus(3) # 0 = /dev/i2c-0 (port I2C0), 1 = /dev/i2c-1 (port I2C1)
17
18
19def relay_on(relay_num):
20 global DEVICE_ADDRESS
21 global DEVICE_REG_DATA
22 global DEVICE_REG_MODE1
23
24 if isinstance(relay_num, int):
25 # do we have a valid relay number?
26 if 0 < relay_num <= NUM_RELAY_PORTS:
27 print('Turning relay', relay_num, 'ON')
28 DEVICE_REG_DATA &= ~(0x1 << (relay_num - 1))
29 bus.write_byte_data(DEVICE_ADDRESS, DEVICE_REG_MODE1, DEVICE_REG_DATA)
30 else:
31 print('Invalid relay #:', relay_num)
32 else:
33 print('Relay number must be an Integer value')
34
35
36def relay_off(relay_num):
37 global DEVICE_ADDRESS
38 global DEVICE_REG_DATA
39 global DEVICE_REG_MODE1
40
41 if isinstance(relay_num, int):
42 # do we have a valid relay number?
43 if 0 < relay_num <= NUM_RELAY_PORTS:
44 print('Turning relay', relay_num, 'OFF')
45 DEVICE_REG_DATA |= (0x1 << (relay_num - 1))
46 bus.write_byte_data(DEVICE_ADDRESS, DEVICE_REG_MODE1, DEVICE_REG_DATA)
47 else:
48 print('Invalid relay #:', relay_num)
49 else:
50 print('Relay number must be an Integer value')
51
52
53def relay_all_on():
54 global DEVICE_ADDRESS
55 global DEVICE_REG_DATA
56 global DEVICE_REG_MODE1
57
58 print('Turning all relays ON')
59 DEVICE_REG_DATA &= ~(0xf << 0)
60 bus.write_byte_data(DEVICE_ADDRESS, DEVICE_REG_MODE1, DEVICE_REG_DATA)
61
62
63def relay_all_off():
64 global DEVICE_ADDRESS
65 global DEVICE_REG_DATA
66 global DEVICE_REG_MODE1
67
68 print('Turning all relays OFF')
69 DEVICE_REG_DATA |= (0xf << 0)
70 bus.write_byte_data(DEVICE_ADDRESS, DEVICE_REG_MODE1, DEVICE_REG_DATA)
71
72
73def relay_toggle_port(relay_num):
74 print('Toggling relay:', relay_num)
75 if relay_get_port_status(relay_num):
76 # it's on, so turn it off
77 relay_off(relay_num)
78 else:
79 # it's off, so turn it on
80 relay_on(relay_num)
81
82
83def relay_get_port_status(relay_num):
84 # determines whether the specified port is ON/OFF
85 global DEVICE_REG_DATA
86 print('Checking status of relay', relay_num)
87 res = relay_get_port_data(relay_num)
88 if res > 0:
89 mask = 1 << (relay_num - 1)
90 # return the specified bit status
91 # return (DEVICE_REG_DATA & mask) != 0
92 return (DEVICE_REG_DATA & mask) == 0
93 else:
94 # otherwise (invalid port), always return False
95 print("Specified relay port is invalid")
96 return False
97
98
99def relay_get_port_data(relay_num):
100 # gets the current byte value stored in the relay board
101 global DEVICE_REG_DATA
102 print('Reading relay status value for relay', relay_num)
103 # do we have a valid port?
104 if 0 < relay_num <= NUM_RELAY_PORTS:
105 # read the memory location
106 DEVICE_REG_DATA = bus.read_byte_data(DEVICE_ADDRESS, DEVICE_REG_MODE1)
107 # return the specified bit status
108 return DEVICE_REG_DATA
109 else:
110 # otherwise (invalid port), always return 0
111 print("Specified relay port is invalid")
112 return 0

Next script showcases the utility of the relay board functions. At the onset, it activates all the relays and then deactivates them, pausing for a second between these actions.

Subsequently, it sequentially powers each relay on and off, with a one-second intermission in between. In the event of a keyboard interrupt, the script terminates and ensures all the relays are switched off.

1#!/usr/bin/python
2
3from __future__ import print_function
4
5import sys
6import time
7
8from relay_lib_seeed import *
9
10
11def process_loop():
12 # turn all of the relays on
13 relay_all_on()
14 # wait a second
15 time.sleep(1)
16 # turn all of the relays off
17 relay_all_off()
18 # wait a second
19 time.sleep(1)
20
21 # now cycle each relay every second in an infinite loop
22 while True:
23 for i in range(1, 5):
24 relay_on(i)
25 time.sleep(1)
26 relay_off(i)
27
28
29# Now see what we're supposed to do next
30if __name__ == "__main__":
31 try:
32 process_loop()
33 except KeyboardInterrupt:
34 # tell the user what we're doing...
35 print("\nExiting application")
36 # turn off all of the relays
37 relay_all_off()
38 # exit the application
39 sys.exit(0)

For implementation, this script draws functions from

relay_lib_seeed
, which was previously explained. Together, these scripts simplify the control of the RPi Relay Board.

Stepper Motor HAT

Using the capabilities of the Portenta X8 alongside specific modules can increase its performance. One such module is the drv8825 HAT, designed specifically for driving stepper motors, especially when paired with the Portenta Hat Carrier.

To use the drv8825 HAT with Portenta Hat Carrier and Portenta X8, please follow these steps:

  1. Place the Portenta X8 onto the Portenta Hat Carrier.

  2. Align and position the drv8825 HAT on the Portenta Hat Carrier. Make sure to align its 40-Pin header.

  3. Proceed by wiring the motor poles. This can be done using either the A1-A2, B1-B2 configurations or the alternative A3-B3, A4-B4 setups. Proper wiring is crucial for achieving the desired rotational motion in the motor.

  4. To ensure the system is powered on, connect an external power source using the VIN-GND terminals. This powers both the Portenta X8 and the drv8825 HAT, securing stable electrical performance.

  5. One distinguishing feature of the drv8825 HAT is the provision for micro-stepping. This feature enhances the precision of motor operations.

    Adjust the micro-stepping settings using the DIP switches present on the drv8825 HAT to achieve the desired level of granularity in motor steps.

Once the hardware setup is ready, use the script below to perform a test run of the connected stepper motor:

1#!/usr/bin/env python3
2
3# created 12 October 2023
4# by Riccardo Mereu & Massimo Pennazio
5
6import os
7
8if os.environ['CARRIER_NAME'] != "rasptenta":
9 print("This script requires Portenta HAT carrier")
10 exit(1)
11
12from rpi_python_drv8825.stepper import StepperMotor
13
14motor = StepperMotor(enable_pin, step_pin, dir_pin, mode_pins, step_type, fullstep_delay)
15
16motor.enable(True) # enables stepper driver
17motor.run(6400, True) # run motor 6400 steps clowckwise
18motor.run(6400, False) # run motor 6400 steps counterclockwise
19motor.enable(False) # disable stepper driver

The parameters for the stepper motor must be defined. Consider the following parameters as an example:

1enable_pin = 12
2step_pin = 23
3dir_pin = 24
4mode_pins = (14, 15, 18)
5step_type = '1/32'
6fullstep_delay = .005

For a comprehensive understanding, and perhaps to delve into advanced configurations, Waveshare's Stepper Motor HAT (B) Wiki is an excellent resource. It provides extensive insights and details about the drv8825 HAT.

Communication

The Portenta Hat Carrier extends multiple communication protocols from the Portenta core board. These protocols are Serial Peripheral Interface (SPI), Inter-Integrated Circuit (I2C), Universal Asynchronous Receiver-Transmitter (UART), JTAG interface, and MIPI tailored for the Portenta X8 camera compatibility. This section offers further details on these protocols.

Dedicated pins are provided as well on the Portenta Hat Carrier for each communication protocol. They are easily accessible via the 40-pin male header connectors, simplifying the process of interfacing with various components, peripherals, and sensors. Additionally, a 16-pin header connector is present to support analog pins, PWM pins, LICELL, and serial communication.

SPI

The Portenta Hat Carrier supports SPI communication via two dedicated ports named

SPI0
and
SPI1
. Both ports are available via High-Density connectors, while
SPI1
is also available over 40-pin connector. This allows data transmission between the board and other SPI-compatible devices. The pins used in the Portenta Hat Carrier for the SPI communication protocol are the following:

Pin numberSilkscreenPortenta HD Standard PinHigh-Density PinInterface
19SPI1 COPISPI1_MOSIJ2-42SPI 1 MOSI
21SPI1 CIPOSPI1_MISOJ2-40SPI 1 MISO
23SPI1 SCKSPI1_CKJ2-38SPI 1 CK
24SPI1 CESPI1_CSJ2-36SPI 1 CS

Please, refer to the board pinout section of the user manual to find them on the board.

Using Linux


With admin (root) access, you can use the following commands within the shell for the Portenta X8:

1sudo modprobe spidev

Present sequence of commands is used to enable the SPI device interface on the Portenta X8. After adding the

spidev
module to the system's configuration, the system is rebooted to apply the changes.

1echo "spidev" | sudo tee > /etc/modules-load.d/spidev.conf
2sudo systemctl reboot

Following section configures a service named

my_spi_service
to use the SPI device available at
/dev/spidev0.0
.

1services:
2 my_spi_service:
3 devices:
4 - '/dev/spidev0.0'

Using Arduino IDE


Include the

SPI
library at the top of your sketch to use the SPI communication protocol. This can be used with Portenta H7 or C33. The SPI library provides functions for SPI communication:

1#include <SPI.h>

The

setup()
function compiles initial SPI processes, by setting the chip select (`CS) with the proper configuration.

1void setup() {
2 // Set the chip select pin as output
3 pinMode(SS, OUTPUT);
4
5 // Pull the CS pin HIGH to unselect the device
6 digitalWrite(SS, HIGH);
7
8 // Initialize the SPI communication
9 SPI.begin();
10}

Following commands can be used to establish transmission and exchange information with an SPI-compatible device.

1// Replace with the target device's address
2byte address = 0x00;
3
4// Replace with the value to send
5byte value = 0xFF;
6
7// Pull the CS pin LOW to select the device
8digitalWrite(SS, LOW);
9
10// Send the address
11SPI.transfer(address);
12
13// Send the value
14SPI.transfer(value);
15
16// Pull the CS pin HIGH to unselect the device
17digitalWrite(SS, HIGH);

I2S

I2S, short for Inter-IC Sound, connects digital audio devices using an electrical serial bus interface.

It operates using three main lines:

  • SCK (Serial Clock) or BCLK: the clock signal.
  • WS (Word Select) or FS (Frame Select): Differentiates data for the Right or Left Channel.
  • SD (Serial Data): transmits the audio data.

The Controller generates the SCK and WS signals, and its frequency is derived from SampleRate x Bits Per Channel x Number of Channels.

In an I2S setup, while one device acts as the Controller, others are in Peripheral mode. Audio data samples can vary from 4 to 32 bits, with higher sample rates and bits offering superior audio quality.

The pins used in the Portenta Hat Carrier for the I2S communication protocol are the following:

Pin numberSilkscreenPortenta HD Standard PinHigh-Density PinInterface
12I2S CKI2S_CKJ1-56I2S CK
35I2S WSI2S_WSJ1-58I2S WS
38I2S SDII2S_SDIJ1-60I2S SDI
40I2S SDOI2S_SDOJ1-62I2S SDO

SAI - Serial Audio Interface

Serial Audio Interface (SAI) is a versatile protocol for transmitting audio data between digital components. Unlike the fixed I2S standard, SAI supports multiple audio data formats and configurations. The carrier works with the following data lines:

  • D0: This serves as the primary data line, transmitting or receiving audio data.
  • CK (BCLK): The Bit Clock, governing the rate of individual audio data bit transmission or reception.
  • FS: Frame Sync, marking the boundary of audio frames, often differentiating channels in stereo audio.

SAI protocol can operate both synchronously and asynchronously, adjusting to various audio system needs. Due to its adaptability, SAI suits complex audio tasks, systems with multi-channel requirements, and specific audio formats.

In essence, SAI offers greater flexibility than I2S, catering to a broader range of audio system configurations.

The pins used in the Portenta Hat Carrier for the SAI protocol are the following:

Pin numberSilkscreenPortenta HD Standard PinHigh-Density PinInterface
15SAI D0SAI_D0J2-53SAI D0
16SAI CKSAI_CKJ2-49SAI CK
18SAI FSSAI_FSJ2-51SAI FS

I2C

The Portenta Hat Carrier supports I2C communication, which allows data transmission between the board and other I2C-compatible devices. The pins used in the Portenta Hat Carrier for the I2C communication protocol are the following:

Pin numberSilkscreenPortenta HD Standard PinHigh-Density PinInterface
27I2C0 SDAI2C0_SDAJ1-44I2C 0 SDA
28I2C0 SCLI2C0_SCLJ1-46I2C 0 SCL
3I2C2 SDAI2C2_SDAJ2-45I2C 2 SDA
5I2C2 SCLI2C2_SCLJ2-47I2C 2 SCL

Please, refer to the pinout section of the user manual to find them on the board. The I2C pins are also available through the onboard ESLOV connector of the Portenta Hat Carrier.

Portenta Hat Carrier CAN Bus Interface Connection Example
Portenta Hat Carrier CAN Bus Interface Connection Example

Using Linux


For the Portenta X8, it is possible to use the following commands within the shell when you have admin (root) access:

1sudo modprobe i2c-dev

Present sequence of commands is used to enable the I2C device interface on the Portenta X8. After adding the

i2c-dev
module to the system's configuration, the system is rebooted to apply the changes.

1echo "i2c-dev" | sudo tee > /etc/modules-load.d/i2c-dev.conf
2sudo systemctl reboot

Following section configures a service named

my_i2c_service
to use the I2C device available at
/dev/i2c-3
.

1services:
2 my_i2c_service:
3 devices:
4 - `/dev/i2c-3`

Within the Portenta X8 shell, you can use specific commands to quickly test I2C communication with compatible devices. The command below lists all the connected I2C devices:

1i2cdetect -y <I2C bus>

To communicate with and fetch information from a connected I2C device, use:

1i2cget -y <I2C bus> <device address> <register address>

Below are some brief examples to help you in using I2C with the Portenta X8 and the Portenta Hat Carrier.

Here, the SMBus (System Management Bus) communication, with SMBus-compatible libraries, is established with the device on

/dev/i2c-3
. A byte of data is read from the device at address 80 and offset 0, then printed.

1from smbus2 import SMBus
2
3# Connect to /dev/i2c-3
4bus = SMBus(3)
5b = bus.read_byte_data(80, 0)
6print(b)

The following code initializes the I2C bus using the smbus2 library and reads multiple bytes from the device. The

read_i2c_block_data
function reads a block of bytes from the I2C device at a given address.

1from smbus2 import SMBus
2
3# Initialize the I2C bus
4bus = SMBus(3) # 3 indicates /dev/i2c-3
5
6device_address = 0x1
7num_bytes = 2
8
9# Read from the I2C device
10data = bus.read_i2c_block_data(device_address, 0, num_bytes) # Starting address is 0 to read from
11
12# Now data is a list of bytes
13for byte in data:
14 print(byte)

The next code shows how to write data to an I2C device using the smbus2 library. A byte of data (

value
) is written to a specific address (
device_address
) with a given instruction.

1from smbus2 import SMBus
2
3# Initialize the I2C bus
4bus = SMBus(3) # 3 indicates /dev/i2c-3
5
6device_address = 0x1
7instruction = 0x00
8value = 0xFF
9
10# Write to the I2C device
11bus.write_byte_data(device_address, instruction, value)

In the following code, the python-periphery library is used to interact with the I2C device. This is useful if a broad spectrum of protocols are required within the same script. The

I2C.transfer()
method performs both write and read operations on the I2C bus.

A byte is read from the EEPROM at address

0x50
and offset
0x100
, then printed.

1from periphery import I2C
2
3# Open i2c-0 controller
4i2c = I2C("/dev/i2c-3")
5
6# Read byte at address 0x100 of EEPROM at 0x50
7msgs = [I2C.Message([0x01, 0x00]), I2C.Message([0x00], read=True)]
8i2c.transfer(0x50, msgs)
9print("0x100: 0x{:02x}".format(msgs[1].data[0]))
10
11i2c.close()

Using Arduino IDE


To use I2C communication, include the

Wire
library at the top of your sketch. This can be used with Portenta H7 or C33. The
Wire
library provides functions for I2C communication:

1#include <Wire.h>

In the

setup()
function, initialize the I2C library:

1// Initialize the I2C communication
2Wire.begin();

Following commands are available to transmit or write with an I2C-compatible device.

1// Replace with the target device's I2C address
2byte deviceAddress = 0x1;
3
4// Replace with the appropriate instruction byte
5byte instruction = 0x00;
6
7// Replace with the value to send
8byte value = 0xFF;
9
10// Begin transmission to the target device
11Wire.beginTransmission(deviceAddress);
12
13// Send the instruction byte
14Wire.write(instruction);
15
16// Send the value
17Wire.write(value);
18
19// End transmission
20Wire.endTransmission();

Following commands are available to request or read with an I2C-compatible device.

1// The target device's I2C address
2byte deviceAddress = 0x1;
3
4// The number of bytes to read
5int numBytes = 2;
6
7// Request data from the target device
8Wire.requestFrom(deviceAddress, numBytes);
9
10// Read while there is data available
11while (Wire.available()) {
12 byte data = Wire.read();
13}

CAN Bus

The CAN bus, short for Controller Area Network bus, is a resilient communication protocol created by Bosch® in the 1980s for vehicles. It lets microcontrollers and devices interact without a central computer. Using a multi-master model, any system device can send data when the bus is available.

This approach ensures system continuity even if one device fails and is especially effective in electrically noisy settings like in vehicles, where various devices need reliable communication.

The Portenta Hat Carrier is equipped with CAN bus communication capabilities, powered by the TJA1049 module, and a high-speed CAN FD transceiver. With this, developers can leverage the robustness and efficiency of CAN communication in their projects.

Portenta Hat Carrier CAN Bus Port
Portenta Hat Carrier CAN Bus Port

Since the CAN bus pins are integrated within the High-Density connectors, they are conveniently accessible on the carrier through the screw terminal. This provides flexibility in connection, allowing developers to design and troubleshoot their systems easily.

Pin numberSilkscreenHigh-Density PinInterface
5CANHJ1-49 (Through U1)CAN BUS - CANH
6CANLJ1-51 (Through U1)CAN BUS - CANL

Portenta Hat Carrier CAN Bus Interface Connection Example
Portenta Hat Carrier CAN Bus Interface Connection Example

For stable CAN bus communication, it is recommended to install a 120 Ω termination resistor between CANH and CANL lines.

Using Linux


For the Portenta X8, when you have admin (root) access, you can execute the following commands within the shell to control the CAN bus protocol. The CAN transceiver can be enabled using the following command

1echo 164 > /sys/class/gpio/export && echo out > /sys/class/gpio/gpio164/direction && echo 0 > /sys/class/gpio/gpio164/value

This command sequence activates the CAN transceiver. It does so by exporting GPIO 164, setting its direction to "

out
", and then writing a value of "
0
" to it. Writing 0 as a value to GPIO 164 means that it will set the GPIO to a LOW state.

For Portenta X8, it is possible to use the following commands:

1sudo modprobe can-dev

The necessary modules for CAN (Controller Area Network) support on the Portenta X8 are loaded. The

can-dev
module is added to the system configuration, after which the system is rebooted to apply the changes.

1echo "can-dev" | sudo tee > /etc/modules-load.d/can-dev.conf
2sudo systemctl reboot

Within the Portenta X8's shell, Docker containers offer a streamlined environment for specific tasks, such as command-based CAN bus operations. The cansend command is one such utility that facilitates sending CAN frames. The command to issue such task is as follows:

1cansend

And as an example, if you need to send a specific CAN frame, the cansend command can be used to achieve this task. The command follows the format:

1cansend <CAN Interface [can0 | can1]> <CAN ID>#<Data_Payload>
  • <CAN Interface [can0 | can1]>
    : defines the CAN interface that the Portenta X8 will use with the Portenta Hat Carrier.
  • <CAN ID>
    : is the identifier of the message and is used for message prioritization. The identifier can be in 11-bit or 29-bit format.
  • <Data_Payload>
    : is the data payload of the CAN message and ranges from 0 to 8 bytes in standard CAN frames.

For instance, the following command example would send a CAN message on the

can0
interface with an ID of
123
, using an 11-bit identifier, and a data payload of
DEADBEEF
:

1cansend can0 123#DEADBEEF

While the following command example with a 29-bit identifier would send a CAN message with an extended ID of

1F334455
and a data payload of
1122334455667788
.

1cansend can0 1F334455#1122334455667788

This command sends a message with an extended CAN ID and an 8-byte payload.

To use the cansend command, it is crucial to set up the appropriate environment. First, clone the following container repository

1git clone https://github.com/pika-spark/pika-spark-containers

Navigate to the can-utils-sh directory:

1cd pika-spark-containers/can-utils-sh

Build the Docker container:

1./docker-build.sh

Run the Docker container with the desired CAN interface and bitrate:

1sudo ./docker-run.sh can0 | can1 [bitrate]

Moreover, if your goal is to monitor and dump all received CAN frames, a slightly different procedure is to be followed. Having the container repository ready with its components, navigate to the candump directory:

1cd pika-spark-containers/candump

Build the Docker container:

1./docker-build.sh

Run the Docker container with the desired CAN interface and bitrate:

1sudo ./docker-run.sh can0 | can1 [bitrate]

As an example, the command can be structured as follows:

1sudo ./docker-run.sh can0 250000

For more information regarding this container utility, please check can-utils-sh and candump.

The list provided offers a quick reference for various

bustype
parameters supported by python-can library.

  • socketcan: For the SocketCAN interface, which is native to Linux.
  • virtual: Creates a virtual CAN bus, useful for testing purposes when you do not have actual CAN hardware.
  • pcan: For Peak-System PCAN-USB adapters.
  • canalystii: For the Canalyst II interface.
  • kvaser: For Kvaser CAN interfaces.
  • systec: For SYS TEC electronic interfaces.
  • vector: For Vector hardware using the XL Driver Library.
  • usb2can: For the 8devices USB2CAN.
  • ixxat: For IXXAT hardware using the VCI driver.
  • nican: For National Instruments CAN hardware.
  • iscan: For the Intrepid Control Systems (ICS) neoVI.

Each bustype corresponds to different CAN interfaces or devices, ranging from the native Linux SocketCAN interface to specific hardware devices like Kvaser, Vector, and more.

In the following Python® script,

send_standard_can_message
and
send_extended_can_message
functions are defined to send standard and extended CAN messages respectively. The
main()
function creates a CAN bus instance with a 'virtual' bus type for demonstration purposes and sends standard and extended CAN messages in a loop.

1import can
2import time
3
4def send_standard_can_message(channel, message_id, data):
5 msg = can.Message(arbitration_id=message_id, data=data, is_extended_id=False)
6 channel.send(msg)
7
8def send_extended_can_message(channel, message_id, data):
9 msg = can.Message(arbitration_id=message_id, data=data, is_extended_id=True)
10 channel.send(msg)
11
12def main():
13 # Assuming you're using a virtual channel for the CAN bus for testing.
14 # If you're using real hardware like the SocketCAN interface, change 'virtual' to 'socketcan'.
15 bus = can.interface.Bus(channel='virtual', bustype='virtual')
16
17 while True:
18 print("Sending packet ... ", end="")
19 send_standard_can_message(bus, 0x12, [ord('h'), ord('e'), ord('l'), ord('l'), ord('o')])
20 print("done")
21
22 time.sleep(1)
23
24 print("Sending extended packet ... ", end="")
25 send_extended_can_message(bus, 0xabcdef, [ord('w'), ord('o'), ord('r'), ord('l'), ord('d')])
26 print("done")
27
28 time.sleep(1)
29
30if __name__ == "__main__":
31 main()

Continuing Python® script defines functions to receive and print incoming CAN messages. The receive_can_messages function continuously listens for CAN messages and calls print_received_message to display the details of the received message, such as whether it is an extended message or a remote transmission request (RTR) and its data.

1import can
2
3def receive_can_messages(bus):
4 while True:
5 message = bus.recv() # Blocks until a message is received
6 print_received_message(message)
7
8def print_received_message(message):
9 print("Received ", end="")
10
11 if message.is_extended_id:
12 print("extended ", end="")
13
14 if message.is_remote_frame:
15 print("RTR ", end="")
16
17 print("packet with id 0x{:X}".format(message.arbitration_id), end="")
18
19 if message.is_remote_frame:
20 print(" and requested length {}".format(message.dlc))
21 else:
22 print(" and length {}".format(len(message.data)))
23
24 # Only print packet data for non-RTR packets
25 for byte in message.data:
26 print(chr(byte), end="")
27 print()
28
29 print()
30
31def main():
32 # Assuming you're using a virtual channel for testing.
33 # If you're using real hardware like the SocketCAN interface, change 'virtual' to 'socketcan'.
34 bus = can.interface.Bus(channel='virtual', bustype='virtual')
35 print("CAN Receiver Callback")
36 receive_can_messages(bus)
37
38if __name__ == "__main__":
39 main()

The

main()
function initializes the CAN bus with a 'virtual' channel for example demonstration and starts the message listening process.

Using Arduino IDE

For users working with the Portenta H7 or Portenta C33, the following simple examples can be used to test the CAN bus protocol's capabilities.

The CAN Read example for Portenta H7/C33 starts CAN communication at a rate of 250 kbps and continuously listens for incoming messages, displaying such information upon receipt.

1#include <Arduino_CAN.h>
2
3/**************************************************************************************
4 * SETUP/LOOP
5 **************************************************************************************/
6
7void setup()
8{
9 Serial.begin(115200);
10 while (!Serial) { }
11
12 if (!CAN.begin(CanBitRate::BR_250k))
13 {
14 Serial.println("CAN.begin(...) failed.");
15 for (;;) {}
16 }
17}
18
19void loop()
20{
21 if (CAN.available())
22 {
23 CanMsg const msg = CAN.read();
24 Serial.println(msg);
25 }
26}

The CAN Write example, also set at 250 kbps, builds and sends a specific message format. This message includes a fixed preamble followed by an incrementing counter value that updates with each loop iteration.

1/**************************************************************************************
2 * INCLUDE
3 **************************************************************************************/
4
5#include <Arduino_CAN.h>
6
7/**************************************************************************************
8 * CONSTANTS
9 **************************************************************************************/
10
11static uint32_t const CAN_ID = 0x20;
12
13/**************************************************************************************
14 * SETUP/LOOP
15 **************************************************************************************/
16
17void setup()
18{
19 Serial.begin(115200);
20 while (!Serial) { }
21
22 if (!CAN.begin(CanBitRate::BR_250k))
23 {
24 Serial.println("CAN.begin(...) failed.");
25 for (;;) {}
26 }
27}
28
29static uint32_t msg_cnt = 0;
30
31void loop()
32{
33 /* Assemble a CAN message with the format of
34 * 0xCA 0xFE 0x00 0x00 [4 byte message counter]
35 */
36 uint8_t const msg_data[] = {0xCA,0xFE,0,0,0,0,0,0};
37 memcpy((void *)(msg_data + 4), &msg_cnt, sizeof(msg_cnt));
38 CanMsg const msg(CanStandardId(CAN_ID), sizeof(msg_data), msg_data);
39
40 /* Transmit the CAN message, capture and display an
41 * error core in case of failure.
42 */
43 if (int const rc = CAN.write(msg); rc < 0)
44 {
45 Serial.print ("CAN.write(...) failed with error code ");
46 Serial.println(rc);
47 for (;;) { }
48 }
49
50 /* Increase the message counter. */
51 msg_cnt++;
52
53 /* Only send one message per second. */
54 delay(1000);
55}

UART

The Portenta Hat Carrier supports UART communication. The pins used in the Portenta Hat Carrier for the UART communication protocol are the following:

Pin numberSilkscreenPortenta HD Standard PinHigh-Density PinInterface
40-Pin Header
8TX3SERIAL3_TXJ2-25UART 3 TX
10RX3SERIAL3_RXJ2-27UART 3 RX
29RX1SERIAL1_RXJ1-35UART 1 RX
32TX1SERIAL1_TXJ1-33UART 1 TX
16-Pin Header
14TX2SERIAL2_TXJ2-26UART 2 TX
16RX2SERIAL2_RXJ2-28UART 2 RX

Please, refer to the board pinout section of the user manual to find them on the board. The UART pins can be used through the built-in (Serial) library functions.

Using Linux


For the Portenta X8, when you have admin (root) access, you can execute the command

ls /dev/ttyUSB* /dev/ttyACM* /dev/ttymxc*
within the shell to list available serial ports in Linux. Typically, USB serial devices could appear as /dev/ttyUSBx, /dev/ttyACMx, or /dev/ttymxcx.

1ls /dev/ttyUSB* /dev/ttyACM* /dev/ttymxc*
1/dev/ttymxc2 // Something similar

The output /dev/ttymxc2 is an example of a potential serial device you might find on Portenta X8.

Following Python® script uses the pyserial library to communicate with devices over UART. It defines the

processData
function which prints the received data. You can modify this function based on your application's needs.

1import serial
2import time
3
4# Define the processData function (you'll need to fill this in based on your requirements)
5def processData(data):
6 print("Received:", data) # For now, just print the data. Modify as needed.
7
8# Set up the serial port
9ser = serial.Serial('/dev/ttymxc2', 9600) # Use the appropriate port and baud rate for your device
10
11incoming = ""
12
13while True:
14 # Check for available data and read individual characters
15 while ser.in_waiting:
16 c = ser.read().decode('utf-8') # Read a single character and decode from bytes to string
17
18 # Check if the character is a newline (line-ending)
19 if c == '\n':
20 # Process the received data
21 processData(incoming)
22
23 # Clear the incoming data string for the next message
24 incoming = ""
25 else:
26 # Add the character to the incoming data string
27 incoming += c
28
29 time.sleep(0.002) # Delay for data buffering, equivalent to Arduino's delay(2);

The script sets up a serial connection on port /dev/ttymxc2 at a baud rate of 9600. It then continuously checks for incoming data. When a newline character (

\n
) is detected, indicating the end of a message, the script processes the accumulated data and then resets the data string to be ready for the next incoming message.

The

time.sleep(0.002)
line adds a slight delay, ensuring data has enough time to buffer, similar to using
delay(2);
in Arduino.

Using Arduino IDE


For Portenta H7 or C33, the following examples can be used to test UART communication. For a proper UART communication, the baud rate (bits per second) must be set within the

setup()
function.

1// Start UART communication at 9600 baud
2Serial.begin(9600);

With the

Serial1.available()
function and the
Serial1.read()
function, you can read incoming data by using a
while()
loop to repeatedly check for available data and read individual characters. When a line-ending character is received, the code below processes the input and stores the incoming characters in a String variable.:

Using the

Serial1.available()
method alongside the
Serial1.read()
method allows you to read incoming data. The technique involves using a
while()
loop to consistently check for available data and then read individual characters.

Upon receiving a line-ending character, the code processes the accumulated characters and stores them in a String variable. The relevant section of the code is presented below:

1void loop() {
2 while (Serial1.available()) {
3 delay(2);
4 char c = Serial1.read();
5 if (c == '\n') {
6 processData(incoming);
7 incoming = "";
8 } else {
9 incoming += c;
10 }
11 }
12}
13
14void processData(String data) {
15 Serial.println("Received: " + data); // Print on Serial Monitor
16}

Consequently, the following provides a complete example illustrating the reception of incoming data via UART:

1String incoming = "";
2
3void setup() {
4 Serial1.begin(9600); // For communication with Arduino using RX1 and TX1
5 Serial.begin(9600); // For debugging over USB
6}
7
8void loop() {
9 while (Serial1.available()) {
10 delay(2);
11 char c = Serial1.read();
12 if (c == '\n') {
13 processData(incoming);
14 incoming = "";
15 } else {
16 incoming += c;
17 }
18 }
19}
20
21void processData(String data) {
22 Serial.println("Received: " + data); // Print on Serial Monitor
23}

For transmitting data over UART, which can complement the receiving board as depicted above, refer to the ensuing example:

1void setup() {
2 Serial1.begin(9600);
3}
4
5void loop() {
6 Serial1.println("Hello from Portenta!");
7 delay(1000);
8}

Using these codes, you should observe the message

"Hello from Portenta!"
being transmitted to the receiving Portenta board when coupled with a Portenta Hat Carrier.

The

Serial.write()
method allows you to send data over UART (Universal Asynchronous Receiver-Transmitter). This method is used when you want to transmit raw bytes or send a byte array.

1// Transmit the string "Hello world!
2Serial.write("Hello world!");

Code snippet above sends the string "

Hello world!
" to another device through UART using the
Serial.write()
function.

The following methods are used to send strings over UART.

1// Transmit the string "Hello world!"
2Serial.print("Hello world!");
3
4// Transmit the string "Hello world!" followed by a newline character
5Serial.println("Hello world!");

The difference between the two is that

Serial.println()
sends a newline character (
\n
) after sending the string, thereby moving the cursor to the next line, whereas
 Serial.print()
does not.

Support

If you encounter any issues or have questions while working with the Portenta Hat Carrier, we provide various support resources to help you find answers and solutions.

Help Center

Explore our Help Center, which offers a comprehensive collection of articles and guides for the Portenta Hat Carrier. The Arduino Help Center is designed to provide in-depth technical assistance and help you make the most of your device.

Forum

Join our community forum to connect with other Portenta Hat Carrier users, share your experiences, and ask questions. The forum is an excellent place to learn from others, discuss issues, and discover new ideas and projects related to the Portenta Hat Carrier.

Contact Us

Please get in touch with our support team if you need personalized assistance or have questions not covered by the help and support resources described before. We're happy to help you with any issues or inquiries about the Portenta Hat Carrier.

Suggest changes

The content on docs.arduino.cc is facilitated through a public GitHub repository. If you see anything wrong, you can edit this page here.

License

The Arduino documentation is licensed under the Creative Commons Attribution-Share Alike 4.0 license.