/* * adc.c * * todo: * - use DMA */ #include "ch32x035_conf.h" #define ADC_CHANNELS 8 #define CHAN_ENABLED 0x80 #define CHAN_MASK 0x7f #define CHAN_IS_NORMAL 0 #define CHAN_IS_TOUCH 1 #define CHAN_IS_LIGHTSENSE 2 #define TOUCH_DEFAULT_CHARGE 0x4c #define TOUCH_DEFAULT_THRESH 0x800 #define TOUCH_HYSTERESIS 66 typedef struct AdcChan { uint8_t chan; // ADC channel number uint8_t type; // ADC channel type (use above defines) uint8_t timing; // sample rate for normal channels, charge values for touch-type channels uint8_t idx; // rawval index uint16_t avg; // averaged output uint16_t thresh; // touch threshold uint16_t rawval[8]; // raw counts } AdcChan; static uint8_t adc_idx = 0; static AdcChan adc_chan[ADC_CHANNELS] = {0}; static uint32_t touch_status = 0; void adc_init() { NVIC_InitTypeDef nvic; ADC_InitTypeDef adc = {0}; // configure pin struct for (uint8_t i = 0; i < ADC_CHANNELS; i++) { adc_chan[i].type = CHAN_ENABLED | CHAN_IS_TOUCH; adc_chan[i].timing = TOUCH_DEFAULT_CHARGE; adc_chan[i].thresh = TOUCH_DEFAULT_THRESH; } adc_chan[0].chan = ADC_Channel_0; adc_chan[0].timing = 0x4f; adc_chan[0].thresh = 3380; adc_chan[1].chan = ADC_Channel_1; adc_chan[1].thresh = 3580; adc_chan[2].chan = ADC_Channel_2; adc_chan[2].timing = 0x6f; adc_chan[2].thresh = 3380; adc_chan[3].chan = ADC_Channel_3; adc_chan[3].timing = 0x54; adc_chan[3].thresh = 3450; adc_chan[4].chan = ADC_Channel_4; adc_chan[4].timing = 0x44; adc_chan[4].thresh = 3000; adc_chan[5].chan = ADC_Channel_8; adc_chan[5].thresh = 3380; adc_chan[6].chan = ADC_Channel_13; adc_chan[6].timing = 0x4a; adc_chan[6].thresh = 3550; adc_chan[7].chan = ADC_Channel_9; adc_chan[7].type = CHAN_IS_LIGHTSENSE; adc_chan[7].timing = ADC_SampleTime_7Cycles; // configure actual peripheral ADC_DeInit(ADC1); ADC_CLKConfig(ADC1, ADC_CLK_Div6); adc.ADC_Mode = ADC_Mode_Independent; adc.ADC_ScanConvMode = DISABLE; adc.ADC_ContinuousConvMode = DISABLE; adc.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; adc.ADC_DataAlign = ADC_DataAlign_Right; adc.ADC_NbrOfChannel = 1; ADC_Init(ADC1, &adc); ADC_Cmd(ADC1, ENABLE); // configure ADC interrupt nvic.NVIC_IRQChannel = ADC1_IRQn; nvic.NVIC_IRQChannelPreemptionPriority = 0; nvic.NVIC_IRQChannelSubPriority = 4; nvic.NVIC_IRQChannelCmd = ENABLE; // enable interrupt ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE); ADC1->STATR = 0; // clear flags NVIC_Init(&nvic); } int8_t adc_get_tkey(uint8_t key_idx) { return (touch_status & (1 << key_idx) ? 1 : 0); } __attribute((section(".ramfunc"))) void adc_next() { AdcChan *chan; volatile uint8_t timeout = 0; // todo: // lightsense is timing-sensitive. will need to have this as an overridden channel? // get next index // so long as the channel is enabled do { adc_idx++; adc_idx %= (sizeof(adc_chan) / sizeof(adc_chan[0])); if (adc_chan[adc_idx].type & CHAN_ENABLED) break; } while (timeout++ < ADC_CHANNELS); // if nothing is enabled, break out if (timeout == ADC_CHANNELS) return; chan = &adc_chan[adc_idx]; // configure sampling switch (chan->type & ~CHAN_ENABLED) { case CHAN_IS_NORMAL: { TKey1->CTLR1 &= ~ADC_TKENABLE; // disable TouchKey // we don't have any break; } case CHAN_IS_TOUCH: { TKey1->CTLR1 |= ADC_TKENABLE; // enable TouchKey ADC_RegularChannelConfig(ADC1, chan->chan, 1, ADC_SampleTime_11Cycles); TKey1->IDATAR1 = chan->timing & 0xf0; // R32_TKEY1_CHGOFFSET Charging Time TKey1->RDATAR = chan->timing & 0x0f; // R32_TKEY1_ACT_DCG Discharging Time // per the RM, setting RDATAR (ACT_DCG) starts the conversion break; } case CHAN_IS_LIGHTSENSE: { TKey1->CTLR1 &= ~ADC_TKENABLE; // disable TouchKey // todo: use thresh as a "timeout" value maybe? // this way the other channels can be sampled, and each time we pass by // the lightsensor, if it isn't time to sample it, it is skipped. ADC_RegularChannelConfig(ADC1, chan->chan, 1, chan->timing & 0xf); ADC_SoftwareStartConvCmd(ADC1, ENABLE); break; } } } void adc_isr() { AdcChan *chan; uint32_t v = 0; uint8_t amax, ashift; chan = &adc_chan[adc_idx]; // conversion done if (ADC1->STATR & ADC_EOC) { chan->rawval[chan->idx] = ADC1->RDATAR; // how many averages should we do? switch (chan->type & CHAN_MASK) { case CHAN_IS_TOUCH: { amax = 4; ashift = 2; break; } default: { amax = 8; ashift = 3; break; } } chan->idx++; if (chan->idx >= amax) { chan->idx = 0; for (int i = 0; i < amax; i++) { v += chan->rawval[i]; } chan->avg = v >> ashift; // calculate if touched if measuring a touch sensor if (amax == 4) { if (chan->avg <= chan->thresh) { touch_status |= (1 << adc_idx); } if (chan->avg > (chan->thresh + TOUCH_HYSTERESIS)) { touch_status &= ~(1 << adc_idx); } } } } // flags cleared by software ADC1->STATR = 0; }