F407定时器PWM输出指定数量脉冲程序
DMA 独立计数法推荐多轴高频场景零脉冲开销为了释放CPU可以利用 TIM8 4个通道各自独立的Compare (CC) DMA请求。让硬件DMA去数脉冲CPU只在脉冲发送完毕时介入一次。实现机制定时器每次输出一个PWM脉冲发生比较匹配时触发一次通道的DMA请求。DMA将一个“无用数据”搬运到“无用地址”纯为了消耗DMA自身的传输计数器NDTR。当NDTR减到0时触发DMA传输完成中断TC我们在DMA的中断中关闭PWM输出。DMA通道映射TIM8_CH1 - DMA2_Stream2 (Channel 7)TIM8_CH2 - DMA2_Stream3 (Channel 7)TIM8_CH3 - DMA2_Stream4 (Channel 7)TIM8_CH4 - DMA2_Stream7 (Channel 7)测试代码这是一个基于 STM32 标准外设库SPLStandard Peripheral Library的完整 C 和 H 代码实现。tim8_pwm_dma.h#ifndef __TIM8_PWM_DMA_H #define __TIM8_PWM_DMA_H #include stm32f4xx.h // 定义控制通道 typedef enum { AXIS_CH1 1, AXIS_CH2 2, AXIS_CH3 3, AXIS_CH4 4 } TIM8_Channel_e; // 外部调用接口 void TIM8_PWM_DMA_Init(void); void TIM8_Output_Pulses(TIM8_Channel_e channel, uint32_t pulses); #endif /* __TIM8_PWM_DMA_H */tim8_pwm_dma.c#include tim8_pwm_dma.h /* * 假负载目标地址DMA只需要每次脉冲触发时搬运一次数据来消耗 NDTR 计数器 * 因此将数据搬运到这些无意义的变量中。 */ static uint16_t dummy_dst[4] {0, 0, 0, 0}; /** * brief 初始化 GPIO, TIM8 及对应 DMA2 * note 使用引脚 PC6, PC7, PC8, PC9 (AF3) 输出 TIM8 CH1-CH4 */ void TIM8_PWM_DMA_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; DMA_InitTypeDef DMA_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; /* 1. 使能时钟 */ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_DMA2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM8, ENABLE); /* 2. 配置 GPIO (PC6 - PC9 复用为 TIM8) */ GPIO_PinAFConfig(GPIOC, GPIO_PinSource6, GPIO_AF_TIM8); GPIO_PinAFConfig(GPIOC, GPIO_PinSource7, GPIO_AF_TIM8); GPIO_PinAFConfig(GPIOC, GPIO_PinSource8, GPIO_AF_TIM8); GPIO_PinAFConfig(GPIOC, GPIO_PinSource9, GPIO_AF_TIM8); GPIO_InitStructure.GPIO_Pin GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_OType GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd GPIO_PuPd_DOWN ; GPIO_Init(GPIOC, GPIO_InitStructure); /* 3. 配置 TIM8 基础时基 (这里假设 APB284MHz, TIM8 Clk168MHz. 生成 10kHz 频率) */ TIM_TimeBaseStructure.TIM_Prescaler 168 - 1; // 1MHz 计数频率 TIM_TimeBaseStructure.TIM_Period 100 - 1; // 10kHz PWM 频率 TIM_TimeBaseStructure.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_RepetitionCounter 0; TIM_TimeBaseInit(TIM8, TIM_TimeBaseStructure); /* 4. 配置 TIM8 输出比较 (PWM1 模式初始占空比 50%) */ TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Disable; // 初始关闭需要时打开 TIM_OCInitStructure.TIM_OutputNState TIM_OutputNState_Disable; TIM_OCInitStructure.TIM_Pulse 50; // 50% 占空比 TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; TIM_OCInitStructure.TIM_OCIdleState TIM_OCIdleState_Reset; TIM_OC1Init(TIM8, TIM_OCInitStructure); TIM_OC1PreloadConfig(TIM8, TIM_OCPreload_Enable); TIM_OC2Init(TIM8, TIM_OCInitStructure); TIM_OC2PreloadConfig(TIM8, TIM_OCPreload_Enable); TIM_OC3Init(TIM8, TIM_OCInitStructure); TIM_OC3PreloadConfig(TIM8, TIM_OCPreload_Enable); TIM_OC4Init(TIM8, TIM_OCInitStructure); TIM_OC4PreloadConfig(TIM8, TIM_OCPreload_Enable); TIM_CtrlPWMOutputs(TIM8, ENABLE); // 使能主输出 MOE TIM_Cmd(TIM8, ENABLE); // 开启定时器计数此时还没有脉冲因为 CCxE 为 0 /* 5. 配置 DMA2 公共基础参数 */ DMA_DeInit(DMA2_Stream2); DMA_DeInit(DMA2_Stream3); DMA_DeInit(DMA2_Stream4); DMA_DeInit(DMA2_Stream7); DMA_InitStructure.DMA_Channel DMA_Channel_7; // TIM8 CH1-CH4 在 DMA2 中均为 Channel 7 DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralToMemory; // 必须是外设到内存响应定时器请求 DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Disable; // 不自增反复写一个假地址 DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode DMA_Mode_Normal; // 单次传输发完即停 DMA_InitStructure.DMA_Priority DMA_Priority_High; DMA_InitStructure.DMA_FIFOMode DMA_FIFOMode_Disable; DMA_InitStructure.DMA_FIFOThreshold DMA_FIFOThreshold_1QuarterFull; DMA_InitStructure.DMA_MemoryBurst DMA_MemoryBurst_Single; DMA_InitStructure.DMA_PeripheralBurst DMA_PeripheralBurst_Single; /* 配置各通道 DMA 的源地址和目标地址 */ // CH1: DMA2_Stream2 DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)(TIM8-CCR1); DMA_InitStructure.DMA_Memory0BaseAddr (uint32_t)dummy_dst[0]; DMA_Init(DMA2_Stream2, DMA_InitStructure); // CH2: DMA2_Stream3 DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)(TIM8-CCR2); DMA_InitStructure.DMA_Memory0BaseAddr (uint32_t)dummy_dst[1]; DMA_Init(DMA2_Stream3, DMA_InitStructure); // CH3: DMA2_Stream4 DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)(TIM8-CCR3); DMA_InitStructure.DMA_Memory0BaseAddr (uint32_t)dummy_dst[2]; DMA_Init(DMA2_Stream4, DMA_InitStructure); // CH4: DMA2_Stream7 DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)(TIM8-CCR4); DMA_InitStructure.DMA_Memory0BaseAddr (uint32_t)dummy_dst[3]; DMA_Init(DMA2_Stream7, DMA_InitStructure); /* 6. 配置 DMA 中断 (NVIC) */ NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority 0; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_InitStructure.NVIC_IRQChannel DMA2_Stream2_IRQn; NVIC_Init(NVIC_InitStructure); NVIC_InitStructure.NVIC_IRQChannel DMA2_Stream3_IRQn; NVIC_Init(NVIC_InitStructure); NVIC_InitStructure.NVIC_IRQChannel DMA2_Stream4_IRQn; NVIC_Init(NVIC_InitStructure); NVIC_InitStructure.NVIC_IRQChannel DMA2_Stream7_IRQn; NVIC_Init(NVIC_InitStructure); } /** * brief 启动指定通道输出特定数量的脉冲 * param channel: AXIS_CH1 ~ AXIS_CH4 * param pulses: 目标脉冲数量 */ void TIM8_Output_Pulses(TIM8_Channel_e channel, uint32_t pulses) { if(pulses 0) return; switch(channel) { case AXIS_CH1: // 1. 关闭通道和DMA请求 TIM8-CCER ~TIM_CCER_CC1E; TIM_DMACmd(TIM8, TIM_DMA_CC1, DISABLE); DMA_Cmd(DMA2_Stream2, DISABLE); // 2. 配置新的传输数量并清标志 DMA_SetCurrDataCounter(DMA2_Stream2, pulses); DMA_ClearFlag(DMA2_Stream2, DMA_FLAG_TCIF2 | DMA_FLAG_HTIF2 | DMA_FLAG_TEIF2); // 3. 开启 DMA 传输完成中断 DMA_ITConfig(DMA2_Stream2, DMA_IT_TC, ENABLE); // 4. 按顺序重新使能 DMA - TIM_DMA - TIM_CCxE 开启输出 DMA_Cmd(DMA2_Stream2, ENABLE); TIM_DMACmd(TIM8, TIM_DMA_CC1, ENABLE); TIM8-CCER | TIM_CCER_CC1E; break; case AXIS_CH2: TIM8-CCER ~TIM_CCER_CC2E; TIM_DMACmd(TIM8, TIM_DMA_CC2, DISABLE); DMA_Cmd(DMA2_Stream3, DISABLE); DMA_SetCurrDataCounter(DMA2_Stream3, pulses); DMA_ClearFlag(DMA2_Stream3, DMA_FLAG_TCIF3 | DMA_FLAG_HTIF3 | DMA_FLAG_TEIF3); DMA_ITConfig(DMA2_Stream3, DMA_IT_TC, ENABLE); DMA_Cmd(DMA2_Stream3, ENABLE); TIM_DMACmd(TIM8, TIM_DMA_CC2, ENABLE); TIM8-CCER | TIM_CCER_CC2E; break; case AXIS_CH3: TIM8-CCER ~TIM_CCER_CC3E; TIM_DMACmd(TIM8, TIM_DMA_CC3, DISABLE); DMA_Cmd(DMA2_Stream4, DISABLE); DMA_SetCurrDataCounter(DMA2_Stream4, pulses); DMA_ClearFlag(DMA2_Stream4, DMA_FLAG_TCIF4 | DMA_FLAG_HTIF4 | DMA_FLAG_TEIF4); DMA_ITConfig(DMA2_Stream4, DMA_IT_TC, ENABLE); DMA_Cmd(DMA2_Stream4, ENABLE); TIM_DMACmd(TIM8, TIM_DMA_CC3, ENABLE); TIM8-CCER | TIM_CCER_CC3E; break; case AXIS_CH4: TIM8-CCER ~TIM_CCER_CC4E; TIM_DMACmd(TIM8, TIM_DMA_CC4, DISABLE); DMA_Cmd(DMA2_Stream7, DISABLE); DMA_SetCurrDataCounter(DMA2_Stream7, pulses); DMA_ClearFlag(DMA2_Stream7, DMA_FLAG_TCIF7 | DMA_FLAG_HTIF7 | DMA_FLAG_TEIF7); DMA_ITConfig(DMA2_Stream7, DMA_IT_TC, ENABLE); DMA_Cmd(DMA2_Stream7, ENABLE); TIM_DMACmd(TIM8, TIM_DMA_CC4, ENABLE); TIM8-CCER | TIM_CCER_CC4E; break; } } /* 中断服务函数 */ void DMA2_Stream2_IRQHandler(void) { // CH1 DMA 传输完成 if (DMA_GetITStatus(DMA2_Stream2, DMA_IT_TCIF2) ! RESET) { TIM8-CCER ~TIM_CCER_CC1E; // 核心瞬间切断硬件输出使能 TIM_DMACmd(TIM8, TIM_DMA_CC1, DISABLE);// 停止DMA请求 DMA_ClearITPendingBit(DMA2_Stream2, DMA_IT_TCIF2); } } void DMA2_Stream3_IRQHandler(void) { // CH2 DMA 传输完成 if (DMA_GetITStatus(DMA2_Stream3, DMA_IT_TCIF3) ! RESET) { TIM8-CCER ~TIM_CCER_CC2E; TIM_DMACmd(TIM8, TIM_DMA_CC2, DISABLE); DMA_ClearITPendingBit(DMA2_Stream3, DMA_IT_TCIF3); } } void DMA2_Stream4_IRQHandler(void) { // CH3 DMA 传输完成 if (DMA_GetITStatus(DMA2_Stream4, DMA_IT_TCIF4) ! RESET) { TIM8-CCER ~TIM_CCER_CC3E; TIM_DMACmd(TIM8, TIM_DMA_CC3, DISABLE); DMA_ClearITPendingBit(DMA2_Stream4, DMA_IT_TCIF4); } } void DMA2_Stream7_IRQHandler(void) { // CH4 DMA 传输完成 if (DMA_GetITStatus(DMA2_Stream7, DMA_IT_TCIF7) ! RESET) { TIM8-CCER ~TIM_CCER_CC4E; TIM_DMACmd(TIM8, TIM_DMA_CC4, DISABLE); DMA_ClearITPendingBit(DMA2_Stream7, DMA_IT_TCIF7); } }主函数测试#include sys.h #include delay.h #include stm32f4xx.h #include stm32f4xx_conf.h #include tim8_pwm_dma.h int main(void) { TIM8_PWM_DMA_Init(); while(1) { TIM8_Output_Pulses(AXIS_CH1,21); delay_ms(20); } }使用仿真测试平台实现效果如下一次发出了21个PWM脉冲

相关新闻

打包一次,随处安装:Observal Agent构建、发布与跨harness分发的完整实操指南

打包一次,随处安装:Observal Agent构建、发布与跨harness分发的完整实操指南

打包一次,随处安装:Observal Agent构建、发布与跨harness分发的完整实操指南 【免费下载链接】Observal Observal is self-hosted registry for your coding agent extensions with a built in insight engine. Setup Observal, define the scope and sh…

2026/9/28 21:03:53 阅读更多 →
国产编码大模型推荐

国产编码大模型推荐

说明 下图为博主近期使用的大模型情况,除 DeepSeek,其他都是用的 GLM 5.2/5.3锐评一波DeepSeek:用的 v4-pro,效果不好,提交代码,操作半天;速度慢;Zhipu:先开的 Lite 版&a…

2026/9/28 21:03:53 阅读更多 →
Lap 外部应用集成指南:3 步设置多个外部打开程序与默认应用

Lap 外部应用集成指南:3 步设置多个外部打开程序与默认应用

Lap 外部应用集成指南:3 步设置多个外部打开程序与默认应用 【免费下载链接】lap An offline-first photo manager for large local libraries 项目地址: https://gitcode.com/GitHub_Trending/lap3/lap Lap 是一款离线优先的照片管理器(offline-…

2026/9/28 21:03:53 阅读更多 →

最新新闻

辉芒微FT62F28X烧录与调试避坑指南

辉芒微FT62F28X烧录与调试避坑指南

1. 项目概述:为什么辉芒微FT62F28X的烧录与调试值得专门拆解FMD IDE、辉芒微、FT62F28X、烧录、调试——这五个词组合在一起,不是泛泛而谈的“单片机开发入门”,而是指向一个非常具体、非常真实、也相当容易踩坑的工程现场:一款国…

2026/9/28 21:54:35 阅读更多 →
AI自主提交128个PR重构83万行代码的工程方法论

AI自主提交128个PR重构83万行代码的工程方法论

前几天看到 GitHub 那波"AI 自己给自己提交了128个PR、改了83万行代码"的消息时,我第一反应是:又来了个噱头。但把三周时间线、PR列表和自动化验证的细节翻了一遍之后,我发现真正值得聊的其实不是"AI 重写自己"这个标题&…

2026/9/28 21:54:35 阅读更多 →
Superpowers实战:给Codex与Claude Code装上结构化技能库

Superpowers实战:给Codex与Claude Code装上结构化技能库

最近一段时间我几乎逢人就推荐一个东西:给手头的 Codex(或者 Claude Code,看你习惯用哪个)装上 superpowers。你第一次听到这个名字可能会觉得夸张,但它解决的事情非常具体——默认状态下,AI 编码代理更像一…

2026/9/28 21:54:35 阅读更多 →
AI批量重写存量代码:GitHub三周128个PR的工程实践拆解

AI批量重写存量代码:GitHub三周128个PR的工程实践拆解

1. 一个反直觉的工程选择:让 AI 修改自己的源码先说一个可能和多数人直觉相悖的事实:GitHub 这个承载了全球数亿个代码仓库的平台,其自身的代码库也面临着所有老系统都会遇到的麻烦——技术债堆叠、依赖版本过于陈旧、核心服务之间的耦合越来…

2026/9/28 21:54:35 阅读更多 →
STM32串口调试5分钟闭环:USB转TTL驱动与硬件连接全指南

STM32串口调试5分钟闭环:USB转TTL驱动与硬件连接全指南

1. 为什么“5分钟搞定”不是口号,而是可复现的操作节奏STM32串口通信,是每个嵌入式新手跨出开发板点亮LED后的第一道真实门槛。它不像GPIO那样只写寄存器就能看到结果,而是一条需要两端协同、软硬咬合、信号精准对齐的“数据通道”。你手里的…

2026/9/28 21:53:33 阅读更多 →
校园POS消费数据清洗与行为建模实战指南

校园POS消费数据清洗与行为建模实战指南

简介:本资源是一份面向本科生与Python初学者的校园消费行为分析实战项目,适用于毕业设计、期末大作业及课程设计场景,聚焦学生群体消费偏好、时段规律与食堂就餐结构等现实问题,助力掌握从数据清洗到建模可视化的完整分析链路。压…

2026/9/28 21:53:33 阅读更多 →

日新闻

济南做网站多少钱:3个案例拆解,防黑源码下载全攻略

济南做网站多少钱:3个案例拆解,防黑源码下载全攻略

济南做网站多少钱:3个案例拆解,防黑源码下载全攻略 上周济南一个做建材的老板找我,脸都绿了。他的官网首页弹出了赌博广告,后台被植入了挖矿脚本。他慌得问我:“网站被黑挂马不知道怎么办?能不能直接找之前的外包公司要源码下载,看看哪里被动了手脚?…

2026/9/28 0:00:34 阅读更多 →
婚恋网站实战案例:避开3个高价坑,省钱50%还能跑赢流量

婚恋网站实战案例:避开3个高价坑,省钱50%还能跑赢流量

婚恋网站实战案例:避开3个高价坑,省钱50%还能跑赢流量 找婚恋网站建站公司,最怕的就是被坑高价。很多同行跟我吐槽,报价单上写得模棱两可,功能栏里全是“高级定制”、“专属UI”,结果落地全是套壳。今天不聊虚的,直接甩几个我经手的 实战案例…

2026/9/28 0:00:34 阅读更多 →
制作网页比较方便的软件怎么选?一文搞懂避坑指南

制作网页比较方便的软件怎么选?一文搞懂避坑指南

制作网页比较方便的软件怎么选?一文搞懂避坑指南 很多老板一上来就问:做个网站多少钱?但我反问他:你的域名买了吗?服务器租了吗?他一脸懵。这就是典型的“域名服务器搞不懂”。别急,今天咱们不聊虚的,直接 一文搞懂 那些让你头秃的技术名词。…

2026/9/28 0:00:34 阅读更多 →

周新闻

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/9/28 5:40:26 阅读更多 →
SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/9/28 9:47:26 阅读更多 →
FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏 【免费下载链接】FireRed-OpenStoryline FireRed-OpenStoryline is an AI video editing agent that transforms manual editing into intention-driven directing through natural language …

2026/9/28 8:07:01 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/28 16:55:15 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/28 3:51:11 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/26 22:52:30 阅读更多 →