Микропроцесорски системи/Фебруар 2022

Извор: SI Wiki
Пређи на навигацију Пређи на претрагу

Фебруарски рок 2022. године нема поставку доступну са странице предмета. Није много познато о овом року, осим задатка наведеног испод.

Задатак

Поставка

Референтна шема у задатку.
  1. У систему постоји један микроконтролер који контролише вентилатор у складу са тренутном вредношћу температуре. У симулатору Proteus направити пројекат и инстанцирати микроконтролер STM32F103R6 (CM3_STM32). Направити пројекат у алату CubeMX који ће служити за конфигурисање микроконтролера.
  2. У симулатору Proteus додати следеће компоненте строго према датој референтној шеми:
    • електромотор MOTOR (MOTORS) који представља вентилатор и чија ће брзина рада бити контролисана PWM сигналом канала 1 тајмера 3 микроконтролера,
    • тастатуру KEYPAD-PHONE (ACTIVE) повезану у складу са референтном шемом,
    • температурни сензор LM35 (NATDAC) чији је излазни пин повезан на канал 7 аналогно-дигиталног конвертора микроконтролера (аналогна вредност напона на излазном пину сензора је линеарно пропорционална вредности температуре),
    • кишомер RAINGAUGE (ACTIVE) повезан у складу са референтном шемом на моностабилни мултивибратор 74HC221 (74HC); кишомер ради по принципу кофе са превртањем (кофа се пуни кишницом све док се не препуни, затим долази до њеног превртања и потпуног пражњења уз генерисање електричног импулса); моностабилни мултивибратор обезбеђује униформно трајање генерисаног електричног импулса; подесити на кишомеру ниво превртања кофе (Trigger Level) на вредност 0.01 милиметара услед чега ће кофа слати електрични импулс на сваких 1200 милисекунди при константном интензитету падавине од 30.0 милиметара по сат (за већи интензитет падавине импулс ће стизати брже и обрнуто); подесити на моностабилном мултивибратору временску константу (Monostable Time Constant) на једну милисекунду (1mS) услед чега ће сигнал на његовом излазу увек трајати тачно једну милисекунду,
    • виртуелни терминал повезан на периферију USART1 микроконтролера,
    • LCD дисплеј LM041L (DISPLAY) са четири линије, којима редом одговарају почетне адресе 0x80, 0xC0, 0x90 и 0xD0 у DDRAM меморији контролера HD44780, повезан на пинове PC0-PC6 микроконтролера и
    • двобојну LED-BIRG (ACTIVE), повезану у складу са референтном шемом, којој треба подесити јачину струје пуног провода (Full drive current) на један микро ампер (1uA); ова диода дозвољава проток струје у оба смера при чему светли различитом бојом у зависности од смера протока струје.
  3. Обезбедити приказ поруке Temp: <t> на виртуелном терминалу, где је <t> тренутна вредност температуре изражено у степенима Целзијуса у опсегу [0, 60] очитана са температурног сензора. Освежавање описане поруке вршити периодично на сваких 200 милисекунди уколико је дошло до промене вредности. Приликом освежавања поруке променити тренутне вредности у постојећој поруци уместо додавања нове поруке на виртуелном терминалу.
  4. Обезбедити приказ поруке Kisa: <k> у првом реду LCD дисплеја, где <k> представља тренутну вредност интензитета падавине изражено у милиметрима по сату очитану са кишомера. Освежавање поруке вршити периодично на сваких 200 милисекунди уколико је дошло до промене вредности.
  5. Обезбедити могућност уноса вредности temp_granica, која представља границу температуре за укључивање вентилатора, преко тастатуре и приказ поруке Thld:(??)=><thld> у четвртом реду LCD дисплеја у складу са референтним снимком, где <thld> представља тренутно важећу вредност границе (иницијална вредност границе је 30) а на месту знакова питања се приказују притиснути тастери до тренутка када се притисне други по реду тастер услед чега се поставља нова вредност границе и враћају знакови питања. Освежавање поруке вршити периодично на сваких 200 милисекунди уколико је дошло до промене вредности.
  6. Обезбедити понашање двобојне LED описано у наставку. Двобојна LED константно светли зеленом бојом када је тренутна температура нижа од temp_granica, док уколико је тренутна температура једнака или већа од temp_granica двобојна LED трепери црвеном бојом са периодом од једне секунде (пола секунде светли, пола секунде не светли).
  7. Обезбедити контролу вентилатора на начин описан у наставку. Зависно од тренутне вредности температуре вентилатор се врти одговарајућом брзином. Уколико тренутна вредност температуре припада опсегу:
    • [0, temp_granica) вентилатор се не врти и
    • [temp_granica, 60] вентилатор се врти брзином једнаком 50% максималне брзине
    Ажурирање брзине вентилатора вршити периодично на 200 милисекунди.

Решење

Цело решење може се преузети одавде. Решење је засновано на решењу домаћег задатка из 2023. године, па ће испод бити наведени фајлови измењени у односу на то решење.

Сви релевантни измењени фајлови са кодом налазе се у cubemx/code/Core/Src и cubemx/code/Core/Inc директоријумима.

НА САМОМ РОКУ НИСУ БИЛЕ ДАТЕ ИМПЛЕМЕНТАЦИЈЕ МЕТОДА У ДВА ФАЈЛА ИСПОД, МОРАЈУ ДА СЕ ДОПУНЕ НА ЛИЦУ МЕСТА.

driver_motor.c

#include "tim.h"

#define ARR 9
#define STEP 2
#define INCREMENT ((ARR+1)/STEP)

static uint32_t volatile compReg = 0;

void MOTOR_SpeedIncrease()
{
	if(compReg < (ARR+1)){
		compReg += INCREMENT;
	}

	htim3.Instance->CCR1 = compReg;
}

void MOTOR_SpeedDecrease()
{
	if(compReg > 0){
		compReg -= INCREMENT;
	}

	htim3.Instance->CCR1 = compReg;
}

void MOTOR_Init()
{
	HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
}

driver_uart.c

#include "driver_uart.h"

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"

#include <string.h>

#include "usart.h"

// TRANSMIT
// -----------------------------------------------------------------------------

static TaskHandle_t UART_TransmitTaskHandle;
static QueueHandle_t UART_TransmitQueueHandle;
static SemaphoreHandle_t UART_TransmitMutexHandle;

static void UART_TransmitTask(void *parameters)
{
	uint8_t buf;
	while(1){
		xQueueReceive(UART_TransmitQueueHandle, &buf,portMAX_DELAY);
		HAL_UART_Transmit_IT(&huart1, &buf, sizeof(uint8_t));
		ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
	}
}

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart->Instance == huart1.Instance){
		BaseType_t woken = pdFALSE;
		vTaskNotifyGiveFromISR(UART_TransmitTaskHandle, &woken);
		portYIELD_FROM_ISR(woken);
	}
}

// RECEIVE
// -----------------------------------------------------------------------------

static TaskHandle_t UART_ReceiveTaskHandle;
static QueueHandle_t UART_ReceiveQueueHandle;
static SemaphoreHandle_t UART_ReceiveMutexHandle;

static void UART_ReceiveTask(void *parameters)
{
	uint8_t buf;
	while(1){
		HAL_UART_Receive_IT(&huart1, &buf, sizeof(uint8_t));
		ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
		xQueueSendToBack(UART_ReceiveQueueHandle,&buf,portMAX_DELAY);
	}
}

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart->Instance == huart1.Instance){
		BaseType_t woken = pdFALSE;
		vTaskNotifyGiveFromISR(UART_ReceiveTaskHandle, &woken);
		portYIELD_FROM_ISR(woken);
	}
}

// GENERAL
// -----------------------------------------------------------------------------

void UART_Init()
{
	UART_TransmitMutexHandle = xSemaphoreCreateMutex();
	UART_TransmitQueueHandle = xQueueCreate(64, sizeof(uint8_t));
	xTaskCreate(UART_TransmitTask, "transmitTask", 64, NULL,4, &UART_TransmitTaskHandle);

	UART_ReceiveMutexHandle = xSemaphoreCreateMutex();
	UART_ReceiveQueueHandle = xQueueCreate(64, sizeof(uint8_t));
	xTaskCreate(UART_ReceiveTask, "receiveTask", 64, NULL, 10, &UART_ReceiveTaskHandle);
}

// TRANSMIT UTIL
// -----------------------------------------------------------------------------

void UART_AsyncTransmitCharacter(char character)
{
	xSemaphoreTake(UART_TransmitMutexHandle, portMAX_DELAY);
	xQueueSendToBack(UART_TransmitQueueHandle, &character, portMAX_DELAY);
	xSemaphoreGive(UART_TransmitMutexHandle);
}


// RECEIVE UTIL
// -----------------------------------------------------------------------------

char UART_BlockReceiveCharacter()
{
	char c;
	xSemaphoreTake(UART_ReceiveMutexHandle, portMAX_DELAY);
	xQueueReceive(UART_ReceiveQueueHandle, &c, portMAX_DELAY);
	xSemaphoreGive(UART_ReceiveMutexHandle);
	return c;
}

CubeMX

У алату CubeMX направљене су следеће измене на пројекту из домаћег задатка:

  • Омогућени су прекиди од EXTI.
  • Пинови PB3..0 и PB9..8 су подешени као GPIO_Output, пинови PB4..6 као GPIO_Input, док су пинови PB7 и PB10 подешени као EXTI.

Након генерисања, линија 310 у Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM3/port.c је закоментарисана.

Makefile

У односу на пројекат из домаћег задатка, у Makefile је додат keypad.c фајл.

keypad.h

#ifndef CORE_INC_KEYPAD_H_
#define CORE_INC_KEYPAD_H_

extern char keys[2];
extern unsigned temp_granica;
extern int keysChanged;

void KEY_Init();

#endif

keypad.c

#include "keypad.h"

#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"

#include "gpio.h"

char keys[2];
unsigned temp_granica = 30;
int keysChanged = 1;

static int keyCount = 0;
static int keyReleased = 1;

TaskHandle_t KEY_TaskHandle;
TimerHandle_t KEY_TimerHandle;

const char KEY_MATRIX[4][3] = {
	{ '1', '2', '3' },
	{ '4', '5', '6' },
	{ '7', '8', '9' },
	{ '*', '0', '#' }
};

extern void homeworkOverflow();

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
	if (GPIO_Pin == GPIO_PIN_7) {
		BaseType_t wokenTask = pdFALSE;
		vTaskNotifyGiveFromISR(KEY_TaskHandle, &wokenTask);
		portYIELD_FROM_ISR(wokenTask);
	} else if (GPIO_Pin == GPIO_PIN_10) {
		homeworkOverflow();
	}
}

static void KEY_Task(void *parameters) {
	UNUSED(parameters);
	keys[0] = '?';
	keys[1] = '?';
	while (1) {
		for (int row = 0; row < 4; ++row) {
			HAL_GPIO_WritePin(GPIOB, 1u << row, GPIO_PIN_SET);
		}
		vTaskDelay(pdMS_TO_TICKS(10));
		if (!keyReleased) {
			continue;
		}
		for (int row = 0; row < 4; ++row) {
			HAL_GPIO_WritePin(GPIOB, 1u << row, GPIO_PIN_RESET);
		}
		for (int row = 0; row < 4; ++row) {
			HAL_GPIO_WritePin(GPIOB, 1u << row, GPIO_PIN_SET);
			for (int column = 0; column < 3; ++column) {
				if (HAL_GPIO_ReadPin(GPIOB, 1u << (4 + column))
						== GPIO_PIN_SET) {
					keys[keyCount] = KEY_MATRIX[row][column];
					if (keyCount == 1) {
						temp_granica = (keys[0] - '0') * 10 + keys[1] - '0';
						keys[0] = '?';
						keys[1] = '?';
					}
					keyCount = 1 - keyCount;
					keysChanged = 1;
					keyReleased = 0;
					xTimerStart(KEY_TimerHandle, portMAX_DELAY);
				}
			}
			HAL_GPIO_WritePin(GPIOB, 1u << row, GPIO_PIN_RESET);
		}
	}
}

void KEY_Timer(TimerHandle_t xTimer) {
	UNUSED(xTimer);
	if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) == GPIO_PIN_SET) {
		xTimerStart(KEY_TimerHandle, portMAX_DELAY);
	} else {
		keyReleased = 1;
	}
}

void KEY_Init() {
	xTaskCreate(KEY_Task, "KEY_Task", 128, NULL, 5, &KEY_TaskHandle);
	KEY_TimerHandle = xTimerCreate("KEY_Timer", pdMS_TO_TICKS(10), pdFALSE,
	NULL, KEY_Timer);
}

homework.h

#ifndef CORE_INC_HOMEWORK_H_
#define CORE_INC_HOMEWORK_H_

typedef enum
{
	TURNED_OFF, SLOW, FAST
} FanState;

extern void homeworkInit();
extern void homeworkOverflow();

#endif

homework.c

#include "homework.h"

#include "FreeRTOS.h"
#include "task.h"

#include <stdlib.h>
#include <string.h>

#include "FreeRTOS.h"
#include "timers.h"

#include "driver_lcd.h"
#include "driver_uart.h"
#include "driver_motor.h"
#include "driver_temp.h"
#include "keypad.h"

#include "gpio.h"

FanState fanState = TURNED_OFF;
TimerHandle_t homeworkTimer;
TimerHandle_t ledTimer;

unsigned passedMs = 0;
unsigned rainfall = 0;

static uint32_t tempValue;
static char tempText[4];
static void homeworkTask(void *parameters) {
	UNUSED(parameters);
	char messageTemp[9] = "Temp:   ";
	char messageKisa[7] = "Kisa: ";
#pragma GCC diagnostic ignored "-Wtrigraphs"
	char messageThld[14] = "Thld:(??)=>30";
	// Ekvivalentna adresa: 0x80
	LCD_CommandEnqueue(LCD_INSTRUCTION,
	LCD_SET_DD_RAM_ADDRESS_INSTRUCTION | 0x00);
	for (uint32_t i = 0; i < 8; i++) {
		UART_AsyncTransmitCharacter(messageTemp[i]);
	}
	for (uint32_t i = 0; i < 6; i++) {
		LCD_CommandEnqueue(LCD_DATA, messageKisa[i]);
	}
	// Ekvivalentna adresa: 0xD0
	LCD_CommandEnqueue(LCD_INSTRUCTION,
	LCD_SET_DD_RAM_ADDRESS_INSTRUCTION | 0x50);
	for (uint32_t i = 0; i < 13; i++) {
		LCD_CommandEnqueue(LCD_DATA, messageThld[i]);
	}

	while (1) {
		tempValue = TEMP_GetCurrentValue();
		itoa(rainfall, tempText, 10);

		FanState fanStateTarget;
		if (tempValue < temp_granica) {
			fanStateTarget = TURNED_OFF;
		} else {
			fanStateTarget = SLOW;
		}
		for (uint32_t i = 0; i < abs(fanStateTarget - fanState); i++) {
			if (fanStateTarget > fanState) {
				MOTOR_SpeedIncrease();
			} else {
				MOTOR_SpeedDecrease();
			}
		}
		fanState = fanStateTarget;

		LCD_CommandEnqueue(LCD_INSTRUCTION,
		LCD_SET_DD_RAM_ADDRESS_INSTRUCTION | 0x06);
		for (uint32_t i = 0; i < strlen(tempText); i++) {
			LCD_CommandEnqueue(LCD_DATA, tempText[i]);
		}
		UART_AsyncTransmitCharacter('\b');
		UART_AsyncTransmitCharacter('\b');
		if (tempValue < 10) {
			UART_AsyncTransmitCharacter(' ');
		} else {
			UART_AsyncTransmitCharacter(tempValue / 10 + '0');
		}
		UART_AsyncTransmitCharacter(tempValue % 10 + '0');

		if (keysChanged) {
			keysChanged = 0;
			LCD_CommandEnqueue(LCD_INSTRUCTION,
			LCD_SET_DD_RAM_ADDRESS_INSTRUCTION | 0x56);
			LCD_CommandEnqueue(LCD_DATA, keys[0]);
			LCD_CommandEnqueue(LCD_DATA, keys[1]);
			LCD_CommandEnqueue(LCD_INSTRUCTION,
			LCD_SET_DD_RAM_ADDRESS_INSTRUCTION | 0x5B);
			LCD_CommandEnqueue(LCD_DATA,
					(temp_granica < 10) ? ' ' : (temp_granica / 10) + '0');
			LCD_CommandEnqueue(LCD_DATA, (temp_granica % 10) + '0');
		}

		vTaskDelay(pdMS_TO_TICKS(200));
	}
}

void homeworkOverflow() {
	rainfall = 36000 / passedMs;
	passedMs = 0;
}

void homeworkCounter(TimerHandle_t xTimer) {
	UNUSED(xTimer);
	++passedMs;
}

void ledCounter(TimerHandle_t xTimer) {
	UNUSED(xTimer);
	if (TEMP_GetCurrentValue() < temp_granica) {
		HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET);
		HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);
	} else {
		HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);
		HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_9);
	}
}

void homeworkInit() {
	LCD_Init();
	UART_Init();
	MOTOR_Init();
	TEMP_Init();
	KEY_Init();
	xTaskCreate(homeworkTask, "homeworkTask", 64, NULL, 5, NULL);
	homeworkTimer = xTimerCreate("homeworkTimer", pdMS_TO_TICKS(1), pdTRUE,
	NULL, homeworkCounter);
	xTimerStart(homeworkTimer, portMAX_DELAY);
	ledTimer = xTimerCreate("ledTimer", pdMS_TO_TICKS(500), pdTRUE,
	NULL, ledCounter);
	xTimerStart(ledTimer, portMAX_DELAY);
}