-
4Jun2016 UpCounterV1.js
The previous Upcounter.js has been extended.
The Pins table has entries for Timer 8 channels added.
The setupCounter() function has dropped the prescale argument as it is not active.
A new function has been added. UpCounter.prototype.setupCounter32=function(Mtim,Mchan,Mfilter,Stim);
It allows two timers to be chained together to form a 32 bit counter.
The master timer (Mtim) is used to input an external signal on Mchan (1 or 2) and be filtered by Mfilter. When the Mtim overflows the slave timer (Stim) count is incremented. Mtim acts as a 16 bit prescaler to Stim
The count value (CNT) from Mtim is the LSB and the CNT value from STM is the MSB part of the count.There are 4 internal paths used to chain the timers named ITR0, ITR1, ITR2, and ITR3.
The array TSvalues is used by the function getTS() to determine the chaining path.
This is all discussed on page 397 of the RM0008 Reference manual
15.3.15 Timer synchronization
Tables 82 and 86
See attached file TSvalues.xlxs
RM0008 Reference manual
http://www.st.com/content/ccc/resource/technical/document/reference_manual/59/b9/ba/7f/11/af/43/d5/CD00171190.pdf/files/CD00171190.pdf/jcr:content/translations/en.CD00171190.pdf
AN2592
http://www.st.com/content/ccc/resource/technical/document/application_note/0c/88/73/b9/d9/7f/4d/70/CD00165509.pdf/files/CD00165509.pdf/jcr:content/translations/en.CD00165509.pdf
AN4013
http://www.st.com/content/ccc/resource/technical/document/application_note/54/0f/67/eb/47/34/45/40/DM00042534.pdf/files/DM00042534.pdf/jcr:content/translations/en.DM00042534.pdf
I’m still puzzling about the nature of these inter timer links. Obviously the master timer is the source of the signals. Can a link with one master timer talk to more than one slave timer?
It is conceivable to connect a master to slave 1 and then have slave 1 act as a master to slave 2. This would make a 48 bit counter possible.The Mfilter argument is discussed on pages 342 and 406. Value can range from 0 thru 15 with 0 being no filtering. It inserts a delay that acts as a de-bounce of a noisy signal.
In sections 14.4.3 TIM1&TIM8 slave mode control register (TIMx_SMCR) and 15.4.3 TIMx slave mode control register (TIMx_SMCR)The counters so far are based on pages 377 and 378 of the RM0008 Reference manual
External clock source mode 1.Pages 379 and 380 discuss External clock source mode 2. The problem is associating the ETR to a pin on the Espruino board. This will entail setting up mode 2 and trying all the pins until the one that works for each timer. Using the ETR as an external input allows the timer to operate in gated mode. A second timer can be used to generate a one time pulse to gate the first timer for a more precise measurement of frequency.
Place UpcounterV1.js in the modules folder of a WebIDE project and the remaining js files in the project folder.
The reader can test these functions by modifying the timer numbers and channel numbers in the test programs. I encourage readers to try the variations and report any problems. An external connection between the timer3 PWM output on pin B0 and the input pin of the counter is required. -
Is it possible to extend setWatch to include timer interrupts?
The scenario:
One counter is being used to count external pulses and is being gated by a second timer in one time pulse mode (OTP). The program starts the one time pulse. If setWatch were able to detect the finished interrupt from the OTP then the program would know that the measured count is available to read and process. -
Thanks for the encouragement. Making a module makes sense. Since my last post I've managed to chain timer 1 and 2 together to do a 32bit counter. I'm working on making it work using any two timers. Future work is mode 2 counting, a one time pulse timer, chaining the OTP to gate a counter, the capture modes that measure a pulse, encoder mode for a shaft encoder, and finally the motor control mode for timers 1 and 8. Lots of work. Maybe more than one module. The biggest headache is determining the pin out of the timers as it is not well documented.
-
UpCounter.js A 16bit Up Counter Module
Place UpCounter.js in the modules folder of your WebIDE project.
Place the TestUpCounterTXChX.js files in the projects folder
For timers 1, 2, 4, and 5 pin B0 is used as a signal source for testing.
For timer 3 Pin A0 is used as a signal source for testing.
For example running TestUpCounter T5Ch1.js produces the following output.
Note that the timer input pin is the first thing displayed. For testing connect the signal source pin to the timer input pin. Use ClearInterval() to stop the program.>echo(0); Pin= A0 =undefined CNT=0.5 Hz. CNT=976 Hz. CNT=972.5 Hz. CNT=1006 Hz. CNT=1000 Hz. CNT=998 Hz. CNT=998.5 Hz. CNT=999 Hz. CNT=999 Hz. CNT=999 Hz. CNT=999 Hz. >clearInterval(); =undefined >
To setup several counters on different timers you only need one instance of UpCounter.
The file Tremap.xlsx shows the pins associated with the timers. It is possible to remap the pins by writing to the remap register. There are unknown pin assignments marked with a question mark. So far I haven’t located a document that gives the pinout of the timers.
The timers have four channels. The registers CCMR1 and CCMR2 allow the configuration of the 4 channels. The register SMCR only allows channels 1 and 2 and not 3 and 4. -
Powering and Resetting Timers
So far programs in this project have been aimed at a specific timer and purpose. At this point I want to shift to a more generalized approach.
To clarify powering a timer consists of turning on the clock to the timer using a bit in either the APB1ENR or APB2ENR registers.
Resetting a timer consist of setting and resetting a bit in the APB1RSTR or APB2RSTR
The attached program TimOnOff.js implements these functions.
If a timer is off and attempts to write to the timer registers, they return zero values when read back. The program loops thru timers 1 to 14 and reports if the timer is on, it then turns on the timer and resets it. It reports if the timer is enabled and then disables the timer.
It write a unique value to the timer’s CNT counter register and reads it back.
If the program is run after a fresh power up of the board reports timers 1 thru 8 as off and is able to write and read the CNT register. A subsequent run of the program without a hard reset reports timers 1 thru 8 as on.
Timers 9, 10, and 11 have been omitted from the disable, write CNT as doing so crashes the Espruino board. The CNT read produces non-zero values. These timers do not report a power on. Additionally if the isTimerEnabled function uses a peek16 command the value returned in bit 0 is different than when a peek32 command is used.
Timers 12, 13, and 14 do not seem to be present on the Espruino board.
One of the difficulties is that the RM0008 Reference manual is written to cover a range of ARM chips with different sets of capabilities.
From the experimental evidence confining future progress should be aimed at timers 1 through 8.Output of TimOnOff.js
Timer 0 out of bounds 1..14 Timer 0 out of bounds 1..14 Timer 0 out of bounds 1..14 Timer 0 out of bounds 1..14 Timer 0 out of bounds 1..14 Timer 0 out of bounds 1..14 Timer 0 out of bounds 1..14 0 Timer 0 out of bounds 1..14 1 is off Timer 1 Disabled ff01 1 is on 2 is off Timer 2 Disabled ff02 2 is on 3 is off Timer 3 Disabled ff03 3 is on 4 is off Timer 4 Disabled ff04 4 is on 5 is off Timer 5 Disabled ff05 5 is on 6 is off Timer 6 Disabled ff06 6 is on 7 is off Timer 7 Disabled ff07 7 is on 8 is off Timer 8 Disabled ff08 8 is on 9 is off Timer 9 Enabled 14c2409 9 is off 10 is off Timer 10 Enabled 1502409 10 is off 11 is off Timer 11 Enabled 1542409 11 is off 12 is off Timer 12 Disabled 0 12 is off 13 is off Timer 13 Disabled 0 13 is off 14 is off Timer 14 Disabled 0 14 is off Timer 15 out of bounds 1..14 Timer 15 out of bounds 1..14 Timer 15 out of bounds 1..14 Timer 15 out of bounds 1..14 Timer 15 out of bounds 1..14 Timer 15 out of bounds 1..14 Timer 15 out of bounds 1..14 0 Timer 15 out of bounds 1..14
-
Thanks for the feedback Gordon. It may be a bit premature for a module as there are other examples in the documentation to work through and a need to make the code a bit more generalized. For example the previous code that measures frequency may be more accurate if two timers are chained together, where one does the counting and the other does the time base gating of the first.
-
To drive relay coils, most circuits use a diode across (in parallel with) the coil pointed so that current doesn't flow when power is applied to the relay coil. When the power is removed from the relay coil the diode suppresses the inductive kick from the coil. Some example diagrams:
http://www.bowdenshobbycircuits.info/r_ctrl.htm -
FreqT5A1.js Measuring Frequency Using Timer 5 and Pin A1
Uses Pin B0 as a frequency source to test./* FreqT5A1.js Frequency measurement using Pin A1 on Timer 5 Use Pin B0 of Timer3 as a source of square waves to measure Connect Pin B0 to Pin A1 zero counter, start counter, count for timebase ms, stop counter, display count as frequency in Hz as adjusted by timebase repeat //Now displays timers 1 thru 14 This was run on an Espruino board To clear the timer requires the board to be powered down or the reset button */ /* http://www.keil.com/dd/docs/datashts/st/stm32f10xxx.pdf */ var TIM5=0x40000C00; //Timer register offsets var CR1=0x0; var CR2=0x04; var SMCR=0x08; var DIER=0x0c; var SR=0x10; var EGR=0x14; var CCMR1=0x18; var CCMR2=0x1c; var CCER=0x20; var CNT=0x24; var PSC=0x28; var ARR=0x2c; var CCR1=0x34; var CCR2=0x38; var CCR3=0x3c; var CCR4=0x40; var DCR=0x48; var DMAR=0x4c; // use B0 as a frequency source to measure analogWrite(B0,0.5,{ freq : 1000 }); //setup pin A1 as the counter input A1.mode('input'); //timer 5 clock var RCCbase=0x40021000; var APB1ENR=RCCbase+0x1c; var APB1RST=RCCbase+0x10; var APB2ENR=RCCbase+0x18; //turn on timer 5 clock poke8(APB1ENR,(peek8(APB1ENR)&0xf7)|0x08); //reset timer 5 poke8(APB1RST,(peek8(APB1RST)&0xf7)|0x08); poke8(APB1RST,(peek8(APB1RST)^0x08)); //disable timer 5 poke32(TIM5+CR1,0x80); //setup timer 5 poke16(TIM5+CCMR1,0x0100); poke16(TIM5+CCER,0x0000); poke16(TIM5+SMCR,0x0067); poke32(TIM5+CNT,0); //Preload timer poke32(TIM5+EGR,1); //Enable timer 5 //poke32(TIM5+CR1,0x81); var timebase=2000; setInterval(function () { poke32(TIM5+CR1,0x80); console.log("CNT="+peek32(TIM5+CNT)*1000/timebase +" Hz."); poke32(TIM5+CNT,1); poke32(TIM5+CR1,0x81); }, timebase);
-
CountT5A1Poke.js
Program that looks at timer registers to test timer 5 in
External clock source mode 1 see page 377,378 of RM0008
Reference manualSets up a PWM on Pin B0, connect to pin A1 channel 2 of timer 5.
analogWrite(B0,0.5,{ freq : 1000 }); //setup pins A0, A1, A2, A3 A0.mode('input'); A1.mode('input'); A2.mode('input'); A3.mode('input'); //timer 5 clock var RCCbase=0x40021000; var APB1ENR=RCCbase+0x1c; var APB1RST=RCCbase+0x10; var APB2ENR=RCCbase+0x18; //turn on timer 5 clock poke8(APB1ENR,(peek8(APB1ENR)&0xf7)|0x08); //reset timer 5 poke8(APB1RST,(peek8(APB1RST)&0xf7)|0x08); poke8(APB1RST,(peek8(APB1RST)^0x08)); //disable timer 5 poke32(TIM5+CR1,0x80); //setup timer 5 poke16(TIM5+CCMR1,0x0100); poke16(TIM5+CCER,0x0000); //0; poke16(TIM5+SMCR,0x0067);//0x0067); //Preload timer poke32(TIM5+EGR,1); //Enable timer 5 poke32(TIM5+CR1,0x81); //10Hz //Display timers for(var i=0; i in timers;i++){ if(isTimerEnabled(timers[i])) showTimer(timers[i].t,16,i); }//next i console.log("Display Count"); for(var i=0;i<1000;i++) console.log("CNT="+peek32(TIM5+CNT));
Try changing the frequency of the PWM on pin B0.
-
-
Using Poke to Do PWM on Pins A0, A1, A2, and A3
Before trying to configure a timer into other modes of operation using poke commands, it would be useful to configure a timer using a known configuration created by the command analogWrite(A0,0.2,{ freq : 50 });
A known configuration:
If we modify isTimerEnabled3.js lines 168 thru 172 to read:analogWrite(A0,0.2,{ freq : 50 }); //analogWrite(A1,0.4,{ freq : 50 }); //analogWrite(A2,0.6,{ freq : 50 }); //analogWrite(A3,0.8,{ freq : 10 }); The output for timer 5 is: Timer 5 Enabled CR1=81 CR2=0 SMCR=0 DIER=0 SR=3 EGR=0 CCMR1=68 CCMR2=0 CCER=1 CNT=cbe9 PSC=15 ARR=ffae CCR1=3322 CCR2=0 CCR3=0 CCR4=0 DCR0 DMAR=81
The attached file PokeA0Timer5a.js contains the code to implement pins A0, A1, A2, A3 on timer 5 using poke commands. Note that either powering the board off or using the reset button will clear the timer settings.
First setup the pins for alternate function output://setup pins A0, A1, A2, A3 A0.mode('af_output'); A1.mode('af_output'); A2.mode('af_output'); A3.mode('af_output');
Next turn timer 5 on and reset it
//timer 5 clock var RCCbase=0x40021000; var APB1ENR=RCCbase+0x1c; var APB1RST=RCCbase+0x10; var APB2ENR=RCCbase+0x18; //turn on timer 5 clock poke8(APB1ENR,(peek8(APB1ENR)&0xf7)|0x08); //reset timer 5 poke8(APB1ENR,(peek8(APB1RST)&0xf7)|0x08); poke8(APB1ENR,(peek8(APB1RST)^0x08));
Finally configure timer 5 and enable it
//disable timer 5 poke32(TIM5+CR1,0x80); //setup timer 5 poke32(TIM5+ARR,0x0ffae); //Setup duty cycle values poke32(TIM5+CCR1,0x3322); //A0 duty cycle poke32(TIM5+CCR2,0x3322); //A1 duty cycle poke32(TIM5+CCR3,0x3322); //A2 duty cycle poke32(TIM5+CCR4,0x3322); //A3 duty cycle //setup CCMR1 and 2 //poke32(TIM5+CCMR1,0x68); //A0 poke32(TIM5+CCMR1,0x6868);//A0,A1 //poke32(TIM5+CCMR2,0x68);//A3 poke32(TIM5+CCMR2,0x6868);//A3,A4 //Setup PSC prescaler poke16(TIM5+PSC,0x6d); //Connect timer output to pins //poke32(TIM5+CCER,0x0001); //A0 //poke32(TIM5+CCER,0x0011); //A0,A1 //poke32(TIM5+CCER,0x0111); //A0,A1,A2 poke32(TIM5+CCER,0x1111); //A0,A1,A2,A3 //Preload timer poke32(TIM5+EGR,1); //Enable timer 5 poke32(TIM5+CR1,0x81); //10Hz //poke32(TIM5+CR1,0x41); //5Hz //poke32(TIM5+CR1,0x01); //10Hz
Note the frequency is determined by the ARR, and PSC registers and can be modified by the CR1 register.
Additionally the frequency depends on the clock chain see Figure 8 on page 92 of the RM0008 Reference manual. The register APB1 controls the prescaler for timers 2, 3 , 4, 5, 6, 7, 12, 13, and 14. The register APB2 controls the prescaler for timers 1, 8, 9, 10, and 11.The CCMR1 and CCMR2 registers configure the CC (capture/compare) pins of the timer as output or input on one of three sources.
The CCER register configures the polarity and enable that connects to the pins A0..A3.
-
Experiments with timers:
Experiment 1:
Setup A0 at 50hz, Duty cycle 20%
Setup A1 at 50hz, Duty cycle 40%
Setup A2 at 50hz, Duty cycle 60%
Setup A3 at 50hz, Duty cycle 80%
Using isTimerEnabled3.js modify lines 168..172analogWrite(A0,0.2,{ freq : 50 }); analogWrite(A1,0.4,{ freq : 50 }); analogWrite(A2,0.6,{ freq : 50 }); analogWrite(A3,0.8,{ freq : 50 }); Results: Timer 5 Enabled CR1=81 CR2=0 SMCR=0 DIER=0 SR=f EGR=0 CCMR1=6868 CCMR2=6868 CCER=1111 CNT=d9ba PSC=15 ARR=ffae CCR1=3322 CCR2=6645 CCR3=9968 CCR4=cc8b DCR0 DMAR=81
Confirm with scope on pins A0, A1, A2, and A3.
All outputs are 50 Hz.
A0 duty cycle is 20%, A1 duty cycle is 40%, A2 duty cycle is 60%, A3 duty cycle is 80%
Modify the A3 line and change the frequency to 10 hzanalogWrite(A0,0.2,{ freq : 50 }); analogWrite(A1,0.4,{ freq : 50 }); analogWrite(A2,0.6,{ freq : 50 }); analogWrite(A3,0.8,{ freq : 10 }); Results: Timer 5 Enabled CR1=81 CR2=0 SMCR=0 DIER=0 SR=11 EGR=0 CCMR1=6868 CCMR2=6868 CCER=1111 CNT=28fd PSC=6d ARR=ffae CCR1=3322 CCR2=6645 CCR3=9968 CCR4=cc8b DCR0 DMAR=81
Use scope on pins A0, A1, A2, and A3 to check frequency and duty cycle.
All outputs are 10 Hz.
A0 duty cycle is 20%, A1 duty cycle is 40%, A2 duty cycle is 60%, A3 duty cycle is 80%
Timer 5 values differ in the PSC registers for each case.
PSC at 10Hz is x6d, PSC at 50Hz is 0x15, converting to decimal give 109 and 21.
109/21 ~= 5Design consideration:
If you need PWM outputs with different frequencies, you will need to select pins that use different timers for different frequencies. -
isTimerEnabled3.js has expanded to cover timers 1 thru 14.
Toffset.xlsx compares the four kinds of timer register offsets over the range of timers 1 thru 14. This was used to build the switch statement in isTimerEnabled3.js
ESpPinsTimers.xlsx shows the results of scans using isTimerEnabled.js one pin at a time. The procedure used: 1. Disconnect WebIDE, 2. Push reset button, 3. Reconncect WebIDE, 4. Edit analogWrite command line (169..172) in isTimerEnabled and 5. Load and run the program and note the results -
Properties of Timers 2, 3, 4, and 5
Valid pins for PWM analogWrite:analogWrite(A4,0.1,{ freq : 10 }); ERROR: Pin A4 is not capable of PWM Output Suitable pins are: A0 A1 A2 A3 A6 A7(AF) A8 A9 A10 A11 B0(AF) B1(AF) B3(AF) B4(AF) B5(AF) B6 B7 B8 B9 B10(AF) B11(AF) B13 B14 B15 C6(AF) C7(AF) C8(AF) C9(AF) Or pins with DAC output are: A4 A5 You can also use analogWrite(pin, val, {soft:true}) for Software PWM on this pin =undefined
Note the AF notation AF = Alternate Function
Using the attached file isTimerEnabled1.js and modifying the analog Write statements
Timer 5 uses pins:
A0, A1, A2, A3 or AF pins
Timer 4 uses pins:
B6, B7, B8, B9 or AF pins
Timer 3 uses pins:
A6, A8, A9, A10 or AF pins B4, B5, B0
Timer 2 uses pins:
Or AF pins B3, B10, B11.
Pins B1, A7, B13, B14, B15, C6, C7, C8, C9 are not seen by timers 2, 3, 4, or 5 when
used in a PWM analog Write statement.
When isTimerEnabled1.js is used with the following analogWrite commands,analogWrite(A0,0.1,{ freq : 10 }); analogWrite(A1,0.2,{ freq : 10 }); analogWrite(A2,0.3,{ freq : 10 }); analogWrite(A3,0.4,{ freq : 10 }); The output: >echo(0); Timer 2 Disabled Timer 3 Disabled Timer 4 Disabled Timer 5 Enabled CR1=81 CR2=0 SMCR=0 DIER=0 SR=1 ERRCW=0 CCMR1=6868 CCMR2=6868 CCER=1111 CNT=1da5 PSC=6d ARR=ffae CCR1=1991 CCR2=3322 CCR3=4cb4 CCR4=6645 DCR=0 DMAR=81
Note the CCR1 thru CCR4 reflect the different duty cycles specified in the analogWrite commands.
If the analog Write commands are changed toanalogWrite(A6,0.1,{ freq : 10 }); analogWrite(A8,0.2,{ freq : 10 }); analogWrite(A9,0.3,{ freq : 10 }); analogWrite(A10,0.4,{ freq : 10 }); The Output: >echo(0); Timer 2 Disabled Timer 3 Enabled CR1=81 CR2=0 SMCR=0 DIER=0 SR=1 ERRCW=0 CCMR1=68 CCMR2=0 CCER=1 CNT=16e2 PSC=6d ARR=ffae CCR1=1991 CCR2=0 CCR3=0 CCR4=0 DCR=0 DMAR=81 Timer 4 Disabled Timer 5 Disabled
Note that CCR1 is non zero but CCR2, CCR3, and CCR4 are zero.
The program PWMflash.js looks for pulses divides them and flashes the red LED.
I used this to test the PWM properties.var FLED=require("flashLED"); var x=new FLED(5); analogWrite(A0,0.1,{ freq : 10 }); analogWrite(A1,0.1,{ freq : 1 }); setWatch(x.divide, C3, { repeat: true, edge:'rising', debounce:1}); /* function FLED(a) { this.A=a; this.count=0; this.LEDstate=0; } exports = FLED; FLED.prototype.divide = function() { this.count++; if(this.count<this.A)return; this.count=0; this.LEDstate=this.LEDstate^1; digitalWrite(LED1,this.LEDstate); };//divide exports=FLED; */
While the duty cycles of pins A0, A1, A2, and A3 can be independently altered, they must all use the same frequency. The frequency is that of the last invoked analogWrite PWM command.
-
-
I want to make use of timer properties beyond that which has been implemented in Espruino. Such things as counting external pulses, shaft encoder input, measure period and frequency, cascading timers for longer counts. The first step is to understand how the timers work and how Espruino uses them.
First obtain the specification sheet from:
http://www.espruino.com/datasheets/STM32F103xC.pdf
Second obtain the RM0008 Reference manual
http://www.keil.com/dd/docs/datashts/st/stm32f10xxx.pdf
(The link on the Espruino site is broken.)From the reference manual memory map Table 3, the locations of the timers were obtained.
0x4000 0C00 - 0x4000 0FFF TIM5 timer
0x4000 0800 - 0x4000 0BFF TIM4 timer
0x4000 0400 - 0x4000 07FF TIM3 timer
0x4000 0000 - 0x4000 03FF TIM2 timer
And define the starting addresses:var TIM5=0x40000C00; var TIM4=0x40000800; var TIM3=0x40000400; var TIM2=0x40000000;
From the reference manual section 15.4 obtain the names and offsets of the timer registers:
//Timer register offsets var CR1=0x0; var CR2=0x04; var SMCR=0x08; var DIER=0x0c; var SR=0x10; var ERRCW=0x14; var CCMR1=0x18; var CCMR2=0x1c; var CCER=0x20; var CNT=0x24; var PSC=0x28; var ARR=0x2c; var CCR1=0x34; var CCR2=0x38; var CCR3=0x3c; var CCR4=0x40; var DCR=0x48; var DMAR=0x4c;
Set up an array containing the names and start addresses of the timers:
var timers=[{name:"Timer 2",t:TIM2}, {name:"Timer 3",t:TIM3}, {name:"Timer 4",t:TIM4}, {name:"Timer 5",t:TIM5} ];
From the reference manual section 15.4.1 that defines the bits in control resister 1 we learn that bit 0 enables or disables a timer. Write a function that tests the timers:
function isTimerEnabled(TIMx){ var CR1x=peek32(TIMx.t+CR1); if(CR1x&1)console.log(TIMx.name+" Enabled"); else console.log(TIMx.name+" Disabled"); return CR1x & 1; }//end isTimerEnabled
And write a function to display the registers of a timer
function showTimer(TIMx,base){ //display the timer registers in binary console.log("CR1="+peek32(TIMx+CR1).toString(base)); console.log("CR2="+peek32(TIMx+CR2).toString(base)); console.log("SMCR="+peek32(TIMx+SMCR).toString(base)); console.log("DIER="+peek32(TIMx+DIER).toString(base)); console.log("SR="+peek32(TIMx+SR).toString(base)); console.log("ERRCW="+peek32(TIMx+ERRCW).toString(base)); console.log("CCMR1="+peek32(TIMx+CCMR1).toString(base)); console.log("CCMR2="+peek32(TIMx+CCMR2).toString(base)); console.log("CCER="+peek32(TIMx+CCER).toString(base)); console.log("CNT="+peek32(TIMx+CNT).toString(base)); console.log("PSC="+peek32(TIMx+PSC).toString(base)); console.log("ARR="+peek32(TIMx+ARR).toString(base)); console.log("CCR1="+peek32(TIMx+CCR1).toString(base)); console.log("CCR2="+peek32(TIMx+CCR2).toString(base)); console.log("CCR3="+peek32(TIMx+CCR3).toString(base)); console.log("CCR4="+peek32(TIMx+CCR4).toString(base)); console.log("DCR="+peek32(TIMx+DCR).toString(base)); console.log("DMAR="+peek32(TIMx+DMAR).toString(base)); }//end showTimer
And finally loop through the timer list
for(var i=0; i in timers;i++){ if(isTimerEnabled(timers[i])) showTimer(timers[i].t,16); }//next i analogWrite(A0,0.1,{ freq : 10 }); for(var i=0; i in timers;i++){ if(isTimerEnabled(timers[i])) showTimer(timers[i].t,16); }//next i analogWrite(A0,0.5,{ freq : 10 }); // change duty cycle for(var i=0; i in timers;i++){ if(isTimerEnabled(timers[i])) showTimer(timers[i].t,16); }//next i
Try it with other analogWrite pins
-
Adaptive Control Continued
The file W-VectorComparisons.xlsx compares the W vectors from previous runs of the adaptive control algorithm. Note that curve for W10a is different from the curves of W10b and W10c. Recall that W10a was the R100k_C10uf sampled at 500ms intervals.
W10b was the R100k_C50uf sampled at 500ms intervals and W10c was the R100k_C10uf sampled at 100ms intervals.
The attached code Acontrol2.js makes use the W10c vector data to preload the control algorithm. The program is setup to run continuously until the variable “stop” is set to a non-zero value. The variable “setpoint” can be changed as well. The console displays the values. The variable sample_interval should be changed to match the RC circuit.@allObjects has mentioned in another thread that WebIDE has a graphing feature that can be turned on. If you are not familiar with it, access the WebIDE setups using the “gear” icon in the upper left and select testing. Try the tutorial.
For use with Acontrol2.js, the WebIDE testing settings are as follows:
Plots
,
out, A.outputY
SP, setpoint
inp, inpAt the bottom add
setpoint,setpoint,Number
Stop,stop,Number
In the properties
Interval 0.5Make setpoint changes as it is running and observe
Try it with the 100k_C50uF low pass filter.
It doesn’t work as well with the 100k_C10uF low pass filter with both the code and the WebIDE intervals changed to 100ms. It works at first but the control seems to stop after a bit. Something about the interface between the two stops working.
Remaining questions:
What is the optimum length of the adaptive vectors X, Y and W ?
In the initial program the process was stepped from 0 to 1 and the inverse transfer function was acquired. What happens is the step was from 0 to 0.5 instead? Would it be better to do a series of steps up and down?
What happens with a second order low pass filter? (Two resistors and two capacitors) -
More on adaptive control
The code has been reworked to show the results of an early step change in set point from 0.5 to 0.6 and to 0.5. This is repeated ten times and the results sent to the console.
The early and final values of the weight vector are also available for comparison
The code is attached Acontrol.js.
Acontrol1a.csv and Acontrol1a.xlsx show the results of R=100k and C=10uF. The sample interval is 500ms.
The response to the step change shows a slight overshoot of the set point with damped ringing. The weight vector shows some change with time.
Acontrol1b.csv and Acontrol1b.xlsx show the results of R=100k and C=50uF. The sample interval is 500ms.
The response to the step change shows a smooth transition to set point with no overshoot. The weight vector shows some change with time.
The 50uF/ 100k circuit has a time constant 5 times greater than the 10uF/100k circuit. So a second run was made with the 10uF/100k circuit but changing the sample rate from 500 to 100ms. These results follow.
Acontrol1c.csv and Acontrol1c.xlsx show the results of R=100k and C=10uF. The sample interval is 100ms.
The response to the step change shows a smooth transition to set point with no overshoot. The weight vector shows some change with time. -
Source for the algorithm
http://adsabs.harvard.edu/abs/1985ph...book.....W%E5%AF%86The process:
Testing an R-C low pass filter response R=100k, C= 10uF sampled at 500ms. intervals
On an Espruino board the resistor connects to pin A4. The resistor-capacitor junction connects to pin A1. The remaining capacitor lead connects to ground.
The RC circuit has a time constant of R*C = 1 second. The cut off frequency is 1/(2pi*R*C) = 1/6.28 =0.159 Hz or 6.28 seconds per cycle.
The software constructs an adaptive linear combiner. This consists of a N element shift register (X array) (sample 0, sample 1, … sample N), and a weight vector (W array) of equal length. The W array is initially all zeros.
A sample is shifted into the X array. The dot product of W and X is computed (output).
The error between a desired value and the output is calculated and the values in the W vector are adjusted. Wi= 2 * mu * error * Xi, where mu is the adaptive gain. If mu is too large the error increases. Below a critical value the error tends toward zero as successive iterations of the modeling process occur.
Adapt1.js Adapt2.csv and Adapt2.xlsx connects the adaptive linear combiner in parallel with the RC filter process. The RC filter is discharged to zero and then charged. The W array in the adaptive linear combiner acquires the transfer function of the RC filter process. Finally the acquired model is used to repeat the discharge/charge cycle.
The spreadsheet compares the RC process and the modeled process.
The program Acontrol.js and the output files Acontrol.csv and Acontrol.xlxs show the results of connecting the adaptive linear combiner to model the inverse transfer function of the RC filter process. Then the program uses the model to control the process while continuing to adapt the W vector. The set point goes from 1.0 to 0.5 and then to 0.6. -
@allObjects the testing part of WebIDE is a cool thing to use. I gave it a try with @Wilberforce's code and after fiddling with the testing buttons managed to get it to plot a graph of the process.
Seems in my haste to try @Wilberforce's module, I violated the rule of object access. Use methods to read/write object properties. Not a big thing if you are just fooling around trying to understand a piece of code, but a big deal if you build with it because a rewrite of the module can break your code. I'm wondering if there is a method to enforce that when using the keyword "export"? C++ classes have private and public keywords that can be applied to class properties.
My testing so far has been using an analog output connected by a 100k resistor to a 10 uF capacitor to ground, and the resistor-capacitor junction connected to an analog input. The other option would be to dig out some chemE textbooks and write some code to simulate a process. My head still hurts thinking about simulating the control of a 20 plate fractioning column with three different control loops on a 286 computer. So slow I had to buy a math coprocessor and even then it was slow. Such things are a lot faster today.
-
Trying out the code Wilberforce posted using the low pass filter as a test process.
CSV files are runs with different tuning parameters.var PID = require('pid-controller'); var setpoint = 0.5; var duty = 0; var N=0; var Kc= 1.5; var Ti= 2.0; var Td= 0.0; var setPoint=0.5; var cycleTime= 1000; var ctr = new PID(0, setPoint, Kc,Ti, Td, PID.fwd); ctr.mode(PID.auto); ctr.setLimits(-0.5, 0.5); ctr.out=0.0; analogWrite("A4",ctr.sp); console.log("N,error,input,output,setpoint"); var I=setInterval(function () { N++; if(N>20)ctr.setpoint(0.6); if(N>50)clearInterval(I); ctr.inp=analogRead("A1"); ctr.compute(); analogWrite("A4",ctr.out+0.5); console.log(""+N+","+(ctr.sp-ctr.inp).toFixed(4)+","+ ctr.inp.toFixed(4)+","+ (ctr.out+0.5).toFixed(4)+","+ctr.sp.toFixed(4)); }, 1000);
-
Thanks that helps a lot.
I've been working on the velocity version of PID and here are some results so far.Deriving the velocity PID equations from the position PID equations.
I used Maxima (download Maxima and open PID.wxm)
Download Maxima from:
http://maxima.sourceforge.net/
Starting at the top line, use the mouse to select the bracket on the left and press control-enter. This will make the line evaluate. Use the down arrow to go to the next line and repeat.
The file PIDobj3.js is the code to do the velocity PID.
Test were conducted using and R-C filter between the analog output and the analog input. R=100k, C=10uF
Input, output and setpoint are scaled to be in the range 0.0 to 1.0.
To make the velocity form work there has to be integral gain in order to get a setpoint change into the math.
The initial setpoint is 0.5 and is changed to 0.6 at some point to test the response.
The ZZZn.csv files contain several combinations of PID tunings.
Adding proportional gain improves the response but introduces “droop” error. It doesn’t hit the setpoint. Tradition is to add integral gain to remove the droop.
Puzzling over this I noticed that if the setpoint is zero the “droop” error disappears.
My first attempt was to modify the PID internals to operate between -0.5 an 0.5. This showed some improvement. Then it occurred to me to shift the PID internals so that the internal setpoint is always zero so that “droop” is eliminated.
Caution beyond this point there be dragons!
PIDobj6.js contains the code.
YYY3.csv contains the output for P=1.0, I=0.1 D=0.0 and
YYY4.csv for P=1.0, I=0.0, D=0.0. -
Thanks for the information.
I've reworked my code and been doing some reference work on the various flavors of PID control.
Seems there are two broad categories dependent and independent, which effect how the PID gains are defined. Then there are position and velocity versions of the basic equation.
So far the best link to explain this is:
http://literature.rockwellautomation.com/idc/groups/literature/documents/wp/logix-wp008_-en-p.pdf
For testing so far I've used two different RC low pass filters R=100k, C=10uF and the other one R=100k C=50uF.
The tuning method depends on the flavor of PID being used. Some head scratching and Google searching can figure or turn up conversions of the PID values between the flavors. You see lots of examples in various blogsI'm working on an independent velocity equation based PID algorithm. I'm going to check using the Arduino link that Wilberforce provided to determine the flavor of that PID.
Lots of ideas here.
In dealing with interrupts over the years on varying platforms, I've run into something I call the nagging interrupt. The interrupt occurs and if you don't clear it before doing the return from interrupt it will continue to occur.
When you set a watch on a pin, does a change on the pin create an interrupt? In the routine that queues the watch do you clear the interrupt bit for the pin?
Here is an outline subject to everyone's revisions;
SetWatch(User,function(),{IVectoraddress,IRegisteraddress,bitnum,bitval 0 or 1})
This would save the old address at the IVectoraddress in the interrupt table,
then insert the Watch action address into the table.
The watch action and queue code:
When the interrupt occurs the IResgisteraddress is used along with the bitnum and bitval to clear the interrupt. The event is queued along with the function to call.
When ClearWatch is called the old address would be restored to the interrupt table
As far as the external wire method between the OTpulse timer and the counting timer in gate mode, I think that works as far as the timers are concerned but I will have to see if the setWatch still works in that setup. The OTpulse pin is setup as an output. The gated timer gate would be an input. I'm a couple of steps from being able to try that in the timer thread.