|
|
View previous topic :: View next topic |
Author |
Message |
berler_d
Joined: 13 Apr 2004 Posts: 8
|
48 pwm channels |
Posted: Wed Jan 16, 2008 3:41 pm |
|
|
Hi All,
I need to develop a controller receiving serial uart, storing data on E2, and activating 48 pwm channels, 8bit resolution, 150Hz, 0-100%.
I'm wondering about the pic device I should use.
Is it possible to do it in one chip ?
What kind of options do I have ?
Thanks, |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
|
John P
Joined: 17 Sep 2003 Posts: 331
|
|
Posted: Thu Jan 17, 2008 12:02 pm |
|
|
If the pointy-haired boss came and asked me to do this, I'd tell him it would take several days and I wouldn't guarantee success. It's quite a challenge. But thinking of this using the PIC16F877A at 20MHz, I'd say there's a good chance it can be done. It would be easier on a faster chip.
Let's assume that the output is delivered 8 bits at a time to 6 8-bit flipflops like the 74HC374. There are going to be 6 independent strobe lines coming from another port to activate the external logic.
I'd insist on being the one to specify the serial protocol that's used to send data to the processor. No decoding text strings!
1/256 * 1/150 * 5000000 instructions/sec gives you about 130 instructions per (potential) interval between output transitions. I'm not sure if I'd want to have continuous interrupts at that rate, which seems to work out to 38400/sec. That might be the way to go, or it might be better to interrupt irregularly, at times when you know there'll be a change in at least one output. Then again, potentially there will be 48 transitions each one interval apart, so if the system has to be able to run at breakneck speed, it might be simpler just to do it all the time. Decide that later.
To get in and out of the interrupts faster, consider using the "global" interrupt mode which Mom warned us not to touch. Save only the registers we know we'll need. Or possibly not interrupt at all, but complete the work for each cycle and then wait in a "while" loop checking the interrupt flag, and move to the next cycle when it's seen (and make sure the flag is cleared by software!)
The way I'd try it first would be to dedicate the two upper memory banks to an "event list" with have up to 48 entries. Each list element would contain the identity of the output bytes which change during a given cycle (6 flags in a single byte) and the new data for those byes. It might also include a pointer to the next list element, or perhaps there'd be an additional array containing these. No, you can't let all the list elements be the same size (6 bytes each by 48 elements is too much memory). But there's a maximum amount of data to store, and it fits. Worst case is if each duty cycle is different. If any are the same, you save a list element.
The idea would be, at time 0, turn on all outputs which have nonzero duty cycles. Make up the event list and somehow (imagine hands waving here) provide for a way to determine which cycles of the timer are going to require events and which won't. When a cycle with an event arrives, grab the data from the right place in the event list, and for each byte indicated by a flag, "and" the data in it with the data in one of the 6 output bytes. Then re-write the outputs. Set up whatever is needed for the next cycle. It might be good to send out the new data not when you first assemble it, but at the start of the next cycle, so that all data goes out at a consistent point within the cycle. That's a TBD item.
Hopefully there'll be time left over to monitor the serial port (check for the RCIF flag rather than messing up the timing with an additional interrupt) and maybe put the incoming data into a small circular buffer, to be processed outside the interrupt. It's tempting to try to detect the longest time between events and turn off interrupts during that time in order to give the processor more leisure, but maybe that's too difficult.
I assume "storing data on E2" means in the EEPROM? That shouldn't be a problem, if there's not too much. Obviously you can't use a routine that makes the processor hang around waiting for the EEPROM write to finish. Stash the data and run away.
This is certainly an interesting problem. |
|
|
Guest
|
|
Posted: Thu Jan 17, 2008 2:32 pm |
|
|
Thanks for the answers;
in order to do the task I need 48pwm + 2 Tx/Rx + 3 Io that leeds to 53pins
It dosent fit the PIC16F877A.
Any Idea on that ..
What will fit in ?? |
|
|
n-squared
Joined: 03 Oct 2006 Posts: 99
|
|
Posted: Thu Jan 17, 2008 2:43 pm |
|
|
Hi,
For such a project I would use a PIC18F8722, which has enough ports and an internal RC oscillator+PLL running at 32MHz.
For the fun of it, I wrote an implementation of such a system, and left the communication functions to you.
The set_pwm_pattern() function stretches the duty cycle pattern over 100 lines.
The timer2 isr outputs ALL 48 pwm outputs in "parallel" 150 time a second.
The wait_for_comm_message() function should call the set_pwm_pattern() function with channel # (0..47) and duty cycle (0..100) following reception of command message.
Best regards,
Noam
Code:
#include <18F8722.h>
#device ICD=TRUE
#device adc=8
#device HIGH_INTERRUPTS=TRUE
#FUSES NOWDT //No Watch Dog Timer
#FUSES WDT128 //Watch Dog Timer uses 1:128 Postscale
#FUSES INTRC_IO //Internal RC Osc, no CLKOUT
#FUSES NOPROTECT //Code not protected from reading
#FUSES IESO //Internal External Switch Over mode enabled
#FUSES BROWNOUT //Reset when brownout detected
#FUSES BORV25 //Brownout reset at 2.5V
#FUSES NOPUT //No Power Up Timer
#FUSES NOCPD //No EE protection
#FUSES STVREN //Stack full/underflow will cause reset
#FUSES NODEBUG //No Debug mode for ICD
#FUSES NOLVP //NO Low Voltage Programming on B3(PIC16) or B5(PIC18)
#FUSES NOWRT //Program memory not write protected
#FUSES NOCPB //No Boot Block code protection
#FUSES NOEBTRB //Boot block not protected from table reads
#FUSES NOEBTR //Memory not protected from table reads
#FUSES NOWRTD //Data EEPROM not write protected
#FUSES NOWRTC //configuration not registers write protected
#FUSES NOWRTB //Boot block not write protected
#FUSES FCMEN //Fail-safe clock monitor enabled
#FUSES LPT1OSC //Timer1 configured for low-power operation
#FUSES MCLR //Master Clear pin enabled
#FUSES NOXINST //Extended set extension and Indexed Addressing mode disabled (Legacy mode)
#FUSES MCU //Microcontroller Mode
#FUSES WAIT //Wait selections for Table Reads and Table Writes
#FUSES BW16 //16-bit external bus mode
#FUSES ABW20 //20-bit Address bus
#FUSES ECCPE //Enhanced CCP PWM outpts multiplexed with RE6 thorugh RE3
#use delay(clock=32000000,RESTART_WDT)
#use rs232(baud=38400,parity=N,xmit=PIN_C6,rcv=PIN_C7,bits=8)
#use fast_io(a)
#use fast_io(d)
#use fast_io(e)
#use fast_io(f)
#use fast_io(h)
#use fast_io(j)
int8 pattern[100][6];
int8 pattern_index;
void set_pwm_pattern(int8 channel, int8 duty_cycle)
{
int8 byte_ix, mask, index;
mask = channel & 7;
byte_ix = channel >> 3;
for (index = 0; index < duty_cycle; index++)
pattern[index][byte_ix] |= mask;
mask ^= 0xFF;
while (index < 100)
pattern[index][byte_ix] &= mask;
}
#int_timer2 high
void timer2_isr(void) // 6.6mS interrupt for 150Hz
{
output_a(pattern[pattern_index][0]);
output_d(pattern[pattern_index][1]);
output_e(pattern[pattern_index][2]);
output_f(pattern[pattern_index][3]);
output_h(pattern[pattern_index][4]);
output_j(pattern[pattern_index][5]);
if (++pattern_index >= 100)
pattern_index = 0;
}
void init(void)
{
// usual setup functions here
setup_adc(ADC_OFF|ADC_TAD_MUL_0);
setup_psp(PSP_DISABLED);
setup_spi(SPI_SS_DISABLED);
setup_wdt(WDT_OFF);
setup_timer_0(RTCC_INTERNAL|RTCC_DIV_16);
setup_timer_1(T1_DISABLED);
setup_timer_2(T2_DIV_BY_16,208,16);// 6.6mS interrupt for 150Hz
setup_timer_3(T3_DISABLED|T3_DIV_BY_1);
setup_timer_4(T4_DISABLED,0,1);
setup_comparator(NC_NC_NC_NC);
setup_vref(FALSE);
// initialize six 8-bit ports as outputs
output_a(0);
output_d(0);
output_e(0);
output_f(0);
output_h(0);
output_j(0);
set_tris_a(0);
set_tris_d(0);
set_tris_e(0);
set_tris_f(0);
set_tris_h(0);
set_tris_j(0);
memset(pattern, 0, sizeof(pattern)); // turn ALL pwm outputs OFF
enable_interrupts(INT_TIMER2);
enable_interrupts(GLOBAL);
}
void main(void)
{
pattern_index = 0;
init();
while (1)
wait_for_comm_message(); // I left something for you to write...
} |
|
|
Guest
|
|
Posted: Thu Jan 17, 2008 3:56 pm |
|
|
Hi,
Nice response, great example code !
Is it possible to do it on a 16F946 chip ?
It has all needed, but I'm not confident about the speed ..
The 18F8722 cost 8.7$ and the 16F946 about 2.6$ for 100 units
Is there a chip under 3$ that will do the work ?? |
|
|
n-squared
Joined: 03 Oct 2006 Posts: 99
|
|
Posted: Thu Jan 17, 2008 4:02 pm |
|
|
Hi,
In general, I try to stay away from PIC16 devices these days. They are harder to debug (single breakpoint) and have a lot less features and speed.
Instead of the PIC18F8722, you can use a 3V device such as the PIC18F86J11, which costs half the price.
Another way to go is with a large PIC16 with an external 20MHz crystal. |
|
|
SherpaDoug
Joined: 07 Sep 2003 Posts: 1640 Location: Cape Cod Mass USA
|
|
Posted: Thu Jan 17, 2008 4:03 pm |
|
|
I would probably use 6 small PICs doing 8 PWMs each, or maybe 3 PICs doing 16 each. One would have the master oscillator and the Oscout of that would drive the Oscin of the other five, so they are perfectly synchronized. All PICs would run the same simple easy to write code. _________________ The search for better is endless. Instead simply find very good and get the job done. |
|
|
John P
Joined: 17 Sep 2003 Posts: 331
|
|
Posted: Thu Jan 17, 2008 4:31 pm |
|
|
I made it quite clear that a PIC16F877 would need external hardware to do this job. It has fewer than 48 pins! (duh) |
|
|
n-squared
Joined: 03 Oct 2006 Posts: 99
|
|
Posted: Thu Jan 17, 2008 11:37 pm |
|
|
SherpaDoug wrote: | I would probably use 6 small PICs doing 8 PWMs each, or maybe 3 PICs doing 16 each. One would have the master oscillator and the Oscout of that would drive the Oscin of the other five, so they are perfectly synchronized. All PICs would run the same simple easy to write code. |
Well, you could use 12 x PIC12F629 eight pin devices, if you really want to push it to the limit. Don't forget you need to send the serial data to ALL PICs so they know what to do with their outputs.
I am not against using multiple MCUs when it is required. In fact we manufacture a product that contains 18 PICs inside a box. They do a LOT more than wave a few signals around, though.
Oh, I just realized that in my code above the timer2 period is 100 times too slow. The setup should read:
Code: |
setup_timer_2(T2_DIV_BY_1,33,16);// 66uS interrupt for 150Hz * 1% steps
|
At 8 MIPS, this allows the PIC18 to execute about 500 instructions between timer interrupts. |
|
|
ELCouz
Joined: 18 Jul 2007 Posts: 427 Location: Montreal,Quebec
|
|
Posted: Mon Feb 04, 2008 8:21 pm |
|
|
Dear n-squared,
Your code look very good.
I want to do simple pwm with leds just for learning.
I'm trying to get it to work with the pic18f4550 (don't have any 8722 lying around) also tried to port to the dspic30 platform (30f6015, had many errors so back to the 4550)
Here's my code (trimmed to just have a PORT (D) for test)
Code: |
#include <18f4550.h>
#device HIGH_INTS=TRUE
#fuses HSPLL,NOWDT,NOPROTECT,NOLVP,NODEBUG,PLL3,CPUDIV1,NOMCLR
#use delay(clock=48000000)
#use fast_io(d)
int8 pattern[100][1];
int8 pattern_index;
void set_pwm_pattern(int8 channel, int8 duty_cycle)
{
int8 byte_ix, mask, index;
mask = channel & 7;
byte_ix = channel >> 3;
for (index = 0; index < duty_cycle; index++)
pattern[index][byte_ix] |= mask;
mask ^= 0xFF;
while (index < 100)
pattern[index][byte_ix] &= mask;
}
#int_timer2 high
void timer2_isr(void) // 6.6mS interrupt for 150Hz
{
output_d(pattern[pattern_index][0]);
if (++pattern_index >= 100)
pattern_index = 0;
}
void init(void)
{
// usual setup functions here
setup_adc(ADC_OFF);
setup_psp(PSP_DISABLED);
setup_spi(SPI_SS_DISABLED);
setup_wdt(WDT_OFF);
setup_timer_0(RTCC_INTERNAL|RTCC_DIV_16);
setup_timer_1(T1_DISABLED);
setup_timer_2(T2_DIV_BY_1,33,16);// 66uS interrupt for 150Hz * 1% steps
setup_timer_3(T3_DISABLED|T3_DIV_BY_1);
setup_comparator(NC_NC_NC_NC);
setup_vref(FALSE);
// initialize six 8-bit ports as outputs
output_d(0);
set_tris_d(0);
memset(pattern, 0, sizeof(pattern)); // turn ALL pwm outputs OFF
enable_interrupts(INT_TIMER2);
enable_interrupts(GLOBAL);
}
void main(void)
{
pattern_index = 0;
init();
while (1){
set_pwm_pattern(1,2);//(pin D0)
set_pwm_pattern(2,75); //(pin D1)
}
}
|
the first set_pwm_pattern work but the second don't :S
The whole code would be using for doing a LED Chaser :P
Code: |
for(i=0;i<100;++i){
set_pwm_pattern(1,i);
delay_ms(250);
}
|
This don't work neither ... i need it to create some led fade in / fade out effect !
Any clue?
oh, btw 12mhz ext. osc is used
thank you
Have a nice day!
Laurent |
|
|
Guest
|
|
Posted: Mon Feb 04, 2008 11:33 pm |
|
|
Hi Laurent,
Oh NO!!!!
I found a bug in my code.
Here is the fixed set_pwm_pattern():
Code: |
void set_pwm_pattern(int8 channel, int8 duty_cycle)
{
int8 byte_ix, mask, index;
mask = 1 <<channel>> 3;
for (index = 0; index < duty_cycle; index++)
pattern[index][byte_ix] |= mask;
mask ^= 0xFF;
while (index < 100)
pattern[index][byte_ix] &= mask;
}
|
For a single output port you should trim the function down to the following:
Code: |
void set_pwm_pattern(int8 channel, int8 duty_cycle)
{
int8 byte_ix, mask, index;
mask = 1 << (channel & 7);
for (index = 0; index < duty_cycle; index++)
pattern[index][0] |= mask;
mask ^= 0xFF;
while (index < 100)
pattern[index][0] &= mask;
}
|
For an even tighter code, you can drop the 2nd dimension of pattern[]altogether.
Good luck
Noam |
|
|
ELCouz
Joined: 18 Jul 2007 Posts: 427 Location: Montreal,Quebec
|
|
Posted: Mon Feb 04, 2008 11:40 pm |
|
|
Woaa thanks Noam for such care :P
Why i can only set the pwm one time ?
this code doesn't work :S
Code: |
while (1){
set_pwm_pattern(1,1);
delay_ms(250);
set_pwm_pattern(1,2);
delay_ms(250);
set_pwm_pattern(1,3);
delay_ms(250);
set_pwm_pattern(1,4);
delay_ms(250);
set_pwm_pattern(1,5);
delay_ms(250);
set_pwm_pattern(1,6);
delay_ms(250);
set_pwm_pattern(1,7);
delay_ms(250);
set_pwm_pattern(1,8);
delay_ms(250);
set_pwm_pattern(1,9);
delay_ms(250);
set_pwm_pattern(1,10);
delay_ms(250);
set_pwm_pattern(1,11);
delay_ms(250);
set_pwm_pattern(1,12);
delay_ms(250);
set_pwm_pattern(1,13);
delay_ms(250);
set_pwm_pattern(1,14);
delay_ms(250);
set_pwm_pattern(1,15);
delay_ms(250);
set_pwm_pattern(1,16);
delay_ms(250);
set_pwm_pattern(1,17);
delay_ms(250);
set_pwm_pattern(1,18);
delay_ms(250);
set_pwm_pattern(1,19);
delay_ms(250);
set_pwm_pattern(1,20);
delay_ms(250);
set_pwm_pattern(1,21);
delay_ms(250);
set_pwm_pattern(1,22);
delay_ms(250);
set_pwm_pattern(1,23);
delay_ms(250);
set_pwm_pattern(1,24);
delay_ms(250);
set_pwm_pattern(1,25);
delay_ms(250);
set_pwm_pattern(1,26);
delay_ms(250);
set_pwm_pattern(1,27);
delay_ms(250);
set_pwm_pattern(1,28);
delay_ms(250);
set_pwm_pattern(1,29);
delay_ms(250);
set_pwm_pattern(1,30);
delay_ms(250);
set_pwm_pattern(1,31);
delay_ms(250);
set_pwm_pattern(1,32);
delay_ms(250);
set_pwm_pattern(1,33);
delay_ms(250);
set_pwm_pattern(1,34);
delay_ms(250);
set_pwm_pattern(1,35);
delay_ms(250);
set_pwm_pattern(1,36);
delay_ms(250);
} |
Only the duty of 1% is set ... get never updated again...
same thing for this:
Code: |
for(i=0;i<100;++i){
set_pwm_pattern(1,i);
delay_ms(250);
} |
Best Regards,
Laurent |
|
|
Guest
|
|
Posted: Thu Feb 07, 2008 10:35 am |
|
|
Hi Laurent,
I found out the problem.
Here is the fixed function:
Code: |
void set_pwm_pattern(int8 channel, int8 duty_cycle)
{
int8 byte_ix, mask, index;
mask = 1 <<channel>> 3;
for (index = 0; index < duty_cycle; index++)
pattern[index][byte_ix] |= mask;
mask ^= 0xFF;
while (index < 100)
pattern[index++][byte_ix] &= mask;
}
|
I forgot to increment index in the while loop.
Sorry about that.
I tested the code and it now seems to work OK.
Regards,
Noam |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Thu Feb 07, 2008 11:32 am |
|
|
You forgot to disable HTML, so the code didn't post correctly.
If you log in as a "guest", you need to remember to do that
before you post. The tickbox for it is just below the post
editing window. It looks like this:
Quote: | x Disable HTML in this post |
If you log in as a registered member, you can edit your profile
to always disable HTML. When you log in, you can select a tickbox
to "always log me in automatically". It's a good idea to do this.
Then all your posts will have HTML disabled. |
|
|
|
|
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
|