final project -- weekly assignments -- about me -- fab academy

Week 07: Microcontroller Fun

We had a lot of time for this assignment, so the plan was to do something crazy. Then, I had to do a course on work safety for working on electric cars. There was a conference to attend, too. Then, I was on a much needed short break myself. So, for now, this is just the bare minimum, more or less. I do intend to come back and do the crazy stuff, though.

The Evil Rival - Working With PICs

I'm working with my Fabboy-board here, which is built around a PIC18F26K22.

Each and every manufacturer has his own quirks about programming their microcontrollers. Also, each manufacturer has his own quirks about writing datasheets. Microchip is no exception, so the first step of programming (or doing anything with) a PIC is to have a look at the documentation on it. The datasheet is a good start (it even provides rough descriptions of using peripherals), and having a look at the errata can't hurt, either.

The PIC I used here, a PIC18F26K22 is a small 8 bit microcontroller. It is the current generation of microchip's 8 bit line, and a bit more optimised for working in "higher level languages" (meaning: c instead of assembler) compared to the older 8 bit PICs (which had, among other traps, banked memory and could only work in one bank at once - which is really no fun when you need more than one bank and have to start switching). Apart from having "more than enough" RAM for my needs (almost 4kB), it has a load of peripherals, a lot of which I haven't used yet. The ones I have used are: A set of seven timers with different capabilities (mostly for historical reasons, as Timer0 is still source code compatible to the very first PICs, Timer1 is still source code compatible to the first generation that had one, ...), a module that can do SPI and I2C, a 10 bit ADC with 17 inputs (it can only sample one at a time, though), three PWM modules (they need a timer, but are their own module instead of a function of a timer like with the ATMegas or MSP430s) and two UARTs. The second UART is actually a new feature of this particular generation, and for some asinine reason shares its pins with the debug interface...

Among the features I haven't used yet are a DAC, an analog comparator (with its output readable from software or as an interrupt source), a hardware module for building touch input (I have to try that...) and some data EEPROM for storing stuff like individual configuration or adjustment values. The programme flash is writable from software, too, so a bootloader system as the one used by arduino is possible. I haven't tried that yet, though, as it's a complicated process that I haven't needed yet (flash is incredibly slow to write and can only be erased in blocks...).

There are quite a lot of options for clocking the PIC. It can use an oscillator with the classic external crystal (or ceramic oscillator), or get a clock from outside altogether. If you don't want the external parts it also features a rather precise internal oscillator - It is factory trimmed to 1%, which is enough for most applications, so I just used it. The PICs are easy to assess in that each instruction, no matter which one, takes four clock cycles - The only Exception being conditional branches, which can take eight. So, at a clock of 16 MHz, the "instruction cycle" would be at 4MHz, which is why this generation offers a 4x PLL. The rather impressive sounding 64MHz clock from that translates to a 16MHz instruction cycle, which is a rather normal value for such a small microcontroller.

A look at the errata sheet shows little of notice for this board. For now, I'm really only using a timer. Timers 1, 3 and 5 are shown to have a problem in gated mode, but I'm not using that, so it shouldn't cause a problem. A second problem with those timers occurs during a mode that counts external pulses, which I'm not using either. Also, I'm using Timer0, so both don't concern me, either. There seems to be some problem with the UART, though, which I'll have to look into when I'm working on doing more with the board.

The work with datasheets and erratasheets doesn't end this week, of course. Doing any of the works done during Input Devices, Output Devices and Networking and Communications wouldn't have been remotely possible without constantly working along the datasheets, especially with a peripheral as complex as the CAN module. I printed out most of those datasheets, as I like to have the quick access a post-it in the printout provides, and I usually fill them with notes on what to do in which situation. The same applies to the rest of my Final Project. The only way to do that much hardware access without working a datasheet or three is to use existing libraries (like the huge library arduino has) and hoping for it to work, which I did not do in this case.

Toolchains

Basically, there are four toolchains you can try to use. There is, rather obviously, the current Microchip toolchain: Their XC8 compiler and the underlying MPASM assembler. Then, there is their old (and more than deprecated) C18 compiler, which is only vaguely interesting for the tons of example code out there on the net. A third option is the set of toolchains made by Mikroelektronika, which I haven't tried yet. Then, for the open source lovers, there is the SDCC project, which seems more and more interesting. As I haven't had time for experiments I'm using XC8 here and now. Giving SDCC a shot should be interesting, though. Time...

To get what I made into the PIC I'm using a PICkit 3. There are some DIY alternatives, some of which might end up a bit cheaper, but this way I do have one feature I don't want to miss: Debugging of a running microcontroller, which can save a lot of time when stuff just doesn't work.

Blinky Blink - A First Test

As a first, "hello world"-like programme the board can blink one of its LEDs (or, in my case, a whole bunch of them). Setting up the OLED-display would be a lot more work and lead to a lot more errors, so a simple blinking LED is a good start. If it works, the microcontroller is running. And if it blinks at the right frequency, the oscillator settings should be right, too.

Basic Setup

Like the fuses of the AVR world, the PICs have configuration words to do basic hardware settings. And while newer features are mostly configured in software, newer PICs still have a whole bunch of those configuration words. They are set via #pragma directives (so they are source code, not "some other information" as with AVR) and there is a (still new) tool in the IDE to generate those. You will still need the datasheet to understand what you're doing:

Excerpt from the datasheet

Using that information, you end up with something like (in my case) this:

#pragma config FOSC = INTIO67 // Oscillator Selection bits (Internal oscillator block)
#pragma config PLLCFG = ON // 4X PLL Enable (Oscillator multiplied by 4)
#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)

Basic oscillator settings as generated by the tool... There is a lot more. Mostly, I don't need it at all - I won't think about stuff like code protection with an open source project, so the defaults will do for most settings.

More Setup - Runtime Stuff

As mentioned above, newer features are configured by software at run time, so they have to be done in "classic" software. For me, the oscillator settings are such a case:

OSCTUNEbits.INTSRC = 1; // select internal HF oscillator
OSCCONbits.IRCF = 7; // select 16 MHz
OSCTUNEbits.PLLEN = 1; // enable PLL

Getting the PIC to actually run at 64 MHz / 16 MIPS from the internal oscillator. The "microchip way" of accessing single bits in a register is different than the one used by Atmel: The headers define a struct of single bit variables, so you can just read and write them without thinking about boolean operations to get at them. While not very effective for setting large amounts of bits at once, I still like it for it's ease of use.

Blinking an LED

There are incredibly many ways to blink an LED. So, while a simple software loop would have done fine for a first sign of life, I did it the "hard way" by setting up a timer. The PIC has different kinds of them, and while one of the compare/match-timers would have been great to get exact timing for my blinking LED, I will need them for PWM at some later point and didn't want to use them now. I used Timer0 instead, the oldest peripheral the newer PICs still have.

To have something I can actually see without an oscilloscope, I had to let the LED blink incredibly slow - from the microcontroller's point of view. I wanted to do roughly 0.5 Hz, have the LED on for one second, then off for one second. Timer0 can count to either 255 (8 Bit) or 65535 (16 Bit), so I can use it to divide the clock input by either (up to) 255 or (up to) 65535. Running at 16 MIPS, there is still a lot of dividing left to do, but we do have a divider to do that - the prescaler. It can be set to powers of 2... Leaving us with a fine match at 256:
16 MHz (instruction clock) / 256 / 65535 = 0.95 Hz.

T0CONbits.TMR0ON = 1; // start timer
T0CONbits.T08BIT = 0; // 16 bit mode
T0CONbits.T0CS = 0; // internal clock = 16 MHz / prescaler
T0CONbits.PSA = 0; // prescaler on
T0CONbits.T0PS = 7; // prescaler set to 256 => clock input is 62.5 kHz

A few more lines are needed to set up the interrupt and the I/O-pins. The interrupt routine stays simple for now, too - It just resets the timer interrupt flag, and it toggles the LED-pin. A bit more code is needed to tell the compiler that a simple function is actually an interrupt routine, and to deal with the sad fact that the 8 Bit PICs only have one or two interrupt vectors:

void interrupt InterruptHandler(void) // not using priorities,
// so any interrupt will end up here:
{
  if(INTCONbits.TMR0IF) // Timer 0 interrupt was triggered
  {
    INTCONbits.TMR0IF = 0; // reset the interrupt flag
    LATC ^= 0x02; // toggle LED pin
  } // end of Timer 0 subroutine
}

I could have added a "preload" value to Timer0 every time the interrupt is triggered to correct its running time to exactly one second. I didn't bother, though - almost any error will lead to massive errors in running time (like the 32 kHz oscillator setting I had at some point...), so the difference just doesn't bother me for now. I will speed stuff up a bit when I continue the gaming idea, as I have plans to use Timer0 as a general system timer then.

the board with the LEDs on

How do you visualize a blinking LED? Videos are lame...

Of course, you can download my project files here.

final project -- weekly assignments -- about me -- fab academy

Creative Commons License
This work by Christoph Nieß is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.