// gps-neo6m.chip.c - GPS NEO-6M Module Simulator
// Based on work by Uri Shaked (https://wokwi.com/projects/296541575458390025)
// Modified for standard NMEA output and improved time/date simulation.
// Corrected to be compatible with latest Wokwi API and C syntax.
#include "wokwi-api.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Define structures for Wokwi API compatibility (if not already defined in wokwi-api.h)
// These definitions are for clarity and ensure compatibility with the call style.
// If your wokwi-api.h already defines these, it's harmless.
typedef struct {
pin_t rx;
pin_t tx;
uint32_t baud_rate;
} uart_config_t;
typedef struct {
uint32_t us; // Interval in microseconds
bool repeat;
void (*callback)(void *);
void *user_data;
} timer_config_t;
typedef struct {
pin_t rx_pin;
pin_t tx_pin;
uart_dev_t* uart;
uint32_t timer_interval_ms;
timer_t timer;
long last_fix_time;
double latitude;
double longitude;
double altitude;
int speed_knots;
int course_deg;
int fix_quality; // 0=invalid, 1=GPS fix, 2=DGPS fix
int num_satellites;
int year, month, day, hour, minute, second;
} chip_state_t;
static void chip_timer_event(void *user_data);
void chip_init() {
chip_state_t *state = (chip_state_t*)malloc(sizeof(chip_state_t));
state->rx_pin = pin_init("RX", INPUT);
state->tx_pin = pin_init("TX", OUTPUT);
// Configure UART using the new uart_config_t struct
uart_config_t uart_cfg = {
.rx = state->rx_pin,
.tx = state->tx_pin,
.baud_rate = 9600
};
state->uart = uart_init(&uart_cfg); // Corrected uart_init call
state->timer_interval_ms = 1000; // Send new data every 1 second
state->latitude = 31.2304; // Default latitude (e.g., Cairo, Egypt)
state->longitude = 30.0595; // Default longitude
state->altitude = 50.0; // Default altitude in meters
state->speed_knots = 5; // Default speed
state->course_deg = 90; // Default course (East)
state->fix_quality = 1; // GPS fix
state->num_satellites = 8;
// Initial time (adjusted to a plausible future time relative to compilation)
state->year = 2024;
state->month = 7;
state->day = 15;
state->hour = 12;
state->minute = 0;
state->second = 0;
state->last_fix_time = get_sim_millis(); // Corrected: use get_sim_millis()
// Configure Timer using the new timer_config_t struct
timer_config_t timer_cfg = {
.us = state->timer_interval_ms * 1000, // Convert ms to us
.repeat = true,
.callback = chip_timer_event,
.user_data = state
};
state->timer = timer_init(&timer_cfg); // Corrected timer_init call
// Corrected timer_start call: timer, microseconds interval, repeat boolean
timer_start(state->timer, state->timer_interval_ms * 1000, true);
printf("GPS NEO-6M simulator initialized.\n");
}
// Function to convert decimal degrees to degrees and decimal minutes (DDMM.MMMM)
// Corrected: `char &hemisphere` changed to `char *hemisphere` for C compatibility
static void convert_coord_to_nmea(double coord, char *buffer, size_t buffer_size, char *hemisphere) {
*hemisphere = (coord >= 0) ? ((coord > 0) ? 'N' : 'E') : ((coord < 0) ? 'S' : 'W');
if (coord < 0) coord = -coord;
int degrees = (int)coord;
double minutes = (coord - degrees) * 60.0;
snprintf(buffer, buffer_size, "%02d%07.4f", degrees, minutes); // DDMM.MMMM (or DDDMM.MMMM for longitude)
}
// Function to calculate NMEA checksum
static uint8_t calculate_nmea_checksum(const char *sentence) {
uint8_t checksum = 0;
int i = 1; // Start after '$'
while (sentence[i] != '*' && sentence[i] != '\0') {
checksum ^= sentence[i];
i++;
}
return checksum;
}
// Send a single NMEA sentence
static void send_nmea_sentence(chip_state_t *state, const char *sentence) {
// Corrected: use uart_write instead of uart_write_bytes
uart_write(state->uart, (const uint8_t*)sentence, strlen(sentence));
uart_write(state->uart, (const uint8_t*)"\r\n", 2);
}
// Generate and send $GPRMC sentence
static void generate_gprmc(chip_state_t *state) {
char buffer[100];
char lat_nmea[12], lon_nmea[12];
char lat_hemi_char, lon_hemi_char; // Use temporary char variables
// Corrected: Pass address of char variables
convert_coord_to_nmea(state->latitude, lat_nmea, sizeof(lat_nmea), &lat_hemi_char);
convert_coord_to_nmea(state->longitude, lon_nmea, sizeof(lon_nmea), &lon_hemi_char);
snprintf(buffer, sizeof(buffer),
"$GPRMC,%02d%02d%02d.00,A,%.*s,%c,%.*s,%c,%.2f,%.2f,%02d%02d%02d,000.0,W,A",
state->hour, state->minute, state->second,
(int)(strchr(lat_nmea, '.') - lat_nmea + 5), lat_nmea, lat_hemi_char,
(int)(strchr(lon_nmea, '.') - lon_nmea + 5), lon_nmea, lon_hemi_char,
(double)state->speed_knots, (double)state->course_deg,
state->day, state->month, state->year % 100); // YY format for date
uint8_t checksum = calculate_nmea_checksum(buffer);
char final_sentence[120];
snprintf(final_sentence, sizeof(final_sentence), "%s*%02X", buffer, checksum);
send_nmea_sentence(state, final_sentence);
}
// Generate and send $GPGGA sentence
static void generate_gpgga(chip_state_t *state) {
char buffer[100];
char lat_nmea[12], lon_nmea[12];
char lat_hemi_char, lon_hemi_char; // Use temporary char variables
// Corrected: Pass address of char variables
convert_coord_to_nmea(state->latitude, lat_nmea, sizeof(lat_nmea), &lat_hemi_char);
convert_coord_to_nmea(state->longitude, lon_nmea, sizeof(lon_nmea), &lon_hemi_char);
snprintf(buffer, sizeof(buffer),
"$GPGGA,%02d%02d%02d.00,%.*s,%c,%.*s,%c,%d,%02d,1.0,%.1f,M,-34.0,M,,*",
state->hour, state->minute, state->second,
(int)(strchr(lat_nmea, '.') - lat_nmea + 5), lat_nmea, lat_hemi_char,
(int)(strchr(lon_nmea, '.') - lon_nmea + 5), lon_nmea, lon_hemi_char,
state->fix_quality, state->num_satellites,
state->altitude);
uint8_t checksum = calculate_nmea_checksum(buffer);
char final_sentence[120];
snprintf(final_sentence, sizeof(final_sentence), "%s*%02X", buffer, checksum);
send_nmea_sentence(state, final_sentence);
}
static void chip_timer_event(void *user_data) {
chip_state_t *state = (chip_state_t*)user_data;
long current_time = get_sim_millis(); // Corrected: use get_sim_millis()
// Update time
state->second++;
if (state->second >= 60) {
state->second = 0;
state->minute++;
if (state->minute >= 60) {
state->minute = 0;
state->hour++;
if (state->hour >= 24) {
state->hour = 0;
state->day++;
// Simple day increment, not handling month/year rollovers for brevity
// For a real sim, you'd add full date logic.
if (state->day > 31) state->day = 1; // Simplified for simulation
}
}
}
// Simulate slight movement or data change
state->latitude += 0.00001; // Tiny change
state->longitude += 0.00002;
state->altitude = 50.0 + (double)(rand() % 100 - 50) / 10.0; // +/- 5m variance
generate_gprmc(state);
delay_ms(10); // Corrected: use delay_ms()
generate_gpgga(state);
}