我在工作中会用到5V,12V等常见的电压输出,由于使用开关电源做输出比较麻烦,所以设置一个四路的降压模块,现在将其分享出来给大家参考一下。
降压电路部分:采用XL4015左右电压芯片,选用该芯片的考虑点是宽电压输入 8V-36V范围,这个范围基本上在工作中比较容易找到,效率也比较高,具体参数如下图:
设计的电路图如下:
由于单片机采用3.3V工作电压,所以固定一路输出为5V输出,该输出电压也可以为其他负载提供电压输出,在PCB布局时,将5V和3.3V输出引出外部提供电压输出。
二:屏幕显示使用的IIC接口的OLED屏幕:资料如下:
采用i2C接口的1.3寸的12864 OLED模块显示,采用该屏幕主要是接线方便,对屏幕的刷新速度要求不高,只是用作显示电压使用;缺点是单色的,看起来并不是很友好。
软件控制部分:硬件设计资料:采用STM32的定时器4的PWM输出功能,为了使输出的电压恒定,输出电压经过了两次RC电路,然后 进入运放LM358,这个运放工作时,不需要负电压就可以,而且价格便宜,图纸如下
电压采集部分:将XL4015输出端的电压经过电阻分压进入单片机的ADC采样口,设置分压电路时,需要注意下,输出电压最大时,不能超过单片IO口最高的承受电压的能力。这里需要将输出电压进入单片机内部进行采集,用于检测PWM输出是否正常。
实物测试图片如下:
实物测试图片如下:
主要软件代码如下:
利用ADC检测电压输出是否正常:
//单通道 DMA adc数据 volatile u16 adc_values; volatile u16 adc_buf[400]; //-------------单通道 ADC读取---------------- void Adc_Init(void) { ADC_InitTypeDef ADC_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE); RCC_ADCCLKConfig(RCC_PCLK2_Div6);//72/6 = 12MHz //ADC1通道11的转换时间为T=(239.5+12.5) x 1/12=21us。 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIOA, &GPIO_InitStructure); ADC_DeInit(ADC1);//复位ADC1 ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//ADC 独立模式 ADC_InitStructure.ADC_ScanConvMode = DISABLE;//单次扫描模式 ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//单次转换模式 ADC_InitStructure.ADC_Mode = ADC_Mode_RegSimult;//ADC1 和ADC2 工作在同步规则模式 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//软件触发 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//数据右对齐 ADC_InitStructure.ADC_NbrOfChannel = 1 ;//进行规则转换的ADC通道数量 ADC_Init(ADC1, &ADC_InitStructure);//初始化ADC1 ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1);//开启复位校准 while(ADC_GetResetCalibrationStatus(ADC1));//等待复位校准成功 ADC_StartCalibration(ADC1);//开始校验ADC1 while(ADC_GetCalibrationStatus(ADC1));//等待校验成功 } u16 get_ADC_Values(u8 ch, u8 rank) { //设置指定ADC规则通道,设置转换时间 ADC_RegularChannelConfig(ADC1, ch, rank, ADC_SampleTime_239Cycles5); ADC_SoftwareStartConvCmd(ADC1, ENABLE); //开启软件转换 while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)); //等待转换结束 return ADC_GetConversionValue(ADC1); } //返回ADC平均值 u16 get_ADC_Average(u8 ch, u8 rank, u8 count) { u32 temp_val = 0; u8 i; for(i = 0; i < count; i++) { temp_val += get_ADC_Values(ch, rank); delay_ms(5); } return temp_val / count; } //-------------单通道 ADC DMA读取---------------- void ADC_DMA_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; ADC_InitTypeDef ADC_InitStructure; DMA_InitTypeDef DMA_InitStructure; //使能DMA1时钟 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_ADC1,ENABLE); //初始化IO GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN; GPIO_Init(GPIOA,&GPIO_InitStructure); //复位DMA1 通道1 ADC DMA_DeInit(DMA1_Channel1); DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&ADC1->DR;//ADC内存基地址 DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&adc_values;//变量基地址 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//外设到内存 DMA_InitStructure.DMA_BufferSize = 1;//数据宽度 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设内存地址不变 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;//变量内存地址不变 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//16字节 12位ADC DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//16字节 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//循环模式 DMA_InitStructure.DMA_Priority = DMA_Priority_High;//高优先级 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//关闭内存到内存传输 DMA_Init(DMA1_Channel1, &DMA_InitStructure);//初始化DMA1 通道1 DMA_Cmd(DMA1_Channel1, ENABLE);//使能DMA 通道1 ADC_DeInit(ADC1);//复位ADC1 ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//独立模式 ADC_InitStructure.ADC_ScanConvMode = DISABLE; //关闭扫描 ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //开启连续转换 触发一次 持续转换 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//软件触发转换 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//数据右对齐 ADC_InitStructure.ADC_NbrOfChannel = 1;//转换一个通道 ADC_Init(ADC1, &ADC_InitStructure);//初始化ADC //ADC时钟 不能大于14MHz RCC_ADCCLKConfig(RCC_PCLK2_Div6);//72/6 = 12MHz //设置指定ADC规则通道,设置转换时间 ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_239Cycles5); //使能ADC DMA ADC_DMACmd(ADC1, ENABLE); //使能ADC ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1);//复位校准寄存器 while(ADC_GetResetCalibrationStatus(ADC1));//等待校准寄存器复位完成 ADC_StartCalibration(ADC1);//ADC校准 while(ADC_GetCalibrationStatus(ADC1));//等待校准完成 ADC_SoftwareStartConvCmd(ADC1, ENABLE);//由于没有采用外部触发,所以使用软件触发ADC转换 } //返回ADC平均值 u16 get_ADC_DMA_Average(u8 count) { u32 temp_val = 0; u8 i; for(i = 0; i < count; i++) { temp_val += adc_values; delay_ms(5); } return temp_val / count; } //-------------多通道 ADC DMA读取---------------- void ADC_DMA_Multichannel_Init(uint16_t *ADC_Buff) { GPIO_InitTypeDef GPIO_InitStructure; ADC_InitTypeDef ADC_InitStructure; DMA_InitTypeDef DMA_InitStructure; //使能DMA1时钟 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_ADC1,ENABLE); //初始化IO GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN; GPIO_Init(GPIOA,&GPIO_InitStructure); //复位DMA1 通道1 ADC DMA_DeInit(DMA1_Channel1); DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&ADC1->DR;//ADC内存基地址 DMA_InitStructure.DMA_MemoryBaseAddr = (u32)ADC_Buff;//变量基地址 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//外设到内存 DMA_InitStructure.DMA_BufferSize = 400;//数据宽度 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设内存地址不变 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//变量内存地址增长 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//16字节 12位ADC DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//16字节 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//循环模式 DMA_InitStructure.DMA_Priority = DMA_Priority_High;//高优先级 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//关闭内存到内存传输 DMA_Init(DMA1_Channel1, &DMA_InitStructure);//初始化DMA1 通道1 DMA_Cmd(DMA1_Channel1, ENABLE);//使能DMA 通道1 ADC_DeInit(ADC1);//复位ADC1 ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//独立模式 ADC_InitStructure.ADC_ScanConvMode = ENABLE; //开启扫描 多通道读取 ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //开启连续转换 触发一次 持续转换 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//软件触发转换 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//数据右对齐 ADC_InitStructure.ADC_NbrOfChannel = 4;//转换3个通道 ADC_Init(ADC1, &ADC_InitStructure);//初始化ADC //ADC时钟 不能大于14MHz RCC_ADCCLKConfig(RCC_PCLK2_Div6);//72/6 = 12MHz //设置指定ADC规则通道,设置转换时间 ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_13Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_13Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_13Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_13Cycles5); //使能ADC DMA ADC_DMACmd(ADC1, ENABLE); //使能ADC ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1);//复位校准寄存器 while(ADC_GetResetCalibrationStatus(ADC1));//等待校准寄存器复位完成 ADC_StartCalibration(ADC1);//ADC校准 while(ADC_GetCalibrationStatus(ADC1));//等待校准完成 ADC_SoftwareStartConvCmd(ADC1, ENABLE);//由于没有采用外部触发,所以使用软件触发ADC转换 }
PWM输出控制:
//TIM4 CH1 PWM 输出设置 //PWM 输出初始化 //arr:自动重装值 //psc:时钟预分频数 void Tim4_PWM_Init(u16 arr,u16 psc) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //使能 TIMx 外设 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能 PB 时钟 TIM_DeInit(TIM4); //设置该引脚为复用输出功能,输出 TIM4 CH1 的 PWM 脉冲波形 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9; //TIM4_CH1 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用功能输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化 GPIO TIM_TimeBaseStructure.TIM_Period = arr; //设置自动重装载周期值 TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置预分频值 不分频 TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数 TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //初始化 TIMx TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //CH1 PWM2 模式 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能 TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Disable; TIM_OCInitStructure.TIM_Pulse = 1800; //设置待装入捕获比较寄存器的脉冲值 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //OC1 高电平有效 TIM_OC1Init(TIM4, &TIM_OCInitStructure); //根据指定的参数初始化外设 TIMx TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable); //CH1 预装载使能 TIM_OCInitStructure.TIM_Pulse = 1800; //设置待装入捕获比较寄存器的脉冲值 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //OC2 高电平有效 TIM_OC2Init(TIM4, &TIM_OCInitStructure); //根据指定的参数初始化外设 TIMx TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Enable); //CH2 预装载使能 TIM_OCInitStructure.TIM_Pulse = 1800; //设置待装入捕获比较寄存器的脉冲值 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //OC3 高电平有效 TIM_OC3Init(TIM4, &TIM_OCInitStructure); //根据指定的参数初始化外设 TIMx TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable); //CH3 预装载使能 TIM_OCInitStructure.TIM_Pulse = 1800; //设置待装入捕获比较寄存器的脉冲值 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //OC4 高电平有效 TIM_OC4Init(TIM4, &TIM_OCInitStructure); //根据指定的参数初始化外设 TIMx TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Enable); //CH4 预装载使能 TIM_ARRPreloadConfig(TIM4, ENABLE); //使能 TIMx 在 ARR 上的预装载寄存器 // TIM_CtrlPWMOutputs(TIM4,ENABLE); //MOE 主输出使能,高级定时器必须开启 TIM_Cmd(TIM4, ENABLE); //使能 TIMx } void Set_Duty(uint8_t ch,uint16_t value) { switch(ch) { case 1: TIM_SetCompare1(TIM4, value); break; case 2: TIM_SetCompare2(TIM4, value); break; case 3: TIM_SetCompare3(TIM4, value); break; case 4: TIM_SetCompare4(TIM4, value); break; } } void Auot_Vol(void) { Set_Duty(3,(float)(Meun.CH2_Vol)*2.321+2207.35+7); Set_Duty(2,(float)(Meun.CH3_Vol)*2.321+2207.35+10); Set_Duty(1,(float)(Meun.CH4_Vol)*2.321+2207.35+7); }
界面显示部分:
void MeunDisplay(void) { char buffer[5]; static char i=50; float adv[4]={0}; int x; if(Meun.SecondM == 0 && Meun.FirstM == 0) { if(Meun.Refresh == 1) { OLED_Fill(0);//全屏填充 Meun.Refresh = 0; Display_Refresh(0); for(x =0;x<100;x++) { adv[0] += adc_buf[0+x*4]; adv[1] += adc_buf[1+x*4]; adv[2] += adc_buf[2+x*4]; adv[3] += adc_buf[3+x*4]; } adv[0] = adv[0]/100; adv[1] = adv[1]/100; adv[2] = adv[2]/100; adv[3] = adv[3]/100; sprintf(buffer,"%4.1fV",(float)adv[3]/4096*3.3*2); OLED_ShowStr(24, 3, (uint8_t *)buffer, 1); sprintf(buffer,"%4.1fV",(float)adv[2]/4096*3.3*8.5); OLED_ShowStr(88, 3, (uint8_t *)buffer, 1); sprintf(buffer,"%4.1fV",(float)adv[1]/4096*3.3*8.5); OLED_ShowStr(24, 4, (uint8_t *)buffer, 1); sprintf(buffer,"%4.1fV",(float)adv[0]/4096*3.3*8.5); OLED_ShowStr(88, 4, (uint8_t *)buffer, 1); HoldReg[4] = Meun.CH2_Vol; HoldReg[5] = Meun.CH3_Vol; HoldReg[6] = Meun.CH4_Vol; USER_Save();//保存用户数据 } if(i % 50 == 0) { for(x =0;x<100;x++) { adv[0] += adc_buf[0+x*4]; adv[1] += adc_buf[1+x*4]; adv[2] += adc_buf[2+x*4]; adv[3] += adc_buf[3+x*4]; } adv[0] = adv[0]/100; adv[1] = adv[1]/100; adv[2] = adv[2]/100; adv[3] = adv[3]/100; sprintf(buffer,"%4.1fV",(float)adv[3]/4096*3.3*2); OLED_ShowStr(24, 3, (uint8_t *)buffer, 1); HoldReg[0] = (float)adv[3]/4096*3.3*2*100; sprintf(buffer,"%4.1fV",(float)adv[2]/4096*3.3*8.5); OLED_ShowStr(88, 3, (uint8_t *)buffer, 1); HoldReg[1] = (float)adv[2]/4096*3.3*8.5*100; sprintf(buffer,"%4.1fV",(float)adv[1]/4096*3.3*8.5); OLED_ShowStr(24, 4, (uint8_t *)buffer, 1); HoldReg[2] = (float)adv[1]/4096*3.3*8.5*100; sprintf(buffer,"%4.1fV",(float)adv[0]/4096*3.3*8.5); OLED_ShowStr(88, 4, (uint8_t *)buffer, 1); HoldReg[3] = (float)adv[0]/4096*3.3*8.5*100; if(i == 100) { OLED_ShowStr(96, 6, (uint8_t *)"*", 2); i = 0; } else if(i == 50) { OLED_ShowStr(96, 6, (uint8_t *)" ", 2); } } i ++; } else if(Meun.SecondM == 0) { if(Meun.Refresh == 1) { OLED_Fill(0);//全屏填充 switch(Meun.FirstM) { case 1: Display_Refresh(1); break; case 2: Display_Refresh(2); break; case 3: Display_Refresh(3); break; } Meun.Refresh = 0; } } else { if(Meun.Refresh == 1) { OLED_Fill(0);//全屏填充 Display_Refresh(4); Meun.Refresh = 0; EDIT_Flag = 1; } switch(Meun.FirstM) { case 1: sprintf(buffer,"%4.1fV",(float)Meun.CH2_Vol/10); OLED_ShowStr(40, 3, (uint8_t *)buffer, 2); break; case 2: sprintf(buffer,"%4.1fV",(float)Meun.CH3_Vol/10); OLED_ShowStr(40, 3, (uint8_t *)buffer, 2); break; case 3: sprintf(buffer,"%4.1fV",(float)Meun.CH4_Vol/10); OLED_ShowStr(40, 3, (uint8_t *)buffer, 2); break; } } }
为了节约点成本,数据存储在单片机内部的flash,没有使用存储芯片。
在实际使用过程中发现电压输出并不恒定,用万用表测试可以明显的看出输出电压在0.2V上下跳动,但是这样并不影响使用,其中OUT1固定为5V输出,其他三路为可调电压输出。现在STM32价格貌似是处于下降的趋势,感兴趣的芯查查的小伙伴们可以在下方留下联系方式,方便自己焊接使用。