使用stm32制作双脉冲发生器
在工作中由于需要对电机驱动电路进行双脉冲测试,以检测电机驱动电路的性能,之前用过555定时器做过一板,但结果不是很理想,后面不甘心就利用业余时间重新做一板双脉冲测试板,不得不说软件的灵活性还是要远远大于硬件的,PCB见下图一,没有使用的I/O口均引出备用。软件主要实现以下功能:
1、每按一次增大按钮,脉冲2以延长10us;
2、每按一次减小按钮,脉冲2以减小10us;
3、按输出按钮,输出两个脉冲;
4、根据AD采样值,定时器TIM2对应的PA0、PA1、PA2、PA3共四个通道输出固定频率,不同占空比的PWM波。
由于网上的参考代码较多,使用库函数编写,程序较为简单,主要驱动代码有:
#include "pwm.h" //PWM驱动
#include "adc.h" //ADC驱动
#include "key.h" //按键驱动
#include "oled.h" //OLED屏驱动
#include "bmp.h" //图像驱动
#include "exti.h" //外部中断驱动程序
PWM驱动程序如下:
/**********************
函数名称:P_OUT_Init
功 能:脉冲PA、PB输出初始化
参 数:无
返回值:无
作 者:lkjx
*********************/
void P_OUT_Init()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能PBD端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_6; //PB5 PB6 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure); //根据设定参数初始化GPIOB
GPIO_ResetBits(GPIOB,GPIO_Pin_5|GPIO_Pin_6); //PB5 PB6 输出低
}
/**********************
函数名称:TIM2_PWM_Init
功 能:定时器2通道1、2、3、4的PWM模块输出初始化
参 数:arr--------自动重装值
psc--------时钟预分频数
返回值:无
作 者:lkjx
*********************/
void TIM2_PWM_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE); //使能GPIO外设时钟使能
//设置该引脚为复用输出功能,输出TIM2 CH1\CH2\CH3\CH4\的GPIO_A0、GPIO_PA1、GPIO_PA2、GPIO_PA3的PWM脉冲波形
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 |GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 ; //TIM_CH1 TIM_CH2 TIM_CH3 TIM_CH4
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//初始化TIM2
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值 20K
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值 不分频
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
//初始化TIM2 Channel1的PWM输出模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
//TIM_OCInitStructure.TIM_Pulse = 0; //设置待装入捕获比较寄存器的脉冲值
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
TIM_OC1Init(TIM2, &TIM_OCInitStructure); //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
//初始化TIM2 Channel2的PWM输出模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
//TIM_OCInitStructure.TIM_Pulse = 0; //设置待装入捕获比较寄存器的脉冲值
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
TIM_OC2Init(TIM2, &TIM_OCInitStructure); //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
//初始化TIM2 Channel3的PWM输出模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
//TIM_OCInitStructure.TIM_Pulse = 0; //设置待装入捕获比较寄存器的脉冲值
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
TIM_OC3Init(TIM2, &TIM_OCInitStructure); //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
//初始化TIM2 Channel4的PWM输出模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
//TIM_OCInitStructure.TIM_Pulse = 0; //设置待装入捕获比较寄存器的脉冲值
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
TIM_OC4Init(TIM2, &TIM_OCInitStructure); //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
// TIM_CtrlPWMOutputs(TIM2,ENABLE); //MOE 主输出使能
TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable); //TIM2 CH1预装载使能
TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable); //TIM2 CH2预装载使能
TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Enable); //TIM2 CH3预装载使能
TIM_OC4PreloadConfig(TIM2, TIM_OCPreload_Enable); //TIM2 CH4预装载使能
// TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable); //CH1预装载使能
// TIM_ARRPreloadConfig(TIM2, ENABLE); //使能TIMx在ARR上的预装载寄存器
TIM_Cmd(TIM2, ENABLE); //使能TIM2
}
ADC驱动程序如下:
/**********************
函数名称:Adc_Init
功 能:ADC1模块初始化
参 数:无
返回值:无
作 者:lkjx
*********************/
void Adc_Init(void)
{
ADC_InitTypeDef ADC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_ADC1 , ENABLE ); //使能ADC1通道时钟
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
//PB0 作为模拟通道输入引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入引脚
GPIO_Init(GPIOB, &GPIO_InitStructure);
ADC_DeInit(ADC1); //复位ADC1,将外设 ADC1 的全部寄存器重设为缺省值
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC工作模式:ADC1和ADC2工作在独立模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //模数转换工作在单通道模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //模数转换工作在单次转换模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //转换由软件而不是外部触发启动
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; //顺序进行规则转换的ADC通道的数目
ADC_Init(ADC1, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器
ADC_Cmd(ADC1, ENABLE); //使能指定的ADC1
ADC_ResetCalibration(ADC1); //使能复位校准
while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束
ADC_StartCalibration(ADC1); //开启AD校准
while(ADC_GetCalibrationStatus(ADC1)); //等待校准结束
// ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使能指定的ADC1的软件转换启动功能
}
/**********************
函数名称:Get_Adc
功 能:获得ADC采样值
参 数:ch------采样通道
返回值:ADC_GetConversionValue(ADC1),ADC采样转换值
作 者:lkjx
*********************/
u16 Get_Adc(u8 ch)
{
//设置指定ADC的规则组通道,一个序列,采样时间
ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5 ); //ADC1,ADC通道,采样时间为239.5周期
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使能指定的ADC1的软件转换启动功能
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束
return ADC_GetConversionValue(ADC1); //返回最近一次ADC1规则组的转换结果
}
/**********************
函数名称:Get_Adc_Average
功 能:对ADC进行多次采样,求取平均值
参 数:ch--------采样通道
times------采样次数
返回值:temp_val/times,采样平均值
作 者:lkjx
*********************/
u16 Get_Adc_Average(u8 ch,u8 times)
{
u32 temp_val=0;
u8 t;
for(t=0;t<times;t++)
{
temp_val+=Get_Adc(ch);
delay_ms(5);
}
return temp_val/times;
}
按键驱动程序如下:
/**********************
函数名称:KEY_Scan
功 能:按键处理函数,对按键进行扫描
此函数有响应优先级,KEY_ADD>KEY_DEC>KEY_OUT!!
参 数:mode------0,不支持连续按;
mode------1,支持连续按;
返回值:0,没有任何按键按下
KEY_ADD,KEY_ADD_PRES按下
KEY_DEC,KEY_DEC_PRES按下
KEY_OUT,KEY_OUT_PRES按下
作 者:lkjx
*********************/
u8 KEY_Scan(u8 mode)
{
static u8 key_up=1;//按键按松开标志
if(mode)key_up=1; //支持连按
if(key_up&&(KEY_ADD==0||KEY_DEC==0||KEY_OUT==0))
{
delay_ms(10);//去抖动
key_up=0;
if(KEY_ADD==0)return KEY_ADD_PRES;
else if(KEY_DEC==0)return KEY_DEC_PRES;
else if(KEY_OUT==0)return KEY_OUT_PRES;
}
else if(KEY_ADD==1&&KEY_DEC==1&&KEY_OUT==1)
key_up=1;
return 0;// 无按键按下
}
exti外部中断驱动程序:
/**********************
函数名称:EXTIX_Init
功 能:外部中断初始化
参 数:无
返回值:无
作 者:lkjx
*********************/
void EXTIX_Init(void)
{
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//外部中断,需要使能AFIO时钟
KEY_Init();//初始化按键对应io模式
//GPIOA.15 中断线以及中断初始化配置
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource15);
EXTI_InitStructure.EXTI_Line=EXTI_Line15;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//下降沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
//GPIOB.3 中断线以及中断初始化配置
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource3);
EXTI_InitStructure.EXTI_Line=EXTI_Line3;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
//GPIOB.4 中断线以及中断初始化配置
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource4);
EXTI_InitStructure.EXTI_Line=EXTI_Line4;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn; //使能按键所在的外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01; //子优先级1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn; //使能按键所在的外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2,
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02; //子优先级1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; //使能按键所在的外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2,
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00; //子优先级1 0x00的优先级最高
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure);
}
/**********************
函数名称:EXTI3_IRQHandler
功 能:外部中断处理程序
参 数:无
返回值:无
作 者:lkjx
*********************/
void EXTI3_IRQHandler(void)
{
delay_ms(5); //消抖
if(KEY_DEC==0) //由1改为0
{
m=m-10;
if(m<0) m=0;
}
EXTI_ClearITPendingBit(EXTI_Line3); //清除EXTI3线路挂起位
}
/**********************
函数名称:EXTI4_IRQHandler
功 能:外部中断处理程序
参 数:无
返回值:无
作 者:lkjx
*********************/
void EXTI4_IRQHandler(void)
{
delay_ms(5); //消抖
PA_M_OUT=0;
PB_M_OUT=0;
delay_us(100);
PA_M_OUT=1;
PB_M_OUT=1;
delay_us(30); //第1次脉冲时间
PA_DQ_OUT=0;
PB_DQ_OUT=0;
delay_us(100); //第1次脉冲与第2次脉冲之间的间隔时间
PA_M_OUT=1;
PB_M_OUT=1;
delay_us(m); //第2次脉冲时间
PA_DQ_OUT=0;
PB_DQ_OUT=0;
EXTI_ClearITPendingBit(EXTI_Line4); //清除LINE4上的中断标志位
}
/**********************
函数名称:EXTI15_10_IRQHandler
功 能:外部中断处理程序
参 数:无
返回值:无
作 者:lkjx
*********************/
void EXTI15_10_IRQHandler(void)
{
delay_ms(5); //消抖
if(KEY_ADD==0)
{
m=m+10;
}
EXTI_ClearITPendingBit(EXTI_Line15); //清除LINE15线路挂起位
}
以上为代码的驱动程序,其中OLED驱动代码太长,这里就不列举了,上述代码主要来自于网络,主要实现功能如下图2。
后续想将该OLED屏做成菜单栏形式,不仅可以设置脉冲1,也能够设置脉冲2,但最近一直忙于学习TI的电机矢量控制相关程序,以后有时间再进行升级。