Opta™ Memory Partitioning for Use with the Arduino IDE

Learn how to partition the memory of an Opta™ device to be used with the Arduino® ecosystem tools.

Overview

The Opta™ is a secure, easy-to-use micro Programmable Logic Controller (PLC) with Industrial Internet of Things (IIoT) capabilities. It can be programmed with the Arduino PLC IDE, a tool that simplifies programming the device through any of the five programming languages defined by the IEC 61131-3 standard.

The Opta™ can also be programmed with other tools from the Arduino ecosystem, such as the Arduino IDE and the Arduino programming language. In case we are using Opta™ with the Arduino PLC IDE and we want to start programming the device using the Arduino IDE and the rest of the Arduino ecosystem tools, we need to partition the memory of the device to enable all of the available features.

In this tutorial, we will learn how to partition the memory of an Opta™ device to enable all of its functionalities to be programmed using the Arduino IDE and the Arduino programming language.

The Opta™ memory map
The Opta™ memory map

Goals

  • Learn how to partition the memory of an Opta™ device to be used with the Arduino IDE and other ecosystem tools
  • Program an Opta™ device using the Arduino IDE and the Arduino programming language.

Hardware and Software Requirements

Hardware Requirements

Software Requirements

Memory Partitioning

Memory partitioning involves dividing the available memory of a device into separate regions or partitions, each serving a specific purpose. This process is used for managing memory efficiently and ensuring that different programs or functionalities of the device can coexist without interfering each other. In the context of the Opta™, partitioning the memory allows the device to accommodate different functionalities, such as its Wi-Fi® module firmware, Over-The-Air (OTA) updates functionality, and certificates storage, while still providing memory space for user programs developed in the Arduino IDE and the Arduino programming language.

Now, we will guide the process of partitioning an Opta™'s memory to enable the full range of its functionalities to be programmed using the Arduino IDE and the Arduino programming language.

Instructions

Setting Up the Arduino IDE

This tutorial requires the latest version of the Arduino IDE; we can download it here. In the Arduino IDE, we need to install the core for Opta™ devices; this can be done by navigating to Tools > Board > Boards Manager or clicking the Boards Manager icon in the left tab of the IDE. In the Boards Manager tab, search for

opta
and install the latest
Arduino Mbed OS Opta Boards
version.

Installing the Opta™ core in the Arduino IDE bootloader
Installing the Opta™ core in the Arduino IDE bootloader

Now we are ready to start compiling and uploading Arduino sketches to an Opta™ device using the Arduino IDE.

Partitioning Memory of an Opta™ Device

The sketch below will guide us through partitioning the memory of an Opta™ device. The complete sketch and the certificates file can be downloaded here. Remember to store both files in the same folder.

1/**
2 Opta memory partitioning
3 Name: opta_memory_partitioning.ino
4 Purpose: Partition the memory of an Opta device
5
6 @author Arduino PRO team
7 @version 1.0 04/04/22
8*/
9
10// Include necessary libraries for working
11#include <BlockDevice.h>
12#include <FATFileSystem.h>
13#include <LittleFileSystem.h>
14#include <MBRBlockDevice.h>
15#include "wiced_resource.h"
16#include "certificates.h"
17
18// Ensure that the M7 core is being used instead of the M4 core
19#ifndef CORE_CM7
20#error Update the WiFi firmware by uploading the sketch to the M7 core instead of the M4 core.
21#endif
22
23using namespace mbed;
24
25// Create instances of block devices and filesystems for the QSPI Flash memory
26BlockDevice* root;
27MBRBlockDevice* wifi_data;
28MBRBlockDevice* ota_data;
29FATFileSystem wifi_data_fs("wlan");
30FATFileSystem ota_data_fs("fs");
31
32void setup() {
33 // Set the built-in LED pin as an output and turn it off
34 pinMode(LED_BUILTIN, OUTPUT);
35 digitalWrite(LED_BUILTIN, LOW);
36
37 // Initialize serial communication and wait up to 2.5 seconds for a connection
38 Serial.begin(115200);
39 for (auto startNow = millis() + 2500; !Serial && millis() < startNow; delay(500))
40 ;
41
42 // Blink the built-in LED 10 times as a visual indicator that the process is starting
43 for (auto i = 0u; i < 10; i++) {
44 digitalWrite(LED_BUILTIN, HIGH);
45 delay(25);
46 digitalWrite(LED_BUILTIN, LOW);
47 delay(50);
48 }
49
50 // Initialize and erase the QSPI flash memory.
51 Serial.println("Erasing the QSPIF");
52 root = BlockDevice::get_default_instance();
53 auto err = root->init();
54 if (err != 0) {
55 Serial.print("Error Initializing the QSPIF: ");
56 Serial.println(err);
57 while (true) {
58 digitalWrite(LED_BUILTIN, HIGH);
59 delay(50);
60 digitalWrite(LED_BUILTIN, LOW);
61 delay(150);
62 }
63 }
64
65 // Create partitions for Wi-Fi firmware, OTA updates, and certificate storage
66 // Get device geometry.
67 const auto erase_size = root->get_erase_size();
68 const auto size = root->size();
69 const auto eraseSectors = size / erase_size;
70
71 for (auto i = 0u; i < eraseSectors; i++) {
72 err = root->erase(i * erase_size, erase_size);
73 if (i % 64 == 0) {
74 digitalWrite(LED_BUILTIN, HIGH);
75 delay(25);
76 digitalWrite(LED_BUILTIN, LOW);
77 }
78 if (err != 0) {
79 Serial.print("Error erasing sector ");
80 Serial.println(i);
81 Serial.print(" [");
82 Serial.print(i * erase_size);
83 Serial.print(" - ");
84 Serial.print(float{ i } / float{ eraseSectors } * 100);
85 Serial.print("%] -> ");
86 Serial.print(err ? "KO" : "OK");
87 Serial.println();
88 for (auto i = 0u; i < 2; i++) {
89 digitalWrite(LED_BUILTIN, HIGH);
90 delay(50);
91 digitalWrite(LED_BUILTIN, LOW);
92 delay(150);
93 }
94 }
95 }
96
97 Serial.println("Done");
98 for (auto i = 0u; i < 5; i++) {
99 digitalWrite(LED_BUILTIN, HIGH);
100 delay(25);
101 digitalWrite(LED_BUILTIN, LOW);
102 delay(50);
103 }
104
105 // Format the partitions and create filesystem instances
106 // WiFi Firmware and TLS TA certificates: 1 MB
107 // Arduino OTA: 13 MB
108 MBRBlockDevice::partition(root, 1, 0x0B, 0 * 1024 * 1024, 1 * 1024 * 1024);
109 MBRBlockDevice::partition(root, 3, 0x0B, 14 * 1024 * 1024, 14 * 1024 * 1024);
110 MBRBlockDevice::partition(root, 2, 0x0B, 1024 * 1024, 14 * 1024 * 1024);
111
112 // Create the filesystem references
113 wifi_data = new MBRBlockDevice(root, 1);
114 ota_data = new MBRBlockDevice(root, 2);
115
116 // Write Wi-Fi firmware and certificate data to the appropriate partitions
117 Serial.print("Formatting WiFi partition... ");
118 err = wifi_data_fs.reformat(wifi_data);
119 if (err != 0) {
120 Serial.println("Error formatting WiFi partition");
121 while (true) {
122 digitalWrite(LED_BUILTIN, HIGH);
123 delay(50);
124 digitalWrite(LED_BUILTIN, LOW);
125 delay(150);
126 }
127 }
128
129 Serial.println("done.");
130 Serial.print("Formatting OTA partition...");
131 err = ota_data_fs.reformat(ota_data);
132 if (err != 0) {
133 Serial.println("Error formatting OTA partition");
134 while (true) {
135 digitalWrite(LED_BUILTIN, HIGH);
136 delay(50);
137 digitalWrite(LED_BUILTIN, LOW);
138 delay(150);
139 }
140 }
141
142 Serial.println("done.");
143 for (auto i = 0u; i < 10; i++) {
144 digitalWrite(LED_BUILTIN, HIGH);
145 delay(25);
146 digitalWrite(LED_BUILTIN, LOW);
147 delay(50);
148 }
149
150 Serial.println("QSPI Flash Storage Ready.");
151
152 // Flash the memory-mapped Wi-Fi firmware and certificates
153 extern const unsigned char wifi_firmware_image_data[];
154 extern const resource_hnd_t wifi_firmware_image;
155 FILE* fp = fopen("/wlan/4343WA1.BIN", "wb");
156 const int file_size = 421098;
157 int chunck_size = 1024;
158 int byte_count = 0;
159
160 Serial.println("Flashing /wlan/4343WA1.BIN file");
161 printProgress(byte_count, file_size, 10, true);
162 while (byte_count < file_size) {
163 if (byte_count + chunck_size > file_size)
164 chunck_size = file_size - byte_count;
165 int ret = fwrite(&wifi_firmware_image_data[byte_count], chunck_size, 1, fp);
166 if (ret != 1) {
167 Serial.println("Error writing firmware data");
168 break;
169 }
170 byte_count += chunck_size;
171 printProgress(byte_count, file_size, 10, false);
172 }
173 fclose(fp);
174
175 chunck_size = 1024;
176 byte_count = 0;
177 const uint32_t offset = 15 * 1024 * 1024 + 1024 * 512;
178
179 Serial.println("Flashing memory mapped firmware");
180 printProgress(byte_count, file_size, 10, true);
181 while (byte_count < file_size) {
182 if (byte_count + chunck_size > file_size)
183 chunck_size = file_size - byte_count;
184 int ret = root->program(wifi_firmware_image_data, offset + byte_count, chunck_size);
185 if (ret != 0) {
186 Serial.println("Error writing firmware data");
187 break;
188 }
189 byte_count += chunck_size;
190 printProgress(byte_count, file_size, 10, false);
191 }
192
193 chunck_size = 128;
194 byte_count = 0;
195 fp = fopen("/wlan/cacert.pem", "wb");
196
197 Serial.println("Flashing certificates");
198 printProgress(byte_count, cacert_pem_len, 10, true);
199 while (byte_count < cacert_pem_len) {
200 if (byte_count + chunck_size > cacert_pem_len)
201 chunck_size = cacert_pem_len - byte_count;
202 int ret = fwrite(&cacert_pem[byte_count], chunck_size, 1, fp);
203 if (ret != 1) {
204 Serial.println("Error writing certificates");
205 break;
206 }
207 byte_count += chunck_size;
208 printProgress(byte_count, cacert_pem_len, 10, false);
209 }
210 fclose(fp);
211
212 fp = fopen("/wlan/cacert.pem", "rb");
213 char buffer[128];
214 int ret = fread(buffer, 1, 128, fp);
215 Serial.write(buffer, ret);
216 while (ret == 128) {
217 ret = fread(buffer, 1, 128, fp);
218 Serial.write(buffer, ret);
219 }
220 fclose(fp);
221
222 Serial.println("\nFirmware and certificates updated!");
223 Serial.println("It's now safe to reboot or disconnect your board.");
224}
225
226void loop() {
227 // Empty loop function, main task is performed in the setup function
228}
229
230/**
231 Get the size of a file
232
233 @param bootloader fp (FP)
234 @return files size
235*/
236long getFileSize(FILE* fp) {
237 fseek(fp, 0, SEEK_END);
238 int size = ftell(fp);
239 fseek(fp, 0, SEEK_SET);
240
241 return size;
242}
243
244/**
245 Display the progress of the flashing process
246
247 @params offset (uint32_t), size (uint32_t), threshold (uint32_t) and reset (bool)
248 @return none
249*/
250void printProgress(uint32_t offset, uint32_t size, uint32_t threshold, bool reset) {
251 static int percent_done = 0;
252 if (reset == true) {
253 percent_done = 0;
254 Serial.println("Flashed " + String(percent_done) + "%");
255 } else {
256 uint32_t percent_done_new = offset * 100 / size;
257 if (percent_done_new >= percent_done + threshold) {
258 percent_done = percent_done_new;
259 Serial.println("Flashed " + String(percent_done) + "%");
260 }
261 }
262}

The sketch shown above performs four main tasks:

  1. Initialize and erase the QSPI Flash memory: The sketch initializes the QSPI Flash memory of the Opta™ device and erases its content to prepare the memory for new firmware and data. One of the built-in LEDs of the device is used to indicate the progress of the memory-erasing process.

  2. Create partitions and format them in the QSPI Flash memory: The sketch creates and formats partitions in the QSPI Flash memory for the Wi-Fi firmware, Over-The-Air (OTA) updates functionality, and certificates storage.

  3. Write Wi-Fi firmware and certificates data: The sketch writes the Wi-Fi firmware and certificate data to the appropriate partitions in the QSPI Flash memory. It also flashes the memory-mapped Wi-Fi firmware and certificates.

  4. Display progress in the Arduino IDE Serial Monitor: The sketch provides a visual indication of the progress of the flashing process using one of the built-in LEDs of the Opta™ device and displays messages through the Arduino IDE Serial Monitor to inform the user about the current status of the flashing process.

To upload the code, click the Verify button to compile the sketch and check for errors; then click the Upload button to program the device with the sketch.

Uploading a sketch to the Opta™ the Arduino IDE
Uploading a sketch to the Opta™ the Arduino IDE

After a while, you should see in the Serial Monitor information on the progress of the flashing process, as shown in the image below.

Flashing process feedback of the Opta™ shown in the Arduino IDE's Serial Monitor
Flashing process feedback of the Opta™ shown in the Arduino IDE's Serial Monitor

If everything is correct, you should see a success message in the Serial Monitor. Now we are ready to start using the full capabilities of the Opta™ with the Arduino IDE.

Conclusion

In this tutorial, we successfully partitioned the memory of an Opta™ device, enabling its full range of functionalities to be programmed using Arduino ecosystem tools such as the Arduino IDE and the Arduino programming language. We walked through initializing and erasing the QSPI Flash memory, partitioning and formatting the memory, and writing the Wi-Fi firmware and certificate data onto the device's memory.

As you move forward, you can explore the extensive Arduino ecosystem, leverage various libraries and hardware add-ons, and create powerful, connected, and secure industrial solutions using the Opta™. Check out our Opta™ User Manual to learn more about its hardware and software features.

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.