Fix bugs B12-B17 (PA cal loop, ADC buffer, DIAG_SECTION args, htim3 init, stale annotations) with regression tests

B12: PA IDQ calibration loop condition inverted (< 0.2 -> > 0.2) for both DAC1/DAC2
B13: DAC2 ADC buffer mismatch — reads from hadc2 now correctly stored to adc2_readings
B14: DIAG_SECTION macro call sites changed from 2-arg to 1-arg form (4 sites)
B15: htim3 definition + MX_TIM3_Init() added (PWM mode, CH2+CH3, Period=999)
B16: Removed stale NO-OP annotation on TriggerTimedSync (already fixed in Bug #3)
B17: Updated stale GPIO-only warnings to reflect TIM3 PWM implementation (Bug #5)

All 15 tests pass (11 original + 4 new for B12-B15).
This commit is contained in:
Jason
2026-03-19 11:04:53 +02:00
parent 49c9aa28ad
commit c466021bb6
8 changed files with 421 additions and 18 deletions
@@ -581,7 +581,7 @@ int ADF4382A_SetPhaseShift(ADF4382A_Manager *manager, uint16_t tx_phase_ps, uint
// Convert phase shift to duty cycle and apply
if (tx_phase_ps != manager->tx_phase_shift_ps) {
uint16_t duty_cycle = phase_ps_to_duty_cycle(tx_phase_ps);
DIAG_WARN("LO", "TX phase: %d ps -> duty_cycle=%d/%d (SIMPLIFIED -- not real PWM)",
DIAG("LO", "TX phase: %d ps -> duty_cycle=%d/%d (TIM3 CH2 PWM)",
tx_phase_ps, duty_cycle, DELADJ_MAX_DUTY_CYCLE);
ADF4382A_SetFinePhaseShift(manager, 0, duty_cycle); // 0 = TX device
manager->tx_phase_shift_ps = tx_phase_ps;
@@ -589,7 +589,7 @@ int ADF4382A_SetPhaseShift(ADF4382A_Manager *manager, uint16_t tx_phase_ps, uint
if (rx_phase_ps != manager->rx_phase_shift_ps) {
uint16_t duty_cycle = phase_ps_to_duty_cycle(rx_phase_ps);
DIAG_WARN("LO", "RX phase: %d ps -> duty_cycle=%d/%d (SIMPLIFIED -- not real PWM)",
DIAG("LO", "RX phase: %d ps -> duty_cycle=%d/%d (TIM3 CH3 PWM)",
rx_phase_ps, duty_cycle, DELADJ_MAX_DUTY_CYCLE);
ADF4382A_SetFinePhaseShift(manager, 1, duty_cycle); // 1 = RX device
manager->rx_phase_shift_ps = rx_phase_ps;
@@ -114,6 +114,7 @@ SPI_HandleTypeDef hspi1;
SPI_HandleTypeDef hspi4;
TIM_HandleTypeDef htim1;
TIM_HandleTypeDef htim3; // B15 fix: DELADJ PWM timer (CH2=TX, CH3=RX)
UART_HandleTypeDef huart5;
UART_HandleTypeDef huart3;
@@ -261,6 +262,7 @@ void PeriphCommonClock_Config(void);
static void MPU_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM1_Init(void);
static void MX_TIM3_Init(void); // B15 fix: DELADJ PWM timer init
static void MX_I2C1_Init(void);
static void MX_I2C2_Init(void);
static void MX_I2C3_Init(void);
@@ -341,7 +343,7 @@ extern "C" {
}
void systemPowerUpSequence() {
DIAG_SECTION("PWR", "systemPowerUpSequence");
DIAG_SECTION("PWR: systemPowerUpSequence");
uint8_t msg[] = "Starting Power Up Sequence...\r\n";
HAL_UART_Transmit(&huart3, msg, sizeof(msg)-1, 1000);
@@ -386,7 +388,7 @@ void systemPowerUpSequence() {
}
void systemPowerDownSequence() {
DIAG_SECTION("PWR", "systemPowerDownSequence");
DIAG_SECTION("PWR: systemPowerDownSequence");
uint8_t msg[] = "Starting Power Down Sequence...\r\n";
HAL_UART_Transmit(&huart3, msg, sizeof(msg)-1, 1000);
@@ -733,7 +735,7 @@ SystemError_t checkSystemHealth(void) {
// Error recovery function
void attemptErrorRecovery(SystemError_t error) {
DIAG_SECTION("SYS", "attemptErrorRecovery");
DIAG_SECTION("SYS: attemptErrorRecovery");
DIAG("SYS", "Attempting recovery from error code %d", error);
char recovery_msg[80];
snprintf(recovery_msg, sizeof(recovery_msg),
@@ -1320,6 +1322,7 @@ int main(void)
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM1_Init();
MX_TIM3_Init(); // B15 fix: init DELADJ PWM timer before LO manager uses it
MX_I2C1_Init();
MX_I2C2_Init();
MX_I2C3_Init();
@@ -1603,9 +1606,7 @@ int main(void)
// - SYNC pin connected for triggering
// When ready to synchronize:
/* [BUG ANNOTATION] TriggerTimedSync is a no-op -- it only prints
* messages but does not actually toggle any sync pin or register. */
DIAG_WARN("LO", "[BUG] Calling TriggerTimedSync -- known NO-OP, does nothing hardware-related");
DIAG("LO", "Calling TriggerTimedSync to pulse sw_sync on both PLLs");
ADF4382A_TriggerTimedSync(&lo_manager);
// At this point, the SYNC pin can be toggled to trigger synchronization
} else {
@@ -1745,7 +1746,7 @@ int main(void)
/************RF Power Amplifier Powering up sequence************/
/***************************************************************/
if(PowerAmplifier){
DIAG_SECTION("PA", "RF Power Amplifier power-up sequence (PowerAmplifier=true)");
DIAG_SECTION("PA: RF Power Amplifier power-up sequence");
/* Initialize DACs */
/* DAC1: Address 0b1001000 = 0x48 */
DIAG("PA", "Initializing DAC1 (I2C1, addr=0x48, 8-bit)");
@@ -1851,7 +1852,7 @@ int main(void)
DAC5578_WriteAndUpdateChannelValue(&hdac1, channel, DAC_val);
adc1_readings[channel] = ADS7830_Measure_SingleEnded(&hadc1, channel);
Idq_reading[channel] = (3.3/255) * adc1_readings[channel] / (50 * 0.005);
} while (DAC_val > 38 && abs(Idq_reading[channel] - 1.680) < 0.2); // Fixed logic
} while (DAC_val > 38 && abs(Idq_reading[channel] - 1.680) > 0.2); // B12 fix: loop while FAR from target
DIAG("PA", " DAC1 ch%d calibrated: DAC_val=%d Idq=%.3fA iters=%d",
channel, DAC_val, Idq_reading[channel], safety_counter);
}
@@ -1869,9 +1870,9 @@ int main(void)
}
DAC_val = DAC_val - 4;
DAC5578_WriteAndUpdateChannelValue(&hdac2, channel, DAC_val);
adc1_readings[channel] = ADS7830_Measure_SingleEnded(&hadc2, channel);
adc2_readings[channel] = ADS7830_Measure_SingleEnded(&hadc2, channel); // B13 fix: was adc1_readings
Idq_reading[channel+8] = (3.3/255) * adc2_readings[channel] / (50 * 0.005);
} while (DAC_val > 38 && abs(Idq_reading[channel+8] - 1.680) < 0.2); // Fixed logic
} while (DAC_val > 38 && abs(Idq_reading[channel+8] - 1.680) > 0.2); // B12 fix: loop while FAR from target
DIAG("PA", " DAC2 ch%d calibrated: DAC_val=%d Idq=%.3fA iters=%d",
channel, DAC_val, Idq_reading[channel+8], safety_counter);
}
@@ -2387,6 +2388,63 @@ static void MX_TIM1_Init(void)
}
/**
* @brief TIM3 Initialization Function — DELADJ PWM for ADF4382A phase shift
* CH2 = TX DELADJ, CH3 = RX DELADJ
* Period (ARR) = 999 → 1000 counts matching DELADJ_MAX_DUTY_CYCLE
* Prescaler = 71 → 1 MHz tick @ 72 MHz APB1 → 1 kHz PWM frequency
* @param None
* @retval None
*/
static void MX_TIM3_Init(void)
{
/* B15 fix: provide htim3 definition so adf4382a_manager.c extern resolves */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
htim3.Instance = TIM3;
htim3.Init.Prescaler = 71; // 72 MHz / (71+1) = 1 MHz tick
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 999; // ARR = 999 → 1000 counts = DELADJ_MAX_DUTY_CYCLE
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0; // Start with 0% duty cycle
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
// Configure CH2 (TX DELADJ)
if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
{
Error_Handler();
}
// Configure CH3 (RX DELADJ)
if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_3) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief UART5 Initialization Function
* @param None
@@ -13,3 +13,8 @@ test_bug7_gpio_pin_conflict
test_bug8_uart_commented_out
test_bug9_platform_ops_null
test_bug10_spi_cs_not_toggled
test_bug11_platform_spi_transmit_only
test_bug12_pa_cal_loop_inverted
test_bug13_dac2_adc_buffer_mismatch
test_bug14_diag_section_args
test_bug15_htim3_dangling_extern
+35 -6
View File
@@ -1,7 +1,7 @@
################################################################################
# Makefile -- MCU firmware unit test harness for AERIS-10
#
# Builds and runs host-side (macOS) tests for the 10 discovered firmware bugs.
# Builds and runs host-side (macOS) tests for all discovered firmware bugs.
# Uses mock HAL + spy/recording pattern to test real firmware code without
# hardware.
#
@@ -40,20 +40,27 @@ TESTS_WITH_REAL := test_bug1_timed_sync_init_ordering \
test_bug4_phase_shift_before_check \
test_bug5_fine_phase_gpio_only \
test_bug9_platform_ops_null \
test_bug10_spi_cs_not_toggled
test_bug10_spi_cs_not_toggled \
test_bug15_htim3_dangling_extern
# Tests that only need mocks (extracted patterns / static analysis)
TESTS_MOCK_ONLY := test_bug2_ad9523_double_setup \
test_bug6_timer_variable_collision \
test_bug7_gpio_pin_conflict \
test_bug8_uart_commented_out
test_bug8_uart_commented_out \
test_bug14_diag_section_args
# Tests that are standalone (no mocks needed, pure logic)
TESTS_STANDALONE := test_bug12_pa_cal_loop_inverted \
test_bug13_dac2_adc_buffer_mismatch
# Tests that need platform_noos_stm32.o + mocks
TESTS_WITH_PLATFORM := test_bug11_platform_spi_transmit_only
ALL_TESTS := $(TESTS_WITH_REAL) $(TESTS_MOCK_ONLY) $(TESTS_WITH_PLATFORM)
ALL_TESTS := $(TESTS_WITH_REAL) $(TESTS_MOCK_ONLY) $(TESTS_STANDALONE) $(TESTS_WITH_PLATFORM)
.PHONY: all build test clean $(addprefix test_,bug1 bug2 bug3 bug4 bug5 bug6 bug7 bug8 bug9 bug10 bug11)
.PHONY: all build test clean \
$(addprefix test_,bug1 bug2 bug3 bug4 bug5 bug6 bug7 bug8 bug9 bug10 bug11 bug12 bug13 bug14 bug15)
all: build test
@@ -61,7 +68,7 @@ build: $(ALL_TESTS)
test: build
@echo "==============================================="
@echo " Running all 11 bug tests..."
@echo " Running all $(words $(ALL_TESTS)) bug tests..."
@echo "==============================================="
@pass=0; fail=0; \
for t in $(ALL_TESTS); do \
@@ -112,6 +119,16 @@ test_bug7_gpio_pin_conflict: test_bug7_gpio_pin_conflict.c $(MOCK_OBJS)
test_bug8_uart_commented_out: test_bug8_uart_commented_out.c
$(CC) $(CFLAGS) -I. $< -o $@
test_bug14_diag_section_args: test_bug14_diag_section_args.c $(MOCK_OBJS)
$(CC) $(CFLAGS) $(INCLUDES) $< $(MOCK_OBJS) -o $@
# Standalone tests (pure logic, no mocks)
test_bug12_pa_cal_loop_inverted: test_bug12_pa_cal_loop_inverted.c
$(CC) $(CFLAGS) $< -lm -o $@
test_bug13_dac2_adc_buffer_mismatch: test_bug13_dac2_adc_buffer_mismatch.c
$(CC) $(CFLAGS) $< -lm -o $@
# Tests that need platform_noos_stm32.o + mocks
$(TESTS_WITH_PLATFORM): %: %.c $(MOCK_OBJS) $(PLATFORM_OBJ)
$(CC) $(CFLAGS) $(INCLUDES) $< $(MOCK_OBJS) $(PLATFORM_OBJ) -o $@
@@ -151,6 +168,18 @@ test_bug10: test_bug10_spi_cs_not_toggled
test_bug11: test_bug11_platform_spi_transmit_only
./test_bug11_platform_spi_transmit_only
test_bug12: test_bug12_pa_cal_loop_inverted
./test_bug12_pa_cal_loop_inverted
test_bug13: test_bug13_dac2_adc_buffer_mismatch
./test_bug13_dac2_adc_buffer_mismatch
test_bug14: test_bug14_diag_section_args
./test_bug14_diag_section_args
test_bug15: test_bug15_htim3_dangling_extern
./test_bug15_htim3_dangling_extern
# --- Clean ---
clean:
@@ -0,0 +1,88 @@
/*******************************************************************************
* test_bug12_pa_cal_loop_inverted.c
*
* Bug #12 (FIXED): PA IDQ calibration loop condition was inverted.
* Old: while (DAC_val > 38 && abs(Idq - 1.680) < 0.2)
* → loop continued ONLY when CLOSE to target, exited when far away
* New: while (DAC_val > 38 && abs(Idq - 1.680) > 0.2)
* → loop continues while FAR from target, exits when converged
*
* Test strategy:
* Simulate the loop logic with known Idq values and verify:
* 1. Loop continues when Idq is far from 1.680A (e.g., 0.5A)
* 2. Loop exits when Idq is within 0.2A of target (e.g., 1.60A)
* 3. Loop exits when DAC_val reaches lower bound (38)
******************************************************************************/
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
/* Extracted calibration loop condition (post-fix) */
static int should_continue_loop(int DAC_val, double Idq_reading)
{
/* This matches the FIXED condition in main.cpp lines 1854/1874 */
return (DAC_val > 38 && fabs(Idq_reading - 1.680) > 0.2);
}
int main(void)
{
printf("=== Bug #12 (FIXED): PA calibration loop condition ===\n");
/* Test 1: Idq far from target → loop should CONTINUE */
printf(" Test 1: Idq=0.500A (far from 1.680A), DAC=100 → ");
assert(should_continue_loop(100, 0.500) == 1);
printf("CONTINUE (correct)\n");
/* Test 2: Idq within tolerance → loop should EXIT */
printf(" Test 2: Idq=1.600A (within 0.2A of 1.680A), DAC=80 → ");
assert(should_continue_loop(80, 1.600) == 0);
printf("EXIT (correct)\n");
/* Test 3: Idq exactly at target → loop should EXIT */
printf(" Test 3: Idq=1.680A (exactly at target), DAC=60 → ");
assert(should_continue_loop(60, 1.680) == 0);
printf("EXIT (correct)\n");
/* Test 4: DAC at lower bound → loop should EXIT regardless of Idq */
printf(" Test 4: Idq=0.200A (far), DAC=38 → ");
assert(should_continue_loop(38, 0.200) == 0);
printf("EXIT (DAC limit, correct)\n");
/* Test 5: Idq just outside tolerance (0.201 from target) → CONTINUE */
printf(" Test 5: Idq=1.479A (|diff|=0.201), DAC=50 → ");
assert(should_continue_loop(50, 1.479) == 1);
printf("CONTINUE (correct)\n");
/* Test 6: Idq just inside tolerance (0.199 from target) → EXIT */
printf(" Test 6: Idq=1.481A (|diff|=0.199), DAC=50 → ");
assert(should_continue_loop(50, 1.481) == 0);
printf("EXIT (correct)\n");
/* Test 7: Simulate full loop convergence */
printf(" Test 7: Full loop simulation... ");
{
int DAC_val = 126;
int iterations = 0;
/* Simulate decreasing DAC → increasing Idq */
while (1) {
DAC_val -= 4;
iterations++;
/* Simulate: Idq = 1.680 - (DAC_val - 50) * 0.02 */
double Idq = 1.680 - (DAC_val - 50) * 0.02;
if (!should_continue_loop(DAC_val, Idq)) {
printf("converged at DAC=%d Idq=%.3fA after %d iterations",
DAC_val, Idq, iterations);
/* Should converge somewhere around DAC=50-60 */
assert(iterations < 50); /* safety counter limit */
assert(fabs(Idq - 1.680) <= 0.2); /* should be in tolerance */
break;
}
assert(iterations < 100); /* prevent infinite loop in test */
}
printf(" → PASS\n");
}
printf("\n=== Bug #12: ALL TESTS PASSED (post-fix) ===\n\n");
return 0;
}
@@ -0,0 +1,84 @@
/*******************************************************************************
* test_bug13_dac2_adc_buffer_mismatch.c
*
* Bug #13 (FIXED): DAC2 calibration loop wrote ADC reading to adc1_readings
* but calculated Idq from adc2_readings — using stale/uninitialized data.
*
* Old: adc1_readings[channel] = ADS7830_Measure_SingleEnded(&hadc2, channel);
* Idq = ... * adc2_readings[channel] / ...; ← reads WRONG buffer
*
* New: adc2_readings[channel] = ADS7830_Measure_SingleEnded(&hadc2, channel);
* Idq = ... * adc2_readings[channel] / ...; ← reads CORRECT buffer
*
* Test strategy:
* Simulate the buffer read/write pattern and verify that the Idq calculation
* uses the freshly-measured value, not a stale one.
******************************************************************************/
#include <assert.h>
#include <stdio.h>
#include <stdint.h>
#include <math.h>
int main(void)
{
printf("=== Bug #13 (FIXED): DAC2 ADC buffer mismatch ===\n");
/* Simulate the buffers */
uint8_t adc1_readings[8] = {0};
uint8_t adc2_readings[8] = {0};
/* Pre-fill with stale data to detect mismatch */
for (int i = 0; i < 8; i++) {
adc1_readings[i] = 42; /* stale DAC1 data */
adc2_readings[i] = 99; /* stale DAC2 data */
}
uint8_t channel = 3;
uint8_t measured_value = 200; /* simulated ADC reading */
/* POST-FIX code pattern (from main.cpp line 1872): */
adc2_readings[channel] = measured_value; /* Write to CORRECT buffer */
/* POST-FIX Idq calculation (from main.cpp line 1873): */
double Idq = (3.3/255.0) * adc2_readings[channel] / (50 * 0.005);
/* Expected Idq: (3.3/255) * 200 / 0.25 = 10.353 A (unrealistic but tests math) */
double expected = (3.3/255.0) * 200.0 / (50.0 * 0.005);
printf(" measured_value=%d, adc2_readings[%d]=%d\n",
measured_value, channel, adc2_readings[channel]);
printf(" Idq=%.6f, expected=%.6f\n", Idq, expected);
/* Test 1: adc2_readings was written (not adc1_readings) */
assert(adc2_readings[channel] == measured_value);
printf(" PASS: adc2_readings[%d] == measured_value (%d)\n",
channel, measured_value);
/* Test 2: adc1_readings was NOT overwritten (still has stale value) */
assert(adc1_readings[channel] == 42);
printf(" PASS: adc1_readings[%d] unchanged (stale=%d)\n", channel, 42);
/* Test 3: Idq was calculated from the correct buffer */
assert(fabs(Idq - expected) < 0.001);
printf(" PASS: Idq calculation uses adc2_readings (correct buffer)\n");
/* Test 4: Verify the BUG pattern would have given wrong result */
{
/* Simulate the OLD buggy code: write to adc1, read from adc2 */
uint8_t bug_adc1[8] = {0};
uint8_t bug_adc2[8] = {0};
bug_adc2[channel] = 99; /* stale value in adc2 */
bug_adc1[channel] = measured_value; /* BUG: wrote to wrong buffer */
double bug_Idq = (3.3/255.0) * bug_adc2[channel] / (50.0 * 0.005);
/* Bug would use stale value 99 instead of measured 200 */
printf(" Buggy Idq=%.3f (from stale val=99), Fixed Idq=%.3f (from measured=%d)\n",
bug_Idq, Idq, measured_value);
assert(fabs(bug_Idq - Idq) > 0.1); /* They should be different */
printf(" PASS: buggy value differs from correct value\n");
}
printf("\n=== Bug #13: ALL TESTS PASSED (post-fix) ===\n\n");
return 0;
}
@@ -0,0 +1,50 @@
/*******************************************************************************
* test_bug14_diag_section_args.c
*
* Bug #14 (FIXED): DIAG_SECTION macro takes 1 arg but 4 call sites passed 2.
* Old: DIAG_SECTION("PWR", "systemPowerUpSequence") → compile error
* New: DIAG_SECTION("PWR: systemPowerUpSequence") → 1 arg, compiles fine
*
* Test strategy:
* Include diag_log.h (via shim) and call DIAG_SECTION with 1 arg.
* If this compiles and runs, the bug is fixed.
* Also verify that DIAG_SECTION produces output containing the title string.
******************************************************************************/
#include "stm32_hal_mock.h"
#include "diag_log.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
printf("=== Bug #14 (FIXED): DIAG_SECTION macro arg count ===\n");
/* Test 1: All 4 fixed call patterns compile and execute */
printf(" Test 1: DIAG_SECTION with 1-arg form compiles... ");
DIAG_SECTION("PWR: systemPowerUpSequence");
DIAG_SECTION("PWR: systemPowerDownSequence");
DIAG_SECTION("SYS: attemptErrorRecovery");
DIAG_SECTION("PA: RF Power Amplifier power-up sequence");
printf("PASS\n");
/* Test 2: DIAG_SECTION with DIAG_DISABLE compiles as no-op */
printf(" Test 2: DIAG_SECTION produces output (not a no-op)... ");
/* We just confirm no crash — the macro is printf-based */
printf("PASS\n");
/* Test 3: Verify the original SYSTEM INIT call still works */
printf(" Test 3: Original 1-arg call (SYSTEM INIT)... ");
DIAG_SECTION("SYSTEM INIT");
printf("PASS\n");
/* Test 4: Verify other DIAG macros still work alongside */
printf(" Test 4: DIAG/DIAG_WARN/DIAG_ERR alongside DIAG_SECTION... ");
DIAG("SYS", "test message %d", 42);
DIAG_WARN("SYS", "test warning");
DIAG_ERR("SYS", "test error %s", "detail");
printf("PASS\n");
printf("\n=== Bug #14: ALL TESTS PASSED (post-fix) ===\n\n");
return 0;
}
@@ -0,0 +1,89 @@
/*******************************************************************************
* test_bug15_htim3_dangling_extern.c
*
* Bug #15 (FIXED): adf4382a_manager.c declared `extern TIM_HandleTypeDef htim3`
* but main.cpp had no `TIM_HandleTypeDef htim3` definition and no
* `MX_TIM3_Init()` call. The extern resolved to nothing → linker error or
* zero-initialized BSS → PWM calls operate on unconfigured timer hardware.
*
* Fix:
* - Added `TIM_HandleTypeDef htim3;` definition in main.cpp (line ~117)
* - Added `static void MX_TIM3_Init(void)` prototype + implementation
* - Added `MX_TIM3_Init();` call in peripheral init sequence
* - TIM3 configured for PWM mode: Prescaler=71, Period=999 (=DELADJ_MAX_DUTY_CYCLE),
* CH2 (TX DELADJ) and CH3 (RX DELADJ) in PWM1 mode
*
* Test strategy:
* 1. Verify htim3 is defined (not just extern) in the mock environment
* 2. Verify SetFinePhaseShift works with the timer (reuses test_bug5 pattern)
* 3. Verify PWM start/stop on both channels works without crash
******************************************************************************/
#include "adf4382a_manager.h"
#include <assert.h>
#include <stdio.h>
int main(void)
{
ADF4382A_Manager mgr;
int ret;
printf("=== Bug #15 (FIXED): htim3 defined + TIM3 PWM configured ===\n");
/* Test 1: htim3 exists and has a valid id */
printf(" Test 1: htim3 is defined (id=%u)... ", htim3.id);
assert(htim3.id == 3);
printf("PASS\n");
/* Test 2: Init manager, then use SetFinePhaseShift which exercises htim3 */
spy_reset();
ret = ADF4382A_Manager_Init(&mgr, SYNC_METHOD_TIMED);
assert(ret == ADF4382A_MANAGER_OK);
printf(" Test 2: Manager init OK\n");
/* Test 3: Intermediate duty cycle on TX (CH2) → PWM start + set compare */
spy_reset();
ret = ADF4382A_SetFinePhaseShift(&mgr, 0, 500);
assert(ret == ADF4382A_MANAGER_OK);
int pwm_starts = spy_count_type(SPY_TIM_PWM_START);
int set_compares = spy_count_type(SPY_TIM_SET_COMPARE);
printf(" Test 3: TX duty=500 → PWM_START=%d SET_COMPARE=%d... ",
pwm_starts, set_compares);
assert(pwm_starts == 1);
assert(set_compares == 1);
/* Verify the timer used is htim3 (id=3) */
int idx = spy_find_nth(SPY_TIM_PWM_START, 0);
const SpyRecord *r = spy_get(idx);
assert(r != NULL && r->value == 3); /* htim3.id == 3 */
printf("timer_id=%u (htim3) PASS\n", r->value);
/* Test 4: Intermediate duty cycle on RX (CH3) */
spy_reset();
ret = ADF4382A_SetFinePhaseShift(&mgr, 1, 300);
assert(ret == ADF4382A_MANAGER_OK);
idx = spy_find_nth(SPY_TIM_PWM_START, 0);
r = spy_get(idx);
assert(r != NULL);
printf(" Test 4: RX duty=300 → channel=0x%02X (expected 0x%02X=CH3) timer_id=%u... ",
r->pin, TIM_CHANNEL_3, r->value);
assert(r->pin == TIM_CHANNEL_3);
assert(r->value == 3);
printf("PASS\n");
/* Test 5: duty=0 stops PWM gracefully */
spy_reset();
ret = ADF4382A_SetFinePhaseShift(&mgr, 0, 0);
assert(ret == ADF4382A_MANAGER_OK);
int pwm_stops = spy_count_type(SPY_TIM_PWM_STOP);
printf(" Test 5: duty=0 → PWM_STOP=%d... ", pwm_stops);
assert(pwm_stops == 1);
printf("PASS\n");
ADF4382A_Manager_Deinit(&mgr);
printf("\n=== Bug #15: ALL TESTS PASSED (post-fix) ===\n\n");
return 0;
}