作者共發了44篇帖子。 內容轉換:不轉換▼
 
點擊 回復
2481 44
【单片机程序】AVRMega8制作的简易示波器(只能检测高低电平)
一派護法 十九級
1樓 發表于:2014-9-30 23:17


单片机端口定义:


一派護法 十九級
2樓 發表于:2014-9-30 23:19






一派護法 十九級
3樓 發表于:2014-9-30 23:20



一派護法 十九級
4樓 發表于:2014-9-30 23:21
注意:本程序按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)
一派護法 十九級
5樓 發表于:2014-9-30 23:22
【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;
}
一派護法 十九級
6樓 發表于:2014-9-30 23:24
【eeprom.h】
void EEPROM_Write(unsigned int address, unsigned char Data);
void EEPROM_Read(unsigned int address, unsigned char* Data);
一派護法 十九級
7樓 發表于:2014-9-30 23:25
【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;
}
一派護法 十九級
8樓 發表于:2014-9-30 23:25
【HC595.h】
void HC595_SerIn(unsigned char Data);
void HC595_ParOut(void);
一派護法 十九級
9樓 發表于:2014-9-30 23:26
【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;
}
一派護法 十九級
10樓 發表于:2014-9-30 23:28
使用时按下K4键开始捕捉或停止捕捉,
完毕后用ISP口连接电路板和电脑,打开AVR Fighter,通过ISP口读取EEPROM数据并保存为bin文件,再用php程序就能生成一张波形图了!
一派護法 十九級
11樓 發表于:2014-9-30 23:30
以下就是我获得的我的遥控器发射的电平,我做了一点修改而已
一派護法 十九級
12樓 發表于:2014-9-30 23:34
以下为根据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);
?>
一派護法 十九級
13樓 發表于:2014-9-30 23:34
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”
一派護法 十九級
14樓 發表于:2014-9-30 23:38
回复:12楼
简单说一下这个php程序的使用方法,在自己的电脑上安装php开发环境(相关资料见php吧),然后把这个php文件放进去
然后用ISP线连接电路板和电脑,打开AVR Fighter,读取EEPROM并保存为bin文件,把这个bin文件放入php文件所在的文件夹,把这个bin文件命名为“错误的红外发射.bin”,当然文件名可以随便取,别忘了改相应的php程序的第7行的那个FILENAME常量的值。
用浏览器访问这个php页面,就可以得到电平图像了。
一派護法 十九級
15樓 發表于:2014-9-30 23:40
以下为我通过这个工具得到的一些波形图:
18b20单总线信号_1ms

一派護法 十九級
16樓 發表于:2014-9-30 23:41

回复:15楼

这个图像太宽了,没法直接看,请右键另存到本地再看
一派護法 十九級
17樓 發表于:2014-9-30 23:42
18b20单总线信号_5ms:
一派護法 十九級
18樓 發表于:2014-9-30 23:42
18b20单总线信号_100ms:
一派護法 十九級
19樓 發表于:2014-9-30 23:42
某51单片机程序P0某口的波形2:
一派護法 十九級
20樓 發表于:2014-9-30 23:47
以下就是一个典型的遥控器发射的波形:

可以用画图软件量出来,引导码是9ms高电平4.5ms低电平,然后一堆用户码和键码,最后用引导码和短码表示重复按键
一派護法 十九級
21樓 發表于:2014-9-30 23:50
顿时感觉自己太触了
这个程序我去年国庆节(2013年10月3日)就做出来了,到现在才正式发布,都怪我没时间
我估计这个电路板一定能给各位单片机开发者带来极大的方便,因为市面上市售的示波器实在是太贵了,几千,,用AVR单片机自己做一个正合适。我计算了一下我做这个的成本也就100块钱上下
一派護法 十九級
22樓 發表于:2014-9-30 23:52

回复:21楼

我这个东西缺点就是扫描时间较短,而且还只能检测高低电平,不能检查准确的电压。
下次发布2.0版本的时候我最好弄一个12864液晶来显示电压的变化,显示一个xy坐标系和曲线
一派護法 十九級
23樓 發表于:2014-9-30 23:53
红外遥控数据示例2_连按某个键:
一派護法 十九級
24樓 發表于:2014-9-30 23:55
自制遥控器信号1:

很显然这个自制的遥控器发射的波形很失败
一派護法 十九級
25樓 發表于:2014-9-30 23:58
波形图可以用windows自带的画图软件打开,可以量波形的长度。
比如如果php程序中设置了:define("US_PER_PX",50); //每像素表示多少微秒
那么每个像素就是50us的时间
你用画图软件量某个高电平的宽度为14像素,那么这个高电平持续时间就为0.7毫秒
真的是非常方便!
一派護法 十九級
26樓 發表于:2014-9-30 23:59
我争取下一个版本再弄一个按键,可以使用AVR单片机中的各种定时器分频器,这样就可以测量出更长的电平了。
一派護法 十九級
27樓 發表于:2014-10-1 00:00
下一个版本我还将再弄一些EEPROM,也可以增加测量的长度,不再是240个单位了。而是几千甚至上万个单位
一派護法 十九級
28樓 發表于:2014-10-1 00:00

回复:27楼

比如24C08芯片
一派護法 十九級
29樓 發表于:2014-10-1 00:02
一派護法 十九級
30樓 發表于:2014-10-1 00:05
我再简单讲解一下这个电路板的功能。
A处的那个黑色的接口就是ISP接口,用来把EEPROM的内容导入到电脑,以便于生成电平图像。

回復帖子

內容:
用戶名: 您目前是匿名發表
驗證碼:
(快捷鍵:Ctrl+Enter)
 

本帖信息

點擊數:2481 回複數:44
評論數: ?
作者:巨大八爪鱼
最後回復:巨大八爪鱼
最後回復時間:2014-11-16 00:43
精品區:優秀程序
 
©2010-2024 Arslanbar Ver2.0
除非另有聲明,本站採用創用CC姓名標示-相同方式分享 3.0 Unported許可協議進行許可。