Monday, 27 October 2014

Cloned chips? From bad to weird

A few people have posted suggesting that a lot of AVR chips need a crystal on them, before they can be programmed. I've never heard of this, and never had experience of it with a PIC (though PIC programming is done at high-voltage, rather than the low-voltage/SPI method favoured by many atmega-variety chips).

We already have a couple of working chips, from Farnell, mounted onto breakout boards and one of our original prototype gaming boards, and they work fine; although possible to put a chip on the breakout boards, we're pretty sure they were programmed before introducing an external crystal (which we introduced to debug the M103 problem which was stopping the chip from working properly).

But our prototype game board has no crystal onboard, and never has. There's no way we could have programmed the chip on the board with a crystal connected. Yet it's programmed up with our latest firmware and is working. So perhaps it's not the crystal.

But in the same way we spent most of Sunday proving that the chip was faulty (it was a lot of effort, to confirm the suspicion "this will never work", but something that needed to be done!) we felt it necessary to prove that the problem didn't stem from the lack of a crystal on the board.

After connecting a 16Mhz crystal to pins 23+24 we tried once again to get AVRDudess to recognise the chip. As before, the result was the same - MCU not detected. So what device ID were we getting this time?



Strangely, the device ID seems to change on each detect request.
There doesn't seem to be a pattern to it either - just apparently random numbers each time we hit the detect button! The id 0x666c61 comes up most frequently, and 0x656570 is also pretty common. But there's also a whole heap of junk in there too.

Whatever this chip is, and whether this chip needs a crystal or not, it's certainly not behaving anything like an AVR atmega chip!


Sunday, 26 October 2014

Proving our atmega128 chips are fake

Sometimes it's nice to spend a few hours of a wet weekend making stuff for the sake of making. Etching PCBs can be fun - after all, that's why so many people are drawn to electronics as a hobby, and making your own circuit boards, from start to finish can be quite rewarding.

So it's with mixed feelings we managed to prove that our chinese-sourced atmega128 chips must be fakes.


Using the last few pin header connectors we had lying around, we stuck one of our suspect atmega128 chips onto a home-etched breakout board (the few pins unsoldered in the photo above were not critical for the operation of the chip) and tried it with the usbtiny programmer


It was almost with relief that the AVRDudess interface reported...


Unable to detect MCU.
This is consistent with the report(s) we were getting with the chips soldered onto our pcbs, earlier in the day. Interestingly, if we tried to "force-write" the hex file to the chip, this time it reported back a different device ID:


Now had the device ID been consistent, we might be considering the programmer or the software could be incorrectly identifying the mcu (perhaps it might have been a later revision, or some other weird incompatible version) or maybe the software needed to be updated (the PICKit2 software needs an updated device list to successfully recognise 16F1825 chips after a fresh install, for example).

But having errors and inconsistency smacks of something being fundamentally wrong.
We've got some genuine AVR chips on order from Farnell. They probably won't be here 'til Tuesday, after which we'll be able to prove whether or not the manufactured pcbs actually work.

But we can be pretty confident that we've managed to prove what we set out to prove - namely that the problem lies with the chip and not the pcb. Unfortunately, it means we've spent most of the afternoon working on something to demonstrate that another project can't possibly work!

Stung by Chinese clone chips?

FTDI recently kicked off a whole argument about how to deal with "chip pirates" and manufacturers who clone their controllers. FTDI's response was rather crude - using an exploit in their own badly written firmware, which only writes values to eeprom one byte after the one being written (so to write to location zero, you need to write a value to location zero, and then write a value to location one - the latter value is ignored) their updated software driver started to "brick" devices using cloned FTDI chips.

It took only a couple of days before the manufacturer backed down, but their heavy-handed approach resulted in a flood of complaints following #ftdigate. Many of the complaints make the valid point that the only person being penalised was the end-user, who may well have bought their FTDI-based products in good faith (the issue of them being complicit in conducting criminal damage, as described in UK law, was never addressed, despite FTDI being a UK-based company). Their decision to not deliberately destroy devices using FTDI chips (the update did not discriminate between cloned and non-cloned chips, it just relied on the FTDI firmware being a bit shonky and failing to apply the brick-this-device approach in the updated driver) was welcomed, but hasn't actually dealt with the issue of cloned IC chips.

All of this was just a matter of discussion, ethics and principle for us at Nerd Towers. We'd never been directly affected with a problem of cloned chips not working - despite buying many devices and gadgets from overseas over the years, and even having a few hundred gadgets manufactured by a Chinese factory back in 2010.

All that seems to have changed, with the latest development on our electronic board game!

A few days ago, we received some manufactured PCBs from 3pcb.com (who we highly recommend btw) and soldered up a board. We used the BuildBrighton toaster oven to reflow the SMT components and were little surprised this weekened to discover that the boards refused to be programmed.

So this morning, we soldered up another microcontroller onto another board (we didn't bother with the hall sensors this time, just get a chip on a board to try out programming it with some firmware). Using the tried and trusted soldering-iron and rake-out-the-excess-paste approach we had an AVR atmega128 in the centre of one of our new boards in just a few minutes.

After plugging the programmer in and trying to flash some firmware to the chip, the AVRDudess software reported that the chip could not be recognised - on either of the manufactured boards.

To make sure we had all the wires in the right holes, we connected the same programmer to our original, hand-etched, prototype board. Sure enough, it detected the chip as an ATMega128. So there's obviously a difference between the two boards....

The manufactured boards use exactly the same design as the hand-etched board: it was only after making sure the hand-etched board worked that we pulled the trigger and actually ordered some from a manufacturer. To avoid any problems, we sent exactly the design we'd used to make our own board. Surely it couldn't be that?

After a couple of hours with the continuity tester, comparing our manufactured boards to the homebrew hand-etched one, we could find no difference: the PCBs are essentially the same. The microcontrollers are soldered the same, continuity between the pins of the mcu and the pads on the pcb are the same on all boards. The only other difference could be the microcontroller itself...


Before making our home-etched boards, we ordered a few atmega128 chips from Farnell. We only ordered a few, since they are relatively expensive. But they could be with us the next day, so it was worth spending the money to get them quickly, and prove the board actually worked. In the meantime, we ordered a batch of thirty chips from an online supplier (at about a quarter of the Farnell price). It didn't matter that these might take longer to arrive, but we wanted to get at least one working board, in order to get our pcb order in.

Above is a photo of the homebrew board with the chip, from Farnell.

As we'd used up all of our Farnell-sourced chips, we soldered our overseas-based chips (we can't remember whether they came from eBay or AliExpress - some investigation may be needed!) onto the manufactured boards.


The labelling on the non-Farnell chips is slightly off-centre. And much brighter/easier-to-read than on the Farnell-sourced chips. Could it be we've be sold some (non-working) clone chips?

The only way to find out, is to wait for our fresh order from Farnell to arrive, and to try soldering a Farnell-sourced chip onto a manufactured PCB, just to be sure. But we're 99% certain that the problem now lies with the actual microcontroller, not the PCB or the method of soldering. Which makes us a little nervous about sourcing components in bulk from overseas in future.
It looks like it's not only FTDI who would like to take action against the chip-cloning industry!


Saturday, 25 October 2014

Soldering SMT components using a toaster oven

We recently had some "professionally made" PCBs sent over from 3pcb.com.
Their service was great - super-fast, made and delivered in under a week - and the website was really impressive, keeping customers up-to-date with every step of the manufacturing process


Pricing was pretty impressive too - a massive 200mmx100mm pcb cost about £8 (when ordered in small volume) and went as low as £2.20 each in bulk. The cheapest we'd been quoted for a fast-turnaround, UK-based PCB was £41 each!

A week later and DHL dropped off a parcel with our ten soon-to-be board game sections


It took a number of days to actually clear some time to do anything with them, and when we did, we thought we'd try using an SMT reflow oven to solder all the components in one go (rather than tacking each one down, one-at-a-time with a soldering iron).

A board with solder mask is much easier to work with than one of our "raw" home-etched boards. It's because of the soldermask we can even contemplate something like this - since any excess solder paste will be drawn to the pads of the components (on a homebrew board, baking it would probably create solder-bridges across the tracks, and nasty burnt board, where the copper has been etched and the exposed paper base remains unsealed).

Placing the solder paste and components by hand (using some horrible chunky tweezers) took about 30 minutes.


Completing this process proved more difficult than we'd first expected. When hand-soldering SMT components with a soldering iron, we tend to work in groups of six or eight components at a time - applying the paste and sticking them down in small clumps. This means that when we come to put more components on the board, we can freely spin it around, and it doesn't matter if we touch - or even lean - on the previously placed components.

With wet solder paste all over the board, it took a lot of effort (and sometimes some re-aligning) to get all the components correctly placed on the board at the same time.

The SMT reflow oven at BuildBrighton is little more than a bog-standard toaster oven, with a controller on the mains plug. The controller ramps up the temperature in a controlled manner, using a rather crude "PWM approach" on the main plug. The oven is set up at full power, full timer, and the controller on the plug literally plugs and unplugs the oven from the mains, to maintain the heating/cooling profile. It's a crude approach, but one that a lot of people are comfortable with, so we went with it too!

It took about eight minutes in the over before the solder started to reflow.
As it did, the hall sensors started to float around on top of the pcb, as the molten solder pulled on each leg, dragging them onto the pads. After leaving for a few minutes to cool down, we took out the pcb to inspect it.


All the hall sensors were soldered well, but the microcontroller had a massive amount of excess solder on it, bridging many of the pins.


Luckily, CNC Paul is quite the whizz with desoldering braid. Quite a few of the BuildBrighton members have said they have little success with the braid, so we were in for a masterclass in how to use it properly, to clean up all the excess solder on this little lot.

True to form, CNC Paul took no time at all, and made a really good job of it!


The whole board just had a quick wipe down with some isopropyl alcohol to clean up any excess flux, and is now ready for programming.

On the whole, we're pretty non-plussed about cooking the boards in a toaster oven. Having to place all the components while carefully avoiding previously placed components is tricky, when all of the solder paste is wet. Our stick-em-down-with-an-iron approach at least ensures that the components are properly stuck down as we go!

To properly solder the microcontroller, we should really have used a stencil to get just the right amount of paste on each pad, rather than a squiggly line of paste across all of the pads.

The nice thing with our soldering-iron approach is that it makes laying down the solder paste on multi-pad components really easy - just place a line of paste, then "rake out" the unwanted paste from between the pads. Using the right conical tip on the iron means that, almost as a by-product of this action, the pads above and below the tip get soldered, while any possible bridging material is removed at the same time.

"Cooking" our board was an interesting experiment, but not really much quicker or easier than hand-soldering with an iron - especially if you consider the amount of "clean up" work required afterwards.
Never mind. It's another technique to have learned, and something we at least know we can do, if we want/need to. We've another 9 boards now to solder up - the only question really is, how?

Saturday, 18 October 2014

Simplified serial string stuffing with PIC and Oshonsoft

If there's one thing we love more than alliteration at Nerd Towers, it's simplifying repetitive tasks. One thing that the Arduino platform, and it's library-based approach to coding has in it's favour, is that it makes prototyping common/simple projects quite easy.

Take serial communications, for example.
Serial.begin(9600) opens the hardware serial port at 9600 baud.
With PIC and Oshonsoft we have HSeropen 9600.
Different syntax, but the same result.

In PIC-land, it's the same as setting the appropriate BAUDCON and SPBRG registers, to get the right baud rate for the current crystal/clock speed. The Arduino IDE is probably doing something similar.

Getting data is pretty easy on both platforms, but where Arduino really shines, is it's string-handing ability. And in particular, the relative ease with which you can build up a string, from incoming serial data.

String s="";
While Serial.bytesAvailable(){
  char b=Serial.readChar();
  s+=b;
}

With PIC and Oshonsoft, we've always just dumped the data into a large buffer, and processed the incoming data. The downside to this approach is that you effectively need to stop all serial communications which you work on the received data. And this isn't something you want to do each time. There's no equivalent of Serial.bytesAvailable. And that's a shame, because, for prototyping stuff quickly, that's a really useful function.

And after messing about with those wifi modules the other night, what we really need is a quick and easy string handling routine (that can handle data coming in at no less than 115,200 bps). So we came up with an idea which, after looking at the source code for the hardware serial library in Arduino, is very similar to the Arduino implementation.

Basically, we're going to keep a "circular buffer".
This means we've got an array of 80 bytes as our buffer. We keep track of which element of the buffer we're going to write any incoming data to (called the "head" of the buffer) and which element we're going to read data from (called the "tail").

The idea is we can use an RX-data-in interrupt to write incoming data, one byte at a time, into the circular buffer, without affecting where we're reading the data back from. Whenever the RX interrupt fires, we advance the head pointer one element along the array, and store the incoming byte into that location



It's called a circular buffer because when either of the pointers need to advance beyond element 79 (the last element in an 80 byte buffer) they reset back to zero and continue filling the array.

When we want to see what data we've received over serial, we read the data out of the buffer, starting from the tail. While we're reading this data back, it's quite possible for more data to be coming in over serial, which in turn gets written to the buffer, advancing the head pointer.


Because of this, it's quite possible that the head pointer can be ahead of the tail (as is the case for the first few bytes received). But after time, it's also possible that the tail has "moved along" a few bytes, and the head pointer has "wrapped around" back to zero, and is "behind" the tail pointer in the array. This is normal.



The only time we have a problem is if the head pointer tries to "cross over" the tail pointer.
When this occurs, we've had a buffer overrun. i.e. we've received more data into our buffer than we've managed to read back out in the time it's taken to fill up (i.e. we've received 80 bytes more than we've read out of the buffer). There are two ways this could be handled - simply stop advancing the head pointer, and any further data received is lost; or advance the tail pointer just ahead of the head pointer, retaining the newest data, and overwriting the older data.

Whichever approach is used will result in lost data, and it depends on the application as to which data has a higher priority and should be kept. For simplicity, we're using the first approach - if we've received a full 80 characters into our buffer and not managed to read them out quickly enough, we keep reading the serial data from the UART module (thus avoiding a true, hardware buffer overrun which would disable the serial peripheral on the chip) but we don't store it anywhere. It's just as easy to amend the code to retain the newer data by overwriting the old data and advancing the tail pointer.

The advantage to this approach, rather than dumping incoming bytes into a "regular"  linear array, is that it allows us to monitor the incoming data for a particular character or combination of characters. If we're looking for the word "HELLO" in our data, we'd have to traverse the array looking for

array(i+0)="H" AND array(i+1)="E" AND array(i+2)="L" AND array(i+3)="L" AND array(i+4)="O"

which can get quite cumbersome. Far easier to read back the contents of the array, until a specific character (such as a line break or carriage return) is hit, and process the contents as a string of data. While processing each individual line of (ascii) data received, the interrupt-driven RX routine is  still populating the rest of the buffer - something we couldn't do if we had to process our array of data immediately after receiving the character or byte acting as a delimiter in our data.

Taking all these ideas and putting them together, we've written a simple serial buffer routine that stores data coming in and sets a flag when a particular character (in the example, it's a carriage return) is received into the UART receiver.

In our example, we demonstrate how processing the data can continue outside of receiving characters on the serial UART peripheral; our example code can detect if a line in the serial data contains the word "OK" (and flashes an LED if it does) or sends the data back out to the serial TX (for debugging) after a delay of about a second. Of course, this doesn't do much on it's own, but it's a great start, for parsing incoming ASCII messages from the wifi module; especially because responses are often written as AT type commands, one on each line, with either OK or ERROR written on it's own line to indicate the success (or otherwise) of each command sent to the wifi module.

It's not quite as elegant as the Arduino's while Serial.bytesAvailable() function - but it's far easier than either handling characters as soon as they arrive at the serial RX port, or traversing a byte array of characters, to see which words they make up!



Define CONFIG1 = 0x0984
Define CONFIG2 = 0x1cff
Define CLOCK_FREQUENCY = 32
Define STRING_MAX_LENGTH = 79
AllDigital

symbols:
     Symbol led_pin = PORTC.2
     Symbol tx = PORTA.0
     Symbol rx = PORTA.1
   
declarations:
     Dim serial_ready As Bit
     Dim serial_buffer(80) As Byte
     Dim serial_head As Byte
     Dim serial_tail As Byte
     Dim serial_rx As Byte
     Dim serial_string As String
     Dim serial_delimiter As Byte
     Dim serial_ignore As Byte
     Dim i As Byte
   
configpins:
     ConfigPin led_pin = Output
     ConfigPin rx = Input
     ConfigPin tx = Output
   
initialise:
     'set the oscillator to 32mhz internal
     OSCCON = 11110000b 'oscillator = 32mhz
     WaitMs 50 'wait for internal voltages to settle

     'put the tx/rx lines onto the programming pins for easy development!
     APFCON0.TXCKSEL = 1 'tx onto RA.0
     APFCON0.RXDTSEL = 1 'rx onto RA.1
   
     'create an interrupt on the serial receive.
     'as data comes in, add to the circular buffer
     PIE1.RCIE = 1
     INTCON.PEIE = 1
     INTCON.GIE = 1
   
     'blank the buffers
     For i = 0 To 79
           serial_buffer(i) = 0
     Next i
     serial_string = ""
     serial_head = 0
     serial_tail = 0
   
     'open the hardware serial port
     Gosub serial_open
   
     'testing:
     'flash the led to show we're alive
     For i = 1 To 4
           High led_pin
           WaitMs 500
           Low led_pin
           WaitMs 500
     Next i
   
     serial_string = "Ready" + CrLf
     Gosub serial_send
   
     'if we're receiving ascii data over serial, each line ends with CrLf
     serial_delimiter = 0x0d
     serial_ignore = 0x0a
   
     'prepare the serial buffer
     serial_ready = 0
   
loop:
     If serial_ready = 1 Then
   
           serial_ready = 0
   
           'testing:
           led_pin = Not led_pin
         
           Gosub get_serial_string
         
           If serial_string = "OK" Then
                 For i = 0 To 4
                       High led_pin
                       WaitMs 100
                       Low led_pin
                       WaitMs 100
                 Next i
           Else
                 'testing:
                 WaitMs 1000
                 Gosub serial_send
           Endif
     Endif
Goto loop
End

On Interrupt
     Save System
   
     If PIR1.RCIF = 1 Then
   
           'get the byte from the rx buffer to clear the RCIF flag
           serial_rx = RCREG
           If serial_rx = serial_delimiter Then
                 'this is a carriage return (or a zero-byte) character
                 serial_ready = 1
           Else
                 If serial_rx = serial_ignore Then
                       'this is a linefeed character so ignore
                 Else
                       'store the received byte in the serial buffer
                       serial_head = serial_head + 1
                       If serial_head > 79 Then serial_head = 0
                       If serial_head = serial_tail Then
                             'crash!
                             'don't put any more data into the buffer until you've read some back
                             '(or maybe empty the older data first?)
                       Else
                             serial_buffer(serial_head) = serial_rx
                       Endif
                 Endif
           Endif
     Endif
Resume

get_serial_string:
     Dim buffer_start As Byte
     Dim buffer_end As Byte
     Dim buffer_end2 As Byte
     Dim k As Byte
   
     serial_string = ""
     buffer_start = serial_tail + 1
     If buffer_start > 79 Then buffer_start = 0
   
     If serial_head > serial_tail Then
           'this is easy
           buffer_end = serial_head
           buffer_end2 = 0xff
     Else
           'the head has reached 80 and wrapped around to zero
           buffer_end = 79
           buffer_end2 = serial_head
     Endif

     For i = buffer_start To buffer_end
           k = serial_buffer(i)
           serial_string = serial_string + Chr(k)
     Next i
     If buffer_end2 <> 0xff Then
           For i = 0 To buffer_end2
                 k = serial_buffer(i)
                 serial_string = serial_string + Chr(k)
           Next i
     Endif
   
     'remember where to start reading from next time
     serial_tail = serial_head

Return

serial_open:
     BAUDCON.4 = 0 'SCKP synchronous bit polarity
     BAUDCON.3 = 0 'BRG16 enable 16 bit brg
     BAUDCON.1 = 0 'WUE wake up enable off
     BAUDCON.0 = 0 'ABDEN auto baud detect

     TXSTA.6 = 0 'TX9 8 bit transmission
     TXSTA.5 = 1 'TXEN transmit enable
     TXSTA.4 = 0 'SYNC async mode
     TXSTA.3 = 0 'sednb break character
     TXSTA.2 = 0 'BRGH high baudrate
     TXSTA.0 = 0 'TX9D bit 9

     RCSTA.7 = 1 'SPEN serial port enable
     RCSTA.6 = 0 'RX9 8 bit operation
     RCSTA.5 = 1 'SREN enable receiver
     RCSTA.4 = 1 'CREN continuous receive enable

     SPBRGH = 0 'brg high byte
     SPBRG = 51 'brg low byte(see datasheet For 9600@32mhz)

Return

serial_send:
     Dim k As Byte
     Dim j As Byte
   
     k = Len(serial_string)
     k = k - 1
     For i = 0 To k
           j = serial_string(i)
           TXREG = j
           While TXSTA.TRMT = 0
                 'wait
           Wend
     Next i
   
     'send a carriage return/line feed combo at the end of the line
     TXREG = 0x0d
     While TXSTA.TRMT = 0
           'nothing
     Wend
     TXREG = 0x0a
     While TXSTA.TRMT = 0
           'nothing
     Wend
   
     serial_string = ""
   
Return

Friday, 17 October 2014

Getting an ESP8266 wifi module to work with Arduino


Last night was another BuildBrighton nerd-meet-up and, luckily, we had a couple of these new fangled ESP8266 wifi modules to try out. In case you've been living in a cave with a tin can tied to the end of piece of string as an internet connection, you'll probably know that these are the ultra-cheap wifi modules currently flooding out of Chinese factories and onto "hacker benches" all over the world.

The reason they've created such a stir is that a) they're cheap and b) the firmware can be (relatively) easily upgraded. So hackers and makers all over the world have been busy trying to access the onboad microcontroller and utilise the extra GPIO pins, to create a single-chip, all-in-one wifi controller.

Our interests are less adventurous - the modules are sold as UART-to-wifi plugin devices, and that's exactly how we're going to use them.




Supposedly you need just four wires to get your microcontroller-based project talking to a home network over wifi. In theory, it's dead simple to bung this into a project and talk to the world. practice, we found a few gotchas which took up most of the evening to resolve - but we got there in the end.

The first thing to do is to power up the device and get some AT commands flowing.
This is where the first gotcha is waiting, for the unwary. It's documented all over the 'net that these little things can get quite power hungry - they can demand upwards of 280mA when sending/receiving data. After 30 minutes or so of operation, they also run slightly warm to the touch. Nothing that could cause skin burns, but noticeably warm.

There's always the old debate about powering devices from a USB port. Some people claim to run 500mA devices straight off a USB port. Some of us have experience of Windows ejecting USB devices that demand more than their allocated 100mA, without specifically requesting it. Who is right depends pretty much on how your USB ports are configured. But if you're going to try powering these off a USB-to-serial converter, they can still work. But they also reset spasmodically too and without warning.

  • So gotcha number 1; use a dedicated power supply (3.3.v)

We did this, connected up the uart lines and ran a terminal emulator at 57,600 bps as per the datasheet. There was nothing on the screen. The red light was on, on the module, but no data coming from ip. Touching the pins on the top of the board caused the onboard blue led to flicker, and a few characters of junk appeared on the screen - nothing useable, but something!

Some sources on the internet say you can leave the unused four pins floating. Some people say they need to be pulled high (though anything between 1k and 10k resistors). Some sites say you can simply connect these to Vcc. We tried all of these and still got no response.

To actually get the device to respond, we found we had to pull the RESET and CH_PD lines high  but leave the GPIO lines low/floating. Pulling all four pins high stopped the module from sending data.

  • Gotcha number 2: pull the RESET + CH_PID lines high, but not the GPIO lines.

Toggling the reset line at this point and suddenly the screen was full of gunk! A lot of sources say this is normal, and after a whole load of junk, you can expect to see the word "ready". In practice, we found that the data was garbled with no "ready" signal. Changing the baud rate to 115,200 bps fixed this

Gotcha number 3: Some modules use 56,700 baud, some use 115,200bps. Try both

Eventually we started seeing something meaningful in the terminal window



Now we did spend quite some time banging AT command in and getting any number of peculiar responses, ranging from an empty string, to "Error", the bizarre "link is not", to the rather more cryptic "no this fun".

Once you have a module responding to AT commands, the fun begins. It took a lot of Googling around, and reading lots of blog posts from people who tried, gave up, tried, fried-the-board-and-bought-another-to-find-it-worked-differently and a fair bit of guesswork to get our modules to work the way we wanted to, but eventually we did manage to establish a connection between our wifi module and a PC, over a home network. This may not be your preferred setup, but this is what we were aiming for, and how we got there:

We wanted to connect the wifi module onto our home network, and have it report back the IP address it had been assigned. We would then create an app (to run on a smartphone or tablet) into which we could enter this IP address, and send and receive commands over the home network, between the wifi module and the tablet.

This means setting up the wifi module as a "station" (rather than an "access point") but also to set it up as a "server" - i.e. to accept incoming connections. As you can imagine, there can be any amount of confusion when some people refer to a "station" as a "client", but then set up the network connection mode as "server". Server!=client. So we're sticking with the terminology from the Chinglish datasheet.

The first thing to do is set the device as a station:

AT+CWMODE = 1

1=station, 2=AP, 3=station + AP (some kind of weird hybrid mode).
Now, query the local access points with:

AT+CWLAP

And the response (sometimes) looks something like this:


Occasionally this fails. Sometimes the module locks up entirely. There are a number of guesses at why this might be - some people favour that hidden APs screw things up, some that too many basically fill the buffer(s) and cause it to lock up, some just that the firmware is crapping out for some unrelated reason. We've had mixed success using the CWLAP command - sometimes we do get a list of access points. And sometimes it locks up so that it needs to be power-cycled to become responsive again.

Once you have the SSID of the access point you're looking for, connect using

AT+JAP="ssid_goes_here","password_here"

And wait for the OK response.
Interestingly, you get OK even if you put an invalid SSID and/or password. OK doesn't mean "connected to the access point ok". It just means "OK, I heard you". The way to test if you're actually connected to the access point is to give it a few seconds, for the negotiations to complete, then query the IP address, using:

AT+CIFSR

The response will either be ERROR (no ip address assigned) or a string with the ip address in it. Confusingly, sometimes the module requires =? to query a property, sometimes just ? (no equals sign) and sometimes neither/none. So far, we've avoiding using the ? to query anything, but that's coming....

If you've got an ip address, your device is connected on the network and you should be able to see it in the list of connected devices on your router/access point admin page.

Interestingly, the wifi module keeps it's connection data in local eeprom, so that it can silently reconnect to a network, if the connection drops. This is very useful (since we don't have to worry about storing SSID and password data in our microcontroller eeprom and attempting to reconnect on power-up: just give the wifi module power and it'll try to connect to the previous access point, if it's still available) but can also be a problem if the device is to be used for anything important.

The wifi module can be subject to the Google Chromecast attack and we're still not sure whether this is desirable behaviour. Here's how the Chromecast attack works:

When the wifi module powers on, it looks for the previous access point (SSID) and connects using the stored password, if  possible. If the original access point is not available, but a spoof or clone access point exists, with the same SSID and accepts incoming connections (is open, or uses the same password) the wifi module will happily connect to the spoof  access point, without reporting any error,



We proved that this was possible with the wifi modules too, by each setting up a wifi hotspot on a Samsung Galaxy S3, using the SSID "test". Each of us set up our phone as a wifi hotspot/access point with the SSID "test", and activated them in turn. In between activating the access points, we power-cycled the wifi module and it firstly connected to Chris' phone, then Steve's, then Jake's phone - each time completely silently, and without reporting that the access point had changed: it basically found an access point with the name "test" and connected using the same credentials as last time.

This does add a resilience into the wifi module - if the connection to the AP drops, the wifi module will silently reconnect, and resume the same ip address as before, and you'd never be any the wiser. But it does mean that your wifi-based project is subject to spoof attacks (should someone be able to reset your home router, and set up their own device as an access point with the same name!)

By this point, we'd managed to get our wifi module onto a home network (it was actually one of the many APs at the hackspace, but the principle is the same!) and to see it appear in the list of connected devices. Now we wanted to get some data exchange going:

AT+CIPSERVER supposedly sets up the device as a server, ready to accept incoming connections, but every time we tried this command, we just got "Error" as a response. A lot of Googling and we still had no answer - except some people simply said that this command doesn't work, and others saying that the firmware needed to be updated.

We found the solution to be a little simpler.
Before the module can accept (multiple) incoming connections, it needs to be put into "accept multiple connections" mode, using the command:

AT+CIPMUX=1

Now, the command AT+CIPSERVER=1,4040 gave the exciting response OK
It looked very much - just as we approached the midnight-deadline-for-getting-things-done that we had a wifi connected server, awaiting connections on port 4040.

After a few false starts with putty (starting with trying to connect over serial in SSH mode, instead of "raw" mode) Other Chris suggested just entering the ip address of the wifi module into a browser address bar. Not expecting anything to happen, and given the late hour, just about ready to give up, we tried it, in desperation:

192.168.43.121: 4040

And, amazingly, the terminal window showed the response:



We had made first contact!
The + IPD message shows us that the response contained 355 bytes. More importantly, it first shows us the "channel number" (or client ID) of the incoming connection. This is important, and this is how we can send the correct data to the appropriate connected client. Following the id number and the length of the message is the main body of the message received.

To send a response, we used the AT command:

AT+CIPSEND=0, 5

which says "send 5 bytes to channel zero".
After committing the command (i.e. hitting the cr+lf combination) the prompt changed to a > symbol to indicate that we were now entering data, not commands.



After entering "Hello" and hitting enter....... nothing happened.
The "loading" icon was still spinning on the web page we'd opened, to connect to the wifi module. It took about 90 seconds to time out. But when it did, the web page had changed:


It looked like we actually had some kind of two-way communication!
So we fired up Putty, only this time, taking care to use the "raw" rather than SSH mode...



... and started typing. This time, we had data!



So we sent some data back



And Putty duly showed the data immediately.



Data appeared on each end immediately. There was no noticeable lag - as soon as we hit enter on one terminal window, the data appeared in the other. By which time, after high fives all round, it was time to go home.

More investigations will continue over the weekend...









Wednesday, 15 October 2014

Nokia 5110 screen module with PIC microcontroller and Oshonsoft

A little while back, we played with a neat little - cheap - LCD screen from an old-fashioned Nokia phone; now resold as "hacker's toys" online as SPI modules, these screens are great for displaying something a bit more interesting than just 16x2 lines of character-based text.

How these displays work is explained in a little more detail in our earlier post http://nerdclub-uk.blogspot.co.uk/2014/08/sending-bender-over-air-to-nokia-5110.html.

But we'd been using some of Steve's lovely Arduino libraries for a lot of it, which meant a lot of the low-level stuff was missing. There are loads of libraries available online to draw text and numbers onto these displays, but they almost always use fixed-width fonts.

Having left the 1980's behind, we decided to write our own "libraries" to write text onto the Nokia 5100 display, using a PIC microcontroller. The code below was written for a 16F877a (because it was the first chip we found already on a breadboard, with a programming header connected) but would work equally well on any PIC microcontroller with enough RAM for the font table.

Define CONFIG = 0x3f32
Define CLOCK_FREQUENCY = 20

AllDigital
Symbol led_pin = PORTB.7
Symbol lcd_sck = PORTD.4
Symbol lcd_data = PORTD.5
Symbol lcd_command = PORTD.6
Symbol lcd_reset = PORTD.7

definitions:
     
     Define STRING_MAX_LENGTH = 30
     
     Dim i As Byte
     Dim j As Byte
     Dim k As Byte
     
     Dim li As Byte
     Dim lj As Byte
     Dim lk As Byte
     
     Dim si As Byte
     Dim sk As Byte
     
     Dim byte_to_send As Byte
     Dim x_value As Byte
     Dim y_value As Byte
     Dim character_to_send As Byte
     Dim char(8) As Byte
     Dim move_x As Byte
     Dim string_to_write As String
     
     Dim invert_display As Bit
     Dim wrap_text As Bit
           
init:
     ConfigPin PORTB = Output
     ConfigPin PORTD = Output
     
     invert_display = 0
     wrap_text = 0
     
     WaitMs 100
     
     Gosub init_lcd
     Gosub lcd_clear
     
     x_value = 2 'columns are individual pixels
     y_value = 0 'must be a row number, which is a multiple of 8
     Gosub lcd_goto_xy
     Gosub draw_band
     
     
     x_value = 2
     y_value = 1
     Gosub lcd_goto_xy
     invert_display = 1
     wrap_text = 0
     string_to_write = "{ ABCDEFGHIJKLMNOPQRSTUVWXYZ"
     Gosub lcd_write_string
     invert_display = 0
     
     x_value = 2
     y_value = 3
     wrap_text = 1
     Gosub lcd_goto_xy
     string_to_write = "abcdefghijklmnopqrstuvwxyz"
     Gosub lcd_write_string
     
     x_value = 2
     y_value = 5
     Gosub lcd_goto_xy
     string_to_write = "123.45.67.89:0"
     Gosub lcd_write_string

loop:
     
     High led_pin
     WaitMs 2000
     Low led_pin
     WaitMs 2000
                       
Goto loop
End

init_lcd:
     Low lcd_reset
     WaitMs 10
     High lcd_reset
     
     'put into command mode
     Low lcd_command
     
     byte_to_send = 0x21 'lcd extended commands
     Gosub lcd_write_byte
     byte_to_send = 0xb1 'set lcd contrast
     Gosub lcd_write_byte
     byte_to_send = 0x04 'set temp coefficient
     Gosub lcd_write_byte
     byte_to_send = 0x14 'lcd bias mode 1:48
     Gosub lcd_write_byte
     byte_to_send = 0x0c 'put lcd into normal mode
     Gosub lcd_write_byte
     byte_to_send = 0x20 'dunno
     Gosub lcd_write_byte
     byte_to_send = 0x0c 'dunno
     Gosub lcd_write_byte
     WaitMs 5
     
     'put into data mode
     High lcd_command

Return

lcd_write_byte:
     For li = 0 To 7
           lk = 7 - li
           lj = 1
           lj = ShiftLeft(lj, lk)
           lj = lj And byte_to_send
           If lj = 0 Then
                 'send a zero bit
                 Low lcd_data
           Else
                 'send a one bit
                 High lcd_data
           Endif
           
           'toggle the clock line
           High lcd_sck
           ASM: nop
           Low lcd_sck
           
     Next li
Return

lcd_clear:

     'put the cursor at 0,0
     x_value = 0
     y_value = 0
     Gosub lcd_goto_xy
     
     'put the lcd into data mode
     High lcd_command
     
     'send 504 (48*84/8) blank pixels
     For i = 0 To 5
           For j = 0 To 83
                 byte_to_send = 0x00
                 If invert_display = 1 Then byte_to_send = byte_to_send Nand 0xff
                 Gosub lcd_write_byte
           Next j
     Next i
     
     'just give everything a sec or two
     WaitMs 10
     
Return

lcd_goto_xy:
     Low lcd_command
     byte_to_send = 0x80
     byte_to_send = byte_to_send Or x_value
     Gosub lcd_write_byte
     
     byte_to_send = 0x40
     byte_to_send = byte_to_send Or y_value
     Gosub lcd_write_byte
     High lcd_command
Return

lcd_get_character:

     'set the default character to zero
     char(0) = 0x00
     char(1) = 0x00
     char(2) = 0x00
     char(3) = 0x00
     char(4) = 0x00
     char(5) = 0x00
     char(6) = 0x00
     move_x = 0

     Select Case character_to_send
           Case "A"
           char(0) = 01111110b
           char(1) = 00001001b
           char(2) = 00001001b
           char(3) = 01111110b
           move_x = 5
           
           Case "B"
           char(0) = 01111111b
           char(1) = 01001001b
           char(2) = 01001001b
           char(3) = 00110110b
           move_x = 5

           Case "C"
           char(0) = 00011100b
           char(1) = 00100010b
           char(2) = 01000001b
           char(3) = 01000001b
           move_x = 5

           Case "D"
           char(0) = 01111111b
           char(1) = 01000001b
           char(2) = 01000001b
           char(3) = 00100010b
           char(4) = 00011100b
           move_x = 6
                       
           Case "E"
           char(0) = 01111111b
           char(1) = 01001001b
           char(2) = 01000001b
           move_x = 4
           
           Case "F"
           char(0) = 01111111b
           char(1) = 00001001b
           char(2) = 00001001b
           move_x = 4

           Case "G"
           char(0) = 00011100b
           char(1) = 00100010b
           char(2) = 01000001b
           char(3) = 01001001b
           char(4) = 01111010b
           move_x = 6

           Case "H"
           char(0) = 01111111b
           char(1) = 00001000b
           char(2) = 00001000b
           char(3) = 01111111b
           move_x = 5
           
           Case "I"
           char(0) = 01000001b
           char(1) = 01111111b
           char(2) = 01000001b
           move_x = 4

           Case "J"
           char(0) = 00100000b
           char(1) = 01000000b
           char(2) = 01000001b
           char(3) = 00100001b
           char(4) = 00011111b
           move_x = 6

           Case "K"
           char(0) = 01111111b
           char(1) = 00001000b
           char(2) = 00010100b
           char(3) = 01100011b
           move_x = 5

           Case "L"
           char(0) = 01111111b
           char(1) = 01000000b
           char(2) = 01000000b
           move_x = 4
           
           Case "M"
           char(0) = 01111111b
           char(1) = 00000001b
           char(2) = 00000010b
           char(3) = 00001100b
           char(4) = 00000010b
           char(5) = 00000001b
           char(6) = 01111111b
           move_x = 8

           Case "N"
           char(0) = 01111111b
           char(1) = 00000100b
           char(2) = 00001000b
           char(3) = 00010000b
           char(4) = 01111111b
           move_x = 6

           Case "O"
           char(0) = 00011100b
           char(1) = 00100010b
           char(2) = 01000001b
           char(3) = 01000001b
           char(4) = 00100010b
           char(5) = 00011100b
           move_x = 7

           Case "P"
           char(0) = 01111111b
           char(1) = 00010001b
           char(2) = 00010001b
           char(3) = 00001110b
           move_x = 5
           
           Case "Q"
           char(0) = 00011100b
           char(1) = 00100010b
           char(2) = 01000001b
           char(3) = 01010001b
           char(4) = 00100010b
           char(5) = 01011100b
           move_x = 7
           
           Case "R"
           char(0) = 01111111b
           char(1) = 00011001b
           char(2) = 00101001b
           char(3) = 01000110b
           move_x = 5

           Case "S"
           char(0) = 00100010b
           char(1) = 01000101b
           char(2) = 01001001b
           char(3) = 00110010b
           move_x = 5
           
           Case "T"
           char(0) = 00000001b
           char(1) = 00000001b
           char(2) = 01111111b
           char(3) = 00000001b
           char(4) = 00000001b
           move_x = 6
           
           Case "U"
           char(0) = 00111111b
           char(1) = 01000000b
           char(2) = 01000000b
           char(3) = 01111111b
           move_x = 5
           
           Case "V"
           char(0) = 00000011b
           char(1) = 00011100b
           char(2) = 01100000b
           char(3) = 00011100b
           char(4) = 00000011b
           move_x = 6
           
           Case "W"
           char(0) = 00111111b
           char(1) = 01000000b
           char(2) = 00100000b
           char(3) = 00011000b
           char(4) = 00100000b
           char(5) = 01000000b
           char(6) = 00111111b
           move_x = 8

           Case "X"
           char(0) = 01100011b
           char(1) = 00010100b
           char(2) = 00001000b
           char(3) = 00010100b
           char(4) = 01100011b
           move_x = 6
           
           Case "Y"
           char(0) = 00000011b
           char(1) = 00000100b
           char(2) = 01111100b
           char(3) = 00000111b
           move_x = 5
           
           Case "Z"
           char(0) = 01111001b
           char(1) = 01001001b
           char(2) = 01001101b
           char(3) = 01001011b
           move_x = 5
                       
           Case "a"
           char(0) = 00100000b
           char(1) = 01010100b
           char(2) = 01010100b
           char(3) = 01111000b
           move_x = 5

           Case "b"
           char(0) = 01111111b
           char(1) = 01000100b
           char(2) = 01000100b
           char(3) = 00111000b
           move_x = 5
           
           Case "c"
           char(0) = 00111000b
           char(1) = 01000100b
           char(2) = 01000100b
           char(3) = 01000100b
           move_x = 5
           
           Case "d"
           char(0) = 00111000b
           char(1) = 01000100b
           char(2) = 01000100b
           char(3) = 01111111b
           move_x = 5
           
           Case "e"
           char(0) = 00111000b
           char(1) = 01010100b
           char(2) = 01010100b
           char(3) = 01011100b
           move_x = 5
           
           Case "f"
           char(0) = 01111110b
           char(1) = 00001001b
           char(2) = 00000001b
           move_x = 4
           
           Case "g"
           char(0) = 10011000b
           char(1) = 10100100b
           char(2) = 10100100b
           char(3) = 01111100b
           move_x = 5
           
           Case "h"
           char(0) = 01111111b
           char(1) = 00000100b
           char(2) = 00000100b
           char(3) = 01111000b
           move_x = 5
           
           Case "i"
           char(0) = 01000100b
           char(1) = 01111101b
           char(2) = 01000000b
           move_x = 4
           
           Case "j"
           char(0) = 10000100b
           char(1) = 10000101b
           char(2) = 01111100b
           move_x = 4
           
           Case "k"
           char(0) = 01111111b
           char(1) = 00010000b
           char(2) = 00010000b
           char(3) = 01101100b
           move_x = 5
           
           Case "l"
           char(0) = 01000001b
           char(1) = 01111111b
           char(2) = 01000000b
           move_x = 4
           
           Case "m"
           char(0) = 01111100b
           char(1) = 00000100b
           char(2) = 00000100b
           char(3) = 01111000b
           char(4) = 00000100b
           char(5) = 00000100b
           char(6) = 01111000b
           move_x = 8
           
           Case "n"
           char(0) = 01111100b
           char(1) = 00000100b
           char(2) = 00000100b
           char(3) = 01111000b
           move_x = 5
           
           Case "o"
           char(0) = 00111000b
           char(1) = 01000100b
           char(2) = 01000100b
           char(3) = 00111000b
           move_x = 5
           
           Case "p"
           char(0) = 11111100b
           char(1) = 00100100b
           char(2) = 00100100b
           char(3) = 00011000b
           move_x = 5
           
           Case "q"
           char(0) = 00011000b
           char(1) = 00100100b
           char(2) = 00100100b
           char(3) = 11111100b
           move_x = 5
           
           Case "r"
           char(0) = 01111000b
           char(1) = 00000100b
           char(2) = 00000100b
           char(3) = 00000100b
           move_x = 5
           
           Case "s"
           char(0) = 01001000b
           char(1) = 01010100b
           char(2) = 01010100b
           char(3) = 01010100b
           char(4) = 00100000b
           move_x = 6
           
           Case "t"
           char(0) = 00111111b
           char(1) = 01000100b
           char(2) = 01000100b
           move_x = 4
           
           Case "u"
           char(0) = 00111100b
           char(1) = 01000000b
           char(2) = 01000000b
           char(3) = 01111100b
           move_x = 5
           
           Case "v"
           char(0) = 00011100b
           char(1) = 00100000b
           char(2) = 01000000b
           char(3) = 00100000b
           char(4) = 00011100b
           move_x = 6
           
           Case "w"
           char(0) = 00111100b
           char(1) = 01000000b
           char(2) = 00100000b
           char(3) = 00010000b
           char(4) = 00100000b
           char(5) = 01000000b
           char(6) = 00111100b
           move_x = 8
           
           Case "x"
           char(0) = 01101100b
           char(1) = 00010000b
           char(2) = 00010000b
           char(3) = 01101100b
           move_x = 5
           
           Case "y"
           char(0) = 10001100b
           char(1) = 10010000b
           char(2) = 10010000b
           char(3) = 01111100b
           move_x = 5
           
           Case "z"
           char(0) = 01100100b
           char(1) = 01010100b
           char(2) = 01001100b
           move_x = 4
           
           Case "0"
           char(0) = 00111110b
           char(1) = 01000001b
           char(2) = 01000001b
           char(3) = 00111110b
           move_x = 5
           
           Case "1", 1
           char(0) = 01000010b
           char(1) = 01111111b
           char(2) = 01000000b
           move_x = 4
           
           Case "2", 2
           char(0) = 01100010b
           char(1) = 01010001b
           char(2) = 01010001b
           char(3) = 01001110b
           move_x = 5
           
           Case "3", 3
           char(0) = 01000010b
           char(1) = 01001001b
           char(2) = 01001001b
           char(3) = 00110110b
           move_x = 5
           
           Case "4", 4
           char(0) = 00011111b
           char(1) = 00010000b
           char(2) = 00010000b
           char(3) = 01111100b
           char(4) = 00010000b
           move_x = 6
           
           Case "5", 5
           char(0) = 00100111b
           char(1) = 01001001b
           char(2) = 01001001b
           char(3) = 00110001b
           move_x = 5
           
           Case "6", 6
           char(0) = 00111110b
           char(1) = 01001001b
           char(2) = 01001001b
           char(3) = 00110010b
           move_x = 5
           
           Case "7", 7
           char(0) = 00000001b
           char(1) = 01110001b
           char(2) = 00011001b
           char(3) = 00000111b
           move_x = 5
           
           Case "8", 8
           char(0) = 00110110b
           char(1) = 01001001b
           char(2) = 01001001b
           char(3) = 00110110b
           move_x = 5
           
           Case "9", 9
           char(0) = 00100110b
           char(1) = 01001001b
           char(2) = 01001001b
           char(3) = 00111110b
           move_x = 5
                             
           Case "."
           char(0) = 01100000b
           char(1) = 01100000b
           move_x = 3
           
           Case ":"
           char(0) = 01100110b
           char(1) = 01100110b
           move_x = 3

           Case 32
           move_x = 4
           
           Case "{"
           Gosub draw_wifi
           
     EndSelect
     
Return

send_character_to_lcd:
     
     'put the lcd into data mode
     High lcd_command

     For i = 0 To 6
           byte_to_send = char(i)
           If invert_display = 1 Then byte_to_send = byte_to_send Nand 0xff
           Gosub lcd_write_byte
     Next i
           
Return

lcd_write_string:

     sk = Len(string_to_write) - 1
     For si = 0 To sk
     
           'convert the requested character into an array of bits to display
           character_to_send = string_to_write(si)
           Gosub lcd_get_character
           
           'check the character will fit on the screen
           k = x_value + move_x
           
           If k > 82 And wrap_text = 0 Then
                 'don't write anything more on this line
                 x_value = 84
           Else
                 If k > 82 Then
                       x_value = 2
                       y_value = y_value + 1
                       Gosub lcd_goto_xy
                 Endif
           
                 'send the character to the lcd
                 Gosub send_character_to_lcd

                 'update the "cursor x" position
                 x_value = x_value + move_x
                 Gosub lcd_goto_xy
           Endif
                 
     Next si
Return

draw_band:
     x_value = 0
     Gosub lcd_goto_xy
     
     'put the lcd into data mode
     High lcd_command

     byte_to_send = 11110000b
     If invert_display = 1 Then byte_to_send = byte_to_send Nand 0xff
     For i = 0 To 83
           Gosub lcd_write_byte
     Next i
     
     byte_to_send = 0xff
     If invert_display = 1 Then byte_to_send = byte_to_send Nand 0xff
     For i = 0 To 83
           Gosub lcd_write_byte
     Next i
     
     byte_to_send = 00000111b
     If invert_display = 1 Then byte_to_send = byte_to_send Nand 0xff
     For i = 0 To 83
           Gosub lcd_write_byte
     Next i
     
     
Return

draw_wifi:

     For i = 0 To 14
           Select Case i
                 Case 0, 14
                 byte_to_send = 00001000b
           
                 Case 1, 13
                 byte_to_send = 00000100b
           
                 Case 2, 12
                 byte_to_send = 00000010b
           
                 Case 3, 11
                 byte_to_send = 00010010b
           
                 Case 4, 10
                 byte_to_send = 00001001b
           
                 Case 5, 9
                 byte_to_send = 00100101b
           
                 Case 6, 8
                 byte_to_send = 10010101b
           
                 Case 7
                 byte_to_send = 11010101b
           EndSelect
           
           If invert_display = 1 Then byte_to_send = byte_to_send Nand 0xff
           Gosub lcd_write_byte
     Next i
     
     x_value = x_value + 16
     Gosub lcd_goto_xy

Return

We've written our font "table" as a select case statement.
When it come to being compiled down, a look up table and a select case statement are very similar anyway (and we just prefer using IF statements - just ask Steve!).

The font values are written in long-hand binary, rather than in decimal or their short-hand hex values. This makes no difference whatsoever to the final code (since the compiler treats all constant values the same, however they are written).

We stuck with long-hand binary, as it helps us to "see" the fonts and graphics inside the source code;


Drawing to the screen is as simple as sending an x and a y co-ordinate value (x is any number 0-83 and y is any "row" from 0 to 5, representing an actual co-ordinate value of 0, 8, 16, 32, 40 or 48). The next eight bits (single byte) are then used to draw "upwards" from the cursor position. If more than 8 bits are sent, the cursor position returns to the original y axis value, and the x-position shifts across one pixel.

There are also a couple of extras built into this example.
You can set the "inverted" flag on and off, to draw white text over a black background, or black text on a white background (the background colour should be fully drawn beforehand).
There's also a simple routine to draw a horizontal bar across the screen, to allow inverted text to be seen against a plain/blank background. An example of this in use would be a menu highlight system.

We also added a "text wrap" toggle, which determines - if the text is two large to fit onto one row - whether or not to break the work/string and draw it across two or more rows.

So far we've got upper and lower case letters, numbers and a few punctuation marks.
We're also using special characters to draw bitmaps at the cursor location (this isn't the correct use of the "library" - we were just keen to see how non-alphanumeric characters appeared!)

Heres' a photo of the screen in action:


We feel that the non-fixed width font just looks that little bit more "friendly" and less "hacky" and "homemade" than other library-based fonts.

The code above could easily be ported to another platform (such as Arduino).
We deliberately avoided using the hardware SPI peripheral, to allow it to be ported to any other microcontroller, without spoiling existing libraries or limit it to only those devices with a free hardware SPI module.

We really like these displays and - for a quid more than a 16x2 line character LCD display - we're looking at using these in a future wifi-based project (just as soon as our ESP8266 modules arrive!)