Skip to content

Latest commit

 

History

423 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Epic HAL logo: a chip-temple inside a laurel wreath

Epic HAL

Built down to what the datasheet requires.

License: MIT Toolchain: epic-cc Toolchain: MPLAB XC8 Release ci

A register-level HAL and a shelf of drop-in modules for 8-bit PIC microcontrollers. Download the bundle for your family, open the reference MPLAB X project, and you are building against a datasheet-faithful driver layer with one API across three PIC families, plus the things firmware always needs: a scheduler, a 1 ms timebase, UART and bit-banged serial, Modbus, PID, fixed-point math, and more.

Getting started (one command)

curl -fsSL https://github.com/apojomovsky/epic-hal/releases/latest/download/install.sh | sh -s -- 16F877A

Pass any supported part; the installer picks the family for you, downloads the right bundle, verifies its checksum, and scaffolds a project in your current directory. It leaves you with:

  • third_party/epic-hal/: the vendored library, pinned to the version you got,
  • myapp.X: a ready MPLAB X project for the part and modules you picked,
  • Makefile and main.c: a working blink.

Then either build it or open it:

make

make builds with epic-cc (build/myapp.hex, intermediates stay in build/, .gitignore ignores it; make clean removes it) with no Microchip download, no device pack, and no .X required. Or open myapp.X in MPLAB X or the MPLAB extension for VS Code and Build there.

That needs two things installed, and the one-liner reports exactly which are missing (with the commands to fix them) after scaffolding:

  1. epic-cc (bundled clang 20.1.8, no /opt/microchip step): https://github.com/apojomovsky/epic-cc/releases or build from source:

    cargo install --git https://github.com/apojomovsky/epic-cc epic-cc
    

    Add its bin/ to PATH.

  2. python3 (stdlib only, for the scaffolder). The installer checks for it and tells you how to finish by hand if it is missing.

With MPLAB XC8 (alternate toolchain)

Scaffold with XC8 instead (still fully supported):

curl -fsSL https://github.com/apojomovsky/epic-hal/releases/latest/download/install.sh | sh -s -- 16F877A --with-xc8
make TOOLCHAIN=xc8

That needs XC8 plus its device pack:

  1. MPLAB XC8 (the free tier is enough): https://www.microchip.com/en-us/tools-resources/develop/mplab-xc-compilers. Add its bin/ to PATH:

    export PATH=$PATH:/opt/microchip/xc8/v4.00/bin
    

    (add that line to ~/.bashrc to keep it).

  2. The device pack for your family (XC8 needs it; it ships with MPLAB X or downloads separately from Microchip's pack CDN). The packs are official Microchip downloads:

    Family Pack Download
    PIC16F87XA Microchip.PIC16Fxxx_DFP https://packs.download.microchip.com/Microchip.PIC16Fxxx_DFP.1.8.167.atpack
    PIC18Fxx5x Microchip.PIC18Fxxxx_DFP https://packs.download.microchip.com/Microchip.PIC18Fxxxx_DFP.1.7.171.atpack
    PIC16F193X Microchip.PIC12-16F1xxx_DFP https://packs.download.microchip.com/Microchip.PIC12-16F1xxx_DFP.1.9.258.atpack

    Install it next to XC8 (the exact version the bundle was built against is pinned in the bundle's examples/epic-hal-demo.X project):

    mkdir -p /opt/microchip/xc8/v4.00/pic/packs
    unzip ~/Downloads/Microchip.PIC16Fxxx_DFP.1.8.167.atpack \
      -d /opt/microchip/xc8/v4.00/pic/packs/Microchip.PIC16Fxxx_DFP
    

    (Or use MPLAB X's Tools > Packs manager, which does this for you.)

Pin a release with ... | sh -s -- 16F877A v0.1.0, choose your modules with --modules (e.g. serial,tick; the default is tick, which keeps the blink build clean and inside the PIC16 hardware stack), and the project name with --name. Family slugs still work (pic16f87xa, pic18fxx5x, pic16f193x); ... | sh -s -- --list shows them. The installer refuses to clobber an existing third_party/epic-hal unless you pass --force, and EPIC_HAL_DIR changes where it lands.

Prefer to inspect before running? Download the script, read it, then run it:

curl -fsSL -o install.sh https://github.com/apojomovsky/epic-hal/releases/latest/download/install.sh
less install.sh && sh install.sh pic16f87xa

What you get

  • One API, three families. The same names and signatures on PIC16F87XA, PIC18F2455, and PIC16F193X. Each family HAL implements the contract over its own registers, every bit cited to Microchip's datasheet. Code written against one builds against the others unchanged.
  • No framework tax. Plain C99, static storage, no RTOS, no dynamic allocation, no C++. A module is a folder of .c files you can read.
  • Logic proven before silicon. Every module also builds and runs as a host program, and CI cross-compiles everything for real parts and runs it under MPLAB SIM, checking actual registers and UART output.
  • One command to a .hex. curl ... | sh -s -- <family> downloads, verifies, and scaffolds a project; build with make or MPLAB X.

Not using the one-liner?

1. Download a bundle and run epic-hal init

Bundles live on the Releases page:

Bundle Parts inside
epic-hal-pic16f87xa-<version>.tar.gz 16F873A / 874A / 876A / 877A
epic-hal-pic18fxx5x-<version>.tar.gz 18F2455 / 2550 / 4455 / 4550
epic-hal-pic16f193x-<version>.tar.gz 16F1933 / 1934 / 1936 / 1937 / 1938 / 1939

The <version> is the release tag (e.g. v0.1.0); the badge above always shows the latest one.

Download and unpack one, then, with the CLI installed globally:

pipx install git+https://github.com/apojomovsky/epic-hal
epic-hal init --bundle /path/to/unpacked/bundle

Answer family, part, and modules. It writes main.c, a filled Makefile, and a ready MPLAB X .X in your current directory for your exact part + module subset. Open myapp.X in MPLAB X (or the MPLAB extension for VS Code) and Build, or make.

Advanced: without the scaffolder

Prefer to wire a project by hand, or add Epic HAL to one you already have? These two paths skip the scaffolder.

Open the reference project in MPLAB X

Unpack the bundle, then open examples/epic-hal-demo.X (File > Open Project). Pick your exact part under Project Properties, and Build. It produces a .hex you can program with MPLAB IPE or any PICkit.

New to MPLAB X?

You need MPLAB X IDE and the MPLAB XC8 compiler, both free from Microchip (the free XC8 tier is enough). The reference project is pre-wired: sources, include paths, and configuration words are already set. Selecting your part under Project Properties is the only manual step.

Or skip the IDE: a six-line Makefile

Just epic-cc and make, no MPLAB X, no Microchip download, no license:

EPIC_HAL_DIR := third_party/epic-hal
EPIC_HAL_MCU := 16F877A
EPIC_HAL_MODULES := serial tick
include $(EPIC_HAL_DIR)/epic-hal.mk

SRCS := main.c $(EPIC_HAL_SRCS)
CFLAGS += $(EPIC_HAL_CFLAGS)

app.hex: $(SRCS)
	epic-cc --device p16f877a $(CFLAGS) $^ -o $@

XC8 alternate: xc8-cc $(CFLAGS) $^ -o $@ -ginhx32 with TOOLCHAIN=xc8 (and its device pack, see above).

Run make, program the result. Dependencies resolve automatically (modbus pulls in serial and tick), and asking for a module on a part it does not fit fails immediately with the reason instead of a wall of XC8 linker errors. Each bundle's SUPPORT.md has the full per-part table.

Adding Epic HAL to an existing MPLAB X project instead? The bundle's MPLABX.md walks through it.

What the API feels like

Four programs, each complete. The same source builds against any of the three families: swap the include path at build time, nothing else changes.

Blink a LED on a 1 ms timebase

#include <xc.h>
#include "epic_tick.h"
#include "peripherals/pic16f87xa_gpio.h"

#pragma config FOSC = HS
#pragma config WDTE = OFF
#pragma config PWRTE = ON
#pragma config BOREN = ON
#pragma config LVP = OFF

int main(void)
{
    EPIC_GPIO_Init(GPIOB, GPIO_PIN_0, GPIO_MODE_OUTPUT);
    epic_tick_init(FOSC_HZ);            /* FOSC_HZ comes from the build */

    uint32_t last = epic_tick_get();
    for (;;) {
        if (epic_tick_get() - last >= 500u) {
            last = epic_tick_get();
            EPIC_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
        }
    }
}

epic_tick_init sets up a Timer2 auto-reload interrupt that keeps a millisecond counter; epic_tick_get reads it. This is the reference project's main.c in full, the smallest thing that proves your toolchain is wired up.

Echo bytes over UART

#include <xc.h>
#include "epic_serial.h"

int main(void)
{
    epic_serial_init(FOSC_HZ, 115200u);

    uint8_t buf[16];
    for (;;) {
        int n = epic_serial_read(buf, sizeof buf);
        if (n > 0) {
            epic_serial_write(buf, n);
        }
    }
}

epic_serial is interrupt-driven with a ring buffer; it also retargets printf through the same pipe. Everything the code above calls is plain C functions, no IDE glue.

Run two tasks on a cooperative scheduler

#include <xc.h>
#include "epic_hal.h"
#include "epic_taskmgr.h"

static void toggle(void *arg)
{
    EPIC_GPIO_TogglePin(GPIOB, (uint16_t)(uintptr_t)arg);
}

int main(void)
{
    EPIC_GPIO_Init(GPIOB, GPIO_PIN_0 | GPIO_PIN_1, GPIO_MODE_OUTPUT);
    epic_taskmgr_init();

    epic_taskmgr_spawn(toggle, (void *)(uintptr_t)GPIO_PIN_0, 100u, 0u);
    epic_taskmgr_spawn(toggle, (void *)(uintptr_t)GPIO_PIN_1, 300u, 0u);

    epic_taskmgr_attach_timer0(61u, TIMER0_PRESCALER_1_256); /* ~10 ms tick */
    EPIC_IRQ_Restore(1);                                     /* arm IRQs    */
    epic_taskmgr_run();                                      /* never returns */
}

epic_taskmgr is a priority-ordered, race-free cooperative scheduler: periodic and one-shot tasks, EPIC_TASKMGR_MAX_TASKS fixed slots, no per-task stack. The 10-line core of example_taskmgr.

Oversample and average an ADC channel

#include <xc.h>
#include "peripherals/pic16f87xa_adc.h"
#include "epic_adcfilter.h"

static uint16_t read_ch3(void *ctx)
{
    (void)ctx;
    EPIC_ADC_SelectChannel(ADC_CHANNEL_AN3);
    EPIC_ADC_Start();
    while (EPIC_ADC_IsConversionInProgress()) { }
    return EPIC_ADC_Read();
}

int main(void)
{
    ADC_HandleTypeDef adc = ADC_HANDLE_DEFAULT;
    EPIC_ADC_Init(&adc);

    static uint16_t buf[8];
    epic_adcfilter_avg_t avg;
    epic_adcfilter_avg_init(&avg, buf, 8u);

    for (;;) {
        uint16_t v = epic_adcfilter_avg_push(
            &avg, epic_adcfilter_oversample(read_ch3, NULL, 1u));
        (void)v;
    }
}

epic_adcfilter decimates the raw samples and keeps an O(1) moving average, both over a callback you provide. The HAL layer is just select, start, poll, read.

What you can build

Modules are grouped by what they do for you. Everything below is family-agnostic unless noted; each ships its own README, and the HALs carry a datasheet-cited register reference.

Timing & control

Module What it does
epic-tick 1 ms timebase on a Timer2 auto-reload ISR (HAL_GetTick equivalent).
epic-taskmgr Cooperative scheduler: periodic and one-shot tasks, priority-ordered, race-free.
epic-debounce Instantiable digital-input debouncer on the real timebase.
epic-encoder Interrupt-driven x4 quadrature decoder, instantiable.
epic-fsm Table-driven finite state machine, the whole machine is one static const table.
epic-pid Fixed-point (Q8.8) PID with anti-windup, derivative-on-measurement, bumpless auto/manual.
epic-adcfilter ADC oversample-and-decimate plus an O(1) moving-average filter.

Communication

Module What it does
epic-serial Interrupt-driven ring-buffered UART + printf retarget.
epic-swuart Bit-banged software UART on CCP capture/compare, two channels on PIC16F193X.
epic-bus I2C/SPI register-access idiom on top of MSSP/SSP.
epic-modbus Modbus RTU slave: core function codes, T3.5 framing, CRC-16, RS-485 driver-enable.
epic-console Line-based serial command dispatcher over epic-serial.
epic-usb USB CDC-ACM virtual serial port. PIC18Fxx5x only.

Storage

Module What it does
epic-settings EEPROM-backed settings blobs with CRC-16 validation and first-boot defaults.
epic-sdcard SD/MMC over SPI block storage. PIC18Fxx5x only (RAM constraint).

Math

Module What it does
epic-math Fixed-point math: multiply, divide, BCD, sqrt, numerical diff/integration, RNGs, with PIC16/PIC18 inline-asm backends behind one API.

Peripherals

Module What it does
epic-lcd HD44780-compatible character LCD: 4-bit GPIO, 8-bit GPIO, or SPI via 74HC595.

The full catalog, including the three HALs and epic-common, lives in the table below. The higher-level modules build against the two mature families today; wiring them to PIC16F193X is in progress now that its peripheral coverage is complete.

Supported devices

Family Parts HAL Peripheral coverage
PIC16F87XA 16F873A / 874A / 876A / 877A pic16f87xa-hal GPIO, Timers 0-2, CCP, MSSP, EUSART, ADC, Comparator, EEPROM, PSP, WDT
PIC18F2455 18F2455 / 2550 / 4455 / 4550 pic18fxx5x-hal GPIO, Timers 0-3, ECCP1/CCP2, MSSP, EUSART, Comparator, EEPROM, ADC, SPP
PIC16F193X 16F1933 / 1934 / 1936 / 1937 / 1938 / 1939 pic16f193x-hal GPIO, Timers 0/1/2/4/6, CCP1-5, EUSART, MSSP, ADC, Comparator, EEPROM, DAC, FVR, SR latch, CPS, LCD

Documentation

Contributing

Bug reports, datasheet-cited corrections, and new devices are welcome. The repo is agent-friendly and plan-first: non-trivial work starts with a short-lived design doc (deleted on completion), and everything is verified by the CI pipeline (host tests, real XC8 cross-compiles, MPLAB SIM runs). See AGENTS.md for the conventions and DEVELOPMENT.md for the toolchain and build workflow.

License

MIT, see LICENSE. The Microchip datasheets and application notes this library is built from are Microchip's property and are not vendored in this repository; links to Microchip's own hosted copies are listed in each module's MANUAL.md.

About

A register-level HAL and a shelf of drop-in modules for 8-bit PIC microcontrollers

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages