Mr. Baybus 3 Preview

Yet another digital baybus.  The original Mr. Baybus, then Mr. Baybus 2, and now this one with PC control and a graphical LCD!

Never really completed this project. It is archived on GitHub for posterity.

Screen Images

Splash screen

Splash screen

Fans

Fans

Lights

Lights

Temperatures

Temperatures

Application Screenshots

Main Menu

Fan Controls

Lighting Controls

Temperatures

Conditional Jumping in PIC16 Assembly

PIC Microcontrollers have a funky way of handling conditionals. I’d like to present a set of macros I’ve made to make this easier to use, as well as explain the basics behind the technique in general.

Most MCU’s I’ve worked with before PICs had nice simple conditional statements… The mnemonic was usually to the effect of “branch if x to destination”. Not so on the PICs.

On a PIC, we have to do an operation between the data to test, then either skip the next instruction or not, based on the results of this test.

Take the instruction “BTFSS” meaning “Bit-test F, Skip if Set”. This instruction takes a register and a bit number, and will jump over the next instruction if that bit number in the register is set. For example:

We have 2 registers, REG1 and REG2. We want to know if the value in REG1 equals the value in REG2. A quick, simple way to do this is first to load one into W, then subtract the other from it, with the result going in to W as not to destroy one of the variables. This sets the “ZERO” bit in the STATUS register to either 1 if the operation resulted in a 0, or 0 otherwise. We know if the operation resulted in 0 the two values are equal, so we can code like so:

TESTIFZERO
  movf REG1, w
  subwf REG2, w
  btfss STATUS, Z
    goto NOTEQUAL
EQUAL
  ; they were equal
  goto DONETESTING
NOTEQUAL
  ; they weren't equal

So we can test for equality.

We can also macro-ize it for ease of use…

BEQ macro REG1, REG2, DEST	; branch if REG1 == REG2
  movf REG2, W			; W <- REG2
  subwf REG1, W			; W <- REG1 - REG2
  btfsc STATUS, Z		; if result was nonzero: skip out
    goto DEST			; otherwise jump
  endm

This gives us a macro we can call with 2 registers and a destination, and have it jump there if the condition ends up being true, and just pass on through if it’s not true. Much, much easier to use.

The other tests: inequality, less than, greater than, less than or equal to, greater than or equal to and so on follow a similar pattern. They are all covered in the “conditionals.inc” file I use quite often. I have both register-register comparisons and register-literal comparisons in there. Feel free to grab a copy and use it in your next project.

Downloads

Mr. Baybus 2

After such a good response to Mr. Baybus, I decided to up the ante. I wanted temperature sensing and light control, as well as a more refined interface. I also wanted a chance to write much, much better code as Mr. Baybus was most definitely a kludge. This led to the design of Mr. Baybus 2.

Mr. Baybus had a few problems I wanted to take care of. One being price, it cost way too much compared to its utililty value, which is normal for projects like this, but still…

Mr. Baybus 2 uses a much more sophisticated microcontroller, a PIC 16F870. This is a 28-pin SDIP style chip, lots more I/O pins, an onboard UART, and even an ADC. This little guy also has twice the instruction memory, so I had more freedom to make the interface somewhat more sleek, and add more features. The benefit of more pins is CHEAPER LCD. The previous serial LCD ran about $42 shipped, which is semi-high for a serial LCD in general, but it was a CrystalFontz so at least it was high quality. Anyways, now I can move to a simple HD44780 based parallel LCD (by CrystalFontz, of course). These run around $20 shipped, and even less from other places selling generics.

Features

  • Three On/Off Fan controls
  • One 12V Light control (Neons, etc)
  • Two centrigrade temperature sensors
  • 20×2 screen (any HD44780 compatible will work)
  • Simple, menu-driven style interface
  • Stores fan status in non-volatile memory

Display Images

Splash screen

Fan status

Fan status

Fan status w/selection

Fan status w/selection

Lighting status

Lighting status

Temperatures

Temperatures

Unit Images

High view of the front

High view of the front

High view of the back

High view of the back

Back view

Back view

Underneath

Underneath

Version 2.1

A slight update to the original. I decided I wanted to re-write it in C as an exercise. While I was at it, I figured I’d add a feature or two.

Pretty basic, a complete re-write in C, Hi-Tech PICC to be exact. It’s a great compiler for the PICs and gave me opportunities to re-write the LCD, ADC and DELAY libraries in C.

New Features

  • New program-loop idea, worked well
  • Strobe mode for Light output
  • Temperatures in Celcius or Fahrenheit

Downloads

All files for Mr. Baybus 2 are available at GitHub.

PIC buttons (interrupt-based)

Schematic

Schematic

Previously, in PIC buttons (polling) we saw how to poll for the state of a line connect to a button, that is all fine and good but really that is not the best way to do them. The “real” way to interface with external components like that is through interrupts, a slick feature.

Interrupts provide you with lots of freedom in your code. They allow you to sit back, relax, and be told when an event occurs, and not be forced to sit and wait for it to happen.

For this program, the schematic and circuit are practically the same, the only thing that changed location is the button.

Instead of looping over and over again, we simply wait, using a goto $ we are essentially goto’ing the same address over and over, “goto here, goto here, goto here…” ad nauseum. A common technique is also to use a SLEEP command, which puts the PIC in a low power mode and halts the program counter. Same effect to the user though. You of course could do ‘real’ work too instead of just burn cycles.

Once the button is pressed and released, the PIC will generate an interrupt, forcing the program to goto memory location 4. This is labeled in the code as ISR (Interrupt Service Routine).

For this program we are using the RB0/INT Interrupt. This interrupt occurs when there is a low-high change in PORTB,0. It can also be configured for high-low as well.

To enable this, we set INTCON,INTE. This bit says we want to know if a change occurs. To enable interrupts in general, we must then set INTCON,GIE. This lets all enabled interrupts occur.

We then wait for the interrupt. Once it occurs, GIE is automatically cleared so we can’t have them inside each other, and blink the LED a couple times. We then clear the interrupt flag, saying we’ve handled the interrupt (INTCON,INTF). We then re-enable interrupts and return from the interrupt: retfie.

It should be noted that if we were using more than one interrupt type, we would have needed to check the flag bits to find out which one interrupted us. We then handle it, and clear its flag. The PIC is somewhat crippled in this manner. Any and all interrupts generated and thrown into address 4 and “we” have to figure out which one occured. Many higher end MCU’s will have a table of addresses to jump to for each particular interrupt type, we then code in each location the correct routine and the processor knows which to call based on what happens.

Downloads

PIC buttons (polling)

Schematic

Polling for button input, how useful! This is pretty brief and gives a good idea how to let buttons control your programs execution.

In this tutorial I’ve switched from using an oscillator to using a crystal. This changes the design a bit. Using the 2 OSC pins, OSC1/OSC2 (CLKOUT/CLKIN) they hook to the crystal in parallel. Then the two sides of the crystal are connected to ground via two capacitors, in this case 18pF. The speed and capacitance needed varies, and can be seen in most any PIC MCU datasheet as to how to lay it out. It will also be in my schematic.

Please note this is essentially NO DIFFERENT than the oscillator. It is simply a different means of providing a clock signal. You can swap in the oscillator to the normal CLKIN pin and it will work just fine.

For this program, we’ll start the processor, and wait for a button to be pressed. After it has been pressed we’ll essentially execute the LED blinker from before.

Polling is extremely simple. Polling is the act of checking something for a certain state to occur. In our case, we poll PORTB,4 waiting for it to go low. Once it does, we continue on and start blinking.

Circuit Image

Circuit Image

Another topic that needs to be covered is that of Pull-Up resistors. In order for a button to be able to change from 0 to 1 (Ground to 5V) we need a way to protect everything from a short circuit. We do this with a Pull-Up (or Pull-Down in some cases) resistor. For our case, we connect PORTB,4 to +5V via a 10k resistor. So when we fire up the program, the PIC sees PORTB,4 as HIGH. We then connect our button to PORTB,4 on one side, and Ground on the other. Now, when we press the button, PORTB,4 is connected to ground and is now LOW. A short circuit between +5 and Ground would occur if not for the resistor, which being 10k limits the current to a measly 500uA, nothing to worry about. Once the button is released, Ground is disconnected and the pin returns to a HIGH state.

Reading the code, you may also notice the _BANK macro has changed a bit. I’ve simply modified it to encompass all possible BANK configs instead of using 4 different macros for each. Reading through it can give you a little insight as to how the conditional assembler works. It’s a bit like C/C++ and a bit like BASIC. Quite nice and handy.

A final new bit is the _MCLRE_OFF in the __CONFIG line at the beginning. This frees us from having to pull _MCLR high to keep from a RESET condition. Just keeps our parts count down. :)

Now for the new section of code.

  btfsc PORTB,4
    goto $-1

That’s polling, yup, that’s it. All it is doing is a bit test on PORTB,4, waiting for it to become clear (Ground). The goto line is telling it to go 1 instruction back ($ means the address of the current instruction, $-1 means one before, the btfsc). Once that pin becomes 0 it skips the loop forcing it to check, and lets it continue on down to the blinker!

Downloads

PIC LED blinker (busy-wait)

LED blinker #1 circuit schematic

Schematic

Beyond all doubt, the #1 beginning program in microcontrollers is the LED blinker. It’s super simple, and teaches the concept of pin voltages and busy-waits. Here is a busy-wait LED blinker program, and a walkthrough building it in MPLab.

First, the delay. This is a busy-wait delay program, busy-wait means you just burn instruction cycles for the delay, keeping the MCU “busy”. There’s a tiny bit of math behind them. First, the clock speed is 20MHz, the instruction frequency is (clock/4) so our instructions are executing at 5MHz. This gives us a period of 200ns per cycle.

I created two delay functions, for versatility, and for LCD stuff which will come later but it’s handy here. One is a “DELAY_US” which will delay a specified amount of microseconds. This is done by wasting 5 cycles (5*200ns = 1us) a specified amount of times (less than or equal to 255us, since I only made an 8-bit “delay” variable). We can learn the cycle times from the data sheet, and make it work from there.

Next, I created a “DELAY_MS” which delays a specified amount of milliseconds. Same 8-bit limitation of max 255ms, but that’s enough to have fun with… It simply calls DELAY_US a few times with specified amounts of delay, adding up to 1000us (=1ms) and repeats as many times as we tell it to.

These delays are used to make the LED blinking visible, otherwise it would blink faster than we could see (if at all, it takes a cycle or two for the pin to change state so it might not even change if we just toggle it every other cycle).

Here’s a little more PIC architecture information. The PIC’s data registers are broken into “banks” (bank 0, 1, 2, 3). Meaning you cannot get at them all at the same time, although some are mapped to all banks so you CAN get at them, important ones. We usually hang out in bank 0… This usually isn’t a problem, just something you need to remember. The data sheet illustrates it pretty well. When it comes up I’ll clarify things about it.

Also, for the I/O pins. Since they’re bi-directional, you need to choose which direction to set them to. Input or output. This is done by setting the tri-state register for the given port, for example PORTA’s tris register is called TRISA (not tough!). You set the direction of a specific bit, by setting the bit of the TRIS register to either 0 or 1, 0 meaning OUTPUT and 1 meaning INPUT. Not tough to remember: 0 = Out and 1 = In.

Source Code

Ok, first you need to make sure you have MPLab from Microchip.com

Once you have MPLab, download the LED Blinker (busy-wait) source code.

MPLab is a nice IDE, you’ll need to create a “project” and then pick your chip, and add the asm file to it (called a “node”). All code is going to be indented 2 spaces, labels will not be indented at all, assembler directives are either 1 or 2 spaces in…

First few lines are kinda simple, the title directive just sets a title for your project…

 title  "LED Blinker Tutorial 1"
 
  LIST R=DEC
  INCLUDE "p16f628.inc"
 __CONFIG _CP_OFF & _WDT_OFF & _HS_OSC & _PWRTE_ON & _LVP_OFF & _MCLRE_ON

LIST R = DEC sets the default “radix” for the program, meaning the number base. So if I put 100 in a line somewhere, it means 100 DECIMAL. If I changed it to LIST R = HEX, then if I put 100 in somewhere, it means 0x100, TOTALLY different. I find DEC easier to work with, and you can still use 0x whatever and it means HEX so you get the best of both worlds.

Next we INCLUDE the “p16f628.inc” which will give us nice little names for our registers so we don’t need to remember their addresses, how nice of Microchip.

Then the __CONFIG line, arguably the ugliest line of code while still being fairly simple. You can see the list of __CONFIG’s in the “p16f628.inc” file, they’re simply setting the configuration of certain chip features, the ones I have listed do this: _CP_OFF copyprotect OFF, you can set the copyprotect bits to make your chip unreadable, this is only good for a production product, don’t use it. _WDT_OFF, watchdog timer off, watchdog timer is there for mission-critical applications, it’s constantly running and needs to be reset constantly, if it isn’t reset it will reset the chip (assuming your program has locked up), we don’t need it here. _HS_OSC specifies a high speed oscillator, it’s in the data sheet. _PWRTE_ON turns on the power up timer delay, it’ll make the MCU wait a bit before executing to make sure voltage is stable, oscillator is stable, etc… _LVP_OFF – low voltage programming, dun’need it… _MCLRE_ON makes us hold MCLR high, instead of letting the chip do it…

;----------------------------------------------------------------------------------------
; Variable declarations
;----------------------------------------------------------------------------------------
 CBLOCK 0x20
DELAY,DELAYTMP			; delay function variables...
 ENDC

Now to the variable declarations, the CBLOCK directive lets us just list out our variable names, and the assembler will assign addresses for us, this is handy. The 0x20 is the starting address of general-purpose registers in BANK 0. We list em out, then end it with ENDC.

;----------------------------------------------------------------------------------------
; Macro declarations
;----------------------------------------------------------------------------------------
BANK0 macro           		; Switch to BANK0
  bcf STATUS,RP1
  bcf STATUS,RP0
  endm
 
BANK1 macro           		; Switch to BANK1
  bcf STATUS,RP1
  bsf STATUS,RP0
  endm
 
DELAY_MILLI macro TIME
  movlw TIME
  movwf DELAY
  call DELAY_MS
  endm
 
DELAY_MICRO macro TIME
  movlw TIME
  movwf DELAY
  call DELAY_US
  endm

Next the Macro’s… Macro’s are one of the coolest features of MPLab, it’s kinda like an inline C function, and kinda like a #define. When called, the code is dumped into where it was called from, but you can use variables in it, and even arguments to customize it’s compiling, stuff I’ll show in later programs.

Anyways, you make one by saying: NAME macro in the first column, then code. End it w/a endm. The ones I have are fairly simple and re-usable. BANK0 sets the bank bits to get us into BANK 0, go figure. BANK1 sets them to get us into BANK1, crazy!

DELAY_MILLI takes the TIME argument and loads it into W, next it moves W to the register labeled DELAY. Then it calls our illustrious DELAY_MS function which will be explained in detail down below… DELAY_MICRO does the same damn thing with DELAY_US!

;----------------------------------------------------------------------------------------
; Program code
;----------------------------------------------------------------------------------------
  PAGE
 
 org 0
  goto MAIN
 
 org 4
ISR
  ; interrupt handler
  retfie

PAGE is a pagebreak for printing, though it doesn’t work for me. :)

org 0 tells the assembler to start assembly at address 0, our first instruction is to jump (goto) the label MAIN.

org 4 starts us in the Interrupt address of PIC’s… This is kind of strange and will be explained later on, for now just accept it as fact…

And if you’re wondering if we lost space for 3 instructions between 0 and 4, you’re right. :)

;----------------------------------------------------------------------------------------
; Subroutines
;----------------------------------------------------------------------------------------
DELAY_US			; busy wait of DELAY us
				; 200ns instruction period assumed
  nop				; (1)
  nop				; (2)
  decfsz DELAY,f		; test DELAY count (3)
    goto DELAY_US		; loop if not done (4,5)
  return			; gtfo (4,5)
 
DELAY_MS			; busy wait of DELAY ms
				; dependant upon DELAY_US being accurate
  movf DELAY,w
  movwf DELAYTMP		; save DELAY time
DELAY_MS_LOOP			; inner loop
  movlw 245			; load 245 (1)
  movwf DELAY			; into DELAY (2)
  call DELAY_US			; wait 245us (3-249)
  movlw 245			; load 245 (250)
  movwf DELAY			; into DELAY (251)
  call DELAY_US			; wait 245us (252-498)
  movlw 245			; load 245 (499)
  movwf DELAY			; into DELAY (500)
  call DELAY_US			; wait 245us (501-747)
  movlw 246			; load 246 (748)
  movwf DELAY			; into DELAY (749)
  call DELAY_US			; wait 246us (750-997)
  decfsz DELAYTMP,f		; test DELAYTMP count (998)
    goto DELAY_MS_LOOP		; loop if not done (999,1000)
  return			; gtfo (999,1000)

Next we have our subroutines, the delays, I have these before the main lines of code just out of habit, it’s not required.

DELAY_US… Pretty simple really, we start out by burning 2 cycles, so we’ve waited 400ns so far, next we decrement our counter, and test if it’s zero, if it isn’t, we goto DELAY_US, looping again, if not, we return. The test itself takes one cycle (600ns so far) and either the goto or return take 2 cycles (1000ns = 1us) so we have our microsecond delay!

DELAY_MS works on the same principle, it’s accurate enough for this! :)

;----------------------------------------------------------------------------------------
; Mainline of code
;----------------------------------------------------------------------------------------
MAIN
  BANK1
  bcf TRISA,2			; PORT A, bit 2 is our output pin.
  BANK0
 
LOOP_BEGIN
  bsf PORTA,2  			; set her.
  DELAY_MILLI 250		; wait 1/4 sec
  bcf PORTA,2			; clear her!
  DELAY_MILLI 250		; wait 1/4 sec!!!!
  goto LOOP_BEGIN		; forever... :o
 
 end

MAIN is our label for the beginning of the code, jumped to by the first line up @ org 0. BANK1 gets us into BANK 1 so we can set our bit direction, we clear bit 2 of TRISA making bit 2 of PORTA our output pin, then we hop back to BANK 0…

Then our introductory programming teachers worst nightmare, a purposely created infinite loop. We label the beginning, then set our pin high, shutting off the LED (as you’ll see in the wiring diagram). We wait 250ms via our handy delay function, then clear the bit, turning the LED on, we wait again and loop ad nauseum.

‘end’ tells the assembler to give up…

Building

Alright, so we have our program, we run the assembler by clicking the weird funnel icon or by going to Project -> Build Node (or All). It’ll crunch and come up with no errors (of course).  Then just toss it into the programmer and feed the chip your tasty code.

Schematic

The assembled circuit

The assembled circuit

Wire up the circuit as in the schematic at the top of the page. Hopefully it illustrates to you why the LED is on when the bit is off, and off when the bit is on… The LED is a typical ~2V yellow LED… Wired up it should look something like the image.

Running

Hook up +5V and Gnd, and fire it up! If everything is set up correctly you’ll get a steady blinking LED!

Something to try

Connect a momentary switch to the _MCLR line, wired to Ground on the other side. Pushing the button will reset the chip, releasing it will start it over from the beginning of the program, of course it will do the same stuff, but this demonstrates how the reset buttons work. I also highly recommend changing the code to add a more interesting blink pattern, longer/shorter delays, and other stuff to get used to modifying code…

Downloads

Mr. Baybus

Mr. Baybus is a microcontroller-based fan control system. It is a completely stand-alone unit, with no computer-control whatsoever.

Control comes from momentary switches on the front panel. You have 4 switches to toggle your fans on/off, and a brightness/contrast button, which switches you into a screen to alter those settings. Another press gets you back to the fan status display.

All settings are saved in EEPROM memory on-chip. So when you shut your system down, then power back up, your fans will be running the same as they were before, and your brightness and contrast will remain unchanged as well.

The display is a CrystalFontz 16×2 Serial LCD. This unit is EXCELLENT. It supports SPI transfers which is what Mr. Baybus prefers!

The fans are switched by power MOSFETs. IRL3102’s to be exact. They are rated to handle up to around 7A for a 12V circuit like this. This is of course far beyond anything I would ever want to throw at it, but it’s nice to know you have the room to expand.

Connections to the system are made via a small 4-pin connector. This facilitates 2 fans per circuit, 4 circuits in all. The connector is the same as the CD-Audio connector on your CD-Rom’s so it’s quick and easy to remove the fans.

The brains of the system come in the form of an 18-pin microcontroller. A very basic PIC16F84a. At 4MHz this little guy is going way faster than this system needs but hey, if you got it, why not. All 912 lines of code were written in assembly over the course of a few nights.

Full source code, schematics and PCB layouts are available on GitHub, enjoy!

Screen Images

Startup Splash

Startup Splash

Main Screen

Main Screen

Options

Options

Unit Images

Blank PCB

Blank PCB

Mr. Baybus

Mr. Baybus

Labeled Close-up

Labeled Close-up

Front view, installed

Rear view, installed

Rear view, installed