SatCam/STM32_mini/Src/satcam.c

592 wiersze
18 KiB
C

/*************************************************************************
*
* SatCam - Camera Module for PSAT-2
* Copyright (c) 2015-2017 Ales Povalac <alpov@alpov.net>
* Dept. of Radio Electronics, Brno University of Technology
*
* This work is licensed under the terms of the MIT license
*
*************************************************************************/
#include "cube.h"
#include "eeprom.h"
#include "ov2640.h"
#include "audio.h"
#include "comm.h"
#include "sstv.h"
#include "eeprom.h"
static uint8_t jpeg[IMG_BUFFER_SIZE];
static struct {
uint32_t length;
char overlay[2][TEXT_LEN];
} img;
IMPORT_BIN("Inc/sstv_monoscope.jpg", uint8_t, img_monoscope);
uint8_t *images[] = {
img_monoscope,
};
/* global configuration variables, linked from eeprom.c */
CONFIG_SYSTEM config;
const CONFIG_SYSTEM config_default = {
.cam = {
.delay = 1000,
.qs = 5,
.agc = true,
.aec = true,
.agc_ceiling = 16,
.agc_manual = 0,
.aec_manual = 0,
.awb = AWB_SUNNY,
.rotate = false,
},
.callsign = "SatCam",
.mode_cmd = {
"SSTV.LIVE.36",
"SSTV.LIVE.73",
"PSK.NVINFO.125.1000",
"SSTV.ROM.115.0",
},
.start_edge = EDGE_BOTH,
.holdoff = 2,
.autoreboot = 0,
.user_pin = 0,
};
static bool camera_snapshot(void)
{
set_led_red(true);
if (!ov2640_hilevel_init(config.cam)) {
set_led_red(false);
return false;
}
img.length = ov2640_snapshot(jpeg, sizeof(jpeg)); // requires enabled turbo
uint16_t agc = ov2640_get_current_agc();
uint16_t aec = ov2640_get_current_aec();
set_led_red(false);
ov2640_enable(false);
uint32_t img_counter = syslog_get_counter(LOG_CAM_SNAPSHOT) % 10000;
uint16_t voltage = adc_read_voltage();
snprintf(img.overlay[OVERLAY_HEADER], sizeof(img.overlay[OVERLAY_HEADER]), "%s +%05u %d\037C #%04u %umV", config.callsign,
(unsigned int)(HAL_GetTick() / 60000), adc_read_temperature(), (unsigned int)img_counter, voltage
);
snprintf(img.overlay[OVERLAY_IMG], sizeof(img.overlay[OVERLAY_IMG]), "#%04u %uB g%u e%u", (unsigned int)img_counter, (int)img.length, agc, aec);
return true;
}
static void send_downlink(CMD_RESULT what)
{
const char *text = NULL;
const char *cw = NULL;
switch (what) {
case R_OK:
text = "OK";
cw = CALLSIGN_CW " RRR";
break;
case R_OK_SILENT:
text = "OK";
break;
case R_ERR_SYNTAX:
text = "Syntax error";
cw = CALLSIGN_CW " EEEEE";
break;
case R_ERR_AUTH:
text = "Not authorized";
cw = CALLSIGN_CW " EEE AUTH";
break;
case R_BOOT_OK:
text = "Booted OK";
// cw = CALLSIGN_CW " BOOT";
break;
case R_BOOT_ERR:
text = "Booted, EEPROM checksum error";
// cw = CALLSIGN_CW " BOOT EEEEE";
break;
case R_QUEUED:
text = "Plan done";
break;
}
if (text) {
cmd_response(text);
}
if (cw) {
audio_start();
audio_morse(CW_WPM, CW_FREQ, cw);
audio_stop();
}
}
static CMD_RESULT cmd_sstv(char **saveptr)
{
char *token = strtok_r(NULL, CMD_SEPARATOR, saveptr);
if (token == NULL) return R_ERR_SYNTAX;
else if (streq(token, "live")) {
uint8_t mode = 36;
char *overlay = NULL;
if ((token = strtok_r(NULL, CMD_SEPARATOR, saveptr)) != NULL) mode = atol(token);
if ((token = strtok_r(NULL, CMD_SEPARATOR, saveptr)) != NULL) overlay = token;
/* start SSTV here */
enable_turbo(true); // peak 18% CPU
bool ok = camera_snapshot();
if (ok) ok = jpeg_test(jpeg, img.length);
if (ok) {
sstv_set_overlay(OVERLAY_HEADER, img.overlay[OVERLAY_HEADER]);
sstv_set_overlay(OVERLAY_IMG, img.overlay[OVERLAY_IMG]);
sstv_set_overlay(OVERLAY_LARGE, overlay);
sstv_set_overlay(OVERLAY_FROM, config.callsign);
sstv_play_jpeg(jpeg, mode);
}
enable_turbo(false);
return R_OK_SILENT;
}
else if (streq(token, "rom")) {
uint8_t mode = 36;
uint8_t sector = 0;
char *overlay = NULL;
if ((token = strtok_r(NULL, CMD_SEPARATOR, saveptr)) != NULL) mode = atol(token);
if ((token = strtok_r(NULL, CMD_SEPARATOR, saveptr)) != NULL) sector = atol(token);
if ((token = strtok_r(NULL, CMD_SEPARATOR, saveptr)) != NULL) overlay = token;
/* load image from ROM: check sector number */
if (sector >= sizeof(images)/sizeof(images[0])) return R_ERR_SYNTAX;
/* send image: mode, overlay */
snprintf(img.overlay[OVERLAY_HEADER], sizeof(img.overlay[OVERLAY_HEADER]), "%s +%05u %d\037C ROM #%u %umV", config.callsign,
(unsigned int)(HAL_GetTick() / 60000), adc_read_temperature(), sector, adc_read_voltage()
);
sstv_set_overlay(OVERLAY_HEADER, img.overlay[OVERLAY_HEADER]);
sstv_set_overlay(OVERLAY_IMG, NULL);
sstv_set_overlay(OVERLAY_LARGE, overlay);
sstv_set_overlay(OVERLAY_FROM, config.callsign);
enable_turbo(true); // peak 18% CPU
sstv_play_jpeg(images[sector], mode);
enable_turbo(false);
return R_OK_SILENT;
}
else return R_ERR_SYNTAX;
}
static CMD_RESULT cmd_psk(char **saveptr)
{
char *token = strtok_r(NULL, CMD_SEPARATOR, saveptr);
char buffer[SYSLOG_MAX_LENGTH];
uint16_t speed = PSK_SPEED;
uint16_t freq = PSK_FREQ;
if (streq(token, "nvinfo")) {
syslog_read_nvinfo(buffer);
speed = PSK_SPEED2;
}
else if (streq(token, "config")) {
syslog_read_config(buffer);
speed = PSK_SPEED2;
}
else if (token == NULL) {
snprintf(buffer, sizeof(buffer), "Greetings from %s, up %umin", config.callsign, (unsigned int)(HAL_GetTick() / 60000));
}
else {
snprintf(buffer, sizeof(buffer), "%s %s", config.callsign, token);
}
if ((token = strtok_r(NULL, CMD_SEPARATOR, saveptr)) != NULL) speed = atol(token);
if ((token = strtok_r(NULL, CMD_SEPARATOR, saveptr)) != NULL) freq = atol(token);
/* start PSK here */
enable_turbo(true); // peak 18% CPU
audio_start();
audio_psk(speed, freq, buffer); // 76% CPU without turbo, 4% CPU with turbo
audio_stop();
enable_turbo(false);
return R_OK_SILENT;
}
static CMD_RESULT cmd_cw(char **saveptr)
{
char *token = strtok_r(NULL, CMD_SEPARATOR, saveptr);
char buffer[CW_MAX_LENGTH];
if (token == NULL) {
snprintf(buffer, sizeof(buffer), "73 DE %s %s %s K", config.callsign, config.callsign, config.callsign);
}
else {
snprintf(buffer, sizeof(buffer), "%s %s", config.callsign, token);
}
uint16_t wpm = CW_WPM;
uint16_t freq = CW_FREQ;
if ((token = strtok_r(NULL, CMD_SEPARATOR, saveptr)) != NULL) wpm = atol(token);
if ((token = strtok_r(NULL, CMD_SEPARATOR, saveptr)) != NULL) freq = atol(token);
/* start CW here */
audio_start();
audio_morse(wpm, freq, buffer);
audio_stop();
return R_OK_SILENT;
}
static CMD_RESULT cmd_auth(char **saveptr)
{
char *token = strtok_r(NULL, CMD_SEPARATOR, saveptr);
if (token == NULL) return R_ERR_SYNTAX;
if (!auth_check_token(atol(token))) return R_ERR_AUTH;
return R_OK;
}
static CMD_RESULT cmd_camcfg(char **saveptr)
{
char *token = strtok_r(NULL, CMD_SEPARATOR, saveptr);
if (!auth_check_req()) return R_ERR_AUTH;
if (token == NULL) return R_ERR_SYNTAX;
else if (streq(token, "delay")) {
if ((token = strtok_r(NULL, CMD_SEPARATOR, saveptr)) == NULL) return R_ERR_SYNTAX;
config.cam.delay = atol(token);
return R_OK;
}
else if (streq(token, "qs")) {
if ((token = strtok_r(NULL, CMD_SEPARATOR, saveptr)) == NULL) return R_ERR_SYNTAX;
config.cam.qs = atol(token);
return R_OK;
}
else if (streq(token, "agc")) {
if ((token = strtok_r(NULL, CMD_SEPARATOR, saveptr)) == NULL) return R_ERR_SYNTAX;
if (streq(token, "ceiling")) {
if ((token = strtok_r(NULL, CMD_SEPARATOR, saveptr)) == NULL) return R_ERR_SYNTAX;
config.cam.agc = true;
config.cam.agc_ceiling = atol(token);
return R_OK;
}
else if (streq(token, "manual")) {
if ((token = strtok_r(NULL, CMD_SEPARATOR, saveptr)) == NULL) return R_ERR_SYNTAX;
config.cam.agc = false;
config.cam.agc_manual = atol(token);
return R_OK;
}
else return R_ERR_SYNTAX;
}
else if (streq(token, "aec")) {
if ((token = strtok_r(NULL, CMD_SEPARATOR, saveptr)) == NULL) return R_ERR_SYNTAX;
if (streq(token, "auto")) {
config.cam.aec = true;
return R_OK;
}
else if (streq(token, "manual")) {
if ((token = strtok_r(NULL, CMD_SEPARATOR, saveptr)) == NULL) return R_ERR_SYNTAX;
config.cam.aec = false;
config.cam.aec_manual = atol(token);
return R_OK;
}
else return R_ERR_SYNTAX;
}
else if (streq(token, "awb")) {
if ((token = strtok_r(NULL, CMD_SEPARATOR, saveptr)) == NULL) return R_ERR_SYNTAX;
if (streq(token, "auto")) {
config.cam.awb = AWB_AUTO;
return R_OK;
}
else if (streq(token, "sunny")) {
config.cam.awb = AWB_SUNNY;
return R_OK;
}
else if (streq(token, "cloudy")) {
config.cam.awb = AWB_CLOUDY;
return R_OK;
}
else if (streq(token, "office")) {
config.cam.awb = AWB_OFFICE;
return R_OK;
}
else if (streq(token, "home")) {
config.cam.awb = AWB_HOME;
return R_OK;
}
else return R_ERR_SYNTAX;
}
else if (streq(token, "rotate")) {
if ((token = strtok_r(NULL, CMD_SEPARATOR, saveptr)) == NULL) return R_ERR_SYNTAX;
if (streq(token, "off")) {
config.cam.rotate = 0;
return R_OK;
}
else if (streq(token, "on")) {
config.cam.rotate = 1;
return R_OK;
}
else return R_ERR_SYNTAX;
}
else if (streq(token, "start")) {
if ((token = strtok_r(NULL, CMD_SEPARATOR, saveptr)) == NULL) return R_ERR_SYNTAX;
uint8_t mode = atol(token);
if (strlen(*saveptr) >= STARTUP_CMD_LENGTH-1) return R_ERR_SYNTAX;
strncpy(config.mode_cmd[mode], *saveptr, STARTUP_CMD_LENGTH);
return R_OK;
}
else if (streq(token, "startedge")) {
if ((token = strtok_r(NULL, CMD_SEPARATOR, saveptr)) == NULL) return R_ERR_SYNTAX;
if (streq(token, "none")) {
config.start_edge = EDGE_NONE;
return R_OK;
}
else if (streq(token, "rising")) {
config.start_edge = EDGE_RISING;
return R_OK;
}
else if (streq(token, "falling")) {
config.start_edge = EDGE_FALLING;
return R_OK;
}
else if (streq(token, "any")) {
config.start_edge = EDGE_BOTH;
return R_OK;
}
else return R_ERR_SYNTAX;
}
else if (streq(token, "holdoff")) {
if ((token = strtok_r(NULL, CMD_SEPARATOR, saveptr)) == NULL) return R_ERR_SYNTAX;
config.holdoff = atol(token);
return R_OK;
}
else if (streq(token, "callsign")) {
if ((token = strtok_r(NULL, CMD_SEPARATOR, saveptr)) == NULL) return R_ERR_SYNTAX;
if (strlen(token) >= CALLSIGN_LENGTH-1) return R_ERR_SYNTAX;
strncpy(config.callsign, token, CALLSIGN_LENGTH);
return R_OK;
}
else if (streq(token, "autoreboot")) {
if ((token = strtok_r(NULL, CMD_SEPARATOR, saveptr)) == NULL) return R_ERR_SYNTAX;
config.autoreboot = atol(token);
return R_OK;
}
else if (streq(token, "userpin")) {
if ((token = strtok_r(NULL, CMD_SEPARATOR, saveptr)) == NULL) return R_ERR_SYNTAX;
config.user_pin = atol(token);
auth_check_token(config.user_pin); // authorize with new pin
return R_OK;
}
else if (streq(token, "clearlog")) {
eeprom_erase_full(false);
return R_OK;
}
else if (streq(token, "reboot")) {
NVIC_SystemReset(); // trigger NVIC system reset
return R_OK_SILENT; // to suppress warning
}
else if (streq(token, "load")) {
config_load_eeprom();
return R_OK;
}
else if (streq(token, "save")) {
config_save_eeprom();
return R_OK;
}
else if (streq(token, "default")) {
config_load_default();
return R_OK;
}
else return R_ERR_SYNTAX;
}
static CMD_RESULT cmd_debug(char **saveptr)
{
char *token = strtok_r(NULL, CMD_SEPARATOR, saveptr);
if (!auth_check_req()) return R_ERR_AUTH;
if (token == NULL) return R_ERR_SYNTAX;
else if (streq(token, "status")) {
char buffer[SYSLOG_MAX_LENGTH];
syslog_read_config(buffer);
printf_debug("\r%s\r", buffer);
syslog_read_nvinfo(buffer);
printf_debug("\r%s\r", buffer);
return R_OK_SILENT;
}
else if (streq(token, "reset")) {
if ((token = strtok_r(NULL, CMD_SEPARATOR, saveptr)) == NULL) return R_ERR_SYNTAX;
if (streq(token, "nvic")) {
NVIC_SystemReset(); // trigger NVIC system reset
return R_OK_SILENT; // to suppress warning
}
else if (streq(token, "watchdog")) {
while (1) {} // wait for watchdog reset, system halted
}
else if (streq(token, "fault")) {
void (*fn)() = (void*)0x0700000;
fn(); // trigger HardFault with invalid jump
return R_OK_SILENT; // to suppress warning
}
else return R_ERR_SYNTAX;
}
else if (streq(token, "sendjpeg")) {
enable_turbo(true);
if (!camera_snapshot()) img.length = 0;
enable_turbo(false);
HAL_UART_Transmit(&huart2, (char*)(&img.length), sizeof(img.length), HAL_MAX_DELAY);
uint8_t *ptr = jpeg;
uint32_t len = img.length;
while (len > 0) {
uint32_t len_next = len > 4096 ? 4096 : len;
HAL_UART_Transmit(&huart2, ptr, len_next, HAL_MAX_DELAY);
ptr += len_next;
len -= len_next;
HAL_IWDG_Refresh(&hiwdg);
}
return R_OK_SILENT;
}
else if (streq(token, "eeprom")) {
if ((token = strtok_r(NULL, CMD_SEPARATOR, saveptr)) == NULL) return R_ERR_SYNTAX;
if (streq(token, "fullerase")) {
eeprom_erase_full(true);
return R_OK;
}
else if (streq(token, "dump")) {
uint16_t addr = 0;
while (addr < 0x0200) {
uint8_t buffer[32];
char s[200];
eeprom_read(addr, buffer, sizeof(buffer));
s[0] = '\0';
for (uint8_t i = 0; i < sizeof(buffer); i++) {
sprintf(s, "%s%02X ", s, buffer[i]);
}
printf_debug(s);
addr += sizeof(buffer);
HAL_IWDG_Refresh(&hiwdg);
}
return R_OK_SILENT;
}
else return R_ERR_SYNTAX;
}
else if (streq(token, "adc")) {
if ((token = strtok_r(NULL, CMD_SEPARATOR, saveptr)) == NULL) return R_ERR_SYNTAX;
if (streq(token, "voltage")) {
printf_debug("Voltage = %u mV", adc_read_voltage());
return R_OK_SILENT;
}
else if (streq(token, "temp")) {
printf_debug("Temperature = %d'C", adc_read_temperature());
return R_OK_SILENT;
}
else return R_ERR_SYNTAX;
}
else return R_ERR_SYNTAX;
}
void cmd_handler(char *cmd)
{
char *token, *saveptr;
CMD_RESULT result = R_ERR_SYNTAX;
set_led_yellow(true);
token = strtok_r(cmd, CMD_SEPARATOR, &saveptr);
if (streq(token, "sstv")) {
result = cmd_sstv(&saveptr);
}
else if (streq(token, "psk")) {
result = cmd_psk(&saveptr);
}
else if (streq(token, "cw")) {
result = cmd_cw(&saveptr);
}
else if (streq(token, "auth")) {
result = cmd_auth(&saveptr);
}
else if (streq(token, "camcfg")) {
result = cmd_camcfg(&saveptr);
}
else if (streq(token, "debug")) {
result = cmd_debug(&saveptr);
}
else if (*token == '\0') {
result = R_OK_SILENT;
}
set_led_yellow(false);
send_downlink(result);
}
void start_in_task(void)
{
bool start_new = HAL_GPIO_ReadPin(GPIO_START_GPIO_Port, GPIO_START_Pin) == GPIO_PIN_SET;
static bool start_old;
static uint32_t start_tick;
uint8_t mode = 0;
bool start = false;
if (HAL_GetTick() <= start_tick) return;
mode |= (HAL_GPIO_ReadPin(GPIO_M0_GPIO_Port, GPIO_M0_Pin) == GPIO_PIN_SET) ? 1 : 0;
mode |= (HAL_GPIO_ReadPin(GPIO_M1_GPIO_Port, GPIO_M1_Pin) == GPIO_PIN_SET) ? 2 : 0;
if ((config.start_edge & EDGE_RISING) && (start_new && !start_old)) start = true;
if ((config.start_edge & EDGE_FALLING) && (!start_new && start_old)) start = true;
if (start) {
cmd_handler_const(config.mode_cmd[mode]);
start_tick = HAL_GetTick() + config.holdoff*1000;
}
start_old = HAL_GPIO_ReadPin(GPIO_START_GPIO_Port, GPIO_START_Pin) == GPIO_PIN_SET;
}
void main_satcam()
{
#if ENABLE_SWD_DEBUG
/* enable debug access */
HAL_DBGMCU_EnableDBGSleepMode();
__HAL_DBGMCU_FREEZE_IWDG();
#endif
/* initialize peripherals */
set_led_red(true);
eeprom_init();
comm_init(); // last
if (config_load_eeprom()) send_downlink(R_BOOT_OK);
else send_downlink(R_BOOT_ERR);
syslog_event(LOG_BOOT);
set_led_red(false);
/* main loop */
while (1) {
/* tasks */
comm_cmd_task();
start_in_task();
/* autoreboot */
if ((config.autoreboot >= 120) && (HAL_GetTick()/1000 > config.autoreboot)) NVIC_SystemReset();
/* watchdog reset */
HAL_IWDG_Refresh(&hiwdg);
/* fix bug with UART DMA error handler */
comm_init();
}
}