Емельянов Эдуард Владимирович (eddy_em) wrote,
Емельянов Эдуард Владимирович
eddy_em

АЦП на STM32

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

Tags: stm32, железяки
Subscribe

  • Чем бы таким заменить STM32F072C8T6?

    Полез сейчас на али цены посмотреть, а там… В среднем уже по 600-700 рублей за штучку просят! Вообще охамели. И это - гарантированно БУшные ведь!.. А…

  • Понаблюдал, блин!

    Опять у нас что-то с сетью поломали. Хотел было протестировать, как наша подвесная часть оптоволоконного спектрографа работает, а из дома связь с…

  • Релюшки на CAN-шине

    Закончил с прошивкой для новой железяки. Как "наследница" USB-CAN переходника, она умеет все то же самое + несколько специфичных вещей (опрос…

promo eddy_em august 17, 2019 12:33 3
Buy for 10 tokens
Юра намедни напечатал корпус для хронометра. Для первого блина получилось неплохо: И еще немного фотографий:
  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic
  • 0 comments