幾天前在做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