项目概述
我成功实现了基于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 实现效果
当摇杆上下左右摇动时摇杆状态

我要赚赏金
