Line data Source code
1 : /** 2 : * @file stm32f4_i2c.c 3 : * @brief STM32F4 I2C HAL implementation 4 : * 5 : * Copyright (c) 2025 Cory McKiel. 6 : * Licensed under the MIT License. See LICENSE file in the project root. 7 : */ 8 : 9 : #ifdef DESKTOP_BUILD 10 : #include "registers.h" 11 : #include "nvic.h" 12 : #else 13 : #include "stm32f4xx.h" 14 : #endif 15 : 16 : #include "i2c.h" 17 : #include "i2c_transaction_queue.h" 18 : #include "stm32f4_hal.h" 19 : 20 : #include <string.h> 21 : #include <stdbool.h> 22 : 23 : #define SYS_FREQ_MHZ 16 24 : #define I2C_DIRECTION_WRITE 0 25 : #define I2C_DIRECTION_READ 1 26 : 27 : static void configure_gpio(); 28 : static void configure_peripheral(); 29 : static void configure_interrupts(); 30 : static bool load_new_transaction(); 31 : static bool current_transaction_is_valid(); 32 : 33 : /* Private variables */ 34 : static hal_i2c_txn_t *current_i2c_transaction = NULL; 35 : 36 : /* ISR variables */ 37 : static volatile hal_i2c_txn_t _current_i2c_transaction; 38 : static volatile size_t _tx_position = 0; 39 : static volatile size_t _rx_position = 0; 40 : static volatile bool _tx_last_byte_written = false; 41 : static volatile bool _rx_last_byte_read = false; 42 : static volatile bool _tx_in_progress = false; 43 : static volatile bool _rx_in_progress = false; 44 : static volatile bool _error_occurred = false; 45 : 46 : #define _SET_ERROR_FLAG_AND_ABORT_TRANSACTION() \ 47 : _error_occurred = true; \ 48 : I2C1->CR2 &= ~I2C_CR2_ITBUFEN; \ 49 : I2C1->CR1 |= I2C_CR1_STOP; \ 50 : _tx_in_progress = false; \ 51 : _rx_in_progress = false; 52 : 53 35 : void I2C1_EV_IRQHandler(void) 54 : { 55 : // ************** START Phase ************** 56 : // Start condition has been generated on the line. 57 : // Start bit has been set. It must be cleared and the 58 : // device address written to DR. 59 : // ***************************************** 60 35 : if (I2C1->SR1 & I2C_SR1_SB) 61 : { 62 : // Reading SR1 clears SB. 63 9 : (void)I2C1->SR1; 64 : 65 : // If we're transmitting data, append the WRITE bit to address. Otherwise, 66 : // append the READ bit. 67 9 : if (_tx_in_progress && !_rx_in_progress) 68 : { 69 4 : I2C1->DR = (_current_i2c_transaction.target_addr << 1) | I2C_DIRECTION_WRITE; 70 : } 71 5 : else if (_rx_in_progress && !_tx_in_progress) 72 : { 73 5 : I2C1->DR = (_current_i2c_transaction.target_addr << 1) | I2C_DIRECTION_READ; 74 : } 75 : else 76 : { 77 : // Error with mutual exclusion of _tx_in_progress and _rx_in_progress. 78 : // Set error flag and bail. 79 0 : _SET_ERROR_FLAG_AND_ABORT_TRANSACTION(); 80 : } 81 : } 82 : 83 : // ************** ADDRESS Phase ************** 84 : // The target address has been sent on the line and 85 : // the target has acknowledged the address. Need to 86 : // set up the rest of the transaction and clear the 87 : // ADDR bit. 88 : // ***************************************** 89 35 : if (I2C1->SR1 & I2C_SR1_ADDR) 90 : { 91 : // Set up ACK hardware base on reception size. 92 8 : if (_rx_in_progress) 93 : { 94 5 : if (_current_i2c_transaction.expected_bytes_to_rx == 1) 95 : { 96 : // Reset ACK bit so that NACK is sent on the next byte reception. 97 1 : I2C1->CR1 &= ~I2C_CR1_ACK; 98 : } 99 4 : else if (_current_i2c_transaction.expected_bytes_to_rx == 2) 100 : { 101 : // Reset ACK bit. 102 2 : I2C1->CR1 &= ~I2C_CR1_ACK; 103 : // Only set POS for 2-byte reception. 104 : // ACK bit controls the NACK of the next byte which is received in the shift register. 105 : // Therefore, the first byte will be ACK'd and the second NACK'd automatically. 106 2 : I2C1->CR1 |= I2C_CR1_POS; 107 : } 108 2 : else if (_current_i2c_transaction.expected_bytes_to_rx > 2) 109 : { 110 : // Set ACK bit to acknowledge received bytes until further notice. 111 2 : I2C1->CR1 |= I2C_CR1_ACK; 112 : } 113 : else 114 : { 115 : // In this scenario, an error occurred. Expected bytes to receive should be one or 116 : // greater if an rx is in progress. 117 : // Set STOP and reset ACK. 118 0 : I2C1->CR1 |= I2C_CR1_STOP; 119 0 : I2C1->CR1 &= ~I2C_CR1_ACK; 120 0 : _rx_in_progress = false; 121 0 : _tx_in_progress = false; 122 0 : _error_occurred = true; 123 : } 124 : } 125 : 126 : // Reading SR1 followed by SR1 clears ADDR. 127 : // SCL stretched low until this cleared. 128 8 : (void)I2C1->SR1; 129 8 : (void)I2C1->SR2; 130 : 131 : // Setup STOP condition for single byte rx. 132 8 : if (_rx_in_progress && _current_i2c_transaction.expected_bytes_to_rx == 1) 133 : { 134 : // If ADDR was cleared by reading SR1 and SR2, then the clock is no longer stretched low and 135 : // the reception of the single byte should be happening right now as we process this instruction. 136 : // Stop bit needs to be set while byte is still in flight so hardware can generate STOP on time. 137 1 : I2C1->CR1 |= I2C_CR1_STOP; 138 : // BTF will never be set for a single byte, in which case we must enable RxNE interrupt to 139 : // receive our byte. 140 1 : I2C1->CR2 |= I2C_CR2_ITBUFEN; 141 : } 142 : 143 8 : if (_tx_in_progress) 144 : { 145 : // Enable TxE interrupts for the transmit phase. 146 3 : I2C1->CR2 |= I2C_CR2_ITBUFEN; 147 : } 148 : } 149 : 150 : // ************** DATA Phase ************** 151 : // The connection has been established and the 152 : // hardware configured for either the tx or rx 153 : // phase. Need to monitor BTF, RxNE, and TxE. 154 : // ***************************************** 155 35 : if (I2C1->SR1 & I2C_SR1_BTF) 156 : { 157 9 : if (_rx_in_progress) 158 : { 159 7 : if (_current_i2c_transaction.expected_bytes_to_rx == 2) 160 : { 161 : // For the case of 2-byte reception and BTF set, byte 1 is 162 : // in the DR and byte 2 is in the shift register and SCL is stretched 163 : // low. Set the STOP bit and then read the two bytes. 164 : // Reset POS for 2-byte read. 165 2 : I2C1->CR1 |= I2C_CR1_STOP; 166 2 : I2C1->CR1 &= ~I2C_CR1_POS; 167 2 : _current_i2c_transaction.rx_data[_rx_position] = I2C1->DR; 168 2 : _rx_position++; 169 : // Wait for the last byte to be read in the RxNE interrupt 170 : // to give hardware time to move it from shift register. 171 2 : I2C1->CR2 |= I2C_CR2_ITBUFEN; 172 : } 173 5 : else if (_current_i2c_transaction.expected_bytes_to_rx > 2) 174 : { 175 : // Count starting from 1 instead of zero indexed array. 176 5 : size_t byte_number = _rx_position + 1; 177 : // Assuming rx bytes numbered 1, 2, ..., N 178 : // If byte N-2 is in the DR, then byte N-1 is in the shift register since BTF 179 : // bit is set. Target is waiting to send byte N while SCL is stretch low by our micro. 180 5 : if (byte_number == (_current_i2c_transaction.expected_bytes_to_rx - 2)) 181 : { 182 : // Reset ACK bit before byte N is sent so the hardware can NACK in time. 183 2 : I2C1->CR1 &= ~I2C_CR1_ACK; 184 : // Reading the DR clears BTF and unstretches the clock. Byte N should be on 185 : // its way. 186 2 : _current_i2c_transaction.rx_data[_rx_position] = I2C1->DR; 187 2 : _rx_position++; 188 : // Arm the flag. Next BTF will mean byte N-1 in DR and N in shift register. 189 2 : _rx_last_byte_read = true; 190 : } 191 3 : else if (_rx_last_byte_read) 192 : { 193 : // Byte N-1 in DR and byte N in shift register. SCL stretched low. 194 : // Time to set STOP and read last two bytes. 195 2 : I2C1->CR1 |= I2C_CR1_STOP; 196 : 197 : // Read byte N-1. 198 2 : _current_i2c_transaction.rx_data[_rx_position] = I2C1->DR; 199 2 : _rx_position++; 200 : // Wait for the last byte to be read in the RxNE interrupt 201 : // to give hardware time to move it from shift register. 202 2 : I2C1->CR2 |= I2C_CR2_ITBUFEN; 203 : 204 : // Reset the flag. 205 2 : _rx_last_byte_read = false; 206 : } 207 : else 208 : { 209 : // Normal read somewhere in the beginning or middle of the transaction. 210 1 : _current_i2c_transaction.rx_data[_rx_position] = I2C1->DR; 211 1 : _rx_position++; 212 : } 213 : } 214 : } 215 2 : else if (_tx_in_progress && _tx_last_byte_written) 216 : { 217 : // With BTF set during the transmit phase, and the last byte already written, 218 : // then both DR and shift register are empty and SCL is stretched low. Time to determine 219 : // whether to begin a read phase or end the transaction. Either way, the transmit phase is over. 220 : 221 : // Reset transmit control variables 222 2 : _tx_in_progress = false; 223 2 : _tx_last_byte_written = false; 224 : 225 : // Determine next phase. 226 2 : if (_current_i2c_transaction.i2c_op == HAL_I2C_OP_WRITE) 227 : { 228 : // There is no read phase. End the transaction. 229 1 : I2C1->CR1 |= I2C_CR1_STOP; 230 : } 231 1 : else if (_current_i2c_transaction.i2c_op == HAL_I2C_OP_WRITE_READ) 232 : { 233 : // There is a read phase. Generate a re-start. 234 1 : _rx_in_progress = true; 235 1 : I2C1->CR1 |= I2C_CR1_START; 236 : } 237 : 238 : // Clear BTF to prevent immediate refire. 239 2 : (void)I2C1->DR; 240 : // Disable TxE and RxNE interrupts. 241 2 : I2C1->CR2 &= ~I2C_CR2_ITBUFEN; 242 : } 243 : } 244 : 245 35 : if (I2C1->SR1 & I2C_SR1_TXE) 246 : { 247 10 : if (_tx_in_progress) 248 : { 249 4 : if (_tx_position < _current_i2c_transaction.expected_bytes_to_tx) 250 : { 251 3 : I2C1->DR = _current_i2c_transaction.tx_data[_tx_position]; 252 3 : _tx_position++; 253 3 : if (_tx_position == _current_i2c_transaction.expected_bytes_to_tx) 254 : { 255 : // we just queued the final byte; arm BTF to finish 256 2 : _tx_last_byte_written = true; 257 : } 258 : } 259 1 : else if (_current_i2c_transaction.expected_bytes_to_tx == 0) 260 : { 261 : // Zero length write. 262 : // Disable TxE interrupt, generate STOP, and close out transaction. 263 1 : I2C1->CR2 &= ~I2C_CR2_ITBUFEN; 264 1 : I2C1->CR1 |= I2C_CR1_STOP; 265 1 : _tx_in_progress = false; 266 : } 267 : } 268 : } 269 : 270 35 : if (I2C1->SR1 & I2C_SR1_RXNE) 271 : { 272 : // In receive mode we only use RxNE to pick up the last byte. 273 8 : if (_rx_in_progress && _rx_position == (_current_i2c_transaction.expected_bytes_to_rx - 1)) 274 : { 275 : // We are about to receive our last byte. 276 5 : _current_i2c_transaction.rx_data[_rx_position] = I2C1->DR; 277 5 : _rx_position++; 278 : 279 : // Turn off buffer interrupt. 280 5 : I2C1->CR2 &= ~I2C_CR2_ITBUFEN; 281 : 282 : // Close out the transaction. 283 5 : _rx_in_progress = false; 284 : } 285 : } 286 35 : } 287 : 288 1 : void I2C1_ER_IRQHandler() 289 : { 290 1 : uint32_t sr1 = I2C1->SR1; // volatile read ok 291 : 292 1 : if (sr1 & I2C_SR1_AF) 293 : { 294 : // The target failed to acknowledge either address or data. 295 : // Reset flag. 296 1 : I2C1->SR1 &= ~I2C_SR1_AF; 297 1 : _SET_ERROR_FLAG_AND_ABORT_TRANSACTION(); 298 : } 299 1 : } 300 : 301 15 : hal_status_t hal_i2c_init() 302 : { 303 15 : configure_gpio(); 304 15 : configure_peripheral(); 305 15 : configure_interrupts(); 306 : 307 15 : return HAL_STATUS_OK; 308 : } 309 : 310 12 : hal_status_t hal_i2c_submit_transaction(hal_i2c_txn_t *txn) 311 : { 312 : // @todo: Some transaction validation here. 313 12 : return (i2c_transaction_queue_add(txn) == I2C_QUEUE_STATUS_SUCCESS) ? HAL_STATUS_OK : HAL_STATUS_ERROR; 314 : } 315 : 316 23 : hal_status_t hal_i2c_transaction_servicer() 317 : { 318 23 : hal_status_t status = HAL_STATUS_BUSY; 319 : 320 : // CRITICAL SECTION ENTER 321 23 : NVIC_DisableIRQ(I2C1_EV_IRQn); 322 23 : NVIC_DisableIRQ(I2C1_ER_IRQn); 323 : 324 : // Check if there is currently no transaction in progress. 325 23 : if (!_tx_in_progress && !_rx_in_progress) 326 : { 327 21 : status = HAL_STATUS_OK; 328 : 329 : // Finish transaction that just completed. 330 21 : if (current_i2c_transaction) 331 : { 332 : // Transfer the results back to the client's transaction object. 333 8 : current_i2c_transaction->actual_bytes_transmitted = _tx_position; 334 8 : current_i2c_transaction->actual_bytes_received = _rx_position; 335 8 : memcpy(current_i2c_transaction->rx_data, (const void*)_current_i2c_transaction.rx_data, current_i2c_transaction->actual_bytes_received); 336 8 : current_i2c_transaction->transaction_result = (_error_occurred) ? HAL_I2C_TXN_RESULT_FAIL : HAL_I2C_TXN_RESULT_SUCCESS; 337 : 338 : // Complete the transaction. 339 8 : current_i2c_transaction->processing_state = HAL_I2C_TXN_STATE_COMPLETED; 340 : 341 : // Reset our pointer away from the completed transaction. 342 8 : current_i2c_transaction = NULL; 343 : } 344 : 345 : // Load in a new transaction if there is one. 346 21 : if (load_new_transaction()) 347 : { 348 12 : if (current_transaction_is_valid()) 349 : { 350 : // Set state to processing. 351 11 : current_i2c_transaction->processing_state = HAL_I2C_TXN_STATE_PROCESSING; 352 : 353 : // Copy the transaction to memory that belongs to the ISR. 354 11 : _current_i2c_transaction = *current_i2c_transaction; 355 : 356 : // Set up the control variables. 357 11 : _error_occurred = false; 358 11 : _tx_position = 0; 359 11 : _rx_position = 0; 360 : 361 11 : if (current_i2c_transaction->i2c_op == HAL_I2C_OP_WRITE || 362 5 : current_i2c_transaction->i2c_op == HAL_I2C_OP_WRITE_READ) 363 : { 364 7 : _tx_in_progress = true; 365 7 : _rx_in_progress = false; 366 : } 367 4 : else if (current_i2c_transaction->i2c_op == HAL_I2C_OP_READ) 368 : { 369 4 : _tx_in_progress = false; 370 4 : _rx_in_progress = true; 371 : } 372 : 373 : // Send start 374 11 : I2C1->CR1 |= I2C_CR1_START; 375 : } 376 : else 377 : { 378 : // Close out the invalid transaction and set error. 379 1 : status = HAL_STATUS_ERROR; 380 1 : current_i2c_transaction->actual_bytes_transmitted = 0; 381 1 : current_i2c_transaction->actual_bytes_received = 0; 382 1 : current_i2c_transaction->transaction_result = HAL_I2C_TXN_RESULT_FAIL; 383 1 : current_i2c_transaction->processing_state = HAL_I2C_TXN_STATE_COMPLETED; 384 1 : current_i2c_transaction = NULL; 385 : } 386 : } 387 : } 388 : 389 23 : NVIC_EnableIRQ(I2C1_EV_IRQn); 390 23 : NVIC_EnableIRQ(I2C1_ER_IRQn); 391 : // CRITICAL SECTION EXIT 392 : 393 23 : return status; 394 : } 395 : 396 : /// @brief Just for testing. 397 : /// @warning Grave consequences if used in production code. 398 15 : void _test_fixture_hal_i2c_reset_internals() 399 : { 400 15 : current_i2c_transaction = NULL; 401 : 402 15 : _current_i2c_transaction.target_addr = 0; 403 15 : _current_i2c_transaction.i2c_op = HAL_I2C_OP_WRITE; 404 15 : _current_i2c_transaction.expected_bytes_to_tx = 0; 405 15 : _current_i2c_transaction.expected_bytes_to_rx = 0; 406 15 : _current_i2c_transaction.processing_state = HAL_I2C_TXN_STATE_CREATED; 407 15 : _current_i2c_transaction.transaction_result = HAL_I2C_TXN_RESULT_NONE; 408 15 : _current_i2c_transaction.actual_bytes_received = 0; 409 15 : _current_i2c_transaction.actual_bytes_transmitted = 0; 410 15 : memset((void*)_current_i2c_transaction.tx_data, 0, sizeof(_current_i2c_transaction.tx_data)); 411 15 : memset((void*)_current_i2c_transaction.rx_data, 0, sizeof(_current_i2c_transaction.rx_data)); 412 : 413 15 : _tx_position = 0; 414 15 : _rx_position = 0; 415 15 : _tx_last_byte_written = false; 416 15 : _rx_last_byte_read = false; 417 15 : _tx_in_progress = false; 418 15 : _rx_in_progress = false; 419 15 : _error_occurred = false; 420 15 : } 421 : 422 : // These pins are broken out right next to each other on the dev board. 423 : // And no interference from other peripherals. 424 : // PB8 - I2C1 SCL 425 : // PB9 - I2C1 SDA 426 : // Need to bring up bus from port B, set these pins in AF4. 427 15 : static void configure_gpio() 428 : { 429 : // Enable Bus. 430 15 : RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN; 431 : 432 : // Set PB8 (i2c1 SCL pin) mode to alternate function. 433 15 : GPIOB->MODER &= ~BIT_16; 434 15 : GPIOB->MODER |= BIT_17; 435 : 436 : // Set PB9 (i2c1 SDA pin) mode to alternate function. 437 15 : GPIOB->MODER &= ~BIT_18; 438 15 : GPIOB->MODER |= BIT_19; 439 : 440 : // Set PB8 alternate function type to I2C (AF04) 441 15 : GPIOB->AFR[1] &= ~(0xF << (PIN_0 * AF_SHIFT_WIDTH)); 442 15 : GPIOB->AFR[1] |= (AF4_MASK << (PIN_0 * AF_SHIFT_WIDTH)); 443 : 444 : // Set PB9 alternate function type to I2C (AF04) 445 15 : GPIOB->AFR[1] &= ~(0xF << (PIN_1 * AF_SHIFT_WIDTH)); 446 15 : GPIOB->AFR[1] |= (AF4_MASK << (PIN_1 * AF_SHIFT_WIDTH)); 447 : 448 : // Open drain 449 15 : GPIOB->OTYPER |= (GPIO_OTYPER_OT_8 | GPIO_OTYPER_OT_9); 450 15 : } 451 : 452 15 : static void configure_peripheral() 453 : { 454 : // Send the clock to I2C1 455 15 : RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; 456 : 457 : // Need to set the APB1 Clock frequency in the CR2 register. 458 : // With no dividers, it is the same as the System Frequency of 16 MHz. 459 15 : I2C1->CR2 &= ~(I2C_CR2_FREQ); 460 15 : I2C1->CR2 |= (SYS_FREQ_MHZ & I2C_CR2_FREQ); 461 : 462 : // Time to rise (TRISE) register. Set to 17 via the calculation 463 : // Assumed 1000 ns SCL clock rise time (maximum permitted for I2C Standard Mode) 464 : // Peripheral's clock period (1 / SYSTEM_FREQ_MHZ) 465 : // (1000ns / 62.5) = 17 OR SYSTEM_FREQ_MHZ + 1 = 17. Either calc works. 466 15 : size_t trise_reg_val = SYS_FREQ_MHZ + 1; 467 15 : I2C1->TRISE &= ~(I2C_TRISE_TRISE); 468 15 : I2C1->TRISE |= (trise_reg_val & I2C_TRISE_TRISE); 469 : 470 : // Set CCR. 471 : // We want to setup CCR so that the peripheral can count up ticks of the 472 : // peripheral bus clock, and use that count to create the SCL clock at 100kHz for 473 : // Standard Mode. 474 : // 100 kHz SCL means 1 / 100kHz period of 10 microseconds. 475 : // So we need to transition the SCL clock every ~5 microseconds. 476 : // On a 16 MHz bus clock with a tick every 62.5 nanoseconds, this means 477 : // we need to transition the SCL line every 80 ticks to achieve 100kHz SCL line. 478 15 : size_t ticks_between_scl_transitions = 80; 479 15 : I2C1->CCR &= ~(I2C_CCR_CCR); 480 15 : I2C1->CCR |= (ticks_between_scl_transitions & I2C_CCR_CCR); 481 : 482 : // Standard mode 483 15 : I2C1->CCR &= ~I2C_CCR_FS; 484 : 485 : // Enable the peripheral. 486 15 : I2C1->CR1 |= I2C_CR1_PE; 487 15 : } 488 : 489 15 : static void configure_interrupts() 490 : { 491 15 : I2C1->CR2 |= I2C_CR2_ITEVTEN; 492 15 : I2C1->CR2 |= I2C_CR2_ITERREN; 493 15 : NVIC_EnableIRQ(I2C1_EV_IRQn); 494 15 : NVIC_EnableIRQ(I2C1_ER_IRQn); 495 15 : } 496 : 497 12 : static bool current_transaction_is_valid() 498 : { 499 24 : return (current_i2c_transaction && 500 23 : ENUM_IN_RANGE(current_i2c_transaction->i2c_op, _HAL_I2C_OP_MIN, _HAL_I2C_OP_MAX) && 501 11 : current_i2c_transaction->processing_state == HAL_I2C_TXN_STATE_QUEUED); 502 : } 503 : 504 21 : static bool load_new_transaction() 505 : { 506 21 : return (I2C_QUEUE_STATUS_SUCCESS == i2c_transaction_queue_get_next(¤t_i2c_transaction) && 507 : current_i2c_transaction); 508 : }