Embedded C
Embedded C
Ezrah Ogori
EMBEDDEDXPRESS
C programming for embedded
microcontroller systems
/* Function definitions*/
int function1(char x) { //parameter x passed to the function, function returns an integer value
int i,j; //local (automatic) variables – allocated to stack or registers
-- instructions to implement the function
}
/* Main program */
void main(void) {
unsigned char sw1; //local (automatic) variable (stack or registers)
int k; //local (automatic) variable (stack or registers)
Declare local variables
/* Initialization section */
-- instructions to initialize variables, I/O ports, devices, function registers Initialize variables/devices
/* Endless loop */
while (1) { //Can also use: for(;;) {
-- instructions to be repeated Body of the program
} /* repeat forever */
void main(void) {
count = 0; //initialize global variable count
while (1) {
math_op();
count++; //increment global variable count
}
C statement types
• Simple variable assignments
– Includes input/output data transfers
• Arithmetic operations
• Logical/shift operations
• Control structures
– IF, WHEN, FOR, SELECT
• Function calls
– User-defined and/or library functions
Arithmetic operations
• C examples – with standard arithmetic operators
int i, j, k; // 32-bit signed integers
uint8_t m,n,p; // 8-bit unsigned numbers
i = j + k; // add 32-bit integers
m = n - 5; // subtract 8-bit numbers
j = i * k; // multiply 32-bit integers
m = n / p; // quotient of 8-bit divide
m = n % p; // remainder of 8-bit divide
i = (j + k) * (i – 2); //arithmetic expression
C = A & B; A 0 1 1 0 0 1 1 0
(AND) B 1 0 1 1 0 0 1 1
C 0 0 1 0 0 0 1 0
C = A | B; A 0 1 1 0 0 1 0 0
(OR) B 0 0 0 1 0 0 0 0
C 0 1 1 1 0 1 0 0
C = A ^ B; A 0 1 1 0 0 1 0 0
(XOR) B 1 0 1 1 0 0 1 1
C 1 1 0 1 0 1 1 1
B = ~A; A 0 1 1 0 0 1 0 0
(COMPLEMENT) B 1 0 0 1 1 0 1 1
Bit set/reset/complement/test
• Use a “mask” to select bit(s) to be altered
C = A & 0xFE; A a b c d e f g h
0xFE 1 1 1 1 1 1 1 0 Clear selected bit of A
C a b c d e f g 0
C = A & 0x01; A a b c d e f g h
0xFE 0 0 0 0 0 0 0 1
C 0 0 0 0 0 0 0 h Clear all but the selected bit of A
C = A | 0x01; A a b c d e f g h
0x01 0 0 0 0 0 0 0 1
C a b c d e f g 1 Set selected bit of A
C = A ^ 0x01; A a b c d e f g h
0x01 0 0 0 0 0 0 0 1
C a b c d e f g h’ Complement selected bit of A
Bit examples for input/output
• Create a “pulse” on bit 0 of PORTA (assume bit
is initially 0)
PORTA = PORTA | 0x01; //Force bit 0 to 1
PORTA = PORTA & 0xFE; //Force bit 0 to 0
• Examples:
if ( (PORTA & 0x80) != 0 ) //Or: ((PORTA & 0x80) == 0x80)
bob(); // call bob() if bit 7 of PORTA is 1
c = PORTB & 0x04; // mask all but bit 2 of PORTB value
if ((PORTA & 0x01) == 0) // test bit 0 of PORTA
PORTA = c | 0x01; // write c to PORTA with bit 0 set to 1
Example of µC register address definitions in STM32Lxx.h
(read this header file to view other peripheral functions)
#define PERIPH_BASE ((uint32_t)0x40000000) //Peripheral base address in memory
#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000) //AHB peripherals
/* Base addresses of blocks of GPIO control/data registers */
#define GPIOA_BASE (AHBPERIPH_BASE + 0x0000) //Registers for GPIOA
#define GPIOB_BASE (AHBPERIPH_BASE + 0x0400) //Registers for GPIOB
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE) //Pointer to GPIOA register block
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE) //Pointer to GPIOB register block
/* Address offsets from GPIO base address – block of registers defined as a “structure” */
typedef struct
{
IO uint32_t MODER; /*!< GPIO port mode register, Address offset: 0x00 */
IO uint16_t OTYPER; /*!< GPIO port output type register, Address offset: 0x04 */
uint16_t RESERVED0; /*!< Reserved, 0x06 */
IO uint32_t OSPEEDR; /*!< GPIO port output speed register, Address offset: 0x08 */
IO uint32_t PUPDR; /*!< GPIO port pull-up/pull-down register, Address offset: 0x0C */
IO uint16_t IDR; /*!< GPIO port input data register, Address offset: 0x10 */
uint16_t RESERVED1; /*!< Reserved, 0x12 */
IO uint16_t ODR; /*!< GPIO port output data register, Address offset: 0x14 */
uint16_t RESERVED2; /*!< Reserved, 0x16 */
IO uint16_t BSRRL; /*!< GPIO port bit set/reset low registerBSRR, Address offset: 0x18 */
IO uint16_t BSRRH; /*!< GPIO port bit set/reset high registerBSRR, Address offset: 0x1A */
IO uint32_t LCKR; /*!< GPIO port configuration lock register, Address offset: 0x1C */
IO uint32_t AFR[2]; /*!< GPIO alternate function low register, Address offset: 0x20-0x24 */
} GPIO_TypeDef;
Example: I/O port bits
(using bottom half of GPIOB)
7 6 5 4 3 2 1 0
GPIOB h g f e d c b a
B = A << 3; A 1 0 1 0 1 1 0 1
(Left shift 3 bits) B 0 1 1 0 1 0 0 0
B = A >> 2; A 1 0 1 1 0 1 0 1
(Right shift 2 bits) B 0 0 1 0 1 1 0 1
The second form is a common error (omitting the second equal sign), and
usually produces unexpected results, namely a TRUE condition if n is 0 and
FALSE if n is non-zero.
IF-THEN-ELSE structure
• Execute one set of statements if a condition is met and an
alternate set if the condition is not met.
if (a == 0)
{
statement s1; Yes No
S1; a == 0 S3;
statement s2; S2; ? S4;
}
else
{
statement s3;
statement s4:
}
IF-THEN-ELSE HCS12 assembly language vs C example
AD_PORT: EQU $91 ; A/D Data Port
MAX_TEMP: EQU 128 ; Maximum temperature
VALVE_OFF: EQU 0 ; Bits for valve off
VALVE_ON: EQU 1 ; Bits for valve on
VALVE_PORT: EQU $258 ; Port P for the valve
...
; Get the temperature
ldaa AD_PORT C version:
; IF Temperature > Allowed Maximum
cmpa #MAX_TEMP #define MAX_TEMP 128
bls ELSE_PART #define VALVE_OFF 0
; THEN Turn the water valve off #define VALVE_ON 1
ldaa VALVE_OFF
staa VALVE_PORT if (AD_PORT <= MAX_TEMP)
bra END_IF
; ELSE Turn the water valve on
VALVE_PORT = VALVE_OFF;
ELSE_PART: else
ldaa VALVE_ON VALVE_PORT = VALVE_ON;
staa VALVE_PORT
END_IF:
; END IF temperature > Allowed Maximum
Ambiguous ELSE association
if (n > 0)
if (a > b)
z = a;
else //else goes with nearest previous “if” (a > b)
z = b;
if (n > 0) {
if (a > b) Braces force proper association
z = a;
while (a < b)
{ S1;
statement s1; a<b S2;
? Yes …
statement s2;
No “loop” through these
…. statements while a < b
}
Something must eventually cause a >= b, to exit the loop
WHILE loop example: AD_PORT: EQU $91 ; A/D Data port
MAX_ALLOWED:EQU 128 ; Maximum Temp
C vs. HCS12 Assembly LIGHT_ON: EQU 1
Language LIGHT_OFF: EQU 0
LIGHT_PORT: EQU $258 ; Port P
; ---
C version: ; Get the temperature from the A/D
ldaa AD_PORT
; WHILE the temperature > maximum allowed
#define MAX_ALLOWED 128 WHILE_START:
#define LIGHT_ON 1 cmpa MAX_ALLOWED
#define LIGHT_OFF 0 bls END_WHILE
; DO - Flash light 0.5 sec on, 0.5 sec off
ldaa LIGHT_ON
while (AD_PORT <= MAX_ALLOWED) staa LIGHT_PORT ; Turn the light
{ jsr delay ; 0.5 sec delay
LIGHT_PORT = LIGHT_ON; ldaa LIGHT_OFF
staa LIGHT_PORT ; Turn the light off
delay(); jsr delay
LIGHT_PORT = LIGHT_OFF; ; End flashing the light, Get temperature from the A/D
delay(); ldaa AD_PORT
} ; END_DO
bra WHILE_START
END_WHILE:
DO-WHILE loop structure
• Repeat a set of statements (one “loop”)
until some condition is met
do “loop” through
S1;
{ S2; these statements
statement s1; … until a < b
statement s2;
….
Yes
} a<b
Read
PORTB
Parameters passed to
Function arguments
• Calling program can pass information to a
function in two ways
– By value: pass a constant or a variable value
• function can use, but not modify the value
– By reference: pass the address of the variable
• function can both read and update the variable
– Values/addresses are typically passed to the
function by pushing them onto the system stack
• Function retrieves the information from the stack
Example – pass by value
/* Function to calculate x2 */
int square ( int x ) { //passed value is type int, return an int value
int y; //local variable – scope limited to square
y = x * x; //use the passed value
return(x); //return the result
}
void main {
int k,n; //local variables – scope limited to main
n = 5;
k = square(n); //pass value of n, assign n-squared to k
n = square(5); // pass value 5, assign 5-squared to n
}
Example – pass by reference
/* Function to calculate x2 */
void square ( int x, int *y ) { //value of x, address of y
*y = x * x; //write result to location whose address is y
}
void main {
int k,n; //local variables – scope limited to main
n = 5;
square(n, &k); //calculate n-squared and put result in k
square(5, &n); // calculate 5-squared and put result in n
}
In the above, main tells square the location of its local variable,
so that square can write the result to that variable.
Example – receive serial data bytes
/* Put string of received SCI bytes into an array */
Int rcv_data[10]; //global variable array for received data
Int rcv_count; //global variable for #received bytes
void SCI_receive ( ) {
while ( (SCISR1 & 0x20) == 0) {} //wait for new data (RDRF = 1)
rcv_data[rcv_count] = SCIDRL; //byte to array from SCI data reg.
rcv_count++; //update index for next byte
}
Other functions can access the received data from the global variable
array rcv_data[].
Some on-line C tutorials
• http://www.cprogramming.com/tutorial/c-
tutorial.html
• http://www.physics.drexel.edu/courses/Comp
_Phys/General/C_basics/
• http://www.iu.hio.no/~mark/CTutorial/CTutor
ial.html
• http://www2.its.strath.ac.uk/courses/c/
Tutorial to be continued …..