21 | | Given the previous definition of an object, we define a directory structure for its implementation in order to homogenize objects from several sources and to access easily to useful information. Generating a new object should follow this layout: |
22 | | |
23 | | ||'''File/Directory'''||'''Comment'''|| |
24 | | ||''myobj''/interf/||A file with name ''interface_name.h'' for each input and output interface defining the used data structure and in case of input ones, an explanation of the object behavior given that input. [[BR]][[BR]]See example: input.h control.h|| |
25 | | ||''myobj''/src/||The source code of your object. Among others, the principal one (where the main() is) with the name ''myobj.c''|| |
26 | | ||''myobj''/bin/||Binaries after compilation and linking are placed here.|| |
27 | | ||myobj/props/||'''Under Work: '''A file with name ''resources.conf ''specifying demanded resource utilization given certain parameters and a file with name ''parameters.conf'' defining the set of initialization parameters and its behavior.|| |
28 | | ||myobj/Makefile||For compilation|| |
29 | | |
| 16 | Given the previous definition of an object, we will organize object files following this directory structure: |
| 17 | |
| 18 | ||'''Directory'''||'''Files'''||'''Comment'''|| |
| 19 | ||''myobj''/interfaces/||inputs.h[[BR]]outputs.h[[BR]]stats.h||Definition of input/output interfaces, statistics variables and initialization parameters|| |
| 20 | ||''myobj''/src/||myobj.c[[BR]]myobj_imp.c[[BR]]xxx.c||Implementation (myobj_imp.c + xxx.c) of the algorithm and main skeleton (myobj.c)[[BR]][[BR]]See below for more information|| |
| 21 | ||''myobj''/bin/||myobj||Binaries after compilation and linking are placed here.|| |
| 22 | |
| 23 | == == |
71 | | The following concepts should be taken into account when designing an object: |
72 | | |
73 | | * Sampling frequency and time slot is unknown: the length of input and output packet (packet length over time slot duration equals sampling frequency) should be allowed to change and should be limited. |
74 | | |
75 | | * Our goal is to write to the output interface certain number of samples (!GetTempo()) each execution cycle (time-slot) ensuring a constant mean sampling frequency along time. |
76 | | |
77 | | * Processing time over data unit (cycles/sample) should be taken into account when defining the required processing resources depending on the operating frequency. |
78 | | |
79 | | * The format of the input and output data has to be well defined (integers, short, signed/unsigned, etc.). |
80 | | |
81 | | * If control data must be received, another interface '''different '''from the data in/out interfaces should be used, in such case, an struct may be defined to receive such parameters. |
82 | | |
83 | | * The values acquired from parameters initialization should always be checked, to ensure they follow our local limitations (i.e. buffer sizes). |
84 | | |
85 | | * '''Always''' define constants as compiler constant definitions (#define …). This facilitates code reusing and maintenance. |
86 | | |
87 | | The following example provides a template of the main object file named, conventionally, as ''myobj.c''. Notice how we first include definition files for each interface we are going to use and the header files for ALOE API utilization. Then, we fall to an infinite loop where we continuously check for an status and execute it, then, we relinquish the cpu. |
88 | | |
89 | | '''Figure 2 – Main example source (myobj.c)''' |
| 65 | In order to facilitate objects programming an skeleton implementing some common functionalities has been defined. In this sense, the user will only have to define the interfaces in a constant structure and provide some functions to process data. The initialization and buffer management will be done by the skeleton. |
| 66 | |
| 67 | This skeleton is provided in the myobj.c file and is exactly the same for any object (so you can copy anyone provided by the example objects). The skeleton needs three header files: inputs.h, outputs.h and stats.h. Interfaces and statistics are defined in structures (defined in swapi_utils.h) which name has to be mantained (input_itfs, output_itfs, params and stats). The following figure describe these structures: |
| 68 | |
| 69 | '''Figure 2 – Structure definitions (swapi_utils.h)''' |
| 70 | |
| 71 | {{{ |
| 72 | |
| 73 | /** Structure for logic interfaces |
| 74 | * |
| 75 | * Regardless the direction of the interface, the user must provide |
| 76 | * a buffer for storing data of size max_buffer_len elements of size sample_sz (in bytes). |
| 77 | * |
| 78 | * For input interfaces. Set the variable name to input_itf: |
| 79 | * process_fnc() will be called as soon as the required amount of |
| 80 | * samples is available. This number is provided by the function get_block_sz() |
| 81 | * which will be called at the beginning of every timeslot (if needed). |
| 82 | * Setting get_block_sz() to NULL is equivalent to returning 0 from this function. |
| 83 | * As soon as any data is available the processing function will be called. |
| 84 | * |
| 85 | * For output interfaces. Set the variable name to output_itf: |
| 86 | * If data needs to be generated, point get_block_sz() to a function returning |
| 87 | * the amount of samples to be generated (you can use GetTempo to convert from |
| 88 | * frequency to number of samples). Then, process_fnc() will be called to |
| 89 | * generate such data. |
| 90 | * If data is being provided synchronously to any input interface, set get_block_sz |
| 91 | * to NULL and use SendItf() function to send the data. |
| 92 | * |
| 93 | * Example: |
| 94 | * |
| 95 | * Configure an input interface which can process any amount of samples (not block oriented) |
| 96 | * |
| 97 | * typedef int input1_t; |
| 98 | * |
| 99 | * struct utils__itf input_itfs[] = { |
| 100 | * {"myInputInterface", |
| 101 | * sizeof(input1_t), |
| 102 | * 1024, |
| 103 | * input_buffer, |
| 104 | * NULL, |
| 105 | * process_input}, |
| 106 | * |
| 107 | * {NULL, 0, 0, 0, 0, 0}}; |
| 108 | * |
| 109 | * |
| 110 | */ |
| 111 | struct utils_itf { |
| 112 | char *name; /**<< Name of the interface */ |
| 113 | int sample_sz; /**<< Size of the sample, in bytes */ |
| 114 | int max_buffer_len; /**<< Max buffer length in samples */ |
| 115 | void *buffer; /**<< Buffer where to store data */ |
| 116 | int (*get_block_sz) (); /**<< Returns number of samples to read/generate. Returning <0 interrupts execution*/ |
| 117 | int (*process_fnc) (int); /**<< Function to process/generate data. Returning 0 interrupts execution */ |
| 118 | }; |
| 119 | |
| 120 | |
| 121 | struct utils_param { |
| 122 | char *name; /**< Name of the parameter */ |
| 123 | char type; /**< Type of the parameter*/ |
| 124 | int size; /**< Size of the parameter value */ |
| 125 | void *value; /**< Pointer to the value to obtain. |
| 126 | Make sure the buffer is big enough (len) */ |
| 127 | }; |
| 128 | |
| 129 | |
| 130 | /** Structure for statistics |
| 131 | * |
| 132 | * You can automatically initialize a variable if you set the configuration |
| 133 | * in this structure. |
| 134 | * The id of the initialized variable is stored in the id pointer and you can |
| 135 | * also initialize the variable with a value if the pointer value is set to any |
| 136 | * non-NULL value. |
| 137 | * |
| 138 | * Set update_mode to READ to automatically read the contents of the variable |
| 139 | * at the beggining of every timeslot (with the contents at value with fixed length size). |
| 140 | * |
| 141 | * Set update_mode to WRITE to automatically set the contents of the variable |
| 142 | * at the end of every timeslot (with the contents at value with fixed length size). |
| 143 | * |
| 144 | * Set update_mode to OFF to disable automatic updates. |
| 145 | */ |
| 146 | struct utils_stat { |
| 147 | char *name; /**< Name of the variable */ |
| 148 | char type; /**< Type of the variable */ |
| 149 | int size; /**< Size of the variable, in elements (of type) */ |
| 150 | int *id; /**< Pointer to store the id for the stat */ |
| 151 | void *value; /**< Initial value for stat */ |
| 152 | enum stat_update update_mode; /**< Mode for automatically updating */ |
| 153 | }; |
| 154 | |
| 155 | |
| 156 | }}} |
| 157 | You can read more of how to use these structures in the example objects. If you downloaded the source, they are in the example directory. |
| 158 | |
| 159 | Finally, the following figure shows the file where the processing code should be written. Note that in the configuration of the structure we have provided a function to process (or generate) data for every interface. Now, the only thing we have to do is to code these functions: |
| 160 | |
| 161 | '''Figure 3 – Object implementation (myobj_imp.c)''' |
96 | | |
97 | | #include "input.h" /**< Definition of interface 'input' */ |
98 | | #include "control.h" /**< Definition of interface 'control' */ |
99 | | #include "output.h" /**< Definition of interface 'output' */ |
100 | | |
101 | | /** Main |
102 | | */ |
103 | | void main() { |
104 | | |
105 | | int status; |
106 | | |
107 | | /* initialize PHAL */ |
108 | | InitPHAL(); |
109 | | |
110 | | while(1) { |
111 | | switch(Status()) { |
112 | | case PHAL_STATUS_INIT: |
113 | | /** Call INITIALIZATION function */ |
114 | | if (!Init()) { |
115 | | ClosePHAL(); |
116 | | exit(0); |
117 | | } |
118 | | break; |
119 | | case PHAL_STATUS_RUN: |
120 | | /** Call RUN function */ |
121 | | if (!Run()) { |
122 | | ClosePHAL(); |
123 | | exit(0); |
124 | | } |
125 | | break; |
126 | | default: |
127 | | /** rest, is STOP */ |
128 | | ClosePHAL(); |
129 | | exit(0); |
130 | | break; |
131 | | } |
132 | | Relinquish(); |
133 | | } |
134 | | } |
135 | | }}} |
136 | | Now following figures shows the content of input.h output.h and control.h here control is an interface to modify object behavior. '''NOTE '''the difference between this control variables and the initialization parameters: any the input we provide to this control interface will produce a change in the object behavior (reconfiguration) '''within '''the time/memory specifications. That means, for example, that certain reconfigurations, lets say, compute filter coeficients may not be realized inside the time window producing a real time failure, thus can not be performed during the execution (RUN) phase (should be realized during the initialization phase, as an initialization parameter). |
137 | | |
138 | | '''Figure 3 – Interface example definitions (input.h)''' |
139 | | |
140 | | {{{ |
141 | | /** Interface Name: input |
142 | | * |
143 | | * Data type: 32-bit signed integer |
144 | | * Max input size: 1024 elements |
145 | | * Min input size: 128 elements |
146 | | * |
147 | | * Description: Input stream of data |
148 | | */ |
149 | | |
150 | | #define INPUTITF_NAME "input" |
151 | | |
152 | | #define INPUT_MAX_DATA 1024 |
153 | | #define INPUT_MIN_DATA 128 |
154 | | |
155 | | struct input_itf { |
156 | | int data[INPUT_MAX_DATA]; |
157 | | }; |
158 | | |
159 | | |
160 | | }}} |
161 | | '''Figure 4 – Interface example definitions (output.h)''' |
162 | | |
163 | | {{{ |
164 | | /** Interface Name: output |
165 | | * |
166 | | * Data type: 16-bit signed integer |
167 | | * Max output size: 2048 elements |
168 | | * Min output size: 256 elements |
169 | | * |
170 | | * Description: Output stream of data |
171 | | */ |
172 | | |
173 | | #define OUTPUTITF_NAME "output" |
174 | | |
175 | | #define OUTPUT_MAX_DATA 2048 |
176 | | #define OUTPUT_MIN_DATA 256 |
177 | | |
178 | | struct output_itf { |
179 | | short data[OUTPUT_MAX_DATA]; |
180 | | }; |
181 | | |
182 | | |
183 | | }}} |
184 | | '''Figure 5 – Interface example definitions (control.h)''' |
185 | | |
186 | | {{{ |
187 | | /** Interface Name: control |
188 | | * |
189 | | * Data type: Structure (see below) |
190 | | * Max input size: 1 element |
191 | | * Min input size: 1 element |
192 | | * |
193 | | * Description: Configures the behavior of the object etc. etc. |
194 | | */ |
195 | | |
196 | | #define CONTROL_MAX_DATA 1 |
197 | | #define INPUT_MIN_DATA 1 |
198 | | |
199 | | struct control_itf { |
200 | | unsigned char modulation_type; |
201 | | short modulation_levels; |
202 | | int roll_factor; |
203 | | }; |
204 | | |
205 | | |
206 | | }}} |
207 | | Finally, the following figure shows where actually the user code should be written. We provide here an skeleton to easy the deployment of new |
208 | | |
209 | | '''Figure 6 – Object implementation (myobj_imp.c)''' |
210 | | |
211 | | {{{ |
212 | | /** ALOE headers |
213 | | */ |
214 | | #include <phal.h> |
215 | | #include <phal_sw_api.h> |
216 | | |
217 | | #include "input.h" /**< Definition of interface 'input' */ |
218 | | #include "control.h" /**< Definition of interface 'control' */ |
219 | | #include "output.h" /**< Definition of interface 'output' */ |
220 | | |
221 | | #define DATA_INOUT_RATE 2 /**< Output/input frecuencies rate */ |
222 | | |
223 | | struct input_itf input; |
224 | | struct output_itf output; |
225 | | struct control_itf control; |
226 | | |
227 | | |
228 | | int fir_coef[FIR_COEF]; |
229 | | int fdi,fdo,fdc; |
230 | | int rcv_len; |
231 | | int long_in_block,long_out_block; |
232 | | float freq; |
233 | | int stat_outsignal; |
234 | | |
235 | | /** Init function |
236 | | * @return 1 if ok, 0 if error |
237 | | */ |
238 | | int Init() |
239 | | { |
240 | | int filter_type; |
241 | | |
242 | | fdi = CreateItf(INPUTITF_NAME, FLOW_READ_ONLY); |
243 | | if (fdi < 0) { |
244 | | WriteLog(“Error creating flow\n”); |
245 | | return 0; |
246 | | } |
247 | | fdo = CreateItf(OUTPUTITF_NAME, FLOW_WRITE_ONLY); |
248 | | if (fdo < 0) { |
249 | | WriteLog(“Error creating flow\n”); |
250 | | return 0; |
251 | | } |
252 | | fdc = CreateItf(“control”, FLOW_READ_ONLY); |
253 | | if (fdo < 0) { |
254 | | WriteLog(“Error creating flow\n”); |
255 | | return 0; |
256 | | } |
257 | | n = InitParamFile(); |
258 | | if (n < 0) { |
259 | | WriteLog (“Error initiating params file\n”); |
260 | | return 0; |
261 | | } |
262 | | n = GetParameter(“freq”, &freq, 1); |
263 | | if (n < 0) { |
264 | | WriteLog (“Error getting parameter\n”); |
265 | | return 0; |
266 | | } |
267 | | n = GetParameter(“filter_type”, &filter_type, 1); |
268 | | if (n < 0) { |
269 | | WriteLog (“Error getting parameter\n”); |
270 | | return 0; |
271 | | } |
272 | | |
273 | | /* perform non-realtime computations */ |
274 | | calc_fir_coefs(fir_coef,filter_type); |
275 | | |
276 | | stat_outsignal = InitStat(“out_signal”); |
277 | | if (stat_outsignal < 0) { |
278 | | WriteLog (“Error initiating stat\n”); |
279 | | return 0; |
280 | | } |
281 | | rcv_len = 0; |
282 | | return 1; |
| 167 | #include <math.h> |
| 168 | #include <swapi_utils.h> |
| 169 | |
| 170 | #include <phal_hw_api.h> |
| 171 | #include <sys/resource.h> |
| 172 | |
| 173 | #define PI 3.1415 |
| 174 | |
| 175 | #include "inputs.h" |
| 176 | #include "outputs.h" |
| 177 | #include "stats.h" |
| 178 | |
| 179 | char str[128]; |
| 180 | |
| 181 | int get_output_length() |
| 182 | { |
| 183 | /* use gettempo to get number of samples */ |
| 184 | return GetTempo(freq); |
312 | | /* receive a block of data */ |
313 | | do { |
314 | | n = ReadItf(fdi, &input.data[rcv_len], long_in_block - rcv_len); |
315 | | if (n < 0) { |
316 | | WriteLog(“Error reading from flow\n”); |
317 | | return 0; |
318 | | } else if (!n) { |
319 | | break; |
320 | | } |
321 | | rcv_len += n; |
322 | | } while(n && rcv_len < long_in_block); |
323 | | |
324 | | /* when enough data is received, process and send it */ |
325 | | if (rcv_len == long_block) { |
326 | | my_process_function(input, output, fir_coef, long_in_block); |
327 | | n = WriteItf(fdo, data_out.data, long_out_block); |
328 | | if (n < 0) { |
329 | | LogWrite(“Error writing to flow\n”); |
330 | | return 0; |
331 | | } else if (!n) { |
332 | | LogWrite(“Caution, missing packet due to full buffer\n”); |
333 | | } |
334 | | |
335 | | rcv_len=0; |
336 | | |
337 | | /* set stats variable */ |
338 | | SetStatsValue(stat_outsignal,output.data,long_out_block); |
339 | | } |
340 | | } |
341 | | }}} |
342 | | [[BR]] |
343 | | [[BR]] |
| 204 | |
| 205 | SetStatsValue(stat_signal, output_data, len); |
| 206 | |
| 207 | return 1; |
| 208 | } |
| 209 | |
| 210 | int InitCustom() |
| 211 | { |
| 212 | return 1; |
| 213 | } |
| 214 | |
| 215 | int RunCustom() |
| 216 | { |
| 217 | return 1; |
| 218 | } |
| 219 | |
| 220 | |
| 221 | }}} |
| 222 | The last two functions (InitCustom() and RunCustom()) have also to be defined. The InitCustom function is used to implement custom initialization routines not related with interface or statistics initialization. For example, if an object has to compute a set of filter coeficients given an initialization parameter, it can be done in this function. The skeleton will get the parameter(s) value if we define it in the specific structure. After that, the InitCustom function will be called and we will perform the computations. |
| 223 | |
| 224 | Analogously, the RunCustom function might be useful to perform some background tasks not related directly with data processing (more formally, asyncrhonous with data flow). An example of this may be updating an internal state. |
| 225 | |