几天前在做51循迹小车程序的时候,为了能用得上PID算法,在程序中用了很多浮点数运算。大家都知道51单片机是8位单片机,而浮点数是32位的,当时我就在想,浮点运算用多了会不会影响到小车程序的反应速度和性能呢,当时为了完工,没有多想,只是想着——反正我在程序里也用不到多少,应该不会有太大的影响。
今天一想,为何不来做个测试呢,说做就做,程序很快调通了,测试结果也出来了。
首先说一下我所用的51单片机配置:
STC12C5A60S2增强型51单片机, 11.0592M晶振, 1T模式(1个时钟周期执行1条指令,大部分51单片机是12T的,单片机这点和PC不同)。
测试原理:
1.用片上定时器/计数器0实现了一个计时器;
2.记录一定量浮点数计算(加法)运算的总时间,并记录浮点运算测试过程中其他运算操作的时间;
3.利用以上记录的两个时间之差和运行前指定的运算次数即可算出每秒浮点运算次数(暂时用fps表示);
先来看看测试结果:
测试总时间:407s
平均速度:25047.8 fps
最快速度:33559.5 fps
最慢速度:22932.8 fps
再来看看我们的测试主体部分吧:
// 先指定浮点运算次数: n = 10000; send_str("t0\tt1\tt2\tfps\ips\r\n"); while(1) { t0 = t_cur; // float t_cur 为当前时间,由中断服务程序自动更新。 for( i=0; i<n; ++i); t1 = t_cur; for( i=0; i<n; ++i) { ft0 += ft; } t2 = t_cur; sprintf(buffer, "%.3f\t%.3f\t%.3f\t", t0, t1, t2); send_str(buffer); // send_str 将buffer发送到串口,插上串口线就可以在“串口助手”里看见结果了. // (t2-t1)为第二个循环时间(浮点加法,整数++运算,整数比较运算) // (t1-t0)为第一个循环时间(整数++,整数比较) // 两者相减就是 n 次浮点数运算的时间 sprintf(buffer, "%g\r\t", (float)n/((t2-t1) - (t1-t0)) ); send_str(buffer); }
在我笔记本电脑上的测试结果:
平均值: | 836263534 |
最大值: | 990099010 |
最小值: | 735294118 |
我笔记本的配置:
CPU: intel core i5 2.30GHz
RAM: 2.00GB DDR3
操作系统: Win7 旗舰版 64bit
用此我们看到了二者的对比,笔记本的速度大约是单片机的 33387.7 倍( = 836263534 / 25047.8 )。然而,这并不是CPU的最快速度(毕竟是在操作系统上运行的 ,CPU同时需要处理其他任务),51MCU却是“开足马力了”。
由此我们也有了一个大致的概念:
增强型 51 单片机每秒也只能做几万次浮点运算(普通的只有它的 1/12 ,大概只有 两千多次/秒);
现在主流PC每秒能作将近一亿次浮点运算。
当然,在电脑上测试的程序要做些改动,运算次数的设定不能太小,否则在后面做除法的时候可能会溢出,而且次数设定得太少的时候误差也不叫大,下面贴出源码,仅供参考:
#include <ctime> #include <cstdio> using namespace std; int main() { int i; float ft=0.001, ft0=0.0; clock_t t0, t1, t2; long n = 100000000; double time_cnt = 500.0; while(1) { t0 = clock(); for( i=0; i<n; ++i ); t1 = clock(); for( i=0; i<n; ++i) { ft0 += ft; } t2 = clock(); printf("%d\t%d\t%d\t", t0, t1, t2); printf("%f\t\n", (double)n/(((t2-t1) - (t1-t0))/(double)CLOCKS_PER_SEC) ); if( clock()/CLOCKS_PER_SEC > time_cnt ) break; } return 0; }
我电脑上使用的是:g++ (GCC) 4.6.1(MinGW版)默认编译设置.
编译前面一段代码的是Keil uVersion 3.0,下面贴出程序全部源码,欢迎各位大虾拍砖。
main.c:
#include "def.h" #define LEDU 0x01 #define LEDL 0x02 #define LEDD 0x04 #define LEDR 0x08 sbit start=P0^5; int i, n; xdata float ft = 0.001, ft0 = 0.0; xdata float t0 = 0.0, t1 = 0.0, t2 = 0.0, t3 = 0.0, t4 = 0.0; char ch=1, ch0=0; void init() { tm0_init(); UART_init(6); P0 &= 0x00; start=1; while( start ); } void main() { init(); restart(); // timer restart. // 先指定浮点运算次数: n = 100000; send_str("t0\tt1\tt2\tfps\ips\r\n"); while(1) { t0 = t_cur; // t_cur 为当前时间,由中断服务程序自动更新。 for( i=0; i<n; ++i); t1 = t_cur; for( i=0; i<n; ++i) { ft0 += ft; } t2 = t_cur; sprintf(buffer, "%.3f\t%.3f\t", t0, t1); send_str(buffer); sprintf(buffer, "%.3f\t", t2 ); send_str(buffer); // (t2-t1)为第二个循环时间(浮点加法,整数++运算,整数比较运算) // (t1-t0)为第一个循环时间(整数++,整数比较) // 两者相减就是 n 次浮点数运算的时间 sprintf(buffer, "%g\r\t", (float)n/((t2-t1) - (t1-t0)) ); send_str(buffer); } }
def.h:
#ifndef _DEF_H_ #define _DEF_H_ #include "stc51.h" /////////////////////////////////////////////////////////////////////////////// #define UART #define TIMER /* 计时器,定时/计数器0实现 */ /////////////////////////////////////////////////////////////////////////////// typedef unsigned char uchar; typedef unsigned int uint; typedef uchar uint8; #ifdef UART // 串口通信 // UART.c void UART_init(uint8 mode); void send_data(char ch); // 向串口发送一个8位整数(非中断方式) void send_str(char* str); // 串口发送字符串 #define SENDOUT() send_str(buffer) #define sendout() SENDOUT() #include <stdio.h> extern xdata char buffer[]; // 外部数组,串口字符串缓存. #endif #ifdef TIMER // 计时器,定时器0实现. void tm0_init(void); void restart(void); // 计时变量: extern uint t_msec; // millisecond counter. extern uchar t_sec; // second counter. extern float t_cur; // second & millisecond. #endif /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void init(void); /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// #endif // _DEF_H_
timer.c:
#include "def.h" #ifdef TIMER // 计时变量: uint t_msec=0; // millisecond counter. uchar t_sec=0; // second counter. float t_cur=0.0; // second & millisecond. #define MODE1T //Timer clock mode, comment this line is 12T mode, uncomment is 1T mode #define FOSC 11059253L // 11.0592 MHz #ifdef MODE1T #define T1MS (65536-FOSC/1000) //1ms timer calculation method in 1T mode #else #define T1MS (65536-FOSC/12/1000) //1ms timer calculation method in 12T mode #endif void tm0_init(void) //50毫秒@11.0592MHz { #ifdef MODE1T AUXR |= 0x80; #else AUXR &= 0x7F; // 最高位置0 //定时器时钟12T模式 #endif TMOD |= 0x01; // 最低位置1 // 16位定时器 EA = 1; ET0 = 1; TL0 = T1MS; //设置定时初值 TH0 = T1MS >> 8; //设置定时初值 TR0 = 1; //定时器0开始计时 } //定时器0中断服务程序 /* Timer0 interrupt routine */ void tm0_isr() interrupt 1 // using 1 { TL0 = T1MS; //reload timer0 low byte TH0 = T1MS >> 8; //reload timer0 high byte ++t_msec; t_cur += 0.001; if (t_msec == 1000) { //1ms * 1000 -> 1s t_msec = 0; //reset millisecond counter ++t_sec; // second counter. P0 ^= 0x0f; // lighting... ... } } void restart(void) { t_msec=0; t_sec=0; t_cur=0.0; tm0_init(); } #endif // TIMER
UART.c:
#include "def.h" #ifdef UART xdata char buffer[32]; // 全局变量. //串口初始化 晶振为 11.0592M 方式 1 波特率 300-57600 void UART_init(unsigned char BaudRate) { unsigned char THTL; switch (BaudRate) { case 1: THTL = 64; break; //波特率 300 case 2: THTL = 160; break; //600 case 3: THTL = 208; break; //1200 case 4: THTL = 232; break; //2400 case 5: THTL = 244; break; //4800 case 6: THTL = 250; break; //9600 case 7: THTL = 253; break; //19200 case 8: THTL = 255; break; //57600 default: THTL = 250; } SCON = 0x50; //串口方式 1 ,8位 波特率可变 允许接收 TMOD = 0x20; //定时器1定时方式2 TCON = 0x40; //设定时器 1 开始计数 PCON = 0x80; //波特率加倍控制,SMOD 位 TH1 = THTL; TL1 = THTL; RI = 0; //清收发标志 TI = 0; // 发送 TR1 = 1; //启动定时器 } void send_data(char OutData) //向串口输出一个字符(非中断方式) { SBUF = OutData; //输出字符 while(!TI); //空语句判断字符是否发完 TI = 0; //清 TI } void send_str(char* str) // 串口发送字符串 { while(*str) send_data(*str++); } // #define UARTOUT(inum) ComOutChar((uchar)inum); // ComOutChar((uchar)inum>>8);ComOutChar((uchar)inum&0xff); #endif // UART