当前位置: 首页 > article >正文

51单片机.之ADC数字模拟转换

在这里插入图片描述

1、数字转模拟电路,输出波形,示波器采集来显示波形
单片机通过i2c给,模数转换器,写入数字信号,定时器1s扫描按键的切换
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
1、key.c
切换波形


#include <reg52.h>

sbit KEY_IN_1  = P2^4;
sbit KEY_IN_2  = P2^5;
sbit KEY_IN_3  = P2^6;
sbit KEY_IN_4  = P2^7;
sbit KEY_OUT_1 = P2^3;
sbit KEY_OUT_2 = P2^2;
sbit KEY_OUT_3 = P2^1;
sbit KEY_OUT_4 = P2^0;

unsigned char code KeyCodeMap[4][4] = { //矩阵按键编号到标准键盘键码的映射表
    { '1',  '2',  '3', 0x26 }, //数字键1、数字键2、数字键3、向上键
    { '4',  '5',  '6', 0x25 }, //数字键4、数字键5、数字键6、向左键
    { '7',  '8',  '9', 0x28 }, //数字键7、数字键8、数字键9、向下键
    { '0', 0x1B, 0x0D, 0x27 }  //数字键0、ESC键、  回车键、 向右键
};
unsigned char pdata KeySta[4][4] = {  //全部矩阵按键的当前状态
    {1, 1, 1, 1},  {1, 1, 1, 1},  {1, 1, 1, 1},  {1, 1, 1, 1}
};

extern void SwitchWave();
extern void KeyAction(unsigned char keycode);

/* 按键驱动函数,检测按键动作,调度相应动作函数,需在主循环中调用 */
void KeyDriver()
{
    unsigned char i, j;
    static unsigned char pdata backup[4][4] = {  //按键值备份,保存前一次的值
        {1, 1, 1, 1},  {1, 1, 1, 1},  {1, 1, 1, 1},  {1, 1, 1, 1}
    };
    
    for (i=0; i<4; i++)  //循环检测4*4的矩阵按键
    {
        for (j=0; j<4; j++)
        {
            if (backup[i][j] != KeySta[i][j])    //检测按键动作
            {
                if (backup[i][j] != 0)           //按键按下时执行动作
                {
                    KeyAction(KeyCodeMap[i][j]); //调用按键动作函数
                }
                backup[i][j] = KeySta[i][j];     //刷新前一次的备份值
            }
        }
    }
}
/* 按键扫描函数,需在定时中断中调用,推荐调用间隔1ms */
void KeyScan()
{
    unsigned char i;
    static unsigned char keyout = 0;   //矩阵按键扫描输出索引
    static unsigned char keybuf[4][4] = {  //矩阵按键扫描缓冲区
        {0xFF, 0xFF, 0xFF, 0xFF},  {0xFF, 0xFF, 0xFF, 0xFF},
        {0xFF, 0xFF, 0xFF, 0xFF},  {0xFF, 0xFF, 0xFF, 0xFF}
    };

    //将一行的4个按键值移入缓冲区
    keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KEY_IN_1;
    keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KEY_IN_2;
    keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KEY_IN_3;
    keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KEY_IN_4;
    //消抖后更新按键状态
    for (i=0; i<4; i++)  //每行4个按键,所以循环4次
    {
        if ((keybuf[keyout][i] & 0x0F) == 0x00)
        {   //连续4次扫描值为0,即4*4ms内都是按下状态时,可认为按键已稳定的按下
            KeySta[keyout][i] = 0;
        }
        else if ((keybuf[keyout][i] & 0x0F) == 0x0F)
        {   //连续4次扫描值为1,即4*4ms内都是弹起状态时,可认为按键已稳定的弹起
            KeySta[keyout][i] = 1;
        }
    }
    //执行下一次的扫描输出
    keyout++;         //输出索引递增
    keyout &= 0x03;   //索引值加到4即归零
    switch (keyout)   //根据索引,释放当前输出引脚,拉低下次的输出引脚
    {
        case 0: KEY_OUT_4 = 1; KEY_OUT_1 = 0; break;
        case 1: KEY_OUT_1 = 1; KEY_OUT_2 = 0; break;
        case 2: KEY_OUT_2 = 1; KEY_OUT_3 = 0; break;
        case 3: KEY_OUT_3 = 1; KEY_OUT_4 = 0; break;
        default: break;
    }
}

2、i2c.c

#include <reg52.h>
#include <intrins.h>

#define I2CDelay()  {_nop_();_nop_();_nop_();_nop_();}

sbit I2C_SCL = P3^7;
sbit I2C_SDA = P3^6;

void I2CStart()  //产生总线起始信号
{
    I2C_SDA = 1; //首先确保SDA、SCL都是高电平
    I2C_SCL = 1;
    I2CDelay();
    I2C_SDA = 0; //先拉低SDA
    I2CDelay();
    I2C_SCL = 0; //再拉低SCL
}
void I2CStop()   //产生总线停止信号
{
    I2C_SCL = 0; //首先确保SDA、SCL都是低电平
    I2C_SDA = 0;
    I2CDelay();
    I2C_SCL = 1; //先拉高SCL
    I2CDelay();
    I2C_SDA = 1; //再拉高SDA
    I2CDelay();
}
bit I2CWrite(unsigned char dat) //I2C总线写操作,待写入字节dat,返回值为应答状态
{
    bit ack;  //用于暂存应答位的值
    unsigned char mask;  //用于探测字节内某一位值的掩码变量

    for (mask=0x80; mask!=0; mask>>=1) //从高位到低位依次进行
    {
        if ((mask&dat) == 0)  //该位的值输出到SDA上
            I2C_SDA = 0;
        else
            I2C_SDA = 1;
        I2CDelay();
        I2C_SCL = 1;          //拉高SCL
        I2CDelay();
        I2C_SCL = 0;          //再拉低SCL,完成一个位周期
    }
    I2C_SDA = 1;   //8位数据发送完后,主机释放SDA,以检测从机应答
    I2CDelay();
    I2C_SCL = 1;   //拉高SCL
    ack = I2C_SDA; //读取此时的SDA值,即为从机的应答值
    I2CDelay();
    I2C_SCL = 0;   //再拉低SCL完成应答位,并保持住总线

    return (~ack); //应答值取反以符合通常的逻辑:0=不存在或忙或写入失败,1=存在且空闲或写入成功
}
unsigned char I2CReadNAK() //I2C总线读操作,并发送非应答信号,返回值为读到的字节
{
    unsigned char mask;
    unsigned char dat;

    I2C_SDA = 1;  //首先确保主机释放SDA
    for (mask=0x80; mask!=0; mask>>=1) //从高位到低位依次进行
    {
        I2CDelay();
        I2C_SCL = 1;      //拉高SCL
        if(I2C_SDA == 0)  //读取SDA的值
            dat &= ~mask; //为0时,dat中对应位清零
        else
            dat |= mask;  //为1时,dat中对应位置1
        I2CDelay();
        I2C_SCL = 0;      //再拉低SCL,以使从机发送出下一位
    }
    I2C_SDA = 1;   //8位数据发送完后,拉高SDA,发送非应答信号
    I2CDelay();
    I2C_SCL = 1;   //拉高SCL
    I2CDelay();
    I2C_SCL = 0;   //再拉低SCL完成非应答位,并保持住总线

    return dat;
}
unsigned char I2CReadACK() //I2C总线读操作,并发送应答信号,返回值为读到的字节
{
    unsigned char mask;
    unsigned char dat;

    I2C_SDA = 1;  //首先确保主机释放SDA
    for (mask=0x80; mask!=0; mask>>=1) //从高位到低位依次进行
    {
        I2CDelay();
        I2C_SCL = 1;      //拉高SCL
        if(I2C_SDA == 0)  //读取SDA的值
            dat &= ~mask; //为0时,dat中对应位清零
        else
            dat |= mask;  //为1时,dat中对应位置1
        I2CDelay();
        I2C_SCL = 0;      //再拉低SCL,以使从机发送出下一位
    }
    I2C_SDA = 0;   //8位数据发送完后,拉低SDA,发送应答信号
    I2CDelay();
    I2C_SCL = 1;   //拉高SCL
    I2CDelay();
    I2C_SCL = 0;   //再拉低SCL完成应答位,并保持住总线

    return dat;
}

3、main.c

#include <reg52.h>

unsigned char code SinWave[] = {  //正弦波波表
    127, 152, 176, 198, 217, 233, 245, 252,
    255, 252, 245, 233, 217, 198, 176, 152,
    127, 102,  78,  56,  37,  21,   9,   2,
      0,   2,   9,  21,  37,  56,  78, 102,
};
unsigned char code TriWave[] = {  //三角波波表
      0,  16,  32,  48,  64,  80,  96, 112,
    128, 144, 160, 176, 192, 208, 224, 240,
    255, 240, 224, 208, 192, 176, 160, 144,
    128, 112,  96,  80,  64,  48,  32,  16,
};
unsigned char code SawWave[] = {  //锯齿波表
      0,   8,  16,  24,  32,  40,  48,  56,
     64,  72,  80,  88,  96, 104, 112, 120,
    128, 136, 144, 152, 160, 168, 176, 184,
    192, 200, 208, 216, 224, 232, 240, 248,
};
unsigned char code *pWave;  //波表指针
unsigned char T0RH = 0;  //T0重载值的高字节
unsigned char T0RL = 0;  //T0重载值的低字节
unsigned char T1RH = 1;  //T1重载值的高字节
unsigned char T1RL = 1;  //T1重载值的低字节

void ConfigTimer0(unsigned int ms);
void SetWaveFreq(unsigned char freq);
extern void KeyScan();
extern void KeyDriver();
extern void I2CStart();
extern void I2CStop();
extern bit I2CWrite(unsigned char dat);

void main()
{    
    EA = 1;             //开总中断
    ConfigTimer0(1);  //配置T0定时1ms
    pWave = SinWave;  //默认正弦波
    SetWaveFreq(10);  //默认频率10Hz
    
    while (1)
    {
        KeyDriver();  //调用按键驱动
    }
}
void KeyAction(unsigned char keycode)
{	
	static unsigned char i = 0;

	if(keycode == 0x26)
	{
		if(i==0)
		{
			i = 1;
			pWave = TriWave;
		}
		else if(i==1)
		{
			i = 2;
			pWave = SawWave;
		}
		else
		{
			i = 0;
			pWave = SinWave;
		}
	}
}

/* 设置DAC输出值,val-设定值 */
void SetDACOut(unsigned char val)
{
    I2CStart();
    if (!I2CWrite(0x48<<1)) //寻址PCF8591,如未应答,则停止操作并返回
    {
        I2CStop();
        return;
    }
    I2CWrite(0x40);         //写入控制字节
    I2CWrite(val);          //写入DA值  
    I2CStop();
}
void SetWaveFreq(unsigned char freq)
{
	unsigned long tmp;

	tmp = (11059200/12)/(freq * 32);
	tmp = 65536 - tmp;
	tmp = tmp + 33;
	T1RH = (unsigned char)(tmp >> 8);
	T1RL = (unsigned char)tmp;
	TMOD &= 0x0F;
	TMOD |= 0X10;
	TH1 = T1RH;
	TL1 = T1RL;
	ET1 = 1;
	PT1 = 1;
	TR1 = 1;
}
/* 配置并启动T0,ms-T0定时时间 */
void ConfigTimer0(unsigned int ms)
{
    unsigned long tmp;  //临时变量
    
    tmp = 11059200 / 12;       //定时器计数频率
    tmp = (tmp * ms) / 1000;  //计算所需的计数值
    tmp = 65536 - tmp;         //计算定时器重载值
    tmp = tmp + 28;            //补偿中断响应延时造成的误差
    T0RH = (unsigned char)(tmp>>8);  //定时器重载值拆分为高低字节
    T0RL = (unsigned char)tmp;
    TMOD &= 0xF0;   //清零T0的控制位
    TMOD |= 0x01;   //配置T0为模式1
    TH0 = T0RH;     //加载T0重载值
    TL0 = T0RL;
    ET0 = 1;        //使能T0中断
    TR0 = 1;        //启动T0
}
/* T0中断服务函数,执行按键扫描 */
void InterruptTimer0() interrupt 1
{
    TH0 = T0RH;  //重新加载重载值
    TL0 = T0RL;
    KeyScan();   //按键扫描
}
/* T1中断服务函数,执行波形输出 */
void InterruptTimer1() interrupt 3
{
    static unsigned char i = 0;
    
    TH1 = T1RH;  //重新加载重载值
    TL1 = T1RL;
    //循环输出波表中的数据
    SetDACOut(pWave[i]);
    i++;
    if (i >= 32)
    {
        i = 0;
    }
}

在这里插入图片描述
在这里插入图片描述
2、模数转换,将电压显示到数码管
通过i2c读取转换结果,计算换成电压值,显示到数码管

/*
*******************************************************************************
*                     《手把手教你学51单片机(C语言版)》
*                    配套 KST-51 单片机开发板 示例源代码
*
*         (c) 版权所有 2014 金沙滩工作室/清华大学出版社 保留所有权利
*                 获取更多资料请访问:http://www.kingst.org
*
* 文件名:main.c
* 描  述:第17章 作业题2 将模拟输入通道0、1的电压值显示到数码管上
* 版本号:v1.0.0
* 备  注:通道0电压值显示在左侧,通道1电压值显示在右侧,中间隔两位不显示。
*******************************************************************************
*/

#include <reg52.h>

sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;

bit flag300ms = 1;       //300ms定时标志
unsigned char T0RH = 0;  //T0重载值的高字节
unsigned char T0RL = 0;  //T0重载值的低字节
unsigned char code LedChar[] = {  //数码管显示字符转换表
    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[6] = {  //数码管显示缓冲区,初值0xFF确保启动时都不亮
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};

void ConfigTimer0(unsigned int ms);
unsigned char GetADCValue(unsigned char chn);
extern void I2CStart();
extern void I2CStop();
extern unsigned char I2CReadACK();
extern unsigned char I2CReadNAK();
extern bit I2CWrite(unsigned char dat);
extern void InitLcd1602();
extern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);

void main()
{
    unsigned char val;
    
    EA = 1;           //开总中断
    ENLED = 0;        //使能U3,选择控制数码管
    ADDR3 = 1;        //因为需要动态改变ADDR0-2的值,所以0-2不需要初始化了
    ConfigTimer0(2);  //配置T0定时2ms
    
    while (1)
    {
        if (flag300ms)
        {
            flag300ms = 0;
            //显示通道0的电压
            val = GetADCValue(0);   //获取ADC通道0的转换值
            val = (val*25) / 255;   //电压值=转换结果*2.5V/255,式中的25隐含了一位十进制小数
            LedBuff[5] = LedChar[val/10] & 0x7F;  //通道0的整数位显示到数码管第5位,并点亮第5位的小数点
            LedBuff[4] = LedChar[val%10];         //通道0的小数位显示到数码管第4位
             //显示通道1的电压
            val = GetADCValue(1);   //获取ADC通道1的转换值
            val = (val*25) / 255;   //电压值=转换结果*2.5V/255,式中的25隐含了一位十进制小数
            LedBuff[1] = LedChar[val/10] & 0x7F;  //通道1的整数位显示到数码管第1位,并点亮第1位的小数点
            LedBuff[0] = LedChar[val%10];         //通道1的小数位显示到数码管第0位
        }
    }
}
/* 读取当前的ADC转换值,chn-ADC通道号0~3 */
unsigned char GetADCValue(unsigned char chn)
{
    unsigned char val;
    
    I2CStart();
    if (!I2CWrite(0x48<<1))  //寻址PCF8591,如未应答,则停止操作并返回0
    {
        I2CStop();
        return 0;
    }
    I2CWrite(0x40|chn);        //写入控制字节,选择转换通道
    I2CStart();
    I2CWrite((0x48<<1)|0x01);  //寻址PCF8591,指定后续为读操作    
    I2CReadACK();              //先空读一个字节,提供采样转换时间
    val = I2CReadNAK();        //读取刚刚转换完的值
    I2CStop();
    
    return val;
}
/* 配置并启动T0,ms-T0定时时间 */
void ConfigTimer0(unsigned int ms)
{
    unsigned long tmp;  //临时变量
    
    tmp = 11059200 / 12;      //定时器计数频率
    tmp = (tmp * ms) / 1000;  //计算所需的计数值
    tmp = 65536 - tmp;        //计算定时器重载值
    tmp = tmp + 12;           //补偿中断响应延时造成的误差
    T0RH = (unsigned char)(tmp>>8);  //定时器重载值拆分为高低字节
    T0RL = (unsigned char)tmp;
    TMOD &= 0xF0;   //清零T0的控制位
    TMOD |= 0x01;   //配置T0为模式1
    TH0 = T0RH;     //加载T0重载值
    TL0 = T0RL;
    ET0 = 1;        //使能T0中断
    TR0 = 1;        //启动T0
}
/* T0中断服务函数,执行数码管动态显示、300ms定时 */
void InterruptTimer0() interrupt 1
{
    static unsigned char i = 0;  //动态扫描的索引
    static unsigned char tmr300ms = 0;  //300ms软件定时计数器
    
    TH0 = T0RH;  //重新加载重载值
    TL0 = T0RL;
    tmr300ms++;
    if (tmr300ms >= 150)  //定时300ms
    {
        tmr300ms = 0;
        flag300ms = 1;
    }
    //以下代码完成数码管动态扫描刷新
    P0 = 0xFF;   //显示消隐
    switch (i)
    {
        case 0: ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=LedBuff[0]; break;
        case 1: ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=LedBuff[1]; break;
        case 2: ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=LedBuff[2]; break;
        case 3: ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=LedBuff[3]; break;
        case 4: ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=LedBuff[4]; break;
        case 5: ADDR2=1; ADDR1=0; ADDR0=1; i=0; P0=LedBuff[5]; break;
        default: break;
    }
}


http://www.kler.cn/news/285111.html

相关文章:

  • 如何使用vcftools提取特定的染色体
  • vim 修改文件
  • 常见协议工作原理 https ARP ICMP DHCP PING
  • 华为手机数据丢失如何恢复?
  • 具身智能(Embodied Intelligence)概述
  • 【Redis】哨兵(Sentinel)
  • 1098 Insertion or Heap Sort
  • 在Docker中使用环境变量改变SpringBoot程序配置
  • 在React中使用TypeScript和Material-UI v5时,要为单个.tsx文件创建一个局部作用域的.scss文件如何做? 另外主题如何获取呢?
  • 【Linux修行路】进程通信——共享内存
  • erlang学习:用OTP构建系统1
  • Java算法之堆排序(Heap Sort)
  • 【软考】路由器
  • C++ 移动构造函数为什么设置noexcept?
  • python网络爬虫(零)——认识网页结构
  • Linux主机网络参数的设置—IP地址的作用和类型
  • LabVIEW呼吸机测试系统开发
  • sqli-labs靶场通关攻略(五十一到五十六关)
  • 【c++】日期类相关实践:计算日期到天数转换、日期差值
  • 如何打造免费体育馆场地预约系统?php vue技术实现,简易操作指南
  • Veeam Data Platform 12.2 发布下载,新增功能概览
  • K8S(Kubernates) 知识目录
  • Redis缓存的一些案例
  • 带权重的随机算法
  • 机械学习—零基础学习日志(概率论总笔记1)
  • DRF——serializer中获取嵌套评论
  • 鸿蒙HarmonyOS之使用preferences首选项保存获取数据
  • 1、Java简介+DOS命令+java的编译运行(字节码/机器码、JRE/JVM/JDK的区别)+一个简单的Java程序
  • Linux 数据结构 树知识
  • shell小白学习记录