2015-06-23 12:42:35 -07:00

300 lines
10 KiB
C++

// a Tiny optimized Software PWM Manager (all pins must be part of the same port)
// Only resources RAM/Program Memory of used pins are declared in the code at compilation time.
// based largely on Atmel's AVR136: Low-Jitter Multi-Channel Software PWM Application Note:
// http://www.atmel.com/dyn/resources/prod_documents/doc8020.pdf
// RC Navy 2013-2015
// http://p.loussouarn.free.fr
// 11/01/2015: Multi port support added for ATtiny167
#include <TinySoftPwm.h>
#if (TINY_SOFT_PWM_CH_MAX == 0)
#error At least one PWM pin shall be declared in TinySoftPwm.h
#endif
#if defined(TINY_SOFT_PWM_DDR1) && defined(TINY_SOFT_PWM_DDR0)
#define TINY_SOFT_PWM_DECLARE_PIN(Pin) do{ \
if(digitalPinToPortIdx(Pin)) \
{ \
TINY_SOFT_PWM_DDR1 |= (1 << digitalPinToPortBit(Pin)); \
Port1_PwmMask |= (1 << digitalPinToPortBit(Pin)); \
} \
else \
{ \
TINY_SOFT_PWM_DDR0 |= (1 << digitalPinToPortBit(Pin)); \
Port0_PwmMask |= (1 << digitalPinToPortBit(Pin)); \
} \
}while(0)
#define TINY_SOFT_PWM_CLEAR_PIN(RamIdx) GET_PWM_PIN_PORT(RamIdx) \
? (Port1_PwmTo1 &= GET_PWM_INV_MSK(RamIdx)) \
: (Port0_PwmTo1 &= GET_PWM_INV_MSK(RamIdx))
#else
#if defined(TINY_SOFT_PWM_DDR1)
#define TINY_SOFT_PWM_DECLARE_PIN(Pin) TINY_SOFT_PWM_DDR1 |= (1 << digitalPinToPortBit(Pin)); Port1_PwmMask |= (1 << digitalPinToPortBit(Pin))
#define TINY_SOFT_PWM_CLEAR_PIN(RamIdx) Port1_PwmTo1 &= GET_PWM_INV_MSK(RamIdx)
#else
#define TINY_SOFT_PWM_DECLARE_PIN(Pin) TINY_SOFT_PWM_DDR0 |= (1 << digitalPinToPortBit(Pin)); Port0_PwmMask |= (1 << digitalPinToPortBit(Pin))
#define TINY_SOFT_PWM_CLEAR_PIN(RamIdx) Port0_PwmTo1 &= GET_PWM_INV_MSK(RamIdx)
#endif
#endif
#define GET_PWM_PIN_ID(RamIdx) ((uint8_t)pgm_read_byte(&PwmPin[(RamIdx)].Id))
#define GET_PWM_PIN_PORT(RamIdx) ((uint8_t)pgm_read_byte(&PwmPin[(RamIdx)].Port))
#define GET_PWM_INV_MSK(RamIdx) ((uint8_t)pgm_read_byte(&PwmPin[(RamIdx)].InvMsk))
typedef struct {
uint8_t Id;
uint8_t Port;
uint8_t InvMsk;
}SoftPwmPinSt_t;
const SoftPwmPinSt_t PwmPin[] PROGMEM ={
#if (TINY_SOFT_PWM_USES_PIN0 == 1)
{0, digitalPinToPortIdx(0), ~(1 << digitalPinToPortBit(0))},
#endif
#if (TINY_SOFT_PWM_USES_PIN1 == 1)
{1, digitalPinToPortIdx(1), ~(1 << digitalPinToPortBit(1))},
#endif
#if (TINY_SOFT_PWM_USES_PIN2 == 1)
{2, digitalPinToPortIdx(2), ~(1 << digitalPinToPortBit(2))},
#endif
#if (TINY_SOFT_PWM_USES_PIN3 == 1)
{3, digitalPinToPortIdx(3), ~(1 << digitalPinToPortBit(3))},
#endif
#if (TINY_SOFT_PWM_USES_PIN4 == 1)
{4, digitalPinToPortIdx(4), ~(1 << digitalPinToPortBit(4))},
#endif
#if (TINY_SOFT_PWM_USES_PIN5 == 1)
{5, digitalPinToPortIdx(5), ~(1 << digitalPinToPortBit(5))},
#endif
#if defined (__AVR_ATtiny167__)
#if (TINY_SOFT_PWM_USES_PIN6 == 1)
{6, digitalPinToPortIdx(6), ~(1 << digitalPinToPortBit(6))},
#endif
#if (TINY_SOFT_PWM_USES_PIN7 == 1)
{7, digitalPinToPortIdx(7), ~(1 << digitalPinToPortBit(7))},
#endif
#if (TINY_SOFT_PWM_USES_PIN8 == 1)
{8, digitalPinToPortIdx(8), ~(1 << digitalPinToPortBit(8))},
#endif
#if (TINY_SOFT_PWM_USES_PIN9 == 1)
{9, digitalPinToPortIdx(9), ~(1 << digitalPinToPortBit(9))},
#endif
#if (TINY_SOFT_PWM_USES_PIN10 == 1)
{10, digitalPinToPortIdx(10), ~(1 << digitalPinToPortBit(10))},
#endif
#if (TINY_SOFT_PWM_USES_PIN11 == 1)
{11, digitalPinToPortIdx(11), ~(1 << digitalPinToPortBit(11))},
#endif
#if (TINY_SOFT_PWM_USES_PIN12 == 1)
{12, digitalPinToPortIdx(12), ~(1 << digitalPinToPortBit(12))},
#endif
#endif
};
static uint8_t Compare[TINY_SOFT_PWM_CH_MAX];
volatile uint8_t PwmOrder[TINY_SOFT_PWM_CH_MAX];
#ifdef TINY_SOFT_PWM_PORT0
static uint8_t Port0_PwmMask = 0;
volatile uint8_t Port0_PwmTo1 = 0x00;
volatile uint8_t Port0_PwmTo0 = 0xFF;
#endif
#ifdef TINY_SOFT_PWM_PORT1
static uint8_t Port1_PwmMask = 0;
volatile uint8_t Port1_PwmTo1 = 0x00;
volatile uint8_t Port1_PwmTo0 = 0xFF;
#endif
static uint8_t _TickMax = 255;
static uint8_t PwmToPwmMax(uint8_t Pwm);
void TinySoftPwm_begin(uint8_t TickMax, uint8_t PwmInit)
{
uint8_t oldSREG = SREG;
cli();
// set the direction of the used ports and update PortPwmMask
#if (TINY_SOFT_PWM_USES_PIN0 == 1)
TINY_SOFT_PWM_DECLARE_PIN(0);
#endif
#if (TINY_SOFT_PWM_USES_PIN1 == 1)
TINY_SOFT_PWM_DECLARE_PIN(1);
#endif
#if (TINY_SOFT_PWM_USES_PIN2 == 1)
TINY_SOFT_PWM_DECLARE_PIN(2);
#endif
#if (TINY_SOFT_PWM_USES_PIN3 == 1)
TINY_SOFT_PWM_DECLARE_PIN(3);
#endif
#if (TINY_SOFT_PWM_USES_PIN4 == 1)
TINY_SOFT_PWM_DECLARE_PIN(4);
#endif
#if (TINY_SOFT_PWM_USES_PIN5 == 1)
TINY_SOFT_PWM_DECLARE_PIN(5);
#endif
#if (TINY_SOFT_PWM_USES_PIN6 == 1)
TINY_SOFT_PWM_DECLARE_PIN(6);
#endif
#if (TINY_SOFT_PWM_USES_PIN7 == 1)
TINY_SOFT_PWM_DECLARE_PIN(7);
#endif
#if (TINY_SOFT_PWM_USES_PIN8 == 1)
TINY_SOFT_PWM_DECLARE_PIN(8);
#endif
#if (TINY_SOFT_PWM_USES_PIN9 == 1)
TINY_SOFT_PWM_DECLARE_PIN(9);
#endif
#if (TINY_SOFT_PWM_USES_PIN10 == 1)
TINY_SOFT_PWM_DECLARE_PIN(10);
#endif
#if (TINY_SOFT_PWM_USES_PIN11 == 1)
TINY_SOFT_PWM_DECLARE_PIN(11);
#endif
#if (TINY_SOFT_PWM_USES_PIN12 == 1)
TINY_SOFT_PWM_DECLARE_PIN(12);
#endif
_TickMax = TickMax;
#ifdef TINY_SOFT_PWM_PORT0
Port0_PwmTo1 = Port0_PwmMask;
#endif
#ifdef TINY_SOFT_PWM_PORT1
Port1_PwmTo1 = Port1_PwmMask;
#endif
// initialise all channels
for(uint8_t i = 0; i < TINY_SOFT_PWM_CH_MAX; i++)
{
Compare[i] = PwmToPwmMax(PwmInit); // set default PWM values
PwmOrder[i] = Compare[i]; // set default PWM values
}
SREG = oldSREG;
}
void TinySoftPwm_analogWrite(uint8_t Pin, uint8_t Pwm)
{
uint8_t RamIdx;
for(RamIdx = 0; RamIdx < TINY_SOFT_PWM_CH_MAX; RamIdx++)
{
if(GET_PWM_PIN_ID(RamIdx) == Pin) break;
}
if(RamIdx < TINY_SOFT_PWM_CH_MAX)
{
PwmOrder[RamIdx] = PwmToPwmMax(Pwm);
}
}
static uint8_t PwmToPwmMax(uint8_t Pwm)
{
uint16_t Pwm16;
Pwm16 = map(Pwm, 0, 255, 0, _TickMax);
Pwm16 = constrain(Pwm16, 0, _TickMax);
return((uint8_t)Pwm16);
}
void TinySoftPwm_process(void)
{
static uint8_t OvfCount=0xFF;
#ifdef TINY_SOFT_PWM_PORT0
Port0_PwmTo0 = (~Port0_PwmTo1) ^ Port0_PwmMask;
TINY_SOFT_PWM_PORT0 |= Port0_PwmTo1; // update ONLY used outputs to 1 without disturbing the others
TINY_SOFT_PWM_PORT0 &= Port0_PwmTo0; // update ONLY used outputs to 0 without disturbing the others
#endif
#ifdef TINY_SOFT_PWM_PORT1
Port1_PwmTo0 = (~Port1_PwmTo1) ^ Port1_PwmMask;
TINY_SOFT_PWM_PORT1 |= Port1_PwmTo1; // update ONLY used outputs to 1 without disturbing the others
TINY_SOFT_PWM_PORT1 &= Port1_PwmTo0; // update ONLY used outputs to 0 without disturbing the others
#endif
if(++OvfCount == _TickMax)
{ // increment modulo 256 counter and update
// the compare values only when counter = 0.
OvfCount=0;
#if (TINY_SOFT_PWM_CH_MAX >= 1)
Compare[0] = PwmOrder[0]; // verbose code for speed
#endif
#if (TINY_SOFT_PWM_CH_MAX >= 2)
Compare[1] = PwmOrder[1];
#endif
#if (TINY_SOFT_PWM_CH_MAX >= 3)
Compare[2] = PwmOrder[2];
#endif
#if (TINY_SOFT_PWM_CH_MAX >= 4)
Compare[3] = PwmOrder[3];
#endif
#if (TINY_SOFT_PWM_CH_MAX >= 5)
Compare[4] = PwmOrder[4];
#endif
#if (TINY_SOFT_PWM_CH_MAX >= 6)
Compare[5] = PwmOrder[5];
#endif
#if (TINY_SOFT_PWM_CH_MAX >= 7)
Compare[6] = PwmOrder[6]; // verbose code for speed
#endif
#if (TINY_SOFT_PWM_CH_MAX >= 8)
Compare[7] = PwmOrder[7];
#endif
#if (TINY_SOFT_PWM_CH_MAX >= 9)
Compare[8] = PwmOrder[8];
#endif
#if (TINY_SOFT_PWM_CH_MAX >= 10)
Compare[9] = PwmOrder[9];
#endif
#if (TINY_SOFT_PWM_CH_MAX >= 11)
Compare[10] = PwmOrder[10];
#endif
#if (TINY_SOFT_PWM_CH_MAX >= 12)
Compare[11] = PwmOrder[11];
#endif
#if (TINY_SOFT_PWM_CH_MAX >= 13)
Compare[12] = PwmOrder[12];
#endif
#ifdef TINY_SOFT_PWM_PORT0
Port0_PwmTo1 = Port0_PwmMask; // set all port used pins high
#endif
#ifdef TINY_SOFT_PWM_PORT1
Port1_PwmTo1 = Port1_PwmMask; // set all port used pins high
#endif
}
// clear port pin on compare match (executed on next interrupt)
#if (TINY_SOFT_PWM_CH_MAX >= 1)
if(Compare[0] == OvfCount) TINY_SOFT_PWM_CLEAR_PIN(0);
#endif
#if (TINY_SOFT_PWM_CH_MAX >= 2)
if(Compare[1] == OvfCount) TINY_SOFT_PWM_CLEAR_PIN(1);
#endif
#if (TINY_SOFT_PWM_CH_MAX >= 3)
if(Compare[2] == OvfCount) TINY_SOFT_PWM_CLEAR_PIN(2);
#endif
#if (TINY_SOFT_PWM_CH_MAX >= 4)
if(Compare[3] == OvfCount) TINY_SOFT_PWM_CLEAR_PIN(3);
#endif
#if (TINY_SOFT_PWM_CH_MAX >= 5)
if(Compare[4] == OvfCount) TINY_SOFT_PWM_CLEAR_PIN(4);
#endif
#if (TINY_SOFT_PWM_CH_MAX >= 6)
if(Compare[5] == OvfCount) TINY_SOFT_PWM_CLEAR_PIN(5);
#endif
#if (TINY_SOFT_PWM_CH_MAX >= 7)
if(Compare[6] == OvfCount) TINY_SOFT_PWM_CLEAR_PIN(6);
#endif
#if (TINY_SOFT_PWM_CH_MAX >= 8)
if(Compare[7] == OvfCount) TINY_SOFT_PWM_CLEAR_PIN(7);
#endif
#if (TINY_SOFT_PWM_CH_MAX >= 9)
if(Compare[8] == OvfCount) TINY_SOFT_PWM_CLEAR_PIN(8);
#endif
#if (TINY_SOFT_PWM_CH_MAX >= 10)
if(Compare[9] == OvfCount) TINY_SOFT_PWM_CLEAR_PIN(9);
#endif
#if (TINY_SOFT_PWM_CH_MAX >= 11)
if(Compare[10] == OvfCount) TINY_SOFT_PWM_CLEAR_PIN(10);
#endif
#if (TINY_SOFT_PWM_CH_MAX >= 12)
if(Compare[11] == OvfCount) TINY_SOFT_PWM_CLEAR_PIN(11);
#endif
#if (TINY_SOFT_PWM_CH_MAX >= 13)
if(Compare[12] == OvfCount) TINY_SOFT_PWM_CLEAR_PIN(12);
#endif
}