Compared to SPI, I2C (a.k.a Two-Wire or sometimes just refered to it's Arduino name, Wire) is even simpler - it's only 2 pins, SCL and SDA. I2C ports are a huge pain to emulate or bitbang due to the somewhat convoluted interface with multiple exceptions (repeated start! clock stretching!) and the bidirectional SDA line.
For that reason, being able to create new Wire's is awesome
How Wire is Created Now
Luckily, Atmel & Arduino did a really great job with structuring SERCOMs so you can create and assign new ones. You can find the actual code that is used to create the "Wire" SERCOM in Wire.cpp
TwoWire Wire(&PERIPH_WIRE, PIN_WIRE_SDA, PIN_WIRE_SCL); void WIRE_IT_HANDLER(void) { Wire.onService(); }
The macros are see in variants.h
#define PIN_WIRE_SDA (20u) #define PIN_WIRE_SCL (21u) #define PERIPH_WIRE sercom3 #define WIRE_IT_HANDLER SERCOM3_Handler
You can see here that Wire is of type TwoWire and if you expand the macros:
TwoWire Wire(&sercom3, 20, 21);
We can match this up with the pins we know are used for I2C:
PA22 D20 / SDA SERCOM3.0 SERCOM5.0 PA23 D21 / SCL SERCOM3.1 SERCOM5.1
You can see that the first argument (&sercom3) does in fact match our observation that I2C is on SERCOM3. The second and third arguments match with the SDA & SCL pins.
Unlike Serial or SPI, there are no passed in arguments for the MUXing. I2C, rather, has 'fixed' pads:
Pad 0 will always be SDA and pad 1 will always be SCL
OK so let's make a new I2C SERCOM already
Now we're ready, lets try creating a new SERCOM and testing it out
Let's make a Wire device on SERCOM 1
Since we have to use pads 0 & 1, lets check the mux table:
Pin Arduino 'Pin' SERCOM SERCOM alt ----------------------------------------- PA18 D10 SERCOM1.2 SERCOM3.2 PA16 D11 SERCOM1.0 SERCOM3.0 PA19 D12 SERCOM1.3 SERCOM3.3 PA17 D13 SERCOM1.1 SERCOM3.1
SDA will be on SERCOM1.0 D11 and SCL will be on SERCOM1.1 D13
The definition for the new Wire will look like this:
TwoWire Wire(&sercom1, 11, 13);
Lets test it out!
#include <Wire.h> TwoWire myWire(&sercom1, 11, 13); void setup() { Serial.begin(115200); myWire.begin(); } uint8_t i=0; void loop() { Serial.println(i); myWire.beginTransmission(0x55); // start transmission to device myWire.write(i); // send a byte myWire.endTransmission(); // end transmission, actually sending }
OK ... that wont work. Why? Good question! Check out those pin definitions in the variants.cpp file:
const PinDescription g_APinDescription[]= ... // 11 { PORTA, 16, PIO_TIMER, (PIN_ATTR_DIGITAL|PIN_ATTR_PWM|PIN_ATTR_TIMER), No_ADC_Channel, PWM2_CH0, TCC2_CH0, EXTERNAL_INT_0 }, // TCC2/WO[0] // 13 (LED) { PORTA, 17, PIO_PWM, (PIN_ATTR_DIGITAL|PIN_ATTR_PWM), No_ADC_Channel, PWM2_CH1, NOT_ON_TIMER, EXTERNAL_INT_1 }, // TCC2/WO[1]
The first argument is the port (e.g. PORTA or PORTB), the second is the port's pin number, e.g. PORTA, 16 -> PA16. The third argument 'sets' the type of pinmux we're going to use.
In this case, these are set to be used as PIO_TIMER & PIO_PWM
You can do that by calling pinPeripheral(pinnumber, function) which is an internal function but hey we're being hardk0re
Note that I've also put in code to control an MCP4725 by hand, since its a very simple I2C device (write 2 bytes to 0x40 to set an analog voltage out)
#include <Wire.h> #include "wiring_private.h" // pinPeripheral() function TwoWire myWire(&sercom1, 11, 13); #define MCP4725_CMD_WRITEDAC (0x40) #define MCP4725_ADDR (0x62) void setup() { Serial.begin(115200); myWire.begin(); // Assign pins 13 & 11 to SERCOM functionality pinPeripheral(11, PIO_SERCOM); pinPeripheral(13, PIO_SERCOM); } uint8_t i=0; void loop() { Serial.println(i); myWire.beginTransmission(MCP4725_ADDR); // start transmission to device myWire.write(MCP4725_CMD_WRITEDAC); myWire.write(i++); myWire.write((uint8_t)0x0); // bottom four bits are 0x0 myWire.endTransmission(); // end transmission, actually sending }
We can also try SERCOM2:
Pin Arduino 'Pin' SERCOM SERCOM alt ----------------------------------------- PA14 D2 SERCOM2.2 SERCOM4.2 PA09 D3 SERCOM0.1 SERCOM2.1 PA08 D4 SERCOM0.0 SERCOM2.0 PA15 D5 SERCOM2.3 SERCOM4.3
We will have to have SDA on D4 (SERCOM2.0), SCL on D3 (SERCOM2.1)
#include <Wire.h> #include "wiring_private.h" // pinPeripheral() function TwoWire myWire(&sercom2, 4, 3); #define MCP4725_CMD_WRITEDAC (0x40) #define MCP4725_ADDR (0x62) void setup() { Serial.begin(115200); myWire.begin(); // Assign pins 4 & 3 to SERCOM functionality pinPeripheral(4, PIO_SERCOM_ALT); pinPeripheral(3, PIO_SERCOM_ALT); } uint8_t i=0; void loop() { Serial.println(i); myWire.beginTransmission(MCP4725_ADDR); // start transmission to device myWire.write(MCP4725_CMD_WRITEDAC); myWire.write(i++); myWire.write((uint8_t)0x0); // bottom four bits are 0x0 myWire.endTransmission(); // end transmission, actually sending }
Note that since pin 3 and 4 use a SERCOM alt mux, we have to pass in PIO_SERCOM_ALT rather than PIO_SERCOM when we call pinPeripheral()
Page last edited March 08, 2024
Text editor powered by tinymce.