Skip to content

Onboarding Task

David Lenfesty edited this page Oct 12, 2021 · 9 revisions

Onboarding task

Not a comprehensive guide to ChibiOS, or embedded development, hopefully just a step-by-step guide that gives you a vague idea of how things work.

Install dev environment

TODO I'm not even sure what the actual process is rn, I just wing it :P

Get your bearings.

Take a look around the repo, and just see where things are, if it doesn't make sense, just take note of it for now.

Read through blinky code and try and understand what's going on. (Start with blinky/src/main.c).

Take a peek through the ChibiOS documentation, try and find some of the functions that we're using.

Peek through STM32F303 reference manual (make very clear that you don't need to read the thing cover-to-cover, just like, read GPIO or something).

Copy blinky project

Copy blinky to new folder named <yourname>_onboarding.

Explain how to copy/set VSCode configurations (using c_cpp_properties.py script and launch.json)

Configure

Start in cfg.

Edit cfg/halconf.h

Change HAL_USE_ADC to TRUE

#define HAL_USE_ADC                     TRUE

(Explain here what this does, it enables the ADC HAL module so everything gets built)

Edit cfg/mcuconf.h

Change STM32_ADC_USE_ADC1 to TRUE

(Explain what this does, it creates the ADC1 object or whatever)

Edit src/main.c

Add the following:

static struct hal_adc_config adc1_config = {
  .difsel = 0,
};

static ADCConversionGroup measure_conversion = {
  .circular = false,
  .num_channels = 1,
  .end_cb = NULL,
  .error_cb = NULL,
  .cfgr = 0,
  .tr1 = 0,
  .smpr = {0b111 << ADC_SMPR1_SMP1_Pos, 0},
  .sqr = {1 << ADC_SQR1_SQ1_Pos, 0, 0, 0},
};

ADC configuration is needed for device-specific stuff. (ChibiOS can't encapsulate everything)

Not strictly necessary here, as the default is exactly what we're doing here.

DIFSEL is all 0's so all the ADC inputs are single-ended (i.e. referenced to ground)

Maybe explain how we know what the struct looks like (alternative is clicking through the defines)

  • ADCConfig: ChibiOS/os/hal/include/hal_adc.h
  • adc_lld_config_fields: somewhere in ports?
  • check ChibiOS/os/hal/ports/STM32
  • ChibiOS/os/hal/ports/STM32/STM32F3xx for the F303
  • Check platform.mk, will include different versions of ADC driver
    • Specifically include $(CHIBIOS)/os/hal/ports/STM32/LLD/ADCv3/driver.mk
  • Now can look in that folder, and see what adc_lld_config_fields is defined as
  • This can also be done just by clicking through in a properly configured VSCode

ADCConversionGroup is similar, except here there's some common configuration to set as well

Common stuff:

  • Circular mode means it will keep starting over again. We want a single reading.
  • We only want to use 1 ADC channel (measure 1 pin)
  • No ending callback, we're waiting for it to finish
  • No error callback, assuming no errors - we aren't doing anything fancy

Peripheral-specific:

  • default CFGR is fine, take a look at the RM
  • No threshold value to set, this is a special feature of this ADC, see RM if curious
  • Set the highest sample time for the most accurate value on channel 1
  • Use channel 1 as first capture in sequence - length is set by driver, so we can ignore setting that

Now configure hardware:

Add the following after starting heartbeat thread:

  palSetPadMode(GPIOA, 0, PAL_MODE_INPUT_ANALOG);
  adcStart(&ADCD1, &adc1_config);

Reading ADC value

Add a new thread:

static THD_WORKING_AREA(waAdcThread, 256);
static THD_FUNCTION(AdcThread, arg) {
  (void)arg;
  while (1) {
    adcsample_t val;
    msg_t rc = adcConvert(&ADCD1, &measure_conversion, &val, 1);
    if (rc == MSG_OK) {
      float voltage = ((float) val / 4096) * 3.3;
    } else {
      // Should never get here
      chSysHalt("ADC Error - should be unreachable");
    }
  }
}

Explain that adcConvert starts a conversion and waits for it to finish. Perhaps explain async functions as well.

Then explain how voltage is calculated.

Start the thread in main after configuration:

  palSetPadMode(GPIOA, 0, PAL_MODE_INPUT_ANALOG);
  adcStart(&ADCD1, &adc1_config);
  chThdCreateStatic(waAdcThread, sizeof(waAdcThread), NORMALPRIO,
                    AdcThread, NULL);

Sets pad mode to an analog input - GPIO has to be configured correctly so that different peripherals can use them. There is a default configuration in board.h in the SPEEDY_CONFIG_F3 folder, but it doesn't include this one.

Then we start ADC thread, and give it access to the waAdcThread, where the stack is placed. (maybe brief overview/reminder of stack?)

Send value over CAN

Hijacking a v0 message type, situation should be improved with v1, but for now making new types is annoying and out of scope

include message type:

include "uavcan/equipment/power/CircuitStatus.h"

Now back in AdcThread:

extern CanardInstance canard_instance; // Code smell, this should be wrapped with a mutex of some sort
static THD_WORKING_AREA(waAdcThread, 256);
static THD_FUNCTION(AdcThread, arg) {
  (void)arg;
  uint8_t transfer_id = 0;

  while (1) {
    adcsample_t val;
    msg_t rc = adcConvert(&ADCD1, &measure_conversion, &val, 1);
    if (rc == MSG_OK) {
      float voltage = ((float) val / 4096) * 3.3;

      uint8_t msg_buf[UAVCAN_EQUIPMENT_POWER_CIRCUITSTATUS_MAX_SIZE];
      uavcan_equipment_power_CircuitStatus status = {
        .circuit_id = 0,
        .voltage = voltage,
        .current = 0,
        .error_flags = 0,
      };
      uint32_t len = uavcan_equipment_power_CircuitStatus_encode(&status, (void *)msg_buf);
      canardBroadcast(&canard_instance,
                      UAVCAN_EQUIPMENT_POWER_CIRCUITSTATUS_SIGNATURE,
                      UAVCAN_EQUIPMENT_POWER_CIRCUITSTATUS_ID,
                      &transfer_id,
                      0,
                      (const void *)msg_buf,
                      len);

      transfer_id += 1;
    } else {
      // Should never get here
      chSysHalt("ADC Error - should be unreachable");
    }
  }
}

Explain we need to get canard_instance from somewhere, and that this is actually bad and there should be wrappers around it.

Explain transfer_id, and how it needs to be static and monotonic.

Message buffer of maximum message size, then create the message. Use the generated encode function to serialize into that buffer (saving the length). Finally, broadcast message with signature and ID. Note that this just enques message onto a list, transmission will be handled in can_handle_forever.

Change out blinky

TODO

But generally I want to modify it so the blinky does a quick blink, one for each node it sees a Heartbeat from.

This will need:

  • Heartbeat subscription
  • message passing into heartbeat thread (probably just a global var that hearbeat only reads from)
    • probably want to introduce concept of message passing (mailboxes, events, etc.)
  • Little list structure to hold up to N node IDs (maybe all 127?)
  • Basic blinky logic
Clone this wiki locally