Pins

The only way a PIC microcontroller can have an effect on the world is through its pins, so pins are an important part of a PIC simulation. RPicSim exposes the modeling of pins that the MPLAB X simulator provides. Each RPicSim::Sim simulation object contains a collection of RPicSim::Pin objects, one for each external pin of the device. Using a Pin object, you can detect whether the pin is an input or an output. If it is an output, you can detect whether the output is driving high or driving low. If it is an input, you can set the simulated value of the input.

Getting a Pin object

The RPicSim::Sim#pin method can be called on your simulation object to retrieve a RPicSim::Pin object. If you are using RPicSim's RSpec integration, the pin method inside an example automatically redirects to the simulation object so you can use it easily like this:

it "works" do
  pin(:RA1)  # => returns a Pin object
end

The first argument of RPicSim::Sim#pin should be the name of the pin as a symbol. The allowed names come from the MPLAB X code, but they should match the names given in the PIC datasheet. For example, the PIC10F322 pin RA1 can be referred to by many names, including :RA1, :PWM2, :AN1, and :NCO1CLK.

Pin aliases

To make your tests readable and protect against future schematic changes, you should try to refer to pins by an application-specific name like “main output” instead of a datasheet name like RA1. RPicSim provides a feature to help you do this called a pin alias.

Within your simulation class definition, call def_pin to define your pins. For example:

class MySim < RRicSim::Sim
  #...

  def_pin :main_output, :RA1

end

This makes :main_output be an alias for :RA1. You can now access the Pin object by passing :main_output as the argument to RPicSim::Sim#pin:

pin(:main_output)

Defining a pin alias also adds a new method by the same name. This means that you can access the pin like this:

sim.main_output

Shortcuts for these methods are also available in RSpec thanks to RPicsim's RSpec integration, so you can simply write main_output instead of sim.main_output in any of your RSpec examples:

it "drives the main output high" do
  expect(main_output).to be_driving_high
end

Note that since these methods are available in many places, your pin names might conflict with names defined in other places.

Pin methods

Once you have a Pin object, you can call any of the methods listed in RPicSim::Pin on it. These methods allow you to ask about the state of the Pin and to set the simulated input value of an input pin.

Issues

The modelling of Pins provided by the MPLAB X simulator is fairly new and there are still some bugs in it. For example, you might need to clear the ANSELx bit of a pin in your firmware before trying to set its output value, or else the simulator will mistakenly think your pin is driving low. For more information, see the Known issues page.

PinMirror example

This section contains a simple example showing how to apply the information above and use RPicSim::Pin objects.

Here is a minimal MPASM assembly program for the PIC10F322 that continuously reads the value from an input pin (RA0) and copies it to an output pin (RA1):

  #include p10F322.inc
  __config(0x3E06)
  code 0
  clrf  ANSELA
  bcf   TRISA, 1
loopStart
  btfss PORTA, 0
  bcf   LATA, 1
  btfsc PORTA, 0
  bsf   LATA, 1
  goto  loopStart
  end

In spec/spec_helper.rb, we make a simulation class that points to the compiled COF file and defines some pin aliases:

require 'rpicsim/rspec'

class PinMirror < RPicSim::Pic
  use_device "PIC10F322"
  use_file File.dirname(__FILE__) + "../firmware/dist/firmware.cof"

  def_pin :main_input, :RA0
  def_pin :main_output, :RA1
end

In spec/pin_mirror_spec.rb, we write a simple test that changes the input and makes sure that the output changes accordingly:

require_relative 'spec_helper'

describe "PinMirror" do
  before do
    start_sim PinMirror
  end

  it "continuously mirrors" do
    main_input.set false
    run_cycles 10
    expect(main_output).to be_driving_low

    run_cycles 10
    expect(main_output).to be_driving_low

    main_input.set true
    run_cycles 10
    expect(main_output).to be_driving_high

    run_cycles 10
    expect(main_output).to be_driving_high

    main_input.set false
    run_cycles 10
    expect(main_output).to be_driving_low
  end
end

The calls to RPicSim::Sim#run_cycles are needed to give the simulated device enough time to react to the change on its input.