Is there a way to generate a sine wave with the IDAC on Happy Gecko? Previous devices with the DAC module, like Gecko, can generate a sine wave under hardware control by setting the SINEMODE bit in the DACn_CTRL register. There is no such bit in the IDACn_CTRL register, so it doesn't look like this is possible.
Answer
While it is true that the IDAC does not have a hardware sine generator, Happy Gecko does have all of the requisite features needed to generate a sine wave (or any other waveform) under software control.
Regardless of the kind of digital-to-analog converter used, any kind of waveform generation requires the presence of a time base to sequence the analog values generated. The IDAC has no such capability, so one of the EFM32 timers must be used. Furthermore, unless the CPU can dedicate itself to this purpose, a hardware assistance mechanism, like a queue or DMA, is needed to periodically update the analog output while the processor performs other tasks. The DMA on Happy Gecko can do this.
Some consideration of how the IDAC differs from the EFM32 DAC is also necessary. The DAC is a ratiometric converter, which means that the value written into the Channel Data Register (DACn_CHxDATA) generates an analog voltage that is, subject to various error contributions, proportional to the selected reference.
The IDAC is also a ratiometric converter, but it differs from the DAC in that the value written into the STEPSEL field of the Current Programming Register (IDAC_CURPROG) permits sinking or sourcing of a current that is proportional to the selected reference. Compared to the DAC, which has a resolution of 12 bits, the Step Size Select field of the IDAC_CURPROG register is only 5 bits wide.
A 5-bit converter might seem rather limited, but for the purpose of waveform generation, it provides ample resolution when allowing 5 µs for the output to settle. Using this as a limiting factor, the IDAC can output a 32-entry wave table (twice that used for the hardware sine wave generator in the DAC) at a respectable 1 ÷ (32 steps × 5 µs/per step) = 6.25 kHz.
Knowing these parameters and the capabilities of the IDAC, the outline for wave generation software takes shape as follows:
Create a data structure with all the points of one iteration of the waveform, scaled to an output range of 0 to 31.
Setup one of the timers to generate a trigger for the DMA occurring at a rate that is the inverse of the number of points in the waveform times the desired frequency, keeping in mind the desired settling time (5 µs in this example).
Prepare the DMA to transfer each point of the waveform to the IDAC Current Programming Register in response to the trigger from the timer.
Enable the IDAC and set its output to an initial state, if desired.
Start the timer in order to begin outputting the waveform.
Restart the DMA in the callback function executed when the channel updating the IDAC completes all of its transfers.
The example Simplicity Studio project attached to this article runs on the Happy Gecko Starter Kit and outputs a nominally 6.25 kHz cosine wave on the IDAC0_OUT pin. A 50 kΩ resistor connected between pins 1 (GND) and 11 (PB11) of the Expansion Header allows the waveform to be observed on an oscilloscope. While the code follows the outline above, a couple of specific details are worth nothing.
First, it's not sufficient to write raw wave table values to the IDAC Current Programming Register. The STEPSEL field occupies bits [12:8] of the register, so the raw values must be shifted left to reflect this. In addition, the output current range of the IDAC is set by the RANGESEL field in bits [1:0]. Because all peripheral register writes must be 32 bits, each wave table entry written to IDACn_CURPROG must include the output range along with the step value. All of this is handled in cosine_table.c as shown below:
Second, note the use of ping-pong instead of basic DMA to service the IDAC. The purpose of ping-pong DMA is to keep a constant stream of data moving between a peripheral and memory by allowing the CPU to periodically step in and update the DMA with the next set of transfers.
As implemented on EFM32 devices, the DMA uses primary and alternate descriptors in ping-pong mode that contain information about the transfers to be processed. When all data specified by the primary descriptor is transferred, work begins on the alternate descriptor's transfers, and the DMA requests interrupt service. The callback function executed in the interrupt handler can update the primary descriptor to point to a new set of data and refresh the ping-pong configuration. On completion of the alternate descriptor's transfers, this process repeats but with the DMA again processing the primary descriptor while the alternate descriptor is updated in the callback function.
If the cosine frequency is 6.25 kHz, a "constant stream of data" to update the IDAC might seem overkill. In this example, the waveform consists of 32 points, so the actual update rate is a much higher 32 × 6.25 kHz = 200 kHz or one DMA transfer every 5 µs or 70 clock cycles at the default HFRCO frequency of 14 MHz.
The DMA takes 17 clock cycles per byte, halfword, or word, which would seem to be sufficiently fast to reproduce the waveform at the desired frequency. It is, but only for a single iteration of the waveform when basic DMA is used. After the IDAC is updated with the last wave table value, the callback function is executed to restart the DMA cycle, and herein lies the problem. The callback function is dispatched from emlib's DMA IRQ handler as shown below:
void DMA_IRQHandler(void)
{
int channel;
DMA_CB_TypeDef *cb;
uint32_t pending;
uint32_t pendingPrio;
uint32_t prio;
uint32_t primaryCpy;
int i;
/* Get all pending and enabled interrupts */
pending = DMA->IF;
pending &= DMA->IEN;
/* Assert on bus error. */
EFM_ASSERT(!(pending & DMA_IF_ERR));
/* Process all pending channel interrupts. First process channels */
/* defined with high priority, then those with default priority. */
prio = DMA->CHPRIS;
pendingPrio = pending & prio;
for (i = 0; i < 2; i++)
{
channel = 0;
/* Process pending interrupts within high/default priority group */
/* honouring priority within group. */
while (pendingPrio)
{
if (pendingPrio & 1)
{
DMA_DESCRIPTOR_TypeDef *descr = (DMA_DESCRIPTOR_TypeDef *)(DMA->CTRLBASE);
uint32_t chmask = 1 << channel;
/* Clear pending interrupt prior to invoking callback, in case it */
/* sets up another DMA cycle. */
DMA->IFC = chmask;
/* Normally, no point in enabling interrupt without callback, but */
/* check if callback is defined anyway. Callback info is always */
/* located in primary descriptor. */
cb = (DMA_CB_TypeDef *)(descr[channel].USER);
if (cb)
{
/* Toggle next-descriptor indicator always prior to invoking */
/* callback (in case callback reconfigurs something) */
primaryCpy = cb->primary;
cb->primary ^= 1;
if (cb->cbFunc)
{
cb->cbFunc(channel, (bool)primaryCpy, cb->userPtr);
}
}
}
pendingPrio >>= 1;
channel++;
}
/* On second iteration, process default priority channels */
pendingPrio = pending & ~prio;
}
}
Even for a single pending DMA channel interrupt, entry into and processing of this IRQ handler and execution of the callback function take more than the 70 clock cycles available when running at 14 MHz. This results in a delay between the last and first points of two subsequent waveforms as shown below:
Increasing the clock frequency minimizes the delay incurred processing the IRQ handler and callback function as does reproducing the waveform at a lower the frequency, but these options do not fix the underlying problem. Ping-pong DMA resolves this altogether by transferring two complete iterations of the waveform in series and refreshing the DMA descriptors between each one:
The example project that generates the waveform above is attached to this article and can be imported into Simplicity Studio v4 as follows:
In the Simplicity Studio IDE, go the File menu (not the Project menu) and select Import…
Under General, select Existing Projects into Workspace, then click Next.
For Select archive file, locate the project .ZIP file and select it.
Make sure Copy projects into workspace is checked.
Click Finish.
Build the project, download it to the Happy Gecko STK, and run it with the debugger. A 50 kΩ resistor connected between pins 11 (PB11/IDAC_OUT) and 1 (GND) on the Expansion Header allows the cosine waveform driven by the IDAC to be observed with an oscilloscope probe also connected to pin 11.
Is there a way to generate a sine wave with the IDAC on Zero Gecko? Previous devices with the DAC module, like Gecko, can generate a sine wave under hardware control by setting the SINEMODE bit in the DACn_CTRL register. There is no such bit in the IDACn_CTRL register, so it doesn't look like this is possible.
Answer
While it is true that the IDAC does not have a hardware sine generator, Zero Gecko does have all of the requisite features needed to generate a sine wave (or any other waveform) under software control.
Regardless of the kind of digital-to-analog converter used, any kind of waveform generation requires the presence of a time base to sequence the analog values generated. The IDAC has no such capability, so one of the EFM32 timers must be used. Furthermore, unless the CPU can dedicate itself to this purpose, a hardware assistance mechanism, like a queue or DMA, is needed to periodically update the analog output while the processor performs other tasks. The DMA on Happy Gecko can do this.
Some consideration of how the IDAC differs from the EFM32 DAC is also necessary. The DAC is a ratiometric converter, which means that the value written into the Channel Data Register (DACn_CHxDATA) generates an analog voltage that is, subject to various error contributions, proportional to the selected reference.
The IDAC is also a ratiometric converter, but it differs from the DAC in that the value written into the STEPSEL field of the Current Programming Register (IDAC_CURPROG) permits sinking or sourcing of a current that is proportional to the selected reference. Compared to the DAC, which has a resolution of 12 bits, the Step Size Select field of the IDAC_CURPROG register is only 5 bits wide.
A 5-bit converter might seem rather limited, but for the purpose of waveform generation, it provides ample resolution when allowing 5 µs for the output to settle. Using this as a limiting factor, the IDAC can output a 32-entry wave table (twice that used for the hardware sine wave generator in the DAC) at a respectable 1 ÷ (32 steps × 5 µs/per step) = 6.25 kHz.
Knowing these parameters and the capabilities of the IDAC, the outline for wave generation software takes shape as follows:
Create a data structure with all the points of one iteration of the waveform, scaled to an output range of 0 to 31.
Setup one of the timers to generate a trigger for the DMA occurring at a rate that is the inverse of the number of points in the waveform times the desired frequency, keeping in mind the desired settling time (5 µs in this example).
Prepare the DMA to transfer each point of the waveform to the IDAC Current Programming Register in response to the trigger from the timer.
Enable the IDAC and set its output to an initial state, if desired.
Start the timer in order to begin outputting the waveform.
Restart the DMA in the callback function executed when the channel updating the IDAC completes all of its transfers.
The example Simplicity Studio project attached to this article runs on the Zero Gecko Starter Kit and outputs a nominally 6.25 kHz cosine wave on the IDAC0_OUT pin. A 50 kΩ resistor connected between pins 1 (GND) and 11 (PB11) of the Expansion Header allows the waveform to be observed on an oscilloscope. While the code follows the outline above, a couple of specific details are worth nothing.
First, it's not sufficient to write raw wave table values to the IDAC Current Programming Register. The STEPSEL field occupies bits [12:8] of the register, so the raw values must be shifted left to reflect this. In addition, the output current range of the IDAC is set by the RANGESEL field in bits [1:0]. Because all peripheral register writes must be 32 bits, each wave table entry written to IDACn_CURPROG must include the output range along with the step value. All of this is handled in cosine_table.c as shown below:
Second, note the use of ping-pong instead of basic DMA to service the IDAC. The purpose of ping-pong DMA is to keep a constant stream of data moving between a peripheral and memory by allowing the CPU to periodically step in and update the DMA with the next set of transfers.
As implemented on EFM32 devices, the DMA uses primary and alternate descriptors in ping-pong mode that contain information about the transfers to be processed. When all data specified by the primary descriptor is transferred, work begins on the alternate descriptor's transfers, and the DMA requests interrupt service. The callback function executed in the interrupt handler can update the primary descriptor to point to a new set of data and refresh the ping-pong configuration. On completion of the alternate descriptor's transfers, this process repeats but with the DMA again processing the primary descriptor while the alternate descriptor is updated in the callback function.
If the cosine frequency is 6.25 kHz, a "constant stream of data" to update the IDAC might seem overkill. In this example, the waveform consists of 32 points, so the actual update rate is a much higher 32 × 6.25 kHz = 200 kHz or one DMA transfer every 5 µs or 70 clock cycles at the default HFRCO frequency of 14 MHz.
The DMA takes 17 clock cycles per byte, halfword, or word, which would seem to be sufficiently fast to reproduce the waveform at the desired frequency. It is, but only for a single iteration of the waveform when basic DMA is used. After the IDAC is updated with the last wave table value, the callback function is executed to restart the DMA cycle, and herein lies the problem. The callback function is dispatched from emlib's DMA IRQ handler as shown below:
void DMA_IRQHandler(void)
{
int channel;
DMA_CB_TypeDef *cb;
uint32_t pending;
uint32_t pendingPrio;
uint32_t prio;
uint32_t primaryCpy;
int i;
/* Get all pending and enabled interrupts */
pending = DMA->IF;
pending &= DMA->IEN;
/* Assert on bus error. */
EFM_ASSERT(!(pending & DMA_IF_ERR));
/* Process all pending channel interrupts. First process channels */
/* defined with high priority, then those with default priority. */
prio = DMA->CHPRIS;
pendingPrio = pending & prio;
for (i = 0; i < 2; i++)
{
channel = 0;
/* Process pending interrupts within high/default priority group */
/* honouring priority within group. */
while (pendingPrio)
{
if (pendingPrio & 1)
{
DMA_DESCRIPTOR_TypeDef *descr = (DMA_DESCRIPTOR_TypeDef *)(DMA->CTRLBASE);
uint32_t chmask = 1 << channel;
/* Clear pending interrupt prior to invoking callback, in case it */
/* sets up another DMA cycle. */
DMA->IFC = chmask;
/* Normally, no point in enabling interrupt without callback, but */
/* check if callback is defined anyway. Callback info is always */
/* located in primary descriptor. */
cb = (DMA_CB_TypeDef *)(descr[channel].USER);
if (cb)
{
/* Toggle next-descriptor indicator always prior to invoking */
/* callback (in case callback reconfigurs something) */
primaryCpy = cb->primary;
cb->primary ^= 1;
if (cb->cbFunc)
{
cb->cbFunc(channel, (bool)primaryCpy, cb->userPtr);
}
}
}
pendingPrio >>= 1;
channel++;
}
/* On second iteration, process default priority channels */
pendingPrio = pending & ~prio;
}
}
Even for a single pending DMA channel interrupt, entry into and processing of this IRQ handler and execution of the callback function take more than the 70 clock cycles available when running at 14 MHz. This results in a delay between the last and first points of two subsequent waveforms as shown below:
Increasing the clock frequency minimizes the delay incurred processing the IRQ handler and callback function as does reproducing the waveform at a lower the frequency, but these options do not fix the underlying problem. Ping-pong DMA resolves this altogether by transferring two complete iterations of the waveform in series and refreshing the DMA descriptors between each one:
The example project that generates the waveform above is attached to this article and can be imported into Simplicity Studio v4 as follows:
In the Simplicity Studio IDE, go the File menu (not the Project menu) and select Import…
Under General, select Existing Projects into Workspace, then click Next.
For Select archive file, locate the project .ZIP file and select it.
Make sure Copy projects into workspace is checked.
Click Finish.
Build the project, download it to the Zero Gecko STK, and run it with the debugger. A 50 kΩ resistor connected between pins 11 (PB11/IDAC_OUT) and 1 (GND) on the Expansion Header allows the cosine waveform driven by the IDAC to be observed with an oscilloscope probe also connected to pin 11.
Is there a way to measure and verify the actual voltage of the EFx32xG1 internal 1.25 V or 2.5 V ADC references, such as by outputting the reference voltage to a pin?
Answer
The internal 2.5 V and 1.25 V references are based upon the internal bandgap reference (BGR), and the full scale reference value (i.e. 2.5V or 1.25V) is achieved through manipulation of input signal attenuation settings. Thus, there is not a 2.5 V reference value that could even be measured per se. Additionally, the internal reference sources can not be routed to a GPIO pin.
Additionally, because the ADC internal reference source in the EFx32xG1 device achieves different VFS values through input signal attenuation factors/dividers, the voltage of 2.5 V (or 1.25 V, or 5 V) does not actually exist in the device as a node voltage. Because of this, we do not specify any absolute voltages for the ADC reference sources, but instead specify a gain error for the ADC module (see "Gain error in ADC," EFR32xG1 datasheet, Table 4.39, section 4.1.16, page 75), which is -0.2% typical and 5% maximum. Thus, this specification can be interpreted to mean that the VFS when using the 2.5V internal reference option will fall within the range [2.35 V, 2.65 V] (worst case). Typical variation will be much tighter ([2.495 V, 2.505 V]). The ~0.833 V BGR reference from which the internal VFS levels are derived is calibrated for each device in production.
If you wish to monitor your ADC reference voltage directly, you can use an external reference by setting ADCn_SINGLECTRL.REF (or ADCn_SCANCTRL.REF) = EXTSINGLE (0x4) and connecting a reference source to the ADCn_EXTP reference pin (for single ended reference).
1) How do you use the APORT to select a pin as an ADC input?
2) What is the correct way to configure a pin for an analog function?
Answer
1) Control of the APORT and associated pin connections for the ADC is accomplished through your selections in the ADC input selection bitfields in ADCn_SINGLECTRL (i.e. NEGSEL and POSSEL) for single conversions. This is true in a similar way for other analog peripherals in the sense that those peripherals have a mechanism within their registers for selecting a particular APORT bus and channel as inputs/outputs to the module. For each of these peripherals, the act of selecting an APORT channel as an input or output will cause that peripheral to exert control of the necessary switching and internal device routing necessary to connect the pin assigned to that channel to the peripheral. Because of this, multiple peripherals requesting access to the same APORT bus can create conflicts, which can be configured to generate interrupts to aid in debugging the issue. For more information about APORT conflicts and generating interrupts on this condition in the ADC module, please refer to section 24.3.5.3 on page 765 of the EFR32xG1 reference manual.
Please note that the MCU pins assigned to each APORT bus and channel are fixed and are defined in each device specific datasheet (see [Pin Definitions] > [Analog Port (APORT)]). The ADC positive input mux is connected only to APORT 'X' buses, while the negative input mux is connected only to APORT 'Y' buses, as shown in Figure 24.5 on page 760 of the EFR32xG1 reference manual. The easiest way to select the correct input mux setting to correspond to the pin you wish to use as input to the ADC is to consult the device datasheet and find which APORT (0, 1, 2, 3, or 4), Bus (X or Y), and channel (0-15) correspond to the pin you wish to use. You can easily find this information in Table 7.4: APORT Client Map on page 68 of the BGM12x datasheet (ADC0 APORT selections begin on page 71). Matching this information to the options given for the NEGSEL and POSSEL fields of the ADCn_SINGLECTRL register (see page 783 of EFR32xG1 reference manual). Writing to these bitfields will immediately cause the ADC to take control of the selected APORT bus.
Also, please note that emlib provides a relatively simple API for selecting the APORT channel for the positive and negative (if used) input to the ADC, using the ADC_InitSingle_TypeDef structure in combination with the ADC_InitSingle() function. For example:
2) In the GPIO module, all you must do to use a GPIO pin as an analog input is to configure the pin mode to "disable" (to disable the input sense, output driver and pull resistors) and disable over-voltage tolerance for that pin (recommended for reduced distortion of analog signals):
GPIO_PinModeSet(gpioPortC, 6, gpioModeDisabled, 0); //set PC6 to DISABLE with no pull resistors GPIO->P[gpioPortC].OVTDIS |= (0x01 << 6); //Disable over-voltage tolerance for PC6
As for the distinction between APORT A, B, C, D, etc... These correspond to APORT0 APORT1, etc. This can be a little confusing. Each APORT (0-4) has an X and a Y bus.
Finally, GPIO pin configuration follows the same guidelines whether the pin is being used as an analog input or output (i.e. set pin to DISABLE w/o pull and disable over-voltage tolerance). For more information on using GPIO pins for analog functions, please refer to section 28.3.4.1 of the EFR32xG1 reference manual on page 918. Note that Push-Pull mode should be used for digital outputs, and alternate port control can be used to apply different slew rate, drive strength, and data input disable settings to digital pins in the same port grouping, but this does not apply for analog functionality.
Why does EM_AES in emlib use while loops on AES_STATUS instead of an interrupt or some other nonblocking method of waiting for completion?
Answer
The general principle of emlib is not to use interrupts to leave the usage of interrupts up to the user. You can modify this code to a nonblocking implementation of your choice if you do not want to wait on AES_STATUS.
32-bit Knowledge Base
Sine Wave Generation with the IDAC on Happy Gecko
Sine Wave Generation with the IDAC on Zero Gecko
mbed TLS FIP certification
Serial Wire Multidrop
EFx32xG1 ADC Voltage Reference (Can it be measured?)
Replacement EFR32 Wireless Starter Kit (WSTK) Mainboards (BRD4001A)
Clarifying APORT Usage for EFR32xG Analog Peripherals
EM_AES
Bit-banding through DMA
Production Programmer