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.

584 lines
18KB

  1. /***************************************************************************
  2. * 16ch RGB 8bit PWM controller *
  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 <avr/wdt.h>
  24. #include <stdio.h>
  25. /*
  26. * using ATmega32 @8MHz:
  27. * Fuse H: 0xD9 (no bootloader, jtag disabled)
  28. * Fuse L: 0xD4 (int. 8MHz Osz, fast rising power, no BOD)
  29. *
  30. * PA0..7 -> COL1..8
  31. * PC0..7 -> COL9..16
  32. * PB0 / PD7(OC2) -> ROW4 (OC2 not used)
  33. * PB1 / PD5(OC1A) -> ROW3 (OC1A not used)
  34. * PB2 / PD4(OC1B) -> ROW2 (OC1B not used)
  35. * PB3(OC0) / PD6 -> ROW1 (OC0 not used)
  36. * PD0 -> RXD
  37. * PD1 -> TXD
  38. * PD2 -> /RX_TX
  39. * PD3 -> /LED
  40. */
  41. #define F_CPU 8000000
  42. #include <util/delay.h>
  43. #define ROW1 PORTB1 /* RED */
  44. #define ROW2 PORTB0 /* GREEN */
  45. #define ROW3 PORTB3 /* BLUE */
  46. #define ROW4 PORTB2 /* not used */
  47. #define RXTX PORTD2 /* RS485 TX enable */
  48. #define LED PORTD3
  49. /* running without mpmboot? */
  50. #define STANDALONE 0
  51. #if (STANDALONE)
  52. #define OSCCAL 0xAA
  53. #define BAUDRATE 115200
  54. #define MPM_ADDRESS 0x11
  55. #define UART_CALC_BAUDRATE(baudRate) (((uint32_t)F_CPU) / (((uint32_t)baudRate)*16) -1)
  56. #endif /* STANDALONE */
  57. const static uint8_t versioninfo[16] = "rgb16mpm v0.99";
  58. #define CMD_WAIT 0x00
  59. #define CMD_SWITCH_MODE 0x01
  60. #define CMD_GET_VERSION 0x02
  61. // #define CMD_GET_CHIPINFO 0x03
  62. // #define CMD_READ_MEMORY 0x11
  63. // #define CMD_WRITE_MEMORY 0x12
  64. #define CMD_WRITE_COLOR 0x81
  65. #define CMD_WRITE_RAW_COLOR 0x82
  66. #define CMD_READ_RAW_COLOR 0x83
  67. #define CMD_WRITE_CONFIG 0x81
  68. /* 16 values per color */
  69. static uint8_t chan_value[3][16];
  70. /* (16 +1) * 4 values (portA, portC, OCR0, flags) per color */
  71. static uint8_t chan_rawdata[3][17 * 4];
  72. /* used only in softpwm ISRs */
  73. register uint8_t * pCurrentStep asm("r2"); /* r3:r2 */
  74. /* used to sync softpwm ISRs and color_update() */
  75. register uint8_t nextColor asm("r4"); /* r4 */
  76. /* worst case: 9+2+12+8+7+13 = 61 clks -> 7.625us @8MHz */
  77. ISR(TIMER0_OVF_vect, ISR_NAKED)
  78. {
  79. asm volatile(
  80. /* save registers 2+1+2+2+2 = 9 */
  81. "push r24 \n\t"
  82. "in r24, __SREG__ \n\t"
  83. "push r24 \n\t"
  84. "push r30 \n\t"
  85. "push r31 \n\t"
  86. ::
  87. );
  88. asm volatile(
  89. /* disable outputs 1+1 = 2 */
  90. "out %0, r1 \n\t" /* PORTA = 0x00; */
  91. "out %1, r1 \n\t" /* PORTC = 0x00; */
  92. :: "I" (_SFR_IO_ADDR(PORTA)),
  93. "I" (_SFR_IO_ADDR(PORTC))
  94. );
  95. asm volatile(
  96. /* switch color and assign pCurrentStep */ // R G B
  97. "mov r24, %0 \n\t" // 1 1 1
  98. "inc %0 \n\t" /* nextColor++ */ // 2 2 2
  99. "cpi r24, 1 \n\t" // 3 3 3
  100. "brlo L_red%= \n\t" /* if (nextColor < 1) -> red */ // 5 4 4
  101. "breq L_green%= \n\t" /* if (nextColor == 1) -> green */ // - 6 5
  102. "clr %0 \n\t" /* nextColor = 0; */ // - - 6
  103. "ldi r24, %7 \n\t" /* PORTB = (1<<ROW4); */ // - - 7
  104. "ldi r30, lo8(%3) \n\t" /* pCurrentStep = &rawdata[2]; */ // - - 8
  105. "ldi r31, hi8(%3) \n\t" // - - 9
  106. "rjmp L_end%= \n\t" // - - 11
  107. "L_red%=: \n\t" /* red: */
  108. "ldi r24, %5 \n\t" /* PORTB = (1<<ROW1); */ // 6 - -
  109. "ldi r30, lo8(%1) \n\t" /* pCurrentStep = &rawdata[0]; */ // 7 - -
  110. "ldi r31, hi8(%1) \n\t" // 8 - -
  111. "rjmp L_end%= \n\t" // 10 - -
  112. "L_green%=: \n\t" /* green: */
  113. "ldi r24, %6 \n\t" /* PORTB = (1<<ROW2); */ // - 7 -
  114. "ldi r30, lo8(%2) \n\t" /* pCurrentStep = &rawdata[1]; */ // - 8 -
  115. "ldi r31, hi8(%2) \n\t" // - 9 -
  116. "L_end%=: \n\t"
  117. "out %4, r24 \n\t" /* set PORTB */ // 11 10 12
  118. :: "r" (nextColor),
  119. "i" (&chan_rawdata[0]), /* RED */
  120. "i" (&chan_rawdata[1]), /* GREEN */
  121. "i" (&chan_rawdata[2]), /* BLUE */
  122. "I" (_SFR_IO_ADDR(PORTB)),
  123. "i" ((1<<ROW1)), /* RED */
  124. "i" ((1<<ROW2)), /* GREEN */
  125. "i" ((1<<ROW3)) /* BLUE */
  126. );
  127. asm volatile(
  128. /* load table values 1+1+1+1+1+1+1+1+1 = 8 */
  129. "ld r24, z+ \n\t"
  130. "out %1, r24 \n\t" /* PORTA = *pCurrentStep++; */
  131. "ld r24, z+ \n\t"
  132. "out %2, r24 \n\t" /* PORTC = *pCurrentStep++; */
  133. "ld r24, z+ \n\t"
  134. "out %3, r24 \n\t" /* OCR0 = *pCurrentStep++; */
  135. "ld r24, z+ \n\t"
  136. "movw %0, r30 \n\t"
  137. :: "r" (pCurrentStep),
  138. "I" (_SFR_IO_ADDR(PORTA)),
  139. "I" (_SFR_IO_ADDR(PORTC)),
  140. "I" (_SFR_IO_ADDR(OCR0))
  141. );
  142. asm volatile(
  143. /* check if IRQ must be enabled 1+1+1+1+1+1+1 = 3/7 */
  144. "or r24, r24 \n\t" /* if (*pCurrentStep++) { */
  145. "breq L_skip%= \n\t"
  146. "ldi r24, %1 \n\t" /* TIFR = (1<<OCF0); */
  147. "out %0, r24 \n\t"
  148. "in r24, %2 \n\t" /* TIMSK |= (1<<OCIE0); */
  149. "ori r24, %3 \n\t"
  150. "out %2, r24 \n\t"
  151. "L_skip%=: \n\t" /* } */
  152. :: "I" (_SFR_IO_ADDR(TIFR)),
  153. "M" ((1<<OCF0)),
  154. "I" (_SFR_IO_ADDR(TIMSK)),
  155. "M" ((1<<OCIE0))
  156. );
  157. asm volatile(
  158. /* restore registers 2+2+2+1+2+4 = 13 */
  159. "pop r31 \n\t"
  160. "pop r30 \n\t"
  161. "pop r24 \n\t"
  162. "out __SREG__, r24 \n\t"
  163. "pop r24 \n\t"
  164. "reti \n\t"
  165. ::
  166. );
  167. }
  168. /* worst case: 9+9+5+13 = 36 clks -> 4.5us @ 8MHz */
  169. ISR(TIMER0_COMP_vect, ISR_NAKED)
  170. {
  171. asm volatile(
  172. /* save registers 2+1+2+2+2 = 9 */
  173. "push r24 \n\t"
  174. "in r24, __SREG__ \n\t"
  175. "push r24 \n\t"
  176. "push r30 \n\t"
  177. "push r31 \n\t"
  178. ::
  179. );
  180. asm volatile(
  181. /* load table values 1+1+1+1+1+1+1+1+1 = 9 */
  182. "movw r30, %0 \n\t"
  183. "ld r24, z+ \n\t"
  184. "out %1, r24 \n\t" /* PORTA = *pCurrentStep++; */
  185. "ld r24, z+ \n\t"
  186. "out %2, r24 \n\t" /* PORTC = *pCurrentStep++; */
  187. "ld r24, z+ \n\t"
  188. "out %3, r24 \n\t" /* OCR0 = *pCurrentStep++; */
  189. "ld r24, z+ \n\t"
  190. "movw %0, r30 \n\t"
  191. :: "r" (pCurrentStep),
  192. "I" (_SFR_IO_ADDR(PORTA)),
  193. "I" (_SFR_IO_ADDR(PORTC)),
  194. "I" (_SFR_IO_ADDR(OCR0))
  195. );
  196. asm volatile(
  197. /* check if IRQ must be disabled 1+1+1+1+1 = 3/5 */
  198. "or r24, r24 \n\t" /* if (!(*pCurrentStep++)) { */
  199. "brne L_skip%= \n\t"
  200. "in r24, %0 \n\t" /* TIMSK &= ~(1<<OCIE0); */
  201. "andi r24, %1 \n\t"
  202. "out %0, r24 \n\t"
  203. "L_skip%=: \n\t" /* } */
  204. :: "I" (_SFR_IO_ADDR(TIMSK)),
  205. "M" (0xFD) /* ~(1<<OCIE0) */
  206. );
  207. asm volatile(
  208. /* restore registers 2+2+2+1+2+4 = 13 */
  209. "pop r31 \n\t"
  210. "pop r30 \n\t"
  211. "pop r24 \n\t"
  212. "out __SREG__, r24 \n\t"
  213. "pop r24 \n\t"
  214. "reti \n\t"
  215. ::
  216. );
  217. }
  218. /* calc chan_valueX => chan_rawdataX */
  219. static void calculate_timer_values(uint8_t *value, uint8_t *pDataStart)
  220. {
  221. uint8_t *pData = pDataStart +4; /* skip first entry (init) */
  222. uint8_t index = 0;
  223. uint16_t chan_used = 0xFFFF;
  224. uint16_t chan_init = 0xFFFF;
  225. /* loop until all channels are calculated */
  226. while (chan_used) {
  227. uint8_t i;
  228. uint8_t min_value = 0xFF;
  229. uint16_t chan_tmp = chan_used;
  230. uint16_t chan_mask = 0x0001;
  231. for (i = 0; i < 16; i++) {
  232. /* skip if channel already used */
  233. if (chan_used & chan_mask)
  234. {
  235. /* channel is not used (value 0x00) */
  236. if (value[i] == 0x00) {
  237. chan_init &= (~chan_mask);
  238. chan_used &= (~chan_mask);
  239. /* found a new lower value */
  240. } else if (value[i] < min_value) {
  241. min_value = value[i];
  242. chan_tmp = chan_used & (~chan_mask);
  243. /* found another value with the same value */
  244. } else if (value[i] == min_value) {
  245. chan_tmp &= (~chan_mask);
  246. }
  247. }
  248. chan_mask <<= 1;
  249. }
  250. chan_used &= chan_tmp;
  251. if (min_value < 0xFF) {
  252. /* set new outputs */
  253. *pData++ = (chan_used & 0xFF); /* PORTA */
  254. *pData++ = ((chan_used >> 8) & 0xFF); /* PORTC */
  255. /* previous step needs timervalue and enable IRQ */
  256. *(pData++ -4) = min_value; /* OCR0 */
  257. *(pData++ -4) = 0x01; /* flags */
  258. }
  259. index++;
  260. }
  261. /* fill all remaining slots */
  262. while (index < 16) {
  263. /* repeat enabled outputs */
  264. *pData++ = (chan_used & 0xFF); /* PORTA */
  265. *pData++ = ((chan_used >> 8) & 0xFF); /* PORTC */
  266. /* previous step was last one (no timevalue / disable IRQ) */
  267. *(pData++ -4) = 0x00; /* OCR0 */
  268. *(pData++ -4) = 0x00; /* flags */
  269. index++;
  270. }
  271. /* first slot/init: enable only channels that are > 0 */
  272. pData = pDataStart;
  273. *pData++ = (chan_init & 0xFF); /* PORTA */
  274. *pData++ = ((chan_init >> 8) & 0xFF); /* PORTC */
  275. }
  276. static uint8_t rx_cmd;
  277. static uint16_t rx_bcnt = 0xFF;
  278. static uint16_t rx_length;
  279. static uint8_t tx_cmd;
  280. static uint8_t tx_cause;
  281. static uint16_t tx_length;
  282. static uint16_t tx_bcnt;
  283. ISR(USART_RXC_vect)
  284. {
  285. uint8_t data = UDR;
  286. sei();
  287. if (rx_bcnt == 0xFF) {
  288. /* MPM address stored in TWI address register by bootloader */
  289. if (data == TWAR) {
  290. UCSRA &= ~(1<<MPCM);
  291. rx_bcnt = 0;
  292. }
  293. } else {
  294. if (rx_bcnt == 0) {
  295. rx_cmd = data;
  296. } else if ((rx_bcnt == 1) || (rx_bcnt == 2)) {
  297. rx_length = (rx_length << 8) | data;
  298. } else if ((rx_bcnt -3) < rx_length) {
  299. // TODO: get data
  300. }
  301. if ((rx_bcnt -2) == rx_length) {
  302. /* enable RS485 TX */
  303. PORTD |= (1<<RXTX);
  304. /* first byte */
  305. tx_cmd = rx_cmd;
  306. UDR = rx_cmd;
  307. /* prepare header */
  308. tx_bcnt = 1;
  309. tx_cause = 0;
  310. tx_length = 0;
  311. if (tx_cmd == CMD_GET_VERSION) {
  312. tx_length = sizeof(versioninfo);
  313. }
  314. /* enable interrupt */
  315. UCSRB |= (1<<UDRIE);
  316. }
  317. rx_bcnt++;
  318. }
  319. }
  320. ISR(USART_UDRE_vect)
  321. {
  322. /* enable IRQs again, but prevent multiple UDRE IRQs */
  323. UCSRB &= ~(1<<UDRIE);
  324. sei();
  325. if ((tx_bcnt < 4) || (tx_bcnt -4) < tx_length) {
  326. uint16_t pos = (tx_bcnt -4);
  327. uint8_t data = 0xFF;
  328. if (tx_bcnt == 1) {
  329. data = tx_cause;
  330. } else if (tx_bcnt == 2) {
  331. data = (tx_length >> 8);
  332. } else if (tx_bcnt == 3) {
  333. data = (tx_length & 0xFF);
  334. } else if (tx_cmd == CMD_GET_VERSION) {
  335. data = versioninfo[pos];
  336. } else {
  337. data = 0xAA;
  338. }
  339. UDR = data;
  340. /* re-enable for next round */
  341. UCSRB |= (1<<UDRIE);
  342. }
  343. tx_bcnt++;
  344. }
  345. ISR(USART_TXC_vect, ISR_NOBLOCK)
  346. {
  347. /* disable RS485 TX */
  348. PORTD &= ~(1<<RXTX);
  349. /* enable MP mode again */
  350. UCSRA |= (1<<MPCM);
  351. rx_bcnt = 0xFF; // FIXME: cli?
  352. if (tx_cmd == CMD_SWITCH_MODE) { // FIXME: check mode
  353. wdt_enable(WDTO_15MS);
  354. }
  355. }
  356. int main(void) __attribute__ ((noreturn));
  357. int main(void)
  358. {
  359. /* 16 PWM Outputs */
  360. PORTA = 0x00;
  361. DDRA = 0xFF;
  362. PORTC = 0x00;
  363. DDRC = 0xFF;
  364. /* color ROWs */
  365. PORTB = 0x00;
  366. DDRB = (1<<ROW1) | (1<<ROW2) | (1<<ROW3) | (1<<ROW4);
  367. PORTD = (1<<LED);
  368. DDRD = (1<<RXTX) | (1<<LED);
  369. #if (STANDALONE)
  370. OSCCAL = OSCCAL_VALUE;
  371. #endif /* (STANDALONE) */
  372. /* timer0, FCPU/64, overflow interrupt */
  373. TCCR0 = (1<<CS01) | (1<<CS00); /* FCPU/64 */
  374. TIMSK = (1<<TOIE0);
  375. TCNT0 = 0x00;
  376. /* USART config */
  377. /* Multi Drop Mode, 9n1 */
  378. UCSRA = (1<<MPCM);
  379. UCSRB = (1<<RXEN) | (1<<TXEN) | (1<<RXCIE) | (1<<TXCIE) | (1<<UCSZ2);
  380. UCSRC = (1<<URSEL) | (1<<UCSZ1) | (1<<UCSZ0);
  381. #if (STANDALONE)
  382. UBRRH = (UART_CALC_BAUDRATE(BAUDRATE)>>8) & 0xFF;
  383. UBRRL = (UART_CALC_BAUDRATE(BAUDRATE) & 0xFF);
  384. /* MPM address stored in TWI address register */
  385. TWAR = MPM_ADDRESS;
  386. #endif /* (STANDALONE) */
  387. sei();
  388. uint8_t x = 0;
  389. uint8_t xdir = 1;
  390. uint16_t ramp = 0;
  391. uint8_t step = 0;
  392. #if 0
  393. /* create worst case for R+G, relaxed for B */
  394. for (x = 0; x < 16; x++) {
  395. chan_value[0][x] = 254 - x;
  396. chan_value[1][x] = x +1;
  397. chan_value[2][x] = x * 16;
  398. }
  399. x = 0;
  400. #endif
  401. while (1) {
  402. uint8_t color_update = 0x07;
  403. while (color_update) {
  404. if ((color_update & 0x01) && (nextColor == 2)) {
  405. calculate_timer_values(chan_value[0], chan_rawdata[0]);
  406. color_update &= ~(0x01);
  407. } else if ((color_update & 0x02) && (nextColor == 0)) {
  408. calculate_timer_values(chan_value[1], chan_rawdata[1]);
  409. color_update &= ~(0x02);
  410. } else if ((color_update & 0x04) && (nextColor == 1)) {
  411. calculate_timer_values(chan_value[2], chan_rawdata[2]);
  412. color_update &= ~(0x04);
  413. }
  414. }
  415. #if 1
  416. PORTD ^= (1<<LED);
  417. // _delay_ms(100);
  418. step++;
  419. if (step == 16) {
  420. step = 0;
  421. if (xdir) {
  422. x++;
  423. if (x == 0x05)
  424. x = 0x08;
  425. else if (x == 0x0C)
  426. xdir = 0;
  427. } else {
  428. x--;
  429. if (x == 0x00)
  430. xdir = 1;
  431. else if (x == 0x07)
  432. x = 0x04;
  433. }
  434. }
  435. uint8_t color[3];
  436. ramp++;
  437. switch (ramp >> 8) {
  438. case 6:
  439. ramp = 0x0000;
  440. /* no break */
  441. case 0: /* red: on, green: ramp up, blue: off */
  442. color[0] = 0xFF;
  443. color[1] = ramp & 0xFF;
  444. color[2] = 0x00;
  445. break;
  446. case 1: /* red: ramp down, green: on, blue:off */
  447. color[0] = 0xFF - (ramp & 0xFF);
  448. color[1] = 0xFF;
  449. color[2] = 0x00;
  450. break;
  451. case 2: /* red: off, green: on, blue: ramp up */
  452. color[0] = 0x00;
  453. color[1] = 0xFF;
  454. color[2] = (ramp & 0xFF);
  455. break;
  456. case 3: /* red: off, green: ramp down: blue: on */
  457. color[0] = 0x00;
  458. color[1] = 0xFF - (ramp & 0xFF);
  459. color[2] = 0xFF;
  460. break;
  461. case 4: /* red: ramp up, green: off, blue: on */
  462. color[0] = (ramp & 0xFF);
  463. color[1] = 0x00;
  464. color[2] = 0xFF;
  465. break;
  466. case 5: /* red: on, green: off, blue: ramp down */
  467. color[0] = 0xFF;
  468. color[1] = 0x00;
  469. color[2] = 0xFF - (ramp & 0xFF);
  470. break;
  471. }
  472. uint8_t i, j;
  473. for (i = 0; i < 16; i++) {
  474. for (j = 0; j < 3; j++) {
  475. #if 0
  476. if (x == i) {
  477. chan_value[j][i] = color[j];
  478. } else if (chan_value[j][i] > 0) {
  479. uint8_t tmp = (chan_value[j][i] >> 5);
  480. chan_value[j][i] -= (tmp > 0) ? tmp : 1;
  481. }
  482. #else
  483. chan_value[j][i] = color[j];
  484. #endif
  485. }
  486. }
  487. #endif
  488. }
  489. }