本文转自: Flywithliye本次设计要求,依据实验数据,设计简易的电池电量监测电路。该3AH的电池,在某固定环境下放电实验数据如表 1所示。要求依据测量电压推算时间,以此作为电量标识,并采用某种方式进行显示。 表 1 放电实验数据 2. 设计思路
2.1. 设计假设 本次设计基于以下假设。在任意时刻t测得开路电压值,不考虑其在t时刻前的具体放电过程。即认为该时刻电池开路电压V,是持续以放电实验中使用的放电电流(500mA),由满电量电压4.35V,放电时间得到的。即,认为,电池剩余电量与电池两端开路电压具有一一对应关系。 2.2. 设计方案通过AD芯片获取电池两端开路电压,将模拟量电压值转换为单片机可处理的数字量。随后依据该测得的电压值按照所建立数学模型进行运算。然后将该数字电压值及其运算结果通过数码管显示,并同时通过串行口发送至上位机。上位机接收到数据后做相关处理显示工作。 2.3. 数学实现 的值由插值的方式计算。依据表 1中的实验数据,通过测得的电压V,由下列公式逆向计算,其中为测得电压两侧的实验数据点时间及电压。则测得该电压时,各量值如下所示。(电压处于实验数据边界的情况已在程序实现中完成,此处省略) 放电时间: 已用电量: 剩余电量: 剩余电量百分比: 其中时间单位为min,电流单位为mA,电量单位为mAmin。 按照如上方式使用实验数据,运用Matlab绘图得到如下结果。 图 1 实验曲线 其绘图过程如下述程序所示。 图 2 实验曲线绘制 3. 电路设计
3.1. 整体电路结构整体电路原理图由Protues软件进行绘制并仿真。为简化连线便于观察逻辑,其中使用了网络标签的方式,对实际物理上相连而原理图中未连接的管脚,给予了相同的名称,则在逻辑上他们互相连接。具体电路原理图见附件1。 3.2. 单片机最小系统单片机最小系统是单片机能够正常执行内置程序,发挥其基础功能的必须组成部分。包括单片机,电源部分,时钟电路部分及复位电路部分。 3.2.1. 原理图图 3 AT89C52最小系统 3.2.2. AT89C52单片机本次设计中主要用到了该型单片机的IO口,串口及定时器。其中,P0口用于和AD转换电路交换数据。P2口部分管脚用于控制AD转换芯片工作及读取其状态。P3口3.1管脚用作第二功能串行口数据输出,其余用到的管脚用于进行数码管位选。P1口用于数码管段选。 本次设计未使用片外数据/程序存储器,因此均处于悬空状态。且本次设计中,与ADC芯片的数据交换未采用外部拓展总线的方式,将其视为普通IO设备值直接进行操作和读取。 3.2.3. 时钟电路时钟电路为单片机最小系统组成之一,用于提供时钟信号,驱动CPU指令的执行,并为定时器等提供内部时间基准。本次设计采用无源晶振,为保证串口通信波特率准确,其频率选定为11.0592Mhz。 3.2.4. 复位电路复位电路用于在单片机程序执行出现异常时,重新初始化运行,从而防止由数字电路竞争冒险及环境干扰引起的,单片机系统的不正常工作。本次设计中的复位电路同时具有上电自动复位及按键复位的功能。其中上电时由电容C5将RST引脚拉高,提供复位信号。 3.2.5. 电源单片机由5V稳压电源供电,由于仿真软件对其供电电路进行了省略,因此在图 3中并未绘出电源系统。本次设计中部分其余芯片也未绘出电源。 3.3. AD转换电路设计AD转换芯片用于将模拟量的电压值转换为可用数字电路处理的数字量值。该部分电路是本次设计中最为核心的部分。用于测试得到电池两端电压值,供单片机进行数学处理和相关显示处理。 由于软件仿真限制,在本次设计中采用电位器RV1模拟电池两端电压。同时,为简化电路,该芯片模拟地同数字地连在一起。 图中AIN0管脚上的U3(AIN0)箭头为Protues软件提供的电压探针。用于直观显示输入电压值,与通过ADS7825转换后的结果做对比参考。 3.3.1. 原理图图 4 ADC转换电路 3.3.2. ADS7825为提高测量分辨率,本次设计ADC芯片选用ADS7825。ADS7825为四通道输入,16位AD转换芯片,5V供电,输入电压范围,内置2.5V参考电压,最大采样转换时间25us。其输出方式可由设置为并行输出和串行输出。本次设计采用并行输出。 其中,当BYTE为0时,D7-D0输出高八位;BYTE为1时,D7-D0输出低八位。用于控制转换和读写,当该位为0时,启动依次转换;为1时,启动数据输出,外部设备可读取。用于指示目前是否转换完成,当该位为0时,正在转换;为1时,转换完成。 其基本串行输出操作流程如下所述。 首先通过A1,A0管脚选择输入通道。将拉低40ns(最大12us)即可启动一次AD转换。(由此处可知,可使用赋值语句连读对进行操作,依次连续令其为1,0,1,由于单片机执行指令需要消耗指令周期的时间,可以满足40ns的要求)转换开始时,引脚被拉低,并将持续为低,直至转换完成,输出寄存器被更新后,该位被拉高。若BYTE位为低,则在上升沿时,转换结果高8位输出,反之,低八位输出。在为0(即转换进行时)时,所有的转换指令都将被忽略。 其内部结构如图 5所示。截取自其数据手册。 图 5 ADS7825内部结构图 本次设计采用AIN0通道输入电池电压,在仿真过程中,由电位器RV1分压模拟变化的电池电压。注意到其输入电阻并不高,因此在实际应用时,输入端应添加电压跟随器提高后级输入阻抗。 3.4. 数码管电路设计
3.4.1. 原理图图 6数码管驱动控制电路 3.4.2. 数码管本次设计采用八位共阴极数码管用于显示结果,其中高四位用于显示当前电池两端电压,低四位用于显示当前电池剩余电量百分比(最低为未用)。采用74LS138进行数码管位选,进行动态扫描。74LS373采用直通方式,用于传送数码管段码及提供驱动电流。 3.4.3. 74LS13874LS138是3线-8线译码器,有三个选择输入端,三个允许输入端和八个输出端。其真值表如表 2所示。G1,,为控制端,A、B、C为输入端, Y0~Y7为输出端,低有效.当 G1,, =1 0 0 时才能进行译码输出,否则8个输出端Y0~Y7全为1。 表 2 74LS138真值表 3.4.4. 74LS37374LS373是8数据锁存器。主要用于数码管、按键等等的控制,以及总线扩展时,P0口地址的锁存。其真值表如表 3所示。 表 3 74HC373 真值表
3.5. 串行口电路设计
3.5.1. 原理图图 7串行口通讯电路 3.5.2. MAX232由于RS-232标准采用负逻辑,即逻辑1为-3V~-15V,逻辑0为+3~+15V。而本单片机系统为TTL信号系统。TTL电平规定,+5V等价于逻辑“1”,0V等价于逻辑“0”。因此,DB-9与单片机的连接需要进行电平转换,如图 8所示,选用MAX232芯片完成上述功能。 图 8 MAX232引脚图及典型应用电路 3.5.3. DB-9由于 RS-232C 并未定义连接器的物理特性,因此,出现DB-25 和 DB-9 各种类型的连接器,其引脚的定义也各不相同。如图 7右侧P1组件所示即为 DB-9 连接器。本次设计仅使用其TXD,GND引脚。 如图所示,使用MAX232芯片进行电平转换后,MAX232输入管脚T1IN与AT89C52串行口P3.1/TXD连接。(由于软件特殊原因,在MAX232芯片1通道T1OUT处增加74LS04非门进行处理。本次设计中单片机仅使用发送功能,未连接AT89C52接收端P3.0/RXD) 4. 程序设计
4.1. 单片机程序设计
4.1.1. 程序流程图图 9 下位机程序流程图 4.1.2. 程序设计本次单片机程序设计中,使用T1作为串口波特率发生器,T0作为定时器用于定时刷新动态数码管。 主函数对各功能函数进行调用,其形式较为简单。在程序右侧添加由详细功能注释说明。其中两次测量间隔使用的是软件延时。通过for语句消耗单片机执行指令的时间,实现延时。 该函数用于控制ADS7825对模拟电压进行采集转换,并将16位转换结果储存在16位整数中。其具体操作过程如3.3.2中文字所述。 该函数主要用于电池电压值,已用时间,剩余电量的计算。其中,对小数的处理采用了扩大1000或100倍后四舍五入取整的方式,这样为后续取出原小数部分各位数值提供了便利。 电压值U计算公式如下。 其中result为ADS7825输出16位数据。由于该芯片支持负电压输入,因此上式中分母中指数部分为15而非16。 已用时间t由插值函数Linear(double v)运算得到。 剩余电量计算公式如下。 其中,t为已用时间,单位min;180000为电池容量Q,单位mAmin;500为放电电流,单位mA。 该函数用于实现由测得电压U反向计算当前已放电时间。计算结果以unsigned int型变量返回。采用线性插值方式计算,其中当测得电压正好与实验数据点相同时,直接取用对应数据点的时间值。 用于进行插值的实验数据以code标识符标识,另其存放在程序存储器当中,以节省RAM空间。当测得电压值超出实验数据时,分别认为以放电时间为0和360,即认为电池充满电未使用和电池电量已完全释放完毕。 该函数主要用于,依据最新获得的数值型电压值,电量值,更新数码管显示缓冲区。但实际上,数码管的动态扫描不在本函数中完成,由定时器T0定时扫描。刷新过程中对电压值最高位小数点及电量百分比最高位0进行了显示处理。 该函数用于组织一帧通信数据并由串口发送至上位机。为测试方便,其中使用了中文字符。定义回车换行符为一帧数据结束,供上位机识别所需。 其他函数包括定时器T0/T1的工作模式配置函数及其中断相应函数,还包括用于串行口发送字符/字符串的函数。 以上提到的函数在附件2中给出。 4.2. 上位机程序设计为有效缩短开发时间,本次设计中的上位机设计采用图形化编程语言Labview设计完成,其主要功能即为通过串行口接收由下位机传送的字符串数据,并对其进行解析,随后使用图形化的方式显示当前剩余电量百分比等。 4.2.1. 程序流程图图 10 上位机程序流程图 4.2.2. 界面设计图 11 上位机界面设计 4.2.3. 程序设计上位机使用图形化编程语言Labview设计完成,其程序框图(即源程序)如下所示。其中主要使用了平铺式顺序结构,while循环,条件结构。 图 12 下位机程序设计 5. 系统调试5.1. 串口参数设置本次设计约定串口通信有关参数如下。 表 4串口参数约定 按照表1设置串口参数如下。
图 13 MCU串口配置 图 14 上位机串口配置
5.2. 调整滑动变阻器调整图 4中滑动变阻器RV1动触点的位置合理。 5.3. 启动MCU软件仿真点击下图第一个按钮启动仿真。 图 15 Protues启动 出现如下图所示内容说明仿真成功执行。 图 16仿真正在进行 5.4. 打开上位机串口连接点击下图第一个按钮启动上位机程序。 图 17 Labview启动 上图变换为下图显示时说明上位机成功启动。 图 18Labview已启动 5.5. 观察数码管显示及上位机显示下图中左侧显示当前测得的电压值,右侧显示当前剩余电量(最高位0做处理后并未显示),最右侧数码管在本次程序设计中被禁止使能,始终处于关闭状态。 图 19 下位机数码管显示 图 20上位机显示电池电压及剩余电量 6. 总结本次设计主要解决了AD转换及数据处理问题。其次主要包括串行口配置使用,数码管动态定时刷新等的设计。在设计开始,查找了较多的AD转换芯片,并通过仿真软件,对照数据手册,对其进行功能验证。早期验证的主要为ADC0808/0809,AD1674,ADS7824等,考虑分辨率及仿真软件限制等因素,最后选择了16位ADS7825芯片。仿真逻辑关系与数据手册显示基本全部对应。 下位机软件设计中,将各功能模块单独写成函数,使得主函数基本流程较为清晰。对各变量,常量,函数等进行了较多的注释说明,以提高程序可读性和后续修改的便利性。为节省开发设计时间,编程过程中对同一数值进行了多次运算,并未考虑存储后再使用的方式,有可能造成了不必要的程序执行时间开销。考虑了电压值最高位(个位)处的小数点处理,及电池电量百分比最高位为0时的处理,适应了阅读习惯。 ADC芯片输入端在设计时未考虑测量电池电压时的负载效应,可能会引起较大误差。可以通过引入电压跟随器提高输入阻抗的方式,进一步降低误差。 上位机设计中主要完成的工作是对下位机传送来的字符串的解析和显示。每次采样转换完成后,下位机发送一条以回车换行结尾的字符串,上位机通过该结束位检测一帧数据传输完毕,随后对接收到的数据帧进行字符串分离解析等工作。
7. 附件1(硬件原理图)
单片机源程序如下:
- #include<reg52.h>
-
- sbit PARSER = P2^0; //串并行控制位
- sbit BYTE = P2^1; //高低字节控制位
- sbit RC = P2^4; //读取转换控制位
- sbit BUSY = P2^2; //忙状态位
- sbit ADDR_A = P3^5; //低位地址控制位
- sbit ADDR_B = P3^6; //低位地址控制位
- sbit ADDR_C = P3^7; //高位地址控制位
-
- void ConfigUART(unsigned int baud); //串行口配置函数
- void ConfigTimer0(); //定时器0配置函数
- void SendData(unsigned char ch); //字符发送函数
- void SendString(char *s); //字符串发送函数
- void GetVoltage(); //ADC电压获取函数
- unsigned int Linear(double v); //线性插值函数,参数v为实测电压
- void DataProcess(); //数据处理函数
- void LedBufRefresh(); //数码管显示缓冲区刷新函数
- void UartSend(); //串口数据发送函数
-
- unsigned char voltage[] = {'0','.','0','0','0',0};
- unsigned char time_used[] = {'0','0','0',0};
- unsigned char percentage[] = {'0','0','0',0};
- unsigned long j,time_used_value,result,percentage_value,voltage_value;
- unsigned int code time_sample[21]={0,18,36,54,72,90,108,126,144,162,180,198,216,234,252,270,288,306,324,342,360};
- double code voltage_sample[21]={4.35,4.24,4.135,4.005,3.92,3.889,3.858,3.826,3.8,3.78,3.762,3.743,3.725,3.705,3.686,3.667,3.65,3.628,3.492,3.05,2};
-
- //数码管显示字符转换表
- unsigned char code LedChar[] = {
- 0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
- 0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
- };
- //数码管显示缓冲区,初值0x00确保启动时都不亮
- unsigned char LedBuff[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
-
- void main()
- {
- ConfigUART(9600); //配置串行口工作模式及参数
- ConfigTimer0(); //配置定时器0用于数码管刷新
- EA = 1; //打开总中断
- while(1)
- {
- GetVoltage(); //获取ADC电压值
- DataProcess(); //数据处理
- LedBufRefresh(); //显示缓冲区刷新
- UartSend(); //串口发送
- for(j=0;j<30000;j++); //延时读取
- }
- }
- /* 数据处理函数 */
- void DataProcess()
- {
- /* 计算电压值 */
- voltage_value = (unsigned long)(((double)result * 10 / 32767) * 1000 + 0.5);
- /* 电压值数组 */
- voltage[4] = '0' + voltage_value % 10;
- voltage[3] = '0' + (voltage_value /10) % 10;
- voltage[2] = '0' + (voltage_value /100) % 10;
- voltage[0] = '0' + (voltage_value /1000) % 10;
- /* 剩余用时数组 */
- time_used_value = Linear((double)result * 10 / 32767);
- time_used[2] = '0' + time_used_value % 10;
- time_used[1] = '0' + (time_used_value / 10) % 10;
- time_used[0] = '0' + (time_used_value / 100) % 10;
- /* 百分比数组 */
- percentage_value = (unsigned long)((double)(180000 - time_used_value * 500) / 180000 * 100 + 0.5);
- percentage[2] = '0' + percentage_value % 10;
- percentage[1] = '0' + (percentage_value / 10) % 10;
- percentage[0] = '0' + (percentage_value / 100) % 10;
- if((percentage_value / 100) % 10)
- {
- percentage[0] = '0' + (percentage_value / 100) % 10;
- }
- else
- {
- percentage[0] = ' ';
- }
- }
- /* 线性插值函数,参数v为实测电压 */
- unsigned int Linear(double v)
- {
- unsigned int i,t1,t2,t;
- double v1,v2;
- if(v >= 4.35) //大于最大电压
- {
- t = 0;
- return t;
- }
- if(v <= 2) //小于最小电压
- {
- t = 360;
- return t;
- }
- for(i=0; i<21; i++) //遍历插值范围
- {
- if(voltage_sample[i] < v)
- {
- v1 = voltage_sample[i-1];
- v2 = voltage_sample[i];
- t1 = time_sample[i-1];
- t2 = time_sample[i];
- t = t2 - (v - v2) * (double)(t2 - t1) / (v1 - v2);
- break; //计算插值结果t
- }
- else if(voltage_sample[i] == v)
- {
- t = time_sample[i]; //恰好取采样值
- break;
- }
- }
- return t;
- }
- /* ADC电压获取函数 */
- void GetVoltage()
- {
- unsigned char high,low;
- PARSER = 1; //并行输出
- RC = 1; //启动转换
- RC = 0;
- RC = 1;
-
- while(!BUSY); //等待转换完毕
-
- P0 = 0xFF; //读取高八位
- BYTE = 0;
- high = P0;
-
- P0 = 0xFF; //读取低八位
- BYTE = 1;
- low = P0;
-
- result = high * 256 + low; //合并高低字节
- }
- /* 定时器0中断服务函数 */
- void InterruptTimer0() interrupt 1
- {
- static unsigned char i = 0; //动态扫描的索引
- TH0 = 0xFC; //重新加载初值
- TL0 = 0x67;
- //以下代码完成数码管动态扫描刷新
- P1 = 0x00; //显示消隐
- switch(i)
- {
- case 0: ADDR_C = 0; ADDR_B = 0; ADDR_A = 0; i++; P1=LedBuff[0]; break;
- case 1: ADDR_C = 0; ADDR_B = 0; ADDR_A = 1; i++; P1=LedBuff[1]; break;
- case 2: ADDR_C = 0; ADDR_B = 1; ADDR_A = 0; i++; P1=LedBuff[2]; break;
- case 3: ADDR_C = 0; ADDR_B = 1; ADDR_A = 1; i++; P1=LedBuff[3]; break;
- case 4: ADDR_C = 1; ADDR_B = 0; ADDR_A = 0; i++; P1=LedBuff[4]; break;
- case 5: ADDR_C = 1; ADDR_B = 0; ADDR_A = 1; i++; P1=LedBuff[5]; break;
- case 6: ADDR_C = 1; ADDR_B = 1; ADDR_A = 0; i=0; P1=LedBuff[6]; break;
- //case 7: ADDR_C = 1; ADDR_B = 1; ADDR_A = 1; i=0; P1=LedBuff[7]; break;
- default: break;
- }
- }
- /* 数码管显示缓冲区刷新函数 */
- void LedBufRefresh()
- {
- LedBuff[6] = ~LedChar[percentage_value % 10];
- LedBuff[5] = ~LedChar[(percentage_value / 10) % 10];
- if((percentage_value / 100) % 10)
- {
- LedBuff[4] = ~LedChar[(percentage_value / 100) % 10];
- }
- else
- {
- LedBuff[4] = 0;
- }
- LedBuff[3] = ~LedChar[voltage_value % 10];
- LedBuff[2] = ~LedChar[(voltage_value /10) % 10];
- LedBuff[1] = ~LedChar[(voltage_value /100) % 10];
- LedBuff[0] = ~(LedChar[(voltage_value /1000) % 10] &0x7F);
- }
- void UartSend()
- {
- SendString("当前电压:");
- SendString(voltage);
- SendString("V");
- // SendString(" 已用时间:");
- // SendString(time_used);
- // SendString("Min");
- SendString(" 剩余电量:");
- SendString(percentage);
- SendString("%\r\n");
- }
- /* 串口配置函数,baud-通信波特率 */
- void ConfigUART(unsigned int baud)
- {
- SCON = 0x50; //配置串口为模式1
- TMOD &= 0x0F; //清零T1的控制位
- TMOD |= 0x20; //配置T1为模式2
- TH1 = 256 - (11059200/12/32)/baud; //计算T1重载值
- TL1 = TH1; //初值等于重载值
- ET1 = 0; //禁止T1中断
- ES = 1; //使能串口中断
- TR1 = 1; //启动T1
- }
- /* 定时器0配置函数 */
- void ConfigTimer0()
- {
- TMOD &= 0xF0; //清零T0的控制位
- TMOD |= 0x01; //设置T0为模式1
- TH0 = 0xFC; //为T0赋初值0xFC67,定时1ms
- TL0 = 0x67;
- ET0 = 1; //使能T0中断
- TR0 = 1; //启动T0
- }
- /* UART中断服务函数 */
- void InterruptUART() interrupt 4
- {
- if(RI) //接收到字节
- {
- RI = 0; //清零接收中断标志位
- }
- if(TI) //字节发送完毕
- {
- TI = 0; //清零发送中断标志位
- }
- }
- /* UART字符发送函数 */
- void SendData(unsigned char ch)
- {
- SBUF = ch; //启动发送
- while(!TI); //等待结束
- }
- /* UART字符串发送函数 */
- void SendString(unsigned char *s)
- {
- while(*s) //循环发送
- {
- SendData(*s++);
- }
- }
复制代码
|