Raspberry Pi 5 IoT In C - I2C with Gpio5 |
Written by Harry Fairhead | ||||
Wednesday, 16 July 2025 | ||||
Page 2 of 3
The first function we need is something to initialize the I2C channel we are about to use: void i2c_enable(I2C i2c, bool enable) { i2c->enable = enable? 1: 0; } uint32_t i2c_init(I2C i2c, uint32_t baudrate) { i2c_enable(i2c, false); // Configure as a fast-mode master with // RepStart support, 7-bit addresses i2c->con = (0x2ul << 1) | 0x01 | 0x040 | This is based on the Pico SDK function and it uses other functions to set the baud rate and format. The set_baudrate function is complicated by the intricacies of the I2C clock format: int32_t i2c_set_baudrate(I2C i2c, int32_t baudrate) { i2c_enable(i2c, false); // use "fast" mode i2c->con = (i2c->con & ~0x06ul) | (0x02 << 1 & 0x06); // set frequency and duty uint32_t period = (I2CClock + baudrate / 2) / baudrate; i2c->fs_scl_lcnt = period * 3 / 5; // 40% duty cycle i2c->fs_scl_hcnt = period - period * 3 / 5; // set spike suppression i2c->fs_spklen = i2c->fs_scl_lcnt < 16 ? 1 : i2c->fs_scl_lcnt / 16; // set hold time uint32_t sda_tx_hold_count=(baudrate < 1000000)? For simplicity, fast mode is always used as it works at the lower speed 400kHz. The clock frequency is set by a count in hcnt which gives the high time and lcnt which gives the low time in terms of the main clock. For fast mode the duty cycle is around 40% and 2/5ths is a good approximation. Next we set some of the more subtle aspects of the clock. The spike suppression sets a threshold for noise pulses which are ignored. Finally the hold time for the data line is set – this is the time that the data line is held after the clock. Once initialized, all we need is a write function and a read function. Following the Pico SDK, it is worth creating functions with timeouts: int i2c_write_blocking_internal(I2C i2c, uint8_t addr, const uint8_t *src, size_t len, bool nostop,
First we set the address of the slave that the data is to be sent to – this does not start transmission and is not sent until the data is sent. The first and last byte of the data are special in that we need to send a start bit and perhaps a stop bit. The stop bit can be sent or suppressed. The instruction: i2c->data_cmd = startbitnext | stopbit | *src++; sends the next item of data and a start bit and stop bit as appropriate. This is also where we start measuring the time for the transaction so as to implement a timeout. Next we loop until the Tx FIFO is empty or a timeout occurs. If a timeout occurs the send loop is exited and the error reported. If the Tx FIFO empties we still have to check for any errors the hardware reported and pass the error code back to the calling program: int32_t i2c_handleAbort(I2C i2c, bool timeout, In this case if there wasn’t a timeout we return a negative value, by setting the high bit, and set the remaining bits of the abort register. If there was a timeout, we return a negative value with bit 30 set. A read with timeout follows very similar lines: int i2c_read_blocking_internal(I2C i2c, uint8_t addr, The first thing we have to do is make sure that there is space in the Tx FIFO buffer. Next we set the command register to the appropriate start and stop bits and attempt a read. To do this we have to loop until there is data in the FIFO – tested for by i2c_get_read_available. If this is successful then the data is in the data_cmd register and can be transferred to the buffer. However we still need to check for errors and timeout and report any status codes to the calling program. The functions used are: size_t i2c_get_write_available(I2C i2c) { return I2C_TX_BUFFER_DEPTH - (i2c->txflr); } size_t i2c_get_read_available(I2C i2c) { return i2c->rxflr; } uint64_t micros() { struct timespec ts; clock_gettime(CLOCK_MONOTONIC_RAW, &ts); uint64_t us = ts.tv_sec * 1000000 + We also need a global variable to keep track of when a stop bit needs to be sent: bool restart_on_next = false; This should be a per-controller variable, but for simplicity we assume that only one controller is active at any given time. If you need to break this rule then you need to store the state in an array with one location per I2C controller. |
||||
Last Updated ( Saturday, 19 July 2025 ) |