Showing posts with label Embedded C. Show all posts
Showing posts with label Embedded C. Show all posts

Reading and writing individual port pins.


/*-------------------------------------------------------------*-
Reading and writing individual port pins.
NOTE: Both pins on the same port
-*-------------------------------------------------------------*/
#include <reg52.H>
void Write_Bit_P1(const unsigned char, const bit);

bit Read_Bit_P1(const unsigned char);
/* ............................................................... */
void main (void)
{
bit x;
while(1)
{
x = Read_Bit_P1(0); /* Read Port 1, Pin 0 */
Write_Bit_P1(1,x); /* Write to Port 1, Pin 1 */
}
}
/* --------------------------------------------------------------- */
void Write_Bit_P1(const unsigned char PIN, const bit VALUE)
{
unsigned char p = 0x01; /* 00000001 */
/* Left shift appropriate number of places */
p <<= PIN;
/* If we want 1 output at this pin */
if (VALUE == 1)
{
P1 |= p; /* Bitwise OR */
return;
}
/* If we want 0 output at this pin */
p = ~p; /* Complement */
P1 &= p; /* Bitwise AND */
}

Why use the Project Header?


Use of PROJECT HEADER can help to make your code more
readable, not least because anyone using your projects knows where

to find key information, such as the model of microcontroller and
the oscillator frequency required to execute the software.

The use of a project header can help to make your code more easily
portable, by placing some of the key microcontroller-dependent data
in one place: if you change the processor or the oscillator used then
- in many cases - you will need to make changes only to the Project
Header.

Main.H



/*-------------------------------------------------------------*-
Main.H (v1.00)
-*-------------------------------------------------------------*/
#ifndef _MAIN_H
#define _MAIN_H
/*--------------------------------------------------------
WILL NEED TO EDIT THIS SECTION FOR EVERY PROJECT
-------------------------------------------------------- */
/* Must include the appropriate microcontroller header file here */
#include <reg52.h>
/* Oscillator / resonator frequency (in Hz) e.g. (11059200UL) */
#define OSC_FREQ (12000000UL)
/* Number of oscillations per instruction (12, etc)
12 - Original 8051 / 8052 and numerous modern versions
6 - Various Infineon and Philips devices, etc.
4 - Dallas 320, 520 etc.
1 - Dallas 420, etc. */
#define OSC_PER_INST (12)
/* --------------------------------------------------------
SHOULD NOT NEED TO EDIT THE SECTIONS BELOW
-------------------------------------------------------- */
/* Typedefs  */
typedef unsigned char tByte;
typedef unsigned int tWord;
typedef unsigned long tLong;
/* Interrupts  */
#define INTERRUPT_Timer_0_Overflow 1
#define INTERRUPT_Timer_1_Overflow 3
#define INTERRUPT_Timer_2_Overflow 5
#endif
/*-------------------------------------------------------------*-
---- END OF FILE ---------------------------------------
-*-------------------------------------------------------------*/

The Project Header


Project Header (Main.H)







#include <AT89S53.H>
...
#define OSC_FREQ (11059200UL)
...
typedef unsigned char tByte;


All program code in a single source file


It is possible to create ‘file-based-classes’ in C without imposing a
significant memory or CPU load.


Object-Oriented Programming with C



Adding Structure to Your Code


We will do three things

1. We will describe how to use an object-oriented style of
programming with C programs, allowing the creation of


libraries of code that can be easily adapted for use in different
embedded projects;

2. We will describe how to create and use a ‘Project Header’
file. This file encapsulates key aspects of the hardware
environment, such as the type of processor to be used, the
oscillator frequency and the number of oscillator cycles
required to execute each instruction. This helps to document
the system, and makes it easier to port the code to a different
processor.

3. We will describe how to create and use a ‘Port Header’ file.
This brings together all details of the port access from the
whole system. Like the Project Header, this helps during
porting and also serves as a means of documenting important
system features.

Example: Counting goats


• With the simple code in the previous example, problems can
arise whenever a switch is pressed for a period longer than
the debounce interval.
• This is a concern, because in many cases, users will press
switches for at least 500 ms (or until they receive feedback
that the system has detected the switch press). As a result, a


user typing “Hello” on a keypad may see:
“HHHHHHHHHeeeeeeeeellllllllllllllllooooooooooo”
appear on the screen.


One consequence is that this code is not suitable for applications
where we need to count the number of times that a switch is pressed
and then released.




/*-------------------------------------------------------------*-
A 'goat counting' program for the 8051...
-*-------------------------------------------------------------*/
#include <Reg52.h>
/* Connect switch to this pin */
sbit Switch_pin = P1^0;
/* Display count (binary) on this port */
#define Count_port P3
/* Return values from Switch_Get_Input() */
#define SWITCH_NOT_PRESSED (bit) 0
#define SWITCH_PRESSED (bit) 1
/* Function prototypes */
void SWITCH_Init(void);
bit SWITCH_Get_Input(const unsigned char DEBOUNCE_PERIOD);
void DISPLAY_COUNT_Init(void);
void DISPLAY_COUNT_Update(const unsigned char);
void DELAY_LOOP_Wait(const unsigned int DELAY_MS);
/* ---------------------------------------------------------------- */
void main(void)
{
unsigned char Switch_presses = 0;
/* Init functions */
SWITCH_Init();
DISPLAY_COUNT_Init();
while(1)
{
if (SWITCH_Get_Input(30) == SWITCH_PRESSED)
{
Switch_presses++;
}
DISPLAY_COUNT_Update(Switch_presses);
}
}

/*-------------------------------------------------------------*/
void SWITCH_Init(void)
{
Switch_pin = 1; /* Use this pin for input */
}
/*-------------------------------------------------------------*-
SWITCH_Get_Input()
Reads and debounces a mechanical switch as follows:
1. If switch is not pressed, return SWITCH_NOT_PRESSED.
2. If switch is pressed, wait for the DEBOUNCE_PERIOD (in ms).
Then:
a. If switch is no longer pressed, return SWITCH_NOT_PRESSED.
b. If switch is still pressed, wait (indefinitely) for
switch to be released, *then* return SWITCH_PRESSED
See Switch_Wait.H for details of return values.
-*-------------------------------------------------------------*/
bit SWITCH_Get_Input(const unsigned char DEBOUNCE_PERIOD)
{
bit Return_value = SWITCH_NOT_PRESSED;
if (Switch_pin == 0)
{
/* Switch is pressed */
/* Debounce - just wait... */
DELAY_LOOP_Wait(DEBOUNCE_PERIOD);
/* Check switch again */
if (Switch_pin == 0)
{
/* Wait until the switch is released. */
while (Switch_pin == 0);
Return_value = SWITCH_PRESSED;
}
}
/* Now (finally) return switch value */
return Return_value;
}

/*-------------------------------------------------------------*-
DISPLAY_COUNT_Init()
Initialisation function for the DISPLAY COUNT library.
-*-------------------------------------------------------------*/
void DISPLAY_COUNT_Init(void)
{
Count_port = 0x00;
}
/*-------------------------------------------------------------*-
DISPLAY_COUNT_Update()
Simple function to display tByte data (COUNT)
on LEDs connected to port (Count_Port)
-*-------------------------------------------------------------*/
void DISPLAY_COUNT_Update(const unsigned char COUNT)
{
Count_port = COUNT;
}
/*-------------------------------------------------------------*-
DELAY_LOOP_Wait()
Delay duration varies with parameter.
Parameter is, *ROUGHLY*, the delay, in milliseconds,
on 12MHz 8051 (12 osc cycles).
You need to adjust the timing for your application!
-*-------------------------------------------------------------*/
void DELAY_LOOP_Wait(const unsigned int DELAY_MS)
{
unsigned int x, y;
for (x = 0; x <= DELAY_MS; x++)
{
for (y = 0; y <= 120; y++);
}
}


Conclusions
The switch interface code presented and discussed in this seminar
has allowed us to do two things:
• To perform an activity while a switch is depressed;
• To respond to the fact that a user has pressed – and then
released – a switch.
In both cases, we have illustrated how the switch may be
‘debounced’ in software.











Example: Reading switch inputs (basic code)


This switch-reading code is adequate if we want to perform
operations such as:
• Drive a motor while a switch is pressed.
• Switch on a light while a switch is pressed.


• Activate a pump while a switch is pressed.
These operations could be implemented using an electrical switch,
without using a microcontroller; however, use of a microcontroller
may well be appropriate if we require more complex behaviour. For
example:
• Drive a motor while a switch is pressed
Condition: If the safety guard is not in place, don’t turn the
motor. Instead sound a buzzer for 2 seconds.
• Switch on a light while a switch is pressed
Condition: To save power, ignore requests to turn on the
light during daylight hours.
• Activate a pump while a switch is pressed
Condition: If the main water reservoir is below 300 litres, do
not start the main pump: instead, start the reserve pump and
draw the water from the emergency tank.

/*-------------------------------------------------------------*-
Switch_read.C (v1.00)
--------------------------------------------------------
A simple 'switch input' program for the 8051.
- Reads (and debounces) switch input on Pin 1^0
- If switch is pressed, changes Port 3 output
-*-------------------------------------------------------------*/
#include <Reg52.h>
/* Connect switch to this pin */
sbit Switch_pin = P1^0;
/* Display switch status on this port */
#define Output_port P3
/* Return values from Switch_Get_Input() */
#define SWITCH_NOT_PRESSED (bit) 0
#define SWITCH_PRESSED (bit) 1
/* Function prototypes */
void SWITCH_Init(void);
bit SWITCH_Get_Input(const unsigned char DEBOUNCE_PERIOD);
void DISPLAY_SWITCH_STATUS_Init(void);
void DISPLAY_SWITCH_STATUS_Update(const bit);
void DELAY_LOOP_Wait(const unsigned int DELAY_MS);

/* ---------------------------------------------------------------- */
void main(void)
{
bit Sw_state;
/* Init functions */
SWITCH_Init();
DISPLAY_SWITCH_STATUS_Init();
while(1)
{
Sw_state = SWITCH_Get_Input(30);
DISPLAY_SWITCH_STATUS_Update(Sw_state);
}
}
/*-------------------------------------------------------------*-
SWITCH_Init()
Initialisation function for the switch library.
-*-------------------------------------------------------------*/
void SWITCH_Init(void)
{
Switch_pin = 1; /* Use this pin for input */
}
/*-------------------------------------------------------------*-
SWITCH_Get_Input()
Reads and debounces a mechanical switch as follows:
1. If switch is not pressed, return SWITCH_NOT_PRESSED.
2. If switch is pressed, wait for the DEBOUNCE_PERIOD (in ms).
Then:
a. If switch is no longer pressed, return SWITCH_NOT_PRESSED.
b. If switch is still pressed, return SWITCH_PRESSED
See Switch_Wait.H for details of return values.
-*-------------------------------------------------------------*/
bit SWITCH_Get_Input(const unsigned char DEBOUNCE_PERIOD)
{
bit Return_value = SWITCH_NOT_PRESSED;
if (Switch_pin == 0)
{
/* Switch is pressed */
/* Debounce - just wait... */
DELAY_LOOP_Wait(DEBOUNCE_PERIOD);
/* Check switch again */
if (Switch_pin == 0)
{
Return_value = SWITCH_PRESSED;
}
}
/* Now return switch value */
return Return_value;
}
/*-------------------------------------------------------------*-
DISPLAY_SWITCH_STATUS_Init()
Initialization function for the DISPLAY_SWITCH_STATUS library.
-*-------------------------------------------------------------*/
void DISPLAY_SWITCH_STATUS_Init(void)
{
Output_port = 0xF0;
}
/*-------------------------------------------------------------*-
DISPLAY_SWITCH_STATUS_Update()
Simple function to display data (SWITCH_STATUS)
on LEDs connected to port (Output_Port)
-*-------------------------------------------------------------*/
void DISPLAY_SWITCH_STATUS_Update(const bit SWITCH_STATUS)
{
if (SWITCH_STATUS == SWITCH_PRESSED)
{
Output_port = 0x0F;
}
else
{
Output_port = 0xF0;
}
}
/*-------------------------------------------------------------*-
DELAY_LOOP_Wait()
Delay duration varies with parameter.
Parameter is, *ROUGHLY*, the delay, in milliseconds,
on 12MHz 8051 (12 osc cycles).
You need to adjust the timing for your application!
-*-------------------------------------------------------------*/
void DELAY_LOOP_Wait(const unsigned int DELAY_MS)
{
unsigned int x, y;
for (x = 0; x <= DELAY_MS; x++)
{
for (y = 0; y <= 120; y++);
}
}


Dealing with switch bounce


In practice, all mechanical switch contacts bounce (that is, turn on
and off, repeatedly, for a short period of time) after the switch is
closed or opened.


As far as the microcontroller is concerned, each ‘bounce’ is


equivalent to one press and release of an ‘ideal’ switch. Without
appropriate software design, this can give rise to a number of
problems, not least:
• Rather than reading ‘A’ from a keypad, we may read
‘AAAAA’
• Counting the number of times that a switch is pressed
becomes extremely difficult.
• If a switch is depressed once, and then released some time
later, the ‘bounce’ may make it appear as if the switch has
been pressed again (at the time of release).

Creating some simple software to check for a valid switch input is
straightforward:
1. We read the relevant port pin.
2. If we think we have detected a switch depression, we wait for
20 ms and then read the pin again.
3. If the second reading confirms the first reading, we assume
the switch really has been depressed.
Note that the figure of ‘20 ms’ will, of course, depend on the switch
used.

The need for pull-up resistors




This hardware operates as follows:
• When the switch is open, it has no impact on the port pin.
An internal resistor on the port ‘pulls up’ the pin to the


supply voltage of the microcontroller (typically 5V). If we
read the pin, we will see the value ‘1’.
• When the switch is closed (pressed), the pin voltage will be
0V. If we read the the pin, we will see the value ‘0’.




Example: Reading and writing bits (generic version)

The six bitwise operators:



/* Desktop program - illustrating the use of bitwise operators */
#include <stdio.h>
void Display_Byte(const unsigned char);
/* ............................................................... */


int main()
{
unsigned char x = 0xFE;
unsigned int y = 0x0A0B;
printf("%-35s","x");
Display_Byte(x);
printf("%-35s","1s complement [~x]");
Display_Byte(~x);
printf("%-35s","Bitwise AND [x & 0x0f]");
Display_Byte(x & 0x0f);
printf("%-35s","Bitwise OR [x | 0x0f]");
Display_Byte(x | 0x0f);
printf("%-35s","Bitwise XOR [x ^ 0x0f]");
Display_Byte(x ^ 0x0f);
printf("%-35s","Left shift, 1 place [x <<= 1] ");
Display_Byte(x <<= 1);
x = 0xfe; /* Return x to original value */
printf("%-35s","Right shift, 4 places [x >>= 4]");
Display_Byte(x >>= 4);
printf("\n\n");
printf("%-35s","Display MS byte of unsigned int y");
Display_Byte((unsigned char) (y >> 8));
printf("%-35s","Display LS byte of unsigned int y");
Display_Byte((unsigned char) (y & 0xFF));
return 0;
}
COPYRIGHT ©MICHAEL J. PONT, 2001-2006. Contains material from:
Pont, M.J. (2002) “Embedded C”, Addison-Wesley.
PES I - 53
/* --------------------------------------------------------------- */
void Display_Byte(const unsigned char CH)
{
unsigned char i, c = CH;
unsigned char Mask = 1 << 7;
for (i = 1; i <= 8; i++)
{
putchar(c & Mask ? '1' : '0');
c <<= 1;
}
putchar('\n');
}


Experienced ‘C’ programmers please note these lines:


sbit Switch_pin = P1^0;
sbit LED_pin = P1^1;
Here we gain access to two port pins through the use of an sbit
variable declaration. The symbol ‘^’ is used, but the XOR bitwise
operator is NOT involved.

Example: Reading and writing bits (simple version)


-*-------------------------------------------------------------*/
#include <Reg52.H>
sbit Switch_pin = P1^0;
sbit LED_pin = P1^1;
/* ............................................................... */
void main (void)


{
bit x;
/* Set switch pin for reading */
Switch_pin = 1;
while(1)
{
x = Switch_pin; /* Read Pin 1.0 */
LED_pin = x; /* Write to Pin 1.1 */
}
}
/*-------------------------------------------------------------*-
---- END OF FILE ---------------------------------------
-*-------------------------------------------------------------*/

Example: Reading and writing bytes (review)


void main (void)
{
unsigned char Port1_value;
/* Must set up P1 for reading */


P1 = 0xFF;
while(1)
{
/* Read the value of P1 */
Port1_value = P1;
/* Copy the value to P2 */
P2 = Port1_value;
}
}

Reading Switches


• Embedded systems usually use switches as part of their user
interface.
• This general rule applies from the most basic remote-control
system for opening a garage door, right up to the most
sophisticated aircraft autopilot system.
• Whatever the system you create, you need to be able to
create a reliable switch interface.









In this seminar, we consider how you can read inputs from
mechanical switches in your embedded application.
Before considering switches themselves, we will consider the
process of reading the state of port pins.



Review: Basic techniques for reading from port pins

We can send some data to Port 1 as follows:
sfr P1 = 0x90; /* Usually in header file */
P1 = 0x0F; /* Write 00001111 to Port 1 */
In exactly the same way, we can read from Port 1 as follows:
unsigned char Port_data;
P1 = 0xFF; /* Set the port to ‘read mode’ */
Port_data = P1; /* Read from the port */


Creating “software delays”


How do you create a simple delay without using any hardware
(timer) resources?

Solution

Loop_Delay()


{
unsigned int x,y;
for (x=0; x <= 65535; x++)
{
y++;
}
}
Longer_Loop_Delay()
{
unsigned int x, y, z;
for (x=0; x<=65535; x++)
{
for (y=0; y<=65535; y++);
{
z++;
}
}
}

Creating and using sbit variables


To write to a single pin, we can make use of an sbit variable in the
Keil (C51) compiler to provide a finer level of control.

Here’s a clean way of doing this:


#define LED_PORT P3


#define LED_ON 0 /* Easy to change the logic here */
#define LED_OFF 1
...
sbit Warning_led = LED_PORT^0; /* LED is connected to pin 3.0 */
...
Warning_led = LED_ON;
... /* delay */
Warning_led = LED_OFF;
... /* delay */
Warning_led = LED_ON;
... /* etc */

SFRs and ports



Control of the 8051 ports through software is carried out using what
are known as ‘special function registers’ (SFRs).

Physically, the SFR is a area of memory in internal RAM:




• P0 is at address 0x80
• P1 at address 0x90
• P2 at address 0xA0
• P3 at address 0xB0

NOTE: 0x means that the number format is HEXADECIMAL

Strengths and weaknesses of “super loops”


* The main strength of Super Loop systems is their simplicity. This
makes them (comparatively) easy to build, debug, test and maintain.




* Super Loops are highly efficient: they have minimal hardware
resource implications.

* Super Loops are highly portable.

BUT:

* If your application requires accurate timing (for example, you need to
acquire data precisely every 2 ms), then this framework will not
provide the accuracy or flexibility you require.

* The basic Super Loop operates at ‘full power’ (normal operating mode)
at all times. This may not be necessary in all applications, and can
have a dramatic impact on system power consumption.