C51单片机控制JQ6500语音模块,不知道如何编写代码在仅使用单片机的情况下使JQ6500播报指定语音而不是通过按键
或者能不能对这段代码的语言播报部分仔细讲解运行原理,有不足之处请指出
/*语音芯片地址与内容对照*/
// 0x00 -- 零
// 0x01 -- 一
// 0x02 -- 二
// 0x03 -- 三
// 0x04 -- 四
// 0x05 -- 五
// 0x06 -- 六
// 0x07 -- 七
// 0x08 -- 八
// 0x09 -- 九
// 0x0A -- 十
// 0x0B -- 百
// 0x0C -- 点
// 0x0D -- 厘米
// 0x0E -- 米
// 0x0F -- 欢迎使用
// 0x10 -- 欢迎使用超声波测距系统
// 0x11 -- 注意安全
#include <reg52.h> //包含头文件
#include <intrins.h>
sfr ISP_DATA = 0xe2; //数据寄存器
sfr ISP_ADDRH = 0xe3; //地址寄存器高八位
sfr ISP_ADDRL = 0xe4; //地址寄存器低八位
sfr ISP_CMD = 0xe5; //命令寄存器
sfr ISP_TRIG = 0xe6; //命令触发寄存器
sfr ISP_CONTR = 0xe7; //控制寄存器
sbit RS = P1^3; //1602液晶的RS管脚
sbit RW = P1^4; //1602液晶的RW管脚
sbit EN = P1^5; //1602液晶的EN管脚
sbit Trig = P2^6; //超声波模块的Trig管脚
sbit Echo = P2^7; //超声波模块的Echo管脚
sbit KeySet = P3^2; //“设置”按键的管脚
sbit KeyDown = P3^3; //“减”按键的管脚
sbit KeyUp = P3^4; //“加”按键的管脚
sbit Led = P1^2; //LED报警灯的管脚
sbit Buzz = P2^0; //蜂鸣器的管脚
sbit DQ = P1^0; //温度传感器的引脚定义
sbit SDA = P3^0; // JQ6500 的接收管脚
sbit BUSY = P3^1; // JQ6500 的发送管脚
unsigned int gAlarm; //报警距离变量
float gSpeed; //保存超声波的速度值
/*********************************************************/
// 单片机内部EEPROM不使能
/*********************************************************/
void ISP_Disable()
{
ISP_CONTR = 0; //命令寄存器赋值为0
ISP_ADDRH = 0; //地址寄存器高八位赋值为0
ISP_ADDRL = 0; //地址寄存器低八位赋值为0
}
/*********************************************************/
// 从单片机内部EEPROM读一个字节
/*********************************************************/
unsigned char ReadE2PROM(unsigned int addr)
{
ISP_DATA = 0x00; //数据寄存器清零
ISP_CONTR = 0x83; //允许改变内部E2PROM,存取数据速度为5MHz
ISP_CMD = 0x01; //读命令
ISP_ADDRH = (unsigned char)(addr >> 8); //输入高8位地址
ISP_ADDRL = (unsigned char)(addr & 0xff); //输入低8位地址
ISP_TRIG = 0x46; //先向命令触发寄存器写入0x46
ISP_TRIG = 0xb9; //再向命令触发寄存器写入0xb9,完成触发
_nop_(); //延时大约1us
ISP_Disable(); //单片机内部EEPROM不使能
return ISP_DATA; //返回读的数据
}
/*********************************************************/
// 从单片机内部EEPROM写一个字节
/*********************************************************/
void WriteE2PROM(unsigned char dat, unsigned int addr)
{
ISP_CONTR = 0x83; //允许改变内部E2PROM,存取数据速度为5MHz
ISP_CMD = 0x02; //写命令
ISP_ADDRH = (unsigned char)(addr >> 8); //输入高8位地址
ISP_ADDRL = (unsigned char)(addr & 0xff); //输入低8位地址
ISP_DATA = dat; //输入要写的数据
ISP_TRIG = 0x46; //先向命令触发寄存器写入0x46
ISP_TRIG = 0xb9; //再向命令触发寄存器写入0xb9,完成触发
_nop_(); //延时大约1us
ISP_Disable(); //单片机内部EEPROM不使能
}
/*********************************************************/
// 从单片机内部EEPROM扇区擦除
/*********************************************************/
void SectorErase(unsigned int addr)
{
ISP_CONTR = 0x83; //允许改变内部E2PROM,存取数据速度为5MHz
ISP_CMD = 0x03; //扇区擦除命令
ISP_ADDRH = (unsigned char)(addr >> 8); //输入高8位地址
ISP_ADDRL = (unsigned char)(addr & 0xff); //输入低8位地址
ISP_TRIG = 0x46; //先向命令触发寄存器写入0x46
ISP_TRIG = 0xb9; //再向命令触发寄存器写入0xb9,完成触发
_nop_(); //延时大约1us
ISP_Disable(); //单片机内部EEPROM不使能
}
/*********************************************************/
// 延时X*ms函数
/*********************************************************/
void DelayMs(unsigned int ms)
{
unsigned int i, j; //定义两个无符号整形变量i,j
for(i=0; i<ms; i++)
for(j=0; j<112; j++);
}
/*********************************************************/
// 读液晶状态函数
/*********************************************************/
void ReadLcdSta()
{
unsigned char sta; //定义无符号char型变量,用于保存液晶状态
P0 = 0xff; //P0全部输入1,为读状态做准备
RS = 0; //选择命令
RW = 1; //设置为读
do{
EN = 1; //使能LCD1602
sta = P0; //读命令
EN = 0; //不使能LCD1602
}while(sta & 0x80); //判断读的命令最高位是否为0
}
/*********************************************************/
// 液晶写命令函数
/*********************************************************/
void WriteLcdCmd(unsigned char cmd)
{
ReadLcdSta(); //读液晶状态,判断液晶是否准备好
RS = 0; //选择命令
RW = 0; //设置为写
P0 = cmd; //输入要写的命令
EN = 1; //产生一个高脉冲
EN = 0;
}
/*********************************************************/
// 液晶写数据函数
/*********************************************************/
void WriteLcdData(unsigned char dat)
{
ReadLcdSta(); //读液晶状态,判断液晶是否准备好
RS = 1; //选择数据
RW = 0; //设置为写
P0 = dat; //输入要写的数据
EN = 1; //产生一个高脉冲
EN = 0;
}
/*********************************************************/
// 液晶坐标设置函数
/*********************************************************/
void SetLcdCursor(unsigned char line, unsigned char column)
{
if(line == 0) //判断是否为第一行
WriteLcdCmd(column + 0x80); //若是,写入第一行列坐标
if(line == 1) //判断是否为第二行
WriteLcdCmd(column + 0x80 + 0x40); //若是,写入第二行列坐标
}
/*********************************************************/
// 液晶显示字符串函数
/*********************************************************/
void ShowLcdStr(unsigned char *str)
{
while(*str != '\0') //当没有指向结束符
WriteLcdData(*str++); //字符指针加1
}
/*********************************************************/
// 液晶初始化函数
/*********************************************************/
void LcdInit()
{
WriteLcdCmd(0x38); //16*2显示,5*7点阵,8位数据口
WriteLcdCmd(0x06); //地址加1,当写入数据后光标右移
WriteLcdCmd(0x0c); //开显示,不显示光标
WriteLcdCmd(0x01); //清屏
}
/*********************************************************/
// 液晶输出数字
/*********************************************************/
void ShowLcdNum(unsigned int num)
{
WriteLcdData(num/100+0x30); //分离百位
WriteLcdData(num%100/10+0x30); //分离十位
WriteLcdData(num%10+0x30); //分离个位
}
/*********************************************************/
// 延时X*10us函数
/*********************************************************/
void DelayX10us(unsigned char t)
{
do{
_nop_(); //延时大约1us
_nop_(); //延时大约1us
_nop_(); //延时大约1us
_nop_(); //延时大约1us
_nop_(); //延时大约1us
_nop_(); //延时大约1us
_nop_(); //延时大约1us
_nop_(); //延时大约1us
}while(--t); //t先自减,然后判断t的值是否大于0
}
/*********************************************************/
// 复位18B20
/*********************************************************/
bit Reset18B20()
{
bit ack; //定义一个bit型变量
DQ = 0; //数据引脚拉低
DelayX10us(50); //延时500us
DQ = 1; //数据引脚拉高
DelayX10us(6); //延时60us
ack = DQ; //读取复位值
while(!DQ); //判断是否为高电平
return ack; //返回复位值
}
/*********************************************************/
// 向18B20写数据或命令
/*********************************************************/
void Write18B20(unsigned char dat)
{
unsigned char mask; //定义一个mask变量
for(mask=0x01; mask!=0; mask<<=1) //循环8次,从低到高依次写入
{
DQ = 0; //初始电平为0
_nop_(); //延时大约为1us
_nop_(); //延时大约为1us
if((dat & mask) == 0) //判断写入的一位是否为0
DQ = 0; //为0,数据位为0
else
DQ = 1; //否则,数据位为1
DelayX10us(6); //延时60us
DQ = 1; //拉高数据位,为下次写作准备
}
}
/*********************************************************/
// 从18B20读数据或命令
/*********************************************************/
unsigned char Read18B20()
{
unsigned char mask; //定义一个mask变量
unsigned char dat; //定义一个dat变量,用于保存读到的数据
for(mask=0x01; mask!=0; mask<<=1) //循环8次,从低到高依次读出
{
DQ = 0; //初始电平为0
_nop_(); //延时大约为1us
_nop_(); //延时大约为1us
DQ = 1; //数据电平拉高
_nop_(); //延时大约为1us
_nop_(); //延时大约为1us
if(DQ == 0) //判读数据引脚是否为0
dat &= ~mask; //若为0,dat按位与上0xfe,读出0
else
dat |= mask; //否则,dat按位或上0x01,读出1
DelayX10us(6); //延时60us
}
return dat; //返回读出数据dat
}
/*********************************************************/
// 获取18B20温度数据
/*********************************************************/
int Get18B20Temp()
{
bit ack; //定义bit型变量
int temp; //定义有符号整形变量temp,用于保存温度数据
unsigned char LSB, MSB; //定义两个无符号字符型变量,用于保存从18B20中读出的两个数据
ack = Reset18B20(); //获取18B20复位位
if(ack == 0) //判读是否复位
{
Write18B20(0xcc); //跳过检测ROM
Write18B20(0x44); //启动温度转换指令
}
ack = Reset18B20(); //获取18B20复位位
if(ack == 0) //判读是否复位
{
Write18B20(0xcc); //跳过检测ROM
Write18B20(0xbe); //读取温度指令
LSB = Read18B20(); //先读第8位
MSB = Read18B20(); //再读高8位
temp = ((int)MSB<<8) + LSB; //高8位,第8位整合成一个有符号的整型变量,并把值保存在temp中
temp = temp * 0.0625 * 10; //合成温度值并放大10倍
}
return temp; //返回读到的温度数值
}
/*********************************************************/
// 在液晶上显示温度
/*********************************************************/
void ShowLcdTemp(int temp)
{
if(temp < 0) //如果温度为负数
{
WriteLcdData('-'); //显示负号
temp = 0 - temp; //负数转为正数
}
if(temp >= 1000) //温度值大于等于100℃
{
WriteLcdData(temp/1000 + 0x30); //分离出百位
}
WriteLcdData(temp%1000/100 + 0x30); //分离出十位
WriteLcdData(temp%100/10 + 0x30); //分离出个位
WriteLcdData('.'); //添加小数点
WriteLcdData(temp%10+0x30); //小数后一位
WriteLcdData(0xdf); //摄氏度符号
WriteLcdData('C');
}
/*********************************************************/
// 获取距离函数
/*********************************************************/
unsigned int GetDistance(void)
{
unsigned int ss; //定义整形变量ss,用于保存测得的距离
TH0 = 0; //定时器0计数值高八位清零
TL0 = 0; //定时器0计数值低八位清零
Trig = 1; //触发定时器
DelayMs(1); //延时1ms
Trig = 0; //触发引脚拉低
while(!Echo); //等待超声波模块的返回脉冲
TR0 = 1; //启动定时器,开始计时
while(Echo); //等待超声波模块的返回脉冲结束
TR0 = 0; //停止定时器,停止计时
ss = ((TH0*256 + TL0) * gSpeed) / 2; //距离cm=(时间us * 速度cm/us)/2
return ss; //返回测得的距离值
}
/*********************************************************/
// 按键扫描
/*********************************************************/
void KeyScanf()
{
unsigned char i; //定义i,for循环用
unsigned char dat1, dat2; //dat1和dat2用来保存gAlarm分离出来的百位与十位
if(KeySet == 0) //判断设置键是否按下
{
WriteLcdCmd(0x01); //清屏
DelayMs(10); //消除按键按下的抖动
while(!KeySet); //等待按键释放
DelayMs(10); //消除按键松开的抖动
SetLcdCursor(0, 0); //设置坐标第1行,第1列
ShowLcdStr(" Alarm= cm" ); //显示字符串" Alarm= cm"
SetLcdCursor(0, 9); //设置坐标第1行,第10列
ShowLcdNum(gAlarm); //显示gAlarm
i = 1; //为下面执行while提供条件
while(i) //while循环
{
if(KeyDown == 0) //如果“减”按键被按下
{
if(gAlarm > 2) //判断gAlarm是否大于2
gAlarm--; //若大于自减一
SetLcdCursor(0, 9); //设置坐标第1行,第10列
ShowLcdNum(gAlarm); //显示gAlarm
DelayMs(300); //延时300ms
}
if(KeyUp == 0) //如果“加”按键被按下
{
if(gAlarm < 400) //判断gAlarm是否小于400
gAlarm++; //若小于自加一
SetLcdCursor(0, 9); //设置坐标第1行,第10列
ShowLcdNum(gAlarm); //显示gAlarm
DelayMs(300); //延时300ms
}
if(KeySet == 0) //如果设置按键被按下
{
DelayMs(10); //消除按键按下的抖动
while(!KeySet); //等待按键释放
DelayMs(10); //消除按键松开的抖动
SetLcdCursor(0, 0); //设置坐标第1行,第1列
ShowLcdStr(" Dist= cm "); //显示字符串" Dist= cm "
SetLcdCursor(1, 0); //设置坐标第2行,第1列
ShowLcdStr(" Temp= C "); //显示字符串" Temp= C "
i = 0; //为退出while循环提供条件
}
}
dat1 = gAlarm / 100; //分离出gAlarm百位
dat2 = gAlarm % 100; //分离出gAlarm十位
SectorErase(0x2000); //擦除扇区
WriteE2PROM(dat1, 0x2000); //向0x2000地址里写入dat1
WriteE2PROM(dat2, 0x2001); //向0x2001地址里写入dat2
}
}
/*********************************************************/
// 报警判断
/*********************************************************/
void AlarmJudge(unsigned int ss)
{
unsigned int i; //定义变量i,for循环用
float alr1, alr2, alr3, alr4; //定义浮点型变量,用来保存gAlarm缩小的报警值
alr1 = gAlarm/4.00*1; //gAlarm的1/4倍
alr2 = gAlarm/4.00*2; //gAlarm的1/2倍
alr3 = gAlarm/4.00*3; //gAlarm的3/4倍
alr4 = gAlarm/4.00*4; //gAlarm不缩小
// 报警频率最快
if(ss < alr1) //距离小于gAlarm的1/4倍
{
for(i=0; i<10; i++) //循环10次
{
Led = 0; //LED灯亮
Buzz = 0; //蜂鸣器响
DelayMs(50); //延时50ms
Led = 1; //LED不亮
Buzz = 1; //蜂鸣器不响
DelayMs(50); //延时50ms
KeyScanf(); //按键扫描
}
}
// 报警频率第二快
else if(ss < alr2) //距离小于gAlarm的1/2倍
{
for(i=0; i<5; i++) //循环5次
{
Led = 0; //LED灯亮
Buzz = 0; //蜂鸣器响
DelayMs(100); //延时100ms
Led = 1; //LED不亮
Buzz = 1; //蜂鸣器不响
DelayMs(100); //延时100ms
KeyScanf(); //按键扫描
}
}
// 报警频率第三快
else if(ss < alr3) //距离小于gAlarm的3/4倍
{
for(i=0; i<2; i++) //循环2次
{
Led = 0; //LED灯亮
Buzz = 0; //蜂鸣器响
DelayMs(200); //延时200ms
Led = 1; //LED不亮
Buzz = 1; //蜂鸣器不响
DelayMs(200); //延时200ms
KeyScanf(); //按键扫描
}
}
// 报警频率最慢
else if(ss < alr4) //距离等于gAlarm的值
{
for(i=0; i<2; i++) //循环2次
{
Led = 0; //LED灯亮
Buzz = 0; //蜂鸣器响
DelayMs(300); //延时300ms
Led = 1; //LED不亮
Buzz = 1; //蜂鸣器不响
DelayMs(300); //延时300ms
KeyScanf(); //按键扫描
}
}
else //不报警
{
Led = 1; //LED不亮
Buzz = 1; //蜂鸣器不响
for(i=0; i<100; i++) //循环100次
{
KeyScanf(); //按键扫描
DelayMs(10); //延时10ms
}
}
}
// 满足报警条件就触发语音报警
//播放一段语音
/*********************************************************/
void PlaySound(unsigned char addr)
{
unsigned char i; //定义无符号字符型变量,for循环用。
SDA = 0; //拉低SDA
DelayMs(5); //延时5ms
for(i=0; i<8; i++) //循环8次,把地址写入语音芯片
{
SDA = 1; //SDA拉高
if(addr & 1) //判断最低位是否为高电平。高电平:低电平 = 3 : 1 表示高电平
{
DelayMs(3); //延时3ms
SDA = 0; //拉低SDA
DelayMs(1); //延时1ms
}
else //判断最低位是否为低电平。高电平:低电平 = 1 : 3 表示低电平
{
DelayMs(1); //延时1ms
SDA = 0; //拉低SDA
DelayMs(3); //延时3ms
}
addr >>= 1; //地址右移一位。为处理下一位地址做准备。
}
SDA = 1; //释放SDA引脚
while(!BUSY); //等待语音播放完毕
}
/*********************************************************/
// 报警判断
/*********************************************************/
void Alarmvioce(unsigned int ss)
{
if(ss <= gAlarm) //判断是否超出范围,超出则报警
{
PlaySound(ss / 100); //播放距离的个位
PlaySound(0x0C); //播放“点”
PlaySound(ss%100/10); //播放距离的小数后第一位
PlaySound(ss%10); //播放距离的小数后第二位
PlaySound(0x0E); //播放“米”
DelayMs(200); //延时0.2s
PlaySound(0x11); //播放“注意安全”
}
}
/*********************************************************/
// 主函数
/*********************************************************/
void main()
{
unsigned char dat1, dat2; //定义变量dat1,dat2,用来保存gAlarm分离后的值
unsigned int dis; //用来保存读取到的距离值
int temp;
LcdInit(); //执行液晶初始化
TMOD = 0x01; //选择定时器0,并且确定是工作方式1(为了超声波模块测量距离计时用的)
GetDistance(); //进行第一次距离采集,结果忽略
Get18B20Temp(); //进行第一次温度值采集,结果忽略
SetLcdCursor(0, 0); //设置坐标第1行,第1列
ShowLcdStr(" Welcome! "); //显示字符串" Welcome! "
DelayMs(2000); //延时2s
SetLcdCursor(0, 0); //设置坐标第1行,第1列
ShowLcdStr(" Dist= cm "); //显示字符串" Dist= cm "
SetLcdCursor(1, 0); //设置坐标第2行,第1列
ShowLcdStr(" Temp= C "); //显示字符串" Temp= C "
dat1 = ReadE2PROM(0x2000); //从E2PROM中读取报警值
dat2 = ReadE2PROM(0x2001);
gAlarm = dat1*100 + dat2; //合并报警值
if((gAlarm==0) || (gAlarm>400)) //如果读取到的报警值异常(等于0或大于400则认为异常)
{
gAlarm = 25; //重新赋值报警值为25
}
while(1) //while(1)循环
{
temp = Get18B20Temp(); //获取DS18B20读取到的温度值
SetLcdCursor(1, 7); //定位到第2行第8列
ShowLcdTemp(temp); //将获取到的温度在液晶上面显示
gSpeed=0.607*(temp/10)+331.4; //根据公式 v=0.607T+331.4 计算出当前温度值对应的超声波速度,这时的单位是“米/秒”
gSpeed=gSpeed/10000; //将超声波的速度从单位“米/秒”转为“厘米/微秒”,方便后面的计算
dis = GetDistance(); //通过超声波模块获取距离
SetLcdCursor(0, 7); //定位到第2行第8列
ShowLcdNum(dis); //将获取到的距离在液晶上面显示
AlarmJudge(dis); //判断一下是否需要报警,是的话则报警
Alarmvoice(dis); //是否语音报警
}
}