重新玩GPS
起因:20来好要滚去海南呆一阵子,家里没人,担心小花小草全灭,所以打算搞一个自动浇花的原理非常简单
1、先设定个开始时间,比如000000,010116 (hhmmss,ddmmyy),然后没隔 5天,浇水200L ,把这个写进arduino 的EEPROM里
2、GPS 读取时间,发现间隔时间到了,就控制电池阀防水浇花,流量计检测流量,流量到达后,停止浇花
3、把本次的浇花时间、是否浇花完成、浇了多少流量写如EEPROM,这样如果因为停水、断电啥的原因,前次没浇够,这次还能补上
4、至于浇花部分,停水时不能浇花,免得电池阀坏了,这个配合流量计要做个检测
注意:GPS方面为了节约资源,降低难度,用命令操作,让gps 只输出GPRMC,每秒一次
先上EEPROM初始化化程序,写入最初始的设定
/*
设置格式如下
hhmmss,ddmmyy,A,B,C,D,E
A=数字1(0-127) 为由存储时间开始,间隔多少个单位时间浇水一次
B=数字2(0-127) 每次浇水多少个单位体积
C=数字3(0-127) 1单位时间=x*10min
D=数字4(0-127) 1单位体积=x*1L
E=数字5(0-127) 索引,如果分每条分配25字节,那么撑死能记录39条浇水记录,默认为字符0
(浇水记录)程序存储格式如下
hhmmss,ddmmyy,A,B,C,D
A=数字1(0-127) 识别码,本条是否已存记录
B=数字2(0-127) 是否浇水完毕
C=数字3(0-127) 已经浇水的单位体积
D=数字4(0-127) 已经消耗的单位时间
*/
#include <EEPROM.h>
char EEPROMtemp;
void setup()
{
Serial.begin(19200);
/*
hhmmss,ddmmyy,A, B,C, D,E
000000,010116,60,20,12,10,0
A=数字1(0-127) 为由存储时间开始,间隔多少个单位时间浇水一次,默认设置为60(60*120min=5天)
B=数字2(0-127) 每次浇水多少个单位体积,默认数值为20,(20*10L=200L),对应ASCII的字符是 DC4 (device control 4) 设备控制4
C=数字3(0-127) 1时间单位=x*10min,默认数值为12
D=数字4(0-127) 1单位体积=x*1L 默认数值是10,对应ASCII的字符是 LF (NL line feed, new line) 换行键
E=数字5(0-127) 索引,如果分每条分配25字节,那么撑死能记录39条浇水记录,默认为字符0,ascII=48
*/
//000000,010116,
EEPROM.write(0, '0');
EEPROM.write(1, '0');
EEPROM.write(2, '0');
EEPROM.write(3, '0');
EEPROM.write(4, '0');
EEPROM.write(5, '0');
EEPROM.write(6, ',');
EEPROM.write(7, '0');
EEPROM.write(8, '1');
EEPROM.write(9, '0');
EEPROM.write(10, '1');
EEPROM.write(11, '1');
EEPROM.write(12, '6');
EEPROM.write(13, ',');
//60,20,12,10,0
EEPROM.write(14,60);
EEPROM.write(15, ',');
EEPROM.write(16, 20);
EEPROM.write(17, ',');
EEPROM.write(18, 12);
EEPROM.write(19, ',');
EEPROM.write(20, 10);
EEPROM.write(21, ',');
EEPROM.write(22, '0');
EEPROM.write(23, 0);
for(int col=0;col<24;col++) EEPROMtemp = EEPROM.read(col);//读取预设参数
}
void loop()
{
for(int col=0;col<14;col++)Serial.print(EEPROMtemp);
int x=EEPROMtemp;
Serial.print(x);
Serial.print(",");
x=EEPROMtemp;
Serial.print(x);
Serial.print(",");
x=EEPROMtemp;
Serial.print(x);
Serial.print(",");
x=EEPROMtemp;
Serial.print(x);
Serial.print(",");
Serial.println(EEPROMtemp);
delay(3000);
}
主程序来了,浇花部分还没上呢
//输出控制:
//调试信息
//#define tiaoshixinxi
#include<EEPROM.h>
int EEPROMvalue; //EEPROM的值
char EEPROMtemp;
/*
设置格式如下
hhmmss,ddmmyy,A,B,C,D,E
A=数字1(0-127) 为由存储时间开始,间隔多少个单位时间浇水一次
B=数字2(0-127) 每次浇水多少个单位体积
C=数字3(0-127) 1单位时间=x*10min
D=数字4(0-127) 1单位体积=x*1L
D=数字5(0-127) 索引,如果分每条分配25字节,那么撑死能记录39条浇水记录
(浇水记录)程序存储格式如下
hhmmss,ddmmyy,A,B
A=数字1(48-49) 48未完毕 49已完毕
B=数字2(0-127) 已经浇水的单位体积
*/
#include <SoftwareSerial.h>
SoftwareSerial gps(8, 9); // RX, TX
char tempx;//提取GPS的时间到tempx数组中hhmmss,ddmmyy
char GPRMC;//临时数组1,放gps数据的
unsigned long gpstimes;//gps计时器,如果超过一定时间10ms,则证明gps数据已经发送完毕
unsigned long quanjujishu;//全局计数,计算1秒之内,循环了多少次,这个次数在一定程度上表示了剩余性能
boolean konghangpanduan=1;//空行判断,因为在读取串口数据后并没有延迟,完全依赖全局循环,那么,特定程序是否执行,就全靠布尔量来标记了
int jsq1=0;
String jianyan="";//GPS数据包的ASCII异或校验
int yihuoyunsuan;//异或运算——校验结果
boolean jiaoyanjieguo=0;//校验结果
boolean dingweiok=0;//定位有效
boolean ruanchuankouchongqi=0;//重启软串口1次,无效时 冷启动GPS
int feiyuqileijiA=0;//非预期累积A,对应gps输出不是GGA 或 RMC时,累积到30次,定义GPS输出1次
int feiyuqileijiB=0;//非预期累积B,对应异或验证不能通过的情况,累积到30次,重启软串口1次,无效时 冷启动GPS 1次
int feiyuqileijiC=0;//非预期累积C,对应RMC长时间不能定位的情况,累积到10次,通过检查年数10位是否为默认值,来确定时间是否可用
boolean TimeOK=0;
int jilusuoyin=0;//记录索引
long shijijiangeshijian=0;//实际间隔时间
long yushejiangeshijian=0;//预设间隔时间
int daijiaoguanliang=0;//待浇灌量
boolean jiaohuakaishi=0;//浇花开始
void setup() {
gps.begin(19200);
Serial.begin(19200);
//EEPROM预处理
/*
设置格式如下
hhmmss,ddmmyy,A,B,C,D,E
000000,010116,60,20,12,10,0
A=数字1(0-127) 为由存储时间开始,间隔多少个单位时间浇水一次,默认设置为60(60*120min=5天)
B=数字2(0-127) 每次浇水多少个单位体积,默认数值为20,(20*10L=200L),对应ASCII的字符是 DC4 (device control 4) 设备控制4
C=数字3(0-127) 1时间单位=x*10min,默认数值为12
D=数字4(0-127) 1单位体积=x*1L 默认数值是10,对应ASCII的字符是 LF (NL line feed, new line) 换行键
E=数字5(0-127) 索引,如果分每条分配25字节,那么撑死能记录39条浇水记录,默认为字符0,ascII=48
(浇水记录)程序存储格式如下
hhmmss,ddmmyy,A,B
A=数字1(48-49) 48未完毕 49已完毕
B=数字2(0-127) 已经浇水的单位体积
*/
for(int col=0;col<24;col++) EEPROMtemp = EEPROM.read(col);//读取预设参数
jilusuoyin=EEPROMtemp-48;//把索引数字化
if(jilusuoyin<40 && jilusuoyin>0){
//每条记录分配25字节空间,jilusuoyin为最近记录的首地址
for(int col=0;col<13;col++) EEPROMtemp = EEPROM.read(col+jilusuoyin*25);//更新至最近一次的浇水时间
}
}
// the loop routine runs over and over again forever:
void loop() {
if(jiaohuakaishi){
//具体浇花程序
jiaohua();
}else{
while (gps.available() > 0 && !TimeOK) {
gpstimes=millis();//重置gpstimes为millis()
konghangpanduan=0;//因为串口有数据,所以空行判断为0,即不空行
GPRMC = gps.read();//软串口读取GPS数据,并保存在临时数组GPRMC中
if(jsq1<99)jsq1++;//确保GPS跑飞时,或设置不当时,单片机自身数组不溢出
delayMicroseconds(500);
}
if(millis()-gpstimes>20 && !konghangpanduan){
konghangpanduan=1;
gpsyihuojianyan();//异或运算,校验GPS输出字符串的准确性
tiqushijian();//提取时间,装入tempx, 存储格式如下 hhmmss,ddmmyy
}
//在取得时间的情况下,讨论是否启动浇花程序
if(TimeOK){
TimeOK=0;
qidongjiaohuadepanduan();
}
}
}
void gpsyihuojianyan()
{
//异或运算,校验GPS输出字符串的准确性
//然而最终的jsq1 与实际的GPRMC的关系究竟如何,还是以实际调试为准
//手中这个jsq1就比GPRMC实际长度大2,估摸着换行符占了一位,但打印不显示,而程序在
//GPS串口传输完毕后,还对jsq1加了1,$ *XX不参与异或运算,第一位$ 所在数组为0
/*
Serial.print("jsq1= ");
Serial.println(jsq1);
Serial.println(GPRMC);
*/
for(int col=1;col<jsq1-5;col++){
if(col==1)yihuoyunsuan=GPRMC;
else yihuoyunsuan=yihuoyunsuan ^ GPRMC;
}
//因为定义int的时候,yihuoyunsuan的结果是以10进制DEC表示的,所以需转换为16进制HEX
//因为GPS校验数据0位也进行了传输,所以在转换的时候有必要将情况分类讨论
//(yihuoyunsuan=0)/(0<yihuoyunsuan<17)/(17<yihuoyunsuan)
if(yihuoyunsuan==0){
//校验数10进制为0,直接填充字符串00
jianyan="00";
}else if(yihuoyunsuan>15){
//此时转换为16进制以后,天然有2位,很理想,不用处理
jianyan = String(yihuoyunsuan,HEX);
}else{
//校验数10进制为1-15时,转换为16进制就只有1位数,所以需要在前面填0
jianyan = "0";
jianyan += String(yihuoyunsuan,HEX);
}
//实践中发现,jianyan中的字符是以小写方式存在,虽然Serial.println(yihuoyunsuan,HEX)会以大写方式打印出来
//但直接Serial.println(jianyan)就是小写的,这是啥情况?
//将其字母转换为大写,否则与GPS传输的校验码对比时大小写问题校验失败(GPS传输的校验码为大写的)
jianyan.toUpperCase();
///////////////////////////////////////////显示异或校验码,方便给GPS编程,虽然还没成功过
/*
Serial.print("jianyan= ");
Serial.println(jianyan);
Serial.print("jsq1= ");
Serial.println(jsq1);
Serial.println(GPRMC);
*/
if(jianyan==GPRMC && jianyan==GPRMC ){
//一致,则说明数据是有效的,输出校验结果
jiaoyanjieguo=1;
feiyuqileijiB=0;
ruanchuankouchongqi=0;//优先通过重启软串口解决校验不过的情况,不行能解决再冷启动GPS
}else{
//不一致
jiaoyanjieguo=0;
feiyuqileijiB++;
}
//对校验数组进行清零
jianyan="";
/*---------------------------异或运算,校验GPS输出字符串的准确性 end--------------------------*/
}
void tiqushijian()
{
//提取时间,装入tempx, 存储格式如下 hhmmss,ddmmyy
//并通过GPS命令操作,在信号不好时RMC、GGA的切换,GPS的重启
if(jiaoyanjieguo){ //通过异或校验的情况下,检查定位情况
jiaoyanjieguo=0;
#ifdef tiaoshixinxi
Serial.println(GPRMC);
#endif
//检查语句 RMC
if(GPRMC=='M' && GPRMC=='C'){
feiyuqileijiA=0;
//RMC 已定位标志判断
//$GPRMC,044344.858,V,0000.0000,N,00000.0000,E,,,030116,,*19
//$GPRMC,044342.000,A,0000.0000,N,00000.0000,E,0.00,,030116,,*1D
if(GPRMC=='A'){
feiyuqileijiC=0;
dingweiok=1;
}else feiyuqileijiC++; //对应RMC长时间不能定位的情况
//对应RMC长时间不能定位的情况,900秒不定位,冷启动gps
if(feiyuqileijiC>900){
feiyuqileijiC=0;
gps.println("$PSRF101,0,0,0,000,0,0,12,4*10");//GPS COLD START
}
if(dingweiok || feiyuqileijiC>10){ //当GPS已定位,或虽未定位,但已经经过10秒
dingweiok=0;
//提取GPS的时间到tempx数组中hhmmss,ddmmyy
//$GPRMC,044342.000,A,0000.0000,N,00000.0000,E,0.00,,030116,,*1D
for(int col=0;col<7;col++)tempx=GPRMC;//时间位置固定,可直接提取
//日期稍微麻烦点,计算标志位“,”这个符号的位置,来规避长度变换(实际航速、实际航迹向这2个不确定因素)
int col2=0;
for(int col=0;col<jsq1-3;col++){
if(GPRMC==',')col2++;
if(col2==9){
/*
Serial.print("GPRMC=");
Serial.print(GPRMC);
Serial.print(" GPRMC= ");
Serial.println(GPRMC);
//当gps信号弱,不能定位,通过检查gps 给出的年月日,是否是默认的160406ddmmyy,来判断时间的可用性,即年十位不为0
GPRMC 是年的10位,GPRMC 是年的个位,默认是06年,现在是16年,只要十位不是0(ascII大于48),那么就算时间可信
*/
if(GPRMC>48){
feiyuqileijiC=0;
TimeOK=1;//时间可用标志
gps.end();//提取时间后关闭软串口
for(int col3=0;col3<7;col3++)tempx=GPRMC;//提取时间
col=jsq1-3;//跳出循环
}
}
}
/*
//Serial.print(" TimeOK ");
Serial.print("tempx=");
Serial.println(tempx);
Serial.print("tempx=");
Serial.println(tempx);
if(tempx==44)Serial.print("tempx=44 ");
if(tempx+2>44)Serial.print("tempx+2>44 ");
int col=tempx;
Serial.print("col=");
Serial.println(col);
*/
}
}else feiyuqileijiA++;//对应gps输出不是 RMC时
}
if(feiyuqileijiA>30){ //对应gps输出不是 RMC时,累积到30次,定义GPS输出1次
feiyuqileijiA=0;
gps.println("$PSRF109,NMEA19200,NULL38400,GGA0,GLL0,GSA0,GSV0,RMC1,VTG0,USER0*32");
}
if(feiyuqileijiB>30){ //对应异或验证不能通过的情况,累积到30次,重启软串口1次,无效时 冷启动GPS 1次
feiyuqileijiB=0;
if(!ruanchuankouchongqi){ //优先重启软串口,ruanchuankouchongqi默认为0
ruanchuankouchongqi=!ruanchuankouchongqi;
gps.end();
gps.begin(19200);
}else{
ruanchuankouchongqi=!ruanchuankouchongqi;
gps.println("$PSRF101,0,0,0,000,0,0,12,4*10");//GPS COLD START
}
}
/*
Serial.print("feiyuqileijiA=");
Serial.print(feiyuqileijiA);
Serial.print(" feiyuqileijiB= ");
Serial.print(feiyuqileijiB);
Serial.print(" feiyuqileijiC= ");
Serial.print(feiyuqileijiC);
Serial.println(GPRMC);
*/
//收尾工作,清空GPS数组,jsq1归零
for(int col=0;col<100;col++)GPRMC=0;
jsq1=0;
}
void qidongjiaohuadepanduan()//启动浇花的判断
{
/*
EEPROM设置格式如下
hhmmss,ddmmyy,A, B,C, D,E
000000,010116,60,20,12,10,0
A=数字1(0-127) 为由存储时间开始,间隔多少个单位时间浇水一次,默认设置为60(60*120min=5天)
B=数字2(0-127) 每次浇水多少个单位体积,默认数值为20,(20*10L=200L),对应ASCII的字符是 DC4 (device control 4) 设备控制4
C=数字3(0-127) 1时间单位=x*10min,默认数值为12
D=数字4(0-127) 1单位体积=x*1L 默认数值是10,对应ASCII的字符是 LF (NL line feed, new line) 换行键
E=数字5(0-127) 索引,如果分每条分配25字节,那么撑死能记录39条浇水记录,默认为字符0,ascII=48
(浇水记录)程序存储格式如下
hhmmss,ddmmyy,A,B
A=数字1(48-49) 48未完毕 49已完毕
B=数字2(0-127) 已经浇水的单位体积
*/
//进行浇花有2种情况, A、上次浇花因中断,现在继续 B、间隔时间到了,应该浇花
//情况A
/*对记录位进行初始化
hhmmss,ddmmyy,A,B
A=数字1(48-49) 48未完毕 49已完毕
B=数字2(0-127) 已经浇水的单位体积
*/
if(jilusuoyin > 0){//上次未完成的情况(索引大于0才可能有存在)
//上次未完成,浇花开始
if(EEPROM.read(14+jilusuoyin*25)==48)jiaohuakaishi=1;
}else if(millis()-quanjujishu>3000){ //讨论因间隔时间到了的浇花,情况,另外的间隔是定义的多久打开串口读取gps 数据一次,为方便调试,这里取3秒
//情况B,一个全新的开始
//时间到了的情况,EEPROMtemp*EEPROMtemp 个10min就是间隔,最大127*127,约2688小时,最小10min
quanjujishu=millis();
gps.begin(19200);
//shijijiangeshijian=计算上次浇水时间到现在,经理过多少个10min
long niancha=(tempx-EEPROMtemp)*10*365*24*6+(tempx-EEPROMtemp)*365*24*6;
long yuecha=(tempx-EEPROMtemp)*10*30*24*6+(tempx-EEPROMtemp)*30*24*6;
long richa=(tempx-EEPROMtemp)*10*24*6+(tempx-EEPROMtemp)*24*6;
long shicha=(tempx-EEPROMtemp)*10*6+(tempx-EEPROMtemp)*6;
shijijiangeshijian=niancha+yuecha+richa+shicha+tempx-EEPROMtemp;
//预设间隔时间 = 间隔多少个单位时间 X 每个单位代表的时间(X * 10min)
yushejiangeshijian=EEPROMtemp*EEPROMtemp;
if(shijijiangeshijian > yushejiangeshijian){
if(jilusuoyin<39)jilusuoyin++;
else jilusuoyin=1;
EEPROM.write(22, jilusuoyin+48);//更新EEPROM中的索引数值=原值+1,等于39的情况归0
for(int col=0;col<13;col++) EEPROMtemp = tempx;//更新预设数组中的时间
for(int col=0;col<13;col++) EEPROM.write(col+jilusuoyin*25, tempx);//更新EEPROM中的时间
/*对记录位进行初始化
hhmmss,ddmmyy,A,B
A=数字1(48-49) 48未完毕 49已完毕
B=数字2(0-127) 已经浇水的单位体积
*/
EEPROM.write(13+jilusuoyin*25, ',');//,
EEPROM.write(14+jilusuoyin*25, 48);//A
EEPROM.write(15+jilusuoyin*25, ',');//,
EEPROM.write(16+jilusuoyin*25, 0);//B
//浇花开始
jiaohuakaishi=1;
}
#ifdef tiaoshixinxi
Serial.print(“Detection value=”);
Serial.println(shijijiangeshijian);
Serial.print(“set valuet=”);
Serial.println(yushejiangeshijian);
Serial.println(tempx);
for(int col=0;col<14;col++)Serial.print(EEPROMtemp);
int x=EEPROMtemp;
Serial.print(x);
Serial.print(",");
x=EEPROMtemp;
Serial.print(x);
Serial.print(",");
x=EEPROMtemp;
Serial.print(x);
Serial.print(",");
x=EEPROMtemp;
Serial.print(x);
Serial.print(",");
Serial.println(EEPROMtemp);
#endif
}
}
void jiaohua()//具体浇花程序
{
//读取已浇灌体积,与预设浇灌体积相减,计算待浇灌量
daijiaoguanliang=EEPROMtemp-EEPROM.read(16+jilusuoyin*25);
}
以前写的浇花程序的一部分,肯定不能直接用,作为参考,后面就改这个程序了
//////////////////////////////////////////////////////////////////////////////////////////////
//浇花的一些定义
//////////////////////////////////////////////////////////////////////////////////////////////
int liuliangji=12;
int diancifa=13;
unsigned long meimiaoyilunhui=millis();//每秒一轮回
boolean chongfupanduanA=0;//重复判定A
int jsqLLx=0;
float shishiliuliang=0;//实时流量
float leijiliuliang=0;//累计流量
int shedingjiaoguanliang=0;//设定浇灌量
unsigned long guzhangjianceA=millis();//故障检测
int guzhangjiancejsq;//故障检测计数器
void setup() {
}
void loop() {
//浇花主程序///////////////////////////////////////////////////////////
jiaohuaA();//每秒频率测定,实时流量计算,累计流量叠加
jiaohuaB();//通过3秒的频率次数的累加,来判断是否停水了(防止电池阀在没水的时候长期运行)
if(leijiliuliang < shedingjiaoguanliang) { //流量控制,流量超了就关了
digitalWrite(diancifa, LOW);//累计流量小于设定流量,打开电磁阀
}else{
digitalWrite(diancifa, HIGH);//关闭电磁阀
shedingjiaoguanliang=0;//清空设定浇灌量
leijiliuliang=0;//清空累计流量
}
}
void jiaohuaA(){
if(millis()-meimiaoyilunhui>1000) {
meimiaoyilunhui=millis();//每一秒还原一次
shishiliuliang=jsqLLx/7.5;//计算实时流量(公式:频率=7.5*流量(L/min))
leijiliuliang=leijiliuliang+(shishiliuliang/60);//(每秒积分)计数累计流量
guzhangjiancejsq=guzhangjiancejsq+jsqLLx;//累加频率的数量,来确定是否故障(停水了)
jsqLLx=0;//清空每秒频率
}else{
int liuliangjidianping =digitalRead(liuliangji);//读取流量计的电平
if(liuliangjidianping == HIGH && !chongfupanduanA){ //高电平 且 本次没计数,就计数
jsqLLx++;
chongfupanduanA=!chongfupanduanA;//本次已计数,则标记已计数
}
if(liuliangjidianping == LOW && chongfupanduanA)chongfupanduanA=!chongfupanduanA;//低电平,且 计数标记为1,则重置计数标记
}
}
void jiaohuaB(){
//停水状态的应急处理
if(millis()-guzhangjianceA>30000) {
if(guzhangjiancejsq<210){//3秒累计频率小于21,则说明流量小于1L/min,这个时候认定为停水了
//切换模式之后,对浇花部分进行操作(即停止浇花)
digitalWrite(diancifa, HIGH);//关闭电磁阀
shedingjiaoguanliang=0;//清空设定浇灌量
leijiliuliang=0;//清空累计流量
guzhangjianceA=millis();//同时清空故障检测的时间,否则一开始就是故障状态
}
guzhangjiancejsq=0;//累计频率置0
}
}
大神,为何用GPS?
RTC更简单啊:D 卓泰科技 发表于 2016-1-5 09:08
大神,为何用GPS?
RTC更简单啊
用GPS的原因很简单啊
A、我有GPS模块,还不少(至少8个+),都是C3-470B拆下来的,GPS上电池是报废了的,所以我全部把电池去掉了(默认是2006年的货, SIRFIII芯片)
B、我同样有RTC,不过常年不用,估计电池同样也报废了,与其用时间不太可靠的RTC,还不如直接用精确的卫星时间呢
C、我还在考虑弄个小的,那种塑料的太阳能板,通过电压来区分白天黑夜,晚上单片机全部断电休息,这样即便是单片机奔溃导致浇水关不了,那么一段时间后也会重启的PS:说道这里,那么下个版本似乎该固定几个浇花时段,这样一旦出问题,浪费的水可以更少哦!
融合浇花程序的草稿版
桌泰的GPS要是能设置只输出GPRMC,也可以来测试一下,这个,改几个参数就能用了
//输出控制:
//调试信息
#define tiaoshixinxi
#define tiaoshixinxi2
#include<EEPROM.h>
int EEPROMvalue; //EEPROM的值
char EEPROMtemp;
/*
设置格式如下
hhmmss,ddmmyy,A,B,C,D,E
A=数字1(0-127) 为由存储时间开始,间隔多少个单位时间浇水一次
B=数字2(0-127) 每次浇水多少个单位体积
C=数字3(0-127) 1单位时间=x*10min
D=数字4(0-127) 1单位体积=x*1L
D=数字5(0-127) 索引,如果分每条分配25字节,那么撑死能记录39条浇水记录
(浇水记录)程序存储格式如下
hhmmss,ddmmyy,A,B
A=数字1(48-49) 48未完毕 49已完毕
B=数字2(0-127) 已经浇水的单位体积
*/
#include <SoftwareSerial.h>
SoftwareSerial gps(8, 9); // RX, TX
char tempx;//提取GPS的时间到tempx数组中hhmmss,ddmmyy
char GPRMC;//临时数组1,放gps数据的
unsigned long gpstimes;//gps计时器,如果超过一定时间10ms,则证明gps数据已经发送完毕
unsigned long quanjujishu;//全局计数,计算1秒之内,循环了多少次,这个次数在一定程度上表示了剩余性能
boolean konghangpanduan=1;//空行判断,因为在读取串口数据后并没有延迟,完全依赖全局循环,那么,特定程序是否执行,就全靠布尔量来标记了
int jsq1=0;
String jianyan="";//GPS数据包的ASCII异或校验
int yihuoyunsuan;//异或运算——校验结果
boolean jiaoyanjieguo=0;//校验结果
boolean dingweiok=0;//定位有效
boolean ruanchuankouchongqi=0;//重启软串口1次,无效时 冷启动GPS
int feiyuqileijiA=0;//非预期累积A,对应gps输出不是GGA 或 RMC时,累积到30次,定义GPS输出1次
int feiyuqileijiB=0;//非预期累积B,对应异或验证不能通过的情况,累积到30次,重启软串口1次,无效时 冷启动GPS 1次
int feiyuqileijiC=0;//非预期累积C,对应RMC长时间不能定位的情况,累积到10次,通过检查年数10位是否为默认值,来确定时间是否可用
boolean TimeOK=0;
int jilusuoyin=0;//记录索引
long shijijiangeshijian=0;//实际间隔时间
long yushejiangeshijian=0;//预设间隔时间
int daijiaoguanliang=0;//待浇灌量
boolean jiaohuakaishi=0;//浇花开始
///////////////////////////////////////////////////////
//浇花的一些定义
///////////////////////////////////////////////////////
int liuliangji=10;
int diancifa=11;
unsigned long meimiaoyilunhui;//每秒一轮回
boolean chongfupanduanA=0;//重复判定A
int jsqLLx=0;
float shishiliuliang=0;//实时流量
float leijiliuliang=0;//累计流量
unsigned long guzhangjianceA;//故障检测
int guzhangjiancejsq;//故障检测计数器
//////////////////////////////////////////////////////////////////////
void setup() {
gps.begin(19200);
Serial.begin(19200);
//EEPROM预处理
/*
设置格式如下
hhmmss,ddmmyy,A,B,C,D,E
000000,010116,60,20,12,10,0
A=数字1(0-127) 为由存储时间开始,间隔多少个单位时间浇水一次,默认设置为60(60*120min=5天)
B=数字2(0-127) 每次浇水多少个单位体积,默认数值为20,(20*10L=200L),对应ASCII的字符是 DC4 (device control 4) 设备控制4
C=数字3(0-127) 1时间单位=x*10min,默认数值为12
D=数字4(0-127) 1单位体积=x*1L 默认数值是10,对应ASCII的字符是 LF (NL line feed, new line) 换行键
E=数字5(0-127) 索引,如果分每条分配25字节,那么撑死能记录39条浇水记录,默认为字符0,ascII=48
(浇水记录)程序存储格式如下
hhmmss,ddmmyy,A,B
A=数字1(48-49) 48未完毕 49已完毕
B=数字2(0-127) 已经浇水的单位体积
*/
for(int col=0;col<24;col++) EEPROMtemp = EEPROM.read(col);//读取预设参数
//打印预设参数
Serial.println();
Serial.println();
for(int col=0;col<14;col++)Serial.print(EEPROMtemp);
int x=EEPROMtemp;
Serial.print(x);
Serial.print(",");
x=EEPROMtemp;
Serial.print(x);
Serial.print(",");
x=EEPROMtemp;
Serial.print(x);
Serial.print(",");
x=EEPROMtemp;
Serial.print(x);
Serial.print(",");
Serial.println(EEPROMtemp);
jilusuoyin=EEPROMtemp-48;//把索引数字化
if(jilusuoyin<40 && jilusuoyin>0){
//每条记录分配25字节空间,jilusuoyin为最近记录的首地址
for(int col=0;col<13;col++) EEPROMtemp = EEPROM.read(col+jilusuoyin*25);//更新至最近一次的浇水时间
for(int col=0;col<13;col++)Serial.print(EEPROMtemp);
//打印记录
Serial.write(EEPROM.read(13+jilusuoyin*25)); //直接打出数字对应的ASCII码
Serial.write(EEPROM.read(14+jilusuoyin*25));
Serial.write(EEPROM.read(15+jilusuoyin*25));
x=EEPROM.read(16+jilusuoyin*25);
Serial.println(x);
Serial.println("setup end");
}
}
// the loop routine runs over and over again forever:
void loop() {
if(jiaohuakaishi){
//具体浇花程序
jiaohua();
}else{
while (gps.available() > 0 && !TimeOK) {
gpstimes=millis();//重置gpstimes为millis()
konghangpanduan=0;//因为串口有数据,所以空行判断为0,即不空行
GPRMC = gps.read();//软串口读取GPS数据,并保存在临时数组GPRMC中
if(jsq1<99)jsq1++;//确保GPS跑飞时,或设置不当时,单片机自身数组不溢出
delayMicroseconds(500);
}
if(millis()-gpstimes>20 && !konghangpanduan){
konghangpanduan=1;
gpsyihuojianyan();//异或运算,校验GPS输出字符串的准确性
tiqushijian();//提取时间,装入tempx, 存储格式如下 hhmmss,ddmmyy
}
//在取得时间的情况下,讨论是否启动浇花程序
if(TimeOK){
TimeOK=0;
qidongjiaohuadepanduan();
}
}
}
void gpsyihuojianyan()
{
//异或运算,校验GPS输出字符串的准确性
//然而最终的jsq1 与实际的GPRMC的关系究竟如何,还是以实际调试为准
//手中这个jsq1就比GPRMC实际长度大2,估摸着换行符占了一位,但打印不显示,而程序在
//GPS串口传输完毕后,还对jsq1加了1,$ *XX不参与异或运算,第一位$ 所在数组为0
/*
Serial.print("jsq1= ");
Serial.println(jsq1);
Serial.println(GPRMC);
*/
for(int col=1;col<jsq1-5;col++){
if(col==1)yihuoyunsuan=GPRMC;
else yihuoyunsuan=yihuoyunsuan ^ GPRMC;
}
//因为定义int的时候,yihuoyunsuan的结果是以10进制DEC表示的,所以需转换为16进制HEX
//因为GPS校验数据0位也进行了传输,所以在转换的时候有必要将情况分类讨论
//(yihuoyunsuan=0)/(0<yihuoyunsuan<17)/(17<yihuoyunsuan)
if(yihuoyunsuan==0){
//校验数10进制为0,直接填充字符串00
jianyan="00";
}else if(yihuoyunsuan>15){
//此时转换为16进制以后,天然有2位,很理想,不用处理
jianyan = String(yihuoyunsuan,HEX);
}else{
//校验数10进制为1-15时,转换为16进制就只有1位数,所以需要在前面填0
jianyan = "0";
jianyan += String(yihuoyunsuan,HEX);
}
//实践中发现,jianyan中的字符是以小写方式存在,虽然Serial.println(yihuoyunsuan,HEX)会以大写方式打印出来
//但直接Serial.println(jianyan)就是小写的,这是啥情况?
//将其字母转换为大写,否则与GPS传输的校验码对比时大小写问题校验失败(GPS传输的校验码为大写的)
jianyan.toUpperCase();
///////////////////////////////////////////显示异或校验码,方便给GPS编程,虽然还没成功过
/*
Serial.print("jianyan= ");
Serial.println(jianyan);
Serial.print("jsq1= ");
Serial.println(jsq1);
Serial.println(GPRMC);
*/
if(jianyan==GPRMC && jianyan==GPRMC ){
//一致,则说明数据是有效的,输出校验结果
jiaoyanjieguo=1;
feiyuqileijiB=0;
ruanchuankouchongqi=0;//优先通过重启软串口解决校验不过的情况,不行能解决再冷启动GPS
}else{
//不一致
jiaoyanjieguo=0;
feiyuqileijiB++;
}
//对校验数组进行清零
jianyan="";
/*---------------------------异或运算,校验GPS输出字符串的准确性 end--------------------------*/
}
void tiqushijian()
{
//提取时间,装入tempx, 存储格式如下 hhmmss,ddmmyy
//并通过GPS命令操作,在信号不好时RMC、GGA的切换,GPS的重启
if(jiaoyanjieguo){ //通过异或校验的情况下,检查定位情况
jiaoyanjieguo=0;
#ifdef tiaoshixinxi
Serial.println(GPRMC);
#endif
//检查语句 RMC
if(GPRMC=='M' && GPRMC=='C'){
feiyuqileijiA=0;
//RMC 已定位标志判断
//$GPRMC,044344.858,V,0000.0000,N,00000.0000,E,,,030116,,*19
//$GPRMC,044342.000,A,0000.0000,N,00000.0000,E,0.00,,030116,,*1D
if(GPRMC=='A'){
feiyuqileijiC=0;
dingweiok=1;
}else feiyuqileijiC++; //对应RMC长时间不能定位的情况
//对应RMC长时间不能定位的情况,900秒不定位,冷启动gps
if(feiyuqileijiC>900){
feiyuqileijiC=0;
gps.println("$PSRF101,0,0,0,000,0,0,12,4*10");//GPS COLD START
}
if(dingweiok || feiyuqileijiC>10){ //当GPS已定位,或虽未定位,但已经经过10秒
dingweiok=0;
//提取GPS的时间到tempx数组中hhmmss,ddmmyy
//$GPRMC,044342.000,A,0000.0000,N,00000.0000,E,0.00,,030116,,*1D
for(int col=0;col<7;col++)tempx=GPRMC;//时间位置固定,可直接提取
//日期稍微麻烦点,计算标志位","这个符号的位置,来规避长度变换(实际航速、实际航迹向这2个不确定因素)
int col2=0;
for(int col=0;col<jsq1-3;col++){
if(GPRMC==',')col2++;
if(col2==9){
/*
Serial.print("GPRMC=");
Serial.print(GPRMC);
Serial.print(" GPRMC= ");
Serial.println(GPRMC);
//当gps信号弱,不能定位,通过检查gps 给出的年月日,是否是默认的160406ddmmyy,来判断时间的可用性,即年十位不为0
GPRMC 是年的10位,GPRMC 是年的个位,默认是06年,现在是16年,只要十位不是0(ascII大于48),那么就算时间可信
*/
if(GPRMC>48){
feiyuqileijiC=0;
TimeOK=1;//时间可用标志
gps.end();//提取时间后关闭软串口
for(int col3=0;col3<7;col3++)tempx=GPRMC;//提取时间
col=jsq1-3;//跳出循环
}
}
}
/*
//Serial.print(" TimeOK ");
Serial.print("tempx=");
Serial.println(tempx);
Serial.print("tempx=");
Serial.println(tempx);
if(tempx==44)Serial.print("tempx=44 ");
if(tempx+2>44)Serial.print("tempx+2>44 ");
int col=tempx;
Serial.print("col=");
Serial.println(col);
*/
}
}else feiyuqileijiA++;//对应gps输出不是 RMC时
}
if(feiyuqileijiA>30){ //对应gps输出不是 RMC时,累积到30次,定义GPS输出1次
feiyuqileijiA=0;
gps.println("$PSRF109,NMEA19200,NULL38400,GGA0,GLL0,GSA0,GSV0,RMC1,VTG0,USER0*32");
}
if(feiyuqileijiB>30){ //对应异或验证不能通过的情况,累积到30次,重启软串口1次,无效时 冷启动GPS 1次
feiyuqileijiB=0;
if(!ruanchuankouchongqi){ //优先重启软串口,ruanchuankouchongqi默认为0
ruanchuankouchongqi=!ruanchuankouchongqi;
gps.end();
gps.begin(19200);
}else{
ruanchuankouchongqi=!ruanchuankouchongqi;
gps.println("$PSRF101,0,0,0,000,0,0,12,4*10");//GPS COLD START
}
}
/*
Serial.print("feiyuqileijiA=");
Serial.print(feiyuqileijiA);
Serial.print(" feiyuqileijiB= ");
Serial.print(feiyuqileijiB);
Serial.print(" feiyuqileijiC= ");
Serial.print(feiyuqileijiC);
Serial.println(GPRMC);
*/
//收尾工作,清空GPS数组,jsq1归零
for(int col=0;col<100;col++)GPRMC=0;
jsq1=0;
}
void qidongjiaohuadepanduan()//启动浇花的判断
{
/*
EEPROM设置格式如下
hhmmss,ddmmyy,A, B,C, D,E
000000,010116,60,20,12,10,0
A=数字1(0-127) 为由存储时间开始,间隔多少个单位时间浇水一次,默认设置为60(60*120min=5天)
B=数字2(0-127) 每次浇水多少个单位体积,默认数值为20,(20*10L=200L),对应ASCII的字符是 DC4 (device control 4) 设备控制4
C=数字3(0-127) 1时间单位=x*10min,默认数值为12
D=数字4(0-127) 1单位体积=x*1L 默认数值是10,对应ASCII的字符是 LF (NL line feed, new line) 换行键
E=数字5(0-127) 索引,如果分每条分配25字节,那么撑死能记录39条浇水记录,默认为字符0,ascII=48
(浇水记录)程序存储格式如下
hhmmss,ddmmyy,A,B
A=数字1(48-49) 48未完毕 49已完毕
B=数字2(0-127) 已经浇水的单位体积
*/
//进行浇花有2种情况, A、上次浇花因中断,现在继续 B、间隔时间到了,应该浇花
//情况A
/*对记录位进行初始化
hhmmss,ddmmyy,A,B
A=数字1(48-49) 48未完毕 49已完毕
B=数字2(0-127) 已经浇水的单位体积
*/
if(jilusuoyin > 0){//上次未完成的情况(索引大于0才可能有存在)
//上次未完成,浇花开始
if(EEPROM.read(14+jilusuoyin*25)==48){
jiaohuakaishi=1;
//浇花开始前的一些准备工作
guzhangjianceA=millis();//清掉故障检测与millis间的时间差距,否则一开始就是故障状态
meimiaoyilunhui=millis();//每一秒还原一次
}
}else if(millis()-quanjujishu>3000){ //讨论因间隔时间到了的浇花,情况,另外的间隔是定义的多久打开串口读取gps 数据一次,为方便调试,这里取3秒
//情况B,一个全新的开始
//时间到了的情况,EEPROMtemp*EEPROMtemp 个10min就是间隔,最大127*127,约2688小时,最小10min
quanjujishu=millis();
gps.begin(19200);
//shijijiangeshijian=计算上次浇水时间到现在,经理过多少个10min
long niancha=(tempx-EEPROMtemp)*10*365*24*6+(tempx-EEPROMtemp)*365*24*6;
long yuecha=(tempx-EEPROMtemp)*10*30*24*6+(tempx-EEPROMtemp)*30*24*6;
long richa=(tempx-EEPROMtemp)*10*24*6+(tempx-EEPROMtemp)*24*6;
long shicha=(tempx-EEPROMtemp)*10*6+(tempx-EEPROMtemp)*6;
shijijiangeshijian=niancha+yuecha+richa+shicha+tempx-EEPROMtemp;
//预设间隔时间 = 间隔多少个单位时间 X 每个单位代表的时间(X * 10min)
yushejiangeshijian=EEPROMtemp*EEPROMtemp;
if(shijijiangeshijian > yushejiangeshijian){
if(jilusuoyin<39)jilusuoyin++;
else jilusuoyin=1;
EEPROM.write(22, jilusuoyin+48);//更新EEPROM中的索引数值=原值+1,等于39的情况归0
for(int col=0;col<13;col++) EEPROMtemp = tempx;//更新预设数组中的时间
for(int col=0;col<13;col++) EEPROM.write(col+jilusuoyin*25, tempx);//更新EEPROM中的时间
/*对记录位进行初始化
hhmmss,ddmmyy,A,B
A=数字1(48-49) 48未完毕 49已完毕
B=数字2(0-127) 已经浇水的单位体积
*/
EEPROM.write(13+jilusuoyin*25, ',');//,
EEPROM.write(14+jilusuoyin*25, 48);//A
EEPROM.write(15+jilusuoyin*25, ',');//,
EEPROM.write(16+jilusuoyin*25, 0);//B
//浇花开始
jiaohuakaishi=1;
//浇花开始前的一些准备工作
guzhangjianceA=millis();//清掉故障检测与millis间的时间差距,否则一开始就是故障状态
meimiaoyilunhui=millis();//每一秒还原一次
}
#ifdef tiaoshixinxi
Serial.print("Detection value=");
Serial.println(shijijiangeshijian);
Serial.print(" set valuet=");
Serial.println(yushejiangeshijian);
Serial.println(tempx); //now time
for(int col=0;col<14;col++)Serial.print(EEPROMtemp);//set time
int x=EEPROMtemp;
Serial.print(x);
Serial.print(",");
x=EEPROMtemp;
Serial.print(x);
Serial.print(",");
x=EEPROMtemp;
Serial.print(x);
Serial.print(",");
x=EEPROMtemp;
Serial.print(x);
Serial.print(",");
Serial.print(EEPROMtemp);
Serial.print("V=");
Serial.print(EEPROMtemp*EEPROMtemp);
Serial.print(" LTime =");
Serial.print(EEPROMtemp*EEPROMtemp/6/24);
Serial.println(" Day");
#endif
}
}
void jiaohua()//具体浇花程序
{
/*对记录位进行初始化
hhmmss,ddmmyy,A,B
A=数字1(48-49) 48未完毕 49已完毕
B=数字2(0-127) 已经浇水的单位体积
*/
//读取已浇灌体积,与预设浇灌体积相减,计算待浇灌量
int yijiaoguanliang=EEPROM.read(16+jilusuoyin*25);
daijiaoguanliang=EEPROMtemp-yijiaoguanliang;
//累积流量达到一定值后,对EEPROM中的已浇灌量进行更新
if(leijiliuliang>10){
leijiliuliang=leijiliuliang-10;
yijiaoguanliang++;
EEPROM.write(16+jilusuoyin*25, yijiaoguanliang);
}
if(daijiaoguanliang<1){
jiaohuakaishi=0;//浇水完毕后跳出浇水循环
EEPROM.write(14+jilusuoyin*25, 49);//将本次浇花已完成写入EEPROM
digitalWrite(diancifa, HIGH);//关闭电磁阀
leijiliuliang=0;//清空累计流量
gps.begin(19200);//重新打开软串口
}else digitalWrite(diancifa, LOW);//累计流量小于设定流量,打开电磁阀
//正常的浇花部分
//浇花主程序///////////////////////////////////////////////////////////
//每秒频率测定,实时流量计算,累计流量叠加
if(millis()-meimiaoyilunhui>1000) {
meimiaoyilunhui=millis();//每一秒还原一次
shishiliuliang=jsqLLx/7.5;//计算实时流量(公式:频率=7.5*流量(L/min))
leijiliuliang=leijiliuliang+(shishiliuliang/60);//(每秒积分)计数累计流量
guzhangjiancejsq=guzhangjiancejsq+jsqLLx;//累加频率的数量,来确定是否故障(停水了)
jsqLLx=0;//清空每秒频率
#ifdef tiaoshixinxi2
Serial.print("yijiaoguanliang=");
Serial.print(yijiaoguanliang); //已浇灌量
Serial.print(" daijiaoguanliang=");
Serial.print(daijiaoguanliang); //待浇灌量
Serial.print(" shishiliuliang=");
Serial.print(shishiliuliang); //实时流量
Serial.print(" leijiliuliang=");
Serial.println(leijiliuliang); //累计流量
Serial.println(tempx); //now time
for(int col=0;col<14;col++)Serial.print(EEPROMtemp);//set time
int x=EEPROMtemp;
Serial.print(x);
Serial.print(",");
x=EEPROMtemp;
Serial.print(x);
Serial.print(",");
x=EEPROMtemp;
Serial.print(x);
Serial.print(",");
x=EEPROMtemp;
Serial.print(x);
Serial.print(",");
Serial.print(EEPROMtemp);
Serial.print("V=");
Serial.print(EEPROMtemp*EEPROMtemp);
Serial.print(" LTime =");
Serial.print(EEPROMtemp*EEPROMtemp/6/24);
Serial.println(" Day");
Serial.println();
#endif
}else{
int liuliangjidianping =digitalRead(liuliangji);//读取流量计的电平
if(liuliangjidianping == HIGH && !chongfupanduanA){ //高电平 且 本次没计数,就计数
jsqLLx++;
chongfupanduanA=!chongfupanduanA;//本次已计数,则标记已计数
}
if(liuliangjidianping == LOW && chongfupanduanA)chongfupanduanA=!chongfupanduanA;//低电平,且 计数标记为1,则重置计数标记
}
//通过3秒的频率次数的累加,来判断是否停水了(防止电池阀在没水的时候长期运行)
//停水状态的应急处理
if(millis()-guzhangjianceA>9000) {
guzhangjianceA=millis();//同时清空故障检测的时间,否则一开始就是故障状态
if(guzhangjiancejsq<63){//3秒累计频率小于21,(9秒63),则说明流量小于1L/min,这个时候认定为停水了
//对浇花部分进行操作(即停止浇花)
digitalWrite(diancifa, HIGH);//关闭电磁阀
//leijiliuliang=0;//清空累计流量,这步没必要,因为只要不断电,来水之后是可以接着计算的
jiaohuakaishi=0;//跳出浇水循环
for(int col=0;col<12;col++){
Serial.println("GAME OVER");
delay(1000);//对应一个延迟,1小时
}
}
guzhangjiancejsq=0;//累计频率置0
gps.begin(19200);//重新打开软串口
}
}
囧囧-科技大神粉 发表于 2016-1-9 09:13
用GPS的原因很简单啊
A、我有GPS模块,还不少(至少8个+),都是C3-470B拆下来的,GPS上电池是报废了的, ...
5555,我用RTC换你GPS,中不?:'(
页:
[1]