PlatformIO package of the Teensy core framework compatible with GCC 10 & C++20
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SoftPWM.cpp 8.4KB

3 yıl önce
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. /*
  2. || @author Brett Hagman <bhagman@wiring.org.co>
  3. || @contribution Paul Stoffregen (paul at pjrc dot com)
  4. || @url http://wiring.org.co/
  5. || @url http://roguerobotics.com/
  6. ||
  7. || @description
  8. || | A Software PWM Library
  9. || |
  10. || | Written by Brett Hagman
  11. || | http://www.roguerobotics.com/
  12. || | bhagman@roguerobotics.com, bhagman@wiring.org.co
  13. || |
  14. || | A Wiring (and Arduino) Library, for Atmel AVR8 bit series microcontrollers,
  15. || | to produce PWM signals on any arbitrary pin.
  16. || |
  17. || | It was originally designed for controlling the brightness of LEDs, but
  18. || | could be adapted to control servos and other low frequency PWM controlled
  19. || | devices as well.
  20. || |
  21. || | It uses a single hardware timer (Timer 2) on the Atmel microcontroller to
  22. || | generate up to 20 PWM channels (your mileage may vary).
  23. || |
  24. || #
  25. ||
  26. || @license Please see the accompanying LICENSE.txt file for this project.
  27. ||
  28. || @notes
  29. || | Minor modification by Paul Stoffregen to support different timers.
  30. || |
  31. || #
  32. ||
  33. || @name Software PWM Library
  34. || @type Library
  35. || @target Atmel AVR 8 Bit
  36. ||
  37. || @version 1.0.0
  38. ||
  39. */
  40. #include <avr/io.h>
  41. #include <avr/interrupt.h>
  42. #include "SoftPWM.h"
  43. #include "SoftPWM_timer.h"
  44. #if defined(WIRING)
  45. #include <Wiring.h>
  46. #elif ARDUINO >= 100
  47. #include <Arduino.h>
  48. #else
  49. #include <WProgram.h>
  50. #endif
  51. // TODO: many other modern boards need 32 bit register access...
  52. #if defined(__IMXRT1062__)
  53. #define OUTPORT_32BITS
  54. #endif
  55. #if F_CPU
  56. #define SOFTPWM_FREQ 60UL
  57. #define SOFTPWM_OCR (F_CPU/(8UL*256UL*SOFTPWM_FREQ))
  58. #else
  59. // 130 == 60 Hz (on 16 MHz part)
  60. #define SOFTPWM_OCR 130
  61. #endif
  62. volatile uint8_t _isr_softcount = 0xff;
  63. uint8_t _softpwm_defaultPolarity = SOFTPWM_NORMAL;
  64. typedef struct
  65. {
  66. // hardware I/O port and pin for this channel
  67. int8_t pin;
  68. uint8_t polarity;
  69. #ifdef OUTPORT_32BITS
  70. volatile uint32_t *outport;
  71. uint32_t pinmask;
  72. #else
  73. volatile uint8_t *outport;
  74. uint8_t pinmask;
  75. #endif
  76. uint8_t pwmvalue;
  77. uint8_t checkval;
  78. uint8_t fadeuprate;
  79. uint8_t fadedownrate;
  80. } softPWMChannel;
  81. softPWMChannel _softpwm_channels[SOFTPWM_MAXCHANNELS];
  82. // Here is the meat and gravy
  83. #ifdef WIRING
  84. void SoftPWM_Timer_Interrupt(void)
  85. #else
  86. ISR(SOFTPWM_TIMER_INTERRUPT)
  87. #endif
  88. {
  89. uint8_t i;
  90. int16_t newvalue;
  91. int16_t direction;
  92. if(++_isr_softcount == 0)
  93. {
  94. // set all channels high - let's start again
  95. // and accept new checkvals
  96. for (i = 0; i < SOFTPWM_MAXCHANNELS; i++)
  97. {
  98. if (_softpwm_channels[i].fadeuprate > 0 || _softpwm_channels[i].fadedownrate > 0)
  99. {
  100. // we want to fade to the new value
  101. direction = _softpwm_channels[i].pwmvalue - _softpwm_channels[i].checkval;
  102. // we will default to jumping to the new value
  103. newvalue = _softpwm_channels[i].pwmvalue;
  104. if (direction > 0 && _softpwm_channels[i].fadeuprate > 0)
  105. {
  106. newvalue = _softpwm_channels[i].checkval + _softpwm_channels[i].fadeuprate;
  107. if (newvalue > _softpwm_channels[i].pwmvalue)
  108. newvalue = _softpwm_channels[i].pwmvalue;
  109. }
  110. else if (direction < 0 && _softpwm_channels[i].fadedownrate > 0)
  111. {
  112. newvalue = _softpwm_channels[i].checkval - _softpwm_channels[i].fadedownrate;
  113. if (newvalue < _softpwm_channels[i].pwmvalue)
  114. newvalue = _softpwm_channels[i].pwmvalue;
  115. }
  116. _softpwm_channels[i].checkval = newvalue;
  117. }
  118. else // just set the channel to the new value
  119. _softpwm_channels[i].checkval = _softpwm_channels[i].pwmvalue;
  120. // now set the pin high (if not 0)
  121. if (_softpwm_channels[i].checkval > 0) // don't set if checkval == 0
  122. {
  123. if (_softpwm_channels[i].polarity == SOFTPWM_NORMAL)
  124. *_softpwm_channels[i].outport |= _softpwm_channels[i].pinmask;
  125. else
  126. *_softpwm_channels[i].outport &= ~(_softpwm_channels[i].pinmask);
  127. }
  128. }
  129. }
  130. for (i = 0; i < SOFTPWM_MAXCHANNELS; i++)
  131. {
  132. if (_softpwm_channels[i].pin >= 0) // if it's a valid pin
  133. {
  134. if (_softpwm_channels[i].checkval == _isr_softcount) // if we have hit the width
  135. {
  136. // turn off the channel
  137. if (_softpwm_channels[i].polarity == SOFTPWM_NORMAL)
  138. *_softpwm_channels[i].outport &= ~(_softpwm_channels[i].pinmask);
  139. else
  140. *_softpwm_channels[i].outport |= _softpwm_channels[i].pinmask;
  141. }
  142. }
  143. }
  144. }
  145. void SoftPWMBegin(uint8_t defaultPolarity)
  146. {
  147. // We can tweak the number of PWM period by changing the prescalar
  148. // and the OCR - we'll default to ck/8 (CS21 set) and OCR=128.
  149. // This gives 1024 cycles between interrupts. And the ISR consumes ~200 cycles, so
  150. // we are looking at about 20 - 30% of CPU time spent in the ISR.
  151. // At these settings on a 16 MHz part, we will get a PWM period of
  152. // approximately 60Hz (~16ms).
  153. uint8_t i;
  154. #ifdef WIRING
  155. Timer2.setMode(0b010); // CTC
  156. Timer2.setClockSource(CLOCK_PRESCALE_8);
  157. Timer2.setOCR(CHANNEL_A, SOFTPWM_OCR);
  158. Timer2.attachInterrupt(INTERRUPT_COMPARE_MATCH_A, SoftPWM_Timer_Interrupt);
  159. #else
  160. SOFTPWM_TIMER_INIT(SOFTPWM_OCR);
  161. #endif
  162. for (i = 0; i < SOFTPWM_MAXCHANNELS; i++)
  163. {
  164. _softpwm_channels[i].pin = -1;
  165. _softpwm_channels[i].polarity = SOFTPWM_NORMAL;
  166. _softpwm_channels[i].outport = 0;
  167. _softpwm_channels[i].fadeuprate = 0;
  168. _softpwm_channels[i].fadedownrate = 0;
  169. }
  170. _softpwm_defaultPolarity = defaultPolarity;
  171. }
  172. void SoftPWMSetPolarity(int8_t pin, uint8_t polarity)
  173. {
  174. uint8_t i;
  175. if (polarity != SOFTPWM_NORMAL)
  176. polarity = SOFTPWM_INVERTED;
  177. for (i = 0; i < SOFTPWM_MAXCHANNELS; i++)
  178. {
  179. if ((pin < 0 && _softpwm_channels[i].pin >= 0) || // ALL pins
  180. (pin >= 0 && _softpwm_channels[i].pin == pin)) // individual pin
  181. {
  182. _softpwm_channels[i].polarity = polarity;
  183. }
  184. }
  185. }
  186. void SoftPWMSetPercent(int8_t pin, uint8_t percent, uint8_t hardset)
  187. {
  188. SoftPWMSet(pin, ((uint16_t)percent * 255) / 100, hardset);
  189. }
  190. void SoftPWMSet(int8_t pin, uint8_t value, uint8_t hardset)
  191. {
  192. int8_t firstfree = -1; // first free index
  193. uint8_t i;
  194. if (hardset)
  195. {
  196. SOFTPWM_TIMER_SET(0);
  197. _isr_softcount = 0xff;
  198. }
  199. // If the pin isn't already set, add it
  200. for (i = 0; i < SOFTPWM_MAXCHANNELS; i++)
  201. {
  202. if ((pin < 0 && _softpwm_channels[i].pin >= 0) || // ALL pins
  203. (pin >= 0 && _softpwm_channels[i].pin == pin)) // individual pin
  204. {
  205. // set the pin (and exit, if individual pin)
  206. _softpwm_channels[i].pwmvalue = value;
  207. if (pin >= 0) // we've set the individual pin
  208. return;
  209. }
  210. // get the first free pin if available
  211. if (firstfree < 0 && _softpwm_channels[i].pin < 0)
  212. firstfree = i;
  213. }
  214. if (pin >= 0 && firstfree >= 0)
  215. {
  216. // we have a free pin we can use
  217. _softpwm_channels[firstfree].pin = pin;
  218. _softpwm_channels[firstfree].polarity = _softpwm_defaultPolarity;
  219. _softpwm_channels[firstfree].outport = portOutputRegister(digitalPinToPort(pin));
  220. _softpwm_channels[firstfree].pinmask = digitalPinToBitMask(pin);
  221. _softpwm_channels[firstfree].pwmvalue = value;
  222. // _softpwm_channels[firstfree].checkval = 0;
  223. // now prepare the pin for output
  224. // turn it off to start (no glitch)
  225. if (_softpwm_defaultPolarity == SOFTPWM_NORMAL)
  226. digitalWrite(pin, LOW);
  227. else
  228. digitalWrite(pin, HIGH);
  229. pinMode(pin, OUTPUT);
  230. }
  231. }
  232. void SoftPWMEnd(int8_t pin)
  233. {
  234. uint8_t i;
  235. for (i = 0; i < SOFTPWM_MAXCHANNELS; i++)
  236. {
  237. if ((pin < 0 && _softpwm_channels[i].pin >= 0) || // ALL pins
  238. (pin >= 0 && _softpwm_channels[i].pin == pin)) // individual pin
  239. {
  240. // now disable the pin (put it into INPUT mode)
  241. digitalWrite(_softpwm_channels[i].pin, 1);
  242. pinMode(_softpwm_channels[i].pin, INPUT);
  243. // remove the pin
  244. _softpwm_channels[i].pin = -1;
  245. }
  246. }
  247. }
  248. void SoftPWMSetFadeTime(int8_t pin, uint16_t fadeUpTime, uint16_t fadeDownTime)
  249. {
  250. int16_t fadeAmount;
  251. uint8_t i;
  252. for (i = 0; i < SOFTPWM_MAXCHANNELS; i++)
  253. {
  254. if ((pin < 0 && _softpwm_channels[i].pin >= 0) || // ALL pins
  255. (pin >= 0 && _softpwm_channels[i].pin == pin)) // individual pin
  256. {
  257. fadeAmount = 0;
  258. if (fadeUpTime > 0)
  259. fadeAmount = 255UL * (SOFTPWM_OCR * 256UL / (F_CPU / 8000UL)) / fadeUpTime;
  260. _softpwm_channels[i].fadeuprate = fadeAmount;
  261. fadeAmount = 0;
  262. if (fadeDownTime > 0)
  263. fadeAmount = 255UL * (SOFTPWM_OCR * 256UL / (F_CPU / 8000UL)) / fadeDownTime;
  264. _softpwm_channels[i].fadedownrate = fadeAmount;
  265. if (pin >= 0) // we've set individual pin
  266. break;
  267. }
  268. }
  269. }