forked from elcojacobs/ShiftPWM
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ShiftPWM.h
201 lines (172 loc) · 8.51 KB
/
ShiftPWM.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
/*
ShiftPWM.h - Library for Arduino to PWM many outputs using shift registers
Copyright (c) 2011-2012 Elco Jacobs, www.elcojacobs.com
All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef ShiftPWM_H
#define ShiftPWM_H
#include "pins_arduino_compile_time.h" // My own version of pins arduino, which does not define the arrays in program memory
#include <Arduino.h>
#include "CShiftPWM.h"
// These should be defined in the file where ShiftPWM.h is included.
extern const int ShiftPWM_latchPin;
extern const bool ShiftPWM_invertOutputs;
extern const bool ShiftPWM_balanceLoad;
// The ShiftPWM object is created in the header file, instead of defining it as extern here and creating it in the cpp file.
// If the ShiftPWM object is created in the cpp file, it is separately compiled with the library.
// The compiler cannot treat it as constant and cannot optimize well: it will generate many memory accesses in the interrupt function.
#if defined(SHIFTPWM_USE_TIMER2)
#if !defined(OCR2A)
#error "The avr you are using does not have a timer2"
#endif
#elif defined(SHIFTPWM_USE_TIMER3)
#if !defined(OCR3A)
#error "The avr you are using does not have a timer3"
#endif
#endif
#ifndef SHIFTPWM_NOSPI
// Use SPI
#if defined(SHIFTPWM_USE_TIMER3)
CShiftPWM ShiftPWM(3,false,ShiftPWM_latchPin,MOSI,SCK);
#elif defined(SHIFTPWM_USE_TIMER2)
CShiftPWM ShiftPWM(2,false,ShiftPWM_latchPin,MOSI,SCK);
#else
CShiftPWM ShiftPWM(1,false,ShiftPWM_latchPin,MOSI,SCK);
#endif
#else
// Don't use SPI
extern const int ShiftPWM_clockPin;
extern const int ShiftPWM_dataPin;
#if defined(SHIFTPWM_USE_TIMER3)
CShiftPWM ShiftPWM(3,true,ShiftPWM_latchPin,ShiftPWM_dataPin,ShiftPWM_clockPin);
#elif defined(SHIFTPWM_USE_TIMER2)
CShiftPWM ShiftPWM(2,true,ShiftPWM_latchPin,ShiftPWM_dataPin,ShiftPWM_clockPin);
#else
CShiftPWM ShiftPWM(1,true,ShiftPWM_latchPin,ShiftPWM_dataPin,ShiftPWM_clockPin);
#endif
#endif
// The macro below uses 3 instructions per pin to generate the byte to transfer with SPI
// Retreive duty cycle setting from memory (ldd, 2 clockcycles)
// Compare with the counter (cp, 1 clockcycle) --> result is stored in carry
// Use the rotate over carry right to shift the compare result into the byte. (1 clockcycle).
#define add_one_pin_to_byte(sendbyte, counter, ledPtr) \
{ \
unsigned char pwmval=*ledPtr; \
asm volatile ("cp %0, %1" : /* No outputs */ : "r" (counter), "r" (pwmval): ); \
asm volatile ("ror %0" : "+r" (sendbyte) : "r" (sendbyte) : ); \
}
// The inline function below uses normal output pins to send one bit to the SPI port.
// This function is used in the noSPI mode and is useful if you need the SPI port for something else.
// It is a lot 2.5x slower than the SPI version.
static inline void pwm_output_one_pin(volatile uint8_t * const clockPort, volatile uint8_t * const dataPort,\
const uint8_t clockBit, const uint8_t dataBit, \
unsigned char counter, unsigned char * ledPtr){
bitClear(*clockPort, clockBit);
if(ShiftPWM_invertOutputs){
bitWrite(*dataPort, dataBit, *(ledPtr)<=counter );
}
else{
bitWrite(*dataPort, dataBit, *(ledPtr)>counter );
}
bitSet(*clockPort, clockBit);
}
static inline void ShiftPWM_handleInterrupt(void){
sei(); //enable interrupt nesting to prevent disturbing other interrupt functions (servo's for example).
// Look up which bit of which output register corresponds to the pin.
// This should be constant, so the compiler can optimize this code away and use sbi and cbi instructions
// The compiler only knows this if this function is compiled in the same file as the pin setting.
// That is the reason the full funcion is in the header file, instead of only the prototype.
// If this function is defined in cpp files of the library, it is compiled seperately from the main file.
// The compiler does not recognize the pins/ports as constant and sbi and cbi instructions cannot be used.
volatile uint8_t * const latchPort = port_to_output_PGM_ct[digital_pin_to_port_PGM_ct[ShiftPWM_latchPin]];
const uint8_t latchBit = digital_pin_to_bit_PGM_ct[ShiftPWM_latchPin];
#ifdef SHIFTPWM_NOSPI
volatile uint8_t * const clockPort = port_to_output_PGM_ct[digital_pin_to_port_PGM_ct[ShiftPWM_clockPin]];
volatile uint8_t * const dataPort = port_to_output_PGM_ct[digital_pin_to_port_PGM_ct[ShiftPWM_dataPin]];
const uint8_t clockBit = digital_pin_to_bit_PGM_ct[ShiftPWM_clockPin];
const uint8_t dataBit = digital_pin_to_bit_PGM_ct[ShiftPWM_dataPin];
#endif
// Define a pointer that will be used to access the values for each output.
// Let it point one past the last value, because it is decreased before it is used.
unsigned char * ledPtr=&ShiftPWM.m_PWMValues[ShiftPWM.m_amountOfOutputs];
// Write shift register latch clock low
bitClear(*latchPort, latchBit);
unsigned char counter = ShiftPWM.m_counter;
#ifndef SHIFTPWM_NOSPI
//Use SPI to send out all bits
SPDR = 0; // write bogus bit to the SPI, because in the loop there is a receive before send.
for(unsigned char i = ShiftPWM.m_amountOfRegisters; i>0;--i){ // do a whole shift register at once. This unrolls the loop for extra speed
unsigned char sendbyte; // no need to initialize, all bits are replaced
if(ShiftPWM_balanceLoad){
counter +=8; // distribute the load by using a shifted counter per shift register
}
add_one_pin_to_byte(sendbyte, counter, --ledPtr);
add_one_pin_to_byte(sendbyte, counter, --ledPtr);
add_one_pin_to_byte(sendbyte, counter, --ledPtr);
add_one_pin_to_byte(sendbyte, counter, --ledPtr);
add_one_pin_to_byte(sendbyte, counter, --ledPtr);
add_one_pin_to_byte(sendbyte, counter, --ledPtr);
add_one_pin_to_byte(sendbyte, counter, --ledPtr);
add_one_pin_to_byte(sendbyte, counter, --ledPtr);
while (!(SPSR & _BV(SPIF))); // wait for last send to finish and retreive answer. Retreive must be done, otherwise the SPI will not work.
if(ShiftPWM_invertOutputs){
sendbyte = ~sendbyte; // Invert the byte if needed.
}
SPDR = sendbyte; // Send the byte to the SPI
}
while (!(SPSR & _BV(SPIF))); // wait for last send to complete.
#else
//Use port manipulation to send out all bits
for(unsigned char i = ShiftPWM.m_amountOfRegisters; i>0;--i){ // do one shift register at a time. This unrolls the loop for extra speed
if(ShiftPWM_balanceLoad){
counter +=8; // distribute the load by using a shifted counter per shift register
}
pwm_output_one_pin(clockPort, dataPort, clockBit, dataBit, counter, --ledPtr); // This takes 12 or 13 clockcycles
pwm_output_one_pin(clockPort, dataPort, clockBit, dataBit, counter, --ledPtr);
pwm_output_one_pin(clockPort, dataPort, clockBit, dataBit, counter, --ledPtr);
pwm_output_one_pin(clockPort, dataPort, clockBit, dataBit, counter, --ledPtr);
pwm_output_one_pin(clockPort, dataPort, clockBit, dataBit, counter, --ledPtr);
pwm_output_one_pin(clockPort, dataPort, clockBit, dataBit, counter, --ledPtr);
pwm_output_one_pin(clockPort, dataPort, clockBit, dataBit, counter, --ledPtr);
pwm_output_one_pin(clockPort, dataPort, clockBit, dataBit, counter, --ledPtr);
}
#endif
// Write shift register latch clock high
bitSet(*latchPort, latchBit);
if(ShiftPWM.m_counter<ShiftPWM.m_maxBrightness){
ShiftPWM.m_counter++; // Increase the counter
}
else{
ShiftPWM.m_counter=0; // Reset counter if it maximum brightness has been reached
}
}
// See table 11-1 for the interrupt vectors */
#if defined(SHIFTPWM_USE_TIMER3)
//Install the Interrupt Service Routine (ISR) for Timer3 compare and match A.
ISR(TIMER3_COMPA_vect) {
ShiftPWM_handleInterrupt();
}
#elif defined(SHIFTPWM_USE_TIMER2)
//Install the Interrupt Service Routine (ISR) for Timer1 compare and match A.
ISR(TIMER2_COMPA_vect) {
ShiftPWM_handleInterrupt();
}
#else
//Install the Interrupt Service Routine (ISR) for Timer1 compare and match A.
ISR(TIMER1_COMPA_vect) {
ShiftPWM_handleInterrupt();
}
#endif
// #endif for include once.
#endif