/**
* @file driver_adc_joystick_test.c
* @brief Blocking helpers for sampling the dual-axis analog joystick plus key.
* @version 1.0.0
* @date 2025-10-19
*
* The module offers three utilities:
* 1. Capture raw and permille-scaled readings for the X/Y axes.
* 2. Print formatted telemetry frames for manual tuning.
* 3. Convert joystick deflection into keypad-style UART codes (4/6/8/5).
*/
#include "driver_adc_joystick_test.h"
#include <stdio.h>
#include "gpio.h"
/** @brief Maximum 12-bit ADC reading reported by hadc1. */
#define ADC_JOYSTICK_TEST_ADC_MAX_VALUE 4095U
static uint16_t adc_joystick_test_scale_permille(uint16_t raw);
/**
* @brief Convert a raw ADC sample to 0..1000 permille (0.1%) resolution.
* @param raw Raw ADC value reported by hadc1.
* @return Scaled permille reading.
*/
static uint16_t adc_joystick_test_scale_permille(uint16_t raw)
{
uint32_t permille = ((uint32_t)raw * 1000U + (ADC_JOYSTICK_TEST_ADC_MAX_VALUE / 2U)) /
ADC_JOYSTICK_TEST_ADC_MAX_VALUE;
if (permille > 1000U)
{
permille = 1000U;
}
return (uint16_t)permille;
}
/**
* @brief Check whether the joystick push-button is pressed.
* @return 1 when pressed (pin pulled low), otherwise 0.
*/
static uint8_t adc_joystick_test_read_key_internal(void)
{
GPIO_PinState state = HAL_GPIO_ReadPin(ADC_JOYSTICK_KEY_GPIO_Port, ADC_JOYSTICK_KEY_Pin);
return (state == GPIO_PIN_RESET) ? 1U : 0U;
}
/**
* @brief Acquire raw joystick readings for both axes plus key state.
* @param[out] sample Receives raw counts, permille scaling, and button flag.
* @return 0 on success, 1 when any ADC operation fails.
*/
uint8_t adc_joystick_test_sample(adc_joystick_sample_t *sample)
{
HAL_StatusTypeDef status;
uint16_t raw_x = 0U;
uint16_t raw_y = 0U;
if (sample == NULL)
{
return 1U;
}
status = HAL_ADC_Start(&hadc1);
if (status != HAL_OK)
{
return 1U;
}
status = HAL_ADC_PollForConversion(&hadc1, ADC_JOYSTICK_TEST_POLL_TIMEOUT_MS);
if (status != HAL_OK)
{
(void)HAL_ADC_Stop(&hadc1);
return 1U;
}
raw_x = (uint16_t)HAL_ADC_GetValue(&hadc1);
status = HAL_ADC_PollForConversion(&hadc1, ADC_JOYSTICK_TEST_POLL_TIMEOUT_MS);
if (status != HAL_OK)
{
(void)HAL_ADC_Stop(&hadc1);
return 1U;
}
raw_y = (uint16_t)HAL_ADC_GetValue(&hadc1);
status = HAL_ADC_Stop(&hadc1);
if (status != HAL_OK)
{
return 1U;
}
sample->x.raw = raw_x;
sample->x.permille = adc_joystick_test_scale_permille(raw_x);
sample->y.raw = raw_y;
sample->y.permille = adc_joystick_test_scale_permille(raw_y);
sample->key_pressed = adc_joystick_test_read_key_internal();
return 0U;
}
/**
* @brief Print joystick samples to the console for manual observation.
* @param sample_count Number of samples to capture (0 uses default count).
* @param delay_ms Delay between samples in milliseconds (0 uses default).
* @return 0 when all samples are captured, otherwise 1 on ADC failure.
*/
uint8_t adc_joystick_test_run(uint32_t sample_count, uint32_t delay_ms)
{
const uint32_t count = (sample_count == 0U) ? ADC_JOYSTICK_TEST_DEFAULT_SAMPLE_COUNT : sample_count;
const uint32_t delay = (delay_ms == 0U) ? ADC_JOYSTICK_TEST_DEFAULT_DELAY_MS : delay_ms;
for (uint32_t i = 0U; i < count; ++i)
{
adc_joystick_sample_t sample;
if (adc_joystick_test_sample(&sample) != 0U)
{
printf("adc joystick: sample %lu failed\r\n", (unsigned long)(i + 1U));
return 1U;
}
printf("adc joystick: #%lu X=%4u (%3lu.%01lu%%) Y=%4u (%3lu.%01lu%%) KEY=%s\r\n",
(unsigned long)(i + 1U),
(unsigned int)sample.x.raw,
(unsigned long)(sample.x.permille / 10U),
(unsigned long)(sample.x.permille % 10U),
(unsigned int)sample.y.raw,
(unsigned long)(sample.y.permille / 10U),
(unsigned long)(sample.y.permille % 10U),
(sample.key_pressed != 0U) ? "DOWN" : "UP");
HAL_Delay(delay);
}
return 0U;
}
/** @brief Empirical ADC thresholds used to derive U/D/L/R events. */
#define ADC_X_LEFT_THR 1600U /**< Lower X readings indicate LEFT. */
#define ADC_X_RIGHT_THR 2900U /**< Higher X readings indicate RIGHT. */
#define ADC_Y_DOWN_THR 2900U /**< Higher Y readings indicate DOWN. */
#define ADC_Y_UP_THR 1600U /**< Lower Y readings indicate UP. */
/**
* @brief Stream U/D/L/R ASCII codes that represent joystick direction.
* @param sample_count Number of samples to emit (0 keeps running).
* @param delay_ms Delay between samples in milliseconds (0 uses default).
* @param huart UART handle used to transmit the codes.
* @return 0 success, 1 sample failure, 2 invalid UART, 3 UART TX failure.
*/
uint8_t adc_joystick_send_udlr_uart(uint32_t sample_count, uint32_t delay_ms, UART_HandleTypeDef *huart)
{
const uint32_t delay = (delay_ms == 0U) ? ADC_JOYSTICK_TEST_DEFAULT_DELAY_MS : delay_ms;
if (huart == NULL) {
return 2U;
}
if (sample_count == 0U)
{
/* 0 indicates "run forever" for quick interactive tests. */
while (1)
{
adc_joystick_sample_t sample;
if (adc_joystick_test_sample(&sample) != 0U) {
return 1U;
}
char out[4];
uint32_t n = 0U;
/* Direction mapping follows the keypad: 4=L, 6=R, 8=U, 5=D. */
// if (sample.x.raw < ADC_X_LEFT_THR) { out[n++] = '4'; } // Left
// if (sample.x.raw > ADC_X_RIGHT_THR) { out[n++] = '6'; } // Right
// if (sample.y.raw < ADC_Y_UP_THR) { out[n++] = '8'; } // Up
// if (sample.y.raw > ADC_Y_DOWN_THR) { out[n++] = '5'; } // Down
if (sample.x.raw < ADC_X_LEFT_THR) { out[n++] = '6'; } // Right
if (sample.x.raw > ADC_X_RIGHT_THR) { out[n++] = '4'; } // Left
if (sample.y.raw < ADC_Y_UP_THR) { out[n++] = '5'; } // Down
if (sample.y.raw > ADC_Y_DOWN_THR) { out[n++] = '8'; } // Up
if (n > 0U) {
if (HAL_UART_Transmit(huart, (uint8_t *)out, (uint16_t)n, 10U) != HAL_OK) {
return 3U;
}
}
HAL_Delay(delay);
}
}
else
{
/* Finite run: iterate a fixed number of samples. */
for (uint32_t i = 0U; i < sample_count; ++i)
{
adc_joystick_sample_t sample;
if (adc_joystick_test_sample(&sample) != 0U) {
return 1U;
}
char out[4];
uint32_t n = 0U;
if (sample.x.raw < ADC_X_LEFT_THR) { out[n++] = '4'; } // Left
if (sample.x.raw > ADC_X_RIGHT_THR) { out[n++] = '6'; } // Right
if (sample.y.raw < ADC_Y_UP_THR) { out[n++] = '8'; } // Up
if (sample.y.raw > ADC_Y_DOWN_THR) { out[n++] = '5'; } // Down
if (n > 0U) {
if (HAL_UART_Transmit(huart, (uint8_t *)out, (uint16_t)n, 10U) != HAL_OK) {
return 3U;
}
}
HAL_Delay(delay);
}
return 0U;
}
}
/**
* @brief Convenience wrapper that only returns the joystick key state.
* @return 1 when pressed, otherwise 0.
*/
uint8_t adc_joystick_test_read_key(void)
{
return adc_joystick_test_read_key_internal();
}