# FlashEEPROM Write on dsPIC33EV256GM102: resets the chip or stores the wrong data (NVMSRCADR never set)

**From:** John Wedlake, Tech Engineering Systems Ltd (john@wedlake.email)
**Flowcode licence:** 15266180
**Date:** October 2026

## Summary

On a dsPIC33EV256GM102, the **FlashEEPROM component `Write`** either:
- **resets the controller** (NVM Address Error soft trap, or IOPUWR when no trap handler is fitted), or
- **writes the wrong data with no error flag**, so a later `Read` returns values that were never written.

The erase and `Read` routines work correctly.

**Cause (confirmed on hardware).** `MX_FLASH_WriteInstructionRow` in `PIC16BIT_CAL_FLASH.c` (`MX_FLASH_TYPE 1`) starts a **row program (NVMOP 0010)** after loading 64 TBLWT latches. On this device the row program takes its data from **RAM at `NVMSRCADRH:NVMSRCADRL`**, and the latches are ignored. The CAL never sets `NVMSRCADR`, so:
- at its reset value (0), the fetch faults with **NAE + WRERR + URERR**;
- pointing at any RAM, the flash receives **that RAM's contents**.

A one-line-per-register fix is proposed below. A minimal test project, attached, isolates each step on the same hardware. The results are below.

## Environment (from the Flowcode build log)

| Item | Version |
|---|---|
| Flowcode | v11.0.2.17 |
| FCD | PIC16.33E.33EV256GM102, 2.0, 07/11/2022 |
| FlashEEPROM component | 4.0 (0x100028), 20/01/2023 |
| cal_flash component | 4.0, 20/01/2023 |
| PIC16BIT_CAL_FLASH.c | rev 1.0, GUID 0A78549D-93F3-48E9-8638-94AC2215E081, 08/08/2022 |
| Compiler | xc16 1.26 (as shipped with Flowcode), DFP dsPIC33EV-GM-00X-10X 1.5.388 |
| Clock | 8 MHz crystal, PLL to 80 MHz Fosc (40 MIPS) |

FlashEEPROM properties: FlashAddress 10240 (0x2800), BitsPerVal 16, BitsPerInst 24, PageSizeInst 512, RowSizeInst 64, MinWriteInst 2, RowsPerPage 8.

The generated header shows:
- `MX_FLASH_TYPE 1`
- `MX_FLASH_ROW_SIZE 64`
- `MX_FLASH_MIN_WRITE_SIZE 2`
- `const MX_UINT16 Placeholder[512] __attribute__((space(psv), address(10240)))`

## What the generated code does

`FlashEEPROM1::Write(Address, Data)`:
1. Reads 64 values into `MXEEDATA[64]`.
2. If the value changed, calls `MX_FLASH_ErasePage(10240)` (NVMCON 0x4003, correct).
3. Calls `MX_FLASH_WriteInstructionRow(10240, MXEEDATA)` once.

`MX_FLASH_WriteInstructionRow` (TYPE 1):
- `NVMCON = 0x4002` (row program);
- `TBLPAG = 0xFA`;
- 64 × `__builtin_tblwtl/tblwth(i*2, …)`;
- `__builtin_write_NVM()`.

On the dsPIC33EV:
- RTSP programming is double-word: NVMOP 0001, with **two** write latches at 0xFA0000/0xFA0002. `MX_FLASH_WriteInstruction` in the same file already does this correctly.
- Row programming (NVMOP 0010) takes its data from **RAM** pointed to by `NVMSRCADRL/H`, in the format set by `NVMCONbits.RPDF`. The device header has `NVMSRCADRL`, `NVMSRCADRH`, `RPDF` and `URERR`. The CAL leaves `NVMSRCADR` unset, so the row program sources whatever RAM it points at (reset value 0, the SFR area), or underruns.
- The flash has **ECC** (`INTCON4bits.ECCDBE`). A badly programmed row can raise a generic hard trap when it is read back.

## First result (5 October 2026, test rev 1)

The first test build called `FlashEEPROM1::Write(0, 1)` straight after boot. The controller went into a **continuous reset loop**. Each boot reported:

```
Last reset: RCON=4000 POR=0 BOR=0 EXTR=0 SWR=0 WDTO=0 TRAPR=0 IOPUWR=1 CM=0
  Raw 0x2800 = FFFFFF   (whole FlashEEPROM page erased; Placeholder had programmed it to 000000)
  Raw 0x2BFE = FFFFFF
  Raw 0x2C00 = 000000   (next page untouched)
```

So:
- The reset is an **IOPUWR reset** (illegal opcode, or an uninitialised W register used as an address pointer). It is not a trap, so trap handlers never see it.
- The page erase works and stays inside 0x2800–0x2BFF, which means the page is 512 instructions.
- The row write programs **nothing**: the row is still erased after the reset.
- The reset happens inside `Write`, after the erase.

Rev 2 records a stage marker in persistent RAM before each step of the row write (each latch load, then the NVM start). That pins down which instruction causes the IOPUWR.

## Second result (5 October 2026, test rev 2): the trap is an NVM Address Error

With trap handlers fitted, the row write is caught as a **generic soft trap, NAE (NVM Address Error)**. It reproduced on two runs:

```
T1 ref C double-word: erase 19937us NVMCON=4003 | prog 78us NVMCON=4001  PASS
T2 Ver34 store (double-word, next free slot): 83us NVMCON=4001 WRERR=0  PASS (x2)
T3: running a copy of Flowcode MX_FLASH_WriteInstructionRow ...
-- reset --
TRAP before this reset: 7 = Generic soft trap | INTCON1=0000 INTCON3=0100 INTCON4=0000 NVMCON=6102 TBLPAG=00FA
>>> RESET HAPPENED DURING TEST STAGE 33: NVM row program (NVMOP 0010) running
```

- `INTCON3 = 0x0100`: **NAE = 1**, an NVM Address Error soft trap.
- `NVMCON = 0x6102`: WREN = 1, **WRERR = 1, URERR = 1** (row programming data underrun), NVMOP = 0010 (row program).
- All 64 TBLWT latch loads complete without a fault (stage 32, 0–63). The trap fires as soon as `__builtin_write_NVM()` starts the row program (stage 33).

**Conclusion:** on the dsPIC33EV, NVMOP 0010 fetches the row data from RAM at `NVMSRCADRL/H`, which the CAL never sets. The fetch faults with an NVM address error and an underrun, the write is aborted, and the unhandled soft trap resets the chip. Rev 1 had no trap handler, so the reset showed as IOPUWR. The double-word method (NVMOP 0001), which `MX_FLASH_WriteInstruction` in the same file already uses, works with no errors.

Note: in rev 2 the T1/T3/T4 page address carried a flag bit from `__builtin_tbladdress` (0x1003000). NVMADRU is 8 bits, so the hardware used 0x003000, and T1 passed at that address. Rev 3 masks it and re-runs T3, T4 and T5, the component itself at 0x2800, to confirm.

## Third result (5 October 2026, test rev 3): the row data comes from RAM at NVMSRCADR

Rev 3 ran T4, a row program from a RAM buffer with `NVMSRCADR` set, **before** T3 and T5. This time T3 and T5 did **not** trap. They programmed **T4's RAM buffer** into flash:

```
T4 row from RAM (NVMSRCADR) @03000: row NVMCON=4002 WRERR=0 URERR=0 1423us, 64/64 words correct  PASS
T3 Flowcode row-write copy @03000: row NVMCON=4002 WRERR=0 URERR=0, 0/64 correct, first bad #0 read 0A5800 want 0A0400  FAIL
T5a 8-bit   A1: W=00000012 R=000A5801 | NVMCON=4002 op=ROW WRERR=0 URERR=0  FAIL
T5b 16-bit  A2: W=00001234 R=000A5802 ...                                   FAIL
T5 EPB Ver33 Job7 save A4: packed=A50001 read=0A5804 -> LH=4 RH=88          FAIL
Flash after T5: addresses 0..7 = 0A5800, 0A5801 ... 0A5807 (T4 test pattern 0x0A0000 | 0x58<<8 | i)
```

T4's pattern was `0x0A0000 | ((run 3 + 0x55) << 8) | i` = `0A58xx`. Every word written by the CAL row routine and by the FlashEEPROM component matches T4's RAM buffer. None of it is the data loaded through TBLWT.

**Conclusion (confirmed on hardware):**
1. On the dsPIC33EV, NVMOP 0010 (row program) takes its 64 instructions from **RAM at `NVMSRCADRH:NVMSRCADRL`**. The 64 TBLWT latch loads in `MX_FLASH_WriteInstructionRow` are ignored.
2. The CAL never sets `NVMSRCADR`. What happens depends on its previous value:
   - **0 (reset value):** the fetch hits the SFR area, giving an NVM Address Error soft trap (NAE) with WRERR and URERR. The application resets, or, with no trap handler, shows an IOPUWR reset.
   - **Any RAM address:** the write "succeeds" with no error flag, and flash gets **whatever is in that RAM**. `Read` then returns the wrong values. This is the "variable reports the wrong number after a FlashEEPROM write" symptom.
3. Row programming itself works when `NVMSRCADR` points at correctly formatted data (T4: 64/64 correct, 1.4 ms).

## Test project and results

The project is `dsPIC33EV_FlashEEPROM_Test.fcfx`. It uses the same chip, config and clock as our application, plus a trap logger and stage markers held in persistent RAM.

Output from UART1 at 115200 is pasted below. *(Fill in after running.)*

| Test | What it does | Result |
|---|---|---|
| T1 | Page erase + **double-word** program (NVMOP 0001) on 0x3000, plain C | PASS (erase 19.9 ms, program 78 µs) |
| T2 | Double-word "next free slot" store at 0x2800, our workaround | PASS ×2, slot advanced 2 → 3 → 4, survived resets |
| T3 | **Exact copy** of `MX_FLASH_WriteInstructionRow`, stage marker before each latch load and before `write_NVM` | **Soft trap NAE at `write_NVM`**, NVMCON 6102 (WRERR + URERR), ×2 |
| T4 | Row program with data from RAM (`NVMSRCADR`, RPDF = 0) | **PASS**: 64/64 correct, 1.4 ms, no errors |
| T5 | The FlashEEPROM component `Write` / `Read` | **FAIL**: writes the RAM at the stale `NVMSRCADR` (T4's buffer), or traps (NAE) when `NVMSRCADR = 0` |
| T6 | **Proposed fix**: `NVMSRCADR` = the CAL's own `MX_UINT32 Data[]` array, RPDF = 0 | (rev 4, to run) |
| Page erase limit | Is 0x2C00 still intact after a component Write? | PASS: the page is 512 instructions |

Boot report after any reset (trap type, INTCON1/3/4, NVMCON, TBLPAG, stage):

```
(paste here)
```

Full terminal log:

```
(paste here)
```

## Suggested fix for MX_FLASH_TYPE 1 (dsPIC33E / PIC24E)

**Option A, the smallest change (tested as T6 in rev 4).** `MXEEDATA` / `Data[]` is already an `MX_UINT32` array. In little-endian RAM each entry is the low 16 bits followed by the upper word, which is exactly the "uncompressed" row format (RPDF = 0). So set `NVMSRCADR` to the array and drop the TBLWT loop:

```c
void MX_FLASH_WriteInstructionRow (MX_UINT32 Address, MX_UINT32 *Data)
{
    NVMCON = MX_FLASH_PROG_ROW;            // 0x4002
    NVMCONbits.RPDF = 0;                   // uncompressed: 2 RAM words per instruction
    NVMSRCADRL = (MX_UINT16)Data;          // row data is read from RAM, not from the TBLWT latches
    NVMSRCADRH = 0;
    MxConv.AsLong = Address;
    NVMADR = MxConv.AsInt[0];
    NVMADRU = MxConv.AsInt[1];
    __builtin_disi(5);
    __builtin_write_NVM();
    while (NVMCONbits.WR == 1);
}
```

T6 also stores one full 32-bit value (0x12345678), which checks that the hardware ignores bits 31–24 and stores 0x345678.

**Option B, double-word programming,** using the routine already in the CAL (proven by T1 and T2):

```c
void MX_FLASH_WriteInstructionRow (MX_UINT32 Address, MX_UINT32 *Data)
{
    MX_UINT16 i;
    for (i = 0; i < MX_FLASH_ROW_SIZE; i += MX_FLASH_MIN_WRITE_SIZE)
        MX_FLASH_WriteInstruction(Address + ((MX_UINT32)i << 1), &Data[i]);
}
```

Option A is about 1.4 ms per row. Option B is 32 double-word writes at about 80 µs each, roughly 2.5 ms.

Please also check the other dsPIC33E / PIC24E parts that use `MX_FLASH_TYPE 1`. Those with RAM-sourced row programming (NVMSRCADR) will have the same fault.

## Other observations in PIC16BIT_CAL_FLASH.c / FlashEEPROM

1. **Only the first row is rewritten after a page erase.** `Write` erases the whole 512-instruction page and rewrites only row 1 (64 instructions). That is fine while NumValues ≤ 64, but nothing prevents a larger setting.
2. **TBLPAG is not restored.** `MX_FLASH_ReadInstruction` and the write routines leave `TBLPAG` changed (0xFA after a write).
3. **`MxConv` is a shared global.** It is not safe if flash routines can run from an interrupt.
4. **No error reporting.** `WRERR` and `URERR` are never checked, and `Write` returns nothing, so a failed write cannot be detected from Flowcode.
5. **Values are silently truncated.** `BitsPerVal` is 16, but `Read` returns 24 bits and `Write` stores 24 bits. Values over 24 bits lose the top byte without warning. A note in the component help would help.
6. **Compiler warning.** `ReadFlash()` gives `cast to pointer from integer of different size` (line 452).

## Workaround in our application

We disabled the FlashEEPROM `Write` and `Read` icons and save with double-word programming into the next free slot of 0x2800–0x2BFF, erasing the page once every 256 saves. We kept the FlashEEPROM component in the project only so its `Placeholder` array keeps 0x2800–0x2BFF reserved.
