C++ on STM32

How to Use C++ with STM32CubeIDE

While C may be the preferred language of many embedded programmers, C++ does have a place, especially if you want to use certain libraries, like TensorFlow Lite for Microcontrollers. In this tutorial, I will show you how to get started making a program (blinky) for an STM32 microcontroller using C++ on STM32CubeIDE.

I will use an ST Nucleo-L432KC for this demonstration, as it’s small, fits on a breadboard, and still has a powerful ARM Cortex-M4 core. You are welcome to use any STM32 part, but I generally recommend starting out with one of the Nucleo boards.

Nucleo-L432KC

Plug your STM32 part into your computer.

Start STM32CubeIDE[link] and select File > New > STM32 Project. Select your target microcontroller or board (I’ll select my Nucleo-L432KC).

Select board in STM32CubeIDE

Click Next and then give your project a name. I like to prefix the board I’m using to the project name, so nucleo-l432-cpp-blinky is what I called mine. In Targeted Language, select C++.

Create new C++ project in STM32CubeIDE

Click Finish and click Yes when asked, “Initialize all peripherals with their default Mode?” Click Yes to open the new perspective.

Leave everything as default on the CubeMX device configuration GUI.

Leave all pin configurations to default

Code should be automatically generated. However, CubeMX generates a main.c file, which is not what we want for a C++ project. Right click on <project name> > Core > Src > main.c and select Rename. Change the name of the file to main.cpp. The compiler is sensitive to file suffixes, so .c files will compile as C code and .cpp or .cc files will compile as C++.

Rename main.c to main.cpp

Open the main.cpp file associated with the project. Scroll down to the while (1) loop and add the following  code just inside the while (1) loop:

HAL_GPIO_TogglePin(LD3_GPIO_Port, LD3_Pin);
HAL_Delay(500);

In context of the rest of the source code, this should look something like:

Where to place code in STM32CubeIDE generated template

Note that the GPIO port and pin names might be different for your board. If you’re using a Nucleo-64, the labels will be LD2_GPIO_Port and LD2_Pin.

In case you are wondering: yes, this is still C inside of a C++ file. Most of the STM32 HAL libraries are written in C, but they’re safe to call in C++.

Save and click Project > Build Project. You should notice that any .cpp or .cc files get automatically built with g++.

G++ compiler working on .cpp files

Click Run > Debug and click OK to accept the default launch configuration properties. Accept any prompts you might see to open the debug perspective. Click Run > Resume, and you should see the LED begin to blink.

Nucleo-L432KC blinky

That’s it! You should be ready to build your STM32 project in C++ with all of the object-oriented programming you so desire.

Please note that you can generally call C functions from within a C++ program and vice versa. You will need to add extern “C”  to your functions, which is detailed in this article.

If you are already working within a C project and you wish to convert it to C++, right-click on the project name in the project explorer pane and click Convert to C++. You will then need to rename any source files (to .cpp or .cc) you wish to be compiled by g++.

Convert project to C++ in STM32CubeIDE

If you right-click on any C++ project, you’ll find that you can revert it back to a C project using a similar method.

I hope this helps! I know that I’m looking forward to using C++ libraries in my STM32 project.

Using an ESP32 as a web server and using WebSockets to control hardware

How to Create a Web Server (with WebSockets) Using an ESP32 in Arduino

A few months ago, I created a video showing how to use WebSockets with an ESP32. WebSockets have proven to be very fast (relatively speaking) at controlling hardware over WiFi. They still rely on TCP, but they have little overhead, so the latency is much less than other methods (e.g. using separate web pages). However, I’ve had a few requests to show how to implement a web server on the ESP32. So, this tutorial will cover exactly that (but I’m still keeping WebSockets, because they’re cool).

If you like your tutorials in video format, here you go:

 

Overview

Rather than just host a simple web page, we’re going to build on the WebSocket idea. Let’s have our ESP32 be an access point (AP) and host a web page. When a browser requests that page, the ESP32 will serve it. As soon as the page loads, the client will immediately make a WebSocket connection back to the ESP32. That allows us to have fast control of our hardware connected to the ESP32.

ESP32 web server and WebSocket server diagram

The WebSocket connection is two-way. Whenever the page loads, it first inquires about the state of the LED from the ESP32. If the LED is on, the page will update a circle (fill in red) to reflect that. The circle on the page will be black if the LED is off. Then, whenever a user presses the “Toggle LED” button, the client will send a WebSocket packet to the ESP32, telling it to toggle the LED. This packet is followed by another request asking about the LED state so that the client can keep the browser updated with the state of the LED.

Hardware Hookup

Connect an LED to pin 15 of your ESP32 (don’t forget a limiting resistor! Something like 330Ω should work). Note that I’m using an Adafruit Feather HUZZAH32, so your pin numbering might be different.

Connecting LED to ESP32

 

Install SPIFFS Plugin

An ESP32 with an attached flash storage chip, like our HUZZAH32, can be configured to hold files, much like a mass storage device. However, it uses a very basic file system (there are no folders–files are just stored in a flat structure). This file system is known as the “Serial Peripheral Interface Flash File System” (SPIFFS). You can read more about SPIFFS here.

We need to use a special program to upload files over SPI. Head to https://github.com/me-no-dev/arduino-esp32fs-plugin and follow the instructions to install the Arduino plugin. Once done, restart Arduino, and you should see the option ESP32 Sketch Data Upload in the Tools menu.

ESP32 SPIFFS upload plugin in Arduino

Install Arduino Libraries

Make sure you have the ESP32 board definition installed for your particular board. If you’re using a board that’s supported by the Espressif Arduino board manager (such as the Adafruit HUZZAH32), you can follow the directions here.

Download the .zip files for the following libraries and install them in Arduino.

Arduino Code

Enter the following code into a new Arduino sketch. Take note of the SSID and password–we’ll need these to connect our phone/computer to the ESP32’s AP.

#include <WiFi.h>
#include <SPIFFS.h>
#include <ESPAsyncWebServer.h>
#include <WebSocketsServer.h>

// Constants
const char *ssid = "ESP32-AP";
const char *password =  "LetMeInPlz";
const char *msg_toggle_led = "toggleLED";
const char *msg_get_led = "getLEDState";
const int dns_port = 53;
const int http_port = 80;
const int ws_port = 1337;
const int led_pin = 15;

// Globals
AsyncWebServer server(80);
WebSocketsServer webSocket = WebSocketsServer(1337);
char msg_buf[10];
int led_state = 0;

/***********************************************************
 * Functions
 */

// Callback: receiving any WebSocket message
void onWebSocketEvent(uint8_t client_num,
                      WStype_t type,
                      uint8_t * payload,
                      size_t length) {

  // Figure out the type of WebSocket event
  switch(type) {

    // Client has disconnected
    case WStype_DISCONNECTED:
      Serial.printf("[%u] Disconnected!\n", client_num);
      break;

    // New client has connected
    case WStype_CONNECTED:
      {
        IPAddress ip = webSocket.remoteIP(client_num);
        Serial.printf("[%u] Connection from ", client_num);
        Serial.println(ip.toString());
      }
      break;

    // Handle text messages from client
    case WStype_TEXT:

      // Print out raw message
      Serial.printf("[%u] Received text: %s\n", client_num, payload);

      // Toggle LED
      if ( strcmp((char *)payload, "toggleLED") == 0 ) {
        led_state = led_state ? 0 : 1;
        Serial.printf("Toggling LED to %u\n", led_state);
        digitalWrite(led_pin, led_state);

      // Report the state of the LED
      } else if ( strcmp((char *)payload, "getLEDState") == 0 ) {
        sprintf(msg_buf, "%d", led_state);
        Serial.printf("Sending to [%u]: %s\n", client_num, msg_buf);
        webSocket.sendTXT(client_num, msg_buf);

      // Message not recognized
      } else {
        Serial.println("[%u] Message not recognized");
      }
      break;

    // For everything else: do nothing
    case WStype_BIN:
    case WStype_ERROR:
    case WStype_FRAGMENT_TEXT_START:
    case WStype_FRAGMENT_BIN_START:
    case WStype_FRAGMENT:
    case WStype_FRAGMENT_FIN:
    default:
      break;
  }
}

// Callback: send homepage
void onIndexRequest(AsyncWebServerRequest *request) {
  IPAddress remote_ip = request->client()->remoteIP();
  Serial.println("[" + remote_ip.toString() +
                  "] HTTP GET request of " + request->url());
  request->send(SPIFFS, "/index.html", "text/html");
}

// Callback: send style sheet
void onCSSRequest(AsyncWebServerRequest *request) {
  IPAddress remote_ip = request->client()->remoteIP();
  Serial.println("[" + remote_ip.toString() +
                  "] HTTP GET request of " + request->url());
  request->send(SPIFFS, "/style.css", "text/css");
}

// Callback: send 404 if requested file does not exist
void onPageNotFound(AsyncWebServerRequest *request) {
  IPAddress remote_ip = request->client()->remoteIP();
  Serial.println("[" + remote_ip.toString() +
                  "] HTTP GET request of " + request->url());
  request->send(404, "text/plain", "Not found");
}

/***********************************************************
 * Main
 */

void setup() {
  // Init LED and turn off
  pinMode(led_pin, OUTPUT);
  digitalWrite(led_pin, LOW);

  // Start Serial port
  Serial.begin(115200);

  // Make sure we can read the file system
  if( !SPIFFS.begin()){
    Serial.println("Error mounting SPIFFS");
    while(1);
  }

  // Start access point
  WiFi.softAP(ssid, password);

  // Print our IP address
  Serial.println();
  Serial.println("AP running");
  Serial.print("My IP address: ");
  Serial.println(WiFi.softAPIP());

  // On HTTP request for root, provide index.html file
  server.on("/", HTTP_GET, onIndexRequest);

  // On HTTP request for style sheet, provide style.css
  server.on("/style.css", HTTP_GET, onCSSRequest);

  // Handle requests for pages that do not exist
  server.onNotFound(onPageNotFound);

  // Start web server
  server.begin();

  // Start WebSocket server and assign callback
  webSocket.begin();
  webSocket.onEvent(onWebSocketEvent);
  
}

void loop() {
  
  // Look for and handle WebSocket data
  webSocket.loop();
}

Save your code (we’ll need the Arduino directory structure for SPIFFS in an upcoming step). I’ll give my program a name like “esp32_websocket_host.” Upload the program to your ESP32 and open a serial console with a baud rate of 115200. You should see the IP address of the ESP32 printed to the screen. When you run the Arduino soft access point software on the ESP32, the default IP address is 192.168.4.1.

ESP32 Access Point IP Address

Web Page Code

Let’s write a simple web page! Like most modern pages, it will contain a mixture of HTML and JavaScript. We leave out CSS, but you’re welcome to create your own .css file, if you wish to make the page look a little nicer. If you look back up in the Arduino code, you’ll see that we have a function to serve up a .css file. However, we will not be making a .css file for the sake of simplicity in this tutorial.

For SPIFFS to work, we need to put the files we wish to upload to the ESP32 in a folder named data/ inside the Arduino project directory:

esp32_websocket_host
 |_ esp32_websocket_host.ino
 |_ data
     |_ index.html

In Arduino, click Sketch > Show Sketch Folder to navigate to your project directory. Create a folder named data and create a new file named index.html inside that new folder. Open index.html with your favorite editor.

In index.html, paste the following code:

<!DOCTYPE html>
<meta charset="utf-8" />
<title>WebSocket Test</title>

<script language="javascript" type="text/javascript">

var url = "ws://192.168.4.1:1337/";
var output;
var button;
var canvas;
var context;

// This is called when the page finishes loading
function init() {

    // Assign page elements to variables
    button = document.getElementById("toggleButton");
    output = document.getElementById("output");
    canvas = document.getElementById("led");
    
    // Draw circle in canvas
    context = canvas.getContext("2d");
    context.arc(25, 25, 15, 0, Math.PI * 2, false);
    context.lineWidth = 3;
    context.strokeStyle = "black";
    context.stroke();
    context.fillStyle = "black";
    context.fill();
    
    // Connect to WebSocket server
    wsConnect(url);
}

// Call this to connect to the WebSocket server
function wsConnect(url) {
    
    // Connect to WebSocket server
    websocket = new WebSocket(url);
    
    // Assign callbacks
    websocket.onopen = function(evt) { onOpen(evt) };
    websocket.onclose = function(evt) { onClose(evt) };
    websocket.onmessage = function(evt) { onMessage(evt) };
    websocket.onerror = function(evt) { onError(evt) };
}

// Called when a WebSocket connection is established with the server
function onOpen(evt) {

    // Log connection state
    console.log("Connected");
    
    // Enable button
    button.disabled = false;
    
    // Get the current state of the LED
    doSend("getLEDState");
}

// Called when the WebSocket connection is closed
function onClose(evt) {

    // Log disconnection state
    console.log("Disconnected");
    
    // Disable button
    button.disabled = true;
    
    // Try to reconnect after a few seconds
    setTimeout(function() { wsConnect(url) }, 2000);
}

// Called when a message is received from the server
function onMessage(evt) {

    // Print out our received message
    console.log("Received: " + evt.data);
    
    // Update circle graphic with LED state
    switch(evt.data) {
        case "0":
            console.log("LED is off");
            context.fillStyle = "black";
            context.fill();
            break;
        case "1":
            console.log("LED is on");
            context.fillStyle = "red";
            context.fill();
            break;
        default:
            break;
    }
}

// Called when a WebSocket error occurs
function onError(evt) {
    console.log("ERROR: " + evt.data);
}

// Sends a message to the server (and prints it to the console)
function doSend(message) {
    console.log("Sending: " + message);
    websocket.send(message);
}

// Called whenever the HTML button is pressed
function onPress() {
    doSend("toggleLED");
    doSend("getLEDState");
}

// Call the init function as soon as the page loads
window.addEventListener("load", init, false);

</script>

<h2>LED Control</h2>

<table>
    <tr>
        <td><button id="toggleButton" onclick="onPress()" disabled>Toggle LED</button></td>
        <td><canvas id="led" width="50" height="50"></canvas></td>
    </tr>
</table>

Save this file. With your ESP32 plugged into your computer, open Arduino and click Tools > ESP32 Sketch Data Upload. Wait a moment, and all the files in the data/ folder (well, just the 1 file, index.html, for this example) should be uploaded to the ESP32.

Uploading files to the ESP32 using SPIFFS

Run It!

With the index.html file uploaded and the Arduino code running, you should be able to connect to the ESP32’s access point. Using your phone or computer, search for open WiFi access points and connect to the one named ESP32-AP. When asked for a password, enter LetMeInPlz (or whatever you set the AP password to in the Arduino code).

Since we are not running a captive portal, we will need to specifically browse to the IP address of the ESP32. Open a browser and enter 192.168.4.1 (or whatever you discovered the IP address of the ESP32 to be). You should be served the index.html page from the ESP32, which looks like a button and a circle. Zoom in, if needed (as we’re not doing any fancy formatting or CSS here).

Press the Toggle LED button, and you should see the LED connected to the ESP32 flicker to life. The circle on the page should also turn red to denote the state of the LED.

Using an ESP32 as a web server and using WebSockets to control hardware

Going Further

Feel free to use this as the basis for your next Internet-controlled hardware project. I suppose it fits in the category of “IoT,” and WebSockets can be super useful for controlling hardware with much less latency than many other methods. You can also use them to get fast updates, if you wish to create a custom dashboard showing sensor data.

 

 

How to Use printf on STM32

By default, most microcontrollers have no concept of a console, so you have to help them out a bit. When it comes to debugging, outputting information to some kind of console can be extremely useful. One option is to use semihosting with STM32CubeIDE.

However, semihosting can be extremely slow. Another good option is to output debug information over the serial port (UART). We can call the STM32 HAL functions (e.g. HAL_UART_Transmit), but sometimes it’s easier to use the standard C library functions printf, scanf, and so on. To do that, we need to re-write the underlying functions.

Note: the code for this section is taken from Carmine Noviello’s Mastering STM32 book. I am simply updating the process for how to get retargeting working in STM32CubeIDE. All credit goes to Carmine Noviello for his code. You are welcome to write your own retarget.h and retarget.c files, if you wish.

Start a new STM32 project with all the defaults. I am using a Nucleo-L476RG for this example. Enable UART, if needed. For almost all the Nucleo boards, UART2 is tied to the ST-LINK microcontroller, which gives us a virtual COM port over USB. Save and generate code.

The first thing we need to do is disable syscalls.c. This file defines many of the same functions that we are trying to make. If you do not disable it, you will likely get a “multiple definition” error when you try to compile and link. For example:

multiple definition of `_isatty'

Right-click on the syscalls.c file and select Properties. Under C/C++ Build > Settings, check Exclude resource from build.

Exclude files from STM32CubeIDE build

Click Apply and Close. The file name in the project browser should now be grayed out.

This will allow us to not include a file (or folder) when we compile and link in STM32CubeIDE. The process is similar for most other Eclipse-based IDEs.

Create a new file in the Inc directory called retarget.h. Copy the following code into this file:

// All credit to Carmine Noviello for this code
// https://github.com/cnoviello/mastering-stm32/blob/master/nucleo-f030R8/system/include/retarget/retarget.h

#ifndef _RETARGET_H__
#define _RETARGET_H__

#include "stm32l4xx_hal.h"
#include <sys/stat.h>

void RetargetInit(UART_HandleTypeDef *huart);
int _isatty(int fd);
int _write(int fd, char* ptr, int len);
int _close(int fd);
int _lseek(int fd, int ptr, int dir);
int _read(int fd, char* ptr, int len);
int _fstat(int fd, struct stat* st);

#endif //#ifndef _RETARGET_H__

Save this file.

Create a new file in the Src directory called retarget.c. Copy the following code into this file:

// All credit to Carmine Noviello for this code
// https://github.com/cnoviello/mastering-stm32/blob/master/nucleo-f030R8/system/src/retarget/retarget.c

#include <_ansi.h>
#include <_syslist.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/times.h>
#include <limits.h>
#include <signal.h>
#include <../Inc/retarget.h>
#include <stdint.h>
#include <stdio.h>

#if !defined(OS_USE_SEMIHOSTING)

#define STDIN_FILENO  0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2

UART_HandleTypeDef *gHuart;

void RetargetInit(UART_HandleTypeDef *huart) {
  gHuart = huart;

  /* Disable I/O buffering for STDOUT stream, so that
   * chars are sent out as soon as they are printed. */
  setvbuf(stdout, NULL, _IONBF, 0);
}

int _isatty(int fd) {
  if (fd >= STDIN_FILENO && fd <= STDERR_FILENO)
    return 1;

  errno = EBADF;
  return 0;
}

int _write(int fd, char* ptr, int len) {
  HAL_StatusTypeDef hstatus;

  if (fd == STDOUT_FILENO || fd == STDERR_FILENO) {
    hstatus = HAL_UART_Transmit(gHuart, (uint8_t *) ptr, len, HAL_MAX_DELAY);
    if (hstatus == HAL_OK)
      return len;
    else
      return EIO;
  }
  errno = EBADF;
  return -1;
}

int _close(int fd) {
  if (fd >= STDIN_FILENO && fd <= STDERR_FILENO)
    return 0;

  errno = EBADF;
  return -1;
}

int _lseek(int fd, int ptr, int dir) {
  (void) fd;
  (void) ptr;
  (void) dir;

  errno = EBADF;
  return -1;
}

int _read(int fd, char* ptr, int len) {
  HAL_StatusTypeDef hstatus;

  if (fd == STDIN_FILENO) {
    hstatus = HAL_UART_Receive(gHuart, (uint8_t *) ptr, 1, HAL_MAX_DELAY);
    if (hstatus == HAL_OK)
      return 1;
    else
      return EIO;
  }
  errno = EBADF;
  return -1;
}

int _fstat(int fd, struct stat* st) {
  if (fd >= STDIN_FILENO && fd <= STDERR_FILENO) {
    st->st_mode = S_IFCHR;
    return 0;
  }

  errno = EBADF;
  return 0;
}

#endif //#if !defined(OS_USE_SEMIHOSTING)

Save this file.

Your project directory structure should look like the following:

Adding files to STM32CubeIDE directory structureNotice that we’ve added retarget.h and retarget.c. We also removed syscalls.c from the build, but the file still exists in our project directory.

Include stdio.h and retarget.h in your main.c file. You can now use printf and scanf, as shown here:

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include "retarget.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart2;

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART2_UART_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
  char buf[100];
  /* USER CODE END 1 */
  

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART2_UART_Init();
  /* USER CODE BEGIN 2 */
  RetargetInit(&huart2);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    printf("\r\nYour name: ");
    scanf("%s", buf);
    printf("\r\nHello, %s!\r\n", buf);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

// ***REST OF INITIALIZATION CODE NOT SHOWN***

Build this project and open a debugging session in STM32CubeIDE. Open your favorite serial terminal program and connect to the Nucleo’s COM port (baud rate of 115200, 8-N-1). You should be greeted by a query. Type out some answer and press enter. Note that you will not be able to see what you typed. However, the program should append your entered text to a “Hello” message.

Using printf and scanf on STM32

Try not to overflow your assigned character buffer. Bad things happen if you do. Or you could write some kind of bounds checking in your code. Anyway, this is just a starting point to hopefully help you with debugging on your STM32 project.

 

 

Debugging STM32 with ST-LINK

How to Use Semihosting with STM32

Semihosting is a debugging tool that is built in to most ARM-based microcontrollers. It allows you to use input and output functions on a host computer that get forwarded to your microcontroller over a hardware debugging tool (e.g. ST-LINK). Specifically, it allows you to use printf() debugging to output strings to your IDE’s console while running code on the microcontroller. Note that we’ll be doing this over the Serial Wire Debug (SWD) port.

Before I show you how to enable semihosting with STM32CubeIDE, note that the downside of semihosting is that it is resource intensive and very slow. This post on the Embedded in Rust blog shows how printing a simple sentence over semihosting can take over 100 ms! The upside is every ARM controller should have semihosting available, which can be a lifesaver if that’s all you have.

If you need printf-style debugging (because you can’t or don’t want to do step-through debugging), here are your options on STM32 parts:

  • Semihosting: should be built in to every ARM chip, but slow
  • Serial (UART): fast, but you need extra pins and hardware (such as a USB to Serial converter board)
  • Virtual COM Port (VCP): fast, but you need USB hardware (see my previous post for setting this up)
  • Instrumentation Trace Macrocell (ITM): fast output over dedicated SWO pin, but it’s only available on Cortex-M3+ parts (i.e. Cortex-M0 chips don’t have it)

Nucleo

If you are using a Nucleo board, semihosting will work, but I highly recommend using Serial (UART) instead. It’s faster, and every Nucleo board has a built-in Serial to USB converter (it’s part of the ST-LINK chip already on the board). If you look at the schematic of your Nucleo board, you should see TX and RX lines going from the target chip to the ST-LINK chip (circled in red below).

UART lines on Nucleo-32 board

Hardware Hookup

For this demonstration, I’ll be using the STM32F070 Breakout Board that I put together (project files can be found here). You will also need an STMicroelectronics ST-LINK to use as a programmer and debugger:

Connect the following pins from the ST-LINK debugger to your STM32 part:

  • 3.3 V
  • SWCLK
  • GND
  • SWDIO
  • RST

Plug the ST-LINK into your computer.

CAUTION! Do not plug in the STM32 board (or give it separate power), as we are using the ST-LINK to power our target board. Otherwise, you might damage the target board.

Debugging STM32 with ST-LINK

Start a New Project

Create a new project for your STM32 part (select STM32F070CB if you’re using the breakout board I mentioned earlier). You can leave everything in the graphical CubeMX portion as default.

STM32CubeIDE

Save and generate code.

Set Linker Parameters

Before writing any code, we need to tell our linker to use the newlib-nano libraries, which houses things like our printf() function. As a result, we also need to tell the linker to ignore the default syscalls.c file. If you forget this step, you will likely end up with several errors like: multiple definition of `_isatty’

To start, go to Project > Properties.

Go in to C/C++ General > Paths and Symbols. Click on the Source Location tab. Click on the arrow next to /<your project>/Src to view the filter. Select Filter (empty) and click the Edit Filter… button. Add syscalls.c to the Exclusion patterns list and click OK.

Adding syscalls.c to the exclusion list

Click Apply.

On the left-side pane, go into C/C++ Build and select the Tool Settings tab. Then, select MCU GCC Linker > Libraries. In the libraries pane, click the Add… button and enter rdimon. This enables librdimon for us to make system calls with semihosting.

Enable librdimon in STM32CubeIDE

Next, click on MCU GCC Linker > Miscellaneous while still in the Tool Settings tab. Click the Add… button and enter –specs=rdimon.specs into the dialog box.

Adding specs to linker

If you have used another STM32 IDE before and got semihosting to work, you probably needed to specify nano.specs and nosys.specs in the linker options. If you take a look at MCU Settings and MCU GCC Linker > General (within the Tool Settings tab), you will see that both of these have been set by default in STM32CubeIDE.

Click Apply and Close.

Add printf Code

Open Src/main.c. Above int main(void) , add the following line (I put mine in the USER CODE 0 section):

extern void initialise_monitor_handles(void);

In int main(void)  (before the while(1) loop), add the following line (I put mine in the USER CODE 1 section):

initialise_monitor_handles();

This configures the semihosting system calls for us.

Finally, inside the while(1) loop, add the following:

printf("Hello, World!\n");
HAL_Delay(1000);

To summarize, here is the section of code from main.c that you need to worry about:

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
extern void initialise_monitor_handles(void);
/* USER CODE END 0 */

/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
initialise_monitor_handles();
/* USER CODE END 1 */

/* MCU Configuration--------------------------------------------------------*/

/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();

/* USER CODE BEGIN Init */

/* USER CODE END Init */

/* Configure the system clock */
SystemClock_Config();

/* USER CODE BEGIN SysInit */

/* USER CODE END SysInit */

/* Initialize all configured peripherals */
/* USER CODE BEGIN 2 */

/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
printf("Hello, World!\n");
HAL_Delay(1000);
}
/* USER CODE END 3 */
}

Click Project > Build Project to compile and link everything.

Set Debug Configuration

Select Run > Debug As > STM32 MCU C/C++ Application. You should get a pop-up window asking you to “edit launch configuration properties.” If you have already done this once before, you will not get the launch configuration window. To get it, click on Run > Debug Configurations and select <your project>.elf under STM32 MCU Debugging.

Click on the Debugger tab. Change the Debug probe to ST-LINK (OpenOCD).

If you are using an official ST-LINK, you should be good to go. However, note that RST pin of the knockoff ST-LINK boards (such as the ST-LINK/V2 I listed above) may not work. As a result, you will need to use the software reset option when GDB starts.

To enable software reset, click Show generator options… in the Debugger tab (while still in the Debug Configurations window). Under Mode Setup, change Reset Mode to Software system reset.

Change OpenOCD to software reset in STM32CubeIDE

Go to the Startup tab and enter the following command: monitor arm semihosting enable

Enable semihosting session in gdb

This tells the GDB session to allow for semihosting. Click OK, and switch to the debugging perspective when asked.

Run It

In the debugging perspective, click Run > Resume, and you should see “Hello, World!” being printed at the bottom of the console once per second.

printf with semihosting on STM32CubeIDE

Semihosting can help you with your debugging efforts, should you need some kind of console output. Note that other standard commands, like scanf, should work (I have not tested it yet).

Nucleo-32 connected to a USB for VCP

Getting Started with STM32 Nucleo USB (Virtual Com Port)

As I continue my journey with STM32 development, I wanted to share my findings with how to get a Virtual COM Port (VCP) working with a Nucleo board.

Specifically, I’m using the Nucleo-F042K6, as it has a built-in USB device peripheral (full speed), and it does not require an external crystal. I highly recommend looking over the USB Hardware and PCB Guidelines document from ST Microelectronics to learn about what’s needed for your particular STM32 part. Note that if you are using a Nucleo board with an STM32F401 or STM32F411, you will need to solder an external crystal to the board, as it is unpopulated on those boards.

To begin, strip a USB cable or get a USB breakout board (like this one from SparkFun) and connect the lines to a breadboard as shown in the Fritzing diagram below. Make the following connections:

  • VUSB → Diode → Nucleo 5V (don’t want to short something if we’re plugging in 2 USB cables!)
  • USB D- → Nucleo D10 (PA11)
  • USB D+ → Nucleo D2 (PA12)
  • USB GND → Nucleo GND
  • USB GND → USB Shield (don’t know if this is necessary, but it makes me feel better)

Also, this is super important: remove the jumper that comes default on your Nucleo board! It bridges D2 and GND and will short out our D+ line if left in place.

Connecting an STM32 Nucleo board to USB for VCP

Plug the two USB cables from the Nucleo board into your computer. We’ll be sending our compiled program over to the ST-LINK side of the board, and the VCP will enumerate on the USB lines that we just added. I don’t have a bootloader working (yet) to where we can send binary files over VCP, but that’s on my to-do list.

Nucleo-32 connected to a USB for VCP

For this, I’m using STM32CubeIDE along with the STM32F0 HAL API.

In STM32CubeIDE, start a new project (File > New > STM32 Project). Choose your part or board. I’ll pick the Nucleo-F042K6, since that’s the board I have.

STM32CubeIDE part selector

On the next screen, give your project a useful name (such as “nucleo-f042k6-vcp-test”). Leave everything else as default. We’ll be using C as our language for this example. Click Finish, and you’ll be asked a few questions:

  • Yes to “initialize all peripherals with their default Mode”
  • Yes to “open this perspective now”

In the CubeMX configuration perspective, you’ll need to enable a few options to initialize the USB as a Virtual COM Port. In the Pinout & Configuration tab, go to Categories > Connectivity and click USB. Enable Device (FS). You should see PA11 and PA12 be automatically configured for USB_DM and USB_DP.

STM32CubeIDE selecting USB

Under Categories > Middleware, select USB_DEVICE. Change Class For FS IP to Communication Device Class (CDC). This tells the USB stack that we want to enumerate as a CDC device, which will allow us to send serial data to and from our computer across the USB lines.

Select USB CDC

Click on the Clock Configuration tab. The software will tell you that your clocks have issues. By default, the Nucleo-F042K6 board is configured for 8 MHz clocks for almost everything. We need to bump those up to 48 MHz for USB to work. Luckily, you can just click Yes on the pop-up when asked to run the automatic clock issues solver. This should automatically change all of the necessary clocks to 48 MHz.

Updating clock speeds to match USB

In the Project Manager tab, change the Minimum Heap Size to 0x100. The STM32F042K6 comes with 6 kB of RAM, which isn’t a whole lot. USB functionality takes up probably 2-3 kB worth of that memory, so we need to be careful about how we use the rest.

We can free up some space by adjusting the minimum heap and stack sizes. These parameters essentially reserve sections of data memory for the heap and stack. By setting 0x200 and 0x400, we’ve told the processor to reserve 1 kB of RAM for the heap and stack, respectively. We need to lower one of them in order to accommodate the USB functions. I chose heap, as it seems less likely we’ll be using dynamically allocated memory for this application.

If you get an error message like `._user_heap_stack’ will not fit in region `RAM’  or region `RAM’ overflowed by 64 bytes  when you compile, it means you are running out of RAM in your application. You will need to go into the Device Configuration Tool (the .ioc file) and adjust the stack and heap settings as described above. Alternatively, you can go into the .ld linker script and look for the _Min_Heap_Size  and _Min_Stack_Size  settings there (just know that this file will be overwritten if you make changes in the graphical Device Configuration Tool).

Allocate minimum heap size in STM32CubeIDE

Click File > Save to save the changes to the CubeMX configuration. You’ll be asked if you want to generate Code. Click Yes.

In your project files, navigate to the Src directory. Notice that you have several USB-related files that have been added. usbd_cdc_if.c contains the functions that allow us to send and receive serial data using the USB Communication Device Class. Feel free to peek in there, if you wish.

Open up Src > main.c. At the top, under /* USER CODE BEGIN Includes */ , enter the following line:

#include "usbd_cdc_if.h"

This will let us call functions from the CDC library. Scroll down to our while(1) loop in main. Under /* USER CODE BEGIN 3 */  (but still inside the while loop), enter the following:

uint8_t buffer[] = "Hello, World!\r\n";
CDC_Transmit_FS(buffer, sizeof(buffer));
HAL_Delay(1000);

Here, we create a simple string and call a USB CDC function to send out that string over the USB lines. We then wait for 1 second before repeating this action ad infinitum.

Code to transmit a string over a serial VCP via USB CDC in STM32

Click Project > Build All to build the project. Click Run > Debug As > STM32 MCU C/C++ Application. A pop-up window will appear asking you to create a new launch configuration. Note that if you are not using a Nucleo board or an ST-LINK, you can change the hardware debugger (e.g. to a Segger J-LINK) in the Debugger tab. If you are using a Nucleo, leave everything as default and click OK.

New STM32 debug configuration

If asked to switch to the Debug perspective, click Switch. When the new perspective opens, click Run > Resume (or the play button on the toolbar).

Debugging in STM32CubeIDE

Your code should now be running, and the microcontroller will enumerate as a USB device! Feel free to verify the new serial port in your OS’s device manager. This should show up as a USB serial or COM port.

STM32 as a USB serial (COM) port

Open your favorite serial terminal program, and enter the following connection details:

  • Port: USB serial or COM port discovered above
  • Baud rate: 9600
  • Data bits: 8 (default)
  • Parity: None (default)
  • Stop bits: 1 (default)

Setting serial terminal to listen for STM32

Open the serial connection, and you should be greeted by that oh-so-familiar phrase, repeating over and over again:

STM32 outputting strings via serial COM port on USB

When you’ve had enough strings, feel free to press the stop button in the IDE to stop the program from running on the STM32.

Interestingly enough, the STM32 seems to support autobaud detection by default. Try changing the serial terminal’s baudrate to anything else and see if you can still receive text. I bet that disabling autobaud would save some RAM, but I have not discovered how yet.

From what I understand, CDC_Receive_FS() is a callback, so you’ll need to change its definition in usbd_cdc_if.c to get it to work. I haven’t played with it yet, so that’ll be a post for another time.

I hope that this helps you get started with your STM32 USB journey!

 

SAMD21 PWM with the FDPLL

Arduino Zero (SAMD21) FDPLL with CMSIS

One of the coolest, tucked-away features in the SAMD21 is the fractional digital phase locked loop (FDPLL). I still only have a vague idea of how phase locked loops (PLLs) work, but nonetheless, we can use the SAMD21’s PLL to create a 96 MHz clock.

The bad news is that, to the best of my knowledge, you cannot use this clock signal as the main clock source for the SAMD21 (essentially overclocking it). However, we can use it to power some other peripherals, like the generic clocks, timers, and pulse width modulation (PWM) signals.

If you are not familiar with how the clocks on the SAMD21 work, I recommend reading my previous post. You will also need to refer to the SAMD21 datasheet.

Let’s take a look at how you use the FDPLL on the SAMD21.

Step 1: Select a Clock Source

The FDPLL isn’t a standalone clock. You’ll need to select a source to feed the PLL, and the source clock needs to be in the 32 kHz to 2 MHz range. According to section 16.8.19 (page 208) of the datasheet, there are only 3 acceptable clock sources for the FDPLL: XOSC32, XOSC, and a specific GCLK (known as GCLK_DPLL). You can set the clock source for the PLL in the DPLLCTRLB register.

Clock sources for DPLL on SAMD21

Step 2: Configure FDPLL

Next, you will need to set the frequency multiplier in DPLLRATIO register. There are two numbers you must configure: the ratio (LDR bits) and the ratio fractional part (LDRFRAC bits). The equation below (from section 16.6.8.3, page 158) shows how to calculate the output frequency of the FDPLL. fclk_fdpll96m is the output frequency and fclk_fdpll96m_ref is the reference clock (the input clock source).

SAMD21 FDPLL equation

So, if we want to turn a 1 MHz clock source into a 96 MHz output, we would set LDR to 95 and LDRFRAC to 0.

Step 3: Turn It On

By default, the FDPLL is disabled on reset. To enable it, we just write a 1 to the ENABLE bit in the DPLLCTRLA register.

Step 4: Use It!

The FDPLL can be used as a clock source for any of the generic clocks found in the generic clock controller. This includes the USB clock, watchdog timer, clock channels, SERCOMs, etc. Section 14.8.3 (page 107) has a table (too big to copy here) that gives you an idea of the available GCLKs. If you use one of the GCLK channels, you can use it to clock a peripheral, like PWM!

Example: A More Complicated 1 MHz Signal

In my last post, I showed you how to create a 1 MHz signal using GCLK4, TCC2, and a PWM peripheral. Let’s do the same thing, but in a much more roundabout way!

We’ll feed GCLK4 from the main 48 MHz clock, divide the clock by 48, feed that signal to GCLK_DPLL, which is then sent to the FDPLL. The DPLL will create a 96 MHz clock signal, which we’ll send to GCLK5 to be used as the basis for TCC2 and our PWM signal. This diagram should offer a much better explanation of what’s going on:

SAMD21 PWM with the FDPLL

Because our PWM clock is running twice as fast as in the previous example, we’ll need to adjust the TOP to be twice as high. We’ll use 95 instead of 47 to achieve our 1 MHz signal.

Here is the Arduino code to accomplish this:

// I have no idea why these definitions were left out of the Arduino headers
#define GCLK_GENCTRL_SRC_DPLL96M_Val 0x8ul  // (GCLK_GENCTRL) DPLL96M output
#define GCLK_GENCTRL_SRC_DPLL96M (GCLK_GENCTRL_SRC_DPLL96M_Val << \
                                  GCLK_GENCTRL_SRC_Pos)

// Number to count to with PWM (TOP value). Frequency can be calculated by
// freq = GCLK4_freq / (TCC2_prescaler * (1 + TOP_value))
// With TOP of 95, we get a 1 MHz square wave in this example.
// TCC2 is 16 bits
uint16_t period = 96 - 1;
 
void setup() {

  // Flash wait states NVMCTRL->CTRLB.bit.RWS set to 1 by Arduino framework

  // Configure generic clock generator 4
  GCLK->GENCTRL.reg = GCLK_GENCTRL_IDC |          // Improve duty cycle
                      GCLK_GENCTRL_GENEN |        // Enable generic clock gen
                      GCLK_GENCTRL_SRC_DFLL48M |  // Select 48MHz as source
                      GCLK_GENCTRL_ID(4);         // Apply to GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Set clock divider of 48 to generic clock generator 4 (1 MHz)
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(48) |        // Divide 48 MHz by 48
                     GCLK_GENDIV_ID(4);           // Apply to GCLK4 4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Enable GCLK4 and connect it to GCLK_DPLL
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |        // Enable generic clock
                      GCLK_CLKCTRL_GEN_GCLK4 |    // Select GCLK4
                      GCLK_CLKCTRL_ID(1);         // Feed GCLK4 to GCLK_DPLL
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Set DPLL ratio to 1 MHz * (95 + 1) = 96 MHz
  SYSCTRL->DPLLRATIO.reg = SYSCTRL_DPLLRATIO_LDRFRAC(0) | // Ratio fractional
                           SYSCTRL_DPLLRATIO_LDR(95);     // Ratio

  // Configure DPLL to disregard phase lock and select GCLK as source
  SYSCTRL->DPLLCTRLB.reg = SYSCTRL_DPLLCTRLB_LBYPASS |    // Bypass lock
                           SYSCTRL_DPLLCTRLB_WUF |        // Wake up fast
    SYSCTRL_DPLLCTRLB_REFCLK(SYSCTRL_DPLLCTRLB_REFCLK_GCLK_Val); // Select GCLK
  
  // Enable DPLL
  SYSCTRL->DPLLCTRLA.reg |= SYSCTRL_DPLLCTRLA_ENABLE;
  
  // Configure generic clock generator 5
  GCLK->GENCTRL.reg = GCLK_GENCTRL_IDC |          // Improve duty cycle
                      GCLK_GENCTRL_GENEN |        // Enable generic clock gen
                      GCLK_GENCTRL_SRC_DPLL96M |  // Select DPLL as source
                      GCLK_GENCTRL_ID(5);         // Apply to GCLK5
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Set clock divider of 1 to generic clock generator 5 (96 MHz)
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(1) |        // Divide 96 MHz by 1
                     GCLK_GENDIV_ID(5);           // Apply to GCLK5
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Enable GCLK5 and connect it to TCC2 and TC3
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |        // Enable generic clock
                      GCLK_CLKCTRL_GEN_GCLK5 |    // Select GCLK5
                      GCLK_CLKCTRL_ID_TCC2_TC3;   // Feed GCLK5 to TCC2/TC3
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization
  
  // Divide counter by 1 giving 96 MHz (10.42 ns) on each TCC2 tick
  TCC2->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV1_Val);

  // Use "Normal PWM" (single-slope PWM): count up to PER, match on CC[n]
  TCC2->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;         // Select NPWM as waveform
  while (TCC2->SYNCBUSY.bit.WAVE);                // Wait for synchronization

  // Set the period (the number to count to (TOP) before resetting timer)
  TCC2->PER.reg = period;
  while (TCC2->SYNCBUSY.bit.PER);

  // Set PWM signal to output 50% duty cycle
  // n for CC[n] is determined by n = x % 4 where x is from WO[x]
  TCC2->CC[0].reg = period / 2;
  while (TCC2->SYNCBUSY.bit.CC0);

  // Configure PA16 (D11 on Arduino Zero) to be output
  PORT->Group[PORTA].DIRSET.reg = PORT_PA16;      // Set pin as output
  PORT->Group[PORTA].OUTCLR.reg = PORT_PA16;      // Set pin to low

  // Enable the port multiplexer for PA16
  PORT->Group[PORTA].PINCFG[16].reg |= PORT_PINCFG_PMUXEN;

  // Connect TCC2 timer to PA16. Function E is TCC2/WO[0] for PA16.
  // n for PMUX[n] is given by floor(pin_num / 2)
  // Odd pin num (2*n + 1): use PMUXO
  // Even pin num (2*n): use PMUXE
  PORT->Group[PORTA].PMUX[8].reg = PORT_PMUX_PMUXE_E;

  // Enable output (start PWM)
  TCC2->CTRLA.reg |= TCC_CTRLA_ENABLE;
  while (TCC2->SYNCBUSY.bit.ENABLE);              // Wait for synchronization
}

void loop() {
  // Do nothing
}

This is certainly a crazy, circuitous route to take for a 1 MHz signal, but it offers much greater control of the PWM frequency. If your application needs this kind of control (e.g. motor controls), firing up the PLL could be incredibly helpful.

In case you’re wondering where I’m going with this project: let’s just say that this fine control of clock speeds lets us create something like a digitally controlled oscillator (DCO).

1 Mhz square wave on Analog Discovery 2

Arduino Zero (SAMD21) Raw PWM Using CMSIS

Arduino has a habit of making pulse width modulation (PWM) pins with a set frequency (or limited frequency options). For the average user, this is not a bad thing, considering it hides all the ugliness (or beauty, depending on your perspective) of manually setting registers.

For those of us that want to dig deeper into the SAMD21, there are a few options. Almost all modern implementations of the ARM Cortex architectures can use the Cortex Microcontroller Software Interface Standard (CMSIS). This is a set of firmware libraries that provide an interface to the registers along with some other functions. Ideally, you should be able to use CMSIS regardless of the manufacturer of the chip (e.g. Microchip, STMicroelectronics, TI).

Some vendors have decided to include their own abstraction layer on top of CMSIS to help people configure and use their parts. In some cases, this can make setting up registers easy (especially if there is a slick GUI program that generates code for you, like STMicro’s STM32CubeMX). Microchip’s abstraction layer is known as the Advanced Software Framework (ASF).

Many professional developers swear by the vendor-provided abstraction layer (e.g. ASF). If you’re like me, you want to know what’s going on at the register level. Not that there is anything wrong with ASF, I just like to learn about the low-level stuff.

Here’s the problem: when it comes to manually configuring registers for 32-bit ARM microcontrollers, it’s vastly more difficult than it is for most other 8- or 16-bit architectures.

I’ve spent the last few days digging through the SAMD21 datasheet and online examples to get some insight into using CMSIS. On the surface, it’s not too bad: you just need to set some values in some registers to get a pin to output a square wave. In reality, it requires a good amount of code to perform this simple action.

Please note that I am just beginning to understand how the SAMD21 works, and this article is simply a chronicle of my learning journey. If you think I missed something or got something wrong, please let me know in the comments!

The best description I can find for how this works is from section 14.3 (page 95) in the datasheet:

To configure a pin for PWM requires a good bit of effort. The basic idea is to select a clock source from SYSCTRL (such as the 48 MHz system clock) and connect it to a Generic Clock Generator. The SAMD21 has 9 different generators we can use. We can configure each generator to have its own divider and mask (so that we can run various peripherals off different speeds thanks to having separate clock generators).

Then, we connect our generator to the peripheral we want. In the example below, we’ll be using the Timer/Counter for Control Applications (TCC). The SAMD21 also has a Timer/Counter peripheral (TC), but from what I could grasp, it’s “TCC lite” (i.e. TCC with less features).

Once we have our peripheral configured (TCC, in this case), we then want to connect that to a hardware pin so that we get an actual waveform to appear.

While this might seem extremely complicated, the upside is that the SAMD21 is an extremely powerful chip. We can connect and configure different peripherals in many, many different ways (thanks to all this multiplexing of clock signals!).

Let’s walk through an example of outputting a 1 MHz square wave on pin PA18. Note that PA18 is D10 on the Arduino Zero, Adafruit Feather M0, and the SparkFun SAMD21 Mini Breakout board. If you don’t have one of these boards, check your schematic to see where PA18 goes. Also, note that there are lots of ways to output a 1 MHz square wave on a pin. We’ll be doing this using a 50% duty cycle PWM and the 48 MHz system clock.

Step 1: Enable Clock Source in SYSCTRL

You have a few options for clock sources:

  • External oscillator (0.4 – 32 MHz)
  • External oscillator at 32 kHz
  • Internal oscillator at 32 kHz
  • Ultra low power internal oscillator at 32 kHz
  • Internal oscillator at 8 MHz
  • Digital frequency-locked loop at 48 MHz
  • Fractional digital phase-locked loop (48 – 96 MHz)

Some of these, like the phase-locked loop, can only be used to provide a clock signal for the peripherals. It cannot be used as a system clock. As such, the maximum processing speed of the SAMD21 is 48 MHz.

In most of the Arduino implementations of the SAMD21, the 48 MHz clock is already enabled for us (in the background, as part of the framework). That means we don’t need to initialize it our user code.

Step 2: Configure Clock Generator and Connect It to a Clock Source

Generic clock generators create a square wave (inside the chip) that we can frequency divide, mask, and connect to peripherals. The SAMD21 has 9 clock generators for us to use. We need to pick one, initialize it, and connect it to our clock source.

From what I could find, Generic Clock Generators 0 and 1 were used for other Arduino functions, so you probably want to avoid using those.

Step 3: Configure Peripheral and Connect It to a Clock Generator

The SAMD21 has several peripherals for us to use. We’ll be focusing on the TCC for this example. There are 3 TCCs for us to use, and each one has its own set of unique features. TCC0 and 1 are 24 bits wide whereas TCC2 is limited to 16 bits for its counter. From section 30.1 (page 651) in the datasheet:

SAMD21 TCC configuration chart

With TCC, we can set it to generate a PWM waveform on a pin. We will use a simple, single-slope PWM wave that counts up to a given value. During that counting process, if the value of the counter is equal to the Compare and Capture (CC) register, then the pin will toggle to low. Once the counter reaches the value in the Period (PER) register, then the pin will toggle back to high. The counter resets and starts over again.

From section 30.6.2.5 (page 660) in the datasheet, we get a diagram of how the PWM counter works. Note that TOP, for this mode, refers to the value in the PER register.

SAMD21 Normal PWM operation

WO[x] stands for “waveform output,” and the x is the pin we wish to use. In the datasheet, navigate to section 6.1 (page 21) to see a chart of the pin to function mapping (too long to copy here). Under functions E and F, you’ll see the TCC options. For our PA18 example, you can see that function F is TCC0/WO[2].

Here is the really confusing part: there are only 4 CC options (CC0 through CC3) for setting the duty cycle, but the pin/function mapping chart shows up to WO[7] channels. So, how do we connect a given CC register to a WO pin that’s greater than 3? This poorly labeled chart buried away in part 30.6.3.7 (page 678) holds the key:

SAMD21 output matrix

WO[7] is on the left and WO[0] is on the right. The OTMX configuration bits are found in the WEXCTRL register. By setting the OTMX bits to values 0-3, you can change which CC registers the various WO pins use.

By default, OTMX is set to 0x0, so the top row of the chart is used. Since we are using WO[2], we need to use CC2, according to the chart. If you want to use WO[5], you would use CC1.

Step 4: Set Data Direction and Peripheral for Pin

Because we’ve already configured TCC0/WO[2], that locks us in to pin PA18. Pins have a variety of functions that they can be set to (once again, refer to the chart in section 6.1).  For our project, we need to set PA18 to Function F (TCC0/WO[2]). We also need to set PA18 as an output in the data direction (DIR) register.

Example Code

Now for the moment you’ve all been waiting for (or, let’s be honest, the part you scrolled down for, skipping the above descriptions). Here is some example Arduino code to create a 1 MHz square wave on PA18 (D10 on the Zero, Adafruit Feather M0, or SparkFun SAMD21 Mini).

Note: Many thanks to MartinL on the Arduino forums! Their code snippet (found here) helped me tremendously. The following code is based on that snippet.

// Number to count to with PWM (TOP value). Frequency can be calculated by
// freq = GCLK4_freq / (TCC0_prescaler * (1 + TOP_value))
// With TOP of 47, we get a 1 MHz square wave in this example
uint32_t period = 48 - 1;
 
void setup() {

  // Because we are using TCC0, limit period to 24 bits
  period = ( period < 0x00ffffff ) ? period : 0x00ffffff;

  // Enable and configure generic clock generator 4
  GCLK->GENCTRL.reg = GCLK_GENCTRL_IDC |          // Improve duty cycle
                      GCLK_GENCTRL_GENEN |        // Enable generic clock gen
                      GCLK_GENCTRL_SRC_DFLL48M |  // Select 48MHz as source
                      GCLK_GENCTRL_ID(4);         // Select GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Set clock divider of 1 to generic clock generator 4
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(1) |         // Divide 48 MHz by 1
                     GCLK_GENDIV_ID(4);           // Apply to GCLK4 4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization
  
  // Enable GCLK4 and connect it to TCC0 and TCC1
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |        // Enable generic clock
                      GCLK_CLKCTRL_GEN_GCLK4 |    // Select GCLK4
                      GCLK_CLKCTRL_ID_TCC0_TCC1;  // Feed GCLK4 to TCC0/1
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Divide counter by 1 giving 48 MHz (20.83 ns) on each TCC0 tick
  TCC0->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV1_Val);

  // Use "Normal PWM" (single-slope PWM): count up to PER, match on CC[n]
  TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;         // Select NPWM as waveform
  while (TCC0->SYNCBUSY.bit.WAVE);                // Wait for synchronization

  // Set the period (the number to count to (TOP) before resetting timer)
  TCC0->PER.reg = period;
  while (TCC0->SYNCBUSY.bit.PER);

  // Set PWM signal to output 50% duty cycle
  // n for CC[n] is determined by n = x % 4 where x is from WO[x]
  TCC0->CC[2].reg = period / 2;
  while (TCC0->SYNCBUSY.bit.CC2);

  // Configure PA18 (D10 on Arduino Zero) to be output
  PORT->Group[PORTA].DIRSET.reg = PORT_PA18;      // Set pin as output
  PORT->Group[PORTA].OUTCLR.reg = PORT_PA18;      // Set pin to low

  // Enable the port multiplexer for PA18
  PORT->Group[PORTA].PINCFG[18].reg |= PORT_PINCFG_PMUXEN;

  // Connect TCC0 timer to PA18. Function F is TCC0/WO[2] for PA18.
  // Odd pin num (2*n + 1): use PMUXO
  // Even pin num (2*n): use PMUXE
  PORT->Group[PORTA].PMUX[9].reg = PORT_PMUX_PMUXE_F;

  // Enable output (start PWM)
  TCC0->CTRLA.reg |= (TCC_CTRLA_ENABLE);
  while (TCC0->SYNCBUSY.bit.ENABLE);              // Wait for synchronization
}

void loop() {
  // Do nothing
}

Run it, and connect an oscilloscope to pin PA18. You should get a nice 1 MHz signal (well, as nice as you can expect from a microcontroller). Here is the output as measured on my Analog Discovery 2:

1 Mhz square wave on Analog Discovery 2

That’s it for the setup of a PWM timer on the SAMD21. Feel free to use the code as a starting point for your own project!

Next SAMD21 article: Arduino Zero (SAMD21) FDPLL with CMSIS

Edit [12/23/18]: Added section about TCC counter sizes and updated code to reflect unsigned 24 bit maximum for TCC0.

Teensy LC input capture test

Learning the Teensy LC: Input Capture

Teensy LC input capture test

As a third step to learning the Teensy LC, I decided to tackle input capture. I discovered that there is no separate interrupt vector for input capture; it is the same vector used by the timer interrupt. This means that if we are looking for a timer overflow event as well as a pin change for input capture, we must check for that specific interrupt flag within the interrupt service routine (ISR).

Continue Reading
Custom ISR in the Teensy LC

Learning the Teensy LC: Interrupt Service Routines

Custom ISR in the Teensy LC

As a follow-on to my PWM experiments, I wanted to create a custom interrupt service routine (ISR) in the Teensy LC. This would be similar to using the ISR() macro in an ATmega-based Arduino. Because the ARM has different vectors (and some other weird/cool things, like configurable interrupt priority levels), I knew the normal ATmega vectors would not work. Luckily, PJRC created a set of vectors to work with the Teensy, which can be found in this code.

You don’t need any additional components for this example. We are just going to flash the on-board LED using our custom ISR.

Continue Reading
The Teensy LC doing PWM the hard way

Learning the Teensy LC: Manual PWM

Teensy_LC_PWM

I’m in the process of learning how to use the Teensy LC, which is the newest Arduino-compatible module from PJRC. It is built around the MKL26Z64VFT4 (ARM Cortex-M0+), which can be had for around $2.20 for 100 (according to Digi-Key). I really like the microcontroller, as it is much more powerful than the ubiquitous ATmega 328p (not that I don’t like the 328p) for about the same price.

The awesome people at PJRC have gone through the process of creating a set of libraries and hardware definitions so that you can program the entire Teensy line from Arduino. It is quite slick, and if you have not tried it yet, I suggest you give it a shot. It does require installing some software on top of the Arduino IDE, but it opens up the world of ARM to Arduino users.

Because I have decided to use the Teensy LC (or, more specifically, the MKL26Z64VFT4) for a personal project, I wanted to learn how to manually set up interrupts. As it turns out, ARM interrupts are more complicated than the interrupts found in most ATmega processors. More importantly, I wanted to learn how to do this from the Arduino IDE (because reasons). PJRC still has many of the labels for registers and bit fields set to the Teensy 3.1, which work well enough for the Teensy LC, but might not be correct.

Continue Reading