3
votes

Overview: I'm trying to program a microbit with Ada using the Ada Drivers Library and I can't understand how to use the i2c functions to establish communications with another chip. I'd like to establish a simple demo so I can understand what's happening because the demos in the components directory of the Ada Drivers Library are going over my head (I'm pretty new to Ada too and that doesn't help matters).

The simplest i2c demo in the Ada Drivers Library appears to be for the AK8963 three axis compass (located in /components/src/motion/ak8963/). But that's still going over my head and I don't have the chip to run and debug the code.

Here's what I've tried: I've created two different demos with arduinos. In both demos the transmitter sends an 'A' and then a 'B' all the way to 'Z' and then loops back to 'A'. In the first demo the master transmits the next character every 500 ms and the slave receives it. And in the second demo the master requests the next character every 500 ms and the slave transmits it.

My demos are adapted from the arduino Wire examples found here and here.

3

3 Answers

2
votes

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.
0
votes

I have not been able to test the code below, but it should at least give you some direction. Please note that the micro:bit acts as a master. I don't think the micro:bit can act as a slave on a I2C bus (but I might be wrong here). Also note that you may have to change the path to the microbit_zfp.gpr in the project file.

default.gpr

with "../Ada_Drivers_Library/boards/MicroBit/microbit_zfp.gpr";

project Default 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;

end Default;

main.adb

with MicroBit.Display; use MicroBit.Display;
with MicroBit.Time;    use MicroBit.Time;
with MicroBit.I2C;     use MicroBit.I2C;
with HAL.I2C;          use HAL.I2C;

procedure Main is
begin

   MicroBit.I2C.Initialize (S400kbps);   --  Change to desired speed.

   declare    
      Ctrl   : constant Any_I2C_Port := MicroBit.I2C.Controller;
      Addr   : constant I2C_Address := 16#08#;    --  Change to correct address.
      Data   : I2C_Data (0 .. 0);
      Status : I2C_Status;
   begin
      loop

         --  Data to be send (here: character 'x').
         Data (0) := Character'Pos ('x');

         --  Display a dot to indicate where we are.
         Display ('.');

         --  Send 1 byte of data (length of Data array is 1).
         Ctrl.Master_Transmit (Addr, Data, Status);

         --  Additional status checking could be done here....

         --  Display a colon to indicate where we are.
         Display (':');

         --  Wait for response (1 byte as the length of the Data array is 1).         
         Ctrl.Master_Receive (Addr, Data, Status);

         --  Check status, and display character if OK.
         if Status = Ok then
            Display (Character'Val (Data (0)));
         else
            Display ('!');
         end if;

         -- Take a short nap (time in milliseconds).
         Sleep (250);

      end loop;
   end;

end Main;
0
votes

I have a current interest in the BBC micro:bit and i2c and tried the program, having earlier got a program to build and upload successfully. Building with these two files have should have easier, still not got it to build, struggling with GPS ... I'll try again soon...