mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-05-12 11:18:11 -07:00
Added a new command (hw decay [which monitors the decay rate of the hf antenna cap]) and updated hw tune to detect booster boards and if the pm3 easy is on a metal surface
This commit is contained in:
@@ -260,6 +260,96 @@ static uint32_t MeasureAntennaTuningLfData(void) {
|
||||
return (MAX_ADC_LF_VOLTAGE * (SumAdc(ADC_CHAN_LF, 32) >> 1)) >> 14;
|
||||
}
|
||||
|
||||
// Measure HF antenna decay after field-off.
|
||||
// Captures peak-detect capacitor discharge curve via burst ADC sampling.
|
||||
static void MeasureAntennaTuningHfDecay(const uint8_t *params, uint16_t params_len) {
|
||||
|
||||
// Parse parameters with defaults
|
||||
uint16_t stabilize_ms = 50;
|
||||
uint16_t measure_us = 2000;
|
||||
|
||||
if (params_len >= 4) {
|
||||
stabilize_ms = params[0] | (params[1] << 8);
|
||||
measure_us = params[2] | (params[3] << 8);
|
||||
}
|
||||
if (stabilize_ms == 0) stabilize_ms = 50;
|
||||
if (measure_us == 0) measure_us = 2000;
|
||||
|
||||
// Response: 8-byte header + up to 252 uint16_t samples = 512 bytes max
|
||||
struct {
|
||||
uint16_t baseline_mv;
|
||||
uint16_t num_samples;
|
||||
uint16_t sample_interval_us;
|
||||
uint16_t measure_window_us;
|
||||
uint16_t samples_mv[252];
|
||||
} PACKED payload;
|
||||
memset(&payload, 0, sizeof(payload));
|
||||
|
||||
LED_B_ON();
|
||||
|
||||
// Drive HF field and wait for stabilization
|
||||
FpgaDownloadAndGo(FPGA_BITSTREAM_HF);
|
||||
FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER);
|
||||
SpinDelay(stabilize_ms);
|
||||
|
||||
// Baseline measurement (averaged)
|
||||
payload.baseline_mv = (MAX_ADC_HF_VOLTAGE * SumAdc(ADC_CHAN_HF, 32)) >> 15;
|
||||
|
||||
// Configure ADC for fast burst mode.
|
||||
// Faster ADC clock + shorter S&H trades absolute accuracy for speed.
|
||||
// Source impedance is ~0.91 MOhm (voltage divider), ADC input cap 12pF,
|
||||
// RC = 10.9us. At SHTIM=3 / ADC_CLK=3MHz, S&H = 1.33us reads ~11.5%
|
||||
// of true voltage. This is fine for relative decay shape measurement.
|
||||
AT91C_BASE_ADC->ADC_CR = AT91C_ADC_SWRST;
|
||||
AT91C_BASE_ADC->ADC_MR =
|
||||
ADC_MODE_PRESCALE(7) // ADC_CLK = MCK / 16 = 3 MHz
|
||||
| ADC_MODE_STARTUP_TIME(8) // (8+1)*8 / 3MHz = 24us (> 20us min)
|
||||
| ADC_MODE_SAMPLE_HOLD_TIME(3); // (3+1) / 3MHz = 1.33us S&H
|
||||
AT91C_BASE_ADC->ADC_CHER = ADC_CHANNEL(ADC_CHAN_HF);
|
||||
|
||||
// Start precise timer (1 tick = MCK/32 = 0.667us)
|
||||
StartTicks();
|
||||
|
||||
// Field OFF — start decay measurement
|
||||
FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF);
|
||||
|
||||
uint32_t start_ticks = GetTicks();
|
||||
uint16_t idx = 0;
|
||||
// Convert us to ticks: 1us = 1.5 ticks
|
||||
uint32_t measure_ticks = (measure_us * 3) / 2;
|
||||
|
||||
// Trigger first conversion
|
||||
AT91C_BASE_ADC->ADC_CR = AT91C_ADC_START;
|
||||
|
||||
while (idx < 252) {
|
||||
if (AT91C_BASE_ADC->ADC_SR & ADC_END_OF_CONVERSION(ADC_CHAN_HF)) {
|
||||
uint16_t raw = AT91C_BASE_ADC->ADC_CDR[ADC_CHAN_HF] & 0x3FF;
|
||||
payload.samples_mv[idx] = (MAX_ADC_HF_VOLTAGE * raw) >> 10;
|
||||
idx++;
|
||||
|
||||
if (GetTicksDelta(start_ticks) >= measure_ticks)
|
||||
break;
|
||||
|
||||
// Trigger next conversion
|
||||
AT91C_BASE_ADC->ADC_CR = AT91C_ADC_START;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t elapsed_ticks = GetTicksDelta(start_ticks);
|
||||
payload.num_samples = idx;
|
||||
payload.measure_window_us = (elapsed_ticks * 2) / 3;
|
||||
payload.sample_interval_us = (idx > 1) ? payload.measure_window_us / (idx - 1) : 0;
|
||||
|
||||
FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF);
|
||||
StopTicks();
|
||||
|
||||
uint16_t response_size = 8 + (idx * sizeof(uint16_t));
|
||||
reply_ng(CMD_HF_DECAY, PM3_SUCCESS,
|
||||
(uint8_t *)&payload, response_size);
|
||||
|
||||
LEDsoff();
|
||||
}
|
||||
|
||||
void print_stack_usage(void) {
|
||||
for (uint32_t *p = _stack_start; ; ++p) {
|
||||
if (*p != 0xdeadbeef) {
|
||||
@@ -2553,6 +2643,10 @@ static void PacketReceived(PacketCommandNG *packet) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CMD_HF_DECAY: {
|
||||
MeasureAntennaTuningHfDecay(packet->data.asBytes, packet->length);
|
||||
break;
|
||||
}
|
||||
case CMD_MEASURE_ANTENNA_TUNING_LF: {
|
||||
if (packet->length != 2)
|
||||
reply_ng(CMD_MEASURE_ANTENNA_TUNING_LF, PM3_EINVARG, NULL, 0);
|
||||
|
||||
+245
-13
@@ -891,6 +891,143 @@ static int CmdStandalone(const char *Cmd) {
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
static int CmdDecay(const char *Cmd) {
|
||||
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "hw decay",
|
||||
"Measure HF antenna decay after field-off.\n"
|
||||
"Captures how quickly the peak-detect capacitor voltage drops\n"
|
||||
"after the 13.56 MHz field is turned off. Different antenna loading\n"
|
||||
"(unloaded, booster board, damaged) produces different decay profiles.",
|
||||
"hw decay\n"
|
||||
"hw decay --ms 100 --> stabilize for 100ms before measurement\n"
|
||||
"hw decay --us 5000 --> measure 5ms decay window\n"
|
||||
);
|
||||
|
||||
void *argtable[] = {
|
||||
arg_param_begin,
|
||||
arg_int0(NULL, "ms", "<dec>", "Field stabilization time in ms (default: 50)"),
|
||||
arg_int0(NULL, "us", "<dec>", "Measurement window in us (default: 2000)"),
|
||||
arg_param_end
|
||||
};
|
||||
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
||||
|
||||
uint16_t stabilize_ms = arg_get_int_def(ctx, 1, 50);
|
||||
uint16_t measure_us = arg_get_int_def(ctx, 2, 2000);
|
||||
CLIParserFree(ctx);
|
||||
|
||||
// Build parameter packet
|
||||
struct {
|
||||
uint16_t stabilize_ms;
|
||||
uint16_t measure_us;
|
||||
uint8_t num_pulses;
|
||||
uint8_t padding[3];
|
||||
} PACKED params = {
|
||||
.stabilize_ms = stabilize_ms,
|
||||
.measure_us = measure_us,
|
||||
.num_pulses = 1,
|
||||
.padding = {0}
|
||||
};
|
||||
|
||||
PrintAndLogEx(INFO, "Measuring HF antenna decay...");
|
||||
PrintAndLogEx(INFO, " Field stabilization: " _YELLOW_("%d") " ms", stabilize_ms);
|
||||
PrintAndLogEx(INFO, " Measurement window: " _YELLOW_("%d") " us", measure_us);
|
||||
|
||||
clearCommandBuffer();
|
||||
SendCommandNG(CMD_HF_DECAY, (uint8_t *)¶ms, sizeof(params));
|
||||
|
||||
PacketResponseNG resp;
|
||||
if (WaitForResponseTimeout(CMD_HF_DECAY, &resp, 5000) == false) {
|
||||
PrintAndLogEx(WARNING, "Timeout waiting for decay measurement");
|
||||
return PM3_ETIMEOUT;
|
||||
}
|
||||
|
||||
if (resp.status != PM3_SUCCESS) {
|
||||
PrintAndLogEx(WARNING, "Decay measurement failed");
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
|
||||
// Parse response header
|
||||
uint16_t baseline_mv = resp.data.asBytes[0] | (resp.data.asBytes[1] << 8);
|
||||
uint16_t num_samples = resp.data.asBytes[2] | (resp.data.asBytes[3] << 8);
|
||||
uint16_t sample_interval_us = resp.data.asBytes[4] | (resp.data.asBytes[5] << 8);
|
||||
uint16_t measure_window_us = resp.data.asBytes[6] | (resp.data.asBytes[7] << 8);
|
||||
uint16_t *samples = (uint16_t *)(resp.data.asBytes + 8);
|
||||
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(INFO, "-------- " _CYAN_("HF Decay Measurement") " ----------");
|
||||
PrintAndLogEx(SUCCESS, "Baseline (field on).... " _YELLOW_("%d") " mV (%.2f V)",
|
||||
baseline_mv, baseline_mv / 1000.0);
|
||||
PrintAndLogEx(SUCCESS, "Samples captured....... %d", num_samples);
|
||||
PrintAndLogEx(SUCCESS, "Sample interval........ ~%d us", sample_interval_us);
|
||||
PrintAndLogEx(SUCCESS, "Total window........... %d us", measure_window_us);
|
||||
|
||||
if (num_samples == 0) {
|
||||
PrintAndLogEx(WARNING, "No samples captured");
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
|
||||
// Decay samples use fast ADC (reduced S&H) for ~5us/sample resolution.
|
||||
// Absolute mV values are ~11% of truth due to RC charging limitation,
|
||||
// but relative decay shape is accurate. Use first sample as 100% reference.
|
||||
uint16_t ref_mv = samples[0];
|
||||
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(INFO, " idx | time (us) | raw | %% of peak");
|
||||
PrintAndLogEx(INFO, "-----+-----------+-------+-------------");
|
||||
|
||||
for (uint16_t i = 0; i < num_samples; i++) {
|
||||
uint32_t time_us = (num_samples > 1)
|
||||
? (uint32_t)i * measure_window_us / (num_samples - 1)
|
||||
: 0;
|
||||
double pct = (ref_mv > 0)
|
||||
? 100.0 * samples[i] / ref_mv
|
||||
: 0;
|
||||
PrintAndLogEx(INFO, " %3d | %7d | %5d | %.1f%%",
|
||||
i, time_us, samples[i], pct);
|
||||
}
|
||||
|
||||
// Find time to 50% decay (relative to first sample)
|
||||
uint16_t half_ref = ref_mv / 2;
|
||||
int t_half_idx = -1;
|
||||
for (uint16_t i = 0; i < num_samples; i++) {
|
||||
if (samples[i] <= half_ref) {
|
||||
t_half_idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
if (t_half_idx >= 0) {
|
||||
uint32_t t_half_us = (num_samples > 1)
|
||||
? (uint32_t)t_half_idx * measure_window_us / (num_samples - 1)
|
||||
: 0;
|
||||
PrintAndLogEx(SUCCESS, "Time to 50%% decay..... ~" _YELLOW_("%d") " us (sample %d)", t_half_us, t_half_idx);
|
||||
} else {
|
||||
PrintAndLogEx(INFO, "Voltage did not reach 50%% decay within measurement window");
|
||||
}
|
||||
|
||||
uint16_t final_mv = samples[num_samples - 1];
|
||||
double final_pct = (ref_mv > 0) ? 100.0 * final_mv / ref_mv : 0;
|
||||
PrintAndLogEx(SUCCESS, "Final voltage.......... %d raw (%.1f%% of peak)", final_mv, final_pct);
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(INFO, "Note: decay samples use fast ADC (~5us/sample, relative values)");
|
||||
|
||||
// Load into graph window
|
||||
for (uint16_t i = 0; i < num_samples; i++) {
|
||||
g_GraphBuffer[i] = (int)samples[i];
|
||||
}
|
||||
g_GraphTraceLen = num_samples;
|
||||
ShowGraphWindow();
|
||||
RepaintGraphWindow();
|
||||
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(INFO, "Decay curve loaded into graph window (mV vs sample index)");
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
static int CmdTune(const char *Cmd) {
|
||||
|
||||
CLIParserContext *ctx;
|
||||
@@ -1058,23 +1195,117 @@ static int CmdTune(const char *Cmd) {
|
||||
|
||||
memset(judgement, 0, sizeof(judgement));
|
||||
|
||||
PrintAndLogEx(SUCCESS, "");
|
||||
PrintAndLogEx(SUCCESS, "Approx. Q factor measurement");
|
||||
// If HF is unusable or marginal, run a quick decay measurement to check
|
||||
// for booster board. With a booster, the first fast-ADC decay sample reads
|
||||
// 50-500 (rapid discharge). Without a booster, it reads >1000.
|
||||
bool hf_booster_detected = false;
|
||||
if (!IfPm3Rdv4Fw() && package->v_hf < HF_MARGINAL_V) {
|
||||
struct {
|
||||
uint16_t stabilize_ms;
|
||||
uint16_t measure_us;
|
||||
uint8_t num_pulses;
|
||||
uint8_t padding[3];
|
||||
} PACKED decay_params = {
|
||||
.stabilize_ms = 50,
|
||||
.measure_us = 50,
|
||||
.num_pulses = 1,
|
||||
.padding = {0}
|
||||
};
|
||||
|
||||
if (package->v_hf >= HF_UNUSABLE_V) {
|
||||
// Q measure with Vlr=Q*(2*Vdd/pi)
|
||||
double hfq = (double)package->v_hf * 3.14 / 2 / vdd;
|
||||
PrintAndLogEx(SUCCESS, "Peak voltage.......... " _YELLOW_("%.1lf"), hfq);
|
||||
clearCommandBuffer();
|
||||
SendCommandNG(CMD_HF_DECAY, (uint8_t *)&decay_params, sizeof(decay_params));
|
||||
|
||||
PacketResponseNG decay_resp;
|
||||
if (WaitForResponseTimeout(CMD_HF_DECAY, &decay_resp, 3000)
|
||||
&& decay_resp.status == PM3_SUCCESS) {
|
||||
|
||||
uint16_t decay_num = decay_resp.data.asBytes[2] | (decay_resp.data.asBytes[3] << 8);
|
||||
uint16_t *decay_samples = (uint16_t *)(decay_resp.data.asBytes + 8);
|
||||
|
||||
if (decay_num > 0 && decay_samples[0] >= 50 && decay_samples[0] <= 500) {
|
||||
hf_booster_detected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (package->v_hf < HF_UNUSABLE_V)
|
||||
snprintf(judgement, sizeof(judgement), _RED_("unusable"));
|
||||
else if (package->v_hf < HF_MARGINAL_V)
|
||||
snprintf(judgement, sizeof(judgement), _YELLOW_("marginal"));
|
||||
else
|
||||
snprintf(judgement, sizeof(judgement), _GREEN_("ok"));
|
||||
if (hf_booster_detected) {
|
||||
PrintAndLogEx(SUCCESS, "");
|
||||
PrintAndLogEx(SUCCESS, "Your HF antenna measurement shows");
|
||||
PrintAndLogEx(SUCCESS, "low voltage that is consistent");
|
||||
PrintAndLogEx(SUCCESS, "with the installation of a booster");
|
||||
PrintAndLogEx(SUCCESS, "board. If you do not have a");
|
||||
PrintAndLogEx(SUCCESS, "booster board installed, either");
|
||||
PrintAndLogEx(SUCCESS, "your antenna is malfunctioning or");
|
||||
PrintAndLogEx(SUCCESS, "you have a tag on the HF antenna.");
|
||||
} else {
|
||||
PrintAndLogEx(SUCCESS, "");
|
||||
PrintAndLogEx(SUCCESS, "Approx. Q factor measurement");
|
||||
|
||||
PrintAndLogEx((package->v_hf < HF_UNUSABLE_V) ? WARNING : SUCCESS, "HF antenna ( %s )", judgement);
|
||||
if (package->v_hf >= HF_UNUSABLE_V) {
|
||||
// Q measure with Vlr=Q*(2*Vdd/pi)
|
||||
double hfq = (double)package->v_hf * 3.14 / 2 / vdd;
|
||||
PrintAndLogEx(SUCCESS, "Peak voltage.......... " _YELLOW_("%.1lf"), hfq);
|
||||
}
|
||||
|
||||
if (package->v_hf < HF_UNUSABLE_V)
|
||||
snprintf(judgement, sizeof(judgement), _RED_("unusable"));
|
||||
else if (package->v_hf < HF_MARGINAL_V)
|
||||
snprintf(judgement, sizeof(judgement), _YELLOW_("marginal"));
|
||||
else
|
||||
snprintf(judgement, sizeof(judgement), _GREEN_("ok"));
|
||||
|
||||
PrintAndLogEx((package->v_hf < HF_UNUSABLE_V) ? WARNING : SUCCESS, "HF antenna ( %s )", judgement);
|
||||
|
||||
// If HF voltage is ok/marginal but below 13V, check for
|
||||
// surface interference via decay measurement.
|
||||
// Only on PM3 Easy — RDV4 has different voltage divider.
|
||||
if (!IfPm3Rdv4Fw()
|
||||
&& package->v_hf >= HF_MARGINAL_V
|
||||
&& package->v_hf < 13000) {
|
||||
|
||||
struct {
|
||||
uint16_t stabilize_ms;
|
||||
uint16_t measure_us;
|
||||
uint8_t num_pulses;
|
||||
uint8_t padding[3];
|
||||
} PACKED surface_params = {
|
||||
.stabilize_ms = 50,
|
||||
.measure_us = 50,
|
||||
.num_pulses = 1,
|
||||
.padding = {0}
|
||||
};
|
||||
|
||||
clearCommandBuffer();
|
||||
SendCommandNG(CMD_HF_DECAY,
|
||||
(uint8_t *)&surface_params,
|
||||
sizeof(surface_params));
|
||||
|
||||
PacketResponseNG surface_resp;
|
||||
if (WaitForResponseTimeout(
|
||||
CMD_HF_DECAY,
|
||||
&surface_resp, 3000)
|
||||
&& surface_resp.status == PM3_SUCCESS) {
|
||||
|
||||
uint16_t sn = surface_resp.data.asBytes[2]
|
||||
| (surface_resp.data.asBytes[3] << 8);
|
||||
uint16_t *ss =
|
||||
(uint16_t *)(surface_resp.data.asBytes + 8);
|
||||
|
||||
if (sn > 0 && ss[0] >= 600 && ss[0] <= 900) {
|
||||
PrintAndLogEx(SUCCESS, "");
|
||||
PrintAndLogEx(SUCCESS,
|
||||
"The surface your proxmark"
|
||||
" may be on could");
|
||||
PrintAndLogEx(SUCCESS,
|
||||
"contain interfering"
|
||||
" materials. Try again");
|
||||
PrintAndLogEx(SUCCESS,
|
||||
"while holding the"
|
||||
" proxmark in free space.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// graph LF measurements
|
||||
// even here, these values has 3% error.
|
||||
@@ -1532,6 +1763,7 @@ static command_t CommandTable[] = {
|
||||
{"standalone", CmdStandalone, IfPm3Present, "Start installed standalone mode on device"},
|
||||
{"tia", CmdTia, IfPm3Present, "Trigger a Timing Interval Acquisition to re-adjust the RealTimeCounter divider"},
|
||||
{"tune", CmdTune, IfPm3Lf, "Measure tuning of device antenna"},
|
||||
{"decay", CmdDecay, IfPm3Present, "Measure HF antenna decay after field-off"},
|
||||
{NULL, NULL, NULL, NULL}
|
||||
};
|
||||
|
||||
|
||||
@@ -775,6 +775,7 @@ typedef struct {
|
||||
#define CMD_MEASURE_ANTENNA_TUNING_LF 0x0402
|
||||
#define CMD_LISTEN_READER_FIELD 0x0420
|
||||
#define CMD_HF_DROPFIELD 0x0430
|
||||
#define CMD_HF_DECAY 0x0440
|
||||
|
||||
// For direct FPGA control
|
||||
#define CMD_FPGA_MAJOR_MODE_OFF 0x0500
|
||||
|
||||
Reference in New Issue
Block a user