8-bit Knowledge Base

      • Pin width: EFM32HG TQFP-48 package

        BrianL | 11/305/2017 | 11:16 AM


        What is the width of the pins on the TQFP-48 package on EFM32HG devices? This is not listed as a dimension in tables 4.4 (package specifications).


        Firstly, page 59, Figure 5.1 does show the recommended landing patterns for this device's pins as 1.6 x 0.3 mm. So, it's reasonable to expect the pin width to be, at most 0.3 mm.

        In table 4.4, we can also derive the pad with by examining the distance between the edges of adjacent pins (0.2 mm, T, U, V, Z) and the pitch width (the distance between the centers of pins (0.5 mm). The pitch width should be equal to (1/2 pin width) * 2 + pin separation. So pin width + 0.2 mm = 0.5 mm, so pin width equals 0.3 mm.


        Examining a TQFP-48 device here with a microscope, I observe that the pins indeed do seem larger than the gaps between them, so 0.3 mm should be the correct number for the pin width, and 0.2 mm for the pin gaps.

      • Measuring Temperature with EFM8LB1

        BrianL | 11/305/2017 | 11:13 AM


        I am measuring the internal temperature sensor with the ADC. I then use the formula from the appnote (https://www.silabs.com/documents/public/application-notes/AN929.pdf) to calculate the temperature, using the internal temp sensor calibration reading.


        ((ADC Temp sensor measurement) - (stored Cal value) / 28 = Temperature C


        However, I am getting odd results. For example, I measure 0xE84 from the temperature sensor. The cal value stored in flash at 0xFFD4-0xFFD5 reads back 0x10C7A. This results in the following formula.


        (0xE84-0x1C7A) / 28 = -127.6 C


        This can't be right since the measurement is being performed at room temperature. What is going on here?


        The temp sensor cal value is taken with the internal voltage reference at 1.65 V, and the ADC set to 14-bit mode with a PGA gain of 1 (rather than 0.5). The settings for taking a temperature sensor reading must be identical, or the measured value will be on the wrong scale compared to the temperature sensor.


        Make sure that the ADC is set to 14-bit mode, 1.65 V internal reference, and PGA gain of 1 (0.5 is the default). As a result, our previous measurement at the same temperature now reads 0x20A9.


        The resulting formula is then:


        (0x20A9 - 0x1C7A) / 28 = 29.2 C


        This is an expected result for a measurement near room temperature.

      • AN945 Bootloader on non-bootloader enabled devices

        BrianL | 06/163/2017 | 05:53 PM

        For the release of Silicon Labs' EFM8 product line, a factory bootloader was created. However, this bootloader only works natively on devices that are 'bootloader enabled'. These 'bootloader enabled' devices are:


        • EFM8BB1 rev A, date code > 1601
        • EFM8BB2 rev C, date code > 1601
        • EFM8BB3 (all)
        • EFM8LB1 (all)
        • EFM8UB1 rev C, date code > 1601
        • EFM8UB2 rev B
        • EFM8SB1 rev A, date code > 1544
        • EFM8SB2 rev B

        The AN945 bootloader will also be compatible on any future EFM8 devices (including newer revisions of the above devices).


        Bootloader enabled devices have the ability to check to see if they should jump to the bootloader or the application after a reset, before code starts running. Other Silicon Labs 8-bit devices, or older EFM8 devices, don't have this capability. However, you can change this by mimicking this function in the bootloader and application firmware.


        Firstly, the bootloader image should contain a jump to the bootloader at the reset vector 0x0000. You can do this by adding the following code to the boot_startup.asm file in the bootloader project:



            RSEG    ?BL_JUMP
            LJMP    boot_start



        So, if there is no application, this will jump automatically to the bootloader.


        Now, you need to modify your application code to mimic the bootloader capable parts' additional functionality. I've done this in the SILABS_STARTUP.A51 file of an application, since this is where you can insert code that effectively runs before your application. In here, we'll simply need to check to see if the bootloader exists. If it does, we'll jump there first and it will perform the other checks to see if we should go to the application.


        However, in this case, we'll also need to determine whether we just came from the bootloader, since we technically are the application. Without this, we would get stuck in a loop - jumping from the application to the bootloader, back to the application, back to the bootloader, etc. I used R7 to store a particular value to say that we've just come from the bootloader. Here is the modified SILAB_STARTUP.A51 file:


                        CSEG    AT      0
        ?C_STARTUP:     LJMP    BootloaderCheck
                        RSEG    ?C_C51STARTUP
        #include "efm8_device.h"
        #define BL_SIGNATURE 0xA5
          ; Read and test R7 to see if we've already entered the bootloader
          ; since the last reset. If so, we should skip to the application
          mov A, #BL_SIGNATURE
          xrl A, R7
          jz GotoApplication
           ; Read and test the boot vector enable byte (byte before Lock Byte)
           ; The signature is present if A is 0 (leave result in A)
           mov DPTR, #(BL_FLASH0_LIMIT - 2)
           movc A, @A+DPTR
           xrl A, #BL_SIGNATURE
           ; Restore the DPTR
           mov DPTR, #0000h
           ; If the signature is present, jump to the boot vector
           jz GotoBootVector
           clr A                ; Restore A
           mov R7, #0x00               ; Restore R7
           jmp STARTUP1          ; Jump to reset vector (use this to save a byte)
        GotoBootVector:         ; A = 0, no need to restore
           mov R7, #BL_SIGNATURE   ; Write 0xA5 to R7 to indicate we've bootloaded
           ljmp BL_START_ADDRESS     ; Jump to boot vector

        The full SILABS_STARTUP.A51 file is zipped and attached to this forum post.


        To summarize:

        • Rebuild the bootloader that contains a jump at address 0x0000 that jumps to the bootloader start address
        • Replace the SILABS_STARTUP.A51 file in your application with the one attached to this post
      • I2C Pull-up resistor calculation

        BrianL | 06/160/2017 | 06:24 PM

        This appnote describes how to calculate the pull-up resistors for a particular I2C bus: http://www.ti.com/lit/an/slva689/slva689.pdf


        Minimum Pull-up Resistance


        The minimum resistance is pretty easy to determine, and is based on the bus voltage (Vbus), the maximum voltage that can be read as a logic-low (VOL), and the maximum current that the pins can sink when at or below VOL (IOL).


        The formula is:


        Rp(min) = (Vbus – VOL) / IOL


        Formula 1. Minimum Pull-up Resistance


        As an example, we will use an EFM8LB1 MCU, operating at VIO = 3.3V with an I2C speed of 400 kHz (fast mode), for the I2C bus characteristics. The EFM8LB1 datasheet is here: https://www.silabs.com/documents/public/data-sheets/efm8lb1-datasheet.pdf


        For the LB1, VIL is 0.3 * VIO, which would be 0.99 V. This is the maximum voltage that will be registered by the LB1 as a logic low. In high-drive mode, the pins can also sink up to 13.5 mA while retaining a voltage of at most 0.6V, which is below the VIL threshold, so we can use that as the input for IOL.


        • Rp(min) = (3.3V – 0.99V ) / 13.5mA = 171 Ohms


        This is the minimum pull-up resistance for an I2C bus composed of EFM8LB1 devices. Lower than this, and we cannot guarantee that the device can pull the I2C bus lines below VOL.


        Maximum Pull-up Resistance


        The maximum pull-up resistance is based on the needed rise-time of the clock (dependent on the I2C clock frequency), and the total capacitance on the bus.


        The I2C specification (http://cache.nxp.com/documents/user_manual/UM10204.pdf) lists the maximum total bus capacitance with a pull-up resistor to be 200 pF (it can be up to 400 pF if the pull-up is a current source, section 5.1).


        This specification also describes the rise-time of SDA/SCL to be a maximum of 300 ns in “Fast-mode” – 400 kHz (table 10).


        Back to the appnote: the formula for calculating the maximum pull-up resistance is:


        Rp(min) = rt / ( 0.8473 * C)


        Formula 2. Maximum Pull-up Resistance


        Where rt is the maximum allowed rise-time of the bus, and Cb is the total bus capacitance.


        The appnote actually already calculated this for the worst-case Fast Mode ( 300 ns / 0.8473 * 200 pF), to be 1.77 k Ohms. These pull-ups would draw 3.3V / 1.77 k  = 1.86 mA each when SCL / SDA is low.


        So, theoretically, if this bus has the absolute maximum amount of capacitance on it, this bus should use at least 1.77 k Ohm pull-up resistors, down to 171 Ohm resistors if their maximum low drive strength is 13.5 mA each during SCL/SDA low.


        Ideally, the bus capacitance should be lower than the I2C maximum specification, so the formula may give you a higher resistance than this theoretical worst case.

      • High Frequency Oscillator Precision over Device Life

        BrianL | 03/89/2017 | 08:14 PM


        C8051 and EFM8 devices include a high-precision 24.5 MHz internal oscillator, with a minimum / maximum frequency specification of 24 MHz / 25 MHz (2% accuracy). Is this specification guaranteed over the life of the device?


        Yes. This specification is guaranteed to be held across the entire life of the device.


        These devices have been characterized with a 1000-hour high-temperature operational life (HTOL) to determine how the devices will change over their lifetime. For the precision internal oscillator, no significant change was observed.

      • Wake EFM8UB1/EFM8BB2 Devices from Snooze Mode with SPI

        BrianL | 03/89/2017 | 08:13 PM


        In Table 1.1 of the BB2/UB1 datasheets, the device lists the capability to be awakened from snooze mode by SPI0 activity. The SPI is also described with the feature that it "Can operate in suspend or snooze modes and wake the CPU on reception of a byte." What is required for this to work?


        Firstly, the device must be in slave mode. Secondly, the Read Request Interrupt Enable bit (RFRQE) in the SPI0 FIFO Control 0 register (SPI0FCN0) must be set. It is not necessary to use the SPI with interrupts in order for this to work.

      • Converting 8-bit IDE Memory Upload to Binary file

        BrianL | 10/281/2016 | 06:02 PM

        In the legacy 8-bit MCU IDE, there is an option to upload the contents of the memory to a text file. This can be useful to record the firmware image on a device:






        However, the format that is produced is non-standard - every byte of flash is recorded in text, separated with a new line, e.g.:





        It may be useful to convert this format into a binary format. Attached to this case is a simple python script that performs this action. The usage for this script is:

        MemoryUpload2Bin.py -m <path to memory upload txt> -b <path to binary to be created>




      • Converting binary firmware image files to intel hex files (bin to hex)

        BrianL | 10/281/2016 | 06:01 PM

        Binary files can be generated from several toolsets, or even by uploading the memory contents of a device using Simplicity Studio. However, it may be useful to convert these .bin files into a .hex file. A python script has been attached to this article to perform this task.


        The usage of this file is:


        Bin2Hex.py -b <path to binary file> -o <path to hex to be created>


      • Branch instruction variable execution time on devices with a flash prefetch engine

        BrianL | 10/281/2016 | 05:57 PM


        Flash Prefetching:


        Some Silicon Labs 8-bit MCUs have a peripheral known as a Flash Prefetch Engine. This is required on devices that support MCU core SYSCLK frequencies greater than 25 MHz. Since the flash memory on the device has a maximum clock frequency of 25 MHz, and the MCU core can execute one byte of code per SYSCLK cycle, it becomes necessary to fetch multiple bytes of flash per SYSCLK cycle if SYSCLK exceeds the flash maximum clock speed.


        For example, if the MCU core was running at 50 MHz, but the flash was still only fetching one byte per 25 MHz flash clock, the MCU core would need to be idle every other SYSCLK cycle in order to wait for new instructions. To combat this, the prefetch engine allows the device to fetch multiple bytes of flash per SYSCLK clock cycle.



        Branch Instructions, variable latency:


        Branch instructions generally have a variable execution time, depending on whether the branch is taken or not taken. This occurs because the core automatically assumes that the branch will not be taken, and fetches the next byte of code after the branch instruction automatically. However, if the branch instruction is executed, and it turns out that the branch is actually taken, the core has to dump any instructions that are currently executing from the branch-not-taken code, load the code from the branch-taken destination, and begin executing this code instead. For example, here is the JZ instruction latency table from the EFM8LB1 reference manual:


              Clock Cycles
        Mnemonic Description Bytes FLRT = 0 FLRT = 1 FLRT = 2
        JZ rel  Jump if A equals zero  2 2 or 3 2 or 6 2 or 8

        Table 1. JZ rel variable instruction execution times


        As you can see, each column for number of clocks to execute this instruction has two values - one for the branch-not-taken case (the smaller number), and one for the branch-taken case (the larger number). The branch-not-taken case will always be faster, since the core, by default, is already in the process of executing the instructions after the branch statement by the time the branch instruction is resolved. If the branch instruction resolves to 'branch not taken', the core continues executing as normal, causing no delays in execution.


        For other instructions, such as SJMP, the branch is always taken, requiring that the core always take this longer route to execution:


              Clock Cycles
        Mnemonic Description Bytes FLRT = 0 FLRT = 1 FLRT = 2
        SJMP rel  Short jump (relative address) 2 3 6 8

        Table 1. SJMP rel instruction execution times



        Prefetch Engine's impact on instruction execution times:


        Another thing to note, however, is that the instruction execution time is now dependent on the FLRT setting in the prefetch engine. This setting has the following description in the EFM8LB1 reference manual:


        "Flash Read Timing:
        This field should be programmed to the smallest allowed value, according to the system clock speed. When transitioning to a faster clock speed, program FLRT before changing the clock. When changing to a slower clock speed, change the clock before changing FLRT."


        FLRT Setting Description
        0 SYSCLK < 25 MHz.
        1 SYSCLK < 50 MHz.
        2 SYSCLK < 75 MHz.


        Effectively, this means that FLRT=0 means 1 byte of flash will be fetched per SYSCLK cycle, FLRT=1 means two bytes will be fetched at once, and FLRT=2 means that four bytes of flash will be fetched at once. This setting also determines the flash clock timing. FLRT=0 means the flash clock will be SYSCLK, FLRT=1 means the flash clock will be SYSCLK/2, and FLRT=2 means that the flash clock will be SYSCLK/3.


        How does this impact instruction execution times? When a jump instruction is executed, and the jump is taken, the core must now load the code from the destination address. This will take one flash clock cycle, during which the core must be idle. In the case of FLRT=1 or FLRT=2, one flash clock cycle equals multiple core cycles - either 2 or 3 cycles, respectively. Some additional latency is also added due to having to load the new flash address, dumping the instructions that are currently being executed in the core, etc. Generally, the jump-not-taken execution time will be +3 clock cycles and +5 clock cycles for FLRT=1 and FLRT=2, respectively.


        More variation in execution times:


        However, these numbers are not absolute rules. More quirks with the flash prefetch engine can speed up or slow down execution, depending on the location of the jump instruction itself, as well as the destination location.


        As an experiment, the SJMP instruction was evaluated on an EFM8LB1 device running at SYCLK=72MHz, FLRT=2. For the first test, the SJMP instruction was placed at address 0x0100, with its destination address modified to be between 0x0102 and 0x011A. 


        SJMP Rel Cycles Destination Address Address Mod 4
        0 3 0x0102 2
        1 3 0x0103 3
        2 3 0x0104 0
        3 3 0x0105 1
        4 3 0x0106 2
        5 5 0x0107 3
        6 6 0x0108 0
        7 6 0x0109 1
        8 6 0x010A 2
        9 8 0x010B 3
        10 6 0x010C 0
        11 6 0x010D 1
        12 6 0x010E 2
        13 8 0x010F 3
        14 6 0x0110 0
        15 6 0x0111 1
        16 6 0x0112 2
        17 8 0x0113 3
        18 6 0x0114 0
        19 6 0x0115 1
        20 6 0x0116 2
        21 8 0x0117 3
        22 6 0x0118 0
        23 6 0x0119 1
        24 6 0x011A 2

         Table 2. SJMP instruction at 0x0100 


        As you can see, the SJMP execution time varies anywhere from 3 cycles (when the destination is within 4 bytes of the SJMP instruction), all the way to 8 cycles (when the destination is farther away from the SJMP instruction, and the destination address ends in 0x3, 0x7, 0xB, or 0xF (effectively, if the address modulo 4 equals 3).


        Another test was performed, this time with the SJMP instruction at address 0x00FE:


        SJMP Rel Cycles Destination Address Address Mod 4
        0 4 0x0100 0
        1 4 0x0101 1
        2 4 0x0102 2
        3 5 0x0103 3
        4 6 0x0104 0
        5 6 0x0105 1
        6 6 0x0106 2
        7 8 0x0107 3
        8 7 0x0108 0
        9 7 0x0109 1
        10 9 0x010A 2
        11 7 0x010B 3
        12 7 0x010C 0
        13 7 0x010D 1
        14 7 0x010E 2
        15 9 0x010F 3
        16 7 0x0110 0
        17 7 0x0111 1
        18 7 0x0112 2
        19 9 0x0113 3
        20 7 0x0114 0
        21 7 0x0115 1
        22 7 0x0116 2
        23 9 0x0117 3
        24 7 0x0118 0

         Table 1. SJMP instruction at 0x00FE


        Again, the SJMP instruction execution time varies, this time with an additional clock cycle added to all instructions. The fastest execution, again, was when the destination was close to the SJMP instruction address, while all instructions ending in 0x3, 0x7, 0xB, and 0xF performed worse.


        Minimizing execution times:


        Several observations can be made that can be used to minimize instruction execution times for branch instructions, in cases where highly optimized branches are needed.


        1. If possible, put the destination address very close to the jump instruction. 
        2. Place the destination address on an even flash boundary. If FLRT=1, place the destination so that its address modulo 2 equals zero. If FLRT=2, place the destination so that its address modulo 4 equals zero.
        3. Place the jump instruction on a similar boundary, as in #2. If FLRT=1, place the jump instruction so that its address modulo 2 equals zero. If FLRT=2, place the jump instruction so that its address modulo 4 equals zero.


        Attached to this topic is a program called SJMP_TEST.asm, which can be used to measure the instruction execution times of jump instructions. This is performed by monitoring the value of TIMER3 (clocked by SYSCLK) once the jump instruction is executed, and the core reaches the destination.


        This thread may also be a good resource for more information on this topic: http://community.silabs.com/t5/8-bit-MCU/Random-Latency-with-Laser-Bee-Branch-Instructions/m-p/177927#U177927


      • Using ADC to measure a current source with no external components

        BrianL | 10/281/2016 | 05:46 PM

        In embedded systems, analog measurements are often required for both voltage and current sources. Common variable current sources include photodiodes and phototransistors, as well as some forms of other sensors, such as temperature sensors. In most cases, current sources are converted into a voltage measurement, usually through the use of a sense resistor, or an amplifier plus a sense resistor to provide a low output impedance of the measured signal. However, there is a way to directly measure these current sources by taking advantage of the architecture of the SAR ADCs present on Silicon Labs MCUs.


        The method described here, which I will refer to as ADC current integration, requires zero external components, with sampling times potentially many times faster than a simple current sense solution.


        1. ADC Architecture


        Firstly, it helps to review the architecture of a SAR ADC. A simple diagram is below:

        Fig 1. ADC SAR Architecture, simplified


        When the ADC is enabled, and a particular pin is selected as the ADC's input, the SAMPLE_SWITCH is closed, allowing CSAMPLE to charge to the signal voltage available on the input pin. When a conversion is initiated, the SAMPLE_SWITCH is opened, allowing the voltage on the CSAMPLE capacitor to stay stable while the ADC performs the conversion. CPIN is merely the pin's parasitic capacitance, which is usually very small, but near the same order of magnitude of the CSAMPLE capacitor.


        Generally, with a voltage source, the signal on the ADC_INPUT_PIN is of very low impedance, charging the relatively small CPIN and CSAMPLE capacitors very quickly to the input voltage.


        However, with current sources, the current is usually converted to a voltage by using a simple current sense resistor. This can effectively increase this output impedance significantly, increasing the amount of time required for the sampling capacitor to charge before a sample can be taken.


        2. Capacitor integration


        An alternative to the current sensing resistor solution is to integrate the current as a voltage on the capacitor. A basic property of a capacitor is that the voltage (V) on a capacitor is equal to the charge (Q) on the capacitor divided by its capacitance (C):



        Equation 1. Capacitor voltage with respect to charge and capacitance


        Since charge is merely equal to current (I) times time (t), we can re-write the equation as:



        Equation 2. Capacitor voltage with respect to current, time, and capacitance


        As you can see, given a fixed amount of time for the capacitor to charge, and a fixed capacitance, the voltage on the capacitor is directly proportional to the amount of current flowing into the capacitor. This means that a capacitor, such as an ADC's sampling capacitor, can be used to convert a constant current into a voltage merely by letting it charge for a fixed period of time.


        3. Implementation


        Implementing this technique is relatively straightforward:

        1. Select the current source as the ADC's input pin.
        2. Ground the ADC's sampling capacitor and the input pin to discharge its parasitic capacitance.
        3. Disconnect ground from the ADC input pin so that the current source can now charge the sampling capacitor.
        4. Wait for some fixed amount of time (discussed in section 6. Determining the integration period) to allow the current to charge the capacitor.
        5. Initiate a conversion, disconnecting the sampling capacitor from the input pin.

        Since the capacitance on the input is fixed, and the user can sample the input for a fixed amount of time, the voltage read on the ADC will be proportional to the current generated by the current source.


        4. A real-world example


        As previously mentioned, photodiodes are a common example of a sensor that generates a current, rather than a voltage, output. For this example, an infra-red photodiode was selected from Digikey: PD204-6B, IR Photodiode


        According to this device's datasheet, this photodiode will generate a current between 1.5uA and 3.5uA, depending on the lighting conditions.


        In this example, we will use an EFM8SB2 8-bit MCU, which, according to its datasheet, has a pin capacitance of 20pF, and an ADC sampling capacitance of 28pF, for a total input capacitance of 48pF.


        Current-sense resistor technique:


        If we wish to convert this current (3.5uA maximum) to a voltage that can be read by the ADC (0-1.65V with a PGA gain of 1.0), we will need to add a very large current sense resistor across the input pin. A value of 350k Ohms will produce a voltage of 1.225V at 3.5uA (V=IR). However, the time constant to charge the input capacitor, with this parallel sense resistor, can be quite large. Here is a simulation of this circut in LTSpice:


        V1 represents VDD of 3.3V

        I1 represents the photodiode, sourcing 3.5uA

        C_sample represents the sampling and pin capacitance in parallel

        R1 is the current sensing resistor


        Fig 2. Sample voltage, sense resistor


        As you can see, this technique effectively provides a measurable voltage from the current source, but, even after 100us, the capacitor is not completely charged to the expected 1.225V.


        Integration technique:


        Removing the sensing resistor, the current source is now feeding directly into the capacitor. A similar LTSpice simulation has been created with three example current sources, 3.5uA, 2.5uA, and 1.5uA.


        Fig 3. Sample voltage, current integration


        In this case, you can see that the capacitor voltage is linear with respect to time. It is also proportional to the input current. For example, where the previous example took 100us to reach 1.225V, this method allows the 3.5uA current source to reach 1.225V in only 16.8us. If we fix this time and measure the other two sources, we get 0.875V for the 2.5uA source and 0.525V for the 1.5uA source.


        Comparing the currents to 3.5uA, we get proportions of:

        3.5uA = 1

        2.5uA = 0.714

        1.5uA = 0.428


        Comparing the resulting voltages at 16.8us, we get proportions of:

        1.225V = 1

        0.875V = 0.714

        0.525V = 0.428


        Exactly the proportions between the given currents. This implies that this technique can be used to accurately measure a current source.


        5. Testing it on the bench


        To test this technique on a bench, a Silicon Labs EFM8SB2 8-bit MCU was used. This was chosen because this MCU has both an ADC and an adjustable current DAC (IREF).


        For this project, the ADC was set to take a single 10-bit conversion when manually writing '1' to the ADBUSY bit in the ADC0CN0 register. The IREF module was set to generate a current between 0 and 63uA (in 1 uA increments) on P0.7.


        To take a current sample, the ADC first selects the IREF pin as its input. The pin is then set to push-pull so that it is grounded to make sure that all residual capacitance is discharged. The pin is then set back to being an analog input and the IREF is enabled with a given setting.


        A small delay allows the IREF output to charge the ADC's sampling capacitor. The ADC is then triggered to start a conversion. The output of this conversion is proportional to the input current.


        The source code for the measurement function is given below:

        uint16_t ADC_GetIntegratedSample(uint8_t irefCurrentSetting)
        	uint16_t sample;
        	// Switch ADC to measure P0.7
        	ADC0MX = ADC0MX_ADC0MX__ADC0P7;
        	// Discharge pin and sampling capacitance
        	IREF0CN0 = 0;
        	P0MDOUT |= P0MDOUT_B7__PUSH_PULL;
        	// Enable Current Source
        	P0MDOUT &= ~P0MDOUT_B7__PUSH_PULL;
        	IREF0CN0 = irefCurrentSetting;
        	// Delay to integrate sample
        	// Take measurement
        	sample = ADC0_GetSample();
        	// Disable current source
        	IREF0CN0 = 0;
        	return sample;



        The ADC result was recorded for every IREF step (0-63uA). The IREF current was also measured using a multimeter for each of these steps. The plot of these two measurements is below:


        Fig 4. ADC Count vs. IREF current


        The results, shown here, indicate that the ADC count very closely matches the curve of the actual IREF current. This shows that this technique can be used in a real-world application to effectively and quickly measure the current from a current source by simply using the device's ADC, using no external components.


        Please see the attached .zip file for this example project.


        6. Determining the integration period


        Given that the charge time for the capacitor is proportional to the amount of current flowing into the capacitor, how do we determine the optimal sampling period for a given maximum current?


        Firstly, let us re-arrange the previous Equation 2 so that we solve for time instead of voltage:



        Equation 3. Integration period


        Since t becomes smaller as I becomes larger, it makes sense for I to represent the maximum current that our sensor will produce, since, given a large enough current, the period would be too small for us to generate with our MCU.


        Similarly, V should represent the largest voltage that our ADC can accurately measure. This would give us the largest range of values from the ADC, increasing accuracy. This value should also take into account the less-than-ideal properties of the circuit that can reduce the linearity of the capacitor voltage. For example, at higher currents, the ESR of the charging circuit can end up becoming the limiting factor on current being delivered to the capacitor at higher voltages.


        C, of course, should be the total capacitance seen by the current source on the input pin. For the EFM8SB2 MCU, this includes, typically, 20pF of pin capacitance as well as 28pF from the ADC's sampling capacitor.



        Calculating the maximum integration period


        In a real-world application, calculating the maximum capacitor charge voltage must take into account the non-ideal components of the charging circuit. Generally, the charging circuit will have some non-trivial amount of equivalent series resistance. This can be modeled in our circuit as a series resistor, as shown here:

        Fig 5. Equivalent charging circuit with ESR


        As previously mentioned, the maximum voltage that the capacitor should be charged to should fall within the linear region of the capacitor voltage charge curve. As the voltage on the capacitor increases, this ESR may begin to limit the current to the capacitor rather than the current source. This occurs because, as the voltage on C_samp rises, the voltage on R_esr decreases (since V_samp + V_esr must equal V_src). At a point, the voltage across R_esr will be so low that the current through R_esr (by way of Ohm's law) will be lower than the current that the current source is able to supply. The current into the capacitor will effectively be limited by this resistance, not the current source. This has the effect of making the voltages on the capacitor after this point non-linear since, as the voltage on the capacitor rises, the voltage on the resistor continues to decrease, further decreasing the series current. From this point on, the charge curve on the capacitor will then resemble Fig 2, rather than Fig 3.


        As an example, the previous EFM8SB2 firmware example was modified so that the IREF current source generated a maximum current of 508uA, while integration for each measurement was performed over 204ns:


        Fig 6. Nonlinear measurements due to the effect of ESR


        Here, it's obvious that the effect of the ESR has impacted our measurements to no longer be linear with respect to the current generated by the current source. This problem can be avoided by shortening the integration period, lowering the maximum voltage seen on the capacitor at the time of sampling. This has the effect of increasing the minimum voltage seen on R_esr, which increases the minimum current through R_esr. If this minimum current is greater than the maximum current from the current source, R_esr will not be a limiting factor.


        Considering the circuit in Fig 5, the voltage on C_samp can now be defined as V_src - V_esr. Replacing V_samp in the previous Equation 3 with this gives us:



        Equation 4. Integration period, substituted sampling capacitor voltage


        We can then re-arrange the equation and, using Ohm's law to replace V_esr / I_max with R_esr, we get the following, simplified equation:



        Equation 5. Integration period, simplified


        This equation also gives us a theoretical maximum current supply from the current source since, if V_src / I_max is less than R_esr, the sampling time would be negative, indicating that no region of the capacitor's voltage over time would be linear.



        Calculating the maximum sampling capacitor voltage for the linear region


        Substituting in Equation 2 into Equation 5 for C_samp, we get the following equation when solving for the maximum voltage on the capacitor:



        Equation 6. Maximum capacitor voltage


        This equation defines the maximum capacitor voltage that will be seen on the capacitor before the series resistance will impact the linearity of the voltage on the capacitor over time.


        7. Example integration period calculation


        As an example, given that V_src (in this case, VDD) = 3.3V, an input capacitance of 48pF, an ESR of the circuit of 5k Ohms, and a maximum current of 0.1mA, we can calculate the amount of time we need to let the sampling capacitor charge by the following:



        Equation 7. Example integration period calculation


        Solving this equation gives us t = 1.34us. With a SYSCLK of 24.5 MHz, each SYSCLK period would equal 40.8ns. This gives us 1.34us / 40.8ns = 32.94 SYSCLK period. This means, with a maximum of 0.1mA current input, we can let the current source charge our sampling capacitor for up to 32 SYSCLK periods before the voltage on the capacitor starts to become non-linear.


        Using Equation 6, we can also calculate the maximum voltage that will be seen on the sampling capacitor: V = 3.3V - (5k Ohm / 0.1mA) = 2.8V


        As a confirmation, we can also use Equation 2 to calculate the resulting voltage after charging the capacitor for 1.34us at 0.1mA: V = (1.34us * 0.1mA) / 44pF = 2.8V


        8. Steps, summarized:


        To implement this technique:

        1. Determine the maximum integration time using equation 5.
          - If this number is zero or negative, the ESR for the measurement circuit is too large for the given current, and this technique will not work.
          - If this period is too small to be effectively generated by the MCU, you may add an external capacitance to the pin to increase the integration period.
        2. Connect the current source directly to the ADC input pin.
        3. Enable the current source, allowing it to charge the capacitance on the ADC's input pin, including the ADC's sampling capacitor.
        4. Wait for the period (or less) determined in step 1.
        5. Trigger an ADC sample. The result will be proportional to the input current, and should not exceed the voltage calculated from equation 6.


        9. Resources:

        Attached to this article is a .zip file that contains:

        • The datasheet for the example photodiode
        • The two LTSpice simulation circuits
        • The example project for the EFM8SB2

        Note: if using the EFM8SB2 STK with this example project, the resistor R1066 will need to be removed, since it connects P0.7 to a pull-up resistor for the I2C bus on the board.