This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Sending 30000 bytes to Android Phone every 30 seconds while UART is receiving data every 40ms

Hi all,

I am working on a project where the requirement is a sensor collecting data and sending to the NRF52832 via UART. Every 40ms, the NRF52832 will receive 40 bytes of data. I need to store 30 seconds worth of data in RAM (about 30000 bytes) then transfer everything via BLE to a mobile app. While it is transferring this data, UART still needs to work and collect 40 bytes every 40ms.

What I have done previously was to send the data out via BLE immediately at every 40ms and this works fine. However, the app developer ask that I only send the data over only every 30s. I am now stuck with how I can collect 30000 bytes first and still have UART working while doing the BLE transfer. I don't really have an idea of what kind of modifications I will need to make to the BLE_UART example code.

Anyone can help? Thanks in advance.

Parents
  • Hi,

    It sounds like using your UART with EasyDMA combined with a timer, a counter, and some PPI channels could solve your problem. In short, you can set up a timer to trigger an event every 40ms and connect this via PPI to a transfer task in your UART. You can then use EasyDMA and let the UART transfer data directly to a buffer in RAM. Use the counter to count the number of transfers and fire an interrupt when you have enough data to send. You can do all these things completely autonomously, without using the CPU and hence, not block any BLE transfers. After 30k samples, when it is time to transfer your data, the counter interrupt triggers, you swap your buffer to a secondary one and then start transferring your first buffer. 

  • it seems like I have no total clue on how to use PPI to start a transfer task from UART. I tried searching on the forum and examples, but there is no mention of it. Hope someone can shed some light on this.

    I have modified the codes from this example, removing the TWI portions:
    https://github.com/Martinsbl/nrf5-mpu-examples/blob/master/nrf52-mpu-easydma-using-timers-and-drivers/main.c

    This is what I have now:

    /* 
      * This example is not extensively tested and only 
      * meant as a simple explanation and for inspiration. 
      * NO WARRANTY of ANY KIND is provided. 
      */
    
    #include <stdio.h>
    #include "boards.h"
    #include "app_util_platform.h"
    #include "app_uart.h"
    #include "app_error.h"
    #include "nrf_delay.h"
    #include "nrf_drv_ppi.h"
    #include "nrf_drv_timer.h"
    #include "nrf_drv_gpiote.h"
    #include "nrf_gpio.h"
    #include "nrf_serial.h"
    
    #include "nrf_log.h"
    #include "nrf_log_ctrl.h"
    #include "nrf_log_default_backends.h"
    
    
    /**@brief Function for initializing the nrf log module.
     */
    static void log_init(void)
    {
        ret_code_t err_code = NRF_LOG_INIT(NULL);
        APP_ERROR_CHECK(err_code);
    
        NRF_LOG_DEFAULT_BACKENDS_INIT();
    }
    
    NRF_SERIAL_DRV_UART_CONFIG_DEF(m_uart0_drv_config,
                          RX_PIN_NUMBER, TX_PIN_NUMBER,
                          RTS_PIN_NUMBER, CTS_PIN_NUMBER,
                          NRF_UART_HWFC_DISABLED, NRF_UART_PARITY_EXCLUDED,
                          NRF_UART_BAUDRATE_115200,
                          UART_DEFAULT_CONFIG_IRQ_PRIORITY);
    
    #define SERIAL_FIFO_TX_SIZE 32
    #define SERIAL_FIFO_RX_SIZE 32
    
    NRF_SERIAL_QUEUES_DEF(serial_queues, SERIAL_FIFO_TX_SIZE, SERIAL_FIFO_RX_SIZE);
    
    #define SERIAL_BUFF_TX_SIZE 1
    #define SERIAL_BUFF_RX_SIZE 1
    
    NRF_SERIAL_BUFFERS_DEF(serial_buffs, SERIAL_BUFF_TX_SIZE, SERIAL_BUFF_RX_SIZE);
    
    NRF_SERIAL_CONFIG_DEF(serial_config_dma, NRF_SERIAL_MODE_DMA,
                          &serial_queues, &serial_buffs, NULL, NULL);
    											
    NRF_SERIAL_UART_DEF(serial_uart, 0)
    
    
    #define SERIAL_RX_BUF_LENGTH  10
    #define SERIAL_RX_BUF_WIDTH   6  
    
    
    /* Define a type with a two dimensioanal array, SERIAL_RX_BUF_WIDTH wide and SERIAL_RX_BUF_LENGTH long, holding a list of uart data */
    typedef struct ArrayList
    {
        uint8_t buffer[SERIAL_RX_BUF_WIDTH];
    }array_list_t;
    
    /* Declare an RX buffer to hold the uart data we want to read. 
     * SERIAL_RX_BUF_LENGTH defines how many samples of 
     * data we want to read out. */
    array_list_t p_rx_buffer[SERIAL_RX_BUF_LENGTH];
    
    /* Flag to indicate to the applications main context that SERIAL_RX_BUF_LENGTH number of samples have been transferred from serial */
    volatile bool serial_transfers_complete = false;
    
    /* Timer instance to trigger each read from Serial */
    static nrf_drv_timer_t timer_instance_serial_start = NRF_DRV_TIMER_INSTANCE(0);
    
    /* Counter instance to count number of transferred samples, and trigger an interrupt and wake up CPU 
     * when SERIAL_RX_BUF_LENGTH number of samples have transferred from  serial */
    static nrf_drv_timer_t serial_transfer_counter_instance = NRF_DRV_TIMER_INSTANCE(1);
    
    
    // Serial transfer start handler. Not used
    void serial_start_timer_handler(nrf_timer_event_t event_type, void * p_context)
    {
        ;
    }
    
    // Serial transfer counter handler. 
    void serial_transfer_counter_handler(nrf_timer_event_t event_type, void * p_context)
    {   
    		ret_code_t err_code;	
        // Set flag to start printing of samples in main loop
        serial_transfers_complete = true;
        
        // Set up next transfer sequence
        
        
        // Set flags for next transfer sequence
        
        
        // Pass configurations and flags to TWIM driver. The TWIM is now on and prepared, but not actually started.
        
    	
        APP_ERROR_CHECK(err_code);
    }
    
    
    /**
     * @brief Serial transfer setup. This functions initiates the timer used to trigger
     * the Serial transfers. It also configures the the Serial transfers and sets up a PPI channel 
     * connecting the timer compare event with the Serial transfer start task. 
     */
    static void serial_transfer_start_timer_init()
    {
        ret_code_t err_code;
        // Variable to store address of timer compare event. 
        uint32_t event_address_timer_compare;
        // Variable to store address of twi start task.
        uint32_t task_address_serial_start;
        // Variable holding PPI channel number
        nrf_ppi_channel_t ppi_channel_serial_start;
        
    		/********************************************* To figure out ****************************************
        // Set up next transfer sequence
        
        
        // Set flags for next transfer sequence
        
        
        // Pass configurations and flags to TWIM driver. The TWIM is now on and prepared, but not actually started.
    	
    	********************************************************************************************************/
        
        APP_ERROR_CHECK(err_code);
        
        // Initializing the timer triggering the Serial transfers. Passing:
        // Instance of timer
        // NULL = default timer configurations
        // Timer handler. Can not be NULL even though it is not used.
        err_code = nrf_drv_timer_init(&timer_instance_serial_start, NULL, serial_start_timer_handler);
        APP_ERROR_CHECK(err_code);
        
        uint32_t serial_sample_period = nrf_drv_timer_us_to_ticks(&timer_instance_serial_start, 40000); // Sample every 40 ms (25 Hz)
        // Configuring the timer triggering the serial transfers. Passing:
        // Timer instance
        // Capture Compare channel number
        // Sample period in micro seconds
        // Shortcut to clear timer on Capture Compare event
        // Disable the interrupt for the compare channel enabling the CPU to sleep during events
        nrf_drv_timer_extended_compare(&timer_instance_serial_start, (nrf_timer_cc_channel_t)0, serial_sample_period, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, false);
        
    
        // Initate ppi driver. Ignore error if driver is already initialized
        err_code = nrf_drv_ppi_init();
        if((err_code != 0) && (err_code != NRF_ERROR_MODULE_ALREADY_INITIALIZED))
        {
            APP_ERROR_CHECK(err_code);
        }
        
    		
    		
    		/************************************************************ To figure out *****************************
    		
        // Get the address of the Serial start task. This is the register address of the actual Serial task. Look in the nRF52 Product Specification -> TWI -> Registers
        task_address_serial_start = nrf_drv_twi_start_task_get(&m_twi_instance, xfer.type);
        // Get the address of the timer compare[0] event. This is the register address of the actual timer compare[0] event. Look in the nRF52 Product Specification -> Timer -> Registers
        event_address_timer_compare = nrf_drv_timer_event_address_get(&timer_instance_serial_start, NRF_TIMER_EVENT_COMPARE0);
        
    		*********************************************************************************************************/
    		
    		
    		
        // Allocate a PPI channel. This function is especially useful when using a softdevice as it will ensure that the the returend PPI channels is free
        err_code = nrf_drv_ppi_channel_alloc(&ppi_channel_serial_start);
        APP_ERROR_CHECK(err_code);
        
        // Connect the timer compare event and serial start task using the PPI channel
        err_code = nrf_drv_ppi_channel_assign(ppi_channel_serial_start, event_address_timer_compare, task_address_serial_start);
        APP_ERROR_CHECK(err_code);
    
        // Enable the PPI channel.
        err_code = nrf_drv_ppi_channel_enable(ppi_channel_serial_start);
        APP_ERROR_CHECK(err_code);
           
    }
    
    /**
     * @brief This function sets up a counter used to count serial transfers. It also sets up a PPI channel 
     * connecting the Counter increment task and the serial transfer complete event. 
     * When each serial transfer is completed the counter increments. When TWIM_RX_BUF_LENGTH number of samples is counted
     * the counter triggers an interrupt, serial_transfer_counter_handler, and wakes up the CPU.
     */
    void serial_transfer_counter_init(void)
    {
        // Setup serial tranfer counter
        uint32_t err_code;
        // Variable to store address of counter increment task. 
        uint32_t task_address_counter_increment;
        // Variable to store address of serial transfer complete event. 
        uint32_t event_address_serial_transfer_complete;
        // Variable holding PPI channel number
        nrf_ppi_channel_t ppi_channel_serial_transfer_count;
        
        // Set up counter with default configuration
        nrf_drv_timer_config_t counter_config = NRF_DRV_TIMER_DEFAULT_CONFIG;
        counter_config.mode = NRF_TIMER_MODE_COUNTER;
        
        // Initializing the counter counting the serial transfers. Passing:
        // Instance of counter
        // Configurations
        // Timer handler. This timer will be triggered when TWIM_RX_BUF_LENGTH number of transfers have completed.
        err_code = nrf_drv_timer_init(&serial_transfer_counter_instance, &counter_config, serial_transfer_counter_handler);
        APP_ERROR_CHECK(err_code);
        
        // Configuring the timer triggering the serial transfers. Passing:
        // Counter instance
        // Capture Compare channel number
        // Number of samples to count
        // Shortcut to clear timer on Capture Compare event
        // Use interrupts
        nrf_drv_timer_extended_compare(&serial_transfer_counter_instance, (nrf_timer_cc_channel_t)0, SERIAL_RX_BUF_LENGTH, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, true);
        
        // Initate ppi driver. Ignore error if driver is already initialized
        err_code = nrf_drv_ppi_init();
        if((err_code != 0) && (err_code != NRF_ERROR_MODULE_ALREADY_INITIALIZED))
        {
            APP_ERROR_CHECK(err_code);
        }
        
        // Allocate a PPI channel. This function is especially useful when using a softdevice as it will ensure that the the returend PPI channels is free
        err_code = nrf_drv_ppi_channel_alloc(&ppi_channel_serial_transfer_count);
        APP_ERROR_CHECK(err_code);
        
        // Get the address of the counter increment task. This is the register address of the actual counter task. Look in the nRF52 Product Specification -> Timer -> Registers
        task_address_counter_increment = nrf_drv_timer_task_address_get(&serial_transfer_counter_instance, NRF_TIMER_TASK_COUNT);
        
    		
    		
    		/******************************************* To figure out *************************************************
    		
    		// Get the address of the TWI transfer complete event. This is the register address of the event. Look in the nRF52 Product Specification -> TWI -> Registers
        event_address_twi_transfer_complete = nrf_drv_twi_stopped_event_get(&m_twi_instance);
        
    		***********************************************************************************************************/
    		
    		
        // Connect the serial transfer complete event and the counter increment task using the PPI channel
        err_code = nrf_drv_ppi_channel_assign(ppi_channel_serial_transfer_count, event_address_serial_transfer_complete, task_address_counter_increment);
        APP_ERROR_CHECK(err_code);
    
        // Enable the PPI channel.
        err_code = nrf_drv_ppi_channel_enable(ppi_channel_serial_transfer_count);
        APP_ERROR_CHECK(err_code);
    }
    
    
    void serial_transfer_start()
    {
        // Enable the counter counting number of serial transfers
        nrf_drv_timer_enable(&serial_transfer_counter_instance);
        
        // Enable timer triggering serial transfers
        nrf_drv_timer_enable(&timer_instance_serial_start);
    }
    
    /**
     * @brief Function for main application entry.
     */
    int main(void)
    {
        // Configure some LEDs
        LEDS_CONFIGURE(LEDS_MASK);
        LEDS_OFF(LEDS_MASK);
        
        // Initialize.
        log_init();
    		NRF_LOG_INFO("\033[2J\033[;H"); // Clear screen
        
        // Start execution.
        NRF_LOG_INFO("\033[2J\033[;HMPU nRF52 EasyDMA using timers and drivers example. Compiled @ %s\r\n", __TIME__);
        
    		ret = nrf_serial_init(&serial_uart, &m_uart0_drv_config, &serial_config);
        APP_ERROR_CHECK(ret);
    	
        // Configure a timer to trigger serial transfers between nRF52
        serial_transfer_start_timer_init();   
        // Configure a counter to count number of serial transfers
        serial_transfer_counter_init();
    	
        // Start the serial transfers between the nRF52 and uart device
        serial_transfer_start();
        
        
        uint32_t sample_nr = 0; // Variable holding number of samples read from MPU
        
        while(1)
        {
            if(NRF_LOG_PROCESS() == false)
            {
                nrf_gpio_pin_set(LED_4); // Turn LED OFF when CPU is sleeping
                
    					while(serial_transfers_complete == false)
                {
                    // Make sure any pending events are cleared
                    __SEV();
                    __WFE();
                    // Enter System ON sleep mode
                    __WFE();           
                }
                nrf_gpio_pin_clear(LED_4); // Turn LED ON when CPU is working
                
                // Clear terminal
                NRF_LOG_RAW_INFO("\033[3;1HSample %d:\r\n", (SERIAL_RX_BUF_LENGTH * ++sample_nr));
                
                // THIS FOR LOOP ASSUMES THAT TWIM_RX_BUF_WIDTH IS 6 BYTES AND THAT ONLY ACCELEROMETER DATA IS SAMPLED
                // IF A WIDER BUFFER IS USED TO SAMPLE TEMPERATURE AND GYROSCOPE AS WELL YOU SHOULD CHANGE THIS LOOP
                // TO PRINT EVERYTHING
                uint8_t *data;
                // Itterate through entire RX buffer 
                for(uint8_t j = 0; j<SERIAL_RX_BUF_LENGTH; j++)
                {
                    // Print sensor data set
                    NRF_LOG_RAW_INFO("");
                    nrf_delay_ms(1); // Small delay
                }
                // Reset data ready flag
                serial_transfers_complete = false;
            }
        }
    }
    
    /** @} */

    Thanks.

  • There is this PPI Example, although I would agree that it is not the best example to show off the capabilities of PPI. However, you should take note of the naming convention of the functions, like nrf_drv_timer_event_address_get(). You have similar functions for the UART driver: nrf_drv_uart_event_address_get(). Use these kind of functions to get the addresses of the tasks and events and tie them together with a PPI channel using nrf_drv_ppi_channel_assign(). 

    It could be that it would be better to start with the PPI example instead of the mpu example. Start small by trying to tie a timer event to a UART TX task and see if you can send a byte at regular intervals. Then add on with counters etc. 

  • Thanks MartinBL,

    I kind of understand how PPI works, but now I am totally confused by UART. There is UART HAL, UARTE HAL and UART driver. There is also Serial library.

    Let me start a separate thread to clear my doubts on the usage of UART first. I will revisit this thread again when I am ready to put the pieces together.

Reply Children
No Data
Related