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

news2025/1/13 15:36:45

前言(可忽略)

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

游戏摇杆模块

网购就能买到,一个大概也就一两块。我买的这个,客服就给了个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固件函数库用户手册》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1401332.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

指标异常检测和诊断

检测 是发现问题 诊断 是找到原因 误差的分类 系统误差&#xff1a;系统误差是由于仪器本身不精确&#xff0c;或实验方法粗略&#xff0c;或实验原理不完善而产生的。随机误差&#xff1a;随机误差是由各种偶然因素对实验者、测量仪器、被测物理量的影响而产生的。粗大误差&…

动态规划——数字金字塔【集训笔记】

题目描述 观察下面的数字金字塔。写一个程序查找从最高点到底部任意处结束的路径&#xff0c;使路径经过数字的和最大。每一步可以从当前点走到左下方的点也可以到达右下方的点。 在上面的样例中,从13到8到26到15到24的路径产生了最大的和86。 输入 第一个行包含R(1≤ R≤…

4.servera修改主机名,配置网络,以及在cmd中远程登录servera的操作

1.先关闭这两节省资源 2.对于新主机修改主机名&#xff0c;配置网络 一、配置网络 1.推荐图形化界面nmtui 修改完成后测试 在redhat ping一下 在redhat远程登录severa 2、使用nmcli来修改网络配置 2.1、配置要求&#xff1a;主机名&#xff1a; node1.domain250.exam…

线程的取消学习笔记

目录 取消线程-pthread_cancel: 线程清理&#xff1a; 取消线程-pthread_cancel: int pthread_cancel(pthread_t thread);//杀死一个线程 示例代码&#xff1a; #include <stdio.h> #include <pthread.h> #include <unistd.h>void *func(void *arg) {p…

[医学多模态融合] 医学图像 + 文本数据 多模态模型发展及预训练模型应用

[医学多模态融合] 医学图像 文本数据 多模态模型发展及预训练模型应用 0. 前言1. 图像数据 多模态模型的发展2. ConVIRT2.1 模型设计2.2 数据集及训练2.3 应用及表现2.3.1 分类任务2.3.2 Zero-shot任务 3. CLIP3.1 模型设计3.2 数据集及训练3.2.1 图像编码器3.2.2 文本编码器 …

用日期类增强对几个默认函数的的理解

首先写一个日期类&#xff1a;包括打印&#xff0c;根据月份判断天数 用类创建对象默认需要构造函数&#xff1b;且也要判断构造出的日期是否符合常理&#xff1a; 在程序结束时需要一个析构函数来释放空间&#xff0c;&#xff08;日期类的对象不会开空间这里只是写出来演…

python—01虚拟环境

文档结构 1、概念简介2、环境配置2.1、多版本解释器2.2、指令创建虚拟环境2.3、idea创建虚拟环境2.3.1、pycharm 1、概念简介 虚拟环境 在某些场景下&#xff0c;不同的项目需要基于不同版本的Python解释器来开发&#xff0c;或者不同的项目需要的第三方包或模块版本也不同。当…

C# Socket通信从入门到精通(17)——单个异步UDP服务器监听一个客户端C#代码实现

前言: 我们在开发UDP通信程序时,除了开发UDP同步客户端程序,有时候我们也需要开发异步UDP服务器程序,所谓的异步最常见的应用就是服务器接收客户端数据以后,程序不会卡在数据接收这里,而是可以继续往下执行,这在实际项目中是经常会遇到的,所以说掌握异步UDP服务器程序…

从关键新闻和最新技术看AI行业发展(2024.1.1-1.14第十四期) |【WeThinkIn老实人报】

Rocky Ding 公众号&#xff1a;WeThinkIn 写在前面 【WeThinkIn老实人报】旨在整理&挖掘AI行业的关键新闻和最新技术&#xff0c;同时Rocky会对这些关键信息进行解读&#xff0c;力求让读者们能从容跟随AI科技潮流。也欢迎大家提出宝贵的优化建议&#xff0c;一起交流学习&…

【论文阅读】Relation-Aware Graph Transformer for SQL-to-Text Generation

Relation-Aware Graph Transformer for SQL-to-Text Generation Abstract SQL2Text 是一项将 SQL 查询映射到相应的自然语言问题的任务。之前的工作将 SQL 表示为稀疏图&#xff0c;并利用 graph-to-sequence 模型来生成问题&#xff0c;其中每个节点只能与 k 跳节点通信。由…

AWTK 开源串口屏开发(7) - 屏幕保护

现代屏幕其实并不需要屏幕保护&#xff0c;不过屏幕保护程序会衍生一些其它用途。比如&#xff1a; 保护隐私。长时间不操作&#xff0c;通过动画或者其它方式隐藏屏幕内容。数据安全。长时间不操作&#xff0c;需要输入密码才能恢复。美观/广告。长时间不操作&#xff0c;显示…

CMeet系列技术生态沙龙---城市开发者组织年度聚会·杭州 《把握未来趋势,持续学习创新》

CSDN始终致力于促进城市区域内尖端新兴技术开发者交流,提供开放自由的切磋平台。在这个充满挑战和机遇的一年即将结束之际&#xff0c;通过本次聚会&#xff0c;汇聚杭州本地各行各业的开发者朋友&#xff0c;回顾过去一年城市社区的成就和收获&#xff0c;感谢携手同行的各位,…

Spring中动态注册和销毁对象

1. 使用说明 通常我们项目中想要往spring容器中注入一个bean可以在项目初始化的时候结合Bean注解实现。但是该方法适合项目初始化时候使用&#xff0c;如果后续想要继续注入对象则无可奈何。本文主要描述一种在后续往spring容器注入bean的方法。 2. 实现 2.1 说明 2.1.1 注册…

记一次多平台免杀PHP木马的制作过程

注意&#xff1a;本文转载自本作者稀土掘金博客 博客地址&#xff1a; 御坂19008号 的个人主页 - 动态 - 掘金 文章目录 前言声明绕过情况使用方法运行环境绕过点介绍技术原理讲解变量传值覆盖模块代码执行阻断模块InazumaPuzzle程序锁定器PerlinNoise危险函数生成与执行类构造…

小样本跨域(cross-domain)系列工作(超级详细)

小样本跨域&#xff08;cross-domain&#xff09;系列工作 本文记录近期阅读过的小样本跨域相关论文&#xff0c;每一篇文章都经过了自己的深入思考和总结&#xff0c;按照&#xff1a;解决什么问题、动机、方法、数据集、结论和启发的顺序进行总结&#xff0c;同时会有部分细…

algotithm -- 排序算法

排序算法总结表&#xff1a; 1. In-place 和 Out-place 含义 参考链接 in-place 占用常数内存&#xff0c;不占用额外内存 假如问题规模是n&#xff0c;在解决问题过程中&#xff0c;只开辟了常数量的空间&#xff0c;与n无关&#xff0c;这是原址操作&#xff0c;就是In-…

谷歌浏览器通过network模拟HTTP中的GET/POST请求获取response

1、F12打开network选中需要模拟的方法Copy->Copy as fetch 2、通过AI帮你进行转换一下调用格式 原代码 fetch("https://mp.amap.com/api/forward/aggregate?mtop.alsc.kbt.intergration.toolkit.call.queryCallBlockInfo", {"headers": {"acce…

Grafana(三)Grafana 免密登录-隐藏导航栏-主题变换

一. 免密登录 Grafana 的常用方式&#xff1a; 将配置好的Grafana图嵌入到系统页面中 为了实现可免登录访问&#xff0c;可以通过如下方式进行设置&#xff1a; 1. 修改Grafana配置文件 在Grafana的配置文件 /etc/grafana/grafana.ini 中&#xff0c;找到 [auth.anonymous] 配…

HarmonyOS开源软件Notice收集策略说明

开源软件Notice是与项目开源相关的文件&#xff0c;收集这些文件的目的是为了符合开源的规范。 收集目标 只收集打包到镜像里面的模块对应的License&#xff1b;不打包的都不收集&#xff0c;比如构建过程使用的工具&#xff08;如clang、python、ninja等&#xff09;都是不收…

如何进行正确的 CodeReview

软件开发生命周期中至关重要的一步是代码审查。它使开发人员能够显著提升代码质量。它类似于书籍的创作过程。首先&#xff0c;作者写故事&#xff0c;然后经过编辑以确保不会出现诸如混淆“you’re”和“yours”之类的错误。在这个语境中&#xff0c;代码审查指的是检查和评估…