Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions T-962-controller.ld
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
* Linker script for the T-962 controller improved firmware.
*
* Amended from a version generated by LPCXpresso v7.5.0
*
* LPC2134 Flash layout (128KB, 11 sectors):
* Sectors 0-9: firmware (96KB contiguous)
* Sector 10: reserved for IAP profile storage (0x18000+)
*/

GROUP(
Expand All @@ -13,12 +17,14 @@ GROUP(

MEMORY {
/* Define each memory region */
MFlash128 (rx) : ORIGIN = 0x0, LENGTH = 0x20000 /* 128K bytes */
Ram16 (rwx) : ORIGIN = 0x40000000, LENGTH = 0x4000 /* 16K bytes */
/* Firmware: sectors 0-9 (96KB). Sector 10 reserved for IAP profile storage. */
MFlash128 (rx) : ORIGIN = 0x0, LENGTH = 0x18000 /* 96KB, sectors 0-9 */
/* Top 256 bytes of RAM (0x40003F00-0x40003FFF) reserved for IAP/ISP (per UM10120) */
Ram16 (rwx) : ORIGIN = 0x40000000, LENGTH = 0x3F00 /* 16KB - 256 bytes IAP */
}
/* Define a symbol for the top of each memory region */
__top_MFlash128 = 0x0 + 0x20000;
__top_Ram16 = 0x40000000 + 0x4000;
__top_MFlash128 = 0x0 + 0x18000;
__top_Ram16 = 0x40000000 + 0x3F00;

ENTRY(_start)

Expand Down
264 changes: 264 additions & 0 deletions src/flashprofiles.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
/*
* flashprofiles.c - IAP flash profile storage for T-962 reflow controller
*
* Stores reflow profiles in LPC2134 flash sector 10 (at 0x18000).
* Each profile occupies a 256-byte block. Supports up to 32 profiles.
*
* Uses NXP IAP (In-Application Programming) ROM routines for erase/write.
* Cache-erase-rewrite pattern: all valid profiles are cached in RAM,
* sector is erased, then profiles are written back.
*/

#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "LPC214x.h"
#include "flashprofiles.h"
#include "reflow_profiles.h"
#include "vic.h"

// IAP ROM entry point
typedef void (*IAP)(unsigned int[], unsigned int[]);
static IAP iap_entry = (void*)0x7ffffff1;

// IAP command codes
#define IAP_PREPARE_SECTORS 50
#define IAP_COPY_RAM_FLASH 51
#define IAP_ERASE_SECTORS 52

// On-flash profile block layout (256 bytes)
typedef struct {
uint32_t magic;
char name[FLASH_PROFILE_NAME_LEN];
uint16_t temps[48]; // 96 bytes
uint8_t _reserved[256 - 4 - FLASH_PROFILE_NAME_LEN - 96];
} FlashProfileBlock_t;

// Validity cache (scanned at init)
static uint8_t profile_valid[FLASH_PROFILE_MAX_SLOTS];
static int profile_count = 0;

// 256-byte write buffer — must be word-aligned for IAP
static uint8_t __attribute__((aligned(4))) flash_write_buf[FLASH_PROFILE_BLOCK_SIZE];

// ---- Internal helpers ----

static const FlashProfileBlock_t* flash_profile_ptr(int slot) {
return (const FlashProfileBlock_t*)((uintptr_t)FLASH_PROFILE_BASE +
(uint32_t)slot * FLASH_PROFILE_BLOCK_SIZE);
}

// IAP command/result arrays — placed in BSS (not stack) to avoid
// corruption from IAP's 128-byte internal stack usage
static unsigned int iap_command[5];
static unsigned int iap_result[3];

static int iap_erase_sector(void) {
uint32_t save = VIC_DisableIRQ();
uint32_t mam_save = MAMCR;
MAMCR = 0; // Disable MAM during IAP (required by NXP)

// Prepare sector (must stay valid until erase)
iap_command[0] = IAP_PREPARE_SECTORS;
iap_command[1] = FLASH_PROFILE_SECTOR;
iap_command[2] = FLASH_PROFILE_SECTOR;
iap_entry(iap_command, iap_result);
if (iap_result[0] != 0) {
MAMCR = mam_save;
VIC_RestoreIRQ(save);
printf("\n[IAP] Prepare for erase failed: code %u\n", iap_result[0]);
return -1;
}

// Erase sector
iap_command[0] = IAP_ERASE_SECTORS;
iap_command[1] = FLASH_PROFILE_SECTOR;
iap_command[2] = FLASH_PROFILE_SECTOR;
iap_command[3] = IAP_CCLK_KHZ;
iap_entry(iap_command, iap_result);

MAMCR = mam_save;
VIC_RestoreIRQ(save);
if (iap_result[0] != 0) {
printf("\n[IAP] Erase failed: code %u\n", iap_result[0]);
}
return (iap_result[0] == 0) ? 0 : -1;
}

static int iap_write_block(uint32_t flash_addr, uint8_t* data) {
uint32_t save = VIC_DisableIRQ();
uint32_t mam_save = MAMCR;
MAMCR = 0; // Disable MAM during IAP (required by NXP)

// Prepare sector (must stay valid until copy)
iap_command[0] = IAP_PREPARE_SECTORS;
iap_command[1] = FLASH_PROFILE_SECTOR;
iap_command[2] = FLASH_PROFILE_SECTOR;
iap_entry(iap_command, iap_result);
if (iap_result[0] != 0) {
MAMCR = mam_save;
VIC_RestoreIRQ(save);
printf("\n[IAP] Prepare for write failed: code %u\n", iap_result[0]);
return -1;
}

// Copy RAM to Flash
iap_command[0] = IAP_COPY_RAM_FLASH;
iap_command[1] = flash_addr;
iap_command[2] = (uintptr_t)data;
iap_command[3] = FLASH_PROFILE_BLOCK_SIZE;
iap_command[4] = IAP_CCLK_KHZ;
iap_entry(iap_command, iap_result);

MAMCR = mam_save;
VIC_RestoreIRQ(save);
if (iap_result[0] != 0) {
printf("\n[IAP] Write to 0x%lX failed: code %u\n", (unsigned long)flash_addr, iap_result[0]);
}
return (iap_result[0] == 0) ? 0 : -1;
}

// ---- Cache for sector rewrite ----
// Compact cache — only stores the essential data (not the full 256-byte block)
typedef struct {
char name[FLASH_PROFILE_NAME_LEN];
uint16_t temps[48];
} ProfileCache_t; // ~116 bytes

// Cache, erase, rewrite the entire sector with one slot changed
static int rewrite_sector(int target_slot, const uint16_t* new_temps,
const char* new_name, int deleting) {
static ProfileCache_t cache[FLASH_PROFILE_MAX_SLOTS]; // ~3.7KB in BSS
int cache_valid[FLASH_PROFILE_MAX_SLOTS];
memset(cache_valid, 0, sizeof(cache_valid));

// Step 1: Cache all existing profiles from flash
for (int i = 0; i < FLASH_PROFILE_MAX_SLOTS; i++) {
if (i == target_slot) {
if (!deleting && new_temps) {
if (new_name) {
strncpy(cache[i].name, new_name, FLASH_PROFILE_NAME_LEN - 1);
cache[i].name[FLASH_PROFILE_NAME_LEN - 1] = '\0';
} else {
snprintf(cache[i].name, FLASH_PROFILE_NAME_LEN, "Flash #%d", i);
}
memcpy(cache[i].temps, new_temps, sizeof(uint16_t) * 48);
cache_valid[i] = 1;
}
} else if (profile_valid[i]) {
const FlashProfileBlock_t* p = flash_profile_ptr(i);
memcpy(cache[i].name, p->name, FLASH_PROFILE_NAME_LEN);
memcpy(cache[i].temps, p->temps, sizeof(uint16_t) * 48);
cache_valid[i] = 1;
}
}

// Step 2: Erase the sector
if (iap_erase_sector() != 0) {
printf("\n[ERROR] Flash erase failed\n");
return -1;
}

// Step 3: Write all cached profiles back
int new_count = 0;
int target_ok = 0;
for (int i = 0; i < FLASH_PROFILE_MAX_SLOTS; i++) {
if (!cache_valid[i]) {
profile_valid[i] = 0;
continue;
}
memset(flash_write_buf, 0xFF, FLASH_PROFILE_BLOCK_SIZE);
FlashProfileBlock_t* blk = (FlashProfileBlock_t*)flash_write_buf;
blk->magic = FLASH_PROFILE_MAGIC;
memcpy(blk->name, cache[i].name, FLASH_PROFILE_NAME_LEN);
memcpy(blk->temps, cache[i].temps, sizeof(uint16_t) * 48);

uint32_t addr = FLASH_PROFILE_BASE + (uint32_t)i * FLASH_PROFILE_BLOCK_SIZE;
if (iap_write_block(addr, flash_write_buf) != 0) {
printf("\n[ERROR] Flash write failed at slot %d\n", i);
profile_valid[i] = 0;
continue;
}
profile_valid[i] = 1;
new_count++;
if (i == target_slot) target_ok = 1;
}
profile_count = new_count;
return target_ok ? 0 : -1;
}

// ---- Public API ----

void FlashProfiles_Init(void) {
profile_count = 0;
for (int i = 0; i < FLASH_PROFILE_MAX_SLOTS; i++) {
const FlashProfileBlock_t* p = flash_profile_ptr(i);
if (p->magic == FLASH_PROFILE_MAGIC) {
profile_valid[i] = 1;
profile_count++;
} else {
profile_valid[i] = 0;
}
}
printf("\nFlash profiles: %d/%d slots used",
profile_count, FLASH_PROFILE_MAX_SLOTS);
}

int FlashProfiles_GetCount(void) { return profile_count; }
int FlashProfiles_GetCapacity(void) { return FLASH_PROFILE_MAX_SLOTS; }

int FlashProfiles_IsValid(int slot) {
if (slot < 0 || slot >= FLASH_PROFILE_MAX_SLOTS) return 0;
return profile_valid[slot];
}

const char* FlashProfiles_GetName(int slot) {
if (!FlashProfiles_IsValid(slot)) return NULL;
return flash_profile_ptr(slot)->name;
}

int FlashProfiles_Read(int slot, uint16_t temps[48], char name[FLASH_PROFILE_NAME_LEN]) {
if (!FlashProfiles_IsValid(slot)) return -1;
const FlashProfileBlock_t* p = flash_profile_ptr(slot);
memcpy(temps, p->temps, sizeof(uint16_t) * 48);
if (name) {
memcpy(name, p->name, FLASH_PROFILE_NAME_LEN);
name[FLASH_PROFILE_NAME_LEN - 1] = '\0';
}
return 0;
}

int FlashProfiles_WriteProfile(int slot, const uint16_t temps[48], const char* name) {
if (slot < 0 || slot >= FLASH_PROFILE_MAX_SLOTS) return -1;
return rewrite_sector(slot, temps, name, 0);
}

int FlashProfiles_Delete(int slot) {
if (slot < 0 || slot >= FLASH_PROFILE_MAX_SLOTS) return -1;
if (!profile_valid[slot]) return 0;
return rewrite_sector(slot, NULL, NULL, 1);
}

void FlashProfiles_BackupAll(void) {
printf("\n# T-962 Profile Backup\n");
printf("# Paste this into serial after reflashing to restore.\n");

// EEPROM profiles (survive reflashing)
printf("# --- EEPROM profiles ---\n");
Reflow_ExportProfile(5); // CUSTOM#1
Reflow_ExportProfile(6); // CUSTOM#2

// Flash profiles (erased on firmware update)
printf("# --- Flash profiles (lost on update) ---\n");
for (int i = 0; i < FLASH_PROFILE_MAX_SLOTS; i++) {
if (!profile_valid[i]) continue;
const FlashProfileBlock_t* p = flash_profile_ptr(i);
printf("save flash %d ", i);
for (int t = 0; t < 48; t++) {
if (t > 0) printf(",");
printf("%d", p->temps[t]);
}
printf(",%s\n", p->name);
}
printf("# --- End of backup ---\n");
}
46 changes: 46 additions & 0 deletions src/flashprofiles.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#ifndef FLASHPROFILES_H_
#define FLASHPROFILES_H_

#include <stdint.h>

// LPC2134 sector 10: last sector at 0x18000+
// Each profile occupies one 256-byte block (IAP minimum write size)
#define FLASH_PROFILE_SECTOR 10
#define FLASH_PROFILE_BASE 0x18000
#define FLASH_PROFILE_END 0x20000
#define FLASH_PROFILE_BLOCK_SIZE 256
#define FLASH_PROFILE_MAX_SLOTS 32 // 8KB / 256 bytes = 32
#define FLASH_PROFILE_NAME_LEN 20 // Including null terminator
#define FLASH_PROFILE_MAGIC 0x50524F46 // "PROF"

// LPC2134 runs at 55.296 MHz
#define IAP_CCLK_KHZ 55296

// Initialize flash profile subsystem — scans sector for valid profiles
void FlashProfiles_Init(void);

// Get number of valid profiles currently stored
int FlashProfiles_GetCount(void);

// Get maximum capacity
int FlashProfiles_GetCapacity(void);

// Read profile from flash slot (0-based). Returns 0 on success, -1 if invalid.
int FlashProfiles_Read(int slot, uint16_t temps[48], char name[FLASH_PROFILE_NAME_LEN]);

// Write profile to flash slot. Erases sector and rewrites all profiles.
int FlashProfiles_WriteProfile(int slot, const uint16_t temps[48], const char* name);

// Delete profile from flash slot. Returns 0 on success.
int FlashProfiles_Delete(int slot);

// Check if a slot contains a valid profile
int FlashProfiles_IsValid(int slot);

// Get the name of a flash profile (returns pointer to flash, or NULL)
const char* FlashProfiles_GetName(int slot);

// Print all profiles (EEPROM + flash) as import-compatible backup text
void FlashProfiles_BackupAll(void);

#endif /* FLASHPROFILES_H_ */
Loading
Loading