Skyward boardcore
Loading...
Searching...
No Matches
stm32f2_f4_i2c.cpp
Go to the documentation of this file.
1/* Copyright (c) 2013 Skyward Experimental Rocketry
2 * Author: Federico Terraneo
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 */
22
23#include "stm32f2_f4_i2c.h"
24
25#include <kernel/scheduler/scheduler.h>
26#include <miosix.h>
27
28// #define I2C_WITH_DMA
29
30using namespace miosix;
31
32static volatile bool error;
33static Thread* waiting = 0;
34
35/* In non-DMA mode the variables below are used to
36 * handle the reception of 2 or more bytes through
37 * an interrupt, avoiding the thread that calls recv
38 * to be locked in polling
39 */
40
41#ifndef I2C_WITH_DMA
42static uint8_t* rxBuf = 0;
43static unsigned int rxBufCnt = 0;
44static unsigned int rxBufSize = 0;
45#endif
46
47#ifdef I2C_WITH_DMA
51void __attribute__((naked)) DMA1_Stream0_IRQHandler()
52{
53 saveContext();
54 asm volatile("bl _Z20I2C1rxDmaHandlerImplv");
55 restoreContext();
56}
57
61void __attribute__((used)) I2C1rxDmaHandlerImpl()
62{
63 DMA1->LIFCR = DMA_LIFCR_CTCIF0 | DMA_LIFCR_CTEIF0 | DMA_LIFCR_CDMEIF0 |
64 DMA_LIFCR_CFEIF0;
65 I2C1->CR1 |= I2C_CR1_STOP;
66 if (waiting == 0)
67 return;
68 waiting->IRQwakeup();
69 if (waiting->IRQgetPriority() >
70 Thread::IRQgetCurrentThread()->IRQgetPriority())
71 {
72 Scheduler::IRQfindNextThread();
73 }
74 waiting = 0;
75}
76
80void DMA1_Stream7_IRQHandler()
81{
82 DMA1->HIFCR = DMA_HIFCR_CTCIF7 | DMA_HIFCR_CTEIF7 | DMA_HIFCR_CDMEIF7 |
83 DMA_HIFCR_CFEIF7;
84
85 // We can't just wake the thread because the I2C is double buffered, and
86 // this interrupt is fired at the same time as the second last byte is
87 // starting to be sent out of the bus. If we return now, the main code
88 // would send a stop condiotion too soon, and the last byte would never be
89 // sent. Instead, we change from DMA mode to IRQ mode, so when the second
90 // last byte is sent, that interrupt is fired and the last byte is sent
91 // out. Note that since no thread is awakened from this IRQ, there's no
92 // need for the saveContext(), restoreContext() and __attribute__((naked))
93 I2C1->CR2 &= ~I2C_CR2_DMAEN;
94 I2C1->CR2 |= I2C_CR2_ITBUFEN | I2C_CR2_ITEVTEN;
95}
96
97#endif
98
102void __attribute__((naked)) I2C1_EV_IRQHandler()
103{
104 saveContext();
105 asm volatile("bl _Z15I2C1HandlerImplv");
106 restoreContext();
107}
108
112void __attribute__((used)) I2C1HandlerImpl()
113{
114#ifdef I2C_WITH_DMA
115 // When called to resolve the last byte not sent issue, clearing
116 // I2C_CR2_ITBUFEN prevents this interrupt being re-entered forever, as
117 // it does not send another byte to the I2C, so the interrupt would remain
118 // pending. When called after the start bit has been sent, clearing
119 // I2C_CR2_ITEVTEN prevents the same infinite re-enter as this interrupt
120 // does not start an address transmission, which is necessary to stop
121 // this interrupt from being pending
122 I2C1->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITBUFEN);
123 if (waiting == 0)
124 return;
125#else
126
127 bool rxFinished = false;
128
129 /* If rxBuf is equal to zero means that we are sending the slave
130 address and this ISR is used to manage the address sent interrupt */
131
132 if (rxBuf == 0)
133 {
134 I2C1->CR2 &= ~I2C_CR2_ITEVTEN;
135 rxFinished = true;
136 }
137
138 if (I2C1->SR1 & I2C_SR1_RXNE)
139 {
140 rxBuf[rxBufCnt++] = I2C1->DR;
141 if (rxBufCnt >= rxBufSize)
142 {
143 I2C1->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITBUFEN);
144 rxFinished = true;
145 }
146 }
147
148 if (waiting == 0 || !rxFinished)
149 return;
150#endif
151 waiting->IRQwakeup();
152 if (waiting->IRQgetPriority() >
153 Thread::IRQgetCurrentThread()->IRQgetPriority())
154 {
155 Scheduler::IRQfindNextThread();
156 }
157 waiting = 0;
158}
159
163void __attribute__((naked)) I2C1_ER_IRQHandler()
164{
165 saveContext();
166 asm volatile("bl _Z18I2C1errHandlerImplv");
167 restoreContext();
168}
169
173void __attribute__((used)) I2C1errHandlerImpl()
174{
175 I2C1->SR1 = 0; // Clear error flags
176 error = true;
177 if (waiting == 0)
178 return;
179 waiting->IRQwakeup();
180 if (waiting->IRQgetPriority() >
181 Thread::IRQgetCurrentThread()->IRQgetPriority())
182 {
183 Scheduler::IRQfindNextThread();
184 }
185 waiting = 0;
186}
187
188namespace miosix
189{
190
191//
192// class I2C
193//
194
196{
197 static I2C1Driver singleton;
198 return singleton;
199}
200
202{
203 // I2C devices are connected to APB1, whose frequency is the system clock
204 // divided by a value set in the PPRE1 bits of RCC->CFGR
205 const int ppre1 = (RCC->CFGR & RCC_CFGR_PPRE1) >> 10;
206 const int divFactor = (ppre1 & 1 << 2) ? (2 << (ppre1 & 0x3)) : 1;
207 const int fpclk1 = SystemCoreClock / divFactor;
208 // iprintf("fpclk1=%d\n",fpclk1);
209
210 {
211 FastInterruptDisableLock dLock;
212
213#ifdef I2C_WITH_DMA
214 RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN;
215 RCC_SYNC();
216#endif
217 RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; // Enable clock gating
218 RCC_SYNC();
219 }
220
221#ifdef I2C_WITH_DMA
222 NVIC_SetPriority(DMA1_Stream7_IRQn, 10); // Low priority for DMA
223 NVIC_ClearPendingIRQ(
224 DMA1_Stream7_IRQn); // DMA1 stream 7 channel 1 = I2C1 TX
225 NVIC_EnableIRQ(DMA1_Stream7_IRQn);
226
227 NVIC_SetPriority(DMA1_Stream0_IRQn, 10); // Low priority for DMA
228 NVIC_ClearPendingIRQ(
229 DMA1_Stream0_IRQn); // DMA1 stream 0 channel 1 = I2C1 RX
230 NVIC_EnableIRQ(DMA1_Stream0_IRQn);
231#endif
232
233 NVIC_SetPriority(I2C1_EV_IRQn, 10); // Low priority for I2C
234 NVIC_ClearPendingIRQ(I2C1_EV_IRQn);
235 NVIC_EnableIRQ(I2C1_EV_IRQn);
236
237 NVIC_SetPriority(I2C1_ER_IRQn, 10);
238 NVIC_ClearPendingIRQ(I2C1_ER_IRQn);
239 NVIC_EnableIRQ(I2C1_ER_IRQn);
240
241 I2C1->CR1 = I2C_CR1_SWRST;
242 I2C1->CR1 = 0;
243 I2C1->CR2 = fpclk1 / 1000000; // Set pclk frequency in MHz
244 // This sets the duration of both Thigh and Tlow (master mode))
245 const int i2cSpeed = 100000; // 100KHz
246 I2C1->CCR =
247 std::max(4, fpclk1 / (2 * i2cSpeed)); // Duty=2, standard mode (100KHz)
248 // Datasheet says with I2C @ 100KHz, maximum SCL rise time is 1000ns
249 // Need to change formula if I2C needs to run @ 400kHz
250 I2C1->TRISE = fpclk1 / 1000000 + 1;
251 I2C1->CR1 = I2C_CR1_PE; // Enable peripheral
252}
253
254bool I2C1Driver::send(unsigned char address, const void* data, int len,
255 bool sendStop)
256{
257 /* if sendStop is true user is requesting to have the stop condition sent,
258 * so noStop has to be set to false
259 */
260 noStop = !sendStop;
261
262 address &= 0xfe; // Mask bit 0, as we are writing
263 if (start(address) == false || (I2C1->SR2 & I2C_SR2_TRA) == 0)
264 {
265 I2C1->CR1 |= I2C_CR1_STOP;
266 return false;
267 }
268
269#ifdef I2C_WITH_DMA
270 waiting = Thread::getCurrentThread();
271 DMA1_Stream7->CR = 0;
272 DMA1_Stream7->PAR = reinterpret_cast<unsigned int>(&I2C1->DR);
273 DMA1_Stream7->M0AR = reinterpret_cast<unsigned int>(data);
274 DMA1_Stream7->NDTR = len;
275 DMA1_Stream7->FCR = DMA_SxFCR_FEIE | DMA_SxFCR_DMDIS;
276 DMA1_Stream7->CR = DMA_SxCR_CHSEL_0 // Channel 1
277 | DMA_SxCR_MINC // Increment memory pointer
278 | DMA_SxCR_DIR_0 // Memory to peripheral
279 | DMA_SxCR_TCIE // Interrupt on transfer complete
280 | DMA_SxCR_TEIE // Interrupt on transfer error
281 | DMA_SxCR_DMEIE // Interrupt on direct mode error
282 | DMA_SxCR_EN; // Start DMA
283
284 // Enable DMA in the I2C peripheral *after* having configured the DMA
285 // peripheral, or a spurious interrupt is triggered
286 I2C1->CR2 |= I2C_CR2_DMAEN | I2C_CR2_ITERREN;
287
288 {
289 FastInterruptDisableLock dLock;
290 while (waiting)
291 {
292 waiting->IRQwait();
293 {
294 FastInterruptEnableLock eLock(dLock);
295 Thread::yield();
296 }
297 }
298 }
299
300 DMA1_Stream7->CR = 0;
301
302 // The DMA interrupt routine changes the interrupt flags!
303 I2C1->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITERREN);
304#else
305
306 I2C1->CR2 |= I2C_CR2_ITERREN;
307
308 const uint8_t* txData = reinterpret_cast<const uint8_t*>(data);
309 for (int i = 0; i < len; i++)
310 {
311 I2C1->DR = txData[i];
312 while (!(I2C1->SR1 & I2C_SR1_TXE))
313 ;
314 }
315
316 I2C1->CR2 &= ~I2C_CR2_ITERREN;
317#endif
318
319 /* The main idea of this driver is to avoid having the processor spinning
320 * waiting on some status flag. Why? Because I2C is slow compared to a
321 * modern processor. A 120MHz core does 1200 clock cycles in the time it
322 * takes to transfer a single bit through an I2C clocked at 100KHz.
323 * This time could be better spent doing a context switch and letting
324 * another thread do useful work, or (and Miosix does it automatically if
325 * there are no ready threads) sleeping the processor core. However,
326 * I'm quite disappointed by the STM32 I2C peripheral, as it seems overly
327 * complicated to use. To come close to achieving this goal I had to
328 * orchestrate among *four* interrupt handlers, two of the DMA, and two
329 * of the I2C itself. And in the end, what's even more disappointing, is
330 * that I haven't found a way to completely avoid spinning. Why?
331 * There's no interrupt that's fired when the stop bit is sent!
332 * And what's worse, the documentation says that after you set the stop
333 * bit in the CR2 register you can't write to it again (for example, to send
334 * a start bit because two i2c api calls are made back to back) until the
335 * MSL bit is cleared. But there's no interrupt tied to that event!
336 * What's worse, is that the closest interrupt flag I've found when doing
337 * an I2C send is fired when the last byte is *beginning* to be sent.
338 * Maybe I haven't searched well enough, but the fact is I found nothing,
339 * so this code below spins for 8 data bits of the last byte plus the ack
340 * bit, plus the stop bit. That's 12000 wasted CPU cycles. Thanks, ST...
341 */
342
343 if (sendStop)
344 {
345 I2C1->CR1 |= I2C_CR1_STOP;
346 while (I2C1->SR2 & I2C_SR2_MSL)
347 ; // Wait for stop bit sent
348 }
349 else
350 {
351 // Dummy write, is the only way to clear
352 // the TxE flag if stop bit is not sent...
353 I2C1->DR = 0x00;
354 }
355
356 return true;
357}
358
359bool I2C1Driver::recv(unsigned char address, void* data, int len)
360{
361 address |= 0x01;
362 if (start(address, len == 1) == false || I2C1->SR2 & I2C_SR2_TRA)
363 {
364 I2C1->CR1 |= I2C_CR1_STOP;
365 return false;
366 }
367
368 error = false;
369 waiting = Thread::getCurrentThread();
370
371#ifdef I2C_WITH_DMA
372 I2C1->CR2 |= I2C_CR2_DMAEN | I2C_CR2_LAST | I2C_CR2_ITERREN;
373
374 DMA1_Stream0->CR = 0;
375 DMA1_Stream0->PAR = reinterpret_cast<unsigned int>(&I2C1->DR);
376 DMA1_Stream0->M0AR = reinterpret_cast<unsigned int>(data);
377 DMA1_Stream0->NDTR = len;
378 DMA1_Stream0->FCR = DMA_SxFCR_FEIE | DMA_SxFCR_DMDIS;
379 DMA1_Stream0->CR = DMA_SxCR_CHSEL_0 // Channel 1
380 | DMA_SxCR_MINC // Increment memory pointer
381 | DMA_SxCR_TCIE // Interrupt on transfer complete
382 | DMA_SxCR_TEIE // Interrupt on transfer error
383 | DMA_SxCR_DMEIE // Interrupt on direct mode error
384 | DMA_SxCR_EN; // Start DMA
385
386 {
387 FastInterruptDisableLock dLock;
388 while (waiting)
389 {
390 waiting->IRQwait();
391
392 {
393 FastInterruptEnableLock eLock(dLock);
394 Thread::yield();
395 }
396 }
397 }
398
399 DMA1_Stream7->CR = 0;
400
401 I2C1->CR2 &= ~(I2C_CR2_DMAEN | I2C_CR2_LAST | I2C_CR2_ITERREN);
402
403#else
404
405 /* Since i2c data reception is a bit tricky (see ST's reference manual for
406 * further details), the thread that calls recv is yelded and reception is
407 * handled using interrupts only if the number of bytes to be received is
408 * greater than one.
409 */
410
411 rxBuf = reinterpret_cast<uint8_t*>(data);
412
413 if (len > 1)
414 {
415 I2C1->CR2 |= I2C_CR2_ITERREN | I2C_CR2_ITEVTEN | I2C_CR2_ITBUFEN;
416
417 rxBufCnt = 0;
418 rxBufSize = len - 2;
419
420 {
421 FastInterruptDisableLock dLock;
422 while (waiting)
423 {
424 waiting->IRQwait();
425 {
426 FastInterruptEnableLock eLock(dLock);
427 Thread::yield();
428 }
429 }
430 }
431 I2C1->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITBUFEN);
432 }
433
434 I2C1->CR1 &= ~I2C_CR1_ACK;
435 I2C1->CR1 |= I2C_CR1_STOP;
436
437 while (!(I2C1->SR1 & I2C_SR1_RXNE))
438 ;
439 rxBuf[len - 1] = I2C1->DR;
440
441 // set pointer to rx buffer to zero after having used it, see i2c event ISR
442 rxBuf = 0;
443
444 I2C1->CR2 &= ~I2C_CR2_ITERREN;
445#endif
446
447 while (I2C1->SR2 & I2C_SR2_MSL)
448 ; // Wait for stop bit sent
449 return !error;
450}
451
452bool I2C1Driver::start(unsigned char address, bool immediateNak)
453{
454 /* Because the only way to send a restart is having the send function not
455 * sending a stop condition after the data transfer, here we have to manage
456 * a couple of things in SR1:
457 * - the BTF flag is set, cleared by a dummy read of DR
458 * - The Berr flag is set, this because the I2C harware detects the start
459 * condition sent without a stop before it as a misplaced start and
460 * rises an error
461 */
462
463 I2C1->CR1 |= I2C_CR1_START | I2C_CR1_ACK;
464 if (!waitStatus1())
465 return false;
466 if ((I2C1->SR1 & I2C_SR1_SB) == 0)
467 return false; // Must read SR1 to clear flag
468 I2C1->DR = address;
469 if (immediateNak)
470 I2C1->CR1 &= ~I2C_CR1_ACK;
471 if (!waitStatus1())
472 return false;
473 if (I2C1->SR1 & I2C_SR1_AF)
474 return false; // Must read SR1 and SR2
475 if ((I2C1->SR2 & I2C_SR2_MSL) == 0)
476 return false;
477 return true;
478}
479
480bool I2C1Driver::waitStatus1()
481{
482 error = false;
483 waiting = Thread::getCurrentThread();
484 I2C1->CR2 |= I2C_CR2_ITEVTEN | I2C_CR2_ITERREN;
485 {
486 FastInterruptDisableLock dLock;
487 while (waiting)
488 {
489 waiting->IRQwait();
490 {
491 FastInterruptEnableLock eLock(dLock);
492 Thread::yield();
493 }
494 }
495 }
496 I2C1->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITERREN);
497 return !error;
498}
499
500} // namespace miosix
bool send(unsigned char address, const void *data, int len, bool sendStop=true)
static I2C1Driver & instance()
bool recv(unsigned char address, void *data, int len)
void __attribute__((naked)) I2C1_EV_IRQHandler()