JeeH 6.0.0

JeeH 6.0.0

June 29, 2024
News

JeeH version 6.0.0 release notes and highlights:

This is a major new release of JeeH. A lot of things have changed:

  • Now you see them, now you don’t: tasks are gone again. The routing of task messages was too complex. Threads are unchanged. Messages can still be sent to threads and to device drivers.

  • Instead of waiting for messages coming back as reply, the sender can now set up a callback method, which is called when the reply is received. For this to work, a thread needs to have while (true) sys::recv(); as its main loop. There is a (template) function to store a callback into a message:

      Message& setCallback(T* obj, void (T::* fun)(Message&));
    

    The message is also returned, which allows using the following idiom:

      Message m {...};
      sys::send(m.setCallBack(...));
    

    Callbacks are more flexible than Task::process() methods, and they tend to get called at just the right time, i.e. when the originating thread is idling.

  • There is a new “synchronous” driver mode, which uses WFE while waiting for a request to finish. It keeps control in the current thread, but still allows interrupts and higher-priority threads to temporarily take over. With WFE, the CPU will enter sleep mode while DMA takes care of the data transfers.

  • There are new drivers for I2C and SPI, and they now come in four flavours:

    1. GPIO - These are bit-banged versions which do not need any hardware support, IRQs, or DMA. See the i2c::Gpio and spi::Gpio classes. These drivers will work with any spare GPIO pins.
    2. POLL - Busy-looping hardware-supported versions, which again do not use IRQs or DMA. See the i2c::Poll and spi::Poll classes.
    3. SYNC - This code uses DMA and interrupts to get things done, but it’s still blocking: a call will block until the request is complete.
    4. CALL - In this mode, the driver is fully asynchronous, but requests are wrapped in sys::call interfaces, again blocking until the request is complete. The difference is that other threads can run, i.e. full multi-threading remains active. If there is no work, the CPU can sleep or enter a stop mode which keeps the H/W + DMA running.

    Note that CALL is merely a thin wrapper around the SYNC driver code. For fully asynchronous use, you can send messages to the (SYNC) driver as before, and then go do other things, with a reply sent back later (and its callback activated if set).

  • The implementations of these four “modes” are very different, but their APIs are identical. This allows starting a new application using GPIO or POLL mode, and then switching to SYNC or CALL mode later.

  • There is some (very preliminary) code to automate the choice of sleep mode when the system is idle. Each driver presumably knows what run mode it needs to be able to handle its current requests (as present in its queue(s)). There are default jeeh::lowestPower() and jeeh::resumePower() functions which can be replaced by an application to customise the way power levels are switched.

  • There are preliminary versions of a new “three-mode” UART driver (uart::Poll, uart::Sync, and uart::Call), but the implementation is output-only so far.

  • The code generator in JeeH has been extended to help simplify the use of different modes. As example, here are two lines in a platformio.ini file which configure I2C for a Nucleo-G431KB board:

      board_mode = sync
      board_i2c = N:I2C1 P:B7:OH4,A15 F:170 D:1 L:CH T:3 R:4 C:17,16 O:1
    

    (JeeH’s code generator is a script to “fill in” lines starting with //CG in header files)

    This is then used as follows in a defs.h header to easily switch modes:

      //CG[ board i2c
      #define I2C_NAME  I2C1
      #define I2C_PINS  "B7:OH4,A15"
      #define I2C_FREQ  170
      #define I2C_TYPE  I2C1.ADDR,DMA1.ADDR,3-1,4-1
      #define I2C_CONF  { ena::I2C1,170,Irq::I2C1_EV,Irq::I2C1_ER,1-1,17,16 }
      //CG]
    
      //CG1 board mode
      #define MODE_SYNC 1
    
      #if MODE_GPIO
      i2c::Gpio i2cBus;
      #elif MODE_POLL
      i2c::Poll<I2C_NAME.ADDR> i2cBus (ena::I2C_NAME, I2C_FREQ);
      #elif MODE_SYNC
      i2c::Sync<I2C_TYPE> i2cBus (I2C_CONF);
      #elif MODE_CALL
      i2c::Call<I2C_TYPE> i2cBus (I2C_CONF);
      #endif
    

    Since all driver modes have the same API, the rest of the app is unaffected. For actual examples, see the examples/i2c/, examples/spi/, and examples/uart/ folders.

  • The I2C, SPI, and new UART drivers all use common DMA setup code (see jee/dma.h), but there are several subtle differences which prevent more extensive code re-use, despite the superficially very similar code in JeeH’s include/jee/{i2c,spi,uart}-dev.h header files.

I’ve probably missed several other major changes in JeeH v6, but the above at least gives an indication of where all this is headed. Everything in JeeH v6 is work-in-progress and bleeding edge code at the moment …

Onwards!