Файл: Курсовой проект по курсу Проектирование цифровых вычислительных (у правляющих) устройств.docx
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 17.03.2024
Просмотров: 63
Скачиваний: 1
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
NVIC->ISER[1] = (1<<5); // разрешаем прерывания от УАПП1
-
Подпрограмма ввода и обработки аналоговых сигналов
Подпрограмму ввода и обработки аналоговых сигналов выполняем в виде двух функций, одна из которых однократно опрашивает аналоговые входы, вторая – выполняет необходимую обработку накопленных данных.
void ProcessAInputs(void)
{
GetAData(); // получить текущие значения
ProcessAData(); // обработать аналоговые данные
}
-
Функция опроса аналоговых входов
Рассматриваемая функция получает и сохраняет текущие значения сигнала для каждого аналогового входа. Алгоритм разрабатываемой функции изображён на рис. 3.3. Его особенностью является необходимость хранения не только текущих, но и нескольких предыдущих измеренных значений входного сигнала. Поэтому для каждого канала (т.е. для каждого входа) должен быть организован кольцевой буфер, способный хранить =4 результатов измерения. Для работы с кольцевым буфером используется индекс очередного значения NextAIdx, то есть номер ячейки буфера, в которую будет записан результат следующего измерения. Так как все аналоговые входы обслуживаются однотипно, для каждого кольцевого буфера используется один и тот же индекс NextAIdx. Перед тем, как выполнить очередной цикл опроса входов, в блоке 1 этот индекс изменяется таким образом, чтобы он указывал на ячейки с наиболее старыми данными.
Рисунок 3.3 Алгоритм функции опроса аналоговых входов
С помощью блоков 2 и 7 организован цикл получения данных с используемых аналоговых входов. Для каждого входа выполняются следующие действия. Прежде всего в блоке 3 с помощью встроенного коммутатора к АЦП подключается очередной вход. Затем на АЦП подаётся команда «Запуск преобразования» (блок 4) и выполняется цикл ожидания результата (блок 5). Как только АЦП уведомит программу об окончании преобразования, результат считывается и сохраняется в кольцевой буфер текущего канала (блок 6).
Для реализации этого алгоритма добавляем в программу несколько описаний:
1) Для удобства модификации и отладки программы вводим следующие константы:
#define N_AI 4 // количество аналоговых входов
#define C_AI 4 // количество сохраняемых значений
// для каждого входа
2) Опишем массив AnData, содержащий кольцевые буферы для каждого канала измерения, и переменную-индекс NextAIdx:
// Кольцевые буферы для каждого аналогового входа
uint16_t AnData[N_AI][C_AI];
// Индекс очередного элемента для записи в массив AnData
unsigned NextAIdx;
Кольцевые буферы выполнены в виде двумерного массива AnData. Его первый индекс позволяет выбрать буфер канала (от 0 до NAI–1), второй – ячейку буфера, в которую будут записаны данные. Размер каждого буфера равен 6 ячейкам – именно такое количество данных для каждого канала нужно будет хранить и обрабатывать
Функция опроса аналоговых входов имеет следующий вид:
void GetAData(void)
{
unsigned Ch; // номер обрабатываемого канала
// Вычисляем индекс очередной ячейки кольцевых буферов
NextAIdx++;
NextAIdx %= C_AI;
// перебираем все каналы измерения
for (Ch=0; Ch
// Подключаем текущий канал к АЦП
ADC1->SQR3=Ch;
// Запускаем преобразование
ADC1-CR2 |= ADC_CR2_SWSTART;
// Ожидаем завершения преобразования
while(!(ADC1->SR & ADC_SR_EOC));
// считывание и сохранения результата преобразования
AnData[Ch][NextAIdx] = ADC1->DR;
}
}
Последовательность номеров опрашиваемых каналов задаём в регистрах АЦП ADC_SQR1… ADC_SQR3. Номер первого канала в этой последовательности храниться в регистре ADC_SQR3, поэтому программа пишет туда номер очередного канала для измерения Ch. Запуск преобразования выполняется установкой бита 22 (SWSTART) в регистре ADC_CR2. Текущее состояние АЦП определим, анализируя бит 1 (EOC) в регистре ADC_SR: единица в этом разряде информирует о завершении АЦ-преобразования. Цикл «while» будет выполняться до тех пор, пока преобразование не закончится. После этого измеренная величина сигнала на выбранном входе считывается из регистра данных АЦП ADC_DR и сохраняется в кольцевой буфер текущего канала Ch в ячейку с номером NextAIdx. Самое старое значение в кольцевом буфере при этом теряется.
-
Функция обработки накопленных данных
Обработка данных, накопленных в кольцевом буфере: поиск минимального значения по каждому из каналов измерения. Алгоритм функции вычисления минимального значения для каждого канала приведён на рис. 3.4
Рисунок 3.4 Алгоритм функции вычисления минимальных значений
Поскольку для каждого канала измерения необходимо выполнить одни и те же действия, основу функции составляет цикл перебора каналов (блоки 1…8). Номер текущего канала хранится в переменной Ch. В теле основного цикла (блоки 2…6) реализован алгоритм вычисления минимального значения для одного канала путём сравнивания 4 последних измеренных значений. Прежде всего выбирается промежуточный минимум (блок 2), а затем в цикле (блоки 3…5) с ним сравниваются накопленные измеренные значения. По окончанию цикла в блоке 6 вычисляется минимальное из последних 4 измеренных значений.
Вычисленное минимальное значение выражено в неких условных единицах, полученных с АЦП. Преобразуем его в число, равное напряжению на соответствующем аналоговом входе. Для этого рассмотрим преобразования, которым подвергается входной сигнал. Первое преобразование выполняет НУ в соответствии с выражением:
????ВЫХ = ????ВХ????ВХ_Ф + ????СМ????СМ_Ф,
где UВЫХ – напряжение на выходе НУ;
UВХ – напряжение на входе НУ;
KВХ_Ф – фактический коэффициент передачи НУ по основному входу;
KСМ_Ф – фактический коэффициент передачи НУ по входу смещения.
Затем сигнал с выхода НУ поступает на вход АЦП, где подвергается второму линейному преобразованию:
CODE = UВЫХKАЦП,
где CODE – результат АЦ-преобразования;
KАЦП – коэффициент преобразования АЦП, равный (2 NАЦП–1) / VREF;
NАЦП – разрядность АЦП (для STM32F101T8 равна 12 бит);
VREF – опорное напряжение АЦП (3.3 В).
KАЦП=(2NАЦП–1) / VREF=(2^12 -1)/3.3=1240.9
С учётом вышеприведённых выражений запишем:
CODE = (UВХKВХ_Ф + UСМKСМ_Ф)×KАЦП (3.1)
Чтобы по известному коду с выхода АЦП вычислить соответствующее ему входное напряжение, преобразуем (3.1):
UВХ = (CODE / KАЦП – UСМKСМ_Ф) / KВХ_Ф = CODE × K1 + K2,
K1 = 1 / (KАЦП × KВХ_Ф)=1/(1240.9 × 0.85700426) = 0,00094033,
K2 = –UСМKСМ_Ф / KВХ_Ф=-3.3×0.04549463 / 0.85700426 = -0,175183.
1 2 3 4 5 6 7
void ProcessAData(void){
unsigned Ch; // номер обрабатываемого канала unsigned Idx;
// индекс очередного значения
// Перебираем все каналы измерения
for (Ch=0; Ch
AnRes[Ch] = 0;// обнуляем значения измеренных по каналу Ch
//значений
// находим минимальные измеренные по каналу Ch значения
for (Idx=0; Idx
if (AnRes[Ch] < AnData[Ch][Idx]) AnRes[Ch]= AnData[Ch][Idx];
// Преобразуем вычисленное значение в вольты
AnRes[Ch] = AnRes[Ch]*K1 + K2;
}
}
-
Подпрограмма ввода и обработки дискретных сигналов
-
Ввод значений сигналов
Подключение выполнено к последовательно пронумерованным линиям одного порта. Ввод текущих значений дискретных сигналов выполняется простым чтением регистра входных данных IDR порта, к которому эти сигналы подключены.
В результате значения входных дискретных сигналов (0 или 1) запишутся в соответствующие биты переменной DInp. Согласно ИЗ активным уровнем для дискретных входов является ноль, полученное значение следует проинвертировать:
DInp =
GPIOА->IDR;
В результате описанных операций двоичное число в переменной DInp для рассматриваемого примера будет иметь вид *ХХХ Х*** **** ****, где «X» – значение соответствующего дискретного входа (1 – активен, 0 – неактивен), «*» – незначащие биты. Для упрощения дальнейшей работы сдвинем группу битов «XXXХ» вправо до нулевого разряда, а оставшиеся незначащие разряды обнулим:
DInp >>=11;
DInp &= (1<< N_DI)-1;
-
Обработка введённых данных
Обработка входных дискретных сигналов происходит по сбросу результатов измерений по соответствующему аналоговому входу. В первую очередь следует задать соответствие входов и выходов. Будем считать, что соответствуют друг другу входы и выходы с одинаковыми номерами.
Сброс результатов измерений по аналоговому входу заключается в том, что все накопленные по этому входу данные удаляются, а накопление и обработка начинаются заново. Фрагмент программы, выполняющий эти действия, приведён ниже.
unsigned Ch;
unsigned AIdx;
for (Ch=0; Ch<3; Ch++)
if ((DInp & (1<< Ch))!=0) {
for (AIdx=0; AIdx
AnData[Ch][AIdx] = AnData[Ch][NextAIdx];
}
ProcessAData()
Первый цикл for перебирает аналоговые входы, для которых имеются соответствующие дискретные входы. В теле этого цикла оператор if проверяет, активен ли соответствующий дискретный вход (т.е. установлен ли в единицу бит с номером Ch в переменной DInp). Если да, то во вложенном цикле for кольцевой буфер текущего канала AnData[Ch] заполняется результатом последнего измерения по этому каналу, что приводит к удалению ранее накопленных данных. После просмотра всех каналов вызывается функция ProcessAData(), выполняющая обработку накопленных результатов измерений с учётом изменившегося содержимого кольцевых буферов.
-
Подпрограмма управления дискретными выходами
Дискретные выходы активируются в зависимости от результатов обработки данных, полученных по соответствующим аналоговым входам из условия:
|0.5RESAI_MAX – RESAI| < 0,42 × CDO × RESAI_MAX
В условиях активации выходов, используются следующие обозначения: RESAI – текущее вычисленное значение для аналогового входа, выраженное в вольтах; RESAIMAX – максимально возможное вычисленное значение для аналогового входа (RESAIMAX = UMAX = 3.5);
CDO – уровень срабатывания дискретного выхода ( CDO=0.84).
#define RES_AI_MAX 3.5
#define C_DO 0.84
unsigned Ch;
unsigned Flags = 0;
for (Ch=0; Ch
if( fabs(0.5*RES_AI_MAX-AnRes[Ch]) < 0.42*C_DO*RES_AI_MAX )
Flags |= (1<< Ch);
}
Константы CDO и RESAIMAX задаём директивами define для упрощения модификации программы. Переменная Ch хранит номер обрабатываемого канала измерения, переменная Flags – признаки активации дискретных выходов (единица в каком-либо разряде этой переменной означает, что соответствующий выход должен быть активирован).
Цикл for обеспечивает перебор аналоговых входов, для которых имеются соответствующие дискретные выходы; при разработке программы условие окончания цикла следует скорректировать согласно числу этих входов. В теле цикла оператор if для каждого такого входа проверяет условие активации выхода и в случае его выполнения устанавливает соответствующий бит в переменной Flags (первому выходу соответствует разряд 0, второму – разряд 1 и т.д.).
Таким образом, если какой-либо бит переменной Flags равен единице, то соответствующий выход нужно активировать. Функция fabs() возвращает модуль своего аргумента, для её использования к тексту программы следует подключить заголовочный файл «math.h».
Сброс выходов, согласно ИЗ, может выполняется, когда выход переходит в неактивное состояние автоматически при невыполнении условия активации. Иными словами, если какой-либо разряд переменной Flags равен единице, то соответствующий выход нужно активировать, иначе – сбросить. Предыдущее состояние выходов при этом никак не учитывается, поэтому содержимое Flags можно просто записать в переменную DOut, которая хранит текущее состояние выходов:
DOut = Flags;
Переменная DOut хранит текущее состояние выходов для внутреннего использования и не влияет на логические уровни на выходах ОМК. Эти уровни задаются путём записи в регистр выходных данных порта. Просто вывести значение DOut в порт нельзя, предварительно нужно сдвинуть его влево таким образом, чтобы биты состояния попали в те разряды порта, к которым подключены выходные цепи ССОИ.
Flags = (DOut<<3) ;
Далее нужно подготовить битовую маску, содержащую единицы в разрядах, соответствующих подключённым выходам, и нули в остальных.
unsigned Mask = ((1<
Теперь значащие разряды Flags нужно записать в регистр выходных данных GPIOА_ODR порта PА.
// обнуляем все дискретные выходы
GPIOА->ODR &=
Mask;
// Выводим единицы на те выходы, где это нужно
GPIOA->ODR |= Flags;
-
Подпрограмма индикации
Алгоритм индикации состоит из двух частей: определение номера аналогового входа с максимальным значением и вывод этого номера на семисегментный индикатор.
Поиск максимума выполняется классическим способом: сначала принимаем за промежуточный максимум вычисленное значение для первого входа, а затем в цикле просматриваем остальные входы. Если вычисленное значение очередного входа больше текущего максимума, то текущий максимум обновляется, а номер очередного входа запоминается.
float Max = AnRes[0]; // промежуточный максимум
unsigned InpNmb = 0; // сохранённый номер входа
unsigned Ch; // Перебираем оставшиеся входы в поисках максимума
for (Ch=1; Ch)
if (Max // значение на входе больше максимума –
Max = AnRes[Ch]; // обновляем текущий максимум
InpNmb = Ch; // и запоминаем номер этого входа
}
InpNmb++; // чтобы нумерация входов начиналась с 1, а не с 0
В результате в переменной InpNmb сохранится номер входа с максимальным вычисленным значением. Чтобы входы нумеровались не от 0 до N_AI – 1, а от 1 до N_AI, в последней строке InpNmb увеличивается на единицу.
Далее значение InpNmb необходимо отобразить на индикаторе. Для этого нужно включить сегменты индикатора, формирующие изображение соответствующей цифры, и выключить остальные, то есть вывести в порт, управляющий индикатором, определённый код символа. Значения этих кодов зависят от схемы включения индикатора и могут быть получены путём составления таблицы, изображённой на рис. 3.5.
Рисунок 3.5 Формирование семисегментных кодов символов
При составлении таблицы, сегмент «a» управляется младшим разрядом кода, сегмент «h» – старшим, включение сегментов выполняется логической единицей на выходе. Возможен любой другой порядок подключения сегментов, важно лишь, чтобы сегменты управлялись последовательно пронумерованными выводами порта. Первый столбец таблицы содержит символы, которые могут отображаться на индикаторе. Далее расположены восемь столбцов, соответствующих двоичным разрядам 7…0 кода символа. Под номером разряда указано название сегмента, управляемого этим разрядом (сегмент «h» не используется). Единицы в столбцах указываются для сегментов, которые должны светиться при отображении соответствующего символа, нули – для погашенных сегментов. Так для индикации символа «3» нужно потушить сегменты «e», «f» и зажечь остальные. Поэтому в столбцах «4 / e», «5 / f» для символа «3» установлены нули, а в остальных столбцах – единицы. В правом столбце записываются коды символов в шестнадцатеричном представлении, пригодном для использования в программе.
Полученные коды оформляются в виде массива восьмиразрядных констант, причём первым записывается код нуля, потом единицы, двойки, тройки и т.д.:
static const uint8_t SegCodes[] =
{0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
Теперь чтобы получить семисегментный код цифры, записанной в переменную InpNmb, достаточно извлечь его из ячейки массива, номер которой равен этой цифре:
unsigned Code = SegCodes[InpNmb];
Далее уже известным способом выводим код символа на линии управления сегментами, не изменяя остальные разряды порта:
// Выключаем все сегменты (обнуляем выходы управления ими)
GPIOB->ODR &=
(0x7F <<0);
void ProcessAData(void){
unsigned Ch; // номер обрабатываемого канала unsigned Idx;
// индекс очередного значения
// Перебираем все каналы измерения
for (Ch=0; Ch
AnRes[Ch] = 0;// обнуляем значения измеренных по каналу Ch
//значений
// находим минимальные измеренные по каналу Ch значения
for (Idx=0; Idx
if (AnRes[Ch] < AnData[Ch][Idx]) AnRes[Ch]= AnData[Ch][Idx];
// Преобразуем вычисленное значение в вольты
AnRes[Ch] = AnRes[Ch]*K1 + K2;
}
}
-
Подпрограмма ввода и обработки дискретных сигналов
-
Ввод значений сигналов
Подключение выполнено к последовательно пронумерованным линиям одного порта. Ввод текущих значений дискретных сигналов выполняется простым чтением регистра входных данных IDR порта, к которому эти сигналы подключены.
В результате значения входных дискретных сигналов (0 или 1) запишутся в соответствующие биты переменной DInp. Согласно ИЗ активным уровнем для дискретных входов является ноль, полученное значение следует проинвертировать:
DInp =
GPIOА->IDR;AnRes[Ch] = 0;// обнуляем значения измеренных по каналу Ch
//значений
// находим минимальные измеренные по каналу Ch значения
for (Idx=0; Idx
if (AnRes[Ch] < AnData[Ch][Idx]) AnRes[Ch]= AnData[Ch][Idx];
// Преобразуем вычисленное значение в вольты
AnRes[Ch] = AnRes[Ch]*K1 + K2;
}
}
-
Подпрограмма ввода и обработки дискретных сигналов-
Ввод значений сигналов
-
Подключение выполнено к последовательно пронумерованным линиям одного порта. Ввод текущих значений дискретных сигналов выполняется простым чтением регистра входных данных IDR порта, к которому эти сигналы подключены.
В результате значения входных дискретных сигналов (0 или 1) запишутся в соответствующие биты переменной DInp. Согласно ИЗ активным уровнем для дискретных входов является ноль, полученное значение следует проинвертировать:
DInp =
В результате описанных операций двоичное число в переменной DInp для рассматриваемого примера будет иметь вид *ХХХ Х*** **** ****, где «X» – значение соответствующего дискретного входа (1 – активен, 0 – неактивен), «*» – незначащие биты. Для упрощения дальнейшей работы сдвинем группу битов «XXXХ» вправо до нулевого разряда, а оставшиеся незначащие разряды обнулим:
DInp >>=11;
DInp &= (1<< N_DI)-1;
-
Обработка введённых данных
Обработка входных дискретных сигналов происходит по сбросу результатов измерений по соответствующему аналоговому входу. В первую очередь следует задать соответствие входов и выходов. Будем считать, что соответствуют друг другу входы и выходы с одинаковыми номерами.
Сброс результатов измерений по аналоговому входу заключается в том, что все накопленные по этому входу данные удаляются, а накопление и обработка начинаются заново. Фрагмент программы, выполняющий эти действия, приведён ниже.
unsigned Ch;
unsigned AIdx;
for (Ch=0; Ch<3; Ch++)
if ((DInp & (1<< Ch))!=0) {
for (AIdx=0; AIdx
AnData[Ch][AIdx] = AnData[Ch][NextAIdx];
}
ProcessAData()
Первый цикл for перебирает аналоговые входы, для которых имеются соответствующие дискретные входы. В теле этого цикла оператор if проверяет, активен ли соответствующий дискретный вход (т.е. установлен ли в единицу бит с номером Ch в переменной DInp). Если да, то во вложенном цикле for кольцевой буфер текущего канала AnData[Ch] заполняется результатом последнего измерения по этому каналу, что приводит к удалению ранее накопленных данных. После просмотра всех каналов вызывается функция ProcessAData(), выполняющая обработку накопленных результатов измерений с учётом изменившегося содержимого кольцевых буферов.
-
Подпрограмма управления дискретными выходами
Дискретные выходы активируются в зависимости от результатов обработки данных, полученных по соответствующим аналоговым входам из условия:
|0.5RESAI_MAX – RESAI| < 0,42 × CDO × RESAI_MAX
В условиях активации выходов, используются следующие обозначения: RESAI – текущее вычисленное значение для аналогового входа, выраженное в вольтах; RESAIMAX – максимально возможное вычисленное значение для аналогового входа (RESAIMAX = UMAX = 3.5);
CDO – уровень срабатывания дискретного выхода ( CDO=0.84).
#define RES_AI_MAX 3.5
#define C_DO 0.84
unsigned Ch;
unsigned Flags = 0;
for (Ch=0; Ch
if( fabs(0.5*RES_AI_MAX-AnRes[Ch]) < 0.42*C_DO*RES_AI_MAX )
Flags |= (1<< Ch);
}
Константы CDO и RESAIMAX задаём директивами define для упрощения модификации программы. Переменная Ch хранит номер обрабатываемого канала измерения, переменная Flags – признаки активации дискретных выходов (единица в каком-либо разряде этой переменной означает, что соответствующий выход должен быть активирован).
Цикл for обеспечивает перебор аналоговых входов, для которых имеются соответствующие дискретные выходы; при разработке программы условие окончания цикла следует скорректировать согласно числу этих входов. В теле цикла оператор if для каждого такого входа проверяет условие активации выхода и в случае его выполнения устанавливает соответствующий бит в переменной Flags (первому выходу соответствует разряд 0, второму – разряд 1 и т.д.).
Таким образом, если какой-либо бит переменной Flags равен единице, то соответствующий выход нужно активировать. Функция fabs() возвращает модуль своего аргумента, для её использования к тексту программы следует подключить заголовочный файл «math.h».
Сброс выходов, согласно ИЗ, может выполняется, когда выход переходит в неактивное состояние автоматически при невыполнении условия активации. Иными словами, если какой-либо разряд переменной Flags равен единице, то соответствующий выход нужно активировать, иначе – сбросить. Предыдущее состояние выходов при этом никак не учитывается, поэтому содержимое Flags можно просто записать в переменную DOut, которая хранит текущее состояние выходов:
DOut = Flags;
Переменная DOut хранит текущее состояние выходов для внутреннего использования и не влияет на логические уровни на выходах ОМК. Эти уровни задаются путём записи в регистр выходных данных порта. Просто вывести значение DOut в порт нельзя, предварительно нужно сдвинуть его влево таким образом, чтобы биты состояния попали в те разряды порта, к которым подключены выходные цепи ССОИ.
Flags = (DOut<<3) ;
Далее нужно подготовить битовую маску, содержащую единицы в разрядах, соответствующих подключённым выходам, и нули в остальных.
unsigned Mask = ((1<
Теперь значащие разряды Flags нужно записать в регистр выходных данных GPIOА_ODR порта PА.
// обнуляем все дискретные выходы
GPIOА->ODR &=
-
Подпрограмма индикации
if (Max
Max = AnRes[Ch]; // обновляем текущий максимум
InpNmb = Ch; // и запоминаем номер этого входа
}
InpNmb++; // чтобы нумерация входов начиналась с 1, а не с 0
В результате в переменной InpNmb сохранится номер входа с максимальным вычисленным значением. Чтобы входы нумеровались не от 0 до N_AI – 1, а от 1 до N_AI, в последней строке InpNmb увеличивается на единицу.
Далее значение InpNmb необходимо отобразить на индикаторе. Для этого нужно включить сегменты индикатора, формирующие изображение соответствующей цифры, и выключить остальные, то есть вывести в порт, управляющий индикатором, определённый код символа. Значения этих кодов зависят от схемы включения индикатора и могут быть получены путём составления таблицы, изображённой на рис. 3.5.
Рисунок 3.5 Формирование семисегментных кодов символов
При составлении таблицы, сегмент «a» управляется младшим разрядом кода, сегмент «h» – старшим, включение сегментов выполняется логической единицей на выходе. Возможен любой другой порядок подключения сегментов, важно лишь, чтобы сегменты управлялись последовательно пронумерованными выводами порта. Первый столбец таблицы содержит символы, которые могут отображаться на индикаторе. Далее расположены восемь столбцов, соответствующих двоичным разрядам 7…0 кода символа. Под номером разряда указано название сегмента, управляемого этим разрядом (сегмент «h» не используется). Единицы в столбцах указываются для сегментов, которые должны светиться при отображении соответствующего символа, нули – для погашенных сегментов. Так для индикации символа «3» нужно потушить сегменты «e», «f» и зажечь остальные. Поэтому в столбцах «4 / e», «5 / f» для символа «3» установлены нули, а в остальных столбцах – единицы. В правом столбце записываются коды символов в шестнадцатеричном представлении, пригодном для использования в программе.
Полученные коды оформляются в виде массива восьмиразрядных констант, причём первым записывается код нуля, потом единицы, двойки, тройки и т.д.:
static const uint8_t SegCodes[] =
{0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
Теперь чтобы получить семисегментный код цифры, записанной в переменную InpNmb, достаточно извлечь его из ячейки массива, номер которой равен этой цифре:
unsigned Code = SegCodes[InpNmb];
Далее уже известным способом выводим код символа на линии управления сегментами, не изменяя остальные разряды порта:
// Выключаем все сегменты (обнуляем выходы управления ими)
GPIOB->ODR &=
// Выводим код символа
GPIOB->ODR |= Code;
Вызов функции индикации необходимо выполнять после обработки данных с аналоговых входов, то есть после каждого вызова функции ProcessAData().
-
Подпрограмма взаимодействия с ВУ-
Подготовка данных для передачи
-
Согласно ИЗ результатов измерений и вычислений периодически передаться по последовательному интерфейсу RS-485 на верхний уровень АСУ ТП. Процесс передачи разделим на две части: подготовку данных (то есть формирование текстовой строки для передачи) и посимвольную передачу подготовленной строки в фоновом режиме с помощью УАПП.
Для подготовки данных опишем глобальный массив (буфер передачи), в котором будет формироваться текстовая строка для передачи, а также некоторые дополнительные константы и переменные:
#define TX_SIZE 38 // размер буфера передачи
char unsigned Ch [TX_SIZE]; // буфер передачи
char *TxPtr; // указатель на очередной символ для передачи
В качестве значения константы TX_SIZE, определяющей размер массива TxBuf, следует использовать размер буфера передачи, вычисленный ранее ,он равен 38 байт.
Функция StartTx(), выполняющая подготовку данных для передачи, прежде всего должна поместить в буфер передачи вычисленные значения сигналов для каждого аналогового входа, хранящиеся в массиве AnRes[N_AI]. Это можно сделать следующим образом:
void StartTX()
{
int Ch; // номер очередного входа
unsigned Pos = 0; // текущая позиция в буфере
// Помещаем в буфер значения аналоговых сигналов
for (Ch=0; Ch
Pos += sprintf(&TxBuf[Pos], "%5.3f, ", AnRes[Ch]);
}
Значения, хранящиеся в массиве AnRes[], преобразуются в текстовый формат и помещаются в буфер передачи библиотечной функцией sprintf(). Она работает аналогично функции printf(), но выводит данные не на стандартное устройство вывода, а в буфер памяти (в массив), указатель на который задаётся первым параметром (в нашем случае это &TxBuf[Pos]). Изначально переменная Pos равна нулю, поэтому первое значение будет записано в начало буфера. По окончанию работы sprintf() возвращает количество символов, записанных ей в буфер; на эту величину увеличивается переменная Pos. Таким образом, при следующем вызове sprintf() запишет символы уже не в начало буфера, а добавит их к ранее записанным. Спецификатор формата «%5.3f,» указывает, что вещественное число AnRes[Ch] будет записано в буфер в виде не менее пяти символов, три из которых – цифры после десятичной точки. После числа будет добавлен разделитель, состоящий из запятой и пробела.
Далее добавляем в буфер значения входных дискретных сигналов, хранящиеся в переменной DInp. Функция sprintf() не может выводить числа в буфер в двоичном представлении, поэтому программно проанализируем каждый значащий бит переменной DInp и в соответствии с его значением добавим в буфер символ «0» или «1». Дополним функцию StartTx() циклом обработки дискретных входов:
for (Ch=N_DI-1; Ch>=0; Ch--){
if ((DInp & (1<
TxBuf[Pos]=‘1’;
else
TxBuf[Pos]=‘0’;
Pos++
}
TxBuf[Pos++] = ‘,’;
TxBuf[Pos++] = ‘ ‘;
В цикле последовательно проверяются биты переменной DInp, начиная с (N_DI – 1) - го и заканчивая нулевым (N_DI – количество дискретных входов). Для этого DInp поразрядно логически умножается на единицу, сдвинутую в позицию проверяемого бита. Если в результате получился ноль, значит проверяемый бит имеет нулевое значение и в текущую позицию буфера передачи записывается символ «0», в противном случае записывается «1». Затем текущая позиция буфера перемещается на следующий элемент путём увеличения переменной Pos на 1. По окончанию работы цикла в буфер посимвольно добавляется разделитель, состоящий из символов «запятая» и «пробел».
Аналогичным способом в буфер добавляем текущие значения дискретных выходов, хранящиеся в переменной DOut (число дискретных выходов задано константой N_DO):
for (Ch=N_DO-1; Ch>=0; Ch--){
if (DOut & (1<
TxBuf[Pos]= ‘1’;
else
TxBuf[Pos] = ‘0’;
Pos++;
}
В результате буфер дополнится информацией о состоянии дискретных входов и выходов и примет вид: «#.###, #.###, ХХXX, YYY», где X – состояние одного дискретного входа («0» или «1»), Y – состояние одного дискретного выхода. Чтобы передаваемые строки наглядно отображались текстовым терминалом, дополним буфер командой перехода на новую строку (последовательность символов с кодами 0x0D, 0x0A) и терминирующим символом с кодом 0:
TxBuf[Pos++] = 0x0D;
TxBuf[Pos++] = 0x0A;
TxBuf[Pos] = 0;
После того, как данные в буфере подготовлены, инициируем передачу. Для этого установим указатель очередного передаваемого символа TxPtr на начало буфера и разрешим прерывания от передатчика. Передатчик УАПП контроллеров STM32F101 может формировать запросы прерываний по освобождению буферного регистра и по фактическому окончанию передачи очередного символа.
Прерывание первого типа возникает в тот момент, когда передаваемый символ перемещается передатчиком из буферного регистра в сдвиговый и начинается его фактическая передача. В ответ на это прерывание программа может загрузить в буферный регистр следующий символ, не дожидаясь окончания передачи предыдущего.
Прерывание второго типа информирует о том, что передача завершена полностью, то есть освобождён не только буферный, но и сдвиговый регистр. Его можно использовать, например, для переключения внешнего драйвера интерфейса на приём.
Реализован интерфейс RS-485, поэтому потребуются и прерывание по освобождению буферного регистра, и прерывание по окончанию передачи. Кроме того, в этом случае перед разрешением прерываний нужно перевести внешний драйвер интерфейса RS-485 в режим передачи.
// устанавливаем указатель очередного символа на начало буфера
TxPtr = TxBuf;
// Переводим драйвер в режим передачи
GPIOA->BSRR = (1<<8) ;
// Разрешаем передатчик и прерывания от него
USART1->CR1 |= (
USART_CR1_TE // разрешаем работу передатчика
| USART_CR1_TXEIE // разрешаем прерывания по освобождению
// буферного регистра
| USART_CR1_TCIE // разрешаем прерывания по окончанию
// передачи
);
-
Обработка прерываний УАПП
Передача данных помощью УАПП – весьма медленный процесс: например, на скорости 19200 бит/с один байт (символ) передаётся в течение примерно 0.5 мс, строка из пятидесяти символов – около 25 мс. При решении реальных задач измерения и управления зачастую недопустимо отвлекать процессор от выполнения основных функций на такое время, поэтому взаимодействие с УАПП обычно реализуется в режиме прерываний. При этом процессор не ждёт окончания передачи всего массива данных, а лишь кратковременно отвлекается от основной программы, чтобы инициировать передачу очередного символа.
Подпрограмма подготовки данных для передачи StartTx(), рассмотренная в п. 3.7.1, активизирует сигнал управления драйвером DE и разрешает прерывания от передатчика. Сразу после этого возникнет первый запрос прерывания, так как передатчик свободен и готов к работе. В ответ на это процессор приостановит выполнение основной программы и перейдёт на подпрограмму обслуживания прерываний, схема алгоритма которой показана на рис. 3.6.
Рисунок 3.6 Алгоритм функции обработки прерываний от передатчика УАПП
Прежде всего в блоке 1 проверяется, остались ли данные для передачи. Признаком конца передаваемой строки является терминирующий символ с кодом 0, который не передаётся. Если код очередного символа не равен нулю, то он записывается в буферный регистр передатчика (блок 2), а указатель очередного символа перемещается на следующий символ (блок 3). На этом подпрограмма завершает работу и процессор возвращается к выполнению основной программы, в то время как УАПП передаёт очередной символ. Как только буферный регистр освободится, снова возникнет прерывание и описанный процесс повторится. Это будет происходить до тех пор, пока в последовательности передаваемых данных не встретится ноль, означающий конец строки. В этом случае подпрограмма обработки прерываний в блоке 4 проверяет, что именно было причиной прерывания: освобождение буфера передатчика или полное окончание передачи. Если освобождение буфера, то в блоке 5 запрещаются прерывания такого типа, так как передавать больше нечего. Если же передача полностью завершена, то есть в линию связи выдан последний бит последнего байта, то в блоке 6 драйвер RS-485 переключается на приём, а затем в блоке 7 полностью запрещаются прерывания от передатчика. В результате запросы прерываний больше не будут формироваться до тех пор, пока функция StartTX() не подготовит очередную строку и вновь не разрешит прерывания.
Заключение
В ходе написания курсового проекта нами были подробно рассмотрены ОМК семейства STM32F101, различные биполярные транзисторы и индикаторы. С помощью дополнительного ПО «sPlan 8.0» [6] нарисована принципиальная схема ОМК. Написан код для обеспечения работы ОМК.
Список используемой литературы
-
STM32F101xx, STM32F102xx, STM32F103xx, STM32F105xx and STM32F107xx advanced Arm®-based 32-bit MCUs. Reference manual. December 2018 RM0008 Rev. 20. © 2018 STMicroelectronics. – 2018. – 1134 p. URL: https:// www.st.com/ content/ ccc/ resource/ technical/ document/ reference_manual/ 59/ b9/ ba/ 7f/ 11/ af/ 43/ d5/ CD00171190.pdf (дата обращения: 03.09.2019). -
STM32F101x8, STM32F101xB, Medium-density access line, ARM®- based 32-bit MCU with 64 or 128 KB Flash, 6 timers, ADC and 7 communication interfaces. Datasheet - production data. June 2015 Rev. 17. © 2015 STMicroelectronics. – 2015. – 101 p. URL: https:// www.st.com / resource / en / datasheet / stm32f101c8.pdf (дата обращения: 20.09.2019). -
ГОСТ 2.710-81 Единая система конструкторской документации (ЕСКД). Обозначения буквенно-цифровые в электрических схемах. – М.: Стандартинформ, 2008. -
MAX3221/ MAX3223/ MAX3243 +3V to +5.5V RS-232 Transceivers with AutoShutdown. © 2019 Maxim Integrated Products, Inc. – 2019. – 16 p. URL: https:// datasheets.maximintegrated.com/ en/ ds/ MAX3221-MAX3243.pdf. -
КТ645Б кремниевый биполярный эпитаксиально-планарный n-p-n транзистор. URL: https://static.chipdip.ru/lib/542/DOC003542577.pdf -
sPlan v8.0 - программа для черчения электронных схем. URL: https://www.electronic-software-shop.com/index.php. -
The High Efficiency Red source color devices are made with Gallium Arsenide Phosphide on Gallium Phosphide Orange Light Emitting Diode.URL: https://static.chipdip.ru/lib/195/DOC000195336.pdf -
ГОСТ 2.702-2011 Единая система конструкторской документации (ЕСКД). Правила выполнения электрических схем. – М.: Стандартинформ, 2011.
Приложение А.
Исходный текст программных модулей
3>
1 2 3 4 5 6 7
#include "stm32f10x.h" // Device header
#include
#include // для функции sprintf()
#include // для функции fabs()
// === Общие параметры программы ===
#define M_DI 3 // множитель периода опроса дискр. входов
#define M_DO 3 // множитель периода обновления дискр. выходов
#define M_COMM 10 // множитель периода передачи на ВУ
// === Параметры подсистемы ввода аналоговых сигналов ===
#define N_AI 4 // количество аналоговых входов
#define C_AI 4 // количество сохраняемых значений для каждого входа
#define K1 0,00094033 // коэффициент К1 для вычисления входного напряжения
#define K2 -0,175183 // коэффициент К2 для вычисления входного напряжения
// Кольцевые буферы для каждого аналогового входа
uint16_t AnData[N_AI][C_AI];
// Массив для сохранения результатов обработки аналоговых данных
float AnRes[N_AI];
// Индекс очередного элемента для записи в массив AnData
unsigned NextAIdx;
// === Параметры подсистем ввода и вывода дискретных сигналов ===
#define N_DI 3 // количество дискретных входов
unsigned DInp; // текущие значения дискретных входов
#define N_DO 2 // количество дискретных выходов
unsigned DOut; // текущие значения дискретных выходов
#define RES_AI_MAX 3.5 // максимальное напряжение на аналоговом входе
#define C_DO 0.84 // порог срабатывания дискретного выхода
// === Переменные для передачи данных на ВУ ===
#define TX_SIZE 38 // размер буфера передачи
char TxBuf[TX_SIZE]; // буфер передачи
char *TxPtr; // указатель на очередной символ для передачи
/////////////////////////////////////////////////////////////////////////////////////////
//
// Функция получения аналоговых данных
// (однократно опрашивает используемые входы АЦП)
//
void GetAData(void) {
unsigned Ch; // номер обрабатываемого канала
// Вычисляем индекс очередной ячейки кольцевых буферов
NextAIdx++;
NextAIdx %= C_AI;
// Перебираем все каналы измерения
for (Ch=0; Ch
// Подключаем текущий канал к АЦП
ADC1->SQR3 = Ch;
// Запускаем преобразование
ADC1->CR2 |= ADC_CR2_SWSTART;
// Ожидаем завершения преобразования
while (!(ADC1->SR & ADC_SR_EOC));
// Считываем и сохраняем результат преобразования
AnData[Ch][NextAIdx] = ADC1->DR;
}
}
//////////////////////////////////////////////////
//
// Функция обработки аналоговых данных
// (вычисляет минимальные значения для каждого канала)
//
void ProcessAData(void) {
unsigned Ch;
// номер обрабатываемого канала
unsigned Idx;
// индекс очередного значения
// Перебираем все каналы измерения
for (Ch=0; Ch
AnRes[Ch] = 0;
// обнуляем значения измеренных по каналу Ch значений
// находим минимальные измеренные по каналу Ch значения
for (Idx=0; Idx
if (AnRes[Ch] < AnData[Ch][Idx]) AnRes[Ch]= AnData[Ch][Idx];
// Преобразуем вычисленное значение в вольты
AnRes[Ch] = AnRes[Ch]*K1 + K2;
}
}
//////////////////////////////////////////////////
//
// Функция получения и обработки аналоговых данных
//
void ProcessAInputs(void) {
GetAData(); // получить текущие значения
ProcessAData(); // обработать аналоговые данные
}
//////////////////////////////////////////////////
//
// Функция пользовательской инициализации
//
void Init(void)
{
// Разрешаем работу необходимого оборудования
RCC->APB2ENR |= (
RCC_APB2ENR_USART1EN; // разрешаем УАПП1
RCC_APB2ENR_ADC1EN // разрешаем таймер АЦП1
|RCC_APB2ENR_IOPAEN // разрешаем порт A
|RCC_APB2ENR_IOPBEN // разрешаем порт B
|RCC_APB2ENR_AFIOEN // разрешаем использование
); //альтернативных функций
// Настраиваем и запускаем системный таймер
SysTick->LOAD = 504000-1; // перезагружаемое значение
SysTick->CTRL = SysTick_CTRL_ENABLE; // разрешение счет
// Настраиваем выводы ОМК
// === Аналоговые входы
// Линия PА0
GPIOA->CRL &=
(0xF<<4*0);
GPIOA->CRL |= (0x0<<4*0);
// Линия PA1
GPIOA->CRL &=
(0xF<<4*1);
GPIOA->CRL |= (0x0<<4*1);
// Линия PА2
GPIOA->CRL &=
(0xF<<4*2);
GPIOA->CRL |= (0x0<<4*2);
// Линия PA3
GPIOA->CRL &=
(0xF<<4*3);
GPIOA->CRL |= (0x0<<4*3);
// === Дискретные входы с подтягивающими резисторами к VDD
// линия PA11
GPIOA->CRH &= (0xF<<4*(11-8));
GPIOA->CRH |= (0x2<<4*(11-8));
// линия PA12
GPIOA->CRH &= (0xF<<4*(12-8));
GPIOA->CRH |= (0x2<<4*(12-8));
// линия PA13
GPIOA->CRH &= (0xF<<4*(13-8));
GPIOA->CRH |= (0x2<<4*(13-8));
// === Дискретные выходы GPIO, 2МГц
// Линия PA4
GPIOA->CRL &=
(0xF<<4*4);
GPIOA->CRL |= (0x4<<4*4);
// Линия PA5
GPIOA->CRL &=
(0xF<<4*5);
GPIOA->CRL |= (0x4<<4*5);
// === выходы под ССИ
// Линия PB0
GPIOB -> CRL &=
(0xF <<4*0);
GPIOB -> CRL |= (0x4 <<4*0);
// Линия PB1
GPIOB -> CRL &=
(0xF <<4*1);
GPIOB -> CRL |= (0x4 <<4*1);
// Линия PB2
GPIOB -> CRL &=
(0xF <<4*2);
GPIOB -> CRL |= (0x4 <<4*2);
// Линия PB3
GPIOB -> CRL &=
(0xF <<4*3);
GPIOB -> CRL |= (0x4 <<4*3);
// Линия PB4
GPIOB -> CRL &=
(0xFu <<4*4);
GPIOB -> CRL |= (0x4 <<4*4);
// Линия PB5
GPIOB -> CRL &=
(0xFu <<4*5);
GPIOB -> CRL |= (0x4 <<4*5);
// Линия PB6
GPIOB -> CRL &=
(0xFu <<4*6);
GPIOB -> CRL |= (0x4 <<4*6);
// Настраиваем АЦП
RCC -> CFGR |=
RCC_CFGR_ADCPRE_DIV4; // задаём частоту АЦП (36/4 = 9 МГц)
ADC1 -> CR2 =
ADC_CR2_EXTSEL // разрешить внешний запуск
| ADC_CR2_EXTTRIG; // выбрать источником запуска разряд SWSTART
ADC1 -> CR2 |= ADC_CR2_ADON; // включить АЦП
// Настраиваем УАПП
USART1 -> CR1 = USART_CR1_UE; // разрешаем USART
USART1 -> BRR = 36000000.0 / 19200 + 0.5; // скорость приёма/передачи 19200 бит/сек
// Настраиваем контроллер прерываний
NVIC->ISER[1] = (1<<5); // разрешаем прерывания от УАПП1
}
//////////////////////////////////////////////////
//
// Функция ввода и обработки дискретных сигналов
//
void ProcessDInputs()
{
// чтение состояния линии порта, к которому подключены дискретные входы
DInp =
GPIOB -> IDR;
//Сдвигаем полученное число так, чтобы значение сигнала на первом дискретном входе
// размещалось в нулевом разряде
DInp >>= 11; //первый дискр. вход подключён к линии PА11
// Обнуляем неиспользуемые разряды переменной DInp
DInp &= (1 << N_DI) - 1;
// Выходы подключены к линиям PA4-PA6
unsigned Rst = DInp & ((1 << N_DO) - 1); // обнуляем разряды "лишних" входов
DOut &=
Rst; // обнуляем разряды в сохранённом состоянии выходов
Rst <<= 3; // сдвигаем в позицию первого выхода
GPIOA -> BSRR = Rst; // обнуляем выходы, для которых активен соответствующий вход
}
//////////////////////////////////////////////////
//
// Функция управления дискретными выходами
//
void ProcessDOutputs()
{
unsigned Ch;
unsigned Flags = 0;
// Перебираем аналоговые входы, для которых есть соответствующие дискретные выходы
for (Ch = 0; Ch < N_DO; Ch++) {
// Если условие активации выхода выполняется,
// то устанавливаем соответствующий бит в переменной Flags
if (fabs(0.5*RES_AI_MAX-AnRes[Ch])<0.42*C_DO*RES_AI_MAX)
Flags |= (1<< Ch);
}
DOut |= Flags;
Flags = (DOut<<3) ;
// Готовим маску для выделения значащих битов.
unsigned Mask = ((1<
// обнуляем все дискретные выходы
GPIOA->ODR &=
Mask;
// Выводим единицы на те выходы, где это нужно
GPIOA->ODR |= Flags;
}
//////////////////////////////////////////////////
//
// Функция вывода данных на индикацию
//
void UpdateLEDs()
{
unsigned Mask;
unsigned LEDs;
unsigned Ch;
// ===Индикация номера аналогового входа с максимальным вычисленным значением
float Max = AnRes[0]; // промежуточный максимум
unsigned InpNmb = 0; // сохранённый номер входа
for (Ch=1; Ch
if (Max // значение на входе больше максимума –
Max = AnRes[Ch]; // обновляем текущий максимум
InpNmb = Ch; // и запоминаем номер этого входа
}
InpNmb++; // чтобы нумерация входов начиналась с 1, а не с 0
// Семисегментные коды символов "0"..."9"
static const uint8_t SegCodes[] = { 0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
unsigned Code = SegCodes[InpNmb];
Code <<= 1;
// Выключаем все сегменты (обнуляем выходы)
GPIOB -> ODR &=
(0x7F << 1);
// Выводим код символа
GPIOB -> ODR |= Code;
}
//////////////////////////////////////////////////
//
// Функция подготовки данных для передачи
//
void StartTX()
{
int Ch; // номер очередного входа
unsigned Pos = 0; // текущая позиция в буфере
// Помещаем в буфер значения аналоговых сигналов
for (Ch = 0; Ch < N_AI; Ch++)
Pos += sprintf(& TxBuf[Pos], "%5.3f, ", AnRes[Ch]);
// Помещаем в буфер значения дискретных входов
for (Ch = N_DI - 1; Ch >= 0; Ch--) {
if ((DInp & (1 << Ch)) != 0)
TxBuf[Pos] = '1';
else
TxBuf[Pos] = '0';
Pos++;
}
TxBuf[Pos++] = ',';
TxBuf[Pos++] = ' ';
// Помещаем в буфер значения дискретных выходов
for (Ch = N_DO - 1; Ch >= 0; Ch--) {
if ((DOut & (1 << Ch)) != 0)
TxBuf[Pos] = '1';
else
TxBuf[Pos] = '0';
Pos++;
}
// Добавляем переход на новую строку и терминатор
TxBuf[Pos++] = 0x0D;
TxBuf[Pos++] = 0x0A;
TxBuf[Pos] = 0;
// Инициируем передачу
// установка указателя очередного символа на начало буфера
TxPtr = TxBuf;
// Переводим драйвер в режим передачи
GPIOA -> BSRR = (1 << 8); // устанавливаем в единицу линию 8 порта A
// Разрешаем передатчик и прерывания от него
USART1 -> CR1 |= (
USART_CR1_TE // разрешаем работу передатчика
| USART_CR1_TXEIE // разрешаем прерывания по освобождению буферного регистра
| USART_CR1_TCIE // разрешаем прерывания по окончанию передачи
);
}
//////////////////////////////////////////////////
//
// Подпрограмма обработки прерываний от УАПП
//
void USART1_IRQHadler(void)
{
// Проверяем, есть ли данные для передачи
if (* TxPtr != 0) {
// Данные есть - записываем очередной символ в буферный регистр передатчика
// и перемещаем указатель на следующий символ
USART1 -> DR = * TxPtr++;
} else {
// Данных для передачи больше нет - проверяем причину прерывания
if ((USART1 -> SR & USART_SR_TC) != 0) {
// В регистре состояния передатчика USART_SR установлен бит TC –
// значит передача полностью завершена
GPIOA -> BRR = (1 << 8); // переводим сигнал DE (линия PA8) в неактивное состояние
// Запрещаем все прерывания от передатчика и сам передатчик
USART1 -> CR1 &=
(
USART_CR1_TE // запрещаем работу передатчика
| USART_CR1_TXEIE // запрещаем прерывания по освобождению буферного регистра
| USART_CR1_TCIE // запрещаем прерывания по окончанию передачи
);
} else {
// Прерывание по освобождению буфера, а передавать больше нечего –
// запрещаем прерывания такого типа
USART1 -> CR1 &=
USART_CR1_TXEIE;
#include "stm32f10x.h" // Device header
#include
#include // для функции sprintf()
#include // для функции fabs()
// === Общие параметры программы ===
#define M_DI 3 // множитель периода опроса дискр. входов
#define M_DO 3 // множитель периода обновления дискр. выходов
#define M_COMM 10 // множитель периода передачи на ВУ
// === Параметры подсистемы ввода аналоговых сигналов ===
#define N_AI 4 // количество аналоговых входов
#define C_AI 4 // количество сохраняемых значений для каждого входа
#define K1 0,00094033 // коэффициент К1 для вычисления входного напряжения
#define K2 -0,175183 // коэффициент К2 для вычисления входного напряжения
// Кольцевые буферы для каждого аналогового входа
uint16_t AnData[N_AI][C_AI];
// Массив для сохранения результатов обработки аналоговых данных
float AnRes[N_AI];
// Индекс очередного элемента для записи в массив AnData
unsigned NextAIdx;
// === Параметры подсистем ввода и вывода дискретных сигналов ===
#define N_DI 3 // количество дискретных входов
unsigned DInp; // текущие значения дискретных входов
#define N_DO 2 // количество дискретных выходов
unsigned DOut; // текущие значения дискретных выходов
#define RES_AI_MAX 3.5 // максимальное напряжение на аналоговом входе
#define C_DO 0.84 // порог срабатывания дискретного выхода
// === Переменные для передачи данных на ВУ ===
#define TX_SIZE 38 // размер буфера передачи
char TxBuf[TX_SIZE]; // буфер передачи
char *TxPtr; // указатель на очередной символ для передачи
/////////////////////////////////////////////////////////////////////////////////////////
//
// Функция получения аналоговых данных
// (однократно опрашивает используемые входы АЦП)
//
void GetAData(void) {
unsigned Ch; // номер обрабатываемого канала
// Вычисляем индекс очередной ячейки кольцевых буферов
NextAIdx++;
NextAIdx %= C_AI;
// Перебираем все каналы измерения
for (Ch=0; Ch
// Подключаем текущий канал к АЦП
ADC1->SQR3 = Ch;
// Запускаем преобразование
ADC1->CR2 |= ADC_CR2_SWSTART;
// Ожидаем завершения преобразования
while (!(ADC1->SR & ADC_SR_EOC));
// Считываем и сохраняем результат преобразования
AnData[Ch][NextAIdx] = ADC1->DR;
}
}
//////////////////////////////////////////////////
//
// Функция обработки аналоговых данных
// (вычисляет минимальные значения для каждого канала)
//
void ProcessAData(void) {
unsigned Ch;
// номер обрабатываемого канала
unsigned Idx;
// индекс очередного значения
// Перебираем все каналы измерения
for (Ch=0; Ch
AnRes[Ch] = 0;
// обнуляем значения измеренных по каналу Ch значений
// находим минимальные измеренные по каналу Ch значения
for (Idx=0; Idx
if (AnRes[Ch] < AnData[Ch][Idx]) AnRes[Ch]= AnData[Ch][Idx];
// Преобразуем вычисленное значение в вольты
AnRes[Ch] = AnRes[Ch]*K1 + K2;
}
}
//////////////////////////////////////////////////
//
// Функция получения и обработки аналоговых данных
//
void ProcessAInputs(void) {
GetAData(); // получить текущие значения
ProcessAData(); // обработать аналоговые данные
}
//////////////////////////////////////////////////
//
// Функция пользовательской инициализации
//
void Init(void)
{
// Разрешаем работу необходимого оборудования
RCC->APB2ENR |= (
RCC_APB2ENR_USART1EN; // разрешаем УАПП1
RCC_APB2ENR_ADC1EN // разрешаем таймер АЦП1
|RCC_APB2ENR_IOPAEN // разрешаем порт A
|RCC_APB2ENR_IOPBEN // разрешаем порт B
|RCC_APB2ENR_AFIOEN // разрешаем использование
); //альтернативных функций
// Настраиваем и запускаем системный таймер
SysTick->LOAD = 504000-1; // перезагружаемое значение
SysTick->CTRL = SysTick_CTRL_ENABLE; // разрешение счет
// Настраиваем выводы ОМК
// === Аналоговые входы
// Линия PА0
GPIOA->CRL &=
(0xF<<4*0);
GPIOA->CRL |= (0x0<<4*0);
// Линия PA1
GPIOA->CRL &=
(0xF<<4*1);
GPIOA->CRL |= (0x0<<4*1);
// Линия PА2
GPIOA->CRL &=
(0xF<<4*2);
GPIOA->CRL |= (0x0<<4*2);
// Линия PA3
GPIOA->CRL &=
(0xF<<4*3);
GPIOA->CRL |= (0x0<<4*3);
// === Дискретные входы с подтягивающими резисторами к VDD
// линия PA11
GPIOA->CRH &= (0xF<<4*(11-8));
GPIOA->CRH |= (0x2<<4*(11-8));
// линия PA12
GPIOA->CRH &= (0xF<<4*(12-8));
GPIOA->CRH |= (0x2<<4*(12-8));
// линия PA13
GPIOA->CRH &= (0xF<<4*(13-8));
GPIOA->CRH |= (0x2<<4*(13-8));
// === Дискретные выходы GPIO, 2МГц
// Линия PA4
GPIOA->CRL &=
(0xF<<4*4);
GPIOA->CRL |= (0x4<<4*4);
// Линия PA5
GPIOA->CRL &=
(0xF<<4*5);
GPIOA->CRL |= (0x4<<4*5);
// === выходы под ССИ
// Линия PB0
GPIOB -> CRL &=
(0xF <<4*0);
GPIOB -> CRL |= (0x4 <<4*0);
// Линия PB1
GPIOB -> CRL &=
(0xF <<4*1);
GPIOB -> CRL |= (0x4 <<4*1);
// Линия PB2
GPIOB -> CRL &=
(0xF <<4*2);
GPIOB -> CRL |= (0x4 <<4*2);
// Линия PB3
GPIOB -> CRL &=
(0xF <<4*3);
GPIOB -> CRL |= (0x4 <<4*3);
// Линия PB4
GPIOB -> CRL &=
(0xFu <<4*4);
GPIOB -> CRL |= (0x4 <<4*4);
// Линия PB5
GPIOB -> CRL &=
(0xFu <<4*5);
GPIOB -> CRL |= (0x4 <<4*5);
// Линия PB6
GPIOB -> CRL &=
(0xFu <<4*6);
GPIOB -> CRL |= (0x4 <<4*6);
// Настраиваем АЦП
RCC -> CFGR |=
RCC_CFGR_ADCPRE_DIV4; // задаём частоту АЦП (36/4 = 9 МГц)
ADC1 -> CR2 =
ADC_CR2_EXTSEL // разрешить внешний запуск
| ADC_CR2_EXTTRIG; // выбрать источником запуска разряд SWSTART
ADC1 -> CR2 |= ADC_CR2_ADON; // включить АЦП
// Настраиваем УАПП
USART1 -> CR1 = USART_CR1_UE; // разрешаем USART
USART1 -> BRR = 36000000.0 / 19200 + 0.5; // скорость приёма/передачи 19200 бит/сек
// Настраиваем контроллер прерываний
NVIC->ISER[1] = (1<<5); // разрешаем прерывания от УАПП1
}
//////////////////////////////////////////////////
//
// Функция ввода и обработки дискретных сигналов
//
void ProcessDInputs()
{
// чтение состояния линии порта, к которому подключены дискретные входы
DInp =
GPIOB -> IDR;
//Сдвигаем полученное число так, чтобы значение сигнала на первом дискретном входе
// размещалось в нулевом разряде
DInp >>= 11; //первый дискр. вход подключён к линии PА11
// Обнуляем неиспользуемые разряды переменной DInp
DInp &= (1 << N_DI) - 1;
// Выходы подключены к линиям PA4-PA6
unsigned Rst = DInp & ((1 << N_DO) - 1); // обнуляем разряды "лишних" входов
DOut &=
Rst; // обнуляем разряды в сохранённом состоянии выходов
Rst <<= 3; // сдвигаем в позицию первого выхода
GPIOA -> BSRR = Rst; // обнуляем выходы, для которых активен соответствующий вход
}
//////////////////////////////////////////////////
//
// Функция управления дискретными выходами
//
void ProcessDOutputs()
{
unsigned Ch;
unsigned Flags = 0;
// Перебираем аналоговые входы, для которых есть соответствующие дискретные выходы
for (Ch = 0; Ch < N_DO; Ch++) {
// Если условие активации выхода выполняется,
// то устанавливаем соответствующий бит в переменной Flags
if (fabs(0.5*RES_AI_MAX-AnRes[Ch])<0.42*C_DO*RES_AI_MAX)
Flags |= (1<< Ch);
}
DOut |= Flags;
Flags = (DOut<<3) ;
// Готовим маску для выделения значащих битов.
unsigned Mask = ((1<
// обнуляем все дискретные выходы
GPIOA->ODR &=
Mask;
// Выводим единицы на те выходы, где это нужно
GPIOA->ODR |= Flags;
}
//////////////////////////////////////////////////
//
// Функция вывода данных на индикацию
//
void UpdateLEDs()
{
unsigned Mask;
unsigned LEDs;
unsigned Ch;
// ===Индикация номера аналогового входа с максимальным вычисленным значением
float Max = AnRes[0]; // промежуточный максимум
unsigned InpNmb = 0; // сохранённый номер входа
for (Ch=1; Ch
if (Max // значение на входе больше максимума –
Max = AnRes[Ch]; // обновляем текущий максимум
InpNmb = Ch; // и запоминаем номер этого входа
}
InpNmb++; // чтобы нумерация входов начиналась с 1, а не с 0
// Семисегментные коды символов "0"..."9"
static const uint8_t SegCodes[] = { 0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
unsigned Code = SegCodes[InpNmb];
Code <<= 1;
// Выключаем все сегменты (обнуляем выходы)
GPIOB -> ODR &=
(0x7F << 1);
#include "stm32f10x.h" // Device header
#include
#include // для функции sprintf()
#include // для функции fabs()
// === Общие параметры программы ===
#define M_DI 3 // множитель периода опроса дискр. входов
#define M_DO 3 // множитель периода обновления дискр. выходов
#define M_COMM 10 // множитель периода передачи на ВУ
// === Параметры подсистемы ввода аналоговых сигналов ===
#define N_AI 4 // количество аналоговых входов
#define C_AI 4 // количество сохраняемых значений для каждого входа
#define K1 0,00094033 // коэффициент К1 для вычисления входного напряжения
#define K2 -0,175183 // коэффициент К2 для вычисления входного напряжения
// Кольцевые буферы для каждого аналогового входа
uint16_t AnData[N_AI][C_AI];
// Массив для сохранения результатов обработки аналоговых данных
float AnRes[N_AI];
// Индекс очередного элемента для записи в массив AnData
unsigned NextAIdx;
// === Параметры подсистем ввода и вывода дискретных сигналов ===
#define N_DI 3 // количество дискретных входов
unsigned DInp; // текущие значения дискретных входов
#define N_DO 2 // количество дискретных выходов
unsigned DOut; // текущие значения дискретных выходов
#define RES_AI_MAX 3.5 // максимальное напряжение на аналоговом входе
#define C_DO 0.84 // порог срабатывания дискретного выхода
// === Переменные для передачи данных на ВУ ===
#define TX_SIZE 38 // размер буфера передачи
char TxBuf[TX_SIZE]; // буфер передачи
char *TxPtr; // указатель на очередной символ для передачи
/////////////////////////////////////////////////////////////////////////////////////////
//
// Функция получения аналоговых данных
// (однократно опрашивает используемые входы АЦП)
//
void GetAData(void) {
unsigned Ch; // номер обрабатываемого канала
// Вычисляем индекс очередной ячейки кольцевых буферов
NextAIdx++;
NextAIdx %= C_AI;
// Перебираем все каналы измерения
for (Ch=0; Ch
// Подключаем текущий канал к АЦП
ADC1->SQR3 = Ch;
// Запускаем преобразование
ADC1->CR2 |= ADC_CR2_SWSTART;
// Ожидаем завершения преобразования
while (!(ADC1->SR & ADC_SR_EOC));
// Считываем и сохраняем результат преобразования
AnData[Ch][NextAIdx] = ADC1->DR;
}
}
//////////////////////////////////////////////////
//
// Функция обработки аналоговых данных
// (вычисляет минимальные значения для каждого канала)
//
void ProcessAData(void) {
unsigned Ch;
// номер обрабатываемого канала
unsigned Idx;
// индекс очередного значения
// Перебираем все каналы измерения
for (Ch=0; Ch
AnRes[Ch] = 0;
// обнуляем значения измеренных по каналу Ch значений
// находим минимальные измеренные по каналу Ch значения
for (Idx=0; Idx
if (AnRes[Ch] < AnData[Ch][Idx]) AnRes[Ch]= AnData[Ch][Idx];
// Преобразуем вычисленное значение в вольты
AnRes[Ch] = AnRes[Ch]*K1 + K2;
}
}
//////////////////////////////////////////////////
//
// Функция получения и обработки аналоговых данных
//
void ProcessAInputs(void) {
GetAData(); // получить текущие значения
ProcessAData(); // обработать аналоговые данные
}
//////////////////////////////////////////////////
//
// Функция пользовательской инициализации
//
void Init(void)
{
// Разрешаем работу необходимого оборудования
RCC->APB2ENR |= (
RCC_APB2ENR_USART1EN; // разрешаем УАПП1
RCC_APB2ENR_ADC1EN // разрешаем таймер АЦП1
|RCC_APB2ENR_IOPAEN // разрешаем порт A
|RCC_APB2ENR_IOPBEN // разрешаем порт B
|RCC_APB2ENR_AFIOEN // разрешаем использование
); //альтернативных функций
// Настраиваем и запускаем системный таймер
SysTick->LOAD = 504000-1; // перезагружаемое значение
SysTick->CTRL = SysTick_CTRL_ENABLE; // разрешение счет
// Настраиваем выводы ОМК
// === Аналоговые входы
// Линия PА0
GPIOA->CRL &=
(0xF<<4*0);
GPIOA->CRL |= (0x0<<4*0);
// Линия PA1
GPIOA->CRL &=
(0xF<<4*1);
GPIOA->CRL |= (0x0<<4*1);
// Линия PА2
GPIOA->CRL &=
(0xF<<4*2);
GPIOA->CRL |= (0x0<<4*2);
// Линия PA3
GPIOA->CRL &=
(0xF<<4*3);
GPIOA->CRL |= (0x0<<4*3);
// === Дискретные входы с подтягивающими резисторами к VDD
// линия PA11
GPIOA->CRH &= (0xF<<4*(11-8));
GPIOA->CRH |= (0x2<<4*(11-8));
// линия PA12
GPIOA->CRH &= (0xF<<4*(12-8));
GPIOA->CRH |= (0x2<<4*(12-8));
// линия PA13
GPIOA->CRH &= (0xF<<4*(13-8));
GPIOA->CRH |= (0x2<<4*(13-8));
// === Дискретные выходы GPIO, 2МГц
// Линия PA4
GPIOA->CRL &=
(0xF<<4*4);
GPIOA->CRL |= (0x4<<4*4);
// Линия PA5
GPIOA->CRL &=
(0xF<<4*5);
GPIOA->CRL |= (0x4<<4*5);
// === выходы под ССИ
// Линия PB0
GPIOB -> CRL &=
(0xF <<4*0);
GPIOB -> CRL |= (0x4 <<4*0);
// Линия PB1
GPIOB -> CRL &=
(0xF <<4*1);
GPIOB -> CRL |= (0x4 <<4*1);
// Линия PB2
GPIOB -> CRL &=
(0xF <<4*2);
GPIOB -> CRL |= (0x4 <<4*2);
// Линия PB3
GPIOB -> CRL &=
(0xF <<4*3);
GPIOB -> CRL |= (0x4 <<4*3);
// Линия PB4
GPIOB -> CRL &=
(0xFu <<4*4);
GPIOB -> CRL |= (0x4 <<4*4);
// Линия PB5
GPIOB -> CRL &=
(0xFu <<4*5);
GPIOB -> CRL |= (0x4 <<4*5);
// Линия PB6
GPIOB -> CRL &=
(0xFu <<4*6);
GPIOB -> CRL |= (0x4 <<4*6);
// Настраиваем АЦП
RCC -> CFGR |=
RCC_CFGR_ADCPRE_DIV4; // задаём частоту АЦП (36/4 = 9 МГц)
ADC1 -> CR2 =
ADC_CR2_EXTSEL // разрешить внешний запуск
| ADC_CR2_EXTTRIG; // выбрать источником запуска разряд SWSTART
ADC1 -> CR2 |= ADC_CR2_ADON; // включить АЦП
// Настраиваем УАПП
USART1 -> CR1 = USART_CR1_UE; // разрешаем USART
USART1 -> BRR = 36000000.0 / 19200 + 0.5; // скорость приёма/передачи 19200 бит/сек
// Настраиваем контроллер прерываний
NVIC->ISER[1] = (1<<5); // разрешаем прерывания от УАПП1
}
//////////////////////////////////////////////////
//
// Функция ввода и обработки дискретных сигналов
//
void ProcessDInputs()
{
// чтение состояния линии порта, к которому подключены дискретные входы
DInp =
GPIOB -> IDR;
//Сдвигаем полученное число так, чтобы значение сигнала на первом дискретном входе
// размещалось в нулевом разряде
DInp >>= 11; //первый дискр. вход подключён к линии PА11
// Обнуляем неиспользуемые разряды переменной DInp
DInp &= (1 << N_DI) - 1;
// Выходы подключены к линиям PA4-PA6
unsigned Rst = DInp & ((1 << N_DO) - 1); // обнуляем разряды "лишних" входов
DOut &=
Rst; // обнуляем разряды в сохранённом состоянии выходов
#include "stm32f10x.h" // Device header
#include
#include // для функции sprintf()
#include // для функции fabs()
// === Общие параметры программы ===
#define M_DI 3 // множитель периода опроса дискр. входов
#define M_DO 3 // множитель периода обновления дискр. выходов
#define M_COMM 10 // множитель периода передачи на ВУ
// === Параметры подсистемы ввода аналоговых сигналов ===
#define N_AI 4 // количество аналоговых входов
#define C_AI 4 // количество сохраняемых значений для каждого входа
#define K1 0,00094033 // коэффициент К1 для вычисления входного напряжения
#define K2 -0,175183 // коэффициент К2 для вычисления входного напряжения
// Кольцевые буферы для каждого аналогового входа
uint16_t AnData[N_AI][C_AI];
// Массив для сохранения результатов обработки аналоговых данных
float AnRes[N_AI];
// Индекс очередного элемента для записи в массив AnData
unsigned NextAIdx;
// === Параметры подсистем ввода и вывода дискретных сигналов ===
#define N_DI 3 // количество дискретных входов
unsigned DInp; // текущие значения дискретных входов
#define N_DO 2 // количество дискретных выходов
unsigned DOut; // текущие значения дискретных выходов
#define RES_AI_MAX 3.5 // максимальное напряжение на аналоговом входе
#define C_DO 0.84 // порог срабатывания дискретного выхода
// === Переменные для передачи данных на ВУ ===
#define TX_SIZE 38 // размер буфера передачи
char TxBuf[TX_SIZE]; // буфер передачи
char *TxPtr; // указатель на очередной символ для передачи
/////////////////////////////////////////////////////////////////////////////////////////
//
// Функция получения аналоговых данных
// (однократно опрашивает используемые входы АЦП)
//
void GetAData(void) {
unsigned Ch; // номер обрабатываемого канала
// Вычисляем индекс очередной ячейки кольцевых буферов
NextAIdx++;
NextAIdx %= C_AI;
// Перебираем все каналы измерения
for (Ch=0; Ch
// Подключаем текущий канал к АЦП
ADC1->SQR3 = Ch;
// Запускаем преобразование
ADC1->CR2 |= ADC_CR2_SWSTART;
// Ожидаем завершения преобразования
while (!(ADC1->SR & ADC_SR_EOC));
// Считываем и сохраняем результат преобразования
AnData[Ch][NextAIdx] = ADC1->DR;
}
}
//////////////////////////////////////////////////
//
// Функция обработки аналоговых данных
// (вычисляет минимальные значения для каждого канала)
//
void ProcessAData(void) {
unsigned Ch;
// номер обрабатываемого канала
unsigned Idx;
// индекс очередного значения
// Перебираем все каналы измерения
for (Ch=0; Ch
AnRes[Ch] = 0;
// обнуляем значения измеренных по каналу Ch значений
// находим минимальные измеренные по каналу Ch значения
for (Idx=0; Idx
if (AnRes[Ch] < AnData[Ch][Idx]) AnRes[Ch]= AnData[Ch][Idx];
// Преобразуем вычисленное значение в вольты
AnRes[Ch] = AnRes[Ch]*K1 + K2;
}
}
//////////////////////////////////////////////////
//
// Функция получения и обработки аналоговых данных
//
void ProcessAInputs(void) {
GetAData(); // получить текущие значения
ProcessAData(); // обработать аналоговые данные
}
//////////////////////////////////////////////////
//
// Функция пользовательской инициализации
//
void Init(void)
{
// Разрешаем работу необходимого оборудования
RCC->APB2ENR |= (
RCC_APB2ENR_USART1EN; // разрешаем УАПП1
RCC_APB2ENR_ADC1EN // разрешаем таймер АЦП1
|RCC_APB2ENR_IOPAEN // разрешаем порт A
|RCC_APB2ENR_IOPBEN // разрешаем порт B
|RCC_APB2ENR_AFIOEN // разрешаем использование
); //альтернативных функций
// Настраиваем и запускаем системный таймер
SysTick->LOAD = 504000-1; // перезагружаемое значение
SysTick->CTRL = SysTick_CTRL_ENABLE; // разрешение счет
// Настраиваем выводы ОМК
// === Аналоговые входы
// Линия PА0
GPIOA->CRL &=
(0xF<<4*0);
GPIOA->CRL |= (0x0<<4*0);
// Линия PA1
GPIOA->CRL &=
(0xF<<4*1);
GPIOA->CRL |= (0x0<<4*1);
// Линия PА2
GPIOA->CRL &=
(0xF<<4*2);
GPIOA->CRL |= (0x0<<4*2);
// Линия PA3
GPIOA->CRL &=
(0xF<<4*3);
GPIOA->CRL |= (0x0<<4*3);
// === Дискретные входы с подтягивающими резисторами к VDD
// линия PA11
GPIOA->CRH &= (0xF<<4*(11-8));
GPIOA->CRH |= (0x2<<4*(11-8));
// линия PA12
GPIOA->CRH &= (0xF<<4*(12-8));
GPIOA->CRH |= (0x2<<4*(12-8));
// линия PA13
GPIOA->CRH &= (0xF<<4*(13-8));
GPIOA->CRH |= (0x2<<4*(13-8));
// === Дискретные выходы GPIO, 2МГц
// Линия PA4
GPIOA->CRL &=
(0xF<<4*4);
GPIOA->CRL |= (0x4<<4*4);
// Линия PA5
GPIOA->CRL &=
(0xF<<4*5);
GPIOA->CRL |= (0x4<<4*5);
// === выходы под ССИ
// Линия PB0
GPIOB -> CRL &=
(0xF <<4*0);
GPIOB -> CRL |= (0x4 <<4*0);
// Линия PB1
GPIOB -> CRL &=
(0xF <<4*1);
GPIOB -> CRL |= (0x4 <<4*1);
// Линия PB2
GPIOB -> CRL &=
(0xF <<4*2);
GPIOB -> CRL |= (0x4 <<4*2);
// Линия PB3
GPIOB -> CRL &=
(0xF <<4*3);
GPIOB -> CRL |= (0x4 <<4*3);
// Линия PB4
GPIOB -> CRL &=
(0xFu <<4*4);
GPIOB -> CRL |= (0x4 <<4*4);
// Линия PB5
GPIOB -> CRL &=
(0xFu <<4*5);
GPIOB -> CRL |= (0x4 <<4*5);
// Линия PB6
GPIOB -> CRL &=
(0xFu <<4*6);
GPIOB -> CRL |= (0x4 <<4*6);
// Настраиваем АЦП
RCC -> CFGR |=
RCC_CFGR_ADCPRE_DIV4; // задаём частоту АЦП (36/4 = 9 МГц)
#include "stm32f10x.h" // Device header
#include
#include // для функции sprintf()
#include // для функции fabs()
// === Общие параметры программы ===
#define M_DI 3 // множитель периода опроса дискр. входов
#define M_DO 3 // множитель периода обновления дискр. выходов
#define M_COMM 10 // множитель периода передачи на ВУ
// === Параметры подсистемы ввода аналоговых сигналов ===
#define N_AI 4 // количество аналоговых входов
#define C_AI 4 // количество сохраняемых значений для каждого входа
#define K1 0,00094033 // коэффициент К1 для вычисления входного напряжения
#define K2 -0,175183 // коэффициент К2 для вычисления входного напряжения
// Кольцевые буферы для каждого аналогового входа
uint16_t AnData[N_AI][C_AI];
// Массив для сохранения результатов обработки аналоговых данных
float AnRes[N_AI];
// Индекс очередного элемента для записи в массив AnData
unsigned NextAIdx;
// === Параметры подсистем ввода и вывода дискретных сигналов ===
#define N_DI 3 // количество дискретных входов
unsigned DInp; // текущие значения дискретных входов
#define N_DO 2 // количество дискретных выходов
unsigned DOut; // текущие значения дискретных выходов
#define RES_AI_MAX 3.5 // максимальное напряжение на аналоговом входе
#define C_DO 0.84 // порог срабатывания дискретного выхода
// === Переменные для передачи данных на ВУ ===
#define TX_SIZE 38 // размер буфера передачи
char TxBuf[TX_SIZE]; // буфер передачи
char *TxPtr; // указатель на очередной символ для передачи
/////////////////////////////////////////////////////////////////////////////////////////
//
// Функция получения аналоговых данных
// (однократно опрашивает используемые входы АЦП)
//
void GetAData(void) {
unsigned Ch; // номер обрабатываемого канала
// Вычисляем индекс очередной ячейки кольцевых буферов
NextAIdx++;
NextAIdx %= C_AI;
// Перебираем все каналы измерения
for (Ch=0; Ch
// Подключаем текущий канал к АЦП
ADC1->SQR3 = Ch;
// Запускаем преобразование
ADC1->CR2 |= ADC_CR2_SWSTART;
// Ожидаем завершения преобразования
while (!(ADC1->SR & ADC_SR_EOC));
// Считываем и сохраняем результат преобразования
AnData[Ch][NextAIdx] = ADC1->DR;
}
}
//////////////////////////////////////////////////
//
// Функция обработки аналоговых данных
// (вычисляет минимальные значения для каждого канала)
//
void ProcessAData(void) {
unsigned Ch;
// номер обрабатываемого канала
unsigned Idx;
// индекс очередного значения
// Перебираем все каналы измерения
for (Ch=0; Ch
AnRes[Ch] = 0;
// обнуляем значения измеренных по каналу Ch значений
// находим минимальные измеренные по каналу Ch значения
for (Idx=0; Idx
if (AnRes[Ch] < AnData[Ch][Idx]) AnRes[Ch]= AnData[Ch][Idx];
// Преобразуем вычисленное значение в вольты
AnRes[Ch] = AnRes[Ch]*K1 + K2;
}
}
//////////////////////////////////////////////////
//
// Функция получения и обработки аналоговых данных
//
void ProcessAInputs(void) {
GetAData(); // получить текущие значения
ProcessAData(); // обработать аналоговые данные
}
//////////////////////////////////////////////////
//
// Функция пользовательской инициализации
//
void Init(void)
{
// Разрешаем работу необходимого оборудования
RCC->APB2ENR |= (
RCC_APB2ENR_USART1EN; // разрешаем УАПП1
RCC_APB2ENR_ADC1EN // разрешаем таймер АЦП1
|RCC_APB2ENR_IOPAEN // разрешаем порт A
|RCC_APB2ENR_IOPBEN // разрешаем порт B
|RCC_APB2ENR_AFIOEN // разрешаем использование
); //альтернативных функций
// Настраиваем и запускаем системный таймер
SysTick->LOAD = 504000-1; // перезагружаемое значение
SysTick->CTRL = SysTick_CTRL_ENABLE; // разрешение счет
// Настраиваем выводы ОМК
// === Аналоговые входы
// Линия PА0
GPIOA->CRL &=
(0xF<<4*0);
GPIOA->CRL |= (0x0<<4*0);
// Линия PA1
GPIOA->CRL &=
(0xF<<4*1);
GPIOA->CRL |= (0x0<<4*1);
// Линия PА2
GPIOA->CRL &=
(0xF<<4*2);
GPIOA->CRL |= (0x0<<4*2);
// Линия PA3
GPIOA->CRL &=
(0xF<<4*3);
GPIOA->CRL |= (0x0<<4*3);
// === Дискретные входы с подтягивающими резисторами к VDD
// линия PA11
GPIOA->CRH &= (0xF<<4*(11-8));
GPIOA->CRH |= (0x2<<4*(11-8));
// линия PA12
GPIOA->CRH &= (0xF<<4*(12-8));
GPIOA->CRH |= (0x2<<4*(12-8));
// линия PA13
GPIOA->CRH &= (0xF<<4*(13-8));
GPIOA->CRH |= (0x2<<4*(13-8));
// === Дискретные выходы GPIO, 2МГц
// Линия PA4
GPIOA->CRL &=
(0xF<<4*4);
GPIOA->CRL |= (0x4<<4*4);
// Линия PA5
GPIOA->CRL &=
(0xF<<4*5);
GPIOA->CRL |= (0x4<<4*5);
// === выходы под ССИ
// Линия PB0
GPIOB -> CRL &=
(0xF <<4*0);
GPIOB -> CRL |= (0x4 <<4*0);
// Линия PB1
GPIOB -> CRL &=
(0xF <<4*1);
GPIOB -> CRL |= (0x4 <<4*1);
// Линия PB2
GPIOB -> CRL &=
(0xF <<4*2);
GPIOB -> CRL |= (0x4 <<4*2);
// Линия PB3
GPIOB -> CRL &=
(0xF <<4*3);
GPIOB -> CRL |= (0x4 <<4*3);
// Линия PB4
GPIOB -> CRL &=
(0xFu <<4*4);
GPIOB -> CRL |= (0x4 <<4*4);
// Линия PB5
GPIOB -> CRL &=
(0xFu <<4*5);
#include "stm32f10x.h" // Device header
#include
#include // для функции sprintf()
#include // для функции fabs()
// === Общие параметры программы ===
#define M_DI 3 // множитель периода опроса дискр. входов
#define M_DO 3 // множитель периода обновления дискр. выходов
#define M_COMM 10 // множитель периода передачи на ВУ
// === Параметры подсистемы ввода аналоговых сигналов ===
#define N_AI 4 // количество аналоговых входов
#define C_AI 4 // количество сохраняемых значений для каждого входа
#define K1 0,00094033 // коэффициент К1 для вычисления входного напряжения
#define K2 -0,175183 // коэффициент К2 для вычисления входного напряжения
// Кольцевые буферы для каждого аналогового входа
uint16_t AnData[N_AI][C_AI];
// Массив для сохранения результатов обработки аналоговых данных
float AnRes[N_AI];
// Индекс очередного элемента для записи в массив AnData
unsigned NextAIdx;
// === Параметры подсистем ввода и вывода дискретных сигналов ===
#define N_DI 3 // количество дискретных входов
unsigned DInp; // текущие значения дискретных входов
#define N_DO 2 // количество дискретных выходов
unsigned DOut; // текущие значения дискретных выходов
#define RES_AI_MAX 3.5 // максимальное напряжение на аналоговом входе
#define C_DO 0.84 // порог срабатывания дискретного выхода
// === Переменные для передачи данных на ВУ ===
#define TX_SIZE 38 // размер буфера передачи
char TxBuf[TX_SIZE]; // буфер передачи
char *TxPtr; // указатель на очередной символ для передачи
/////////////////////////////////////////////////////////////////////////////////////////
//
// Функция получения аналоговых данных
// (однократно опрашивает используемые входы АЦП)
//
void GetAData(void) {
unsigned Ch; // номер обрабатываемого канала
// Вычисляем индекс очередной ячейки кольцевых буферов
NextAIdx++;
NextAIdx %= C_AI;
// Перебираем все каналы измерения
for (Ch=0; Ch
// Подключаем текущий канал к АЦП
ADC1->SQR3 = Ch;
// Запускаем преобразование
ADC1->CR2 |= ADC_CR2_SWSTART;
// Ожидаем завершения преобразования
while (!(ADC1->SR & ADC_SR_EOC));
// Считываем и сохраняем результат преобразования
AnData[Ch][NextAIdx] = ADC1->DR;
}
}
//////////////////////////////////////////////////
//
// Функция обработки аналоговых данных
// (вычисляет минимальные значения для каждого канала)
//
void ProcessAData(void) {
unsigned Ch;
// номер обрабатываемого канала
unsigned Idx;
// индекс очередного значения
// Перебираем все каналы измерения
for (Ch=0; Ch
AnRes[Ch] = 0;
// обнуляем значения измеренных по каналу Ch значений
// находим минимальные измеренные по каналу Ch значения
for (Idx=0; Idx
if (AnRes[Ch] < AnData[Ch][Idx]) AnRes[Ch]= AnData[Ch][Idx];
// Преобразуем вычисленное значение в вольты
AnRes[Ch] = AnRes[Ch]*K1 + K2;
}
}
//////////////////////////////////////////////////
//
// Функция получения и обработки аналоговых данных
//
void ProcessAInputs(void) {
GetAData(); // получить текущие значения
ProcessAData(); // обработать аналоговые данные
}
//////////////////////////////////////////////////
//
// Функция пользовательской инициализации
//
void Init(void)
{
// Разрешаем работу необходимого оборудования
RCC->APB2ENR |= (
RCC_APB2ENR_USART1EN; // разрешаем УАПП1
RCC_APB2ENR_ADC1EN // разрешаем таймер АЦП1
|RCC_APB2ENR_IOPAEN // разрешаем порт A
|RCC_APB2ENR_IOPBEN // разрешаем порт B
|RCC_APB2ENR_AFIOEN // разрешаем использование
); //альтернативных функций
// Настраиваем и запускаем системный таймер
SysTick->LOAD = 504000-1; // перезагружаемое значение
SysTick->CTRL = SysTick_CTRL_ENABLE; // разрешение счет
// Настраиваем выводы ОМК
// === Аналоговые входы
// Линия PА0
GPIOA->CRL &=
(0xF<<4*0);
GPIOA->CRL |= (0x0<<4*0);
// Линия PA1
GPIOA->CRL &=
(0xF<<4*1);
GPIOA->CRL |= (0x0<<4*1);
// Линия PА2
GPIOA->CRL &=
(0xF<<4*2);
GPIOA->CRL |= (0x0<<4*2);
// Линия PA3
GPIOA->CRL &=
(0xF<<4*3);
GPIOA->CRL |= (0x0<<4*3);
// === Дискретные входы с подтягивающими резисторами к VDD
// линия PA11
GPIOA->CRH &= (0xF<<4*(11-8));
GPIOA->CRH |= (0x2<<4*(11-8));
// линия PA12
GPIOA->CRH &= (0xF<<4*(12-8));
GPIOA->CRH |= (0x2<<4*(12-8));
// линия PA13
GPIOA->CRH &= (0xF<<4*(13-8));
GPIOA->CRH |= (0x2<<4*(13-8));
// === Дискретные выходы GPIO, 2МГц
// Линия PA4
GPIOA->CRL &=
(0xF<<4*4);
GPIOA->CRL |= (0x4<<4*4);
// Линия PA5
GPIOA->CRL &=
(0xF<<4*5);
GPIOA->CRL |= (0x4<<4*5);
// === выходы под ССИ
// Линия PB0
GPIOB -> CRL &=
(0xF <<4*0);
GPIOB -> CRL |= (0x4 <<4*0);
// Линия PB1
GPIOB -> CRL &=
(0xF <<4*1);
GPIOB -> CRL |= (0x4 <<4*1);
// Линия PB2
GPIOB -> CRL &=
(0xF <<4*2);
GPIOB -> CRL |= (0x4 <<4*2);
// Линия PB3
GPIOB -> CRL &=
(0xF <<4*3);
#include "stm32f10x.h" // Device header
#include
#include // для функции sprintf()
#include // для функции fabs()
// === Общие параметры программы ===
#define M_DI 3 // множитель периода опроса дискр. входов
#define M_DO 3 // множитель периода обновления дискр. выходов
#define M_COMM 10 // множитель периода передачи на ВУ
// === Параметры подсистемы ввода аналоговых сигналов ===
#define N_AI 4 // количество аналоговых входов
#define C_AI 4 // количество сохраняемых значений для каждого входа
#define K1 0,00094033 // коэффициент К1 для вычисления входного напряжения
#define K2 -0,175183 // коэффициент К2 для вычисления входного напряжения
// Кольцевые буферы для каждого аналогового входа
uint16_t AnData[N_AI][C_AI];
// Массив для сохранения результатов обработки аналоговых данных
float AnRes[N_AI];
// Индекс очередного элемента для записи в массив AnData
unsigned NextAIdx;
// === Параметры подсистем ввода и вывода дискретных сигналов ===
#define N_DI 3 // количество дискретных входов
unsigned DInp; // текущие значения дискретных входов
#define N_DO 2 // количество дискретных выходов
unsigned DOut; // текущие значения дискретных выходов
#define RES_AI_MAX 3.5 // максимальное напряжение на аналоговом входе
#define C_DO 0.84 // порог срабатывания дискретного выхода
// === Переменные для передачи данных на ВУ ===
#define TX_SIZE 38 // размер буфера передачи
char TxBuf[TX_SIZE]; // буфер передачи
char *TxPtr; // указатель на очередной символ для передачи
/////////////////////////////////////////////////////////////////////////////////////////
//
// Функция получения аналоговых данных
// (однократно опрашивает используемые входы АЦП)
//
void GetAData(void) {
unsigned Ch; // номер обрабатываемого канала
// Вычисляем индекс очередной ячейки кольцевых буферов
NextAIdx++;
NextAIdx %= C_AI;
// Перебираем все каналы измерения
for (Ch=0; Ch
// Подключаем текущий канал к АЦП
ADC1->SQR3 = Ch;
// Запускаем преобразование
ADC1->CR2 |= ADC_CR2_SWSTART;
// Ожидаем завершения преобразования
while (!(ADC1->SR & ADC_SR_EOC));
// Считываем и сохраняем результат преобразования
AnData[Ch][NextAIdx] = ADC1->DR;
}
}
//////////////////////////////////////////////////
//
// Функция обработки аналоговых данных
// (вычисляет минимальные значения для каждого канала)
//
void ProcessAData(void) {
unsigned Ch;
// номер обрабатываемого канала
unsigned Idx;
// индекс очередного значения
// Перебираем все каналы измерения
for (Ch=0; Ch
AnRes[Ch] = 0;
// обнуляем значения измеренных по каналу Ch значений
// находим минимальные измеренные по каналу Ch значения
for (Idx=0; Idx
if (AnRes[Ch] < AnData[Ch][Idx]) AnRes[Ch]= AnData[Ch][Idx];
// Преобразуем вычисленное значение в вольты
AnRes[Ch] = AnRes[Ch]*K1 + K2;
}
}
//////////////////////////////////////////////////
//
// Функция получения и обработки аналоговых данных
//
void ProcessAInputs(void) {
GetAData(); // получить текущие значения
ProcessAData(); // обработать аналоговые данные
}
//////////////////////////////////////////////////
//
// Функция пользовательской инициализации
//
void Init(void)
{
// Разрешаем работу необходимого оборудования
RCC->APB2ENR |= (
RCC_APB2ENR_USART1EN; // разрешаем УАПП1
RCC_APB2ENR_ADC1EN // разрешаем таймер АЦП1
|RCC_APB2ENR_IOPAEN // разрешаем порт A
|RCC_APB2ENR_IOPBEN // разрешаем порт B
|RCC_APB2ENR_AFIOEN // разрешаем использование
); //альтернативных функций
// Настраиваем и запускаем системный таймер
SysTick->LOAD = 504000-1; // перезагружаемое значение
SysTick->CTRL = SysTick_CTRL_ENABLE; // разрешение счет
// Настраиваем выводы ОМК
// === Аналоговые входы
// Линия PА0
GPIOA->CRL &=
(0xF<<4*0);
GPIOA->CRL |= (0x0<<4*0);
// Линия PA1
GPIOA->CRL &=
(0xF<<4*1);
GPIOA->CRL |= (0x0<<4*1);
// Линия PА2
GPIOA->CRL &=
(0xF<<4*2);
GPIOA->CRL |= (0x0<<4*2);
// Линия PA3
GPIOA->CRL &=
(0xF<<4*3);
GPIOA->CRL |= (0x0<<4*3);
// === Дискретные входы с подтягивающими резисторами к VDD
// линия PA11
GPIOA->CRH &= (0xF<<4*(11-8));
GPIOA->CRH |= (0x2<<4*(11-8));
// линия PA12
GPIOA->CRH &= (0xF<<4*(12-8));
GPIOA->CRH |= (0x2<<4*(12-8));
// линия PA13
GPIOA->CRH &= (0xF<<4*(13-8));
GPIOA->CRH |= (0x2<<4*(13-8));
// === Дискретные выходы GPIO, 2МГц
// Линия PA4
GPIOA->CRL &=
(0xF<<4*4);
GPIOA->CRL |= (0x4<<4*4);
// Линия PA5
GPIOA->CRL &=
(0xF<<4*5);
GPIOA->CRL |= (0x4<<4*5);
// === выходы под ССИ
// Линия PB0
GPIOB -> CRL &=
(0xF <<4*0);
GPIOB -> CRL |= (0x4 <<4*0);
// Линия PB1
GPIOB -> CRL &=
(0xF <<4*1);
#include "stm32f10x.h" // Device header
#include
#include // для функции sprintf()
#include // для функции fabs()
// === Общие параметры программы ===
#define M_DI 3 // множитель периода опроса дискр. входов
#define M_DO 3 // множитель периода обновления дискр. выходов
#define M_COMM 10 // множитель периода передачи на ВУ
// === Параметры подсистемы ввода аналоговых сигналов ===
#define N_AI 4 // количество аналоговых входов
#define C_AI 4 // количество сохраняемых значений для каждого входа
#define K1 0,00094033 // коэффициент К1 для вычисления входного напряжения
#define K2 -0,175183 // коэффициент К2 для вычисления входного напряжения
// Кольцевые буферы для каждого аналогового входа
uint16_t AnData[N_AI][C_AI];
// Массив для сохранения результатов обработки аналоговых данных
float AnRes[N_AI];
// Индекс очередного элемента для записи в массив AnData
unsigned NextAIdx;
// === Параметры подсистем ввода и вывода дискретных сигналов ===
#define N_DI 3 // количество дискретных входов
unsigned DInp; // текущие значения дискретных входов
#define N_DO 2 // количество дискретных выходов
unsigned DOut; // текущие значения дискретных выходов
#define RES_AI_MAX 3.5 // максимальное напряжение на аналоговом входе
#define C_DO 0.84 // порог срабатывания дискретного выхода
// === Переменные для передачи данных на ВУ ===
#define TX_SIZE 38 // размер буфера передачи
char TxBuf[TX_SIZE]; // буфер передачи
char *TxPtr; // указатель на очередной символ для передачи
/////////////////////////////////////////////////////////////////////////////////////////
//
// Функция получения аналоговых данных
// (однократно опрашивает используемые входы АЦП)
//
void GetAData(void) {
unsigned Ch; // номер обрабатываемого канала
// Вычисляем индекс очередной ячейки кольцевых буферов
NextAIdx++;
NextAIdx %= C_AI;
// Перебираем все каналы измерения
for (Ch=0; Ch
// Подключаем текущий канал к АЦП
ADC1->SQR3 = Ch;
// Запускаем преобразование
ADC1->CR2 |= ADC_CR2_SWSTART;
// Ожидаем завершения преобразования
while (!(ADC1->SR & ADC_SR_EOC));
// Считываем и сохраняем результат преобразования
AnData[Ch][NextAIdx] = ADC1->DR;
}
}
//////////////////////////////////////////////////
//
// Функция обработки аналоговых данных
// (вычисляет минимальные значения для каждого канала)
//
void ProcessAData(void) {
unsigned Ch;
// номер обрабатываемого канала
unsigned Idx;
// индекс очередного значения
// Перебираем все каналы измерения
for (Ch=0; Ch
AnRes[Ch] = 0;
// обнуляем значения измеренных по каналу Ch значений
// находим минимальные измеренные по каналу Ch значения
for (Idx=0; Idx
if (AnRes[Ch] < AnData[Ch][Idx]) AnRes[Ch]= AnData[Ch][Idx];
// Преобразуем вычисленное значение в вольты
AnRes[Ch] = AnRes[Ch]*K1 + K2;
}
}
//////////////////////////////////////////////////
//
// Функция получения и обработки аналоговых данных
//
void ProcessAInputs(void) {
GetAData(); // получить текущие значения
ProcessAData(); // обработать аналоговые данные
}
//////////////////////////////////////////////////
//
// Функция пользовательской инициализации
//
void Init(void)
{
// Разрешаем работу необходимого оборудования
RCC->APB2ENR |= (
RCC_APB2ENR_USART1EN; // разрешаем УАПП1
RCC_APB2ENR_ADC1EN // разрешаем таймер АЦП1
|RCC_APB2ENR_IOPAEN // разрешаем порт A
|RCC_APB2ENR_IOPBEN // разрешаем порт B
|RCC_APB2ENR_AFIOEN // разрешаем использование
); //альтернативных функций
// Настраиваем и запускаем системный таймер
SysTick->LOAD = 504000-1; // перезагружаемое значение
SysTick->CTRL = SysTick_CTRL_ENABLE; // разрешение счет
// Настраиваем выводы ОМК
// === Аналоговые входы
// Линия PА0
GPIOA->CRL &=
(0xF<<4*0);
GPIOA->CRL |= (0x0<<4*0);
// Линия PA1
GPIOA->CRL &=
(0xF<<4*1);
GPIOA->CRL |= (0x0<<4*1);
// Линия PА2
GPIOA->CRL &=
(0xF<<4*2);
GPIOA->CRL |= (0x0<<4*2);
// Линия PA3
GPIOA->CRL &=
(0xF<<4*3);
GPIOA->CRL |= (0x0<<4*3);
// === Дискретные входы с подтягивающими резисторами к VDD
// линия PA11
GPIOA->CRH &= (0xF<<4*(11-8));
GPIOA->CRH |= (0x2<<4*(11-8));
// линия PA12
GPIOA->CRH &= (0xF<<4*(12-8));
GPIOA->CRH |= (0x2<<4*(12-8));
// линия PA13
GPIOA->CRH &= (0xF<<4*(13-8));
GPIOA->CRH |= (0x2<<4*(13-8));
// === Дискретные выходы GPIO, 2МГц
// Линия PA4
GPIOA->CRL &=
(0xF<<4*4);
GPIOA->CRL |= (0x4<<4*4);
// Линия PA5
GPIOA->CRL &=
(0xF<<4*5);
#include "stm32f10x.h" // Device header
#include
#include // для функции sprintf()
#include // для функции fabs()
// === Общие параметры программы ===
#define M_DI 3 // множитель периода опроса дискр. входов
#define M_DO 3 // множитель периода обновления дискр. выходов
#define M_COMM 10 // множитель периода передачи на ВУ
// === Параметры подсистемы ввода аналоговых сигналов ===
#define N_AI 4 // количество аналоговых входов
#define C_AI 4 // количество сохраняемых значений для каждого входа
#define K1 0,00094033 // коэффициент К1 для вычисления входного напряжения
#define K2 -0,175183 // коэффициент К2 для вычисления входного напряжения
// Кольцевые буферы для каждого аналогового входа
uint16_t AnData[N_AI][C_AI];
// Массив для сохранения результатов обработки аналоговых данных
float AnRes[N_AI];
// Индекс очередного элемента для записи в массив AnData
unsigned NextAIdx;
// === Параметры подсистем ввода и вывода дискретных сигналов ===
#define N_DI 3 // количество дискретных входов
unsigned DInp; // текущие значения дискретных входов
#define N_DO 2 // количество дискретных выходов
unsigned DOut; // текущие значения дискретных выходов
#define RES_AI_MAX 3.5 // максимальное напряжение на аналоговом входе
#define C_DO 0.84 // порог срабатывания дискретного выхода
// === Переменные для передачи данных на ВУ ===
#define TX_SIZE 38 // размер буфера передачи
char TxBuf[TX_SIZE]; // буфер передачи
char *TxPtr; // указатель на очередной символ для передачи
/////////////////////////////////////////////////////////////////////////////////////////
//
// Функция получения аналоговых данных
// (однократно опрашивает используемые входы АЦП)
//
void GetAData(void) {
unsigned Ch; // номер обрабатываемого канала
// Вычисляем индекс очередной ячейки кольцевых буферов
NextAIdx++;
NextAIdx %= C_AI;
// Перебираем все каналы измерения
for (Ch=0; Ch
// Подключаем текущий канал к АЦП
ADC1->SQR3 = Ch;
// Запускаем преобразование
ADC1->CR2 |= ADC_CR2_SWSTART;
// Ожидаем завершения преобразования
while (!(ADC1->SR & ADC_SR_EOC));
// Считываем и сохраняем результат преобразования
AnData[Ch][NextAIdx] = ADC1->DR;
}
}
//////////////////////////////////////////////////
//
// Функция обработки аналоговых данных
// (вычисляет минимальные значения для каждого канала)
//
void ProcessAData(void) {
unsigned Ch;
// номер обрабатываемого канала
unsigned Idx;
// индекс очередного значения
// Перебираем все каналы измерения
for (Ch=0; Ch
AnRes[Ch] = 0;
// обнуляем значения измеренных по каналу Ch значений
// находим минимальные измеренные по каналу Ch значения
for (Idx=0; Idx
if (AnRes[Ch] < AnData[Ch][Idx]) AnRes[Ch]= AnData[Ch][Idx];
// Преобразуем вычисленное значение в вольты
AnRes[Ch] = AnRes[Ch]*K1 + K2;
}
}
//////////////////////////////////////////////////
//
// Функция получения и обработки аналоговых данных
//
void ProcessAInputs(void) {
GetAData(); // получить текущие значения
ProcessAData(); // обработать аналоговые данные
}
//////////////////////////////////////////////////
//
// Функция пользовательской инициализации
//
void Init(void)
{
// Разрешаем работу необходимого оборудования
RCC->APB2ENR |= (
RCC_APB2ENR_USART1EN; // разрешаем УАПП1
RCC_APB2ENR_ADC1EN // разрешаем таймер АЦП1
|RCC_APB2ENR_IOPAEN // разрешаем порт A
|RCC_APB2ENR_IOPBEN // разрешаем порт B
|RCC_APB2ENR_AFIOEN // разрешаем использование
); //альтернативных функций
// Настраиваем и запускаем системный таймер
SysTick->LOAD = 504000-1; // перезагружаемое значение
SysTick->CTRL = SysTick_CTRL_ENABLE; // разрешение счет
// Настраиваем выводы ОМК
// === Аналоговые входы
// Линия PА0
GPIOA->CRL &=
(0xF<<4*0);
GPIOA->CRL |= (0x0<<4*0);
// Линия PA1
GPIOA->CRL &=
(0xF<<4*1);
GPIOA->CRL |= (0x0<<4*1);
// Линия PА2
GPIOA->CRL &=
(0xF<<4*2);
GPIOA->CRL |= (0x0<<4*2);
// Линия PA3
GPIOA->CRL &=
(0xF<<4*3);
#include "stm32f10x.h" // Device header
#include
#include // для функции sprintf()
#include // для функции fabs()
// === Общие параметры программы ===
#define M_DI 3 // множитель периода опроса дискр. входов
#define M_DO 3 // множитель периода обновления дискр. выходов
#define M_COMM 10 // множитель периода передачи на ВУ
// === Параметры подсистемы ввода аналоговых сигналов ===
#define N_AI 4 // количество аналоговых входов
#define C_AI 4 // количество сохраняемых значений для каждого входа
#define K1 0,00094033 // коэффициент К1 для вычисления входного напряжения
#define K2 -0,175183 // коэффициент К2 для вычисления входного напряжения
// Кольцевые буферы для каждого аналогового входа
uint16_t AnData[N_AI][C_AI];
// Массив для сохранения результатов обработки аналоговых данных
float AnRes[N_AI];
// Индекс очередного элемента для записи в массив AnData
unsigned NextAIdx;
// === Параметры подсистем ввода и вывода дискретных сигналов ===
#define N_DI 3 // количество дискретных входов
unsigned DInp; // текущие значения дискретных входов
#define N_DO 2 // количество дискретных выходов
unsigned DOut; // текущие значения дискретных выходов
#define RES_AI_MAX 3.5 // максимальное напряжение на аналоговом входе
#define C_DO 0.84 // порог срабатывания дискретного выхода
// === Переменные для передачи данных на ВУ ===
#define TX_SIZE 38 // размер буфера передачи
char TxBuf[TX_SIZE]; // буфер передачи
char *TxPtr; // указатель на очередной символ для передачи
/////////////////////////////////////////////////////////////////////////////////////////
//
// Функция получения аналоговых данных
// (однократно опрашивает используемые входы АЦП)
//
void GetAData(void) {
unsigned Ch; // номер обрабатываемого канала
// Вычисляем индекс очередной ячейки кольцевых буферов
NextAIdx++;
NextAIdx %= C_AI;
// Перебираем все каналы измерения
for (Ch=0; Ch
// Подключаем текущий канал к АЦП
ADC1->SQR3 = Ch;
// Запускаем преобразование
ADC1->CR2 |= ADC_CR2_SWSTART;
// Ожидаем завершения преобразования
while (!(ADC1->SR & ADC_SR_EOC));
// Считываем и сохраняем результат преобразования
AnData[Ch][NextAIdx] = ADC1->DR;
}
}
//////////////////////////////////////////////////
//
// Функция обработки аналоговых данных
// (вычисляет минимальные значения для каждого канала)
//
void ProcessAData(void) {
unsigned Ch;
// номер обрабатываемого канала
unsigned Idx;
// индекс очередного значения
// Перебираем все каналы измерения
for (Ch=0; Ch
AnRes[Ch] = 0;
// обнуляем значения измеренных по каналу Ch значений
// находим минимальные измеренные по каналу Ch значения
for (Idx=0; Idx
if (AnRes[Ch] < AnData[Ch][Idx]) AnRes[Ch]= AnData[Ch][Idx];
// Преобразуем вычисленное значение в вольты
AnRes[Ch] = AnRes[Ch]*K1 + K2;
}
}
//////////////////////////////////////////////////
//
// Функция получения и обработки аналоговых данных
//
void ProcessAInputs(void) {
GetAData(); // получить текущие значения
ProcessAData(); // обработать аналоговые данные
}
//////////////////////////////////////////////////
//
// Функция пользовательской инициализации
//
void Init(void)
{
// Разрешаем работу необходимого оборудования
RCC->APB2ENR |= (
RCC_APB2ENR_USART1EN; // разрешаем УАПП1
RCC_APB2ENR_ADC1EN // разрешаем таймер АЦП1
|RCC_APB2ENR_IOPAEN // разрешаем порт A
|RCC_APB2ENR_IOPBEN // разрешаем порт B
|RCC_APB2ENR_AFIOEN // разрешаем использование
); //альтернативных функций
// Настраиваем и запускаем системный таймер
SysTick->LOAD = 504000-1; // перезагружаемое значение
SysTick->CTRL = SysTick_CTRL_ENABLE; // разрешение счет
// Настраиваем выводы ОМК
// === Аналоговые входы
// Линия PА0
GPIOA->CRL &=
(0xF<<4*0);
GPIOA->CRL |= (0x0<<4*0);
// Линия PA1
GPIOA->CRL &=
(0xF<<4*1);#include "stm32f10x.h" // Device header
#include
#include
#include
// === Общие параметры программы ===
#define M_DI 3 // множитель периода опроса дискр. входов
#define M_DO 3 // множитель периода обновления дискр. выходов
#define M_COMM 10 // множитель периода передачи на ВУ
// === Параметры подсистемы ввода аналоговых сигналов ===
#define N_AI 4 // количество аналоговых входов
#define C_AI 4 // количество сохраняемых значений для каждого входа
#define K1 0,00094033 // коэффициент К1 для вычисления входного напряжения
#define K2 -0,175183 // коэффициент К2 для вычисления входного напряжения
// Кольцевые буферы для каждого аналогового входа
uint16_t AnData[N_AI][C_AI];
// Массив для сохранения результатов обработки аналоговых данных
float AnRes[N_AI];
// Индекс очередного элемента для записи в массив AnData
unsigned NextAIdx;
// === Параметры подсистем ввода и вывода дискретных сигналов ===
#define N_DI 3 // количество дискретных входов
unsigned DInp; // текущие значения дискретных входов
#define N_DO 2 // количество дискретных выходов
unsigned DOut; // текущие значения дискретных выходов
#define RES_AI_MAX 3.5 // максимальное напряжение на аналоговом входе
#define C_DO 0.84 // порог срабатывания дискретного выхода
// === Переменные для передачи данных на ВУ ===
#define TX_SIZE 38 // размер буфера передачи
char TxBuf[TX_SIZE]; // буфер передачи
char *TxPtr; // указатель на очередной символ для передачи
/////////////////////////////////////////////////////////////////////////////////////////
//
// Функция получения аналоговых данных
// (однократно опрашивает используемые входы АЦП)
//
void GetAData(void) {
unsigned Ch; // номер обрабатываемого канала
// Вычисляем индекс очередной ячейки кольцевых буферов
NextAIdx++;
NextAIdx %= C_AI;
// Перебираем все каналы измерения
for (Ch=0; Ch
// Подключаем текущий канал к АЦП
ADC1->SQR3 = Ch;
// Запускаем преобразование
ADC1->CR2 |= ADC_CR2_SWSTART;
// Ожидаем завершения преобразования
while (!(ADC1->SR & ADC_SR_EOC));
// Считываем и сохраняем результат преобразования
AnData[Ch][NextAIdx] = ADC1->DR;
}
}
//////////////////////////////////////////////////
//
// Функция обработки аналоговых данных
// (вычисляет минимальные значения для каждого канала)
//
void ProcessAData(void) {
unsigned Ch;
// номер обрабатываемого канала
unsigned Idx;
// индекс очередного значения
// Перебираем все каналы измерения
for (Ch=0; Ch
AnRes[Ch] = 0;
// обнуляем значения измеренных по каналу Ch значений
// находим минимальные измеренные по каналу Ch значения
for (Idx=0; Idx
if (AnRes[Ch] < AnData[Ch][Idx]) AnRes[Ch]= AnData[Ch][Idx];
// Преобразуем вычисленное значение в вольты
AnRes[Ch] = AnRes[Ch]*K1 + K2;
}
}
//////////////////////////////////////////////////
//
// Функция получения и обработки аналоговых данных
//
void ProcessAInputs(void) {
GetAData(); // получить текущие значения
ProcessAData(); // обработать аналоговые данные
}
//////////////////////////////////////////////////
//
// Функция пользовательской инициализации
//
void Init(void)
{
// Разрешаем работу необходимого оборудования
RCC->APB2ENR |= (
RCC_APB2ENR_USART1EN; // разрешаем УАПП1
RCC_APB2ENR_ADC1EN // разрешаем таймер АЦП1
|RCC_APB2ENR_IOPAEN // разрешаем порт A
|RCC_APB2ENR_IOPBEN // разрешаем порт B
|RCC_APB2ENR_AFIOEN // разрешаем использование
); //альтернативных функций
// Настраиваем и запускаем системный таймер
SysTick->LOAD = 504000-1; // перезагружаемое значение
SysTick->CTRL = SysTick_CTRL_ENABLE; // разрешение счет
// Настраиваем выводы ОМК
// === Аналоговые входы
// Линия PА0
GPIOA->CRL &=
GPIOA->CRL |= (0x0<<4*1);
// Линия PА2
GPIOA->CRL &=
GPIOA->CRL |= (0x0<<4*3);
// === Дискретные входы с подтягивающими резисторами к VDD
// линия PA11
GPIOA->CRH &= (0xF<<4*(11-8));
GPIOA->CRH |= (0x2<<4*(11-8));
// линия PA12
GPIOA->CRH &= (0xF<<4*(12-8));
GPIOA->CRH |= (0x2<<4*(12-8));
// линия PA13
GPIOA->CRH &= (0xF<<4*(13-8));
GPIOA->CRH |= (0x2<<4*(13-8));
// === Дискретные выходы GPIO, 2МГц
// Линия PA4
GPIOA->CRL &=
GPIOA->CRL |= (0x4<<4*5);
// === выходы под ССИ
// Линия PB0
GPIOB -> CRL &=
GPIOB -> CRL |= (0x4 <<4*1);
// Линия PB2
GPIOB -> CRL &=
GPIOB -> CRL |= (0x4 <<4*3);
// Линия PB4
GPIOB -> CRL &=
GPIOB -> CRL |= (0x4 <<4*5);
// Линия PB6
GPIOB -> CRL &=
ADC1 -> CR2 =
ADC_CR2_EXTSEL // разрешить внешний запуск
| ADC_CR2_EXTTRIG; // выбрать источником запуска разряд SWSTART
ADC1 -> CR2 |= ADC_CR2_ADON; // включить АЦП
// Настраиваем УАПП
USART1 -> CR1 = USART_CR1_UE; // разрешаем USART
USART1 -> BRR = 36000000.0 / 19200 + 0.5; // скорость приёма/передачи 19200 бит/сек
// Настраиваем контроллер прерываний
NVIC->ISER[1] = (1<<5); // разрешаем прерывания от УАПП1
}
//////////////////////////////////////////////////
//
// Функция ввода и обработки дискретных сигналов
//
void ProcessDInputs()
{
// чтение состояния линии порта, к которому подключены дискретные входы
DInp =
Rst <<= 3; // сдвигаем в позицию первого выхода
GPIOA -> BSRR = Rst; // обнуляем выходы, для которых активен соответствующий вход
}
//////////////////////////////////////////////////
//
// Функция управления дискретными выходами
//
void ProcessDOutputs()
{
unsigned Ch;
unsigned Flags = 0;
// Перебираем аналоговые входы, для которых есть соответствующие дискретные выходы
for (Ch = 0; Ch < N_DO; Ch++) {
// Если условие активации выхода выполняется,
// то устанавливаем соответствующий бит в переменной Flags
if (fabs(0.5*RES_AI_MAX-AnRes[Ch])<0.42*C_DO*RES_AI_MAX)
Flags |= (1<< Ch);
}
DOut |= Flags;
Flags = (DOut<<3) ;
// Готовим маску для выделения значащих битов.
unsigned Mask = ((1<
// обнуляем все дискретные выходы
GPIOA->ODR &=
if (Max
Max = AnRes[Ch]; // обновляем текущий максимум
InpNmb = Ch; // и запоминаем номер этого входа
}
InpNmb++; // чтобы нумерация входов начиналась с 1, а не с 0
// Семисегментные коды символов "0"..."9"
static const uint8_t SegCodes[] = { 0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
unsigned Code = SegCodes[InpNmb];
Code <<= 1;
// Выключаем все сегменты (обнуляем выходы)
GPIOB -> ODR &=
// Выводим код символа
GPIOB -> ODR |= Code;
}
//////////////////////////////////////////////////
//
// Функция подготовки данных для передачи
//
void StartTX()
{
int Ch; // номер очередного входа
unsigned Pos = 0; // текущая позиция в буфере
// Помещаем в буфер значения аналоговых сигналов
for (Ch = 0; Ch < N_AI; Ch++)
Pos += sprintf(& TxBuf[Pos], "%5.3f, ", AnRes[Ch]);
// Помещаем в буфер значения дискретных входов
for (Ch = N_DI - 1; Ch >= 0; Ch--) {
if ((DInp & (1 << Ch)) != 0)
TxBuf[Pos] = '1';
else
TxBuf[Pos] = '0';
Pos++;
}
TxBuf[Pos++] = ',';
TxBuf[Pos++] = ' ';
// Помещаем в буфер значения дискретных выходов
for (Ch = N_DO - 1; Ch >= 0; Ch--) {
if ((DOut & (1 << Ch)) != 0)
TxBuf[Pos] = '1';
else
TxBuf[Pos] = '0';
Pos++;
}
// Добавляем переход на новую строку и терминатор
TxBuf[Pos++] = 0x0D;
TxBuf[Pos++] = 0x0A;
TxBuf[Pos] = 0;
// Инициируем передачу
// установка указателя очередного символа на начало буфера
TxPtr = TxBuf;
// Переводим драйвер в режим передачи
GPIOA -> BSRR = (1 << 8); // устанавливаем в единицу линию 8 порта A
// Разрешаем передатчик и прерывания от него
USART1 -> CR1 |= (
USART_CR1_TE // разрешаем работу передатчика
| USART_CR1_TXEIE // разрешаем прерывания по освобождению буферного регистра
| USART_CR1_TCIE // разрешаем прерывания по окончанию передачи
);
}
//////////////////////////////////////////////////
//
// Подпрограмма обработки прерываний от УАПП
//
void USART1_IRQHadler(void)
{
// Проверяем, есть ли данные для передачи
if (* TxPtr != 0) {
// Данные есть - записываем очередной символ в буферный регистр передатчика
// и перемещаем указатель на следующий символ
USART1 -> DR = * TxPtr++;
} else {
// Данных для передачи больше нет - проверяем причину прерывания
if ((USART1 -> SR & USART_SR_TC) != 0) {
// В регистре состояния передатчика USART_SR установлен бит TC –
// значит передача полностью завершена
GPIOA -> BRR = (1 << 8); // переводим сигнал DE (линия PA8) в неактивное состояние
// Запрещаем все прерывания от передатчика и сам передатчик
USART1 -> CR1 &=