Gas station without pumps

2017 November 17

Tape sensors

Filed under: Robotics — gasstationwithoutpumps @ 15:06
Tags: , , , ,

One of the requirements of the robot for Mechatronics is that it have optical sensors on the bottom to detect black tape on a white background, for detecting the edges of the field, alignment marks for the Ren-ship target, and other marks on the field that seem to be there just to be a nuisance and make the state-machine design harder.

We were issued a number of TCRT5000L reflective sensors, which consist of an IR emitter and a phototransistor in a plastic package that is optimized for a surface 2mm away.  Although the sensors are designed to snap into PC boards and could probably be snapped into carefully sized holes in MDF, I’ll be mounting them on perfboards, which will be screwed onto the top of the baseplate of my robot, with rectangular holes in the MDF for the sensor to poke through.  They’ll barely poke through (6mm high — 5mm MDF), which means they’ll be about 7mm from the floor.  At that distance, the collector current is about half what it would be at the optimum distance.  The MDF provides some extra shrouding from stray light from the sides, which should help with detection.

I’m planning to turn the IR emitters on only when I need to read the sensors, to save power and to allow synchronous detection of reflectance (looking at the difference in light levels between the IR emitter being on or off).  By keeping the duty cycle below 50%, I can run the IR emitters up to 50mA, though I may not need them to be that bright.

If I switch on 5 IR emitters at once, at 50mA each, I’ll be  using 250mA, which is the limit for what I could get from the Teensy 3.2 3.3V regulator (rated for 250mA, though that’s a thermal limit, so 500mA may be available for low duty cycle) and far exceeds the 100mA limit for Teensy LC or Teensy 3.1 boards.  I will be using a 5V regulator to power the Teensy boards, so I could use the 5V supply to power the IR emitters also.

If I use only 10mA per IR emitter, I only need 50mA total and so could power off of either 3.3V or 5V.

For 50mA at 5V, I’d need a current-limiting resistor of ≥(5V–1.2V)/50mA=76Ω, so an 82Ω resistor for 46–48mA would work.  For 10mA at 5V, I’d need ≥(5V–1.1V)/10mA=390Ω, and for 10mA at 3.3V, I’d need ≥(3.3V–1.1V)/10mA=220Ω.

I have a couple of choices for wiring up the sensors:

There are 3 wires to each sensor board, but only the current output is unique to the board—the IR emitter connections can be shared among all sensors.

Low-side switching only requires an nFET, but the power connection for the IR emitter is limited to 3.3V. High-side switching keeps the 3.3V power line from being routed near the motors, but requires a 5v-powered inverter to get a sufficiently large voltage to shut off the pFET.

The expected current from a white background @7mm is about 400µA for 10mA input or 2mA for 50mA input, assuming VCE is in the active region (above about 0.5V).  If I’m looking for a 1V swing between dark and light, I’ll want about 2.5kΩ for the sense resistor for a 10mA input and 500Ω for a 50mA input.

I did a couple of tests with the TCRT5000L using the low-side switching configuration, with 3.3V and a 220Ω current-limiting resistor.  I was doing the tests in a well-lit room (my breakfast room on a sunny day, but without direct sunlight in the room), so the background light is probably brighter than the robot will ever have to deal with.  I used one of the wheels from the MockRobot to be the target, since I could put a screw through the axle hole to get a more-or-less consistent spacing from the sensor.  I adjusted the nut so that the spacing was about 7mm from the sensor, but the angle was only controlled by eyeballing the levelness of the target, so probably varied by ±10°. I did not have any of the black tape that was used on the fields, so I substituted black electrical tape, which I think may be slightly more reflective.

I initially tried 2.7kΩ as the sense resistor, but after seeing the voltage range for that, I upped it to 5.1kΩ.

With 5.1kΩ, the voltages when illuminated are very different between black and white—a simple digital input would suffice.

The currents can be seen to be about the same, independent of the sense resistor, with the white MDF giving about 10 times current of the black electrical tape. The signal from background illumination is insignificant, though that might not be true if the IR beacon is lighting up the field.

It looks like 10mA (measured as 9.65mA) is more than enough light for the sensors to tell black from white, and that the signal is strong enough that I don’t really need analog input or synchronous sensing for the tape sensors.  If I get really worried about the digital signal in worst-case conditions (like too large a distance), I could increase the size of the sense resistor further.  With a 22kΩ sense resistor, I get a very clean separation between white and black even at 1cm.

I have to decide whether to pulse the IR emitters and wait at least 100µs before reading all the sensors, or just to leave them on all the time (at a cost of 10mA per tape sensor). If they are on all the time, I could use the 5V supply (with a 390Ω current-limiting resistor instead of a 220Ω one), and not worry about overloading the Teensy regulator. This could also simplify wiring, as the 5V regulator could be on the power board on the first level and the battery wiring would be limited to the first level.

The Teensy 3.1/3.2 digital pins are 5V-tolerant, so I could even connect the phototransistor collectors to 5V and put the multiplexer on the first level using 5V power. I could then put the sense resistors on the tape-sensor boards and have three wires for each board: 5V, GND, and WHITE.  I could use 3-pin male headers and standard Futaba servo cables to do the wiring.

I ordered a set of servo cables through Amazon, but they are not expected to arrive for another 2–6 weeks, so aren’t going to do me much good.)  I can make my own, but I’ve been having trouble lately with intermittent contacts from the crimp-on connectors and have taken to wicking tiny amounts of solder into the crimp, which adds a lot of time to making the cables.

With the multiplexer on the first layer, the only cabling from the tape sensors to higher levels would be 5V, Gnd, 3 select signals, and the (now digital) signal for sensing the tape. The motors are also on the bottom level, and they would additionally need 2 wires for controlling each motor and 2 wires per motor for feedback from the Hall-effect encoders. This makes the total wire bundle from the bottom level up to higher levels be only 14 wires (5V, Gnd, 4 for tape sensors, 2×4 for motors), with none of the wires having high-current switching.  The only high current wires are the 5V and Gnd wires, which may be feeding servo motors on higher levels—I may want a separate power wire from one of the switching regulators for powering servo motors on the higher levels, though.

The Teensy LC is not 5V-tolerant, so if I used that for the main controller board, I would need to communicate a 3.3V power line as well for the phototransistors and the motor encoders.  I think that I’ll simply declare that the main controller needs to be 5V-tolerant for digital inputs.

[Update 2017 Nov 18: I soldered up one of the TCRT5000L circuits, so that I could do a little more testing and make sure that I had a layout that worked.  It turned out that 22kΩ for the sense resistor was too large—at some distances the electrical tape reflected enough light to get above the 1.15V which is the guaranteed max VIL and maybe even enough to get to switch to high. I took out the 22kΩ and used a 10kΩ, and the problem with black reflecting too much light went away, but white still produced a very strong signal. I should be able to detect the white board reliably with just digital inputs out to about 1.4cm, much more than the 0.8cm clearance.

Advertisements

2017 November 15

Progress report on mechatronics

Filed under: Robotics — gasstationwithoutpumps @ 19:27
Tags: , ,

I’m falling behind in the mechatronics course—I still don’t have a complete design for the robot, and I’ve built nothing so far except the cat-toy ball track, which is not complete yet—I need to know how much of it to cut away for the ball launcher(s), which I’ve not designed.  I also have the digital beacon detector done, though I think I want to retweak the code for it, as I messed it up trying to get more sensitivity at the distance competition.

I spent yesterday struggling with SolidWorks.  I’m getting better at it, but it is still a terribly complicated, unintuitive interface.  Even trivial things like creating a sphere as a model ping-pong ball took a long time to figure out (I had to Google it, and even then it took me a long time to realize that “centerline” was a special tool, not just a line through the center.  My biggest accomplishment in SolidWorks so far is modeling the helical cat-toy ball track—I’ll have to get a picture of that model to put on the blog.

I did get most of the first layer of MDF designed yesterday, with the motor mounts, the wheel wells, the tape-sensor holes, the battery holder (MDF and velcro), and the M10 ball-tip set screws (to use as skids) all modeled.  I even got one support board for positioning the next layer done, though I’ll probably have to adjust it a bit, as I changed my mind last night about what bumper switches to get.  Instead of spending $3–5 a switch for gold contacts (which is the “right” way to handle low-current switching), I followed the lead of one of the students and am getting super cheap switches from Amazon, which are $7 for 10 switches.  These are standard 5A switches and probably get unreliable with currents below 100mA, though I’ll be using them with 1mA or less.  They are also tiny (specs from the Amazon page):

  • Overall Size : 20 x 6.5 x 13.5mm / 0.79″ x 0.26″ x 0.53″ (L*W*H)
  • Mounting Hole Dia. : 2mm / 0.08″;Mounting Hole Spacing : 9.5mm / 0.37″;Pin Pitch : 7.2 x 9mm / 0.28″ x 0.35″
  • Lever Arm Size : 17.5 x 4mm / 0.69″ x 0.16″ (L*W);Roller Size : 5 x 3mm / 0.2″ x 0.12″ (D*T)

The small size means that the second level will have to be slightly higher to put the bumpers at the right height.

Before I cut the first-layer board, I want to add the rest of the spacer boards between the first and second layer—one of which will have my volt/ammeter and power barrel jack (so I need to measure those and design the panel for them).

I also have to decide how I’m going to lock the boards together.  I don’t want to glue things (messy and hard to disassemble), so I’ll probably clamp everything together with threaded rods.  I’ll need to get the threaded rods and figure out where to put the holes for them so as not to interfere on any level—avoiding the cat-toy ball track will be the hardest, as it takes up most of the second level. Although most of my robot will be using metric parts, I’ll probably go with threaded rod from the hardware store, which will most likely be ¼” rods with 20 threads per inch, as that seems to be the cheapest (at 50¢–$1 a foot).  I think I’ll need 4 pieces, a little under 11″ long each (so that the top of the rod is at 11″ but the bottom doesn’t touch the floor).

I need to laser cut a small test piece with different hole sizes, to see how much increase in diameter there is from the laser cutting.  Most of my holes can be a little loose without problems, but the set screws need to screw into the MDF (unless I get some M10 nuts and put them on either side of the MDF, which might be a better solution anyway).

Today I was going to build tape sensors (soldering the TCRT5000 reflective sensors and current-limiting resistor onto protoboards that have screw holes drilled for mounting), but I’ve not done any hardware work so far today.  Last night I did order the bigger (100mH) inductors for the track-wire sensor, plus a few smaller shielded inductors, in case I decide to add another LC resonator to the sensor, to increase noise rejection, but I forgot to order an 8:1 analog mux, which I think I’ll need for extending the number of pins on the Teensy.  Oh well, another $3–4 in shipping costs and another few days delay.

I’ve spent most of today working on porting the Events and Services Framework to the Teensyduino environment.  I’ve got it all compiling and I’ve got event checkers working, but I haven’t checked the timers or other layers of services.  It turns out that the test harnesses built into the code have not been maintained, and some wouldn’t run (or even compile) even on the Uno32, because the code they supposedly test has been changed and no longer has the entry points they rely on (ES_Timers is one example).

The ES_Framework code is also not very modular, despite being broken up into many files—almost everything is intertwined to the point where you need it all before anything can compile.  Luckily, most of it has very little dependence on the specific processor, so I was able to port it without trouble.  One of the biggest problems was that the Teensyduino environment does not support printf, and the Serial.print() routine is C++, not C.  I added a few C wrappers for different flavors of Serial.print() and converted all the printf statements to a series of Serial_print_… calls.

I’ve not still figured out the best way to handle the analog-to-digital conversion.  The Pic32 processor used in the Uno32 boards for the class have a “scan” mode where the hardware handles scanning the selected analog channels, just doing an interrupt after all the channels have been scanned.  The Kinetis processors on the Teensy boards do not have that feature, so I have several options:

  • doing conversions in event detectors or services that need the values.  This will make the individual detectors slower, but no time will be wasted doing unused conversions. It would also make it easy for users to extend the number of analog pins with an external MUX, as the code for doing the analog conversions is not buried inside the system, but in the user-level code.  The disadvantage is that the code will need to busy-wait for each conversion, as the result is needed at the point in the code where it is requested.  I could just use the Teensyduino analogRead(), which already provides support for setting the resolution and averaging levels.  Then there is no need to port the AD.c file.
  • do a block of conversions just before calling all the user-defined event detectors.  This will put a largish delay before the event detectors, but several detectors or services can look at the value with needing another conversion. Currently, the event detector loop with just a trivial DummyEventChecker (that reports an event every 100,000 calls and toggles the on-board LED) takes about 3.34µs on a 72MHz Teensy 3.1, but doing 6–8 conversions would add 8–500µs, depending on how much hardware averaging I decide to do (which the user should be given access to control).  This technique is probably the closest to letting me duplicate the existing interface, which is designed around the Pic32 scan.
  • do a start-conversion and interrupt for each conversion.  If I do little hardware averaging, the overhead of the interrupt processing is large and nothing is gained, but if I opt for 32× averaging then interrupts have a big advantage over blocking code that busy waits for the conversions.

Currently, I’m leaning towards just using the analogRead() code, discarding all the Pic32-specific AD code.  I still need to decide whether to build in a battery-level check that shuts down the system on a drained battery.  Perhaps the cleanest way to do this would be to build a separate event checker that samples the battery voltage about every millisecond and low-pass filters it (rather than at 9345Hz/number of pins, the way the Uno32 code does).  (Sampling about every millisecond could be done just by watching the clock in the event checker and taking a new sample every time that millis() changed.)  Having a separate checker in a separate file, rather than intertwined with BOARD.c, AD.c , and roach.c would be much cleaner. After I’ve gotten all the basic functions working, I’ll think about dedicating a pin (perhaps A0, which has the fewest other functions) to battery-voltage monitoring.

I’m not sure how to ensure that all the external devices (servo motors, gear motors, … ) are properly turned off in BOARD_End(), before sleeping or halting the processor.  The Uno32 version of BOARD_End() doesn’t seem to do this either—in fact, it keeps the ADC and the serial port on, and just turns off most interrupts and sets pins to be digital inputs, not doing anything about the external peripherals that are the power-hungry ones.

2017 November 8

Starting to port the Events and Services Framework to Teensy boards

Filed under: Uncategorized — gasstationwithoutpumps @ 23:23
Tags: , , ,

Because I want to work at home with boards that I own, I need to port the Events and Services framework used in the Mechatronics class to the Teensy LC or Teensy 3.1 board.  The first thing to do is to identify what resources are used by the Teensyduino framework, and so inaccessible to me (except through their API).

The ES framework itself has only one interrupt routine, in ES_Timers.c, which runs on a millisecond tick, updating FreeRunningTimer and checking to see whether any of the ES_Timers have just timed out. While I would have loved to do this with the SysTick timer on the Teensy boards, Teensyduino uses that its own millisecond timer (accessible through millis()).  So I’ll probably have to use the IntervalTimer interface, which will use up one of the PIT (programmable interrupt timer resources).

Because I’ll be doing hardware pwm on selected pins, not software pwm, I don’t need to port the pwm code from the Uno32 library.  I will need timer resources for hardware pwm, which means using pins that have pwm support.  Different pins use different timers for the pwm, and the table at https://www.pjrc.com/teensy/td_pulse.html provides the mapping:

Teensy 3.2
Teensy 3.1
FTM0 5, 6, 9, 10, 20, 21, 22, 23 488.28 Hz
FTM1 3, 4 488.28 Hz
FTM2 25, 32 488.28 Hz
Teensy LC FTM0 6, 9, 10, 20, 22, 23 488.28 Hz
FTM1 16, 17 488.28 Hz
FTM2 3, 4 488.28 Hz

(They provide the mapping for other Teensy boards also, but I will only use LC and 3.1/3.2 boards on this project.) If I just use two PWM channels, then pins 3 and 4 would be a good choice on either chip, as few other uses conflict with those pins and they share a timer.  If I need more than 2 PWM channels, then pins 6, 9, 10, 20, 22, and 23 share FTM0 on both chips. Three of those channels are also A-to-D channels and pin 20 is SCK1 (the second SPI interface clock).  I’m planning to use pints 11, 12, and 13 for the SPI interface, plus one other digital pin for the chip select, so the conflicts on the PWM pins are probably irrelevant.

I don’t like the default PWM frequency of 488.28 Hz, though, and so will be changing the PWM resolution and frequency to get a PWM frequency above the audible range (the MAX14870 H-bridges had no trouble handling up to 50kHz).  Using an 10-bit resolution with a clock speed of 48MHz (Teensy LC) would give me a PWM frequency of 46875Hz and  with a clock speed of 72MHz (Teensy 3.1) 35156.25Hz (the code does some prescaling).  These look like good frequencies to request.

The code provided for the Uno supports RC servos with another timer.  In the Teensyduino library there are two different servo implementations: Servo and PWMServo.  PWMServo uses the same PWM hardware that the pwm library uses, but at a 50Hz PWM frequency.  If I use that library, it will be important to cluster the servo pins on to one of the FTM timers and the pwm pins onto a different timer.  The Servo library uses the LPTM timer on the Teensy LC and the PDB timer on the Teensy 3.1/3.2, which does not conflict with other uses, but the servo timing is subject to glitching, especially if interrupts are ever disabled in the code.

The Uno32 board is set up to read analog-to-digital values continuously so that reading the data can be a non-blocking operation that just grabs the latest value without waiting for a new conversion.  For my first version of the port, I’ll just use analogRead to read pins, but if that turns out to be too slow (which it might), I’ll change to using the analog reading used by PteroDAQ. One oddity of the ES framework is that the battery-check code is built into the analog-to-digital conversion code, and the processor put to sleep if the battery is low.  I suppose this was done so that filtering of the battery voltage could be done in the interrupt routine that samples all the ADC channels.  It seems like a worthwhile thing to shut everything down if the battery gets too low, so I might decide  to do something like theADC library.  The Teensy boards don’t have an interface for cycling through the channels the way the Uno32 appears to, so I would probably have to set up a rather different interface for this.  This looks like the biggest difficulty in porting the framework—it might be better to set up a battery monitor that is not embedded so deeply in the code, perhaps having a battery-check event checker that is automatically polled.

The provided timers.c package gives access to another set of millisecond timers, which can be polled.  This library does not seem very useful—if I want to know the time, I can call millis() and if I want to poll whether enough time has elapsed, I can compare millis() to a computed stop time.  It does not seem worthwhile to use up another timer just for millisecond resolution.

I’m not planning to use a stepper motor, so I won’t port the Stepper routines (which we had to modify as a homework exercise).  If I were to port these routines, I’d probably use up another IntervalTimer (which exhausts them for the Teensy LC).

The serial.c code uses a UART interrupt to send or receive over the USB port, but the Teensyduino enviroment includes Serial, so all I would need to do is to

#define printf Serial.printf

and there is no need to port serial.

So far, it looks like the partial port that I’m planning (porting the ES framework but not the peripherals) looks very straightforward, other than the battery voltage monitor.  I might skip that in the first port, and add it back in later.

2016 September 27

Teensy 3.5 & 3.6 Kickstarter

Filed under: Uncategorized — gasstationwithoutpumps @ 16:59
Tags: , , ,

As many of the followers of my blog know, the Teensy 3.1 and Teensy LC have been my favorite microcontroller boards for the past couple of years.  The Teensy 3.1 has since been replaced by the slightly better Teensy 3.2, which has a better voltage regulator but is otherwise pretty much the same as the 3.1.  I’ve been using the Teensy LC with PteroDAQ software for my electronics course.

I’ve just noticed that PJRC has a Kickstarter campaign for a new set of boards the Teensy 3.5 and 3.6.  These will be much more powerful ARM processors (120MHz and 180MHz Cortex M4 processors with floating-point units, so at least 2.5 times faster than the Teensy 3.2, more if floating-point is used much).  The form factor is similar to before, but the boards are longer, taking up 24 rows of a breadboard, instead of just 14.  The extra board space is mainly to provide more I/O, but there is also a MicroSD card slot.

The designer is still dedicated to making the Teensy boards run in the Arduino environment, and the breadboard-friendly layout is very good for experimenting.

PJRC is positioning the new boards between the old Teensy boards and the Linux-based boards like the Raspberry Pi boards. The new Teensy boards will have a lot of raw power, but not an operating system, though I suspect that people outside PJRC will try porting one of the small real-time operating systems to the board.

The new boards are a bit pricey compared to the Teensy LC ($23–28 instead of under $12 for the Teensy LC), but still reasonable for what they provide.  PJRC also has a history of providing good software for their boards.

I probably need to get both a Teensy 3.5 and a 3.6 to port PteroDAQ to them—that looks like a $50 purchase. If the boards and the software are available in time for me do development on PteroDAQ by December, I might get it done—any later than that and I’ll have no time, as I have a very heavy teaching and service load for Winter quarter.

I suspect that the new Teensyduino software will need a newer version of the Arduino development environment, which in turn would require a newer version of the Mac operating system (my laptop is still running 10.6.8), which in turn probably means a new laptop.

I’m waiting to see if Apple releases a new, usable MacBook Pro in October, so there is a bit of built-in delay in the whole process. I’m not impressed with their recent design choices for iPhones and MacBook Air—I need connections to my laptop—so there is a strong possibility that I may be having to leave the Macintosh family of products after having been a loyal user since 1984 (that’s 32 years now).

Update 2016 Sep 27: I just watched the Kickstarter video.  They used the Karplus-Strong algorithm as a demo.  Of course, that demo could have been done on a Z80 chip from 1976 (though not easily—the sampling rate suffered a bit to run 4 strings on the Z80, and only 8-bit DACs were affordable in those days).  It would have been more instructive to do an FM synthesis algorithm, which takes a lot more processor power than Karplus-Strong.

2016 July 13

Autoranging capacitance meter using TeensyLC

Filed under: Circuits course — gasstationwithoutpumps @ 09:59
Tags: , , , ,

 

My earlier post, Capacitance meter using touchRead(), showed the beginnings of a capacitance meter, using the touchRead() routine in the Teensyduino environment.

Today I’ll share a slightly more complicated program that uses the TSI (touch sensing input) peripheral more directly to make an autoranging capacitance meter that can measure capacitors down to 1pF and up to 3µF.  The repeatability of the measurements is not great (about ±3%), but the linearity seems pretty good.

The program includes the ability to zero out the test fixture (important for measuring small capacitances) and to calibrate the meter to a known capacitor.  I don’t have a capacitor with a tight tolerance, so I had to do my calibration against a DT-9205A multimeter, which is not a very reliable standard.  Still, it seemed more consistent than the labeling on the cheap ceramic capacitors I have, some of which seem to be off by a factor of two!

The multimeter could not measure capacitances at the low end of the range, so only nominal values are used there.

The multimeter could not measure capacitances at the low end of the range, so only nominal values are used there.

A number of the capacitors in the 100pF–10nF range seemed to drift consistently upward for quite a while, both with the multimeter and my homebrew capacitance meter. Touching them with my finger brought them sharply back down again, but touching them with stainless steel tweezers did not. I suspect that the effect is a thermal one, with the capacitance dropping when my fingers warmed the capacitors and going up again as the capacitors cooled to room temperature. The change was several percent, which is consistent with a low-quality “Y” dielectric.

// Preliminary program for a capacitance meter
// Kevin Karplus
// 2016 Jul 13


// To use:
//	* Connect a serial monitor (like the Arduino IDE) to the USB port
//	* Connect capacitor to measure between pin 0 and ground
//	* Press "a" and <return> to make an autorange measurement.

// Capacitances in the range 1pF to 3uF can be measured, but large
// capacitors take a long time to measure, up to 9s for 3uF.
// The measurement time can be reduced by reducing NUM_READS, perhaps
//	replacing the constant by an array dependent on range.
//	A factor of about 10 reduction in measurement time is available, as
//	NUM_READS is currrently set to 20.

// Note: readings may drift by +- 5%.  I've not yet determined the
// cause of this drift.  It may be thermal (cooling from finger
// temperature to room temperature may raise the capacitance by 2%,
// depending on the additives to the ceramic used as the dielectric).

// To calibrate:
//	* Remove capacitors from pin 0
//	* Type 'z' to measure the counts for the empty test fixture
//	* Pick a known capacitor (around 1nF) and connect it between
//		pin 0 and ground.  The capacitor must be small enough
//		to be measurable on the highest-resolution range (<=3nF).
//		To calibrate with a larger capacitor, first calibrate with
//		a small one to get the highest-resolution range set, then
//		repeat the calibration with the larger capacitor, which
//		will only reset the larger ranges.
//	* Type 'k', followed by an integer known capacitance in pF,
//		followed by a separator (like ';')
//	* Type 'c', to measure the pF per count

// Note: the calibrations are reported to the USB serial port, so they
// can be saved and used to edit the source code, changing count_for_0pF
// and pf_per_count arrays.

// The measurement process can be followed in more detail by turning
// on debugging with the 'D' command.
// Debugging can be turned off with the 'd' command.

// Capacitance is measured on pin 0 (one of several pins with TSI),
// because pin 0 is immediately next to ground, making it easy to connect
// small capacitors between pin 0 and ground.
#define TOUCH_PIN (0)

// forward references to later routines
int32_t capRead(uint8_t pin, 
		uint8_t I_ext=3, uint8_t I_ref=4, 
		uint8_t Prescale=2, uint8_t N_scan=9);
float autoCapRead(uint8_t pin);
int readInt(void);
void print_calib(int count, int n_cycles=1);
float read_counts_per_cycle(uint8_t i_ext, uint8_t i_ref,float count_for_zero);

volatile uint8_t debug;

// The current for the external oscillator and the reference
// oscillator of the TSI interface determine the capacitance range, resolution,
// and speed of the measurement.
// The program uses the highest-resolution range that does not cause
// the 16-bit counter to overflow.

// Settings of the current paramters for the different ranges
// The highest-resolution range is first, the widest range is last.
#define NUM_RANGES (6)
const int8_t i_ext_choices[NUM_RANGES] = {2,3,4,5,6,7};
const int8_t i_ref_choices[NUM_RANGES] = {5,4,3,2,1,0};

float pF_per_count[NUM_RANGES]=
	{0.2126, 0.772, 2.897, 11.08, 47., 195.3};
float count_for_0pF[NUM_RANGES] =
	{48.7612, 13.82, 3.813, 1.0605, 0.273, 0.0517};

// parameters for calibration
int C_known=0;	// known capacitance value 

void setup() 
{
    pinMode(TOUCH_PIN, INPUT);
    Serial.begin(115200);
    debug=0;
}

void loop()
{
     if (Serial.available())
     {   char c=Serial.read();
         if (c=='D') {debug=1;}
	 else if (c=='d') {debug=0;}
	 else if (c=='a')
         {   // do one reading and print it
             Serial.print(autoCapRead(TOUCH_PIN));
             Serial.println(" pF");
        }
        else if (c=='k')
        {   // set known capacitance for calibration checks
	   C_known=readInt();
        }
	else if (c=='z')
	{   // Set the count_for_0pF array for each range.
	    // Print the current parameters and zero count for each range.
	    // Only issue this command if there is no capacitor connected
	    // to TOUCH_PIN
	    for (int range=0; range<NUM_RANGES; range++)
	    {   uint8_t i_ext=i_ext_choices[range];
	        uint8_t i_ref=i_ref_choices[range];
		count_for_0pF[range] =read_counts_per_cycle(i_ext,i_ref,0.0);
		Serial.print("# zero for ");
		Serial.print(i_ext); Serial.print("\t");
		Serial.print(i_ref); Serial.print("\t");
		Serial.println(count_for_0pF[range],4);
	    }	
	}
	else if (c=='c')
	{   // do autocalibration, setting pF_per_count
	    // printing known capacitance, 
	    //		external current setting,
	    //		reference current setting,
	    //		average pF_per_count for a single cycle
	    
	    if (C_known==0)
	    {   Serial.println("# use 'k<known C in pF>;' first");
	    	return;
	    }
	    for (int range=0; range<NUM_RANGES; range++) { uint8_t i_ext=i_ext_choices[range]; uint8_t i_ref=i_ref_choices[range]; float calib=read_counts_per_cycle(i_ext,i_ref,count_for_0pF[range]); if (!isnan(calib)) { calib = C_known/calib; pF_per_count[range]= calib; } else { Serial.print("# "); // comment out overflows } Serial.print(C_known); Serial.print("\t"); Serial.print(i_ext); Serial.print("\t"); Serial.print(i_ref); Serial.print("\t"); Serial.println(calib,4); } } } } // print a calibration line void print_calib(int count, uint8_t i_ext, uint8_t i_ref, int n_cycles) { float avg_count = (count+0.0)/n_cycles; Serial.print(C_known); Serial.print("\t"); Serial.print(i_ext); Serial.print("\t"); Serial.print(i_ref); Serial.print("\t"); Serial.print(count); Serial.print("\t"); Serial.print(n_cycles); Serial.print("\t"); Serial.println(C_known/avg_count,4); } float read_counts_per_cycle(uint8_t i_ext, uint8_t i_ref, float count_for_zero) { // Do one measurement and return average counts/cycle - count_for_zero. // If debug set, print C_known, count, i_ext, i_ref, n_cycles, cap/count // // Actually does NUM_READS+1 measurements: one with a single cycle, // then again NUM_READS times with as many cycles as can be fit without // overflowing counter. if (debug) { Serial.println("# C\ti_ext\ti_ref\tcount\tn_cycle\tC/avg_count"); } int count=capRead(TOUCH_PIN, i_ext, i_ref, 0, 0); if (count>=0xFFFF)
    {   if (debug)
    	{   Serial.print("# "); // comment out overflows
	    print_calib(count,i_ext,i_ref,1);
	}
	return NAN;	// abort rest of calibration check
    }

    // Determine max number of cycles that can fit
    int cycles= 0xFFFE/count; // how many cycles to use
    int prescale,n_scan;
    for (prescale=0; cycles>32 && prescale<7; prescale++) {cycles/=2;} if (cycles==0) {n_scan=0;} else if (cycles>32) {n_scan=31;}
    else {n_scan=cycles-1;}
    cycles = (n_scan+1)<<prescale;

#define NUM_READS	(20)
    // To do: replace NUM_READS with a const uint8_t num_reads[NUM_RANGES]
    // array, to speed up measurement at high ranges.

    int sum_count=0;
    for (int i=0; i<NUM_READS; i++) { count=capRead(TOUCH_PIN, i_ext, i_ref, prescale, n_scan); if (debug) { print_calib(count, i_ext, i_ref, cycles); } sum_count+=count; } return (sum_count+0.0)/ (NUM_READS*cycles) -count_for_zero; } // Read a non-negative integer from Serial as a series of digits, terminated // by any non-digit (recommend using something obvious like ';'). // The terminating character is discarded. int readInt(void) { int value=0; while (!Serial.available()) {} // wait for next char char c=Serial.read(); while (c>='0' && c<='9')
    {   value= 10*value + (c-'0');
	while (!Serial.available()) {}	// wait for next char
       	c=Serial.read();
    }
    return value;
}
   


float autoCapRead(uint8_t pin)
{
    // Return the capcitance in pF at the pin, using the highest-resolution
    // range that doesn't overflow the counter.
    
    // pick the lowest (highest resolution) range that doesn't overflow
    int32_t count;	// number of counts of ref oscillator
    int range;
    for (range=0, count=0xFFFF;  range<NUM_RANGES && count>=0xFFFF; range++)
    {	count=capRead(pin, i_ext_choices[range],i_ref_choices[range],0,0);
    }
    range--;
    
    if (count>=0xFFFF)
    {    return NAN;	// capacitance too big to measure with TSI
    }
    
    float count_per_cycle=read_counts_per_cycle(i_ext_choices[range],
		i_ref_choices[range],
		count_for_0pF[range]);
    return pF_per_count[range]*count_per_cycle;
}


/* Raw reading is based on the
 * Teensyduino Core Library touch.c (which implements touchRead)
 * http://www.pjrc.com/teensy/
 * Copyright (c) 2013 PJRC.COM, LLC.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * 1. The above copyright notice and this permission notice shall be 
 * included in all copies or substantial portions of the Software.
 *
 * 2. If the Software is incorporated into a build system that allows 
 * selection among a list of target devices, then similar target
 * devices manufactured by PJRC.COM must be included in the list of
 * target devices and selectable in the same manner.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

// #include "core_pins.h"

// capacitance is supposed to be
//	Cref * (1<<I_ext) / ( (1<<I_ref) * (1<<Prescale) * (N_scan+1) ) * COUNT
// but use 
// Cref * ext_current[I_ext] / ( ref_current[I_ref] * (1<<Prescale) * (N_scan+1) ) * COUNT // because the current ratios are not a constant factor of 2 // with DVOLT==0 (slowest, but least noise sensitive), // I_ext = I_ref-1, // Prescale=2, // N_scan=9, // Capacitance is approx 0.01846 pF * COUNT, // so Cref approx 1.47694pF #if defined(__MK20DX128__) || defined(__MK20DX256__) // These settings give approx 0.02 pF sensitivity and 1200 pF range // Lower current, higher number of scans, and higher prescaler // increase sensitivity, but the trade-off is longer measurement // time and decreased range. static const uint8_t pin2tsi[] = { //0 1 2 3 4 5 6 7 8 9 9, 10, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 13, 0, 6, 8, 7, 255, 255, 14, 15, 255, 12, 255, 255, 255, 255, 255, 255, 11, 5 }; #elif defined(__MK66FX1M0__) static const uint8_t pin2tsi[] = { //0 1 2 3 4 5 6 7 8 9 9, 10, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 13, 0, 6, 8, 7, 255, 255, 14, 15, 255, 255, 255, 255, 255, 11, 12, 255, 255, 255, 255, 255, 255, 255, 255, 255 }; #elif defined(__MKL26Z64__) static const uint8_t pin2tsi[] = { //0 1 2 3 4 5 6 7 8 9 9, 10, 255, 2, 3, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 13, 0, 6, 8, 7, 255, 255, 14, 15, 255, 255, 255 }; #endif // for I_ref=I_ext+1, N_scan=9, Prescale=2, output is approx pF * 50 // time to measure 33 pF is approx 0.25 ms // time to measure 1000 pF is approx 4.5 ms int32_t capRead(uint8_t pin, uint8_t I_ext, uint8_t I_ref, uint8_t Prescale, uint8_t N_scan) { uint32_t ch; int32_t count; if (pin >= NUM_DIGITAL_PINS) return 0;
    ch = pin2tsi[pin];
    if (ch == 255) return 0;

    *portConfigRegister(pin) = PORT_PCR_MUX(0);
    SIM_SCGC5 |= SIM_SCGC5_TSI;
#if defined(KINETISK)
    TSI0_GENCS = 0;
    TSI0_PEN = (1 << ch);
    TSI0_SCANC = TSI_SCANC_REFCHRG(I_ref) | TSI_SCANC_EXTCHRG(I_ext);
    TSI0_GENCS = TSI_GENCS_NSCN(N_scan) | TSI_GENCS_PS(Prescale) | TSI_GENCS_TSIEN | TSI_GENCS_SWTS;
    delayMicroseconds(10);
    while (TSI0_GENCS & TSI_GENCS_SCNIP) ; // wait
    delayMicroseconds(1);
    count= *((volatile uint16_t *)(&TSI0_CNTR1) + ch);
#elif defined(KINETISL)
    TSI0_GENCS = TSI_GENCS_REFCHRG(I_ref) | TSI_GENCS_EXTCHRG(I_ext) | TSI_GENCS_PS(Prescale)
	    | TSI_GENCS_NSCN(N_scan) | TSI_GENCS_TSIEN | TSI_GENCS_EOSF;
    TSI0_DATA = TSI_DATA_TSICH(ch) | TSI_DATA_SWTS;
    delayMicroseconds(10);
    while (TSI0_GENCS & TSI_GENCS_SCNIP) ; // wait
    delayMicroseconds(1);
    count= TSI0_DATA & 0xFFFF;
#endif
    if (debug)
    {
	Serial.print("  I_ext= ");  Serial.print(I_ext);
	Serial.print("  I_ref= ");  Serial.print(I_ref);
	Serial.print("  Prescale= ");  Serial.print(Prescale);
	Serial.print("  N_scan= ");  Serial.print(N_scan);
	Serial.print("  count= ");  Serial.print(count);
	Serial.println();
    }
    return count;
}
Next Page »

Create a free website or blog at WordPress.com.

%d bloggers like this: