annotate core/PRU.cpp @ 528:5c8f46fcd4d0 API-update

Updated BelaContext to use separate values for in/ou channels
author Giulio Moro <giuliomoro@yahoo.it>
date Thu, 23 Jun 2016 18:17:35 +0100
parents 3a28a4eb948d
children
rev   line source
andrewm@0 1 /*
andrewm@0 2 * PRU.cpp
andrewm@0 3 *
andrewm@0 4 * Code for communicating with the Programmable Realtime Unit (PRU)
andrewm@0 5 * on the BeagleBone AM335x series processors. The PRU loads and runs
andrewm@0 6 * a separate code image compiled from an assembly file. Here it is
andrewm@0 7 * used to handle audio and SPI ADC/DAC data.
andrewm@0 8 *
andrewm@0 9 * This code is specific to the PRU code in the assembly file; for example,
andrewm@0 10 * it uses certain GPIO resources that correspond to that image.
andrewm@0 11 *
andrewm@0 12 * Created on: May 27, 2014
andrewm@0 13 * Author: andrewm
andrewm@0 14 */
andrewm@0 15
andrewm@0 16 #include "../include/PRU.h"
andrewm@0 17 #include "../include/prussdrv.h"
andrewm@0 18 #include "../include/pruss_intc_mapping.h"
giuliomoro@19 19 #include "../include/digital_gpio_mapping.h"
andrewm@0 20 #include "../include/GPIOcontrol.h"
giuliomoro@301 21 #include "../include/Bela.h"
andrewm@15 22 #include "../include/pru_rtaudio_bin.h"
andrewm@0 23
andrewm@0 24 #include <iostream>
andrewm@0 25 #include <stdlib.h>
andrewm@0 26 #include <cstdio>
andrewm@0 27 #include <cerrno>
andrewm@0 28 #include <fcntl.h>
andrewm@0 29 #include <sys/mman.h>
giuliomoro@16 30 #include <unistd.h>
andrewm@0 31
andrewm@0 32 // Xenomai-specific includes
andrewm@0 33 #include <sys/mman.h>
andrewm@0 34 #include <native/task.h>
andrewm@0 35 #include <native/timer.h>
andrewm@0 36 #include <rtdk.h>
andrewm@0 37
andrewm@0 38 using namespace std;
andrewm@0 39
andrewm@318 40 // Select whether to use NEON-based sample conversion
andrewm@318 41 // (this will probably go away in a future commit once its performance
andrewm@318 42 // is verified over extended use)
andrewm@318 43 #undef USE_NEON_FORMAT_CONVERSION
andrewm@318 44
andrewm@268 45 // PRU memory: PRU0 and PRU1 RAM are 8kB (0x2000) long each
andrewm@268 46 // PRU-SHARED RAM is 12kB (0x3000) long
andrewm@268 47
andrewm@0 48 #define PRU_MEM_MCASP_OFFSET 0x2000 // Offset within PRU-SHARED RAM
andrewm@253 49 #define PRU_MEM_MCASP_LENGTH 0x1000 // Length of McASP memory, in bytes
andrewm@0 50 #define PRU_MEM_DAC_OFFSET 0x0 // Offset within PRU0 RAM
andrewm@0 51 #define PRU_MEM_DAC_LENGTH 0x2000 // Length of ADC+DAC memory, in bytes
andrewm@0 52 #define PRU_MEM_COMM_OFFSET 0x0 // Offset within PRU-SHARED RAM
giuliomoro@19 53 #define PRU_MEM_DIGITAL_OFFSET 0x1000 //Offset within PRU-SHARED RAM
giuliomoro@19 54 #define MEM_DIGITAL_BUFFER1_OFFSET 0x400 //Start pointer to DIGITAL_BUFFER1, which is 256 words.
giuliomoro@16 55 // 256 is the maximum number of frames allowed
andrewm@280 56
andrewm@280 57 // Offsets within CPU <-> PRU communication memory (4 byte slots)
andrewm@0 58 #define PRU_SHOULD_STOP 0
andrewm@0 59 #define PRU_CURRENT_BUFFER 1
andrewm@0 60 #define PRU_BUFFER_FRAMES 2
andrewm@0 61 #define PRU_SHOULD_SYNC 3
andrewm@0 62 #define PRU_SYNC_ADDRESS 4
andrewm@0 63 #define PRU_SYNC_PIN_MASK 5
andrewm@253 64 #define PRU_LED_ADDRESS 6
andrewm@253 65 #define PRU_LED_PIN_MASK 7
andrewm@253 66 #define PRU_FRAME_COUNT 8
andrewm@253 67 #define PRU_USE_SPI 9
andrewm@12 68 #define PRU_SPI_NUM_CHANNELS 10
andrewm@253 69 #define PRU_USE_DIGITAL 11
andrewm@253 70 #define PRU_PRU_NUMBER 12
andrewm@280 71 #define PRU_MUX_CONFIG 13
andrewm@303 72 #define PRU_MUX_END_CHANNEL 14
giuliomoro@16 73
andrewm@280 74 short int digitalPins[NUM_DIGITALS] = {
giuliomoro@16 75 GPIO_NO_BIT_0,
giuliomoro@16 76 GPIO_NO_BIT_1,
giuliomoro@16 77 GPIO_NO_BIT_2,
giuliomoro@16 78 GPIO_NO_BIT_3,
giuliomoro@16 79 GPIO_NO_BIT_4,
giuliomoro@16 80 GPIO_NO_BIT_5,
giuliomoro@16 81 GPIO_NO_BIT_6,
giuliomoro@16 82 GPIO_NO_BIT_7,
giuliomoro@16 83 GPIO_NO_BIT_8,
giuliomoro@16 84 GPIO_NO_BIT_9,
giuliomoro@16 85 GPIO_NO_BIT_10,
giuliomoro@16 86 GPIO_NO_BIT_11,
giuliomoro@16 87 GPIO_NO_BIT_12,
giuliomoro@16 88 GPIO_NO_BIT_13,
giuliomoro@16 89 GPIO_NO_BIT_14,
giuliomoro@16 90 GPIO_NO_BIT_15,
giuliomoro@16 91 };
andrewm@0 92
andrewm@12 93 #define PRU_SAMPLE_INTERVAL_NS 11338 // 88200Hz per SPI sample = 11.338us
andrewm@0 94
andrewm@0 95 #define GPIO0_ADDRESS 0x44E07000
andrewm@0 96 #define GPIO1_ADDRESS 0x4804C000
andrewm@0 97 #define GPIO_SIZE 0x198
andrewm@0 98 #define GPIO_CLEARDATAOUT (0x190 / 4)
andrewm@0 99 #define GPIO_SETDATAOUT (0x194 / 4)
andrewm@0 100
andrewm@0 101 #define TEST_PIN_GPIO_BASE GPIO0_ADDRESS // Use GPIO0(31) for debugging
andrewm@0 102 #define TEST_PIN_MASK (1 << 31)
andrewm@0 103 #define TEST_PIN2_MASK (1 << 26)
andrewm@0 104
andrewm@0 105 #define USERLED3_GPIO_BASE GPIO1_ADDRESS // GPIO1(24) is user LED 3
andrewm@0 106 #define USERLED3_PIN_MASK (1 << 24)
andrewm@0 107
andrewm@0 108 const unsigned int PRU::kPruGPIODACSyncPin = 5; // GPIO0(5); P9-17
andrewm@0 109 const unsigned int PRU::kPruGPIOADCSyncPin = 48; // GPIO1(16); P9-15
andrewm@0 110
andrewm@0 111 const unsigned int PRU::kPruGPIOTestPin = 60; // GPIO1(28); P9-12
andrewm@0 112 const unsigned int PRU::kPruGPIOTestPin2 = 31; // GPIO0(31); P9-13
andrewm@0 113 const unsigned int PRU::kPruGPIOTestPin3 = 26; // GPIO0(26); P8-14
andrewm@0 114
giuliomoro@231 115 extern int gShouldStop;
andrewm@0 116 extern int gRTAudioVerbose;
andrewm@0 117
andrewm@318 118 // These four functions are written in assembly in FormatConvert.S
andrewm@318 119 extern "C" {
andrewm@318 120 void int16_to_float_audio(int numSamples, int16_t *inBuffer, float *outBuffer);
andrewm@318 121 void int16_to_float_analog(int numSamples, uint16_t *inBuffer, float *outBuffer);
andrewm@318 122 void float_to_int16_audio(int numSamples, float *inBuffer, int16_t *outBuffer);
andrewm@318 123 void float_to_int16_analog(int numSamples, float *inBuffer, uint16_t *outBuffer);
andrewm@318 124 }
andrewm@318 125
andrewm@0 126 // Constructor: specify a PRU number (0 or 1)
andrewm@307 127 PRU::PRU(InternalBelaContext *input_context)
andrewm@45 128 : context(input_context), pru_number(0), running(false), analog_enabled(false),
andrewm@45 129 digital_enabled(false), gpio_enabled(false), led_enabled(false),
andrewm@303 130 mux_channels(0),
andrewm@45 131 gpio_test_pin_enabled(false),
andrewm@45 132 pru_buffer_comm(0), pru_buffer_spi_dac(0), pru_buffer_spi_adc(0),
andrewm@45 133 pru_buffer_digital(0), pru_buffer_audio_dac(0), pru_buffer_audio_adc(0),
andrewm@45 134 xenomai_gpio_fd(-1), xenomai_gpio(0)
andrewm@0 135 {
andrewm@0 136
andrewm@0 137 }
andrewm@0 138
andrewm@0 139 // Destructor
andrewm@0 140 PRU::~PRU()
andrewm@0 141 {
andrewm@0 142 if(running)
andrewm@0 143 disable();
andrewm@0 144 if(gpio_enabled)
andrewm@0 145 cleanupGPIO();
andrewm@0 146 if(xenomai_gpio_fd >= 0)
andrewm@0 147 close(xenomai_gpio_fd);
andrewm@0 148 }
andrewm@0 149
andrewm@0 150 // Prepare the GPIO pins needed for the PRU
andrewm@0 151 // If include_test_pin is set, the GPIO output
andrewm@0 152 // is also prepared for an output which can be
andrewm@0 153 // viewed on a scope. If include_led is set,
andrewm@0 154 // user LED 3 on the BBB is taken over by the PRU
andrewm@0 155 // to indicate activity
andrewm@45 156 int PRU::prepareGPIO(int include_test_pin, int include_led)
andrewm@0 157 {
andrewm@45 158 if(context->analogFrames != 0) {
andrewm@0 159 // Prepare DAC CS/ pin: output, high to begin
andrewm@0 160 if(gpio_export(kPruGPIODACSyncPin)) {
andrewm@0 161 if(gRTAudioVerbose)
andrewm@0 162 cout << "Warning: couldn't export DAC sync pin\n";
andrewm@0 163 }
andrewm@0 164 if(gpio_set_dir(kPruGPIODACSyncPin, OUTPUT_PIN)) {
andrewm@0 165 if(gRTAudioVerbose)
andrewm@0 166 cout << "Couldn't set direction on DAC sync pin\n";
andrewm@0 167 return -1;
andrewm@0 168 }
andrewm@0 169 if(gpio_set_value(kPruGPIODACSyncPin, HIGH)) {
andrewm@0 170 if(gRTAudioVerbose)
andrewm@0 171 cout << "Couldn't set value on DAC sync pin\n";
andrewm@0 172 return -1;
andrewm@0 173 }
andrewm@0 174
andrewm@0 175 // Prepare ADC CS/ pin: output, high to begin
andrewm@0 176 if(gpio_export(kPruGPIOADCSyncPin)) {
andrewm@0 177 if(gRTAudioVerbose)
andrewm@0 178 cout << "Warning: couldn't export ADC sync pin\n";
andrewm@0 179 }
andrewm@0 180 if(gpio_set_dir(kPruGPIOADCSyncPin, OUTPUT_PIN)) {
andrewm@0 181 if(gRTAudioVerbose)
andrewm@0 182 cout << "Couldn't set direction on ADC sync pin\n";
andrewm@0 183 return -1;
andrewm@0 184 }
andrewm@0 185 if(gpio_set_value(kPruGPIOADCSyncPin, HIGH)) {
andrewm@0 186 if(gRTAudioVerbose)
andrewm@0 187 cout << "Couldn't set value on ADC sync pin\n";
andrewm@0 188 return -1;
andrewm@0 189 }
andrewm@0 190
andrewm@45 191 analog_enabled = true;
andrewm@0 192 }
andrewm@0 193
andrewm@45 194 if(context->digitalFrames != 0){
andrewm@45 195 for(unsigned int i = 0; i < context->digitalChannels; i++){
giuliomoro@19 196 if(gpio_export(digitalPins[i])) {
giuliomoro@16 197 if(gRTAudioVerbose)
giuliomoro@38 198 cerr << "Warning: couldn't export digital GPIO pin " << digitalPins[i] << "\n"; // this is left as a warning because if the pin has been exported by somebody else, can still be used
giuliomoro@16 199 }
giuliomoro@38 200 if(gpio_set_dir(digitalPins[i], INPUT_PIN)) {
giuliomoro@16 201 if(gRTAudioVerbose)
giuliomoro@38 202 cerr << "Error: Couldn't set direction on digital GPIO pin " << digitalPins[i] << "\n";
giuliomoro@16 203 return -1;
giuliomoro@16 204 }
giuliomoro@16 205 }
andrewm@45 206 digital_enabled = true;
giuliomoro@16 207 }
giuliomoro@16 208
andrewm@0 209 if(include_test_pin) {
andrewm@0 210 // Prepare GPIO test output (for debugging), low to begin
andrewm@0 211 if(gpio_export(kPruGPIOTestPin)) {
andrewm@0 212 if(gRTAudioVerbose)
andrewm@0 213 cout << "Warning: couldn't export GPIO test pin\n";
andrewm@0 214 }
andrewm@0 215 if(gpio_set_dir(kPruGPIOTestPin, OUTPUT_PIN)) {
andrewm@0 216 if(gRTAudioVerbose)
andrewm@0 217 cout << "Couldn't set direction on GPIO test pin\n";
andrewm@0 218 return -1;
andrewm@0 219 }
andrewm@0 220 if(gpio_set_value(kPruGPIOTestPin, LOW)) {
andrewm@0 221 if(gRTAudioVerbose)
andrewm@0 222 cout << "Couldn't set value on GPIO test pin\n";
andrewm@0 223 return -1;
andrewm@0 224 }
andrewm@0 225
andrewm@0 226 if(gpio_export(kPruGPIOTestPin2)) {
andrewm@0 227 if(gRTAudioVerbose)
andrewm@0 228 cout << "Warning: couldn't export GPIO test pin 2\n";
andrewm@0 229 }
andrewm@0 230 if(gpio_set_dir(kPruGPIOTestPin2, OUTPUT_PIN)) {
andrewm@0 231 if(gRTAudioVerbose)
andrewm@0 232 cout << "Couldn't set direction on GPIO test pin 2\n";
andrewm@0 233 return -1;
andrewm@0 234 }
andrewm@0 235 if(gpio_set_value(kPruGPIOTestPin2, LOW)) {
andrewm@0 236 if(gRTAudioVerbose)
andrewm@0 237 cout << "Couldn't set value on GPIO test pin 2\n";
andrewm@0 238 return -1;
andrewm@0 239 }
andrewm@0 240
andrewm@0 241 if(gpio_export(kPruGPIOTestPin3)) {
andrewm@0 242 if(gRTAudioVerbose)
andrewm@0 243 cout << "Warning: couldn't export GPIO test pin 3\n";
andrewm@0 244 }
andrewm@0 245 if(gpio_set_dir(kPruGPIOTestPin3, OUTPUT_PIN)) {
andrewm@0 246 if(gRTAudioVerbose)
andrewm@0 247 cout << "Couldn't set direction on GPIO test pin 3\n";
andrewm@0 248 return -1;
andrewm@0 249 }
andrewm@0 250 if(gpio_set_value(kPruGPIOTestPin3, LOW)) {
andrewm@0 251 if(gRTAudioVerbose)
andrewm@0 252 cout << "Couldn't set value on GPIO test pin 3\n";
andrewm@0 253 return -1;
andrewm@0 254 }
andrewm@0 255 gpio_test_pin_enabled = true;
andrewm@0 256 }
andrewm@0 257
andrewm@0 258 if(include_led) {
andrewm@0 259 // Turn off system function for LED3 so it can be reused by PRU
andrewm@0 260 led_set_trigger(3, "none");
andrewm@0 261 led_enabled = true;
andrewm@0 262 }
andrewm@0 263
andrewm@0 264 gpio_enabled = true;
andrewm@0 265
andrewm@0 266 return 0;
andrewm@0 267 }
andrewm@0 268
andrewm@0 269 // Clean up the GPIO at the end
andrewm@0 270 void PRU::cleanupGPIO()
andrewm@0 271 {
andrewm@0 272 if(!gpio_enabled)
andrewm@0 273 return;
andrewm@45 274 if(analog_enabled) {
andrewm@0 275 gpio_unexport(kPruGPIODACSyncPin);
andrewm@0 276 gpio_unexport(kPruGPIOADCSyncPin);
andrewm@0 277 }
giuliomoro@19 278 if(digital_enabled){
andrewm@45 279 for(unsigned int i = 0; i < context->digitalChannels; i++){
giuliomoro@19 280 gpio_unexport(digitalPins[i]);
giuliomoro@16 281 }
giuliomoro@16 282 }
andrewm@0 283 if(gpio_test_pin_enabled) {
andrewm@0 284 gpio_unexport(kPruGPIOTestPin);
andrewm@0 285 gpio_unexport(kPruGPIOTestPin2);
andrewm@0 286 gpio_unexport(kPruGPIOTestPin3);
andrewm@0 287 }
andrewm@0 288 if(led_enabled) {
andrewm@0 289 // Set LED back to default eMMC status
andrewm@0 290 // TODO: make it go back to its actual value before this program,
andrewm@0 291 // rather than the system default
andrewm@0 292 led_set_trigger(3, "mmc1");
andrewm@0 293 }
andrewm@0 294 gpio_enabled = gpio_test_pin_enabled = false;
andrewm@0 295 }
andrewm@0 296
andrewm@0 297 // Initialise and open the PRU
andrewm@280 298 int PRU::initialise(int pru_num, int frames_per_buffer, int spi_channels, int mux_channels, bool xenomai_test_pin)
andrewm@0 299 {
andrewm@0 300 uint32_t *pruMem = 0;
andrewm@0 301
andrewm@0 302 if(!gpio_enabled) {
andrewm@0 303 rt_printf("initialise() called before GPIO enabled\n");
andrewm@0 304 return 1;
andrewm@0 305 }
andrewm@0 306
andrewm@0 307 pru_number = pru_num;
andrewm@303 308 this->mux_channels = mux_channels;
andrewm@0 309
andrewm@0 310 /* Initialize structure used by prussdrv_pruintc_intc */
andrewm@0 311 /* PRUSS_INTC_INITDATA is found in pruss_intc_mapping.h */
andrewm@0 312 tpruss_intc_initdata pruss_intc_initdata = PRUSS_INTC_INITDATA;
andrewm@0 313
andrewm@0 314 /* Allocate and initialize memory */
andrewm@0 315 prussdrv_init();
andrewm@45 316 if(prussdrv_open(PRU_EVTOUT_0)) {
andrewm@0 317 rt_printf("Failed to open PRU driver\n");
andrewm@0 318 return 1;
andrewm@0 319 }
andrewm@0 320
andrewm@0 321 /* Map PRU's INTC */
andrewm@0 322 prussdrv_pruintc_init(&pruss_intc_initdata);
andrewm@0 323
andrewm@0 324 /* Map PRU memory to pointers */
andrewm@0 325 prussdrv_map_prumem (PRUSS0_SHARED_DATARAM, (void **)&pruMem);
andrewm@0 326 pru_buffer_comm = (uint32_t *)&pruMem[PRU_MEM_COMM_OFFSET/sizeof(uint32_t)];
andrewm@0 327 pru_buffer_audio_dac = (int16_t *)&pruMem[PRU_MEM_MCASP_OFFSET/sizeof(uint32_t)];
andrewm@0 328
andrewm@12 329 /* ADC memory starts 2(ch)*2(buffers)*bufsize samples later */
andrewm@45 330 pru_buffer_audio_adc = &pru_buffer_audio_dac[4 * context->audioFrames];
andrewm@0 331
andrewm@45 332 if(analog_enabled) {
andrewm@0 333 prussdrv_map_prumem (pru_number == 0 ? PRUSS0_PRU0_DATARAM : PRUSS0_PRU1_DATARAM, (void **)&pruMem);
andrewm@0 334 pru_buffer_spi_dac = (uint16_t *)&pruMem[PRU_MEM_DAC_OFFSET/sizeof(uint32_t)];
andrewm@0 335
andrewm@12 336 /* ADC memory starts after N(ch)*2(buffers)*bufsize samples */
giuliomoro@528 337 pru_buffer_spi_adc = &pru_buffer_spi_dac[2 * context->analogInChannels * context->analogFrames];
andrewm@0 338 }
andrewm@0 339 else {
andrewm@0 340 pru_buffer_spi_dac = pru_buffer_spi_adc = 0;
andrewm@0 341 }
andrewm@0 342
giuliomoro@19 343 if(digital_enabled) {
giuliomoro@16 344 prussdrv_map_prumem (PRUSS0_SHARED_DATARAM, (void **)&pruMem);
giuliomoro@19 345 pru_buffer_digital = (uint32_t *)&pruMem[PRU_MEM_DIGITAL_OFFSET/sizeof(uint32_t)];
giuliomoro@16 346 }
giuliomoro@16 347 else {
giuliomoro@19 348 pru_buffer_digital = 0;
giuliomoro@16 349 }
andrewm@45 350
andrewm@0 351 /* Set up flags */
andrewm@0 352 pru_buffer_comm[PRU_SHOULD_STOP] = 0;
andrewm@0 353 pru_buffer_comm[PRU_CURRENT_BUFFER] = 0;
andrewm@45 354 pru_buffer_comm[PRU_BUFFER_FRAMES] = context->analogFrames;
andrewm@0 355 pru_buffer_comm[PRU_SHOULD_SYNC] = 0;
andrewm@0 356 pru_buffer_comm[PRU_SYNC_ADDRESS] = 0;
andrewm@0 357 pru_buffer_comm[PRU_SYNC_PIN_MASK] = 0;
andrewm@253 358 pru_buffer_comm[PRU_PRU_NUMBER] = pru_number;
andrewm@280 359
andrewm@280 360 if(mux_channels == 2)
andrewm@280 361 pru_buffer_comm[PRU_MUX_CONFIG] = 1;
andrewm@280 362 else if(mux_channels == 4)
andrewm@280 363 pru_buffer_comm[PRU_MUX_CONFIG] = 2;
andrewm@280 364 else if(mux_channels == 8)
andrewm@280 365 pru_buffer_comm[PRU_MUX_CONFIG] = 3;
andrewm@280 366 else
andrewm@280 367 pru_buffer_comm[PRU_MUX_CONFIG] = 0;
andrewm@280 368
andrewm@0 369 if(led_enabled) {
andrewm@0 370 pru_buffer_comm[PRU_LED_ADDRESS] = USERLED3_GPIO_BASE;
andrewm@0 371 pru_buffer_comm[PRU_LED_PIN_MASK] = USERLED3_PIN_MASK;
andrewm@0 372 }
andrewm@0 373 else {
andrewm@0 374 pru_buffer_comm[PRU_LED_ADDRESS] = 0;
andrewm@0 375 pru_buffer_comm[PRU_LED_PIN_MASK] = 0;
andrewm@0 376 }
andrewm@45 377 if(analog_enabled) {
andrewm@0 378 pru_buffer_comm[PRU_USE_SPI] = 1;
giuliomoro@528 379 if(context->analogInChannels != context->analogOutChannels){
giuliomoro@528 380 printf("Error: TODO: a different number of channels for inputs and outputs is not yet supported\n");
giuliomoro@528 381 return 1;
giuliomoro@528 382 }
giuliomoro@528 383 unsigned int analogChannels = context->analogInChannels;
giuliomoro@528 384 pru_buffer_comm[PRU_SPI_NUM_CHANNELS] = analogChannels;
andrewm@0 385 }
andrewm@0 386 else {
andrewm@0 387 pru_buffer_comm[PRU_USE_SPI] = 0;
andrewm@12 388 pru_buffer_comm[PRU_SPI_NUM_CHANNELS] = 0;
andrewm@0 389 }
giuliomoro@19 390 if(digital_enabled) {
giuliomoro@38 391 pru_buffer_comm[PRU_USE_DIGITAL] = 1;
giuliomoro@38 392 //TODO: add mask
giuliomoro@16 393 }
giuliomoro@16 394 else {
giuliomoro@38 395 pru_buffer_comm[PRU_USE_DIGITAL] = 0;
giuliomoro@38 396
giuliomoro@16 397 }
andrewm@0 398
giuliomoro@38 399 /* Clear ADC and DAC memory.*/
giuliomoro@38 400 //TODO: this initialisation should only address the memory effectively used by these buffers, i.e.:depend on the number of frames
giuliomoro@38 401 // (otherwise might cause issues if we move memory locations later on)
andrewm@45 402 if(analog_enabled) {
andrewm@0 403 for(int i = 0; i < PRU_MEM_DAC_LENGTH / 2; i++)
andrewm@0 404 pru_buffer_spi_dac[i] = 0;
andrewm@0 405 }
andrewm@320 406 if(digital_enabled){
andrewm@320 407 for(int i = 0; i < PRU_MEM_DIGITAL_OFFSET*2; i++)
andrewm@320 408 pru_buffer_digital[i] = 0x0000ffff; // set to all inputs, to avoid unexpected spikes
andrewm@320 409 }
andrewm@0 410 for(int i = 0; i < PRU_MEM_MCASP_LENGTH / 2; i++)
andrewm@0 411 pru_buffer_audio_dac[i] = 0;
andrewm@45 412
andrewm@0 413 /* If using GPIO test pin for Xenomai (for debugging), initialise the pointer now */
andrewm@0 414 if(xenomai_test_pin && xenomai_gpio_fd < 0) {
andrewm@0 415 xenomai_gpio_fd = open("/dev/mem", O_RDWR);
andrewm@0 416 if(xenomai_gpio_fd < 0)
andrewm@0 417 rt_printf("Unable to open /dev/mem for GPIO test pin\n");
andrewm@0 418 else {
andrewm@0 419 xenomai_gpio = (uint32_t *)mmap(0, GPIO_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, xenomai_gpio_fd, TEST_PIN_GPIO_BASE);
andrewm@0 420 if(xenomai_gpio == MAP_FAILED) {
andrewm@0 421 rt_printf("Unable to map GPIO address for test pin\n");
andrewm@0 422 xenomai_gpio = 0;
andrewm@0 423 close(xenomai_gpio_fd);
andrewm@0 424 xenomai_gpio_fd = -1;
andrewm@0 425 }
andrewm@0 426 }
andrewm@0 427 }
andrewm@0 428
andrewm@81 429 // Allocate audio buffers
andrewm@318 430 #ifdef USE_NEON_FORMAT_CONVERSION
andrewm@318 431 if(posix_memalign((void **)&context->audioIn, 16, 2 * context->audioFrames * sizeof(float))) {
andrewm@318 432 printf("Error allocating audio input buffer\n");
andrewm@318 433 return 1;
andrewm@318 434 }
andrewm@318 435 if(posix_memalign((void **)&context->audioOut, 16, 2 * context->audioFrames * sizeof(float))) {
andrewm@318 436 printf("Error allocating audio output buffer\n");
andrewm@318 437 return 1;
andrewm@318 438 }
andrewm@318 439 #else
andrewm@81 440 context->audioIn = (float *)malloc(2 * context->audioFrames * sizeof(float));
andrewm@81 441 context->audioOut = (float *)malloc(2 * context->audioFrames * sizeof(float));
andrewm@81 442 if(context->audioIn == 0 || context->audioOut == 0) {
andrewm@81 443 rt_printf("Error: couldn't allocate audio buffers\n");
andrewm@81 444 return 1;
andrewm@81 445 }
andrewm@318 446 #endif
andrewm@318 447
andrewm@81 448 // Allocate analog buffers
andrewm@81 449 if(analog_enabled) {
andrewm@318 450 #ifdef USE_NEON_FORMAT_CONVERSION
andrewm@318 451 if(posix_memalign((void **)&context->analogIn, 16,
andrewm@318 452 context->analogChannels * context->analogFrames * sizeof(float))) {
andrewm@318 453 printf("Error allocating analog input buffer\n");
andrewm@318 454 return 1;
andrewm@318 455 }
andrewm@318 456 if(posix_memalign((void **)&context->analogOut, 16,
andrewm@318 457 context->analogChannels * context->analogFrames * sizeof(float))) {
andrewm@318 458 printf("Error allocating analog output buffer\n");
andrewm@318 459 return 1;
andrewm@318 460 }
andrewm@318 461 last_analog_out_frame = (float *)malloc(context->analogChannels * sizeof(float));
andrewm@318 462
andrewm@318 463 if(last_analog_out_frame == 0) {
andrewm@318 464 rt_printf("Error: couldn't allocate analog persistence buffer\n");
andrewm@318 465 return 1;
andrewm@318 466 }
andrewm@318 467 #else
giuliomoro@528 468 context->analogIn = (float *)malloc(context->analogInChannels * context->analogFrames * sizeof(float));
giuliomoro@528 469 context->analogOut = (float *)malloc(context->analogOutChannels * context->analogFrames * sizeof(float));
giuliomoro@528 470 last_analog_out_frame = (float *)malloc(context->analogOutChannels * sizeof(float));
andrewm@81 471
andrewm@81 472 if(context->analogIn == 0 || context->analogOut == 0 || last_analog_out_frame == 0) {
andrewm@81 473 rt_printf("Error: couldn't allocate analog buffers\n");
andrewm@81 474 return 1;
andrewm@81 475 }
andrewm@318 476 #endif
andrewm@318 477
giuliomoro@528 478 memset(last_analog_out_frame, 0, context->analogOutChannels * sizeof(float));
andrewm@81 479 }
andrewm@81 480
andrewm@81 481 // Allocate digital buffers
andrewm@81 482 digital_buffer0 = pru_buffer_digital;
andrewm@81 483 digital_buffer1 = pru_buffer_digital + MEM_DIGITAL_BUFFER1_OFFSET / sizeof(uint32_t);
andrewm@81 484 if(digital_enabled) {
andrewm@81 485 last_digital_buffer = (uint32_t *)malloc(context->digitalFrames * sizeof(uint32_t)); //temp buffer to hold previous states
andrewm@81 486 if(last_digital_buffer == 0) {
andrewm@81 487 rt_printf("Error: couldn't allocate digital buffers\n");
andrewm@81 488 return 1;
andrewm@81 489 }
andrewm@81 490
andrewm@81 491 for(unsigned int n = 0; n < context->digitalFrames; n++){
andrewm@81 492 // Initialize lastDigitalFrames to all inputs
andrewm@81 493 last_digital_buffer[n] = 0x0000ffff;
andrewm@81 494 }
andrewm@81 495 }
andrewm@81 496
andrewm@81 497 context->digital = digital_buffer0;
andrewm@81 498
andrewm@0 499 return 0;
andrewm@0 500 }
andrewm@0 501
andrewm@0 502 // Run the code image in the specified file
giuliomoro@16 503 int PRU::start(char * const filename)
andrewm@0 504 {
andrewm@0 505 /* Clear any old interrupt */
andrewm@45 506 prussdrv_pru_clear_event(PRU_EVTOUT_0, PRU0_ARM_INTERRUPT);
andrewm@45 507
giuliomoro@16 508 /* Load and execute binary on PRU */
giuliomoro@16 509 if(filename[0] == '\0') { //if the string is empty, load the embedded code
giuliomoro@16 510 if(gRTAudioVerbose)
giuliomoro@16 511 rt_printf("Using embedded PRU code\n");
giuliomoro@16 512 if(prussdrv_exec_code(pru_number, PRUcode, sizeof(PRUcode))) {
giuliomoro@16 513 rt_printf("Failed to execute PRU code\n");
giuliomoro@16 514 return 1;
giuliomoro@16 515 }
giuliomoro@16 516 } else {
giuliomoro@16 517 if(gRTAudioVerbose)
giuliomoro@16 518 rt_printf("Using PRU code from %s\n",filename);
giuliomoro@16 519 if(prussdrv_exec_program(pru_number, filename)) {
giuliomoro@16 520 rt_printf("Failed to execute PRU code from %s\n", filename);
giuliomoro@16 521 return 1;
giuliomoro@16 522 }
giuliomoro@16 523 }
andrewm@0 524
andrewm@0 525 running = true;
andrewm@0 526 return 0;
andrewm@0 527 }
andrewm@0 528
andrewm@0 529 // Main loop to read and write data from/to PRU
andrewm@45 530 void PRU::loop(RT_INTR *pru_interrupt, void *userData)
andrewm@0 531 {
andrewm@303 532 #ifdef BELA_USE_XENOMAI_INTERRUPTS
andrewm@50 533 RTIME irqTimeout = PRU_SAMPLE_INTERVAL_NS * 1024; // Timeout for PRU interrupt: about 10ms, much longer than any expected period
andrewm@50 534 #else
andrewm@0 535 // Polling interval is 1/4 of the period
giuliomoro@528 536 if(context->analogInChannels != context->analogOutChannels){
giuliomoro@528 537 printf("Error: TODO: a different number of channels for inputs and outputs is not yet supported\n");
giuliomoro@528 538 return;
giuliomoro@528 539 }
giuliomoro@528 540 unsigned int analogChannels = context->analogInChannels;
giuliomoro@528 541 RTIME sleepTime = PRU_SAMPLE_INTERVAL_NS * (analogChannels / 2) * context->analogFrames / 4;
andrewm@50 542 #endif
andrewm@45 543
andrewm@45 544 uint32_t pru_audio_offset, pru_spi_offset;
andrewm@0 545
andrewm@81 546 // Before starting, look at the last state of the analog and digital outputs which might
andrewm@81 547 // have been changed by the user during the setup() function. This lets us start with pin
andrewm@81 548 // directions and output values at something other than defaults.
andrewm@81 549
andrewm@81 550 if(analog_enabled) {
andrewm@303 551 if(context->flags & BELA_FLAG_ANALOG_OUTPUTS_PERSIST) {
andrewm@81 552 // Remember the content of the last_analog_out_frame
giuliomoro@528 553 for(unsigned int ch = 0; ch < context->analogOutChannels; ch++){
giuliomoro@528 554 last_analog_out_frame[ch] = context->analogOut[context->analogOutChannels * (context->analogFrames - 1) + ch];
andrewm@81 555 }
andrewm@81 556 }
andrewm@0 557 }
andrewm@45 558
andrewm@45 559 if(digital_enabled) {
andrewm@45 560 for(unsigned int n = 0; n < context->digitalFrames; n++){
andrewm@81 561 last_digital_buffer[n] = context->digital[n];
andrewm@45 562 }
giuliomoro@38 563 }
andrewm@45 564
andrewm@45 565 // TESTING
andrewm@50 566 // uint32_t testCount = 0;
andrewm@45 567 // RTIME startTime = rt_timer_read();
andrewm@45 568
andrewm@303 569 #ifdef BELA_USE_XENOMAI_INTERRUPTS
andrewm@56 570 int result;
andrewm@56 571 #else
andrewm@50 572 // Which buffer the PRU was last processing
andrewm@50 573 uint32_t lastPRUBuffer = 0;
andrewm@50 574 #endif
andrewm@50 575
andrewm@0 576 while(!gShouldStop) {
andrewm@303 577 #ifdef BELA_USE_XENOMAI_INTERRUPTS
andrewm@45 578 // Wait for PRU to move to change buffers;
andrewm@45 579 // PRU will send an interrupts which we wait for
andrewm@45 580 rt_intr_enable(pru_interrupt);
andrewm@45 581 while(!gShouldStop) {
andrewm@45 582 result = rt_intr_wait(pru_interrupt, irqTimeout);
andrewm@45 583 if(result >= 0)
andrewm@45 584 break;
andrewm@45 585 else if(result == -ETIMEDOUT)
andrewm@45 586 rt_printf("Warning: PRU timeout!\n");
andrewm@45 587 else {
andrewm@45 588 rt_printf("Error: wait for interrupt failed (%d)\n", result);
andrewm@45 589 gShouldStop = 1;
andrewm@45 590 }
andrewm@0 591 }
andrewm@45 592
andrewm@45 593 // Clear pending PRU interrupt
andrewm@45 594 prussdrv_pru_clear_event(PRU_EVTOUT_1, PRU1_ARM_INTERRUPT);
andrewm@50 595 #else
andrewm@50 596 // Poll
andrewm@50 597 while(pru_buffer_comm[PRU_CURRENT_BUFFER] == lastPRUBuffer && !gShouldStop) {
andrewm@50 598 rt_task_sleep(sleepTime);
andrewm@50 599 }
andrewm@50 600
andrewm@50 601 lastPRUBuffer = pru_buffer_comm[PRU_CURRENT_BUFFER];
andrewm@50 602 #endif
andrewm@45 603
andrewm@0 604 if(gShouldStop)
andrewm@0 605 break;
andrewm@0 606
andrewm@45 607 // Check which buffer we're on-- will have been set right
andrewm@45 608 // before the interrupt was asserted
andrewm@45 609 if(pru_buffer_comm[PRU_CURRENT_BUFFER] == 1) {
andrewm@45 610 // PRU is on buffer 1. We read and write to buffer 0
andrewm@45 611 pru_audio_offset = 0;
andrewm@45 612 pru_spi_offset = 0;
andrewm@45 613 if(digital_enabled)
andrewm@81 614 context->digital = digital_buffer0;
andrewm@45 615 }
andrewm@45 616 else {
andrewm@45 617 // PRU is on buffer 0. We read and write to buffer 1
giuliomoro@528 618 if(context->audioInChannels != context->audioOutChannels){
giuliomoro@528 619 printf("Error: TODO: a different number of channels for inputs and outputs is not yet supported\n");
giuliomoro@528 620 return;
giuliomoro@528 621 }
giuliomoro@528 622 unsigned int audioChannels = context->audioInChannels;
giuliomoro@528 623 pru_audio_offset = context->audioFrames * audioChannels;
giuliomoro@528 624 if(context->analogInChannels != context->analogOutChannels){
giuliomoro@528 625 printf("Error: TODO: a different number of channels for inputs and outputs is not yet supported\n");
giuliomoro@528 626 return;
giuliomoro@528 627 }
giuliomoro@528 628 unsigned int analogChannels = context->analogInChannels;
giuliomoro@528 629 pru_spi_offset = context->analogFrames * analogChannels;
andrewm@45 630 if(digital_enabled)
andrewm@81 631 context->digital = digital_buffer1;
andrewm@45 632 }
andrewm@45 633
andrewm@45 634 // FIXME: some sort of margin is needed here to prevent the audio
andrewm@45 635 // code from completely eating the Linux system
andrewm@50 636 // testCount++;
andrewm@45 637 //rt_task_sleep(sleepTime*4);
andrewm@45 638 //rt_task_sleep(sleepTime/4);
andrewm@45 639
andrewm@0 640 if(xenomai_gpio != 0) {
andrewm@0 641 // Set the test pin high
andrewm@0 642 xenomai_gpio[GPIO_SETDATAOUT] = TEST_PIN_MASK;
andrewm@0 643 }
andrewm@0 644
andrewm@45 645 // Convert short (16-bit) samples to float
andrewm@318 646 #ifdef USE_NEON_FORMAT_CONVERSION
andrewm@318 647 int16_to_float_audio(2 * context->audioFrames, &pru_buffer_audio_adc[pru_audio_offset], context->audioIn);
andrewm@318 648 #else
andrewm@318 649 for(unsigned int n = 0; n < 2 * context->audioFrames; n++) {
giuliomoro@231 650 context->audioIn[n] = (float)pru_buffer_audio_adc[n + pru_audio_offset] / 32768.0f;
andrewm@318 651 }
andrewm@318 652 #endif
andrewm@318 653
andrewm@45 654 if(analog_enabled) {
andrewm@303 655 if(mux_channels != 0) {
andrewm@303 656 // If multiplexer is enabled, find out which channels we have by pulling out
andrewm@303 657 // the place that it ended.
andrewm@303 658 // int lastMuxChannel = pru_buffer_comm[PRU_MUX_END_CHANNEL];
andrewm@303 659
andrewm@303 660 // TODO
andrewm@303 661 }
andrewm@303 662
andrewm@318 663 #ifdef USE_NEON_FORMAT_CONVERSION
andrewm@318 664 int16_to_float_analog(context->analogChannels * context->analogFrames,
andrewm@318 665 &pru_buffer_spi_adc[pru_spi_offset], context->analogIn);
andrewm@318 666 #else
giuliomoro@528 667 for(unsigned int n = 0; n < context->analogInChannels * context->analogFrames; n++) {
giuliomoro@231 668 context->analogIn[n] = (float)pru_buffer_spi_adc[n + pru_spi_offset] / 65536.0f;
andrewm@318 669 }
andrewm@318 670 #endif
andrewm@45 671
andrewm@303 672 if(context->flags & BELA_FLAG_ANALOG_OUTPUTS_PERSIST) {
andrewm@45 673 // Initialize the output buffer with the values that were in the last frame of the previous output
giuliomoro@528 674 for(unsigned int ch = 0; ch < context->analogOutChannels; ch++){
andrewm@45 675 for(unsigned int n = 0; n < context->analogFrames; n++){
giuliomoro@528 676 context->analogOut[n * context->analogOutChannels + ch] = last_analog_out_frame[ch];
andrewm@45 677 }
giuliomoro@23 678 }
giuliomoro@23 679 }
andrewm@45 680 else {
andrewm@45 681 // Outputs are 0 unless set otherwise
giuliomoro@528 682 memset(context->analogOut, 0, context->analogOutChannels * context->analogFrames * sizeof(float));
giuliomoro@23 683 }
andrewm@45 684 }
andrewm@45 685
andrewm@45 686 if(digital_enabled){
andrewm@45 687 // Use past digital values to initialize the array properly.
andrewm@45 688 // For each frame:
andrewm@45 689 // - pins previously set as outputs will keep the output value they had in the last frame of the previous buffer,
andrewm@45 690 // - pins previously set as inputs will carry the newly read input value
andrewm@45 691
andrewm@45 692 for(unsigned int n = 0; n < context->digitalFrames; n++){
andrewm@81 693 uint16_t inputs = last_digital_buffer[n] & 0xffff; // half-word, has 1 for inputs and 0 for outputs
andrewm@45 694
andrewm@45 695 uint16_t outputs = ~inputs; // half-word has 1 for outputs and 0 for inputs;
andrewm@81 696 context->digital[n] = (last_digital_buffer[context->digitalFrames - 1] & (outputs << 16)) | // keep output values set in the last frame of the previous buffer
andrewm@45 697 (context->digital[n] & (inputs << 16)) | // inputs from current context->digital[n];
andrewm@81 698 (last_digital_buffer[n] & (inputs)); // keep pin configuration from previous context->digital[n]
andrewm@45 699 // context->digital[n]=digitalBufferTemp[n]; //ignores inputs
andrewm@45 700 }
andrewm@45 701 }
andrewm@45 702
andrewm@45 703 // Call user render function
andrewm@45 704 // ***********************
andrewm@307 705 render((BelaContext *)context, userData);
andrewm@45 706 // ***********************
andrewm@45 707
andrewm@45 708 if(analog_enabled) {
andrewm@303 709 if(context->flags & BELA_FLAG_ANALOG_OUTPUTS_PERSIST) {
andrewm@81 710 // Remember the content of the last_analog_out_frame
giuliomoro@528 711 for(unsigned int ch = 0; ch < context->analogOutChannels; ch++){
giuliomoro@528 712 last_analog_out_frame[ch] = context->analogOut[context->analogOutChannels * (context->analogFrames - 1) + ch];
andrewm@45 713 }
andrewm@45 714 }
andrewm@45 715
andrewm@45 716 // Convert float back to short for SPI output
andrewm@318 717 #ifdef USE_NEON_FORMAT_CONVERSION
andrewm@318 718 float_to_int16_analog(context->analogChannels * context->analogFrames,
andrewm@318 719 context->analogOut, (uint16_t*)&pru_buffer_spi_dac[pru_spi_offset]);
andrewm@318 720 #else
giuliomoro@528 721 for(unsigned int n = 0; n < context->analogOutChannels * context->analogFrames; n++) {
giuliomoro@231 722 int out = context->analogOut[n] * 65536.0f;
giuliomoro@16 723 if(out < 0) out = 0;
giuliomoro@16 724 else if(out > 65535) out = 65535;
andrewm@45 725 pru_buffer_spi_dac[n + pru_spi_offset] = (uint16_t)out;
giuliomoro@16 726 }
andrewm@318 727 #endif
giuliomoro@16 728 }
andrewm@45 729
andrewm@45 730 if(digital_enabled) { // keep track of past digital values
andrewm@45 731 for(unsigned int n = 0; n < context->digitalFrames; n++){
andrewm@81 732 last_digital_buffer[n] = context->digital[n];
andrewm@45 733 }
andrewm@45 734 }
andrewm@45 735
andrewm@45 736 // Convert float back to short for audio
andrewm@318 737 #ifdef USE_NEON_FORMAT_CONVERSION
andrewm@318 738 float_to_int16_audio(2 * context->audioFrames, context->audioOut, &pru_buffer_audio_dac[pru_audio_offset]);
andrewm@318 739 #else
giuliomoro@528 740 for(unsigned int n = 0; n < context->audioOutChannels * context->audioFrames; n++) {
giuliomoro@231 741 int out = context->audioOut[n] * 32768.0f;
andrewm@0 742 if(out < -32768) out = -32768;
andrewm@0 743 else if(out > 32767) out = 32767;
andrewm@45 744 pru_buffer_audio_dac[n + pru_audio_offset] = (int16_t)out;
andrewm@0 745 }
andrewm@318 746 #endif
andrewm@0 747
andrewm@52 748 // Increment total number of samples that have elapsed
andrewm@311 749 context->audioFramesElapsed += context->audioFrames;
andrewm@52 750
andrewm@0 751 if(xenomai_gpio != 0) {
andrewm@0 752 // Set the test pin high
andrewm@0 753 xenomai_gpio[GPIO_CLEARDATAOUT] = TEST_PIN_MASK;
andrewm@0 754 }
l@258 755
giuliomoro@301 756 Bela_autoScheduleAuxiliaryTasks();
andrewm@0 757
andrewm@45 758 }
andrewm@0 759
andrewm@303 760 #ifdef BELA_USE_XENOMAI_INTERRUPTS
andrewm@45 761 // Turn off the interrupt for the PRU if it isn't already off
andrewm@45 762 rt_intr_disable(pru_interrupt);
andrewm@50 763 #endif
andrewm@0 764
andrewm@0 765 // Tell PRU to stop
andrewm@0 766 pru_buffer_comm[PRU_SHOULD_STOP] = 1;
andrewm@0 767
andrewm@45 768 // Wait two buffer lengths for the PRU to finish
andrewm@45 769 rt_task_sleep(PRU_SAMPLE_INTERVAL_NS * context->analogFrames * 4 * 2);
andrewm@45 770
andrewm@45 771 // Clean up after ourselves
andrewm@45 772 free(context->audioIn);
andrewm@45 773 free(context->audioOut);
andrewm@45 774
andrewm@45 775 if(analog_enabled) {
andrewm@45 776 free(context->analogIn);
andrewm@45 777 free(context->analogOut);
andrewm@81 778 free(last_analog_out_frame);
andrewm@45 779 }
andrewm@45 780
andrewm@45 781 if(digital_enabled) {
andrewm@81 782 free(last_digital_buffer);
andrewm@45 783 }
andrewm@45 784
andrewm@45 785 context->audioIn = context->audioOut = 0;
andrewm@45 786 context->analogIn = context->analogOut = 0;
andrewm@45 787 context->digital = 0;
andrewm@0 788 }
andrewm@0 789
andrewm@0 790 // Wait for an interrupt from the PRU indicate it is finished
andrewm@0 791 void PRU::waitForFinish()
andrewm@0 792 {
andrewm@0 793 if(!running)
andrewm@0 794 return;
andrewm@45 795 prussdrv_pru_wait_event (PRU_EVTOUT_0);
andrewm@45 796 prussdrv_pru_clear_event(PRU_EVTOUT_0, PRU0_ARM_INTERRUPT);
andrewm@0 797 }
andrewm@0 798
andrewm@0 799 // Turn off the PRU when done
andrewm@0 800 void PRU::disable()
andrewm@0 801 {
andrewm@0 802 /* Disable PRU and close memory mapping*/
andrewm@0 803 prussdrv_pru_disable(pru_number);
andrewm@0 804 prussdrv_exit();
andrewm@0 805 running = false;
andrewm@0 806 }
andrewm@0 807
andrewm@0 808 // Debugging
andrewm@0 809 void PRU::setGPIOTestPin()
andrewm@0 810 {
andrewm@0 811 if(!xenomai_gpio)
andrewm@0 812 return;
andrewm@0 813 xenomai_gpio[GPIO_SETDATAOUT] = TEST_PIN2_MASK;
andrewm@0 814 }
andrewm@0 815
andrewm@0 816 void PRU::clearGPIOTestPin()
andrewm@0 817 {
andrewm@0 818 if(!xenomai_gpio)
andrewm@0 819 return;
andrewm@0 820 xenomai_gpio[GPIO_CLEARDATAOUT] = TEST_PIN2_MASK;
andrewm@0 821 }