Subversion Repositories f9daq

Rev

Blame | Last modification | View Log | RSS feed

/**
 * $Id: fpga_osc.c 881 2013-12-16 05:37:34Z rp_jmenart $
 *
 * @brief Red Pitaya Oscilloscope FPGA controller.
 *
 * @Author Jure Menart <juremenart@gmail.com>
 *
 * (c) Red Pitaya  http://www.redpitaya.com
 *
 * This part of code is written in C programming language.
 * Please visit http://en.wikipedia.org/wiki/C_(programming_language)
 * for more details on the language used herein.
 */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

#include "fpga_osc.h"

/**
 * GENERAL DESCRIPTION:
 *
 * This module initializes and provides for other SW modules the access to the
 * FPGA OSC module. The oscilloscope memory space is divided to three parts:
 *   - registers (control and status)
 *   - input signal buffer (Channel A)
 *   - input signal buffer (Channel B)
 *
 * This module maps physical address of the oscilloscope core to the logical
 * address, which can be used in the GNU/Linux user-space. To achieve this,
 * OSC_FPGA_BASE_ADDR from CPU memory space is translated automatically to
 * logical address with the function mmap(). After all the initialization is done,
 * other modules can use this module to controll oscilloscope FPGA core. Before
 * any functions or functionality from this module can be used it needs to be
 * initialized with osc_fpga_init() function. When this module is no longer
 * needed osc_fpga_exit() must be called.
 *  
 * FPGA oscilloscope state machine in various modes. Basic principle is that
 * SW sets the machine, 'arm' the writting machine (FPGA writes from ADC to
 * input buffer memory) and then set the triggers. FPGA machine is continue
 * writting to the buffers until the trigger is detected plus the amount set
 * in trigger delay register. For more detauled description see the FPGA OSC
 * registers description.
 *
 * Nice example how to use this module can be seen in worker.c module.
 */


/* internal structures */
/** The FPGA register structure (defined in fpga_osc.h) */
osc_fpga_reg_mem_t *g_osc_fpga_reg_mem = NULL;
/** The FPGA input signal buffer pointer for channel A */
uint32_t           *g_osc_fpga_cha_mem = NULL;
/** The FPGA input signal buffer pointer for channel B */
uint32_t           *g_osc_fpga_chb_mem = NULL;

/** The memory file descriptor used to mmap() the FPGA space */
int             g_osc_fpga_mem_fd = -1;

/* Constants */
/** ADC number of bits */
const int c_osc_fpga_adc_bits = 14;
/** @brief Max and min voltage on ADCs.
 * Symetrical - Max Voltage = +14, Min voltage = -1 * c_osc_fpga_max_v
 */

const float c_osc_fpga_adc_max_v  = +14;
/** Sampling frequency = 125Mspmpls (non-decimated) */
const float c_osc_fpga_smpl_freq = 125e6;
/** Sampling period (non-decimated) - 8 [ns] */
const float c_osc_fpga_smpl_period = (1. / 125e6);

/**
 * @brief Internal function used to clean up memory.
 *
 * This function un-maps FPGA register and signal buffers, closes memory file
 * descriptor and cleans all memory allocated by this module.
 *
 * @retval 0 Success
 * @retval -1 Error happened during cleanup.
 */

int __osc_fpga_cleanup_mem(void)
{
    /* If register structure is NULL we do not need to un-map and clean up */
    if(g_osc_fpga_reg_mem) {
        if(munmap(g_osc_fpga_reg_mem, OSC_FPGA_BASE_SIZE) < 0) {
            fprintf(stderr, "munmap() failed: %s\n", strerror(errno));
            return -1;
        }
        g_osc_fpga_reg_mem = NULL;
        if(g_osc_fpga_cha_mem)
            g_osc_fpga_cha_mem = NULL;
        if(g_osc_fpga_chb_mem)
            g_osc_fpga_chb_mem = NULL;
    }
    if(g_osc_fpga_mem_fd >= 0) {
        close(g_osc_fpga_mem_fd);
        g_osc_fpga_mem_fd = -1;
    }
    return 0;
}

/**
 * @brief Maps FPGA memory space and prepares register and buffer variables.
 *
 * This function opens memory device (/dev/mem) and maps physical memory address
 * OSC_FPGA_BASE_ADDR (of length OSC_FPGA_BASE_SIZE) to logical addresses. It
 * initializes the pointers g_osc_fpga_reg_mem, g_osc_fpga_cha_mem and
 * g_osc_fpga_chb_mem to point to FPGA OSC.
 * If function failes FPGA variables must not be used.
 *
 * @retval 0  Success
 * @retval -1 Failure, error is printed to standard error output.
 */

int osc_fpga_init(void)
{
    /* Page variables used to calculate correct mapping addresses */
    void *page_ptr;
    long page_addr, page_off, page_size = sysconf(_SC_PAGESIZE);

    /* If module was already initialized once, clean all internals. */
    if(__osc_fpga_cleanup_mem() < 0)
        return -1;

    /* Open /dev/mem to access directly system memory */
    g_osc_fpga_mem_fd = open("/dev/mem", O_RDWR | O_SYNC);
    if(g_osc_fpga_mem_fd < 0) {
        fprintf(stderr, "open(/dev/mem) failed: %s\n", strerror(errno));
        return -1;
    }

    /* Calculate correct page address and offset from OSC_FPGA_BASE_ADDR and
     * OSC_FPGA_BASE_SIZE
     */

    page_addr = OSC_FPGA_BASE_ADDR & (~(page_size-1));
    page_off  = OSC_FPGA_BASE_ADDR - page_addr;

    /* Map FPGA memory space to page_ptr. */
    page_ptr = mmap(NULL, OSC_FPGA_BASE_SIZE, PROT_READ | PROT_WRITE,
                          MAP_SHARED, g_osc_fpga_mem_fd, page_addr);
    if((void *)page_ptr == MAP_FAILED) {
        fprintf(stderr, "mmap() failed: %s\n", strerror(errno));
        __osc_fpga_cleanup_mem();
        return -1;
    }

    /* Set FPGA OSC module pointers to correct values. */
    g_osc_fpga_reg_mem = page_ptr + page_off;
    g_osc_fpga_cha_mem = (uint32_t *)g_osc_fpga_reg_mem +
        (OSC_FPGA_CHA_OFFSET / sizeof(uint32_t));
    g_osc_fpga_chb_mem = (uint32_t *)g_osc_fpga_reg_mem +
        (OSC_FPGA_CHB_OFFSET / sizeof(uint32_t));

    return 0;
}

/**
 * @brief Cleans up FPGA OSC module internals.
 *
 * This function closes the memory file descriptor, unmap the FPGA memory space
 * and cleans also all other internal things from FPGA OSC module.
 * @retval 0 Sucess
 * @retval -1 Failure
 */

int osc_fpga_exit(void)
{
    return __osc_fpga_cleanup_mem();
}

// TODO: Move to a shared folder and share with scope & spectrum.
/**
 * @brief Provides equalization & shaping filter coefficients.
 *
 *
 * This function provides equalization & shaping filter coefficients, based on
 * the type of use and gain settings.
 *
 * @param [in]  equal    Enable(1)/disable(0) equalization filter.
 * @param [in]  shaping  Enable(1)/disable(0) shaping filter.
 * @param [in]  gain     Gain setting (0 = LV, 1 = HV).
 * @param [out] filt     Filter coefficients.
 */

void get_equ_shape_filter(ecu_shape_filter_t *filt, uint32_t equal,
                          uint32_t shaping, uint32_t gain)
{
    /* Equalization filter */
    if (equal) {
        if (gain == 0) {
            /* High gain = LV */
            filt->aa = 0x7D93;
            filt->bb = 0x437C7;
        } else {
            /* Low gain = HV */
            filt->aa = 0x4C5F;
            filt->bb = 0x2F38B;
        }
    } else {
        filt->aa = 0;
        filt->bb = 0;
    }

    /* Shaping filter */
    if (shaping) {
        filt->pp = 0x2666;
        filt->kk = 0xd9999a;
    } else {
        filt->pp = 0;
        filt->kk = 0xffffff;
    }
}


/**
 * @brief Updates triggering parameters in FPGA registers.
 *
 * This function updates trigger related parameters in FPGA registers.
 *
 * @param [in] trig_imm Trigger immediately - if set to 1, FPGA state machine
 *                      will trigger immediately and other trigger parameters
 *                      will be ignored.
 * @param [in] trig_source Trigger source, as defined in rp_main_params.
 * @param [in] trig_edge Trigger edge, as defined in rp_main_params.
 * @param [in] trig_delay Trigger delay in [s].
 * @param [in] trig_level Trigger level in [V].
 * @param [in] time_range Time range, as defined in rp_main_params.
 * @param [in] equal    Enable(1)/disable(0) equalization filter.
 * @param [in] shaping  Enable(1)/disable(0) shaping filter.
 * @param [in] gain1    Gain setting for Channel1 (0 = LV, 1 = HV).
 * @param [in] gain2    Gain setting for Channel2 (0 = LV, 1 = HV).
 *
 *
 * @retval 0 Success
 * @retval -1 Failure
 *
 * @see rp_main_params
 */

int osc_fpga_update_params(int trig_imm, int trig_source, int trig_edge,
                           float trig_delay, float trig_level, int time_range,
                           int equal, int shaping, int gain1, int gain2)
{
    int fpga_trig_source = osc_fpga_cnv_trig_source(trig_imm, trig_source,
                                                    trig_edge);
    int fpga_dec_factor = osc_fpga_cnv_time_range_to_dec(time_range);
    int fpga_delay;
    float after_trigger; /* how much after trigger FPGA should write */
    int fpga_trig_thr = osc_fpga_cnv_v_to_cnt(trig_level);
   
    /* Equalization filter coefficients */
    ecu_shape_filter_t cha_filt;
    ecu_shape_filter_t chb_filt;
    get_equ_shape_filter(&cha_filt, equal, shaping, gain1);
    get_equ_shape_filter(&chb_filt, equal, shaping, gain2);
   
    if((fpga_trig_source < 0) || (fpga_dec_factor < 0)) {
        fprintf(stderr, "osc_fpga_update_params() failed\n");
        return -1;
    }

    /* Pre-trigger - we need to limit after trigger acquisition so we can
     * readout historic (pre-trigger) values */

   
    if (trig_imm)
      after_trigger=OSC_FPGA_SIG_LEN* c_osc_fpga_smpl_period * fpga_dec_factor;
    else
    after_trigger =
        ((OSC_FPGA_SIG_LEN-7) * c_osc_fpga_smpl_period * fpga_dec_factor) +
        trig_delay;

    if(after_trigger < 0)
        after_trigger = 0;

    fpga_delay = osc_fpga_cnv_time_to_smpls(after_trigger, fpga_dec_factor);

    /* Trig source is written after ARM */
    /*    g_osc_fpga_reg_mem->trig_source   = fpga_trig_source;*/
    if(trig_source == 0)
        g_osc_fpga_reg_mem->cha_thr   = fpga_trig_thr;
    else
        g_osc_fpga_reg_mem->chb_thr   = fpga_trig_thr;

    g_osc_fpga_reg_mem->data_dec      = fpga_dec_factor;
    g_osc_fpga_reg_mem->trigger_delay = (uint32_t)fpga_delay;
   
    /* Update equalization filter with desired coefficients. */
    g_osc_fpga_reg_mem->cha_filt_aa = cha_filt.aa;
    g_osc_fpga_reg_mem->cha_filt_bb = cha_filt.bb;
    g_osc_fpga_reg_mem->cha_filt_pp = cha_filt.pp;
    g_osc_fpga_reg_mem->cha_filt_kk = cha_filt.kk;
   
    g_osc_fpga_reg_mem->chb_filt_aa = chb_filt.aa;
    g_osc_fpga_reg_mem->chb_filt_bb = chb_filt.bb;
    g_osc_fpga_reg_mem->chb_filt_pp = chb_filt.pp;
    g_osc_fpga_reg_mem->chb_filt_kk = chb_filt.kk;
   
    return 0;
}

/** @brief OSC FPGA reset
 *
 * Triggers internal oscilloscope FPGA state machine reset.
 *
 * @retval 0 Always returns 0.
 */

int osc_fpga_reset(void)
{
    g_osc_fpga_reg_mem->conf |= OSC_FPGA_CONF_RST_BIT;
    return 0;
}

/** @brief OSC FPGA ARM
 *
 * ARM internal oscilloscope FPGA state machine to start writting input buffers.

 * @retval 0 Always returns 0.
 */

int osc_fpga_arm_trigger(void)
{
    g_osc_fpga_reg_mem->conf |= OSC_FPGA_CONF_ARM_BIT;

    return 0;
}

/** @brief Sets the trigger source in OSC FPGA register.
 *
 * Sets the trigger source in oscilloscope FPGA register.
 *
 * @param [in] trig_source Trigger source, as defined in FPGA register
 *                         description.
 */

int osc_fpga_set_trigger(uint32_t trig_source)
{
    g_osc_fpga_reg_mem->trig_source = trig_source;
    return 0;
}

/** @brief Sets the trigger delay in OSC FPGA register.
 *
 * Sets the trigger delay in oscilloscope FPGA register.
 *
 * @param [in] trig_delay Trigger delay, as defined in FPGA register
 *                         description.
 *
 * @retval 0 Always returns 0.
 */

int osc_fpga_set_trigger_delay(uint32_t trig_delay)
{
    g_osc_fpga_reg_mem->trigger_delay = trig_delay;
    return 0;
}

/** @brief Checks if FPGA detected trigger.
 *
 * This function checks if trigger was detected by the FPGA.
 *
 * @retval 0 Trigger not detected.
 * @retval 1 Trigger detected.
 */

int osc_fpga_triggered(void)
{
    return ((g_osc_fpga_reg_mem->trig_source & OSC_FPGA_TRIG_SRC_MASK)==0);
}

/** @brief Returns memory pointers for both input signal buffers.
 *
 * This function returns pointers for input signal buffers for both channels.
 *
 * @param [out] cha_signal Output pointer for Channel A buffer
 * @param [out] cha_signal Output pointer for Channel B buffer
 *
 * @retval 0 Always returns 0.
 */

int osc_fpga_get_sig_ptr(int **cha_signal, int **chb_signal)
{
    *cha_signal = (int *)g_osc_fpga_cha_mem;
    *chb_signal = (int *)g_osc_fpga_chb_mem;
    return 0;
}

/** @brief Returns values for current and trigger write FPGA pointers.
 *
 * This functions returns values for current and trigger write pointers. They
 * are an address of the input signal buffer and are the same for both channels.
 *
 * @param [out] wr_ptr_curr Current FPGA input buffer address.
 * @param [out] wr_ptr_trig Trigger FPGA input buffer address.
 *
 * @retval 0 Always returns 0.
  */

int osc_fpga_get_wr_ptr(int *wr_ptr_curr, int *wr_ptr_trig)
{
    if(wr_ptr_curr)
        *wr_ptr_curr = g_osc_fpga_reg_mem->wr_ptr_cur;
    if(wr_ptr_trig)
        *wr_ptr_trig = g_osc_fpga_reg_mem->wr_ptr_trigger;
    return 0;
}

/** @brief Convert trigger parameters to FPGA trigger source value.
 *
 * This function takes as an argument trigger parameters and converts it to
 * trigger source value used by the FPGA trigger source reigster.
 *
 * @param [in] trig_imm Trigger immediately, if set to 1 other trigger parameters
 *                      are ignored.
 * @param [in] trig_source Trigger source as defined in rp_main_params
 * @param [in] trig_edge Trigger edge as defined in rp_main_params
 *
 * @retval -1 Error
 * @retval otherwise Trigger source FPGA value
  */

int osc_fpga_cnv_trig_source(int trig_imm, int trig_source, int trig_edge)
{
    int fpga_trig_source = 0;

    /* Trigger immediately */    
    if(trig_imm)
        return 1;

    switch(trig_source) {
    case 0: /* ChA*/
        if(trig_edge == 0)
            fpga_trig_source = 2;
        else
            fpga_trig_source = 3;
        break;
    case 1: /* ChB*/
        if(trig_edge == 0)
            fpga_trig_source = 4;
        else
            fpga_trig_source = 5;
        break;
    case 2: /* External */
        if(trig_edge == 0)
            fpga_trig_source = 6;
        else
            fpga_trig_source = 7;

        break;
    default:
        /* Error */
        return -1;
    }

    return fpga_trig_source;
}

/** @brief Converts time range to decimation value.
 *
 * This function converts time range value defined by rp_main_params to
 * decimation factor value.
 *
 * @param [in] time_range Time range, integer between 0 and 5, as defined by
 *                        rp_main_params.
 *
 * @retval -1 Error
 *
 * @retval otherwise Decimation factor.
*/

int osc_fpga_cnv_time_range_to_dec(int time_range)
{
    /* Input: 0, 1, 2, 3, 4, 5 translates to:
     * Output: 1x, 8x, 64x, 1kx, 8kx, 65kx */

    switch(time_range) {
    case 0:
        return 1;
        break;
    case 1:
        return 8;
        break;
    case 2:
        return 64;
        break;
    case 3:
        return 1024;
        break;
    case 4:
        return 8*1024;
        break;
    case 5:
        return 64*1024;
        break;
    default:
        return -1;
    }

    return -1;
}

/** @brief Converts time to number of samples.
 *
 * This function converts time in [s], based on current decimation factor to
 * number of samples at ADC sampling frequency.
 *
 * @param [in] time Time in [s]
 * @param [in] dec_factor Decimation factor
 *
 * @retval Number of ADC samples define dby input parameters.
 */

int osc_fpga_cnv_time_to_smpls(float time, int dec_factor)
{
    /* Calculate sampling period (including decimation) */
    float smpl_p = (c_osc_fpga_smpl_period * dec_factor);
    int fpga_smpls = (int)round(time / smpl_p);

    return fpga_smpls;
}

/** @brief Converts voltage to ADC counts.
 *
 * This function converts voltage in [V] to ADC counts.
 *
 * @param [in] voltage Voltage in [V]
 *
 * @retval adc_cnts ADC counts
 */

int osc_fpga_cnv_v_to_cnt(float voltage)
{
    int adc_cnts = 0;

    if((voltage > c_osc_fpga_adc_max_v) || (voltage < -c_osc_fpga_adc_max_v))
        return -1;
   
    adc_cnts = (int)round(voltage * (float)((int)(1<<c_osc_fpga_adc_bits)) /
                          (2*c_osc_fpga_adc_max_v));

    /* Clip highest value (+14 is calculated in int32_t to 0x2000, but we have
     * only 14 bits
     */

    if((voltage > 0) && (adc_cnts & (1<<(c_osc_fpga_adc_bits-1))))
        adc_cnts = (1<<(c_osc_fpga_adc_bits-1))-1;
    else
        adc_cnts = adc_cnts & ((1<<(c_osc_fpga_adc_bits))-1);

    return adc_cnts;
}

/** @brief Converts ADC counts to voltage
 *
 * This function converts ADC counts to voltage (in [V])
 *
 * @param [in] cnts ADC counts
 *
 * @retval voltage Voltage in [V]
 */

float osc_fpga_cnv_cnt_to_v(int cnts)
{
    int m;

    if(cnts & (1<<(c_osc_fpga_adc_bits-1))) {
        /* negative number */
        m = -1 *((cnts ^ ((1<<c_osc_fpga_adc_bits)-1)) + 1);
    } else {
        m = cnts;
        /* positive number */
    }
    return m;
}