Author Topic: IO port access in uTasker  (Read 12346 times)

Offline mhoneywill

  • Full Member
  • ***
  • Posts: 173
    • View Profile
IO port access in uTasker
« on: March 06, 2009, 01:16:03 AM »
Hi Mark,

Is there a standard way to access / configure IO ports in uTasker, I have noticed some functions in debug.c like fnSetPort and fnInitialisePorts but cannot find any documentation on them. Should I modify these or have you defined a standard set of functions within uTasker for I/O port coonfiguration and access?

Cheers

Martin

Offline mark

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3236
    • View Profile
    • uTasker
Re: IO port access in uTasker
« Reply #1 on: March 06, 2009, 01:54:00 AM »
Hi Martin

I did in fact do a little work on this very recently and I would like to try to do as much as possible with macros. They need to be easy to use (portable between processors if possible) and understandable.

These two are suggestions (examples) to configure bits of a port to work as general purpose inputs and outputs.

First how they look in code:

_CONFIG_PORT_OUTPUT(A, PORTA_BIT0);
_CONFIG_PORT_INPUT(B, PORTB_BIT2);


I think that these are quite easy to use and are understandable. The first is configuring an output to work on bit 0 of port A and the second is configuring an input to work on bit 2 of port B.

Now to look under the bonnet: The LM3Sxxxx requires more than just setting the data direction register. There are three things to do to ensure that these work:
- ensure that the port is clocked
- ensure that the bit is configured as digital port
- set the required DDR

These macros do it (I think...):

#define _CONFIG_PORT_OUTPUT(ref, pin)  RCGC2 |= CGC2_GPIO##ref; GPIODEN_##ref |= pin; GPIODIR_##ref |= pin
#define _CONFIG_PORT_INPUT(ref, pin)   RCGC2 |= CGC2_GPIO##ref; GPIODEN_##ref |= pin; GPIODIR_##ref &= ~pin



The next speciality of the ports (as GPIO) is that they don't have an output holding register. Consider a port which is presently an input.
If you do PORT = 0x55 and then DDR = 0xff the output will no (necessarily) be 0x55. It will in fact drive the value that it had seen as input before the DDR was set to output - this means that the signals will not change when measured before and after the DDR command. There is some code which needed to be rewritten to respect the fact that first the DDR must be written and then the data value.

I support something like
#define _DRIVE_PORT_OUTPUT_VALUE(ref, pin, value) GPIODIR_##ref |= pin; GPIODATA_##ref = value
would ensure the order, but it doesn't address masking of data bits - see next point

You probably know that a port can be written at one of 255 different register locations, depending on which bits (s) you wish to change. Presently I have always written to the last location so that all bits are affected. This makes life easier but in some cases one may like to save an instruction or two in a critical routine and use an access to just particular bits. I haven't studied the best way to offer this yet...

So basically there is work open here. Any suggestions on a nice interface (based on portable macros if possible) are this welcome!
Note that the Luminary ports are very flexible and have some unique features. Whether they are really needed is another question but it is certainly quite a challenge to define such an interface!

regards

Mark



« Last Edit: March 16, 2009, 06:41:34 PM by mark »

Offline mark

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3236
    • View Profile
    • uTasker
Re: IO port access in uTasker
« Reply #2 on: March 09, 2009, 06:05:29 PM »
Hi Martin

As follow up on this. I am presently porting the Luminary project to the LM3S9B95 (pin compatible with a few others in the new Tempest Class with Ethernet and USB-OTG).

The previous devices hat only one peripheral function per pin but the new ones can have to to 11 peripheral functions per pin (!!!) and so setting them up is a little different (not that different but requires another register write).

Here is a new macro which I am using to achieve driver compatibility. It does have a drawback that it only respects setting peripherals once (assumed register is 0) but is adequate in the majority of reals usage cases.

Example of use (from configuring SPI)

    _CONFIG_PERIPHERAL(A, SSI0, (PORTA_BIT2 | PORTA_BIT4 | PORTA_BIT5), (PA2_SSI0Clk | PA4_SSI0Rx | PA5_SSI0Tx));


macro:
#define _CONFIG_PERIPHERAL(ref, periph, pins, functions) RCGC2 |= CGC2_GPIO##ref; SRCR2 &= ~CGC2_GPIO##ref;  \
                                              RCGC1 |= CGC1_##periph;  SRCR1 &= ~(CGC1_##periph); \
                                              GPIOPCTL_##ref  |= functions;                       \
                                              GPIOAFSEL_##ref |= pins; GPIODEN_##ref |= pins


The GPIOPCTL register selects the peripheral function of each port bit (from new defines for things like PA2_SSI0Clk). [I have just spend about 5 hours just defining the masses of new defines needed to achieve a comfortable solution for the new devices!!]
The GPIOPCTL is ignored when working with pre-Tempest class devices, so that the code is fully compatible.

It certainly seems worth while getting the Tempest support included now rather than later. The new devices are rather interesting too..

Regards

Mark




« Last Edit: March 09, 2009, 06:07:09 PM by mark »

Offline mark

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3236
    • View Profile
    • uTasker
Re: IO port access in uTasker
« Reply #3 on: March 15, 2009, 01:41:33 AM »
Hi

Since the next goal is an official release (no longer Beta) for the Luminary Micro project I am presently working on ensuring compatibility between the various family classes and how the actual device (one of presently 138 possibilities) can be (fairly) easily configured. So I am also taking the opportunity to make last fundamental changes before the project gets stricter version control - making such things a bit trickier.

I have found that the System Control device capabilities are proving a good way to do this. Since it is not practical to configure and test every possible device, several have been selected, which are sort of umbrella parts for various others with only minor differences (one UART more, or less, no I2C rather than with I2C, etc). When a specific device has been chosen, which deviates from the reference part, the user can effectively copy the Device Capability register values from its specific user manual and put these into a new device structure (in LM3Sxxxx.h). The project uses the structure to determine which peripherals, how much SRAM and FLASH, or which ports are actually available and configures itself fairly independently.

At the same time I have added a new port macro.

As you probably know it is possible to change only certain bits on a port, or read only certain ones (a form of bit masking). This is achieved by the port access address, where the bits A9..A2 define which port bits are masked (either modified or returned). The following macro is useful when fastest possible port access speed is required, although it only has advantages when the port address and the mask values are fixed and so the address can be calculated at compile time - otherwise the address calculation will cancel any speed advantages.

First a typical method of setting a bit (or bits) without affecting others on the same port:

GPIODATA_F |= 0x02;          // set bit 2

The following show the assembler code to do this
      4A05       ldr r2, [pc, #0x014]
      6813       ldr r3, [r2, #0x00]
      F0830301   orr r3, r3, #0x00000001
      6013       str r3, [r2, #0x00]

The following code does the same thing:

    _WRITE_PORT_MASK(F, 0x02, 0x02);

using the macro:
    #define _WRITE_PORT_MASK(ref, value, mask)    *(unsigned long *)(GPIO_PORT_##ref##_BLOCK + 4*mask) = value

The resulting assembler looks like this:

      4B03       ldr r3, [pc, #0x00C]
      6019       str r1, [r3, #0x00]


In fact the improvement is greater when more than one bit has to be changed, where some need to be set high and some low. As a comparison:

GPIODATA_F |= 0x02;     // set bit 2
GPIODATA_F &= ~0x04;     // clear bit 3


or GPIODATA_F = ((GPIODATA_F | 0x02) | (GPIODATA_F & ~0x04));

against

    _WRITE_PORT_MASK(F, 0x02, 0x06);

It should be clear that this will save at least another instruction...

When reading there is also a chance to speed things up by avoiding the need to mask bits.

Eg.
Value =  (GPIODATA_F & 0x08);      // read a port and mask bit 3

becomes

Value = _READ_PORT_MASK(F, 0x08);

using this macro:
    #define _READ_PORT_MASK(ref, mask)    *(unsigned long *)(GPIO_PORT_##ref##_BLOCK + 4*mask)

In this case the mask is not achieved with an additional AND instruction since the result is automatically masked in the hardware.

Finally, the newer classes (Dust-Devil and Tempest) offer a single cycle port write through a different memory aperture. The project automatically selects the optimum aperture based on the device used and so doesn't use the legacy ports. This assumes that no Beta projects have specific timing needs where the slower accesses need to be retained.

Regards


Mark

P.S. The reason why I never used this earlier was due to a complication in the simulator. But, I managed to overcome this now!
« Last Edit: March 15, 2009, 01:51:01 AM by mark »

Offline mark

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3236
    • View Profile
    • uTasker
Re: IO port access in uTasker
« Reply #4 on: August 25, 2010, 03:08:28 PM »
Hi All

A particular characteristic of the GPIOs in the Stellaris devices is that they don't have an output holding register that can be set before enabling a port as an output. The following is a short discussion concerning this property:

Quote
The case to be discussed is what happens when a port that is configured as input is configured as output. As an example it is assumed that a pin on port A, named CS0_LINE (eg. PORTA_BIT3) is initially an input and is to be configured as an output and drive an initial value of '1'.

1) Consider that there is a pull-down (or nothing) and so the original input state is '0'.
GPIODATA_A |= CS0_LINE; - this actually has no effect since the output doesn't have a holding register when configured as input
GPIODIR_A |= CS0_LINE; - this now converts the input to an output. The output will drive '0' since this is the state that the input was originally at
This means that the output is not driving '1' as may be expected.

2) Consider that there is a pull-down and so the original input state is '1'.
GPIODATA_A |= CS0_LINE; - this actually has no effect since the output doesn't have a holding register when configured as input
GPIODIR_A |= CS0_LINE; - this now converts the input to an output. The output will drive '1' since this is the state that the input was originally at
This means that the output is driving '1' as expected.

3) Consider the general case (note that instructions have been inverted compared to previous cases)
GPIODIR_A |= CS0_LINE; - this converts the input to an output. The output will drive '0' or '1' depending on the original input state [see 1) and 2)] GPIODATA_A |= CS0_LINE; - this now sets the output to '1'

It is interesting to note that most other processor ports have a holding register which can be read and written even when the port is not set as output. This allows the initial driving level to be set and then the DDR to drive it on to the line.
The Stellaris design (ST also uses similar) means that the order of the instructions is essentially inverted.
In both cases, however, it is possible to set a port from input to output without risk of generating a glitch on the line (i.e. input '1' converted to output '1' without a short period of output '0').
- In the 'more general case' the port output register is written with the input value before the port is driven.
- In the Stellaris case the configuration from input to output automatically drives the 'same' state and the output can be set as required (no further action is required if already correct)

The only potential problems that I see with the Stellaris ports is that the input state may be changing, or floating, so the initial driving state may not be guaranteed. Furthermore, if an input with logic potential '0' is to be turned into an open-drain output without initially driving a '0' it would not be possible without a very short drive '0' time (with the more conventional port it would). Probably there is never a case where this requirement actually exists though (?).


Regards

Mark