文号
949829
阅读
446
回复
5
收藏
0
所有教程由网友发布,仅供参考,请谨慎采纳。科创不对教程的科学性、准确性、可靠性负责。
RP2040学习之GPS速度计
2026/04/28
IP:北京

最近动手做了个GPS速度计,测骑马的速度,没什么技术含量,可给RP2040初学者练手之用。因为这种板子可以直接拷执行代码,有想要自己攒一个来玩或送人也很简单,焊上几根线,拷一下代码就跑起来了,不需要开发环境和编译。能实时显示速度,显示4分钟内的最大速度,显示卫星数,实时语音报速度,单片机U盘存每秒的速度值,数据包含日期时间信息,可接手机或电脑查看拷贝。

硬件上,包含一个RP2040,一个18Hz刷新率的ublox协议的GPS,一个OLED显示,一个语音芯片和手机喇叭报速度,还可以配上小电池和充电板,装到一个塑料盒子里。注意OLED要1306芯片驱动的,不同驱动芯片不通用。

Screenshot_20260428_180753.jpgScreenshot_20260428_180454.jpgScreenshot_20260428_180420.jpgScreenshot_20260428_180653.jpgScreenshot_20260428_180644.jpg

Screenshot_20260428_180605.jpg

直接拷代码的话,就是按住单片机上的小boot键,插到电脑USB,插好后才放开boot键,电脑上会出现个U盘,吧这个uf2文件拷进去就能运行起来了。

attachment iconGPS_Speed_v22_Snd.ino.uf2234.50KBUF23次下载

可以先拷程序再焊接各个零件,比如焊上OLED后通电马上就应该能看到显示。GPS插头左数,第1根线接单片机GND脚,第2根线接单片机5V脚(实际锂电正极3.8V左右),第3根线接单片机12脚,第4根线接单片机13脚。OLED除了电源线分别接GND和5V外,SCL接单片机27脚,SDA接单片机26脚,语音芯片除电源要连好外,信号输入脚接单片机4脚,PWM1、2接喇叭。

IMG_20260416_200841_edit_2176422080972068.jpg

IMG_20260428_183225.jpg


代码如下:

1,包含各种库;

2,定义变量数组;

3,设置GPS串口及中断;

4,OLED显示,主循环就是一直在OLED循环显示,然后会被GPS串口“数据准备好了”所中断,获取并刷新显示速度;

5,设置U盘存数,见以前帖子,自动累加生成文件名,每4分钟存一个文本文件,文件开头还会写入从GPS获取的日期时间信息;

6,语音驱动,发送N个短脉冲,可以触发第N段语音,该芯片可以定制语音,不需要额外费用;

7,其他功能,如滚动显示速度曲线,最大速度计算等,18Hz采样的速度值会进行滑动平均滤波获得每秒的速度值。

#include <Arduino.h> // 引入 Arduino 核心头文件

#include <stdio.h>

#include <pico/stdlib.h>

#include <hardware/divider.h>

#include <hardware/timer.h>

#include <hardware/clocks.h>

#include <SPI.h>

#include <Wire.h>

#include "pico/binary_info.h"

#include <hardware/i2c.h>

#include "hardware/uart.h"

#include "hardware/irq.h"

#include <SoftwareSerial.h>

#include <FatFS.h>

#include <FatFSUSB.h>

#include <Adafruit_ssd1306syp.h>

Adafruit_ssd1306syp display(26,27); //(SDA_PIN,SCL_PIN),注意OLED要占用大量内存!

uint16_t Yr;

uint8_t SA,Mt,Dy,Hr,Mn,Sd;

int32_t X,Y,Z;

int32_t Vx = 0;

int32_t Vy = 0;

int32_t Vz = 0;

int32_t Vg=0;

int32_t mVx = 0;

int32_t mVy = 0;

int32_t mVz = 0;

uint32_t eV;

int16_t mVg=0;

int16_t maxV=0;

int16_t V0[250]={0}; //各时间点的速度,总时长300秒;

uint8_t dpV[260]={0};

unsigned char GPS_buf[75];

int32_t Vxs[20]={0}; //每1秒内的18个速度值;

int32_t Vys[20]={0};

int32_t Vzs[20]={0};

char buff[32];

int i=0;

int Snd0=6; //语音输出标志

int Snd_V;

int Hsnd=0; //十位数已报标志

int j=0;

int sni = 1; //时间秒数计数

int fln = 0; //文件数每120秒增1

int dati = 0; //每个文件的120个数据号

int Wi=0;

int dTm,Tm0,dpi;

int nRun = 0; //记录次数,每次开机增1

void setup() {

set_sys_clock_khz(66500,true);

//使用Uart0串口及中断读GPS

uart_init(uart0, 115200); // 初始化 UART

gpio_set_function(12, GPIO_FUNC_UART); // 设置 GPIO 引脚为 UART 功能(例如 GP0 作为 TX,GP1 作为 RX)

gpio_set_function(13, GPIO_FUNC_UART);

gpio_pull_up(13); // 确保 RX 引脚上拉,避免悬空输入

uart_set_irqs_enabled(uart0, true, false); // 启用 UART 中断,读数中断,写数不中断

irq_set_exclusive_handler(UART0_IRQ, GPS_Read); // 设置GPS读数中断处理函数

irq_set_enabled(UART0_IRQ, true); // 启用串口中断

delay(1000);

pinMode(4, OUTPUT);

pinMode(5, OUTPUT);

Serial.begin(115200); //主串口用于计算机通信

display.initialize(); //OLED初始化

FatFSUSB.end();

FatFS.begin();

delay(1000);

//累加Run次数,每次重启加一次

File f2 = XXXXXXXXXXXXXXXXX("NRun.txt", "r");

if (f2) {

bzero(buff, 32);//将内存块(字符串)的前32个字节清零

XXXXXXXXXXXXXX((uint8_t *)buff, 31);

sscanf(buff, "%d", &nRun);

}

f2.close();

FatFS.end();

FatFSUSB.begin();

while(SA<7 || eV>1000){ //先搜星再进行下一步

//实时显示

display.clear();

display.update();

delay(200);

display.setTextSize(5);

display.setTextColor(1);

display.setCursor(0,0);

display.print("SA:");

display.print(SA);

display.update();

delay(200);

}

//语音

for (i=0;i<24;i++){ //24->“你好”

digitalWrite(4, HIGH);

delayMicroseconds(100);

digitalWrite(4, LOW);

delayMicroseconds(100);

}

delay(800);

for (i=0;i<(SA%10+2);i++){ //卫星数,11->9,12->十

digitalWrite(4, HIGH);

delayMicroseconds(100);

digitalWrite(4, LOW);

delayMicroseconds(100);

}

delay(800);

for (i=0;i<25;i++){ //25->“谢谢”

digitalWrite(4, HIGH);

delayMicroseconds(100);

digitalWrite(4, LOW);

delayMicroseconds(100);

}

delay(800);

FatFSUSB.end();

FatFS.begin();

//累加Run次数,每次重启加一次

nRun++;

if(nRun>20){

nRun=1;

}

sprintf(buff, "%d\n", nRun);

f2 = XXXXXXXXXXXXXXXXX("NRun.txt", "w");

f2.write(buff, strlen(buff));

f2.close();

Tm0=millis(); //计时开始

}

// GPS读数中断函数

void GPS_Read() {

while (uart_is_readable(uart0)) {

uint8_t ch = uart_getc(uart0); // 读取一个字

if (ch==0xB5){

ch = uart_getc(uart0); // 读取一个字

if (ch==0x62){

uint8_t msgClass = uart_getc(uart0);

uint8_t msgId = uart_getc(uart0);

uint8_t length = (uart_getc(uart0));

if (msgClass 0x01 && msgId 0x07) { // NAV-POSLLH消息

for(uint8_t i=0;i<73;i++){

GPS_buf[i] = uart_getc(uart0);

}

Yr = (GPS_buf[6]<<8)|GPS_buf[5];

Mt = GPS_buf[7];

Dy = GPS_buf[8];

Hr = GPS_buf[9]+8;

Mn = GPS_buf[10];

Sd = GPS_buf[11];

SA = GPS_buf[24];

Vx = (GPS_buf[52]<<24)|(GPS_buf[51]<<16)|(GPS_buf[50]<<8)|GPS_buf[49]; //单点新速度不超过均值+/- 1 m/s

Vy = (GPS_buf[56]<<24)|(GPS_buf[55]<<16)|(GPS_buf[54]<<8)|GPS_buf[53];

Vz = (GPS_buf[60]<<24)|(GPS_buf[59]<<16)|(GPS_buf[58]<<8)|GPS_buf[57];

eV = (GPS_buf[72]<<24)|(GPS_buf[71]<<16)|(GPS_buf[70]<<8)|GPS_buf[69];

Vg = sqrt(Vx*Vx+Vy*Vy+Vz*Vz); //单点地速mm/s

if (SA<7 || eV>max(0.1*Vg,1000)){ //卫星数少于8,地速误差大于5%的数据排除,但小于1m/s不排除

Vx = 0.7*mVx;

Vy = 0.7*mVy;

Vz = 0.7*mVz;

}

//18次采样滑动平均滤波

for(j=0;j<17;j++)

{

Vxs[j]=Vxs[j+1];

Vys[j]=Vys[j+1];

Vzs[j]=Vzs[j+1];

}

Vxs[17]=Vx/18;

Vys[17]=Vy/18;

Vzs[17]=Vz/18;

mVx=Vxs[17];

mVy=Vys[17];

mVz=Vzs[17];

for(j=0;j<17;j++)

{

mVx=mVx+Vxs[j];

mVy=mVy+Vys[j];

mVz=mVz+Vzs[j];

}

mVg = sqrt(mVx*mVx+mVy*mVy+mVz*mVz)*0.036; //速度值0.1*xkM

}

}

}

}

}

void loop() {

if (millis()-Tm0>1000*sni){

sni = sni+1; //秒数累计

fln = (sni-1)/240;

dati = sni-fln*240-1;

V0[dati] = mVg; //10x kM/h

for(i=0;i<255;i++){

dpV[i] = dpV[i+1];

}

dpV[255]=mVg/10;

Snd_V = mVg/10;

Snd0=0; //每整秒为语音零点,GPS 18Hz中断中累加,以设置个位数十位数报数间隔

//最近120秒内的最大速度

maxV=0;

for (i=1;i<239;i++){

if (V0[i]>maxV&&V0[i-1]>0.7*V0[i]&&V0[i+1]>0.7*V0[i]){

maxV=V0[i];

}

}

}

//语音报数

if(Snd_V>1 && Snd0==0){ //整秒

for (i=0;i<(Snd_V/10)%10+2;i++){ //报十位数

digitalWrite(4, HIGH);

delayMicroseconds(100);

digitalWrite(4, LOW);

delayMicroseconds(100);

}

Hsnd = 1;

}

if(Snd_V>1 && Snd0==2 && Hsnd==1){ //间隔大约1/3秒报个位数

for (i=0;i<Snd_V%10+2;i++){

digitalWrite(4, HIGH);

delayMicroseconds(100);

digitalWrite(4, LOW);

delayMicroseconds(100);

}

Hsnd==0;

}

//实时显示

display.clear();

display.setTextSize(2);

display.setTextColor(1);

display.setCursor(0,0);

display.print("Mx:");

display.print(maxV/10.0,1);

display.setCursor(0,20);

display.print("SA:");

display.print(SA);

display.print(" R:");

display.print(nRun);

display.setTextSize(3);

display.setCursor(0,40);

display.print("V:");

display.print(mVg*0.1,1); //速度km/h

for(i=1;i<127;i++){

display.drawLine(i,63-(dpV[i*2-1]/2+dpV[i*2]/2)-1,i,63-(dpV[i*2+1]/2+dpV[i*2+2]/2)-1,0);

display.drawLine(i,63-(dpV[i*2-1]/2+dpV[i*2]/2)+1,i,63-(dpV[i*2+1]/2+dpV[i*2+2]/2)+1,0);

display.drawLine(i+1,63-(dpV[i*2-1]/2+dpV[i*2]/2),i+1,63-(dpV[i*2+1]/2+dpV[i*2+2]/2),0);

display.drawLine(i-1,63-(dpV[i*2-1]/2+dpV[i*2]/2),i-1,63-(dpV[i*2+1]/2+dpV[i*2+2]/2),0);

}

display.update();

for(i=1;i<127;i++){

display.drawLine(i,63-(dpV[i*2-1]/2+dpV[i*2]/2),i,63-(dpV[i*2+1]/2+dpV[i*2+2]/2),1);

}

display.update();

Snd0++;

//存数据文件

if(dati==239 && maxV>20){ //静止时<2km/h不存数

char filename[32];

sprintf(filename, "R%d_%d.txt", nRun,fln); // 生成文件名

File f = XXXXXXXXXXXXXXXXX(filename, "w");

sprintf(buff, "%d",Yr);

f.write(buff, strlen(buff));

sprintf(buff, "-%d",Mt);

f.write(buff, strlen(buff));

sprintf(buff, "-%d ",Dy);

f.write(buff, strlen(buff));

sprintf(buff, " %d:",Hr);

f.write(buff, strlen(buff));

sprintf(buff, "%d:",Mn);

f.write(buff, strlen(buff));

sprintf(buff, "%d\n",Sd);

f.write(buff, strlen(buff));

for(j=0;j<240;j++){

sprintf(buff, "%d \n", V0[j]);

f.write(buff, strlen(buff));

}

f.close();

}

}


Tangshm作者
6个月1天前IP:中国
949877
2楼

确实RP2040算是业余界先进单片机型号了,即使对新手也很适合。相比普通arduino单片机,连接IDE的时候多了两步,但是能拷uf2文件直接运行足以补偿,毕竟对于初学者先看到手头东西跑起来,可以快速确认硬件没问题,后边也就有信心了。MicroPython编程我还没试,整个流程要特别简化才好

引用
评论
2
Tangshm作者
1个月3天前IP:北京
952359
5楼

UBLOX协议加上刷新率高,要贵一些

引用
评论
笔记
{{note.content}}
{{n.user.username}}
{{fromNow(n.toc)}} {{n.status === noteStatus.disabled ? "已屏蔽" : ""}} {{n.status === noteStatus.unknown ? "正在审核" : ""}} {{n.status === noteStatus.deleted ? '已删除' : ''}}
  • 编辑
  • 删除
  • {{n.status === 'disabled' ? "解除屏蔽" : "屏蔽" }}
我也是有底线的