Digital I/O - Reading and Writing
Using the PDQ Board's digital I/O (input-output) lines, timer-controlled I/O, PWM outputs, and general purpose digital I/O (GPIO).
Overview of available digital I/O
The PDQ Single Board Computer (SBC) provides 8 I/O lines controlled by the Enhanced Capture/Timer (ECT), 8 Pulse-Width Modulated (PWM) lines, 8 general purpose digital I/O lines (GPIO), 16 analog input lines (which may be used as digital inputs), and several communications channels – all derived from its 9S12 microcontroller unit. All of the timer-controlled and PWM signals can be configured as general purpose digital I/O, and the two lines associated with the IIC (Inter-IC) serial bus can also be reconfigured as digital I/O. A maximum of 42 digital inputs and outputs are available on the PDQ Board.
The following table summarizes the available digital I/O lines:
The PDQ Board’s Digital I-O | |||
---|---|---|---|
I/O Lines | Type | Signal Names | Comments/Alternate Uses |
8 | General purpose digital I/O | PM0-7 | Bit-by-bit configured by the application as inputs or outputs; PM0-5 are on Digital Field Header, PM6,7 are on Analog Field Header. |
8 | PWM digital outputs, or general purpose digital I/O | PP0-7 | Bit-by-bit configured by the application as inputs or outputs. Each bit can be configured as a PWM output, or as a digital I/O line. Available on Digital Field Header. |
8 | Timer-controlled digital I/O, or general purpose Digital I/O | PT0-7 | Bit-by-bit configured by the application as inputs or outputs. Each bit can be configured as an ECT-controlled output compare or input capture, 4 lines can be configured as pulse accumulators, and 4 lines can be configured as two logic-level serial ports using the Software UART library. Any bits that are not under the control of the ECT timer system can be used as general purpose digital I/O lines. Available on Digital Field Header. |
16 | Analog inputs, also configurable as digital inputs. | AN0-15 | Bit-by-bit configured by the application as analog or digital inputs. Any input pin not used as an analog input can be used as a digital input. Available on Analog Field Header. |
2 | IIC bus, or digital I/O | PJ6(SDA) PJ7(SCL) | If the IIC bus is not in use, can be configured as digital I/O, but note that each signal has a 100 ohm series resistor and a 4.7K pullup resistor. Available on Analog Field Header. |
After a power-up or hardware reset, all of the digital I/O lines are configured as inputs, and AN0-15 are configured as analog inputs. All PWM and ECT features are disabled by default after a hardware reset. With the exception of pins AN0-15, the signals listed in the table may be reconfigured as outputs via software commands. Pins AN0-15 are software configurable as either analog or digital inputs as described below.
The maximum number of digital inputs and outputs is 42. Up to 42 can be configured as inputs, and up to 26 as outputs if none are used for the ATD (Analog To Digital) converter or the IIC 2-wire serial bus.
Using the digital I/O ports on the Freescale 9S12 (HCS12) chip
Digital inputs and outputs are very useful in data acquisition, monitoring, and instrument control applications. A low voltage (near 0 Volts) is established on a digital output pin when the processor writes a logical 0 to the corresponding bit in a data register associated with the digital output port. A high voltage (near 5 Volts) is established on the digital output pin when the processor writes a 1 to a corresponding bit in the port’s data register. This allows software to control external events and processes. For example, a C-language application program can use digital outputs to activate solenoids and turn switches and lights on and off, or to interface the PDQ Board with a wide variety of digital accessories such as D/A converters, displays, etc.
A digital input allows the processor to monitor the logical state of an external voltage by reading a data register associated with the port. External voltages near 0 Volts connected to a digital input cause the corresponding bit in the port’s data register to be read as a logical 0, and external voltages near 5 Volts connected to a digital input are read as a logical 1. Application programs can use digital inputs to read switches and keypads or to interface to digital devices such as ATD converters and real-time clocks.
Using digital I/O is very easy: simply configure the output port as a digital input or output as explained below, and then use functions or assignment statements to read from or write to the port. The names of the data and direction registers and all required initialization routines are pre-defined in the header files so you don’t have to worry about hexadecimal register addresses in your code.
These register names enable use of the digital I/O ports:
Digital I/O Port Direction Control and Access Registers | |||
---|---|---|---|
Signal Names | I/O Register Input Register | Configuration Register | Comments |
PM0-7 | PORTM | PORTM_DIRECTION | 8 General purpose digital I/O lines. To configure a line as an output, write a 1 to the corresponding bit in PORTM_DIRECTION. |
PP0-7 | PORTP PORTP_IN | PORTP_DIRECTION | 8 Digital I/O or PWM-controlled outputs. To configure a line as an output, write a 1 to the corresponding bit in PORTP_DIRECTION. Lines under PWM control should be read via PORTP_IN. |
PT0-7 | PORTT PORTT_IN | PORTT_DIRECTION | 8 Digital I/O or ECT timer-controlled I/O. To configure a line as an output, write a 1 to the corresponding bit in PORTT_DIRECTION. Outputs under timer control should be read via PORTT_IN |
AN0-7 | PORTAD0 | PORTAD0_MODE | 8 Digital or Analog inputs. To configure as a digital input, write a 1 to the corresponding bit in PORTAD0_MODE, or use the ATDDigitalInputs() function. |
AN8-15 | PORTAD1 | PORTAD1_MODE | 8 Digital or Analog inputs. To configure as a digital input, write a 1 to the corresponding bit in PORTAD1_MODE, or use the ATDDigitalInputs() function. |
PJ6 (SDA), PJ7 (SCL) | PORTJ | PORTJ_DIRECTION | 2 Digital I/O if the IIC port is not in use. To configure a line as an output, write a 1 to the corresponding bit in PORTJ_DIRECTION. Be careful not to modify other bits in PORTJ or PORTJ_DIRECTION, as they control other hardware on the PDQ Board. |
Definitions of the register names in the above table can be found in the C Glossary; they are descriptive synonyms for the standard Motorola register names. Both the standard register names and the synonyms are declared in the C:\MosaicPlus\c\libraries\include\mosaic\HCS12REGS.h file. The synonyms are defined as follows:
// some digital i/o port synonyms (used in C glossary): #define PORTJ (PTJ) #define PORTJ_DIRECTION (DDRJ) #define PORTM (PTM) #define PORTM_DIRECTION (DDRM) #define PORTP (PTP) #define PORTP_DIRECTION (DDRP) #define PORTP_IN (PTIP) #define PORTT (PTT) #define PORTT_DIRECTION (DDRT) #define PORTT_IN (PTIT) #define PORTAD0_MODE (ATD0DIEN) #define PORTAD1_MODE (ATD1DIEN) // PORTAD0 and PORTAD1 are defined above; // they allow digital reads of A/D ports.
Additional digital I/O port options
There are additional digital I/O port features that can be controlled via the registers listed in the following table. In most cases, these features will not be needed.
Each output pin can be configured to have reduced drive capability. Full-drive HCS12 pins configured as digital outputs in the high state can maintain the output voltage within 0.8 Volts of the positive supply if 10 mA or less is being drawn from the pin. In the low state, full-drive digital outputs can keep the voltage below 0.8 Volts while sinking up to 10 mA of current. Reduced drive pins can source or sink only 2 mA of current while staying within these voltage specifications. After a hardware reset, full drive is the default, and this is the recommended operating condition for most applications. The primary reason for using reduced drive mode is to reduce the electromagnetic interference emitted by pin state changes. If it appears that pin state changes are a source of unacceptable EMI, pins may be set to reduced drive mode by setting the corresponding bit in the RDR register to 1:
// Set upper four bits of port M to reduced drive mode, // lower four bits to full drive mode. RDRM = 0xF0;
Other Digital I/O Port Control Registers | ||||
---|---|---|---|---|
Digital I/O Port | Reduced Drive Register | Pull Enable Register | Pull Polarity Select | Interrupt Registers |
PORTM | RDRM | PERM | PPSM | – |
PORTP | RDRP | PERP | PPSP | PIEP, PIFP |
PORTT | RDRT | PERT | PPST | – |
PORTJ | RDRJ | PERJ | PPSJ | PIEJ, PIFJ |
PTS | RDRS | PERS | PPSS | – |
PTH | RDRH | PERH | PPSH | PIEH, PIFH |
Ports S and H are used for SPI and UART (RS-232) communication, and generally should not be directly accessed. However, pins in these ports can be switched to reduced drive mode if it appears that serial transmission generates an unacceptable level of electromagnetic interference. In particular, pins 1 and 3 on port S are the transmit lines for the PDQ Board's Serial1 and Serial2, respectively. To set both these pins to reduced drive mode, use the following line of code:
// Set UART (RS-232) transmit pins to reduced-drive mode. // These are pins 1 and 3 on port S. RDRS = 0x0A;
After a hardware reset, PORTS and PORTH are configured for SPI and UART (RS-232) communication by the QED-Forth operating system, and the available digital I/O pins on PORTM, PORTP, PORTT, and PORTJ come up as inputs with no pull-up or pull-down resistor. By writing a 1 to the corresponding bit in the Pull Enable Register as shown in the above table, a pull-up or pull-down is connected to the pin when it is used as a digital input. If the Pull Polarity Select bit contents equal 0 (the default after a reset), a pull-up is connected; otherwise, a pull-down is connected.
PORTP and PORTJ transitions can trigger an interrupt on the PORTP_ID and PORTJ_ID interrupt vectors, respectively. (PORTH transitions can also trigger interrupts on the PORTH_ID vector; this port is reserved to implement SPI1 and SPI2 for inter-processor communications). To enable interrupt generation by a specified pin or set of pins, set the corresponding bits in the interrupt enable register (PIEP for PORTP, PIEJ for PORTJ). An interrupt is triggered on an active edge, which is defined as a falling edge (the default) if the corresponding bit in the Polarity Select Register in the above table is 0, or a rising edge if the Polarity Select Register bit is 1. When an interrupt occurs, the flag bit of the pin that caused the interrupt is set in the interrupt flag register (PIEF for PORTP, PIEJ for PORTJ).
The following sub-sections describe how to configure and access the digital I/O ports on the PDQ Board. Subsequent chapters will describe how to use the alternate pin functions such as PWM outputs, output compares, input captures, pulse accumulators, and Analog To Digital converters.
As you work through the code examples in the remaining sections of the chapter, you can use a voltmeter to verify that the outputs are behaving as you expect. You can also connect the input signals through a 1 kOhm resistor to +5V or GND to verify that you can digitally read their values. The 1 kOhm resistor is just a safety precaution to minimize the chance that you’ll blow out a port bit by mistakenly connecting an output bit to a supply voltage; even if you make this mistake, the resistor would limit the current to safe levels.
Using the 9S12 PORTM
PORTM is an 8-bit general purpose digital I/O port configurable as input or output on a bit-by-bit basis. PM0-5 are available on the Digital Field Header, and PM6 and PM7 are accessible on the Analog Field Header. To configure PORTM, use an assignment statement to write to the PORTM_DIRECTION register, also called the DDRM (Data Direction Register M) register. These and all of the HCS12 register names are defined in the HCS12REGS.h file in the C:\MosaicPlus\c\libraries\include\mosaic directory. Writing a 1 to a bit position in PORTM_DIRECTION configures the corresponding port bit as an output, and writing a 0 to a bit position configures the corresponding bit as an input. The default after a hardware reset is all inputs. For example, the following C statement configures PORTM as all outputs:
PORTM_DIRECTION = 0xFF;
To configure PORTM as all inputs, use the statement:
PORTM_DIRECTION = 0x00;
If we want to configure bits 0-6 as inputs, and bit 7 as output, we can execute:
PORTM_DIRECTION = 0x80;
To change the state of an output bit on PORTM, use an assignment statement with PORTM on the left hand side to write to the port’s data register named PORTM. For example, if PORTM is configured as all outputs, the following C statement sets all PORTM bits high:
PORTM = 0xFF;
To read the state of PORTM, use an assignment statement with PORTM on the right hand side to read the port’s data register. For example, the following code fragment reads PORTM and places the results in the variable named latest_portm_state:
static unsigned char latest_portm_state; latest_portm_state = PORTM;
Because PORTM is not associated with a timer or PWM subsystem, the last value written to the register is typically the value present on the pins. This means that the PORTM register can be reliably used both for reading and writing, and use of the read-only PORM_IN register is not required.
\ Forth code equivalent to the C examples above. 0xFF PORTM.DIRECTION C! \ Configure PORTM as all outputs. 0 PORTM.DIRECTION C! \ Configure PORTM as all inputs. 0x80 PORTM.DIRECTION C! \ Configure PM0-6 as inputs, PM7 as output. 0xFF PORTM C! \ Set all PORTM output pins high. PORTM C@ HEX. \ Read and display the current state of PORTM pins.
Using the 9S12 PORTP
PORTP is an 8-bit digital I/O port configurable as input or output on a bit-by-bit basis. The PP0-7 signals are available on the Digital Field Header on the PDQ Board. Any or all of the bits in PORTP can be configured as Pulse-Width Modulated (PWM) outputs under the control of the HCS12 processor’s PWM subsystem. After a hardware reset, PORTP is a digital input port with PWM disabled. The PWM drivers are explained in a later chapter. Briefly, the PWMEnable() function enables PWM control of a specified PORTP bit, and the PWMDisable() function disables PWM control of a specified bit. Both of these functions work by writing to the PWME (PWM Enable) register. In this section we assume that PWM control is disabled for all of the PORTP pins, allowing them to be used as standard digital I/O.
To configure PORTP, use an assignment statement to write to the PORTP_DIRECTION register, also called the DDRP
(Data Direction Register P) register. These and all of the HCS12 register names are defined in the HCS12REGS.h file in the C:\MosaicPlus\c\libraries\include\mosaic\ directory. Writing a 1 to a bit position in PORTP_DIRECTION configures the corresponding port bit as an output, and writing a 0 to a bit position configures the corresponding bit as an input. The default after a hardware reset is all inputs. For example, the following C statement configures PORTP as all outputs:
PORTP_DIRECTION = 0xFF;
To configure PORTP as all inputs, use the statement:
PORTP_DIRECTION = 0x00;
If we want to configure bits 0-6 as inputs, and bit 7 as output, we can execute:
PORTP_DIRECTION = 0x80;
To change the state of an output bit on PORTP, use an assignment statement with PORTP on the left hand side to write to the port’s data register named PORTP. For example, if PORTP is configured as all outputs, the following C statement sets all PORTP bits high:
PORTP = 0xFF;
To read the state of PORTP, use an assignment statement with PORTP_IN on the right hand side to read the port’s data register. Reading PORTP may give incorrect results for any bits that are under PWM output control. Reading PORTP_IN
returns the actual state of each pin, as opposed to the last value written to the PORTP register. For example, the following code fragment reads PORTP and places the results in the variable named latest_portp_state:
static unsigned char latest_portp_state; latest_portp_state = PORTP_IN;
\ Forth code equivalent to the C examples above. 0xFF PORTP.DIRECTION C! \ Configure PORTP as all outputs. 0 PORTP.DIRECTION C! \ Configure PORTP as all inputs. 0x80 PORTP.DIRECTION C! \ Configure PP0-6 as inputs, PP7 as output. 0xFF PORTP C! \ Set all PORTP output pins high. PORTP.IN C@ HEX. \ Read and display the current state of PORTP pins.
Using the 9S12 PORTT
PORTT is an 8-bit digital I/O port configurable as input or output on a bit-by-bit basis. The PT0-7 signals are available on the Digital Field Header on the PDQ Board. Any or all of the bits in PORTT can be configured as timer-controlled I/O under the control of the processor’s Enhanced Capture/Timer (ECT) subsystem. After a hardware reset, PORTT is a digital input port with timer control disabled. The ECT software drivers are explained in a later chapter. The ECT system has many features, and there is a suite of functions that configure various timer actions on the PORTT pins. In this section we assume that ECT control is disabled for all of the PORTT pins, allowing them to be used as standard digital I/O.
To configure PORTT, use an assignment statement to write to the PORTT_DIRECTION register, also called the DDRT (Data Direction Register T) register. These and all of the HCS12 register names are defined in the HCS12REGS.h file in the C:\MosaicPlus\c\libraries\include\mosaic\ directory. Writing a 1 to a bit position in PORTT_DIRECTION configures the corresponding port bit as an output, and writing a 0 to a bit position configures the corresponding bit as an input. The default after a hardware reset is all inputs. For example, the following C statement configures PORTT as all outputs:
PORTT_DIRECTION = 0xFF;
To configure PORTT as all inputs, use the statement:
PORTT_DIRECTION = 0x00;
If we want to configure bits 0-6 as inputs, and bit 7 as output, we can execute:
PORTT_DIRECTION = 0x80;
To change the state of an output bit on PORTT, use an assignment statement with PORTT on the left hand side to write to the port’s data register named PORTT. For example, if PORTT is configured as all outputs, the following C statement sets all PORTT bits high:
PORTT = 0xFF;
To read the state of PORTT, use an assignment statement with PORTT_IN on the right hand side to read the port’s data register. Reading PORTT may give incorrect results for any bits that are under ECT (Enhanced Capture/Timer) output control. Reading PORTT_IN returns the actual state of each pin, as opposed to the last value written to the PORTT register. For example, the following code fragment reads PORTT and places the results in the variable named latest_portt_state:
static unsigned char latest_portt_state; latest_portt_state = PORTT_IN;
\ Forth code equivalent to the C examples above. 0xFF PORTT.DIRECTION C! \ Configure PORTT as all outputs. 0 PORTT.DIRECTION C! \ Configure PORTT as all inputs. 0x80 PORTT.DIRECTION C! \ Configure PT0-6 as inputs, PT7 as output. 0xFF PORTT C! \ Set all PORTT output pins high. PORTT.IN C@ HEX. \ Read and display the current state of PORTT pins.
Using the 9S12 PORTAD0 and PORTAD1
PORTAD0 and PORTAD1 are each 8-signal analog or digital input ports; they cannot be configured as outputs. Analog inputs are controlled by the dual 8-channel ATD (Analog To Digital) subsystems on the HCS12 processor. PORTAD0 pins are named AN0-7, and PORTAD1 pins are named AN8-15; these 16 inputs are accessible on the Analog I/O header on the PDQ Board. To configure any or all of these lines as a digital input, create a 16-bit mask with a 1 in the bit position of each digital input, and pass the mask to the ATDDigitalInputs() function. ATDDigitalInputs() splits the 16-bit value into its 8-bit components and writes to the PORTAD0_MODE and PORTAD1_MODE registers (also called the ATD0DIEN and ATD1DIEN registers, respectively). If you prefer, you can configure specified pins as digital inputs by directly writing to the PORTAD0_MODE and PORTAD1_MODE registers. Both ports PORTAD0 and PORTAD1 are configured as analog inputs after a hardware reset; that is, the PORTAD0_MODE and PORTAD1_MODE registers equal 0 after a power-up or reset.
If a pin on one of these ports is configured as a digital input, it is important that your system not present an analog input to that pin. An analog input driving a pin configured as a digital input can put the pin electronics in an intermediate logic state that results in high currents inside the processor chip.
The ATD software drivers are explained in another chapter. In this section we assume that ATD inputs are disabled for all of the PORTAD0 and PORTAD1 pins, allowing them to be used as standard digital inputs. To configure the ports as digital inputs, execute:
ATDDigitalInputs( 0xFFFF );
As an alternative, you could execute the equivalent assignment statements:
PORT_AD0_MODE = 0xFF; PORT_AD1_MODE = 0xFF;
These and all of the HCS12 register names are defined in the HCS12REGS.h file in the C:\MosaicPlus\c\libraries\include\mosaic\ directory. Writing a 1 to bit positions in PORTAD0_MODE configures the corresponding AN0-7 port bits as digital inputs, and writing a 0 to a bit positions configures the corresponding bits as analog inputs.
To read the digital state of the AN0-7 inputs associated with PORTAD0, use an assignment statement with PORTAD0 on the right hand side to read the port’s digital data register. For example, the following code fragment reads PORTAD0 and places the results in the variable named latest_portad0_state:
static unsigned char latest_portad0_state; latest_portad0_state = PORTAD0;
Similarly, to read the digital state of the AN8-15 inputs associated with PORTAD1, use an assignment statement with PORTAD1 on the right hand side to read the port’s digital data register. For example, the following code fragment reads PORTAD1 and places the results in the variable named latest_portad1_state:
static unsigned char latest_portad1_state; latest_portad1_state = PORTAD1;
\ Forth code equivalent to the C examples above. 0xFFFF ATD.DIGITAL.INPUTS \ Configure all ATD pins as digital inputs. 0xFF PORTAD0.MODE C! \ Alternative to the above. 0xFF PORTAD1.MODE C! \ Alternative to the above. PORTAD0 C@ HEX. \ Read and display digital state of AN0-7 pins. PORTAD1 C@ HEX. \ Read and display digital state of AN8-15 pins.
Using the 9S12 PORTJ
PORTJ is a 4-bit multi-purpose digital I/O port. Bits PJ0 and PJ1 are reserved by the operating system to implement the transmit direction control for the RS485 driver chips of the serial1 and serial2 ports. Bits PJ2-5 are not implemented. Bit PJ6 implements the IIC (Inter-IC) serial bus SDA data line, and PJ7 implements the IIC SCL clock line; these lines are available on the PDQ Board’s analog field header. Each of these two IIC lines is conditioned with a 100 Ω series resistor and a nominal 4.7 KΩ pull-up resistor. If the IIC bus is not used, bits PJ6 and PJ7 can be configured for general purpose digital I/O. To configure PORTJ, use a read/modify/write assignment statement to write to bits 6 and 7 of the PORTJ_DIRECTION (also called DDRJ) register without modifying bits PJ0 or PJ1. These and all of the HCS12 register names are defined in the HCS12REGS.h file in the C:\MosaicPlus\c\libraries\include\mosaic\ directory. Writing a 1 to a bit position in PORTJ_DIRECTION configures the corresponding port bit as an output, and writing a 0 to a bit position configures the corresponding bit as an input. The default contents of PORTJ_DIRECTION after a hardware reset is 0x03: the RS485 transmit control signals on PJ0 and PJ1 are outputs, and the IIC signals on PJ6 and PJ7 are inputs. For example, the following C statement configures PJ6 and PJ7 as outputs:
PORTJ_DIRECTION |= 0xC0;
To configure PJ6 and PJ7 as inputs, use the statement:
PORTJ_DIRECTION &= 0x3F;
To change the state of an bit PJ6 or PJ7 on PORTJ, use a read/modify/write statement such as |= or &= with PORTJ on the left hand side to write to the port’s data register named PORTJ. For example, if PJ6 and PJ7 are configured as outputs, the following C statement sets PJ6 and PJ7 high:
PORTJ |= 0xC0;
Note that this statement does not change the state of PJ0 or PJ1, and so will not corrupt any in-process RS485 data transfers. To read the state of PORTJ, use an assignment statement with PORTJ on the right hand side to read the port’s data register. For example, the following code fragment reads PORTJ and places the results in the variable named latest_portj_state:
static unsigned char latest_portj_state; latest_portj_state = PORTJ;
\ Forth code equivalent to the C examples above. 0xC0 PORTJ.DIRECTION SET.BITS \ Configure PJ6-7 as outputs. 0xC0 PORTJ.DIRECTION CLEAR.BITS \ Configure PJ6-7 as inputs. 0xC0 PORTJ SET.BITS \ Set PJ6-7 high if they are outputs. PORTJ C@ HEX. \ Read and display the current state of PORTJ pins.
Using uninterruptable operators to read and write I/O
The importance of uninterruptable operators
Care must be taken when performing read/modify/write operations in applications that use interrupts or multitasking. Operations such as setting or clearing individual bits in a byte while leaving other bits unchanged are called read/modify/write operations because they involve reading the port, modifying the read contents, and writing the result back to the port. Unpredictable results can occur if more than one interrupt service routine or task tries to access a single port or memory location at the same time using a read/modify/write sequence. The simplest solution to this problem is to access the memory location or port using an uninterruptable read/modify/write operator.
The following scenario illustrates the importance of uninterruptable operators when more than one task or interrupt routine is writing to a memory location. Let’s assume that two different tasks are controlling the bits of PORTM. Assume that TASK1 is controlling the state of bit PM4, and TASK2 controls bit PM7. Let’s assume that bit 4 is low when TASK2 tries to execute the following code:
static unsigned char mask = 0x80; PORTM |= mask;
TASK2 is merely trying to set the top bit in PORTM to 1, but this statement may have unintended consequences. The compiler generates code that reads the contents of PORTM, performs a bitwise OR with the contents of mask, and stores the result back into PORTM. Assume that the timeslicer interrupt is serviced just after the OR instruction and transfers control to TASK1. TASK1 may change the state of bit PM4 to a 1. When control is then transferred back to TASK2, the final store to PORTM is executed. Unfortunately, this store command erroneously sets bit PM4 back to the low state! TASK2 was interrupted after it read the state of PORTM but before it had a chance to write the new contents, so it undoes the change that TASK1 made in the state of bit PM4! This can indeed cause problems in an application program.
Pre-coded read/modify/write functions
The pre-coded read/modify/write functions avoid this problem by disabling interrupts for ten to thirteen processor cycles (0.5 to 0.65 microseconds). This prevents the corruption of the contents when different tasks or interrupts share access to a single location. The following functions are uninterruptable:
void ChangeBits( uchar data, uchar mask, xaddr address ) void ClearBits( uchar mask, xaddr address ) void SetBits( uchar mask, xaddr address ) void ToggleBits( uchar mask, xaddr address )
The following additional uninterruptable operators are declared
in the memory.h
header file in the directory
C:\MosaicPlus\c\libraries\Kernel_V6.xx\include\mosaic\
void StoreLongProtected (long val, xaddr address) void StoreFloatProtected (float val, xaddr address) long FetchLongProtected (xaddr address) float FetchFloatProtected (xaddr address)
To simplify using the above routines with C variables, the following macros are defined in memory.h
in Mosaic IDE+ revisions 1500 and greater, allowing a pointer to common memory (i.e. a C variable, array, or struct) to be passed directly as a parameter rather than an extended address:
#define FETCH_LONG_PROTECTED( PTR ) FetchLongProtected( COMMON_XADDR( PTR ) ) #define FETCH_FLOAT_PROTECTED( PTR ) FetchFloatProtected( COMMON_XADDR( PTR ) ) #define STORE_LONG_PROTECTED( VALUE, PTR ) StoreLongProtected( (VALUE), COMMON_XADDR( PTR ) ) #define STORE_FLOAT_PROTECTED( VALUE, PTR ) StoreFloatProtected( (VALUE), COMMON_XADDR( PTR ) ) #define CHANGE_BITS( DATA, MASK, PTR ) ChangeBits( (DATA), (MASK), COMMON_XADDR( PTR ) ) #define CLEAR_BITS( MASK, PTR ) ClearBits( (MASK), COMMON_XADDR( PTR ) ) #define SET_BITS( MASK, PTR ) SetBits( (MASK), COMMON_XADDR( PTR ) ) #define TOGGLE_BITS( MASK, PTR ) ToggleBits( (MASK), COMMON_XADDR( PTR ) )
These routines are described in detail in the C Glossary.
Click for Forth equivalent functions
Create your own uninterruptable functions
It is easy to create your own uninterruptable functions using the lock()
and unlock()
inline functions.
To use these handy utilities, declare inside your function a local unsigned char (8-bit) variable named prior_interrupt_state (or any appropriate name that you choose). Execute lock()
and assign its 8-bit return value to prior_interrupt_state. This saves the prior interrupt state (globally enabled or globally disabled) and then globally disables interrupts by setting the I bit in the condition code register (CCR). Then you can perform any read/modify/write statements while interrupts are disabled. To return to the prior interrupt state, pass the prior_interrupt_state variable to restore() which restores the prior state of the I bit in the CCR.
For example, the following uninterruptable function sets specified bits in a port or memory byte:
void SetBitsUninterrupted( uchar mask, char* address ) { unsigned char prior_interrupt_state; prior_interrupt_state = lock(); // read/modify/write code goes here: *address |= mask; restore (prior_interrupt_state); }
Simple stores to and fetches from 1-byte or 2-byte memory locations are intrinsically uninterruptable because they are implemented with single assembly-language opcodes. However, the HCS12 processor does not have a single opcode that can access a 32 bit memory location. Thus, problems can arise when one task writes to a floating point or long variable, and a second task needs to read the saved value. The data that is read may be invalid if the read or the write is interrupted between the time of the writing/reading of the first 16 bits and the writing/reading of the second 16 bits. In these cases uninterruptable operators such as should be used. An example is presented by the functions named:
void PokeFloatUninterrupted(float value, float* destination ) float PeekFloatUninterrupted(float * source )
which are defined keyword in the TURNKEY.c example program found in the demo project called "Turnkey Demo".
See also → Pulse Width Modulated I/O