1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/cpu/kinetis/periph/adc.c
Joakim Nohlgård dd2fad6b9b kinetis: ADC: Add hardware averaging configuration
This allows a board configuration to select which kind of averaging
should be applied to the pin instead of always using 32 sample average.
Very high impedance sources may need to disable hardware averaging in
order to give correct values, the on-chip temperature sensor is one such
signal source.
2018-09-12 16:32:48 +02:00

239 lines
6.5 KiB
C

/*
* Copyright (C) 2014-2016 Freie Universität Berlin
* Copyright (C) 2014 PHYTEC Messtechnik GmbH
* Copyright (C) 2015 Eistec AB
*
* This file is subject to the terms and conditions of the GNU Lesser General
* Public License v2.1. See the file LICENSE in the top level directory for more
* details.
*/
/**
* @ingroup cpu_kinetis
* @ingroup drivers_periph_adc
* @{
*
* @file
* @brief Low-level ADC driver implementation
*
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
* @author Johann Fischer <j.fischer@phytec.de>
* @author Jonas Remmert <j.remmert@phytec.de>
* @author Joakim Nohlgård <joakim.nohlgard@eistec.se>
*
* @}
*/
#include <stdio.h>
#include "cpu.h"
#include "bit.h"
#include "mutex.h"
#include "periph/adc.h"
/**
* @brief Maximum clock speed
*
* The ADC clock speed must be configured to be >2MHz and <12MHz in order for
* the ADC to work. For ADC calibration to work as intended, the ADC clock speed
* must further no be >4MHz, so we target a ADC clock speed > 2 and <4MHz.
*/
#define ADC_MAX_CLK (4000000U)
static mutex_t locks[] = {
MUTEX_INIT,
#ifdef ADC1
MUTEX_INIT,
#endif
};
static inline ADC_Type *dev(adc_t line)
{
return adc_config[line].dev;
}
static inline int dev_num(adc_t line)
{
if (dev(line) == ADC0) {
return 0;
}
#ifdef ADC1
else if (dev(line) == ADC1) {
return 1;
}
#endif
return -1;
}
static inline void prep(adc_t line)
{
if (dev(line) == ADC0) {
bit_set32(&SIM->SCGC6, SIM_SCGC6_ADC0_SHIFT);
}
#ifdef ADC1
else if (dev(line) == ADC1) {
#if defined(SIM_SCGC3_ADC1_SHIFT)
bit_set32(&SIM->SCGC3, SIM_SCGC3_ADC1_SHIFT);
#elif defined(SIM_SCGC6_ADC1_SHIFT)
bit_set32(&SIM->SCGC6, SIM_SCGC6_ADC1_SHIFT);
#else
#error ADC1 clock gate is not known!
#endif
}
#endif /* ADC1 */
mutex_lock(&locks[dev_num(line)]);
}
static inline void done(adc_t line)
{
if (dev(line) == ADC0) {
bit_clear32(&SIM->SCGC6, SIM_SCGC6_ADC0_SHIFT);
}
#ifdef ADC1
else if (dev(line) == ADC1) {
#if defined(SIM_SCGC3_ADC1_SHIFT)
bit_clear32(&SIM->SCGC3, SIM_SCGC3_ADC1_SHIFT);
#elif defined(SIM_SCGC6_ADC1_SHIFT)
bit_clear32(&SIM->SCGC6, SIM_SCGC6_ADC1_SHIFT);
#endif
}
#endif /* ADC1 */
mutex_unlock(&locks[dev_num(line)]);
}
/**
* @brief Perform ADC calibration routine.
*
* This is a recipe taken straight from the Kinetis K60 reference manual. It has
* been tested on MK60DN512VLL10.
*
* @param[in] dev Pointer to ADC device to operate on.
*
* @return 0 on success
* @return <0 on errors
*/
int kinetis_adc_calibrate(ADC_Type *dev)
{
uint16_t cal;
dev->SC3 |= ADC_SC3_CAL_MASK;
while (dev->SC3 & ADC_SC3_CAL_MASK) {} /* wait for calibration to finish */
while (!(dev->SC1[0] & ADC_SC1_COCO_MASK)) {}
if (dev->SC3 & ADC_SC3_CALF_MASK) {
/* calibration failed for some reason, possibly SC2[ADTRG] is 1 ? */
return -1;
}
/* From the reference manual */
/* 1. Initialize or clear a 16-bit variable in RAM. */
/* 2. Add the plus-side calibration results CLP0, CLP1, CLP2, CLP3, CLP4,
* and CLPS to the variable. */
cal = dev->CLP0 + dev->CLP1 + dev->CLP2 + dev->CLP3 + dev->CLP4 + dev->CLPS;
/* 3. Divide the variable by two. */
cal /= 2;
/* 4. Set the MSB of the variable. */
cal |= (1 << 15);
/* (5. The previous two steps can be achieved by setting the carry bit,
* rotating to the right through the carry bit on the high byte and again on
* the low byte.)
* We ignore the above optimization suggestion, we most likely only perform
* this calibration on startup and it will only save nanoseconds. */
/* 6. Store the value in the plus-side gain calibration register PG. */
dev->PG = cal;
/* 7. Repeat the procedure for the minus-side gain calibration value. */
cal = dev->CLM0 + dev->CLM1 + dev->CLM2 + dev->CLM3 + dev->CLM4 + dev->CLMS;
cal /= 2;
cal |= (1 << 15);
dev->MG = cal;
return 0;
}
int adc_init(adc_t line)
{
/* make sure the given line is valid */
if (line >= ADC_NUMOF) {
return -1;
}
/* prepare the device: lock and power on */
prep(line);
/* configure the connected pin mux */
if (adc_config[line].pin != GPIO_UNDEF) {
gpio_init_port(adc_config[line].pin, GPIO_AF_ANALOG);
}
/* The ADC requires at least 2 MHz module clock for full accuracy, and less
* than 12 MHz */
/* For the calibration it is important that the ADC clock is <= 4 MHz */
uint32_t adiv;
if (CLOCK_BUSCLOCK > (ADC_MAX_CLK << 3)) {
#if KINETIS_HAVE_ADICLK_BUS_DIV_2
/* Some CPUs, e.g. MK60D10, MKW22D5, provide an additional divide by two
* divider for the bus clock as CFG1[ADICLK] = 0b01
*/
adiv = ADC_CFG1_ADIV(3) | ADC_CFG1_ADICLK(1);
#else
/* Newer CPUs seem to have replaced this with various alternate clock
* sources instead */
adiv = ADC_CFG1_ADIV(3);
#endif
}
else {
unsigned int i = 0;
while ((i < 3) && (CLOCK_BUSCLOCK > (ADC_MAX_CLK << i))) {
++i;
}
adiv = ADC_CFG1_ADIV(i);
}
/* set configuration register 1: clocking and precision */
/* Set long sample time */
dev(line)->CFG1 = ADC_CFG1_ADLSMP_MASK | adiv;
/* select ADxxb channels, longest sample time (20 extra ADC cycles) */
dev(line)->CFG2 = ADC_CFG2_MUXSEL_MASK | ADC_CFG2_ADLSTS(0);
/* select software trigger, external ref pins */
dev(line)->SC2 = ADC_SC2_REFSEL(ADC_REF_SETTING);
/* set an arbitrary input channel configuration */
dev(line)->SC3 = 0;
dev(line)->SC1[0] = ADC_SC1_ADCH(31);
/* perform calibration routine */
int res = kinetis_adc_calibrate(dev(line));
done(line);
return res;
}
int adc_sample(adc_t line, adc_res_t res)
{
int sample;
/* check if resolution is applicable */
if (res > 0xf0) {
return -1;
}
/* lock and power on the device */
prep(line);
/* set resolution */
dev(line)->CFG1 &= ~(ADC_CFG1_MODE_MASK);
dev(line)->CFG1 |= (res);
/* set the chosen hardware averaging setting for this channel */
dev(line)->SC3 = adc_config[line].avg;
/* select the channel that is sampled */
dev(line)->SC1[0] = adc_config[line].chan;
/* wait until conversion is complete */
while (!(dev(line)->SC1[0] & ADC_SC1_COCO_MASK)) {}
/* read and return result */
sample = (int)dev(line)->R[0];
/* power off and unlock the device */
done(line);
return sample;
}