PIC Benchmark

Comparing the computing performance of the PIC microntroller series

Written by Guy Fernando

Created Mar 2023 - Last modified Nov 2024


The PIC Series

Microchip PIC Series

Microchip PICs (Peripheral Interface Controllers) are a family of microcontrollers that are widely used in embedded systems for a variety of applications. These microcontrollers are small, low-power, and low-cost devices that can be programmed to perform a specific task or set of tasks.

PICs offer a range of features and capabilities, such as analogue-to-digital converters, timers, communication interfaces, and more. They can be programmed using various programming languages, including the C and C++ programming languages, and assembly language.

One of the benefits of using PICs is that many devices are still available in the DIL (Dual In-line) packages, which offer ease of use for prototyping new products, and for in education. Also if a DIL packaged PIC is damaged or needs to be replaced, it can be easily removed and replaced with a new one, without the need for specialized equipment. DIL packaged PICs are often used in educational settings to teach basic electronics and programming, due to their ease of use and accessibility.

Microchip PIC microcontrollers come in many different series, each with its own set of features and capabilities.

  • PIC10 / PIC12 / PIC16 - These are 8-bit microcontrollers with varying amounts of flash memory, RAM, and peripherals. They are often used in simple applications, such as remote controls, lighting controls, and small appliances.

  • PIC18 - This is an 8-bit microcontroller with a larger memory and more peripherals than the previous series. It is often used in applications that require more processing power, such as industrial automation, medical devices, and automotive systems.

  • PIC24 - This is a 16-bit microcontroller that offers higher performance and more memory than the previous series. It is often used in applications that require more advanced features, such as high-speed communication, digital signal processing, and real-time control.

  • dsPIC - This is a series of 16-bit microcontrollers that are optimized for digital signal processing (DSP) applications. They offer high-speed arithmetic operations and multiple peripherals that are designed specifically for DSP.

  • PIC32 - This is a 32-bit microcontroller that offers even higher performance and more memory than the previous series. It is often used in applications that require complex processing, such as advanced robotics, audio/video processing, and network communication.

Each series of PIC microcontrollers has its own advantages and disadvantages, and the choice of which one to use depends on the specific requirements of the application. Factors such as cost, power consumption, processing speed, and memory size will all influence the selection process.


Benchmark Code

The synthetic benchmark discussed here involves running a series of computing tests and measuring the time it takes to complete each test, and therefore provides a standard metric for checking the performance across the PIC series.

  • M1 - Fixed-point maths test
  • M2 - Floating-point maths test
  • M3 - Logical operations test
  • M4 - Looping and conditional jump test
  • M5 - Recursion and stack test
  • M6 - Character string manipulation test

The standard Whetstone and Dhrystone benchmark tests were considered, but their code is just too large to fit into the program flash memory of the lower end PICs.

main.c
        
          
          
#include "mcc_generated_files/mcc.h"

// Define which benchmark to run (M1 ... M6).
#define BENCHMARK_M1

// Define multiplier for extending the benchmark time.
#define BENCHMARK_X10

// Include the benchmark library.
#include "../benchmark.h"

void main(void)
{
    SYSTEM_Initialize();

    while (1)
    {
        // Generate pulse output t1, from GPIO RA2,
        // indicating the time taken to run the benchmark.
        IO_RA2_SetHigh();
        benchmark();
        IO_RA2_SetLow();
        
    	// Re-run the benchmark every 3 seconds.
        __delay_ms(3000);
    }
}
        
      
benchmark.h
        
          
          
/**********************************************************
 * 
 * File:   benchmark.h
 * Author: Guy Fernando
 * 
 *********************************************************/

#ifndef _BENCHMARK_H
#define	_BENCHMARK_H

/*
M1 - Fixed-point maths test.
M2 - Floating-point maths test.
M3 - Logical operations test.
M4 - Looping and conditional jump test.
M5 - Recursion and stack test.
M6 - Character string manipulation test.
*/

#include "string.h"
#include "math.h"
#include "stdint.h"

#if defined(BENCHMARK_X1000)
    #define BENCHMARK_MULTIPLIER 1000
#elif defined(BENCHMARK_X100)
    #define BENCHMARK_MULTIPLIER 100
#elif defined(BENCHMARK_X10)
    #define BENCHMARK_MULTIPLIER 10
#else
    #define BENCHMARK_MULTIPLIER 1
#endif

#if defined(BENCHMARK_M1) || defined(BENCHMARK_ALL)

// M1 - Fixed-point maths test.
//
void benchmark_m1(void)
{
    uint16_t i;
    uint16_t result1 = 0;
    uint32_t result2 = 0L;
    uint16_t m;

    for (m = 0; m < BENCHMARK_MULTIPLIER; m++)
    {
#if defined(__PIC32MX__)
        Nop();
#endif
        for (i = 0; i < UINT16_MAX >> 1; i++)
        {
            result1 += (i % 32767) * (i % 32767);
        }

        for (i = 0; i < UINT32_MAX >> 17; i++)
        {
            result2 += (i % 2147483647L) * (i % 2147483647L);
        }
    }
}

#endif
#if defined(BENCHMARK_M2) || defined(BENCHMARK_ALL)

// M2 - Floating-point maths test.
//
void benchmark_m2(void)
{
    double i;
    double result = 0.F;
    uint16_t m;

#if defined(__PIC32MX__)
    Nop();
#endif
    for (m = 0; m < BENCHMARK_MULTIPLIER; m++)
    {
        for (i = 0.F; i < 1000.F; i++)
        {
            result += sin(i) * sinh(i);
        }   
    }
}

#endif
#if defined(BENCHMARK_M3) || defined(BENCHMARK_ALL)

// M3 - Logical operations test.
//
void benchmark_m3(void)
{
    uint32_t i;
    uint32_t result = 0;
    uint16_t m;

    for (m = 0; m < BENCHMARK_MULTIPLIER; m++)
    {
#if defined(__PIC32MX__)
        Nop();
#endif
        for (i = 0; i < UINT32_MAX >> 13; i++)
        {
            result |= (i & 1) ^ (i & 2);
        }
    }
}

#endif
#if defined(BENCHMARK_M4) || defined(BENCHMARK_ALL)

// M4 - Looping and conditional jump test.
//
void benchmark_m4(void)
{
    uint32_t i;
    uint8_t result = 0;
    uint16_t m;

    for (m = 0; m < BENCHMARK_MULTIPLIER; m++)
    {
#if defined(__PIC32MX__)
        Nop();
#endif
        for (i = 0; i < UINT32_MAX >> 12; i++)
        {
            if ((i % 2) == 0) 
                result++;
            else
                result--;
        }    
    }
}

#endif
#if defined(BENCHMARK_M5) || defined(BENCHMARK_ALL)

// M5 - Recursion and stack test.
//
void benchmark_m5_recurse(uint8_t depth)
{
    if (depth == 0)
        return;
    else
        benchmark_m5_recurse(--depth);
}

void benchmark_m5(void)
{
    uint16_t i;
    uint16_t m;

    for (m = 0; m < BENCHMARK_MULTIPLIER; m++)
    {
#if defined(__PIC32MX__)
        Nop();
#endif
        for (i = 0; i < UINT16_MAX; i++)
        {
            benchmark_m5_recurse(32);
        }
    }
}

#endif
#if defined(BENCHMARK_M6) || defined(BENCHMARK_ALL)

// M6 - Character string manipulation test.
//
void benchmark_m6(void)
{
    uint16_t i;
    char s1[11] = "0123456789";
    char s2[11];
    uint16_t m;

    for (m = 0; m < BENCHMARK_MULTIPLIER; m++)
    {
#if defined(__PIC32MX__)
        Nop();
#endif
        for (i = 0; i < UINT16_MAX; i++)
        {
            strncpy(s2, s1, 10);
            s2[5] = '\0';
            strncat(s2, s1 + 5, 5);
        }
    }
}
#endif

// Main benchmarking function.
//
void benchmark(void)
{
#if defined(BENCHMARK_M1) || defined(BENCHMARK_ALL)
    benchmark_m1();
#endif
#if defined(BENCHMARK_M2) || defined(BENCHMARK_ALL)
    benchmark_m2();
#endif
#if defined(BENCHMARK_M3) || defined(BENCHMARK_ALL)
    benchmark_m3();
#endif
#if defined(BENCHMARK_M4) || defined(BENCHMARK_ALL)
    benchmark_m4();
#endif
#if defined(BENCHMARK_M5) || defined(BENCHMARK_ALL)
    benchmark_m5();
#endif
#if defined(BENCHMARK_M6) || defined(BENCHMARK_ALL)
    benchmark_m6();
#endif
}

#endif	/* _BENCHMARK_H */

/**********************************************************
 End of File
 *********************************************************/

        
      

It is worth remembering that this benchmark written in the C programming language will evaluate both the C compiler as well as the PIC device itself. Since the three proprietary compilers used here XC8, XC16 and XC32 are all made by Microchip, we trust there to be a degree of standardisation, optimisation and consistency across compilers for the purpose of machine code generation. The microcontroller clocks in all cases were set to their maximum frequency for the best processing speed possible.

MPLAB Code Configurator (MCC) or MPLAB Harmony (MC) is used from within Microchip’s integrated development environment (MPLAB) to generate boilerplate code for creating an empty project, and for simplifying code to control the microcontroller itself and its peripherals. A few lines of code are added to main.c for including the benchmark library benchmark.h, for defining which benchmark test to run (M1-M6), and finally to generate an output a pulse from a GPIO pin (RA2). It is this output pulse whose width is measured to determine the benchmark execution time.


Benchmark Wiring

For each PIC a simple test circuit was built as shown in the schematic below. A PICkit4 was connected and used to both program the PIC and to provide power. The output from RA2 was connected to the Universal Counter Timer.

Test Circuit Schematic


Schematic

The Universal Counter Timer was configured to start timing on the rising edge of the pulse and to stop timing on the falling edge of the pulse. The final count was displayed in milliseconds, and automatically reset after 2 seconds. Since the benchmark test is repeated in a code loop every 3 seconds, the benchmark timing result could be checked for repeatability.

Universal Counter Timer
Counter Timer

Since the Microchip PICDEM LAB2 Board supports prototyping for various low end PICs, it made sense to use this board as a basis for wiring the PIC10, PIC12, PIC16 and PIC18 devices.

Test using PICDEM LAB2 Board
PICDEM LAB2 Board

Bare breadboard was used as a basis for wiring the high end dsPIC, PIC24 and PIC32 devices.

Test using Breadboard
Measuring on Breadboard


Benchmark Results

Each benchmark test was performed on each device representing a particular PIC series, with a few exceptions due to the following reasons:

  • Recursion (M5) is not supported by the PIC10, PIC12, PIC16 and PIC18 series, since these microcontrollers have a small hardware stack which is impractical for supporting recursive functions.
  • And floating-point maths (M2) is not supported by the PIC10 and PIC12 series, as there is insufficient program memory to include the floating-point routines.

Displayed in Tabular Form

Benchmark test results for each PIC representing a particular series are presented in the table below. The measured benchmark test times are quoted in milliseconds (ms).

Series Device M1 (ms) M2 (ms) M3 (ms) M4 (ms) M5 (ms) M6 (ms) Clock (MHz) Compiler
PIC10 10F322 12.746 - 8.865 9.388 - 10.371 16.0 XC8
PIC12 12F1572 5.326 - 4.143 4.012 - 5.114 32.0 XC8
PIC16 16F18326 5.321 5.984 4.119 3.989 - 5.081 32.0 XC8
PIC18 18F14K50 3.580 2.573 4.079 3.288 - 5.326 32.0 XC8
PIC24 24FJ128GB202 2.132 0.223 1.050 2.264 2.046 5.326 32.0 XC16
dsPIC 33EP256GP502 0.939 0.099 0.383 0.854 1.132 0.535 84.8 XC16
PIC32 32MX150F128B 0.005 0.007 0.026 0.052 0.590 0.245 80.0 XC32

Displayed as a Bar Chart

The graph below shows the relative performance of each PIC series as compared to the PIC32, which is given a ranking of 1 for all the benchmark tests. By default the graph uses Logarithmic Y-Axis scaling to better fit all the results, but differences will appear less obvious. Select Linear Y-Axis scaling to noticeably illustrate the performance variation between each PIC series. The Normalize Clock checkbox adjusts the results to assume each PIC series is tested at the same clock frequency.


Normalize Clock


Conclusion

As expected there is an incremental improvement in processing speed as the PIC series ascend, most strikingly with the fixed-point maths results, and least apparent with the character string manipulation results. Some lower end PICs are unsuitable for performing floating-point operations or for computing recursive functions. The PIC32 microcontroller exhibits a marked processing speed improvement in all benchmark tests mainly owing to its unique optimised MK4 MIPS 32-bit core, pipelined architecture and higher clock speed.

This benchmark test of course is not an absolute definitive comparison for contrasting the performance of each PIC series, but serves as a useful assessment. Microchip produces many devices in each series with various internal differences, and regularly releases devices with enhanced processing cores. The above benchmark code can of course be used to measure the computing performance of any future devices that may become available.

Microchip has to be commended for continuing to produce many PIC devices in DIL packaging, enabling the ease of benchmark testing as conducted here for the purpose of this article.