/**
* $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) {
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) {
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) {
__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;
}