Created
December 16, 2016 12:42
-
-
Save pingud98/e931d7b2682f55f70a80942fc17914d0 to your computer and use it in GitHub Desktop.
Use ADC->DMA to render Cosmic Pi sampling as an oscilloscope on a PC running Python based on https://gist.github.com/pklaus/5921022
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#undef HID_ENABLED | |
// Arduino Due ADC->DMA->USB 1MSPS | |
// by stimmer | |
// from http://forum.arduino.cc/index.php?topic=137635.msg1136315#msg1136315 | |
// Input: Analog in A0 | |
// Output: Raw stream of uint16_t in range 0-4095 on Native USB Serial/ACM | |
// on linux, to stop the OS cooking your data: | |
// stty -F /dev/ttyACM0 raw -iexten -echo -echoe -echok -echoctl -echoke -onlcr | |
volatile int bufn,obufn; | |
uint16_t buf[4][256]; // 4 buffers of 256 readings | |
//HV PSU init | |
const int SS_pin = 42; //tbc | |
const int SCK_pin = 44; | |
const int MISO_pin = 22; | |
const int MOSI_pin = 43; | |
byte sendValue; // Value we are going to send | |
void ADC_Handler(){ // move DMA pointers to next buffer | |
int f=ADC->ADC_ISR; | |
if (f&(1<<27)){ | |
bufn=(bufn+1)&3; | |
ADC->ADC_RNPR=(uint32_t)buf[bufn]; | |
ADC->ADC_RNCR=256; | |
} | |
} | |
void setup(){ | |
//HV Setup | |
digitalWrite(SS, HIGH); // Start with SS high | |
pinMode(SS_pin, OUTPUT); | |
pinMode(SCK_pin, OUTPUT); | |
pinMode(MISO_pin, INPUT); //this is the avalanche pin, not implemented yet | |
pinMode(MOSI_pin, OUTPUT); | |
sendValue = 0xFF; //Set the HV here onetime | |
sendValue = bitBang(sendValue); // Transmit data | |
//set up the thresholds | |
pinMode(35, OUTPUT); //setup for the MAX5387 - later set to 0. signal PA0 | |
pinMode(36, OUTPUT);//setup for the MAX5387 - later set to 0. signal PA1 | |
pinMode(37, OUTPUT);//setup for the MAX5387 - later set to 0. signal PA2 | |
digitalWrite(35, LOW);//configure the address of the MAX5387 pot | |
digitalWrite(36, LOW);//configure the address of the MAX5387 pot | |
digitalWrite(37, LOW);//configure the address of the MAX5387 pot | |
pinMode(5, INPUT); //set the interrupt pin for trigger as high impedance, probably not required | |
Wire.begin(); | |
Wire.beginTransmission(byte(0x28)); // transmit to device #112 | |
Wire.write(byte(B00010011)); //set both registers | |
//Wire.write(byte(B00010001)); //set both register A only | |
//Wire.write(byte(B00010010)); //set both register B only | |
Wire.write(byte(0x50));// sets registers to this value (1 step = 13mV) | |
Wire.endTransmission(); // stop transmitting | |
//ADC setup | |
SerialUSB.begin(0); | |
while(!SerialUSB); | |
pmc_enable_periph_clk(ID_ADC); | |
adc_init(ADC, SystemCoreClock, ADC_FREQ_MAX, ADC_STARTUP_FAST); | |
ADC->ADC_MR |=0x80; // free running | |
ADC->ADC_CHER=0x80; | |
NVIC_EnableIRQ(ADC_IRQn); | |
ADC->ADC_IDR=~(1<<27); | |
ADC->ADC_IER=1<<27; | |
ADC->ADC_RPR=(uint32_t)buf[0]; // DMA buffer | |
ADC->ADC_RCR=256; | |
ADC->ADC_RNPR=(uint32_t)buf[1]; // next DMA buffer | |
ADC->ADC_RNCR=256; | |
bufn=obufn=1; | |
ADC->ADC_PTCR=1; | |
ADC->ADC_CR=2; | |
} | |
void loop(){ | |
while(obufn==bufn); // wait for buffer to be full | |
SerialUSB.write((uint8_t *)buf[obufn],512); // send it - 512 bytes = 256 uint16_t | |
SerialUSB.write((uint8_t *)buf[obufn],512); // send it - 512 bytes = 256 uint16_t | |
obufn=(obufn+1)&3; | |
} | |
byte bitBang(byte _send) // This function is what bitbangs the data | |
{ | |
//reception isn't implemented in this version. | |
byte _receive = 0; | |
digitalWrite(SS_pin, LOW); // SS low | |
for(int i=0; i<8; i++) // There are 8 bits in a byte | |
{ | |
digitalWrite(MOSI_pin, bitRead(_send, 7-i)); // Set MOSI | |
//delay(1); | |
digitalWrite(SCK_pin, HIGH); // SCK high | |
//bitWrite(_receive, i, digitalRead(MISO_pin)); // Capture MISO | |
digitalWrite(SCK_pin, LOW); // SCK low | |
//digitalWrite(MOSI_pin, LOW); // Set MOSI | |
} | |
digitalWrite(SS_pin, HIGH); // SS high again | |
//return _receive; // Return the received data | |
} | |
//timing system | |
void TimersStart() { | |
uint32_t config = 0; | |
// Set up the power management controller for TC0 and TC2 | |
pmc_set_writeprotect(false); // Enable write access to power management chip | |
pmc_enable_periph_clk(ID_TC0); // Turn on power for timer block 0 channel 0 | |
pmc_enable_periph_clk(ID_TC3); // Turn on power for timer block 1 channel 0 | |
pmc_enable_periph_clk(ID_TC6); // Turn on power for timer block 2 channel 0 | |
// Timer block 0 channel 0 is connected only to the PPS | |
// We set it up to load regester RA on each PPS and reset | |
// So RA will contain the number of clock ticks between two PPS, this | |
// value is the clock frequency and should be very stable +/- one tick | |
config = TC_CMR_TCCLKS_TIMER_CLOCK1 | // Select fast clock MCK/2 = 42 MHz | |
TC_CMR_ETRGEDG_RISING | // External trigger rising edge on TIOA0 | |
TC_CMR_ABETRG | // Use the TIOA external input line | |
TC_CMR_LDRA_RISING; // Latch counter value into RA | |
TC_Configure(TC0, 0, config); // Configure channel 0 of TC0 | |
TC_Start(TC0, 0); // Start timer running | |
TC0->TC_CHANNEL[0].TC_IER = TC_IER_LDRAS; // Enable the load AR channel 0 interrupt each PPS | |
TC0->TC_CHANNEL[0].TC_IDR = ~TC_IER_LDRAS; // and disable the rest of the interrupt sources | |
NVIC_EnableIRQ(TC0_IRQn); // Enable interrupt handler for channel 0 | |
// Timer block 1 channel 0 is the PLL for when the GPS chip isn't providing the PPS | |
// it has the frequency loaded in reg C and is triggered from the TC0 ISR | |
config = TC_CMR_TCCLKS_TIMER_CLOCK1 | // Select fast clock MCK/2 = 42 MHz | |
TC_CMR_CPCTRG; // Compare register C with count value | |
TC_Configure(TC1, 0, config); // Configure channel 0 of TC1 | |
TC_SetRC(TC1, 0, FREQ); // One second approx initial PLL value | |
TC_Start(TC1, 0); // Start timer running | |
TC1->TC_CHANNEL[0].TC_IER = TC_IER_CPCS; // Enable the C register compare interrupt | |
TC1->TC_CHANNEL[0].TC_IDR = ~TC_IER_CPCS; // and disable the rest | |
NVIC_EnableIRQ(TC3_IRQn); // Enable interrupt handler for channel 0 | |
// Timer block 2 channel 0 is connected to the RAY event | |
// It is kept in phase by the PPS comming from TC0 when the PPS arrives | |
// or from TC1 when the PLL is active (This is the so called software diode logic) | |
config = TC_CMR_TCCLKS_TIMER_CLOCK1 | // Select fast clock MCK/2 = 42 MHz | |
TC_CMR_ETRGEDG_RISING | // External trigger rising edge on TIOA1 | |
TC_CMR_ABETRG | // Use the TIOA external input line | |
TC_CMR_LDRA_RISING; // Latch counter value into RA | |
TC_Configure(TC2, 0, config); // Configure channel 0 of TC2 | |
TC_Start(TC2, 0); // Start timer running | |
TC2->TC_CHANNEL[0].TC_IER = TC_IER_LDRAS; // Enable the load AR channel 0 interrupt each PPS | |
TC2->TC_CHANNEL[0].TC_IDR = ~TC_IER_LDRAS; // and disable the rest of the interrupt sources | |
NVIC_EnableIRQ(TC6_IRQn); // Enable interrupt handler for channel 0 | |
// Set up the PIO controller to route input pins for TC0 and TC2 | |
PIO_Configure(PIOC,PIO_INPUT, | |
PIO_PB25B_TIOA0, // D2 Input | |
PIO_DEFAULT); | |
PIO_Configure(PIOC,PIO_INPUT, | |
PIO_PC25B_TIOA6, // D5 Input | |
PIO_DEFAULT); | |
} | |
// Timer chip interrupt handlers try to get time stamps to within 4 system clock ticks | |
static uint32_t displ = 0; // Display values in loop | |
static uint32_t rega0 = FREQ, // RA reg | |
stsr0 = 0, // Interrupt status register | |
ppcnt = 0, // PPS count | |
delcn = 0; // Synthetic PPS ms | |
static uint32_t rega1, stsr1 = 0; | |
static uint32_t stsr2 = 0; | |
boolean pll_flag = false; | |
int old_ra = 0; | |
int new_ra = 0; | |
#define DEAD_TIME 42000 // 1ms | |
// Handle the PPS interrupt in counter block 0 ISR | |
void TC0_Handler() { | |
// In principal we could connect a diode | |
// to pass on the PPS to counter blocks 1 & 2. However for some unknown | |
// reason this pulls down the PPS voltage level to less than 1V and | |
// the trigger becomes unreliable !! | |
// In any case the PPS is 100ms wide !! Introducing a blind spot when | |
// the diode creates the OR of the event trigger and the PPS. | |
// So this is a software diode | |
TC2->TC_CHANNEL[0].TC_CCR = TC_CCR_SWTRG; // Forward PPS to counter block 2 | |
TC1->TC_CHANNEL[0].TC_CCR = TC_CCR_SWTRG; // Forward PPS to counter block 1 | |
rega0 = TC0->TC_CHANNEL[0].TC_RA; // Read the RA reg (PPS period) | |
stsr0 = TC_GetStatus(TC0, 0); // Read status and clear load bits | |
if (rega0 < MFRQ) // Sanity check against noise | |
rega0 = FREQ; // Use nominal value | |
TC_SetRC(TC1, 0, rega0); // Set the PLL count to what we just counted | |
SwapBufs(); // Every PPS swap the read/write buffers | |
ppcnt++; // PPS count | |
displ = 1; // Display stuff in the loop | |
gps_ok = true; // Its OK because we got a PPS | |
pll_flag = true; // Inhibit PLL, dont take over PPS arrived | |
old_ra = 0; // Dead time counters | |
new_ra = 0; | |
IncDateTime(); // Next second | |
if (pps_led) { | |
digitalWrite(PPS_PIN,HIGH); | |
pps_led = false; | |
} else { | |
digitalWrite(PPS_PIN,LOW); | |
pps_led = true; | |
} | |
} | |
// Handle PLL interrupts | |
// When/If the PPS goes missing due to a lost lock we carry on with the last measured | |
// value for the second from TC0 | |
void TC3_Handler() { | |
stsr2 = TC_GetStatus(TC1, 0); // Read status and clear interrupt | |
#if FLG_PIN | |
digitalWrite(FLG_PIN,HIGH); // Flag set (for debug) | |
digitalWrite(FLG_PIN,LOW); | |
#endif | |
if (pll_flag == false) { // Only take over when no PPS | |
TC2->TC_CHANNEL[0].TC_CCR = TC_CCR_SWTRG; // Forward PPS to counter block 2 | |
SwapBufs(); // Every PPS swap the read/write buffers | |
ppcnt++; // PPS count | |
displ = 1; // Display stuff in the loop | |
gps_ok = false; // PPS missing | |
IncDateTime(); // Next second | |
} | |
pll_flag = false; // Take over until PPS comes back | |
} | |
// We need a double buffer, one is being written by the ISR while | |
// the other is read from user space within one second. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment