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

Meet Teensy LC (low cost)

$
0
0

Meet the new Teensy LC, a more affordable version of popular Teensy ARM development board and is now available for pre-order for only $$11.65. It features an ARM Cortex-M0+ processor at 48 MHz, 62K Flash, 8K RAM, 12 bit analog input & output, hardware Serial, SPI & I2C, USB, and a total of 27 I/O pins. And most importantly, Teensy-LC maintains the same form-factor as Teensy 3.1, with most pins offering similar peripheral features.

Teensy LC pinouts

Teensy LC pinouts

Most modern projects involve serial communication with sensors, other chips, other systems, or even the internet. Hardware serial ports greatly simplify projects and enable excellent performance.

Teensy-LC provide plenty of serial connectivity: 2 SPI ports, 2 I2C, and 3 Serial ports. All 3 serial ports are supported by high quality drivers in Teensyduino, with both transmit and receive buffering, and even support for RS485 transmitter enable.

Teensy-LC will be ideal for inexpensive “Internet Of Things” projects when paired with a ESP8266 Wifi module, which requires a fast hardware serial or SPI port.

 

The post Meet Teensy LC (low cost) appeared first on Embedded Lab.


Give your multimeter a remote display

Microsoft brings free Windows 10 for Raspberry Pi 2

$
0
0

Microsoft has announced that it will give away a free version of Windows 10 that supports Raspberry Pi 2.

Free Windows for Raspberry Pi 2 is coming soon

Free Windows for Raspberry Pi 2 is coming soon

We’re excited to announce that we are expanding our Windows Developer Program for IoT by delivering a version of Windows 10 that supports Raspberry Pi 2. This release of Windows 10 will be free for the Maker community through the Windows Developer Program for IoT.

Windows 10 is the first step to an era of more personal computing. This vision framed our work on Windows 10, where we are moving Windows to a world that is more mobile, natural and grounded in trust. With the Windows for IoT developer program we’re bringing our leading development tools, services and ecosystem to the Raspberry Pi community!

We see the Maker community as an amazing source of innovation for smart, connected devices that represent the very foundation for the next wave of computing, and we’re excited to be a part of this community.

We are excited about our partnership with the Raspberry Pi Foundation and delivering a version of Windows 10 that supports Raspberry Pi 2, and we will be sharing more details about our Windows 10 plans for IoT in the coming months.

 

The post Microsoft brings free Windows 10 for Raspberry Pi 2 appeared first on Embedded Lab.

XMega External Interrupt

$
0
0

External interrupts are a must have feature in any microcontroller. Interrupts solve a lot of problem that would have otherwise been dependent on polling methods. For instance when we press the volume up key of a TV tuner’s remote controller, the remote controller quickly responds by transmitting the volume up command to the TV set and in turn the TV’s volume increases. This fast response is due to external interrupt issued by the remote’s button to the microcontroller it is connected to. If, however, all the keys of the remote were regularly and frequently scanned and then responded up on a press, the process would have been both slow and energy consuming because its microcontroller would then have never went to sleep or low power states and continuously kept scanning. In other words, the micro would have always ran despite no mandatory necessity and during standby conditions. This would have quickly drained the batteries. Since interrupt is typically used in such cases the remote controller will respond to a button press fast, wake up from sleep/idle/low power mode, transmit command data and then go back to sleep/idle/low power state. Thus the overall energy consumption is reduced while achieving fastest possible reaction. This is how real world applications work applying external interrupts.

XMega 3D

 

XMega devices just like any micro are equipped with interrupts for both external and internal peripherals but it is more than you can even guess. Any I/O pin or group of I/O pins can be used for external interrupt. Now that’s a cool feature and a great relief as well because most traditional 8-bit micros like the ATMega328P, which is by the way the most widely used by the Arduino community, has only two external interrupts and fixed to one I/O port only. Other larger Mega AVRs have up to eight external interrupts. However XMega’s interrupt system is much more complex than those traditional AVRs. XMega devices support both maskable and non-maskable interrupts (NMI). Currently, however, there is only one NMI and it occurs due to crystal oscillator failure event. NMI is independent of global interrupt enable status. Unlike most traditional micros, interrupt priority can be set apart from edge selection and other stuffs. Priority setting has the effect of determining which interrupt can or will further interrupt another interrupt and so forth.

Interrupt System Internal Circuitry

 

The PMIC and Interrupt Priority

The Programmable Multilevel Interrupt Controller (PMIC) is responsible for handling and prioritizing interrupt requests. As with any microcontroller the global interrupt flag (SREG_I_bit) should be set before using the interrupt system. When a given interrupt condition is enabled and present, the PMIC receives interrupt request and it either acknowledges it or keeps it waiting based on interrupt priority and ongoing interrupts (if any). After executing an interrupt request, the PMIC returns to the proper interrupt level or to a state before the occurrence of the interrupt.

 

XMega reference manual states that there are three possible interrupt priority levels:

Interrupt Priority

The PMIC prioritizes interrupts according to the following order:Interrupt Priority Levels

This shows that a higher order interrupt will interrupt a lower order interrupt if both are occurring at the same instance. After processing the higher order interrupt the lower order interrupt is processed. Within a given order, interrupts with lower interrupt vector addresses have higher priorities than those with higher addresses. Typically reset has the highest priority and the lowest interrupt vector address. The same is true for XMega micros too. NMI is the next highest priority interrupt request.

ISR Address vs Priority

 

In the XMega, there are two types of interrupt and these are static and dynamic. Static interrupts are those which have fixed vector addresses while low priority interrupts are dynamic and this dynamic behaviour is achieved by Round-robin Scheduling (RRS) technique. The RRS ensures that the low level interrupts are not skipped. If this technique is applied then the last invoked interrupt will have the lowest priority for one or more interrupts. Thus within the low level interrupt there is a mechanism that makes sure that all of them are served within a certain time frame.

Round Robin Scheme

Interrupt Service Routine in XMega Devices

All maskable interrupt peripherals have interrupt flags and settings to set interrupt priority as well as enabling them. Irrespective of its settings an interrupt flag will be set when it occurs. The interrupt flags in most cases are automatically cleared up on executing them or by setting them in software.

Some key points regarding interrupt handling:

  • When a higher priority interrupt is ongoing and lower priority interrupt invokes, the lower priority interrupt is remembered and kept pending until its priority comes.
  • When a higher priority interrupt overtakes an ongoing lower priority interrupt, the lower priority interrupt is remembered and served after executing the higher priority interrupt.
  • If an interrupt has lower vector address then it will overtake another interrupt of same priority having a higher address. The latter will be remembered and served after executing the former.
  • If an interrupt is disabled and it occurs, it is remembered until it is enabled or cleared by software. Previously in my post regarding the XMega ADC block we encountered this stuff.
  • If interrupts occur when the global interrupt enable bit is disabled, all of the interrupts are remembered and processed according to priority when the global interrupt is enabled.
  • Active interrupts can be cleared by issuing clear interrupt instruction asm cli; in assembly. This is just the opposite of global interrupt enable using the asm sei; instruction.

Remember these points when watching the code example video. You’ll certainly see all these happen.

 

Interrupt Disarmament

All maskable interrupts are disabled for four clock cycles automatically when the Configuration Change Protection (CPU_CCP) register is feed with two correct instruction signatures. These are:

  • Protect I/O instruction (CCP_IOREG, 0xD8).
    Prevents unintentional writes into protected peripheral registers. We already encountered it in the XMega Clock System post.

  • Store Program Memory (CPU_SPM, 0x9D).
    Used to write to the XMega’s flash memory from application software.

Correct signature values as above should be feed to the CPU_CCP register before executing SPM instruction or modifying protected peripheral registers.

 

XMega Port Interrupt

Before understanding I/O port pin interrupt, I would like to request readers to brush up my earlier post on XMega I/O Ports here because in that post I discussed about edge selection as well as other I/O port pin properties which are necessary for external interrupts. The I/O pins of the XMega are such that an external interrupt can occur when the logic state of a single pin or a group of pins changes. Every port in an XMega device has two independent external interrupt vector addresses (INT0 and INT1).

To configure external interrupts, we need to configure I/O pins first. After having I/Os setup, interrupt priorities are set for respective vector address. Finally before setting global interrupt flag, interrupt pin masks are applied to set which pins will invoke a given interrupt. When an interrupt occurs its corresponding flag is set. Shown below are port pin interrupt registers. We’ll have to set them.
Port Interrupt Registers

 

Coding

The example code for external interrupt not only demonstrates the typical external interrupt response but also shows how priorities are handled. The ATXMega32A4U I used in this post runs at 8MHz clock derived from prescaled internal 32MHz oscillator. There are three LEDs (Red, Yellow and Green), two push buttons and a 16×1 LCD attached to the XMega micro as shown:

Schematic

 

I/O pin PA0 is programmed as INT0 pin while PA1 is programmed as INT1 pin. Both pins are configured to sense rising edge. Without any interrupt the main loop executes and the LCD shows “Main Loop” and D3 (Red LED) blinks. When INT0 (low level) interrupt occurs due to button press, the LCD shows “LOW Lvl ISR.” and D2 (Yellow LED) blinks sixteen times, halting the actions of the red LED. Similarly when INT1 (high level) interrupt occurs the LCD shows “HIGH Lvl ISR.” and D1 (Green LED) blinks sixteen times, pausing the actions of other two LEDs. After executing an interrupt either the next priority interrupt is served or moved to the main loop. In the example you’ll notice everything I mentioned about Interrupt Service Routine (ISR) execution here.


#include "io.h"
#include "clock.h"
#include "interrupt.h"
 
sbit LCD_RS at PORTC_OUT.B2;
sbit LCD_EN at PORTC_OUT.B3;
sbit LCD_D4 at PORTC_OUT.B4;
sbit LCD_D5 at PORTC_OUT.B5;
sbit LCD_D6 at PORTC_OUT.B6;
sbit LCD_D7 at PORTC_OUT.B7;
 
sbit LCD_RS_Direction at PORTC_DIR.B2;
sbit LCD_EN_Direction at PORTC_DIR.B3;
sbit LCD_D4_Direction at PORTC_DIR.B4;
sbit LCD_D5_Direction at PORTC_DIR.B5;
sbit LCD_D6_Direction at PORTC_DIR.B6;
sbit LCD_D7_Direction at PORTC_DIR.B7;
 
void setup_clock();
void setup_io();
void setup_interrupts();
void setup_lcd();
 
void PORTA_INT0_ISR()
org IVT_ADDR_PORTA_INT0
{
    unsigned char s = 16;
    
    while(s > 0)
    {
         Lcd_Cmd(_LCD_CLEAR);
         Lcd_Out(1, 1, "LOW Lvl");
         Lcd_Out(2, 3, "ISR.");
         PORTD_OUT.B1 ^= 1;
         delay_ms(200);
         s--;
    }
}
 
void PORTA_INT1_ISR()
org IVT_ADDR_PORTA_INT1
{
    unsigned char s = 16;
    
    while(s > 0)
    {
         Lcd_Cmd(_LCD_CLEAR);
         Lcd_Out(1, 1, "HIGH Lvl");
         Lcd_Out(2, 3, "ISR.");
         PORTD_OUT.B2 ^= 1;
         delay_ms(200);
         s--;
    }
}
 
void main() 
{
     setup_interrupts();
     setup_clock();
     setup_io();
     setup_lcd();
     
     while(1)
     {
         Lcd_Cmd(_LCD_CLEAR);
         Lcd_Out(1, 3, "Main");
         Lcd_Out(2, 3, "Loop");
         PORTD_OUT.B0 ^= 1;
         delay_ms(900);
     }
}
 
void setup_clock()
{
    clear_global_interrupt();
    OSC_CTRL |= OSC_RC32KEN_bm;
    while(!(OSC_STATUS & OSC_RC32KRDY_bm));
    DFLLRC32M_CTRL = 0;
    OSC_CTRL |= OSC_RC32MEN_bm;
    while(!(OSC_STATUS & OSC_RC32MRDY_bm));
    OSC_DFLLCTRL = OSC_RC32MCREF_RC32K_gc;
    DFLLRC32M_CTRL = DFLL_ENABLE_bm;
    CPU_CCP = CCP_IOREG_gc;
    CLK_PSCTRL = (CLK_PSADIV_4_gc | CLK_PSBCDIV_1_1_gc);
    CPU_CCP = CCP_IOREG_gc;
    CLK_CTRL = CLK_SCLKSEL_RC32M_gc;
    OSC_CTRL &= ~(OSC_RC2MEN_bm | OSC_XOSCEN_bm | OSC_PLLEN_bm);
    PORTCFG_CLKEVOUT = 0x00;
}
 
void setup_io()
{
    PORTA_OUT = 0x00;
    PORTA_DIR = 0x00;
    PORTA_PIN0CTRL = (PORT_OPC_TOTEM_gc | PORT_ISC_RISING_gc);
    PORTA_PIN1CTRL = (PORT_OPC_TOTEM_gc | PORT_ISC_RISING_gc);
    PORTA_PIN2CTRL = (PORT_OPC_TOTEM_gc | PORT_ISC_BOTHEDGES_gc);
    PORTA_PIN3CTRL = (PORT_OPC_TOTEM_gc | PORT_ISC_BOTHEDGES_gc);
    PORTA_PIN4CTRL = (PORT_OPC_TOTEM_gc | PORT_ISC_BOTHEDGES_gc);
    PORTA_PIN5CTRL = (PORT_OPC_TOTEM_gc | PORT_ISC_BOTHEDGES_gc);
    PORTA_PIN6CTRL = (PORT_OPC_TOTEM_gc | PORT_ISC_BOTHEDGES_gc);
    PORTA_PIN7CTRL = (PORT_OPC_TOTEM_gc | PORT_ISC_BOTHEDGES_gc);
    PORTA_INTCTRL = (PORT_INT1LVL_HI_gc | PORT_INT0LVL_LO_gc);
    PORTA_INT0MASK = 0x01;
    PORTA_INT1MASK = 0x02;
    
    PORTD_OUT = 0x00;
    PORTD_DIR = 0x07;
    PORTD_PIN0CTRL = (PORT_OPC_TOTEM_gc | PORT_ISC_BOTHEDGES_gc);
    PORTD_PIN1CTRL = (PORT_OPC_TOTEM_gc | PORT_ISC_BOTHEDGES_gc);
    PORTD_PIN2CTRL = (PORT_OPC_TOTEM_gc | PORT_ISC_BOTHEDGES_gc);
    PORTD_PIN3CTRL = (PORT_OPC_TOTEM_gc | PORT_ISC_BOTHEDGES_gc);
    PORTD_PIN4CTRL = (PORT_OPC_TOTEM_gc | PORT_ISC_BOTHEDGES_gc);
    PORTD_PIN5CTRL = (PORT_OPC_TOTEM_gc | PORT_ISC_BOTHEDGES_gc);
    PORTD_PIN6CTRL = (PORT_OPC_TOTEM_gc | PORT_ISC_BOTHEDGES_gc);
    PORTD_PIN7CTRL = (PORT_OPC_TOTEM_gc | PORT_ISC_BOTHEDGES_gc);
    PORTD_INTCTRL = (PORT_INT1LVL_OFF_gc | PORT_INT0LVL_OFF_gc);
    PORTD_INT0MASK = 0x00;
    PORTD_INT1MASK = 0x00;
    
    set_global_interrupt();
}
 
void setup_interrupts()
{
    clear_global_interrupt();
    CPU_CCP = CCP_IOREG_gc;
    PMIC_CTRL = (PMIC_LOLVLEN_bm | PMIC_HILVLEN_bm);
    PMIC_INTPRI = 0x00;
}
 
void setup_lcd()
{
    Lcd_Init();
    Lcd_Cmd(_LCD_CLEAR);
    Lcd_Cmd(_LCD_CURSOR_OFF);
}

 

20150205_192725 20150205_192746 20150205_192752

 

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

 

 

So far we just dealt with external interrupts but there are also interrupts that result due to internal hardware peripherals like timers, communication modules, ADC, etc. I’ll discuss about them in my upcoming posts when I deal with those hardware. Should you have understood how external interrupts work in the XMega, I believe you have clearly understood how other interrupts will behave. Lastly please do read the reference manual for more clear understanding.

Files: XMega External Interrupt.

References:

Happy coding.

Author: Shawon M. Shahryiar
https://www.facebook.com/groups/microarena   
https://www.facebook.com/MicroArena 
+8801970046495                                                                                                                                              
06.02.2015

The post XMega External Interrupt appeared first on Embedded Lab.

DIY AC power meter using PIC18F252

$
0
0

Electro-Labs new tutorial project is a PIC microcontroller-based digital power meter that will let you measure the power consumption of an electrical appliance connected to the 230V/50Hz mains line. The project implements a resistor-divider network to scale down the mains voltage and then uses MCP3202 ADC chip to convert the scaled AC voltage to digital counts. For current measurement, the ACS712-30A hall-effect sensor is used in conjunction with another MCP3202 chip. The digital outputs from the ADC chips are then read by the PIC18F252 microcontroller for computing the power. The measured AC RMS voltage, current, and Watts are then displayed on the dot matrix LCD. The beauty of this project is the meter itself is powered from the mains, thus avoiding the need for an external power source.

PIC-based AC watt meter

PIC-based AC watt meter

The post DIY AC power meter using PIC18F252 appeared first on Embedded Lab.

Integrating STM32F4xx Standard Peripheral Library with MikroC Pro for ARM

$
0
0

STM32F4xx series micros are far more advanced than anything else similar in the market. Apart from being fast 32-bit MCUs, STM32F4s have rich hardware peripheral support with DSP engine bonus. In terms of capabilities versus price tag, STM32F4s are all-square-winners. In recent times there’s a surge in the STM32 user community. STM32 Discovery boards are proliferating like never before. In several occasions recently, I received tangible amounts of queries from readers regarding integration of STM32F4xx Standard Peripheral Library (SPL) with MikroC Pro for ARM and so even though it is not one of my mainstream posts on STM32 ARMs, I felt that I should address this topic. Previously I showed how to port STM32F1xx SPL for STM32F1xx series devices with MikroC. This post will not be different from the former one – only minute changes. I suggest readers to read the earlier post first before reading this one. STM32F4 Discovery Board

At present MikroC compiler is one of the few compilers that hasn’t yet officially provided any support for SPL integration. Hopefully they will add this support in the future but the way MikroC addresses a solution is different from other compilers. MikroC has a really awesome IDE and a rich library base support while SPL gives its user more control over on a chip’s hardware. Like I said before together these become a doubled-edge sword.

Tools Needed

Firstly you’ll need MikroC Pro for ARM. The compiler has a demo version that provides an 8k code limit, enough for first-hand ARM experience. You’ll need a STM32F4xx board. I used a STM32F407VG Discovery. This board from ST Microelectronics is somewhat the flagship Discovery board of the STM32F4 series and it is very popular. Finally you’ll need STM32F4xxx SPL which I included in my example. The files in them are modified to work with MikroC environment. 

Things to do

Firstly we need to prepare our MikroC PRO for ARM compiler for linking the SPL. I’m assuming that the compiler is preinstalled. First go to the compiler’s installation folder and locate the include folder. In my case, it is: C:\Users\Public\Documents\Mikroelektronika\mikroC PRO for ARM\Include.

In this folder, there’s a file named stdint.h. Rename it to stdint (backup).h and copy the new stdint.h file from the supplied folder to this location. If you already did so during my previous post or upgraded to MikroC Pro for ARM version 4.15 (the latest release at the time of this writing), then skip this part.

stdint modification

Next we have to change some options of the compiler. Launch the compiler and the follow as shown below:
Tools    >>    Options    >>    Output Settings

Check Case sensitive. Apply and close this window. Again skip this part if you already did so earlier.

compiler changes

 

Be cautious from now on with regards to naming and letter cases because the compiler is now sensitive to this or otherwise you’ll be doomed in the abyss of programming misfortune. Just a simple but useful tip – Compile your code few often after some edits or modifications. This will greatly help you to debug tiny mistakes in a small frame of coding window.

Finally keep the SPL in secured location. Whenever you code a new project with the SPL, copy it from this location and use it with the project. This will make sure that the original SPL copy is not accidentally modified.

Coding with SPL

The example code is just another blinking code example as it will suffice.  I’m not going to tell you how to create a new project in MikroC. I presume it is known to you. Create a new project and using the Project Manager add all the source and header files from the SPL folder. You can simply add the header and source files of only the required peripherals or you can add all if you are not sure which one stand for which. The former approach takes less compilation time than the latter. Additionally add the defines.pld Project Level Definition file. Now you are ready to code with the SPL. IDE

 

Please be noted that since the SPL is not yet officially integrated by MikroElektronika, you’ll notice red error markers under various parts of a code with SPL. This is not unusual and not an error.  So no worries.

Code:
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_usart.h"
#include "stm32f4xx_rcc.h"
 
void setup_GPIOs();
void check_LEDs();
 
void main() 
{
     unsigned char s = 0;
     
     setup_GPIOs();
     check_LEDs();
     
     while(1)
     {
             if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0))
             {
                 while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 1);
                 s++;
             }
             
             if(s >= 6)
             {
                 s = 0;
             }
             
             switch(s)
             {
                 case 1:
                 {
                      GPIO_SetBits(GPIOD, GPIO_Pin_12);
                      break;
                 }
                 case 2:
                 {
                      GPIO_SetBits(GPIOD, GPIO_Pin_13);
                      break;
                 }
                 case 3:
                 {
                      GPIO_SetBits(GPIOD, GPIO_Pin_14);
                      break;
                 }
                 case 4:
                 {
                      GPIO_SetBits(GPIOD, GPIO_Pin_15);
                      break;
                 }
                 case 5:
                 {
                      GPIO_SetBits(GPIOD, GPIO_Pin_12);
                      GPIO_SetBits(GPIOD, GPIO_Pin_13);
                      GPIO_SetBits(GPIOD, GPIO_Pin_14);
                      GPIO_SetBits(GPIOD, GPIO_Pin_15);
                      break;
                 }
                 default:
                 {
                     break;
                 }
             }
             Delay_ms(90);
             
             GPIO_ResetBits(GPIOD, GPIO_Pin_12);
             GPIO_ResetBits(GPIOD, GPIO_Pin_13);
             GPIO_ResetBits(GPIOD, GPIO_Pin_14);
             GPIO_ResetBits(GPIOD, GPIO_Pin_15);
             Delay_ms(90);
     }
}
 
void setup_GPIOs()
{
  GPIO_InitTypeDef GPIO_InitStructure;
 
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
 
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
 
  GPIO_InitStructure.GPIO_Pin = (GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15);
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
  GPIO_Init(GPIOD, &GPIO_InitStructure);
}
 
void check_LEDs()
{
    GPIO_SetBits(GPIOD, (GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15));
    Delay_ms(900);
    GPIO_ResetBits(GPIOD, (GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15));
}

 

20150207_224408 20150207_224410
Demo video link: https://www.youtube.com/watch?v=ngk-4Lm11yU.

Personally I haven’t made myself dependent on SPL so far and I believe in raw level coding-learning. SPL allows textbook entry in STM32 arena. No wonder it makes coding easy but the adverse effect of it in my eyes is the fact that its users will spend less time with reference manuals and datasheet. This in turn will affect them in acquiring solid learning of internal hardware peripherals and their working principles. If SPL is to make life easy this way then why not use MikroC libraries. Those are even more easy to use. Sometimes libraries are lifesavers while sometimes they are curse. Sometimes you would not like to reinvent the “wheel” but you would certainly like to make a vehicle with it and that my friend worth more than anything else. At this point I don’t want to debate further in this matter. I leave this choice to users.

Files: Integrating STM32F4xx Standard Peripheral Library with MikroC Pro for ARM.

Happy coding.

Author: Shawon M. Shahryiar

https://www.facebook.com/MicroArena

+8801970046495

08.02.2015

 

The post Integrating STM32F4xx Standard Peripheral Library with MikroC Pro for ARM appeared first on Embedded Lab.

PIC18F2550 breakout board

$
0
0

Jesus Echavarria’s PICnano is a breadboard-friendly breakout board for the PIC18F2550 microcontroller in an Arduino Nano form factor.

PICnano board

PICnano board

The goal of this new design is try to have an Arduino Nano compatible module but with some new features, such have direct access to the power supply microcontroller. First of all, here you can download the schematic of the board: PICNANO BREADBOARD SCHEMATIC V1.0.

The board is based on the PIC18F2550 microcontroller, here you can download the datasheet.

 

The post PIC18F2550 breakout board appeared first on Embedded Lab.

Quick start-up guide for ESP8266

$
0
0

The ESP8266 Wifi-to-serial transceiver is the latest and most inexpensive way to get your project connected to the Internet. If you wonder how to setup up this thing in your project, this Start-Up PDF guide from rancidbacon.com will be very helpful to you.

Out of many ESP8266 modules available in the market, the ESP-01 version is the most popular one. While the headers are 0.1″ pitch, the pin arrangements are not breadboard friendly and are not labeled on board, which makes it little inconvenient for breadboarding. So you might be interested in our Breadboard Friendly Adapter  (shown below) with clear pin labels printed on the board to make prototyping with the ESP-01 module easier.

Buy ESP8266 Breadboard adapter

The post Quick start-up guide for ESP8266 appeared first on Embedded Lab.


Setting up an ESP8266 web server

$
0
0

Rui Santos has written a short tutorial about creating a standalone web server using an ESP8266 module that can toggle two LEDs through a web interface. He first flashed the ESP-01 module with NodeMCU so that he could program the ESP8266 chip with LUA script. After that it gets pretty easy to modify the ESP8266 firmware for WiFi connection, controlling the GPIOs, etc.

ESP8266 web server

ESP8266 web server

And don’t forget to check out our breadboard friendly adapters for ESP-01 modules that provides clear pin labels printed on the board to make prototyping with the ESP-01 module easier.

ESP-01 breadboard adapter

The post Setting up an ESP8266 web server appeared first on Embedded Lab.

Making a low cost portable phone charger

$
0
0

If you have any spare old phones with no use lying around, you may want to use their batteries to build this portable charger for your new smartphone. On the electronics side, this project only requires a LiPo battery charger module and a step-up voltage converter module, both of which can be bought for less than $5 on eBay.

DIY portable phone charger

DIY portable phone charger

The post Making a low cost portable phone charger appeared first on Embedded Lab.

ESP8266 WiFi controlled thermostat

Hackaday announces 2015 Hackaday Prize Competition

$
0
0

Hackaday has just announced their second Hackaday Prize competition today.

Hackaday Prize 2015

Hackaday Prize 2015

Last year The Hackaday Prize began a movement, this year we’re solving problems that matter to everyone. The creative energy and years of experience found in our huge community of Hackers, Designers, and Engineers is waiting to be unleashed. Let’s use that potential and move humanity forward. This doesn’t mean one giant solution. Thousands of people, each lifting one stone, moves mountains.

The post Hackaday announces 2015 Hackaday Prize Competition appeared first on Embedded Lab.

STM32 ADC

$
0
0

Most of us who have experienced 8-bit MCUs previously know how much important it is to have an Analogue-to-Digital Converter (ADC) built-in with a microcontroller. Apart from other hardware extensions unavailable in the early era microcontrollers, many former 8051 microcontroller users shifted primarily to more robust Atmel AVRs and Microchip PICs just for this important peripheral. I don’t feel it necessary to restate the advantages of having such a peripheral embedded in a micro. In traditional 8-bit MCUs aforementioned, the ADC block is somewhat incomplete and users have to work out tricky methods to solve certain problems. The ADC block of STM32 micros is one of the most advanced and sophisticated element to deal with in the entire STM32 arena. There are way too many options for this block in a STM32 micro. In this issue, we will explore this block. ADC Hardware Block Diagram

A Simplified Overview of STM32’s ADC Block

The block diagram shown above might look a bit complex at first but it still worth having a look. It shows us the interfaces that are related to the ADC block and some key features. It seems that some external interrupt pins and internal timer peripherals have some kind of relation with the ADC block. Well these hardware peripherals can trigger ADC conversion. The more we go forward, the more we explore. In short I would like to point out some key features of a typical STM32 ADC:

  • 12-bit successive approximation ADC.
  • Maximum ADC conversion rate is 1MHz and more than 2MHz in some STM32 families.
  • A/D conversion range: 0 – 3.6V DC.
  • ADC power supply operating range: 2.4V – 3.6V DC.
  • ADC input range: (V_Ref- and V_Ref+ pins are available only in some devices and packages).
  • Different modes of operation available for different measurement cases.
  • Dual mode conversion on devices with at least 2 ADC units. Some devices have more than 2 ADCs.
  • An integrated ADC sequencer ranks channels according to order.
  • Channel-by-channel sample time programmability. Having this feature is an advantage because we can set different sampling time for different channels and the ADC block need not to be stopped for making such changes.
  • Several external input channels are available. 10 external channels in STM32F103C8T6.
  • Two internal ADC channels available with ADC1 (aka. Master ADC). These channels are connected to an internal band gap voltage reference source and an on-chip temperature sensor.
  • An Analogue Watchdog (AWD) unit can detect if an ADC channel(s) is operating within a predefined ADC count window.
  • DMA-based fast data transferability.
  • Several hardware interrupts are available to flag important events.

The Basics of the STM32 ADC Block

The words mode and conversion in STM32 documentations are somewhat ambiguous unless you get them straight. Down to the basics, there are two modes of operation primarily. These are:

  • Independent mode. It is just as the typical ADC use. Each ADC unit is operating on its own and without any mutual dependency.
  • Dual mode. In this mode two ADCs are converted simultaneously or with some (literally negligible) delay. Two ADC units mutually working together as if they are a single unit.

A/D conversion can be:

  • Single Conversion. One sample conversion at a given instant.
  • Continuous Conversion. Non-stop sample collection and conversion.
  • Discontinuous Conversion. Sequential conversion of some channels in a group.
  • Scan Conversion. Sequential sampling and converting of an array of channels one after another.

To start A/D conversion, an ADC unit needs to be stimulated with a trigger signal:

  • Software Trigger. A/D conversion as per demand from coded program.
  • Hardware Trigger.  A/D conversion as per hardware events like external interrupts or timer events.

A/D conversions are done in groups. Group members are ADC channels and need not to be multiple channels. A group may consist of only one channel. Within these groups ADC channels are converted on a scheduled round-robin basis. The good stuff is the fact that unlike most micros we can program which channels belong in a group and with which sequence the A/D conversion is commenced. We can also set the sampling time for each channel separately. ADC groups can be categorized as follows:

  • Regular Group. A given fixed group of ADC channels are regularly converted. Up to 16 channels can be present in a regular group. A regular group is similar to a code running in the main loop. Regular Group Conversion
  • Injected Group. This group can interrupt a regular group conversion as it has a higher priority over the former. Up to 4 channels can be present in an injected group. When an injected group is present or injected over a regular group, all regular group conversions are halted temporarily. The injected group is processed first and then the regular groups are resumed. An injected group is analogous to having an interrupt over a running code. Alone without a regular group present, an injected group will behave like a regular group. Injected Group Conversion

ADC Clock

Shown below is the STM32F10x clock tree. Notice some areas are highlighted. Check them out because these are related to the ADC block. STM32 Clock Tree   We can clearly see that the ADC peripheral is connected to the APB2 peripheral bus. If you can recall from my earlier post on STM32’s clock options then you’ll remember that APB2 can run at 72MHz speed which is by the way the maximum operating speed for STM32F10x series MCUs. ST recommends that the ADC be feed with no more than a 14MHz clock. Thus we should make sure that ADC clock is in the range of 600 kHz to 14MHz. Since it takes about 14 cycles to process ADC data the maximum possible number of conversions per second is one million. I previously pointed out in one of my posts that MikroC’s IDE provided an easy to use tool to configure clocks for various STM32 peripheral buses. I recommend using this tool to configure peripheral clocks rather than coding them on your own. Pay attention particularly to APB1, APB2 and PCLK2 clocks when setting clock for the ADC block. Wrong settings will lead to unpredictable/erratic behaviours. MikroC Clock Configuration Tool Additionally you can use STM32CubeMX to verify if your clock configuration is okay. You can also use this software to check which I/O pins belong to which ADC channels and much more. However be aware, STM32CubeMX is not yet compatible with MikroC compiler. Personally I rarely use it. STM32CubeMX Clock Configuration Tool

The ADC Registers

There are several registers associated with each ADC unit. At first you may feel like getting lost in an ocean of 32-bit registers. However things aren’t so. Of these there are two ADC control registers called ADC_CR1 and ADC_CR2 that set ADC properties and mode of operation. There’s a status register, ADC_SR which flags important ADC events like end of a conversion, etc. These are the most important ones. I’m not going to explain every bit because they are well explained in the reference manual. ADC Registers The next set of registers are responsible for specifying channel sequence, sequence length, sampling times, offset and holding ADC conversion results. While dealing with the STM32 ADC block, you’ll come across the letter J before many stuffs. These stuffs signify that they are related to inJected group channels only. The rest of the stuffs are mostly related to regular groups and common uses. ADC_SQRx registers specify channel sequences. There are three of these registers and of them ADC_SQR1 also specifies the number of channels in a regular sequence. ADC_SQR3 through ADC_SQR1 are sequentially feed with channel numbers according to position in a given sequence. The injected group equivalent of the ADC_SQRx is the single ADC_JSQR register. ADC_SMPR1 and ADC_SMPR2 registers specify the sampling times of each channel. The sampling time is common to both regular and injected groups. ADC_LTR and ADC_HTR registers hold the lower and upper analogue watchdog ADC count limits respectively. Again these are common for both groups. ADC_JOFRx registers hold offset values for injected channels. The values in them are automatically subtracted from injected channel ADC conversion results. Lastly the ADC_DR and ADC_JDRx registers hold ADC conversion results for regular and injected groups respectively. ADC_DR is common for all regular group channels. There are four ADC_JDRx registers for four injected channels. Recall that there can be up to four ADC channels for an injected group. When coding there’s no need to follow any specific sequence for programming registers. Just make sure you coded for the required ones correctly.

My Version of Standard Peripheral Library

Unlike any of my previous posts on STM32, I’m moving a bit faster toward coding section rather than giving detailed explanations at all steps. To fully realize the ADC block’s various functionalities you need to code and experience it right after you have understood it. As I stated earlier, this block is a very complex one. It was not possible for me to give examples of every possible combination of operating modes. I’m here presenting only the basics needed to get started with STM32’s ADC. I would highly recommend readers to read this document from ST: http://www.st.com/web/en/resource/technical/document/application_note/CD00258017.pdf and read the reference manual for STM32F10x series for more details. Even after all these, this is a mega post. None of my previous posts on STM32 is so long. We all know that when it comes to handling 32-bit registers of 32-bit micros, programming turns into tiles of a tricky puzzle. Another big headache for programmers is the coding of registers with numeric jargons. I don’t know for sure if it is for these reasons or for some others that most STM32 users are SPL dependent. Almost no one codes STM32s at raw level. MikroC compiler simplifies things a lot of stuffs in several areas. It is easy to get started with it no matter if you are an expert or a novice. Personally I like MikroC compiler more than any other compiler for several reasons. I didn’t want to change it when I planned to play with STM32-based ARM micros. When I finally chose MikroC for ARM over other more popular and widely used compilers like Keil and Coocox, I was aware of SPL’s advantages and what I would go through next for selecting so. I wondered how to get rid of handling 32-bit registers effectively and at the same time develop a strategy to avoid memorizing the functional uses and names of different registers. I was looking for something similar to SPL that can be easily integrated with MikroC compiler. Personally I don’t like to use ST’s SPL and so I coded my version of SPL. From now on I’ll be sharing my version of SPL codes with my posts. I recommend readers to check them out before using them. Just for demo, check out my basic library for GPIO configuration:

//I/O Modes
 
#define input_mode                                                 0x00
#define output_mode_low_speed                                      0x02
#define output_mode_medium_speed                                   0x01
#define output_mode_high_speed                                     0x03
 
//I/O Configurations
 
#define GPIO_PP_output                                             0x00
#define GPIO_open_drain_output                                     0x04
#define AFIO_PP_output                                             0x08
#define AFIO_open_drain_output                                     0x0C
 
#define analog_input                                               0x00
#define floating_input                                             0x04
#define input_without_pull_resistors                               0x04
#define digital_input                                              0x08
 
//Pull Resistor Configurations
 
#define pull_down                                                  0x00
#define pull_up                                                    0x01
 
//Miscellaneous
 
#define enable                                                     0x01
#define disable                                                    0x00
 
//CR Register Configuration Macro
 
#define pin_configure_low(reg, pin_no, io_type)       do{reg &= (~(0xF << (pin_no << 2))); reg |= (io_type << (pin_no << 2));}while(0)
#define pin_configure_high(reg, pin_no, io_type)      do{reg &= (~(0xF << ((pin_no - 8) << 2))); reg |= (io_type << ((pin_no - 8) << 2));}while(0)
 
//Bitwise Operations for GPIOs
 
#define bit_set(reg, bit_value)                         (reg |= (1 << bit_value))
#define bit_clr(reg, bit_value)                         (reg &= (~(1 << bit_value)))
#define get_bits(reg, mask)                             (reg & mask)
#define get_input(reg, bit_value)                       (reg & (1 << bit_value))
 
//Pull Resistor Functions
 
#define pull_up_enable(reg, pin_no)                     (reg |= (1 << pin_no))
#define pull_down_enable(reg, pin_no)                   (reg &= (~(1 << pin_no)))
 
//GPIO Enabling Functions
 
#define enable_GPIOA(mode)                              RCC_APB2ENRbits.IOPAEN = mode
#define enable_GPIOB(mode)                              RCC_APB2ENRbits.IOPBEN = mode
#define enable_GPIOC(mode)                              RCC_APB2ENRbits.IOPCEN = mode
#define enable_GPIOD(mode)                              RCC_APB2ENRbits.IOPDEN = mode
#define enable_GPIOE(mode)                              RCC_APB2ENRbits.IOPEEN = mode
#define enable_GPIOF(mode)                              RCC_APB2ENRbits.IOPFEN = mode
#define enable_GPIOG(mode)                              RCC_APB2ENRbits.IOPGEN = mode

Now suppose you want to configure GPIO pins with this library. You can do so like this:

pin_configure_low(GPIOA_CRL, 6, (analog_input | input_mode));
pin_configure_high(GPIOB_CRH, 15, (GPIO_PP_output | output_mode_low_speed));   

Now you can see that you don’t need to handle 32-bit registers, bit positions and possible combinations of bit values. In fact sometimes you won’t have remember the names of registers at all. All of these things are replaced with something more meaningful and easy to understand. Unlike ST’s SPL you don’t even have to be a programming wiz to use these libraries. Please check the header files for better understanding. At this point I must specify that I single headedly developed this idea and the libraries themselves. It took several months to develop, code, test and implement them. I tried my level best to keep things error-free and as per reference manuals. There could be some unintentional flaws in them. Should you spot one please notify me for correction.

Coding and Explanations

The more we code, the better picture we’ll get about the STM32’s ADC. At some point, you’ll feel that the codes are self-explanatory. For demo purposes I used a STM32F103C8T6 micro embedded in a cheap STM32 test board. The basic connection looks like this: Connection Diagram All of the stuffs above except the connections for the ADC inputs and the LCD exist in most development boards. ADC pins are typically attached with GPIOA port.

MikroC ADC Library 

Obviously the first example is based on MikroC’s ADC library. MikroC compiler provided an easy to use ADC library. With this library you can do basic ADC readings and set up ADC units easily with minimum coding. MikroC’s built-in ADC library functions are as follows:

MikroC IDE’s help section explains the purpose of these function.

sbit LCD_RS at GPIOB_ODR.B1;
sbit LCD_EN at GPIOB_ODR.B2;
sbit LCD_D4 at GPIOB_ODR.B12;
sbit LCD_D5 at GPIOB_ODR.B13;
sbit LCD_D6 at GPIOB_ODR.B14;
sbit LCD_D7 at GPIOB_ODR.B15;
 
void setup();
unsigned int adc_avg(unsigned char no_of_samples, unsigned char channel);
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value);
float map(float v, float x_min, float x_max, float y_min, float y_max);
 
void main()
{
    unsigned char s = 0;
    register unsigned int adc_data = 0;
    float v = 0.0;
 
    setup();
 
    while(1)
    {
        for(s = 0; s <= 1; s++)
        {
              adc_data = adc_avg(10, s);
              v = map(adc_data, 0, 4095, 0, 3300);
              lcd_print(5, (s + 1), adc_data);
              lcd_print(11, (s + 1), v);
        }
 
        GPIOC_ODRbits.ODR13 ^= 1;
        delay_ms(600);
    };
}
 
void setup()
{
    GPIO_Clk_Enable(&GPIOA_BASE);
    GPIO_Clk_Enable(&GPIOB_BASE);
    GPIO_Clk_Enable(&GPIOC_BASE);
    GPIO_Config(&GPIOA_BASE, (_GPIO_PINMASK_0 | _GPIO_PINMASK_1), (_GPIO_CFG_MODE_ANALOG | _GPIO_CFG_PULL_NO));
    GPIO_Config(&GPIOC_BASE, _GPIO_PINMASK_13, (_GPIO_CFG_MODE_OUTPUT | _GPIO_CFG_SPEED_MAX | _GPIO_CFG_OTYPE_PP));
 
    ADC1_init();
    LCD_Init();
    LCD_Cmd(_LCD_CLEAR);
    LCD_Cmd(_LCD_CURSOR_OFF);
 
    lcd_out(1, 1, "A0:");
    lcd_out(2, 1, "A1:");
    lcd_out(1, 15, "mV");
    lcd_out(2, 15, "mV");
}
 
unsigned int adc_avg(unsigned char no_of_samples, unsigned char channel)
{
      unsigned long avg = 0;
      unsigned char samples = no_of_samples;
 
      while(samples > 0)
      {
          avg += ADC1_Get_Sample(channel);
          samples--;
      }
      avg /= no_of_samples;
 
      return avg;
}
 
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value)
{
     unsigned char tmp = 0;
 
     tmp = (value / 1000);
     lcd_chr(y_pos, x_pos, (tmp + 48));
     tmp = ((value / 100) % 10);
     lcd_chr_cp((tmp + 48));
     tmp = ((value / 10) % 10);
     lcd_chr_cp((tmp + 48));
     tmp = (value % 10);
     lcd_chr_cp((tmp + 48));
}
 
float map(float v, float x_min, float x_max, float y_min, float y_max)
{
    return (y_min + (((y_max - y_min)/(x_max - x_min)) * (v - x_min)));
}

In this demo I just showed how to use MikroC’s built-in ADC library functions to initialize, get average ADC counts from two ADC channels and show both ADC counts and voltage levels of each channel on a LCD display. Just a piece of cake. Not much to explain. MikroC Library (2) MikroC Library (1) Demo video link: https://www.youtube.com/watch?v=TwMTOLkWt-Q.

Analogue Watchdog

One of the cool feature of STM32 micros is the Analogue Watchdog (AWD) unit. In traditional micros, a programmer needs to add an if-else clause to monitor if the ADC readouts are within some predefined limits. There are, therefore, some coding and resource involved. The AWD unit of STM32 micros can monitor if an ADC channel or all ADC channels are exceeding or within predefine upper and lower ADC count limits. In this way we can effectively set an analogue voltage level window for the ADC block. Thus this unit allows users to easily implement signal level monitors, zero-crossing detectors, analogue comparators and many other stuffs. Thanks to ST for hardcoding this useful feature with the ADC block. ADC AWD Unit The upper and lower limit 12-bit values are stored in the High Threshold (HTR) and Lower Threshold (LTR) registers respectively. When the analogue input to be monitored is within these limits or inside guarded zone the AWD unit stays idle. When it is the other way around, the AWD wakes up. An AWD event interrupt can be generated if the AWD interrupt is enabled. AWD   Setting AWDSGL bit, AWDEN bit and JAWDEN bit in the ADC_CR1 select which channel types the AWD unit would monitor. The demo code for AWD is just same as the previous one but this time the AWD is also used to monitor all regular ADC channels. If the ADC readings exceed the guarded zone of 400 to 3695 ADC counts, the AWD fires. This mark starts to blink a LED connected to PC13, indicating an AWD event.

sbit LCD_RS at GPIOB_ODR.B1;
sbit LCD_EN at GPIOB_ODR.B2;
sbit LCD_D4 at GPIOB_ODR.B12;
sbit LCD_D5 at GPIOB_ODR.B13;
sbit LCD_D6 at GPIOB_ODR.B14;
sbit LCD_D7 at GPIOB_ODR.B15;
 
#define high_level       3695
#define low_level         400
 
void setup();
unsigned int adc_avg(unsigned char no_of_samples, unsigned char channel);
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value);
float map(float v, float x_min, float x_max, float y_min, float y_max);
 
void main()
{
    unsigned char s = 0;
    register unsigned int adc_data = 0;
    float v = 0.0;
 
    setup();
 
    while(1)
    {
        for(s = 0; s < 2; s++)
        {
              adc_data = adc_avg(20, s);
              v = map(adc_data, 0, 4095, 0, 3300);
              lcd_print(5, (s + 1), adc_data);
              lcd_print(11, (s + 1), v);
        }
 
        if(ADC1_SRbits.AWD == 1)
        {
            GPIOC_ODRbits.ODR13 ^= 1;
            ADC1_SRbits.AWD = 0;
        }
        else
        {
            GPIOC_ODRbits.ODR13 = 0;
        }
        delay_ms(400);
    };
}
 
void setup()
{
    GPIO_Clk_Enable(&GPIOA_BASE);
    GPIO_Clk_Enable(&GPIOB_BASE);
    GPIO_Clk_Enable(&GPIOC_BASE);
    GPIO_Config(&GPIOA_BASE, (_GPIO_PINMASK_0 | _GPIO_PINMASK_1), (_GPIO_CFG_MODE_ANALOG | _GPIO_CFG_PULL_NO));
    GPIO_Config(&GPIOC_BASE, _GPIO_PINMASK_13, (_GPIO_CFG_MODE_OUTPUT | _GPIO_CFG_SPEED_MAX | _GPIO_CFG_OTYPE_PP));
 
    ADC1_init();
    ADC1_LTR = low_level;
    ADC1_HTR = high_level;
    ADC1_CR1bits.AWDEN = 1;
 
    LCD_Init();
    LCD_Cmd(_LCD_CLEAR);
    LCD_Cmd(_LCD_CURSOR_OFF);
 
    lcd_out(1, 1, "A0:");
    lcd_out(2, 1, "A1:");
    lcd_out(1, 15, "mV");
    lcd_out(2, 15, "mV");
}
 
unsigned int adc_avg(unsigned char no_of_samples, unsigned char channel)
{
      register unsigned long avg = 0;
      unsigned char samples = no_of_samples;
 
      while(samples > 0)
      {
          avg += ADC1_Get_Sample(channel);
          samples--;
      }
      avg /= no_of_samples;
 
      return avg;
}
 
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value)
{
     unsigned char tmp = 0;
 
     tmp = (value / 1000);
     lcd_chr(y_pos, x_pos, (tmp + 48));
     tmp = ((value / 100) % 10);
     lcd_chr_cp((tmp + 48));
     tmp = ((value / 10) % 10);
     lcd_chr_cp((tmp + 48));
     tmp = (value % 10);
     lcd_chr_cp((tmp + 48));
}
 
float map(float v, float x_min, float x_max, float y_min, float y_max)
{
    return (y_min + (((y_max - y_min)/(x_max - x_min)) * (v - x_min)));
}

AWD (2) AWD (1)   Demo video link: https://www.youtube.com/watch?v=xMDIaEq-Q8o

Interrupt-Driven Single Channel Continuous Conversion Mode

Many of us may overlook one feature of STM32 micros and that is no other than the presence of an on-chip temperature sensor. All STM32 micros have this sensor. Although it is not recommended for precise temperature measurements, it can be used to estimate PCB or surrounding temperatures around a STM32 micro. The single channel continuous conversion mode is one of the most basic ADC mode. In this mode, once an ADC is triggered a single channel associated with it is continuously converted. In this demo, the single channel is the temperature sensor. The end of conversion interrupt is used to extract ADC conversion result after a conversion finishes. In this way, the CPU is not busy waiting for the ADC to finish conversion. After the end of a conversion, the entire process is repeated and this makes it continuous. From this example onwards you’ll mostly see me incorporating header file from my own SPL with my codes. Take this into account. In the reference manual for STM32F10x series, there’s an entire section dedicated to this temperature sensor. I suggest that you check it out first before trying to understand the code example.

#include "ADC.h"
#include "GPIO.h"
 
#define V25                1430
#define T_offset           17.5
 
sbit LCD_RS at GPIOB_ODR.B1;
sbit LCD_EN at GPIOB_ODR.B2;
sbit LCD_D4 at GPIOB_ODR.B12;
sbit LCD_D5 at GPIOB_ODR.B13;
sbit LCD_D6 at GPIOB_ODR.B14;
sbit LCD_D7 at GPIOB_ODR.B15;
 
register unsigned int adc_data = 0;
 
const char symbol[8] = {0x00, 0x06, 0x09, 0x09, 0x06, 0x00, 0x00, 0x00};
 
void setup();
void GPIO_init();
void ADC_init();
void CustomChar(unsigned char y_pos, unsigned char x_pos);
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value, unsigned char disp_type);
float map(float v, float x_min, float x_max, float y_min, float y_max);
 
void ADC_ISR() 
iv IVT_INT_ADC1_2 
ics ICS_AUTO
{
     adc_data = (ADC1_DR & 0x0FFF);
     bit_set(GPIOC_ODR, 13);
}
 
void main()
{
    register float t = 0.0;
 
    setup();
 
    while(1)
    {
        t = map(adc_data, 0, 4095, 0, 3300);
        t = (((V25 - t) / 4.3) + 25);
        t -=  T_offset;
        t *= 100;
 
        if(t <= 0)
        {
            t = 0;
        }
        if(t >= 9999)
        {
            t = 9999;
        }
 
        lcd_print(13, 1, adc_data, 1);
        lcd_print(12, 2, t, 0);
        
        bit_clr(GPIOC_ODR, 13);
        delay_ms(90);
    };
}
 
void setup()
{
    GPIO_init();
    ADC_init();
    LCD_Init();
 
    LCD_Cmd(_LCD_CLEAR);
    LCD_Cmd(_LCD_CURSOR_OFF);
 
    lcd_out(1, 1, "CH16:");
    lcd_out(2, 1, "T/ C:");
    CustomChar(2, 3);
}
 
void GPIO_init()
{
     enable_GPIOB(enable);
     enable_GPIOC(enable);
     pin_configure_high(GPIOC_CRH, 13, (GPIO_PP_output | output_mode_low_speed));
}
 
void ADC_init()
{
     ADC1_Enable();
     clr_ADC1_settings();
     set_ADC_mode(independent_mode);
     set_ADC1_data_alignment(right_alignment);
     set_ADC1_scan_conversion_mode(disable);
     set_ADC1_continuous_conversion_mode(enable);
     set_ADC1_external_trigger_regular_conversion_edge(SWSTART_trigger);
     set_ADC1_regular_number_of_conversions(1);
     set_ADC1_sample_time(sample_time_13_5_cycles, 16);
     set_ADC1_regular_sequence(1, 16);
     set_ADC1_reference_and_temperature_sensor(enable);
     set_ADC1_regular_end_of_conversion_interrupt(enable);
     NVIC_IntEnable(IVT_INT_ADC1_2);
     EnableInterrupts;
     ADC1_calibrate();
     start_ADC1();
}
 
void CustomChar(unsigned char y_pos, unsigned char x_pos)
{
    unsigned char i = 0;
    
    Lcd_Cmd(64);
    for (i = 0; i < 8; i += 1)
    {
        Lcd_Chr_CP(symbol[i]);
    }
    Lcd_Cmd(_LCD_RETURN_HOME);
    Lcd_Chr(y_pos, x_pos, 0);
}
 
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value, unsigned char disp_type)
{
     unsigned char tmp = 0;
 
     tmp = (value / 1000);
     lcd_chr(y_pos, x_pos, (tmp + 48));
     tmp = ((value / 100) % 10);
     lcd_chr_cp((tmp + 48));
     switch(disp_type)
     {
        case 0:
        {
            lcd_chr_cp(46);
            break;
        }
        case 1:
        {
            break;
        }
     }
     tmp = ((value / 10) % 10);
     lcd_chr_cp((tmp + 48));
     tmp = (value % 10);
     lcd_chr_cp((tmp + 48));
}
 
float map(float v, float x_min, float x_max, float y_min, float y_max)
{
    return (y_min + (((y_max - y_min)/(x_max - x_min)) * (v - x_min)));
}

Internal Temperature Sensor (1) Internal Temperature Sensor (2) Demo video link: https://www.youtube.com/watch?v=01sV-vgegtk

Interrupt-Driven Single Channel Single Conversion Mode

STM32 micros have an internal bandgap voltage reference source. The internal voltage reference source is not used by the ADC unlike other micros. It can, however, be used as a comparator input for zero-crossing detection. It may also be possible to use it to calibrate external readings or the V_Ref+ pin. The single channel single conversion mode is another basic ADC mode. In this mode a single channel (the internal reference source as in this example) is converted once when triggered. A single software trigger is used to invoke one ADC conversion. The end of conversion interrupt is used to notify the completion of ADC conversion and then the ADC stops. The only difference between the previous example and this one is how often the ADC conversion is done.

#include "ADC.h"
#include "GPIO.h"
 
sbit LCD_RS at GPIOB_ODR.B1;
sbit LCD_EN at GPIOB_ODR.B2;
sbit LCD_D4 at GPIOB_ODR.B12;
sbit LCD_D5 at GPIOB_ODR.B13;
sbit LCD_D6 at GPIOB_ODR.B14;
sbit LCD_D7 at GPIOB_ODR.B15;
 
register unsigned int adc_data = 0;
 
void setup();
void GPIO_init();
void ADC_init();
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value);
float map(float v, float x_min, float x_max, float y_min, float y_max);
 
void ADC_ISR() 
iv IVT_INT_ADC1_2 
ics ICS_AUTO
{
     adc_data = (ADC1_DR & 0x0FFF);
     bit_clr(GPIOC_ODR, 13);
}
 
void main()
{
    register float V = 0;
 
    setup();
 
    while(1)
    {
        set_ADC1_regular_conversions(enable);
        bit_set(GPIOC_ODR, 13);
        
        V = map(adc_data, 0, 4095, 0, 3300);
        lcd_print(13, 1, adc_data);
        lcd_print(13, 2, V);
        delay_ms(90);
    };
}
void setup()
{
    GPIO_init();
    ADC_init();
    LCD_Init();
 
    LCD_Cmd(_LCD_CLEAR);
    LCD_Cmd(_LCD_CURSOR_OFF);
 
    lcd_out(1, 1, "CH17:");
    lcd_out(2, 1, "Vref/mV:");
}
 
void GPIO_init()
{
     enable_GPIOB(enable);
     enable_GPIOC(enable);
     pin_configure_high(GPIOC_CRH, 13, (GPIO_PP_output | output_mode_low_speed));
}
 
void ADC_init()
{
     ADC1_Enable();
     clr_ADC1_settings();
     set_ADC_mode(independent_mode);
     set_ADC1_data_alignment(right_alignment);
     set_ADC1_scan_conversion_mode(disable);
     set_ADC1_continuous_conversion_mode(disable);
     set_ADC1_external_trigger_regular_conversion_edge(SWSTART_trigger);
     set_ADC1_regular_number_of_conversions(1);
     set_ADC1_sample_time(sample_time_13_5_cycles, 17);
     set_ADC1_regular_sequence(1, 17);
     set_ADC1_reference_and_temperature_sensor(enable);
     set_ADC1_regular_end_of_conversion_interrupt(enable);
     NVIC_IntEnable(IVT_INT_ADC1_2);
     EnableInterrupts();
     ADC1_calibrate();
     start_ADC1();
}
 
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value)
{
     unsigned char tmp = 0;
 
     tmp = (value / 1000);
     lcd_chr(y_pos, x_pos, (tmp + 48));
     tmp = ((value / 100) % 10);
     lcd_chr_cp((tmp + 48));
     tmp = ((value / 10) % 10);
     lcd_chr_cp((tmp + 48));
     tmp = (value % 10);
     lcd_chr_cp((tmp + 48));
}
 
float map(float v, float x_min, float x_max, float y_min, float y_max)
{
    return (y_min + (((y_max - y_min)/(x_max - x_min)) * (v - x_min)));
}

Internal Reference Source Demo video link: https://www.youtube.com/watch?v=BpiCa0ANWVI.

Single Channel Continuous Conversion Mode

This is similar to the third ADC example but this one is based on polling method rather than end of conversion interrupt-based one. This example is another basic example but I would prefer interrupt-based ADC conversion rather than this one as it will slow down other tasks.

#include "ADC.h"
#include "GPIO.h"
 
sbit LCD_RS at GPIOB_ODR.B1;
sbit LCD_EN at GPIOB_ODR.B2;
sbit LCD_D4 at GPIOB_ODR.B12;
sbit LCD_D5 at GPIOB_ODR.B13;
sbit LCD_D6 at GPIOB_ODR.B14;
sbit LCD_D7 at GPIOB_ODR.B15;
 
void setup();
void GPIO_init();
void ADC_init();
unsigned int read_ADC1();
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value);
float map(float v, float x_min, float x_max, float y_min, float y_max);
 
void main()
{
    unsigned int adc_data = 0;
    float V = 0.0;
 
    setup();
 
    while(1)
    {
        bit_set(GPIOC_ODR, 13);
        delay_ms(10);
        
        adc_data = read_ADC1();
        V = map(adc_data, 0, 4095, 0, 3300);
        lcd_print(13, 1, adc_data);
        lcd_print(13, 2, V);
 
        bit_clr(GPIOC_ODR, 13);
        delay_ms(90);
    };
}
 
void setup()
{
    GPIO_init();
    ADC_init();
    LCD_Init();
 
    LCD_Cmd(_LCD_CLEAR);
    LCD_Cmd(_LCD_CURSOR_OFF);
 
    lcd_out(1, 1, "CH02:");
    lcd_out(2, 1, "V/mV:");
}
 
void GPIO_init()
{           
     enable_GPIOA(enable);
     enable_GPIOB(enable);
     enable_GPIOC(enable);
     pin_configure_low(GPIOA_CRL, 2, (analog_input | input_mode));
     pin_configure_high(GPIOC_CRH, 13, (GPIO_PP_output | output_mode_low_speed));
}
 
void ADC_init()
{
     ADC1_Enable();
     clr_ADC1_settings();
     set_ADC_mode(independent_mode);
     set_ADC1_data_alignment(right_alignment);
     set_ADC1_scan_conversion_mode(disable);
     set_ADC1_continuous_conversion_mode(enable);
     set_ADC1_regular_number_of_conversions(1);
     set_ADC1_sample_time(sample_time_41_5_cycles, 2);
     set_ADC1_regular_sequence(1, 2);
     set_ADC1_external_trigger_regular_conversion_edge(SWSTART_trigger);
     ADC1_calibrate();
     start_ADC1();
}
 
unsigned int read_ADC1()
{
     while(ADC1_SRbits.EOC == 0);
     return (ADC1_DR & 0x0FFF);
}
 
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value)
{
     unsigned char tmp = 0;
 
     tmp = (value / 1000);
     lcd_chr(y_pos, x_pos, (tmp + 48));
     tmp = ((value / 100) % 10);
     lcd_chr_cp((tmp + 48));
     tmp = ((value / 10) % 10);
     lcd_chr_cp((tmp + 48));
     tmp = (value % 10);
     lcd_chr_cp((tmp + 48));
}
 
float map(float v, float x_min, float x_max, float y_min, float y_max)
{
    return (y_min + (((y_max - y_min)/(x_max - x_min)) * (v - x_min)));
}

Single Channel Continuous Conversion Mode (2) Single Channel Continuous Conversion Mode (1) Demo video link: https://www.youtube.com/watch?v=8fh1-rkpFAw.

Multi-Single Channel Single Conversion Mode

This example is similar to the 4th example of this post but instead of reading a single channel multiple single channels with different sampling times are read. It is useful when you need to read multiple channels without scanning them.

#include "ADC.h"
#include "GPIO.h"
 
sbit LCD_RS at GPIOB_ODR.B1;
sbit LCD_EN at GPIOB_ODR.B2;
sbit LCD_D4 at GPIOB_ODR.B12;
sbit LCD_D5 at GPIOB_ODR.B13;
sbit LCD_D6 at GPIOB_ODR.B14;
sbit LCD_D7 at GPIOB_ODR.B15;
 
void setup();
void GPIO_init();
void ADC_init();
unsigned int read_ADC1(unsigned char channel, unsigned char sample_time);
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value);
                   
void main()
{
    unsigned int ch_a = 0;
    unsigned int ch_b = 0;
    unsigned int ch_c = 0;
 
    setup();
 
    while(1)
    {
        ch_a = read_ADC1(1, sample_time_239_5_cycles);
        ch_b = read_ADC1(3, sample_time_1_5_cycles);
        ch_c = read_ADC1(2, sample_time_28_5_cycles);
        
        lcd_print(1, 2, ch_a);                  
        lcd_print(7, 2, ch_b);
        lcd_print(13, 2, ch_c);
 
        bit_set(GPIOC_ODR, 13);
        delay_ms(10);
        bit_clr(GPIOC_ODR, 13);
        delay_ms(90);
    };
}
 
void setup()
{
    GPIO_init();
    ADC_init();
    LCD_Init();
 
    LCD_Cmd(_LCD_CLEAR);
    LCD_Cmd(_LCD_CURSOR_OFF);
 
    lcd_out(1, 1, "CH01  CH03  CH02");
}
 
void GPIO_init()
{
     enable_GPIOA(enable);
     enable_GPIOB(enable);
     enable_GPIOC(enable);
     pin_configure_low(GPIOA_CRL, 1, (analog_input | input_mode));
     pin_configure_low(GPIOA_CRL, 2, (analog_input | input_mode));
     pin_configure_low(GPIOA_CRL, 3, (analog_input | input_mode));
     pin_configure_high(GPIOC_CRH, 13, (GPIO_PP_output | output_mode_low_speed));
}
 
void ADC_init()
{
     ADC1_Enable();
     clr_ADC1_settings();
     set_ADC_mode(independent_mode);
     set_ADC1_data_alignment(right_alignment);
     set_ADC1_continuous_conversion_mode(disable);
     set_ADC1_regular_number_of_conversions(1);
     set_ADC1_external_trigger_regular_conversion_edge(SWSTART_trigger);
     ADC1_calibrate();
     start_ADC1();
}
 
unsigned int read_ADC1(unsigned char channel, unsigned char sample_time)
{
     ADC1_JSQR = 0x00000000;
     ADC1_SQR1 = 0x00000000;
     ADC1_SQR2 = 0x00000000;
     ADC1_SQR3 = 0x00000000;
     ADC1_SMPR1 = 0x00000000;
     ADC1_SMPR2 = 0x00000000;
     set_ADC1_regular_sequence(1, channel);
     set_ADC1_sample_time(sample_time, channel);
     set_ADC1_regular_conversions(enable);
     while(ADC1_SRbits.EOC == reset);
     return (0x0FFF & ADC1_DR);
}
 
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value)
{
     unsigned char tmp = 0;
 
     tmp = (value / 1000);
     lcd_chr(y_pos, x_pos, (tmp + 48));
     tmp = ((value / 100) % 10);
     lcd_chr_cp((tmp + 48));
     tmp = ((value / 10) % 10);
     lcd_chr_cp((tmp + 48));
     tmp = (value % 10);
     lcd_chr_cp((tmp + 48));
}

Multiple Injected Channels in Scan Conversion Mode (2) Multi-Single Channel Single Conversion Mode (1) Demo video link: https://www.youtube.com/watch?v=hcGHQFFkdfs.

External Interrupt Triggered Single Channel Single Conversion Mode

Up till now the ADC trigger was given from the software end. However STM32 micros can perform ADC conversions when triggered externally by some other hardware. In this example, an external interrupt pin is used to trigger a single ADC conversion. After completing an ADC conversion the ADC halts. We can also use timer events as trigger sources too.

#include "ADC.h"
#include "GPIO.h"
#include "AFIO.h"
#include "Ex_Int.h"
 
sbit LCD_RS at GPIOB_ODR.B1;
sbit LCD_EN at GPIOB_ODR.B2;
sbit LCD_D4 at GPIOB_ODR.B12;
sbit LCD_D5 at GPIOB_ODR.B13;
sbit LCD_D6 at GPIOB_ODR.B14;
sbit LCD_D7 at GPIOB_ODR.B15;
 
register unsigned int adc_data = 0;
 
void setup();
void GPIO_init();
void ADC_init();
void interrupts_init();
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value);
float map(float v, float x_min, float x_max, float y_min, float y_max);
 
void void EXTI11_ISR()
iv IVT_INT_EXTI15_10
ics ICS_AUTO
{
    if(read_pending_reg(11) != 0)
    {
        bit_set(GPIOC_ODR, 13);
        delay_ms(10);
        bit_clr(GPIOC_ODR, 13);
        pending_clr(11);
    }
}
 
void void ADC1_ISR()
iv IVT_INT_ADC1_2
ics ICS_AUTO
{
    adc_data = (ADC1_DR & 0x0FFF);
}
 
void main()
{
    float v = 0;
 
    setup();
 
    while(1)
    {
        v = map(adc_data, 0, 4095, 0, 3300);
        lcd_print(13, 1, adc_data);
        lcd_print(13, 2, v);
    };
}
 
void setup()
{
    GPIO_init();
    ADC_init();
    LCD_Init();
    interrupts_init();
 
    LCD_Cmd(_LCD_CLEAR);
    LCD_Cmd(_LCD_CURSOR_OFF);
 
    lcd_out(1, 1, "CH01:");
    lcd_out(2, 1, "V/mV:");
}
 
void GPIO_init()
{
     enable_GPIOA(enable);
     enable_GPIOB(enable);
     enable_GPIOC(enable);
 
     pin_configure_low(GPIOA_CRL, 1, (analog_input | input_mode));
     pin_configure_high(GPIOC_CRH, 13, (GPIO_PP_output | output_mode_medium_speed));
     pin_configure_high(GPIOB_CRH, 11, (digital_input | input_mode));
     pull_up_enable(GPIOB_ODR, 11);
}
 
void ADC_init()
{
     ADC1_Enable();
     clr_ADC1_settings();
     set_ADC_mode(independent_mode);
     set_ADC1_data_alignment(right_alignment);
     set_ADC1_scan_conversion_mode(disable);
     set_ADC1_continuous_conversion_mode(disable);
     set_ADC1_sample_time(sample_time_71_5_cycles, 1);
     set_ADC1_external_trigger_regular_conversion_edge(EXTI_11_trigger);
     set_ADC1_regular_number_of_conversions(1);
     set_ADC1_regular_sequence(1, 1);
     set_ADC1_regular_end_of_conversion_interrupt(enable);
     ADC1_calibrate();
     start_ADC1();
}
 
void interrupts_init()
{
     AFIO_enable(enable);
     falling_edge_selector(11);
     set_EXTI8_11(11, PB_pin);
     interrupt_mask(11);
     NVIC_IntEnable(IVT_INT_EXTI15_10);
     NVIC_IntEnable(IVT_INT_ADC1_2);
     EnableInterrupts();
}
 
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value)
{
     unsigned char tmp = 0;
 
     tmp = (value / 1000);
     lcd_chr(y_pos, x_pos, (tmp + 48));
     tmp = ((value / 100) % 10);
     lcd_chr_cp((tmp + 48));
     tmp = ((value / 10) % 10);
     lcd_chr_cp((tmp + 48));
     tmp = (value % 10);
     lcd_chr_cp((tmp + 48));
}
 
float map(float v, float x_min, float x_max, float y_min, float y_max)
{
    return (y_min + (((y_max - y_min)/(x_max - x_min)) * (v - x_min)));
}

External Interrupt Triggered Single Channel Single Conversion Mode (2) External Interrupt Triggered Single Channel Single Conversion Mode (1) Demo video link: https://www.youtube.com/watch?v=dT5wkXKs42E.

Single Injected Channel Continuous Conversion Mode

Previously I stated that injected groups are no different than regular groups when used alone. They will behave like regular groups if regular groups are absent. In this demo there’s no regular group channel and you’ll see that the injected group channel is behaving like a regular group channel.

#include "ADC.h"
#include "GPIO.h"
 
sbit LCD_RS at GPIOB_ODR.B1;
sbit LCD_EN at GPIOB_ODR.B2;
sbit LCD_D4 at GPIOB_ODR.B12;
sbit LCD_D5 at GPIOB_ODR.B13;
sbit LCD_D6 at GPIOB_ODR.B14;
sbit LCD_D7 at GPIOB_ODR.B15;
 
void setup();
void GPIO_init();
void ADC_init();
unsigned int read_ADC1();
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value);
float map(float v, float x_min, float x_max, float y_min, float y_max);
 
void main()
{
    register unsigned int adc_data = 0;
    float V = 0.0;
 
    setup();
 
    while(1)
    {
        bit_set(GPIOC_ODR, 13);
        delay_ms(10);
        
        adc_data = read_ADC1();
        V = map(adc_data, 0, 4095, 0, 3300);
        lcd_print(13, 1, adc_data);
        lcd_print(13, 2, V);
 
        bit_clr(GPIOC_ODR, 13);
        delay_ms(90);
    };
}
 
void setup()
{
    GPIO_init();
    ADC_init();
    LCD_Init();
 
    LCD_Cmd(_LCD_CLEAR);
    LCD_Cmd(_LCD_CURSOR_OFF);
 
    lcd_out(1, 1, "CH03:");
    lcd_out(2, 1, "V/mV:");
}
 
void GPIO_init()
{
     enable_GPIOA(enable);
     enable_GPIOB(enable);
     enable_GPIOC(enable);
     pin_configure_low(GPIOA_CRL, 3, (analog_input | input_mode));
     pin_configure_high(GPIOC_CRH, 13, (GPIO_PP_output | output_mode_low_speed));
}
 
void ADC_init()
{
     ADC1_Enable();
     clr_ADC1_settings();
     set_ADC_mode(independent_mode);
     set_ADC1_data_alignment(right_alignment);
     set_ADC1_scan_conversion_mode(disable);
     set_ADC1_continuous_conversion_mode(enable);
     set_ADC1_injected_number_of_conversions(1);
     set_ADC1_sample_time(sample_time_239_5_cycles, 3);
     set_ADC1_injected_sequence(4, 3);
     set_ADC1_external_trigger_injected_conversion_edge(JSWSTART_trigger);
     ADC1_calibrate();
     start_ADC1();
}
 
unsigned int read_ADC1()
{
     set_ADC1_injected_conversions(enable);
     while(ADC1_SRbits.JEOC == 0);
     return (ADC1_JDR1 & 0x0FFF);
}
 
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value)
{
     unsigned char tmp = 0;
 
     tmp = (value / 1000);
     lcd_chr(y_pos, x_pos, (tmp + 48));
     tmp = ((value / 100) % 10);
     lcd_chr_cp((tmp + 48));
     tmp = ((value / 10) % 10);
     lcd_chr_cp((tmp + 48));
     tmp = (value % 10);
     lcd_chr_cp((tmp + 48));
}
 
float map(float v, float x_min, float x_max, float y_min, float y_max)
{
    return (y_min + (((y_max - y_min)/(x_max - x_min)) * (v - x_min)));

Single Channel Continuous Conversion Mode (2) Single Channel Continuous Conversion Mode (1) Demo video link: https://www.youtube.com/watch?v=R1MuJaWrfn8

Multiple Injected Channels in Discontinuous Conversion Mode 

In discontinuous mode, each injected group channel is converted according to a sequence and after each channel conversion an injected channel end of conversion flag is set. Since there can be four channels in an injected group, their conversion results are stored in four separate data registers. We just need to read those data registers when valid data values are ready. To use discontinuous mode we must specify the number of channels in the discontinuous mode apart from how many channels that are actually present in the group.

#include "ADC.h"
#include "GPIO.h"
 
sbit LCD_RS at GPIOB_ODR.B1;
sbit LCD_EN at GPIOB_ODR.B2;
sbit LCD_D4 at GPIOB_ODR.B12;
sbit LCD_D5 at GPIOB_ODR.B13;
sbit LCD_D6 at GPIOB_ODR.B14;
sbit LCD_D7 at GPIOB_ODR.B15;
 
void setup();
void GPIO_init();
void ADC_init();
void read_ADC1_injected(unsigned int temp_data[3]);
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value);
 
void main()
{
    unsigned int channel_data[3];
 
    setup();
 
    while(1)
    {
        read_ADC1_injected(channel_data);
        lcd_print(1, 2, channel_data[0]);
        lcd_print(7, 2, channel_data[1]);
        lcd_print(13, 2, channel_data[2]);
 
        bit_set(GPIOC_ODR, 13);
        delay_ms(10);
        bit_clr(GPIOC_ODR, 13);
        delay_ms(90);
    };
}
 
void setup()
{
    GPIO_init();
    ADC_init();
    LCD_Init();
 
    LCD_Cmd(_LCD_CLEAR);
    LCD_Cmd(_LCD_CURSOR_OFF);
 
    lcd_out(1, 1, "CH00  CH01  CH02");
}
 
void GPIO_init()
{
     enable_GPIOA(enable);
     enable_GPIOB(enable);
     enable_GPIOC(enable);
     pin_configure_low(GPIOA_CRL, 0, (analog_input | input_mode));
     pin_configure_low(GPIOA_CRL, 1, (analog_input | input_mode));
     pin_configure_low(GPIOA_CRL, 2, (analog_input | input_mode));
     pin_configure_high(GPIOC_CRH, 13, (GPIO_PP_output | output_mode_low_speed));
}
 
void ADC_init()
{
     ADC1_Enable();
     clr_ADC1_settings();
     set_ADC_mode(independent_mode);
     set_ADC1_data_alignment(right_alignment);
     set_ADC1_scan_conversion_mode(disable);
     set_ADC1_continuous_conversion_mode(disable);
     set_ADC1_injected_number_of_conversions(3);
     set_ADC1_sample_time(sample_time_28_5_cycles, 0);
     set_ADC1_sample_time(sample_time_41_5_cycles, 1);
     set_ADC1_sample_time(sample_time_13_5_cycles, 2);
     set_ADC1_injected_sequence(1, 0);
     set_ADC1_injected_sequence(2, 2);
     set_ADC1_injected_sequence(3, 1);
     set_ADC1_number_of_discontinuous_conversions(3);
     set_ADC1_external_trigger_injected_conversion_edge(JSWSTART_trigger);
     set_ADC1_discontinuous_conversion_mode_in_injected_mode(enable);
     ADC1_calibrate();
     start_ADC1();
}
 
void read_ADC1_injected(unsigned int temp_data[3])
{
     set_ADC1_injected_conversions(enable);
     while(ADC1_SRbits.JEOC == 0);
     temp_data[0] = (ADC1_JDR3 & 0x0FFF);
     set_ADC1_injected_conversions(enable);
     while(ADC1_SRbits.JEOC == 0);
     temp_data[1] = (ADC1_JDR2 & 0x0FFF);
     set_ADC1_injected_conversions(enable);
     while(ADC1_SRbits.JEOC == 0);
     temp_data[2] = (ADC1_JDR1 & 0x0FFF);
}
 
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value)
{
     unsigned char tmp = 0;
 
     tmp = (value / 1000);
     lcd_chr(y_pos, x_pos, (tmp + 48));
     tmp = ((value / 100) % 10);
     lcd_chr_cp((tmp + 48));
     tmp = ((value / 10) % 10);
     lcd_chr_cp((tmp + 48));
     tmp = (value % 10);
     lcd_chr_cp((tmp + 48));
}

Multiple Injected Channels in Discontinuous Conversion Mode  (2) Multiple Injected Channels in Discontinuous Conversion Mode  (1)Demo video link: https://www.youtube.com/watch?v=YwymJs8LlNU.

Multiple Injected Channels in Scan Conversion Mode

Scan mode is another interesting mode. In scan mode all ADC channels are scanned as per sequence. One conversion is performed for each channel. After having completed a scan an injected channel end of conversion flag is set. If interrupt is used then an end of conversion interrupt is issued. Scan mode is useful when we need to monitor several ADC channels, for example temperature and relative humidity. There’s a word of caution for using scan mode. STM32 reference manual states it like this:

Unlike a regular conversion sequence, if JL[1:0] length is less than four, the channels
are converted in a sequence starting from (4-JL). Example: ADC_JSQR[21:0] = 10
00011 00011 00111 00010 means that a scan conversion will convert the following
channel sequence: 7, 3, 3. (not 2, 7, 3)

This is why you’ll see some anomalies in this code example:

set_ADC2_injected_sequence(3, 4);
set_ADC2_injected_sequence(4, 6);
 
void ADC1_2_ISR() 
iv IVT_INT_ADC1_2 
ics ICS_AUTO 
{
     ADC2_SRbits.JEOC = 0;
     channel_data[0] = (ADC2_JDR1 & 0x0FFF);
     channel_data[1] = (ADC2_JDR2 & 0x0FFF);
 

Notice the sequence order and how the data is actually extracted. Keep this in mind while coding for injected groups in scan mode. Also note that in this code I used ADC2 instead of ADC1. This is not mandatory. I did so just to show that you can also use ADC2 rather than ADC1. For regular group channels the scan mode is DMA dependent because for regular group channels there’s only one data register for all the regular channels. I didn’t find a good way to avoid using the DMA unit. Of course using DMA is a much more clever approach. Since I won’t be discussing about DMA in this issue, I’m skipping this part for future.

#include "ADC.h"
#include "GPIO.h"
 
sbit LCD_RS at GPIOB_ODR.B1;
sbit LCD_EN at GPIOB_ODR.B2;
sbit LCD_D4 at GPIOB_ODR.B12;
sbit LCD_D5 at GPIOB_ODR.B13;
sbit LCD_D6 at GPIOB_ODR.B14;
sbit LCD_D7 at GPIOB_ODR.B15;
 
unsigned int channel_data[2];         
 
void setup();
void GPIO_init();
void ADC_init();
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value);
 
void ADC1_2_ISR() 
iv IVT_INT_ADC1_2 
ics ICS_AUTO 
{
     ADC2_SRbits.JEOC = 0;
     channel_data[0] = (ADC2_JDR1 & 0x0FFF);
     channel_data[1] = (ADC2_JDR2 & 0x0FFF);
}   
 
void main()
{
    setup();
 
    while(1)
    {
        bit_set(GPIOC_ODR, 13);
        set_ADC2_injected_conversions(enable);
        delay_ms(10);
        lcd_print(1, 2, channel_data[0]);
        lcd_print(13, 2, channel_data[1]);
        bit_clr(GPIOC_ODR, 13);
        delay_ms(90);
    };
}
 
void setup()                            
{
    GPIO_init();
    LCD_Init();
    ADC_init();
 
    LCD_Cmd(_LCD_CLEAR);
    LCD_Cmd(_LCD_CURSOR_OFF);
 
    lcd_out(1, 1, "CH04        CH06");
}
 
void GPIO_init()
{
     enable_GPIOA(enable);
     enable_GPIOB(enable);
     enable_GPIOC(enable);
     pin_configure_low(GPIOA_CRL, 4, (analog_input | input_mode));
     pin_configure_low(GPIOA_CRL, 6, (analog_input | input_mode));
     pin_configure_high(GPIOC_CRH, 13, (GPIO_PP_output | output_mode_low_speed));
}
 
void ADC_init()
{
     ADC2_Enable();
     clr_ADC2_settings();
     set_ADC_mode(independent_mode);
     set_ADC2_data_alignment(right_alignment);
     set_ADC2_scan_conversion_mode(enable);
     set_ADC2_continuous_conversion_mode(disable);
     set_ADC2_sample_time(sample_time_1_5_cycles, 4);
     set_ADC2_sample_time(sample_time_13_5_cycles, 6);
     set_ADC2_external_trigger_injected_conversion_edge(JSWSTART_trigger);
     set_ADC2_injected_number_of_conversions(2);
     set_ADC2_injected_sequence(3, 4);
     set_ADC2_injected_sequence(4, 6);
     set_ADC2_injected_end_of_conversion_interrupt(enable);
     NVIC_IntEnable(IVT_INT_ADC1_2);
     EnableInterrupts();
     ADC2_calibrate();
     start_ADC2();
}
 
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value)
{
     unsigned char tmp = 0;
 
     tmp = (value / 1000);
     lcd_chr(y_pos, x_pos, (tmp + 48));
     tmp = ((value / 100) % 10);
     lcd_chr_cp((tmp + 48));
     tmp = ((value / 10) % 10);
     lcd_chr_cp((tmp + 48));
     tmp = (value % 10);
     lcd_chr_cp((tmp + 48));
}

Multiple Injected Channels in Scan Conversion Mode (2) Multiple Injected Channels in Scan Conversion Mode (1)Demo video link: https://www.youtube.com/watch?v=Mjax8dwClGE.

Regular Group Channels Discontinuous in Mode

This example is similar to the one I coded for injected group channels. It is somewhat an alternative of scan mode for regular group though technically they are not same. You can use this mode to monitor several ADC channels in a systematic order.

#include "ADC.h"
#include "GPIO.h"
 
sbit LCD_RS at GPIOB_ODR.B1;
sbit LCD_EN at GPIOB_ODR.B2;
sbit LCD_D4 at GPIOB_ODR.B12;
sbit LCD_D5 at GPIOB_ODR.B13;
sbit LCD_D6 at GPIOB_ODR.B14;
sbit LCD_D7 at GPIOB_ODR.B15;
 
unsigned char channel_no = 0;
unsigned int ch_data[3] = {0x0000, 0x0000, 0x0000};
 
void setup();
void GPIO_init();
void ADC_init();
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value);
 
void ADC1_2_ISR() 
iv IVT_INT_ADC1_2 
ics ICS_AUTO 
{
    ADC2_SRbits.EOC = 0;
    ch_data[channel_no] = (ADC2_DR & 0x0FFF);
    channel_no++;
}
 
void main()
{
    unsigned char s = 0;
    
    setup();
 
    while(1)
    {
        bit_set(GPIOC_ODR, 13);
        set_ADC2_regular_conversions(enable);
        while(channel_no < 3);
        channel_no = 0;
        for(s = 0 ; s < 3; s++)
        {
            lcd_print(((s * 6) + 1), 2, ch_data[s]);
        }
        bit_clr(GPIOC_ODR, 13);
    };
}
 
void setup()
{
    GPIO_init();
    ADC_init();
    LCD_Init();
 
    LCD_Cmd(_LCD_CLEAR);
    LCD_Cmd(_LCD_CURSOR_OFF);
 
    lcd_out(1, 1, "CH02  CH00  CH01");
}
 
void GPIO_init()
{
     enable_GPIOA(enable);
     enable_GPIOB(enable);
     enable_GPIOC(enable);
     pin_configure_low(GPIOA_CRL, 0, (analog_input | input_mode));
     pin_configure_low(GPIOA_CRL, 1, (analog_input | input_mode));
     pin_configure_low(GPIOA_CRL, 2, (analog_input | input_mode));
     pin_configure_high(GPIOC_CRH, 13, (GPIO_PP_output | output_mode_low_speed));
}
 
void ADC_init()
{
     ADC2_Enable();
     clr_ADC2_settings();
     set_ADC_mode(independent_mode);
     set_ADC2_data_alignment(right_alignment);
     set_ADC2_scan_conversion_mode(disable);
     set_ADC2_continuous_conversion_mode(disable);
     set_ADC2_regular_number_of_conversions(3);
     set_ADC2_number_of_discontinuous_conversions(3);
     set_ADC2_sample_time(sample_time_41_5_cycles, 0);
     set_ADC2_sample_time(sample_time_13_5_cycles, 1);
     set_ADC2_sample_time(sample_time_28_5_cycles, 2);
     set_ADC2_regular_sequence(1, 0);
     set_ADC2_regular_sequence(2, 1);
     set_ADC2_regular_sequence(3, 2);
     set_ADC2_external_trigger_regular_conversion_edge(SWSTART_trigger);
     set_ADC2_discontinuous_conversion_mode_in_regular_mode(enable);
     set_ADC2_regular_end_of_conversion_interrupt(enable);
     NVIC_IntEnable(IVT_INT_ADC1_2);
     EnableInterrupts();
     ADC2_calibrate();
     start_ADC2();
}
 
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value)
{
     unsigned char tmp = 0;
 
     tmp = (value / 1000);
     lcd_chr(y_pos, x_pos, (tmp + 48));
     tmp = ((value / 100) % 10);
     lcd_chr_cp((tmp + 48));
     tmp = ((value / 10) % 10);
     lcd_chr_cp((tmp + 48));
     tmp = (value % 10);
     lcd_chr_cp((tmp + 48));
}

Regular Group Channels Discontinuous in Mode (1) Regular Group Channels Discontinuous in Mode (2) Demo video link: https://www.youtube.com/watch?v=pz-y_W2Lxr8.

Dual Conversion Mode

One of the cool feature of STM32’s ADC is its ability to simultaneous convert two ADC channels. This ability is sometimes highly demanded in some applications. For example in an energy meter you’ll need to measure both voltage and current simultaneous if you want precision. Applications like such demand the use of dual ADC mode. Please note that this feature is available only in those STM32 micros that have at least two ADC units. Fortunately there are only a few STM32 micros that have only one ADC unit. ST provided several sub modes for dual ADC modes but none attracted me much because those seemed to my achievable using one ADC and some programming tricks. It’s just my instinct. I could be wrong. To use dual ADC mode, we need to configure two ADC units separately. They’ll have some common setups. The individual ADCs are configured as such that as if they are configured for single ADC operation. The common settings are just ADC mode of operation setting and interrupt configuration. The results of ADC conversions of both channels are stored in ADC1_DR register Please note that ST recommends that we set the DMA bit for ADC1 even if we don’t use the DMA block itself.

#include "ADC.h"
#include "GPIO.h"
 
sbit LCD_RS at GPIOB_ODR.B1;
sbit LCD_EN at GPIOB_ODR.B2;
sbit LCD_D4 at GPIOB_ODR.B12;
sbit LCD_D5 at GPIOB_ODR.B13;
sbit LCD_D6 at GPIOB_ODR.B14;
sbit LCD_D7 at GPIOB_ODR.B15;
 
register unsigned long adc_data = 0;
 
void setup();
void GPIO_init();
void ADC_init();
void setup_ADC1();
void setup_ADC2();
void setup_common_ADC_settings();
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value);
 
void ADC1_2_ISR() 
iv IVT_INT_ADC1_2 
ics ICS_AUTO 
{
    ADC1_SRbits.EOC = 0;
    ADC2_SRbits.EOC = 0;
    adc_data = adc1_dr;
    bit_set(GPIOC_ODR, 13);
}
 
void main()
{
    unsigned int adc1_data = 0;
    unsigned int adc2_data = 0;
    
    setup();
 
    while(1)
    {
        set_ADC1_regular_conversions(enable);
        
        adc1_data = (adc_data & 0x00000FFF);
        adc2_data = ((adc_data & 0x0FFF0000) >> 16);
        
        lcd_print(1, 2, adc1_data);
        lcd_print(13, 2, adc2_data);
 
        bit_clr(GPIOC_ODR, 13);
        delay_ms(90);
    };
}
 
void setup()
{
    GPIO_init();
    ADC_init();
    LCD_Init();
 
    LCD_Cmd(_LCD_CLEAR);
    LCD_Cmd(_LCD_CURSOR_OFF);
 
    lcd_out(1, 1, "CH00        CH01");
}
 
void GPIO_init()
{
     enable_GPIOA(enable);
     enable_GPIOB(enable);
     enable_GPIOC(enable);
     pin_configure_low(GPIOA_CRL, 0, (analog_input | input_mode));
     pin_configure_low(GPIOA_CRL, 1, (analog_input | input_mode));
     pin_configure_high(GPIOC_CRH, 13, (GPIO_PP_output | output_mode_low_speed));
}
 
void ADC_init()
{
     setup_ADC1();
     setup_ADC2();
     setup_common_ADC_settings();
}
 
void setup_ADC1()
{
     ADC1_Enable();
     clr_ADC1_settings();
     set_ADC1_data_alignment(right_alignment);
     set_ADC1_scan_conversion_mode(disable);
     set_ADC1_continuous_conversion_mode(disable);
     set_ADC1_sample_time(sample_time_41_5_cycles, 0);
     set_ADC1_external_trigger_regular_conversion_edge(SWSTART_trigger);
     set_ADC1_regular_number_of_conversions(1);
     set_ADC1_regular_sequence(1, 0);
     set_ADC1_regular_end_of_conversion_interrupt(enable);
     ADC1_calibrate();
     start_ADC1();
}
 
void setup_ADC2()
{
     ADC2_Enable();
     clr_ADC2_settings();
     set_ADC2_data_alignment(right_alignment);
     set_ADC2_scan_conversion_mode(disable);
     set_ADC2_continuous_conversion_mode(disable);
     set_ADC2_sample_time(sample_time_41_5_cycles, 1);
     set_ADC2_external_trigger_regular_conversion_edge(SWSTART_trigger);
     set_ADC2_regular_number_of_conversions(1);
     set_ADC2_regular_sequence(1, 1);
     set_ADC2_regular_end_of_conversion_interrupt(enable);
     ADC2_calibrate();
     start_ADC2();                       
}
 
void setup_common_ADC_settings()
{
     set_ADC1_DMA(enable);
     set_ADC_mode(regular_simultaneous_mode_only);
     NVIC_IntEnable(IVT_INT_ADC1_2);
     EnableInterrupts();
}
 
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value)
{
     unsigned char tmp = 0;
 
     tmp = (value / 1000);
     lcd_chr(y_pos, x_pos, (tmp + 48));
     tmp = ((value / 100) % 10);
     lcd_chr_cp((tmp + 48));
     tmp = ((value / 10) % 10);
     lcd_chr_cp((tmp + 48));
     tmp = (value % 10);
     lcd_chr_cp((tmp + 48));
}

Dual ADC ModeDemo video link: https://www.youtube.com/watch?v=sSGJUkX-mSg.

Injected Group vs. Regular Group

Till now we saw regular and injected groups independently. In this example we’ll see them together. There are two ADC channels. One of them belongs to regular group while the other belongs to injected group. We’ll see that the injected channel will override the conversion of regular channel. Both of these groups are triggered externally. A flashing LED is indicated which conversion is going on. Please check the video for better understanding.

#include "ADC.h"
#include "GPIO.h"
#include "AFIO.h"
#include "Ex_Int.h"
 
sbit LCD_RS at GPIOB_ODR.B1;
sbit LCD_EN at GPIOB_ODR.B2;
sbit LCD_D4 at GPIOB_ODR.B12;
sbit LCD_D5 at GPIOB_ODR.B13;
sbit LCD_D6 at GPIOB_ODR.B14;
sbit LCD_D7 at GPIOB_ODR.B15;
 
register unsigned int regular_adc_data = 0;
register unsigned int injected_adc_data = 0;
 
void setup();
void GPIO_init();
void ADC_init();
void exeternal_interrupt_init();
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value);
 
void ADC1_2_ISR() 
iv IVT_INT_ADC1_2 
ics ICS_AUTO 
{
    if(ADC1_SRbits.EOC)
    {
        ADC1_SRbits.EOC = 0;
        regular_adc_data = (ADC1_DR & 0x0FFF);
    }
    if(ADC1_SRbits.JEOC)
    {
        ADC1_SRbits.JEOC = 0;
        injected_adc_data = (ADC1_JDR1 & 0x0FFF);
    }
}
 
void void EXTI11_ISR()
iv IVT_INT_EXTI15_10
ics ICS_AUTO
{
    unsigned char s = 0;
    
    if(read_pending_reg(15) != 0)
    {
        for(s = 0; s < 3; s++)
        {
            bit_set(GPIOC_ODR, 13);
            delay_ms(10);
            bit_clr(GPIOC_ODR, 13);
            delay_ms(10);
        }
        pending_clr(15);
    }
    
    if(read_pending_reg(11) != 0)
    {
        for(s = 0; s < 6; s++)
        {
            bit_set(GPIOC_ODR, 13);
            delay_ms(90);
            bit_clr(GPIOC_ODR, 13);
            delay_ms(90);
        }
        pending_clr(11);
    }
}
 
void main()
{
    setup();
 
    while(1)
    {
        lcd_print(2, 2, regular_adc_data);
        lcd_print(12, 2, injected_adc_data);
    };
}
 
void setup()
{
    GPIO_init();
    ADC_init();
    exeternal_interrupt_init();
    LCD_Init();
 
    LCD_Cmd(_LCD_CLEAR);
    LCD_Cmd(_LCD_CURSOR_OFF);
 
    lcd_out(1, 1, "CH00 R");
    lcd_out(1, 11, "CH01 I");
}
 
void GPIO_init()
{
     enable_GPIOA(enable);
     enable_GPIOB(enable);
     enable_GPIOC(enable);
     pin_configure_low(GPIOA_CRL, 0, (analog_input | input_mode));
     pin_configure_low(GPIOA_CRL, 1, (analog_input | input_mode));
     pin_configure_high(GPIOB_CRH, 11, (digital_input | input_mode));
     pin_configure_high(GPIOC_CRH, 15, (digital_input | input_mode));
     pin_configure_high(GPIOC_CRH, 13, (GPIO_PP_output | output_mode_low_speed));
     pull_up_enable(GPIOB_ODR, 11);
     pull_up_enable(GPIOC_ODR, 15);
}
 
void ADC_init()
{
     ADC1_Enable();
     clr_ADC1_settings();
     set_ADC_mode(independent_mode);
     set_ADC1_data_alignment(right_alignment);
     set_ADC1_scan_conversion_mode(disable);
     set_ADC1_continuous_conversion_mode(disable);
     set_ADC1_sample_time(sample_time_239_5_cycles, 0);
     set_ADC1_sample_time(sample_time_239_5_cycles, 1);
     
     set_ADC1_injected_sequence(4, 1);
     set_ADC1_injected_number_of_conversions(1);
     set_ADC1_external_trigger_injected_conversion_edge(EXTI_15_trigger);
     set_ADC1_injected_end_of_conversion_interrupt(enable);
     
     set_ADC1_regular_sequence(1, 1);
     set_ADC1_regular_number_of_conversions(1);
     set_ADC1_external_trigger_regular_conversion_edge(EXTI_11_trigger);
     set_ADC1_regular_end_of_conversion_interrupt(enable);
     
     NVIC_IntEnable(IVT_INT_ADC1_2);
     EnableInterrupts();
     ADC1_calibrate();
     start_ADC1();
}
 
void exeternal_interrupt_init()
{
     AFIO_enable(enable);
     falling_edge_selector(11);
     falling_edge_selector(15);
     set_EXTI8_11(11, PB_pin);
     set_EXTI12_15(15, PC_pin);
     interrupt_mask(11);
     interrupt_mask(15);
     NVIC_IntEnable(IVT_INT_EXTI15_10);
}
 
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value)
{
     unsigned char tmp = 0;
 
     tmp = (value / 1000);
     lcd_chr(y_pos, x_pos, (tmp + 48));
     tmp = ((value / 100) % 10);
     lcd_chr_cp((tmp + 48));
     tmp = ((value / 10) % 10);
     lcd_chr_cp((tmp + 48));
     tmp = (value % 10);
     lcd_chr_cp((tmp + 48));
}

Injected vs Regular (1) Injected vs Regular (2) Demo video link: https://www.youtube.com/watch?v=K5XTYtVEoSo

Common Pitfalls

When dealing with new stuffs, we often make some silly mistakes. In the case of STM32 ADC, the following mistakes are pretty much common:

  • ADC not powered.
  • ADC not calibrated.
  • ADC settings not cleared/reset to defaults.
  • Incorrect GPIO port selection and configuration. If a pin is to be set for an analogue input channel then it should be either kept floating or configured as analogue input.
  • Wrong mode of operation selected.
  • Incorrect peripheral clock selection.
  • Same channels configured for different ADCs, for example CH0 configured for ADC1 and 2.
  • Different sampling times for two simultaneous channels in dual conversion mode.
  • Interrupts not enabled when using them. MikroC’s interrupt assistant feature should be used to avoid such mistakes. The interrupt assistant also help in code generation.
  • Improper selection of regular and injected modes.
  • Incorrect data alignment. Right alignment should always be used unless needed otherwise.
  • Wrong variable type declaration. If the resulting data from a conversion is signed then signed integers should be used.
  • Wrong ADC channel sequencing with incorrect channel order.

Final Words

There are many ways to meet a specific measurement case with STM32 ADCs. Imagination is the limit for so. As I stated earlier it is not possible for me to show all ADC operations in this post. Thus I would like readers to try and experiment whatever I showed. If you can take the challenge and if you have understood the basics then I would suggest that you experiment those stuffs which I didn’t cover. Finally there’s a saying

ADC + DMA = Unbeatable Assets

Of course the DMA block is an asset and when there’s an ADC-DMA collaboration, the result is a revolution in measurement. This issue was not regarding DMA and so when I cover the DMA block, I’ll show more about ADC. For now this post is enough to introduce the basics of STM32 ADC.   Files: STM32 ADC Files.   Happy coding.  

Author: Shawon M. Shahryiar
https://www.facebook.com/groups/microarena
https://www.facebook.com/MicroArena
+8801970046495                                                                                                               18.03.2015

The post STM32 ADC appeared first on Embedded Lab.

Tsunami: Arduino-based Signal Generator

$
0
0

Nick Johnson from Arachnid Labs has designed a powerful and affordable signal generator (named Tsunami) based on the Arduino platform, and is now running a Kickstarter for making it available to electronics hobbyists worldwide.

The Tsunami is a powerful and flexible signal generator built on the Arduino platform. It’s the best way to get started experimenting with analog signals, and a great tool for a huge variety of tasks, too.

The tsunami takes the versatile processor behind the Arduino Leonardo, and combines it with a Direct Digital Synthesis chip, which makes generating analog signals incredibly straightforward. It also has flexible input and output circuitry, and an easy to use software library, to make working with analog signals as easy as blinking an LED.

Tsunami: Arduino based signal generator

Tsunami: Arduino based signal generator

The post Tsunami: Arduino-based Signal Generator appeared first on Embedded Lab.

Adding ESP8266 module to IKEA Molgan Light

$
0
0

IKEA Molgan is a battery powered night light with a built-in motion (PIR) sensor. This hack describes how to add the ESP8266 WiFi module and use it for motion detection and remote notification through Internet.

Night light notifies the user about motion detection through WiFi

Night light notifies the user about motion detection through WiFi

My original plan was to use an cheap Ebay PIR and 3D printed case for this project but I happen to have a spareIkea Molgan PIR light lying around, I opened it up and take a peek inside and decided to work this hack with it, overall this is an attractive and cheap unit.

Removing the top dome was easy as it is friction fitted and held together with some tape, upon closer examination, it uses the same parts as most cheap Ebay PIR, just with a bigger dome lens, LEDs and battery holder. Running on 3 x AAA battery , voltage wise it is pretty ideal but I don’t think it will last too long once I hook the ESP8266 to it.

 

The post Adding ESP8266 module to IKEA Molgan Light appeared first on Embedded Lab.


Tutorial: Raspberry Pi connects to ThingSpeak

Meet Konekt Dash, a cellular development kit for IoT

$
0
0

Konekt Dash is a new cellular network development kit for the Internet of Things (IoT) applications and is looking for funding at Kickstarter.

Konet

Konekt Dash

The Konekt Dash is a cellular development kit for building Internet of Things (IoT) devices. You can use it to build all sorts of fun connected products like sensors, tracking devices, alarm systems, connected car applications, and more (see examples below).

The Konekt Platform is made to bring enterprise grade features to the individual developer, so its perfect for a solo hobby project or building the connected hardware business of your dreams!

Each Konekt Dash comes preloaded with a Konekt Global SIM and 6 months of our basic data plan (1MB/month or 6MB total).  If you need more data, thats cool too; we have great carrier agreements already in place to provide super affordable connectivity at higher data levels (check the deets below for pricing).

The Konekt Dash will work anywhere you get a cellular signal and can easily and securely communicate to the internet or other devices via the Konekt Cloud.

The post Meet Konekt Dash, a cellular development kit for IoT appeared first on Embedded Lab.

Raspberry Pi smartphone with 2G support

$
0
0

Tyler Spadgenske’s entry to 2015 Hack-A-Day Prize contest is tyfone, a DIY smartphone with 2G support using the Raspberry Pi, a 3.5in touchscreen display, and an Adafruit Fona module.

Tyfone:

Tyfone: DIY smartphone

The Raspberry Pi handles all the processing, and connects everything together. The TFT talks to the Raspberry Pi over SPI, and the FONA talks to it over UART. Everything is powered with a 1200mah battery connected to the FONA.The FONA has a charging circuit perfect for use with the phone. Since the battery is only 3.7v, a power boost is used to boost the voltage to 5v for the Raspberry Pi and TFT. A metal speaker and microphone is connected to the FONA for audio, along with an antenna. The FONA also has a real time clock built in, so the Raspberry Pi can keep time even when off wifi connection. The Raspberry Pi also has a USB Wifi Adapter connected to it giving it internet access. Right now all that it is used for is to upload camera photos to dropbox, but more features will be implemented in the future. The Raspberry Pi camera module is used for pictures and video.

The post Raspberry Pi smartphone with 2G support appeared first on Embedded Lab.

Tutorial on Thermocouple Amplifier

$
0
0

Bill Herd at Hackaday has posted a new video tutorial on Thermocouple Amplifier that covers the most basic instrumentation required to interface a thermocouple to a microcontroller ADC channel for reliable temperature measurements. A thermocouple consists of a junction of two wires made of different metals and are characterized by a temperature coefficient that is required to convert the thermocouple output voltage to the sensed temperature. In addition, the thermocouple also needs to be calibrated to a reference temperature point. In his short tutorial, Bill explains a practical circuit to implement precise temperature readings from a thermocouple.

Thermocouple amplifier

Thermocouple amplifier

Different thermocouples sensors have a different temperature coefficients meaning that they will generate different amounts of voltage for the same change in temperature, usually specified in volts per degree of Celsius (v/◦C). Knowing the temperature coefficient of a sensor is only half the equation, we also need to nail down the zero point, meaning that we establish a calibrated reference point. Applying a known temperature such as immersing the sensor in ice water would be a simple if inconvenient way to establish a known reference temperature. Basically we could zero out and measure the change in volts per degree C from there. Alternatively we could use a Cold Junction Compensator (CJC) such as the LT1025, a chip made to not only replicate the different temperature coefficients of the various thermocouples, but also give us a pretty reasonable calibration.

The post Tutorial on Thermocouple Amplifier appeared first on Embedded Lab.

DAN64: A DIY 8-bit microcomputer using Arduino

$
0
0

Juan J. Martínez’s DAN64 is a single board 8-bit microcomputer based on Arduino board and features a keyboard input, an output screen, and is able to load and run external programs.

DAN64 microcomputer

DAN64 microcomputer

Current version of the project has the following features:

  • Composite video black and white output, 256 x 192 resolution, 32 x 24 characters (8 x 8 pixels font, code page 437 character set).
  • PS/2 keyboard support.
  • 6502 virtual machine with system call interface to native code services.
  • Linear 64KB memory access from the virtual machine (256 bytes page zero, 256 bytes hardware stack, 6144 bytes of video RAM and 58880 bytes for user programs).
  • External storage support via audio in/out.
  • Integrated 6502 assembler and disassembler.
  • Basic shell supporting peek, poke, load, run, etc.

 

The post DAN64: A DIY 8-bit microcomputer using Arduino appeared first on Embedded Lab.

Viewing all 713 articles
Browse latest View live