Zubax / kocherga

Robust platform-agnostic Cyphal/DroneCAN bootloader for deeply embedded systems
https://zubax.com
MIT License
45 stars 10 forks source link

RP2040 Implementation Using RP2040 #23

Closed joerossrobotics closed 1 year ago

joerossrobotics commented 1 year ago

Hi,

I was wondering if there exists any examples of using this project that would help me integrate it with an RP2040 & MCP2515 CAN Controller. I'm familiar with using libcanard and the 107 MCP2515 library to produce cyphal nodes. I've also written the code to boot a user program from flash on the RP2040 - the main issue is implementing classes such as:

class MyCANDriver final : public kocherga::can::ICANDriver

Any help would be greatly appreciated.

pavel-kirienko commented 1 year ago

I am not aware of any ready-made examples for this (perhaps try GitHub code search, maybe someone has already made one), but I imagine it to be a straightforward task. If you need any advanced support with this including writing code, please reach out to sales@zubax.com. Thanks!

joerossrobotics commented 1 year ago

Hi,

In terms of implementing the ICANDriver class, are the push / pop / etc functions supposed to work with the libcanard queue, or directly with the MCP2515? I'm slightly confused as to whether the user is supposed to work with cyphal directly, or if this is handled internally by kocherga.

Thanks for the quick reply :)

pavel-kirienko commented 1 year ago

Cyphal is handled by Kocherga entirely, so the user works with raw CAN frames. That is to say, with MCP2515.

joerossrobotics commented 1 year ago

One final question,

In terms of implementing IROMBackend, the RP2040 is only able to erase sectors of 4096 bytes, and write pages of 256 bytes. In terms of erasing the flash, does Kocherga make the image_size variable of the AppDescriptor struct available to the user prior to beginning writing the data (one option is to erase this to the closet multiple of the sector size in the beginWrite() method). The other option is to erase the data dynamically by tracking the amount of data written with a static variable and erasing another sector of flash as needed in the write() method.

I'm aware that the firmware is written progressively using write() until the upload is finished. Is there internal provision to tell Kocherga to write the data in multiples of the page size? The best solution I suppose would be to tell Kocherga to write the application to flash in multiples of the sector size, and erase the sector about to be written every time write() is called.

Thanks again for all your help! :)

pavel-kirienko commented 1 year ago

The other option is to erase the data dynamically by tracking the amount of data written with a static variable and erasing another sector of flash as needed in the write() method.

This is the recommended approach (sans the static variable, you don't need this).

I'm aware that the firmware is written progressively using write() until the upload is finished. Is there internal provision to tell Kocherga to write the data in multiples of the page size?

No, you should implement buffering in your IROMBackend implementation.

The best solution I suppose would be to tell Kocherga to write the application to flash in multiples of the sector size

This would require Kocherga to implement buffering internally and thus do the job of your IROMBackend.

joerossrobotics commented 1 year ago

I see, so something along these lines:

class MyROMBackend final : public kocherga::IROMBackend
{
public:
    std::optional<std::size_t> write(const std::size_t offset, const std::byte *const data, const std::size_t size) override
    {
        // Add Data To Buffer
        writeBuffer.insert(writeBuffer.end(), data, data + size);

        // If There Is At Least One Sector To Write
        while (writeBuffer.size() >= FLASH_SECTOR_SIZE)
        {
            // Erase Sector
            flash_range_erase(currentOffset, FLASH_SECTOR_SIZE);

            // Write Sector To Flash
            uint8_t tempBuffer[FLASH_SECTOR_SIZE];
            memcpy(tempBuffer, data, FLASH_SECTOR_SIZE);
            flash_range_program(currentOffset, tempBuffer, FLASH_SECTOR_SIZE);

            // Remove Written Data From Buffer
            writeBuffer.erase(writeBuffer.begin(), writeBuffer.begin() + FLASH_SECTOR_SIZE);

            // Increment Current Sector Offset
            currentOffset += FLASH_SECTOR_SIZE;
        }

        return size;
    }

    void endWrite() override
    {
        // Pad Final Secttor With Zeros
        while (writeBuffer.size() < FLASH_SECTOR_SIZE)
            writeBuffer.emplace_back(0);

        // Write Final Sector To Flash
        uint8_t tempBuffer[FLASH_SECTOR_SIZE];
        memcpy(tempBuffer, &writeBuffer, FLASH_SECTOR_SIZE);
        flash_range_program(currentOffset, tempBuffer, FLASH_SECTOR_SIZE);
    }

    std::size_t read(const std::size_t offset, std::byte *const out_data, const std::size_t size) const override
    {
        memcpy(out_data, reinterpret_cast<uint8_t *>(offset), size);
        return size;
    }

private:
    size_t currentOffset = 0;
    std::deque<std::byte> writeBuffer;
};
pavel-kirienko commented 1 year ago

Perhaps. The memcpy you invoke at the end is ill-formed and you don't really need a deque here, a regular std::array would do; otherwise it's close.

Willmac16 commented 1 year ago

@joerossrobotics I'm currently working on getting Kocherga working on rp2040 with can2040 for the can driver and CMake for build. Some components will obviously be different, but I am happy to work with you/help test the stuff that could be the same between our use cases--probably linker scripts, IROMDriver, and generic pico sdk calls.