RMUL2025/lib/cmsis_5/CMSIS/DSP/PythonWrapper/docs/source/api.rst

225 lines
6.4 KiB
ReStructuredText
Executable File

API
===
.. highlight:: python
The idea is to follow as closely as possible the CMSIS-DSP API to ease the migration to the final implementation on a board.
First you need to import the module::
import cmsisdsp as dsp
If you use numpy::
import numpy as np
If you use scipy signal processing functions::
from scipy import signal
Standard APIs
*************
.. code-block:: C
void arm_add_f32(
const float32_t * pSrcA,
const float32_t * pSrcB,
float32_t * pDst,
uint32_t blockSize);
.. py:function:: dsp.arm_add_f32(pSrcA,pSrcB)
Return a list of random ingredients as strings.
:param pSrcA: array.
:type pSrcA: NumPy array
:param pSrcB: array.
:type pSrcB: NumPy array
:return: array.
:rtype: NumPy array
Example::
import cmsisdsp as dsp
r = dsp.arm_add_f32([1.,2,3],[4.,5,7])
You can use a CMSIS-DSP function with numpy arrays: ::
r = dsp.arm_add_f32(np.array([1.,2,3]),np.array([4.,5,7]))
The result of a CMSIS-DSP function will always be a numpy array whatever the arguments were (numpy array or list).
Functions with instance arguments
*********************************
When the CMSIS-DSP function is requiring an instance data structure, it is just a bit more complex to use it:
First you need to create this instance::
import cmsisdsp as dsp
firf32 = dsp.arm_fir_instance_f32()
.. code-block:: C
void arm_fir_init_f32(
arm_fir_instance_f32 * S,
uint16_t numTaps,
const float32_t * pCoeffs,
float32_t * pState,
uint32_t blockSize);
.. py:function:: dsp.arm_fir_init_f32(S,numTaps,pCoeffs,pState)
Return a list of random ingredients as strings.
:param S: f32 instance.
:type S: int
:param pCoeffs: array.
:type pCoeffs: NumPy array
:param pState: array.
:type pState: NumPy array
:return: array.
:rtype: NumPy array
Example of use::
dsp.arm_fir_init_f32(firf32,3,[1.,2,3],[0,0,0,0,0,0,0])
The third argument in this function is the state. Since all arguments (except the instance ones) are read-only in this Python API, this state will never be changed ! It is just used to communicate the length of the state array which must be allocated by the init function. This argument is required because it is present in the CMSIS-DSP API and in the final C implementation you'll need to allocate a state array with the right dimension.
Since the goal is to be as close as possible to the C API, the API is forcing the use of this argument.
The only change compared to the C API is that the size variables (like blockSize for filter) are computed automatically from the other arguments. This choice was made to make it a bit easier the use of numpy array with the API.
Now, you can check that the instance was initialized correctly::
.. code-block:: python
print(firf32.numTaps())
The filter can then be called:
.. code-block:: C
void arm_fir_f32(
const arm_fir_instance_f32 * S,
const float32_t * pSrc,
float32_t * pDst,
uint32_t blockSize);
.. py:function:: dsp.arm_fir_f32(S,pSrc)
Return a list of random ingredients as strings.
:param S: f32 instance.
:type S: int
:param pSrc: array of input samples.
:type pSrc: NumPy array
:return: array.
:rtype: NumPy array
Then, you can filter with CMSIS-DSP::
print(dsp.arm_fir_f32(firf32,[1,2,3,4,5]))
The size of this signal should be blockSize. blockSize was inferred from the size of the state array : numTaps + blockSize - 1 according to CMSIS-DSP. So here the signal must have 5 samples.
If you want to filter more than 5 samples, then you can just call the function again. The state variable inside firf32 will ensure that it works like in the CMSIS-DSP C code::
print(dsp.arm_fir_f32(firf32,[6,7,8,9,10]))
If you want to compare with scipy it is easy but warning : coefficients for the filter are in opposite order in scipy ::
filtered_x = signal.lfilter([3,2,1.], 1.0, [1,2,3,4,5,6,7,8,9,10])
print(filtered_x)
FFT
***
The CMSIS-DSP cfft is requiring complex signals with a specific layout in memory.
To remain as close as possible to the C API, we are not using complex numbers in the wrapper. So a complex signal must be converted into a real one. A function like the bewlo one can be used::
def imToReal1D(a):
ar=np.zeros(np.array(a.shape) * 2)
ar[0::2]=a.real
ar[1::2]=a.imag
return(ar)
In the same way, the return array from the CMSIS-DSP FFT will not be containing complex Python scalars. It must be converted back with a function like::
def realToIm1D(ar):
return(ar[0::2] + 1j * ar[1::2])
Then, the utilisation of the API si very similar to what was done for the FIR example:
Then, you create the FFT instance with::
cfftf32=dsp.arm_cfft_instance_f32()
You initialize the instance with the init function ::
status=dsp.arm_cfft_init_f32(cfftf32, nb)
print(status)
You convert the complex signal to the format expected by the wrapper::
signalR = imToReal1D(signal)
You compute the FFT of the signal with::
resultR = dsp.arm_cfft_f32(cfftf32,signalR,0,1)
You convert back to a complex format to compare with scipy::
resultI = realToIm1D(resultR)
print(resultI)
Matrix
******
For matrix, the instance variables are masked by the Python API. We decided that for matrix only there was no use for having the CMSIS-DSP instance visibles since they contain the same information as the numpy array (samples and dimension).
So to use a CMSIS-DSP matrix function, it is very simple::
a=np.array([[1.,2,3,4],[5,6,7,8],[9,10,11,12]])
b=np.array([[1.,2,3],[5.1,6,7],[9.1,10,11],[5,8,4]])
Numpy result as reference::
print(np.dot(a , b))
CMSIS-DSP result::
v=dsp.arm_mat_mult_f32(a,b)
print(v)
In a real C code, a pointer to a data structure for the result v would have to be passed as argument of the function.
The C API is:
.. code-block:: C
arm_status arm_mat_mult_f32(
const arm_matrix_instance_f32 * pSrcA,
const arm_matrix_instance_f32 * pSrcB,
arm_matrix_instance_f32 * pDst);
The Python API is:
.. py:function:: dsp.arm_mat_mult_f32(pSrcA,pSrcB)
Return the matrix product pSrcA * pSrcB
:param pSrcA: array of input samples.
:type pSrcA: NumPy array
:param pSrcB: array of input samples.
:type pSrcB: NumPy array
:return: the matrix product.
:rtype: NumPy array