#include "stm32c0xx.h"
// Function Prototypes
void GPIO_Init(void);
void ADC_Init(void);
void TIM3_PWM_Init(void);
void USART2_Init(void);
void EXTI_Init(void);
void UART_SendString(char* str);
void UART_SendNumber(uint32_t num);
void Servo_SetAngle(uint8_t angle);
uint16_t ADC_Read(void);
// Volatile is required here because this variable is modified inside an ISR
// and read inside the main loop. It prevents compiler optimization errors.
volatile uint8_t emergency_stop = 0;
// EXTI2 to 3 Interrupt Service Routine
void EXTI2_3_IRQHandler(void) {
// Check if the EXTI2 falling edge flag is set
if (EXTI->FPR1 & (1 << 2)) {
// Clear the flag by writing 1 to it (STM32 clear-on-write mechanism)
EXTI->FPR1 = (1 << 2);
// Signal the main loop to halt the system
emergency_stop = 1;
}
// Clear rising edge flag as well to prevent switch bouncing issues
if (EXTI->RPR1 & (1 << 2)) {
EXTI->RPR1 = (1 << 2);
}
}
int main(void) {
// Hardware initialization sequence
GPIO_Init();
ADC_Init();
TIM3_PWM_Init();
USART2_Init();
EXTI_Init();
// Enable global interrupts to ensure EXTI can trigger
__enable_irq();
uint16_t adc_value = 0;
// Threshold for a 12-bit ADC (0-4095). 2048 is the midpoint (approx 1.65V)
uint16_t threshold = 2048;
while(1) {
// Hardware Fallback: Polling the button state manually in case the ISR is missed
// PB2 is active-low (pulled up), so 0 means pressed.
if ((GPIOB->IDR & (1 << 2)) == 0) {
emergency_stop = 1;
}
// Halt condition check
if (emergency_stop == 1) {
// Disable TIM3 counter (CEN = 0) to stop the servo PWM signal
TIM3->CR1 &= ~(1 << 0);
UART_SendString("ALARM: EMERGENCY STOP PRESSED! SYSTEM HALTED\r\n");
// Trap the CPU in an infinite loop for safety
while(1);
}
// Read the current weight/load from the potentiometer
adc_value = ADC_Read();
// Classification logic based on ADC reading
if (adc_value < threshold) {
Servo_SetAngle(0); // Let the light item pass
UART_SendString("Load: ");
UART_SendNumber(adc_value);
UART_SendString(" - Status: PASS\r\n");
} else {
Servo_SetAngle(90); // Reject the heavy item
UART_SendString("Load: ");
UART_SendNumber(adc_value);
UART_SendString(" - Status: REJECTED\r\n");
}
// Smart delay loop to prevent serial terminal spam
for(volatile uint32_t i = 0; i < 200000; i++) {
// High-speed polling during the delay to ensure immediate response
if ((GPIOB->IDR & (1 << 2)) == 0) {
emergency_stop = 1;
}
// Break the delay loop instantly if the emergency flag is raised
if (emergency_stop == 1) {
break;
}
}
}
}
// ==========================================================
// PERIPHERAL CONFIGURATION FUNCTIONS
// ==========================================================
void GPIO_Init(void) {
// Enable clock for Port A and Port B on the IOPORT bus
RCC->IOPENR |= (1 << 0); // GPIOA
RCC->IOPENR |= (1 << 1); // GPIOB
// PA0 configuration: Analog mode for ADC input
// MODER bits: 11 (Analog)
GPIOA->MODER |= (3 << 0);
// PA6 configuration: Alternate Function mode for TIM3_CH1 (PWM output)
GPIOA->MODER &= ~(3 << 12); // Clear mode bits
GPIOA->MODER |= (2 << 12); // Set to AF mode (10)
GPIOA->AFR[0] &= ~(0xF << 24); // Clear Alternate Function bits
GPIOA->AFR[0] |= (1 << 24); // Set AF1 (TIM3_CH1)
// PB2 configuration: Input mode with internal Pull-Up resistor (Emergency Button)
GPIOB->MODER &= ~(3 << 4); // Clear mode bits (00 = Input)
GPIOB->PUPDR &= ~(3 << 4); // Clear pull-up/down bits
GPIOB->PUPDR |= (1 << 4); // Set Pull-Up (01)
}
void ADC_Init(void) {
// Enable clock for ADC1 on the APB2 bus
RCC->APBENR2 |= (1 << 20);
// Enable the ADC internal voltage regulator
ADC1->CR |= (1 << 28); // ADVREGEN = 1
// Wait for the voltage regulator to stabilize (tADCVREG_SETUP)
for(volatile uint32_t i = 0; i < 1000; i++);
// Start ADC calibration process
ADC1->CR |= (1 << 31); // ADCAL = 1
// Wait until the calibration is complete (hardware clears the bit)
while (ADC1->CR & (1 << 31));
// Enable the ADC
ADC1->CR |= (1 << 0); // ADEN = 1
// Wait until ADC is ready
while (!(ADC1->ISR & (1 << 0)));
// Select Channel 0 (mapped to PA0)
ADC1->CHSELR = (1 << 0);
// Set maximum sampling time for better accuracy
ADC1->SMPR |= (7 << 0);
}
uint16_t ADC_Read(void) {
// Start the analog-to-digital conversion
ADC1->CR |= (1 << 2); // ADSTART = 1
// Wait for End Of Conversion (EOC) flag
while (!(ADC1->ISR & (1 << 2)));
// Read and return the 12-bit converted value from the Data Register
return (uint16_t)ADC1->DR;
}
void TIM3_PWM_Init(void) {
// Enable clock for TIM3 on the APB1 bus
RCC->APBENR1 |= (1 << 1);
// PWM Frequency Calculation:
// System Clock = 48 MHz. Target Frequency = 50 Hz (for servo).
// Timer Frequency = 48MHz / (PSC + 1) = 48MHz / 48 = 1 MHz.
TIM3->PSC = 47;
// PWM Period = 1MHz / 50Hz = 20000 ticks.
TIM3->ARR = 19999;
// Configure Channel 1 for PWM Mode 1
TIM3->CCMR1 &= ~(7 << 4); // Clear OC1M bits
TIM3->CCMR1 |= (6 << 4); // Set OC1M to 110 (PWM Mode 1)
TIM3->CCMR1 |= (1 << 3); // Enable Output Compare Preload (OC1PE)
// Enable Capture/Compare output for Channel 1
TIM3->CCER |= (1 << 0); // CC1E = 1
// Enable Auto-Reload Preload (ARPE) and generate an update event (UG)
TIM3->CR1 |= (1 << 7);
TIM3->EGR |= (1 << 0);
// Enable the timer counter (CEN = 1)
TIM3->CR1 |= (1 << 0);
}
void Servo_SetAngle(uint8_t angle) {
// Map angle (0-90) to pulse width:
// 0 degrees = 1000 us (1 ms)
// 90 degrees = 1500 us (1.5 ms)
uint16_t pulse = 1000 + (angle * 500 / 90);
// Update the Capture/Compare Register with the new duty cycle
TIM3->CCR1 = pulse;
}
void USART2_Init(void) {
// Enable clock for USART2 on the APB1 bus
RCC->APBENR1 |= (1 << 17);
// PA2 configuration: Alternate Function mode for USART2_TX
GPIOA->MODER &= ~(3 << 4);
GPIOA->MODER |= (2 << 4);
GPIOA->AFR[0] &= ~(0xF << 8);
GPIOA->AFR[0] |= (1 << 8); // AF1
// PA3 configuration: Alternate Function mode for USART2_RX
GPIOA->MODER &= ~(3 << 6);
GPIOA->MODER |= (2 << 6);
GPIOA->AFR[0] &= ~(0xF << 12);
GPIOA->AFR[0] |= (1 << 12); // AF1
// Baud Rate Calculation:
// System Clock = 48 MHz. Target Baud Rate = 115200.
// USARTDIV = 48,000,000 / 115200 = 416.66 (round to 417)
USART2->BRR = 417;
// Enable Transmitter (TE), Receiver (RE), and USART module (UE)
USART2->CR1 |= (1 << 3);
USART2->CR1 |= (1 << 2);
USART2->CR1 |= (1 << 0);
}
void UART_SendString(char* str) {
// Loop until the null terminator is reached
while (*str) {
// Wait until the Transmit Data Register is Empty (TXE flag)
while (!(USART2->ISR & (1 << 7)));
// Write the character to the Transmit Data Register
USART2->TDR = *str++;
}
}
void UART_SendNumber(uint32_t num) {
char buffer[12];
int i = 0;
// Handle zero explicitly
if (num == 0) {
buffer[i++] = '0';
} else {
char temp[12];
int j = 0;
// Extract digits in reverse order
while (num > 0) {
temp[j++] = '0' + (num % 10);
num /= 10;
}
// Reverse the array to get the correct string
for (int k = j - 1; k >= 0; k--) {
buffer[i++] = temp[k];
}
}
buffer[i] = '\0'; // Null-terminate the string
UART_SendString(buffer); // Send the converted string
}
void EXTI_Init(void) {
// Enable clock for SYSCFG (Required to configure the EXTI multiplexer)
RCC->APBENR2 |= (1 << 0);
// Route PB2 to EXTI Line 2
// EXTI2 configuration is located in EXTICR[0], bits 16-23
EXTI->EXTICR[0] &= ~(0xFF << 16); // Clear previous configuration
EXTI->EXTICR[0] |= (1 << 16); // Set Port B (value 1) for EXTI2
// Enable falling edge trigger for EXTI Line 2
EXTI->FTSR1 |= (1 << 2);
// Unmask (enable) the interrupt for EXTI Line 2
EXTI->IMR1 |= (1 << 2);
// Enable the EXTI2_3 interrupt in the Nested Vectored Interrupt Controller (NVIC)
NVIC_EnableIRQ(EXTI2_3_IRQn);
}