Сегодня я решил попробовать поупражняться с аналоговым коммутатором ADG506A.
Идея такая: к 16 входам коммутатора поочередно подключаются термосопротивления, вторая нога которых сидит на земле. Выход коммутатора подключен к аналоговому входу МКшки, через резистор подтянутый на +5В. В результате АЦП контроллера измеряет падение напряжения на термометрах, а выбор термометра осуществляется посредством задания цифрового адреса четырьмя битами какого-нибудь выходного порта.
Код -
все тот же.
Изучая спецификацию на коммутатор, я "внезапно" обнаружил, что питается-то он не пятью вольтами, а жрет аж 10..16В! Поэтому для начала я решил "потренироваться на кошках", а именно: написать нужную прошивку и посмотреть, как вообще себя ведет АЦП.
Примеров работы STM32 с АЦП полным-полно, я взял пример из STDPeriphLib. Я решил попробовать запустить непрерывное преобразование с обновлением результата в памяти при помощи DMA. Время преобразования устанавливаю в самое большое (чтобы поточнее было), а сам вход АЦП пока что повешу на ногу PB0 (ADC8).
// 0. Configure ADC8 (PB0) as analog input (clocking GPIOB sat on in onewire.c)
RCC_ADCCLKConfig(RCC_PCLK2_Div4);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 1. DMA for converted value (DMA1 clocking sat on at onewire.c)
//RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_DeInit(DMA1_Channel1);
DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ADC_value;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = 1;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1, ENABLE);
// 2. ADC1 config
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
// Connect ADC to ADC8 (PB0),
ADC_RegularChannelConfig(ADC1, ADC_Channel_8, 1, ADC_SampleTime_239Cycles5);
// Enable ADC1 DMA
ADC_DMACmd(ADC1, ENABLE);
ADC_Cmd(ADC1, ENABLE);
// Calibration of ADC1
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
ADC_SoftwareStartConvCmd(ADC1, ENABLE); // turn conversion on
Еще нужно сконфигурировать пять бит управляющего порта. Чтобы не париться с преобразованием бит, я просто взял первые четыре бита порта C в качестве адреса, а пятый бит - в качестве ключа, включающего коммутатор.
GPIO_InitStructure.GPIO_Pin = 0x1f; // first 5 bits of PC0
// PC0..PC3 - analog channel address, PC4 - analog enable switch
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOC, &GPIO_InitStructure);
Прерываний здесь никаких не надо, а в файле interrupts.c надо дописать установку нового флага при поступлении команды (скажем, команды 'a') отображения напряжения на датчиках. В main() добавим обработку этого флага после обработки флага отображения показаний датчика Холла.
}else if(FLAGS & FLAG_PRINTADC){
prntADC();
FLAGS ^= FLAG_PRINTADC;
}
// changhe channel address & turn on switch
GPIOC->BSRR = address;
Delay(2); // wait for AD conversion
// put data into appropriate array item
temperatures[address & 0x0f] = ADC_value;
// turn off switch & reset bits
GPIOC->BRR = (uint32_t)0x1f;
// increment address
if(++address == 0x20) address = 0x10; // reset address on overflow
Delay(98); // sleep
и добавить фукнцию
inline void prntADC(){
uint8_t *_2b = (uint8_t *) temperatures;
uint8_t i;
for(i = 0; i < 32; i+=2){
prnt((uint8_t*)"Temperature ");
printInt(i>>1); prnt((uint8_t*)" = ");
printInt(_2b[i+1]);
printInt(_2b[i]);
newline();
Delay(2); // delay to flush USB buffer
}
}
Итак, если нужный флаг установлен, вызывается функция отображения напряжений на всех 16-ти каналах.
В бесконечном цикле внутри main() после обработки флагов запускаем считывание очередного значения напряжения: устанавливаем адрес устройства (заодно разрешая ему включить нагрузку, т.к. пятый бит переменной address равен единице. Потом спим пару миллисекунд, чтобы АЦП наверняка считал показания напряжения, заносим считанное значение в текущую ячейку и сбрасываем адрес и собственно коммутатор. После этого спим 98мс, чтобы не дергать железо слишком часто.
При последних модификациях наткнулся на странную штуку: при проверке записанных данных выдается ошибка:
2012-11-21T15:59:44 INFO src/stlink-common.c: Starting verification of write complete
2012-11-21T15:59:44 WARN src/stlink-common.c: Verification of flash failed at offset: 0
stlink_fwrite_flash() == -1
make: *** [load] Ошибка 255
При этом можно запустить make load раз 20 подряд, и каждый раз будет эта ошибка. Ситуацию спасло только принудительное стирание:
st-flash erase
2012-11-21T15:59:56 INFO src/stlink-common.c: Loading device parameters....
2012-11-21T15:59:56 INFO src/stlink-common.c: Device connected is: F1 Medium-density device, id 0x20036410
2012-11-21T15:59:56 INFO src/stlink-common.c: SRAM size: 0x5000 bytes (20 KiB), Flash: 0x20000 bytes (128 KiB) in pages of 1024 bytes
Mass erasing
и то, помогало не с первой попытки.
Неужели у МКшки уже "мозги набекрень"? Вроде, я еще и тысячи раз его не прошил…
Итак, к порту PB0 (ADC8) я подключил делитель напряжения на переменном резисторе. Вот такой получается выхлоп:
Temperature 0x00 = 0x02 0xe9
Temperature 0x01 = 0x02 0xea
Temperature 0x02 = 0x02 0xea
Temperature 0x03 = 0x02 0xe8
Temperature 0x04 = 0x02 0xe8
Temperature 0x05 = 0x02 0xe9
Temperature 0x06 = 0x02 0xea
Temperature 0x07 = 0x02 0xe9
Temperature 0x08 = 0x02 0xe9
Temperature 0x09 = 0x02 0xeb
Temperature 0x0a = 0x02 0xe8
Temperature 0x0b = 0x02 0xe9
Temperature 0x0c = 0x02 0xe9
Temperature 0x0d = 0x02 0xea
Temperature 0x0e = 0x02 0xea
Temperature 0x0f = 0x02 0xe9
Учитывая то, что измеряется каждый раз одно и то же падение напряжения, можно сделать вывод, что либо напряжение питания USB "скачет", либо такая уж плохая точность АЦП: в реальности оно получается чуть лучше, чем дясятибитным.
В принципе, если "растянуть" рабочий диапазон (примерно 250К) на промежуток 0..3.3В, можно будет даже отбрасывая последние два бита от полученного значения, достигнем точности измерения порядка .25К, чего с лихвой хватит для измерения температуры всех сильно охлаждаемых узлов спектрометра (кроме светоприемника, но его термостабилизацию будет обеспечивать система сбора, я к ней не причастен).
Так как задержки между измерениями составляют примерно 0.1с, можно посмотреть, как заполняются массивы, для этого буду крутить ползунок резистора и нажму "a". Вот что получается при плавном повороте от нуля до +3.3В:
Temperature 0x00 = 0x00 0x0b
Temperature 0x01 = 0x00 0x08
Temperature 0x02 = 0x00 0x4f
Temperature 0x03 = 0x00 0x77
Temperature 0x04 = 0x00 0xb5
Temperature 0x05 = 0x00 0xfe
Temperature 0x06 = 0x01 0x39
Temperature 0x07 = 0x01 0xd9
Temperature 0x08 = 0x03 0x1a
Temperature 0x09 = 0x04 0x28
Temperature 0x0a = 0x06 0x6e
Temperature 0x0b = 0x08 0xfa
Temperature 0x0c = 0x0b 0x09
Temperature 0x0d = 0x0d 0xc2
Temperature 0x0e = 0x0f 0xfd
Temperature 0x0f = 0x0f 0xff
(натренировать нужную скорость вращения получилось далеко не с первой попытки).
Завтра, если будет время, попробую собрать на макетке делитель напряжения на грозди резисторов (чтобы на ногах коммутатора получать постепенно возрастающее напряжение), прицепить 12В питания к коммутатору и посмотреть, что выйдет.