-
Notifications
You must be signed in to change notification settings - Fork 0
Home
Welcome to the karman-avionics wiki!
Here you'll find info about the project and resources on embedded programming.
Pages
Important Concepts
For the general Project Karman wiki, see here
I2C, or "Inter-Integrated Circuit", is the proprietary name for a communication protocol developed by Phillips Semiconductor (Now NXP semiconductors). Atmel, the developers of the AVR architecture, chose to call it TWI, or Twin Wire Interface, on their microcontrollers.
The protocol calls for two wires (hence Twin wire interface) to do "half-duplex" communication between integrated circuits. The two wires are SCL (Serial CLock) and SDA (Serial DAta). In order for it to work, there must be a Master and at least one Slave. It is important to know that Master controls the clock. The wikipedia page says that some Slaves can perform something called "clock stretching", but we won't cover that.
In order to send information to a particular slave, you need to know its address. The addresses for I2C-compliant devices are purchased from NXP Semiconductors, and are as such handled globally. Each integrated circuit (sensor, in our case) has its own unique device address, that acts as its "phone number."
I2C communication happens in a moderately complicated way. First, the Master pulls SDA low, to alert all the slaves that a communication is coming. Then, Master transmits the device address of the slave it wants to talk to on the SDA line. Timing is guaranteed by Master changing the value of SDA on the falling edge of SCL and the slaves reading the value of the SDA line (0 or 1) at the rising edge of SCL. See the timing diagram on wikipedia for a clearer picture). After the address, Master either transmits one or two bytes of data, or waits for the slave to send it data back.
Each I2C address is 7 bits long. The last bit of the byte is the R/W bit (read/write). A common thing to do is to treat the address as a 7 bit word, and add the last bit in just before sending it to the data register. For example, if your device's read address is b10001011, the write address will be b10001010. A better way to express this would be with hexadecimal, and with some fancy bit shifts. So, for example:
/* b10001010 == 0x8A */
/* b10001011 == 0x8B */
/* ^ ^ */
/* 8 B */
#define DEV_ADDR (0x45) /* This is the address above left shifted 1 */
/* b1000101X --> shift left 1, the last bit is a don't care
b01000101 --> The rightmost digit drops off
^ ^
4 5
*/
#define DEV_WRITE (DEV_ADDR << 1 | 0x00) /* 0x8A */
#define DEV_READ (DEV_ADDR << 1 | 0x01) /* 0x8B */A strong knowledge of hexadecimal numbers and how it relates to binary is very important.
In order to communicate with a TWI device, the hardware driver must know two things:
- The address of the slave
- The data you want to send
For most use cases, there are either two or three bytes needed to do that. For a write command, you would send three bytes:
- Slave Address (with write bit set)
- Register Address (where do you want to write?)
- Value (what do you want to write there?)
For a read command, you might only send two bytes:
- Slave Address (with read bit set)
- Register Address (what register do you want to read?)
The exact bytes that need to be sent will be specified in the datasheet of the device.
One important note is that, on AVR XMEGA, writing to the ADDR register will initiate the transaction. Therefore, the data must be ready to go before doing that.
After sending these bytes, the Master will wait for the slave to start toggling the SDA line, sending the data back. The incoming data ends up in the TWI DATA register. There it can be retrieved by an Interrupt Service Routine (ISR) and stored somewhere for retrieval by the driver or task.
SPI stands for Serial Peripheral Interface. It is a multi-wire communication protocol that many special function chips use to communicate with each other and a "Master". In common digital communications terminology, our Microcontroller Unit (MCU), i.e. the processor, will be acting as "Master", while all the sensors will be the "Slaves".
There are four data lines that matter for SPI. The first is the clock line, or SCK (Serial ClocK). This is toggled high and low by the Master module at a specific rate, or clock frequency. The resulting signal is a square wave, which tells the Slave at what rate to send or receive data. Next is MOSI, which stands for "Master Out Slave In". This is the data line that the Master writes out its information for the slave into. Any outputs from the microcontroller to the "peripheral" (sensor, in our case) will be sent along this wire. MISO, which stands for "Master In Slave Out", is the data line that lets the Slave send information to the Master. Note that there are two data lines, MISO and MOSI. Master and Slave can be talking at the same time, something known as "Full Duplex."
An important note with SPI is that you can have an arbitrary number of Slaves connected to the same wires. All Slaves that want to talk to the Master have the same MISO, MOSI, and SCK lines. These three wires act as a common "bus" connecting all the devices together. The last data line is the Chip Select (CS). Without this pin, nothing happens. The chip select is line that connects a pin on your Master to the CS pin on the device. By raising the Chip select pin high, you are telling that particular device that you want it to be listening. Each peripheral has its own chip select pin assigned to it. It is really bad to have multiple chip select pins high at the same time. Note that this pin is NOT part of the SPI hardware registers or implementation. It is generally a GPIO (General Purpose Input/Output) pin. It is the responsibility of the application developer to manage the chip select pin. This involves setting it high to talk to your device and pulling it low to signal that you're done.
One method for managing SPI devices would be to handle arbitration of the hardware registers to a "manager" module. This manager would manage requests for SPI to be used, and prevent any driver from pulling their chip selects high in an unsafe manner.
See section 13 of the manual.
The AVR XMEGA architecture has many "Ports" with pins associated with them. They are labeled A-E, for the physical ports. There is such a thing as a virtual port, but we don't need that for this application. The first application we'll need to use these ports for is the chip select pin for an SPI (Serial Peripheral Interface) device. This pin, when pulled high, tells the device it's connected to that it's the intended recipient of any SPI traffic on the line.
Each port has a set of eight pins in it. You'll know which pin is your device's chip select pin. For example, if your pin is pin 4 on port C, then you need to do two things to control it as a chip select. First, you need to set bit 4 of the data direction register for port C to 1. Each bit of the register corresponds to one pin on the port. To set (assert) pin 4 of port C to output, you would use the following code:
PORTC.DIRSET = PIN4_bm;This writes (1 << 4) to a special register that sets pin 4 of port c to be an output.
To assert/deassert your pin, you would use code formed like the following:
PORTC.OUTSET = PIN4_bm;
PORTC.OUTCLR = PIN4_bm;There are other registers defined for each port (and each pin!). One that should probably be done for every output pin is to set it to pull-up mode. This is done as follows:
PORTC.PIN4CTRL &= 0;
PORTC.PIN4CTRL |= PORT_OPC_PULLUP_gc;The code above does two things. One, it clears the control register. Pin Control registers are described on page 152 of the manual. Then we set the pin to use a pullup by using the "group mask" for OutPut Control Pullup. If we wanted, we could also set the pin to limit its slew rate, invert the input/output (i.e writing 1 actually deasserts the pin. Oh god), and change its input sense configuration. Since we're using it as an output, we don't really care about the input sensing. It's not high frequency, so we don't care about slew rate, and we want to assert the pin when we write 1 to it so we want all the other bits of the control register to be 0.
The proper syntax to set multiple bits/group masks for a register would be something like:
PORTC.PIN4CTRL |= (PORT_OPC_PULLUP_gc | PORT_ISC_RISING_gc | PORT_MASK_FOO_bm);The special DIRSET, OUTSET, and OUTCLEAR, and a few other registers are designed to be used with a bit mask. It is possible to set multiple bits at once, and to write multiple bits at once. You can even toggle every pin on the port! Just use the PORTn.DIR and PORTn.OUT registers. These control the entire port. Be careful! If someone else is trying to use the same port for their peripheral, you modifying the entire port could cause huge problems. It's way safer to just use the special registers.
In order to read the value currently at a pin you've set as an input, just read the PORTn.IN register as below:
uint8_t fooPortInput = PORTE.IN;
if ((fooPortInput & MY_COOL_BITMASK) == MY_COOL_BITMASK)
{
/* Do stuff that requires your bits to be set */
}Note that reading the PORTn.IN register gives you the value of every pin on the port, not just one.
See this blogpost for some additional info. Also see this pdf for an AVR application note.