Jerry Petrey
Published © GPL3+

Muon Detector

Using an STM32F429 board and two Geiger counters to detect muons from cosmic ray collisions in our atmosphere. ###See 7-3-19 updates! ###

Intermediate2 hours6,452
Muon Detector

Things used in this project

Hardware components

DIY Geiger Counter Kit Module
Can get from Banggood.com for $40 each.
×2
STMicroelectronics NUCLEO-F429ZI
×1
Adafruit 2.8" TFT Touch Shield for Arduino
Note: I have recently discovered that some new Display boards from Adafruit have some solder jumpers set wrong to work with the Nucleo board. Please read the updated "The MuonHunter Project.docx" in the project zip file to learn how to correct this!
×1

Software apps and online services

GNAT Community
AdaCore GNAT Community
Using GNAT 2017 for ARM and GNAT 2018 for Windows tools (GPS).

Story

Read more

Schematics

Muon Detector Block Diagram

Code

MuonHunter Project

ADA
The Ada main driver and muon_detector module are shown here. All this plus the support files are in the zip file.

MuonHunter Software Notes
The main program is muonhunter.adb which just ‘withs’ (includes) the package Driver were all the action takes place.
Inside Driver’s body is the Controller task that does all the work after some initialization takes place. The one-time initialization that takes place is things like configuring the interrupts for the two Geiger Counter boards (located in the muon_detector package), initializing the LCD display and writing some information to the LCD display. Once in the main endless loop of controller task, the software just updates the elapsed time once per second and displays the most recent counts for the detectors and muon counts. This loop is adjusted to take 1/5 of a second to run so every 5 loops equals one second of elapsed time. The muon_detector package has the interrupt service routines which updates the counts variables that the Driver task displays.
The Geiger Counter board’s output pin is high until a detection occurs, then it goes low for about 0.8 milliseconds. In order to detect a muon, we need to see both detectors low at the same time. Since muon can only come from above (not through the Earth and up through the detector stack), it is really only necessary to detect a near simultaneous detection from the upper detector followed by the lower detector but I have chosen to just look for either direction and therefore it doesn’t matter which order the detectors are placed. If an interrupt occurs from one detector, it just checks the other detector’s interrupt line and if it is low also, that must have bene a muon triggering both at nearly the same time; otherwise only the applicable detector count is incremented.
The rest of the included software is all the support for the ARM chip on the Nucleo board included by AdaCore. I have my versions of some of these support files. The LCD driver is one, because I had implemented it before AdaCore provided one and I have some extensive graphics primitives that I like to use in some of my applications.
Hopefully, looking at this application and the examples AdaCore provides will make you consider Ada in a future application. It is a powerful, easy to use language with a lot of support now for the ST series of ARM processors.

-----------------------------------------------------------------------------------------------------------------------------------------------
High energy cosmic rays from space constantly bombard our planet. When these particles (usually protons) hit our atmosphere, they cause a cascade of secondary particle to be generated. The most energetic of these is the muon (basically a heavy electron with about 200times the electron’s mass and traveling at just under the speed of light). The lifetime of a muon is only about 2.2 microseconds but due to the relativistic speeds and because of the phenomenon of time dilation, it actually lives quite a bit longer and has time to reach the surface of the Earth from about 30 - 50 km up where the collisions usually take place. Because of the high energy at relativistic speed, these particles can trigger two Geiger-Muller tubes with a sheet of lead between them almost simultaneously whereas normal background radiation and other particles will only trigger the two detectors randomly and background gamma radiation is not energetic enough to pass through both tubes (and the lead) and trigger them both. Thus, when you get a coincident detection, it is very certain to be a muon! You can aim the array at different parts at the sky; if pointed towards the sun, the count may be slightly higher but most of the really high energy cosmic ray particles that cause the muon showers come from beyond our solar system – like supernovas in other galaxies.
You can normally expect about 1 muon per minute per square cm at sea level and about 1.4 muons per minute per square cm at 2000' with the muons traveling approx. 0.994 c and time dilation applied.

I use a ST-Microsystems Nucleo-F429ZI board with a STM32F429ARM chip programmed in Ada to detect the coincident detections from the two low-cost Geiger counters and display the count data.

I am using AdaCore’s GNAT Ada 2017 (with some of the compiler tools from the 2018 version) and a modified version of the Ada drivers library from AdaCore since I have been doing my own driver development in parallel with AdaCore for the ARM processors for several years.

To build this project you just need to stack the two Geiger counter boards one on top of the other and it is best to put a layer of 1/16 inch thick (or greater) of a lead sheet between the two detectors as well as above and below them. This helps reduce the chance of any other radiation from possibly firing both tubes at once and being counted as a muon. In general, only the high energy muons will have the energy to be detected by the top detector and still have enough energy to also trigger the bottom tube. The lead sheets also help insure that this is the case. There won’t be ant muon coming from the bottom up because they can’t travel all the way though the Earth (remember they have a very short half-life and can only make it from about 30 km or so from the upper atmosphere where they are generated).
After mounting these detectors in some way that suits you along with the Nucleo board and hooking up the detector outputs to the Nucleo board according the provided block diagram, it should be ready to use. By pointing the stacked array of detectors, you can aim the muon hunter to some degree. If you take it to higher altitudes, you will find it registers a higher muon count since there is a greater number of them that haven’t yet decayed the closer you get to where they are created in the upper atmosphere. Far fewer make it all the way down to sea level.



---------------------------------------------------------------------------------------------------------------------------------------------------------
Using Arm Ada on a PC (it is also available for Linux but this description is for Windows)
Note: I have used the AdaCore 2017 version for all the ARM support. The 2018 version had too many changes in structure for me to modify all my support for it since I have also been developing many of the drivers for my own use and I use a different structure for building, etc.
I do use the 2018 version of the compiler tools (like GPS) but you don’t have to. If you do, you need to install the 2018 version of GNAT Ada for Windows as well and then, in GPS, set the Build-Settings-Toolchain options to use the 2017\bin version for the compiler path and the 2018\bin for the tools path. (You can just use the 2017 version of everything if you wish to avoid this extra work. I just like the 2018 version of the Ada IDE, GPS, a little better but the 2017 version is fine too.) The 2018 AdaCore version of the Ada for ARM was changed quite a bit and especially the layout of the runtime code so because I have so much based on the 2017 version, I have stuck with that for a while and used it on this project.
Download and install GNAT Ada 2107 from https://www.adacore.com/download/more.
Make sure you choose the x86 Windows (32 bits) version from the Platform select. It is best to use ‘Run as Administrator’ for all these installs but do not allow the installers to set the environment paths if you are going to install both the 2017 and 2018 versions.
Then download and install the Win32Ada 2017 from the same location (Note: when installing, specify the location of the proper Ada compiler when asked – should be C:\GNAT\2017).
Finally, download and install the ARM bareboards support by selecting ARM ELF format (hosted on Windows) from the Platform box. All of these three are installed in C:\GNAT\2017.
There are some demos in C:\GNAT\2017\share\examples\gnat-cross. I usually create my applications in C:\Projects\GNAT_Projects, so you can place the entire MuonHunter files there (except for the runtime zip file (which has the ‘ravenscar-sfp-stm32f429nucleo’ runtime for the 429 Nucleo board) – it should be unzipped in the GNAT Ada runtimes directory which is typically C:\GNAT\2017\arm-eabi\lib\gnat. Then open the Muonhunter project file (MuonHunter.gpr) with GPS and select Project-Build All. You should get a clean build with no errors or warnings.
You will then see the executable ‘muonhunter’ appear in the project directory. You then need to double click on the ‘create hex.bat’ script and that will create a muonhunter.hex file which can then be downloaded into the Nucleo board using the STM32 ST-Link Utility via the USB ST-Link connection on the board. This utility is available on ST’s website – I have included the 4.2 version installer in the project files zip file.
I place all my source files in one directory in the project directory rather than having them spread out over multiple directories as AdaCore does since I use a lot of different boards and many of my own files and I find it easier to duplicate all the files in each board’s directory. That way if I need to make special mods to some files for a specific case, it won’t affect other boards unless I want to move them there. There may also be some files in the ‘src’ directory that aren’t currently used but might be in the future.
Note that AdaCore has the latest ARM drivers at https://github.com/AdaCore/Ada_Drivers_Library but my drivers are based on an earlier 2017 version and modified in some cases since I have been developing these for years along with AdaCore and sometimes I mix my versions with theirs since I have a lot of code for different ARM boards (over 2 dozen) and I don’t always want to change them all because AdaCore decided on a different approach.

----------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------
--  Muon_Detector
--
--  This package defines an abstract data machine representing the detectors.
--  It uses an interrupt handler to track counts.
------------------------------------------------------------------------------
with STM32.GPIO;    use STM32.GPIO;
with STM32.Device;  use STM32.Device;
with Ada.Interrupts.Names;  use Ada.Interrupts;


package Muon_Detector is

   Detector1_Point  : GPIO_Point renames PG1;
   Detector2_Point  : GPIO_Point renames PG2;
   
   Muon_Count       : Natural := 0;
   Detector1_Counts : Natural := 0;
   Detector2_Counts : Natural := 0;
   
   Detector1_Interrupt : constant Interrupt_ID := Names.EXTI1_Interrupt;
   Detector2_Interrupt : constant Interrupt_ID := Names.EXTI2_Interrupt;

   procedure Configure_Detectors_GPIO;
   --  Configures the GPIO port/pin for the muon detectors. Sufficient
   --  for polling the button, and necessary for having the button generate
   --  interrupts.

   procedure Initialize (Use_Falling_Edge : Boolean := True) with
     Pre  => not Initialized,
     Post => Initialized;

   function Initialized return Boolean;
   
   procedure Clear_Counts1;
   
   procedure Clear_Counts2;

end Muon_Detector;



------------------------------------------------------------------------------
--  Muon_Detector
------------------------------------------------------------------------------
with STM32.EXTI;    use STM32.EXTI;

package body Muon_Detector is

   pragma Warnings (Off, "*is not referenced");
   

   EXTI_Line_1 : constant External_Line_Number := 
                         Detector1_Point.Interrupt_Line_Number;
   EXTI_Line_2 : constant External_Line_Number := 
                         Detector2_Point.Interrupt_Line_Number;

   Initialization_Complete : Boolean := False;
  
   
   -------------------
   -- Clear_Counts1 --
   -------------------
   procedure Clear_Counts1 is
   begin
      Detector1_Counts := 0;
   end Clear_Counts1;
   
   
   -------------------
   -- Clear_Counts2 --
   -------------------
   procedure Clear_Counts2 is
   begin
      Detector2_Counts := 0;
   end Clear_Counts2;
   
   
   --------------------------------
   -- Configure_Detectors_GPIO --
   --------------------------------
   procedure Configure_Detectors_GPIO is
      Config : GPIO_Port_Configuration;
   begin
      Enable_Clock (Detector1_Point);
      Enable_Clock (Detector2_Point);

      Config.Mode      := Mode_In;
      Config.Resistors := Pull_Up;

      Detector1_Point.Configure_IO (Config);
      Detector2_Point.Configure_IO (Config);
   end Configure_Detectors_GPIO;
   
   
   ---------------
   -- Detectors --
   ---------------
   protected Detectors is
      pragma Interrupt_Priority;


   private

      procedure Interrupt1;
      pragma Attach_Handler (Interrupt1, Detector1_Interrupt);
      
      procedure Interrupt2;
      pragma Attach_Handler (Interrupt2, Detector2_Interrupt);

   end Detectors;

   
   ---------------
   -- Detectors --
   ---------------
   protected body Detectors is

      ----------------
      -- Interrupt1 --
      ----------------
      procedure Interrupt1 is
      begin
         Clear_External_Interrupt (EXTI_Line_0);
         Detector1_Counts := Detector1_Counts + 1;
         if not Set (Detector2_Point) then
            Muon_Count := Muon_Count + 1;
         end if;
      end Interrupt1;

      
      ----------------
      -- Interrupt2 --
      ----------------
      procedure Interrupt2 is
      begin
         Clear_External_Interrupt (EXTI_Line_1);
         Detector2_Counts := Detector2_Counts + 1;
         if not Set (Detector1_Point) then
            Muon_Count := Muon_Count + 1;
         end if;
      end Interrupt2;

   end Detectors;

   
   ----------------
   -- Initialize --
   ----------------
   procedure Initialize (Use_Falling_Edge : Boolean := True) is
      Edge : constant STM32.EXTI.External_Triggers :=
               (if Use_Falling_Edge then Interrupt_Rising_Edge
                else Interrupt_Falling_Edge);
   begin
      Muon_Count       := 0;
      Detector1_Counts := 0;
      Detector2_Counts := 0;
      
      Enable_Clock (Detector1_Point);
      Enable_Clock (Detector2_Point);

      Detector1_Point.Configure_IO
        ((Mode        => Mode_In,
          Output_Type => Open_Drain,
          Speed       => Speed_50MHz,
          Resistors   => (if Use_Falling_Edge then Pull_Up else Pull_Down)));

      --  Connect the detector's pin to the External Interrupt Handler
      Detector1_Point.Configure_Trigger (Edge);
      
      Detector2_Point.Configure_IO
        ((Mode        => Mode_In,
          Output_Type => Open_Drain,
          Speed       => Speed_50MHz,
          Resistors   => (if Use_Falling_Edge then Pull_Up else Pull_Down)));

      --  Connect the detector's pin to the External Interrupt Handler
      Detector2_Point.Configure_Trigger (Edge);
      
      Initialization_Complete := True;
   end Initialize;

   
   -----------------
   -- Initialized --
   -----------------
   function Initialized return Boolean is (Initialization_Complete);

end Muon_Detector;


------------------------------------------------------------------------------
--                                                                          --
--                     MuonHunter Driver  Package                           --
--                                                                          --
-- Created by:  Jerry Petrey 10-13-18                                       --
-- Modified by: Jerry Petrey 12-30-18                                       --
--                                                                          --
------------------------------------------------------------------------------
with Ada.Real_Time;            use Ada.Real_Time;
with HAL;                      use HAL;
with STM32.Board;              use STM32.Board;
with STM32.Device;             use STM32.Device;
with STM32.User_Button;        use STM32.User_Button;
with LCD_ILI934x_Fast;         use LCD_ILI934x_Fast;
with ILI934x_Fonts;            use ILI934x_Fonts;
with Muon_Detector;            use Muon_Detector;
-- for testing
--with Random;                   use Random;

package body Driver is

   pragma Warnings (Off, "*is not referenced");
   pragma Warnings (Off, "*is not modified");

   package Board  renames STM32.Board;
   package LCD    renames LCD_ILI934x_Fast;
   package Device renames STM32.Device;


   ---------------------------------------------------------------------------
   --    * Update with new build if need to verify correct build loaded *   --
   ---------------------------------------------------------------------------
   Ver_Num  : constant String := "3.5";
   ---------------------------------------------------------------------------

   -- default 9341 LCD colors for test
   BkGnd    : constant UInt16 := LCD_WHITE;
   Txt      : constant UInt16 := LCD_BLACK;

   --Ran_Num  : UInt32;

   --Det1_Count : Natural := 0;
   --Det2_Count : Natural := 0;

   type Time_Type is mod 60;

   -- for time running
   Hours   : Natural   := 0;
   Minutes : Time_Type := 0;
   Seconds : Time_Type := 0;


   ---------------------------------------------------------------------------
   -- Initialize_LCD
   -- Init the LCD
   ---------------------------------------------------------------------------
   procedure Initialize_LCD is
   begin
      Init_LCD (Port        => LCD_SPI,
                Color       => LCD_Blue,
                Orientation => Landscape_2,
                Font        => Font11_18);
   end Initialize_LCD;



   --------------------------------------------------------------------------
   -- Controller
   --
   -- The main controller task to perform all the action.
   --------------------------------------------------------------------------
   task body Controller is
      -- period should be 200 ms for five loops per second but is adjusted
      -- slightly to account for other activities within main loop
      Period      : constant Time_Span := Milliseconds (172);
      Next_Start  : Time               := Clock;
      Loop_Count  : Natural            := 1;
      Update_Time : Boolean            := False;
   begin
      Board.Configure_User_Button_GPIO;

      Initialize_LEDs;
      -- Initialize button
      STM32.User_Button.Initialize;

      -- init detector interrupts
      Configure_Detectors_GPIO;
      Muon_Detector.Initialize;

      Initialize_LCD;
      --Init_RNG;  -- for testing

      Fill_Screen (LCD_Yellow);
      Set_Default_Font (Font11_18);
      Set_Default_ForeGnd_Color (LCD_Blue);
      Set_Default_BackGnd_Color (LCD_Yellow);

      LCD.Put_String (X1   => 90,
                      Y1   => 5,
                      Str  => "Muon Detector");

      Set_Default_ForeGnd_Color (LCD_Black);
      Set_Default_Font (Font7_10);

      LCD.Put_String (X1   => 5,
                      Y1   => 30,
                      Str  => "Version - " & Ver_Num);
      Set_Default_Font (Font11_18);

      -- show counts
      LCD.Put_String (X1   => 10,
                      Y1   => 50,
                      Str  => "Detector 1 count: ");
      LCD.Put_String (X1   => 200,
                      Y1   => 50,
                      Str  => Integer'Image (Detector1_Counts));

      LCD.Put_String (X1   => 10,
                      Y1   => 80,
                      Str  => "Detector 2 count: ");
      LCD.Put_String (X1   => 200,
                      Y1   => 80,
                      Str  => Integer'Image (Detector2_Counts));

      Set_Default_ForeGnd_Color (LCD_MAGENTA);
      LCD.Put_String (X1   => 10,
                      Y1   => 110,
                      Str  => "Muon count: ");
      Set_Default_ForeGnd_Color (LCD_Black);
      LCD.Put_String (X1   => 200,
                      Y1   => 110,
                      Str  => Integer'Image (Muon_Count));

      -- show elapsed time
      Set_Default_ForeGnd_Color (LCD_RED);
      LCD.Put_String (X1   => 10,
                      Y1   => 140,
                      Str  => "Elapsed Time:");
      Set_Default_ForeGnd_Color (LCD_Black);
      LCD.Put_String (X1   => 10,
                      Y1   => 160,
                      Str  => "Hours:   ");
      LCD.Put_String (X1   => 10,
                      Y1   => 180,
                      Str  => "Minutes: ");
      LCD.Put_String (X1   => 10,
                      Y1   => 200,
                      Str  => "Seconds: ");

      --delay until Clock + Milliseconds (1000);

      ------------------------
      -- Setup for main loop
      ------------------------
      Next_Start := Clock;
      ------------------------------------------------------------------------
      loop

         -- for testing
--           Ran_Num := Random.Random (100);
--           if Ran_Num > 10 and Ran_Num < 20 then
--              Det1_Count := Det1_Count + 1;
--           end if;
--           if Ran_Num > 80 and Ran_Num < 90 then
--              Det2_Count := Det2_Count + 1;
--           end if;
--           if Ran_Num > 0 and Ran_Num < 2 then
--              Muon_Count := Muon_Count + 1;
--           end if;

         -- update counts
         LCD.Put_String (X1   => 200,
                         Y1   => 50,
                         Str  => Integer'Image (Detector1_Counts));
         LCD.Put_String (X1   => 200,
                         Y1   => 80,
                         Str  => Integer'Image (Detector2_Counts));
         LCD.Put_String (X1   => 200,
                         Y1   => 110,
                         Str  => Integer'Image (Muon_Count));
         -- update time and display it
         if Loop_Count > 5 then
            Seconds     := Seconds + 1;
            Loop_Count  := 1;
            Update_Time := True;
            if Seconds = 0 then
               Minutes := Minutes + 1;
               if Minutes = 0 then
                  Hours := Hours + 1;
               end if;
            end if;
         else
            Loop_Count := Loop_Count + 1;
         end if;

         if Update_Time then
            -- hours
            LCD.Put_String (X1   => 100,
                            Y1   => 160,
                            Str  => Natural'Image (Hours));
            -- minutes
            LCD.Put_String (X1   => 100,
                            Y1   => 180,
                            Str  => Natural'Image (Natural (Minutes)) & " ");
            -- seconds
            LCD.Put_String (X1   => 100,
                            Y1   => 200,
                            Str  => Natural'Image (Natural (Seconds)) & " ");
            Update_Time := False;
         end if;

         Next_Start := Next_Start + Period;
         delay until Next_Start;

      end loop;

   end Controller;

end Driver;

MuonHunter_Zip

ADA
This is a zip file of the project directory. It includes all the necessary files and documentation and the files that go into the GNAT ARM Ada runtime location (usually C:\GNAT\2017\arm-eabi\lib\gnat) plus some notes on the software
No preview (download only).

Credits

Jerry Petrey

Jerry Petrey

1 project • 13 followers

Comments