基于STM32F407 ADC双通道 PS2游戏机摇杆ADC采集
时间:2023-03-06 04:00:00
基于STM32F407 ADC双通道 PS2游戏机摇杆ADC采集
- 一、PS2游戏机摇杆概述
-
- 1.1PS2游戏机摇杆概述
- 1.2PS2游戏机摇杆图
- 二、硬件连接分析
- 三、实现代码
-
- 3.1 ADC代码
- 3.1 定时器
- 3.2 主函数
- 四、实验效果分析
一、PS2游戏机摇杆概述
1.1PS2游戏机摇杆概述
摇杆一般广泛应用于无人机、电子游戏、遥控车、云台等设备,许多带屏幕的设备经常使用摇杆作为菜单选择的输入控制。
PS游戏双轴摇杆传感器模块采用原装优质金属PS22轴摇杆电位器制成(X,Y)模拟输出,1路(Z)按钮数字输出。
原理:摇杆为双向十字10K电阻器。使用5个模块V在原始状态下供电X、Y读出电压约为2.5V,当摇杆向某个方向推动时,相应的轴电压值增加或减小,大值5V,小值0V。
工作电压:5V
2.输出电压范围:0~5V
3.接口:两个模拟信号代表X、Y偏移量、数字信号SW代表Z轴是否按下
1.2PS2游戏机摇杆图
(1)SW:按下摇杆时触发标识符,引脚电平为高低电平变化,但当我通过万用表测量时,没有电平变化,引脚电平不通过。我不知道我买的模块是否有问题,其实在编程过程中并不难,加个中断就行了。VRY:输出Y轴电压变化范围:0~3.3V 或 0~5V ;电压范围取决于 5V标识的连接方式
(3)VRX:输出Y轴电压变化范围:0~3.3V 或 0~5V;电压范围取决于 5V标识的连接方式
(4)GND:接电源地
(5) 5V:这是接电压,虽然写着 5V,但也可以接3.3V(如果接3.3V, X电压变化范围:0~3.3V),由于STM32F407 ADC参考电压为3.3V,所以这个引脚接3.3V
X、Y如图所示
二、硬件连接分析
由于X、Y轴输出电压可变量,有两个ADC需要同时采集,所以会采用ADC设计双通道,通过搜索手册确定使用ADC1 通道2及通道3
因为需要快速响应采集速度,所以使用DMA通过搜索手册过搜索手册知道ADC1与DMA的关系为
三、代码实现
3.1 ADC代码
adc.c
#include "adc.h" //用于存储PA2 PA3的ADC值 __IO uint16_t aADCDualConvertedValue[2]; /************************************* 硬件说明 PA2 -- ADC123_IN2 PA3 -- ADC123_IN3 选用ADC1 *************************************/ void ADC_PA2_PA3_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure; ADC_CommonInitTypeDef ADC_CommonInitStructure; ADC_InitTypeDef ADC_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);//使能GPIOA时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //使能ADC1时钟 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2|GPIO_Pin_3; //引脚2 3 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN; ///模拟模式 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ; //浮空 GPIO_Init(GPIOA, &GPIO_InitStructure); 复位ADC寄存器 RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1,ENABLE); //reset adc
RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1,DISABLE); //end reset adc
ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent; //独立模式
ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_20Cycles;
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; //这是混合ADC才使用的模式,这里由于是独立模式,选择参数ADC_DMAAccessMode_Disabled
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;//预分频4分频。ADCCLK=PCLK2/4=84/4=21Mhz,ADC时钟最好不要超过36Mhz
ADC_CommonInit(&ADC_CommonInitStructure);//初始化
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; //12位模式
ADC_InitStructure.ADC_ScanConvMode = ENABLE; //扫描模式 多通道采集需要
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //连续转换
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;//禁止触发检测,使用软件触发
// ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_CC1; //禁止硬件触发,些值可以不用填写
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//右对齐
ADC_InitStructure.ADC_NbrOfConversion = 2;//2个转换在规则序列中
ADC_Init(ADC1, &ADC_InitStructure);//ADC初始化
// 配置 ADC 通道转换顺序和采样时间周期
ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 1, ADC_SampleTime_15Cycles ); //ADC1,ADC通道,480个周期,提高采样时间可以提高精确度
ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 2, ADC_SampleTime_28Cycles ); //ADC1,ADC通道,480个周期,提高采样时间可以提高精确度
// 使能 DMA 请求 after last transfer (Single-ADC mode)
ADC_DMARequestAfterLastTransferCmd(ADC1, ENABLE);
//使能 ADC DMA
ADC_DMACmd(ADC1, ENABLE);
//ADC_ExternalTrigConvCmd(ADC1, ENABLE);
ADC_Cmd(ADC1, ENABLE);//开启AD转换器
}
void DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
DMA_InitStructure.DMA_Channel = DMA_Channel_0; //选择 DMA 通道,通道存在于流中0中
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(ADC1->DR); //数据源在ADC1->DR中
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)aADCDualConvertedValue;//数据目的地在aADCDualConvertedValue数组
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;//数据传输方向为外设到存储器
DMA_InitStructure.DMA_BufferSize = 2; //与通道数设置一致
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设寄存器只有一个,地址不用递增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 存储器地址固定
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // 外设数据大小为半字,即两个字节
DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_HalfWord; // 存储器数据大小也为半字,跟外设数据大小相同
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 循环传输模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High; // DMA 传输通道优先级为高,当使用一个 DMA 通道时,优先级设置不影响
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; // 禁止 DMA FIFO ,使用直连模式
// FIFO 大小, FIFO 模式禁止时,这个不用配置
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_Init(DMA2_Stream0, &DMA_InitStructure);
//使能 DMA 流
DMA_Cmd(DMA2_Stream0, ENABLE);
}
adc.h
#ifndef __ADC_H
#define __ADC_H
#include "stm32f4xx.h"
void ADC_PA2_PA3_Init(void);
void DMA_Config(void);
#endif
3.1 定时器
在这里,我选择每隔1S采集ADC的值,如果在项目中需要快速采集,则减少定时器中断即可
tim.c
#include "tim.h"
/********************************************* 功能说明: TIM3 -- APB1 -- 16位定时器(65535) TIM3时钟频率: APB1*2 = 42*2 = 84MHZ **********************************************/
void Tim3_Init(void)
{
NVIC_InitTypeDef NVIC_InitStruct;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
// 1、使能定时器时钟。
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
TIM_TimeBaseInitStruct.TIM_Prescaler = 8400-1; //8400分频 84000 000HZ/84000 = 10000HZ
TIM_TimeBaseInitStruct.TIM_Period = 10000-1; //计10000个数,用时1s
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; //分频因子
// 2、初始化定时器,配置ARR,PSC。
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);
NVIC_InitStruct.NVIC_IRQChannel = TIM3_IRQn; //中断通道 ,在stm32f4xx.h
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2; //抢占优先级
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2; //响应优先级
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; //中断通道使能
// 3、启定时器中断,配置NVIC。
NVIC_Init(&NVIC_InitStruct);
// 4、设置 TIM3_DIER 允许更新中断
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
// 5、使能定时器。
TIM_Cmd(TIM3, ENABLE);
}
tim.h
#ifndef __TIM_H
#define __TIM_H
#include "stm32f4xx.h"
void Tim3_Init(void);
#endif
3.2 主函数
#include "stm32f4xx.h"
#include "adc.h"
#include "delay.h"
#include "usart.h"
#include "tim.h"
//用于存储PA2 PA3的ADC值
extern __IO uint16_t aADCDualConvertedValue[2];
unsigned int adcVal,adcVal01,adcVal02;
char msgstr[64];
int main(void)
{
//设置系统中断优先级分组2
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
Delay_Init();
Usart1_Init(115200);
ADC_PA2_PA3_Init();
DMA_Config();
//1S产生一次中断,在中断获取ADC数据
Tim3_Init();
//开始 adc 转换,软件触发
ADC_SoftwareStartConv(ADC1);
while(1)
{
delay_s(1);//延时200ms
};
return 0;
}
// 6、编写中断服务函数。
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3, TIM_IT_Update) == SET)
{
printf("While Run...\r\n");
adcVal01= aADCDualConvertedValue[0];
adcVal02= aADCDualConvertedValue[1];
printf("X轴电压:%f, Y轴电压:%f\r\n", (adcVal01/4095.0)*3.3 , (adcVal02/4095.0)*3.3);
sprintf(msgstr,"PA2 ch2=%d PA3 ch3=%d\r\n",adcVal01,adcVal02);
printf(msgstr);
printf("\r\n");
}
//清除中断线0标志位
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}
四、实验效果分析
将程序下载到开发板后,打开串口,通过摇杆,即可改变不同的电压
单个的PS游戏摇杆就写到这里了。两个摇杆的话,写成四通道即可。源码链接:https://download.csdn.net/download/wwwqqq2014/85309506?spm=1001.2014.3001.5503