Переключение 2 светодиодов с помощью таймера XMEGA-A3BU Xplained

В настоящее время я прохожу онлайн-курс по встроенному программированию. Это было наше четвертое задание по программированию. Устройство Microchip (Atmel) XMEGA-A3BU Xplained Development Kit, Mfr. Номер детали: ATXMEGAA3BU-XPLD, краткое название Atxmega256A3BU. Устройство имеет 8-битную шину данных и 16-битную адресную шину. Я занимаюсь разработкой под Windows 10 в Microchip Studio 7 (очень убогий клон Visual Studio). Я строю код без оптимизации. Оптимизатор имеет привычку оптимизировать мои циклы синхронизации.

ATXMEGAA3BU-XPLD

Назначение состоит в том, чтобы чередовать 2 светодиода, и если одна из кнопок нажата, изменить поведение, чтобы загорелся светодиод, связанный с этой кнопкой. Текущий светодиод 0 связан с кнопкой SW1, а светодиод 1 связан с кнопкой SW2. Мы должны использовать таймер TCC0 для переключения между двумя светодиодами. Мой вопрос в дополнение к общему обзору заключается в том, что код для чтения и записи на аппаратный адрес чаще всего похож на код в ReadReg() а также WriteReg() в коде ниже? Я спрашиваю об указателе, а не о volatile ключевое слово. я знаю volatile ключевое слово долгое время.

К вашему сведению, инструктор очень любит комментарии. Если вас интересует история изменений, я не использую GIT для этого класса.

Это поручение сдано.

reg_io_wrappers.h

/*
 * reg_io_wrappers.h
 *
 * Functions to read and write the hardware registers on the device.
 * Required for assignments from my class.
 *
 * Created: 4/27/2022 4:20:26 PM
 *  Author: pacmaninbw
 *
 * Change History:
 * 5/11/2022    Changed added include for stdint-gcc.h to reg_io_wrappers.h,
 *      changed input and output types to uint8_t. Easier to find and change
 *      than unsigned char, better representation.
 */ 

#ifndef REG_IO_WRAPPERS_H_
#define REG_IO_WRAPPERS_H_

#include <stdint-gcc.h>

extern  void WriteReg(uint16_t RegAddress, uint8_t Value);
extern  uint8_t ReadReg(uint16_t RegAddress);

#endif /* REG_IO_WRAPPERS_H_ */

reg_io_wrappers.c

/*
 * reg_io_wrappers.c
 *
 * Functions to read and write the hardware registers on the device.
 * Required for assignments from my class.
 *
 * Created: 4/27/2022 4:08:13 PM
 *  Author: pacmaninbw
 *
 * Change History:
 * 5/11/2022    Changed added include for stdint-gcc.h to reg_io_wrappers.h,
 *      changed input and output types to uint8_t. Easier to find and change
 *      than unsigned char, better representation.
 */ 

#include "reg_io_wrappers.h"

/* Write data to a hardware register */
void WriteReg(uint16_t RegAddress, uint8_t Value)
{
    *((volatile unsigned char *)RegAddress) = Value;
}

 /* Read data from a hardware register */
uint8_t ReadReg(uint16_t RegAddress)
{
    // disable interrupts
    uint8_t return_val = *((volatile uint8_t *)RegAddress);
    // enable interrupts

    return return_val;
}

main.c

/**
 * Programming Assignment 4: Add timing using Clock to LED On/Off using switches
 *
 * 1. Create a code using Round Robin architecture to blink both LED’s when no
 *      switch is pressed like in previous assignment. 
 *
 * 2. When any switch is pressed, then only blink the LED(s) associated to the
 *      switch number. Use the SW1 switch to control LED 0 and SW2 switch to
 *      control LED 1.
 *
 * 3. Create a function that controls the blink operation.
 *
 * 4. The main task loop should check the switch setting and make decisions 
 *      whether to blink both LED's on at a time OR only blink the LEDS associated
 *      with the switches that are pressed.
 *
 * Change History
 * 05/11/2022 - Moved call to delay function from main round robin to service_leds().
 *      This will allow buttons to interrupt the delay loop and force an early
 *      from the service_leds() function.
 *      Added service_buttons() function.
 *      Added include for stdint-gcc.h so that uint8_t is defined, better than 
 *          putting unsigned char everywhere. Easier to modify if necessary.
 *
 * 05/14/2022 - 05/15/2022 - Converted the delay subroutine to use the
 *      TCC0 timer/counter from a timing loop.
 *
 * 05/16/2022 - Fixed bug in delay function, was not complementing the read back
 *      of the overflow flag. Moved the declaration of toggle into main(), there
 *      was no need for it to be global to the file.
 */

 #include <stdint-gcc.h>

/* Include the hardware address macros. */
#include "devreg.h"
/*
 * The I/O read and write wrapper functions are in a separate file, they will rarely
 * need to be recompiled.
 */
#include "reg_io_wrappers.h"

/*
 * Not including stdbool.h, using K & R solution instead.
 */
#define TRUE 1
#define FALSE 0

#if 0
/*
 * Used for debugging purposes.
 */
static void lightRedLED(void)
{
    uint8_t pin4 = 0x01 << 4;
    WriteReg(PORTD_DIR_REG, pin4);
    WriteReg(PORTD_OUTTGL_REG, ~pin4);
}
#endif

/*
 * Non-interrupt service function.
 * First determine if a button has been pushed, then determine which button
 * it is. Set the global button pushed flag.
 *
 * The button read back is in bits 1 and 2 (pins 1 and 2). Since the toggle
 * is an index into an array this needs to change to bits 0 and 1 so right
 * shift 1 bit. Since the pins are low when the button is pushed we need to
 * get the complement to find the proper value.
 */
static uint8_t service_buttons(uint8_t toggle)
{
    /*  Set PORTF direction to input
     */
    WriteReg(BUTTON_ENABLE_REG, ENABLE_BUTTON_INPUT);
    uint8_t current_pin_read = ReadReg(BUTTON_VALUE_REG);   /* Read back if any buttons are pushed. */
    current_pin_read = current_pin_read >> 1;
    current_pin_read = ~current_pin_read & (uint8_t)0x03;

    /*
     * If no buttons are currently pushed return the current toggle value.
     */
    current_pin_read = (current_pin_read)? current_pin_read : toggle;
    return current_pin_read;
}

    /*
     * Keep each display state to approximately 1 to 2 seconds. Hopefully 1000
     * loop executions equals 1 second. The delay function will be terminated
     * if the button status has changed. Use the TCC0 timer for the delay.
     */
static void DelayUsingTCC0(uint8_t requestedDelay)
{
    /*
     * As per the instructions for this weeks lab,
     * 1) Set CTRLA for prescaling divide by 1024
     * 2) Since we want the counter to overflow based on only the 8 bits in
     *      the low counter register, set the high count register to all high.
     * 3) Set the desired delay value in the lower 8 bits of the counter.
     * 4) Make sure the overflow interrupt bit is cleared.
     * 5) Poll the clock interrupt flags to check for overflow conditions.
     */
    WriteReg(TCC0_INTFLAGS, CLEAR_OVFIF);
    WriteReg(TCC0_CTRLA, CLK_CTRL_OPTS_DIV1024);
    WriteReg(TCC0_COUNT_HI, 0xFF);
    /*
     * Subtract the desired delay from the overflow value for the lower
     * 8 bits of the timer/counter. The overflow should occur when the
     * lower 8 bits reaches 0xFF.
     */
    WriteReg(TCC0_COUNT_LOW, 0xFF - requestedDelay);

    /*
     * When overflow occurs the overflow bit in the interrupt register goes low.
     * Poll the interrupt flags for overflow.
     */
    volatile uint8_t noOverflow = TRUE;
    while(noOverflow)
    {
        volatile uint8_t overFlowCheck = ReadReg(TCC0_INTFLAGS);
        noOverflow = ~overFlowCheck & OVFIF_MASK;
    }

    /* Make sure there are not interrupts for the rest of the program. */
    WriteReg(TCC0_INTFLAGS, CLEAR_OVFIF);
}

/*
 * Non-interrupt service function.
 *
 * Toggle the LEDs. First LED 0 then LED 1. 
 */
#define PIN0 (uint8_t) 0x01
#define PIN1 (uint8_t) 0x02
#define PINS_0_AND_1 (uint8_t) 0x03
static void service_leds(uint8_t pin_index)
{
    uint8_t pins[] = {PIN0, PIN1, PINS_0_AND_1};
    uint8_t pin = pins[pin_index];

    WriteReg(LED_ENABLE_REG, pin);  /* Enable output on the specific pin(s) */
    WriteReg(LED_TOGGLE_REG, ~pin); /* Drive the pin low. */

    DelayUsingTCC0(250);
    WriteReg(LED_ENABLE_REG, 0);    /* Disable output on the pin */
}

int main (void)
{
    uint8_t toggle = 0;

    while (TRUE)
    {
    /* 
     * Simple Round Robin Architecture without interrupts.
     * Buttons are a higher priority than the LEDs because the buttons provide
     * user input and require a better response time. The buttons also change
     * the status of which LEDs to display.
     */
        toggle = service_buttons(toggle);
        service_leds(toggle);
        toggle = (toggle)? 0 : 1;
    }
}

devreg.h

/*
 * devreg.h
 *  Portable Register Addressing to allow the code using this file to port to
 *  other devices.
 *  Required for assignments from my class.
 *
 * Created: 4/27/2022 3:33:12 PM
 *  Author: pacmaninbw
 *
 * Change History
 * Created 4/27/2022.Currently only contains macros to program the PORTR
 *      functionality, to enable and disable the LEDs. To make the LED
 *      programming more portable LED macros were added to hide the PORTR
 *      implementation on the device.
 *
 * 5/1/2022. Changed the PORT Address macros implementation, Ports can be added
 *      by copy and paste, select region, find and replace base port register
 *      name.
 *
 * 5/8/2022 Added PORTF Macros.
 *
 * 5/10/2022 Converted LED constants to use the complement of decimal numbers
 *      rather than Hex values.
 *
 * 5/14/2022 Added clock address offsets, CLOCK_TCC0 and the clock prescaler values
 *      for clock TCC0.
 *
 * 5/16/2022 Added Port D addresses, corrected copy paste errors in Ports B & C.
 *      Attempting to use red LED for debugging.
 */ 

#ifndef DEVREG_H_
#define DEVREG_H_

/*
 * Clock Control Registers for TCC0
 */
#define CLOCK_TCC0                          0x0800
#define CLOCK_CTRLA_OFFSET                  0x00
#define CLOCK_CTRLB_OFFSET                  0x01
#define CLOCK_CTRLC_OFFSET                  0x02
#define CLOCK_CTRLD_OFFSET                  0x03
#define CLOCK_CTRLE_OFFSET                  0x04
#define CLOCK_INTERRUPT_CTRLA_OFFSET        0x06
#define CLOCK_INTERRUPT_CTRLB_OFFSET        0x07
#define CLOCK_CTRLF_CLEAR_OFFSET            0X08
#define CLOCK_CTRLF_SET_OFFSET              0X09
#define CLOCK_CTRLG_CLEAR_OFFSET            0X0A
#define CLOCK_CTRLG_SET_OFFSET              0X0B
#define CLOCK_INTERRUPT_FLAGS_OFFSET        0X0C
#define CLOCK_COUNTER_LOW_OFFSET            0X20
#define CLOCK_COUNTER_HI_OFFSET             0X21

/*
 * Adding Clock Offsets
 */
#define ADD_CLOCK_CTRLA_OFFSET(baseAddress) \
        (baseAddress + CLOCK_CTRLA_OFFSET)
#define ADD_CLOCK_CTRLB_OFFSET(baseAddress) \
        (baseAddress + CLOCK_CTRLB_OFFSET)
#define ADD_CLOCK_CTRLC_OFFSET(baseAddress) \
        (baseAddress + CLOCK_CTRLC_OFFSET)
#define ADD_CLOCK_CTRLD_OFFSET(baseAddress) \
        (baseAddress + CLOCK_CTRLD_OFFSET)
#define ADD_CLOCK_CTRLE_OFFSET(baseAddress) \
        (baseAddress + CLOCK_CTRLE_OFFSET)
#define ADD_CLOCK_INTERRUPT_CTRLA_OFFSET(baseAddress)   \
        (baseAddress + CLOCK_INTERRUPT_CTRLA_OFFSET)
#define ADD_CLOCK_INTERRUPT_CTRLB_OFFSET(baseAddress)   \
        (baseAddress + CLOCK_INTERRUPT_CTRLB_OFFSET)
#define ADD_CLOCK_CTRLF_CLEAR_OFFSET(baseAddress)   \
        (baseAddress + CLOCK_CTRLF_CLEAR_OFFSET)
#define ADD_CLOCK_CTRLF_SET_OFFSET(baseAddress) \
        (baseAddress + CLOCK_CTRLF_SET_OFFSET)
#define ADD_CLOCK_CTRLG_CLEAR_OFFSET(baseAddress)   \
        (baseAddress + CLOCK_CTRLG_CLEAR_OFFSET)
#define ADD_CLOCK_CTRLG_SET_OFFSET(baseAddress) \
        (baseAddress + CLOCK_CTRLG_SET_OFFSET)
#define ADD_CLOCK_INTERRUPT_FLAGS_OFFSET(baseAddress)   \
        (baseAddress + CLOCK_INTERRUPT_FLAGS_OFFSET)
#define ADDCLOCK_COUNTER_LOW_OFFSET(baseAddress)    \
        (baseAddress + CLOCK_COUNTER_LOW_OFFSET)
#define ADD_CLOCK_COUNTER_HI_OFFSET(baseAddress)    \
        (baseAddress + CLOCK_COUNTER_HI_OFFSET)

/*
 * Clock Register Addresses
 */

/*
 * TCC0 Clock Control Register Addresses
 */
#define TCC0_CTRLA      ADD_CLOCK_CTRLA_OFFSET(CLOCK_TCC0)
#define TCC0_CTRLB      ADD_CLOCK_CTRLB_OFFSET(CLOCK_TCC0)
#define TCC0_CTRLC      ADD_CLOCK_CTRLC_OFFSET(CLOCK_TCC0)
#define TCC0_CTRLD      ADD_CLOCK_CTRLD_OFFSET(CLOCK_TCC0)
#define TCC0_CTRLE      ADD_CLOCK_CTRLE_OFFSET(CLOCK_TCC0)
#define TCC0_CTRLA_INT  ADD_CLOCK_INTERRUPT_CTRLA_OFFSET(CLOCK_TCC0)
#define TCC0_CTRLB_INT  ADD_CLOCK_INTERRUPT_CTRLB_OFFSET(CLOCK_TCC0)
#define TCCO_CTRLF_CLR  ADD_CLOCK_CTRLF_CLEAR_OFFSET(CLOCK_TCC0)
#define TCC0_CTRLF_SET  ADD_CLOCK_CTRLF_SET_OFFSET(CLOCK_TCC0)
#define TCCO_CTRLG_CLR  ADD_CLOCK_CTRLG_CLEAR_OFFSET(CLOCK_TCC0)
#define TCC0_CTRLG_SET  ADD_CLOCK_CTRLG_SET_OFFSET(CLOCK_TCC0)
#define TCC0_INTFLAGS   ADD_CLOCK_INTERRUPT_FLAGS_OFFSET(CLOCK_TCC0)
#define TCC0_COUNT_LOW  ADDCLOCK_COUNTER_LOW_OFFSET(CLOCK_TCC0)
#define TCC0_COUNT_HI   ADD_CLOCK_COUNTER_HI_OFFSET(CLOCK_TCC0)

/*
 * Clock Control Options and Prescaler Settings
 */
#define CLK_CTRL_OPTS_OFF       (uint8_t) 0X00
#define CLK_CTRL_OPTS_DIV1      (uint8_t) 0X01
#define CLK_CTRL_OPTS_DIV2      (uint8_t) 0X02
#define CLK_CTRL_OPTS_DIV4      (uint8_t) 0X03
#define CLK_CTRL_OPTS_DIV8      (uint8_t) 0X04
#define CLK_CTRL_OPTS_DIV64     (uint8_t) 0X05
#define CLK_CTRL_OPTS_DIV256    (uint8_t) 0X06
#define CLK_CTRL_OPTS_DIV1024   (uint8_t) 0X07

/*
 * Other Clock Control settings
 */
#define CLEAR_OVFIF             (uint8_t) 0x01
#define OVFIF_MASK              (uint8_t) 0x01

// LED control values
// For this device there are 2 LEDs, so there are 4 LED states, all LEDs off,
// all LEDs on, LED 0 on, LED 1 on.
// The yellow LEDs are controlled by pins 0 and 1 in PORTR. The LED controlled
// by pin 0 will be called LED 0 and the LED controlled by pin 1 will be called
// LED 1. When driving low on either pin 0 or pin 1 the associated LED will
// light up.
#define ALL_LEDS_OFF        (uint8_t) ~0x00
#define ALL_LEDS_ON         (uint8_t) ~0x03
#define LED_0_ON            (uint8_t) ~0x01
#define LED_1_ON            (uint8_t) ~0x02
#define LED_MAX_STATES      4
#define LED_STATE_MASK      0x03 /* The 2 LSBs in the PORT R register */
#define ENABLE_ALL_LEDS     (uint8_t) 0x03
#define TURN_OFF_ALL_LEDS   (uint8_t) 0x03

#define PORT_DIR_OUTPUT     0xFF /* Page 148 of the Manual Bits 0 and 1 output */
#define PORT_DIR_INPUT      0xFC /* Bits 0 and 1 input */

// Device Port Address Offsets
// These offsets are documented on page 160 of the manual
// Atmel-8331-8-and-16-bit-AVR-Microcontroller-XMEGA-AU_Manual.pdf
#define PORT_DIR_OFFSET     0x00
#define PORT_DIRSET_OFFSET  0x01
#define PORT_DIRCLR_OFFSET  0X02
#define PORT_DIRTGL_OFFSET  0X03
#define PORT_OUT_OFFSET     0x04
#define PORT_OUTSET_OFFSET  0x05
#define PORT_OUTCLR_OFFSET  0x06
#define PORT_OUTTGL_OFFSET  0x07
#define PORT_IN_OFFSET      0x08

// Adding Device Port Offsets
#define ADD_PORT_DIR_OFFSET(baseAddress)    \
         (baseAddress + PORT_DIR_OFFSET)
#define ADD_PORT_DIRSET_OFFSET(baseAddress) \
        (baseAddress + PORT_DIRSET_OFFSET)
#define ADD_PORT_DIRCLR_OFFSET(baseAddress) \
        (baseAddress + PORT_DIRCLR_OFFSET)
#define ADD_PORT_DIRTGL_OFFSET(baseAddress) \
        (baseAddress + PORT_DIRTGL_OFFSET)
#define ADD_PORT_OUT_OFFSET(baseAddress)    \
        (baseAddress + PORT_OUT_OFFSET)
#define ADD_PORT_OUTSET_OFFSET(baseAddress) \
        (baseAddress + PORT_OUTSET_OFFSET)
#define ADD_PORT_OUTCLR_OFFSET(baseAddress) \
        (baseAddress + PORT_OUTCLR_OFFSET)
#define ADD_PORT_OUTTGL_OFFSET(baseAddress) \
        (baseAddress + PORT_OUTTGL_OFFSET)
#define ADD_PORT_IN_OFFSET(baseAddress)     \
        (baseAddress + PORT_IN_OFFSET)

/* PORT R Device Addresses */
#define PORTR_BASE_ADDRESS  0x07E0
#define PORTR_DIR_REG       ADD_PORT_DIR_OFFSET(PORTR_BASE_ADDRESS)
#define PORTR_DIRSET_REG    ADD_PORT_DIRSET_OFFSET(PORTR_BASE_ADDRESS)
#define PORTR_DIRCLR_REG    ADD_PORT_DIRCLR_OFFSET(PORTR_BASE_ADDRESS)
#define PORTR_DIRTGL_REG    ADD_PORT_DIRTGL_OFFSET(PORTR_BASE_ADDRESS)
#define PORTR_OUT_REG       ADD_PORT_OUT_OFFSET(PORTR_BASE_ADDRESS)
#define PORTR_OUTSET_REG    ADD_PORT_OUTSET_OFFSET(PORTR_BASE_ADDRESS)
#define PORTR_OUTCLR_REG    ADD_PORT_OUTCLR_OFFSET(PORTR_BASE_ADDRESS)
#define PORTR_OUTTGL_REG    ADD_PORT_OUTTGL_OFFSET(PORTR_BASE_ADDRESS)
#define PORTR_IN_REG        ADD_PORT_IN_OFFSET(PORTR_BASE_ADDRESS)

/* PORT F Device Addresses */
#define PORTF_BASE_ADDRESS  0x06A0
#define PORTF_DIR_REG       ADD_PORT_DIR_OFFSET(PORTF_BASE_ADDRESS)
#define PORTF_DIRSET_REG    ADD_PORT_DIRSET_OFFSET(PORTF_BASE_ADDRESS)
#define PORTF_DIRCLR_REG    ADD_PORT_DIRCLR_OFFSET(PORTF_BASE_ADDRESS)
#define PORTF_DIRTGL_REG    ADD_PORT_DIRTGL_OFFSET(PORTF_BASE_ADDRESS)
#define PORTF_OUT_REG       ADD_PORT_OUT_OFFSET(PORTF_BASE_ADDRESS)
#define PORTF_OUTSET_REG    ADD_PORT_OUTSET_OFFSET(PORTF_BASE_ADDRESS)
#define PORTF_OUTCLR_REG    ADD_PORT_OUTCLR_OFFSET(PORTF_BASE_ADDRESS)
#define PORTF_OUTTGL_REG    ADD_PORT_OUTTGL_OFFSET(PORTF_BASE_ADDRESS)
#define PORTF_IN_REG        ADD_PORT_IN_OFFSET(PORTF_BASE_ADDRESS)

/* PORT A Device Addresses */
#define PORTA_BASE_ADDRESS  0x0600
#define PORTA_DIR_REG       ADD_PORT_DIR_OFFSET(PORTA_BASE_ADDRESS)
#define PORTA_DIRSET_REG    ADD_PORT_DIRSET_OFFSET(PORTA_BASE_ADDRESS)
#define PORTA_DIRCLR_REG    ADD_PORT_DIRCLR_OFFSET(PORTA_BASE_ADDRESS)
#define PORTA_DIRTGL_REG    ADD_PORT_DIRTGL_OFFSET(PORTA_BASE_ADDRESS)
#define PORTA_OUT_REG       ADD_PORT_OUT_OFFSET(PORTA_BASE_ADDRESS)
#define PORTA_OUTSET_REG    ADD_PORT_OUTSET_OFFSET(PORTA_BASE_ADDRESS)
#define PORTA_OUTCLR_REG    ADD_PORT_OUTCLR_OFFSET(PORTA_BASE_ADDRESS)
#define PORTA_OUTTGL_REG    ADD_PORT_OUTTGL_OFFSET(PORTA_BASE_ADDRESS)
#define PORTA_IN_REG        ADD_PORT_IN_OFFSET(PORTA_BASE_ADDRESS)

/* PORT B Device Addresses */
#define PORTB_BASE_ADDRESS  0x0620
#define PORTB_DIR_REG       ADD_PORT_DIR_OFFSET(PORTB_BASE_ADDRESS)
#define PORTB_DIRSET_REG    ADD_PORT_DIRSET_OFFSET(PORTB_BASE_ADDRESS)
#define PORTB_DIRCLR_REG    ADD_PORT_DIRCLR_OFFSET(PORTB_BASE_ADDRESS)
#define PORTB_DIRTGL_REG    ADD_PORT_DIRTGL_OFFSET(PORTB_BASE_ADDRESS)
#define PORTB_OUT_REG       ADD_PORT_OUT_OFFSET(PORTB_BASE_ADDRESS)
#define PORTB_OUTSET_REG    ADD_PORT_OUTSET_OFFSET(PORTB_BASE_ADDRESS)
#define PORTB_OUTCLR_REG    ADD_PORT_OUTCLR_OFFSET(PORTB_BASE_ADDRESS)
#define PORTB_OUTTGL_REG    ADD_PORT_OUTTGL_OFFSET(PORTB_BASE_ADDRESS)
#define PORTB_IN_REG        ADD_PORT_IN_OFFSET(PORTB_BASE_ADDRESS)

/* PORT C Device Addresses */
#define PORTC_BASE_ADDRESS  0x0640
#define PORTC_DIR_REG       ADD_PORT_DIR_OFFSET(PORTC_BASE_ADDRESS)
#define PORTC_DIRSET_REG    ADD_PORT_DIRSET_OFFSET(PORTC_BASE_ADDRESS)
#define PORTC_DIRCLR_REG    ADD_PORT_DIRCLR_OFFSET(PORTC_BASE_ADDRESS)
#define PORTC_DIRTGL_REG    ADD_PORT_DIRTGL_OFFSET(PORTC_BASE_ADDRESS)
#define PORTC_OUT_REG       ADD_PORT_OUT_OFFSET(PORTC_BASE_ADDRESS)
#define PORTC_OUTSET_REG    ADD_PORT_OUTSET_OFFSET(PORTC_BASE_ADDRESS)
#define PORTC_OUTCLR_REG    ADD_PORT_OUTCLR_OFFSET(PORTC_BASE_ADDRESS)
#define PORTC_OUTTGL_REG    ADD_PORT_OUTTGL_OFFSET(PORTC_BASE_ADDRESS)
#define PORTC_IN_REG        ADD_PORT_IN_OFFSET(PORTC_BASE_ADDRESS)

/* PORT D Device Addresses */
#define PORTD_BASE_ADDRESS  0x0660
#define PORTD_DIR_REG       ADD_PORT_DIR_OFFSET(PORTD_BASE_ADDRESS)
#define PORTD_DIRSET_REG    ADD_PORT_DIRSET_OFFSET(PORTD_BASE_ADDRESS)
#define PORTD_DIRCLR_REG    ADD_PORT_DIRCLR_OFFSET(PORTD_BASE_ADDRESS)
#define PORTD_DIRTGL_REG    ADD_PORT_DIRTGL_OFFSET(PORTD_BASE_ADDRESS)
#define PORTD_OUT_REG       ADD_PORT_OUT_OFFSET(PORTD_BASE_ADDRESS)
#define PORTD_OUTSET_REG    ADD_PORT_OUTSET_OFFSET(PORTD_BASE_ADDRESS)
#define PORTD_OUTCLR_REG    ADD_PORT_OUTCLR_OFFSET(PORTD_BASE_ADDRESS)
#define PORTD_OUTTGL_REG    ADD_PORT_OUTTGL_OFFSET(PORTD_BASE_ADDRESS)
#define PORTD_IN_REG        ADD_PORT_IN_OFFSET(PORTD_BASE_ADDRESS)

/*
 * Portable names for device registers does not require knowledge of device
 */
#define LED_ENABLE_REG      PORTR_DIR_REG           
#define LED_TOGGLE_REG      PORTR_OUTTGL_REG
#define ENABLE_LEDS         PORT_DIR_OUTPUT
#define DISABLE_LEDS        PORT_DIR_INPUT
#define LEDS_OFF_REG        PORTR_OUT_REG

/*
 * Buttons
 * The SW1 and SW2 button input is available on PORTF PIN1 and PIN2 respectively
 * as documented on page 10 of doc8394.pdf.
 */
 #define SW1_AND_SW2_ARE_PRESSED    (uint8_t) ~0x06     // Pins 1 and 2 are low
 #define SW1_IS_PRESSED             (uint8_t) ~0x02     // Pin 1 is low
 #define SW2_IS_PRESSED             (uint8_t) ~0x04     // Pin 2 is low
 #define BUTTON_ENABLE_REG          PORTF_DIR_REG
 #define BUTTON_VALUE_REG           PORTF_IN_REG
 #define ENABLE_BUTTON_INPUT        (uint8_t) ~0x06
 

#endif /* DEVREG_H_ */


  [1]: https://thefaq.ru/wp-content/uploads/2022/10/g0zPK.jpg

3 ответа
3

Оптимизатор имеет привычку оптимизировать мои циклы синхронизации.

Это (а) неудивительно и (б) что-то вроде красного флага, когда дело доходит до практики кодирования. Если вы пишете циклы синхронизации, которые пытаются использовать преимущества длительности инструкций для временных событий, ожидайте трудностей, если только вы не перейдете к ассемблеру (в обход компилятора и оптимизатора) или сделаете более разумную вещь и по возможности используете аппаратные таймеры.

Касательно ReadReg а также WriteReg. Не рассматривайте адреса как uint16_t. Вам нужно будет прочитать руководство по компилятору, чтобы исключить странное поведение, такое как модификаторы close/far и т. д., но, если это возможно, эти RegAddress должен быть актуальным * указатели. Вы бросили на volatile unsigned char *: проверьте, подходит ли вам этот размер, и если да, просто используйте его в качестве типа аргумента.

Подробнее о вышеизложенном: как это типично для микроконтроллеров, они имеют адресное пространство, которое в некоторых отношениях сложнее использовать, чем современные архитектуры настольных компьютеров. Читать главу 3 ЦП АВРособенно

Прямая адресация до 16 МБ памяти программ и 16 МБ памяти данных

Пространства памяти линейны. Пространство памяти данных и пространство памяти программы — это два разных пространства памяти.

Все регистры состояния ввода-вывода и управляющие регистры находятся в младших 4-килобайтных адресах памяти данных. Это называется пространством памяти ввода-вывода. Доступ к младшим 64 адресам можно получить напрямую или как расположение пространства данных от 0x00 до 0x3F. Остальное — это расширенное пространство памяти ввода-вывода в диапазоне от 0x0040 до 0x0FFF. Доступ к регистрам ввода-вывода здесь должен осуществляться как к адресам пространства данных с использованием инструкций загрузки (LD/LDS/LDD) и сохранения (ST/STS/STD).

и глава 4 Воспоминания

  • Память данных
    • Одно линейное адресное пространство
    • Одноцикловый доступ из ЦП
    • SRAM
    • ЭСППЗУ
      • Байт и страница доступны
      • Дополнительное отображение памяти для прямой загрузки и сохранения
    • память ввода/вывода
      • Регистры конфигурации и состояния для всех периферийных устройств и модулей
      • 16-битные регистры общего назначения для глобальных переменных или флагов

[…] Все области памяти являются линейными и не требуют переключения банков памяти.

Не все адреса одинаковы. Адреса в нижних 64 байтах используют механизм доступа, отличный от указанных выше. Читать этот раздел в GCC для доказательства того, что gcc специально обрабатывает AVR. Вообще говоря, gcc мог (хотя и не кажется) решил добавить near префикс для указателей на этот нижний сегмент и far для указателей на этот верхний сегмент — это сделано в других компиляторах для других архитектур. Поскольку я так не думаю, я призываю вас

  • Перепишите свои макросы, например #define PORTD_DIR_REG ((volatile unsigned char*)0x0660)
  • Избавься от своего ReadReg/WriteReg
  • Просто *PORTD_DIR_REG = 0x...;
  • Вручную проверьте скомпилированную сборку, чтобы убедиться, что она имеет смысл с учетом адреса назначения.

Я не понимаю комментарий // disable interrupts. Потому что.. ты этого не делаешь? Или это какая-то магия, которую компилятор делает для вас, чтобы обернуть volatile разыменования? Вы должны указать.

Set PORTF direction to input похоже на то, что нужно сделать один раз при инициализации, а затем оставить в покое.

    current_pin_read = (current_pin_read)? current_pin_read : toggle;
    return current_pin_read;

довольно неожиданный способ письма

    return current_pin_read || toggle;

Функция задержки будет прекращена, если состояние кнопки изменилось.

Циклы опроса — это крайняя мера. Надеюсь, ваша плата разработки подключила кнопку к контакту, поддерживающему аппаратное прерывание. На основе документация, контакты вашей кнопки — PE5, PF1 и PF2. В твоей руководство по микроконтроллеру глава 13.6, это показывает

Два прерывания порта с маскировкой контактов на порт ввода/вывода

Включите это аппаратное прерывание.

В срочном порядке вам нужно узнать, какая у вас тактовая частота (спросите своего профессора?). Тогда не будет ручных итераций, а одна установка таймера с одним истечением.

Вы утверждаете, что текущая задержка составляет где-то между 250-500 мс для вашего значения таймера 250. Это предполагает системные часы между

$$ 1024 * 250 / 0,250 = 1,024 \text{МГц} $$

а также

$$ 1024 * 250 / 0,500 = 512 \text{кГц} $$

Визуальный осмотр показывает только кварцевый резонатор в стиле RTC с частотой 32 кГц и никакого другого аппаратного генератора. Между этим и тем фактом, что внутренний генератор с частотой 2 МГц является источником синхронизации по умолчанию, используемым при запуске, есть вероятность, что ваш базовый генератор имеет частоту 2 МГц, изображенную в правом нижнем углу:

часы

Маловероятно, что XOSCSEL и PLLSRC установлены так, что ваши системные часы несколько кратны кристаллу 32 768 Гц. Коэффициент PLL 15-31 соответствовал бы задержке, которую вы видите, но опять же, если вы не выбрали это явно, вы работаете только на 2 МГц.

TC0 на рис. 14-1 показывает только вход clkPER, поэтому маловероятно, что системный предварительный делитель оказывает какое-либо влияние, что, в свою очередь, увеличивает вероятность того, что вы работаете на частоте 2 МГц или ниже. Вы должны попытаться зафиксировать это перед вашим следующим заданием.

По истечении одной секунды вы не можете использовать только нижнюю половину вашего таймера. Вам нужно использовать все 16 бит. Если вы добавите макрос, который вместо указания на uint8_tуказывает на uint16_t — и вы правильно понимаете порядок следования байтов, простое разыменование и назначение должно скомпилироваться правильно.

Предположим на данный момент, что ваши системные часы с частотой 2 МГц точны, с 16-разрядным счетчиком и доступными настройками предварительного делителя. область выполнимости предварительного делителя похоже:

прескалер

Таймер должен быть «установил и забыл». См. главу 14.8.1. Генерация сигналов — вывод должен быть автоматическим, и вы должны активировать переопределение порта только в том случае, если вы получаете прерывание кнопки.

Затем переведите ЦП в состояние ожидания или сна без циклов. По главе 8.2:

Когда устройство переходит в спящий режим, выполнение программы останавливается и прерывается, или для повторного пробуждения устройства используется сброс.

Портативная регистровая адресация, позволяющая коду, использующему этот файл, переноситься на другие устройства.

Я серьезно сомневаюсь в этом. Другие устройства могут иметь (и будут иметь) совершенно другое отображение. Что еще хуже, сопоставление внешнего оборудования (например, светодиодов и кнопок) с конкретными контактами зависит от конкретного устройства. доскано не к устройство.

Этот файл может улучшить читабельность, но он совсем не касается переносимости. Вы можете добавить еще один уровень абстракции, например, readButtonStatus(...), setLED(...)так далее.

Изменить, чтобы уточнить: вывод является свойством контроллера. Это бит X в порту Y. То, к какому аппаратному обеспечению подключен контакт, является свойством платы. Поддержка контроллера и поддержка платы — разные звери.


Кнопки имеют более высокий приоритет

Код не поддерживает это утверждение. service_leds звонки DelayUsingTCC0, который запускает цикл задержки до завершения, поэтому, пока задержка не завершится, кнопки не будут обслуживаться. В бесконечном цикле порядок вызовов не имеет значения.


Очень непонятно, как должно вести себя приложение, если нажаты обе кнопки. Если я правильно понимаю этот код, реализация предпочитает состояние PIN1. Возможно, это ожидаемое поведение, возможно, нет. В любом случае это должно быть задокументировано.

Делиться

Улучшить этот ответ

Используйте стандартную библиотеку

Похоже, вы написали много кода для работы с регистрами AVR. Возможно, требовалось, чтобы вы написали все с нуля, но было бы намного лучше, если бы вы могли использовать стандартную библиотеку для взаимодействия с микроконтроллерами AVR. Конечно, есть Arduino, но я рекомендую использовать AVR Libcкоторый кажется, входит в состав Atmel Studio 7. Выставляет все регистры вашего устройства, правильно сделанные volatileтак что вы можете написать:

#include <avr/io.h>

static void lightRedLED(void)
{
    PORTD_DIR = PIN4_bm;
    PORTD_OUTTGL = ~PIN4_bm;
}

Почему бы не использовать stdbool.h?

GCC для AVR поставляется с stdbool.hя не понимаю, почему вы определяете TRUE а также FALSE вручную? Нет закона против использования современных стандартов программирования, когда речь идет о встроенных устройствах, вам не нужно прибегать к K&R C.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *