news 2026/11/6 3:24:06

ADC+DWA

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ADC+DWA

1.5V

2.GND

3. PA0

4.PD11

使用DWA之前先看STM32的参考手册-DMA映像147,防止冲突

你的现状:完全够用,互不干扰

STM32F103 系列有两个 DMA 控制器,资源其实是挺丰富的:

  • DMA1:有 7 个通道。
  • DMA2:有 5 个通道。
  • 总计:你有 12 条“高速公路”可以用。

你的项目目前只用了 2 条:

  1. 光敏 (ADC3):占用了DMA2的通道5。
  2. MQ2 (ADC1):将占用DMA1的通道1。

结论:你手里还有 10 条空闲通道,哪怕你再加几个传感器(比如再加个 ADC2,或者加几个串口 USART),通道都是绰绰有余的。


什么时候会觉得“通道不够”?

虽然总共有 12 个通道,但在实际工程中,确实会出现“抢车道”的情况。主要有两个原因:

1. “硬性绑定”导致的局部拥堵

这是最常见的问题。STM32 的外设和 DMA 通道是固定死的,不能随便换。

  • 例子:假设你想用 3 个 ADC 同时采集(ADC1, ADC2, ADC3)。
    • ADC1只能走DMA1 Channel 1。
    • ADC2只能走DMA1 Channel 1(注意!它和 ADC1 抢同一个通道)。
    • ADC3只能走DMA2 Channel 5。
  • 冲突点:如果你同时开启 ADC1 和 ADC2 的 DMA,它们就会在DMA1 Channel 1撞车。这时候你就得二选一,或者放弃 DMA 改用中断。
2. 高级外设占用了“黄金通道”

有些通道是“兵家必争之地”。

  • SPI / I2S:如果你要用高速 SPI 传输屏幕数据,或者用 I2S 播放音乐,它们通常会占用 DMA1 或 DMA2 的特定通道。
  • 定时器 (TIM):如果你要做复杂的电机控制(PWM 输入捕获),定时器也会占用 DMA 通道。
  • 如果你的项目既要搞音频(I2S),又要搞多路 ADC,还要搞串口通信,那 12 个通道可能很快就分完了。

如果通道真的不够了怎么办?

假如你是一个超级复杂的项目,12 个通道全被占满了,还想加功能,有以下 3 种解决方案(按推荐程度排序):

方案 A:使用“轮询”或“中断”代替 DMA(最常用)

不是所有数据都需要 DMA 搬运。

  • 低速数据:比如温度传感器(DS18B20)、按键扫描,速度很慢,直接用 CPU 读取(轮询)或者用中断读取即可,没必要浪费 DMA 通道。
  • 中速数据:比如普通的串口打印调试信息,用TXE(发送寄存器空)中断来发,虽然比 DMA 稍微占一点 CPU,但完全来得及。
方案 B:分时复用(Time Division Multiplexing)

如果两个设备不会同时工作,可以共用一个通道。

  • 例子:你有一个 ADC 用来测电池电压(几分钟测一次),另一个 ADC 用来测电机电流(一直在测)。
  • 做法:平时把 DMA 配置给电机电流用。当需要测电池时,暂停电机 DMA,把 DMA 重新配置给电池 ADC,测完再切回去。这需要写代码动态切换 DMA 的配置。
方案 C:软件模拟 DMA(下策)

如果硬件 DMA 真的没了,你可以写一个定时器中断(比如 1ms 进一次),在中断里手动把数据从外设寄存器读到内存数组里。这其实就是用 CPU 模拟了 DMA 的功能,虽然效率低一点,但能解决问题。

然后此处使用

ADC 的作用就是把连续的模拟电压,量化成一个离散的数字

配置ADC的流程

1. 开 ADC 时钟
2. 配置引脚为模拟输入
3. 设置 ADC 分频(复制一份主频,降速到合适的频率,再送给这个机器)
4. 设置采样时间(采集一次花的时间)


5. 设置转换通道和顺序(通道 = 选哪个引脚,顺序 = 先采谁后采谁)
6. 设置工作模式


7. 上电 ADC 并校准(每次上电后校准一次,测出 ADC 内部的固定零点偏差并补偿,之后就可以正常采样了。校准解决的是"天生不准",不是"随机抖动"。)

8. 启动转换并读取结果

第一步:光敏电阻是什么?

光敏电阻就是一个会随光照变化的电阻。

  • 光照强 → 电阻变小
  • 光照弱 → 电阻变大

它本身不会说话,不会告诉你"现在是多亮"。

第二步:怎么让单片机"看到"它?

光敏电阻单独接在单片机上,单片机是读不出来的。

所以我们要把它和一个固定电阻串在一起,接在 3.3V 和 GND 之间,中间那个点接到单片机的 PA5 引脚。

这样光照变化时,PA5 上的电压就会跟着变化。

第三步:单片机怎么读这个电压?

单片机有一个东西叫ADC,它的作用就是:把电压变成一个数字。

你的单片机 ADC 是 12 位的,它会把 0~3.3V 的电压分成 4096 个等级:

  • 0V → 数字 0
  • 3.3V → 数字 4095
  • 中间电压 → 中间的数字

所以 ADC 读出来的就是一个0 到 4095 之间的整数。

第四步:这个数字代表什么?

这个数字代表的是PA5 引脚上的电压高低,不是光照强度本身。

但因为你用了光敏电阻,电压会随光照变化,所以这个数字间接反映了光照强弱。

你的电路接法是:光越亮,ADC 数字越小;光越暗,ADC 数字越大。

第五步:怎么变成百分比?

百分比的意思就是:当前亮度占最大亮度的多少。

但是问题来了——你不知道"最亮"是多少,也不知道"最暗"是多少。

所以你要先测:

  1. 用手完全遮住光敏电阻,看 ADC 读出来是多少,比如 3800。这就是"最暗"。
  2. 用手电筒照光敏电阻,看 ADC 读出来是多少,比如 200。这就是"最亮"。

有了这两个数,就可以算了:

  • ADC = 200 → 100%(最亮)
  • ADC = 3800 → 0%(最暗)
  • ADC = 2000 → 中间某个百分比

公式就是:用"最暗的值减去当前值",再除以"最暗减最亮的范围",乘以 100,就得到百分比。

总结一句话

光敏电阻随光照变阻值 → 变成电压 → ADC 变成 0~4095 的数字 → 你先测出最亮和最暗时各是多少 → 用公式算出 0~100% → 串口打印出来。

只要你能测出遮光和光照下的两个 ADC 值,就能映射

不好意思,2026年11月4号的时候发现光敏的电压不够,现在是调整过的3.3v

通过网盘分享的文件:ADC
链接: https://pan.baidu.com/s/1TVpcMFcf8o6lLUOWRFFCAg?pwd=8888 提取码: 8888
--来自百度网盘超级会员v5的分享

其中发送数字转字符串的逻辑如下

/*
* ==================== 串口发送数字(先转换再发送,逻辑清晰版) ====================
*
* 转换原理:
* 1. 整数在内存里是二进制,没有"5、2、8"这样的字符,
* 必须用除法"算"出每一位。
*
* 2. 每次用 val % 10 取出最右边一位(个位),
* 再用 '0' + 余数 把数字 0~9 变成字符 '0'~'9'。
* 例如:数字 8 → '0' + 8 → 字符 '8'(ASCII 码 56)
*
* 3. 因为每次都取最右边的个位,所以先取到的是低位,
* 存进 buf 的顺序是反的。
* 例如 val=528:
* 第1轮:528 % 10 = 8 → buf[0]='8'
* 第2轮: 52 % 10 = 2 → buf[1]='2'
* 第3轮: 5 % 10 = 5 → buf[2]='5'
* 此时 buf = ['8']['2']['5'],是反的。
*
* 4. 翻转 buf:首尾交换,把反序变成正序。
* ['8']['2']['5'] → 交换 buf[0] 和 buf[2] → ['5']['2']['8']
*
* 5. 正序发送:从头到尾逐个发出去,串口收到的就是 "528"。
*
* 示例:val = 528
* 阶段1 转换后:buf = ['8']['2']['5'] i = 3
* 阶段2 翻转后:buf = ['5']['2']['8']
* 阶段3 发送: 依次发出 '5' '2' '8' → 串口显示 528
*/

然后是DMA的配置和讲解

开时钟 → 复位通道 → 配规则 → 使能DMA → 使能外设DMA请求 → 启动外设

  1. 开启 DMA 时钟
    DMA 也是一个外设,使用前必须先开时钟。DMA1 属于 AHB 总线,所以调用RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE)使能时钟。不开时钟的话,后面所有 DMA 寄存器都写不进去。

  2. 复位 DMA 通道
    调用DMA_DeInit(DMAy_Channelx),把该通道的寄存器恢复到默认状态。这样即使之前有人用过这个通道,也不会留下错误配置。

  3. 设置外设基地址
    告诉 DMA:数据从哪里来,或者数据送到哪里去。
    如果是从外设读取数据,就写外设的数据寄存器地址,例如(uint32_t)&USART1->DR或(uint32_t)&ADC1->DR。
    如果是向串口发送数据,源地址是内存,外设地址仍然是USART1->DR,因为 DMA 每次都要把数据写进同一个寄存器。

  4. 设置内存基地址
    告诉 DMA:数据要搬到内存里的哪个数组。
    例如你定义了uint8_t rx_buf[100];,就写(uint32_t)rx_buf。
    DMA 每收到一个数据,就会依次放进rx_buf[0]、rx_buf[1]、rx_buf[2]……
    所以内存基地址就是缓冲区的起始地址。

  5. 设置传输方向
    传输方向决定数据是从外设到内存,还是从内存到外设。

    • DMA_DIR_PeripheralSRC:外设是数据源,表示从外设搬到内存,常用于接收。
    • DMA_DIR_PeripheralDST:外设是目的地,表示从内存搬到外设,常用于发送。
  6. 设置缓冲区大小
    告诉 DMA 一共要搬运多少个数据。例如数组有 100 个元素,就写100。单位由后面的数据宽度决定:如果数据宽度是字节,就是 100 个字节;如果是半字,就是 100 个半字。

  7. 设置外设地址是否递增
    这个要看外设地址是不是同一个寄存器。
    像USART1->DR、ADC1->DR都是固定地址,每次都要访问同一个寄存器,所以选择DMA_PeripheralInc_Disable,表示外设地址不递增。
    如果外设是一连串寄存器,才需要开启递增。大多数情况下,外设地址都不递增。

  8. 设置内存地址是否递增
    如果数据要依次放进数组的每一个元素,就选择DMA_MemoryInc_Enable。
    例如接收数据时,第一个数据放buf[0],第二个放buf[1],第三个放buf[2],所以内存地址要递增。
    如果始终只操作同一个内存地址,就选择DMA_MemoryInc_Disable。

  9. 设置外设数据宽度
    告诉 DMA 每次从外设搬运多少数据。

    • Byte:每次搬 1 个字节,常用于 USART。
    • HalfWord:每次搬 2 个字节,常用于 ADC。
    • Word:每次搬 4 个字节,常用于 32 位数据。
      外设数据宽度要和外设寄存器宽度匹配,否则数据会错位。
  10. 设置内存数据宽度
    内存数据宽度通常和外设数据宽度保持一致。
    如果外设每次给 1 个字节,内存也按 1 个字节存放;如果外设每次给 2 个字节,内存也按 2 个字节存放。两边宽度不一致时,数据排列会出错。

  11. 设置工作模式

    • DMA_Mode_Normal:正常模式。搬运完指定数量的数据后,DMA 自动停止。
    • DMA_Mode_Circular:循环模式。搬运完成后自动回到起始位置继续搬运,适合连续采集数据,例如 ADC 连续采样。
  12. 设置通道优先级
    当多个 DMA 通道同时请求时,优先级高的先服务。可选 Low、Medium、High、VeryHigh。一般普通外设用 Medium 或 High 即可。

  13. 设置内存到内存模式
    如果不使用内存到内存拷贝,就选择DMA_M2M_Disable。
    如果开启DMA_M2M_Enable,DMA 可以不依赖外设,直接把一块内存的数据拷贝到另一块内存,类似于memcpy,但由硬件完成。

  14. 初始化 DMA 通道
    调用DMA_Init(DMAy_Channelx, &DMA_InitStructure),把前面设置的所有参数写入 DMA 通道寄存器。

  15. 使能 DMA 通道
    调用DMA_Cmd(DMAy_Channelx, ENABLE),DMA 通道正式开始工作。只初始化不使能,DMA 不会搬运数据。

  16. 使能外设的 DMA 请求
    仅使能 DMA 通道还不够,还必须让外设主动发出 DMA 请求。
    例如串口发送需要调用USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE),ADC 需要调用ADC_DMACmd(ADC1, ENABLE)。
    如果不开启外设的 DMA 请求,DMA 通道虽然已经使能,但不会开始传输。

通过网盘分享的文件:ADC +DMA
链接: https://pan.baidu.com/s/1d0BlhWj-LRbAZQ6BX-FIcg?pwd=8888 提取码: 8888
--来自百度网盘超级会员v5的分享

嵌入式外设开发实战流程

  1. 粗看手册,先抓框架

    • 看芯片参考手册里这个模块是干什么的。
    • 重点看:时钟、通道/引脚、寄存器、工作模式、触发条件。
    • 不要一开始死磕每个位,先知道“有哪些寄存器、哪些参数要配”。
  2. 用 AI 或例程梳理流程

    • 让 AI 给出配置步骤,比如 DMA 的:
      • 开时钟
      • 复位通道
      • 配源地址、目标地址、方向、长度、递增、模式
      • 使能 DMA
      • 使能外设 DMA 请求
      • 启动外设
    • 这一步不是照抄,而是先建立正确骨架。
  3. 对照手册逐项核对

    • 每个参数都要回到手册确认:
      • 通道是否对应正确?
      • 地址是不是外设数据寄存器?
      • 方向是外设到内存,还是内存到外设?
      • 数据宽度是否匹配?
      • 是否需要循环模式?
      • 是否需要中断?
    • 这一步最容易发现错误,比如 DMA 方向配反、通道选错、没开外设 DMA 请求。
  4. 写最小可运行代码

    • 先只实现核心功能,不要一次加太多东西。
    • 比如 DMA + ADC 先只验证:
      • 数组能被自动填充;
      • remain会变化;
      • 数据会循环刷新。
  5. 烧录验证,看现象

    • 用串口、LED、示波器、调试器观察。
    • 如果现象不对,不要乱改,先判断是哪一步没生效:
      • 没开时钟 → 模块不工作;
      • 通道没使能 → 不搬运;
      • 外设没开 DMA 请求 → 不触发;
      • 方向/地址错 → 数据不对。
  6. 根据手册微调

    • 发现异常后,再回到手册查具体位域。
    • 比如 DMA 模式、优先级、中断标志、传输完成判断等。
  7. 封装成稳定函数

    • 验证通过后,把初始化、启动、状态查询封装成函数。
    • 后面换外设时,只要改源地址、方向、通道即可。

套到 DMA 上就是

看手册 → 列步骤 → 写初始化 → 开时钟 → 配通道 → 使能 DMA → 使能外设 DMA 请求 → 启动外设 → 串口/调试验证 → 对照手册修正。

你总结的“看手册 → AI 给流程 → 按手册改”已经抓住了重点,真正拉开差距的地方就在第三步:每个配置项都要回到手册确认,而不是只凭感觉改

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/6 3:22:12

【芳芯科技】图像识别教室无人关灯系统

实物效果图:实现功能:1.采用ARM内核STM32单片机作为控制核心。2.采用OV7670摄像头实现图像获取。3.采用TFT2.8显示配合图像调试显示。4.通过按键进行布放,布放后采用图像算法进行检测,检测到有人体后通过电开关进行教室灯光的开启…

作者头像 李华
网站建设 2026/11/6 3:20:57

互联网的数据请求的架构演变

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/6 3:12:26

Spyglass:官方Hands-on Training(一)

相关阅读 Spyglasshttps://blog.csdn.net/weixin_45791458/category_12828934.html?spm1001.2014.3001.5482 本文是对Spyglass Hands-on Training中第一个实验的翻译(有删改),Lab文件可以从以下链接获取。Spyglass Hands-on Traininghttps:…

作者头像 李华
网站建设 2026/11/6 3:11:11

用 mpv 换掉浏览器的播放芯:Jellyfin 桌面客户端快速上手

用 mpv 换掉浏览器的播放芯:Jellyfin 桌面客户端快速上手 【免费下载链接】jellyfin-desktop Jellyfin Desktop Client 项目地址: https://gitcode.com/GitHub_Trending/je/jellyfin-desktop 周五晚上你打开浏览器看那部 4K 电影,进度条拖两秒才动…

作者头像 李华
网站建设 2026/11/6 3:08:51

高通学习9--烧写

1 QFIL WIndows 来自:高通系列烧写操作指南_Telpo开发者平台 https://www.cnblogs.com/blogs-of-lxl/p/10620662.html 这个看起来简单,实际也有很多门门道道,而且有时候这个是bringup的第一步,有可能在这里烧写会失败&#xff0…

作者头像 李华
网站建设 2026/11/6 3:05:48

工业控制器分级存储设计:EEPROM、NOR Flash与SD卡选型及STM32/FPGA协同

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华