1 软件功能:
1.1 两路ADC测量,一路测量电池电压,另一路测量电极片电流,检测喷雾器有水、水位低、无水等状态。
测量电池电压原理:电池电压直接接MCU VCC和VRef+,通过测量内部1.19V基准源反推电池电压(VRef)和电极片电流。具体反推公式请参考源码。
1.2 两路PWM,一路控制马达软启动以及工作档位,低档70%占空比,高档99%占空比,频率2K,高档软启动时间约1S。
另一路PWM控制升压芯片,频率4K,占空比4.3%,让电极片电压有一个波动(可能是电极片控制要求这样,为什么我们这里不管,只按样机给出PWM即可)。
1.3 一路串口调试打印,用于运行时诊断程序。
1.4 一个时间基准定时器,中断频率1ms,用于万用时钟。
1.5 其他均做普通IO口处理,其中读取电池是否插入充电器的管脚charge_test,比较奇特,一定要设置成开漏输出,才能正确读取到是否插入了充电器。
1.6 低功耗模式,当因各种原因关机5秒后,自动进入掉电模式(一条语句即可PCON |=0x02;),掉电模式耗电流27uA, 按按键P3.7,又立马启动。
2 MCU管脚接线:
2.1 电路图如下:
3 软件源码:
main.c
#include "stc8h.h" #include "intrins.h" #include "stdio.h" #include "adc.h" #include "key.h" #include "pwm.h" //外部导入变量 //extern int *BGV; //extern bit alarm_low_voltage; //extern u8 alarm_water_position; #define FOSC 11059200UL #define BRT (65536 - FOSC / 115200 / 4) bit busy; void UartIsr() interrupt 4 { if (TI) { TI = 0; busy = 0; } if (RI) { RI = 0; } } void UartInit() { SCON = 0x50; TMOD = 0x00; TL1 = BRT; TH1 = BRT >> 8; TR1 = 1; AUXR = 0x40; busy = 0; } void UartSend(char dat) { while (busy); busy = 1; SBUF = dat; } char putchar(char c) { UartSend(c); return c; } void systick_init(void) { AUXR |= 0x80; /*1MS时基定时器*/ TMOD &= 0xF0; /*1MS时基定时器*/ TL0 = 0xCD; /*1MS时基定时器*/ TH0 = 0xD4; /*1MS时基定时器*/ TR0 = 1; /*1MS时基定时器*/ ET0 = 1; /*1MS时基定时器*/ } volatile u32 ticks =0; void TM0_Isr() interrupt 1 { static u8 count=0; count++; ticks++; if((count%10)==0){ key_scan_flag =1; } if(count ==250){ count =0; adc_sample_flag =1; } motor_pwm_update_dutycycle(); } void INT3_Isr() interrupt 11 { P15 = 1; //测试端口 } void power_down_process(void) { if(power_down_enable){ if((ticks-power_down_time_stamp) >5000){ power_down_enable=0; _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); PCON |=0x02; _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); } } } void main()//可打印出正确的ADC值 { P_SW2 |= 0x80; //扩展寄存器访问使能 P0M0 = 0x00; P0M1 = 0x00; P1M0 = 0x00; P1M1 = 0x00; P2M0 = 0x00; P2M1 = 0x00; P3M0 = 0x00; P3M1 = 0x00; P4M0 = 0x00; P4M1 = 0x00; P5M0 = 0x00; P5M1 = 0x00; P3M0 &=0x7F;/*P3.7按键高阻态输入*/ P3M1 |=0x80;/*P3.7按键高阻态输入*/ // P3M0 &=0xFE; /*P3.0高组输入 断开电路,高阻输入有4V*/ // P3M1 |=0x01; // // P3M0 &=0xFD;/*P3.1高组输入*/ // P3M1 |=0x02; P3M0 |=0x04; /* P3.2推挽输出 臭氧控制脚,如果设置成双向输出,则置高后,外部只有1V电压,设成推挽就有4V*/ P3M1 &=0xFB; P5M0|=0x10;/*P5.4低档指示黄灯推挽输出*/ P5M1&=0xEF;/*P5.4低档指示黄灯推挽输出*/ P1M0|=0x40;/*P1.6高档指示白灯推挽输出*/ P1M1&=0xBF;/*P1.6高档指示白灯推挽输出*/ P1M0|=0x10; /*P1.4充电指示红灯推挽输出*/ P1M1&=0xEF; /*P1.4充电指示红灯推挽输出*/ P1M0|=0x20; /*P1.5充满指示绿灯推挽输出*/ P1M1&=0xDF; /*P1.5充满指示绿灯推挽输出*/ P1M0|=0x80; /*P1.7少水无水指示蓝灯推挽输出*/ P1M1&=0x7F; /*P1.7少水无水指示蓝灯推挽输出*/ //双向IO,高阻IO都不行,调成开漏输出 P1M0 |=0x08; /**/ P1M1 |=0x08; INTCLKO |=(1<<5); //使能INT3中断,用于唤醒MCU 固定为下降沿中断 BGV = (int idata *)0xef; ADCInit(); //ADC初始化 // UartInit(); //串口初始化 // ES = 1; systick_init(); //系统时基初始化 fb_pwm_init(); motor_pwm_init(); EA = 1; while (1) { key_update_message(); key_event_process(); adc_process(); alarm_process(); charge_process(); power_down_process(); } }
adc.h
#ifndef __adc_H #define __adc_H #include "stc8h.h" void ADCInit(void) ; int ADCRead_VCC(void) ; int ADCRead_qzone_current(void) ; void adc_process(void) ; void alarm_process(void) ; void charge_process(void) ; extern int *BGV; extern bit alarm_low_voltage; extern u8 alarm_water_position; extern bit adc_enable; extern u32 adc_time_stamp; extern bit charging; extern bit adc_sample_flag; #endif
adc.c
#include "stc8h.h" //包含此头文件后,不需要再包含"reg51.h"头文件 #include "adc.h" #include "intrins.h" #include "stdio.h" #include "pwm.h" #include "key.h" void ADCInit(void) { ADCTIM = 0x3f; //设置ADC内部时序 ADCCFG = 0x2f; //设置ADC时钟为系统时钟/2/16 } int ADCRead_VCC(void ) { int res; ADC_CONTR = 0x8f; _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); ADC_CONTR |= 0x40; //启动AD转换 _nop_(); _nop_(); _nop_(); _nop_(); while (!(ADC_CONTR & 0x20)); //查询ADC完成标志 ADC_CONTR &= ~0x20; //清完成标志 res = (ADC_RES << 8) | ADC_RESL; //读取ADC结果 return res; } int ADCRead_qzone_current(void) { int res; ADC_CONTR = 0x8D; _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); ADC_CONTR |= 0x40; //启动AD转换 _nop_(); _nop_(); _nop_(); _nop_(); while (!(ADC_CONTR & 0x20)); //查询ADC完成标志 ADC_CONTR &= ~0x20; //清完成标志 res = (ADC_RES << 8) | ADC_RESL; //读取ADC结果 return res; } bit alarm_low_voltage; //电池低电压标志 1:低电压状态 0:电压正常 u8 alarm_water_position; //水位状态变量 0:水位正常 1:低水位 2:无水 int *BGV; //电压单位为毫伏(mV) bit adc_enable; u32 adc_time_stamp; extern u32 ticks ; u32 red_led_stamp; //低压红灯亮3S后关机 u32 blue_led_stamp; bit adc_sample_flag; #define RED_LED_ON() P14=0 #define RED_LED_OFF() P14=1 #define WHITE_LED_ON() P16=0 #define WHITE_LED_OFF() P16=1 #define GREEN_LED_ON() P15=0 #define GREEN_LED_OFF() P15=1 #define YELLOW_LED_ON() P54=0 #define YELLOW_LED_OFF() P54=1 #define BLUE_LED_ON() P17=0 #define BLUE_LED_OFF() P17=1 #define LOW_LEVEL_LED_ON() P16=0 #define LOW_LEVEL_LED_OFF() P16=1 #define HIGH_LEVEL_LED_ON() P54=0 #define HIGH_LEVEL_LED_OFF() P54=1 #define BLUE_LED_TOGGLE() P17=!P17 void adc_process(void) //该函数最终操作alarm_low_voltage和alarm_no_water,应用程序根据这两个变量进行控制 { int res; int vcc; int i; int qzone_current; int adc; int res_BGV; // BGV = (int idata *)0xef; if(adc_sample_flag ==1){ //250ms采样一次 adc_sample_flag =0; if(adc_enable){ if((ticks -adc_time_stamp)>5000){ res = 0; for (i=0; i<10; i++) { if(i<2){ adc = ADCRead_VCC(); //去掉头两次 } else{ res += ADCRead_VCC(); //读取8次数据 } } res >>= 3; //取平均值 // printf("res=%u\r\n",res); //输出ADC值到串口 vcc = (int)(1024L * *BGV / res); //(10位ADC算法)计算VREF管脚电压,即电池电压 // printf("BGV=%umV\r\n",*BGV); //注意,此电压的单位为毫伏(mV) // // printf("vcc=%umV\r\n",vcc); //输出电压值到串口 res_BGV=res; res = 0; for (i=0; i<10; i++) { if(i<2){ adc =ADCRead_qzone_current(); //去掉头两次 } else{ res += ADCRead_qzone_current(); //读取8次数据 } } res>>=3; qzone_current =(int)(res * 1190L / res_BGV); // printf("qzone_current2=%umV\r\n",qzone_current); if(qzone_current >200){ alarm_water_position =0;//水位正常 BLUE_LED_OFF(); } else if(qzone_current >50){ if(alarm_water_position ==0){ blue_led_stamp =ticks; alarm_water_position =1;//水位低 } } else{ alarm_water_position =2;//无水 蓝灯亮10S关机 blue_led_stamp =ticks; BLUE_LED_ON(); adc_enable=0;// 不再进入ADC处理程序 } if(vcc<3300){ alarm_low_voltage=1;//低压状态 red_led_stamp =ticks; RED_LED_ON(); adc_enable=0;// 不再进入ADC处理程序 } else{ alarm_low_voltage=0;//电压正常 RED_LED_OFF(); } } } } } void alarm_process(void) { if(alarm_low_voltage){ //低压处理 if((ticks -red_led_stamp) >3000){ //关机 RED_LED_OFF(); WHITE_LED_OFF(); YELLOW_LED_OFF(); BLUE_LED_OFF(); fb_pwm_stop(); motor_pwm_stop(); P32=0; //臭氧控制关 //相关变量清0 system_status =0; adc_enable =0; alarm_low_voltage=0; alarm_water_position=0;//恢复水位标志 power_down_enable =1; power_down_time_stamp =ticks; } } if(alarm_water_position==1){ //水位处理 if((ticks -blue_led_stamp) >500){ //蓝灯闪烁 blue_led_stamp=ticks ; BLUE_LED_TOGGLE(); } } else if(alarm_water_position==2){ if((ticks -blue_led_stamp) >3000){ //关机 RED_LED_OFF(); WHITE_LED_OFF(); YELLOW_LED_OFF(); BLUE_LED_OFF(); fb_pwm_stop(); motor_pwm_stop(); P32=0; //臭氧控制关 //相关变量清0 system_status =0; adc_enable =0; alarm_low_voltage=0; //恢复低压标志 alarm_water_position=0;//恢复水位标志 power_down_enable =1; power_down_time_stamp =ticks; } } } bit pre_P13; bit cur_P13; bit charging; //正在充电,关机,并且不能操作按键 //P13充电检查 //P12充满指示 //P13调成开漏输出后 : // 插入电池 插入充电器 断开充电器 插入充满 // P13 0 3.6 慢慢变0V 3.6V // P12 4.0 0 4V 4V // 充满 P12 4V void charge_process(void) //充电处理 { cur_P13 =P13; if((cur_P13==1)&&(P12==0)){ //充电时没充满亮红灯 GREEN_LED_OFF(); RED_LED_ON(); charging=1; //正在充电 关机 power_down_enable =0; //取消进入掉电模式,充电时按一下按钮,就会进入启动程序,程序检测到在充电,就不使能掉电检测。 //关闭其他LED 清除标志位 WHITE_LED_OFF(); //关闭相应LED YELLOW_LED_OFF(); BLUE_LED_OFF(); motor_pwm_stop();//关闭PWM fb_pwm_stop(); adc_enable=0; P32=0; //臭氧控制关 //相关变量清0 system_status =0; alarm_water_position=0;//恢复水位标志 alarm_low_voltage=0; //恢复低压标志 } else if((cur_P13==1)&&(P12==1)){ //充电时 充满了亮绿灯 GREEN_LED_ON(); RED_LED_OFF(); charging=0; } if((pre_P13==1)&&(cur_P13==0)){ //充电插头拔掉 P13慢慢由高变低 GREEN_LED_OFF(); RED_LED_OFF(); charging=0; power_down_enable =1; power_down_time_stamp =ticks; } pre_P13 =cur_P13; }
key.h
#ifndef __key_H #define __key_H #include "stc8h.h" extern bit key_scan_flag; extern bit system_status; extern bit long_press_flag; /*关机时有长按标志就关机,无长按标志不关机*/ void key_update_message(void) //主程序周期性调用该函数更新 ; void key_event_process(void) ; extern u32 power_down_time_stamp; extern bit power_down_enable; #endif
key.c
#include "stc8h.h" //包含此头文件后,不需要再包含"reg51.h"头文件 #include "key.h" #include "pwm.h" #include "adc.h" #define KEY_EVENT_PRESSED 1 #define KEY_EVENT_LONG 2 #define KEY_EVENT_CONTINUE 3 #define KEY_EVENT_RELEASED 4 #define KEY_STATE_INIT 0 #define KEY_STATE_PRESS 1 #define KEY_STATE_LONG 2 #define KEY_STATE_CONTINUE 3 #define KEY_STATE_RELEASE 4 #define KEY_PERIOD_LONG 90 #define KEY_PERIOD_CONTINUE 25 #define SYSTEM_STATE_IDLE 0 #define SYSTEM_STATE_RUN 1 u32 power_down_time_stamp; //关机5S后进入掉电模式 , 5S内有开机或者充电,则取消掉电模式。 bit power_down_enable; u8 key_event; /*按键事件,使用完后清零。0:表示无按键事件, >1:表示有按键事件*/ bit key_scan_flag; /*系统时钟给的扫描标志位*/ void key_update_message(void) { static u8 key_state_machine = KEY_STATE_INIT; static u16 key_counter =0; bit pin_status; if(key_scan_flag) { key_scan_flag=0; pin_status=P37; //按键端口 switch(key_state_machine) { case KEY_STATE_INIT : { if(!pin_status) /*detected one of the button was pressed*/ key_state_machine = KEY_STATE_PRESS; /*run state machine*/ } break ; case KEY_STATE_PRESS : { if( !pin_status) { key_state_machine = KEY_STATE_LONG ; /*runs state machine*/ key_event =KEY_EVENT_PRESSED; } else { key_state_machine = KEY_STATE_RELEASE; } } break; case KEY_STATE_LONG : { if(!pin_status) { if(++key_counter> KEY_PERIOD_LONG) { key_counter=0; key_state_machine=KEY_STATE_CONTINUE; /*Runs state machine*/ key_event =KEY_EVENT_LONG; /*TRIGGER EVENT*/ } } else { key_state_machine= KEY_STATE_RELEASE; key_counter=0; } } break; case KEY_STATE_CONTINUE : { if(!pin_status) { if(++key_counter >KEY_PERIOD_CONTINUE) { key_counter = 0; key_state_machine=KEY_STATE_CONTINUE; key_event =KEY_EVENT_CONTINUE; /*TRIGGER EVENT*/ } } else { key_state_machine = KEY_STATE_RELEASE; key_counter =0; } } break; case KEY_STATE_RELEASE : { key_event =KEY_EVENT_RELEASED; /*TRIGGER EVENT*/ key_state_machine = KEY_STATE_INIT; /*Runs the state machine*/ } break; default : break; } } } bit qzone_level; //臭氧高低档位 1:高档位 0:低档位,第一次上电运行默认是0低档位 bit long_press_flag; /*关机时有长按标志就关机,无长按标志不关机*/ bit system_status; //开机 关机 不管开关机 长按都切换档位。 extern u32 ticks ; #define RED_LED_ON() P14=0 #define RED_LED_OFF() P14=1 #define WHITE_LED_ON() P16=0 #define WHITE_LED_OFF() P16=1 #define GREEN_LED_ON() P15=0 #define GREEN_LED_OFF() P15=1 #define YELLOW_LED_ON() P54=0 #define YELLOW_LED_OFF() P54=1 #define BLUE_LED_ON() P17=0 #define BLUE_LED_OFF() P17=1 #define LOW_LEVEL_LED_ON() P16=0 #define LOW_LEVEL_LED_OFF() P16=1 #define HIGH_LEVEL_LED_ON() P54=0 #define HIGH_LEVEL_LED_OFF() P54=1 void key_event_process(void) { // if((key_event) &&(P31==0)){ //没充电,并且有按键事件过来 就进行按键处理 if(key_event &&(charging==0)){ //没充电,并且有按键事件过来 就进行按键处理 if(key_event ==KEY_EVENT_PRESSED){ } else if(key_event ==KEY_EVENT_LONG){ long_press_flag=1; //给出长按标志 qzone_level =!qzone_level; //切换档位 if(qzone_level){ max_duty_cycle =5530; //设置马达PWM } else{ max_duty_cycle =3870; //设置马达PWM } if(system_status){ //如果当前状态是开机状态 if(qzone_level){ //臭氧高档位 HIGH_LEVEL_LED_ON(); //点亮高档位灯 LOW_LEVEL_LED_OFF(); //关闭低档位灯 } else{//臭氧低档位 HIGH_LEVEL_LED_OFF(); //点亮高档位灯 LOW_LEVEL_LED_ON(); //关闭低档位灯 } } } else if(key_event ==KEY_EVENT_RELEASED){ if(long_press_flag==0){ //按键没有长按过 system_status =!system_status; //切换开关机状态 if(system_status){ //如果当前状态是开机状态 P32=1;//臭氧控制开 if(qzone_level){ //臭氧高档位 HIGH_LEVEL_LED_ON(); //点亮高档位灯 LOW_LEVEL_LED_OFF(); //关闭低档位灯 max_duty_cycle =5530; //设置马达PWM } else{//臭氧低档位 HIGH_LEVEL_LED_OFF(); //点亮高档位灯 LOW_LEVEL_LED_ON(); //关闭低档位灯 max_duty_cycle =3870; //设置马达PWM } adc_enable=1; //使能ADC处理 adc_time_stamp=ticks; //启动3S后开始ADC采样 motor_pwm_start(); //启动马达 fb_pwm_start(); //启动臭氧PWM power_down_enable =0; //取消掉电使能 } else{ P32=0; //臭氧控制关 //关机 RED_LED_OFF(); WHITE_LED_OFF(); //关闭相应LED YELLOW_LED_OFF(); BLUE_LED_OFF(); motor_pwm_stop();//关闭PWM fb_pwm_stop(); adc_enable=0; power_down_enable =1; //只要一关机就开始使能掉电模式 power_down_time_stamp =ticks; //相关变量清0 alarm_water_position=0;//恢复水位标志 alarm_low_voltage=0; } } else{ long_press_flag=0; //有长按标志,松开按键后长按标志应清0 } } } key_event =0;//按键事件已经处理完,清0按键消息 }
pwm.h
#ifndef __pwm_H #define __pwm_H #include "stc8h.h" #define FB_PWM_START() PWMA_CR1 =0x01; #define FB_PWM_STOP() PWMA_CR1 =0x00; #define MOTOR_PWM_START() PWMB_CR1 =0x01; #define MOTOR_PWM_STOP() PWMB_CR1 =0x00; void motor_pwm_stop(void) ; void motor_pwm_start(void) ; void fb_pwm_stop(void) ; void fb_pwm_start(void) ; void fb_pwm_init(void) ; void motor_pwm_init(void) ; void motor_pwm_update_dutycycle(void) ; extern u16 max_duty_cycle; #endif
pwm.c
#include "stc8h.h" //包含此头文件后,不需要再包含"reg51.h"头文件 #include "pwm.h" bit motor_run; void fb_pwm_init(void) // PWM4B_2 4.1K 4.3% { P_SW2|=0x80; //初始化IO口 PWMA_PS |=0xC0; //PWM功能脚切换 PWMA_CCER1 =0x00; //写CCMRx前必须先清零CCERx关闭通道 PWMA_CCMR4 =0x60; //设置CC4为PWMA输出模式 PWMA_CCER2 =0x10; //使能CC4通道 PWMA_CCR4 =0; //设置占空比时间 PWMA_ARR =2680; //设置周期时间 PWMA_ENO =0x40; //使能PWM4P端口输出 PWMA_BKR =0x80; //使能主输出 PWMA_CR1 =0x00; //开始计时 关闭定时器时,管脚电平为低 } void fb_pwm_stop(void) { PWMA_CCR4 =0; //设置占空比时间 PWMA_CR1 =0x00; } void fb_pwm_start(void) { PWMA_CCR4 =115; //设置占空比时间 PWMA_CR1 =0x01; } void motor_pwm_init(void) //PWM7_2 { P_SW2|=0x80; PWMB_PS =0x10; //PWM7_2 在P3.3 上输出PWM PWMB_CCER1 =0x00; //写CCMRx前必须先清零CCERx关闭通道 PWMB_CCMR3 =0x60; //设置CC7为PWMB输出模式 PWMB_CCER2 =0x01; //使能CC7通道 PWMB_CCR7 =0; //设置占空比时间 PWMB_ARR =5530; //设置周期时间 PWMB_ENO =0x10; //使能PWM7端口输出 PWMB_BKR =0x80; //使能主输出 PWMB_CR1 =0x00; //开始计时 关闭定时器时,管脚电平为低 } void motor_pwm_stop(void) { PWMB_CCR7 =0; PWMB_CR1 =0x00; motor_run =0; } void motor_pwm_start(void) { PWMB_CCR7 =0; PWMB_CR1 =0x01; motor_run =1; } u16 max_duty_cycle; void motor_pwm_update_dutycycle(void) { if(motor_run){ if(PWMB_CCR7 <max_duty_cycle){//12000:8400 PWMB_CCR7 +=5; } else{ PWMB_CCR7 -=5; } } }
标签:LED,KEY,过氧化氢,void,key,软件,喷雾器,pwm,define From: https://www.cnblogs.com/okyihu/p/17811909.html