BPLightContraption
 All Classes Namespaces Files Functions Variables Macros Pages
triaclight.c
Go to the documentation of this file.
1 /*! \file
2  * \brief Main functions for the microcontroller
3  * \author Benjamin Pritchard (ben@bennyp.org)
4  * \copyright 2013 Benjamin Pritchard. Released under the MIT License
5  */
6 
7 #include <avr/io.h>
8 #include <avr/interrupt.h>
9 /*#include <avr/pgmspace.h>*/
10 #include <util/delay.h>
11 
12 #include "serial.h"
13 #include "commands.h"
14 #include "bits.h"
15 
16 
17 #define NULL 0x0
18 
19 /*! \brief The size of the command received buffer (in bytes)
20 
21  Note that the buffer is index using a uint8_t. Therefore,
22  BUFSIZE must be less than or equal to 256
23 */
24 #define BUFSIZE 64
25 
26 
27 /*! \brief Pulse width required to turn on the triac, in microseconds
28 
29  Should be as short as possible
30 */
31 #define PULSE_WIDTH 3 /* microseconds */
32 
33 
34 /*! \brief The number of clock ticks for half of a 60Hz sine wave */
35 #define ONETWENTYHERTZ 16667ul
36 
37 
38 /*! \brief Integers representing the fraction of 2^16 for the
39  different levels.
40 
41  The level (as a %) is given as the index. This is
42  normalized so that the precentage represent the percentage
43  output power, rather than the percent phase-shift for the
44  sine wave.
45 
46  For a given percentage, the level is stored as the fraction of
47  2^16, rather than the fraction of 100. This gets multiplied
48  by ONETWENTYHERTZ and then divided by 2^16, allowing only
49  bit shift operations and not floating point math.
50 
51  \note I don't have much in ram at the moment.
52  this may be moved to PROGMEM eventually
53  \warning I don't remember exactly how I did this...
54 */
55 
56 const uint16_t levelarray[101] =
57 {
58  65535,57934,55907,54462,53297,52300,51419,50621,49889,49208,
59  48568,47964,47390,46841,46314,45807,45317,44842,44381,43933,
60  43495,43068,42650,42240,41838,41443,41055,40672,40295,39923,
61  39556,39193,38834,38479,38127,37778,37432,37089,36748,36409,
62  36072,35737,35403,35071,34740,34410,34080,33752,33424,33096,
63  32768,32440,32112,31784,31456,31126,30796,30465,30133,29799,
64  29464,29127,28788,28447,28104,27758,27409,27057,26702,26343,
65  25980,25613,25241,24864,24481,24093,23698,23296,22886,22468,
66  22041,21603,21155,20694,20219,19729,19222,18695,18146,17572,
67  16968,16328,15647,14915,14117,13236,12239,11074,9629,7602,
68  00000
69 };
70 
71 struct DimmerClock;
72 
73 /*! \brief A struct representing a controllable power output
74 
75  For now, its either one of the lights or the receptacle.
76  The combination of portreg and portbit represents the
77  output pin that this dimmer controls (for example,
78  portreg = &PORTG and portbit = 1 means this
79  DimmerClock controls the output on PG1.
80  */
81 struct PowerUnit
82 {
83  /*! \brief A numeric ID for this unit */
84  uint8_t id;
85 
86  /*< \brief The current state of the PowerUnit */
87  uint8_t state;
88 
89  /*! \brief Pointer to the register that this PowerUnit uses */
90  volatile uint8_t * portreg;
91 
92  /*! \brief The bit on the portreg register that the PowerUnit controls */
93  uint8_t portbit;
94 
95  /*! \brief Pointer to the dimmer that this is connected to */
96  volatile struct DimmerClock * dimmer;
97 };
98 
99 
100 /*! \brief A struct for a clock used for dimming a PowerUnit
101 
102  The combination of interrupt and interrupt represents the
103  timer interrupt register and bit to be set/cleared when
104  dimming is started or stopped. This prevents unecessary
105  interrupts when thing are not being dimmed.
106  The compare register pointed to by comparereg
107  is set to the appropriate time that represents
108  when to turn on/pulse the triac.
109 */
111 {
112  /*! \brief The current level of the dimmer (0-100) */
113  uint8_t level;
114 
115  /*! \brief The timer interrupt register */
116  volatile uint8_t * interruptreg;
117 
118  /*! \brief The bit on the timer interrupt register */
119  uint8_t interruptbit;
120 
121  /*! \brief Time timer compare register to use */
122  volatile uint16_t * comparereg;
123 
124  /*! \brief The currently-connected PowerUnit, if any */
125  volatile struct PowerUnit * pu;
126 };
127 
128 
129 /* \brief The PowerUnits used by this microcontroller */
130 volatile struct PowerUnit punits[PU_COUNT];
131 
132 /* \brief The DimmerClocks used by this microcontroller */
134 
135 
136 /* \brief Count of the number of zero-crossings */
137 /* volatile uint16_t zerocrosscount; */
138 
139 /*! \brief The times from the zero-crossing timer represing the
140  falling edge [0] and the rising edge [1] */
141 volatile uint16_t zerocrossstamp[2];
142 
143 /*! \brief A buffer for receiving input from the serial port */
144 volatile uint8_t serbuffer[BUFSIZE];
145 
146 /*! \brief Index to be read next in the input buffer (serbuffer) */
147 volatile uint8_t curRead;
148 
149 /*! \brief Index to be written next in the input buffer (serbuffer) */
150 volatile uint8_t curWrite;
151 
152 
153 /*! \brief Read the next entry in the input buffer
154 
155  This takes care of wrapping around the end of the buffer
156 */
157 uint8_t ReadNextBuff(void)
158 {
159  char c;
160 
161  while(curRead == curWrite);
162 
163  c = serbuffer[curRead++];
164 
165  if(curRead >= BUFSIZE)
166  curRead = 0;
167 
168  return c;
169 }
170 
171 /*! \brief Write the next entry in the input buffer
172 
173  This takes care of wrapping around the end of the buffer
174 */
175 void WriteNextBuff(const uint8_t c)
176 {
177  serbuffer[curWrite++] = c;
178  if(curWrite >= BUFSIZE)
179  curWrite = 0;
180 }
181 
182 
183 /*! \brief Start dimming a power unit at the given level
184 
185  Finds the next available dimmer. If no dimmer is available, it
186  returns RES_NODIMMER. Otherwise, returns RES_SUCCESS.
187  \note pu->dimmer should be checked for NULL prior
188  to calling this
189 */
190 uint8_t StartDimming(volatile struct PowerUnit * pu, uint8_t level)
191 {
192  volatile struct DimmerClock * dim = NULL;
193  uint8_t dindex;
194 
195  /* find the next available dimmer */
196  for(dindex = 0; dindex < DIMMER_COUNT; dindex++)
197  {
198  if(dimclocks[dindex].pu == NULL)
199  {
200  dim = &(dimclocks[dindex]);
201  break;
202  }
203  }
204 
205  if(dim == NULL)
206  return RES_NODIMMER; /* No available dimmers */
207 
208  dim->pu = pu;
209 
210  dim->level = level;
211 
212  /* NOT using PROGMEM */
213  *(dim->comparereg) = (((uint32_t)levelarray[level] * (uint32_t)ONETWENTYHERTZ) >> 16);
214 
215  /* If using PROGMEM */
216  /**(dim->comparereg) = (((uint32_t)pgm_read_word(&levelarray[level]) * (uint32_t)ONETWENTYHERTZ) >> 16);*/
217 
218  /* Enable the timer interrupt */
219  bit_set(*(dim->interruptreg), dim->interruptbit);
220 
221  pu->dimmer = dim;
222 
223  pu->state = PUSTATE_DIM;
224 
225  return RES_SUCCESS;
226 }
227 
228 /*! \brief Stops dimming on a given PowerUnit
229 
230  \note This does not turn off the pin - it may be left on!
231 */
232 void StopDimming(volatile struct PowerUnit* pu)
233 {
234  /* Turn off the timer interrupt */
236  pu->dimmer->level = 0;
237 
238  /* turn off the interrupts before doing this! */
239  pu->dimmer->pu = NULL;
240  pu->dimmer = NULL;
241 }
242 
243 
244 /*! \brief Completely turns on a PowerUnit
245 
246  This will force the output on the IO pin to be set continuously to 1
247 */
248 void TurnOn(volatile struct PowerUnit* pu)
249 {
250  /* Is it currently being dimmed? */
251  if(pu->dimmer == NULL) /* Nope, not being dimmed */
252  {
253  bit_set(*(pu->portreg), pu->portbit);
254  pu->state = PUSTATE_ON;
255  }
256  else /* It is being dimmed */
257  {
258  /* Stop dimming, turn on completely */
259  /**(pu->dimmer->comparereg) = 1000; */ /* may prevent flashes */
260  StopDimming(pu);
261  bit_set(*(pu->portreg), pu->portbit);
262  pu->state = PUSTATE_ON;
263  }
264 }
265 
266 
267 /*! \brief Completely turns off a PowerUnit
268 
269  This will force the output on the IO pin to be set continuously to 0
270 */
271 void TurnOff(volatile struct PowerUnit* pu)
272 {
273  /* Is it currently being dimmed? */
274  if(pu->dimmer == NULL) /* Nope, not being dimmed */
275  {
276  bit_clear(*(pu->portreg), pu->portbit);
277  pu->state = PUSTATE_OFF;
278  }
279  else /* It is being dimmed */
280  {
281  /* Stop dimming, turn off completely */
282  /**(pu->dimmer->comparereg) = 65535; */ /* may prevent flashes */
283  StopDimming(pu);
284  bit_clear(*(pu->portreg), pu->portbit);
285  pu->state = PUSTATE_OFF;
286  }
287 }
288 
289 
290 /*! \brief Change the dimming level of a PowerUnit
291 
292  \bug Sometimes the light will flash momentarily. This
293  is possibly due to some timing issues when changing
294  the level while the timer is still running (ie there
295  is a timer interrupt at an inopportune time, or
296  the compare value changes at a bad time)
297 */
298 uint8_t Level(volatile struct PowerUnit* pu, uint8_t level)
299 {
300  uint8_t ret = RES_SUCCESS;
301  uint16_t newreg = 0;
302 
303  if(level >= 100)
304  TurnOn(pu);
305  else if(level == 0)
306  TurnOff(pu);
307  else
308  {
309  if(pu->dimmer == NULL)
310  ret = StartDimming(pu, level);
311  else
312  {
313  /* see documentation for levelarray */
314  newreg = (((uint32_t)levelarray[level] * (uint32_t)ONETWENTYHERTZ) >> 16);
315 
316  /* may prevent some flashes due to errors in timing ? */
317  if(level > pu->dimmer->level && ICR4 >= newreg)
318  {
319 
320  bit_set(PORTB,7);
321  bit_set(*(pu->portreg), pu->portbit);
322  _delay_us(PULSE_WIDTH);
323  bit_clear(*(pu->portreg), pu->portbit);
324  }
325 
326  pu->dimmer->level = level;
327  *(pu->dimmer->comparereg) = newreg;
328  }
329  }
330 
331  return ret;
332 }
333 
334 
335 /*! \brief Process a command stored in the buffer
336 
337  The command is given by the only parameter, and any
338  further information is obtained directly from the buffer
339 
340  This function also returns the appropriate response through the
341  serial port
342 */
343 uint8_t ProcessCommand(uint8_t command)
344 {
345  uint8_t ret = RES_SUCCESS;
346  uint8_t id = 0;
347  uint8_t level = 0;
348  uint8_t i;
349  uint8_t counter = 0;
350  uint8_t info[3+INFO_SIZE];
351 
352  switch (command)
353  {
354  case COM_NOTHING:
355  break;
356 
357  case COM_LEVEL:
358  id = ReadNextBuff();
359  level = ReadNextBuff();
360  if(id > PU_COUNT || id == 0)
361  ret = RES_INVALID_ID;
362  else
363  ret = Level(&punits[id-1], level);
364 
365  Serial_send3(ret, command, id);
366  break;
367 
368  case COM_ON:
369  id = ReadNextBuff();
370  if(id > PU_COUNT || id == 0)
371  ret = RES_INVALID_ID;
372  else
373  ret = Level(&punits[id-1], 100);
374 
375  Serial_send3(ret, command, id);
376  break;
377 
378  case COM_OFF:
379  id = ReadNextBuff();
380  if(id > PU_COUNT || id == 0)
381  ret = RES_INVALID_ID;
382  else
383  ret = Level(&punits[id-1], 0);
384 
385  Serial_send3(ret, command, id);
386  break;
387 
388  case COM_INFO:
389  info[0] = RES_SUCCESS;
390  info[1] = COM_INFO;
391  info[2] = 0;
392  info[3] = zerocrossstamp[0]; /* low part */
393  info[4] = (zerocrossstamp[0] >> 8); /* high part */
394  info[5] = zerocrossstamp[1]; /* low part */
395  info[6] = (zerocrossstamp[1] >> 8); /* high part */
396  counter = 7;
397  for(i = 0; i < DIMMER_COUNT; i++)
398  {
399  if(dimclocks[i].pu == NULL)
400  {
401  info[counter++] = 0;
402  info[counter++] = 0;
403  info[counter++] = 0;
404  info[counter++] = 0;
405  }
406  else
407  {
408  info[counter++] = dimclocks[i].pu->id;
409  info[counter++] = dimclocks[i].level;
410  info[counter++] = *(dimclocks[i].comparereg);
411  info[counter++] = (*(dimclocks[i].comparereg) >> 8);
412  }
413  }
414 
415  for(i = 0; i < PU_COUNT; i++)
416  {
417  info[counter++] = punits[i].id;
418  info[counter++] = punits[i].state;
419 
420  if(punits[i].dimmer == NULL)
421  info[counter++] = 0;
422  else
423  info[counter++] = punits[i].dimmer->level;
424  }
425 
426  Serial_sendarr(info, INFO_SIZE+3);
427  break;
428  default:
429  ret = RES_INVALID_COM;
430  Serial_send3(ret, command, id);
431  break;
432  }
433 
434  return ret;
435 }
436 
437 
438 /*! \brief Creates a new dimmer clock object
439 
440  For a description of the parameters, see
441  the details for the DimmerClock struct
442 */
443 void NewDimmerClock(volatile struct DimmerClock * dim,
444  volatile uint8_t * interruptreg,
445  uint8_t interruptbit,
446  volatile uint16_t * comparereg)
447 {
448  dim->interruptreg = interruptreg;
449  dim->interruptbit = interruptbit;
450  dim->comparereg = comparereg;
451  dim->pu = NULL;
452  dim->level = 0;
453 }
454 
455 /*! \brief Creates a new PowerUnit object
456 
457  For a description of the parameters, see
458  the details for the PoerUnit struct
459 */
460 void NewPowerUnit(volatile struct PowerUnit * pu,
461  uint8_t id,
462  volatile uint8_t * portreg,
463  uint8_t portbit)
464 {
465  pu->id = id;
466  pu->state = PUSTATE_OFF;
467  pu->portreg = portreg;
468  pu->portbit = portbit;
469  pu->dimmer = NULL;
470 }
471 
472 
473 /*! \brief Main loop of the microcontroller
474 
475  This loop sets up the power units and dimmer
476  clocks, initializes the serial port, as well
477  as sets up the input pin and interrupt for
478  the zero-crossing timer. It also initializes the
479  command buffer and sends an identification
480  string to the PC program.
481 
482  After that, it enters a loop, waiting for
483  commands to be written to the command buffer (serbuffer)
484  and executes ProcessCommand() if necessary.
485 */
486 int main(void)
487 {
488  uint8_t c;
489 
490  NewPowerUnit(&punits[0], PU_LIGHT1, &PORTG, 0);
491  NewPowerUnit(&punits[1], PU_LIGHT2, &PORTG, 1);
492  NewPowerUnit(&punits[2], PU_RECEPTACLE, &PORTG, 2);
493 
494  NewDimmerClock(&dimclocks[0], &TIMSK1, OCIE1A, &OCR1A);
495  NewDimmerClock(&dimclocks[1], &TIMSK1, OCIE1B, &OCR1B);
496  NewDimmerClock(&dimclocks[2], &TIMSK1, OCIE1C, &OCR1C);
497  NewDimmerClock(&dimclocks[3], &TIMSK3, OCIE3A, &OCR3A);
498  NewDimmerClock(&dimclocks[4], &TIMSK3, OCIE3B, &OCR3B);
499  NewDimmerClock(&dimclocks[5], &TIMSK3, OCIE3C, &OCR3C);
500 
501  /* Initialize */
502  curRead = curWrite = 0;
503  /* zerocrosscount = 0; */
504 
505  /* Initialize the serial port */
506  Serial_init();
507 
508  /* For the internal LED */
509  bit_set(DDRB, 7);
510 
511  /* Outputs to the triacs */
512  /* Light 1 -> Arduino pin 41 */
513  /* Light 2 -> Arduino pin 40 */
514  /* Receptacle -> Arduino pin 39 */
515  bit_set(DDRG, 0);
516  bit_set(DDRG, 1);
517  bit_set(DDRG, 2);
518 
519 
520  /****************************************/
521  /* Zero crossing detector/input capture */
522  /****************************************/
523  /* Input capture ICP4/PL0 (Arduino pin 49) */
524  /* Noise canceller */
525  bit_set(TCCR4B, ICNC4);
526  /* Input capture interrupt */
527  bit_set(TIMSK4, ICIE4);
528  /* Internal pull-up resistor */
529  /* Already configured as input by default */
530  bit_set(PORTL, 0);
531 
532 
533  /****************************************/
534  /* Zero crossing Timer setup */
535  /****************************************/
536  /* Timer 4 is used as the input capture */
537  /* and is being run in normal mode, also at fcpu8 */
538  /* Both: Maximum (TOP) should be about 32.7ms */
539  bit_set(TCCR4B, CS41);
540 
541  /****************************************/
542  /* Dimmer Timer setup */
543  /****************************************/
544  /* Timers 1&3 are the phase shift delay */
545  /* Run in CTC mode at fcpu/8 */
546  /* Interrupts initially off, levels at 0 */
547  /* The max value is set to the zero crossing time */
548  bit_set(TCCR1B, WGM13);
549  bit_set(TCCR1B, WGM12);
550  ICR1 = ONETWENTYHERTZ;
551  bit_set(TCCR1B, CS11);
552 
553  bit_set(TCCR3B, WGM33);
554  bit_set(TCCR3B, WGM32);
555  ICR3 = ONETWENTYHERTZ;
556  bit_set(TCCR3B, CS31);
557 
558  /* sleep_enable(); */
559 
560  /* Enable global interrupts */
561  sei();
562 
563  /* Send the identification string */
564  Serial_send6(0,0,0,'B','e','n');
565 
566  while(1)
567  {
568  /* sleep_cpu(); */
569 
570  /*while(Serial_needsreading())
571  WriteNextBuff(Serial_receive());*/
572 
573  /*if(bit_get(UCSR0A, DOR0) || bit_get(UCSR0A, FE0))
574  bit_clear(PORTB, 7);*/
575 
576  /*if(zerocrosscount >= 120)
577  {
578  bit_flip(PORTB,7);
579  zerocrosscount =0;
580  }*/
581 
582  /* Check for new commands & process them */
583  if(curRead != curWrite)
584  {
585  c = ReadNextBuff();
586  if( c == '\\')
587  {
588  if(ProcessCommand((uint8_t) ReadNextBuff()) != RES_SUCCESS)
589  {
590  Serial_flush();
591  curRead = curWrite = 0;
592  }
593  }
594  else
595  {
597  Serial_flush();
598  curRead = curWrite = 0;
599  }
600  }
601  }
602 
603  return 0;
604 }
605 
606 
607 
608 /*! \brief Interrupt routine for zero-cross
609 
610  This gets run on a rising or falling edge (dependong on
611  register TCCR4B, bit ICES4). It records the timestamp
612  in zerocrossstamp.
613 
614  After detecting a rising edge, it adjusts the counters for the dimmer clocks
615  to what would be expected so that they remain in sync.
616 */
617 ISR(TIMER4_CAPT_vect)
618 {
619  /* Adjust the dimmer timers' counters continuously
620  Do this only on the rising edge. We
621  can calculate what the other counter should be at this point
622  TCNT3 = ((zerocrossstamp[0] + zerocrossstamp[1])/2 - ICR4)/2 + ICR4
623  ...some algebra...
624  TCNT3 = (zerocrossstamp[0] + zerocrossstamp[1] + (2*ICR4))/4;
625  ...then use bit shifts rather than multiplication
626  */
627  if(bit_get(TCCR4B, ICES4))
628  {
629  TCNT1 = TCNT3 = ((zerocrossstamp[0] + zerocrossstamp[1] + (ICR4<<1))>>2);
630  }
631 
632  /* Store the timestamp */
633 
634  /* I don't know why the following line doesn't work */
635  /*zerocrossstamp[bit_get(TCCR4B, ICES4)] = ICR4; */
636  if(bit_get(TCCR4B, ICES4))
637  zerocrossstamp[1] = ICR4;
638  else
639  zerocrossstamp[0] = ICR4;
640 
641  /* Flip the input capture edge detector */
642  bit_flip(TCCR4B, ICES4);
643 
644  /* Increment the counter */
645  /*zerocrosscount++;*/
646 
647  /* Zero the timer counter */
648  TCNT4 = 0;
649 
650 }
651 
652 /************************************
653  * INTERRUPTS
654  ************************************/
655 
656 /*! \brief Timer interrupt for phase-shifting
657 
658  Gets run when the pulse must be sent to the triac
659  to turn on the circuit. The value at which this gets
660  called is set with Level()
661 */
662 ISR(TIMER1_COMPA_vect)
663 {
665  _delay_us(PULSE_WIDTH);
667 }
668 
669 /*! \copydoc ISR(TIMER1_COMPA_vect) */
670 ISR(TIMER1_COMPB_vect)
671 {
673  _delay_us(PULSE_WIDTH);
675 }
676 
677 /*! \copydoc ISR(TIMER1_COMPA_vect) */
678 ISR(TIMER1_COMPC_vect)
679 {
681  _delay_us(PULSE_WIDTH);
683 }
684 
685 /*! \copydoc ISR(TIMER1_COMPA_vect) */
686 ISR(TIMER3_COMPA_vect)
687 {
689  _delay_us(PULSE_WIDTH);
691 }
692 
693 /*! \copydoc ISR(TIMER1_COMPA_vect) */
694 ISR(TIMER3_COMPB_vect)
695 {
697  _delay_us(PULSE_WIDTH);
699 }
700 
701 /*! \copydoc ISR(TIMER1_COMPA_vect) */
702 ISR(TIMER3_COMPC_vect)
703 {
705  _delay_us(PULSE_WIDTH);
707 }
708 
709 /* \brief Interrupt routine for receiving commands through the serial port
710 
711  If data is received from the serial port, this function is called, which
712  just writes it to the command buffer (serbuffer)
713 */
714 ISR(USART0_RX_vect)
715 {
717 }
718