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;
}
}