Experiment 6
Experiment 6
Page 1 of 30
Experiment 5 provided the basics of controlling motors, including a little on PWM. The goal in this experiment is to increase the usefulness of the system by configuring it in such a way as to permit a program to turn a bit on or off using simple function calls, and to do so with minimum memory usage and time-consuming overhead. Something like this: TurnOn(WarningLED); TurnOff(MainMotor); TurnOn(LeftArmMotor); We will then look into ways to automate the control of outputs by using a timer built into the computer. Let's take another look at pointers since they will be used in the final code. Consider the following. It wouldn't hurt to compile, run and play with it to get a feel for what's going on:
void main(void) { int x,y,*ptr1,*ptr2; // cause ptr1 to point to the memory location of x ptr1 = &x; // notice that x is being set equal to 5 AFTER ptr1 is made to point to x x = 5; // the * dereferences ptr1, which is to say it provides the value pointed to printf("ptr1 points to x's address: x = %d *prt1 = %d\n",x,*ptr1); y = 10; ptr2 = &y; printf("ptr2 points to y's address: y = %d *prt2 = %d\n",y,*ptr2); ptr2 = &x; printf("both ptr1 and ptr2 point to x's address: x = %d y = %d *prt1 = %d *prt2 = %d\n" ,x,y,*ptr1,*ptr2); *ptr2 = 123; printf("dereferenced ptr2 changed to 123: x = %d y = %d *prt1 = %d *prt2 = %d\n" ,x,y,*ptr1,*ptr2); } // end experi6a.c
http://www.learn-c.com/experiment6.htm
10/7/2011
Page 2 of 30
ptr1 points to x's address: x = 5 *prt1 = 5 ptr2 points to y's address: y = 10 *prt2 = 10 both ptr1 and ptr2 point to x's address: x = 5 y = 10 *prt1 = 5 *prt2 = 5 dereferenced ptr2 changed to 123: x = 123 y = 10 *prt1 = 123 *prt2 = 123
Notice that ptr1 is made to point to x's address before x is set equal to 5, and yet the dereferenced ptr1 also provides 5 (see Experiment 4 if you don't remember dereferencing). The same process applies when ptr2 is given the address of y and y is set to 10. Finally, the program points both ptr1 and ptr2 to the address location of x. It then sets the dereferenced ptr2 to 123. Notice that x, the dereferenced ptr1 and the dereferenced ptr2 are now all equal to 123. Also note that no change was made directly to x. The change was made by changing the value pointed to by ptr2, which happens to be x and the value pointed to by ptr1. All values show the same because both pointers point to x. When the contents of the memory location are changed, all three get changed. That's the capability that's needed; the ability to control any output bit on any port by any of several control locations. For basic on/off operation we need the port address, access to the current port value and something that will allow us to change an individual bit to turn it on or to turn it off, such as a couple of masks. It would be nice to be able to package all four elements in a single object so they could be treated as a unit. That's possible with something called a structure:
The name does not have to be OC. I used it because it's symbolic of Output Control. In fact, it just describes an object. It can't be used until you declare something as being an OC. That's done almost the same way you would declare anything else, such as "int x;." All you add is the struct keyword: struct OC OutputControl; OutputControl is now an instance of OC. You can refer to its members with a dot. For example: OutputControl.PortAddress = 0x123; Now try this piece of code:
// experi6b.c #include #include #include #include <malloc.h> <conio.h> <stdio.h> <bios.h>
struct OC { int PortAddress; char onmask; char offmask; int *PortData; }; void main(void) { int pa=0x345,m=0xd,pd; struct OC OutputControl; OutputControl.PortAddress = pa; OutputControl.onmask = m; OutputControl.PortData = &pd; pd = 12345; printf("pa = %#X m = %#X pd = %d\n",pa,m,pd); printf("OutputControl.PortAddress = %#X\nOutputControl.onmask = %#X\n*OutputControl.PortData = %d\n" ,OutputControl.PortAddress,OutputControl.onmask,*OutputControl.PortData);
http://www.learn-c.com/experiment6.htm
10/7/2011
Page 3 of 30
} // end experi6b.c
A small note -- notice what the %#X does for you? Everything works just like you'd expect, including the dereferenced pointer. The only change is that you use a dot to pick elements out of the object you have declared. There are a maximum of 24 possible output lines on the board (which you can buy here -- just in case you were wondering). The OC structure allows us to establish the port address that will be used, mask off a bit and access the port data by means of a pointer. Since we are going to need up to 24 such control structures, we could declare an array of them almost the same way we would any other data type: struct OC OutputControl[24]; If we wanted to reference the 17th structure's address member for example, we would simply do something like this: OutputControl[16].PortAddress = 0X345; Remember, everything in C is zero-based, so the 17th structure is at [16]. The problem with using an array of structures however, is that it takes up memory that might never be used. You already know that each character uses 1 byte and that an integer uses 2 bytes in a DOS 16-bit system. A pointer needs 4 bytes. That makes the whole structure 8 bytes. At 8 bytes each, 24 of the OC structures need 192 bytes. That's not much for a multi-megabyte system, but it can get to be a problem with memory-starved control and embedded systems. In addition, the OC structure will be expanded before we get through, so it would be nice if we could use only the amount of memory that we need as we need it. Again, pointers come to the rescue. Rather than declare an array of structures, just declare an array of pointers to the structures: struct OC *OutputControl[24]; This array reserves only enough memory for the pointers that will be used to point to the memory locations of the structures. Thus, it takes up 4 * 24 = 96 bytes. Try the following to illustrate the point. Here, the sizeof(..) operator will be used. As might be expected, it provides the size of an object:
struct OC { int PortAddress; char onmask; char offmask; int *PortData; }; void main(void) { struct OC oc; struct OC *poc; struct OC OutputControl[24]; struct OC *pOutputControl[24]; printf("size of char = %d\n",sizeof(char));
http://www.learn-c.com/experiment6.htm
10/7/2011
Page 4 of 30
of of of of of of of
int = %d\n",sizeof(int)); double = %d\n",sizeof(float)); double = %d\n",sizeof(double)); oc = %d\n",sizeof(oc)); poc = %d\n",sizeof(poc)); OutputControl = %d\n",sizeof(OutputControl)); pOutputControl = %d\n",sizeof(pOutputControl));
} // end experi6c.c
of of of of of of of of
These object sizes are true only for the system the program was compiled and run on. The numbers will be accurate for most 16-bit DOS systems and compilers, but could be different for your system and compiler. Notice that the single oc structure has a size of 8 bytes, whereas the size of pointer poc is only 4 bytes. That's the reason the array of 24 pointers is 96 bytes smaller than the array of 24 structures. The difference will become even more significant as the size of the structure increases. Note that the pointer poc could have been given the address of oc the same way integer pointers were given the address of an integer: poc = &oc; The members of a pointer to a structure are referenced with "->". Thus, poc would reference the PortAddress thus: poc->PortAddress = 0x345; and dereferencing the pointer element looks like this: *poc->PortData = 2; That means we can do this -- try it, it's good for you:
struct OC { int PortAddress; char onmask; char offmask; int *PortData; }; void main(void) { struct OC oc; struct OC *poc; struct OC OutputControl[24]; struct OC *pOutputControl[24]; int x = 1000; oc.PortData = &x; poc = &oc;
http://www.learn-c.com/experiment6.htm
10/7/2011
Page 5 of 30
oc.PortAddress = 100; poc->PortAddress = 10; printf("poc->PortAddress = %d\noc.PortAddress = %d\nx = %d\n*poc->PortData = %d\n*oc.PortData = %d\n" ,poc->PortAddress ,oc.PortAddress ,x ,*poc->PortData ,*oc.PortData); } // end experi6d.c
Notice that oc.PortAddress was set to 100, but got changed to 10. Changing poc->PortAddress didn't change poc->PortAddress alone. It also changed oc.PortAddress. Also, since oc.PortData was made to point to the memory location of x, and since x had already been made equal to 1000, both *oc.PortData and *poc->PortData became 1000. Let's take a look at x as it would appear in memory. Since 1000 is equal to the HEX value 03E8 and x is a two-byte integer, it would look like the following in memory. Note that we really don't care what the actual address location is. Lower memory in the table below is toward the left, and upper memory is toward the right. Notice that the bytes are reversed from the left-to-right order we might expect. This is typical of Intel processors: E8 03
When oc.PortData is made to point to x, it points to the memory location containing E8. When oc.PortData is dereferenced, the value residing in that memory location, along with the value in the next location, are returned as a single integer; the 03E8 value (1000) is returned. If x is changed to another value, the same memory location is referenced, so the changed information is obtained. The process also goes the other way. If the dereferenced oc.PortData is set equal to 100 (= 0x0064), then the above memory locations will change as well, and x will be made equal to 100: 64 00
The best way to understand what's going on is to play with the numbers then compile and run the program. An array of pointers to OC structures is a good start, but that's all it is. It is still necessary to make the pointers point to an area in memory that can hold the structures. We need a way to make some room -- to do some memory allocation. No problem, we'll use malloc(..) . That's its job. The prototype for malloc(..) is: void *malloc(size_t size); The void simply means malloc(..) returns a pointer that is of any type you wish. It returns NULL if there is not enough memory. The size_t type is the same type that sizeof(..) returns. It's usually a long. Such types are usually declared using typdef . For example, size_t might be defined as follows: typedef long size_t; You can define virtually anything you like this way. Maybe you'd like to use the word integer in the place of int. OK:
http://www.learn-c.com/experiment6.htm
10/7/2011
Page 6 of 30
typedef int integer; How about OC? You would define it this way:
typedef struct { int PortAddress; char onmask; char offmask; int *PortData; } OC;
Now you can declare an instance without the struct keyword: OC oc; I like the non-typedef method because it reminds me that it's a structure that's being used. If you like typedef, go for it. Back to the subject. Let's say that an array of pointers to OC structures has been defined like this: struct OC *OutputControl[24]; All you would need to do to reserve memory for the 15th location is this: OutputControl[14] = malloc(sizeof(struct OC)); Malloc will return the address of a block of memory for the 15th place that is the size of OC, providing the memory is available. A NULL will be returned if it's not. Just as it is necessary to allocate memory for the structures, it is also necessary to free the memory when it is no longer being used. That is done by calling the free(..) function. Always free memory after you are through with it. Not doing so will give the computer severe heartburn. The following example frees the same memory that was allocated above: free(OutputControl[14]); The following enumeration will be used to set up each node of the array as needed. The most significant two bits [4:3] will be used to determine what port is to be used, and the least significant three bits [2:0] will determine the bit to be used in the port. It should be added to the constants header file:
enum OutSetNums { PA0, // 0 00 PA1, // 1 00 PA2, // 2 00 PA3, // 3 00 PA4, // 4 00 PA5, // 5 00 PA6, // 6 00 PA7, // 7 00 PB0, // 8 01 PB1, // 9 01 PB2, // 10 01 PB3, // 11 01 PB4, // 12 01 PB5, // 13 01 PB6, // 14 01 PB7, // 15 01 PC0, // 16 10 PC1, // 17 10 PC2, // 18 10 PC3, // 19 10 PC4, // 20 10 PC5, // 21 10 PC6, // 22 10 PC7 // 23 10 };
000 001 010 011 100 101 110 111 000 001 010 011 100 101 110 111 000 001 010 011 100 101 110 111
Port Port Port Port Port Port Port Port Port Port Port Port Port Port Port Port Port Port Port Port Port Port Port Port
A A A A A A A A B B B B B B B B C C C C C C C C
Bit Bit Bit Bit Bit Bit Bit Bit Bit Bit Bit Bit Bit Bit Bit Bit Bit Bit Bit Bit Bit Bit Bit Bit
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
http://www.learn-c.com/experiment6.htm
10/7/2011
Page 7 of 30
Put the structure definitions somewhere close to the variable declarations in the digital C module. Near the top will be fine:
// The following are known only to the functions in this file. // They can't be modified or even accessed by anything outside this // file except through funtions in this file designed to provide access. struct OC { int PortAddress; char onmask; char offmask; int *PortData; }; struct OC *OutputControl[24]; unsigned unsigned unsigned unsigned unsigned base; switch_port; ppi_porta; ppi_portb; ppi_portc;
int porta_val = 0xa; // port values are set for test int porta_mask; int portb_val = 0xb; int portb_mask; int portc_val = 0xc; int portc_mask;
The port values are pre-set for test purposes only. If we are pointing to porta_val for example, the value read should be 0XA. Now add the following configure and free routines to the digital C module and their prototypes to the extern header. Notice the slight change to set_up_ppi() as well:
// configure the array number location to a port // and bit number dictated by portselect int ConfigureOutput(int arraynumber, int portselect) { int x; if(arraynumber < 0 || arraynumber > 23) return 0; // illegal number if(portselect < 0 || portselect > 23) return 0; // illegal number if(OutputControl[arraynumber] == NULL) { if((OutputControl[arraynumber] = malloc(sizeof(struct OC))) == NULL) { printf("Not enough memory\n"); return 0; } } x = portselect >> 3; // the port number is in bits 3 and 4 switch(x) // 0 = Port A, 1 = Port B, 2 = Port C { case 0: OutputControl[arraynumber]->PortAddress = ppi_porta; // address for Port A OutputControl[arraynumber]->PortData = &porta_val; // point to Port A data value break; case 1: OutputControl[arraynumber]->PortAddress = ppi_portb; // address for Port B OutputControl[arraynumber]->PortData = &portb_val; // point to Port B data value
http://www.learn-c.com/experiment6.htm
10/7/2011
Page 8 of 30
break; case 2: OutputControl[arraynumber]->PortAddress = ppi_portc; // address for Port C OutputControl[arraynumber]->PortData = &portc_val; // point to Port C data value break; } OutputControl[arraynumber]->onmask = 1 << (portselect & 7); // shift by bits[2:0] OutputControl[arraynumber]->offmask = ~OutputControl[arraynumber]->onmask; // /* remove the double slash rem to skip debug print statements printf("arraynum=%02d port select=%02d ",arraynumber,portselect); printf("Addr=%#X " ,OutputControl[arraynumber]->PortAddress); printf("*Data=%X " ,*OutputControl[arraynumber]->Data); printf("onmsk="); for(x=128; x>0; x/=2) { if(x & OutputControl[arraynumber]->onmsk) printf("1"); else printf("0"); } printf(" offmsk="); for(x=128; x>0; x/=2) { if(x & OutputControl[arraynumber]->offmsk) printf("1"); else printf("0"); } printf("\n"); // remove the double slash rem to skip debug print statements */ return 1; } // free the output control structures void FreeOutputControl(void) { int x; for(x=0; x<24; x++) { if(OutputControl[x] != NULL) free(OutputControl[x]); } } // set up the ppi according to the dictates of the mode argument void set_up_ppi(int mode) { unsigned control = base + 0x23; int command; // make certain control locations start at NULL for(command=0; command<24; command++) OutputControl[command] = NULL; mode>>=6; // shift the mode value to the right 6 places command = (mode & 0x0c) << 1; // shift bits 2 and 3 into positions 4 and 5 command += (mode & 3); // add in bits 0 and 2 command |= 0x80; // OR in bit 7 for PPI set up outp(control, command); // set according to mode command } // end set_up_ppi()
http://www.learn-c.com/experiment6.htm
10/7/2011
Page 9 of 30
Let's take a look at ConfigureOutput(.......). You have already seen a lot of this type of code. The first thing it does is reject any output or port select number that's less than 0 or greater than 23 since that's what will be used as the index into the array of structure pointers, as well as to determine what port and bit to use. If OutputControl at the index location is NULL then memory is allocated. Be sure to add the loop in set_up_ppi(..) that sets all locations to NULL in the first place so you can be sure this will work. Next, x is set to portselect shifted to the right 3 places. This will put bits 3 and 4 in bit locations 0 and 1. The original 3 bits will go in the "bit bucket" -- they will disappear as far as x is concerned. Remember however, that nothing will happen to portselect. It can and will be used later. The resulting x value will range from 0 to 2, representing Port A through Port C. The switch statement sets the appropriate port address and causes the data pointer to point to the correct data value variable. The final thing to do is to set up the masks. The on mask is generated by shifting a 1 to the left by the bit number. This value can range from 0 to 7 and is in bits [2:0] of portselect. The maximum value the three bits can produce = 1 + 2 + 4 = 7, so portselect is ANDed with all 3 bits, or 7. The off mask is the inverted version of the on mask. It would be a very good idea to take another look at the boolean and data lines sections if you don't understand those last two sentences. Here is the extern header so far:
// external prototypes extern int ConfigureOutput(extern int arraynumber, extern int portselect); extern int TurnOn(int arraynumber); extern int TurnOff(int arraynumber); extern int is_closure(int input); extern void set_up_ppi(int mode); extern void blinker(long on, long off); extern void btoa(void); extern void motor(long on, long off); extern void motor2(long on, long off); extern void portaon(void); extern void portaoff(void); extern void portbon(void); extern void portboff(void); extern void portcon(void); extern void portcoff(void);
// include header with constants #include "const6a.h" // include header with external prototypes #include "extern6a.h" void main(void) { int x,p,y; get_port(); // get the port number and establish register locations // make everything an output set_up_ppi(Aout_CUout_Bout_CLout); for(x=-1,p=PC7; p>=PA0; x++,p--) { if(!ConfigureOutput(x,p)) printf("Error for arraynum = %d port select = %d\n",x,p); } for(x=0,p=PA0-1; p<=PC7+1; x++,p++) { if(!ConfigureOutput(x,p))
http://www.learn-c.com/experiment6.htm
10/7/2011
Page 10 of 30
printf("Error for arraynum = %d port select = %d\n",x,p); } // don't forget to free memory! FreeOutputControl(); } // end experi6e.c
Click here to download experi6e.c Click here to download digi6e.c Click here to download const6a.h Click here to download extern6a.h The x variable is used for the arraynum and p is used for port select. The arraynum goes up while port select goes down in the first loop, and they both travel up in the second. Errors are introduced to test the function. This is the output you should see:
Error for arraynum = -1 port select = 23 arraynum=00 port select=22 Addr=0X262 *Data=C arraynum=01 port select=21 Addr=0X262 *Data=C arraynum=02 port select=20 Addr=0X262 *Data=C arraynum=03 port select=19 Addr=0X262 *Data=C arraynum=04 port select=18 Addr=0X262 *Data=C arraynum=05 port select=17 Addr=0X262 *Data=C arraynum=06 port select=16 Addr=0X262 *Data=C arraynum=07 port select=15 Addr=0X261 *Data=B arraynum=08 port select=14 Addr=0X261 *Data=B arraynum=09 port select=13 Addr=0X261 *Data=B arraynum=10 port select=12 Addr=0X261 *Data=B arraynum=11 port select=11 Addr=0X261 *Data=B arraynum=12 port select=10 Addr=0X261 *Data=B arraynum=13 port select=09 Addr=0X261 *Data=B arraynum=14 port select=08 Addr=0X261 *Data=B arraynum=15 port select=07 Addr=0X260 *Data=A arraynum=16 port select=06 Addr=0X260 *Data=A arraynum=17 port select=05 Addr=0X260 *Data=A arraynum=18 port select=04 Addr=0X260 *Data=A arraynum=19 port select=03 Addr=0X260 *Data=A arraynum=20 port select=02 Addr=0X260 *Data=A arraynum=21 port select=01 Addr=0X260 *Data=A arraynum=22 port select=00 Addr=0X260 *Data=A Error for arraynum = 0 port select = -1 arraynum=01 port select=00 Addr=0X260 *Data=A arraynum=02 port select=01 Addr=0X260 *Data=A arraynum=03 port select=02 Addr=0X260 *Data=A arraynum=04 port select=03 Addr=0X260 *Data=A arraynum=05 port select=04 Addr=0X260 *Data=A arraynum=06 port select=05 Addr=0X260 *Data=A arraynum=07 port select=06 Addr=0X260 *Data=A arraynum=08 port select=07 Addr=0X260 *Data=A arraynum=09 port select=08 Addr=0X261 *Data=B arraynum=10 port select=09 Addr=0X261 *Data=B arraynum=11 port select=10 Addr=0X261 *Data=B arraynum=12 port select=11 Addr=0X261 *Data=B arraynum=13 port select=12 Addr=0X261 *Data=B arraynum=14 port select=13 Addr=0X261 *Data=B arraynum=15 port select=14 Addr=0X261 *Data=B arraynum=16 port select=15 Addr=0X261 *Data=B arraynum=17 port select=16 Addr=0X262 *Data=C arraynum=18 port select=17 Addr=0X262 *Data=C arraynum=19 port select=18 Addr=0X262 *Data=C arraynum=20 port select=19 Addr=0X262 *Data=C arraynum=21 port select=20 Addr=0X262 *Data=C arraynum=22 port select=21 Addr=0X262 *Data=C arraynum=23 port select=22 Addr=0X262 *Data=C Error for arraynum = 24 port select = 23 Error for arraynum = 25 port select = 24
onmsk=01000000 onmsk=00100000 onmsk=00010000 onmsk=00001000 onmsk=00000100 onmsk=00000010 onmsk=00000001 onmsk=10000000 onmsk=01000000 onmsk=00100000 onmsk=00010000 onmsk=00001000 onmsk=00000100 onmsk=00000010 onmsk=00000001 onmsk=10000000 onmsk=01000000 onmsk=00100000 onmsk=00010000 onmsk=00001000 onmsk=00000100 onmsk=00000010 onmsk=00000001 onmsk=00000001 onmsk=00000010 onmsk=00000100 onmsk=00001000 onmsk=00010000 onmsk=00100000 onmsk=01000000 onmsk=10000000 onmsk=00000001 onmsk=00000010 onmsk=00000100 onmsk=00001000 onmsk=00010000 onmsk=00100000 onmsk=01000000 onmsk=10000000 onmsk=00000001 onmsk=00000010 onmsk=00000100 onmsk=00001000 onmsk=00010000 onmsk=00100000 onmsk=01000000
offmsk=10111111 offmsk=11011111 offmsk=11101111 offmsk=11110111 offmsk=11111011 offmsk=11111101 offmsk=11111110 offmsk=01111111 offmsk=10111111 offmsk=11011111 offmsk=11101111 offmsk=11110111 offmsk=11111011 offmsk=11111101 offmsk=11111110 offmsk=01111111 offmsk=10111111 offmsk=11011111 offmsk=11101111 offmsk=11110111 offmsk=11111011 offmsk=11111101 offmsk=11111110 offmsk=11111110 offmsk=11111101 offmsk=11111011 offmsk=11110111 offmsk=11101111 offmsk=11011111 offmsk=10111111 offmsk=01111111 offmsk=11111110 offmsk=11111101 offmsk=11111011 offmsk=11110111 offmsk=11101111 offmsk=11011111 offmsk=10111111 offmsk=01111111 offmsk=11111110 offmsk=11111101 offmsk=11111011 offmsk=11110111 offmsk=11101111 offmsk=11011111 offmsk=10111111
http://www.learn-c.com/experiment6.htm
10/7/2011
Page 11 of 30
Setting up output control ahead of time in this manner provides for faster action when in control mode because no decisions have to be made. They are all made during setup. For example, to turn on an output just do the following:
int TurnOn(int arraynumber) { if(OutputControl[arraynumber] == NULL) return 0; // node not set up // keep existing bits and OR this one in *OutputControl[arraynumber]->PortData |= OutputControl[arraynumber]->onmask; // put the result in this node's port register outp(OutputControl[arraynumber]->PortAddress, *OutputControl[arraynumber]->PortData); return 1; }
int TurnOff(int arraynumber) { if(OutputControl[arraynumber] == NULL) return 0; // node not set up // keep existing bits but remove this one *OutputControl[arraynumber]->PortData &= OutputControl[arraynumber]->offmask; // put the result in this node's port register outp(OutputControl[arraynumber]->PortAddress, *OutputControl[arraynumber]->PortData); return 1; }
Turning off a bit works the same way except that the port data value is ANDed with the off mask in order to turn off the desired bit. It is important to note that up to 8 nodes can access a single port data variable. That's what the pointers do for us. Each manipulates only the bit it uses to turn on or off its particular line. Now add the On/Off routines to the digital C module. You will need to change the starting values of the port data variables to 0 so things will work correctly:
// digi6f.c #include <stdio.h> // The following are known only to the functions in this file. // They can't be modified or even accessed by anything outside this // file except through funtions in this file designed to provide access. struct OC {
http://www.learn-c.com/experiment6.htm
10/7/2011
Page 12 of 30
int PortAddress; char onmask; char offmask; int *PortData; }; struct OC *OutputControl[24]; unsigned unsigned unsigned unsigned unsigned base; switch_port; ppi_porta; ppi_portb; ppi_portc;
int porta_val = 0; int porta_mask; int portb_val = 0; int portb_mask; int portc_val = 0; int portc_mask; // configure the array number location to a port // and bit number dictated by portselect int ConfigureOutput(int arraynumber, int portselect) { int x; if(arraynumber < 0 || arraynumber > 23) { printf("arraynumber error --- %d\n",arraynumber); return 0; // illegal number } if(portselect < 0 || portselect > 23) { printf("portselect error --- %d\n",portselect); return 0; // illegal number } if(OutputControl[arraynumber] == NULL) { if((OutputControl[arraynumber] = malloc(sizeof(struct OC))) == NULL) { printf("Not enough memory\n"); return 0; } } x = portselect >> 3; // the port number is in bits 3 and 4 switch(x) // 0 = Port A, 1 = Port B, 2 = Port C { case 0: OutputControl[arraynumber]->PortAddress = ppi_porta; // address for Port A OutputControl[arraynumber]->PortData = &porta_val; // point to Port A data value break; case 1: OutputControl[arraynumber]->PortAddress = ppi_portb; // address for Port B OutputControl[arraynumber]->PortData = &portb_val; // point to Port B data value break; case 2: OutputControl[arraynumber]->PortAddress = ppi_portc; // address for Port C OutputControl[arraynumber]->PortData = &portc_val; // point to Port C data value break; } OutputControl[arraynumber]->onmask = 1 << (portselect & 7); // shift by bits[2:0] OutputControl[arraynumber]->offmask = ~OutputControl[arraynumber]->onmask; // /* add double slash rem to print debug statements
http://www.learn-c.com/experiment6.htm
10/7/2011
Page 13 of 30
printf("arraynum=%02d port select=%02d ",arraynumber,portselect); printf("Addr=%#X " ,OutputControl[arraynumber]->PortAddress); printf("*Data=%X " ,*OutputControl[arraynumber]->PortData); printf("onmsk="); for(x=128; x>0; x/=2) { if(x & OutputControl[arraynumber]->onmask) printf("1"); else printf("0"); } printf(" offmsk="); for(x=128; x>0; x/=2) { if(x & OutputControl[arraynumber]->offmask) printf("1"); else printf("0"); } printf("\n"); // add double slash rem to print debug statements */ return 1; }
// free the output control structures void FreeOutputControl(void) { int x; for(x=0; x<24; x++) { if(OutputControl[x] != NULL) free(OutputControl[x]); } }
// turn on an output node int TurnOn(int arraynumber) { if(OutputControl[arraynumber] == NULL) { printf("can't turn on -- location %d not set up\n",arraynumber); return 0; // node not set up } // keep existing bits and OR this one in *OutputControl[arraynumber]->PortData |= OutputControl[arraynumber]->onmask; // put the result in this node's port register outp(OutputControl[arraynumber]->PortAddress, *OutputControl[arraynumber]->PortData); return 1; }
// turn off an output node int TurnOff(int arraynumber) { if(OutputControl[arraynumber] == NULL) { printf("can't turn off -- location %d not set up\n",arraynumber); return 0; // node not set up } // keep existing bits but remove this one *OutputControl[arraynumber]->PortData &= OutputControl[arraynumber]->offmask;
http://www.learn-c.com/experiment6.htm
10/7/2011
Page 14 of 30
// put the result in this node's port register outp(OutputControl[arraynumber]->PortAddress, *OutputControl[arraynumber]->PortData); return 1; } // set up the ppi according to the dictates of the mode argument void set_up_ppi(int mode) { unsigned control = base + 0x23; int command; // make certain control locations start at NULL for(command=0; command<24; command++) OutputControl[command] = NULL; command = (mode & 0x0c) << 1; // shift bits 2 and 3 into positions 4 and 5 command += (mode & 3); // add in bits 0 and 2 command |= 0x80; // OR in bit 7 for PPI set up outp(control, command); // set according to mode command } // end set_up_ppi()
Use experi6f.c to test the routines: NOTE: Please be sure to read the Warranty And Disclaimer before working with the hardware!
// experi6f.c void waitalittlewhile(void); #include #include #include #include <malloc.h> <conio.h> <stdio.h> <bios.h> // timer for test only
// include header with constants #include "constant.h" // include header with external prototypes #include "extern6f.h" enum { WarningLED, MainMotor, LeftArmMotor }; void main(void) { int x,y,*ptr1,*ptr2; get_port(); // get the port number and establish register locations // make everthing an output set_up_ppi(Aout_CUout_Bout_CLout); if(!ConfigureOutput(WarningLED,PA0)) printf("Error setting up Warning LED = %d port select = %d\n" ,WarningLED,PA0); if(!ConfigureOutput(MainMotor,PA1)) printf("Error setting up Main Motor = %d port select = %d\n" ,MainMotor,PA1); if(!ConfigureOutput(LeftArmMotor,PA2)) printf("Error setting up Left Arm Motor = %d port select = %d\n" ,LeftArmMotor,PA2);
http://www.learn-c.com/experiment6.htm
10/7/2011
Page 15 of 30
while(!kbhit()) { if(!TurnOn(WarningLED)) printf("Can't turn on the Warning LED\n"); else printf("Turned on the Warning LED\n"); waitalittlewhile(); if(!TurnOff(WarningLED)) printf("Can't turn off the Warning LED\n"); else printf("Turned off the Warning LED\n"); waitalittlewhile(); if(!TurnOn(LeftArmMotor)) printf("Can't turn on the Left Arm Motor\n"); else printf("Turned on the Left Arm Motor\n"); waitalittlewhile(); if(!TurnOff(LeftArmMotor)) printf("Can't turn off the Left Arm Motor\n"); else printf("Turned off the Left Arm Motor\n"); waitalittlewhile(); if(!TurnOn(MainMotor)) printf("Can't turn on the Main Motor\n"); else printf("Turned on the Main Motor\n"); waitalittlewhile(); if(!TurnOff(MainMotor)) printf("Can't turn off the Main Motor\n"); else printf("Turned off the Main Motor\n"); waitalittlewhile(); } // don't forget to free memory! FreeOutputControl(); portaoff(); } // end experi6f.c void waitalittlewhile(void) { long x; for(x=0L; x<100000L; x++); } // end waitalittlewhile(..)
Click here to download experi6f.c Click here to download digi6f.c Click here to download constant.h Click here to download extern6f.h You will need to hook up some LEDs and/or motors using the additional circuitry in Experiment 5 to test the program. That would be a very good idea, since what follows depends on what has past (quick, somebody write that down!). Seriously though, you need to make certain ConfigureOutput(..) works as it should before moving forward, and you won't really know that until you are sure the proper port lines are activated when commanded to do so. The example only uses Port A. It would be a very good idea to try the others as well. You might need to play with the number in the delay loop a little to get it to work right on your computer. Take out the // remark signs so ConfigureOutput(..) won't print any debug information once you get it working properly. Speaking of bugs, Professor/Admiral Grace Murray Hopper found the first computer bug in the Mark II computer on September 9th, 1945 -- a moth got caught in one of the relays. (read more about this computer science pioneer) The delay loop is still a problem. A more reliable way of timing events is needed. Fortunately, the computer has a built-in timer. It is a very accurate crystal-controlled device that produces an interrupt at reliable intervals. An interrupt is analogous to the bell on a telephone. A telephone with no bell would require a person to periodically pick it up to see if anyone was on the other end, which would be debatably more annoying than having to answer its ring. Devices such as the timer can interrupt the microprocessor to cause it to sevice the interrupt as required. Special functions called interrupt handlers or interrupt service routines ( ISR ) are set up for that purpose. Each interrupt is assigned a number which is a
http://www.learn-c.com/experiment6.htm
10/7/2011
Page 16 of 30
reference to the location of a pointer in the first 1024 bytes of memory in a PC, much like the index to the array of pointers we used earlier. The table is called an interrupt vector table or dispatch table. Our only concern here will be the first 16 locations. They are used for hardware interrupt requests ( IRQ ). Each pointer uses four bytes. The following program will display the first 16 pointers or vectors of a machine. It first sets a pointer to character to NULL, which means it points to location 0 in memory. The for() loop starts both x and n at 0, but increments x by 4 while incrementing n by 1. Thus, n keeps track of the interrupt number while x keeps track of the memory location of the vector. The dereferenced ptr shows what is in memory.
// experi6g.c #include <conio.h> #include <stdio.h> void main(void) { int x,y,n; char *ptr; ptr = NULL; for(x=0,n=0; n<16; x+=4,n++) { printf("Number = %03d Memory Location = %04d Vector = ",n,x); for(y=0; y<2; y++) { printf("%02X",*ptr); ptr++; } printf(":"); for(y=0; y<2; y++) { printf("%02X",*ptr); ptr++; } printf("\n"); } } // experi6g.c
Click here to download experi6g.c The program should produce the first 16 locations as shown below, although the pointer values will vary among machines. The pointers are found in memory in a segment:offset format. The segment/offset scheme is a way to address more memory than 16 bit registers would normally allow. It divides memory into 65536 16-byte segments or paragraphs, for a total of 65536 * 16 = 1048576 bytes. That means the starting point of the segment is easy to get -- just multiply the segment value by 16. The offset is the number of bytes into the segment. Thus, to determine where something actually is in memory, simply multiply the segment by 16 then add the offset. The timer uses interrupt 8. It can be seen from the table below that the vector is at 3120:3032 for the machine the program was run on. Remember from above however, that the bytes are reversed in memory. Thus, the segment = 0x2031 and the offset = 0x3230. First, Multiply the segment by 16. That is the same as shifting it to the left 4 bits, which gives us the HEX segment number with a 0 stuck on the right end of it -- 0x20310 -- now add the offset: 0x20310 + 0x3230 = 0x23540 = 144704 decimal
= = = = = = =
= = = = = = =
= = = = = = =
http://www.learn-c.com/experiment6.htm
10/7/2011
Page 17 of 30
= = = = = = = = =
= = = = = = = = =
= = = = = = = = =
3038:2F32 3120:3032 (timer interrupt) 3A32:333A 3535:2063 6C79:6465 2045:7870 2024:0043 204C:6962 7261:7279
Ralf Browns Interrupt List will provide you with more detail on vectors if you are interested. The routine for 8 actually invokes interrupt 1C which can be used by programs other than the operating system. That would be us. A special routine is written to handle the interrupt. The compiler knows what it is by the interrupt keyword:
// this happens on a timer interrupt interrupt new_timer() { disable(); // disable interrupts timer_counter++; // increment the counter enable(); // enable interrupts } // end interrupt new_timer()
Interrupt handlers have no return value and no arguments. Notice how little the routine does. Interrupt handlers should always do as little as possible due to the fact that they might prevent other important processes from taking place if they do too much. That's because they usually turn off interrupts before doing their work. The compiler I use uses the call to disable() to turn off interrupts, as do many others. You might have to check to find out what yours uses. Here is the updated extern.h with a few things not yet discussed:
// extern6h.h // external prototypes // digital routines -- also put at the top of digital.c without "extern" extern int ConfigureOutput(int arraynumber, int portselect); extern int TurnOn(int arraynumber); extern int TurnOff(int arraynumber); extern int is_closure(int input); extern void set_up_ppi(int mode); extern void blinker(long on, long off); extern void btoa(void); extern void motor(long on, long off); extern void motor2(long on, long off); extern void portaon(void); extern void portaoff(void); extern void portbon(void); extern void portboff(void); extern void portcon(void); extern void portcoff(void); // timer routines -- also put at the top of timer.c without "extern" extern double get_frequency(void); extern long get_timer_counter(void); extern void set_up_new_timer(void); extern void wait(double seconds); extern void restore_old_timer(void); // end extern6h.h
Click here to download extern6h.h The location of the new interrupt service routine must be placed in the vector table so the system can find it. The location of the old
http://www.learn-c.com/experiment6.htm
10/7/2011
Page 18 of 30
routine must also be saved so the vector can be restored when the program is through with it. A call to getvect(...) will get the pointer to the former location so it can be saved, and a call to setvect(...) will put the new one in the table. Since the former routine is probably not located in this program's segment, it is declared as a far pointer. A routine to get the value of the timer counter will also be added. Notice the two prototypes just above the timer routines in the following. One is the new interrupt handler. The other is a place holder for the old handler. The following is the timer module. Notice that the prototypes contained in this module are declared here as well as in extern6h.h. Here however, they are not declared as externals, since they are not external to this file. The digital prototypes should be placed at the top of digital.c in a like manner. It helps keep the confusion down for the compiler. With the compiler I use, the timer test would not run properly until I included the prototypes in both places. Notice how the far pointer to the old timer routine is declared. The name is surrounded by parenthesis due to the precedence problems that would occur without them (see Experiment 3). Also notice the call to disable() and enable() in almost all of the timer routines. They disable interrupts for the short time needed to perform various tasks, then enable interrupts. It's not a good idea to have an interrupt occur while you are trying to work with something that is or will become part of an interrupt routine. Recall that we will be dealing with the vector information at location 0x1C in the vector table. To set up the new routine, the old timer routine location is saved in the old_timer pointer using getvect(0x1c). The new routine is then recorded in the vector table using setvect(0x1c, new_timer). Restoring the old routine is simply a matter of using setvect() with the old timer pointer.
long get_timer_counter(void); void set_up_new_timer(void); void wait(double seconds); void restore_old_timer(void); double get_frequency(void); void interrupt new_timer(), interrupt (far *old_timer)(); unsigned long timer_counter; // save the old vector, set up new vector, zero out counter void set_up_new_timer(void) { disable(); // turn off interrupts old_timer = getvect(0x1c); setvect(0x1c, new_timer); timer_counter = 0L; enable(); // turn interrupts back on } // restore former table entry and rate void restore_old_timer() { disable(); setvect(0x1c, old_timer); enable(); } // return the value of the counter to the caller long get_timer_counter(void) { return timer_counter; } // the interrupt handler interrupt new_timer()
http://www.learn-c.com/experiment6.htm
10/7/2011
Page 19 of 30
Click here to download timer6h.c Now the new routine, rather than the old routine will be called when a timer interrupt occurs. All the new_timer() routine does is increment the timer counter which was set to 0 when the routine was placed in the vector table. It will do a little more latter, but should never do too much. It especially should not call such things as printf(). The reason is that many routines are not reentrant , which is to say they can't be reliably re-entered while they are in the middle of something. That is often because they use the same piece of memory each time for what they do. For example, something in main() could be using printf() when an interrupt occurs. If the ISR then uses printf(), the fixed-memory location would be clobbered and the results would probably look like anything but what was expected. Similar situations can cause much worse things to happen. See this article by David K. Every if you are interested in more detail about reentrant code. The test program is simplicity personified:
Click here to download experi6h.c Click here to download extern6h.h Now compile experi6h and timer6h and link them. The end product should be experi6h.exe. Run experi6h and you should see the timer counter changing. The only thing that's changing the variable however, is the timer interrupt service routine. Nothing in experi6h has access to the counter. The timer in the PC is driven by a 1193180 Hz (cycles per second) clock. It looks a lot like the square wave in Experiment 5 when viewed on an oscilloscope . There is a 16-bit divider register in the timer that is set to 0. The register counts down from wherever it's set -- 0 in this case -- with the first pulse rolling the register over from 0 to 65535. The timer will issue an interrupt when the register reaches 0 (but not on the first, loaded 0). It will then re-load the register with the original value, which it retains internally. The result is to divide the clock signal by 65536, producing an effective output of 18.20648193 Hz, and one interrupt every .054925493 seconds, or about 55ms. It would be nice to be able to set up the timer for any rate desired. No problem. Just set up the register with 1193180/frequency. Let's say 1000 Hz is desired so the system will produce 1000 interrupts per second. 1193180/1000 = 1193.180. Digital register can't be loaded with a floating point number, but 1193 gets pretty close since 1193180/1193 = 1000.15088 Hz. The port address for the timer register is 0x40. The same location is used for the ms and ls values. It's the order that determines what will go where -- least significant then most significant. The new set_up_new_timer() gets a double as an argument. That's the same as a float, but with higher precision. The argument tells set_up_new_timer() what frequency to use in setting up the timer. Another double called
http://www.learn-c.com/experiment6.htm
10/7/2011
Page 20 of 30
divideby is used to determine what the divider would be by setting it equal to the input frequency divided into 1193180. Since only the result less the fraction will be used, divideby is rounded by adding .5 to it. Any fraction >= .5 will cause the whole number part to move up by one. Recall that we need to end up with is a 16-bit number for the timer's counter register. The most signifcant portion is the upper 8 bits, and the least significant portion is the lower 8 bits. The ms portion is divideby shifted to the right 8 bits, with divideby cast as an unsigned integer. Forgotten what some of that is all about?: most significant and least significant: Data Lines shift operators: Experiment 1 casting: Experiment 5 The actual frequency that results is then calculated for access by other routines through get_frequency().
long get_timer_counter(void); int set_up_new_timer(double freq); void wait(double seconds); void restore_old_timer(void); double get_frequency(void); void interrupt new_timer(), interrupt (far *old_timer)(); unsigned long timer_counter; double frequency; // save the old vector, set up new vector, zero out counter // set up timer rate int set_up_new_timer(double freq) { unsigned ms,ls; double divideby; if(freq < (1193180.0/65536.0)) return 0; // can't go below this if(freq > 1193180.0) return 0; // or above this divideby = 1193180.0/freq; divideby+=0.5; // causes a round above .5 ms = (unsigned)divideby >> 8; // get upper 8 for ms ls = (unsigned)divideby & 0xff; // mask off lower 8 for ls frequency = 1193180.0/(double)((ms << 8) + ls); timer_counter = 0L; disable(); // turn off interrupts outp(0x40, ls); // least significant byte of timer count outp(0x40, ms); // most significant byte of timer count old_timer = getvect(0x1c); setvect(0x1c, new_timer); enable(); // turn interrupts back on return 1; } // restore former table entry and rate void restore_old_timer() { disable();
http://www.learn-c.com/experiment6.htm
10/7/2011
Page 21 of 30
outp(0x40, 0); // least significant byte of timer count outp(0x40, 0); // most significant byte of timer count setvect(0x1c, old_timer); enable(); } // return the frequency to the caller double get_frequency(void) { return frequency; } // return the value of the counter to the caller long get_timer_counter(void) { return timer_counter; } // wait for seconds and/or fractions of a second void wait(double seconds) { long wait_count, start_count; if(!seconds) return; if(timer_counter < 0L) return; wait_count = (long)((seconds * frequency) + 0.5); // round at .5 start_count = timer_counter; while((timer_counter - start_count) < wait_count); } // the interrupt handler interrupt new_timer() { disable(); timer_counter++; enable(); } // end timer6i.c
Click here to download timer6i.c The wait() routine takes advantage of the new timer setup. It is sent a double argument called seconds, representing the number of seconds of delay desired, which can include decimal fractions. The number of counts to wait will be the number of interrupts per second times the number of seconds to wait. Since the number of interrupts per second is the frequency calculated during setup, the count needed is the number of seconds desired times the frequency. This is rounded then cast to a long variable called wait_time. Another long called start_count gets the current counter value timer_counter. From then on, the number of counts elapsed will be equal to timer_counter - start_count. A while loop waits for that difference to be >= wait_count. Please note that set_up_new_timer () must be called or none of this will work. Also, it would probably be a good idea not to try to set the frequency too high. My test machine didn't like it when I tried 10K Hz. It worked up to 5K Hz just fine. The new extern header:
// extern6i.h // external prototypes // digital routines -- also put at the top of digital.c without "extern"
http://www.learn-c.com/experiment6.htm
10/7/2011
Page 22 of 30
extern extern extern extern extern extern extern extern extern extern extern extern extern extern extern
int ConfigureOutput(int arraynumber, int portselect); int TurnOn(int arraynumber); int TurnOff(int arraynumber); int is_closure(int input); void set_up_ppi(int mode); void blinker(long on, long off); void btoa(void); void motor(long on, long off); void motor2(long on, long off); void portaon(void); void portaoff(void); void portbon(void); void portboff(void); void portcon(void); void portcoff(void);
// timer routines -- also put at the top of timer.c without "extern" extern double get_frequency(void); extern long get_timer_counter(void); extern int set_up_new_timer(double freq); extern void wait(double seconds); extern void restore_old_timer(void); // extern6i.h
The test program sets the timer up for 4K Hz then attempts a 60 second timer at 1 second intervals. It waits for a keystroke to start so a watch can be used to check accuracy. The call to getch() waits for a single character from the keyboard. It would be a very good idea to compile timer and experi6i, link them, then run this test since the routines will be used in the future:
void main(void) { int x; set_up_new_timer(4000); printf("frequency = %f\n",get_frequency()); printf("Press any key to begin test: "); getch(); // wait for key puts(""); // print a blank line for(x=0; x<60; x++) { if(kbhit()) break; printf("x = %2d counter = %6ld\n",x,get_timer_counter()); wait(1); } // don't forget this one -- your computer could freeze!!! restore_old_timer(); } // end experi6i.c
Click here to download experi6i.c Click here to download extern6i.h The following test program combines output control with the interrupt-based timing routines. It is put together by linking the three object files after compiling. In MIX PowerC that would be: pcl experi6j digital timer
http://www.learn-c.com/experiment6.htm
10/7/2011
Page 23 of 30
This will produce an executible called experi6j.exe. Notice how the timer was set up. The desired interval is .1 second. In order to get good precision, the timer was set up to be about 100 times that fast. This was done by setting the timer close to 1000 Hz, but not exactly. The idea is to get close to the precision needed, but to try to avoid rounding in the counter calculations. To do that, divide the clock by the desired frequency, then use the nearest non-fractional number. For example, 1193180/1000 = 1193.180. Another one: the goal is to get close to 5K Hz. 1193180/5000 = 238.636. Use 1193180/239. Here, the closest non-fractional number to 1193.180 is 1000, so use 1193180/1000 as the argument for set_up_new_timer().
// experi6j.c #include <dos.h> #include <stdio.h> #include <bios.h> // include header with constants #include "constant.h" // include header with external prototypes #include "extern.h" enum { WarningLED, MainMotor, LeftArmMotor }; void main(void) { int x; get_port(); // get the port number and establish register locations // make everthing an output set_up_ppi(Aout_CUout_Bout_CLout); printf("1193180/1000 = %f\n" ,1193180/1000); set_up_new_timer(1193180/1000); printf("frequency = %f\n",get_frequency()); if(!ConfigureOutput(WarningLED,PA0)) printf("Error setting up Warning LED = %d port select = %d\n" ,WarningLED,PA0); if(!ConfigureOutput(MainMotor,PA1)) printf("Error setting up Main Motor = %d port select = %d\n" ,MainMotor,PA1); if(!ConfigureOutput(LeftArmMotor,PA2)) printf("Error setting up Left Arm Motor = %d port select = %d\n" ,LeftArmMotor,PA2); while(!kbhit()) { if(!TurnOn(WarningLED)) printf("Can't turn on the Warning LED\n"); else printf("Turned on the Warning LED\n"); wait(.1); if(!TurnOff(WarningLED)) printf("Can't turn off the Warning LED\n"); else printf("Turned off the Warning LED\n"); wait(.1); if(!TurnOn(LeftArmMotor)) printf("Can't turn on the Left Arm Motor\n"); else printf("Turned on the Left Arm Motor\n"); wait(.1); if(!TurnOff(LeftArmMotor))
http://www.learn-c.com/experiment6.htm
10/7/2011
Page 24 of 30
printf("Can't turn off the Left Arm Motor\n"); else printf("Turned off the Left Arm Motor\n"); wait(.1); if(!TurnOn(MainMotor)) printf("Can't turn on the Main Motor\n"); else printf("Turned on the Main Motor\n"); wait(.1); if(!TurnOff(MainMotor)) printf("Can't turn off the Main Motor\n"); else printf("Turned off the Main Motor\n"); wait(.1); } portaoff(); // always free memory! FreeOutputControl(); // always restore the old timer! restore_old_timer(); } // end experi6j.c
Click here to download experi6j.c The timer module can use the output control structure if it is provided with its description and declares it as an external. To do this, move the structure definition to outcont.h. A few more members have been added for future use. The name here is outcnt6k.h:
// outcnt6k.h struct OC { int PortAddress; // address of the port with this output line char onmask; // mask that will turn this line on when // ORed with the data value and stored back // mask that will turn this line off when // ANDed with the data value and stored back // pointer to the data value for the port // on-time setting for this line // a -1 means run continuously // off-time setting for this line // counts left for on time // counts left for off time
char offmask;
Click here to download outcnt6k.h The digital C file module no longer contains the declaration for the OC structure. It now includes outcont.h which does that. It does however, declare an instance of the structure: struct OC *OutputControl[24]; The timer C module file also includes outcnt6k.h, but shows the instance of OC as external: extern struct OC *OutputControl[24];
http://www.learn-c.com/experiment6.htm
10/7/2011
Page 25 of 30
The OutputControl array of OC pointers can be viewed as being in digital and accessable to both the digital and timer modules. Here are the tops of each. Digital -- OutputControl belongs to the digital C module:
// digi6k.c #include #include #include #include <dos.h> <stdio.h> <bios.h> "outcnt6k.h" // defines output control structure
struct OC *OutputControl[24]; // prototypes int ConfigureOutput(int arraynumber, int portselect); int TurnOn(int arraynumber); int TurnOff(int arraynumber); int is_closure(int input); void set_up_ppi(int mode); void blinker(long on, long off); void btoa(void); void motor(long on, long off); void motor2(long on, long off); void portaon(void); void portaoff(void); void portbon(void); void portboff(void); void portcon(void); void portcoff(void); // The following are known only to the functions in this file. // They can't be modified or even accessed by anything outside this // file except through funtions in this file designed to provide access. . . . . . // end digit6k.c
// timer6k.c #include #include #include #include <dos.h> <stdio.h> <bios.h> "outcnt6k.h" // defines output control structure
// in digi6k.c extern int ConfigureOutput(int arraynumber, int portselect); extern struct OC *OutputControl[24]; // local prototypes long get_timer_counter(void); int set_up_new_timer(double freq); void wait(double seconds); void restore_old_timer(void); double get_frequency(void); void interrupt new_timer(), interrupt (far *old_timer)(); unsigned long timer_counter; double frequency; . . . .
http://www.learn-c.com/experiment6.htm
10/7/2011
Page 26 of 30
. // end timer6k.c
It would actually be a lot cleaner to put everything at the top of the files into corresponding header files, then include the header files. Everything above that goes at the top of digi6k.c would be put in digi6k.h, then #include "digi6k.h" entered at the top of digi6k.c Everything above that goes at the top of timer6k.c would be put in timer6k.h, then #include "timer6k.h" entered at the top of timer6k.c It makes no difference how you do it. It cleans up the C file to have the header, but it's easier to see what items are if they are declared at the top of the file being worked on. Since the timer module now has access to the output control array, it can be used to set some of the variables for a node of the array. The following sets up pulse width modulation for a node. It first checks to make certain that offtime is reasonable, then calls ConfigureOutput(...). The seton member is set to -1 if the on time is less than 0, indicating the node stays on. Otherwise, seton is calculated to be the frequency (which is the number of interrupts per second) times the number of seconds desired. If ontime is greater than 0, the oncount member is set equal to the seton member, the setoff member is calculated and the node is turned on. The setoff and oncount members are set to 0 if ontime is less than or equal to 0. Offcount is always set to 0:
// Set up Pulse Width Modulation for an output // // arraynumber is the position in the output control array // // type: // 0 = unidirectional, no brake // 1 = unidirectional with brake // 2 = pwm line, directional line, no brake // 3 = pwm line, directional line, with brake // 4 = dual pwm lines -- both high = brake // 5 = pwm line and two direction lines as for L298 // 255 = last slot -- leave // // Forward and Reverse port numbers are pwm lines for each // // The Direction port number is provided for bridges that have a reverse line // Set to anything if not used // // The Brake port number is provided for circuits that have a brake line // Set to anything if not used // int pwm(int arraynumber, int type, int ForwardPortNumber, int ReversePortNumber, int DirectionPortNumber, int BrakePortNumber, double ForwardOnTime, double ForwardOffTime, double ReverseOnTime, double ReverseOffTime, int StartDirection) { if(StartDirection < 0 || StartDirection > 2) return 0; if(ForwardOnTime <= MinTime || ForwardOnTime >= MaxTime || ForwardOffTime <= MinTime || ForwardOffTime >= MaxTime) return 0; disable(); // no interrupts while setting up if(!ConfigureOutput(arraynumber, type, ForwardPortNumber, ReversePortNumber, DirectionPortNumber, BrakePortNumber)) { enable(); return 0; } OutputControlActive = 1; OutputControl[arraynumber]->ForwardSetOn = (long)((frequency * ForwardOnTime) + 0.5); // round up at .5 OutputControl[arraynumber]->ForwardSetOff = (long)((frequency * ForwardOffTime) + 0.5);
http://www.learn-c.com/experiment6.htm
10/7/2011
Page 27 of 30
OutputControl[arraynumber]->ForwardOnCount = OutputControl[arraynumber]->ForwardSetOn; OutputControl[arraynumber]->ForwardOffCount = OutputControl[arraynumber]->ForwardSetOff; OutputControl[arraynumber]->direction = StartDirection; OutputControl[arraynumber]->type = type; if(!type) // uni directional { enable(); return 1; } if(type == 1 || type == 3) // 1 and 3 have a brake { *OutputControl[arraynumber]->BrakePortData &= OutputControl[arraynumber]->BrakeOffMask; // turn off the brake outp(OutputControl[arraynumber]->BrakePortAddress, *OutputControl[arraynumber]->BrakePortData); } if(type > 1) // 2,3,4,5 use reverse pwm line, 2,3,5 use direction line { if(ReverseOffTime <= MinTime || ReverseOffTime >= MaxTime || ReverseOffTime <= MinTime || ReverseOffTime >= MaxTime) { free(OutputControl[arraynumber]); OutputControl[arraynumber] = NULL; enable(); return 0; } OutputControl[arraynumber]->ReverseSetOn = (long)((frequency * ReverseOnTime) + 0.5); // round up at .5 OutputControl[arraynumber]->ReverseOnCount = OutputControl[arraynumber]->ReverseSetOn; OutputControl[arraynumber]->ReverseSetOff = (long)((frequency * ReverseOffTime) + 0.5); OutputControl[arraynumber]->ReverseOffCount = OutputControl[arraynumber]->ReverseSetOff; if(type == 2 || type == 3 || type == 5) // 2,3,5 use a direction line { if(StartDirection == 1) *OutputControl[arraynumber]->DirectionPortData != OutputControl[arraynumber]->DirectionOnMask; // set for forward else if(StartDirection == 2) *OutputControl[arraynumber]->DirectionPortData &= OutputControl[arraynumber]->DirectionOffMask; // clear for reverse outp(OutputControl[arraynumber]->DirectionPortAddress, *OutputControl[arraynumber]->DirectionPortData); if(type == 5) { if(StartDirection == 1) *OutputControl[arraynumber]->BrakePortData &= OutputControl[arraynumber]->BrakeOffMask; // turn off the brake else if(StartDirection == 2) *OutputControl[arraynumber]->BrakePortData |= OutputControl[arraynumber]->BrakeOnMask; // turn on the brake outp(OutputControl[arraynumber]->BrakePortAddress, *OutputControl[arraynumber]->BrakePortData); }
http://www.learn-c.com/experiment6.htm
10/7/2011
Page 28 of 30
This information is used by the updated timer interrupt service routine to determine when an output should be turned on or off. The routine now runs through the 24 Output Control nodes. It skips all nodes that have not been set up or ones that run all the time. Notice that the on count was set to seton in pwm(..), and that the off count was set to 0. Thus, the first time around, new_timer() will find that the on counter is greater than 0 and decrement it. If the on counter is made zero by the decrement, the node will be turned off and the off counter loaded with setoff. If, on the other hand, the off counter is greater than zero, it will be decremented. If it is made zero by the decrement, the node will be turned back on and the on counter loaded with seton again.
// the timer interrupt handler interrupt new_timer() { int x; disable(); timer_counter++; for(x=0; x<24; x++) { if(OutputControl[x] == NULL) // not set up continue; if(OutputControl[x]->seton <= 0L) // stay on or not set continue; if(OutputControl[x]->oncount > 0L) { OutputControl[x]->oncount--; if(!OutputControl[x]->oncount) { // keep existing bits but remove this one *OutputControl[x]->PortData &= OutputControl[x]->offmask; // put the result in this node's port register outp(OutputControl[x]->PortAddress, *OutputControl[x]->PortData); OutputControl[x]->offcount = OutputControl[x]->setoff; } } // end if(OutputControl[x]->oncount > 0L) // note that this will not decrement as soon as set // above, but will wait until the next interrupt else if(OutputControl[x]->offcount > 0L) { OutputControl[x]->offcount--; if(!OutputControl[x]->offcount) { // keep existing bits and OR this one in *OutputControl[x]->PortData |= OutputControl[x]->onmask; // put the result in this node's port register outp(OutputControl[x]->PortAddress, *OutputControl[x]->PortData); OutputControl[x]->oncount = OutputControl[x]->seton; }
http://www.learn-c.com/experiment6.htm
10/7/2011
Page 29 of 30
} // end else if(OutputControl[x]->offcount > 0L) } // end for(x=0; x<24; x++) enable(); }
A node is continually turned on and off automatically in the new_timer() ISR. All main() has to do is call pwm(..) to set it up. It can then go about its business. On and off times can range from sub-milliseconds to years! That makes it good for everything from controlling the speed of a motor to flashing an LED to turning on and off such things as air-conditioning systems (providing output devices are added that can take the current and voltage). Here is the test program. There is no need to copy it as it and the other files can be downloaded below:
// experi6k.c // test extern void show(void); #include <dos.h> #include <stdio.h> #include <bios.h> // include header with constants #include "const6a.h" // include header with external prototypes #include "extern6a.h" enum { MainMotor, WarningLED, LeftArmMotor }; void main(void) { int x; double ontime; get_port(); // get the port number and establish register locations // make everthing an output set_up_ppi(Aout_CUout_Bout_CLout); printf("1193180/1000 = %f\n",1193180.0/1000.0); set_up_new_timer(1193180.0/1000.0); printf("frequency = %f\n",get_frequency()); if(!pwm(MainMotor,PA0, .003, .002)) printf("Error setting up Warning LED = %d port select = %d\n" ,WarningLED,PA0); if(!pwm(WarningLED,PA1, .03, .02)) printf("Error setting up Main Motor = %d port select = %d\n" ,MainMotor,PA1); if(!pwm(LeftArmMotor,PA2, .3, .2)) printf("Error setting up Left Arm Motor = %d port select = %d\n" ,LeftArmMotor,PA2); show(); // a little test routine in timer that shows contents of nodes
printf("press any key to end test\n"); getch(); // don't forget to free memory!
http://www.learn-c.com/experiment6.htm
10/7/2011
Page 30 of 30
To make life a little easier, you can download all of the files for this experiment below. On most machines, just right-click then do a save-as: experi6k.c digi6f.c timer6k.c timer6k.h const6a.h extern6a.h outcnt6k.h Now compile experi6k, timer6k and digi6f, then link them to form experi6k.exe. Notice the difference between this program and the previous ones? There is no infinite loop such as while(!kbhit()). Everything is set up, then there is a getch() which simply sits there and waits for a keystroke. Other activities could just as easily take place. The timer is taking care of pulse width modulation tasks in the background. Using the proper interfacing and driver devices (see Experiment 5), hook up LEDs and/or motors to the outputs. Notice the difference in rates. The .3 seconds on and .2 seconds off for PA2 provides a normal-looking flasing LED cycle. PA1 on the other hand, is too fast for an indicator but could be used for motor speed control. PA0's 3ms on and 2ms off moves too fast to tell it's blinking at all, but works well for speed control. In other words, it's the choice of numbers that determines what an output will do.
Previous: Experiment 5 - Controlling Motors Next: Experiment 7 - Bi-directional Control Of Motors And The H-Bridge
Problems, comments, ideas? Please Let me know what you think Copyright 2001, Joe D. Reeder. All Rights Reserved.
http://www.learn-c.com/experiment6.htm
10/7/2011