Console with persistant storage.
This commit is contained in:
parent
debd87c17c
commit
ef14d63209
29
Core/Inc/NVmem.h
Normal file
29
Core/Inc/NVmem.h
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
/**
|
||||||
|
*********************************************************************
|
||||||
|
*
|
||||||
|
* @file NVmem.h
|
||||||
|
* @brief
|
||||||
|
*
|
||||||
|
* @date 2024-07-13 12:24:17
|
||||||
|
* @author CT
|
||||||
|
*
|
||||||
|
* @details
|
||||||
|
*
|
||||||
|
*************************************************************************
|
||||||
|
**/
|
||||||
|
|
||||||
|
#ifndef _NVMEM_H_
|
||||||
|
#define _NVMEM_H_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define WRITE_TIMEOUT (6000u) // 6s
|
||||||
|
#define NVMEM_SIZE (2048u) // in bytes
|
||||||
|
|
||||||
|
int32_t NVmem_init(void);
|
||||||
|
void NVmem_service(void);
|
||||||
|
int32_t NVmem_write(uint8_t* data, uint32_t addr, uint32_t len);
|
||||||
|
int32_t NVmem_write_immediate(uint8_t* data, uint32_t addr, uint32_t len);
|
||||||
|
int32_t NVmem_read(uint8_t* data, uint32_t addr, uint32_t len);
|
||||||
|
|
||||||
|
#endif // _NVMEM_H_
|
|
@ -16,6 +16,78 @@
|
||||||
#ifndef _CIR_BUF_H_
|
#ifndef _CIR_BUF_H_
|
||||||
#define _CIR_BUF_H_
|
#define _CIR_BUF_H_
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/// Opaque circular buffer structure
|
||||||
|
typedef struct circular_buf_t circular_buf_t;
|
||||||
|
|
||||||
|
/// Handle type, the way users interact with the API
|
||||||
|
typedef circular_buf_t* cbuf_handle_t;
|
||||||
|
|
||||||
|
/// Pass in a storage buffer and size, returns a circular buffer handle
|
||||||
|
/// Requires: buffer is not NULL, size > 0 (size > 1 for the threadsafe
|
||||||
|
// version, because it holds size - 1 elements)
|
||||||
|
/// Ensures: me has been created and is returned in an empty state
|
||||||
|
cbuf_handle_t circular_buf_init(uint8_t* buffer, size_t size);
|
||||||
|
|
||||||
|
/// Free a circular buffer structure
|
||||||
|
/// Requires: me is valid and created by circular_buf_init
|
||||||
|
/// Does not free data buffer; owner is responsible for that
|
||||||
|
void circular_buf_free(cbuf_handle_t me);
|
||||||
|
|
||||||
|
/// Reset the circular buffer to empty, head == tail. Data not cleared
|
||||||
|
/// Requires: me is valid and created by circular_buf_init
|
||||||
|
void circular_buf_reset(cbuf_handle_t me);
|
||||||
|
|
||||||
|
/// Put that continues to add data if the buffer is full
|
||||||
|
/// Old data is overwritten
|
||||||
|
/// Note: if you are using the threadsafe version, this API cannot be used, because
|
||||||
|
/// it modifies the tail pointer in some cases. Use circular_buf_try_put instead.
|
||||||
|
/// Requires: me is valid and created by circular_buf_init
|
||||||
|
void circular_buf_put(cbuf_handle_t me, uint8_t data);
|
||||||
|
|
||||||
|
/// Put that rejects new data if the buffer is full
|
||||||
|
/// Note: if you are using the threadsafe version, *this* is the put you should use
|
||||||
|
/// Requires: me is valid and created by circular_buf_init
|
||||||
|
/// Returns 0 on success, -1 if buffer is full
|
||||||
|
int circular_buf_try_put(cbuf_handle_t me, uint8_t data);
|
||||||
|
|
||||||
|
/// Retrieve a value from the buffer
|
||||||
|
/// Requires: me is valid and created by circular_buf_init
|
||||||
|
/// Returns 0 on success, -1 if the buffer is empty
|
||||||
|
int circular_buf_get(cbuf_handle_t me, uint8_t* data);
|
||||||
|
|
||||||
|
/// Checks if the buffer is empty
|
||||||
|
/// Requires: me is valid and created by circular_buf_init
|
||||||
|
/// Returns true if the buffer is empty
|
||||||
|
bool circular_buf_empty(cbuf_handle_t me);
|
||||||
|
|
||||||
|
/// Checks if the buffer is full
|
||||||
|
/// Requires: me is valid and created by circular_buf_init
|
||||||
|
/// Returns true if the buffer is full
|
||||||
|
bool circular_buf_full(cbuf_handle_t me);
|
||||||
|
|
||||||
|
/// Check the capacity of the buffer
|
||||||
|
/// Requires: me is valid and created by circular_buf_init
|
||||||
|
/// Returns the maximum capacity of the buffer
|
||||||
|
size_t circular_buf_capacity(cbuf_handle_t me);
|
||||||
|
|
||||||
|
/// Check the number of elements stored in the buffer
|
||||||
|
/// Requires: me is valid and created by circular_buf_init
|
||||||
|
/// Returns the current number of elements in the buffer
|
||||||
|
size_t circular_buf_size(cbuf_handle_t me);
|
||||||
|
|
||||||
|
/// Look ahead at values stored in the circular buffer without removing the data
|
||||||
|
/// Requires:
|
||||||
|
/// - me is valid and created by circular_buf_init
|
||||||
|
/// - look_ahead_counter is less than or equal to the value returned by circular_buf_size()
|
||||||
|
/// Returns 0 if successful, -1 if data is not available
|
||||||
|
int circular_buf_peek(cbuf_handle_t me, uint8_t* data, unsigned int look_ahead_counter);
|
||||||
|
|
||||||
|
// TODO: int circular_buf_get_range(circular_buf_t me, uint8_t *data, size_t len);
|
||||||
|
// TODO: int circular_buf_put_range(circular_buf_t me, uint8_t * data, size_t len);
|
||||||
|
|
||||||
|
|
||||||
#endif /* _CIR_BUF_H_ */
|
#endif /* _CIR_BUF_H_ */
|
25
Core/Inc/console.h
Normal file
25
Core/Inc/console.h
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/**
|
||||||
|
*********************************************************************
|
||||||
|
*
|
||||||
|
* @file console.h
|
||||||
|
* @brief
|
||||||
|
*
|
||||||
|
* @date 2024-04-14 19:12:40
|
||||||
|
* @author CT
|
||||||
|
*
|
||||||
|
* @details
|
||||||
|
*
|
||||||
|
*************************************************************************
|
||||||
|
**/
|
||||||
|
|
||||||
|
#ifndef _CONSOLE_H_
|
||||||
|
#define _CONSOLE_H_
|
||||||
|
|
||||||
|
#include "cir_buf.h"
|
||||||
|
|
||||||
|
void console_init(void);
|
||||||
|
void console_service(void);
|
||||||
|
int console_push(uint8_t data);
|
||||||
|
|
||||||
|
|
||||||
|
#endif // _CONOSLE_H_
|
11
Core/Inc/version.h
Normal file
11
Core/Inc/version.h
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
#ifndef _VERSION_H_
|
||||||
|
#define _VERSION_H_
|
||||||
|
|
||||||
|
#define MAJOR_VERSION 0
|
||||||
|
#define MINOR_VERSION 0
|
||||||
|
#define PATCH_VERSION 1
|
||||||
|
|
||||||
|
#define VERSION_STR "0.0.1"
|
||||||
|
|
||||||
|
|
||||||
|
#endif // _VERSION_H_
|
176
Core/Src/NVmem.c
Normal file
176
Core/Src/NVmem.c
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
/**
|
||||||
|
*********************************************************************
|
||||||
|
*
|
||||||
|
* @file NVmem.c
|
||||||
|
* @brief
|
||||||
|
*
|
||||||
|
* @date 2024-07-13 12:24:17
|
||||||
|
* @author CT
|
||||||
|
*
|
||||||
|
* @details Provides an interface for reading and writting to non-volatile memory
|
||||||
|
* Operates with a shadow buffer and write timeout to minimize writes to NV-memory
|
||||||
|
*
|
||||||
|
*************************************************************************
|
||||||
|
**/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "stm32g0xx_hal.h"
|
||||||
|
|
||||||
|
#include "NVmem.h"
|
||||||
|
|
||||||
|
// write timeout
|
||||||
|
static uint32_t m_last_write = 0;
|
||||||
|
//
|
||||||
|
static int32_t m_shadow_buf_synced = 0;
|
||||||
|
//
|
||||||
|
static uint8_t m_shadow_buf[NVMEM_SIZE] = {0};
|
||||||
|
// non-volatile memory location
|
||||||
|
uint8_t __attribute__((section (".ConfigData"))) config_data[NVMEM_SIZE] __attribute__ ((aligned (2048)));
|
||||||
|
|
||||||
|
|
||||||
|
static int write_flash(void);
|
||||||
|
|
||||||
|
int32_t NVmem_init(void)
|
||||||
|
{
|
||||||
|
// copy flash to shadow buffer
|
||||||
|
for (uint32_t i = 0; i < NVMEM_SIZE; i++)
|
||||||
|
{
|
||||||
|
m_shadow_buf[i] = config_data[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NVmem_service(void)
|
||||||
|
{
|
||||||
|
// if the write timeout has expried and the shadow buffer has not been synced to NVmem
|
||||||
|
if (((HAL_GetTick() - m_last_write) >= WRITE_TIMEOUT) && (!m_shadow_buf_synced))
|
||||||
|
{
|
||||||
|
// write shadow buffer to nvmem
|
||||||
|
write_flash();
|
||||||
|
|
||||||
|
// set synced flag
|
||||||
|
m_shadow_buf_synced = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int32_t NVmem_write(uint8_t* data, uint32_t addr, uint32_t len)
|
||||||
|
{
|
||||||
|
// bound check
|
||||||
|
if (addr + len >= NVMEM_SIZE)
|
||||||
|
return (-1);
|
||||||
|
|
||||||
|
// write data to shadow buf
|
||||||
|
uint32_t data_index = 0;
|
||||||
|
for (uint32_t i = addr; i < len; i++)
|
||||||
|
{
|
||||||
|
m_shadow_buf[i] = data[data_index++];
|
||||||
|
}
|
||||||
|
|
||||||
|
// set sync flag to out of sync
|
||||||
|
m_shadow_buf_synced = 0;
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int32_t NVmem_write_immediate(uint8_t* data, uint32_t addr, uint32_t len)
|
||||||
|
{
|
||||||
|
int32_t ret = 0;
|
||||||
|
|
||||||
|
// bound check
|
||||||
|
if (addr + len >= NVMEM_SIZE)
|
||||||
|
return (-1);
|
||||||
|
|
||||||
|
// write data to shadow buf
|
||||||
|
uint32_t data_index = 0;
|
||||||
|
for (uint32_t i = addr; i < len; i++)
|
||||||
|
{
|
||||||
|
m_shadow_buf[i] = data[data_index++];
|
||||||
|
}
|
||||||
|
|
||||||
|
// write shadow buffer to nvmem
|
||||||
|
write_flash();
|
||||||
|
|
||||||
|
// set synced flag
|
||||||
|
m_shadow_buf_synced = 1;
|
||||||
|
|
||||||
|
return (ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int32_t NVmem_read(uint8_t* data, uint32_t addr, uint32_t len)
|
||||||
|
{
|
||||||
|
// bound check
|
||||||
|
if (addr + len >= NVMEM_SIZE)
|
||||||
|
return (-1);
|
||||||
|
|
||||||
|
// copy data using provided pointer
|
||||||
|
uint32_t index = 0;
|
||||||
|
for (uint32_t i = addr; i < len; i++)
|
||||||
|
{
|
||||||
|
data[index++] = m_shadow_buf[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PRIVATE FUNCTIONS */
|
||||||
|
|
||||||
|
// Read from flash
|
||||||
|
|
||||||
|
|
||||||
|
// Write to flash
|
||||||
|
int write_flash(void)
|
||||||
|
{
|
||||||
|
uint32_t page_error = 0;
|
||||||
|
HAL_StatusTypeDef status;
|
||||||
|
|
||||||
|
HAL_FLASH_Unlock();
|
||||||
|
|
||||||
|
// Erase flash area
|
||||||
|
FLASH_EraseInitTypeDef erase_init_struct;
|
||||||
|
|
||||||
|
erase_init_struct.TypeErase = FLASH_TYPEERASE_PAGES;
|
||||||
|
erase_init_struct.Banks = FLASH_BANK_1;
|
||||||
|
erase_init_struct.Page = (0x801F800 - FLASH_BASE) / FLASH_PAGE_SIZE;
|
||||||
|
erase_init_struct.NbPages = 1;
|
||||||
|
|
||||||
|
status = HAL_FLASHEx_Erase(&erase_init_struct, &page_error);
|
||||||
|
|
||||||
|
if (HAL_OK != status)
|
||||||
|
{
|
||||||
|
HAL_FLASH_Lock();
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t buf;
|
||||||
|
|
||||||
|
uint32_t len = NVMEM_SIZE;
|
||||||
|
uint32_t prog_addr = 0x801F800;
|
||||||
|
uint32_t index = 0;
|
||||||
|
|
||||||
|
while (len >= 8)
|
||||||
|
{
|
||||||
|
buf = (uint64_t)m_shadow_buf[index];
|
||||||
|
buf |= ((uint64_t)m_shadow_buf[index + 1] << 8);
|
||||||
|
buf |= ((uint64_t)m_shadow_buf[index + 2] << 16);
|
||||||
|
buf |= ((uint64_t)m_shadow_buf[index + 3] << 24);
|
||||||
|
buf |= ((uint64_t)m_shadow_buf[index + 4] << 32);
|
||||||
|
buf |= ((uint64_t)m_shadow_buf[index + 5] << 40);
|
||||||
|
buf |= ((uint64_t)m_shadow_buf[index + 6] << 48);
|
||||||
|
buf |= ((uint64_t)m_shadow_buf[index + 7] << 56);
|
||||||
|
|
||||||
|
status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, prog_addr, buf);
|
||||||
|
|
||||||
|
index += 8;
|
||||||
|
prog_addr += 8;
|
||||||
|
len -= 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
HAL_FLASH_Lock();
|
||||||
|
|
||||||
|
return ((int)status);
|
||||||
|
}
|
|
@ -13,6 +13,178 @@
|
||||||
*************************************************************************
|
*************************************************************************
|
||||||
**/
|
**/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
#include "cir_buf.h"
|
#include "cir_buf.h"
|
||||||
|
|
||||||
|
// The definition of our circular buffer structure is hidden from the user
|
||||||
|
struct circular_buf_t
|
||||||
|
{
|
||||||
|
uint8_t* buffer;
|
||||||
|
size_t head;
|
||||||
|
size_t tail;
|
||||||
|
size_t max; // of the buffer
|
||||||
|
bool full;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* PRIVATE FUNCTIONS */
|
||||||
|
static inline size_t advance_headtail_value(size_t value, size_t max)
|
||||||
|
{
|
||||||
|
return (value + 1) % max;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void advance_head_pointer(cbuf_handle_t me)
|
||||||
|
{
|
||||||
|
assert(me);
|
||||||
|
|
||||||
|
if(circular_buf_full(me))
|
||||||
|
{
|
||||||
|
me->tail = advance_headtail_value(me->tail, me->max);
|
||||||
|
}
|
||||||
|
|
||||||
|
me->head = advance_headtail_value(me->head, me->max);
|
||||||
|
me->full = (me->head == me->tail);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* API FUNCTIONS */
|
||||||
|
|
||||||
|
cbuf_handle_t circular_buf_init(uint8_t* buffer, size_t size)
|
||||||
|
{
|
||||||
|
assert(buffer && size);
|
||||||
|
|
||||||
|
cbuf_handle_t cbuf = malloc(sizeof(circular_buf_t));
|
||||||
|
assert(cbuf);
|
||||||
|
|
||||||
|
cbuf->buffer = buffer;
|
||||||
|
cbuf->max = size;
|
||||||
|
circular_buf_reset(cbuf);
|
||||||
|
|
||||||
|
assert(circular_buf_empty(cbuf));
|
||||||
|
|
||||||
|
return cbuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
void circular_buf_free(cbuf_handle_t me)
|
||||||
|
{
|
||||||
|
assert(me);
|
||||||
|
free(me);
|
||||||
|
}
|
||||||
|
|
||||||
|
void circular_buf_reset(cbuf_handle_t me)
|
||||||
|
{
|
||||||
|
assert(me);
|
||||||
|
|
||||||
|
me->head = 0;
|
||||||
|
me->tail = 0;
|
||||||
|
me->full = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t circular_buf_size(cbuf_handle_t me)
|
||||||
|
{
|
||||||
|
assert(me);
|
||||||
|
|
||||||
|
size_t size = me->max;
|
||||||
|
|
||||||
|
if(!circular_buf_full(me))
|
||||||
|
{
|
||||||
|
if(me->head >= me->tail)
|
||||||
|
{
|
||||||
|
size = (me->head - me->tail);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
size = (me->max + me->head - me->tail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t circular_buf_capacity(cbuf_handle_t me)
|
||||||
|
{
|
||||||
|
assert(me);
|
||||||
|
|
||||||
|
return me->max;
|
||||||
|
}
|
||||||
|
|
||||||
|
void circular_buf_put(cbuf_handle_t me, uint8_t data)
|
||||||
|
{
|
||||||
|
assert(me && me->buffer);
|
||||||
|
|
||||||
|
me->buffer[me->head] = data;
|
||||||
|
|
||||||
|
advance_head_pointer(me);
|
||||||
|
}
|
||||||
|
|
||||||
|
int circular_buf_try_put(cbuf_handle_t me, uint8_t data)
|
||||||
|
{
|
||||||
|
int r = -1;
|
||||||
|
|
||||||
|
assert(me && me->buffer);
|
||||||
|
|
||||||
|
if(!circular_buf_full(me))
|
||||||
|
{
|
||||||
|
me->buffer[me->head] = data;
|
||||||
|
advance_head_pointer(me);
|
||||||
|
r = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
int circular_buf_get(cbuf_handle_t me, uint8_t* data)
|
||||||
|
{
|
||||||
|
assert(me && data && me->buffer);
|
||||||
|
|
||||||
|
int r = -1;
|
||||||
|
|
||||||
|
if(!circular_buf_empty(me))
|
||||||
|
{
|
||||||
|
*data = me->buffer[me->tail];
|
||||||
|
me->tail = advance_headtail_value(me->tail, me->max);
|
||||||
|
me->full = false;
|
||||||
|
r = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool circular_buf_empty(cbuf_handle_t me)
|
||||||
|
{
|
||||||
|
assert(me);
|
||||||
|
|
||||||
|
return (!circular_buf_full(me) && (me->head == me->tail));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool circular_buf_full(cbuf_handle_t me)
|
||||||
|
{
|
||||||
|
assert(me);
|
||||||
|
|
||||||
|
return me->full;
|
||||||
|
}
|
||||||
|
|
||||||
|
int circular_buf_peek(cbuf_handle_t me, uint8_t* data, unsigned int look_ahead_counter)
|
||||||
|
{
|
||||||
|
int r = -1;
|
||||||
|
size_t pos;
|
||||||
|
|
||||||
|
assert(me && data && me->buffer);
|
||||||
|
|
||||||
|
// We can't look beyond the current buffer size
|
||||||
|
if(circular_buf_empty(me) || look_ahead_counter > circular_buf_size(me))
|
||||||
|
{
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
pos = me->tail;
|
||||||
|
for(unsigned int i = 0; i < look_ahead_counter; i++)
|
||||||
|
{
|
||||||
|
data[i] = me->buffer[pos];
|
||||||
|
pos = advance_headtail_value(pos, me->max);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
444
Core/Src/console.c
Normal file
444
Core/Src/console.c
Normal file
|
@ -0,0 +1,444 @@
|
||||||
|
/**
|
||||||
|
*********************************************************************
|
||||||
|
*
|
||||||
|
* @file console.c
|
||||||
|
* @brief
|
||||||
|
*
|
||||||
|
* @date 2024-04-14 19:12:40
|
||||||
|
* @author CT
|
||||||
|
*
|
||||||
|
* @details
|
||||||
|
*
|
||||||
|
*************************************************************************
|
||||||
|
**/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Operational Concept:
|
||||||
|
Sending enter displays the current menu
|
||||||
|
Simple 1 char command interface
|
||||||
|
*/
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "console.h"
|
||||||
|
#include "NVmem.h"
|
||||||
|
#include "version.h"
|
||||||
|
|
||||||
|
#include "usbd_cdc_if.h"
|
||||||
|
|
||||||
|
|
||||||
|
#define CONSOLE_BUF_SIZE (256u)
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct config_data
|
||||||
|
{
|
||||||
|
int32_t val_1;
|
||||||
|
int32_t val_2;
|
||||||
|
int32_t val_3;
|
||||||
|
} config_data_t;
|
||||||
|
|
||||||
|
typedef union config_data_u
|
||||||
|
{
|
||||||
|
config_data_t config;
|
||||||
|
uint8_t data[12];
|
||||||
|
} config_data_u_t;
|
||||||
|
|
||||||
|
static cbuf_handle_t m_console_input;
|
||||||
|
static cbuf_handle_t m_console_output;
|
||||||
|
static uint8_t m_console_input_buf[CONSOLE_BUF_SIZE] = {0};
|
||||||
|
static uint8_t m_console_output_buf[CONSOLE_BUF_SIZE] = {0};
|
||||||
|
static uint8_t tx_buf[CONSOLE_BUF_SIZE];
|
||||||
|
static uint8_t m_command_buf[16] = {0};
|
||||||
|
static uint8_t m_command_len = 0;
|
||||||
|
static bool m_command_ready = false;
|
||||||
|
static bool m_echo_console = false;
|
||||||
|
|
||||||
|
// static int32_t submenu_value_one = 0;
|
||||||
|
// static int32_t submenu_value_two = 0;
|
||||||
|
// static int32_t submenu_value_three = 0;
|
||||||
|
config_data_u_t configuration_data;
|
||||||
|
|
||||||
|
enum console_menu_state{main_menu, submenu_1, submenu_2, submenu_3, save_menu, version_menu};
|
||||||
|
enum console_menu_state console_menu = main_menu;
|
||||||
|
|
||||||
|
|
||||||
|
static void process_incoming(void);
|
||||||
|
static void process_outgoing(void);
|
||||||
|
static void process_command(void);
|
||||||
|
static int console_send(uint8_t* buf, uint32_t len);
|
||||||
|
static void menu_state_machine(void);
|
||||||
|
static int parse_input(int32_t* data);
|
||||||
|
static int32_t read_line(uint8_t data);
|
||||||
|
|
||||||
|
static void print_main_menu(void);
|
||||||
|
static void print_submenu_one(void);
|
||||||
|
static void print_submenu_two(void);
|
||||||
|
static void print_submenu_three(void);
|
||||||
|
static void print_saved_menu(int32_t val);
|
||||||
|
|
||||||
|
|
||||||
|
static void set_console_reset(void);
|
||||||
|
static void set_console_red(void);
|
||||||
|
static void set_console_green(void);
|
||||||
|
static void reset_console(void);
|
||||||
|
|
||||||
|
|
||||||
|
/* PUBLIC FUNCTIONS */
|
||||||
|
|
||||||
|
void console_init(void)
|
||||||
|
{
|
||||||
|
m_console_input = circular_buf_init(m_console_input_buf, CONSOLE_BUF_SIZE);
|
||||||
|
m_console_output = circular_buf_init(m_console_output_buf, CONSOLE_BUF_SIZE);
|
||||||
|
|
||||||
|
// for this demonstratior, load example values from nvmem
|
||||||
|
NVmem_read(configuration_data.data, 0, 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void console_service(void)
|
||||||
|
{
|
||||||
|
// if there is received data waiting
|
||||||
|
process_incoming();
|
||||||
|
|
||||||
|
// if there is data waiting to be transmitted
|
||||||
|
process_outgoing();
|
||||||
|
|
||||||
|
// Process command
|
||||||
|
process_command();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @brief Attempts to push one byte into the console input buffer
|
||||||
|
/// @param data byte to be pushed to input
|
||||||
|
/// @return 0 on success, -1 on failure
|
||||||
|
int console_push(uint8_t data)
|
||||||
|
{
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
ret = circular_buf_try_put(m_console_input, data);
|
||||||
|
|
||||||
|
return (ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* PRIVATE FUNCTIONS */
|
||||||
|
|
||||||
|
void process_incoming(void)
|
||||||
|
{
|
||||||
|
if (!circular_buf_empty(m_console_input))
|
||||||
|
{
|
||||||
|
uint8_t data;
|
||||||
|
while (!circular_buf_empty(m_console_input))
|
||||||
|
{
|
||||||
|
circular_buf_get(m_console_input, &data);
|
||||||
|
m_command_ready = read_line(data);
|
||||||
|
|
||||||
|
if (m_echo_console)
|
||||||
|
{
|
||||||
|
circular_buf_try_put(m_console_output, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void process_outgoing(void)
|
||||||
|
{
|
||||||
|
if(!circular_buf_empty(m_console_output))
|
||||||
|
{
|
||||||
|
size_t tx_len = circular_buf_size(m_console_output);
|
||||||
|
for (uint32_t i = 0; i < tx_len; i++)
|
||||||
|
{
|
||||||
|
circular_buf_get(m_console_output, &tx_buf[i]);
|
||||||
|
}
|
||||||
|
// Note: directly interacts with interface
|
||||||
|
CDC_Transmit_FS(tx_buf, tx_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void process_command(void)
|
||||||
|
{
|
||||||
|
if(m_command_ready)
|
||||||
|
{
|
||||||
|
m_command_ready = false;
|
||||||
|
// for(uint32_t i = 0; i < m_command_len; i++)
|
||||||
|
// {
|
||||||
|
// circular_buf_put(m_console_output, m_command_buf[i]);
|
||||||
|
// }
|
||||||
|
menu_state_machine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int console_send(uint8_t* buf, uint32_t len)
|
||||||
|
{
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
ret |= circular_buf_try_put(m_console_output, buf[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @brief Reads in a line terminated by \\n
|
||||||
|
/// @param data
|
||||||
|
/// @return 1 if EOL found, 0 otherwise
|
||||||
|
int32_t read_line(uint8_t data)
|
||||||
|
{
|
||||||
|
int32_t ret = 0;
|
||||||
|
static uint8_t m_command_buf_index = 0;
|
||||||
|
|
||||||
|
// if EOL, return on and set length
|
||||||
|
if((data == '\n') || (data == '\r'))
|
||||||
|
{
|
||||||
|
m_command_buf[m_command_buf_index++] = '\n';
|
||||||
|
// set length of command for use within rest of module
|
||||||
|
m_command_len = m_command_buf_index;
|
||||||
|
// reset index
|
||||||
|
m_command_buf_index = 0;
|
||||||
|
|
||||||
|
ret = 1;
|
||||||
|
}
|
||||||
|
else if (data == 0x1B) // ESCAPE
|
||||||
|
{
|
||||||
|
m_command_len = 1;
|
||||||
|
m_command_buf[0] = 0x1B;
|
||||||
|
// reset index
|
||||||
|
m_command_buf_index = 0;
|
||||||
|
ret = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (m_command_buf_index < 16)
|
||||||
|
{
|
||||||
|
m_command_buf[m_command_buf_index++] = data;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_command_buf_index = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Called when a complete line has been received
|
||||||
|
void menu_state_machine(void)
|
||||||
|
{
|
||||||
|
int32_t success = 0;
|
||||||
|
|
||||||
|
switch (console_menu)
|
||||||
|
{
|
||||||
|
case main_menu:
|
||||||
|
if (m_command_buf[0] == '1')
|
||||||
|
{
|
||||||
|
print_submenu_one();
|
||||||
|
console_menu = submenu_1;
|
||||||
|
}
|
||||||
|
else if (m_command_buf[0] == '2')
|
||||||
|
{
|
||||||
|
print_submenu_two();
|
||||||
|
console_menu = submenu_2;
|
||||||
|
}
|
||||||
|
else if (m_command_buf[0] == '3')
|
||||||
|
{
|
||||||
|
print_submenu_three();
|
||||||
|
console_menu = submenu_3;
|
||||||
|
}
|
||||||
|
else if (m_command_buf[0] == 'S')
|
||||||
|
{
|
||||||
|
success = NVmem_write_immediate(configuration_data.data, 0, 12);
|
||||||
|
print_saved_menu(success);
|
||||||
|
// need to call service because we are blocking all normal execution here
|
||||||
|
process_outgoing();
|
||||||
|
|
||||||
|
// Delay for user to read result
|
||||||
|
HAL_Delay(3000);
|
||||||
|
|
||||||
|
console_menu = main_menu;
|
||||||
|
print_main_menu();
|
||||||
|
}
|
||||||
|
else// if (m_command_buf[0] == '\n')
|
||||||
|
{
|
||||||
|
print_main_menu();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case submenu_1:
|
||||||
|
if (m_command_buf[0] == 0x1B) // ESCAPE
|
||||||
|
{
|
||||||
|
console_menu = main_menu;
|
||||||
|
print_main_menu();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Take input and immediately return to main menu
|
||||||
|
parse_input(&configuration_data.config.val_1);
|
||||||
|
console_menu = main_menu;
|
||||||
|
print_main_menu();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case submenu_2:
|
||||||
|
if (m_command_buf[0] == 0x1B) // ESCAPE
|
||||||
|
{
|
||||||
|
console_menu = main_menu;
|
||||||
|
print_main_menu();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
parse_input(&configuration_data.config.val_2);
|
||||||
|
console_menu = main_menu;
|
||||||
|
print_main_menu();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case submenu_3:
|
||||||
|
if (m_command_buf[0] == 0x1B) // ESCAPE
|
||||||
|
{
|
||||||
|
console_menu = main_menu;
|
||||||
|
print_main_menu();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
parse_input(&configuration_data.config.val_3);
|
||||||
|
console_menu = main_menu;
|
||||||
|
print_main_menu();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case save_menu:
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @brief Parses line for integer
|
||||||
|
/// @param data parsed out integer
|
||||||
|
/// @return 1 on success, otherwise fail...kinda
|
||||||
|
int parse_input(int32_t* data)
|
||||||
|
{
|
||||||
|
// *data = atoi((char*)m_command_buf);
|
||||||
|
int ret = sscanf((char*)m_command_buf, "%ld", data);
|
||||||
|
|
||||||
|
return (ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also acts as initialization for state
|
||||||
|
void print_main_menu(void)
|
||||||
|
{
|
||||||
|
char buf[128];
|
||||||
|
|
||||||
|
m_echo_console = true;
|
||||||
|
|
||||||
|
reset_console();
|
||||||
|
set_console_red();
|
||||||
|
|
||||||
|
uint32_t len = snprintf(buf, 128, "Main Menu:\r\n[1]Option 1: \"%li\"\r\n[2]Option 2: \"%li\"\r\n[3]Option 3: \"%li\"\r\n[S]ave\r\nVersion: %s\r\nEnter Selection: ",
|
||||||
|
configuration_data.config.val_1, configuration_data.config.val_2, configuration_data.config.val_3, VERSION_STR);
|
||||||
|
console_send((uint8_t*)buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_submenu_one(void)
|
||||||
|
{
|
||||||
|
char buf[128];
|
||||||
|
|
||||||
|
m_echo_console = true;
|
||||||
|
|
||||||
|
reset_console();
|
||||||
|
set_console_reset();
|
||||||
|
|
||||||
|
uint32_t len = snprintf(buf, 128, "Submenu 1:\r\n");
|
||||||
|
console_send((uint8_t*)buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void print_submenu_two(void)
|
||||||
|
{
|
||||||
|
char buf[128];
|
||||||
|
|
||||||
|
m_echo_console = true;
|
||||||
|
|
||||||
|
reset_console();
|
||||||
|
set_console_reset();
|
||||||
|
|
||||||
|
uint32_t len = snprintf(buf, 128, "Submenu 2:\r\n");
|
||||||
|
console_send((uint8_t*)buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void print_submenu_three(void)
|
||||||
|
{
|
||||||
|
char buf[128];
|
||||||
|
|
||||||
|
m_echo_console = true;
|
||||||
|
|
||||||
|
reset_console();
|
||||||
|
set_console_reset();
|
||||||
|
|
||||||
|
uint32_t len = snprintf(buf, 128, "Submenu 3:\r\n");
|
||||||
|
console_send((uint8_t*)buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void print_saved_menu(int32_t val)
|
||||||
|
{
|
||||||
|
char buf[128];
|
||||||
|
uint32_t len;
|
||||||
|
|
||||||
|
reset_console();
|
||||||
|
set_console_green();
|
||||||
|
|
||||||
|
if (0 == val)
|
||||||
|
{
|
||||||
|
len = snprintf(buf, 128, "SAVED!!1!");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
len = snprintf(buf, 128, "SAVE FAILED!");
|
||||||
|
}
|
||||||
|
|
||||||
|
console_send((uint8_t*)buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void set_console_reset(void)
|
||||||
|
{
|
||||||
|
char buf[16] = {0};
|
||||||
|
uint32_t len = snprintf(buf, 16, "\x1b[1;0m");
|
||||||
|
console_send((uint8_t*)buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void set_console_red(void)
|
||||||
|
{
|
||||||
|
char buf[16] = {0};
|
||||||
|
uint32_t len = snprintf(buf, 16, "\x1b[1;31m");
|
||||||
|
console_send((uint8_t*)buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void set_console_green(void)
|
||||||
|
{
|
||||||
|
char buf[16] = {0};
|
||||||
|
uint32_t len = snprintf(buf, 16, "\x1b[1;32m");
|
||||||
|
console_send((uint8_t*)buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void reset_console(void)
|
||||||
|
{
|
||||||
|
char buf[16] = {0};
|
||||||
|
|
||||||
|
uint32_t len = snprintf(buf, 16, "\x1b[2J\x1b[H");
|
||||||
|
console_send((uint8_t*)buf, len);
|
||||||
|
}
|
|
@ -30,9 +30,11 @@
|
||||||
|
|
||||||
/* Private includes ----------------------------------------------------------*/
|
/* Private includes ----------------------------------------------------------*/
|
||||||
/* USER CODE BEGIN Includes */
|
/* USER CODE BEGIN Includes */
|
||||||
|
#include "console.h"
|
||||||
#include "usbd_cdc_if.h"
|
#include "usbd_cdc_if.h"
|
||||||
#include "sht_40.h"
|
#include "sht_40.h"
|
||||||
#include "IS66.h"
|
#include "IS66.h"
|
||||||
|
#include "NVmem.h"
|
||||||
/* USER CODE END Includes */
|
/* USER CODE END Includes */
|
||||||
|
|
||||||
/* Private typedef -----------------------------------------------------------*/
|
/* Private typedef -----------------------------------------------------------*/
|
||||||
|
@ -53,7 +55,7 @@
|
||||||
/* Private variables ---------------------------------------------------------*/
|
/* Private variables ---------------------------------------------------------*/
|
||||||
|
|
||||||
/* USER CODE BEGIN PV */
|
/* USER CODE BEGIN PV */
|
||||||
char buf[256] = {0};
|
|
||||||
/* USER CODE END PV */
|
/* USER CODE END PV */
|
||||||
|
|
||||||
/* Private function prototypes -----------------------------------------------*/
|
/* Private function prototypes -----------------------------------------------*/
|
||||||
|
@ -116,6 +118,12 @@ int main(void)
|
||||||
// Turn on SD Card power, active low
|
// Turn on SD Card power, active low
|
||||||
HAL_GPIO_WritePin(SD_PWR_EN_GPIO_Port, SD_PWR_EN_Pin, GPIO_PIN_RESET);
|
HAL_GPIO_WritePin(SD_PWR_EN_GPIO_Port, SD_PWR_EN_Pin, GPIO_PIN_RESET);
|
||||||
|
|
||||||
|
NVmem_init();
|
||||||
|
// uint8_t buf[4] = {0xEF, 0xBE, 0xAD, 0xDE};
|
||||||
|
// NVmem_write(buf, 0, 4);
|
||||||
|
|
||||||
|
console_init();
|
||||||
|
|
||||||
static uint32_t prev_tick = 0;
|
static uint32_t prev_tick = 0;
|
||||||
|
|
||||||
if (HAL_OK == sht40_init(&hi2c1))
|
if (HAL_OK == sht40_init(&hi2c1))
|
||||||
|
@ -137,12 +145,22 @@ int main(void)
|
||||||
prev_tick = tick;
|
prev_tick = tick;
|
||||||
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
|
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
|
||||||
|
|
||||||
sht40_read();
|
// sht40_read();
|
||||||
temp = sht40_get_temp_C();
|
// temp = sht40_get_temp_C();
|
||||||
rh = sht40_get_rh();
|
// float temp_f = sht40_get_temp_F();
|
||||||
len = snprintf(buf, 128, "Temp: %03.2fC\tRH: %02.1f\r\n", temp, rh);
|
// rh = sht40_get_rh();
|
||||||
CDC_Transmit_FS((uint8_t *)buf, len);
|
// len = snprintf(buf, 128, "Temp: %03.2fC\t %03.2fF\r\n", temp, temp_f);
|
||||||
|
// len += snprintf((buf + len), (128 - len), "RH: %02.1f\r\n", rh);
|
||||||
|
// // CDC_Transmit_FS((uint8_t *)buf, len);
|
||||||
|
// for (uint32_t i = 0; i < len; i++)
|
||||||
|
// {
|
||||||
|
// console_push(buf[i]);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NVmem_service();
|
||||||
|
console_service();
|
||||||
|
|
||||||
/* USER CODE END WHILE */
|
/* USER CODE END WHILE */
|
||||||
|
|
||||||
/* USER CODE BEGIN 3 */
|
/* USER CODE BEGIN 3 */
|
||||||
|
|
|
@ -62,12 +62,19 @@ _Min_Stack_Size = 0x400; /* required amount of stack */
|
||||||
MEMORY
|
MEMORY
|
||||||
{
|
{
|
||||||
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 144K
|
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 144K
|
||||||
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 128K
|
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 126K
|
||||||
|
CONFIG (r) : ORIGIN = 0x801F800, LENGTH = 2K
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Define output sections */
|
/* Define output sections */
|
||||||
SECTIONS
|
SECTIONS
|
||||||
{
|
{
|
||||||
|
/* Application NV configuration data */
|
||||||
|
.ConfigData (NOLOAD) :
|
||||||
|
{
|
||||||
|
KEEP(*(.ConfigData))
|
||||||
|
} >CONFIG
|
||||||
|
|
||||||
/* The startup code goes first into FLASH */
|
/* The startup code goes first into FLASH */
|
||||||
.isr_vector :
|
.isr_vector :
|
||||||
{
|
{
|
||||||
|
@ -134,7 +141,7 @@ SECTIONS
|
||||||
_sidata = LOADADDR(.data);
|
_sidata = LOADADDR(.data);
|
||||||
|
|
||||||
/* Initialized data sections goes into RAM, load LMA copy after code */
|
/* Initialized data sections goes into RAM, load LMA copy after code */
|
||||||
.data :
|
.data :
|
||||||
{
|
{
|
||||||
. = ALIGN(4);
|
. = ALIGN(4);
|
||||||
_sdata = .; /* create a global symbol at data start */
|
_sdata = .; /* create a global symbol at data start */
|
||||||
|
@ -145,7 +152,7 @@ SECTIONS
|
||||||
_edata = .; /* define a global symbol at data end */
|
_edata = .; /* define a global symbol at data end */
|
||||||
} >RAM AT> FLASH
|
} >RAM AT> FLASH
|
||||||
|
|
||||||
|
|
||||||
/* Uninitialized data section */
|
/* Uninitialized data section */
|
||||||
. = ALIGN(4);
|
. = ALIGN(4);
|
||||||
.bss :
|
.bss :
|
||||||
|
@ -173,7 +180,7 @@ SECTIONS
|
||||||
. = ALIGN(8);
|
. = ALIGN(8);
|
||||||
} >RAM
|
} >RAM
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Remove information from the standard libraries */
|
/* Remove information from the standard libraries */
|
||||||
/DISCARD/ :
|
/DISCARD/ :
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
#include "usbd_cdc_if.h"
|
#include "usbd_cdc_if.h"
|
||||||
|
|
||||||
/* USER CODE BEGIN INCLUDE */
|
/* USER CODE BEGIN INCLUDE */
|
||||||
|
#include "console.h"
|
||||||
/* USER CODE END INCLUDE */
|
/* USER CODE END INCLUDE */
|
||||||
|
|
||||||
/* Private typedef -----------------------------------------------------------*/
|
/* Private typedef -----------------------------------------------------------*/
|
||||||
|
@ -263,8 +263,11 @@ static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
|
||||||
/* USER CODE BEGIN 6 */
|
/* USER CODE BEGIN 6 */
|
||||||
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
|
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
|
||||||
USBD_CDC_ReceivePacket(&hUsbDeviceFS);
|
USBD_CDC_ReceivePacket(&hUsbDeviceFS);
|
||||||
// TODO add USB received handler here
|
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < *Len; i++)
|
||||||
|
{
|
||||||
|
console_push(Buf[i]);
|
||||||
|
}
|
||||||
return (USBD_OK);
|
return (USBD_OK);
|
||||||
/* USER CODE END 6 */
|
/* USER CODE END 6 */
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,9 +88,11 @@ target_sources(stm32cubemx INTERFACE
|
||||||
../../Core/Src/sysmem.c
|
../../Core/Src/sysmem.c
|
||||||
../../Core/Src/syscalls.c
|
../../Core/Src/syscalls.c
|
||||||
../../startup_stm32g0b1xx.s
|
../../startup_stm32g0b1xx.s
|
||||||
|
../../Core/Src/console.c
|
||||||
../../Core/Src/cir_buf.c
|
../../Core/Src/cir_buf.c
|
||||||
../../Core/Src/data_table.c
|
../../Core/Src/data_table.c
|
||||||
../../Core/Src/IS66.c
|
../../Core/Src/IS66.c
|
||||||
|
../../Core/Src/NVmem.c
|
||||||
../../Core/Src/sht_40.c
|
../../Core/Src/sht_40.c
|
||||||
../../Core/Src/testmode.c
|
../../Core/Src/testmode.c
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user