Bit Banging

This experimental code is able to pulse a GIOP at about 750 KHz (2.4 MHz when built as Release)

        public static void PulseViaDirect()
        {
            using var rcc = Peripheral.CreateRcc(RccAddress.RCC);
            using var gpioa = Peripheral.CreateGpio(GpioAddress.GPIOA);

            rcc.MP_AHB4ENSETR[0] = true;

            gpioa.MODER[4] = ModerMode.Output;

            while(true)
            {
                gpioa.ODR[4] = true;
                gpioa.ODR[4] = false;
            }
        }

This the observed signal FYI

This code uses no interop and uses no pointers (unsafe etc), very little overhead at all in fact.

If I rebuild in Release mode, with optimizations and so on, it ramps up to 2.4 MHz:

This is possible due to various innovations and improvements that have been made to .Net and C# over the years.

6 Likes

This code when compiled as Release generates a 9.3 MHz square wave using the BSRR register:

public static void PulseViaDirect()
{
    using var rcc   = Peripheral.CreateRcc(RccAddress.RCC);
    using var gpioa = Peripheral.CreateGpio(GpioAddress.GPIOA);

    rcc.MP_AHB4ENSETR[0] = true;

    gpioa.MODER[4] = ModerMode.Output;
    gpioa.OSPEEDR[4] = SpeedMode.VeryHigh;

    uint pin_4_on  = 0x00000010;
    uint pin_4_off = 0x00100000;

    while (true)
    {
        gpioa.BSRR.AllBits = pin_4_on ;
        gpioa.BSRR.AllBits = pin_4_off ;
    }
}

and interestingly, that is almost an identical frequency to this native code:

int32_t wave(uint32_t addr) 
{
	uint32_t * ptr = (uint32_t*)(addr);
	
	while (1)
	{
		*ptr = 0x00000010;
		*ptr = 0x00100000;
	}
	
	return 0;
}

What clock frequency does the processor(s) on this board run at?

1 Like

Features:

  • 32bit ARM 650Mhz, 512MB DDR3
  • Display FPC connector
  • 2x mikroBUS headers
  • USB Host
  • User LED
  • Buzzer
  • 40-pin expansion header
  • microSD card slot
  • USB-C connector

I did a bit more on this stuff and have a crude polled timer example I can share. Like the other stuff this is basic proof of concept stuff:

public static void PulseViaTimer()
{
    using var rcc = Peripheral.OpenRcc();
    using var timer = Peripheral.OpenTimer(GeneralTimer.TIM2);
    using var gpioa = Peripheral.OpenGpio(GpioPort.GPIOA);

    // Enable clocks for GPIOA and TIM2

    rcc.MP_APB1ENSETR.TIM2EN = true;
    rcc.MP_AHB4ENSETR[GpioPort.GPIOA] = true;

    // Setup the GPIO pin 4 here

    gpioa.MODER[4] = Mode.Output;
    gpioa.OSPEEDR[4] = Speed.VeryHigh;
    gpioa.PUPDR[4] = Pull.Down;

    // Setup the timer registers

    timer.PSC = 1000;
    timer.ARR.ARR_LONG = 200;
    timer.CNT_NRE.CNT = 0;

    // Enable counting

    timer.CR1.CEN = true;

    // Crudely poll and toggle PA4 on each timer overflow

    while(true)
    {
        while(timer.SR.UIF == false)
        {
        }

        gpioa.BSRR = 0x_00000010;
        gpioa.BSRR = 0x_00100000;

        // Reset the flag

        timer.SR.UIF = false;
    }
}

This generates a 1KHz pulse, with an “on” pulse duration of about 400 ns when built in Debug configuration, when we build with Release configuration that pulse width drops to about 50 ns but of course the frequency remains the same because a timer is being used.

I played around with using the timer in compare mode, driving a port in alternate function mode. This works but the effort to define register structures and accessor properties is high and error prone even when one tries to be meticulous.

So I created a register source code generator and this can now generate an entire C# class for a peripheral and generate all of its registers, automatically, taking care of all the shifting, ANDing and ORing and so on, for all of the register sub-fields.

As I mentioned MS have made numerous improvements to C# that make working at this level both easier and more efficient at runtime, much more so than a few years ago.

There’s a simple “register description language” that is consumed and spurts out C# source, so the effort to gradually add more registers to peripherals is now very low.

This little sample causes a 1 MHz signal to appear on PA5:

  public static void PulseViaTimerGpio()
  {
      using var clock = Peripheral.OpenRcc();
      using var timer = Peripheral.OpenTimer(GeneralTimer.TIM2);
      using var gpioa = Peripheral.OpenGpio(GpioPort.A);

      // Enable clocks for GPIOA and TIM2

      clock.MP_APB1ENSETR.TIM2EN = true;
      clock.MP_AHB4ENSETR[GpioPort.A] = true;

      // Setup the GPIO pin 5 here

      gpioa.Moder[5] = Gpio.Mode.Alternate;
      gpioa.Ospeedr[5] = Gpio.Speed.Low;
      gpioa.Pupdr[5] = Gpio.Pull.None;
      gpioa.Afrl[5] = Gpio.AlternateFunction.AF1;

      // Setup the timer registers

      timer.Psc = 5;
      timer.Arr = 15;
      timer.Ccmr1.OC1M = TimerGeneral.OCMode.ToggleOnMatch;
      timer.Ccr1.AllBits = 0;
      timer.Ccer.CC1E = true;
      timer.Cnt_Nre = 0;
      timer.Cr1.CEN = true;

      // Sleep, freeing up the CPU

      Thread.Sleep(Timeout.Infinite);
  }

My core intertest here is in exploring how we can use C# 12 on .Net 8 to write low level code, avoiding interop (unless we’re talking to the OS) and exploiting C# performance oriented features.

2 Likes

Awesome. I would summarize your findings for others to utilize.

Can you try to set registers to get DAC (not ADC) working? :D.

Yes, that’s great goal actually given what I’ve got so far. I am refactoring the code daily and refining the patterns and so on to get the code to a high standard and it’s very clean and stable now so a new test is a good idea.

On the (very) back burner are dealing with interrupts and DMA, providing there’s no fundamental restriction here (like Linux and so on, limiting what user code can do) then these are things I very much want to explore.

So I’ll begin by playing with very basic DAC working, I’ve got a very good codebase now to attempt this, being able to generate robust error-free register access code from a simple descriptor language, makes this much less tedious too!

Every time I see this word, just want to go home.

This went well, the following simple code generates an approx. 1 KHz sawtooth on PA4:

        public static void PulseViaDac()
        {
            using var clocks = Peripheral.OpenRcc();
            using var gpioa = Peripheral.OpenGpio(GpioPort.A);
            using var dac1 = Peripheral.OpenDac(DacInstance.DAC1);

            clocks.MP_APB1ENSETR.DAC12EN = true;
            clocks.MP_AHB4ENSETR[(int)GpioPort.A] = true;

            gpioa.MODER[4] = Mode.Analog;

            dac1.CR.EN1 = true;

            uint data = 0;

            while (true)
            {
                dac1.DHR12R1.DACC1DHR = data++;
            }
        }

That’s the 12 bit register so the analog output is actually changing at around 4 MHz I guess.

1 Like

This somewhat “hacky” code generates an almost 4 KHz sine wave, again 12 bit resolution just cobbled together to see how it behaves:

        public static void PulseViaDac()
        {
            double[] sine = new double[4096];
            uint[] intsine = new uint[4096];

            double full = Math.PI * 2;

            double incr = full / 2048;

            for (int i = 0; i < 4096; i++)
            {
                sine[i] = Math.Sin(i * incr);
                intsine[i] = Convert.ToUInt32((sine[i] + 1) * 2048);
            }

            using var clocks = Peripheral.OpenRcc();
            using var gpioa = Peripheral.OpenGpio(GpioPort.A);
            using var dac1 = Peripheral.OpenDac(DacInstance.DAC1);

            clocks.MP_APB1ENSETR.DAC12EN = true;
            clocks.MP_AHB4ENSETR[(int)GpioPort.A] = true;

            gpioa.MODER[4] = Mode.Analog;

            dac1.CR.EN1 = true;

            uint data = 0;

            while (true)
            {
                for (int i = 0; i < 4096; i++)
                {
                    dac1.DHR12R1.DACC1DHR = intsine[i];
                }
            }
        }

Both of these DAC examples are built Release mode.

That jittering must simply be due to the OS scheduler preemptively interrupting the process from time to time, but nobody would generate a sine by such a CPU bound process anyway, not in real-life.

1 Like

Thank you a lot. I think next release Endpoint will have DAC :))).

With DMA support?