www.pudn.com > LPCARM_GPS_stat_machine.rar > main.c, change:2007-01-28,size:19533b
/*--------------------------------------------------------------------------
LPCARM之GPS应用及使用状态机解码祥解
本程序在周立功的LPC213x开发板上调试通过,用汽车电子的串口通V3.22进行输出测试。
本程序是在FIFO和RTC例程的基础上追加GPS解码部分构成的。
注:只对常用的GPRMC和GPGGA语句进行了解码。
本程序主要是熟悉NMEA0183语句及使用状态机解码的优缺点,以及与传统解码程序的不同点。
菜地公告:引用本文必须注明出处!!!
菜农HotPower 2007.1.28 20:55 于西安大雁塔菜地 http://HotPower.21ic.org/
---------------------------------------------------------------------------*/
#include
#include
#include "LPC213xdef.h" //水鸟HotPower的LPCARM菜鸟作
#include "main.h" //配置头文件
void __swi(0) Enable(void);//同时开放FIQ/IRQ中断
void __SWI_0 (void) {
int tmp;
__asm
{
MRS tmp, SPSR
BIC tmp, tmp, #0xc0
MSR SPSR_c, tmp
}
}
void __swi(1) Disable(void);//同时关闭FIQ/IRQ中断
void __SWI_1 (void) {
int tmp;
__asm
{
MRS tmp, SPSR
ORR tmp, tmp, #0xc0
MSR SPSR_c, tmp
}
}
void IRQ_BOD(void) __irq
{
POWER->P_CONP &= ~(1 << PCRTC);
VIC->IntEnable = 0;
VIC->SoftIntClr = 0xffffffff;//清除所有软中断标志
VIC->IntSelect = 0;//全部中断为IRQ中断或默认中断
VIC->VectAddr = 0; // 向量中断结束
POWER->P_CON = (1 << PD);//掉电
}
void IRQ_RTC(void) __irq
{
Rtc.Sec = RTC->RTC_SEC;
if (Rtc.Sec == 0) {
Rtc.Min = RTC->RTC_MIN;
if (Rtc.Min == 0) {
Rtc.Hour = RTC->RTC_HOUR;
if (Rtc.Hour == 0) {
Rtc.Doy = RTC->RTC_DOY;
if (Rtc.Doy == 1) {
Rtc.Month = RTC->RTC_MONTH;
if (Rtc.Month == 1) {
Rtc.Year = RTC->RTC_YEAR;
}
}
}
}
}
RTC->RTC_ILR = (1 << RTCALF) | (1 << RTCCIF); // 清除RTC增量和报警中断标志
VIC->VectAddr = 0; // 向量中断结束
}
/*-----------------------------------
串口0中断服务程序
-----------------------------------*/
void IRQ_UART0 (void) __irq
{
unsigned int i;
unsigned char ch;
switch(U0->IIR & 0x0f) {
case 0x06://接收线状态
switch (U0->LSR) {
case 0x63:
break;
}
break;
/*-----------------------------------------------------------------
串口接收部分代码
用while循环从FIFO中快速地取出全部数据,也可不用循环每次取出一个字节
的数据但达不到FIFO的功效。
-----------------------------------------------------------------*/
case 0x04://接收数据可用
case 0x0c://字符超时指示
while (U0->LSR & 0x01) {//从FIFO中取出全部数据
ch = U0->RBR;//从FIFO中取出1个字符的数据
Uart.RxBuffer[Uart.RxCount ++] = ch;//暂存入缓冲区
/*-----------------------------------------------------------------
串口接收解码程序激活部分代码
当接收字符为换行符后,软件激活用户软件中断IRQ_WriteRTC(),
进入命令解码分析及执行
-----------------------------------------------------------------*/
if (ch == '\n') {//时间设置和NMEA0183语句的结尾符都是\r\n
VIC->SoftInt = (1 << VICIntSel_SoftInt22);//激活Time/GPS解码程序
}
}
break;
/*-----------------------------------------------------------------
串口发送激活部分代码
由于U0->IIR & 0x0f==1时为LPCARM保留中断,可用于软件模拟激活UART0中断
利用此漏洞来实现非典的思想来达到实战的要求。
-----------------------------------------------------------------*/
case 0x01://LPCARM保留中断,可用于软件模拟激活UART0中断
if (!(VIC->SoftInt & (1 << VICIntSel_UART0))) {//硬件UART0中断
break;//正常的UART0中断退出
}
//至此成功利用了软件模拟中断偷入敌阵~~~继续运行,以达到发送首字符的目的
/*-----------------------------------------------------------------
串口发送部分代码
这里主要是充分地利用LPCARM的16个字节的FIFO来实现“无限FIFO”
-----------------------------------------------------------------*/
case 0x02://THRE中断
Uart.TxBusy = Uart.TxCount != Uart.TxdCount;//保证FIFO发送全部结束时,缓冲区空不拒绝发送
for (i = 0; (i < 16) && (Uart.TxCount != Uart.TxdCount); i ++) {//1次写入FIFO最多16个字节
ch = Uart.TxBuffer[Uart.TxdCount ++];//取出缓冲区1个字节数据
U0->THR = ch;//将缓冲区1个字节数据写入FIFO
}
VIC->SoftIntClr = (1 << VICIntSel_UART0);//这里必须清除此软件标志!!!
break;
}
VIC->VectAddr = 0x00; /* 通知VIC中断处理结束 */
}
/*-----------------------------------------
串口命令解码分析及执行(使用状态机)
------------------------------------------*/
void IRQ_WriteRTC(void) __irq
{
unsigned int state = 0, recno = 0;//状态机初始化
unsigned char ch, crc, sum = 0;
unsigned int i, val;
unsigned char str[256];//虽然浪费,但算法简单
unsigned char count;
unsigned char prnstrg[] = "GPS数据校验成功!!!\r\n";
unsigned char prnstrb[] = "GPS数据校验失败!!!\r\n";
while (Uart.RxdCount != Uart.RxCount) {
ch = Uart.RxBuffer[Uart.RxdCount ++];
switch (state) {
case 0://同步状态
if (ch == '$') {//在字符串中搜索'$'
state = 1;//搜索成功,进入命令分析状态
crc = 0;
}
count = 0;
break;
case 1://命令分析状态
crc ^= ch;
if (ch != ',') {//$Date,2007-01-28,$Time,11:11:11,$Week,0
str[count ++] = ch;//合法数据
}
else {//命令分析开始
switch(str[0]) {
case 'D'://Date
if ((str[1] == 'a') && (str[2] == 't') && (str[3] == 'e')) {
state = 2;//进入Date命令分析状态
count = 0;
}
else {
state = 0;//Date解码失败!!!
}
break;
case 'T'://Time
if ((str[1] == 'i') && (str[2] == 'm') && (str[3] == 'e')) {
state = 3;//进入Time命令分析状态
count = 0;
}
else {
state = 0;//Time解码失败!!!
}
break;
case 'W'://Week
if ((str[1] == 'e') && (str[2] == 'e') && (str[3] == 'k')) {
state = 4;//进入Week命令分析状态
count = 0;
}
else {
state = 0;//Week解码失败!!!
}
break;
case 'G'://只分析GPRMC和GPGGA语句
if (str[1] == 'P') {
if (str[2] == 'R') {//可能是GPRMC语句
if ((str[3] == 'M') && (str[4] == 'C')) {
state = 5;//进入GPRMC语句分析状态
count = 0;
recno = 1;//11
}
else {
state = 0;//GPRMC语句解码失败!!!
}
}
else if (str[2] == 'G') {//可能是GPGGA语句
if ((str[3] == 'G') && (str[4] == 'A')) {
state = 6;//进入GPGGA语句分析状态
count = 0;
recno = 1;//12
}
else {
state = 0;//GPGGA语句解码失败!!!
}
}
else {
state = 0;//GPS解码失败!!!
}
}
else {
state = 0;//GPS解码失败!!!
}
break;
default://其他命令出错
state = 0;//失败!!!
}
}
break;
case 2://Date命令分析状态,例$Date,2007-01-28
if ((ch == '-') || ((ch >= '0') && (ch <= '9'))){
str[count ++] = ch;//合法数据
}
if ((count > 10) || (ch == '\n')) {
if ((count == 10) && (ch == '\n')) {
RTC->RTC_CCR = 0;
val = 0;
for (i = 0; i < 4; i ++) {
ch = str[i];
val = val * 10 + (ch - '0');
}
if (val < 10000) {//万年历,严防万年虫出现
RTC->RTC_YEAR = val;
}
val = 0;
for (i = 5; i < 7; i ++) {
ch = str[i];
val = val * 10 + (ch - '0');
}
if ((val >= 1) && (val <= 12)) {//倒塌了,LPCARM连此都不校验
RTC->RTC_MONTH = val;
}
val = 0;
for (i = 8; i < 10; i ++) {
ch = str[i];
val = val * 10 + (ch - '0');
}
if ((val > 0) && (val <= GetDom(RTC->RTC_YEAR, RTC->RTC_MONTH))) {//合法日
RTC->RTC_DOY = val;//允许改写
}
//LPCARM的RTC只能在0时星期加1且不校对,故增加星期自动调节功能。
val = GetDow(RTC->RTC_YEAR, RTC->RTC_MONTH, RTC->RTC_DOY);//计算某日的星期数
if (val != RTC->RTC_DOW) {
RTC->RTC_DOW = val;//星期纠错
}
RTC->RTC_CCR = (1 << CLKEN) | (1 << CLKSRC);//启动RTC
}
state = 0;//成功结束或失败!!!
}
break;
case 3://Time命令分析状态,例$Time,11:11:11
if ((ch == ':') || ((ch >= '0') && (ch <= '9'))){
str[count ++] = ch;//合法数据
}
if ((count > 8) || (ch == '\n')) {
if ((count == 8) && (ch == '\n')) {
RTC->RTC_CCR = 0;
val = 0;
for (i = 0; i < 2; i ++) {
ch = str[i];
val = val * 10 + (ch - '0');
}
RTC->RTC_HOUR = val % 24;
val = 0;
for (i = 3; i < 5; i ++) {
ch = str[i];
val = val * 10 + (ch - '0');
}
RTC->RTC_MIN = val % 60;
val = 0;
for (i = 6; i < 8; i ++) {
ch = str[i];
val = val * 10 + (ch - '0');
}
RTC->RTC_SEC = val % 60;
RTC->RTC_CCR = (1 << CLKEN) | (1 << CLKSRC);// 启动RTC
}
state = 0;//成功结束或失败!!!
}
break;
case 4://Week命令分析状态,例$Week,0
if ((ch == ':') || ((ch >= '0') && (ch <= '9'))){
str[count ++] = ch;//合法数据
}
if ((count > 1) || (ch == '\n')) {
if ((count == 1) && (ch == '\n')) {
RTC->RTC_CCR = 0;
ch = str[0];
val = ch - '0';
RTC->RTC_DOW = val;//故意没校验,主要是验证$Date命令的正确性
RTC->RTC_CCR = (1 << CLKEN) | (1 << CLKSRC);// 启动RTC
}
state = 0;//成功结束或失败!!!
}
break;
case 5://GPRMC语句分析状态
/*----------------------------------------------------------------------
NMEA0183语句(推荐定位信息(GPRMC))
$GPRMC,030545,A,3414.2522,N,10853.9838,E,0.0,0.0,300305,2.5,W*61
NMEA0183语句翻译
$GPRMC, 讯息代码
<1> 030545, UTC 时间 北京时间=UTC 时间+8小时=11:05:45
<2> A, 定位状态, A=有效定位,V=无效定位
<3> 3414.2522, 纬度 北纬34.142522度
<4> N, 纬度半球N(北半球)或S(南半球)
<5> 10853.9838, 经度 东经108.539838度
<6> E, 经度半球E(东经)或W(西经)
<7> 0.0, 地面速率 0.0
<8> 0.0, 地面航向 0.0
<9> 300305, UTC 日期 2005年03月30日
<10>2.5, 磁偏角 2.5度
<11>W 磁偏角方向 E(东)或W(西)
*61 总和检查码 *61
----------------------------------------------------------------------*/
if (ch > ' '){
if (ch == '*') {//GPRMC语句解码参数部分结束
state = 7;//进入GPRMC语句校验分析状态
str[count ++] = 0;//打印结束串
}
else {
crc ^= ch;
if (ch != ',') {
str[count ++] = ch;
}
else {
str[count ++] = 0;//打印结束串
}
}
if (str[count - 1] == 0) {//需要打印
switch(recno) {
case 1://030545, UTC 时间 北京时间=UTC 时间+8小时=11:05:45
break;
case 2://A, 定位状态, A=有效定位,V=无效定位
break;
case 3://3414.2522, 纬度 北纬34.142522度
break;
case 4://N, 纬度半球N(北半球)或S(南半球)
break;
case 5://10853.9838, 经度 东经108.539838度
break;
case 6://E, 经度半球E(东经)或W(西经)
break;
case 7://0.0, 地面速率 0.0
break;
case 8://0.0, 地面航向 0.0
break;
case 9://300305, UTC 日期 2005年03月30日
break;
case 10://2.5, 磁偏角 2.5度
break;
case 11://W 磁偏角方向 E(东)或W(西)
break;
}
recno ++;
count = 0;
}
}
else {//防止\r\n死锁
state = 0;//
}
break;
case 6://GPGGA语句分析状态
/*----------------------------------------------------------------------
NMEA0183语句(卫星定位信息(GPGGA))
$GPGGA,030545,3414.2522,N,10853.9838,E,1,05,2.9,442.2,M,-29.4,M,,*69
NMEA0183语句翻译
$GPGGA, 讯息代码
<1> 030545, UTC 时间 北京时间=UTC 时间+8小时=11:05:45
<2> 3414.2522, 纬度 北纬34.142522度
<3> N, 纬度半球N(北半球)或S(南半球)
<4> 10853.9838, 经度 东经108.539838度
<5> E, 经度半球E(东经)或W(西经)
<6> 1, GPS 状态:0=未定位,1=非差分定位,2=差分定位,6=正在估算
<7> 05, 正在使用解算位置的卫星数量(00~12)(前面的0 也将被传输)
<8> 2.9, HDOP水平精度因子(0.5~99.9)
<9> 442.2, 海拔高度442.2公尺(-9999.9~99999.9)
<10> M, 海拔高度单位公尺
<11> -29.4, 地表平均高度-29.4公尺
<12> M, 地表平均高度单位公尺
<13> ,
*69 总和检查码 *69
----------------------------------------------------------------------*/
if (ch > ' '){
if (ch == '*') {//GPRMC语句解码参数部分结束
state = 7;//进入GPRMC语句校验分析状态
str[count ++] = 0;//打印结束串
}
else {
crc ^= ch;
if (ch != ',') {
str[count ++] = ch;
}
else {
str[count ++] = 0;//打印结束串
}
}
if (str[count - 1] == 0) {//需要打印
switch(recno) {
case 1://030545, UTC 时间 北京时间=UTC 时间+8小时=11:05:45
break;
case 2://3414.2522, 纬度 北纬34.142522度
break;
case 3://N, 纬度半球N(北半球)或S(南半球)
break;
case 4://10853.9838, 经度 东经108.539838度
break;
case 5://E, 经度半球E(东经)或W(西经)
break;
case 6://1, GPS 状态:0=未定位,1=非差分定位,2=差分定位,6=正在估算
break;
case 7://05, 正在使用解算位置的卫星数量(00~12)(前面的0 也将被传输)
break;
case 8://2.9, HDOP水平精度因子(0.5~99.9)
break;
case 9://442.2, 海拔高度442.2公尺(-9999.9~99999.9)
break;
case 10://M, 海拔高度单位公尺
break;
case 11://-29.4, 地表平均高度-29.4公尺
break;
case 12://M, 地表平均高度单位公尺
break;
case 13:
break;
case 14:
break;
}
recno ++;
count = 0;
}
}
else {//防止\r\n死锁
state = 0;//
}
break;
case 7://GPRMC/GPGGA语句校验分析状态
if (((ch >= '0') && (ch <= '9')) || ((ch >= 'A') && (ch <= 'F'))) {
str[count ++] = ch;//合法数据
}
else if (ch == '\r') {
ch = str[count - 2];
if ((ch >= '0') && (ch <= '9')) ch -= '0';
else ch -= 'A' - 10;
sum = ch << 4;
ch = str[count - 1];
if ((ch >= '0') && (ch <= '9')) ch -= '0';
else ch -= 'A' - 10;
sum |= ch;
if (sum == crc) {
state = 8;//
}
else {
ch = crc >> 4;
if (ch <= 9) ch += '0';
else ch += 'A' - 10;
Uart.TxBuffer[Uart.TxCount ++] = ch;
ch = crc & 0x0f;
if (ch <= 9) ch += '0';
else ch += 'A' - 10;
Uart.TxBuffer[Uart.TxCount ++] = ch;
for (i = 0; prnstrb[i] != 0; i ++) {
Uart.TxBuffer[Uart.TxCount ++] = prnstrb[i];
}
if (!Uart.TxBusy) {//发送器不忙可以立即发送
VIC->SoftInt = (1 << VICIntSel_UART0);//模拟51的TI=1来引发发送中断
}
state = 0;//校验失败!!!
}
}
else {
state = 0;//
}
break;
case 8:
if (ch == '\n') {
for (i = 0; prnstrg[i] != 0; i ++) {
Uart.TxBuffer[Uart.TxCount ++] = prnstrg[i];
}
if (!Uart.TxBusy) {//发送器不忙可以立即发送
VIC->SoftInt = (1 << VICIntSel_UART0);//模拟51的TI=1来引发发送中断
}
state = 0;//成功解码
}
break;
default://其他状态出错
state = 0;
}
}
VIC->SoftIntClr = (1 << VICIntSel_SoftInt22);
VIC->VectAddr = 0; // 向量中断结束
}
void SystemInit(void)
{
volatile unsigned int start;
for (start = 0; start < 10000; start ++);
if (SystemRamTest != 0x55aa) {
SystemRamTest = 0x55aa;
}
VicInit();
PortInit();
UartInit();
RtcInit();
}
void VicInit(void)
{
unsigned int i;
VIC->IntEnable = 0;
VIC->SoftIntClr = 0xffffffff;//清除所有软中断标志
VIC->IntSelect = 0;//全部中断为IRQ中断或默认中断
for(i = 0;i < 16;i++) {
VIC->VectCntls[i] = 0;
VIC->VectAddrs[i] = 0;
}
}
void PortInit(void)
{
PINSEL->PIN_SEL0 = 0x00000000; // 设置管脚连接GPIO
PINSEL->PIN_SEL1 = 0x00000000; // 设置管脚连接GPIO
PINSEL->PIN_SEL2 = 0x00000000; // 设置管脚连接GPIO
}
void UartInit(void)
{
unsigned int Fdiv;
Uart.TxCount = 0;
Uart.RxCount = 0;
Uart.TxdCount = 0;
Uart.RxdCount = 0;
Uart.TxBusy = 0;
PINSEL->PIN_SEL0 |= (P0_0_TXD0 << P0_0_PINSEL) | (P0_1_RXD0 << P0_1_PINSEL); //设置I/O连接到UART0
U0->LCR = 0x83;// DLAB=1,允许设置波特率
Fdiv = (Fpclk / 16) / UART_BPS;// 设置波特率38400
U0->DLM = Fdiv / 256;
U0->DLL = Fdiv % 256;
U0->LCR = 0x03;
U0->FCR = 0xc7;/* 初始化FIFO 接收14个字节就中断*/
U0->IER = 0x07;/* 允许接收发送中断 */
VIC->VectAddrs[0] = (unsigned int)IRQ_BOD;
VIC->VectCntls[0] = VICIntSel_Enable
| VICIntSel_BOD;
VIC->VectAddrs[1] = (unsigned int)IRQ_UART0;
VIC->VectCntls[1] = VICIntSel_Enable//使能IRQ中断
| VICIntSel_UART0;//获取UART0的IRQ级别
VIC->IntEnable |= (1 << VICIntSel_UART0)//使能UART0中断
| (1 << VICIntSel_BOD);
}
void RtcInit(void)
{
POWER->P_CONP |= (1 << PCRTC);
RTC->RTC_CCR = 0;
RTC->RTC_CIIR = (1 << IMSEC); // 设置秒值的增量产生一次中断
RTC->RTC_ILR = (1 << RTCALF) | (1 << RTCCIF); // 清除RTC增量和报警中断标志
RTC->RTC_CCR = (1 << CLKEN) | (1 << CLKSRC); // 启动RTC
VIC->VectAddrs[2] = (unsigned int)IRQ_RTC;
VIC->VectCntls[2] = VICIntSel_Enable//使能RTC中断
| VICIntSel_RTC;//获取RTC的IRQ级别
VIC->VectAddrs[3] = (unsigned int)IRQ_WriteRTC;
VIC->VectCntls[3] = VICIntSel_Enable
| VICIntSel_SoftInt22;
VIC->IntEnable |= (1 << VICIntSel_RTC)//使能RTC中断
| (1 << VICIntSel_SoftInt22);
Rtc.Year = RTC->RTC_YEAR;
Rtc.Month = RTC->RTC_MONTH;
Rtc.Doy = RTC->RTC_DOY;
Rtc.Hour = RTC->RTC_HOUR;
Rtc.Min = RTC->RTC_MIN;
Rtc.Sec = RTC->RTC_SEC;
}
/*----------------------------------------------------------------------------------------
0000年~9999年星期算法(菜农自创)
-----------------------------------------------------------------------------------------*/
unsigned int GetDow(unsigned int y, unsigned int m, unsigned int d)
{
unsigned int w, c;
if (m <= 2){
m |= 4;//1月2月同5月六月表
y--;
}
c = y / 100;
c &= 0x03;//百年%4
y %= 100;
w = ((c | (c << 2)) + (y + (y >> 2)) + (13 * m + 8)/ 5 + d) % 7;//(星期=百年%4*5+年+年/4+(13*月+8)/5+日)%7
return w;//返回星期
}
/*----------------------------------------------------------------------------------------
附赠0000年~9999年月最大天数算法(菜农自创)
-----------------------------------------------------------------------------------------*/
int GetDom(unsigned int y, unsigned int m)
{
int dn;
dn = GetDow(y, m + 1, 1) - GetDow(y, m, 1);//m+1=13表示明年的1月
if (dn < 0) dn += 7;
return dn + 28;//返回当月的最大天数
}
int main(void)
{
Disable();//关中断
SystemInit();//系统初始化
Enable();//开中断
while(1) {//本例主要是演示串口的FIFO,主循环不想做其他目的。
POWER->P_CON = (1 << IDL);//_idle_() 休眠,串口中断可以苏醒。
}
}