Quantcast
Channel: Embedded Lab
Viewing all 713 articles
Browse latest View live

Arduino Pocket Tetris

$
0
0

A portable pocket Tetris project using ATTiny85 and SSD1306 based OLED screen by dombeef.

Pocket Tetris project

Pocket Tetris project

This project originally was meant as a fathers day gift for my tetris-loving father, and I felt like it should be available for others if they want to make a pretty small tetris clone. This was the smallest I could make it with a big enough battery and a thick durable 3d printed housing.

I modified tetris code created by Andy Jackson to be used with 3 buttons, since his original code was made for the AttinyArcade platform that has 2 buttons.

The post Arduino Pocket Tetris appeared first on Embedded Lab.


DIY PICKit 3 programmer/debugger

$
0
0

Here’s a detail description from Reviahh on a DIY version of the PICKit 3 programmer/debugger. It uses all SMT components and is portable in size.

DIY PICKit 3

DIY PICKit 3

This schematic is very similar to the one Hendrik used, with a couple component changes and a fix for a PNP transistor that was shown backwards on his schematic. I’ll briefly talk about the different sections that I have labeled above. First – in the upper left corner is the pic24 processor that controls this device. It is a PIC24FJ256GB106 mcu. There are the requisite capacitors and 12MHz crystal attached, as well as a programming header to load its firmware. In addition to these components, the USB connector is shown, as well as the status LEDs and OTG Button connections. Directly below the MCU is the MCP1727 voltage regulator. At the bottom is a LTC4411, a MAX893L, and associated circuitry that among a couple other things, controls the power to the programming target, if it is not self-powered, and this device is supplying power to it. Above that is a MCP601 op-amp and voltage boosting circuitry. In the middle of the page is a MCP1525 voltage reference chip and the Target programming header. Top center you will see the three 74LVC1T45 voltage level shifters, and to the right are the 25LC256 EEPROM and also the SST25VF040B serial flash chip used for the Code image when doing OTG programming.

 

The post DIY PICKit 3 programmer/debugger appeared first on Embedded Lab.

An old radio turned into RPi powered internet radio

$
0
0

Marcel describes in this Instructable how he gave a new life to his old radio by powering it with an Raspberry Pi that streams various radio channels over the internet. He kept the functionality of the original tuning knob for tuning to radio channels. The RPi speaks up the name of the tuned channel in English or in your own language, as configured. The radio also features a safe-power-off button for proper shutdown of the RPi. He wrote the software for the radio in Python that runs automatically upon boot.

RPi powered internet radio

RPi powered internet radio

The post An old radio turned into RPi powered internet radio appeared first on Embedded Lab.

Arduino midi controller

$
0
0

An Arduino powered midi controller project for music makers.

This my first arduino (microcontroller) project. I want to learn arduino with a usefull and large project.

I decided to make a midi DJ controller that have all the functions needed to be standalone to mix.

Every kind of sensor (potentiometer, push button,…) can be learn independantly and I think the best way is to learn “how it works” and “how it is connected” for each sensor step by step.

Midi controller using Arduino

Midi controller using Arduino

The post Arduino midi controller appeared first on Embedded Lab.

Arduino powered weather station

$
0
0

This Instructables describes how to build a complete WiFi enabled weather station using Arduino and ESP8266 along with a bunch of sensors that collect environment data like temperature, humidity, wind speed, pressure, UV level, and rain.

Arduino powered WiFi weather station

Arduino powered WiFi weather station

The location weather station is the most important part of installation. If weather station is located under a tree or an overhang, the rainfall data measured by the station will not be correct. If you place your weather station in an alley, you could very well get a wind tunnel effect on the anemometer, resulting in erroneous wind data. Weather station should have good “fetch”, or distance from any other tall object.

 

The post Arduino powered weather station appeared first on Embedded Lab.

Introducing TI MSP430 Microcontrollers

$
0
0

Texas Instruments (TI) is a well-known US-based semiconductor manufacturer. TI is perhaps best known to many as the manufacturer of some of the fanciest scientific calculators in the market. Of the long list of electronic devices produced by TI, microcontrollers are on the top. TI manufactures some of the coolest and advanced microcontrollers of the market today. There are several categories of micros from TI. These include general purpose low power MCUs which mainly comprise of MSP430s, ARMs like TM4Cs, MSP432s, etc, micros for wireless communications like CC2xxx series, ARM + DSP micros, DSP-specialized micros like the TMS32xxx series and so on. It will look as if TI is committed toward mixed signal microcontrollers that are engineered for highly sophisticated industrial challenges. This issue will cover an insight of value-line MSP430 general purpose micros.

MSP430 Launchpad Board

MSP430s are not seen as much as the popular 8051s, PICs and AVRs. In most of the Asian market, for example, MSP430s are rare when compared to other microcontrollers and even still rare when compared to other chips produced by TI itself. I don’t know why there is such an imbalance. Perhaps one big reason is its inclination towards low power consumption and limited resources. Low-power means that these MCUs are crafted for special low power applications unlike most other micros. Secondly TI micros are a bit expensive than other micros. Despites these, TI has provided some great tools for making things simple. You can get your hands on some cool MSP430 chips through some affordable Launchpad boards and still it worth every penny learning MSP430s. Firstly, it is a family of ultra-low power high performance 16-bit (16-bit data bus) micros which are unlike the popular 8-bit platforms. Secondly MSP430s have highly rich internal hardware peripherals that are second to none. For instance, MSP430s can be operated over a wide voltage and frequency ranges. Another great feature that is less common in most 8-bit micros is the DMA controller. Fortunately, MSP430s possess this. Probably it is your first such micro family that is somewhere between 8-bit and 32-bit micros. In the end, MSP430s will surely give you a taste of absolute American technology and concepts.

The MSP430 Family

Shown below is the family tree of MSP430 series microcontrollers from TI.

TI MSP430 Family

The most common MSP430 micros are the MSP430FR series, MSP430F series, MSP430G series and the newly introduced MSP432 series.

MSP430FR series micros mainly feature high reliability, high endurance, 10-year data retention non-volatile FRAM (ferroelectric random-access memory) memories. This series offer 16-bit solutions for ultra-low-power sensing and system management in areas like smart building management systems, smart grids, military and industrial designs. They feature the lowest standby power consumption of about 350 nA with RTC, 100 µA/MHz active power consumption and the unique ability to save and instantly restore system state right after power failures.

MSP432 series micros are the perfect combinations of MSP430 low-power portfolio with advanced mixed-signal features and the high-performance processing capabilities of 32-bit ARM M4F engine. These micros have high measurement precisions and contain high performance peripherals like high resolution differential ADCs, DMA, IOT connectivity, etc. This series fills the gap between 16-bit MSP430s and 32-bit ARM architecture and as of this moment this series is the most recent development in the MSP430 family.

CC430 series is a small series of MSP430s with almost all features one can find in a typical MSP430 micro and embedded sub-GHz radio transceivers. They are well-stuffed true System-on-Chip (SOC) solutions that remove the necessity of additional off-board wireless solutions.

Yet another tiny series of MSP430 micros include MSP430Cxx and MSP430Lxx micros. These micros are intended for extreme low voltage (0.9 – 1.65V) operations. With just one Nickel Cadmium (NiCd) battery it is possible to run any micro of this series. They feature low resolution analogue frontends and low pin counts.

The only series that is left to be discussed is the general purpose MSP430s series microcontrollers. By general purpose, it is meant that these micros can be use in almost any scenario. It is this series of micros that we will be dealing here. The table below summarizes this family:

General Purpose MSP430s

For now, don’t struggle to understand the abbreviations of this table. You’ll eventually know about them as we proceed.

MSP430Fxxx and MSP430Gxxx

Note that there are some other MSP430 devices that are not accounted here. These devices are either rare or subset of the mentioned families. If you want to know more about MSP430 microcontrollers then you can go through the following Wikipedia article: https://en.wikipedia.org/wiki/TI_MSP430.

Launchpad Boards and BoosterPacks

Launchpad boards are affordable MSP430 evaluation kits. For just few dollars, you can have your own Launchpad boards. They are so cheap that an average school-going student can afford one with his/her own pocket money. However, this cheapness doesn’t compromise quality nor performance. They are more-or-less alike Arduino boards in terms of board size and on-board resources except they don’t share same pin naming conventions and board layouts. Well that’s not a big issue. However, it would have been better if the Launchpads shared Arduino-like form factor. This would have enabled using Arduino shields with Launchpads. TI has, however, its own brand of shields called BoosterPacks and they seem to like promoting their own idea, owing to which there’s still no official TI Launchpad that share exactly Arduino form-factor/shape. It is a very aggressive marketing boldness. Shown below is the BOOSTXL-EDUMKII BoosterPack. It is just like Arduino Esplora with lots of on-board sensors and devices except for the MCU. It is good for game development and sensor applications. There are other useful BoosterPacks dedicated for capacitive touch, displays and so on.

BoosterPack

Launchpad boards are not the only development boards offered by TI. There are many other dev boards too. Shown below is the TI MSP-EXP430F5438 Bluetooth platform development board.

MSP430 Bluetooth Development Platform

However due to cheapness and relatively good availability Launchpad boards are by far most popular, particularly the MSP-EXP430G2 Experimenter’s Launchpad board.

Launchpad Box

This board comes boxed with two micros – MSP430G2452 and MSP430G2553. Both of these mixed-signal MCUs come in PDIP packages. We can take them off from the board and use them in bread boards, strip boards, PCBs, etc. Shown below is the layout of a MSP-EXP430G2 Launchpad board.

Launchpad Board Layout

Just like any other evaluation kit, every Launchpad comes embedded with user LEDs, buttons, I/O port headers (also known as BoosterPack connectors), onboard power supply, USB-to-serial converter and programming interfaces.

MSP430s are ultra-low power 16-bit general purpose microcontrollers. A MSP430 micro consists of a 16-bit RISC CPU, wide variety of feature-rich peripherals and a flexible clock system all under the hood of a von-Neumann architecture. Because of their ultra-low energy consumption profile, MSP430s are well-suited for battery/solar powered/limited or renewable energy applications.

MSP430 Internal Architecture

MSP430G2452 and MSP430G2553 are both almost identical in terms of hardware peripherals and pin count. MSP430G2553 has some additional hardware features like more RAM-ROM memories, timers and USCI-based hardware interfaces for LIN and IrDA communications.

MSP430 Value-Line Chips

Shown below is a comparison table of some common MSP430 chips usually found with Launchpad boards.

Comparison of Various MSP430 Value Line Devices

Other Launchpad boards contain other chips but fortunately they share the same pin layout and so they are fully pin compatible. The fourteen pin parts and twenty pin parts share the same pin layout as shown below:

MSP430 Pin Compatibility

Though the pin names are labelled properly on the silkscreens of the boards, it is sometimes necessary to check the schematic for details. Shown below is the basic schematic of a MSP-EXP430G2 Launchpad.

MSP430G2 Launchpad Basic Schematic

From the schematic, we can see what has been placed on the board by default. Parts labelled DNP are not placed on a fresh Launchpad board. These have been left for the users.

If these don’t matter much and you need something simpler, then there are Arduino-like pin maps for Launchpads. Though they are made for Energia IDE, they are useful for quick overviews.

MSP430G2553 Pinmap

MSP430G2452 Pinmap

Hardware

We will obviously need a MSP430 Launchpad board. For this tutorial, I used MSP-EXP430G2 Launchpad board with MSP430G2553 and MSP430G2452 microcontrollers.

VLD Launchpad Board

Apart from the Launchpad board, we will need basic tools like a digital multimeter (DMM), wires or jumper cables, a power bank and other stuffs typically available in an Arduino starter kit like the RIASpire one shown below.

RIAspire Arduino Starter Kit

An additional external or off board programmer/debugger is not needed since MSP430 Launchpad boards come with on board Spy-Bi-Wire (SBW) programming/debugging interface. This interface utilizes pins labelled TEST and RST apart from power pins and so only four wires are needed. Note reset pin should be externally pulled up. Check the diagram below.

Similarly, we don’t need to buy/use any external USB-serial converter for serial communication as the boards come with onboard hardware for this communication.

Spy-Bi-Wire Interface

In all Launchpad boards, there are headers with jumpers as shown below to separate onboard programmer from the target. This allows us to use a Launchpad board as standalone programmer. We can also detach it. The top dotted line marks the border between the target and the programmer.

SWI

It is still nice to have one external MSP-FET programmer. FET stands for Flash Emulation Tool and it supports both JTAG and SWD interfaces. FETs are pretty expensive tools.

MSP-FET Programmer

Lastly, I strongly recommend having an oscilloscope or a logic analyser for checking signals and timing-related info. In many cases, informations provided by these tools become extremely necessary.

Logic Analyzer

Analyzer and Oscilloscope

Software

There are numerous ways of learning and using a new microcontroller family effectively. A number of C/C++ compilers are available for coding MSP430 micros. Hobbyists and novice users find Arduino-like solutions easy and quick but from an engineer’s perspective such solutions are inexplicably incapable of extracting the sweet fruits of a well-armed microcontroller. Rawer approaches are preferred by professionals but they too seek reduced efforts and quick solutions. The learning curve is also needed to be a smooth one. In this segment, we will checkout some common software solutions for MSP430s.

Firstly, there is the free open-source Energia IDE. This is an Arduino-like IDE that enables users to code MSP430s in the Arduino way. It supports many Launchpad boards including those which are based on ARM cores. I have used it a lot and it is fun using it for simple hobby projects. However, I wanted to harness the true power of MSP430s. As with Arduino, you can access MSP430 registers in Energia too but that doesn’t make significant differences in terms of coding efficiency and memory consumptions. Energia has the same issues as with Arduino. Arduino framework on top of an AVR makes it much less robust when compared to a crude AVR. The same thing applies for Energia too. Another key limitation of Energia is the fact that not all MSP430 chips are supported by it. Energia is, however, very easy to use, quick and useful for rapid prototyping or testing. The costs are low overall efficiency and larger code size. Just like Arduino, Energia is not well-suited for highly sophisticate professional projects. It is just a rapid prototyping tool that we can use to check a proof-of-concept but not the right tool to build that concept. A smaller hammer is useful for nailing a pin but it is not the perfect tool for breaking a giant boulder.

Energia is available at http://energia.nu/.

Energia IDE

Please note that Energia is not supported by the Arduino LCC which means it not developed or maintained by the guys from the Arduino team. This doesn’t matter much for its users. However, the IDE is not frequently updated like the Arduino IDE.

Next, we have TI’s own compiler – the Code Composer Studio (CCS). CCS is a C/C++ compiler based on Eclipse IDE. CCS comes with TI’s proprietary compilers that are best in code optimization. Those who have used Eclipse-based IDEs before know the advantages Eclipse framework brings with it. It has an excellent code navigation system, perspective views, refactoring, etc. CCS compiler comes with all of these stuffs and many more like debugger interface and TI App Store. Apart from all these there are some helpful cloud-based tools from TI. This tutorial is based on CCS compiler. It is free for download from TI’s website – http://www.ti.com/tool/ccstudio. Make sure you have a TI account.

CCS IDE

Then there is IAR compiler. I have never used IAR products but I have heard from many that it is very popular and widely used. However, it is expensive too.

Lastly, like for many other platforms we have the free open-source GCC compiler for MSP430s. MSP430 GCC can be integrated with CCS IDE.

We will see during coding that compilers don’t make significant differences in coding style, making codes cross-platform compatible. I also intend to keep things simple and quick. This is why I focus on tools rather than other stuffs. With right set of tools, any issue can be addressed decently and rapidly.

Additionally, some more software tools are needed for supportive purposes. Proteus VSM is a good interactive simulator cum PCB design software. Luckily it supports MSP430 micros. However, it is very expensive unless you are using a pirated version of it. Frankly speaking, I have never advocated for simulations because simulations do not address the real-world challenges we encounter in a real-life real-time project. Simulations, for example, cannot simulate real-world environment conditions nor can they emulate situations which result in “hang”-like stuck up conditions. Simulations don’t take the effect of noise and electromagnetic disturbances into account. Additionally, sometimes simulations give wrong results. I have spent many wasted hours trying to debug an issue with simulation only to find out that the simulation was incorrect. Still however, simulations are helpful in some special cases. For instance, when designing a LCD menu, simulation is a time and effort saver. Personally, I recommend and use real-world debugging over simulations. This gives me a lot of confidence.

Proteus VSM

The coolest stuffs for MSP430s are TI’s MSP430Ware driver libraries (driverlib) and GRACE. Driver libraries remove the pain of traditionally coding MSP430s using registers. Driver libraries provide easy-to-use API functions for configuring MSP430 peripherals just like the Standard Peripheral Libraries (SPL) of STM8 micros. However, these libraries are only supported for the newest and resource-rich MSP430 microcontrollers like the MSP430F5529LP. This idea of driver libraries is quickly gaining mass popularity and is becoming standard day-by-day for all modern era micros. MSP430Ware can be downloaded for free from http://www.ti.com/tool/mspware.

MSP430Ware

You can use TI’s Resource Explorer to check out what’s in driverlib and if your target MCU is supported.

GRACE, on the other hand, is intended for relatively less-resourceful micros like the MSP430G2231 that are mostly well-suited for lower level assembly language environments. GRACE is basically a graphical code generator tool, much like the STM32CubeMX that can be used to generate setup configuration codes for MSP430F2xx, MSP430G2xx and some FR series micros. However, GRACE only generates register values for small MSP430 micros. For large and advanced MSP430 micros driverlib-based codes are generated instead of register-level codes. The rest is just like coding any other microcontroller. Throughout this tutorial, GRACE has been used for all demos.

Grace

Configuring BCS+ with Grace

Grace Generated Code

As we can see the generated code snippet sets appropriate registers as per our setup in GRACE GUI.

GRACE can be downloaded without any charge from TI’s website: http://www.ti.com/tool/GRACE?keyMatch=grace%203&tisearch=Search-EN-Products.

We will also need a separate standalone programmer GUI tool. Why? Because it looks totally stupid to open the heavy CCS software every time to upload a code to a new target after having built the final code for it. For this purpose, we will need UniFlash programmer GUI.

UniFlash GUI

UniFlash can be downloaded from http://www.ti.com/tool/uniflash or can be accessed via TI cloud.

Additionally, I recommend using Sublime Text (https://www.sublimetext.com/3) or Notepad++ (https://notepad-plus-plus.org/download/v7.4.2.html) as a code viewer/editor.

Sublime Text

Make sure you also downloaded Launchpad board drivers from here: http://www.ti.com/lit/sw/slac524/slac524.zip.

Documents, Pages and Forums

The following documents must be acquired:

  • Reference Manual
    MSP430x2xx reference manual covers the details of all the hardware available in this family of microcontrollers. Unlike other microcontrollers, datasheet of a MSP430 micro, only says about technical specs and characteristics. Reference manuals say about internal hardware, how to use them and about internal registers. This is the most important document of all.
    http://www.ti.com/lit/ug/slau144j/slau144j.pdf 
  • Launchpad User Manual and Associated Files
    Visit the following link for Launchpad board user manual and other docs:
    http://www.ti.com/tool/msp-exp430g2. This document not just introduces the Launchpad board, it also contains schematics, layouts and other stuffs. Off all the stuffs in the user manual, Launchpad board’s schematic is the most valuable thing.
  • App Notes.
    Though not mandatory, having a collection of MSP430 application notes is a surplus. These show various ideas and design concepts. Visit TI’s website for these docs.

 

There are some important websites, online communities and forums that are very helpful. Some of the most popular ones are:

http://www.43oh.com/

http://forum.43oh.com/

https://e2e.ti.com/support/microcontrollers/msp430/

http://processors.wiki.ti.com/index.php/Main_Page

http://www.ti.com/lsds/ti/microcontrollers-16-bit-32-bit/msp/overview.page

http://www.ti.com/lsds/ti/tools-software/launchpads/overview/overview.page

Starting a New CCS Project

Beginning a new CCS project is not too complicated. Provided that CCS is installed in your PC, simply run it.

CCS Icon

In just a few seconds, CCS’s logo splashes.

CCS Logo

You’ll be asked for workspace location. Either select an existing workspace if you have one or create a new one.

Workspace Selection

Next click File >> New >> CCS Project.

CCS Project Selection

The following window will show up then:

Project Parameter Setup

Here we just need to setup target MCU, name of the project, compiler version and project type. Keep other options unchanged unless you are sure of your actions.

The following window appears after hitting the finish and we are good to go for coding. It is just that simple.

Project Editor

One advice I would like to give here, never delete any workspace file or folder unless you created it. It is possible to rename and remove projects from CCS IDE.

GRACE

As stated earlier, GRACE is a graphical configuration tool. It reduces the effort of thoroughly reading datasheets and reference manuals. I highly recommend using it no matter if you are novice or expert.

First run GRACE.

Grace Icon

Wait for it to launch.

Grace Welcome Screen

TI’s Resource Explorer kicks in on first start up. It may not do so every time though.

Click File >> New to begin a new GRACE project.

New Grace Project

You’ll be prompted for project location, MCU part number and project name as shown below:

Grace Project Properties

Once all the parameter fields are filled, we are ready to configure our target MCU. A welcome screen shows up next.

Grace Project Overview

From the welcome screen, we have to click on Device Overview and get an insight of the device’s peripherals as peripheral blocks.

Grace

We can now click desired hardware peripheral block (blue blocks) to check what features and code examples it offers and set it up as required. There are two types of setups for all modules – Basic and Power users. Basic User setup is for simple setup when you don’t know everything of a peripheral in details and don’t want to mess things up. Power User setup is for expert users with more advanced options. Shown below are the Power User options for setting up the clock system:

Configuring BCS+ with Grace

After setting up everything, just hit the hammer or Build button and the configuration codes are generated in the preset folder. It may take some time to complete the process. Next, we just need to open the generated files and copy them in our main CCS code.

How to create a new CCS project and use Grace is well documented in this video: https://www.youtube.com/watch?v=QCYMbsKwRfY.

UniFlash

Most of the times during development, a separate standalone programmer software is not a compulsory necessity as CCS IDE provides an inbuilt programmer/debugger interface. However, at times long after final application development, it becomes completely unnecessary to reopen CCS just to load a firmware to a new micro. UniFlash comes in aid at that time.

Run Uniflash by clicking its icon.

UniFlash Icon

 The following window appears:

UniFlash GUI

From here we need to select either target board or target microcontroller. No need to type the whole part number of a micro, just few digits/letters are enough to find the correct micro:

UniFlash Chip Selection

Similarly, we need to setup connection to target too. USB1 – the first option is what we need to select.

UniFlash Connection Selction

Now we are ready to begin firmware upload. Click the Start button.

UniFlash Start Button

Browse the firmware you wish to upload.

UniFlash File Selection

You can optionally set some more parameters for the target as shown:

UniFlash Additional Properties

To flash new firmware, just hit the Load Image button.

Watch this video for details: https://www.youtube.com/watch?v=4uwQSSX-HrM.

Strategies and Tactics

Before we begin exploring MSP430 micros, I would like to discuss certain things though they may look like advanced stuffs for the moment.

 

Generating HEX Output Files

By default, CCS doesn’t generate any hex formatted output file. Everyone working in the embedded system sector is familiar with it. Hex outputs are useful for Proteus VSM simulations and loading code to a new MSP430 micro using an external programmer like UniFlash. Thus, it is essential if not imperative to unlock hex utility.

First go to Project >> Properties.

Project Properties

Navigate to find MSP430 Hex Utility and then enable it as shown below.

Hex Utility

Finally select hex output format as shown:

Hex File Format

Select Intel hex format as it is the one widely used.

Having set as shown, from now on, whenever you build your current project, you’ll get an output .hex file in the project’s debug/release folder depending on your project type selection.

Building New Libraries

Building new libraries is simple. All we need to do is to follow a few set of rules:

  • For each module, there should be a separate header file and source file.
  • Header file should contain definitions, variables, constants and function prototypes only.
  • Header files should begin with the inclusion of MSP430 header file.
  • Source files should include their respective header files and addition header files (if any).
  • Source files should contain actual function codes only.
  • Be aware of reserved keywords and constants.
  • Global variables with same names should never be declared more than once.
  • Empty functions and functions without prototypes must be avoided.
  • Functions should have small and meaningful names.
  • Be careful about case-senstivity, function type assignment and argument type of functions.
  • Hierarchical order of library inclusion must be followed.

Delay Library

Adding Custom Library Files

Adding own developed libraries to a project is key requirement for any software developer. This is because no compiler includes libraries for all hardware. We must realize a compiler as a tool only. The rest is how we use it and what we do with it. As for example, CCS comes with _delay_cycles instead of more commonly preferred delay_ms or delay_us functions. We will, thus, need software delay library. We need to code it and include it in our projects.

Custom libraries can be included in two ways. The easiest way is to add the include and source files in your projects root location, i.e. its folder. No additional job is needed because the root contains the main.c source file and its location is automatically included when you start a project.

Root Library Location

However, the aforementioned method becomes clumsy and unprofessional when there are too many custom library files in a large project and if you care for some neatness. The other method needs some additional tasks to be done before we can use our library files.

First make a folder in your project directory and give it a name. For example, I named this folder Libraries in my examples.

Next add the desired library source and header files in this folder.

Library Folder

At this stage, we still cannot use these libraries because the compiler does not know their path(s) and so we need to inform the compiler about their location. We need to go to Project >> Properties first and then navigate to Include Options menu under Build >> MSP430 Compiler menu as shown:

Library Folder Browse

Right after clicking the circled icon as shown above, we will be asked for folder location. It is just a simple browsing to the library folder location.

Library Folder Inclusion

To use the libraries we added, we now just need to add some #include statements in our main.c file.

Order of Include Files in Main Source Code

Be careful about the hierarchical order of library files because they may be interdependent. For example, as shown above, the LCD library has dependency on software delay library and so the delay library is added or called before the LCD library.

Using GRACE Simply but Effectively

GRACE should be used for quick setups. Who would like to waste time fixing register values when we have such a useful tool at our side. We will need a code viewer/editor like Sublime Text for viewing codes generated by GRACE. I like Sublime Text for its way of highlighting keywords and important stuffs with different colours. This helps in building quick situational awareness. A dark IDE is also good for night-time coding and less stressful for eyes.

Previously I showed how to use GRACE to generate configuration codes. GRACE generates individual source files for each hardware used. In this way, it doesn’t create too much mess. We will open each of these files with Sublime Text and copy only the needed init functions in our main source code. GRACE also generates other stuffs but that are like mere junks to us and so we will just ignore them.

Important Parts in Grace

My code examples will give you an idea of what to copy. Keep in mind to stop the watchdog timer in the beginning of your code or it may reset your micro before entering actual application.

Optional Customizations

Explore the IDE Preferences for customizing CCS IDE according to your wishes. For instance, unlike the default white theme, I like a full black IDE interface like the one in Sublime Text. This is really effective when you work at night and in low light conditions. Just like Sublime Text key words are highlighted and it is easy to navigate in such an environment.

In a busy world, we often forget to update software in regular schedules and therefore miss important changes and bug fixes. Automatic updates come in aid in such case. I configured my CCS in such a way that it auto updates and notifies me about new software versions. I also added some tools from CCS/TI App Store like the GCC compiler.

Other customizations include MSP430Ware and EnergyTrace Technology debugger. EnergyTrace Technology allows us to compute energy consumptions. It helps in estimating battery requirements (if any).

I recommend readers to explore CCS IDE properties for more custom settings.

Advanced Concepts

Most of the times during code compilation you’ll notice that the compiler not only compiled your code but has also given you some optional advices. These advices aid in code optimizations and hint ways to enhance overall performance. For instance, it is better to use switch-case statements instead of if-else statements when dealing with fixed-discrete conditions. Try to follow the advices whenever possible. Same goes for compiler warnings. You must address the warnings for flawless coding.

In the internet, we can find lot of documents on C code optimization and good C language practices. Here is one such document from Atmel: http://www.atmel.com/images/doc8453.pdf. Although it was written for Atmel AVR microcontrollers but the document applies for all microcontrollers and C compilers. Similarly, Microchip has documents named Tips ‘n Tricks. Visit and search Microchip’s site for these documents These tricks and tips help in designs significantly. Try to follow these to achieve best utilization of your micro’s assets. TI’s application notes are also equally helpful literatures. Personally, I recommend studying the app notes, and other documents of other micro families too. This will help advance in developing new concepts, strategies and techniques.

Additionally, I would like to point out some issues and techniques when using CCS. Here are followings:

  • Be careful about case sensitivity. Also, be careful about compiler’s reserved keywords and constants.
  • Although not mandatory, it is, however, a good practice not to keep empty argument field in any function. Argumentless functions should have void argument instead of empty spaces.
  • Flags are important event markers and so wherever they are present and needed you must use and check them unless automatically cleared. For instance, it necessary to clear timer flags upon exiting timer interrupts.
  • Try to avoid polling methods. Try to use interrupt-based ones but make sure that there is no interrupt-within-interrupt case or otherwise your code may behave erratically. Best is to attach interrupts for important tasks like timing-related jobs, ADC conversions and communications. It is up to your design requirements and choices.
  • Where fast processing is required, try to mix assembly with your C-code if you can or temporarily speed up your micro by increasing its oscillator speed or switching to a faster clock source. Checkout the assembly examples from TI’s Resource Explorer and MSP430Ware. Also try to study and learn about advanced C concepts like pointer, structures, functions, etc.
  • Avoid empty loops and blank conditional statements.
  • When you hover mouse cursor over a function, a small window appears. This window shows the internal coding of that function and relieve you from opening a new tab for it.
  • CTRL + Space or code assist is a great helper. Likewise, CCS has some auto complete features.
  • If you are using multiple computers during your project’s development stage, make sure that your custom library locations and workspace paths are properly added.
  • Try to follow compiler advices and optimizations. Study the MSP430 header files if you can.
  • You can straight include the header file of your target MCU like as shown below if code cross compatibility among different MCUs of the same group like MSP430x2xx is not needed:
    #include <msp430g2553.h>

    instead of:

    #include <msp430.h>

     

    The latter is universal for all MSP430 micros and should be used unless otherwise.

  • Bitwise and logic operations are useful. Not only they are fast, they just deal with the designated bits only. Although the MSP430 header files have efficient support for such operations, it is still better to know them. Shown below are some such common operations:
#define bit_set(reg, bit_val)   reg |= (1 << bit_val)    //For setting a bit of a register  
#define bit_clr(reg, bit_val)   reg &= (~(1 << bit_val)) //For clearing a bit of a register
#define bit_tgl(reg, bit_val)   reg ^= (1 << bit_val)    //For toggling a bit of a register
#define get_bit(reg, bit_val)   (reg & (1 << bit_val))   //For extracting the bit state of a register
#define get_reg(reg, msk)       (reg & msk)              //For extracting masked bits of a register

Basic Clock System Plus (BCS+)

In all microcontrollers, power consumption and operating speed are interdependent and it is needed to balance these well to maximize overall performance while conserving energy. MSP430s are crafted with ultra-low power consumption feature in mind while not compromising performance. For this reason, MSP430s are equipped with a number of clock sources that vary in speed, accuracy and area of use. They also have clock dividers at various points apart from peripherals prescalers. This combination leads to a highly flexible clock system called Basic Clock System Plus (BCS+).

Block Diagram

The block diagram for MSP420x2xx BCS+ module shown above highlights important components. It looks very sophisticated but if we divide it into important sections then it becomes simple to understand. Highlighted in green are clock sources and highlighted in purple are various clock signals that can be used for peripherals and the CPU. Now let’s check BCS+ in short.

Clock Sources

Clock Signals

After having a sneak-a-peek of the MSP430’s clock system, we have to know some basic rules of using the BCS+ module of MSP430G2xx devices:

  • XT2CLK and LFXT1CLK high frequency mode are both unavailable. We can’t use them.
  • DCOCLK is the most reliable clock source and should be used for both MCLK and SMCLK.
  • DCOCLK is dependent on VDD and so set VDD in GRACE before trying to setup BCS+.
  • Pre-calibrated DCOCLK values should be used as they offer good tolerance figures.
  • It is not wise to use custom DCOCLK values as accuracy issues surface.
  • If LFXT1CLK is used and needs precise timings, use a good clock crystal/TCXO/clock source.
  • Use proper capacitance value for LFXT1CLK when an external crystal is used.
  • Unused clock sources should be disabled to reduce power consumption.
  • Pins that connect with external clock sources should be set as inputs if they are not used.
  • Add a system start up delay of about 10 – 100ms to allow stabilization of clock sources.
  • Clock outputs are available only in certain pins. If used, they are needed to be set accordingly.
  • Use oscillator fault interrupt if needed. This becomes extremely necessary to ensure fail-safe clock operation when external clock sources are used alongside internal clock sources.
  • For time sensitive hardware like timers, try to use a reliable clock source.
  • Check for warnings in GRACE.

 

Code Example

#include <msp430.h>


void BCSplus_graceInit(void);
void GPIO_graceInit(void);
void WDTplus_graceInit(void);
void System_graceInit(void);


void main(void)
{
    /* Stop watchdog timer from timing out during initial start-up. */
    WDTCTL = WDTPW | WDTHOLD;

    /* initialize Config for the MSP430 GPIO */
    GPIO_graceInit();

    /* initialize Config for the MSP430 2xx family clock systems (BCS) */
    BCSplus_graceInit();

    /* initialize Config for the MSP430 System Registers */
    System_graceInit();

    /* initialize Config for the MSP430 WDT+ */
    WDTplus_graceInit();

    while(1)
    {
         P1OUT ^= BIT6;
         _delay_cycles(1);
    };
}


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_1 -- Divide by 2
     * SELS -- XT2CLK when XT2 oscillator present. LFXT1CLK or VLOCLK when XT2 oscillator not present
     * DIVS_3 -- Divide by 8
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~DCOR indicates that DCOR has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_1 | SELS | DIVS_3;

    if (CALBC1_1MHZ != 0xFF) {
        /* Follow recommended flow. First, clear all DCOx and MODx bits. Then
         * apply new RSELx values. Finally, apply new DCOx and MODx bit values.
         */
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_1MHZ;      /* Set DCO to 1MHz */
        DCOCTL = CALDCO_1MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_2 -- If XTS = 0, XT1 = VLOCLK ; If XTS = 1, XT1 = 3 - 16-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_2 | XCAP_1;

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}



void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

    /* Port 1 Output Register */
    P1OUT = 0;

    /* Port 1 Port Select Register */
    P1SEL = BIT0 | BIT4;

    /* Port 1 Direction Register */
    P1DIR = BIT0 | BIT4 | BIT6;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Direction Register */
    P2DIR = 0;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* Port 3 Output Register */
    P3OUT = 0;

    /* Port 3 Direction Register */
    P3DIR = 0;

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */
}


void WDTplus_graceInit(void)
{
    /* USER CODE START (section: RTC_B_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: RTC_B_graceInit_prologue) */

    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * WDTHOLD -- Watchdog timer+ is stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * ~WDTTMSEL -- Watchdog mode
     * ~WDTCNTCL -- No action
     * ~WDTSSEL -- SMCLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * ~WDTIS1 -- Watchdog clock source bit1 disabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTHOLD;

    /* USER CODE START (section: RTC_B_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: RTC_B_graceInit_epilogue) */
}


void System_graceInit(void)
{
    /* USER CODE START (section: System_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: System_graceInit_prologue) */

    /* Clear oscillator fault flag with software delay */
    do
    {
        // Clear OSC fault flag
        IFG1 &= ~OFIFG;

        // 50us delay
        __delay_cycles(25);
    } while (IFG1 & OFIFG);

    /*
     * SR, Status Register
     *
     * ~SCG1 -- Disable System clock generator 1
     * ~SCG0 -- Disable System clock generator 0
     * ~OSCOFF -- Oscillator On
     * ~CPUOFF -- CPU On
     * GIE -- General interrupt enable
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    __bis_SR_register(GIE);

    /* USER CODE START (section: System_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: System_graceInit_epilogue) */
}

 

Simulation

BCSP Simulation

Explanation

The demo example here is primarily used to extract SMCLK and ACLK signals. Here we just verified if these signals are as they are supposed to be. Using GRACE, we set MCLK 500 kHz, SMCLK 1500 Hz and ACLK 12 kHz. Clock outputs are obtained from respective pins as shown in the snap. The only thing additional here (not shown) is the P1.6 digital I/O. In the code, this I/O is toggled every one CPU cycle. It is not an indicator of CPU clock speed but just a test of I/O toggling speed for. MCLK has no output associated with it and so we can’t see its signal.

One major thing to note is the power supply voltage level. This is so because DCOCLK is dependent on VDD level. For low VDD voltages, high frequency oscillation generation is not possible. We have to remember that MSP430s are energy efficient devices and so there is always a trade-off between operating frequency and operating power consumption.

Another odd but important thing you may notice is the fact that the VDD value in MSP430 Launchpads is 3.6V instead of the more commonly used 3.3V. This is the maximum recommended VDD value although MSP430s can tolerate voltages up to 4.0V.

GRACE Settings

Oscillators may show deviations in frequency due to changes in ambient temperature conditions. This in turn may affect communication and timing-related tasks.

Demo

BCSP

Saleae Logic Screenshot

Demo video: https://www.youtube.com/watch?v=gHDibVxfegU.

Digital I/Os (DIO)

MSP430s, just like any micro, have digital I/Os for general-purpose input-output operations. The resources and features of MSP430 digital I/Os are very rich and more or less comparable to a typical ARM microcontroller. All I/O can be independently programmed. Many I/Os have external interrupt feature. Another cool feature is the availability of both internal pull-up and pull-down resistors for all I/Os and they can be individually and independently set. Additionally, many I/Os have alternate roles for communication buses, clock, etc. However, the digital I/Os are not 5V tolerant and we must be careful interfacing external devices with our MSP430 chips. I strongly recommend using some form of logic-level conversion circuitry in such cases.

Block Diagram

Typically, there are four major components in a digital I/O as highlighted in the block diagram above. The yellow region is responsible for setting I/O properties, the light blue area is dedicated for output functionalities, the orange area for inputs and external interrupts, the pink zone for internal pull resistors and finally the red area signifying the presence of a Schmitt trigger input stage which is very useful for noisy environments.

Code Example

#include <msp430g2452.h>


void GPIO_graceInit(void);
void BCSplus_graceInit(void);
void WDTplus_graceInit(void);
void System_graceInit(void);


void main(void)
{
    /* Stop watchdog timer from timing out during initial start-up. */
    WDTCTL = WDTPW | WDTHOLD;

    /* initialize Config for the MSP430 GPIO */
    GPIO_graceInit();

    /* initialize Config for the MSP430 2xx family clock systems (BCS) */
    BCSplus_graceInit();

    /* initialize Config for the MSP430 System Registers */
    System_graceInit();

    /* initialize Config for the MSP430 WDT+ */
    WDTplus_graceInit();

       for(;;)
       {
           if((P1IN & BIT3) == !BIT3)
           {
               P1OUT |= BIT0;
               _delay_cycles(40000);
               P1OUT &= ~BIT0;
           }

           P1OUT ^= BIT6;
           _delay_cycles(30000);
       }
}


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

    if (CALBC1_1MHZ != 0xFF) {
        /* Follow recommended flow. First, clear all DCOx and MODx bits. Then
         * apply new RSELx values. Finally, apply new DCOx and MODx bit values.
         */
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_1MHZ;      /* Set DCO to 1MHz */
        DCOCTL = CALDCO_1MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_2 -- If XTS = 0, XT1 = VLOCLK ; If XTS = 1, XT1 = 3 - 16-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_2 | XCAP_1;

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}


void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

    /* Port 1 Output Register */
    P1OUT = BIT3;

    /* Port 1 Direction Register */
    P1DIR = BIT0 | BIT6;

    /* Port 1 Resistor Enable Register */
    P1REN = BIT3;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Direction Register */
    P2DIR = 0;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */
}


void System_graceInit(void)
{
    /* USER CODE START (section: System_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: System_graceInit_prologue) */

    /* Clear oscillator fault flag with software delay */
    do
    {
        // Clear OSC fault flag
        IFG1 &= ~OFIFG;

        // 50us delay
        __delay_cycles(50);
    } while (IFG1 & OFIFG);

    /*
     * SR, Status Register
     *
     * ~SCG1 -- Disable System clock generator 1
     * ~SCG0 -- Disable System clock generator 0
     * ~OSCOFF -- Oscillator On
     * ~CPUOFF -- CPU On
     * GIE -- General interrupt enable
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    __bis_SR_register(GIE);

    /* USER CODE START (section: System_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: System_graceInit_epilogue) */
}


void WDTplus_graceInit(void)
{
    /* USER CODE START (section: RTC_B_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: RTC_B_graceInit_prologue) */

    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * WDTHOLD -- Watchdog timer+ is stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * ~WDTTMSEL -- Watchdog mode
     * ~WDTCNTCL -- No action
     * ~WDTSSEL -- SMCLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * ~WDTIS1 -- Watchdog clock source bit1 disabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTHOLD;

    /* USER CODE START (section: RTC_B_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: RTC_B_graceInit_epilogue) */
}

 

Simulation

DIO Simulation

Explanation

The most classical “Hello World” demo for digital I/Os is blinking a LED. Here I demonstrated the same but with some minor differences. Here Launchpad board’s user button and LEDs are used. LED connected to P1.6 blinks continuously while P1.0 LED blinks only when the user button is pressed, slowing down P1.6 LED’s blink rate. Note _delay_cycles were used to create software delays.

Grace Setting

At this stage I must point out, some basic rules that we must follow when we use digital I/Os of MSP430s:

  • Unused I/Os should be declared as inputs or they should be externally pulled to VDD/VSS.
  • Unused digital I/Os can be left unconnected and floating although it is not wise to do so.
  • The same applies for oscillator pins. By default, GRACE treats them as oscillator pins.
  • I/Os are not 5V tolerant. Some sort of mechanism must be applied when using 5V devices.
  • Most I/Os have more than one function and so be aware of conflicts.
  • When driving large loads with I/O, use external components like BJTs, FETs, drivers, etc.
  • When using GRACE, be sure of the IC package you are using.
  • When external pull resistor is present, do not use the internal ones and vice versa.
  • Explore your device’s datasheet for I/O features and limits although most are common.

 

Demo

DIO Demo

Demo video: https://www.youtube.com/watch?v=fWlNiEZk4iM.

External Interrupts (EXTI)

External interrupt is an extended feature of digital I/Os in input mode. External interrupts make a micro to respond instantly to changes done on it digital input pin(s) by an external event(s)/trigger(s), skipping other tasks. Such interrupts are useful in a wide variety of applications. In case of low power energy efficient micros like the MSP430s, interrupts as such can be used to bring a micro out of sleep mode. In other words, an external interrupt acts like a wakeup call. For example, it is extremely important to conserve very limited battery energy in a TV remote controller while at the same time it is also necessary to keep it completely responsive to button presses. Thus, we need to put its host micro in sleep mode when we are not using it and make it respond to button presses immediately when any button is pressed. A MSP430 micro in sleep/idle mode consumes literally no energy at all and that is why they are the best micros for battery-backed low power applications.

Interrupt Vectors

Fortunately for us, most MSP430G2xx digital I/Os have external interrupt handling capability – a much desired feature. Shown above is the interrupt map for MSP430G2553. Note external interrupts are maskable interrupts and have low priority compares to other interrupts. We must consider this fact when coding a multi-interrupt-based application.

Code Example

#include <MSP430G2452.h>


unsigned char state = 0x00;


void BCSplus_graceInit(void);
void GPIO_graceInit(void);
void System_graceInit(void);
void WDTplus_graceInit(void);


#pragma vector=PORT1_VECTOR
__interrupt void PORT1_ISR_HOOK(void)
{
    state = ~state;
    P1OUT ^= BIT0;
    P1IFG = 0x00;
}


void main(void)
{
    /* Stop watchdog timer from timing out during initial start-up. */
    WDTCTL = WDTPW | WDTHOLD;

    /* initialize Config for the MSP430 GPIO */
    GPIO_graceInit();

    /* initialize Config for the MSP430 2xx family clock systems (BCS) */
    BCSplus_graceInit();

    /* initialize Config for the MSP430 System Registers */
    System_graceInit();

    /* initialize Config for the MSP430 WDT+ */
    WDTplus_graceInit();

    do
    {
         P1OUT ^= BIT6;
         if(state)
         {
             _delay_cycles(60000);
         }
         else
         {
             _delay_cycles(30000);
         }
    }while(1);
}


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

    if (CALBC1_1MHZ != 0xFF) {
        /* Follow recommended flow. First, clear all DCOx and MODx bits. Then
         * apply new RSELx values. Finally, apply new DCOx and MODx bit values.
         */
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_1MHZ;      /* Set DCO to 1MHz */
        DCOCTL = CALDCO_1MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_2 -- If XTS = 0, XT1 = VLOCLK ; If XTS = 1, XT1 = 3 - 16-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_2 | XCAP_1;

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}


void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

    /* Port 1 Output Register */
    P1OUT = 0;

    /* Port 1 Direction Register */
    P1DIR = BIT0 | BIT6;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = BIT3;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 1 Interrupt Enable Register */
    P1IE = BIT3;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Port Select Register */
    P2SEL &= ~(BIT6 | BIT7);

    /* Port 2 Direction Register */
    P2DIR = 0;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */
}


void System_graceInit(void)
{
    /* USER CODE START (section: System_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: System_graceInit_prologue) */

    /* Clear oscillator fault flag with software delay */
    do
    {
        // Clear OSC fault flag
        IFG1 &= ~OFIFG;

        // 50us delay
        __delay_cycles(50);
    } while (IFG1 & OFIFG);

    /*
     * SR, Status Register
     *
     * ~SCG1 -- Disable System clock generator 1
     * ~SCG0 -- Disable System clock generator 0
     * ~OSCOFF -- Oscillator On
     * ~CPUOFF -- CPU On
     * GIE -- General interrupt enable
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    __bis_SR_register(GIE);

    /* USER CODE START (section: System_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: System_graceInit_epilogue) */
}


void WDTplus_graceInit(void)
{
    /* USER CODE START (section: RTC_B_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: RTC_B_graceInit_prologue) */

    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * WDTHOLD -- Watchdog timer+ is stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * ~WDTTMSEL -- Watchdog mode
     * ~WDTCNTCL -- No action
     * ~WDTSSEL -- SMCLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * ~WDTIS1 -- Watchdog clock source bit1 disabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTHOLD;

    /* USER CODE START (section: RTC_B_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: RTC_B_graceInit_epilogue) */
}

 

Simulation

EXTI Simulation

Explanation

The basic theme and the hardware setup for this demo is same as that of the previous one. The only difference is the Launchpad user button. Rather than using polling method, external interrupt method is used. When P1.3 detects a falling edge, P1.0’s state is altered while P1.6 toggles independently in the main loop, denoting two separate independent processes at work simultaneously. Every external interrupt changes the toggle speed of P1.6 LED.

Grace Settings

Demo

EXTI

Demo video: https://www.youtube.com/watch?v=LlTvj-CSiBE.

Alphanumeric LCD

Alphanumeric LCDs are popular for projecting data and information quickly and efficiently. To use them, we do not need any additional hardware feature in a micro. Digital I/Os are what we need to use these displays.

LCD

Usually to use these LCDs we need 5V power supply. 3.3V versions of these LCD are rare on the other hand.  It is, however, possible to power up 5V version LCDs with 5V supply while using 3.3V logic for communication. In TTL logic, something above 2.8V is just above the minimum allowed logic high voltage. When VDD is less than 3V, it, then, becomes necessary to use logic level conversion circuitry. Here in my demo I used a 3.3V version LCD to keep things simple and tidy. Shown below are LCDs for different voltage levels. Both LCDs look same but if you check the backside of both LCDs you’ll notice a difference. In the 3.3V version LCD, there are some additional components present, notably an 8 pin SMD IC. This is a ICL7660 negative voltage generator IC.

Seeeduino LCD (1) Seeeduino LCD (2)

Similarly, alphanumeric LCDs also have operating frequency limit. Usually it is about 250 kHz. Refer to the datasheet of the LCD you are using. If the digital I/Os are faster than this max limit value, it is highly likely that the LCD won’t show any valid data at all or it will show up garbage characters. To counter this issue, either we have to use a low MCU clock frequency or add some delays in our LCD library to slow the processes down.

Software delays are often required to introduce time delays in a code. Such delays will be needed for the LCD library. As discussed before by default, CCS doesn’t provide delay time functions i.e. delay milliseconds (delay_ms) and delay micro-seconds (delay_us). Instead it provides _delay_cycles function for delays. Most of us will not be comfortable with delay cycles function as it doesn’t directly signify the amount of time wasted. Thus, delay time functions are musts. Although software delays are inefficient in terms of coding and performance, they are helpful in debugging stuffs quickly in a rudimentary fashion. A much novel and precise approach of creating time delays is achieved using a timer. In this example, we will need both LCD and delay libraries. I already showed how to incorporate custom libraries in a CCS Project and here I implemented that addition.

There are other types of displays in the realm of LCDs. These include monochrome graphical LCDs, TFT LCDs, OLED LCDs, etc. However, it is literally impossible to integrate these displays with MSP430G2xx micros. Firstly, this is so due to low memories and secondly due to low pin counts. MSP430G2xx micros are also not as fast as ARM micros. Speed plays a vital role in processing graphics.

TFT LCD

Code Example

 

delay.h

#include <msp430.h>


#define F_CPU                   8


void delay_us(unsigned int value);
void delay_ms(unsigned int value);

 

delay.c

#include "delay.h"


void delay_us(unsigned int value)
{
    register unsigned int loops =  ((F_CPU * value) >> 2) ;

    while(loops)
    {
        _delay_cycles(1);
        loops--;
    };
}


void delay_ms(unsigned int value)
{
    while(value)
    {
        delay_us(1000);
        value--;
    };
}

 

lcd.h

#include <msp430.h>
#include <delay.h>


#define LCD_PORT                            P2OUT


#define LCD_RS                              BIT0
#define LCD_EN                              BIT1
#define LCD_DB4                             BIT2
#define LCD_DB5                             BIT3
#define LCD_DB6                             BIT4
#define LCD_DB7                             BIT5

#define LCD_RS_HIGH                         LCD_PORT |= LCD_RS
#define LCD_RS_LOW                          LCD_PORT &= ~LCD_RS

#define LCD_EN_HIGH                         LCD_PORT |= LCD_EN
#define LCD_EN_LOW                          LCD_PORT &= ~LCD_EN

#define LCD_DB4_HIGH                        LCD_PORT |= LCD_DB4
#define LCD_DB4_LOW                         LCD_PORT &= ~LCD_DB4

#define LCD_DB5_HIGH                        LCD_PORT |= LCD_DB5
#define LCD_DB5_LOW                         LCD_PORT &= ~LCD_DB5

#define LCD_DB6_HIGH                        LCD_PORT |= LCD_DB6
#define LCD_DB6_LOW                         LCD_PORT &= ~LCD_DB6

#define LCD_DB7_HIGH                        LCD_PORT |= LCD_DB7
#define LCD_DB7_LOW                         LCD_PORT &= ~LCD_DB7

#define clear_display                       0x01
#define goto_home                           0x02

#define cursor_direction_inc               (0x04 | 0x02)
#define cursor_direction_dec               (0x04 | 0x00)
#define display_shift                      (0x04 | 0x01)
#define display_no_shift                   (0x04 | 0x00)

#define display_on                         (0x08 | 0x04)
#define display_off                        (0x08 | 0x02)
#define cursor_on                          (0x08 | 0x02)
#define cursor_off                         (0x08 | 0x00)
#define blink_on                           (0x08 | 0x01)
#define blink_off                          (0x08 | 0x00)

#define _8_pin_interface                   (0x20 | 0x10)
#define _4_pin_interface                   (0x20 | 0x00)
#define _2_row_display                     (0x20 | 0x08)
#define _1_row_display                     (0x20 | 0x00)
#define _5x10_dots                         (0x20 | 0x40)
#define _5x7_dots                          (0x20 | 0x00)

#define DAT                                 1
#define CMD                                 0


void LCD_init(void);
void LCD_send(unsigned char value, unsigned char mode);
void LCD_4bit_send(unsigned char lcd_data);
void LCD_putstr(char *lcd_string);
void LCD_putchar(char char_data);
void LCD_clear_home(void);
void LCD_goto(unsigned char x_pos, unsigned char y_pos);
void toggle_EN_pin(void);
void toggle_io(unsigned char lcd_data, unsigned char bit_pos, unsigned char pin_num);

 

lcd.c

#include "lcd.h"


void LCD_init(void)
{
    LCD_PORT |= (LCD_RS | LCD_EN | LCD_DB4 | LCD_DB5 | LCD_DB6 | LCD_DB7);

    delay_ms(20);

    toggle_EN_pin();

    LCD_RS_LOW;

    LCD_DB7_LOW;
    LCD_DB6_LOW;
    LCD_DB5_HIGH;
    LCD_DB4_HIGH;

    toggle_EN_pin();

    LCD_DB7_LOW;
    LCD_DB6_LOW;
    LCD_DB5_HIGH;
    LCD_DB4_HIGH;

    toggle_EN_pin();

    LCD_DB7_LOW;
    LCD_DB6_LOW;
    LCD_DB5_HIGH;
    LCD_DB4_HIGH;

    toggle_EN_pin();

    LCD_DB7_LOW;
    LCD_DB6_LOW;
    LCD_DB5_HIGH;
    LCD_DB4_LOW;

    toggle_EN_pin();

    LCD_send((_4_pin_interface | _2_row_display | _5x7_dots), CMD);
    LCD_send((display_on | cursor_off | blink_off), CMD);
    LCD_send(clear_display, CMD);
    LCD_send((cursor_direction_inc | display_no_shift), CMD);
}


void LCD_send(unsigned char value, unsigned char mode)
{
    switch(mode)
    {
        case DAT:
        {
            LCD_RS_HIGH;
            break;
        }
        case CMD:
        {
            LCD_RS_LOW;
            break;
        }
    }

    LCD_4bit_send(value);
}


void LCD_4bit_send(unsigned char lcd_data)
{
    toggle_io(lcd_data, 7, LCD_DB7);
    toggle_io(lcd_data, 6, LCD_DB6);
    toggle_io(lcd_data, 5, LCD_DB5);
    toggle_io(lcd_data, 4, LCD_DB4);

    toggle_EN_pin();

    toggle_io(lcd_data, 3, LCD_DB7);
    toggle_io(lcd_data, 2, LCD_DB6);
    toggle_io(lcd_data, 1, LCD_DB5);
    toggle_io(lcd_data, 0, LCD_DB4);

    toggle_EN_pin();
}


void LCD_putstr(char *lcd_string)
{
    do
    {
        LCD_send(*lcd_string++, DAT);
    }while(*lcd_string != '\0');
}


void LCD_putchar(char char_data)
{
    LCD_send(char_data, DAT);
}


void LCD_clear_home(void)
{
    LCD_send(clear_display, CMD);
    LCD_send(goto_home, CMD);
}


void LCD_goto(unsigned char x_pos, unsigned char y_pos)
{
    if(y_pos == 0)
    {
        LCD_send((0x80 | x_pos), CMD);
    }
    else
    {
        LCD_send((0x80 | 0x40 | x_pos), CMD);
    }
}


void toggle_EN_pin(void)
{
    LCD_EN_HIGH;
    delay_ms(2);
    LCD_EN_LOW;
    delay_ms(2);
}


void toggle_io(unsigned char lcd_data, unsigned char bit_pos, unsigned char pin_num)
{
    unsigned char temp = 0x00;

    temp = (0x01 & (lcd_data >> bit_pos));

    switch(temp)
    {
        case 0:
        {
            LCD_PORT &= ~pin_num;
            break;
        }

        default:
        {
            LCD_PORT |= pin_num;
            break;
        }
    }
}

 

main.c

#include <msp430.h>
#include "delay.h"
#include "lcd.h"


void GPIO_graceInit(void);
void BCSplus_graceInit(void);
void System_graceInit(void);
void WDTplus_graceInit(void);


void main(void)
{
    unsigned char s = 0;

    const char txt1[] = {"MICROARENA"};
    const char txt2[] = {"SShahryiar"};
    const char txt3[] = {"MSP-EXP430G2"};
    const char txt4[] = {"Launchpad!"};

    /* Stop watchdog timer from timing out during initial start-up. */
    WDTCTL = WDTPW | WDTHOLD;

    /* initialize Config for the MSP430 GPIO */
    GPIO_graceInit();

    /* initialize Config for the MSP430 2xx family clock systems (BCS) */
    BCSplus_graceInit();

    /* initialize Config for the MSP430 System Registers */
    System_graceInit();

    /* initialize Config for the MSP430 WDT+ */
    WDTplus_graceInit();

    LCD_init();

    LCD_clear_home();

    LCD_goto(3, 0);
    LCD_putstr(txt1);
    LCD_goto(3, 1);
    LCD_putstr(txt2);
    delay_ms(2600);

    LCD_clear_home();

    for(s = 0; s < 12; s++)
    {
        LCD_goto((2 + s), 0);
        LCD_putchar(txt3[s]);
        delay_ms(90);
    }
    for(s = 0; s < 10; s++)
    {
        LCD_goto((3 + s), 1);
        LCD_putchar(txt4[s]);
        delay_ms(90);
    }

    while(1)
    {
    };
}


void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

    /* Port 1 Output Register */
    P1OUT = 0;

    /* Port 1 Direction Register */
    P1DIR = 0;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Port Select Register */
    P2SEL &= ~(BIT6 | BIT7);

    /* Port 2 Direction Register */
    P2DIR = BIT0 | BIT1 | BIT2 | BIT3 | BIT4 | BIT5;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */
}


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

    if (CALBC1_8MHZ != 0xFF) {
        /* Adjust this accordingly to your VCC rise time */
        __delay_cycles(100000);

        // Follow recommended flow. First, clear all DCOx and MODx bits. Then
        // apply new RSELx values. Finally, apply new DCOx and MODx bit values.
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_8MHZ;      /* Set DCO to 8MHz */
        DCOCTL = CALDCO_8MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_0 -- If XTS = 0, XT1 = 32768kHz Crystal ; If XTS = 1, XT1 = 0.4 - 1-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_0 | XCAP_1;

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}


void System_graceInit(void)
{
    /* USER CODE START (section: System_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: System_graceInit_prologue) */

    /*
     * SR, Status Register
     *
     * ~SCG1 -- Disable System clock generator 1
     * ~SCG0 -- Disable System clock generator 0
     * ~OSCOFF -- Oscillator On
     * ~CPUOFF -- CPU On
     * GIE -- General interrupt enable
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    __bis_SR_register(GIE);

    /* USER CODE START (section: System_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: System_graceInit_epilogue) */
}


void WDTplus_graceInit(void)
{
    /* USER CODE START (section: RTC_B_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: RTC_B_graceInit_prologue) */

    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * WDTHOLD -- Watchdog timer+ is stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * ~WDTTMSEL -- Watchdog mode
     * ~WDTCNTCL -- No action
     * ~WDTSSEL -- SMCLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * ~WDTIS1 -- Watchdog clock source bit1 disabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTHOLD;

    /* USER CODE START (section: RTC_B_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: RTC_B_graceInit_epilogue) */
}

 

Simulation

LCD Simulation

Explanation

Hardly there is a thing to explain here. However, there are a few things to note. After having decided the CPU clock frequency, we must edit the following line of the delay header file to make our delays work as much as accurate as possible:

#define F_CPU                   8       //CPU Clock in MHz

Software delays are not 100% accurate since they employ wasteful loops of delay cycles which in turn add extra CPU cycles. They are always somewhat close to the actual required value. Delays are also dependent on oscillator accuracy.

grace

Though it is not mandatory but I still recommend that we try to use digital I/Os that have no or least alternate functionalities. This will ensure maximum utilization of limited resources and avoid conflicts as well. Here in this demo, I used P2 port just for that.

Choosing the right display for a project is often tedious. We have lot of options nowadays ranging from simple LED displays to TFT LCDs. However, considering low power consumption and limited resource availability, it wise to use the simplest form of display. If a project can be completed using seven segment displays, it wasteful and expensive to use a OLED display with seven segment fonts.

LCD displays host their own set of electronics and are prone to Electromagnetic Interference (EMI) and related issues. In many cases, one may end up having a EMI troubled LCD while having the host MCU fully functional and vice-versa. It is always wise to use a short path between a LCD and its host MCU. If needed, use passive low pass antialiasing filters.

Demo

LCD Demo

Demo video: https://www.youtube.com/watch?v=sJF8oJPu18s.

Timer Overview

There are several types of timers in MSP430 micros. These include Watchdog Timer (WDT), Real Time Clock (RTC), Basic Timer 1 and Timer A and Timer B. Timer As and Bs are general purpose 16-bit timers that are suitable for time base generations, pulse width modulations (PWM) and input captures. the other three types have special uses and their names suggest their purposes. All MSP430 devices have at least one Timer A module and a WDT module. Since both are common amongst all devices, we will be studying about them here. Timer As and Bs are almost same. The difference between the two is the presence of some additional features like more Capture/Compare (CC) channels in Timer Bs.

Timer A

In TI’s literature, timers are named like Timer0_A3, Timer_B7, etc. A number can be present right after the word Timer in some case and in other cases, it may be absent. This number is present whenever there are multiple timers of same type in a MSP430 micro. For example, there are two Timer A3s in MSP430G2553 micros and so they are labelled as Timer0_A3 and Timer1_A3. The other number in the timer’s name after the timer type letter denotes the number of CC channels available with it.

Shown below is the block diagram of Timer A module. MSP430G2452 has one and MSP430G2553 has two Timer A3 modules. Typically, in any micro’s timer we would expect two things – first a counter block (highlighted in purple) and second CC modules (highlighted in green). As we can see the CC channels share the same TAR register and so they share the similar properties.

Block Diagram

The diagram below shows that clock sources we can use for a Timer A module. It can be clocked with two internal clock sources – ACLK and SMCLK or with two external clock sources – TACLK and INCLK. Actually, INCLK and TACLK are same but one is complementary of the other. These external clocks can be used to make the timer module work as counter.

Counter Block Diagram

Next, we have a prescaler to scale down selected clock source followed by the counter block. What’s interesting about MSP430 timers is the counter’s mode of operation. There are four modes of counter operation and these modes define counting direction:

  • Stop Mode
    It is basically a hold state. All timer registers are retained and the timer is halted.
  • Continuous Mode
    In this mode, the timer counts up from 0 to top value (here 0xFFFF or 65535 in 16-bit Timer A3) and then rolls over to zero.
  • Up Mode
    This mode is same as continuous mode except for the top value. The top value of the timer is set by the value in TACCR0.
  • Up/Down Mode
    In this mode, the timer counts from 0 to TACCR0 value and then rolls back from that value to 0. The period of the timer is twice the TACCR0 counts.

Then we have timer interrupt just as in any other microcontroller.

MSP430G2452 has one Timer A3 module and so it has three CC channels. Likewise, MSP430G2553 has six CC channels. When it comes to extreme engineering, TI sometimes seems to overengineer their products. For example, CC channels are not hard fixed to dedicated pins only unlike other micros. Each CC channel has a set of pins associated with it and so they can be remapped if needed. Shown below is the block diagram of a Timer A3 CC channel. The left side of the diagram has all the components for input capture while the right side is intended for compare-match or PWM output. Common to both is the TACCRn block. It is a very important block.

CC Block Diagram

The basic theme of PWM generation as with any microcontroller is to change the logic state of an output pin when the count in it associated TACCR register matches with the count in its timer’s counter register – simply like a binary comparator. This process is called compare-match process. This is exactly the same idea used in MSP430s. Check the rudimentary timing diagram below. For five successive falling edges of the reference clock, the PWM output is high and for one edge, the output is low, resulting in about 83% duty cycle. The reference clock here is actually the timer clock and the comparison is done by comparing the count stored in TACCRn. Varying TACCRn’s count results in duty cycle change.

PWM

Input capture is somewhat just the opposite of PWM generation. In capture mode, CC channels can be used to record time-related info of an incoming waveform. A timer in this mode can be left to run on its own. When a waveform edge is detected, the corresponding time count of the timer is stored in CC register. With two such consecutive captures, we get a difference in timer’s time counts. This difference can be used to measure frequency if the captured events are alike (two successive rising/falling edges) or duty cycle if the captured events are different (different edges). Again, TACCRn stores the time capture when a capture event occurs.

Capture_modes

As an example, check the arbitrary timing diagram below:

Capture Mode

 

Here four falling edges of reference clock (timer clock) is equal to the high or on time of the captured waveform. Since the reference clock’s period is known, we can deduce pulse width.

WDT+

WDT+ is a 15/16-bit watchdog timer. It is mainly intended to prevent unanticipated loops or stuck up conditions due to malfunctions or firmware bugs. The concept behind any watchdog timer is to regularly refresh a counter so that the count never reaches a predefined limit. If due to any reason this limit is exceeded, a reset is issued, causing the host micro to start over again.

WDT+ Block Diagram

WDT+ can additional be used as an interval timer just like other timers if watchdog timer functionality is not needed. We can, then, use WDT+ for time-base generations.
WDT+ is password protected and so a wrong password causes it to fail and reset immediately. This is the red box in the diagram above. Whenever we need to change anything related to WDT+, we have to enter the correct password which is 0x5A00.  WDT+ consists of a 16-bit counter (green box) but we don’t have access to it. The purple region consists of WDT options. The red and the purple boxes make up Watchdog Control (WDTCTL) register and this is what we are only allowed to code.

Free Running Timer

Free running timers are useful in many cases. Free running timers can be used as random number generators, time delay generators, instance markers, etc. Consider the case of time delay generation for instance. Rather than using wasteful CPU-cycle dependent software delay loops, it is much wiser to use a hardware timer to create precise delays and timed events. In terms of ideal coding, no task should keep CPU busy unnecessarily nor should it keep other tasks waiting for its completion. Best coding and design are achieved if things are arranged in such an orderly way that that there is almost no wastage of any resource at all.

Free Running Timer

By free-running what I really mean is we start a timer at the beginning of our code and keep it running without timer interrupt. We just take note of its counter. Here we will see how to use Timer_A3 like a free running timer and we will use it to blink Launchpad board’s LEDs.

Code Example

#include <msp430.h>


void GPIO_graceInit(void);
void BCSplus_graceInit(void);
void Timer0_A3_graceInit(void);
void System_graceInit(void);
void WDTplus_graceInit(void);


#pragma vector=TIMER0_A1_VECTOR
__interrupt void TIMER0_A1_ISR_HOOK(void)
{

}


void main(void)
{
    /* Stop watchdog timer from timing out during initial start-up. */
    WDTCTL = WDTPW | WDTHOLD;

    /* initialize Config for the MSP430 GPIO */
    GPIO_graceInit();

    /* initialize Config for the MSP430 2xx family clock systems (BCS) */
    BCSplus_graceInit();

    /* initialize Config for the MSP430 A3 Timer0 */
    Timer0_A3_graceInit();

    /* initialize Config for the MSP430 System Registers */
    System_graceInit();

    /* initialize Config for the MSP430 WDT+ */
    WDTplus_graceInit();

    while(1)
    {
        if(TA0R >= 32768)
        {
            P1OUT |= BIT6;
            P1OUT &= ~BIT0;
        }

        else
        {
            P1OUT |= BIT0;
            P1OUT &= ~BIT6;
        }
    };
}


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

    if (CALBC1_1MHZ != 0xFF) {
        /* Follow recommended flow. First, clear all DCOx and MODx bits. Then
         * apply new RSELx values. Finally, apply new DCOx and MODx bit values.
         */
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_1MHZ;      /* Set DCO to 1MHz */
        DCOCTL = CALDCO_1MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_0 -- If XTS = 0, XT1 = 32768kHz Crystal ; If XTS = 1, XT1 = 0.4 - 1-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_0 | XCAP_1;

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}


void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

    /* Port 1 Output Register */
    P1OUT = 0;

    /* Port 1 Direction Register */
    P1DIR = BIT0 | BIT6;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Port Select Register */
    P2SEL &= ~(BIT6 | BIT7);

    /* Port 2 Direction Register */
    P2DIR = 0;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */
}


void Timer0_A3_graceInit(void)
{
    /* USER CODE START (section: Timer0_A3_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: Timer0_A3_graceInit_prologue) */

    /*
     * TA0CTL, Timer_A3 Control Register
     *
     * TASSEL_2 -- SMCLK
     * ID_3 -- Divider - /8
     * MC_2 -- Continuous Mode
     */
    TA0CTL = TASSEL_2 | ID_3 | MC_2;

    /* USER CODE START (section: Timer0_A3_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: Timer0_A3_graceInit_epilogue) */
}


void System_graceInit(void)
{
    /* USER CODE START (section: System_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: System_graceInit_prologue) */

    /*
     * SR, Status Register
     *
     * ~SCG1 -- Disable System clock generator 1
     * ~SCG0 -- Disable System clock generator 0
     * ~OSCOFF -- Oscillator On
     * ~CPUOFF -- CPU On
     * GIE -- General interrupt enable
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    __bis_SR_register(GIE);

    /* USER CODE START (section: System_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: System_graceInit_epilogue) */
}


void WDTplus_graceInit(void)
{
    /* USER CODE START (section: RTC_B_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: RTC_B_graceInit_prologue) */

    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * WDTHOLD -- Watchdog timer+ is stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * ~WDTTMSEL -- Watchdog mode
     * ~WDTCNTCL -- No action
     * ~WDTSSEL -- SMCLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * ~WDTIS1 -- Watchdog clock source bit1 disabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTHOLD;

    /* USER CODE START (section: RTC_B_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: RTC_B_graceInit_epilogue) */
}

 

Simulation

Free Running Timer Simulation

Explanation

In this demo, Timer_A3 is clocked with SMCLK divided by 8. SMCLK has a frequency of 1MHz and so Timer_A3 is feed with a 125kHz clock. This gives us a timer tick period of 8 microseconds. Since the timer is programmed to operate in continuous mode, i.e. it will count from 0 to top value of 65535, the timer will overflow roughly about every 500 milliseconds. We, thus, have an interval window of 500 milliseconds.

Grace Free Running Timer Settings

    /*
     * TA0CTL, Timer_A3 Control Register
     *
     * TASSEL_2 -- SMCLK
     * ID_3 -- Divider - /8
     * MC_2 -- Continuous Mode
     */
    TA0CTL = TASSEL_2 | ID_3 | MC_2;

We want to blink the Launchpad board’s LEDs at the rate of 250 milliseconds. Thus, we need to check or compare if the counter register TA0R has gone past 50% of the full scale 16-bit value. This is an event marker because with reference to this mark, we toggle the states of the LEDs. The idea shown here is actually the concept with which PWMs are generated by compare-match principle inside timer hardware and so it is worth understanding this idea.

        if(TA0R >= 32768)
        {
            P1OUT |= BIT6;
            P1OUT &= ~BIT0;
        }

        else
        {
            P1OUT |= BIT0;
            P1OUT &= ~BIT6;
        }

 

Demo

Free Running Timer Free Running Timer (1) Free Running Timer (2)

Demo video: https://www.youtube.com/watch?v=OGvswRhb7ds.

Timer Interrupt

We have already seen how to use a timer as a free-running timer. However, in most cases we will need timer interrupts. Timer interrupts are periodic interrupts which means they occur at fixed intervals just like clock ticks. Owing to this nature we can split multiple tasks and make them appear as if all of them are happening concurrently. For instance, we can use an ADC to measure the temperature of a room while using a timer to periodically update the temperature display. This is the main concept in driving segment displays, dot-matrix displays and many more although it is not the only thing we can do with timer interrupts.

serial-7-seg-display-2-board-large_default-12x

A timer interrupt is also at the heart of any typical Real-Time Operating System (RTOS).

Basic RTOS Concept

Here we will stick to a simple example of a seven-segment display-based second counter. Rather than demoing LED blinking with timer interrupt I chose this example because this has many applications in the field of displaying information on LED-based displays. The same concept can be expanded for dot-matrix displays, LED bar graphs, alphanumerical segmented displays and many more.

Code Example

#include <msp430.h>


unsigned int ms = 0;
unsigned int value = 0;

unsigned char n = 0;
unsigned char seg = 0;
const unsigned char num[10] = {0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90};


void GPIO_graceInit(void);
void BCSplus_graceInit(void);
void Timer0_A3_graceInit(void);
void System_graceInit(void);
void WDTplus_graceInit(void);


#pragma vector=TIMER0_A1_VECTOR
__interrupt void TIMER0_A1_ISR_HOOK(void)
{
    ms++;
    if(ms > 999)
    {
        ms = 0;
        value++;

        if(value > 9999)
        {
            value = 0;
        }
    }
    switch(seg)
    {
        case 1:
        {
            n = (value / 1000);
            P2OUT = num[n];
            P1OUT = 0xE0;
            break;
        }

        case 2:
        {
            n = ((value / 100) % 10);
            P2OUT = num[n];
            P1OUT = 0xD0;
            break;
        }

        case 3:
        {
            n = ((value / 10) % 10);
            P2OUT = num[n];
            P1OUT = 0xB0;
            break;
        }

        case 4:
        {
            n = (value % 10);
            P2OUT = num[n];
            P1OUT = 0x70;
            break;
        }
    }

    seg++;
    if(seg > 4)
    {
        seg = 1;
    }

    TA0CTL &= ~TAIFG;
    TAIV &= ~TA0IV_TAIFG;
}


void main(void)
{
    GPIO_graceInit();

    /* initialize Config for the MSP430 2xx family clock systems (BCS) */
    BCSplus_graceInit();

    /* initialize Config for the MSP430 A3 Timer0 */
    Timer0_A3_graceInit();

    /* initialize Config for the MSP430 System Registers */
    System_graceInit();

    /* initialize Config for the MSP430 WDT+ */
    WDTplus_graceInit();

    while(1)
    {
    };
}


void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

    /* Port 1 Output Register */
    P1OUT = 0;

    /* Port 1 Direction Register */
    P1DIR = BIT4 | BIT5 | BIT6 | BIT7;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Port Select Register */
    P2SEL &= ~(BIT6 | BIT7);

    /* Port 2 Direction Register */
    P2DIR = BIT0 | BIT1 | BIT2 | BIT3 | BIT4 | BIT5 | BIT6 | BIT7;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */
}


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

    if (CALBC1_1MHZ != 0xFF) {
        /* Follow recommended flow. First, clear all DCOx and MODx bits. Then
         * apply new RSELx values. Finally, apply new DCOx and MODx bit values.
         */
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_1MHZ;      /* Set DCO to 1MHz */
        DCOCTL = CALDCO_1MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_0 -- If XTS = 0, XT1 = 32768kHz Crystal ; If XTS = 1, XT1 = 0.4 - 1-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_0 | XCAP_1;

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}


void Timer0_A3_graceInit(void)
{
    /* USER CODE START (section: Timer0_A3_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: Timer0_A3_graceInit_prologue) */

    /* TA0CCR0, Timer_A Capture/Compare Register 0 */
    TA0CCR0 = 999;

    /*
     * TA0CTL, Timer_A3 Control Register
     *
     * TASSEL_2 -- SMCLK
     * ID_0 -- Divider - /1
     * MC_1 -- Up Mode
     */
    TA0CTL = TASSEL_2 | ID_0 | MC_1 | TAIE;

    /* USER CODE START (section: Timer0_A3_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: Timer0_A3_graceInit_epilogue) */
}


void System_graceInit(void)
{
    /* USER CODE START (section: System_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: System_graceInit_prologue) */

    /*
     * SR, Status Register
     *
     * ~SCG1 -- Disable System clock generator 1
     * ~SCG0 -- Disable System clock generator 0
     * ~OSCOFF -- Oscillator On
     * ~CPUOFF -- CPU On
     * GIE -- General interrupt enable
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    __bis_SR_register(GIE);

    /* USER CODE START (section: System_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: System_graceInit_epilogue) */
}


void WDTplus_graceInit(void)
{
    /* USER CODE START (section: RTC_B_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: RTC_B_graceInit_prologue) */

    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * WDTHOLD -- Watchdog timer+ is stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * ~WDTTMSEL -- Watchdog mode
     * ~WDTCNTCL -- No action
     * ~WDTSSEL -- SMCLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * ~WDTIS1 -- Watchdog clock source bit1 disabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTHOLD;

    /* USER CODE START (section: RTC_B_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: RTC_B_graceInit_epilogue) */
}

 

Simulation

Timer Interrupt Simulation

 

Explanation

In this demo, Timer_A3 is configured as an up counting timer, i.e. it will count from 0 to a top value determined by the contents of TA0CCR0 register.  Again, SMCLK is used as the clock source for the timer but this time it is not scaled down. SMCLK is set to 1 MHz and so does Timer_A3. This means every one tick of Timer_A3 is one microsecond in duration. To make it appear that all four seven segments are simultaneously on without any flickering, we need to scan them fast enough to fool our eyes. We also have to ensure that each segment gets enough time to light up properly. In order to do so we need to scan the segments at one millisecond rate. To get one millisecond from a timer with one microsecond tick interval, we have to load it with 999, not 1000. This is so because from 0 to 999 the total number of ticks is 1000. In short, 1000 times 1 microsecond equals 1 millisecond. Thus, TA0CCR0 is loaded with 999.

Grace Settings for Timer Interrupt

    /* TA0CCR0, Timer_A Capture/Compare Register 0 */
    TA0CCR0 = 999;

    /*
     * TA0CTL, Timer_A3 Control Register
     *
     * TASSEL_2 -- SMCLK
     * ID_0 -- Divider - /1
     * MC_1 -- Up Mode
     */
    TA0CTL = TASSEL_2 | ID_0 | MC_1 | TAIE;

Everything else in this demo is done inside Timer_A3 interrupt subroutine (ISR). There are two parts inside the ISR. The first as shown below is responsible for counting time.

    ms++;
    if(ms > 999)
    {
        ms = 0;
        value++;

        if(value > 9999)
        {
            value = 0;
        }
    }

The other portion is tasked with the LED segment scanning and data display part. At every millisecond, a new segment is turned on, keeping others off. At the end of the ISR, interrupt flags are cleared.

    switch(seg)
    {
        case 1:
        {
            n = (value / 1000);
            P2OUT = num[n];
            P1OUT = 0xE0;
            break;
        }

        case 2:
        {
            n = ((value / 100) % 10);
            P2OUT = num[n];
            P1OUT = 0xD0;
            break;
        }

        case 3:
        {
            n = ((value / 10) % 10);
            P2OUT = num[n];
            P1OUT = 0xB0;
            break;
        }

        case 4:
        {
            n = (value % 10);
            P2OUT = num[n];
            P1OUT = 0x70;
            break;
        }
    }

    seg++;
    if(seg > 4)
    {
        seg = 1;
    }

    TA0CTL &= ~TAIFG;
    TAIV &= ~TA0IV_TAIFG;

 

Demo

Timer Interrupt Demo

Demo video: https://www.youtube.com/watch?v=8EG9rcOAATo.

Pulse Width Modulation (PWM)

At present PWM hardware is a must for any microcontroller because in many cases, it is needed to extract something more than ones and zeros from it. For example, consider the case of a sine wave generator. Without the aid of a Digital-to-Analog Converter (DAC), generating sine waves seems nearly impossible. However, we can still achieve that using Pulse Width Modulation (PWM) and some mathematical tricks.

PWM Sine Generation

Generating waveforms, pulses with variable widths, patterns, sounds, communications pulses like those in IR remotes, speed control, etc require PWM hardware. Keep in mind that all MSP30x2xxx devices do not have any inbuilt DAC and so if analogue output is needed, we can use PWM with necessary external RC filtering to create analogue output.

A few things must be observed before using MSP430 PWM hardware:

  • Timer A PWMs are general purpose PWMs.
  • There’s no separate option for dead-time.
  • Maximum PWM resolution is 16-bit.
  • CC Channel 0 is not like the other two channels. It has limited output options and in GRACE you’ll notice that you can’t set PWM duty cycle.
  • PWM frequency and resolution are interdependent.
  • PWM pins are limitedly remappable.

 

Code Example

#include <msp430.h>
#include "delay.h"


void GPIO_graceInit(void);
void BCSplus_graceInit(void);
void Timer0_A3_graceInit(void);
void System_graceInit(void);
void WDTplus_graceInit(void);


void main(void)
{
    unsigned int pwm_value = 0;

    /* Stop watchdog timer from timing out during initial start-up. */
    WDTCTL = WDTPW | WDTHOLD;

    /* initialize Config for the MSP430 GPIO */
    GPIO_graceInit();

    /* initialize Config for the MSP430 2xx family clock systems (BCS) */
    BCSplus_graceInit();

    /* initialize Config for the MSP430 A3 Timer0 */
    Timer0_A3_graceInit();

    /* initialize Config for the MSP430 System Registers */
    System_graceInit();

    /* initialize Config for the MSP430 WDT+ */
    WDTplus_graceInit();

    while(1)
    {
          for(pwm_value = 0; pwm_value < 1000; pwm_value++)
          {
              TA0CCR1 = pwm_value;
              TA0CCR2 = pwm_value;
              delay_ms(1);
          }
          for(pwm_value = 999; pwm_value > 0; pwm_value--)
          {
              TA0CCR1 = pwm_value;
              TA0CCR2 = pwm_value;
              delay_ms(1);
          }
    };
}


void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

    /* Port 1 Port Select 2 Register */
    P1SEL2 = BIT4;

    /* Port 1 Output Register */
    P1OUT = 0;

    /* Port 1 Port Select Register */
    P1SEL = BIT1 | BIT4 | BIT6;

    /* Port 1 Direction Register */
    P1DIR = BIT1 | BIT4 | BIT6;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Port Select Register */
    P2SEL &= ~(BIT6 | BIT7);

    /* Port 2 Direction Register */
    P2DIR = 0;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */
}


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

    if (CALBC1_1MHZ != 0xFF) {
        /* Follow recommended flow. First, clear all DCOx and MODx bits. Then
         * apply new RSELx values. Finally, apply new DCOx and MODx bit values.
         */
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_1MHZ;      /* Set DCO to 1MHz */
        DCOCTL = CALDCO_1MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_2 -- If XTS = 0, XT1 = VLOCLK ; If XTS = 1, XT1 = 3 - 16-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_2 | XCAP_1;

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}


void Timer0_A3_graceInit(void)
{
    /* USER CODE START (section: Timer0_A3_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: Timer0_A3_graceInit_prologue) */

    /*
     * TA0CCTL0, Capture/Compare Control Register 0
     *
     * CM_0 -- No Capture
     * CCIS_0 -- CCIxA
     * ~SCS -- Asynchronous Capture
     * ~SCCI -- Latched capture signal (read)
     * ~CAP -- Compare mode
     * OUTMOD_4 -- PWM output mode: 4 - Toggle
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    TA0CCTL0 = CM_0 | CCIS_0 | OUTMOD_4;

    /*
     * TA0CCTL1, Capture/Compare Control Register 1
     *
     * CM_0 -- No Capture
     * CCIS_0 -- CCIxA
     * ~SCS -- Asynchronous Capture
     * ~SCCI -- Latched capture signal (read)
     * ~CAP -- Compare mode
     * OUTMOD_3 -- PWM output mode: 3 - PWM set/reset
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    TA0CCTL1 = CM_0 | CCIS_0 | OUTMOD_3;

    /*
     * TA0CCTL2, Capture/Compare Control Register 2
     *
     * CM_0 -- No Capture
     * CCIS_0 -- CCIxA
     * ~SCS -- Asynchronous Capture
     * ~SCCI -- Latched capture signal (read)
     * ~CAP -- Compare mode
     * OUTMOD_7 -- PWM output mode: 7 - PWM reset/set
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    TA0CCTL2 = CM_0 | CCIS_0 | OUTMOD_7;

    /* TA0CCR0, Timer_A Capture/Compare Register 0 */
    TA0CCR0 = 999;

    /* TA0CCR1, Timer_A Capture/Compare Register 1 */
    TA0CCR1 = 10;

    /* TA0CCR2, Timer_A Capture/Compare Register 2 */
    TA0CCR2 = 10;

    /*
     * TA0CTL, Timer_A3 Control Register
     *
     * TASSEL_2 -- SMCLK
     * ID_0 -- Divider - /1
     * MC_1 -- Up Mode
     */
    TA0CTL = TASSEL_2 | ID_0 | MC_1;

    /* USER CODE START (section: Timer0_A3_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: Timer0_A3_graceInit_epilogue) */
}


void System_graceInit(void)
{
    /* USER CODE START (section: System_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: System_graceInit_prologue) */

    /* Clear oscillator fault flag with software delay */
    do
    {
        // Clear OSC fault flag
        IFG1 &= ~OFIFG;

        // 50us delay
        __delay_cycles(50);
    } while (IFG1 & OFIFG);

    /*
     * SR, Status Register
     *
     * ~SCG1 -- Disable System clock generator 1
     * ~SCG0 -- Disable System clock generator 0
     * ~OSCOFF -- Oscillator On
     * ~CPUOFF -- CPU On
     * GIE -- General interrupt enable
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    __bis_SR_register(GIE);

    /* USER CODE START (section: System_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: System_graceInit_epilogue) */
}


void WDTplus_graceInit(void)
{
    /* USER CODE START (section: RTC_B_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: RTC_B_graceInit_prologue) */

    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * WDTHOLD -- Watchdog timer+ is stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * ~WDTTMSEL -- Watchdog mode
     * ~WDTCNTCL -- No action
     * ~WDTSSEL -- SMCLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * ~WDTIS1 -- Watchdog clock source bit1 disabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTHOLD;

    /* USER CODE START (section: RTC_B_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: RTC_B_graceInit_epilogue) */
}

 

Simulation

PWM Simulation

Explanation

Just as in the previous example, SMCLK is set to 1 MHz. Timer_A3 is also setup for up counting with a top value of 999, resulting in 1ms time duration. Note no interrupt is used and other CCR registers are loaded with 10 – an arbitrary value.

    /* TA0CCR0, Timer_A Capture/Compare Register 0 */
    TA0CCR0 = 999;

    /* TA0CCR1, Timer_A Capture/Compare Register 1 */
    TA0CCR1 = 10;

    /* TA0CCR2, Timer_A Capture/Compare Register 2 */
    TA0CCR2 = 10;

    /*
     * TA0CTL, Timer_A3 Control Register
     *
     * TASSEL_2 -- SMCLK
     * ID_0 -- Divider - /1
     * MC_1 -- Up Mode
     */
    TA0CTL = TASSEL_2 | ID_0 | MC_1;

Prior to Timer_A3 setup, CC channels are setup. OUTMOD is that stuff that sets PWM type.

    /*
     * TA0CCTL0, Capture/Compare Control Register 0
     *
     * CM_0 -- No Capture
     * CCIS_0 -- CCIxA
     * ~SCS -- Asynchronous Capture
     * ~SCCI -- Latched capture signal (read)
     * ~CAP -- Compare mode
     * OUTMOD_4 -- PWM output mode: 4 - Toggle
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    TA0CCTL0 = CM_0 | CCIS_0 | OUTMOD_4;

    /*
     * TA0CCTL1, Capture/Compare Control Register 1
     *
     * CM_0 -- No Capture
     * CCIS_0 -- CCIxA
     * ~SCS -- Asynchronous Capture
     * ~SCCI -- Latched capture signal (read)
     * ~CAP -- Compare mode
     * OUTMOD_3 -- PWM output mode: 3 - PWM set/reset
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    TA0CCTL1 = CM_0 | CCIS_0 | OUTMOD_3;

    /*
     * TA0CCTL2, Capture/Compare Control Register 2
     *
     * CM_0 -- No Capture
     * CCIS_0 -- CCIxA
     * ~SCS -- Asynchronous Capture
     * ~SCCI -- Latched capture signal (read)
     * ~CAP -- Compare mode
     * OUTMOD_7 -- PWM output mode: 7 - PWM reset/set
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    TA0CCTL2 = CM_0 | CCIS_0 | OUTMOD_7;

In this demo, three different PWMs are set to show the differences.

Grace PWM1 Grace PWM2 Grace PWM3

PWMs can be of the following types:

PWM Modes

Note that CCR0 has limited PWM options and it is the value of its register that sets PWM frequency. The other CCR registers are loaded with values that determine respective PWM duty cycles.

Demo

PWM Demo 1 PWM Capture Demo 1 PWM Demo 2 PWM Capture Demo 2

Demo video: https://www.youtube.com/watch?v=dbSPi6LbsQg.

Timer Input Capture

In many cases, it is needed to measure the timing info like period, frequency, duty cycle, etc of an incoming signal. Based on these data we can find out the RPM of a robot’s wheel, the pulse widths of an IR remote stream carrying command information, the frequency of AC mains, the patterns of an incoming waveform, etc. Hence comes the purpose of timer input captures.

Capture

We know that Timer A3 has three such CC channels and so there are three capture inputs per Timer A module. We can use these channels to capture incoming waveforms of unknown frequencies/duty cycles and have them measured with respect to a known clock like SMCLK.

Again a few things must be observed before using MSP430 input capture hardware:

  • Signals coming to input pins must never cross the max. VDD limit or fall below ground level (i.e. sensing negative potentials).
  • It is better to galvanically isolate input pins if they are to sense external high voltage signals.
  • Input pins must not be left floating.
  • Unless needed, it is wise not to use RC filters for inputs.
  • Input capture pins are limited remappable just like PWM pins.
  • Timer clock must be set as such that we get maximum measurement resolution without compromising reliability.
  • Timer overruns/overflows must be taken into account.
  • Timer capture inputs can be tied to power pins internally and connecting so results in no measurements. When not capturing anything, select stop mode and clear the timer.

 

Code Example

#include <msp430.h>
#include "delay.h"
#include "lcd.h"


unsigned int overflow_count = 0;
unsigned int pulse_ticks = 0;
unsigned int start_time = 0;
unsigned int end_time = 0;


void GPIO_graceInit(void);
void BCSplus_graceInit(void);
void Timer0_A3_graceInit(void);
void Timer1_A3_graceInit(void);
void System_graceInit(void);
void WDTplus_graceInit(void);
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned long value);


#pragma vector=TIMER1_A1_VECTOR
__interrupt void TIMER1_A1_ISR_HOOK(void)
{
    if(TA1IV == TA1IV_TACCR1)
    {
        end_time = TA1CCR1;
        pulse_ticks = (end_time - start_time);
        start_time = end_time;
        TA1CCTL1 &= ~CCIFG;
    }
}


void main(void)
{
    unsigned char i = 0;
    unsigned long time_period = 0;

    /* Stop watchdog timer from timing out during initial start-up. */
    WDTCTL = WDTPW | WDTHOLD;

    /* initialize Config for the MSP430 GPIO */
    GPIO_graceInit();

    /* initialize Config for the MSP430 2xx family clock systems (BCS) */
    BCSplus_graceInit();

    /* initialize Config for the MSP430 A3 Timer0 */
    Timer0_A3_graceInit();

    /* initialize Config for the MSP430 A3 Timer0 */
    Timer1_A3_graceInit();

    /* initialize Config for the MSP430 System Registers */
    System_graceInit();

    /* initialize Config for the MSP430 WDT+ */
    WDTplus_graceInit();

    LCD_init();
    LCD_clear_home();

    LCD_goto(0, 0);
    LCD_putstr("Capt./us:");
    delay_ms(10);

    while(1)
    {
        if((P1IN & BIT3) == 0)
        {
            P1OUT |= BIT0;
            while((P1IN & BIT3) == 0);

            i++;
            if(i > 9)
            {
                i = 0;
            }
            P1OUT &= ~BIT0;
        }

        switch(i)
        {
            case 1:
            {
                TA0CCR0 = 9999;
                LCD_goto(0, 1);
                LCD_putstr("Period/ms:    20");
                break;
            }
            case 2:
            {
                TA0CCR0 = 4999;
                LCD_goto(0, 1);
                LCD_putstr("Period/ms:    10");
                break;
            }
            case 3:
            {
                TA0CCR0 = 1999;
                LCD_goto(0, 1);
                LCD_putstr("Period/ms:     4");
                break;
            }
            case 4:
            {
                TA0CCR0 = 999;
                LCD_goto(0, 1);
                LCD_putstr("Period/ms:     2");
                break;
            }
            case 5:
            {
                TA0CCR0 = 166;
                LCD_goto(0, 1);
                LCD_putstr("Period/us:   334");
                break;
            }
            case 6:
            {
                TA0CCR0 = 1230;
                LCD_goto(0, 1);
                LCD_putstr("Period/ms:   2.5");
                break;
            }
            case 7:
            {
                TA0CCR0 = 2626;
                LCD_goto(0, 1);
                LCD_putstr("Period/ms:   5.3");
                break;
            }
            case 8:
            {
                TA0CCR0 = 4579;
                LCD_goto(0, 1);
                LCD_putstr("Period/ms:   9.2");
                break;
            }
            case 9:
            {
                TA0CCR0 = 499;
                LCD_goto(0, 1);
                LCD_putstr("Period/ms:     1");
                break;
            }
            default:
            {
                TA0CCR0 = 6964;
                LCD_goto(0, 1);
                LCD_putstr("Period/ms:  13.9");
                break;
            }
        }

        time_period = (pulse_ticks >> 1);
        lcd_print(10, 0, time_period);
        delay_ms(400);
    };
}


void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

    /* Port 1 Output Register */
    P1OUT = BIT3;

    /* Port 1 Port Select Register */
    P1SEL = BIT1;

    /* Port 1 Direction Register */
    P1DIR = BIT0 | BIT1 | BIT6 | BIT7;

    /* Port 1 Resistor Enable Register */
    P1REN = BIT3;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Port Select Register */
    P2SEL = BIT1;

    /* Port 2 Port Select Register */
    P2SEL &= ~(BIT6 | BIT7);

    /* Port 2 Direction Register */
    P2DIR = 0;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* Port 3 Output Register */
    P3OUT = 0;

    /* Port 3 Direction Register */
    P3DIR = 0;

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */
}


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

    if (CALBC1_1MHZ != 0xFF) {
        /* Follow recommended flow. First, clear all DCOx and MODx bits. Then
         * apply new RSELx values. Finally, apply new DCOx and MODx bit values.
         */
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_1MHZ;      /* Set DCO to 1MHz */
        DCOCTL = CALDCO_1MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_2 -- If XTS = 0, XT1 = VLOCLK ; If XTS = 1, XT1 = 3 - 16-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_2 | XCAP_1;

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}


void Timer0_A3_graceInit(void)
{
    /* USER CODE START (section: Timer0_A3_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: Timer0_A3_graceInit_prologue) */

    /*
     * TA0CCTL0, Capture/Compare Control Register 0
     *
     * CM_0 -- No Capture
     * CCIS_0 -- CCIxA
     * ~SCS -- Asynchronous Capture
     * ~SCCI -- Latched capture signal (read)
     * ~CAP -- Compare mode
     * OUTMOD_4 -- PWM output mode: 4 - Toggle
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    TA0CCTL0 = CM_0 | CCIS_0 | OUTMOD_4;

    /* TA0CCR0, Timer_A Capture/Compare Register 0 */
    TA0CCR0 = 9999;

    /*
     * TA0CTL, Timer_A3 Control Register
     *
     * TASSEL_2 -- SMCLK
     * ID_1 -- Divider - /2
     * MC_1 -- Up Mode
     */
    TA0CTL = TASSEL_2 | ID_1 | MC_1;

    /* USER CODE START (section: Timer0_A3_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: Timer0_A3_graceInit_epilogue) */
}


void Timer1_A3_graceInit(void)
{
    /* USER CODE START (section: Timer1_A3_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: Timer1_A3_graceInit_prologue) */

    /*
     * TA1CCTL0, Capture/Compare Control Register 0
     *
     * CM_1 -- Rising Edge
     * CCIS_0 -- CCIxA
     * ~SCS -- Asynchronous Capture
     * ~SCCI -- Latched capture signal (read)
     * ~CAP -- Compare mode
     * OUTMOD_1 -- PWM output mode: 1 - Set
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    TA1CCTL0 = CM_1 | CCIS_0 | OUTMOD_1;

    /*
     * TA1CCTL1, Capture/Compare Control Register 1
     *
     * CM_1 -- Rising Edge
     * CCIS_0 -- CCIxA
     * SCS -- Sychronous Capture
     * ~SCCI -- Latched capture signal (read)
     * CAP -- Capture mode
     * OUTMOD_0 -- PWM output mode: 0 - OUT bit value
     *
     * Note: ~SCCI indicates that SCCI has value zero
     */
    TA1CCTL1 = CM_1 | CCIS_0 | SCS | CAP | OUTMOD_0 | CCIE;

    /*
     * TA1CTL, Timer_A3 Control Register
     *
     * TASSEL_2 -- SMCLK
     * ID_0 -- Divider - /1
     * MC_2 -- Continuous Mode
     */
    TA1CTL = TASSEL_2 | ID_0 | MC_2;

    /* USER CODE START (section: Timer1_A3_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: Timer1_A3_graceInit_epilogue) */
}


void System_graceInit(void)
{
    /* USER CODE START (section: System_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: System_graceInit_prologue) */

    /* Clear oscillator fault flag with software delay */
    do
    {
        // Clear OSC fault flag
        IFG1 &= ~OFIFG;

        // 50us delay
        __delay_cycles(50);
    } while (IFG1 & OFIFG);

    /*
     * SR, Status Register
     *
     * ~SCG1 -- Disable System clock generator 1
     * ~SCG0 -- Disable System clock generator 0
     * ~OSCOFF -- Oscillator On
     * ~CPUOFF -- CPU On
     * GIE -- General interrupt enable
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    __bis_SR_register(GIE);

    /* USER CODE START (section: System_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: System_graceInit_epilogue) */
}


void WDTplus_graceInit(void)
{
    /* USER CODE START (section: RTC_B_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: RTC_B_graceInit_prologue) */

    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * WDTHOLD -- Watchdog timer+ is stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * ~WDTTMSEL -- Watchdog mode
     * ~WDTCNTCL -- No action
     * ~WDTSSEL -- SMCLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * ~WDTIS1 -- Watchdog clock source bit1 disabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTHOLD;

    /* USER CODE START (section: RTC_B_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: RTC_B_graceInit_epilogue) */
}


void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned long value)
{
    char tmp[6] = {0x20, 0x20, 0x20, 0x20, 0x20, 0x20} ;

    tmp[0] = (((value / 100000) % 10) + 0x30);
    tmp[1] = (((value / 10000) % 10) + 0x30);
    tmp[2] = (((value / 1000) % 10) + 0x30);
    tmp[3] = (((value / 100) % 10) + 0x30);
    tmp[4] = (((value / 10) % 10) + 0x30);
    tmp[5] = ((value % 10) + 0x30);

    LCD_goto(x_pos, y_pos);
    LCD_putstr(tmp);
}

 

Simulation

Capture sim

Explanation

For this demo, I used a MSP430G2553 micro as it has two independent timers. One of these timers is used as a waveform generator and the other as a waveform capture unit.

Timer0_A3 is set up as a PWM output generator with only CC channel 0 active. CC channel 0 is set just to toggle its output logic states and not duty cycle. SMCLK is divided by two and feed to this timer, i.e. this timer has a tick frequency of 500 kHz. As stated before, in order to change the PWM frequency, we need to change the value of TA0CCR0 in up mode counting. We will employ this fact to alter waveform frequency in the main loop.

    /*
     * TA0CCTL0, Capture/Compare Control Register 0
     *
     * CM_0 -- No Capture
     * CCIS_0 -- CCIxA
     * ~SCS -- Asynchronous Capture
     * ~SCCI -- Latched capture signal (read)
     * ~CAP -- Compare mode
     * OUTMOD_4 -- PWM output mode: 4 - Toggle
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    TA0CCTL0 = CM_0 | CCIS_0 | OUTMOD_4;

    /* TA0CCR0, Timer_A Capture/Compare Register 0 */
    TA0CCR0 = 9999;

    /*
     * TA0CTL, Timer_A3 Control Register
     *
     * TASSEL_2 -- SMCLK
     * ID_1 -- Divider - /2
     * MC_1 -- Up Mode
     */
    TA0CTL = TASSEL_2 | ID_1 | MC_1;

Grace Timer Capture 1

The other timer – Timer1_A3 is setup to capture the waveform generated by Timer0_A3. Timer interrupt is used to quickly respond to incoming waveform’s rising edges. We will be measuring the time period of Timer0_A3’s waveform and so we are interest in measuring the time difference between like edges – here rising edges. To get high accuracy, SMCLK is not divided. This gives us a minimum capture period of 1 microsecond and a maximum of about 65 milliseconds provided that we are using continuous mode counting. In other words, Timer1_A3 has double scanning rate than what Timer0_A3 can throw at it – a concept similar to Nyquist theorem.

/*
     * TA1CCTL0, Capture/Compare Control Register 0
     *
     * CM_1 -- Rising Edge
     * CCIS_0 -- CCIxA
     * ~SCS -- Asynchronous Capture
     * ~SCCI -- Latched capture signal (read)
     * ~CAP -- Compare mode
     * OUTMOD_1 -- PWM output mode: 1 - Set
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    TA1CCTL0 = CM_1 | CCIS_0 | OUTMOD_1;

    /*
     * TA1CCTL1, Capture/Compare Control Register 1
     *
     * CM_1 -- Rising Edge
     * CCIS_0 -- CCIxA
     * SCS -- Sychronous Capture
     * ~SCCI -- Latched capture signal (read)
     * CAP -- Capture mode
     * OUTMOD_0 -- PWM output mode: 0 - OUT bit value
     *
     * Note: ~SCCI indicates that SCCI has value zero
     */
    TA1CCTL1 = CM_1 | CCIS_0 | SCS | CAP | OUTMOD_0 | CCIE;

    /*
     * TA1CTL, Timer_A3 Control Register
     *
     * TASSEL_2 -- SMCLK
     * ID_0 -- Divider - /1
     * MC_2 -- Continuous Mode
     */
    TA1CTL = TASSEL_2 | ID_0 | MC_2;

Grace Timer Capture 2

Grace Timer Capture 3

In the timer ISR, we need to check first what caused the interrupt. If it was due to a rising edge capture then we have to take note of current time count in TA1CCR1 since we are using input capture channel 1 of Timer1_A3. Two such time counts are needed to find out the time difference between two adjacent rising edges. This gives us the time period of the captured incoming waveform. However, we are not stopping input capture or the timer even after two successive capture events. This is because we are continuous monitoring the incoming waveform.

#pragma vector=TIMER1_A1_VECTOR
__interrupt void TIMER1_A1_ISR_HOOK(void)
{
    if(TA1IV == TA1IV_TACCR1)
    {
        end_time = TA1CCR1;
        pulse_ticks = (end_time - start_time);
        start_time = end_time;
        TA1CCTL1 &= ~CCIFG;
    }
}

In the main loop, we are using the Launchpad’s user button to alter TA0CCR0’s value and hence PWM frequency or period. There are ten different time periods to select. The main code also displays captured waveform time period vs expected time period on a LCD.

        if((P1IN & BIT3) == 0)
        {
            P1OUT |= BIT0;
            while((P1IN & BIT3) == 0);

            i++;
            if(i > 9)
            {
                i = 0;
            }
            P1OUT &= ~BIT0;
        }

        switch(i)
        {
            case 1:
            {
                TA0CCR0 = 9999;
                LCD_goto(0, 1);
                LCD_putstr("Period/ms:    20");
                break;
            }
            case 2:
            {
                TA0CCR0 = 4999;
                LCD_goto(0, 1);
                LCD_putstr("Period/ms:    10");
                break;
            }
            case 3:
            {
                TA0CCR0 = 1999;
                LCD_goto(0, 1);
                LCD_putstr("Period/ms:     4");
                break;
            }
            case 4:
            {
                TA0CCR0 = 999;
                LCD_goto(0, 1);
                LCD_putstr("Period/ms:     2");
                break;
            }
            case 5:
            {
                TA0CCR0 = 166;
                LCD_goto(0, 1);
                LCD_putstr("Period/us:   334");
                break;
            }
            case 6:
            {
                TA0CCR0 = 1230;
                LCD_goto(0, 1);
                LCD_putstr("Period/ms:   2.5");
                break;
            }
            case 7:
            {
                TA0CCR0 = 2626;
                LCD_goto(0, 1);
                LCD_putstr("Period/ms:   5.3");
                break;
            }
            case 8:
            {
                TA0CCR0 = 4579;
                LCD_goto(0, 1);
                LCD_putstr("Period/ms:   9.2");
                break;
            }
            case 9:
            {
                TA0CCR0 = 499;
                LCD_goto(0, 1);
                LCD_putstr("Period/ms:     1");
                break;
            }
            default:
            {
                TA0CCR0 = 6964;
                LCD_goto(0, 1);
                LCD_putstr("Period/ms:  13.9");
                break;
            }
        }

        time_period = (pulse_ticks >> 1);
        lcd_print(10, 0, time_period);
        delay_ms(400);

 

Demo

Capture (1)

Capture Logic

Capture (2)

Demo video: https://www.youtube.com/watch?v=CHLkXf8MqbQ.

Watchdog Timer Plus (WDT+)

At present, any commercial/industrial/professional electronic good must pass a number of tests and obtain some certifications before its introduction to market, most notably CE, FCC, UL and TUV certifications. This is so as it is imperative that a device pass Electromagnetic Compliance (EMC) test not just for flawless performance but also for user safety. Hobby electronics projects don’t need these and most hobbyists don’t fully understand the issues caused by EMI or what causes them. This is why many simple robots like the line follower robot shown below fail to perform properly in robotics competitions. Some of them seem to behave erratically while others seem to be unresponsive after working for some time. If both hardware and software designs are well designed and tested against harsh conditions, the chances of failure reduce significantly. A hardware designer should consider proper PCB layout and component placement as well and component selection. Likewise, a programmer should avoid polling-based solutions, blocking codes, unwanted loops, and should consider using watchdog timers and other coding tricks. To avoid getting a device into a stalled state, both hardware and software ends must merge properly and accordingly.

WDT+ Line Follower

A watchdog timer is basically a fail-safe time. It is a very important module when considering an embedded-system-based design that is likely to operate in noisy environments or when there is a probability of its the application firmware to get stuck due to malfunctions. Any programmer would want to get that stuck up firmware up and running again after recovering from the issue that cause it to fail. All MSP430s are equipped with a WDT module and here we will see how it helps us in recovering it when we simulate an entry into an unanticipated loop.

Code Example

#include <msp430.h>


void GPIO_graceInit(void);
void BCSplus_graceInit(void);
void InterruptVectors_graceInit(void);
void System_graceInit(void);
void WDTplus_graceInit(void);


void main(void)
{
    // Stop WDT+
    WDTCTL = WDTPW + WDTHOLD; // Set hold bit and clear others

    /* initialize Config for the MSP430 GPIO */
    GPIO_graceInit();

    /* initialize Config for the MSP430 2xx family clock systems (BCS) */
    BCSplus_graceInit();

    /* initialize Config for the MSP430 System Registers */
    System_graceInit();

    /* initialize Config for the MSP430 WDT+ */
    WDTplus_graceInit();

    while(1)
    {
        P1OUT ^= BIT0;
        _delay_cycles(60000);

        WDTCTL = WDTPW | WDTCNTCL;


        if((P1IN & BIT3) == !BIT3)
        {
            WDTCTL = WDTPW | WDTSSEL;

            while(1)
            {
                P1OUT ^= BIT6;
                _delay_cycles(45000);
            };
        }
    }
}


void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

    /* Port 1 Output Register */
    P1OUT = BIT3;

    /* Port 1 Direction Register */
    P1DIR = BIT0 | BIT6;

    /* Port 1 Resistor Enable Register */
    P1REN = BIT3;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Port Select Register */
    P2SEL &= ~(BIT6 | BIT7);

    /* Port 2 Direction Register */
    P2DIR = 0;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */
}


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

    if (CALBC1_1MHZ != 0xFF) {
        /* Follow recommended flow. First, clear all DCOx and MODx bits. Then
         * apply new RSELx values. Finally, apply new DCOx and MODx bit values.
         */
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_1MHZ;      /* Set DCO to 1MHz */
        DCOCTL = CALDCO_1MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_3 -- Divide by 8
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_3;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_2 -- If XTS = 0, XT1 = VLOCLK ; If XTS = 1, XT1 = 3 - 16-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_2 | XCAP_1;

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}


void System_graceInit(void)
{
    /* USER CODE START (section: System_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: System_graceInit_prologue) */

    /* Clear oscillator fault flag with software delay */
    do
    {
        // Clear OSC fault flag
        IFG1 &= ~OFIFG;

        // 50us delay
        __delay_cycles(50);
    } while (IFG1 & OFIFG);

    /*
     * SR, Status Register
     *
     * ~SCG1 -- Disable System clock generator 1
     * ~SCG0 -- Disable System clock generator 0
     * ~OSCOFF -- Oscillator On
     * ~CPUOFF -- CPU On
     * GIE -- General interrupt enable
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    __bis_SR_register(GIE);

    /* USER CODE START (section: System_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: System_graceInit_epilogue) */
}


void WDTplus_graceInit(void)
{
    /* USER CODE START (section: RTC_B_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: RTC_B_graceInit_prologue) */

    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * ~WDTHOLD -- Watchdog timer+ is not stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * ~WDTTMSEL -- Watchdog mode
     * ~WDTCNTCL -- No action
     * WDTSSEL -- ACLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * ~WDTIS1 -- Watchdog clock source bit1 disabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTSSEL;

    /* USER CODE START (section: RTC_B_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: RTC_B_graceInit_epilogue) */
}

 

Simulation

WDT+ Simulation

 

Explanation

Unlike previous timer examples, 12 kHz ACLK is used. ACLK is divided by 8 to make it 1.5 kHz low speed clock source for the WDT+ module. This clock is further prescaled by 32768 to get a WDT+ timeout of about 22 seconds.

Grace WDT+ Settings

    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * ~WDTHOLD -- Watchdog timer+ is not stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * ~WDTTMSEL -- Watchdog mode
     * ~WDTCNTCL -- No action
     * WDTSSEL -- ACLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * ~WDTIS1 -- Watchdog clock source bit1 disabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTSSEL;

In the main loop, P1.0 toggles without any issue. WDT+ is regularly refreshed. However, when the user button is pressed, WDT+ is no longer refreshed and P1.6 LED is toggled inside a simulated undesired loop. P1.0 LED appears to have gotten stuck. This causes the WDT+ to cross maximum timeout limit and thereby trigger a reset.

        P1OUT ^= BIT0;
        _delay_cycles(60000);

        WDTCTL = WDTPW | WDTCNTCL;


        if((P1IN & BIT3) == !BIT3)
        {
            WDTCTL = WDTPW | WDTSSEL;

            while(1)
            {
                P1OUT ^= BIT6;
                _delay_cycles(45000);
            };
        }

In reality, the timeout time may vary due to variations in ACLK time period. In my demo, I noticed this variation. During that time, I found that ACLK is about 10 kHz instead of 12 kHz.

Demo

WDT+ (1) WDT+ (2)

Demo video: https://www.youtube.com/watch?v=vYCLeWZZt7U.

WDT+ as an Interval Timer

There are cases in which we don’t need the protection feature of WDT+. This leaves with a free timer which can be used for other jobs. As I said before, Americans think differently than the rest of the world and here is one proof of that ingenious concept. However, since WDT+ was intended for a special mission, we cannot expect it to be completely like other timers. For example, it doesn’t have any capture-compare pin associated with it nor do we have access to its counter. Even with these limitations, it is still a useful bonus.

rtos2

A lame demonstration of RTOS concept is demoed here.

 

Code Example

#include <msp430.h>


unsigned char state = 0;


void GPIO_graceInit(void);
void BCSplus_graceInit(void);
void System_graceInit(void);
void WDTplus_graceInit(void);


/*
 *  ======== Watchdog Timer Interval Interrupt Handler Generation ========
 */
#pragma vector=WDT_VECTOR
__interrupt void WDT_ISR_HOOK(void)
{
    state++;

    if(state >= 3)
    {
        state = 0;
    }

    IFG1 &= ~WDTIFG;
}


void main(void)
{
    unsigned int s = 0;

    unsigned char i = 0;

    /* Stop watchdog timer from timing out during initial start-up. */
    WDTCTL = WDTPW | WDTHOLD;

    /* initialize Config for the MSP430 GPIO */
    GPIO_graceInit();

    /* initialize Config for the MSP430 2xx family clock systems (BCS) */
    BCSplus_graceInit();

    /* initialize Config for the MSP430 System Registers */
    System_graceInit();

    /* initialize Config for the MSP430 WDT+ */
    WDTplus_graceInit();

    while(1)
    {
        switch(state)
        {
            case 1:
            {
                switch(i)
                {
                    case 0:
                    {
                        P1OUT &= ~BIT0;
                        break;
                    }
                    default:
                    {
                        P1OUT |= BIT0;
                        break;
                    }
                }

                break;
            }
            case 2:
            {
                _delay_cycles(1);
                s++;
                if(s > 20000)
                {
                    P1OUT ^= BIT6;
                    s = 0;
                }

                break;
            }
            default:
            {
                if((P1IN & BIT3) != BIT3)
                {
                    i ^= BIT0;
                }
                break;
            }
        }
    }
}


void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

    /* Port 1 Output Register */
    P1OUT = BIT3;

    /* Port 1 Direction Register */
    P1DIR = BIT0 | BIT6;

    /* Port 1 Resistor Enable Register */
    P1REN = BIT3;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Port Select Register */
    P2SEL &= ~(BIT6 | BIT7);

    /* Port 2 Direction Register */
    P2DIR = 0;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */
}


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

    if (CALBC1_1MHZ != 0xFF) {
        /* Follow recommended flow. First, clear all DCOx and MODx bits. Then
         * apply new RSELx values. Finally, apply new DCOx and MODx bit values.
         */
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_1MHZ;      /* Set DCO to 1MHz */
        DCOCTL = CALDCO_1MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_2 -- Divide by 4
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_2;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_2 -- If XTS = 0, XT1 = VLOCLK ; If XTS = 1, XT1 = 3 - 16-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_2 | XCAP_1;

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}


void System_graceInit(void)
{
    /* USER CODE START (section: System_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: System_graceInit_prologue) */

    /* Clear oscillator fault flag with software delay */
    do
    {
        // Clear OSC fault flag
        IFG1 &= ~OFIFG;

        // 50us delay
        __delay_cycles(50);
    } while (IFG1 & OFIFG);

    /*
     * IFG1, Interrupt Flag Register 1
     *
     * ~ACCVIFG -- No interrupt pending
     * ~NMIIFG -- No interrupt pending
     * ~OFIFG -- No interrupt pending
     * WDTIFG -- Interrupt pending
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    IFG1 &= ~(WDTIFG);

    /*
     * IE1, Interrupt Enable Register 1
     *
     * ~ACCVIE -- Interrupt not enabled
     * ~NMIIE -- Interrupt not enabled
     * ~OFIE -- Interrupt not enabled
     * WDTIE -- Interrupt enabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    IE1 |= WDTIE;

    /*
     * SR, Status Register
     *
     * ~SCG1 -- Disable System clock generator 1
     * ~SCG0 -- Disable System clock generator 0
     * ~OSCOFF -- Oscillator On
     * ~CPUOFF -- CPU On
     * GIE -- General interrupt enable
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    __bis_SR_register(GIE);

    /* USER CODE START (section: System_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: System_graceInit_epilogue) */
}


void WDTplus_graceInit(void)
{
    /* USER CODE START (section: RTC_B_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: RTC_B_graceInit_prologue) */

    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * ~WDTHOLD -- Watchdog timer+ is not stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * WDTTMSEL -- Interval timer mode
     * ~WDTCNTCL -- No action
     * ~WDTSSEL -- SMCLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * WDTIS1 -- Watchdog clock source bit1 enabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTTMSEL | WDTIS1;

    /* USER CODE START (section: RTC_B_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: RTC_B_graceInit_epilogue) */
}

 

Simulation

WDT+ Interval Mode Simulation

Explanation

WDT+ is used here as an interval timer with a time period of 512 microseconds. After every 512 microseconds, there is a WDT+ interrupt.

/*
* WDTCTL, Watchdog Timer+ Register
*
* WDTPW -- Watchdog password
* ~WDTHOLD -- Watchdog timer+ is not stopped
* ~WDTNMIES -- NMI on rising edge
* ~WDTNMI -- Reset function
* WDTTMSEL -- Interval timer mode
* ~WDTCNTCL -- No action
* ~WDTSSEL -- SMCLK
* ~WDTIS0 -- Watchdog clock source bit0 disabled
* WDTIS1 -- Watchdog clock source bit1 enabled
*
* Note: ~<BIT> indicates that <BIT> has value zero
*/
WDTCTL = WDTPW | WDTTMSEL | WDTIS1;

Grace WDT+ Interval Mode Settings

Inside this interrupt we just change the value of a variable called task.

#pragma vector=WDT_VECTOR
__interrupt void WDT_ISR_HOOK(void)
{
    task++;

    if(task >= 3)
    {
        task = 0;
    }

    IFG1 &= ~WDTIFG;
}

The variable task in the main loop is used to switch between different tasks, each task having same time frame and priority. This is called a task scheduling.

        switch(task)
        {
            case 1:
            {
                switch(i)
                {
                    case 0:
                    {
                        P1OUT &= ~BIT0;
                        break;
                    }
                    default:
                    {
                        P1OUT |= BIT0;
                        break;
                    }
                }

                break;
            }
            case 2:
            {
                _delay_cycles(1);
                s++;
                if(s > 20000)
                {
                    P1OUT ^= BIT6;
                    s = 0;
                }

                break;
            }
            default:
            {
                if((P1IN & BIT3) != BIT3)
                {
                    i ^= BIT0;
                }
                break;
            }
        }

Task 1 lights P1.0 LED based on the logic state of the user button. Task 0 checks the state of the user button. Task 2 blinks P1.6 LED independent of the other tasks. The total time for the completion of all these processes is about 1.5 milliseconds – a very short time. In this method, no task waits for other tasks. The whole process is so fast to human eyes that everything this code does will appear to occur parallelly.

Demo

WDT IM (1) WDT IM (2)

Demo video: https://www.youtube.com/watch?v=BNwxdgLQerU.

Analogue Frontend Overview

MSP stands for mixed signal processor. Mixed signal means combination of both analogue and digital. We can, therefore, expect a great deal of cool stuffs when it comes to their analogue features. Chips vary in features and so do the analogue peripherals. There are various types of analogue peripherals offered by MSP430s and we can categorize them into four basic categories:

Analogue-to-Digital Converters (ADC)

ADC are used to measure time-varying voltages. They digitize analogue signals by representing them in quantized binary formats. The most common ADCs in MSP430s are ADC10 and ADC12. These are Successive Approximation (SAR) ADCs. Both of these ADCs are similar in many aspects except in resolution. Some MSP430 devices have more advanced high-resolution delta-sigma ADCs like SD16_A and SD24_A. All MSP430s additionally have internal temperature sensors.

ADC

Digital-to-Analogue Converters (DAC)

DACs are opposites of ADCs. They give variable voltage output with respect to binary inputs and can be used to generate waveforms, audio signals, wave patterns, control actuator and power supplies, etc. 12-bit DACs – DAC12 are available in some advanced MSP430 devices.

DAC

Comparators (COMP)

A comparator compares two analogue voltage levels. This comparison results in an indication of which signal is at a higher/lower voltage level than the other. In simple terms, it is a one-bit ADC. Though it may look that a comparator is unnecessary when we have a good built-in ADC, it is otherwise. A comparator is a very important analogue building block. A whole lot of electronics is based on it. Examples of such electronics include oscillators, level sensing, VU meters, capacitive touch sensing, measurement devices, etc. A LC meter is a perfect example. A LC meter is usually based on an oscillator. This oscillator uses a comparator. Its frequency varies with the L and C components, oscillating at a fixed frequency with known L and C values. Measuring frequency shifts as a result of changing L/C values leads us to measure unknown L/C effectively.

Comp

Op-Amps (OA)

Some MSP430s are equipped with single supply general-purpose Op-Amps. These can be used like any other external Op-Amps but they have wide variety of goodies like PGA built-in. We can use them as comparators (although it is unnecessary in the presence of comparator modules) signal amplifiers, etc. We can also use them to make filters, oscillators, analogue computers, etc.

OA

Comp_A+ Module

Apart from ADCs MSP430x2xx devices are equipped with an analogue comparator called Comparator A+ or simply Comp_A+ module.

Block Diagram

Shown above is the block diagram for Comp A+ module. The left most side includes comparator inputs. A good thing to note is that unlike other micros where comparator pins are generally fixed to some dedicated I/Os only, the pins of COMP A+ can be tied with a number of I/O, adding great flexibility in design. The purple region in the centre houses reference sources that can be tied to the comparator inputs. At the comparator output stage (orange area), there is an optional low pass filter. Additionally, comparator inputs can be shorted to remove any stray static. Use GRACE to explore more features of Comp A+.

Code Example

#include <msp430.h>
#include "delay.h"


void BCSplus_graceInit(void);
void GPIO_graceInit(void);
void Comparator_Aplus_graceInit(void);
void System_graceInit(void);
void WDTplus_graceInit(void);


#pragma vector=COMPARATORA_VECTOR
__interrupt void COMPARATORA_ISR_HOOK(void)
{
    P1OUT ^= BIT0;
    CACTL2 &= ~CAIFG;
}


void main(void)
{
    /* Stop watchdog timer from timing out during initial start-up. */
    WDTCTL = WDTPW | WDTHOLD;

    /* initialize Config for the MSP430 GPIO */
    GPIO_graceInit();

    /* initialize Config for the MSP430 2xx family clock systems (BCS) */
    BCSplus_graceInit();

    /* initialize Config for the MSP430F2xx Comparator_A+ */
    Comparator_Aplus_graceInit();

    /* initialize Config for the MSP430 System Registers */
    System_graceInit();

    /* initialize Config for the MSP430 WDT+ */
    WDTplus_graceInit();

    while(1)
    {
        if (CACTL2 & CAOUT)
        {
            delay_ms(300);
        }

        if (!(CACTL2 & CAOUT))
        {
            delay_ms(100);
        }

        P1OUT ^= BIT6;
    }
}


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

    if (CALBC1_8MHZ != 0xFF) {
        /* Adjust this accordingly to your VCC rise time */
        __delay_cycles(100000);

        // Follow recommended flow. First, clear all DCOx and MODx bits. Then
        // apply new RSELx values. Finally, apply new DCOx and MODx bit values.
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_8MHZ;      /* Set DCO to 8MHz */
        DCOCTL = CALDCO_8MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_0 -- If XTS = 0, XT1 = 32768kHz Crystal ; If XTS = 1, XT1 = 0.4 - 1-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_0 | XCAP_1;

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}


void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

    /* Port 1 Output Register */
    P1OUT = 0;

    /* Port 1 Port Select Register */
    P1SEL = BIT7;

    /* Port 1 Direction Register */
    P1DIR = BIT0 | BIT6 | BIT7;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Port Select Register */
    P2SEL &= ~(BIT6 | BIT7);

    /* Port 2 Direction Register */
    P2DIR = 0;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* Port 3 Output Register */
    P3OUT = 0;

    /* Port 3 Direction Register */
    P3DIR = 0;

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */
}


void Comparator_Aplus_graceInit(void)
{
    /* USER CODE START (section: Comparator_Aplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: Comparator_Aplus_graceInit_prologue) */

    /* CACTL1 Register */
    CACTL1 = CAREF_2 | CAON | CAIES | CAIE;

    /* CACTL2 Register */
    CACTL2 = P2CA3 | P2CA1;

    /* CAPD, Register */
    CAPD = CAPD5;

    /* USER CODE START (section: Comparator_Aplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: Comparator_Aplus_graceInit_epilogue) */
}


void System_graceInit(void)
{
    /* USER CODE START (section: System_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: System_graceInit_prologue) */

    /*
     * SR, Status Register
     *
     * ~SCG1 -- Disable System clock generator 1
     * ~SCG0 -- Disable System clock generator 0
     * ~OSCOFF -- Oscillator On
     * ~CPUOFF -- CPU On
     * GIE -- General interrupt enable
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    __bis_SR_register(GIE);

    /* USER CODE START (section: System_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: System_graceInit_epilogue) */
}


void WDTplus_graceInit(void)
{
    /* USER CODE START (section: RTC_B_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: RTC_B_graceInit_prologue) */

    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * WDTHOLD -- Watchdog timer+ is stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * ~WDTTMSEL -- Watchdog mode
     * ~WDTCNTCL -- No action
     * ~WDTSSEL -- SMCLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * ~WDTIS1 -- Watchdog clock source bit1 disabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTHOLD;

    /* USER CODE START (section: RTC_B_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: RTC_B_graceInit_epilogue) */
}

 

Simulation

Comp_A+ Simulation

Explanation

For this demo, a MSP430G2553 is used. Launchpad board’s LEDs are used and additionally a potentiometer (pot) is tied to P1.5. The pot has its ends connected to VDD and GND. The internal connection is as shown below:

Grace Settings

The code dictates that Comp A+ interrupt will occur on falling edges only. Now the question is whose falling edge? Certainly not the inputs. Interrupt will only occur when there is a logic high-low transition on CAOUT pin – P1.7 here. According to the simplified comparator internal connection shown above, this transition will occur only when P1.5’s voltage exceeds that of the 1.8V reference. When a Comp_A+ interrupt occurs, P1.0’s logic state is toggled.

One thing to note here is the fact that though it is not mandatory to clear comparator interrupt flag, it is wise to clear it after processing the interrupt request.

Demo

Comp_A+ (1) Comp_A+ (2)

Demo video: https://www.youtube.com/watch?v=XIzvhpWopiI.

ADC10

In most value-line devices (VLD) like MSP430G2553 and MSP430G2452, ADC12 is not present and the ADC tasks are accomplished with ADC10 modules.

ADC10 Block Diagram

Shown above is the simple block diagram of the ADC10 module. ADC10 is a SAR ADC. Highlighted segments include:

  • ADC channel selector (light blue area) – select the highest channel from where the first ADC conversion starts. An internal counter counts down from this channel all the way down to A0.
  • DMA and ADC output (red area) – here we get AD conversion results and can optionally do a peripheral to memory DMA transfer.
  • ADC clock source (purple area) – this is the clock source that runs the ADC.
  • ADC trigger source (light green area) – selects what triggers the ADC to start a conversion.
  • Reference selectors (orange boxes) – selects ADC’s positive and negative references.
  • Built-in signal sources (Deep red area) – includes on-chip temperature sensor, supply voltage sensing voltage divider, etc.

 

Code Example

#include <msp430.h>
#include "delay.h"
#include "lcd.h"


#define T_offset        -18


void BCSplus_graceInit(void);
void GPIO_graceInit(void);
void ADC10_graceInit(void);
void System_graceInit(void);
void WDTplus_graceInit(void);
unsigned int get_ADC(unsigned int channel);
unsigned int get_volt(unsigned int value);
unsigned int get_temp(unsigned int value);
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value);


void main(void)
{
    unsigned char n = 0;

    unsigned int res = 0;
    unsigned int ADC_Value = 0;

    /* Stop watchdog timer from timing out during initial start-up. */
    WDTCTL = WDTPW | WDTHOLD;

    /* initialize Config for the MSP430 GPIO */
    GPIO_graceInit();

    /* initialize Config for the MSP430 2xx family clock systems (BCS) */
    BCSplus_graceInit();

    /* initialize Config for the MSP430 10-bit Analog to Digital Converter (ADC) */
    ADC10_graceInit();

    /* initialize Config for the MSP430 System Registers */
    System_graceInit();

    /* initialize Config for the MSP430 WDT+ */
    WDTplus_graceInit();

    LCD_init();
    LCD_clear_home();

    while(1)
    {
        if((P1IN & BIT3) == 0)
        {
            P1OUT |= BIT6;
            while((P1IN & BIT3) == 0);

            n++;
            if(n > 2)
            {
                n = 0;
            }
            P1OUT &= ~BIT6;
        }

        switch(n)
        {
            case 1:
            {
                ADC_Value = get_ADC(INCH_1);
                res = get_volt(ADC_Value);

                LCD_goto(0, 0);
                LCD_putstr("ADC Ch01:");
                LCD_goto(0, 1);
                LCD_putstr("Volts/mV:");
                break;
            }
            case 2:
            {
                ADC_Value = get_ADC(INCH_2);
                res = get_volt(ADC_Value);

                LCD_goto(0, 0);
                LCD_putstr("ADC Ch02:");
                LCD_goto(0, 1);
                LCD_putstr("Volts/mV:");
                break;
            }
            default:
            {
                ADC_Value = get_ADC(INCH_10);
                res = get_temp(get_volt(ADC_Value));

                LCD_goto(0, 0);
                LCD_putstr("ADC Ch10:");
                LCD_goto(0, 1);
                LCD_putstr("TC/Deg.C:");
                break;
            }
        }

        lcd_print(12, 0, ADC_Value);
        lcd_print(12, 1, res);
        delay_ms(200);
    };
}


void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

    /* Port 1 Output Register */
    P1OUT = BIT3;

    /* Port 1 Direction Register */
    P1DIR = BIT0 | BIT6;

    /* Port 1 Resistor Enable Register */
    P1REN = BIT3;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Port Select Register */
    P2SEL &= ~(BIT6 | BIT7);

    /* Port 2 Direction Register */
    P2DIR = BIT0 | BIT1 | BIT2 | BIT3 | BIT4 | BIT5;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* Port 3 Output Register */
    P3OUT = 0;

    /* Port 3 Direction Register */
    P3DIR = 0;

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */
}


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

    if (CALBC1_1MHZ != 0xFF) {
        /* Follow recommended flow. First, clear all DCOx and MODx bits. Then
         * apply new RSELx values. Finally, apply new DCOx and MODx bit values.
         */
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_1MHZ;      /* Set DCO to 1MHz */
        DCOCTL = CALDCO_1MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_0 -- If XTS = 0, XT1 = 32768kHz Crystal ; If XTS = 1, XT1 = 0.4 - 1-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_0 | XCAP_1;

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}


void ADC10_graceInit(void)
{
    /* USER CODE START (section: ADC10_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: ADC10_graceInit_prologue) */

    /* disable ADC10 during initialization */
    ADC10CTL0 &= ~ENC;

    /*
     * Control Register 0
     *
     * ~ADC10SC -- No conversion
     * ~ENC -- Disable ADC
     * ~ADC10IFG -- Clear ADC interrupt flag
     * ~ADC10IE -- Disable ADC interrupt
     * ADC10ON -- Switch On ADC10
     * ~REFON -- Disable ADC reference generator
     * ~REF2_5V -- Set reference voltage generator = 1.5V
     * ~MSC -- Disable multiple sample and conversion
     * ~REFBURST -- Reference buffer on continuously
     * ~REFOUT -- Reference output off
     * ~ADC10SR -- Reference buffer supports up to ~200 ksps
     * ADC10SHT_3 -- 64 x ADC10CLKs
     * SREF_0 -- VR+ = VCC and VR- = VSS
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    ADC10CTL0 = ADC10ON | ADC10SHT_3 | SREF_0;

    /*
     * Control Register 1
     *
     * ~ADC10BUSY -- No operation is active
     * CONSEQ_2 -- Repeat single channel
     * ADC10SSEL_3 -- SMCLK
     * ADC10DIV_3 -- Divide by 4
     * ~ISSH -- Input signal not inverted
     * ~ADC10DF -- ADC10 Data Format as binary
     * SHS_0 -- ADC10SC
     * INCH_10 -- Temperature Sensor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    ADC10CTL1 = CONSEQ_2 | ADC10SSEL_3 | ADC10DIV_3 | SHS_0;

    /* Analog (Input) Enable Control Register 0 */
    ADC10AE0 = 0x6;


    /* enable ADC10 */
    ADC10CTL0 |= ENC;

    /* USER CODE START (section: ADC10_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: ADC10_graceInit_epilogue) */
}


void System_graceInit(void)
{
    /* USER CODE START (section: System_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: System_graceInit_prologue) */

    /*
     * SR, Status Register
     *
     * ~SCG1 -- Disable System clock generator 1
     * ~SCG0 -- Disable System clock generator 0
     * ~OSCOFF -- Oscillator On
     * ~CPUOFF -- CPU On
     * GIE -- General interrupt enable
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    __bis_SR_register(GIE);

    /* USER CODE START (section: System_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: System_graceInit_epilogue) */
}


void WDTplus_graceInit(void)
{
    /* USER CODE START (section: RTC_B_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: RTC_B_graceInit_prologue) */

    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * WDTHOLD -- Watchdog timer+ is stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * ~WDTTMSEL -- Watchdog mode
     * ~WDTCNTCL -- No action
     * ~WDTSSEL -- SMCLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * ~WDTIS1 -- Watchdog clock source bit1 disabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTHOLD;

    /* USER CODE START (section: RTC_B_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: RTC_B_graceInit_epilogue) */
}


unsigned int get_ADC(unsigned int channel)
{
    P1OUT ^= BIT0;

    ADC10CTL0 &= ~ENC;

    ADC10CTL1 &= ~(0xF000);
    ADC10CTL1 |= channel;

    ADC10CTL0 |= ENC;

    ADC10CTL0 |= ADC10SC;
    while ((ADC10CTL0 & ADC10IFG) == 0);

    return ADC10MEM;
}


unsigned int get_volt(unsigned int value)
{
    return (unsigned int)((value * 3600.0) / 1023.0);
}


unsigned int get_temp(unsigned int value)
{
    return (unsigned int)((((value / 1000.0) - 0.986) / 0.00355) + T_offset);
}


void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value)
{
    char chr = 0x00;

    chr = ((value / 1000) + 0x30);
    LCD_goto(x_pos, y_pos);
    LCD_putchar(chr);

    chr = (((value / 100) % 10) + 0x30);
    LCD_goto((x_pos + 1), y_pos);
    LCD_putchar(chr);

    chr = (((value / 10) % 10) + 0x30);
    LCD_goto((x_pos + 2), y_pos);
    LCD_putchar(chr);

    chr = ((value % 10) + 0x30);
    LCD_goto((x_pos + 3), y_pos);
    LCD_putchar(chr);
}

 

Simulation

ADC10 Simulation

Explanation

ADC10 is set up initially as depicted below:

ADC10 Grace Setup

Note that we are not using ADC10 interrupt and enabled two external channels (A1 and A2) although only the temperature sensor channel is selected for conversion. The internal temperature sensor has the highest channel number (channel 10) after VDD measurement channel (channel 11). If we also want to sense A1 and A2 along with the internal temperature sensor, we have to select all channels from channel 0 to channel 10 even when we don’t want the others. This is so because in sequence scan mode conversion, all channels are scanned from the highest channel to channel 0. The other way to sense only the desired channels is to sense them one at a time i.e. as single channels. The latter method is used here.

unsigned int get_ADC(unsigned int channel)
{
    P1OUT ^= BIT0;

    ADC10CTL0 &= ~ENC;

    ADC10CTL1 &= ~(0xF000);
    ADC10CTL1 |= channel;

    ADC10CTL0 |= ENC;

    ADC10CTL0 |= ADC10SC;
    while ((ADC10CTL0 & ADC10IFG) == 0);

    return ADC10MEM;
}

The function above first disables the momentarily and clears channel number or count. Then the desired channel is chosen. AD conversion is started following ADC restart and we wait for AD conversion completion. At the end of the conversion ADC result is extracted and returned.

In the main loop, we simply select channel using the Launchpad user button and display ADC data on a LCD. For ease, I demoed ADC10 using two external channels (A1 and A2) and one internal ADC channel – the internal temperature sensor.

        switch(n)
        {
            case 1:
            {
                ADC_Value = get_ADC(INCH_1);
                res = get_volt(ADC_Value);

                LCD_goto(0, 0);
                LCD_putstr("ADC Ch01:");
                LCD_goto(0, 1);
                LCD_putstr("Volts/mV:");
                break;
            }
            case 2:
            {
                ADC_Value = get_ADC(INCH_2);
                res = get_volt(ADC_Value);

                LCD_goto(0, 0);
                LCD_putstr("ADC Ch02:");
                LCD_goto(0, 1);
                LCD_putstr("Volts/mV:");
                break;
            }
            default:
            {
                ADC_Value = get_ADC(INCH_10);
                res = get_temp(get_volt(ADC_Value));

                LCD_goto(0, 0);
                LCD_putstr("ADC Ch10:");
                LCD_goto(0, 1);
                LCD_putstr("TC/Deg.C:");
                break;
            }
        }

        lcd_print(12, 0, ADC_Value);
        lcd_print(12, 1, res);
        delay_ms(200);

 

Demo

ADC10 (1) ADC10 (2) ADC10 (3)

Demo video: https://www.youtube.com/watch?v=M_txQs2ajdk.

ADC10 Interrupt

In previous example, we didn’t use ADC10 interrupt and the code was based on polling. ADC interrupts are as important as timer interrupts. We can start an ADC and extract conversion data in an orderly manner when conversion is complete. No other process waits for the ADC, freeing up the CPU.

Temperature Sensor Transfer Function

Many present-day microcontrollers have on-chip temperature sensors. While many people think they are just mere additions, they are not. Such sensors have a number of applications – most notably correction of ADC readings with temperature drift. Other applications include thermal protection, temperature comphensations, temperature reference, etc. However, these sensors are not meant to be as precise and accurate as dedicated temperature sensor chips like LM35 and DS18B20. This makes them highly unsuitable for measurements of wide range of temperatures and unsuitable for reliable readings. Shown above is the typical voltage output vs temperature graph of MSP430G2xx devices’ internal temperature sensor. Though it is linear, the word “typical” is indirectly telling us that there can be some deviations.

In this example, we will see how to read the internal temperature sensor of a MSP430G2xx micro using ADC10 interrupt.

Code Example

#include <msp430.h>
#include "delay.h"
#include "lcd.h"

#define T_offset        -18

unsigned int ADC_Value = 0;

void GPIO_graceInit(void);
void BCSplus_graceInit(void);
void ADC10_graceInit(void);
void System_graceInit(void);
void WDTplus_graceInit(void);
unsigned int get_volt(unsigned int value);
unsigned int get_temp(unsigned int value);
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value);


#pragma vector = ADC10_VECTOR
__interrupt void ADC10_ISR_HOOK(void)
{
    P1OUT ^= BIT0;
    ADC_Value = ADC10MEM;
    ADC10CTL0 &= ~ADC10IFG;
}


void main(void)
{
    signed int t = 0;

    /* Stop watchdog timer from timing out during initial start-up. */
    WDTCTL = WDTPW | WDTHOLD;

    /* initialize Config for the MSP430 GPIO */
    GPIO_graceInit();

    /* initialize Config for the MSP430 2xx family clock systems (BCS) */
    BCSplus_graceInit();

    /* initialize Config for the MSP430 10-bit Analog to Digital Converter (ADC) */
    ADC10_graceInit();

    /* initialize Config for the MSP430 System Registers */
    System_graceInit();

    /* initialize Config for the MSP430 WDT+ */
    WDTplus_graceInit();

    LCD_init();

    LCD_clear_home();

    LCD_goto(0, 0);
    LCD_putstr("ADC Value:");
    LCD_goto(0, 1);
    LCD_putstr("Tmp/Deg.C:");

    while(1)
    {
        // ADC Start Conversion - Software trigger
        ADC10CTL0 |= ADC10SC;

        P1OUT ^= BIT6;

        t = get_volt(ADC_Value);
        t = get_temp(t);

        lcd_print(12, 0, ADC_Value);
        lcd_print(12, 1, t);
        delay_ms(200);
    };
}


void GPIO_graceInit(void)
{
    /* Port 1 Output Register */
    P1OUT = 0;

    /* Port 1 Direction Register */
    P1DIR = BIT0 | BIT6;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Port Select Register */
    P2SEL &= ~(BIT6 | BIT7);

    /* Port 2 Direction Register */
    P2DIR = BIT0 | BIT1 | BIT2 | BIT3 | BIT4 | BIT5;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* Port 3 Output Register */
    P3OUT = 0;

    /* Port 3 Direction Register */
    P3DIR = 0;
}


void BCSplus_graceInit(void)
{
    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

    if (CALBC1_12MHZ != 0xFF)
    {
        /* Adjust this accordingly to your VCC rise time */
        __delay_cycles(1000);

        /* Follow recommended flow. First, clear all DCOx and MODx bits. Then
         * apply new RSELx values. Finally, apply new DCOx and MODx bit values.
         */
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_12MHZ;     /* Set DCO to 12MHz */
        DCOCTL = CALDCO_12MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_2 -- If XTS = 0, XT1 = VLOCLK ; If XTS = 1, XT1 = 3 - 16-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_2 | XCAP_1;
}


void ADC10_graceInit(void)
{
    /* disable ADC10 during initialization */
    ADC10CTL0 &= ~ENC;

    /*
     * Control Register 0
     *
     * ~ADC10SC -- No conversion
     * ~ENC -- Disable ADC
     * ~ADC10IFG -- Clear ADC interrupt flag
     * ADC10IE -- Enable ADC interrupt
     * ADC10ON -- Switch On ADC10
     * ~REFON -- Disable ADC reference generator
     * ~REF2_5V -- Set reference voltage generator = 1.5V
     * MSC -- Enable multiple sample and conversion
     * ~REFBURST -- Reference buffer on continuously
     * ~REFOUT -- Reference output off
     * ADC10SR -- Reference buffer supports up to ~50 ksps
     * ADC10SHT_3 -- 64 x ADC10CLKs
     * SREF_0 -- VR+ = VCC and VR- = VSS
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    ADC10CTL0 = ADC10IE | ADC10ON | MSC | ADC10SR | ADC10SHT_3 | SREF_0;

    /*
     * Control Register 1
     *
     * ~ADC10BUSY -- No operation is active
     * CONSEQ_2 -- Repeat single channel
     * ADC10SSEL_3 -- SMCLK
     * ADC10DIV_7 -- Divide by 8
     * ~ISSH -- Input signal not inverted
     * ~ADC10DF -- ADC10 Data Format as binary
     * SHS_0 -- ADC10SC
     * INCH_10 -- Temperature Sensor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    ADC10CTL1 = CONSEQ_2 | ADC10SSEL_3 | ADC10DIV_7 | SHS_0 | INCH_10;


    /* enable ADC10 */
    ADC10CTL0 |= ENC;
}


void System_graceInit(void)
{
    /* Clear oscillator fault flag with software delay */
    do
    {
        // Clear OSC fault flag
        IFG1 &= ~OFIFG;

        // 50us delay
        __delay_cycles(600);
    } while (IFG1 & OFIFG);

    /*
     * SR, Status Register
     *
     * ~SCG1 -- Disable System clock generator 1
     * ~SCG0 -- Disable System clock generator 0
     * ~OSCOFF -- Oscillator On
     * ~CPUOFF -- CPU On
     * GIE -- General interrupt enable
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    __bis_SR_register(GIE);
}


void WDTplus_graceInit(void)
{
    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * WDTHOLD -- Watchdog timer+ is stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * ~WDTTMSEL -- Watchdog mode
     * ~WDTCNTCL -- No action
     * ~WDTSSEL -- SMCLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * ~WDTIS1 -- Watchdog clock source bit1 disabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTHOLD;
}


unsigned int get_volt(unsigned int value)
{
    return (unsigned int)((value * 3600.0) / 1023.0);
}


unsigned int get_temp(unsigned int value)
{
    return (unsigned int)((((value / 1000.0) - 0.986) / 0.00355) + T_offset);
}


void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value)
{
    char chr = 0x00;

    chr = ((value / 1000) + 0x30);
    LCD_goto(x_pos, y_pos);
    LCD_putchar(chr);

    chr = (((value / 100) % 10) + 0x30);
    LCD_goto((x_pos + 1), y_pos);
    LCD_putchar(chr);

    chr = (((value / 10) % 10) + 0x30);
    LCD_goto((x_pos + 2), y_pos);
    LCD_putchar(chr);

    chr = ((value % 10) + 0x30);
    LCD_goto((x_pos + 3), y_pos);
    LCD_putchar(chr);
}

 

Simulation

ADC10 ISR Simulation

*Note Proteus VSM cannot simulate internal temperature sensor. The simulation here only shows connections. The readouts are misleading.

 

Explanation

ADC10 ISR Grace Setup

ADC10 setup is similar to the setup we have seen in the last example. However, this time we enabled ADC10 interrupt as this is an interrupt-based demo.

The ADC ISR is simple. Here we are just toggling P1.0 LED to indicate ADC conversion completion and clearing ADC interrupt flag after reading ADC data value.

#pragma vector = ADC10_VECTOR
__interrupt void ADC10_ISR_HOOK(void)
{
    P1OUT ^= BIT0;
    ADC_Value = ADC10MEM;
    ADC10CTL0 &= ~ADC10IFG;
}

In the main loop, AD conversion process starts using software trigger. The result of this conversion is extracted from the ADC ISR shown above. The ADC data obtained from the ISR is shown on a LCD. Two values are shown – actual ADC count and the corresponding temperature against that ADC readout.

         // ADC Start Conversion - Software trigger
        ADC10CTL0 |= ADC10SC;

        P1OUT ^= BIT6;

        t = get_volt(ADC_Value);
        t = get_temp(t);

        lcd_print(12, 0, ADC_Value);
        lcd_print(12, 1, t);
        delay_ms(200);

To extract temperature in degree Celsius, first the ADC count is converted to millivolts and then this voltage value is translated to temperature value using the voltage vs temperature transfer function shown earlier. T_offset is an optional constant that is used to negate any offset in temperature readout.

unsigned int get_volt(unsigned int value)
{
    return (unsigned int)((value * 3600.0) / 1023.0);
}

unsigned int get_temp(unsigned int value)
{
    return (unsigned int)((((value / 1000.0) - 0.986) / 0.00355) + T_offset);
}

 

Demo

ADC ISR

Demo video: https://www.youtube.com/watch?v=XOlI3Vfjfb8.

Communication Overview

Most MSP430 micros are equipped with Universal Serial Interface (USI) and Universal Serial Communication Interface (USCI) modules. Some devices have Universal Asynchronous Receiver-Transmitter (UART) modules. These interfaces are needed to communicate with external devices like sensors, actuators, drives, other microcontrollers or onboard devices, etc and are responsible for handling the most commonly used serial communications like Universal Asynchronous Receiver-Transmitter (UART), Serial Peripheral Interface (SPI) and Inter-Integrated Circuit (I2C). Also, there are other additional more robust communication interfaces like Controller Area Network (CAN), Local Interconnect Network (LIN), Infrared Data Association (IrDA) and RS-485. The latter communications will not be discussed here as they are advanced and are basically extension of the aforementioned serial communications. Each method communication has its own advantages and disadvantages. In the table below, some individual basics of various methods of communications are shown:

Communication Methods

We can also use software-based methods instead of using dedicated hardware to replicate some of these communications but these methods are not efficient as they consume resources like clock cycles, memories and often employ polling strategies. However, in the absence of dedicated hardware, software methods are the last resorts.

USI vs USCI – Which one is better?

USI and USCI are both hardware-based serial communication handlers but the question which one is better lurks in every beginner’s mind. Although both modules do same tasks, they are not identical.

  • Universal Serial Interface (USI)
    Mainly USI is intended for I2C and SPI communications. Technically speaking, USI is a pumped-up shift register that does all the bit-banging in hardware that a programmer would have done in software end. Apart from its aforementioned shift register, it has clock generator, bit counter and few extra assists for I2C communications.
    In the firmware end, we need to load the bit counter with the number of bits to transfer. This bit counter always counts down to zero. During transmission, the shift register is loaded with the value that is to be transmitted while during reception, the shift register is read back once the bit counter hits zero.
  • Universal Serial Communication Interface (USCI)
    USCI, on the other hand, is a highly sophisticated module that is intended for most forms of serial communications. USCI is more advanced than USI in terms of hardware. It has a one-byte I/O buffer and DMA transfer capability for higher throughput. USCI can be subcategorized in two types:
    Asynchronous USCI (USCI_A)
    It is used for UART, SPI, LIN and IRDA as it can detect the baud rate of an incoming signal. This type is most common.
    Synchronous USCI (USCI_B)
    This type can handle synchronous communications like SPI and I2C that need a clock signal. The coolest part is the fact that the full I2C communication protocol as per NXP is implemented in I2C mode with an in-built I2C state machine.

Both USI and USCI support master-slave modes although most of the times we don’t need slave modes. This is because most of the times we do not need communicate with multiple microcontrollers on board. I2C and SPI are usually used to establish communication between external devices (sensors and drivers) and a host microcontroller. UART, on the other hand, is mainly used to communicate with a computer. It is also used for long-distance communications like RS232, RS485, LIN and IrDA.

Software-based Communication

When USI/USCI are absent or used up, we have to stick to software-based methods to emulate SPI, I2C and UART. They are slow and may require the aid of other hardware like timers and external interrupts. Software methods add considerable amounts of coding overhead and consume both CPU cycles as well as memories. We can’t also use them for communications that are more complex than I2C, SPI or UART. It is better to avoid them whenever possible. Still however, these methods help in learning the details of UART, I2C and SPI communications to a great extent. A good thing about software solutions is the fact that we can virtually create unlimited amounts of communication ports.

Hardware SPI – USI

SPI communication is an onboard synchronous communication method. It is used for communicating with a number of devices including sensors, TFT displays, port expanders, PWM controller ICs, memory chips, addon support devices and even other microcontrollers.

In a SPI communication bus, there is always one master device which generates clock and select slave(s). Master sends commands to slave(s). Slave(s) responds to commands sent by the master. The number of slaves in a SPI bus is virtually unlimited provided that there is no issue with slave selection hardware and bus speed. Except the chip selection pin, all SPI devices in a bus can share the same clock and data pins.

Typical full-duplex SPI bus requires four basic I/O pins:

  • Master-Out-Slave-In (MOSI) connected to Slave-Data-In (SDI).
  • Master-In-Slave-Out (MIS0) connected to Slave-Data-Out (SDO).
  • Serial Clock (SCLK) connected to Slave Clock (SCK).
  • Slave Select (SS) connected to Chip Select (CS).

The diagram below illustrates SPI communication with a MSP430 micro. The green labels are for slaves while the red ones are for the master or host MSP430 micro.

SPI Comms

In general, if you wish to know more about SPI bus here are some cool links:

 

Code Example

 

SPI.h

#include <msp430.h>


unsigned char SPI_transfer(unsigned char data_out);

 

SPI.c

#include "SPI.h"


unsigned char SPI_transfer(unsigned char data_out)
{
    unsigned char data_in = 0;

    USISRL = data_out;            // Load shift register with data byte to be TXed
    USICNT = 8;                   // Load bit-counter to send/receive data byte
    while (!(USIIFG & USICTL1));  // Loop until data byte transmitted
    data_in = USISRL;             // Read out the received data

    return data_in;
}

 

MAX72xx.h

#include <MSP430.h>
#include "delay.h"
#include "SPI.h"


#define HW_SPI_DIR              P1DIR
#define HW_SPI_OUT              P1OUT
#define HW_SPI_IN               P1IN

#define CS_pin                  BIT4

#define CS_DIR_OUT()            do{HW_SPI_DIR |= CS_pin;}while(0)
#define CS_DIR_IN()             do{HW_SPI_DIR &= ~CS_pin;}while(0)

#define CS_HIGH()               do{HW_SPI_OUT |= CS_pin;}while(0)
#define CS_LOW()                do{HW_SPI_OUT &= ~CS_pin;}while(0)

#define NOP                     0x00
#define DIG0                    0x01
#define DIG1                    0x02
#define DIG2                    0x03
#define DIG3                    0x04
#define DIG4                    0x05
#define DIG5                    0x06
#define DIG6                    0x07
#define DIG7                    0x08
#define decode_mode_reg         0x09
#define intensity_reg           0x0A
#define scan_limit_reg          0x0B
#define shutdown_reg            0x0C
#define display_test_reg        0x0F

#define shutdown_cmd            0x00
#define run_cmd                 0x01

#define no_test_cmd             0x00
#define test_cmd                0x01


void MAX72xx_init(void);
void MAX72xx_write(unsigned char address, unsigned char value);

 

MAX72xx.c

#include "MAX72xx.h"


void MAX72xx_init(void)
{
    CS_DIR_OUT();
    CS_HIGH();

    MAX72xx_write(shutdown_reg, run_cmd);                
    MAX72xx_write(decode_mode_reg, 0x00);
    MAX72xx_write(scan_limit_reg, 0x07);
    MAX72xx_write(intensity_reg, 0x04);
    MAX72xx_write(display_test_reg, test_cmd);
    delay_ms(100);    
    MAX72xx_write(display_test_reg, no_test_cmd);           
}


void MAX72xx_write(unsigned char address, unsigned char value)
{
    CS_LOW();

    SPI_transfer(address);
    SPI_transfer(value);

    CS_HIGH();
}

 

main.c

#include <msp430.h>
#include <string.h>
#include "delay.h"
#include "SPI.h"
#include "MAX72xx.h"


void GPIO_graceInit(void);
void BCSplus_graceInit(void);
void USI_graceInit(void);
void System_graceInit(void);
void WDTplus_graceInit(void);


void main(void)
{
    unsigned char i = 0;
    unsigned char j = 0;

    unsigned char temp[8];

    const unsigned char text[80] =
    {
        0x00, 0x7E, 0x04, 0x08, 0x08, 0x04, 0x7E, 0x00,       //M
        0x00, 0x42, 0x42, 0x7E, 0x7E, 0x42, 0x42, 0x00,       //I
        0x00, 0x3C, 0x42, 0x42, 0x42, 0x42, 0x24, 0x00,       //C
        0x00, 0x7E, 0x1A, 0x1A, 0x1A, 0x2A, 0x44, 0x00,       //R
        0x00, 0x3C, 0x42, 0x42, 0x42, 0x42, 0x3C, 0x00,       //O
        0x00, 0x7C, 0x12, 0x12, 0x12, 0x12, 0x7C, 0x00,       //A
        0x00, 0x7E, 0x1A, 0x1A, 0x1A, 0x2A, 0x44, 0x00,       //R
        0x00, 0x7E, 0x7E, 0x4A, 0x4A, 0x4A, 0x42, 0x00,       //E
        0x00, 0x7E, 0x04, 0x08, 0x10, 0x20, 0x7E, 0x00,       //N
        0x00, 0x7C, 0x12, 0x12, 0x12, 0x12, 0x7C, 0x00        //A
    };

    const unsigned char symbols[56] =
    {
       0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA,
       0x3C, 0x42, 0x95, 0xA1, 0xA1, 0x95, 0x42, 0x3C,
       0xFF, 0xC3, 0xBD, 0xA5, 0xA5, 0xBD, 0xC3, 0xFF,
       0x99, 0x5A, 0x3C, 0xFF, 0xFF, 0x3C, 0x5A, 0x99,
       0x1C, 0x22, 0x41, 0x86, 0x86, 0x41, 0x22, 0x1C,
       0xDF, 0xDF, 0xD8, 0xFF, 0xFF, 0x1B, 0xFB, 0xFB,
       0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55
    };

    memset(temp, 0x00, sizeof(temp));

    /* Stop watchdog timer from timing out during initial start-up. */
    WDTCTL = WDTPW | WDTHOLD;

    /* initialize Config for the MSP430 GPIO */
    GPIO_graceInit();

    /* initialize Config for the MSP430 2xx family clock systems (BCS) */
    BCSplus_graceInit();

    /* initialize Config for the MSP430 USI */
    USI_graceInit();

    /* initialize Config for the MSP430 System Registers */
    System_graceInit();

    /* initialize Config for the MSP430 WDT+ */
    WDTplus_graceInit();

    MAX72xx_init();

    while(1)
    {
        for(i = 0; i < 80; i++)
        {
           for(j = 0; j < 8; j++)
           {
                 temp[j] = text[(i + j)];
                 MAX72xx_write((j + 1), temp[j]);
                 delay_ms(6);
           }
        }

        for(j = 0; j < 56; j = (j + 8))
        {
            for(i = 0; i < 8; i++)
            {
               MAX72xx_write((i + 1), symbols[(i + j)]);
            }

            delay_ms(2000);
        }
    };
}


void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

    /* Port 1 Output Register */
    P1OUT = 0;

    /* Port 1 Port Select Register */
    P1SEL = BIT5 | BIT6;

    /* Port 1 Direction Register */
    P1DIR = BIT4;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Direction Register */
    P2DIR = 0;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */
}


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

    if (CALBC1_1MHZ != 0xFF) {
        /* Follow recommended flow. First, clear all DCOx and MODx bits. Then
         * apply new RSELx values. Finally, apply new DCOx and MODx bit values.
         */
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_1MHZ;      /* Set DCO to 1MHz */
        DCOCTL = CALDCO_1MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_2 -- If XTS = 0, XT1 = VLOCLK ; If XTS = 1, XT1 = 3 - 16-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_2 | XCAP_1;

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}


void USI_graceInit(void)
{
    /* USER CODE START (section: USI_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: USI_graceInit_prologue) */

    /* Disable USI */
    USICTL0 |= USISWRST;

    /*
     * USI Control Register 0
     *
     * ~USIPE7 -- USI function disabled
     * USIPE6 -- USI function enabled
     * USIPE5 -- USI function enabled
     * ~USILSB -- MSB first
     * USIMST -- Master mode
     * ~USIGE -- Output latch enable depends on shift clock
     * USIOE -- Output enabled
     * USISWRST -- USI logic held in reset state
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    USICTL0 = USIPE6 | USIPE5 | USIMST | USIOE | USISWRST;

    /*
     * USI Clock Control Register
     *
     * USIDIV_3 -- Divide by 8
     * USISSEL_2 -- SMCLK
     * USICKPL -- Inactive state is high
     * ~USISWCLK -- Input clock is low
     *
     * Note: ~USISWCLK indicates that USISWCLK has value zero
     */
    USICKCTL = USIDIV_3 | USISSEL_2 | USICKPL;

    /* Enable USI */
    USICTL0 &= ~USISWRST;

    /* USER CODE START (section: USI_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: USI_graceInit_epilogue) */
}


void System_graceInit(void)
{
    /* USER CODE START (section: System_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: System_graceInit_prologue) */

    /* Clear oscillator fault flag with software delay */
    do
    {
        // Clear OSC fault flag
        IFG1 &= ~OFIFG;

        // 50us delay
        __delay_cycles(50);
    } while (IFG1 & OFIFG);

    /*
     * SR, Status Register
     *
     * ~SCG1 -- Disable System clock generator 1
     * ~SCG0 -- Disable System clock generator 0
     * ~OSCOFF -- Oscillator On
     * ~CPUOFF -- CPU On
     * GIE -- General interrupt enable
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    __bis_SR_register(GIE);

    /* USER CODE START (section: System_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: System_graceInit_epilogue) */
}


void WDTplus_graceInit(void)
{
    /* USER CODE START (section: RTC_B_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: RTC_B_graceInit_prologue) */

    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * WDTHOLD -- Watchdog timer+ is stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * ~WDTTMSEL -- Watchdog mode
     * ~WDTCNTCL -- No action
     * ~WDTSSEL -- SMCLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * ~WDTIS1 -- Watchdog clock source bit1 disabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTHOLD;

    /* USER CODE START (section: RTC_B_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: RTC_B_graceInit_epilogue) */
}

 

Simulation

USI-SPI-Sim

Explanation

USI-based SPI is best realized with a MAX7219-based dot-matrix display and this demo is based on it.  USI is setup for SPI mode. Every time USI is initialized it is disabled first just like other hardware. We, then, proceed to setup the SPI data transfer properties like SPI clock speed, device role, mode of operation, etc. Finally, USI is enabled for data transfer over SPI bus.

USI-SPI

    /* Disable USI */
    USICTL0 |= USISWRST;

    /*
     * USI Control Register 0
     *
     * ~USIPE7 -- USI function disabled
     * USIPE6 -- USI function enabled
     * USIPE5 -- USI function enabled
     * ~USILSB -- MSB first
     * USIMST -- Master mode
     * ~USIGE -- Output latch enable depends on shift clock
     * USIOE -- Output enabled
     * USISWRST -- USI logic held in reset state
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    USICTL0 = USIPE6 | USIPE5 | USIMST | USIOE | USISWRST;

    /*
     * USI Clock Control Register
     *
     * USIDIV_3 -- Divide by 8
     * USISSEL_2 -- SMCLK
     * USICKPL -- Inactive state is high
     * ~USISWCLK -- Input clock is low
     *
     * Note: ~USISWCLK indicates that USISWCLK has value zero
     */
    USICKCTL = USIDIV_3 | USISSEL_2 | USICKPL;

    /* Enable USI */
    USICTL0 &= ~USISWRST;

The function below does the actual SPI data transfer. It both transmits and receives data over SPI bus. Data to be transmitted is loaded in USISRL register. USI bit counter is loaded with the number of bits to transfer, here 8. In the hardware end, this counter is decremented with data being shifted and clock signal being generated on each decrement. Since SPI bus is a circular bus, reading USISRL register back returns received data. Note slave select pin is not used here as it is used in MAX7219 source file.

unsigned char SPI_transfer(unsigned char data_out)
{
    unsigned char data_in = 0;

    USISRL = data_out;            // Load shift register with data byte to be TXed
    USICNT = 8;                   // Load bit-counter to send/receive data byte
    while (!(USIIFG & USICTL1));  // Loop until data byte transmitted
    data_in = USISRL;             // Read out the received data

    return data_in;
}

The rest of the code is the implementation of MAX7219 driver and how to use it to create patterns in the dot-matrix display. When started, the display scrolls the letters of the word “MICROARENA” –  the name of my Facebook page followed by some symbols. I assume that readers will understand what I have done here.

Demo

USI-SPI

MAX7219 Display

Demo video: https://www.youtube.com/watch?v=FY3jtRD8FCA.

Software SPI

Software SPI is the only solution in absence of USI/USCI modules. We need to code every step after studying device datasheet since there are four modes of SPI communication and we need to be sure which modes are supported by the device we are trying to communicate with. It should be noted that software SPI is not as fast as hardware-based SPI and this become more evident when software SPI is used to drive displays like TFTs, OLEDs, dot-matrix displays and monochrome displays like the one shown below. Software SPI may encounter issues due to glitches if improperly coded.

PIC18F252 Nokia LCD Mini Digital Oscilloscope (4)

However, for a beginner, software-based SPI is good for understanding the concept behind SPI communication. Shown below is a typical SPI bus timing diagram. As you see it is simply a pattern of ones and zeroes. Digital I/O can generate these patterns if coded.

SPI

Code Example

 

MCP4921.h

 

#include <msp430.h>
#include "delay.h"


#define SW_SPI_DIR          P2DIR
#define SW_SPI_OUT          P2OUT
#define SW_SPI_IN           P2IN

#define SCK_pin             BIT0
#define CS_pin              BIT1
#define SDI_pin             BIT2
#define LDAC_pin            BIT3

#define SCK_DIR_OUT()       do{SW_SPI_DIR |= SCK_pin;}while(0)
#define SCK_DIR_IN()        do{SW_SPI_DIR &= ~SCK_pin;}while(0)
#define CS_DIR_OUT()        do{SW_SPI_DIR |= CS_pin;}while(0)
#define CS_DIR_IN()         do{SW_SPI_DIR &= ~CS_pin;}while(0)
#define SDI_DIR_OUT()       do{SW_SPI_DIR |= SDI_pin;}while(0)
#define SDI_DIR_IN()        do{SW_SPI_DIR &= ~SDI_pin;}while(0)
#define LDAC_DIR_OUT()      do{SW_SPI_DIR |= LDAC_pin;}while(0)
#define LDAC_DIR_IN()       do{SW_SPI_DIR &= ~LDAC_pin;}while(0)

#define SCK_HIGH()          do{SW_SPI_OUT |= SCK_pin;}while(0)
#define SCK_LOW()           do{SW_SPI_OUT &= ~SCK_pin;}while(0)
#define CS_HIGH()           do{SW_SPI_OUT |= CS_pin;}while(0)
#define CS_LOW()            do{SW_SPI_OUT &= ~CS_pin;}while(0)
#define SDI_HIGH()          do{SW_SPI_OUT |= SDI_pin;}while(0)
#define SDI_LOW()           do{SW_SPI_OUT &= ~SDI_pin;}while(0)
#define LDAC_HIGH()         do{SW_SPI_OUT |= LDAC_pin;}while(0)
#define LDAC_LOW()          do{SW_SPI_OUT &= ~LDAC_pin;}while(0)

#define ignore_cmd          0x80
#define DAC_write_cmd       0x00
#define Buffer_on           0x40
#define Buffer_off          0x00
#define Gain_1X             0x20
#define Gain_2X             0x00
#define Run_cmd             0x10
#define Shutdown            0x00


void MCP4921_init(void);
void MCP4921_write(unsigned char cmd, unsigned int dac_value);

 

MCP4921.c

 

#include "MCP4921.h"


void MCP4921_init(void)
{
   CS_DIR_OUT();
   SCK_DIR_OUT();
   SDI_DIR_OUT();
   LDAC_DIR_OUT();

   CS_HIGH();
   LDAC_HIGH();
   SCK_HIGH();
   SDI_HIGH();
}


void MCP4921_write(unsigned char cmd, unsigned int dac_value)
{
    unsigned char s = 16;

    unsigned int value = 0;

    value = cmd;
    value <<= 8;
    value |= (dac_value & 0x0FFF);

    CS_LOW();

    while(s > 0)
    {
        if((value & 0x8000) != 0)
        {
            SDI_HIGH();
        }
        else
        {
            SDI_LOW();
        }

        SCK_LOW();
        SCK_HIGH();
        value <<= 1;
        s--;
    }

    LDAC_LOW();
    CS_HIGH();
    delay_us(10);
    LDAC_HIGH();
}

 

main.c

 

#include <msp430.h>
#include "delay.h"
#include "MCP4921.h"


void GPIO_graceInit(void);
void BCSplus_graceInit(void);
void System_graceInit(void);
void WDTplus_graceInit(void);


const unsigned int sine_table[33] =
{
    0,
    100,
    201,
    301,
    400,
    498,
    595,
    690,
    784,
    876,
    965,
   1053,
   1138,
   1220,
   1299,
   1375,
   1448,
   1517,
   1583,
   1645,
   1703,
   1757,
   1806,
   1851,
   1892,
   1928,
   1960,
   1987,
   2026,
   2038,
   2046,
   2047
};


const unsigned int triangle_table[33] =
{
   0,
   64,
   128,
   192,
   256,
   320,
   384,
   448,
   512,
   576,
   640,
   704,
   768,
   832,
   896,
   960,
   1024,
   1088,
   1152,
   1216,
   1280,
   1344,
   1408,
   1472,
   1536,
   1600,
   1664,
   1728,
   1792,
   1856,
   1920,
   1984,
   2047
};


const unsigned int square_table[33] =
{
   2047,
   2047,
   2047,
   2047,
   2047,
   2047,
   2047,
   2047,
   2047,
   2047,
   2047,
   2047,
   2047,
   2047,
   2047,
   2047,
   2047,
   2047,
   2047,
   2047,
   2047,
   2047,
   2047,
   2047,
   2047,
   2047,
   2047,
   2047,
   2047,
   2047,
   2047,
   2047,
   2047
};


void main(void)
{
    unsigned char s = 0;
    unsigned char wave = 0;

    /* Stop watchdog timer from timing out during initial start-up. */
    WDTCTL = WDTPW | WDTHOLD;

    /* initialize Config for the MSP430 GPIO */
    GPIO_graceInit();

    /* initialize Config for the MSP430 2xx family clock systems (BCS) */
    BCSplus_graceInit();

    /* initialize Config for the MSP430 System Registers */
    System_graceInit();

    /* initialize Config for the MSP430 WDT+ */
    WDTplus_graceInit();

    MCP4921_init();

    while(1)
    {
        if((P1IN & BIT3) == 0)
        {
            P1OUT |= BIT0;

            while((P1IN & BIT3) == 0);
            wave++;

            if(wave > 2)
            {
                wave = 0;
            }

            P1OUT &= ~BIT0;
        }

        else
        {
            switch(wave)
            {
                case 1:
                {
                    for(s = 0; s < 32; s++)
                    {
                        MCP4921_write((DAC_write_cmd | Buffer_on | Gain_1X | Run_cmd), (2047 + square_table[s]));
                        delay_ms(10);
                    }
                    for(s = 31; s > 0; s--)
                    {
                        MCP4921_write((DAC_write_cmd | Buffer_on | Gain_1X | Run_cmd), (2047 + square_table[s]));
                        delay_ms(10);
                    }
                    for(s = 0; s < 32; s++)
                    {
                        MCP4921_write((DAC_write_cmd | Buffer_on | Gain_1X | Run_cmd), (2047 - square_table[s]));
                        delay_ms(10);
                    }
                    for(s = 31; s > 0; s--)
                    {
                        MCP4921_write((DAC_write_cmd | Buffer_on | Gain_1X | Run_cmd), (2047 - square_table[s]));
                        delay_ms(10);
                    }
                    break;
                }
                case 2:
                {
                    for(s = 0; s < 32; s++)
                    {
                        MCP4921_write((DAC_write_cmd | Buffer_on | Gain_1X | Run_cmd), (2047 + triangle_table[s]));
                        delay_ms(10);
                    }
                    for(s = 31; s > 0; s--)
                    {
                        MCP4921_write((DAC_write_cmd | Buffer_on | Gain_1X | Run_cmd), (2047 + triangle_table[s]));
                        delay_ms(10);
                    }
                    for(s = 0; s < 32; s++)
                    {
                        MCP4921_write((DAC_write_cmd | Buffer_on | Gain_1X | Run_cmd), (2047 - triangle_table[s]));
                        delay_ms(10);
                    }
                    for(s = 31; s > 0; s--)
                    {
                        MCP4921_write((DAC_write_cmd | Buffer_on | Gain_1X | Run_cmd), (2047 - triangle_table[s]));
                        delay_ms(10);
                    }
                    break;
                }
                default:
                {
                    for(s = 0; s < 32; s++)
                    {
                        MCP4921_write((DAC_write_cmd | Buffer_on | Gain_1X | Run_cmd), (2047 + sine_table[s]));
                        delay_ms(10);
                    }
                    for(s = 31; s > 0; s--)
                    {
                        MCP4921_write((DAC_write_cmd | Buffer_on | Gain_1X | Run_cmd), (2047 + sine_table[s]));
                        delay_ms(10);
                    }
                    for(s = 0; s < 32; s++)
                    {
                        MCP4921_write((DAC_write_cmd | Buffer_on | Gain_1X | Run_cmd), (2047 - sine_table[s]));
                        delay_ms(10);
                    }
                    for(s = 31; s > 0; s--)
                    {
                        MCP4921_write((DAC_write_cmd | Buffer_on | Gain_1X | Run_cmd), (2047 - sine_table[s]));
                        delay_ms(10);
                    }
                    break;
                }
            }
        }
    };
}


void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

    /* Port 1 Output Register */
    P1OUT = BIT3;

    /* Port 1 Direction Register */
    P1DIR = BIT0;

    /* Port 1 Resistor Enable Register */
    P1REN = BIT3;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Port Select Register */
    P2SEL &= ~(BIT6 | BIT7);

    /* Port 2 Direction Register */
    P2DIR = BIT0 | BIT1 | BIT2 | BIT3;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */
}


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

    if (CALBC1_1MHZ != 0xFF) {
        /* Follow recommended flow. First, clear all DCOx and MODx bits. Then
         * apply new RSELx values. Finally, apply new DCOx and MODx bit values.
         */
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_1MHZ;      /* Set DCO to 1MHz */
        DCOCTL = CALDCO_1MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_0 -- If XTS = 0, XT1 = 32768kHz Crystal ; If XTS = 1, XT1 = 0.4 - 1-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_0 | XCAP_1;

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}


void System_graceInit(void)
{
    /* USER CODE START (section: System_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: System_graceInit_prologue) */

    /*
     * SR, Status Register
     *
     * ~SCG1 -- Disable System clock generator 1
     * ~SCG0 -- Disable System clock generator 0
     * ~OSCOFF -- Oscillator On
     * ~CPUOFF -- CPU On
     * GIE -- General interrupt enable
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    __bis_SR_register(GIE);

    /* USER CODE START (section: System_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: System_graceInit_epilogue) */
}


void WDTplus_graceInit(void)
{
    /* USER CODE START (section: RTC_B_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: RTC_B_graceInit_prologue) */

    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * WDTHOLD -- Watchdog timer+ is stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * ~WDTTMSEL -- Watchdog mode
     * ~WDTCNTCL -- No action
     * ~WDTSSEL -- SMCLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * ~WDTIS1 -- Watchdog clock source bit1 disabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTHOLD;

    /* USER CODE START (section: RTC_B_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: RTC_B_graceInit_epilogue) */
}

 

Simulation

MCP4921

Explanation

Software SPI requires digital I/Os only. Software SPI is achieved using bit-banging technique. For this demo, a MCP4921 12-bit DAC is used. This DAC communicates with a host micro using SPI bus. Check the function below. Here from the data bit to the clock signal, everything is controlled by changing the logic states of digital I/Os. The changes are done in such a way that they do exactly the same thing a hardware SPI would have done. If we used hardware-based SPI as in the last example, it would have taken one or two lines of code unlike the long listing given below. This extra coding along with some hardware limitations reduces SPI clock speed.

void MCP4921_write(unsigned char cmd, unsigned int dac_value)
{
    unsigned char s = 16;

    unsigned int value = 0;

    value = cmd;
    value <<= 8;
    value |= (dac_value & 0x0FFF);

    CS_LOW();

    while(s > 0)
    {
        if((value & 0x8000) != 0)
        {
            SDI_HIGH();
        }
        else
        {
            SDI_LOW();
        }

        SCK_LOW();
        SCK_HIGH();
        value <<= 1;
        s--;
    }

    LDAC_LOW();
    CS_HIGH();
    delay_us(10);
    LDAC_HIGH();
}

The rest of the code simply does the work of a Digital Signal Synthesizer (DSS) a.k.a waveform generator. It generates three types of waves using wave tables and some basic mathematics.

Demo

SW-SPI

Demo video: https://www.youtube.com/watch?v=gPZJyL9LWQc.

LCD using DIO Bit-Banging

Some MSP430 devices, especially the 14-pin parts have limited number of pins and so it is wise to use port expanders like MCP23S17 or MAX7300, serial LCDs and shift register-based LCDs. Everything is same here just as in the LCD example. The only exception is the method of handling the device responsible for port-expansion task. In some cases, it may be necessary to use additional hardware like UART/I2C/SPI or emulate these methods using software when such dedicated hardware is either unavailable or used up for some other jobs. Thus, in such cases, things are no longer as simple as with digital I/Os. In the software end, we will also need to code for the additional interface too.

I2C SPI LCD

Everything that has an advantage must also have a disadvantage. The primary disadvantages that we are left with when using port-expanded LCDs are slower displays and vulnerability to EMI. Noise and glitches are big issues when your device is working in a harsh industrial environment surrounded by electromagnetics. Likewise, if the wires connecting the LCD pack with the host MCU are too long, it is highly likely to fail or show garbage characters after some time. The simplest solution to these issues are to use shorter connection wires, slower communication speed and frequent but periodic reinitialization of the LCD pack. Again, all these lead to slow functioning. Thus, a careful system design is an absolute must.

The most popular methods of driving alphanumeric LCDs with fewer wires include:

  • SPI-based solutions using shift registers like 74HC595 and CD4094B
  • I2C-based solutions using I2C port expander ICs like PCF8574 and MCP23S17.

We can use either hardware-based SPI/I2C modules or emulate these in software using bit-banging methods. The former adds some much-needed processing speed which is impossible with bit-banging.

In this segment, we will see how to use a CD4094B CMOS shift-register with software SPI to drive an alphanumeric LCD.

Code Example

 

lcd.h

 

#include <msp430.h>
#include <delay.h>


#define LCD_PORT                                P2OUT

#define SDO                                     BIT0
#define SCK                                     BIT1
#define STB                                     BIT2

#define SDO_HIGH                                LCD_PORT |= SDO
#define SDO_LOW                                 LCD_PORT &= ~SDO

#define SCK_HIGH                                LCD_PORT |= SCK
#define SCK_LOW                                 LCD_PORT &= ~SCK

#define STB_HIGH                                LCD_PORT |= STB
#define STB_LOW                                 LCD_PORT &= ~STB

#define clear_display                           0x01
#define goto_home                               0x02

#define cursor_direction_inc                    (0x04 | 0x02)
#define cursor_direction_dec                    (0x04 | 0x00)
#define display_shift                           (0x04 | 0x01)
#define display_no_shift                        (0x04 | 0x00)

#define display_on                              (0x08 | 0x04)
#define display_off                             (0x08 | 0x02)
#define cursor_on                               (0x08 | 0x02)
#define cursor_off                              (0x08 | 0x00)
#define blink_on                                (0x08 | 0x01)
#define blink_off                               (0x08 | 0x00)

#define _8_pin_interface                        (0x20 | 0x10)
#define _4_pin_interface                        (0x20 | 0x00)
#define _2_row_display                          (0x20 | 0x08)
#define _1_row_display                          (0x20 | 0x00)
#define _5x10_dots                              (0x20 | 0x40)
#define _5x7_dots                               (0x20 | 0x00)

#define dly                                     1


unsigned char data_value;


void SIPO(void);
void LCD_init(void); 
void LCD_command(unsigned char value);
void LCD_send_data(unsigned char value);
void LCD_4bit_send(unsigned char lcd_data);                                     
void LCD_putstr(char *lcd_string);
void LCD_putchar(char char_data);
void LCD_clear_home(void);           
void LCD_goto(unsigned char x_pos, unsigned char y_pos);

 

lcd.c

 

#include "lcd.h"


void SIPO(void)
{
    unsigned char bit = 0;
    unsigned char clk = 8;
    unsigned char temp = 0;

    temp = data_value;
    STB_LOW;

    while(clk > 0)
    {
        bit = ((temp & 0x80) >> 0x07);
        bit &= 0x01;

        switch(bit)
        {
            case 0:
            {
                SDO_LOW;
                break;
            }
            default:
            {
                SDO_HIGH;
                break;
            }

        }

        SCK_HIGH;

        temp <<= 1;
        clk--;

        SCK_LOW;
    }

    STB_HIGH;
}


void LCD_init(void)
{                                    
    unsigned char t = 0x0A;

    data_value = 0x08;
    SIPO();
    while(t > 0x00)
    {  
            delay_ms(dly);
            t--;
    };     

    data_value = 0x30;
    SIPO();

    data_value |= 0x08;
    SIPO();
    delay_ms(dly);
    data_value &= 0xF7;
    SIPO();
    delay_ms(dly);

    data_value = 0x30;
    SIPO();

    data_value |= 0x08;
    SIPO();
    delay_ms(dly);
    data_value &= 0xF7;
    SIPO();
    delay_ms(dly);

    data_value = 0x30;
    SIPO();

    data_value |= 0x08;
    SIPO();
    delay_ms(dly);
    data_value &= 0xF7;
    SIPO();
    delay_ms(dly);

    data_value = 0x20;
    SIPO();

    data_value |= 0x08;
    SIPO();
    delay_ms(dly);
    data_value &= 0xF7;
    SIPO();
    delay_ms(dly);

    LCD_command(_4_pin_interface | _2_row_display | _5x7_dots);        
    LCD_command(display_on | cursor_off | blink_off);    
    LCD_command(clear_display);        
    LCD_command(cursor_direction_inc | display_no_shift);       
}  


void LCD_command(unsigned char value)
{                                  
    data_value &= 0xFB;
    SIPO();
    LCD_4bit_send(value);          
}


void LCD_send_data(unsigned char value)
{                              
    data_value |= 0x04;
    SIPO();
    LCD_4bit_send(value);


void LCD_4bit_send(unsigned char lcd_data)      
{
    unsigned char temp = 0x00;

    temp = (lcd_data & 0xF0);
    data_value &= 0x0F;
    data_value |= temp;
    SIPO();

    data_value |= 0x08;
    SIPO();
    delay_ms(dly);
    data_value &= 0xF7;
    SIPO();
    delay_ms(dly);

    temp = (lcd_data & 0x0F);
    temp <<= 0x04;
    data_value &= 0x0F;
    data_value |= temp;
    SIPO();

    data_value |= 0x08;
    SIPO();
    delay_ms(dly);
    data_value &= 0xF7;
    SIPO();
    delay_ms(dly);


void LCD_putstr(char *lcd_string)
{
    while (*lcd_string != '\0')  
    {
        LCD_send_data(*lcd_string);
        lcd_string++;
    };
}


void LCD_putchar(char char_data)
{
    LCD_send_data(char_data);
}


void LCD_clear_home(void)
{
    LCD_command(clear_display);
    LCD_command(goto_home);
}


void LCD_goto(unsigned char x_pos,unsigned char y_pos)
{                                                  
    if(y_pos == 0)   
    {                              
        LCD_command(0x80 | x_pos);
    }
    else
    {                                             
        LCD_command(0x80 | 0x40 | x_pos);
    }
}

 

main.c

 

#include <msp430.h>
#include "delay.h"
#include "lcd.h"


void BCSplus_graceInit(void);
void GPIO_graceInit(void);
void System_graceInit(void);

/*
 * main.c
 */
void main(void)
{
    unsigned char s = 0x00;

    const char txt1[] = {"MICROARENA"};
    const char txt2[] = {"SShahryiar"};
    const char txt3[] = {"MSP-EXP430G2"};
    const char txt4[] = {"Launchpad!"};


    BCSplus_graceInit();
    GPIO_graceInit();
    System_graceInit();
    LCD_init();

    LCD_clear_home();

    LCD_goto(3, 0);
    LCD_putstr(txt1);
    LCD_goto(3, 1);
    LCD_putstr(txt2);
    delay_ms(2600);

    LCD_clear_home();

    for(s = 0; s < 12; s++)
    {
        LCD_goto((2 + s), 0);
        LCD_putchar(txt3[s]);
        delay_ms(60);
    }
    for(s = 0; s < 10; s++)
    {
        LCD_goto((3 + s), 1);
        LCD_putchar(txt4[s]);
        delay_ms(60);
    }

    s = 0;
    LCD_clear_home();

    LCD_goto(3, 0);
    LCD_putstr(txt1);

    while(1)
    {
        show_value(s);
        s++;
        delay_ms(200);
    };
}


void BCSplus_graceInit(void)
{
    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

    if (CALBC1_16MHZ != 0xFF)
    {
        /* Adjust this accordingly to your VCC rise time */
        __delay_cycles(1000);

        /* Follow recommended flow. First, clear all DCOx and MODx bits. Then
         * apply new RSELx values. Finally, apply new DCOx and MODx bit values.
         */
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_16MHZ;     /* Set DCO to 16MHz */
        DCOCTL = CALDCO_16MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_0 -- If XTS = 0, XT1 = 32768kHz Crystal ; If XTS = 1, XT1 = 0.4 - 1-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_0 | XCAP_1;
}


void GPIO_graceInit(void)
{
    /* Port 1 Output Register */
    P1OUT = 0;

    /* Port 1 Direction Register */
    P1DIR = 0;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Port Select Register */
    P2SEL &= ~(BIT6 | BIT7);

    /* Port 2 Direction Register */
    P2DIR = BIT0 | BIT1 | BIT2;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;
}


void System_graceInit(void)
{
    WDTCTL = WDTPW | WDTHOLD;
}


void show_value(unsigned char value)
{
   unsigned char ch = 0x00;

   ch = ((value / 100) + 0x30);
   LCD_goto(6, 1);
   LCD_putchar(ch);

   ch = (((value / 10) % 10) + 0x30);
   LCD_goto(7, 1);
   LCD_putchar(ch);

   ch = ((value % 10) + 0x30);
   LCD_goto(8, 1);
   LCD_putchar(ch);
}

 

Simulation

Simulation 3

Explanation

Basically here, the same LCD library as in the LCD example has been used. The only difference is the Serial-In-Parallel-Out (SIPO) part. I used a CD4094B CMOS SIPO shift register between the LCD and the host MSP430 micro to achieve the port-expansion task. Only three pins of the host micro are needed to drive the LCD. In the LCD library, you can see SIPO function on the top. This part translates serial inputs to parallel outputs. Note that at every level the stored value in the SIPO is either ORed or inverse ANDed instead of entirely changing the SIPO’s current content. In simple terms, it is like read-modification-write.  This is to ensure that only the target bits are altered, not the entire content of the SIPO. In this way to some extent, data corruption is prevented.

Demo

SPI LCD (1) SPI LCD (2)

Demo video: https://www.youtube.com/watch?v=NBfBqtjZ35s.

Hardware I2C – USI

I2C is another popular form of on board synchronous serial communication developed by NXP. It just uses two wires for communication and so it is also referred as Two Wire Interface (TWI). Just like SPI, I2C is widely used in interfacing real-time clocks (RTC), digital sensors, memory chips and so on. It rivals SPI but compared to SPI it is slower and have some limitations. Typical bus speed ranges from a few kilohertz to 400kHz. Up to 127 devices can coexist in an I2C bus. In an I2C bus it is not possible, by conventional means to interface devices with same device IDs or devices with different logic voltage levels without logic level converters and so on. Still however, I2C is very popular because these issues rarely arise and because of its simplicity. Unlike other communications, there’s no pin/wire swapping as two wires connect straight to the bus – SDA to SDA and SCL to SCL.

I2C Comms

As with SPI, an I2C bus must contain one master and one or more slaves. The master is solely responsible for generating clock signals and initiating communication. Communication starts when master sends out a slave’s ID with read/write command and request. The slave reacts to this command by processing the request from the master and sending out data or processing it. I2C bus is always pulled-up using pull-up resistors. Without these pull-up resistors, I2C bus may not function properly.

To know more about I2C interface visit the following links:

Other protocols like SMBus and I2S have similarities with I2C and so learning about I2C advances learning these too.

Code Example

 

I2C.h

 

#include <msp430.h>


#define FALSE                           0
#define TRUE                            1

#define wr                              FALSE
#define rd                              TRUE

#define SET_SDA_AS_OUTPUT()             (USICTL0 |= USIOE)
#define SET_SDA_AS_INPUT()              (USICTL0 &= ~USIOE)


#define FORCING_SDA_HIGH()             \
        {                              \
          USISRL = 0xFF;               \
          USICTL0 |= USIGE;            \
          USICTL0 &= ~(USIGE + USIOE); \
        }

#define FORCING_SDA_LOW()              \
        {                              \
          USISRL = 0x00;               \
          USICTL0 |= (USIGE + USIOE);  \
          USICTL0 &= ~USIGE;           \
        }


void i2c_usi_mst_gen_start(void);
void i2c_usi_mst_gen_repeated_start(void);
void i2c_usi_mst_gen_stop(void);
void i2c_usi_mst_wait_usi_cnt_flag(void);
unsigned char i2c_usi_mst_send_byte(unsigned char value);
unsigned char i2c_usi_mst_read_byte(void);
void i2c_usi_mst_send_n_ack(unsigned char ack);
unsigned char i2c_usi_mst_send_address(unsigned char addr, unsigned char r_w);

 

I2C.c

 

#include "I2C.h"


static unsigned char usi_cnt_flag = FALSE;


// function to generate I2C START condition
void i2c_usi_mst_gen_start(void)
{
  // make sure SDA line is in HIGH level
  FORCING_SDA_HIGH();

  // small delay
  _delay_cycles(100);

  // pull down SDA to create START condition
  FORCING_SDA_LOW();
}


// function to generate I2C REPEATED START condition
void i2c_usi_mst_gen_repeated_start(void)
{
  USICTL0 |= USIOE;
  USISRL = 0xFF;
  USICNT = 1;

  // wait for USIIFG is set
  i2c_usi_mst_wait_usi_cnt_flag();

  // small delay
  _delay_cycles(100);

  // pull down SDA to create START condition
  FORCING_SDA_LOW();

  // small delay
  _delay_cycles(100);
}


// function to generate I2C STOP condition
void i2c_usi_mst_gen_stop(void)
{
  USICTL0 |= USIOE;
  USISRL = 0x00;
  USICNT = 1;

  // wait for USIIFG is set
  i2c_usi_mst_wait_usi_cnt_flag();

  FORCING_SDA_HIGH();
}


// function to wait for I2C counter flag condition
void i2c_usi_mst_wait_usi_cnt_flag(void)
{
  while(usi_cnt_flag == FALSE)
  {
    //__bis_SR_register(LPM0_bits);
  }

  // reset flag
  usi_cnt_flag = FALSE;
}


// function to send a byte
unsigned char i2c_usi_mst_send_byte(unsigned char data_byte)
{
  // send address and R/W bit
  SET_SDA_AS_OUTPUT();
  USISRL = data_byte;
  USICNT = (USICNT & 0xE0) + 8;

  // wait until USIIFG is set
  i2c_usi_mst_wait_usi_cnt_flag();

  // check NACK/ACK
  SET_SDA_AS_INPUT();
  USICNT = (USICNT & 0xE0) + 1;

  // wait for USIIFG is set
  i2c_usi_mst_wait_usi_cnt_flag();

  if(USISRL & 0x01)
  {
    // NACK received returns FALSE
    return FALSE;
  }

  return TRUE;
}


// function to read a byte
unsigned char i2c_usi_mst_read_byte(void)
{
  SET_SDA_AS_INPUT();
  USICNT = (USICNT & 0xE0) + 8;

  // wait for USIIFG is set
  i2c_usi_mst_wait_usi_cnt_flag();

  return USISRL;
}


// function to send (N)ACK bit
void i2c_usi_mst_send_n_ack(unsigned char ack)
{
  // send (N)ack bit
  SET_SDA_AS_OUTPUT();
  if(ack)
  {
    USISRL = 0x00;
  }
  else
  {
    USISRL = 0xFF;
  }
  USICNT = (USICNT & 0xE0) + 1;

  // wait until USIIFG is set
  i2c_usi_mst_wait_usi_cnt_flag();

  // set SDA as input
  SET_SDA_AS_INPUT();
}


// function to send I2C address with R/W bit
unsigned char i2c_usi_mst_send_address(unsigned char addr, unsigned char r_w)
{
  addr <<= 1;
  if(r_w)
  {
    addr |= 0x01;
  }
  return(i2c_usi_mst_send_byte(addr));
}


// USI I2C ISR function
#pragma vector=USI_VECTOR
__interrupt void USI_ISR (void)
{
  if(USICTL1 & USISTTIFG)
  {
    // do something if necessary

    // clear flag
    USICTL1 &= ~USISTTIFG;
  }

  if(USICTL1 & USIIFG)
  {
    // USI counter interrupt flag
    usi_cnt_flag = TRUE;

    // clear flag
    USICTL1 &= ~USIIFG;
  }

  //__bic_SR_register_on_exit(LPM0_bits);
}

 

PCF8574.h

 

#include "I2C.h"


#define PCF8574_address                 0x20


unsigned char PCF8574_read(void);
void PCF8574_write(unsigned char data_byte);

 

PCF8574.c

 

#include "PCF8574.h"


unsigned char PCF8574_read(void)
{
    unsigned char port_byte = 0x00;

    i2c_usi_mst_gen_start();
    i2c_usi_mst_send_address(PCF8574_address, rd);
    port_byte = i2c_usi_mst_read_byte();
    i2c_usi_mst_send_n_ack(0);
    i2c_usi_mst_gen_stop();

    return port_byte;
}


void PCF8574_write(unsigned char data_byte)
{
    i2c_usi_mst_gen_start();
    i2c_usi_mst_send_address(PCF8574_address, wr);
    i2c_usi_mst_send_byte(data_byte);
    i2c_usi_mst_gen_stop();
}

 

main.c

 

#include <msp430.h>
#include "delay.h"
#include "I2C.h"
#include "PCF8574.h"


void GPIO_graceInit(void);
void BCSplus_graceInit(void);
void USI_graceInit(void);
void System_graceInit(void);
void WDTplus_graceInit(void);


void main(void)
{
    unsigned char i = 0;

    /* Stop watchdog timer from timing out during initial start-up. */
    WDTCTL = WDTPW | WDTHOLD;

    /* initialize Config for the MSP430 GPIO */
    GPIO_graceInit();

    /* initialize Config for the MSP430 2xx family clock systems (BCS) */
    BCSplus_graceInit();

    /* initialize Config for the MSP430 USI */
    USI_graceInit();

    /* initialize Config for the MSP430 System Registers */
    System_graceInit();

    /* initialize Config for the MSP430 WDT+ */
    WDTplus_graceInit();

    while(1)
    {
        for(i = 1; i < 128; i <<= 1)
        {
            PCF8574_write(i);
            delay_ms(200);
        }
        for(i = 128; i > 1; i >>= 1)
        {
            PCF8574_write(i);
            delay_ms(200);
        }
    };
}


void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

    /* Port 1 Output Register */
    P1OUT = 0;

    /* Port 1 Port Select Register */
    P1SEL = BIT6 | BIT7;

    /* Port 1 Direction Register */
    P1DIR = 0;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Direction Register */
    P2DIR = 0;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */
}


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

    if (CALBC1_1MHZ != 0xFF) {
        /* Follow recommended flow. First, clear all DCOx and MODx bits. Then
         * apply new RSELx values. Finally, apply new DCOx and MODx bit values.
         */
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_1MHZ;      /* Set DCO to 1MHz */
        DCOCTL = CALDCO_1MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_2 -- If XTS = 0, XT1 = VLOCLK ; If XTS = 1, XT1 = 3 - 16-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_2 | XCAP_1;

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}


void USI_graceInit(void)
{
    /* USER CODE START (section: USI_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: USI_graceInit_prologue) */

    /* Disable USI */
    USICTL0 |= USISWRST;

    /*
     * USI Control Register 0
     *
     * USIPE7 -- USI function enabled
     * USIPE6 -- USI function enabled
     * ~USIPE5 -- USI function disabled
     * ~USILSB -- MSB first
     * USIMST -- Master mode
     * ~USIGE -- Output latch enable depends on shift clock
     * ~USIOE -- Output disabled
     * USISWRST -- USI logic held in reset state
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    USICTL0 = USIPE7 | USIPE6 | USIMST | USISWRST;

    /*
     * USI Control Register 1
     *
     * ~USICKPH -- Data is changed on the first SCLK edge and captured on the following edge
     * USII2C -- I2C mode enabled
     * ~USISTTIE -- Interrupt on START condition disabled
     * USIIE -- Interrupt enabled
     * ~USIAL -- No arbitration lost condition
     * ~USISTP -- No STOP condition received
     * ~USISTTIFG -- No START condition received. No interrupt pending
     * USIIFG -- Interrupt pending
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    USICTL1 = USII2C | USIIE | USIIFG;

    /*
     * USI Clock Control Register
     *
     * USIDIV_4 -- Divide by 16
     * USISSEL_2 -- SMCLK
     * USICKPL -- Inactive state is high
     * ~USISWCLK -- Input clock is low
     *
     * Note: ~USISWCLK indicates that USISWCLK has value zero
     */
    USICKCTL = USIDIV_4 | USISSEL_2 | USICKPL;

    /*
     * USI Bit Counter Register
     *
     * ~USISCLREL -- SCL line is held low if USIIFG is set
     * ~USI16B -- 8-bit shift register mode. Low byte register USISRL is used
     * USIIFGCC -- USIIFG is not cleared automatically
     * ~USICNT4 -- USI bit count
     * ~USICNT3 -- USI bit count
     * ~USICNT2 -- USI bit count
     * ~USICNT1 -- USI bit count
     * ~USICNT0 -- USI bit count
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    USICNT = USIIFGCC;

    /* Enable USI */
    USICTL0 &= ~USISWRST;

    /* Clear pending flag */
    USICTL1 &= ~(USIIFG + USISTTIFG);

    /* USER CODE START (section: USI_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: USI_graceInit_epilogue) */
}


void System_graceInit(void)
{
    /* USER CODE START (section: System_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: System_graceInit_prologue) */

    /* Clear oscillator fault flag with software delay */
    do
    {
        // Clear OSC fault flag
        IFG1 &= ~OFIFG;

        // 50us delay
        __delay_cycles(50);
    } while (IFG1 & OFIFG);

    /*
     * SR, Status Register
     *
     * ~SCG1 -- Disable System clock generator 1
     * ~SCG0 -- Disable System clock generator 0
     * ~OSCOFF -- Oscillator On
     * ~CPUOFF -- CPU On
     * GIE -- General interrupt enable
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    __bis_SR_register(GIE);

    /* USER CODE START (section: System_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: System_graceInit_epilogue) */
}


void WDTplus_graceInit(void)
{
    /* USER CODE START (section: RTC_B_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: RTC_B_graceInit_prologue) */

    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * WDTHOLD -- Watchdog timer+ is stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * ~WDTTMSEL -- Watchdog mode
     * ~WDTCNTCL -- No action
     * ~WDTSSEL -- SMCLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * ~WDTIS1 -- Watchdog clock source bit1 disabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTHOLD;

    /* USER CODE START (section: RTC_B_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: RTC_B_graceInit_epilogue) */
}

 

Simulation

USI-I2C Sim

Explanation

To keep things simple, I demoed USI-based I2C using PCF8574 8-bit I2C GPIO expander. Here the GPIOs of PCF8574 are used to create a running LED light pattern similar to Kitt Scan from the popular series Knight Rider. The code for USI-based I2C implementation is obtained from TI’s wiki page: http://processors.wiki.ti.com/index.php/I2C_Communication_with_USI_Module.  I did some minor modifications on it. The code is self-explanatory with detailed documentation on the wiki page and so I won’t be discussing it. The rest of the code is the implementation of PCF8574 driver and actual demo code.

USI-I2C

Demo

USI-I2C

Demo video: https://www.youtube.com/watch?v=9svi-0cd2gk.

Software I2C

Software I2C implementation is a bit complex compared to software SPI. This is because there are start-stop conditions and acknowledgments that are also needed to be taken care off along with data transactions. However, it becomes the only option when USI/USCI are absent. Unlike SPI, I2C is slow and so software implementation doesn’t affect performance significantly.

ATMega328P Weather Station

Shown above is an I2C-based SSD1306 OLED display. This was the display for my weather station. The weather station was based on a Sparkfun Weather Board.  During that project, I was encountering an issue with the on board SHT15 relative humidity-temperature sensor chip and I could not find out the issue for quite some time. I thought that the MCU’s TWI module was malfunctioning and so I took the help of software I2C for rooting out the cause. Cases like these make software I2C-based testing really useful and interesting.

I2C

Shown above is the typical timing diagram for a I2C bus. We can expect the same patterns in both hardware I2C and software I2C but bus speed may differ. I2C bus speed become critical in some cases. For example, consider the case of the OLED display above.

Code Example

 

SW_I2C.h

 

#include <msp430.h>
#include "delay.h"

#define SW_I2C_DIR      P1DIR
#define SW_I2C_OUT      P1OUT
#define SW_I2C_IN       P1IN

#define SDA_pin         BIT7
#define SCL_pin         BIT6

#define SDA_DIR_OUT()   do{SW_I2C_DIR |= SDA_pin;}while(0)
#define SDA_DIR_IN()    do{SW_I2C_DIR &= ~SDA_pin;}while(0)
#define SCL_DIR_OUT()   do{SW_I2C_DIR |= SCL_pin;}while(0)
#define SCL_DIR_IN()    do{SW_I2C_DIR &= ~SCL_pin;}while(0)

#define SDA_HIGH()      do{SW_I2C_OUT |= SDA_pin;}while(0)
#define SDA_LOW()       do{SW_I2C_OUT &= ~SDA_pin;}while(0)
#define SCL_HIGH()      do{SW_I2C_OUT |= SCL_pin;}while(0)
#define SCL_LOW()       do{SW_I2C_OUT &= ~SCL_pin;}while(0)

#define SDA_IN()        (SW_I2C_IN & SDA_pin)

#define I2C_ACK         0xFF
#define I2C_NACK        0x00

#define I2C_timeout     1000

void SW_I2C_init(void);
void SW_I2C_start(void);
void SW_I2C_stop(void);
unsigned char SW_I2C_read(unsigned char ack);
void SW_I2C_write(unsigned char value);
void SW_I2C_ACK_NACK(unsigned char mode);
unsigned char SW_I2C_wait_ACK(void);

 

SW_I2C.c

 

#include "SW_I2C.h"


void SW_I2C_init(void)
{
    SDA_DIR_OUT();
    SCL_DIR_OUT();
    delay_ms(1);
    SDA_HIGH();
    SCL_HIGH();
}


void SW_I2C_start(void)
{
    SDA_DIR_OUT();
    SDA_HIGH();
    SCL_HIGH();
    delay_us(4);
    SDA_LOW();
    delay_us(4);
    SCL_LOW();
}


void SW_I2C_stop(void)
{
    SDA_DIR_OUT();
    SDA_LOW();
    SCL_LOW();
    delay_us(4);
    SDA_HIGH();
    SCL_HIGH();
    delay_us(4);
}


unsigned char SW_I2C_read(unsigned char ack)
{
    unsigned char i = 8;
    unsigned char j = 0;

    SDA_DIR_IN();

    while(i > 0)
    {
        SCL_LOW();
        delay_us(2);
        SCL_HIGH();
        delay_us(2);
        j <<= 1;

        if(SDA_IN() != 0x00)
        {
            j++;
        }

        delay_us(1);
        i--;
    };

    switch(ack)
    {
        case I2C_ACK:
        {
            SW_I2C_ACK_NACK(I2C_ACK);;
            break;
        }
        default:
        {
            SW_I2C_ACK_NACK(I2C_NACK);;
            break;
        }
    }

    return j;
}


void SW_I2C_write(unsigned char value)
{
    unsigned char i = 8;

    SDA_DIR_OUT();
    SCL_LOW();

    while(i > 0)
    {

        if(((value & 0x80) >> 7) != 0x00)
        {
            SDA_HIGH();
        }
        else
        {
            SDA_LOW();
        }


        value <<= 1;
        delay_us(2);
        SCL_HIGH();
        delay_us(2);
        SCL_LOW();
        delay_us(2);
        i--;
    };
}


void SW_I2C_ACK_NACK(unsigned char mode)
{
    SCL_LOW();
    SDA_DIR_OUT();

    switch(mode)
    {
        case I2C_ACK:
        {
            SDA_LOW();
            break;
        }
        default:
        {
            SDA_HIGH();
            break;
        }
    }

    delay_us(2);
    SCL_HIGH();
    delay_us(2);
    SCL_LOW();
}


unsigned char SW_I2C_wait_ACK(void)
{
    signed int timeout = 0;

    SDA_DIR_IN();

    SDA_HIGH();
    delay_us(1);
    SCL_HIGH();
    delay_us(1);

    while(SDA_IN() != 0x00)
    {
        timeout++;

        if(timeout > I2C_timeout)
        {
            SW_I2C_stop();
            return 1;
        }
    };

    SCL_LOW();
    return 0;
}

 

PCF8591.h

 

#define PCF8591_address                               0x90

#define PCF8591_read_cmd                              (PCF8591_address | 0x01)
#define PCF8591_write_cmd                             PCF8591_address

#define AIN0                                          0x00
#define AIN1                                          0x01
#define AIN2                                          0x02
#define AIN3                                          0x03

#define Auto_Increment_Enable                         0x04
#define Auto_Increment_Disable                        0x00

#define Four_Channel_ADC                              0x00
#define Three_differential_Inputs                     0x10
#define AIN0_and_1_Single_AIN2_and_AIN3_Differential  0x20
#define All_Differential                              0x30

#define AOut_enable                                   0x40
#define AOut_disable                                  0x00


void PCF8591_write(unsigned char control_value, unsigned char data_value);
unsigned char PCF8591_read(unsigned char control_value);

 

 

 

PCF8591.c

 

#include "PCF8591.h"


void PCF8591_write(unsigned char control_value, unsigned char data_value)
{
     SW_I2C_start();
     SW_I2C_write(PCF8591_write_cmd);
     SW_I2C_wait_ACK();
     SW_I2C_write((control_value & 0xFF));
     SW_I2C_wait_ACK();
     SW_I2C_write(data_value);
     SW_I2C_wait_ACK();
     SW_I2C_stop();
}


unsigned char PCF8591_read(unsigned char control_value)
{
     unsigned char value = 0;

     SW_I2C_start();
     SW_I2C_write(PCF8591_write_cmd);
     SW_I2C_wait_ACK();
     SW_I2C_write((control_value & 0xFF));
     SW_I2C_ACK_NACK(I2C_ACK);
     SW_I2C_stop();

     SW_I2C_start();
     SW_I2C_write(PCF8591_read_cmd);
     SW_I2C_wait_ACK();
     value = SW_I2C_read(0);
     SW_I2C_wait_ACK();
     SW_I2C_stop();

     return value;
}

 

main.c

 

#include <msp430.h>
#include "delay.h"
#include "SW_I2C.h"
#include "PCF8591.h"
#include "lcd.h"


void GPIO_graceInit(void);
void BCSplus_graceInit(void);
void System_graceInit(void);
void WDTplus_graceInit(void);
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned char value);


void main(void)
{
    unsigned char adc0 = 0;
    unsigned char adc1 = 0;
    unsigned char adc2 = 0;
    unsigned char adc3 = 0;

    WDTCTL = WDTPW | WDTHOLD;    // Stop watchdog timer

    /* initialize Config for the MSP430 GPIO */
    GPIO_graceInit();

    /* initialize Config for the MSP430 2xx family clock systems (BCS) */
    BCSplus_graceInit();

    /* initialize Config for the MSP430 System Registers */
    System_graceInit();

    /* initialize Config for the MSP430 WDT+ */
    WDTplus_graceInit();

    SW_I2C_init();
    LCD_init();

    LCD_goto(0, 0);
    LCD_putstr("A0:");

    LCD_goto(9, 0);
    LCD_putstr("A1:");

    LCD_goto(0, 1);
    LCD_putstr("A2:");

    LCD_goto(9, 1);
    LCD_putstr("A3:");

    while(1)
    {
        adc0 = PCF8591_read(AOut_enable | Four_Channel_ADC | Auto_Increment_Disable | AIN0);
        lcd_print(4, 0, adc0);

        adc1 = PCF8591_read(AOut_enable | Four_Channel_ADC | Auto_Increment_Disable | AIN1);
        lcd_print(13, 0, adc1);

        adc2 = PCF8591_read(AOut_enable | Four_Channel_ADC | Auto_Increment_Disable | AIN2);
        lcd_print(4, 1, adc2);

        adc3 = PCF8591_read(AOut_enable | Four_Channel_ADC | Auto_Increment_Disable | AIN3);
        lcd_print(13, 1, adc3);

        PCF8591_write(AOut_enable, adc0);
        delay_ms(400);
    };
}


void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

    /* Port 1 Output Register */
    P1OUT = 0;

    /* Port 1 Direction Register */
    P1DIR = BIT0 | BIT6 | BIT7;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Port Select Register */
    P2SEL &= ~(BIT6 | BIT7);

    /* Port 2 Direction Register */
    P2DIR = BIT0 | BIT1 | BIT2;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */
}


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

    if (CALBC1_1MHZ != 0xFF) {
        /* Follow recommended flow. First, clear all DCOx and MODx bits. Then
         * apply new RSELx values. Finally, apply new DCOx and MODx bit values.
         */
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_1MHZ;      /* Set DCO to 1MHz */
        DCOCTL = CALDCO_1MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_0 -- If XTS = 0, XT1 = 32768kHz Crystal ; If XTS = 1, XT1 = 0.4 - 1-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_0 | XCAP_1;

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}


void System_graceInit(void)
{
    /* USER CODE START (section: System_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: System_graceInit_prologue) */

    /*
     * SR, Status Register
     *
     * ~SCG1 -- Disable System clock generator 1
     * ~SCG0 -- Disable System clock generator 0
     * ~OSCOFF -- Oscillator On
     * ~CPUOFF -- CPU On
     * GIE -- General interrupt enable
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    __bis_SR_register(GIE);

    /* USER CODE START (section: System_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: System_graceInit_epilogue) */
}


void WDTplus_graceInit(void)
{
    /* USER CODE START (section: RTC_B_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: RTC_B_graceInit_prologue) */

    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * WDTHOLD -- Watchdog timer+ is stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * ~WDTTMSEL -- Watchdog mode
     * ~WDTCNTCL -- No action
     * ~WDTSSEL -- SMCLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * ~WDTIS1 -- Watchdog clock source bit1 disabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTHOLD;

    /* USER CODE START (section: RTC_B_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: RTC_B_graceInit_epilogue) */
}


void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned char value)
{
   unsigned char ch = 0;

   ch = ((value / 100) + 0x30);
   LCD_goto(x_pos, y_pos);
   LCD_putchar(ch);

   ch = (((value / 10) % 10) + 0x30);
   LCD_goto((x_pos + 1), y_pos);
   LCD_putchar(ch);

   ch = ((value % 10) + 0x30);
   LCD_goto((x_pos + 2), y_pos);
   LCD_putchar(ch);
}

 

Simulation

SW-I2C Sim

Explanation

Just like software-based SPI, software-based I2C employs bit-banging ordinary digital I/Os. The whole functioning of I2C protocol is implemented in software. Again, the software I2C codes are self-explanatory. This software I2C implementation can be used for any I2C-based device. If you have gone through the pages recommended earlier, you will understand how it is working.

For this demo, a PCF8591 8-bit ADC-DAC module is used. This ready-made board hosts four 8-bit ADC input channels and an 8-bit DAC output channel. Off the shelf, it contains a thermistor for sensing ambient temperature, a light-dependent resistor (LDR) for detecting light level, a potentiometer connected across the power rail and a free ADC input. The ADC inputs can also be used in various ways. Check the device’s datasheet for details. I did a very raw level coding for the demo, and so I just read and showed the ADC values. The DAC is operated using the value of ADC channel 0.

Demo

SW-I2C

Demo video: https://www.youtube.com/watch?v=ElRvBRv7YY4.

Two Wire LCD

This segment is basically the repetition of the bit-banging-based LCD example shown earlier. In that example, we saw software SPI-based LCD driving technique. Here we will see the same but with USI-based I2C. We can also use software I2C for the same purpose.

I2C LCD

In the embedded system world, there is a cheap and popular 2-wire LCD module based on PCF8574T.

i2c_backpack_01-1024x527

There are a few advantages of this module. Firstly, it is based on a PCF8574T chip that is made by NXP (a.k.a Philips). NXP happens to be the founder of I2C communication and so the chip is well documented in terms of I2C communication. Secondly, there are three external address selection bits which can be used to address multiple LCDs existing on the same I2C bus. Lastly the module is compact and readily plug-and-playable.

Code Example

 

lcd.h

 

#include <msp430.h>
#include "PCF8574.h"
#include "delay.h"


#define clear_display                           0x01
#define goto_home                               0x02

#define cursor_direction_inc                    (0x04 | 0x02)
#define cursor_direction_dec                    (0x04 | 0x00)
#define display_shift                           (0x04 | 0x01)
#define display_no_shift                        (0x04 | 0x00)

#define display_on                              (0x08 | 0x04)
#define display_off                             (0x08 | 0x02)
#define cursor_on                               (0x08 | 0x02)
#define cursor_off                              (0x08 | 0x00)
#define blink_on                                (0x08 | 0x01)
#define blink_off                               (0x08 | 0x00)

#define _8_pin_interface                        (0x20 | 0x10)
#define _4_pin_interface                        (0x20 | 0x00)
#define _2_row_display                          (0x20 | 0x08)
#define _1_row_display                          (0x20 | 0x00)
#define _5x10_dots                              (0x20 | 0x40)
#define _5x7_dots                               (0x20 | 0x00)

#define dly                                     1

#define CMD                                     0
#define DAT                                     1

#define BL_ON                                   1
#define BL_OFF                                  0


unsigned char bl_state;
unsigned char data_value;


void LCD_init(void);
void LCD_send(unsigned char value, unsigned char control_type);
void LCD_4bit_send(unsigned char lcd_data);
void LCD_putstr(char *lcd_string);
void LCD_putchar(char char_data);
void LCD_clear_home(void);
void LCD_goto(unsigned char x_pos, unsigned char y_pos);

 

lcd.c

 

#include "lcd.h"


extern unsigned char data_value;


void LCD_init(void)
{
    bl_state = BL_ON;
    data_value = 0x04;
    PCF8574_write(data_value);

    delay_ms(10);

    data_value = 0x30;
    PCF8574_write(data_value);

    data_value |= 0x04;
    PCF8574_write(data_value);
    delay_ms(dly);
    data_value &= 0xF1;
    PCF8574_write(data_value);
    delay_ms(dly);

    data_value = 0x30;
    PCF8574_write(data_value);

    data_value |= 0x04;
    PCF8574_write(data_value);
    delay_ms(dly);
    data_value &= 0xF1;
    PCF8574_write(data_value);
    delay_ms(dly);

    data_value = 0x30;
    PCF8574_write(data_value);

    data_value |= 0x04;
    PCF8574_write(data_value);
    delay_ms(dly);
    data_value &= 0xF1;
    PCF8574_write(data_value);
    delay_ms(dly);

    data_value = 0x20;
    PCF8574_write(data_value);

    data_value |= 0x04;
    PCF8574_write(data_value);
    delay_ms(dly);
    data_value &= 0xF1;
    PCF8574_write(data_value);
    delay_ms(dly);

    LCD_send((_4_pin_interface | _2_row_display | _5x7_dots), CMD);
    LCD_send((display_on | cursor_off | blink_off), CMD);
    LCD_send((clear_display), CMD);
    LCD_send((cursor_direction_inc | display_no_shift), CMD);
}


void LCD_send(unsigned char value, unsigned char control_type)
{
    switch(control_type)
    {
       case CMD:
       {
            data_value &= 0xF4;
            break;
       }
       case DAT:
       {
           data_value |= 0x01;
           break;
       }
    }

    switch(bl_state)
    {
       case BL_ON:
       {
          data_value |= 0x08;
          break;
       }
       case BL_OFF:
       {
          data_value &= 0xF7;
          break;
       }
    }

    PCF8574_write(data_value);
    LCD_4bit_send(value);
    delay_ms(10);
}


void LCD_4bit_send(unsigned char lcd_data)
{
    unsigned char temp = 0x00;

    temp = (lcd_data & 0xF0);
    data_value &= 0x0F;
    data_value |= temp;
    PCF8574_write(data_value);

    data_value |= 0x04;
    PCF8574_write(data_value);
    delay_ms(dly);
    data_value &= 0xF9;
    PCF8574_write(data_value);
    delay_ms(dly);

    temp = (lcd_data & 0x0F);
    temp <<= 0x04;
    data_value &= 0x0F;
    data_value |= temp;
    PCF8574_write(data_value);

    data_value |= 0x04;
    PCF8574_write(data_value);
    delay_ms(dly);
    data_value &= 0xF9;
    PCF8574_write(data_value);
    delay_ms(dly);
}


void LCD_putstr(char *lcd_string)
{
    while(*lcd_string != '\0')
    {
        LCD_send((*lcd_string++), DAT);
    };
}


void LCD_putchar(char char_data)
{
    LCD_send(char_data, DAT);
}


void LCD_clear_home(void)
{
    LCD_send(clear_display, CMD);
    LCD_send(goto_home, CMD);
}


void LCD_goto(unsigned char x_pos,unsigned char y_pos)
{
    if(y_pos == 0)
    {
        LCD_send((0x80 | x_pos), CMD);
    }
    else
    {
        LCD_send((0x80 | 0x40 | x_pos), CMD);
    }
}

 

main.c

 

#include <msp430.h>
#include "delay.h"
#include "I2C.h"
#include "PCF8574.h"
#include "lcd.h"


void GPIO_graceInit(void);
void BCSplus_graceInit(void);
void USI_graceInit(void);
void System_graceInit(void);
void WDTplus_graceInit(void);
void show_value(unsigned char value);


void main(void)
{
    unsigned char s = 0;

    char txt1[] = {"MICROARENA"};
    char txt2[] = {"SShahryiar"};
    char txt3[] = {"MSP-EXP430G2"};
    char txt4[] = {"Launchpad!"};

    /* Stop watchdog timer from timing out during initial start-up. */
    WDTCTL = WDTPW | WDTHOLD;

    /* initialize Config for the MSP430 GPIO */
    GPIO_graceInit();

    /* initialize Config for the MSP430 2xx family clock systems (BCS) */
    BCSplus_graceInit();

    /* initialize Config for the MSP430 USI */
    USI_graceInit();

    /* initialize Config for the MSP430 System Registers */
    System_graceInit();

    /* initialize Config for the MSP430 WDT+ */
    WDTplus_graceInit();

    LCD_init();
    LCD_clear_home();

    LCD_goto(3, 0);
    LCD_putstr(txt1);
    LCD_goto(3, 1);
    LCD_putstr(txt2);
    delay_ms(2600);

    LCD_clear_home();

    for(s = 0; s < 12; s++)
    {
        LCD_goto((2 + s), 0);
        LCD_putchar(txt3[s]);
        delay_ms(60);
    }
    for(s = 0; s < 10; s++)
    {
        LCD_goto((3 + s), 1);
        LCD_putchar(txt4[s]);
        delay_ms(60);
    }

    delay_ms(2600);

    s = 0;
    LCD_clear_home();

    LCD_goto(3, 0);
    LCD_putstr(txt1);

    while(1)
    {
        show_value(s);
        s++;
        delay_ms(200);
    };
}


void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

    /* Port 1 Output Register */
    P1OUT = 0;

    /* Port 1 Port Select Register */
    P1SEL = BIT6 | BIT7;

    /* Port 1 Direction Register */
    P1DIR = 0;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Direction Register */
    P2DIR = 0;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */
}


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

    if (CALBC1_1MHZ != 0xFF) {
        /* Follow recommended flow. First, clear all DCOx and MODx bits. Then
         * apply new RSELx values. Finally, apply new DCOx and MODx bit values.
         */
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_1MHZ;      /* Set DCO to 1MHz */
        DCOCTL = CALDCO_1MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_2 -- If XTS = 0, XT1 = VLOCLK ; If XTS = 1, XT1 = 3 - 16-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_2 | XCAP_1;

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}


void USI_graceInit(void)
{
    /* USER CODE START (section: USI_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: USI_graceInit_prologue) */

    /* Disable USI */
    USICTL0 |= USISWRST;

    /*
     * USI Control Register 0
     *
     * USIPE7 -- USI function enabled
     * USIPE6 -- USI function enabled
     * ~USIPE5 -- USI function disabled
     * ~USILSB -- MSB first
     * USIMST -- Master mode
     * ~USIGE -- Output latch enable depends on shift clock
     * ~USIOE -- Output disabled
     * USISWRST -- USI logic held in reset state
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    USICTL0 = USIPE7 | USIPE6 | USIMST | USISWRST;

    /*
     * USI Control Register 1
     *
     * ~USICKPH -- Data is changed on the first SCLK edge and captured on the following edge
     * USII2C -- I2C mode enabled
     * ~USISTTIE -- Interrupt on START condition disabled
     * USIIE -- Interrupt enabled
     * ~USIAL -- No arbitration lost condition
     * ~USISTP -- No STOP condition received
     * ~USISTTIFG -- No START condition received. No interrupt pending
     * USIIFG -- Interrupt pending
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    USICTL1 = USII2C | USIIE | USIIFG;

    /*
     * USI Clock Control Register
     *
     * USIDIV_4 -- Divide by 16
     * USISSEL_2 -- SMCLK
     * USICKPL -- Inactive state is high
     * ~USISWCLK -- Input clock is low
     *
     * Note: ~USISWCLK indicates that USISWCLK has value zero
     */
    USICKCTL = USIDIV_7 | USISSEL_2 | USICKPL;

    /*
     * USI Bit Counter Register
     *
     * ~USISCLREL -- SCL line is held low if USIIFG is set
     * ~USI16B -- 8-bit shift register mode. Low byte register USISRL is used
     * USIIFGCC -- USIIFG is not cleared automatically
     * ~USICNT4 -- USI bit count
     * ~USICNT3 -- USI bit count
     * ~USICNT2 -- USI bit count
     * ~USICNT1 -- USI bit count
     * ~USICNT0 -- USI bit count
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    USICNT = USIIFGCC;

    /* Enable USI */
    USICTL0 &= ~USISWRST;

    /* Clear pending flag */
    USICTL1 &= ~(USIIFG + USISTTIFG);

    /* USER CODE START (section: USI_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: USI_graceInit_epilogue) */
}


void System_graceInit(void)
{
    /* USER CODE START (section: System_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: System_graceInit_prologue) */

    /* Clear oscillator fault flag with software delay */
    do
    {
        // Clear OSC fault flag
        IFG1 &= ~OFIFG;

        // 50us delay
        __delay_cycles(50);
    } while (IFG1 & OFIFG);

    /*
     * SR, Status Register
     *
     * ~SCG1 -- Disable System clock generator 1
     * ~SCG0 -- Disable System clock generator 0
     * ~OSCOFF -- Oscillator On
     * ~CPUOFF -- CPU On
     * GIE -- General interrupt enable
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    __bis_SR_register(GIE);

    /* USER CODE START (section: System_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: System_graceInit_epilogue) */
}


void WDTplus_graceInit(void)
{
    /* USER CODE START (section: RTC_B_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: RTC_B_graceInit_prologue) */

    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * WDTHOLD -- Watchdog timer+ is stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * ~WDTTMSEL -- Watchdog mode
     * ~WDTCNTCL -- No action
     * ~WDTSSEL -- SMCLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * ~WDTIS1 -- Watchdog clock source bit1 disabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTHOLD;

    /* USER CODE START (section: RTC_B_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: RTC_B_graceInit_epilogue) */
}


void show_value(unsigned char value)
{
   unsigned char ch = 0x00;

   ch = ((value / 100) + 0x30);
   LCD_goto(6, 1);
   LCD_putchar(ch);

   ch = (((value / 10) % 10) + 0x30);
   LCD_goto(7, 1);
   LCD_putchar(ch);

   ch = ((value % 10) + 0x30);
   LCD_goto(8, 1);
   LCD_putchar(ch);
}

 

Simulation

I2C LCD Sim

*Note Proteus VSM could not simulate I2C-based simulations. The simulation here only shows connections.

 

Explanation

SW_I2C_LCD2 SW_I2C_LCD1

Demo video: https://www.youtube.com/watch?v=y6HQATVucNk.

UART – USCI

Serial communication is perhaps the most widely used communication method for interfacing a PC or other machines with a micro and over long distances. With just two cross-connecting wires, we can achieve a full-duplex point-to-point communication. Owing to its simplicity, range and wide usage, it is the communication interface backbone that is used for GSM modems, RF modules, Zigbee devices like CC2530, BLE devices like CC2540, Wi-Fi devices like CC3200, etc. Other forms of advance serial communications have their lineages tracing back to it, for example, RS-485, LIN, IrDA, etc.

UART Comms

Shown below is a demo of HMC1022-based LED compass. To communicate with this chip to get compass heading, we need to send-receive data via UART.

HMC1022

To learn more about UART visit the following link:

https://learn.mikroe.com/uart-serial-communication/

Code Example

 

#include <msp430.h>
#include "delay.h"
#include "lcd.h"


unsigned char rx = 0;


void GPIO_graceInit(void);
void BCSplus_graceInit(void);
void USCI_A0_graceInit(void);
void System_graceInit(void);
void WDTplus_graceInit(void);
void UART_putc(char ch);
void UART_puts(char *str);


#pragma vector=USCIAB0RX_VECTOR
__interrupt void USCI0RX_ISR_HOOK(void)
{
    rx = UCA0RXBUF;      
}


void main(void)
{
    unsigned char tx = 0x20;

    /* Stop watchdog timer from timing out during initial start-up. */
    WDTCTL = WDTPW | WDTHOLD;

    /* initialize Config for the MSP430 GPIO */
    GPIO_graceInit();

    /* initialize Config for the MSP430 2xx family clock systems (BCS) */
    BCSplus_graceInit();

    /* initialize Config for the MSP430 USCI_A0 */
    USCI_A0_graceInit();

    /* initialize Config for the MSP430 System Registers */
    System_graceInit();

    /* initialize Config for the MSP430 WDT+ */
    WDTplus_graceInit();

    UART_puts("\f");
    UART_puts("MSP430G2553 UART Demo\n");
    UART_puts("Shawon Shahryiar\n");
    UART_puts("https://www.facebook.com/MicroArena\n");

    LCD_init();

    LCD_goto(0, 0);
    LCD_putstr("TXD:");
    LCD_goto(0, 1);
    LCD_putstr("RXD:");

    while(1)
    {
        LCD_goto(15, 0);
        LCD_putchar(tx);
        UART_putc(tx);
        tx++;

        if(tx > 0x7F)
        {
            tx = 0x20;
        }

        LCD_goto(15, 1);
        LCD_putchar(rx);

        delay_ms(400);
    };
}


void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

    /* Port 1 Port Select 2 Register */
    P1SEL2 = BIT1 | BIT2;

    /* Port 1 Output Register */
    P1OUT = 0;

    /* Port 1 Port Select Register */
    P1SEL = BIT1 | BIT2;

    /* Port 1 Direction Register */
    P1DIR = 0;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Direction Register */
    P2DIR = BIT0 | BIT1 | BIT2;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* Port 3 Output Register */
    P3OUT = 0;

    /* Port 3 Direction Register */
    P3DIR = 0;

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */
}


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

    if (CALBC1_1MHZ != 0xFF) {
        /* Follow recommended flow. First, clear all DCOx and MODx bits. Then
         * apply new RSELx values. Finally, apply new DCOx and MODx bit values.
         */
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_1MHZ;      /* Set DCO to 1MHz */
        DCOCTL = CALDCO_1MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_0 -- If XTS = 0, XT1 = 32768kHz Crystal ; If XTS = 1, XT1 = 0.4 - 1-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_0 | XCAP_1;

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}


void USCI_A0_graceInit(void)
{
    /* USER CODE START (section: USCI_A0_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: USCI_A0_graceInit_prologue) */

    /* Disable USCI */
    UCA0CTL1 |= UCSWRST;

    /*
     * Control Register 0
     *
     * ~UCPEN -- Parity Disabled
     * UCPAR -- Even parity
     * ~UCMSB -- LSB first
     * ~UC7BIT -- 8-bit
     * ~UCSPB -- One stop bit
     * UCMODE_0 -- UART Mode
     * ~UCSYNC -- Asynchronous mode
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    UCA0CTL0 = UCPAR | UCMODE_0;

    /*
     * Control Register 1
     *
     * UCSSEL_2 -- SMCLK
     * ~UCRXEIE -- Erroneous characters rejected and UCAxRXIFG is not set
     * ~UCBRKIE -- Received break characters do not set UCAxRXIFG
     * ~UCDORM -- Not dormant. All received characters will set UCAxRXIFG
     * ~UCTXADDR -- Next frame transmitted is data
     * ~UCTXBRK -- Next frame transmitted is not a break
     * UCSWRST -- Enabled. USCI logic held in reset state
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    UCA0CTL1 = UCSSEL_2 | UCSWRST;

    /*
     * Modulation Control Register
     *
     * UCBRF_0 -- First stage 0
     * UCBRS_1 -- Second stage 1
     * ~UCOS16 -- Disabled
     *
     * Note: ~UCOS16 indicates that UCOS16 has value zero
     */
    UCA0MCTL = UCBRF_0 | UCBRS_1;

    /* Baud rate control register 0 */
    UCA0BR0 = 104;

    /* Enable USCI */
    UCA0CTL1 &= ~UCSWRST;

    /* USER CODE START (section: USCI_A0_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: USCI_A0_graceInit_epilogue) */
}


void System_graceInit(void)
{
    /* USER CODE START (section: System_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: System_graceInit_prologue) */

    /*
     * IFG2, Interrupt Flag Register 2
     *
     * ~UCB0TXIFG -- No interrupt pending
     * ~UCB0RXIFG -- No interrupt pending
     * ~UCA0TXIFG -- No interrupt pending
     * UCA0RXIFG -- Interrupt pending
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    IFG2 &= ~(UCA0RXIFG);

    /*
     * IE2, Interrupt Enable Register 2
     *
     * ~UCB0TXIE -- Interrupt disabled
     * ~UCB0RXIE -- Interrupt disabled
     * ~UCA0TXIE -- Interrupt disabled
     * UCA0RXIE -- Interrupt enabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    IE2 |= UCA0RXIE;

    /*
     * SR, Status Register
     *
     * ~SCG1 -- Disable System clock generator 1
     * ~SCG0 -- Disable System clock generator 0
     * ~OSCOFF -- Oscillator On
     * ~CPUOFF -- CPU On
     * GIE -- General interrupt enable
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    __bis_SR_register(GIE);

    /* USER CODE START (section: System_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: System_graceInit_epilogue) */
}


void WDTplus_graceInit(void)
{
    /* USER CODE START (section: RTC_B_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: RTC_B_graceInit_prologue) */

    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * WDTHOLD -- Watchdog timer+ is stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * ~WDTTMSEL -- Watchdog mode
     * ~WDTCNTCL -- No action
     * ~WDTSSEL -- SMCLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * ~WDTIS1 -- Watchdog clock source bit1 disabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTHOLD;

    /* USER CODE START (section: RTC_B_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: RTC_B_graceInit_epilogue) */
}


void UART_putc(char ch)
{
    while(!(IFG2 & UCA0TXIFG));
    UCA0TXBUF = ch;
}


void UART_puts(char *str)
{
    while(*str != 0)
    {
        while(!(IFG2 & UCA0TXIFG));
        UCA0TXBUF = *str++;
    };
}

 

Simulation

UART_Sim

Explanation

MSP430G2553 is used for this demo since it has UART-supporting USCI module. An important thing to note before trying to use the UART is the jumper settings shown below:

jumpers

The photo shows that the TX-RX jumpers are connected in way perpendicular to other jumpers. However, by default, jumpers are connected the other way around, i.e. software UART.  If the jumpers are setup as shown, we can access the on-board USB-UART converter, i.e. hardware UART. We will need this piece of hardware for communicating with a computer.

Using GRACE, we setup the USCI module in asynchronous UART mode with typical settings like 9600 baudrate and 8-bit data transfer mode. GRACE also takes care of baud rate generation calculation. UART data reception interrupt is also used to quickly respond to incoming characters.

USCI-UART

    /* Disable USCI */
    UCA0CTL1 |= UCSWRST;

    /*
     * Control Register 0
     *
     * ~UCPEN -- Parity Disabled
     * UCPAR -- Even parity
     * ~UCMSB -- LSB first
     * ~UC7BIT -- 8-bit
     * ~UCSPB -- One stop bit
     * UCMODE_0 -- UART Mode
     * ~UCSYNC -- Asynchronous mode
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    UCA0CTL0 = UCPAR | UCMODE_0;

    /*
     * Control Register 1
     *
     * UCSSEL_2 -- SMCLK
     * ~UCRXEIE -- Erroneous characters rejected and UCAxRXIFG is not set
     * ~UCBRKIE -- Received break characters do not set UCAxRXIFG
     * ~UCDORM -- Not dormant. All received characters will set UCAxRXIFG
     * ~UCTXADDR -- Next frame transmitted is data
     * ~UCTXBRK -- Next frame transmitted is not a break
     * UCSWRST -- Enabled. USCI logic held in reset state
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    UCA0CTL1 = UCSSEL_2 | UCSWRST;

    /*
     * Modulation Control Register
     *
     * UCBRF_0 -- First stage 0
     * UCBRS_1 -- Second stage 1
     * ~UCOS16 -- Disabled
     *
     * Note: ~UCOS16 indicates that UCOS16 has value zero
     */
    UCA0MCTL = UCBRF_0 | UCBRS_1;

    /* Baud rate control register 0 */
    UCA0BR0 = 104;

    /* Enable USCI */
    UCA0CTL1 &= ~UCSWRST;

Right after initialization of all required hardware, the UART starts sending some strings.

    UART_puts("\f");
    UART_puts("MSP430G2553 UART Demo\n");
    UART_puts("Shawon Shahryiar\n");
    UART_puts("https://www.facebook.com/MicroArena\n");

The following functions transmit data via UART. The first one can transmit one character at a time while the second can transmit a string of characters. In both cases, it is checked if the last character has been successfully sent before sending a new character.

void UART_putc(char ch)
{
    while(!(IFG2 & UCA0TXIFG));
    UCA0TXBUF = ch;
}

void UART_puts(char *str)
{
    while(*str != 0)
    {
        while(!(IFG2 & UCA0TXIFG));
        UCA0TXBUF = *str++;
    };
}

Since data recption interrupt is used, data received is extracted from UART reception ISR.

#pragma vector=USCIAB0RX_VECTOR
__interrupt void USCI0RX_ISR_HOOK(void)
{
    rx = UCA0RXBUF;      
}

Whenever a new character is received by the UART, RX interrupt fires up and catches the sent character. The received character is shown on a LCD. In the main loop, the code sends out ASCII characters and the LCD shows what has been sent out.

CCS IDE comes with a built-in serial terminal interface. You can that or you can use third-party software like CoolTerm, Putty, Termite, Docklight, RealTerm, Tera Term, etc to check or monitor serial communication transactions.

Demo

UART

Demo video: https://www.youtube.com/watch?v=Mm2i4rVGyoo.

Ending

A microcontroller like the ones in MSP430G2xxx series may look tiny and less resourceful but it is impossible to show everything in full details. This writeup is just a summary. Even at this stage after explaining and demoing multiple basic hardware of MSP430s, I have yet to cover more stuffs. For example, I skipped capacitive touch sensing, some details of ADC, low power modes, driver libraries and some other minor stuffs that are less important in the beginning. Hopefully these will be covered in future posts. As of now I’m planning for a sequel of this write up.

As an advice, it is imperative that readers study app notes, reference manuals and TI wiki pages to improve proficiency and in depth knowledge. There are some good books on MSP430s too. Books like MSP430 Microcontroller Basics by John Davies and Analog and Digital Circuits for Electronic Control System Applications Using the TI MSP430 Microcontroller by Jerry Luecke discuss MSP430 microcontrollers and their applications in really great details. For now, I ask readers to practice and experiment what I have shown so far. Try stuffs without using GRACE assist, try out different combinations and try out different MSP430 micros.

PDF version of this blog post can be downloaded from here.

Code examples and Grace output files can be found here.

Happy coding.

 

Author: Shawon M. Shahryiar

https://www.facebook.com/MicroArena                                                                          25.08.2017

 

 

** Some images have been taken from the documents and webpages of Texas Instruments (TI) and Mikroelektronika.

The post Introducing TI MSP430 Microcontrollers appeared first on Embedded Lab.

Printed Circuit Boards – Things You Must Know as a Beginner

$
0
0

A systematic arrangement of electrical components defined by pathways, signal traces, and conductive paths is called a Printed Circuit Board. If as a beginner you question, ‘what’s a PCB?’, then for simpler understanding, you can look at a PCB this way. Functionally speaking, the PCB offers mechanical support to electrical connections arranged in a logical manner with the help of pads, tracks, and other features. You can find the best PCBs at a cheap PCB manufacturer such as Elecrow.

Types of PCB

PCBs can be categorized as follows:

  • Single Sided PCBs
  • Multilayer PCBs
  • Double Sided PCBs
  • Flex PCBs
  • Rigid PCBs
  • Rigid-Flex PCBs

Single Sided PCBs

This single sided printed circuit board includes a single layer of the substrate. Either side of the substrate is coated with a good conducting material, such as copper. It is made by applying a protecting solder cover on the top layer of the copper substrate. Additionally, a silkscreen coating is done to define the mark elements of the board.

Single sided PCBs

This is the most easily made PCB and as a beginner, you may be given a task to learn this first before trying hands on more complex types of PCBs. All components and circuits are demarcated on the single side, providing a suitable ground for easy electronics. These can be produced in large numbers but do not find much utility due to limited design options available in these.

Double Sided PCBs (2 layer PCB)

In these PCBs, both sides of the substrate are used for placing elements. The placement of elements is done with the help of surface mount or through hole technology. Its structure is quite similar to single sided PCBs w the only difference being that both sides of the substrate are employed.

Multi-layer PCBs

These PCBs are quite useful for creating complex and thick designs. The layers are added both to the top as well as bottom in the configuration of double-sided. The additional layers act as power planes that do the dual function of providing supply to the circuit and of decreasing the electromagnetic interference. The EMI levels can be lowered further by using the middle portion of these power planes.

Rigid PCBs

PCBs are also available in varying rigidities. In addition to adding layers, PCBs are also designed keeping flexibilities in mind. A motherboard inside a computer tower is the most common example of a rigid PCB. The rigid PCB  is characterized by a substrate made of fiberglass that keeps the board from bending or twisting.

Rigid PCBs

Apart from these, Flex PCBs and rigid-flex PCBs from Elecrow Premium PCB service are also manufactured in order to meet the requirements of different kinds of environments. For example, flexible PCBs are used in environments such as satellite gears, which are exposed to a lot of twisting and shifting.

Principle of PCB Layout

1. PCB layout guidelines suggest that the structure of this board should meet the following requirements: Traces carrying currents should be amply thick;

  • Traces carrying currents should be amply thick;
  • The board should support low impedance;
  • Signals of sensitive type should be able to stop interference caused by noisy traces.

Thus, PCB Layout Guidelines entail the use of plane shapes wherever possible. These shapes support high current input supplies such as DCIN, VBAT, and VBUS. Plane shapes deliver the benefit of cutting on power losses. The continuous flow of current between the layers is to be ensured and this is provided by the plane shapes. To achieve this, the plane is not to be interrupted or shortened too much by vias.

Another guideline is to maximize the productivity of decoupling capacitors. This is done by minimizing the inductance between the capacitor and the power pin of any device. To achieve this, the capacitor is placed closest possible to the device. Traces should be short and thick, and the vias should not have inductive element if you want to enhance the effectiveness of decoupling capacitors. Similarly, the capacitor and ground should be connected in low impedance manner so that the impedance of the former can be minimized. To further reduce the ground impedance, the unused areas of PCB should be flooded with the ground.

2. The grounding of PCBs is another factor that constitutes the guidelines for ensuring better efficiency of the circuit board. There are two types of layouts prevalent – star grounding and ground looping. Of these, star grounding performs better and delivers minimum impedance.

Ground loopings cause the disturbed flow of current across different layers. These loopings need careful identification especially in multi-layer boards for avoiding duplication and fault.

3. Placement of external components: The external components like reference-setting components should be placed near to each other. It can also be achieved by switching inductors and capacitors with their drive-pin components. The layout of sensitive parts and high current devices should be given topmost preference, in order to ensure better system performance.

Guidelines are also laid for the placement of boards in correct proximity to the power source, ADC, etc.

How do circuit boards work

This is how circuit boards work. The electronic components are connected electrically using pads, conductive tracks and other features peeled from the copper sheets. The circuit boards also provide mechanical support to these components. All basic components such as resistors, capacitors and other active devices are fixed on the PCB by the way of soldering.

Circuit boards are, therefore, simple in design, easy to work and can be made from basic materials such as copper boards and basic kinds of electronic components. These form the core of internal networks present inside computers, or other electronic devices.

The post Printed Circuit Boards – Things You Must Know as a Beginner appeared first on Embedded Lab.

ESP32 BLE/WiFi development board

$
0
0

We have added a new IoT product to our Tindie store. It is a rapid prototyping and development board for the powerful ESP32 WiFi/BLE module. It is targeted toward rapid development of a wide variety of IoT applications, supporting both WiFi and Bluetooth connectivities. The development board breaks out all the module’s pins to 0.1″ headers and provides a CP2102 USB-TTL serial adapter on board for easy programming. The board also features on-board power supply regulator and an integrated LiPo Battery Charger.

ESP32 development board

ESP32 development board

ESP-WROOM-32 is a powerful, generic Wi-Fi+BT+BLE MCU module that targets a wide variety of applications, ranging from low-power sensor networks to the most demanding tasks, such as voice encoding, music streaming and MP3 decoding. The development board breaks out all the module’s pins to 0.1″ headers and provides a CP2102 USB-TTL serial adapter, programming and reset buttons, and a power regulator to supply the ESP32 with a stable 3.3 V. Espressif doubled-down on the CPU resources for the ESP32 with a dual core, running at 160MHz and tons more pins and peripherals. The integration of Bluetooth, Bluetooth LE and Wi-Fi ensures that a wide range of applications can be targeted, and that the module is future proof: using Wi-Fi allows a large physical range and direct connection to the internet through a Wi-Fi router, while using Bluetooth allows the user to conveniently connect to the phone or broadcast low energy beacons for its detection. The sleep current of the ESP32 chip is less than 5 µA, making it suitable for battery powered and wearable electronics applications. ESP-WROOM-32 supports data rates of up to 150 Mbps, and 22 dBm output power at the PA to ensure the widest physical range. As such the chip does offer industry-leading specifications and the best performance for electronic integration, range, power consumption, and connectivity.

Download the datasheet

But it today from our Tindie Store.

The post ESP32 BLE/WiFi development board appeared first on Embedded Lab.


A DIY vending machine illustration using Arduino

$
0
0

Dejan Nedelkovski from HowToMechatronics illustrates a DIY vending machine using Arduino platform. The vending machine features four discharging units controlled via four continuous rotation servo motors, a carrier system controlled via stepper motors, an LCD, four buttons for selecting an item and a coin detector.

DIY vending machine using Arduino

DIY vending machine using Arduino

You can watch his build process in the following video.

The post A DIY vending machine illustration using Arduino appeared first on Embedded Lab.

ESP32 weather station

$
0
0

Another ESP32 based weather station that retrieves weather data from OpenWeatherMap via WiFi and display the infor on a 3.2″ Nextion Touch Display. It also uses the BME280 sensor for measuring weather data locally. The display is refreshed every two seconds and the weather forecast is made every hour.

Weather station

Weather station

The post ESP32 weather station appeared first on Embedded Lab.

Continuing the STM8 Expedition

$
0
0

This post is the continuation of the first post on STM8 microcontrollers here.

STM8S105 Discovery

ADC Interrupt

Instead of polling for ADC’s end of conversion (EOC) state, it is wise to use ADC interrupt. Just as with any hardware peripheral, interrupt methods make a system highly responsive. Interrupts like these free up the CPU for other tasks. However, it is up to the coder to determine interrupt priorities and look out for situation that may cause too many interrupts to be processed in a short while.

Hardware Connection

adc_isr

Code Example

 

stm8s_it.h (top part only)

#ifndef __STM8S_IT_H
#define __STM8S_IT_H

@far @interrupt void ADC_IRQHandler(void);

/* Includes ------------------------------------------------------------------*/
#include "stm8s.h"
....

 

stm8s_it.c (top part only)

#include "stm8s.h"
#include "stm8s_it.h"


extern unsigned int adc_value;


void ADC_IRQHandler(void)
{
       adc_value = ADC1_GetConversionValue();
       ADC1_ClearFlag(ADC1_FLAG_EOC);
}
....

 

stm8_interrupt_vector.c (shortened)

#include "stm8s_it.h"


typedef void @far (*interrupt_handler_t)(void);

struct interrupt_vector {
       unsigned char interrupt_instruction;
       interrupt_handler_t interrupt_handler;
};

//@far @interrupt void NonHandledInterrupt (void)
//{
       /* in order to detect unexpected events during development,
          it is recommended to set a breakpoint on the following instruction
       */
//     return;
//}

extern void _stext();     /* startup routine */


struct interrupt_vector const _vectab[] = {
       {0x82, (interrupt_handler_t)_stext}, /* reset */
       {0x82, NonHandledInterrupt}, /* trap  */
       {0x82, NonHandledInterrupt}, /* irq0  */
       ....
       {0x82, (interrupt_handler_t)ADC_IRQHandler}, /* irq22 */
       ....
       {0x82, NonHandledInterrupt}, /* irq29 */
};

 

main.c

#include "STM8S.h"
#include "lcd.h"


unsigned int adc_value;


unsigned char bl_state;
unsigned char data_value;


void clock_setup(void);
void GPIO_setup(void);
void ADC1_setup(void);
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value);


void main(void)
{
       float mv = 0x00000000;

       clock_setup();
       GPIO_setup();
       ADC1_setup();

       LCD_init(); 
       LCD_clear_home();

       LCD_goto(2, 0);
       LCD_putstr("STM8 ADC ISR");
       LCD_goto(0, 1);
       LCD_putstr("A0/mV");

       while(TRUE)
       {
              ADC1_StartConversion();

              mv = (adc_value * 5000.0);
              mv /= 1023.0;

              lcd_print(7, 1, mv);            
              lcd_print(12, 1, adc_value);
              GPIO_WriteReverse(GPIOD, GPIO_PIN_0);
              delay_ms(160);
       };
}


void clock_setup(void)
{
       CLK_DeInit();

       CLK_HSECmd(DISABLE);
       CLK_LSICmd(DISABLE);
       CLK_HSICmd(ENABLE);
       while(CLK_GetFlagStatus(CLK_FLAG_HSIRDY) == FALSE);

       CLK_ClockSwitchCmd(ENABLE);
       CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV4);
       CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1);

       CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSI,
       DISABLE, CLK_CURRENTCLOCKSTATE_ENABLE);

       CLK_PeripheralClockConfig(CLK_PERIPHERAL_I2C, ENABLE);
       CLK_PeripheralClockConfig(CLK_PERIPHERAL_ADC, ENABLE);
       CLK_PeripheralClockConfig(CLK_PERIPHERAL_SPI, DISABLE);
       CLK_PeripheralClockConfig(CLK_PERIPHERAL_AWU, DISABLE);
       CLK_PeripheralClockConfig(CLK_PERIPHERAL_UART1, DISABLE);
       CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER1, DISABLE);
       CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER2, DISABLE);
       CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER4, DISABLE);
}


void GPIO_setup(void)
{
       GPIO_DeInit(GPIOB);
       GPIO_Init(GPIOB, ((GPIO_Pin_TypeDef)(GPIO_PIN_0 | GPIO_PIN_1)),      GPIO_MODE_IN_FL_NO_IT);

       GPIO_DeInit(GPIOD);
       GPIO_Init(GPIOD, GPIO_PIN_0, GPIO_MODE_OUT_OD_HIZ_FAST);
}


void ADC1_setup(void)
{
       ADC1_DeInit();     

       ADC1_Init(ADC1_CONVERSIONMODE_CONTINUOUS,
                 ADC1_CHANNEL_0,
                 ADC1_PRESSEL_FCPU_D18,
                 ADC1_EXTTRIG_GPIO,
                 DISABLE,
                 ADC1_ALIGN_RIGHT,
                 ADC1_SCHMITTTRIG_CHANNEL0,
                 DISABLE);

       ADC1_ITConfig(ADC1_IT_EOCIE, ENABLE);
       enableInterrupts();
       ADC1_Cmd(ENABLE);
}


void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value)
{
       char chr = 0x00;

       chr = ((value / 1000) + 0x30);  
       LCD_goto(x_pos, y_pos);
       LCD_putchar(chr);

       chr = (((value / 100) % 10) + 0x30);
       LCD_goto((x_pos + 1), y_pos);
       LCD_putchar(chr);

       chr = (((value / 10) % 10) + 0x30);
       LCD_goto((x_pos + 2), y_pos);
       LCD_putchar(chr);

       chr = ((value % 10) + 0x30);
       LCD_goto((x_pos + 3), y_pos);
       LCD_putchar(chr);
}

 

Explanation

This example and the first ADC example is all same except for the interrupt part. Note the last three lines below.

void ADC1_setup(void)
{
       ADC1_DeInit();     

       ADC1_Init(ADC1_CONVERSIONMODE_CONTINUOUS,
                 ADC1_CHANNEL_0,
                 ADC1_PRESSEL_FCPU_D18,
                 ADC1_EXTTRIG_GPIO,
                 DISABLE,
                 ADC1_ALIGN_RIGHT,
                 ADC1_SCHMITTTRIG_CHANNEL0,
                 DISABLE);

       ADC1_ITConfig(ADC1_IT_EOCIE, ENABLE);
       enableInterrupts();
       ADC1_Cmd(ENABLE);
}

As can be seen, End-of-Conversion (EOC) interrupt has been enabled along with global interrupt and the ADC itself.

In the vector mapping file, ADC interrupt is set.

{0x82, (interrupt_handler_t)ADC_IRQHandler}, /* irq22 */

When ADC EOC interrupt triggers, the ADC data buffer is read and the EOC flag is cleared.

void ADC_IRQHandler(void)
{
       adc_value = ADC1_GetConversionValue();
       ADC1_ClearFlag(ADC1_FLAG_EOC);
}

 

Demo

ADC ISR (2) ADC ISR (1)

The post Continuing the STM8 Expedition appeared first on Embedded Lab.

STM8 Microcontrollers – the Final Chapters

$
0
0

This post is the sequel of the posts on STM8 microcontrollers here and here.

Hardware

Software I2C – DS1307

Software-based I2C is not a big requirement in case of STM8s because STM8 chips have hardware I2C blocks. However, for some reason if we are unable to use hardware I2C blocks, we can implement software-based I2C by bit-banging ordinary GPIOs. I2C protocol is itself slow compared to SPI and other protocols and so implementing software-based I2C will not significantly affect communication speed and overall throughput in most applications. However, extra coding is required and this in turn adds some coding and resource overheads.

Hardware Connection

sw_i2c

Code Example

SW_I2C.h

#include "STM8S.h"


#define SW_I2C_port              GPIOD

#define SDA_pin                  GPIO_PIN_6
#define SCL_pin                  GPIO_PIN_7

#define SW_I2C_OUT()             do{GPIO_DeInit(SW_I2C_port); GPIO_Init(SW_I2C_port, SDA_pin, GPIO_MODE_OUT_PP_LOW_FAST); GPIO_Init(SW_I2C_port, SCL_pin, GPIO_MODE_OUT_PP_LOW_FAST);}while(0)

#define SW_I2C_IN()              do{GPIO_DeInit(SW_I2C_port); GPIO_Init(SW_I2C_port, SDA_pin, GPIO_MODE_IN_FL_NO_IT); GPIO_Init(SW_I2C_port, SCL_pin, GPIO_MODE_OUT_PP_LOW_FAST);}while(0)

#define SDA_HIGH()               GPIO_WriteHigh(SW_I2C_port, SDA_pin)
#define SDA_LOW()                GPIO_WriteLow(SW_I2C_port, SDA_pin)
#define SCL_HIGH()               GPIO_WriteHigh(SW_I2C_port, SCL_pin)
#define SCL_LOW()                GPIO_WriteLow(SW_I2C_port, SCL_pin)

#define SDA_IN()                 GPIO_ReadInputPin(SW_I2C_port, SDA_pin)

#define I2C_ACK                  0xFF
#define I2C_NACK                 0x00

#define I2C_timeout              1000


void SW_I2C_init(void);
void SW_I2C_start(void);
void SW_I2C_stop(void);
unsigned char SW_I2C_read(unsigned char ack);
void SW_I2C_write(unsigned char value);
void SW_I2C_ACK_NACK(unsigned char mode);
unsigned char SW_I2C_wait_ACK(void);

 

SW_I2C.c

#include "SW_I2C.h"


void SW_I2C_init(void)
{
    SW_I2C_OUT();
    delay_ms(10);
    SDA_HIGH();
    SCL_HIGH();
}


void SW_I2C_start(void)
{
    SW_I2C_OUT();
    SDA_HIGH();
    SCL_HIGH();
    delay_us(40);
    SDA_LOW();
    delay_us(40);
    SCL_LOW();
}


void SW_I2C_stop(void)
{
    SW_I2C_OUT();
    SDA_LOW();
    SCL_LOW();
    delay_us(40);
    SDA_HIGH();
    SCL_HIGH();
    delay_us(40);
}


unsigned char SW_I2C_read(unsigned char ack)
{
    unsigned char i = 0x08;
    unsigned char j = 0x00;

    SW_I2C_IN();

    while(i > 0x00)
    {
        SCL_LOW();
        delay_us(20);
        SCL_HIGH();
        delay_us(20);
        j <<= 1;

        if(SDA_IN() != 0x00)
        {
            j++;
        }

        delay_us(10);
        i--;
    };

    switch(ack)
    {
        case I2C_ACK:
        {
            SW_I2C_ACK_NACK(I2C_ACK);;
            break;
        }
        default:
        {
            SW_I2C_ACK_NACK(I2C_NACK);;
            break;
        }
    }

    return j;
}


void SW_I2C_write(unsigned char value)
{
    unsigned char i = 0x08;

    SW_I2C_OUT();
    SCL_LOW();

    while(i > 0x00)
    {

        if(((value & 0x80) >> 0x07) != 0x00)
        {
            SDA_HIGH();
        }
        else
        {
            SDA_LOW();
        }


        value <<= 1;
        delay_us(20);
        SCL_HIGH();
        delay_us(20);
        SCL_LOW();
        delay_us(20);
        i--;
    };
}


void SW_I2C_ACK_NACK(unsigned char mode)
{
    SCL_LOW();
    SW_I2C_OUT();

    switch(mode)
    {
        case I2C_ACK:
        {
            SDA_LOW();
            break;
        }
        default:
        {
            SDA_HIGH();
            break;
        }
    }

    delay_us(20);
    SCL_HIGH();
    delay_us(20);
    SCL_LOW();
}


unsigned char SW_I2C_wait_ACK(void)
{
    signed int timeout = 0x0000;

    SW_I2C_IN();

    SDA_HIGH();
    delay_us(10);
    SCL_HIGH();
    delay_us(10);

    while(SDA_IN() != 0x00)
    {
        timeout++;

        if(timeout > I2C_timeout)
        {
            SW_I2C_stop();
            return 1;
        }
    };

    SCL_LOW();

    return 0x00;
}

 

DS1307.h

#include "SW_I2C.h"


#define sec_reg                0x00
#define min_reg                0x01
#define hr_reg                 0x02
#define day_reg                0x03
#define date_reg               0x04
#define month_reg              0x05
#define year_reg               0x06
#define control_reg            0x07

#define DS1307_addr            0xD0
#define DS1307_WR              DS1307_addr
#define DS1307_RD              0xD1


void DS1307_init(void);
unsigned char DS1307_read(unsigned char address);
void DS1307_write(unsigned char address, unsigned char value);
unsigned char bcd_to_decimal(unsigned char value);
unsigned char decimal_to_bcd(unsigned char value);
void get_time(void);
void set_time(void);

 

DS1307.c

#include "DS1307.h"


extern struct
{
   unsigned char s;
   unsigned char m;
   unsigned char h;
   unsigned char dy;
   unsigned char dt;
   unsigned char mt;
   unsigned char yr;
}time;


void DS1307_init(void)
{
    SW_I2C_init();
    DS1307_write(control_reg, 0x00);
}


unsigned char DS1307_read(unsigned char address)
{
    unsigned char value = 0x00;

    SW_I2C_start();
    SW_I2C_write(DS1307_WR);
    SW_I2C_write(address);

    SW_I2C_start();
    SW_I2C_write(DS1307_RD);
    value = SW_I2C_read(I2C_NACK);
    SW_I2C_stop();

    return value;
}


void DS1307_write(unsigned char address, unsigned char value)
{
    SW_I2C_start();
    SW_I2C_write(DS1307_WR);
    SW_I2C_write(address);
    SW_I2C_write(value);
    SW_I2C_stop();
}


unsigned char bcd_to_decimal(unsigned char value)
{
    return ((value & 0x0F) + (((value & 0xF0) >> 0x04) * 0x0A));
}


unsigned char decimal_to_bcd(unsigned char value)
{
    return (((value / 0x0A) << 0x04) & 0xF0) | ((value % 0x0A) & 0x0F);
}


void get_time(void)
{
       time.s = DS1307_read(sec_reg);
       time.s = bcd_to_decimal(time.s);

       time.m = DS1307_read(min_reg);
       time.m = bcd_to_decimal(time.m);

       time.h = DS1307_read(hr_reg);
       time.h = bcd_to_decimal(time.h);

       time.dy = DS1307_read(day_reg);
       time.dy = bcd_to_decimal(time.dy);

       time.dt = DS1307_read(date_reg);
       time.dt = bcd_to_decimal(time.dt);

       time.mt = DS1307_read(month_reg);
       time.mt = bcd_to_decimal(time.mt);

       time.yr = DS1307_read(year_reg);
       time.yr = bcd_to_decimal(time.yr);
}


void set_time(void)
{
       time.s = decimal_to_bcd(time.s);
       DS1307_write(sec_reg, time.s);

       time.m = decimal_to_bcd(time.m);
       DS1307_write(min_reg, time.m);

       time.h = decimal_to_bcd(time.h);
       DS1307_write(hr_reg, time.h);

       time.dy = decimal_to_bcd(time.dy);
       DS1307_write(day_reg, time.dy);

       time.dt = decimal_to_bcd(time.dt);
       DS1307_write(date_reg, time.dt);

       time.mt = decimal_to_bcd(time.mt);
       DS1307_write(month_reg, time.mt);

       time.yr = decimal_to_bcd(time.yr);
       DS1307_write(year_reg, time.yr);
}

 

main.c

#include "STM8S.h"
#include "lcd.h"
#include "SW_I2C.h"
#include "DS1307.h"


#define Button_port      GPIOB
#define Button_pin         GPIO_PIN_7


struct
{
   unsigned char s;
   unsigned char m;
   unsigned char h;
   unsigned char dy;
   unsigned char dt;
   unsigned char mt;
   unsigned char yr;
}time;

bool set = FALSE;

unsigned char menu = 0;
unsigned char data_value;


void clock_setup(void);
void GPIO_setup(void);
void show_value(unsigned char x_pos, unsigned char y_pos, unsigned char value);
void display_time(void);
void setup_time(void);
unsigned char adjust(unsigned char x_pos, unsigned char y_pos, signed char value_max, signed char value_min, signed char value);


void main(void)
{
       clock_setup();
       GPIO_setup();

       DS1307_init();

       LCD_init();
       LCD_clear_home();

       LCD_goto(0, 0);
       LCD_putstr("STM8 SW-I2C Test");

    while(1)
    {
       if((GPIO_ReadInputPin(Button_port, Button_pin) == FALSE) && (set == FALSE))
       {
              delay_ms(1000);
              if(GPIO_ReadInputPin(Button_port, Button_pin) == FALSE)
              {
                     while(GPIO_ReadInputPin(Button_port, Button_pin) == FALSE);
                     delay_ms(1000);

                     menu = 0;
                     set = TRUE;
              }
       }

       if(set)
       {
              setup_time();
       }
       else
       {
              get_time();
              display_time();
       }
    };
}


void clock_setup(void)
{
       CLK_DeInit();

       CLK_HSECmd(DISABLE);
       CLK_LSICmd(DISABLE);
       CLK_HSICmd(ENABLE);
       while(CLK_GetFlagStatus(CLK_FLAG_HSIRDY) == FALSE);

       CLK_ClockSwitchCmd(ENABLE);
       CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV2);
       CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1);

       CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSI,
       DISABLE, CLK_CURRENTCLOCKSTATE_ENABLE);

       CLK_PeripheralClockConfig(CLK_PERIPHERAL_SPI, ENABLE);
       CLK_PeripheralClockConfig(CLK_PERIPHERAL_I2C, DISABLE);
       CLK_PeripheralClockConfig(CLK_PERIPHERAL_ADC, DISABLE);
       CLK_PeripheralClockConfig(CLK_PERIPHERAL_AWU, DISABLE);
       CLK_PeripheralClockConfig(CLK_PERIPHERAL_UART1, DISABLE);
       CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER1, DISABLE);
       CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER2, DISABLE);
       CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER4, DISABLE);
}


void GPIO_setup(void)
{
       GPIO_DeInit(GPIOB);
       GPIO_Init(Button_port, Button_pin, GPIO_MODE_IN_PU_NO_IT);
}


void show_value(unsigned char x_pos, unsigned char y_pos, unsigned char value)
{
   char ch[0x03] = {0x20, 0x20, '\0'};

   ch[0] = (((value / 10) % 10) + 0x30);
   ch[1] = ((value % 10) + 0x30);

   LCD_goto(x_pos, y_pos);
   LCD_putstr(ch);
}


void display_time(void)
{     
       show_value(4, 1, time.h);
       show_value(7, 1, time.m);
       show_value(10, 1, time.s);

       LCD_goto(6, 1);
       LCD_putchar(':');
       LCD_goto(9, 1);
       LCD_putchar(':');  
       delay_ms(100);
}


void setup_time(void)
{
       switch(menu)
       {
              case 0:
              {
                     time.h = adjust(4, 1, 23, 0, time.h);
                     break;
              }            
              case 1:
              {
                     time.m = adjust(7, 1, 59, 0, time.m);
                     break;
              }
              case 2:
              {
                     time.s = adjust(10, 1, 59, 0, time.s);
                     break;
              }
       }
}


unsigned char adjust(unsigned char x_pos, unsigned char y_pos, signed char value_max, signed char value_min, signed char value)
{
       if(GPIO_ReadInputPin(Button_port, Button_pin) == FALSE)
       {
              delay_ms(900);

              if(GPIO_ReadInputPin(Button_port, Button_pin) == FALSE)
              {
                     while(GPIO_ReadInputPin(Button_port, Button_pin) == FALSE);
                     menu++;

                     if(menu >= 3)
                     {
                           LCD_goto(3, 1);
                           LCD_putchar(' ');
                           LCD_goto(12, 1);
                           LCD_putchar(' ');  
                           set_time();
                           set = FALSE;

                           return;
                     }
              }
       }

       else
       {
              value++;

              if(value > value_max)
              {
                  value = value_min;
              }
       }

       LCD_goto(3, 1);
       LCD_putchar('<');
       LCD_goto(12, 1);
       LCD_putchar('>');  

       LCD_goto(x_pos, y_pos);
       LCD_putstr("  ");
       delay_ms(90);

       show_value(x_pos, y_pos, value);
       delay_ms(90);

       return value;
}

 

Explanation

SW_I2C.h and SW_I2C.c files translate the entire I2C communication operations with the manipulation of ordinary GPIOs. Any GPIO pin pair can be used for software I2C. Just define the software I2C port, and the serial data (SDA) and serial clock (SDA) pins:

#define SW_I2C_port              GPIOD

 

#define SDA_pin                  GPIO_PIN_6
#define SCL_pin                  GPIO_PIN_7

The functions in these files are self-explanatory and so I’m not going to explain them here.

The rest of the code is about using the software I2C library with DS1307 real time clock (RTC) chip to make a real time clock.

 

Demo

SW-I2C

The post STM8 Microcontrollers – the Final Chapters appeared first on Embedded Lab.

Arduino Pong clock

$
0
0

An Arudino powered pong clock used the classic Pong video game to tell the time, and has multiple display modes to choose from.

Arduino pong clock

Arduino pong clock

The 2 players automatically win and lose so their scores show the hours and minutes. It is based on a clock by Nick Hall.

This is the 2nd version of my clock and now displays temperature in slide mode and also has a timer in normal mode. The 1st version can be found here Pong Clock Mk1 instructable

The clock has lots of different display modes to choose from:

Pong Clock, Large Digits,Time written in words, e.g. “Ten Past Twelve”, Time and date with seconds,Time and date with seconds and a slide effect,

Options-12/24 hour option, Brightness option, Random clock mode option (changes the display mode between Slide with temp and Pong see bottom for details), Daylight saving option to add an extra hour.

The post Arduino Pong clock appeared first on Embedded Lab.

The 2018 Hackaday Prize has launched

$
0
0

The 2018 Hackaday Prize has been announced. This is the fifth contest of the annual Hackaday Prize series and is jointly sponsored by Digi-Key and Supplyframe. This year’s challenge to the hardware hackers across the globe is to “Build Hope” through open source hardware projects. Over the past 4 years, the Hackaday Prize contest has already given away nearly $1 million to the innovative makers who contributed towards building awesome stuffs to make this world a better place. This year has following 5 themed challenges that run in series:

    • Hardware Design Challenge: 3/12 – 4/23
    • Robotics Module Challenge: 4/23 – 6/4
    • Power Harvesting Challenge: 6/4 – 7/16
    • Human – Computer Interface Challenge: 7/16 – 8/27
    • Musical Instrument Challenge: 8/27 – 10/8

      The Hackaday 2018 Prize has launched

      The Hackaday 2018 Prize has launched

The first round of the competition is the “Open Hardware Design Challenge,” where entrants are encouraged to design the boldest plan they can dream up. Prototypes are not necessary for this challenge – only pictures, charts and theory are required. The Open Hardware Design Challenge kicks off today and runs through April 23.

The remaining rounds are the “Robotics Module Challenge” (April 23-June 4), “Power Harvesting Challenge” (June 4-July 16), “Human-Computer Interface Challenge” (July 16-Aug. 27) and the “Innovative Musical Instrument Challenge” (Aug. 27-Oct. 8).

“We’re excited to partner with Hackaday for another year of challenging inventors to be curious, creative and determined. The Hackaday Prize contest aligns with Digi-Key’s vision to encourage and enable innovation in technology that will solve problems and advance civilization. With the amazing projects we’ve seen in previous years, we can’t wait to see what the entrants create this year, ” said David Sandys, director, Business Ecosystem Development at Digi-Key. 

 

The top 20 entries from each challenge will win $1,000 and be considered for the Finals Round. The top five finalists, including the Grand Prize winner, will be announced at the Hackaday Superconference taking place Nov. 2-3 in Pasadena, California. The Grand Prize winner will be awarded $50,000 and considered for a residency at the Supplyframe DesignLab in Pasadena, California. The second-, third-, fourth- and fifth-place winners will receive $20,000, $15,000, $10,000 and $5,000, respectively.

 

 

In addition to cash prizes, participants will compete throughout the competition for most impressive, outlandish and otherwise notable projects. Although there is no cash value associated with these accomplishments, they do come along with bragging rights. Examples of possible Achievements include the Diva Plavalaguna Achievement (most unexpected musical instrument), the Sonic Screwdriver Achievement (hacks that seemingly do everything) and the Ender’s Achievement (most incredible student submission).

The official rules and other details about the 2018 Hackaday Prize can be found at the Hackaday Prize page.

The post The 2018 Hackaday Prize has launched appeared first on Embedded Lab.

Seeed Fusion OPL Enables Complete PCB Manufacture and Assembly in One Week at Low Cost

$
0
0

What is the Seeed OPL?

The Seeed Fusion Open Parts Library or OPL is a tailored catalog of over 600 carefully selected components available for use in Seeed Fusion’s PCB assembly service. From capacitors and connectors to ICs and displays, these parts are always in-stock and guaranteed to be cheaper than externally sourced parts. By using these components in Seeed’s PCBA service, you will save time and money and at great convenience.

More Reliable

Components from the OPL are sourced from Seeed’s long-term partners. They are tried and tested in Seeed’s own products and undergo the same strict quality control procedures. And since there is always stock, if one part is defective it can easily be replaced. Arguably this is better than outsourcing from a reliable distributor such as DigiKey since outsourced parts cannot be easily replaced and may have been damaged while traveling halfway across the world.

More Convenient

The Seeed OPL was designed for convenience and simplicity. Seeed partnered with PCB design giants Eagle and KiCad to produce the Seeed OPL component libraries, so you can just download the packages and start designing. The resulting design is guaranteed to be compatible with the Seeed Fusion OPL and PCB Assembly service, so no more wasting hours checking availability and compatibility with suppliers.

There is also plenty to choose from; the Seeed OPL does not just include passive parts but many that are utilized in Seeed products and more. Hand-selected by our engineers, the parts are decided on based on customer popularity and feedback. Most recently, in response to the increasing interest in IoT, we introduced Particle’s popular P0 and P1 Wifi modules into our stock.

1

Faster

Typically, the lead time for PCBA is 25 working days. That is over a month before you can begin testing the design and make amendments if necessary. For some, that is much too long. But by sourcing all parts from the OPL, the lead time can be reduced to as short as one week.
2

When a PCBA order is placed, Seeed will simultaneously begin PCB manufacture and order the parts. Both the PCBs and parts are needed to begin assembly, assembly itself can be completed from 8 hours when all the materials arrive. The long lead time for standard PCBA is a result of the long import procedures which cannot be rushed. But since the OPL parts are always in stock, using these will effectively reduce the lead time for parts procurement to zero, speeding up PCBA and allowing greater control over the overall lead time.

Cheaper

Since Seeed OPL parts are purchased in bulk from trusted partners, the cost per component is guaranteed to be significantly lower compared to buying them in small handfuls, and you won’t need to worry about delivery costs. These cost savings are passed on directly to customers. What’s more, this month only, Seeed Fusion has waivered the cost of over 400 parts from the OPL, so you can use them completely free, no restrictions applied.

The Seeed Fusion OPL has already helped many of you reach critical deadlines and slash costs, and we hope it can continue to do so. We would love to hear if you have any new additions you would like to see in the OPL or other suggestions regarding any of our services. Please drop us a message at fusion@seeed.cc. The library and Fusion as a whole is continuously being enhanced thanks to feedback from you.

Don’t forget, Seeed Fusion is currently holding huge Spring sales across PCB and PCBA services. For the last three days, get 10% off PCB orders, $50 off PCBA orders and over 400 OPL completely free!

Watch the factory tour of Seeed Studio:

The post Seeed Fusion OPL Enables Complete PCB Manufacture and Assembly in One Week at Low Cost appeared first on Embedded Lab.


More on TI MSP430s

$
0
0
This post is a sequel of the first post on TI MSP430 micros here.

5529

Low Power Modes (LPM)

From the smartwatches in our wrists to the vehicles we use for transportation, many modern electronic gadgets and gizmos are battery-powered. Some are even dependent on renewable energy sources like solar energy. In such devices, there is always an inherent energy crisis and so saving energy is a must in such designs for prolonged usage. At present there is hardly any microcontroller in the market that does not come equipped with energy-saving schemes or low power modes of operation. MSP430s were mainly designed for battery-backed instruments and it is no surprise that they come loaded with the some of the best possible energy-saving mechanisms.

There six modes of operation of which five are low power modes. These are as follows:

Modes

Of these six modes, three modes are mostly used – Active Mode (AM), LPM0 and LPM3. In Active Mode, the typical self-consumption of a MSP430 device is roughly about 300µA with nothing connected to it. In LPM0 the self-consumption is about a third of active mode while in LPM3, this consumption is just about 1µA. These figures tell us how much energy efficient MSP430s are.

Entering and exiting LPM is easy in terms of coding. However, the most common question that coders face with LPMs is how to get back to active mode or some other low power mode from a given low power mode. Well, it is pretty simple and it is accomplished with interrupts. It is up to coders to decide how to manage interrupts, clock sources and what do to after waking up from a LPM condition. Note that in LPMs, the CPU is disabled and so any task that requires CPU’s intervention is stalled. Since the CPU and some clocks are halted in LPMs, don’t even think that the tasks depending on them will be magically done. For instance, if a code has entered LPM3 and a timer is being driven with SMCLK, we should not expect it to tick because in LPM3, SMCLK is turned off. Organizing the code in a decent and well-planned manner is the secret behind successfully implementing LPMs.

Code Example

#include <msp430.h>
#include "delay.h"


void GPIO_graceInit(void);
void BCSplus_graceInit(void);
void System_graceInit(void);
void WDTplus_graceInit(void);


#pragma vector = PORT1_VECTOR
__interrupt void PORT1_ISR_HOOK(void)
{
    LPM2_EXIT;
    P1OUT |= BIT6;
    P1IFG = 0x00;
}


void main(void)
{
    unsigned char s = 0;

    /* Stop watchdog timer from timing out during initial start-up. */
    WDTCTL = WDTPW | WDTHOLD;

    /* initialize Config for the MSP430 GPIO */
    GPIO_graceInit();

    /* initialize Config for the MSP430 2xx family clock systems (BCS) */
    BCSplus_graceInit();

    /* initialize Config for the MSP430 System Registers */
    System_graceInit();

    /* initialize Config for the MSP430 WDT+ */
    WDTplus_graceInit();

    while(1)
    {
        for(s = 0; s < 9; s++)
        {
            P1OUT ^= BIT0;
            delay_ms(160);
        }

        P1OUT &= ~BIT6;
        LPM2;
    };
}


void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

    /* Port 1 Output Register */
    P1OUT = BIT3;

    /* Port 1 Direction Register */
    P1DIR = BIT0 | BIT6;

    /* Port 1 Resistor Enable Register */
    P1REN = BIT3;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = BIT3;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 1 Interrupt Enable Register */
    P1IE = BIT3;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Port Select Register */
    P2SEL &= ~(BIT6 | BIT7);

    /* Port 2 Direction Register */
    P2DIR = 0;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */
}


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

    if (CALBC1_1MHZ != 0xFF)
    {
        /* Follow recommended flow. First, clear all DCOx and MODx bits. Then
         * apply new RSELx values. Finally, apply new DCOx and MODx bit values.
         */
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_1MHZ;      /* Set DCO to 1MHz */
        DCOCTL = CALDCO_1MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_2 -- If XTS = 0, XT1 = VLOCLK ; If XTS = 1, XT1 = 3 - 16-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_2 | XCAP_1;

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}


void System_graceInit(void)
{
    /* USER CODE START (section: System_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: System_graceInit_prologue) */

    /* Clear oscillator fault flag with software delay */
    do
    {
        // Clear OSC fault flag
        IFG1 &= ~OFIFG;

        // 50us delay
        __delay_cycles(50);
    } while (IFG1 & OFIFG);

    /*
     * SR, Status Register
     *
     * ~SCG1 -- Disable System clock generator 1
     * ~SCG0 -- Disable System clock generator 0
     * ~OSCOFF -- Oscillator On
     * ~CPUOFF -- CPU On
     * GIE -- General interrupt enable
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    __bis_SR_register(GIE);

    /* USER CODE START (section: System_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: System_graceInit_epilogue) */
}


void WDTplus_graceInit(void)
{
    /* USER CODE START (section: RTC_B_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: RTC_B_graceInit_prologue) */

    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * WDTHOLD -- Watchdog timer+ is stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * ~WDTTMSEL -- Watchdog mode
     * ~WDTCNTCL -- No action
     * ~WDTSSEL -- SMCLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * ~WDTIS1 -- Watchdog clock source bit1 disabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTHOLD;

    /* USER CODE START (section: RTC_B_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: RTC_B_graceInit_epilogue) */
}

 

Simulation

SIM

The simulation log of Proteus shown below shows when the internal oscillators started and stopped. These indicate LPM and AM states.

SIM log

Explanation

This is a pretty straight example. The code here works by first flashing the Launchpad board’s red LED for some time. During this time the MSP430 is running in active mode. After the flashing is over, the MP430 micro enters LPM2 state. Note that in LPM2 state all except the DC generator and ACLK are turned off. At this stage to wake up and exit LPM2, an interrupt is needed. Here this interrupt is generated by the external interrupt caused by pressing the Launchpad board’s user button. In the interrupt service routine (ISR), LPM2 is left and is indicated by a brief flash from the Launchpad board’s green LED. After executing the ISR, the code returns to main function and the process repeats again.

Note that for LPMs, there is no segment in Grace and LPM code definitions can be found in device’s header files.

Demo

LPM (2) LPM (1)

Internal Flash Memory

In some applications, there are some very important data that we wish to retain in our target device even when it is powered down. For such purposes we need a nonvolatile memory. Like many modern micros of today’s market, MSP430s do not contain any separate EEPROM memory or battery-backed nonvolatile memory. For storing data like calibration data, settings, etc. that we would have saved in EEPROM memories, we can use the internal flash memory of our MSP430 devices. Though it may sound difficult and challenging, it is not so. However, we need to be very careful about storage locations as such that we don’t accidentally use locations where application codes reside.

Shown below is a flash memory map example of a MSP430G2xxx device.

memory_map

Note that there are four segments labelled A through D. These are the locations that we will be using for data storage and are called information memory. The rest is code space. We can also use the code space too but the code space has 512-byte segment size compared to 64-byte segment size of information memory. Now why is it so important to use information memory space instead of code memory? This is because of its small segment size. During memory erase, we have to erase a full segment. Bit, byte and word level read-write operations can be done easily but erasing is not possible at these levels. Two separate segments can be used to emulate low level erase. When such mechanism is applied. One segment acts like a buffer while the other is used for actual storage. Wear-leveling may optionally be applied. However, these processes add delays and extra cosing.

Segment A is a very important segment as it stores important internal calibration data like DCO frequency variables, etc. Thus, it is protected and locked separately. It will be wise to leave it and use the other three segments of information memory space to store data.

Code Example

#include <msp430.h>
#include "delay.h"
#include "SW_I2C.h"
#include "PCF8574.h"
#include "lcd.h"


void Flash_graceInit(void);
void GPIO_graceInit(void);
void BCSplus_graceInit(void);
void System_graceInit(void);
void WDTplus_graceInit(void);
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned char value);
void Flash_Erase(unsigned int address);
void Flash_Write_Char(unsigned int address, char value);
char Flash_Read_Char(unsigned int address);
void Flash_Write_Word(unsigned int address, unsigned int value);
unsigned int Flash_Read_Word(unsigned int address);


void main(void)
{
    unsigned char value = 0x00;

    /* Stop watchdog timer from timing out during initial start-up. */
    WDTCTL = WDTPW | WDTHOLD;

    /* initialize Config for the MSP430F2xx Flash Memory Controller */
    Flash_graceInit();

    /* initialize Config for the MSP430 GPIO */
    GPIO_graceInit();

    /* initialize Config for the MSP430 2xx family clock systems (BCS) */
    BCSplus_graceInit();

    /* initialize Config for the MSP430 System Registers */
    System_graceInit();

    /* initialize Config for the MSP430 WDT+ */
    WDTplus_graceInit();

    LCD_init();
    LCD_clear_home();

    LCD_goto(0, 0);
    LCD_putstr("MSP430 Flash Ex.");

    value = Flash_Read_Char(0x1000);

    LCD_goto(0, 1);
    LCD_putstr("WR: ---");
    LCD_goto(9, 1);
    LCD_putstr("RD:");
    lcd_print(13, 1, value);
    delay_ms(2000);

    while(1)
    {
        if((P1IN & BIT3) == !BIT3)
        {
            while((P1IN & BIT3) == !BIT3);
            Flash_Erase(0x1000);
            Flash_Write_Char(0x1000, value);
            lcd_print(13, 1, value);
            P1OUT |= BIT0;
            _delay_cycles(40000);
            P1OUT &= ~BIT0;
        }

        delay_ms(20);
        lcd_print(4, 1, value);

        value++;
        delay_ms(200);
    };
}


void Flash_graceInit(void)
{
    /* USER CODE START (section: Flash_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: Flash_graceInit_prologue) */

    /*
     * Flash Memory Control Register 2
     *
     * FSSEL_1 -- MCLK
     * ~FN5 -- Flash controller clock divider bit 5
     * FN4 -- Flash controller clock divider bit 4
     * ~FN3 -- Flash controller clock divider bit 3
     * FN2 -- Flash controller clock divider bit 2
     * ~FN1 -- Flash controller clock divider bit 1
     * FN0 -- Flash controller clock divider bit 0
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    FCTL2 = FWKEY | FSSEL_1 | FN4 | FN2 | FN0;

    /* USER CODE START (section: Flash_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: Flash_graceInit_epilogue) */
}


void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

    /* Port 1 Output Register */
    P1OUT = BIT3;

    /* Port 1 Direction Register */
    P1DIR = BIT0;

    /* Port 1 Resistor Enable Register */
    P1REN = BIT3;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Direction Register */
    P2DIR = 0;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* Port 3 Output Register */
    P3OUT = 0;

    /* Port 3 Direction Register */
    P3DIR = 0;

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */

}


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

    if (CALBC1_8MHZ != 0xFF) {
        /* Adjust this accordingly to your VCC rise time */
        __delay_cycles(100000);

        // Follow recommended flow. First, clear all DCOx and MODx bits. Then
        // apply new RSELx values. Finally, apply new DCOx and MODx bit values.
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_8MHZ;      /* Set DCO to 8MHz */
        DCOCTL = CALDCO_8MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_0 -- If XTS = 0, XT1 = 32768kHz Crystal ; If XTS = 1, XT1 = 0.4 - 1-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_0 | XCAP_1;

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}


void System_graceInit(void)
{
    /* USER CODE START (section: System_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: System_graceInit_prologue) */

    /*
     * SR, Status Register
     *
     * ~SCG1 -- Disable System clock generator 1
     * ~SCG0 -- Disable System clock generator 0
     * ~OSCOFF -- Oscillator On
     * ~CPUOFF -- CPU On
     * GIE -- General interrupt enable
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    __bis_SR_register(GIE);

    /* USER CODE START (section: System_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: System_graceInit_epilogue) */
}


void WDTplus_graceInit(void)
{
    /* USER CODE START (section: RTC_B_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: RTC_B_graceInit_prologue) */

    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * WDTHOLD -- Watchdog timer+ is stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * ~WDTTMSEL -- Watchdog mode
     * ~WDTCNTCL -- No action
     * ~WDTSSEL -- SMCLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * ~WDTIS1 -- Watchdog clock source bit1 disabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTHOLD;

    /* USER CODE START (section: RTC_B_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: RTC_B_graceInit_epilogue) */
}


void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned char value)
{
    char chr = 0x00;

    chr = ((value / 100) + 0x30);
    LCD_goto(x_pos, y_pos);
    LCD_putchar(chr);

    chr = (((value / 10) % 10) + 0x30);
    LCD_goto((x_pos + 1), y_pos);
    LCD_putchar(chr);

    chr = ((value % 10) + 0x30);
    LCD_goto((x_pos + 2), y_pos);
    LCD_putchar(chr);
}


void Flash_Erase(unsigned int address)
{
    char *FlashPtr;

    FlashPtr = (char *)address;
    FCTL1 = FWKEY + ERASE;                      // Set Erase bit
    FCTL3 = FWKEY;                              // Clear Lock bit
    __bic_SR_register(GIE);                     // Disable Interrupts
    *FlashPtr = 0;                              // Dummy write to erase Flash segment B
    while((FCTL3 & BUSY) == BUSY);              // Busy
    __bis_SR_register(GIE);                     // Enable Interrupts
    FCTL1 = FWKEY;                              // Lock
    FCTL3 = FWKEY + LOCK;                       // Set Lock bit
}


void Flash_Write_Char(unsigned int address, char value)
{
    char *FlashPtr = (char *)address;

    FCTL1 = FWKEY + WRT;                        // Set WRT bit for write operation
    FCTL3 = FWKEY;                              // Clear Lock bit
    __bic_SR_register(GIE);                     // Disable Interrupts
    *FlashPtr = value;                          // Save Data
    while((FCTL3 & BUSY) == BUSY);              // Busy
    __bis_SR_register(GIE);                     // Enable Interrupts
    FCTL1 = FWKEY;                              // Clear WRT bit
    FCTL3 = FWKEY + LOCK;                       // Set LOCK bit
}


char Flash_Read_Char(unsigned int address)
{
    char value = 0x00;
    char *FlashPtr = (char *)address;

    value = *FlashPtr;

    return value;
}


void Flash_Write_Word(unsigned int address, unsigned int value)
{
    unsigned int *FlashPtr = (unsigned int *)address;

    FCTL1 = FWKEY + WRT;                        // Set WRT bit for write operation
    FCTL3 = FWKEY;                              // Clear Lock bit
    __bic_SR_register(GIE);                     // Disable Interrupts
    *FlashPtr = value;                          // Save Data
    while((FCTL3 & BUSY) == BUSY);              // Busy
    __bis_SR_register(GIE);                     // Enable Interrupts
    FCTL1 = FWKEY;                              // Clear WRT bit
    FCTL3 = FWKEY + LOCK;                       // Set LOCK bit
}


unsigned int Flash_Read_Word(unsigned int address)
{
    unsigned int value = 0x0000;
    unsigned int *FlashPtr = (unsigned int *)address;

    value = *FlashPtr;

    return value;
}

 

Simulation

sim

Explanation

The flash memory module of MSP430s has an integrated controller that controls programming and erase operations. The controller has four registers, a timing generator, and a voltage generator to supply program and erase voltages.

Grace

Using Grace, we initialize the aforementioned:

FCTL2 = FWKEY | FSSEL_1 | FN4 | FN2 | FN0;

To write a byte, we need two things – memory location and the value we wish to write. This memory location is that piece of memory space where we wish to store the value.

void Flash_Write_Char(unsigned int address, char value)
{
    char *FlashPtr = (char *)address;

    FCTL1 = FWKEY + WRT;                        // Set WRT bit for write operation
    FCTL3 = FWKEY;                              // Clear Lock bit
    __bic_SR_register(GIE);                     // Disable Interrupts
    *FlashPtr = value;                          // Save Data
    while((FCTL3 & BUSY) == BUSY);              // Busy
    __bis_SR_register(GIE);                     // Enable Interrupts
    FCTL1 = FWKEY;                              // Clear WRT bit
    FCTL3 = FWKEY + LOCK;                       // Set LOCK bit
}

Firstly, the address of the memory location where data is to be stored is pointed out. Flash write process starts by setting the write bit, followed by removing the flash protection. Once these are done, all interrupts are temporarily disabled to avoid any accidental write or illegal operation. The value to be written is then pointed. Until the value is successfully written all other processes are halted. Once the value to be stored is successfully written, interrupts are enabled, the write bit is cleared and the flash lock is applied.

Reading the flash is simpler. We just have to point the location we wish to read.

char Flash_Read_Char(unsigned int address)
{
    char value = 0x00;
    char *FlashPtr = (char *)address;

    value = *FlashPtr;

    return value;
}

The process for erasing is similar to write processes. The only difference is Erase bit instead of Write bit.

void Flash_Erase(unsigned int address)
{
    char *FlashPtr;

    FlashPtr = (char *)address;
    FCTL1 = FWKEY + ERASE;                      // Set Erase bit
    FCTL3 = FWKEY;                              // Clear Lock bit
    __bic_SR_register(GIE);                     // Disable Interrupts
    *FlashPtr = 0;                              // Dummy write to erase Flash segment B
    while((FCTL3 & BUSY) == BUSY);              // Busy
    __bis_SR_register(GIE);                     // Enable Interrupts
    FCTL1 = FWKEY;                              // Lock
    FCTL3 = FWKEY + LOCK;                       // Set Lock bit
}

The same read-write processes can also be applied to read/write word-level values.

The code demoed her works by reading the last data stored in the target flash location (0x1000) and incrementing a variable named value. Only this location is read and updated when the Launchpad board’s button is pressed.

Demo

Flash

Time Delay Generation with Timer Compare-Match Feature

Time-bases and delays can be generated in many different ways, ranging from software techniques to using a dedicated hardware timer. Between software-based methods and hardware-based ones, the latter is more efficient and effective. This is because software-based methods rely on wasteful CPU-intensive loops and other resource-consuming processes. Hardware approaches for generating time-bases and delays are smart choices because the prime job of a timer is to count ticks or measure time. Yet within hardware-based methods, there are several techniques and tricks. We can choose between polling a free running timer or using interrupts to get things done in a more real-time sense. We have seen previously that we can use timer interrupts to time events. Here we will also see the same but this time compare-match interrupt is used instead of timer interrupt.

TIMCCR

Code Example

#include <msp430.h>


void GPIO_graceInit(void);
void BCSplus_graceInit(void);
void Timer0_A3_graceInit(void);
void System_graceInit(void);
void WDTplus_graceInit(void);


#pragma vector = TIMER0_A0_VECTOR
__interrupt void TIMER0_A0_ISR_HOOK(void)
{
    P1OUT ^= (BIT0 | BIT6);
    __bic_SR_register_on_exit(LPM0_bits);
}


void main(void)
{
    /* Stop watchdog timer from timing out during initial start-up. */
    WDTCTL = WDTPW | WDTHOLD;

    /* initialize Config for the MSP430 GPIO */
    GPIO_graceInit();

    /* initialize Config for the MSP430 2xx family clock systems (BCS) */
    BCSplus_graceInit();

    /* initialize Config for the MSP430 A3 Timer0 */
    Timer0_A3_graceInit();

    /* initialize Config for the MSP430 System Registers */
    System_graceInit();

    /* initialize Config for the MSP430 WDT+ */
    WDTplus_graceInit();

    P1OUT |= BIT0;
    P1OUT &= ~BIT6;

    while(1)
    {
        __bis_SR_register(LPM0_bits);
    };
}


void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

    /* Port 1 Output Register */
    P1OUT = 0;

    /* Port 1 Direction Register */
    P1DIR = BIT0 | BIT6;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Port Select Register */
    P2SEL &= ~(BIT6 | BIT7);

    /* Port 2 Direction Register */
    P2DIR = 0;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */
}


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

    if (CALBC1_1MHZ != 0xFF)
    {
        /* Follow recommended flow. First, clear all DCOx and MODx bits. Then
         * apply new RSELx values. Finally, apply new DCOx and MODx bit values.
         */
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_1MHZ;      /* Set DCO to 1MHz */
        DCOCTL = CALDCO_1MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_0 -- If XTS = 0, XT1 = 32768kHz Crystal ; If XTS = 1, XT1 = 0.4 - 1-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_0 | XCAP_1;

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}


void Timer0_A3_graceInit(void)
{
    /* USER CODE START (section: Timer0_A3_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: Timer0_A3_graceInit_prologue) */

    /*
     * TA0CCTL0, Capture/Compare Control Register 0
     *
     * CM_0 -- No Capture
     * CCIS_0 -- CCIxA
     * ~SCS -- Asynchronous Capture
     * ~SCCI -- Latched capture signal (read)
     * ~CAP -- Compare mode
     * OUTMOD_0 -- PWM output mode: 0 - OUT bit value
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    TA0CCTL0 = CM_0 | CCIS_0 | OUTMOD_0 | CCIE;

    /* TA0CCR0, Timer_A Capture/Compare Register 0 */
    TA0CCR0 = 49999;

    /*
     * TA0CTL, Timer_A3 Control Register
     *
     * TASSEL_2 -- SMCLK
     * ID_3 -- Divider - /8
     * MC_1 -- Up Mode
     */
    TA0CTL = TASSEL_2 | ID_3 | MC_1;

    /* USER CODE START (section: Timer0_A3_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: Timer0_A3_graceInit_epilogue) */
}


void System_graceInit(void)
{
    /* USER CODE START (section: System_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: System_graceInit_prologue) */

    /*
     * SR, Status Register
     *
     * ~SCG1 -- Disable System clock generator 1
     * ~SCG0 -- Disable System clock generator 0
     * ~OSCOFF -- Oscillator On
     * ~CPUOFF -- CPU On
     * GIE -- General interrupt enable
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    __bis_SR_register(GIE);

    /* USER CODE START (section: System_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: System_graceInit_epilogue) */
}


void WDTplus_graceInit(void)
{
    /* USER CODE START (section: RTC_B_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: RTC_B_graceInit_prologue) */

    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * WDTHOLD -- Watchdog timer+ is stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * ~WDTTMSEL -- Watchdog mode
     * ~WDTCNTCL -- No action
     * ~WDTSSEL -- SMCLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * ~WDTIS1 -- Watchdog clock source bit1 disabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTHOLD;

    /* USER CODE START (section: RTC_B_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: RTC_B_graceInit_epilogue) */
}

 

Simulation

SIM

Explanation

Timer0_A3 is set here for compare-match interval mode. In this mode, Timer0_A3’s settings are same as we would do for ordinary timer overflow interrupt. However, the key difference is the interrupt source. Note that in the diagram below timer overflow interrupt is not being used. Timer capture-compare interrupt is used instead.

Grace

The desire time period is set for 400ms or 2.5Hz. At every 400ms interval, a compare-match interrupt will occur. How this is done? Well the timer is set for up counting and it has an input clock of 125kHz – 1MHz SMCLK prescaled by 8.

void Timer0_A3_graceInit(void)
{
    /* USER CODE START (section: Timer0_A3_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: Timer0_A3_graceInit_prologue) */

    /*
     * TA0CCTL0, Capture/Compare Control Register 0
     *
     * CM_0 -- No Capture
     * CCIS_0 -- CCIxA
     * ~SCS -- Asynchronous Capture
     * ~SCCI -- Latched capture signal (read)
     * ~CAP -- Compare mode
     * OUTMOD_0 -- PWM output mode: 0 - OUT bit value
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    TA0CCTL0 = CM_0 | CCIS_0 | OUTMOD_0 | CCIE;

    /* TA0CCR0, Timer_A Capture/Compare Register 0 */
    TA0CCR0 = 49999;

    /*
     * TA0CTL, Timer_A3 Control Register
     *
     * TASSEL_2 -- SMCLK
     * ID_3 -- Divider - /8
     * MC_1 -- Up Mode
     */
    TA0CTL = TASSEL_2 | ID_3 | MC_1;

    /* USER CODE START (section: Timer0_A3_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: Timer0_A3_graceInit_epilogue) */
}

Inside the interrupt function, the LEDs of Launchpad board are toggled. Note that after the occurrence of the interrupt LPM0 is exited.

#pragma vector = TIMER0_A0_VECTOR
__interrupt void TIMER0_A0_ISR_HOOK(void)
{
    P1OUT ^= (BIT0 | BIT6);
    __bic_SR_register_on_exit(LPM0_bits);
}

In the main, there is no task and in it LPM0 is entered. Thus, the process is independent of the main and is energy efficient.

while(1)
{
    __bis_SR_register(LPM0_bits);
};

 

Demo

Comp_match

One Wire (OW) – Interfacing DS18B20 Temperature Sensor

One Wire (OW) or single wire communication is different from other more common and conventional communication platforms like SPI or I2C in terms of data exchange behavior. OW communication is also not very much popular compared to SPI, UART (RS232), I2C, RS485, etc. From device to device, the way of exchanging data varies but what’s common is the fact that all devices that use this communication method use a sort of time-slotting mechanism. Ones and zeros are defined by high pulse time over a fixed period. This trick is widely used in infrared remote controllers. One major advantage of OW communication is the fact that no special or dedicated hardware block is needed to implement it. All that is typically needed is a digital I/O pin. A timer can be used for tracking time-slots but it is optional. External interrupts can also be optionally used alongside the timer. DS18B20 one wire digital temperature sensor from Dallas semiconductor uses this communication protocol.

OW-DS18B20

Code Example

 

one_wire.h

#include <msp430.h>
#include "delay.h"


#define DS18B20_DIR              P2DIR
#define DS18B20_OUT_PORT         P2OUT
#define DS18B20_IN_PORT          P2IN
#define DS18B20_PIN              BIT0

#define DS18B20_OUTPUT()         do{DS18B20_DIR |= DS18B20_PIN;}while(0)
#define DS18B20_INPUT()          do{DS18B20_DIR &= ~DS18B20_PIN;}while(0)

#define DS18B20_IN()             (DS18B20_IN_PORT & DS18B20_PIN)

#define DS18B20_OUT_LOW()        do{DS18B20_OUT_PORT &= ~DS18B20_PIN;}while(0)
#define DS18B20_OUT_HIGH()       do{DS18B20_OUT_PORT |= DS18B20_PIN;}while(0)

#define TRUE                     1
#define FALSE                    0


unsigned char onewire_reset(void);
void onewire_write_bit(unsigned char bit_value);
unsigned char onewire_read_bit(void);
void onewire_write(unsigned char value);   
unsigned char onewire_read(void);

 

one_wire.c

#include "one_wire.h"


unsigned char onewire_reset(void
{                                        
        unsigned char res = FALSE;

        DS18B20_OUTPUT();                
        DS18B20_OUT_LOW();
        delay_us(480);       
        DS18B20_OUT_HIGH();
        delay_us(60);       

        DS18B20_INPUT();
        res = DS18B20_IN();
        delay_us(480);      

        return res;
}


void onewire_write_bit(unsigned char bit_value)
{
       DS18B20_OUTPUT();
       DS18B20_OUT_LOW();

       if(bit_value)
       {    
              delay_us(104);
              DS18B20_OUT_HIGH();  
       }             
}    


unsigned char onewire_read_bit(void)       
{    
    DS18B20_OUTPUT();
    DS18B20_OUT_LOW(); 
    DS18B20_OUT_HIGH(); 
    delay_us(15);     
    DS18B20_INPUT();

       return(DS18B20_IN());   
}


void onewire_write(unsigned char value)
{                   
        unsigned char s = 0;

        DS18B20_OUTPUT();

        while(s < 8)   
        {                             
                if((value & (1 << s)))
                {
                     DS18B20_OUT_LOW();
                     _delay_cycles(1);
                     DS18B20_OUT_HIGH(); 
                     delay_us(60);  
                }      

                else
                {
                    DS18B20_OUT_LOW();          
                    delay_us(60);          
                    DS18B20_OUT_HIGH();  
                    _delay_cycles(1);
                }

                s++;
        }
}                                     


unsigned char onewire_read(void)
{
        unsigned char s = 0x00;
        unsigned char value = 0x00;

        while(s < 8)
        {
                DS18B20_OUTPUT();

                DS18B20_OUT_LOW();
                _delay_cycles(1);
                DS18B20_OUT_HIGH(); 

                DS18B20_INPUT();
                if(DS18B20_IN()) 
                {                                     
                    value |= (1 << s);                        
                }       

                delay_us(60);

                s++;
        }    

        return value;
}

 

DS18B20.h

#include <msp430.h>
#include "delay.h"
#include "one_wire.h" 


#define convert_T                              0x44
#define read_scratchpad                        0xBE           
#define write_scratchpad                       0x4E
#define copy_scratchpad                        0x48  
#define recall_E2                              0xB8
#define read_power_supply                      0xB4   
#define skip_ROM                               0xCC

#define resolution                             12


void DS18B20_init(void);
float DS18B20_get_temperature(void);

 

DS18B20.c

#include "DS18B20.h"


void DS18B20_init(void)                            
{                                      
       onewire_reset();
       delay_ms(100);
}             


float DS18B20_get_temperature(void)
{                                              
       unsigned char msb = 0x00;
       unsigned char lsb = 0x00;
       register float temp = 0.0; 

       onewire_reset();    
       onewire_write(skip_ROM);       
       onewire_write(convert_T);

       switch(resolution)  
       {                                                 
              case 12:
              {                                               
                     delay_ms(750);
                     break;
              }               
              case 11:                                    
              {             
                     delay_ms(375);
                     break;
              }          
              case 10:                            
              {                                 
                     delay_ms(188);  
                     break;
              }                                       
              case 9:                                  
              {                                               
                     delay_ms(94);                
                     break;                           
              }                       
       }                 

       onewire_reset();

       onewire_write(skip_ROM);                
       onewire_write(read_scratchpad);

       lsb = onewire_read();
       msb = onewire_read();

       temp = msb;                          
       temp *= 256.0;
       temp += lsb;


       switch(resolution)  
       {                                 
              case 12:           
              {                                               
                     temp *= 0.0625;                
                     break;                           
              }            
              case 11:
              {              
                     temp *= 0.125;     
                     break;
              }               
              case 10:
              {           
                     temp *= 0.25;      
                     break;
              } 
              case 9:                                
              {                                
                     temp *= 0.5;       
                     break;     
              }                          
       } 

       delay_ms(40);      

       return (temp);      
}

 

main.c

#include <msp430.h>
#include "delay.h"
#include "SW_I2C.h"
#include "PCF8574.h"
#include "one_wire.h"
#include "DS18B20.h"
#include "lcd.h"


const unsigned char symbol[8] =
{
   0x00, 0x06, 0x09, 0x09, 0x06, 0x00, 0x00, 0x00
};


void GPIO_graceInit(void);
void BCSplus_graceInit(void);
void System_graceInit(void);
void WDTplus_graceInit(void);
void lcd_symbol(void);
void print_C(unsigned char x_pos, unsigned char y_pos, signed int value);
void print_I(unsigned char x_pos, unsigned char y_pos, signed long value);
void print_D(unsigned char x_pos, unsigned char y_pos, signed int value, unsigned char points);
void print_F(unsigned char x_pos, unsigned char y_pos, float value, unsigned char points);


void main(void)
{
    float t = 0.0;

    /* Stop watchdog timer from timing out during initial start-up. */
    WDTCTL = WDTPW | WDTHOLD;

    /* initialize Config for the MSP430 GPIO */
    GPIO_graceInit();

    /* initialize Config for the MSP430 2xx family clock systems (BCS) */
    BCSplus_graceInit();

    /* initialize Config for the MSP430 System Registers */
    System_graceInit();

    /* initialize Config for the MSP430 WDT+ */
    WDTplus_graceInit();

    DS18B20_init();
    LCD_init();
    lcd_symbol();

    LCD_goto(1, 0);
    LCD_putstr("MSP430 DS18B20");

    LCD_goto(0, 1);
    LCD_putstr("T/ C");
    LCD_goto(2, 1);
    LCD_send(0, DAT);

    while(1)
    {
        t = DS18B20_get_temperature();
        print_F(9, 1, t, 3);
        delay_ms(1000);
    };
}


void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

    /* Port 1 Output Register */
    P1OUT = 0;

    /* Port 1 Direction Register */
    P1DIR = 0;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Port Select Register */
    P2SEL &= ~(BIT6 | BIT7);

    /* Port 2 Direction Register */
    P2DIR = 0;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */

}


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

    if (CALBC1_8MHZ != 0xFF)
    {
        /* Adjust this accordingly to your VCC rise time */
        __delay_cycles(100000);

        // Follow recommended flow. First, clear all DCOx and MODx bits. Then
        // apply new RSELx values. Finally, apply new DCOx and MODx bit values.
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_8MHZ;      /* Set DCO to 8MHz */
        DCOCTL = CALDCO_8MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_0 -- If XTS = 0, XT1 = 32768kHz Crystal ; If XTS = 1, XT1 = 0.4 - 1-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_0 | XCAP_1;

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}


void System_graceInit(void)
{
    /* USER CODE START (section: System_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: System_graceInit_prologue) */

    /*
     * SR, Status Register
     *
     * ~SCG1 -- Disable System clock generator 1
     * ~SCG0 -- Disable System clock generator 0
     * ~OSCOFF -- Oscillator On
     * ~CPUOFF -- CPU On
     * GIE -- General interrupt enable
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    __bis_SR_register(GIE);

    /* USER CODE START (section: System_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: System_graceInit_epilogue) */
}


void WDTplus_graceInit(void)
{
    /* USER CODE START (section: RTC_B_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: RTC_B_graceInit_prologue) */

    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * WDTHOLD -- Watchdog timer+ is stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * ~WDTTMSEL -- Watchdog mode
     * ~WDTCNTCL -- No action
     * ~WDTSSEL -- SMCLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * ~WDTIS1 -- Watchdog clock source bit1 disabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTHOLD;

    /* USER CODE START (section: RTC_B_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: RTC_B_graceInit_epilogue) */
}


void lcd_symbol(void)
{
    unsigned char s = 0;

   LCD_send(0x40, CMD);

   for(s = 0; s < 8; s++)
   {
        LCD_send(symbol[s], DAT);
   }

   LCD_send(0x80, CMD);
}


void print_C(unsigned char x_pos, unsigned char y_pos, signed int value)
{
     char ch[5] = {0x20, 0x20, 0x20, 0x20, '\0'};

     if(value < 0x00)
     {
        ch[0] = 0x2D;
        value = -value;
     }
     else
     {
        ch[0] = 0x20;
     }

     if((value > 99) && (value <= 999))
     {
         ch[1] = ((value / 100) + 0x30);
         ch[2] = (((value % 100) / 10) + 0x30);
         ch[3] = ((value % 10) + 0x30);
     }
     else if((value > 9) && (value <= 99))
     {
         ch[1] = (((value % 100) / 10) + 0x30);
         ch[2] = ((value % 10) + 0x30);
         ch[3] = 0x20;
     }
     else if((value >= 0) && (value <= 9))
     {
         ch[1] = ((value % 10) + 0x30);
         ch[2] = 0x20;
         ch[3] = 0x20;
     }

     LCD_goto(x_pos, y_pos);
     LCD_putstr(ch);
}


void print_I(unsigned char x_pos, unsigned char y_pos, signed long value)
{
    char ch[7] = {0x20, 0x20, 0x20, 0x20, 0x20, 0x20, '\0'};

    if(value < 0)
    {
        ch[0] = 0x2D;
        value = -value;
    }
    else
    {
        ch[0] = 0x20;
    }

    if(value > 9999)
    {
        ch[1] = ((value / 10000) + 0x30);
        ch[2] = (((value % 10000)/ 1000) + 0x30);
        ch[3] = (((value % 1000) / 100) + 0x30);
        ch[4] = (((value % 100) / 10) + 0x30);
        ch[5] = ((value % 10) + 0x30);
    }

    else if((value > 999) && (value <= 9999))
    {
        ch[1] = (((value % 10000)/ 1000) + 0x30);
        ch[2] = (((value % 1000) / 100) + 0x30);
        ch[3] = (((value % 100) / 10) + 0x30);
        ch[4] = ((value % 10) + 0x30);
        ch[5] = 0x20;
    }
    else if((value > 99) && (value <= 999))
    {
        ch[1] = (((value % 1000) / 100) + 0x30);
        ch[2] = (((value % 100) / 10) + 0x30);
        ch[3] = ((value % 10) + 0x30);
        ch[4] = 0x20;
        ch[5] = 0x20;
    }
    else if((value > 9) && (value <= 99))
    {
        ch[1] = (((value % 100) / 10) + 0x30);
        ch[2] = ((value % 10) + 0x30);
        ch[3] = 0x20;
        ch[4] = 0x20;
        ch[5] = 0x20;
    }
    else
    {
        ch[1] = ((value % 10) + 0x30);
        ch[2] = 0x20;
        ch[3] = 0x20;
        ch[4] = 0x20;
        ch[5] = 0x20;
    }

    LCD_goto(x_pos, y_pos);
    LCD_putstr(ch);
}


void print_D(unsigned char x_pos, unsigned char y_pos, signed int value, unsigned char points)
{
    char ch[5] = {0x2E, 0x20, 0x20, '\0'};

    ch[1] = ((value / 100) + 0x30);

    if(points > 1)
    {
        ch[2] = (((value / 10) % 10) + 0x30);

        if(points > 1)
        {
            ch[3] = ((value % 10) + 0x30);
        }
    }

    LCD_goto(x_pos, y_pos);
    LCD_putstr(ch);
}


void print_F(unsigned char x_pos, unsigned char y_pos, float value, unsigned char points)
{
    signed long tmp = 0x0000;

    tmp = value;
    print_I(x_pos, y_pos, tmp);
    tmp = ((value - tmp) * 1000);

    if(tmp < 0)
    {
       tmp = -tmp;
    }

    if(value < 0)
    {
        value = -value;
        LCD_goto(x_pos, y_pos);
        LCD_putchar(0x2D);
    }
    else
    {
        LCD_goto(x_pos, y_pos);
        LCD_putchar(0x20);
    }

    if((value >= 10000) && (value < 100000))
    {
        print_D((x_pos + 6), y_pos, tmp, points);
    }
    else if((value >= 1000) && (value < 10000))
    {
        print_D((x_pos + 5), y_pos, tmp, points);
    }
    else if((value >= 100) && (value < 1000))
    {
        print_D((x_pos + 4), y_pos, tmp, points);
    }
    else if((value >= 10) && (value < 100))
    {
        print_D((x_pos + 3), y_pos, tmp, points);
    }
    else if(value < 10)
    {
        print_D((x_pos + 2), y_pos, tmp, points);
    }
}

 

Simulation

DS18B20

Explanation

One wire communication is detailed in these application notes from Maxim:

https://www.maximintegrated.com/en/app-notes/index.mvp/id/126

https://www.maximintegrated.com/en/app-notes/index.mvp/id/162

These notes are all that are needed for implementing the one wire communication interface for DS18B20. Please go through these notes for details. The codes are self-explanatory and are implemented from the code examples in these app notes.

Demo

DS18B20

One Wire (OW) – Interfacing DHT22 Hygrometer Sensor

Like DS18B20, DHT22 (a.k.a AM2302) digital relative humidity-temperature or hygrometer sensor uses time-slotting principle over one wire to transfer data to its host controller. Apart from other technical specs, these sensors differ in terms of time-slots and in the process of data exchanging and communication bus arbitration. Since data is transferred digitally over one wire, there is no need for such sensors to be present on board and close to the host MCU. Thus, such sensors can be placed significantly far from the host micro. This is not so easily possible with analog sensors or with sensors using multiple wires. This feature is what makes OW communication method an impressive one.

keyes-am2302-module

Code Example

 

DHT22.h

#include <msp430.h>
#include <delay.h>


#define DHT22_DIR                       P2DIR
#define DHT22_OUT_PORT                  P2OUT
#define DHT22_IN_PORT                   P2IN
#define DHT22_PIN                       BIT0

#define DHT22_DIR_OUT()                 do{DHT22_DIR |= DHT22_PIN;}while(0)
#define DHT22_DIR_IN()                  do{DHT22_DIR &= ~DHT22_PIN;}while(0)

#define DHT22_IN()                      (DHT22_IN_PORT & DHT22_PIN)

#define DHT22_OUT_LOW()                 do{DHT22_OUT_PORT &= ~DHT22_PIN;}while(0)
#define DHT22_OUT_HIGH()                do{DHT22_OUT_PORT |= DHT22_PIN;}while(0)

#define TRUE                            1
#define FALSE                           0


extern unsigned char values[5];


void DHT22_init(void);
unsigned char DHT22_get_byte(void);
unsigned char DHT22_get_data(void);

 

DHT22.c

#include "DHT22.h"


unsigned char values[5];


void DHT22_init(void)
{
   DHT22_DIR_IN();
   delay_ms(1000);
}


unsigned char DHT22_get_byte(void)
{
   unsigned char s = 8;
   unsigned char value = 0;

   DHT22_DIR_IN();

   while(s > 0)
   {
      value <<= 1;

      while(DHT22_IN() == FALSE);
      delay_us(30);

      if(DHT22_IN())
      {
          value |= 1;
      }

      while(DHT22_IN());
      s--;
   }

   return value;
}


unsigned char DHT22_get_data(void)
{
       unsigned char chk = FALSE;
       unsigned char s = 0;
       unsigned char check_sum = 0;

       DHT22_DIR_OUT();

       DHT22_OUT_HIGH();
       DHT22_OUT_LOW();

       delay_ms(1);

       DHT22_OUT_HIGH();

       delay_us(32);
       DHT22_DIR_IN();

       chk = DHT22_IN();
       delay_us(2);

       if(chk == TRUE)
       {
              return 1;
       }

       delay_us(80);

       chk = DHT22_IN();

       if(chk == FALSE)
       {
              return 2;
       }

       delay_us(80);

       for(s = 0; s <= 4; s += 1)
       {
              values[s] = DHT22_get_byte();
       }

       DHT22_DIR_OUT();
       DHT22_OUT_HIGH();

       for(s = 0; s < 4; s++)
       {
       check_sum += values[s];
       }

       if(check_sum != values[4])
       {
              return 3;
       }
       else
       {
              return 0;
       }
}

 

main.c

#include <msp430.h>
#include "delay.h"
#include "SW_I2C.h"
#include "PCF8574.h"
#include "lcd.h"
#include "DHT22.h"


const unsigned char symbol[8] =
{
   0x00, 0x06, 0x09, 0x09, 0x06, 0x00, 0x00, 0x00
};


void GPIO_graceInit(void);
void BCSplus_graceInit(void);
void System_graceInit(void);
void WDTplus_graceInit(void);
void lcd_symbol(void);
void print_C(unsigned char x_pos, unsigned char y_pos, signed int value);
void print_I(unsigned char x_pos, unsigned char y_pos, signed long value);
void print_D(unsigned char x_pos, unsigned char y_pos, signed int value, unsigned char points);
void print_F(unsigned char x_pos, unsigned char y_pos, float value, unsigned char points);


void main(void)
{
    float value = 0.0;
    unsigned char state = 0;

    /* Stop watchdog timer from timing out during initial start-up. */
    WDTCTL = WDTPW | WDTHOLD;

    /* initialize Config for the MSP430 GPIO */
    GPIO_graceInit();

    /* initialize Config for the MSP430 2xx family clock systems (BCS) */
    BCSplus_graceInit();

    /* initialize Config for the MSP430 System Registers */
    System_graceInit();

    /* initialize Config for the MSP430 WDT+ */
    WDTplus_graceInit();

    DHT22_init();
    LCD_init();
    lcd_symbol();

    while(1)
    {
        state = DHT22_get_data();

        switch(state)
        {
            case 1:
            {
               LCD_goto(0, 0);
               LCD_putstr("No Sensor Found!");
               LCD_goto(0, 1);
               LCD_putstr("                ");
               break;
            }

            case 2:
            {
               LCD_goto(0, 0);
               LCD_putstr("Checksum Error!");
               LCD_goto(0, 1);
               LCD_putstr("               ");
               break;
            }

            default:
            {
               value =  ((values[0] * 256.0 + values[1]) * 0.1);

               LCD_goto(0, 0);
               LCD_putstr("R.H/%:       ");
               print_F(11, 0, value, 1);

               value =  ((values[2] * 256.0 + values[3]) * 0.1);

               LCD_goto(0, 1);
               LCD_putstr("T/ C :       ");
               LCD_goto(2, 1);
               LCD_send(0, DAT);
               print_F(11, 1, value, 1);

               break;
            }
        }

        delay_ms(1000);
    };
}


void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

    /* Port 1 Output Register */
    P1OUT = 0;

    /* Port 1 Direction Register */
    P1DIR = 0;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Port Select Register */
    P2SEL &= ~(BIT6 | BIT7);

    /* Port 2 Direction Register */
    P2DIR = 0;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */

}


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

    if (CALBC1_8MHZ != 0xFF)
    {
        /* Adjust this accordingly to your VCC rise time */
        __delay_cycles(100000);

        // Follow recommended flow. First, clear all DCOx and MODx bits. Then
        // apply new RSELx values. Finally, apply new DCOx and MODx bit values.
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_8MHZ;      /* Set DCO to 8MHz */
        DCOCTL = CALDCO_8MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_0 -- If XTS = 0, XT1 = 32768kHz Crystal ; If XTS = 1, XT1 = 0.4 - 1-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_0 | XCAP_1;

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}


void System_graceInit(void)
{
    /* USER CODE START (section: System_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: System_graceInit_prologue) */

    /*
     * SR, Status Register
     *
     * ~SCG1 -- Disable System clock generator 1
     * ~SCG0 -- Disable System clock generator 0
     * ~OSCOFF -- Oscillator On
     * ~CPUOFF -- CPU On
     * GIE -- General interrupt enable
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    __bis_SR_register(GIE);

    /* USER CODE START (section: System_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: System_graceInit_epilogue) */
}


void WDTplus_graceInit(void)
{
    /* USER CODE START (section: RTC_B_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: RTC_B_graceInit_prologue) */

    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * WDTHOLD -- Watchdog timer+ is stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * ~WDTTMSEL -- Watchdog mode
     * ~WDTCNTCL -- No action
     * ~WDTSSEL -- SMCLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * ~WDTIS1 -- Watchdog clock source bit1 disabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTHOLD;

    /* USER CODE START (section: RTC_B_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: RTC_B_graceInit_epilogue) */
}


void lcd_symbol(void)
{
    unsigned char s = 0;

   LCD_send(0x40, CMD);

   for(s = 0; s < 8; s++)
   {
        LCD_send(symbol[s], DAT);
   }

   LCD_send(0x80, CMD);
}


void print_C(unsigned char x_pos, unsigned char y_pos, signed int value)
{
     char ch[5] = {0x20, 0x20, 0x20, 0x20, '\0'};

     if(value < 0x00)
     {
        ch[0] = 0x2D;
        value = -value;
     }
     else
     {
        ch[0] = 0x20;
     }

     if((value > 99) && (value <= 999))
     {
         ch[1] = ((value / 100) + 0x30);
         ch[2] = (((value % 100) / 10) + 0x30);
         ch[3] = ((value % 10) + 0x30);
     }
     else if((value > 9) && (value <= 99))
     {
         ch[1] = (((value % 100) / 10) + 0x30);
         ch[2] = ((value % 10) + 0x30);
         ch[3] = 0x20;
     }
     else if((value >= 0) && (value <= 9))
     {
         ch[1] = ((value % 10) + 0x30);
         ch[2] = 0x20;
         ch[3] = 0x20;
     }

     LCD_goto(x_pos, y_pos);
     LCD_putstr(ch);
}


void print_I(unsigned char x_pos, unsigned char y_pos, signed long value)
{
    char ch[7] = {0x20, 0x20, 0x20, 0x20, 0x20, 0x20, '\0'};

    if(value < 0)
    {
        ch[0] = 0x2D;
        value = -value;
    }
    else
    {
        ch[0] = 0x20;
    }

    if(value > 9999)
    {
        ch[1] = ((value / 10000) + 0x30);
        ch[2] = (((value % 10000)/ 1000) + 0x30);
        ch[3] = (((value % 1000) / 100) + 0x30);
        ch[4] = (((value % 100) / 10) + 0x30);
        ch[5] = ((value % 10) + 0x30);
    }

    else if((value > 999) && (value <= 9999))
    {
        ch[1] = (((value % 10000)/ 1000) + 0x30);
        ch[2] = (((value % 1000) / 100) + 0x30);
        ch[3] = (((value % 100) / 10) + 0x30);
        ch[4] = ((value % 10) + 0x30);
        ch[5] = 0x20;
    }
    else if((value > 99) && (value <= 999))
    {
        ch[1] = (((value % 1000) / 100) + 0x30);
        ch[2] = (((value % 100) / 10) + 0x30);
        ch[3] = ((value % 10) + 0x30);
        ch[4] = 0x20;
        ch[5] = 0x20;
    }
    else if((value > 9) && (value <= 99))
    {
        ch[1] = (((value % 100) / 10) + 0x30);
        ch[2] = ((value % 10) + 0x30);
        ch[3] = 0x20;
        ch[4] = 0x20;
        ch[5] = 0x20;
    }
    else
    {
        ch[1] = ((value % 10) + 0x30);
        ch[2] = 0x20;
        ch[3] = 0x20;
        ch[4] = 0x20;
        ch[5] = 0x20;
    }

    LCD_goto(x_pos, y_pos);
    LCD_putstr(ch);
}


void print_D(unsigned char x_pos, unsigned char y_pos, signed int value, unsigned char points)
{
    char ch[5] = {0x2E, 0x20, 0x20, '\0'};

    ch[1] = ((value / 100) + 0x30);

    if(points > 1)
    {
        ch[2] = (((value / 10) % 10) + 0x30);

        if(points > 1)
        {
            ch[3] = ((value % 10) + 0x30);
        }
    }

    LCD_goto(x_pos, y_pos);
    LCD_putstr(ch);
}


void print_F(unsigned char x_pos, unsigned char y_pos, float value, unsigned char points)
{
    signed long tmp = 0x0000;

    tmp = value;
    print_I(x_pos, y_pos, tmp);
    tmp = ((value - tmp) * 1000);

    if(tmp < 0)
    {
       tmp = -tmp;
    }

    if(value < 0)
    {
        value = -value;
        LCD_goto(x_pos, y_pos);
        LCD_putchar(0x2D);
    }
    else
    {
        LCD_goto(x_pos, y_pos);
        LCD_putchar(0x20);
    }

    if((value >= 10000) && (value < 100000))
    {
        print_D((x_pos + 6), y_pos, tmp, points);
    }
    else if((value >= 1000) && (value < 10000))
    {
        print_D((x_pos + 5), y_pos, tmp, points);
    }
    else if((value >= 100) && (value < 1000))
    {
        print_D((x_pos + 4), y_pos, tmp, points);
    }
    else if((value >= 10) && (value < 100))
    {
        print_D((x_pos + 3), y_pos, tmp, points);
    }
    else if(value < 10)
    {
        print_D((x_pos + 2), y_pos, tmp, points);
    }
}

 

Explanation

Unlike I2C, SPI, UART and other communication methods, one wire communication has no fixed communication standard. A perfect example is the difference between the way of communicating with DHT22 and DS18B20. Although they appear to share a similar methodology but the communication protocols are different.

DHT22

Shown above is the timing diagram of DHT22. If you compare the timings for ones and zeroes in both devices you’ll notice that these timings are way different. Same goes for the data, command and control processes. Here again the datasheet of DHT22 is used to create the library for DHT22 and the process is just manipulation of a single GPIO pin.

Demo

DHT22

Software UART

Software UART is seldom needed but it comes really useful in absence of hardware UART. In some cases, we may not have the luxury of using hardware UART. Hardware UART block may also be absent. We already know that USCI module can be used for implementing hardware UART but this block is not present in chips like MSP430G2452. In such devices, we have to use software-generated UART. Software UART uses ordinary digital I/Os and delays. Both additionally and optionally external interrupts and timers can be used for better results. Owing to its hardware independency and simplicity, it is very robust. However extra coding and therefore extra memory spaces are needed.

uart_bus

Code Example

 

SW_UART.h

#include <msp430.h>
#include "delay.h"


#define SW_UART_RXD_DIR          P1DIR
#define SW_UART_TXD_DIR          P1DIR
#define SW_UART_RXD_OUT         P1OUT
#define SW_UART_TXD_OUT          P1OUT
#define SW_UART_RXD_IN           P1IN
#define SW_UART_RXD_IN_RES       P1REN

#define SW_UART_RXD_PIN          BIT1
#define SW_UART_TXD_PIN          BIT2

#define SW_UART_RXD_DIR_IN()     do{SW_UART_RXD_OUT |= SW_UART_RXD_PIN; SW_UART_RXD_DIR &= ~SW_UART_RXD_PIN; SW_UART_RXD_IN_RES |= SW_UART_RXD_PIN;}while(0)

#define SW_UART_TXD_DIR_OUT()    do{SW_UART_TXD_DIR |= SW_UART_TXD_PIN;}while(0)

#define SW_UART_TXD_OUT_HIGH()   do{SW_UART_TXD_OUT |= SW_UART_TXD_PIN;}while(0)
#define SW_UART_TXD_OUT_LOW()    do{SW_UART_TXD_OUT &= ~SW_UART_TXD_PIN;}while(0)

#define SW_UART_RXD_INPUT()     (SW_UART_RXD_IN & SW_UART_RXD_PIN)

#define baudrate                  4800
#define no_of_bits               8
#define one_bit_delay            (1000000 / baudrate)
#define half_bit_delay           (one_bit_delay / 2)


void SW_UART_init(void);
void SW_UART_transmit(unsigned char value);
unsigned char SW_UART_receive(void);

 

SW_UART.c

#include "SW_UART.h"


void SW_UART_init(void)
{
       SW_UART_TXD_DIR_OUT();
       SW_UART_RXD_DIR_IN();
       SW_UART_TXD_OUT_HIGH();
       delay_ms(10);
}


void SW_UART_transmit(unsigned char value)
{
       unsigned char bits = 0;

       SW_UART_TXD_OUT_LOW();
       delay_us(one_bit_delay);

       for(bits = 0; bits < no_of_bits; bits++)
       {
              if((value >> bits) & 0x01)
              {
                     SW_UART_TXD_OUT_HIGH();
              }

              else
              {
                     SW_UART_TXD_OUT_LOW();
              }

              delay_us(one_bit_delay);
       };

       SW_UART_TXD_OUT_HIGH();
       delay_us(one_bit_delay);
}


unsigned char SW_UART_receive(void)
{
       unsigned char bits = 0;
       unsigned char value = 0;

       while(SW_UART_RXD_INPUT());
       delay_us(one_bit_delay);
       delay_us(half_bit_delay);

       for(bits = 0; bits < no_of_bits; bits++)
       {
              if(SW_UART_RXD_INPUT())
              {
                     value += (1 << bits);
              }

              delay_us(one_bit_delay);
       };

       if(SW_UART_RXD_INPUT())
       {
              delay_us(half_bit_delay);

              return value;
       }

       else
       {
              delay_us(half_bit_delay);

              return 0;
       }
}

 

main.c

#include <msp430.h>
#include "delay.h"
#include "SW_I2C.h"
#include "PCF8574.h"
#include "lcd.h"
#include "SW_UART.h"


void GPIO_graceInit(void);
void BCSplus_graceInit(void);
void System_graceInit(void);
void WDTplus_graceInit(void);


void main(void)
{
    unsigned char rx_value = 0x00;
    unsigned char tx_value = 0x20;

    /* Stop watchdog timer from timing out during initial start-up. */
    WDTCTL = WDTPW | WDTHOLD;

    /* initialize Config for the MSP430 GPIO */
    GPIO_graceInit();

    /* initialize Config for the MSP430 2xx family clock systems (BCS) */
    BCSplus_graceInit();

    /* initialize Config for the MSP430 System Registers */
    System_graceInit();

    /* initialize Config for the MSP430 WDT+ */
    WDTplus_graceInit();

    LCD_init();
    LCD_clear_home();

    LCD_goto(0, 0);
    LCD_putstr("TXD:");
    LCD_goto(0, 1);
    LCD_putstr("RXD:");

    SW_UART_init();

    while(1)
    {
        rx_value = SW_UART_receive();
        LCD_goto(15, 0);
        LCD_putchar(rx_value);
        tx_value++;
        LCD_goto(15, 1);
        LCD_putchar(tx_value);
        SW_UART_transmit(tx_value);
        delay_ms(200);
    };
}


void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

    /* Port 1 Output Register */
    P1OUT = 0;

    /* Port 1 Direction Register */
    P1DIR = 0;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Direction Register */
    P2DIR = 0;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */

}


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

    if (CALBC1_8MHZ != 0xFF) {
        /* Adjust this accordingly to your VCC rise time */
        __delay_cycles(100000);

        // Follow recommended flow. First, clear all DCOx and MODx bits. Then
        // apply new RSELx values. Finally, apply new DCOx and MODx bit values.
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_8MHZ;      /* Set DCO to 8MHz */
        DCOCTL = CALDCO_8MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_0 -- If XTS = 0, XT1 = 32768kHz Crystal ; If XTS = 1, XT1 = 0.4 - 1-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_0 | XCAP_1;

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}


void System_graceInit(void)
{
    /* USER CODE START (section: System_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: System_graceInit_prologue) */

    /*
     * SR, Status Register
     *
     * ~SCG1 -- Disable System clock generator 1
     * ~SCG0 -- Disable System clock generator 0
     * ~OSCOFF -- Oscillator On
     * ~CPUOFF -- CPU On
     * GIE -- General interrupt enable
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    __bis_SR_register(GIE);

    /* USER CODE START (section: System_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: System_graceInit_epilogue) */
}


void WDTplus_graceInit(void)
{
    /* USER CODE START (section: RTC_B_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: RTC_B_graceInit_prologue) */

    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * WDTHOLD -- Watchdog timer+ is stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * ~WDTTMSEL -- Watchdog mode
     * ~WDTCNTCL -- No action
     * ~WDTSSEL -- SMCLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * ~WDTIS1 -- Watchdog clock source bit1 disabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTHOLD;

    /* USER CODE START (section: RTC_B_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: RTC_B_graceInit_epilogue) */
}

 

Simulation

sim

Explanation

Software UART is created with digital I/Os. Thus, the very first task we need to do is to initialize these pins. The SW_UART header file states which pins and ports are used. So, you only need to set these first. All of my codes are modular and so once you set these properly the functions and the definitions associated take care of other tasks. This, in turn, makes the codes easily to use and ready for quick modifications/deployments.

#define SW_UART_RXD_DIR                 P1DIR
#define SW_UART_TXD_DIR                 P1DIR
#define SW_UART_RXD_OUT                 P1OUT
#define SW_UART_TXD_OUT                 P1OUT
#define SW_UART_RXD_IN                  P1IN
#define SW_UART_RXD_IN_RES              P1REN

#define SW_UART_RXD_PIN                 BIT1
#define SW_UART_TXD_PIN                 BIT2

Once the pins are set as per requirement, it is needed to initialize them for software UART functionality.

void SW_UART_init(void)
{
       SW_UART_TXD_DIR_OUT();
       SW_UART_RXD_DIR_IN();
       SW_UART_TXD_OUT_HIGH();
       delay_ms(10);
}

The header file also states the communication baud rate and number of bits:

#define baudrate                  4800
#define no_of_bits                8
#define one_bit_delay            (1000000 / baudrate)
#define half_bit_delay           (one_bit_delay / 2)

Based on the baud rate further timing infos are calculated. Software UART is not as reliable as hardware UART and so it is better to use low baud rates. It is even better if it can be skipped. However, when there is no other option or when there is a need for additional UART, it must be used.

The UART transmit and receive functions are written using polling methods. External digital I/O interrupt can be used for receiving data. These functions are created just by studying the signal patterns and using the same tactics as with other software communication libraries. The trick is to emulate/receive the signals as a real hardware would do.

void SW_UART_transmit(unsigned char value)
{
       unsigned char bits = 0;

       SW_UART_TXD_OUT_LOW();
       delay_us(one_bit_delay);

       for(bits = 0; bits < no_of_bits; bits++)
       {
              if((value >> bits) & 0x01)
              {
                     SW_UART_TXD_OUT_HIGH();
              }

              else
              {
                     SW_UART_TXD_OUT_LOW();
              }

              delay_us(one_bit_delay);
       };

       SW_UART_TXD_OUT_HIGH();
       delay_us(one_bit_delay);
}

unsigned char SW_UART_receive(void)
{
       unsigned char bits = 0;
       unsigned char value = 0;

       while(SW_UART_RXD_INPUT());
       delay_us(one_bit_delay);
       delay_us(half_bit_delay);

       for(bits = 0; bits < no_of_bits; bits++)
       {
              if(SW_UART_RXD_INPUT())
              {
                     value += (1 << bits);
              }

              delay_us(one_bit_delay);
       };

       if(SW_UART_RXD_INPUT())
       {
              delay_us(half_bit_delay);

              return value;
       }

       else
       {
              delay_us(half_bit_delay);

              return 0;
       }
}

 

Demo

UART (2) UART (1)

USCI SPI – Interfacing MPL115A1 Atmospheric Pressure Sensor

We have already seen that MSP430’s USI module can be used to implement both I2C and SPI communication platforms. However, there will be times we will have to use USCI modules. USCI is a bit complicated and is a bit difficult to use in simple terms. Here in this article, however, I kept things simple and projected ways to use this module simply. Four examples of USCI module in I2C and SPI modes will be presented in this article. The first one will demo how to interface a MSP430 device with a MPL115A1 atmospheric pressure sensor in a full-duplex SPI bus. Full-duplex SPI bus is needed the most when interfacing sensors, RTCs, SPI-based memory chips, SD cards, etc.

Block

 

Code Example

 

HW_SPI.h

#include <msp430.h>


void HW_SPI_init(void);
void SPI_write(unsigned char tx_data);
unsigned char SPI_read(void);
unsigned char SPI_transfer(unsigned char tx_data);

 

HW_SPI.c

#include "HW_SPI.h"


void HW_SPI_init(void)
{
       UCA0CTL1 |= UCSWRST;
       UCA0CTL0 = UCCKPH | UCMSB | UCMST | UCMODE_1 | UCSYNC;
       UCA0CTL1 = UCSSEL_2 | UCSWRST;
       UCA0BR0 = 8;
       UCA0CTL1 &= ~UCSWRST;
}


void SPI_write(unsigned char tx_data)
{
       while(!(IFG2 & UCA0TXIFG));
       UCA0TXBUF = tx_data;
       while(UCA0STAT & UCBUSY);
}


unsigned char SPI_read(void)
{
       unsigned char rx_data = 0;

       while(!(IFG2 & UCA0RXIFG));
       rx_data = UCA0RXBUF;
       while(UCA0STAT & UCBUSY);

       return rx_data;
}


unsigned char SPI_transfer(unsigned char tx_data)
{
       unsigned char rx_data = 0;

       while(!(IFG2 & UCA0TXIFG));
       UCA0TXBUF = tx_data;
       while(UCA0STAT & UCBUSY);

       while(!(IFG2 & UCA0RXIFG));
       rx_data = UCA0RXBUF;
       while(UCA0STAT & UCBUSY);

       return rx_data;
}

 

MPL115A1.h

#include <msp430.h>
#include "delay.h"
#include "HW_SPI.h"


#define LOW                                              0
#define HIGH                                             1

#define PRESH                                         0x80
#define PRESL                                         0x82
#define TEMPH                                         0x84
#define TEMPL                                         0x86

#define A0_H                                          0x88                        
#define A0_L                                          0x8A
#define B1_H                                          0x8C
#define B1_L                                          0x8E
#define B2_H                                          0x90            
#define B2_L                                          0x92
#define C12_H                                         0x94
#define C12_L                                         0x96                   

#define conv_cmd                                      0x24            

#define MPL115A1_CSN_PORT_OUT                         P2OUT
#define MPL115A1_SDN_PORT_OUT                         P2OUT

#define MPL115A1_CSN_PORT_DIR                         P2DIR
#define MPL115A1_SDN_PORT_DIR                         P2DIR

#define MPL115A1_SDN_pin                              BIT0
#define MPL115A1_CSN_pin                              BIT1

#define MPL115A1_SDN_HIGH()                           P2OUT |= MPL115A1_SDN_pin
#define MPL115A1_SDN_LOW()                            P2OUT &= ~MPL115A1_SDN_pin  
#define MPL115A1_CSN_HIGH()                           P2OUT |= MPL115A1_CSN_pin
#define MPL115A1_CSN_LOW()                            P2OUT &= ~MPL115A1_CSN_pin  


struct
{
  float A0;
  float B1;
  float B2;
  float C12;
}coefficients;


void MPL115A1_init(void);
unsigned char MPL115A1_read(unsigned char address);
void MPL115A1_write(unsigned char address, unsigned char value);
void MPL115A1_get_coefficients(void);
void MPL115A1_get_bytes(unsigned int *hb, unsigned int *lb, unsigned char address);
void MPL115A1_get_data(float *pres, float *temp);

 

MPL115A1.c

#include "MPL115A1.h"


void MPL115A1_init(void)
{
  MPL115A1_SDN_PORT_DIR |= MPL115A1_SDN_pin;
  MPL115A1_CSN_PORT_DIR |= MPL115A1_CSN_pin;

  MPL115A1_SDN_HIGH();
  MPL115A1_CSN_HIGH();
  HW_SPI_init();
  MPL115A1_get_coefficients();
}      


unsigned char MPL115A1_read(unsigned char address)
{
  unsigned char value = 0;

  MPL115A1_CSN_LOW();
  delay_ms(3);
  SPI_write(address);
  value = SPI_read();

  value = SPI_transfer(address);
  MPL115A1_CSN_HIGH();

  return value;       
}                                          


void MPL115A1_write(unsigned char address, unsigned char value)
{                                                    
  MPL115A1_CSN_LOW();
  delay_ms(3);
  SPI_write((address & 0x7F));
  SPI_write(value);
  MPL115A1_CSN_HIGH();
}


void MPL115A1_get_coefficients(void)
{    
  unsigned int hb = 0;
  unsigned int lb = 0;

  MPL115A1_get_bytes(&hb, &lb, A0_H);
  coefficients.A0 = ((hb << 5) + (lb >> 3) + ((lb & 0x07) / 8.0));

  MPL115A1_get_bytes(&hb, &lb, B1_H);             
  coefficients.B1 = (((((hb & 0x1F) * 0x0100) + lb) / 8192.0) - 3.0);

  MPL115A1_get_bytes(&hb, &lb, B2_H);   
  coefficients.B2 = (((((hb - 0x80) << 8) + lb) / 16384.0) - 2.0);

  MPL115A1_get_bytes(&hb, &lb, C12_H);                
  coefficients.C12 = (((hb * 0x100) + lb) / 16777216.0);


void MPL115A1_get_bytes(unsigned int *hb, unsigned int *lb, unsigned char address)
{
  *hb = ((unsigned int)MPL115A1_read(address));
  *lb = ((unsigned int)MPL115A1_read((address + 2)));
}


void MPL115A1_get_data(float *pres, float *temp)  
{  
   unsigned int hb = 0;
   unsigned int lb = 0;

   signed long Padc = 0;
   signed long Tadc = 0;

   MPL115A1_write(conv_cmd, 0);

   MPL115A1_get_bytes(&hb, &lb, PRESH);
   Padc = (((hb << 8) + lb) >> 6);

   MPL115A1_get_bytes(&hb, &lb, TEMPH);
   Tadc = (((hb << 8) + lb) >> 6);

   *pres = ( coefficients.A0 + (( coefficients.B1 + ( coefficients.C12 * Tadc)) * Padc) + ( coefficients.B2 * Tadc));
   *pres = (((*pres * 65.0) / 1023.0) + 50.0);     

   *temp = (30.0 + ((Tadc - 472) / (-5.35)));
}

 

main.c

#include <msp430.h>
#include "delay.h"
#include "HW_SPI.h"
#include "SW_I2C.h"
#include "PCF8574.h"
#include "lcd.h"
#include "MPL115A1.h"


const unsigned char symbol[8] =
{
   0x00, 0x06, 0x09, 0x09, 0x06, 0x00, 0x00, 0x00
};


void GPIO_graceInit(void);
void BCSplus_graceInit(void);
void USCI_A0_graceInit(void);
void System_graceInit(void);
void WDTplus_graceInit(void);
void lcd_symbol(void);
void print_C(unsigned char x_pos, unsigned char y_pos, signed int value);
void print_I(unsigned char x_pos, unsigned char y_pos, signed long value);
void print_D(unsigned char x_pos, unsigned char y_pos, signed int value, unsigned char points);
void print_F(unsigned char x_pos, unsigned char y_pos, float value, unsigned char points);


void main(void)
  {
    float t = 0.0;
    float p = 0.0;

    /* Stop watchdog timer from timing out during initial start-up. */
    WDTCTL = WDTPW | WDTHOLD;

    /* initialize Config for the MSP430 GPIO */
    GPIO_graceInit();

    /* initialize Config for the MSP430 2xx family clock systems (BCS) */
    BCSplus_graceInit();

    /* initialize Config for the MSP430 USCI_A0 */
    USCI_A0_graceInit();

    /* initialize Config for the MSP430 System Registers */
    System_graceInit();

    /* initialize Config for the MSP430 WDT+ */
    WDTplus_graceInit();

    LCD_init();
    LCD_clear_home();
    lcd_symbol();

    LCD_goto(0, 0);
    LCD_putstr("P/kPa:");
    LCD_goto(0, 1);
    LCD_putstr("T/ C :");
    LCD_goto(2, 1);
    LCD_send(0, DAT);

    MPL115A1_init();

    while(1)
    {
        MPL115A1_get_data(&p, &t);
        print_F(10, 0, p, 1);
        print_F(11, 1, t, 1);
        delay_ms(400);
    };
}


void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

    /* Port 1 Port Select 2 Register */
    P1SEL2 = BIT1 | BIT2 | BIT4;

    /* Port 1 Output Register */
    P1OUT = 0;

    /* Port 1 Port Select Register */
    P1SEL = BIT1 | BIT2 | BIT4;

    /* Port 1 Direction Register */
    P1DIR = 0;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Direction Register */
    P2DIR = BIT0 | BIT1;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* Port 3 Output Register */
    P3OUT = 0;

    /* Port 3 Direction Register */
    P3DIR = 0;

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */

}


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

    if (CALBC1_8MHZ != 0xFF)
    {
        /* Adjust this accordingly to your VCC rise time */
        __delay_cycles(100000);

        // Follow recommended flow. First, clear all DCOx and MODx bits. Then
        // apply new RSELx values. Finally, apply new DCOx and MODx bit values.
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_8MHZ;      /* Set DCO to 8MHz */
        DCOCTL = CALDCO_8MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_0 -- If XTS = 0, XT1 = 32768kHz Crystal ; If XTS = 1, XT1 = 0.4 - 1-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_0 | XCAP_1;

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}


void USCI_A0_graceInit(void)
{
    /* USER CODE START (section: USCI_A0_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: USCI_A0_graceInit_prologue) */

    /* Disable USCI */
    /* Disable USCI */
    UCA0CTL1 |= UCSWRST;

    /*
     * Control Register 0
     *
     * UCCKPH -- Data is captured on the first UCLK edge and changed on the following edge
     * ~UCCKPL -- Inactive state is low
     * UCMSB -- MSB first
     * ~UC7BIT -- 8-bit
     * UCMST -- Master mode
     * UCMODE_1 -- 4-Pin SPI with UCxSTE active high: slave enabled when UCxSTE = 1
     * UCSYNC -- Synchronous Mode
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    UCA0CTL0 = UCCKPH | UCMSB | UCMST | UCMODE_1 | UCSYNC;

    /*
     * Control Register 1
     *
     * UCSSEL_2 -- SMCLK
     * UCSWRST -- Enabled. USCI logic held in reset state
     */
    UCA0CTL1 = UCSSEL_2 | UCSWRST;

    /* Bit Rate Control Register 0 */
    UCA0BR0 = 8;

    /* Enable USCI */
    UCA0CTL1 &= ~UCSWRST;

    /* USER CODE START (section: USCI_A0_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: USCI_A0_graceInit_epilogue) */
}


void System_graceInit(void)
{
    /* USER CODE START (section: System_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: System_graceInit_prologue) */

    /*
     * SR, Status Register
     *
     * ~SCG1 -- Disable System clock generator 1
     * ~SCG0 -- Disable System clock generator 0
     * ~OSCOFF -- Oscillator On
     * ~CPUOFF -- CPU On
     * GIE -- General interrupt enable
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    __bis_SR_register(GIE);

    /* USER CODE START (section: System_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: System_graceInit_epilogue) */
}


void WDTplus_graceInit(void)
{
    /* USER CODE START (section: RTC_B_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: RTC_B_graceInit_prologue) */

    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * WDTHOLD -- Watchdog timer+ is stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * ~WDTTMSEL -- Watchdog mode
     * ~WDTCNTCL -- No action
     * ~WDTSSEL -- SMCLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * ~WDTIS1 -- Watchdog clock source bit1 disabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTHOLD;

    /* USER CODE START (section: RTC_B_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: RTC_B_graceInit_epilogue) */
}


void lcd_symbol(void)
{
    unsigned char s = 0;

   LCD_send(0x40, CMD);

   for(s = 0; s < 8; s++)
   {
        LCD_send(symbol[s], DAT);
   }

   LCD_send(0x80, CMD);
}


void print_C(unsigned char x_pos, unsigned char y_pos, signed int value)
{
     char ch[5] = {0x20, 0x20, 0x20, 0x20, '\0'};

     if(value < 0x00)
     {
        ch[0] = 0x2D;
        value = -value;
     }
     else
     {
        ch[0] = 0x20;
     }

     if((value > 99) && (value <= 999))
     {
         ch[1] = ((value / 100) + 0x30);
         ch[2] = (((value % 100) / 10) + 0x30);
         ch[3] = ((value % 10) + 0x30);
     }
     else if((value > 9) && (value <= 99))
     {
         ch[1] = (((value % 100) / 10) + 0x30);
         ch[2] = ((value % 10) + 0x30);
         ch[3] = 0x20;
     }
     else if((value >= 0) && (value <= 9))
     {
         ch[1] = ((value % 10) + 0x30);
         ch[2] = 0x20;
         ch[3] = 0x20;
     }

     LCD_goto(x_pos, y_pos);
     LCD_putstr(ch);
}


void print_I(unsigned char x_pos, unsigned char y_pos, signed long value)
{
    char ch[7] = {0x20, 0x20, 0x20, 0x20, 0x20, 0x20, '\0'};

    if(value < 0)
    {
        ch[0] = 0x2D;
        value = -value;
    }
    else
    {
        ch[0] = 0x20;
    }

    if(value > 9999)
    {
        ch[1] = ((value / 10000) + 0x30);
        ch[2] = (((value % 10000)/ 1000) + 0x30);
        ch[3] = (((value % 1000) / 100) + 0x30);
        ch[4] = (((value % 100) / 10) + 0x30);
        ch[5] = ((value % 10) + 0x30);
    }

    else if((value > 999) && (value <= 9999))
    {
        ch[1] = (((value % 10000)/ 1000) + 0x30);
        ch[2] = (((value % 1000) / 100) + 0x30);
        ch[3] = (((value % 100) / 10) + 0x30);
        ch[4] = ((value % 10) + 0x30);
        ch[5] = 0x20;
    }
    else if((value > 99) && (value <= 999))
    {
        ch[1] = (((value % 1000) / 100) + 0x30);
        ch[2] = (((value % 100) / 10) + 0x30);
        ch[3] = ((value % 10) + 0x30);
        ch[4] = 0x20;
        ch[5] = 0x20;
    }
    else if((value > 9) && (value <= 99))
    {
        ch[1] = (((value % 100) / 10) + 0x30);
        ch[2] = ((value % 10) + 0x30);
        ch[3] = 0x20;
        ch[4] = 0x20;
        ch[5] = 0x20;
    }
    else
    {
        ch[1] = ((value % 10) + 0x30);
        ch[2] = 0x20;
        ch[3] = 0x20;
        ch[4] = 0x20;
        ch[5] = 0x20;
    }

    LCD_goto(x_pos, y_pos);
    LCD_putstr(ch);
}


void print_D(unsigned char x_pos, unsigned char y_pos, signed int value, unsigned char points)
{
    char ch[5] = {0x2E, 0x20, 0x20, '\0'};

    ch[1] = ((value / 100) + 0x30);

    if(points > 1)
    {
        ch[2] = (((value / 10) % 10) + 0x30);

        if(points > 1)
        {
            ch[3] = ((value % 10) + 0x30);
        }
    }

    LCD_goto(x_pos, y_pos);
    LCD_putstr(ch);
}


void print_F(unsigned char x_pos, unsigned char y_pos, float value, unsigned char points)
{
    signed long tmp = 0x0000;

    tmp = value;
    print_I(x_pos, y_pos, tmp);
    tmp = ((value - tmp) * 1000);

    if(tmp < 0)
    {
       tmp = -tmp;
    }

    if(value < 0)
    {
        value = -value;
        LCD_goto(x_pos, y_pos);
        LCD_putchar(0x2D);
    }
    else
    {
        LCD_goto(x_pos, y_pos);
        LCD_putchar(0x20);
    }

    if((value >= 10000) && (value < 100000))
    {
        print_D((x_pos + 6), y_pos, tmp, points);
    }
    else if((value >= 1000) && (value < 10000))
    {
        print_D((x_pos + 5), y_pos, tmp, points);
    }
    else if((value >= 100) && (value < 1000))
    {
        print_D((x_pos + 4), y_pos, tmp, points);
    }
    else if((value >= 10) && (value < 100))
    {
        print_D((x_pos + 3), y_pos, tmp, points);
    }
    else if(value < 10)
    {
        print_D((x_pos + 2), y_pos, tmp, points);
    }
}

 

Simulation

The model for MPL115A1 is not available in Proteus VSM and so it cannot be simulated. Only the pinouts are shown in the schematic below.

USCI_SPI_MPL115A1

Explanation

HW_SPI.h and HW_SPI.c files describe the functionality of USCI-SPI hardware.

void HW_SPI_init(void);
void SPI_write(unsigned char tx_data);
unsigned char SPI_read(void);
unsigned char SPI_transfer(unsigned char tx_data);

The first function as shown above initiates the USCI-SPI hardware. The initialization is generated using Grace. Note the Grace screenshot below:

USCI_A0_initialization

These settings describe which pins are being used, their purposes, communication speed, SPI mode, clock polarity and so on. Note the slave select pin is not shown here as we have used a different pin for that purpose.

SPI_read, SPI_write and SPI_transfer functions do their jobs as per their namings.

Now let’s get inside the read and write function of USCI-SPI

void SPI_write(unsigned char tx_data)
{
       while(!(IFG2 & UCA0TXIFG));
       UCA0TXBUF = tx_data;
       while(UCA0STAT & UCBUSY);
}

 

unsigned char SPI_read(void)
{
       unsigned char rx_data = 0;

 

       while(!(IFG2 & UCA0RXIFG));
       rx_data = UCA0RXBUF;
       while(UCA0STAT & UCBUSY);

 

       return rx_data;
}

 

unsigned char SPI_transfer(unsigned char tx_data)
{
       unsigned char rx_data = 0;

 

       while(!(IFG2 & UCA0TXIFG));
       UCA0TXBUF = tx_data;
       while(UCA0STAT & UCBUSY);

 

       while(!(IFG2 & UCA0RXIFG));
       rx_data = UCA0RXBUF;
       while(UCA0STAT & UCBUSY);

 

       return rx_data;
}

The SPI read and write processes are simplest to understand. Before starting communication, respective data transaction interrupt flags are polled. Note that these flags are needed even if we don’t use USCI interrupts. Once polled okay, data is sent from MSP430 device in SPI write mode or received while in read mode. Then we have to check if data has been fully received/transmitted by asserting the USCI busy flag. Lastly the USCI SPI transfer function is a mixture of both SPI read and write functions.

The code here is used to read a MPL115A1 barometric pressure sensor and display atmospheric pressure-temperature data. MPL115A1 uses full-duplex SPI communication medium to communicate with its host device and here MSP430’s USCI_A0 in SPI mode is employed to achieved that.

Demo

MPL115A1

USCI SPI – Interfacing SSD1306 OLED Display

SPI is perhaps best known for the communication speed it offers. This raw communication speed is most needed when we need to interface external memories and smart displays like TFT displays and OLED displays. Here, we will see how to interface a SSD1306 OLED display with MSP430 using half-duplex or unidirectional USCI-based SPI communication bus.

OLED

Code Example

 

HW_SPI.h

#include <msp430.h>


void HW_SPI_init(void);
void SPI_write(unsigned char tx_data);
unsigned char SPI_read(void);
unsigned char SPI_transfer(unsigned char tx_data);

 

HW_SPI.c

#include "HW_SPI.h"


void HW_SPI_init(void)
{
  UCB0CTL1 |= UCSWRST;
  UCB0CTL0 = UCCKPH | UCMSB | UCMST | UCMODE_1 | UCSYNC;
  UCB0CTL1 = UCSSEL_2;
  UCB0BR0 = 8;
  UCB0BR1 = 0;
  UCB0CTL1 &= ~UCSWRST;
}


void SPI_write(unsigned char tx_data)
{
  while(!(IFG2 & UCB0TXIFG));
  UCB0TXBUF = tx_data;
  while(UCB0STAT & UCBUSY);
}


unsigned char SPI_read(void)
{
  unsigned char rx_data = 0;

  while(!(IFG2 & UCB0RXIFG));
  rx_data = UCB0RXBUF;
  while(UCB0STAT & UCBUSY);

  return rx_data;
}


unsigned char SPI_transfer(unsigned char tx_data)
{
  unsigned char rx_data = 0;

  while(!(IFG2 & UCB0TXIFG));
  UCB0TXBUF = tx_data;
  while(UCB0STAT & UCBUSY);

  while(!(IFG2 & UCB0RXIFG));
  rx_data = UCB0RXBUF;
  while(UCB0STAT & UCBUSY);

  return rx_data;
}

 

SSD1306.h

#include <MSP430.h>
#include "delay.h"
#include "HW_SPI.h"


#define OLED_PORT                              P1OUT
#define OLED_DIR                               P1DIR

#define RST_pin                                BIT2
#define DC_pin                                 BIT3
#define CS_pin                                 BIT4

#define OLED_PORT_OUT()                        OLED_DIR |= (RST_pin | DC_pin | CS_pin)
#define OLED_RST_HIGH()                        OLED_PORT |= RST_pin
#define OLED_RST_LOW()                         OLED_PORT &= ~RST_pin
#define OLED_DC_HIGH()                         OLED_PORT |= DC_pin
#define OLED_DC_LOW()                          OLED_PORT &= ~DC_pin
#define OLED_CS_HIGH()                         OLED_PORT |= CS_pin
#define OLED_CS_LOW()                          OLED_PORT &= ~CS_pin

#define Set_Lower_Column_Start_Address_CMD    0x00
#define Set_Higher_Column_Start_Address_CMD   0x10
#define Set_Memory_Addressing_Mode_CMD        0x20
#define Set_Column_Address_CMD                0x21
#define Set_Page_Address_CMD                  0x22
#define Set_Display_Start_Line_CMD            0x40
#define Set_Contrast_Control_CMD              0x81
#define Set_Charge_Pump_CMD                   0x8D
#define Set_Segment_Remap_CMD                 0xA0
#define Set_Entire_Display_ON_CMD             0xA4
#define Set_Normal_or_Inverse_Display_CMD     0xA6
#define Set_Multiplex_Ratio_CMD               0xA8
#define Set_Display_ON_or_OFF_CMD             0xAE
#define Set_Page_Start_Address_CMD            0xB0
#define Set_COM_Output_Scan_Direction_CMD     0xC0
#define Set_Display_Offset_CMD                0xD3
#define Set_Display_Clock_CMD                 0xD5
#define Set_Pre_charge_Period_CMD             0xD9
#define Set_Common_HW_Config_CMD              0xDA
#define Set_VCOMH_Level_CMD                   0xDB
#define Set_NOP_CMD                           0xE3
#define Horizontal_Addressing_Mode            0x00
#define Vertical_Addressing_Mode              0x01
#define Page_Addressing_Mode                  0x02
#define Disable_Charge_Pump                   0x00
#define Enable_Charge_Pump                    0x04                           
#define Column_Address_0_Mapped_to_SEG0       0x00
#define Column_Address_0_Mapped_to_SEG127     0x01
#define Normal_Display                        0x00
#define Entire_Display_ON                     0x01 
#define Non_Inverted_Display                  0x00
#define Inverted_Display                      0x01     
#define Display_OFF                           0x00
#define Display_ON                            0x01
#define Scan_from_COM0_to_63                  0x00
#define Scan_from_COM63_to_0                  0x08

#define x_size                                128
#define x_max                                 x_size
#define x_min                                 0
#define y_size                                64
#define y_max                                 8
#define y_min                                 0

#define ON                                    1
#define OFF                                   0

#define YES                                   1
#define NO                                    0

#define HIGH                                  1
#define LOW                                   0

#define DAT                                   1
#define CMD                                   0


void setup_GPIOs(void);
void OLED_init(void);
void OLED_reset_sequence(void);
void OLED_write(unsigned char value, unsigned char type);
void OLED_gotoxy(unsigned char x_pos, unsigned char y_pos);
void OLED_fill(unsigned char bmp_data);
void OLED_clear_screen(void);
void OLED_cursor(unsigned char x_pos, unsigned char y_pos);
void OLED_print_char(unsigned char x_pos, unsigned char y_pos, unsigned char ch);
void OLED_print_string(unsigned char x_pos, unsigned char y_pos, unsigned char *ch);
void OLED_print_chr(unsigned char x_pos, unsigned char y_pos, signed int value);
void OLED_print_int(unsigned char x_pos, unsigned char y_pos, signed long value);
void OLED_print_decimal(unsigned char x_pos, unsigned char y_pos, unsigned int value, unsigned char points);
void OLED_print_float(unsigned char x_pos, unsigned char y_pos, float value, unsigned char points);

 

SSD1306.c

#include "SSD1306.h"
#include "fonts.h"


void setup_GPIOs(void)
{
    OLED_PORT_OUT();
    P1SEL2 = BIT5 | BIT7;
    P1SEL = BIT5 | BIT7;
}


void OLED_init(void)
{
    setup_GPIOs();
    HW_SPI_init();

    OLED_reset_sequence();

    OLED_write((Set_Display_ON_or_OFF_CMD + Display_OFF), CMD);

    OLED_write(Set_Display_Clock_CMD, CMD);
    OLED_write(0x80, CMD);

    OLED_write(Set_Multiplex_Ratio_CMD, CMD);
    OLED_write(0x3F, CMD);

    OLED_write(Set_Display_Offset_CMD, CMD);
    OLED_write(0x00, CMD);

    OLED_write((Set_Display_Start_Line_CMD | 0x00), CMD);

    OLED_write(Set_Charge_Pump_CMD, CMD);
    OLED_write((Set_Higher_Column_Start_Address_CMD | Enable_Charge_Pump), CMD);

    OLED_write(Set_Memory_Addressing_Mode_CMD, CMD);
    OLED_write(Page_Addressing_Mode, CMD);

    OLED_write((Set_Segment_Remap_CMD | Column_Address_0_Mapped_to_SEG127), CMD);

    OLED_write((Set_COM_Output_Scan_Direction_CMD | Scan_from_COM63_to_0), CMD);

    OLED_write(Set_Common_HW_Config_CMD, CMD);
    OLED_write(0x12, CMD);

    OLED_write(Set_Contrast_Control_CMD, CMD);
    OLED_write(0xCF, CMD);

    OLED_write(Set_Pre_charge_Period_CMD, CMD);
    OLED_write(0xF1, CMD);

    OLED_write(Set_VCOMH_Level_CMD, CMD);
    OLED_write(0x40, CMD);

    OLED_write((Set_Entire_Display_ON_CMD | Normal_Display), CMD);

    OLED_write((Set_Normal_or_Inverse_Display_CMD | Non_Inverted_Display), CMD);

    OLED_write((Set_Display_ON_or_OFF_CMD + Display_ON) , CMD);

    OLED_gotoxy(0, 0);

    OLED_clear_screen();
}


void OLED_reset_sequence(void)
{
    delay_ms(40);
    OLED_RST_LOW();
    delay_ms(40);
    OLED_RST_HIGH();
}


void OLED_write(unsigned char value, unsigned char type)
{
    switch(type)
    {
        case DAT:
        {
            OLED_DC_HIGH();
            break;
        }
        case CMD:
        {
            OLED_DC_LOW();
            break;
        }
    }

    OLED_CS_LOW();
    SPI_transfer(value);
    OLED_CS_HIGH();


void OLED_gotoxy(unsigned char x_pos, unsigned char y_pos)
{                                   
    OLED_write((Set_Page_Start_Address_CMD + y_pos), CMD);
    OLED_write(((x_pos & 0x0F) | Set_Lower_Column_Start_Address_CMD), CMD);
    OLED_write((((x_pos & 0xF0) >> 0x04) | Set_Higher_Column_Start_Address_CMD), CMD);
}


void OLED_fill(unsigned char bmp_data)
{                                                    
    unsigned char x_pos = 0;
    unsigned char page = 0;

    for(page = y_min; page < y_max; page++)
    {
        OLED_write((Set_Page_Start_Address_CMD + page), CMD);
        OLED_write(Set_Lower_Column_Start_Address_CMD, CMD);
        OLED_write(Set_Higher_Column_Start_Address_CMD, CMD);

        for(x_pos = x_min; x_pos < x_max; x_pos++)
        {
            OLED_write(bmp_data, DAT);
        }
    }
}


void OLED_clear_screen(void)
{
    OLED_fill(0x00);
}


void OLED_cursor(unsigned char x_pos, unsigned char y_pos)
{        
    unsigned char i = 0;

    if(y_pos != 0)
    {
        if(x_pos == 1)
        {
            OLED_gotoxy(0x00, (y_pos + 0x02));
        }
        else
        {
            OLED_gotoxy((0x50 + ((x_pos - 0x02) * 0x06)), (y_pos + 0x02));
        }

        for(i = 0; i < 6; i++)
        {
            OLED_write(0xFF, DAT);
        }
    }
}


void OLED_print_char(unsigned char x_pos, unsigned char y_pos, unsigned char ch)
{
    unsigned char chr = 0;
    unsigned char s = 0;

    chr = (ch - 32);

    if(x_pos > (x_max - 6))
    {
        x_pos = 0;
        y_pos++;
    }

    OLED_gotoxy(x_pos, y_pos);

    for(s = 0; s < 6; s++)
    {
        OLED_write(font_regular[chr][s], DAT);
    }
}


void OLED_print_string(unsigned char x_pos, unsigned char y_pos, unsigned char *ch)
{
    unsigned char chr = 0;
    unsigned char i = 0;
    unsigned char j = 0;

    while(ch[j] != '\0')
    {
        chr = (ch[j] - 32);

        if(x_pos > (x_max - 0x06))
        {
            x_pos = 0x00;
            y_pos++;
        }
        OLED_gotoxy(x_pos, y_pos);

        for(i = 0; i < 6; i++)
        {
            OLED_write(font_regular[chr][i], DAT);
        }

        j++;
        x_pos += 6;
     }
}   


void OLED_print_chr(unsigned char x_pos, unsigned char y_pos, signed int value)
{                                            
    unsigned char ch = 0;

    if(value < 0)
    {
        OLED_print_char(x_pos, y_pos, '-');
        value = -value;
    }
    else
    {
        OLED_print_char(x_pos, y_pos,' ');
    }

     if((value > 99) && (value <= 999))
     {
         ch = (value / 100);
         OLED_print_char((x_pos + 6), y_pos , (0x30 + ch));
         ch = ((value % 100) / 10);
         OLED_print_char((x_pos + 12), y_pos , (0x30 + ch));
         ch = (value % 10);
         OLED_print_char((x_pos + 18), y_pos , (0x30 + ch));
     }
     else if((value > 9) && (value <= 99))
     {
         ch = ((value % 100) / 10);
         OLED_print_char((x_pos + 6), y_pos , (0x30 + ch));
         ch = (value % 10);
         OLED_print_char((x_pos + 12), y_pos , (0x30 + ch));
         OLED_print_char((x_pos + 18), y_pos , 0x20);
     }
     else if((value >= 0) && (value <= 9))
     {
         ch = (value % 10);
         OLED_print_char((x_pos + 6), y_pos , (0x30 + ch));
         OLED_print_char((x_pos + 12), y_pos , 0x20);
         OLED_print_char((x_pos + 18), y_pos , 0x20);
     }
}


void OLED_print_int(unsigned char x_pos, unsigned char y_pos, signed long value)
{
    unsigned char ch = 0;

    if(value < 0)
    {
        OLED_print_char(x_pos, y_pos, '-');
        value = -value;
    }
    else
    {
        OLED_print_char(x_pos, y_pos,' ');
    }

    if(value > 9999)
    {
        ch = (value / 10000);
        OLED_print_char((x_pos + 6), y_pos , (0x30 + ch));

        ch = ((value % 10000)/ 1000);
        OLED_print_char((x_pos + 12), y_pos , (0x30 + ch));

        ch = ((value % 1000) / 100);
        OLED_print_char((x_pos + 18), y_pos , (0x30 + ch));

        ch = ((value % 100) / 10);
        OLED_print_char((x_pos + 24), y_pos , (0x30 + ch));

        ch = (value % 10);
        OLED_print_char((x_pos + 30), y_pos , (0x30 + ch));
    }

    else if((value > 999) && (value <= 9999))
    {
        ch = ((value % 10000)/ 1000);
        OLED_print_char((x_pos + 6), y_pos , (0x30 + ch));

        ch = ((value % 1000) / 100);
        OLED_print_char((x_pos + 12), y_pos , (0x30 + ch));

        ch = ((value % 100) / 10);
        OLED_print_char((x_pos + 18), y_pos , (0x30 + ch));

        ch = (value % 10);
        OLED_print_char((x_pos + 24), y_pos , (0x30 + ch));
        OLED_print_char((x_pos + 30), y_pos , 0x20);
    }
    else if((value > 99) && (value <= 999))
    {
        ch = ((value % 1000) / 100);
        OLED_print_char((x_pos + 6), y_pos , (0x30 + ch));

        ch = ((value % 100) / 10);
        OLED_print_char((x_pos + 12), y_pos , (0x30 + ch));

        ch = (value % 10);
        OLED_print_char((x_pos + 18), y_pos , (0x30 + ch));
        OLED_print_char((x_pos + 24), y_pos , 0x20);
        OLED_print_char((x_pos + 30), y_pos , 0x20);
    }
    else if((value > 9) && (value <= 99))
    {
        ch = ((value % 100) / 10);
        OLED_print_char((x_pos + 6), y_pos , (0x30 + ch));

        ch = (value % 10);
        OLED_print_char((x_pos + 12), y_pos , (0x30 + ch));

        OLED_print_char((x_pos + 18), y_pos , 0x20);
        OLED_print_char((x_pos + 24), y_pos , 0x20);
        OLED_print_char((x_pos + 30), y_pos , 0x20);
    }
    else
    {
        ch = (value % 10);
        OLED_print_char((x_pos + 6), y_pos , (0x30 + ch));
        OLED_print_char((x_pos + 12), y_pos , 0x20);
        OLED_print_char((x_pos + 18), y_pos , 0x20);
        OLED_print_char((x_pos + 24), y_pos , 0x20);
        OLED_print_char((x_pos + 30), y_pos , 0x20);
    }
}                                                     


void OLED_print_decimal(unsigned char x_pos, unsigned char y_pos, unsigned int value, unsigned char points)
{
    unsigned char ch = 0;

    OLED_print_char(x_pos, y_pos, '.');

    ch = (value / 1000);
    OLED_print_char((x_pos + 6), y_pos , (0x30 + ch));

    if(points > 1)
    {
        ch = ((value % 1000) / 100);
        OLED_print_char((x_pos + 12), y_pos , (0x30 + ch));


        if(points > 2)
        {
            ch = ((value % 100) / 10);
            OLED_print_char((x_pos + 18), y_pos , (0x30 + ch));

            if(points > 3)
            {
                ch = (value % 10);
                OLED_print_char((x_pos + 24), y_pos , (0x30 + ch));
            }
        }
    }
}


void OLED_print_float(unsigned char x_pos, unsigned char y_pos, float value, unsigned char points)
{
    signed long tmp = 0;

    tmp = value;
    OLED_print_int(x_pos, y_pos, tmp);
    tmp = ((value - tmp) * 10000);

    if(tmp < 0)
    {
       tmp = -tmp;
    }

    if((value >= 10000) && (value < 100000))
    {
        OLED_print_decimal((x_pos + 36), y_pos, tmp, points);
    }
    else if((value >= 1000) && (value < 10000))
    {
        OLED_print_decimal((x_pos + 30), y_pos, tmp, points);
    }
    else if((value >= 100) && (value < 1000))
    {
        OLED_print_decimal((x_pos + 24), y_pos, tmp, points);
    }
    else if((value >= 10) && (value < 100))
    {
        OLED_print_decimal((x_pos + 18), y_pos, tmp, points);
    }
    else if(value < 10)
    {
        OLED_print_decimal((x_pos + 12), y_pos, tmp, points);
        if(value < 0)
        {
            OLED_print_char(x_pos, y_pos, '-');
        }
        else
        {
            OLED_print_char(x_pos, y_pos, ' ');
        }
    }
}

 

main.c

#include <msp430.h>
#include "delay.h"
#include "HW_SPI.h"
#include "SSD1306.h"


void GPIO_graceInit(void);
void BCSplus_graceInit(void);
void USCI_B0_graceInit(void);
void System_graceInit(void);
void WDTplus_graceInit(void);


void main(void)
{
    signed char c = -11;
    signed int i = -111;
    float f = -1.9;

    /* Stop watchdog timer from timing out during initial start-up. */
    WDTCTL = WDTPW | WDTHOLD;

    /* initialize Config for the MSP430 GPIO */
    GPIO_graceInit();

    /* initialize Config for the MSP430 2xx family clock systems (BCS) */
    BCSplus_graceInit();

    /* initialize Config for the MSP430 USCI_B0 */
    USCI_B0_graceInit();

    /* initialize Config for the MSP430 System Registers */
    System_graceInit();

    /* initialize Config for the MSP430 WDT+ */
    WDTplus_graceInit();

    OLED_init();

    OLED_print_string(4, 0, "MSP430G2553 SSD1306");
    OLED_print_string(16, 1, "USCI_B0 SPI Test");
    OLED_print_string(0, 4, "Char :");
    OLED_print_string(0, 5, "Int. :");
    OLED_print_string(0, 6, "Float:");

    while(1)
    {
        OLED_print_chr(92, 4, c);
        OLED_print_int(92, 5, i);
        OLED_print_float(92, 6, f, 1);
        c++;
        i++;
        f += 0.1;
        delay_ms(200);
    };
}


void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

    /* Port 1 Port Select 2 Register */
    P1SEL2 = BIT5 | BIT7;

    /* Port 1 Output Register */
    P1OUT = 0;

    /* Port 1 Port Select Register */
    P1SEL = BIT5 | BIT7;

    /* Port 1 Direction Register */
    P1DIR = BIT2 | BIT3 | BIT4;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Direction Register */
    P2DIR = 0;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* Port 3 Output Register */
    P3OUT = 0;

    /* Port 3 Direction Register */
    P3DIR = 0;

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */
}


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

    if (CALBC1_8MHZ != 0xFF)
    {
        /* Adjust this accordingly to your VCC rise time */
        __delay_cycles(100000);

        // Follow recommended flow. First, clear all DCOx and MODx bits. Then
        // apply new RSELx values. Finally, apply new DCOx and MODx bit values.
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_8MHZ;      /* Set DCO to 8MHz */
        DCOCTL = CALDCO_8MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_0 -- If XTS = 0, XT1 = 32768kHz Crystal ; If XTS = 1, XT1 = 0.4 - 1-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_0 | XCAP_1;

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}


void USCI_B0_graceInit(void)
{
    /* USER CODE START (section: USCI_B0_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: USCI_B0_graceInit_prologue) */

    /* Disable USCI */
    UCB0CTL1 |= UCSWRST;

    /*
     * Control Register 0
     *
     * UCCKPH -- Data is captured on the first UCLK edge and changed on the following edge
     * ~UCCKPL -- Inactive state is low
     * UCMSB -- MSB first
     * ~UC7BIT -- 8-bit
     * UCMST -- Master mode
     * UCMODE_1 -- 4-Pin SPI with UCxSTE active high: slave enabled when UCxSTE = 1
     * UCSYNC -- Synchronous Mode
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    UCB0CTL0 = UCCKPH | UCMSB | UCMST | UCMODE_1 | UCSYNC;

    /*
     * Control Register 1
     *
     * UCSSEL_2 -- SMCLK
     * UCSWRST -- Enabled. USCI logic held in reset state
     */
    UCB0CTL1 = UCSSEL_2 | UCSWRST;

    /* Bit Rate Control Register 0 */
    UCB0BR0 = 8;

    /* Enable USCI */
    UCB0CTL1 &= ~UCSWRST;

    /* USER CODE START (section: USCI_B0_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: USCI_B0_graceInit_epilogue) */
}


void System_graceInit(void)
{
    /* USER CODE START (section: System_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: System_graceInit_prologue) */

    /*
     * SR, Status Register
     *
     * ~SCG1 -- Disable System clock generator 1
     * ~SCG0 -- Disable System clock generator 0
     * ~OSCOFF -- Oscillator On
     * ~CPUOFF -- CPU On
     * GIE -- General interrupt enable
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    __bis_SR_register(GIE);

    /* USER CODE START (section: System_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: System_graceInit_epilogue) */
}


void WDTplus_graceInit(void)
{
    /* USER CODE START (section: RTC_B_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: RTC_B_graceInit_prologue) */

    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * WDTHOLD -- Watchdog timer+ is stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * ~WDTTMSEL -- Watchdog mode
     * ~WDTCNTCL -- No action
     * ~WDTSSEL -- SMCLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * ~WDTIS1 -- Watchdog clock source bit1 disabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTHOLD;

    /* USER CODE START (section: RTC_B_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: RTC_B_graceInit_epilogue) */
}

 

Simulation

The model for SPI-based SSD1306 OLED display is not available in Proteus VSM and so it cannot be simulated. Only the pinouts are shown in the schematic below.

USCI_SPI_OLED

Explanation

The code here basically uses the same functions as in the previous example except for the fact that USCI_B0 is used in half-duplex mode here. The rest of the code is the driver implementation for SSD1306 OLED display. Note that the driver has been cut short of graphical functions due to low memory capacity of Value-Line Devices. Explaining the operation of the OLED display is beyond the scope of this article.

USCI_B0_initialization

Demo

OLED

USCI I2C – Interfacing BH1750 Ambient Light Sensor

Using USCI in I2C mode is a bit difficult compared to using USCI in SPI mode. This is because of the many iterations and function calls in I2C mode. Again, here I tried to keep things simple and kept things in a fashion we would normally expect. USCI-based I2C can be realized with a state-of-machine too but that way is not easy for beginners.

Block

Code Example

 

HW_I2C.h

#include <msp430.h>


void I2C_USCI_init(unsigned char address);
void I2C_USCI_set_address(unsigned char address);
unsigned char I2C_USCI_read_byte(unsigned char address);
unsigned char I2C_USCI_read_word(unsigned char address,unsigned char *value, unsigned char length);
unsigned char I2C_USCI_write_byte(unsigned char address, unsigned char value);

 

HW_I2C.c

#include "HW_I2C.h"


void I2C_USCI_init(unsigned char address)
{
    P1DIR &= ~(BIT6 + BIT7);
    P1OUT |= (BIT6 + BIT7);
    P1SEL2 |= (BIT6 | BIT7);
    P1SEL |= (BIT6 | BIT7);

    UCB0CTL1 |= UCSWRST;
    UCB0CTL0 = (UCMST | UCMODE_3 | UCSYNC);
    UCB0CTL1 = (UCSSEL_2 | UCSWRST);
    UCB0BR0 = 20;
    UCB0I2CSA = address;
    UCB0CTL1 &= ~UCSWRST;
}


void I2C_USCI_set_address(unsigned char address)
{
    UCB0CTL1 |= UCSWRST;
    UCB0I2CSA = address;
    UCB0CTL1 &= ~UCSWRST;
}


unsigned char I2C_USCI_read_byte(unsigned char address)
{
    while(UCB0CTL1 & UCTXSTP);
    UCB0CTL1 |= (UCTR | UCTXSTT);

    while(!(IFG2 & UCB0TXIFG));
    UCB0TXBUF = address;

    while(!(IFG2 & UCB0TXIFG));
    UCB0CTL1 &= ~UCTR;
    UCB0CTL1 |= UCTXSTT;
    IFG2 &= ~UCB0TXIFG;

    while(UCB0CTL1 & UCTXSTT);
    UCB0CTL1 |= UCTXSTP;

    return UCB0RXBUF;
}


unsigned char I2C_USCI_read_word(unsigned char address,unsigned char *value, unsigned char length)
{
    unsigned char i = 0;

    while (UCB0CTL1 & UCTXSTP);

    UCB0CTL1 |= (UCTR | UCTXSTT);

    while (!(IFG2 & UCB0TXIFG));

    IFG2 &= ~UCB0TXIFG;

    if(UCB0STAT & UCNACKIFG)
    {
        return UCB0STAT;
    }

    UCB0TXBUF = address;

    while (!(IFG2 & UCB0TXIFG));

    if(UCB0STAT & UCNACKIFG)
    {
        return UCB0STAT;
    }

    UCB0CTL1 &= ~UCTR;
    UCB0CTL1 |= UCTXSTT;
    IFG2 &= ~UCB0TXIFG;

    while (UCB0CTL1 & UCTXSTT);

    for(i = 0; i < (length - 1); i++)
    {
        while (!(IFG2&UCB0RXIFG));
        IFG2 &= ~UCB0TXIFG;
        value[i] = UCB0RXBUF;
    }

    while (!(IFG2 & UCB0RXIFG));

    IFG2 &= ~UCB0TXIFG;
    UCB0CTL1 |= UCTXSTP;
    value[length - 1] = UCB0RXBUF;
    IFG2 &= ~UCB0TXIFG;

    return 0;
}

unsigned char I2C_USCI_write_byte(unsigned char address, unsigned char value)
{
    while(UCB0CTL1 & UCTXSTP);

    UCB0CTL1 |= (UCTR | UCTXSTT);

    while(!(IFG2 & UCB0TXIFG));

    if(UCB0STAT & UCNACKIFG)
    {
        return UCB0STAT;
    }

    UCB0TXBUF = address;


    while(!(IFG2 & UCB0TXIFG));

    if(UCB0STAT & UCNACKIFG)
    {
        return UCB0STAT;
    }

    UCB0TXBUF = value;

    while(!(IFG2 & UCB0TXIFG));

    if(UCB0STAT & UCNACKIFG)
    {
        return UCB0STAT;
    }

    UCB0CTL1 |= UCTXSTP;
    IFG2 &= ~UCB0TXIFG;

    return 0;
}

 

BH1750.h

#include <msp430.h>
#include "delay.h"
#include "HW_I2C.h"


#define  BH1750_addr                            0x23

#define  power_down                            0x00
#define  power_up                              0x01
#define  reset                                 0x07
#define  cont_H_res_mode1                      0x10
#define  cont_H_res_mode2                      0x11 
#define  cont_L_res_mode                       0x13   
#define  one_time_H_res_mode1                  0x20
#define  one_time_H_res_mode2                  0x21
#define  one_time_L_res_mode                   0x23 


void BH1750_init(void);
void BH1750_write(unsigned char cmd);     
unsigned int BH1750_read_word(void);
unsigned int get_lux_value(unsigned char mode, unsigned int delay_time);

 

BH1750.c

#include "BH1750.h"


void BH1750_init(void)
{
   I2C_USCI_init(BH1750_addr);
   delay_ms(10); 
   BH1750_write(power_down);
}               


void BH1750_write(unsigned char cmd)
    I2C_USCI_write_byte(BH1750_addr, cmd);
}


unsigned int BH1750_read_word(void)
{                     
  unsigned long value = 0x0000;
  unsigned char bytes[2] = {0x00, 0x00};

  I2C_USCI_read_word(0x11, bytes, 2);  

  value = ((bytes[1] << 8) | bytes[0]); 

  return value;
}


unsigned int get_lux_value(unsigned char mode, unsigned int delay_time)
{
  unsigned long lux_value = 0x00; 
  unsigned char dly = 0x00;
  unsigned char s = 0x08;

  while(s)
  {
     BH1750_write(power_up);
     BH1750_write(mode);
     lux_value += BH1750_read_word();
     for(dly = 0; dly < delay_time; dly += 1)
     {
         delay_ms(1);
     }
     BH1750_write(power_down);
     s--;
  }
  lux_value >>= 3;

  return ((unsigned int)lux_value);
}                   

 

main.c

#include <msp430.h>
#include "delay.h"
#include "HW_I2C.h"
#include "SW_I2C.h"
#include "PCF8574.h"
#include "lcd.h"
#include "BH1750.h"


void GPIO_graceInit(void);
void BCSplus_graceInit(void);
void USCI_B0_graceInit(void);
void System_graceInit(void);
void WDTplus_graceInit(void);
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value);


void main(void)
{
    unsigned int LX = 0x0000;
    unsigned int tmp = 0x0000;

    /* Stop watchdog timer from timing out during initial start-up. */
    WDTCTL = WDTPW | WDTHOLD;

    /* initialize Config for the MSP430 GPIO */
    GPIO_graceInit();

    /* initialize Config for the MSP430 2xx family clock systems (BCS) */
    BCSplus_graceInit();

    /* initialize Config for the MSP430 USCI_B0 */
    USCI_B0_graceInit();

    /* initialize Config for the MSP430 System Registers */
    System_graceInit();

    /* initialize Config for the MSP430 WDT+ */
    WDTplus_graceInit();

    BH1750_init();

    LCD_init();
    LCD_clear_home();

    LCD_goto(0, 0);
    LCD_putstr("MSP430G USCI I2C");
    LCD_goto(0, 1);
    LCD_putstr("Lux:");

    while(1)
    {
        tmp = get_lux_value(cont_H_res_mode1, 20);

        if(tmp > 10)
        {
            LX = tmp;
        }
        else
        {
            LX = get_lux_value(cont_H_res_mode1, 140);
        }

        lcd_print(11, 1, LX);

        delay_ms(200);
    }
}


void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

    /* Port 1 Port Select 2 Register */
    P1SEL2 = BIT6 | BIT7;

    /* Port 1 Output Register */
    P1OUT = 0;

    /* Port 1 Port Select Register */
    P1SEL = BIT6 | BIT7;

    /* Port 1 Direction Register */
    P1DIR = 0;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Direction Register */
    P2DIR = 0;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* Port 3 Output Register */
    P3OUT = 0;

    /* Port 3 Direction Register */
    P3DIR = 0;

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */

}


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

    if (CALBC1_1MHZ != 0xFF)
    {
        /* Follow recommended flow. First, clear all DCOx and MODx bits. Then
         * apply new RSELx values. Finally, apply new DCOx and MODx bit values.
         */
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_1MHZ;      /* Set DCO to 1MHz */
        DCOCTL = CALDCO_1MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_0 -- If XTS = 0, XT1 = 32768kHz Crystal ; If XTS = 1, XT1 = 0.4 - 1-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_0 | XCAP_1;

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}


void USCI_B0_graceInit(void)
{
    /* USER CODE START (section: USCI_B0_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: USCI_B0_graceInit_prologue) */

    /* Disable USCI */
    UCB0CTL1 |= UCSWRST;

    /*
     * Control Register 0
     *
     * ~UCA10 -- Own address is a 7-bit address
     * ~UCSLA10 -- Address slave with 7-bit address
     * ~UCMM -- Single master environment. There is no other master in the system. The address compare unit is disabled
     * UCMST -- Master mode
     * UCMODE_3 -- I2C Mode
     * UCSYNC -- Synchronous Mode
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    UCB0CTL0 = UCMST | UCMODE_3 | UCSYNC;

    /*
     * Control Register 1
     *
     * UCSSEL_2 -- SMCLK
     * ~UCTR -- Receiver
     * ~UCTXNACK -- Acknowledge normally
     * ~UCTXSTP -- No STOP generated
     * ~UCTXSTT -- Do not generate START condition
     * UCSWRST -- Enabled. USCI logic held in reset state
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    UCB0CTL1 = UCSSEL_2 | UCSWRST;

    /* I2C Slave Address Register */
    UCB0I2CSA = BH1750_addr;

    /* Bit Rate Control Register 0 */
    UCB0BR0 = 20;

    /* Enable USCI */
    UCB0CTL1 &= ~UCSWRST;

    /* USER CODE START (section: USCI_B0_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: USCI_B0_graceInit_epilogue) */
}


void System_graceInit(void)
{
    /* USER CODE START (section: System_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: System_graceInit_prologue) */

    /*
     * SR, Status Register
     *
     * ~SCG1 -- Disable System clock generator 1
     * ~SCG0 -- Disable System clock generator 0
     * ~OSCOFF -- Oscillator On
     * ~CPUOFF -- CPU On
     * GIE -- General interrupt enable
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    __bis_SR_register(GIE);

    /* USER CODE START (section: System_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: System_graceInit_epilogue) */
}


void WDTplus_graceInit(void)
{
    /* USER CODE START (section: RTC_B_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: RTC_B_graceInit_prologue) */

    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * WDTHOLD -- Watchdog timer+ is stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * ~WDTTMSEL -- Watchdog mode
     * ~WDTCNTCL -- No action
     * ~WDTSSEL -- SMCLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * ~WDTIS1 -- Watchdog clock source bit1 disabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTHOLD;

    /* USER CODE START (section: RTC_B_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: RTC_B_graceInit_epilogue) */
}


void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value)
{
    char tmp[6] = {0x20, 0x20, 0x20, 0x20, 0x20, '\0'};

    tmp[0] = ((value / 10000) + 0x30);
    tmp[1] = (((value / 1000) % 10) + 0x30);
    tmp[2] = (((value / 100) % 10) + 0x30);
    tmp[3] = (((value / 10) % 10) + 0x30);
    tmp[4] = ((value % 10) + 0x30);

    LCD_goto(x_pos, y_pos);
    LCD_putstr(tmp);
}

 

Simulation

The model for BH1750FVI is not available in Proteus VSM and so it cannot be simulated. Only the pinouts are shown in the schematic below.

BH1750 Sim

Explanation

Just like USCI SPI setup, Grace is used for setting the basic parameters for USCI I2C communication. Note only USCI_B0 supports I2C communication unlike USCI SPI communication. Since our MSP430 micro is the master device in the I2C bus, USCI module is configured as I2C master. One particular thing to observe is the I2C Slave Address. Here as shown in the screenshot below, it is 208. This is not an important figure. Same goes for the I2C Own Address part. No interrupts are to be used and so none of them are enabled.

Grace

Out of the configuration set by Grace, I2C pins are set also in the I2C initialization function. This should be done manually before initializing the USCI hardware.

void I2C_USCI_init(unsigned char address)
{
    P1DIR &= ~(BIT6 + BIT7);
    P1OUT |= (BIT6 + BIT7);
    P1SEL2 |= (BIT6 | BIT7);
    P1SEL |= (BIT6 | BIT7);

 

    UCB0CTL1 |= UCSWRST;
    UCB0CTL0 = (UCMST | UCMODE_3 | UCSYNC);
    UCB0CTL1 = (UCSSEL_2 | UCSWRST);
    UCB0BR0 = 20;
    UCB0I2CSA = address;
    UCB0CTL1 &= ~UCSWRST;
}

Off all the functions used in the HW_I2C files, the following are of high importance:

unsigned char I2C_USCI_read_byte(unsigned char address);
unsigned char I2C_USCI_read_word(unsigned char address,unsigned char *value, unsigned char length);
unsigned char I2C_USCI_write_byte(unsigned char address, unsigned char value);

Their names suggest their functionality. The codes inside them are arranged as such that they take care of start-stop conditions generation, clock generation, etc. I2C communication needs device address along side read-write info. Based on read-write info, the host device writes or read I2C bus. The drawback of using I2C with these functions is the vulnerability to falling inside a loop since loops are used in these functions widely. This setback can be overcome with timeouts.

Demo

Lux

USCI I2C – Interfacing DS1307 Real Time Clock (RTC)

This part shows another example of I2C implementation using MSP430’s USCI hardware. This time the I2C device that is connected with a MSP430 is the popular DS1307 real time clock (RTC). This is the last USCI hardware example.

DS1307

Code Example

 

DS1307.h

#include "HW_I2C.h"


#define DS1307_address                    0x68

#define sec_reg                           0x00
#define min_reg                           0x01
#define hr_reg                            0x02
#define day_reg                           0x03
#define date_reg                          0x04
#define month_reg                         0x05
#define year_reg                          0x06
#define control_reg                       0x07


void DS1307_init(void);
unsigned char DS1307_read(unsigned char address);
void DS1307_write(unsigned char address, unsigned char value);
unsigned char bcd_to_decimal(unsigned char value);
unsigned char decimal_to_bcd(unsigned char value);
void get_time(void);
void get_date(void);
void set_time(void);
void set_date(void);

 

DS1307.c

#include "DS1307.h"


extern struct
{
   unsigned char sec;
   unsigned char min;
   unsigned char hr;
   unsigned char day;
   unsigned char dt;
   unsigned char mt;
   unsigned char yr;
}rtc;


void DS1307_init(void)
{
       I2C_USCI_init(DS1307_address);
       DS1307_write(sec_reg, 0x00);
    DS1307_write(control_reg, 0x90);
}


unsigned char DS1307_read(unsigned char address)
{
    return I2C_USCI_read_byte(address);
}


void DS1307_write(unsigned char address, unsigned char value)
{
    I2C_USCI_write_byte(address, value);
}


unsigned char bcd_to_decimal(unsigned char value)
{
    return ((value & 0x0F) + (((value & 0xF0) >> 0x04) * 0x0A));
}


unsigned char decimal_to_bcd(unsigned char value)
{
    return (((value / 0x0A) << 0x04) & 0xF0) | ((value % 0x0A) & 0x0F);
}


void get_time(void)
{
    rtc.sec = DS1307_read(sec_reg);
    rtc.sec = bcd_to_decimal(rtc.sec);

    rtc.min = DS1307_read(min_reg);
    rtc.min = bcd_to_decimal(rtc.min);

    rtc.hr = DS1307_read(hr_reg);
    rtc.hr = bcd_to_decimal(rtc.hr);
}


void get_date(void)
{
    rtc.day = DS1307_read(day_reg);
    rtc.day = bcd_to_decimal(rtc.day);

    rtc.dt = DS1307_read(date_reg);
    rtc.dt = bcd_to_decimal(rtc.dt);

    rtc.mt = DS1307_read(month_reg);
    rtc.mt = bcd_to_decimal(rtc.mt);

    rtc.yr = DS1307_read(year_reg);
    rtc.yr = bcd_to_decimal(rtc.yr);
}


void set_time(void)
{
    rtc.sec = decimal_to_bcd(rtc.sec);
    DS1307_write(sec_reg, rtc.sec);

    rtc.min = decimal_to_bcd(rtc.min);
    DS1307_write(min_reg, rtc.min);

    rtc.hr = decimal_to_bcd(rtc.hr);
    DS1307_write(hr_reg, rtc.hr);
}


void set_date(void)
{
    rtc.day = decimal_to_bcd(rtc.day);
    DS1307_write(day_reg, rtc.day);

    rtc.dt = decimal_to_bcd(rtc.dt);
    DS1307_write(date_reg, rtc.dt);

    rtc.mt = decimal_to_bcd(rtc.mt);
    DS1307_write(month_reg, rtc.mt);

    rtc.yr = decimal_to_bcd(rtc.yr);
    DS1307_write(year_reg, rtc.yr);
}

 

main.c

#include <msp430.h>
#include "delay.h"
#include "HW_I2C.h"
#include "DS1307.h"
#include "lcd.h"


struct
{
   unsigned char sec;
   unsigned char min;
   unsigned char hr;
   unsigned char day;
   unsigned char dt;
   unsigned char mt;
   unsigned char yr;
}rtc;


void GPIO_graceInit(void);
void BCSplus_graceInit(void);
void USCI_B0_graceInit(void);
void System_graceInit(void);
void WDTplus_graceInit(void);
void show_value(unsigned char x_pos, unsigned char y_pos, unsigned char value);
void display_time(void);


void main(void)
{
    rtc.sec = 30;
    rtc.min = 58;
    rtc.hr = 23;

    /* Stop watchdog timer from timing out during initial start-up. */
    WDTCTL = WDTPW | WDTHOLD;

    /* initialize Config for the MSP430 GPIO */
    GPIO_graceInit();

    /* initialize Config for the MSP430 2xx family clock systems (BCS) */
    BCSplus_graceInit();

    /* initialize Config for the MSP430 USCI_B0 */
    USCI_B0_graceInit();

    /* initialize Config for the MSP430 System Registers */
    System_graceInit();

    /* initialize Config for the MSP430 WDT+ */
    WDTplus_graceInit();

    LCD_init();
    LCD_clear_home();

    LCD_goto(0, 0);

    LCD_putstr("MSP430G USCI I2C");

    DS1307_init();
    set_time();

    while(1)
    {
        get_time();
        display_time();
    }
}


void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

    /* Port 1 Port Select 2 Register */
    P1SEL2 = BIT6 | BIT7;

    /* Port 1 Output Register */
    P1OUT = 0;

    /* Port 1 Port Select Register */
    P1SEL = BIT6 | BIT7;

    /* Port 1 Direction Register */
    P1DIR = 0;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Direction Register */
    P2DIR = 0;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* Port 3 Output Register */
    P3OUT = 0;

    /* Port 3 Direction Register */
    P3DIR = 0;

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */

}


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

    if (CALBC1_1MHZ != 0xFF)
    {
        /* Follow recommended flow. First, clear all DCOx and MODx bits. Then
         * apply new RSELx values. Finally, apply new DCOx and MODx bit values.
         */
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_1MHZ;      /* Set DCO to 1MHz */
        DCOCTL = CALDCO_1MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_0 -- If XTS = 0, XT1 = 32768kHz Crystal ; If XTS = 1, XT1 = 0.4 - 1-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_0 | XCAP_1;

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}


void USCI_B0_graceInit(void)
{
    /* USER CODE START (section: USCI_B0_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: USCI_B0_graceInit_prologue) */

    /* Disable USCI */
    UCB0CTL1 |= UCSWRST;

    /*
     * Control Register 0
     *
     * ~UCA10 -- Own address is a 7-bit address
     * ~UCSLA10 -- Address slave with 7-bit address
     * ~UCMM -- Single master environment. There is no other master in the system. The address compare unit is disabled
     * UCMST -- Master mode
     * UCMODE_3 -- I2C Mode
     * UCSYNC -- Synchronous Mode
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    UCB0CTL0 = UCMST | UCMODE_3 | UCSYNC;

    /*
     * Control Register 1
     *
     * UCSSEL_2 -- SMCLK
     * ~UCTR -- Receiver
     * ~UCTXNACK -- Acknowledge normally
     * ~UCTXSTP -- No STOP generated
     * ~UCTXSTT -- Do not generate START condition
     * UCSWRST -- Enabled. USCI logic held in reset state
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    UCB0CTL1 = UCSSEL_2 | UCSWRST;

    /* I2C Slave Address Register */
    UCB0I2CSA = DS1307_address;

    /* Bit Rate Control Register 0 */
    UCB0BR0 = 20;

    /* Enable USCI */
    UCB0CTL1 &= ~UCSWRST;

    /* USER CODE START (section: USCI_B0_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: USCI_B0_graceInit_epilogue) */
}


void System_graceInit(void)
{
    /* USER CODE START (section: System_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: System_graceInit_prologue) */

    /*
     * SR, Status Register
     *
     * ~SCG1 -- Disable System clock generator 1
     * ~SCG0 -- Disable System clock generator 0
     * ~OSCOFF -- Oscillator On
     * ~CPUOFF -- CPU On
     * GIE -- General interrupt enable
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    __bis_SR_register(GIE);

    /* USER CODE START (section: System_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: System_graceInit_epilogue) */
}


void WDTplus_graceInit(void)
{
    /* USER CODE START (section: RTC_B_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: RTC_B_graceInit_prologue) */

    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * WDTHOLD -- Watchdog timer+ is stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * ~WDTTMSEL -- Watchdog mode
     * ~WDTCNTCL -- No action
     * ~WDTSSEL -- SMCLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * ~WDTIS1 -- Watchdog clock source bit1 disabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTHOLD;

    /* USER CODE START (section: RTC_B_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: RTC_B_graceInit_epilogue) */
}


void show_value(unsigned char x_pos, unsigned char y_pos, unsigned char value)
{
   char chr = 0;

   chr = ((value / 10) + 0x30);
   LCD_goto(x_pos, y_pos);
   LCD_putchar(chr);

   chr = ((value % 10) + 0x30);
   LCD_goto((x_pos + 1), y_pos);
   LCD_putchar(chr);
}


void display_time(void)
{
    LCD_goto(6, 1);
    LCD_putchar(' ');
    LCD_goto(9, 1);
    LCD_putchar(' ');
    delay_ms(450);

    show_value(7, 1, rtc.hr);
    show_value(10, 1, rtc.min);
    show_value(4, 1, rtc.sec);

    LCD_goto(6, 1);
    LCD_putchar(':');
    LCD_goto(9, 1);
    LCD_putchar(':');
    delay_ms(450);
}

 

Simulation

DS1307 simulation encountered some weird issues but it does work.

DS1307 sim

Explanation

Since it uses the same ideas as in the previous example, there is hardly a thing to explain here.

Demo

RTC

ADC10 with Direct Memory Access (DMA)

The concept of Direct Memory Access (DMA) or Data Transfer Controller (DTC) is usually found in 32-bit ARM-Cortex-based micros and in some highly advanced DSP microcontrollers. In recent times, a few new generation 8-bit and 16-bit devices have emerged with DMA hardware feature. The DMA hardware may look a bit complicated for those who are new to it but it is practically very simple. Just consider what you have been doing all these times without DMA. Consider an ADC for an example. Without DMA, the ADC senses its channel(s) and saves sensed data on an ADC result register waiting to be read and continue the same operation over and over again. This process involves the CPU very often as sensing/extracting analog data, storing it and starting ADC conversion all needs CPU’s attention. Thus, the CPU bus is always busy with these engagements. With DMA the process becomes more automatic and intelligent. The DMA controller automatically transfers AD conversion data to specified memory location(s) using its separate data bus. In the whole process the CPU is not involved much. Conversions take place and the conversion results are immediately transferred. Thus, the process becomes both autonomous and fast.

Please note that the DMA bus is only available for ADC to memory or in other words peripheral to memory transfers. It cannot be used for other peripherals like USCI, USI, etc. or for memory-memory transfers. However, it is not a big issue for now.

Code Example

 

#include <msp430.h>
#include "delay.h"
#include "SW_I2C.h"
#include "PCF8574.h"
#include "lcd.h"


#define no_of_samples       16


unsigned int adc_pointer[no_of_samples];


void GPIO_graceInit(void);
void BCSplus_graceInit(void);
void ADC10_graceInit(void);
void System_graceInit(void);
void WDTplus_graceInit(void);
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value);



void main(void)
{
    unsigned char s = 0;
    unsigned int adc_avg = 0;
    /* Stop watchdog timer from timing out during initial start-up. */
    WDTCTL = WDTPW | WDTHOLD;

    /* initialize Config for the MSP430 GPIO */
    GPIO_graceInit();

    /* initialize Config for the MSP430 2xx family clock systems (BCS) */
    BCSplus_graceInit();

    /* initialize Config for the MSP430 10-bit Analog to Digital Converter (ADC) */
    ADC10_graceInit();

    /* initialize Config for the MSP430 System Registers */
    System_graceInit();

    /* initialize Config for the MSP430 WDT+ */
    WDTplus_graceInit();

    LCD_init();
    LCD_clear_home();

    LCD_goto(0, 0);
    LCD_putstr("MSP430 ADC + DMA:");

    LCD_goto(0, 1);
    LCD_putstr("A1:");


    while(1)
    {
            adc_avg = 0;
            P1OUT |= BIT0;
            ADC10CTL0 &= ~ENC;
            while (ADC10CTL1 & BUSY);
            ADC10CTL0 |= (ENC | ADC10SC);

            for(s = 0; s < no_of_samples; s++)
            {
                adc_avg += adc_pointer[s];
            }

            adc_avg = (adc_avg / no_of_samples);
            lcd_print(12, 1, adc_avg);
            delay_ms(100);
            P1OUT &= ~BIT0;
            delay_ms(100);
    };
}


void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

    /* Port 1 Output Register */
    P1OUT = 0;

    /* Port 1 Direction Register */
    P1DIR = BIT0;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Direction Register */
    P2DIR = 0;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */

}


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

    if (CALBC1_8MHZ != 0xFF)
    {
        /* Adjust this accordingly to your VCC rise time */
        __delay_cycles(100000);

        // Follow recommended flow. First, clear all DCOx and MODx bits. Then
        // apply new RSELx values. Finally, apply new DCOx and MODx bit values.
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_8MHZ;      /* Set DCO to 8MHz */
        DCOCTL = CALDCO_8MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_2 -- If XTS = 0, XT1 = VLOCLK ; If XTS = 1, XT1 = 3 - 16-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_2 | XCAP_1;

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}


void ADC10_graceInit(void)
{
    /* USER CODE START (section: ADC10_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: ADC10_graceInit_prologue) */

    /* disable ADC10 during initialization */
    ADC10CTL0 &= ~ENC;

    /*
     * Control Register 0
     *
     * ~ADC10SC -- No conversion
     * ~ENC -- Disable ADC
     * ~ADC10IFG -- Clear ADC interrupt flag
     * ~ADC10IE -- Disable ADC interrupt
     * ADC10ON -- Switch On ADC10
     * ~REFON -- Disable ADC reference generator
     * ~REF2_5V -- Set reference voltage generator = 1.5V
     * MSC -- Enable multiple sample and conversion
     * ~REFBURST -- Reference buffer on continuously
     * ~REFOUT -- Reference output off
     * ~ADC10SR -- Reference buffer supports up to ~200 ksps
     * ADC10SHT_2 -- 16 x ADC10CLKs
     * SREF_0 -- VR+ = VCC and VR- = VSS
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    ADC10CTL0 = ADC10ON | MSC | ADC10SHT_2 | SREF_0;

    /*
     * Control Register 1
     *
     * ~ADC10BUSY -- No operation is active
     * CONSEQ_2 -- Repeat single channel
     * ADC10SSEL_0 -- ADC10OSC
     * ADC10DIV_0 -- Divide by 1
     * ~ISSH -- Input signal not inverted
     * ~ADC10DF -- ADC10 Data Format as binary
     * SHS_0 -- ADC10SC
     * INCH_1 -- ADC Channel 1
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    ADC10CTL1 = CONSEQ_2 | ADC10SSEL_0 | ADC10DIV_0 | SHS_0 | INCH_1;

    /* Analog (Input) Enable Control Register 0 */
    ADC10AE0 = 0x2;

    /*
     * Data Transfer Control Register 0
     *
     * ~ADC10TB -- One-block transfer mode
     * ADC10CT -- Data is transferred continuously after every conversion
     *
     * Note: ~ADC10TB indicates that ADC10TB has value zero
     */
    ADC10DTC0 = ADC10CT;

    /* Data Transfer Control Register 1 */
    ADC10DTC1 = no_of_samples;

    /* Data Transfer Start Address */
    ADC10SA = ((unsigned int)adc_pointer);

    /* enable ADC10 */
    ADC10CTL0 |= ENC;

    /* USER CODE START (section: ADC10_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: ADC10_graceInit_epilogue) */
}


void System_graceInit(void)
{
    /* USER CODE START (section: System_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: System_graceInit_prologue) */

    /* Clear oscillator fault flag with software delay */
    do
    {
        // Clear OSC fault flag
        IFG1 &= ~OFIFG;

        // 50us delay
        __delay_cycles(400);
    } while (IFG1 & OFIFG);

    /*
     * SR, Status Register
     *
     * ~SCG1 -- Disable System clock generator 1
     * ~SCG0 -- Disable System clock generator 0
     * ~OSCOFF -- Oscillator On
     * ~CPUOFF -- CPU On
     * GIE -- General interrupt enable
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    __bis_SR_register(GIE);

    /* USER CODE START (section: System_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: System_graceInit_epilogue) */
}


void WDTplus_graceInit(void)
{
    /* USER CODE START (section: RTC_B_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: RTC_B_graceInit_prologue) */

    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * WDTHOLD -- Watchdog timer+ is stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * ~WDTTMSEL -- Watchdog mode
     * ~WDTCNTCL -- No action
     * ~WDTSSEL -- SMCLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * ~WDTIS1 -- Watchdog clock source bit1 disabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTHOLD;

    /* USER CODE START (section: RTC_B_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: RTC_B_graceInit_epilogue) */
}


void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value)
{
    char chr = 0;

    chr = ((value / 1000) + 0x30);
    LCD_goto(x_pos, y_pos);
    LCD_putchar(chr);

    chr = (((value / 100) % 10) + 0x30);
    LCD_goto((x_pos + 1), y_pos);
    LCD_putchar(chr);

    chr = (((value / 10) % 10) + 0x30);
    LCD_goto((x_pos + 2), y_pos);
    LCD_putchar(chr);

    chr = ((value % 10) + 0x30);
    LCD_goto((x_pos + 3), y_pos);
    LCD_putchar(chr);
}

 

Simulation

adc+dma

Explanation

Use Power User setup screen of Grace to configure the DTC. Observe the setup as shown below:

dma

Here only channel 1 was used. The setup is similar to the first ADC10 example setup. The main difference is the enabling of the DTC block. Note the starting memory address and the memory block size. The memory we are talking about here is no other than MSP430’s RAM. Unless you want to assign a different pointer address, keep it untouched. So why is it pointed at RAM location 512 (0x200) by default? The answer is the fact that this is first RAM location for our target MSP430G2452 microcontroller. Check the memory map below:

Memory Address

The block size indicates that number of locations that we will need to store ADC10 data. MSP430s are 16-bit microcontrollers and since ADC10 gives 10-bit data, we need sixteen word-sized (16-bit) locations to store sixteen ADC samples. These 16 samples are to be averaged.

#define no_of_samples       16
....
unsigned int adc_pointer[no_of_samples];
....
....
ADC10DTC1 = no_of_samples;
ADC10SA = ((unsigned int)adc_pointer);

Grace generates the initialization code but the above lines must be edited by the coder.

In the main function, ADC10 is commanded to begin and store conversions. Once all sixteen ADC10 data are captured, they are summed up and averaged. The averaged data is shown on a LCD screen. Note that ADC10 interrupt is not used and not anywhere in the code the ADC10MEM register is directly read. The ADC is read and processed by the DMA, freeing up the CPU for other tasks. The process is repetitive, automomous and continuous.

adc_avg = 0;
P1OUT |= BIT0;
ADC10CTL0 &= ~ENC;
while (ADC10CTL1 & BUSY);
ADC10CTL0 |= (ENC | ADC10SC);

for(s = 0; s < no_of_samples; s++)
{
    adc_avg += adc_pointer[s];
}

adc_avg = (adc_avg / no_of_samples);
lcd_print(12, 1, adc_avg);
delay_ms(100);
P1OUT &= ~BIT0;
delay_ms(100);

 

Demo

DMA

Sensing a Sequence of ADC10 Channels with DMA

We have seen in the last segment that how we can compute the average value of an ADC10 channel without involving the CPU much and using the MSP430’s DMA/DTC controller. The DTC of MSP430s can be used in many innovative ways. One such way is to sense multiple channels in a row. In this method, the ADC is basically scanned in an orderly fashion from the coder-specified topmost channel to the bottommost (channel 0), saving the result of each ADC channel conversion in separate memory locations. Scanning a sequence of AD channels in this way has many potential applications. Consider the case of a solar charger controller. With one command you get both the input and output voltages, and currents quickly from your MSP430 micro. DMA-assisted ADC scanning is perhaps the most efficient and simple way to sense multiple ADC channels quickly.

Code Example

 

#include <msp430.h>
#include "delay.h"
#include "SW_I2C.h"
#include "PCF8574.h"
#include "lcd.h"


unsigned int ADC_value[2] = {0, 0};


void GPIO_graceInit(void);
void BCSplus_graceInit(void);
void ADC10_graceInit(void);
void System_graceInit(void);
void WDTplus_graceInit(void);
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value);


void main(void)
{
    /* Stop watchdog timer from timing out during initial start-up. */
    WDTCTL = WDTPW | WDTHOLD;

    /* initialize Config for the MSP430 GPIO */
    GPIO_graceInit();

    /* initialize Config for the MSP430 2xx family clock systems (BCS) */
    BCSplus_graceInit();

    /* initialize Config for the MSP430 10-bit Analog to Digital Converter (ADC) */
    ADC10_graceInit();

    /* initialize Config for the MSP430 System Registers */
    System_graceInit();

    /* initialize Config for the MSP430 WDT+ */
    WDTplus_graceInit();

    LCD_init();
    LCD_clear_home();

    LCD_goto(0, 0);
    LCD_putstr("A0:");

    LCD_goto(0, 1);
    LCD_putstr("A1:");

    while(1)
    {
        ADC10CTL0 &= ~ENC;
        while (ADC10CTL1 & BUSY);
        ADC10CTL0 |= (ENC | ADC10SC);
        lcd_print(12, 0, ADC_value[1]);
        lcd_print(12, 1, ADC_value[0]);
        delay_ms(400);
    };
}


void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

    /* Port 1 Output Register */
    P1OUT = 0;

    /* Port 1 Direction Register */
    P1DIR = 0;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Direction Register */
    P2DIR = 0;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */
}


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

    if (CALBC1_8MHZ != 0xFF)
    {
        /* Adjust this accordingly to your VCC rise time */
        __delay_cycles(100000);

        // Follow recommended flow. First, clear all DCOx and MODx bits. Then
        // apply new RSELx values. Finally, apply new DCOx and MODx bit values.
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_8MHZ;      /* Set DCO to 8MHz */
        DCOCTL = CALDCO_8MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_2 -- If XTS = 0, XT1 = VLOCLK ; If XTS = 1, XT1 = 3 - 16-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_2 | XCAP_1;

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}


void ADC10_graceInit(void)
{
    /* USER CODE START (section: ADC10_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: ADC10_graceInit_prologue) */

    /* disable ADC10 during initialization */
    ADC10CTL0 &= ~ENC;

    /*
     * Control Register 0
     *
     * ~ADC10SC -- No conversion
     * ~ENC -- Disable ADC
     * ~ADC10IFG -- Clear ADC interrupt flag
     * ~ADC10IE -- Disable ADC interrupt
     * ADC10ON -- Switch On ADC10
     * ~REFON -- Disable ADC reference generator
     * ~REF2_5V -- Set reference voltage generator = 1.5V
     * MSC -- Enable multiple sample and conversion
     * ~REFBURST -- Reference buffer on continuously
     * ~REFOUT -- Reference output off
     * ~ADC10SR -- Reference buffer supports up to ~200 ksps
     * ADC10SHT_2 -- 16 x ADC10CLKs
     * SREF_0 -- VR+ = VCC and VR- = VSS
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    ADC10CTL0 = ADC10ON | MSC | ADC10SHT_2 | SREF_0;

    /*
     * Control Register 1
     *
     * ~ADC10BUSY -- No operation is active
     * CONSEQ_3 -- Repeat sequence of channels
     * ADC10SSEL_0 -- ADC10OSC
     * ADC10DIV_0 -- Divide by 1
     * ~ISSH -- Input signal not inverted
     * ~ADC10DF -- ADC10 Data Format as binary
     * SHS_0 -- ADC10SC
     * INCH_1 -- ADC Channel 1
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    ADC10CTL1 = CONSEQ_3 | ADC10SSEL_0 | ADC10DIV_0 | SHS_0 | INCH_1;

    /* Analog (Input) Enable Control Register 0 */
    ADC10AE0 = 0x3;

    /*
     * Data Transfer Control Register 0
     *
     * ~ADC10TB -- One-block transfer mode
     * ADC10CT -- Data is transferred continuously after every conversion
     *
     * Note: ~ADC10TB indicates that ADC10TB has value zero
     */
    ADC10DTC0 = ADC10CT;

    /* Data Transfer Control Register 1 */
    ADC10DTC1 = 2;

    /* Data Transfer Start Address */
    ADC10SA = ((unsigned int)ADC_value);

    /* enable ADC10 */
    ADC10CTL0 |= ENC;

    /* USER CODE START (section: ADC10_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: ADC10_graceInit_epilogue) */
}


void System_graceInit(void)
{
    /* USER CODE START (section: System_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: System_graceInit_prologue) */

    /* Clear oscillator fault flag with software delay */
    do
    {
        // Clear OSC fault flag
        IFG1 &= ~OFIFG;

        // 50us delay
        __delay_cycles(400);
    } while (IFG1 & OFIFG);

    /*
     * SR, Status Register
     *
     * ~SCG1 -- Disable System clock generator 1
     * ~SCG0 -- Disable System clock generator 0
     * ~OSCOFF -- Oscillator On
     * ~CPUOFF -- CPU On
     * GIE -- General interrupt enable
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    __bis_SR_register(GIE);

    /* USER CODE START (section: System_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: System_graceInit_epilogue) */
}


void WDTplus_graceInit(void)
{
    /* USER CODE START (section: RTC_B_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: RTC_B_graceInit_prologue) */

    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * WDTHOLD -- Watchdog timer+ is stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * ~WDTTMSEL -- Watchdog mode
     * ~WDTCNTCL -- No action
     * ~WDTSSEL -- SMCLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * ~WDTIS1 -- Watchdog clock source bit1 disabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTHOLD;

    /* USER CODE START (section: RTC_B_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: RTC_B_graceInit_epilogue) */
}


void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value)
{
    char chr = 0;

    chr = ((value / 1000) + 0x30);
    LCD_goto(x_pos, y_pos);
    LCD_putchar(chr);

    chr = (((value / 100) % 10) + 0x30);
    LCD_goto((x_pos + 1), y_pos);
    LCD_putchar(chr);

    chr = (((value / 10) % 10) + 0x30);
    LCD_goto((x_pos + 2), y_pos);
    LCD_putchar(chr);

    chr = ((value % 10) + 0x30);
    LCD_goto((x_pos + 3), y_pos);
    LCD_putchar(chr);
}

 

Simulation

multi_adc

 

Explanation

Except some minor differences, the Grace setup here is same as the one used in the DMA example. The first difference is the Sequence of Channels selection, second is the number of ADC channels and finally the size of memory block.

seq

In the main, most of the things are same as in the DMA example. Since two channels are read, two memory locations store individual ADC data. Scanning starts from topmost channel to the bottommost and so the bottommost memory location will hold the data of the topmost channel and vice versa. In short, the memory locations are flipped with respect to ADC channels.

ADC10CTL0 &= ~ENC;
while (ADC10CTL1 & BUSY);
ADC10CTL0 |= (ENC | ADC10SC);
lcd_print(12, 0, ADC_value[1]);
lcd_print(12, 1, ADC_value[0]);
delay_ms(400);

 

Demo

seq

Sensing Multiple Out-of-Sequence ADC10 Channels with DMA

Due to several obligations and design constraints, we may often fall in situations where we would not be able to enjoy the sequential DTC-assisted ADC scanning feature demonstrated in the previous section. ADC channels may not be in an orderly sequence. Still we can apply similar techniques as in ADC scanning but certain things are needed to be kept in mind in such cases.

Code Example

 

#include <msp430.h>
#include "delay.h"
#include "SW_I2C.h"
#include "PCF8574.h"
#include "lcd.h"

 


#define no_of_channels 12

 


unsigned int ADC_value[no_of_channels];

 


void GPIO_graceInit(void);
void BCSplus_graceInit(void);
void ADC10_graceInit(void);
void System_graceInit(void);
void WDTplus_graceInit(void);
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value);

 


void main(void)
{
    /* Stop watchdog timer from timing out during initial start-up. */
    WDTCTL = WDTPW | WDTHOLD;

 

    /* initialize Config for the MSP430 GPIO */
    GPIO_graceInit();

 

    /* initialize Config for the MSP430 2xx family clock systems (BCS) */
    BCSplus_graceInit();

 

    /* initialize Config for the MSP430 10-bit Analog to Digital Converter (ADC) */
    ADC10_graceInit();

 

    /* initialize Config for the MSP430 System Registers */
    System_graceInit();

 

    /* initialize Config for the MSP430 WDT+ */
    WDTplus_graceInit();

 

    LCD_init();
    LCD_clear_home();

 


    while(1)
    {
        ADC10CTL0 &= ~ENC;
        while (ADC10CTL1 & BUSY);
        ADC10CTL0 |= (ENC | ADC10SC);

 

        LCD_goto(0, 0);
        LCD_putstr("A03:");
        lcd_print(12, 0, ADC_value[8]);
        LCD_goto(0, 1);
        LCD_putstr("A11:");
        lcd_print(12, 1, ADC_value[0]);

 

        P1OUT ^= BIT0;
        delay_ms(900);

 

        ADC10CTL0 &= ~ENC;
        while (ADC10CTL1 & BUSY);
        ADC10CTL0 |= (ENC | ADC10SC);

 

        LCD_goto(0, 0);
        LCD_putstr("A00:");
        lcd_print(12, 0, ADC_value[11]);
        LCD_goto(0, 1);
        LCD_putstr("A10:");
        lcd_print(12, 1, ADC_value[1]);

 

        P1OUT ^= BIT0;
        delay_ms(900);

 

    };
}

 


void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

 

    /* Port 1 Output Register */
    P1OUT = 0;

 

    /* Port 1 Direction Register */
    P1DIR = BIT0;

 

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

 

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

 

    /* Port 2 Output Register */
    P2OUT = 0;

 

    /* Port 2 Port Select Register */
    P2SEL &= ~(BIT6 | BIT7);

 

    /* Port 2 Direction Register */
    P2DIR = 0;

 

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

 

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

 

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */
}

 


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

 

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

 

    if (CALBC1_8MHZ != 0xFF)
    {
        /* Adjust this accordingly to your VCC rise time */
        __delay_cycles(100000);

 

        // Follow recommended flow. First, clear all DCOx and MODx bits. Then
        // apply new RSELx values. Finally, apply new DCOx and MODx bit values.
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_8MHZ;      /* Set DCO to 8MHz */
        DCOCTL = CALDCO_8MHZ;
    }

 

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

 

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_2 -- If XTS = 0, XT1 = VLOCLK ; If XTS = 1, XT1 = 3 - 16-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_2 | XCAP_1;

 

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}

 


void ADC10_graceInit(void)
{
    /* USER CODE START (section: ADC10_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: ADC10_graceInit_prologue) */

 

    /* disable ADC10 during initialization */
    ADC10CTL0 &= ~ENC;

 

    /*
     * Control Register 0
     *
     * ~ADC10SC -- No conversion
     * ~ENC -- Disable ADC
     * ~ADC10IFG -- Clear ADC interrupt flag
     * ~ADC10IE -- Disable ADC interrupt
     * ADC10ON -- Switch On ADC10
     * ~REFON -- Disable ADC reference generator
     * ~REF2_5V -- Set reference voltage generator = 1.5V
     * MSC -- Enable multiple sample and conversion
     * ~REFBURST -- Reference buffer on continuously
     * ~REFOUT -- Reference output off
     * ~ADC10SR -- Reference buffer supports up to ~200 ksps
     * ADC10SHT_2 -- 16 x ADC10CLKs
     * SREF_0 -- VR+ = VCC and VR- = VSS
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    ADC10CTL0 = ADC10ON | MSC | ADC10SHT_2 | SREF_0;

 

    /*
     * Control Register 1
     *
     * ~ADC10BUSY -- No operation is active
     * CONSEQ_3 -- Repeat sequence of channels
     * ADC10SSEL_0 -- ADC10OSC
     * ADC10DIV_0 -- Divide by 1
     * ~ISSH -- Input signal not inverted
     * ~ADC10DF -- ADC10 Data Format as binary
     * SHS_0 -- ADC10SC
     * INCH_11 -- ADC convert VCC
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    ADC10CTL1 = CONSEQ_3 | ADC10SSEL_0 | ADC10DIV_0 | SHS_0 | INCH_11;

 

    /* Analog (Input) Enable Control Register 0 */
    ADC10AE0 = 0x8;

 

    /*
     * Data Transfer Control Register 0
     *
     * ~ADC10TB -- One-block transfer mode
     * ADC10CT -- Data is transferred continuously after every conversion
     *
     * Note: ~ADC10TB indicates that ADC10TB has value zero
     */
    ADC10DTC0 = ADC10CT;

 

    /* Data Transfer Control Register 1 */
    ADC10DTC1 = 12;

 

    /* Data Transfer Start Address */
    ADC10SA = ((unsigned int)ADC_value);

 

    /* enable ADC10 */
    ADC10CTL0 |= ENC;

 

    /* USER CODE START (section: ADC10_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: ADC10_graceInit_epilogue) */
}

 


void System_graceInit(void)
{
    /* USER CODE START (section: System_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: System_graceInit_prologue) */

 

    /* Clear oscillator fault flag with software delay */
    do
    {
        // Clear OSC fault flag
        IFG1 &= ~OFIFG;

 

        // 50us delay
        __delay_cycles(400);
    } while (IFG1 & OFIFG);

 

    /*
     * SR, Status Register
     *
     * ~SCG1 -- Disable System clock generator 1
     * ~SCG0 -- Disable System clock generator 0
     * ~OSCOFF -- Oscillator On
     * ~CPUOFF -- CPU On
     * GIE -- General interrupt enable
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    __bis_SR_register(GIE);

 

    /* USER CODE START (section: System_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: System_graceInit_epilogue) */
}

 


void WDTplus_graceInit(void)
{
    /* USER CODE START (section: RTC_B_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: RTC_B_graceInit_prologue) */

 

    /*
     * WDTCTL, Watchdog Timer+ Register
     *
     * WDTPW -- Watchdog password
     * WDTHOLD -- Watchdog timer+ is stopped
     * ~WDTNMIES -- NMI on rising edge
     * ~WDTNMI -- Reset function
     * ~WDTTMSEL -- Watchdog mode
     * ~WDTCNTCL -- No action
     * ~WDTSSEL -- SMCLK
     * ~WDTIS0 -- Watchdog clock source bit0 disabled
     * ~WDTIS1 -- Watchdog clock source bit1 disabled
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    WDTCTL = WDTPW | WDTHOLD;

 

    /* USER CODE START (section: RTC_B_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: RTC_B_graceInit_epilogue) */
}

 


void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value)
{
    char chr = 0;

 

    chr = ((value / 1000) + 0x30);
    LCD_goto(x_pos, y_pos);
    LCD_putchar(chr);

 

    chr = (((value / 100) % 10) + 0x30);
    LCD_goto((x_pos + 1), y_pos);
    LCD_putchar(chr);

 

    chr = (((value / 10) % 10) + 0x30);
    LCD_goto((x_pos + 2), y_pos);
    LCD_putchar(chr);

 

    chr = ((value % 10) + 0x30);
    LCD_goto((x_pos + 3), y_pos);
    LCD_putchar(chr);
}

 

Simulation

multi_adc_out

Explanation

In the last example, the ADC channels that were scanned were in an order. Here, however, the story is different but the concept is the same.

multi

Note that all channels are enabled since the topmost channel we have measured here is the Measure VCC channel but intentionally the memory block size is set 1 instead of 12 and only one external channel is enabled. Well, there are some proof-of-concepts to show. The first proof-of-concept is the fact that since we have to set memory block size and memory pointer on our own in the initialization code, it doesn’t matter what these values are in Grace. Secondly, only external channel A3 is sensed since its external I/O is enabled. The rest of the external channels are ignored. This is why A0 reads floating values when the code runs because its external I/O to ADC is disconnected and furthermore it is used to blink the onboard Launchpad LED connected with it. Channel A10 (internal temperature sensor) and A11 (internal VCC sensor) are internal channels and so are not dependent on external I/Os. These channels read as they should. Despite scanning all channels, we can decide which ones we need. Here I demoed sensing channels A0, A3, A10 and A11. Note that these channels are out of a regular or orderly sequence, hence the name of the topic Sensing Multiple Out-of-Sequence ADC10 Channels with DMA.

Demo

multi_ADC (2) multi_ADC (1)

Capacitive Touch Overview

Capacitive touch sensing technology is nothing new at present. Cell phones, smartwatches, tablets, portable music players, and even many home appliances that you can name no longer have mechanical touch keys/switches/buttons/variables. All such switching elements have been replaced by more smart and elegant capacitive touch sensors. At present due to this trend even the tiniest new generation microcontroller has capacitive touch sensing capability. When it comes to TI micros, this trend seems to explode to a whole new level.

I haven’t seen so far, any TI MCU without capacitive touch feature. Almost all digital I/Os can be used for capacitive touch as there is no specific dedicated I/Os for such implementations. This makes capacitive touch sensing easy in terms of hardware design. Designing capacitive touch sensors is another story.

Block Diagram

In MSP430s, capacitive touch sensing requires two separate timers. These timers create independent time bases and these time bases are compared against each other. One of these time bases is fixed while the other is dependent of the value of touch sensor’s capacitance. When a touch is detected, capacitance changes. This creates a significant difference between the time bases which otherwise remains fairly constant. This is how a touch is detected. This is also a mean to measure capacitance other than touch sensing.

For making all these tasks simple and for rapid development, TI has provided a dedicated Hardware Abstraction Layer (HAL) library for capacitive touch sensing. Use TI’s Resource Explorer to download code examples and library files. Programmer’s Guide SLAA490D discusses implementation of capacitive touch sensing in terms of coding, hardware combinations and others. This page is also an equally important one. Apart from these, there are good literatures from TI that discuss tons of valuable info about capacitive touch sensing technology – from hardware designs to implementation methods.

TI discusses about three methods with which capacitive touch sensing can be achieved. These methods are as follows:

Relaxation Oscillator (RO)

This method counts the number of relaxation oscillator cycles within a fixed period called gate time. Usually for the Value-Line Devices (VLD), the PinOsc feature is used in this method and the key thing to note here is the fact that no external components like external resistors or capacitors are needed to implement capacitive touch sensors. It is this method that we will be observing in this article as our target devices are VLDs.

Resistor Capacitor (RC)

This method is just the opposite of RO method. In this method, the gate time is variable as it is the representation of capacitance while the oscillator time period is fixed. The fixed time base is connected to an internal MSP430 oscillator like the DCO. The variable time base is connected to a capacitor and resistor network. The time it takes to charge and discharge the capacitor through the resistor is now the gate time. The RC method can be realized with any MSP430.

Fast Relaxation Oscillator (fRO)

This method is similar to the RC method except that the variable gate period is created with a relaxation oscillator instead of the charge and discharge time.

TI’s capacitive touch library documentation also recommends which hardware combination to use for a given family of MSP430 microcontroller. Though these are not mandatory, following these recommended combinations reduces code development time and best performances.

HAL_Recommendation

Single-Channel Capacitive Touch

This is the very first capacitive touch example we will look at. Although a single capacitive touch button has very little use in real life, it is good for realizing the mechanism behind this capacitive touch technology. If this example is well understood then everything related to capacitive touch sensing will be realized without any confusion or doubt.

single_multi_element

Code Example

 

structure.h (top part only)

#ifndef CTS_STRUCTURE_H_
#define CTS_STRUCTURE_H_

#include "msp430.h"
#include <stdint.h>

/* Public Globals */

// Middle Element on the LaunchPad Capacitive Touch BoosterPack
extern const struct Element middle_element;
// One Button Sensor
extern const struct Sensor one_button;

//****** RAM ALLOCATION ********************************************************
// TOTAL_NUMBER_OF_ELEMENTS represents the total number of elements used, even if
// they are going to be segmented into seperate groups.  This defines the
// RAM allocation for the baseline tracking.  If only the TI_CAPT_Raw function
// is used, then this definition should be removed to conserve RAM space.
#define TOTAL_NUMBER_OF_ELEMENTS 1
// If the RAM_FOR_FLASH definition is removed, then the appropriate HEAP size
// must be allocated. 2 bytes * MAXIMUM_NUMBER_OF_ELEMENTS_PER_SENSOR + 2 bytes
// of overhead.
#define RAM_FOR_FLASH
//****** Structure Array Definition ********************************************
// This defines the array size in the sensor strucure.  In the event that
// RAM_FOR_FLASH is defined, then this also defines the amount of RAM space
// allocated (global variable) for computations.
#define MAXIMUM_NUMBER_OF_ELEMENTS_PER_SENSOR  1
//****** Choosing a  Measurement Method ****************************************
// These variables are references to the definitions found in structure.c and
// must be generated per the application.
// possible values for the method field

// OSCILLATOR DEFINITIONS
//#define RO_COMPAp_TA0_WDTp            64
#define RO_PINOSC_TA0_WDTp              65
. . . .

 

structure.c

#include "structure.h"

// Middle Element (P2.5)
const struct Element middle_element =
{
    .inputPxselRegister = (uint8_t *)&P2SEL,
    .inputPxsel2Register = (uint8_t *)&P2SEL2,
    .inputBits = BIT4,
    // When using an abstracted function to measure the element
    // the 100*(maxResponse - threshold) < 0xFFFF
    // ie maxResponse - threshold < 655
    .maxResponse = (450 + 655),
    .threshold = 450
};

// One Button Sensor
const struct Sensor one_button =
{
    .halDefinition = RO_PINOSC_TA0_WDTp,  // Sensing Method
    .numElements = 1,                     // # of Elements
    .baseOffset = 0,                      // First element index = 0
    // Pointer to elements
    .arrayPtr[0] = &middle_element,       // point to middle element
    // Timer Information
    .measGateSource= GATE_WDT_ACLK,     //  0->SMCLK, 1-> ACLK
    .accumulationCycles= WDTp_GATE_64   //64 - Default
};

 

main.c

#include <msp430.h>
#include "CTS_Layer.h"
#include "CTS_HAL.h"
#include "structure.h"


#define DELAY 4000


void GPIO_graceInit(void);
void BCSplus_graceInit(void);
void sleep(unsigned int time);


#pragma vector = TIMER0_A0_VECTOR
__interrupt void ISR_Timer0_A0(void)
{
  TA0CTL &= ~MC_1;
  TA0CCTL0 &= ~(CCIE);
  __bic_SR_register_on_exit(LPM3_bits + GIE);
}


#pragma vector = PORT2_VECTOR,             \
  PORT1_VECTOR,                          \
  TIMER0_A1_VECTOR,                      \
  USI_VECTOR,                            \
  NMI_VECTOR,COMPARATORA_VECTOR,         \
  ADC10_VECTOR
__interrupt void ISR_trap(void)
{
  // the following will cause an access violation which results in a PUC reset
  WDTCTL = 0;
}



// Main Function
void main(void)
{
  WDTCTL = WDTPW + WDTHOLD;             // Stop watchdog timer

  GPIO_graceInit();
  BCSplus_graceInit();

  TI_CAPT_Init_Baseline(&one_button);
  TI_CAPT_Update_Baseline(&one_button, 6);

  while (1)
  {
    if(TI_CAPT_Button(&one_button))
    {
        P1OUT ^= BIT0;
        P1OUT |= BIT6;
    }

    else
    {
        P1OUT &= ~BIT6;
    }

    sleep(DELAY);

  }
} // End Main


void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

    /* Port 1 Output Register */
    P1OUT = 0;

    /* Port 1 Direction Register */
    P1DIR = BIT0 | BIT6;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Port Select Register */
    P2SEL &= ~(BIT6 | BIT7);

    /* Port 2 Direction Register */
    P2DIR = 0;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */

}


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_3 -- Divide by 8
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_3;

    if (CALBC1_12MHZ != 0xFF) {
        /* Adjust this accordingly to your VCC rise time */
        __delay_cycles(100000);

        /* Follow recommended flow. First, clear all DCOx and MODx bits. Then
         * apply new RSELx values. Finally, apply new DCOx and MODx bit values.
         */
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_12MHZ;     /* Set DCO to 12MHz */
        DCOCTL = CALDCO_12MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_2 -- If XTS = 0, XT1 = VLOCLK ; If XTS = 1, XT1 = 3 - 16-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_2 | XCAP_1;

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}


void sleep(unsigned int time)
{
    TA0CCR0 = time;
    TA0CTL = TASSEL_1 | ID_0 | MC_1 | TACLR;
    TA0CCTL0 &= ~CCIFG;
    TA0CCTL0 |= CCIE;
    __bis_SR_register(LPM3_bits+GIE);
    __no_operation();
}

 

Simulation

Capacitive touch sensing cannot be simulated in software like Proteus VSM.

Single Sim

Explanation

In Grace, there is no option to generate configuration for capacitive touch. However, MSP430Ware demos some examples for such. It is better to use the examples given there for project start up. I did the same. However, for a beginner it is must to which things to modify and why do so. Implementing capacitive touch requires three pairs of TI’s HAL library files. These are as follows and are needed to be integrated with your project:

Files

Of these files, the important files that are needed to be edited to set capacitive touch properties of capacitive touch sensor(s) are the structure header and source files. The rest two pairs of files should be left untouched.

In the structure header file, external constant structure named middle_element defines I/O port(s) properties of capacitive sensor(s). Similarly, one_button defines timer/watchdog properties. These two external structures have other functions too. We will see these properties when will discuss the source file. Next, we have to define how many capacitive touch sensors are there in our design. Finally, we have to set which hardware combination to use. In our case, Timer0_A, watchdog timer and digital I/O’s PinOsc functionality are used to implement capacitive touch. The rest of the file is not needed to be changed anywhere.

// Middle Element on the LaunchPad Capacitive Touch BoosterPack
extern const struct Element middle_element;
// One Button Sensor
extern const struct Sensor one_button;
//****** RAM ALLOCATION ********************************************************
// TOTAL_NUMBER_OF_ELEMENTS represents the total number of elements used, even if
// they are going to be segmented into seperate groups.  This defines the
// RAM allocation for the baseline tracking.  If only the TI_CAPT_Raw function
// is used, then this definition should be removed to conserve RAM space.
#define TOTAL_NUMBER_OF_ELEMENTS 1
// If the RAM_FOR_FLASH definition is removed, then the appropriate HEAP size
// must be allocated. 2 bytes * MAXIMUM_NUMBER_OF_ELEMENTS_PER_SENSOR + 2 bytes
// of overhead.
#define RAM_FOR_FLASH
//****** Structure Array Definition ********************************************
// This defines the array size in the sensor strucure.  In the event that
// RAM_FOR_FLASH is defined, then this also defines the amount of RAM space
// allocated (global variable) for computations.
#define MAXIMUM_NUMBER_OF_ELEMENTS_PER_SENSOR  1
//****** Choosing a  Measurement Method ****************************************
// These variables are references to the definitions found in structure.c and
// must be generated per the application.
// possible values for the method field
// OSCILLATOR DEFINITIONS
//#define RO_COMPAp_TA0_WDTp            64
#define RO_PINOSC_TA0_WDTp              65

Now it is time to explain the structure source file.

const struct Element middle_element =
{
    .inputPxselRegister = (uint8_t *)&P2SEL,
    .inputPxsel2Register = (uint8_t *)&P2SEL2,
    .inputBits = BIT4,
    // When using an abstracted function to measure the element
    // the 100*(maxResponse - threshold) < 0xFFFF
    // ie maxResponse - threshold < 655
    .maxResponse = (450 + 655),
    .threshold = 450
};

PxSEL and PxSEL2 bits internally set PinOsc feature. Since this example demonstrated one capacitive touch button only one element is connected to BIT4 of port P2. Optionally, we can set the threshold limit on which we can positively identify a touch.

// One Button Sensor
const struct Sensor one_button =
{
    .halDefinition = RO_PINOSC_TA0_WDTp,  // Sensing Method
    .numElements = 1,                     // # of Elements
    .baseOffset = 0,                      // First element index = 0
    // Pointer to elements
    .arrayPtr[0] = &middle_element,       // point to middle element
    // Timer Information
    .measGateSource= GATE_WDT_ACLK,     //  0->SMCLK, 1-> ACLK
    .accumulationCycles= WDTp_GATE_64   //64 - Default
};

The second part of the source file details which method is used along with the name and number of element(s) to sense. Basically, here we have nothing to do other than to let this part know the structure name of our element(s) and the number of sensors.

#pragma vector = TIMER0_A0_VECTOR
__interrupt void ISR_Timer0_A0(void)
{
  TA0CTL &= ~MC_1;
  TA0CCTL0 &= ~(CCIE);
  __bic_SR_register_on_exit(LPM3_bits + GIE);
}
#pragma vector = PORT2_VECTOR,             \
  PORT1_VECTOR,                          \
  TIMER0_A1_VECTOR,                      \
  USI_VECTOR,                            \
  NMI_VECTOR,COMPARATORA_VECTOR,         \
  ADC10_VECTOR
__interrupt void ISR_trap(void)
{
  // the following will cause an access violation which results in a PUC reset
  WDTCTL = 0;
}

Two interrupts are needed to be called. The first is the Timer0_A ISR. This acts like a wakeup alarm. Once the capacitive sensing and other tasks in the main loop are completed, this timer is started and low power mode is entered. After timeout, this timer interrupts causing the main tasks to reoccur and leave low power mode momentarily.  The second is a Trap ISR. Trap ISR ensures that if for some reason something happens that you didn’t expect it will reset the MCU. The interrupt vectors assigned here are those vectors which we won’t be using. If any of these pop-up, a reset will occur.

void main(void)
{
  WDTCTL = WDTPW + WDTHOLD;             // Stop watchdog timer
  GPIO_graceInit();
  BCSplus_graceInit();
  TI_CAPT_Init_Baseline(&one_button);
  TI_CAPT_Update_Baseline(&one_button, 6);
  while (1)
  {
    if(TI_CAPT_Button(&one_button))
    {
        P1OUT ^= BIT0;
        P1OUT |= BIT6;
    }
    else
    {
        P1OUT &= ~BIT6;
    }
    sleep(DELAY);
  }
}

The main function is perhaps the smallest one here. Except for the other parts with which by now we are familiar, there are a few new lines of code. Just before the main loop, the two lines of code right above it, initialize the capacitive touch sensor. Number 6 in the function TI_CAPT_Update_Baseline states the number of samples to capture for accurately sensing a touch. If a valid touch is detected, the LEDs of Launchpad board are toggled.

Demo

IMG_0656

Multi-Channel Capacitive Touch

Unlike single capacitive touch buttons, multiple capacitive sensors have several potential uses. These include multi-touch buttons, sliders, wheels, rotary encoders, etc. Here we will have a look at multiple capacitive touch buttons and the example demonstrated here is basically the extension of the last one. However, for multi-touch capacitive touch sensors, there are twists in the software end apart from hardware design and considerations.

Capacitive Touch Booster

Code Example

 

structure.h (top part only)

#ifndef CTS_STRUCTURE_H_
#define CTS_STRUCTURE_H_

#include "msp430.h"
#include <stdint.h>

/* Public Globals */
extern const struct Element middle_element;
extern const struct Element up_element;
extern const struct Element down_element;

// Identify all sensors defined in structure.c
extern const struct Sensor multi_buttons;

//****** RAM ALLOCATION ********************************************************
// TOTAL_NUMBER_OF_ELEMENTS represents the total number of elements used, even if
// they are going to be segmented into seperate groups.  This defines the
// RAM allocation for the baseline tracking.  If only the TI_CAPT_Raw function
// is used, then this definition should be removed to conserve RAM space.
#define TOTAL_NUMBER_OF_ELEMENTS 3
// If the RAM_FOR_FLASH definition is removed, then the appropriate HEAP size
// must be allocated. 2 bytes * MAXIMUM_NUMBER_OF_ELEMENTS_PER_SENSOR + 2 bytes
// of overhead.
#define RAM_FOR_FLASH
//****** Structure Array Definition ********************************************
// This defines the array size in the sensor strucure.  In the event that
// RAM_FOR_FLASH is defined, then this also defines the amount of RAM space
// allocated (global variable) for computations.
#define MAXIMUM_NUMBER_OF_ELEMENTS_PER_SENSOR  3
//****** Choosing a  Measurement Method ****************************************
// These variables are references to the definitions found in structure.c and
// must be generated per the application.
// possible values for the method field

// OSCILLATOR DEFINITIONS
//#define RO_COMPAp_TA0_WDTp            64
#define RO_PINOSC_TA0_WDTp              65
. . . .

 

structure.c

#include "structure.h"

// Middle Element (P2.5)
const struct Element middle_element = {

              .inputPxselRegister = (unsigned char *)&P2SEL,
              .inputPxsel2Register = (unsigned char *)&P2SEL2,
              .inputBits = BIT5,
              // When using an abstracted function to measure the element
              // the 100*(maxResponse - threshold) < 0xFFFF
              // ie maxResponse - threshold < 655
              .maxResponse = (100 + 655),
              .threshold = 100
};

// Up Element (P2.4)
const struct Element up_element = {

              .inputPxselRegister = (unsigned char *)&P2SEL,
              .inputPxsel2Register = (unsigned char *)&P2SEL2,
              .inputBits = BIT4,
              // When using an abstracted function to measure the element
              // the 100*(maxResponse - threshold) < 0xFFFF
              // ie maxResponse - threshold < 655
              .maxResponse = (100 + 655),
              .threshold = 100
};


// Down Element (P2.3)
const struct Element down_element =
{

    .inputPxselRegister = (unsigned char *)&P2SEL,
    .inputPxsel2Register = (unsigned char *)&P2SEL2,
    .inputBits = BIT3,
    // When using an abstracted function to measure the element
    // the 100*(maxResponse - threshold) < 0xFFFF
    // ie maxResponse - threshold < 655
    .maxResponse = (100 + 655),
    .threshold = 100
};

//*** CAP TOUCH HANDLER *******************************************************/
// This defines the grouping of sensors, the method to measure change in
// capacitance, and the function of the group

const struct Sensor multi_buttons =
{
    .halDefinition = RO_PINOSC_TA0_WDTp,
    .numElements = 3,
    .baseOffset = 0,
    // Pointer to elements
    .arrayPtr[0] = &up_element,           // point to up element
    .arrayPtr[1] = &down_element,         // point to down element
    .arrayPtr[2] = &middle_element,       // point to middle element
    // Timer Information
    .measGateSource= GATE_WDT_ACLK,     //  0->SMCLK, 1-> ACLK
    .accumulationCycles= WDTp_GATE_64   //64 - Default
};

 

 

main.c

#include <msp430.h>
#include "CTS_Layer.h"
#include "CTS_HAL.h"
#include "structure.h"


#define DELAY 4000


struct Element * keyPressed;


void GPIO_graceInit(void);
void BCSplus_graceInit(void);
void sleep(unsigned int time);


#pragma vector = TIMER0_A0_VECTOR
__interrupt void ISR_Timer0_A0(void)
{
  TA0CTL &= ~MC_1;
  TA0CCTL0 &= ~(CCIE);
  __bic_SR_register_on_exit(LPM3_bits + GIE);
}


#pragma vector = PORT2_VECTOR,             \
  PORT1_VECTOR,                          \
  TIMER0_A1_VECTOR,                      \
  USI_VECTOR,                            \
  NMI_VECTOR,COMPARATORA_VECTOR,         \
  ADC10_VECTOR
__interrupt void ISR_trap(void)
{
  // the following will cause an access violation which results in a PUC reset
  WDTCTL = 0;
}



// Main Function
void main(void)
{
  WDTCTL = WDTPW + WDTHOLD;             // Stop watchdog timer

  GPIO_graceInit();
  BCSplus_graceInit();

  TI_CAPT_Init_Baseline(&multi_buttons);
  TI_CAPT_Update_Baseline(&multi_buttons, 25);

  // Main loop starts here
  while (1)
  {
    keyPressed = (struct Element *)TI_CAPT_Buttons(&multi_buttons);

    if(keyPressed)
    {
        // Up Element
        if(keyPressed == &up_element)
        {
            P1OUT |= BIT0;
        }
        // Down Element
        if(keyPressed == &down_element)
        {
            P1OUT |= BIT6;
        }
        // Middle Element
        if(keyPressed == &middle_element)
        {
            P1OUT = 0;
        }
    }

    sleep(DELAY);
  }

} // End Main


void GPIO_graceInit(void)
{
    /* USER CODE START (section: GPIO_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: GPIO_graceInit_prologue) */

    /* Port 1 Output Register */
    P1OUT = 0;

    /* Port 1 Direction Register */
    P1DIR = BIT0 | BIT6;

    /* Port 1 Interrupt Edge Select Register */
    P1IES = 0;

    /* Port 1 Interrupt Flag Register */
    P1IFG = 0;

    /* Port 2 Output Register */
    P2OUT = 0;

    /* Port 2 Port Select Register */
    P2SEL &= ~(BIT6 | BIT7);

    /* Port 2 Direction Register */
    P2DIR = 0;

    /* Port 2 Interrupt Edge Select Register */
    P2IES = 0;

    /* Port 2 Interrupt Flag Register */
    P2IFG = 0;

    /* USER CODE START (section: GPIO_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: GPIO_graceInit_epilogue) */

}


void BCSplus_graceInit(void)
{
    /* USER CODE START (section: BCSplus_graceInit_prologue) */
    /* User initialization code */
    /* USER CODE END (section: BCSplus_graceInit_prologue) */

    /*
     * Basic Clock System Control 2
     *
     * SELM_0 -- DCOCLK
     * DIVM_0 -- Divide by 1
     * ~SELS -- DCOCLK
     * DIVS_0 -- Divide by 1
     * ~DCOR -- DCO uses internal resistor
     *
     * Note: ~<BIT> indicates that <BIT> has value zero
     */
    BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;

    if (CALBC1_8MHZ != 0xFF)
    {
        /* Adjust this accordingly to your VCC rise time */
        __delay_cycles(100000);

        // Follow recommended flow. First, clear all DCOx and MODx bits. Then
        // apply new RSELx values. Finally, apply new DCOx and MODx bit values.
        DCOCTL = 0x00;
        BCSCTL1 = CALBC1_8MHZ;      /* Set DCO to 8MHz */
        DCOCTL = CALDCO_8MHZ;
    }

    /*
     * Basic Clock System Control 1
     *
     * XT2OFF -- Disable XT2CLK
     * ~XTS -- Low Frequency
     * DIVA_0 -- Divide by 1
     *
     * Note: ~XTS indicates that XTS has value zero
     */
    BCSCTL1 |= XT2OFF | DIVA_0;

    /*
     * Basic Clock System Control 3
     *
     * XT2S_0 -- 0.4 - 1 MHz
     * LFXT1S_2 -- If XTS = 0, XT1 = VLOCLK ; If XTS = 1, XT1 = 3 - 16-MHz crystal or resonator
     * XCAP_1 -- ~6 pF
     */
    BCSCTL3 = XT2S_0 | LFXT1S_2 | XCAP_1;

    /* USER CODE START (section: BCSplus_graceInit_epilogue) */
    /* User code */
    /* USER CODE END (section: BCSplus_graceInit_epilogue) */
}


void sleep(unsigned int time)
{
    TA0CCR0 = time;
    TA0CTL = TASSEL_1 | ID_0 | MC_1 | TACLR;
    TA0CCTL0 &= ~CCIFG;
    TA0CCTL0 |= CCIE;
    __bis_SR_register(LPM3_bits + GIE);
    __no_operation();
}

 

Simulation

Capacitive touch sensing cannot be simulated in software like Proteus VSM.

Multi Sim

Explanation

This example uses the same ideas as in the previous example. The header file is slightly modified. Three sensor elements are used and so the number of sensor is set 3. The elements are named differently since each are independent of the other.

extern const struct Element middle_element;
extern const struct Element up_element;
extern const struct Element down_element;

 

// Identify all sensors defined in structure.c
extern const struct Sensor multi_buttons;

 

//****** RAM ALLOCATION ********************************************************
// TOTAL_NUMBER_OF_ELEMENTS represents the total number of elements used, even if
// they are going to be segmented into seperate groups.  This defines the
// RAM allocation for the baseline tracking.  If only the TI_CAPT_Raw function
// is used, then this definition should be removed to conserve RAM space.
#define TOTAL_NUMBER_OF_ELEMENTS 3
....
//****** Structure Array Definition ********************************************
// This defines the array size in the sensor strucure.  In the event that
// RAM_FOR_FLASH is defined, then this also defines the amount of RAM space
// allocated (global variable) for computations.
#define MAXIMUM_NUMBER_OF_ELEMENTS_PER_SENSOR  3

The main difference is present in the structure source file. The three different elements are declared independently despite being in the same port. This is so because we need to identify them when a touch is detected.

const struct Element middle_element = {

 

              .inputPxselRegister = (unsigned char *)&P2SEL,
              .inputPxsel2Register = (unsigned char *)&P2SEL2,
              .inputBits = BIT5,
              // When using an abstracted function to measure the element
              // the 100*(maxResponse - threshold) < 0xFFFF
              // ie maxResponse - threshold < 655
              .maxResponse = (100 + 655),
              .threshold = 100
};

 

// Up Element (P2.4)
const struct Element up_element = {

 

              .inputPxselRegister = (unsigned char *)&P2SEL,
              .inputPxsel2Register = (unsigned char *)&P2SEL2,
              .inputBits = BIT4,
              // When using an abstracted function to measure the element
              // the 100*(maxResponse - threshold) < 0xFFFF
              // ie maxResponse - threshold < 655
              .maxResponse = (100 + 655),
              .threshold = 100
};

 

// Down Element (P2.3)
const struct Element down_element =
{

 

    .inputPxselRegister = (unsigned char *)&P2SEL,
    .inputPxsel2Register = (unsigned char *)&P2SEL2,
    .inputBits = BIT3,
    // When using an abstracted function to measure the element
    // the 100*(maxResponse - threshold) < 0xFFFF
    // ie maxResponse - threshold < 655
    .maxResponse = (100 + 655),
    .threshold = 100
};

 

//*** CAP TOUCH HANDLER *******************************************************/
// This defines the grouping of sensors, the method to measure change in
// capacitance, and the function of the group

 

const struct Sensor multi_buttons =
{
    .halDefinition = RO_PINOSC_TA0_WDTp,
    .numElements = 3,
    .baseOffset = 0,
    // Pointer to elements
    .arrayPtr[0] = &up_element,           // point to up element
    .arrayPtr[1] = &down_element,         // point to down element
    .arrayPtr[2] = &middle_element,       // point to middle element
    // Timer Information
    .measGateSource= GATE_WDT_ACLK,     //  0->SMCLK, 1-> ACLK
    .accumulationCycles= WDTp_GATE_64   //64 - Default
};

Similarly, the sensor structure is also modified for these three elements. The main code is almost identical to the single sensor demo. However, the variable keyPressed is used to check which element was touched. According to touch on different element states of Launchpad board LEDs are altered.

keyPressed = (struct Element *)TI_CAPT_Buttons(&multi_buttons);

 

if(keyPressed)
{
    // Up Element
    if(keyPressed == &up_element)
    {
        P1OUT |= BIT0;
    }
    // Down Element
    if(keyPressed == &down_element)
    {
        P1OUT |= BIT6;
    }
    // Middle Element
    if(keyPressed == &middle_element)
    {
        P1OUT = 0;
    }
}

 

Demo

IMG_0644

A Brief Intro of MSP430F5529LP Launchpad and TI’s Driver Library

Up till now we have seen and used the power of Grace configuration tool, MSP430G2xxx devices and only mentioned the name Driver Library. At present for micros with too many hardware resources, it is really very difficult to go through their individual datasheet line-by-line and memorize register names and their purposes. However, we just need peripheral initialization once in a code and it should not take much of a project’s development time. Likewise, when moving from one micro sub family to another or just interchanging devices within a given family, there should be some similarity in coding and hardware or else it be really very much difficult to keep track of everything. To overcome such issues and many others, mainstream embedded system solution manufacturers like TI offer different code development solutions ranging from graphical tools like Grace to code examples/templates as in TI’s Resource Explorer. The Peripheral Driver Library or simply DriverLib is one solution that somewhat resides between aforementioned two. It is a set of drivers for accessing the peripherals found on MSP430 micros and is similar to HAL libraries used for ARM micros. So far, we have not used this library pack as it doesn’t support VLDs. Details of TI’s driver library can be found here. Please have it downloaded as we will need it for the demo.

As mentioned before, there are other more resourceful and powerful MSP430s and the driver library is intended for such robust devices. MSP430F5529LP is one such powerful device. It is a microcontroller mainly intended for USB application development and has 128kB of flash and 8kB RAM. There is an inexpensive Launchpad board dedicated for this awesome micro and it delivers the punch needed in complex big projects. Details of this Launchpad board can be found here.

5529

In this final section, we will briefly see the potential of combining TI’s Driver Library with MSP430F5529LP.

How tos?

Google and download the Energia pinmap for MSP430F5529LP.

MSP430F5529 Launchpad

I assume that by now you have downloaded the latest version of DriverLib and other documentations regarding this and the MSP430F5529LP Launchpad board.

Extract the DriverLib zip file and copy the correct DriverLib folder (MSP430F5xx_6xx folder in our case) to your project folder. In my case, I copied this folder and renamed it as driverlib. The process is same as what we did for our own-built library files.

Includes

Let the compiler know the physical paths of this folder just like the custom library files. Rest of the works is same as before.

Code Example

 

#include "driverlib.h"
#include "delay.h"

void main(void)
{
    unsigned char s = 0x00;

    WDT_A_hold(WDT_A_BASE);

    GPIO_setAsOutputPin (GPIO_PORT_P1, GPIO_PIN0);
    GPIO_setDriveStrength(GPIO_PORT_P1, GPIO_PIN0, GPIO_FULL_OUTPUT_DRIVE_STRENGTH);
    GPIO_setAsOutputPin (GPIO_PORT_P4, GPIO_PIN7);
    GPIO_setDriveStrength(GPIO_PORT_P4, GPIO_PIN7, GPIO_FULL_OUTPUT_DRIVE_STRENGTH);
    GPIO_setAsInputPinWithPullUpResistor(GPIO_PORT_P2, GPIO_PIN1);

    GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P5, (GPIO_PIN2 | GPIO_PIN4));
    GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P5, (GPIO_PIN2 | GPIO_PIN4));
    UCS_setExternalClockSource(32768, 4000000);
    UCS_turnOnXT2(UCS_XT2_DRIVE_4MHZ_8MHZ);
    UCS_turnOnLFXT1(UCS_XT1_DRIVE_0, UCS_XCAP_3);
    UCS_initClockSignal(UCS_MCLK, UCS_XT2CLK_SELECT, UCS_CLOCK_DIVIDER_1);
    UCS_initClockSignal(UCS_SMCLK, UCS_XT2CLK_SELECT, UCS_CLOCK_DIVIDER_1);
    UCS_initClockSignal(UCS_ACLK, UCS_XT1CLK_SELECT, UCS_CLOCK_DIVIDER_1);

    while(1)
    {
        if(GPIO_getInputPinValue(GPIO_PORT_P2, GPIO_PIN1) == false)
        {
            while(GPIO_getInputPinValue(GPIO_PORT_P2, GPIO_PIN1) == false);
            GPIO_setOutputHighOnPin(GPIO_PORT_P1, GPIO_PIN0);
            delay_ms(100);
            GPIO_setOutputLowOnPin(GPIO_PORT_P1, GPIO_PIN0);
            s = ~s;
        }

        P4OUT ^= BIT7;
        switch(s)
        {
            case 0:
            {
                delay_ms(100);
                break;
            }

            default:
            {
               delay_ms(600);
               break;
            }
        }
    };
}

 

Explanation

To keep things simple, I demoed another LED blinking code. Notice that with inclusion of driverlib, everything has changed with meaningful functions. Take the setting of watchdog timer as an example.

WDTCTL = WDTPW | WDTHOLD; //Register-level access
 
WDT_A_hold(WDT_A_BASE);   //DriverLib function call

Instead of setting registers, driverlib functions are just taking some function argument(s) to set desired pin according to our wish. The functions and the arguments have meaningful names instead of magical numbers. This way of coding gives a fast overview of our code and the development time and efforts are greatly reduced. All register-level tasks are done under the hood of driverlib. This doesn’t however restrict us from going the old-fashioned way of using register-based coding. Still it is possible:

P4OUT ^= BIT7;

The code begins with GPIO settings as follows:

GPIO_setAsOutputPin (GPIO_PORT_P1, GPIO_PIN0);
GPIO_setDriveStrength(GPIO_PORT_P1, GPIO_PIN0, GPIO_FULL_OUTPUT_DRIVE_STRENGTH);
GPIO_setAsOutputPin (GPIO_PORT_P4, GPIO_PIN7);
GPIO_setDriveStrength(GPIO_PORT_P4, GPIO_PIN7, GPIO_FULL_OUTPUT_DRIVE_STRENGTH);

 

GPIO_setAsInputPinWithPullUpResistor(GPIO_PORT_P2, GPIO_PIN1);
 
GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P5, (GPIO_PIN2 | GPIO_PIN4));
GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P5, (GPIO_PIN2 | GPIO_PIN4));

Two pins P1_0 and P4_7 are set as outputs with full drive strength since these pins have LEDs connected with them. P2_1 is set as an input with pull-up as it is connected with an onboard push button. Some pins of P5 are set for peripheral modules because these pins are connected with external crystals.

Next, we set to configure the clock system. There are two onboard external crystals – one 32.768kHz clock crystal and one 4MHz crystal. UCS stands for Unified Clock System. Like the basic clock system in MSP430G2xxx devices, this a complex network of clock system with lot of options. There are several internal and external clock sources to clock the main clock, the sub-main clock and the auxiliary clock signals. Here, I used the external crystal clocks to clock MCLK, SMCLK and ACLK

UCS_setExternalClockSource(32768, 4000000);

 

UCS_turnOnXT2(UCS_XT2_DRIVE_4MHZ_8MHZ);
UCS_turnOnLFXT1(UCS_XT1_DRIVE_0, UCS_XCAP_3);

 

UCS_initClockSignal(UCS_MCLK, UCS_XT2CLK_SELECT, UCS_CLOCK_DIVIDER_1);
UCS_initClockSignal(UCS_SMCLK, UCS_XT2CLK_SELECT, UCS_CLOCK_DIVIDER_1);
UCS_initClockSignal(UCS_ACLK, UCS_XT1CLK_SELECT, UCS_CLOCK_DIVIDER_1);

In the main loop, the Launchpad’s green LED (P4_7) is toggled at a given flash rate. When the onboard user push button (P2_1) is pressed, the onboard red LED (P1_0) is briefly turned on and the rate of green LED’s flashing is altered.

if(GPIO_getInputPinValue(GPIO_PORT_P2, GPIO_PIN1) == false)
{
    while(GPIO_getInputPinValue(GPIO_PORT_P2, GPIO_PIN1) == false);
    GPIO_setOutputHighOnPin(GPIO_PORT_P1, GPIO_PIN0);
    delay_ms(100);
    GPIO_setOutputLowOnPin(GPIO_PORT_P1, GPIO_PIN0);
    s = ~s;
}

 

P4OUT ^= BIT7;
 
switch(s)
{
    case 0:
    {
       delay_ms(100);
       break;
    }

 

    default:
    {
       delay_ms(600);
       break;
    }
}

 

Demo

IMG_0536

All code examples and this documentation is available for download from here.

 

Happy coding.

 

Author: Shawon M. Shahryiar

https://www.facebook.com/groups/microarena

https://www.facebook.com/MicroArena                                                                          31.03.2018

 

 

** Some images have been taken from the documents and webpages of Texas Instruments (TI) and Mikroelektronika.

The post More on TI MSP430s appeared first on Embedded Lab.

Getting Started with Nuvoton 8-bit Microcontrollers

$
0
0

Many of us who are involved in the embedded system industry like 8-bit microcontrollers. They are cheap, easy to use and solve most of the common automation problems. 32-bit micros are in that contrast expensive and are mainly intended for advance-level works that cannot be solved with 8-bit micros. Thus, 32-bit micros are not much demanding as the 8-bit ones. In most places, 8051s, AVRs and PICs are the most used 8-bit micros. The popular Arduino platform is mainly based on 8-bit AVR micros. However, these are not the only 8-bit micros of the whole embedded world. Nuvoton – a Taiwan-based semiconductor manufacturer, is one such company that has its own flavour of 8-bit and 32-bit micros. The 8-bit micros from Nuvoton are based on the popular 8051 architectures. In this series of articles, we will be discovering Nuvoton N76E003 1T-8051-based microcontroller.

SDK

N76E003 vs STM8S003

Hardware Internals

In terms of outlook, part numbering, pin layout and other basic features, N76E003 looks like a cheap Chinese copy of STMicroelectronics’ STM8S003. However, as we all know, looks can be deceptive. Though several similarities, N76E003 is not a replica of STM8S003 in any way. In fact, in terms of architecture and some hardware features, N76E003 varies a lot from STM8S003. For instance, the processor core of these chips is not same. Even after having same pin layouts, N76E003 has several additional hardware than STM8S003, for instance 8 channel – 12-bit ADC.

ST Vs Nu

The table below summarizes an actual comparison of these chips.

Features

It is a good idea to consider N76E003 as an advanced 8051 micro in STM8S003 form-factor and having several similarities with the STM8S003.

Hardware Tools

To get started only two things are needed – a prototyping/development board and a programmer called NuLink.  There are two options either you can buy the expensive official NuTiny SDK boards with built-in NULink programmer/debugger or you can buy the cheap unofficial ones as shown below.

programmer + board

My personal choice is the unofficial one.

 

Software Tools

Despite Nuvoton being a giant in manufacturing some of the industry’s coolest chips, it greatly lags in terms of software tools. Unlike other manufacturers like STMicroelectronics and Microchip, Nuvoton doesn’t have a free C compiler of its own. To use Nuvoton 8-bit micros, we have to use either Keil Micro Vision or IAR Embedded Workbench. Both industry-standard tools but are not free. However, there are trial and evaluation versions of these tools.

keil vs iar

The next stuffs that we will be needing are device datasheet, drivers, GUI for NuLink and sample codes with Nuvoton’s official header and source files. These are available here.

 

We will also be needing a pin map because there are few pins in N76E003 and they have multiple functions.

N76E003

How to get started?

I have made two videos – one for Keil and the other for IAR. These videos show in details how to start building projects with N76E003 using these compilers.

 

Nuvoton Files

No matter which compiler you use ultimately, you’ll need the following header (.h) and source files (.c) to get things done properly. These are available in Keil BSP and IAR BSP zip files.

Files

Now what these files do? These files make up something like a standard peripheral library. They define registers and functions that are needed to simplify coding. These files replace tradition register-level access to meaningful codes. For instance, the Delay files dictate the software delay functions by using hardware timers. Likewise, SFR_Macro and Function_Define files define hardware-based functions and SFR uses. I highly recommend going through the files.

The post Getting Started with Nuvoton 8-bit Microcontrollers appeared first on Embedded Lab.

Acme traffic light restoration

$
0
0

The Southern California Transportation Museum is one of the largest private transportation museums in the United States. We are privileged to have among our artifacts a set of Acme traffic lights. This type of traffic signal was deployed in the Los Angeles area in the 1920s and 1930s. This was the time when every city was experimenting with different types of traffic signals. Later the Automobile Club convinced everyone to adopt the three-light signal they use today. During the brief time they were in use, the ACME traffic light became the favorite of the Hollywood cartoonist so, that’s why you see them all over the movies. It’s the one with the arm and lights. The arms were used during the day since lights used electricity and electricity cost money. Then, at night the lights were used because, logically, you couldn’t see the arm.

The museum has one of the original ACME signal controllers – but it has a fatal design flaw, it thinks that it’s running a traffic signal. That means it will run it all day and night, wearing out the motors. So, the museum decided to replace the old controller with a new one consisting of a Raspberry Pi and a 16-channel relay module. The relay module was chosen because in the past it had provided reliable service in the museum’s signal garden. Also, because it is incredibly easy to program.

Acme controller

Acme traffic light controller

The Raspberry Pi and the relay board must work in a somewhat harsh environment. The museum moved to Perris, CA because the hot dry climate is good for the  artifacts. However, the climate is not good for the electronic parts. Most of the components sit in a cast iron box, painted black, with no ventilation, in a semi-desert. We are not certain of how hot it gets inside, but the 3d printed hinges we implemented in the box melted. Fortunately, all of the other parts are still working properly.

Now, you might be wondering why it takes so many relays to control one signal. The ACME traffic light is not a simple signal. There are arms, lights, and a bell. So, the relays are for:

  1. Red Light
  2. Green Light
  3. Yellow Light
  4. Motor power
  5. Motor direction
  6. Bell
  7. Fold (Keeps the arms from extending when returning them to the housing.)

And everything must be doubled because we have two signal heads.

Some additional relays were needed since the arm motor works by shorting two wires and applying power to a third. To change direction, you change which wires are shorted and which gets power. This would have been possible with two relays on the 16-channel board, but in this configuration, it would be possible for a software glitch to damage the motor. To ensure that would not happen we used a DPDT relay which provided a hardware interlock. Also, the motor, fold magnet, and bell all use big A/C coils. When these are turned off they generate a huge electric pulse. This does nasty things to the Raspberry Pi. (Funny enough the board has no problem.) To get around this problem three zero crossing solid state relays were added. By turning off the coils when no voltage is present you avoid the pulse problem.

Railroad equipment is designed to last a long time. The last railroad relay I used had an inspection sticker on it of 1910 (that’s when it was taken out of service, not when it was made). I don’t know if the new parts will last as long, but so far, the new parts have been very reliable in a challenging environment.

Watch the demo video below:

Author of the project: Eduardo Martinez

The post Acme traffic light restoration appeared first on Embedded Lab.

Getting Started with Nuvoton 8-bit Microcontrollers – Coding Part 1

$
0
0

This post is a continuation of the first post on Nuvoton N76E003 microcontroller here.

s-l1600

 

About the N76E006 Test Board

Shown below are the actual photo, the PCB layout and the schematic of the unofficial cheap N76E003 test/development board. This is the board I will be using in this tutorial series.

board

There is nothing much about the 30mm x 43.5mm board. Everything is visible and neat. However, having the schematic alongside the board layout in possession is a great advantage. There are two sidewise header that bring out the GPIO pins and positive supply rails. There is another header opposite to the USB port. This header is for connecting a Nulink programmer-debugger interface and it also has a serial port interface brought out straight from the N76E003 chip. This serial port is useful for quickly debugging/testing stuffs with a serial port monitor. There is an LED connected with P15 pin via a computer jumper. The only thing that is wrong in this board is the crystal resonator part. N76E003 has an external clock input pin but it is meant to be used with active oscillators/crystal modules. In the official, SDK there is no such points to connect an external crystal resonator.  The internal high frequency oscillator is accurate enough for most cases.

At this point, I would like to thank Electro Dragon for these images because they are the only ones who shared these resources online.

2018-02-12_235823

Shown below is the official SDK board’s schematic:

Official SDK Schematic

Coding Nuvoton N76E003

When entering a new environment, things are not very easy at first. It takes times to get acquainted with new the new environment, new tools and new stuffs. New means everything different from the ones that you have been using prior to its introduction. In many occasions, I had trouble playing with new microcontrollers due to this. However, after some decent play offs things unfolded themselves.

President JFK’s speech on lunar landing should be an inspirational note for everyone who is trying to do something new or something that he/she has never done before:

We choose to go to the Moon in this decade and do the other things, not because they are easy, but because they are hard; because that goal will serve to organize and measure the best of our energies and skills, because that challenge is one that we are willing to accept, one we are unwilling to postpone, and one we intend to win, and the others, too.

Both Keil and IAR are excellent tools for coding Nuvoton MCUs. I have used both and everything is same in both cases. Literally there is no difference at all. I won’t recommend which one to use and I leave the choice to the readers. However, there are some areas where you may find difficulty porting codes of one compiler to the other. The table below summarizes some of these differences.

keil vs iar

Two more things I would like to highlight here. Firstly, both Keil and IAR compiler can throw some errors during code compilations. Most of these errors are due to BSP definitions. One such error is in the line below:

error

If you try to use set_P0S_6 definition, IAR sometimes throws an error because it can’t find BIT_TMP. However, there are other similar definitions that don’t throw such error and in Keil you won’t notice something like this. Such things are nasty illogical surprises. We have to understand that the BSPs are still in development. Always remember that the datasheet is your friend. I suggest that when you try out the examples I have shown here, you read the relevant parts of the datasheet to enhance learning and understanding.

The other thing to note is the fact that not always we have the luxury to avoid register-level coding and so when needed we must have the right knowledge to use them. We can also use bit-level manipulations as shown below:

Coding

Sometimes but not always, we have to code things the old ways. Sometimes mixing assembly code with C code becomes a necessity. For instance, the software delay library uses this concept.

There are other aspects to consider too like case sensitivity and coding conventions. It is wise to choose interrupt-driven methods over polling-based ones. Codes should be included in hierarchical orders. Like such there are tons of stuffs to make your code smart and error-free. The best source of knowledge of such things and much more are app notes of various manufacturers.

Whenever making a new library, add the followings in your library’s source code along with other header files of your choice to avoid errors and nasty surprises:

Common Headers

Additionally, I have made a set of files called “Extended_Functions”. Here I added all the functions that we will need almost every time when we deal with common internal hardware like timers, ADC, etc. These files are like repositories of all the additional functions that I made for some internal hardware – something that Nuvoton didn’t provide and something that makes coding lot easier. I’ll share these and all the codes with the PDF of the tutorials after Part 2 of this tutorial series.

Here I’m sharing two videos to demonstrate how to code and add custom-made library files in both Keil C51 and IAR Embedded-Workbench compliers.


General Purpose Input-Output (GPIO)

GPIOs are the most common hardware that we use in a microcontroller. Since N76E003 is based on 8051 architecture, we should be getting some similarities with the old school 8051s. Shown below is the hardware schematic of N76E003’s GPIO block:

GPIO Structure

On close inspection, we can realize that this structure has striking resemblance with the GPIO structure of a typical 8051 microcontroller as shown below:

8051 GPIO

Thus, we can expect similar behaviour.

There are four GPIO modes and these are as follows:

GPIO types

PxM1.n and PxM2.n bits decide these modes. For most cases, we can stick to push-pull and input modes as they are the most commonly used ones.

Code

#include "N76E003_iar.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "Delay.h"

void setup(void);

void main(void)
  setup();

  while(1)
  {
    if(P05 != 0x00)
    {
      Timer0_Delay1ms(900);
    }

    set_P15;
    Timer0_Delay1ms(100);
    clr_P15;
    Timer0_Delay1ms(100);
  };
}

void setup(void)
  P15_PushPull_Mode;
  P05_Input_Mode;
}

Schematic

GPIO_Schematic

Explanation

The Function_define BSP header file states GPIO modes as follows:

GPIO_Def

Similarly, SFR_Macro BSP header file defines the bit-level setting of all N76E003 registers. To set the logic level of GPIO pins we can use the following definitions:

GPIO_Def2

However, these don’t restrict us from using classical register-level coding. N76E003 header file states all the registers present in it.

For port/pin reading I didn’t see any function definition as like one I already discussed. Thus, there are two ways to do it on your own. The following as two examples of such:

Coding 2

The demo here is a simple one. The onboard LED connected to P15 pin is toggled at a fixed interval. When a button connected to P05 is pressed the off time of the LED is increased, affecting toggle rate.

Demo

GPIO

Driving 2×16 LCD

Driving alphanumeric/text LCDs requires no special hardware as simple manipulation of GPIO pins and understanding of their working principle are all that are needed.

LCD

Code

 

lcd.h

#define LCD_GPIO_init()                    do{P00_PushPull_Mode; P01_PushPull_Mode; P10_PushPull_Mode; P11_PushPull_Mode; P12_PushPull_Mode; P13_PushPull_Mode;}while(0)

#define LCD_RS_HIGH                        set_P00
#define LCD_RS_LOW                         clr_P00

#define LCD_EN_HIGH                        set_P01
#define LCD_EN_LOW                         clr_P01

#define LCD_DB4_HIGH                       set_P10
#define LCD_DB4_LOW                        clr_P10

#define LCD_DB5_HIGH                       set_P11
#define LCD_DB5_LOW                        clr_P11

#define LCD_DB6_HIGH                       set_P12
#define LCD_DB6_LOW                        clr_P12

#define LCD_DB7_HIGH                       set_P13
#define LCD_DB7_LOW                        clr_P13

#define clear_display                      0x01
#define goto_home                          0x02

#define cursor_direction_inc               (0x04 | 0x02)
#define cursor_direction_dec               (0x04 | 0x00)
#define display_shift                      (0x04 | 0x01)
#define display_no_shift                   (0x04 | 0x00)

#define display_on                         (0x08 | 0x04)
#define display_off                        (0x08 | 0x02)
#define cursor_on                          (0x08 | 0x02)
#define cursor_off                         (0x08 | 0x00)
#define blink_on                           (0x08 | 0x01)
#define blink_off                          (0x08 | 0x00)

#define _8_pin_interface                   (0x20 | 0x10)
#define _4_pin_interface                   (0x20 | 0x00)
#define _2_row_display                     (0x20 | 0x08)
#define _1_row_display                     (0x20 | 0x00)
#define _5x10_dots                         (0x20 | 0x40)
#define _5x7_dots                          (0x20 | 0x00)

#define DAT                                1
#define CMD                                0

void LCD_init(void);
void LCD_send(unsigned char value, unsigned char mode);
void LCD_4bit_send(unsigned char lcd_data);
void LCD_putstr(char *lcd_string);
void LCD_putchar(char char_data);
void LCD_clear_home(void);
void LCD_goto(unsigned char x_pos, unsigned char y_pos);
void toggle_EN_pin(void);

 

lcd.c

#include "N76E003.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "Delay.h"
#include "lcd.h"

void LCD_init(void)
{
    Timer0_Delay1ms(10);

    LCD_GPIO_init();

    Timer0_Delay1ms(100);

    toggle_EN_pin();

    LCD_RS_LOW;

    LCD_DB7_LOW;
    LCD_DB6_LOW;
    LCD_DB5_HIGH;
    LCD_DB4_HIGH;

    toggle_EN_pin();

    LCD_DB7_LOW;
    LCD_DB6_LOW;
    LCD_DB5_HIGH;
    LCD_DB4_HIGH;

    toggle_EN_pin();

    LCD_DB7_LOW;
    LCD_DB6_LOW;
    LCD_DB5_HIGH;
    LCD_DB4_HIGH;

    toggle_EN_pin();

    LCD_DB7_LOW;
    LCD_DB6_LOW;
    LCD_DB5_HIGH;
    LCD_DB4_LOW;

    toggle_EN_pin();

    LCD_send((_4_pin_interface | _2_row_display | _5x7_dots), CMD);
    LCD_send((display_on | cursor_off | blink_off), CMD);
    LCD_send(clear_display, CMD);
    LCD_send((cursor_direction_inc | display_no_shift), CMD);
}

void LCD_send(unsigned char value, unsigned char mode)
{
    switch(mode)
    {
        case DAT:
        {
            LCD_RS_HIGH;
            break;
        }
        case CMD:
        {
            LCD_RS_LOW;
            break;
        }
    }

    LCD_4bit_send(value);
}

void LCD_4bit_send(unsigned char lcd_data)
{
    unsigned char temp = 0;

    temp = ((lcd_data & 0x80) >> 7);

    switch(temp)
    {
        case 1:
        {
            LCD_DB7_HIGH;
            break;
        }
        default:
        {
            LCD_DB7_LOW;
            break;
        }
    }

    temp = ((lcd_data & 0x40) >> 6);

    switch(temp)
    {
        case 1:
        {
            LCD_DB6_HIGH;
            break;
        }
        default:
        {
            LCD_DB6_LOW;
            break;
        }
    }

    temp = ((lcd_data & 0x20) >> 5);

    switch(temp)
    {
        case 1:
        {
            LCD_DB5_HIGH;
            break;
        }
        default:
        {
            LCD_DB5_LOW;
            break;
        }
    }

    temp = ((lcd_data & 0x10) >> 4);

    switch(temp)
    {
        case 1:
        {
            LCD_DB4_HIGH;
            break;
        }
        default:
        {
            LCD_DB4_LOW;
            break;
        }
    }

    toggle_EN_pin();

    temp = ((lcd_data & 0x08) >> 3);

    switch(temp)
    {
        case 1:
        {
            LCD_DB7_HIGH;
            break;
        }
        default:
        {
            LCD_DB7_LOW;
            break;
        }
    }

    temp = ((lcd_data & 0x04) >> 2);

    switch(temp)
    {
        case 1:
        {
            LCD_DB6_HIGH;
            break;
        }
        default:
        {
            LCD_DB6_LOW;
            break;
        }
    }

    temp = ((lcd_data & 0x02) >> 1);

    switch(temp)
    {
        case 1:
        {
            LCD_DB5_HIGH;
            break;
        }
        default:
        {
            LCD_DB5_LOW;
            break;
        }
    }

    temp = ((lcd_data & 0x01));

    switch(temp)
    {
        case 1:
        {
            LCD_DB4_HIGH;
            break;
        }
        default:
        {
            LCD_DB4_LOW;
            break;
        }
    }

    toggle_EN_pin();
}

void LCD_putstr(char *lcd_string)
{
    do
    {
        LCD_send(*lcd_string++, DAT);
    }while(*lcd_string != '\0');
}

void LCD_putchar(char char_data)
{
    LCD_send(char_data, DAT);
}

void LCD_clear_home(void)
{
    LCD_send(clear_display, CMD);
    LCD_send(goto_home, CMD);
}

void LCD_goto(unsigned char x_pos, unsigned char y_pos)
{
    if(y_pos == 0)
    {
        LCD_send((0x80 | x_pos), CMD);
    }
    else
    {
        LCD_send((0x80 | 0x40 | x_pos), CMD);
    }
}

void toggle_EN_pin(void)
{
    LCD_EN_HIGH;
    Timer0_Delay1ms(4);
    LCD_EN_LOW;
    Timer0_Delay1ms(4);
}

 

main.c

#include "N76E003.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "Delay.h"
#include "lcd.h"

void show_value(unsigned char value);

void main(void)
{   
    unsigned char s = 0;

    const char txt1[] = {"MICROARENA"};
    const char txt2[] = {"SShahryiar"};
    const char txt3[] = {"Nuvoton 8-bit uC"};
    const char txt4[] = {"N76E003"};

    LCD_init();

    LCD_clear_home();

    LCD_goto(3, 0);
    LCD_putstr(txt1);
    LCD_goto(3, 1);
    LCD_putstr(txt2);
    Timer3_Delay100ms(30);

    LCD_clear_home();

    for(s = 0; s < 16; s++)
    {
        LCD_goto(s, 0);
        LCD_putchar(txt3[s]);
        Timer0_Delay1ms(90);
    }

    Timer3_Delay100ms(20);

    for(s = 0; s < 7; s++)
    {
        LCD_goto((4 + s), 1);
        LCD_putchar(txt4[s]);
        Timer0_Delay1ms(90);
    }

    Timer3_Delay100ms(30);

    s = 0;
    LCD_clear_home();

    LCD_goto(3, 0);
    LCD_putstr(txt1);

    while(1)
    {
        show_value(s);
        s++;
        Timer3_Delay100ms(4);
    };
}

void show_value(unsigned char value)
{
   unsigned char ch = 0x00;

   ch = ((value / 100) + 0x30);
   LCD_goto(6, 1);
   LCD_putchar(ch);

   ch = (((value / 10) % 10) + 0x30);
   LCD_goto(7, 1);
   LCD_putchar(ch);

   ch = ((value % 10) + 0x30);
   LCD_goto(8, 1);
   LCD_putchar(ch);
}

 

Schematic

LCD Schematic

Explanation

There is nothing to explain here. The LCD driver is based on simple manipulation of GPIO pins. The codes for the LCD are coded using all available info on LCD datasheet – just initialization and working principle. If you need to change GPIO pins just edit the following lines in the LCD header file:

LCD Coding

Demo

2x16 LCD

Driving 2×16 LCD with Software SPI

One problem with alphanumeric LCDs and GLCDs is the number of GPIOs needed to connect so with host micros. For a small micro like N76E003, each GPIO pin is like a gem and we can’t afford to use too many GPIO pins for an LCD. The solution to this problem is to use SPI/I2C-based LCD drivers that significantly reduce GPIO pin requirement. Implementing software-based SPI/I2C for such LCD drivers is also both easy and universal since these solutions don’t need hardware SPI/I2C ports. Since the SPI/I2C functionality is software emulated, any set of GPIO pins can be used – another advantage.

SPI-LCD

In this segment, we will be driving a 2×16 LCD with CD4094B Serial-In-Parallel-Out (SIPO) shift register using software SPI. The same idea can be used for other similar shift registers like 74HC595. There are other ways of using SPI-based LCDs but the aforementioned are the cheapest ways.

Code

 

LCD_3_Wire.h

#define LCD_GPIO_init()                        do{P02_PushPull_Mode; P03_PushPull_Mode; P04_PushPull_Mode;}while(0)

#define LCD_SDO_HIGH()                          set_P00
#define LCD_SDO_LOW()                           clr_P00

#define LCD_SCK_HIGH()                          set_P01
#define LCD_SCK_LOW()                           clr_P01

#define LCD_STB_HIGH()                          set_P02
#define LCD_STB_LOW()                           clr_P02

#define DAT                                     1
#define CMD                                     0

#define clear_display                           0x01
#define goto_home                               0x02

#define cursor_direction_inc                    (0x04 | 0x02)
#define cursor_direction_dec                    (0x04 | 0x00)
#define display_shift                           (0x04 | 0x01)
#define display_no_shift                        (0x04 | 0x00)

#define display_on                              (0x08 | 0x04)
#define display_off                             (0x08 | 0x02)
#define cursor_on                               (0x08 | 0x02)
#define cursor_off                              (0x08 | 0x00)
#define blink_on                                (0x08 | 0x01)
#define blink_off                               (0x08 | 0x00)

#define _8_pin_interface                        (0x20 | 0x10)
#define _4_pin_interface                        (0x20 | 0x00)
#define _2_row_display                          (0x20 | 0x08)
#define _1_row_display                          (0x20 | 0x00)
#define _5x10_dots                              (0x20 | 0x40)
#define _5x7_dots                               (0x20 | 0x00)

extern unsigned char data_value;


void SIPO(void);
void LCD_init(void);
void LCD_toggle_EN(void);
void LCD_send(unsigned char value, unsigned char mode);
void LCD_4bit_send(unsigned char lcd_data);           
void LCD_putstr(char *lcd_string);
void LCD_putchar(char char_data);
void LCD_clear_home(void);
void LCD_goto(unsigned char x_pos, unsigned char y_pos);

 

LCD_3_Wire.c

#include "N76E003_iar.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "Delay.h"
#include "LCD_3_Wire.h"

unsigned char data_value;

void SIPO(void)
{
    unsigned char bit_value = 0x00;
    unsigned char clk = 0x08;
    unsigned char temp = 0x00;

    temp = data_value;
    LCD_STB_LOW();

    while(clk > 0)
    {
        bit_value = ((temp & 0x80) >> 0x07);
        bit_value &= 0x01;

        switch(bit_value)
        {
            case 0:
            {
                LCD_SDO_LOW();
                break;
            }
            default:
            {
                LCD_SDO_HIGH();
                break;
            }
        }

        LCD_SCK_HIGH();

        temp <<= 0x01;
        clk--;

        LCD_SCK_LOW();
    };

    LCD_STB_HIGH();
}

void LCD_init(void)
{                                     
    Timer0_Delay1ms(10);

    LCD_GPIO_init();

    Timer0_Delay1ms(10);

    data_value = 0x08;
    SIPO();
    Timer0_Delay1ms(10);

    LCD_send(0x33, CMD);
    LCD_send(0x32, CMD);

    LCD_send((_4_pin_interface | _2_row_display | _5x7_dots), CMD);        
    LCD_send((display_on | cursor_off | blink_off), CMD);    
    LCD_send((clear_display), CMD);        
    LCD_send((cursor_direction_inc | display_no_shift), CMD);       
}  

void LCD_toggle_EN(void)
{
    data_value |= 0x08;
    SIPO();
    Timer0_Delay1ms(2);
    data_value &= 0xF7;
    SIPO();
    Timer0_Delay1ms(2);
}


void LCD_send(unsigned char value, unsigned char mode)
{                              
    switch(mode)
    {
        case DAT:
        {
            data_value |= 0x04;
            break;
        }
        default:
        {
            data_value &= 0xFB;
            break;
        }
    }

    SIPO();
    LCD_4bit_send(value);


void LCD_4bit_send(unsigned char lcd_data)       
{
    unsigned char temp = 0x00;

    temp = (lcd_data & 0xF0);
    data_value &= 0x0F;
    data_value |= temp;
    SIPO();
    LCD_toggle_EN();

    temp = (lcd_data & 0x0F);
    temp <<= 0x04;
    data_value &= 0x0F;
    data_value |= temp;
    SIPO();
    LCD_toggle_EN();

void LCD_putstr(char *lcd_string)
{
    while(*lcd_string != '\0') 
    {
        LCD_putchar(*lcd_string++);
    }
}

void LCD_putchar(char char_data)
{
    if((char_data >= 0x20) && (char_data <= 0x7F))
    {
        LCD_send(char_data, DAT);
    }
}

void LCD_clear_home(void)
{
    LCD_send(clear_display, CMD);
    LCD_send(goto_home, CMD);
}

void LCD_goto(unsigned char x_pos,unsigned char y_pos)
{                                                  
    if(y_pos == 0)   
    {                             
        LCD_send((0x80 | x_pos), CMD);
    }
    else
    {                                             
        LCD_send((0x80 | 0x40 | x_pos), CMD);
    }
}

 

main.c

#include "N76E003_iar.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "Delay.h"
#include "LCD_3_Wire.h"

void show_value(unsigned char value);

void main(void)
  unsigned char s = 0;

  static char txt1[] = {"MICROARENA"};
  static char txt2[] = {"SShahryiar"};
  static char txt3[] = {"Nuvoton 8-bit uC"};
  static char txt4[] = {"N76E003"};

  LCD_init();

  LCD_clear_home();

  LCD_goto(3, 0);
  LCD_putstr(txt1);
  LCD_goto(3, 1);
  LCD_putstr(txt2);
  Timer3_Delay100ms(30);

  LCD_clear_home();

  for(s = 0; s < 16; s++)
  {
    LCD_goto(s, 0);
    LCD_putchar(txt3[s]);
    Timer0_Delay1ms(90);
  }

  Timer3_Delay100ms(20);

  for(s = 0; s < 7; s++)
  {
    LCD_goto((4 + s), 1);
    LCD_putchar(txt4[s]);
    Timer0_Delay1ms(90);
  }

  Timer3_Delay100ms(30);

  s = 0;
  LCD_clear_home();

  LCD_goto(3, 0);
  LCD_putstr(txt1);

  while(1)
  {
    show_value(s);
    s++;
    Timer3_Delay100ms(4);
  };
}

void show_value(unsigned char value)
{
   unsigned char ch = 0x00;

   ch = ((value / 100) + 0x30);
   LCD_goto(6, 1);
   LCD_putchar(ch);

   ch = (((value / 10) % 10) + 0x30);
   LCD_goto(7, 1);
   LCD_putchar(ch);

   ch = ((value % 10) + 0x30);
   LCD_goto(8, 1);
   LCD_putchar(ch);
}

 

Schematic

LCD Schematic

Explanation

The code demoed here is same as the last LCD code and so there is not much to explain. The GPIO operations of the LCD are handled using a CD4094B Serial-In-Parallel-Out (SIPO) shift register. This shift register here acts like an output expander. With just three GPIOs we are able to interface a 4-bit LCD that needs at least six GPIOs to work.

The SIPO function shown below simulates software-based SPI:

void SIPO(void)
{
    unsigned char bit_value = 0x00;
    unsigned char clk = 0x08;
    unsigned char temp = 0x00;

    temp = data_value;
    LCD_STB_LOW();

    while(clk > 0)
    {
        bit_value = ((temp & 0x80) >> 0x07);
        bit_value &= 0x01;

        switch(bit_value)
        {
            case 0:
            {
                LCD_SDO_LOW();
                break;
            }
            default:
            {
                LCD_SDO_HIGH();
                break;
            }
        }

        LCD_SCK_HIGH();

        temp <<= 0x01;
        clk--;

        LCD_SCK_LOW();
    };

    LCD_STB_HIGH();
}

To change pins, change the following the lines in the LCD_3_Wire header file:

#define LCD_GPIO_init()                        do{P02_PushPull_Mode; P03_PushPull_Mode; P04_PushPull_Mode;}while(0)

#define LCD_SDO_HIGH()                          set_P00
#define LCD_SDO_LOW()                           clr_P00

#define LCD_SCK_HIGH()                          set_P01
#define LCD_SCK_LOW()                           clr_P01

#define LCD_STB_HIGH()                          set_P02
#define LCD_STB_LOW()                           clr_P02

Lastly, I have code two versions of this LCD library – one with BSP-based delays and the other with software delays. Technically there’s no big change. The software-based one frees up a hardware timer or two.

Demo

SPI LCD

Driving 2×16 LCD with Software I2C

We have already seen in the last segment how to use software SPI with a shift register to drive a 2×16 LCD. In this segment, we will explore the same concept with software I2C and PCF8574 I2C port expander IC. There is a popular readymade module for such task and I used it here. The advantage of I2C-based LCD over SPI-based LCD driver is the lesser number of GPIOs required compared to SPI-based LCD. However, it is slower than SPI-based drivers.

2-Wire LCD

Code

 

SW_I2C.h

#define SDA_DIR_OUT()   P03_PushPull_Mode
#define SDA_DIR_IN()    P03_Input_Mode

#define SCL_DIR_OUT()   P04_PushPull_Mode
#define SCL_DIR_IN()    P04_Input_Mode

#define SDA_HIGH()      set_P03
#define SDA_LOW()       clr_P03

#define SCL_HIGH()      set_P04
#define SCL_LOW()       clr_P04

#define SDA_IN()        P03

#define I2C_ACK         0xFF
#define I2C_NACK        0x00

#define I2C_timeout     1000

void SW_I2C_init(void);
void SW_I2C_start(void);
void SW_I2C_stop(void);
unsigned char SW_I2C_read(unsigned char ack);
void SW_I2C_write(unsigned char value);
void SW_I2C_ACK_NACK(unsigned char mode);
unsigned char SW_I2C_wait_ACK(void);

 

SW_I2C.c

#include "N76E003_iar.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "Delay.h"
#include "SW_I2C.h"

void SW_I2C_init(void)
{
    SDA_DIR_OUT();
    SCL_DIR_OUT();
    Timer0_Delay100us(1);
    SDA_HIGH();
    SCL_HIGH();
}

void SW_I2C_start(void)
{
    SDA_DIR_OUT();
    SDA_HIGH();
    SCL_HIGH();
    Timer3_Delay10us(4);
    SDA_LOW();
    Timer3_Delay10us(4);
    SCL_LOW();
}

void SW_I2C_stop(void)
{
    SDA_DIR_OUT();
    SDA_LOW();
    SCL_LOW();
    Timer3_Delay10us(4);
    SDA_HIGH();
    SCL_HIGH();
    Timer3_Delay10us(4);
}

unsigned char SW_I2C_read(unsigned char ack)
{
    unsigned char i = 8;
    unsigned char j = 0;

    SDA_DIR_IN();

    while(i > 0)
    {
        SCL_LOW();
        Timer3_Delay10us(2);
        SCL_HIGH();
        Timer3_Delay10us(2);
        j <<= 1;

        if(SDA_IN() != 0x00)
        {
            j++;
        }

        Timer3_Delay10us(1);
        i--;
    };

    switch(ack)
    {
        case I2C_ACK:
        {
            SW_I2C_ACK_NACK(I2C_ACK);;
            break;
        }
        default:
        {
            SW_I2C_ACK_NACK(I2C_NACK);;
            break;
        }
    }

    return j;
}

void SW_I2C_write(unsigned char value)
{
    unsigned char i = 8;

    SDA_DIR_OUT();
    SCL_LOW();

    while(i > 0)
    {
        if(((value & 0x80) >> 7) != 0x00)
        {
            SDA_HIGH();
        }
        else
        {
            SDA_LOW();
        }

        value <<= 1;
        Timer3_Delay10us(2);
        SCL_HIGH();
        Timer3_Delay10us(2);
        SCL_LOW();
        Timer3_Delay10us(2);
        i--;
    };
}

void SW_I2C_ACK_NACK(unsigned char mode)
{
    SCL_LOW();
    SDA_DIR_OUT();

    switch(mode)
    {
        case I2C_ACK:
        {
            SDA_LOW();
            break;
        }
        default:
        {
            SDA_HIGH();
            break;
        }
    }

    Timer3_Delay10us(2);
    SCL_HIGH();
    Timer3_Delay10us(2);
    SCL_LOW();
}

unsigned char SW_I2C_wait_ACK(void)
{
    signed int timeout = 0;

    SDA_DIR_IN();

    SDA_HIGH();
    Timer3_Delay10us(1);
    SCL_HIGH();
    Timer3_Delay10us(1);

    while(SDA_IN() != 0x00)
    {
        timeout++;

        if(timeout > I2C_timeout)
        {
            SW_I2C_stop();
            return 1;
        }
    };

    SCL_LOW();
    return 0;
}

 

PCF8574.h

#include "SW_I2C.h"

#define PCF8574_address                 0x4E

#define PCF8574_write_cmd               PCF8574_address
#define PCF8574_read_cmd                (PCF8574_address | 1)

void PCF8574_init(void);
unsigned char PCF8574_read(void);
void PCF8574_write(unsigned char data_byte);

 

PCF8574.c

#include "N76E003_iar.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "Delay.h"
#include "PCF8574.h"

void PCF8574_init(void)
{
    SW_I2C_init();
    Timer0_Delay1ms(20);
}

unsigned char PCF8574_read(void)
{
    unsigned char port_byte = 0;

    SW_I2C_start();
    SW_I2C_write(PCF8574_read_cmd);
    port_byte = SW_I2C_read(I2C_NACK);
    SW_I2C_stop();

    return port_byte;
}

void PCF8574_write(unsigned char data_byte)
{
    SW_I2C_start();
    SW_I2C_write(PCF8574_write_cmd);
    SW_I2C_ACK_NACK(I2C_ACK);
    SW_I2C_write(data_byte);
    SW_I2C_ACK_NACK(I2C_ACK);
    SW_I2C_stop();
}

 

LCD_2_Wire.h

#include "PCF8574.h"

#define clear_display                                0x01
#define goto_home                                    0x02

#define cursor_direction_inc                         (0x04 | 0x02)
#define cursor_direction_dec                         (0x04 | 0x00)
#define display_shift                                (0x04 | 0x01)
#define display_no_shift                             (0x04 | 0x00)

#define display_on                                   (0x08 | 0x04)
#define display_off                                  (0x08 | 0x02)
#define cursor_on                                    (0x08 | 0x02)
#define cursor_off                                   (0x08 | 0x00)
#define blink_on                                     (0x08 | 0x01)
#define blink_off                                    (0x08 | 0x00)

#define _8_pin_interface                             (0x20 | 0x10)
#define _4_pin_interface                             (0x20 | 0x00)
#define _2_row_display                               (0x20 | 0x08)
#define _1_row_display                               (0x20 | 0x00)
#define _5x10_dots                                   (0x20 | 0x40)
#define _5x7_dots                                    (0x20 | 0x00)

#define BL_ON                                        1
#define BL_OFF                                       0

#define dly                                          2

#define DAT                                          1
#define CMD                                          0

void LCD_init(void);
void LCD_toggle_EN(void);
void LCD_send(unsigned char value, unsigned char mode);
void LCD_4bit_send(unsigned char lcd_data);           
void LCD_putstr(char *lcd_string);
void LCD_putchar(char char_data);
void LCD_clear_home(void);
void LCD_goto(unsigned char x_pos, unsigned char y_pos);

 

LCD_2_Wire.c

#include "N76E003_iar.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "Delay.h"
#include "LCD_2_Wire.h"

static unsigned char bl_state;
static unsigned char data_value;

void LCD_init(void)
{                       
  PCF8574_init();
  Timer0_Delay1ms(10);

  bl_state = BL_ON;
  data_value = 0x04;
  PCF8574_write(data_value);

  Timer0_Delay1ms(10);

  LCD_send(0x33, CMD);
  LCD_send(0x32, CMD);

  LCD_send((_4_pin_interface | _2_row_display | _5x7_dots), CMD);        
  LCD_send((display_on | cursor_off | blink_off), CMD);    
  LCD_send((clear_display), CMD);        
  LCD_send((cursor_direction_inc | display_no_shift), CMD);       
}  

void LCD_toggle_EN(void)
{
  data_value |= 0x04;
  PCF8574_write(data_value);
  Timer0_Delay1ms(1);
  data_value &= 0xF9;
  PCF8574_write(data_value);
  Timer0_Delay1ms(1);
}


void LCD_send(unsigned char value, unsigned char mode)
{
  switch(mode)
  {
     case CMD:
     {
        data_value &= 0xF4;
        break;
     }
     case DAT:
     {
        data_value |= 0x01;
        break;
     }
  }

  switch(bl_state)
  {
     case BL_ON:
     {
        data_value |= 0x08;
        break;
     }
     case BL_OFF:
     {
        data_value &= 0xF7;
        break;
     }
  }

  PCF8574_write(data_value);
  LCD_4bit_send(value);
  Timer0_Delay1ms(1);
}


void LCD_4bit_send(unsigned char lcd_data)      
{
  unsigned char temp = 0x00;

  temp = (lcd_data & 0xF0);
  data_value &= 0x0F;
  data_value |= temp;
  PCF8574_write(data_value);
  LCD_toggle_EN();

  temp = (lcd_data & 0x0F);
  temp <<= 0x04;
  data_value &= 0x0F;
  data_value |= temp;
  PCF8574_write(data_value);
  LCD_toggle_EN();

void LCD_putstr(char *lcd_string)
{
  do
  {
    LCD_putchar(*lcd_string++);
  }while(*lcd_string != '\0') ;
}

void LCD_putchar(char char_data)
{
  if((char_data >= 0x20) && (char_data <= 0x7F))
  {
    LCD_send(char_data, DAT);
  }
}

void LCD_clear_home(void)
{
  LCD_send(clear_display, CMD);
  LCD_send(goto_home, CMD);
}

void LCD_goto(unsigned char x_pos,unsigned char y_pos)
{                                                  
  if(y_pos == 0)   
  {                             
    LCD_send((0x80 | x_pos), CMD);
  }
  else
  {                                              
    LCD_send((0x80 | 0x40 | x_pos), CMD);
  }
}

 

main.c

#include "N76E003_iar.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "Delay.h"
#include "LCD_2_Wire.h"

void show_value(unsigned char value);

void main(void)
  unsigned char s = 0;

  static char txt1[] = {"MICROARENA"};
  static char txt2[] = {"SShahryiar"};
  static char txt3[] = {"Nuvoton 8-bit uC"};
  static char txt4[] = {"N76E003"};

  LCD_init();

  LCD_clear_home();

  LCD_goto(3, 0);
  LCD_putstr(txt1);
  LCD_goto(3, 1);
  LCD_putstr(txt2);
  Timer3_Delay100ms(30);

  LCD_clear_home();

  for(s = 0; s < 16; s++)
  {
    LCD_goto(s, 0);
    LCD_putchar(txt3[s]);
    Timer0_Delay1ms(90);
  }

  Timer3_Delay100ms(20);

  for(s = 0; s < 7; s++)
  {
    LCD_goto((4 + s), 1);
    LCD_putchar(txt4[s]);
    Timer0_Delay1ms(90);
  }

  Timer3_Delay100ms(30);

  s = 0;
  LCD_clear_home();

  LCD_goto(3, 0);
  LCD_putstr(txt1);

  while(1)
  {
    show_value(s);
    s++;
    Timer3_Delay100ms(4);
  };
}

void show_value(unsigned char value)
{
  unsigned char ch = 0x00;

  ch = ((value / 100) + 0x30);
  LCD_goto(6, 1);
  LCD_putchar(ch);

  ch = (((value / 10) % 10) + 0x30);
  LCD_goto(7, 1);
  LCD_putchar(ch);

  ch = ((value % 10) + 0x30);
  LCD_goto(8, 1);
  LCD_putchar(ch);
}

 

Schematic

I2C LCD Driver Schematic 2 Wire LCD_Schematic

Explanation

Just like the last example, software method is used to emulate I2C protocol using ordinary GPIOs. There are three parts of the code – first the software I2C driver, second the driver library for PCF8574 I2C 8-bit port expander and lastly the LCD driver itself. The LCD driver is same as the other LCD drivers in this document. I kept the code modular so that it is easy to understand the role of each piece of code. The I2C driver (SW_I2C) implements software I2C which is used by the PCF8574 driver. Thus, the port expander driver is dependent on the SW_I2C driver and the LCD driver is dependent on the port expander driver, and in cases like such we must find add libraries according to the order of dependency.

The advantage of keeping things modular is to easily modify things in a fast and trouble-free manner while keeping things ready for other deployments. In my codes I try to avoid repetitive and meaningless stuffs with meaningful definitions. For instance, just change the following lines to change pin configurations without going through the whole code:

#define SDA_DIR_OUT()   P03_PushPull_Mode
#define SDA_DIR_IN()    P03_Input_Mode

#define SCL_DIR_OUT()   P04_PushPull_Mode
#define SCL_DIR_IN()    P04_Input_Mode

#define SDA_HIGH()      set_P03
#define SDA_LOW()       clr_P03

#define SCL_HIGH()      set_P04
#define SCL_LOW()       clr_P04

#define SDA_IN()        P03

Likewise, the SW_I2C functions are not implemented inside the LCD or port expander driver files so that they can be used for other I2C devices.

I have code two versions of this LCD library just like the SPI-based ones – one with BSP-based delays and the other with software delays.

Demo

I2C LCD

Driving seven Segments by Bit-banging TM1640

Seven segment displays take up lot of GPIO pins when they are required to be interfaced with a host micro. There are several driver ICs like MAX7219, TM1640, 74HC594, etc to overcome this issue. TM1640 from Titan Micro Electronics does not support standard I2C or SPI communication protocol unlike most other driver ICs. Thus, to interface it with our host N76E003 micro, we need to apply bit-banging method just like the LCD examples.

TM1640

Code

 

fonts.h

const unsigned char fonts[11] =
{                       
  0x00, // (32)   <space>
  0x3F, // (48)   0
  0x06, // (49)   1
  0x5B, // (50)   2
  0x4F, // (51)   3
  0x66, // (52)   4 
  0x6D, // (53)   5 
  0x7D, // (54)   6 
  0x27, // (55)   7
  0x7F, // (56)   8 
  0x6F, // (57)   9 
};  

 

TM1640.h

#define TM1640_GPIO_init()                               do{P03_PushPull_Mode; P04_PushPull_Mode;}while(0)

#define DIN_pin_HIGH()                                   set_P03
#define DIN_pin_LOW()                                    clr_P03

#define SCLK_pin_HIGH()                                  set_P04
#define SCLK_pin_LOW()                                   clr_P04

#define no_of_segments                                   16

#define auto_address                                     0x40
#define fixed_address                                    0x44
#define normal_mode                                      0x40
#define test_mode                                        0x48

#define start_address                                    0xC0

#define brightness_5_pc                                  0x88
#define brightness_10_pc                                 0x89
#define brightness_25_pc                                 0x8A
#define brightness_60_pc                                 0x8B
#define brightness_70_pc                                 0x8C
#define brightness_75_pc                                 0x8D
#define brightness_80_pc                                 0x8E
#define brightness_100_pc                                0x8F
#define display_off                                      0x80
#define display_on                                       0x8F


void TM1640_init(unsigned char brightness_level);  
void TM1640_start(void);
void TM1640_stop(void);
void TM1640_write(unsigned char value);      
void TM1640_send_command(unsigned char value);
void TM1640_send_data(unsigned char address, unsigned char value);
void TM1640_clear_display(void);

 

TM1640.c

#include "N76E003.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "Delay.h"
#include "TM1640.h"


void TM1640_init(unsigned char brightness_level)
{               
  TM1640_GPIO_init();

  Timer0_Delay1ms(10); 

  DIN_pin_HIGH();
  SCLK_pin_HIGH();

  TM1640_send_command(auto_address);
  TM1640_send_command(brightness_level);
  TM1640_clear_display();
}  

void TM1640_start(void)
{
  DIN_pin_HIGH();
  SCLK_pin_HIGH();
  Timer3_Delay10us(1);
  DIN_pin_LOW();
  Timer3_Delay10us(1);
  SCLK_pin_LOW();
}

void TM1640_stop(void)
{
  DIN_pin_LOW();
  SCLK_pin_LOW();
  Timer3_Delay10us(1);
  SCLK_pin_HIGH();
  Timer3_Delay10us(1);
  DIN_pin_HIGH();
}


void TM1640_write(unsigned char value) 
{                                                      
  unsigned char s = 0x08;

  while(s > 0)
  {
    SCLK_pin_LOW();

    if((value & 0x01) == 0x01)
    {
     DIN_pin_HIGH();
    }
    else
    {
     DIN_pin_LOW();
    }

    SCLK_pin_HIGH();

    value >>= 0x01;
    s--;
  };
}                                

void TM1640_send_command(unsigned char value)   
{                           
  TM1640_start();
  TM1640_write(value);
  TM1640_stop();
}              

void TM1640_send_data(unsigned char address, unsigned char value)
{                 
  TM1640_send_command(fixed_address);

  TM1640_start();

  TM1640_write((0xC0 | (0x0F & address)));
  TM1640_write(value);

  TM1640_stop();

void TM1640_clear_display(void)
{
  unsigned char s = 0x00;

  for(s = 0x00; s < no_of_segments; s++)
  {
    TM1640_send_data(s, 0);
  };
}

 

main.c

#include "N76E003.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "Delay.h"
#include "font.h"
#include "TM1640.h"

void display_data(unsigned char segment, signed int value);

void main(void)
  unsigned int i = 0;
  unsigned int j = 999;

  TM1640_init(brightness_75_pc);;

  while(1)
  {
    display_data(0, i++);
    display_data(4, j--);
    Timer3_Delay100ms(4);
  };
}

void display_data(unsigned char segment, signed int value)
{
    unsigned char ch = 0;

    if((value > 99) && (value <= 999))
    {
      ch = (value / 100);
      TM1640_send_data((2 + segment), fonts[1 + ch]);

      ch = ((value / 10) % 10);
      TM1640_send_data((1 + segment), fonts[1 + ch]);

      ch = (value % 10);
      TM1640_send_data(segment, fonts[1 + ch]);
    }

    else if((value > 9) && (value <= 99))
    {
      TM1640_send_data((2 + segment), 0);

      ch = (value / 10);
      TM1640_send_data((1 + segment), fonts[1 + ch]);

      ch = (value % 10);
      TM1640_send_data(segment, fonts[1 + ch]);
    }

    else
    {
      TM1640_send_data((2 + segment), 0);

      TM1640_send_data((1 + segment), 0);

      ch = (value % 10);
      TM1640_send_data(segment, fonts[1 + ch]);
    }
}

 

Schematic

TM1640_Schematic

Explanation

Like the LCD libraries demoed previously, TM1640 is driven with GPIO bit-banging. Please read the datasheet of TM1640 to fully understand how the codes are implemented. It uses two pins just like I2C but don’t be fooled as it doesn’t support I2C protocol. It uses a protocol of its own. To change pin configuration, just change the following lines of code:

#define TM1640_GPIO_init()                               do{P03_PushPull_Mode; P04_PushPull_Mode;}while(0)

#define DIN_pin_HIGH()                                   set_P03
#define DIN_pin_LOW()                                    clr_P03

#define SCLK_pin_HIGH()                                  set_P04
#define SCLK_pin_LOW()                                   clr_P04

 

Demo

TM1640

External Interrupt (EXTI)

External interrupt is a key GPIO feature in input mode. It momentarily interrupts regular program flow just like other interrupts and does some tasks before resuming interrupted task. In traditional 8051s, there are two external interrupts with dedicated and separate interrupt vector addresses. The same applies to N76E003. Highlighted below in the N76E003’s interrupt vector table are the interrupt vector addresses/numbers of these two external interrupts:

Interrupt Vector

Code

#include "N76E003.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "Delay.h"

void setup(void);

void EXT_INT0(void)
interrupt 0
{
  set_P00;
}

void EXT_INT1(void)
interrupt 2
{
  set_P01;
}

void main(void)
{
  setup();

  while(1)
  {
    Timer0_Delay1ms(1000);
    clr_P00;
    clr_P01;
  };
}

void setup(void)
{
  P00_PushPull_Mode;
  P01_PushPull_Mode;
  P17_Input_Mode;
  P30_Input_Mode; 
  set_P1S_7;
  set_P3S_0;
  set_IT0;
  set_IT1;
  set_EX0;
  set_EX1;
  set_EA;   
}

 

Schematic

EXTI schematic

Explanation

The setup for this demo is simple. There are two LEDs and two buttons connected with a N76E003 chip as per schematic. The buttons are connected with external interrupt pins. Obviously, these pins are declared as input pins. Additionally, internal input Schmitt triggers of these pins are used to ensure noise cancellation. Both interrupts are enabled along with their respective interrupt hardware. Finally, global interrupt is set. Optionally interrupt priority can be applied.

P17_Input_Mode;
P30_Input_Mode; 

set_P1S_7;
set_P3S_0;

set_IT0;
set_IT1;

set_EX0;
set_EX1;
set_EA;   

Since we enabled two interrupts with different interrupt vectors, there will be two interrupt subroutine functions. Each of these functions will briefly turn on LEDs assigned to them. The LEDs are turned off in the main function. Thus, the LEDs mark which interrupt occurred.

void EXT_INT0(void)
interrupt 0
{
  set_P00;
}

void EXT_INT1(void)
interrupt 2
{
  set_P01;
}

 

Demo

EXTI

Pin Interrupt – Interfacing Rotary Encoder

Apart from dedicated external interrupts, N76E003 is equipped with pin interrupt facility – a feature that can be found in almost every microcontroller of modern times. With pin interrupt, any GPIO can be made to behave like external interrupt. However, unlike external interrupts, a single hardware interrupt channel and therefore one vector address is used for mapping a maximum of eight different GPIO pins. These pins need not to be on the same GPIO port. When interrupt occurs, we need to assert from which pin it originated. This feature becomes very useful when interfacing keypads and buttons.

Pin Interrupt Structure

Code

#include "N76E003_IAR.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "soft_delay.h"
#include "LCD_2_Wire.h"

signed char encoder_value = 0;

void setup(void);
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value);

#pragma vector = 0x3B
__interrupt void PIN_INT(void)
{
  clr_EA;

  if(PIF == 0x01)
  {
    if((P1 & 0x03) == 0x02)
    {
      encoder_value++;
    }

    if(encoder_value > 99)
    {
      encoder_value = 0;
    }
  }

  if(PIF == 0x02)
  {       
    if((P1 & 0x03) == 0x01)
    {
      encoder_value--;
    }

    if(encoder_value < 0)
    {
      encoder_value = 99;
    }
  }

  PIF = 0x00;

  P15 = ~P15;
}

void main(void)
{
  setup();

  while(1)
  {
    set_EA;
    lcd_print(14, 0, encoder_value);
    delay_ms(40);
  }
}

void setup(void)
{   
  P10_Input_Mode;
  P11_Input_Mode; 

  P15_PushPull_Mode;

  Enable_BIT0_LowLevel_Trig;
  Enable_BIT1_LowLevel_Trig;

  Enable_INT_Port1;

  set_EPI;  

  LCD_init();
  LCD_clear_home();
  LCD_goto(0, 0);
  LCD_putstr("ENC Count:");
}

void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value)
  LCD_goto(x_pos, y_pos);
  LCD_putchar((value / 10) + 0x30);
  LCD_goto((x_pos + 1), y_pos);
  LCD_putchar((value % 10) + 0x30);
}

 

Schematic

pin int schematic

Explanation

Pin interrupt is not same as dedicated external interrupt but still it is very useful in a number of cases. In this demo, two pin interrupts are used to decode a rotary encoder. Probably this is the simplest method of decoding a rotary encoder.

Setting up pin interrupt is very easy. We need to set the pin interrupt pins are inputs. We can optionally use the internal Schmitt triggers. Then we decide the edge to detect and which ports to check for pin interrupt. Finally, we set the pin interrupt hardware.

P10_Input_Mode;
P11_Input_Mode;

Enable_BIT0_LowLevel_Trig;
Enable_BIT1_LowLevel_Trig;

Enable_INT_Port1;

set_EPI;  

Inside the pin interrupt function, we need to check which pin shot the interrupt by checking respective flags. Encoder count is incremented/decremented based on which flag got shot first and the logic state of the other pin. Since here a rotary encoder is interfaced with pin interrupt facility of N76E003, we have to ensure that the micro doesn’t detect any further or false interrupts while already processing one interrupt condition. This is why the global interrupt is disabled every time the code enters the pin interrupt function. This is restarted in the main. Similarly, to ensure proper working we have clear the interrupt flags before exiting the function. P15 is toggled with interrupt to visually indicate the rotation of the encoder.

#pragma vector = 0x3B
__interrupt void PIN_INT(void)
{
  clr_EA;

  if(PIF == 0x01)
  {
    if((P1 & 0x03) == 0x02)
    {
      encoder_value++;
    }

    if(encoder_value > 99)
    {
      encoder_value = 0;
    }
  }

  if(PIF == 0x02)
  {       
    if((P1 & 0x03) == 0x01)
    {
      encoder_value--;
    }

    if(encoder_value < 0)
    {
      encoder_value = 99;
    }
  }

  PIF = 0x00;

  P15 = ~P15;
}

The main code just shows the encoder count. When the encoder is rotated in one direction, the count increases while rotating it in the opposite direction causes the encoder count to decrease.

Demo

Pin Int

Clock System

The clock system of N76E003 is very straight forward and very flexible. To begin with, there are three clock sources, a clock selector and a common clock divider block apart from other blocks. Shown below is the block diagram of N76E003’s clock system:

Clock System

The three sources are as follows:

Clock Sources

Once a given clock source is set, it becomes the clock for all systems. The only exception here is the watchdog timer and the self-wake-up timer which are only run by the LIRC.

 

Code

#include "N76E003_iar.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "Delay.h"

#define HIRC    0
#define LIRC    1
#define ECLK    2

void set_clock_source(unsigned char clock_source);
void disable_clock_source(unsigned char clock_source);
void set_clock_division_factor(unsigned char value);

void main(void)
{
  signed char i = 30;     

  P11_PushPull_Mode; 
  P15_PushPull_Mode;

  set_clock_division_factor(0);
  set_clock_source(HIRC);

  set_CLOEN;

  while(i > 0)                                  
  {  
    clr_P15;                            
    Timer0_Delay1ms(100);
    set_P15;
    Timer0_Delay1ms(100);
    i--;
  }

  set_clock_source(ECLK);
  disable_clock_source(HIRC);

  i = 30;

  while(i > 0)                                  
  {  
    clr_P15;                            
    Timer0_Delay1ms(100);
    set_P15;
    Timer0_Delay1ms(100);
    i--;
  }

  set_clock_source(LIRC);
  disable_clock_source(HIRC);

  while(1)
  {
    clr_P15;                            
    Timer0_Delay1ms(1);
    set_P15;
    Timer0_Delay1ms(1);
  };
}

void set_clock_source(unsigned char clock_source)
{
  switch(clock_source)
  {
    case LIRC:
    {
      set_OSC1;                     
      clr_OSC0; 

      break;
    }

    case ECLK:
    {
      set_EXTEN1;
      set_EXTEN0;

      while((CKSWT & SET_BIT3) == 0); 

      clr_OSC1;                     
      set_OSC0;

      break;
    }

    default:
    {
      set_HIRCEN;         

      while((CKSWT & SET_BIT5) == 0);   

      clr_OSC1;                       
      clr_OSC0;

      break;
    }
  }

  while((CKEN & SET_BIT0) == 1);  
}

void disable_clock_source(unsigned char clock_source)
{
  switch(clock_source)
  {
    case HIRC:
    {
      clr_HIRCEN;
      break;
    }

    default:
    {
      clr_EXTEN1;
      clr_EXTEN0;
      break;
    }
  }
}

void set_clock_division_factor(unsigned char value)
{
  CKDIV = value;
}

 

Schematic

CLK_Schematic

Explanation

The very first thing to note is the absence of two OSC pins unlike other micros and yet a crystal resonator is connected with P30 and P17. This configuration is not correct. There is only OSCIN pin. This is because N76E003 can only be driven with active clock sources like crystal modules, external electronic circuitry, etc. The HIRC clock source is accurate enough for most purposes and there is literally no need for external clock. I have done most of the experiments with HIRC and I’m satisfied with it.

Many people don’t understand the difference between a crystal oscillator module and a crystal resonator. Both are based on quartz crystals but the oscillator one has internal electronics to generate clock pulses precisely while the resonator just contains the quartz crystal. Crystal modules are accurate compared to resonators because the internal electronics in them take care of the effects of temperature. Resonators are therefore called passive clock crystals while the clock modules are termed active clocks.

crystal

Here to test all three clock sources, I used two things – first the onboard LED and second the clock output pin. Different clock sources are enabled briefly one after another and the onboard LED is blinked. The blinking rate of the LED is an indirect indicator of clock speed. The clock output too is monitored with an oscilloscope/signal analyser for clock speeds. HIRC is turned on first, then ECLK and finally LIRC. By default, both HIRC and LIRC are turned on during power on. When switching between clock sources, we should poll if the new clock source is stable prior to using it and disable the one that we don’t need.

I have coded the following three for setting up the clock system. Their names suggest their purposes.

void set_clock_source(unsigned char clock_source);
void disable_clock_source(unsigned char clock_source);
void set_clock_division_factor(unsigned char value);

These three functions will be all that you’ll ever need to configure the clock system without any hassle. The first two are most important as they select clock source and disabled the one that is not need. If you are still confused about setting the system clock then you can avoid the clock division function and straight use the following function:

void set_clock_frequency(unsigned long F_osc, unsigned long F_sys)
{
  F_osc = (F_osc / (2 * F_sys));

  if((F_osc >= 0x00) && (F_osc <= 0xFF))
  {
    CKDIV = ((unsigned char)F_osc);
  }
}

This function takes two parameters – the frequency of the clock source and the frequency of the system after clock division.

Demo

Clock System

12-Bit ADC – LM35 Thermometer

Most 8051s don’t have any embedded ADC but N76E003 comes with a 12-bit SAR ADC. This is also one area where N76E003 differs a lot from STM8S003. The 12-bit resolution is the factor. N76E003 has eight single-ended ADC inputs along with a bandgap voltage generator and a built-in comparator. The ADC can be triggered internally with software or by external hardware pins/PWM. Everything is same as the ADCs of other microcontrollers and there’s not much difference.

ADC_Structure

LM35

Code

#include "N76E003_IAR.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "Delay.h"
#include "LCD_3_Wire.h"

#define scalar          0.12412

void setup(void);
unsigned int ADC_read(void);
void lcd_print_i(unsigned char x_pos, unsigned char y_pos, unsigned int value);
void lcd_print_f(unsigned char x_pos, unsigned char y_pos, unsigned int value);

void main(void)
{
  unsigned int temp = 0;
  unsigned int adc_count = 0;

  setup();

  while(1)
  {
    adc_count = ADC_read();
    temp = ((unsigned int)(((float)adc_count) / scalar));
    lcd_print_i(12, 0, adc_count);
    lcd_print_f(11, 1, temp);
    Timer0_Delay1ms(600);
  }
}

void setup(void)
{
  LCD_init();
  LCD_clear_home();
  LCD_goto(0, 0);
  LCD_putstr("ADC Count:");
  LCD_goto(0, 1);
  LCD_putstr("Tmp/deg C:");

  Enable_ADC_AIN0;
}

unsigned int ADC_read(void)
{
  register unsigned int value = 0x0000;

  clr_ADCF;
  set_ADCS;                 
  while(ADCF == 0);

  value = ADCRH;
  value <<= 4;
  value |= ADCRL;

  return value;
}

void lcd_print_i(unsigned char x_pos, unsigned char y_pos, unsigned int value)
{  
  LCD_goto(x_pos, y_pos);
  LCD_putchar((value / 1000) + 0x30);
  LCD_goto((x_pos + 1), y_pos);
  LCD_putchar(((value % 1000) / 100) + 0x30); 
  LCD_goto((x_pos + 2), y_pos);
  LCD_putchar(((value % 100) / 10) + 0x30);
  LCD_goto((x_pos + 3), y_pos);
  LCD_putchar((value % 10) + 0x30);
}

void lcd_print_f(unsigned char x_pos, unsigned char y_pos, unsigned int value)
  LCD_goto(x_pos, y_pos);
  LCD_putchar((value / 1000) + 0x30);
  LCD_goto((x_pos + 1), y_pos);
  LCD_putchar(((value % 1000) / 100) + 0x30); 
  LCD_goto((x_pos + 2), y_pos);
  LCD_putchar('.');
  LCD_goto((x_pos + 3), y_pos);
  LCD_putchar(((value % 100) / 10) + 0x30);
  LCD_goto((x_pos + 4), y_pos);
  LCD_putchar((value % 10) + 0x30);
}

 

Schematic

ADC_Schematic

Explanation

In this demo, one ADC channel (AIN0) is used to read a LM35 temperature sensor. Polling method is used to read the ADC.

Enabling the ADC is simply done by coding the following line:

Enable_ADC_AIN0;

In the background of this, ADC channel selection and other parameters are set. If you want more control over the ADC then you must set the ADC registers on your own. Most of the times that can be avoided.

Reading the ADC needs some attention because the ADC data registers are not aligned like other registers and we just need 12-bits, not 8/16-bits.

ADC_data_register

Notice that the we must extract ADC from ADCCRH and from the low four bits of ADCCRL. To handle this issue the follow function is devised:

unsigned int ADC_read(void)
{
  register unsigned int value = 0x0000;

 

  clr_ADCF;
  set_ADCS;                 
  while(ADCF == 0);

 

  value = ADCRH;
  value <<= 4;
  value |= ADCRL;

 

  return value;
}

When this function is called, the ADC conversion completion flag is cleared and the ADC is software-triggered. The conversion completion flag is polled. When this flag status is changed, the ADC data registers are read. Finally, the value is returned.

LM35 gives 10mV output for each degree of temperature. Therefore, for 26°C the sensor is supposed to give 260mV output. At 3.3V the ADC count will 4095 while at 0V the ADC count will be 0 count. Thus, 0.806mV equals one count and so 260mV should give:

Counts

 

In the display, we have to show 26.00°C i.e. 2600 considering no decimal point and fully integer value. Thus, to transform 322 to 2600, we have to divide the result with a scalar (0.12412). The main code just does that after reading the ADC. The ADC count and temperature are then both shown in the LCD.

Demo

ADC

ADC Interrupt – LDR-based Light Sensor

Like any other interrupts, ADC interrupt is a very interrupt. In the last example we saw polling-based ADC readout. In this segment, we will see how to use interrupt-based method to extract ADC data. The concept of ADC interrupt is simply to notify that an ADC data has been made ready for reading once triggered.

LDR

Code

#include "N76E003_IAR.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "soft_delay.h"
#include "LCD_2_Wire.h"

//LDR Definitions//

#define LDR_constant                  100000.0
#define R_fixed                        10000.0            
#define VDD                               4095

unsigned int adc_value = 0x0000;

void setup(void);
unsigned int ADC_read(void);
void lcd_print_i(unsigned char x_pos, unsigned char y_pos, unsigned int value);
unsigned int measure_light_intensity(void);

#pragma vector = 0x5B
__interrupt void ADC_ISR(void)
{
  adc_value = ADC_read();
  clr_ADCF;
}

void main(void)
{
  unsigned int lux = 0;

  setup();

  while(1)
  {
    set_ADCS;
    lux = measure_light_intensity();
    lcd_print_i(12, 0, adc_value);
    lcd_print_i(12, 1, lux);
    delay_ms(400);
  }
}

void setup(void)
{
  LCD_init();
  LCD_clear_home();
  LCD_goto(0, 0);
  LCD_putstr("ADC Count:");
  LCD_goto(0, 1);
  LCD_putstr("Lux Value:");

  Enable_ADC_AIN4;
  set_EADC;
  set_EA;
}

unsigned int ADC_read(void)
{
  register unsigned int value = 0x0000; 

  value = ADCRH;
  value <<= 4;
  value |= ADCRL;

  return value;
}

void lcd_print_i(unsigned char x_pos, unsigned char y_pos, unsigned int value)
  LCD_goto(x_pos, y_pos);
  LCD_putchar((value / 1000) + 0x30);
  LCD_goto((x_pos + 1), y_pos);
  LCD_putchar(((value % 1000) / 100) + 0x30); 
  LCD_goto((x_pos + 2), y_pos);
  LCD_putchar(((value % 100) / 10) + 0x30);
  LCD_goto((x_pos + 3), y_pos);
  LCD_putchar((value % 10) + 0x30);
}

unsigned int measure_light_intensity(void)
{
  float lux = 0;

  lux = adc_value;

  lux = (LDR_constant * ((VDD / (R_fixed * lux))) - 0.1);

  if((lux >= 0) && (lux <= 9999))
  {
    return ((unsigned int)lux);
  }
  else
  {
    return 0;
  }
}

 

Schematic

ADC_INT_Schematic

Explanation

Setting the ADC in interrupt is not much different from the previous example except for the interrupt parts.

Enable_ADC_AIN4;
set_EADC;
set_EA;

We have to enable both the ADC and global interrupts.

The reading process is also same:

unsigned int ADC_read(void)
{
  register unsigned int value = 0x0000; 

 

  value = ADCRH;
  value <<= 4;
  value |= ADCRL;

 

  return value;
}

The ADC is triggered in the main with the following line of code since we are using software-based triggering:

set_ADCS;

Now instead of reading the ADC in the main by polling, the ADC is read in the interrupt.

#pragma vector = 0x5B
__interrupt void ADC_ISR(void)
{
  adc_value = ADC_read();
  clr_ADCF;
}

When ADC interrupt occurs, we must clear ADC interrupt flag and read the ADC data registers. In this way, the main code is made free from polling and free for other tasks.

The demo here is a rudimentary LDR-based light intensity meter. Light falling on the LDR changes its resistance. Using voltage divider method, we can back calculate the resistance of the LDR and use this info to measure light intensity.

Demo

ADC Int

ADC Comparator

Many micros are equipped with on-chip analogue comparator. N76E003 has an embedded ADC comparator. The main feature of this comparator is the range it offers. Unlike comparators of other micros in which only a few selectable set points can set, the ADC comparator of N76E003 can be set in any range from 0 count to the max ADC count of 4095. This allows us to easily implement it for many applications like low battery alarm, SMPSs, over voltage sense, overload detection, etc. It must be noted however, it is not a true comparator because a true comparator has nothing to do with ADC.

adc_comparator_block

The comparator block is situated at the output of the ADC and so it is just a single block like the ADC but all eight channels share it. Thus, when it is needed to compare multiple channels, it should be reset and reconfigured.

Code

#include "N76E003_IAR.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "soft_delay.h"
#include "LCD_2_Wire.h"

unsigned int adc_value = 0x0000;

void setup(void);
unsigned int ADC_read(void);
void set_ADC_comparator_value(unsigned int value);
void lcd_print_i(unsigned char x_pos, unsigned char y_pos, unsigned int value);

#pragma vector = 0x5B
__interrupt void ADC_ISR(void)
{
  adc_value = ADC_read();
  clr_ADCF;
}

void main(void)
{
  setup();

  while(1)
  {
    set_ADCS;
    lcd_print_i(12, 0, adc_value);

    LCD_goto(12, 1);

    if((ADCCON2 & 0x10) != 0x00)
    {
      LCD_putstr("HIGH");
      set_P15;
    }
    else
    {
      LCD_putstr(" LOW");
      clr_P15;
    }

    delay_ms(400);
  }
}

void setup(void)
{
  P15_PushPull_Mode;

  LCD_init();
  LCD_clear_home();
  LCD_goto(0, 0);
  LCD_putstr("ADC Count:");
  LCD_goto(0, 1);
  LCD_putstr("Cmp State:");

  Enable_ADC_BandGap;
  Enable_ADC_AIN4;

  set_ADC_comparator_value(1023); 
  set_ADCMPEN;

  set_EADC;
  set_EA;
}

unsigned int ADC_read(void)
{
  register unsigned int value = 0x0000; 

  value = ADCRH;
  value <<= 4;
  value |= ADCRL;

  return value;
}

void set_ADC_comparator_value(unsigned int value)
{
  ADCMPH = ((value & 0x0FF0) >> 4);
  ADCMPL = (value & 0x000F);
}

void lcd_print_i(unsigned char x_pos, unsigned char y_pos, unsigned int value)
  LCD_goto(x_pos, y_pos);
  LCD_putchar((value / 1000) + 0x30);
  LCD_goto((x_pos + 1), y_pos);
  LCD_putchar(((value % 1000) / 100) + 0x30); 
  LCD_goto((x_pos + 2), y_pos);
  LCD_putchar(((value % 100) / 10) + 0x30);
  LCD_goto((x_pos + 3), y_pos);
  LCD_putchar((value % 10) + 0x30);
}

 

Schematic

ADC_comparator_Schematic

Explanation

To configure the ADC comparator block, we just need to specify two things – the reference value with which the comparison should to be done and the polarity of comparison. After setting these up, we have to enable the comparator block. These are done as follows:

set_ADC_comparator_value(1023); 
set_ADCMPEN;

Note in the code snippet above, I didn’t code anything regarding polarity because by default the polarity is set as such that the comparator’s output will change state when the input voltage is greater than or equal to the set ADC count level of 1023.

ADC Comparator

Just like ADC reading, we have to take care of the bit positions for the comparator set point. It is coded as follows.

void set_ADC_comparator_value(unsigned int value)
{
  ADCMPH = ((value & 0x0FF0) >> 4);
  ADCMPL = (value & 0x000F);
}

In this demo, I also used the bandgap voltage as reference source:

Enable_ADC_BandGap;

The rest of the code is similar to the ADC interrupt code.

Demo

ADC Comparator (1) ADC Comparator (2)

Data Flash – Using APROM as EEPROM

In most standard 8051s, there is no dedicated memory space as EEPROM. This is unlike other microcontrollers of modern era. EEPROM memory is needed for the storage of critical data that need to be retained even in power down state. In N76E003, there are two types of ROM memory. EEPROM-like storage can be achieved by using APROM (Application ROM). APROM is the actual flash memory where store our application codes. User Code Loader ROM or LDROM of N76E003 microcontroller, is another ROM where we can keep a bootloader and configuration codes. Sizes of these ROMs can be varied according to configuration bits.

Memory Block

Code

 

Flash.h

#define  CID_READ            0x0B
#define  DID_READ            0x0C

#define  PAGE_ERASE_AP       0x22
#define  BYTE_READ_AP        0x00
#define  BYTE_PROGRAM_AP     0x21
#define  PAGE_SIZE           128u

#define  ERASE_FAIL          0x70
#define  PROGRAM_FAIL        0x71
#define  IAPFF_FAIL          0x72
#define  IAP_PASS            0x00

void enable_IAP_mode(void);
void disable_IAP_mode(void);
void trigger_IAP(void);
unsigned char write_data_to_one_page(unsigned int u16_addr, const unsigned char *pDat, unsigned char num);
void write_data_flash(unsigned int u16_addr, unsigned char *pDat, unsigned int num);
void read_data_flash(unsigned int u16_addr, unsigned char *pDat, unsigned int num);

 

Flash.c

#include "N76E003_iar.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "Delay.h"
#include "soft_delay.h"
#include "Flash.h"

static unsigned char EA_Save_bit;

void enable_IAP_mode(void)
{
    EA_Save_bit = EA;
    clr_EA;
    TA = 0xAA;
    TA = 0x55;
    CHPCON |= 0x01 ;
    TA = 0xAA;
    TA = 0x55;
    IAPUEN |= 0x01;
    EA = EA_Save_bit;
}

void disable_IAP_mode(void)
{
    EA_Save_bit = EA;
    clr_EA;
    TA = 0xAA;
    TA = 0x55;
    IAPUEN &= ~0x01;
    TA = 0xAA;
    TA = 0x55;
    CHPCON &=~ 0x01;
    EA = EA_Save_bit;
}

void trigger_IAP(void)
{
    EA_Save_bit = EA;
    clr_EA;
    TA = 0xAA;
    TA = 0x55;
    IAPTRG |= 0x01;
    EA = EA_Save_bit;
}


unsigned char write_data_to_one_page(unsigned int u16_addr, const unsigned char *pDat, unsigned char num)
{
    unsigned char i = 0;
    unsigned char offset = 0;
    unsigned char __code *pCode;
    unsigned char __xdata *xd_tmp;

    enable_IAP_mode();
    offset = (u16_addr & 0x007F);
    i = (PAGE_SIZE - offset);

    if(num > i)
    {
      num = i;
    }

    pCode = (unsigned char __code *)u16_addr;

    for(i = 0; i < num; i++)
    {
        if(pCode[i] != 0xFF)
        {
          break;
        }
    }

    if(i == num)
    {
        IAPCN = BYTE_PROGRAM_AP;
        IAPAL = u16_addr;
        IAPAH = (u16_addr >> 8);

        for(i = 0; i < num; i++)
        {
          IAPFD = pDat[i];
          trigger_IAP();
          IAPAL++;
        }

        for(i = 0; i < num; i++)
        {
          if(pCode[i] != pDat[i])
          {
                   break;   
          } 
        }

        if(i != num)
        {
          goto WriteDataToPage20;
        }
    }

    else
    {
      WriteDataToPage20:
      pCode = (unsigned char __code *)(u16_addr & 0xFF80);
      for(i = 0; i < 128; i++)
      {
           xd_tmp[i] = pCode[i];
      }

      for(i = 0; i < num; i++)
      {
           xd_tmp[offset + i] = pDat[i];
      }

      do
      {
           IAPAL = (u16_addr & 0xFF80);
           IAPAH = (u16_addr >> 8);
           IAPCN = PAGE_ERASE_AP;
           IAPFD = 0xFF;    
           trigger_IAP();
           IAPCN =BYTE_PROGRAM_AP;

           for(i = 0; i < 128; i++)
           {
                IAPFD = xd_tmp[i];
                trigger_IAP();
                IAPAL++;
           }

           for(i = 0; i < 128; i++)
           {
                if(pCode[i] != xd_tmp[i])
                {
                     break;
                }
           }
      }while(i != 128);

    }

    disable_IAP_mode();

    return num;
}   


void write_data_flash(unsigned int u16_addr, unsigned char *pDat,unsigned int num)
{
    unsigned int CPageAddr = 0;
    unsigned int EPageAddr = 0;
    unsigned int cnt = 0;

    CPageAddr = (u16_addr >> 7);
    EPageAddr = ((u16_addr + num) >> 7);

    while(CPageAddr != EPageAddr)
    {
      cnt = write_data_to_one_page(u16_addr, pDat, 128);
      u16_addr += cnt;
      pDat += cnt;
      num -= cnt;
      CPageAddr = (u16_addr >> 7);
    }

    if(num)
    {
      write_data_to_one_page(u16_addr, pDat, num);
    }
}

void read_data_flash(unsigned int u16_addr, unsigned char *pDat, unsigned int num)
{
    unsigned int i = 0;

    for(i = 0; i < num; i++)
    {
        pDat[i] = *(unsigned char __code *)(u16_addr+i);
    }
}

 

main.c

#include "N76E003_IAR.h"
#include "Common.h"
#include "Delay.h"
#include "soft_delay.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "LCD_2_Wire.h"
#include "Flash.h"

#define BASE_ADDRESS        3700

void lcd_print_c(unsigned char x_pos, unsigned char y_pos, unsigned char value);
void lcd_print_i(unsigned char x_pos, unsigned char y_pos, unsigned int value);

void main (void)
{
  unsigned char s = 0;
  unsigned char val[1] = {0};
  unsigned char ret_val[1] = {0};

  P15_PushPull_Mode;

  LCD_init();
  LCD_clear_home();

  clr_P15;  
  LCD_goto(0, 0);
  LCD_putstr("R Addr:");
  LCD_goto(0, 1);
  LCD_putstr("R Data:");

  for(s = 0; s <= 9; s++)
  {
    read_data_flash((s + BASE_ADDRESS), ret_val, 1);
    delay_ms(10);
    lcd_print_i(11, 0, (s + BASE_ADDRESS));
    lcd_print_c(13, 1, ret_val[0]);
    delay_ms(600);
  }

  delay_ms(2000);

  set_P15;  
  LCD_goto(0, 0);
  LCD_putstr("W Addr:");
  LCD_goto(0, 1);
  LCD_putstr("W Data:");

  for(s = 0; s <= 9; s++)
  {
    val[0] = s;   
    write_data_flash((s + BASE_ADDRESS), val, 1);
    delay_ms(10);
    lcd_print_i(11, 0, (s + BASE_ADDRESS));
    lcd_print_c(13, 1, val[0]);
    delay_ms(600);
  }

  while(1)
  {
  };
}

void lcd_print_c(unsigned char x_pos, unsigned char y_pos, unsigned char value)
  LCD_goto(x_pos, y_pos);
  LCD_putchar((value / 100) + 0x30);
  LCD_goto((x_pos + 1), y_pos);
  LCD_putchar(((value % 10) / 10) + 0x30);
  LCD_goto((x_pos + 2), y_pos);
  LCD_putchar((value % 10) + 0x30);
}

void lcd_print_i(unsigned char x_pos, unsigned char y_pos, unsigned int value)
  LCD_goto(x_pos, y_pos);
  LCD_putchar((value / 10000) + 0x30);
  LCD_goto((x_pos + 1), y_pos);
  LCD_putchar(((value % 10000) / 1000) + 0x30); 
  LCD_goto((x_pos + 2), y_pos);
  LCD_putchar(((value % 1000) / 100) + 0x30);
  LCD_goto((x_pos + 3), y_pos);
  LCD_putchar(((value % 100) / 10) + 0x30);
  LCD_goto((x_pos + 4), y_pos);
  LCD_putchar((value % 10) + 0x30);
}

 

Schematic

EEPROM Schematic

Explanation

To write and read data from flash we can use the following functions:

void write_data_flash(unsigned int u16_addr, unsigned char *pDat, unsigned int num);
void read_data_flash(unsigned int u16_addr, unsigned char *pDat, unsigned int num);

Both of these functions are pointer-based functions. The first parameter of these function is the physical location of data, the second argument is the data pointer itself and the last argument is the number of bytes to read/write. Try to use the upper addresses of the flash where application code usually doesn’t reside. Reading the flash doesn’t require any involvement of IAP while writing does require IAP. The functions are self-explanatory and can be used readily without any changes since they are provided in the BSP examples.

In the demo, ten data bytes are saved in ten separate locations starting from location 3700 to 3709. Prior to that these locations are read. On first start up, these locations have no saved data and so they show up garbage values (205/255 usually). When a valid user data is saved, the location is updated with it. When reset or powered down, the newly written data bytes are retained and reread when powered up again.

Try not to frequently write on the flash since flash memories wear on frequent writes. Use a RAM-based buffer and try to write one page of flash when the buffer is full. In this way, flash memory wear-and-tear is slightly reduced.

Be careful about saving locations. Don’t use locations that hold application code. Try to use unoccupied empty flash locations or upper flash addresses. Use the NuMicro ISP Programming Tool for finding empty locations.

Demo

EEPROM

The post Getting Started with Nuvoton 8-bit Microcontrollers – Coding Part 1 appeared first on Embedded Lab.

Getting Started with Nuvoton 8-bit Microcontrollers – Coding Part 2

$
0
0

This post is a continuation of the previous post on Nuvoton N76E003 microcontroller here.

SDK

Overview of N76E003 Timers

Since N76E003 is based on 8051 architecture, many of its hardware features will have similarities with a standard 8051 microcontroller. The timers of N76E003 have such similarities but keep in mind that similarities don’t mean same. Strictly speaking in N76E003, there six timers and these are:

  •  Timer 0 and Timer 1
    These are both 16-bit general purpose timers with four modes of operation. With these modes we can allow the timers to operate as general purpose 8-bit auto-reloading timers, 13-bit and 16-bit timers. Most commonly the 16-bit timer mode (Mode 1) is used as it is the simplest mode available. Additionally, we can take outputs of these timers. Unlike typical 8051s, we can use chose from Fsys, fixed and prescaled Fsys (Fsys / 12) or external input as timer clock sources.

 

  • Timer 2
    Timer 2 is different from Timers 0 and 1 in many aspects. Mainly it is used for waveform captures. It can also be used in compare mode to generate timed events or compare-match-based PWM. It has auto-reloading feature unlike Timers 0 and 1.

 

  • Timer 3
    Timer 3 is an auto-reloading 16-bit timer with no output and Fsys as clock source. Like Timer 1, Timer 3 can be used for serial communication hardware (UART).

 

  • Self-Wake Up Timer (WKT)
    The self-wake-up timer or simply wake-up timer is a special event timer that is not available in standard 8051s. The purpose of this timer is to wake-up or restore the normal working of a N76E003 chip from low power modes after a certain period of time. It can also be used to trigger timed events or trigger other hardware.

 

  • Watchdog Timer
    The watchdog timer of N76E003 can be used either as a 6-bit general purpose timer or as a standard watchdog timer. Most commonly it is not used as a general-purpose timer because that is not what it is meant to be.  In watchdog mode, it helps in recovering a stuck N76E003 micro by resetting it.

 

While using BSPs be careful about timer-based delay routines and UART routines. These built-in routines reset and reconfigure timers according to their purposes. Make sure that there is no conflicting issue when using them. To avoid such conflict, either use software delay routines or one dedicate timer for a particular job. For instance, use Timer 1 for delays and Timer 3 for UART. You have to know what you are doing and with which hardware.

Timer 0 – Time base Generation

Time-base generation is one of the most basic function of a timer. By using time-bases, we can avoid software delays and unwanted loops when we need to wait or check something after a fixed amount of time. BSP-based delay functions are based on this principle.

Here in this section, we will see how to create a time-base to toggle P15 LED.

TIMER Mode 1

Code

#include "N76E003.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "Delay.h"

#define HIRC    0
#define LIRC    1
#define ECLK    2

void setup(void);
void set_clock_source(unsigned char clock_source);
void disable_clock_source(unsigned char clock_source);
void set_clock_division_factor(unsigned char value);
void set_Timer_0(unsigned int value);
unsigned int get_Timer_0(void);

void main(void)
{
  setup();

  while(1)
  {   
    if(get_Timer_0() < 32767)
    {
      P15 = 1;
    }
    else
    {
      P15 = 0;
    }
  };
}

void setup(void)
  disable_clock_source(ECLK);   
  set_clock_source(HIRC);       
  set_clock_division_factor(80);

  P15_PushPull_Mode;            

  set_T0M;                      
  TIMER0_MODE1_ENABLE;          
  set_Timer_0(0);               
  set_TR0;                       
}

void set_clock_source(unsigned char clock_source)
{
switch(clock_source)
{
    case LIRC:
    {
      set_OSC1;                     
      clr_OSC0; 

      break;
    }

    case ECLK:
    {
      set_EXTEN1;
      set_EXTEN0;

      while((CKSWT & SET_BIT3) == 0); 

      clr_OSC1;                     
      set_OSC0;

      break;
    }

    default:
    {
      set_HIRCEN;         

      while((CKSWT & SET_BIT5) == 0);   

      clr_OSC1;                       
      clr_OSC0;

      break;
    }
  }

  while((CKEN & SET_BIT0) == 1);  
}

void disable_clock_source(unsigned char clock_source)
{
  switch(clock_source)
  {
    case HIRC:
    {
      clr_HIRCEN;
      break;
    }

    default:
    {
      clr_EXTEN1;
      clr_EXTEN0;
      break;
    }
  }
}

void set_clock_division_factor(unsigned char value)
{
  CKDIV = value;
}

void set_Timer_0(unsigned int value)
{
  TH0 = ((value && 0xFF00) >> 8);
  TL0 = (value & 0x00FF);
}

unsigned int get_Timer_0(void)
{
  unsigned int value = 0x0000;

  value = TH0;
  value <<= 8;
  value |= TL0;

  return value;
}

 

Schematic

Timer0_Schematic

Explanation

For demoing time-base generation, we don’t need any additional hardware since we will just be blinking P15 LED without software-based delays.

Before I start explaining the code example, I would like to discuss how Timer 0 and 1 works in Mode 1. In Mode 1, these timers behave like up-counting 16-bit timers. This means they can count (contents of TL and TH registers) from 0 to 65535 with every pulse/clock tick. There is no down-counting feature in these timers.

The source of these pulse/clock ticks can be from system clock, prescaled system clock or from external inputs. When external inputs are used, these timers can be used like counters. Unlike conventional 8051, there are options to take output from timers. The outputs change state with overflows/rollovers, i.e. when the timer counts resets from 65535 to 0.

In the setup function, we do a couple of things. First, we setup the system clock source to 100kHz with HIRC as clock source. P15 is set as an output. Timer 0 is used and so there a 0 in timer setups. Timer 0 is clocked with system clock, i.e. 100kHz and so one tick is:

Timer Tick Formula

In Mode 1, the timer will reset/overflow after:

Time Formula

provided that the initial count of the timer was set to 0. This is what we are doing in the set up. After setting the timer’s count and the mode of operation, the timer is started.

disable_clock_source(ECLK);   
set_clock_source(HIRC);       
set_clock_division_factor(80);

P15_PushPull_Mode;            

set_T0M;                      
TIMER0_MODE1_ENABLE;          
set_Timer_0(0);                
set_TR0;

Functions set_Timer_0 and get_Timer_0 writes Timer 0’s count and reads Timer 0’s count respectively.

void set_Timer_0(unsigned int value);
unsigned int get_Timer_0(void);

It is needed to toggle the state of the P15 LED and yeah, that is done at every half count of the timer, i.e. at every 327ms. In the main, the timer’s count is checked using get_Timer_0 function. When the timer’s count is less than 32767 counts, P15 LED is held high. P15 LED is set low when the timer’s count is greater than this 32767. In this way the toggling effect is achieved without software delays. However, the timer’s count is frequently polled.

if(get_Timer_0() < 32767)
{
   P15 = 1;
}
else
{
   P15 = 0;
}

 

Demo

Time base logic analyzer

Timer 0 (1) Timer 0 (2)

Timer 1 – Stopwatch

We have already seen the operation of general-purpose timers in Mode 1. In this segment, we will how to use these timers in Mode 0. Everything is same between Mode 0 and 1. However, the timer counts or resolutions vary in these modes. Mode 0 makes general purpose timers behave as 13-bit timer/counters.

TIMER Mode 0

Check the block diagram shown above and compare with the one previously shown. Note the greyed-out part in this diagram. The upper bits of TL registers are not used in this mode. Between Mode 0 and 1, personally Mode 1 is easier than Mode 0.

Here Timer 1 in Mode 0 is used to create a digital stopwatch.

Code

#include "N76E003_iar.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "Delay.h"
#include "LCD_3_Wire.h"

#define HIRC    0
#define LIRC    1
#define ECLK    2

unsigned char toggle = 0;
unsigned int ms = 0;

unsigned char sec = 0;
unsigned char min = 0;
unsigned char hrs = 0;

void setup(void);
void set_clock_source(unsigned char clock_source);
void disable_clock_source(unsigned char clock_source);
void set_clock_division_factor(unsigned char value);
void set_Timer_1_for_Mode_0(unsigned int value);
unsigned int get_Timer_1_for_Mode_0(void);
void print_C(unsigned char x_pos, unsigned char y_pos, unsigned char value);
void print_I(unsigned char x_pos, unsigned char y_pos, unsigned int value);

#pragma vector = 0x1B
__interrupt void Timer1_ISR (void)    
{
    set_Timer_1_for_Mode_0(0x1D64);

    ms++;

    if(ms == 499)
    {
      toggle = ~toggle;
    }

    if(ms > 999)
    {
      ms = 0;
      sec++;

      if(sec > 59)
      {
        sec = 0;
        min++;

        if(min > 59)
        {
          min = 0;
          hrs++;

          if(hrs > 23)
          {
            hrs = 0;
          }
        }
      }
    }
}

void main(void)
{
  static char txt[] = {"Nu Stopwatch"};

  setup();

  LCD_goto(2, 0);
  LCD_putstr(txt);

  while(1)
  {   
    if(P05 == 1)
    {
      set_ET1;                       
      set_EA;               
      set_TR1;                    
      set_Timer_1_for_Mode_0(0x1D64);
    }

    if(P06 == 1)
    {
      clr_ET1;                  
      clr_EA;                    
      clr_TR1;                     
      toggle = 0;
    }

    if((P05 == 1) && (P06 == 1))
    {
      clr_ET1;                     
      clr_EA;                         
      clr_TR1;                      

      ms = 0;
      sec = 0;
      min = 0;
      hrs = 0;

      toggle = 0; 
      set_Timer_1_for_Mode_0(0x1D64);
    }


    print_C(2, 1, hrs);
    print_C(5, 1, min);
    print_C(8, 1, sec);
    print_I(11, 2, ms);

    if(!toggle)
    {
       LCD_goto(4, 1);
       LCD_putchar(':');
       LCD_goto(7, 1);
       LCD_putchar(':');
       LCD_goto(10, 1);
       LCD_putchar(':');
    }
    else
    {
       LCD_goto(4, 1);
       LCD_putchar(' ');
       LCD_goto(7, 1);
       LCD_putchar(' ');
       LCD_goto(10, 1);
       LCD_putchar(' ');
    }
  };
}

void setup(void)
  disable_clock_source(ECLK);
  set_clock_source(HIRC);   
  set_clock_division_factor(1);

  P05_Input_Mode;
  P06_Input_Mode;

  clr_T1M;                 
  TIMER1_MODE0_ENABLE;        
  set_Timer_1_for_Mode_0(0x1D64);

  LCD_init();
  LCD_clear_home();
}

void set_clock_source(unsigned char clock_source)
{
switch(clock_source)
{
    case LIRC:
    {
      set_OSC1;                     
      clr_OSC0; 

      break;
    }

    case ECLK:
    {
      set_EXTEN1;
      set_EXTEN0;

      while((CKSWT & SET_BIT3) == 0); 

      clr_OSC1;                     
      set_OSC0;

      break;
    }

    default:
    {
      set_HIRCEN;         

      while((CKSWT & SET_BIT5) == 0);   

      clr_OSC1;                       
      clr_OSC0;

      break;
    }
  }

  while((CKEN & SET_BIT0) == 1);  
}

void disable_clock_source(unsigned char clock_source)
{
  switch(clock_source)
  {
    case HIRC:
    {
      clr_HIRCEN;
      break;
    }

    default:
    {
      clr_EXTEN1;
      clr_EXTEN0;
      break;
    }
  }
}

void set_clock_division_factor(unsigned char value)
{
  CKDIV = value;
}

void set_Timer_1_for_Mode_0(unsigned int value)
{
  TL1 = (value & 0x1F);
  TH1 = ((value & 0xFFE0) >> 5);
}

unsigned int get_Timer_1_for_Mode_0(void)
{
  unsigned char hb = 0x00;
  unsigned char lb = 0x00;
  unsigned int value = 0x0000;


  value = TH1;
  value <<= 8;
  value |= TL1;

  lb = (value & 0x001F);
  hb = ((value & 0xFFE0) >> 5);

  value = hb;
  value <<= 8;
  value |= lb; 

  return value;
}

void print_C(unsigned char x_pos, unsigned char y_pos, unsigned char value)
{
  LCD_goto(x_pos, y_pos);
  LCD_putchar((value / 10) + 0x30);
  LCD_goto((x_pos + 1), y_pos);
  LCD_putchar((value % 10) + 0x30);
}

void print_I(unsigned char x_pos, unsigned char y_pos, unsigned int value)
{
  LCD_goto(x_pos, y_pos);
  LCD_putchar((value / 100) + 0x30);
  LCD_goto((x_pos + 1), y_pos);
  LCD_putchar(((value % 100) / 10) + 0x30);
  LCD_goto((x_pos + 2), y_pos);
  LCD_putchar((value % 10) + 0x30);
}

 

Schematic

Timer 1 Stopwatch Schematic

Explanation

Firstly, let us inspect the setup code:

disable_clock_source(ECLK);
set_clock_source(HIRC);   
set_clock_division_factor(1);

 

P05_Input_Mode;
P06_Input_Mode;

 

clr_T1M;                 
TIMER1_MODE0_ENABLE;        
set_Timer_1_for_Mode_0(0x1D64);

The system clock is set to 8MHz using HIRC. As per schematic and code, two buttons labelled Start and Stop are connected with P05 and P06 respectively. Timer 1 is set up in Mode 1 with prescaled system clock source (Fsys / 12). Thus, it has an input clock frequency of 666.67kHz. Thus, one timer tick is 1.5μs. To get the timer overflow and reset every 1 millisecond we will need:

tim1 formula 1

Since the timer is an up-counting timer and has a resolution of 13-bits, it will overflow after 8191 counts. Thus, to get 667 counts, we need to set is at:

tim1 formula 2

The timer is, therefore, set at this count value.

Now how do we set the timer in Mode 1?

Check the register diagram below. The grey part is what is ignored. Remember that TL and TH are 16-bits as a whole but out of that we will just be using 13-bits.

Timer Mode 0 Count

In our case, the count 0x1D64 is segmented as the following in binary:

tim1 formula 3

This number is masked as follows:

mode 1 explain

All of these is done by the following function:

void set_Timer_1_for_Mode_0(unsigned int value)
{
  TL1 = (value & 0x1F);
  TH1 = ((value & 0xFFE0) >> 5);
}

Reading the time is doing just the opposite of writing and this is accomplished by the following function:

unsigned int get_Timer_1_for_Mode_0(void)
{
  unsigned char hb = 0x00;
  unsigned char lb = 0x00;
  unsigned int value = 0x0000;

 


  value = TH1;
  value <<= 8;
  value |= TL1;

 

  lb = (value & 0x001F);
  hb = ((value & 0xFFE0) >> 5);

 

  value = hb;
  value <<= 8;
  value |= lb; 

 

  return value;
}

Unlike the previous example, here interrupt is used to keep time. The timer run bit, global interrupt and Timer 1 interrupt are all enabled when the start button is pressed and are all disabled when the stop button is pressed or the stopwatch is reset by pressing both buttons.

if(P05 == 1)
{
   set_ET1;                      
   set_EA;               
   set_TR1;                     
   set_Timer_1_for_Mode_0(0x1D64);
}

 

if(P06 == 1)
{
   clr_ET1;                  
   clr_EA;                    
   clr_TR1;                     
   toggle = 0;
}

 

if((P05 == 1) && (P06 == 1))
{
   clr_ET1;                      
   clr_EA;                         
   clr_TR1;                      
   ms = 0;
   sec = 0;
   min = 0;
   hrs = 0;
   toggle = 0; 
   set_Timer_1_for_Mode_0(0x1D64);
}

Inside the interrupt subroutine, the time keeping is done:

#pragma vector = 0x1B
__interrupt void Timer1_ISR (void)    
{
    set_Timer_1_for_Mode_0(0x1D64);

    ms++;

    if(ms == 499)
    {
      toggle = ~toggle;
    }

    if(ms > 999)
    {
      ms = 0;
      sec++;

      if(sec > 59)
      {
        sec = 0;
        min++;

        if(min > 59)
        {
          min = 0;
          hrs++;
          if(hrs > 23)
          {
            hrs = 0;
          }
        }
      }
    }

At every timer overflow interrupt the timer’s counter is reset to 0x1D64 to make sure that there are 667 counts before the overflow.

The time data is displayed on an LCD screen.

Demo

IMG_0769

Timer 2 Input Capture – Frequency Counter

Timer 2, as I stated before, is different in many areas from the other timers of N76E003. In my opinion, it is best suited for measuring pulse widths, their periods and thereby frequencies. In other words, it is the best wave capturing tool that we have in N76E003s.

Timer 2 Block

Unlike conventional 8051s, there varieties of option to capture wave or generate compare-match events.

In this segment, we will see how to make a frequency counter with Timer 2’s capture facility. We will also see how to generate Timer 1’s output.

Code

#include "N76E003_iar.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "Delay.h"
#include "LCD_3_Wire.h"

#define HIRC    0
#define LIRC    1
#define ECLK    2

#define timer_clock_speed  8000000.0

unsigned long overflow = 0;
unsigned long pulse_time = 0;
unsigned long start_time = 0;
unsigned long end_time = 0;

void setup(void);
void set_clock_source(unsigned char clock_source);
void disable_clock_source(unsigned char clock_source);
void set_clock_division_factor(unsigned char value);
void setup_GPIOs(void);
void setup_Timer_1(void);
void setup_Timer_2(void);
void setup_capture(void);
void set_Timer_1(unsigned int value);
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned long value);

#pragma vector = 0x1B
__interrupt void Timer_1_ISR(void)    
  set_Timer_1(0);
}

#pragma vector = 0x2B
__interrupt void Timer_2_ISR(void)    
  clr_TF2;
  overflow++;
}

#pragma vector = 0x63
__interrupt void Input_Capture_ISR(void)    
{
  if((CAPCON0 & SET_BIT0) != 0)
  {
    clr_CAPF0;   
    end_time = C0H;
    end_time <<= 8;
    end_time |= C0L;
    pulse_time = ((overflow << 16) - start_time + end_time);
    start_time = end_time;
    overflow = 0;
  }
}

void main(void)
{
  register float f = 0.0;

  setup();

  LCD_init();
  LCD_clear_home();
  LCD_goto(1, 0);
  LCD_putstr("Nu Freq. Meter");
  LCD_goto(0, 1);
  LCD_putstr("Freq./Hz:");

  while(1)
  {
    f = (timer_clock_speed / ((float)pulse_time));
    lcd_print(11, 1, ((unsigned long)f));
    Timer0_Delay1ms(100);
  };
}

void setup(void)
{
  disable_clock_source(ECLK);   
  set_clock_source(HIRC);       
  set_clock_division_factor(1); 
  setup_GPIOs();
  setup_capture();
  setup_Timer_1();
  setup_Timer_2();
  set_EA;                       
}

void set_clock_source(unsigned char clock_source)
{
  switch(clock_source)
  {
    case LIRC:
    {
      set_OSC1;                     
      clr_OSC0; 

      break;
    }

    case ECLK:
    {
      set_EXTEN1;
      set_EXTEN0;

      while((CKSWT & SET_BIT3) == 0); 

      clr_OSC1;                     
      set_OSC0;

      break;
    }

    default:
    {
      set_HIRCEN;         

      while((CKSWT & SET_BIT5) == 0);   

      clr_OSC1;                       
      clr_OSC0;

      break;
    }
  }

  while((CKEN & SET_BIT0) == 1);  
}

void disable_clock_source(unsigned char clock_source)
{
  switch(clock_source)
  {
    case HIRC:
    {
       clr_HIRCEN;
       break;
    }

    default:
    {
       clr_EXTEN1;
       clr_EXTEN0;
        break;
    }
  }
}

void set_clock_division_factor(unsigned char value)
{
  CKDIV = value;
}

void setup_GPIOs(void)
{
  P00_PushPull_Mode;
  P12_Input_Mode;
}

void setup_Timer_1(void)
{
  set_T1M;                      
  TIMER1_MODE1_ENABLE;          
  set_Timer_1(0);              
  P2S |= SET_BIT3;              
  set_TR1;                      
  set_ET1;                     
}

void setup_Timer_2(void)
{
  T2CON &= ~SET_BIT0;           
  T2MOD = 0x00;                 
  set_TR2;                      
  set_ET2;                      
}

void setup_capture(void)
{
  IC0_P12_CAP0_FallingEdge_Capture;
  set_ECAP;                     
}


void set_Timer_1(unsigned int value)
{
  TH1 = ((value && 0xFF00) >> 8);
  TL1 = (value & 0x00FF);
}

void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned long value)
  LCD_goto(x_pos, y_pos);
  LCD_putchar((value / 10000) + 0x30);
  LCD_goto((x_pos + 1), y_pos);
  LCD_putchar(((value % 10000) / 1000) + 0x30); 
  LCD_goto((x_pos + 2), y_pos);
  LCD_putchar(((value % 1000) / 100) + 0x30);
  LCD_goto((x_pos + 3), y_pos);
  LCD_putchar(((value % 100) / 10) + 0x30);
  LCD_goto((x_pos + 4), y_pos);
  LCD_putchar((value % 10) + 0x30);
}

 

Schematic

Timer 2 Capture Schematic

Explanation

Before explaining the capture part, I would like to put emphasis on how to generate Timer 1’s output. The setup for timer is nothing different from other timer settings in Mode 1 except for the part shown below:

P2S |= SET_BIT3;

This code sets the timer output. Note that in this example, the system clock speed and hence the clock speed of all hardware sub-systems is set to 8MHz with HIRC and clock divider. Thus, Timer 1’s output will be high for:

tim 2 formula 1

and low for the same amount of time. So, the total time period of Timer 1’s output is roughly 16ms. Therefore, the frequency of this output is about 60Hz.

P00 is the Timer 1’s output pin and P12 is the capture pin of Timer 2. When P00 and P12 are shorted together, the frequency counter made here with Timer 2 and its capture unit will read 60Hz.

So how it is done by Timer 2 and the capture unit?

Here in this example, we don’t need the compare-match feature for the measurement of waveform timings and so all compare match features are disabled. We need to reload the timer when it overflows. Since the timer’s counter registers are not manipulated, they are set to zeros by default and so the reload count is zero. These are reflected by the first two lines of the timer’s setup:

void setup_Timer_2(void)
{
  T2CON &= ~SET_BIT0;           
  T2MOD = 0x00;                 
  set_TR2;                      
  set_ET2;                      
}

Next, Timer 2 is started with its interrupt enabled.

Finally, the capture pin and its channel to be used are enabled using BSP-based definition. Capture interrupt is also enabled.

void setup_capture(void)
{
  IC0_P12_CAP0_FallingEdge_Capture;
  set_ECAP;                     
}

Note that this is the confusing part of the code. There are three capture channels (CAP0, CAP1 and CAP2). These channels share 9 input capture GPIO pins. The pins are cleverly multiplexed. Detection edge selections can be also done.  The BSP definition-names state these parameters. For example, the definition used here means:

 “input capture pin 0 connected to P12 GPIO pin is connected to capture channel 0 to detect falling edge transitions”.

Once the hardware setup is complete, the game now resides in the capture interrupt part:

#pragma vector = 0x63
__interrupt void Input_Capture_ISR(void)    
{
  if((CAPCON0 & SET_BIT0) != 0)
  {
    clr_CAPF0;   
    end_time = C0H;
    end_time <<= 8;
    end_time |= C0L;
    pulse_time = ((overflow << 16) - start_time + end_time);
    start_time = end_time;
    overflow = 0;
  }
}

Since there is one interrupt vector for all three capture channels, we have check first which capture channel caused the interrupt and clear that particular interrupt flag soon. Capture interrupt, in this case, occurs when a falling edge is detected by CAP0 channel. When such an interrupt occurs, the time of its happening, i.e. the counter value of Timer 2 at that instance is stored. This marks the first falling edge event. To measure period or frequency of a waveform, another such falling edge is needed. Therefore, when another falling edge is detected by the capture hardware, the process repeats. This results in the time capture of two events.

We know the frequency of the timer’s clock and so we can find the value of its each tick. We have two captures in form of two Timer 2 counts. We can find the difference between them and thereby compute the time period (pulse_time).

Since the time period of the captured waveform is calculated, its frequency can be deduced. This is done in the main loop.

f = (timer_clock_speed / ((float)pulse_time));

 

Demo

Timer 2 F-Meter

Timer 2 Pulse Width Capture – Interfacing HC-SR04 SONAR

In the last segment, it was demonstrated how to capture a waveform and compute its frequency using Timer 2’s capture hardware. This segment is basically an extension of the last segment. Here, it will be shown how we can use two capture channels connected to the same capture pin to find out the pulse width of a pulse. For demoing this idea, a HC-SR04 ultrasonic SONAR sensor is used. The SONAR sensor gives a pulse output that varies in width according to the distance between the sensor and a target.

HC-SR04

Code

#include "N76E003.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "Delay.h"
#include "LCD_3_Wire.h"

#define HIRC    0
#define LIRC    1
#define ECLK    2

unsigned int pulse_width = 0;

void setup(void);
void set_clock_source(unsigned char clock_source);
void disable_clock_source(unsigned char clock_source);
void set_system_clock_frequency(unsigned long F_osc, unsigned long F_sys);
void setup_GPIOs(void);
void setup_Timer_2(void);
void setup_capture(void);
void set_Timer_2(unsigned int value);
void set_Timer_2_reload_compare(unsigned int value);
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value);

void Input_Capture_ISR(void)    
interrupt 12
{
  if(CAPCON0 & 0x01)
  {
    clr_CAPF0;   
  }

  if(CAPCON0 & 0x02)
  {
    clr_CAPF1;                   

    pulse_width = C1H;
    pulse_width <<= 8;
    pulse_width |= C1L;
  }
}

void main(void)
{
  unsigned int range = 0;

  LCD_init();
  LCD_clear_home();
  LCD_goto(0, 0);
  LCD_putstr("Pamge/cm:");
  LCD_goto(0, 1);
  LCD_putstr("Pulse/us:");

  setup();

  while(1)
  {
    set_P11;
    Timer3_Delay10us(1);
    clr_P11;

    range = ((unsigned int)(((float)pulse_width) / 58.0));

    lcd_print(11, 0, range);
    lcd_print(11, 1, pulse_width);
    Timer0_Delay1ms(900);
  };
}

void setup(void)
{
  disable_clock_source(ECLK);   
  set_clock_source(HIRC);      
  set_system_clock_frequency(16, 16);
  setup_GPIOs();
  setup_capture();
  setup_Timer_2();
  set_EA;                       
}

void set_clock_source(unsigned char clock_source)
{
  switch(clock_source)
  {
    case LIRC:
    {
      set_OSC1;                     
      clr_OSC0; 

      break;
    }

    case ECLK:
    {
      set_EXTEN1;
      set_EXTEN0;

      while((CKSWT & SET_BIT3) == 0); 

      clr_OSC1;                     
      set_OSC0;

      break;
    }

    default:
    {
      set_HIRCEN;         

      while((CKSWT & SET_BIT5) == 0);   

      clr_OSC1;                       
      clr_OSC0;

      break;
    }
  }

  while((CKEN & SET_BIT0) == 1);  
}

void disable_clock_source(unsigned char clock_source)
{
  switch(clock_source)
  {
    case HIRC:
    {
       clr_HIRCEN;
       break;
    }

    default:
    {
       clr_EXTEN1;
       clr_EXTEN0;
       break;
    }
  }
}

void set_system_clock_frequency(unsigned long F_osc, unsigned long F_sys)
{
  F_osc = (F_osc / (0x02 * F_sys));

  if((F_osc >= 0x00) && (F_osc <= 0xFF))
  {
    CKDIV = ((unsigned char)F_osc);
  }
}

void setup_GPIOs(void)
{
  P11_PushPull_Mode;
  P12_Input_Mode;
}

void setup_Timer_2(void)
{
  set_Timer_2_reload_compare(0);
  set_Timer_2(0); 
  set_LDEN;
  T2MOD |= 0x01; 
  T2MOD |= 0x20;
  set_TR2;                 
}

void setup_capture(void)
{
  CAPCON0 = 0x30;
  CAPCON1 = 0x01;
  CAPCON2 = 0x30;
  CAPCON3 = 0x00;
  CAPCON4 = 0x00;
  set_ECAP;            
}

void set_Timer_2(unsigned int value)
{
  TL2 = (value & 0x00FF);
  TH2 = ((value & 0xFF00) >> 0x08);
}

void set_Timer_2_reload_compare(unsigned int value)
{
  RCMP2L = (value & 0x00FF);
  RCMP2H = ((value & 0xFF00) >> 0x08);
}

void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value)
  LCD_goto(x_pos, y_pos);
  LCD_putchar((value / 10000) + 0x30);
  LCD_goto((x_pos + 1), y_pos);
  LCD_putchar(((value % 10000) / 1000) + 0x30); 
  LCD_goto((x_pos + 2), y_pos);
  LCD_putchar(((value % 1000) / 100) + 0x30);
  LCD_goto((x_pos + 3), y_pos);
  LCD_putchar(((value % 100) / 10) + 0x30);
  LCD_goto((x_pos + 4), y_pos);
  LCD_putchar((value % 10) + 0x30);
}

 

Schematic

Timer 2 Pulse Width Capture Schematic

Explanation

HC-SR04 SONAR sensor has two pins apart from power supply pins. These pins are labelled “Echo” and “Trigger”. When the trigger pin of a HC-SR04 is set high for about 10μs, it acknowledges this short duration pulse in the trigger pin as a command from its host micro to measure and return distance data.

HC-SR04 Data

P11 is setup as the trigger pin and P12 is set up as the echo pin. The concepts of input capture are same as in the last capture example but there are a few minor changes. Firstly, the system clock speed is set to full 16MHz using HIRC. Secondly, the setup of Timer 2 is slightly changed. Auto-reloading is enabled this time. Timer 2’s clock is prescaled to 1MHz. This is done so in order to make sure that the capture has a resolution of 1μs and the timer doesn’t overflow while taking measurements. The maximum possible width of a pulse from HC-SR04 is 38ms when no obstacle is detected by it but with this timer setup we can measure pulse widths up to 65ms. Timer 2 is also set up as to reset its count when a CAP0 event occurs.

void setup_Timer_2(void)
{
  set_Timer_2_reload_compare(0);
  set_Timer_2(0); 
  set_LDEN;
  T2MOD |= 0x01; 
  T2MOD |= 0x20;
  set_TR2;                 
}

In order to measure pulse widths, we can use one capture channel in both edge capture mode or use two capture channels connected to the same input capture GPIO pin detecting different edges. The latter is used here.

void setup_capture(void)
{
  CAPCON0 = 0x30;
  CAPCON1 = 0x01;
  CAPCON2 = 0x30;
  CAPCON3 = 0x00;
  CAPCON4 = 0x00;
  set_ECAP;            
}

I didn’t use BSP-based definitions for setting up captures in this example because of some limitation. BSP-based definitions reset selections. This is why I configured the input capture control registers manually. Two capture channels having different edge detections are used and both of these channels share P12 or IC0 pin.

Now when HC-SR04 is triggered, it will give out a pulse. We have to measure the width of that pulse. A pulse is defined by a rising edge and a falling edge. CAP0 channel is set to detect the rising edge of the pulse and reset Timer 2 to 0 count. CAP1 channel is used to detect the falling edge of the same pulse and read Timer 2’s count. This count represents pulse width in microseconds.

void Input_Capture_ISR(void)    
interrupt 12
{
  if(CAPCON0 & 0x01)
  {
    clr_CAPF0;   
  }

  if(CAPCON0 & 0x02)
  {
    clr_CAPF1;                   

    pulse_width = C1H;
    pulse_width <<= 8;
    pulse_width |= C1L;
  }
}

In the main loop, distance is calculated using this measured pulse width time and the formula given in HC-SR04’s datasheet.

range = ((unsigned int)(((float)pulse_width) / 58.0));  

The distance found between an object and HC-SR04 is displayed on a text LCD.

Demo

TIM2 HC-SR04

Timer 3 – Driving 7 Segments, LED and Scanning Keypad

We all know that N76E003 is a cool chip but it lacks GPIOs unlike other chips of similar capabilities. Thus, we must be miser while using GPIOs. When it is needed to interface several inputs and outputs with a microcontroller using fewer pins, we often take the assistance of logic ICs like shift registers, counters, multiplexers, etc. We have already used this technique while making the 3-wire LCD interface. An LCD has its own controller(s) to take care of projecting data on the screen once data has been feed to it. However, that’s not the case with seven segment displays and keypads when used without any specialized driver IC like TM1640/MAX7219. It is, then, a job for a host micro to do the scanning and data manipulation periodically with affecting other tasks. This can be achieved easily with a timer.

7 segment keypad module

In this segment, we will see how the aforementioned is done with Timer 3. For the demo, I used a salvaged temperature controller’s I/O unit. The I/O unit consists of two 4-digit seven segment displays, four LEDs and four push buttons. It is made with a 74HC164 Serial-In-Parallel-Out (SPIO) shift register and a 74HC145 BCD-to-Decimal decoder. In order to use it in real-time, its displays, LEDs and buttons should be scanned and updated at a fast rate without hindering any part of an application running in main loop.

Code

#include "N76E003_iar.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "Delay.h"

#define HIRC                        0
#define LIRC                        1
#define ECLK                        2

#define GATE_HIGH                   set_P15
#define GATE_LOW                    clr_P15

#define CLK_HIGH                    set_P16
#define CLK_LOW                     clr_P16

#define A_HIGH                      set_P00
#define A_LOW                       clr_P00

#define B_HIGH                      set_P01
#define B_LOW                       clr_P01

#define C_HIGH                      set_P02
#define C_LOW                       clr_P02

#define D_HIGH                      set_P03
#define D_LOW                       clr_P03

#define SW                          P17

#define top_seg                     4
#define bot_seg                     0

#define HIGH                        1
#define LOW                         0

const unsigned char num[0x0A] = {0xED, 0x21, 0x8F, 0xAB, 0x63, 0xEA, 0xEE, 0xA1, 0xEF, 0xEB};
unsigned char data_values[0x09] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

unsigned char SW_in = 0;
unsigned char n = 0;

void setup(void);
void setup_GPIOs(void);
void setup_Timer_3(void);
void set_Timer_3(unsigned int value);
unsigned int get_Timer_3(void);
void write_74HC164(register unsigned char value);
void write_74HC145(register unsigned char channel);
void show_LEDs(unsigned char LED1_state, unsigned char LED2_state, unsigned char LED3_state, unsigned char LED4_state);
void show_numbers(signed int value, unsigned char pos);

#pragma vector = 0x83
__interrupt void Timer3_ISR(void)
{
  write_74HC164(data_values[n]);
  write_74HC145(n);

  n++;
  if(n > 9)
  {
    n = 0;
  }

  clr_TF3;
}

void main (void)
{

  unsigned int i = 0;
  unsigned int j = 9999;

  setup();

  while(1)
  {
    switch(SW_in)
    {
     case 1:
     {
         show_LEDs(1, 0, 0, 0);
         break;
     }
     case 2:
     {
         show_LEDs(0, 1, 0, 0);
         break;
     }
     case 3:
     {
         show_LEDs(0, 0, 1, 0);
         break;
     }
     case 4:
     {
         show_LEDs(0, 0, 0, 1);
         break;
     }
    }

    SW_in = 0x00;

    i++;
    j--;

    if(i > 9999)
    {
     i = 0;
     j = 9999;
    }

    show_numbers(i, bot_seg);
    show_numbers(j, top_seg);

    Timer1_Delay10ms(40);
    show_LEDs(0, 0, 0, 0);
  };
}

void setup(void)
{
  setup_GPIOs();
  setup_Timer_3();
}

void setup_GPIOs(void)
{
  P00_PushPull_Mode;
  P01_PushPull_Mode;
  P02_PushPull_Mode;
  P03_PushPull_Mode;
  P15_PushPull_Mode;
  P16_PushPull_Mode;
  P17_Input_Mode;
}

void setup_Timer_3(void)
{
  set_Timer_3(0xF9C0);
  set_ET3;                                 
  set_EA;                                   
  set_TR3;   
}

void set_Timer_3(unsigned int value)
{
  RL3 = (value & 0x00FF); 
  RH3 = ((value && 0xFF00) >> 8);
}

unsigned int get_Timer_3(void)
{
  unsigned int value = 0x0000;

  value = RH3;
  value <<= 8;
  value |= RL3;

  return value;  
}

void write_74HC164(register unsigned char value)
{
  register unsigned char s = 0x08;

  while(s > 0)
  {
    if((value & 0x80) != 0x00)
    {
        GATE_HIGH;
    }
    else
    {
        GATE_LOW;
    }

    CLK_HIGH;
    CLK_LOW;

    value <<= 1;
    s--;
  };
}

void write_74HC145(register unsigned char channel)
{
  P0 = 0x00;   

  switch(channel)
  {
    case 0:
    {
      asm("nop");

      if(SW == LOW)
      {
          SW_in = 1;
      }
      break;
  }

    case 1:
    {
      P0 = 0x01;
      break;
    }

    case 2:
    {
      P0 = 0x02;
      break;
    }

    case 3:
    {
      P0 = 0x03;
      break;
    }

    case 4:
    {
      P0 = 0x04;
      break;
    }

    case 5:
    {
      P0 = 0x05;
      break;
    }

    case 6:
    {
      P0 = 0x06;
      break;
    }

    case 7:
    {
      P0 = 0x07;
      asm("nop");

      if(SW == LOW)
      {
          SW_in = 2;
      }
      break;
    }

    case 8:
    {
      P0 = 0x08;
      asm("nop");

      if(SW == LOW)
      {
          SW_in = 3;
      }
      break;
    }

    case 9:
    {
      P0 = 0x09;
      asm("nop");

      if(SW == LOW)
      {
          SW_in = 4;
      }
      break;
    }
  }
}

void show_LEDs(unsigned char LED1_state, unsigned char LED2_state, unsigned char LED3_state, unsigned char LED4_state)
{
  switch(LED1_state)
  {
    case HIGH:
    {
       data_values[8] |= 0x80;
       break;
    }
    case LOW:
    {
       data_values[8] &= 0x7F;
       break;
    }
  }

  switch(LED2_state)
  {
    case HIGH:
    {
       data_values[8] |= 0x40;
       break;
    }
    case LOW:
    {
       data_values[8] &= 0xBF;
       break;
    }
  }

  switch(LED3_state)
  {
    case HIGH:
    {
       data_values[8] |= 0x08;
       break;
    }
    case LOW:
    {
       data_values[8] &= 0xF7;
       break;
    }
  }

  switch(LED4_state)
  {
    case HIGH:
    {
       data_values[8] |= 0x02;
       break;
    }
    case LOW:
    {
       data_values[8] &= 0xFD;
       break;
    }
  }
}

void show_numbers(signed int value, unsigned char pos)
{
  register unsigned char ch = 0x00;

  if((value >= 0) && (value <= 9))
  {
    ch = (value % 10);
    data_values[(0 + pos)] = num[ch];
    data_values[(1 + pos)] = 0x00;
    data_values[(2 + pos)] = 0x00;
    data_values[(3 + pos)] = 0x00;
  }
  else if((value > 9) && (value <= 99))
  {
    ch = (value % 10);
    data_values[(0 + pos)] = num[ch];
    ch = ((value / 10) % 10);
    data_values[(1 + pos)] = num[ch];
    data_values[(2 + pos)] = 0x00;
    data_values[(3 + pos)] = 0x00;
  }
  else if((value > 99) && (value <= 999))
  {
    ch = (value % 10);
    data_values[(0 + pos)] = num[ch];
    ch = ((value / 10) % 10);
    data_values[(1 + pos)] = num[ch];
    ch = ((value / 100) % 10);
    data_values[(2 + pos)] = num[ch];
    data_values[(3 + pos)] = 0x00;
  }
  else if((value > 999) && (value <= 9999))
  {
    ch = (value % 10);
    data_values[(0 + pos)] = num[ch];
    ch = ((value / 10) % 10);
    data_values[(1 + pos)] = num[ch];
    ch = ((value / 100) % 10);
    data_values[(2 + pos)] = num[ch];
    ch = (value / 1000);
    data_values[(3 + pos)] = num[ch];
  }
}

 

Schematic

Timer 3_Schematic 7 Segment Module

Explanation

Timer 3’s internal hardware is very simple. It is an up-counting timer and is run by using system clock as clock source. There is a prescalar to reduce the system clock input. The main feature of Timer 3 is it auto-reload feature. This feature basically allows us to forget reloading its 16-bit counter unlike Timer 0 and 1. There no external input or output option for this timer. Owing to all these, it is most suitable for time-base generation and serial communication.

Timer 3 Block

In this demo, Timer 3 is set to interrupt every 100μs.

tim3

The following code sets up Timer 3 as discussed:

void setup_Timer_3(void)
{
  set_Timer_3(0xF9C0);
  set_ET3;                                 
  set_EA;                                   
  set_TR3;   
}

Note that Timer 3’s input clock and system clock frequency is 16MHz since no prescalar is used.

Now what we are doing inside the timer interrupt? According to the schematic and objective of this project, we need to update the seven segment displays and read the 4-bit keypad so fast that it looks as if everything is being done without any delay and in real time.

#pragma vector = 0x83
__interrupt void Timer3_ISR(void)
{
  write_74HC164(data_values[n]);
  write_74HC145(n);

 

  n++;
  if(n > 9)
  {
    n = 0;
  }

 

  clr_TF3;
}

Inside timer ISR, both logic ICs are updated. Firstly, the number to be displayed is sent to the 74HC164 IC and then the seven-segment display to show the number is updated by writing the 74HC145 IC. At every interrupt, one seven segment display is updated. There are 8 such displays and so it takes less than a millisecond to update all these displays. Everything seems to complete in the blink of an eye. During this time the keypad is also scanned in the main. With different keys, different LEDs light up.

Demo

TIM3

Simple PWM – RGB LED Fading

PWM hardware is another basic requirement for any modern-era microcontroller. We can use PWM for a number of applications like motor control, light control, switch-mode power supplies (SMPSs), etc. With PWM we can also simulate digital-to-analogue converter (DAC). Fortunately, N76E003 comes with a separate PWM block that is not a part of any internal timer. It has all the feature that you can imagine. It can be used to generate simple independent 6 single channel PWMs. It can also be used to generate complementary and interdependent PWMs with dead time feature.

PWM Block

Code

#include "N76E003.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "Delay.h"
#include "soft_delay.h"

unsigned int R_value[10] = {20, 150, 250, 360, 440, 560, 680, 820, 900, 1020};
unsigned int G_value[10] = {440, 560, 680, 820, 900, 1020, 20, 150, 250, 360};
unsigned int B_value[10] = {900, 1020, 20, 150, 250, 360, 440, 560, 680, 820};

void set_PWM_period(unsigned int value);
void set_PWM0(unsigned int value);
void set_PWM1(unsigned int value);
void set_PWM2(unsigned int value);
void set_PWM3(unsigned int value);
void set_PWM4(unsigned int value);
void set_PWM5(unsigned int value);

void main(void)
{
    signed int i = 0;
    signed char j = 0;

    P01_PushPull_Mode;
    P10_PushPull_Mode;
    P11_PushPull_Mode;

    PWM1_P11_OUTPUT_ENABLE;
    PWM2_P10_OUTPUT_ENABLE;
    PWM4_P01_OUTPUT_ENABLE;

    PWM_IMDEPENDENT_MODE;
    PWM_EDGE_TYPE;
    set_CLRPWM;
    PWM_CLOCK_FSYS;
    PWM_CLOCK_DIV_64;
    PWM_OUTPUT_ALL_NORMAL;
    set_PWM_period(1023);
    set_PWMRUN;

    while(1)
    {
        for(i = 0; i < 1024; i += 10)
        {
            set_PWM1(i);
            delay_ms(20);
        }
        for(i = 1023; i > 0; i -= 10)
        {
            set_PWM1(i);
            delay_ms(20);
        }             

        for(i = 0; i < 1024; i += 10)
        {
            set_PWM2(i);
            delay_ms(20);
        }
        for(i = 1023; i > 0; i -= 10)
        {
            set_PWM2(i);
            delay_ms(20);
        }

        for(i = 0; i < 1024; i += 10)
        {
            set_PWM4(i);
            delay_ms(20);
        }
        for(i = 1023; i > 0; i -= 10)
        {
            set_PWM4(i);
            delay_ms(20);
        }

        delay_ms(600);

        for(i = 0; i <=9; i++)
        {
            for(j = 0; j <= 9; j++)
            {
                set_PWM4(R_value[j]);
                set_PWM1(G_value[j]);
                set_PWM2(B_value[j]);
                delay_ms(200);
            }
            for(j = 9; j >= 0; j--)
            {
                set_PWM4(R_value[j]);
                set_PWM1(G_value[j]);
                set_PWM2(B_value[j]);
                delay_ms(200);
            }
        }

        delay_ms(600);
    }
}

void set_PWM_period(unsigned int value)
{
  PWMPL = (value & 0x00FF);
  PWMPH = ((value & 0xFF00) >> 8); 
}

void set_PWM0(unsigned int value)
{
  PWM0L = (value & 0x00FF);
  PWM0H = ((value & 0xFF00) >> 8);
  set_LOAD;
}

void set_PWM1(unsigned int value)
{
  PWM1L = (value & 0x00FF);
  PWM1H = ((value & 0xFF00) >> 8);
  set_LOAD;
}

void set_PWM2(unsigned int value)
{
  PWM2L = (value & 0x00FF);
  PWM2H = ((value & 0xFF00) >> 8);
  set_LOAD;
}

void set_PWM3(unsigned int value)
{
  PWM3L = (value & 0x00FF);
  PWM3H = ((value & 0xFF00) >> 8);
  set_LOAD;
}

void set_PWM4(unsigned int value)
{
  set_SFRPAGE;
  PWM4L = (value & 0x00FF);
  PWM4H = ((value & 0xFF00) >> 8);
  clr_SFRPAGE;
  set_LOAD;
}

void set_PWM5(unsigned int value)
{
  set_SFRPAGE;
  PWM5L = (value & 0x00FF);
  PWM5H = ((value & 0xFF00) >> 8);
  clr_SFRPAGE;
  set_LOAD;
}

 

Schematic

Simple PWM_Schematic

Explanation

PWM generated by the N76E003’s PWM hardware is based on compare-match principle and this is evident from its block diagram. There are 6 PWM channels and they can be used independently or as interdependent groups to form complimentary PWMs. PWMs generated by N76E003 can be edge-aligned or centre-aligned PWMs as per user’s requirement.  The PWM block can be clocked directly by the system clock or by Timer 1 overflow. Additionally, there is a prescalar unit to reduce input clock source.

For demonstrating N76E003’s PWM feature, I used an RGB LED. Three independent PWMs were generated using PWM channels 1,2 and 4. Let us look into the process of setting up the PWM channels and the PWM hardware. We must firstly set the PWM GPIOs as push-pull GPIOs since PWM is an output function of GPIO pins. We must also enable the PWM output channels we want to use.

P01_PushPull_Mode;
P10_PushPull_Mode;
P11_PushPull_Mode;
PWM1_P11_OUTPUT_ENABLE;
PWM2_P10_OUTPUT_ENABLE;
PWM4_P01_OUTPUT_ENABLE;

Secondly, the PWM hardware is set up according to our needs. Since we need independent PWMs here, independent PWM mode is selected. Edge-align PWM is selected since it is most easy to understand and use. The PWM channels are reset before using them. The PWM outputs are non-inverted and so they are characterized as normal PWMs. Here, I use system clock as the clock source for the PWM block but it is prescaled/divided by 64 to get an effective PWM clock of 250kHz. The PWM resolution is set to 10-bits when we set the period count or maximum PWM value/duty cycle. Setting the period count yields in setting up the internal PWM counter. This gives a PWM of approximately 245Hz frequency or 4ms period. Finally, the PWM hardware is enabled after setting up all these.

PWM_IMDEPENDENT_MODE;
PWM_EDGE_TYPE;
set_CLRPWM;
PWM_CLOCK_FSYS;
PWM_CLOCK_DIV_64;
PWM_OUTPUT_ALL_NORMAL;
set_PWM_period(1023);
set_PWMRUN;

PWM3 PWM1 PWM2

To change PWM duty cycle, functions like the one shown below is called. After altering the duty cycle or compare value, the new value is loaded and run.

void set_PWMn(unsigned int value)
{
  PWMnL = (value & 0x00FF);
  PWMnH = ((value & 0xFF00) >> 8);
  set_LOAD;
}

In the demo, the RGB LED fades different colours as a symbol of changing PWMs.

Demo

PWM

PWMS (1) PWMS (2) PWMS (3)

Complementary PWM with Dead Time

In the last section, we saw how we can generate independent PWM. Now we will see complimentary or group PWM with dead-time. We will also see the things that were skipped in the previous segment.

Complementary PWM with dead-time feature is a must-have feature of any PWM drives in today’s embedded-system industry. Unlike the simple PWM we saw previously, this kind of PWM has most usage. Complementary PWM with dead-time is used in applications where we need to design motor controllers, SMPSs, inverters, etc with H-bridges or half-bridges. In such applications, PWMs need to operate in groups while ensuring that both PWMs don’t turn on at the same time. One PWM in a group should be the opposite/antiphase of the other in terms of duty-cycle or waveshape.

bridges

Complimentary PWM

Code

#include "N76E003.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "Delay.h"

void set_PWM_period(unsigned int value);
void set_PWM0(unsigned int value);
void set_PWM1(unsigned int value);
void set_PWM2(unsigned int value);
void set_PWM3(unsigned int value);
void set_PWM4(unsigned int value);
void set_PWM5(unsigned int value);
void set_PWM_dead_time(unsigned int value);

void main(void)
{
  signed int i = 0;

  P11_PushPull_Mode;
  P12_PushPull_Mode;

  PWM0_P12_OUTPUT_ENABLE; 
  PWM1_P11_OUTPUT_ENABLE;

  PWM_COMPLEMENTARY_MODE;
  PWM_CENTER_TYPE;
  set_CLRPWM;
  PWM_CLOCK_FSYS;
  PWM_CLOCK_DIV_64;
  PWM0_OUTPUT_INVERSE;
  PWM1_OUTPUT_INVERSE;
  set_PWM_period(600);
  set_PWM_dead_time(40);
  PWM01_DEADTIME_ENABLE;
  set_PWMRUN;

  while(1)
  {
    for(i = 0; i < 600; i++)
    {
      set_PWM0(i);
      Timer0_Delay1ms(5);
    }
    for(i = 600; i > 0; i--)
    {
      set_PWM0(i);
      Timer0_Delay1ms(5);
    }
  };
}

void set_PWM_period(unsigned int value)
{
  PWMPL = (value & 0x00FF);
  PWMPH = ((value & 0xFF00) >> 8); 
}

void set_PWM0(unsigned int value)
{
  PWM0L = (value & 0x00FF);
  PWM0H = ((value & 0xFF00) >> 8);
  set_LOAD;
}

void set_PWM1(unsigned int value)
{
  PWM1L = (value & 0x00FF);
  PWM1H = ((value & 0xFF00) >> 8);
  set_LOAD;
}

void set_PWM2(unsigned int value)
{
  PWM2L = (value & 0x00FF);
  PWM2H = ((value & 0xFF00) >> 8);
  set_LOAD;
}

void set_PWM3(unsigned int value)
{
  PWM3L = (value & 0x00FF);
  PWM3H = ((value & 0xFF00) >> 8);
  set_LOAD;
}

void set_PWM4(unsigned int value)
{
  set_SFRPAGE;
  PWM4L = (value & 0x00FF);
  PWM4H = ((value & 0xFF00) >> 8);
  clr_SFRPAGE;
  set_LOAD;
}

void set_PWM5(unsigned int value)
{
  set_SFRPAGE;
  PWM5L = (value & 0x00FF);
  PWM5H = ((value & 0xFF00) >> 8);
  clr_SFRPAGE;
  set_LOAD;
}

void set_PWM_dead_time(unsigned int value)
{
  unsigned char hb = 0;
  unsigned char lb = 0;

  lb = (value & 0x00FF);
  hb = ((value & 0x0100) >> 8);
  BIT_TMP = EA;

  EA = 0;
  TA = 0xAA;
  TA = 0x55;
  PDTEN &= 0xEF;
  PDTEN |= hb;
  PDTCNT = lb;
  EA = BIT_TMP;
}

 

Schematic

Comp PWM Schematic

Explanation

As discussed, there are two types of PWM in terms of count alignment. These are shown below:

PWM types

The first type was demonstrated in the simple PWM example. The second is demonstrated here. From the perspective of a general user, the difference in them don’t affect much. Make sure that you check the formulae for each type before using.

The set up for complimentary PWM with dead time is no different from the simple PWM set up. As mentioned, there are a few minor differences. First the mode is set for complimentary PWM mode. Secondly, centre-aligned PWM is used here. The outputs are also set as inverted outputs, i.e. 100% duty cycle means minimum PWM value/count. The dead time period is set and implemented.

P11_PushPull_Mode;
P12_PushPull_Mode;

 

PWM0_P12_OUTPUT_ENABLE; 
PWM1_P11_OUTPUT_ENABLE;

 

PWM_COMPLEMENTARY_MODE;
PWM_CENTER_TYPE;
set_CLRPWM;
PWM_CLOCK_FSYS;
PWM_CLOCK_DIV_64;
PWM0_OUTPUT_INVERSE;
PWM1_OUTPUT_INVERSE;
set_PWM_period(600);
set_PWM_dead_time(40);
PWM01_DEADTIME_ENABLE;
set_PWMRUN;

Now what is dead time in complimentary PWMs? Well simply is a short duration delay that is inserted between the polarity shifts of two PWMs in a group.

MOSFETs Half-bridge

Consider a half-bridge MOSFET configuration as shown above. Perhaps it is the best way to describe the concept of dead-time. Surely, nobody would ever want to turn on both MOSFETs simultaneously in any condition and also during transition. Doing so would lead to a temporary short circuit between voltage source and ground, and would also lead to unnecessary heating of the MOSFETs and even permanent damage. By applying dead-time this can be avoided. In half/H-bridges, complimentary PWMs ensure that when one MOSFET is on, the other is off. However, at the edges of PWM polarity shifts, i.e. when one is rising while the other is falling, there is short but certain such short-circuit duration. If a dead time is inserted between the transitions, it will ensure that one MOSFET is only turned on when the other has been turned off fully.

Setting dead time requires us to disable TA protection. The function for setting dead time is shown below:

void set_PWM_dead_time(unsigned int value)
{
  unsigned char hb = 0;
  unsigned char lb = 0;

  lb = (value & 0x00FF);
  hb = ((value & 0x0100) >> 8);
  BIT_TMP = EA;

  EA = 0;
  TA = 0xAA;
  TA = 0x55;
  PDTEN &= 0xEF;
  PDTEN |= hb;
  PDTCNT = lb;
  EA = BIT_TMP;
}

Since complimentary PWMs work in groups, changing the duty cycle of one PWM channel will affect the other and so we don’t need to change the duty cycles of both PWMs individually. This is why only one PWM channel is manipulated in the code.

Demo

PWMC (1) PWMC (2)

Wakeup Timer and Power Modes

One of key feature of many modern era microcontrollers is a way to wake up a micro once it went to low power, sleep or idle mode. Like STM8s, N76E003 has this feature. The wakeup timer (WKT) is not a complex hardware. As the block diagram below shows it is just a timer-counter with LIRC as clock source. When the counter overflows, an interrupt is triggered. This interrupt wakes up the N76E003 chip.

WKT_Block

Wakeup timer is particularly very useful when used with low power modes. In many applications, we need to measure data on a fixed time basis while at other times when measurements are not taken, idle times are passed. These idle periods consume power and so if we use a wakeup timer with low power sleep mode, we can save energy. This is a big requirement for portable battery-operated devices. Examples of such portable devices include data-loggers, energy meters and smart watches.

Code

#include "N76E003_iar.h"
#include "Common.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "soft_delay.h"

#pragma vector = 0x8B
__interrupt void WKT_ISR(void)
{
  clr_WKTR;
  clr_WKTF;                                
}

void main(void)
{
  unsigned char s = 0;

  P15_PushPull_Mode;

  WKCON = 0x03;                       
  RWK = 0X00;
  set_EWKT;                       
  set_EA;

  while(1)
  {
    for(s = 0; s < 9; s++)
    {
      P15 = ~P15;
      delay_ms(100);
    }

    set_WKTR;                           
    set_PD;

    for(s = 0; s <= 9; s++)
    {
      P15 = ~P15;
      delay_ms(300);
    }

    set_WKTR;                           
    set_PD;
  };
}

 

Schematic

WKT_Schematic

Explanation

In the demo demonstrated here, P15 LED is toggle fast for nine times. This marks the start of the demo. After this toggling has been done, both the wakeup timer and the power down modes are turned on.

set_WKTR;                           
set_PD;

During Power Down time, all operations inside the micro are suspended. There is also another low power mode called Idle Mode. Both are similar but in idle mode peripherals are kept active.

Power Mode Registers

After the first set of LED toggles and power down mode, it takes about 1.5 seconds (according to the prescalar value of 64 and 256 counts) for the wakeup timer to get triggered.

WKT

When the interrupt kicks in, the MCU is brought back to working mode and the wakeup timer is disabled. Again, the P15 LED is toggled. This time the LED is toggled at a slower rate, indicating the continuation of the rest of the tasks after power down. After this the process is repeated with power down and wake up timer reenabled.

Setting up the wakeup timer requires the same stuffs that we need during a timer configuration, i.e. a prescalar value, counter value and interrupt. These three settings are done by the following lines of code:

WKCON = 0x03;                       
RWK = 0X00;
set_EWKT;                       
set_EA;

Since interrupt is used, WKT interrupt will be triggered when its counter rolls over.

#pragma vector = 0x8B
__interrupt void WKT_ISR(void)
{
  clr_WKTR;
  clr_WKTF;                                
}

When WKT interrupt is triggered, its interrupt flag should be cleared in the software.

Demo

Timer 0 (2) Timer 0 (1)

Watchdog Timer

The watchdog timer (WDT) of N76E003 is just a reset-issuing timer and the purpose of this timer is to recover a N76E003 micro from an unanticipated event/loop that may result in unresponsive or erratic behaviour. It is clocked with LIRC oscillator and this makes it independent from main clock (HIRC or ECLK) failure.

WDT_Block_Diagram

LIRC is prescaled and feed to a counter. When the counter overflows, WDT interrupt is issued, furthermore a reset is also generated based on delay. In normal running condition, the counter must be periodically reset to 0 count to avoid reset. If for some reason this is not done then a reset will be triggered.

The watchdog timer of N76E003 can also be used as a 6-bit general-purpose timer. However, this usually not used as such. The only difference between using it as timer and as a watchdog timer is the reset part.

WDT_GPT_Block_Diagram

Code

#include "N76E003.h"
#include "Common.h"
#include "Delay.h"
#include "SFR_Macro.h"
#include "Function_define.h"

void main (void)
{
    unsigned char s = 0;

    P15_PushPull_Mode;

    Timer0_Delay1ms(1000);

    for(s = 0; s <= 9; s++)
    {
        P15 = ~P15;
        Timer0_Delay1ms(60);
    }

    TA = 0xAA;
    TA = 0x55;
    WDCON = 0x07;                       
    set_WDCLR;                                                  
    while((WDCON | ~SET_BIT6) == 0xFF);         
    EA = 1;
    set_WDTR;                                                       

    while(1)
    {
        for(s = 0; s <= 9; s++)
        {
            P15 = ~P15;
            Timer0_Delay1ms(200);
        }
        while(1);
    }
}

 

Schematic

WDT_Schematic

Explanation

For demoing WDT, again the P15 LED is used. The code starts with it toggling states ten times. This marks the beginning of the code and the interval prior to WDT configuration.

Next the WDT is setup. Note that the WDT is Timed-Access (TA) protected. TA protection protects crucial hardware like brownout detection circuit and WDT hardware from erratic writes.

TA Register

For using the TA hardware, we need not to follow any special procedure. To enable access to some hardware that are TA protected like the WDT all we have to do is to write the followings to TA register:

TA = 0xAA;
TA = 0x55;

Without TA protection access, any changes to made to the registers of the TA protected hardware is unaffected.

After enabling access to WDT, we can set it up. Setting the WDT requires us mainly to setup LIRC clock prescalar. Once the prescalar is set we are good to go for enabling the WDT.

WDCON = 0x07;                       
set_WDCLR;                                                  
while((WDCON | ~SET_BIT6) == 0xFF);         
set_EA;
set_WDTR;  

Before all these can be done, we have to enable the WDT hardware either through In-Application Programming (IAP) or by configuration bits during programming.

WDT Config

Demo

Timer 0 (2) Timer 0 (1)

The post Getting Started with Nuvoton 8-bit Microcontrollers – Coding Part 2 appeared first on Embedded Lab.

Viewing all 713 articles
Browse latest View live