/
stm32I2C.cpp
437 lines (361 loc) · 11.2 KB
/
stm32I2C.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
#include <atomic>
#include <cstring>
#include "hal/MarklinBusGPIOMap.h"
#include "hal/stm32I2C.h"
#include <libopencm3/cm3/nvic.h>
#include <libopencm3/stm32/exti.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/i2c.h>
#include "AtomicRingBuffer/ObjectRingBuffer.h"
#include "DataModel.h"
namespace hal {
namespace {
using I2CMessagePtr_t = I2CMessage_t*;
constexpr static const uint8_t kI2CQueueSize = 10;
using QueueType = AtomicRingBuffer::ObjectRingBuffer<I2CMessage_t, kI2CQueueSize>;
class TransmissionControlBlock {
public:
constexpr const static uint8_t kMsgBytesLength = 3;
QueueType::MemoryRange msgMemory;
uint_fast8_t bytesProcessed;
/**
* \brief Checks whether the buffer contains a valid message.
*
* \return true if the message is valid, false otherwise.
*/
bool messageValid() const {
// Buffer occupied
// its 3 bytes long.
return isOccupied() && bytesProcessed == MarklinI2C::kAccessoryMessageLength;
}
/**
* \brief A buffer is occupied if the transmission ISR has access to it.
*
* This means: A transmission is...
* * ongoing,
* * pending,
* * About to be done.
*/
bool isOccupied() const { return bufferOccupied.load(std::memory_order_acquire); }
/// A buffer is free if it is not occupied.
bool isFree() const { return !isOccupied(); }
/**
* \brief Try to allocate the buffer for active message operation.
*
* \return TRUE if the buffer could be claimed, FALSE otherwise.
*/
bool tryClaim() {
bool bufferOccupationExpected = false;
const bool newBufferOccupation = true;
bool claimed = bufferOccupied.compare_exchange_strong(
bufferOccupationExpected, newBufferOccupation, std::memory_order_acquire);
if (claimed) {
bytesProcessed = 0;
msgMemory = QueueType::MemoryRange{};
}
return claimed;
}
void release() {
bytesProcessed = 0;
msgMemory = QueueType::MemoryRange{};
bufferOccupied.store(false, std::memory_order_release);
}
private:
std::atomic_bool bufferOccupied;
};
TransmissionControlBlock rxControl;
TransmissionControlBlock txControl;
QueueType i2cRxQueue;
QueueType i2cTxQueue;
freertossupport::OsTask taskToNotify;
StopGoRequest stopGoRequest;
bool i2cBusy() { return (I2C_SR2(I2C1) & I2C_SR2_BUSY) != 0; }
/**
* Take a message from the Queue and start transmitting.
*
* Function from Task
*/
void startTx();
/**
* Take a message from the Queue and start transmitting.
*
* Function from ISR
*/
void startTxFromISR();
/**
* Start a message from the filled TX buffer.
*/
void resumeTx();
/**
* Same as resumeTx(), but ignores whether the bus is busy.
*/
void resumeTxForce();
/**
* Abort a transmission and loose the message that may be transmitted.
*/
void finishTx();
/**
* Send the message from the RX buffer for forwarding.
*/
void forwardRx();
/**
* Release RX Buffer memory for an I2C RX Message.
*/
void freeI2CRXMessage(I2CMessagePtr_t msgPtr);
} // namespace
// ***************************
// Implementation
// ***************************
void beginI2C(uint8_t slaveAddress, freertossupport::OsTask routingTask) {
i2c_peripheral_disable(I2C1);
i2c_reset(I2C1);
taskToNotify = routingTask;
gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN,
GPIO_I2C1_SCL | GPIO_I2C1_SDA);
gpio_set(GPIOB, GPIO_I2C1_SCL | GPIO_I2C1_SDA);
// Basic I2C configuration
// i2c_set_speed(I2C1, i2c_speed_sm_100k, I2C_CR2_FREQ_36MHZ);
i2c_set_standard_mode(I2C1);
i2c_set_clock_frequency(I2C1, I2C_CR2_FREQ_36MHZ);
i2c_set_trise(I2C1, 36);
i2c_set_dutycycle(I2C1, I2C_CCR_DUTY_DIV2);
i2c_set_ccr(I2C1, 180);
i2c_set_own_7bit_slave_address(I2C1, slaveAddress);
// Set I2C IRQ to support slave mode
rxControl.release();
txControl.release();
nvic_enable_irq(NVIC_I2C1_EV_IRQ);
nvic_enable_irq(NVIC_I2C1_ER_IRQ);
nvic_set_priority(NVIC_I2C1_EV_IRQ, configMAX_SYSCALL_INTERRUPT_PRIORITY + 64);
nvic_set_priority(NVIC_I2C1_ER_IRQ, configMAX_SYSCALL_INTERRUPT_PRIORITY + 64);
i2c_enable_interrupt(I2C1, I2C_CR2_ITEVTEN | I2C_CR2_ITBUFEN | I2C_CR2_ITERREN);
i2c_peripheral_enable(I2C1);
i2c_enable_ack(I2C1);
// Setup GPIO for Stop/Go
gpio_set_mode(hal::MarklinBusGPIOMap::STOP.port, GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN,
hal::MarklinBusGPIOMap::STOP.bits);
gpio_set_mode(hal::MarklinBusGPIOMap::GO.port, GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN,
hal::MarklinBusGPIOMap::GO.bits);
gpio_clear(hal::MarklinBusGPIOMap::STOP.port, hal::MarklinBusGPIOMap::STOP.bits);
gpio_clear(hal::MarklinBusGPIOMap::GO.port, hal::MarklinBusGPIOMap::GO.bits);
// STOP IRQ
nvic_enable_irq(NVIC_EXTI4_IRQ);
nvic_set_priority(NVIC_EXTI4_IRQ, configMAX_SYSCALL_INTERRUPT_PRIORITY + 64);
exti_select_source(EXTI4, hal::MarklinBusGPIOMap::STOP.port);
exti_set_trigger(EXTI4, EXTI_TRIGGER_RISING);
exti_enable_request(EXTI4);
// GO IRQ
nvic_enable_irq(NVIC_EXTI15_10_IRQ);
nvic_set_priority(NVIC_EXTI15_10_IRQ, configMAX_SYSCALL_INTERRUPT_PRIORITY + 64);
exti_select_source(EXTI15, hal::MarklinBusGPIOMap::GO.port);
exti_set_trigger(EXTI15, EXTI_TRIGGER_RISING);
exti_enable_request(EXTI15);
}
I2CRxMessagePtr_t getI2CMessage() {
return I2CRxMessagePtr_t{i2cRxQueue.peek().ptr, freeI2CRXMessage};
}
void sendI2CMessage(const I2CMessage_t& msg) {
auto memory = i2cTxQueue.allocate();
if (memory.ptr != nullptr) {
*memory.ptr = msg;
i2cTxQueue.publish(memory);
}
startTx();
}
StopGoRequest getStopGoRequest() {
StopGoRequest currentRequest = stopGoRequest;
stopGoRequest = StopGoRequest{};
return currentRequest;
}
namespace {
void freeI2CRXMessage(I2CMessagePtr_t msgPtr) {
i2cRxQueue.consume(QueueType::MemoryRange{msgPtr, 1});
}
void startTx() {
const bool txControlClaimed = txControl.tryClaim();
if (txControlClaimed) {
txControl.msgMemory = i2cTxQueue.peek();
const auto messageAvailable = txControl.msgMemory.ptr != nullptr;
if (messageAvailable) {
resumeTx();
} else {
txControl.release();
}
}
}
void startTxFromISR() {
if (txControl.isFree()) {
startTx();
} else {
resumeTx();
}
}
void resumeTx() {
if (txControl.isOccupied() && !i2cBusy()) {
// If bus is idle, send a start condition.
i2c_send_start(I2C1);
}
}
void resumeTxForce() {
if (txControl.isOccupied()) {
// If there is data, send a start condition.
i2c_send_start(I2C1);
}
}
void forwardRx() {
// Forecefully abort the current transaction
i2c_peripheral_enable(I2C1);
// If there is a complete message in the buffer, consider it received.
if (rxControl.messageValid()) {
if (rxControl.msgMemory.ptr != nullptr) {
i2cRxQueue.publish(rxControl.msgMemory);
rxControl.msgMemory = QueueType::MemoryRange{};
} else {
// Received a message that could not be stored during reception
asm("bkpt");
}
rxControl.release();
// See if there is something to be sent.
startTxFromISR();
// Notify processing thread
taskToNotify.notifyFromISRWithWake();
}
}
void finishTx() {
i2cTxQueue.consume(txControl.msgMemory);
txControl.release();
i2c_send_stop(I2C1);
// See if there is something to be sent.
startTx();
}
} // namespace
// i2c1 event ISR
// Code based on
// https://amitesh-singh.github.io/stm32/2018/01/07/making-i2c-slave-using-stm32f103.html
extern "C" void i2c1_ev_isr(void) {
// ISR appears to be called once per I2C byte received
// gpio_toggle(GPIOA, GPIO5);
uint32_t sr1, sr2;
sr1 = I2C_SR1(I2C1);
if (sr1 & I2C_SR1_SB) {
// Refrence Manual: EV5 (Master)
txControl.bytesProcessed = 1;
i2c_send_7bit_address(I2C1, txControl.msgMemory.ptr->destination_ >> 1, I2C_WRITE);
} else if (sr1 & I2C_SR1_ADDR) {
// Address matched (Slave)
// Refrence Manual: EV6 (Master)/EV1 (Slave)
// Clear the ADDR sequence by reading SR2.
sr2 = I2C_SR2(I2C1);
if (!(sr2 & I2C_SR2_MSL)) {
// Reference Manual: EV1
if (rxControl.tryClaim()) {
rxControl.msgMemory = i2cRxQueue.allocate();
}
if (rxControl.msgMemory.ptr != nullptr) {
rxControl.msgMemory.ptr->destination_ = DataModel::kMyAddr;
} else {
// TODO: Queue full. Handle somehow.
__asm("bkpt 4");
}
rxControl.bytesProcessed = 1;
}
}
// Receive buffer not empty
else if (sr1 & I2C_SR1_RxNE) {
// Reference Manual: EV2
if (rxControl.isOccupied() && rxControl.msgMemory.ptr != nullptr) {
if (rxControl.bytesProcessed < 3) {
switch (rxControl.bytesProcessed) {
case 1:
rxControl.msgMemory.ptr->destination_ = i2c_get_data(I2C1);
break;
case 2:
rxControl.msgMemory.ptr->data_ = i2c_get_data(I2C1);
break;
}
++rxControl.bytesProcessed;
}
}
}
// Transmit buffer empty & Data byte transfer not finished
else if ((sr1 & I2C_SR1_TxE) && !(sr1 & I2C_SR1_BTF)) {
// EV8, 8_1
switch (txControl.bytesProcessed) {
case 1:
i2c_send_data(I2C1, txControl.msgMemory.ptr->source_ << 1);
++txControl.bytesProcessed;
break;
case 2:
i2c_send_data(I2C1, txControl.msgMemory.ptr->data_);
++txControl.bytesProcessed;
break;
default:
// EV 8_2
finishTx();
break;
}
}
// done by master by sending STOP
// this event happens when slave is in Recv mode at the end of communication
else if (sr1 & I2C_SR1_STOPF) {
// Reference Manual: EV3
forwardRx();
}
// this event happens when slave is in transmit mode at the end of communication
else if (sr1 & I2C_SR1_AF) {
//(void) I2C_SR1(I2C1);
I2C_SR1(I2C1) &= ~(I2C_SR1_AF);
}
}
// Error condition interrupt for I2C1
extern "C" void i2c1_er_isr(void) {
// check which error bit was set
uint32_t sr1 = I2C_SR1(I2C1);
uint32_t sr2 = I2C_SR2(I2C1);
// gpio_toggle(GPIOA, GPIO4);
if (sr1 & I2C_SR1_AF) {
// Acknowledge Failure (AF)
// Reset AF bit
I2C_SR1(I2C1) &= ~I2C_SR1_AF;
// Abort the current transmission. Assume that noone is available on the bus.
finishTx();
//__asm("bkpt 6");
} else if (sr1 & I2C_SR1_ARLO) {
// Arbitration Lost
// Interface automatically goes to slave mode.
// End of reception will trigger another attempt at starting.
//__asm("bkpt 6");
} else if (sr1 & I2C_SR1_BERR) {
if (sr2 & I2C_SR2_MSL) {
// In Master mode: software must clean up
//__asm("bkpt 6");
// In out usecase, this is caused by a misbehaving Memory device.
// Restart the transmission.
resumeTxForce();
} else {
// In Slave mode: auto-release
//__asm("bkpt 6");
// If a valid message was received, this is caused by a misbehaving Memory devide.
forwardRx();
}
// Reset the error flag as the error was handled.
I2C_SR1(I2C1) &= ~I2C_SR1_BERR;
} else if (sr1 & I2C_SR1_TxE) {
__asm("bkpt 6");
} else if (sr1 & I2C_SR1_RxNE) {
__asm("bkpt 6");
} else {
__asm("bkpt 6");
}
}
extern "C" void exti4_isr() {
stopGoRequest.stopRequest = true;
taskToNotify.notifyFromISRWithWake();
exti_reset_request(EXTI4);
}
extern "C" void exti15_isr() {
stopGoRequest.goRequest = true;
taskToNotify.notifyFromISRWithWake();
exti_reset_request(EXTI15);
}
} // namespace hal