АЦП на STM32

Nov 21, 2012 16:34

Сегодня я решил попробовать поупражняться с аналоговым коммутатором 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В питания к коммутатору и посмотреть, что выйдет.

железяки, stm32

Previous post Next post
Up