CHAPTER 9 I/O and Interrupts 1. Data IO review This chapter covers: 1. • • • • • Review of DDR, PORT, PIN Polling/Interrupt handling Timers / Watchdogs PWM ADC Bräunl/Pham 2023 1 1 Set up IO directions: a. DDR(B/C/D) is a special register that stores an 8-bit value representing the directions for the pins on the corresponding port. (Most significant first). Outputs = 1. Inputs = 0. b. To set the register, place the 8-bit value you’d like to set inside a general purpose register (eg. R16). c. Then set the port to the corresponding value using OUT. eg. To set Port B to all inputs, except B4 and B0 which are outputs: LDI R16, 0b00010001 OR In C: DDRB = 0x11 OUT DDRB, R16 or DDRB = (1<<DDB4) | (1<<DDB0); 2. To set pins use OUT and PORT(B/C/D), after setting up DDR. eg. To set Pins 4 and 0 in port B to high (internal pull-up): LDI R16, 0b00010001 OR in C: PORTB 0x11 OUT PORTB, R16 or DDRB = (1<<PB4) | (1<<PB0); 3. To read from pins use IN and PIN(B/C/D) eg. To read from Pins 5 and 1 on port B: IN R16, PINB OR in C: int portB = PINB Bräunl/Pham 2023 2 Device Input 2. Polling Handle device without interrupt A data transfer can be 1. CPU-initiated (“polling”) 2. Device-initiated (“interrupt”) digital input line CPU CPU Bräunl/Pham 2023 3 2 device Task: Count pulses, e.g. controller: buttons robot: bumper switch camera: frame start signal “You need to be quick …” device 3 Bräunl/Pham 2023 4 4 1 Polling Polling Example Program: Count pulses (active low, use dig. input) Program is not correct! This technique is called “polling” because it may count single pulse twice or more! • Depending on controller speed • Controller may also miss a short pulse if too slow, but we assume for now this is not the case int main() { int counter = 0; while(1) { if (!(DIGITALRead(1)) counter++; } } Read input line 1 Check for value 0 We want to count the falling edges 1à0 Is this correct? Bräunl/Pham 2023 5 5 Bräunl/Pham 2023 6 6 Polling Polling Second Try: Count pulses (active low, use dig. input) Program should work now! int main() { int counter = 0, previous = 0, now = 1; while(1) { now = DIGITALRead(1); // read input line 1 if (!now && previous) counter++; previous = now; } if ( now ==0 && previous ==1 ) } We want to count the falling edges 1à 0 if ( now ==0 && previous ==1 ) n=1 n=0 n=1 n=1 n=0 n=0 n=1 n=0 n=1 n=0 n=1 p=1 p=1 p=0 p=1 p=1 p=0 p=0 p=1 p=0 p=1 p=0 Is this correct? Bräunl/Pham 2023 7 7 Bräunl/Pham 2023 8 8 2 Polling 3. Interrupts Program should work now! We want to count the falling edges 1à 0 if ( now ==0 && previous ==1 ) n=1 n=0 n=1 n=1 n=0 n=0 n=1 n=0 n=1 n=0 n=1 p=1 p=1 p=0 p=1 p=1 p=0 p=0 p=1 p=0 p=1 p=0 Bräunl/Pham 2023 9 9 Bräunl/Pham 2023 10 Interrupts Interrupts • Execution of one program (user) is temporarily suspended for another program with higher priority (somewhat like a unscheduled subroutine call) Handle device with interrupt CPU IRQ1’ interrupt line • Sometimes also called exception device • Interrupts can be raised either by software (special CPU command) or by hardware (external signals linked to CPU interrupt lines) • Many embedded systems have interrupts that occur at regular time intervals (e.g. every 0.01s) Þ timer interrupts • Controller has several interrupt lines with different priorities • Interrupts can come from internal sources (e.g. timer interrupt) or external sources (e.g. sensor via interrupt request line) Bräunl/Pham 2023 11 10 11 Bräunl/Pham 2023 12 12 3 Interrupts Interrupts • Often programmed in Assembly (time critical) Input/Output can be • Special return operation required to clear interrupt register (acknowledging that interrupt has been handled), e.g. Atmel: RETI (return from interrupt) instead of RET (return from subroutine) • CPU-initiation à Polling – CPU initiates read/write with IN/OUT instruction – Timing relies only on CPU – May have to do this in loop in case device is not ready à “busy-wait loop” à loss of CPU time à inefficient • Often CPU registers need to be saved to / restored from stack (time consuming) • Each interrupt (e.g. interrupt line) has to be associated with the address of an interrupt service routine. This is done during system setup • Device-initiated à Interrupts – Device signals CPU that it is ready via special interrupt line – CPU interrupts whatever it was doing and calls special “interrupt service routine” (ISR) – CPU returns to previous task after finishing ISR (like subroutine) Bräunl/Pham 2023 13 13 Bräunl/Pham 2023 14 14 Interrupts Interrupts Example Program: Count pulses (use interrupt) Example Program: Count pulses (use interrupt) int main() { ??? return 0; } Step 1: Write routine that is called when an interrupt arrives Step 2: Associate interrupt with this routine (initialization) Step 3: Enable interrupt How would you do this? Unfortunately, this is where standard C/C++ stops – it does not have language constructs to deal with this. Therefore Þ Non-standard C commands or back to Assembly! Bräunl/Pham 2023 15 15 Bräunl/Pham 2023 16 16 4 Interrupt Example Interrupt Example Example: Create an interrupt when input button is pressed • Pin change interrupts: Select pin on chip • 4 Steps required to enable interrupts: • Read ATMega328P documentation! • Use interrupts to react to button press on ATMega line • Easier and more efficient than reading (polling) • Interrupts are machine-specific – even C programs with interrupts will not work on other processor! • E.g.: Pin-change interrupt for ATMega. When enabled, the status change (rising or falling edge) on the specified pin will raise an interrupt. Bräunl/Pham 2023 1. Pin Change Mask 2. Pin Change Interrupt Flag Register 3. Pin Change Interrupt Control Register 4. SEI (Global Set Interrupt Enable) 17 17 Bräunl/Pham 2023 18 18 Interrupt Example Interrupt Example • We want to use PIN C4 as interrupt line • C4 = PCINT12 Bräunl/Pham 2023 19 19 Bräunl/Pham 2023 20 20 5 Interrupt Example Bräunl/Pham 2023 21 Interrupt Example • Assembly Instructions SEI, CLI for enabling/disabling interrupts via status register Bräunl/Pham 2023 23 22 Bräunl/Pham 2023 24 22 23 ATMega328P Interrupt Vectors 21 Bräunl/Pham 2023 24 6 Interrupt Example ASM for Input C4 Interrupt Example ASM for Input C4 .include "m328Pdef.inc" JMP main ; 0: RESET Jump over vector table JMP err ; 4: ext Int0 JMP err ; 8: ext Int1 JMP err ; 12: PCINT0 Interrupt JMP intr ; 16: PCINT1 Interrupt _______________________________________________________________ .include "m328Pdef.inc" JMP main ; 0: RESET Jump over vector table JMP err ; 4: ext Int0 JMP err ; 8: ext Int1 JMP err ; 12: PCINT0 Interrupt JMP intr ; 16: PCINT1 Interrupt _______________________________________________________________ main: CLR R15 ... main: LDI OUT LDI OUT intr: INC R15 RETI ; count interrupt ; return from interrupt err: ; error: endless loop JMP err Bräunl/Pham 2023 CLR LDI OUT LDI OUT OUT R15 R16, 0x00 DDRC, R16 R16, 0xFF PORTC, R16 DDRB, R16 ; Init stack pointer to 0x08FF ; use R15 as counter ; C all input ; enable pull-up for all pins ; B all output ; PCMSK1 = 0x10 ; load PCMSK1 address in Z ; write 0x10 to PCMSK1 26 26 Interrupt Example Interrupt Example ASM for Input C4 ... LDI R16, 0x02 OUT PCIFR, R16 ; clear any pending inter. bank 1 LDI ZL, PCICR ST Z, R16 ; activate interrupts for bank 1 SEI ; global interrupt enable • Enable C4/PCINT12 interrupt on PCMSK1 (pin change mask 1) PCMSK1 = 0x10; // connection to C4 • Clear flags in PCIFR (external interrupt flag register) PCIFR = 0x02; // clear flag for PCMSK1 • Enable interrupt in PCICR (external interrupt mask register) PCICR = 0x02; //enable PCMSK1 loop: OUT PORTB, R15 ; output counter to led on B5 JMP loop ; endless loop ________________________________________________________________ intr: INC R15 RETI • Interrupt service routines for pin changes ISR(PCINT1_vect) { /* handle interrupts PCINT 15 ..8 */ ; count interrupt ; return from interrupt } err: JMP err Bräunl/Pham 2023 27 0x08 R16 0xFF R16 LDI R16, 0x10 LDI ZH, 0 LDI ZL, PCMSK1 ST Z, R16 ... Bräunl/Pham 2023 25 25 R16, SPH, R16, SPL, ; error: endless loop … ISR(PCINT0_vect) // not used here { /* handle interrupts PCINT 7 ..0 */ } 27 … Bräunl/Pham 2023 28 28 7 4. Timer Interrupt Example C for Input C4 #include <avr/io.h> #include <avr/interrupt.h> int count = 0; // init count int main (void) { DDRC = 0x00; PORTC = 0xFF; DDRB = 0xFF; • Most microcontrollers contain a number of timers that can be set by software. // all input // enable pull-up for all pins // all output, use LED at B5 • They can just increment register values or call interrupt service routines PCMSK1 = 0x10; PCIFR = 0x02; PCICR = 0x02; sei(); // // // // interrupt line C4 (PCINT12) clear any pending interrupt enable interrupt for pin-change-1 global interrupt enable while(1) PORTB = count; // endless loop – display count } ISR(PCINT1_vect) { count++; } // count interrupts Bräunl/Pham 2023 29 29 Bräunl/Pham 2023 30 Timer Watchdog Timer Timer OSAttachTimer(int scale, (*fct)(void)); // Add fct to 1000Hz/scale timer int // Remove fct from timer OSDetachTimer(Timer handle) A watchdog is a specialized timer/counter • It is initialized to a certain value and keeps counting down • If the watchdog counter reaches zero, an interrupt is raised • A correctly running program will reset the watchdog timer in regular intervals to its initial value, so no interrupt will occur If “scale” is 1, then 1000Hz is being used. For “scale” > 1, 1000Hz/scale is used, e.g. scale=10 à 100 Hz timer Bräunl/Pham 2023 31 30 31 Bräunl/Pham 2023 32 32 8 Watchdog Timer int count; main() { count=100; // reset OSAttachTimer(10, watchdog); … while(1) { count = 100; // reset /* main processing loop */ … } } Watchdog Timer void watchdog() /* 100Hz */ { count--; if (count<0) error(“..”); } Bräunl/Pham 2023 int count; main() { count=100; // reset OSAttachTimer(10, watchdog); … while(1) reset { count = 100; // reset /* main processing loop */ … } } 33 33 Bräunl/Pham 2023 34 34 Watchdog Timer PWM • PWM or Pulse Width Modulation is a useful technique for a digital microcontroller to output an analog value • A watchdog is a very common and very effective tool for fault detection • Can be used to detect hardware errors and software errors • Especially useful for if a program “hangs” • Can be implemented using blocking (direct IO) or non-blocking (timers/interrupts) • Interrupt or error routine called can reset system or restart individual task Bräunl/Pham 2023 35 void watchdog() /* 100Hz */ { count--; decrement if (count<0) error(“..”); } 35 Bräunl/Pham 2023 36 36 9 PWM Blocking - Assembly PWM Non-Blocking – Timers • 2x 8 bit timer/counters or 16 bit timer/counters, One method to create a PWM signal is to set the desired pin to high and then run a loop of commands (NOP) to delay the next line of code, before setting to low and repeating the process. – Timer 0 – 8 Bit – Timer 1 – 16 bit – Timer 2 – 8 bit 1.First thing you will need is the clock speed of the controller that you are using. For the Arduino nano (ATMEGA328P), the clock speed is 16MHz. • Control Registers 2.From this you can calculate the time needed for the signal to be high then create a loop that blocks the next execution a certain amount of cycles. • – TCCRXA, TCCRXB, TCCRXC controlling usage Timer/Counter register – TCNTX • Output compare/Input capture – OCRXA, OCRXB – ICRX • Timer interrupt registers – – Bräunl/Pham 2023 37 37 TIMSKX TIFRX Bräunl/Pham 2023 38 38 PWM –TCCR1A PWM – TCCR1B • ICNC1 - input capture noise cancel • ICES – input capture edge select Bräunl/Pham 2023 39 39 Bräunl/Pham 2023 40 40 10 PWM – Counter Bräunl/Pham 2023 PWM – Input/Output Comp 41 41 Bräunl/Pham 2023 42 PWM – Interrupt Mask PWM – Interrupt Flag ICIE – Input capture interrupt enable OCIE1B – Output capture B interrupt enable OCIE1A – Output capture A interrupt enable TOIE1 – Overflow Interrupt Enable Bräunl/Pham 2023 43 42 ICFE – Input capture interrupt flag OCIF1B – Output capture B interrupt flag OCIF1A – Output capture A interrupt flag TOV1 – Overflow Interrupt flag 43 Bräunl/Pham 2023 44 44 11 PWM – Assembly example PWM – Assembly example Timer2 LDS R16, TCCR1A ; Storing the current value of the timer register to R16 ORI R16, (1<<COM1A1)|(1<<WGM11) ; Sets COM1A1 and WGM11, leaving the rest STS TTCR1A, R16 ; Stores the new configuration back to the timer register LDS ORI STS R16, TTCR1B ; Storing the current value of the timer register R16, (1<<WGM13)|(1<<WGM12)|(1<<CS11)|(1<<CS10) ; setting pins TTCR1B, R16 ; Storing configuration These steps set your timers to : - Clear OC1A/B on match (set to low, for the low section of our PWM) - Mode 14: Fast PWM, with ICR1 as the top. - Prescaler of clk/64 Bräunl/Pham 2023 45 45 46 46 PWM – Assembly example PWM – Assembly example To set period, we can now set ICR1 to reflect the entire period LDI R17, HIGH(<Value of ICR1>) LDI R16, LOW(<Value of ICR1>) STS ICR1H, R17 STS ICR1L, R16 To set period, we can now set ICR1 to reflect the entire period LDI R17, HIGH(<Value of ICR1>) LDI R16, LOW(<Value of ICR1>) STS ICR1H, R17 STS ICR1L, R16 To set the high sections of our period we set OCR1A LDI R16, <Value of OCR1> LDI R17, <Value of OCR1> STS OCR1AH, R17 STS OCR1AL, R16 To set the high sections of our period we set OCR1A LDI R16, <Value of OCR1> LDI R17, <Value of OCR1> STS OCR1AH, R17 STS OCR1AL, R16 The calculation for the ICR/OCR (TOP): The calculation for the ICR/OCR (TOP): Bräunl/Pham 2023 47 Bräunl/Pham 2023 47 Bräunl/Pham 2023 48 48 12 ADC ADMUX – multiplexer select ADC = Analog to digital conversion • Allows digital microcontroller to convert an analog value to digital (reduced resolution) • Takes variable time depending on value • Setup using several registers, and result gets stored in a separate register Bräunl/Pham 2023 49 49 Bräunl/Pham 2023 50 50 ADCSRA– control and status ADCL/H– Result Register ADEN – ADC Enable ADSC – Start conversion ADATE – Auto trigger enable ADIF – Interrupt flag ADPS – Prescaler Bräunl/Pham 2023 51 51 Bräunl/Pham 2023 52 52 13 ADC Example - Setup ADC Example - Running 1. Set ADMUX and ADCSRA: LDI R16, (1<<REFS0)|(1<<ADLAR)|(0<<MUX3) STS ADMUX, R16 LDI R16, (1<<ADEN)|(1<<ADPS2|(1<<ADPS1)|(1<<ADPS0) STS ADCSRA, R16 This sets our reference to Vcc (should out a capacitor), Left adjust. Also sets MUX3-0 as 0, giving us ADC0 for use. Additionally, it sets the ADC control and status register A. This setting will set the enable, and select the prescaler mode (in this case, 128) 1. To run the ADC, you must set the start conversion pin (ADSC) on ADCSRA register LDS R16, ADCSRA ORI R16, (1<<ADSC) STS ADCSRA, R16 Bräunl/Pham 2023 2. As the conversion takes time, we must wait until the conversion is complete. This is signalled by bit 6 (ADSC), the start conversion pin becoming 0. So inside a loop we check that pin before using the number: adc_loop: LDS R16, ADCSRA SBRC R16, 6 ; could have used ADSC RJMP adc_loop 53 53 Bräunl/Pham 2023 54 54 ADC Example - Result 1. So if the conversion is complete, it will break out of the loop and we can now read the calue • The value is stored in ADCH (and ADCL). This value is dependant on if we selected most significant bit first or not (ADLAR from before). LDS R17, ADCH LDS R16, ADCL Bräunl/Pham 2023 55 55 14