项目概述
我成功实现了基于SAME51J20A的双轴摇杆驱动,为后去功能开发奠定了基础。主要实现目的替代之前通过串口实现的按键功能,当前仅用一个摇杆实现上下左右和确认功能。
第一章 关键外设配置 (两个ADC通道 + 一个IO)
1.1 硬件连接:
1.2 软件配置
本来是计划使用DMA+ADC的结果没配置成功,因此采用这种方式
1.3 软件实现策略
ADC | 轮询采样 | DMA配置未完成,快速实现 |
按键检测 | GPIO轮询 | 暂未使用中断 |
1.4 代码实现
// custom/hal/peripheral/adc_hal.c #include "adc_hal.h" // ADC初始化 static bool adc_init(void) { // 初始化ADC外设(使用MPLAB Harmony配置的参数) ADC0_Initialize(); ADC0_Enable(); return true; } // 获取ADC值 static uint16_t adc_get_value(adc_channel_t channel) { if (channel >= ADC_CHANNEL_MAX) { return 0; } // 选择通道 switch (channel) { case ADC_CHANNEL_0: ADC0_ChannelSelect(ADC_POSINPUT_AIN0, ADC_NEGINPUT_GND); break; case ADC_CHANNEL_1: ADC0_ChannelSelect(ADC_POSINPUT_AIN1, ADC_NEGINPUT_GND); break; case ADC_CHANNEL_2: ADC0_ChannelSelect(ADC_POSINPUT_AIN2, ADC_NEGINPUT_GND); break; case ADC_CHANNEL_3: ADC0_ChannelSelect(ADC_POSINPUT_AIN3, ADC_NEGINPUT_GND); break; case ADC_CHANNEL_4: ADC0_ChannelSelect(ADC_POSINPUT_AIN4, ADC_NEGINPUT_GND); break; case ADC_CHANNEL_5: ADC0_ChannelSelect(ADC_POSINPUT_AIN5, ADC_NEGINPUT_GND); break; case ADC_CHANNEL_6: ADC0_ChannelSelect(ADC_POSINPUT_AIN6, ADC_NEGINPUT_GND); break; case ADC_CHANNEL_7: ADC0_ChannelSelect(ADC_POSINPUT_AIN7, ADC_NEGINPUT_GND); break; case ADC_CHANNEL_8: ADC0_ChannelSelect(ADC_POSINPUT_AIN8, ADC_NEGINPUT_GND); break; case ADC_CHANNEL_9: ADC0_ChannelSelect(ADC_POSINPUT_AIN9, ADC_NEGINPUT_GND); break; case ADC_CHANNEL_10: ADC0_ChannelSelect(ADC_POSINPUT_AIN10, ADC_NEGINPUT_GND); break; case ADC_CHANNEL_11: ADC0_ChannelSelect(ADC_POSINPUT_AIN11, ADC_NEGINPUT_GND); break; case ADC_CHANNEL_12: ADC0_ChannelSelect(ADC_POSINPUT_AIN12, ADC_NEGINPUT_GND); break; case ADC_CHANNEL_13: ADC0_ChannelSelect(ADC_POSINPUT_AIN13, ADC_NEGINPUT_GND); break; case ADC_CHANNEL_14: ADC0_ChannelSelect(ADC_POSINPUT_AIN14, ADC_NEGINPUT_GND); break; case ADC_CHANNEL_15: ADC0_ChannelSelect(ADC_POSINPUT_AIN15, ADC_NEGINPUT_GND); break; default: return 0; } // 启动转换 ADC0_ConversionStart(); // 等待转换完成 while (!ADC0_ConversionStatusGet()) { // 等待转换完成 } // 读取结果 uint16_t result = ADC0_ConversionResultGet(); return result; } // ADC HAL实例 const adc_hal_t adc_hal = {.init = adc_init, .get_value = adc_get_value};
以对象的方式对外提供adc_hal对象,主要包括初始化函数以及获取对应通道raw值的接口
其中
init主要是配置adc以及使能ADC0的时钟功能
get_value实现为,选择对应通道,开始转换,等待转换完成,然后转换结果,耗时12.5us
第二章 基于外设实现摇杆驱动
首先明确知道摇杆有x和y两轴,即对应两个adc通道,因此对应上下左右即代表y轴和x轴的adc值
实现代码:
// custom/hal/module/input/joystick.c #include "joystick.h" #include "custom/hal/peripheral/adc_hal.h" #include "definitions.h" // 摇杆初始化 static bool joystick_init(joystick_device_t* joystick, uint8_t x_channel, uint8_t y_channel, const joystick_config_t* config) { if (joystick == NULL) { return false; } adc_hal.init(); joystick->x_channel = x_channel; joystick->y_channel = y_channel; // 设置默认配置 if (config != NULL) { joystick->config = *config; } else { joystick->config.center_x = 2048; // 默认中心值 joystick->config.center_y = 2048; // 默认中心值 joystick->config.deadzone = 100; // 默认死区 joystick->config.threshold = 200; // 默认阈值 } joystick->initialized = true; return true; } // 摇杆反初始化 static void joystick_deinit(joystick_device_t* joystick) { if (joystick == NULL || !joystick->initialized) { return; } joystick->initialized = false; } // 获取摇杆位置 static joystick_position_t joystick_get_position(joystick_device_t* joystick) { joystick_position_t position = {0, 0}; if (joystick == NULL || !joystick->initialized) { return position; } // 获取X轴值 position.x = adc_hal.get_value(joystick->x_channel); // 获取Y轴值 position.y = adc_hal.get_value(joystick->y_channel); return position; } // 获取摇杆方向 static joystick_direction_t joystick_get_direction(joystick_device_t* joystick) { if (joystick == NULL || !joystick->initialized) { return JOYSTICK_DIR_CENTER; } joystick_position_t pos = joystick_get_position(joystick); int16_t dx = pos.x - joystick->config.center_x; int16_t dy = pos.y - joystick->config.center_y; // 检查是否在死区内 if ((dx * dx + dy * dy) < (joystick->config.deadzone * joystick->config.deadzone)) { return JOYSTICK_DIR_CENTER; } // 判断方向 bool is_up = dy < -joystick->config.threshold; bool is_down = dy > joystick->config.threshold; bool is_left = dx < -joystick->config.threshold; bool is_right = dx > joystick->config.threshold; if (is_up) { return JOYSTICK_DIR_UP; } else if (is_down) { return JOYSTICK_DIR_DOWN; } else if (is_left) { return JOYSTICK_DIR_LEFT; } else if (is_right) { return JOYSTICK_DIR_RIGHT; } return JOYSTICK_DIR_CENTER; } // 检查摇杆是否移动 static bool joystick_is_moved(joystick_device_t* joystick) { if (joystick == NULL || !joystick->initialized) { return false; } joystick_position_t pos = joystick_get_position(joystick); int16_t dx = pos.x - joystick->config.center_x; int16_t dy = pos.y - joystick->config.center_y; return (dx * dx + dy * dy) > (joystick->config.threshold * joystick->config.threshold); } // 摇杆模块实例 const joystick_module_t joystick_module = { .init = joystick_init, .deinit = joystick_deinit, .get_position = joystick_get_position, .get_direction = joystick_get_direction, .is_moved = joystick_is_moved};
3.1 验证代码实现
// 初始化摇杆 joystick_config_t joystick_config = {.center_x = 2048, .center_y = 2048, .deadzone = 100, .threshold = 200}; joystick_module.init(&joystick1, JOYSTICK1_X_CHANNEL, JOYSTICK1_Y_CHANNEL, &joystick_config); // 测试摇杆功能 static void test_joystick(void) { // 获取摇杆方向 joystick_direction_t direction = joystick_module.get_direction(&joystick1); // 检查摇杆方向是否发生变化 if (direction != last_joystick_direction) { last_joystick_direction = direction; const char* msg = NULL; switch (direction) { case JOYSTICK_DIR_CENTER: msg = "Joystick: Center\r\n"; break; case JOYSTICK_DIR_UP: msg = "Joystick: Up\r\n"; break; case JOYSTICK_DIR_DOWN: msg = "Joystick: Down\r\n"; break; case JOYSTICK_DIR_LEFT: msg = "Joystick: Left\r\n"; break; case JOYSTICK_DIR_RIGHT: msg = "Joystick: Right\r\n"; break; default: msg = "Joystick: Unknown\r\n"; break; } serial_hal.write((uint8_t*)msg, strlen(msg)); } }
3.2 实现效果
当摇杆上下左右摇动时摇杆状态