|
|
View previous topic :: View next topic |
Author |
Message |
younder
Joined: 24 Jan 2013 Posts: 53 Location: Brazil
|
DSPic30F4012 Cycle time measurement with 133Hz software PWM |
Posted: Sat Sep 26, 2015 12:04 pm |
|
|
Hi Everyone!
I've been testing the DSPic30F4012 on a trip computer for my 4WD for a while. I could not use HW PWM due to EXT_INT0, 1 & 2 (are being used by RPM, Speed and fuel flow meter). So I decided to study software PWM. Yes, I know it may not be a good idea for high frequencies like i.e. > 100Hz, however, I decided to test it and measure the cpu cycle time to compare the performance @ different frequencies.
Below is the test code, with software PWM by Ttelmah & PCM_Programmer.
The point is that, even with my full code, It did not show the CPU to be overloaded when I increase the frequency of timer 3 from 1Khz to 4Khz or even more. Is it because of the 30MIPS? Or at this rate it shouldn't affect anyway?
Longest cpu cycle time w/ Software PWM @ 66Hz (Timer 3 Period = 500uSec): 0,005277Sec
Longest cpu cycle time w/ Software PWM @ 133Hz (Timer 3 Period = 250uSec): 0,005294Sec
CCS Version: 5.049
Any comments would be appreciated!!
Hugo
_________________ Hugo Silva
Last edited by younder on Sat Sep 26, 2015 5:44 pm; edited 6 times in total |
|
|
younder
Joined: 24 Jan 2013 Posts: 53 Location: Brazil
|
Custom_Fct.h |
Posted: Sat Sep 26, 2015 12:05 pm |
|
|
Custom_Fct.h
Code: |
unsigned int i,Temp0;
// =============================================================
// "Timer1 ISR", -> called every 568,888mSec <-
// =============================================================
Unsigned int1 Cycle_Time_Overflow=0;
Unsigned int16 Cycle_Time=0,Cycle_Time_Mem[2]={0,0};
Unsigned int32 Cycle_Time_Total=0,Cycle_Time_Max=0;
#INT_TIMER1
void timer1()
{
Cycle_Time++;
Cycle_Time_Overflow=1;
}
// *************************************************************
// =============================================================
// "Timer2 ISR", -> called every 25ms <-
// 117,964,800 as the oscillator internal (Tosc) rate.
// Divide by 4 to get the timer increment rate (Tclk), so 29,491,200 Hz.
// Divide by 64 since the divide by 64 pre-scaler was set, so 460,800 counts / sec on the timer.
// The timer is set to 54,015 (65,536-11,520), so it will overflow after 11,520 counts.
// (1/460,800)*11,520=0,025s or 25ms.
// set_timer2(54,015 + get_timer2()) Timer2 will overflow every 25ms...
// =============================================================
union pulse_data_type {
int8 buffer;
int1 clock[8];
struct {
int1 _25;
int1 _50;
int1 _100;
int1 _200;
int1 _500;
int1 _1000;
} mSec;
};
union pulse_data_type pulse=0;
#INT_TIMER2
void timer2()
{
static int8 counter_SP[6] = {1,2,4,8,20,40}; //Counters Setpoint (Value*25ms)
static int8 i, counter[6] = {1,2,3,4,5,6}, mask;
// set_timer2(54015 + get_timer2()); //11520counts = 25mSec
mask=1;
for (i=0;i<6;i++)
{
counter[i]--;
if (counter[i]==0)
{
counter[i]=counter_SP[i];
pulse.buffer^=mask;
}
mask*=2;
}
}
// *************************************************************
// =============================================================
// Rising/Falling edge pulses
// =============================================================
union edge_data_type {
int1 buffer[8];
struct {
int1 _25;
int1 _50;
int1 _100;
int1 _200;
int1 _500;
int1 _1000;
} mSec;
};
union edge_data_type R_Edge={0,0,0,0,0,0,0,0};
typedef struct Edge_scan {
int1 scan[8];
};
union edge_scan_type {
struct Edge_scan Buffer[6];
struct {
int1 _25_scan[8];
int1 _50_scan[8];
int1 _100_scan[8];
int1 _200_scan[8];
int1 _500_scan[8];
int1 _1000_scan[8];
} mSec;
};
union edge_scan_type Edge={0,0,0,0,0,0};
void Edge_Pulses()
{
static int1 aux_R[6]; //Auxiliar Variable
static int8 Scan_Count[6]={0,0,0,0,0,0};
for (i=0;i<6;i++)
{
//Rising edge
if (pulse.clock[i]) { //If pulse is true
if (aux_R[i]==0) R_Edge.buffer[i]=1; else R_Edge.buffer[i]=0; //and aux is false then edge=TRUE for only one scan
aux_R[i]=1; //Next scan edge pulse will be false
} else aux_R[i]=0,R_Edge.buffer[i]=0; //Clear aux till next rising edge detection
//Edge array bits
if (Edge.Buffer[i].scan[Scan_Count[i]]) {
if (Scan_Count[i]<7) Edge.Buffer[i].scan[Scan_Count[i]+1]=1;
Edge.Buffer[i].scan[Scan_Count[i]]=0;
Scan_Count[i]++;
if (Scan_Count[i]>7) Scan_Count[i]=0;
}
if (R_Edge.buffer[i] && (Scan_Count[i]==0)) Edge.Buffer[i].scan[0]=1;
}
}
// *************************************************************
// =============================================================
// A/D Function
// =============================================================
Long Read_AD(int Channel)
{
Static Long Analog_Value;
Switch(Channel)
{
case 0 : input(BAT_VOLTAGE), setup_adc_ports(sAN0, VSS_VDD); break;
case 1 : input(ALTERNATOR_CURRENT), setup_adc_ports(sAN0 | sAN1, VSS_VDD); break;
case 2 : input(FUEL_TANK_LEVEL), setup_adc_ports(sAN0 | sAN1 | sAN2, VSS_VDD); break;
case 3 : input(COOLANT_TEMP), setup_adc_ports(sAN0 | sAN1 | sAN2 | sAN3, VSS_VDD); break;
case 4 : input(AIR_TEMP), setup_adc_ports(sAN0 | sAN1 | sAN2 | sAN3 | sAN4, VSS_VDD); break;
default: return(0);
}
set_adc_channel(channel);
Analog_Value = read_adc();
setup_adc_ports(NO_ANALOGS);
return(Analog_Value);
}
// *************************************************************
|
_________________ Hugo Silva |
|
|
younder
Joined: 24 Jan 2013 Posts: 53 Location: Brazil
|
Test.c |
Posted: Sat Sep 26, 2015 12:05 pm |
|
|
Test.c
Code: |
#include <30F4012.h>
#device ADC=10
#fuses FRC_PLL16,NOPROTECT,NOWRT,MCLR,BORRES,PUT64,NOCKSFSM,BORV27,NODEBUG,NOWDT
#use delay(Internal=117964800) // Internal 7.3728Mhz * FRC_PLL16 = 117964800Hz (30MIPS)
#use i2c(MASTER, SCL=PIN_F3, SDA=PIN_F2, FAST, stream=I2C_SOFTWARE)
#build (stack=256) // 128,256,384 or 512 depending how big the code is
//PORT B
#define BAT_VOLTAGE PIN_B0 //AD0 - 12V battery level
#define ALTERNATOR_CURRENT PIN_B1 //AD1 - AlternatorĀ“s current
#define FUEL_TANK_LEVEL PIN_B2 //AD2 - Fuel tank level
#define COOLANT_TEMP PIN_B3 //AD3 - Coolant Temperature
#define AIR_TEMP PIN_B4 //AD4 - Internal Air Temperature
#define KEY_BIT_1 PIN_B5 //IN - Key Bit 01
//PORT C
#define KEY_BIT_2 PIN_C13 //IN - Key Bit 02
#define KEY_BIT_3 PIN_C14 //IN - Key Bit 03
#define LED_OUT PIN_C15 //OUT - LED
//PORT D
#define GAS_FLOW_IN PIN_D0 //INT1 - Gasoline Flow Pulse
#define SPEED_IN PIN_D1 //INT2 - Speed Sensor pulse
//PORT E
#define AUX_01_OUT PIN_E0 //OUT - Auxiliar Output 1 (PWM 1)
#define AUX_02_OUT PIN_E1 //OUT - Auxiliar Output 2 (PWM 2)
#define RELAY_01_OUT PIN_E2 //OUT - Relay #01
#define RELAY_02_OUT PIN_E3 //OUT - Relay #02
#define AUX_01_IN PIN_E4 //IN - Auxiliar Input 1
#define AUX_02_IN PIN_E5 //IN - Auxiliar Input 2
#define RPM_IN PIN_E8 //INT0 - Tachometer Pulse
#use FIXED_IO( E_outputs=PIN_E0,PIN_E1,PIN_E2,PIN_E3 )
#include <lib\i2c_Flex_LCD.h>
#include <lib\Custom_Fct.h>
#define PWM_PIN PIN_E2
#define PWM_PIN2 PIN_E3
#define LOOPCNT 29
// =============================================================
// Global variables declaration
// =============================================================
Unsigned int16 ADC_0=0,ADC_1=0,ADC_2=0,ADC_3=0,ADC_4=0,Key;
Unsigned int8 width, width2;
int1 first_scan_bit=1,Edge_new_screen=1,UP_Key, DOWN_Key, ENTER_Key;
#int_timer3
void timer3_isr(void)
{
static int8 loop = LOOPCNT;
static int8 pulse,pulse2;
if(--loop == 0) {
loop = LOOPCNT;
pulse = width;
pulse2 = width2;
}
if(pulse) output_high(PWM_PIN), pulse--; else output_low(PWM_PIN);
if(pulse2) output_high(PWM_PIN2), pulse2--; else output_low(PWM_PIN2);
}
void Initialization() {
//Disable all interrupts
disable_interrupts(INTR_GLOBAL);
// Initialize 20x4 LCD Display
lcd_init();
//Configures Timer1
setup_timer1 (TMR_INTERNAL | TMR_DIV_BY_256, 65535); //Configures timer1 to overflow every 568,888mSec
enable_interrupts(INT_TIMER1);
//Configures Timer2
setup_timer2 (TMR_INTERNAL | TMR_DIV_BY_64, 11520); //Configures timer2 to overflow every 25mSec
enable_interrupts(INT_TIMER2);
//Configures Timer3
setup_timer3 (TMR_INTERNAL | TMR_DIV_BY_1, 7500); //7500counts = 250uSec
enable_interrupts(INT_TIMER3);
//ADC clock
setup_adc(ADC_CLOCK_DIV_64 | ADC_TAD_MUL_8);
//Set GIE&PEIE bits and Enable all interrupts
enable_interrupts(INTR_GLOBAL);
}
void LCD_Display_Diag()
{
if (Edge.mSec._50_scan[0]) {
lcd_gotoxy(1, 1);
printf(LCD_PUTC,"Actual Cycle:");
lcd_gotoxy(1, 2);
printf(LCD_PUTC,"%1.6lws",Cycle_Time_Total/100);
}
if (Edge.mSec._100_scan[1]) {
lcd_gotoxy(1, 3);
printf(LCD_PUTC,"Longest Cycle:");
lcd_gotoxy(1, 4);
printf(LCD_PUTC,"%1.6lws",Cycle_Time_Max/100);
}
}
long KeyScan() {
static long inKey=0, key_mem=0;
#bit inKey_bit0 = inKey.0
#bit inKey_bit1 = inKey.1
#bit inKey_bit2 = inKey.2
If (input(KEY_BIT_1)) {
if (!UP_Key) delay_ms(20); //debouncing time
UP_Key=1;
inKey_bit0=1;
} else UP_Key=0,inKey_bit0=0;
If (input(KEY_BIT_2)) {
if (!DOWN_Key) delay_ms(20); //debouncing time
DOWN_Key=1;
inKey_bit1=1;
} else DOWN_Key=0,inKey_bit1=0;
If (input(KEY_BIT_3)) {
if (!ENTER_Key) delay_ms(20); //debouncing time
ENTER_Key=1;
inKey_bit2=1;
} else ENTER_Key=0,inKey_bit2=0;
if(inKey != key_mem){
key_mem=inKey;
return inKey;
} else return 0;
}
void PWM() {
static int8 cnt=0, cnt2=0;
static int1 dup=1, ddown=1;
//PWM 1
If (Edge.mSec._25_scan[1]) {
if (dup) cnt++; else cnt--;
if (cnt>29) dup=0;
if (cnt==0) dup=1;
width = cnt;
}
//PWM 2
If (Edge.mSec._50_scan[2]) {
if (ddown) cnt2++; else cnt2--;
if (cnt2>29) ddown=0;
if (cnt2==0) ddown=1;
width2 = cnt2;
}
}
void AD() {
static int8 Read_Analog=0;
Read_Analog++;
if (Read_Analog>4)Read_Analog=0;
Switch(Read_Analog)
{
case 0 : {
ADC_0=Read_AD(0);
} break;
case 1 : {
ADC_1=Read_AD(1);
} break;
case 2 : {
ADC_2=Read_AD(2);
} break;
case 3 : {
ADC_3=Read_AD(3);
} break;
case 4 : {
ADC_4=Read_AD(4);
} break;
}
}
void Cycle_time_measuring()
{
static int1 Flag_ResetCycleTime=0;
static int8 Reset_Cycle_Time=0;
static unsigned int16 Cycle_Time_Buffer=0;
Cycle_Time_Buffer=Cycle_Time;
Cycle_Time_Mem[0]=get_timer1(); //get actual tmr1 value
if ((Cycle_Time_Mem[0]==65535) && Cycle_Time_Overflow) Cycle_Time_Mem[0]=0,Cycle_Time_Overflow=0;
if (Cycle_Time_Mem[0]>Cycle_Time_Mem[1])
{
Temp0=Cycle_Time_Mem[0]-Cycle_Time_Mem[1]; //how many tmr1 counts since last measuring?
Cycle_Time_Total=((((int32)Cycle_Time_Buffer*65535)+Temp0)*868);
}
else
{
if (Cycle_Time_Buffer<=0) Temp0=1; else Temp0=Cycle_Time_Buffer;
Cycle_Time_Total=((((int32)Temp0*65535)-Cycle_Time_Mem[1]+Cycle_Time_Mem[0])*868);
}
Cycle_Time_Mem[1]=Cycle_Time_Mem[0]; //memorizes current tmr1 value in case a new interrupt happens before tmr1 overflows
Cycle_Time=0; //Reset Actual Cycle time counter
if (Cycle_Time_Total>Cycle_Time_Max) Cycle_Time_Max=Cycle_Time_Total;
//Reset Longest Cycle Time
If (ENTER_Key) Flag_ResetCycleTime=1;
If (!ENTER_Key && Flag_ResetCycleTime && R_Edge.mSec._50) Reset_Cycle_Time++;
if (Reset_Cycle_Time>=10) {
Flag_ResetCycleTime=0;
Reset_Cycle_Time=0;
Cycle_Time_Max=0;
}
}
void Main() {
Initialization();
while(1){
Edge_Pulses(); //Edge pulses pulses for general use
Key=keyScan(); //Up, Down and Enter Keys
Cycle_time_measuring(); //At this point, measure actual and longest cycle time
AD(); //Just read AD ports
PWM(); //Perform a Software 4Khz PWM (Timer 3 @ 250uSec)
LCD_Display_Diag(); //Display measured values
}
}
//------------------ EOF ---------------------
|
_________________ Hugo Silva |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19545
|
|
Posted: Sun Sep 27, 2015 1:42 am |
|
|
Nobody is going to plod though that length of code.
Obviously 30MIPS does make a huge difference. Most of the people here asking about software PWM, are running at perhaps only 1 or 2 MIPS.
However you are also missing a fundamental 'point'. It is not the frequency of the PWM that matters, but the product of frequency and resolution. If a poster is talking about a PWM to control a device needing perhaps 100 different levels, and needs 100Hz, then the timer tick has to be occurring at 10KHz....
A 1000Hz PWM, with only four levels, conversely only needs 4KHz.
Most of the posters asking about software PWM here are doing things that require fine control. :(
Many of the posters want PWM at KHz rates, and high resolution.
Then you have the second question/problem. If an error in the timing of the PWM 'matters' at all significantly, then this has to be given priority over the other interrupts. Otherwise if (for instance) serial data, arrives just at the same time, the actual pulse edge on the PWM will be delayed by the total latency for the serial handler. Now on the smaller PIC's the total serial buffer is less than two characters, so the serial - if fast, will have to be given priority - result errors in the PWM timing when serial data arrives. On the PIC 30, you have much more buffering, so you could instead give the PWM priority....
I'm puzzled though about your comment about not being able to use hardware PWM. You need to be aware that you can specify an input change interrupt on CN0 to CN7, so there are actually eight more pins that can be used for input interrupts. |
|
|
younder
Joined: 24 Jan 2013 Posts: 53 Location: Brazil
|
|
Posted: Sun Sep 27, 2015 8:41 am |
|
|
Thanks Ttelmah for your comments about the product of frequency and resolution.
In my case it is going to be used to control a fuel and temperature gauges, so it may works considering that the gauge has a slow response...I'm not sure about the resolution, as I do not have the spec., just need to test it later to see if like 10 or 20 levels @ 4Khz are enough.
Regarding the CNx Interrupts, yes I went thru this already, but I realized that it is not so flexible, since I have only one ISR handler and the interrupts always happens in the falling and rising edges. I thought it would be not nice to have RPM, Fuel flow & speed pulses being processed in the same ISR, besides it would increase the interrupt ticks twice at least.
I was aware about the code length, however in the main loop you can see that there are just a couple of basic function calls...so that the cpu cycle time analysis & sw pwm could be tested considering a few lines of code. BTW, I hope this is a right way to measure cpu time.
Thanks
Hugo _________________ Hugo Silva |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19545
|
|
Posted: Sun Sep 27, 2015 1:42 pm |
|
|
You need to be aware that there are dozens of hidden 'ticks' involved _outside_ your interrupt routine, to handle each interrupt.
There is _less_ process work handling multiple things in one handler, than having multiple handlers. For every interrupt handler you have, dozens of registers are saved and restored when it is called....
It is always tempting to think interrupts just jump you to the routine, but there is a lot of overhead instructions and time involved in this.
On IOC, have a look at this thread:
<https://www.ccsinfo.com/forum/viewtopic.php?t=52574>
and the example.
Several of the later chips have the ability to specify which edge is used as well. I don't think yours offers this though. |
|
|
guy
Joined: 21 Oct 2005 Posts: 297
|
|
Posted: Mon Sep 28, 2015 1:42 am |
|
|
You could also use a more straightforward PWM implementation (but it requires a timer for each PWM channel) -
1. output_high(PWM)
2. set the timer for positive pulse time
3. wait for timer (polling or interrupt)
4. output_low(PWM)
5. set the timer for negative pulse time
6. wait for timer (polling or interrupt)
This (which could be done with or without an interrupt) saves dozens of interrupt calls (=overhead). Anyway 4KHz on 30 MIPS is not very challenging, esp. if you don't need extremely high accuracy and resolution in the PWM. You could actually do it in the main loop as I explained and avoid interrupt issues altogether.
As for measuring interrupt "costs", I either use the simulator with stopwatch, or create a simple
void main() {
output_toggle(SQWAVE);
}
this shows on the oscilloscope how much time is spent 'not running the main code' which is equal to how much time is spent on interrupts. |
|
|
younder
Joined: 24 Jan 2013 Posts: 53 Location: Brazil
|
|
Posted: Mon Sep 28, 2015 5:07 pm |
|
|
The 'output_toggle(SQWAVE);' in the main loop is a good idea, however I've been using this cpu measure method as an embedded function in my "diag. screen".
I normally enable it when I want to see the interrupt "costs" at different times and conditions in the field, this give me an idea about code behavior at all w/o a scope.
I know it is related to the particular application, but I'm wondering what would be a good main loop maximum cycle time? In my case (the test code above = 5,294mSec) or even the full Trip Computer code (which is much bigger and with more interrupts = 8,011mSec) running in a chip @ 30MIPS... I'm just curious to know if there is a good pratice for this, to be considered when developing a project.
BW,
Hugo _________________ Hugo Silva |
|
|
guy
Joined: 21 Oct 2005 Posts: 297
|
|
Posted: Mon Sep 28, 2015 11:56 pm |
|
|
In my humble opinion it depends on the specific application - critical time between tasks in the main loop, buffer sizes etc.
I didn't go into the code so I can't comment on it but I rarely have main loops which take more than a few cycles. Tasks are executed 'on-demand': If an interrupt raises a flag the main loop will continue handling the data; display and UI functions are handled every 100ms (or upon change to the display). etc.
hope this helps. |
|
|
younder
Joined: 24 Jan 2013 Posts: 53 Location: Brazil
|
|
Posted: Tue Sep 29, 2015 11:27 am |
|
|
Guy, thanks for your comments, I'm not doing projects for living, but I definitely love to spend hours (my wife doesn't!) trying to improve my poor knowledge, I hope one day I'll be able to finish my trip computer!
For non critical timing application, I usually use in the main loop my custom ticks. For each custom clock, like i.e. 100mSec, I have an 8 bit edge moment array associated to it. I then try to do not "overload that particular scan" by executing different tasks in different scans.
Code: |
If (Edge.mSec._100_scan[0]) Speed_Calc(); //Call the function every 100ms (scan 0)
.
.
.
If (Edge.mSec._100_scan[7]) PWM(); //Call the function every 100ms (scan 7)
|
Same as above for other clocks (25mSec,200mSec,500mSec etc.)
I Think I can combine also the 'on-demand' requests in some cases, which will also improve the main loop.
Code: |
union pulse_data_type {
int8 buffer;
int1 clock[8];
struct {
int1 _25;
int1 _50;
int1 _100;
int1 _200;
int1 _500;
int1 _1000;
} mSec;
};
union pulse_data_type pulse=0;
#INT_TIMER2
void timer2()
{
static int8 counter_SP[6] = {1,2,4,8,20,40}; //Counters Setpoint (Value*25ms)
static int8 i, counter[6] = {1,2,3,4,5,6}, mask;
// set_timer2(54015 + get_timer2()); //11520counts = 25mSec
mask=1;
for (i=0;i<6;i++)
{
counter[i]--;
if (counter[i]==0)
{
counter[i]=counter_SP[i];
pulse.buffer^=mask;
}
mask*=2;
}
}
// *************************************************************
// =============================================================
// Rising/Falling edge pulses
// =============================================================
union edge_data_type {
int1 buffer[8];
struct {
int1 _25;
int1 _50;
int1 _100;
int1 _200;
int1 _500;
int1 _1000;
} mSec;
};
union edge_data_type R_Edge={0,0,0,0,0,0,0,0};
typedef struct Edge_scan {
int1 scan[8];
};
union edge_scan_type {
struct Edge_scan Buffer[6];
struct {
int1 _25_scan[8];
int1 _50_scan[8];
int1 _100_scan[8];
int1 _200_scan[8];
int1 _500_scan[8];
int1 _1000_scan[8];
} mSec;
};
union edge_scan_type Edge={0,0,0,0,0,0};
void Edge_Pulses()
{
static int1 aux_R[6]; //Auxiliar Variable
static int8 Scan_Count[6]={0,0,0,0,0,0};
for (i=0;i<6;i++)
{
//Rising edge
if (pulse.clock[i]) { //If pulse is true
if (aux_R[i]==0) R_Edge.buffer[i]=1; else R_Edge.buffer[i]=0; //and aux is false then edge=TRUE for only one scan
aux_R[i]=1; //Next scan edge pulse will be false
} else aux_R[i]=0,R_Edge.buffer[i]=0; //Clear aux till next rising edge detection
//Edge array bits
if (Edge.Buffer[i].scan[Scan_Count[i]]) {
if (Scan_Count[i]<7) Edge.Buffer[i].scan[Scan_Count[i]+1]=1;
Edge.Buffer[i].scan[Scan_Count[i]]=0;
Scan_Count[i]++;
if (Scan_Count[i]>7) Scan_Count[i]=0;
}
if (R_Edge.buffer[i] && (Scan_Count[i]==0)) Edge.Buffer[i].scan[0]=1;
}
}
|
_________________ Hugo Silva |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|