commit 5730241efc3a20704f4ea2c792f54b6f5a4e35ec
parent ce87f96af691c3478fc62aac0783ef27df6991e9
Author: William Lindholm <william_lindholm@outlook.com>
Date: Wed, 20 Nov 2024 17:21:07 +0000
Implemented servo control
Diffstat:
2 files changed, 405 insertions(+), 4 deletions(-)
diff --git a/Project2/Project2/main-williams-laptop.c b/Project2/Project2/main-williams-laptop.c
@@ -0,0 +1,382 @@
+/*
+ * Project2.c
+ *
+ * Created: 2024-11-06 11:04:34
+ * Author : William Lindholm & Thomas Berry
+ */
+
+#define F_CPU 20000000
+#define USART3_BAUD_RATE(BAUD_RATE) ((float)(F_CPU * 64 / (16 * (float)BAUD_RATE)) + 0.5)
+#define BAUD_RATE 115200
+#define QUEUE_SIZE 512
+
+#include <avr/io.h>
+#include <avr/interrupt.h>
+#include <avr/cpufunc.h>
+#include <util/delay.h>
+#include <util/delay.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdbool.h>
+#include <ctype.h>
+
+
+/// TODO
+// ENABLE EVENT CONTROLLED START CONVERSION FOR ADC
+
+/************************************************************************/
+/* Globals */
+/************************************************************************/
+
+/* Led related globals */
+struct LED_BITS
+{
+ PORT_t *LED_PORT;
+ uint8_t bit_mapping;
+};
+
+struct LED_BITS LED_Array[10] = {
+ {&PORTC, PIN5_bm}, {&PORTC, PIN4_bm}, {&PORTA, PIN0_bm}, {&PORTF, PIN5_bm}, {&PORTC, PIN6_bm}, {&PORTB, PIN2_bm}, {&PORTF, PIN4_bm}, {&PORTA, PIN1_bm}, {&PORTA, PIN2_bm}, {&PORTA, PIN3_bm}
+};
+
+
+/* Serial com related globals */
+uint16_t qcntr = 0, sndcntr = 0; /*indexes into the queue*/
+unsigned char queue[QUEUE_SIZE]; /*character queue*/
+
+/* ADC related globals */
+bool ADC_READING_READY;
+uint16_t ADC_VALUE;
+
+/* 555 Timer related globals */
+bool TIMER_READING_READY;
+uint16_t TCB0_HIGH_PULSE;
+uint16_t TCB0_LOW_PULSE;
+uint16_t TCB0_PERIOD;
+
+
+/************************************************************************/
+/* Function declarations */
+/************************************************************************/
+void CLOCK_init (void);
+static void configure_USART3(void);
+void sendmsg (char *s);
+void configure_ADC0(void);
+void configure_EVSYS();
+void configure_TCB0(void);
+void print_tcb0_high_pulse(void);
+void print_tcb0_low_pulse(void);
+void print_adc_voltage(void);
+void print_adc_value(void);
+void print_available_commands(void);
+void print_tcb0_timer_period(void);
+bool queue_is_empty(void);
+
+
+
+int main(void)
+{
+ char ch;
+ char str_buffer[32];
+
+ // Flags
+ bool continuous_adc_reporting = false;
+ bool continuous_timer_reporting = false;
+
+ CLOCK_init();
+ configure_EVSYS();
+ configure_TCB0();
+ configure_USART3();
+ configure_ADC0();
+
+ sei(); /* Enable Global Interrupts */
+
+ while (1)
+ {
+ if (USART3.STATUS & USART_RXCIF_bm)
+ { /* If a character has been received, read it - this structure allows other code to run */
+ ch = USART3.RXDATAL;
+
+ // if received character is a digit
+ if (isdigit(ch))
+ {
+ char str_buffer[16];
+ sprintf(str_buffer, "received %c", ch);
+ sendmsg(str_buffer);
+ }
+
+ // if received character is a letter
+ switch (ch)
+ {
+ case 'H':
+ case 'h':
+ print_tcb0_high_pulse();
+ break;
+ case 'L':
+ case 'l':
+ print_tcb0_low_pulse();
+ break;
+ case 'T':
+ case 't':
+ print_tcb0_timer_period();
+ break;
+ case 'A':
+ case 'a':
+ print_adc_value();
+ break;
+ case 'C':
+ case 'c':
+ continuous_timer_reporting = true;
+ break;
+ case 'E':
+ case 'e':
+ continuous_timer_reporting = false;
+ break;
+ case 'V': // print ADC voltage
+ case 'v':
+ print_adc_voltage();
+ break;
+ case 'M': // Start continuous reporting of ADC voltage in mV
+ case 'm':
+ continuous_adc_reporting = true;
+ break;
+ case 'N': // Stop continuous reporting of ADC
+ case 'n':
+ continuous_adc_reporting = false;
+ sprintf(str_buffer, "Stopped\n");
+ sendmsg(str_buffer);
+ break;
+ default:
+ print_available_commands();
+ break;
+ }
+ }
+
+ // Continuous reporting
+ if (continuous_adc_reporting && ADC_READING_READY && queue_is_empty())
+ {
+ print_adc_voltage();
+ ADC_READING_READY = false;
+ }
+ if (continuous_timer_reporting && TIMER_READING_READY && queue_is_empty())
+ {
+ print_tcb0_timer_period();
+ TIMER_READING_READY = false;
+ }
+ }
+}
+
+void handle_character_instructions()
+{
+
+}
+
+void CLOCK_init (void)
+{
+ /* Do not use low frequency clock, disable CLK_PER Prescaler */
+ ccp_write_io( (void *) &CLKCTRL.MCLKCTRLB , (0 << CLKCTRL_PEN_bp));
+ /* If set from the fuses during programming, the CPU will now run at 20MHz (default is /6) */
+}
+
+void configure_EVSYS()
+{
+ EVSYS.CHANNEL4 = EVSYS_GENERATOR_PORT0_PIN3_gc; // Select PE3 as event generator
+ EVSYS.USERTCB0 = EVSYS_CHANNEL_CHANNEL4_gc; // Select TCB0 as channel4 user
+}
+
+static void configure_USART3(void)
+{
+ // Assign the USART3 transmit complete interrupt high priority (1)
+ CPUINT.LVL1VEC = USART3_TXC_vect_num;
+
+ PORTB.DIR &= ~PIN5_bm; /* this is the RX input */
+ PORTB.DIR |= PIN4_bm; /* this is the TX output */
+ USART3.BAUD = (uint16_t)USART3_BAUD_RATE(BAUD_RATE); // set the baud rate
+ USART3.CTRLB |= (USART_TXEN_bm | USART_RXEN_bm); // enable transmitter and receiver
+ PORTMUX.USARTROUTEA |= PORTMUX_USART3_ALT1_gc; // USART3 on PB[5:4]
+
+ USART3.CTRLA = USART_TXCIE_bm; // Transmit Complete Interrupt Enable
+}
+
+/************************************************************************/
+/* Enable and configure ADC0 */
+/************************************************************************/
+void configure_ADC0()
+{
+ ADC0.CTRLA = ADC_RESSEL_10BIT_gc | ADC_FREERUN_bm; // Set 10 bit resolution and free run mode
+ ADC0.CTRLC = ADC_SAMPCAP_bm | ADC_REFSEL_VDDREF_gc | ADC_PRESC_DIV64_gc; // Enable SAMPCAP, Set reference voltage to VDD, Set ADC prescaler to div 128
+ ADC0.MUXPOS = ADC_MUXPOS_AIN3_gc; // Set input to Analog in 3
+ ADC0.INTCTRL = ADC_RESRDY_bm; // Enable interrupt on result ready
+ ADC0.CTRLD = ADC_INITDLY_DLY0_gc; // Initial delay of 0 cycles
+ ADC0.CTRLA |= ADC_ENABLE_bm; // Enable ADC
+ ADC0.COMMAND = ADC_STCONV_bm; // Start first measurement
+}
+
+/************************************************************************/
+/* Enable and configure TCB0 for PW measurement */
+/************************************************************************/
+void configure_TCB0()
+{
+ TCB0.CTRLA = TCB_CLKSEL_CLKDIV2_gc | TCB_ENABLE_bm; // Select CLK_PER/2 source and enable
+ TCB0.CTRLB = TCB_CNTMODE_FRQPW_gc; // Set mode to PW measurment mode
+ TCB0.INTCTRL = TCB_CAPT_bm; // Capture Interrupt Enable
+ TCB0.EVCTRL = (1<<TCB_EDGE_bp) | (1<<TCB_CAPTEI_bp); // Event on falling edge & enable capture event input
+}
+
+/*this function loads the queue and */
+/*starts the sending process*/
+void sendmsg (char *s)
+{
+ uint16_t msg_len = 0;
+ char *temp = s;
+
+ while (*temp)
+ {
+ temp++;
+ msg_len++;
+ }
+
+ uint8_t available_space = (sndcntr > qcntr) ? (sndcntr - qcntr - 1) : (QUEUE_SIZE - qcntr + sndcntr - 1);
+
+ if (msg_len > available_space) // prevent it from adding any more to the buffer if its full.
+ return;
+
+ // Send message with circular buffer
+ while (*s)
+ {
+ // Prevent new message from overwriting data in queue
+ if (((qcntr + 1) % QUEUE_SIZE) != sndcntr)
+ {
+ queue[qcntr] = *s++;
+ qcntr = (qcntr + 1) % QUEUE_SIZE; // wrap around after hitting pos QUEUE_SIZE
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ if (qcntr != sndcntr && !(USART3.STATUS & USART_TXCIF_bm))
+ {
+ USART3.TXDATAL = queue[sndcntr];
+ sndcntr = (sndcntr + 1) % QUEUE_SIZE;
+ }
+}
+
+bool queue_is_empty()
+{
+ return (qcntr == sndcntr);
+}
+
+/************************************************************************/
+/* Allows printing of ADC information */
+/************************************************************************/
+
+// Print the current ADC volatage reading (mV)
+void print_adc_voltage()
+{
+ char str_buffer[16];
+ uint32_t adc_reading_mv;
+ adc_reading_mv = ((uint32_t)ADC_VALUE * 5000) / 1023;
+ sprintf(str_buffer, "ADC0 = %dmV\n", (uint16_t)adc_reading_mv);
+ sendmsg(str_buffer);
+}
+
+// Print the current ADC value (0-1024)
+void print_adc_value()
+{
+ char str_buffer[16];
+ sprintf(str_buffer, "ADC0 = %d\n", ADC_VALUE);
+ sendmsg(str_buffer);
+}
+
+/************************************************************************/
+/* Printing of 555 timer information */
+/************************************************************************/
+void print_tcb0_high_pulse()
+{
+ char str_buffer[16];
+ sprintf(str_buffer, "Low = %dmS\n", TCB0_LOW_PULSE);
+ sendmsg(str_buffer);
+}
+
+void print_tcb0_low_pulse()
+{
+ char str_buffer[16];
+ sprintf(str_buffer, "High = %dmS\n", TCB0_HIGH_PULSE);
+ sendmsg(str_buffer);
+}
+
+void print_tcb0_timer_period()
+{
+ char str_buffer[16];
+ sprintf(str_buffer, "Timer = %dmS\n", TCB0_PERIOD);
+ sendmsg(str_buffer);
+}
+
+// Print the available commands
+void print_available_commands()
+{
+ char str_buffer[256];
+ sprintf(
+ str_buffer,
+ "Undefined instruction.\n"
+ "A/a = ADC value.\n"
+ "V/v = ADC voltage reading (mV).\n"
+ "M/m = Continuous ADC reporting (mV).\n"
+ "N/n = Stop ADC reporting.\n"
+ );
+ sendmsg(str_buffer);
+
+ _delay_ms(50);
+
+ sprintf(
+ str_buffer,
+ "T/t = Report timer period (mS)\n"
+ "L/l = Report low pulse\n"
+ "H/h = Report high pulse\n"
+ "C/c Continuously report timer period\n"
+ "E/e Stop continuously reporting timer period\n"
+ );
+ sendmsg(str_buffer);
+}
+
+ISR(USART3_TXC_vect)
+{
+ // Clear the transmit complete flag
+ USART3.STATUS |= USART_TXCIF_bm;
+
+ // Continue sending if there is data in the queue
+ if (sndcntr != qcntr)
+ {
+ // Load the next character from the queue
+ USART3.TXDATAL = queue[sndcntr];
+ sndcntr = (sndcntr + 1) % QUEUE_SIZE; // Move sndcntr forward with wrap-around
+ }
+}
+
+/************************************************************************/
+/* ADC0 result ready ISR (handle voltage reading measurements) */
+/************************************************************************/
+ISR(ADC0_RESRDY_vect)
+{
+ ADC_READING_READY = true; // Set flag informing that new voltage value is ready
+ ADC_VALUE = ADC0.RES; // Store the reading of the ADC result in a global variable
+ ADC0.INTFLAGS = ADC_RESRDY_bm; // Clear interrupt flag
+}
+
+
+ISR(TCB0_INT_vect)
+{
+ TCB0.INTFLAGS = 1; // Reset interrupt flag
+ uint16_t ccmp = TCB0.CCMP;
+ uint16_t cnt = TCB0.CNT;
+
+ // 1 / 1MHz = 0.1uS per tick
+ TCB0_PERIOD = 0.1 * cnt; // calculate timer period in uS
+
+ TCB0_LOW_PULSE = 0.1 * ccmp;
+ TCB0_HIGH_PULSE = 0.1 * (cnt - ccmp);
+
+ TIMER_READING_READY = true;
+}
+\ No newline at end of file
diff --git a/Project2/Project2/main.c b/Project2/Project2/main.c
@@ -54,6 +54,9 @@ uint16_t TCB0_HIGH_PULSE;
uint16_t TCB0_LOW_PULSE;
uint16_t TCB0_PERIOD;
+/* Servo related globals */
+uint8_t SERVO_SPEED;
+
/************************************************************************/
/* Function declarations */
@@ -63,6 +66,7 @@ static void configure_USART3(void);
void sendmsg (char *s);
void configure_ADC0(void);
void configure_EVSYS();
+void configure_TCA0();
void configure_TCB0(void);
void configure_TCB1(void);
void configure_TCB2(void);
@@ -93,6 +97,7 @@ int main(void)
configure_USART3();
configure_TCB2();
configure_ADC0();
+ configure_TCA0();
sei(); /* Enable Global Interrupts */
@@ -105,9 +110,10 @@ int main(void)
// if received character is a digit
if (isdigit(ch))
{
- char str_buffer[16];
- sprintf(str_buffer, "received %c", ch);
+ char str_buffer[32];
+ sprintf(str_buffer, "Set Servo speed to %c", ch);
sendmsg(str_buffer);
+ SERVO_SPEED = ch - '0'; // Convert to integer representation in ASCII
}
else
{
@@ -245,7 +251,7 @@ void configure_TCB1()
TCB1.CTRLA = TCB_CLKSEL_CLKDIV2_gc | TCB_ENABLE_bm; // set clock source to CLP_PER / 2 and enable
TCB1.CTRLB = TCB_CNTMODE_TIMEOUT_gc; // Enable timeout check mode
TCB1.INTCTRL = TCB_CAPT_bm; // Capture Interrupt Enable
- TCB1.CCMP = 65000;
+ TCB1.CCMP = 65535;
TCB1.EVCTRL = (1<<TCB_EDGE_bp) | (1<<TCB_CAPTEI_bp); // Event on falling edge & enable capture event input
}
@@ -260,6 +266,17 @@ void configure_TCB2()
TCB2.CCMP = 50000; // (5*10^-3)/(1/(10*10^6)) = 50000 -> 5mS
}
+void configure_TCA0()
+{
+ TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV16_gc; // Set clock to clock peripheral with div 16 prescaler
+ TCA0.SINGLE.CTRLB = TCA_SINGLE_CMP0EN_bm | TCA_SINGLE_WGMODE_SINGLESLOPE_gc; // W0-0 output and single slope PWM mode
+ TCA0.SINGLE.PER = 24999; // Set to 50Hz PWM frequency, 20mS
+ TCA0.SINGLE.CMP0 = 1250; // Set to 1mS, -90 deg
+ TCA0.SINGLE.CTRLA |= TCA_SINGLE_ENABLE_bm; // Enable TCA0
+ PORTA.DIRSET = PIN0_bm; // Set PA0 as output
+
+}
+
/*this function loads the queue and */
/*starts the sending process*/
void sendmsg (char *s)
@@ -428,5 +445,6 @@ ISR(TCB1_INT_vect)
ISR(TCB2_INT_vect)
{
- //TODO
+ uint16_t servo_pos = 1250 + ((1250 * (uint16_t)SERVO_SPEED)/10);
+ TCA0.SINGLE.CMP0BUF = servo_pos;
}
\ No newline at end of file