【STM32F103】DMA直接存储器访问&游戏摇杆模块(ADC&DMA&EXTI)

发布时间:2024年01月21日

前言(可忽略)

当初下定决心要走嵌入式的时候买了一堆传感器,但是因为懒和忙所以闲置了一堆,今天考完了最后一门,所以打算一个个都玩一遍,今天先从这个摇杆开始,当初买这个是想着以后做个遥控小车的,现在先把这个模块玩明白。

游戏摇杆模块

网购就能买到,一个大概也就一两块。我买的这个,客服就给了个pdf,看得出来写的很随意(内容比较口语化),给的代码示例也是arduino的,不过我结合着看了看就发现,这个摇杆模块实际上就是一个电位器,使用ADC转换可以获取到摇杆的状态。

从上图可以看出有5针,从上到下的GND,+5V,URX,URY,SW。

虽然上面写着5V,并且示例代码给的arduino也是5V供电的,但是我试过了,STM32F103C8T6的3.3V也是能使用的。

URX和URY分别输出的是X轴和Y轴的信号量。

SW什么意思我不知道,不过它输出的是Z轴的信号量,在摇杆的侧面可以看到一个按钮,当整个摇杆向下按的时候,凸出来的白色的东西会挤压这个按钮,从而将按钮按下(朴实无华的物理结构)

我用万用表测了一下URX和URY的电压范围都是0~3.3V(接的STM32的3.3V和GND)。

但是SW似乎没有电压,但是按下的时候却可以使初始化成上拉输入的GPIO口发生下降沿,因此我推测SW连接的按钮的通道GND的,GPIO口配置成上拉输入,默认就是高电平,一旦SW按下,也就是接地了,高电平就变成下降沿,触发了下降沿。

思路

一共是三个信号量,XYZ三轴,XY轴可以使用ADC来获取具体的数值,而Z轴本质就是一个按键,因此Z轴使用GPIO口外部中断来判断是否按下。

而使用ADC来转换的XY一共有两个通道,因此可以配置为ADC和DMA联动,并且配上ADC的循环转换和扫描转换,这样就不用一直手动触发ADC转换。

外部中断和ADC在我之前的文章中有介绍,这里就只简单说下DMA吧。

另外需要注意的是接线,我下面的代码是用的ADC1的通道0和通道1,对应的GPIO口是GPIOA的0号引脚和1号引脚,如果要更改GPIO口的话需要查询引脚定义图然后修改ADC的通道参数。

DMA

DMA(Direct Memory Access)直接存储器访问,不需要CPU的干预就可以将数据快速移动,CPU只要下达命令之后,就不用再管DMA了,DMA会自动把数据搬运好,可以从外设的数据搬到存储器里,也可以把存储器的数据搬到外设,也可以把存储器的数据搬到存储器。

?从上图可知,我们搬运的目标和目的地可以是Flash,SRAM,APB1和APB2上的外设。

STM32F103C8T6属于中容量产品,因此只有DMA1,一共七个通道,不同通道对应的外设不一样,我们需要查询对应的映射关系。

可以看出我们计算搬运的ADC1在通道1里,因此我们需要使用DMA1的通道1。

了解上面内容之后就可以直接来使用DMA了,使用步骤很简单,一共三步,第一步打开时钟,第二步配置初始化,第三步上电使能。

DMA固件库函数

DMA不在APB1和APB2上,而是在AHB上,所以打开时钟的函数会和之前打开外设时钟不一样。

RCC_AHBPeriphClockCmd

就使用上图最下面的例子就是打开DMA时钟的。

DMA_Init

传入DMA通道和一个DMA_InitTypeDef指针类型的参数。

DMA_BufferSize?:DMA缓存大小,搬几个数据就填几。

DMA_DIR:根据传入的参数决定搬运方向。

DMA_M2M:是否从存储器搬运到存储器,我们这里是从ADC外设搬到存储器中,因此我们选择DMA_M2M_Disable。

DMA_MemoryBaseAddr:存储器基地址。

DMA_MemoryDataSize:存储器数据大小。由于ADC转换结果的范围为0~4095,因此我们选择半字(16位,0~65535,主要是因为8位不够)

DMA_PeripheralInc:外设地址是否自增

DMA_Priority:优先级

DMA_MemoryInc:存储器地址是否自增

DMA_Mode:DMA搬运模式,我们使用循环模式

DMA_PeripheralBaseAddr:外设基地址

DMA_PeripheralDataSize:外设数据大小。

DMA_Cmd

上电使能。

现象

不动的时候

往左掰的时候

整个按下的时候

代码

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"

uint16_t xy[2];

void interruptInit(void){                               //外部中断初始化
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //使能AFIO时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//使能GPIOA外设时钟
    GPIO_InitTypeDef gitd;                              
    gitd.GPIO_Mode=GPIO_Mode_IPU;                       //上拉输入模式
    gitd.GPIO_Pin=GPIO_Pin_6;                           //选择GPIOA的6号引脚
    gitd.GPIO_Speed=GPIO_Speed_50MHz;                   //不懂选啥就选50MHz
    GPIO_Init(GPIOA,&gitd);                             
    
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource6);//配置AFIO外部中断配置
    
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);     //抢占优先级和响应优先级各占两位.
    NVIC_InitTypeDef nitd;              
    nitd.NVIC_IRQChannel=EXTI9_5_IRQn;                  //选择中断通道
    nitd.NVIC_IRQChannelCmd=ENABLE; 
    nitd.NVIC_IRQChannelPreemptionPriority=2;           //抢占优先级
    nitd.NVIC_IRQChannelSubPriority=2;                  //响应优先级
    NVIC_Init(&nitd);
    
    EXTI_InitTypeDef eitd;
    eitd.EXTI_Line=EXTI_Line6;                          //6号中断线,对应6号引脚
    eitd.EXTI_LineCmd=ENABLE;
    eitd.EXTI_Mode=EXTI_Mode_Interrupt;     
    eitd.EXTI_Trigger=EXTI_Trigger_Falling;             //脉冲下降沿触发
    EXTI_Init(&eitd);                                   
}

int main(void){
    OLED_Init();
    interruptInit();                                    
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//使能GPIOA外设时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE); //使能ADC1外设时钟
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);   //使能DMA1外设时钟
    RCC_ADCCLKConfig(RCC_PCLK2_Div6);
    
    GPIO_InitTypeDef gitd;
    gitd.GPIO_Mode=GPIO_Mode_AIN;                       //选择GPIO口的模式
    gitd.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1;                //选择具体是哪一个GPIO口
    gitd.GPIO_Speed=GPIO_Speed_50MHz;                   //默认选择50MHz
    GPIO_Init(GPIOA,&gitd);
    
    ADC_InitTypeDef itd;
    itd.ADC_ContinuousConvMode=ENABLE;                  //开启连续转换模式
    itd.ADC_DataAlign=ADC_DataAlign_Right;              //数据右对齐
    itd.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None; //关闭外部触发,即软件触发ADC转换
    itd.ADC_Mode=ADC_Mode_Independent;                  //独立模式
    itd.ADC_NbrOfChannel=2;                             //2个通道数目
    itd.ADC_ScanConvMode=ENABLE;                        //开启扫描转换模式
    ADC_Init(ADC1,&itd);
    
    //配置规则组通道
    ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
    ADC_RegularChannelConfig(ADC1,ADC_Channel_1,2,ADC_SampleTime_55Cycles5);
    
    DMA_InitTypeDef ditd;
    ditd.DMA_BufferSize=2;                      
    ditd.DMA_DIR=DMA_DIR_PeripheralSRC;                     //传输方向为外设到存储器
    ditd.DMA_M2M=DMA_M2M_Disable;                           //软件触发
    ditd.DMA_MemoryBaseAddr=(uint32_t)&xy;                  //数据存储器地址
    ditd.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord;    //存储器数据大小为半字(16位,0~65535)
    ditd.DMA_MemoryInc=DMA_MemoryInc_Enable;                //开启存储器地址自增
    ditd.DMA_Mode=DMA_Mode_Circular;                        //循环模式
    ditd.DMA_PeripheralBaseAddr=(uint32_t)&ADC1->DR;        //外设地址
    ditd.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;//外设数据大小为半字
    ditd.DMA_PeripheralInc=DMA_PeripheralInc_Disable;       //外设地址不自增
    ditd.DMA_Priority=DMA_Priority_Medium;                  //优先级
    DMA_Init(DMA1_Channel1,&ditd);              
    DMA_Cmd(DMA1_Channel1,ENABLE);  
    
    ADC_DMACmd(ADC1,ENABLE);                                //开启ADC和DMA的联动
    ADC_Cmd(ADC1,ENABLE);
    
    ADC_ResetCalibration(ADC1);                             //复位校准
    while(SET==ADC_GetResetCalibrationStatus(ADC1));        //等到复位校准完成
    ADC_StartCalibration(ADC1);                             //开始校准
    while(SET==ADC_GetCalibrationStatus(ADC1));             //等待校准完毕
    
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);                 //开启软件触发ADC转换
    OLED_ShowString(1,1,"X:");
    OLED_ShowString(2,1,"Y:");
    while(1){
        int x=0,y=0;
        for(uint8_t i=0;i<10;++i) x+=xy[0];
        for(uint8_t i=0;i<10;++i) y+=xy[1];    
        x/=10;y/=10;
        
        OLED_ShowNum(1,3,x,5);
        OLED_ShowNum(2,3,y,5);
    }
}

void EXTI9_5_IRQHandler(void){
    if(EXTI_GetITStatus(EXTI_Line6)){                           //确认中断来自那个中断线
        if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6)==0){         
            OLED_ShowString(3,1,"Z DOWN");
            Delay_ms(20);                                       //消除机械按键抖动
            while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6)==0);  //长按可能会影响主程序运行
            Delay_ms(20);                                       //消除机械按键抖动
            OLED_ShowString(3,1,"        ");
        }
        EXTI_ClearITPendingBit(EXTI_Line6);                     //清除中断线标志位
    }
}

参考

《STM32F10xxx参考手册(中文)》

《ARM Cortex-M3 嵌入式原理及应用 基于STM32F103微控制器》

《STM32F103xx固件函数库用户手册》

文章来源:https://blog.csdn.net/m0_63235356/article/details/135251690
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。