MCC Melody Design Patterns Introduction

Different ways to organize main.c and other application-level files.

The control flow of a microcontroller (MCU) refers to the sequence of tasks that its Central Processing Unit (CPU) works on. The main.c and other application level code manage the control flow in a microcontroller. This document describes some of the most common control flow patterns and implementation considerations for building projects using MCC Melody API.

MCC Melody Components, i.e., Libraries, Drivers and Peripheral Libraries (PLIBs), generate code/APIs that become the building blocks that developers can use to simplify and accelerate the process of developing embedded applications. The examples integrated into the MCC Melody Example Components (or API reference) represent larger building blocks of basic application functionality, such as:
  • 100 ms Timer: Configures a timer to toggle an LED at a period of 100 ms
  • UART Control Commands: Implement a basic Command Line Interface (CLI) to turn an LED ‘ON’ or ‘OFF’
  • ADC Basic Printf: An ADC sample is taken every 500 ms, and the result is sent to a PC terminal

Moving beyond these simple examples to a more complex and complete embedded application, e.g., by combining a number of these building blocks, will require deliberate decisions on how the application control flow is structured. As an application grows, e.g., by adding new features, it may pass through certain complexity thresholds, at which point the most suitable control flow design pattern may change. Ideally, at this point, the application should be refactored.

Tip: MCC Melody Example Components have implementations corresponding to the design patterns presented below to facilitate more easily combining use cases from the same design pattern, when building your application.
Attention: This document includes various examples. They serve to illustrate the concepts introduced. Links to full configuration instructions and code are added above the code, as the examples are incomplete.
The following chapters consider some of the most widely used options for structuring the control flow of embedded applications.
  1. Polled control flow (blocking) - This sequential form of control flow waits for each task to be complete before moving on to the next. It is the simplest form of control flow but can be inefficient in terms of both CPU usage and power consumption. The software continuously checks (or polls) the status of a device and waits (blocks) until a specific condition is met or an operation is completed. During this waiting period, the system does not perform other tasks, dedicating its resources entirely to monitoring the device’s status.
  2. Interrupt/Callback-based control flow (non-blocking) - Tasks performed by the system are triggered by different events without continuous CPU involvement, i.e., this is a form of Event-Driven Programming. Interrupts are used to immediately respond to events, temporarily pausing the main program. Callbacks are specific functions invoked in response to these interrupts, providing a structured way to handle the interrupt-triggered events.
  3. State Machine-based control flow - Each application task has its control loop consisting of a series of states. Transitions between these states based on internal or external events provide a clear, organized structure to handle application logic predictably. It is possible to poll (block) state transitions and use interrupts/callbacks for quick events. This approach primarily focuses on the internal state and logic of the application.
  4. Low Power - Some of these approaches are better suited to achieving an ultra-low power embedded application. However, within each category, steps to achieve lower power are considered.
Tip: MCC Melody API is non-blocking (with very few exceptions), allowing the user to easily implement non-blocking code or, in the case that blocking code is required, to build the blocking checks around the MCC Melody API.
Tip: The code is often categorized as blocking vs. non-blocking. Polled code is often blocking, and interrupt/callback-based code tends to be non-blocking. While polled code typically does not use callbacks or interrupts, there are exceptions:
  • Polled code with callbacks: Some Melody Components generate a task routine for Polling mode. The non-blocking task routines check a relevant interrupt flag before calling a callback function. See Task Routines.

  • Polled code with interrupt/callback: For critical tasks that require immediate attention, such as if there is a hardware Fault or a real-time event that quickly must be handled while the application is polling for something else, an interrupt can be used to trigger a polling routine to make a quick response to the event.

Note: API from MCC Melody Drivers and Peripheral Libraries (PLIBs) configure the MCU registers covered in the foundational application notes PIC1000 and AVR1000B. MCUs contain a CPU, memories and peripherals (e.g., an ADC, UART, etc.). Each is defined in the device data sheet and configurable through registers. The form of this register-level code is influenced by the device header file, which, in turn, influenced by the structure of the MCU’s peripheral modules (data sheet). These application notes have symmetrical tables of contents for PIC® and AVR® MCUs, respectively, covering:
  1. Data Sheet Module Structure and Naming Conventions.

  2. Modules Representation in Header Files.

  3. Writing code to configure registers.

  4. Application example to show alternative ways of writing code.

To better understand the drivers generated by MCC Melody, see How to Write Bare-metal Code for PIC/AVR MCUs, which discusses PIC1000/AVR1000B and shows how these documents apply to the code generated by MCC.