Dual Joystick Position Tracking Solution
实时追踪并存储双摇杆动作,结合持久内存记录与精确方向检测功能
你将学到和构建的内容
简介
Dual Joystick Position Tracking 解决方案集成了两个 Joystick 3 Click 板和一个 Flash 12 Click 板,实现了双摇杆输入的实时同步追踪与持久存储。该方案专为游戏控制器、机器人系统以及多轴输入设备而设计,通过采集两个摇杆的原始 ADC 值,判断各自的方向状态,并在发生移动变化时将其记录到非易失性 Flash 存储器中。该系统提供清晰且响应迅速的输入追踪机制,通过 UART 输出反馈信息,具备双通道输入能力,为需要精确用户输入历史记录的交互式系统提供了可靠的平台。
mikroBUS 1
Joystick 3 Click
Joystick 3 Click 是一款紧凑型扩展板,可满足各类方向性模拟输入需求。该板搭载 Adafruit Industries 出品的 2765 高品质微型双轴模拟摇杆。该摇杆为“自动回中”模拟型结构,配有黑色摇杆帽,外形类似于 PSP 游戏摇杆。它内部集成两个 10kΩ 电位器,分别用于上下(Y 轴)与左右(X 轴)方向的控制。由于该摇杆为模拟信号输出类型,Joystick 3 Click 通过 MCP3204 12 位 A/D 转换器,将模拟信号转换为数字信号,并通过 SPI 接口与 mikroBUS™ 进行连接与通信。这款 Click 板非常适合用作人机交互设备(HMI)、机器人控制系统及其他控制接口场景,是构建交互式控制系统的理想组件。
mikroBUS 2
Flash 12 Click
Flash 12 Click 是一款紧凑型扩展板,提供高度可靠的存储解决方案。该板搭载 Renesas 出品的 AT25EU0041A,这是一款 4Mbit 串行 Flash 存储器,以超低功耗著称。此 Click 板专为运行于 IoT 网络边缘的系统设计,特别适合作为程序代码的存储与直接从 NOR Flash 执行的解决方案。该器件采用创新的擦除架构,在读写擦除各操作中均实现了低功耗和快速响应,擦除时间短,是性能与效率兼具的闪存存储器。Flash 12 Click 特别适用于新一代低功耗 IoT 设备的开发,满足快速启动、代码缓存、事件记录和数据日志等应用场景下对低功耗、高性能存储的需求。
mikroBUS 4
Joystick 3 Click
Joystick 3 Click 是一款紧凑型扩展板,可满足各类方向性模拟输入需求。该板搭载 Adafruit Industries 出品的 2765 高品质微型双轴模拟摇杆。该摇杆为“自动回中”模拟型结构,配有黑色摇杆帽,外形类似于 PSP 游戏摇杆。它内部集成两个 10kΩ 电位器,分别用于上下(Y 轴)与左右(X 轴)方向的控制。由于该摇杆为模拟信号输出类型,Joystick 3 Click 通过 MCP3204 12 位 A/D 转换器,将模拟信号转换为数字信号,并通过 SPI 接口与 mikroBUS™ 进行连接与通信。这款 Click 板非常适合用作人机交互设备(HMI)、机器人控制系统及其他控制接口场景,是构建交互式控制系统的理想组件。
功能概述
开发板
Clicker 4 for STM32F4 是一款紧凑型开发板,专为快速构建自定义设备而设计。它配备 STM32F407VGT6 MCU、四个 mikroBUS™ 插座(用于 Click board™ 连接)、电源管理等功能,使其成为快速应用开发的理想选择。核心采用 STM32F407VGT6 MCU,该芯片基于 Arm® Cortex®-M4 32 位处理器,运行频率高达 168 MHz,提供充足的计算能力以满足高负载任务需求。除了两个 1x20 排针接口外,四个 mikroBUS™ 插座可支持庞大且不断增长的 Click boards™ 生态系统。开发板上清晰标记的功能区域提供直观、易用的界面,加快开发进程。Clicker 4 不仅能加速原型设计,还可直接集成到项目中,无需额外的硬件修改。四个 4.2mm(0.165”)的安装孔位于角落,便于使用螺丝固定,实现简便安装。
微控制器概述
MCU卡片 / MCU

建筑
ARM Cortex-M4
MCU 内存 (KB)
10
硅供应商
STMicroelectronics
引脚数
100
RAM (字节)
100
一步一步来
项目组装
实时跟踪您的结果
应用程序输出
1. 应用程序输出 - 在调试模式下,“应用程序输出”窗口支持实时数据监控,直接提供执行结果的可视化。请按照提供的教程正确配置环境,以确保数据正确显示。

2. UART 终端 - 使用UART Terminal通过USB to UART converter监视数据传输,实现Click board™与开发系统之间的直接通信。请根据项目需求配置波特率和其他串行设置,以确保正常运行。有关分步设置说明,请参考提供的教程。

3. Plot 输出 - Plot功能提供了一种强大的方式来可视化实时传感器数据,使趋势分析、调试和多个数据点的对比变得更加直观。要正确设置,请按照提供的教程,其中包含使用Plot功能显示Click board™读数的分步示例。在代码中使用Plot功能时,请使用以下函数:plot(insert_graph_name, variable_name);。这是一个通用格式,用户需要将“insert_graph_name”替换为实际图表名称,并将“variable_name”替换为要显示的参数。

软件支持
库描述
Dual Joystick Position Tracking Solution 使用 NECTO Studio 开发,确保兼容 mikroSDK 的开源库和工具。该解决方案采用即插即用的设计,支持快速实施和测试,并与所有配备 mikroBUS™ 插座的开发板、入门套件和 mikromedia 板完全兼容。
示例描述
Dual Joystick Position Tracking 解决方案集成了两个 Joystick 3 Click 和一个 Flash 12 Click,用于同时追踪和存储两个摇杆的位置。Joystick 3 Click 捕捉两个摇杆输入的原始 ADC 值,并检测每个摇杆的移动方向。Flash 12 Click 将这些位置存储在非易失性内存中,实现持久记录。该系统适用于需要实时追踪和位置历史存储的交互式应用,如游戏控制器、机器人系统和具有双输入的用户操作跟踪。
关键功能:
joystick3_init
- 初始化 Joystick 3 Click,用于读取两个摇杆的移动状态和 ADC 值。joystick3_read_raw_adc
- 读取两个摇杆 X 和 Y 轴的原始 ADC 数值。joystick3_get_position
- 将原始 ADC 数值转换为每个摇杆的具体方向状态(例如:上、下、左、右、中立)。flash12_init
- 初始化 Flash 12 Click,用于将摇杆位置数据存储到非易失性内存中。flash12_memory_write
- 将两个摇杆的位置数据写入指定地址的 Flash 存储器中。flash12_memory_read
- 从 Flash 存储器中读取已存储的摇杆位置数据以供验证。
应用初始化
初始化序列用于配置系统以实现双摇杆位置追踪功能:
1. 初始化 UART 日志记录器以用于调试。
2. 通过 SPI 接口配置 Joystick 3 Click 以读取摇杆位置。
3. 初始化 Flash 12 Click,用于将摇杆位置持久存储至 Flash 内存中。
如果任一初始化步骤失败,系统将记录错误并停止运行以防止异常行为。
应用任务
主循环执行以下操作:
1. 读取两个摇杆的 ADC 数值以检测其移动情况。
2. 将两个摇杆的 ADC 数值映射为各自的位置状态(例如:上、下、左、右)。
3. 比较两个摇杆当前的位置与之前的位置,以检测是否发生变化。
4. 如果检测到位置变化,则将两个摇杆的位置记录到 Flash 存储器中。
5. 读取并验证 Flash 存储中的数据,以确保记录准确。
6. 等待 2 秒后再检测下一次位置变化。
开源
代码示例
完整的应用程序代码和一个现成的项目可以通过NECTO Studio包管理器直接安装到NECTO Studio。 应用程序代码也可以在MIKROE的GitHub账户中找到。
/*
* Solution Name: Dual Joystick Real-Time Position Tracking Solution
*
* Description:
* This embedded application continuously tracks joystick positions
* using two Joystick 3 Click boards and logs the detected positions
* to Flash memory via the Flash 12 Click.
*
* The system utilizes the following Click boards:
* - Joystick 3 Click (x2): Captures joystick positions in raw ADC values and detects
* movement directions for both joysticks.
* - Flash 12 Click: Stores detected joystick positions in non-volatile memory.
*
* The `application_init` function initializes both Joystick 3 Click boards and the Flash 12 Click,
* ensuring proper configuration and communication, and sets up the UART logger for debugging.
*
* The `application_task` function monitors position changes for both joysticks and logs the detected
* positions to Flash memory when a new position is detected. It also verifies stored data
* by reading from memory.
*
* Hardware Setup:
* - MIKROBUS_1: Joystick 3 Click #1 (Joystick position tracking)
* - MIKROBUS_2: Flash 12 Click (Persistent storage for joystick positions)
* - MIKROBUS_4: Joystick 3 Click #2 (Second joystick position tracking)
*
* Key Features:
* - Real-time detection of joystick position changes for both joysticks.
* - Logging of joystick positions to Flash memory.
* - Verification of stored data through memory read-back.
* - UART-based logging for displaying position updates.
*
* Development Environment:
* - [NECTO Studio](https://www.mikroe.com/necto)
* - [mikroSDK v2.0](https://www.mikroe.com/mikrosdk) framework
* - MIKROE [Click boards](https://www.mikroe.com/click-boards) Add-ons
*
* Author: Branko Jaksic
* Date: March, 2025
*/
// ------------------------------------------------------------------- INCLUDES
#include "board.h"
#include "log.h"
#include "flash12.h"
#include "joystick3.h"
// ------------------------------------------------------------- PRIVATE MACROS
// Starting memory address
#define STARTING_ADDRESS 0x012345
// ------------------------------------------------------------------ VARIABLES
static log_t logger;
static flash12_t flash12;
static joystick3_t joystick3_1; // First Joystick (mikroBUS_1)
static joystick3_t joystick3_2; // Second Joystick (mikroBUS_4)
typedef uint8_t joystick3_position_t;
// ------------------------------------------------------ APPLICATION FUNCTIONS
void application_init ( void )
{
log_cfg_t log_cfg; /**< Logger config object. */
joystick3_cfg_t joystick3_cfg_1; /**< Click config object. */
joystick3_cfg_t joystick3_cfg_2; /**< Click config object. */
flash12_cfg_t flash12_cfg; /**< Click config object. */
/**
* Logger initialization.
* Default baud rate: 115200
* Default log level: LOG_LEVEL_DEBUG
* @note If USB_UART_RX and USB_UART_TX
* are defined as HAL_PIN_NC, you will
* need to define them manually for log to work.
* See @b LOG_MAP_USB_UART macro definition for detailed explanation.
*/
LOG_MAP_USB_UART( log_cfg );
log_init( &logger, &log_cfg );
log_info( &logger, " Application Init " );
// Joystick 3 (1st) Click initialization.
joystick3_cfg_setup( &joystick3_cfg_1 );
JOYSTICK3_MAP_MIKROBUS( joystick3_cfg_1, MIKROBUS_1 );
if ( SPI_MASTER_ERROR == joystick3_init( &joystick3_1, &joystick3_cfg_1 ) )
{
log_error( &logger, " Communication init." );
for ( ; ; );
}
// Joystick 3 (2nd) Click initialization.
joystick3_cfg_setup( &joystick3_cfg_2 );
JOYSTICK3_MAP_MIKROBUS( joystick3_cfg_2, MIKROBUS_4 );
if ( SPI_MASTER_ERROR == joystick3_init( &joystick3_2, &joystick3_cfg_2 ) )
{
log_error( &logger, " Communication init." );
for ( ; ; );
}
// Flash 12 Click initialization.
flash12_cfg_setup( &flash12_cfg );
FLASH12_MAP_MIKROBUS( flash12_cfg, MIKROBUS_2 );
if ( SPI_MASTER_ERROR == flash12_init( &flash12, &flash12_cfg ) )
{
log_error( &logger, " Communication init." );
for ( ; ; );
}
if ( FLASH12_ERROR == flash12_default_cfg ( &flash12 ) )
{
log_error( &logger, " Default configuration." );
for ( ; ; );
}
log_info( &logger, " Application Task " );
}
void read_logged_positions ( void )
{
uint32_t mem_address_1 = STARTING_ADDRESS;
uint32_t mem_address_2 = STARTING_ADDRESS + 0x100;
uint8_t data_buf[32] = { 0 };
log_printf( &logger, "\r\n--- Reading Stored Joystick Positions ---\r\n" );
// Read Joystick 1 logged positions
log_printf( &logger, "Joystick 1 Positions:\r\n" );
while ( 1 )
{
memset( data_buf, 0, sizeof( data_buf ) );
if ( FLASH12_OK != flash12_memory_read( &flash12, mem_address_1, data_buf, sizeof( data_buf ) ) )
{
log_printf( &logger, "Error reading memory at 0x%.6lX\r\n", mem_address_1 );
break;
}
// Stop if we reach an empty space (default erased flash value is 0xFF)
if ( data_buf[0] == 0xFF || data_buf[0] == '\0' )
{
log_printf( &logger, "End of Joystick 1 logged data.\r\n" );
break;
}
log_printf( &logger, "Memory 0x%.6lX: %s\r\n", mem_address_1, data_buf );
mem_address_1 += strlen( (char*)data_buf ) + 1; // Move to the next stored entry
}
// Read Joystick 2 logged positions
log_printf( &logger, "Joystick 2 Positions:\r\n" );
while ( 1 )
{
memset( data_buf, 0, sizeof( data_buf ) );
if ( FLASH12_OK != flash12_memory_read( &flash12, mem_address_2, data_buf, sizeof( data_buf ) ) )
{
log_printf( &logger, "Error reading memory at 0x%.6lX\r\n", mem_address_2 );
break;
}
// Stop if we reach an empty space (default erased flash value is 0xFF)
if ( data_buf[0] == 0xFF || data_buf[0] == '\0' )
{
log_printf( &logger, "End of Joystick 2 logged data.\r\n" );
break;
}
log_printf( &logger, "Memory 0x%.6lX: %s\r\n", mem_address_2, data_buf );
mem_address_2 += strlen( (char*)data_buf ) + 1; // Move to the next stored entry
}
log_printf( &logger, "-----------------------------------------\r\n" );
}
const char* joystick_position_to_string( joystick3_position_t position )
{
switch ( position )
{
case JOYSTICK3_POSITION_NEUTRAL: return "NEUTRAL";
case JOYSTICK3_POSITION_UP: return "UP";
case JOYSTICK3_POSITION_DOWN: return "DOWN";
case JOYSTICK3_POSITION_LEFT: return "LEFT";
case JOYSTICK3_POSITION_RIGHT: return "RIGHT";
case JOYSTICK3_POSITION_UPPER_LEFT: return "UPPER-LEFT";
case JOYSTICK3_POSITION_UPPER_RIGHT: return "UPPER-RIGHT";
case JOYSTICK3_POSITION_LOWER_LEFT: return "LOWER-LEFT";
case JOYSTICK3_POSITION_LOWER_RIGHT: return "LOWER-RIGHT";
default: return "UNKNOWN";
}
}
void application_task ( void )
{
static uint32_t mem_address_1 = STARTING_ADDRESS; // Rolling memory address
static uint32_t mem_address_2 = STARTING_ADDRESS + 0x100; // Offset for second joystick
static joystick3_position_t last_position_1 = JOYSTICK3_POSITION_NEUTRAL;
static joystick3_position_t last_position_2 = JOYSTICK3_POSITION_NEUTRAL;
uint16_t raw_x1, raw_y1, raw_x2, raw_y2;
uint8_t data_buf[32] = { 0 }; // Buffer for logging position
// Read Joystick 1 raw ADC values
if ( JOYSTICK3_OK == joystick3_read_raw_adc ( &joystick3_1, &raw_x1, &raw_y1 ) )
{
joystick3_position_t current_position_1 = joystick3_get_position( raw_x1, raw_y1 );
// Log position if it changes
if ( current_position_1 != last_position_1 )
{
const char* position_str_1 = joystick_position_to_string( current_position_1 );
log_printf( &logger, "Joystick 1 position: %s\r\n", position_str_1 );
// Write position to Flash memory
memset( data_buf, 0, sizeof( data_buf ) );
strncpy( (char*)data_buf, position_str_1, sizeof( data_buf ) - 1 );
if ( FLASH12_OK == flash12_memory_write( &flash12,
mem_address_1,
data_buf,
strlen( position_str_1 ) + 1 ) )
{
log_printf( &logger, "Stored JoyStick 1 in Flash at 0x%.6lX: %s\r\n",
mem_address_1,
data_buf );
mem_address_1 += strlen( position_str_1 ) + 1; // Move address forward
}
last_position_1 = current_position_1; // Update last position
}
read_logged_positions(); // Call to read and verify stored positions
}
// Read Joystick 2 raw ADC values
if ( JOYSTICK3_OK == joystick3_read_raw_adc ( &joystick3_2, &raw_x1, &raw_y2 ) )
{
joystick3_position_t current_position_2 = joystick3_get_position( raw_x2, raw_y2 );
// Log position if it changes
if ( current_position_2 != last_position_2 )
{
const char* position_str_2 = joystick_position_to_string( current_position_2 );
log_printf( &logger, "Joystick 2 position: %s\r\n", position_str_2 );
// Write position to Flash memory
memset( data_buf, 0, sizeof( data_buf ) );
strncpy( (char*)data_buf, position_str_2, sizeof( data_buf ) - 1 );
if ( FLASH12_OK == flash12_memory_write( &flash12,
mem_address_2,
data_buf,
strlen( position_str_2 ) + 1 ) )
{
log_printf( &logger, "Stored JoyStick 2 in Flash at 0x%.6lX: %s\r\n",
mem_address_2,
data_buf );
mem_address_2 += strlen( position_str_2 ) + 1; // Move address forward
}
last_position_2 = current_position_2; // Update last position
}
read_logged_positions(); // Call to read and verify stored positions
}
Delay_ms( 100 );
}
int main ( void )
{
/* Do not remove this line or clock might not be set correctly. */
#ifdef PREINIT_SUPPORTED
preinit();
#endif
application_init( );
for ( ; ; )
{
application_task( );
}
return 0;
}
// ------------------------------------------------------------------------ END
额外支持
资源
类别:Human-Machine Interface (HMI)