summaryrefslogtreecommitdiff
path: root/src/i2c.c
blob: 25b2d0fd83f35eeca7befcb7ead7ccd736d5451c (plain)
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
#include <stdio.h>
#include <avr/io.h>
#include <util/twi.h>
#include <util/delay.h>
#include <stdbool.h>
#include <string.h>
#ifdef DEBUG_BUILD
#include "debug.h"
#endif

#include "i2c.h"


/** I2C notes
 * TWDR: data address shift register
 *  contians address or data bytes to be transmitted/recieved
 *  ACK bit not accesible
 * TWAR: matches recieved addresses
 * TWCR: control register, sets settings
  *  TWINT: tw interrupt flag 
  *  also is dependent on GIE in SREG
  * TWSR: TWI status register (only matters if TWI interurupt flag asserted)
  
 * Steps simplified:
 * SEND START
  * Send START with TWCR
  * Set TWINT in TWCR to 1; as this clears the flag
  * TWSR will then say start has begun
 * Load SLA+W into TWDR (what is this? TODO)
 * Write something to TWCR to initiate send? (make sure twint is set after)
 * poll TWSR interrupt bit to see if we recieve a message (or figure out interrupts)
 * 
 * Note: You may be wondering why I'm not handling i2c errors.
 * I have limited time, and I don't think it's nessesary unless I start running into them.
 *
 * Some I may work on handling later.
 * 
  */

void error() {}

void i2c_init() {
  TWBR = 12;
  TWSR &= 0b11111100;
}


void i2c_start(uint8_t addr, bool rw) {
  TWCR = (_BV(TWSTA) | _BV(TWEN) | _BV(TWINT)); //send start signal
  loop_until_bit_is_set(TWCR, TWINT);
  if((TWSR & 0xf8) != TW_START) { 
    #ifdef DEBUG_BUILD
      printf("Couldn't set start condition?\n");
    #endif
    error();
  }

  TWDR = (addr << 1) | rw;
  TWCR = _BV(TWINT) | _BV(TWEN); 
  loop_until_bit_is_set(TWCR, TWINT);

  if((TWSR & 0xf8) != TW_MT_SLA_ACK) { 
    #ifdef DEBUG_BUILD
      printf("unhandled NACK in address transmission\
          TWCR: 0x%x\
          TWSR: 0x%x\n", TWCR, (TWSR & ~(0b11)));
    #endif
    error();
  }

}

void i2c_stop() { TWCR = (_BV(TWSTO) | _BV(TWEN) | _BV(TWINT)); }

void i2c_send(uint8_t byte) {
  TWDR = byte; //fist packet is address 
  TWCR = _BV(TWINT) | _BV(TWEN); //send over the byte
  loop_until_bit_is_set(TWCR, TWINT);
 
  if((TWSR & 0xf8) != TW_MT_DATA_ACK) { 
    #ifdef DEBUG_BUILD
      printf("unhandled NACK in data transmission\
          TWCR: 0x%x\
          TWSR: 0x%x\n", TWCR, (TWSR & ~(0b11)));
    #endif
    error();
  }
}

uint8_t i2c_recv() {
  uint8_t value;
  loop_until_bit_is_set(TWCR, TWINT);
  value = TWDR;
  if((TWSR & 0xf8) != TW_MR_SLA_ACK) {
#ifdef DEBUG_BUILD
    printf("Error recieving byte from i2c device\n");
#endif
  error();
  }
  return value;
}

//does NOT control start/stop, simply reads TWDR

uint8_t i2c_read_reg_addr16(uint8_t device, uint16_t addr) {
  uint8_t value;

  i2c_start(device, I2C_WRITE); 
  i2c_send((uint8_t)addr & 0xff);
  i2c_send((uint8_t)addr >> 8);
  i2c_start(device, I2C_READ);
  value = i2c_recv();
  i2c_stop();

  return(value);
}

void i2c_write_reg(uint8_t device_addr, uint8_t device_reg, uint8_t value) {
  i2c_start(device_addr, I2C_WRITE);
  i2c_send(device_reg);
  i2c_send(value);
  i2c_stop();
}

void i2c_write_reg_multi(uint8_t device_addr, uint8_t device_reg, size_t len, uint8_t *values) {
  i2c_start(device_addr, I2C_WRITE);
  i2c_send(device_reg);
  for(size_t on_byte = 0; on_byte < len; on_byte++) i2c_send(values[on_byte]);
  i2c_stop();
}