|  | 【單片機程序】AVRMega8製作的簡易示波器(只能檢測高低電平) | 
                
          |   一派掌門 二十級 | 
               單片機端口定義:   | 
                
          |   一派掌門 二十級 |  | 
|
        
                
          |   一派掌門 二十級 |  | 
|
        
                
          |   一派掌門 二十級 | 
              注意:本程序按GPL協議發佈。程序如下:
 【ports.h】
 #define K1 (PINB&BIT(0)) //id減1
 #define K2 (PINB&BIT(1)) //id加1
 #define K3 (PINB&BIT(2)) //ctrl鍵,同時按下k1或k2可以加/減25
 #define K4 (PINB&BIT(3)) //開始/停止
 
 #define INPUT (PIND&BIT(2)) //輸入的信號 PD2
 #define HC595_SHCK1 PORTC|=BIT(0)
 #define HC595_SHCK0 PORTC&=~BIT(0)
 #define HC595_STCK1 PORTC|=BIT(1)
 #define HC595_STCK0 PORTC&=~BIT(1)
 #define HC595_SD1 PORTC|=BIT(2)
 #define HC595_SD0 PORTC&=~BIT(2)
 
 #define LED1_ON PORTB&=~BIT(4) //L1表示端口檢測器是否正在工作
 #define LED1_OFF PORTB|=BIT(4)
 #define LED2_ON PORTB&=~BIT(5) //L2表示當前端口是否是高電平
 #define LED2_OFF PORTB|=BIT(5)
 
 | 
|
        
                
          |   一派掌門 二十級 | 
              【eeprom.c】#include <iom8v.h>
 #include <macros.h>
 #include "eeprom.h"
 
 void EEPROM_Write(unsigned int address, unsigned char Data)
 {
 while (EECR&BIT(EEWE)) //等待上一次寫操作結束
 ; /*seg8_scan();*/ //如果程序中有數碼管,請去掉該註釋,避免寫入時數碼管熄滅或閃爍
 EEAR=address; //可以直接對地址寄存器賦int值
 EEDR=Data;
 EECR|=BIT(EEMWE); //主機寫入允許
 EECR|=BIT(EEWE); //允許EEPROM
 }
 
 void EEPROM_Read(unsigned int address, unsigned char* Data)
 {
 while (EECR&BIT(EEWE)) //等待上一次寫操作結束
 ; /*seg8_scan();*/ //如果程序中有數碼管,請去掉該註釋,避免寫入時數碼管熄滅或閃爍
 EEAR=address;
 EECR|=BIT(EERE); //啟動讀操作
 *Data=EEDR;
 }
 
 | 
|
        
                
          |   一派掌門 二十級 | 
              【eeprom.h】void EEPROM_Write(unsigned int address, unsigned char Data);
 void EEPROM_Read(unsigned int address, unsigned char* Data);
 
 | 
|
        
                
          |   一派掌門 二十級 | 
              【HC595.c】#include <iom8v.h>
 #include <macros.h>
 #include "HC595.h"
 #include "ports.h"
 
 void HC595_SerIn(unsigned char Data)
 {
 unsigned char i;
 for (i=0;i<8;i++)
 {
 HC595_SHCK0; //CLOCK_MAX=100MHz
 if (Data&BIT(7-i))
 HC595_SD1;
 else
 HC595_SD0;
 HC595_SHCK1;
 }
 }
 
 void HC595_ParOut(void)
 {
 HC595_STCK0;
 HC595_STCK1;
 }
 
 | 
|
        
                
          |   一派掌門 二十級 | 
              【HC595.h】void HC595_SerIn(unsigned char Data);
 void HC595_ParOut(void);
 
 | 
|
        
                
          |   一派掌門 二十級 | 
              【PortChecker.c】#include <iom8v.h>
 #include <macros.h>
 #include "HC595.h"
 #include "eeprom.h"
 #include "ports.h"
 
 flash unsigned char seg8[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90};
 unsigned int time[256]={0};
 unsigned char port_states[32]={0xff};
 
 int id=0;
 unsigned int num=0;
 unsigned char cfg=0x00;
 unsigned int length=1;
 unsigned int interval=0;
 
 void delay250us(void)
 {
 unsigned int i;
 for (i=0;i<285;i++);
 }
 
 void delay(unsigned int n)
 {
 unsigned int i;
 while (n--)
 for (i=0;i<1140;i++);
 }
 
 //數碼管動態掃描
 //參數n為掃描次數
 void seg8_scan(unsigned char n)
 {
 unsigned char i;
 unsigned int f;
 unsigned char tmp;
 while (n--)
 {
 f=1000; //每次循環時f必須初始化
 for (i=0;i<=2;i++)
 {
 tmp=~seg8[id%f/(f/10)];
 if (i==2)
 tmp|=BIT(7); //小數點
 HC595_SerIn(BIT(i));
 HC595_SerIn(tmp);
 HC595_ParOut();
 delay250us();
 f/=10;
 }
 
 f=10000;
 for (i=3;i<=6;i++)
 {
 tmp=~seg8[num%f/(f/10)];
 if ((cfg&0x03)+0x03==i)
 tmp|=BIT(7);
 HC595_SerIn(BIT(i));
 HC595_SerIn(tmp);
 HC595_ParOut();
 delay250us();
 f/=10;
 }
 }
 }
 
 //存儲數據到EEPROM中
 void savedata(void)
 {
 unsigned char i;
 for (i=0;i<240;i++) //只存儲前240個數據,佔用空間480位元組
 {
 EEPROM_Write(i*2,time[i]/256);
 EEPROM_Write(i*2+1,time[i]%256);
 }
 
 //寫入高低電平標識,佔用30位元組
 for (i=0;i<30;i++)
 EEPROM_Write(0x1e0+i,port_states[i]);
 
 //寫入長度
 if (length>240)
 i=240;
 else
 {
 i=length%256;
 if (i<1)
 i=1;
 }
 EEPROM_Write(0x1fe,0x3d); //倒數第二位為是否有數據的標識
 EEPROM_Write(0x1ff,i); //最後一位表示長度
 }
 
 //停止鍵的檢測
 unsigned char stop_working(void)
 {
 if (!K4)
 {
 while (!K4);
 return 0x80;
 }
 else
 return 0x00;
 }
 
 //禁用數碼管
 void disable_seg8(void)
 {
 HC595_SerIn(0x7f); //七個數碼管全部顯示
 HC595_SerIn(0x40); //字符「-」
 HC595_ParOut();
 }
 
 //核心函數:
 //給輸入端的高低電平計時
 void work(void)
 {
 unsigned int i;
 unsigned char low;
 disable_seg8();
 TIMSK&=~BIT(TOIE0); //禁止定時器0中斷
 LED1_ON;
 LED2_OFF;
 for (length=1;length<=256;length++)
 {
 TCNT1H=0x00;
 TCNT1L=0x00;
 
 if (INPUT)
 {
 cfg|=BIT(2); //倒數第3位表示當前檢測的是什麼電平
 while (INPUT)
 {
 if (stop_working()==0x80)
 {
 cfg|=BIT(3);
 break;
 }
 }
 }
 else
 {
 cfg&=~BIT(2);
 while (!INPUT)
 {
 if (stop_working()==0x80)
 {
 cfg|=BIT(3);
 break;
 }
 }
 }
 
 //記錄時間(us)
 low=TCNT1L; //先讀取一次低八位
 time[length-1]=TCNT1H;
 time[length-1]*=256;
 time[length-1]+=low;
 
 //記錄電平
 if (cfg&BIT(2))
 port_states[(length-1)/8]|=BIT((length-1)%8);
 else
 port_states[(length-1)/8]&=~BIT((length-1)%8);
 
 if (stop_working()==0x80)
 break;
 if (cfg&BIT(3))
 {
 cfg&=~BIT(3);
 break;
 }
 }
 LED1_OFF;
 length--;
 savedata();
 TIMSK|=BIT(TOIE0); //允許定時器0中斷
 }
 
 //顯示時間和電平
 void gettime(void)
 {
 num=time[id];
 if (num>9999)
 {
 num/=10;
 cfg&=~BIT(1); //小數點位置
 cfg|=BIT(0); //65.46ms
 }
 else
 {
 cfg&=~BIT(1);
 cfg&=~BIT(0); //7.000ms
 }
 
 if (port_states[id/8]&BIT(id%8))
 LED2_ON;
 else
 LED2_OFF;
 interval=0; //防止再次自動跳變
 }
 
 //按鍵掃描
 void key_scan(void)
 {
 //減1、25
 if (!K1)
 {
 seg8_scan(5); //防止按下按鍵後數碼管熄滅
 if (!K1)
 {
 if (!K3)
 {
 if (id>=25)
 id-=25;
 }
 else
 {
 id--;
 if (id<0)
 id=length-1;
 }
 
 gettime();
 while (!K1)
 seg8_scan(1);
 seg8_scan(3);
 }
 }
 //加1、25
 if (!K2)
 {
 seg8_scan(5);
 if (!K2)
 {
 if (!K3)
 {
 if (id+25<=length-1)
 id+=25;
 }
 else
 {
 id++;
 if (id>=length)
 id=0;
 }
 
 gettime();
 while (!K2)
 seg8_scan(1);
 seg8_scan(3);
 }
 }
 
 if (!K4)
 {
 while (!K4)
 seg8_scan(1);
 work();
 id=1; //完畢後自動顯示第2個時間值,通常情況下採集到的第一個和最後一個時間值很不準確
 if (length<=1 || length>300)
 {
 id=0;
 length=1;
 }
 gettime();
 }
 }
 
 //讀取數據
 void readdata(void)
 {
 unsigned char i;
 unsigned char l,h;
 EEPROM_Read(0x1fe,&i); //讀標誌位
 if (i==0x3d)
 {
 //如果已存儲了數據,則讀出來
 EEPROM_Read(0x1ff,&l); //獲取數據長度
 length=l;
 for (i=0;i<240;i++)
 {
 EEPROM_Read(i*2,&h);
 EEPROM_Read(i*2+1,&l);
 time[i]=h*256+l;
 }
 //讀高低電平標識
 for (i=0;i<30;i++)
 EEPROM_Read(0x1e0+i,&port_states[i]);
 if (length>=2)
 id=1;
 gettime();
 }
 }
 
 void main(void)
 {
 DDRC=0xff;
 PORTC=0xff;
 DDRB=0xf0; //PB口低四位為按鍵
 PORTB=0xff;
 DDRD=0xf3; //兩個外中斷口設為輸入
 PORTD=0xff;
 
 TCCR1A=0x00;
 TCCR1B=0x02; //定時器1設為8分頻,也就相當於51單片機接12M晶振
 readdata();
 
 TCNT0=0x06; //定時器0定時32ms
 TCCR0=0x05; //定時器0設為1024分頻
 TIMSK|=BIT(TOIE0); //允許定時器0中斷
 SEI();
 
 while (1)
 {
 seg8_scan(1);
 key_scan();
 }
 }
 
 //自動跳變
 #pragma interrupt_handler et0:iv_TIM0_OVF
 void et0(void)
 {
 if (length>1)
 {
 interval++;
 if (interval>=1000) //定時32s
 {
 id++;
 if (id>=length)
 id=0;
 gettime();
 }
 }
 TCNT0=0x06;
 }
 
 
              
                | 
|
        
                
          |   一派掌門 二十級 | 
              使用時按下K4鍵開始捕捉或停止捕捉,完畢後用ISP口連接電路板和電腦,打開AVR Fighter,通過ISP口讀取EEPROM數據並保存為bin文件,再用php程序就能生成一張波形圖了!
 
 | 
|
        
                
          |   一派掌門 二十級 | 
              以下就是我獲得的我的遙控器發射的電平,我做了一點修改而已   | 
|
        
                
          |   一派掌門 二十級 | 
              以下為根據bin文件生成波形圖的php程序代碼:【portcheck.php】
 <?php
 /* 根據EEPROM內容生成I/O口波形圖像
 * 作者:巨大八爪魚
 * 時間:2013年9月30日15:08:06
 **/
 define("US_PER_PX",50); //每像素表示多少微秒
 define("FILENAME","錯誤的紅外發射.bin");
 header("Content-type:image/png");
 
 function BIT($n)
 {
 return 1<<$n;
 }
 
 $file=fopen(FILENAME,"rb"); # 打開文件
 $time=fread($file,480); # 持續時間
 $port_states=fread($file,30); # 高低電平標識
 fread($file,1); # 該位始終是0x3d,跳過
 $length=ord(fread($file,1)); # 長度
 
 # 計算圖片寬度
 $width=0;
 for ($i=0;$i<$length;$i++)
 {
 $t=ord($time[$i*2])*256+ord($time[$i*2+1]);
 $width+=$t;
 }
 $width=ceil($width/US_PER_PX);
 
 $im=imagecreatetruecolor($width,64);
 $back_color=imagecolorallocate($im,251,252,205);
 imagefill($im,10,5,$back_color); # 背景顏色
 
 $color=imagecolorallocate($im,0,64,0);
 $x=$y=0;
 for ($i=0;$i<$length;$i++)
 {
 $lasty=$y;
 $y=10;
 if (ord($port_states[(int)floor($i/8)])&BIT($i%8))
 $y=50; # 如果該位是高電平
 
 # 電平發生跳變時加豎線
 if ($y!=$lasty && $i>0)
 imageline($im,$x,11,$x,49,$color);
 
 # 從第二個電平開始更換顏色
 if ($i==1)
 $color=imagecolorallocate($im,0,0,128);
 
 $t=ord($time[$i*2])*256+ord($time[$i*2+1]);
 $t/=US_PER_PX;
 imageline($im,$x,$y,$x+$t,$y,$color); # 繪製水平線
 $x+=$t;
 }
 
 fclose($file);
 
 imagepng($im);
 imagedestroy($im);
 ?>
 
 
              
                | 
|
        
                
          |   一派掌門 二十級 | 
              1.EEPROM倒數第二位在該版本程序中恆為0x3d,這是版本識別碼,將來的版本會改變這個識別碼2.把「18b20單總線信號.bin」燒寫回EEPROM,然後打開單片機,就會發現第142、143、144等多個連續的id號是相同的。通常如果電平變化得太快,小於1us,那麼就會出現這種情況。所以程序中port_states數組還是很有必要設置的。
 3.默認在php繪圖的時候每像素表示100us,如果繪出的圖形太密,比如像「18b20單總線信號_100ms.png」那樣,那麼請減小US_PER_PX常量的值,比如設為1us,出來的效果就是「18b20單總線信號_1ms.png」
 | 
|
        
                
          |   一派掌門 二十級 | 
              回覆:12樓簡單說一下這個php程序的使用方法,在自己的電腦上安裝php開發環境(相關資料見php吧),然後把這個php文件放進去
 然後用ISP線連接電路板和電腦,打開AVR Fighter,讀取EEPROM並保存為bin文件,把這個bin文件放入php文件所在的文件夾,把這個bin文件命名為「錯誤的紅外發射.bin」,當然文件名可以隨便取,別忘了改相應的php程序的第7行的那個FILENAME常量的值。
 用瀏覽器訪問這個php頁面,就可以得到電平圖像了。
 
 | 
|
        
                
          |   一派掌門 二十級 | 
              以下為我通過這個工具得到的一些波形圖: 18b20單總線信號_1ms   | 
|
        
                
          |   一派掌門 二十級 | 
              回覆:15樓 這個圖像太寬了,沒法直接看,請右鍵另存到本地再看             | 
|
        
                
          |   一派掌門 二十級 | 
              18b20單總線信號_5ms:   | 
|
        
                
          |   一派掌門 二十級 | 
              18b20單總線信號_100ms:   | 
|
        
                
          |   一派掌門 二十級 | 
              某51單片機程序P0某口的波形2:   | 
|
        
                
          |   一派掌門 二十級 | 
              以下就是一個典型的遙控器發射的波形:  可以用畫圖軟件量出來,引導碼是9ms高電平4.5ms低電平,然後一堆用戶碼和鍵碼,最後用引導碼和短碼表示重複按鍵             | 
|
        
                
          |   一派掌門 二十級 | 
              頓時感覺自己太觸了這個程序我去年國慶節(2013年10月3日)就做出來了,到現在才正式發佈,都怪我沒時間
 我估計這個電路板一定能給各位單片機開發者帶來極大的方便,因為市面上市售的示波器實在是太貴了,幾千,,用AVR單片機自己做一個正合適。我計算了一下我做這個的成本也就100塊錢上下
 
 | 
|
        
                
          |   一派掌門 二十級 | 
              回覆:21樓 我這個東西缺點就是掃描時間較短,而且還只能檢測高低電平,不能檢查準確的電壓。 下次發佈2.0版本的時候我最好弄一個12864液晶來顯示電壓的變化,顯示一個xy坐標系和曲線             | 
|
        
                
          |   一派掌門 二十級 | 
              紅外遙控數據示例2_連按某個鍵:   | 
|
        
                
          |   一派掌門 二十級 | 
              自製遙控器信號1:  很顯然這個自製的遙控器發射的波形很失敗             | 
|
        
                
          |   一派掌門 二十級 | 
              波形圖可以用windows自帶的畫圖軟件打開,可以量波形的長度。比如如果php程序中設置了:define("US_PER_PX",50); //每像素表示多少微秒
 那麼每個像素就是50us的時間
 你用畫圖軟件量某個高電平的寬度為14像素,那麼這個高電平持續時間就為0.7毫秒
 真的是非常方便!
 
 | 
|
        
                
          |   一派掌門 二十級 | 
              我爭取下一個版本再弄一個按鍵,可以使用AVR單片機中的各種定時器分頻器,這樣就可以測量出更長的電平了。
 | 
|
        
                
          |   一派掌門 二十級 | 
              下一個版本我還將再弄一些EEPROM,也可以增加測量的長度,不再是240個單位了。而是幾千甚至上萬個單位
 | 
|
        
                
          |   一派掌門 二十級 |  | 
|
        
                
          |   一派掌門 二十級 |  | 
|
        
                
          |   一派掌門 二十級 | 
              我再簡單講解一下這個電路板的功能。 A處的那個黑色的接口就是ISP接口,用來把EEPROM的內容導入到電腦,以便於生成電平圖像。   | 
|