Popis konstrukce jednoduché navigace s RPi Pico, Waveshare L76K GPS modulem a LCD displejem ST7920 (SPI sběrnice).
Programovací techniky
Multiprocesing a mutexy. Kruhový seznam. Získání dat ze sériové linky.
Sběr dat z modulu L76K
GNSS modul L76K je jádrem celé navigace. Data získaná z navigačních satelitů GPS posílá po sériové lince asynchronním způsobem.
Dat jsou posílána ve formě vět protokolem NMEA 0183.
Každá věta začíná znakem $ a končí znakem *, za nímž jsou dva znaky kontrolního součtu a znaky \r a \n.
Data ve větě jsou oddělena čárkou ,.
Popis dat je dán normou NMEA 0183 a můžeme jej získat třeba z datasheetu výrobce Quectel_L76K_GNSS_Protocol_Specification_V1.1.pdf
Konkrétní věta vypadá třeba takto:
$GNGGA,181117.000,5020.93040,N,01555.34639,E,1,11,1.1,257.3,M,43.9,M,,*46\r\n
-
začátek Znak
$označuje začátek věty. -
systém satelitů Písmena
GNznamenají systém satelitů (GNje mix všech satelitů,GPje americký Global Positioning System,GLje ruský GLONASS aBDje čínský navigační systém BeiDou). -
typ věty Písmena
GGAje typ věty, v tomto případě je to jsou to fixní data globálního pozičního systému (Global Posiotioning System Fix Data). -
data
181117.000je aktuální světový čas (UTC).18jsou hodiny,11jsou minuty,17.000jsou sekundy. -
data
5020.93040je zeměpisná šířka (kde se nacházíme),50jsou stupně20.93040jsou minuty. -
data Písmeno
Nznamená, že se nacházíme severně od rovníku, písmenoSby označovalo jižně od rovníku. -
data
01555.34639je zeměpisná délka,015jsou stupně a55.234639jsou minuty. -
data Písmeno
Eznamená, že se nacházíme východně od nultého poledníku. Pokud by tam byloW, tak se nacházíme západně od nultého poledníku. -
data
1určuje kvalitu informací, znamená konkrétně platná data. Místo toho tam může být0, pak jsou data neplatná, nebo2což je rozdílový režim nebo6, kdy jsou data odhadnuta. -
data
11znamená aktuální počet rádiově viditelných satelitů. -
data
1.1je horizontální fluktuace dat. -
data
257.3je výška nad mořem. Toto pole u neplatných dat bude prázdné. -
data
Mznamená, že výška nad mořem je měřena v metrech. -
data
43.9je rozdíl mezi ideálním geoidem a výškou na mořem. -
data
Mznamená, že předchozí hodnota je v metrech. (U neplatných dat bude prázdná). -
data dvě čárky za sebou znamenají, že pole mezi nimi je prázdné
-
konec věty *
je konec věty. -
kontrolní součet Znaky
46jsou kontrolním součtem všech znaků mezi$a*. Počítá se jako exkluzivní NEBO (XOR). -
konec znaky
\r\n(nebudeme je potřebovat a proto je zahodíme).
Celá NMEA věta nepřekročí 127 znaků.
Typ věty, které nám modul L76K může poslat:
-
RMCdoporučená minimální GNSS data (Recommended Minimum Specific GNSS Data.) -
GGAfixní data GPS (Global Positioning System Fix Data.) -
GSVviditelné satelity (GNSS Satellites in View.) -
GSAGNSS fluktuace a aktivní satelity (GNSS DOP and Active Satellites.) -
VTGazimut pohybu a rychlost pohybu (Course Over Ground & Ground Speed.) -
GLLpozice — zeměpisná šířka a délka (Geographic Position – Latitude/Longitude.) -
TXTtextová informace o zařízení -
ZDAsvětový čas (Time UTC) — zařízení L76K neumí určit lokální čas (nezná časouvou zónu v místě, kde se nachází).
Modul sype data po sériové lince do Pica neustále a asynchronně rychlostí 9600 baudů.
Proto pro sběr dat ze sériové linky využijeme druhé jádro procesoru, které bude číst sériovou linku.
Při čtení musíme rozpoznat začátek a konec NMEA věty a pokud máme větu načtenou ověříme její kontrolní součet.
Jestliže nám kontrolní součet sedí, tak větu uložíme do buňky kruhového seznamu (nebo kruhové vyrovnávací paměti), označíme ji jako platnou a nezpracovanou, posuneme se v kruhovém seznamu dále a čteme dál.
Tím se bude zabývat výhradně core1. První jádro core0 máme osvobozeno od časově náročného sběru dat a na něm se budeme věnovat zpracování sebraných dat a zobrazením na displeji.
V jazyce C to vypadá takto:
#define UBUF_LEN 9600+4
#define KBDATA_LEN 128
#define POCET_VET 75
uint8_t uart_buf[UBUF_LEN];
typedef struct kb {
struct kb* next;
char* data;
int len;
volatile bool valid;
volatile bool zpracovano;
mutex_t mtx;
} KRUHOVY_BUFFER;
KRUHOVY_BUFFER kbdata[POCET_VET];
// nastavení pro sériovou linku
#define UART_ID uart0
#define BAUD_RATE 9600
#define DATA_BITS 8
#define STOP_BITS 1
#define PARITY UART_PARITY_NONE
#define UART_TX_PIN 0
#define UART_RX_PIN 1
// sběr dat ze sériové linky (běží na core1)
void seriova_linka( void )
{
int i = 0; // pořadí znaku ve bufferu
uint8_t ch; // znak ze sériové linky
KRUHOVY_BUFFER* kbptr = &kbdata[0]; // ukazatel na kruhovou vyrovnávací paměť
char buf[KBDATA_LEN]; // pomocný buffer
bool zacatek = false; // máme začátek věty
bool konec = false; // máme konec věty
seriova_linka_init();
kruhovy_buffer_init();
while( true ) {
// tady se sbírají data a ukládají do vyrovnávací paměti
kbptr = kbptr->next;
}
}
int main( void )
{
// inicializace zařízení
// spouštíme sběr dat ze sériové linky na core1
multicore_launch_core1(seriova_linka);
while( true ) {
// zpracování dat a zobrazení na displeji v pravidelných intervalech
}
}
Kruhový seznam
Do kruhového seznamu (nebo kruhového bufferu) se ukládají ověřené NMEA věty, které si potom core0 zpracuje.
Paměť je alokována staticky, seznam má místo pro 75 vět, buffer zabírá 9600 bytů.
#define UBUF_LEN 9600+4
#define KBDATA_LEN 128
#define POCET_VET 75
uint8_t uart_buf[UBUF_LEN];
typedef struct kb {
struct kb* next; // ukazatel na další kb
char* data; // ukazatel na paměť pro NMEA větu
int len; // skutečná délka NMEA věty
volatile bool valid; // true, pokud je věta platná a je možné ji zpracovat
volatile bool zpracovano; // true, pokud je věta zpracovaná a je možné ji přepsat
mutex_t mtx; // mutex pro zamykání záznamu
} KRUHOVY_BUFFER;
KRUHOVY_BUFFER kbdata[POCET_VET];
// inicializace kruhového bufferu
void kruhovy_buffer_init(void)
{
for( int j = 0; j<POCET_VET; j++) {
kbdata[j].valid = false;
kbdata[j].zpracovano = false;
kbdata[j].data = &uart_buf[j*KBDATA_LEN];
kbdata[j].next = &kbdata[j+1];
kbdata[j].len = 0;
mutex_init(&kbdata[j].mtx);
}
kbdata[POCET_VET-1].next = &kbdata[0]; // poslední ukazuje na první a kruh je uzavřen
}
Příznak volatile bool valid;: true slouží příjemci věty k tomu, že nemusí počítat kontrolní součet (checksum) a může větu zpracovat.
Příznak volatile bool zpracovano;: true je signálem pro zapisovatele dat (funkci seriova_linka()), že předchozí věta byla ve funkci main() zpracována a místo v kruhovém bufferu je možné přepsat.
Pokud main() nestihne včas větu zpracovat, tak se na to nebere ohled a zapisovatel ji přepíše novou větou.
Mutexy (zámky)
Protože obě jádra procesoru RP2040 mohou přistupovat k určitému místu paměti současně, je potřeba, aby při zápisu věty byla daná pamět uzamčena a nemohlo ji číst jádro 0 ve stejný okamžik, kdy jádro 1 do ní zapisuje. Jádro 0 by mohlo přečíst nesmysly. Aby k tomu nedošlo, máme k dispozici tzv. mutexy (mutualy exclusive access), neboli zámky.
Mutexy mutex_t mtx; ve struktuře KRUHOVY_BUFFER souží k zamykání části paměti při zápisu nových dat do kruhového buferu funkcí seriova_linka() a také při zpracování věty a nastavení příznaku zpracovano ve funkci main().
K získání zámku (mutexu) se mohou použít funkce Pico C SDK mutex_enter_blocking(&mtx) anebo mutex_enter_timeout_ms(&mtx, ms).
Pokud nelze získat mutex (paměť je zamčena jiným jádrem), funkce mutex_enter_blocking(&mtx) se zablokuje a bude čekat, dokud mutex nezíská.
V případě získání zámku funkce vrací true.
Funkce mutex_enter_timeout_ms( &mtx, ms) se zablokuje jenom na daný počet milisekund a potom to eventuelně vzdá a vrátí false.
Pří získání zámku funkce vrací true.
Funkce mutex_exit(&mtx) ukončuje výhradní přístup (vrací klíč k zámku).
if( zacatek && konec && nmea_checksum(buf,i) ) {
// získání výhradního přístupu k části kruhového bufferu
// pokud funkce nezíská zámek (mutex), dojde k dočasnému zablokování
mutex_enter_blocking(&kbptr->mtx);
// manipulace s daty
strncpy(kbptr->data, buf, KBDATA_LEN);
kbptr->valid = true;
kbptr->zpracovano = false;
kbptr->len = i;
// uvolnění výhradního přístupu
mutex_exit(&kbptr->mtx);
// .. pokračování
}
// čekání na mutex je omezeno 1 ms, pokud není získán mutex, tak se věta vypustí
// počká se a bude se zpracovávat další věta
if( mutex_enter_timeout_ms(&kbptr->mtx,1) ) {
len = kbptr->len;
strncpy(buf,&kbptr->data[0],KBDATA_LEN);
kbptr->valid = false;
kbptr->zpracovano = true;
// uvolnění zámku
mutex_exit(&kbptr->mtx);
// ... pokračování
}
Je dobrou programátorskou praktikou uvolnit použitý mutex funkcí mutex_exit(&mtx) co nejdříve.
Pokud bychom v obou vláknech programu použili funkce mutex_enter_blocking( &mtx), tak se může stát, že obě vlákna budou čekat na získání mutexu nekonečně dlouho a program se zakousne dokud Pico nevypneme.
To je pochopitelně něžádoucí stav.
Zpracování sebraných dat
Zpracování sebraných dat se provádí v cyklu for() funkce main().
Toto zpracování se každých 100 ms přeruší a výsledky se vypisují na dipleji.
Z kruhového bufferu e vybírají pořád dokola nezpracované věty a některé z nich (GNZDA — čas, GNGGA — souřadnice a GNVTG — kurs a rychlost)
se zpracovávají ve funkcích parse_gnzda(), parse_gngga() a parse_gnvtg(). Tyto funkce vytahují užitečné informace ke zobrazení na dipleji.
Vybírání dat z kruhového bufferu je ošetřeno získáním mutexu.
Parsovací funkce pro zpracování informací z NMEA vět jsou v souborech parse_nmea.h (deklarace struktur a prototypy) a parse_nmea.c (implementace funkcí).
Výpis požadovaných dat je na grafický monochromatický displej s čipem ST7920 a má dvě obrazovky. První obrazovka se zeměpisnými souřadnicemi a časem a druhá obrazovka s rychlostí, kursem a časem. Přepínání obrazovek se provádí tlačítkem připojeným na GPIO pin 21.
while( true ) {
// zpracovani nezpracovanych dat (každých 100ms zpracování přerušíme
// a budeme zobrazovat informace na displeji)
for(uint64_t t = time_us_64(); (time_us_64() - t) < 100000; kbptr=kbptr->next) {
chsum = false;
if( kbptr->valid && kbptr->zpracovano == false ) { // dosud nezpracované věty
if( mutex_enter_timeout_ms(&kbptr->mtx,1) ) {
len = kbptr->len;
strncpy(buf,&kbptr->data[0],KBDATA_LEN);
kbptr->valid = false;
kbptr->zpracovano = true;
mutex_exit(&kbptr->mtx);
chsum = true;
// nastavení času
if( buf[0] == '$' && buf[1] == 'G' && buf[2] == 'N'
&& buf[3] == 'Z' && buf[4] == 'D' && buf[5] == 'A' ) {
GNZDA gnzda = nmea_gnzda(buf, len);
// čas stačí nastavit jednou a potom každou hodinu
if( gnzda.parse_ok == 6 && (ctim.year == 0 | ctim.min == 0)) {
ctim.year = gnzda.year;
ctim.month = gnzda.month;
ctim.day = gnzda.day;
ctim.dotw = gnzda.dotw;
ctim.hour = gnzda.hour;
ctim.min = gnzda.min;
ctim.sec = gnzda.sec;
rtc_set_datetime(&ctim);
// vynulejeme chyby
chyby = 0;
}
}
// zeměpisné souřadnice
if( buf[0] == '$' && buf[1] == 'G' && buf[2] == 'N'
&& buf[3] == 'G' && buf[4] == 'G' && buf[5] == 'A' ) {
gngga = nmea_gngga(buf,len);
// gngga.fix == 0 jsou neplatná data
if( gngga.parse_ok == 14 && gngga.fix > 0 ) {
memcpy( &st_gga, &gngga, sizeof(gngga));
break;
} else {
chyby++;
}
}
// kurs a rychlost
if( buf[0] == '$' && buf[1] == 'G' && buf[2] == 'N'
&& buf[3] == 'V' && buf[4] == 'T' && buf[5] == 'G' ) {
gnvtg = nmea_gnvtg(buf,len);
if( gnvtg.parse_ok >= 7 ) {
memcpy( &stable_gnvtg, &gnvtg, sizeof(VTG));
break;
}
}
}
} else {
sleep_ms(10);
continue;
}
} // for
// test tlačítka přepínání obrazovek
if( gpio_get_events(TLAC_PIN) & GPIO_IRQ_EDGE_RISE ) {
gpio_clear_events(TLAC_PIN, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL );
disp_stav = disp_stav ? false : true;
}
// tisk na displeji
if( disp_stav ) { // první obrazovka
rtc_get_datetime(&ctim);
datetime_to_str(datetime_str, sizeof(datetime_buf), &ctim);
u8g2_ClearDisplay(&u8g2);
u8g2_SetFont(&u8g2, u8g2_font_spleen8x16_me);
sprintf(dbuf,"Šíř: %f˚ %c",st_gga.latitude, st_gga.latitude_ch);
u8g2_DrawUTF8(&u8g2, 0, 16, dbuf);
sprintf(dbuf,"Dél: %f˚ %c",st_gga.longitude, st_gga.longitude_ch);
u8g2_DrawUTF8(&u8g2, 0, 32, dbuf);
sprintf(dbuf,"Výš: %5.1f%c",st_gga.altitude, st_gga.altitude_unit=='M'?'m':' ');
u8g2_uint_t width = u8g2_GetUTF8Width(&u8g2, dbuf);
u8g2_DrawUTF8(&u8g2, 0, 48, dbuf);
u8g2_SetFont(&u8g2, u8g2_font_spleen5x8_me);
sprintf(dbuf,"%d sat.",st_gga.satelites);
u8g2_DrawUTF8(&u8g2, width+5, 48, dbuf);
sprintf(dbuf,"%2d.%2d.%4d %02d:%02d:%02d UTC %d",
ctim.day, ctim.month, ctim.year, ctim.hour, ctim.min, ctim.sec, chyby);
u8g2_DrawUTF8(&u8g2, 0, 60, dbuf);
u8g2_UpdateDisplay(&u8g2);
} else { // druhá obrazovka
rtc_get_datetime(&ctim);
datetime_to_str(datetime_str, sizeof(datetime_buf), &ctim);
u8g2_ClearDisplay(&u8g2);
u8g2_SetFont(&u8g2, u8g2_font_spleen8x16_me);
sprintf(dbuf,"Rych: %4.1f km/h", gnvtg.kmh);
u8g2_DrawUTF8(&u8g2, 0, 16, dbuf);
sprintf(dbuf,"Kurs: %f", gnvtg.kurs);
u8g2_DrawUTF8(&u8g2, 0, 32, dbuf);
sprintf(dbuf,"Výš: %5.1f%c",st_gga.altitude, st_gga.altitude_unit=='M'?'m':' ');
u8g2_uint_t width = u8g2_GetUTF8Width(&u8g2, dbuf);
u8g2_DrawUTF8(&u8g2, 0, 48, dbuf);
u8g2_SetFont(&u8g2, u8g2_font_spleen5x8_me);
sprintf(dbuf,"%d sat.",st_gga.satelites);
u8g2_DrawUTF8(&u8g2, width+5, 48, dbuf);
sprintf(dbuf,"%2d.%2d.%4d %02d:%02d:%02d UTC %d",
ctim.day, ctim.month, ctim.year, ctim.hour, ctim.min, ctim.sec, chyby);
u8g2_DrawUTF8(&u8g2, 0, 60, dbuf);
u8g2_UpdateDisplay(&u8g2);
}
sleep_ms(100);
gpio_put(LED_PIN, led_stav);
led_stav = led_stav ? false : true;
}
Zapojení projektu



| GPS modul L76K nechce korektně chodit při napětí 5.0V, musí se napájet 5.1 V. Je to možná způsobeno dlouhým kabelem (4m) mezi Picem a modulem L76K. |
Program
Sběr dat, zpracování NMEA vět a výpis na dipleji, funkce main().
navigace.c
/* navigace.c v 0.10
* (c) Jirka Chráska 26.10.2025, <jirka@lixis.cz>
* Jednoduchá navigace
* GPS data se získávají z modulu L76K sériovou linkou a ukládají do
* kruhového bufferu pomocí vlákna na core1
* core0 zpracovává data a zobrazuje na displeji
*/
#include <stdio.h>
#include <stdlib.h>
#include "pico/stdlib.h"
#include "pico/multicore.h"
#include <string.h>
#include "hardware/uart.h"
#include "hardware/rtc.h"
#include "hardware/pwm.h"
#include "hardware/structs/iobank0.h"
#include "pico/util/datetime.h"
#include <u8g2.h>
#include "st7920_spi_u8g2_hal.h"
#include "podsvit.h"
#include "nmea_parse.h"
# define DEBUG 0
#define UBUF_LEN 9600+4
#define KBDATA_LEN 128
#define POCET_VET 75
uint8_t uart_buf[UBUF_LEN];
typedef struct kb {
struct kb* next;
char* data;
int len;
volatile bool valid;
volatile bool zpracovano;
mutex_t mtx;
} KRUHOVY_BUFFER;
KRUHOVY_BUFFER kbdata[POCET_VET];
#define UART_ID uart0
#define BAUD_RATE 9600
#define DATA_BITS 8
#define STOP_BITS 1
#define PARITY UART_PARITY_NONE
#define UART_TX_PIN 0
#define UART_RX_PIN 1
// struktura pro kreslení na displej
u8g2_t u8g2;
// inicializace UART
void seriova_linka_init(void)
{
uart_init(UART_ID, BAUD_RATE);
gpio_set_function( UART_TX_PIN, UART_FUNCSEL_NUM(UART_ID, UART_TX_PIN ));
gpio_set_function( UART_RX_PIN, UART_FUNCSEL_NUM(UART_ID, UART_RX_PIN ));
int actual = uart_set_baudrate(UART_ID, BAUD_RATE );
uart_set_hw_flow( UART_ID, false, false );
uart_set_fifo_enabled( UART_ID, true );
memset(uart_buf,'\0',UBUF_LEN);
}
// inicializace kruhoveho bufferu
void kruhovy_buffer_init(void)
{
for( int j = 0; j<POCET_VET; j++) {
kbdata[j].valid = false;
kbdata[j].zpracovano = false;
kbdata[j].data = &uart_buf[j*KBDATA_LEN];
kbdata[j].next = &kbdata[j+1];
kbdata[j].len = 0;
mutex_init(&kbdata[j].mtx);
}
kbdata[POCET_VET-1].next = &kbdata[0];
}
// sběr dat ze sériové linky (běží na core1)
void seriova_linka( void )
{
int i = 0; // znak v NMEA větě
uint8_t ch; // znak ze sériové linky
KRUHOVY_BUFFER* kbptr = &kbdata[0];
char buf[KBDATA_LEN];
bool zacatek = false;
bool konec = false;
seriova_linka_init();
kruhovy_buffer_init();
while( true ) {
memset(buf,'\0',KBDATA_LEN);
while( ch = uart_getc(UART_ID) ) {
if( i > KBDATA_LEN - 1 ) { // vynucený konec věty
i = 0;
zacatek = false;
konec = false;
buf[0] = '\0';
mutex_enter_blocking(&kbptr->mtx);
kbptr->valid = false;
mutex_exit(&kbptr->mtx);
kbptr = kbptr->next;
}
if( ch == '$' ) { // začátek věty
buf[i] = ch;
buf[i+1] = '\0';
i++;
zacatek = true;
} else if( ch == '\n' ) { // konec věty
buf[i] = '\0';
konec = true;
if( zacatek && konec && nmea_checksum(buf,i) ) {
mutex_enter_blocking(&kbptr->mtx);
strncpy(kbptr->data, buf, KBDATA_LEN);
kbptr->valid = true;
kbptr->zpracovano = false;
kbptr->len = i;
mutex_exit(&kbptr->mtx);
}
kbptr = kbptr->next;
i=0;
zacatek = false;
konec = false;
break;
} else if( ch == '\r' ) { // vynecháváme
} else {
if( zacatek ) {
buf[i] = ch;
buf[i+1] = '\0';
i++;
}
}
} // uart_getc()
} // while(true)
}
// -------------------------------------------------------------------------------------
#define BUFLEN 128
#define LED_PIN 25
#define TLAC_PIN 21
#define PODSVIT_PIN 22
uint32_t gpio_get_events(uint gpio)
{
int32_t mask = 0xF << 4 * ( gpio % 8 );
return (iobank0_hw->intr[gpio / 8] & mask) >> 4 * ( gpio % 8 );
}
void gpio_clear_events(uint gpio, uint32_t events)
{
gpio_acknowledge_irq(gpio, events);
}
int main(void)
{
bool led_stav = true;
bool disp_stav = true;
char buf[BUFLEN];
int len = 0;
int v = 0;
char *ptr;
bool chsum = false;
GNGGA gngga;
GNGGA st_gga;
GLL gpgll;
VTG gnvtg = {0.0,0.0,0.0,'i',0};
VTG stable_gnvtg = {0.0,0.0,0.0,'i',0};
char dbuf[BUFLEN];
int sat = 1;
datetime_t ctim = {0,0,0,0,0,0,0}; // aktuální čas
char datetime_buf[256];
char *datetime_str = &datetime_buf[0];
KRUHOVY_BUFFER* kbptr = &kbdata[0];
int sekunda = 0;
double rychlost;
int chyby = 0;
#if DEBUG
stdio_init_all();
#endif
gpio_init( LED_PIN );
gpio_set_function( LED_PIN, GPIO_FUNC_SIO );
gpio_set_dir( LED_PIN, true );
gpio_put(LED_PIN, led_stav);
// tlacitko prepinani obrazovek
gpio_set_function( TLAC_PIN, GPIO_FUNC_SIO );
gpio_set_dir( TLAC_PIN, false );
gpio_pull_down( TLAC_PIN );
sleep_ms(1500);
#if DEBUG
printf("GPS navigace v0.10 (c) Jirka Chráska 2025, <jirka@lixis.cz>\n");
#endif
memset(&buf[0],'\0',BUFLEN);
memset(&st_gga,0,sizeof(GNGGA));
// nastavení displeje
u8g2_Setup_st7920_s_128x64_f(&u8g2, U8G2_R0,
u8x8_byte_pico_hw_spi, u8x8_gpio_and_delay_pico);
u8g2_InitDisplay(&u8g2);
// nastavení podsvitu displeje
podsvit_init(PODSVIT_PIN);
podsvit(60);
// výmaz displeje
u8g2_ClearDisplay(&u8g2);
u8g2_SetDrawColor(&u8g2, 1);
u8g2_SetFont(&u8g2, u8g2_font_simple1_te);
sprintf(dbuf,"Jednoduchá GPS navigace");
u8g2_DrawUTF8(&u8g2, 0, 14, dbuf);
sprintf(dbuf,"(c) Jirka Chráska 2025");
u8g2_DrawUTF8(&u8g2, 0, 28, dbuf);
sprintf(dbuf,"<jirka@lixis.cz>");
u8g2_DrawUTF8(&u8g2, 0, 42, dbuf);
sprintf(dbuf,"Inicializace. Hledám satelity...");
u8g2_DrawUTF8(&u8g2, 0, 58, dbuf);
u8g2_UpdateDisplay(&u8g2);
// inicializace hodin reálného času
rtc_init();
sleep_ms(5000);
multicore_launch_core1(seriova_linka);
sleep_ms(100);
while( true ) {
// zpracovani nezpracovanych dat (každých 100ms zpracování přerušíme
// a budeme zobrazovat informace na displeji)
for(uint64_t t = time_us_64(); (time_us_64() - t) < 100000; kbptr=kbptr->next) {
chsum = false;
if( kbptr->valid && kbptr->zpracovano == false ) {
if( mutex_enter_timeout_ms(&kbptr->mtx,1) ) {
len = kbptr->len;
strncpy(buf,&kbptr->data[0],KBDATA_LEN);
kbptr->valid = false;
kbptr->zpracovano = true;
mutex_exit(&kbptr->mtx);
chsum = true;
#if DEBUG
//printf("Věta %p; len=%3d %18s '%s'\n",
//kbptr, len, chsum ? "Correct data:" : "Checksum error:", buf);
#endif
// nastavení času
if( buf[0] == '$' && buf[1] == 'G' && buf[2] == 'N'
&& buf[3] == 'Z' && buf[4] == 'D' && buf[5] == 'A' ) {
GNZDA gnzda = nmea_gnzda(buf, len);
// čas stačí nastavit jednou a potom každou hodinu
if( gnzda.parse_ok == 6 && (ctim.year == 0 | ctim.min == 0)) {
ctim.year = gnzda.year;
ctim.month = gnzda.month;
ctim.day = gnzda.day;
ctim.dotw = gnzda.dotw;
ctim.hour = gnzda.hour;
ctim.min = gnzda.min;
ctim.sec = gnzda.sec;
rtc_set_datetime(&ctim);
// vynulejeme chyby
chyby = 0;
}
}
// zeměpisné souřadnice
if( buf[0] == '$' && buf[1] == 'G' && buf[2] == 'N'
&& buf[3] == 'G' && buf[4] == 'G' && buf[5] == 'A' ) {
gngga = nmea_gngga(buf,len);
// gngga.fix == 0 jsou neplatná data
if( gngga.parse_ok == 14 && gngga.fix > 0 ) {
memcpy( &st_gga, &gngga, sizeof(gngga));
break;
} else {
chyby++;
}
}
// kurs a rychlost
if( buf[0] == '$' && buf[1] == 'G' && buf[2] == 'N'
&& buf[3] == 'V' && buf[4] == 'T' && buf[5] == 'G' ) {
gnvtg = nmea_gnvtg(buf,len);
if( gnvtg.parse_ok >= 7 ) {
memcpy( &stable_gnvtg, &gnvtg, sizeof(VTG));
break;
}
}
}
} else {
sleep_ms(10);
continue;
}
} // for
// test tlačítka přepínání obrazovek
if( gpio_get_events(TLAC_PIN) & GPIO_IRQ_EDGE_RISE ) {
gpio_clear_events(TLAC_PIN, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL );
disp_stav = disp_stav ? false : true;
}
// tisk na displeji
if( disp_stav ) { // první obrazovka
rtc_get_datetime(&ctim);
datetime_to_str(datetime_str, sizeof(datetime_buf), &ctim);
u8g2_ClearDisplay(&u8g2);
u8g2_SetFont(&u8g2, u8g2_font_spleen8x16_me);
sprintf(dbuf,"Šíř: %f˚ %c",st_gga.latitude, st_gga.latitude_ch);
u8g2_DrawUTF8(&u8g2, 0, 16, dbuf);
sprintf(dbuf,"Dél: %f˚ %c",st_gga.longitude, st_gga.longitude_ch);
u8g2_DrawUTF8(&u8g2, 0, 32, dbuf);
sprintf(dbuf,"Výš: %5.1f%c",st_gga.altitude, st_gga.altitude_unit=='M'?'m':' ');
u8g2_uint_t width = u8g2_GetUTF8Width(&u8g2, dbuf);
u8g2_DrawUTF8(&u8g2, 0, 48, dbuf);
u8g2_SetFont(&u8g2, u8g2_font_spleen5x8_me);
sprintf(dbuf,"%d sat.",st_gga.satelites);
u8g2_DrawUTF8(&u8g2, width+5, 48, dbuf);
sprintf(dbuf,"%2d.%2d.%4d %02d:%02d:%02d UTC %d",
ctim.day, ctim.month, ctim.year, ctim.hour, ctim.min, ctim.sec, chyby);
u8g2_DrawUTF8(&u8g2, 0, 60, dbuf);
u8g2_UpdateDisplay(&u8g2);
} else { // druhá obrazovka
rtc_get_datetime(&ctim);
datetime_to_str(datetime_str, sizeof(datetime_buf), &ctim);
u8g2_ClearDisplay(&u8g2);
u8g2_SetFont(&u8g2, u8g2_font_spleen8x16_me);
sprintf(dbuf,"Rych: %4.1f km/h", gnvtg.kmh);
u8g2_DrawUTF8(&u8g2, 0, 16, dbuf);
sprintf(dbuf,"Kurs: %f", gnvtg.kurs);
u8g2_DrawUTF8(&u8g2, 0, 32, dbuf);
sprintf(dbuf,"Výš: %5.1f%c",st_gga.altitude, st_gga.altitude_unit=='M'?'m':' ');
u8g2_uint_t width = u8g2_GetUTF8Width(&u8g2, dbuf);
u8g2_DrawUTF8(&u8g2, 0, 48, dbuf);
u8g2_SetFont(&u8g2, u8g2_font_spleen5x8_me);
sprintf(dbuf,"%d sat.",st_gga.satelites);
u8g2_DrawUTF8(&u8g2, width+5, 48, dbuf);
sprintf(dbuf,"%2d.%2d.%4d %02d:%02d:%02d UTC %d",
ctim.day, ctim.month, ctim.year, ctim.hour, ctim.min, ctim.sec, chyby);
u8g2_DrawUTF8(&u8g2, 0, 60, dbuf);
u8g2_UpdateDisplay(&u8g2);
}
sleep_ms(100);
gpio_put(LED_PIN, led_stav);
led_stav = led_stav ? false : true;
}
}
Parsování posílaných dat z modulu L76K po sériové lince, protokol NMEA 0183.
nmea_parse.h
/* nmea_parse.h
*
*/
// parsování věty $GNZDA,155647.000,19,10,2025,00
// 15 jsou hodiny
// 56 jsou minuty
// 47.000 jsou sekundy
// 19 je den v měsíci
// 10 je měsíc
// 2025 je rok
// 00 je den v týdnu, začíná se nedělí
// informace o světovém času (UTC) podle Gregoriánského kalendáře
typedef struct {
int year;
int month;
int day;
int dotw; // pořadí dne v týdnu 0 je neděle
int hour;
int min;
int sec;
int msec;
int parse_ok;
} GNZDA;
/* Indexy */
#define NMEA_GNZDA_TIME 1
#define NMEA_GNZDA_DAY 2
#define NMEA_GNZDA_MONTH 3
#define NMEA_GNZDA_YEAR 4
#define NMEA_GNZDA_DAYOFWEEK 5
GNZDA nmea_gnzda( char *data, int len );
//---------------------------------------------------
// informace o zeměpisných souřadnicích
typedef struct {
double time;
int time_hour;
int time_min;
int time_sec;
int time_msec;
double latitude;
uint8_t latitude_ch;
int latitude_degree;
int latitude_minutes;
double latitude_vteriny;
double longitude;
uint8_t longitude_ch;
int longitude_degree;
int longitude_minutes;
double longitude_vteriny;
int fix;
int satelites;
double horizontal_dilution;
double altitude;
uint8_t altitude_unit;
double geoid_height;
uint8_t geoid_height_unit;
uint8_t checksum;
bool checksum_ok;
int parse_ok;
} GNGGA;
/* Indexy */
#define NMEA_GPGGA_TIME 1
#define NMEA_GPGGA_LATITUDE 2
#define NMEA_GPGGA_LATITUDE_CARDINAL 3
#define NMEA_GPGGA_LONGITUDE 4
#define NMEA_GPGGA_LONGITUDE_CARDINAL 5
#define NMEA_GPGGA_POSITION_FIX 6
#define NMEA_GPGGA_N_SATELLITES 7
#define NMEA_GPGGA_HORIZ_DILUTION 8
#define NMEA_GPGGA_ALTITUDE 9
#define NMEA_GPGGA_ALTITUDE_UNIT 10
#define NMEA_GPGGA_UNDULATION 11
#define NMEA_GPGGA_UNDULATION_UNIT 12
// parsování věty $GNGGA
GNGGA nmea_gngga( char *data, int len );
// -------------------------------------------------
typedef struct {
double latitude;
char latitude_ch;
double longitude;
char longitude_ch;
char valid;
double time;
int time_hour;
int time_min;
int time_sec;
int time_msec;
int parse_ok;
} GLL;
/* Indexy */
#define NMEA_GPGLL_LATITUDE 1
#define NMEA_GPGLL_LATITUDE_CARDINAL 2
#define NMEA_GPGLL_LONGITUDE 3
#define NMEA_GPGLL_LONGITUDE_CARDINAL 4
#define NMEA_GPGLL_TIME 5
#define NMEA_GPGLL_VALID_DATA 6
GLL nmea_gpgll( char *data, int len );
// -------------------------------------------------
// GPVTG -- kurs a rychlost
typedef struct {
double kurs;
double uzly;
double kmh;
char mode;
int parse_ok;
} VTG;
#define NMEA_GPVTG_COURSE_AZIMUT 1
#define NMEA_GPVTG_COURSE_AZIMUT_CARDINAL 2
#define NMEA_GPVTG_COURSE_MAGNETIC 1000
#define NMEA_GPVTG_COURSE_MAGNETIC_CARDINAL 3
#define NMEA_GPVTG_SPEED_KNOTS 4
#define NMEA_GPVTG_SPEED_KNOTS_CARDINAL 5
#define NMEA_GPVTG_SPEED_KMPERHOUR 6
#define NMEA_GPVTG_SPEED_KMPERHOUR_CARDINAL 7
#define NMEA_GPVTG_MODE_INDICATOR 8
VTG nmea_gnvtg( char *data, int len );
// -------------------------------------------------
bool nmea_checksum(const char *data, int len );
nmea_parse.c
/* nmea_parse.c
*
*/
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "nmea_parse.h"
GNZDA nmea_gnzda( char *data, int len )
{
GNZDA res = {0, 0, 0, 0, 0, 0, 0};
int section = 0;
char *str1;
char *saveptr1;
char *token;
char buf[10];
for(section=0, str1 = data ; section<=5; section++, str1=NULL) {
token = strtok_r(str1,",",&saveptr1);
if( token == NULL ) {
break;
}
switch ( section ) {
case 0: // nazev
if( strcmp(token,"$GNZDA") == 0 ) { res.parse_ok=1; }
break;
case NMEA_GNZDA_TIME: // cas
buf[0] = token[0]; buf[1]=token[1]; buf[2] = '\0';
res.hour = atoi(buf);
buf[0] = token[2]; buf[1]=token[3]; buf[2] = '\0';
res.min = atoi(buf);
buf[0] = token[4]; buf[1]=token[5]; buf[2] = '\0';
res.sec = atoi(buf);
res.msec = atoi(&token[7]);
res.parse_ok++;
break;
case NMEA_GNZDA_DAY: // den v měsíci
res.day = atoi(token);
res.parse_ok++;
break;
case NMEA_GNZDA_MONTH: // měsíc
res.month = atoi(token);
res.parse_ok++;
break;
case NMEA_GNZDA_YEAR: // rok
res.year = atoi(token);
res.parse_ok++;
break;
case NMEA_GNZDA_DAYOFWEEK: // den v týdnu
res.dotw = atoi(token);
res.parse_ok++;
break;
default:
res.parse_ok = -1;
break;
}
}
return res;
}
//---------------------------------------------------
// parsování věty $GNGGA
GNGGA nmea_gngga( char *data, int len )
{
GNGGA res = {0.0, 0, 0, 0, 0, 0.0, ' ', 0.0, ' ', 0, 0, 0.0, 0.0, ' ', 0.0, ' ', '\0', false, 0};
int section = 0;
char *str1;
char *saveptr1;
char *token;
char *buf[10];
for(section=0, str1 = data ; section<=13; section++, str1=NULL) {
token = strtok_r(str1,",*",&saveptr1);
if( token == NULL ) {
break;
}
switch ( section ) {
case 0: // nazev
if( strcmp(token,"$GNGGA") == 0 ) { res.parse_ok=1; }
break;
case NMEA_GPGGA_TIME: // cas
res.time = strtod(token, NULL);
res.time_hour = ((int)res.time)/10000;
res.time_min = ((int)res.time - (res.time_hour*10000))/100;
res.time_sec = ((int)res.time - (res.time_hour*10000) - (res.time_min*100));
res.time_msec = (res.time - res.time_hour*10000.0 - res.time_min*100.0 - res.time_sec)*1000;
res.parse_ok++;
break;
case NMEA_GPGGA_LATITUDE: // zeměpisná šířka ve stupních
res.latitude = strtod(token,NULL)/100.0;
res.latitude_degree = atoi(token)/100;
res.latitude_minutes = atoi(token+2);
res.latitude_vteriny = res.latitude - res.latitude_degree - res.latitude_minutes;
if( res.latitude_vteriny < 0.0) { res.latitude_minutes += 1; res.latitude_vteriny += 60.0; }
res.parse_ok++;
break;
case NMEA_GPGGA_LATITUDE_CARDINAL: // severní N nebo jižní S
res.latitude_ch = token[0];
res.parse_ok++;
break;
case NMEA_GPGGA_LONGITUDE: // zeměpisná délka
res.longitude = strtod(token,NULL)/100.0;
res.longitude_degree = atoi(token)/100;
res.longitude_minutes = (atoi(token) - res.longitude_degree*100)*6/10;
res.longitude_vteriny = (res.longitude - res.longitude_degree*1.0 - res.longitude_minutes*6.0/10.0)*6.0/10.0;
if( res.longitude_vteriny < 0.0) { res.longitude_minutes += 1; res.longitude_vteriny += 60.0; }
res.parse_ok++;
break;
case NMEA_GPGGA_LONGITUDE_CARDINAL: // západní W nebo východní E
res.longitude_ch = token[0];
res.parse_ok++;
break;
case NMEA_GPGGA_POSITION_FIX: // fixace
res.fix = atoi(token);
res.parse_ok++;
break;
case NMEA_GPGGA_N_SATELLITES: // počet satelitů
res.satelites = atoi(token);
res.parse_ok++;
break;
case NMEA_GPGGA_HORIZ_DILUTION: // plavání chyby
res.horizontal_dilution = strtod(token,NULL);
case NMEA_GPGGA_ALTITUDE: // výška nad mořem
res.altitude = strtod(token,NULL);
res.parse_ok++;
break;
case NMEA_GPGGA_ALTITUDE_UNIT: // jednotka nadmořské výšky
res.altitude_unit = token[0];
res.parse_ok++;
break;
case NMEA_GPGGA_UNDULATION: // výška nad ideálním WGS84 elipsoidem
res.geoid_height = strtod(token,NULL);
res.parse_ok++;
break;
case NMEA_GPGGA_UNDULATION_UNIT: // jednotka výšky nad WGS84 elipsoidem
res.geoid_height_unit = token[0];
res.parse_ok++;
break;
case 13: // checksum
res.checksum = atoi(token);
res.parse_ok++;
break;
default:
res.parse_ok = -1;
break;
}
}
return res;
}
//---------------------------------------------------
GLL nmea_gpgll( char *data, int len )
{
GLL res = {0.0, ' ', 0.0, ' ', 'N', 0.0, 0, 0, 0 };
int section = 0;
char *str1;
char *saveptr1;
char *token;
//char buf[10];
for(section=0, str1 = data ; section<=6; section++, str1=NULL) {
token = strtok_r(str1,",*",&saveptr1);
if( token == NULL ) {
break;
}
switch ( section ) {
case 0: // nazev
if( strcmp(token,"$GPGLL") == 0 ) { res.parse_ok=1; }
break;
case NMEA_GPGLL_LATITUDE: // zeměpisná délka
res.latitude = strtod(token,NULL)/100.0;
res.parse_ok++;
break;
case NMEA_GPGLL_LATITUDE_CARDINAL: // N - severní, S - jižní
res.latitude_ch = token[0];
res.parse_ok++;
break;
case NMEA_GPGLL_LONGITUDE: // zeměpisná délka
res.longitude = strtod(token,NULL)/100.0;
res.parse_ok++;
break;
case NMEA_GPGLL_LONGITUDE_CARDINAL: // N - severní, S - jižní
res.longitude_ch = token[0];
res.parse_ok++;
break;
case NMEA_GPGLL_TIME: // toto je volitelné
res.time = strtod(token,NULL);
res.time_hour = ((int)res.time)/10000;
res.time_min = ((int)res.time - (res.time_hour*10000))/100;
res.time_sec = ((int)res.time - (res.time_hour*10000) - (res.time_min*100));
res.time_msec = (res.time - res.time_hour*10000.0 - res.time_min*100.0 - res.time_sec)*1000;
res.parse_ok++;
break;
case NMEA_GPGLL_VALID_DATA: // A - data jsou platná
res.valid = token[0];
res.parse_ok++;
break;
}
}
return res;
}
// ---------------------------------------------------
// kurs a rychlost GNVTG
VTG nmea_gnvtg( char *data, int len)
{
VTG res = { 0.0, 0.0, 0.0, '\0', 0};
int section = 0;
char *str1;
char *saveptr1;
char *token;
for(section=0, str1 = data ; section<=9; section++, str1=NULL) {
token = strtok_r(str1,",*",&saveptr1);
if( token == NULL ) {
break;
}
// printf("Sekce=%d token='%s'",section,token);
switch ( section ) {
case 0: // nazev
if( strcmp(token,"$GNVTG") == 0 ) { res.parse_ok=1; }
break;
case NMEA_GPVTG_COURSE_AZIMUT: // azimut
res.kurs = strtod(token,NULL);
res.parse_ok++;
break;
case NMEA_GPVTG_COURSE_AZIMUT_CARDINAL: // písmenko T
if( strcmp(token, "T") == 0 ) { res.parse_ok++; }
break;
case NMEA_GPVTG_COURSE_MAGNETIC: // tady nebude nic
res.parse_ok++;
break;
case NMEA_GPVTG_COURSE_MAGNETIC_CARDINAL: // tady bude písmenko M
if( strcmp(token, "M") == 0 ) { res.parse_ok++; }
break;
case NMEA_GPVTG_SPEED_KNOTS: // rychlost v uzlech
res.uzly = strtod(token,NULL);
res.parse_ok++;
break;
case NMEA_GPVTG_SPEED_KNOTS_CARDINAL: // písmenko N
if( strcmp(token, "N") == 0 ) { res.parse_ok++; }
break;
case NMEA_GPVTG_SPEED_KMPERHOUR: // rychlost v km/h
res.kmh = strtod(token,NULL);
res.parse_ok++;
break;
case NMEA_GPVTG_SPEED_KMPERHOUR_CARDINAL: // písmenko K
if( strcmp(token, "K") == 0 ) { res.parse_ok++; }
break;
case NMEA_GPVTG_MODE_INDICATOR: // A - autonomní režim, E odhad, N - neplatná data
res.mode = token[0];
res.parse_ok++;
break;
}
}
return res;
}
// ---------------------------------------------------
// porovná kontrolní součet NMEA věty
bool nmea_checksum(const char *data, int len )
{
uint8_t checksum = 0;
uint8_t checksum_calculated = 1;
if(len < 7 ) {
return false; // příliš krátká věta
}
if(data[len-3] != '*') return false;
if( ((data[len-2]>='0' && data[len-2]<='9') || (data[len-2]>='A' && data[len-2]<='F'))
&& ((data[len-1]>='0' && data[len-1]<='9') || (data[len-1]>='A' && data[len-1]<='F'))) {
checksum = strtol(&data[len-2],NULL,16);
checksum_calculated = (uint8_t) data[1];
for( int i=2; i<len-3; i++) {
checksum_calculated ^= (uint8_t) data[i];
}
}
return checksum == checksum_calculated ? true : false;
}
// ---------------------------------------------------
Ovladač hardware displeje.
st7920_spi_u8g2_hal.c
#include "st7920_spi_u8g2_hal.h"
void st7920_writeReg_SPI(uint8_t aByte)
{
uint8_t cmdBuf[3];
cmdBuf[0] = 0b11111000;
cmdBuf[1] = aByte & 0xf0;
cmdBuf[2] = (aByte & 0x0f) << 0x04;
spi_write_blocking(SPI_PORT, cmdBuf, sizeof(cmdBuf));
}
void st7920_writeData_SPI(uint8_t aByte)
{
uint8_t cmdBuf[3];
cmdBuf[0] = 0b11111010;
cmdBuf[1] = aByte & 0xf0;
cmdBuf[2] = (aByte & 0x0f) << 0x04;
spi_write_blocking(SPI_PORT, cmdBuf, sizeof(cmdBuf));
}
uint8_t u8x8_gpio_and_delay_pico(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
switch (msg)
{
case U8X8_MSG_GPIO_AND_DELAY_INIT:
gpio_init(PIN_RST);
gpio_init(PIN_CS);
gpio_set_dir(PIN_RST, GPIO_OUT);
gpio_set_dir(PIN_CS, GPIO_OUT);
gpio_set_function(PIN_SCK, GPIO_FUNC_SPI);
gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI);
spi_init(SPI_PORT, SPI_SPEED);
gpio_put(PIN_RST, 0);
sleep_ms(100);
gpio_put(PIN_RST, 1);
gpio_put(PIN_CS, 1);
break;
case U8X8_MSG_DELAY_NANO: // delay arg_int * 1 nano second
sleep_us(arg_int); // 1000 times slower, though generally fine in practice given rp2040 has no `sleep_ns()`
break;
case U8X8_MSG_DELAY_100NANO: // delay arg_int * 100 nano seconds
sleep_us(arg_int);
break;
case U8X8_MSG_DELAY_10MICRO: // delay arg_int * 10 micro seconds
sleep_us(arg_int * 10);
break;
case U8X8_MSG_DELAY_MILLI: // delay arg_int * 1 milli second
sleep_ms(arg_int);
break;
case U8X8_MSG_GPIO_CS: // CS (chip select) pin: Output level in arg_int
gpio_put(PIN_CS, arg_int);
break;
case U8X8_MSG_GPIO_DC: // DC (data/cmd, A0, register select) pin: Output level
break;
case U8X8_MSG_GPIO_RESET: // Reset pin: Output level in arg_int
gpio_put(PIN_RST, arg_int); // printf("U8X8_MSG_GPIO_RESET %d\n", arg_int);
break;
default:
u8x8_SetGPIOResult(u8x8, 1); // default return value
break;
}
return 1;
}
uint8_t u8x8_byte_pico_hw_spi(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
uint8_t *data;
switch (msg)
{
case U8X8_MSG_BYTE_SEND:
spi_write_blocking(SPI_PORT, (uint8_t*)arg_ptr, arg_int);
break;
case U8X8_MSG_BYTE_INIT:
break;
case U8X8_MSG_BYTE_SET_DC:
break;
case U8X8_MSG_BYTE_START_TRANSFER:
gpio_put(PIN_CS, 1);
break;
case U8X8_MSG_BYTE_END_TRANSFER:
gpio_put(PIN_CS, 0);
break;
default:
return 0;
}
return 1;
}
st7920_spi_u8g2_hal.h
#ifndef ST7920_SPI_U8G2_HAL_H
#define ST7920_SPI_U8G2_HAL_H
// Reference: https://github.com/olikraus/u8g2/issues/2159
#include <u8g2.h>
#include "pico/stdlib.h"
#include "hardware/spi.h"
// zapojení displeje a parametry DPI sběrnice
#define SPI_PORT spi0
#define PIN_CS 5
#define PIN_SCK 2
#define PIN_MOSI 3
#define SPI_SPEED 1000 * 1000
#define PIN_RST 13
void st7920_writeReg_SPI(uint8_t aByte);
void st7920_writeData_SPI(uint8_t aByte);
uint8_t u8x8_gpio_and_delay_pico(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
uint8_t u8x8_byte_pico_hw_spi(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
#endif
CMakeLists.txt
cmake_minimum_required(VERSION 3.13)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffunction-sections -fdata-sections -Wl,--gc-sections")
# místo pico můžete napsat picow (Pico s Wifi) nebo pico2 (Pico 2 s procesorem RP3520)
set(PICO_BOARD pico CACHE STRING "Board type")
include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake)
# nastaveni jmena projektu
project(navigace C CXX ASM)
# nastaveni jmena spustitelneho souboru (abychom to nemuseli stále opisovat)
set(EXE navigace)
#inicializace Raspberry Pi Pico SDK
pico_sdk_init()
add_executable(${EXE}
st7920_spi_u8g2_hal.c
navigace.c
podsvit.c
nmea_parse.c
)
pico_set_program_name(${EXE} "navigace")
pico_set_program_version(${EXE} "0.10")
# nastavení standardního výstupu 0 znamená nepoužito, 1 znamená použito
pico_enable_stdio_uart(${EXE} 0)
pico_enable_stdio_usb(${EXE} 1)
# přidání standardní knihovny do projektu
target_link_libraries(${EXE}
pico_stdlib
)
# include adresáře
target_include_directories( ${EXE} PRIVATE
${CMAKE_CURRENT_LIST_DIR}
${CMAKE_CURRENT_LIST_DIR}/.. # pokud potřebujeme třeba pro lwipopts
u8g2/csrc
)
file(GLOB U8G2_SRC u8g2/csrc/*.c)
add_library(u8g2 ${U8G2_SRC})
# volby pro linker
target_link_options( ${EXE} PRIVATE -Xlinker --print-memory-usage)
# další knihovny, které budeme v projektu potřebovat
target_link_libraries( ${EXE}
hardware_spi # hardware_spi potřebujeme pro displej
hardware_pwm # hardware_pwm je pro řízení jasu displeje
u8g2 # u8g2 potřebujeme pro displej
# dále doplňte knihovny, které potřebujete
hardware_rtc # hodiny reálného času
hardware_pwm # řízení podsvitu
pico_multicore # zapojení druhého jádra procesoru
)
# toto je potřeba pro generování uf2 souboru, který nahráváme do Pica
pico_add_extra_outputs(${EXE})
mkdir build
cd build
cmake ..
make -j8
make