Outputs, Inputs, and Timers

 


Here, we well go on the product and the outputs, inputs, and timers based on this specific project. 

Digital states can be read or set by the software as inputs or outputs. These are IO pins, GPIO pins, and GIO pins (General Purpose Input Output, Input Output, General Input Output). We will go over Atme's AVR microcontroller, the TI MSP430x2xx, and the ARM Cortex Microcontroller in this article. 

We initialize a pint as an output or input and set the pin high when you want the LED on or set the pin low when you want the pin off, or you can go active low and reverse this logic. We need to talk to the appropriate register to do anything with the I/O line. The registers are an API to the hardware, described in the chip manual to configure the processor and control peripherals, and a memory mapped, so you can write to a specific address to modify that particular register. 

Registers use bitwise operations | is an OR and & is an AND symbol. Here are some of the register operations that you ideally should know: 

register = register | (1 << 3); // turn on the 3rd bit on the register. 

register |= 1 << 3 //turn on the 3rd bit of the register, but in a more concise version. 

A nibble is half a byte, or 4 bits. 

0001 is (1 << 0)

0010 is (1 << 1)

0100 is (1 << 2) 

0110 can be ((1<<2)|(1<<1)) or ((1<<2) + (1<<1)) or (3<<1) where << is left shift and >> is right shift, as indicated by the direction that the arrow is pointing in. 

A byte is 2 nibbles so 0x80 is (0x8 << 4). 

There is a register that can be set controlling the direction o a pin such that it is an output. 

0x1234 is (0x12 << 8) + (0x34). If you want to see a change in your values, use an oscilloscope.

Most I/O pins can be either inputs or outputs, and the first register will control the direction of the pin so that it is an output. First we determine the pin that we are changing, and we'll say the by default a specific pin is an I/O.



The user manual tells whether the pin is an I/O or SPI pin. Another register may determine the purpose of the pin, you may also look at the peripheral section to turn off unwanted functionality if some pins are shared between peripherals. 

To configure a pin as I/O pin, you should look for a section denoted as the 'I/O Configuration'. 

Once finding the register you can figure out whether you need to set or clear the bit in the register. You can attempt to hardcode the result, but don't do this, because you can treat registers as global variables, not this:

*((int*)0x0070C1) |= (1 << 2);

There should be a header file to treat registers as global variables as a result. 

There are different ways of setting IO1_2 processor of being an output here for different processors, such as the following:

LPC_GPIO1->DIR |= (1 << 2);

P1DIR |= BIT2; 

DDRB |= 0x4 which sets the third least significant bit. 

The register modification will change the intended bit but also might has unintended consequences. You need to read the current register value, modify this value, and writing it back to the register and you need to do this sequentially immediately after each other, else the register might have changed in the between operations. 

So that's how you set the pin to be an output. 

The next step is to the LED and we need to go to the go to the appropriate register and perform the command. 

To do this, we need to find the appropriate register in the user manual, So let's put some register examples here:

LPC_GPIO1->DATA |= (1 << 2);

P11OUT |= BIT2;

PORTB |= 0x4;

The I/O registers are accessed at an address through a structure, and have a bit modified, in a header file. 

typedef struct {

    __IO uint32_t DATA; 

    uint32_t RESERVED0[4095]; //the same data appears at 4096 locations in the gpio address space and 12 bits of the address bus can be used for bit masking.

    __IO uint32_t DIR; //direction for output, clear for input

    __IO uint32_t IS; //interrupt sense

    __IO uint32_t IBE; //interrupt on falling and rising edges

    __IO uint32_t IEV; //interrupt event register

    __IO uint32_t IE; //interrupt enable

    __IO uint32_t RIS; //raw status register

    __IO uint32_t MIS; //masked interrupt status register

    __IO uint32_t IC; //interrupt clear

} LPC_GPIO_TypeDef;


#define LPC_AHB_BASE    (0x50000000UL)

#define LPC_GPIOo_BASE (LPC_AHB_BASE + 0x00000)

#define LPC_GPIO1             ((LPC_GPIO_TypeDef    *) LPC_GPIO1_BASE)

After this article would be a good time to describe pointers in C. 

We eventually need to turn the LED off, as follows: 

LPC_GPIO1->DATA |= ~(1 << 2);

P11OUT |= ~(BIT2);

PORTB |= ~0x4;

So now, what we need to do is to put things together and compile the program, load it, and test it, and may re tweak delay loops. 

main

    initialize the direction of the I/O pin to be an output

loop

    set the LED on

    do nothing for some period of time

    set the LED off

    do nothing for the same period of time

    repeat

If you ever have trouble, follow this process:

First, the peripheral is a device that is interacting with the embedded processor.

First we want to check to see whether a pin is shared between different peripherals. Verify the I/O pin's functionality is as expected. Then, check that the pin doesn't need additional configuration or have a feature turned off by default. Some registers need a bit set to act as an IO pin, some don't, make sure to check this. Now make sure that the code being run is the code being compiled. Eliminate any noncritical initializations and make sure that the watchdog timer is off. Pins can sink more current than they can provide, so now you turn on an LED by writing 0 rather than 1. If still doesn't work, then consider that it might be a hardware issue. Did you look at the pin specifications? (Current) The datasheet? Ask for help or get a multimeter. Make sure that no pins are broken, and the pins aren't shorted together. 

Let's say that marketing liked your first prototype, and they might want to tweak it a bit later. If a pin changes, you have to change the header that corresponds to the board as well. 

To avoid hardcoding the pin, you should use a board-specific header file. You just have to change the value of the header, and the lines of code to configure a particular part can be processor-independent: 


#define LED_SET_DIRECTION (P1DIR)

#define LED_REGISTER (P1OUT)

#define LED_BIT (1 << 3)


and this is how we configure them: 

LED_SET_DIRECTION |= LED_BIT; //set I/O to be the output

LED_REGISTER |= LED_BIT; //turn the LED on

LED_REGISTER &= ~LED_BIT; //Turn the LED off


Things can be unwieldy if you have wayyy too many I/O lines and need other registers. To fix this, give only

1. The port

2. The position of the port. 


Like this:

#define LED_PORT 1

#define LED_PIN 3


And we can use header files to recompile to use different builds for different boards.

We can use 3 header files. There is a file for the old board pin assignments, then one for the new pin assignments, and then include the one needed in main.c file. We can also have a generic file as well. 

Here it is:

#if COMPILING_FOR_V1

#include "ioMapping_v1.h"

#elif COMPILING_FOR_V2

#include "ioMapping_v2.h"

#else

#error "NO I/O MAP. WHAT'S THE TARGET?"

#endif

Using a board-specific header file hardens development process. Sequester the information from the functionality of the system. 

Sometimes, we need to handle multiple ports in a generic way, which means initializing a pin to be an output, setting a pin high when LED is on and setting a pin low when the LED is off. Sometimes you can group initializations but that breaks the modularity of the systems. 

We need to set the pin. It is better to have each subsystem to initialize the I/O as needed. However, don't separate the interfaces in the header file, because the pins are collected together to make the interface with hardware more easily. 

Setting a pin high and low can be done with IOWrite(port, pin, high/low). Or we can do it to IOSet(port, pin) or IOClear(port, pin). We want to make the LED toggle. We can hide IOSet and IOClear in IOToggle, or something like that.


Now XOR (exclusive or) is a somewhat magical bitwise operation, and it doesn't have logical analog so remembering it can be tough. It can be used to find overflows or toggle LEDs on and off using XOR.

Here's the truth table and Venn Diagram: 



The IOWrite function does everything in one function so it takes up less code space, but has more parameters, which takes up more stack space. There is more functions with IOSet/Clear/Toggle Options, but less possible variables. This evaluation gets you to think of the interface in another dimension. 

The modifications put the IO handling code in the main module, and here can be a possible implementation: 

void main(void) {

    IOSetDir(LED_PORT, LED_PIN, OUTPUT);

    while(1) {

        IOToggle(LED_PORT, LED_PIN);

        DelayMs(DELAY_TIME);

    }

}

With the main function no longer directly dependent on the processor, we can subsequently reuse this code for other projects. 

Here is a comparison of architectures, which we can use to create even more flexible and reusable architectures. 

As the product features expands, the I/O interface is going to get much more complex. A façade provides a simplified interface to a piece of code and these basically make software libraries easier to use. It hides the details of the processor and hardware, something like the software library.

The adapter pattern is like a general version of the façade pattern, which helps to simplify certain layers. Adapters restate similar information, while façades reinterpret information. Its goal is to make the software library easier to use. The underlying code should be able to change while leaving a façade intact. Facades may increase the size of the code but might be worth it in terms of debuggability and maintainability. 

LEDInit() calls the I/O initialization for the LED pin, while LEDBlink() blinks the LED. 

The next section we want to go over is the input in I/O. The addition of a button doesn't make the schematic too much more complex. The button uses IO2_2 which is denoted as switch 1 S1. The pin will be connected to the ground when you press the switch.

When a pin is an input, pull-up resistors give a consistent value even if nothing is attached. Some processors can even have pull-down resistors. Internal pullups, however, conserve some power.

A pull-up resistor connects unused input pins (AND and NAND gates) to the dc supply voltage, (Vcc) to keep the given input HIGH. A pull-down resistor connects unused input pins (OR and NOR gates) to ground, (0V) to keep the given input LOW.

The setup to setup pins is:

1. Add the pin to the I/O map header file
2. Configure this pin to be an input and verify it is part of a peripheral. 
3. Configure a pull-up is necessary. 

After which we need a function to us this pin, as denoted:

The button will connect to ground which means that this signal is an active low. 

We make subsystems in order to keep details of a system hidden, and in this case, the button. We want to know whether the use of an I/O function has taken action. 

void ButtonInit() calls the initialization function for the button and ButtonPressed() returns true when the button is down.  The I/O function returns level of a pin, but we want to know whether a user is taking action or not. 

We can invert the signal in order to determine whether a button is currently pressed. I will draw this diagram, and then try to explain everything from there.

There's a few ways to implement main() at the higher level. Here's one: 

main:
    initialize LED
    initialize Button
loop:
    if button pressed, turn LED off
    else toggle LED
    do nothing for a period of time
    repeat

A response of 100ms is around ideal. 

We can also poll to see if a button is pressed.

loop:
    if button pressed, turn LED off
    else
        if enough time has passed,
            toggle LED
            clear how much time has passed
    repeat


If an LED needs to be turned off as fast as possible, use an interrupt. 
The code can be much more simpler with an interrupt routing.

loop:
    it button not pressed, toggle LED
    do nothing for a period of time
    repeat

The interrupt will call the function to turn off the LED, making the Button and LED systems depend on each other. You exchange intertwining for speed usually, or modularity for speed. 


The next section that I want to go over is the Momentary button press. This time we need to know when the button will be pressed and when it will be released. We like the switch to look like the following: 



The interrupt can help us catch the user input so that the main loop won't have to poll the I/O pin so quickly. With an interrupt, the input pins can be configured to interrupt when the signal at the pin is at a certain level. Interrupt on the rising edge so when the user presses a button, nothing happens until she releases it, as the signal goes high. 

The following code is on how to detect different blink rates when pressing a button: 

interrupt when the user presses the button: 
    set global button pressed = true
loop: 
    if global button pressed,
        set the delay period (reset or decrease it) 
        set global button pressed = false
    if enough time has passed,
        toggle led
        clear how much time has passed

Basically here, the light turns on, and then it turns off, and turn the slight off when it goes the opposite direction. 

To check a global variable, you need the volatile keyword. Volatile means that the global variable can be changed unexpectedly and should never be optimized out. Everything shared between interrupts and normal code should be marked as volatile. 

Setting a pin to be an interrupt is separate than setting a pin to be an input. You should save the complexity of the interrupt configuration for the pins that require it. 

IOConfigureInterrupt(port, pin, trigger type, trigger state) configures a pin to be an interrupt which will trigger when it sees an edge or level. An interrupt occurs when a level is at high or low, the extremes. IOInterruptEnable(port, pin) enables the interrupt associated with a pin, and IOInterruptDisable(port, pin) disables the interrupt associated with this pin. Interrupts are more generic when they are consisted of ports and not of pin.

Many buttons do not provide clean signal in the ideal button, but bouncy signals as a mechanical and electrical effect, is called Switch Bouncing. 

This is not the worst, unfortunately analog level signals can have uncertain logic levels. You want to look for a relatively long period of consistent signal in order to perform debouncing. 

To debounce the switch take multiple readings of the pin, and you need the I/O line reading, a counter, and the debounced button value. The length of debouncing depends on the required magnitude of the counter before the debounced button is changed state. Looking at 5 consecutive samples is pretty conservative. Oftentimes, you can get away with just looking at 3 samples. Although the code can be much more complicated this is the skeleton code: 

read button: 
    if raw reading same as debounced button value:
        reset the counter
    else:
        decrement the counter
        if the counter is zero:
            set the debounced button value to raw reading
            set the changed to true
            reset the counter
main loop: 
    if time to read button:
        read button
        if button changed
            set button changed to false
            reset the delay period
        if time to toggle the LED
            toggle LED
        repeat

There can be a timer to add more specificity of the function. 

The LED subsystem knows only about the ouput on the pins of the board, and now you can toggle the number of LEDs based solely on the number of button presses.

if number button presses = 0 toggle blue led
if number button presses = 1 toggle red led
if number button presses = 2 toggle green led

You need three different LED subsystems in the LED toggle function, or a LED function will need to take a parameter. A function mapping consumes processor cycles, but the other type will repeat the code.
        
The goal is to create a method to use one option out of particular options, and we can do this by adding a state variable to save a few processor cycles, despite creating more complication.

main loop: 
    if time to read button,
        read button
    if button changed and button is no longer pressed
        set button change to false
        change the LED
   if time to toggle the LED
        toggle LED
    repeat

This gets rid of the numbers, and make everything dependent of the state variable. 

We can go beyond a state variable to something more flexible. We can use abstraction to deal with dynamic changes, in a process called dependency injection. Before, we were hiding the I/O pin and we remove the dependency of LED code with the injection, letting the IO code know what to handly. A car depends on an engine. A manufacturer can inject any of the dependency options that the car can use to get around, the car doesn't do this. The LED code is like a car, made generic enough to avoid dependence on pins. This allows composing the system at runtime. In C++ we do this by passing a I/O pin handler object to the LED whenever a button is pressed. It is a very powerful technique, especially if a module is way more complicated. This doesn't work with more complex systems though, which require more specialization.

Dependency injection allows flexibility, and takes more RAM and extra processor cycles. You need to find a balance in such a system. 

We use a timer if we want to cycle through the a series of precise blink rates for a button. We use a timer to make things more precise, and then subsequently see if marketing can accept this. A timer is a simple counter measuring time by accumulating a certain number of clock ticks. The more deterministic a master clock is, the more precise the timer. They operate independently of software execution and you need to determine the clock input in order to set the frequency of the timer. It can be a processor clock or a peripheral clock.

The last number of a processor determines the clock, the number of instructions that the processor can handle in a second. The code is not the same as the oscillator. Phase Lock loops multiply slower clocks to get a faster clock, and this makes the processor speed possibly much faster than the onboard oscillator. However there can be some drift, and as a result, errors. 

A prescale register is able to divide the clock so the clock counter increments at a slower rate. If a prescale is 2 the prescaled clock will toggle at half of the system clock speed. The timer will count up, and the processor notes when the timer matches the compare register. When the timer matches, it continues counting up and reset. 


The timer counter holds the changing value of the timer. The compare register is doing the action whenever the timer equals the register. The action register sets up an action to take when the timer and the compare register are the same. The action register sets up an action to take when the timer and the compare register are the same. 4 actions are interrupts, stop or continuing counting, resetting the counter, and setting the output pins to high, low, toggle, or nothing. The clock configure register tells the subsystem which clock to use, the precale register divides the clock and the control register sets the timer to start counting. Interrupt register, checks, clear, and enable status of the interrupt.

The timer frequency is the clockIn / (prescaler * compareReg). We need to adjust the prescaler such that the timer frequency is close enough to the goal. Returning to the 8-bit timer and goal frequency.  The regsisters are all whole values and the compare register has to lie between 0 and 255. There are some heuristics for finding a prescalar which can be

prescalar = clockIn / (compareReg * timerFrequency). 

Prescaler * compare Register = Clock Input / Goal Frequency. Determine the factors of ClockIn/TimeFrequency and arrange them into the prescalar and compareReg value. Sometimes, floating point number cannot be simplified. We calculate percent error by the following:

Error = 100 * (goal_frequency - actual_frequency) / goal_frequency. The below diagram will help calculate the timer heuristics. Wwe might try binary values to search up for a minimum prescalar. You can also use brute force timer solution to find the prescaler. Here are the solutions: 


1. Limit the value of the min and max prescalar so they are integers and fit into the bits. Calculate the compare register for goal timer frequency for each whole number in the range. Round this register to whole number, and calculate the actual timer frequency. Find the prescale and compare register with the least amount of error between the minimum and maximum prescaler (set the compare register to 1). 


If you get an exception, either use a larger timer or disconnect the IO line from the timer, and call an interrupt when the timer expires, which increments and takes action whenever the timer is long enough. Once after determining settings, you need to remove the code to toggle the LED, configure the pins, configure the timer settings, and start the timer. 

The last thing I want to do is put an example of this, since these concepts can be hard to understand. 

Using Pulse-Width Modulation: 

Market research shows that potential customers are bothered by the brightness the LED. This is a good time to describe the theory of pulse-width modulation, determining whether a pin stays high or low. PWMs operate continuously and turn a peripheral on and off on a regular schedule. Cycle in the PWM is usually very fast. These signals often drive motors and LEDs and a processor can control the amount of power the hardware gets. The brightness is dependent on the amount of times the LEDs are on for each cycle. A 50% duty cycle is when a light is on 50% of the time, 20% when the light is on 20% of the time, and so on.  

PWM with 100% duty cycle is always on, and a 0% duty cycle is driven low. We can implement a PWM with an interrupt. 


Given a timer we can implement a PWM with an interrupt.

The first if there are 200 ticks is 

Turn on led, set compare register to 160 (80% of 200), then reset the timer. 

Turn LED off, set compare register to 40 and reset the timer, for the interrupt (if we want to do 80%, we ping these 2 attempts). 

The more you increase the frequency, the more the LED will look dim. You can carry out this procedure in the processor. Which pins acting as PWM really depends on the processor, though often they are a subset of the pins that act as timer inputs. There are PWM controller configurations as well and they can be used in various devices such as motors. You modify the duty cycle to get the snoring effect. You can also set the LED colors to different levels using PWM. 

It is simple tot set the parameters and ship the code. A product starts out to an idea and often takes many iterations to solidify into reality, so make sure to get a good prototype going. Always update the code, but make sure you can revert to the old code whenever possible. Here compares spaghetti prototype with a much simpler design.

Many things are easy to remove if they are not needed. However, we need to decide if we should leave a dependency injection in. It increases flexibility but leaves the configuration of the system rigid, especially if you have to allocate a timer. You can make a file with the goal of reducing the bugs. 

On the right the code base is trimmed down, and it keeps the definitions because the cost is low. Embedded engineers tend to use older project. The code aids in marketing and you learned a lot when writing and testing it, and ends up clean. You need to balance leaving the code's in flexibility with maintainability coming with cleanness. 

Here's an interview question: What's wrong with this code? 

void IOWaitForRegChange(unsigned int* reg, unsigned int bitmask) {
    unsigned int orig = *reg & bitmask;
    while(orig == (*reg & bitmask) {/*do nothing*/;}
}

The code compiles with optimization on and the code is missing the volatile keyword since the orig code can change, when the register changes, thus requiring the volatile word.  

Comments

Popular Posts