One of the things I’ve wanted to do since I was a teenager in the 80’s is to build a homebrew 8-bit microcomputer. If you scan through YouTube you’ll quickly find that I’m not the only one. In fact many people have done this and done it very well. The question I keep coming up against though is, if I’m creating something from scratch, what would I consider “from scratch” to actually mean?

Many of the implementations you see online for example, will put together a 6502 or Z80 chipset with either a serial interface or a simple video interface and keyboard and plug in a Microsoft BASIC ROM. For me though, I’ve always known that I want to try to write my own version of BASIC. In some instances I’ve found people who have built microprocessors (or parts of microprocessors) from logic gates. This makes the “from scratch’ question even harder to answer. For example, do you allow yourself to use RAM, multiplexer, ALU components etc and create you’re own CPU and memory? Will you only allow yourself to use gates? Good luck if you choose the latter!

Similarly there’s the question of what tools you allow yourself to use. If I want to create a BASIC ROM, is it ‘cheating’ for me to compile the code on my MacBook Pro and test it using an emulator? In the end it comes down to practicality as there’s only so much someone can do on their own or within a small group of hobbyists and makers.

It was while pondering some of these questions that I came across Jeri Ellsworth’s YouTube channel which adds a whole new dimension to the question of what “from scratch” actually means!

While these are not new videos, if you haven’t seen them already I recommend you grab a coffee and spend 20 minutes giving them a watch.

It’s a lot of fun writing code on your Discovery board with its myriad of peripherals to explore and exposed headers for further experimentation. Nothing, however, beats the satisfaction to be had from creating and coding your own board and there comes a time when every red blooded geek must strike out on their own!

I’ve recently been presented with an opportunity to create a new board that required a lot more grunt than the PIC based boards I normally deal with. We needed a much faster processor, an FPU and some DSP capability. As a result I opted to use the STM32F405RG as the MCU. The next thing I wanted to do was to mash up a prototype but I needed a quick and easy way to program it. I remembered that the Discovery board has an ST-LINK section with an SWD interface broken out onto headers so I thought I’d give it a go.

STM32F4 Discovery ST-LINK Jumpers

Setting up your Discovery to use it as a programmer is a very straightforward affair. The ST-LINK section is the top third of the board and can be easily isolated by removing the two CN3 jumpers shown above.

You’re also clearly going to need to expose the SWD interface on your board so you have something to connect the ST-LINK to. The schematic below shows how I did it.

jtag

A couple of things to note about the circuit above. I’ve pulled BOOT0 low which means that I’m programming flash memory. If you want to program other areas of memory then you’ll want to be able to change the state of this and the BOOT1 pin which is multiplexed with PB2 on the STM32F40x parts. Details can be found in section 2.4 of the reference manual.

The other thing I did was to expose the additional JTAG interface pins also in order to give me several programming and debugging options. Finally, I connected the NRST pin on the ST-LINK to the NJRST pin on my STM32. That may be wrong, you might be better off connecting it to the NRST pin on the STM32. Perhaps someone out there can let me know in the comments below.

The SWD interface is connected as follows:

ST-LINK to STM32F405 Connections

I chose to expose the interface as a row of 0.1” pitch header and connected the two boards together with female to female patch leads.

The next step is to try and get some code running on the device. I’m using Atollic TrueSTUDIO Lite for this but I’m sure the process is pretty much the same for other IDEs too.

Project Creation Steps

To get up a program up and running on the new board you need to create a new project that targets the STM32F405 or whichever STM32 part you’re using. This will generate a new, compilable and runnable project. If you run the program up now in the debugger you should be able to step through the code, look at variable values and memory content. This shows that everything is working fine.

The final step is to sort out your clocks. TrueSTUDIO will create a default system_stm32f4xx.c file containing a clock configuration for you but unless you happen to be using a 25MHz crystal you’ll want to change it for one targeting your own board. Mine, for example, uses a 4MHz external clock.

STM32 Clock Configuration Tool

STM32 Clock Configuration Tool

The easiest way to do this (as far as I’m aware at least) is to use the clock configuration tool provided by STMicroelectronics for the purpose. You can download the tool and the accompanying notes from their website. I used the simple wizard mode, followed the instructions very carefully and where I wasn’t sure of a value, I looked it up in the original file created by TrueSTUDIO. Once the new system_stm32f4xx.c is generated, it needs to replace the one in your project. Simply re-build the project, make sure you can still launch and debug it and you’re up and running.

So I’ve been running with this setup for a couple of weeks now with very few problems. I did get a bit of a scare when I saw smoke pouring out of the Discovery’s USB port once but I think that was a pretty freak incident as it hasn’t happened since. I’ve had the odd unexplained debugger disconnection but given the amount of myopic prodding I tend to do with scope probes that could be more about what I’m doing than the what the debugger is doing. All in all I think it provides quite a good option to anyone wishing to experiment with creating their own STM32 boards without forking out for new kit.

I’ve recently developed what some might consider to be an unhealthy interest in FPGAs and Verilog. The stuff you can can do with these things is just awesome. As a result of this I went onto YouTube to look for inspiration and information and I came across a series of video lectures by Cornell University Senior Lecturer Bruce Land. Just check out the stack machine and compiler lecture. I would seriously recommend these videos to anyone else out there who might be struggling with an FPGA obsession.

Bruce Land, honestly, you absolutely rock!

Rich and Jon

I mentioned in a post a while back that I was planning to leave my previous job to try to put something new together. Well a whole lot has happened since then and so I reckon it’s probably about time for a bit of an update.

I eventually left Siemens at the end of September 2013 after an eight month notice period (blame a lack of self-confidence!) and, along with fellow geek Richard Hackett, we formed Polyhedrus Electronics Ltd in order to grab an opportunity that came our way to work in the world of motor sport.

The opportunity seemed like a pretty straightforward one: to come up with a sensor that can make contactless linear measurements over a variety of lengths. We were pointed at a bunch of existing products in the marketplace that were close to what was required and off we went.

Within a few months Rich and I had a working prototype that came close to meeting the spec. It didn’t quite have the linear range we needed and the output, an analogue voltage signal, didn’t quite have the range we were after but when it was demonstrated at the Performance Racing Industry show in Indianapolis towards the end of 2013 it caused quite a stir and it wasn’t long before orders came in and we were suddenly faced with the prospect of turning our prototype into a full on production unit.

This is pretty much where we are now. The first few production units are out there and are being tested by an F1 team who are hoping to use them in the 2014 season. We’ve also produced a capacitive fuel level sensor PCB and have a few other exciting projects on the go which I’m sure we’ll be discussing over the coming months.

So the million dollar question then, was it worth it?

Well the answer at the moment would have to be, “Hell yeah!”

I wouldn’t say that we’ve got it completely nailed yet. Unlike Rich who has worked at home for many years, I’m still getting used to that aspect of the work (hardest thing is to stop!) There are still a few niggles with the boards that we’re working through and timescales in motor racing are crazy so we’ve had to make that adjustment too. Trying to get a production process and supply chain going at short notice is a huge challenge and we’re still debugging that. Our feet haven’t touched the ground in the last 6 months!

Despite all of this though I think it’s fair to say that this has been an exhilarating experience and we’re loving it. Rich and I decided very early on that the core value of our new business would be Freedom. Freedom to travel (we both enjoy that), freedom to work the way we want to work and freedom to explore new and interesting technology and ideas. The business has a bit of a hippy vibe to it that I love and we’ll need to work hard to make sure that the daily pressures of keeping our business going don’t detract from that.

For anyone out there who is thinking of doing something like this themselves I’d say go for it! Make sure you are doing something you love and seek out the work of people such as Seth Godin, Marianne Cantwell and others who can provide concrete help and advice.

Meanwhile if there’s anyone out there with a really cool idea and needs a couple of talented geeks to make it happen, feel free to drop me a line and we’d be more than happy to discuss it with you.

We’ve been too busy to get ourselves a website yet but we have just put together a small Facebook page if you fancy finding out more about what we’re up to. The link is https://www.facebook.com/polyhedruselectronics.

LM60 circuit

Measuring the temperature of a PCB is quite a common thing to have to do in embedded systems and I have been working on a board that needs to do just that. There are dozens of ways to achieve this but for this project we chose to use the LM60 from Texas Instruments. The data sheet for this part can be found at http://www.ti.com/lit/ds/snis119d/snis119d.pdf. Note that for this project I’m using a PIC18 MCU and the CCS C compiler. If you’re using another development environment you’ll need to adapt the source code accordingly.

The LM60 isn’t exactly the most accurate temperature sensor in the world, accuracy in the data sheet is quoted at +-3.0-4.0% but in this application we were looking to provide PCB temperature over a CAN interface so that this can be monitored and correlated to any PCB failure that might occur in the field. The accuracy of the LM60 is good enough in this particular instance.

What this part does have going for it though is that it is small, it comes in SOT-23 as well as TO-92 packages, and it has a linear voltage output with respect to temperature which makes it pretty straightforward to convert to an absolute value in degrees Celsius using an ADC in a microcontroller. The LM60 is capable of measuring temperatures from -40 to +125 degrees and there is an automotive spec version, the LM60-Q1 which is fortunate as that was exactly what I needed.

The output voltage of the LM60 ranges from 174mV at -40ºC to 1205mV at 125ºC regardless of input voltage which can range from 2.7 to 10V. The output voltage with respect to temperature is calculated using the equation:

Vo = (+6.25mV/ºC x TºC) + 424mV

Translating that into the general form for a linear equation y = mx + c we can see that the two coefficients are:

Gradient m is 6.25
Offset c is 424

The next step is to figure out what values these voltages will be converted to by the ADC. The PIC18 I used in this project has 12 bit ADCs and the circuit runs at 3.6V. By dividing the ADC range (0-4095) by the voltage range (0-3600mV) we find that each 1mV at the ADC results in a value of 1.14… or it would if the value wasn’t an integer.

With that in mind we can convert the Vo to ºC equation into an ADC value to ºC equation by multiplying each of the coefficients by 1.14. This results in the new equation:

ADCin = (7.11 x TºC) + 482.3

Finally we need to move the equation around a bit as we’re not trying to calculate the ADCin value, we’re going to be getting that from the MCU. What we’re interested in is converting from the ADCin value to a temperature. Re-arranging the equation gives us:

TºC = (ADCin – 482.3) / 7.11

There’s just one other trick used in this driver and that is to avoid the use of floating point maths by multiplying up the values by a fixed amount and returning a result in milli-Celsius. Floating point maths, especially on an 8-bit PIC18, is an incredibly slow thing and best avoided where at all possible. To get around this I used a fairly unsophisticated technique of multiplying the input value and c-coefficient by 1,000,000 and the m-coefficient which will be the divisor in the equation by 1,000. By having the dividend an order of magnitude higher than the divisor and outputting in 1000ths of a degree, we can perform integer division without losing too much precision.

The final code for the driver is listed below. There are plenty of ways in which it could be extended and improved. For example, the code configures the ADC to use a VREF of VDD which is circuit dependent and affects the coefficients used in the equation. There are of course other options. You could for example fix VREF at a specific voltage if your PIC supports that. For example, the PIC18 I was using supports a fixed voltage reference (FVR) of 2.048V which would make better use of the 12-bit ADC and make it VDD independent (as long as VDD > 2.048V obviously!) Alternatively you could pass the value of VDD into the initialisation method and calculate the coefficients at that point.

lm60_driver.h

/*
 * File:   lm60_driver.h
 * Author: Jon Masters
 *
 * Created on 22 January 2014, 11:11
 *
 * Driver code for lm60 temperature sensing using a 10 or 12-bit ADC channel.
 * Assumes that the lm60 chip is being driven at VDD. All temperatures are
 * returned as degrees "milli" Celsius to avoid FP maths.
 *
 * PREREQUISITES:
 * ==============
 *
 * Code must include a #use delay.
 * Code should include either of the following:
 *
 * #device ADC=10
 * #device ADC=12
 *
 * If #device ADC=10 is used, include the following pre-processor definition:
 *
 * #define __LM60_10BIT_ADC
 *
 * Before using any ADC, CCS requires you to call setup_adc(). This driver
 * library will call this as part of the initialisation method. If you don't
 * want it to do that then include the following pre-processor directive:
 *
 * #define __LM60_INIT_NOSETUP
 *
 * You are then responsible for ensuring the call to setup_adc() is made.
 *
 * USAGE:
 * ======
 *
 * Start by calling the lm60_init method passing in the analog channel information
 * corresponding to the channel that the LM60 is connected to. Note that the method will,
 * by default initialise the ADC to use the internal clock. To override this
 * behaviour use __LM60_INIT_NOSETUP and call setup_adc() yourself.
 *
 * Once this is done you can call lm60_read_temp_mC() to read the temperature in
 * thousandths of a degree Celsius.
 */

#ifndef LM60_DRIVER_H
#define     LM60_DRIVER_H

#ifdef     __cplusplus
extern "C" {
#endif

/* Constants used to convert voltages to temperatures. */
#define lm60_y_intercept 482300000
#define lm60_y_grad 7110

/*
 * Holds the index of the ADC channel that the LM60 Vout is connected to.
 */
int8 lm60_adc_channel;

/*
 * Initialises the driver library.
 *
 * ARGS:
 *  sANx - the analog port that the LM60 Vout is connected to. Use the constants
 *          defined in the PIC header e.g. sAN0
 *  channel - the index of the analog channel that matches the sANx argument e.g.
 *          for sAN0, this will be the index 0, sAN25 will be 25.
 *
 * RETURNS:
 *  Nothing.
 */
void lm60_init(unsigned int16 sANx, int8 channel);

/*
 * Returns a temperature reading from the LM60 in milli-Celsius i.e. in 1/1000's
 * of a Celsius. E.g. 25C is returned as 25000mC. The means that FP maths isn't
 * required which should improve performance.
 *
 * ARGS:
 *  NONE.
 *
 * RETURNS:
 *  Temperature in mC.
 */
signed int32 lm60_read_temp_mC();

#ifdef     __cplusplus
}
#endif

#endif     /* LM60_DRIVER_H */

lm60_driver.c

/*
 * File:   lm60_driver.c
 * Author: Jon Masters
 *
 * Created on 22 January 2014, 11:05
 *
 * See header for pre-reqs, usage and function documentation.
 */

#include "lm60_driver.h"

void lm60_init(unsigned int16 sANx, int8 channel)
{
   
#ifndef __LM60_INIT_NOSETUP
    setup_adc(ADC_CLOCK_INTERNAL | ADC_TAD_MUL_16);
#endif //__LM60_INIT_NOSETUP

    setup_adc_ports(sANx, VSS_VDD);
    lm60_adc_channel = channel;
}

signed int32 lm60_read_temp_mC()
{
    set_adc_channel(lm60_adc_channel);
    delay_us(10);
    unsigned int16 r = read_adc();

#ifdef __LM60_10BIT_ADC
    r = r << 2;
#endif

    unsigned int32 adc_val = r;
    adc_val *= 1000000;

    // calculate result in milli-Celsius
    signed int32 rv = adc_val - lm60_y_intercept;
    rv /= lm60_y_grad;
    return rv;
}

As always I welcome your feedback. Please feel free to post comments / questions below, especially if I’ve made some horrendous gaff! Otherwise, I hope this has been in some way useful and I’ll see you next time.

PWM LEDS Breadboard

PWM LEDS Breadboard

In last few posts I looked at ways to chase a few LEDs and dim them using the PWM functionality of the PIC16F887 which is mounted on the PICkit2 demo board. I decided that I wanted to go a bit further with this though and to combine the two things together and see if I could get some smoother chase action going by using PWM to pulse the LEDs on and off rather than switch them on and off.

I had to go ditch the demo board to do this though as the LEDs on the demo board aren’t really connected to suitable inputs, unless of course you implement PWM in code using GPIO which is possible. I happen to have a PIC18F26K22 knocking about in a 28 pin DIP package, some breadboard and a bunch of other bits and bobs so I decided to build my own demo board for this project.

The first thing I did was to build a basic circuit that would allow me to power, program and run the PIC. I then checked the PIC data sheet to see where the CCP (capture, compare, PWM) pins are and added in a few LEDs along with current limiting resistors. Finally I dug out a 10K variable resistor and wired that up to one of the analog inputs of the PIC. The circuit diagram is shown below.

PWM LEDs Schematic

PWM LEDs Schematic

As the PIC18F26K22 is not supported by the PICkit 2 I also had to get hold of a PICkit 3 programmer. It’s time I just bought the programmer on its own rather than buy one with a demo board. After all, I’m creating my own board now right?

Next I had to figure out exactly what it was that I wanted the thing to do. I decided that I’d keep the chasing LEDs but use PWM to dim the brightness using a set of brightness values derived from a sine wave. Each of the three LEDs would start from a different point in the sine wave so that the first is at 0 degrees, the second at 45 degrees and the last one at 90 degrees. I decided to implement this using a look up table of byte values. The variable resistor, I decided, would be used to control speed.

Having moved to a new PIC, I had to generate a new set of config values. Once again I used the tool built into the MPLAB IDE to do this. I set it to use the internal oscillator and I switched LVP off as requested by the PICKit 3 programmer. Generating the code yielded the following which I added into the main.c code file.

// PIC18F26K22 Configuration Bit Settings

// CONFIG1H
#pragma config FOSC = INTIO67   // Oscillator Selection bits (Internal oscillator block)
#pragma config PLLCFG = OFF     // 4X PLL Enable (Oscillator used directly)
#pragma config PRICLKEN = ON    // Primary clock enable bit (Primary clock enabled)
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable bit (Fail-Safe Clock Monitor disabled)
#pragma config IESO = OFF       // Internal/External Oscillator Switchover bit (Oscillator Switchover mode disabled)

// CONFIG2L
#pragma config PWRTEN = OFF     // Power-up Timer Enable bit (Power up timer disabled)
#pragma config BOREN = SBORDIS  // Brown-out Reset Enable bits (Brown-out Reset enabled in hardware only (SBOREN is disabled))
#pragma config BORV = 190       // Brown Out Reset Voltage bits (VBOR set to 1.90 V nominal)

// CONFIG2H
#pragma config WDTEN = ON       // Watchdog Timer Enable bits (WDT is always enabled. SWDTEN bit has no effect)
#pragma config WDTPS = 32768    // Watchdog Timer Postscale Select bits (1:32768)

// CONFIG3H
#pragma config CCP2MX = PORTC1  // CCP2 MUX bit (CCP2 input/output is multiplexed with RC1)
#pragma config PBADEN = ON      // PORTB A/D Enable bit (PORTB pins are configured as analog input channels on Reset)
#pragma config CCP3MX = PORTB5  // P3A/CCP3 Mux bit (P3A/CCP3 input/output is multiplexed with RB5)
#pragma config HFOFST = ON      // HFINTOSC Fast Start-up (HFINTOSC output and ready status are not delayed by the oscillator stable status)
#pragma config T3CMX = PORTC0   // Timer3 Clock input mux bit (T3CKI is on RC0)
#pragma config P2BMX = PORTB5   // ECCP2 B output mux bit (P2B is on RB5)
#pragma config MCLRE = EXTMCLR  // MCLR Pin Enable bit (MCLR pin enabled, RE3 input pin disabled)

// CONFIG4L
#pragma config STVREN = ON      // Stack Full/Underflow Reset Enable bit (Stack full/underflow will cause Reset)
#pragma config LVP = OFF        // Single-Supply ICSP Enable bit (Single-Supply ICSP disabled)
#pragma config XINST = OFF      // Extended Instruction Set Enable bit (Instruction set extension and Indexed Addressing mode disabled (Legacy mode))

// CONFIG5L
#pragma config CP0 = OFF        // Code Protection Block 0 (Block 0 (000800-003FFFh) not code-protected)
#pragma config CP1 = OFF        // Code Protection Block 1 (Block 1 (004000-007FFFh) not code-protected)
#pragma config CP2 = OFF        // Code Protection Block 2 (Block 2 (008000-00BFFFh) not code-protected)
#pragma config CP3 = OFF        // Code Protection Block 3 (Block 3 (00C000-00FFFFh) not code-protected)

// CONFIG5H
#pragma config CPB = OFF        // Boot Block Code Protection bit (Boot block (000000-0007FFh) not code-protected)
#pragma config CPD = OFF        // Data EEPROM Code Protection bit (Data EEPROM not code-protected)

// CONFIG6L
#pragma config WRT0 = OFF       // Write Protection Block 0 (Block 0 (000800-003FFFh) not write-protected)
#pragma config WRT1 = OFF       // Write Protection Block 1 (Block 1 (004000-007FFFh) not write-protected)
#pragma config WRT2 = OFF       // Write Protection Block 2 (Block 2 (008000-00BFFFh) not write-protected)
#pragma config WRT3 = OFF       // Write Protection Block 3 (Block 3 (00C000-00FFFFh) not write-protected)

// CONFIG6H
#pragma config WRTC = OFF       // Configuration Register Write Protection bit (Configuration registers (300000-3000FFh) not write-protected)
#pragma config WRTB = OFF       // Boot Block Write Protection bit (Boot Block (000000-0007FFh) not write-protected)
#pragma config WRTD = OFF       // Data EEPROM Write Protection bit (Data EEPROM not write-protected)

// CONFIG7L
#pragma config EBTR0 = OFF      // Table Read Protection Block 0 (Block 0 (000800-003FFFh) not protected from table reads executed in other blocks)
#pragma config EBTR1 = OFF      // Table Read Protection Block 1 (Block 1 (004000-007FFFh) not protected from table reads executed in other blocks)
#pragma config EBTR2 = OFF      // Table Read Protection Block 2 (Block 2 (008000-00BFFFh) not protected from table reads executed in other blocks)
#pragma config EBTR3 = OFF      // Table Read Protection Block 3 (Block 3 (00C000-00FFFFh) not protected from table reads executed in other blocks)

// CONFIG7H
#pragma config EBTRB = OFF      // Boot Block Table Read Protection bit (Boot Block (000000-0007FFh) not protected from table reads executed in other blocks)

I also set the clock frequency by setting the IRCF bits in the OSCCON register. This is the first statement in my main() function.

/*
 * The main function.
 */
void main()
{
    // set up the internal oscillator
    OSCCONbits.IRCF = 0b111;  // 16MHz

    while(1)
    {
    }
}

Knowing I was going to need a delay function later on, I defined _XTAL_FREQ at 16MHz and created a millisecond delay function based on the __delay_ms macro.

#define _XTAL_FREQ 16000000

/*
 * Implements a delay of a number of milliseconds.
 */
void delay(int ms)
{
    while(ms-- > 0)
    {
        __delay_ms(1);
    }
}

The approach I took to generating the sine table of brightness values was to calculate the values using a spreadsheet. I arbitrarily decided on 100 values and created a list of angles in 3.6 degree steps. In the column next to it I generate raw sine values for each angle which I multiplied by 128 and added 127. This gave me a range of values between 0 and 255 and I transferred those into an array of values in the program.

/*
 * Defines a list of values that are derived from a sine wave that
 * can be used to smoothly pulse the PWM duty cycle and the LEDs.
 */
const char brightness_values[] = {
    128, 136, 144, 152, 160, 168, 175, 182, 190, 197,
    203, 210, 216, 221, 227, 232, 236, 240, 244, 247,
    250, 252, 254, 255, 255, 255, 255, 255, 254, 252,
    250, 247, 244, 240, 236, 232, 227, 221, 216, 210,
    203, 197, 190, 182, 175, 168, 160, 152, 144, 136,
    128, 120, 112, 104,  96,  88,  81,  74,  66,  59,
     53,  46,  40,  35,  29,  24,  20,  16,  12,   9,
      6,   4,   2,   1,   0,   0,   0,   1,   2,   4,
      6,   9,  12,  16,  20,  24,  29,  35,  40,  46,
     53,  59,  66,  74,  81,  88,  96, 104, 112, 120
};

#define BRIGHTNESS_VALUES_COUNT 100

These values are going to act as my duty cycles to control the LED brightness.

Once I had done that I added in a method to initialise the PIC PWM on the three CCP modules I wanted to use to drive the LEDs. The configuration on the PIC18 is unsurprisingly slightly different to that of the PIC16 I was using before so, having used the previous trick of turning the steps in the data sheet into comments, I went back to the data sheet and worked through each step to make sure everything was properly configured.

I wanted to make sure my resolution was still 8 bits to match my brightness data so having selected a TMRx divider of 8 and a PRx value of 64, I added a new sheet to my ‘useful calculations’ spreadsheet to do the math and check it.

PWM Calculator

PWM Calculator

It turns out that the resolution is 8 bits with those settings and I have a period frequency of 7.7KHz which is great. The final code for the initPWM() method is below.

/*
 * Initialises PWM on the three PWM generators.
 */
void initPWM()
{
    // 1. Disable the CCPx pin output driver by setting the associated TRIS bit.
    TRISC = 0b00000110; // RC1 and RC2
    TRISBbits.RB5 = 1;

    // 2. Select the 8-bit TimerX resource, (Timer2, Timer4 or Timer6) to be
    //    used for PWM generation by setting the CxTSEL bits in the
    //    CCPTMRSx register.
    CCPTMRS0bits.C1TSEL = 0; // user timer 2

    // 3. Load the PRx register for the selected TimerX with the PWM period value.
    PR2 = 0x40; // decimal 64 period

    // 4. Configure the CCP module for the PWM mode by loading the CCPxCON
    //    register with the appropriate values.
    CCP1CONbits.CCP1M = 0b1100;
    CCP2CONbits.CCP2M = 0b1100;
    CCP3CONbits.CCP3M = 0b1100;

    // 5. Load the CCPRxL register and the DCxB bits of the CCPxCON register,
    //    with the PWM duty cycle value.
    CCPR1L = 128 >> 2;
    CCPR2L = 128 >> 2;
    CCPR3L = 128 >> 2;

    CCP1CONbits.DC1B = 128 & 0b00000011; // 128 is 50% duty cycle
    CCP2CONbits.DC2B = 128 & 0b00000011; // 128 is 50% duty cycle
    CCP3CONbits.DC3B = 128 & 0b00000011; // 128 is 50% duty cycle

    // 6. Configure and start the 8-bit TimerX resource:
    //    - Clear the TMRxIF interrupt flag bit of the
    //      PIR2 or PIR4 register. See Note 1 below.
    PIR1bits.TMR2IF = 0;

    //    - Configure the TxCKPS bits of the TxCON
    //      register with the Timer prescale value.
    T2CONbits.T2CKPS = 0b11; // prescale 8

    //    - Enable the Timer by setting the TMRxON
    //      bit of the TxCON register.
    T2CONbits.TMR2ON = 1;

    // 7. Enable PWM output pin:
    //    - Wait until the Timer overflows and the TMRxIF bit of the PIR2 or PIR4
    //      register is set. See Note 1 below.
    while(PIR1bits.TMR2IF == 0){}

    //    - Enable the CCPx pin output driver by clearing the associated TRIS bit.
    TRISC = 0;
    TRISBbits.RB5 = 0;
}

This method can then be called from the main() function prior to the while loop to initialise PWM across the three outputs. I also added a separate method to allow the duty cycles for each output to be set individually.

/*
 * Sets the PWM values for each of the three PWM controllers.
 */
void setPWM(char value1, char value2, char value3)
{
    CCPR1L = value1 >> 2;
    CCP1CONbits.DC1B = value1 & 0b00000011;

    CCPR2L = value2 >> 2;
    CCP2CONbits.DC2B = value2 & 0b00000011;

    CCPR3L = value3 >> 2;
    CCP3CONbits.DC3B = value3 & 0b00000011;
}

All this method does is replace the PWM duty cycle value in each of the CCPRxL and CCPxCON registers. The bit shifting and masking ensures that the LSBs end up in the DCxB bits of the CCPxCON register while the MSBs are written to the CCPRxL registers.

The next task was to add in the code to configure the ADC to read the data from the variable resistor. Again, I took the initialisation procedure from the data sheet and turned it into a set of comments that I could then take my time filling in. There are a couple of things that needed some thought. Firstly the ADC clock needed to be divided by 16 to bring it into the range of speeds that the ADC can cope with. This is all detailed in the data sheet in table 17-1 so there’s no mysterious voodoo in any of that. The other thing that needs to be calculated is the acquisition delay length. The ADC uses an sample and hold circuit based on an RC network and the acquisition time is the amount of time it’ll take to charge the capacitor before performing the conversion. Obviously you need to give it enough time to do that job and the way to do that is to set a period in TAD units in the ACQT bits of the ADCON2 register. Equation 17-1 is provided in the data sheet to help calculate the minimum TAD required. Again, I popped it into my ever growing spreadsheet of useful calculations and selected 12 as my value. This leaves plenty of time for signal capture in this application.

/*
 * Initialises the variable resistor ADC configuration.
 */
void initVR()
{
    // 1. Configure Port:
    //  - Disable pin output driver(See TRIS register)
    TRISAbits.RA0 = 1;

    //  - Configure pin as analog
    ANSELAbits.ANSA0 = 1;

    // 2. Configure the ADC module:
    //  - Select ADC conversion clock
    ADCON2bits.ADCS = 0b101; // Fosc/16

    //  - Configure voltage reference
    ADCON1bits.PVCFG = 0; // +ref is VDD
    ADCON1bits.NVCFG = 0; // -ref is VSS

    //  - Select ADC input channel
    ADCON0bits.CHS = 0; // connect AN0 to the ADC

    //  - Select result format
    ADCON2bits.ADFM = 0; // left justified

    //  - Select acquisition delay
    ADCON2bits.ACQT = 0b101; // 12 TAD

    //  - Turn on ADC module
    ADCON0bits.ADON = 1;
}

As with the previous programs I set the result alignment to be left aligned so that I could return a single byte value that worked across the whole of the potentiometer range. There’s plenty of resolution there for something this simple. Reference voltages again are VDD and VSS (GND) as that matches the potentiometer circuit and the analog input is configured to be taken from AN0 which is multiplexed with RA0.

I also added in a function to read and return the potentiometer value. This simply starts a conversion and waits for it to complete by set the GO bit in ADCON0 and waiting for it to clear. We could use interrupts for this which would be a neater solution but this will do for now. Finally the result is returned.

/*
 * Reads the value of the variable resistor into a single byte.
 */
char readVR()
{
    // start a conversion
    ADCON0bits.GO = 1;

    // wait for it to complete
    while(ADCON0bits.GO == 1){}

    // return the top 8 bits of the value
    return ADRESH;
}

Almost there now, the last thing to do was to update the main function to call of these methods and light up the LEDs.

/*
 * The main function.
 */
void main()
{
    // set up the internal oscillator
    OSCCONbits.IRCF = 0b111;  // 16MHz

    // init the PWM
    initPWM();

    // init the VR
    initVR();

    // brightness counter
    char val1 = 0;
    char val2 = 0;
    char val3 = 0;

    while(1)
    {
        // create a set of values at 45 degrees to each other in the table
        val1 = (val1 + 1) % 100;
        val2 = (val1 + 33) % 100;
        val3 = (val1 + 66) % 100;

        // set the PWM values
        setPWM(brightness_values[val1],
               brightness_values[val2],
               brightness_values[val3]);

        // create a delay proportional to the VR value
        delay((readVR() / 10) + 2);
    }
}

It’s all very straightforward. I created three values to hold individual indexes into the brightness values for each LED. They start at different points in the cycle and increment each time through the loop. The modulus operator resets the index back to the start of the look up table if they wrap around. Meanwhile the delay is set by the potentiometer reading from the AD conversion so that the speed of the chasing can be set. The delay value is manipulated a bit because I wanted to limit it from 2ms to roughly 30ms. Those values were determined through experimentation to see what felt about right.

The complete main.c code is included below.

#include <htc.h>
#include <pic18f26k22.h>


// PIC18F26K22 Configuration Bit Settings

// CONFIG1H
#pragma config FOSC = INTIO67   // Oscillator Selection bits (Internal oscillator block)
#pragma config PLLCFG = OFF     // 4X PLL Enable (Oscillator used directly)
#pragma config PRICLKEN = ON    // Primary clock enable bit (Primary clock enabled)
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable bit (Fail-Safe Clock Monitor disabled)
#pragma config IESO = OFF       // Internal/External Oscillator Switchover bit (Oscillator Switchover mode disabled)

// CONFIG2L
#pragma config PWRTEN = OFF     // Power-up Timer Enable bit (Power up timer disabled)
#pragma config BOREN = SBORDIS  // Brown-out Reset Enable bits (Brown-out Reset enabled in hardware only (SBOREN is disabled))
#pragma config BORV = 190       // Brown Out Reset Voltage bits (VBOR set to 1.90 V nominal)

// CONFIG2H
#pragma config WDTEN = ON       // Watchdog Timer Enable bits (WDT is always enabled. SWDTEN bit has no effect)
#pragma config WDTPS = 32768    // Watchdog Timer Postscale Select bits (1:32768)

// CONFIG3H
#pragma config CCP2MX = PORTC1  // CCP2 MUX bit (CCP2 input/output is multiplexed with RC1)
#pragma config PBADEN = ON      // PORTB A/D Enable bit (PORTB pins are configured as analog input channels on Reset)
#pragma config CCP3MX = PORTB5  // P3A/CCP3 Mux bit (P3A/CCP3 input/output is multiplexed with RB5)
#pragma config HFOFST = ON      // HFINTOSC Fast Start-up (HFINTOSC output and ready status are not delayed by the oscillator stable status)
#pragma config T3CMX = PORTC0   // Timer3 Clock input mux bit (T3CKI is on RC0)
#pragma config P2BMX = PORTB5   // ECCP2 B output mux bit (P2B is on RB5)
#pragma config MCLRE = EXTMCLR  // MCLR Pin Enable bit (MCLR pin enabled, RE3 input pin disabled)

// CONFIG4L
#pragma config STVREN = ON      // Stack Full/Underflow Reset Enable bit (Stack full/underflow will cause Reset)
#pragma config LVP = OFF        // Single-Supply ICSP Enable bit (Single-Supply ICSP disabled)
#pragma config XINST = OFF      // Extended Instruction Set Enable bit (Instruction set extension and Indexed Addressing mode disabled (Legacy mode))

// CONFIG5L
#pragma config CP0 = OFF        // Code Protection Block 0 (Block 0 (000800-003FFFh) not code-protected)
#pragma config CP1 = OFF        // Code Protection Block 1 (Block 1 (004000-007FFFh) not code-protected)
#pragma config CP2 = OFF        // Code Protection Block 2 (Block 2 (008000-00BFFFh) not code-protected)
#pragma config CP3 = OFF        // Code Protection Block 3 (Block 3 (00C000-00FFFFh) not code-protected)

// CONFIG5H
#pragma config CPB = OFF        // Boot Block Code Protection bit (Boot block (000000-0007FFh) not code-protected)
#pragma config CPD = OFF        // Data EEPROM Code Protection bit (Data EEPROM not code-protected)

// CONFIG6L
#pragma config WRT0 = OFF       // Write Protection Block 0 (Block 0 (000800-003FFFh) not write-protected)
#pragma config WRT1 = OFF       // Write Protection Block 1 (Block 1 (004000-007FFFh) not write-protected)
#pragma config WRT2 = OFF       // Write Protection Block 2 (Block 2 (008000-00BFFFh) not write-protected)
#pragma config WRT3 = OFF       // Write Protection Block 3 (Block 3 (00C000-00FFFFh) not write-protected)

// CONFIG6H
#pragma config WRTC = OFF       // Configuration Register Write Protection bit (Configuration registers (300000-3000FFh) not write-protected)
#pragma config WRTB = OFF       // Boot Block Write Protection bit (Boot Block (000000-0007FFh) not write-protected)
#pragma config WRTD = OFF       // Data EEPROM Write Protection bit (Data EEPROM not write-protected)

// CONFIG7L
#pragma config EBTR0 = OFF      // Table Read Protection Block 0 (Block 0 (000800-003FFFh) not protected from table reads executed in other blocks)
#pragma config EBTR1 = OFF      // Table Read Protection Block 1 (Block 1 (004000-007FFFh) not protected from table reads executed in other blocks)
#pragma config EBTR2 = OFF      // Table Read Protection Block 2 (Block 2 (008000-00BFFFh) not protected from table reads executed in other blocks)
#pragma config EBTR3 = OFF      // Table Read Protection Block 3 (Block 3 (00C000-00FFFFh) not protected from table reads executed in other blocks)

// CONFIG7H
#pragma config EBTRB = OFF      // Boot Block Table Read Protection bit (Boot Block (000000-0007FFh) not protected from table reads executed in other blocks)

#define _XTAL_FREQ 16000000

/*
 * Defines a list of values that are derived from a sine wave that
 * can be used to smoothly pulse the PWM duty cycle and the LEDs.
 */
const char brightness_values[] = {
    128, 136, 144, 152, 160, 168, 175, 182, 190, 197,
    203, 210, 216, 221, 227, 232, 236, 240, 244, 247,
    250, 252, 254, 255, 255, 255, 255, 255, 254, 252,
    250, 247, 244, 240, 236, 232, 227, 221, 216, 210,
    203, 197, 190, 182, 175, 168, 160, 152, 144, 136,
    128, 120, 112, 104,  96,  88,  81,  74,  66,  59,
     53,  46,  40,  35,  29,  24,  20,  16,  12,   9,
      6,   4,   2,   1,   0,   0,   0,   1,   2,   4,
      6,   9,  12,  16,  20,  24,  29,  35,  40,  46,
     53,  59,  66,  74,  81,  88,  96, 104, 112, 120
};

#define BRIGHTNESS_VALUES_COUNT 100

/*
 * Implements a delay of a number of milliseconds.
 */
void delay(int ms)
{
    while(ms-- > 0)
    {
        __delay_ms(1);
    }
}

/*
 * Initialises the variable resistor ADC configuration.
 */
void initVR()
{
    // 1. Configure Port:
    //  - Disable pin output driver(See TRIS register)
    TRISAbits.RA0 = 1;

    //  - Configure pin as analog
    ANSELAbits.ANSA0 = 1;

    // 2. Configure the ADC module:
    //  - Select ADC conversion clock
    ADCON2bits.ADCS = 0b101; // Fosc/16

    //  - Configure voltage reference
    ADCON1bits.PVCFG = 0; // +ref is VDD
    ADCON1bits.NVCFG = 0; // -ref is VSS

    //  - Select ADC input channel
    ADCON0bits.CHS = 0; // connect AN0 to the ADC

    //  - Select result format
    ADCON2bits.ADFM = 0; // left justified

    //  - Select acquisition delay
    ADCON2bits.ACQT = 0b101; // 12 TAD

    //  - Turn on ADC module
    ADCON0bits.ADON = 1;
}

/*
 * Reads the value of the variable resistor into a single byte.
 */
char readVR()
{
    // start a conversion
    ADCON0bits.GO = 1;

    // wait for it to complete
    while(ADCON0bits.GO == 1)
    {
        delay(1);
    }

    // return the top 8 bits of the value
    return ADRESH;
}

/*
 * Initialises PWM on the three PWM generators.
 */
void initPWM()
{
    // 1. Disable the CCPx pin output driver by setting the associated TRIS bit.
    TRISC = 0b00000110; // RC1 and RC2
    TRISBbits.RB5 = 1;

    // 2. Select the 8-bit TimerX resource, (Timer2, Timer4 or Timer6) to be
    //    used for PWM generation by setting the CxTSEL bits in the
    //    CCPTMRSx register.
    CCPTMRS0bits.C1TSEL = 0; // user timer 2

    // 3. Load the PRx register for the selected TimerX with the PWM period value.
    PR2 = 0x40; // decimal 64 period

    // 4. Configure the CCP module for the PWM mode by loading the CCPxCON
    //    register with the appropriate values.
    CCP1CONbits.CCP1M = 0b1100;
    CCP2CONbits.CCP2M = 0b1100;
    CCP3CONbits.CCP3M = 0b1100;

    // 5. Load the CCPRxL register and the DCxB bits of the CCPxCON register,
    //    with the PWM duty cycle value.
    CCPR1L = 128 >> 2;
    CCPR2L = 128 >> 2;
    CCPR3L = 128 >> 2;

    CCP1CONbits.DC1B = 128 & 0b00000011; // 128 is 50% duty cycle
    CCP2CONbits.DC2B = 128 & 0b00000011; // 128 is 50% duty cycle
    CCP3CONbits.DC3B = 128 & 0b00000011; // 128 is 50% duty cycle

    // 6. Configure and start the 8-bit TimerX resource:
    //    - Clear the TMRxIF interrupt flag bit of the
    //      PIR2 or PIR4 register. See Note 1 below.
    PIR1bits.TMR2IF = 0;

    //    - Configure the TxCKPS bits of the TxCON
    //      register with the Timer prescale value.
    T2CONbits.T2CKPS = 0b11; // prescale 8

    //    - Enable the Timer by setting the TMRxON
    //      bit of the TxCON register.
    T2CONbits.TMR2ON = 1;

    // 7. Enable PWM output pin:
    //    - Wait until the Timer overflows and the TMRxIF bit of the PIR2 or PIR4
    //      register is set. See Note 1 below.
    while(PIR1bits.TMR2IF == 0){}

    //    - Enable the CCPx pin output driver by clearing the associated TRIS bit.
    TRISC = 0;
    TRISBbits.RB5 = 0;
}

/*
 * Sets the PWM values for each of the three PWM controllers.
 */
void setPWM(char value1, char value2, char value3)
{
    CCPR1L = value1 >> 2;
    CCP1CONbits.DC1B = value1 & 0b00000011;

    CCPR2L = value2 >> 2;
    CCP2CONbits.DC2B = value2 & 0b00000011;

    CCPR3L = value3 >> 2;
    CCP3CONbits.DC3B = value3 & 0b00000011;
}

/*
 * The main function.
 */
void main()
{
    // set up the internal oscillator
    OSCCONbits.IRCF = 0b111;  // 16MHz

    // init the PWM
    initPWM();

    // init the VR
    initVR();

    // brightness counter
    char val1 = 0;
    char val2 = 0;
    char val3 = 0;

    while(1)
    {
        // create a set of values at 45- to each other in the table
        val1 = (val1 + 1) % 100;
        val2 = (val1 + 33) % 100;
        val3 = (val1 + 66) % 100;

        // set the PWM values
        setPWM(brightness_values[val1],
               brightness_values[val2],
               brightness_values[val3]);

        // create a delay proportional to the VR value
        delay((readVR() / 10) + 2);
    }
}

So there it is, sinusoidally pulsed, PWM implemented, variable speed LED chasing. At some point I might look at driving a string of LEDs this way. Perhaps it could be a project for later in the year. In the meantime though I’m going to continue to explore some of the other features of the PIC18F26K22 and see what fun can be had.

Once again, please feel free to contact me with any comments or suggestions. I’m always happy to hear from you!

Getting Started with PICs

Getting Started with PICs

At the end of the last post I had managed to cobble something together that replicates the demo code that the PIC on the PICkit 2 demo board comes with complete with chasing LEDs, direction changing and speed control. Having done that it occurred to me that one thing I’d really like to try out is to see if I could get the LEDs to dim using the PIC’s onboard PWM control capabilities. I had a quick check of the data sheet and luckily enough, three of the four PWM outputs happen to be multiplexed with port D GPIO which means the demo board LEDs can be driven from them. Coincidence? Dunno but it made me happy anyway!

Okay so let’s back up a here and get a little perspective on this whole thing by tackling two fairly obvious questions that might pop up if you’re fairly new to this stuff. Firstly, what’s PWM and secondly why do you need it to dim LEDs?

Starting with the second question first, LED brightness can be controlled using variable voltages much like a normal tungsten bulb but in digital circuits signals are either on or off so in order to dim a LED in a digital circuit we need to find another way. This is where PWM (Pulse Width Modulation) come a long and gives us a big wet, sloppy kiss on the lips.

The way this works is basically to say that if you rapidly switch something on and off, say for example an electric motor, it’s a bit like running it at a lower voltage and how fast the motor will go depends on the amount of time it is switched on when compared to the amount of time it’s switched off. The terminology can be seen in the diagram below which comes from the PIC data sheet.

PWM Terminology

PWM Terminology

The total length of time for an on / off cycle is called the ‘period’ and this can also be expressed as a frequency which is 1/period. The amount of time that the circuit is switched on for during the period is called the ‘duty cycle’ and this is where the work gets done, motors get turned and LEDs light up. The stuff on there about timer 2 should become clearer later on.

So how is all this implemented in a PIC16F887 then? Well the PWM functionality comes as part of the ‘Capture Compare PWM’ (CCP) modules of which there are two in this particular PIC. These are cunningly named CCP1 and CCP2 and the one I chose to use was CCP1 because it’s the one that connects to the LEDs.

Configuring the PWM peripheral consists of setting the period, configuring how the duty cycle will be implemented (e.g. active high, active low and on which pins), setting the duty cycle and configuring the timer that’ll keep the whole thing going.

Easy peasy right? Err sort of!

Okay so the next thing to do was to fire up MPLAB and create a new project. After creating the project I grabbed the code from the previous PIC post as a starting point and deleted the button code. The ADC code I kept because I wanted to use it to control the LED dimming. I also deleted out the code that did the chasing so I ended up with an empty while loop. While I was in there I refactored the button initialisation code into a new function to get it out of the way. Basically all that was left in my main function was the oscillator configuration, a call to initialise the variable resistor ADC and an empty while loop.

Once I did that I used a little trick that I use a lot when I have a long and involved procedure or algorithm I need to work through and that is to add in all the comments first and add the code afterwards. The reason I sometimes do this is twofold. Firstly it means I can sketch out everything that needs to happen in comments before I get bogged down in how to actually get it to happen in that language on that platform which means I don’t forget anything. Secondly it allows me to tackle the code in any order I want to so i can start out by filling in the easy stuff and work through the complex stuff later on.

The data sheet helps us out a lot if we take that approach because it documents the exact initialisation procedure in chapter 11.5.7 Setup for PWM Operation. Hurrah! I copied the text out of the data sheet, pasted it into the code editor and converted each step into a comment. Now I just needed to fill in the blanks.

The first step is to temporarily set the pins we’re outputting to as inputs. The PWM output can go to four pins called P1A, P1B, P1C and P1D which are multiplexed with RC2, RD5, RD6 and RD7 respectively. Incidentally, the reason I elected to use CCP1 is because I needed to get output to the pins with LEDs attached and that can be done by using a trick called ‘Pulse Steering Mode’. Anyhow, as I had pins on both port B and port C and I’m not using them for anything else, I simply set them all to input.

    // Disable the PWM pin (CCPx) output drivers as an input by setting
    // the associated TRIS bit.
    TRISC = 0xff;  // blat the lot, we're not using them
    TRISD = 0xff;  // for anything else

The next job is to set the period. This is a bit of an awkward process because the period has dependencies not only on the period register (PR2) but also on the oscillator frequency and any pre-scaling on timer 2 that may or may not be set up. There is a full discussion including equations for calculating everything you need in the data sheet in section 11.5.1, 11.5.2 and 11.5.3. There are also a few examples in table 11-4 which I used as a starting point for subsequent tweakery.

    // Set the PWM period by loading the PR2 register.
    PR2 = 0x40; // this combined with a TMR2 prescale of 16 gives us
                // a PWM frequency of just under 2kHz

The value 0x40 is decimal 64 which is a quarter of 255, the maximum value for a single byte such as the one returned by my variable resistor reading function. If you check out equation 11-3 you’ll see that to get a fully on signal you need to supply a value for the duty cycle that is four times the value in the PR2 register. I ran through the calculations to see what my period frequency would end up as and it came out at just under 2KHz which seemed pretty good to me.

The next thing to do is to configure the CCP module so that it knows what kind of output you’re after. As I wanted to use pulse steering mode to direct the same output to RD5-7 I needed to use single output mode and that’s configured with the P1M bits in the CCP1CON register. I also set the extended CCP mode bits to tell that I wanted all output to be active high and finally set the pulse steering mode register PSTRCON to send output to the three LEDs.

    // Configure the CCP module for the PWM mode by loading the CCPxCON
    // register with the appropriate values.
    CCP1CONbits.P1M = 0b00;     // Single output mode
    CCP1CONbits.CCP1M = 0b1100; // ECCP Mode PWM P1A-D active high
    PSTRCON = 0b00011110;       // set up pulse steering mode on port D pins

Next I loaded up the duty cycle which is a 10bit value so it’s stored across a couple of registers. The least significant bits are held in the DC1B bits of the CCP1CON register if you’re using CCP1 and the most significant bits are written to CCPR1L.

    // Set the PWM duty cycle by loading the CCPRxL register and DCxB
    // bits of the CCPxCON register.
    CCP1CONbits.DC1B = 0x80 & 0b11;
    CCPR1L = 0x80 >> 2; // 0x80 is approx 50% duty cycle... I think!

In the code above I took an initial duty cycle value 0x80, masked off the two LSBs and wrote them to DC1B. I then shifted the same initial duty cycle value, shifted right by two places (they’re written to the other register) and wrote the result to the CCPR1L register.

The next job is to configure timer 2 which is going to drive the whole thing. The timer speed depends on the oscillator frequency and you can vary it by using pre-scalers and post-scalers. As mentioned above I used a pre-scaler value of 16 which is configured by setting the T2CKPS bits in the T2CON register to 0b10.

    // Configure and start Timer2:
    //  • Clear the TMR2IF interrupt flag bit of the PIR1 register.
    PIR1bits.TMR2IF = 0;

    //  • Set the Timer2 prescale value by loading the T2CKPS bits of the
    //    T2CON register.
    T2CONbits.T2CKPS = 0b10; // 16

    //  • Enable Timer2 by setting the TMR2ON bit of the T2CON register.
    T2CONbits.TMR2ON = 1;

Once all that is done the timer is switched on but we need to wait until a new PWM cycle has started before carrying on.

    // Enable PWM output after a new PWM cycle has started:
    //  • Wait until Timer2 overflows(TMR2IF bit of the PIR1 register is set).
    while(PIR1bits.TMR2IF == 0){}

Finally we let the output flow out of the pins.

    //  • Enable the CCPxp in output driver by clearing the associated
    //    TRIS bit.
    TRISD = 0;

One last thing I did was to set the LED adjacent to the dimmed ones to on so that I could compare brightness levels and get some idea of how well it was working.

    // switch on one of the adjacent LEDs for brightness comparison
    PORTD = 0b00010000;

Then of course you have your empty, infinite while loop.

If you run the code at this point the LEDs on RD5-7 display but are clearly dimmer than the one that is fully on which is great, it means the thing actually works. The next step though is to get that variable resistor into the mix so we can use that to set the amount of dimming.

Well we already have the code to get a byte value from the variable resistor and we already have the code to set the duty cycle. All we have to do is combine it together.

    char duty = 0;  // the duty cycle value derived from the VR value

    // loop forever
    while(1)
    {
        // read an 8-bit value from the VR
        duty = readVR();

        // set the new duty cycle
        CCP1CONbits.DC1B = duty & 0b11; // LSB
        CCPR1L = duty >> 2;             // MSB
    }

I then built the code, connected my PICKit 2 and demo board and gave it a spin. Sure enough it all seemed to work just fine. Turning the variable resistor allows a full range of dimming control from completely off to completely on.

#include <pic.h>
#include <pic16f887.h>

// PIC16F887 Configuration Bit Settings

// CONFIG1
#pragma config FOSC = INTRC_NOCLKOUT// Oscillator Selection bits (INTOSCIO oscillator: I/O function on RA6/OSC2/CLKOUT pin, I/O function on RA7/OSC1/CLKIN)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled and can be enabled by SWDTEN bit of the WDTCON register)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config MCLRE = ON       // RE3/MCLR pin function select bit (RE3/MCLR pin function is MCLR)
#pragma config CP = OFF         // Code Protection bit (Program memory code protection is disabled)
#pragma config CPD = OFF        // Data Code Protection bit (Data memory code protection is disabled)
#pragma config BOREN = ON       // Brown Out Reset Selection bits (BOR enabled)
#pragma config IESO = ON        // Internal External Switchover bit (Internal/External Switchover mode is enabled)
#pragma config FCMEN = ON       // Fail-Safe Clock Monitor Enabled bit (Fail-Safe Clock Monitor is enabled)
#pragma config LVP = OFF        // Low Voltage Programming Enable bit (RB3 pin has digital I/O, HV on MCLR must be used for programming)

// CONFIG2
#pragma config BOR4V = BOR40V   // Brown-out Reset Selection bit (Brown-out Reset set to 4.0V)
#pragma config WRT = OFF        // Flash Program Memory Self Write Enable bits (Write protection off)

#define _XTAL_FREQ 8000000

void initVR()
{
    // configure RA0 as analog AN0
    ADCON1bits.ADFM = 0; // left justified result
    ADCON1bits.VCFG0 = 0; // use VDD as reference voltage
    ADCON1bits.VCFG1 = 0; // use VCC as reference gnd

    TRISA = 1;
    ANSELbits.ANS0 = 1;

    ADCON0bits.ADCS = 0b10; // clock / 32
    ADCON0bits.CHS = 0;     // AN0 selected
    ADCON0bits.ADON = 1;    // turn on the ad converter
}

char readVR()
{
    ADCON0bits.GO = 1;
    while(ADCON0bits.GO == 1){}
    return ADRESH;
}

void main()
{
    // set up the oscillator to run at 8MHz
    OSCCONbits.IRFC = 0b111;

    // init the ADC to read the VR
    initVR();

    // Disable the PWM pin (CCPx) output drivers as an input by setting
    // the associated TRIS bit.
    TRISC = 0xff;  // blat the lot, we're not using them
    TRISD = 0xff;  // for anything else

    // Set the PWM period by loading the PR2 register.
    PR2 = 0x40; // this combined with a TMR2 prescale of 16 gives us
                // a PWM frequency of just under 2kHz

    // Configure the CCP module for the PWM mode by loading the CCPxCON
    // register with the appropriate values.
    CCP1CONbits.P1M = 0b00;     // Single output mode
    CCP1CONbits.CCP1M = 0b1100; // ECCP Mode PWM P1A-D active high
    PSTRCON = 0b00011110;       // set up pulse steering mode on port D pins

    // Set the PWM duty cycle by loading the CCPRxL register and DCxB
    // bits of the CCPxCON register.
    CCP1CONbits.DC1B = 0x80 & 0b11;
    CCPR1L = 0x80 >> 2; // 0x80 is approx 50% duty cycle... I think!

    // Configure and start Timer2:
    //  • Clear the TMR2IF interrupt flag bit of the PIR1 register.
    PIR1bits.TMR2IF = 0;

    //  • Set the Timer2 prescale value by loading the T2CKPS bits of the
    //    T2CON register.
    T2CONbits.T2CKPS = 0b10; // 16

    //  • Enable Timer2 by setting the TMR2ON bit of the T2CON register.
    T2CONbits.TMR2ON = 1;

    // Enable PWM output after a new PWM cycle has started:
    //  • Wait until Timer2 overflows(TMR2IF bit of the PIR1 register is set).
    while(PIR1bits.TMR2IF == 0){}

    //  • Enable the CCPxp in output driver by clearing the associated
    //    TRIS bit.
    TRISD = 0;

    // switch on one of the adjacent LEDs for brightness comparison
    PORTD = 0b00010000;

    char duty = 0;  // the duty cycle value derived from the VR value

    // loop forever
    while(1)
    {
        // read an 8-bit value from the VR
        duty = readVR();

        // set the new duty cycle
        CCP1CONbits.DC1B = duty & 0b11; // LSB
        CCPR1L = duty >> 2;             // MSB
    }
}

The full, final code is above if you fancy picking through it (no pun intended). I hope there’s something vaguely useful in there. As always questions, comments and suggestions are more than welcome otherwise feel free just to say hi.

Getting Started with PICs

Getting Started with PICs

In my last PIC post I wrote about how to get the LEDs on the PICKit 2 demo board to chase by configuring the clock and using the delay macro to update the GPIO port. I also added some code to read the button and reverse the direction of the chasing mimicking the behaviour of the program on the PIC that is provided with the board. The other feature that program has though is for the speed of the chasing LEDs to be varied by turning the variable resistor.

PIC Demo Board Input

PIC Demo Board Input

This is a classic job for an Analog to Digital Converter (ADC) and these work by sampling a voltage and converting it to a number that can be used to represent some kind of quantity in the code. Before going ahead and getting the code going though, we need to go back to the demo board data sheet to find out where and how the variable resistor is connected to the PIC.

The pin we’re interested in is RA0 which is multiplexed with AN0, our analog input. Input from the variable resistor will range from +5V (VDD) to 0V (VCC) as I’m running the board at 5V. Next it’s off to the PIC16F887 data sheet to figure out how to get this thing done.

The first thing to do is to perform some configuration. We need to tell the PIC what the input range is going to be and how we’d like the 10 bit result laid out in the two 8 bit registers it’ll be written to.

    // configure RA0 as analog AN0
    ADCON1bits.ADFM = 0; // left justified result
    ADCON1bits.VCFG0 = 0; // use VDD as reference voltage
    ADCON1bits.VCFG1 = 0; // use VCC as reference gnd

I chose to left justify the result because I wanted to turn the value into a single byte that spanned the full range of the variable resistor. Using the highest 8 bits of the result gives me that and I can simply ignore the two least significant bits. The other two values tell the ADC that I’ll be using an input voltage range between VDD and VSS (GND). For details on the other options you’ll need to refer to the data sheet. This is probably a good point to remind anyone out there that while clearly I have no issue whatsoever with anyone pinching this code (or it wouldn’t be here!) this is code for the PIC16F887 and things can be different on other PICs. Learn to love those data sheets!

Next I set up the RA0 / AN0 pin so that it acts as an analogy input.

    TRISA = 1;
    ANSELbits.ANS0 = 1;

This is done by configuring the TRISA register so that bit 0, which maps to the pin, is set to input and then make sure it’s selected in the ANSEL register. It should be set anyway after a power on but this way we can be sure…

In the final part of the configuration I had to do some fiddling with the clock because I’m running at 8MHz and the ADC can’t go that fast. Dividing the clock by 32 puts us inside the recommended range of values.

    ADCON0bits.ADCS = 0b10; // clock / 32
    ADCON0bits.CHS = 0;     // AN0 selected
    ADCON0bits.ADON = 1;    // turn on the ad converter

Next I connect the AN0 channel to the ADC. I did this as part of the configuration stage because I’m only using one channel so there’s no need to keep changing it. Finally the ADC is switched on.

So that’s the setup work done, the next thing is to take the readings from the ADC. I created a separate function to do this so I can re-use it more easily.

char readVR()
{
    ADCON0bits.GO = 1;
    while(ADCON0bits.GO == 1){}
    return ADRESH;
}

To start a conversion all you have to do is to set the ‘GO’ bit in the ADCON register and wait until it gets cleared after the conversion is complete. The result is then stored as a 10 bit number across the ADRESH and ADRESL registers. All I need are the top 8 bits so I grab them from ADRESH and return the byte. That byte is then used in a delay in the chase code to provide a delay of between 0 and 255 milliseconds which is good enough for me.

The final code is listed below:

#include <pic.h>
#include <pic16f887.h>

// PIC16F887 Configuration Bit Settings

// CONFIG1
#pragma config FOSC = INTRC_NOCLKOUT// Oscillator Selection bits (INTOSCIO oscillator: I/O function on RA6/OSC2/CLKOUT pin, I/O function on RA7/OSC1/CLKIN)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled and can be enabled by SWDTEN bit of the WDTCON register)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config MCLRE = ON       // RE3/MCLR pin function select bit (RE3/MCLR pin function is MCLR)
#pragma config CP = OFF         // Code Protection bit (Program memory code protection is disabled)
#pragma config CPD = OFF        // Data Code Protection bit (Data memory code protection is disabled)
#pragma config BOREN = ON       // Brown Out Reset Selection bits (BOR enabled)
#pragma config IESO = ON        // Internal External Switchover bit (Internal/External Switchover mode is enabled)
#pragma config FCMEN = ON       // Fail-Safe Clock Monitor Enabled bit (Fail-Safe Clock Monitor is enabled)
#pragma config LVP = OFF        // Low Voltage Programming Enable bit (RB3 pin has digital I/O, HV on MCLR must be used for programming)

// CONFIG2
#pragma config BOR4V = BOR40V   // Brown-out Reset Selection bit (Brown-out Reset set to 4.0V)
#pragma config WRT = OFF        // Flash Program Memory Self Write Enable bits (Write protection off)

#define _XTAL_FREQ 8000000

int buttonPressed()
{
    // flag to tell us when the button is
    // being pressed
    static int buttonDown = 0;

    // is the button being pressed?
    if (PORTBbits.RB0 == 0)
    {
        // yes, set the flag
        buttonDown = 1;
    }

    // has the button been released?
    if (PORTBbits.RB0 == 1 && buttonDown == 1)
    {
        // yes, clear the flag
        buttonDown = 0;

        // tell the calling method
        return 1;
    }

    return 0;
}

char readVR()
{
    ADCON0bits.GO = 1;
    while(ADCON0bits.GO == 1){}
    return ADRESH;
}

void main()
{
    // set up the oscillator to run at 8MHz
    OSCCONbits.IRFC = 0b111;

    // configure port d as output
    TRISD = 0;

    // Configure bit 0 (AN12) on PORTB as a digital input
    PORTB = 1;
    ANSELHbits.ANS12 = 0;
    TRISB = 0b00000001;

    // configure RA0 as analog AN0
    ADCON1bits.ADFM = 0; // left justified result
    ADCON1bits.VCFG0 = 0; // use VDD as reference voltage
    ADCON1bits.VCFG1 = 0; // use VCC as reference gnd

    TRISA = 1;
    ANSELbits.ANS0 = 1;

    ADCON0bits.ADCS = 0b10; // clock / 32
    ADCON0bits.CHS = 0;     // AN0 selected
    ADCON0bits.ADON = 1;    // turn on the ad converter

    int d = 1;  // sets the direction of the chasing
    int i = 0;  // the index of the led that will be lit

    // loop forever
    while(1)
    {
        // if the button is pushed...
        if (buttonPressed() == 1)
        {
            // ...reverse the direction
            d = -d;
        }

        // select the next LED wrapping around
        // if required
        i = (i + d) % 8;

        // light up the correct LED
        PORTD = 1 << i;

        // read a delay value from the variable resistor
        // analog channel
        int del = readVR();

        // do the delay
        for (int l=0; l<del;l++)
        {
            __delay_ms(1);
        }
    }
}

So that’s that then, we have a version of the demo program that chases the LEDs, allows you to swap the direction of the chasing and also change the speed. I’m sure we can get more out of this board than that though but that can be a subject for another post.

As always I’d be really interested to hear your feedback. Feel free to post comments, suggestions, errors etc, they are gratefully received.

Nothing kills innovation quite like the words, “That’s all very well but let’s be realistic here!”
Nothing kills a project quite like unrealistic expectations and requirement creep.

So does this represent an impossible dichotomy that acts to prevent us from building the innovative new applications that all engineering businesses need to stay on top of their game? Well, a lot of the time yes it does. It can seem as though the choices are either to take a risk, bite off more than you can chew and deal with escalating costs, requirement creep and late delivery in order to get ahead of the market or, like a tortoise peeking out and sensing impending danger, tuck your head back under the shell of your technical comfort zone and hope you can hold out for a little longer in a rapidly changing ecosystem.

Of the two options I can understand why the second may seem more attractive but the reality is that the longer that carries on, the further and further behind the rest of the world you get and the more risk you’ll need to take in order to re-establish yourself later on. But hey, no-one wants that kind of risk on their watch right? Leave it to the next person.

I’ve recently adopted a different view though. I think you can have both and that key to the whole thing is the ability to clearly differentiate between ideas and commitments when talking to customers about their needs.

Over the last few weeks I have been involved in a series of customer workshops in a sort of combined role of business analyst and system architect. It’s clear from the brief we’ve been given that the customer is very keen to see innovation in any solution they will end up buying but at the same time, delivery timescales are very short. At one point during the discussion I was talking to some of the users about some of the cutting edge things the solution we were proposing could deliver when I heard the fateful words, “That’s all very well but let’s be realistic here!”

Talk about throwing a bucket of cold water over proceedings!

The comment actually really irritated me. The reason for this was that we had clearly been talking about possibilities, exploring ideas to see if there was any mileage in them. I was trying to figure out what excited them, what was important to them and how new features might impact on the way they currently work. In that moment I realised that while most of the room knew and understood what was going on, it was clear that many didn’t. Worse than that, the ones that didn’t seemed to believe I was committing to features in the end solution. A situation ripe for confusion later on!

Later on as I sat back down after the discussion I started to mull over what had happened in that moment. Why had some of them not realised that we had been in ‘exploration’ mode? After all, the whole point of the workshop was to poke at the limits of the requirements and find out where they were. One thing was for sure, no-one was going to dare to suggest new ideas now. Suddenly the project had nothing exciting in it anymore.

It occurred to me that while I had been speaking, my mind had been jumping between several different modes and I couldn’t even remember at what points I had switched from one to another. No surprise then that I hadn’t brought everyone in the room along with me. If they had snoozed even for a moment (the view from the top floor meeting room was quite impressive too) or they didn’t understand some of the deeper techie stuff we’d been discussing, they could quite easily have been left behind.

As a long time geeky kind of a chap, I began to think about these different modes as a set of states in a finite state machine. I thought about how the conversation had traversed these various states and what those states might be. I came up with three and popped them into a note on my iPhone.

Stages of Requirement Capture

Stages of Requirement Capture

What’s possible?
What’s important?
What’s feasible?

When thinking about what’s possible you’re thinking innovatively.

“Technology is now at this point here and our systems currently look like this but imagine if…”

This is the ‘opening up’ part of the discussion whereas the others are the ‘nailing down’ parts. When thinking about what’s important we take the ideas and concepts discussed in the ‘what’s possible’ phase and start to focus in on the ones that make the most difference to the customer. Having figured out what’s important we then need to look at our constraints and build a project around what’s feasible. This is where we finally nail down the project scope.

It seems to me that if you want to create something great then a discussion like this must include all of these things. By dwelling on what’s feasible you close yourself off to innovation or what the users might really need. By dwelling on what’s possible you can lose track of what’s important and by dwelling on what’s important you can become too bogged down in the status quo and shut yourself off from new ideas that could offer real benefits to the customer.

The trick then has to be in finding a way to let other participants know where the discussion is at any particular moment in time. The next challenge for me is to find a way in which to do that. Perhaps some kind of discussion board divided into three areas could be used and ideas written on sticky notes placed on it. Perhaps a simple diagram showing the three states and a sticky movable arrow that can be pointed at the one describing where the conversation currently is. Something to explore I think. Please feel free to let me know if you have any ideas!

So how does this help with keeping your technology business alive and on the cutting edge? Well I believe that this creates a framework that allows us to engage in new thinking without the fear that it will get out of control because everybody involved knows whether the discussion is about something exploratory or not and that it doesn’t matter if the ideas may seem a little trivial at the time because the subsequent phases of the discussion will filter these into what is important and finally what is feasible. Good innovation will make it through all of these filters and what you hopefully end up with something that combines the best of the established with the best of the new.