Форум программистов, компьютерный форум, киберфорум
Наши страницы
Микроконтроллеры ARM, Cortex, STM32
Войти
Регистрация
Восстановить пароль
 
Рейтинг 4.67/9: Рейтинг темы: голосов - 9, средняя оценка - 4.67
_SayHello
536 / 299 / 98
Регистрация: 30.07.2015
Сообщений: 1,064
#1

Stm32f303vc + ADC (Dual Mode) + DMA. Оцифровываем синхронно

05.04.2017, 10:44. Просмотров 1839. Ответов 6

Добрый день. Поковырялся немного с АЦП в f303 и решил осилить Dual Mode. Вкратце зачем он нужен: у STMов заожена куча возможностей АЦП : оцифровка регулярных каналов, инжектированных каналов, и того и другого. Оцифровка одного канала или сразу секции с различными приоритетами и временами накопления для каждого канала. Так же есть прерывистый режим, когда секция разбивается на подсекции и они оцифровываются по очереди, по каждому пинку программного или внутреннего триггера. Ну и конечно данные можно вытаскивать с помощью DMA.
У меня появилась необходимость снимать показания с датчика подключенного по трехпроводной линии. Кто не в курсе, объясню: датчик представляет собой сопротивление меняющееся от температуры, казалось бы что сложного, кидаем один конец на землю пропускаем через него постоянный(известный нам) ток, а на верхнем выводе меряем падение напряжения. Но прелесть таких датчиков, в том, что они находятся непосредственно на плате, а могут находиться за углом на длинных проводах. И эти самые провода получаются включены последовательно с самим термосопротивлением. Учитывая, что dR/dT составляет доли Ома на градус, при большой длине проводов, можем обмануться на несколько градусов, что не хорошо. И тут придумали умные люди компенсацию сопротивления проводов. К верхнему или нижнему выводу присоединяется провод, параллельный выводному, и он заводится на второй канал АЦП. Зачем все это нужно: после оцифровки двух каналов мы получаем два падения напряжения : 1 - датчик + паразитные сопротивления, 2 датчик + паразитные сопротивления + паразитное сопротивления доп. провода. Найдем их разность и получим падение на проводе, ну и вычтем из первого значения две эти разности и получим чистое сопротивление датчика. При условии, что провода одной длины, одного сечения и материала. Для большей точности могут применять четырехпроводные схемы.
Мое первое решение: настраиваем 2 канала АЦП в одну секцию и оцифровываем по очереди.
Инициализация:
Кликните здесь для просмотра всего текста
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
void InitADC1()
{
     //Тактирование ADC1, DMA1, и GPIOA 
     RCC_ADCCLKConfig(RCC_ADC12PLLCLK_Div2);
     RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
     RCC_AHBPeriphClockCmd(RCC_AHBPeriph_ADC12, ENABLE);
     RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
 
     //Настроим 2 канала 1 и 2
     ADC1_GPIO_Structure.GPIO_Pin = (GPIO_Pin_0 | GPIO_Pin_1);
     ADC1_GPIO_Structure.GPIO_Mode = GPIO_Mode_AN;
     GPIO_Init(GPIOA, &ADC1_GPIO_Structure);
     DMA_DeInit(DMA1_Channel1);
     DMA1_InitStructure.DMA_PeripheralBaseAddr = (unsigned int)&ADC1->DR; // Откуда берем
     DMA1_InitStructure.DMA_MemoryBaseAddr = (unsigned int)&MB_table.regs[20]; // Куда кладем
     DMA1_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // Напраление из периферии в память
     DMA1_InitStructure.DMA_BufferSize = 2; // Два канала - 2 значения
     DMA1_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //Адрес переферии не инкрементируем
     DMA1_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //Адрес в памяти увеличиваем, чтобы данные не накладывались 
     DMA1_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //Размер результата преобразования 2 байта    
     DMA1_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //размер ячейки в памяти 2 байта
     DMA1_InitStructure.DMA_Mode = DMA_Mode_Circular; // Зацикливаем работу DMA
     DMA1_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
     DMA1_InitStructure.DMA_M2M = DMA_M2M_Disable;
     DMA_Init(DMA1_Channel1, &DMA1_InitStructure);
 
     // стандартная процедура по даташиту - калибровка и регулятор 
     ADC_DeInit(ADC1);
     ADC_VoltageRegulatorCmd(ADC1, ENABLE);
     ADC_SelectCalibrationMode(ADC1, ADC_CalibrationMode_Single);
     ADC_StartCalibration(ADC1);
     while(ADC_GetCalibrationStatus(ADC1) != RESET );
 
     // Циклическую оцифровку можем включить, но я хочу пинать АЦП программно   
     ADC1_InitStructure.ADC_ContinuousConvMode = ADC_ContinuousConvMode_Disable;
     ADC1_InitStructure.ADC_Resolution = ADC_Resolution_12b; // Разрешение 12 бит
     ADC1_InitStructure.ADC_ExternalTrigConvEvent = ADC_ExternalTrigConvEvent_0; // Программный триггер
     ADC1_InitStructure.ADC_ExternalTrigEventEdge = ADC_ExternalTrigEventEdge_None;
     ADC1_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // выравнивание вправо
     ADC1_InitStructure.ADC_OverrunMode = ADC_OverrunMode_Disable;
     ADC1_InitStructure.ADC_AutoInjMode = ADC_AutoInjec_Disable;
     ADC1_InitStructure.ADC_NbrOfRegChannel = 2; // два канала оцифровываем
     ADC_Init(ADC1, &ADC1_InitStructure);
     //Тут приоритет и время сэмплирования
     ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_601Cycles5);
     ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 2, ADC_SampleTime_601Cycles5);
     
     //Вторая структура: независимый режим и асинхронное тактирование
     ADC1_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
     ADC1_CommonInitStructure.ADC_Clock = ADC_Clock_AsynClkMode;
     //Это доступ DMA к общему регистру данных, не путать с нашим ADC1->DR
     ADC1_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
     ADC_CommonInit(ADC1, &ADC1_CommonInitStructure);
    //Ну и тут мы все включаем и поехали
     ADC_DMAConfig(ADC1, ADC_DMAMode_Circular);
     ADC_DMACmd(ADC1, ENABLE);
     DMA_Cmd(DMA1_Channel1, ENABLE);
     ADC_Cmd(ADC1, ENABLE);
     while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_RDY));
}

Пинаем АЦП в main:
Кликните здесь для просмотра всего текста
C++
1
2
3
4
5
6
7
8
9
10
void vMeasurements(void *pvParameters)
{
    portTickType xLastWakenTime;
    xLastWakenTime = xTaskGetTickCount();
    while(1)
    {
        ADC_StartConversion(ADC1);
        vTaskDelayUntil(&xLastWakenTime, 20);
    }
}

Первые грабли: значения АЦП всегда плавают и флуктуируют, при хорошей схемотехнике флуктуации малы, но младшие разряды гуляют вверх да вниз от точного значения, да и черт с ними, потом отфильтруем. Так как преобразование происходит последовательно, между ними есть временной промежуток в который наши любимые шумы могут воздействовать по разному. Получаем картинку номер 1. Очевидно, что U(датчик+провод) должно быть > U(датчик). Но по факту из-за задержки между оцифровками показания хаотичны из за шумов. Возможно после фильтрации Условие выполнится, а вдруг нет? Тогда вся компенсация коту под хвост.
И тут вступает в игру режим Dual Mode. Что это такое: в данном камне 4 независимых АЦП, 1 и 2, 3 и 4 мы можем настроить в парную работу где 1 и 3 АЦП мастера, а 2 и 4 их слейвы. В чем прелесть? В том что мы можем настроить по одному канала 1 и 2 АЦП и по пинку мастера они синхронно оцифруют данные. Так же в данном режиме есть куча разных плюшек см. в мануале.
Проинициализируем и посмотрим на результаты:
Кликните здесь для просмотра всего текста
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
 void InitADC1()
{
     //Тактирование ADC12, DMA1, и GPIOA
     RCC_ADCCLKConfig(RCC_ADC12PLLCLK_Div2);
     RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
     RCC_AHBPeriphClockCmd(RCC_AHBPeriph_ADC12, ENABLE);
     RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
 
 
     //Настроим 2 канала #2 для ADC1 и #2 для ADC2
     ADC1_GPIO_Structure.GPIO_Pin = ( GPIO_Pin_1 | GPIO_Pin_5);
     ADC1_GPIO_Structure.GPIO_Mode = GPIO_Mode_AN;
     GPIO_Init(GPIOA, &ADC1_GPIO_Structure);
 
     DMA_DeInit(DMA1_Channel1);
     // Данные берем из общего регистра(!) он 4 байтный старшие 2 байта с ADC2, младшие ADC1
     DMA1_InitStructure.DMA_PeripheralBaseAddr = (unsigned int)&ADC1_2->CDR;
     //Кладем куда надо ( у меня массив с двухбайтными ячейками, поэтому аказываем адрес первой
     // размер буффера указываю 1, и длину слова целиком. Таким образом
     //результат преобразование аккуратно поместится в 2 ячейки моего массива
     DMA1_InitStructure.DMA_MemoryBaseAddr = (unsigned int)&MB_table.regs[20];
     DMA1_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
     DMA1_InitStructure.DMA_BufferSize = 1;
     DMA1_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
     DMA1_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
     DMA1_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; // Слово целиком
     DMA1_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; // Слово целиком
     DMA1_InitStructure.DMA_Mode = DMA_Mode_Circular;
     DMA1_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
     DMA1_InitStructure.DMA_M2M = DMA_M2M_Disable;
     DMA_Init(DMA1_Channel1, &DMA1_InitStructure);
 
     //Настраиваем каждый АЦП отдельно, аналогично с первым опытом
 
     ADC_DeInit(ADC1);
     ADC_VoltageRegulatorCmd(ADC1, ENABLE);
     ADC_SelectCalibrationMode(ADC1, ADC_CalibrationMode_Single);
     ADC_StartCalibration(ADC1);
     while(ADC_GetCalibrationStatus(ADC1) != RESET );
 
     ADC1_InitStructure.ADC_ContinuousConvMode = ADC_ContinuousConvMode_Disable;
     ADC1_InitStructure.ADC_Resolution = ADC_Resolution_12b;
     ADC1_InitStructure.ADC_ExternalTrigConvEvent = ADC_ExternalTrigConvEvent_0;
     ADC1_InitStructure.ADC_ExternalTrigEventEdge = ADC_ExternalTrigEventEdge_None;
     ADC1_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
     ADC1_InitStructure.ADC_OverrunMode = ADC_OverrunMode_Disable;
     ADC1_InitStructure.ADC_AutoInjMode = ADC_AutoInjec_Disable;
     ADC1_InitStructure.ADC_NbrOfRegChannel = 1;
     ADC_Init(ADC1, &ADC1_InitStructure);
     //второй канал ADC1
     ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 1, ADC_SampleTime_601Cycles5);
 
     //ADC_DeInit(ADC1); (!) Вот так не делаем! Иначе получится лажа.
     ADC_VoltageRegulatorCmd(ADC2, ENABLE);
     ADC_SelectCalibrationMode(ADC2, ADC_CalibrationMode_Single);
     ADC_StartCalibration(ADC2);
     while(ADC_GetCalibrationStatus(ADC2) != RESET );
 
    //Аналогичная настройка
     ADC2_InitStructure.ADC_ContinuousConvMode = ADC_ContinuousConvMode_Disable;
     ADC2_InitStructure.ADC_Resolution = ADC_Resolution_12b;
     ADC2_InitStructure.ADC_ExternalTrigConvEvent = ADC_ExternalTrigConvEvent_0;
     ADC2_InitStructure.ADC_ExternalTrigEventEdge = ADC_ExternalTrigEventEdge_None;
     ADC2_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
     ADC2_InitStructure.ADC_OverrunMode = ADC_OverrunMode_Disable;
     ADC2_InitStructure.ADC_AutoInjMode = ADC_AutoInjec_Disable;
     ADC2_InitStructure.ADC_NbrOfRegChannel = 1;
     ADC_Init(ADC2, &ADC2_InitStructure);
    //Второй канал ADC2
     ADC_RegularChannelConfig(ADC2, ADC_Channel_2, 1, ADC_SampleTime_601Cycles5);
 
    //Вот тут основа:
     ADC1_CommonInitStructure.ADC_Mode = ADC_Mode_RegSimul; // Dual-mode синхронная оцифровка регулярных каналов
     ADC1_CommonInitStructure.ADC_Clock = ADC_Clock_AsynClkMode;
     ADC1_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_1; // Доступ DMA к общему регистру, при 12-бит оцифровке
     ADC1_CommonInitStructure.ADC_DMAMode = ADC_DMAMode_Circular;
     ADC1_CommonInitStructure.ADC_TwoSamplingDelay = 0x0;
     ADC_CommonInit(ADC1, &ADC1_CommonInitStructure);
 
 
     //Все включаем и поехали
     ADC_Cmd(ADC1, ENABLE);
     while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_RDY));
 
 
     ADC_Cmd(ADC2, ENABLE);
     while(!ADC_GetFlagStatus(ADC2, ADC_FLAG_RDY));
 
 
    DMA_Cmd(DMA1_Channel1, ENABLE);
}

На графике видно, что все стало гораздо приятнее. На глаз заметна разница между измерениями, эта разница и есть сопротивление провода. Остается только отфильтровать данные и можно считать сопротивление датчика.
Данный режим может быть полезен при измерении мощности, чтобы оцифровывать ток и напряжении в один момент времени.
Может кому пригодится
4
Миниатюры
Stm32f303vc + ADC (Dual Mode) + DMA. Оцифровываем синхронно   Stm32f303vc + ADC (Dual Mode) + DMA. Оцифровываем синхронно  
Изображения
 
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
05.04.2017, 10:44
Ответы с готовыми решениями:

Stm32f303vc+ADC(Dual mode)+DMA
Добрый день! никак не могу стартануть АЦП1 и 2 в дуал моде. Вот код: ...

STM32F4 Dual ADC mode
Не могу найти как работать dual ADC mode, что бы одновременно стартовать 2 АЦП...

STM32F4Discovery - ADC DMA и FSMC DMA
Привет всем. Вынужден опять обратиться за Вашей помощью :) Ситуация...

ADC +DMA
может кому то понадобится буфер приема данных необходимо выравнивать по 32х...

STM32F0 ADC+DMA
Есть кто-то кто программировал АЦП с несколькими каналами STM32F0? Я...

6
_SayHello
536 / 299 / 98
Регистрация: 30.07.2015
Сообщений: 1,064
06.04.2017, 16:27  [ТС] #2
Добавим кое какую фильтрацию. Так как я делаю измерения температуры, а процесс это достаточно энерционный, динамическую фильтрацию производить не вижу особого смысла. Пойдем в лоб: накопим побольше оцифровок (я делал от 1000 до 5000) и усредним их. Программно дергать я не захотел, поэтому решил прикрутить таймер, в котором по событию сравнения будет пинаться АЦП. Почему именно по сравнению: во первых на сравнении имеем несколько каналов (до 6 штук), то есть одним таймером мы можем пинать несколько других периферий. Во вторых, постоянно пускать ток через датчик не круто, ибо он сам будет греться, что не хорошо. Поэтому по второму каналу таймера сравнения мы можем, с небольшим сдвигом фазы включать заранее ЦАП который задает ток, а после преобразования выключать.
Убрал пока использование DMA. В прерываниях АЦП накапливаю и усредняю.
Добавим таймер:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
     TIM_DeInit(TIM4);
     Timer1.TIM_Period          = 200-1; //0,2 мс
     Timer1.TIM_Prescaler       = 64-1; // 1 МГц
     Timer1.TIM_CounterMode     = TIM_CounterMode_Up;
     Timer1.TIM_ClockDivision   = 0;
     TIM_TimeBaseInit(TIM4, &Timer1);
 
     TIM_OCStructInit(&Timer1OC);
     Timer1OC.TIM_Pulse = 1;
     Timer1OC.TIM_OCMode = TIM_OCMode_Toggle;
     Timer1OC.TIM_OutputState = TIM_OutputState_Enable;
     Timer1OC.TIM_OCPolarity = TIM_OCPolarity_Low;
     TIM_OC4Init(TIM4, &Timer1OC);
     TIM_SelectOutputTrigger(TIM4,  TIM_TRGOSource_OC4Ref); // триггер для АЦП
     TIM_SetCompare4(TIM4, 100); // Сравниваем со 100
Настроим прерывания:
C++
1
2
3
4
5
6
7
8
9
  NVIC_InitTypeDef NVIC_InitStructure;
 
         NVIC_InitStructure.NVIC_IRQChannel = ADC1_2_IRQn;
     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = configLIBRARY_LOWEST_INTERRUPT_PRIORITY;
     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
     NVIC_Init(&NVIC_InitStructure);
 
         ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE);
В Dual-Mode настраиваем прерывание для мастера, для слева будет автоматически.
Запускаем таймер и дергаем
C++
1
2
3
    TIM_Cmd(TIM4, ENABLE);
 
     ADC_StartConversion(ADC1);
Если цифруем по триггеру, то первый раз обязательно надо дернуть самому, иначе работать не будет.

Ну и простейшее усреднение в прерывании:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void ADC1_2_IRQHandler()
{
    if(ADC_GetITStatus(ADC1, ADC_IT_EOC))
    {
        ADC_ClearITPendingBit(ADC1, ADC_IT_EOC);
        if(Samples < 5000)
        {
            ADC_SUM[0] += ADC1->DR;
            ADC_SUM[1] += ADC2->DR;
            Samples++;
        }
        else
        {
            MB_table.regs[20] = (ADC_SUM[0]/5000);
            MB_table.regs[21] = (ADC_SUM[1]/5000);
            ADC_SUM[0] = 0;
            ADC_SUM[1] = 0;
            Samples = 0;
        }
    }
}
Получилось довольно сносно, наш триггер дергает ацп с частотой 5кГц. Таким образом накопление 5000 отсчетов и усреднение займет 1 секунду. Для измерения температуры пойдет
0
Voland_
1652 / 998 / 96
Регистрация: 04.01.2010
Сообщений: 3,299
07.04.2017, 09:30 #3
Хороший мануал. Единственное - забыли упомянуть насчет времени преобразования. У вас он поставлен в 602,5 такта АЦП. И при клоке "как он есть" у вас - АЦП в принципе может "дергать" в два раза быстрее вроде бы. Надо уточнять, т.к. клок таймера и АЦП - разные, соответственно, отсель не видно какое их соотношение. В STM32F1xx CLK1 имеет минимальный делитель 2. Тогда мои заключения вроде бы верны.
1
_SayHello
536 / 299 / 98
Регистрация: 30.07.2015
Сообщений: 1,064
07.04.2017, 10:03  [ТС] #4
Voland_, В моем случае(в примере) частота PLL = 64 МГц. Частота PLL/2 =32 МГц. Время оцифровки Tconv = (601.5+12.5)ADC_CLK = 19.18 мкс. Таймер тикает с частотой 64 МГц, ставим делитель на 64 - > 1 МГц. Период 200 -> итоговый период дерганья 200 мкс. В теории можно дергать и почаще, но есть НО. Термодатчик рекомендуют питать током < 1мА, чтобы исключить саморазогрев. Но при таком токе изменение напряжения на 1 градус = 0,37*0,001 = 0,00037 В. При Vref АЦП =3.3В Получаем дискретность 3,3/4095 = 0,0008В. Таким образом мы не можем снимать показания даже с точностью в 1 градус. Есть вариант прогнать через ОУ. Измеряем мы в пределах 100 -250 Ом (0-400 градусов).При токе 0.1 мА - > 0.1 -0.25 В. Можно взять коэф. усиления 12 получим 1.2-3В , как раз вписывающихся в наш диапазон АЦП. Но такая схема будет сильно зависеть от точности резисторов в усилительных каскадах. Я пошел по другому пути, я взял источник тока на 10 мА, и включаю его на время выборки, затем отключаю таким образом 19 мкс/200 = 1/10. Таким образом ток подается на датчик только десятую часть времени, в итоге мощность выделяемая за период при токе 10 мА будет практически равна мощности выделяющейся на датчике при токе 1 мА. А так да, можно и чаще дергать. Если я ответил не ответил на ваше замечание, то поясните пожалуйста поподробнее.
0
Voland_
1652 / 998 / 96
Регистрация: 04.01.2010
Сообщений: 3,299
07.04.2017, 12:02 #5
Цитата Сообщение от _SayHelli Посмотреть сообщение
частота PLL = 64 МГц. Частота PLL/2 =32 МГц. Время оцифровки Tconv = (601.5+12.5)ADC_CLK = 19.18 мкс. Таймер тикает с частотой 64 МГц, ставим делитель на 64 - > 1 МГц.
Да все оно так (наверное). Пожалуй, проще всего было бы обратиться к схеме клоков в F303, чтобы уточнить.
Просто ADC управляется клоком домена AHB:
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_ADC12, ENABLE);
а таймер TIM4 посажен на CLK1:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
Соответственно, клоки могут быть не равны, т.к. на APB1 и APB2 есть свои делители, о которых я говорил. Т.к. в вашем примере я вроде бы не встретил инита PLL и делителей - отсель не видно, какой частотой тактуется APB1 и соответственно, TIM4.

Расчеты ваши вполне вдумчивы, поддерживаю. Единственное, чего вы упустили - опорное напряжение АЦП. Источник тока, это конечно же, хорошо, но помимо него нужен довольно стабильный ИОН для АЦП МК, чтобы получить ту же стабильность в замерах. И к погрешности, которую вы считаете, нужно прибавить его погрешность.

ЗЫ: помимо ваших, есть схемы подключения ТСМ и тензодатчиков к внешним АЦП, ADS1256 стр.76, например. Ваше решение гораздо проще в исполнении, и удобнее. Но, может быть для кого-то станет полезной и схематика внешних усилителей и внешних АЦП.
0
_SayHello
536 / 299 / 98
Регистрация: 30.07.2015
Сообщений: 1,064
07.04.2017, 12:19  [ТС] #6
Voland_,
Цитата Сообщение от Voland_ Посмотреть сообщение
Пожалуй, проще всего было бы обратиться к схеме клоков в F303, чтобы уточнить.
Да, это упустил, не ставил целью описания работы тактирования)
Цитата Сообщение от Voland_ Посмотреть сообщение
него нужен довольно стабильный ИОН для АЦП МК
Это да, пока эксперементирую на отладке Discovery, жадные STMщики зажали внешний Vref ( на рабочем обрзце собираюсь поставить опору ИОН 2.5В как раз при токе 10 мА, на сопротивлении 250 Ом будет 2.5В. Да и для ЦАПа полезно будет, током точнее управляем.
Цитата Сообщение от Voland_ Посмотреть сообщение
ЗЫ: помимо ваших, есть схемы подключения ТСМ и тензодатчиков к внешним АЦП, ADS1256 стр.76, например. Ваше решение гораздо проще в исполнении, и удобнее. Но, может быть для кого-то станет полезной и схематика внешних усилителей и внешних АЦП.
Не претендую на единственно верное решение, схем разных куча, у меня была цель использовать доступные на МК возможности по максимуму. Да и хороший АЦП влетит в копеечку.
Кстати, в моем случае, датчики будут использоваться в микроволновой установке, где помех и наводок хоть отбавляй, большой ток обеспечит лучшее отношение сигнал/шум. С усилителями так может не прокатить.
0
RefSol
498 / 243 / 73
Регистрация: 31.10.2010
Сообщений: 724
05.08.2017, 19:21 #7
_SayHello, фильтрация интересная, но для таких вычислений лучше брать числа кратные 2^n, где n -- целое число, т.е., например: 2, 4, 8, 16 ..., тогда деление можно заменить сдвигом, получим следующий код:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void ADC1_2_IRQHandler()
{
    if(ADC_GetITStatus(ADC1, ADC_IT_EOC))
    {
        ADC_ClearITPendingBit(ADC1, ADC_IT_EOC);
        if(Samples < (1<<8))  // или 2^8
        {
            ADC_SUM[0] += ADC1->DR;
            ADC_SUM[1] += ADC2->DR;
            Samples++;
        }
        else
        {
            MB_table.regs[20] = (ADC_SUM[0]>>8);
            MB_table.regs[21] = (ADC_SUM[1]>>8);
            ADC_SUM[0] = 0;
            ADC_SUM[1] = 0;
            Samples = 0;
        }
    }
}
У такого подхода есть ещё один замечательный плюс, например,
если при делении сдвигом ADC_SUM[0]>>7 (сдвигать на один бит меньше),
то в нулевом бите (б0) получим дробную часть, если б0 == 1, то это 0.5,
если сдвигать на два бита меньше, то получим б0 == 1, то это 0.25, а б1 == 1, то это 0.5.
Таким образом, можно добиться высокой точности, ведь смысл фильтрации заключается,
в повышении точности за счёт устранения шума.
А когда дробные числа выражаются с помощью целочисленных форматов,
это называется: числа с фиксированной точкой.

Добавлено через 14 часов 43 минуты
Совсем забыл сказать зачем деление заменять сдвигом. Дело в том что деление, особенно на числа не равные 2^n (где n - целое), достаточно долго вычислимая операция, в то время как сдвиг выполняется быстро и потребляет меньше ресурсов.
Кстати это же возможно сказать и о замене чисел с плавающей точкой, на числа с фиксированной точкой. Числа с плавающей точкой требуют специальных библиотек или сопроцессоров (FPU), и даже при наличии сопроцессора простые операции как сложение, умножение, деление требую несколько тактов, в то время как числа с фиксированной точкой обрабатываются на много быстрее основным процессором и в принципе не требуют специальных библиотек (хотя возможно и создать для разнообразных сложных вычислений, например, для деления на число не равное 2^n или для расчёта cos(x), и т.д).
Самая затратная базовая (сложение, умножение, деление, вычитание) операция, для чисел с фиксированной точкой, это деление на число не равное 2^n.
В приложениях где критична скорость вычисления обычно заменяют числа с плавающей точкой, на числа с фиксированной точкой, выигрыш в скорости вычислений может быть 10-15 и более раз.
Если система многопоточная, и есть необходимость повышения скорости,то замена числе с плавающей точкой на фиксированную та же позволяет повысить скорость вычислений, но естественно при выполнении закона Амдала: «В случае, когда задача разделяется на несколько частей, суммарное время её выполнения на параллельной системе не может быть меньше времени выполнения самого длинного фрагмента».

Какие могут быть проблемы при использовании чисел с фиксированной точкой?
Главная проблема это вероятность переполнения и достаточно ограниченное число операций гарантирующих не переполнение с числами одного порядка.
Например, если каждое число занимает 16 бит, и значение каждого числа 0x00 <= x <= 0xFF (255), все числа положительные, тогда гарантированно без переполнения возможно выполнить 0x01010101 сложений и всего 0x03 умножение. Таким образом, при большом числе операций необходимо проверять переполнение и смещать "фиксированную" точку.
Следующая проблема это относительно (относительно чисел с плавающей точкой) небольшой диапазон допустимых значений.

Из положительных качеств возможно отметить быстроту вычислений, особенно важно для обработки сигналов в режиме реального времени. Одинаковость шага разрядной сетки около нулевых и у самых больших допустимых значений.
Быстрое преобразование формата, от целочисленного к дробному и соответственно быстрота реализации различных интерфейсных преобразований.
1
05.08.2017, 19:21
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
05.08.2017, 19:21

stm32f4 + ADC + DMA
Доброго времени суток. Вопрос такой... Сделал АЦП на плате ф4дискавери, и...

ADC+DMA+TIMER
Эта тема уже подымалась на форме, но все используют стандартную библиотеку, а я...

ADC+DMA пример
Нужен примерчик для K40 (или вообще для любого из семейства кинетис), для...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
7
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2018, vBulletin Solutions, Inc.
Рейтинг@Mail.ru