diff --git a/conf/modules/system_core.xml b/conf/modules/system_core.xml
index aed9d8e3b8..be26138c33 100644
--- a/conf/modules/system_core.xml
+++ b/conf/modules/system_core.xml
@@ -9,7 +9,7 @@
- mcu,math,state_interface,@actuators|@intermcu,settings|@no_settings,@datalink,@telemetry
+ mcu,threads,math,state_interface,@actuators|@intermcu,settings|@no_settings,@datalink,@telemetry
core
diff --git a/conf/modules/thd_test.xml b/conf/modules/thd_test.xml
new file mode 100644
index 0000000000..e10cf106e2
--- /dev/null
+++ b/conf/modules/thd_test.xml
@@ -0,0 +1,37 @@
+
+
+
+
+ A module to test threads functionnalities, like threads, mutexes, semaphores and so on.
+ A thread follow the syracuse suit, cadenced by the main ap thread from a periodic
+ function signaling a binary semaphore.
+ The counter thread and the AP thread exchange data protected by a mutex.
+ The counter thread is joined by the AP thread when the suit reach 1.
+ The counter thread can be restarted by setting the initial value from the settings: Threads test/thd_test_start_value
+
+
+
+
+
+
+
+
+
+
+
+ threads
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/conf/modules/threads.xml b/conf/modules/threads.xml
new file mode 100644
index 0000000000..164d15e9fb
--- /dev/null
+++ b/conf/modules/threads.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+ Provide threads and synchronization primitives.
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sw/airborne/arch/chibios/modules/core/threads_arch.c b/sw/airborne/arch/chibios/modules/core/threads_arch.c
new file mode 100644
index 0000000000..ddd093e674
--- /dev/null
+++ b/sw/airborne/arch/chibios/modules/core/threads_arch.c
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2025 The Paparazzi Team
+ *
+ * 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * See LICENSE file for the full license version, or see http://www.gnu.org/licenses/
+ */
+#include "modules/core/threads.h"
+#include "modules/core/threads_arch.h"
+#include "stdbool.h"
+
+
+int pprz_mtx_init(pprz_mutex_t* mtx) {
+ chMtxObjectInit(&mtx->mtx);
+ return 0;
+}
+
+int pprz_mtx_lock(pprz_mutex_t* mtx) {
+ chMtxLock(&mtx->mtx);
+ return 0;
+}
+
+int pprz_mtx_trylock(pprz_mutex_t* mtx) {
+ return chMtxTryLock(&mtx->mtx) ? 0 : -1;
+}
+
+int pprz_mtx_unlock(pprz_mutex_t* mtx) {
+ chMtxUnlock(&mtx->mtx);
+ return 0;
+}
+
+
+
+void pprz_bsem_init(pprz_bsem_t* bsem, bool taken) {
+ chBSemObjectInit(&bsem->bsem, taken);
+}
+
+void pprz_bsem_wait(pprz_bsem_t* bsem) {
+ chBSemWait(&bsem->bsem);
+}
+
+void pprz_bsem_signal(pprz_bsem_t* bsem) {
+ chBSemSignal(&bsem->bsem);
+}
+
+
+int pprz_thread_create(pprz_thread_t* thread, size_t size, const char *name, uint8_t prio, void (*pf)(void *), void* arg) {
+ *thread = chThdCreateFromHeap(NULL, THD_WORKING_AREA_SIZE(size), name, prio, pf, arg);
+ if(*thread == NULL) {
+ return -1;
+ }
+ return 0;
+}
+
+void pprz_thread_exit(void *retval) {
+ chThdExit((msg_t)retval);
+}
+
+
+int pprz_thread_join(pprz_thread_t* thread, void** retval) {
+ *retval = (void*)chThdWait(*thread);
+ return 0;
+}
+
+
+int pprz_thread_tryjoin(pprz_thread_t* thread, void** retval) {
+ if(chThdTerminatedX(*thread)) {
+ return pprz_thread_join(thread, retval);
+ }
+ return -1;
+}
diff --git a/sw/airborne/arch/chibios/modules/core/threads_arch.h b/sw/airborne/arch/chibios/modules/core/threads_arch.h
new file mode 100644
index 0000000000..670d6930aa
--- /dev/null
+++ b/sw/airborne/arch/chibios/modules/core/threads_arch.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2025 The Paparazzi Team
+ *
+ * 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * See LICENSE file for the full license version, or see http://www.gnu.org/licenses/
+ */
+
+#pragma once
+
+#include
+
+#define PPRZ_NORMAL_PRIO NORMALPRIO
+
+#define THREADS_ATTRIBUTES
+
+struct pprzMutex {
+ mutex_t mtx;
+};
+
+struct pprzBSem {
+ binary_semaphore_t bsem;
+};
+
+
+typedef thread_t* pprz_thread_t;
diff --git a/sw/airborne/arch/linux/modules/core/threads_arch.c b/sw/airborne/arch/linux/modules/core/threads_arch.c
new file mode 100644
index 0000000000..a4c1e22de7
--- /dev/null
+++ b/sw/airborne/arch/linux/modules/core/threads_arch.c
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2025 The Paparazzi Team
+ *
+ * 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * See LICENSE file for the full license version, or see http://www.gnu.org/licenses/
+ */
+#include "modules/core/threads.h"
+#include "modules/core/threads_arch.h"
+#include "stdbool.h"
+#include
+#include
+
+
+int pprz_mtx_init(pprz_mutex_t* mtx) {
+ return pthread_mutex_init(&mtx->mtx, NULL);
+}
+
+int pprz_mtx_lock(pprz_mutex_t* mtx) {
+ return pthread_mutex_lock(&mtx->mtx);
+}
+
+int pprz_mtx_trylock(pprz_mutex_t* mtx) {
+ return pthread_mutex_trylock(&mtx->mtx);
+}
+
+int pprz_mtx_unlock(pprz_mutex_t* mtx) {
+ return pthread_mutex_unlock(&mtx->mtx);
+}
+
+
+
+void pprz_bsem_init(pprz_bsem_t* bsem, bool taken) {
+ pthread_mutex_init(&bsem->mtx, NULL);
+ sem_init(&bsem->sem, 0, taken ? 0: 1);
+}
+
+void pprz_bsem_wait(pprz_bsem_t* bsem) {
+ sem_wait(&bsem->sem);
+}
+
+void pprz_bsem_signal(pprz_bsem_t* bsem) {
+ int val;
+ pthread_mutex_lock(&bsem->mtx);
+ if(!sem_getvalue(&bsem->sem, &val)) {
+ if(val < 1) {
+ sem_post(&bsem->sem);
+ }
+ }
+ pthread_mutex_unlock(&bsem->mtx);
+}
+
+
+
+// stub function argument: the thread function to run with its argument.
+struct stub_arg
+{
+ void (*func)(void*);
+ void* arg;
+};
+
+// stub function, ran in a new thread
+static void* stub(void* arg) {
+ struct stub_arg* sa = (struct stub_arg*) arg;
+ sa->func(sa->arg);
+ free(sa);
+ return NULL;
+}
+
+int pprz_thread_create(pprz_thread_t* thread, size_t size, const char *name, uint8_t prio, void (*func)(void*), void* arg) {
+ (void)size;
+ (void)prio;
+ struct stub_arg *sa = malloc(sizeof(struct stub_arg)); // allocate a stub arg. Must be allocated on the heap
+ sa->func = func;
+ sa->arg = arg;
+ int ret = pthread_create(thread, NULL, stub, (void*)sa); // launch the stub in a new thread
+ if(ret) {return ret;}
+ return pthread_setname_np(*thread, name);
+}
+
+void pprz_thread_exit(void *retval) {
+ pthread_exit(retval);
+}
+
+int pprz_thread_join(pprz_thread_t* thread, void** retval) {
+ return pthread_join(*thread, retval);
+}
+
+int pprz_thread_tryjoin(pprz_thread_t* thread, void** retval) {
+ return pthread_tryjoin_np(*thread, retval);
+}
diff --git a/sw/airborne/arch/linux/modules/core/threads_arch.h b/sw/airborne/arch/linux/modules/core/threads_arch.h
new file mode 100644
index 0000000000..896f4912ce
--- /dev/null
+++ b/sw/airborne/arch/linux/modules/core/threads_arch.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2025 The Paparazzi Team
+ *
+ * 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * See LICENSE file for the full license version, or see http://www.gnu.org/licenses/
+ */
+
+#pragma once
+
+#include
+#include
+
+#define PPRZ_NORMAL_PRIO 128
+
+#define THREADS_ATTRIBUTES
+
+struct pprzMutex {
+ pthread_mutex_t mtx;
+};
+
+struct pprzBSem {
+ pthread_mutex_t mtx;
+ sem_t sem;
+};
+
+
+typedef pthread_t pprz_thread_t;
diff --git a/sw/airborne/arch/sim/modules/core/threads_arch.c b/sw/airborne/arch/sim/modules/core/threads_arch.c
new file mode 120000
index 0000000000..60ceeec516
--- /dev/null
+++ b/sw/airborne/arch/sim/modules/core/threads_arch.c
@@ -0,0 +1 @@
+../../../linux/modules/core/threads_arch.c
\ No newline at end of file
diff --git a/sw/airborne/arch/sim/modules/core/threads_arch.h b/sw/airborne/arch/sim/modules/core/threads_arch.h
new file mode 120000
index 0000000000..d138d342ef
--- /dev/null
+++ b/sw/airborne/arch/sim/modules/core/threads_arch.h
@@ -0,0 +1 @@
+../../../linux/modules/core/threads_arch.h
\ No newline at end of file
diff --git a/sw/airborne/arch/stm32/modules/core/threads_arch.c b/sw/airborne/arch/stm32/modules/core/threads_arch.c
new file mode 100644
index 0000000000..fab9ba91a8
--- /dev/null
+++ b/sw/airborne/arch/stm32/modules/core/threads_arch.c
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2025 The Paparazzi Team
+ *
+ * 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * See LICENSE file for the full license version, or see http://www.gnu.org/licenses/
+ */
+#include "modules/core/threads.h"
+#include "modules/core/threads_arch.h"
+#include "stdbool.h"
+
+
+int pprz_mtx_init(pprz_mutex_t* mtx) {
+ (void)mtx;
+ return 0;
+}
+
+int pprz_mtx_lock(pprz_mutex_t* mtx) {
+ (void)mtx;
+ return 0;
+}
+
+int pprz_mtx_trylock(pprz_mutex_t* mtx) {
+ (void)mtx;
+ (void)mtx;
+ return 0;
+}
+
+int pprz_mtx_unlock(pprz_mutex_t* mtx) {
+ (void)mtx;
+ return 0;
+}
+
+
+
+void pprz_bsem_init(pprz_bsem_t* bsem, bool taken) {
+ bsem->value = taken ? 0: 1;
+}
+
+void pprz_bsem_wait(pprz_bsem_t* bsem) {
+ while (bsem->value == 0)
+ {
+ // active wait
+ asm("NOP");
+ }
+ bsem->value = 0;
+}
+
+void pprz_bsem_signal(pprz_bsem_t* bsem) {
+ bsem->value = 1;
+}
diff --git a/sw/airborne/arch/stm32/modules/core/threads_arch.h b/sw/airborne/arch/stm32/modules/core/threads_arch.h
new file mode 100644
index 0000000000..ceff0f05d7
--- /dev/null
+++ b/sw/airborne/arch/stm32/modules/core/threads_arch.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2025 The Paparazzi Team
+ *
+ * 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * See LICENSE file for the full license version, or see http://www.gnu.org/licenses/
+ */
+
+#pragma once
+
+#define PPRZ_NORMAL_PRIO NORMALPRIO
+
+#define THREADS_ATTRIBUTES __attribute__((error("Threads cannot be used in STM32 bare metal ARCH.")))
+
+struct pprzMutex {
+ int dummy; // avoid warning: empty declaration
+};
+
+struct pprzBSem {
+ volatile int value;
+};
+
+
+typedef int pprz_thread_t;
diff --git a/sw/airborne/modules/cam_control/point.c b/sw/airborne/modules/cam_control/point.c
index 2e74bdec70..46e9a49080 100644
--- a/sw/airborne/modules/cam_control/point.c
+++ b/sw/airborne/modules/cam_control/point.c
@@ -186,7 +186,7 @@ void vPoint(float fPlaneEast, float fPlaneNorth, float fPlaneAltitude,
if (cam_mode == CAM_MODE_STABILIZED || cam_mode == CAM_MODE_RC) {
// protect acces to fbw state
- PPRZ_MUTEX_LOCK(fbw_state_mtx);
+ pprz_mtx_lock(&fbw_state_mtx);
/*######################################## TILT CONTROL INPUT #############################################*/
#ifdef CAM_TILT_NEUTRAL
@@ -266,7 +266,7 @@ void vPoint(float fPlaneEast, float fPlaneNorth, float fPlaneAltitude,
#endif //#ifdef CAM_PAN_NEUTRAL
/*######################################## END OF PAN CONTROL INPUT #############################################*/
- PPRZ_MUTEX_UNLOCK(fbw_state_mtx);
+ pprz_mtx_unlock(&fbw_state_mtx);
// Bound Pan and Tilt angles.
if (cam_theta > RadOfDeg(CAM_TILT_MAX)) {
diff --git a/sw/airborne/modules/core/threads.h b/sw/airborne/modules/core/threads.h
new file mode 100644
index 0000000000..1feee7db7a
--- /dev/null
+++ b/sw/airborne/modules/core/threads.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2025 The Paparazzi Team
+ *
+ * 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * See LICENSE file for the full license version, or see http://www.gnu.org/licenses/
+ */
+
+#pragma once
+#include "stdbool.h"
+#include "modules/core/threads_arch.h"
+#include "stdint.h"
+#include
+
+
+typedef struct pprzMutex pprz_mutex_t;
+typedef struct pprzBSem pprz_bsem_t;
+
+
+int pprz_mtx_init(pprz_mutex_t* mtx);
+int pprz_mtx_lock(pprz_mutex_t* mtx);
+
+/**
+ * @brief Performs a nonblocking lock on the mutex.
+ * @return 0 if successful
+ */
+int pprz_mtx_trylock(pprz_mutex_t* mtx);
+int pprz_mtx_unlock(pprz_mutex_t* mtx);
+
+void pprz_bsem_init(pprz_bsem_t* bsem, bool taken);
+void pprz_bsem_wait(pprz_bsem_t* bsem);
+void pprz_bsem_signal(pprz_bsem_t* bsem);
+
+/**
+ * @brief Creates a new thread whose stack is dynamically allocated.
+ * @param[in] size size of the stack. Unused on linux at the moment.
+ * @param[in] prio the priority level for the new thread. Unused on linux at the moment. NORMALPRIO
+ * @param[in] pf the thread function
+ * @param[in] arg an argument passed to the thread function. It can be
+ * @p NULL.
+ * @param[out] thread pointer to a pprz_thread_t that will be writen with the handle to the thread
+ * @return Return 0 on success, or a platform dependant error code.
+ *
+ * @api
+ */
+int pprz_thread_create(pprz_thread_t* thread, size_t size, const char *name, uint8_t prio, void (*func)(void*), void* arg) THREADS_ATTRIBUTES;
+
+/**
+ * @brief Exit the current thread
+ * @param[out] retval The thread's return value
+ */
+void pprz_thread_exit(void *retval) THREADS_ATTRIBUTES;
+
+/**
+ * @brief Wait for the thread to terminate.
+ * @note Warning ! This is a blocking function, do not use it in the main AP thread !
+ * @param[in] thread The thread to be joined
+ * @param[out] retval The thread's return value will be written to the variable pointed by retval
+ */
+int pprz_thread_join(pprz_thread_t* thread, void** retval) THREADS_ATTRIBUTES;
+
+
+/**
+ * @brief Performs a nonblocking join with the thread.
+ * @param[in] thread The thread to be joined
+ * @param[out] retval The thread's return value will be written to the variable pointed by retval.
+ * must not be used if this fuction did not returned 0.
+ * @return 0 if the thread successfully joined.
+ */
+int pprz_thread_tryjoin(pprz_thread_t* thread, void** retval) THREADS_ATTRIBUTES;
diff --git a/sw/airborne/modules/datalink/bitcraze/syslink_dl.c b/sw/airborne/modules/datalink/bitcraze/syslink_dl.c
index 6bd8008330..7c2a17cbc0 100644
--- a/sw/airborne/modules/datalink/bitcraze/syslink_dl.c
+++ b/sw/airborne/modules/datalink/bitcraze/syslink_dl.c
@@ -30,12 +30,12 @@
#include "mcu_periph/uart.h"
#include
#include "led.h"
+#include "modules/core/threads.h"
struct syslink_dl syslink;
/** Protect syslink TX with Mutex when using RTOS */
-#include "pprz_mutex.h"
-PPRZ_MUTEX(syslink_tx_mtx);
+pprz_mutex_t syslink_tx_mtx;
/** Send a syslink message
*/
@@ -220,7 +220,7 @@ static void handle_raw(syslink_message_t *msg)
// send next raw message if fifo is not empty
if (syslink.tx_extract_idx != syslink.tx_insert_idx) {
- PPRZ_MUTEX_LOCK(syslink_tx_mtx);
+ pprz_mtx_lock(&syslink_tx_mtx);
syslink_message_t msg_raw;
msg_raw.type = SYSLINK_RADIO_RAW;
memcpy(&msg_raw.length, &syslink.msg_tx[syslink.tx_extract_idx], sizeof(crtp_message_t));
@@ -230,7 +230,7 @@ static void handle_raw(syslink_message_t *msg)
if (syslink.tx_extract_idx == CRTP_BUF_LEN) {
syslink.tx_extract_idx = 0;
}
- PPRZ_MUTEX_UNLOCK(syslink_tx_mtx);
+ pprz_mtx_unlock(&syslink_tx_mtx);
}
}
@@ -293,7 +293,7 @@ static int syslink_check_free_space(struct syslink_dl *s, long *fd UNUSED, uint1
}
int space = CRTP_MAX_DATA_SIZE * (slots - 1);
if (space >= len) {
- PPRZ_MUTEX_LOCK(syslink_tx_mtx);
+ pprz_mtx_lock(&syslink_tx_mtx);
return space;
}
return 0;
@@ -342,7 +342,7 @@ static void syslink_put_byte(struct syslink_dl *s, long fd, const uint8_t b)
// send_message is not needed as messages are stored in a fifo
static void syslink_send_message(struct syslink_dl *s UNUSED, long fd UNUSED)
{
- PPRZ_MUTEX_UNLOCK(syslink_tx_mtx); // release mutex
+ pprz_mtx_lock(&syslink_tx_mtx); // release mutex
}
static uint8_t syslink_getch(struct syslink_dl *s)
@@ -390,7 +390,7 @@ void syslink_dl_init(void)
syslink.device.get_byte = (get_byte_t) syslink_getch;
// init mutex if needed
- PPRZ_MUTEX_INIT(syslink_tx_mtx);
+ pprz_mtx_init(&syslink_tx_mtx);
}
/** Periodic function */
diff --git a/sw/airborne/modules/datalink/gec_dl.c b/sw/airborne/modules/datalink/gec_dl.c
index 6e547298d6..0fa2f32e37 100644
--- a/sw/airborne/modules/datalink/gec_dl.c
+++ b/sw/airborne/modules/datalink/gec_dl.c
@@ -144,7 +144,7 @@ static void start_message(struct pprzlink_msg *msg,
long fd __attribute__((unused)),
uint8_t payload_len __attribute__((unused)))
{
- PPRZ_MUTEX_LOCK(get_trans(msg)->mtx_tx); // lock mutex
+ pprz_mtx_lock(&get_trans(msg)->mtx_tx); // lock mutex
memset(get_trans(msg)->tx_msg, 0, TRANSPORT_PAYLOAD_LEN);// erase message data
get_trans(msg)->tx_msg_idx = 0;// reset index
}
@@ -205,7 +205,7 @@ static void end_message(struct pprzlink_msg *msg, long fd)
break;
}
// unlock mutex
- PPRZ_MUTEX_UNLOCK(get_trans(msg)->mtx_tx);
+ pprz_mtx_unlock(&get_trans(msg)->mtx_tx);
}
/**
@@ -219,7 +219,7 @@ void gec_encapsulate_and_send_msg(struct pprzlink_msg *msg, long fd)
get_trans(msg)->pprz_tp.trans_tx.put_bytes(msg, fd, DL_TYPE_UINT8,
DL_FORMAT_SCALAR, get_trans(msg)->tx_msg, get_trans(msg)->tx_msg_idx);
// unlock mutex
- PPRZ_MUTEX_UNLOCK(get_trans(msg)->mtx_tx);
+ pprz_mtx_unlock(&get_trans(msg)->mtx_tx);
get_trans(msg)->pprz_tp.trans_tx.end_message(msg, fd);
}
@@ -263,7 +263,7 @@ static void start_message(struct gec_transport *trans,
long fd __attribute__((unused)),
uint8_t payload_len __attribute__((unused)))
{
- PPRZ_MUTEX_LOCK(trans->mtx_tx); // lock mutex
+ pprz_mtx_lock(&trans->mtx_tx); // lock mutex
memset(trans->tx_msg, 0, TRANSPORT_PAYLOAD_LEN); // erase message data
trans->tx_msg_idx = 0; // reset index
}
@@ -283,7 +283,7 @@ static void end_message(struct gec_transport *trans, struct link_device *dev,
break;
}
// unlock mutex
- PPRZ_MUTEX_UNLOCK(trans->mtx_tx);
+ pprz_mtx_unlock(&trans->mtx_tx);
}
void gec_encapsulate_and_send_msg(struct gec_transport *trans,
@@ -293,7 +293,7 @@ void gec_encapsulate_and_send_msg(struct gec_transport *trans,
trans->pprz_tp.trans_tx.put_bytes(trans, dev, fd, DL_TYPE_UINT8,
DL_FORMAT_SCALAR, trans->tx_msg, trans->tx_msg_idx);
// unlock mutex
- PPRZ_MUTEX_UNLOCK(trans->mtx_tx);
+ pprz_mtx_unlock(&trans->mtx_tx);
trans->pprz_tp.trans_tx.end_message(trans, dev, fd);
}
@@ -335,7 +335,7 @@ void gec_transport_init(struct gec_transport *t)
t->trans_tx.overrun = (overrun_t) overrun;
t->trans_tx.count_bytes = (count_bytes_t) count_bytes;
t->trans_tx.impl = (void *)(t);
- PPRZ_MUTEX_INIT(t->mtx_tx); // init mutex, check if correct pointer
+ pprz_mtx_init(&t->mtx_tx); // init mutex, check if correct pointer
// add whitelist messages
gec_add_to_whitelist(&(t->whitelist), KEY_EXCHANGE_MSG_ID_UAV);
diff --git a/sw/airborne/modules/datalink/gec_dl.h b/sw/airborne/modules/datalink/gec_dl.h
index b8cf43790c..82b239c53b 100644
--- a/sw/airborne/modules/datalink/gec_dl.h
+++ b/sw/airborne/modules/datalink/gec_dl.h
@@ -29,7 +29,7 @@
#include "pprzlink/pprzlink_transport.h"
#include "pprzlink/pprz_transport.h"
#include "modules/datalink/gec/gec.h"
-#include "pprz_mutex.h"
+#include "modules/core/threads.h"
#include "mcu_periph/uart.h"
#if USE_USB_SERIAL || USE_USB_SERIAL_DEBUG
@@ -84,7 +84,7 @@ struct gec_transport {
struct gec_whitelist whitelist;
// optional mutex
- PPRZ_MUTEX(mtx_tx);
+ pprz_mutex_t mtx_tx;
};
/** PPRZ transport structure */
diff --git a/sw/airborne/modules/mission/copilot.h b/sw/airborne/modules/mission/copilot.h
index b15e7ed34e..b48058549f 100644
--- a/sw/airborne/modules/mission/copilot.h
+++ b/sw/airborne/modules/mission/copilot.h
@@ -46,11 +46,12 @@
#include "modules/datalink/datalink.h"
#include "modules/datalink/extra_pprz_dl.h"
-#include "pprz_mutex.h"
+#include "modules/core/threads.h"
+
+extern pprz_mutex_t copilot_cam_snapshot_mtx;
+extern pprz_mutex_t copilot_cam_payload_mtx;
+extern pprz_mutex_t copilot_status_mtx;
-PPRZ_MUTEX_DECL(copilot_cam_snapshot_mtx);
-PPRZ_MUTEX_DECL(copilot_cam_payload_mtx);
-PPRZ_MUTEX_DECL(copilot_status_mtx);
struct CameraPayload {
float timestamp;
diff --git a/sw/airborne/modules/mission/copilot_common.c b/sw/airborne/modules/mission/copilot_common.c
index 9fc221c5bb..fcdefd9ac2 100644
--- a/sw/airborne/modules/mission/copilot_common.c
+++ b/sw/airborne/modules/mission/copilot_common.c
@@ -52,9 +52,9 @@ struct CameraPayload cam_payload;
struct CameraSnapshot cam_snapshot;
struct CopilotStatus copilot_status;
-PPRZ_MUTEX(copilot_cam_snapshot_mtx);
-PPRZ_MUTEX(copilot_cam_payload_mtx);
-PPRZ_MUTEX(copilot_status_mtx);
+pprz_mutex_t copilot_cam_snapshot_mtx;
+pprz_mutex_t copilot_cam_payload_mtx;
+pprz_mutex_t copilot_status_mtx;
/** Init function */
void copilot_init(void)
@@ -67,15 +67,15 @@ void copilot_init(void)
memset(&cam_snapshot, 0, sizeof(cam_snapshot));
memset(&copilot_status, 0, sizeof(copilot_status));
- PPRZ_MUTEX_INIT(copilot_cam_snapshot_mtx);
- PPRZ_MUTEX_INIT(copilot_cam_payload_mtx);
- PPRZ_MUTEX_INIT(copilot_status_mtx);
+ pprz_mtx_init(&copilot_cam_snapshot_mtx);
+ pprz_mtx_init(&copilot_cam_payload_mtx);
+ pprz_mtx_init(&copilot_status_mtx);
}
/** Periodic function */
void copilot_periodic(void)
{
- PPRZ_MUTEX_LOCK(copilot_cam_snapshot_mtx);
+ pprz_mtx_lock(&copilot_cam_snapshot_mtx);
if (send_cam_snapshot) {
// send down to GCS
DOWNLINK_SEND_CAMERA_SNAPSHOT(DefaultChannel, DefaultDevice,
@@ -85,9 +85,9 @@ void copilot_periodic(void)
send_cam_snapshot = false;
}
- PPRZ_MUTEX_UNLOCK(copilot_cam_snapshot_mtx);
+ pprz_mtx_unlock(&copilot_cam_snapshot_mtx);
- PPRZ_MUTEX_LOCK(copilot_cam_payload_mtx);
+ pprz_mtx_lock(&copilot_cam_payload_mtx);
if (send_cam_payload) {
// NOTE: to send the message over the EXTRA_DL port
// use "DOWNLINK_SEND_CAMERA_PAYLOAD(extra_pprz_tp, EXTRA_DOWNLINK_DEVICE,"
@@ -99,9 +99,9 @@ void copilot_periodic(void)
send_cam_payload = false;
}
- PPRZ_MUTEX_UNLOCK(copilot_cam_payload_mtx);
+ pprz_mtx_unlock(&copilot_cam_payload_mtx);
- PPRZ_MUTEX_LOCK(copilot_status_mtx);
+ pprz_mtx_lock(&copilot_status_mtx);
// send down to GCS
if (send_copilot_status) {
DOWNLINK_SEND_COPILOT_STATUS(DefaultChannel, DefaultDevice,
@@ -111,7 +111,7 @@ void copilot_periodic(void)
send_copilot_status = false;
}
- PPRZ_MUTEX_UNLOCK(copilot_status_mtx);
+ pprz_mtx_unlock(&copilot_status_mtx);
}
@@ -126,7 +126,7 @@ void copilot_periodic(void)
*/
void copilot_parse_cam_snapshot_dl(uint8_t *buf)
{
- PPRZ_MUTEX_LOCK(copilot_cam_snapshot_mtx);
+ pprz_mtx_lock(&copilot_cam_snapshot_mtx);
// copy CAMERA_SNAPSHOT message and mark it to be sent
cam_snapshot.cam_id = DL_CAMERA_SNAPSHOT_DL_camera_id(buf);
@@ -138,7 +138,7 @@ void copilot_parse_cam_snapshot_dl(uint8_t *buf)
send_cam_snapshot = true;
- PPRZ_MUTEX_UNLOCK(copilot_cam_snapshot_mtx);
+ pprz_mtx_unlock(&copilot_cam_snapshot_mtx);
}
/**
@@ -146,7 +146,7 @@ void copilot_parse_cam_snapshot_dl(uint8_t *buf)
*/
void copilot_parse_cam_payload_dl(uint8_t *buf)
{
- PPRZ_MUTEX_LOCK(copilot_cam_payload_mtx);
+ pprz_mtx_lock(&copilot_cam_payload_mtx);
cam_payload.timestamp = DL_CAMERA_PAYLOAD_DL_timestamp(buf);
cam_payload.used_mem = DL_CAMERA_PAYLOAD_DL_used_memory(buf);
@@ -156,7 +156,7 @@ void copilot_parse_cam_payload_dl(uint8_t *buf)
send_cam_payload = true;
- PPRZ_MUTEX_UNLOCK(copilot_cam_payload_mtx);
+ pprz_mtx_unlock(&copilot_cam_payload_mtx);
}
/**
@@ -164,7 +164,7 @@ void copilot_parse_cam_payload_dl(uint8_t *buf)
*/
void copilot_parse_copilot_status_dl(uint8_t *buf)
{
- PPRZ_MUTEX_LOCK(copilot_status_mtx);
+ pprz_mtx_lock(&copilot_status_mtx);
copilot_status.timestamp = DL_COPILOT_STATUS_DL_timestamp(buf);
copilot_status.used_mem = DL_COPILOT_STATUS_DL_used_memory(buf);
@@ -174,7 +174,7 @@ void copilot_parse_copilot_status_dl(uint8_t *buf)
send_copilot_status = true;
- PPRZ_MUTEX_UNLOCK(copilot_status_mtx);
+ pprz_mtx_unlock(&copilot_status_mtx);
}
/**
diff --git a/sw/airborne/modules/sensors/thd_test.c b/sw/airborne/modules/sensors/thd_test.c
new file mode 100644
index 0000000000..1ee19ef0c2
--- /dev/null
+++ b/sw/airborne/modules/sensors/thd_test.c
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2025 fab
+ *
+ * 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/sensors/thd_test.c"
+ * @author fab
+ * A module to test threads functionnalities, like threads, mutexes, semaphores and so on.
+ */
+
+#include "modules/sensors/thd_test.h"
+#include "modules/core/threads.h"
+#include "led.h"
+#include "modules/datalink/telemetry.h"
+#include
+
+float thd_test_start_value;
+
+pprz_bsem_t bsem;
+
+pprz_mutex_t mtx; // mutex protecting syracuse
+static int syracuse = 14;
+
+// Remember if the thread has already been joined or not.
+// From https://linux.die.net/man/3/pthread_join:
+// "Joining with a thread that has previously been joined results in undefined behavior."
+static bool joined = true;
+
+static void test_thd(void*);
+
+pprz_thread_t thd_handle;
+
+void thd_test_syracuse_restart(float s) {
+ // try changing syracuse
+ // non blocking because we are in the main loop.
+ // If it doesn't work, just try again!
+ if(!pprz_mtx_trylock(&mtx)) {
+ syracuse = (int)s;
+ thd_test_start_value = s;
+ pprz_mtx_unlock(&mtx);
+
+ // if it was stopped, restart it!
+ if(joined) {
+ if(!pprz_thread_create(&thd_handle, 512, "test", PPRZ_NORMAL_PRIO+1, test_thd, NULL)) {
+ joined = false;
+ }
+ }
+ } else {
+ char err[] = "Could not lock";
+ DOWNLINK_SEND_INFO_MSG(DefaultChannel, DefaultDevice, strlen(err), err);
+ }
+}
+
+void thd_test_init(void)
+{
+ pprz_bsem_init(&bsem, true); // init binary semaphore
+ pprz_mtx_init(&mtx); // init mutex
+ // create thread, and remember to try joining it.
+ if(!pprz_thread_create(&thd_handle, 512, "test", PPRZ_NORMAL_PRIO, test_thd, NULL)) {
+ joined = false;
+ }
+}
+
+void thd_test_periodic(void)
+{
+ if(!joined) {
+ size_t ret;
+ char msg[30];
+ size_t len = 0;
+ if(!pprz_thread_tryjoin(&thd_handle, (void**)(&ret))) {
+ // try joining the thread. If successful, send its exit value.
+ len = snprintf(msg, sizeof(msg), "Finished in %zu steps!", ret);
+ joined = true;
+ } else {
+ // the thread is still running, print the current value of the syracuse suit (mutex protected)
+ // then signal the binary semaphore to allow the thread to resume execution
+ if(!pprz_mtx_trylock(&mtx)) {
+ len = snprintf(msg, sizeof(msg), "%d", syracuse);
+ pprz_mtx_unlock(&mtx);
+ pprz_bsem_signal(&bsem);
+ }
+ }
+ // send the message to the ground (current value or end message)
+ if(len > 0) {
+ DOWNLINK_SEND_INFO_MSG(DefaultChannel, DefaultDevice, len, msg);
+ }
+
+
+ }
+
+}
+
+
+static void test_thd(void* arg) {
+ (void)arg;
+ size_t count = 0;
+ while(syracuse != 1) {
+ pprz_bsem_wait(&bsem); // wait to be woken up by the AP thread
+
+ pprz_mtx_lock(&mtx);
+ if(syracuse%2) {
+ syracuse = 3*syracuse + 1;
+ } else {
+ syracuse = syracuse / 2;
+ }
+ pprz_mtx_unlock(&mtx);
+ count++;
+ }
+
+ pprz_thread_exit((void*)((size_t)count));
+}
diff --git a/sw/airborne/modules/sensors/thd_test.h b/sw/airborne/modules/sensors/thd_test.h
new file mode 100644
index 0000000000..5f56394c28
--- /dev/null
+++ b/sw/airborne/modules/sensors/thd_test.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2025 fab
+ *
+ * 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/sensors/thd_test.h"
+ * @author fab
+ * A module to test threads functionnalities, like threads, mutexes, semaphores and so on.
+ */
+
+#ifndef THD_TEST_H
+#define THD_TEST_H
+
+extern void thd_test_init(void);
+extern void thd_test_periodic(void);
+extern void thd_test_event(void);
+
+void thd_test_syracuse_restart(float s);
+
+extern float thd_test_start_value;
+
+#endif // THD_TEST_H
diff --git a/sw/airborne/pprz_mutex.h b/sw/airborne/pprz_mutex.h
deleted file mode 100644
index 9ef38375f3..0000000000
--- a/sw/airborne/pprz_mutex.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2016 Gautier Hattenberger
- *
- * 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 pprz_mutex.h
- *
- * Utility functions and macros to abstract some RTOS functionalities
- * such as mutexes.
- */
-
-#ifndef PPRZ_MUTEX_H
-#define PPRZ_MUTEX_H
-
-#if USE_CHIBIOS_RTOS
-
-#include
-
-#define PPRZ_MUTEX(_mtx) mutex_t _mtx
-#define PPRZ_MUTEX_DECL(_mtx) extern mutex_t _mtx
-#define PPRZ_MUTEX_INIT(_mtx) chMtxObjectInit(&(_mtx))
-#define PPRZ_MUTEX_LOCK(_mtx) chMtxLock(&(_mtx))
-#define PPRZ_MUTEX_UNLOCK(_mtx) chMtxUnlock(&(_mtx))
-
-#else // no RTOS
-
-#define PPRZ_MUTEX(_mtx)
-#define PPRZ_MUTEX_DECL(_mtx)
-#define PPRZ_MUTEX_INIT(_mtx) {}
-#define PPRZ_MUTEX_LOCK(_mtx) {}
-#define PPRZ_MUTEX_UNLOCK(_mtx) {}
-
-#endif
-
-#endif
-
diff --git a/tests/modules/test_arch/modules/core/threads_arch.h b/tests/modules/test_arch/modules/core/threads_arch.h
new file mode 100644
index 0000000000..9fcd75948b
--- /dev/null
+++ b/tests/modules/test_arch/modules/core/threads_arch.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#define PPRZ_NORMAL_PRIO 128
+
+#define THREADS_ATTRIBUTES
+
+struct pprzMutex {
+};
+
+struct pprzBSem {
+};
+
+
+typedef struct{} pprz_thread_t;