Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ext] Adding ext json library and example #1146

Merged
merged 4 commits into from
May 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,6 @@
[submodule "ext/arm/cmsis-dsp"]
path = ext/arm/cmsis-dsp
url = https://github.com/modm-ext/cmsis-dsp-partial.git
[submodule "ext/nlohmann/json"]
path = ext/nlohmann/json
url = git@github.com:modm-ext/json-partial.git
194 changes: 194 additions & 0 deletions examples/nucleo_g474re/flash_json/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
/*
* Copyright (c) 2024, Henrik Hose
*
* This file is part of the modm project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

#include <modm/board.hpp>
#include <modm/debug/logger.hpp>
#include <nlohmann-json/json.hpp>

#undef MODM_LOG_LEVEL
#define MODM_LOG_LEVEL modm::log::INFO

// ----------------------------------------------------------------------------

extern "C" const uint32_t __flash_reserved_start[];

using json = nlohmann::json;

constexpr auto max_flash_pages{256};

namespace data
{
struct person
{
std::string name;
float height;
int age;
auto
operator<=>(const person&) const = default;
};

void
to_json(json& j, const person& p)
{
j = json{{"name", p.name}, {"height", p.height}, {"age", p.age}};
}

void
from_json(const json& j, person& p)
{
j.at("name").get_to(p.name);
j.at("height").get_to(p.height);
j.at("age").get_to(p.age);
}
} // namespace data

int
main()
{
Board::initialize();

MODM_LOG_INFO << "\n\n+++++++++++++++++++++++++++" << modm::endl;
MODM_LOG_INFO << "++ NLOHMANN JSON example ++" << modm::endl;
MODM_LOG_INFO << "+++++++++++++++++++++++++++\n" << modm::endl;

data::person alice_struct = {"Alice", 1.85, 80};
json alice_json = alice_struct;

// basic JSON character string usage
{
auto alice_json_string = alice_json.dump();

// imagine that we transmit this string over some interface
MODM_LOG_INFO << "JSON from struct is: " << alice_json_string.c_str() << modm::endl;

// and now we can retrieve the struct from the json object
json retrieved_alice_json = json::parse(alice_json_string);

// JSON is fairly self-annotated, so we might not even need the original struct
MODM_LOG_INFO << "\nRetrieved from string JSON has fields ... ";
for (auto& [key, value] : retrieved_alice_json.items())
{
MODM_LOG_INFO << key.c_str() << ", ";
}
MODM_LOG_INFO << modm::endl;
MODM_LOG_INFO.flush();

// check if retrieved struct is same
auto retrieved_alice_struct = retrieved_alice_json.template get<data::person>();
if (retrieved_alice_struct == alice_struct)
{
MODM_LOG_INFO << "\nRetrieved from string and original struct are identical"
<< modm::endl;
}
MODM_LOG_INFO.flush();
}

// basic binary JSON usage
{
// convert to BSON
auto alice_binary = json::to_bson(alice_json);

MODM_LOG_INFO << "\nBinary JSON (BSON): ";
for (const auto& byte : alice_binary) { MODM_LOG_INFO.printf(" 0x%02x", byte); }
MODM_LOG_INFO << modm::endl;

// put BSON in reserved flash
uint32_t err{0};
const size_t page_start =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we need to do +1 to get the first page completely inside the reserved flash area?

Copy link
Contributor Author

@hshose hshose Apr 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the reserved size in this case is a multiple of the flash page size (1024*16 in the project.xml of the example) so the reserved start should align with a flash page?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added an exception in the lbuild module to only allow multiples of 8Kbytes reserved flash (8Kbytes was the largest flash page size I found for some H7 devices). Now reserved flash should always start on its own page. Is this what you had in mind?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rleh ?

Flash::getPage(reinterpret_cast<uint32_t>(&__flash_reserved_start));
const size_t num_bytes = alice_binary.size();
const size_t flash_page_size = Flash::getSize(page_start);
const size_t end_page = page_start + (num_bytes + flash_page_size - 1) / flash_page_size;
if (end_page >= max_flash_pages)
{
MODM_LOG_ERROR << "\nRequested flash end page exceeds flash [" << page_start << ", "
<< end_page << ")" << modm::endl;
MODM_LOG_ERROR.flush();
while (1)
;
}

// erase the pages before programming
MODM_LOG_INFO << "\nErasing flash sectors [" << page_start << ", " << end_page << ")"
<< modm::endl;
MODM_LOG_INFO.flush();
if (not Flash::unlock())
{
MODM_LOG_ERROR << "Flash unlock failed!" << modm::endl;
while (1)
;
}
for (size_t page{page_start}; page < end_page; page++) err |= Flash::erase(page);
if (err != 0)
{
MODM_LOG_ERROR << "\nThere was an error while erasing flash!" << modm::endl;
MODM_LOG_ERROR.flush();
while (1)
;
}

// pad the data with zeros to fit flash words
size_t unpadded_size{alice_binary.size()};
size_t word_size{sizeof(Flash::MaxWordType)};
if (auto padding = (word_size - unpadded_size % word_size) % word_size; padding > 0)
{
alice_binary.resize(alice_binary.size() + padding, 0);
}

// now, write the padded data
MODM_LOG_INFO << "\nWriting, word size: " << word_size
<< ", num of bytes (payload): " << unpadded_size
<< ", num of bytes (padded): " << alice_binary.size() << "... ";

const auto flash_write_base_addr{reinterpret_cast<uint32_t>(Flash::getAddr(page_start))};
for (size_t ii = 0; ii < alice_binary.size(); ii += sizeof(Flash::MaxWordType))
{
Flash::MaxWordType outdata;
memcpy(&outdata, &alice_binary[ii], sizeof(Flash::MaxWordType));
err |= Flash::program(flash_write_base_addr + ii, outdata);
}

if (err != 0)
{
MODM_LOG_ERROR << "\nThere was an error while programming flash!" << modm::endl;
MODM_LOG_ERROR.flush();
while (1)
;
}
MODM_LOG_INFO << "Writing complete! " << modm::endl;
MODM_LOG_INFO.flush();

// we can now read BSON back from flash, first 4 bytes entry is the size
uint32_t read_len;
memcpy(&read_len, Flash::getAddr(page_start), sizeof(read_len));

// read the actual data
MODM_LOG_INFO << "\nReading BSON of size " << read_len << " from flash: ";
auto raw_bytes =
std::vector<uint8_t>(Flash::getAddr(page_start), Flash::getAddr(page_start) + read_len);
for (const auto& byte : raw_bytes) { MODM_LOG_INFO.printf(" 0x%02x", byte); }
MODM_LOG_INFO << modm::endl;
MODM_LOG_INFO.flush();

// reconstruct the data
auto retrieved_alice_json = json::from_bson(raw_bytes);
auto retrieved_alice_struct = retrieved_alice_json.template get<data::person>();
if (retrieved_alice_struct == alice_struct)
{
MODM_LOG_INFO << "\nRetrieved from flash and original struct are identical"
<< modm::endl;
MODM_LOG_INFO.flush();
}
}

while (1)
;
return 0;
}
14 changes: 14 additions & 0 deletions examples/nucleo_g474re/flash_json/project.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<library>
<extends>modm:nucleo-g474re</extends>
<options>
<option name="modm:build:build.path">../../../build/nucleo_g474re/flash_json</option>
<option name="modm:platform:cortex-m:linkerscript.flash_reserved">1024*16</option>
</options>
<modules>
<module>modm:debug</module>
<module>modm:platform:gpio</module>
<module>modm:platform:flash</module>
<module>modm:nlohmann-json</module>
<module>modm:build:scons</module>
</modules>
</library>
1 change: 1 addition & 0 deletions ext/nlohmann/json
Submodule json added at 965578
36 changes: 36 additions & 0 deletions ext/nlohmann/json.lb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (c) 2024, Henrik Hose
#
# This file is part of the modm project.
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# -----------------------------------------------------------------------------

def init(module):
module.name = ":nlohmann-json"
module.description = """
# Header-only JSON library

The nlohmann JSON C++ library

- https://json.nlohmann.me/
- https://github.com/nlohmann/json

"""

def prepare(module, options):
if options[":target"].identifier.platform != "hosted":
module.depends(":platform:heap")

return True

def build(env):
env.collect(":build:path.include", "modm/ext/nlohmann-json")
env.outbasepath = "modm/ext/nlohmann-json"

env.copy("json/single_include/nlohmann/json.hpp", dest="json.hpp")
env.copy("json/single_include/nlohmann/json_fwd.hpp", dest="json_fwd.hpp")
38 changes: 34 additions & 4 deletions src/modm/platform/core/cortex/module.lb
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,23 @@ def common_memories(env):
"start": "0x10000000",
"size": hex(flash_size)})
flash_offset = env.get(":platform:cortex-m:linkerscript.flash_offset", 0)
flash_reserved = env.get(":platform:cortex-m:linkerscript.flash_reserved", 0)
for m in memories:
if m["name"] == "flash":
m["start"] = int(m["start"], 0) + flash_offset
m["size"] = int(m["size"], 0) - flash_offset
m["size"] = int(m["size"], 0) - flash_offset - flash_reserved
else:
m["start"] = int(m["start"], 0)
m["size"] = int(m["size"], 0)

# Add reserved FLASH section to memories
if flash_reserved:
memories.append({
"name": "flash_reserved",
"access": "rx",
"start": next(m["start"] + m["size"] for m in memories if m["name"] == "flash"),
"size": flash_reserved})

# Find all continuous internal SRAM sections
cont = []
index = 0
Expand Down Expand Up @@ -229,7 +238,7 @@ def prepare(module, options):
default=default_location))

# Find the size of the flash memory
flash_size = next((int(x['size']) for x in memories if x['name'] == 'flash'), 16*1024*1024)
flash_size = next((int(x["size"]) for x in memories if x["name"] == "flash"), 16*1024*1024)
module.add_option(
NumericOption(
name="linkerscript.flash_offset",
Expand All @@ -238,6 +247,14 @@ def prepare(module, options):
maximum=hex(flash_size),
default=0))

module.add_option(
NumericOption(
name="linkerscript.flash_reserved",
description="Add a reserved section at the end of the flash.",
minimum=0,
maximum=hex(flash_size),
default=0))

module.add_option(
PathOption(
name="linkerscript.override",
Expand Down Expand Up @@ -290,8 +307,21 @@ def validate(env):
raise ValidateException("Invalid flash offset in option '{}' (value=0x{:X}). "
"The offset needs to be {} bit aligned."
.format(flash_offset_option_name,
flash_offset,
bit_alignment))
flash_offset, bit_alignment))

device = env[":target"]
memories = listify(device.get_driver("core")["memory"])
flash_size = next((int(x["size"]) for x in memories if x["name"] == "flash"), 16*1024*1024)
flash_reserved_option_name = "modm:platform:cortex-m:linkerscript.flash_reserved"
flash_reserved = env[flash_reserved_option_name]
if flash_reserved != 0:
if flash_reserved + flash_offset > flash_size:
raise ValidateException("Invalid reserved flash size in option '{}'. "
"The 'linkerscript.flash_offset' of {} at the beginning "
"of FLASH is growing into the 'linkerscript.flash_reserved' "
"of {} at the end of FLASH due to flash size {}."
.format(flash_reserved_option_name, flash_offset,
flash_reserved, flash_size))


def build(env):
Expand Down
14 changes: 13 additions & 1 deletion src/modm/platform/flash/stm32/flash.cpp.in
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,21 @@ Flash::erase(uint8_t index)
FLASH->CR = FLASH_CR_STRT | FLASH_CR_SER | uint32_t(size) |
((index << FLASH_CR_SNB_Pos) & FLASH_CR_SNB_Msk);
%% else
%% if family in ["g0","g4"]
%% if family in ["g0", "g4"]
%% if dual_bank
uint32_t cr = FLASH_CR_STRT | FLASH_CR_PER;
uint32_t page = index;
if (FLASH->OPTR & {{dual_bank}} and index >= (Size/2 >> 11))
{
// second bank index starts back at zero again
page -= (Size/2 >> 11);
cr |= FLASH_CR_BKER;
}
FLASH->CR = cr | ((page << FLASH_CR_PNB_Pos) & FLASH_CR_PNB_Msk);
%% else
FLASH->CR = FLASH_CR_STRT | FLASH_CR_PER |
((index << FLASH_CR_PNB_Pos) & FLASH_CR_PNB_Msk);
%% endif
%% else
FLASH->CR &= ~FLASH_CR_STRT;
FLASH->CR |= FLASH_CR_PER;
Expand Down