I figured it out.
Let's start with the two Arduino programs to prove that the Arduino code works.
Arduino Slave transmit:
/*
Sends the next letter of the alphabet with each
request for data from master.
Watch the serial monitor to see what's happening.
*/
#include <avr/wdt.h>
#include <Wire.h>
// A note about I2C addresses.
// The Ada program is looking for the slave on address 16
// but this code says the slave is on 8.
// What's happening? As best as I can tell it works
// like this:
// 16 in binary is 10000. But arduino strips the read/write bit
// (which is the last bit) off of the address so it becomes
// 1000 in binary. And 1000 in binary is 8.
const int SLAVE_ADDRESS = 8;
byte letter = 65; // letter A
unsigned long counter = 0;
void setup()
{
wdt_reset();
wdt_enable(WDTO_8S);
Serial.begin(9600);
Serial.println("beginning");
Wire.begin(SLAVE_ADDRESS); // join i2c bus
Wire.onRequest(requestEvent); // register event
}
void loop()
{
wdt_reset();
counter++;
if(counter % 1000 == 0)
{
// Display a heart beat so we know the arduino has not hung.
Serial.print("looping: ");
Serial.println(counter);
}
delay(5);
}
// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent()
{
// send the current letter on I2C
Wire.write(letter);
Serial.print("transmitting: ");
Serial.println(char(letter));
letter++;
if(letter > 90) // if greater than Z
{
letter = 65; // reset to A
}
}
Arduino Master receive:
/*
Requests a character from the slave every 500 ms and prints it
to the serial monitor.
*/
#include <avr/wdt.h>
#include <Wire.h>
const int SLAVE_ADDRESS = 8;
void setup()
{
wdt_reset();
wdt_enable(WDTO_8S);
Wire.begin(); // join i2c bus
Serial.begin(9600);
}
void loop()
{
// reset the watchdog timer
wdt_reset();
// request one byte from the slave
Wire.requestFrom(SLAVE_ADDRESS, 1);
while(Wire.available()) // slave may send less than requested
{
// receive a byte as character
char c = Wire.read();
Serial.println(c);
}
delay(500);
}
These two Arduino sketches will happily pass characters all day. Now replace the Arduino master receiver with the Ada version below and physically disconnect the Arduino master receiver.
Ada master receiver (main.abd):
-- Request a character from the I2C slave and
-- display it on the 5x5 display in a loop.
with HAL.I2C; use HAL.I2C;
with MicroBit.Display; use MicroBit.Display;
with MicroBit.I2C;
with MicroBit.Time;
procedure Main is
Ctrl : constant Any_I2C_Port := MicroBit.I2C.Controller;
Addr : constant I2C_Address := 16;
Data : I2C_Data (0 .. 0);
Status : I2C_Status;
begin
MicroBit.I2C.Initialize (MicroBit.I2C.S100kbps);
if MicroBit.I2C.Initialized then
-- Successfully initialized I2C
Display ('I');
else
-- Error initializing I2C
Display ('E');
end if;
MicroBit.Time.Delay_Ms (2000);
MicroBit.Display.Clear;
loop
-- Request a character
Ctrl.Master_Receive (Addr => Addr, Data => Data, Status => Status);
-- Display the character or the error
if Status = Ok then
Display (Character'Val (Data (0)));
else
MicroBit.Display.Display (Status'Image);
end if;
-- Give the user time to read the display
MicroBit.Time.Delay_Ms (1000);
MicroBit.Display.Clear;
MicroBit.Time.Delay_Ms (250);
end loop;
end Main;
And here is the Ada project file for completeness:
with "..\..\Ada_Drivers_Library\boards\MicroBit\microbit_zfp.gpr";
project I2C_Master_Receive_Demo is
for Runtime ("ada") use Microbit_Zfp'Runtime ("Ada");
for Target use "arm-eabi";
for Main use ("main.adb");
for Languages use ("Ada");
for Source_Dirs use ("src");
for Object_Dir use "obj";
for Create_Missing_Dirs use "True";
package Compiler renames Microbit_Zfp.Compiler;
package Linker is
for Default_Switches ("ada") use Microbit_Zfp.Linker_Switches & ("-Wl,--print-memory-usage", "-Wl,--gc-sections", "-U__gnat_irq_trap");
end Linker;
package Ide is
for Program_Host use ":1234";
for Communication_Protocol use "remote";
for Connection_Tool use "pyocd";
end Ide;
end I2C_Master_Receive_Demo;
Tips:
- you need to observe the I2C address offsets (16 in Ada = 8 on Arduino in my case). See the explanation in the comments of the slave transmit arduino code above. It took me a long time to figure that out.
- nothing worked with three devices connected to the I2C bus, even if one of them was not powered. I don't know exactly what's happening there but I suspect its related to documentation stating that the I2C bus cannot pull its lines back to HIGH. Some documentation recommends placing a resistor on both I2C lines connected to your source voltage so the line voltages return to HIGH after the devices pulls them low.
- this work would be easier with an oscilloscope. I could have figured out this problem much more quickly if I had had one.