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

This account has disabled anonymous posting.
If you don't have an account you can create one now.
HTML doesn't work in the subject.
More info about formatting

If you are unable to use this captcha for any reason, please contact us by email at support@dreamwidth.org

October 2025

S M T W T F S
   1234
567 89 1011
121314 15161718
19202122232425
2627 28293031 

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Feb. 25th, 2026 04:32 pm
Powered by Dreamwidth Studios