Hey. Thank you for any advice. It would be greatly appreciated.
PS: I know PWM driver ICs are available...
For a bit of practice (and also to see if I can do it basically) I am trying to make a 328p act as a PWM driver with a UART interface (could do i2c as well / instead of maybe).
I want to use it possibly later to make a basic control board for a quadruped.
I know I only need 12 channels for a quad...but I thought "hell...why not do 18".
The PORTC and PORTB pins respond well to my coding...they can be set to PWM Duty cycles as stated in the setup()...
PORTD on the other-hand gives me a load of rubbish...and I can not think why!
Any help would be greatly appreciated. I could push on and just use PORTB and PORTC but having the extra PWM ports would be nice!
Here are the cycles I want: (0->100% in 20% increments on each channel...6 per portX):
PWMvalue[0] = dutyCycle(0); //PB0
PWMvalue[1] = dutyCycle(20); //PB1
PWMvalue[2] = dutyCycle(40); //PB2
PWMvalue[3] = dutyCycle(60); //PB3
PWMvalue[4] = dutyCycle(80); //PB4
PWMvalue[5] = dutyCycle(100); //PB5
PWMvalue[6] = dutyCycle(0); //PD2
PWMvalue[7] = dutyCycle(20); //PD3
PWMvalue[8] = dutyCycle(40); //PD4
PWMvalue[9] = dutyCycle(60); //PD5
PWMvalue[10] = dutyCycle(80); //PD6
PWMvalue[11] = dutyCycle(100); //PD7
PWMvalue[12] = dutyCycle(0); //PC0
PWMvalue[13] = dutyCycle(20); //PC1
PWMvalue[14] = dutyCycle(40); //PC2
PWMvalue[15] = dutyCycle(60); //PC3
PWMvalue[16] = dutyCycle(80); //PC4
PWMvalue[17] = dutyCycle(100); //PC5
Full code:
/*
An ~18 channel PWM software driver with Serial I/O
Use a timer to increment a count.
Every overflow (256 counts)...
With a /8 clk pre-scaler...16MHz / 8 / 256 = 7.8125KHz per OVF
Run the OVF_ISR:
Set PORTA and PORTB to the 2 values generated in the main().
PORTB - Gives 6 (PB6 and 7 used for crystal....)
PORTD - Gives 6 (PD0 and PD1 are Rx and Tx UART)
PORTC - Gives 6 (If not using I2C)
Options could include dropping Rx and Tx for I2C
Or dropping I2C for UART.
Total = 18 pins for PWM + 2 for communications.
Assuming Serial UART for command use:
PORTB = XTAL XTAL PB5 PB4 PB3 PB2 PB1 PB0 = 6
PORTD = PD7 PD6 PD5 PD4 PD3 PD2 Rx Tx = 6
PORTC = - RST PC5 PC4 PC3 PC2 PC1 PC0 = 6
An array of bytes can hold the wanted value for each PWM
channel 0-17. - PWMvalue[0-17]
Timer2 will be used to tick.
It will tick 0-255 and overflow.
The number of overwlows is recorded.
On each overflow, the pin-states are updated.
After 256 overflows...the PWM phase resets.
PWMout() will set the pins on an oveflow...
*/
volatile unsigned long overflows = 0;
volatile boolean overflowed = false;
volatile byte PWMvalue[18]; // An array in RAM to hold the PWM values.
void setup() {
Serial.begin(230400);
PWMvalue[0] = dutyCycle(0); //PB0
PWMvalue[1] = dutyCycle(20); //PB1
PWMvalue[2] = dutyCycle(40); //PB2
PWMvalue[3] = dutyCycle(60); //PB3
PWMvalue[4] = dutyCycle(80); //PB4
PWMvalue[5] = dutyCycle(100); //PB5
PWMvalue[6] = dutyCycle(0); //PD2
PWMvalue[7] = dutyCycle(20); //PD3
PWMvalue[8] = dutyCycle(40); //PD4
PWMvalue[9] = dutyCycle(60); //PD5
PWMvalue[10] = dutyCycle(80); //PD6
PWMvalue[11] = dutyCycle(100); //PD7
PWMvalue[12] = dutyCycle(0); //PC0
PWMvalue[13] = dutyCycle(20); //PC1
PWMvalue[14] = dutyCycle(40); //PC2
PWMvalue[15] = dutyCycle(60); //PC3
PWMvalue[16] = dutyCycle(80); //PC4
PWMvalue[17] = dutyCycle(100); //PC5
startTimer();
DDRB = 0xFF; // Set port B as outputs.
DDRD |= (1 << PD2) | (1 << PD3) | (1 << PD4) | (1 << PD5) | (1 << PD6) | (1 << PD7);
DDRC |= (1 << PC0) | (1 << PC1) | (1 << PC2) | (1 << PC3) | (1 << PC4) | (1 << PC5);
PORTB = 0; // Turn all portB pins off.
PORTD = 0;
}
void loop() {
//Check for update values...
if (Serial.available()) {
processSerial();
}
}
void startTimer() {
TCCR2A = 0; // No PORT outputs...
TCCR2B = 0; // Reset Timer Control Register...
TCCR2B |= (1 << CS20); // Start Timer...No precale. 22:20 (001 = no scaling, 010 = /8 , 011 = /32, 100 = /64);
TIMSK2 = 0;
TIMSK2 |= (1 << TOIE2); // Overflow enabled when I bit in SREG enabled.
TIFR2 = 0;
SREG |= 0b10000000; // Enable I-bit in SREG...interrupts begun.
TCNT2 = 0;
}
ISR(TIMER2_OVF_vect) {
overflows++;
overflowed = true;
PWMout(); // Set the pins
if (overflows > 255) {
overflows = 0;
}
}
byte PWMout() {
/* The timer goes from 0-256 and then overflows.
We can use say 256 overflows to determine what state
eahc pin needs to be in. This gives "8 bit resoltuion...".
For example, if we want PWM channel 0 (PORTB0) to be at 50%...
it needs to be on for 128 over flows...then off for the rest.
We can use an array to hold the "max" values for each channel (can be sent via serial!).
When the number of overflows > "max value in index" then that pin is set 0.
Assuming Serial UART for command use:
BITS- = 7 6 5 4 3 2 1 0
PORTB = XTAL XTAL PB5 PB4 PB3 PB2 PB1 PB0 = 6
PORTD = PD7 PD6 PD5 PD4 PD3 PD2 Rx Tx = 6
PORTC = - RST PC5 PC4 PC3 PC2 PC1 PC0 = 6
*/
unsigned int B = 0; // holder int for PORT B.
unsigned int D = 0; // holder int for PORT D.
unsigned int C = 0; // holder int for PORT C.
// SET PORTB 0-5 (6 and 7 are OSCILLATOR pins).
for (int x = 5; x >= 0; x--) { // Read from pwmvalues[5]->[0]
if (PWMvalue[x] < overflows) { //If value <= overflows...make it HIGH.
B += 1;
B = B << 1;
} else { //Otherwise make it low (shift along a zero).
B = B << 1;
}
}
unsigned int portB = B >> 1;
//set PORTD...the next 6 array values and bits...
for (byte x = 11; x > 5; x++) { // Read from 11 to 5.
if (PWMvalue[x] < overflows) {
D += 1;
D = D << 1;
} else {
D = D << 1;
}
}
unsigned int portD = D << 2; // Shift again...PORTD bit 0 and 1 are Tx and Rx...so leave as 0.
// SET PORTC 0-5 (6 and 7 are NC and RST pins).
for (byte x = 17; x > 11; x--) { // Read from pwmvalues[17]->[11]
if (PWMvalue[x] < overflows) { //If value <= overflows...make it HIGH.
C += 1;
C = C << 1;
} else { //Otherwise make it low (shift along a zero).
C = C << 1;
}
}
unsigned int portC = C >> 1;
PORTC = ~portC;
PORTD = ~portD;
PORTB = ~portB;
// Serial.print("Overflows:\t");
// Serial.print(overflows);
//
// Serial.print("\t B:");
// printBinary(B);
// Serial.print("\t D:");
// Serial.print(D,BIN);
// Serial.print("\t C:");
// printBinary(C);
// Serial.println("");
}
void processSerial() {
//'A' = channel 0...'B' = 1 etc.
//First byte is channel.
// Second is value.
byte channel = Serial.read() - 65;
delay(1);
byte val = Serial.read();
Serial.print(channel);
Serial.print(",");
Serial.println(val);
PWMvalue[channel] = val;
Serial.flush();
}
byte dutyCycle(byte percent) {
float range = 256.0;
return (range / 100.0) * percent;
}
void printBinary(byte val) {
for (byte mask = B10000000; mask; mask = mask >> 1) {
if (mask & val) {
Serial.print("1");
} else {
Serial.print("0");
}
}
}
Here is the output on my logic analyser: