aboutsummaryrefslogtreecommitdiffstats
path: root/src/temperature/i2c.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/temperature/i2c.c')
-rw-r--r--src/temperature/i2c.c334
1 files changed, 334 insertions, 0 deletions
diff --git a/src/temperature/i2c.c b/src/temperature/i2c.c
new file mode 100644
index 0000000..25c9166
--- /dev/null
+++ b/src/temperature/i2c.c
@@ -0,0 +1,334 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2020 Matthias P. Braendli
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+*/
+
+#include "i2c.h"
+
+#include "stm32f4xx_conf.h"
+#include "stm32f4xx_i2c.h"
+#include "stm32f4xx.h"
+#include "FreeRTOS.h"
+#include "FreeRTOSConfig.h"
+#include "task.h"
+#include "semphr.h"
+
+/* I2C 1 on PB9 and PB6
+ * See pio.txt for PIO allocation details */
+const uint16_t GPIOB_PIN_SDA = GPIO_Pin_9;
+const uint16_t GPIOB_PIN_SCL = GPIO_Pin_6;
+
+#define trigger_fault(x) do { __asm__ volatile("nop"); } while (1);
+
+
+static int i2c_init_done = 0;
+static int i2c_error = 0;
+
+static I2C_TypeDef* const I2Cx = I2C1;
+
+static SemaphoreHandle_t i2c_semaphore;
+
+static void i2c_device_init(void);
+
+/* According to I2C spec UM10204:
+ * 3.1.16 Bus clear
+ * In the unlikely event where the clock (SCL) is stuck LOW, the preferential
+ * procedure is to reset the bus using the HW reset signal if your I2C devices
+ * have HW reset inputs. If the I2C devices do not have HW reset inputs, cycle
+ * power to the devices to activate the mandatory internal Power-On Reset (POR)
+ * circuit.
+ *
+ * If the data line (SDA) is stuck LOW, the master should send nine clock
+ * pulses. The device that held the bus LOW should release it sometime within
+ * those nine clocks. If not, then use the HW reset or cycle power to clear the
+ * bus.
+ */
+static int i2c_recover_count;
+static void i2c_recover_from_lockup(void)
+{
+ i2c_recover_count++;
+ if (i2c_recover_count > 3) {
+ trigger_fault(FAULT_SOURCE_I2C);
+ }
+
+ I2C_SoftwareResetCmd(I2Cx, ENABLE);
+ for (int i = 0; i < 0x4fff; i++) {
+ __asm__ volatile("nop");
+ }
+ I2C_SoftwareResetCmd(I2Cx, DISABLE);
+}
+
+static void i2c_device_init(void)
+{
+ // Configure I2C SCL and SDA pins.
+ GPIO_InitTypeDef GPIO_InitStructure;
+ GPIO_InitStructure.GPIO_Pin = GPIOB_PIN_SCL | GPIOB_PIN_SDA;
+ GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
+ GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
+ GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
+ GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
+ GPIO_Init(GPIOB, &GPIO_InitStructure);
+
+ GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_I2C1);
+ GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_I2C1);
+
+ // Reset I2C.
+ RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1, ENABLE);
+ RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1, DISABLE);
+
+ // configure I2C1
+ I2C_InitTypeDef I2C_InitStruct;
+ I2C_InitStruct.I2C_ClockSpeed = 100000; //Hz
+ I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
+ I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2; // 50% duty cycle
+ I2C_InitStruct.I2C_OwnAddress1 = 0x00; // not relevant in master mode
+
+ // disable acknowledge when reading (can be changed later on)
+ I2C_InitStruct.I2C_Ack = I2C_Ack_Disable;
+
+ I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
+ I2C_Init(I2C1, &I2C_InitStruct);
+
+ // enable I2C1
+ I2C_Cmd(I2C1, ENABLE);
+
+ i2c_recover_count = 0;
+}
+
+void i2c_init()
+{
+ if (i2c_init_done == 1) {
+ return;
+ }
+
+ i2c_semaphore = xSemaphoreCreateBinary();
+
+ if ( i2c_semaphore == NULL ) {
+ trigger_fault(FAULT_SOURCE_I2C);
+ }
+ else {
+ xSemaphoreGive(i2c_semaphore);
+ }
+
+ RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
+ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
+
+ i2c_device_init();
+ i2c_init_done = 1;
+}
+
+static int i2c_check_busy_flag(void)
+{
+ const TickType_t i2c_timeout = pdMS_TO_TICKS(1000);
+ const TickType_t time_start = xTaskGetTickCount();
+
+ while (I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY)) {
+ const TickType_t time_now = xTaskGetTickCount();
+
+ if (time_now - time_start > i2c_timeout) {
+ i2c_error = 1;
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static int i2c_check_event(uint32_t event)
+{
+ const TickType_t i2c_timeout = pdMS_TO_TICKS(1000);
+ const TickType_t time_start = xTaskGetTickCount();
+
+ while (!I2C_CheckEvent(I2Cx, event)) {
+ const TickType_t time_now = xTaskGetTickCount();
+
+ if (time_start + i2c_timeout < time_now) {
+ i2c_error = 1;
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static int i2c_start(uint8_t device, uint8_t direction)
+{
+ I2C_GenerateSTART(I2Cx, ENABLE);
+
+ // wait for bus free
+ if (!i2c_check_event(I2C_EVENT_MASTER_MODE_SELECT)) {
+ I2C_GenerateSTART(I2Cx, DISABLE);
+ return 0;
+ }
+
+ I2C_Send7bitAddress(I2Cx, device << 1, direction);
+
+ /* wait for I2C1 EV6, check if
+ * either Slave has acknowledged Master transmitter or
+ * Master receiver mode, depending on the transmission
+ * direction
+ */
+ uint32_t event = 0;
+ if (direction == I2C_Direction_Transmitter) {
+ event = I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED;
+ }
+ else if (direction == I2C_Direction_Receiver) {
+ event = I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED;
+ }
+ else {
+ trigger_fault(FAULT_SOURCE_I2C);
+ }
+
+ if (!i2c_check_event(event)) {
+ return 0;
+ }
+
+ return 1;
+}
+
+static int i2c_send(uint8_t data)
+{
+ I2C_SendData(I2Cx, data);
+
+ // wait for I2C1 EV8_2 --> byte has been transmitted
+ return i2c_check_event(I2C_EVENT_MASTER_BYTE_TRANSMITTED);
+}
+
+int i2c_write(uint8_t device, const uint8_t *txbuf, int len)
+{
+ if (i2c_init_done == 0) {
+ trigger_fault(FAULT_SOURCE_I2C);
+ }
+
+ int success = i2c_check_busy_flag();
+
+ if (success) {
+ success = i2c_start(device, I2C_Direction_Transmitter);
+ }
+
+ if (success) {
+ for (int i = 0; i < len; i++) {
+ success = i2c_send(txbuf[i]);
+ if (!success) {
+ break;
+ }
+ }
+
+ I2C_GenerateSTOP(I2Cx, ENABLE);
+ success = i2c_check_event(I2C_EVENT_MASTER_BYTE_TRANSMITTED);
+ }
+
+ return success;
+}
+
+static int i2c_read_nobuscheck(uint8_t device, uint8_t *rxbuf, int len)
+{
+ if (i2c_init_done == 0) {
+ trigger_fault(FAULT_SOURCE_I2C);
+ }
+
+ if (i2c_start(device, I2C_Direction_Receiver)) {
+ for (int i = 0; i < len; i++) {
+ if (i == len-1) {
+ I2C_AcknowledgeConfig(I2Cx, DISABLE);
+ }
+ else {
+ I2C_AcknowledgeConfig(I2Cx, ENABLE);
+ }
+
+ // wait until one byte has been received, possibly timout
+ if (!i2c_check_event(I2C_EVENT_MASTER_BYTE_RECEIVED)) {
+ I2C_GenerateSTOP(I2Cx, ENABLE);
+ return 0;
+ }
+
+ if (i == len-1) {
+ I2C_GenerateSTOP(I2Cx, ENABLE);
+ }
+
+ rxbuf[i] = I2C_ReceiveData(I2Cx);
+ }
+ return len;
+ }
+
+ return 0;
+}
+
+int i2c_read(uint8_t device, uint8_t *rxbuf, int len)
+{
+ int success = i2c_check_busy_flag();
+
+ if (success) {
+ success = i2c_read_nobuscheck(device, rxbuf, len);
+ }
+
+ return success;
+}
+
+int i2c_read_from(uint8_t device, uint8_t address, uint8_t *rxbuf, int len)
+{
+ if (i2c_init_done == 0) {
+ trigger_fault(FAULT_SOURCE_I2C);
+ }
+
+ int success = i2c_check_busy_flag();
+
+ if (success) {
+ success = i2c_start(device, I2C_Direction_Transmitter);
+ }
+
+ if (success) {
+ success = i2c_send(address);
+ }
+ // Don't do a STOP
+
+ if (success) {
+ success = i2c_read_nobuscheck(device, rxbuf, len);
+ }
+
+ return success;
+}
+
+
+int i2c_transaction_start()
+{
+ if (i2c_init_done == 0) {
+ trigger_fault(FAULT_SOURCE_I2C);
+ }
+ return xSemaphoreTake(i2c_semaphore, portMAX_DELAY) == pdTRUE;
+}
+
+void i2c_transaction_end()
+{
+ if (i2c_init_done == 0) {
+ trigger_fault(FAULT_SOURCE_I2C);
+ }
+
+ if (i2c_error) {
+ i2c_recover_from_lockup();
+
+ i2c_error = 0;
+ }
+
+ xSemaphoreGive(i2c_semaphore);
+}
+