Silicon ChipPIC n’Mix - September 2021 SILICON CHIP
  1. Outer Front Cover
  2. Contents
  3. Subscriptions: PE Subscription
  4. Subscriptions: PicoLog Cloud
  5. Back Issues: PICOLOG
  6. Publisher's Letter
  7. Feature: The Fox Report by Barry Fox
  8. Feature: Techno Talk by Mark Nelson
  9. Feature: Net Work by Alan Winstanley
  10. Project: USB SUPERCODEC by Phil Prosser
  11. Project: USB Supercodec by Andrew Woodfield
  12. Project: High-power Ultrasonic Cleaner Part 1 by John Clarke
  13. Project: Night Keeper Lighthouse by Andrew Woodfield
  14. Feature: AUDIO OUT by Jake Rothman
  15. Feature: Max’s Cool Beans by Max the Magnificent
  16. Feature: Flowcode Graphia I Programming by Martin Whitlock
  17. Feature: PIC n’Mix by Mike Hibbett
  18. Feature: Practically Speaking by Jake Rothman
  19. Feature: Circuit Surgery by Ian Bell
  20. PCB Order Form
  21. Advertising Index

This is only a preview of the September 2021 issue of Practical Electronics.

You can view 0 of the 72 pages in the full issue.

Articles in this series:
  • (November 2020)
  • Techno Talk (December 2020)
  • Techno Talk (January 2021)
  • Techno Talk (February 2021)
  • Techno Talk (March 2021)
  • Techno Talk (April 2021)
  • Techno Talk (May 2021)
  • Techno Talk (June 2021)
  • Techno Talk (July 2021)
  • Techno Talk (August 2021)
  • Techno Talk (September 2021)
  • Techno Talk (October 2021)
  • Techno Talk (November 2021)
  • Techno Talk (December 2021)
  • Communing with nature (January 2022)
  • Should we be worried? (February 2022)
  • How resilient is your lifeline? (March 2022)
  • Go eco, get ethical! (April 2022)
  • From nano to bio (May 2022)
  • Positivity follows the gloom (June 2022)
  • Mixed menu (July 2022)
  • Time for a total rethink? (August 2022)
  • What’s in a name? (September 2022)
  • Forget leaves on the line! (October 2022)
  • Giant Boost for Batteries (December 2022)
  • Raudive Voices Revisited (January 2023)
  • A thousand words (February 2023)
  • It’s handover time (March 2023)
  • AI, Robots, Horticulture and Agriculture (April 2023)
  • Prophecy can be perplexing (May 2023)
  • Technology comes in different shapes and sizes (June 2023)
  • AI and robots – what could possibly go wrong? (July 2023)
  • How long until we’re all out of work? (August 2023)
  • We both have truths, are mine the same as yours? (September 2023)
  • Holy Spheres, Batman! (October 2023)
  • Where’s my pneumatic car? (November 2023)
  • Good grief! (December 2023)
  • Cheeky chiplets (January 2024)
  • Cheeky chiplets (February 2024)
  • The Wibbly-Wobbly World of Quantum (March 2024)
  • Techno Talk - Wait! What? Really? (April 2024)
  • Techno Talk - One step closer to a dystopian abyss? (May 2024)
  • Techno Talk - Program that! (June 2024)
  • Techno Talk (July 2024)
  • Techno Talk - That makes so much sense! (August 2024)
  • Techno Talk - I don’t want to be a Norbert... (September 2024)
  • Techno Talk - Sticking the landing (October 2024)
  • Techno Talk (November 2024)
  • Techno Talk (December 2024)
  • Techno Talk (January 2025)
  • Techno Talk (February 2025)
  • Techno Talk (March 2025)
  • Techno Talk (April 2025)
  • Techno Talk (May 2025)
  • Techno Talk (June 2025)
PIC n’Mix Mike Hibbett’s column for PIC project enlightenment and related topics Part 7: PIC18F Development Board T his month, we expand on the voltage monitoring capability we developed previously by logging the data to a file on a standard Micro-SD card, so we can download the data later to a PC. As our development board is already equipped with the necessary hardware, this month is going to be a software discussion. Bringing file storage capability into a small, embedded microprocessor project is a big task, even with the support of the Microchip Code Composer utility, so it’s worth taking some time over this. The hardware interface, shown in Fig.1, is very simple electronically – it’s just regular SPI (Serial Peripheral Interface). There are additional functions available on card sockets, such as a pin that signals when a card is inserted and a write-protect signal, but for an application such as ours, where the card will always be connected and inside an enclosure, it is perfectly acceptable and common to ignore those extras and stick with standard four-wire SPI. To make life even more interesting we are going to add one further feature into the mix this month – including a time stamp with the sample data as it is written to a file on the card. For data logging applications this is an essential feature, because knowing when a change in a sensed value occurs is often just as important as knowing what that sensed value is. Tracking time There are several relatively cheap ICs available that are specifically designed PIC n’ Mix PIC18F Development Board The PCB for the PIC18F Development Board is available from the PE PCB Service – see the July 2021 section. www.electronpublishing.com/ product-category/pe-pcb-service/ 50 to store and maintain a clock, with communication over I2C or SPI. These ICs are designed to operate with a small battery which will keep the clock running when the system power is turned off. Your PC, laptop and mobile phone have one of these devices (in PCs and laptops, the battery is typically a replaceable coin cell.) If you are happy to set the time and date when the system is powered on each time (we are, in this project) then that cost can be avoided as we have everything required in our existing hardware. The processor contains a number of timer peripherals which can be configured to increment on every clock cycle (or on a division of the input clock signal) and generate an interrupt when a particular count (say, one second’s worth of clocks) is reached. That interrupt could call an interrupt routine that updates a clock timekeeping variable by 1s. The timer peripheral can be configured to automatically restart; counting up from zero and giving you an accurate clock that is updated automatically without interfering with your main application. For a clock to be useful, it must be reasonably accurate. Our processor operates on a clock signal generated internally, based on a resistor and capacitor to provide the reference frequency. This clock signal is calibrated during IC manufacture to 2% accuracy. That’s perfectly fine for running code and communicating with other devices over serial interfaces, but it would be terrible for maintaining a realtime clock – it could lose 30 minutes each day. This is why we have a crystal fitted to our board, which oscillates accurately at 32.768kHz (but hereafter referred to as the ‘32kHz’ crystal). The crystal is wired to two pins that connect internally to the secondary oscillator peripheral. This is designed to work with 32kHz crystals, a type of crystal that can run with very low power consumption. It’s also such a low frequency that we can configure a timer to interrupt once every second, which will make our interrupt function nice and simple. What’s the date? So, we now know how we will produce an accurate, one-second timer. Next we need to decide the format we will use to store the timestamp. A naive choice would be in a ‘human-understandable format’, for example: 02/07/2021 11:25. That format is easy to read by both humans and computers but has several problems. First, is that date in Day/Month or Month/Day format? Is the time in 12hour or 24-hour format? If 12-hour format, what is the pm indicator going to be? You can define these settings in advance of course, but it is not trivial to update this information in software – you need to allow differing number of days in each month, and for leap years. These problems, however, are nothing compared to the main issue. What time zone is the device operating in? Also, daylight saving time, when the clock moves forward or backward by an hour, depends on your particular time zone. Or rather, the time zone your device runs in. This can quickly become extremely complicated to manage, for what should be a relatively simple problem. Thankfully, this problem has been considered before, and a simple solution proposed – ‘Unix Time’. This is a simple 32-bit positive integer, which marks the number of seconds since 1 January 1970, UTC. By storing and maintaining a Unix Time variable, we only need to increment it by one every second, using the very simple algorithm we discussed earlier. The interpretation of what that value means can then be left to whatever application the user is using to display or process the data. This means, instead of (V S S ) G N D V D D V D D D A T 2 D I IR Q /D A T 1 D O G N D (V S S ) S C L K C S C a r d D e t e ct Fig.1. Micro-SD card connector pinout. Practical Electronics | September | 2021 H a rd w a re M C C a u to -g e n e ra te d c o d e C o d e w e w r ite (perhaps to send data over a Bluetooth link) A p p l i ca t i o n co d e without the application code requiring any changes. U A R T F i l e syt e m W i- F i T im e r A D C ‘Layering’ is the d r i ve r d r i ve r d r i ve r d r i ve r d r i ve r technique of creating drivers at different deS D -C a rd U A R T grees of abstraction d r i ve r d r i ve r from the underlying hardware. When we S P I draw this as a diad r i ve r gram, layers that are closer to the hardware M C P 2 2 2 1 A M i cr o - S D E S P -0 1 O p a m p are at the bottom, with U S B U A R T IC ca r d m o d u le b u ffe r the level of abstraction increasing as we move up. The final Fig.2. Software architecture, showing how the program is made up in layers. Thanks to MCC, we only need to write one application code appearing at the top. layer of software. The MCC tool creates drivers for us, so it makes sense to writing ‘25/06/2021 18:25’ to a data file, draw out a design for our project based we would write ‘1624641898’. on this approach, as shown in Fig.2. Essentially, we push the problem to The simplest driver shown here is the system displaying the datafile conthe Timer. The driver code contains all tent, and there is a very good chance the configuration settings and functions that system would be a PC, connected to to interact with the timer and gives us the Internet, and able to make sense of two very simple functions to use – one the time zone it inhabits, and adjust the to start the timer running, and one to data it is receiving as appropriate to the handle the periodic interrupt. Neither time zone settings the user has specified. of these functions require us to look at This makes our life much easier. the datasheet of the processor to underIf you would like to convert a value stand how to use them. from Unix Time to local time, there are A more complex example is how we online tools to help, for example: www. access files on the Micro-SD card. The unixtimestamp.com file system driver will provide functions In summary, we will store a timestamp such as ‘open file’, ‘write to file’ and ‘read for each data sample recorded, using Unix from file’. The file system driver knows Time. Initially, when our device starts nothing about how those files are stored up we will set this time to 0, but in the on the card; it uses the SD-Card driver to next article we will provide an interface manage that. Likewise, the SD-Card driver for setting the true current time value. does not know how to communicate with the card via the physical interface; it uses Software architecture an SPI driver to do that for us. As we move forward with the software for There are several benefits to this this application, we start to move away approach. First, most of these (quite from a simple design to something with complicated) drivers are created for us quite a complex organisation. This is a by the MCC tool. Second, if we were to good time to talk about how we segregate change, say, to a USB-based memory stick, program functions in a way that simplithe file system driver and our application fies the design of the overall system: by code would not change – only the lower breaking the design down into smaller layer driver would need to be replaced. chunks. We do this using two concepts: By the time we finish our application software drivers, and software layering. this month you will see the benefits. The A software driver is a piece of software application code we write will be simple, that performs a specific job. It is typically and easy to read. a set of variables and functions that proThe most complex of the drivers we will vide some kind of service. One example be using is the file system driver, called is a serial port driver. This contains the ‘FatFs’. This allows us to create, read and code to configure the UART port and write to files in a format that can be read provides a character transmit and charby a computer. FatFs is actually a thirdacter receive function. The application party library, created and maintained as can then call these functions to send a hobby project by an engineer in Japan. data over the UART port without requirIt’s been a freely available library for over ing the application code to ‘know’ that 15 years and has been very professionally a UART is being used. When properly maintained by the owner over this time. written, the driver code could be reWith the latest updates made just a few moved and replaced with another driver Practical Electronics | September | 2021 months ago, it’s clearly a popular, valuable and effective driver. Microchip have integrated it into the MCC tool which saves us having to ‘integrate’ it ourselves – so many thanks to both parties for making our lives easy! If you are interested in viewing the history of the development, you can find the author’s website here: http://elm-chan.org/fsw/ff/00index_e.html Adding the drivers Before making any updates to our application code, we need to add the drivers into our project. We start by opening up the project files from the previous article. These can be found on the September 2021 page of the PE website in a single zip file (named picnmix-sept-beginning. zip). Unzip the file into a directory of your choice. The top-level directory within the zip file is: monitor.X Now open MPLAB, click on ‘Open Project...’ and navigate to the directory containing the monitor.X directory you just created. You will see it shown with an IC icon next to it in the file listing, indicating that MPLAB recognises it as an MPLAB project. Double click on monitor.X to open the project. Next, click on the MCC icon on the top menu bar, which should bring up the ‘Project Resources’ and ‘Device Resources’ windows on the left-hand side, as shown in Fig.3. The Project Resources window shows the features already included in our Fig.3. MCC Resources windows. 51 add the following line of code just before the TMR1_StartTimer() call: TMR1_SetInterruptHandler(timer1_ callback); Fig.4. TMR1 configuration window. project (we did that in the previous article), and Device Resources also lists those additional drivers available for us to add. Let’s start with the simplest driver – we want to add a timer, which we will use to maintain a ‘Unix Time’ clock. Scroll down the Device Resources list to the ‘Timer’ drop down entry, expand it (by clicking on the triangle icon) and click the ‘+’ icon nest to ‘TMR1’. Change the ‘Clock Source’, ‘Timer Period’, ‘Enable Timer Interrupt’ and ‘Callback Function Rate’ as shown in Fig.4. These changes configure TMR1 to use the secondary oscillator inside the processor which is connected to the 32kHz crystal. Then click the ‘Generate’ button at the top of the Project Resources window. Notice that we have enabled interrupt operation – we have chosen to enable the timer interrupt to allow the timer to run in the background so that our main application does not need to handle looking for an actual timeout event once a second. We will instead use the ‘callback function’, which is a function we write that will be called by the timer code once every second in the interrupt handler. All of which is managed for us by MCC. Again, simplifying our interaction with the timer specifics. The generation process creates a number of new files and adds these into our project directory. The interesting ones for us in this case are the two files tmr1.c and tmr1.h tmr1.c holds the source code for the peripheral’s configuration, which we do not need to look at. tmr1.h contains the ‘API’, the list of functions that we can make use of in our application. Looking through this file, we can find the important functions for us for this project. The first interesting function is TMR1_Initialize(), which sets the 52 peripheral’s control registers. This is important, but is called for us automatically by the SYSTEM_Initialize() function already present in our main.c file. That function was updated automatically when we clicked the Generate button. Next up is TMR1_StartTimer() – we call that to start the timer running. We can do this at any time after the call to SYSTEM_Initialize(), so we copy it into our main.c directly after that line of code. While in main.c we have to uncomment this line: //INTERRUPT_GlobalInterruptEnable(); Doing this allows the timer interrupts to be actioned by the processor. F i n a l l y, b a c k i n t m r 1 . h , w e find an important function: TMR1_SetInterruptHandler() It is not immediately obvious, but this is the function we call to assign our timer callback function. Let’s head back to main.c and implement that function. First, we need a global variable that will be our Unix Time counter. We create that just above the main() function. Then we write a simple callback function – this just increments that Unix Time variable by one each time it is called. The code looks like this: uint32_t unixtime = 0; void timer1_callback(void) { unixtime = unixtime + 1; } Note, the name of the function can be whatever you want. Finally, TMR1_SetInterruptHandler() is the function used to assign our callback function to the timer driver. To do this we That’s it – if we run the code in debug mode on our development board, we should see the Unix Time counter incrementing when we pause the program. Now we get to the slightly more challenging part, adding support for the Micro-SD card. We start by adding the ‘SD Card (SPI)’ driver from the Libraries section of the Device Resources window. This automatically brings in the SPI1 driver at the same time. Next, add in the FatFs driver. Now, we are ready to configure these drivers. We will start at the lowest layer, the SPI1 driver. Click on the SPI1 driver in the Project Resources window; then, in the SPI1 driver details window that appears, we change the ‘Clock Divider’ from 0 to 64. This reduces the SPI clock frequency down to 123kHz. Why? That’s because Micro-SD cards must, on their initial configuration following power on, be run at a very slow speed, presumably for compatibility reasons. We can increase the speed of access later. Right now, it’s not clear how we would do that within the software, so we will leave it as is and move on. In the SD Card (SPI) driver window, uncheck ‘Enable Card Detect (CD)’ and ‘Enable Write Protect (WP)’, as these are features, we do not need in our application. We must now make sure that the correct processor pins are assigned to our SPI interface. Open the ‘Pin Module’ from the Project Resources window by double-clicking on the name, and then select the ‘Pin Manager: Grid View’ in the lower window. Notice how some pins have already been allocated for the SPI1 module. Click on the padlock symbols to select the correct pins as defined by our circuit. Then, in the ‘Pin Module’ window, click ‘Start High’ for the SDCard_CS signal (it’s an active low), and also unclick the ‘Analog’ checkbox associated with RC3. Your settings should look like Fig.5. Now we can turn our attention to the file system driver. Click on the FatFs label in the Project Resources window, then in the FatFs window that appears, click on the ‘Configuration’ tab. The list that appears is long, so we will concentrate only on the features that we will change from their defaults. We start by leaving the ‘Generate example/demo files’ checkbox set, as these files may serve as a useful reference later. Next, we must tell FatFs what SD-Card driver it should use to communicate with the physical card. You could – in theory – have several Practical Electronics | September | 2021 Fig.5. Mapping pins for the Micro-SD card interface. cards attached at one time. We only have the one driver, listed as ‘SD Card (SPI)’, so just click the ‘+ Insert Driver’ button to link that to the FatFs driver. Now we get to enable or disable a variety of FatFs functionalities. This capability has been provided to help us minimise the amount of code and RAM used by the driver; it can get quite high if all features are enabled as it is a very comprehensive driver. Microchip have provided default settings that allow the driver to be used with PIC18F processors, but as we have a fairly large device on our board, we will enable some extra functions, just to be able to experiment more with the files written to the card. Let’s start by changing ‘Function optimization’ from ‘3’ to ‘0’, enabling more features in general. Then change ‘String function optimization’ from ‘0’ to ‘1’, which will allow us to write text to the file. Click on the ‘Enable find/search functions’, as these enable us to perform directory listings. Practical Electronics | September | 2021 We will leave the remaining configuration settings at their defaults. This means we can only use simple filenames and are limited to a single file open at any one time – a reasonable setup for us, since having multiple files open at any one time can consume RAM quickly. We can now click the ‘Generate’ button to have MCC create the source files for us. Performing a code build now should result in a successful build, but with dozens of warnings – these are all in the FatFs code itself (and hence of no concern to us) or simply warnings about code that has been generated, yet not used. So, let’s go ahead and use some of those functions to write to a card! The functions that we can use to access the card (referred to as the driver’s ‘API’, or application programming interface) are listed in the file ff.h. This is quite a complicated file with limited comments, so we look instead to one of the demonstration files provided: fatfs_demo.c You can find this file by exploring the list of project files in the ‘Files’ tab in the left-hand window of MPLAB-X. From this, we can see that accessing the card requires the following steps; we call: f_mount() to initialise access to the card f_open() to open or create a file on the card f_write() one or more times to write to the file f_close() to commit any unwritten data to the card Note that f_close() is very important. Data is written to the card in blocks to allow for efficient data transfer. If you wrote say, a single byte, that would be stored in RAM until more data is written. f_close() flushes any unwritten data to the file, and makes sure that the file system is updated correctly. You can see how we integrated these calls to write our sensor data to a file named a.txt in Listing 1. Having typed in this code, we attempted to build the project. Surprisingly, this resulted in an error message: ff.c:6197:: error: (1089) recursive function call to "_putc_bfd" 53 This error is emitted by the compiler, so let’s take a look at the offending lines of code: if (FF_USE_STRFUNC == 2 && c == ‘\n’) {/* LF -> CRLF conversion */ putc_bfd(pb, ‘\r’); } We can see that this block of code should never be called, since FF_USE_STRFUNC is set to 1, not 2. We were unable to find an answer on the Internet as to why this issue occurs, so we simply commented out the code. On re-building the project, we end up with a successful build with a 30% utilisation of code and memory – that’s perfectly fine, and plenty of space remaining to allow us to add extra features. This kind of issue probably arose because there are so many different configuration options for FatFs, we may have selected an unusual combination. No matter, the issue appears to have been easy to correct. Before we test the code, we must prepare a Micro-SD card by formatting it on a PC. This does two things – it removes old files from the drive, freeing up space (make sure you take a copy of any valuables files first!) and it also allows you to specify the type of file system that will Listing 1: Our updated application code. // Mount the SD Card, and do it immediately fr = f_mount(&fs, "", 1); // stop here if there was an issue mounting the drive if (fr != FR_OK) { printf("*** Failed to mount disk\r\n"); do {} while (1); } while (1) { fr = f_open(&fil, "a.txt", FA_OPEN_APPEND | FA_READ | FA_WRITE); if (fr != FR_OK) { printf("*** Retrying file open\r\n"); fr = f_open(&fil, "a.txt", FA_OPEN_APPEND | FA_READ | FA_WRITE); if (fr != FR_OK) { printf("*** Failed to open file\r\n"); do {} while (1); } } ADCC_StartConversion(channel_ANA0); while(!ADCC_IsConversionDone()); solarPanel_volts_adc = ADCC_GetConversionResult(); solarPanel_volts = (double)solarPanel_volts_adc; // Convert ADC value to volts, compensate for divide by 2 solarPanel_volts = (solarPanel_volts * 3.3 * 2.0) / 4095.0; ADCC_StartConversion(channel_ANA5); while(!ADCC_IsConversionDone()); battery_volts_adc = ADCC_GetConversionResult(); battery_volts = (double)battery_volts_adc; // Convert ADC value to volts, compensate for divide by 2 battery_volts = (battery_volts * 3.3 * 2.0) / 4095.0; // Print the values over the serial port sprintf(str, "Time: %ld: Battery voltage: %1.2f, Solar voltage: %1.2f\r\n",unixtime,battery_volts, solarPanel_volts); printf(str); bytes_written = f_puts(str, &fil); if ((bytes_written < 45) || (bytes_written > 99)) { printf("*** Failed to write to file correct number of bytes.\r\n"); do {} while (1); } __delay_ms(500); f_close(&fil); __delay_ms(500); } 54 be on the disk. There are typically three options to choose from – FAT32, NTFS or exFAT. Ensure you select FAT32 – the other two are not supported with the configuration of FatFs that we are using. The file system is a specification for how files are stored on the card, and how a program accessing the card can locate those files. Different file systems have different benefits, but for use with an embedded system design, FAT32 is perfectly acceptable – you will be hard pressed to find any limitations (for example, file sizes are ‘limited’ to a maximum of 4GB.) Eject the card from your PC and insert it into the development board socket. Start the code running, allow it to continue for a few minutes, then stop the code, remove the card and re-insert it into your PC card reader. You should find the file a.txt, which when opened with a text editor such as notepad, should reveal the written data. Wrapping up By integrating the wonderful FatFs library into MCC, Microchip have made bringing access to huge amounts of cheap persistent data storage a breeze. MicroSD cards are not only great for storing data, but also for storing configuration information, readable data sets (like graphics or sound) and even firmware updates. The full source code for this month’s article – both the starting point and the final functional code – is available for download from the September 2021 page of the PE website. We have now reached a stage where (in theory) the software is functional enough to perform the task we originally set out to do – monitor a solar-powered device’s power supply rails. There are, however, some enhancements that will not only make the hardware easier to work with but will also allow us to demonstrate the next major piece of functionality available on our development board – Wi-Fi. Coming up next In our next article we will look to add in the Wi-Fi interface so we can read the data file from the device remotely. We’ll also add some user interface features, implemented to be accessed over the serial interface. We will also use the Micro-SD card to store a text file containing our Wi-Fi credentials, rather than hard code them into the program. At that point we should be done and can move the circuit from the development board to a small circuit and put it to practical use. PIC n’ Mix files The programming files discussed in this article are available for download from the PE website. Practical Electronics | September | 2021