diff options
Diffstat (limited to 'platforms/avr')
30 files changed, 2625 insertions, 34 deletions
| diff --git a/platforms/avr/_print.h b/platforms/avr/_print.h new file mode 100644 index 0000000000..5c1fdd26d8 --- /dev/null +++ b/platforms/avr/_print.h @@ -0,0 +1,33 @@ +/* Copyright 2012 Jun Wako <[email protected]> */ +/* Very basic print functions, intended to be used with usb_debug_only.c + * http://www.pjrc.com/teensy/ + * Copyright (c) 2008 PJRC.COM, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#pragma once + +#include "avr/xprintf.h" + +// Create user & normal print defines +#define print(s) xputs(PSTR(s)) +#define println(s) xputs(PSTR(s "\r\n")) +#define uprint(s) xputs(PSTR(s)) +#define uprintln(s) xputs(PSTR(s "\r\n")) +#define uprintf(fmt, ...) __xprintf(PSTR(fmt), ##__VA_ARGS__)
\ No newline at end of file diff --git a/platforms/avr/_timer.h b/platforms/avr/_timer.h new file mode 100644 index 0000000000..b81e0f68b7 --- /dev/null +++ b/platforms/avr/_timer.h @@ -0,0 +1,19 @@ +/* Copyright 2021 Simon Arlott + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ +#pragma once + +// The platform is 8-bit, so prefer 16-bit timers to reduce code size +#define FAST_TIMER_T_SIZE 16 diff --git a/platforms/avr/_wait.h b/platforms/avr/_wait.h new file mode 100644 index 0000000000..683db6ae57 --- /dev/null +++ b/platforms/avr/_wait.h @@ -0,0 +1,49 @@ +/* Copyright 2021 QMK + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <util/delay.h> + +#define wait_ms(ms)                             \ +    do {                                        \ +        if (__builtin_constant_p(ms)) {         \ +            _delay_ms(ms);                      \ +        } else {                                \ +            for (uint16_t i = ms; i > 0; i--) { \ +                _delay_ms(1);                   \ +            }                                   \ +        }                                       \ +    } while (0) +#define wait_us(us)                             \ +    do {                                        \ +        if (__builtin_constant_p(us)) {         \ +            _delay_us(us);                      \ +        } else {                                \ +            for (uint16_t i = us; i > 0; i--) { \ +                _delay_us(1);                   \ +            }                                   \ +        }                                       \ +    } while (0) +#define wait_cpuclock(n) __builtin_avr_delay_cycles(n) +#define CPU_CLOCK F_CPU + +/* The AVR series GPIOs have a one clock read delay for changes in the digital input signal. + * But here's more margin to make it two clocks. */ +#ifndef GPIO_INPUT_PIN_DELAY +#    define GPIO_INPUT_PIN_DELAY 2 +#endif + +#define waitInputPinDelay() wait_cpuclock(GPIO_INPUT_PIN_DELAY) diff --git a/platforms/avr/atomic_util.h b/platforms/avr/atomic_util.h new file mode 100644 index 0000000000..7c5d2e7dcc --- /dev/null +++ b/platforms/avr/atomic_util.h @@ -0,0 +1,22 @@ +/* Copyright 2021 QMK + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ +#pragma once + +/* atomic macro for AVR */ +#include <util/atomic.h> + +#define ATOMIC_BLOCK_RESTORESTATE ATOMIC_BLOCK(ATOMIC_RESTORESTATE) +#define ATOMIC_BLOCK_FORCEON ATOMIC_BLOCK(ATOMIC_FORCEON) diff --git a/platforms/avr/bootloader.c b/platforms/avr/bootloader.c new file mode 100644 index 0000000000..c0272903b8 --- /dev/null +++ b/platforms/avr/bootloader.c @@ -0,0 +1,293 @@ +#include <stdint.h> +#include <stdbool.h> +#include <avr/io.h> +#include <avr/eeprom.h> +#include <avr/interrupt.h> +#include <avr/wdt.h> +#include <util/delay.h> +#include "bootloader.h" +#include <avr/boot.h> + +#ifdef PROTOCOL_LUFA +#    include <LUFA/Drivers/USB/USB.h> +#endif + +/** \brief Bootloader Size in *bytes* + * + * AVR Boot section size are defined by setting BOOTSZ fuse in fact. Consult with your MCU datasheet. + * Note that 'Word'(2 bytes) size and address are used in datasheet while TMK uses 'Byte'. + * + * Size of Bootloaders in bytes: + *   Atmel DFU loader(ATmega32U4)   4096 + *   Atmel DFU loader(AT90USB128)   8192 + *   LUFA bootloader(ATmega32U4)    4096 + *   Arduino Caterina(ATmega32U4)   4096 + *   USBaspLoader(ATmega***)        2048 + *   Teensy   halfKay(ATmega32U4)   512 + *   Teensy++ halfKay(AT90USB128)   1024 + * + * AVR Boot section is located at the end of Flash memory like the followings. + * + * byte     Atmel/LUFA(ATMega32u4)          byte     Atmel(AT90SUB128) + * 0x0000   +---------------+               0x00000  +---------------+ + *          |               |                        |               | + *          |               |                        |               | + *          |  Application  |                        |  Application  | + *          |               |                        |               | + *          =               =                        =               = + *          |               | 32KB-4KB               |               | 128KB-8KB + * 0x7000   +---------------+               0x1E000  +---------------+ + *          |  Bootloader   | 4KB                    |  Bootloader   | 8KB + * 0x7FFF   +---------------+               0x1FFFF  +---------------+ + * + * + * byte     Teensy(ATMega32u4)              byte     Teensy++(AT90SUB128) + * 0x0000   +---------------+               0x00000  +---------------+ + *          |               |                        |               | + *          |               |                        |               | + *          |  Application  |                        |  Application  | + *          |               |                        |               | + *          =               =                        =               = + *          |               | 32KB-512B              |               | 128KB-1KB + * 0x7E00   +---------------+               0x1FC00  +---------------+ + *          |  Bootloader   | 512B                   |  Bootloader   | 1KB + * 0x7FFF   +---------------+               0x1FFFF  +---------------+ + */ +#define FLASH_SIZE (FLASHEND + 1L) + +#if !defined(BOOTLOADER_SIZE) +uint16_t bootloader_start; +#endif + +// compatibility between ATMega8 and ATMega88 +#if !defined(MCUCSR) +#    if defined(MCUSR) +#        define MCUCSR MCUSR +#    endif +#endif + +/** \brief Entering the Bootloader via Software + * + * http://www.fourwalledcubicle.com/files/LUFA/Doc/120730/html/_page__software_bootloader_start.html + */ +#define BOOTLOADER_RESET_KEY 0xB007B007 +uint32_t reset_key __attribute__((section(".noinit,\"aw\",@nobits;"))); + +/** \brief initialize MCU status by watchdog reset + * + * FIXME: needs doc + */ +__attribute__((weak)) void bootloader_jump(void) { +#if !defined(BOOTLOADER_SIZE) +    uint8_t high_fuse = boot_lock_fuse_bits_get(GET_HIGH_FUSE_BITS); + +    if (high_fuse & ~(FUSE_BOOTSZ0 & FUSE_BOOTSZ1)) { +        bootloader_start = (FLASH_SIZE - 512) >> 1; +    } else if (high_fuse & ~(FUSE_BOOTSZ1)) { +        bootloader_start = (FLASH_SIZE - 1024) >> 1; +    } else if (high_fuse & ~(FUSE_BOOTSZ0)) { +        bootloader_start = (FLASH_SIZE - 2048) >> 1; +    } else { +        bootloader_start = (FLASH_SIZE - 4096) >> 1; +    } +#endif + +    // Something like this might work, but it compiled larger than the block above +    // bootloader_start = FLASH_SIZE - (256 << (~high_fuse & 0b110 >> 1)); + +#if defined(BOOTLOADER_HALFKAY) +    //  http://www.pjrc.com/teensy/jump_to_bootloader.html +    cli(); +    // disable watchdog, if enabled (it's not) +    // disable all peripherals +    // a shutdown call might make sense here +    UDCON  = 1; +    USBCON = (1 << FRZCLK);  // disable USB +    UCSR1B = 0; +    _delay_ms(5); +#    if defined(__AVR_AT90USB162__)  // Teensy 1.0 +    EIMSK  = 0; +    PCICR  = 0; +    SPCR   = 0; +    ACSR   = 0; +    EECR   = 0; +    TIMSK0 = 0; +    TIMSK1 = 0; +    UCSR1B = 0; +    DDRB   = 0; +    DDRC   = 0; +    DDRD   = 0; +    PORTB  = 0; +    PORTC  = 0; +    PORTD  = 0; +    asm volatile("jmp 0x3E00"); +#    elif defined(__AVR_ATmega32U4__)   // Teensy 2.0 +    EIMSK  = 0; +    PCICR  = 0; +    SPCR   = 0; +    ACSR   = 0; +    EECR   = 0; +    ADCSRA = 0; +    TIMSK0 = 0; +    TIMSK1 = 0; +    TIMSK3 = 0; +    TIMSK4 = 0; +    UCSR1B = 0; +    TWCR   = 0; +    DDRB   = 0; +    DDRC   = 0; +    DDRD   = 0; +    DDRE   = 0; +    DDRF   = 0; +    TWCR   = 0; +    PORTB  = 0; +    PORTC  = 0; +    PORTD  = 0; +    PORTE  = 0; +    PORTF  = 0; +    asm volatile("jmp 0x7E00"); +#    elif defined(__AVR_AT90USB646__)   // Teensy++ 1.0 +    EIMSK  = 0; +    PCICR  = 0; +    SPCR   = 0; +    ACSR   = 0; +    EECR   = 0; +    ADCSRA = 0; +    TIMSK0 = 0; +    TIMSK1 = 0; +    TIMSK2 = 0; +    TIMSK3 = 0; +    UCSR1B = 0; +    TWCR   = 0; +    DDRA   = 0; +    DDRB   = 0; +    DDRC   = 0; +    DDRD   = 0; +    DDRE   = 0; +    DDRF   = 0; +    PORTA  = 0; +    PORTB  = 0; +    PORTC  = 0; +    PORTD  = 0; +    PORTE  = 0; +    PORTF  = 0; +    asm volatile("jmp 0xFC00"); +#    elif defined(__AVR_AT90USB1286__)  // Teensy++ 2.0 +    EIMSK  = 0; +    PCICR  = 0; +    SPCR   = 0; +    ACSR   = 0; +    EECR   = 0; +    ADCSRA = 0; +    TIMSK0 = 0; +    TIMSK1 = 0; +    TIMSK2 = 0; +    TIMSK3 = 0; +    UCSR1B = 0; +    TWCR   = 0; +    DDRA   = 0; +    DDRB   = 0; +    DDRC   = 0; +    DDRD   = 0; +    DDRE   = 0; +    DDRF   = 0; +    PORTA  = 0; +    PORTB  = 0; +    PORTC  = 0; +    PORTD  = 0; +    PORTE  = 0; +    PORTF  = 0; +    asm volatile("jmp 0x1FC00"); +#    endif + +#elif defined(BOOTLOADER_CATERINA) +    // this block may be optional +    // TODO: figure it out + +    uint16_t *const bootKeyPtr = (uint16_t *)0x0800; + +    // Value used by Caterina bootloader use to determine whether to run the +    // sketch or the bootloader programmer. +    uint16_t bootKey = 0x7777; + +    *bootKeyPtr = bootKey; + +    // setup watchdog timeout +    wdt_enable(WDTO_60MS); + +    while (1) { +    }  // wait for watchdog timer to trigger + +#elif defined(BOOTLOADER_USBASP) +    // Taken with permission of Stephan Baerwolf from https://github.com/tinyusbboard/API/blob/master/apipage.c +    wdt_enable(WDTO_15MS); +    wdt_reset(); +    asm volatile("cli                    \n\t" +                 "ldi    r29 ,       %[ramendhi] \n\t" +                 "ldi    r28 ,       %[ramendlo] \n\t" +#    if (FLASHEND > 131071) +                 "ldi    r18 ,       %[bootaddrhi]   \n\t" +                 "st     Y+,         r18     \n\t" +#    endif +                 "ldi    r18 ,       %[bootaddrme]   \n\t" +                 "st     Y+,     r18     \n\t" +                 "ldi    r18 ,       %[bootaddrlo]   \n\t" +                 "st     Y+,     r18     \n\t" +                 "out    %[mcucsrio],    __zero_reg__    \n\t" +                 "bootloader_startup_loop%=:         \n\t" +                 "rjmp bootloader_startup_loop%=     \n\t" +                 : +                 : [mcucsrio] "I"(_SFR_IO_ADDR(MCUCSR)), +#    if (FLASHEND > 131071) +                   [ramendhi] "M"(((RAMEND - 2) >> 8) & 0xff), [ramendlo] "M"(((RAMEND - 2) >> 0) & 0xff), [bootaddrhi] "M"((((FLASH_SIZE - BOOTLOADER_SIZE) >> 1) >> 16) & 0xff), +#    else +                   [ramendhi] "M"(((RAMEND - 1) >> 8) & 0xff), [ramendlo] "M"(((RAMEND - 1) >> 0) & 0xff), +#    endif +                   [bootaddrme] "M"((((FLASH_SIZE - BOOTLOADER_SIZE) >> 1) >> 8) & 0xff), [bootaddrlo] "M"((((FLASH_SIZE - BOOTLOADER_SIZE) >> 1) >> 0) & 0xff)); + +#else  // Assume remaining boards are DFU, even if the flag isn't set + +#    if !(defined(__AVR_ATmega32A__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega328__) || defined(__AVR_ATtiny85__))  // no USB - maybe BOOTLOADER_BOOTLOADHID instead though? +    UDCON  = 1; +    USBCON = (1 << FRZCLK);  // disable USB +    UCSR1B = 0; +    _delay_ms(5);  // 5 seems to work fine +#    endif + +#    ifdef BOOTLOADER_BOOTLOADHID +    // force bootloadHID to stay in bootloader mode, so that it waits +    // for a new firmware to be flashed +    eeprom_write_byte((uint8_t *)1, 0x00); +#    endif + +    // watchdog reset +    reset_key = BOOTLOADER_RESET_KEY; +    wdt_enable(WDTO_250MS); +    for (;;) +        ; +#endif +} + +/* this runs before main() */ +void bootloader_jump_after_watchdog_reset(void) __attribute__((used, naked, section(".init3"))); +void bootloader_jump_after_watchdog_reset(void) { +#ifndef BOOTLOADER_HALFKAY +    if ((MCUCSR & (1 << WDRF)) && reset_key == BOOTLOADER_RESET_KEY) { +        reset_key = 0; + +        // My custom USBasploader requires this to come up. +        MCUCSR = 0; + +        // Seems like Teensy halfkay loader requires clearing WDRF and disabling watchdog. +        MCUCSR &= ~(1 << WDRF); +        wdt_disable(); + +// This is compled into 'icall', address should be in word unit, not byte. +#    ifdef BOOTLOADER_SIZE +        ((void (*)(void))((FLASH_SIZE - BOOTLOADER_SIZE) >> 1))(); +#    else +        asm("ijmp" ::"z"(bootloader_start)); +#    endif +    } +#endif +} diff --git a/platforms/avr/bootloader_size.c b/platforms/avr/bootloader_size.c new file mode 100644 index 0000000000..a029f9321f --- /dev/null +++ b/platforms/avr/bootloader_size.c @@ -0,0 +1,21 @@ +// Copyright 2017 Jack Humbert +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. + +#include <avr/io.h> +#include <avr/boot.h> + +// clang-format off +// this is not valid C - it's for computing the size available on the chip +AVR_SIZE: FLASHEND + 1 - BOOTLOADER_SIZE diff --git a/platforms/avr/drivers/analog.c b/platforms/avr/drivers/analog.c index 8d299ffdb9..628835ccef 100644 --- a/platforms/avr/drivers/analog.c +++ b/platforms/avr/drivers/analog.c @@ -23,29 +23,6 @@ static uint8_t aref = ADC_REF_POWER;  void analogReference(uint8_t mode) { aref = mode & (_BV(REFS1) | _BV(REFS0)); } -// Arduino compatible pin input -int16_t analogRead(uint8_t pin) { -#if defined(__AVR_ATmega32U4__) -    // clang-format off -    static const uint8_t PROGMEM pin_to_mux[] = { -        //A0    A1    A2    A3    A4    A5 -        //F7    F6    F5    F4    F1    F0 -        0x07, 0x06, 0x05, 0x04, 0x01, 0x00, -        //A6    A7    A8    A9   A10   A11 -        //D4    D7    B4    B5    B6    D6 -        0x20, 0x22, 0x23, 0x24, 0x25, 0x21 -    }; -    // clang-format on -    if (pin >= 12) return 0; -    return adc_read(pgm_read_byte(pin_to_mux + pin)); -#elif defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB647__) || defined(__AVR_AT90USB1286__) || defined(__AVR_AT90USB1287__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega328__) -    if (pin >= 8) return 0; -    return adc_read(pin); -#else -    return 0; -#endif -} -  int16_t analogReadPin(pin_t pin) { return adc_read(pinToMux(pin)); }  uint8_t pinToMux(pin_t pin) { diff --git a/platforms/avr/drivers/analog.h b/platforms/avr/drivers/analog.h index 058882450d..fa2fb0d89b 100644 --- a/platforms/avr/drivers/analog.h +++ b/platforms/avr/drivers/analog.h @@ -22,8 +22,7 @@  #ifdef __cplusplus  extern "C" {  #endif -void    analogReference(uint8_t mode); -int16_t analogRead(uint8_t pin); +void analogReference(uint8_t mode);  int16_t analogReadPin(pin_t pin);  uint8_t pinToMux(pin_t pin); diff --git a/platforms/avr/drivers/audio_pwm.h b/platforms/avr/drivers/audio_pwm.h new file mode 100644 index 0000000000..d6eb3571da --- /dev/null +++ b/platforms/avr/drivers/audio_pwm.h @@ -0,0 +1,17 @@ +/* Copyright 2020 Jack Humbert + * Copyright 2020 JohSchneider + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ +#pragma once diff --git a/platforms/avr/drivers/audio_pwm_hardware.c b/platforms/avr/drivers/audio_pwm_hardware.c new file mode 100644 index 0000000000..df03a4558c --- /dev/null +++ b/platforms/avr/drivers/audio_pwm_hardware.c @@ -0,0 +1,332 @@ +/* Copyright 2016 Jack Humbert + * Copyright 2020 JohSchneider + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + +#if defined(__AVR__) +#    include <avr/pgmspace.h> +#    include <avr/interrupt.h> +#    include <avr/io.h> +#endif + +#include "audio.h" + +extern bool    playing_note; +extern bool    playing_melody; +extern uint8_t note_timbre; + +#define CPU_PRESCALER 8 + +/* +  Audio Driver: PWM + +  drive up to two speakers through the AVR PWM hardware-peripheral, using timer1 and/or timer3 on Atmega32U4. + +  the primary channel_1 can be connected to either pin PC4 PC5 or PC6 (the later being used by most AVR based keyboards) with a PMW signal generated by timer3 +  and an optional secondary channel_2 on either pin PB5, PB6 or PB7, with a PWM signal from timer1 + +  alternatively, the PWM pins on PORTB can be used as only/primary speaker +*/ + +#if defined(AUDIO_PIN) && (AUDIO_PIN != C4) && (AUDIO_PIN != C5) && (AUDIO_PIN != C6) && (AUDIO_PIN != B5) && (AUDIO_PIN != B6) && (AUDIO_PIN != B7) && (AUDIO_PIN != D5) +#    error "Audio feature enabled, but no suitable pin selected as AUDIO_PIN - see docs/feature_audio under the AVR settings for available options." +#endif + +#if (AUDIO_PIN == C4) || (AUDIO_PIN == C5) || (AUDIO_PIN == C6) +#    define AUDIO1_PIN_SET +#    define AUDIO1_TIMSKx TIMSK3 +#    define AUDIO1_TCCRxA TCCR3A +#    define AUDIO1_TCCRxB TCCR3B +#    define AUDIO1_ICRx ICR3 +#    define AUDIO1_WGMx0 WGM30 +#    define AUDIO1_WGMx1 WGM31 +#    define AUDIO1_WGMx2 WGM32 +#    define AUDIO1_WGMx3 WGM33 +#    define AUDIO1_CSx0 CS30 +#    define AUDIO1_CSx1 CS31 +#    define AUDIO1_CSx2 CS32 + +#    if (AUDIO_PIN == C6) +#        define AUDIO1_COMxy0 COM3A0 +#        define AUDIO1_COMxy1 COM3A1 +#        define AUDIO1_OCIExy OCIE3A +#        define AUDIO1_OCRxy OCR3A +#        define AUDIO1_PIN C6 +#        define AUDIO1_TIMERx_COMPy_vect TIMER3_COMPA_vect +#    elif (AUDIO_PIN == C5) +#        define AUDIO1_COMxy0 COM3B0 +#        define AUDIO1_COMxy1 COM3B1 +#        define AUDIO1_OCIExy OCIE3B +#        define AUDIO1_OCRxy OCR3B +#        define AUDIO1_PIN C5 +#        define AUDIO1_TIMERx_COMPy_vect TIMER3_COMPB_vect +#    elif (AUDIO_PIN == C4) +#        define AUDIO1_COMxy0 COM3C0 +#        define AUDIO1_COMxy1 COM3C1 +#        define AUDIO1_OCIExy OCIE3C +#        define AUDIO1_OCRxy OCR3C +#        define AUDIO1_PIN C4 +#        define AUDIO1_TIMERx_COMPy_vect TIMER3_COMPC_vect +#    endif +#endif + +#if defined(AUDIO_PIN) && defined(AUDIO_PIN_ALT) && (AUDIO_PIN == AUDIO_PIN_ALT) +#    error "Audio feature: AUDIO_PIN and AUDIO_PIN_ALT on the same pin makes no sense." +#endif + +#if ((AUDIO_PIN == B5) && ((AUDIO_PIN_ALT == B6) || (AUDIO_PIN_ALT == B7))) || ((AUDIO_PIN == B6) && ((AUDIO_PIN_ALT == B5) || (AUDIO_PIN_ALT == B7))) || ((AUDIO_PIN == B7) && ((AUDIO_PIN_ALT == B5) || (AUDIO_PIN_ALT == B6))) +#    error "Audio feature: PORTB as AUDIO_PIN and AUDIO_PIN_ALT at the same time is not supported." +#endif + +#if defined(AUDIO_PIN_ALT) && (AUDIO_PIN_ALT != B5) && (AUDIO_PIN_ALT != B6) && (AUDIO_PIN_ALT != B7) +#    error "Audio feature: the pin selected as AUDIO_PIN_ALT is not supported." +#endif + +#if (AUDIO_PIN == B5) || (AUDIO_PIN == B6) || (AUDIO_PIN == B7) || (AUDIO_PIN_ALT == B5) || (AUDIO_PIN_ALT == B6) || (AUDIO_PIN_ALT == B7) || (AUDIO_PIN == D5) +#    define AUDIO2_PIN_SET +#    define AUDIO2_TIMSKx TIMSK1 +#    define AUDIO2_TCCRxA TCCR1A +#    define AUDIO2_TCCRxB TCCR1B +#    define AUDIO2_ICRx ICR1 +#    define AUDIO2_WGMx0 WGM10 +#    define AUDIO2_WGMx1 WGM11 +#    define AUDIO2_WGMx2 WGM12 +#    define AUDIO2_WGMx3 WGM13 +#    define AUDIO2_CSx0 CS10 +#    define AUDIO2_CSx1 CS11 +#    define AUDIO2_CSx2 CS12 + +#    if (AUDIO_PIN == B5) || (AUDIO_PIN_ALT == B5) +#        define AUDIO2_COMxy0 COM1A0 +#        define AUDIO2_COMxy1 COM1A1 +#        define AUDIO2_OCIExy OCIE1A +#        define AUDIO2_OCRxy OCR1A +#        define AUDIO2_PIN B5 +#        define AUDIO2_TIMERx_COMPy_vect TIMER1_COMPA_vect +#    elif (AUDIO_PIN == B6) || (AUDIO_PIN_ALT == B6) +#        define AUDIO2_COMxy0 COM1B0 +#        define AUDIO2_COMxy1 COM1B1 +#        define AUDIO2_OCIExy OCIE1B +#        define AUDIO2_OCRxy OCR1B +#        define AUDIO2_PIN B6 +#        define AUDIO2_TIMERx_COMPy_vect TIMER1_COMPB_vect +#    elif (AUDIO_PIN == B7) || (AUDIO_PIN_ALT == B7) +#        define AUDIO2_COMxy0 COM1C0 +#        define AUDIO2_COMxy1 COM1C1 +#        define AUDIO2_OCIExy OCIE1C +#        define AUDIO2_OCRxy OCR1C +#        define AUDIO2_PIN B7 +#        define AUDIO2_TIMERx_COMPy_vect TIMER1_COMPC_vect +#    elif (AUDIO_PIN == D5) && defined(__AVR_ATmega32A__) +#        pragma message "Audio support for ATmega32A is experimental and can cause crashes." +#        undef AUDIO2_TIMSKx +#        define AUDIO2_TIMSKx TIMSK +#        define AUDIO2_COMxy0 COM1A0 +#        define AUDIO2_COMxy1 COM1A1 +#        define AUDIO2_OCIExy OCIE1A +#        define AUDIO2_OCRxy OCR1A +#        define AUDIO2_PIN D5 +#        define AUDIO2_TIMERx_COMPy_vect TIMER1_COMPA_vect +#    endif +#endif + +// C6 seems to be the assumed default by many existing keyboard - but sill warn the user +#if !defined(AUDIO1_PIN_SET) && !defined(AUDIO2_PIN_SET) +#    pragma message "Audio feature enabled, but no suitable pin selected - see docs/feature_audio under the AVR settings for available options. Don't expect to hear anything... :-)" +// TODO: make this an error - go through the breaking-change-process and change all keyboards to the new define +#endif +// ----------------------------------------------------------------------------- + +#ifdef AUDIO1_PIN_SET +static float channel_1_frequency = 0.0f; +void         channel_1_set_frequency(float freq) { +    if (freq == 0.0f)  // a pause/rest is a valid "note" with freq=0 +    { +        // disable the output, but keep the pwm-ISR going (with the previous +        // frequency) so the audio-state keeps getting updated +        // Note: setting the duty-cycle 0 is not possible on non-inverting PWM mode - see the AVR data-sheet +        AUDIO1_TCCRxA &= ~(_BV(AUDIO1_COMxy1) | _BV(AUDIO1_COMxy0)); +        return; +    } else { +        AUDIO1_TCCRxA |= _BV(AUDIO1_COMxy1);  // enable output, PWM mode +    } + +    channel_1_frequency = freq; + +    // set pwm period +    AUDIO1_ICRx = (uint16_t)(((float)F_CPU) / (freq * CPU_PRESCALER)); +    // and duty cycle +    AUDIO1_OCRxy = (uint16_t)((((float)F_CPU) / (freq * CPU_PRESCALER)) * note_timbre / 100); +} + +void channel_1_start(void) { +    // enable timer-counter ISR +    AUDIO1_TIMSKx |= _BV(AUDIO1_OCIExy); +    // enable timer-counter output +    AUDIO1_TCCRxA |= _BV(AUDIO1_COMxy1); +} + +void channel_1_stop(void) { +    // disable timer-counter ISR +    AUDIO1_TIMSKx &= ~_BV(AUDIO1_OCIExy); +    // disable timer-counter output +    AUDIO1_TCCRxA &= ~(_BV(AUDIO1_COMxy1) | _BV(AUDIO1_COMxy0)); +} +#endif + +#ifdef AUDIO2_PIN_SET +static float channel_2_frequency = 0.0f; +void         channel_2_set_frequency(float freq) { +    if (freq == 0.0f) { +        AUDIO2_TCCRxA &= ~(_BV(AUDIO2_COMxy1) | _BV(AUDIO2_COMxy0)); +        return; +    } else { +        AUDIO2_TCCRxA |= _BV(AUDIO2_COMxy1); +    } + +    channel_2_frequency = freq; + +    AUDIO2_ICRx  = (uint16_t)(((float)F_CPU) / (freq * CPU_PRESCALER)); +    AUDIO2_OCRxy = (uint16_t)((((float)F_CPU) / (freq * CPU_PRESCALER)) * note_timbre / 100); +} + +float channel_2_get_frequency(void) { return channel_2_frequency; } + +void channel_2_start(void) { +    AUDIO2_TIMSKx |= _BV(AUDIO2_OCIExy); +    AUDIO2_TCCRxA |= _BV(AUDIO2_COMxy1); +} + +void channel_2_stop(void) { +    AUDIO2_TIMSKx &= ~_BV(AUDIO2_OCIExy); +    AUDIO2_TCCRxA &= ~(_BV(AUDIO2_COMxy1) | _BV(AUDIO2_COMxy0)); +} +#endif + +void audio_driver_initialize() { +#ifdef AUDIO1_PIN_SET +    channel_1_stop(); +    setPinOutput(AUDIO1_PIN); +#endif + +#ifdef AUDIO2_PIN_SET +    channel_2_stop(); +    setPinOutput(AUDIO2_PIN); +#endif + +    // TCCR3A / TCCR3B: Timer/Counter #3 Control Registers TCCR3A/TCCR3B, TCCR1A/TCCR1B +    // Compare Output Mode (COM3An and COM1An) = 0b00 = Normal port operation +    //   OC3A -- PC6 +    //   OC3B -- PC5 +    //   OC3C -- PC4 +    //   OC1A -- PB5 +    //   OC1B -- PB6 +    //   OC1C -- PB7 + +    // Waveform Generation Mode (WGM3n) = 0b1110 = Fast PWM Mode 14. Period = ICR3, Duty Cycle OCR3A) +    //   OCR3A - PC6 +    //   OCR3B - PC5 +    //   OCR3C - PC4 +    //   OCR1A - PB5 +    //   OCR1B - PB6 +    //   OCR1C - PB7 + +    // Clock Select (CS3n) = 0b010 = Clock / 8 +#ifdef AUDIO1_PIN_SET +    // initialize timer-counter +    AUDIO1_TCCRxA = (0 << AUDIO1_COMxy1) | (0 << AUDIO1_COMxy0) | (1 << AUDIO1_WGMx1) | (0 << AUDIO1_WGMx0); +    AUDIO1_TCCRxB = (1 << AUDIO1_WGMx3) | (1 << AUDIO1_WGMx2) | (0 << AUDIO1_CSx2) | (1 << AUDIO1_CSx1) | (0 << AUDIO1_CSx0); +#endif + +#ifdef AUDIO2_PIN_SET +    AUDIO2_TCCRxA = (0 << AUDIO2_COMxy1) | (0 << AUDIO2_COMxy0) | (1 << AUDIO2_WGMx1) | (0 << AUDIO2_WGMx0); +    AUDIO2_TCCRxB = (1 << AUDIO2_WGMx3) | (1 << AUDIO2_WGMx2) | (0 << AUDIO2_CSx2) | (1 << AUDIO2_CSx1) | (0 << AUDIO2_CSx0); +#endif +} + +void audio_driver_stop() { +#ifdef AUDIO1_PIN_SET +    channel_1_stop(); +#endif + +#ifdef AUDIO2_PIN_SET +    channel_2_stop(); +#endif +} + +void audio_driver_start(void) { +#ifdef AUDIO1_PIN_SET +    channel_1_start(); +    if (playing_note) { +        channel_1_set_frequency(audio_get_processed_frequency(0)); +    } +#endif + +#if !defined(AUDIO1_PIN_SET) && defined(AUDIO2_PIN_SET) +    channel_2_start(); +    if (playing_note) { +        channel_2_set_frequency(audio_get_processed_frequency(0)); +    } +#endif +} + +static volatile uint32_t isr_counter = 0; +#ifdef AUDIO1_PIN_SET +ISR(AUDIO1_TIMERx_COMPy_vect) { +    isr_counter++; +    if (isr_counter < channel_1_frequency / (CPU_PRESCALER * 8)) return; + +    isr_counter        = 0; +    bool state_changed = audio_update_state(); + +    if (!playing_note && !playing_melody) { +        channel_1_stop(); +#    ifdef AUDIO2_PIN_SET +        channel_2_stop(); +#    endif +        return; +    } + +    if (state_changed) { +        channel_1_set_frequency(audio_get_processed_frequency(0)); +#    ifdef AUDIO2_PIN_SET +        if (audio_get_number_of_active_tones() > 1) { +            channel_2_set_frequency(audio_get_processed_frequency(1)); +        } else { +            channel_2_stop(); +        } +#    endif +    } +} +#endif + +#if !defined(AUDIO1_PIN_SET) && defined(AUDIO2_PIN_SET) +ISR(AUDIO2_TIMERx_COMPy_vect) { +    isr_counter++; +    if (isr_counter < channel_2_frequency / (CPU_PRESCALER * 8)) return; + +    isr_counter        = 0; +    bool state_changed = audio_update_state(); + +    if (!playing_note && !playing_melody) { +        channel_2_stop(); +        return; +    } + +    if (state_changed) { +        channel_2_set_frequency(audio_get_processed_frequency(0)); +    } +} +#endif diff --git a/platforms/avr/drivers/i2c_master.c b/platforms/avr/drivers/i2c_master.c index 2773e00778..111b55d6b0 100644 --- a/platforms/avr/drivers/i2c_master.c +++ b/platforms/avr/drivers/i2c_master.c @@ -202,6 +202,25 @@ i2c_status_t i2c_writeReg(uint8_t devaddr, uint8_t regaddr, const uint8_t* data,      return status;  } +i2c_status_t i2c_writeReg16(uint8_t devaddr, uint16_t regaddr, const uint8_t* data, uint16_t length, uint16_t timeout) { +    i2c_status_t status = i2c_start(devaddr | 0x00, timeout); +    if (status >= 0) { +        status = i2c_write(regaddr >> 8, timeout); + +        if (status >= 0) { +            status = i2c_write(regaddr & 0xFF, timeout); + +            for (uint16_t i = 0; i < length && status >= 0; i++) { +                status = i2c_write(data[i], timeout); +            } +        } +    } + +    i2c_stop(); + +    return status; +} +  i2c_status_t i2c_readReg(uint8_t devaddr, uint8_t regaddr, uint8_t* data, uint16_t length, uint16_t timeout) {      i2c_status_t status = i2c_start(devaddr, timeout);      if (status < 0) { @@ -235,6 +254,43 @@ error:      return (status < 0) ? status : I2C_STATUS_SUCCESS;  } +i2c_status_t i2c_readReg16(uint8_t devaddr, uint16_t regaddr, uint8_t* data, uint16_t length, uint16_t timeout) { +    i2c_status_t status = i2c_start(devaddr, timeout); +    if (status < 0) { +        goto error; +    } + +    status = i2c_write(regaddr >> 8, timeout); +    if (status < 0) { +        goto error; +    } +    status = i2c_write(regaddr & 0xFF, timeout); +    if (status < 0) { +        goto error; +    } + +    status = i2c_start(devaddr | 0x01, timeout); + +    for (uint16_t i = 0; i < (length - 1) && status >= 0; i++) { +        status = i2c_read_ack(timeout); +        if (status >= 0) { +            data[i] = status; +        } +    } + +    if (status >= 0) { +        status = i2c_read_nack(timeout); +        if (status >= 0) { +            data[(length - 1)] = status; +        } +    } + +error: +    i2c_stop(); + +    return (status < 0) ? status : I2C_STATUS_SUCCESS; +} +  void i2c_stop(void) {      // transmit STOP condition      TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO); diff --git a/platforms/avr/drivers/i2c_master.h b/platforms/avr/drivers/i2c_master.h index e5af73364b..2d95846db5 100644 --- a/platforms/avr/drivers/i2c_master.h +++ b/platforms/avr/drivers/i2c_master.h @@ -39,5 +39,7 @@ int16_t      i2c_read_nack(uint16_t timeout);  i2c_status_t i2c_transmit(uint8_t address, const uint8_t* data, uint16_t length, uint16_t timeout);  i2c_status_t i2c_receive(uint8_t address, uint8_t* data, uint16_t length, uint16_t timeout);  i2c_status_t i2c_writeReg(uint8_t devaddr, uint8_t regaddr, const uint8_t* data, uint16_t length, uint16_t timeout); +i2c_status_t i2c_writeReg16(uint8_t devaddr, uint16_t regaddr, const uint8_t* data, uint16_t length, uint16_t timeout);  i2c_status_t i2c_readReg(uint8_t devaddr, uint8_t regaddr, uint8_t* data, uint16_t length, uint16_t timeout); +i2c_status_t i2c_readReg16(uint8_t devaddr, uint16_t regaddr, uint8_t* data, uint16_t length, uint16_t timeout);  void         i2c_stop(void); diff --git a/platforms/avr/drivers/ps2/ps2_io.c b/platforms/avr/drivers/ps2/ps2_io.c new file mode 100644 index 0000000000..7c826fbf1a --- /dev/null +++ b/platforms/avr/drivers/ps2/ps2_io.c @@ -0,0 +1,51 @@ +#include <stdbool.h> +#include "ps2_io.h" +#include "gpio.h" +#include "wait.h" + +/* Check port settings for clock and data line */ +#if !(defined(PS2_CLOCK_PIN)) +#    error "PS/2 clock setting is required in config.h" +#endif + +#if !(defined(PS2_DATA_PIN)) +#    error "PS/2 data setting is required in config.h" +#endif + +/* + * Clock + */ +void clock_init(void) {} + +void clock_lo(void) { +    // Transition from input with pull-up to output low via Hi-Z instead of output high +    writePinLow(PS2_CLOCK_PIN); +    setPinOutput(PS2_CLOCK_PIN); +} + +void clock_hi(void) { setPinInputHigh(PS2_CLOCK_PIN); } + +bool clock_in(void) { +    setPinInputHigh(PS2_CLOCK_PIN); +    wait_us(1); +    return readPin(PS2_CLOCK_PIN); +} + +/* + * Data + */ +void data_init(void) {} + +void data_lo(void) { +    // Transition from input with pull-up to output low via Hi-Z instead of output high +    writePinLow(PS2_DATA_PIN); +    setPinOutput(PS2_DATA_PIN); +} + +void data_hi(void) { setPinInputHigh(PS2_DATA_PIN); } + +bool data_in(void) { +    setPinInputHigh(PS2_DATA_PIN); +    wait_us(1); +    return readPin(PS2_DATA_PIN); +} diff --git a/platforms/avr/drivers/ps2/ps2_usart.c b/platforms/avr/drivers/ps2/ps2_usart.c new file mode 100644 index 0000000000..151cfcd68f --- /dev/null +++ b/platforms/avr/drivers/ps2/ps2_usart.c @@ -0,0 +1,227 @@ +/* +Copyright 2010,2011,2012,2013 Jun WAKO <[email protected]> + +This software is licensed with a Modified BSD License. +All of this is supposed to be Free Software, Open Source, DFSG-free, +GPL-compatible, and OK to use in both free and proprietary applications. +Additions and corrections to this file are welcome. + + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright +  notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright +  notice, this list of conditions and the following disclaimer in +  the documentation and/or other materials provided with the +  distribution. + +* Neither the name of the copyright holders nor the names of +  contributors may be used to endorse or promote products derived +  from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ + +/* + * PS/2 protocol USART version + */ + +#include <stdbool.h> +#include <avr/interrupt.h> +#include <util/delay.h> +#include "gpio.h" +#include "ps2.h" +#include "ps2_io.h" +#include "print.h" + +#ifndef PS2_CLOCK_DDR +#    define PS2_CLOCK_DDR PORTx_ADDRESS(PS2_CLOCK_PIN) +#endif +#ifndef PS2_CLOCK_BIT +#    define PS2_CLOCK_BIT (PS2_CLOCK_PIN & 0xF) +#endif +#ifndef PS2_DATA_DDR +#    define PS2_DATA_DDR PORTx_ADDRESS(PS2_DATA_PIN) +#endif +#ifndef PS2_DATA_BIT +#    define PS2_DATA_BIT (PS2_DATA_PIN & 0xF) +#endif + +#define WAIT(stat, us, err)     \ +    do {                        \ +        if (!wait_##stat(us)) { \ +            ps2_error = err;    \ +            goto ERROR;         \ +        }                       \ +    } while (0) + +uint8_t ps2_error = PS2_ERR_NONE; + +static inline uint8_t pbuf_dequeue(void); +static inline void    pbuf_enqueue(uint8_t data); +static inline bool    pbuf_has_data(void); +static inline void    pbuf_clear(void); + +void ps2_host_init(void) { +    idle();  // without this many USART errors occur when cable is disconnected +    PS2_USART_INIT(); +    PS2_USART_RX_INT_ON(); +    // POR(150-2000ms) plus BAT(300-500ms) may take 2.5sec([3]p.20) +    //_delay_ms(2500); +} + +uint8_t ps2_host_send(uint8_t data) { +    bool parity = true; +    ps2_error   = PS2_ERR_NONE; + +    PS2_USART_OFF(); + +    /* terminate a transmission if we have */ +    inhibit(); +    _delay_us(100);  // [4]p.13 + +    /* 'Request to Send' and Start bit */ +    data_lo(); +    clock_hi(); +    WAIT(clock_lo, 10000, 10);  // 10ms [5]p.50 + +    /* Data bit[2-9] */ +    for (uint8_t i = 0; i < 8; i++) { +        _delay_us(15); +        if (data & (1 << i)) { +            parity = !parity; +            data_hi(); +        } else { +            data_lo(); +        } +        WAIT(clock_hi, 50, 2); +        WAIT(clock_lo, 50, 3); +    } + +    /* Parity bit */ +    _delay_us(15); +    if (parity) { +        data_hi(); +    } else { +        data_lo(); +    } +    WAIT(clock_hi, 50, 4); +    WAIT(clock_lo, 50, 5); + +    /* Stop bit */ +    _delay_us(15); +    data_hi(); + +    /* Ack */ +    WAIT(data_lo, 50, 6); +    WAIT(clock_lo, 50, 7); + +    /* wait for idle state */ +    WAIT(clock_hi, 50, 8); +    WAIT(data_hi, 50, 9); + +    idle(); +    PS2_USART_INIT(); +    PS2_USART_RX_INT_ON(); +    return ps2_host_recv_response(); +ERROR: +    idle(); +    PS2_USART_INIT(); +    PS2_USART_RX_INT_ON(); +    return 0; +} + +uint8_t ps2_host_recv_response(void) { +    // Command may take 25ms/20ms at most([5]p.46, [3]p.21) +    uint8_t retry = 25; +    while (retry-- && !pbuf_has_data()) { +        _delay_ms(1); +    } +    return pbuf_dequeue(); +} + +uint8_t ps2_host_recv(void) { +    if (pbuf_has_data()) { +        ps2_error = PS2_ERR_NONE; +        return pbuf_dequeue(); +    } else { +        ps2_error = PS2_ERR_NODATA; +        return 0; +    } +} + +ISR(PS2_USART_RX_VECT) { +    // TODO: request RESEND when error occurs? +    uint8_t error = PS2_USART_ERROR;  // USART error should be read before data +    uint8_t data  = PS2_USART_RX_DATA; +    if (!error) { +        pbuf_enqueue(data); +    } else { +        xprintf("PS2 USART error: %02X data: %02X\n", error, data); +    } +} + +/* send LED state to keyboard */ +void ps2_host_set_led(uint8_t led) { +    ps2_host_send(0xED); +    ps2_host_send(led); +} + +/*-------------------------------------------------------------------- + * Ring buffer to store scan codes from keyboard + *------------------------------------------------------------------*/ +#define PBUF_SIZE 32 +static uint8_t     pbuf[PBUF_SIZE]; +static uint8_t     pbuf_head = 0; +static uint8_t     pbuf_tail = 0; +static inline void pbuf_enqueue(uint8_t data) { +    uint8_t sreg = SREG; +    cli(); +    uint8_t next = (pbuf_head + 1) % PBUF_SIZE; +    if (next != pbuf_tail) { +        pbuf[pbuf_head] = data; +        pbuf_head       = next; +    } else { +        print("pbuf: full\n"); +    } +    SREG = sreg; +} +static inline uint8_t pbuf_dequeue(void) { +    uint8_t val = 0; + +    uint8_t sreg = SREG; +    cli(); +    if (pbuf_head != pbuf_tail) { +        val       = pbuf[pbuf_tail]; +        pbuf_tail = (pbuf_tail + 1) % PBUF_SIZE; +    } +    SREG = sreg; + +    return val; +} +static inline bool pbuf_has_data(void) { +    uint8_t sreg = SREG; +    cli(); +    bool has_data = (pbuf_head != pbuf_tail); +    SREG          = sreg; +    return has_data; +} +static inline void pbuf_clear(void) { +    uint8_t sreg = SREG; +    cli(); +    pbuf_head = pbuf_tail = 0; +    SREG                  = sreg; +} diff --git a/platforms/avr/drivers/uart.c b/platforms/avr/drivers/uart.c index c6abcb6fe0..01cf6b1fb8 100644 --- a/platforms/avr/drivers/uart.c +++ b/platforms/avr/drivers/uart.c @@ -100,7 +100,7 @@ void uart_init(uint32_t baud) {  }  // Transmit a byte -void uart_putchar(uint8_t c) { +void uart_write(uint8_t data) {      uint8_t i;      i = tx_buffer_head + 1; @@ -110,27 +110,39 @@ void uart_putchar(uint8_t c) {      while (tx_buffer_tail == i)          ;  // wait until space in buffer      // cli(); -    tx_buffer[i]   = c; +    tx_buffer[i]   = data;      tx_buffer_head = i;      UCSRnB         = (1 << RXENn) | (1 << TXENn) | (1 << RXCIEn) | (1 << UDRIEn);      // sei();  }  // Receive a byte -uint8_t uart_getchar(void) { -    uint8_t c, i; +uint8_t uart_read(void) { +    uint8_t data, i;      while (rx_buffer_head == rx_buffer_tail)          ;  // wait for character      i = rx_buffer_tail + 1;      if (i >= RX_BUFFER_SIZE) i = 0; -    c              = rx_buffer[i]; +    data           = rx_buffer[i];      rx_buffer_tail = i; -    return c; +    return data; +} + +void uart_transmit(const uint8_t *data, uint16_t length) { +    for (uint16_t i = 0; i < length; i++) { +        uart_write(data[i]); +    } +} + +void uart_receive(uint8_t *data, uint16_t length) { +    for (uint16_t i = 0; i < length; i++) { +        data[i] = uart_read(); +    }  }  // Return whether the number of bytes waiting in the receive buffer is nonzero. -// Call this before uart_getchar() to check if it will need +// Call this before uart_read() to check if it will need  // to wait for a byte to arrive.  bool uart_available(void) {      uint8_t head, tail; diff --git a/platforms/avr/drivers/uart.h b/platforms/avr/drivers/uart.h index 602eb3d8b0..e2dc664eda 100644 --- a/platforms/avr/drivers/uart.h +++ b/platforms/avr/drivers/uart.h @@ -28,8 +28,12 @@  void uart_init(uint32_t baud); -void uart_putchar(uint8_t c); +void uart_write(uint8_t data); -uint8_t uart_getchar(void); +uint8_t uart_read(void); + +void uart_transmit(const uint8_t *data, uint16_t length); + +void uart_receive(uint8_t *data, uint16_t length);  bool uart_available(void); diff --git a/platforms/avr/flash.mk b/platforms/avr/flash.mk index 985cb60e52..6d50e72534 100644 --- a/platforms/avr/flash.mk +++ b/platforms/avr/flash.mk @@ -130,6 +130,15 @@ avrdude-split-right: $(BUILD_DIR)/$(TARGET).hex check-size cpfirmware  	$(call EXEC_AVRDUDE,eeprom-righthand.eep)  define EXEC_USBASP +	if $(AVRDUDE_PROGRAMMER) -p $(AVRDUDE_MCU) -c usbasp 2>&1 | grep -q "could not find USB device with"; then \ +		printf "$(MSG_BOOTLOADER_NOT_FOUND_QUICK_RETRY)" ;\ +		sleep $(BOOTLOADER_RETRY_TIME) ;\ +		until $(AVRDUDE_PROGRAMMER) -p $(AVRDUDE_MCU) -c usbasp 2>&1 | (! grep -q "could not find USB device with"); do\ +			printf "." ;\ +			sleep $(BOOTLOADER_RETRY_TIME) ;\ +		done ;\ +		printf "\n" ;\ +	fi  	$(AVRDUDE_PROGRAMMER) -p $(AVRDUDE_MCU) -c usbasp -U flash:w:$(BUILD_DIR)/$(TARGET).hex  endef diff --git a/platforms/avr/gpio.h b/platforms/avr/gpio.h new file mode 100644 index 0000000000..e9be68491d --- /dev/null +++ b/platforms/avr/gpio.h @@ -0,0 +1,49 @@ +/* Copyright 2021 QMK + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <avr/io.h> +#include "pin_defs.h" + +typedef uint8_t pin_t; + +/* Operation of GPIO by pin. */ + +#define setPinInput(pin) (DDRx_ADDRESS(pin) &= ~_BV((pin)&0xF), PORTx_ADDRESS(pin) &= ~_BV((pin)&0xF)) +#define setPinInputHigh(pin) (DDRx_ADDRESS(pin) &= ~_BV((pin)&0xF), PORTx_ADDRESS(pin) |= _BV((pin)&0xF)) +#define setPinInputLow(pin) _Static_assert(0, "AVR processors cannot implement an input as pull low") +#define setPinOutput(pin) (DDRx_ADDRESS(pin) |= _BV((pin)&0xF)) + +#define writePinHigh(pin) (PORTx_ADDRESS(pin) |= _BV((pin)&0xF)) +#define writePinLow(pin) (PORTx_ADDRESS(pin) &= ~_BV((pin)&0xF)) +#define writePin(pin, level) ((level) ? writePinHigh(pin) : writePinLow(pin)) + +#define readPin(pin) ((bool)(PINx_ADDRESS(pin) & _BV((pin)&0xF))) + +#define togglePin(pin) (PORTx_ADDRESS(pin) ^= _BV((pin)&0xF)) + +/* Operation of GPIO by port. */ + +typedef uint8_t port_data_t; + +#define readPort(port) PINx_ADDRESS(port) + +#define setPortBitInput(port, bit) (DDRx_ADDRESS(port) &= ~_BV((bit)&0xF), PORTx_ADDRESS(port) &= ~_BV((bit)&0xF)) +#define setPortBitInputHigh(port, bit) (DDRx_ADDRESS(port) &= ~_BV((bit)&0xF), PORTx_ADDRESS(port) |= _BV((bit)&0xF)) +#define setPortBitOutput(port, bit) (DDRx_ADDRESS(port) |= _BV((bit)&0xF)) + +#define writePortBitLow(port, bit) (PORTx_ADDRESS(port) &= ~_BV((bit)&0xF)) +#define writePortBitHigh(port, bit) (PORTx_ADDRESS(port) |= _BV((bit)&0xF)) diff --git a/platforms/avr/pin_defs.h b/platforms/avr/pin_defs.h new file mode 100644 index 0000000000..23d948041d --- /dev/null +++ b/platforms/avr/pin_defs.h @@ -0,0 +1,128 @@ +/* Copyright 2021 QMK + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <avr/io.h> + +#define PORT_SHIFTER 4  // this may be 4 for all AVR chips + +// If you want to add more to this list, reference the PINx definitions in these header +// files: https://github.com/vancegroup-mirrors/avr-libc/tree/master/avr-libc/include/avr + +#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega16U4__) +#    define ADDRESS_BASE 0x00 +#    define PINB_ADDRESS 0x3 +#    define PINC_ADDRESS 0x6 +#    define PIND_ADDRESS 0x9 +#    define PINE_ADDRESS 0xC +#    define PINF_ADDRESS 0xF +#elif defined(__AVR_AT90USB162__) || defined(__AVR_ATmega32U2__) || defined(__AVR_ATmega16U2__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega328__) +#    define ADDRESS_BASE 0x00 +#    define PINB_ADDRESS 0x3 +#    define PINC_ADDRESS 0x6 +#    define PIND_ADDRESS 0x9 +#elif defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB647__) || defined(__AVR_AT90USB1286__) || defined(__AVR_AT90USB1287__) +#    define ADDRESS_BASE 0x00 +#    define PINA_ADDRESS 0x0 +#    define PINB_ADDRESS 0x3 +#    define PINC_ADDRESS 0x6 +#    define PIND_ADDRESS 0x9 +#    define PINE_ADDRESS 0xC +#    define PINF_ADDRESS 0xF +#elif defined(__AVR_ATmega32A__) +#    define ADDRESS_BASE 0x10 +#    define PIND_ADDRESS 0x0 +#    define PINC_ADDRESS 0x3 +#    define PINB_ADDRESS 0x6 +#    define PINA_ADDRESS 0x9 +#elif defined(__AVR_ATtiny85__) +#    define ADDRESS_BASE 0x10 +#    define PINB_ADDRESS 0x6 +#else +#    error "Pins are not defined" +#endif + +#define PINDEF(port, pin) ((PIN##port##_ADDRESS << PORT_SHIFTER) | pin) + +#define _PIN_ADDRESS(p, offset) _SFR_IO8(ADDRESS_BASE + ((p) >> PORT_SHIFTER) + (offset)) +// Port X Input Pins Address +#define PINx_ADDRESS(p) _PIN_ADDRESS(p, 0) +// Port X Data Direction Register,  0:input 1:output +#define DDRx_ADDRESS(p) _PIN_ADDRESS(p, 1) +// Port X Data Register +#define PORTx_ADDRESS(p) _PIN_ADDRESS(p, 2) + +/* I/O pins */ +#ifdef PORTA +#    define A0 PINDEF(A, 0) +#    define A1 PINDEF(A, 1) +#    define A2 PINDEF(A, 2) +#    define A3 PINDEF(A, 3) +#    define A4 PINDEF(A, 4) +#    define A5 PINDEF(A, 5) +#    define A6 PINDEF(A, 6) +#    define A7 PINDEF(A, 7) +#endif +#ifdef PORTB +#    define B0 PINDEF(B, 0) +#    define B1 PINDEF(B, 1) +#    define B2 PINDEF(B, 2) +#    define B3 PINDEF(B, 3) +#    define B4 PINDEF(B, 4) +#    define B5 PINDEF(B, 5) +#    define B6 PINDEF(B, 6) +#    define B7 PINDEF(B, 7) +#endif +#ifdef PORTC +#    define C0 PINDEF(C, 0) +#    define C1 PINDEF(C, 1) +#    define C2 PINDEF(C, 2) +#    define C3 PINDEF(C, 3) +#    define C4 PINDEF(C, 4) +#    define C5 PINDEF(C, 5) +#    define C6 PINDEF(C, 6) +#    define C7 PINDEF(C, 7) +#endif +#ifdef PORTD +#    define D0 PINDEF(D, 0) +#    define D1 PINDEF(D, 1) +#    define D2 PINDEF(D, 2) +#    define D3 PINDEF(D, 3) +#    define D4 PINDEF(D, 4) +#    define D5 PINDEF(D, 5) +#    define D6 PINDEF(D, 6) +#    define D7 PINDEF(D, 7) +#endif +#ifdef PORTE +#    define E0 PINDEF(E, 0) +#    define E1 PINDEF(E, 1) +#    define E2 PINDEF(E, 2) +#    define E3 PINDEF(E, 3) +#    define E4 PINDEF(E, 4) +#    define E5 PINDEF(E, 5) +#    define E6 PINDEF(E, 6) +#    define E7 PINDEF(E, 7) +#endif +#ifdef PORTF +#    define F0 PINDEF(F, 0) +#    define F1 PINDEF(F, 1) +#    define F2 PINDEF(F, 2) +#    define F3 PINDEF(F, 3) +#    define F4 PINDEF(F, 4) +#    define F5 PINDEF(F, 5) +#    define F6 PINDEF(F, 6) +#    define F7 PINDEF(F, 7) +#endif diff --git a/platforms/avr/platform.c b/platforms/avr/platform.c new file mode 100644 index 0000000000..3e35b4fe4c --- /dev/null +++ b/platforms/avr/platform.c @@ -0,0 +1,21 @@ +/* Copyright 2021 QMK + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + +#include "platform_deps.h" + +void platform_setup(void) { +    // do nothing +} diff --git a/platforms/avr/platform.mk b/platforms/avr/platform.mk new file mode 100644 index 0000000000..b45108736f --- /dev/null +++ b/platforms/avr/platform.mk @@ -0,0 +1,179 @@ +# Hey Emacs, this is a -*- makefile -*- +############################################################################## +# Compiler settings +# +CC = $(CC_PREFIX) avr-gcc +OBJCOPY = avr-objcopy +OBJDUMP = avr-objdump +SIZE = avr-size +AR = avr-ar +NM = avr-nm +HEX = $(OBJCOPY) -O $(FORMAT) -R .eeprom -R .fuse -R .lock -R .signature +EEP = $(OBJCOPY) -j .eeprom --set-section-flags=.eeprom="alloc,load" --change-section-lma .eeprom=0 --no-change-warnings -O $(FORMAT) +BIN = + +COMPILEFLAGS += -funsigned-char +COMPILEFLAGS += -funsigned-bitfields +COMPILEFLAGS += -ffunction-sections +COMPILEFLAGS += -fdata-sections +COMPILEFLAGS += -fpack-struct +COMPILEFLAGS += -fshort-enums + +ASFLAGS += $(AVR_ASFLAGS) + +CFLAGS += $(COMPILEFLAGS) $(AVR_CFLAGS) +CFLAGS += -fno-inline-small-functions +CFLAGS += -fno-strict-aliasing + +CXXFLAGS += $(COMPILEFLAGS) +CXXFLAGS += -fno-exceptions -std=c++11 + +LDFLAGS +=-Wl,--gc-sections + +OPT_DEFS += -DF_CPU=$(F_CPU)UL + +MCUFLAGS = -mmcu=$(MCU) + +# List any extra directories to look for libraries here. +#     Each directory must be seperated by a space. +#     Use forward slashes for directory separators. +#     For a directory that has spaces, enclose it in quotes. +EXTRALIBDIRS = + + +#---------------- External Memory Options ---------------- + +# 64 KB of external RAM, starting after internal RAM (ATmega128!), +# used for variables (.data/.bss) and heap (malloc()). +#EXTMEMOPTS = -Wl,-Tdata=0x801100,--defsym=__heap_end=0x80ffff + +# 64 KB of external RAM, starting after internal RAM (ATmega128!), +# only used for heap (malloc()). +#EXTMEMOPTS = -Wl,--section-start,.data=0x801100,--defsym=__heap_end=0x80ffff + +EXTMEMOPTS = + +#---------------- Debugging Options ---------------- + +# Debugging format. +#     Native formats for AVR-GCC's -g are dwarf-2 [default] or stabs. +#     AVR Studio 4.10 requires dwarf-2. +#     AVR [Extended] COFF format requires stabs, plus an avr-objcopy run. +DEBUG = dwarf-2 + +# For simulavr only - target MCU frequency. +DEBUG_MFREQ = $(F_CPU) + +# Set the DEBUG_UI to either gdb or insight. +# DEBUG_UI = gdb +DEBUG_UI = insight + +# Set the debugging back-end to either avarice, simulavr. +DEBUG_BACKEND = avarice +#DEBUG_BACKEND = simulavr + +# GDB Init Filename. +GDBINIT_FILE = __avr_gdbinit + +# When using avarice settings for the JTAG +JTAG_DEV = /dev/com1 + +# Debugging port used to communicate between GDB / avarice / simulavr. +DEBUG_PORT = 4242 + +# Debugging host used to communicate between GDB / avarice / simulavr, normally +#     just set to localhost unless doing some sort of crazy debugging when +#     avarice is running on a different computer. +DEBUG_HOST = localhost + +#============================================================================ + +# Convert hex to bin. +bin: $(BUILD_DIR)/$(TARGET).hex +ifeq ($(BOOTLOADER),lufa-ms) +	$(eval BIN_PADDING=$(shell n=`expr 32768 - $(BOOTLOADER_SIZE)` && echo $$(($$n)) || echo 0)) +	$(OBJCOPY) -Iihex -Obinary $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin --pad-to $(BIN_PADDING) +else +	$(OBJCOPY) -Iihex -Obinary $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin +endif +	$(COPY) $(BUILD_DIR)/$(TARGET).bin $(TARGET).bin; + +# copy bin to FLASH.bin +flashbin: bin +	$(COPY) $(BUILD_DIR)/$(TARGET).bin FLASH.bin; + +# Generate avr-gdb config/init file which does the following: +#     define the reset signal, load the target file, connect to target, and set +#     a breakpoint at main(). +gdb-config: +	@$(REMOVE) $(GDBINIT_FILE) +	@echo define reset >> $(GDBINIT_FILE) +	@echo SIGNAL SIGHUP >> $(GDBINIT_FILE) +	@echo end >> $(GDBINIT_FILE) +	@echo file $(BUILD_DIR)/$(TARGET).elf >> $(GDBINIT_FILE) +	@echo target remote $(DEBUG_HOST):$(DEBUG_PORT)  >> $(GDBINIT_FILE) +ifeq ($(DEBUG_BACKEND),simulavr) +	@echo load  >> $(GDBINIT_FILE) +endif +	@echo break main >> $(GDBINIT_FILE) + +debug: gdb-config $(BUILD_DIR)/$(TARGET).elf +ifeq ($(DEBUG_BACKEND), avarice) +	@echo Starting AVaRICE - Press enter when "waiting to connect" message displays. +	@$(WINSHELL) /c start avarice --jtag $(JTAG_DEV) --erase --program --file \ +	$(BUILD_DIR)/$(TARGET).elf $(DEBUG_HOST):$(DEBUG_PORT) +	@$(WINSHELL) /c pause + +else +	@$(WINSHELL) /c start simulavr --gdbserver --device $(MCU) --clock-freq \ +	$(DEBUG_MFREQ) --port $(DEBUG_PORT) +endif +	@$(WINSHELL) /c start avr-$(DEBUG_UI) --command=$(GDBINIT_FILE) + + + + +# Convert ELF to COFF for use in debugging / simulating in AVR Studio or VMLAB. +COFFCONVERT = $(OBJCOPY) --debugging +COFFCONVERT += --change-section-address .data-0x800000 +COFFCONVERT += --change-section-address .bss-0x800000 +COFFCONVERT += --change-section-address .noinit-0x800000 +COFFCONVERT += --change-section-address .eeprom-0x810000 + + + +coff: $(BUILD_DIR)/$(TARGET).elf +	@$(SECHO) $(MSG_COFF) $(BUILD_DIR)/$(TARGET).cof +	$(COFFCONVERT) -O coff-avr $< $(BUILD_DIR)/$(TARGET).cof + + +extcoff: $(BUILD_DIR)/$(TARGET).elf +	@$(SECHO) $(MSG_EXTENDED_COFF) $(BUILD_DIR)/$(TARGET).cof +	$(COFFCONVERT) -O coff-ext-avr $< $(BUILD_DIR)/$(TARGET).cof + +ifeq ($(strip $(BOOTLOADER)), qmk-dfu) +QMK_BOOTLOADER_TYPE = DFU +else ifeq ($(strip $(BOOTLOADER)), qmk-hid) +QMK_BOOTLOADER_TYPE = HID +endif + +bootloader: +ifeq ($(strip $(QMK_BOOTLOADER_TYPE)),) +	$(error Please set BOOTLOADER to "qmk-dfu" or "qmk-hid" first!) +else +	make -C lib/lufa/Bootloaders/$(QMK_BOOTLOADER_TYPE)/ clean +	$(QMK_BIN) generate-dfu-header --quiet --keyboard $(KEYBOARD) --output lib/lufa/Bootloaders/$(QMK_BOOTLOADER_TYPE)/Keyboard.h +	$(eval MAX_SIZE=$(shell n=`$(CC) -E -mmcu=$(MCU) -D__ASSEMBLER__ $(CFLAGS) $(OPT_DEFS) platforms/avr/bootloader_size.c 2> /dev/null | sed -ne 's/\r//;/^#/n;/^AVR_SIZE:/,$${s/^AVR_SIZE: //;p;}'` && echo $$(($$n)) || echo 0)) +	$(eval PROGRAM_SIZE_KB=$(shell n=`expr $(MAX_SIZE) / 1024` && echo $$(($$n)) || echo 0)) +	$(eval BOOT_SECTION_SIZE_KB=$(shell n=`expr  $(BOOTLOADER_SIZE) / 1024` && echo $$(($$n)) || echo 0)) +	$(eval FLASH_SIZE_KB=$(shell n=`expr $(PROGRAM_SIZE_KB) + $(BOOT_SECTION_SIZE_KB)` && echo $$(($$n)) || echo 0)) +	make -C lib/lufa/Bootloaders/$(QMK_BOOTLOADER_TYPE)/ MCU=$(MCU) ARCH=$(ARCH) F_CPU=$(F_CPU) FLASH_SIZE_KB=$(FLASH_SIZE_KB) BOOT_SECTION_SIZE_KB=$(BOOT_SECTION_SIZE_KB) +	printf "Bootloader$(QMK_BOOTLOADER_TYPE).hex copied to $(TARGET)_bootloader.hex\n" +	cp lib/lufa/Bootloaders/$(QMK_BOOTLOADER_TYPE)/Bootloader$(QMK_BOOTLOADER_TYPE).hex $(TARGET)_bootloader.hex +endif + +production: $(BUILD_DIR)/$(TARGET).hex bootloader cpfirmware +	@cat $(BUILD_DIR)/$(TARGET).hex | awk '/^:00000001FF/ == 0' > $(TARGET)_production.hex +	@cat $(TARGET)_bootloader.hex >> $(TARGET)_production.hex +	echo "File sizes:" +	$(SIZE) $(TARGET).hex $(TARGET)_bootloader.hex $(TARGET)_production.hex diff --git a/platforms/avr/platform_deps.h b/platforms/avr/platform_deps.h new file mode 100644 index 0000000000..45d9dcebfa --- /dev/null +++ b/platforms/avr/platform_deps.h @@ -0,0 +1,20 @@ +/* Copyright 2021 QMK + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <avr/pgmspace.h> +#include <avr/io.h> +#include <avr/interrupt.h> diff --git a/platforms/avr/printf.c b/platforms/avr/printf.c new file mode 100644 index 0000000000..9ad7a38693 --- /dev/null +++ b/platforms/avr/printf.c @@ -0,0 +1,20 @@ +/* +Copyright 2011 Jun Wako <[email protected]> + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program.  If not, see <http://www.gnu.org/licenses/>. +*/ +#include "xprintf.h" +#include "sendchar.h" + +void print_set_sendchar(sendchar_func_t func) { xdev_out(func); } diff --git a/platforms/avr/printf.mk b/platforms/avr/printf.mk new file mode 100644 index 0000000000..060ad88c57 --- /dev/null +++ b/platforms/avr/printf.mk @@ -0,0 +1,2 @@ +TMK_COMMON_SRC += $(PLATFORM_COMMON_DIR)/xprintf.S +TMK_COMMON_SRC += $(PLATFORM_COMMON_DIR)/printf.c diff --git a/platforms/avr/sleep_led.c b/platforms/avr/sleep_led.c new file mode 100644 index 0000000000..9a3b52abe5 --- /dev/null +++ b/platforms/avr/sleep_led.c @@ -0,0 +1,124 @@ +#include <stdint.h> +#include <avr/io.h> +#include <avr/interrupt.h> +#include <avr/pgmspace.h> +#include "led.h" +#include "sleep_led.h" + +#ifndef SLEEP_LED_TIMER +#    define SLEEP_LED_TIMER 1 +#endif + +#if SLEEP_LED_TIMER == 1 +#    define TCCRxB TCCR1B +#    define TIMERx_COMPA_vect TIMER1_COMPA_vect +#    if defined(__AVR_ATmega32A__)  // This MCU has only one TIMSK register +#        define TIMSKx TIMSK +#    else +#        define TIMSKx TIMSK1 +#    endif +#    define OCIExA OCIE1A +#    define OCRxx OCR1A +#elif SLEEP_LED_TIMER == 3 +#    define TCCRxB TCCR3B +#    define TIMERx_COMPA_vect TIMER3_COMPA_vect +#    define TIMSKx TIMSK3 +#    define OCIExA OCIE3A +#    define OCRxx OCR3A +#else +error("Invalid SLEEP_LED_TIMER config") +#endif + +/* Software PWM + *  ______           ______           __ + * |  ON  |___OFF___|  ON  |___OFF___|   .... + * |<-------------->|<-------------->|<- .... + *     PWM period       PWM period + * + * 256              interrupts/period[resolution] + * 64               periods/second[frequency] + * 256*64           interrupts/second + * F_CPU/(256*64)   clocks/interrupt + */ +#define SLEEP_LED_TIMER_TOP F_CPU / (256 * 64) + +/** \brief Sleep LED initialization + * + * FIXME: needs doc + */ +void sleep_led_init(void) { +    /* Timer1 setup */ +    /* CTC mode */ +    TCCRxB |= _BV(WGM12); +    /* Clock selelct: clk/1 */ +    TCCRxB |= _BV(CS10); +    /* Set TOP value */ +    uint8_t sreg = SREG; +    cli(); +    OCRxx = SLEEP_LED_TIMER_TOP; +    SREG  = sreg; +} + +/** \brief Sleep LED enable + * + * FIXME: needs doc + */ +void sleep_led_enable(void) { +    /* Enable Compare Match Interrupt */ +    TIMSKx |= _BV(OCIExA); +} + +/** \brief Sleep LED disable + * + * FIXME: needs doc + */ +void sleep_led_disable(void) { +    /* Disable Compare Match Interrupt */ +    TIMSKx &= ~_BV(OCIExA); +} + +/** \brief Sleep LED toggle + * + * FIXME: needs doc + */ +void sleep_led_toggle(void) { +    /* Disable Compare Match Interrupt */ +    TIMSKx ^= _BV(OCIExA); +} + +/** \brief Breathing Sleep LED brighness(PWM On period) table + * + * (64[steps] * 4[duration]) / 64[PWM periods/s] = 4 second breath cycle + * + * https://www.wolframalpha.com/input/?i=sin%28x%2F64*pi%29**8+*+255%2C+x%3D0+to+63 + * (0..63).each {|x| p ((sin(x/64.0*PI)**8)*255).to_i } + */ +static const uint8_t breathing_table[64] PROGMEM = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 4, 6, 10, 15, 23, 32, 44, 58, 74, 93, 113, 135, 157, 179, 199, 218, 233, 245, 252, 255, 252, 245, 233, 218, 199, 179, 157, 135, 113, 93, 74, 58, 44, 32, 23, 15, 10, 6, 4, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +ISR(TIMERx_COMPA_vect) { +    /* Software PWM +     * timer:1111 1111 1111 1111 +     *       \_____/\/ \_______/____  count(0-255) +     *          \    \______________  duration of step(4) +     *           \__________________  index of step table(0-63) +     */ +    static union { +        uint16_t row; +        struct { +            uint8_t count : 8; +            uint8_t duration : 2; +            uint8_t index : 6; +        } pwm; +    } timer = {.row = 0}; + +    timer.row++; + +    // LED on +    if (timer.pwm.count == 0) { +        led_set(1 << USB_LED_CAPS_LOCK); +    } +    // LED off +    if (timer.pwm.count == pgm_read_byte(&breathing_table[timer.pwm.index])) { +        led_set(0); +    } +} diff --git a/platforms/avr/suspend.c b/platforms/avr/suspend.c new file mode 100644 index 0000000000..b614746e6c --- /dev/null +++ b/platforms/avr/suspend.c @@ -0,0 +1,152 @@ +#include <stdbool.h> +#include <avr/sleep.h> +#include <avr/wdt.h> +#include <avr/interrupt.h> +#include "matrix.h" +#include "action.h" +#include "suspend.h" +#include "timer.h" +#include "led.h" +#include "host.h" + +#ifdef PROTOCOL_LUFA +#    include "lufa.h" +#endif +#ifdef PROTOCOL_VUSB +#    include "vusb.h" +#endif + +/** \brief Suspend idle + * + * FIXME: needs doc + */ +void suspend_idle(uint8_t time) { +    cli(); +    set_sleep_mode(SLEEP_MODE_IDLE); +    sleep_enable(); +    sei(); +    sleep_cpu(); +    sleep_disable(); +} + +// TODO: This needs some cleanup + +#if !defined(NO_SUSPEND_POWER_DOWN) && defined(WDT_vect) + +// clang-format off +#define wdt_intr_enable(value) \ +__asm__ __volatile__ ( \ +    "in __tmp_reg__,__SREG__" "\n\t" \ +    "cli" "\n\t" \ +    "wdr" "\n\t" \ +    "sts %0,%1" "\n\t" \ +    "out __SREG__,__tmp_reg__" "\n\t" \ +    "sts %0,%2" "\n\t" \ +    : /* no outputs */ \ +    : "M" (_SFR_MEM_ADDR(_WD_CONTROL_REG)), \ +    "r" (_BV(_WD_CHANGE_BIT) | _BV(WDE)), \ +    "r" ((uint8_t) ((value & 0x08 ? _WD_PS3_MASK : 0x00) | _BV(WDIE) | (value & 0x07))) \ +    : "r0" \ +) +// clang-format on + +/** \brief Power down MCU with watchdog timer + * + * wdto: watchdog timer timeout defined in <avr/wdt.h> + *          WDTO_15MS + *          WDTO_30MS + *          WDTO_60MS + *          WDTO_120MS + *          WDTO_250MS + *          WDTO_500MS + *          WDTO_1S + *          WDTO_2S + *          WDTO_4S + *          WDTO_8S + */ +static uint8_t wdt_timeout = 0; + +/** \brief Power down + * + * FIXME: needs doc + */ +static void power_down(uint8_t wdto) { +    wdt_timeout = wdto; + +    // Watchdog Interrupt Mode +    wdt_intr_enable(wdto); + +    // TODO: more power saving +    // See PicoPower application note +    // - I/O port input with pullup +    // - prescale clock +    // - BOD disable +    // - Power Reduction Register PRR +    set_sleep_mode(SLEEP_MODE_PWR_DOWN); +    sleep_enable(); +    sei(); +    sleep_cpu(); +    sleep_disable(); + +    // Disable watchdog after sleep +    wdt_disable(); +} +#endif + +/** \brief Suspend power down + * + * FIXME: needs doc + */ +void suspend_power_down(void) { +#ifdef PROTOCOL_LUFA +    if (USB_DeviceState == DEVICE_STATE_Configured) return; +#endif +#ifdef PROTOCOL_VUSB +    if (!vusb_suspended) return; +#endif + +    suspend_power_down_quantum(); + +#ifndef NO_SUSPEND_POWER_DOWN +    // Enter sleep state if possible (ie, the MCU has a watchdog timeout interrupt) +#    if defined(WDT_vect) +    power_down(WDTO_15MS); +#    endif +#endif +} + +__attribute__((weak)) void matrix_power_up(void) {} +__attribute__((weak)) void matrix_power_down(void) {} +bool                       suspend_wakeup_condition(void) { +    matrix_power_up(); +    matrix_scan(); +    matrix_power_down(); +    for (uint8_t r = 0; r < MATRIX_ROWS; r++) { +        if (matrix_get_row(r)) return true; +    } +    return false; +} + +/** \brief run immediately after wakeup + * + * FIXME: needs doc + */ +void suspend_wakeup_init(void) { +    // clear keyboard state +    clear_keyboard(); + +    suspend_wakeup_init_quantum(); +} + +#if !defined(NO_SUSPEND_POWER_DOWN) && defined(WDT_vect) +/* watchdog timeout */ +ISR(WDT_vect) { +    // compensate timer for sleep +    switch (wdt_timeout) { +        case WDTO_15MS: +            timer_count += 15 + 2;  // WDTO_15MS + 2(from observation) +            break; +        default:; +    } +} +#endif diff --git a/platforms/avr/timer.c b/platforms/avr/timer.c new file mode 100644 index 0000000000..c2e6c6e081 --- /dev/null +++ b/platforms/avr/timer.c @@ -0,0 +1,133 @@ +/* +Copyright 2011 Jun Wako <[email protected]> + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program.  If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <avr/io.h> +#include <avr/interrupt.h> +#include <util/atomic.h> +#include <stdint.h> +#include "timer_avr.h" +#include "timer.h" + +// counter resolution 1ms +// NOTE: union { uint32_t timer32; struct { uint16_t dummy; uint16_t timer16; }} +volatile uint32_t timer_count; + +/** \brief timer initialization + * + * FIXME: needs doc + */ +void timer_init(void) { +#if TIMER_PRESCALER == 1 +    uint8_t prescaler = _BV(CS00); +#elif TIMER_PRESCALER == 8 +    uint8_t prescaler = _BV(CS01); +#elif TIMER_PRESCALER == 64 +    uint8_t prescaler = _BV(CS00) | _BV(CS01); +#elif TIMER_PRESCALER == 256 +    uint8_t prescaler = _BV(CS02); +#elif TIMER_PRESCALER == 1024 +    uint8_t prescaler = _BV(CS00) | _BV(CS02); +#else +#    error "Timer prescaler value is not valid" +#endif + +#if defined(__AVR_ATmega32A__) +    // Timer0 CTC mode +    TCCR0 = _BV(WGM01) | prescaler; + +    OCR0  = TIMER_RAW_TOP; +    TIMSK = _BV(OCIE0); +#elif defined(__AVR_ATtiny85__) +    // Timer0 CTC mode +    TCCR0A = _BV(WGM01); +    TCCR0B = prescaler; + +    OCR0A = TIMER_RAW_TOP; +    TIMSK = _BV(OCIE0A); +#else +    // Timer0 CTC mode +    TCCR0A = _BV(WGM01); +    TCCR0B = prescaler; + +    OCR0A  = TIMER_RAW_TOP; +    TIMSK0 = _BV(OCIE0A); +#endif +} + +/** \brief timer clear + * + * FIXME: needs doc + */ +inline void timer_clear(void) { +    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { timer_count = 0; } +} + +/** \brief timer read + * + * FIXME: needs doc + */ +inline uint16_t timer_read(void) { +    uint32_t t; + +    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { t = timer_count; } + +    return (t & 0xFFFF); +} + +/** \brief timer read32 + * + * FIXME: needs doc + */ +inline uint32_t timer_read32(void) { +    uint32_t t; + +    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { t = timer_count; } + +    return t; +} + +/** \brief timer elapsed + * + * FIXME: needs doc + */ +inline uint16_t timer_elapsed(uint16_t last) { +    uint32_t t; + +    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { t = timer_count; } + +    return TIMER_DIFF_16((t & 0xFFFF), last); +} + +/** \brief timer elapsed32 + * + * FIXME: needs doc + */ +inline uint32_t timer_elapsed32(uint32_t last) { +    uint32_t t; + +    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { t = timer_count; } + +    return TIMER_DIFF_32(t, last); +} + +// excecuted once per 1ms.(excess for just timer count?) +#ifndef __AVR_ATmega32A__ +#    define TIMER_INTERRUPT_VECTOR TIMER0_COMPA_vect +#else +#    define TIMER_INTERRUPT_VECTOR TIMER0_COMP_vect +#endif +ISR(TIMER_INTERRUPT_VECTOR, ISR_NOBLOCK) { timer_count++; } diff --git a/platforms/avr/timer_avr.h b/platforms/avr/timer_avr.h new file mode 100644 index 0000000000..c1b726bd01 --- /dev/null +++ b/platforms/avr/timer_avr.h @@ -0,0 +1,39 @@ +/* +Copyright 2011 Jun Wako <[email protected]> + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program.  If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <stdint.h> + +#ifndef TIMER_PRESCALER +#    if F_CPU > 16000000 +#        define TIMER_PRESCALER 256 +#    elif F_CPU > 2000000 +#        define TIMER_PRESCALER 64 +#    elif F_CPU > 250000 +#        define TIMER_PRESCALER 8 +#    else +#        define TIMER_PRESCALER 1 +#    endif +#endif +#define TIMER_RAW_FREQ (F_CPU / TIMER_PRESCALER) +#define TIMER_RAW TCNT0 +#define TIMER_RAW_TOP (TIMER_RAW_FREQ / 1000) + +#if (TIMER_RAW_TOP > 255) +#    error "Timer0 can't count 1ms at this clock freq. Use larger prescaler." +#endif diff --git a/platforms/avr/xprintf.S b/platforms/avr/xprintf.S new file mode 100644 index 0000000000..c5a414c35c --- /dev/null +++ b/platforms/avr/xprintf.S @@ -0,0 +1,498 @@ +;---------------------------------------------------------------------------; +; Extended itoa, puts, printf and atoi                     (C)ChaN, 2011 +;---------------------------------------------------------------------------; + +				// Base size is 152 bytes +#define	CR_CRLF		0	// Convert \n to \r\n (+10 bytes) +#define USE_XPRINTF	1	// Enable xprintf function (+194 bytes) +#define USE_XSPRINTF	0	// Add xsprintf function (+78 bytes) +#define USE_XFPRINTF	0	// Add xfprintf function (+54 bytes) +#define USE_XATOI	0	// Enable xatoi function (+182 bytes) + + +#if FLASHEND > 0x1FFFF +#error xitoa module does not support 256K devices +#endif + +.nolist +#include <avr/io.h>	// Include device specific definitions. +.list + +#ifdef SPM_PAGESIZE	// Recent devices have "lpm Rd,Z+" and "movw". +.macro	_LPMI	reg +	lpm	\reg, Z+ +.endm +.macro	_MOVW	dh,dl, sh,sl +	movw	\dl, \sl +.endm +#else			// Earlier devices do not have "lpm Rd,Z+" nor "movw". +.macro	_LPMI	reg +	lpm +	mov	\reg, r0 +	adiw	ZL, 1 +.endm +.macro	_MOVW	dh,dl, sh,sl +	mov	\dl, \sl +	mov	\dh, \sh +.endm +#endif + + + +;--------------------------------------------------------------------------- +; Stub function to forward to user output function +; +;Prototype: void xputc (char chr	// a character to be output +;			); +;Size: 12/12 words + +.section .bss +.global xfunc_out	; xfunc_out must be initialized before using this module. +xfunc_out:	.ds.w	1 +.section .text + + +.func xputc +.global xputc +xputc: +#if CR_CRLF +	cpi	r24, 10		;LF --> CRLF +	brne	1f		; +	ldi	r24, 13		; +	rcall	1f		; +	ldi	r24, 10		;/ +1: +#endif +	push	ZH +	push	ZL +	lds	ZL, xfunc_out+0	;Pointer to the registered output function. +	lds	ZH, xfunc_out+1	;/ +	sbiw	ZL, 0		;Skip if null +	breq	2f		;/ +	icall +2:	pop	ZL +	pop	ZH +	ret +.endfunc + + + +;--------------------------------------------------------------------------- +; Direct ROM string output +; +;Prototype: void xputs (const char *str_p // rom string to be output +;			); + +.func xputs +.global xputs +xputs: +	_MOVW	ZH,ZL, r25,r24	; Z = pointer to rom string +1:	_LPMI	r24 +	cpi	r24, 0 +	breq	2f +	rcall	xputc +	rjmp	1b +2:	ret +.endfunc + + +;--------------------------------------------------------------------------- +; Extended direct numeral string output (32bit version) +; +;Prototype: void xitoa (long value,	// value to be output +;                       char radix,	// radix +;                       char width);	// minimum width +; + +.func xitoa +.global xitoa +xitoa: +				;r25:r22 = value, r20 = base, r18 = digits +	clr	r31		;r31 = stack level +	ldi	r30, ' '	;r30 = sign +	ldi	r19, ' '	;r19 = filler +	sbrs	r20, 7		;When base indicates signd format and the value +	rjmp	0f		;is minus, add a '-'. +	neg	r20		; +	sbrs	r25, 7		; +	rjmp	0f		; +	ldi	r30, '-'	; +	com	r22		; +	com	r23		; +	com	r24		; +	com	r25		; +	adc	r22, r1		; +	adc	r23, r1		; +	adc	r24, r1		; +	adc	r25, r1		;/ +0:	sbrs	r18, 7		;When digits indicates zero filled, +	rjmp	1f		;filler is '0'. +	neg	r18		; +	ldi	r19, '0'	;/ +				;----- string conversion loop +1:	ldi	r21, 32		;r26 = r25:r22 % r20 +	clr	r26		;r25:r22 /= r20 +2:	lsl	r22		; +	rol	r23		; +	rol	r24		; +	rol	r25		; +	rol	r26		; +	cp	r26, r20	; +	brcs	3f		; +	sub	r26, r20	; +	inc	r22		; +3:	dec	r21		; +	brne	2b		;/ +	cpi	r26, 10		;r26 is a numeral digit '0'-'F' +	brcs	4f		; +	subi	r26, -7		; +4:	subi	r26, -'0'	;/ +	push	r26		;Stack it +	inc	r31		;/ +	cp	r22, r1		;Repeat until r25:r22 gets zero +	cpc	r23, r1		; +	cpc	r24, r1		; +	cpc	r25, r1		; +	brne	1b		;/ + +	cpi	r30, '-'	;Minus sign if needed +	brne	5f		; +	push	r30		; +	inc	r31		;/ +5:	cp	r31, r18	;Filler +	brcc	6f		; +	push	r19		; +	inc	r31		; +	rjmp	5b		;/ + +6:	pop	r24		;Flush stacked digits and exit +	rcall	xputc		; +	dec	r31		; +	brne	6b		;/ + +	ret +.endfunc + + + +;---------------------------------------------------------------------------; +; Formatted string output (16/32bit version) +; +;Prototype: +; void __xprintf (const char *format_p, ...); +; void __xsprintf(char*, const char *format_p, ...); +; void __xfprintf(void(*func)(char), const char *format_p, ...); +; + +#if USE_XPRINTF + +.func xvprintf +xvprintf: +	ld	ZL, Y+		;Z = pointer to format string +	ld	ZH, Y+		;/ + +0:	_LPMI	r24		;Get a format char +	cpi	r24, 0		;End of format string? +	breq	90f		;/ +	cpi	r24, '%'	;Is format? +	breq	20f		;/ +1:	rcall	xputc		;Put a normal character +	rjmp	0b		;/ +90:	ret + +20:	ldi	r18, 0		;r18: digits +	clt			;T: filler +	_LPMI	r21		;Get flags +	cpi	r21, '%'	;Is a %? +	breq	1b		;/ +	cpi	r21, '0'	;Zero filled? +	brne	23f		; +	set			;/ +22:	_LPMI	r21		;Get width +23:	cpi	r21, '9'+1	; +	brcc	24f		; +	subi	r21, '0'	; +	brcs	90b		; +	lsl	r18		; +	mov	r0, r18		; +	lsl	r18		; +	lsl	r18		; +	add	r18, r0		; +	add	r18, r21	; +	rjmp	22b		;/ + +24:	brtc	25f		;get value (low word) +	neg	r18		; +25:	ld	r24, Y+		; +	ld	r25, Y+		;/ +	cpi	r21, 'c'	;Is type character? +	breq	1b		;/ +	cpi	r21, 's'	;Is type RAM string? +	breq	50f		;/ +	cpi	r21, 'S'	;Is type ROM string? +	breq	60f		;/ +	_MOVW	r23,r22,r25,r24	;r25:r22 = value +	clr	r24		; +	clr	r25		; +	clt			;/ +	cpi	r21, 'l'	;Is long int? +	brne	26f		; +	ld	r24, Y+		;get value (high word) +	ld	r25, Y+		; +	set			; +	_LPMI	r21		;/ +26:	cpi	r21, 'd'	;Is type signed decimal? +	brne	27f		;/ +	ldi	r20, -10	; +	brts	40f		; +	sbrs	r23, 7		; +	rjmp	40f		; +	ldi	r24, -1		; +	ldi	r25, -1		; +	rjmp	40f		;/ +27:	cpi	r21, 'u'	;Is type unsigned decimal? +	ldi	r20, 10		; +	breq	40f		;/ +	cpi	r21, 'X'	;Is type hexdecimal? +	ldi	r20, 16		; +	breq	40f		;/ +	cpi	r21, 'b'	;Is type binary? +	ldi	r20, 2		; +	breq	40f		;/ +	ret			;abort +40:	push	ZH		;Output the value +	push	ZL		; +	rcall	xitoa		; +42:	pop	ZL		; +	pop	ZH		; +	rjmp	0b		;/ + +50:	push	ZH		;Put a string on the RAM +	push	ZL +	_MOVW	ZH,ZL, r25,r24 +51:	ld	r24, Z+ +	cpi	r24, 0 +	breq	42b +	rcall	xputc +	rjmp	51b + +60:	push	ZH		;Put a string on the ROM +	push	ZL +	rcall	xputs +	rjmp	42b +.endfunc + + +.func __xprintf +.global __xprintf +__xprintf: +	push	YH +	push	YL +	in	YL, _SFR_IO_ADDR(SPL) +#ifdef SPH +	in	YH, _SFR_IO_ADDR(SPH) +#else +	clr	YH +#endif +	adiw	YL, 5		;Y = pointer to arguments +	rcall	xvprintf +	pop	YL +	pop	YH +	ret +.endfunc + + +#if USE_XSPRINTF + +.func __xsprintf +putram: +	_MOVW	ZH,ZL, r15,r14 +	st	Z+, r24 +	_MOVW	r15,r14, ZH,ZL +	ret +.global __xsprintf +__xsprintf: +	push	YH +	push	YL +	in	YL, _SFR_IO_ADDR(SPL) +#ifdef SPH +	in	YH, _SFR_IO_ADDR(SPH) +#else +	clr	YH +#endif +	adiw	YL, 5		;Y = pointer to arguments +	lds	ZL, xfunc_out+0	;Save registered output function +	lds	ZH, xfunc_out+1	; +	push	ZL		; +	push	ZH		;/ +	ldi	ZL, lo8(pm(putram));Set local output function +	ldi	ZH, hi8(pm(putram)); +	sts	xfunc_out+0, ZL	; +	sts	xfunc_out+1, ZH	;/ +	push	r15		;Initialize pointer to string buffer +	push	r14		; +	ld	r14, Y+		; +	ld	r15, Y+		;/ +	rcall	xvprintf +	_MOVW	ZH,ZL, r15,r14	;Terminate string +	st	Z, r1		; +	pop	r14		; +	pop	r15		;/ +	pop	ZH		;Restore registered output function +	pop	ZL		; +	sts	xfunc_out+0, ZL	; +	sts	xfunc_out+1, ZH	;/ +	pop	YL +	pop	YH +	ret +.endfunc +#endif + + +#if USE_XFPRINTF +.func __xfprintf +.global __xfprintf +__xfprintf: +	push	YH +	push	YL +	in	YL, _SFR_IO_ADDR(SPL) +#ifdef SPH +	in	YH, _SFR_IO_ADDR(SPH) +#else +	clr	YH +#endif +	adiw	YL, 5		;Y = pointer to arguments +	lds	ZL, xfunc_out+0	;Save registered output function +	lds	ZH, xfunc_out+1	; +	push	ZL		; +	push	ZH		;/ +	ld	ZL, Y+		;Set output function +	ld	ZH, Y+		; +	sts	xfunc_out+0, ZL	; +	sts	xfunc_out+1, ZH	;/ +	rcall	xvprintf +	pop	ZH		;Restore registered output function +	pop	ZL		; +	sts	xfunc_out+0, ZL	; +	sts	xfunc_out+1, ZH	;/ +	pop	YL +	pop	YH +	ret +.endfunc +#endif + +#endif + + + +;--------------------------------------------------------------------------- +; Extended numeral string input +; +;Prototype: +; char xatoi (           /* 1: Successful, 0: Failed */ +;      const char **str, /* pointer to pointer to source string */ +;      long *res         /* result */ +; ); +; + + +#if USE_XATOI +.func xatoi +.global xatoi +xatoi: +	_MOVW	r1, r0, r23, r22 +	_MOVW	XH, XL, r25, r24 +	ld	ZL, X+ +	ld	ZH, X+ +	clr	r18		;r21:r18 = 0; +	clr	r19		; +	clr	r20		; +	clr	r21		;/ +	clt			;T = 0; + +	ldi	r25, 10		;r25 = 10; +	rjmp	41f		;/ +40:	adiw	ZL, 1		;Z++; +41:	ld	r22, Z		;r22 = *Z; +	cpi	r22, ' '	;if(r22 == ' ') continue +	breq	40b		;/ +	brcs	70f		;if(r22 < ' ') error; +	cpi	r22, '-'	;if(r22 == '-') { +	brne	42f		; T = 1; +	set			; continue; +	rjmp	40b		;} +42:	cpi	r22, '9'+1	;if(r22 > '9') error; +	brcc	70f		;/ +	cpi	r22, '0'	;if(r22 < '0') error; +	brcs	70f		;/ +	brne	51f		;if(r22 > '0') cv_start; +	ldi	r25, 8		;r25 = 8; +	adiw	ZL, 1		;r22 = *(++Z); +	ld	r22, Z		;/ +	cpi	r22, ' '+1	;if(r22 <= ' ') exit; +	brcs	80f		;/ +	cpi	r22, 'b'	;if(r22 == 'b') { +	brne	43f		; r25 = 2; +	ldi	r25, 2		; cv_start; +	rjmp	50f		;} +43:	cpi	r22, 'x'	;if(r22 != 'x') error; +	brne	51f		;/ +	ldi	r25, 16		;r25 = 16; + +50:	adiw	ZL, 1		;Z++; +	ld	r22, Z		;r22 = *Z; +51:	cpi	r22, ' '+1	;if(r22 <= ' ') break; +	brcs	80f		;/ +	cpi	r22, 'a'	;if(r22 >= 'a') r22 =- 0x20; +	brcs	52f		; +	subi	r22, 0x20	;/ +52:	subi	r22, '0'	;if((r22 -= '0') < 0) error; +	brcs	70f		;/ +	cpi	r22, 10		;if(r22 >= 10) { +	brcs	53f		; r22 -= 7; +	subi	r22, 7		; if(r22 < 10) +	cpi	r22, 10		; +	brcs	70f		;} +53:	cp	r22, r25	;if(r22 >= r25) error; +	brcc	70f		;/ +60:	ldi	r24, 33		;r21:r18 *= r25; +	sub	r23, r23	; +61:	brcc	62f		; +	add	r23, r25	; +62:	lsr	r23		; +	ror	r21		; +	ror	r20		; +	ror	r19		; +	ror	r18		; +	dec	r24		; +	brne	61b		;/ +	add	r18, r22	;r21:r18 += r22; +	adc	r19, r24	; +	adc	r20, r24	; +	adc	r21, r24	;/ +	rjmp	50b		;repeat + +70:	ldi	r24, 0 +	rjmp	81f +80:	ldi	r24, 1 +81:	brtc	82f +	clr	r22 +	com	r18 +	com	r19 +	com	r20 +	com	r21 +	adc	r18, r22 +	adc	r19, r22 +	adc	r20, r22 +	adc	r21, r22 +82:	st	-X, ZH +	st	-X, ZL +	_MOVW	XH, XL, r1, r0 +	st	X+, r18 +	st	X+, r19 +	st	X+, r20 +	st	X+, r21 +	clr	r1 +	ret +.endfunc +#endif diff --git a/platforms/avr/xprintf.h b/platforms/avr/xprintf.h new file mode 100644 index 0000000000..80834f1714 --- /dev/null +++ b/platforms/avr/xprintf.h @@ -0,0 +1,103 @@ +/*--------------------------------------------------------------------------- +   Extended itoa, puts and printf                    (C)ChaN, 2011 +-----------------------------------------------------------------------------*/ + +#pragma once + +#include <inttypes.h> +#include <avr/pgmspace.h> + +#ifdef __cplusplus +extern "C" { +#endif + +extern void (*xfunc_out)(uint8_t); +#define xdev_out(func) xfunc_out = (void (*)(uint8_t))(func) + +/* This is a pointer to user defined output function. It must be initialized +   before using this modle. +*/ + +void xputc(char chr); + +/* This is a stub function to forward outputs to user defined output function. +   All outputs from this module are output via this function. +*/ + +/*-----------------------------------------------------------------------------*/ +void xputs(const char *string_p); + +/*  The string placed in the ROM is forwarded to xputc() directly. + */ + +/*-----------------------------------------------------------------------------*/ +void xitoa(long value, char radix, char width); + +/* Extended itoa(). + +      value  radix  width   output +        100     10      6   "   100" +        100     10     -6   "000100" +        100     10      0   "100" + 4294967295     10      0   "4294967295" + 4294967295    -10      0   "-1" +     655360     16     -8   "000A0000" +       1024     16      0   "400" +       0x55      2     -8   "01010101" +*/ + +/*-----------------------------------------------------------------------------*/ +#define xprintf(format, ...) __xprintf(PSTR(format), ##__VA_ARGS__) +#define xsprintf(str, format, ...) __xsprintf(str, PSTR(format), ##__VA_ARGS__) +#define xfprintf(func, format, ...) __xfprintf(func, PSTR(format), ##__VA_ARGS__) + +void __xprintf(const char *format_p, ...); /* Send formatted string to the registered device */ +// void __xsprintf(char*, const char *format_p, ...);	/* Put formatted string to the memory */ +// void __xfprintf(void(*func)(uint8_t), const char *format_p, ...); /* Send formatted string to the specified device */ + +/* Format string is placed in the ROM. The format flags is similar to printf(). + +   %[flag][width][size]type + +   flag +     A '0' means filled with '0' when output is shorter than width. +     ' ' is used in default. This is effective only numeral type. +   width +     Minimum width in decimal number. This is effective only numeral type. +     Default width is zero. +   size +     A 'l' means the argument is long(32bit). Default is short(16bit). +     This is effective only numeral type. +   type +     'c' : Character, argument is the value +     's' : String placed on the RAM, argument is the pointer +     'S' : String placed on the ROM, argument is the pointer +     'd' : Signed decimal, argument is the value +     'u' : Unsigned decimal, argument is the value +     'X' : Hexdecimal, argument is the value +     'b' : Binary, argument is the value +     '%' : '%' + +*/ + +/*-----------------------------------------------------------------------------*/ +char xatoi(char **str, long *ret); + +/* Get value of the numeral string. + +  str +    Pointer to pointer to source string + +    "0b11001010" binary +    "0377" octal +    "0xff800" hexdecimal +    "1250000" decimal +    "-25000" decimal + +  ret +    Pointer to return value +*/ + +#ifdef __cplusplus +} +#endif | 
