close-circle
Close

Firmware hacking and adding new applications

A project log for Camera badge for Supercon 2017

Conference badge for the 2017 Hackaday Superconference Features: camera module, 128x128 OLED display, MicroSD card socket and accelerometer

Mike Harrison 10/11/2017 at 11:000 Comments


Firmware APIs

These are not documented here, but in the globals.h include file. This avoids the need to keep this page synchronised with the current firmware version as features are added or modified.

Tools required

Microchip MPLABX (4.0x) and XC32 1.44 (free version). You do NOT (thankfully) need Microchip Harmony.
Although the project compiles using the MPLAB XPRESS online IDE, it does not appear to be possible to download the generated .hex file so it's not useful.

If you want to get more serious you will want a PIC programmer such as PicKit 3 or MPLAB ICD3/4, or a 3rd-party programmer that supports the PIC32MX170F256D. When installing MPLABX, if you have a Microchip hardware programmer, also install IPE (MPLABX installer installs it by default), which is used for device programming outside MPLABX - this will be useful for restoring the bootloader.

The firmware is compiled with -01 optimisation, which is the highest available in the free version of XC32. Higher optimisation levels in the paid-for compiler (available as a free trial) may improve speed for compute-heavy tasks ( like particle demo), but things like display and camera grab/save are mostly limited by hardware clock rates etc. so won't see much benefit.Teh compiler flag compiler flag -fno-aggressive-loop-optimizations appears to be needed to avoid some problems seen with -01 optimisation. this is all set up in the project.

There are two MPLABX projects - cambadge and camboot.
See files section for latest version of cambadge project.
Camboot is the bootloader, which you will only need if you want to modify the bootloader.

The cambadge project is compiled to a .hex file  ( <project path>.cambadge.X\dist\Normal\production\cambadge.X.production.hex) which can then be loaded from the MicroSD card via the bootloader, or programmed directly onto the device via the ISP header using a programmer such as PIckit 3 or MPLAB ICD3/4. (The ICD  programmers are significantly faster than Pickit 3 - it may actually be quicker to use the MicroSD card+bootloader method than a Pickit 3!).
For fastest programming speed within MPLABX, be sure to select  the MPLABX option "Maintain active connection to hardware tool" in options->embedded.

Note that direct programming will overwrite the bootloader! To restore it, you need to program this .hex file which contains the factory-programmed image containing the bootloader and early application code. This can be programmed using MPLABX IPE.

Important note when using a hardware programmer
As the power is soft-switched, when a programmer starts programming the PIC, the power-control line will float, and turn off the power, so programming will fail. To avoid this you need to hold the power button for the duration of programming.
An alternative is to fit the header at J6 (next to the power button), and put a jumper on it. this holds the power always on, avoiding the need to press the button.
The jumper does not affect the use of the power button asn an input. With the jumper fitted, if the badge wants to power down (e.g. timeout) it will sit in a screen showing "awaiting powerdown" and a button to allow a reset.

The linker file  ( link_forboot.ld ) has been set up to compile to the correct memory locations for use with the bootloader, but also patches a jump into the reset vector, so the same .hex file will run standalone when programmed direct via a device programmer. This avoids the need for seperate standalone and bootloader build variants. 

Project Files

apptemplate.c : Demonstration application for use as template for user applications
browser.c
: file browser application
cambadge.c  : main(), main menu polling loop
camera.c
: Camera application
codescan.c : Barcode reader application
display.c : OLED display drivers and display formatting/font stuff
fileformats.c : Routines for parsing, displaying and creating BMP and AVI files
FSIO.C : Microchip MDD file system (with slight hacks for speed)
globals.c : Declarations of global variables
hardware.c : Hardware drivers and initialisation, miscellaneous utility functions
interrupts.c
: Interrupt handlers (camera and serial)  and interrupt setup
particle.c : Particle demo application
SD-SPI.C : SD card driver for MDD file systems ( with slight hacks fpr speed)
serial.c : Serial remote control
settings.c : Settings application

appmap.h : mapping of application names for main menu
cambadge.h : includes like xc.h, stdio.h, misc. definitions, build options, display formatting constants  etc. must be #included in all source files
camvals_9650.h : camera register intialisation data
font.6x8.h : normal display font data
FSconfg.h : configuration for Microchip MDD file system
FSdefs.h
: used by Microchip MDD filesystem
FSIO.h
: function prototypes and constants for Microchip MDD file system
genericTypeDefs.h
: used by Microchip MDD filesystem
globals.h
: function prototypes, extern declarations for global variables, useful macros. #include in all source files
HardwareProfile.h : used by Microchip MDD filesystem
pindefs.h
: hardware pin definitions and access macros Only of interest if radically changing hardware.
SD-SPI.h
: used by Microchip MDD filesystem

link_forboot.ld : linker script. Edit at your peril!

<path>.cambadge.X\dist\Normal\production\cambadge.X.production.hex : generated .hex file

Adding user applications to the main menu

Each of the applications in the main menu has its own source file. A simple modular application architecture allows user functionality to be added easily to the  menu, with no changes to the main source files. To add a new function, you only need to do the following : 

Your function is called with an action code to tell it what to do. See the template application for more details.  Current action codes are listed below. Unknown action codes must be ignored and return 0.

Applications have access to global functions for accessing various hardware functionality, and global variables for status ( e.g. accelerometer values, button states, cursor position etc.) See the file globals.h for details of these. 

Your application will receive act_poll requests whenever the menu code isn't doing anything, typically every few microseconds, except when reading teh accelerometer and buttsons, whcih takes typically around 400uS, and occurs every 20mS You should try to keep the execution time for each poll request below 20mS. The only thing that will happen if this is extended significantly is you may miss button-presses that occur while your code is executing.  If you really need to sit in a long loop without returning control, you can call readbuttons() to update the button state, but note that unless you call at approx 20mS intervals, auto-repeat timings will be wrong. You can set reptimer to zero in your loop to disable auto-repeat if this is a problem. Note that auto-poweroff will not work if you sit in a loop forever.

Application code for act_poll will typically check the "tick" global to see if it is time to do any timed functionality, respond to buttons, accelerometer  etc. "tick" will usually be set to 1 every 20mS, however if application code takes more than the tick period (20mS) to execute, the tick variable will indicate how many tick periods have expired since the last act_poll call. This allows things like timeouts to be kept roughly the same regardless of  how long the application takes on an act_poll call simply by adding the tick value to your timer variable instead of just incrementing it.  See the demo template application for more details.

If an application depends on the presence of  additional hardware, it should if possible detect the presence of the hardware during act_init, and if not present, store this state in a local static variable. Subsequent calls with  act_help should include an explanation of what hardware is needed, and calls to act_start and act_poll should return nonzero if the hardware is not present..

Three methods are available for screen access. It is not possible to read back screen data from the OLED display.

If you are plotting a lot of pixels, especially small ones, plotlblock() will be rather slow due to the OLED controller command overhead of each operation. You will find it MUCH faster to build the image in a memory buffer and then display it using dispimage().
There is variant of plotblock(),  mplotblock(), which plots to a memory buffer, for later display by dispimage(). See particle.c for an example of usage.

Printf() output can be redirected to UART1 or UART2 by setting the global dispuart to 1 or 2 respectively, and 0 to return to normal display output. Setting bit 4 of dispuart outputs to both the screen and the selected serial port.

Buttons are detected by looking at the butstate and butpress global variables. Bits (but1..but5,powerbut, defined in cambadge.h) in butstate reflect the realtime state of the buttons, which are read on each 20mS tick event. Bits in the butpress variable are set once when a button is pressed, i.e. you will see the bit set on exactly one act_poll call to your application code, and this call will coincide with a tick event. If button reads in loops outside the main polling loop are needed, readbuttons() can be called to update the button states. Note that readbuttons() assumes it;s being called at the 20mS tick period for purposes of auto-repeating.

The accelerometer is read on each 20ms tick event, the results being stored in globals signed int accx,accy,accz The range is approximately -17000 to +17000. Note that the zero position and span will vary slightly from device to device, and with temperature.

Using the  camera

The camera is initially enabled using cam_enable(), specifying a mode number which sets the basic capture parameters. See cambadge.h for details of available modes).This powers up and initialises the camera module, sets up default capture parameters.
Capture may then be started using cam_grabenable()
cam_enable(0) disables the camera and puts it into low-power standby mode,

After setting up the basic mode using cam_enable, capture parameters may be changed via the following global variables:

xpixels,ypixels: number of pixels captured in each direction
xstart,ystart : Position of first pixel within camera image to capture.  These can be used to pan around the camera image.
xdiv,ydiv : divisor for captured pixels, e.g. 2 captures every other pixel, 4 captures every 4th etc. These will generally work as expected in mono mode, but there are other constraints on xdiv in colour modes, which are limited by DMA bandwidth. xdiv must be >=2,
Invalid combinations of parameters can cause wierd things to happen.
See the description of cam_enable and cam_grabenable in globals.h for more info.
Do not change settings while grabbing is enabled, as a vsync interrupt may start capture in between changing different variables, as a capture may start with a mix of old and new parameters.

Global variables are available for the application code to determine the current capture state:
cam_started - set when active part of frame starts capturing ( cleared by user code)
cam_done - set when a frame has been captured ( cleared by user code, or cam_grabenable)
cam_busy - camera is still in the process of capturing a frame. Set on Vsync, cleared when all lines are captured
cam_line - last line that was captured

PIC resources available to user code

RAM : Applications should not use malloc! A large buffer is declared globally, and is available for use by application code. This is declared as a union of chars, shorts and longs, and so may be used for arrays of any of these types within application code. A seperate 1024 byte buffer is also available, again defined as a union of chars,shorts and longs.

See globals.h for more details of available memory and methods to share user array types with the global image buffer. 

Nonvolatile memory : The flash memory 0x1D00 7C00 to 0x1D00 7FFF is reserved for nonvoltile storage. This will not be overwritten by the bootloader or direct programming. It is sized to be one erase page (1K), as a whole page must be erased to chnage values.

The firmware includes two functions to support this ; nvm_read() and nvm_write(). these read and write the whole 1K NVM area to global RAM buffer nvmbuf[]. This is a union that can be accessed as bytes, shorts or words :
nvmbuf.bytes[0..nvm_size]
nvmbuf.shorts[0..nvm_size/2]
nvmbuf.words[0..nvm_size/4]

To use NVM, use nvm_read()  and copy your data from nvmbuf. To update, do nvm_read(), update your values in nvmbuf, then nvm_write(); nvmbuffer is overlayed with avibuf and pallette, so must not be relied on for long-term storage.

NVM will not be overwritten when loading new hex files via the the bootloader, but it will be erased to 0xffffffff if directly programming via a hardware programmer. There  appears to be an option in MPLABX program dialogue to make it preserve ranges by reading and then rewriting, but I have yet to get this to actually work correctly.

NVM allocation : NVM byte addresses 0300-0x3ff within the NVM space are reserved for internal use. If you want to use NVM for a new application, please add the address range you use in the comments for this page, and check the page for existing allocations. Allocations should be in multiples of 4 bytes, and word aligned.

The PIC clock frequency is 48MHz - this is chosen to get nice baudrate divisors for high baudrates (1,2,3,4,6Mbaud). Parameters like tick frequency and baudrate use the clockfreq #define to adjust to different rates.

PIC peripherals  used by badge firmware

Items marked * are used within the main polling loop. Others are only used by applications and may be used by other applications with care if their other functions are not being used.

ADC* : battery read.
Parallel Master Port : Camera. I2C2* : Camera and accelerometer
Timer 1 : Used for delays via delayus()
Timer 2 : Used by camera and browser applications for frame-rates
Timer 3 : Used by camera capture task to count pixel clocks
Timer 4* : Used for system tick
REFO : Used to clock camera ( do not disable - camera will screw up I2C bus)
SPI1 : Display
SPI2 : SD card and SRAM
UART1 : Can be used for debug output
UART2 : Debug output and serial remote control

 If you use a timer in an application, set it up on the act_start call, not act_init as other applications may have changed it before yours runs.
Timer 3 is used by the camera, and is configured by cam_enable, so can be used by other applications.

The ADC is used to measure battery voltage on a tick event. It may be used by application code by using claimadc(1) to disable battery reads, and claimadc(0) when done to reconfigure it for battery reads. When claimed, battery voltage will read a dummy value of  4000mV.

UART2 is used by serial control and may be set up to output debug data.
UART1 is free for use, and by default is routed to the expansion header and the peripheral selects mapped to these header pins. initial baudrates for both UARTS are defined in cambadge.h 
Printf() output can be redirected to UART1 or UART2 by setting the global dispuart to 1 or 2 respectively, and 0 to return to normal output.

At startup, UART1 is not enabled, and the RC3/RC5 pins configured as input ( with pullup) and output ( defaulting high0 respectively. the first time u1txbyte is called, the UART will be enabled, and the pins configured for UART operation.

See interrupts.c for current interrupt usage. DMA channels 1 to 3 are unused.

Serial control

Some simple serial control functionality is included,  accessed via a TTL232 cable plugged into the TTL232 header. This is primarily intended for debugging, for example messing with camera parameters, debug text output, pulling out camera data etc. A 3.3v TTL cable should be used. There is a 2k2 series resistor in RXD so a 5v cable will probably not cause damage, but the 2.8v logic levels may be marginal for transmitted data to a 5V input.

As this interface is mostly there for debugging , it is subject to frequent changes and it is not documented here. See the serial.c source file for details.

Blue screen of death

An general exception handler is provided to catch memory errors and div by zero - this displays "BSOD" in inverse blue and then the watchdog will cause a reset a couple of seconds later.

Discussions