Files
feeder_mk2/code/Core/Src/main.c

2479 lines
68 KiB
C

/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 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 "photon_protocol.h"
#include <string.h>
#include <stdint.h>
#include "pid.h"
#include "crc.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define UM_PER_REV 127863 // pi * 40.7mm dia in um, per revolution
#define GEAR_RATIO 1030 // 1030 (gear ratio)
#define CNT_MAX 65535
#define CNT_LIMIT_ZONE 1000
#define ENCODER_PPR 7
#define ENCODER_CPR ENCODER_PPR *4
#define UUID_LENGTH 12 // 12 8bit values
#define PHOTON_NETWORK_CONTROLLER_ADDRESS 0x00
#define PHOTON_NETWORK_BROADCAST_ADDRESS 0xFF
#define PWM_MAX 2400
#define MAX_PWM_DIFFERENCE 10
#define PROTOCOL_VERSION 1
// Feed timing constants (from original firmware)
#define PEEL_TIME_PER_TENTH_MM 18 // ms per 0.1mm for forward peel
#define BACKWARDS_PEEL_TIME_PER_TENTH_MM 30 // ms per 0.1mm for backward unpeel
#define PEEL_BACKOFF_TIME 30 // ms to reverse peel after forward peel
#define TIMEOUT_TIME_PER_TENTH_MM 100 // ms per 0.1mm before timeout
#define BACKWARDS_FEED_FILM_SLACK_REMOVAL_TIME 350 // ms to take up slack after backward
#define BACKLASH_COMP_TENTH_MM 10 // 1mm backlash compensation
#define FEED_SETTLE_TIME 50 // ms to wait for position to settle
#define FEED_POSITION_TOLERANCE 10 // encoder counts tolerance (~0.044mm)
// OneWire timing (microseconds)
#define ONEWIRE_DELAY_A 6
#define ONEWIRE_DELAY_B 64
#define ONEWIRE_DELAY_C 60
#define ONEWIRE_DELAY_D 10
#define ONEWIRE_DELAY_E 9
#define ONEWIRE_DELAY_F 55
#define ONEWIRE_DELAY_G 0
#define ONEWIRE_DELAY_H 480
#define ONEWIRE_DELAY_I 70
#define ONEWIRE_DELAY_J 410
// DS2431 EEPROM commands
#define DS2431_SKIP_ROM 0xCC
#define DS2431_READ_MEMORY 0xF0
#define DS2431_WRITE_SCRATCHPAD 0x0F
#define DS2431_READ_SCRATCHPAD 0xAA
#define DS2431_COPY_SCRATCHPAD 0x55
// Floor address EEPROM location
#define FLOOR_ADDRESS_LOCATION 0x00
#define FLOOR_ADDRESS_NOT_PROGRAMMED 0x00
#define FLOOR_ADDRESS_NOT_DETECTED 0xFF
// Position overflow prevention
#define POSITION_OVERFLOW_THRESHOLD (INT32_MAX / 2)
#define POSITION_RESET_AMOUNT (INT32_MAX / 4)
// Feed state machine states
typedef enum {
FEED_STATE_IDLE = 0,
FEED_STATE_PEEL_FORWARD, // Forward feed: peeling film
FEED_STATE_PEEL_BACKOFF, // Forward feed: brief reverse peel
FEED_STATE_UNPEEL, // Backward feed: unspooling film
FEED_STATE_DRIVING, // Driving tape to target
FEED_STATE_SLACK_REMOVAL, // Backward feed: taking up slack
FEED_STATE_DRIVING_BACKLASH, // Backward feed: final forward approach
FEED_STATE_SETTLING, // Waiting for position to settle
FEED_STATE_COMPLETE,
FEED_STATE_TIMEOUT
} FeedState;
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
ADC_HandleTypeDef hadc1;
TIM_HandleTypeDef htim1;
TIM_HandleTypeDef htim3;
TIM_HandleTypeDef htim14;
TIM_HandleTypeDef htim16;
TIM_HandleTypeDef htim17;
UART_HandleTypeDef huart1;
UART_HandleTypeDef huart2;
DMA_HandleTypeDef hdma_usart2_rx;
DMA_HandleTypeDef hdma_usart2_tx;
/* USER CODE BEGIN PV */
uint8_t sw1_pressed,sw2_pressed = 0;
int32_t encoder_count_extra=0;
uint16_t encoder_previous=0;
uint8_t UUID[UUID_LENGTH];
uint8_t is_initialized=0;
uint8_t network_buffer_RX[64];
#define MSG_BUF_COUNT 54
volatile uint8_t msg_buf_empty[MSG_BUF_COUNT]; // initialized in code
volatile uint8_t msg_buf_size[MSG_BUF_COUNT] = {0};
uint8_t msg_buf[MSG_BUF_COUNT][64];
uint8_t DMA_buffer[64];
volatile uint16_t rx_msg_count = 0;
uint8_t my_address = 0xFF;
int32_t total_count = 0;
int32_t target_count = 0;
int32_t kp = 4;
int32_t ki = 1;
int32_t kd = 6;
int32_t i_min = -400;
int32_t i_max = 400;
int32_t pid_max_step = 30;
pid_i32_t motor_pid;
pid_motor_cmd_t motor_cmd;
uint8_t vendor_options[VENDOR_SPECIFIC_OPTIONS_LENGTH];
uint8_t feed_distance = 4;
int32_t pid_add = 0;
volatile int32_t brake_time_tenths = 30; // Adaptive braking in 0.1ms units (3.0ms initial)
uint8_t last_feed_status = STATUS_OK;
volatile uint8_t feed_in_progress = 0;
volatile uint8_t feed_just_completed = 0; // flag set by ISR when feed completes
volatile uint16_t feed_ok_count = 0;
volatile uint16_t feed_fail_count = 0;
volatile uint16_t feed_retry_total = 0;
// Feed state machine
volatile FeedState feed_state = FEED_STATE_IDLE;
uint32_t feed_state_start_time = 0; // when current state started
uint32_t feed_state_duration = 0; // how long current state should last
uint32_t feed_timeout_time = 0; // absolute timeout for driving phase
int16_t feed_distance_tenths = 0; // requested feed distance in 0.1mm
uint8_t feed_direction = 1; // 1 = forward, 0 = backward
int32_t feed_target_position = 0; // target encoder position for current phase
uint8_t feed_retry_count = 0; // retry counter
#define FEED_RETRY_LIMIT 3
// Version string
const char VERSION_STRING[] = "2.0.0-dev";
// Peel motor ramp state
#define PEEL_RAMP_TIME_MS 100
int16_t peel_target_pwm = 0; // Target: positive=fwd, negative=rev, 0=stop
int16_t peel_current_pwm = 0; // Current ramped value
uint32_t peel_last_ramp_time = 0;
// Button/driving state
uint8_t drive_mode = 0; // 0 = tape drive, 1 = film peel
uint8_t driving = 0; // currently in continuous drive mode
uint8_t driving_direction = 0; // 0 = backward, 1 = forward
uint8_t sw1_long_handled = 0; // flag to prevent repeated long press triggers
uint8_t sw2_long_handled = 0;
uint8_t both_pressed_handled = 0;
uint32_t both_pressed_start = 0; // timestamp for both-button hold timing
// Floor address (from EEPROM)
uint8_t floor_address = FLOOR_ADDRESS_NOT_DETECTED;
uint8_t floor_address_status = 0; // 0=not detected, 1=not programmed, 2=valid
// Position tracking for overflow prevention
int32_t mm_position = 0; // Position in tenths of mm
// Debug output
#define DEBUG_OUTPUT_INTERVAL_MS 100
uint32_t last_debug_output_time = 0;
uint8_t debug_enabled = 1;
char debug_tx_buffer[96];
volatile int16_t debug_pid_output = 0; // Captured from ISR for debug
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_TIM1_Init(void);
static void MX_TIM3_Init(void);
static void MX_USART1_UART_Init(void);
static void MX_USART2_UART_Init(void);
static void MX_TIM16_Init(void);
static void MX_TIM17_Init(void);
static void MX_TIM14_Init(void);
static void MX_ADC1_Init(void);
/* USER CODE BEGIN PFP */
void set_LED (uint8_t R, uint8_t G, uint8_t B);
void handleRS485Message(uint8_t *buffer, uint8_t size);
void set_Feeder_PWM(uint16_t PWM, uint8_t direction);
void update_Feeder_Target(int32_t difference);
void peel_motor(uint8_t forward);
void peel_brake(void);
void peel_ramp_update(void);
void drive_continuous(uint8_t forward);
void halt_all(void);
void identify_feeder(void);
void show_version(void);
// Feed state machine functions
void start_feed(int16_t distance_tenths, uint8_t forward);
void feed_state_machine_update(void);
void set_feed_target_counts(int16_t distance_tenths);
int32_t tenths_to_counts(int16_t tenths);
uint16_t calculate_expected_feed_time(uint8_t distance, uint8_t forward);
// Stall detection
// OneWire / EEPROM functions
void onewire_delay_us(uint32_t us);
void onewire_drive_low(void);
void onewire_release(void);
uint8_t onewire_read_bit(void);
uint8_t onewire_reset(void);
void onewire_write_bit(uint8_t bit);
void onewire_write_byte(uint8_t byte);
uint8_t onewire_read_byte(void);
uint8_t read_floor_address(void);
uint8_t write_floor_address(uint8_t address);
// Position management
void reset_position_if_needed(void);
void handle_vendor_options(uint8_t *options, uint8_t *response);
// Debug output (lightweight, no printf)
void debug_output(void);
uint8_t debug_itoa(int32_t val, char *buf);
uint8_t debug_hex8(uint8_t val, char *buf);
/* 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--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
pid_init(&motor_pid,kp,ki,kd,i_min,i_max,PWM_MAX,pid_max_step);
/* 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_DMA_Init();
MX_TIM1_Init();
MX_TIM3_Init();
MX_USART1_UART_Init();
MX_USART2_UART_Init();
MX_TIM16_Init();
MX_TIM17_Init();
MX_TIM14_Init();
MX_ADC1_Init();
/* USER CODE BEGIN 2 */
uint32_t * puuid = (uint32_t *)UUID;
*puuid = HAL_GetUIDw0();
*(puuid+1) = HAL_GetUIDw1();
*(puuid+2) = HAL_GetUIDw2();
// Start encoder timer (TIM3) in encoder mode
HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);
// Start PWM timer (TIM1) for motor control
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); // Feed motor
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2); // Feed motor
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3); // Peel motor
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_4); // Peel motor
// Start PID control timer (TIM14) with interrupt
HAL_TIM_Base_Start_IT(&htim14);
// Read floor address from EEPROM
floor_address = read_floor_address();
if (floor_address == FLOOR_ADDRESS_NOT_DETECTED)
{
floor_address_status = 0;
set_LED(1, 0, 0); // Red = EEPROM not detected
}
else if (floor_address == FLOOR_ADDRESS_NOT_PROGRAMMED)
{
floor_address_status = 1;
set_LED(0, 0, 1); // Blue = not programmed
}
else
{
floor_address_status = 2;
my_address = floor_address;
set_LED(0, 1, 0); // Green briefly = valid address
HAL_Delay(200);
set_LED(0, 0, 0);
}
for (uint8_t i = 0; i < MSG_BUF_COUNT; i++) msg_buf_empty[i] = 1;
HAL_UARTEx_ReceiveToIdle_DMA (&huart2,DMA_buffer,64);
__HAL_DMA_DISABLE_IT(huart2.hdmarx, DMA_IT_HT); // Disable half-transfer interrupt
__HAL_UART_ENABLE_IT(&huart2, UART_IT_ERR); // Enable error interrupt for overrun recovery
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
uint8_t sw1_state = HAL_GPIO_ReadPin(SW1_GPIO_Port, SW1_Pin); // 1 = released, 0 = pressed
uint8_t sw2_state = HAL_GPIO_ReadPin(SW2_GPIO_Port, SW2_Pin);
// Handle ongoing continuous drive - stop when button released
if (driving)
{
if ((driving_direction && sw2_state) || (!driving_direction && sw1_state))
{
halt_all();
driving = 0;
HAL_TIM_Base_Stop(&htim16);
HAL_TIM_Base_Stop(&htim17);
sw1_pressed = 0;
sw2_pressed = 0;
sw1_long_handled = 0;
sw2_long_handled = 0;
set_LED(0, 0, 0);
}
else if (!drive_mode)
{
// Tape mode: keep target ahead of current position
if (driving_direction)
target_count = total_count + 10000;
else
target_count = total_count - 10000;
}
}
else if (both_pressed_handled)
{
// Both-press mode: wait for release, handle long-hold actions
if (sw1_state && sw2_state)
{
// Both released - show mode color briefly then clear
both_pressed_handled = 0;
HAL_TIM_Base_Stop(&htim16);
HAL_TIM_Base_Stop(&htim17);
sw1_pressed = 0;
sw2_pressed = 0;
sw1_long_handled = 0;
sw2_long_handled = 0;
HAL_Delay(400);
set_LED(0, 0, 0);
}
else
{
// Still holding - check for long-hold actions
uint32_t hold_time = HAL_GetTick() - both_pressed_start;
if (hold_time > 2000 && hold_time < 2100)
{
show_version();
}
else if (hold_time > 4000 && hold_time < 6000)
{
set_LED((hold_time / 100) % 2, 0, !((hold_time / 100) % 2));
}
else if (hold_time >= 6000)
{
set_LED(1, 0, 1);
HAL_Delay(100);
NVIC_SystemReset();
}
}
}
else if (sw1_pressed || sw2_pressed)
{
// At least one button pressed - use decision window
uint16_t time1 = sw1_pressed ? htim16.Instance->CNT : 0;
uint16_t time2 = sw2_pressed ? htim17.Instance->CNT : 0;
uint16_t max_time = (time1 > time2) ? time1 : time2;
// Wait 100ms decision window before acting (unless already past it)
if (max_time < 100)
{
// Still in decision window - do nothing yet
}
else if (sw1_pressed && sw2_pressed && !sw1_state && !sw2_state)
{
// Both pressed - toggle mode
both_pressed_handled = 1;
both_pressed_start = HAL_GetTick();
if (drive_mode)
{
drive_mode = 0;
set_LED(0, 0, 1); // Blue = tape mode
}
else
{
drive_mode = 1;
set_LED(1, 1, 0); // Yellow = peel mode
}
}
else if (sw1_pressed && !sw2_pressed)
{
// Single SW1 handling
if (!sw1_state && time1 > 2000 && !sw1_long_handled)
{
sw1_long_handled = 1;
set_LED(1, 1, 1);
if (drive_mode)
peel_motor(0);
else
drive_continuous(0);
driving = 1;
driving_direction = 0;
}
else if (sw1_state && time1 <= 2000 && time1 > 100)
{
set_LED(1, 1, 1);
start_feed(20, 0);
HAL_TIM_Base_Stop(&htim16);
sw1_pressed = 0;
sw1_long_handled = 0;
}
else if (sw1_state)
{
HAL_TIM_Base_Stop(&htim16);
sw1_pressed = 0;
sw1_long_handled = 0;
if (!driving) set_LED(0, 0, 0);
}
}
else if (sw2_pressed && !sw1_pressed)
{
// Single SW2 handling
if (!sw2_state && time2 > 2000 && !sw2_long_handled)
{
sw2_long_handled = 1;
set_LED(1, 1, 1);
if (drive_mode)
peel_motor(1);
else
drive_continuous(1);
driving = 1;
driving_direction = 1;
}
else if (sw2_state && time2 <= 2000 && time2 > 100)
{
set_LED(1, 1, 1);
start_feed(20, 1);
HAL_TIM_Base_Stop(&htim17);
sw2_pressed = 0;
sw2_long_handled = 0;
}
else if (sw2_state)
{
HAL_TIM_Base_Stop(&htim17);
sw2_pressed = 0;
sw2_long_handled = 0;
if (!driving) set_LED(0, 0, 0);
}
}
else if (sw1_state && sw2_state)
{
// Both released without triggering both-press (one was released too fast)
HAL_TIM_Base_Stop(&htim16);
HAL_TIM_Base_Stop(&htim17);
sw1_pressed = 0;
sw2_pressed = 0;
sw1_long_handled = 0;
sw2_long_handled = 0;
}
}
// Update feed state machine
feed_state_machine_update();
// Ramp peel motor PWM
peel_ramp_update();
// Debug output via USART1
debug_output();
// Turn off LED when feed completes
if (feed_just_completed)
{
feed_just_completed = 0;
if (last_feed_status == STATUS_OK)
{
set_LED(0, 0, 0); // Success - LED off
// Reset position tracking to prevent overflow
reset_position_if_needed();
}
else
{
set_LED(1, 0, 0); // Error - LED red
}
}
for (uint8_t bi = 0; bi < MSG_BUF_COUNT; bi++)
{
if (!msg_buf_empty[bi])
{
handleRS485Message(msg_buf[bi], msg_buf_size[bi]);
msg_buf_size[bi] = 0;
msg_buf_empty[bi] = 1;
}
}
/* 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};
__HAL_FLASH_SET_LATENCY(FLASH_LATENCY_1);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSIDiv = RCC_HSI_DIV1;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
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_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
RCC_ClkInitStruct.SYSCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.AHBCLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_APB1_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief ADC1 Initialization Function
* @param None
* @retval None
*/
static void MX_ADC1_Init(void)
{
/* USER CODE BEGIN ADC1_Init 0 */
/* USER CODE END ADC1_Init 0 */
ADC_ChannelConfTypeDef sConfig = {0};
/* USER CODE BEGIN ADC1_Init 1 */
/* USER CODE END ADC1_Init 1 */
/** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
*/
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.ScanConvMode = ADC_SCAN_SEQ_FIXED;
hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
hadc1.Init.LowPowerAutoWait = DISABLE;
hadc1.Init.LowPowerAutoPowerOff = DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.NbrOfConversion = 1;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc1.Init.DMAContinuousRequests = DISABLE;
hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED;
hadc1.Init.SamplingTimeCommon1 = ADC_SAMPLETIME_1CYCLE_5;
hadc1.Init.OversamplingMode = DISABLE;
hadc1.Init.TriggerFrequencyMode = ADC_TRIGGER_FREQ_HIGH;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_7;
sConfig.Rank = ADC_RANK_CHANNEL_NUMBER;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_22;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN ADC1_Init 2 */
/* USER CODE END ADC1_Init 2 */
}
/**
* @brief TIM1 Initialization Function
* @param None
* @retval None
*/
static void MX_TIM1_Init(void)
{
/* USER CODE BEGIN TIM1_Init 0 */
/* USER CODE END TIM1_Init 0 */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
/* USER CODE BEGIN TIM1_Init 1 */
/* USER CODE END TIM1_Init 1 */
htim1.Instance = TIM1;
htim1.Init.Prescaler = 0;
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.Period = 2400;
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.RepetitionCounter = 0;
htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_Init(&htim1) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_3) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_4) != HAL_OK)
{
Error_Handler();
}
sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
sBreakDeadTimeConfig.DeadTime = 0;
sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
sBreakDeadTimeConfig.BreakFilter = 0;
sBreakDeadTimeConfig.BreakAFMode = TIM_BREAK_AFMODE_INPUT;
sBreakDeadTimeConfig.Break2State = TIM_BREAK2_DISABLE;
sBreakDeadTimeConfig.Break2Polarity = TIM_BREAK2POLARITY_HIGH;
sBreakDeadTimeConfig.Break2Filter = 0;
sBreakDeadTimeConfig.Break2AFMode = TIM_BREAK_AFMODE_INPUT;
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM1_Init 2 */
/* USER CODE END TIM1_Init 2 */
HAL_TIM_MspPostInit(&htim1);
}
/**
* @brief TIM3 Initialization Function
* @param None
* @retval None
*/
static void MX_TIM3_Init(void)
{
/* USER CODE BEGIN TIM3_Init 0 */
/* USER CODE END TIM3_Init 0 */
TIM_Encoder_InitTypeDef sConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
/* USER CODE BEGIN TIM3_Init 1 */
/* USER CODE END TIM3_Init 1 */
htim3.Instance = TIM3;
htim3.Init.Prescaler = 0;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 65535;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
sConfig.EncoderMode = TIM_ENCODERMODE_TI12;
sConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
sConfig.IC1Prescaler = TIM_ICPSC_DIV1;
sConfig.IC1Filter = 0;
sConfig.IC2Polarity = TIM_ICPOLARITY_RISING;
sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
sConfig.IC2Prescaler = TIM_ICPSC_DIV1;
sConfig.IC2Filter = 0;
if (HAL_TIM_Encoder_Init(&htim3, &sConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM3_Init 2 */
/* USER CODE END TIM3_Init 2 */
}
/**
* @brief TIM14 Initialization Function
* @param None
* @retval None
*/
static void MX_TIM14_Init(void)
{
/* USER CODE BEGIN TIM14_Init 0 */
/* USER CODE END TIM14_Init 0 */
/* USER CODE BEGIN TIM14_Init 1 */
/* USER CODE END TIM14_Init 1 */
htim14.Instance = TIM14;
htim14.Init.Prescaler = 480-1;
htim14.Init.CounterMode = TIM_COUNTERMODE_UP;
htim14.Init.Period = 50;
htim14.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim14.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim14) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM14_Init 2 */
/* USER CODE END TIM14_Init 2 */
}
/**
* @brief TIM16 Initialization Function
* @param None
* @retval None
*/
static void MX_TIM16_Init(void)
{
/* USER CODE BEGIN TIM16_Init 0 */
/* USER CODE END TIM16_Init 0 */
/* USER CODE BEGIN TIM16_Init 1 */
/* USER CODE END TIM16_Init 1 */
htim16.Instance = TIM16;
htim16.Init.Prescaler = 48000-1;
htim16.Init.CounterMode = TIM_COUNTERMODE_UP;
htim16.Init.Period = 65535;
htim16.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim16.Init.RepetitionCounter = 0;
htim16.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim16) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM16_Init 2 */
/* USER CODE END TIM16_Init 2 */
}
/**
* @brief TIM17 Initialization Function
* @param None
* @retval None
*/
static void MX_TIM17_Init(void)
{
/* USER CODE BEGIN TIM17_Init 0 */
/* USER CODE END TIM17_Init 0 */
/* USER CODE BEGIN TIM17_Init 1 */
/* USER CODE END TIM17_Init 1 */
htim17.Instance = TIM17;
htim17.Init.Prescaler = 48000-1;
htim17.Init.CounterMode = TIM_COUNTERMODE_UP;
htim17.Init.Period = 65535;
htim17.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim17.Init.RepetitionCounter = 0;
htim17.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim17) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM17_Init 2 */
/* USER CODE END TIM17_Init 2 */
}
/**
* @brief USART1 Initialization Function
* @param None
* @retval None
*/
static void MX_USART1_UART_Init(void)
{
/* USER CODE BEGIN USART1_Init 0 */
/* USER CODE END USART1_Init 0 */
/* USER CODE BEGIN USART1_Init 1 */
/* USER CODE END USART1_Init 1 */
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
huart1.Init.ClockPrescaler = UART_PRESCALER_DIV1;
huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_SetTxFifoThreshold(&huart1, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_SetRxFifoThreshold(&huart1, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_DisableFifoMode(&huart1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART1_Init 2 */
/* USER CODE END USART1_Init 2 */
}
/**
* @brief USART2 Initialization Function
* @param None
* @retval None
*/
static void MX_USART2_UART_Init(void)
{
/* USER CODE BEGIN USART2_Init 0 */
/* USER CODE END USART2_Init 0 */
/* USER CODE BEGIN USART2_Init 1 */
/* USER CODE END USART2_Init 1 */
huart2.Instance = USART2;
huart2.Init.BaudRate = 57600;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
huart2.Init.ClockPrescaler = UART_PRESCALER_DIV1;
huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if (HAL_RS485Ex_Init(&huart2, UART_DE_POLARITY_HIGH, 31, 31) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_SetTxFifoThreshold(&huart2, UART_TXFIFO_THRESHOLD_1_2) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_SetRxFifoThreshold(&huart2, UART_RXFIFO_THRESHOLD_1_2) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_DisableFifoMode(&huart2) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART2_Init 2 */
/* USER CODE END USART2_Init 2 */
}
/**
* Enable DMA controller clock
*/
static void MX_DMA_Init(void)
{
/* DMA controller clock enable */
__HAL_RCC_DMA1_CLK_ENABLE();
/* DMA interrupt init */
/* DMA1_Channel1_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
/* DMA1_Channel2_3_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Channel2_3_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel2_3_IRQn);
}
/**
* @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_GPIOF_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, USART2_NRE_Pin|ONEWIRE_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOB, LED_R_Pin|LED_B_Pin|LED_G_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin : USART2_NRE_Pin */
GPIO_InitStruct.Pin = USART2_NRE_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(USART2_NRE_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : ONEWIRE_Pin */
GPIO_InitStruct.Pin = ONEWIRE_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(ONEWIRE_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pins : LED_R_Pin LED_B_Pin LED_G_Pin */
GPIO_InitStruct.Pin = LED_R_Pin|LED_B_Pin|LED_G_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/*Configure GPIO pins : SW2_Pin SW1_Pin */
GPIO_InitStruct.Pin = SW2_Pin|SW1_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* EXTI interrupt init*/
HAL_NVIC_SetPriority(EXTI4_15_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI4_15_IRQn);
/* USER CODE BEGIN MX_GPIO_Init_2 */
// PEEL1/PEEL2 (PA2/PA3) are TIM1_CH3/CH4 - configured by MX_TIM1_Init
/* USER CODE END MX_GPIO_Init_2 */
}
/* USER CODE BEGIN 4 */
void HAL_TIM_PeriodElapsedCallback (TIM_HandleTypeDef * htim)
{
if (htim == &htim14) // encoder check timer (runs at 20khz)
{
uint16_t count = htim3.Instance->CNT;
if ((encoder_previous > (CNT_MAX-CNT_LIMIT_ZONE)) && (count < CNT_LIMIT_ZONE)) // positive turnaround
{
encoder_count_extra ++;
}
else if ((encoder_previous < CNT_LIMIT_ZONE) && (count > CNT_MAX-CNT_LIMIT_ZONE)) // negative turnaround
{
encoder_count_extra --;
}
encoder_previous = count; // update previous for next cycle
total_count = (encoder_count_extra * 65536) + count;
// Position overflow prevention - reset counts when they get too large
if (total_count > POSITION_OVERFLOW_THRESHOLD)
{
int32_t adjustment = POSITION_RESET_AMOUNT;
total_count -= adjustment;
target_count -= adjustment;
feed_target_position -= adjustment;
encoder_count_extra -= (adjustment / 65536);
mm_position = 0; // Reset mm tracking too
}
else if (total_count < -POSITION_OVERFLOW_THRESHOLD)
{
int32_t adjustment = POSITION_RESET_AMOUNT;
total_count += adjustment;
target_count += adjustment;
feed_target_position += adjustment;
encoder_count_extra += (adjustment / 65536);
mm_position = 0;
}
if (pid_add!=0)
{
int64_t temp = target_count + pid_add;
pid_add = 0;
if (temp < (INT32_MIN+10000))
{
//todo throw error
}
else if(temp > (INT32_MAX-10000))
{
//todo throw error
}
target_count = temp;
}
// Velocity-based predictive braking
{
// Measure velocity every 1ms (20 ISR cycles at 20kHz)
static int32_t vel_prev_pos = 0;
static uint8_t vel_counter = 0;
static int32_t velocity = 0; // counts per 1ms
vel_counter++;
if (vel_counter >= 20)
{
velocity = total_count - vel_prev_pos;
vel_prev_pos = total_count;
vel_counter = 0;
}
// Detect standstill: position unchanged for 2ms
static int32_t last_moved_pos = 0;
static uint16_t still_counter = 0;
if (total_count != last_moved_pos)
{
last_moved_pos = total_count;
still_counter = 0;
}
else if (still_counter < 1000)
{
still_counter++;
}
uint8_t is_stopped = still_counter > 40; // 40 * 50us = 2ms
int32_t error = target_count - total_count;
int32_t brake_distance = velocity * brake_time_tenths / 10;
if (error > FEED_POSITION_TOLERANCE && (is_stopped || error > brake_distance))
{
set_Feeder_PWM(PWM_MAX, 1); // Full forward
debug_pid_output = PWM_MAX;
}
else if (error < -FEED_POSITION_TOLERANCE)
{
// Significantly past target: reverse (retry backup)
set_Feeder_PWM(PWM_MAX, 0); // Full reverse
debug_pid_output = -PWM_MAX;
}
else
{
// Within tolerance: brake
htim1.Instance->CCR1 = PWM_MAX;
htim1.Instance->CCR2 = PWM_MAX;
debug_pid_output = 0;
}
}
// Note: Feed completion is now handled by feed_state_machine_update() in main loop
}
if (htim == &htim1) return; // PWM timer
else if (htim == &htim3) // encoder overflow
{
// will fire upon wraparound if update IT is enabled
}
if (htim == &htim16) //SW1 timer
{
sw1_pressed = 0;
//todo handle overflow after ~65seconds (48MHz / 48000) *
}
else if (htim == &htim17) //SW2 timer
{
//todo
sw2_pressed = 0;
}
}
void HAL_GPIO_EXTI_Falling_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == SW1_Pin) // SW1 (lower button)
{
if (!sw1_pressed)
{
htim16.Instance->CNT = 0;
HAL_TIM_Base_Start_IT(&htim16);
sw1_pressed = 1;
// now the main loop has to sample sw1_pressed and act. It can check how long its been pressed by reading TIM->CNT
// the main loop has to sample GPIO_IDR to check pin state if its still pressed to determine which function is to be called
}
}
else if (GPIO_Pin == SW2_Pin) // SW2 (upper button)
{
if (!sw2_pressed)
{
htim17.Instance->CNT = 0;
HAL_TIM_Base_Start_IT(&htim17);
sw2_pressed = 1;
}
}
}
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if (Size > 64) return; // todo error handling
rx_msg_count++;
for (uint8_t i = 0; i < MSG_BUF_COUNT; i++)
{
if (msg_buf_empty[i])
{
memcpy(msg_buf[i], DMA_buffer, Size);
msg_buf_size[i] = Size;
msg_buf_empty[i] = 0;
break;
}
}
// Always re-arm DMA — even if all buffers full, keep listening
HAL_UARTEx_ReceiveToIdle_DMA(&huart2, DMA_buffer, 64);
__HAL_DMA_DISABLE_IT(huart2.hdmarx, DMA_IT_HT);
}
volatile uint16_t uart_error_count = 0;
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART2)
{
uart_error_count++;
// Clear all error flags and re-arm DMA
__HAL_UART_CLEAR_FLAG(huart, UART_CLEAR_OREF | UART_CLEAR_FEF | UART_CLEAR_NEF | UART_CLEAR_PEF);
HAL_UARTEx_ReceiveToIdle_DMA(&huart2, DMA_buffer, 64);
__HAL_DMA_DISABLE_IT(huart2.hdmarx, DMA_IT_HT);
}
}
void set_LED (uint8_t R, uint8_t G, uint8_t B)
{
if (R) R = GPIO_PIN_SET;
if (G) G = GPIO_PIN_SET;
if (B) B = GPIO_PIN_SET;
HAL_GPIO_WritePin(LED_R_GPIO_Port,LED_R_Pin,R);
HAL_GPIO_WritePin(LED_G_GPIO_Port,LED_G_Pin,G);
HAL_GPIO_WritePin(LED_B_GPIO_Port,LED_B_Pin,B);
}
void rs485_transmit(uint8_t *data, uint16_t len)
{
// Turnaround delay — let controller switch to RX mode
HAL_Delay(1);
// Disable receiver, transmit, re-enable receiver, re-arm DMA
HAL_GPIO_WritePin(USART2_NRE_GPIO_Port, USART2_NRE_Pin, GPIO_PIN_SET); // NRE high = receiver off
HAL_UART_Transmit(&huart2, data, len, 100);
HAL_GPIO_WritePin(USART2_NRE_GPIO_Port, USART2_NRE_Pin, GPIO_PIN_RESET); // NRE low = receiver on
HAL_UARTEx_ReceiveToIdle_DMA(&huart2, DMA_buffer, 64);
__HAL_DMA_DISABLE_IT(huart2.hdmarx, DMA_IT_HT);
}
void comp_crc_header(CRC8_107 *lcrc, PhotonResponse *lresponse)
{
CRC8_107_add(lcrc,lresponse->header.toAddress);
CRC8_107_add(lcrc,lresponse->header.fromAddress);
CRC8_107_add(lcrc,lresponse->header.packetId);
CRC8_107_add(lcrc,lresponse->header.payloadLength);
}
volatile uint16_t drop_size = 0;
volatile uint16_t drop_crc = 0;
volatile uint16_t drop_addr = 0;
volatile uint16_t msg_handled = 0;
volatile uint8_t last_rx_size = 0;
volatile uint8_t last_rx_to = 0;
volatile uint8_t last_rx_cmd = 0;
volatile uint8_t last_rx_bytes[8] = {0};
volatile uint8_t my_addr_bytes[8] = {0}; // trap: last packet addressed to us
volatile uint8_t my_addr_size = 0;
volatile uint8_t my_addr_crc_exp = 0;
void handleRS485Message(uint8_t *buffer, uint8_t size)
{
PhotonPacketHeader *header = (PhotonPacketHeader *) buffer;
last_rx_size = size;
for (uint8_t i = 0; i < 8 && i < size; i++) last_rx_bytes[i] = buffer[i];
// Trap packets addressed to us (before any validation)
if (size >= 1 && buffer[0] == my_address)
{
my_addr_size = size;
for (uint8_t i = 0; i < 8 && i < size; i++) my_addr_bytes[i] = buffer[i];
}
// Validate minimum packet size
if (size < sizeof(PhotonPacketHeader) + 1) // header + at least commandId
{
drop_size++;
return; // packet too small
}
last_rx_to = header->toAddress;
// Validate CRC
CRC8_107 rx_crc;
CRC8_107_init(&rx_crc);
CRC8_107_add(&rx_crc, header->toAddress);
CRC8_107_add(&rx_crc, header->fromAddress);
CRC8_107_add(&rx_crc, header->packetId);
CRC8_107_add(&rx_crc, header->payloadLength);
// Add payload bytes to CRC (everything after header)
for (uint8_t i = 0; i < header->payloadLength; i++)
{
CRC8_107_add(&rx_crc, buffer[sizeof(PhotonPacketHeader) + i]);
}
if (CRC8_107_getChecksum(&rx_crc) != header->crc)
{
drop_crc++;
if (header->toAddress == my_address)
my_addr_crc_exp = CRC8_107_getChecksum(&rx_crc);
return; // CRC mismatch, discard packet
}
// check if message is for this device or is broadcast
if ((header->toAddress != PHOTON_NETWORK_BROADCAST_ADDRESS) &&
(header->toAddress != my_address))
{
drop_addr++;
return; // message not for us
}
// this message is relevant to this device (unicast to us or broadcast)
msg_handled++;
last_rx_cmd = buffer[sizeof(PhotonPacketHeader)]; // commandId
{
PhotonCommand *command = (PhotonCommand *) buffer;
PhotonResponse response;
CRC8_107 crc;
CRC8_107_init(&crc);
response.header.fromAddress = my_address;
response.header.packetId = command->header.packetId;
response.header.toAddress = command->header.fromAddress;
uint8_t *payload_ptr;
size_t packet_len;
switch (command->commandId)
{
case GET_FEEDER_ID:
memcpy(response.payload.getFeederId.uuid,UUID,UUID_LENGTH);
response.status = STATUS_OK;
response.header.payloadLength = sizeof(response.payload.getFeederId)+1; // +1 for status byte
comp_crc_header(&crc,&response);
payload_ptr =(uint8_t*) &response.status;
for (uint32_t i = 0; i<response.header.payloadLength; i++)
{
CRC8_107_add(&crc,*(payload_ptr+i));
}
response.header.crc = CRC8_107_getChecksum(&crc);
packet_len = sizeof(PhotonPacketHeader) + response.header.payloadLength;
rs485_transmit((uint8_t *)&response, packet_len);
break;
case INITIALIZE_FEEDER:
memcpy(response.payload.initializeFeeder.uuid,UUID,UUID_LENGTH);
if(memcmp(UUID,command->payload.initializeFeeder.uuid,UUID_LENGTH) == 0)
{
is_initialized = 1;
response.status = STATUS_OK;
}
else
{
response.status = STATUS_WRONG_FEEDER_ID;
}
response.header.payloadLength = sizeof(response.payload.initializeFeeder)+1;
comp_crc_header(&crc,&response);
payload_ptr =(uint8_t*) &response.status;
for (uint32_t i = 0; i<response.header.payloadLength; i++)
{
CRC8_107_add(&crc,*(payload_ptr+i));
}
response.header.crc = CRC8_107_getChecksum(&crc);
packet_len = sizeof(PhotonPacketHeader) + response.header.payloadLength;
rs485_transmit((uint8_t *)&response, packet_len);
break;
case GET_VERSION:
response.status = STATUS_OK;
response.payload.protocolVersion.version = PROTOCOL_VERSION;
response.header.payloadLength = sizeof(response.payload.protocolVersion)+1;
comp_crc_header(&crc,&response);
payload_ptr =(uint8_t*) &response.status;
for (uint32_t i = 0; i<response.header.payloadLength; i++)
{
CRC8_107_add(&crc,*(payload_ptr+i));
}
response.header.crc = CRC8_107_getChecksum(&crc);
packet_len = sizeof(PhotonPacketHeader) + response.header.payloadLength;
rs485_transmit((uint8_t *)&response, packet_len);
break;
case MOVE_FEED_FORWARD:
if (!is_initialized)
{
response.status = STATUS_UNINITIALIZED_FEEDER;
memcpy(response.payload.initializeFeeder.uuid, UUID, UUID_LENGTH);
response.header.payloadLength = sizeof(response.payload.initializeFeeder) + 1;
comp_crc_header(&crc, &response);
payload_ptr = (uint8_t*)&response.status;
for (uint32_t i = 0; i < response.header.payloadLength; i++)
{
CRC8_107_add(&crc, *(payload_ptr + i));
}
response.header.crc = CRC8_107_getChecksum(&crc);
packet_len = sizeof(PhotonPacketHeader) + response.header.payloadLength;
rs485_transmit((uint8_t *)&response, packet_len);
break;
}
{
uint16_t exp_time = calculate_expected_feed_time(command->payload.move.distance, 1);
uint16_t exp_time_be = (exp_time >> 8) | (exp_time << 8); // byte swap for network order
response.status = STATUS_OK;
response.payload.expectedTimeToFeed.expectedFeedTime = exp_time_be;
response.header.payloadLength = sizeof(response.payload.expectedTimeToFeed) + 1;
comp_crc_header(&crc, &response);
payload_ptr = (uint8_t*)&response.status;
for (uint32_t i = 0; i < response.header.payloadLength; i++)
{
CRC8_107_add(&crc, *(payload_ptr + i));
}
response.header.crc = CRC8_107_getChecksum(&crc);
packet_len = sizeof(PhotonPacketHeader) + response.header.payloadLength;
rs485_transmit((uint8_t *)&response, packet_len);
start_feed(command->payload.move.distance, 1);
}
break;
case MOVE_FEED_BACKWARD:
if (!is_initialized)
{
response.status = STATUS_UNINITIALIZED_FEEDER;
memcpy(response.payload.initializeFeeder.uuid, UUID, UUID_LENGTH);
response.header.payloadLength = sizeof(response.payload.initializeFeeder) + 1;
comp_crc_header(&crc, &response);
payload_ptr = (uint8_t*)&response.status;
for (uint32_t i = 0; i < response.header.payloadLength; i++)
{
CRC8_107_add(&crc, *(payload_ptr + i));
}
response.header.crc = CRC8_107_getChecksum(&crc);
packet_len = sizeof(PhotonPacketHeader) + response.header.payloadLength;
rs485_transmit((uint8_t *)&response, packet_len);
break;
}
{
uint16_t exp_time = calculate_expected_feed_time(command->payload.move.distance, 0);
uint16_t exp_time_be = (exp_time >> 8) | (exp_time << 8); // byte swap for network order
response.status = STATUS_OK;
response.payload.expectedTimeToFeed.expectedFeedTime = exp_time_be;
response.header.payloadLength = sizeof(response.payload.expectedTimeToFeed) + 1;
comp_crc_header(&crc, &response);
payload_ptr = (uint8_t*)&response.status;
for (uint32_t i = 0; i < response.header.payloadLength; i++)
{
CRC8_107_add(&crc, *(payload_ptr + i));
}
response.header.crc = CRC8_107_getChecksum(&crc);
packet_len = sizeof(PhotonPacketHeader) + response.header.payloadLength;
rs485_transmit((uint8_t *)&response, packet_len);
start_feed(command->payload.move.distance, 0);
}
break;
case MOVE_FEED_STATUS:
if (feed_in_progress)
{
response.status = STATUS_FEEDING_IN_PROGRESS;
}
else
{
response.status = last_feed_status;
}
response.header.payloadLength = 1; // only status byte
comp_crc_header(&crc,&response);
CRC8_107_add(&crc, response.status);
response.header.crc = CRC8_107_getChecksum(&crc);
packet_len = sizeof(PhotonPacketHeader) + response.header.payloadLength;
rs485_transmit((uint8_t *)&response, packet_len);
break;
case VENDOR_OPTIONS:
if (!is_initialized)
{
response.status = STATUS_UNINITIALIZED_FEEDER;
memcpy(response.payload.initializeFeeder.uuid, UUID, UUID_LENGTH);
response.header.payloadLength = sizeof(response.payload.initializeFeeder) + 1;
comp_crc_header(&crc, &response);
payload_ptr = (uint8_t*)&response.status;
for (uint32_t i = 0; i < response.header.payloadLength; i++)
{
CRC8_107_add(&crc, *(payload_ptr + i));
}
response.header.crc = CRC8_107_getChecksum(&crc);
packet_len = sizeof(PhotonPacketHeader) + response.header.payloadLength;
rs485_transmit((uint8_t *)&response, packet_len);
break;
}
handle_vendor_options(command->payload.vendorOptions.options, response.payload.vendorOptions.options);
response.status = STATUS_OK;
comp_crc_header(&crc,&response);
payload_ptr =(uint8_t*) &response.status;
for (uint32_t i = 0; i<sizeof(response.payload.vendorOptions)+1; i++)
{
CRC8_107_add(&crc,*(payload_ptr+i));
}
response.header.crc = CRC8_107_getChecksum(&crc);
response.header.payloadLength = sizeof(response.payload.vendorOptions)+1; // +1 for the status byte
packet_len = sizeof(PhotonPacketHeader) + response.header.payloadLength;
rs485_transmit((uint8_t *)&response, packet_len);
break;
case GET_FEEDER_ADDRESS:
if(memcmp(UUID,command->payload.getFeederAddress.uuid,UUID_LENGTH) == 0)
{
response.status = STATUS_OK;
}
else
{
return; // this makes no sense, but original code behaves like this
}
response.header.payloadLength = 1; // only status byte
comp_crc_header(&crc,&response);
CRC8_107_add(&crc,response.status);
response.header.crc = CRC8_107_getChecksum(&crc);
packet_len = sizeof(PhotonPacketHeader) + response.header.payloadLength;
rs485_transmit((uint8_t *)&response, packet_len);
break;
case IDENTIFY_FEEDER:
if(memcmp(UUID,command->payload.identifyFeeder.uuid,UUID_LENGTH) == 0)
{
response.status = STATUS_OK;
}
else
{
return; // UUID doesn't match, ignore
}
response.header.payloadLength = 1; // only status byte
comp_crc_header(&crc,&response);
CRC8_107_add(&crc,response.status);
response.header.crc = CRC8_107_getChecksum(&crc);
packet_len = sizeof(PhotonPacketHeader) + response.header.payloadLength;
rs485_transmit((uint8_t *)&response, packet_len);
identify_feeder();
break;
case PROGRAM_FEEDER_FLOOR:
{
uint8_t new_address = command->payload.programFeederFloorAddress.address;
uint8_t write_success = write_floor_address(new_address);
if (write_success)
{
my_address = new_address;
floor_address = new_address;
floor_address_status = 2;
response.status = STATUS_OK;
}
else
{
response.status = STATUS_FAIL;
}
response.header.payloadLength = 1; // only status byte
comp_crc_header(&crc, &response);
CRC8_107_add(&crc, response.status);
response.header.crc = CRC8_107_getChecksum(&crc);
packet_len = sizeof(PhotonPacketHeader) + response.header.payloadLength;
rs485_transmit((uint8_t *)&response, packet_len);
}
break;
case UNINITIALIZED_FEEDERS_RESPOND:
if (is_initialized) return;
memcpy(response.payload.getFeederId.uuid,UUID,UUID_LENGTH);
response.status=STATUS_OK;
response.header.payloadLength = sizeof(response.payload.getFeederId)+1;
comp_crc_header(&crc,&response);
payload_ptr =(uint8_t*) &response.status;
for (uint32_t i = 0; i<response.header.payloadLength; i++)
{
CRC8_107_add(&crc,*(payload_ptr+i));
}
response.header.crc = CRC8_107_getChecksum(&crc);
packet_len = sizeof(PhotonPacketHeader) + response.header.payloadLength;
rs485_transmit((uint8_t *)&response, packet_len);
break;
default:
// todo error handling
return;
break;
}
}
}
void update_Feeder_Target(int32_t difference)
{
int64_t temp = (difference * 1000 * GEAR_RATIO * ENCODER_CPR) / UM_PER_REV;
if (temp == 0)
{
//todo error
return;
}
else if (temp > INT32_MAX/2)
{
//todo error
return;
}
else if (temp < INT32_MIN/2)
{
//todo error
return;
}
else
{
pid_add += temp;
return;
}
}
void set_Feeder_PWM(uint16_t PWM, uint8_t direction)
{
if (direction)
{
htim1.Instance->CCR1 = PWM;
htim1.Instance->CCR2 = 0;
}
else
{
htim1.Instance->CCR1 = 0;
htim1.Instance->CCR2 = PWM;
}
}
void peel_motor(uint8_t forward)
{
peel_target_pwm = forward ? PWM_MAX : -PWM_MAX;
}
void peel_brake(void)
{
peel_target_pwm = 0;
}
void peel_ramp_update(void)
{
uint32_t now = HAL_GetTick();
uint32_t dt = now - peel_last_ramp_time;
if (dt == 0) return;
peel_last_ramp_time = now;
if (peel_current_pwm == peel_target_pwm) return;
// Step size: full range (PWM_MAX) in PEEL_RAMP_TIME_MS
int16_t step = (int16_t)((int32_t)PWM_MAX * dt / PEEL_RAMP_TIME_MS);
if (step < 1) step = 1;
if (peel_target_pwm > peel_current_pwm)
{
peel_current_pwm += step;
if (peel_current_pwm > peel_target_pwm)
peel_current_pwm = peel_target_pwm;
}
else
{
peel_current_pwm -= step;
if (peel_current_pwm < peel_target_pwm)
peel_current_pwm = peel_target_pwm;
}
// Apply to TIM1 CH3/CH4
if (peel_current_pwm > 0)
{
htim1.Instance->CCR3 = peel_current_pwm;
htim1.Instance->CCR4 = 0;
}
else if (peel_current_pwm < 0)
{
htim1.Instance->CCR3 = 0;
htim1.Instance->CCR4 = -peel_current_pwm;
}
else
{
htim1.Instance->CCR3 = 0;
htim1.Instance->CCR4 = 0;
}
}
void drive_continuous(uint8_t forward)
{
// Set target far away so motor drives at full speed
if (forward)
target_count = total_count + 10000;
else
target_count = total_count - 10000;
}
void halt_all(void)
{
// Stop drive motor (brake)
htim1.Instance->CCR1 = PWM_MAX;
htim1.Instance->CCR2 = PWM_MAX;
// Stop peel motor immediately
peel_target_pwm = 0;
peel_current_pwm = 0;
htim1.Instance->CCR3 = 0;
htim1.Instance->CCR4 = 0;
// Set target to current position to prevent further movement
target_count = total_count;
pid_add = 0;
}
void identify_feeder(void)
{
// Flash LED white 3 times
for (int i = 0; i < 3; i++)
{
set_LED(1, 1, 1);
HAL_Delay(300);
set_LED(0, 0, 0);
HAL_Delay(300);
}
}
void show_version(void)
{
// Flash green for release version
set_LED(0, 1, 0);
HAL_Delay(250);
set_LED(0, 0, 0);
HAL_Delay(250);
}
// Convert distance in tenths of mm to encoder counts
int32_t tenths_to_counts(int16_t tenths)
{
// counts = (tenths * 100 * GEAR_RATIO * ENCODER_CPR) / UM_PER_REV
// 1 tenth-mm = 100um
int64_t temp = ((int64_t)tenths * 100 * GEAR_RATIO * ENCODER_CPR) / UM_PER_REV;
return (int32_t)temp;
}
// Calculate expected feed time in milliseconds
uint16_t calculate_expected_feed_time(uint8_t distance, uint8_t forward)
{
if (forward)
{
// Forward: peel time + peel backoff + drive time + extra for first feed
uint16_t time = (distance * PEEL_TIME_PER_TENTH_MM) +
PEEL_BACKOFF_TIME +
(distance * TIMEOUT_TIME_PER_TENTH_MM) + 200;
return time;
}
else
{
// Backward: unpeel + drive (with backlash) + slack removal
return (distance * BACKWARDS_PEEL_TIME_PER_TENTH_MM) +
((distance + (BACKLASH_COMP_TENTH_MM * 2)) * TIMEOUT_TIME_PER_TENTH_MM) +
BACKWARDS_FEED_FILM_SLACK_REMOVAL_TIME + 50;
}
}
// Start a feed operation
void start_feed(int16_t distance_tenths, uint8_t forward)
{
if (feed_state != FEED_STATE_IDLE)
{
return; // Already feeding
}
feed_distance_tenths = distance_tenths;
feed_direction = forward;
feed_retry_count = 0;
feed_in_progress = 1;
last_feed_status = STATUS_OK;
set_LED(1, 1, 1); // White during feed
if (forward)
{
// Forward feed: drive both motors simultaneously
feed_state = FEED_STATE_PEEL_FORWARD;
feed_state_start_time = HAL_GetTick();
feed_state_duration = distance_tenths * PEEL_TIME_PER_TENTH_MM;
peel_motor(1); // Peel forward
// Start feed motor at the same time
feed_timeout_time = HAL_GetTick() + (distance_tenths * TIMEOUT_TIME_PER_TENTH_MM) + 500;
feed_target_position = total_count + tenths_to_counts(distance_tenths);
target_count = feed_target_position;
}
else
{
// Backward feed: start with unpeeling to give slack
feed_state = FEED_STATE_UNPEEL;
feed_state_start_time = HAL_GetTick();
feed_state_duration = distance_tenths * BACKWARDS_PEEL_TIME_PER_TENTH_MM;
peel_motor(0); // Peel backward (unpeel)
}
}
// Update feed state machine - called from main loop
void feed_state_machine_update(void)
{
if (feed_state == FEED_STATE_IDLE)
{
return;
}
uint32_t now = HAL_GetTick();
uint32_t elapsed = now - feed_state_start_time;
switch (feed_state)
{
case FEED_STATE_PEEL_FORWARD:
// Peeling film while feed motor drives simultaneously
if (elapsed >= feed_state_duration)
{
peel_brake(); // Peel done, feed motor continues via PID
feed_state = FEED_STATE_DRIVING;
feed_state_start_time = now;
// feed_target_position and feed_timeout_time already set in start_feed
}
break;
case FEED_STATE_UNPEEL:
// Unpeeling film before backward drive
if (elapsed >= feed_state_duration)
{
peel_brake();
// Start driving backward with backlash overshoot
feed_state = FEED_STATE_DRIVING;
feed_state_start_time = now;
int16_t total_backward = feed_distance_tenths + BACKLASH_COMP_TENTH_MM;
feed_timeout_time = now + (total_backward * TIMEOUT_TIME_PER_TENTH_MM) + 500;
feed_target_position = total_count - tenths_to_counts(total_backward);
target_count = feed_target_position;
}
break;
case FEED_STATE_DRIVING:
// Check for position reached, overshoot, and timeout
{
int32_t error = feed_target_position - total_count;
int32_t abs_error = error < 0 ? -error : error;
// Brake time adjustment based on error magnitude
// <5: none, >=5: ±5, >=10: ±10, >=20: ±20
int32_t brake_adj = abs_error >= 20 ? 20 : abs_error >= 10 ? 10 : abs_error >= 5 ? 5 : 0;
if (abs_error < FEED_POSITION_TOLERANCE)
{
// Position reached - fine-tune brake time based on where we stopped
if (error < 0) // overshot
brake_time_tenths += brake_adj;
else if (error > 0 && brake_time_tenths > brake_adj) // undershot
brake_time_tenths -= brake_adj;
else if (error > 0)
brake_time_tenths = 1;
if (!feed_direction)
{
// Backward feed: take up slack then do final approach
feed_state = FEED_STATE_SLACK_REMOVAL;
feed_state_start_time = now;
feed_state_duration = BACKWARDS_FEED_FILM_SLACK_REMOVAL_TIME;
peel_motor(1); // Peel forward to take up slack
}
else
{
// Forward feed complete
feed_state = FEED_STATE_SETTLING;
feed_state_start_time = now;
feed_state_duration = FEED_SETTLE_TIME;
}
}
else if (error < -FEED_POSITION_TOLERANCE)
{
// Overshot - increase braking time, back up 1mm and re-approach
brake_time_tenths += brake_adj;
feed_retry_total++;
target_count = total_count - tenths_to_counts(10);
HAL_Delay(200);
htim1.Instance->CCR1 = PWM_MAX;
htim1.Instance->CCR2 = PWM_MAX;
HAL_Delay(50);
target_count = feed_target_position;
feed_state_start_time = now;
feed_timeout_time = HAL_GetTick() + (feed_distance_tenths * TIMEOUT_TIME_PER_TENTH_MM) + 500;
}
else if (now > feed_timeout_time)
{
// Fell short - decrease braking time, back up 1mm and re-approach
if (brake_time_tenths > brake_adj) brake_time_tenths -= brake_adj;
else brake_time_tenths = 1;
feed_retry_total++;
target_count = total_count - tenths_to_counts(10);
HAL_Delay(200);
htim1.Instance->CCR1 = PWM_MAX;
htim1.Instance->CCR2 = PWM_MAX;
HAL_Delay(50);
target_count = feed_target_position;
feed_state_start_time = now;
feed_timeout_time = HAL_GetTick() + (feed_distance_tenths * TIMEOUT_TIME_PER_TENTH_MM) + 500;
}
}
break;
case FEED_STATE_SLACK_REMOVAL:
// Taking up film slack after backward movement
if (elapsed >= feed_state_duration)
{
peel_brake();
// Final forward approach for backlash compensation
feed_state = FEED_STATE_DRIVING_BACKLASH;
feed_state_start_time = now;
feed_timeout_time = now + (BACKLASH_COMP_TENTH_MM * TIMEOUT_TIME_PER_TENTH_MM) + 200;
// Move forward by backlash amount to final position
feed_target_position = total_count + tenths_to_counts(BACKLASH_COMP_TENTH_MM);
target_count = feed_target_position;
}
break;
case FEED_STATE_DRIVING_BACKLASH:
// Final forward approach after backward feed
{
int32_t error = feed_target_position - total_count;
if (error < 0) error = -error;
if (error < FEED_POSITION_TOLERANCE)
{
feed_state = FEED_STATE_SETTLING;
feed_state_start_time = now;
feed_state_duration = FEED_SETTLE_TIME;
}
else if (now > feed_timeout_time)
{
feed_state = FEED_STATE_TIMEOUT;
}
}
break;
case FEED_STATE_SETTLING:
// Wait for position to settle
if (elapsed >= feed_state_duration)
{
feed_state = FEED_STATE_COMPLETE;
}
break;
case FEED_STATE_COMPLETE:
feed_state = FEED_STATE_IDLE;
feed_in_progress = 0;
last_feed_status = STATUS_OK;
feed_just_completed = 1;
feed_ok_count++;
break;
case FEED_STATE_TIMEOUT:
// Handle timeout - back up 1mm to clear backlash, then retry
if (feed_retry_count < FEED_RETRY_LIMIT)
{
feed_retry_count++;
feed_retry_total++;
// Back up 1mm (10 tenths) to overcome backlash
target_count = total_count - tenths_to_counts(10);
HAL_Delay(200); // Let motor reverse past backlash
// Brake and re-approach original target
htim1.Instance->CCR1 = PWM_MAX;
htim1.Instance->CCR2 = PWM_MAX;
HAL_Delay(50); // Settle
target_count = feed_target_position;
feed_state = FEED_STATE_DRIVING;
feed_state_start_time = now;
feed_timeout_time = HAL_GetTick() + (feed_distance_tenths * TIMEOUT_TIME_PER_TENTH_MM) + 500;
}
else
{
// Retry limit exceeded
feed_state = FEED_STATE_IDLE;
feed_in_progress = 0;
last_feed_status = STATUS_COULDNT_REACH;
feed_just_completed = 1;
feed_fail_count++;
halt_all();
}
break;
default:
feed_state = FEED_STATE_IDLE;
break;
}
}
// Handle vendor-specific options
void handle_vendor_options(uint8_t *options, uint8_t *response)
{
uint8_t command = options[0];
// Commands 0x00-0x0F: Get firmware version string in chunks
if (command <= 0x0F)
{
size_t version_len = strlen(VERSION_STRING);
size_t start_index = command * VENDOR_SPECIFIC_OPTIONS_LENGTH;
if (start_index < version_len)
{
size_t remaining = version_len - start_index;
size_t copy_len = (remaining < VENDOR_SPECIFIC_OPTIONS_LENGTH) ? remaining : VENDOR_SPECIFIC_OPTIONS_LENGTH;
memcpy(response, VERSION_STRING + start_index, copy_len);
// Null-terminate if there's room
if (copy_len < VENDOR_SPECIFIC_OPTIONS_LENGTH)
{
response[copy_len] = '\0';
}
}
else
{
memset(response, 0, VENDOR_SPECIFIC_OPTIONS_LENGTH);
}
return;
}
switch (command)
{
case 0x10:
{
// LED control
uint8_t led_mask = options[1];
int blue = led_mask & 1;
int green = (led_mask >> 1) & 1;
int red = (led_mask >> 2) & 1;
int set = (led_mask >> 3) & 1;
if (set)
{
set_LED(red, green, blue);
}
break;
}
default:
// Unknown command, return zeros
memset(response, 0, VENDOR_SPECIFIC_OPTIONS_LENGTH);
break;
}
}
// ============================================================================
// OneWire Functions (Bit-banging for DS2431 EEPROM)
// ============================================================================
// Microsecond delay using SysTick (cycle-accurate at 48MHz)
void onewire_delay_us(uint32_t us)
{
uint32_t cycles = us * 48;
uint32_t reload = SysTick->LOAD;
uint32_t prev = SysTick->VAL;
uint32_t elapsed = 0;
while (elapsed < cycles)
{
uint32_t now = SysTick->VAL;
if (now <= prev)
elapsed += prev - now;
else
elapsed += prev + reload + 1 - now;
prev = now;
}
}
// Pin is always open-drain: SET = release (high-Z), RESET = drive low
// Reading IDR works regardless since it's open-drain
void onewire_drive_low(void)
{
ONEWIRE_GPIO_Port->BRR = ONEWIRE_Pin; // Direct register for speed
}
void onewire_release(void)
{
ONEWIRE_GPIO_Port->BSRR = ONEWIRE_Pin; // Direct register for speed
}
uint8_t onewire_read_bit(void)
{
return (ONEWIRE_GPIO_Port->IDR & ONEWIRE_Pin) ? 1 : 0;
}
// Reset pulse - returns 1 if device present, 0 if not
uint8_t onewire_reset(void)
{
uint8_t presence;
onewire_drive_low();
onewire_delay_us(ONEWIRE_DELAY_H); // 480us
onewire_release();
onewire_delay_us(ONEWIRE_DELAY_I); // 70us
presence = !onewire_read_bit(); // Device pulls low if present
onewire_delay_us(ONEWIRE_DELAY_J); // 410us
return presence;
}
void onewire_write_bit(uint8_t bit)
{
if (bit)
{
// Write 1: pull low briefly, then release
onewire_drive_low();
onewire_delay_us(ONEWIRE_DELAY_A); // 6us
onewire_release();
onewire_delay_us(ONEWIRE_DELAY_B); // 64us
}
else
{
// Write 0: pull low for full slot
onewire_drive_low();
onewire_delay_us(ONEWIRE_DELAY_C); // 60us
onewire_release();
onewire_delay_us(ONEWIRE_DELAY_D); // 10us
}
}
uint8_t onewire_read_bit_slot(void)
{
uint8_t bit;
onewire_drive_low();
onewire_delay_us(ONEWIRE_DELAY_A); // 6us
onewire_release();
onewire_delay_us(ONEWIRE_DELAY_E); // 9us
bit = onewire_read_bit();
onewire_delay_us(ONEWIRE_DELAY_F); // 55us
return bit;
}
void onewire_write_byte(uint8_t byte)
{
for (int i = 0; i < 8; i++)
{
onewire_write_bit(byte & 0x01);
byte >>= 1;
}
}
uint8_t onewire_read_byte(void)
{
uint8_t byte = 0;
for (int i = 0; i < 8; i++)
{
byte >>= 1;
if (onewire_read_bit_slot())
{
byte |= 0x80;
}
}
return byte;
}
// Read floor address from DS2431 EEPROM
uint8_t read_floor_address(void)
{
// Disable interrupts for timing-critical OneWire
__disable_irq();
if (!onewire_reset())
{
__enable_irq();
return FLOOR_ADDRESS_NOT_DETECTED;
}
// Skip ROM (only one device on bus)
onewire_write_byte(DS2431_SKIP_ROM);
// Read Memory command
onewire_write_byte(DS2431_READ_MEMORY);
// Address (2 bytes, LSB first)
onewire_write_byte(FLOOR_ADDRESS_LOCATION & 0xFF);
onewire_write_byte((FLOOR_ADDRESS_LOCATION >> 8) & 0xFF);
// Read the data byte
uint8_t address = onewire_read_byte();
__enable_irq();
return address;
}
// Write floor address to DS2431 EEPROM
uint8_t write_floor_address(uint8_t address)
{
// Disable interrupts for timing-critical OneWire
__disable_irq();
if (!onewire_reset())
{
__enable_irq();
return 0; // Device not present
}
// Skip ROM
onewire_write_byte(DS2431_SKIP_ROM);
// Write Scratchpad command
onewire_write_byte(DS2431_WRITE_SCRATCHPAD);
// Address (2 bytes, LSB first)
onewire_write_byte(FLOOR_ADDRESS_LOCATION & 0xFF);
onewire_write_byte((FLOOR_ADDRESS_LOCATION >> 8) & 0xFF);
// Write the data (8 bytes for DS2431, we only need 1)
onewire_write_byte(address);
for (int i = 1; i < 8; i++)
{
onewire_write_byte(0xFF); // Pad with 0xFF
}
// Small delay
onewire_delay_us(100);
// Reset for next command
if (!onewire_reset())
{
__enable_irq();
return 0;
}
// Skip ROM
onewire_write_byte(DS2431_SKIP_ROM);
// Read Scratchpad to get authorization bytes
onewire_write_byte(DS2431_READ_SCRATCHPAD);
uint8_t ta1 = onewire_read_byte(); // Target address 1
uint8_t ta2 = onewire_read_byte(); // Target address 2
uint8_t es = onewire_read_byte(); // E/S register
// Read back data to verify (8 bytes)
uint8_t verify = onewire_read_byte();
if (verify != address)
{
__enable_irq();
return 0; // Scratchpad write failed
}
// Skip remaining bytes
for (int i = 1; i < 8; i++)
{
onewire_read_byte();
}
// Reset for copy command
if (!onewire_reset())
{
__enable_irq();
return 0;
}
// Skip ROM
onewire_write_byte(DS2431_SKIP_ROM);
// Copy Scratchpad command
onewire_write_byte(DS2431_COPY_SCRATCHPAD);
// Send authorization bytes
onewire_write_byte(ta1);
onewire_write_byte(ta2);
onewire_write_byte(es);
__enable_irq();
// Wait for copy to complete (10ms typical for EEPROM)
HAL_Delay(15);
// Verify by reading back
uint8_t read_back = read_floor_address();
return (read_back == address) ? 1 : 0;
}
// ============================================================================
// Debug Output Functions (lightweight, no printf/snprintf)
// ============================================================================
// Convert int32 to decimal string, returns length written
uint8_t debug_itoa(int32_t val, char *buf)
{
char tmp[12];
uint8_t i = 0, len = 0;
uint8_t neg = 0;
if (val < 0) { neg = 1; val = -val; }
if (val == 0) { tmp[i++] = '0'; }
else { while (val > 0) { tmp[i++] = '0' + (val % 10); val /= 10; } }
if (neg) { *buf++ = '-'; len++; }
while (i > 0) { *buf++ = tmp[--i]; len++; }
return len;
}
// Convert uint8 to 2-digit hex, returns 2
uint8_t debug_hex8(uint8_t val, char *buf)
{
const char hex[] = "0123456789ABCDEF";
buf[0] = hex[(val >> 4) & 0x0F];
buf[1] = hex[val & 0x0F];
return 2;
}
void debug_output(void)
{
if (!debug_enabled) return;
uint32_t now = HAL_GetTick();
if ((now - last_debug_output_time) < DEBUG_OUTPUT_INTERVAL_MS) return;
last_debug_output_time = now;
// Format: $P:pos:tgt:err,I:pid,S:state,F:flags,A:addr,D:drv*\r\n
char *p = debug_tx_buffer;
*p++ = '$'; *p++ = 'P'; *p++ = ':';
p += debug_itoa(total_count, p);
*p++ = ':';
p += debug_itoa(target_count, p);
*p++ = ':';
p += debug_itoa(target_count - total_count, p);
*p++ = ','; *p++ = 'I'; *p++ = ':';
p += debug_itoa(debug_pid_output, p);
*p++ = ','; *p++ = 'S'; *p++ = ':';
*p++ = '0' + feed_state; // State as single digit 0-9
*p++ = ','; *p++ = 'F'; *p++ = ':';
*p++ = '0' + is_initialized;
*p++ = '0' + feed_in_progress;
*p++ = ','; *p++ = 'C'; *p++ = ':';
p += debug_itoa(feed_ok_count, p);
*p++ = ':';
p += debug_itoa(feed_fail_count, p);
*p++ = ':';
p += debug_itoa(brake_time_tenths, p);
*p++ = ':';
p += debug_itoa(feed_retry_total, p);
*p++ = ','; *p++ = 'A'; *p++ = ':';
p += debug_hex8(my_address, p);
*p++ = ','; *p++ = 'R'; *p++ = ':';
p += debug_itoa(drop_crc, p);
*p++ = ':';
p += debug_itoa(msg_handled, p);
*p++ = ':';
p += debug_itoa(uart_error_count, p);
*p++ = ','; *p++ = 'T'; *p++ = ':';
p += debug_itoa(my_addr_size, p);
*p++ = ':';
for (uint8_t i = 0; i < 8 && i < my_addr_size; i++)
p += debug_hex8(my_addr_bytes[i], p);
*p++ = ':';
p += debug_hex8(my_addr_crc_exp, p);
*p++ = '*'; *p++ = '\r'; *p++ = '\n';
HAL_UART_Transmit(&huart1, (uint8_t*)debug_tx_buffer, p - debug_tx_buffer, 10);
}
// ============================================================================
// Position Management
// ============================================================================
void reset_position_if_needed(void)
{
// This is called from main loop periodically or after feed completes
// to reset position tracking and prevent overflow
// Only reset when position is at a "round" value to avoid drift
int32_t counts_per_mm = tenths_to_counts(10); // counts per 1mm
// Check if we're at a position that's a multiple of 1mm
if ((total_count % counts_per_mm) == 0 && feed_state == FEED_STATE_IDLE)
{
// Calculate how many mm we've moved
int32_t mm_moved = total_count / counts_per_mm;
mm_position += mm_moved;
// Reset encoder tracking
__disable_irq();
encoder_count_extra = 0;
htim3.Instance->CNT = 0;
encoder_previous = 0;
total_count = 0;
target_count = 0;
feed_target_position = 0;
__enable_irq();
}
}
/* USER CODE END 4 */
/**
* @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 */