Файл: Курсовой проект по курсу Проектирование цифровых вычислительных (у правляющих) устройств.docx

ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 17.03.2024

Просмотров: 75

Скачиваний: 1

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

СОДЕРЖАНИЕ

Содержание

Введение

Формулировка задачи

Разрешение работы встроенного оборудованияРазрешим работу встроенных блоков с помощью схемы управления сбросом и синхронизацией (Reset and Clock Control, RCC). Эта схема имеет управляющие регистры RCC_APB1ENR и RCC_APB2ENR, каждый бит которых разрешает или запрещает подачу тактовых импульсов на соответствующий блок. Листинг разрешающей работу УАПП1, АЦП1, порта А и B:RCC->APB2ENR |= ( RCC_APB2ENR_USART1EN; // разрешаем УАПП1RCC_APB2ENR_ADC1EN // разрешаем таймер АЦП1 |RCC_APB2ENR_IOPAEN // разрешаем порт A |RCC_APB2ENR_IOPBEN // разрешаем порт B |RCC_APB2ENR_AFIOEN // разрешаем использование); // альтернативных функцийНастройка системного таймераДля определения периодичности опроса входов, то есть отсчёта временных интервалов, используем системный таймер, имеющийся во всех ОМК с ядром Cortex-M3. Таймер уменьшает свой значение на единицу с каждым тактовым импульсом, при достижении нуля устанавливает флаг перезагрузки COUNTFLAG, автоматически загружает в счётный регистр заранее заданное значение и продолжает работу. Изменяя перезагружаемое значение, можно задавать нужный период установки COUNTFLAG.Для правильной настройки системного таймера прежде всего вычисляем перезагружаемое значение. Таймер должен устанавливать COUNTFLAG каждые миллисекунд.Длительность такта определяется по формуле: =1/ ,где – тактовая частота процессорного ядра (в нашем случае 36 МГц). Теперь для установки COUNTFLAG через миллисекунд, таймер должен начать счёт с величины: 504000.Полученное значение, уменьшенное на единицу, записываем в регистр таймера STK_LOAD, после чего разрешаем работу таймера путём установки в единицу бита ENABLE (нулевой бит) в регистре STK_CTRL. Остальные биты регистра STK_CTRL обнуляем, тем самым запрещая формирование запросов прерываний и выбирая тактовую частоту таймера 36 МГц:SysTick->LOAD = 504000-1; // перезагружаемое значениеSysTick->CTRL = SysTick_CTRL_ENABLE; // разрешение счётНастройка выводов ОМКБольшинство линий ввода-вывода в используемом ОМК работают в нескольких режимах. Перед использованием производим настройку этих линий, то есть задаем направление передачи данных и выполняемую функцию. ОМК содержит несколько 16-разрядных портов ввода-вывода, обозначенных буквами: порт «А» (РА), порт «В» (РВ). Отдельные линии каждого порта настраиваются с помощью 32-разрядных регистров GPIOx_CRL и GPIOx_CRH, где «х» - имя порта. Регистр GPIOА_CRL настраиваем 8 младших линий порта , регистр GPIOА_CRH – 8 старших линий порта. Для каждой линии порта в этих регистрах есть 4 управляющих бита: CNF[1:0] и MODE[1:0] (рис. 3.2).Параметр MODE задаёт направление передачи данных, принимает следующие значения:00 – ввод данных;01 – вывод с максимальной частотой 10 МГц;10 – вывод с максимальной частотой 2 МГц; 11 – вывод с максимальной частотой 50 МГц.При настройке линии выход выбираем режим с минимально необходимой частотой, чтобы снизить уровни излучаемых помех. Рисунок 3.2 Регистры настройки GPIOПараметр CNF уточняет режим работы линии GPIO и имеет разный смысл для входов и для выходов. Если параметром MODE линия настроена на ввод, то CNF позволяет указать:00 – аналоговый вход;01 – «плавающий» цифровой вход (без подтягивающих резисторов);10 – цифровой вход с подтягивающим резистором к Vdd или к GND;11 – недопустимая комбинация.Если линия настроена как выход, то CNF имеет следующий смысл:00 – обычный выход общего назначения;01 – выход общего назначения с открытым стоком;10 – выход, выполняющий альтернативную функцию;11 – выход с открытым стоком, выполняющий альтернативную функцию.Линии GPIO обычно удобно настраивать индивидуально, поэтому в регистрах GPIOx_CRL и GPIOx_CRH приходится изменять только часть битов, а не всё содержимое сразу. Для этого все биты MODE[1:0] и CNF[1:0], относящиеся к выбранной линии, сначала обнуляют операцией «И», а затем устанавливают необходимые биты в единицу операцией «ИЛИ». Для установки используем операции сдвига. Параметры настройки линии GPIO с номером К начинаются в регистре GPIOx_CRL с бита NL = 4×K, а в регистре GPIOx_CRH – с бита NH = 4×(K–8).Настроим аналоговые входы РА0…PA3:// Линия PА0GPIOА->CRL &= (0xF<<4*0);GPIOА->CRL |= (0x0<<4*0); // Линия PA1GPIOА->CRL &= (0xF<<4*1);GPIOА->CRL |= (0x0<<4*1); // Линия PА2GPIOА->CRL &= (0xF<<4*2);GPIOА->CRL |= (0x0<<4*2); // Линия PA3GPIOА->CRL &= (0xF<<4*3);GPIOА->CRL |= (0x0<<4*3); Настроим выходы под ССИ РB0…РB6:// Линия PB0GPIOB->CRL &= (0xF<<4*0);GPIOB->CRL |= (0x4<<4*0);// Линия PB1GPIOB->CRL &= (0xF<<4*1);GPIOB->CRL |= (0x4<<4*1);// Линия PB2GPIOB->CRL &= (0xF<<4*2);GPIOB->CRL |= (0x4<<4*2);// Линия PB3GPIOB->CRL &= (0xF<<4*3);GPIOB->CRL |= (0x4<<4*3);// Линия PB4GPIOB->CRL &= (0xF<<4*4);GPIOB->CRL |= (0x4<<4*4);// Линия PB5GPIOB->CRL &= (0xF<<4*5);GPIOB->CRL |= (0x4<<4*5);// Линия PB6GPIOB->CRL &= (0xF<<4*6);GPIOB->CRL |= (0x4<<4*6);Настроим дискретные выходы РA4, A5:// Линия PA4GPIOA->CRL &= (0xF<<4*4);GPIOA->CRL |= (0x4<<4*4);// Линия PA5GPIOA->CRL &= (0xF<<4*5);GPIOA->CRL |= (0x4<<4*5);Теперь необходимо настроить линию РА9 на выполнение альтернативной функции (USART1_TX) в режиме выхода. Обнулим биты конфигурации РА9 и зададим им нужные значения:GPIOA->CRH &= (0xF<<4*(9-8)); GPIOA->CRH |= (0xA<<4*(9-8));Настраиваем линию РА10 на выполнение альтернативной функции (USART1_RX) в режиме выхода. GPIOA->CRH &= (0xF<<4*(10-8));GPIOA->CRH |= (0x4<<4*(10-8));Настроим линии РA11…РA13 как дискретные входы. // линия PA11GPIOA->CRH &= (0xF<<4*(11-8));GPIOA->CRH |= (0x2<<4*(11-8));// линия PA12GPIOA->CRH &= (0xF<<4*(12-8));GPIOA->CRH |= (0x2<<4*(12-8));// линия PA13GPIOA->CRH &= (0xF<<4*(13-8));GPIOA->CRH |= (0x2<<4*(13-8));Настройка АЦПЗададим рабочую частоту АЦП. Для этого надо разделить частоту «APB2 Clock», равную 36 МГц, на 4. Полученный результат не должен превышать 14 МГц [2]. Коэффициент деления задаем в регистре RCC_CFGR битами ADCPRE[1:0]. Результирующая частота АЦП равняется 9 МГц (меньше 14 МГц):RCC->CFGR |=

void ProcessAData(void){unsigned Ch; // номер обрабатываемого канала unsigned Idx; // индекс очередного значения// Перебираем все каналы измерения for (Ch=0; ChAnRes[Ch] = 0;// обнуляем значения измеренных по каналу Ch//значений// находим минимальные измеренные по каналу Ch значения for (Idx=0; Idxif (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 &=

#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];// Индекс очередного элемента для записи в массив AnDataunsigned 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; ChAnRes[Ch] = 0; // обнуляем значения измеренных по каналу Ch значений// находим минимальные измеренные по каналу Ch значенияfor (Idx=0; Idxif (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; // разрешаем УАПП1RCC_APB2ENR_ADC1EN // разрешаем таймер АЦП1 |RCC_APB2ENR_IOPAEN // разрешаем порт A |RCC_APB2ENR_IOPBEN // разрешаем порт B |RCC_APB2ENR_AFIOEN // разрешаем использование); //альтернативных функций// Настраиваем и запускаем системный таймерSysTick->LOAD = 504000-1; // перезагружаемое значениеSysTick->CTRL = SysTick_CTRL_ENABLE; // разрешение счет// Настраиваем выводы ОМК// === Аналоговые входы// Линия PА0GPIOA->CRL &= (0xF<<4*0);GPIOA->CRL |= (0x0<<4*0); // Линия PA1GPIOA->CRL &= (0xF<<4*1);GPIOA->CRL |= (0x0<<4*1); // Линия PА2GPIOA->CRL &= (0xF<<4*2);GPIOA->CRL |= (0x0<<4*2); // Линия PA3GPIOA->CRL &= (0xF<<4*3);GPIOA->CRL |= (0x0<<4*3); // === Дискретные входы с подтягивающими резисторами к VDD// линия PA11GPIOA->CRH &= (0xF<<4*(11-8));GPIOA->CRH |= (0x2<<4*(11-8));// линия PA12GPIOA->CRH &= (0xF<<4*(12-8));GPIOA->CRH |= (0x2<<4*(12-8));// линия PA13GPIOA->CRH &= (0xF<<4*(13-8));GPIOA->CRH |= (0x2<<4*(13-8));// === Дискретные выходы GPIO, 2МГц// Линия PA4GPIOA->CRL &= (0xF<<4*4);GPIOA->CRL |= (0x4<<4*4);// Линия PA5GPIOA->CRL &= (0xF<<4*5);GPIOA->CRL |= (0x4<<4*5);// === выходы под ССИ// Линия PB0GPIOB -> CRL &= (0xF <<4*0);GPIOB -> CRL |= (0x4 <<4*0);// Линия PB1GPIOB -> CRL &= (0xF <<4*1);GPIOB -> CRL |= (0x4 <<4*1);// Линия PB2GPIOB -> CRL &= (0xF <<4*2);GPIOB -> CRL |= (0x4 <<4*2);// Линия PB3GPIOB -> CRL &= (0xF <<4*3);GPIOB -> CRL |= (0x4 <<4*3);// Линия PB4GPIOB -> CRL &= (0xFu <<4*4);GPIOB -> CRL |= (0x4 <<4*4);// Линия PB5GPIOB -> CRL &= (0xFu <<4*5);GPIOB -> CRL |= (0x4 <<4*5);// Линия PB6GPIOB -> 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; // выбрать источником запуска разряд SWSTARTADC1 -> CR2 |= ADC_CR2_ADON; // включить АЦП// Настраиваем УАППUSART1 -> CR1 = USART_CR1_UE; // разрешаем USARTUSART1 -> 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-PA6unsigned 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++) {// Если условие активации выхода выполняется,// то устанавливаем соответствующий бит в переменной Flagsif (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; Chif (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';elseTxBuf[Pos] = '0';Pos++;}TxBuf[Pos++] = ',';TxBuf[Pos++] = ' ';// Помещаем в буфер значения дискретных выходовfor (Ch = N_DO - 1; Ch >= 0; Ch--) {if ((DOut & (1 << Ch)) != 0)TxBuf[Pos] = '1';elseTxBuf[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 &=



Из ряда Е96 выберем ближайшие значения:

R2 = 5.11 кОм; R3 = 95.3 кОм.

Проверим параметры разработанного НУ. Подставляя выбранные номиналы резисторов в (2.3), вычислим фактические значения коэффициентов KВХ и KСМ:

KВХ_Ф ≈ 0.85700426 ; KСМ_Ф ≈ 0.04549463 .
Из выражений (2.1) получим фактические минимальное и максимальное значения сигнала на выходе НУ:

UВЫХ_MIN_Ф = 0 0.85700426 +3.3 0.04549463 ≈ 0.150132 В;

UВЫХ_MAX_Ф = 3.5 0.85700426 + 3.3 0.04549463 ≈ 3,1496 В.
Полученные значения входят в допустимый диапазон 0.1…3.2 В и незначительно отличаются от исходных UВЫХ_MIN и UВЫХ_MAX.

Для нормальной работы ОУ необходимо, чтобы напряжение на его прямом входе не превышало напряжение питания 3.3 В. Кроме того, из выражений (2.4) следует, что сумма коэффициентов KД_ВХ и KД_СМ должна быть меньше единицы. Поэтому при невыполнении хотя бы одного из условий UMAX×KД_ВХ+UСМ×KД_СМ 3.3 В, KД_ВХ + KД_СМ < 1 нужно увеличить коэффициент усиления KУ путём уменьшения R4 или увеличения R5, а затем повторить расчёт.

Проверка выполнения условий:
1) 3.5 0.6433 + 3.3 0.03415 3.3В

2.36424 3.3В, Условие выполняется;

2) 0.6433 + 0.03415 < 1

0.6774 < 1, Условие выполняется.



            1. Подсистема ввода дискретных сигналов

Многоразрядные порты ввода-вывода входят в состав выбранного ОМК, поэтому для ввода дискретных сигналов требуется только согласовать их электрические параметры с параметрами входов контроллера.

Согласно [2] логические входы ОМК при напряжении питания 3.3 В имеют следующие характеристики:

– напряжение логического «0», VIL.............................................0 В ≤VIL≤ 1.15 В

– напряжение логической «1», VIH
.......................................... 2.15 В ≤VIH≤ 3.3 В

– входной ток утечки, ILKG...........................................................................± 1 мкА

– входное сопротивление

при включённом подтягивающем резисторе, кОм...................................... 40

при отключённом подтягивающем резисторе, МОм, не менее ................ 3.3



Рисунок 2.4 Подключение дискретного входа

Для подключения потенциального сигнала «0…24 В» необходимо уменьшить его напряжение до уровня логической «1» VIHОМК. Примем средний уровень VIHравным (2.15 + 3.3) / 2, то есть примерно 2.7 В. Тогда напряжение входного сигнала следует уменьшить в 24 / 2.7 = 8.9 раза. Это можно сделать с помощью делителя напряжения R1, R2, показанного на рис. 2.4. Для снижения тепловыделения и предотвращения перегрузки источника входного сигнала номиналы R1 и R2 следует выбирать достаточно большими, однако при этом нужно помнить, что увеличение R2 может привести к тому, что падение напряжения на нём, вызванное током утечки входа, превысит уровень логического «0».

Рассчитаем номиналы R1, R2 для выбранного коэффициента деления KД = 8.9. При этом будем считать, что встроенный подтягивающий резистор на входе ОМК отключён, а выходное сопротивление источника сигнала достаточно мало и им можно пренебречь.

KД = (R1 + R2 || RВХ) / (R2 || RВХ),

где RВХ – входное сопротивление вывода ОМК (3.3 МОм);

R2 || RВХ – общее сопротивление параллельно включённых R2 и RВХ.

Тогда R2 || RВХ = R1 / (KД – 1) или R2 × RВХ / (R2 + RВХ) = R1 / (KД – 1).

Отсюда получим:

R1 = R2 × RВХ × (KД – 1) / (R2 + RВХ). (2.5)

Выберем R2 равным 51 кОм. Падение напряжения на R2 при токе утечки входа 1 мкА составит 0.05 В, что соответствует уровню логического «0» VILс большим запасом. Подставляя значения R2, RВХ и KД в выражение (2.5) получим:

R1= 51000×3.3× ×(8.9 - 1) / ( 51000 + 3.3× ) ≈ 396768.12892 Ом ≈ 397 кОм

Выберем ближайшее значение из ряда Е24, равное 390 кОм.


            1. Подсистема вывода дискретных сигналов

Выбранный ОМК имеет в своём составе порты вывода с необходимым количеством линий, поэтому разработать требуется только узлы согласования. Для выходов с открытым коллектором узел согласования представлен на рис. 2.5.





Рисунок 2.5 Подключение дискретного выхода

При его разработке прежде всего выбираются транзисторы с допустимым током коллектора IK не менее 300мАи напряжением «коллектор-эмиттер» VКЭ не менее 11В. Из этой группы отбираются такие, минимальный коэффициент передачи тока hFEкоторых позволит обеспечить требуемый выходной ток IDOпри токе базы порядка сотен микроампер – единиц миллиампер.

Исходя из этих условий выбираем биполярный n-p-n транзистор КТ645Б [5] с типом корпуса КТ-26, у которого ток коллектора Iк = 300 мА, напряжение коллектор-эмиттер Uкэ = 40 В, статический коэффициент передачи тока hFE = 80, напряжение база-эмиттер VБЭ = 1.2 В.

Рассчитывается необходимый ток базы. Его минимальную величину IБ_МИН можно получить из выражения:

IБ_МИН = IDO / hFE

IБ_МИН = (300 мА / 80) = 3,75 мА

Для стабильной работы транзистора в ключевом режиме рекомендуется увеличить вычисленное значение в 1.5…2 раза.

IБ= IБ_МИН×2 = 3,75 × 2 = 7.5 мА = 0,0075 A

Величина тока базы не превышает допустимый ток выхода ОМК 25 мА.

Требуемая величина IБ обеспечивается резистором R1, сопротивление которого вычисляется по формуле:

R1 = (VOH – VБЭ) / IБ

где VБЭ – падение напряжения на переходе «база-эмиттер» выбранного транзистора,

VOH – минимальное напряжение логической «1» на выходе ОМК. Параметр VOH зависит от тока: при токе 7.5 мА он равен 2.9 В.

R1=(2.9 - 1.2) / 7.5 мА ≈ 226.667 Ом

Выберем ближайшее значение из ряда Е24, равное 220 Ом.


            1. Подсистема индикации

Семисегментный индикатор представляет собой устройство, содержащее восемь светодиодов, расположенных специальным образом и включённых по схеме с общим анодом (рис. 2.6).



Рисунок 2.6 Семисегментные светодиодные индикаторы
Согласно [2] логические выходы ОМК при напряжении питания 3.3 В имеют следующие характеристики:

– напряжение логического «0» (при токе до 8 мА), VOL1..........0 В ≤VOL≤ 0.4 В

– напряжение логической «1» (при токе до 8 мА), VOH1........ 2.9 В ≤VIH≤ 3.3 В

– напряжение логического «0» (при токе до 20 мА), VOL2........0 В ≤
VOL≤ 1.3 В

– напряжение логической «1» (при токе до 20 мА), VOH2...... 2.0 В ≤VIH≤ 3.3 В

Из приведённых характеристик видно, что при токе 20 мА напряжение логического «0» VOL2может достигать 1.3 В, разность VDD VOL2 будет равна 2 В, что недостаточно для некоторых типов СCИ. Поэтому выберем СCИ с номинальным прямым током IСCИ = 10 мА – при таком токе VOL1 ≤ 0.4 В, а VDD VOL1≥ 2.9 В. Выберем ССИ SA08-21EWA [7]. Данный ССИ имеет красный цвет свечения, максимальный прямой ток равен 30 мА, максимальное прямое напряжение равно 2.5 В, общий анод, а рабочая температура варьируется от -40 до 85.

На рис. 2.7 у выбранного ССИ при токе 10 мА прямое падение напряжения VССИ составляет 1.9 В. Тогда напряжение на токоограничительном резисторе VRможно вычислить как:

VR= VDDVOL1VССИ

VR = 3.3 - 0.4 - 1.9 = 1 В

Сопротивление токоограничительного резистора вычисляется по формуле: R = VR / IССИ

R = 1 / 10 мА = 100 Ом

Из ряда Е24 выбираем ближайшее стандартное значение 100 Ом. Необходимая рассеиваемая мощность резистора вычисляется как:

P = R × IССИ2

P = 100 × 102 мА = 10 Вт



Рисунок 2.7 Прямое падение напряжения


            1. Подсистема связи с ВУ

ОМК имеет в своём составе универсальный асинхронный приемопередатчик (УАПП), способный передавать и принимать информацию в формате, соответствующем заданию. Однако назначение и уровни сигналов на выводах УАПП не соответствуют спецификации физических интерфейсов RS-232, RS-485, RS-422, при помощи которых ССОИ должна взаимодействовать с верхним уровнем АСУ ТП.

Для решения описанной проблемы выпускается большое количество специальных микросхем-драйверов, обеспечивающих согласование интерфейсных сигналов. Рассмотрим особенности их применения для перечисленных интерфейсов.

Интерфейс RS-485 похож на RS-422, но использует единственную пару проводников для подключения до 32 устройств по схеме общей шины. Из-за этой особенности полнодуплексный обмен данными невозможен: в каждый момент времени на шине может быть активен лишь один передатчик. В связи с этим драйверу необходим дополнительный входной управляющий сигнал DE, разрешающий работу передатчика. Обычно есть и вход разрешения работы приёмника
RE, но его использование не является обязательным. Схема подключения драйвера MAX3483 [4] к ОМК приведена на рис. 2.8.



Рисунок 2.8 Подключение драйвера RS-485
У драйвера имеются входы разрешения передатчика DE (прямой) и приёмника /RE (инверсный), управляемые одним сигналом USART_DE, поступающим с ОМК. Если USART_DE находится в состоянии логической «1», то работа передатчика разрешена, а приёмника – запрещена. Когда на линии USART_DE присутствует логический «0», передатчик блокируется, а работа приёмника разрешается. УАПП некоторых ОМК обеспечивают аппаратное формирование сигнала USART_DE – в этом случае программа должна лишь правильно настроить УАПП. В рекомендуемых ОМК такой возможности нет, поэтому USART_DE необходимо формировать программно.


            1. Вспомогательные элементы

Для нормальной работы ОМК к нему необходимо подключить цепи питания и некоторые вспомогательные цепи. Вариант схемы включения контроллера STM32F101T8 [2] показан на рис. 2.9.



Рисунок 2.9 Схема включения контроллера STM32F101T8
Все входы питания GND подключаются к общему проводу, VDD– к цепи питания +3.3 В. Блокировочные конденсаторы C1…C4 ёмкостью 0.1 мкФ служат для подавления импульсных помех по цепям питания, возникающих при работе ОМК. Они подключаются к каждому выводу VDDи устанавливаются на печатной плате в непосредственной близости от этих выводов. Такие же конденсаторы рекомендуется устанавливать в цепи питания каждой микросхемы.

Для стабилизации частоты встроенного в ОМК тактового генератора используется кварцевый резонатор Z1. Согласно [1] резонансная частота Z1 может выбираться в пределах от 4 до 16 МГц. При выборе частоты Z1 следует учитывать, что умножитель в составе ОМК может увеличить её в целое число раз от 1 до 16, а максимальная частота на выходе умножителя не должна превышать 36 МГц.

Сброс ОМК, то есть приведение всех его узлов в исходное состояние, может выполняться с помощью внешнего сигнала, подаваемого на вход /RST.

Если внешний сброс не требуется, вход можно оставить свободным: за счёт встроенного подтягивающего резистора на нём будет поддерживаться неактивный уровень. Сброс при включении питания выполняется встроенной в ОМК схемой, она же перезапускает контроллер при сбоях питания.