Intro to microcontrollers (long)
steve ravet
Steve.Ravet at arm.com
Thu May 27 00:40:05 GMT 1999
Shannen Durphey wrote:
> Some guys seem to treat this programming info like it's reserved for a
> chosen few. Generally, the people on this list are willing to help, I
> think they just forget where some of us stand in the puddle of
> knowledge.
> Shannen
> *doing the clue mating dance*
I'll start by saying I'm no expert on 6811 or GM stuff, so I'll try to
keep it general. Lets look at the 6811 architecture first. It'll help
if you have a Moto 6811 to refer to for this because it'll have diagrams
of stuff that I can't duplicate in email. The file I'm referring to was
chosen basically at random from the moto site, right here:
http://mot-sps.com/mcu/documentation/pdf/hc11rmr3.pdf
There are probably lots of differences between this part and the one GM
uses but it should be fine as far as the instruction set, core
architecture, and other stuff I'll talk about.
The "architecture" of a 6811 chip refers to the microcontroller core
(MCU), and any peripheral devices connected to it. Look at page 1-3 in
the above manual for a diagram that shows the architecture of this
particular chip. Right in the middle is a box called "CPU". This is
called the core, and is the part that is common to all 6811 variants.
The core executes instructions, contains the register file, and other
parts that make the 6811 what it is. Around the core you see other
peripheral devices, like:
A/D -- converter (takes an analog voltage and coverts it to a number
that can be used inside the processor)
SCI -- serial port for communications to other 6811s or a PC or
anything else.
ROM -- Memory that can only be programmed once, retains data while
power is off
EEPROM -- Memory that can be reprogrammed more than once, keeps data
while power is off
RAM -- Can be reprogrammed, loses data when power is off.
Timer subsystem -- Generates pulses of specific lengths at specific
times. Can be used for injectors, ignition timing, lots of other
things.
etc. There are dozens or more flavors of 6811, they all differ in the
types of peripherals around the core but they all have the same core.
GM 6811s also have external memory (the infamous PROM). Picture that as
another box in the diagram.
So let's concentrate on the core. Most microcontrollers/microprocessors
share some common features. One feature is called the register file.
Registers are places inside the MCU where values can be stored
temporarily while operating on them. Look in the above manual at page
1-4. There's a list of registers inside the CPU. The list shows these
registers:
A,B (D)
IX
IY
SP
PC
condition codes.
First a note on bits. The width of a register is expressed in bits.
>From the diagram we see that A is 8 bits wide, B is 8 bits wide, IX, IY,
SP, PC are all 16 bits wide. The width of a register specifies the
largest number that register can hold. Each bit can be a one or zero.
Counting in binary is like this:
0000 0000 = 0
0000 0001 = 1
0000 0010 = 2
0000 0011 = 3
0000 0100 = 4
0000 0101 = 5
0000 0110 = 6
0000 0111 = 7
and so on. an 8 bit register can hold values from 00000000 to 11111111
which corresponds to 0-255. A 16 bit register can hold values from 0 to
65,535
PC is an easy one. It's the program counter, and it always contains the
address of the next instruction to execute. It always increases,
meaning that the next instruction is always the one following the
current one. The only exception is a branch instruction which tells the
processor to start executing from a different location. It loads a new
value into the PC.
SP is the stack pointer. There is usually a region of RAM set up that
is called the stack. The stack is used to store temporary values during
a calculation, or to store values that are passed to a subroutine. A
subroutine is a well defined piece of code that takes some parameters,
calculates a value based on them, and returns it. An example would be a
subroutine that calculates injector pulse width. You would put it's
parameters on the stack (temp, rpm, MAP, etc) and call the subroutine.
The subroutine takes them off, calculates the pulse width, and puts it
on the stack, then returns. The calling routine takes the pulse width
off the stack and sends it to the hardware. It's called a stack because
it's like a stack of plates. You can only get to the top one. To get
to others requires removing the ones above it.
There are instructions called "psh" and "pul" in the 6811. These "push"
values on the stack and "pull" them back off respectively. The thing
that makes it a stack is the fact that after you push a value on the
stack the SP register is automatically decremented. After you pull
something off the stack it is incremented, so it always points to the
"top" of the stack. The stack starts at a high address and "grows"
towards a lower address. Might seem backwards but there are good
reasons for doing it that way.
IX and IY are index registers. These registers are used to access
memory (RAM,ROM,EEPROM, whatever). Basically you put the address you
want to read into IX or IY and then execute an instruction to read the
value from that location into a register.
A, B, and D are general purpose registers. The above registers are
special purpose. General purpose registers are used to store the
results from arithmetic instructions like add, subtract, shift, etc.
>From the diagram you can see that A and B are 8 bits wide, hence can
hold numbers up to 255. If you need to work with larger numbers you can
use the D register which is 16 bits wide. The only thing is that D is
really A and B stuck together. If you store a value to D you wipe out
what was in A and B. A will be the upper 8 bits and B will be the
lower.
How does all this work together? Lets say you want to calculate
injector pulse width via something like this (simple example, not the
way GM does it):
pulsewidth=base_width + PE
base_width has already been calculated from MAP and rpm or MAF or
whatever. You want to determine if the conditions for power enrich have
been met, if they have then an additional amount of fuel is added. In
addition, if the conditions for deceleration are met you want to cut the
fuel off altogether.
Lets assume there's a PE table that gives additional pulse width, and
it's indexed by RPM. You only use the table if TPS is 80% or more.
deceleration cutoff is determined by RPM being greater than 2000 and
throttle at 0%.
So the inputs to the calculation are TPS, RPM, base_width. TPS and RPM
come from sensors on the engine through the A/D and/or timer module.
We'll assume that's already been done and the values are sitting in RAM
somewhere. TPS is an 8 bit value where 0=0% and 255=100%. RPM is an 8
bit value where 0=0rpm, 1=20 rpm, 2=40 rpm, 255=5100 rpm. These values
are stored in RAM, which starts at 0 and goes to 255. Their particular
locations are:
0x05 (TPS)
0x22 (RPM)
0x3f (base pulse width)
0x1f0 (PE table)
The PE table is 16 bytes long and is located in ROM. The first location
represents rpm from 1000-1249, the second from 1250-1499, and the last
from 4750 to 4999. You index into the table based on rpm, and the value
there is an additional injector pulse width that is added to the base
width.
So let's write some code. semicolons indicate comments. a $ indicates
a hex value, no $ indicates decimal. A # sign means the number is a
constant, no # sign means indirection. Indirection means the number is
an address, and the value to use in the instruction is the value at the
address:
ldaa #0 ;loads the value 0 into register a
ldaa 0 ;load the value stored at location 0 into a
so here's the code:
;first get the base pulsewidth, store it in register b
ldx #$3f ;address of base pulsewidth
ldab x ;load the base pulsewidth, store it into b
;now check if PE mode applies
ldx #$05 ;address of TPS
ldaa x ;put TPS value into a
cmp #204 ;compare a (TPS) to 204. 80% of 255=204
bls decel ;if less than 80% skip PE lookup
;bls stands for branch if "less or same"
;it checks the result of the compare and
;takes the branch if the value of a was 204
;or less. If it was greater than execution
;continues from this point.
;PE mode
;first read RPM and convert it to a value between
;0-15 to index into the PE table
pshb ;save value of b
ldx #$22 ;address of RPM
ldaa x ;get rpm value into b
lsrb 4 ;divide rpm by 16 (convert to 0-15)
ldx #$1f0 ;base of PE table
abx ;ix=ix+b: add rpm index to table address
ldaa x ;get PE additional PW from PE table
pulb ;restore original value of b (base PW)
aba ;a=a+b add PE pulsewidth, result to a
;
; at this point register a holds the injector pulse width, either with
; or without the PE addition. Now check for decel and set PW to 0
; if necessary
; note that decel label is here. If the PE calculation wasn't
; necessary then the program jumped straight here, otherwise
; it executed to here
;
;for decel: if TPS=0% and RPM > 2000 than set the injector PW to 0
decel:
psha ;push PW onto stack
pulb ;pop PW into reg b
ldx #$5 ;address of TPS value
ldaa x ;get TPS into a
cmp #0 ;is it zero?
bne done ;if not zero, not in decel mode, done
ldx #$22 ;address of RPM
ldaa x ;get rpm to a
cmp #100 ;100=2000 rpm
bhi done ;if >2000, not in decel mode, done
;if we execute to this point them RPM is < 2000 and TPS is = 0.
;that means decel mode, so set the base PW to 0
ldab #0 ;PW=0
done:
yada yada yada
More code comes here to actually turn the injector on for the time
specified by the PW.
Some of the above might need a little more explanation. Whenever you
see two instructions like this:
cmp #204 ;compare a (TPS) to 204. 80% of 255=204
bls decel ;if less than 80% skip PE lookup
you are implementing a test. The compare instruction compares register
a to the value 204. It sets some bits in the condition code register
depending on if the values are the same, greater, less, etc. The bls
instruction checks those bits and takes the branch if it determines that
the values was "less or same". There are lots of branches, for equal,
not equal, signed/unsigned greater/lessthan, etc.
Most arithmetic instructions set condition codes so you might see code
like this without the compare:
aba
beq label
In this case if the sum of a and b was zero then the branch would be
taken, otherwise execution falls through to the next instruction.
A few last notes. labels are used to avoid repeating the addresses of
the variables all the time (#$5, #$22, etc.).
This is the first 6811 program I've ever written and I'm sure that are
both syntax errors that would keep it from assembling, and I'm sure that
the code could be condensed. But it illustrates how you'd write some
assembly code to get what you want done.
I haven't addressed how to set up all the peripherals, how to use the
A/D converter, etc. but those are a little more advanced than this
example. Once you understand how assembly language works you can look
at the examples in the manual to find out how to use the peripheral
devices.
Also, it's a much simpler problem to write the assembly to do what you
want than it is to stare at a disassembly (without comments!) and figure
out what it does. But if it wasn't hard it wouldn't be as much fun.
Hope this helps some people out, post any questions but please snip out
irrelevant parts.
--steve
--
Steve Ravet
steve.ravet at arm.com
Advanced Risc Machines, Inc.
www.arm.com
More information about the Gmecm
mailing list