Embedded Mastery Series · Volume 1 · Companion
Chip B Target Firmware
A single unified firmware for the second NUCLEO-H563ZI used in Volume 1's two-board labs. Flash it once. It runs as the I3C target for §S7 and as the FDCAN echo node for §S8.3. The bench harness on chip A talks to it across iterate.sh runs without manual reset thanks to a built-in independent watchdog.
What this firmware does
- §S7.1 ENTDAA + private write target. Listens
on PB8 (SCL) / PB9 (SDA) at AF3 with the ES0565 §2.15.3
pull-up workaround. Responds to the controller's ENTDAA broadcast
with its 8-byte BCR/DCR/PID payload, accepts DA 0x32, then receives
a 4-byte private write into
tgt_rx[]. - §S7.2 IBI source. The blue USER button (B1, on
PC13) edge-triggers an empty-payload IBI to the controller via
HAL_I3C_Tgt_IBIReq_IT. The controller'sHAL_I3C_NotifyCallbackreads the source DA from the CCC info struct, increments a counter, prints the DA. Auto-rearms its receive IT after the IBI completes (onEVENT_ID_IBIEND). - §S7.3 GETPID Direct CCC responder. The H5 I3C peripheral handles GETPID 0x8D entirely in hardware — no firmware path needed; the firmware just ensures DA is set and the bus is armed.
- §S8.3 FDCAN echo node. FDCAN1 on PD0 (RX) / PD1 (TX), AF9, wired through an MCP2562FD pair to chip A's FDCAN1. Filter accepts every standard 11-bit ID into RX FIFO 0; the callback echoes each frame back at the source ID + 0x100, in the same FD/BRS / DLC the original came in.
Why a single firmware
Earlier drafts of the bench had separate projects for each two-board lab (an I3C-only target, an FDCAN-only echo). That meant the customer had to re-flash chip B between stages, which broke the cumulative-project tenet (flash once, run the rest of the book). The unified firmware here brings up every chip-B-side peripheral the book uses, so flashing chip B is a one-time setup at the start of §S7.
§S9 (USB CDC) is a one-board lab — the customer flashes ST's
Ux_Device_HID_CDC_ACM example temporarily to whichever
board has a free USB cable, runs the lab, then re-flashes the chip B
target firmware here. Chip B doesn't need to do anything for §S9.
Bench autonomy (no manual reset between iterations)
The H5 I3C peripheral keeps its dynamic address in DEVR0
across firmware iterations on chip A — meaning a second
HAL_I3C_Ctrl_DynAddrAssign_IT(... I3C_ONLY_ENTDAA) call
from chip A would silently no-op (chip B already has DA 0x32 from the
last session). The clean fix is on the controller side: chip A uses
I3C_RSTDAA_THEN_ENTDAA instead of I3C_ONLY_ENTDAA,
which broadcasts a Reset Dynamic Address before the new ENTDAA so
targets drop and re-enroll fresh.
Chip B's firmware adds an Independent Watchdog (IWDG)
with a 3-second timeout, kicked on every main-loop iteration. If the
I3C state machine ever does end up stuck in a no-progress loop
(e.g., a future protocol corner the firmware doesn't anticipate),
the watchdog reboots chip B autonomously. DBGMCU.APB1FZR1's
DBG_IWDG_STOP bit is set so the IWDG freezes when the
CPU is paused at a breakpoint — debugger sessions don't trip it.
Build & flash
- Open STM32CubeMX. File → New Project, select NUCLEO-H563ZI board. Click No when asked whether to enable TrustZone (this firmware runs in non-TZ mode for simplicity).
- Configure I3C1 on PB8/PB9 in Target mode. The IOC settings can be left at CubeMX defaults — the firmware overrides the timing fields at runtime.
- Configure FDCAN1 in Normal mode at 500 kbps nominal / 1 Mbps data. Pin PD0 = FDCAN1_RX, PD1 = FDCAN1_TX.
- Configure USART1 as the VCP (NUCLEO-H563ZI's ST-LINK VCP is wired to USART3, but USART1 is fine for this firmware too — adjust the BSP_COM_Init line if you change pins).
- Enable HAL_IWDG_MODULE_ENABLED in
stm32h5xx_hal_conf.h. - Generate code, drop the
main.cbelow intoCore/Src/main.c(replacing the generated file inside the USER CODE blocks), build, and flash the board you've chosen as chip B.
On boot the VCP at 115200 8N1 prints:
I3C Target ready — waiting for ENTDAA
FDCAN echo node ready — listening on FDCAN1 RX FIFO0, echo with ID+0x100
Green LED on. Done. Leave chip B running for the rest of the book.
Source — Core/Src/main.c
This is the complete main.c from the
NUCLEO_H563ZI_I3C_Target STM32CubeIDE project. Copy it
into your CubeMX-generated project's Core/Src/main.c,
preserving the USER CODE block markers.
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2026 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <string.h>
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define RX_BUF_SIZE 16
#define TX_BUF_SIZE 16
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
COM_InitTypeDef BspCOMInit;
I3C_HandleTypeDef hi3c1;
/* USER CODE BEGIN PV */
uint8_t tgt_rx[RX_BUF_SIZE];
uint8_t tgt_tx[TX_BUF_SIZE] = {0xDE, 0xAD, 0xBE, 0xEF, 0x01, 0x02, 0x03, 0x04};
volatile uint8_t tgt_rx_done = 0;
volatile uint8_t tgt_tx_done = 0;
volatile uint8_t got_daa = 0;
volatile uint8_t pending_reset_chip = 0; /* set in IRQ on RSTACT 0x02; main loop reboots */
volatile uint8_t pending_reset_i3c = 0; /* set in IRQ on RSTACT 0x01; main loop reinit's */
volatile uint8_t da_assigned = 0; /* latched: 1 once DA has ever been assigned */
volatile uint8_t ibi_in_flight = 0; /* set when IBIReq submitted, cleared on EVENT_ID_IBIEND */
volatile uint8_t need_rearm_rx = 0; /* main loop re-arms receive when this is set */
volatile uint32_t ibi_count = 0;
I3C_XferTypeDef tgt_xfer;
/* S8.3 — FDCAN1 echo node. Replies to received frames with ID+0x100,
* same payload. Bit timing matches the v3 controller (500 kbps nominal,
* 1 Mbps data, FD_BRS, sample point 75% / 70%). */
FDCAN_HandleTypeDef hfdcan1;
IWDG_HandleTypeDef hiwdg;
static volatile uint32_t echo_rx_count = 0;
static volatile uint32_t echo_tx_count = 0;
static volatile uint32_t echo_last_rx_id = 0;
static volatile uint32_t echo_last_tx_id = 0;
static volatile uint32_t echo_last_err = 0;
static FDCAN_RxHeaderTypeDef echo_rx_hdr;
static FDCAN_TxHeaderTypeDef echo_tx_hdr;
static uint8_t echo_payload[64];
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MPU_Config(void);
static void MX_GPIO_Init(void);
static void MX_I3C1_Init(void);
static void MX_ICACHE_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* MPU Configuration--------------------------------------------------------*/
MPU_Config();
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_I3C1_Init();
MX_ICACHE_Init();
/* USER CODE BEGIN 2 */
/* Initialize leds */
BSP_LED_Init(LED_GREEN);
BSP_LED_Init(LED_YELLOW);
BSP_LED_Init(LED_RED);
/* Initialize USER push-button, will be used to trigger an interrupt each time it's pressed.*/
BSP_PB_Init(BUTTON_USER, BUTTON_MODE_EXTI);
/* Initialize COM1 port (115200, 8 bits (7-bit data + 1 stop bit), no parity */
BspCOMInit.BaudRate = 115200;
BspCOMInit.WordLength = COM_WORDLENGTH_8B;
BspCOMInit.StopBits = COM_STOPBITS_1;
BspCOMInit.Parity = COM_PARITY_NONE;
BspCOMInit.HwFlowCtl = COM_HWCONTROL_NONE;
if (BSP_COM_Init(COM1, &BspCOMInit) != BSP_ERROR_NONE)
{
Error_Handler();
}
/* Enable internal pull-ups on I3C pins */
MODIFY_REG(GPIOB->PUPDR,
GPIO_PUPDR_PUPD8 | GPIO_PUPDR_PUPD9,
GPIO_PUPDR_PUPD8_0 | GPIO_PUPDR_PUPD9_0);
/* Activate target notifications. Per ST reference example
* I3C_Target_ENTDAA_IT, we only enable DAUPDIE here — pre-arming
* ERRIE/FCIE or HAL_I3C_Tgt_Receive_IT before DA is assigned puts the
* peripheral in a half-state where it doesn't terminate ENTDAA cleanly,
* causing the controller's FCF to never fire. The receive IT gets armed
* later in the main loop after the DA-update event lands. */
/* DAUPDIE = dynamic-address update; IBIEND = our IBI request finished
* (so we know when it's safe to issue another IBI). */
HAL_I3C_ActivateNotification(&hi3c1, NULL,
HAL_I3C_IT_DAUPDIE | EVENT_ID_IBIEND | HAL_I3C_IT_RSTIE);
/* Independent Watchdog — autonomous recovery if the I3C state machine
* ever gets stuck in a no-progress loop (e.g., post-IBI BUSY state
* that won't accept fresh ENTDAA). LSI runs at ~32 kHz; prescaler /256
* gives a 125 Hz tick, reload 375 → 3 second timeout. Main loop kicks
* the watchdog every iteration; if it doesn't kick for 3 s, chip B
* reboots cleanly. Customer flashes once and the firmware self-heals
* across all I3C lab interactions. */
DBGMCU->APB1FZR1 |= DBGMCU_APB1FZR1_DBG_IWDG_STOP; /* halt IWDG when CPU is at a breakpoint */
hiwdg.Instance = IWDG;
hiwdg.Init.Prescaler = IWDG_PRESCALER_256;
hiwdg.Init.Window = IWDG_WINDOW_DISABLE;
hiwdg.Init.Reload = 375;
if (HAL_IWDG_Init(&hiwdg) != HAL_OK) {
BSP_LED_On(LED_RED);
}
/* S8.3 — FDCAN1 echo node. Init peripheral with the same parameters
* the v3 controller uses (FD_BRS, 500k nominal / 1M data, prescaler 1).
* Filter accepts every standard 11-bit ID into RX FIFO 0; the
* RxFifo0Callback below replies with ID+0x100 on each received frame. */
hfdcan1.Instance = FDCAN1;
hfdcan1.Init.ClockDivider = FDCAN_CLOCK_DIV1;
hfdcan1.Init.FrameFormat = FDCAN_FRAME_FD_BRS;
hfdcan1.Init.Mode = FDCAN_MODE_NORMAL;
hfdcan1.Init.AutoRetransmission = DISABLE;
hfdcan1.Init.TransmitPause = DISABLE;
hfdcan1.Init.ProtocolException = DISABLE;
/* Bit timing must match the v3 controller exactly (sum + sample point):
* v3 nominal = 1+13+2 = 16 quanta @ 87.5% sample, SJW=1.
* v3 data = 1+5+2 = 8 quanta @ 75% sample, SJW=1. */
hfdcan1.Init.NominalPrescaler = 1;
hfdcan1.Init.NominalSyncJumpWidth = 1;
hfdcan1.Init.NominalTimeSeg1 = 13;
hfdcan1.Init.NominalTimeSeg2 = 2;
hfdcan1.Init.DataPrescaler = 1;
hfdcan1.Init.DataSyncJumpWidth = 1;
hfdcan1.Init.DataTimeSeg1 = 5;
hfdcan1.Init.DataTimeSeg2 = 2;
hfdcan1.Init.StdFiltersNbr = 1;
hfdcan1.Init.ExtFiltersNbr = 0;
hfdcan1.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION;
if (HAL_FDCAN_Init(&hfdcan1) != HAL_OK) Error_Handler();
{
FDCAN_FilterTypeDef f = {0};
f.IdType = FDCAN_STANDARD_ID;
f.FilterIndex = 0;
f.FilterType = FDCAN_FILTER_RANGE;
f.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
f.FilterID1 = 0x000;
f.FilterID2 = 0x7FF;
HAL_FDCAN_ConfigFilter(&hfdcan1, &f);
HAL_FDCAN_ConfigGlobalFilter(&hfdcan1, FDCAN_REJECT, FDCAN_REJECT,
FDCAN_FILTER_REMOTE, FDCAN_FILTER_REMOTE);
HAL_FDCAN_Start(&hfdcan1);
HAL_FDCAN_ActivateNotification(&hfdcan1,
FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0);
}
printf("I3C Target ready — waiting for ENTDAA\r\n");
printf("FDCAN echo node ready — listening on FDCAN1 RX FIFO0, echo with ID+0x100\r\n");
BSP_LED_On(LED_GREEN);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
if (got_daa)
{
got_daa = 0;
da_assigned = 1;
printf("DAA complete — dynamic address assigned\r\n");
BSP_LED_On(LED_YELLOW);
/* Now that we have a DA, arm receive IT for incoming private
* writes from the controller. Doing this BEFORE DA assignment
* keeps the peripheral in a state that breaks ENTDAA. */
memset(tgt_rx, 0, RX_BUF_SIZE);
tgt_xfer.RxBuf.pBuffer = tgt_rx;
/* H5 target HAL treats RxBuf.Size as EXPECTED bytes, not max.
* If the controller sends fewer it raises HAL_I3C_ERROR_SIZE
* even though the bytes arrived correctly. Match the
* controller's 4-byte private write exactly. */
tgt_xfer.RxBuf.Size = 4;
HAL_I3C_Tgt_Receive_IT(&hi3c1, &tgt_xfer);
}
if (tgt_rx_done)
{
tgt_rx_done = 0;
printf("RX: %02X %02X %02X %02X\r\n",
tgt_rx[0], tgt_rx[1], tgt_rx[2], tgt_rx[3]);
BSP_LED_Toggle(LED_GREEN);
/* Do NOT auto-rearm receive here. The H5 target HAL puts the
* peripheral in BUSY_RX while a receive IT is armed, which
* blocks IBI submission. Leave the peripheral in READY so
* the user button can fire IBIs. Re-arm happens after each
* IBI completes (need_rearm_rx branch above). */
}
if (tgt_tx_done)
{
tgt_tx_done = 0;
printf("TX complete\r\n");
}
/* Re-arm receive IT after an IBI completes (we aborted it before
* the IBI submit so the state machine would accept the IBI). */
if (need_rearm_rx) {
need_rearm_rx = 0;
memset(tgt_rx, 0, RX_BUF_SIZE);
tgt_xfer.RxBuf.pBuffer = tgt_rx;
tgt_xfer.RxBuf.Size = 4;
HAL_I3C_Tgt_Receive_IT(&hi3c1, &tgt_xfer);
printf("RX re-armed after IBI\r\n");
}
/* IWDG keepalive — kicked every main-loop iteration so that if
* the I3C state machine ever gets stuck in a no-progress loop
* (e.g., post-IBI BUSY state that never clears), the watchdog
* fires after WATCHDOG_TIMEOUT and chip B reboots cleanly. */
HAL_IWDG_Refresh(&hiwdg);
/* S7.2 IBI: USER button (B1, PC13, active-low — asserted when LOW)
* triggers an empty-payload IBI to the controller. Edge-detected
* with debounce; only allowed once we have a DA AND no IBI is
* in flight. */
{
static GPIO_PinState last_btn = GPIO_PIN_SET;
GPIO_PinState btn = HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13);
if (last_btn == GPIO_PIN_SET && btn == GPIO_PIN_RESET) {
HAL_Delay(20); /* debounce */
if (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_RESET
&& da_assigned
&& ibi_in_flight == 0) {
HAL_I3C_StateTypeDef st = HAL_I3C_GetState(&hi3c1);
ibi_in_flight = 1;
HAL_StatusTypeDef rc = HAL_I3C_Tgt_IBIReq_IT(&hi3c1, NULL, 0U);
printf("IBI request submitted (rc=0x%02X, count=%lu, prev_state=0x%02X)\r\n",
rc, ++ibi_count, st);
if (rc != HAL_OK) ibi_in_flight = 0;
}
}
last_btn = btn;
}
{
static uint32_t last_toggle = 0;
if (HAL_GetTick() - last_toggle >= 500)
{
BSP_LED_Toggle(LED_RED);
last_toggle = HAL_GetTick();
}
}
HAL_Delay(10);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE0);
while(!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {}
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_BYPASS;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLL1_SOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 4;
RCC_OscInitStruct.PLL.PLLN = 250;
RCC_OscInitStruct.PLL.PLLP = 2;
RCC_OscInitStruct.PLL.PLLQ = 2;
RCC_OscInitStruct.PLL.PLLR = 2;
RCC_OscInitStruct.PLL.PLLRGE = RCC_PLL1_VCIRANGE_1;
RCC_OscInitStruct.PLL.PLLVCOSEL = RCC_PLL1_VCORANGE_WIDE;
RCC_OscInitStruct.PLL.PLLFRACN = 0;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2
|RCC_CLOCKTYPE_PCLK3;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB3CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
{
Error_Handler();
}
/** Configure the programming delay
*/
__HAL_FLASH_SET_PROGRAM_DELAY(FLASH_PROGRAMMING_DELAY_2);
}
/**
* @brief I3C1 Initialization Function
* @param None
* @retval None
*/
static void MX_I3C1_Init(void)
{
/* USER CODE BEGIN I3C1_Init 0 */
/* USER CODE END I3C1_Init 0 */
I3C_FifoConfTypeDef sFifoConfig = {0};
I3C_TgtConfTypeDef sTgtConfig = {0};
/* USER CODE BEGIN I3C1_Init 1 */
/* USER CODE END I3C1_Init 1 */
hi3c1.Instance = I3C1;
hi3c1.Mode = HAL_I3C_MODE_TARGET;
hi3c1.Init.TgtBusCharacteristic.BusAvailableDuration = 0xf8;
if (HAL_I3C_Init(&hi3c1) != HAL_OK)
{
Error_Handler();
}
/** Configure FIFO
*/
sFifoConfig.RxFifoThreshold = HAL_I3C_RXFIFO_THRESHOLD_1_4;
sFifoConfig.TxFifoThreshold = HAL_I3C_TXFIFO_THRESHOLD_1_4;
sFifoConfig.ControlFifo = HAL_I3C_CONTROLFIFO_DISABLE;
sFifoConfig.StatusFifo = HAL_I3C_STATUSFIFO_DISABLE;
if (HAL_I3C_SetConfigFifo(&hi3c1, &sFifoConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure Target
*/
sTgtConfig.Identifier = 0;
sTgtConfig.MIPIIdentifier = 0;
sTgtConfig.CtrlRoleRequest = DISABLE;
sTgtConfig.HotJoinRequest = DISABLE;
sTgtConfig.IBIRequest = ENABLE; /* S7.2: target may issue In-Band Interrupts */
sTgtConfig.IBIPayload = DISABLE; /* keep payload empty for the simplest demo */
sTgtConfig.IBIPayloadSize = HAL_I3C_PAYLOAD_1_BYTE;
sTgtConfig.MaxReadDataSize = 0;
sTgtConfig.MaxWriteDataSize = 0;
sTgtConfig.CtrlCapability = DISABLE;
sTgtConfig.GroupAddrCapability = DISABLE;
sTgtConfig.DataTurnAroundDuration = HAL_I3C_TURNAROUND_TIME_TSCO_LESS_12NS;
sTgtConfig.MaxReadTurnAround = 0;
sTgtConfig.MaxDataSpeed = HAL_I3C_GETMXDS_FORMAT_1;
sTgtConfig.MaxSpeedLimitation = DISABLE;
sTgtConfig.HandOffActivityState = HAL_I3C_HANDOFF_ACTIVITY_STATE_0;
sTgtConfig.HandOffDelay = DISABLE;
sTgtConfig.PendingReadMDB = DISABLE;
if (HAL_I3C_Tgt_Config(&hi3c1, &sTgtConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN I3C1_Init 2 */
/* USER CODE END I3C1_Init 2 */
}
/**
* @brief ICACHE Initialization Function
* @param None
* @retval None
*/
static void MX_ICACHE_Init(void)
{
/* USER CODE BEGIN ICACHE_Init 0 */
/* USER CODE END ICACHE_Init 0 */
/* USER CODE BEGIN ICACHE_Init 1 */
/* USER CODE END ICACHE_Init 1 */
/** Enable instruction cache in 1-way (direct mapped cache)
*/
if (HAL_ICACHE_ConfigAssociativityMode(ICACHE_1WAY) != HAL_OK)
{
Error_Handler();
}
if (HAL_ICACHE_Enable() != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN ICACHE_Init 2 */
/* USER CODE END ICACHE_Init 2 */
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOE_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOF_CLK_ENABLE();
__HAL_RCC_GPIOH_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOG_CLK_ENABLE();
/*Configure GPIO pins : RMII_MDC_Pin RMII_RXD0_Pin RMII_RXD1_Pin */
GPIO_InitStruct.Pin = RMII_MDC_Pin|RMII_RXD0_Pin|RMII_RXD1_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF11_ETH;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
/*Configure GPIO pins : RMII_REF_CLK_Pin RMII_MDIO_Pin RMII_CRS_DV_Pin */
GPIO_InitStruct.Pin = RMII_REF_CLK_Pin|RMII_MDIO_Pin|RMII_CRS_DV_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF11_ETH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pin : VBUS_SENSE_Pin */
GPIO_InitStruct.Pin = VBUS_SENSE_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(VBUS_SENSE_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pins : UCPD_CC1_Pin UCPD_CC2_Pin */
GPIO_InitStruct.Pin = UCPD_CC1_Pin|UCPD_CC2_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/*Configure GPIO pin : RMII_TXD1_Pin */
GPIO_InitStruct.Pin = RMII_TXD1_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF11_ETH;
HAL_GPIO_Init(RMII_TXD1_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : UCPD_FLT_Pin */
GPIO_InitStruct.Pin = UCPD_FLT_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(UCPD_FLT_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pins : USB_FS_N_Pin USB_FS_P_Pin */
GPIO_InitStruct.Pin = USB_FS_N_Pin|USB_FS_P_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF10_USB;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pins : RMII_TXT_EN_Pin RMI_TXD0_Pin */
GPIO_InitStruct.Pin = RMII_TXT_EN_Pin|RMI_TXD0_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF11_ETH;
HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);
/*Configure GPIO pins : ARD_D1_TX_Pin ARD_D0_RX_Pin */
GPIO_InitStruct.Pin = ARD_D1_TX_Pin|ARD_D0_RX_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF8_LPUART1;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}
/* USER CODE BEGIN 4 */
void HAL_I3C_TgtRxCpltCallback(I3C_HandleTypeDef *hi3c)
{
tgt_rx_done = 1;
}
void HAL_I3C_TgtTxCpltCallback(I3C_HandleTypeDef *hi3c)
{
tgt_tx_done = 1;
}
void HAL_I3C_NotifyCallback(I3C_HandleTypeDef *hi3c, uint32_t eventId)
{
if (eventId & EVENT_ID_DAU)
{
got_daa = 1;
}
if (eventId & EVENT_ID_IBIEND)
{
ibi_in_flight = 0;
need_rearm_rx = 1; /* main loop re-arms the receive IT */
}
if (eventId & EVENT_ID_RSTACT)
{
/* I3C v1.1 Reset Action CCC. Set a flag for the main loop to act
* on — printf and NVIC_SystemReset are not safe to call from IRQ
* context (newlib printf is non-reentrant; HAL_Delay spins on
* SysTick which can't fire from inside another IRQ). */
I3C_CCCInfoTypeDef info = {0};
if (HAL_I3C_GetCCCInfo(hi3c, EVENT_ID_RSTACT, &info) == HAL_OK)
{
if (info.ResetAction == 0x02U) {
pending_reset_chip = 1;
} else if (info.ResetAction == 0x01U) {
pending_reset_i3c = 1;
}
}
}
}
void HAL_I3C_ErrorCallback(I3C_HandleTypeDef *hi3c)
{
printf("I3C Tgt Err: 0x%08lX\r\n", HAL_I3C_GetError(hi3c));
BSP_LED_On(LED_RED);
}
/* S8.3 — FDCAN MspInit override (weak in HAL). Configures FDCAN kernel
* clock from HSE, enables peripheral clock, sets PD0/PD1 as AF9_FDCAN1,
* arms the FDCAN1_IT0 NVIC. */
void HAL_FDCAN_MspInit(FDCAN_HandleTypeDef *hfdcan)
{
if (hfdcan->Instance != FDCAN1) return;
RCC_PeriphCLKInitTypeDef p = {0};
p.PeriphClockSelection = RCC_PERIPHCLK_FDCAN;
p.FdcanClockSelection = RCC_FDCANCLKSOURCE_HSE;
if (HAL_RCCEx_PeriphCLKConfig(&p) != HAL_OK) Error_Handler();
__HAL_RCC_FDCAN_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
GPIO_InitTypeDef g = {0};
g.Pin = GPIO_PIN_0 | GPIO_PIN_1;
g.Mode = GPIO_MODE_AF_PP;
g.Pull = GPIO_NOPULL;
g.Speed = GPIO_SPEED_FREQ_LOW;
g.Alternate = GPIO_AF9_FDCAN1;
HAL_GPIO_Init(GPIOD, &g);
HAL_NVIC_SetPriority(FDCAN1_IT0_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(FDCAN1_IT0_IRQn);
}
/* S8.3 — RX FIFO0 callback. Pulls each received frame, transmits an echo
* with Identifier += 0x100 and identical DLC/FDFormat/BRS/payload. The
* +0x100 offset means a sender at ID 0x300 hears its own echo at 0x400,
* which the controller-side test code uses to distinguish self-RX from
* the echoed reply. */
void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs)
{
if (hfdcan->Instance != FDCAN1) return;
if (!(RxFifo0ITs & FDCAN_IT_RX_FIFO0_NEW_MESSAGE)) return;
if (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0,
&echo_rx_hdr, echo_payload) != HAL_OK) {
echo_last_err = 0xE1;
} else {
echo_rx_count++;
echo_last_rx_id = echo_rx_hdr.Identifier;
echo_tx_hdr.Identifier = echo_rx_hdr.Identifier + 0x100;
echo_tx_hdr.IdType = echo_rx_hdr.IdType;
echo_tx_hdr.TxFrameType = FDCAN_DATA_FRAME;
echo_tx_hdr.DataLength = echo_rx_hdr.DataLength;
echo_tx_hdr.ErrorStateIndicator = FDCAN_ESI_ACTIVE;
echo_tx_hdr.BitRateSwitch = echo_rx_hdr.BitRateSwitch;
echo_tx_hdr.FDFormat = echo_rx_hdr.FDFormat;
echo_tx_hdr.TxEventFifoControl = FDCAN_NO_TX_EVENTS;
echo_tx_hdr.MessageMarker = 0;
if (HAL_FDCAN_AddMessageToTxFifoQ(hfdcan, &echo_tx_hdr, echo_payload) != HAL_OK) {
echo_last_err = 0xE2;
} else {
echo_tx_count++;
echo_last_tx_id = echo_tx_hdr.Identifier;
}
}
HAL_FDCAN_ActivateNotification(hfdcan, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0);
}
void FDCAN1_IT0_IRQHandler(void)
{
HAL_FDCAN_IRQHandler(&hfdcan1);
}
/* USER CODE END 4 */
/* MPU Configuration */
void MPU_Config(void)
{
MPU_Region_InitTypeDef MPU_InitStruct = {0};
MPU_Attributes_InitTypeDef MPU_AttributesInit = {0};
/* Disables the MPU */
HAL_MPU_Disable();
/** Initializes and configures the Region 0 and the memory to be protected
*/
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.BaseAddress = 0x08FFF000;
MPU_InitStruct.LimitAddress = 0x08FFFFFF;
MPU_InitStruct.AttributesIndex = MPU_ATTRIBUTES_NUMBER0;
MPU_InitStruct.AccessPermission = MPU_REGION_ALL_RO;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/** Initializes and configures the Attribute 0 and the memory to be protected
*/
MPU_AttributesInit.Number = MPU_ATTRIBUTES_NUMBER0;
MPU_AttributesInit.Attributes = INNER_OUTER(MPU_NOT_CACHEABLE);
HAL_MPU_ConfigMemoryAttributes(&MPU_AttributesInit);
/* Enables the MPU */
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}
/**
* @brief Period elapsed callback in non blocking mode
* @note This function is called when TIM6 interrupt took place, inside
* HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment
* a global variable "uwTick" used as application time base.
* @param htim : TIM handle
* @retval None
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
/* USER CODE BEGIN Callback 0 */
/* USER CODE END Callback 0 */
if (htim->Instance == TIM6)
{
HAL_IncTick();
}
/* USER CODE BEGIN Callback 1 */
/* USER CODE END Callback 1 */
}
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */