Dual-CPU targets (F28P65x) – GPIO direction

Digital Output assigned to the secondary CPU never drives the pin

(F28P65x dual-core) – bug, or are we missing something?

What we are seeing

On a dual-core F28P65x, a Digital Output block assigned to the secondary
CPU (CPU2)
does not drive its pin. The model logic itself runs correctly, but
the pin shows no electrical activity. The exact same model with the same
block assigned to CPU1 works perfectly.

Looking at the generated code, we noticed that no
GPIO_setDirectionMode(..., GPIO_DIR_MODE_OUT) call is generated for that
pin – on either core – so the pin’s direction appears to stay at its reset
default (input). That would explain the symptom, since writes to the
output latch have no effect on a pin configured as an input.

We then dug into the Lua sources to understand why, and found something
that might explain it (see below). However, we are not sure whether this
is an actual bug or whether we are simply configuring something
incorrectly – dual-CPU operation is new to us. If there is a required
setting or a supported way to do this that we have missed, we would be
grateful for a pointer.
We are posting the full details in case they are
useful either way.

Environment

  • PLECS version: [ 5.0.4]
  • TI C2000 Target Support Package: 2.3.2
  • Target: TMS320F28P65x (LAUNCHXL-F28P65X), dual-core
  • Tested on hardware with F28P65x only.

Steps to reproduce

We reproduced this with the official demo, without any of our own model
content:

  1. Open the example model simple_PIL_model.plecs
    (tsp_ti_c2000/demos/simple_PIL_model/).
  2. Remove PIL support (PIL is not supported on multi-CPU targets).
  3. Move the blink/Digital Output task (which drives GPIO12 in the
    unmodified demo) from CPU1 to CPU2.
  4. Build and flash. The LED no longer blinks. Moving the task back to CPU1
    makes it work again.

No other changes are needed – a single Digital Output block on a single
pin is enough.

What the generated code looks like

CPU1 file (Controller_CPU0_hal.c) – the core handoff and the pad
configuration are generated:

GPIO_setControllerCore(12, GPIO_CORE_CPU2);
...
GPIO_setPadConfig(12, GPIO_PIN_TYPE_STD);

CPU2 file (Controller_CPU1_hal.c) – this is the only line that
references pin 12 at all:

PLX_DIO_configureOut(DoutHandles[0], 12, &props);

GPIO_setDirectionMode does not appear in either file (0 occurrences in
both). As far as we can tell, PLX_DIO_configureOut()
(tsp_ti_c2000/ccs/28p65x/src/dio_28p65x.c) only sets up register
pointers and writes the initial passive value, and GPIO_setPadConfig()
only controls pull-up / open-drain / invert – neither one sets the
direction.

For comparison, with the same block on CPU1 the generated code does
contain GPIO_setDirectionMode(12, GPIO_DIR_MODE_OUT);.

What we found in the Lua sources (this is where we may be wrong)

We may well be misreading the code generator, so please take this part
with a grain of salt – but this is what led us to suspect a gap rather
than a mistake on our side.

lua/blocks/syscfg.lua appears to be the place where
GPIO_setDirectionMode() is emitted, gated by this condition:

-- GPIO direction may need to be configured by primary or secondary CPU
if (gpio.core == globals.target.getGpioDirConfigCpuNumber(self.cpu)) then
  if gpio.cbx_direction.equals('OUTPUT') then
    f.PostInitCode:append(
      'GPIO_setDirectionMode(%d, GPIO_DIR_MODE_OUT);' % {gpio.unit})
  ...

The header comment of that same file describes the intended mechanism:

Instances of this module are created implicitly in Coder.lua.
For most targets, a single instance is created to generate initialization
code for the primary CPU.
For some targets, a second instance is created to generate initialization
code for the secondary CPU, e.g. for configuring the direction of GPIO.

In lua/Coder.lua, however, only one syscfg instance seems to be
created for the C28x targets, always for the primary CPU (cpu = 0),
while the F29 (29H85x) family gets one instance per CPU:

if T.targetMatches({'2806x', '2833x', '2837x', '2838x', '28003x',
                     '28004x', '280013x', '28P55x', '28P65x'}) then
    FactoryBlock:makeBlock('syscfg', 0)
elseif T.targetMatches({'29H85x'}) then
  for _, cpu in ipairs(Registry.UsedCpus) do
    FactoryBlock:makeBlock('syscfg', cpu)
  end
...

And getGpioDirConfigCpuNumber() appears to be overridden only in
lua/targets/TI29H85x.lua:

function T.getGpioDirConfigCpuNumber(cpu)
  -- on this chip, the GPIO direction is configured by the CPU that owns the GPIO
  return T.getTiCpuNumber(cpu)
end

TI28P65x.lua has no such override (it does local T = require('targets.TI28')), so it would fall back to the base
implementation in lua/targets/TI28.lua:

function T.getGpioDirConfigCpuNumber(cpu)
  -- default is CPU1
  return 1
end

If we read this correctly, that would mean the gating condition always
evaluates to gpio.core == 1 on F28P65x. Our pin is registered with
core = 2 – this is visible in the generated output itself, since the
emitted line GPIO_setControllerCore(12, GPIO_CORE_CPU2) is produced from
the format string 'GPIO_CORE_CPU%(core)d'. With 2 == 1 never being
true, the direction-mode line would never be emitted, which matches what
we observe. It would also explain why the CPU1 case works (core = 1).

This suggests to us that the per-CPU syscfg mechanism described in the
header comment is implemented for F29H85x but not (yet) for the C28x
dual-core devices. But again – we may be misreading how the generator
works.

Questions

  1. Is a Digital Output block on the secondary CPU a supported
    configuration on F28P65x? If not, could the Coder emit a warning or an
    error instead of generating silently non-functional code?
  2. If it is supported – is there a setting or an additional block we are
    missing that would cause the direction to be configured?

Thanks a lot in advance – and apologies if we have simply overlooked
something obvious.

Update

By using an C-Script one the base task with:

Declation:
#ifdef \__TMS320C28XX_\_
extern void GPIO_setDirectionMode(unsigned long pin, int pinIO);
#endif

Start:
#ifdef \__TMS320C28XX_\_
GPIO_setDirectionMode(12, 1); /* 1 = GPIO_DIR_MODE_OUT */;
#endif

the Blink task on CPU2 works.