基于Arduino的开源自行车行车电脑.docx
《基于Arduino的开源自行车行车电脑.docx》由会员分享,可在线阅读,更多相关《基于Arduino的开源自行车行车电脑.docx(29页珍藏版)》请在冰豆网上搜索。
基于Arduino的开源自行车行车电脑
gTracking——基于Arduino的开源自行车行车电脑系统
时间:
2012-12-0714:
18:
43 作者:
WellsWang 来源:
查看:
937 评论:
导读:
自从19世纪中期自行车运动从欧洲、北美发源以来,吸引了世界上一批又一批的爱好者参与其中。
在曾经被称为“自行车王国”的中国,上世纪80年代,自行车也作为曾经的“四大件”之一走入千家万户,在那时,自行车成为了民众主要的交通工具。
随着社会经济的发展,汽车逐..
自从19世纪中期自行车运动从欧洲、北美发源以来,吸引了世界上一批又一批的爱好者参与其中。
在曾经被称为“自行车王国”的中国,上世纪80年代,自行车也作为曾经的“四大件”之一走入千家万户,在那时,自行车成为了民众主要的交通工具。
随着社会经济的发展,汽车逐步走入寻常家庭,自行车也一度淡出了人们的生活。
不过近些年来,随着“绿色低碳”的生活理念渐入人心,自行车运动开始展现出了自己独特的魅力,使其又成为了一项时尚的健身运动,越来越多的人参与到了其中。
自从我参加了骑行运动之后,便被其“挑战极限,积极向前”的魅力所深深吸引,深陷其中不可自拔。
业余折腾电子数码的时间也慢慢转向了自行车运动。
在参加一些骑行活动的过程中,常常会想记录一下自己的骑行路线、骑行数据,事后可以进行分析,作为训练数据也能使自己得到提高。
在一番寻找后发现智能手机上有提供这样功能的例如Endomondo应用供爱好者免费使用。
虽然智能手机现在已经非常普遍,但是智能手机的续航力以及国外应用与国内用户的使用习惯差异都是不小的问题。
再加上自行车运动存在一定的危险性以及需要适应不同的气候,一旦摔车,损坏智能手机的成本就会显得比较高。
因此我就想到了可以利用Arduino来做一个低成本专用的自行车车载电脑来记录并实时显示骑行数据,并在训练完成后使用电脑针对记录的数据进行分析,以得到想要的结果和报表。
在应用设计初期,就把这款应用分成了两大部分来进行设计,第一部分是基于arduino的硬件,体积小,可以安装在自行车的把横上,负责收集和记录骑行数据,并通过LCD显示屏实时显示时速等信息。
第二部分则是分析统计的系统,由于arduino的SRAM和频率的限制,不太适合做数据的分析,因此我把这部分功能拆分开来,设计成由计算机系统来完成——arduino记录的数据上传到计算机系统上后进行分析并绘制图表。
第二部分的系统,在后期设计中我设计成了一个Web2.0的应用。
这样就可以方便的将统计的结果进行分享,可以在任何地方给任何你想分享的伙伴分享你的训练数据、骑行路线。
在我设计并实现的原型产品中,基于arduino的硬件部分,主要由如下几个模块来构成:
∙arduino主控板,行车电脑的核心
∙电源模块,为所有硬件提供电源
∙GPS模块,提供GPS定位信息,以得到位置数据、速度数据、高度数据
∙LCD模块,实时显示骑行数据
∙SD/TF卡存储模块,储存骑行数据
在未来还可能会加上如下模块来进一步完善功能:
∙红外或磁感应模块,进行踏频统计
∙无线心率探测模块,心率数据统计
基于arduino的gTracking系统架构简图
在实际制作的过程中,由于对体积有小型化的要求,我选用了如下的硬件:
∙Arduinopromini,省去了RS232TTL转USB部分的电路,体积进一步缩小,ATMega328P也能保证有足够的Flash和SRAM。
∙3.7v转5v升压充电一体模块,去除了USB母口,缩小体积。
∙UC-915GPS模块,使用U-Blox6010芯片,带内置天线,3.5cmx1.6cmx0.75cm超小体积。
∙Nokia5110显示屏,84x48分辨率,够用,便宜,成本低,体积小。
∙自制TF存储模块,体积超小,带3.3V电源转换。
TF卡是工作在3.3v的电压下的,由于Arduinopromini上没有3.3v的电压输出,于是,在自制的TFT模块上,使用了AMS1117-3.3来将5v电源转成3.3v,同时这个3.3v的输出也为LCD模块提供了电源输入。
Arduino的SPIIO端口输入输出都是5V的TTL电平,因此需要一个levelshifter来将5V的电平信号转化成3.3v的以供TF卡使用。
在早期的设计中,我使用了74LVC245来做Levelshifter,但是由于需要尽量减小体积,即使SSOP封装的74LVC245也会显得较占空间。
考虑到负载电路并不复杂,于是在这里就用了简单的分压电路,使用1.8K和3.3K的贴片电阻实现了电平电压转换的功能。
TF卡模块PCB覆铜板用热转印草图
由于Nokia5110显示屏背面没有任何电子元件,于是我将包括arduinopromini、SD模块、GPS模块都用双面胶固定在了LCD显示屏的背面,整体的厚度可以做到小于1cm。
这样就完美的实现了缩小体积的目标。
面包板测试
将组件粘贴到LCD背面后的LCD模块
LCD背面粘贴的arduino和GPS、TF模块
电池部分选择了3.7V锂聚合物电池,这样就能把身材做的很小。
不过由于我只是做一个原型产品,所以用了手上现成的4200mAh的电池,体积显得略大了些。
准备好了硬件的部分后,就需要做连线焊接的工作了,下面是该应用设备使用的Arduino端口的规划表。
PIN0(RX)GPS模块TX
PIN1(TX)GPS模块RX
PIN2
PIN3Nokia5110LCD模块SCK
PIN4Nokia5110LCD模块MOSI
PIN5Nokia5110LCD模块A0
PIN6Nokia5110LCD模块Reset
PIN7
PIN8
PIN9
PIN10TF卡模块片选SS
PIN11TF卡模块MOSI
PIN12TF卡模块MISO
PIN13TF卡模块SCK
在这里,使用硬件Serial来作为GPSNMEA信号输入而不使用SoftSerial的好处是:
1.避免SoftSerial的兼容问题;
2.节省Flash的空间,减少SRAM使用。
而需要注意的是,Nokia5110LCD模块使用的是非标准的SPI通信协议,因此不能使用硬件SPI,而需要使用SoftSPI来驱动。
连接完成后就开始代码的编写工作了。
在这个项目中,GPS模块的驱动使用了TinyGPS库,LCD显示则使用了u8glib,TF卡模块驱动使用了SD库。
当然,为了节省SRAM,对库也进行了修改。
例如对TinyGPS的cardinal函数进行了修改,将数组使用PROGMEM进行存储,节省SRAM的空间。
整个系统的代码逻辑其实很简单。
初始化完成后,每秒检查一次GPS信号,如果信号正常则更新信息并在LCD屏幕更新显示的实时数据。
由于事后用于分析的数据不需要精确到每秒这样的级别,因此设定每5秒判断一次,如果当前位置和5秒前相比发生了一定的位移量则将数据记录到TF卡,以供分析。
为了简化数据存储的方式,数据以类似CSV的格式存储在TF卡上,文件名则为开始记录的日期,每一段数据以数据格式的版本号开始,每一行都是一笔数据。
格式如下:
日期,时间,连接的卫星个数,纬度,经度,海拔高度,时速,行驶方向,
流程图
下面是目前处于beta测试阶段的主程序的源代码,以供参考。
C代码:
1.#include"U8glib.h"
2.#include
3.#include
4.#include
5.
6.//使用PROGMEM存放GPS方向数组,节省SRAM
7. prog_chard_0[] PROGMEM= "N";
8. prog_chard_1[] PROGMEM= "NNE";
9. prog_chard_2[] PROGMEM= "NE";
10. prog_chard_3[] PROGMEM= "ENE";
11. prog_chard_4[] PROGMEM= "E";
12. prog_chard_5[] PROGMEM= "ESE";
13. prog_chard_6[] PROGMEM= "SE";
14. prog_chard_7[] PROGMEM= "SSE";
15. prog_chard_8[] PROGMEM= "S";
16. prog_chard_9[] PROGMEM= "SSW";
17. prog_chard_10[] PROGMEM= "SW";
18. prog_chard_11[] PROGMEM= "WSW";
19. prog_chard_12[] PROGMEM= "W";
20. prog_chard_13[] PROGMEM= "WNW";
21. prog_chard_14[] PROGMEM= "NW";
22. prog_chard_15[] PROGMEM= "NNW";
23.
24. PROGMEM const char *dir_table[] =
25. {
26. d_0,
27. d_1,
28. d_2,
29. d_3,
30. d_4,
31. d_5,
32. d_6,
33. d_7,
34. d_8,
35. d_9,
36. d_10,
37. d_11,
38. d_12,
39. d_13,
40. d_14,
41. d_15
42. };
43.
44.
45.TinyGPSgps;
46.U8GLIB_PCD8544u8g(3, 4, 99, 5, 6); //SPICom:
SCK=3,MOSI=4,CS=永远接地,A0=5,Reset=6
47.FilemyFile;
48.
49.booleansderror= false; //TF卡状态
50.char logname[13]; //记录文件名
51.booleanwritelog= true; //是否要记录当前数据到TF卡标志
52.booleanrefresh= false; //是否要更新液晶显示标志
53.booleanfinish_init= false; //初始化完成标志
54.
55.bytesatnum=0; //连接上的卫星个数
56.float flat,flon,spd,alt,oflat,oflon; //GPS信息,经纬度、速度、高度、上一次的经纬度
57.unsigned long age; //GPS信息fixage
58.int year; //GPS信息年
59.bytemonth,day,hour,minute,second,hundredths; //GPS信息时间信息
60.char crs[4]; //GPS信息行驶方向
61.char sz[10]; //文本信息
62.bytecnt=0; //循环计数器
63.
64.
65.static void gpsdump(TinyGPS&gps);
66.static boolfeedgps();
67.static void print_date(TinyGPS&gps);
68.static void print_satnum(TinyGPS&gps);
69.static void print_pos(TinyGPS&gps);
70.static void print_alt(TinyGPS&gps);
71.static void print_speed(TinyGPS&gps);
72.static void print_course(TinyGPS&gps);
73.static String float2str(float val,bytelen);
74.
75.
76.//AVR定时器,每秒触发
77.ISR(TIMER1_OVF_vect) {
78. TCNT1=0x0BDC; //setinitialvaluetoremovetimeerror(16bitcounterregister)
79. if (finish_init) refresh= true; //在完成初始化后,将刷新显示标志设为true
80.}
81.
82.
83.void setup()
84.{
85.
86. finish_init= false;
87.
88. //设置并激活AVR计时器
89. TIMSK1=0x01; //启用全局计时器中断
90. TCCR1A=0x00; //normaloperationpage148(mode0);
91. TCNT1=0x0BDC; //setinitialvaluetoremovetimeerror(16bitcounterregister)
92. TCCR1B=0x04; //启动计时器
93.
94. pinMode(10,OUTPUT);
95.
96. oflat= 0;
97. oflon= 0;
98. logname[0]='';
99.
100. Serial.begin(9600); //GPS模块默认输出9600bps的NMEA信号
101.
102. u8g.setColorIndex
(1); //设置LCD显示模式,黑白
103. u8g.setFont(u8g_font_04b_03br); //字体
104.
105. //TF卡的片选端口是10
106. if (!
SD.begin(10)) {
107. sderror= true;
108. }
109.
110. //初始化完成
111. finish_init= true;
112.}
113.
114.void loop()
115.{
116. //读取并分析GPS数据
117. feedgps();
118. //刷新显示
119. if (refresh)
120. {
121. cnt%=10;
122. writelog= true;
123.
124. u8g.firstPage();
125. do{
126. gpsdump(gps);
127. u8g.setPrintPos(70,48);
128. u8g.print( cnt);
129. } while ( u8g.nextPage() );
130.
131. //每5秒且GPS信号正常时将数据记录到TF卡
132. if (cnt% 5 == 0 &&writelog)
133. {
134. logEvent();
135. }
136.
137. //刷新完毕,更新秒计数器
138. refresh= false;
139. cnt++;
140. }
141.}
142.
143.static void gpsdump(TinyGPS&gps)
144.{
145. print_satnum(gps);
146. print_date(gps);
147. print_pos(gps);
148. print_speed(gps);
149. print_alt(gps);
150. print_course(gps);
151.}
152.
153.//更新并显示卫星个数
154.static void print_satnum(TinyGPS&gps)
155.{
156. satnum=gps.satellites();
157. if ( satnum!
=TinyGPS:
:
GPS_INVALID_SATELLITES){
158. u8g.setPrintPos( 46, 6);
159. u8g.print(satnum);
160. writelog&= true;
161. }
162. else {
163. u8g.drawStr( 10, 15,F("gTrackingSystem"));
164. u8g.drawStr( 22, 23,F("build2506"));
165. u8g.drawStr( 0, 34, (cnt% 2) ?
F("Searching...") :
F(" "));
166. //u8g.drawStr(7,48,F(""));
167. writelog= false;
168. }
169. feedgps();
170.}
171.
172.//更新并显示GPS经纬度信息
173.static void print_pos(TinyGPS&gps)
174.{
175. gps.f_get_position(&flat,&flon,&age);
176.
177. if (flat!
=TinyGPS:
:
GPS_INVALID_F_ANGLE &&flon!
= TinyGPS:
:
GPS_INVALID_F_ANGLE) {
178. u8g.setPrintPos(0,40);
179. u8g.print(float2str(flon,8));
180. u8g.print(F(":
"));
181. u8g.print(float2str(flat,8));
182. writelog&= true;
183. }
184. else
185. writelog= false;
186.
187. feedgps();
188.}
189.
190.//更新并显示GPS高度信息
191.static void print_alt(TinyGPS&gps)
192.{
193. alt=gps.f_altitude();
194.
195. if (alt!
=TinyGPS:
:
GPS_INVALID_F_ALTITUDE){
196. u8g.setPrintPos(0,48);
197. u8g.print(F("Alt:
"));
198. u8g.print(float2str(alt,5));
199. writelog&= true;
200. }
201. else
202. {
203. writelog= false;
204. }
205. feedgps();
206.}
207.
208.//更新并显示行驶方向
209.static void print_course(TinyGPS&gps)
210.{
211. if (gps.f_course() ==TinyGPS:
:
GPS_INVALID_F_ANGLE)
212. writelog= false;
213. else
214. {
215. //从PROGMEM中读取数组中的字符串
216. strcpy_P(crs, (char*)pgm_read_word(&(dir_table[gps.cardinal(gps.f_course())])));
217. u8g.setPrintPos(60,6);
218. u8g.print(crs);
219. }
220. feedgps();
221.}
222.
223.//更新并显示GPS时间
224.static void print_date(TinyGPS&gps)
225.{
226. gps.crack_datetime(&year,&month,&day,&hour,&minute,&second,&hundredths,&age);
227. if (age!
=TinyGPS:
:
GPS_INVALID_AGE &&month>0 &&day>0)
228. {
229. u8g.setPrintPos( 0, 6);
230. sprintf(sz, "%02d",((hour+8) % 24)); //显示北京时间GMT+8
231. u8g.print(sz);
232. u8g.setPrintPos( 11, 6);
233. u8g.print(second% 2 ?
F(":
") :
F(""));
234. u8g.setPrintPos(15,6);
235. sprintf(sz, "%02d",minute);
236. u8g.print(sz);
237. writelog&= true;
238. }
239. else
240. writelog= false;
241. feedgps();
242.}
243.
2