|
| 1 | +# ⚙️ HW3: Embedded Systems, Timers, EXTI Interrupts & Sensor LCD Interfacing |
| 2 | + |
| 3 | +> **Course:** Instrumentation Engineering (Spring 2026 / 1405) |
| 4 | +> **Instructor:** Dr. Nayeri |
| 5 | +> **Student:** Amirali Dehghani (ID: 810102443) |
| 6 | +> **Microcontroller:** STM32F103 Series (ARM Cortex-M3) |
| 7 | +> **Tools:** STM32CubeIDE, STM32 HAL Library, Proteus 8 Professional, LaTeX |
| 8 | +> **Files Included:** C Source Code (`main.c`), STM32 Config (`.ioc`), Executables (`.hex`), Proteus Files (`.pdsprj`), Demonstration Videos (`.mp4`), Images (`img/`), and Solved Lab Report (`.pdf`). |
| 9 | +
|
| 10 | +--- |
| 11 | + |
| 12 | +## 📖 Overview |
| 13 | + |
| 14 | +Homework 3 transitions into embedded microcontrollers (STM32F103) for industrial data acquisition, timing control, actuator driving, and visual display interfacing: |
| 15 | +1. **Elevator Floor Indicator (7-Segment & EXTI Interrupts):** GPIO configuration for Common Cathode 7-Segment display, continuous 0–9 sequential floor cycling, and **External Interrupts (EXTI)** for emergency floor holds (`Floor 1` and `Floor 9`). |
| 16 | +2. **DC Motor Speed & Direction Control (PWM TIM2 + L298 Driver + ADC):** Timer configuration for $1\text{ kHz}$ PWM generation, mathematical Prescaler & Auto-Reload calculations, 12-bit ADC potentiometer speed scaling, L298 H-Bridge driver interfacing, and EXTI direction toggling. |
| 17 | +3. **15-Second Precision Countdown System (TIM2 Interrupts + 16x2 LCD + Alarm):** Hardware timer interrupt callback ($500\text{ ms}$ tick), synchronous $1\text{ Hz}$ LED blinking & Buzzer beeping, continuous alarm on expiration ($0\text{ s}$), real-time 16x2 LCD display, and instant `LogicState` reset. |
| 18 | +4. **Automatic Industrial Sensor Identification System (12-Bit ADC + Alphanumeric LCD):** Multi-sensor simulation via potentiometer sampling with a 12-bit ADC ($0.8\text{ mV}$ resolution over $3.3\text{ V}$ range), automatic identification of **LM35 Temperature**, **HIH-4000 Humidity**, **MPX4115 Pressure**, and **MQ-135 Gas** sensors, and formatted LCD readout. |
| 19 | + |
| 20 | +--- |
| 21 | + |
| 22 | +## 📂 Directory Structure |
| 23 | + |
| 24 | +```text |
| 25 | +HW3/ |
| 26 | +├── Q1/ |
| 27 | +│ ├── main.c # Source code for 7-Segment Elevator & EXTI emergency hold |
| 28 | +│ ├── Q1.pdsprj # Proteus schematic (STM32 + 7-Segment + Buttons) |
| 29 | +│ ├── HW3-1.hex # Compiled binary firmware |
| 30 | +│ └── HW3-1.ioc # STM32CubeMX pinout & clock configuration |
| 31 | +├── Q2/ |
| 32 | +│ ├── main.c # Source code for PWM DC motor speed & direction control |
| 33 | +│ ├── Q2.pdsprj # Proteus schematic (STM32 + L298 Driver + DC Motor + Pot) |
| 34 | +│ ├── Q2.hex # Compiled binary firmware |
| 35 | +│ └── Q2.ioc # STM32CubeMX configuration (TIM2 PWM + ADC1 + EXTI) |
| 36 | +├── Q3/ |
| 37 | +│ ├── main.c # Source code for 15s Timer Interrupt countdown system |
| 38 | +│ ├── Q3.pdsprj # Proteus schematic (STM32 + 16x2 LCD + Buzzer + LED) |
| 39 | +│ ├── Q3.hex # Compiled binary firmware |
| 40 | +│ └── Q3.ioc # STM32CubeMX configuration (TIM2 Interrupt + GPIO) |
| 41 | +├── Q4/ |
| 42 | +│ ├── main.c # Source code for Auto Sensor Classifier & Physical Unit LCD |
| 43 | +│ ├── q4.pdsprj # Proteus schematic (STM32 + Active Pot + 16x2 LCD) |
| 44 | +│ ├── Q4.hex # Compiled binary firmware |
| 45 | +│ └── Q4.ioc # STM32CubeMX configuration (ADC1 12-Bit + GPIO LCD) |
| 46 | +├── LCD Files/ |
| 47 | +│ ├── lcd.c # HD44780 LCD HAL driver implementation |
| 48 | +│ └── lcd.h # HD44780 LCD library header |
| 49 | +├── Video/ |
| 50 | +│ ├── Q1.mp4 # Video demonstration for Question 1 |
| 51 | +│ ├── Q2.mp4 # Video demonstration for Question 2 |
| 52 | +│ ├── Q3.mp4 # Video demonstration for Question 3 |
| 53 | +│ └── Q4.mp4 # Video demonstration for Question 4 |
| 54 | +├── img/ |
| 55 | +│ ├── q1-1.png - q1-5-9.png # Pinouts, schematics & 7-Segment floor counting frames (0-9) |
| 56 | +│ ├── q2-1.png - q2-4-2.png # TIM2 PWM pinouts, L298 connections & motor rotation plots |
| 57 | +│ ├── q3-1.png - q3-3.png # Countdown LCD schematic, active state & zero-alarm hold |
| 58 | +│ └── q4-1.png - q4-6.png # ADC pinout & LCD readings for LM35, HIH4000, MPX4115, MQ135 |
| 59 | +├── Inst_HW3.pdf # Original Assignment Question Paper |
| 60 | +└── Inst-HW3-810102443.pdf # Complete Solved Lab Report (PDF) |
| 61 | +``` |
| 62 | + |
| 63 | +--- |
| 64 | + |
| 65 | +## 💻 C Source Code & HAL Implementation Analysis |
| 66 | + |
| 67 | +--- |
| 68 | + |
| 69 | +### 🔹 Question 1: Elevator Floor Indicator (`Q1/main.c`) |
| 70 | + |
| 71 | +#### **Code Architecture & Register Manipulation:** |
| 72 | +- **7-Segment Hex Look-up Table:** |
| 73 | + ```c |
| 74 | + uint8_t segCode[10] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F}; |
| 75 | + ``` |
| 76 | + Pre-computed Common Cathode hex codes mapping digits `0` through `9` to GPIOA pins `PA0..PA6`. |
| 77 | +
|
| 78 | +- **Atomic ODR Register Update:** |
| 79 | + To update segment outputs without altering high GPIOA pins, atomic bitmasking is performed on the Output Data Register: |
| 80 | + ```c |
| 81 | + GPIOA->ODR = (GPIOA->ODR & 0xFF80) | segCode[currentNumber]; |
| 82 | + ``` |
| 83 | + |
| 84 | +- **State Machine & EXTI Interrupt Callbacks:** |
| 85 | + ```c |
| 86 | + volatile uint8_t systemState = 0; // State 0: Init (Floor 0), State 1: Running, State 2: Emergency 1, State 3: Emergency 9 |
| 87 | + |
| 88 | + void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { |
| 89 | + if (GPIO_Pin == GPIO_PIN_1) { |
| 90 | + systemState = 2; // Immediate hold on Floor 1 |
| 91 | + } else if (GPIO_Pin == GPIO_PIN_2) { |
| 92 | + systemState = 3; // Immediate hold on Floor 9 |
| 93 | + } |
| 94 | + } |
| 95 | + ``` |
| 96 | + The asynchronous EXTI callbacks interrupt the $1000\text{ ms}$ `HAL_Delay` loop immediately, guaranteeing zero-latency emergency stopping. |
| 97 | +
|
| 98 | +| Figure 1.1: STM32CubeIDE Pinout Configuration | Figure 1.2: Proteus Circuit Schematic | |
| 99 | +| :---: | :---: | |
| 100 | +|  |  | |
| 101 | +
|
| 102 | +| Figure 1.3: Floor 0 (Initial) | Figure 1.4: Floor 5 (Cycling) | Figure 1.5: Emergency Hold Floor 1 (`EXTI1`) | Figure 1.6: Emergency Hold Floor 9 (`EXTI2`) | |
| 103 | +| :---: | :---: | :---: | :---: | |
| 104 | +|  |  |  |  | |
| 105 | +
|
| 106 | +--- |
| 107 | +
|
| 108 | +### 🔹 Question 2: DC Motor Speed & Direction Control (`Q2/main.c`) |
| 109 | +
|
| 110 | +#### **Theoretical Prescaler & PWM Frequency Math:** |
| 111 | +Target PWM frequency: $f_{\text{PWM}} = 1\text{ kHz}$ on a $f_{\text{TIM}} = 36\text{ MHz}$ timer clock: |
| 112 | +$$f_{\text{PWM}} = \frac{f_{\text{TIM}}}{(\text{PSC} + 1) \times (\text{ARR} + 1)} = \frac{36 \times 10^6}{(35 + 1) \times (999 + 1)} = \frac{36 \times 10^6}{36000} = \mathbf{1000\text{ Hz}}$$ |
| 113 | +
|
| 114 | +#### **C Implementation Highlights:** |
| 115 | +- **Timer & ADC Initialization:** |
| 116 | + ```c |
| 117 | + HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); |
| 118 | + ``` |
| 119 | + |
| 120 | +- **ADC Sampling & Dynamic PWM Scaling Loop:** |
| 121 | + ```c |
| 122 | + HAL_ADC_Start(&hadc1); |
| 123 | + if (HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK) { |
| 124 | + adcValue = HAL_ADC_GetValue(&hadc1); |
| 125 | + } |
| 126 | + HAL_ADC_Stop(&hadc1); |
| 127 | + |
| 128 | + // Mappings: 12-bit ADC (0..4095) -> PWM Duty Cycle ARR range (0..1000) |
| 129 | + pwmValue = (adcValue * 1000) / 4095; |
| 130 | + __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, pwmValue); |
| 131 | + ``` |
| 132 | +
|
| 133 | +- **H-Bridge Direction Control & EXTI Inversion:** |
| 134 | + ```c |
| 135 | + volatile uint8_t motorDirection = 0; |
| 136 | +
|
| 137 | + void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { |
| 138 | + if (GPIO_Pin == GPIO_PIN_0) { |
| 139 | + motorDirection = !motorDirection; // Toggle direction on button press |
| 140 | + } |
| 141 | + } |
| 142 | + ``` |
| 143 | + The main loop evaluates `motorDirection` and sets L298 driver input pins `PA2` (`IN1`) and `PA3` (`IN2`) accordingly. |
| 144 | + |
| 145 | +| Figure 2.1: TIM2 PWM & ADC Pinout Config | Figure 2.2: L298 Motor Driver Proteus Schematic | |
| 146 | +| :---: | :---: | |
| 147 | +|  |  | |
| 148 | + |
| 149 | +| Figure 2.3: Clockwise Rotation & Speed Adjustment | Figure 2.4: Counter-Clockwise Inversion (`EXTI`) | |
| 150 | +| :---: | :---: | |
| 151 | +|  |  | |
| 152 | + |
| 153 | +--- |
| 154 | + |
| 155 | +### 🔹 Question 3: 15-Second Precision Countdown System (`Q3/main.c`) |
| 156 | + |
| 157 | +#### **Timer Interrupt Callbacks & Sub-Second Ticking:** |
| 158 | +TIM3 is configured with Prescaler $\text{PSC} = 35999$ and Period $\text{ARR} = 499$, generating a hardware interrupt callback every **$500\text{ ms}$** ($2\text{ Hz}$ tick). |
| 159 | + |
| 160 | +- **Synchronous Alarm & Clock Management in `HAL_TIM_PeriodElapsedCallback`:** |
| 161 | + ```c |
| 162 | + void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { |
| 163 | + if (htim->Instance == TIM3) { |
| 164 | + if (isRunning == 1) { |
| 165 | + if (countdown > 0) { |
| 166 | + timerTick++; |
| 167 | + if (timerTick % 2 == 1) { // 500ms ON state |
| 168 | + HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET); // LED ON |
| 169 | + HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET); // Buzzer ON |
| 170 | + } else { // 500ms OFF state (Full 1s elapsed) |
| 171 | + HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET); // LED OFF |
| 172 | + HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET); // Buzzer OFF |
| 173 | + countdown--; |
| 174 | + } |
| 175 | + } else { // Expiration (0s): Latch Alarm Continuously |
| 176 | + HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET); |
| 177 | + HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET); |
| 178 | + } |
| 179 | + } |
| 180 | + } |
| 181 | + } |
| 182 | + ``` |
| 183 | +
|
| 184 | +- **LCD Rendering & Hardware LogicState Reset in `main()`:** |
| 185 | + ```c |
| 186 | + if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET) { |
| 187 | + if (isRunning == 0 && countdown == 15) isRunning = 1; |
| 188 | + } else { |
| 189 | + isRunning = 0; countdown = 15; // Reset countdown to 15s |
| 190 | + HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET); |
| 191 | + HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET); |
| 192 | + } |
| 193 | +
|
| 194 | + if (countdown != last_countdown) { |
| 195 | + sprintf(lcdBuffer, "Time: %02d ", countdown); |
| 196 | + Lcd_cursor(&lcd, 0, 0); |
| 197 | + Lcd_string(&lcd, lcdBuffer); |
| 198 | + last_countdown = countdown; |
| 199 | + } |
| 200 | + ``` |
| 201 | + |
| 202 | +| Figure 3.1: Countdown System Proteus Schematic | |
| 203 | +| :---: | |
| 204 | +|  | |
| 205 | + |
| 206 | +| Figure 3.2: Countdown Active (T-minus 8s, Blinking/Beeping) | Figure 3.3: Expiration Alarm Hold (0s, Continuous Alarm) | |
| 207 | +| :---: | :---: | |
| 208 | +|  |  | |
| 209 | + |
| 210 | +--- |
| 211 | + |
| 212 | +### 🔹 Question 4: Automatic Multi-Sensor Classifier (`Q4/main.c`) |
| 213 | + |
| 214 | +#### **ADC Sampling & Real-Time Sensor Classification Code:** |
| 215 | +- **Quantization Voltage Formula:** |
| 216 | + $$\text{sensorVoltage} = \frac{\text{adcValue} \times 3.3\text{ V}}{4095.0}$$ |
| 217 | + |
| 218 | +- **Conditional Multi-Sensor Classification & Formatting:** |
| 219 | + ```c |
| 220 | + HAL_ADC_Start(&hadc1); |
| 221 | + if (HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK) { |
| 222 | + adcValue = HAL_ADC_GetValue(&hadc1); |
| 223 | + sensorVoltage = (adcValue * 3.3f) / 4095.0f; |
| 224 | + } |
| 225 | + HAL_ADC_Stop(&hadc1); |
| 226 | + |
| 227 | + if (sensorVoltage >= 0.0f && sensorVoltage < 1.0f) { |
| 228 | + sprintf(sensorName, "Temp: LM35"); |
| 229 | + physicalValue = 100.0f * sensorVoltage; |
| 230 | + sprintf(unit, "C"); |
| 231 | + } else if (sensorVoltage >= 1.0f && sensorVoltage < 2.0f) { |
| 232 | + sprintf(sensorName, "Hum: HIH-4000"); |
| 233 | + physicalValue = 33.3f * sensorVoltage; |
| 234 | + sprintf(unit, "%%"); |
| 235 | + } else if (sensorVoltage >= 2.0f && sensorVoltage < 3.0f) { |
| 236 | + sprintf(sensorName, "Pres: MPX4115"); |
| 237 | + physicalValue = 100.0f * sensorVoltage; |
| 238 | + sprintf(unit, "hPa"); |
| 239 | + } else if (sensorVoltage >= 3.0f && sensorVoltage <= 3.3f) { |
| 240 | + sprintf(sensorName, "Air: MQ-135"); |
| 241 | + physicalValue = 100.0f * (sensorVoltage / 3.3f); |
| 242 | + sprintf(unit, "%%"); |
| 243 | + } |
| 244 | + |
| 245 | + sprintf(line1, "%-16s", sensorName); |
| 246 | + sprintf(line2, "Val: %.2f %s ", physicalValue, unit); |
| 247 | + Lcd_cursor(&lcd, 0, 0); Lcd_string(&lcd, line1); |
| 248 | + Lcd_cursor(&lcd, 1, 0); Lcd_string(&lcd, line2); |
| 249 | + ``` |
| 250 | +
|
| 251 | +| Voltage Window ($V$) | Classified Sensor | Physical Formula | LCD Line 1 / Line 2 Output | |
| 252 | +| :---: | :---: | :---: | :---: | |
| 253 | +| **$0.00 - 0.99\text{ V}$** | **LM35 Temperature** | $\text{Temp} = 100 \times V$ | `Temp: LM35` / `Val: 50.00 C` | |
| 254 | +| **$1.00 - 1.99\text{ V}$** | **HIH-4000 Humidity** | $\text{Humidity} = 33.3 \times V$ | `Hum: HIH-4000` / `Val: 49.95 %` | |
| 255 | +| **$2.00 - 2.99\text{ V}$** | **MPX4115 Pressure** | $\text{Pressure} = 100 \times V$ | `Pres: MPX4115` / `Val: 250.00 hPa` | |
| 256 | +| **$3.00 - 3.30\text{ V}$** | **MQ-135 Air Quality** | $\text{Air Quality} = \frac{V}{3.3} \times 100$ | `Air: MQ-135` / `Val: 100.00 %` | |
| 257 | +
|
| 258 | +| Figure 4.1: STM32 ADC & LCD Pinout Config | Figure 4.2: Proteus Circuit Diagram | |
| 259 | +| :---: | :---: | |
| 260 | +|  |  | |
| 261 | +
|
| 262 | +| Figure 4.3: LM35 Temp ($0.50\text{ V} \to 50.00^\circ\text{C}$) | Figure 4.4: HIH4000 Humidity ($1.50\text{ V} \to 49.95\%$) | |
| 263 | +| :---: | :---: | |
| 264 | +|  |  | |
| 265 | +
|
| 266 | +| Figure 4.5: MPX4115 Pressure ($2.50\text{ V} \to 250.00\text{ hPa}$) | Figure 4.6: MQ-135 Air Quality ($3.30\text{ V} \to 100.00\%$) | |
| 267 | +| :---: | :---: | |
| 268 | +|  |  | |
| 269 | +
|
| 270 | +--- |
| 271 | +
|
| 272 | +## 💻 How to Build & Simulate |
| 273 | +
|
| 274 | +### 1. Building Firmware in STM32CubeIDE |
| 275 | +1. Open **STM32CubeIDE**. |
| 276 | +2. File $\to$ Open Projects from File System $\to$ Select target folder (`HW3/Q1`, `HW3/Q2`, `HW3/Q3`, or `HW3/Q4`). |
| 277 | +3. Build Project (`Ctrl + B`). The output `.hex` binary will generate automatically in `Debug/`. |
| 278 | +
|
| 279 | +### 2. Running Proteus Simulation |
| 280 | +1. Launch **Proteus 8 Professional**. |
| 281 | +2. Open target `.pdsprj` file from `HW3/Q1/`, `HW3/Q2/`, `HW3/Q3/`, or `HW3/Q4/`. |
| 282 | +3. Double-click the STM32 microcontroller component and ensure **Program File** points to the corresponding `.hex` file. |
| 283 | +4. Press **Play** to start real-time simulation. Demonstration MP4 videos are also available in `HW3/Video/`. |
| 284 | +
|
| 285 | +--- |
| 286 | +
|
| 287 | +<div align="center"> |
| 288 | +
|
| 289 | +**[Go back to Main Repository README](../README.md)** |
| 290 | +
|
| 291 | +</div> |
0 commit comments