#define UBRR_VAL ((F_CPU / (8UL * BAUD)) - 1) /* U2X mode */
#include <avr/interrupt.h>
/* --- Pin definitions --- */
#define LIMIT_PIN PD2 /* INT0 */
#define EE_STEPS_MM ((float *)0) /* EEPROM address for calibration */
/* --- Global state --- */
static volatile int32_t current_pos = 0; /* steps from home */
static volatile int32_t target_pos = 0;
static volatile uint8_t moving = 0;
static volatile uint8_t limit_hit = 0;
static volatile uint8_t homing = 0;
static float steps_per_mm = 25.0; /* default: 200 steps/rev, 8mm pitch */
static float feedrate = 300.0; /* mm/min */
static float rapid_rate = 1000.0; /* mm/min for G0 */
/* ================================
UART (with U2X for 115200 baud)
================================ */
static void uart_init(void)
UBRR0H = (uint8_t)(UBRR_VAL >> 8);
UBRR0L = (uint8_t)(UBRR_VAL);
UCSR0A = (1 << U2X0); /* Double speed for lower baud error */
UCSR0B = (1 << TXEN0) | (1 << RXEN0);
UCSR0C = (1 << UCSZ01) | (1 << UCSZ00); /* 8N1 */
static void uart_putc(char c)
while (!(UCSR0A & (1 << UDRE0)));
static void uart_puts(const char *s)
while (*s) uart_putc(*s++);
static void uart_put_i32(int32_t val)
static uint8_t uart_available(void)
return (UCSR0A & (1 << RXC0)) ? 1 : 0;
static char uart_getc(void)
while (!(UCSR0A & (1 << RXC0)));
/* Read a full line (up to CR or LF) into buf. Returns length. */
static uint8_t uart_readline(char *buf, uint8_t maxlen)
if (c == '\n' || c == '\r') {
if (i > 0) break; /* ignore leading newlines */
/* ================================
ADC (jog potentiometer on PC0)
================================ */
static void adc_init(void)
ADMUX = (1 << REFS0); /* AVcc reference, channel 0 */
ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
static uint16_t adc_read(void)
while (ADCSRA & (1 << ADSC));
/* ================================
Timer1: step pulse generation
================================ */
static void timer1_start(float steps_sec)
if (steps_sec < 1.0) steps_sec = 1.0;
uint16_t ocr = (uint16_t)(F_CPU / (2UL * 64 * (uint32_t)steps_sec) - 1);
TCCR1B = (1 << WGM12) | (1 << CS11) | (1 << CS10); /* CTC, prescaler 64 */
static void timer1_stop(void)
/* Timer1 compare match: one step per interrupt */
/* Step toward home (negative direction) */
PORTD |= (1 << STEP_PIN);
PORTD &= ~(1 << STEP_PIN);
if (current_pos == target_pos) {
PORTD |= (1 << STEP_PIN);
PORTD &= ~(1 << STEP_PIN);
if (current_pos < target_pos) current_pos++;
/* ================================
================================ */
/* ================================
================================ */
static void move_to(float pos_mm, float rate_mm_min)
int32_t target = (int32_t)(pos_mm * steps_per_mm);
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
diff = target_pos - current_pos;
if (diff > 0) PORTD |= (1 << DIR_PIN);
else { PORTD &= ~(1 << DIR_PIN); diff = -diff; }
float steps_sec = (rate_mm_min / 60.0) * steps_per_mm;
/* Wait for move to complete */
/* Check for limit switch during move */
if (limit_hit && !homing) {
uart_puts("!! Limit hit during move\n");
static void do_home(void)
uart_puts("Homing...\n");
/* Move in negative direction toward limit switch */
PORTD &= ~(1 << DIR_PIN);
float home_speed = 200.0; /* mm/min */
float steps_sec = (home_speed / 60.0) * steps_per_mm;
while (homing); /* Wait for limit switch or timeout */
uart_puts("Home found. Position reset to 0.\n");
/* ================================
Jog mode (potentiometer control)
================================ */
static void jog_check(void)
uint16_t adc = adc_read();
/* Dead zone in center (480-544): no movement */
if (adc > 480 && adc < 544) return;
/* Map ADC to speed: further from center = faster */
PORTD |= (1 << DIR_PIN); /* Forward */
speed = (float)(adc - 544) / 480.0 * 600.0; /* 0-600 mm/min */
PORTD &= ~(1 << DIR_PIN); /* Reverse */
speed = (float)(480 - adc) / 480.0 * 600.0;
if (speed < 10.0) return;
/* Single step at computed rate */
float steps_sec = (speed / 60.0) * steps_per_mm;
uint16_t delay_us = (uint16_t)(1000000.0 / steps_sec);
if (delay_us < 100) delay_us = 100;
PORTD |= (1 << STEP_PIN);
PORTD &= ~(1 << STEP_PIN);
if (PORTD & (1 << DIR_PIN)) current_pos++;
/* Simple delay for step rate */
for (uint16_t i = 0; i < delay_us / 10; i++) {
/* ================================
================================ */
static float parse_value(const char *line, char letter)
static void process_line(const char *line)
int cmd = atoi(line + 1);
case 0: /* G0: rapid move */
x = parse_value(line, 'X');
if (x >= 0) move_to(x, rapid_rate);
case 1: /* G1: linear move */
f = parse_value(line, 'F');
x = parse_value(line, 'X');
if (x >= 0) move_to(x, feedrate);
uart_puts("Unknown G command\n");
} else if (line[0] == 'M') {
int cmd = atoi(line + 1);
case 114: /* M114: report position */
uart_put_i32(current_pos);
int32_t um = (int32_t)(current_pos * 1000.0 / steps_per_mm);
int32_t frac = um % 1000;
if (frac < 0) frac = -frac;
if (frac < 100) uart_putc('0');
if (frac < 10) uart_putc('0');
case 500: /* M500: save calibration */
eeprom_update_float(EE_STEPS_MM, steps_per_mm);
uart_puts("Saved steps/mm: ");
uart_put_i32((int32_t)steps_per_mm);
case 501: /* M501: load calibration */
float val = eeprom_read_float(EE_STEPS_MM);
if (val > 0.0 && val < 10000.0) {
uart_puts("Loaded steps/mm: ");
uart_put_i32((int32_t)steps_per_mm);
uart_puts("No valid calibration in EEPROM\n");
uart_puts("Unknown M command\n");
} else if (line[0] == 'J') {
/* J1: enter jog mode, J0: exit */
uart_puts("Jog mode (pot controls speed). Send J0 to exit.\n");
if (c == 'J' || c == 'j') break;
uart_puts("Jog mode exited.\n");
} else if (line[0] != '\0') {
/* ================================
================================ */
DDRD |= (1 << STEP_PIN) | (1 << DIR_PIN);
/* Limit switch on PD2 (INT0): input with pull-up */
DDRD &= ~(1 << LIMIT_PIN);
PORTD |= (1 << LIMIT_PIN);
/* INT0: falling edge (switch closes to GND) */
/* Load calibration from EEPROM */
float saved = eeprom_read_float(EE_STEPS_MM);
if (saved > 0.0 && saved < 10000.0) {
uart_puts("\n--- ATmega328P Stepper Controller ---\n");
uart_puts("Commands: G0 X50, G1 X25 F300, G28, M114, M500, M501, J1\n");
uart_put_i32((int32_t)steps_per_mm);
uart_readline(line, sizeof(line));
Comments