diff --git a/README.md b/README.md index e7b12674..8aecf42f 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ The real-time operating system for Brown Space Engineering's second satellite, P - Log output can be viewed by running `python3 scripts/rtt_logs.py` in a separate terminal window. This will also record logs to the `/logs` folder. - If the script fails to run, you may need to install 'pylink-square' (`pip install pylink-square`) - Alternatively, you can try running `python3 scripts/rtt_splitscreen.py` for both the PVDXos Shell and log output in the same terminal window, but this might not work! + - To sample the internal temperature sensor from the shell, run `temperature` (add the optional `raw` argument to display PTAT/CTAT diagnostic values). The command initializes the sensor if needed and prints the reading in Celsius. ## Toolchain Installation diff --git a/src/Makefile b/src/Makefile index 23c4a220..a468411d 100644 --- a/src/Makefile +++ b/src/Makefile @@ -64,6 +64,9 @@ export OBJS := \ ../src/tasks/photodiode/photodiode_main.o \ ../src/tasks/photodiode/photodiode_task.o \ ../src/tasks/photodiode/photodiode_driver.o \ +../src/tasks/temperature/temperature_driver.o \ +../src/tasks/temperature/temperature_task.o \ +../src/tasks/temperature/temperature_main.o \ ### ALL DIRECTORIES WITH SOURCE FILES MUST BE LISTED HERE ### ### THESE ARE WRITTEN RELATIVE TO THE ./ASF/gcc/Makefile FILE ### @@ -85,6 +88,7 @@ export EXTRA_VPATH := \ ../../src/tasks/magnetometer \ ../../src/tasks/shell \ ../../src/tasks/photodiode \ +../../src/tasks/temperature \ ../../src/mutexes diff --git a/src/globals.h b/src/globals.h index dce579d4..63405a32 100644 --- a/src/globals.h +++ b/src/globals.h @@ -70,10 +70,12 @@ typedef enum { // Magnetometer operations OPERATION_READ, // p_data: magnetometer_read_args_t *readings - // Photodiode operations OPERATION_PHOTODIODE_READ, + // Temperature operations + OPERATION_TEMPERATURE_READ, + // TESTING TEST_OP, // p_data: char message[] } operation_t; diff --git a/src/tasks/shell/shell_commands.c b/src/tasks/shell/shell_commands.c index 7cafd497..3f20075e 100644 --- a/src/tasks/shell/shell_commands.c +++ b/src/tasks/shell/shell_commands.c @@ -13,6 +13,7 @@ #include "logging.h" #include "shell_helpers.h" +#include "temperature_driver.h" #include "watchdog_task.h" shell_command_t shell_commands[] = { @@ -22,6 +23,7 @@ shell_command_t shell_commands[] = { {"loglevel", shell_loglevel, help_loglevel}, {"reboot", shell_reboot, help_reboot}, {"display", shell_display, help_display}, + {"temperature", shell_temperature, help_temperature}, {NULL, NULL, NULL} // Null-terminated array }; @@ -259,4 +261,47 @@ void shell_display(char **args, int arg_count) { void help_display() { terminal_printf("Usage: display\n"); terminal_printf("\tgjnerergjkn\n"); +} + +/* TEMPERATURE COMMAND */ + +void shell_temperature(char **args, int arg_count) { + bool show_raw = false; + if (arg_count > 2) { + terminal_printf("Invalid usage. Try 'help temperature'\n"); + return; + } + if (arg_count == 2) { + if (strcmp(args[1], "raw") == 0) { + show_raw = true; + } else { + terminal_printf("Invalid option '%s'. Try 'help temperature'\n", args[1]); + return; + } + } + + status_t status = temp_sensor_init(); + if (status != SUCCESS) { + terminal_printf("temperature: init failed (%d)\n", status); + return; + } + + temp_sensor_sample_t sample = {0}; + status = temp_sensor_sample(&sample); + if (status != SUCCESS) { + terminal_printf("temperature: sample failed (%d)\n", status); + return; + } + + terminal_printf("Temperature: %.2f C\n", sample.temperature_c); + if (show_raw) { + terminal_printf(" PTAT: raw=%u -> %.2f C\n", sample.ptat_raw, sample.temperature_ptat_c); + terminal_printf(" CTAT: raw=%u -> %.2f C\n", sample.ctat_raw, sample.temperature_ctat_c); + } +} + +void help_temperature() { + terminal_printf("Usage: temperature [raw]\n"); + terminal_printf("\tSamples the onboard temperature sensor and prints the result in Celsius.\n"); + terminal_printf("\tPass 'raw' to also display PTAT/CTAT channels and their converted estimates.\n"); } \ No newline at end of file diff --git a/src/tasks/shell/shell_commands.h b/src/tasks/shell/shell_commands.h index b0254c0a..e0c58c8c 100644 --- a/src/tasks/shell/shell_commands.h +++ b/src/tasks/shell/shell_commands.h @@ -28,4 +28,7 @@ void help_reboot(); void shell_display(char **args, int arg_count); void help_display(); +void shell_temperature(char **args, int arg_count); +void help_temperature(); + #endif // SHELL_COMMANDS_H \ No newline at end of file diff --git a/src/tasks/task_list.c b/src/tasks/task_list.c index 998625d9..fe257600 100644 --- a/src/tasks/task_list.c +++ b/src/tasks/task_list.c @@ -101,6 +101,24 @@ pvdx_task_t photodiode_task = { .task_type = SENSOR }; +pvdx_task_t temperature_task = { + .name = "Temperature", + .enabled = false, + .handle = NULL, + .command_queue = NULL, + .init = init_temperature, + .function = main_temperature, + .stack_size = TEMPERATURE_TASK_STACK_SIZE, + .stack_buffer = temperature_mem.temperature_task_stack, + .pvParameters = NULL, + .priority = 2, + .task_tcb = &temperature_mem.temperature_task_tcb, + .watchdog_timeout_ms = 5000, + .last_checkin_time_ticks = 0xDEADBEEF, + .has_registered = false, + .task_type = SENSOR +}; + pvdx_task_t shell_task = { .name = "Shell", .enabled = false, @@ -161,6 +179,7 @@ pvdx_task_t *const p_command_dispatcher_task = &command_dispatcher_task; pvdx_task_t *const p_task_manager_task = &task_manager_task; pvdx_task_t *const p_magnetometer_task = &magnetometer_task; pvdx_task_t *const p_photodiode_task = &photodiode_task; +pvdx_task_t *const p_temperature_task = &temperature_task; pvdx_task_t *const p_shell_task = &shell_task; pvdx_task_t *const p_display_task = &display_task; pvdx_task_t *const p_heartbeat_task = &heartbeat_task; @@ -178,6 +197,7 @@ pvdx_task_t *task_list[] = { p_task_manager_task, p_magnetometer_task, p_photodiode_task, + p_temperature_task, p_shell_task, p_display_task, p_heartbeat_task, diff --git a/src/tasks/task_list.h b/src/tasks/task_list.h index 3d17c470..ea9b8ff5 100644 --- a/src/tasks/task_list.h +++ b/src/tasks/task_list.h @@ -8,6 +8,7 @@ #include "heartbeat_task.h" #include "magnetometer_task.h" #include "photodiode_task.h" +#include "temperature_task.h" #include "shell_task.h" #include "task_manager_task.h" #include "watchdog_task.h" @@ -18,6 +19,7 @@ extern pvdx_task_t *const p_command_dispatcher_task; extern pvdx_task_t *const p_task_manager_task; extern pvdx_task_t *const p_magnetometer_task; extern pvdx_task_t *const p_photodiode_task; +extern pvdx_task_t *const p_temperature_task; extern pvdx_task_t *const p_shell_task; extern pvdx_task_t *const p_display_task; extern pvdx_task_t *const p_heartbeat_task; diff --git a/src/tasks/temperature/temperature_driver.c b/src/tasks/temperature/temperature_driver.c new file mode 100644 index 00000000..8bd84635 --- /dev/null +++ b/src/tasks/temperature/temperature_driver.c @@ -0,0 +1,211 @@ +#include "temperature_driver.h" + +#include "logging.h" + +#include +#include +#include + +#define TEMP_SENSOR_ADC_INSTANCE ADC1 + +#define TEMP_SENSOR_DEFAULT_PRESCALER ADC_CTRLA_PRESCALER_DIV32 +#define TEMP_SENSOR_DEFAULT_SAMPLENUM ADC_AVGCTRL_SAMPLENUM_32 +#define TEMP_SENSOR_DEFAULT_ADJRES 5 +#define TEMP_SENSOR_DEFAULT_SAMPLEN 5 + +typedef struct { + bool loaded; + float room_temp_c; + float hot_temp_c; + uint16_t room_ptat; + uint16_t hot_ptat; + uint16_t room_ctat; + uint16_t hot_ctat; + float ptat_slope; + float ptat_intercept; + float ctat_slope; + float ctat_intercept; +} temp_calibration_t; + +static temp_calibration_t g_calibration = {0}; +static bool g_temp_sensor_initialized = false; + +static inline void wait_sync(uint32_t mask) { + while (TEMP_SENSOR_ADC_INSTANCE->SYNCBUSY.reg & mask) { + } +} + +static inline float decode_temperature(uint8_t integer_part, uint8_t fractional_part) { + return (float)integer_part + ((float)fractional_part / 16.0f); +} + +static status_t load_calibration(temp_calibration_t *cal) { + if (!cal) { + return ERROR_SANITY_CHECK_FAILED; + } + + memset(cal, 0, sizeof(*cal)); + + const uint32_t temp_log0 = *((uint32_t *)NVMCTRL_TEMP_LOG); + const uint32_t temp_log1 = *((uint32_t *)(NVMCTRL_TEMP_LOG + 4U)); + const uint32_t temp_log2 = *((uint32_t *)(NVMCTRL_TEMP_LOG + 8U)); + + const uint8_t room_temp_int = (temp_log0 & FUSES_ROOM_TEMP_VAL_INT_Msk) >> FUSES_ROOM_TEMP_VAL_INT_Pos; + const uint8_t room_temp_dec = (temp_log0 & FUSES_ROOM_TEMP_VAL_DEC_Msk) >> FUSES_ROOM_TEMP_VAL_DEC_Pos; + const uint8_t hot_temp_int = (temp_log0 & FUSES_HOT_TEMP_VAL_INT_Msk) >> FUSES_HOT_TEMP_VAL_INT_Pos; + const uint8_t hot_temp_dec = (temp_log0 & FUSES_HOT_TEMP_VAL_DEC_Msk) >> FUSES_HOT_TEMP_VAL_DEC_Pos; + + const uint16_t room_ptat_raw = (temp_log1 & FUSES_ROOM_ADC_VAL_PTAT_Msk) >> FUSES_ROOM_ADC_VAL_PTAT_Pos; + const uint16_t hot_ptat_raw = (temp_log1 & FUSES_HOT_ADC_VAL_PTAT_Msk) >> FUSES_HOT_ADC_VAL_PTAT_Pos; + + const uint16_t room_ctat_raw = (temp_log2 & FUSES_ROOM_ADC_VAL_CTAT_Msk) >> FUSES_ROOM_ADC_VAL_CTAT_Pos; + const uint16_t hot_ctat_raw = (temp_log2 & FUSES_HOT_ADC_VAL_CTAT_Msk) >> FUSES_HOT_ADC_VAL_CTAT_Pos; + + cal->room_temp_c = decode_temperature(room_temp_int, room_temp_dec); + cal->hot_temp_c = decode_temperature(hot_temp_int, hot_temp_dec); + + const float delta_temp = cal->hot_temp_c - cal->room_temp_c; + if (fabsf(delta_temp) < 0.01f) { + warning("temperature: calibration delta temperature too small (%.2f)", delta_temp); + return ERROR_SANITY_CHECK_FAILED; + } + + cal->room_ptat = room_ptat_raw; + cal->hot_ptat = hot_ptat_raw; + cal->room_ctat = room_ctat_raw; + cal->hot_ctat = hot_ctat_raw; + + cal->ptat_slope = ((float)hot_ptat_raw - (float)room_ptat_raw) / delta_temp; + cal->ctat_slope = ((float)hot_ctat_raw - (float)room_ctat_raw) / delta_temp; + + if (fabsf(cal->ptat_slope) < 1e-6f || fabsf(cal->ctat_slope) < 1e-6f) { + warning("temperature: calibration slope too small (ptat %.6f, ctat %.6f)", cal->ptat_slope, cal->ctat_slope); + return ERROR_SANITY_CHECK_FAILED; + } + + cal->ptat_intercept = (float)room_ptat_raw - (cal->ptat_slope * cal->room_temp_c); + cal->ctat_intercept = (float)room_ctat_raw - (cal->ctat_slope * cal->room_temp_c); + cal->loaded = true; + + return SUCCESS; +} + +static inline uint16_t read_adc_result(void) { + while (!(TEMP_SENSOR_ADC_INSTANCE->INTFLAG.reg & ADC_INTFLAG_RESRDY)) { + } + const uint16_t result = TEMP_SENSOR_ADC_INSTANCE->RESULT.reg; + TEMP_SENSOR_ADC_INSTANCE->INTFLAG.reg = ADC_INTFLAG_RESRDY | ADC_INTFLAG_OVERRUN; + return result; +} + +static uint16_t sample_internal_channel(uint8_t muxpos) { + wait_sync(ADC_SYNCBUSY_INPUTCTRL); + TEMP_SENSOR_ADC_INSTANCE->INPUTCTRL.reg = + ADC_INPUTCTRL_MUXPOS(muxpos) | ADC_INPUTCTRL_MUXNEG_GND; + wait_sync(ADC_SYNCBUSY_INPUTCTRL); + + TEMP_SENSOR_ADC_INSTANCE->SWTRIG.reg |= ADC_SWTRIG_START; + wait_sync(ADC_SYNCBUSY_SWTRIG); + return read_adc_result(); +} + +static status_t ensure_adc_ready(void) { + if (!(TEMP_SENSOR_ADC_INSTANCE->CTRLA.reg & ADC_CTRLA_ENABLE)) { + TEMP_SENSOR_ADC_INSTANCE->CTRLA.reg |= ADC_CTRLA_ENABLE; + wait_sync(ADC_SYNCBUSY_ENABLE); + } + return SUCCESS; +} + +status_t temp_sensor_init(void) { + if (g_temp_sensor_initialized) { + return SUCCESS; + } + + debug("temperature: initializing internal temperature sensor driver\n"); + + status_t status = load_calibration(&g_calibration); + if (status != SUCCESS) { + return status; + } + + hri_supc_set_VREF_TSEN_bit(SUPC); + + if (TEMP_SENSOR_ADC_INSTANCE->CTRLA.reg & ADC_CTRLA_ENABLE) { + TEMP_SENSOR_ADC_INSTANCE->CTRLA.reg &= ~ADC_CTRLA_ENABLE; + wait_sync(ADC_SYNCBUSY_ENABLE); + } + + TEMP_SENSOR_ADC_INSTANCE->CTRLA.reg &= ~ADC_CTRLA_PRESCALER_Msk; + TEMP_SENSOR_ADC_INSTANCE->CTRLA.reg |= TEMP_SENSOR_DEFAULT_PRESCALER; + + wait_sync(ADC_SYNCBUSY_REFCTRL); + TEMP_SENSOR_ADC_INSTANCE->REFCTRL.reg = ADC_REFCTRL_REFSEL_INTREF; + + wait_sync(ADC_SYNCBUSY_AVGCTRL); + TEMP_SENSOR_ADC_INSTANCE->AVGCTRL.reg = + TEMP_SENSOR_DEFAULT_SAMPLENUM | ADC_AVGCTRL_ADJRES(TEMP_SENSOR_DEFAULT_ADJRES); + + wait_sync(ADC_SYNCBUSY_SAMPCTRL); + TEMP_SENSOR_ADC_INSTANCE->SAMPCTRL.reg = ADC_SAMPCTRL_SAMPLEN(TEMP_SENSOR_DEFAULT_SAMPLEN); + + wait_sync(ADC_SYNCBUSY_CTRLB); + TEMP_SENSOR_ADC_INSTANCE->CTRLB.reg = ADC_CTRLB_RESSEL_12BIT; + + TEMP_SENSOR_ADC_INSTANCE->INTFLAG.reg = ADC_INTFLAG_MASK; + + wait_sync(ADC_SYNCBUSY_ENABLE); + TEMP_SENSOR_ADC_INSTANCE->CTRLA.reg |= ADC_CTRLA_ENABLE; + wait_sync(ADC_SYNCBUSY_ENABLE); + + g_temp_sensor_initialized = true; + info("temperature: driver initialized (room %.2fC, hot %.2fC)\n", + g_calibration.room_temp_c, g_calibration.hot_temp_c); + return SUCCESS; +} + +status_t temp_sensor_sample(temp_sensor_sample_t *sample) { + if (!sample) { + return ERROR_SANITY_CHECK_FAILED; + } + if (!g_temp_sensor_initialized || !g_calibration.loaded) { + return ERROR_NOT_READY; + } + + taskENTER_CRITICAL(); + ensure_adc_ready(); + const uint16_t ptat = sample_internal_channel(ADC1_PTAT); + const uint16_t ctat = sample_internal_channel(ADC1_CTAT); + taskEXIT_CRITICAL(); + + const float ptat_norm = (float)ptat; + const float ctat_norm = (float)ctat; + + const float temp_ptat = + (ptat_norm - g_calibration.ptat_intercept) / g_calibration.ptat_slope; + const float temp_ctat = + (ctat_norm - g_calibration.ctat_intercept) / g_calibration.ctat_slope; + + sample->ptat_raw = ptat; + sample->ctat_raw = ctat; + sample->temperature_ptat_c = temp_ptat; + sample->temperature_ctat_c = temp_ctat; + sample->temperature_c = (temp_ptat + temp_ctat) * 0.5f; + + return SUCCESS; +} + +status_t temp_sensor_get_celsius(float *temperature_c) { + if (!temperature_c) { + return ERROR_SANITY_CHECK_FAILED; + } + temp_sensor_sample_t sample = {0}; + status_t status = temp_sensor_sample(&sample); + if (status != SUCCESS) { + return status; + } + + *temperature_c = sample.temperature_c; + return SUCCESS; +} + diff --git a/src/tasks/temperature/temperature_driver.h b/src/tasks/temperature/temperature_driver.h new file mode 100644 index 00000000..d3234226 --- /dev/null +++ b/src/tasks/temperature/temperature_driver.h @@ -0,0 +1,20 @@ +#ifndef TEMPERATURE_DRIVER_H +#define TEMPERATURE_DRIVER_H + +#include "atmel_start.h" +#include "globals.h" + +typedef struct { + uint16_t ptat_raw; + uint16_t ctat_raw; + float temperature_c; + float temperature_ptat_c; + float temperature_ctat_c; +} temp_sensor_sample_t; + +status_t temp_sensor_init(void); +status_t temp_sensor_sample(temp_sensor_sample_t *sample); +status_t temp_sensor_get_celsius(float *temperature_c); + +#endif // TEMPERATURE_DRIVER_H + diff --git a/src/tasks/temperature/temperature_main.c b/src/tasks/temperature/temperature_main.c new file mode 100644 index 00000000..7eed3b85 --- /dev/null +++ b/src/tasks/temperature/temperature_main.c @@ -0,0 +1,30 @@ +#include "temperature_task.h" +#include "task_list.h" +#include "command_dispatcher_task.h" + +temperature_task_memory_t temperature_mem; + +void main_temperature(void *pvParameters) { + (void)pvParameters; + + info("temperature: Task Started!\n"); + + pvdx_task_t *const current_task = get_current_task(); + command_t cmd_checkin = get_watchdog_checkin_command(current_task); + const TickType_t queue_block_time_ticks = get_command_queue_block_time_ticks(current_task); + command_t cmd; + + while (true) { + if (current_task->command_queue && + xQueueReceive(current_task->command_queue, &cmd, queue_block_time_ticks) == pdPASS) { + do { + exec_command_temperature(&cmd); + } while (xQueueReceive(current_task->command_queue, &cmd, 0) == pdPASS); + } + + if (should_checkin(current_task)) { + enqueue_command(&cmd_checkin); + } + } +} + diff --git a/src/tasks/temperature/temperature_task.c b/src/tasks/temperature/temperature_task.c new file mode 100644 index 00000000..91a93e92 --- /dev/null +++ b/src/tasks/temperature/temperature_task.c @@ -0,0 +1,77 @@ +#include "temperature_task.h" +#include "task_list.h" + +status_t temperature_read(temperature_data_t *const data) { + if (!data) { + return ERROR_SANITY_CHECK_FAILED; + } + + temp_sensor_sample_t sample = {0}; + status_t result = temp_sensor_sample(&sample); + if (result != SUCCESS) { + warning("temperature: sensor sample failed (%d)\n", result); + return result; + } + + data->sample = sample; + data->timestamp = xTaskGetTickCount(); + data->valid = true; + + return SUCCESS; +} + +command_t get_temperature_read_command(temperature_data_t *const data) { + temperature_read_args_t args = { + .data_buffer = data + }; + + return (command_t){ + .target = p_temperature_task, + .operation = OPERATION_TEMPERATURE_READ, + .p_data = &args, + .len = sizeof(temperature_read_args_t), + .result = PROCESSING, + .callback = NULL + }; +} + +QueueHandle_t init_temperature(void) { + QueueHandle_t queue_handle = xQueueCreateStatic(COMMAND_QUEUE_MAX_COMMANDS, COMMAND_QUEUE_ITEM_SIZE, + temperature_mem.temperature_command_queue_buffer, &temperature_mem.temperature_task_queue); + + if (queue_handle == NULL) { + fatal("temperature: failed to create command queue\n"); + } + + status_t status = temp_sensor_init(); + if (status != SUCCESS) { + warning("temperature: hardware init failed (%d)\n", status); + } + + return queue_handle; +} + +void exec_command_temperature(command_t *const p_cmd) { + if (!p_cmd) { + return; + } + + if (p_cmd->target != p_temperature_task) { + fatal("temperature: invalid command target (target %p)\n", (void *)p_cmd->target); + p_cmd->result = ERROR_SANITY_CHECK_FAILED; + return; + } + + switch (p_cmd->operation) { + case OPERATION_TEMPERATURE_READ: { + temperature_read_args_t *args = (temperature_read_args_t *)p_cmd->p_data; + p_cmd->result = temperature_read(args ? args->data_buffer : NULL); + break; + } + default: + fatal("temperature: unsupported operation %d\n", p_cmd->operation); + p_cmd->result = ERROR_SANITY_CHECK_FAILED; + break; + } +} + diff --git a/src/tasks/temperature/temperature_task.h b/src/tasks/temperature/temperature_task.h new file mode 100644 index 00000000..aad83d4d --- /dev/null +++ b/src/tasks/temperature/temperature_task.h @@ -0,0 +1,39 @@ +#ifndef TEMPERATURE_TASK_H +#define TEMPERATURE_TASK_H + +#include "globals.h" +#include "logging.h" +#include "queue.h" +#include "temperature_driver.h" +#include "watchdog_task.h" + +#define TEMPERATURE_TASK_STACK_SIZE 512U + +typedef struct { + StackType_t overflow_buffer[TASK_STACK_OVERFLOW_PADDING]; + StackType_t temperature_task_stack[TEMPERATURE_TASK_STACK_SIZE]; + uint8_t temperature_command_queue_buffer[COMMAND_QUEUE_MAX_COMMANDS * COMMAND_QUEUE_ITEM_SIZE]; + StaticQueue_t temperature_task_queue; + StaticTask_t temperature_task_tcb; +} temperature_task_memory_t; + +typedef struct { + temp_sensor_sample_t sample; + uint32_t timestamp; + bool valid; +} temperature_data_t; + +typedef struct { + temperature_data_t *data_buffer; +} temperature_read_args_t; + +extern temperature_task_memory_t temperature_mem; + +QueueHandle_t init_temperature(void); +void main_temperature(void *pvParameters); +void exec_command_temperature(command_t *const p_cmd); +status_t temperature_read(temperature_data_t *const data); +command_t get_temperature_read_command(temperature_data_t *const data); + +#endif // TEMPERATURE_TASK_H +