MPM controlled 16ch RGB LED dimmer
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.

314 lines
11KB

  1. /***************************************************************************
  2. * nvram parameter read/write *
  3. * *
  4. * Copyright (C) 2011 - 2012 by Olaf Rempel *
  5. * razzor AT kopf MINUS tisch DOT de *
  6. * *
  7. * This program is free software; you can redistribute it and/or modify *
  8. * it under the terms of the GNU General Public License as published by *
  9. * the Free Software Foundation; version 2 of the License, *
  10. * *
  11. * This program is distributed in the hope that it will be useful, *
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of *
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
  14. * GNU General Public License for more details. *
  15. * *
  16. * You should have received a copy of the GNU General Public License *
  17. * along with this program; if not, write to the *
  18. * Free Software Foundation, Inc., *
  19. * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
  20. ***************************************************************************/
  21. #include <avr/io.h>
  22. #include <avr/interrupt.h>
  23. #include <string.h>
  24. #include "rgb16mpm.h"
  25. /* 16 values per color */
  26. uint8_t chan_value[3][16];
  27. /* (16 +1) * 4 values (portA, portC, OCR0, flags) per color */
  28. static uint8_t chan_rawdata[3][17 * 4];
  29. /* used only in softpwm ISRs */
  30. register uint8_t * pCurrentStep asm("r2"); /* r3:r2 */
  31. /* used to sync softpwm ISRs and color_update() */
  32. register uint8_t nextColor asm("r4"); /* r4 */
  33. /* worst case: 9+2+12+8+7+13 = 61 clks -> 7.625us @8MHz */
  34. ISR(TIMER0_OVF_vect, ISR_NAKED)
  35. {
  36. asm volatile(
  37. /* save registers 2+1+2+2+2 = 9 */
  38. "push r24 \n\t"
  39. "in r24, __SREG__ \n\t"
  40. "push r24 \n\t"
  41. "push r30 \n\t"
  42. "push r31 \n\t"
  43. ::
  44. );
  45. asm volatile(
  46. /* disable outputs 1+1 = 2 */
  47. "out %0, r1 \n\t" /* PORTA = 0x00; */
  48. "out %1, r1 \n\t" /* PORTC = 0x00; */
  49. :: "I" (_SFR_IO_ADDR(PORTA)),
  50. "I" (_SFR_IO_ADDR(PORTC))
  51. );
  52. asm volatile(
  53. /* switch color and assign pCurrentStep */ // R G B
  54. "mov r24, %0 \n\t" // 1 1 1
  55. "inc %0 \n\t" /* nextColor++ */ // 2 2 2
  56. "cpi r24, 1 \n\t" // 3 3 3
  57. "brlo L_red%= \n\t" /* if (nextColor < 1) -> red */ // 5 4 4
  58. "breq L_green%= \n\t" /* if (nextColor == 1) -> green */ // - 6 5
  59. "clr %0 \n\t" /* nextColor = 0; */ // - - 6
  60. "ldi r24, %7 \n\t" /* PORTB = (1<<ROW4); */ // - - 7
  61. "ldi r30, lo8(%3) \n\t" /* pCurrentStep = &rawdata[2]; */ // - - 8
  62. "ldi r31, hi8(%3) \n\t" // - - 9
  63. "rjmp L_end%= \n\t" // - - 11
  64. "L_red%=: \n\t" /* red: */
  65. "ldi r24, %5 \n\t" /* PORTB = (1<<ROW1); */ // 6 - -
  66. "ldi r30, lo8(%1) \n\t" /* pCurrentStep = &rawdata[0]; */ // 7 - -
  67. "ldi r31, hi8(%1) \n\t" // 8 - -
  68. "rjmp L_end%= \n\t" // 10 - -
  69. "L_green%=: \n\t" /* green: */
  70. "ldi r24, %6 \n\t" /* PORTB = (1<<ROW2); */ // - 7 -
  71. "ldi r30, lo8(%2) \n\t" /* pCurrentStep = &rawdata[1]; */ // - 8 -
  72. "ldi r31, hi8(%2) \n\t" // - 9 -
  73. "L_end%=: \n\t"
  74. "out %4, r24 \n\t" /* set PORTB */ // 11 10 12
  75. :: "r" (nextColor),
  76. "i" (&chan_rawdata[0]), /* RED */
  77. "i" (&chan_rawdata[1]), /* GREEN */
  78. "i" (&chan_rawdata[2]), /* BLUE */
  79. "I" (_SFR_IO_ADDR(PORTB)),
  80. "i" ((1<<ROW1)), /* RED */
  81. "i" ((1<<ROW2)), /* GREEN */
  82. "i" ((1<<ROW3)) /* BLUE */
  83. );
  84. asm volatile(
  85. /* load table values 1+1+1+1+1+1+1+1+1 = 8 */
  86. "ld r24, z+ \n\t"
  87. "out %1, r24 \n\t" /* PORTA = *pCurrentStep++; */
  88. "ld r24, z+ \n\t"
  89. "out %2, r24 \n\t" /* PORTC = *pCurrentStep++; */
  90. "ld r24, z+ \n\t"
  91. "out %3, r24 \n\t" /* OCR0 = *pCurrentStep++; */
  92. "ld r24, z+ \n\t"
  93. "movw %0, r30 \n\t"
  94. :: "r" (pCurrentStep),
  95. "I" (_SFR_IO_ADDR(PORTA)),
  96. "I" (_SFR_IO_ADDR(PORTC)),
  97. "I" (_SFR_IO_ADDR(OCR0))
  98. );
  99. asm volatile(
  100. /* check if IRQ must be enabled 1+1+1+1+1+1+1 = 3/7 */
  101. "or r24, r24 \n\t" /* if (*pCurrentStep++) { */
  102. "breq L_skip%= \n\t"
  103. "ldi r24, %1 \n\t" /* TIFR = (1<<OCF0); */
  104. "out %0, r24 \n\t"
  105. "in r24, %2 \n\t" /* TIMSK |= (1<<OCIE0); */
  106. "ori r24, %3 \n\t"
  107. "out %2, r24 \n\t"
  108. "L_skip%=: \n\t" /* } */
  109. :: "I" (_SFR_IO_ADDR(TIFR)),
  110. "M" ((1<<OCF0)),
  111. "I" (_SFR_IO_ADDR(TIMSK)),
  112. "M" ((1<<OCIE0))
  113. );
  114. asm volatile(
  115. /* restore registers 2+2+2+1+2+4 = 13 */
  116. "pop r31 \n\t"
  117. "pop r30 \n\t"
  118. "pop r24 \n\t"
  119. "out __SREG__, r24 \n\t"
  120. "pop r24 \n\t"
  121. "reti \n\t"
  122. ::
  123. );
  124. }
  125. /* worst case: 9+9+5+13 = 36 clks -> 4.5us @ 8MHz */
  126. ISR(TIMER0_COMP_vect, ISR_NAKED)
  127. {
  128. asm volatile(
  129. /* save registers 2+1+2+2+2 = 9 */
  130. "push r24 \n\t"
  131. "in r24, __SREG__ \n\t"
  132. "push r24 \n\t"
  133. "push r30 \n\t"
  134. "push r31 \n\t"
  135. ::
  136. );
  137. asm volatile(
  138. /* load table values 1+1+1+1+1+1+1+1+1 = 9 */
  139. "movw r30, %0 \n\t"
  140. "ld r24, z+ \n\t"
  141. "out %1, r24 \n\t" /* PORTA = *pCurrentStep++; */
  142. "ld r24, z+ \n\t"
  143. "out %2, r24 \n\t" /* PORTC = *pCurrentStep++; */
  144. "ld r24, z+ \n\t"
  145. "out %3, r24 \n\t" /* OCR0 = *pCurrentStep++; */
  146. "ld r24, z+ \n\t"
  147. "movw %0, r30 \n\t"
  148. :: "r" (pCurrentStep),
  149. "I" (_SFR_IO_ADDR(PORTA)),
  150. "I" (_SFR_IO_ADDR(PORTC)),
  151. "I" (_SFR_IO_ADDR(OCR0))
  152. );
  153. asm volatile(
  154. /* check if IRQ must be disabled 1+1+1+1+1 = 3/5 */
  155. "or r24, r24 \n\t" /* if (!(*pCurrentStep++)) { */
  156. "brne L_skip%= \n\t"
  157. "in r24, %0 \n\t" /* TIMSK &= ~(1<<OCIE0); */
  158. "andi r24, %1 \n\t"
  159. "out %0, r24 \n\t"
  160. "L_skip%=: \n\t" /* } */
  161. :: "I" (_SFR_IO_ADDR(TIMSK)),
  162. "M" (0xFD) /* ~(1<<OCIE0) */
  163. );
  164. asm volatile(
  165. /* restore registers 2+2+2+1+2+4 = 13 */
  166. "pop r31 \n\t"
  167. "pop r30 \n\t"
  168. "pop r24 \n\t"
  169. "out __SREG__, r24 \n\t"
  170. "pop r24 \n\t"
  171. "reti \n\t"
  172. ::
  173. );
  174. }
  175. /* calc chan_valueX => chan_rawdataX */
  176. static void calculate_timer_values(uint8_t *value, uint8_t *pDataStart)
  177. {
  178. uint8_t *pData = pDataStart +4; /* skip first entry (init) */
  179. uint8_t index = 0;
  180. uint16_t chan_used = 0xFFFF;
  181. uint16_t chan_init = 0xFFFF;
  182. /* loop until all channels are calculated */
  183. while (chan_used) {
  184. uint8_t i;
  185. uint8_t min_value = 0xFF;
  186. uint16_t chan_tmp = chan_used;
  187. uint16_t chan_mask = 0x0001;
  188. for (i = 0; i < 16; i++) {
  189. /* skip if channel already used */
  190. if (chan_used & chan_mask)
  191. {
  192. /* channel is not used (value 0x00) */
  193. if (value[i] == 0x00) {
  194. chan_init &= (~chan_mask);
  195. chan_used &= (~chan_mask);
  196. /* found a new lower value */
  197. } else if (value[i] < min_value) {
  198. min_value = value[i];
  199. chan_tmp = chan_used & (~chan_mask);
  200. /* found another value with the same value */
  201. } else if (value[i] == min_value) {
  202. chan_tmp &= (~chan_mask);
  203. }
  204. }
  205. chan_mask <<= 1;
  206. }
  207. chan_used &= chan_tmp;
  208. if (min_value < 0xFF) {
  209. /* set new outputs */
  210. *pData++ = (chan_used & 0xFF); /* PORTA */
  211. *pData++ = ((chan_used >> 8) & 0xFF); /* PORTC */
  212. /* previous step needs timervalue and enable IRQ */
  213. *(pData++ -4) = min_value; /* OCR0 */
  214. *(pData++ -4) = 0x01; /* flags */
  215. }
  216. index++;
  217. }
  218. /* fill all remaining slots */
  219. while (index < 16) {
  220. /* repeat enabled outputs */
  221. *pData++ = (chan_used & 0xFF); /* PORTA */
  222. *pData++ = ((chan_used >> 8) & 0xFF); /* PORTC */
  223. /* previous step was last one (no timevalue / disable IRQ) */
  224. *(pData++ -4) = 0x00; /* OCR0 */
  225. *(pData++ -4) = 0x00; /* flags */
  226. index++;
  227. }
  228. /* first slot/init: enable only channels that are > 0 */
  229. pData = pDataStart;
  230. *pData++ = (chan_init & 0xFF); /* PORTA */
  231. *pData++ = ((chan_init >> 8) & 0xFF); /* PORTC */
  232. }
  233. uint8_t rgb_update(uint8_t dirty_mask, uint8_t blocking)
  234. {
  235. static uint8_t chan_dirty;
  236. chan_dirty |= (dirty_mask & COLOR_MASK);
  237. do {
  238. if ((chan_dirty & (1<<COLOR_RED)) && (nextColor == COLOR_BLUE)) {
  239. calculate_timer_values(chan_value[COLOR_RED], chan_rawdata[COLOR_RED]);
  240. chan_dirty &= ~(1<<COLOR_RED);
  241. } else if ((chan_dirty & (1<<COLOR_GREEN)) && (nextColor == COLOR_RED)) {
  242. calculate_timer_values(chan_value[COLOR_GREEN], chan_rawdata[COLOR_GREEN]);
  243. chan_dirty &= ~(1<<COLOR_GREEN);
  244. } else if ((chan_dirty & (1<<COLOR_BLUE)) && (nextColor == COLOR_GREEN)) {
  245. calculate_timer_values(chan_value[COLOR_BLUE], chan_rawdata[COLOR_BLUE]);
  246. chan_dirty &= ~(1<<COLOR_BLUE);
  247. } else if (!blocking) {
  248. break;
  249. }
  250. } while (chan_dirty);
  251. return chan_dirty;
  252. }
  253. void rgb_init(void)
  254. {
  255. /* 16 PWM Outputs */
  256. PORTA = 0x00;
  257. DDRA = 0xFF;
  258. PORTC = 0x00;
  259. DDRC = 0xFF;
  260. /* color ROWs */
  261. PORTB &= ~((1<<ROW1) | (1<<ROW2) | (1<<ROW3) | (1<<ROW4));
  262. DDRB |= (1<<ROW1) | (1<<ROW2) | (1<<ROW3) | (1<<ROW4);
  263. /* timer0, FCPU/64, overflow interrupt */
  264. TCCR0 = (1<<CS01) | (1<<CS00); /* FCPU/64 */
  265. TIMSK = (1<<TOIE0);
  266. /* load initial values from eeprom */
  267. memcpy(chan_value, nvram_data.initialRGB, sizeof(chan_value));
  268. }