diff --git a/conf/modules/digital_cam_pwm.xml b/conf/modules/digital_cam_pwm.xml
new file mode 100644
index 0000000000..174833b458
--- /dev/null
+++ b/conf/modules/digital_cam_pwm.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+ Trigger using pwm cameras like MAPIR (https://www.mapir.camera/en-gb)
+ or ThermalCapture 2.0 from TeAx (https://thermalcapture.com/thermalcapture-2-0/)
+
+ **** MAPIR ****
+ Duty cycle == 2000us : TRIGGER_ON
+ Duty cycle == 1500us : SD Unmount
+ Duty cycle == 1000us : TRIGGER_OFF
+ Image capture frequency : 1.5s for JPG and 2.5-3.0s RAW+JPG
+
+ The quickest the 2000us command should be sent is about once every 1.5s as the camera cannot
+ capture JPG images more quickly than 1.5s. For RAW+JPG mode we recommend a 2.5-3.0s wait time.
+
+ RAW images are used for capturing data for reflectance measurements, otherwise the pixels in the JPG are not usable.
+ im_freq_timer should therefore be superior 1.5s (TRIGGER_CAMERA_CAPTURE_IMAGE_PERIOD = 1.5 seconds)
+
+ NMEA GPS data are stored in metadata
+
+ **** ThermalCapture ****
+ Duty cycle inferior 1500us : TRIGGER_OFF
+ Duty cycle superior 1500us : TRIGGER_ON
+ Image capture frequency up to 30 Hz
+ Possibility to capture one frame per trigger in configurator : Trigger frame mode
+ Convert 14-bit data into temperature values :
+ High gain mode: temp [°C] = raw * 0.04 - 273.15
+ Low gain mode : temp [°C] = raw * 0.4 - 273.15
+
+ NMEA GPS data are stored in metadata
+
+
+
+
+
+ digital_cam_common
+ digital_cam_gpio,digital_cam_i2c,digital_cam_uart,digital_cam_video,digital_cam_pprzlink,digital_cam_servo
+ gps_nmea_send
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/conf/modules/gps_nmea_send.xml b/conf/modules/gps_nmea_send.xml
new file mode 100644
index 0000000000..9e15ab1487
--- /dev/null
+++ b/conf/modules/gps_nmea_send.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+ module used to send GPS data over for external instrument using NMEA input.
+ Example: MAPIR camera stores GPS data in metadata on each frame.
+
+
+
+
+
+
+ uart
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sw/airborne/modules/digital_cam/dc_shoot_pwm.c b/sw/airborne/modules/digital_cam/dc_shoot_pwm.c
new file mode 100644
index 0000000000..873c969576
--- /dev/null
+++ b/sw/airborne/modules/digital_cam/dc_shoot_pwm.c
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2025 Jean-Baptiste Forestier
+ *
+ * This file is part of paparazzi
+ *
+ * paparazzi is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * paparazzi is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with paparazzi; see the file COPYING. If not, see
+ * .
+ */
+/**
+ * @file "modules/digital_cam/dc_shoot_pwm.c"
+ * @author Jean-Baptiste Forestier
+ * Trigger using pwm cameras like MAPIR (https://www.mapir.camera/en-gb)
+ * or ThermalCapture 2.0 from TeAx (https://thermalcapture.com/thermalcapture-2-0/)
+ *
+ * **** MAPIR ****
+ * Duty cycle == 2000us => TRIGGER_ON
+ * Duty cycle == 1500us => SD Unmount
+ * Duty cycle == 1000us => TRIGGER_OFF
+ * Image capture frequency = 1.5s for JPG and 2.5-3.0s RAW+JPG
+ *
+ * The quickest the 2000us command should be sent is about once every 1.5s as the camera cannot
+ * capture JPG images more quickly than 1.5s. For RAW+JPG mode we recommend a 2.5-3.0s wait time.
+ *
+ * RAW images are used for capturing data for reflectance measurements, otherwise the pixels in the JPG are not usable.
+ * im_freq_timer should therefore be > 1.5s (=>TRIGGER_CAMERA_CAPTURE_IMAGE_PERIOD = 1.5 seconds)
+ *
+ * NMEA GPS data are stored in metadata
+ *
+ * **** ThermalCapture ****
+ * Duty cycle < 1500us => TRIGGER_OFF
+ * Duty cycle > 1500us => TRIGGER_ON
+ * Image capture frequency up to 30 Hz
+ * Possibility to capture one frame per trigger in configurator : Trigger frame mode
+ * Convert 14-bit data into temperature values :
+ * High gain mode: temp [°C] = raw * 0.04 - 273.15
+ * Low gain mode : temp [°C] = raw * 0.4 - 273.15
+ *
+ * NMEA GPS data are stored in metadata
+ *
+ * **** Trigger system ****
+ *
+ * TIME ────────────────────────────────────────────────────────────>
+ *
+ * PWM 1000us 2000us 1000us 2000us
+ * Vcc ┌────┐ ┌───────┐ ┌────┐ ┌───────┐
+ * │ OFF│ │ ON │ │ OFF│ │ ON │
+ * 0V ────┘ └────────┘ └───────┘ └────────┘ └────
+ * im_freq_timer: ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 0 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 0 ↓↓↓↓↓↓↓↓↓↓
+ * shutter_timer: ↓↓↓↓ 0 ↓↓↓↓ 0
+ *
+ * ↑ ↑
+ * Picture 1 Picture 2
+ *
+ */
+
+#include "modules/digital_cam/dc_shoot_pwm.h"
+
+// Include Standard Camera Control Interface
+#include "dc.h"
+
+#include "generated/airframe.h"
+#include "generated/modules.h"
+#include "modules/actuators/actuators.h"
+
+
+/** how long to push shutter in seconds */
+#ifndef DC_CAM_PWM_SHUTTER_DELAY
+#define DC_CAM_PWM_SHUTTER_DELAY 0.1
+#endif
+
+/** Max PWM Value */
+#ifndef DC_CAM_PWM_ON_VALUE
+#define DC_CAM_PWM_ON_VALUE MAX_PPRZ
+#endif
+
+/** Min PWM Value */
+#ifndef DC_CAM_PWM_OFF_VALUE
+#define DC_CAM_PWM_OFF_VALUE MIN_PPRZ
+#endif
+
+/** Servo destination */
+#ifndef DC_CAM_PWM_SERVO
+#define DC_CAM_PWM_SERVO DC_CAM_TRIGGER
+#endif
+
+#define CamActuatorSet(_a, _v) ActuatorSet(_a, _v)
+
+/**
+ * Timer used for Shutter delay control
+ */
+static uint8_t shutter_timer;
+
+/**
+ * Initialization function
+ */
+void dc_shoot_pwm_init(void)
+{
+ shutter_timer = 0;
+ CamActuatorSet(DC_CAM_PWM_SERVO, DC_CAM_PWM_OFF_VALUE);
+}
+
+/**
+ * Periodic function to send data
+ */
+void dc_shoot_pwm_periodic(void)
+{
+ // Manage the shutter opening time each DC_CAM_PWM_SHUTTER_DELAY seconds
+ if (shutter_timer == 0) {
+ CamActuatorSet(DC_CAM_PWM_SERVO, DC_CAM_PWM_OFF_VALUE);
+ } else {
+ shutter_timer--;
+ }
+
+ // Common DC Periodic task
+ dc_periodic();
+}
+
+/* Command The Camera */
+void dc_send_command(uint8_t cmd)
+{
+ if (cmd == DC_SHOOT) {
+ CamActuatorSet(DC_CAM_PWM_SERVO, DC_CAM_PWM_ON_VALUE);
+ shutter_timer = DC_CAM_PWM_SHUTTER_DELAY * DC_SHOOT_PWM_PERIODIC_FREQ;
+ dc_send_shot_position();
+ }
+
+ // call command send_command function
+ dc_send_command_common(cmd);
+}
+
diff --git a/sw/airborne/modules/digital_cam/dc_shoot_pwm.h b/sw/airborne/modules/digital_cam/dc_shoot_pwm.h
new file mode 100644
index 0000000000..22f987bc33
--- /dev/null
+++ b/sw/airborne/modules/digital_cam/dc_shoot_pwm.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2025 Jean-Baptiste Forestier
+ *
+ * This file is part of paparazzi
+ *
+ * paparazzi is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * paparazzi is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with paparazzi; see the file COPYING. If not, see
+ * .
+ */
+/**
+ * @file "modules/digital_cam/dc_shoot_pwm.h
+ * @author Jean-Baptiste Forestier
+ * Trigger using pwm cameras like MAPIR (https://www.mapir.camera/en-gb)
+ * or ThermalCapture 2.0 from TeAx (https://thermalcapture.com/thermalcapture-2-0/)
+ *
+ * **** MAPIR ****
+ * Duty cycle == 2000us => TRIGGER_ON
+ * Duty cycle == 1500us => SD Unmount
+ * Duty cycle == 1000us => TRIGGER_OFF
+ * Image capture frequency = 1.5s for JPG and 2.5-3.0s RAW+JPG
+ *
+ * The quickest the 2000us command should be sent is about once every 1.5s as the camera cannot
+ * capture JPG images more quickly than 1.5s. For RAW+JPG mode we recommend a 2.5-3.0s wait time.
+ *
+ * RAW images are used for capturing data for reflectance measurements, otherwise the pixels in the JPG are not usable.
+ * im_freq_timer should therefore be > 1.5s (=>TRIGGER_CAMERA_CAPTURE_IMAGE_PERIOD = 1.5 seconds)
+ *
+ * NMEA GPS data are stored in metadata
+ *
+ * **** ThermalCapture ****
+ * Duty cycle < 1500us => TRIGGER_OFF
+ * Duty cycle > 1500us => TRIGGER_ON
+ * Image capture frequency up to 30 Hz
+ * Possibility to capture one frame per trigger in configurator : Trigger frame mode
+ * Convert 14-bit data into temperature values :
+ * High gain mode: temp [°C] = raw * 0.04 - 273.15
+ * Low gain mode : temp [°C] = raw * 0.4 - 273.15
+ *
+ * NMEA GPS data are stored in metadata
+ *
+ * **** Trigger system ****
+ *
+ * TIME ────────────────────────────────────────────────────────────>
+ *
+ * PWM 1000us 2000us 1000us 2000us
+ * Vcc ┌────┐ ┌───────┐ ┌────┐ ┌───────┐
+ * │ OFF│ │ ON │ │ OFF│ │ ON │
+ * 0V ────┘ └────────┘ └───────┘ └────────┘ └────
+ * im_freq_timer: ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 0 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 0 ↓↓↓↓↓↓↓↓↓↓
+ * shutter_timer: ↓↓↓↓ 0 ↓↓↓↓ 0
+ *
+ * ↑ ↑
+ * Picture 1 Picture 2
+ *
+ * im_freq_timer should therefore be > 1.5s (=>TRIGGER_CAMERA_CAPTURE_IMAGE_PERIOD = 1.5 seconds)
+ *
+ */
+
+
+#ifndef DC_SHOOT_PWM_H
+#define DC_SHOOT_PWM_H
+
+#include "std.h"
+
+void dc_shoot_pwm_init(void);
+void dc_shoot_pwm_periodic(void);
+
+#endif
+
diff --git a/sw/airborne/modules/gps/gps_nmea_send.c b/sw/airborne/modules/gps/gps_nmea_send.c
new file mode 100644
index 0000000000..fbdd89658d
--- /dev/null
+++ b/sw/airborne/modules/gps/gps_nmea_send.c
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2025 Jean-Baptiste FORESTIER
+ *
+ * This file is part of paparazzi
+ *
+ * paparazzi is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * paparazzi is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with paparazzi; see the file COPYING. If not, see
+ * .
+ */
+/**
+ * @file "modules/gps/gps_nmea_send.c"
+ * @author Jean-Baptiste FORESTIER
+ * @brief module used to send GPS data over a Tawaki UART for extern instrument using NMEA protocol
+ * Exemple of use : MAPIR camera stores GPS data in metadata on each frame
+ */
+
+
+
+#include "modules/gps/gps_nmea_send.h"
+#include "mcu_periph/uart.h"
+#include "generated/airframe.h"
+#include "state.h" // State interface for rotation compensation
+#include "modules/gps/gps.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+// ** Declaration ** //
+struct Gps_Nmea_Send gps_nmea_send;
+
+static void recover_gps_data(void);
+static void build_nmea_sentence(void);
+static uint8_t nmea_checksum(const char *sentence, int length);
+static void nmea_convert_deg_to_DDMM(double deg, char *buf, int is_lat);
+static void gps_nmea_get_system_date_str(char *buf_date, size_t buf_date_size, char *buf_time, size_t buf_time_size);
+static void nmea_send(const char *payload, int payload_length);
+
+
+/**
+ * Initialization function
+ */
+void gps_nmea_send_init(void)
+{
+ gps_nmea_send.error_init = false;
+
+ gps_nmea_send.msg.lat = 0;
+ gps_nmea_send.msg.lon = 0;
+ gps_nmea_send.msg.num_sv = 0;
+ gps_nmea_send.msg.pdop = 0;
+ gps_nmea_send.msg.hmsl = 0;
+ gps_nmea_send.msg.vground = 0;
+ gps_nmea_send.msg.course = 0;
+}
+
+/**
+ * Periodic function to send data
+ */
+void gps_nmea_send_periodic(void)
+{
+ recover_gps_data();
+ build_nmea_sentence();
+}
+
+
+/**
+ * Function to recover data available onboard from GPS
+ */
+void recover_gps_data(void)
+{
+#if GPS_NMEA_SEND_USE_STATE_DATA
+ struct LlaCoor_f *lla = stateGetPositionLla_f();
+ gps_nmea_send.msg.lat = (double)gps->lat;
+ gps_nmea_send.msg.lon = (double)gps->lon;
+ gps_nmea_send.msg.hmsl = stateGetHmslOrigin_f() + stateGetPositionEnu_f()->z;
+ gps_nmea_send.msg.vground = stateGetHorizontalSpeedNorm_f();
+ gps_nmea_send.msg.course = NormCourseRad(stateGetHorizontalSpeedDir_f());
+#else // Default, use unfiltered GPS data
+ gps_nmea_send.msg.lat = (double)gps.lla_pos.lat / 1e7;
+ gps_nmea_send.msg.lon = (double)gps.lla_pos.lon / 1e7;
+ gps_nmea_send.msg.hmsl = (float)gps.hmsl / 1000.f; //in meters
+ gps_nmea_send.msg.vground = (float)gps.gspeed / 100.f; // in meters
+ gps_nmea_send.msg.course = (float)DegOfRad(gps.course / 1e7);
+#endif
+ gps_nmea_send.msg.num_sv = gps.num_sv;
+ gps_nmea_send.msg.pdop = gps.pdop;
+}
+
+/**
+ * Function to build sentence for NMEA Protocol
+ */
+void build_nmea_sentence(void)
+{
+ // Latitude and Longitude
+ char lat_buf[16], lon_buf[16], gga[120], rmc[120], date_sys[6], time_sys[6];
+ int len_gga = 0;
+ int len_rmc = 0;
+ uint8_t cs;
+ nmea_convert_deg_to_DDMM(gps_nmea_send.msg.lat, lat_buf, 1);
+ nmea_convert_deg_to_DDMM(gps_nmea_send.msg.lon, lon_buf, 0);
+ char lat_hemi = (gps_nmea_send.msg.lat >= 0) ? 'N' : 'S';
+ char lon_hemi = (gps_nmea_send.msg.lon >= 0) ? 'E' : 'W';
+
+ for (int i = 0; i < 120; i++) { gga[i] = '\0'; rmc[i] = '\0'; }
+
+ if (gps.fix >= GPS_FIX_3D) {
+ gps_nmea_get_system_date_str(date_sys, sizeof(date_sys) + 1, time_sys, sizeof(time_sys) + 1);
+
+ // -------------------------------
+ // GPGGA Frame
+ // -------------------------------
+ sprintf(gga,
+ "$GPGGA,%s,%s,%c,%s,%c,1,%02u,%04u,%09.3f,M,0,M,,",
+ time_sys, lat_buf, lat_hemi, lon_buf, lon_hemi,
+ gps_nmea_send.msg.num_sv, gps_nmea_send.msg.pdop,
+ gps_nmea_send.msg.hmsl);
+ len_gga = 7; // $GPGGA, = 7
+ len_gga += 7; // time_nmea, = 7
+ len_gga += 10; // lat_buf = 9 + comma
+ len_gga += 2; // lat_hemi = N,
+ len_gga += 11; // lon_buf = 10 + comma
+ len_gga += 2; // lon_hemi = W,
+ len_gga += 2; // GPS Quality indicator GPS fix = 1,
+ len_gga += 3; // number of sats = 08,
+ len_gga += 5; // position dilution of precision scaled by 100 = 1020,
+ len_gga += 10; // height above mean sea level (MSL) in m = 02945.127,
+ len_gga += 2; // height above mean sea level unit = M,
+ len_gga += 2; // Ellipsoid to geoid distance set 0, don't where to find it = 0,
+ len_gga += 2; // Distance above unit = M,
+ len_gga += 1; // Empty = ,
+
+ cs = nmea_checksum(gga, len_gga);
+ sprintf(gga + len_gga, "*%02X\r\n", cs);
+ len_gga += 5; // * + Checksum + \r\n = *4F\r\n
+
+ nmea_send(gga, len_gga);
+
+ // -------------------------------
+ // GPRMC Frame
+ // -------------------------------
+ sprintf(rmc,
+ "$GPRMC,%s,A,%s,%c,%s,%c,%05.1f,%05.1f,%s,000.0,W",
+ time_sys, lat_buf, lat_hemi, lon_buf, lon_hemi,
+ gps_nmea_send.msg.vground, gps_nmea_send.msg.course, date_sys);
+ len_rmc = 7; // $GPRMC, = 7
+ len_rmc += 7; // time_nmea, = 7
+ len_rmc += 2; // Status A=active or V=void = A,
+ len_rmc += 10; // lat_buf = 9 + comma
+ len_rmc += 2; // lat_hemi = N,
+ len_rmc += 11; // lon_buf = 10 + comma
+ len_rmc += 2; // lon_hemi = W,
+ len_rmc += 6; // vground = 050.4,
+ len_rmc += 6; // track angle course = 050.4,
+ len_rmc += 7; // Date = 230394,
+ len_rmc += 7; // Magnetic variation, in degrees = 003.1,W,
+
+ cs = nmea_checksum(rmc, len_rmc);
+ sprintf(rmc + len_rmc, "*%02X\r\n", cs);
+ len_rmc += 5; // * + Checksum + \r\n = *4F\r\n
+
+ nmea_send(rmc, len_rmc);
+
+ } else {
+ //If no fix, empty GGA
+ sprintf(gga, "$GPGGA,,,,,,0,00,99.99,,,,,,*68\r\n");
+ nmea_send(gga, 33);
+
+ //If no fix, empty RMC
+ sprintf(rmc, "$GPRMC,,V,,,,,,,,,,*53\r\n");
+ nmea_send(rmc, 24);
+ }
+}
+
+/**
+ * Function to calculate checksum
+ */
+uint8_t nmea_checksum(const char *sentence, int length)
+{
+ uint8_t cs = 0;
+ for (int i = 0; i < length; i++) {
+ cs ^= (uint8_t)sentence[i];
+ }
+ return cs;
+}
+
+/**
+ * Function to convert lat long in DDLL.MMMM Format
+ * Lat and long don't have the same length
+ */
+void nmea_convert_deg_to_DDMM(double deg, char *buf, int is_lat)
+{
+ double abs_deg = fabs(deg);
+ int d = (int)abs_deg;
+ double minutes = (abs_deg - d) * 60.0;
+
+ if (is_lat) {
+ // 2 integers for degrees 0 to 90 without sign => %02d | 2 integers for minutes, 1 for point, 4 for minutes decimals
+ sprintf(buf, "%02d%07.4f", d, minutes);
+ } else {
+ // 3 integers for degrees 0 to 180 without sign => %03d | 2 integers for minutes, 1 for point, 4 for minutes decimals
+ sprintf(buf, "%03d%07.4f", d, minutes);
+ }
+}
+
+/**
+ * Get Date from system
+ */
+void gps_nmea_get_system_date_str(char *buf_date, size_t buf_date_size, char *buf_time, size_t buf_time_size)
+{
+ // days from 1970-01-01
+ const int64_t gps_epoch_days = 3657;
+ // Total time gps
+ uint64_t gps_sec = (uint64_t)gps.week * 7 * 24 * 3600 + gps.tow / 1000;
+ // Day gps + gps epoch
+ int64_t days = gps_epoch_days + gps_sec / 86400;
+
+ // Algo to get year month day from Week and tow
+ // Shift day count so that day 0 corresponds to the Gregorian epoch (0000-03-01)
+ days += 719468;
+ // Determine the 400-year Gregorian cycle, One Gregorian era = 400 years = 146097 days.
+ int64_t era = (days >= 0 ? days : days - 146096) / 146097;
+ // Day Of Era: number of days elapsed within the current 400-year cycle.
+ uint64_t doe = (uint64_t)(days - era * 146097);
+ // Year Of Era: compute the year number inside the cycle, correcting for leap years (every 4 years, except centuries, except 400-year multiples).
+ uint64_t yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
+ // Convert "year in era" to an absolute Gregorian year.
+ int64_t y = (int64_t)yoe + era * 400;
+ // Day Of Year: subtract all full days from completed years to get day index within the year.
+ uint64_t doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
+ // Month part: day-of-year into a month index in a calendar where March = 0 and February = 11
+ uint64_t mp = (5 * doy + 2) / 153;
+ // Compute the day-of-month
+ uint64_t d = doy - (153 * mp + 2) / 5 + 1;
+ // Convert shifted month index back to standard month 0-12
+ uint64_t m = mp + (mp < 10 ? 3 : -9);
+ y += (m <= 2);
+
+ // Hours / minutes / secondes
+ uint32_t sec_of_day = gps_sec % 86400;
+ uint8_t hour = sec_of_day / 3600;
+ uint8_t min = (sec_of_day % 3600) / 60;
+ uint8_t sec = sec_of_day % 60;
+
+ // Format : DDMMYY
+ snprintf(buf_date, buf_date_size, "%02d%02d%02d",
+ (uint8_t)d,
+ (uint8_t)m,
+ (uint8_t)(y % 100));
+
+ // Format : HHMMSS
+ snprintf(buf_time, buf_time_size, "%02d%02d%02d",
+ hour,
+ min,
+ sec);
+}
+
+/**
+ * Function to send payload to UART
+ */
+void nmea_send(const char *payload, int payload_length)
+{
+ uart_put_buffer(&NMEA_SEND_UART, 0, (const uint8_t *)payload, payload_length);
+}
+
diff --git a/sw/airborne/modules/gps/gps_nmea_send.h b/sw/airborne/modules/gps/gps_nmea_send.h
new file mode 100644
index 0000000000..a5053c1668
--- /dev/null
+++ b/sw/airborne/modules/gps/gps_nmea_send.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2025 Jean-Baptiste FORESTIER
+ *
+ * This file is part of paparazzi
+ *
+ * paparazzi is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * paparazzi is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with paparazzi; see the file COPYING. If not, see
+ * .
+ */
+/**
+ * @file "modules/gps/gps_nmea_send.h"
+ * @author Jean-Baptiste FORESTIER
+ * @brief module used to send GPS data over a Tawaki UART for extern instrument using NMEA protocol
+ * Exemple of use : MAPIR camera stores GPS data in metadata on each frame
+ */
+
+#ifndef GPS_NMEA_SEND_H
+#define GPS_NMEA_SEND_H
+
+#include "std.h"
+
+struct gps_nmea_send_msg_t {
+ double lat; ///< Latitude
+ double lon; ///< Longiitude
+ uint8_t num_sv; ///< number of sat in fix
+ uint16_t pdop; ///< position dilution of precision scaled by 100
+ float hmsl; ///< Orthometric height (MSL reference)
+ float vground; ///< Speed over ground in m/s
+ float course; ///< GPS course over ground in rad*1e7, [0, 2*Pi]*1e7 (CW/north)
+};
+
+struct Gps_Nmea_Send {
+ bool error_init; // Flag to indicate if there was an error during initialization
+ struct gps_nmea_send_msg_t msg;
+};
+
+extern void gps_nmea_send_init(void);
+extern void gps_nmea_send_periodic(void);
+
+#endif