Loading...
  Show Posts
Pages: [1]
1  Products / Arduino Due / Re: PWM Duty Cycle Capture using Timer Counter on: Today at 09:07:16 am
Registers TC_RA and TC_RB are actually capture registers, at least when the TC is put into capture mode. Setting TC_CMR_LDRA and TC_CMR_LDRB dictate when the registers capture the counter value based on the TIOA signal behaviour. In my original example I use TC_CMR_LDRA_FALLING, so every time the TIOA signal falls the current counter value is stored in register TC_RA. This way you never need to actually read the counter value directly.

I seem to have a working sketch now. It incorporates some code from the Atmel Software Framework, mainly the direct register manipulation.

The basic working principle is that a timer counter (TC) is put into capture mode. The TC is told to load capture register A (TC_RA) on the rising edge of a PWM signal and capture register B (TC_RB) on the falling edge. The number of counts that the signal is active for is equal to TC_RB - TC_RA. An interrupt routine is run every time TC_RB is loaded and saves the register values to variables. The clock is also reset on the falling edge. The PWM frequency, duty cycle and time active can be calculated because the TC clock frequency, and therefore the duration of each count, is known.

I have tested this using all 5 available TIOAx channels individually and they all seem to work. I have not yet tested multiple channels running at the same time. The sketch also has some issues when the PWM signal is noisy, occasionally giving very incorrect values.

TL;DR The timer counter calculates PWM frequency and duty cycle while using almost no CPU time (i.e., it is non-blocking)

Code:
/*
 * PWM Capture using Arduino Due Timer Counters
 *
 * Available channels:
 *   TC    Chan   NVIC irq   Handler       PMC ID   Arduino Pin
 *   TC0   0      TC0_IRQn   TC0_Handler   ID_TC0   D2     (TIOA0)
 *   TC0   1      TC1_IRQn   TC1_Handler   ID_TC1   D61/A7 (TIOA1)
 *   TC2   0      TC6_IRQn   TC6_Handler   ID_TC6   D5     (TIOA6)
 *   TC2   1      TC7_IRQn   TC7_Handler   ID_TC7   D3     (TIOA7)
 *   TC2   2      TC8_IRQn   TC8_Handler   ID_TC8   D11    (TIOA8)
 *
 * Change the following defines to use different channels as input.
 *
 */

#define CAPTURE_TC TC0
#define CAPTURE_CHANNEL 0
#define CAPTURE_IRQn TC0_IRQn
#define CAPTURE_Handler TC0_Handler
#define CAPTURE_ID ID_TC0
#define CAPTURE_PIN 2
#define CAPTURE_CLOCK_SELECTION TC_CMR_TCCLKS_TIMER_CLOCK3

// clock divisors corresponding to CAPTURE_CLOCK_SELECTION
static const uint32_t divisors[5] = { 2, 8, 32, 128, 0};


volatile uint32_t captured_pulses = 0;
volatile uint32_t captured_ra = 0;
volatile uint32_t captured_rb = 0;
uint32_t frequency, duty_cycle, active_time;


// timer interrupt handle
void CAPTURE_Handler() {
  if ((TC_GetStatus(CAPTURE_TC, CAPTURE_CHANNEL) & TC_SR_LDRBS) == TC_SR_LDRBS) {
    captured_pulses++;
    captured_ra = CAPTURE_TC->TC_CHANNEL[CAPTURE_CHANNEL].TC_RA;
    captured_rb = CAPTURE_TC->TC_CHANNEL[CAPTURE_CHANNEL].TC_RB;
  }
}


void setup() {
  Serial.begin(57600);
  Serial.print("Initializing...");
  
  // configure the PIO pin as peripheral
  const PinDescription *config = &g_APinDescription[CAPTURE_PIN];
PIO_Configure(
 config->pPort,
 config->ulPinType,
 config->ulPin,
 config->ulPinConfiguration
);
    
  // enable timer peripheral clock
  pmc_enable_periph_clk(CAPTURE_ID);
  
  // configure the timer
  TC_Configure(CAPTURE_TC, CAPTURE_CHANNEL,
    CAPTURE_CLOCK_SELECTION /* Clock Selection */
    | TC_CMR_LDRA_RISING /* RA Loading: rising edge of TIOA */
    | TC_CMR_LDRB_FALLING /* RB Loading: falling edge of TIOA */
    | TC_CMR_ABETRG /* External Trigger: TIOA */
    | TC_CMR_ETRGEDG_FALLING /* External Trigger Edge: Falling edge */
  );
  
  // configure TC interrupts
  NVIC_DisableIRQ(CAPTURE_IRQn);
  NVIC_ClearPendingIRQ(CAPTURE_IRQn);
  NVIC_SetPriority(CAPTURE_IRQn, 0);
  NVIC_EnableIRQ(CAPTURE_IRQn);
  
  // enable interrupts
  CAPTURE_TC->TC_CHANNEL[CAPTURE_CHANNEL].TC_IER = TC_IER_LDRBS;
  
  // start timer counter
  CAPTURE_TC->TC_CHANNEL[CAPTURE_CHANNEL].TC_CCR = TC_CCR_CLKEN | TC_CCR_SWTRG;
  
  Serial.println("ready!");
  
}


void loop() {
  // measurement is interrupt based so delay doesn't really block it
  delay(1000);
  
  Serial.print("Captured "); Serial.print(captured_pulses);
  Serial.println(" pulses from TC since last read");
  captured_pulses = 0;
  
  // frequency in Hz
  frequency
    = (F_CPU / divisors[CAPTURE_CLOCK_SELECTION]) / captured_rb;
  
  // duty cycle in percent
  duty_cycle
    = (captured_rb - captured_ra) * 100 / captured_rb;
  
  // time active in microseconds
  active_time
    = ((captured_rb - captured_ra) * 1000) /
      ((F_CPU / divisors[CAPTURE_CLOCK_SELECTION]) / 1000);
  
  Serial.print("Captured wave frequency = "); Serial.print(frequency);
  Serial.print(" Hz, Duty cycle = "); Serial.print(duty_cycle);
  Serial.print(" %, Pulse width = "); Serial.print(active_time);
  Serial.println(" us");
  
}

output:
Code:
Initializing...ready!
Captured 52 pulses from TC since last read
Captured wave frequency = 52 Hz, Duty cycle = 6 %, Pulse width = 1292 us
Captured 53 pulses from TC since last read
Captured wave frequency = 52 Hz, Duty cycle = 6 %, Pulse width = 1292 us
Captured 53 pulses from TC since last read
Captured wave frequency = 52 Hz, Duty cycle = 6 %, Pulse width = 1290 us
Captured 52 pulses from TC since last read
Captured wave frequency = 52 Hz, Duty cycle = 6 %, Pulse width = 1292 us
2  Products / Arduino Due / Re: PWM Duty Cycle Capture using Timer Counter on: May 24, 2013, 06:02:47 am
I haven't figured it out yet, but I'm still working on it. There is example code included in the Atmel Software Framework (tc_capture_waveform_example.c) that should do exactly what I'm attempting to do but it seems that a lot of the driver functions they use aren't included in the Arduino IDE.

I will post some code as soon as I get this sorted out.
3  Products / Arduino Due / [SOLVED] Non-blocking PWM duty cycle capture using Timer Counter on: May 22, 2013, 07:39:12 am
May 25, 2013 - See my later post for working code

I am attempting to use one of the Due's timer/counters to measure the duty cycle of a PWM signal from an RC receiver. Using the TC's external trigger I want the counter to reset at every rising edge (start of duty cycle) and then record the timer value at the falling edge (end of duty cycle) to one of the TC's registers. What I was expecting to see as output was the number of counts that the signal was high, however all I get is 0.

I have also attached an interrupt such that every time the register is loaded by the TC (i.e., every falling edge) it is read and a variable that counts the number of pulses is incremented. The number of pulses increases as expected. See the code and example output below.

Does anyone know why the counter doesn't appear to be actually counting?

RX_example.ino
Code:
#define TC_WPMR_WPKEY_VALUE TC_WPMR_WPKEY((uint32_t)0x54494D)

volatile uint32_t duty_value = 0;
volatile uint32_t pulses_captured = 0;

long start_time, previous_time;

// timer interrupt handle
void TC0_Handler() {
  TC_GetStatus(TC0, 0);
  
  pulses_captured++;
  duty_value = TC0->TC_CHANNEL[0].TC_RA;
  
}



void setup() {
  Serial.begin(57600);
  Serial.print("Initializing...");
  
  // enable timer clock
  pmc_enable_periph_clk(ID_TC0);
  
  // configure the PIO pin as peripheral
  PIO_Configure(
    g_APinDescription[2].pPort,
    g_APinDescription[2].ulPinType,
    g_APinDescription[2].ulPin,
    g_APinDescription[2].ulPinConfiguration);
  
  // disable TC register write protection
  TC0->TC_WPMR = TC_WPMR_WPKEY_VALUE;
  
  // configure the timer
  TC_Configure(TC0, 0,
    TC_CMR_TCCLKS_TIMER_CLOCK4
    | TC_CMR_ETRGEDG_RISING
    | TC_CMR_ABETRG
    | TC_CMR_LDRA_FALLING
  );

  // start counter
  TC0->TC_CHANNEL[0].TC_CCR = TC_CCR_CLKEN | TC_CCR_SWTRG;
  
  // enable interrupts on loading of Register A
  TC0->TC_CHANNEL[0].TC_IER=TC_IER_LDRAS;
  TC0->TC_CHANNEL[0].TC_IDR=~TC_IER_LDRAS;
  
  // enable the NVIC (Nested Vector Interrupt Controller)
  NVIC_EnableIRQ(TC0_IRQn);
  
  Serial.println("ready!");
  
  previous_time = micros();
}


void loop() {
  start_time = micros();
  
  if(start_time - previous_time > 50000) {
    Serial.print(pulses_captured); Serial.print(",");
    Serial.println(duty_value);
    
    previous_time = start_time;
  }
}

output.txt
Code:
Initializing...ready!
5,0
11,0
17,0
23,0
27,0
33,0
39,0
45,0
50,0
55,0
61,0
67,0
73,0
77,0
83,0
89,0
96,0
102,0
107,0
113,0
119,0
125,0
129,0
136,0
143,0
150,0
155,0
...
4  Using Arduino / Microcontrollers / Re: Anyone dare to port arduino for STM32F4? on: April 18, 2013, 11:40:39 am
I am not aware of any ports. The stm32f4 is ARM based just like the Arduino Due, but from a different manufacturer (i.e., not ATMEL). Therefore the SAM3X libraries in Arduino 1.5.x would need to be replaced, which would require a lot of work.

On the other hand, I have been having a lot of luck programming a stm32f4 discovery board using the GNU ARM toolchain (available at https://launchpad.net/gcc-arm-embedded) and the open source stlink software (at https://github.com/texane/stlink) to compile and flash my programs. While this is a bit more work, ST provides a lot of libraries and examples that show how to interact with the board's peripherals (e.g. i2c, spi, etc.). I have this all set up under Mac OS 10.7 but from what I can gather it is much easier to do under windows.
5  Using Arduino / Sensors / Re: Exceeding the maximum output data rate of the HMC5883L on: January 20, 2013, 12:39:07 am
Those are the kind of issues I had imagined, although I am mainly concerned about heating.

The quality of the measurements does decrease with the higher sampling frequencies. I don't really know how to measure the accuracy of the device, but I did record a few seconds of data while it was stationary. There is a noticeable increase in noise when sampling at 200 Hz, but not enough that it couldn't be filtered out. See for yourself:


P.S. Thanks for catching that potential infinite loop, I don't know how I missed that

[EDIT] Fixed the image link
6  Using Arduino / Sensors / Exceeding the maximum output data rate of the HMC5883L on: January 19, 2013, 04:10:18 pm
In the data sheet of the HMC5883L 3-axis compass the maximum output data rate is listed as 160 Hz when using single-measurement mode (interrupt driven sampling). I've written a simple arduino sketch using interrupts to only grab data when the device data ready pin has changed status, indicating the samples are ready. However, I've found that with my code I am able to sample at up to ~240 Hz. Will sampling at this rate for long durations damage the sensor? Why else would the listed maximum rate be so much lower than what I can achieve?

Here is my code where I've limited the sampling to 200 Hz :

Code:
#include <Wire.h>

#define MAG_ADDRESS ((char) 0x1E)
uint8_t mag_buffer[6];
int16_t mag_raw[3];

long start_time = 0;
long previous_time = 0;
long loop_duration = 0;

volatile boolean isDataReady = false;

void setup() {
  Serial.begin(115200);
  Serial.println("Initializing...");
 
  Wire.begin();

  configMag();
 
  attachInterrupt(13, magDataReady, RISING);
 
  previous_time = micros();
}


//interrupt function when mag DRDY pin is brought LOW
void magDataReady() {
  isDataReady = true;
}


void loop() {

  start_time = micros();
  loop_duration = start_time - previous_time;
 
  if(loop_duration >= 5000) {
    if(isDataReady) {
      isDataReady = false;
      readMag();
 
//      Serial.print(mag_raw[0], DEC); Serial.print(",");
//      Serial.print(mag_raw[1], DEC); Serial.print(",");
//      Serial.print(mag_raw[2], DEC); Serial.println();
      previous_time = start_time;
   
    }
    else {
      Serial.println("Missed one");
    }
  }
 
}


void configMag() {
  uint8_t mag_name;
 
  // make sure that the device is connected
  Wire.beginTransmission(MAG_ADDRESS);
  Wire.write((byte) 0x0A); // Identification Register A
  Wire.endTransmission();
 
  Wire.beginTransmission(MAG_ADDRESS);
  Wire.requestFrom(MAG_ADDRESS, 1);
  mag_name = Wire.read();
  Wire.endTransmission();
 
  if(mag_name != 0x48) {
    Serial.println("HMC5883L not found!");
    Serial.print(mag_name, HEX); Serial.println(" found, should be 0x48");
    delay(1000);
  }
 
  // Register 0x00: CONFIG_A
  // normal measurement mode (0x00) and 75 Hz ODR (0x18)
  Wire.beginTransmission(MAG_ADDRESS);
  Wire.write((byte) 0x00);
  Wire.write((byte) 0x18);
  Wire.endTransmission();
  delay(5);
 
  // Register 0x01: CONFIG_B
  // default range of +/- 130 uT (0x20)
  Wire.beginTransmission(MAG_ADDRESS);
  Wire.write((byte) 0x01);
  Wire.write((byte) 0x20);
  Wire.endTransmission();
  delay(5);
 
  // Register 0x02: MODE
  // continuous measurement mode at configured ODR (0x00)
  // possible to achieve 160 Hz by using single measurement mode (0x01) and DRDY
  Wire.beginTransmission(MAG_ADDRESS);
  Wire.write((byte) 0x02);
  Wire.write((byte) 0x01);
  Wire.endTransmission();
 
  delay(200);
 
}


// read 6 bytes (x,y,z magnetic field measurements) from the magnetometer
void readMag() {
 
  // multibyte burst read of data registers (from 0x03 to 0x08)
  Wire.beginTransmission(MAG_ADDRESS);
  Wire.write((byte) 0x03); // the address of the first data byte
  Wire.endTransmission();
 
  Wire.beginTransmission(MAG_ADDRESS);
  Wire.requestFrom(MAG_ADDRESS, 6);  // Request 6 bytes
  int i = 0;
  while(Wire.available())
  {
    mag_buffer[i] = Wire.read();  // Read one byte
    i++;
  }
  Wire.read();
  Wire.endTransmission();
 
  // combine the raw data into full integers (HMC588L sends MSB first)
  //           ________ MSB _______   _____ LSB ____
  mag_raw[0] = (mag_buffer[0] << 8) | mag_buffer[1];
  mag_raw[1] = (mag_buffer[2] << 8) | mag_buffer[3];
  mag_raw[2] = (mag_buffer[4] << 8) | mag_buffer[5];
 
  // put the device back into single measurement mode
  Wire.beginTransmission(MAG_ADDRESS);
  Wire.write((byte) 0x02);
  Wire.write((byte) 0x01);
  Wire.endTransmission();
 
}
7  Products / Arduino Due / Re: Simultaneous i2c and SPI not working on Due on: January 17, 2013, 08:50:39 pm
I created a minimal working example and that seemed to sort out my issue. Something else in my original code must have been causing the issue, not the Due's libraries.

Just for the heck of it here is my minimal example in case it is useful for anyone.

Code:
/* minimal working example using both i2c and spi
  hardware:
  - Arduino Due
  - ADXL345 (accel)
  - HMC5883L (mag)
  - L3G4200 (gyro)
*/

#include <Wire.h>
#include <SPI.h>

#define MAG_ADDRESS ((char) 0x1E) // i2c device address
#define ACCEL_SS_PIN 10 // SPI device slave select pin
#define GYRO_SS_PIN 52 // second SPI device pin
#define READ   0x80 // general SPI transfer bits
#define WRITE  0x00
#define MULTI  0x40
#define SINGLE 0x00

void setup() {
  Serial.begin(115200);
 
  // initialize i2c
  Wire.begin();

  // initialize first device SPI
  SPI.begin(ACCEL_SS_PIN);
  SPI.setBitOrder(ACCEL_SS_PIN, MSBFIRST);
  SPI.setDataMode(ACCEL_SS_PIN, SPI_MODE3);
 
  // initialize second device SPI
  SPI.begin(GYRO_SS_PIN);
  SPI.setBitOrder(GYRO_SS_PIN, MSBFIRST);
  SPI.setDataMode(GYRO_SS_PIN, SPI_MODE0);
 
  delay(500);
 
 
  // test i2c by getting the device identifier registry
  Wire.beginTransmission(MAG_ADDRESS);
  Wire.write(0x0A); // identification register, should equal 0x48
  Wire.endTransmission();
 
  Wire.beginTransmission(MAG_ADDRESS);
  Wire.requestFrom(MAG_ADDRESS,1);
  uint8_t mag_name = Wire.read();
  Wire.endTransmission();
  Serial.print(mag_name, HEX); Serial.println(" value recieved, should be 0x48");
 
 
  // test SPI by getting the first device identifier registry
  SPI.transfer(ACCEL_SS_PIN, READ | SINGLE | 0x00, SPI_CONTINUE);
  uint8_t accel_name = SPI.transfer(ACCEL_SS_PIN, 0x00);
  Serial.print(accel_name, HEX); Serial.println(" value recieved, should be 0xE5");
 
 
  // test SPI by getting the second device identifier registry
  SPI.transfer(GYRO_SS_PIN, READ | SINGLE | 0x0F, SPI_CONTINUE);
  uint8_t gyro_name = SPI.transfer(GYRO_SS_PIN, 0x00);
  Serial.print(gyro_name, HEX); Serial.println(" value recieved, should be 0xD3");
 
}

void loop() {
 
}
8  Products / Arduino Due / Simultaneous i2c and SPI not working on Due on: January 17, 2013, 03:10:51 pm
I'm having trouble communicating using both i2c and SPI in the same sketch. I've been trying to use the Arduino Due to gather measurements from an ADXL345 (using SPI), an L3G4200 (using SPI) and an HMC5883L (using i2c). I have a separate sketch that only communicates with the HMC5883 and everything works fine and I receive measurements. But in my main sketch, which includes both the Wire.h and SPI.h libraries, the communication over i2c stops working. I have checked the SCL and SDA pins and both appear to be stuck high. Has anyone else experienced similar behaviour?
Pages: [1]