软件环境:Keil5
硬件环境:STM32F103C8T6
最近有个项目需要用到STM32F0产生一定数量不同占空比的单脉冲信号,初步构思了一下。以前配置一些传感器即根据时序图写脉冲序列就是用简单的延时模拟单个脉冲信号,这种方法在工程项目中太浪费CPU资源。定时器产生的PWM波又是连续的,如果能让连续的PWM波变成单个的,那么就可以简单的通过写CCRx寄存器产生单脉冲信号,所以可以开一个定时器捕获PWM脉冲的上升沿,在第二个上升沿的时候关闭定时器。这个方案其实也想试试,但是如果数据量很大的情况下,每一个字节的发送就要进出中断8次,感觉不是很好。
结合STM32F0系列吃紧的资源,初步选用F0上面的大概筛选了3种方法去实现我们需要的单脉冲信号!
PlanA : 利用单脉冲信号
翻了翻手册,是有一个寄存器去开启定时器单脉冲模式的。

讯享网
即开启了这个寄存器以后,计数器可以自动地在产生下一个更新时间UEV时停止。完美的满足了我们的需求,控制简单,占用内存资源少。下面上代码:
#include "pwm.h" //TIM3CH1 <--> PA6 extern u16 DMA_Buff[8]; void PWM_Init(u16 arr,u16 psc) {
GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO , ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3 , ENABLE); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA,&GPIO_InitStructure); TIM_TimeBaseInitStructure.TIM_ClockDivision = 0; TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInitStructure.TIM_Period = arr; TIM_TimeBaseInitStructure.TIM_Prescaler = psc; TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure); TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = 0; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC1Init(TIM3,&TIM_OCInitStructure); TIM_OC1PreloadConfig(TIM3,TIM_OCPreload_Enable); TIM_SelectOnePulseMode(TIM3,TIM_OPMode_Single);//开启单脉冲模式 第二次调用SetCompare一直产生不了波形,初步怀疑这个函数产生了某个事件,没有清除,导致的。 TIM_Cmd(TIM3, ENABLE); //使能TIM3 } int main(void) {
delay_init(); //延时函数初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级 uart_init(); //串口初始化为 LED_Init(); //LED端口初始化 PWM_Init(1000-1,72-1); //TIM3CH1 TIM_SetCompare1(TIM3,500); while(1) {
} }
讯享网
这个方法的关键在于设置好定时器的PWM模式后,开启TIM_SelectOnePulseMode(TIM3,TIM_OPMode_Single);这个函数,打开定时器单脉冲模式,然后设置一次TIM_SetCompare1(TIM3,500);开启脉冲传输。仿真后的波形图为:

改变TIM_SetCompare1(TIM3,x)x的值即可获得不同占空比的单脉冲信号!但是测试的时候,重复调用TIM_SetCompare1这个函数发现并不能产生第二个波形,顿时陷入了自我怀疑,初步怀疑是某个事件没有清楚导致PWM通道被强制关闭了,重新初始化TIM3定时器倒是可以发送新的脉冲信号,但是每一个脉冲信号都要重新配置一遍定时器这点实在是让人不能接受,遂放弃这个方案。
用高级定时器可以通过设置这个RCR寄存器产生多个脉冲
没有试这个方案,看起来并不能改变每个脉冲的占空比。
PlanB :DMA+TIM 利用定时器和DMA去完成(亲测成功)
这个方案是网上看到的比较主流的产生一定数量不同占空比的单脉冲信号,利用了定时器输出比较通道产生PWM波,然后将DMA与该通道的CCRx寄存器“绑定”,DMA把内存中数组的值发送给“绑定”的CCRx寄存器,以产生不同占空比的信号,当DMA传输完成时,进入DMA传输完成中断关闭定时器即可。每次发送DMA时在开启定时器。
虽然看起来感觉不是很难,但是调试过程真心折磨人,我也是在出错了很多次以后,才意识到这个方案有两种解决问题的方法。一种是通过 更新事件+DMA“绑定”CCRx寄存器 ,即通过改变输出比较的阈值(CCRx)改变信号占空比,然后通过更新事件触发DMA计数次数从而产生中断,另一种是通过 输出比较事件+DMA“绑定”ARR寄存器,即通过改变定时器的计数次数(ARR)的值改变信号占空比,然后通过每次输出比较事件的产生触发DMA计数从而产生中断。
简单来说,就是第一种用了TIM3_UP这个DMA通道,即DMA1CH3,将定时器与该通道绑定,每一次计数到ARR时,就会产生更新事件来增加DMA计数。该方法对应的库函数为:
TIM_DMACmd(TIM3, TIM_DMA_Update, ENABLE);
DMA1_CH3_Init(DMA1_Channel3,(u32)&TIM3->CCR1,(u32)DMA_Buff,8);
第二种用了TIM3_CH1这个通道,即对应DMA1CH6,通过DMA与ARR寄存器绑定,而CCRx寄存器的值不改变,来改变每个脉冲的占空比,同时每一个脉冲会产生一个输出比较事件,该事件会增加DMA的计数。该方法对应的库函数为:
TIM_DMACmd(TIM3, TIM_DMA_CC1, ENABLE);
DMA1_CH3_Init(DMA1_Channel6,(u32)&TIM3->ARR,(u32)DMA_Buff,8);

下面先上第一种方法代码:
讯享网//定时器配置函数 >< >< >< >< >< >< >< >< >< >< >< >< #include "pwm.h" //TIM3CH1 <--> PA6 extern u16 DMA_Buff[8]; void PWM_Init(u16 arr,u16 psc) {
GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO , ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3 , ENABLE); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA,&GPIO_InitStructure); TIM_TimeBaseInitStructure.TIM_ClockDivision = 0; TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInitStructure.TIM_Period = arr; TIM_TimeBaseInitStructure.TIM_Prescaler = psc; TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure); TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = DMA_Buff[0]; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //TIM相关初始化! TIM_OC1Init(TIM3,&TIM_OCInitStructure); TIM_DMACmd(TIM3, TIM_DMA_Update, ENABLE); //使能定时器更新事件增加DMA计数 //TIM_DMACmd(TIM3, TIM_DMA_CC1 ,ENABLE); TIM_Cmd(TIM3, ENABLE); //使能TIM3 } //DMA配置函数!@!!@!@!@!@!@!@!@!@!@!@!@!@!@!@!@ #include "dma.h" #include "usart.h" DMA_Tx_Achieve_Flag dmaTx_Flag ; /* 初始化DMA1通道6 <--> TIM3CH1 <--> PA6 函数思路:通过DMA从内存拿出数组传递给TIMx->CCR1,改变PWM波的占空比 传递完成后进入DMA传输完成中断,关闭定时器。 */ void DMA1_CH3_Init(DMA_Channel_TypeDef* DMAy_Channelx,u32 perAddr,u32 memAddr,u16 ndtr) {
DMA_InitTypeDef DMA_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE); __NOP(); DMA_DeInit(DMAy_Channelx);//软件复位后在进行查忙判断,不然会卡死!!用while的坏处。。 while ( 0 != DMA_GetCurrDataCounter(DMAy_Channelx)){
}//等待DMA可配置 //printf("DMA Init Success!@!"); // DMA_InitStructure.DMA_BufferSize = ndtr; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;/* 数据传输方向,从内存读取发送到外设 */ DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_InitStructure.DMA_MemoryBaseAddr = memAddr; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; /*正常模式,即由程序控制发送数据*/ DMA_InitStructure.DMA_PeripheralBaseAddr = perAddr; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; /*半字,即16字节,和数组对应*/ DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_Init(DMAy_Channelx,&DMA_InitStructure); // NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel3_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; NVIC_Init(&NVIC_InitStructure); // // DMA_ITConfig(DMAy_Channelx,DMA_IT_TC,ENABLE); DMA_ClearITPendingBit(DMA1_IT_TC3); //这里比较恶心,想换通道还要改这里的标志位! // } //开启一次DMA传输 void MYDMA_Enable(DMA_Channel_TypeDef* DMA_CHx,u16 myndtr) {
DMA_Cmd(DMA_CHx, DISABLE ); //关闭USART1 TX DMA1 所指示的通道 //printf("完成了一次DMA传输!"); DMA_SetCurrDataCounter(DMA_CHx,myndtr);//DMA通道的DMA缓存的大小 DMA_Cmd(DMA_CHx, ENABLE); //使能USART1 TX DMA1 所指示的通道 TIM_Cmd(TIM3,ENABLE); } //进入这里就说明我们已经成功发送了一个字节的数据给到外设,那么我们在这里就可以;/ //关闭定时器,防止产生过多的脉冲,而且进入这里以后,说明当前DMA没有在发送数据,那么我们就可以;/ //在这里做一个判断,去告诉外界当前DMA状态! //如果不加DMA发送完成中断,在DMA发送完成中断中关闭定时器,那么就会在发送第8个数据后持续发送第8个数据! void DMA1_Channel3_IRQHandler(void) {
if(SET == DMA_GetITStatus(DMA1_IT_TC3)) {
//dmaTx_Flag = Tx_OK; //printf("DMA发送完成!"); TIM_Cmd(TIM3,DISABLE); DMA_ClearITPendingBit(DMA1_IT_TC3); } } //主函数!@!!@!@!@!@!@!@!@!@!@!@!@!@!@!@!@ #include "led.h" #include "delay.h" #include "key.h" #include "sys.h" #include "usart.h" #include "timer.h" #include "pwm.h" #include "dma.h" u16 DMA_Buff[8] = {
100,200,300,400,500,600,700,800}; /* 目的:生成固定数量不同占空比的脉冲,可驱动彩灯IC,数码管IC PlanA : 利用单脉冲 失败 PlanB : DMA+TIM 成功 PlanC : 主从定时器 待测试(较高级) */ int main(void) {
delay_init(); //延时函数初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级 uart_init(); //串口初始化为 LED_Init(); //LED端口初始化 PWM_Init(1000-1,72-1); //TIM3CH1 //TIM_SetCompare1(TIM3,500); DMA1_CH3_Init(DMA1_Channel3,(u32)&TIM3->CCR1,(u32)DMA_Buff,8); //DMA1_CH3_Init(DMA1_Channel6,(u32)&TIM3->ARR,(u32)DMA_Buff,8); MYDMA_Enable(DMA1_Channel3,8);//将DMA_Buff的数据发送到CCR1上面 while(1) {
} }
打开虚拟示波器可见:

产生了8个,占空比依次增加的单脉冲信号,通过改变数组的大小和对应的CCRx的值,即可增加至40甚至更多个单脉冲信号。
当然也可以产生连续的多个信号:

每开启一次DMA传输,便将数组数据传输到对应的寄存器,最好有一定间隔或者在中断中进行判断一下。
在函数中注释的两部分分别是:
//TIM_DMACmd(TIM3, TIM_DMA_CC1 ,ENABLE);
//DMA1_CH3_Init(DMA1_Channel6,(u32)&TIM3->ARR,(u32)DMA_Buff,8);
这两个即为第二种方法,显示的波形和上图没差,这里就不展示了。
在调试的过程中,我错误的以为我用了定时器输出比较通道,就要把DMA绑定到CCRx寄存器,但其实应该绑定到DMA1CH3,这上面有TIM3_UP,即通过更新事件的次数(到达ARR的次数)来改变DMA计数的值。否则出现的波形数量会少于数组设置的大小。

其实这些方法的本质,就是决定什么时候DMA传输完成了进入中断了,因为只有及时进入中断关闭定时器,才不会产生多余的脉冲信号,这也是这个方案较大的弊端把,我在中断里加了一个printf作为DEBUG结果生生多了4个波形!
PlanC:通过主从定时器模式+PlanA
看手册的时候发现手册里记录一种产生单个脉冲信号的方法:

看了一下这个方法应该是和项目用法匹配度最高的,记录一下,有时间的时候测试!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/42600.html