A small C module for the dsPIC33CK and a browser user interface on the PC. The controller samples any RAM variables (8, 16 or 32 bit) at a fixed rate and sends them in binary over a UART to the PC. The CPU is never halted, so a motor control application keeps running undisturbed.
Two modes:
- Stream: every sample goes to the PC immediately and appears as a running curve. The sample rate is limited by the baud rate; the GUI shows whether the rate fits.
- Capture: the samples go into a RAM buffer in the controller first (4 KB by default). A trigger condition (rising or falling edge of a channel through a threshold, with pretrigger) starts the recording. When the buffer is full, it is transferred. Any sample rate is possible, up to what the interrupt load allows.
| File | Purpose |
|---|---|
firmware/scope.c, scope.h |
module for the controller (register level, no MCC) |
firmware/example_main.c |
minimal example with clock setup and demo variables |
gui/scope_gui.py |
PC user interface (NiceGUI in the browser) |
start_gui.bat |
starts the user interface, sets up Python on the first start |
start_sim.bat |
builds test.X for the simulator and starts the user interface |
USER_GUIDE.md |
user guide |
SIMULATOR.md |
guide for the simulation without hardware |
TROUBLESHOOTING.md |
troubleshooting guide: what to check when it does not work |
test.X |
MPLAB X test project, configurations default (PICkit 5) and sim (simulator) |
tools/setup_env.bat |
creates the Python environment .venv on the first start |
tools/mdb_serial.py |
connects the GUI to the firmware in the MPLAB X simulator |
tools/doctor.py |
checks the PC and the project settings, writes doctor_report.txt |
tools/hw_check.py |
checks mini-scope on the board (ping, clock, rates, capture, reset), writes hw_report.txt |
tools/make_pdfs.py |
builds PDF versions of the three guides (needs pandoc and Edge) |
CLAUDE.md |
notes for Claude Code, for help with getting mini-scope running on your board |
tests/ |
PC model: scope.c built as a DLL, plus a protocol test |
LICENSE.txt |
license |
- Add
scope.candscope.hto the MPLAB X project. - Call
scope_init();inmain()after the clock and PPS setup.scope_init()configures PPS, UART, timer and interrupts and sets the global interrupt enable (GIE). - Check the defaults in
scope.h. Change them there or with-Din the project properties:
| Setting | Default | Meaning |
|---|---|---|
SCOPE_FP_HZ |
32000000 | peripheral clock FP = FOSC/2 |
SCOPE_BAUD |
115200 | baud rate, +0.6 % at 32 MHz. The stream carries about 11 kB/s; a full capture buffer takes about 0.4 s to transfer. |
SCOPE_UART |
1 | which UART: 1, 2 or 3, see below |
SCOPE_TIMER |
SCOPE_T1 |
sample clock: SCOPE_T1, SCOPE_CCP1 … SCOPE_CCP4 or SCOPE_NONE, see below |
SCOPE_TIMER_IPL / SCOPE_UART_IPL |
2 / 1 | interrupt priorities; sampling must be higher than the UART |
SCOPE_MAX_CH |
16 | maximum number of channels |
SCOPE_CAPBUF |
4096 | capture buffer in bytes, 64 to 65535, in practice limited by RAM. The GUI reads the value from the firmware before every start, so it only lives here; nothing to change in the GUI, even after reprogramming. |
SCOPE_TXBUF |
512 | stream transmit buffer in bytes, power of two |
SCOPE_TX_RP / SCOPE_RX_RP |
37 / 38 | UART pins as RP numbers, default RB5/RB6 = ICSP header, see below |
Selecting the UART and the sample clock: both are at the top of
scope.h. Only the selected modules are used; everything else stays free for
the application:
#define SCOPE_UART 1 /* 1, 2 or 3 */
#define SCOPE_TIMER SCOPE_T1 /* SCOPE_T1, SCOPE_CCP1 ... SCOPE_CCP4 or SCOPE_NONE */SCOPE_TIMER |
Sample clock | Interrupt used |
|---|---|---|
SCOPE_T1 |
Timer1 | _T1Interrupt |
SCOPE_CCP1 … SCOPE_CCP4 |
SCCP1 … SCCP4 as 16-bit timer | _CCT1Interrupt … _CCT4Interrupt |
SCOPE_NONE |
no timer: call scope_sample() yourself, for example at the end of the control ISR. Sampling is then synchronous to the PWM, and the sample period in the GUI must match the ISR period. |
– |
The selected UART uses _UxTXInterrupt and _UxRXInterrupt. All three UARTs
can be mapped to any remappable pin by PPS; scope.h sets the codes (U1TX = 1,
U2TX = 3, U3TX = 27) automatically. An invalid value stops the build with a
clear error message.
RAM: the dsPIC33CK64MC103 has only 8 KB of RAM, so all buffers can be
sized in scope.h. The library needs
RAM =
SCOPE_CAPBUF+SCOPE_TXBUF+ 7 ·SCOPE_MAX_CH+ 56 bytes
plus 4 · SCOPE_MAX_CH bytes of stack while sampling in stream mode.
Measured with XC-DSC 3.30, -O1:
SCOPE_CAPBUF |
SCOPE_TXBUF |
SCOPE_MAX_CH |
RAM | Capture depth with one 16-bit channel |
|---|---|---|---|---|
| 4096 | 512 | 16 | 4776 B (default) | 2048 samples |
| 2048 | 512 | 16 | 2728 B | 1024 samples |
| 1024 | 256 | 8 | 1392 B | 512 samples |
| 512 | 128 | 4 | 724 B | 256 samples |
| 256 | 64 | 2 | 390 B | 128 samples |
SCOPE_CAPBUFsets the length of a capture. The depth in samples isSCOPE_CAPBUFdivided by the bytes of all channels. The GUI picks it up automatically.SCOPE_TXBUFbuffers the stream. It must be a power of two and hold at least one frame (2 + 4 ·SCOPE_MAX_CHbytes). A larger buffer only bridges short peaks; the average data rate is limited by the baud rate.SCOPE_MAX_CHlimits the number of channels; each channel costs 7 bytes.
scope.h rejects unsuitable values at compile time. Whether the total still
fits into RAM is only checked by the linker; leave room for the stack and your
own variables. The library needs about 2 KB of flash.
Pins: by default TX is on RP37/RB5 (PGD3) and RX on RP38/RB6 (PGC3), that is on the ICSP header, so no rework is needed:
- Program a release build with the PICkit, then disconnect the PICkit.
- Connect a 3.3 V USB-UART adapter to the header: pin 4 (PGD) to the adapter's RX, pin 5 (PGC) to the adapter's TX, pin 3 to GND. Do not connect pin 2 (3V3).
- Debugging and mini-scope cannot run at the same time. In a debug build,
leave out
scope_init(), for example with#ifndef __DEBUG. - Programming always remains possible, because the PICkit enters via MCLR.
Other pins: set the RP numbers in scope.h. RBn is RP(32 + n), RCn is
RP(48 + n); RP32 … RP53 are possible on the dsPIC33CK64MC10x.
#define SCOPE_TX_RP 39 /* RB7 */
#define SCOPE_RX_RP 40 /* RB8 */scope_init() then sets the PPS registers, the pin directions (TRIS) and
digital mode (ANSEL) for both pins; the last one matters, because a pin left
in analog mode reads 0 and the UART receives nothing. Invalid pin numbers, or
TX and RX on the same pin, stop the build with an error message. For a
completely custom setup, define SCOPE_PINS_INIT() yourself before
#include "scope.h".
Pin table for the dsPIC33CK64MC103 (36 pins):
| Port pin | SCOPE_TX_RP / SCOPE_RX_RP |
Package pin (36-pin) | Other functions on the pin | Note |
|---|---|---|---|---|
| RB0 | 32 | 17 | OSCI, CLKI, AN5 | not with a crystal or external clock |
| RB1 | 33 | 18 | OSCO, CLKO, AN6 | not with a crystal |
| RB2 | 34 | 19 | AN1, AN7, ANA0, CMP1D, OA2OUT, INT0 | analog pin, set to digital automatically |
| RB3 | 35 | 20 | PGD2, AN8, OA2IN- | ICSP channel 2 |
| RB4 | 36 | 21 | PGC2, OA2IN+ | ICSP channel 2 |
| RB5 | 37 | 24 | PGD3 | default TX: ICSP channel 3 |
| RB6 | 38 | 25 | PGC3 | default RX: ICSP channel 3 |
| RB7 | 39 | 26 | TDO, AN2 | JTAG must be off (JTAGEN = OFF) |
| RB8 | 40 | 27 | PGD1, AN10, SCL1 | ICSP channel 1, I²C1 |
| RB9 | 41 | 28 | PGC1, AN11, SDA1 | ICSP channel 1, I²C1 |
| RB10 | 42 | 33 | TMS, PWM3H | PWM output, JTAG |
| RB11 | 43 | 34 | TCK, PWM3L | PWM output, JTAG |
| RB12 | 44 | 35 | TDI, PWM2H | PWM output, JTAG |
| RB13 | 45 | 36 | PWM2L | PWM output |
| RB14 | 46 | 1 | PWM1H | PWM output |
| RB15 | 47 | 2 | PWM1L | PWM output |
| RC0 | 48 | 4 | AN12 | analog pin, set to digital automatically |
| RC1 | 49 | 12 | AN13, CMP1B, OA3IN-, ISRC0 | analog pin, set to digital automatically |
| RC2 | 50 | 13 | AN14, OA3IN+, ISRC1 | analog pin, set to digital automatically |
| RC3 | 51 | 16 | AN15, IBIAS2 | analog pin, set to digital automatically |
| RC4 | 52 | 29 | – | free digital pin |
| RC5 | 53 | 30 | – | free digital pin |
Port A (RA0 … RA4) is not remappable and cannot be used. Source: the device
description in the dsPIC33CK-MC_DFP pack (DSPIC33CK64MC103.PIC); the
package pin numbers apply to the 36-pin dsPIC33CK64MC103.
Notes:
- If the debugger uses the same ICSP channel (
ICSconfiguration bit), do not debug while mini-scope runs on these pins. - On motor control boards the PWM pins are usually taken by the power stage.
Adapter: any 3.3 V USB-UART adapter for 115200 baud will do. On a motor board it should be galvanically isolated, or the laptop should run on battery, so that there is no ground loop.
start_gui.bat
The first start finds Python 3.9 or later, creates the environment .venv in
the project folder and installs the packages from gui\requirements.txt. This
takes about a minute; later starts take a few seconds. Set MINISCOPE_VENV
to put the environment somewhere else. The browser opens at
http://localhost:8080.
- Select the port and click Connect. The status line shows whether the target responds; the button then reads Disconnect.
- Click Browse … and pick the ELF file, or enter its path and click
Load. The ELF is at
<project>.X\dist\default\production\<project>.X.production.elf. Then pick the variables from a list with address and size; typing filters the list, address and width are filled in automatically. Without an ELF, enter the address by hand, for example from the.mapfile. - Set up channels. The type is s8/u8/s16/u16/s32/u32/f32. Factor converts to physical units, for example Q15 → A, or scales signals of different size.
- Select the mode, enter the sample period in µs and click Start. Capture mode adds trigger channel (number in the channel list, from 0), edge, threshold as a raw value and pretrigger. Auto re-arm re-arms after every capture. In stream mode, an orange warning appears when the sample period is too short for the UART, with the shortest period that still fits.
- Write variable (next to the channels): one row per variable, set up
like a channel, with a value field. Enter or the arrow writes the value
into the controller at once, also during a measurement. The demo variables
sine_gain(sine amplitude, Q15) andramp_step(slope of the ramp) show the effect. - CSV saves the data shown (comma separated, decimal point). PNG saves a
1920 × 1080 picture (16:9, a PowerPoint slide) with the zoomed chart, the
cursors, the measurement table and the settings; the text in Note for
the PNG goes into its header. Both go into the folder of
the settings file in use (normally the project folder), named
mini-scope_<date>_<time>.csv/.png. - Save (next to the title) stores all settings in
mini-scope.jsonin the project folder, which is loaded at every start. Save as ... and Load from ... use a file of your choice.
Zoom in the chart with the mouse wheel or the slider. Clicking a name in the legend hides that channel. Autoscale sets the factors so that all channels use the same range (the largest channel keeps factor 1, the others get a round factor 1/2/5 × 10ⁿ); factors apply at once, also during a measurement. Below the chart, a table shows Min, Max, Pk-Pk, Mean, RMS, frequency, period and duty cycle of every channel. Time cursors (T1, T2) and value cursors (Y1, Y2), also both at once, are set by clicking into the chart (with both on: Shift+click for the value cursors); with two time cursors the table also shows the values at the cursors and measures only between them.
test.X is an MPLAB X project for the dsPIC33CK64MC103 with
example_main.c and scope.c. It has two configurations:
- default: for the PICkit 5, with target power from the PICkit switched off.
- sim: for the MPLAB X simulator, with the macro
__MPLAB_DEBUGGER_SIMULATOR.
The demo variables are sine, ramp, counter and toggle, plus
sine_gain and ramp_step to try out writing from the GUI.
There are two ways to run without hardware:
1. MPLAB X simulator (real firmware, slow): the firmware compiled for the
dsPIC33CK runs in the simulator, driven by MDB, the command line debugger of
MPLAB X. The simulator's UART cannot receive, so tools/mdb_serial.py writes
the commands into a mailbox in example_main.c via MDB. The answers come from
the simulator's UART output file.
start_sim.bat
Select port "MDB" and click Connect. Starting takes 40–60 s while MDB
(Java) and the simulator come up; a progress bar shows the phase and the time
so far. Then click Load, the ELF path is already filled in. The simulator
runs much slower than real time and data arrives a few seconds late, which is
good enough for functional tests. Details are in SIMULATOR.md.
2. PC model (fast, needs gcc, for example MinGW): scope.c is built with
modelled registers as a DLL. Data arrives in real time.
gcc -O2 -shared -Itests/mock -Ifirmware -o tests/sim.dll tests/mock/sim.c firmware/scope.c
start_gui.bat --sim (port "SIM", leave the ELF field empty, Load)
.venv\Scripts\python tests\test_protocol.py (34 automatic checks)
The paths to MPLAB X v6.35, XC-DSC v3.30 and the DFP are at the top of
tools/mdb_serial.py and in start_sim.bat.
scope_init() brings everything the library needs; nothing has to be set up
in your own code.
| What | Setting | Source |
|---|---|---|
| PPS | TX pin ← UxTX, UxRX ← RX pin (default RP37/RB5 and RP38/RB6); TX output, RX input, both digital | data sheet DS70005399D p. 113, 117 |
| UART | asynchronous 8N1, clock FP (BCLKSEL = 00), BRGH = 1, UxBRG = 68 → 115,942 baud (+0.6 %) |
p. 272–279 |
| Interrupts | UART RX/TX priority 1, sample clock priority 2, GIE = 1 |
– |
| Timer1 | clock FP (TCS = 0), prescaler and PR1 from the sample period sent by the GUI; runs only during a measurement |
p. 329–331 |
| SCCPx | 16-bit timer (MOD = 0), clock FP (CLKSEL = 0), prescaler and CCPxPRL from the sample period, interrupt CCTxIF on period match |
p. 340–342 |
Checked in the disassembly of the linked ELF (xc-dsc-objdump) against the
data sheet and against MCC-generated code for the dsPIC33CK
(microchip-pic-avr-examples/dspic33ck-lvmc-x2cscope-blinky for the UART,
mclv2-mchv2-dspic33ck256mp508-an1160 for the SCCP timer). All 18
combinations of UART and sample clock compile and link without warnings.
UART1 with Timer1 and UART2 with SCCP1 have run in the MPLAB X simulator.
Preconditions in the application:
- FP = FOSC/2 must match
SCOPE_FP_HZ, default 32 MHz. With a different clock the baud rate is wrong and no connection is made. - The selected UART, the selected timer and the two UART pins must not be
used by the application. The linker reports interrupts defined twice
(
multiple definition of _T1Interrupt); then select a free UART or timer inscope.h.
- Only addresses in data memory below 0x8000 are read. 16- and 32-bit channels need an even address; otherwise the target rejects the configuration instead of running into an address error trap.
- 32-bit values are read as two 16-bit accesses. If a higher-priority ISR writes in between, a single value can be torn.
- SFRs can be entered as well. Avoid registers with read side effects (for
example
UxRXREG). - Writing a variable sends no answer, because it may arrive in the middle of a stream; the GUI checks address and value range before sending. A 32-bit value is written in two 16-bit steps, so code that reads it at that moment can see a mix of old and new value. Writes to SFRs work, but take effect immediately on the hardware.
- The trigger compares raw integer values; an f32 channel cannot be the trigger.
- Very short sample periods (a few µs) load the CPU noticeably, because every
sample is an interrupt. In a motor control application, set
SCOPE_TIMER = SCOPE_NONEand callscope_sample()from the control ISR.
- Build: the firmware compiles without warnings with XC-DSC 3.30 for the
dsPIC33CK64MC103 (
-Wall -Wextra). - PC model: protocol, trigger, pretrigger, frame loss at too high a
stream rate, buffer sizes and the GUI are tested with the original
scope.c. - MPLAB X simulator: stream and capture with trigger run with the
compiled firmware from
test.X, through the GUI. - Hardware: the module has not yet run on real hardware.
Two points only showed up in the simulator:
GIEwas 0 after reset in the simulator (on silicon it resets to 1 according to the data sheet), soscope_init()sets it.- The TX interrupt was not raised again after the FIFO had been filled, so the timer also kicks the transmitter while a capture is being transferred.
Whether the hardware behaves the same in these two points is open; the firmware works either way.
Code, tests and documentation were developed with the help of an AI coding assistant (Claude by Anthropic). The register settings were checked against the data sheet DS70005399D and against MCC-generated code that runs on dsPIC33CK silicon, and the firmware was exercised in the MPLAB X simulator and in a PC model, as described above. It has not yet run on real hardware; please report any issue.
See LICENSE.txt. You may use this software exclusively with Microchip products.
