确实RP2040算是业余界先进单片机型号了,即使对新手也很适合。相比普通arduino单片机,连接IDE的时候多了两步,但是能拷uf2文件直接运行足以补偿,毕竟对于初学者先看到手头东西跑起来,可以快速确认硬件没问题,后边也就有信心了。MicroPython编程我还没试,整个流程要特别简化才好
最近动手做了个GPS速度计,测骑马的速度,没什么技术含量,可给RP2040初学者练手之用。因为这种板子可以直接拷执行代码,有想要自己攒一个来玩或送人也很简单,焊上几根线,拷一下代码就跑起来了,不需要开发环境和编译。能实时显示速度,显示4分钟内的最大速度,显示卫星数,实时语音报速度,单片机U盘存每秒的速度值,数据包含日期时间信息,可接手机或电脑查看拷贝。
硬件上,包含一个RP2040,一个18Hz刷新率的ublox协议的GPS,一个OLED显示,一个语音芯片和手机喇叭报速度,还可以配上小电池和充电板,装到一个塑料盒子里。注意OLED要1306芯片驱动的,不同驱动芯片不通用。
直接拷代码的话,就是按住单片机上的小boot键,插到电脑USB,插好后才放开boot键,电脑上会出现个U盘,吧这个uf2文件拷进去就能运行起来了。
可以先拷程序再焊接各个零件,比如焊上OLED后通电马上就应该能看到显示。GPS插头左数,第1根线接单片机GND脚,第2根线接单片机5V脚(实际锂电正极3.8V左右),第3根线接单片机12脚,第4根线接单片机13脚。OLED除了电源线分别接GND和5V外,SCL接单片机27脚,SDA接单片机26脚,语音芯片除电源要连好外,信号输入脚接单片机4脚,PWM1、2接喇叭。
代码如下:
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();
}
}
确实RP2040算是业余界先进单片机型号了,即使对新手也很适合。相比普通arduino单片机,连接IDE的时候多了两步,但是能拷uf2文件直接运行足以补偿,毕竟对于初学者先看到手头东西跑起来,可以快速确认硬件没问题,后边也就有信心了。MicroPython编程我还没试,整个流程要特别简化才好