├── README.md ├── app1 └── app_main.c └── modules ├── blinky └── blinky.c ├── cmd └── cmd.c ├── console └── console.c ├── dio └── dio.c ├── gps_gtu7 └── gps_gtu7.c ├── include ├── blinky.h ├── cmd.h ├── console.h ├── dio.h ├── gps_gtu7.h ├── log.h ├── mem.h ├── module.h ├── stat.h ├── tmr.h └── ttys.h ├── log └── log.c ├── mem └── mem.c ├── stat └── stat.c ├── templates ├── module.c.template └── module.h.template ├── tmr └── tmr.c └── ttys └── ttys.c /README.md: -------------------------------------------------------------------------------- 1 | This repo contains the code for my YouTube class: 2 | "Bare Metal Embedded Software Development: Theory and Practice Using STM32". 3 | This course consists of around 25 video lessons. The URL is: 4 | 5 | https://www.youtube.com/playlist?list=PL4cGeWgaBTe155QQSQ72DksLIjBn5Jn2Z 6 | 7 | This course was developed using a `ST NUCLEO-F401RE` board. Since then, I ported 8 | the code to run on a `ST NUCLEO-L452RE` board. That code is on branch 9 | `nucleo-l452re`. Someday I might merge these two branches, and use `#defines` to 10 | select the board type. 11 | 12 | If you do use the branch for STM32LXX, be aware there is a bug in the IDE for 13 | this case. I reported it here: 14 | 15 | https://community.st.com/s/profile/0053W000001pgpwQAA 16 | 17 | To workaround this bug, you have to change this generated line of code in `main.c`: 18 | 19 | `LL_RCC_HSI_SetCalibTrimming(16);` 20 | 21 | to: 22 | 23 | `LL_RCC_HSI_SetCalibTrimming(64);` 24 | -------------------------------------------------------------------------------- /app1/app_main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * @brief Main application file 3 | * 4 | * This file is the main application file that initializes and starts the various 5 | * modules and then runs the super loop. 6 | * 7 | * MIT License 8 | * 9 | * Copyright (c) 2021 Eugene R Schroeder 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in 19 | * all copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | * SOFTWARE. 28 | */ 29 | 30 | #include 31 | #include 32 | #include 33 | 34 | #include "blinky.h" 35 | #include "cmd.h" 36 | #include "console.h" 37 | #include "dio.h" 38 | #include "gps_gtu7.h" 39 | #include "log.h" 40 | #include "mem.h" 41 | #include "module.h" 42 | #include "ttys.h" 43 | #include "stat.h" 44 | #include "tmr.h" 45 | 46 | //////////////////////////////////////////////////////////////////////////////// 47 | // Common macros 48 | //////////////////////////////////////////////////////////////////////////////// 49 | 50 | //////////////////////////////////////////////////////////////////////////////// 51 | // Type definitions 52 | //////////////////////////////////////////////////////////////////////////////// 53 | 54 | enum main_u16_pms { 55 | CNT_INIT_ERR, 56 | CNT_START_ERR, 57 | CNT_RUN_ERR, 58 | 59 | NUM_U16_PMS 60 | }; 61 | 62 | //////////////////////////////////////////////////////////////////////////////// 63 | // Private (static) function declarations 64 | //////////////////////////////////////////////////////////////////////////////// 65 | 66 | static int32_t cmd_main_status(); 67 | 68 | //////////////////////////////////////////////////////////////////////////////// 69 | // Private (static) variables 70 | //////////////////////////////////////////////////////////////////////////////// 71 | 72 | static int32_t log_level = LOG_DEFAULT; 73 | 74 | static struct cmd_cmd_info cmds[] = { 75 | { 76 | .name = "status", 77 | .func = cmd_main_status, 78 | .help = "Get main status, usage: main status [clear]", 79 | }, 80 | }; 81 | 82 | static uint16_t cnts_u16[NUM_U16_PMS]; 83 | 84 | static const char* cnts_u16_names[NUM_U16_PMS] = { 85 | "init err", 86 | "start err", 87 | "run err", 88 | }; 89 | 90 | static struct cmd_client_info cmd_info = { 91 | .name = "main", 92 | .num_cmds = ARRAY_SIZE(cmds), 93 | .cmds = cmds, 94 | .log_level_ptr = &log_level, 95 | .num_u16_pms = NUM_U16_PMS, 96 | .u16_pms = cnts_u16, 97 | .u16_pm_names = cnts_u16_names, 98 | }; 99 | 100 | 101 | // Config info for dio module. These variables must be static since the dio 102 | // module holds a pointer to them. 103 | 104 | enum dout_index { 105 | DIN_BUTTON_1, 106 | DIN_GPS_PPS, 107 | 108 | DIN_NUM 109 | }; 110 | 111 | static struct dio_in_info d_inputs[DIN_NUM] = { 112 | { 113 | // Button 1 114 | .name = "Button_1", 115 | .port = DIO_PORT_C, 116 | .pin = DIO_PIN_13, 117 | .pull = DIO_PULL_NO, 118 | .invert = 1, 119 | }, 120 | { 121 | // GPS PPS, connected to PB2 (CN10, pin 22). 122 | .name = "PPS", 123 | .port = DIO_PORT_A, 124 | .pin = DIO_PIN_8, 125 | .pull = DIO_PULL_NO, 126 | } 127 | }; 128 | 129 | enum din_index { 130 | DOUT_LED_2, 131 | 132 | DOUT_NUM 133 | }; 134 | 135 | static struct dio_out_info d_outputs[DOUT_NUM] = { 136 | { 137 | // LED 2 138 | .name = "LED_2", 139 | .port = DIO_PORT_A, 140 | .pin = DIO_PIN_5, 141 | .pull = DIO_PULL_NO, 142 | .init_value = 0, 143 | .speed = DIO_SPEED_FREQ_LOW, 144 | .output_type = DIO_OUTPUT_PUSHPULL, 145 | } 146 | }; 147 | 148 | static struct dio_cfg dio_cfg = { 149 | .num_inputs = ARRAY_SIZE(d_inputs), 150 | .inputs = d_inputs, 151 | .num_outputs = ARRAY_SIZE(d_outputs), 152 | .outputs = d_outputs, 153 | }; 154 | 155 | static struct stat_dur stat_loop_dur; 156 | 157 | //////////////////////////////////////////////////////////////////////////////// 158 | // Public (global) variables and externs 159 | //////////////////////////////////////////////////////////////////////////////// 160 | 161 | //////////////////////////////////////////////////////////////////////////////// 162 | // Public (global) functions 163 | //////////////////////////////////////////////////////////////////////////////// 164 | 165 | void app_main(void) 166 | { 167 | int32_t result;; 168 | struct console_cfg console_cfg; 169 | struct gps_cfg gps_cfg; 170 | struct ttys_cfg ttys_cfg; 171 | struct blinky_cfg blinky_cfg = { 172 | .dout_idx = DOUT_LED_2, 173 | .code_num_blinks = 5, 174 | .code_period_ms = 1000, 175 | .sep_num_blinks = 5, 176 | .sep_period_ms = 200, 177 | }; 178 | 179 | // 180 | // Invoke the init API on modules the use it. 181 | // 182 | 183 | setvbuf(stdout, NULL, _IONBF, 0); 184 | // Before ttys is init-ed we have to put in \r explicitly. 185 | printf("\n\rInit: Init modules\n\r"); 186 | result = ttys_get_def_cfg(TTYS_INSTANCE_UART2, &ttys_cfg); 187 | if (result < 0) { 188 | log_error("ttys_get_def_cfg error %d\n", result); 189 | INC_SAT_U16(cnts_u16[CNT_INIT_ERR]); 190 | } else { 191 | result = ttys_init(TTYS_INSTANCE_UART2, &ttys_cfg); 192 | if (result < 0) { 193 | log_error("ttys_init UART2 error %d\n", result); 194 | INC_SAT_U16(cnts_u16[CNT_INIT_ERR]); 195 | } 196 | } 197 | 198 | result = ttys_get_def_cfg(TTYS_INSTANCE_UART6, &ttys_cfg); 199 | if (result < 0) { 200 | log_error("ttys_get_def_cfg error %d\n", result); 201 | INC_SAT_U16(cnts_u16[CNT_INIT_ERR]); 202 | } else { 203 | result = ttys_init(TTYS_INSTANCE_UART6, &ttys_cfg); 204 | if (result < 0) { 205 | log_error("ttys_init UART6 error %d\n", result); 206 | INC_SAT_U16(cnts_u16[CNT_INIT_ERR]); 207 | } 208 | } 209 | 210 | result = cmd_init(NULL); 211 | if (result < 0) { 212 | log_error("cmd_init error %d\n", result); 213 | INC_SAT_U16(cnts_u16[CNT_INIT_ERR]); 214 | } 215 | 216 | result = console_get_def_cfg(&console_cfg); 217 | if (result < 0) { 218 | log_error("console_get_def_cfg error %d\n", result); 219 | INC_SAT_U16(cnts_u16[CNT_INIT_ERR]); 220 | } else { 221 | result = console_init(&console_cfg); 222 | if (result < 0) { 223 | log_error("console_init error %d\n", result); 224 | INC_SAT_U16(cnts_u16[CNT_INIT_ERR]); 225 | } 226 | } 227 | 228 | result = tmr_init(NULL); 229 | if (result < 0) { 230 | log_error("tmr_init error %d\n", result); 231 | INC_SAT_U16(cnts_u16[CNT_INIT_ERR]); 232 | } 233 | 234 | result = dio_init(&dio_cfg); 235 | if (result < 0) { 236 | log_error("dio_init error %d\n", result); 237 | INC_SAT_U16(cnts_u16[CNT_INIT_ERR]); 238 | } 239 | 240 | result = gps_get_def_cfg(&gps_cfg); 241 | if (result < 0) { 242 | log_error("gps_get_def_cfg error %d\n", result); 243 | INC_SAT_U16(cnts_u16[CNT_INIT_ERR]); 244 | } else { 245 | result = gps_init(&gps_cfg); 246 | if (result < 0) { 247 | log_error("gps_init error %d\n", result); 248 | INC_SAT_U16(cnts_u16[CNT_INIT_ERR]); 249 | } 250 | } 251 | 252 | result = blinky_init(&blinky_cfg); 253 | if (result < 0) { 254 | log_error("blinky_init error %d\n", result); 255 | INC_SAT_U16(cnts_u16[CNT_INIT_ERR]); 256 | } 257 | 258 | // 259 | // Invoke the start API on modules the use it. 260 | // 261 | 262 | printf("Init: Start modules\n"); 263 | 264 | result = ttys_start(TTYS_INSTANCE_UART2); 265 | if (result < 0) { 266 | log_error("ttys_start UART2 error %d\n", result); 267 | INC_SAT_U16(cnts_u16[CNT_START_ERR]); 268 | } 269 | 270 | result = ttys_start(TTYS_INSTANCE_UART6); 271 | if (result < 0) { 272 | log_error("ttys_start UART6 error %d\n", result); 273 | INC_SAT_U16(cnts_u16[CNT_START_ERR]); 274 | } 275 | 276 | result = tmr_start(); 277 | if (result < 0) { 278 | log_error("tmr_start error %d\n", result); 279 | INC_SAT_U16(cnts_u16[CNT_START_ERR]); 280 | } 281 | 282 | result = dio_start(); 283 | if (result < 0) { 284 | log_error("dio_start error %d\n", result); 285 | INC_SAT_U16(cnts_u16[CNT_START_ERR]); 286 | } 287 | 288 | result = gps_start(); 289 | if (result < 0) { 290 | log_error("gps_start error %d\n", result); 291 | INC_SAT_U16(cnts_u16[CNT_START_ERR]); 292 | } 293 | 294 | result = mem_start(); 295 | if (result < 0) { 296 | log_error("mem_start error %d\n", result); 297 | INC_SAT_U16(cnts_u16[CNT_START_ERR]); 298 | } 299 | 300 | result = blinky_start(); 301 | if (result < 0) { 302 | log_error("blinky_start error %d\n", result); 303 | INC_SAT_U16(cnts_u16[CNT_START_ERR]); 304 | } 305 | 306 | result = cmd_register(&cmd_info); 307 | if (result < 0) { 308 | log_error("main: cmd_register error %d\n", result); 309 | INC_SAT_U16(cnts_u16[CNT_START_ERR]); 310 | } 311 | 312 | stat_dur_init(&stat_loop_dur); 313 | 314 | // 315 | // In the super loop invoke the run API on modules the use it. 316 | // 317 | 318 | printf("Init: Enter super loop\n"); 319 | 320 | while (1) 321 | { 322 | stat_dur_restart(&stat_loop_dur); 323 | 324 | result = console_run(); 325 | if (result < 0) 326 | INC_SAT_U16(cnts_u16[CNT_RUN_ERR]); 327 | 328 | result = gps_run(); 329 | if (result < 0) 330 | INC_SAT_U16(cnts_u16[CNT_RUN_ERR]); 331 | 332 | result = tmr_run(); 333 | if (result < 0) 334 | INC_SAT_U16(cnts_u16[CNT_RUN_ERR]); 335 | 336 | } 337 | } 338 | 339 | //////////////////////////////////////////////////////////////////////////////// 340 | // Private (static) functions 341 | //////////////////////////////////////////////////////////////////////////////// 342 | 343 | /* 344 | * @brief Console command function for "main status". 345 | * 346 | * @param[in] argc Number of arguments, including "main" 347 | * @param[in] argv Argument values, including "main" 348 | * 349 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 350 | * 351 | * Command usage: main status [clear] 352 | */ 353 | static int32_t cmd_main_status(int32_t argc, const char** argv) 354 | { 355 | bool clear = false; 356 | bool bad_arg = false; 357 | 358 | if (argc == 3) { 359 | if (strcasecmp(argv[2], "clear") == 0) 360 | clear = true; 361 | else 362 | bad_arg = true; 363 | } else if (argc > 3) { 364 | bad_arg = true; 365 | } 366 | 367 | if (bad_arg) { 368 | printf("Invalid arguments\n"); 369 | return MOD_ERR_ARG; 370 | } 371 | 372 | printf("Super loop samples=%lu min=%lu ms, max=%lu ms, avg=%lu us\n", 373 | stat_loop_dur.samples, stat_loop_dur.min, stat_loop_dur.max, 374 | stat_dur_avg_us(&stat_loop_dur)); 375 | 376 | if (clear) { 377 | printf("Clearing loop stat\n"); 378 | stat_dur_init(&stat_loop_dur); 379 | } 380 | return 0; 381 | } 382 | -------------------------------------------------------------------------------- /modules/blinky/blinky.c: -------------------------------------------------------------------------------- 1 | /* 2 | * @brief Implementation of blinky module. 3 | * 4 | * This module blinks an LED a given number of times, at a given rate. It then 5 | * blinks a "separator" which some number of blinks at a different 6 | * rate. Normally the separator rate is much faster than the blink rate. This 7 | * process then repeats. 8 | * 9 | * The following console commands are provided: 10 | * > blinky status 11 | * > blinky blinks num-blinks period-ms 12 | * > blinky sep num-blinks period-ms 13 | * See code for details. 14 | * 15 | * MIT License 16 | * 17 | * Copyright (c) 2021 Eugene R Schroeder 18 | * 19 | * Permission is hereby granted, free of charge, to any person obtaining a copy 20 | * of this software and associated documentation files (the "Software"), to deal 21 | * in the Software without restriction, including without limitation the rights 22 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | * copies of the Software, and to permit persons to whom the Software is 24 | * furnished to do so, subject to the following conditions: 25 | * 26 | * The above copyright notice and this permission notice shall be included in 27 | * all copies or substantial portions of the Software. 28 | * 29 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 35 | * SOFTWARE. 36 | */ 37 | 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | #include "stm32f4xx_ll_cortex.h" 44 | 45 | #include "cmd.h" 46 | #include "dio.h" 47 | #include "log.h" 48 | #include "module.h" 49 | #include "tmr.h" 50 | 51 | #include "blinky.h" 52 | 53 | //////////////////////////////////////////////////////////////////////////////// 54 | // Common macros 55 | //////////////////////////////////////////////////////////////////////////////// 56 | 57 | #define PRE_BLINK_DELAY_MS 2000 58 | #define POST_BLINK_DELAY_MS 2000 59 | 60 | //////////////////////////////////////////////////////////////////////////////// 61 | // Type definitions 62 | //////////////////////////////////////////////////////////////////////////////// 63 | 64 | enum states { 65 | STATE_OFF, 66 | STATE_SEPARATOR_ON, 67 | STATE_SEPARATOR_OFF, 68 | STATE_PRE_BLINK_DELAY, 69 | STATE_BLINK_ON, 70 | STATE_BLINK_OFF, 71 | STATE_POST_BLINK_DELAY, 72 | }; 73 | 74 | struct blinky_state 75 | { 76 | struct blinky_cfg cfg; 77 | enum states state; 78 | bool valid_dio; 79 | uint32_t blink_counter; 80 | int32_t tmr_id; 81 | }; 82 | //////////////////////////////////////////////////////////////////////////////// 83 | // Private (static) function declarations 84 | //////////////////////////////////////////////////////////////////////////////// 85 | 86 | static int32_t cmd_blinky_status(int32_t argc, const char** argv); 87 | static int32_t cmd_blinky_blinks(int32_t argc, const char** argv); 88 | static void start(); 89 | static enum tmr_cb_action tmr_cb(int32_t tmr_id, uint32_t user_data); 90 | 91 | //////////////////////////////////////////////////////////////////////////////// 92 | // Private (static) variables 93 | //////////////////////////////////////////////////////////////////////////////// 94 | 95 | struct blinky_state state; 96 | 97 | static struct cmd_cmd_info cmds[] = { 98 | { 99 | .name = "status", 100 | .func = cmd_blinky_status, 101 | .help = "Get module status, usage: blinky status", 102 | }, 103 | { 104 | .name = "blinks", 105 | .func = cmd_blinky_blinks, 106 | .help = "Set blinks/period, usage: blinky blinks num-blinks [period-ms]", 107 | }, 108 | { 109 | .name = "sep", 110 | .func = cmd_blinky_blinks, 111 | .help = "Set separator blinks/period, usage: blinky sep num-blinks [period-ms]", 112 | }, 113 | }; 114 | 115 | static int32_t log_level = LOG_DEFAULT; 116 | 117 | static struct cmd_client_info cmd_info = { 118 | .name = "blinky", 119 | .num_cmds = ARRAY_SIZE(cmds), 120 | .cmds = cmds, 121 | .log_level_ptr = &log_level, 122 | }; 123 | 124 | //////////////////////////////////////////////////////////////////////////////// 125 | // Public (global) variables and externs 126 | //////////////////////////////////////////////////////////////////////////////// 127 | 128 | //////////////////////////////////////////////////////////////////////////////// 129 | // Public (global) functions 130 | //////////////////////////////////////////////////////////////////////////////// 131 | 132 | /* 133 | * @brief Get default blinky configuration. 134 | * 135 | * @param[out] cfg The blinky configuration with defaults filled in. 136 | * 137 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 138 | */ 139 | int32_t blinky_get_def_cfg(struct blinky_cfg* cfg) 140 | { 141 | if (cfg == NULL) 142 | return MOD_ERR_ARG; 143 | 144 | memset(cfg, 0, sizeof(*cfg)); 145 | cfg->code_num_blinks = 1; 146 | cfg->code_period_ms = 1000; 147 | cfg->sep_num_blinks = 5; 148 | cfg->sep_period_ms = 200; 149 | 150 | return 0; 151 | } 152 | 153 | /* 154 | * @brief Initialize blinky module instance. 155 | * 156 | * @param[in] cfg The blinky configuration. (FUTURE) 157 | * 158 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 159 | * 160 | * This function initializes the blinky singleton module. Generally, it should 161 | * not access other modules as they might not have been initialized yet. An 162 | * exception is the log module. 163 | */ 164 | int32_t blinky_init(struct blinky_cfg* cfg) 165 | { 166 | log_debug("In blinky_init()\n"); 167 | 168 | memset(&state, 0, sizeof(state)); 169 | state.tmr_id = -1; 170 | 171 | state.cfg = *cfg; 172 | return 0; 173 | } 174 | 175 | /* 176 | * @brief Start blinky module instance. 177 | * 178 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 179 | * 180 | * This function starts the blinky singleton module, to enter normal operation. 181 | */ 182 | int32_t blinky_start(void) 183 | { 184 | int32_t result; 185 | 186 | log_debug("In blinky_start()\n"); 187 | result = dio_get_num_out(); 188 | if (result < 0) { 189 | log_error("blinky_start: dio error %d\n", result); 190 | return MOD_ERR_RESOURCE; 191 | } 192 | else if (state.cfg.dout_idx >= result) { 193 | log_error("blinky_start: bad dout_idx %u (>=%d)\n", 194 | state.cfg.dout_idx, result); 195 | return MOD_ERR_ARG; 196 | } 197 | state.valid_dio = true; 198 | result = cmd_register(&cmd_info); 199 | if (result < 0) { 200 | log_error("blinky_start: cmd error %d\n", result); 201 | return MOD_ERR_RESOURCE; 202 | } 203 | state.tmr_id = tmr_inst_get_cb(0, tmr_cb, 0); 204 | if (state.tmr_id < 0) { 205 | log_error("blinky_start: tmr error %d\n", state.tmr_id); 206 | return MOD_ERR_RESOURCE; 207 | } 208 | start(); 209 | return 0; 210 | } 211 | 212 | /* 213 | * @brief Set number of code blinks. 214 | * 215 | * @param[in] num_blinks The number of blinks (set to 0 for no blinks). 216 | * 217 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 218 | */ 219 | int32_t blinky_set_code_blinks(uint32_t num_blinks) 220 | { 221 | state.cfg.code_num_blinks = num_blinks; 222 | start(); 223 | return 0; 224 | } 225 | 226 | /* 227 | * @brief Set number of separator blinks. 228 | * 229 | * @param[in] num_blinks The number of separator blinks (set to 0 for no 230 | * separator blinks). 231 | * 232 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 233 | */ 234 | int32_t blinky_set_sep_blinks(uint32_t num_blinks) 235 | { 236 | state.cfg.sep_num_blinks = num_blinks; 237 | start(); 238 | return 0; 239 | } 240 | 241 | /* 242 | * @brief Set code blink period. 243 | * 244 | * @param[in] period_ms Period in ms (if set to 0, there will be no code blinks). 245 | * 246 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 247 | */ 248 | int32_t blinky_set_code_period(uint32_t period_ms) 249 | { 250 | state.cfg.code_period_ms = period_ms; 251 | start(); 252 | return 0; 253 | } 254 | 255 | /* 256 | * @brief Set separator blink period. 257 | * 258 | * @param[in] period_ms Period in ms (if set to 0, there will be no separator 259 | * blinks). 260 | * 261 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 262 | */ 263 | int32_t blinky_set_sep_period(uint32_t period_ms) 264 | { 265 | state.cfg.sep_period_ms = period_ms; 266 | start(); 267 | return 0; 268 | } 269 | 270 | //////////////////////////////////////////////////////////////////////////////// 271 | // Private (static) functions 272 | //////////////////////////////////////////////////////////////////////////////// 273 | 274 | /* 275 | * @brief Console command function for "blinky status". 276 | * 277 | * @param[in] argc Number of arguments, including "blinky" 278 | * @param[in] argv Argument values, including "blinky" 279 | * 280 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 281 | * 282 | * Command usage: blinky status 283 | */ 284 | static int32_t cmd_blinky_status(int32_t argc, const char** argv) 285 | { 286 | printf("state=%d blink_counter=%lu tmr_id=%ld\n", 287 | state.state, state.blink_counter, state.tmr_id); 288 | printf("code-num-blinks=%lu code-period-ms=%lu sep-num-blinks=%lu sep-perioid-ms=%lu\n", 289 | state.cfg.code_num_blinks, state.cfg.code_period_ms, 290 | state.cfg.sep_num_blinks, state.cfg.sep_period_ms); 291 | return 0; 292 | } 293 | 294 | /* 295 | * @brief Console command function for "blinky blinks" and "blinky sep" 296 | * 297 | * @param[in] argc Number of arguments, including "blinky" 298 | * @param[in] argv Argument values, including "blinky" 299 | * 300 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 301 | * 302 | * Command usage: 303 | * blinky blinks num-blinks [period-ms] 304 | * blinky sep num-blinks [period-ms] 305 | */ 306 | static int32_t cmd_blinky_blinks(int32_t argc, const char** argv) 307 | { 308 | struct cmd_arg_val arg_vals[2]; 309 | int32_t num_args; 310 | 311 | num_args = cmd_parse_args(argc-2, argv+2, "u[u]", arg_vals); 312 | if (num_args < 0) 313 | return MOD_ERR_BAD_CMD; 314 | 315 | if (strcasecmp(argv[1], "blinks") == 0) { 316 | blinky_set_code_blinks(arg_vals[0].val.u); 317 | if (num_args >= 2) 318 | blinky_set_code_period(arg_vals[1].val.u); 319 | } else { 320 | blinky_set_sep_blinks(arg_vals[0].val.u); 321 | if (num_args >= 2) 322 | blinky_set_sep_period(arg_vals[1].val.u); 323 | } 324 | start(); 325 | return 0; 326 | } 327 | 328 | /* 329 | * @brief Start or restart blinky. 330 | * 331 | * This is called during the initial module start, and after any change of 332 | * parameters. 333 | */ 334 | void start(void) 335 | { 336 | if (!state.valid_dio || state.tmr_id < 0) 337 | return; 338 | 339 | dio_set(state.cfg.dout_idx, false); 340 | state.state = STATE_PRE_BLINK_DELAY; 341 | tmr_inst_start(state.tmr_id, PRE_BLINK_DELAY_MS); 342 | } 343 | 344 | /* 345 | * @brief Blinky timer callback. 346 | * 347 | * @param[in] tmr_id Timer ID. 348 | * @param[in] user_data User callback data. 349 | * 350 | * @return TMR_CB_NONE 351 | */ 352 | enum tmr_cb_action tmr_cb(int32_t tmr_id, uint32_t user_data) 353 | { 354 | enum states next_state; 355 | bool led_on; 356 | uint32_t next_timer_value; 357 | 358 | switch (state.state) { 359 | case STATE_OFF: 360 | log_error("blinky unexpected tmr_cb in off state\n"); 361 | return TMR_CB_NONE; 362 | 363 | case STATE_SEPARATOR_ON: 364 | if (++state.blink_counter < state.cfg.sep_num_blinks) { 365 | next_state = STATE_SEPARATOR_OFF; 366 | led_on = false; 367 | next_timer_value = (state.cfg.sep_period_ms+1)/2; 368 | } else { 369 | next_state = STATE_PRE_BLINK_DELAY; 370 | led_on = false; 371 | next_timer_value = PRE_BLINK_DELAY_MS; 372 | } 373 | break; 374 | 375 | case STATE_SEPARATOR_OFF: 376 | next_state = STATE_SEPARATOR_ON; 377 | led_on = true; 378 | next_timer_value = (state.cfg.sep_period_ms+1)/2; 379 | break; 380 | 381 | case STATE_PRE_BLINK_DELAY: 382 | if (state.cfg.code_period_ms > 0 && state.cfg.code_num_blinks > 0) { 383 | next_state = STATE_BLINK_ON; 384 | led_on = true; 385 | next_timer_value = (state.cfg.code_period_ms+1)/2; 386 | state.blink_counter = 0; 387 | } else { 388 | next_state = STATE_POST_BLINK_DELAY; 389 | led_on = false; 390 | next_timer_value = POST_BLINK_DELAY_MS; 391 | } 392 | break; 393 | 394 | case STATE_BLINK_ON: 395 | if (++state.blink_counter < state.cfg.code_num_blinks) { 396 | next_state = STATE_BLINK_OFF; 397 | led_on = false; 398 | next_timer_value = (state.cfg.code_period_ms+1)/2; 399 | } else { 400 | next_state = STATE_POST_BLINK_DELAY; 401 | led_on = false; 402 | next_timer_value = POST_BLINK_DELAY_MS; 403 | } 404 | break; 405 | 406 | case STATE_BLINK_OFF: 407 | next_state = STATE_BLINK_ON; 408 | led_on = true; 409 | next_timer_value = (state.cfg.code_period_ms+1)/2; 410 | break; 411 | 412 | case STATE_POST_BLINK_DELAY: 413 | if (state.cfg.sep_period_ms > 0 && state.cfg.sep_num_blinks > 0) { 414 | next_state = STATE_SEPARATOR_ON; 415 | led_on = true; 416 | next_timer_value = (state.cfg.sep_period_ms+1)/2; 417 | state.blink_counter = 0; 418 | } else { 419 | next_state = STATE_PRE_BLINK_DELAY; 420 | led_on = false; 421 | next_timer_value = PRE_BLINK_DELAY_MS; 422 | } 423 | break; 424 | 425 | default: 426 | log_error("blinky unexpected state %d\n", state.state); 427 | next_state = STATE_OFF; 428 | return TMR_CB_NONE; 429 | } 430 | 431 | state.state = next_state; 432 | dio_set(state.cfg.dout_idx, led_on); 433 | tmr_inst_start(state.tmr_id, next_timer_value); 434 | return TMR_CB_NONE; 435 | } 436 | 437 | -------------------------------------------------------------------------------- /modules/cmd/cmd.c: -------------------------------------------------------------------------------- 1 | /* 2 | * @brief Implementation of cmd module. 3 | * 4 | * This module is a utility used by clients (e.g. other modules) to provide 5 | * console commands. It works like this: 6 | * - Client register a set of console commands during startup. 7 | * - When the console module receives a command line string, it passes it to 8 | * this (cmd) module which parses the string, and handles it directly, or 9 | * calls a client command handler function. 10 | * - This module also provides some common commands automatically, on behalf 11 | * of the clients, as described below. 12 | * 13 | * Generally, a command line string consists of a client name, then the command 14 | * name, and then optional command arguments. 15 | * 16 | * For example, say the tmr module has two commands, "status" and "test". The 17 | * tmr module would register these commands, with this (cmd) module. It would 18 | * specify a client name of "tmr". It would provide information for the commands 19 | * "status" and "test". Then, to invoke these commands, the console user would 20 | * enter commands like this. 21 | * 22 | * > tmr status 23 | * > tmr test get 0 24 | * 25 | * This module would parse the command line, and call the appropriate command 26 | * handler function specified by the tmr client (as part of registration). 27 | * 28 | * All command line tokens (i.e. the client name, the command name, and any 29 | * arguments) are passed to the command handler using the (int32_t argc, char** 30 | * argv) pattern. It is up to the command handler function to validate the 31 | * arguments. 32 | * 33 | * The cmd module provides several commands automatically on behalf of the 34 | * clients: 35 | * - A help command. For example, if the console user enters "tmr help" then a 36 | * list of tmr commands is printed. 37 | * - A command to get and set the client's logging level, if the client provided 38 | * access to its logging level variable (as part of registration). For 39 | * example, the console user could enter "tmr log" to get the current log 40 | * level, or "tmr log debug" or "tmr log off" to set the log level. 41 | * - A command to get or clear the client's performance measurements (typically 42 | * counters), if the client provided access to these measurements (as part of 43 | * registration). For example, the console user could enter "ttys pm" to get 44 | * the current performance measurement values, or "ttys pm clear" to clear 45 | * them. 46 | * 47 | * The cmd module provides a global "help" command to list the commands of all 48 | * clients. The token "?" can be used in place of help. 49 | * 50 | * The cmd module provides "wild card" commands which are executed for all 51 | * clients. In this case, the first token in the command line is "*" rather than 52 | * a client name. The following wild card commands are supported: 53 | * 54 | * > * log 55 | * > * log 56 | * 57 | * MIT License 58 | * 59 | * Copyright (c) 2021 Eugene R Schroeder 60 | * 61 | * Permission is hereby granted, free of charge, to any person obtaining a copy 62 | * of this software and associated documentation files (the "Software"), to deal 63 | * in the Software without restriction, including without limitation the rights 64 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 65 | * copies of the Software, and to permit persons to whom the Software is 66 | * furnished to do so, subject to the following conditions: 67 | * 68 | * The above copyright notice and this permission notice shall be included in 69 | * all copies or substantial portions of the Software. 70 | * 71 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 72 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 73 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 74 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 75 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 76 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 77 | * SOFTWARE. 78 | */ 79 | 80 | #include 81 | #include 82 | #include 83 | #include 84 | #include 85 | 86 | #include "cmd.h" 87 | #include "log.h" 88 | #include "module.h" 89 | 90 | //////////////////////////////////////////////////////////////////////////////// 91 | // Common macros 92 | //////////////////////////////////////////////////////////////////////////////// 93 | 94 | #define MAX_CMD_TOKENS 10 95 | 96 | //////////////////////////////////////////////////////////////////////////////// 97 | // Type definitions 98 | //////////////////////////////////////////////////////////////////////////////// 99 | 100 | //////////////////////////////////////////////////////////////////////////////// 101 | // Private (static) function declarations 102 | //////////////////////////////////////////////////////////////////////////////// 103 | 104 | static const char* log_level_str(int32_t level); 105 | static int32_t log_level_int(const char* level_name); 106 | 107 | //////////////////////////////////////////////////////////////////////////////// 108 | // Private (static) variables 109 | //////////////////////////////////////////////////////////////////////////////// 110 | 111 | // Client info. 112 | static const struct cmd_client_info* client_info[CMD_MAX_CLIENTS]; 113 | 114 | static int32_t log_level = LOG_DEFAULT; 115 | 116 | static const char* log_level_names[] = { 117 | LOG_LEVEL_NAMES_CSV 118 | }; 119 | 120 | //////////////////////////////////////////////////////////////////////////////// 121 | // Public (global) variables and externs 122 | //////////////////////////////////////////////////////////////////////////////// 123 | 124 | //////////////////////////////////////////////////////////////////////////////// 125 | // Public (global) functions 126 | //////////////////////////////////////////////////////////////////////////////// 127 | 128 | /* 129 | * @brief Initialize cmd instance. 130 | * 131 | * @param[in] cfg The cmd configuration. (FUTURE) 132 | * 133 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 134 | * 135 | * This function initializes the cmd singleton module. Generally, it should not 136 | * access other modules as they might not have been initialized yet. An 137 | * exception is the log module. 138 | */ 139 | int32_t cmd_init(struct cmd_cfg* cfg) 140 | { 141 | memset(client_info, 0, sizeof(client_info)); 142 | return 0; 143 | } 144 | 145 | /* 146 | * @brief Register a client. 147 | * 148 | * @param[in] _client_info The client's configuration (eg. command metadata) 149 | * 150 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 151 | * 152 | * @note This function keeps a copy of the _client_info pointer. 153 | */ 154 | int32_t cmd_register(const struct cmd_client_info* _client_info) 155 | { 156 | int32_t idx; 157 | 158 | for (idx = 0; idx < CMD_MAX_CLIENTS; idx++) { 159 | if (client_info[idx] == NULL || 160 | strcasecmp(client_info[idx]->name, _client_info->name) == 0) { 161 | client_info[idx] = _client_info; 162 | return 0; 163 | } 164 | } 165 | return MOD_ERR_RESOURCE; 166 | } 167 | 168 | /* 169 | * @brief Execute a command line. 170 | * 171 | * @param[in] bfr The command line. 172 | * 173 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 174 | * 175 | * This function parses the command line and then executes the command, 176 | * typically by running a command function handler for a client. 177 | */ 178 | int32_t cmd_execute(char* bfr) 179 | { 180 | int32_t num_tokens = 0; 181 | const char* tokens[MAX_CMD_TOKENS]; 182 | char* p = bfr; 183 | int32_t idx; 184 | int32_t idx2; 185 | const struct cmd_client_info* ci; 186 | const struct cmd_cmd_info* cci; 187 | 188 | // Tokenize the command line in-place. 189 | while (1) { 190 | // Find start of token. 191 | while (*p && isspace((unsigned char)*p)) 192 | p++; 193 | if (*p == '\0') { 194 | // Found end of line. 195 | break; 196 | } else { 197 | if (num_tokens >= MAX_CMD_TOKENS) { 198 | printf("Too many tokens\n"); 199 | return MOD_ERR_BAD_CMD; 200 | } 201 | // Record pointer to token and find its end. 202 | tokens[num_tokens++] = p; 203 | while (*p && !isspace((unsigned char)*p)) 204 | p++; 205 | if (*p) { 206 | // Terminate token. 207 | *p++ = '\0'; 208 | } else { 209 | // Found end of line. 210 | break; 211 | } 212 | } 213 | } 214 | 215 | // If there are no tokens, nothing to do. 216 | if (num_tokens == 0) 217 | return 0; 218 | 219 | // Handle wild card commands 220 | if (strcmp("*", tokens[0]) == 0) { 221 | if (num_tokens < 2) { 222 | printf("Wildcard missing command\n"); 223 | return MOD_ERR_BAD_CMD; 224 | } 225 | if (strcasecmp(tokens[1], "log") == 0) { 226 | int32_t log_level = 0; 227 | if (num_tokens == 3) { 228 | log_level = log_level_int(tokens[2]); 229 | if (log_level < 0) { 230 | printf("Invalid log level: %s\n", tokens[2]); 231 | return MOD_ERR_ARG; 232 | } 233 | } else if (num_tokens > 3) { 234 | printf("Invalid arguments\n"); 235 | return MOD_ERR_ARG; 236 | } 237 | for (idx = 0; 238 | idx < CMD_MAX_CLIENTS && client_info[idx] != NULL; 239 | idx++) { 240 | ci = client_info[idx]; 241 | if (ci->log_level_ptr != NULL) { 242 | if (num_tokens == 3) { 243 | *ci->log_level_ptr = log_level; 244 | } else { 245 | printf("Log level for %s = %s\n", ci->name, 246 | log_level_str(*ci->log_level_ptr)); 247 | } 248 | } 249 | } 250 | } 251 | return 0; 252 | } 253 | // Handle top-level help. 254 | if (strcasecmp("help", tokens[0]) == 0 || 255 | strcasecmp("?", tokens[0]) == 0) { 256 | for (idx = 0; 257 | idx < CMD_MAX_CLIENTS && client_info[idx] != NULL; 258 | idx++) { 259 | ci = client_info[idx]; 260 | if (ci->num_cmds == 0) 261 | continue; 262 | printf("%s (", ci->name); 263 | for (idx2 = 0; idx2 < ci->num_cmds; idx2++) { 264 | cci = &ci->cmds[idx2]; 265 | printf("%s%s", idx2 == 0 ? "" : ", ", cci->name); 266 | } 267 | // If client provided log level, include log command. 268 | if (ci->log_level_ptr) 269 | printf("%s%s", idx2 == 0 ? "" : ", ", "log"); 270 | 271 | // If client provided pm info, include pm command. 272 | if (ci->num_u16_pms > 0) 273 | printf("%s%s", idx2 == 0 ? "" : ", ", "pm"); 274 | 275 | printf(")\n"); 276 | } 277 | printf("\nLog levels are: %s\n", LOG_LEVEL_NAMES); 278 | return 0; 279 | } 280 | 281 | // Find and execute the command. 282 | for (idx = 0; 283 | idx < CMD_MAX_CLIENTS && client_info[idx] != NULL; 284 | idx++) { 285 | ci = client_info[idx]; 286 | if (strcasecmp(tokens[0], ci->name) != 0) 287 | continue; 288 | 289 | // If there is no command, create a dummy. 290 | if (num_tokens == 1) 291 | tokens[1] = ""; 292 | 293 | // Handle help command directly. 294 | if (strcasecmp(tokens[1], "help") == 0 || 295 | strcasecmp(tokens[1], "?") == 0) { 296 | log_debug("Handle client help\n"); 297 | for (idx2 = 0; idx2 < ci->num_cmds; idx2++) { 298 | cci = &ci->cmds[idx2]; 299 | printf("%s %s: %s\n", ci->name, cci->name, cci->help); 300 | } 301 | 302 | // If client provided log level, print help for log command. 303 | if (ci->log_level_ptr) { 304 | printf("%s log: set or get log level, args: [level]\n", 305 | ci->name); 306 | } 307 | 308 | // If client provided pm info, print help for pm command. 309 | if (ci->num_u16_pms > 0) 310 | printf("%s pm: get or clear performance measurements, " 311 | "args: [clear]\n", ci->name); 312 | 313 | if (ci->log_level_ptr) 314 | printf("\nLog levels are: %s\n", LOG_LEVEL_NAMES); 315 | 316 | return 0; 317 | } 318 | 319 | // Handle log command directly. 320 | if (strcasecmp(tokens[1], "log") == 0) { 321 | log_debug("Handle command log\n"); 322 | if (ci->log_level_ptr) { 323 | if (num_tokens < 3) { 324 | printf("Log level for %s = %s\n", ci->name, 325 | log_level_str(*ci->log_level_ptr)); 326 | } else { 327 | int32_t log_level = log_level_int(tokens[2]); 328 | if (log_level < 0) { 329 | printf("Invalid log level: %s\n", tokens[2]); 330 | return MOD_ERR_ARG; 331 | } 332 | *ci->log_level_ptr = log_level; 333 | } 334 | } 335 | return 0; 336 | } 337 | 338 | // Handle pm command directly. 339 | if (strcasecmp(tokens[1], "pm") == 0) { 340 | bool clear = ((num_tokens >= 3) && 341 | (strcasecmp(tokens[2], "clear") == 0)); 342 | if (ci->num_u16_pms > 0) { 343 | if (clear) 344 | printf("Clearing performance measurements for %s\n", 345 | ci->name); 346 | else 347 | printf("%s:\n", ci->name); 348 | for (idx2 = 0; idx2 < ci->num_u16_pms; idx2++) { 349 | if (clear) 350 | ci->u16_pms[idx2] = 0; 351 | else 352 | printf(" %s: %d\n", ci->u16_pm_names[idx2], 353 | ci->u16_pms[idx2]); 354 | } 355 | } 356 | return 0; 357 | } 358 | 359 | // Find the command 360 | for (idx2 = 0; idx2 < ci->num_cmds; idx2++) { 361 | if (strcasecmp(tokens[1], ci->cmds[idx2].name) == 0) { 362 | log_debug("Handle command\n"); 363 | ci->cmds[idx2].func(num_tokens, tokens); 364 | return 0; 365 | } 366 | } 367 | printf("No such command (%s %s)\n", tokens[0], tokens[1]); 368 | return MOD_ERR_BAD_CMD; 369 | } 370 | printf("No such command (%s)\n", tokens[0]); 371 | return MOD_ERR_BAD_CMD; 372 | } 373 | 374 | /* 375 | * @brief Parse and validate command arguments 376 | * 377 | * @param[in] argc The number of arguments to be parsed. 378 | * @param[in] argv Pointer string array of arguments to be be parsed. 379 | * @param[in] fmt String indicating expected argument types 380 | * @param[out] arg_vals Pointer to array of parsed argument values 381 | * 382 | * @return Number of parsed arguments, negative value if error. 383 | * 384 | * @note In case of error, an error message is always printed to the 385 | * console. 386 | * 387 | * This function provides a common method to parse and validate command 388 | * arguments. In particular, it does string to numeric conversions, with error 389 | * checking. 390 | * 391 | * A format string is used to guide the parsing. The format string contains a 392 | * letter for each expected argument. The supported letters are: 393 | * - i Integer value, in either decimal, octal, or hex formats. Octal values 394 | * must start with 0, and hex values must start with 0x. 395 | * - u Unsigned value, in either decimal, octal, or hex formats. Octal values 396 | * must start with 0, and hex values must start with 0x. 397 | * - p Pointer, in hex format. No leading 0x is necessary (but allowed). 398 | * - s String 399 | * 400 | * In addition: 401 | * - [ indicates that remaining arguments are optional. However, 402 | * if one optional argument is present, then subsequent arguments 403 | * are required. 404 | * - ] is ignored; it can be put into the fmt string for readability, 405 | * to match brackets. 406 | * 407 | * Examples: 408 | * "up" - Requires exactly one unsigned and one pointer argument. 409 | * "ii" - Requires exactly two integer arguments. 410 | * "i[i" - Requires either one or two integer arguments. 411 | * "i[i]" - Same as above (matched brackets). 412 | * "i[i[i" - Requires either one, two, or three integer arguments. 413 | * "i[i[i]]" - Same as above (matched brackets). 414 | * "i[ii" - Requires either one or three integer arguments. 415 | * "i[ii]" - Same as above (matched brackets). 416 | * 417 | * @return On success, the number of arguments present (>=0), a "MOD_ERR" value 418 | * (<0). See code for details. 419 | */ 420 | int32_t cmd_parse_args(int32_t argc, const char** argv, const char* fmt, 421 | struct cmd_arg_val* arg_vals) 422 | { 423 | int32_t arg_cnt = 0; 424 | char* endptr; 425 | bool opt_args = false; 426 | 427 | while (*fmt) { 428 | if (*fmt == '[') { 429 | opt_args = true; 430 | fmt++; 431 | continue; 432 | } 433 | if (*fmt == ']') { 434 | fmt++; 435 | continue; 436 | } 437 | 438 | if (arg_cnt >= argc) { 439 | if (opt_args) { 440 | return arg_cnt; 441 | } 442 | printf("Insufficient arguments\n"); 443 | return MOD_ERR_BAD_CMD; 444 | } 445 | 446 | // These error conditions should not occur, but we check them for 447 | // safety. 448 | if (*argv == NULL || **argv == '\0') { 449 | printf("Invalid empty arguments\n"); 450 | return MOD_ERR_BAD_CMD; 451 | } 452 | 453 | switch (*fmt) { 454 | case 'i': 455 | arg_vals->val.i = strtol(*argv, &endptr, 0); 456 | if (*endptr) { 457 | printf("Argument '%s' not a valid integer\n", *argv); 458 | return MOD_ERR_ARG; 459 | } 460 | break; 461 | case 'u': 462 | arg_vals->val.u = strtoul(*argv, &endptr, 0); 463 | if (*endptr) { 464 | printf("Argument '%s' not a valid unsigned integer\n", *argv); 465 | return MOD_ERR_ARG; 466 | } 467 | break; 468 | case 'p': 469 | arg_vals->val.p = (void*)strtoul(*argv, &endptr, 16); 470 | if (*endptr) { 471 | printf("Argument '%s' not a valid pointer\n", *argv); 472 | return MOD_ERR_ARG; 473 | } 474 | break; 475 | case 's': 476 | arg_vals->val.s = *argv; 477 | break; 478 | default: 479 | printf("Bad argument format '%c'\n", *fmt); 480 | return MOD_ERR_ARG; 481 | } 482 | arg_vals->type = *fmt; 483 | arg_vals++; 484 | arg_cnt++; 485 | argv++; 486 | fmt++; 487 | opt_args = false; 488 | } 489 | if (arg_cnt < argc) { 490 | printf("Too many arguments\n"); 491 | return MOD_ERR_BAD_CMD; 492 | } 493 | return arg_cnt; 494 | } 495 | 496 | //////////////////////////////////////////////////////////////////////////////// 497 | // Private (static) functions 498 | //////////////////////////////////////////////////////////////////////////////// 499 | 500 | /* 501 | * @brief Convert integer log level to a string. 502 | * 503 | * @param[in] level The log level as an integer. 504 | * 505 | * @return Log level as a string (or "INVALID" on error) 506 | */ 507 | static const char* log_level_str(int32_t level) 508 | { 509 | if (level < ARRAY_SIZE(log_level_names)) 510 | return log_level_names[level]; 511 | return "INVALID"; 512 | } 513 | 514 | 515 | /* 516 | * @brief Convert log level string to an int. 517 | * 518 | * @param[in] level_name The log level as a string. 519 | * 520 | * @return Log level as an int, or -1 on error. 521 | */ 522 | static int32_t log_level_int(const char* level_name) 523 | { 524 | int32_t level; 525 | int32_t rc = -1; 526 | 527 | for (level = 0; level < ARRAY_SIZE(log_level_names); level++) { 528 | if (strcasecmp(level_name, log_level_names[level]) == 0) { 529 | rc = level; 530 | break; 531 | } 532 | } 533 | return rc; 534 | } 535 | -------------------------------------------------------------------------------- /modules/console/console.c: -------------------------------------------------------------------------------- 1 | /* 2 | * @brief Implementation of console module. 3 | * 4 | * This module supports a CLI console on a ttys. Upon receiving a command it 5 | * passes the command string to the cmd module for execution. 6 | * 7 | * This module provides simple line discipline functions: 8 | * - Echoing received characters. 9 | * - Handling backspace/delete. 10 | * 11 | * This module recognizes a special character to enable/disable logging output 12 | * (i.e. toggle). This is handy to temporarily stop logging output when running 13 | * commands. 14 | * 15 | * MIT License 16 | * 17 | * Copyright (c) 2021 Eugene R Schroeder 18 | * 19 | * Permission is hereby granted, free of charge, to any person obtaining a copy 20 | * of this software and associated documentation files (the "Software"), to deal 21 | * in the Software without restriction, including without limitation the rights 22 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | * copies of the Software, and to permit persons to whom the Software is 24 | * furnished to do so, subject to the following conditions: 25 | * 26 | * The above copyright notice and this permission notice shall be included in 27 | * all copies or substantial portions of the Software. 28 | * 29 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 35 | * SOFTWARE. 36 | */ 37 | 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | #include "cmd.h" 45 | #include "console.h" 46 | #include "log.h" 47 | #include "module.h" 48 | #include "ttys.h" 49 | 50 | //////////////////////////////////////////////////////////////////////////////// 51 | // Common macros 52 | //////////////////////////////////////////////////////////////////////////////// 53 | 54 | #define PROMPT "> " 55 | 56 | //////////////////////////////////////////////////////////////////////////////// 57 | // Type definitions 58 | //////////////////////////////////////////////////////////////////////////////// 59 | 60 | #define CONSOLE_CMD_BFR_SIZE 80 61 | 62 | struct console_state { 63 | struct console_cfg cfg; 64 | char cmd_bfr[CONSOLE_CMD_BFR_SIZE]; 65 | uint16_t num_cmd_bfr_chars; 66 | bool first_run_done; 67 | }; 68 | 69 | //////////////////////////////////////////////////////////////////////////////// 70 | // Private (static) function declarations 71 | //////////////////////////////////////////////////////////////////////////////// 72 | 73 | //////////////////////////////////////////////////////////////////////////////// 74 | // Private (static) variables 75 | //////////////////////////////////////////////////////////////////////////////// 76 | 77 | static struct console_state state; 78 | 79 | static int32_t log_level = LOG_DEFAULT; 80 | 81 | //////////////////////////////////////////////////////////////////////////////// 82 | // Public (global) variables and externs 83 | //////////////////////////////////////////////////////////////////////////////// 84 | 85 | //////////////////////////////////////////////////////////////////////////////// 86 | // Public (global) functions 87 | //////////////////////////////////////////////////////////////////////////////// 88 | 89 | /* 90 | * @brief Get default console configuration. 91 | * 92 | * @param[out] cfg The console configuration with defaults filled in. 93 | * 94 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 95 | */ 96 | int32_t console_get_def_cfg(struct console_cfg* cfg) 97 | { 98 | if (cfg == NULL) 99 | return MOD_ERR_ARG; 100 | 101 | memset(cfg, 0, sizeof(*cfg)); 102 | cfg->ttys_instance_id = TTYS_INSTANCE_UART2; 103 | return 0; 104 | } 105 | 106 | /* 107 | * @brief Initialize console module instance. 108 | * 109 | * @param[in] cfg The console configuration. (FUTURE) 110 | * 111 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 112 | * 113 | * This function initializes the console singleton module. Generally, it should 114 | * not access other modules as they might not have been initialized yet. An 115 | * exception is the log module. 116 | */ 117 | int32_t console_init(struct console_cfg* cfg) 118 | { 119 | if (cfg == NULL) { 120 | return MOD_ERR_ARG; 121 | } 122 | log_debug("In console_init()\n"); 123 | memset(&state, 0, sizeof(state)); 124 | state.cfg = *cfg; 125 | return 0; 126 | } 127 | 128 | /* 129 | * @brief Run console instance. 130 | * 131 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 132 | * 133 | * @note This function should not block. 134 | * 135 | * This function runs the console singleton module, during normal operation. 136 | */ 137 | int32_t console_run(void) 138 | { 139 | char c; 140 | if (!state.first_run_done) { 141 | state.first_run_done = true; 142 | printf("%s", PROMPT); 143 | } 144 | 145 | while (ttys_getc(state.cfg.ttys_instance_id, &c)) { 146 | 147 | // Handle processing completed command line. 148 | if (c == '\n' || c == '\r') { 149 | state.cmd_bfr[state.num_cmd_bfr_chars] = '\0'; 150 | printf("\n"); 151 | cmd_execute(state.cmd_bfr); 152 | state.num_cmd_bfr_chars = 0; 153 | printf("%s", PROMPT); 154 | continue; 155 | } 156 | 157 | // Handle backspace/delete. 158 | if (c == '\b' || c == '\x7f') { 159 | if (state.num_cmd_bfr_chars > 0) { 160 | // Overwrite last character with a blank. 161 | printf("\b \b"); 162 | state.num_cmd_bfr_chars--; 163 | } 164 | 165 | continue; 166 | } 167 | 168 | // Handle logging on/off toggle. 169 | if (c == LOG_TOGGLE_CHAR) { 170 | log_toggle_active(); 171 | printf("\n\n", log_is_active() ? "on" : "off"); 172 | continue; 173 | } 174 | 175 | // Echo the character back. 176 | if (isprint(c)) { 177 | if (state.num_cmd_bfr_chars < (CONSOLE_CMD_BFR_SIZE-1)) { 178 | state.cmd_bfr[state.num_cmd_bfr_chars++] = c; 179 | printf("%c", c); 180 | } else { 181 | // No space in buffer for the character, so ring the bell. 182 | printf("\a"); 183 | } 184 | continue; 185 | } 186 | 187 | } 188 | return 0; 189 | } 190 | 191 | //////////////////////////////////////////////////////////////////////////////// 192 | // Private (static) functions 193 | //////////////////////////////////////////////////////////////////////////////// 194 | 195 | -------------------------------------------------------------------------------- /modules/dio/dio.c: -------------------------------------------------------------------------------- 1 | /* 2 | * @brief Implementation of dio module. 3 | * 4 | * This module provides access to discrete inputs and outputs, sometimes 5 | * referred to as digital inputs and outputs. 6 | * 7 | * During configuration, the user must specify the set of inputs and outputs and 8 | * their characteristics. 9 | * 10 | * The following console commands are provided: 11 | * > dio status 12 | * > dio get 13 | * > dio set 14 | * See code for details. 15 | * 16 | * Currently, defintions from the STMicroelectronics Low Level (LL) device 17 | * library are used for some configuration paramters. A future enhancment would 18 | * be to define all configuration parameters in this module, so that user code 19 | * is more portable. 20 | * 21 | * MIT License 22 | * 23 | * Copyright (c) 2021 Eugene R Schroed3er 24 | * 25 | * Permission is hereby granted, free of charge, to any person obtaining a copy 26 | * of this software and associated documentation files (the "Software"), to deal 27 | * in the Software without restriction, including without limitation the rights 28 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 29 | * copies of the Software, and to permit persons to whom the Software is 30 | * furnished to do so, subject to the following conditions: 31 | * 32 | * The above copyright notice and this permission notice shall be included in 33 | * all copies or substantial portions of the Software. 34 | * 35 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 36 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 37 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 38 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 39 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 40 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 41 | * SOFTWARE. 42 | */ 43 | 44 | #include 45 | #include 46 | #include 47 | #include 48 | 49 | #include "cmd.h" 50 | #include "dio.h" 51 | #include "log.h" 52 | #include "module.h" 53 | 54 | //////////////////////////////////////////////////////////////////////////////// 55 | // Common macros 56 | //////////////////////////////////////////////////////////////////////////////// 57 | 58 | //////////////////////////////////////////////////////////////////////////////// 59 | // Type definitions 60 | //////////////////////////////////////////////////////////////////////////////// 61 | 62 | //////////////////////////////////////////////////////////////////////////////// 63 | // Private (static) function declarations 64 | //////////////////////////////////////////////////////////////////////////////// 65 | 66 | static int32_t cmd_dio_status(int32_t argc, const char** argv); 67 | static int32_t cmd_dio_get(int32_t argc, const char** argv); 68 | static int32_t cmd_dio_set(int32_t argc, const char** argv); 69 | 70 | //////////////////////////////////////////////////////////////////////////////// 71 | // Private (static) variables 72 | //////////////////////////////////////////////////////////////////////////////// 73 | 74 | static struct dio_cfg* cfg; 75 | 76 | static struct cmd_cmd_info cmds[] = { 77 | { 78 | .name = "status", 79 | .func = cmd_dio_status, 80 | .help = "Get module status, usage: dio status", 81 | }, 82 | { 83 | .name = "get", 84 | .func = cmd_dio_get, 85 | .help = "Get input value, usage: dio get ", 86 | }, 87 | { 88 | .name = "set", 89 | .func = cmd_dio_set, 90 | .help = "Set output value, usage: dio set {0|1}", 91 | }, 92 | }; 93 | 94 | static int32_t log_level = LOG_DEFAULT; 95 | 96 | static struct cmd_client_info cmd_info = { 97 | .name = "dio", 98 | .num_cmds = ARRAY_SIZE(cmds), 99 | .cmds = cmds, 100 | .log_level_ptr = &log_level, 101 | }; 102 | 103 | //////////////////////////////////////////////////////////////////////////////// 104 | // Public (global) variables and externs 105 | //////////////////////////////////////////////////////////////////////////////// 106 | 107 | //////////////////////////////////////////////////////////////////////////////// 108 | // Public (global) functions 109 | //////////////////////////////////////////////////////////////////////////////// 110 | 111 | /* 112 | * @brief Initialize dio module instance. 113 | * 114 | * @param[in] cfg The dio configuration. 115 | * 116 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 117 | * 118 | * This function initializes the dio singleton module. Generally, it should not 119 | * access other modules as they might not have been initialized yet. An 120 | * exception is the log module. 121 | */ 122 | int32_t dio_init(struct dio_cfg* _cfg) 123 | { 124 | uint32_t idx; 125 | const struct dio_in_info* dii; 126 | const struct dio_out_info* doi; 127 | 128 | cfg = _cfg; 129 | 130 | for (idx = 0; idx < cfg->num_inputs; idx++) { 131 | dii = &cfg->inputs[idx]; 132 | LL_GPIO_SetPinPull(dii->port, dii->pin, dii->pull); 133 | LL_GPIO_SetPinMode(dii->port, dii->pin, LL_GPIO_MODE_INPUT); 134 | } 135 | for (idx = 0; idx < cfg->num_outputs; idx++) { 136 | doi = &cfg->outputs[idx]; 137 | LL_GPIO_SetPinSpeed(doi->port, doi->pin, doi->speed); 138 | LL_GPIO_SetPinOutputType(doi->port, doi->pin, doi->output_type); 139 | LL_GPIO_SetPinPull(doi->port, doi->pin, doi->pull); 140 | LL_GPIO_SetPinMode(doi->port, doi->pin, LL_GPIO_MODE_OUTPUT); 141 | } 142 | return 0; 143 | } 144 | 145 | /* 146 | * @brief Start dio module instance. 147 | * 148 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 149 | * 150 | * This function starts the dio singleton module, to enter normal operation. 151 | */ 152 | int32_t dio_start(void) 153 | { 154 | int32_t result; 155 | 156 | result = cmd_register(&cmd_info); 157 | if (result < 0) { 158 | log_error("dio_start: cmd error %d\n", result); 159 | return MOD_ERR_RESOURCE; 160 | } 161 | return 0; 162 | } 163 | 164 | /* 165 | * @brief Get value of discrete input. 166 | * 167 | * @param[in] din_idx Discrete input index per module configuration. 168 | * 169 | * @return Input state (0/1), else a "MOD_ERR" value (< 0). See code for 170 | * details. 171 | */ 172 | int32_t dio_get(uint32_t din_idx) 173 | { 174 | if (din_idx >= cfg->num_inputs) 175 | return MOD_ERR_ARG; 176 | return LL_GPIO_IsInputPinSet(cfg->inputs[din_idx].port, 177 | cfg->inputs[din_idx].pin) ^ 178 | cfg->inputs[din_idx].invert; 179 | } 180 | 181 | /* 182 | * @brief Get value of discrete output. 183 | * 184 | * @param[in] dout_idx Discrete output index per module configuration. 185 | * 186 | * @return Output state (0/1), else a "MOD_ERR" value (< 0). See code for 187 | * details. 188 | */ 189 | int32_t dio_get_out(uint32_t dout_idx) 190 | { 191 | if (dout_idx >= cfg->num_outputs) 192 | return MOD_ERR_ARG; 193 | 194 | return LL_GPIO_IsOutputPinSet(cfg->outputs[dout_idx].port, 195 | cfg->outputs[dout_idx].pin) ^ 196 | cfg->outputs[dout_idx].invert; 197 | } 198 | 199 | /* 200 | * @brief Set value of discrete output. 201 | * 202 | * @param[in] dout_idx Discrete output index per module configuration. 203 | * @param[in] value Output value 0/1. 204 | * 205 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 206 | */ 207 | int32_t dio_set(uint32_t dout_idx, uint32_t value) 208 | { 209 | if (dout_idx >= cfg->num_outputs) 210 | return MOD_ERR_ARG; 211 | if (value ^ cfg->outputs[dout_idx].invert) { 212 | LL_GPIO_SetOutputPin(cfg->outputs[dout_idx].port, 213 | cfg->outputs[dout_idx].pin); 214 | } else { 215 | LL_GPIO_ResetOutputPin(cfg->outputs[dout_idx].port, 216 | cfg->outputs[dout_idx].pin); 217 | } 218 | return 0; 219 | } 220 | 221 | /* 222 | * @brief Get number of discrete inputs. 223 | * 224 | * @return Return number of inputs (non-negative) for success, else a "MOD_ERR" 225 | * value. See code for details. 226 | */ 227 | int32_t dio_get_num_in(void) 228 | { 229 | return cfg == NULL ? MOD_ERR_RESOURCE : cfg->num_inputs; 230 | } 231 | 232 | /* 233 | * @brief Get number of discrete output. 234 | * 235 | * @return Return number of outputs (non-negative) for success, else a "MOD_ERR" 236 | * value. See code for details. 237 | */ 238 | int32_t dio_get_num_out(void) 239 | { 240 | return cfg == NULL ? MOD_ERR_RESOURCE : cfg->num_outputs; 241 | } 242 | 243 | //////////////////////////////////////////////////////////////////////////////// 244 | // Private (static) functions 245 | //////////////////////////////////////////////////////////////////////////////// 246 | 247 | /* 248 | * @brief Console command function for "dio status". 249 | * 250 | * @param[in] argc Number of arguments, including "dio". 251 | * @param[in] argv Argument values, including "dio". 252 | * 253 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 254 | * 255 | * Command usage: dio status 256 | */ 257 | static int32_t cmd_dio_status(int32_t argc, const char** argv) 258 | { 259 | uint32_t idx; 260 | 261 | printf("Inputs:\n"); 262 | for (idx = 0; idx < cfg->num_inputs; idx++) 263 | printf(" %2lu: %s = %ld\n", idx, cfg->inputs[idx].name, dio_get(idx)); 264 | 265 | 266 | printf("Outputs:\n"); 267 | for (idx = 0; idx < cfg->num_outputs; idx++) 268 | printf(" %2lu: %s = %ld\n", idx, cfg->outputs[idx].name, 269 | dio_get_out(idx)); 270 | 271 | return 0; 272 | } 273 | 274 | /* 275 | * @brief Console command function for "dio get". 276 | * 277 | * @param[in] argc Number of arguments, including "dio". 278 | * @param[in] argv Argument values, including "dio". 279 | * 280 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 281 | * 282 | * Command usage: dio get 283 | */ 284 | static int32_t cmd_dio_get(int32_t argc, const char** argv) 285 | { 286 | uint32_t idx; 287 | struct cmd_arg_val arg_vals[1]; 288 | 289 | if (cmd_parse_args(argc-2, argv+2, "s", arg_vals) != 1) 290 | return MOD_ERR_BAD_CMD; 291 | 292 | for (idx = 0; idx < cfg->num_inputs; idx++) 293 | if (strcasecmp(arg_vals[0].val.s, cfg->inputs[idx].name) == 0) 294 | break; 295 | if (idx < cfg->num_inputs) { 296 | printf("%s = %ld\n", cfg->inputs[idx].name, dio_get(idx)); 297 | return 0; 298 | } 299 | 300 | for (idx = 0; idx < cfg->num_outputs; idx++) 301 | if (strcasecmp(arg_vals[0].val.s, cfg->outputs[idx].name) == 0) 302 | break; 303 | if (idx < cfg->num_outputs) { 304 | printf("%s %ld\n", cfg->outputs[idx].name, dio_get_out(idx)); 305 | return 0; 306 | } 307 | printf("Invalid dio input/output name '%s'\n", arg_vals[0].val.s); 308 | return MOD_ERR_ARG; 309 | } 310 | 311 | /* 312 | * @brief Console command function for "dio set". 313 | * 314 | * @param[in] argc Number of arguments, including "dio". 315 | * @param[in] argv Argument values, including "dio". 316 | * 317 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 318 | * 319 | * Command usage: dio set {0|1} 320 | */ 321 | static int32_t cmd_dio_set(int32_t argc, const char** argv) 322 | { 323 | uint32_t idx; 324 | struct cmd_arg_val arg_vals[2]; 325 | uint32_t value; 326 | 327 | if (cmd_parse_args(argc-2, argv+2, "su", arg_vals) != 2) 328 | return MOD_ERR_BAD_CMD; 329 | 330 | for (idx = 0; idx < cfg->num_outputs; idx++) 331 | if (strcasecmp(arg_vals[0].val.s, cfg->outputs[idx].name) == 0) 332 | break; 333 | if (idx >= cfg->num_outputs) { 334 | printf("Invalid dio name '%s'\n", arg_vals[0].val.s); 335 | return MOD_ERR_ARG; 336 | } 337 | 338 | value = arg_vals[1].val.u; 339 | if (value != 0 && value != 1) { 340 | printf("Invalid value '%s'\n", argv[3]); 341 | return MOD_ERR_ARG; 342 | } 343 | return dio_set(idx, value); 344 | } 345 | -------------------------------------------------------------------------------- /modules/gps_gtu7/gps_gtu7.c: -------------------------------------------------------------------------------- 1 | /* 2 | * @brief Implementation of gps_gtu7 module. 3 | * 4 | * This module provides a very basic interface for playing with a GPS GTU7 5 | * hardware module. This hardware module contains a GPS receiver with an ASCII 6 | * serial interface, and a pulse-per-second (PPS) discrete output. 7 | * 8 | * This module provides: 9 | * - Parsing of the GGPS GTU7 output messages that contain satellite positions. 10 | * - Map satellite positions to a 2-D grid, and plot it to the console, using 11 | * ANSI escape sequences so the satellite map stays at a fixed position. 12 | * 13 | * The following console commands are provided: 14 | * > gps status 15 | * > gps map 16 | * See code for details. 17 | * 18 | * MIT License 19 | * 20 | * Copyright (c) 2021 Eugene R Schroeder 21 | * 22 | * Permission is hereby granted, free of charge, to any person obtaining a copy 23 | * of this software and associated documentation files (the "Software"), to deal 24 | * in the Software without restriction, including without limitation the rights 25 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 26 | * copies of the Software, and to permit persons to whom the Software is 27 | * furnished to do so, subject to the following conditions: 28 | * 29 | * The above copyright notice and this permission notice shall be included in 30 | * all copies or substantial portions of the Software. 31 | * 32 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 33 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 34 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 35 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 36 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 37 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 38 | * SOFTWARE. 39 | */ 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | 46 | #include "cmd.h" 47 | #include "gps_gtu7.h" 48 | #include "log.h" 49 | #include "module.h" 50 | #include "tmr.h" 51 | 52 | //////////////////////////////////////////////////////////////////////////////// 53 | // Common macros 54 | //////////////////////////////////////////////////////////////////////////////// 55 | 56 | #define GPS_IN_BFR_SIZE 80 57 | #define MAX_SATS 32 58 | #define CLEANUP_TMR_MS 5000 59 | 60 | //////////////////////////////////////////////////////////////////////////////// 61 | // Type definitions 62 | //////////////////////////////////////////////////////////////////////////////// 63 | 64 | struct sat_data { 65 | uint32_t last_update_ms; // Used to detect disappering satellites. 66 | uint16_t azimuth; // 0-359 degress 67 | uint8_t present; // 1 if satellite being reported. 68 | uint8_t elevation; // 0-90 maximum 69 | uint8_t snr; // 0-99 dB 70 | }; 71 | 72 | struct gps_state { 73 | enum ttys_instance_id ttys_instance_id; 74 | char in_bfr[GPS_IN_BFR_SIZE]; 75 | uint16_t in_bfr_chars; 76 | struct sat_data sat_data[MAX_SATS]; 77 | bool disp_map_on; 78 | bool disp_map_clear_screen; 79 | bool disp_map_update; 80 | bool disp_map_clear_history; 81 | int32_t cleanup_tmr_id; 82 | }; 83 | 84 | //////////////////////////////////////////////////////////////////////////////// 85 | // Private (static) function declarations 86 | //////////////////////////////////////////////////////////////////////////////// 87 | 88 | static int32_t cmd_gps_status(int32_t argc, const char** argv); 89 | static int32_t cmd_gps_map(int32_t argc, const char** argv); 90 | 91 | static void process_msg(char* msg); 92 | static char sat_idx_to_char(int32_t sat_idx); 93 | static char* csv_get_token(char* start, char** next_start); 94 | static void display_map(void); 95 | static enum tmr_cb_action cleanup_tmr_cb(int32_t tmr_id, uint32_t user_data); 96 | 97 | //////////////////////////////////////////////////////////////////////////////// 98 | // Private (static) variables 99 | //////////////////////////////////////////////////////////////////////////////// 100 | 101 | static struct gps_state gps_state; 102 | 103 | static struct cmd_cmd_info cmds[] = { 104 | { 105 | .name = "status", 106 | .func = cmd_gps_status, 107 | .help = "Get module status, usage: gps status", 108 | }, 109 | { 110 | .name = "map", 111 | .func = cmd_gps_map, 112 | .help = "Map display on/off/clear, usage: gps map {on|off|clear}", 113 | } 114 | }; 115 | 116 | static int32_t log_level = LOG_DEFAULT; 117 | 118 | static struct cmd_client_info cmd_info = { 119 | .name = "gps", 120 | .num_cmds = ARRAY_SIZE(cmds), 121 | .cmds = cmds, 122 | .log_level_ptr = &log_level, 123 | }; 124 | 125 | //////////////////////////////////////////////////////////////////////////////// 126 | // Public (global) variables and externs 127 | //////////////////////////////////////////////////////////////////////////////// 128 | 129 | //////////////////////////////////////////////////////////////////////////////// 130 | // Public (global) functions 131 | //////////////////////////////////////////////////////////////////////////////// 132 | 133 | /* 134 | * @brief Get default gps configuration. 135 | * 136 | * @param[out] cfg The gps configuration with defaults filled in. 137 | * 138 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 139 | */ 140 | int32_t gps_get_def_cfg(struct gps_cfg* cfg) 141 | { 142 | if (cfg == NULL) 143 | return MOD_ERR_ARG; 144 | 145 | memset(cfg, 0, sizeof(*cfg)); 146 | cfg->ttys_instance_id = TTYS_INSTANCE_UART6; 147 | return 0; 148 | } 149 | 150 | /* 151 | * @brief Initialize gps module instance. 152 | * 153 | * @param[in] cfg The gps module configuration. 154 | * 155 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 156 | * 157 | * This function initializes a gps singleton module. Generally, it should not 158 | * access other modules as they might not have been initialized yet. 159 | */ 160 | int32_t gps_init(struct gps_cfg* cfg) 161 | { 162 | if (cfg == NULL) { 163 | return MOD_ERR_ARG; 164 | } 165 | memset(&gps_state, 0, sizeof(gps_state)); 166 | gps_state.ttys_instance_id = cfg->ttys_instance_id; 167 | gps_state.disp_map_clear_history = true; 168 | return 0; 169 | } 170 | 171 | /* 172 | * @brief Start gps module instance. 173 | * 174 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 175 | * 176 | * This function starts a gps module instance, to enter normal operation. 177 | */ 178 | int32_t gps_start(void) 179 | { 180 | int32_t result; 181 | 182 | cmd_register(&cmd_info); 183 | result = cmd_register(&cmd_info); 184 | if (result < 0) { 185 | log_error("gps_start: cmd error %d\n", result); 186 | return MOD_ERR_RESOURCE; 187 | } 188 | gps_state.cleanup_tmr_id = tmr_inst_get_cb(CLEANUP_TMR_MS, 189 | cleanup_tmr_cb, 190 | 0); 191 | if (gps_state.cleanup_tmr_id < 0) { 192 | log_error("gps_start: tmr error %d\n", gps_state.cleanup_tmr_id); 193 | return MOD_ERR_RESOURCE; 194 | } 195 | 196 | return 0; 197 | } 198 | 199 | /* 200 | * @brief Run gps module instance. 201 | * 202 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 203 | * 204 | * @note This function should not block. 205 | */ 206 | int32_t gps_run(void) 207 | { 208 | char c; 209 | while (ttys_getc(gps_state.ttys_instance_id, &c)) { 210 | if (c == '\n' || c == '\r') { 211 | if (gps_state.in_bfr_chars > 0) { 212 | gps_state.in_bfr[gps_state.in_bfr_chars] = '\0'; 213 | process_msg(gps_state.in_bfr); 214 | gps_state.in_bfr_chars = 0; 215 | } 216 | continue; 217 | } 218 | if (isprint(c)) { 219 | if (gps_state.in_bfr_chars < (GPS_IN_BFR_SIZE-1)) { 220 | gps_state.in_bfr[gps_state.in_bfr_chars++] = c; 221 | } else { 222 | gps_state.in_bfr[GPS_IN_BFR_SIZE-1] = '\0'; 223 | log_debug("Truncated: %s\n", gps_state.in_bfr); 224 | gps_state.in_bfr_chars = 0; 225 | } 226 | continue; 227 | } 228 | } 229 | if (gps_state.disp_map_on && gps_state.disp_map_update) { 230 | display_map(); 231 | gps_state.disp_map_update = false; 232 | } 233 | return 0; 234 | } 235 | 236 | //////////////////////////////////////////////////////////////////////////////// 237 | // Private (static) functions 238 | //////////////////////////////////////////////////////////////////////////////// 239 | 240 | /* 241 | * @brief Console command function for "gps status". 242 | * 243 | * @param[in] argc Number of arguments, including "gps" 244 | * @param[in] argv Argument values, including "gps" 245 | * 246 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 247 | * 248 | * Command usage: gps status 249 | */ 250 | static int32_t cmd_gps_status(int32_t argc, const char** argv) 251 | { 252 | int32_t idx; 253 | 254 | printf("Reported satellites:\n"); 255 | for (idx = 0; idx < MAX_SATS; idx++) { 256 | struct sat_data* sat_data = &gps_state.sat_data[idx]; 257 | if (sat_data->present) { 258 | printf(" %c: azimuth=%3d deg elevation=%2d deg snr=%2d dB " 259 | "data-age=%lu ms\n", 260 | sat_idx_to_char(idx), 261 | sat_data->azimuth, 262 | sat_data->elevation, 263 | sat_data->snr, 264 | tmr_get_ms() - sat_data->last_update_ms); 265 | } 266 | } 267 | printf("gps map: %s\n", gps_state.disp_map_on ? "on" : "off"); 268 | return 0; 269 | } 270 | 271 | /* 272 | * @brief Console command function for "gps map". 273 | * 274 | * @param[in] argc Number of arguments, including "gps" 275 | * @param[in] argv Argument values, including "gps" 276 | * 277 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 278 | * 279 | * Command usage: gps map {on|off|clear} 280 | */ 281 | static int32_t cmd_gps_map(int32_t argc, const char** argv) 282 | { 283 | struct cmd_arg_val arg_vals[1]; 284 | const char* op; 285 | 286 | if (cmd_parse_args(argc-2, argv+2, "s", arg_vals) != 1) 287 | return MOD_ERR_BAD_CMD; 288 | op = arg_vals[0].val.s; 289 | if (strcasecmp(op, "on") == 0) { 290 | gps_state.disp_map_on = true; 291 | gps_state.disp_map_clear_screen = true; 292 | } else if (strcasecmp(op, "off") == 0) { 293 | gps_state.disp_map_on = false; 294 | } else if (strcasecmp(op, "clear") == 0) { 295 | gps_state.disp_map_clear_history = false; 296 | } else { 297 | printf("Invalid operation '%s'\n", op); 298 | return MOD_ERR_ARG; 299 | } 300 | return 0; 301 | } 302 | 303 | /* 304 | * @brief Process a message received from the GPS hardware module. 305 | * 306 | * @param[in] msg GPS hardware module message. 307 | */ 308 | static void process_msg(char* msg) 309 | { 310 | enum parse_state { 311 | PARSE_STATE_START, 312 | PARSE_STATE_GPGSV, 313 | PARSE_STATE_IGNORE, 314 | }; 315 | 316 | char* next = msg; 317 | enum parse_state parse_state = PARSE_STATE_START; 318 | int32_t field_num = 0; 319 | int32_t satellite_num = -1; 320 | uint16_t msg_azimuth; 321 | uint8_t msg_elevation; 322 | uint8_t msg_snr; 323 | struct sat_data* sat_data; 324 | 325 | log_trace("Msg: %s\n", msg); 326 | while (1) { 327 | if (next == NULL) 328 | break; 329 | char* token = csv_get_token(next, &next); 330 | field_num++; 331 | switch (parse_state) { 332 | case PARSE_STATE_START: 333 | parse_state = strcasecmp(token, "$GPGSV") == 0 ? 334 | PARSE_STATE_GPGSV : PARSE_STATE_IGNORE; 335 | break; 336 | case PARSE_STATE_GPGSV: 337 | switch ((field_num - 5) % 4) { 338 | case 0: 339 | // Satellite PRN number 340 | satellite_num = atoi(token); 341 | if (satellite_num < 1 || satellite_num > 32) { 342 | log_debug("Unused satellite, number=%s\n", token); 343 | parse_state = PARSE_STATE_IGNORE; 344 | } else { 345 | // Make satellite number zero-based. 346 | satellite_num--; 347 | } 348 | break; 349 | case 1: 350 | // Elevation degress (0-90) 351 | msg_elevation = atoi(token); 352 | break; 353 | case 2: 354 | // Azimuth degress (000-359) 355 | msg_azimuth = atoi(token); 356 | break; 357 | case 3: 358 | // SNR (00-99) 359 | msg_snr = atoi(token); 360 | sat_data = &gps_state.sat_data[satellite_num]; 361 | if ((!sat_data->present) || 362 | (msg_elevation != sat_data->elevation) || 363 | (msg_azimuth != sat_data->azimuth)) { 364 | log_debug("Update sat %d ele=%d az=%d snr=%d\n", 365 | satellite_num+1, msg_elevation, 366 | msg_azimuth, msg_snr); 367 | sat_data->present = true; 368 | sat_data->elevation = msg_elevation; 369 | sat_data->azimuth = msg_azimuth; 370 | gps_state.disp_map_update = true; 371 | } 372 | sat_data->snr = msg_snr; 373 | sat_data->last_update_ms = tmr_get_ms(); 374 | break; 375 | } 376 | break; 377 | case PARSE_STATE_IGNORE: 378 | continue; 379 | } 380 | if (next == NULL) 381 | break; 382 | } 383 | } 384 | 385 | /* 386 | * @brief Convert statellite index of the display char. 387 | * 388 | * @param[in] sat_idx Satellite index (zero-based). 389 | * 390 | * @return The satellite dislay char. 391 | */ 392 | static char sat_idx_to_char(int32_t sat_idx) 393 | { 394 | if (sat_idx < 9) 395 | return '1' + sat_idx; 396 | return 'A' + (sat_idx - 9); 397 | } 398 | 399 | /* 400 | * @brief Tokenize a CSV string in-place, incrementally. 401 | * 402 | * @param[in] start The current point in the string, typically pointing to the 403 | * next token. 404 | * @param[out] next_start The start of the next token. This should be passed in 405 | * as "start" in the next call. If there are no more tokens, it will 406 | * be set to NULL. 407 | * 408 | * @return The next token to process. The token might have zero length (e.g. 409 | * in the case of consecutive commas). 410 | * 411 | * @note The char '*' is also treated as a separator (i.e. like a ','). 412 | */ 413 | static char* csv_get_token(char* start, char** next_start) 414 | { 415 | char* p = start; 416 | while (*p && *p != ',' && *p != '*') p++; 417 | if (*p == ',' || *p == '*') { 418 | *p++ = '\0'; 419 | *next_start = p; 420 | } else { 421 | *next_start = NULL; 422 | } 423 | return start; 424 | } 425 | 426 | /* 427 | * @brief Display the satellite positions as a map. 428 | */ 429 | static void display_map(void) 430 | { 431 | #define DISP_MAX_RAD 10 432 | #define DISP_ROWS (1 + 2*DISP_MAX_RAD) 433 | #define DISP_COLS (1 + 2*DISP_MAX_RAD) 434 | #define DISP_X_OFFSET DISP_MAX_RAD 435 | #define DISP_Y_OFFSET DISP_MAX_RAD 436 | #define DEG_TO_RAD(d) ((double)(d) * M_PI / 180.0) 437 | 438 | static char map[DISP_COLS][DISP_ROWS]; 439 | int32_t idx; 440 | int32_t idy; 441 | 442 | if (gps_state.disp_map_clear_history) { 443 | memset(map, '.', sizeof(map)); 444 | gps_state.disp_map_clear_history = false; 445 | } 446 | for (idx = 0; idx < MAX_SATS; idx++) { 447 | struct sat_data* sat_data = &gps_state.sat_data[idx]; 448 | if (sat_data->present && sat_data->elevation <= 90) { 449 | double r = cos(DEG_TO_RAD(sat_data->elevation)) * (double)(DISP_MAX_RAD); 450 | double theta = DEG_TO_RAD(90 - sat_data->azimuth); 451 | int32_t x = (int32_t)round(cos(theta) * r) + DISP_X_OFFSET; 452 | int32_t y = (int32_t)round(sin(theta) * r) + DISP_Y_OFFSET; 453 | x = CLAMP(x, 0, DISP_COLS-1); 454 | y = CLAMP(y, 0, DISP_ROWS-1); 455 | map[x][y] = sat_idx_to_char(idx); 456 | log_debug("%c %c az=%3d el=%3d r*1000=%5d x=%3d y=%3d\n", 457 | sat_idx_to_char(idx), 458 | map[x][y], 459 | sat_data->azimuth, 460 | sat_data->elevation, 461 | (int32_t)round(r*1000.0), 462 | x, 463 | y); 464 | } 465 | } 466 | 467 | printf("\x1B[?25l"); 468 | if (gps_state.disp_map_clear_screen) { 469 | gps_state.disp_map_clear_screen = false; 470 | printf("\x1B[2J"); 471 | } 472 | printf("\x1B[1;1H"); 473 | for (idy = DISP_ROWS-1; idy >= 0; idy--) { 474 | for (idx = 0; idx < DISP_COLS; idx++) 475 | printf("%c ", map[idx][idy]); 476 | printf("\n"); 477 | } 478 | printf("\x1B[?25h"); 479 | } 480 | 481 | /* 482 | * @brief Timer callback to remove satellites that have disappeared. 483 | * 484 | * @param[in] tmr_id The timer ID (not used). 485 | * @param[in] user_data User data for the timer (not used). 486 | * 487 | * If a satellite has not been reported for some amount of time, it is removed. 488 | */ 489 | static enum tmr_cb_action cleanup_tmr_cb(int32_t tmr_id, uint32_t user_data) 490 | { 491 | uint32_t now_ms = tmr_get_ms(); 492 | uint32_t idx; 493 | 494 | log_debug("In cleanup_tmr_cb()\n"); 495 | for (idx = 0; idx < MAX_SATS; idx++) { 496 | struct sat_data* sat_data = &gps_state.sat_data[idx]; 497 | if (sat_data->present && 498 | ((now_ms - sat_data->last_update_ms) > 499 | CLEANUP_TMR_MS)) { 500 | log_debug("Clean up satellite %d\n", idx+1); 501 | sat_data->present = false; 502 | gps_state.disp_map_update = true; 503 | } 504 | } 505 | return TMR_CB_RESTART; 506 | } 507 | -------------------------------------------------------------------------------- /modules/include/blinky.h: -------------------------------------------------------------------------------- 1 | #ifndef _BLINKY_H_ 2 | #define _BLINKY_H_ 3 | 4 | /* 5 | * @brief Interface declaration of blinky module. 6 | * 7 | * See implementation file for information about this module. 8 | * 9 | * MIT License 10 | * 11 | * Copyright (c) 2021 Eugene R Schroeder 12 | * 13 | * Permission is hereby granted, free of charge, to any person obtaining a copy 14 | * of this software and associated documentation files (the "Software"), to deal 15 | * in the Software without restriction, including without limitation the rights 16 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | * copies of the Software, and to permit persons to whom the Software is 18 | * furnished to do so, subject to the following conditions: 19 | * 20 | * The above copyright notice and this permission notice shall be included in 21 | * all copies or substantial portions of the Software. 22 | * 23 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | * SOFTWARE. 30 | */ 31 | 32 | #include 33 | #include 34 | 35 | struct blinky_cfg 36 | { 37 | uint32_t dout_idx; 38 | uint32_t code_num_blinks; // N1. 39 | uint32_t code_period_ms; // T1. 40 | uint32_t sep_num_blinks; // N2. 41 | uint32_t sep_period_ms; // T2. 42 | 43 | }; 44 | 45 | // Core module interface functions. 46 | int32_t blinky_get_def_cfg(struct blinky_cfg* cfg); 47 | int32_t blinky_init(struct blinky_cfg* cfg); 48 | int32_t blinky_start(void;); 49 | 50 | // Other module-level APIs: 51 | int32_t blinky_set_code_blinks(uint32_t num_blinks); 52 | int32_t blinky_set_code_period(uint32_t period_ms); 53 | int32_t blinky_set_sep_blinks(uint32_t num_blinks); 54 | int32_t blinky_set_sep_period(uint32_t period_ms); 55 | 56 | #endif // _BLINKY_H_ 57 | -------------------------------------------------------------------------------- /modules/include/cmd.h: -------------------------------------------------------------------------------- 1 | #ifndef _CMD_H_ 2 | #define _CMD_H_ 3 | 4 | /* 5 | * @brief Interface declaration of cmd module. 6 | * 7 | * See implementation file for information about this module. 8 | * 9 | * MIT License 10 | * 11 | * Copyright (c) 2021 Eugene R Schroeder 12 | * 13 | * Permission is hereby granted, free of charge, to any person obtaining a copy 14 | * of this software and associated documentation files (the "Software"), to deal 15 | * in the Software without restriction, including without limitation the rights 16 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | * copies of the Software, and to permit persons to whom the Software is 18 | * furnished to do so, subject to the following conditions: 19 | * 20 | * The above copyright notice and this permission notice shall be included in 21 | * all copies or substantial portions of the Software. 22 | * 23 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | * SOFTWARE. 30 | */ 31 | 32 | #include 33 | 34 | // Number of clients supported. 35 | #define CMD_MAX_CLIENTS 10 36 | 37 | // Function signature for a command handler function. 38 | typedef int32_t (*cmd_func)(int32_t argc, const char** argv); 39 | 40 | // Information about a single command, provided by the client. 41 | struct cmd_cmd_info { 42 | const char* const name; // Name of command 43 | const cmd_func func; // Command function 44 | const char* const help; // Command help string 45 | }; 46 | 47 | // Information provided by the client. 48 | // - Command base name 49 | // - Command set info. 50 | // - Pointer to log level variable (optional). 51 | // - Performance measurement info (optional). 52 | 53 | struct cmd_client_info { 54 | const char* const name; // Client name (first command line token) 55 | const int32_t num_cmds; // Number of commands. 56 | const struct cmd_cmd_info* const cmds; // Pointer to array of command info 57 | int32_t* const log_level_ptr; // Pointer to log level variable (or NULL) 58 | const int32_t num_u16_pms; // Number of pm values. 59 | uint16_t* const u16_pms; // Pointer to array of pm values 60 | const char* const* const u16_pm_names; // Pointer to array of pm names 61 | }; 62 | 63 | struct cmd_arg_val { 64 | char type; 65 | union { 66 | void* p; 67 | uint8_t* p8; 68 | uint16_t* p16; 69 | uint32_t* p32; 70 | int32_t i; 71 | uint32_t u; 72 | const char* s; 73 | } val; 74 | }; 75 | 76 | struct cmd_cfg { 77 | // FUTURE 78 | }; 79 | 80 | // Core module interface functions. 81 | int32_t cmd_init(struct cmd_cfg* cfg); 82 | 83 | // Other APIs. 84 | // Note: cmd_register() keeps a copy of the client_info pointer. 85 | int32_t cmd_register(const struct cmd_client_info* client_info); 86 | int32_t cmd_execute(char* bfr); 87 | int32_t cmd_parse_args(int32_t argc, const char** argv, const char* fmt, 88 | struct cmd_arg_val* arg_vals); 89 | 90 | #endif // _CMD_H_ 91 | -------------------------------------------------------------------------------- /modules/include/console.h: -------------------------------------------------------------------------------- 1 | #ifndef _CONSOLE_H_ 2 | #define _CONSOLE_H_ 3 | 4 | /* 5 | * @brief Interface declaration of console module. 6 | * 7 | * See implementation file for information about this module. 8 | * 9 | * MIT License 10 | * 11 | * Copyright (c) 2021 Eugene R Schroeder 12 | * 13 | * Permission is hereby granted, free of charge, to any person obtaining a copy 14 | * of this software and associated documentation files (the "Software"), to deal 15 | * in the Software without restriction, including without limitation the rights 16 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | * copies of the Software, and to permit persons to whom the Software is 18 | * furnished to do so, subject to the following conditions: 19 | * 20 | * The above copyright notice and this permission notice shall be included in 21 | * all copies or substantial portions of the Software. 22 | * 23 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | * SOFTWARE. 30 | */ 31 | 32 | #include 33 | 34 | #include "ttys.h" 35 | 36 | struct console_cfg 37 | { 38 | enum ttys_instance_id ttys_instance_id; 39 | }; 40 | 41 | // Core module interface functions. 42 | int32_t console_get_def_cfg(struct console_cfg* cfg); 43 | int32_t console_init(struct console_cfg* cfg);; 44 | int32_t console_run(void); 45 | 46 | // Other APIs. 47 | 48 | #endif // _CONSOLE_H_ 49 | -------------------------------------------------------------------------------- /modules/include/dio.h: -------------------------------------------------------------------------------- 1 | #ifndef _DIO_H_ 2 | #define _DIO_H_ 3 | 4 | /* 5 | * @brief Interface declaration of dio module. 6 | * 7 | * See implementation file for information about this module. 8 | * 9 | * MIT License 10 | * 11 | * Copyright (c) 2021 Eugene R Schroed3er 12 | * 13 | * Permission is hereby granted, free of charge, to any person obtaining a copy 14 | * of this software and associated documentation files (the "Software"), to deal 15 | * in the Software without restriction, including without limitation the rights 16 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | * copies of the Software, and to permit persons to whom the Software is 18 | * furnished to do so, subject to the following conditions: 19 | * 20 | * The above copyright notice and this permission notice shall be included in 21 | * all copies or substantial portions of the Software. 22 | * 23 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | * SOFTWARE. 30 | */ 31 | 32 | #include 33 | 34 | #include "stm32f4xx_ll_gpio.h" 35 | 36 | /* 37 | * Guide to defining dio inputs and outputs. 38 | * - An array of dio_in_info and dig_out_info structures is created for inputs 39 | * and outputs. 40 | * - Pointers to these arrays are passed to the dio module, and the dio module 41 | * stores the pointers, and accesses the arrays during normal operation. 42 | * 43 | * Fields common for inputs and outputs: 44 | * - name : A readable name for the input/output. 45 | * - port : One of DIO_PORT_A, DIO_PORT_B, ... 46 | * - pin : One of: 47 | * + DIO_PIN_0 48 | * + DIO_PIN_1 49 | * : 50 | * + DIO_PIN_15 51 | * - pull : One of: 52 | * + DIO_PULL_NO 53 | * + DIO_PULL_UP 54 | * + DIO_PULL_DOWN 55 | * - invert : True to invert the signal value. 56 | * 57 | * Fields for outputs only: 58 | * - init_value : 0 or 1 59 | * - speed : One of: 60 | * + DIO_SPEED_FREQ_LOW 61 | * + DIO_SPEED_FREQ_MEDIUM 62 | * + DIO_SPEED_FREQ_HIGH 63 | * + DIO_SPEED_FREQ_VERY_HIGH 64 | * - output_type : One of: 65 | * + DIO_OUTPUT_PUSHPULL 66 | * + DIO_OUTPUT_OPENDRAIN 67 | */ 68 | 69 | // 70 | // Mappings for STM32F401. 71 | // 72 | #define DIO_PORT_A (GPIOA) 73 | #define DIO_PORT_B (GPIOB) 74 | #define DIO_PORT_C (GPIOC) 75 | #define DIO_PORT_D (GPIOD) 76 | #define DIO_PORT_E (GPIOE) 77 | #define DIO_PORT_F (GPIOF) 78 | #define DIO_PORT_G (GPIOG) 79 | #define DIO_PORT_H (GPIOH) 80 | 81 | #define DIO_PIN_0 (LL_GPIO_PIN_0) 82 | #define DIO_PIN_1 (LL_GPIO_PIN_1) 83 | #define DIO_PIN_2 (LL_GPIO_PIN_2) 84 | #define DIO_PIN_3 (LL_GPIO_PIN_3) 85 | #define DIO_PIN_4 (LL_GPIO_PIN_4) 86 | #define DIO_PIN_5 (LL_GPIO_PIN_5) 87 | #define DIO_PIN_6 (LL_GPIO_PIN_6) 88 | #define DIO_PIN_7 (LL_GPIO_PIN_7) 89 | #define DIO_PIN_8 (LL_GPIO_PIN_8) 90 | #define DIO_PIN_9 (LL_GPIO_PIN_9) 91 | #define DIO_PIN_10 (LL_GPIO_PIN_10) 92 | #define DIO_PIN_11 (LL_GPIO_PIN_11) 93 | #define DIO_PIN_12 (LL_GPIO_PIN_12) 94 | #define DIO_PIN_13 (LL_GPIO_PIN_13) 95 | #define DIO_PIN_14 (LL_GPIO_PIN_14) 96 | #define DIO_PIN_15 (LL_GPIO_PIN_15) 97 | 98 | #define DIO_PULL_NO (LL_GPIO_PULL_NO) 99 | #define DIO_PULL_UP (LL_GPIO_PULL_UP) 100 | #define DIO_PULL_DOWN (LL_GPIO_PULL_DOWN) 101 | 102 | #define DIO_SPEED_FREQ_LOW (LL_GPIO_SPEED_FREQ_LOW) 103 | #define DIO_SPEED_FREQ_MEDIUM (LL_GPIO_SPEED_FREQ_MEDIUM) 104 | #define DIO_SPEED_FREQ_HIGH (LL_GPIO_SPEED_FREQ_HIGH) 105 | #define DIO_SPEED_FREQ_VERY_HIGH (LL_GPIO_SPEED_FREQ_VERY_HIGH) 106 | 107 | #define DIO_OUTPUT_PUSHPULL (LL_GPIO_OUTPUT_PUSHPULL) 108 | #define DIO_OUTPUT_OPENDRAIN (LL_GPIO_OUTPUT_OPENDRAIN) 109 | 110 | typedef GPIO_TypeDef dio_port; 111 | 112 | struct dio_in_info { 113 | const char* const name; 114 | dio_port* const port; 115 | const uint32_t pin; 116 | const uint32_t pull; 117 | const uint8_t invert; 118 | }; 119 | 120 | struct dio_out_info { 121 | const char* const name; 122 | dio_port* const port; 123 | const uint32_t pin; 124 | const uint32_t pull; 125 | const uint8_t invert; 126 | const uint8_t init_value; 127 | const uint32_t speed; 128 | const uint32_t output_type; 129 | }; 130 | 131 | struct dio_cfg 132 | { 133 | const uint32_t num_inputs; 134 | const struct dio_in_info* const inputs; 135 | const uint32_t num_outputs; 136 | const struct dio_out_info* const outputs; 137 | }; 138 | 139 | // Core module interface functions. 140 | // Note: dio_init() keeps a copy of the cfg pointer. 141 | int32_t dio_init(struct dio_cfg* cfg); 142 | int32_t dio_start(void); 143 | 144 | // Other APIs. 145 | int32_t dio_get(uint32_t din_idx); 146 | int32_t dio_get_out(uint32_t dout_idx); 147 | int32_t dio_set(uint32_t dout_idx, uint32_t value); 148 | int32_t dio_get_num_in(void); 149 | int32_t dio_get_num_out(void); 150 | 151 | #endif // _DIO_H_ 152 | -------------------------------------------------------------------------------- /modules/include/gps_gtu7.h: -------------------------------------------------------------------------------- 1 | #ifndef _GPS_GTU7_H_ 2 | #define _GPS_GTU7_H_ 3 | 4 | /* 5 | * @brief Interface declaration of gps_gtu7 module. 6 | * 7 | * See implementation file for information about this module. 8 | * 9 | * MIT License 10 | * 11 | * Copyright (c) 2021 Eugene R Schroeder 12 | * 13 | * Permission is hereby granted, free of charge, to any person obtaining a copy 14 | * of this software and associated documentation files (the "Software"), to deal 15 | * in the Software without restriction, including without limitation the rights 16 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | * copies of the Software, and to permit persons to whom the Software is 18 | * furnished to do so, subject to the following conditions: 19 | * 20 | * The above copyright notice and this permission notice shall be included in 21 | * all copies or substantial portions of the Software. 22 | * 23 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | * SOFTWARE. 30 | */ 31 | 32 | #include 33 | 34 | #include "ttys.h" 35 | 36 | struct gps_cfg 37 | { 38 | enum ttys_instance_id ttys_instance_id; 39 | }; 40 | 41 | // Core module interface functions. 42 | int32_t gps_get_def_cfg(struct gps_cfg* cfg); 43 | int32_t gps_init(struct gps_cfg* cfg); 44 | int32_t gps_start(void); 45 | int32_t gps_run(void); 46 | 47 | // Other APIs. 48 | 49 | #endif // _GPS_GTU7_H_ 50 | -------------------------------------------------------------------------------- /modules/include/log.h: -------------------------------------------------------------------------------- 1 | #ifndef _LOG_H_ 2 | #define _LOG_H_ 3 | 4 | /* 5 | * @brief Interface declaration of log module. 6 | * 7 | * See implementation file for information about this module. 8 | * 9 | * MIT License 10 | * 11 | * Copyright (c) 2021 Eugene R Schroeder 12 | * 13 | * Permission is hereby granted, free of charge, to any person obtaining a copy 14 | * of this software and associated documentation files (the "Software"), to deal 15 | * in the Software without restriction, including without limitation the rights 16 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | * copies of the Software, and to permit persons to whom the Software is 18 | * furnished to do so, subject to the following conditions: 19 | * 20 | * The above copyright notice and this permission notice shall be included in 21 | * all copies or substantial portions of the Software. 22 | * 23 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | * SOFTWARE. 30 | */ 31 | 32 | #include 33 | 34 | // The log toggle char at the console is ctrl-L which is form feed, or 0x0c. 35 | #define LOG_TOGGLE_CHAR '\x0c' 36 | 37 | enum log_level { 38 | LOG_OFF = 0, 39 | LOG_ERROR, 40 | LOG_WARNING, 41 | LOG_INFO, 42 | LOG_DEBUG, 43 | LOG_TRACE, 44 | LOG_DEFAULT = LOG_INFO 45 | }; 46 | 47 | #define LOG_LEVEL_NAMES "off, error, warning, info, debug, trace" 48 | #define LOG_LEVEL_NAMES_CSV "off", "error", "warning", "info", "debug", "trace" 49 | 50 | // Core module interface functions. 51 | 52 | // Other APIs. 53 | void log_toggle_active(void); 54 | bool log_is_active(void); 55 | void log_printf(const char* fmt, ...); 56 | 57 | #define log_error(fmt, ...) do { if (_log_active && log_level >= LOG_ERROR) \ 58 | log_printf("ERR " fmt, ##__VA_ARGS__); } while (0) 59 | #define log_warning(fmt, ...) do { if (_log_active && log_level >= LOG_WARNING) \ 60 | log_printf("WARN " fmt, ##__VA_ARGS__); } while (0) 61 | #define log_info(fmt, ...) do { if (_log_active && log_level >= LOG_INFO) \ 62 | log_printf("INFO " fmt, ##__VA_ARGS__); } while (0) 63 | #define log_debug(fmt, ...) do { if (_log_active && log_level >= LOG_DEBUG) \ 64 | log_printf("DBG " fmt, ##__VA_ARGS__); } while (0) 65 | #define log_trace(fmt, ...) do { if (_log_active && log_level >= LOG_TRACE) \ 66 | log_printf("TRC " fmt, ##__VA_ARGS__); } while (0) 67 | 68 | // Following variable is global to allow efficient access by macros, 69 | // but is considered private. 70 | 71 | extern bool _log_active; 72 | 73 | #endif // _LOG_H_ 74 | -------------------------------------------------------------------------------- /modules/include/mem.h: -------------------------------------------------------------------------------- 1 | #ifndef _MEM_H_ 2 | #define _MEM_H_ 3 | 4 | /* 5 | * @brief Interface declaration of mem module. 6 | * 7 | * See implementation file for information about this module. 8 | * 9 | * MIT License 10 | * 11 | * Copyright (c) 2021 Eugene R Schroeder 12 | * 13 | * Permission is hereby granted, free of charge, to any person obtaining a copy 14 | * of this software and associated documentation files (the "Software"), to deal 15 | * in the Software without restriction, including without limitation the rights 16 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | * copies of the Software, and to permit persons to whom the Software is 18 | * furnished to do so, subject to the following conditions: 19 | * 20 | * The above copyright notice and this permission notice shall be included in 21 | * all copies or substantial portions of the Software. 22 | * 23 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | * SOFTWARE. 30 | */ 31 | 32 | #include 33 | 34 | // Core module interface functions. 35 | int32_t mem_start(void); 36 | 37 | // Other APIs. 38 | 39 | #endif // _MEM_H_ 40 | -------------------------------------------------------------------------------- /modules/include/module.h: -------------------------------------------------------------------------------- 1 | #ifndef _MODDEFS_H_ 2 | #define _MODDEFS_H_ 3 | 4 | /* 5 | * @brief Common definitions for modules. 6 | * 7 | * MIT License 8 | * 9 | * Copyright (c) 2021 Eugene R Schroed3er 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in 19 | * all copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | * SOFTWARE. 28 | */ 29 | 30 | #include 31 | 32 | // Error codes. 33 | #define MOD_ERR_ARG -1 34 | #define MOD_ERR_RESOURCE -2 35 | #define MOD_ERR_STATE -3 36 | #define MOD_ERR_BAD_CMD -4 37 | #define MOD_ERR_BUF_OVERRUN -5 38 | #define MOD_ERR_BAD_INSTANCE -6 39 | 40 | // Get size of an array. 41 | #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) 42 | 43 | // Increment a uint16_t, saturating at the maximum value. 44 | #define INC_SAT_U16(a) do { (a) += ((a) == UINT16_MAX ? 0 : 1); } while (0) 45 | 46 | // Clamp a numeric value between a lower and upper limit, inclusive. 47 | #define CLAMP(a, low, high) ((a) <= (low) ? (low) : ((a) > (high) ? (high) : (a))) 48 | 49 | #endif // _MODDEFS_H_ 50 | -------------------------------------------------------------------------------- /modules/include/stat.h: -------------------------------------------------------------------------------- 1 | #ifndef _STAT_H_ 2 | #define _STAT_H_ 3 | 4 | /* 5 | * @brief Interface declaration of stat utility. 6 | * 7 | * See implementation file for information about this utility. 8 | * 9 | * MIT License 10 | * 11 | * Copyright (c) 2021 Eugene R Schroeder 12 | * 13 | * Permission is hereby granted, free of charge, to any person obtaining a copy 14 | * of this software and associated documentation files (the "Software"), to deal 15 | * in the Software without restriction, including without limitation the rights 16 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | * copies of the Software, and to permit persons to whom the Software is 18 | * furnished to do so, subject to the following conditions: 19 | * 20 | * The above copyright notice and this permission notice shall be included in 21 | * all copies or substantial portions of the Software. 22 | * 23 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | * SOFTWARE. 30 | */ 31 | 32 | #include 33 | #include 34 | 35 | struct stat_dur { 36 | uint64_t accum_ms; 37 | uint32_t start_ms; 38 | uint32_t min; 39 | uint32_t max; 40 | uint32_t samples; 41 | bool started; 42 | }; 43 | 44 | void stat_dur_init(struct stat_dur* stat); 45 | void stat_dur_start(struct stat_dur* stat); 46 | void stat_dur_restart(struct stat_dur* stat); 47 | void stat_dur_end(struct stat_dur* stat); 48 | uint32_t stat_dur_avg_us(struct stat_dur* stat); 49 | 50 | #endif // _STAT_H_ 51 | -------------------------------------------------------------------------------- /modules/include/tmr.h: -------------------------------------------------------------------------------- 1 | #ifndef _TMR_H_ 2 | #define _TMR_H_ 3 | 4 | /* 5 | * @brief Interface declaration of tmr module. 6 | * 7 | * See implementation file for information about this module. 8 | * 9 | * MIT License 10 | * 11 | * Copyright (c) 2021 Eugene R Schroeder 12 | * 13 | * Permission is hereby granted, free of charge, to any person obtaining a copy 14 | * of this software and associated documentation files (the "Software"), to deal 15 | * in the Software without restriction, including without limitation the rights 16 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | * copies of the Software, and to permit persons to whom the Software is 18 | * furnished to do so, subject to the following conditions: 19 | * 20 | * The above copyright notice and this permission notice shall be included in 21 | * all copies or substantial portions of the Software. 22 | * 23 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | * SOFTWARE. 30 | */ 31 | 32 | #include 33 | #include 34 | 35 | #define TMR_NUM_INST 5 36 | 37 | // Return values from a timer handler indicating whether or not to restart the 38 | // timer. 39 | enum tmr_cb_action { 40 | TMR_CB_NONE, 41 | TMR_CB_RESTART, 42 | }; 43 | 44 | // Timer handler function signature. 45 | typedef enum tmr_cb_action (*tmr_cb_func)(int32_t tmr_id, uint32_t user_data); 46 | 47 | struct tmr_cfg 48 | { 49 | // FUTURE 50 | }; 51 | 52 | // Core module interface functions. 53 | int32_t tmr_init(struct tmr_cfg* cfg);; 54 | int32_t tmr_start(void;); 55 | int32_t tmr_run(void); 56 | 57 | // Other module-level APIs: 58 | uint32_t tmr_get_ms(void); 59 | 60 | // Timer instance-level APIs. 61 | int32_t tmr_inst_get(uint32_t ms); 62 | int32_t tmr_inst_get_cb(uint32_t ms, tmr_cb_func cb_func, uint32_t cb_user_data); 63 | int32_t tmr_inst_start(int32_t tmr_id, uint32_t ms); 64 | int32_t tmr_inst_release(int32_t tmr_id); 65 | int32_t tmr_inst_is_expired(int32_t tmr_id); 66 | 67 | // Private API that must be declared publically. 68 | void tmr_SysTick_Handler(void); 69 | 70 | #endif // _TMR_H_ 71 | -------------------------------------------------------------------------------- /modules/include/ttys.h: -------------------------------------------------------------------------------- 1 | #ifndef _TTYS_H_ 2 | #define _TTYS_H_ 3 | 4 | /* 5 | * @brief Interface declaration of ttys module. 6 | * 7 | * See implementation file for information about this module. 8 | * 9 | * MIT License 10 | * 11 | * Copyright (c) 2021 Eugene R Schroeder 12 | * 13 | * Permission is hereby granted, free of charge, to any person obtaining a copy 14 | * of this software and associated documentation files (the "Software"), to deal 15 | * in the Software without restriction, including without limitation the rights 16 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | * copies of the Software, and to permit persons to whom the Software is 18 | * furnished to do so, subject to the following conditions: 19 | * 20 | * The above copyright notice and this permission notice shall be included in 21 | * all copies or substantial portions of the Software. 22 | * 23 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | * SOFTWARE. 30 | */ 31 | 32 | #include 33 | #include 34 | #include 35 | 36 | // UART numbering is based on the MCU hardware definition. 37 | enum ttys_instance_id { 38 | TTYS_INSTANCE_UART1, 39 | TTYS_INSTANCE_UART2, // File discriptor 1 (stdin/stdout). 40 | TTYS_INSTANCE_UART6, 41 | 42 | TTYS_NUM_INSTANCES 43 | }; 44 | 45 | #define TTYS_RX_BUF_SIZE 80 46 | #define TTYS_TX_BUF_SIZE 1024 47 | 48 | struct ttys_cfg { 49 | bool create_stream; 50 | bool send_cr_after_nl; 51 | }; 52 | 53 | // Core module interface functions. 54 | int32_t ttys_get_def_cfg(enum ttys_instance_id instance_id, struct ttys_cfg* cfg); 55 | int32_t ttys_init(enum ttys_instance_id instance_id, struct ttys_cfg* cfg); 56 | int32_t ttys_start(enum ttys_instance_id instance_id); 57 | 58 | // Other APIs. 59 | int32_t ttys_putc(enum ttys_instance_id instance_id, char c); 60 | int32_t ttys_getc(enum ttys_instance_id instance_id, char* c); 61 | int ttys_get_fd(enum ttys_instance_id instance_id); 62 | FILE* ttys_get_stream(enum ttys_instance_id instance_id); 63 | 64 | #endif // _TTYS_H_ 65 | -------------------------------------------------------------------------------- /modules/log/log.c: -------------------------------------------------------------------------------- 1 | /* 2 | * @brief Implementation of log module. 3 | * 4 | * This module provides a simple logging utility. Most of the API is implemented 5 | * via macros found in the header file. Output is written to a ttys instance 6 | * specified during module creation. 7 | * 8 | * A client that uses the log API macros must have a private/static variable 9 | * called "log_level" to control the amount of logging. Typically, the client 10 | * provides a pointer to this variable to the cmd module. The cmd module then 11 | * allows the console user to display and set the client's logging level. 12 | * 13 | * There is also a global variable, log_active, that can be used to inhibit 14 | * log output. The console module toggles this variable on/off based on a 15 | * input key (ctrl-L). 16 | * 17 | * MIT License 18 | * 19 | * Copyright (c) 2021 Eugene R Schroeder 20 | * 21 | * Permission is hereby granted, free of charge, to any person obtaining a copy 22 | * of this software and associated documentation files (the "Software"), to deal 23 | * in the Software without restriction, including without limitation the rights 24 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | * copies of the Software, and to permit persons to whom the Software is 26 | * furnished to do so, subject to the following conditions: 27 | * 28 | * The above copyright notice and this permission notice shall be included in 29 | * all copies or substantial portions of the Software. 30 | * 31 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37 | * SOFTWARE. 38 | */ 39 | 40 | #include 41 | #include 42 | 43 | #include "tmr.h" 44 | #include "log.h" 45 | 46 | //////////////////////////////////////////////////////////////////////////////// 47 | // Common macros 48 | //////////////////////////////////////////////////////////////////////////////// 49 | 50 | //////////////////////////////////////////////////////////////////////////////// 51 | // Type definitions 52 | //////////////////////////////////////////////////////////////////////////////// 53 | 54 | //////////////////////////////////////////////////////////////////////////////// 55 | // Private (static) function declarations 56 | //////////////////////////////////////////////////////////////////////////////// 57 | 58 | //////////////////////////////////////////////////////////////////////////////// 59 | // Private (static) variables 60 | //////////////////////////////////////////////////////////////////////////////// 61 | 62 | //////////////////////////////////////////////////////////////////////////////// 63 | // Public (global) variables and externs 64 | //////////////////////////////////////////////////////////////////////////////// 65 | 66 | bool _log_active = true; 67 | 68 | //////////////////////////////////////////////////////////////////////////////// 69 | // Public (global) functions 70 | //////////////////////////////////////////////////////////////////////////////// 71 | 72 | /* 73 | * @brief Toggle state of "log active". 74 | */ 75 | void log_toggle_active(void) 76 | { 77 | _log_active = _log_active ? false : true; 78 | } 79 | 80 | /* 81 | * @brief Get state of "log active". 82 | * 83 | * @return Value of log active. 84 | * 85 | * Each output line starts with a ms resolution timestamp (relative time). 86 | */ 87 | bool log_is_active(void) 88 | { 89 | return _log_active; 90 | } 91 | 92 | /* 93 | * @brief Base "printf" style function for logging. 94 | * 95 | * @param[in] fmt Format string 96 | * 97 | * Each output line starts with a ms resolution timestamp (relative time). 98 | */ 99 | void log_printf(const char* fmt, ...) 100 | { 101 | va_list args; 102 | uint32_t ms = tmr_get_ms(); 103 | 104 | printf("%lu.%03lu ", ms / 1000U, ms % 1000U); 105 | va_start(args, fmt); 106 | vprintf(fmt, args); 107 | va_end(args); 108 | } 109 | 110 | //////////////////////////////////////////////////////////////////////////////// 111 | // Private (static) functions 112 | //////////////////////////////////////////////////////////////////////////////// 113 | -------------------------------------------------------------------------------- /modules/mem/mem.c: -------------------------------------------------------------------------------- 1 | /* 2 | * @brief Implementation of mem module. 3 | * 4 | * This module simply provides console commands to read and write memory, for 5 | * debugging. 6 | * 7 | * The following console commands are provided: 8 | * > mem r 9 | * > mem w 10 | * See code for details. 11 | * 12 | * MIT License 13 | * 14 | * Copyright (c) 2021 Eugene R Schroeder 15 | * 16 | * Permission is hereby granted, free of charge, to any person obtaining a copy 17 | * of this software and associated documentation files (the "Software"), to deal 18 | * in the Software without restriction, including without limitation the rights 19 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 20 | * copies of the Software, and to permit persons to whom the Software is 21 | * furnished to do so, subject to the following conditions: 22 | * 23 | * The above copyright notice and this permission notice shall be included in 24 | * all copies or substantial portions of the Software. 25 | * 26 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 32 | * SOFTWARE. 33 | */ 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | #include "cmd.h" 41 | #include "log.h" 42 | #include "module.h" 43 | 44 | //////////////////////////////////////////////////////////////////////////////// 45 | // Common macros 46 | //////////////////////////////////////////////////////////////////////////////// 47 | 48 | //////////////////////////////////////////////////////////////////////////////// 49 | // Type definitions 50 | //////////////////////////////////////////////////////////////////////////////// 51 | 52 | //////////////////////////////////////////////////////////////////////////////// 53 | // Private (static) function declarations 54 | //////////////////////////////////////////////////////////////////////////////// 55 | 56 | static int32_t cmd_mem_read(int32_t argc, const char** argv); 57 | static int32_t cmd_mem_write(int32_t argc, const char** argv); 58 | 59 | //////////////////////////////////////////////////////////////////////////////// 60 | // Private (static) variables 61 | //////////////////////////////////////////////////////////////////////////////// 62 | 63 | static struct cmd_cmd_info cmds[] = { 64 | { 65 | .name = "r", 66 | .func = cmd_mem_read, 67 | .help = "Read memory, usage: mem r addr [count [data-unit-size]]", 68 | }, 69 | { 70 | .name = "w", 71 | .func = cmd_mem_write, 72 | .help = "Write memory, usage: mem w addr value ...", 73 | }, 74 | }; 75 | 76 | static int32_t log_level = LOG_DEFAULT; 77 | 78 | static struct cmd_client_info cmd_info = { 79 | .name = "mem", 80 | .num_cmds = ARRAY_SIZE(cmds), 81 | .cmds = cmds, 82 | .log_level_ptr = &log_level, 83 | }; 84 | 85 | //////////////////////////////////////////////////////////////////////////////// 86 | // Public (global) variables and externs 87 | //////////////////////////////////////////////////////////////////////////////// 88 | 89 | //////////////////////////////////////////////////////////////////////////////// 90 | // Public (global) functions 91 | //////////////////////////////////////////////////////////////////////////////// 92 | 93 | /* 94 | * @brief Start mem instance. 95 | * 96 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 97 | * 98 | * This function starts the mem singleton module, to enter normal operation. 99 | */ 100 | int32_t mem_start(void) 101 | { 102 | int32_t result; 103 | 104 | log_debug("In mem_start()\n"); 105 | result = cmd_register(&cmd_info); 106 | if (result < 0) { 107 | log_error("mem_start: cmd error %d\n", result); 108 | return MOD_ERR_RESOURCE; 109 | } 110 | return 0; 111 | } 112 | 113 | //////////////////////////////////////////////////////////////////////////////// 114 | // Private (static) functions 115 | //////////////////////////////////////////////////////////////////////////////// 116 | 117 | /* 118 | * @brief Console command function for "mem r". 119 | * 120 | * @param[in] argc Number of arguments, including "mem" 121 | * @param[in] argv Argument values, including "mem" 122 | * 123 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 124 | * 125 | * Command usage: mem r addr [count [data-unit-size]] 126 | */ 127 | static int32_t cmd_mem_read(int32_t argc, const char** argv) 128 | { 129 | int32_t num_args; 130 | struct cmd_arg_val arg_vals[3]; 131 | uint32_t count = 1; 132 | uint32_t unit_size = 4; 133 | int32_t items_per_line; 134 | int32_t line_item_ctr; 135 | 136 | num_args = cmd_parse_args(argc-2, argv+2, "p[u[u]]", arg_vals); 137 | if (num_args >= 2) { 138 | count = arg_vals[1].val.u; 139 | } 140 | if (num_args >= 3) { 141 | unit_size = arg_vals[2].val.u; 142 | } 143 | if (num_args < 1 || num_args > 3) 144 | return num_args; 145 | 146 | switch (unit_size) { 147 | case 1: 148 | items_per_line = 16; 149 | break; 150 | case 2: 151 | items_per_line = 8; 152 | break; 153 | case 4: 154 | items_per_line = 4; 155 | break; 156 | default: 157 | printf("Invalid data unit size %lu\n", unit_size); 158 | return MOD_ERR_ARG; 159 | } 160 | line_item_ctr = 0; 161 | while (count-- > 0) { 162 | if (line_item_ctr == 0) 163 | printf("%08x:", (unsigned)arg_vals[0].val.p); 164 | switch (unit_size) { 165 | case 1: 166 | printf(" %02x", *arg_vals[0].val.p8); 167 | break; 168 | case 2: 169 | printf(" %04x", *arg_vals[0].val.p16); 170 | break; 171 | case 4: 172 | printf(" %08lx", *arg_vals[0].val.p32); 173 | break; 174 | } 175 | arg_vals[0].val.p8 += unit_size; 176 | if (++line_item_ctr == items_per_line) 177 | { 178 | printf("\n"); 179 | line_item_ctr = 0; 180 | } 181 | } 182 | if (line_item_ctr != 0) 183 | printf("\n"); 184 | return 0; 185 | } 186 | 187 | 188 | /* 189 | * @brief Console command function for "mem r". 190 | * 191 | * @param[in] argc Number of arguments, including "mem" 192 | * @param[in] argv Argument values, including "mem" 193 | * 194 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 195 | * 196 | * Command usage: mem w addr value ... 197 | */ 198 | static int32_t cmd_mem_write(int32_t argc, const char** argv) 199 | { 200 | int32_t num_args; 201 | struct cmd_arg_val arg_vals[6]; 202 | uint32_t unit_size; 203 | int32_t val_arg_idx; 204 | 205 | num_args = cmd_parse_args(argc-2, argv+2, "puu[u[u[u]]]", arg_vals); 206 | if (num_args < 3) 207 | return num_args; 208 | unit_size = arg_vals[1].val.u; 209 | if (unit_size != 1 && unit_size != 2 && unit_size != 4) { 210 | printf("Invalid data unit_size %lu\n", unit_size); 211 | return MOD_ERR_ARG; 212 | } 213 | val_arg_idx = 2; 214 | while (val_arg_idx < num_args) { 215 | switch (unit_size) { 216 | case 1: 217 | *arg_vals[0].val.p8++ = (uint8_t)arg_vals[val_arg_idx].val.u; 218 | break; 219 | case 2: 220 | *arg_vals[0].val.p16++ = (uint16_t)arg_vals[val_arg_idx].val.u; 221 | break; 222 | case 4: 223 | *arg_vals[0].val.p32++ = (uint32_t)arg_vals[val_arg_idx].val.u; 224 | break; 225 | } 226 | val_arg_idx++; 227 | } 228 | return 0; 229 | } 230 | -------------------------------------------------------------------------------- /modules/stat/stat.c: -------------------------------------------------------------------------------- 1 | /* 2 | * @brief Implementation of stat utility. 3 | * 4 | * This utility collects data and performs statistical calculations. Currently it 5 | * supports time duration measurements. 6 | * 7 | * MIT License 8 | * 9 | * Copyright (c) 2021 Eugene R Schroeder 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in 19 | * all copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | * SOFTWARE. 28 | */ 29 | 30 | #include 31 | #include 32 | #include 33 | 34 | #include "tmr.h" 35 | #include "stat.h" 36 | 37 | //////////////////////////////////////////////////////////////////////////////// 38 | // Common macros 39 | //////////////////////////////////////////////////////////////////////////////// 40 | 41 | //////////////////////////////////////////////////////////////////////////////// 42 | // Type definitions 43 | //////////////////////////////////////////////////////////////////////////////// 44 | 45 | //////////////////////////////////////////////////////////////////////////////// 46 | // Private (static) function declarations 47 | //////////////////////////////////////////////////////////////////////////////// 48 | 49 | //////////////////////////////////////////////////////////////////////////////// 50 | // Private (static) variables 51 | //////////////////////////////////////////////////////////////////////////////// 52 | 53 | //////////////////////////////////////////////////////////////////////////////// 54 | // Public (global) variables and externs 55 | //////////////////////////////////////////////////////////////////////////////// 56 | 57 | //////////////////////////////////////////////////////////////////////////////// 58 | // Public (global) functions 59 | //////////////////////////////////////////////////////////////////////////////// 60 | 61 | /* 62 | * @brief Initializize time duration statistic. 63 | * 64 | * @param[in] stat Time duration statistic. 65 | */ 66 | void stat_dur_init(struct stat_dur* stat) 67 | { 68 | memset(stat, 0, sizeof(*stat)); 69 | stat->min = UINT32_MAX; 70 | stat->max = 0; 71 | } 72 | 73 | /* 74 | * @brief Indicate start of duration interval. 75 | * 76 | * @param[in] stat Time duration statistic. 77 | */ 78 | void stat_dur_start(struct stat_dur* stat) 79 | { 80 | stat->start_ms = tmr_get_ms(); 81 | stat->started = true; 82 | } 83 | 84 | /* 85 | * @brief Indicate end of duration interval. 86 | * 87 | * @param[in] stat Time duration statistic. 88 | */ 89 | void stat_dur_end(struct stat_dur* stat) 90 | { 91 | uint32_t dur; 92 | 93 | if (stat->samples == UINT32_MAX || !stat->started) 94 | return; 95 | 96 | stat->started = false; 97 | dur = tmr_get_ms() - stat->start_ms; 98 | stat->accum_ms += dur; 99 | stat->samples++; 100 | if (dur > stat->max) 101 | stat->max = dur; 102 | if (dur < stat->min) 103 | stat->min = dur; 104 | } 105 | 106 | /* 107 | * @brief Restart measurement of a duration interval. 108 | * 109 | * @param[in] stat Time duration statistic. 110 | * 111 | * If a duration measurement was in progress, it is ended and recorded, and a 112 | * new measurement is started. This function allows, for example, a loop 113 | * duration to be measured by simply calling this function at one location in 114 | * the loop. This method ensures that no time is lost between ending a 115 | * measurement, and starting the next one. 116 | */ 117 | void stat_dur_restart(struct stat_dur* stat) 118 | { 119 | uint32_t now_ms; 120 | uint32_t dur; 121 | 122 | if (stat->samples == UINT32_MAX) 123 | return; 124 | 125 | now_ms = tmr_get_ms(); 126 | 127 | if (stat->started) { 128 | dur = now_ms - stat->start_ms; 129 | stat->accum_ms += dur; 130 | stat->samples++; 131 | if (dur > stat->max) 132 | stat->max = dur; 133 | if (dur < stat->min) 134 | stat->min = dur; 135 | } 136 | 137 | stat->started = true; 138 | stat->start_ms = now_ms; 139 | } 140 | 141 | /* 142 | * @brief Get average duration in us. 143 | * 144 | * @param[in] stat Time duration statistic. 145 | * 146 | * @return Average duration in us (0 if there are no samples). 147 | */ 148 | uint32_t stat_dur_avg_us(struct stat_dur* stat) 149 | { 150 | if (stat->samples == 0) 151 | return 0; 152 | return (stat->accum_ms * 1000) / stat->samples; 153 | } 154 | 155 | //////////////////////////////////////////////////////////////////////////////// 156 | // Private (static) functions 157 | //////////////////////////////////////////////////////////////////////////////// 158 | -------------------------------------------------------------------------------- /modules/templates/module.c.template: -------------------------------------------------------------------------------- 1 | /* 2 | * @brief Implementation of MYMOD module. 3 | * 4 | * This module ... 5 | * 6 | * The following console commands are provided: 7 | * > MYMOD status 8 | * See code for details. 9 | * 10 | * MIT License 11 | * 12 | * Copyright (c) 2021 Eugene R Schroeder 13 | * 14 | * Permission is hereby granted, free of charge, to any person obtaining a copy 15 | * of this software and associated documentation files (the "Software"), to deal 16 | * in the Software without restriction, including without limitation the rights 17 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | * copies of the Software, and to permit persons to whom the Software is 19 | * furnished to do so, subject to the following conditions: 20 | * 21 | * The above copyright notice and this permission notice shall be included in 22 | * all copies or substantial portions of the Software. 23 | * 24 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 | * SOFTWARE. 31 | */ 32 | 33 | #include 34 | 35 | #include "module.h" 36 | 37 | #include "MYMOD.h" 38 | 39 | //////////////////////////////////////////////////////////////////////////////// 40 | // Common macros 41 | //////////////////////////////////////////////////////////////////////////////// 42 | 43 | //////////////////////////////////////////////////////////////////////////////// 44 | // Type definitions 45 | //////////////////////////////////////////////////////////////////////////////// 46 | 47 | //////////////////////////////////////////////////////////////////////////////// 48 | // Private (static) function declarations 49 | //////////////////////////////////////////////////////////////////////////////// 50 | 51 | //////////////////////////////////////////////////////////////////////////////// 52 | // Private (static) variables 53 | //////////////////////////////////////////////////////////////////////////////// 54 | 55 | //////////////////////////////////////////////////////////////////////////////// 56 | // Public (global) variables and externs 57 | //////////////////////////////////////////////////////////////////////////////// 58 | 59 | //////////////////////////////////////////////////////////////////////////////// 60 | // Public (global) functions 61 | //////////////////////////////////////////////////////////////////////////////// 62 | 63 | // TODO: CHOOSE ONE SET OF FUNCTIONS BELOW BASED ON NUMBER OF INSTANCES 64 | 65 | /* 66 | * @brief Get default MYMOD configuration. 67 | * 68 | * @param[out] cfg The MYMOD configuration with defaults filled in. 69 | * 70 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 71 | */ 72 | int32_t MYMOD_get_def_cfg(struct MYMOD_cfg* cfg) 73 | { 74 | } 75 | 76 | /* 77 | * @brief Initialize MYMOD instance. 78 | * 79 | * @param[in] cfg The MYMOD configuration. (FUTURE) 80 | * 81 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 82 | * 83 | * This function initializes the MYMOD singleton module. Generally, it should 84 | * not access other modules as they might not have been initialized yet. An 85 | * exception is the log module. 86 | */ 87 | int32_t MYMOD_init(struct MYMOD_cfg* cfg) 88 | { 89 | return 0; 90 | } 91 | 92 | /* 93 | * @brief Start MYMOD instance. 94 | * 95 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 96 | * 97 | * This function starts the MYMOD singleton module, to enter normal operation. 98 | */ 99 | int32_t MYMOD_start(void) 100 | { 101 | return 0; 102 | } 103 | 104 | /* 105 | * @brief Run MYMOD instance. 106 | * 107 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 108 | * 109 | * @note This function should not block. 110 | * 111 | * This function runs the MYMOD singleton module, during normal operation. 112 | */ 113 | int32_t MYMOD_run(void) 114 | { 115 | return 0; 116 | } 117 | 118 | // TODO: START OF MULTI-INSTANCE TEMPLATES 119 | 120 | /* 121 | * @brief Get default MYMOD configuration. 122 | * 123 | * @param[in] instance_id Identifies the MYMOD instance. 124 | * @param[out] cfg The MYMOD configuration with defaults filled in. 125 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 126 | */ 127 | int32_t MYMOD_get_def_cfg(MYMOD_instance_id instance_id, struct MYMOD_cfg* cfg) 128 | { 129 | } 130 | 131 | /* 132 | * @brief Initialize MYMOD instance. 133 | * 134 | * @param[in] instance_id Identifies the MYMOD instance. 135 | * @param[in] cfg The MYMOD configuration. (FUTURE) 136 | * 137 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 138 | * 139 | * This function initializes a MYMOD module instance. Generally, it should not 140 | * access other modules as they might not have been initialized yet. An 141 | * exception is the log module. 142 | */ 143 | int32_t MYMOD_init(MYMOD_instance_id instance_id, struct MYMOD_cfg* cfg) 144 | { 145 | return 0; 146 | } 147 | 148 | /* 149 | * @brief Start MYMOD instance. 150 | * 151 | * @param[in] instance_id Identifies the MYMOD instance. 152 | * 153 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 154 | * 155 | * This function starts a MYMOD module instance, to enter normal operation. 156 | */ 157 | int32_t MYMOD_start(MYMOD_instance_id instance_id) 158 | { 159 | return 0; 160 | } 161 | 162 | /* 163 | * @brief Run MYMOD instance. 164 | * 165 | * @param[in] instance_id Identifies the MYMOD instance. 166 | * 167 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 168 | * 169 | * @note This function should not block. 170 | * 171 | * This function runs a MYMOD module instance, during normal operation. 172 | */ 173 | int32_t MYMOD_run(MYMOD_instance_id instance_id) 174 | { 175 | return 0; 176 | } 177 | 178 | //////////////////////////////////////////////////////////////////////////////// 179 | // Private (static) functions 180 | //////////////////////////////////////////////////////////////////////////////// 181 | -------------------------------------------------------------------------------- /modules/templates/module.h.template: -------------------------------------------------------------------------------- 1 | #ifndef _MYMOD_H_ 2 | #define _MYMOD_H_ 3 | 4 | /* 5 | * @brief Interface declaration of MYMOD module. 6 | * 7 | * See implementation file for information about this module. 8 | * 9 | * MIT License 10 | * 11 | * Copyright (c) 2021 Eugene R Schroeder 12 | * 13 | * Permission is hereby granted, free of charge, to any person obtaining a copy 14 | * of this software and associated documentation files (the "Software"), to deal 15 | * in the Software without restriction, including without limitation the rights 16 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | * copies of the Software, and to permit persons to whom the Software is 18 | * furnished to do so, subject to the following conditions: 19 | * 20 | * The above copyright notice and this permission notice shall be included in 21 | * all copies or substantial portions of the Software. 22 | * 23 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | * SOFTWARE. 30 | */ 31 | 32 | #include 33 | 34 | struct MYMOD_cfg 35 | { 36 | // FUTURE. 37 | }; 38 | 39 | // TODO: CHOOSE ONE SET OF FUNCTIONS BELOW BASED ON NUMBER OF INSTANCES 40 | 41 | // Core module interface functions. 42 | int32_t MYMOD_get_def_cfg(struct MYMOD_cfg* cfg); 43 | uin32_t MYMOD_init(struct MYMOD_cfg* cfg); 44 | uin32_t MYMOD_start(void); 45 | uin32_t MYMOD_run(void); 46 | 47 | // Core module interface functions. 48 | int32_t MYMOD_get_def_cfg(MYMOD_instance_id instance_id, struct MYMOD_cfg* cfg); 49 | uin32_t MYMOD_init(MYMOD_instance_id instance_id, struct MYMOD_cfg* cfg); 50 | uin32_t MYMOD_start(MYMOD_instance_id instance_id); 51 | uin32_t MYMOD_run(MYMOD_instance_id instance_id); 52 | 53 | // Other APIs. 54 | 55 | #endif // _MYMOD_H_ 56 | -------------------------------------------------------------------------------- /modules/tmr/tmr.c: -------------------------------------------------------------------------------- 1 | /* 2 | * @brief Implementation of tmr module. 3 | * 4 | * This module provides timing servies, consisting of: 5 | * - Software timers, with a resolution based on the system tick rate (e.g. 1 6 | * ms). When the timer expires, a callback function is executed, in the 7 | * context of this module (not in interrupt context). The callback function 8 | * can request that the timer be restarted, to get a precise periodic timer. 9 | * If a callback function is not provided, the user can poll the state of the 10 | * timer to see when it has expired. 11 | * - A function to get the current ms time value, an unsigned value which 12 | * periodically rolls over. With the current 32 bit variable, it rolls over 13 | * every 49.7 days. 14 | * 15 | * The number of software timers is fixed at compile time (see TMR_NUM_INST). 16 | * Each software timer has one of the following states: 17 | * TMR_UNUSED: Not in use. 18 | * TMR_STOPPED: Initialized (gotten) but not running. 19 | * TMR_RUNNING: Initialized (gotten) and running. 20 | * TMR_EXPIRED Expired, and callback did not request restart. 21 | * 22 | * The following console commands are provided: 23 | * > tmr status 24 | * > tmr test 25 | * See code for details. 26 | * 27 | * MIT License 28 | * 29 | * Copyright (c) 2021 Eugene R Schroeder 30 | * 31 | * Permission is hereby granted, free of charge, to any person obtaining a copy 32 | * of this software and associated documentation files (the "Software"), to deal 33 | * in the Software without restriction, including without limitation the rights 34 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 35 | * copies of the Software, and to permit persons to whom the Software is 36 | * furnished to do so, subject to the following conditions: 37 | * 38 | * The above copyright notice and this permission notice shall be included in 39 | * all copies or substantial portions of the Software. 40 | * 41 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 42 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 43 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 44 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 45 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 46 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 47 | * SOFTWARE. 48 | */ 49 | 50 | #include 51 | #include 52 | #include 53 | #include 54 | 55 | #include "stm32f4xx_ll_cortex.h" 56 | 57 | #include "cmd.h" 58 | #include "log.h" 59 | #include "module.h" 60 | #include "tmr.h" 61 | 62 | //////////////////////////////////////////////////////////////////////////////// 63 | // Common macros 64 | //////////////////////////////////////////////////////////////////////////////// 65 | 66 | //////////////////////////////////////////////////////////////////////////////// 67 | // Type definitions 68 | //////////////////////////////////////////////////////////////////////////////// 69 | 70 | enum tmr_state { 71 | TMR_UNUSED = 0, 72 | TMR_STOPPED, 73 | TMR_RUNNING, 74 | TMR_EXPIRED, 75 | }; 76 | 77 | // State information for a timer instance. 78 | struct tmr_inst_info { 79 | uint32_t period_ms; 80 | uint32_t start_time; 81 | enum tmr_state state; 82 | tmr_cb_func cb_func; 83 | uint32_t cb_user_data; 84 | }; 85 | 86 | //////////////////////////////////////////////////////////////////////////////// 87 | // Private (static) function declarations 88 | //////////////////////////////////////////////////////////////////////////////// 89 | 90 | static int32_t cmd_tmr_status(int32_t argc, const char** argv); 91 | static int32_t cmd_tmr_test(int32_t argc, const char** argv); 92 | static enum tmr_cb_action test_cb_func(int32_t tmr_id, uint32_t user_data); 93 | 94 | //////////////////////////////////////////////////////////////////////////////// 95 | // Private (static) variables 96 | //////////////////////////////////////////////////////////////////////////////// 97 | 98 | static uint32_t tick_ms_ctr; 99 | 100 | static struct tmr_inst_info tmrs[TMR_NUM_INST]; 101 | 102 | static struct cmd_cmd_info cmds[] = { 103 | { 104 | .name = "status", 105 | .func = cmd_tmr_status, 106 | .help = "Get module status, usage: tmr status", 107 | }, 108 | { 109 | .name = "test", 110 | .func = cmd_tmr_test, 111 | .help = "Run test, usage: tmr test [ [ []]] (enter no op/args for help)", 112 | }, 113 | }; 114 | 115 | static int32_t log_level = LOG_DEFAULT; 116 | 117 | static struct cmd_client_info cmd_info = { 118 | .name = "tmr", 119 | .num_cmds = ARRAY_SIZE(cmds), 120 | .cmds = cmds, 121 | .log_level_ptr = &log_level, 122 | }; 123 | 124 | //////////////////////////////////////////////////////////////////////////////// 125 | // Public (global) variables and externs 126 | //////////////////////////////////////////////////////////////////////////////// 127 | 128 | //////////////////////////////////////////////////////////////////////////////// 129 | // Public (global) functions 130 | //////////////////////////////////////////////////////////////////////////////// 131 | 132 | /* 133 | * @brief Initialize tmr module instance. 134 | * 135 | * @param[in] cfg The tmr configuration. (FUTURE) 136 | * 137 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 138 | * 139 | * This function initializes the tmr singleton module. Generally, it should not 140 | * access other modules as they might not have been initialized yet. An 141 | * exception is the log module. 142 | */ 143 | int32_t tmr_init(struct tmr_cfg* cfg) 144 | { 145 | log_debug("In tmr_init()\n"); 146 | memset(&tmrs, 0, sizeof(tmrs)); 147 | LL_SYSTICK_EnableIT(); 148 | return 0; 149 | } 150 | 151 | /* 152 | * @brief Start tmr module instance. 153 | * 154 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 155 | * 156 | * This function starts the tmr singleton module, to enter normal operation. 157 | */ 158 | int32_t tmr_start(void) 159 | { 160 | int32_t result; 161 | 162 | log_debug("In tmr_start()\n"); 163 | result = cmd_register(&cmd_info); 164 | if (result < 0) { 165 | log_error("tmr_start: cmd error %d\n", result); 166 | return MOD_ERR_RESOURCE; 167 | } 168 | return 0; 169 | } 170 | 171 | /* 172 | * @brief Run tmr module instance. 173 | * 174 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 175 | * 176 | * @note This function should not block. 177 | * 178 | * This function runs the tmr singleton module, during normal operation. It 179 | * checks for expired timers, and runs the callback function. 180 | */ 181 | int32_t tmr_run(void) 182 | { 183 | static uint32_t last_ms = 0; 184 | int32_t idx; 185 | uint32_t now_ms = tmr_get_ms(); 186 | 187 | // Fast exit if time has not changed. 188 | if (now_ms == last_ms) 189 | return 0; 190 | 191 | last_ms = now_ms; 192 | for (idx = 0; idx < TMR_NUM_INST; idx++) { 193 | struct tmr_inst_info* ti = &tmrs[idx]; 194 | if (ti->state == TMR_RUNNING) { 195 | if (now_ms - ti->start_time >= ti->period_ms) { 196 | ti->state = TMR_EXPIRED; 197 | if (ti->cb_func != NULL) { 198 | enum tmr_cb_action result = 199 | ti->cb_func(idx, ti->cb_user_data); 200 | now_ms = tmr_get_ms(); 201 | if (result == TMR_CB_RESTART) { 202 | ti->state = TMR_RUNNING; 203 | ti->start_time += ti->period_ms; 204 | } 205 | } 206 | } 207 | } 208 | } 209 | return 0; 210 | } 211 | 212 | /* 213 | * @brief Get system tick counter. 214 | * 215 | * @return System tick value. 216 | * 217 | * @note This function should not block. 218 | * 219 | * This function runs the tmr singleton module, during normal operation. It 220 | * checks for expired timers, and runs the callback function. 221 | */ 222 | uint32_t tmr_get_ms(void) 223 | { 224 | return tick_ms_ctr; 225 | } 226 | 227 | /* 228 | * @brief Get a timer instance without a callback function. 229 | * 230 | * @param[in] ms Timeout value in ms (or 0 to get a stopped timer). 231 | * 232 | * @return Timer instance ID (>= 0), else a "MOD_ERR" value (< 0). See code 233 | * for details. 234 | * 235 | * If a positive timeout value is provided, the timer is automatically started 236 | * and put in the TMR_RUNNING state. Otherwise, it is put in the TMR_STOPPED 237 | * state. 238 | * 239 | * Since no callback function is provided, the user should poll the state of 240 | * the timer instance, to check if it has expired. 241 | */ 242 | int32_t tmr_inst_get(uint32_t ms) 243 | { 244 | uint32_t idx; 245 | 246 | for (idx = 0; idx < TMR_NUM_INST; idx++) { 247 | struct tmr_inst_info* ti = &tmrs[idx]; 248 | if (ti->state == TMR_UNUSED) { 249 | ti->period_ms = ms; 250 | if (ms == 0) { 251 | ti->state = TMR_STOPPED; 252 | } else { 253 | ti->start_time = tmr_get_ms(); 254 | ti->state = TMR_RUNNING; 255 | } 256 | ti->cb_func = NULL; 257 | ti->cb_user_data = 0; 258 | return idx; 259 | } 260 | } 261 | // Out of timers. 262 | log_error("Out of timers\n"); 263 | return MOD_ERR_RESOURCE; 264 | } 265 | 266 | /* 267 | * @brief Get a timer instance with a callback function. 268 | * 269 | * @param[in] ms Timeout value in ms (or 0 to get a stopped timer). 270 | * @param[in] cb_func Function to call when timer expires. 271 | * @param[in] cb_user_data Data to pass to callback function. 272 | * 273 | * @return Timer instance ID (>= 0), else a "MOD_ERR" value (< 0). See code 274 | * for details. 275 | * 276 | * If a positive timeout value is provided, the timer is automatically started 277 | * and put in the TMR_RUNNING state. Otherwise, it is put in the TMR_STOPPED 278 | * state. 279 | */ 280 | int32_t tmr_inst_get_cb(uint32_t ms, tmr_cb_func cb_func, uint32_t cb_user_data) 281 | { 282 | int32_t tmr_id; 283 | 284 | tmr_id = tmr_inst_get(ms); 285 | if (tmr_id >= 0) { 286 | struct tmr_inst_info* ti = &tmrs[tmr_id]; 287 | ti->cb_func = cb_func; 288 | ti->cb_user_data = cb_user_data; 289 | } 290 | return tmr_id; 291 | } 292 | 293 | /* 294 | * @brief Start, or restart, or stop, a timer instance. 295 | * 296 | * @param[in] tmr_id Timer ID (returned by tmr_inst_get/tmr_inst_get_cb). 297 | * @param[in] ms Timeout value in ms (or 0 to stop the timer). 298 | * @param[in] cb_user_data Data to pass to callback function. 299 | * 300 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 301 | * 302 | * If the timer is already running, it will be restarted. 303 | */ 304 | int32_t tmr_inst_start(int32_t tmr_id, uint32_t ms) 305 | { 306 | int32_t rc; 307 | 308 | if (tmr_id >= 0 && tmr_id < TMR_NUM_INST) { 309 | struct tmr_inst_info* ti = &tmrs[tmr_id]; 310 | if (ti->state != TMR_UNUSED) { 311 | ti->period_ms = ms; 312 | if (ms == 0) { 313 | ti->state = TMR_STOPPED; 314 | } else { 315 | ti->start_time = tmr_get_ms(); 316 | ti->state = TMR_RUNNING; 317 | } 318 | rc = 0; 319 | } else { 320 | rc = MOD_ERR_STATE; 321 | } 322 | } else { 323 | rc = MOD_ERR_ARG; 324 | } 325 | return rc; 326 | } 327 | 328 | /* 329 | * @brief Release a timer instance. 330 | * 331 | * @param[in] tmr_id Timer ID (returned by tmr_inst_get/tmr_inst_get_cb). 332 | * 333 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 334 | */ 335 | int32_t tmr_inst_release(int32_t tmr_id) 336 | { 337 | if (tmr_id >= 0 && tmr_id < TMR_NUM_INST) { 338 | tmrs[tmr_id].state = TMR_UNUSED; 339 | return 0; 340 | } 341 | return MOD_ERR_ARG; 342 | } 343 | 344 | /* 345 | * @brief Check if timer instance is expired. 346 | * 347 | * @param[in] tmr_id Timer ID (returned by tmr_inst_get/tmr_inst_get_cb). 348 | * 349 | * @return Expiration state (0/1), else a "MOD_ERR" value (< 0). See code for 350 | * details. 351 | */ 352 | int32_t tmr_inst_is_expired(int32_t tmr_id) 353 | { 354 | if (tmr_id >= 0 && tmr_id < TMR_NUM_INST) 355 | return tmrs[tmr_id].state == TMR_EXPIRED; 356 | return MOD_ERR_ARG; 357 | } 358 | 359 | /* 360 | * @brief System tick interrupt handler. 361 | * 362 | * This function is called from the SysTick_Handler(). It must be a public 363 | * function so it can be called externally, but is not really a part of the API. 364 | */ 365 | void tmr_SysTick_Handler(void) 366 | { 367 | tick_ms_ctr++; 368 | } 369 | 370 | //////////////////////////////////////////////////////////////////////////////// 371 | // Private (static) functions 372 | //////////////////////////////////////////////////////////////////////////////// 373 | 374 | /* 375 | * @brief Convert timer instance state enum value to a string. 376 | * 377 | * @param[in] state Timer instance state enum. 378 | * 379 | * @return String corresponding to enum value. 380 | */ 381 | static const char* tmr_state_str(enum tmr_state state) 382 | { 383 | const char* rc; 384 | 385 | switch (state) { 386 | case TMR_UNUSED: 387 | rc = "unused"; 388 | break; 389 | case TMR_STOPPED: 390 | rc = "stopped"; 391 | break; 392 | case TMR_RUNNING: 393 | rc = "running"; 394 | break; 395 | case TMR_EXPIRED: 396 | rc = "expired"; 397 | break; 398 | default: 399 | rc = "invalid"; 400 | break; 401 | } 402 | return rc; 403 | } 404 | 405 | /* 406 | * @brief Console command function for "tmr status". 407 | * 408 | * @param[in] argc Number of arguments, including "tmr" 409 | * @param[in] argv Argument values, including "tmr" 410 | * 411 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 412 | * 413 | * Command usage: tmr status 414 | */ 415 | static int32_t cmd_tmr_status(int32_t argc, const char** argv) 416 | { 417 | uint32_t idx; 418 | uint32_t now_ms = tmr_get_ms(); 419 | 420 | printf("SysTick->CTRL=0x%08lx\n", SysTick->CTRL); // TODO REMOVE 421 | printf("Current millisecond tmr=%lu\n\n", now_ms); 422 | 423 | printf("ID Period Start time Time left CB User data State\n"); 424 | printf("-- ---------- ---------- ---------- -- ---------- ------\n"); 425 | for (idx = 0; idx < TMR_NUM_INST; idx++) { 426 | struct tmr_inst_info* ti = &tmrs[idx]; 427 | if (ti->state == TMR_UNUSED) 428 | continue; 429 | printf("%2lu %10lu %10lu %10lu %2s %10lu %s\n", idx, ti->period_ms, 430 | ti->start_time, 431 | ti->state == TMR_RUNNING ? 432 | ti->period_ms - (now_ms - ti->start_time) : 0, 433 | ti->cb_func == NULL ? "N" : "Y", 434 | ti->cb_user_data, 435 | tmr_state_str(ti->state)); 436 | } 437 | return 0; 438 | } 439 | 440 | /* 441 | * @brief Console command function for "tmr test". 442 | * 443 | * @param[in] argc Number of arguments, including "tmr" 444 | * @param[in] argv Argument values, including "tmr" 445 | * 446 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 447 | * 448 | * Command usage: tmr test [ [ []]] 449 | */ 450 | static int32_t cmd_tmr_test(int32_t argc, const char** argv) 451 | { 452 | struct cmd_arg_val arg_vals[2]; 453 | uint32_t param1; 454 | uint32_t param2 = 0; 455 | int32_t rc; 456 | 457 | // Handle help case. 458 | if (argc == 2) { 459 | printf("Test operations and param(s) are as follows:\n" 460 | " Get a non-callback tmr, usage: tmr test get \n" 461 | " Get a callback tmr, usage: tmr test get_cb \n" 462 | " Start a tmr, usage: tmr test start \n" 463 | " Release a tmr, usage: tmr test release \n" 464 | " Check if expired, usage: tmr test is_expired \n"); 465 | return 0; 466 | } 467 | 468 | if (argc < 4) { 469 | printf("Insufficent arguments\n"); 470 | return MOD_ERR_BAD_CMD; 471 | } 472 | 473 | // Initial argument checking. 474 | if (strcasecmp(argv[2], "get_cb") == 0 || 475 | strcasecmp(argv[2], "start") == 0) { 476 | if (cmd_parse_args(argc-3, argv+3, "uu", arg_vals) != 2) 477 | return MOD_ERR_BAD_CMD; 478 | param2 = arg_vals[1].val.u; 479 | } else { 480 | if (cmd_parse_args(argc-3, argv+3, "u", arg_vals) != 1) 481 | return MOD_ERR_BAD_CMD; 482 | } 483 | param1 = arg_vals[0].val.u; 484 | 485 | if (strcasecmp(argv[2], "get") == 0) { 486 | // command: tmr test get 487 | rc = tmr_inst_get(param1); 488 | } else if (strcasecmp(argv[2], "get_cb") == 0) { 489 | // command: tmr test get_cb 490 | rc = tmr_inst_get_cb(param1, test_cb_func, param2); 491 | } else if (strcasecmp(argv[2], "start") == 0) { 492 | // command: tmr test start 493 | rc = tmr_inst_start(param1, param2); 494 | } else if (strcasecmp(argv[2], "release") == 0) { 495 | // command: tmr test release 496 | rc = tmr_inst_release(param1); 497 | } else if (strcasecmp(argv[2], "is_expired") == 0) { 498 | // command: tmr test is_expired 499 | rc = tmr_inst_is_expired(param1); 500 | } else { 501 | printf("Invalid operation '%s'\n", argv[2]); 502 | return MOD_ERR_BAD_CMD; 503 | } 504 | printf("Operation returns %ld\n", rc); 505 | return 0; 506 | } 507 | 508 | /* 509 | * @brief Timer callback function for "tmr test" command. 510 | * 511 | * @param[in] tmr_id Timer ID. 512 | * @param[in] user_data User callback data. 513 | * 514 | * @return TMR_CB_RESTART or TMR_CB_NONE based on test logic. 515 | */ 516 | static enum tmr_cb_action test_cb_func(int32_t tmr_id, uint32_t user_data) 517 | { 518 | log_debug("test_cb_func(tmr_id=%d user_data=%lu\n", 519 | tmr_id, user_data); 520 | return user_data == 0 ? TMR_CB_RESTART : TMR_CB_NONE; 521 | } 522 | -------------------------------------------------------------------------------- /modules/ttys/ttys.c: -------------------------------------------------------------------------------- 1 | /* 2 | * @brief Implementation of ttys module. 3 | * 4 | * This module provides a simple "TTY serial" interface for MCU UARTs. 5 | * Main features: 6 | * - Buffering on output to prevent blocking (overrun is possible) 7 | * - Buffering on input to avoid loss of input characters (overrun is possible) 8 | * - Integrate into the C standard library streams I/O (to support printf and 9 | friends) 10 | * - Performance measurements. 11 | * - Console commands 12 | * 13 | * The following console commands are provided: 14 | * > ttys status 15 | * > ttys test 16 | * See code for details. 17 | * 18 | * This library makes use of the STMicroelectronics Low Level (LL) device 19 | * library. 20 | * 21 | * Currently, this module does not perform hardware initialization of the UART 22 | * and associated hardware (e.g. GPIO), except for the interrupt controller (see 23 | * below). It is expected that the driver library has been used for 24 | * initilazation (e.g. via generated IDE code). This avoids having to deal with 25 | * the issue of inconsistent driver libraries among MCUs. 26 | * 27 | * This module enables USART interrupts on the Nested Vector Interrupt 28 | * Controller (NVIC). It also overrides the (weak) USART interrupt handler 29 | * functions (USARTx_IRQHandler). Thus, in the IDE device configuration tool, 30 | * the "USARTx global interrupt" should NOT be chosen or you will get a 31 | * duplicate symbol at link time. 32 | * 33 | * A future feature is to perform full hardware initialization in this library, 34 | * and allowing at least some UART parameters to be set (e.g. buad). 35 | * 36 | * MIT License 37 | * 38 | * Copyright (c) 2021 Eugene R Schroeder 39 | * 40 | * Permission is hereby granted, free of charge, to any person obtaining a copy 41 | * of this software and associated documentation files (the "Software"), to deal 42 | * in the Software without restriction, including without limitation the rights 43 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 44 | * copies of the Software, and to permit persons to whom the Software is 45 | * furnished to do so, subject to the following conditions: 46 | * 47 | * The above copyright notice and this permission notice shall be included in 48 | * all copies or substantial portions of the Software. 49 | * 50 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 51 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 52 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 53 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 54 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 55 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 56 | * SOFTWARE. 57 | */ 58 | 59 | #include 60 | #include 61 | #include 62 | #include 63 | #include 64 | #include 65 | 66 | #include "stm32f4xx_ll_usart.h" 67 | 68 | #include "cmd.h" 69 | #include "log.h" 70 | #include "module.h" 71 | #include "tmr.h" 72 | #include "ttys.h" 73 | 74 | //////////////////////////////////////////////////////////////////////////////// 75 | // Common macros 76 | //////////////////////////////////////////////////////////////////////////////// 77 | 78 | // This module integrates into the C language stdio system. The ttys "device 79 | // files" can be viewed as always "open", with the following file 80 | // descriptors. These are then mapped to FILE streams. 81 | // 82 | // Note that one of the UARTs is mapped to stdout (file descriptor 1), which 83 | // is thus the one used for printf() and friends. 84 | 85 | #define UART1_FD 4 86 | #define UART2_FD 1 87 | #define UART6_FD 3 88 | 89 | //////////////////////////////////////////////////////////////////////////////// 90 | // Type definitions 91 | //////////////////////////////////////////////////////////////////////////////// 92 | 93 | // Per-instance ttys state information. 94 | struct ttys_state { 95 | struct ttys_cfg cfg; 96 | FILE* stream; 97 | int fd; 98 | USART_TypeDef* uart_reg_base; 99 | uint16_t rx_buf_get_idx; 100 | uint16_t rx_buf_put_idx; 101 | uint16_t tx_buf_get_idx; 102 | uint16_t tx_buf_put_idx; 103 | char tx_buf[TTYS_TX_BUF_SIZE]; 104 | char rx_buf[TTYS_RX_BUF_SIZE]; 105 | }; 106 | 107 | // Performance measurements for ttys. Currently these are common to all 108 | // instances. A future enhancement would be to make them per-instance. 109 | 110 | enum ttys_u16_pms { 111 | CNT_RX_UART_ORE, 112 | CNT_RX_UART_NE, 113 | CNT_RX_UART_FE, 114 | CNT_RX_UART_PE, 115 | CNT_TX_BUF_OVERRUN, 116 | CNT_RX_BUF_OVERRUN, 117 | 118 | NUM_U16_PMS 119 | }; 120 | 121 | //////////////////////////////////////////////////////////////////////////////// 122 | // Private (static) function declarations 123 | //////////////////////////////////////////////////////////////////////////////// 124 | 125 | static void ttys_interrupt(enum ttys_instance_id instance_id, 126 | IRQn_Type irq_type); 127 | static int32_t cmd_ttys_status(int32_t argc, const char** argv); 128 | static int32_t cmd_ttys_test(int32_t argc, const char** argv); 129 | 130 | //////////////////////////////////////////////////////////////////////////////// 131 | // Private (static) variables 132 | //////////////////////////////////////////////////////////////////////////////// 133 | 134 | static struct ttys_state ttys_states[TTYS_NUM_INSTANCES]; 135 | 136 | static int32_t log_level = LOG_DEFAULT; 137 | 138 | // Storage for performance measurements. 139 | static uint16_t cnts_u16[NUM_U16_PMS]; 140 | 141 | // Names of performance measurements. 142 | static const char* cnts_u16_names[NUM_U16_PMS] = { 143 | "uart rx overrun err", 144 | "uart rx noise err", 145 | "uart rx frame err", 146 | "uart rx parity err", 147 | "tx buf overrun err", 148 | "rx buf overrun err", 149 | }; 150 | 151 | // Data structure with console command info. 152 | static struct cmd_cmd_info cmds[] = { 153 | { 154 | .name = "status", 155 | .func = cmd_ttys_status, 156 | .help = "Get module status, usage: ttys status", 157 | }, 158 | { 159 | .name = "test", 160 | .func = cmd_ttys_test, 161 | .help = "Run test, usage: ttys test [ []] (enter no op/arg for help)", 162 | } 163 | }; 164 | 165 | // Data structure passed to cmd module for console interaction. 166 | static struct cmd_client_info cmd_info = { 167 | .name = "ttys", 168 | .num_cmds = ARRAY_SIZE(cmds), 169 | .cmds = cmds, 170 | .log_level_ptr = &log_level, 171 | .num_u16_pms = NUM_U16_PMS, 172 | .u16_pms = cnts_u16, 173 | .u16_pm_names = cnts_u16_names, 174 | }; 175 | 176 | //////////////////////////////////////////////////////////////////////////////// 177 | // Public (global) variables and externs 178 | //////////////////////////////////////////////////////////////////////////////// 179 | 180 | //////////////////////////////////////////////////////////////////////////////// 181 | // Public (global) functions 182 | //////////////////////////////////////////////////////////////////////////////// 183 | 184 | /* 185 | * @brief Get default ttys configuration. 186 | * 187 | * @param[out] cfg The ttys configuration with defaults filled in. 188 | * 189 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 190 | */ 191 | int32_t ttys_get_def_cfg(enum ttys_instance_id instance_id, struct ttys_cfg* cfg) 192 | { 193 | if (cfg == NULL) 194 | return MOD_ERR_ARG; 195 | 196 | memset(cfg, 0, sizeof(*cfg)); 197 | cfg->create_stream = true; 198 | cfg->send_cr_after_nl = true; 199 | return 0; 200 | } 201 | 202 | /* 203 | * @brief Initialize ttys module instance. 204 | * 205 | * @param[in] instance_id Identifies the ttys instance. 206 | * @param[in] cfg The ttys module configuration (FUTURE) 207 | * 208 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 209 | * 210 | * This function initializes a ttys module instance. Generally, it should not 211 | * access other modules as they might not have been initialized yet. 212 | */ 213 | int32_t ttys_init(enum ttys_instance_id instance_id, struct ttys_cfg* cfg) 214 | { 215 | struct ttys_state* st; 216 | 217 | if (instance_id >= TTYS_NUM_INSTANCES) 218 | return MOD_ERR_BAD_INSTANCE; 219 | 220 | if (cfg == NULL) 221 | return MOD_ERR_ARG; 222 | 223 | // We selectively initialize the state structure, as we want to preserve the 224 | // transmit queue in case there is output in it. However, if the transmit 225 | // queue appears corrupted, we initialize the whole thing. 226 | 227 | st = &ttys_states[instance_id]; 228 | if (st->tx_buf_get_idx >= TTYS_TX_BUF_SIZE || 229 | st->tx_buf_put_idx >= TTYS_TX_BUF_SIZE) { 230 | memset(st, 0, sizeof(*st)); 231 | } else { 232 | st->rx_buf_get_idx = 0; 233 | st->rx_buf_put_idx = 0; 234 | } 235 | st->cfg = *cfg; 236 | 237 | switch (instance_id) { 238 | case TTYS_INSTANCE_UART1: 239 | st->uart_reg_base = USART1; 240 | st->fd = UART1_FD; 241 | break; 242 | case TTYS_INSTANCE_UART2: 243 | st->uart_reg_base = USART2; 244 | st->fd = UART2_FD; 245 | break; 246 | case TTYS_INSTANCE_UART6: 247 | st->uart_reg_base = USART6; 248 | st->fd = UART6_FD; 249 | break; 250 | default: 251 | return MOD_ERR_BAD_INSTANCE; 252 | } 253 | if (st->cfg.create_stream) { 254 | st->stream = fdopen(st->fd, "r+"); 255 | if (st->stream != NULL) 256 | setvbuf(st->stream, NULL, _IONBF, 0); 257 | } else { 258 | st->stream = NULL; 259 | } 260 | return 0; 261 | } 262 | 263 | /* 264 | * @brief Start ttys module instance. 265 | * 266 | * @param[in] instance_id Identifies the ttys instance. 267 | * 268 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 269 | * 270 | * This function starts a ttys module instance, to enter normal operation. This 271 | * includes enabling UART interrupts. 272 | */ 273 | int32_t ttys_start(enum ttys_instance_id instance_id) 274 | { 275 | struct ttys_state* st; 276 | IRQn_Type irq_type; 277 | int32_t result; 278 | 279 | if (instance_id >= TTYS_NUM_INSTANCES || 280 | ttys_states[instance_id].uart_reg_base == NULL) 281 | return MOD_ERR_BAD_INSTANCE; 282 | 283 | result = cmd_register(&cmd_info); 284 | if (result < 0) { 285 | log_error("ttys_start: cmd error %d\n", result); 286 | return MOD_ERR_RESOURCE; 287 | } 288 | 289 | st = &ttys_states[instance_id]; 290 | LL_USART_EnableIT_RXNE(st->uart_reg_base); 291 | LL_USART_EnableIT_TXE(st->uart_reg_base); 292 | 293 | switch (instance_id) { 294 | case TTYS_INSTANCE_UART1: 295 | irq_type = USART1_IRQn; 296 | break; 297 | case TTYS_INSTANCE_UART2: 298 | irq_type = USART2_IRQn; 299 | break; 300 | case TTYS_INSTANCE_UART6: 301 | irq_type = USART6_IRQn; 302 | break; 303 | default: 304 | return MOD_ERR_BAD_INSTANCE; 305 | } 306 | NVIC_SetPriority(irq_type, 307 | NVIC_EncodePriority(NVIC_GetPriorityGrouping(),0, 0)); 308 | NVIC_EnableIRQ(irq_type); 309 | 310 | return 0; 311 | } 312 | 313 | /* 314 | * @brief Put a character for transmission. 315 | * 316 | * @param[in] instance_id Identifies the ttys instance. 317 | * @param[in] c Character to transmit. 318 | * 319 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 320 | * 321 | * @note Before this module is started, the UART is not known, but the user can 322 | * still put chars in the TX buffer that will be transmitted if and when 323 | * the module is started. 324 | */ 325 | int32_t ttys_putc(enum ttys_instance_id instance_id, char c) 326 | { 327 | struct ttys_state* st; 328 | uint16_t next_put_idx; 329 | 330 | if (instance_id >= TTYS_NUM_INSTANCES) 331 | return MOD_ERR_BAD_INSTANCE; 332 | st = &ttys_states[instance_id]; 333 | 334 | // Calculate the new TX buffer put index 335 | next_put_idx = st->tx_buf_put_idx + 1; 336 | if (next_put_idx >= TTYS_TX_BUF_SIZE) 337 | next_put_idx = 0; 338 | 339 | // If buffer is full, then return error. 340 | while (next_put_idx == st->tx_buf_get_idx) { 341 | INC_SAT_U16(cnts_u16[CNT_TX_BUF_OVERRUN]); 342 | return MOD_ERR_BUF_OVERRUN; 343 | } 344 | 345 | // Put the char in the TX buffer. 346 | st->tx_buf[st->tx_buf_put_idx] = c; 347 | st->tx_buf_put_idx = next_put_idx; 348 | 349 | // Ensure the TX interrupt is enabled. 350 | if (ttys_states[instance_id].uart_reg_base != NULL) { 351 | __disable_irq(); 352 | LL_USART_EnableIT_TXE(st->uart_reg_base); 353 | __enable_irq(); 354 | } 355 | return 0; 356 | } 357 | 358 | /* 359 | * @brief Get a received character. 360 | * 361 | * @param[in] instance_id Identifies the ttys instance. 362 | * @param[out] c Received character. 363 | * 364 | * @return Number of characters returned (0 or 1) 365 | */ 366 | int32_t ttys_getc(enum ttys_instance_id instance_id, char* c) 367 | { 368 | struct ttys_state* st; 369 | int32_t next_get_idx; 370 | 371 | if (instance_id >= TTYS_NUM_INSTANCES) 372 | return MOD_ERR_BAD_INSTANCE; 373 | 374 | st = &ttys_states[instance_id]; 375 | 376 | // Check if buffer is empty. 377 | if (st->rx_buf_get_idx == st->rx_buf_put_idx) 378 | return 0; 379 | 380 | // Get a character and advance get index. 381 | next_get_idx = st->rx_buf_get_idx + 1; 382 | if (next_get_idx >= TTYS_RX_BUF_SIZE) 383 | next_get_idx = 0; 384 | *c = st->rx_buf[st->rx_buf_get_idx]; 385 | st->rx_buf_get_idx = next_get_idx; 386 | return 1; 387 | } 388 | 389 | /* 390 | * @brief Get file descriptor for a ttys instance. 391 | * 392 | * @param[in] instance_id Identifies the ttys instance. 393 | * 394 | * @return File descriptor (>= 0) for success, else a "MOD_ERR" value (<0). See 395 | * code for details. 396 | */ 397 | int ttys_get_fd(enum ttys_instance_id instance_id) 398 | { 399 | if (instance_id >= TTYS_NUM_INSTANCES) 400 | return MOD_ERR_ARG; 401 | if (ttys_states[instance_id].fd >= 0) 402 | return ttys_states[instance_id].fd; 403 | return MOD_ERR_RESOURCE; 404 | } 405 | 406 | /* 407 | * @brief Get FILE stream for a ttys instance. 408 | * 409 | * @param[in] instance_id Identifies the ttys instance. 410 | * 411 | * @return FILE stream pointer, or NULL if error. 412 | */ 413 | FILE* ttys_get_stream(enum ttys_instance_id instance_id) 414 | { 415 | if (instance_id >= TTYS_NUM_INSTANCES) 416 | return NULL; 417 | 418 | return ttys_states[instance_id].stream; 419 | } 420 | 421 | // The following interrupt handler functions override the default handlers, 422 | // which are "weak" symobols. 423 | 424 | void USART1_IRQHandler(void) 425 | { 426 | ttys_interrupt(TTYS_INSTANCE_UART1, USART1_IRQn); 427 | } 428 | 429 | void USART2_IRQHandler(void) 430 | { 431 | ttys_interrupt(TTYS_INSTANCE_UART2, USART2_IRQn); 432 | } 433 | 434 | void USART6_IRQHandler(void) 435 | { 436 | ttys_interrupt(TTYS_INSTANCE_UART6, USART6_IRQn); 437 | } 438 | 439 | //////////////////////////////////////////////////////////////////////////////// 440 | // Private (static) functions 441 | //////////////////////////////////////////////////////////////////////////////// 442 | 443 | /* 444 | * @brief UART interrupt handler 445 | * 446 | * @param[in] instance_id Identifies the ttys instance. 447 | * @param[in] irq_type Identifies the type of interrupt. 448 | * 449 | * @note This function should not write output using stdio API, to ensure we do 450 | * not corrupt the put index on the tx buffer. 451 | */ 452 | static void ttys_interrupt(enum ttys_instance_id instance_id, 453 | IRQn_Type irq_type) 454 | { 455 | struct ttys_state* st; 456 | uint8_t sr; 457 | 458 | if (instance_id >= TTYS_NUM_INSTANCES) 459 | return; 460 | 461 | st = &ttys_states[instance_id]; 462 | 463 | // If instance is not open, we should not get an interrupt, but for safety 464 | // just disable it. 465 | if (st->uart_reg_base == NULL) { 466 | NVIC_DisableIRQ(irq_type); 467 | return; 468 | } 469 | 470 | sr = st->uart_reg_base->SR; 471 | 472 | if (sr & LL_USART_SR_RXNE) { 473 | // Got an incoming character. 474 | uint16_t next_rx_put_idx = st->rx_buf_put_idx + 1; 475 | char rx_data = st->uart_reg_base->DR; 476 | if (next_rx_put_idx >= TTYS_RX_BUF_SIZE) 477 | next_rx_put_idx = 0; 478 | if (next_rx_put_idx == st->rx_buf_get_idx) { 479 | INC_SAT_U16(cnts_u16[CNT_RX_BUF_OVERRUN]); 480 | } else { 481 | st->rx_buf[st->rx_buf_put_idx] = rx_data; 482 | st->rx_buf_put_idx = next_rx_put_idx; 483 | } 484 | } 485 | if (sr & LL_USART_SR_TXE) { 486 | // Can send a character. 487 | if (st->tx_buf_get_idx == st->tx_buf_put_idx) { 488 | // No characters to send, disable the interrrupt. 489 | LL_USART_DisableIT_TXE(st->uart_reg_base); 490 | } else { 491 | st->uart_reg_base->DR = st->tx_buf[st->tx_buf_get_idx]; 492 | if (st->tx_buf_get_idx < TTYS_TX_BUF_SIZE-1) 493 | st->tx_buf_get_idx++; 494 | else 495 | st->tx_buf_get_idx = 0; 496 | } 497 | } 498 | if (sr & (LL_USART_SR_ORE | LL_USART_SR_NE | LL_USART_SR_FE | 499 | LL_USART_SR_PE)) { 500 | 501 | // Error conditions. To clear the bit, we need to read the data 502 | // register, but we don't use it. 503 | 504 | (void)st->uart_reg_base->DR; 505 | if (sr & LL_USART_SR_ORE) 506 | INC_SAT_U16(cnts_u16[CNT_RX_UART_ORE]); 507 | if (sr & LL_USART_SR_NE) 508 | INC_SAT_U16(cnts_u16[CNT_RX_UART_NE]); 509 | if (sr & LL_USART_SR_FE) 510 | INC_SAT_U16(cnts_u16[CNT_RX_UART_FE]); 511 | if (sr & LL_USART_SR_PE) 512 | INC_SAT_U16(cnts_u16[CNT_RX_UART_PE]); 513 | } 514 | } 515 | 516 | /* 517 | * @brief Console command function for "ttys status". 518 | * 519 | * @param[in] argc Number of arguments, including "ttys" 520 | * @param[in] argv Argument values, including "ttys" 521 | * 522 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 523 | * 524 | * Command usage: ttys status 525 | */ 526 | static int32_t cmd_ttys_status(int32_t argc, const char** argv) 527 | { 528 | enum ttys_instance_id instance_id; 529 | 530 | for (instance_id = 0; instance_id < TTYS_NUM_INSTANCES; instance_id++) { 531 | struct ttys_state* st = &ttys_states[instance_id]; 532 | printf("Instance %d:\n", instance_id); 533 | if (st->uart_reg_base == NULL) { 534 | printf(" NULL\n"); 535 | } else { 536 | printf(" TX buffer: get_idx=%u put_idx=%u\n", 537 | st->tx_buf_get_idx, st->tx_buf_put_idx); 538 | printf(" RX buffer: get_idx=%u put_idx=%d\n", 539 | st->rx_buf_get_idx, st->rx_buf_put_idx); 540 | } 541 | } 542 | return 0; 543 | } 544 | 545 | /* 546 | * @brief Console command function for "ttys test". 547 | * 548 | * @param[in] argc Number of arguments, including "ttys" 549 | * @param[in] argv Argument values, including "ttys" 550 | * 551 | * @return 0 for success, else a "MOD_ERR" value. See code for details. 552 | * 553 | * Command usage: ttys test [ []] 554 | */ 555 | static int32_t cmd_ttys_test(int32_t argc, const char** argv) 556 | { 557 | struct cmd_arg_val arg_vals[1]; 558 | uint32_t param; 559 | FILE* f = NULL; 560 | int fd = 0; 561 | int rc; 562 | uint32_t start_ms; 563 | const char* test_msg = "Test\n"; 564 | const int test_msg_len = strlen(test_msg); 565 | 566 | // Handle help case. 567 | if (argc == 2) { 568 | printf("Test operations and param(s) are as follows:\n" 569 | " Write test msg using fprintf, usage: ttys test fprintf \n" 570 | " Write test msg using write, usage: ttys test write \n" 571 | " Read chars for 5 seconds using fgetc, usage: ttys test fgetc \n" 572 | " Read chars for 5 seconds using read, usage: ttys test read \n" 573 | "\nWARNING! Read tests block!\n" 574 | ); 575 | return 0; 576 | } 577 | 578 | // Initial argument checking. 579 | if (cmd_parse_args(argc-3, argv+3, "u", arg_vals) != 1) 580 | return MOD_ERR_BAD_CMD; 581 | 582 | param = arg_vals[0].val.u; 583 | 584 | if (strcasecmp(argv[2], "fprintf") == 0 || 585 | strcasecmp(argv[2], "fgetc") == 0) { 586 | f = ttys_get_stream((enum ttys_instance_id)param); 587 | if (f == NULL) { 588 | printf("Can't get FILE stream for instance.\n"); 589 | return MOD_ERR_RESOURCE; 590 | } 591 | } 592 | else if (strcasecmp(argv[2], "write") == 0 || 593 | strcasecmp(argv[2], "read") == 0) { 594 | fd = ttys_get_fd((enum ttys_instance_id)param); 595 | if (fd < 0) { 596 | printf("Can't get fd for instance result=%d.\n", fd); 597 | return MOD_ERR_RESOURCE; 598 | } 599 | } else { 600 | printf("Invalid operation '%s'\n", argv[2]); 601 | return MOD_ERR_BAD_CMD; 602 | } 603 | 604 | if (strcasecmp(argv[2], "fprintf") == 0) { 605 | // command: ttys test fprintf 606 | rc = fprintf(f, test_msg); 607 | fflush(f); 608 | printf("fprintf to FILE %p returns %d errno=%d\n", f, rc, errno); 609 | } else if (strcasecmp(argv[2], "write") == 0) { 610 | // command: ttys test write 611 | rc = write(fd, test_msg, test_msg_len); 612 | printf("write to fd %d returns %d errno=%d\n", fd, rc, errno); 613 | } else if (strcasecmp(argv[2], "fgetc") == 0) { 614 | // command: ttys test fgetc 615 | start_ms = tmr_get_ms(); 616 | while (tmr_get_ms() - start_ms < 5000) { 617 | rc = fgetc(f); 618 | if (rc > 0) 619 | printf("Got char 0x%02x\n", rc); 620 | else if (rc != -1 || errno != EAGAIN) { 621 | printf("Unexpected result rc=%d errno=%d\n", rc, errno); 622 | break; 623 | } 624 | } 625 | } else if (strcasecmp(argv[2], "read") == 0) { 626 | // command: ttys test read 627 | start_ms = tmr_get_ms(); 628 | while (tmr_get_ms() - start_ms < 5000) { 629 | char c; 630 | rc = read(fd, &c, 1); 631 | if (rc == 1) 632 | printf("Got char 0x%02x\n", c); 633 | else if (rc != -1 || errno != EWOULDBLOCK) { 634 | printf("Unexpected result rc=%d errno=%d\n", rc, errno); 635 | break; 636 | } 637 | } 638 | } 639 | return 0; 640 | } 641 | 642 | //////////////////////////////////////////////////////////////////////////////// 643 | // The following functions are used to integrate this module into the C 644 | // language stdio system. This is largely based on overriding of the default 645 | // "system call functions" _write and _read. The default functions use "weak" 646 | // symbols. 647 | //////////////////////////////////////////////////////////////////////////////// 648 | 649 | /* 650 | * @brief Map file descriptor to ttys instance. 651 | * 652 | * @param[in] fd File descriptor. 653 | * 654 | * @return Instance ID corresponding to file instance, or TTYS_NUM_INSTANCES if 655 | * no match. 656 | */ 657 | static enum ttys_instance_id fd_to_instance(int fd) 658 | { 659 | enum ttys_instance_id instance_id = TTYS_NUM_INSTANCES; 660 | 661 | switch (fd) { 662 | case UART6_FD: 663 | instance_id = TTYS_INSTANCE_UART6; 664 | break; 665 | case UART1_FD: 666 | instance_id = TTYS_INSTANCE_UART1; 667 | break; 668 | case UART2_FD: 669 | instance_id = TTYS_INSTANCE_UART2; 670 | break; 671 | } 672 | return instance_id; 673 | } 674 | 675 | /* 676 | * @brief System call function for write(). 677 | * 678 | * @param[in] file File descriptor. 679 | * @param[in] ptr Data to be written. 680 | * @param[in] len Length of data. 681 | * 682 | * @return Number of characters written, or -1 for error. 683 | * 684 | * @note Characters might be dropped due to buffer overrun, and this is not 685 | * reflected the return value. An alternative would be to return -1 and 686 | * errno=EWOULDBLOCK. 687 | */ 688 | int _write(int file, char* ptr, int len) 689 | { 690 | int idx; 691 | enum ttys_instance_id instance_id = fd_to_instance(file); 692 | 693 | if (instance_id >= TTYS_NUM_INSTANCES) { 694 | errno = EBADF; 695 | return -1; 696 | } 697 | 698 | for (idx = 0; idx < len; idx++) { 699 | char c = *ptr++; 700 | ttys_putc(instance_id, c); 701 | if (c == '\n' && ttys_states[instance_id].cfg.send_cr_after_nl) { 702 | ttys_putc(instance_id, '\r'); 703 | } 704 | } 705 | return len; 706 | } 707 | 708 | /* 709 | * @brief System call function for read(). 710 | * 711 | * @param[in] file File descriptor. 712 | * @param[in] ptr Location of buffer to place characters. 713 | * @param[in] len Length of buffer. 714 | * 715 | * @return Number of characters written, or -1 for error. 716 | * 717 | * @note Assumes non-blocking operation. 718 | */ 719 | int _read(int file, char* ptr, int len) 720 | { 721 | int rc = 0; 722 | char c; 723 | enum ttys_instance_id instance_id = fd_to_instance(file); 724 | 725 | if (instance_id >= TTYS_NUM_INSTANCES) { 726 | errno = EBADF; 727 | return -1; 728 | } 729 | 730 | if (ttys_states[instance_id].rx_buf_get_idx == 731 | ttys_states[instance_id].rx_buf_put_idx) { 732 | errno = EAGAIN; 733 | rc = -1; 734 | } else { 735 | while (rc < len && ttys_getc(instance_id, &c)) { 736 | *ptr++ = c; 737 | rc++; 738 | } 739 | } 740 | return rc; 741 | } 742 | --------------------------------------------------------------------------------