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