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 <wakojun@gmail.com> */ +/* 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 <wakojun@gmail.com> + +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 <wakojun@gmail.com> + +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 <wakojun@gmail.com> + +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 <wakojun@gmail.com> + +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 |