/* * 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 0x88 #define TOUCH_DEFAULT_THRESH 0x800 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}; void adc_init() { 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[1].chan = ADC_Channel_1; adc_chan[2].chan = ADC_Channel_2; adc_chan[3].chan = ADC_Channel_3; adc_chan[4].chan = ADC_Channel_4; adc_chan[5].chan = ADC_Channel_8; adc_chan[6].chan = ADC_Channel_13; 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); } int8_t adc_get_tkey(uint8_t key_idx) { if (key_idx > ADC_CHANNELS) return 0; if (adc_chan[key_idx].avg < adc_chan[key_idx].thresh) { return 1; } return 0; } void adc_next() { AdcChan *chan; uint8_t timeout = 0; // todo: // lightsense is timing-sensitive. will need to have this as an overridden channel? // get next index adc_idx++; if (adc_idx > (sizeof(adc_chan) / sizeof(adc_chan[0]))) { adc_idx = 0; } // so long as the channel is enabled while (!(adc_chan[adc_idx].type & CHAN_ENABLED) && (timeout++ < ADC_CHANNELS)) { adc_idx++; } // if nothing is enabled, break out if (timeout == ADC_CHANNELS) return; chan = &adc_chan[adc_idx]; // configure sampling switch (chan->type) { 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; } } // flags cleared by software ADC1->STATR = 0; }