Wire-free Miniscope v4: Difference between revisions

    From Aharoni Lab Wiki
    No edit summary
    Line 22: Line 22:


    === Initialization ===
    === Initialization ===
    === Buffers ===
    The MCU firmware creates a circular buffer to fill and hold pixel data. This set of buffers should each have a size a multiple of 512 bytes so that each can be nicely written into an SD Card whose memory blocks are 512 bytes in size. Outside of the constraint that each buffer should be some multiple of 512 bytes there are no other constraints in terms of number of buffers or buffer size relative to image sensor frame size.
    The circular buffers are defined globally within main.c :<syntaxhighlight lang="c">
    COMPILER_ALIGNED(4)
    volatile uint32_t dataBuffer[NUM_BUFFERS][BUFFER_BLOCK_LENGTH * BLOCK_SIZE_IN_WORDS]; //Allocate memory for DMA image buffers
    </syntaxhighlight>This set of buffers gets circularly iterated through using DMA linked lists as pixel data arrives from the image sensor.


    === State Machine ===
    === State Machine ===
    Line 37: Line 45:
    #define DEVICE_STATE_SDCARD_WRITE_ERROR 1<<10
    #define DEVICE_STATE_SDCARD_WRITE_ERROR 1<<10
    #define DEVICE_STATE_SDCARD_INIT_WRITE_ERROR 1<<11
    #define DEVICE_STATE_SDCARD_INIT_WRITE_ERROR 1<<11
    </syntaxhighlight>Once the MCU moves into the device = DEVICE_STATE_RECORDING, management of detecting newly filled buffers and sending those over to the SD Card is all handed within the function '''void recording()'''.
    </syntaxhighlight>Once the MCU moves into the '''device = DEVICE_STATE_RECORDING''', management of detecting newly filled buffers and sending those over to the SD Card is all handed within the function '''void recording()'''. '''recording()''' is continuously called within the while loop in '''main()'''.
     
    === Callback Functions ===
    Callback functions handle specific events within the MCU that generate interrupts. Roughly speaking, when certain interrupts happen, the MCU will jump from wherever it is currently in its code, to the callback function linked to this interrupt event. Callback functions are denoted with a '''_cb''' at the end of their function name:<syntaxhighlight lang="c">
    // callbacks
    static void millisecondTimer_cb(const struct timer_task *const timer_task);
    static void checkBattVoltage_cb(const struct timer_task *const timer_task);
     
    static void battCharging_cb(void);
    static void irReceive_cb(void);
    static void pushButton_cb(void);
    static void frameValid_cb(void);
    </syntaxhighlight>


    === DMA ===
    === DMA ===
    Line 43: Line 63:


    ==== PCC DMA (Image Sensor -> MCU Memory) ====
    ==== PCC DMA (Image Sensor -> MCU Memory) ====
    ===== Linked List DMA Descriptors =====
    <syntaxhighlight lang="c">
    COMPILER_ALIGNED(16) // Taken from hpl_dmac.c but I think this could be '8' since descriptors need to be 64bit aligned from data sheet
    volatile DmacDescriptor linkedList[NUM_BUFFERS];
    </syntaxhighlight>


    ==== SD Card ADMA (MCU Memory -> SD Card) ====
    ==== SD Card ADMA (MCU Memory -> SD Card) ====

    Revision as of 10:27, 31 March 2023

    The wire-free Miniscope v4 is a battery powered, data logging Miniscope based around the original wired Miniscope v4 project. All code, CAD, and PCB files can be found at https://github.com/Aharoni-Lab/Miniscope-v4-Wire-Free

    Specifications

    • Weight:
    • Size:
    • Field of View:
    • Recording Resolution:
    • Recording Length:
    • Record Triggering:

    Overview of System

    Workflow of Recording

    Extracting Data from SD Card

    MCU Firmware Walkthrough

    The microcontroller (MCU) used in the Wire-Free Miniscope v4 is an ATSAMD51. It sits between the Python 480 CMOS image sensor and the micro SD Card and handles all on-board control and operation of the Miniscope. The firmware uses a combination of custom written driver and ones imported using ATMEL SMART.

    Initialization

    Buffers

    The MCU firmware creates a circular buffer to fill and hold pixel data. This set of buffers should each have a size a multiple of 512 bytes so that each can be nicely written into an SD Card whose memory blocks are 512 bytes in size. Outside of the constraint that each buffer should be some multiple of 512 bytes there are no other constraints in terms of number of buffers or buffer size relative to image sensor frame size.

    The circular buffers are defined globally within main.c :

    COMPILER_ALIGNED(4)
    volatile uint32_t dataBuffer[NUM_BUFFERS][BUFFER_BLOCK_LENGTH * BLOCK_SIZE_IN_WORDS]; //Allocate memory for DMA image buffers
    

    This set of buffers gets circularly iterated through using DMA linked lists as pixel data arrives from the image sensor.

    State Machine

    // ---------- Device State Definitions -------
    #define DEVICE_STATE_IDLE				1<<1
    #define DEVICE_STATE_START_RECORDING	1<<2
    #define DEVICE_STATE_RECORDING			1<<3
    #define DEVICE_STATE_STOP_RECORDING		1<<4
    #define DEVICE_STATE_CHARGING			1<<5
    #define DEVICE_STATE_CONFIG_LOADED		1<<6
    #define DEVICE_STATE_ERROR				1<<7
    #define DEVICE_STATE_LOW_VOLTAGE		1<<8
    #define DEVICE_STATE_START_RECORDING_WAITING	1<<9
    #define DEVICE_STATE_SDCARD_WRITE_ERROR			1<<10
    #define DEVICE_STATE_SDCARD_INIT_WRITE_ERROR	1<<11
    

    Once the MCU moves into the device = DEVICE_STATE_RECORDING, management of detecting newly filled buffers and sending those over to the SD Card is all handed within the function void recording(). recording() is continuously called within the while loop in main().

    Callback Functions

    Callback functions handle specific events within the MCU that generate interrupts. Roughly speaking, when certain interrupts happen, the MCU will jump from wherever it is currently in its code, to the callback function linked to this interrupt event. Callback functions are denoted with a _cb at the end of their function name:

    // callbacks
    static void millisecondTimer_cb(const struct timer_task *const timer_task);
    static void checkBattVoltage_cb(const struct timer_task *const timer_task);
    
    static void battCharging_cb(void);
    static void irReceive_cb(void);
    static void pushButton_cb(void);
    static void frameValid_cb(void);
    

    DMA

    The MCU using Direct Memory Access (DMA) to stream pixel data into memory from the image sensor as well as write imaging data from memory into the SD Card. For pixel data coming from the image sensor, the MCU uses its Parallel Capture Controller (PCC) with DMA to handle an 8-bit parallel pixel bus. For writing imaging data into the SD Card, the MCU uses its ADMA to move raw data in the MCU memory into the SD Card storage blocks.

    PCC DMA (Image Sensor -> MCU Memory)

    Linked List DMA Descriptors
    COMPILER_ALIGNED(16) // Taken from hpl_dmac.c but I think this could be '8' since descriptors need to be 64bit aligned from data sheet
    volatile DmacDescriptor linkedList[NUM_BUFFERS];
    

    SD Card ADMA (MCU Memory -> SD Card)

    Writing and reading from the SD Card takes advantage of ADMA. I can't quite remember what all the differences are here between ADMA and regular DMA but it took a while to get up and running. To get everything up and running in the firmware, I added two additional functions in sd_mmc.c:

    • sd_mmc_err_t sd_mmc_write_with_ADMA(uint8_t slot, uint32_t start, uint32_t *descAdd, uint16_t nb_block)
    • sd_mmc_err_t sd_mmc_wait_end_of_ADMA_write(bool abort)