LEDComm  1.0
ledcomm.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2013, 2014 Kevin L. Pauba
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without modification,
6  * are permitted provided that the following conditions are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright notice,
9  * this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright notice,
11  * this list of conditions and the following disclaimer in the documentation
12  * and/or other materials provided with the distribution.
13  * 3. The name of the author may not be used to endorse or promote products
14  * derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
19  * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
21  * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
24  * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
25  * OF SUCH DAMAGE.
26  *
27  * This file is part of the LEDComm Driver for ChibiOS/RT.
28  *
29  * Author: Kevin L. Pauba <klpauba@gmail.com>
30  *
31  */
32 
33 /**
34  * @file ledcomm.c
35  * @brief LEDComm Driver code (see http://mecrisp.sourceforge.net/ledcomm.htm)
36  *
37  * @addtogroup LEDComm
38  * @{
39  */
40 
41 #include "ch.h"
42 #include "hal.h"
43 
44 #include "ledcomm.h"
45 #include "led.h"
46 
47 /*===========================================================================*/
48 /* Driver local definitions. */
49 /*===========================================================================*/
50 #if !CH_USE_QUEUES
51 #error "LEDComm Driver requires CH_USE_QUEUES"
52 #endif
53 
54 #if LEDCOMM_USE_LCOM1 || defined(__DOXYGEN__)
55 LEDCommDriver_t LCOM1;
56 #endif
57 #if LEDCOMM_USE_LCOM2 || defined(__DOXYGEN__)
58 LEDCommDriver_t LCOM2;
59 #endif
60 
61 #define LED_SYNC_COUNT 18 /* Number of MARKS required to mark link as up */
62 
63 /*===========================================================================*/
64 /* Driver exported variables. */
65 /*===========================================================================*/
66 
67 /*===========================================================================*/
68 /* Driver local variables and types. */
69 /*===========================================================================*/
70 
71 /*===========================================================================*/
72 /* Driver local functions. */
73 /*===========================================================================*/
74 
75 /*
76  * Interface implementation, the following functions just invoke the equivalent
77  * queue-level function or macro.
78  */
79 
80 static size_t write(void *ip, const uint8_t *bp, size_t n) {
81 
82  return chOQWriteTimeout(&((LEDCommDriver_t *)ip)->oqueue, bp,
83  n, TIME_INFINITE);
84 }
85 
86 static size_t read(void *ip, uint8_t *bp, size_t n) {
87 
88  return chIQReadTimeout(&((LEDCommDriver_t *)ip)->iqueue, bp,
89  n, TIME_INFINITE);
90 }
91 
92 static msg_t put(void *ip, uint8_t b) {
93 
94  return chOQPutTimeout(&((LEDCommDriver_t *)ip)->oqueue, b, TIME_INFINITE);
95 }
96 
97 static msg_t get(void *ip) {
98 
99  return chIQGetTimeout(&((LEDCommDriver_t *)ip)->iqueue, TIME_INFINITE);
100 }
101 
102 static msg_t putt(void *ip, uint8_t b, systime_t timeout) {
103 
104  return chOQPutTimeout(&((LEDCommDriver_t *)ip)->oqueue, b, timeout);
105 }
106 
107 static msg_t gett(void *ip, systime_t timeout) {
108 
109  return chIQGetTimeout(&((LEDCommDriver_t *)ip)->iqueue, timeout);
110 }
111 
112 static size_t writet(void *ip, const uint8_t *bp, size_t n, systime_t time) {
113 
114  return chOQWriteTimeout(&((LEDCommDriver_t *)ip)->oqueue, bp, n, time);
115 }
116 
117 static size_t readt(void *ip, uint8_t *bp, size_t n, systime_t time) {
118 
119  return chIQReadTimeout(&((LEDCommDriver_t *)ip)->iqueue, bp, n, time);
120 }
121 
122 static const struct LEDCommDriverVMT vmt = {
123  write, read, put, get,
124  putt, gett, writet, readt
125 };
126 
127 inline bool_t isLinkUp(LEDCommDriver_t *l) {
128  return ledCommPollLinkStatus(l);
129 }
130 
131 inline bool_t isLinkDown(LEDCommDriver_t *l) {
132  return !ledCommPollLinkStatus(l);
133 }
134 
135 inline void linkUp(LEDCommDriver_t *l) {
136  l->link = 1;
137  l->syncs = LEDCOMM_DEFAULT_SYNCS;
138  l->txrdy = 1;
139 #if CH_USE_EVENTS
140 #if LEDCOMM_THREADED
141  chSysLock();
142 #else
143  chSysLockFromIsr();
144 #endif
145  chIQResetI(&(l->iqueue));
146  chOQResetI(&(l->oqueue));
147  chnAddFlagsI(l, CHN_CONNECTED);
148 #if LEDCOMM_THREADED
149  chSysUnlock();
150 #else
151  chSysUnlockFromIsr();
152 #endif
153 #endif /* CH_USE_EVENTS */
154 }
155 
156 inline void linkDown(LEDCommDriver_t *l) {
157  l->link = 0;
158 #if CH_USE_EVENTS
159 #if LEDCOMM_THREADED
160  chSysLock();
161 #else
162  chSysLockFromIsr();
163 #endif
164  chnAddFlagsI(l, CHN_DISCONNECTED);
165 #if LEDCOMM_THREADED
166  chSysUnlock();
167 #else
168  chSysUnlockFromIsr();
169 #endif
170 #endif /* CH_USE_EVENTS */
171 }
172 
173 /*
174  * The default configuration (used if no other is provided).
175  */
176 static LEDCommConfig_t default_config = {
177  .anode_port = GPIOB, .anode_pad = 5,
178  .cathode_port = GPIOB, .cathode_pad = 4,
179  .cathode_extmode = (EXT_CH_MODE_FALLING_EDGE | EXT_MODE_GPIOB), /* This is kinda specific to STM32 parts */
180  .data_bits = LEDCOMM_DEFAULT_DATA_BITS,
181 };
182 
183 void
184 ledCommSerialHandler(LEDCommDriver_t *ldp)
185 {
186  if (ldp->enable == 0) {
187  return;
188  }
189 
190  ldp->count += 1;
191 
192  if (ldp->count == 1) {
193  /* Start shining. */
194  extChannelDisableI(&EXTD_LEDComm, ldp->cathode_pad); /* prevent more interrupts */
195  palSetPadMode(ldp->cathode_port, ldp->cathode_pad, PAL_MODE_OUTPUT_PUSHPULL | PAL_STM32_OSPEED_LOWEST);
196  ledCommOn(ldp);
197  } else if (ldp->count == 2) {
198  if (ldp->txrdy == 1) {
199  msg_t b;
200 
201  /* Get the next character to transmit. */
202 #if LEDCOMM_THREADED
203  chSysLock();
204 #else
205  chSysLockFromIsr();
206 #endif
207  b = ldRequestDataI(ldp);
208 #if LEDCOMM_THREADED
209  chSysUnlock();
210 #else
211  chSysUnlockFromIsr();
212 #endif
213 
214  if (b < Q_OK) {
215  /* Nothing in output queue. */
216  ldp->tx_char = 0;
217  ldp->txbit = MARK; /* Send the IDLE pattern */
218  } else {
219  ldp->tx_char = (uint8_t)b;
220  ldp->txrdy = 0;
221  }
222  ldp->tx_bits = (ldp->data_bits == LEDCOMM_DATA_BITS7 ? 7 : 8);
223  }
224 
225  } else if (ldp->count == 3) {
226  /* decide whether to transmit a MARK, SPACE or STOP bit. */
227  if (isLinkDown(ldp) || ldp->txrdy == 1) {
228  /* there is no link or there is nothing to transmit -- we're IDLE, send a MARK */
229  ldp->txbit = MARK; /* Send the IDLE pattern */
230  } else if (ldp->tx_bits == 0) {
231  ldp->txbit = STOP; /* Send the STOP bit pattern */
232  } else {
233  if (ldp->tx_char & (1 << (ldp->tx_bits - 1))) {
234  ldp->txbit = SPACE; /* Send the "1" bit pattern */
235  } else {
236  ldp->txbit = MARK; /* Send the "0" bit mattern */
237  }
238  ldp->tx_bits = ldp->tx_bits - 1;
239  }
240  } else if (ldp->count >= 4 && ldp->count <= 13) {
241  if ((ldp->count == 5 && ldp->txbit == SPACE) ||
242  (ldp->count == 9 && ldp->txbit == MARK) ||
243  (ldp->count == 13 && ldp->txbit == STOP)) {
244  /* we've transmitted 4 (or 8 or 12) shines: stop shining and start detecting received shines. */
245  ldp->count = 13; /* start detecting shines */
246  if (ldp->txbit == STOP) {
247  ldp->txrdy = 1;
248  }
249  ledCommOff(ldp);
250  ledCommPrepare(ldp);
251  }
252  } else if (ldp->count >= 14 && ldp->count < 46) { /* DETECT SHINES */
253  /* Keep detecting received shines */
254  ledCommDetect(ldp);
255  ledCommPrepare(ldp);
256  } else {
257  ledCommDetect(ldp);
258  if (ldp->count != 0) { /* ledCommDetect might have changed ldp->count */
259  /*
260  * There was not a 11100 sequence (otherwise,
261  * the counter would be set to zero in
262  * LEDCommDetect). We've lost the link.
263  */
264  ldp->count = 0; /* Maybe set this value to
265  close its maximum value
266  (65,535; with a little
267  randomness) allowing it to
268  roll over to zero. This
269  will have the effect to
270  dither the start to help
271  prevent occasional
272  sychronization with the
273  other LED? Note that the
274  logic will have to change a
275  little for this
276  behavior. */
277  if (isLinkUp(ldp)) {
278  linkDown(ldp);
279  }
280  }
281  }
282 }
283 
284 
285 /*===========================================================================*/
286 /* Driver exported functions. */
287 /*===========================================================================*/
288 
289 /**
290  * @brief LEDComm Driver initialization.
291  * @note This function is <B>NOT</B> implicitly invoked by @p halInit(), you must
292  * explicitly initialize the driver.
293  *
294  * @init
295  */
296 void ldInit(void) {
297 
298 #if LEDCOMM_USE_LCOM1
299  ldObjectInit(&LCOM1, NULL, NULL);
300 #endif
301 
302 #if LEDCOMM_USE_LCOM2
303  ldObjectInit(&LCOM2, NULL, NULL);
304 #endif
305 }
306 
307 /**
308  * @brief Initializes a generic full duplex driver object.
309  * @details The HW dependent part of the initialization has to be performed
310  * outside, usually in the hardware initialization code.
311  *
312  * @param[out] ldp pointer to a @p LEDCommDriver structure
313  * @param[in] inotify pointer to a callback function that is invoked when
314  * some data is read from the Queue. The value can be
315  * @p NULL.
316  * @param[in] onotify pointer to a callback function that is invoked when
317  * some data is written in the Queue. The value can be
318  * @p NULL.
319  *
320  * @init
321  */
322 void ldObjectInit(LEDCommDriver_t *ldp, qnotify_t inotify, qnotify_t onotify) {
323 
324  ldp->vmt = &vmt;
325  chEvtInit(&ldp->event);
326  ldp->state = LD_STOP;
327  chIQInit(&ldp->iqueue, ldp->ib, LEDCOMM_BUFFERS_SIZE, inotify, ldp);
328  chOQInit(&ldp->oqueue, ldp->ob, LEDCOMM_BUFFERS_SIZE, onotify, ldp);
329 }
330 
331 /**
332  * @brief Configures and starts the driver.
333  *
334  * @param[in] ldp pointer to a @p LEDCommDriver object
335  * @param[in] config the architecture-dependent LEDComm driver configuration.
336  * If this parameter is set to @p NULL then a default
337  * configuration is used.
338  *
339  * @api
340  */
341 void ldStart(LEDCommDriver_t *ldp, const LEDCommConfig_t *config) {
342 #if !LEDCOMM_THREADED
343  extern GPTConfig GPTC_LEDComm;
344 #endif
345  extern EXTConfig extcfg;
346  extern WORKING_AREA(waLEDCommThread, LEDCOMM_THREAD_STACK_SIZE);
347 
348  chDbgCheck(ldp != NULL, "ldStart");
349 
350  chSysLock();
351  chDbgAssert((ldp->state == LD_STOP) || (ldp->state == LD_READY),
352  "ldStart(), #1",
353  "invalid state");
354 
355  if (config == NULL) {
356  config = &default_config;
357  }
358 
359  ldp->anode_port = config->anode_port;
360  ldp->anode_pad = config->anode_pad;
361  ldp->cathode_port = config->cathode_port;
362  ldp->cathode_pad = config->cathode_pad;
363  ldp->threshold = config->threshold;
364  ldp->syncs = LEDCOMM_DEFAULT_SYNCS;
365  ldp->data_bits = config->data_bits;
366 
367  ledCommInitPad(ldp);
368 
369  extcfg.channels[ldp->cathode_pad].mode = config->cathode_extmode;
370  extcfg.channels[ldp->cathode_pad].cb = (extcallback_t)extcb1;
371 
372  if (ldp->state == LD_STOP) {
373  ldp->enable = 1;
374  ldp->link = 0;
375  ldp->txrdy = 1;
376  ldp->rxrdy = 0;
377  ldp->txbit = MARK;
378  ldp->tx_bits = (ldp->data_bits == LEDCOMM_DATA_BITS7 ? 7 : 8);
379  ldp->tx_char = 0;
380  ldp->rx_char = 0;
381  ldp->rx_bits = 0;
382  ldp->rx_register = 0;
383  ldp->c = 0;
384  ldp->count = 0;
385  }
386 
387 #if LEDCOMM_THREADED
388  chSysUnlock();
389  chThdCreateStatic(waLEDCommThread, sizeof(waLEDCommThread), HIGHPRIO, LEDCommThread, NULL);
390  chSysLock();
391 #else
392  chSysUnlock();
393  extStart(&EXTD_LEDComm, &extcfg);
394 
395  gptObjectInit(&GPTD_LEDComm);
396  gptStart(&GPTD_LEDComm, &GPTC_LEDComm);
397  gptStartContinuous(&GPTD_LEDComm, 2);
398  chSysLock();
399 #endif
400  ldp->state = LD_READY;
401  chSysUnlock();
402 }
403 
404 /**
405  * @brief Stops the driver.
406  * @details Any thread waiting on the driver's queues will be awakened with
407  * the message @p Q_RESET.
408  *
409  * @param[in] ldp pointer to a @p LEDCommDriver object
410  *
411  * @api
412  */
414 
415  chDbgCheck(ldp != NULL, "ldStop");
416 
417  chSysLock();
418  chDbgAssert((ldp->state == LD_STOP) || (ldp->state == LD_READY),
419  "ldStop(), #1",
420  "invalid state");
421  ldp->state = LD_STOP;
422  chOQResetI(&ldp->oqueue);
423  chIQResetI(&ldp->iqueue);
424  chSchRescheduleS();
425  chSysUnlock();
426  extStop(&EXTD_LEDComm);
427 }
428 
429 /**
430  * @brief Handles incoming data.
431  * @details This function must be called from the input interrupt service
432  * routine in order to enqueue incoming data and generate the
433  * related events.
434  * @note The incoming data event is only generated when the input queue
435  * becomes non-empty.
436  * @note In order to gain some performance it is suggested to not use
437  * this function directly but copy this code directly into the
438  * interrupt service routine.
439  *
440  * @param[in] ldp pointer to a @p LEDCommDriver structure
441  * @param[in] b the byte to be written in the driver's Input Queue
442  *
443  * @iclass
444  */
445 void ldIncomingDataI(LEDCommDriver_t *ldp, uint8_t b) {
446 
447  chDbgCheckClassI();
448  chDbgCheck(ldp != NULL, "ldIncomingDataI");
449 
450 #if CH_USE_EVENTS
451  if (chIQIsEmptyI(&ldp->iqueue))
452  chnAddFlagsI(ldp, CHN_INPUT_AVAILABLE);
453 #endif
454 
455  if (chIQPutI(&ldp->iqueue, b) < Q_OK)
456  chnAddFlagsI(ldp, LD_OVERRUN_ERROR);
457 }
458 
459 /**
460  * @brief Handles outgoing data.
461  * @details Must be called from the output interrupt service routine in order
462  * to get the next byte to be transmitted.
463  * @note In order to gain some performance it is suggested to not use
464  * this function directly but copy this code directly into the
465  * interrupt service routine.
466  *
467  * @param[in] ldp pointer to a @p LEDCommDriver structure
468  * @return The byte value read from the driver's output queue.
469  * @retval Q_EMPTY if the queue is empty (the lower driver usually
470  * disables the interrupt source when this happens).
471  *
472  * @iclass
473  */
475  msg_t b;
476 
477  chDbgCheckClassI();
478  chDbgCheck(ldp != NULL, "ldRequestDataI");
479 
480  b = chOQGetI(&ldp->oqueue);
481 #if CH_USE_EVENTS
482  if (b < Q_OK)
483  chnAddFlagsI(ldp, CHN_OUTPUT_EMPTY);
484 #endif
485  return b;
486 }
487 
488 /** @} */
void ldStart(LEDCommDriver_t *ldp, const LEDCommConfig_t *config)
Configures and starts the driver.
Definition: ledcomm.c:341
ioportid_t cathode_port
The port where the LED cathode is connected.
Definition: ledcomm.h:169
ioportid_t anode_port
The port where the LED anode is connected.
Definition: ledcomm.h:167
#define LD_OVERRUN_ERROR
Overflow happened.
Definition: ledcomm.h:56
uint16_t threshold
The highest number of elapsed HAL ticks that represents a 'shine' – try LEDCOMM_DEFAULT_THRESHOLD (3...
Definition: ledcomm.h:172
Ready.
Definition: ledcomm.h:189
uint32_t cathode_extmode
The processor-specific EXT mode used to configure the extcfg table.
Definition: ledcomm.h:171
void ldStop(LEDCommDriver_t *ldp)
Stops the driver.
Definition: ledcomm.c:413
uint8_t data_bits
LEDCOMM_DATA_BITS8 or LEDCOMM_DATA_BITS7
Definition: ledcomm.h:175
LEDCommDriver virtual methods table.
Definition: ledcomm.h:208
LEDComm Driver macros and structures.
void ldInit(void)
LEDComm Driver initialization.
Definition: ledcomm.c:296
LEDComm Driver configuration structure.
Definition: ledcomm.h:166
msg_t ldRequestDataI(LEDCommDriver_t *ldp)
Handles outgoing data.
Definition: ledcomm.c:474
#define LEDCOMM_BUFFERS_SIZE
The size of the input and output queues.
Definition: ledcommconf.h:92
void ldObjectInit(LEDCommDriver_t *ldp, qnotify_t inotify, qnotify_t onotify)
Initializes a generic full duplex driver object.
Definition: ledcomm.c:322
void ldIncomingDataI(LEDCommDriver_t *ldp, uint8_t b)
Handles incoming data.
Definition: ledcomm.c:445
#define LEDCOMM_DEFAULT_SYNCS
Number of MARKs before considering the link is "up".
Definition: ledcomm.h:87
const struct LEDCommDriverVMT * vmt
Virtual Methods Table.
Definition: ledcomm.h:221
Full duplex LEDComm driver class.
Definition: ledcomm.h:219
#define LEDCOMM_DATA_BITS7
7 data bits per character (default).
Definition: ledcomm.h:91
ioportmask_t cathode_pad
The pad where the LED cathode is connected.
Definition: ledcomm.h:170
ioportmask_t anode_pad
The pad where the LED anode is connected.
Definition: ledcomm.h:168
Stopped.
Definition: ledcomm.h:188