Firmware/Marlin/pinsDebug.h
Scott Lahteine 55d296aaf0 pinsDebug with more features, uses less RAM
I've just uploaded a major change to pinsDebug.
The big change was creating an array in FLASH that contained every
active pin definition. That reduced the RAM memory usage considerably
but increased the FLASH usage.
Creating the array requires going through the pin list twice. Rather
than having two copies of it in the code I moved the list out to another
file (pinsDebug_list.h) and then just did two #includes.
From the user’s view they’ll see the following changes:
1. Now reports all the names assigned to a pin
2. The port is now reported in addition to the pin number.
3. When PWM0A & PWM1C share a pin, both PWMs are reported
4. More PWM/Timer info is reported
One new item that may cause some concern is the usage of the LINE
predefined preprocessor macro. It may not be available if the Arduino
IDE goes to a different compiler.

Includes support for 1284 & 1286 families.

Memory usage changes when enabling PINS_DEBUGGING:
ATmega2560
FLASH
.           without   52576
.           with new  64592
.           with old  62826
.           new-out   12016
.           old-out   10250
.           new-old   1766
.
RAM
.           without   2807
.           with new  2875
.           with old  3545
.           new-out   68
.           old-out   738
.           new-old   -670

==================================================================

minor changes - mostly formatting

1) added newline to end of teensyduino file

2) changed flag name from TEENSYDUINO to TEENSYDUINO_IDE.  Got warnings
about redefining TEENSYDUINO

3) removed some trailing spaces

reduce PROGMEM size & update pin list

Reduced PROGMEM usage by
1) converting often used macro to a function
2) moved as much as possible into the function

This required creating two arrays of address pointers for the PWM
registers.

==================================================================

update with new M3, M4, M5 pin names

==================================================================

report I/O status for unused/unknown pins
2017-04-06 16:06:01 -05:00

545 lines
17 KiB
C

/**
* Marlin 3D Printer Firmware
* Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
*
* 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/>.
*
*/
bool endstop_monitor_flag = false;
#define NAME_FORMAT "%-28s" // one place to specify the format of all the sources of names
// "-" left justify, "28" minimum width of name, pad with blanks
#define IS_ANALOG(P) ((P) >= analogInputToDigitalPin(0) && ((P) <= analogInputToDigitalPin(15) || (P) <= analogInputToDigitalPin(7)))
#define AVR_ATmega2560_FAMILY (defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega1281__) || defined(__AVR_ATmega2561__) || defined(__AVR_ATmega1281__) || defined(__AVR_ATmega2561__))
#define AVR_AT90USB1286_FAMILY (defined(__AVR_AT90USB1287__) || defined(__AVR_AT90USB1286__) || defined(__AVR_AT90USB1286P__) || defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB646P__) || defined(__AVR_AT90USB647__))
#define AVR_ATmega1284_FAMILY (defined(__AVR_ATmega644__) || defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644PA__) || defined(__AVR_ATmega1284P__))
/**
* This routine minimizes RAM usage by creating a FLASH resident array to
* store the pin names, pin numbers and analog/digital flag.
*
* Creating the array in FLASH is a two pass process. The first pass puts the
* name strings into FLASH. The second pass actually creates the array.
*
* Both passes use the same pin list. The list contains two macro names. The
* actual macro definitions are changed depending on which pass is being done.
*
*/
// first pass - put the name strings into FLASH
#define _ADD_PIN_2(PIN_NAME, ENTRY_NAME) static const unsigned char ENTRY_NAME[] PROGMEM = {PIN_NAME};
#define _ADD_PIN(PIN_NAME, COUNTER) _ADD_PIN_2(PIN_NAME, entry_NAME_##COUNTER)
#define REPORT_NAME_DIGITAL(NAME, COUNTER) _ADD_PIN(#NAME, COUNTER)
#define REPORT_NAME_ANALOG(NAME, COUNTER) _ADD_PIN(#NAME, COUNTER)
#line 0 // set __LINE__ to a known value for the first pass
#include "pinsDebug_list.h"
#line 59 // set __LINE__ to the correct line number or else compiler error messages don't make sense
// manually add pins that have names that are macros which don't play well with these macros
#if SERIAL_PORT == 0 && (AVR_ATmega2560_FAMILY || AVR_ATmega1284_FAMILY)
static const unsigned char RXD_NAME[] PROGMEM = {"RXD"};
static const unsigned char TXD_NAME[] PROGMEM = {"TXD"};
#endif
/////////////////////////////////////////////////////////////////////////////
// second pass - create the array
#undef _ADD_PIN_2
#undef _ADD_PIN
#undef REPORT_NAME_DIGITAL
#undef REPORT_NAME_ANALOG
#define _ADD_PIN_2( ENTRY_NAME, NAME, IS_DIGITAL) {(const char*) ENTRY_NAME, (const char*)NAME, (const char*)IS_DIGITAL},
#define _ADD_PIN( NAME, COUNTER, IS_DIGITAL) _ADD_PIN_2( entry_NAME_##COUNTER, NAME, IS_DIGITAL)
#define REPORT_NAME_DIGITAL(NAME, COUNTER) _ADD_PIN( NAME, COUNTER, (uint8_t)1)
#define REPORT_NAME_ANALOG(NAME, COUNTER) _ADD_PIN( analogInputToDigitalPin(NAME), COUNTER, 0)
const char* const pin_array[][3] PROGMEM = {
/**
* [pin name] [pin number] [is digital or analog] 1 = digital, 0 = analog
* Each entry takes up 6 bytes in FLASH:
* 2 byte pointer to location of the name string
* 2 bytes containing the pin number
* analog pin numbers were convereted to digital when the array was created
* 2 bytes containing the digital/analog bool flag
*/
// manually add pins ...
#if SERIAL_PORT == 0
#if AVR_ATmega2560_FAMILY
{RXD_NAME, 0, 1},
{TXD_NAME, 1, 1},
#elif AVR_ATmega1284_FAMILY
{RXD_NAME, 8, 1},
{TXD_NAME, 9, 1},
#endif
#endif
#line 0 // set __LINE__ to the SAME known value for the second pass
#include "pinsDebug_list.h"
}; // done populating the array
#line 109 // set __LINE__ to the correct line number or else compiler error messages don't make sense
#define n_array (sizeof (pin_array) / sizeof (const char *))/3
#if !defined(TIMER1B) // working with Teensyduino extension so need to re-define some things
#include "pinsDebug_Teensyduino.h"
#endif
#define PWM_PRINT(V) do{ sprintf(buffer, "PWM: %4d", V); SERIAL_ECHO(buffer); }while(0)
#define PWM_CASE(N,Z) \
case TIMER##N##Z: \
if (TCCR##N##A & (_BV(COM##N##Z##1) | _BV(COM##N##Z##0))) { \
PWM_PRINT(OCR##N##Z); \
return true; \
} else return false
/**
* Print a pin's PWM status.
* Return true if it's currently a PWM pin.
*/
static bool pwm_status(uint8_t pin) {
char buffer[20]; // for the sprintf statements
switch(digitalPinToTimer(pin)) {
#if defined(TCCR0A) && defined(COM0A1)
#if defined (TIMER0A)
PWM_CASE(0,A);
#endif
PWM_CASE(0,B);
#endif
#if defined(TCCR1A) && defined(COM1A1)
PWM_CASE(1,A);
PWM_CASE(1,B);
#if defined(COM1C1) && defined (TIMER1C)
PWM_CASE(1,C);
#endif
#endif
#if defined(TCCR2A) && defined(COM2A1)
PWM_CASE(2,A);
PWM_CASE(2,B);
#endif
#if defined(TCCR3A) && defined(COM3A1)
PWM_CASE(3,A);
PWM_CASE(3,B);
#ifdef COM3C1
PWM_CASE(3,C);
#endif
#endif
#ifdef TCCR4A
PWM_CASE(4,A);
PWM_CASE(4,B);
PWM_CASE(4,C);
#endif
#if defined(TCCR5A) && defined(COM5A1)
PWM_CASE(5,A);
PWM_CASE(5,B);
PWM_CASE(5,C);
#endif
case NOT_ON_TIMER:
default:
return false;
}
SERIAL_PROTOCOLPGM(" ");
} // pwm_status
const uint8_t* const PWM_other[][3] PROGMEM = {
{&TCCR0A, &TCCR0B, &TIMSK0},
{&TCCR1A, &TCCR1B, &TIMSK1},
#if defined(TCCR2A) && defined(COM2A1)
{&TCCR2A, &TCCR2B, &TIMSK2},
#endif
#if defined(TCCR3A) && defined(COM3A1)
{&TCCR3A, &TCCR3B, &TIMSK3},
#endif
#ifdef TCCR4A
{&TCCR4A, &TCCR4B, &TIMSK4},
#endif
#if defined(TCCR5A) && defined(COM5A1)
{&TCCR5A, &TCCR5B, &TIMSK5},
#endif
};
const uint8_t* const PWM_OCR[][3] PROGMEM = {
#if defined (TIMER0A)
{&OCR0A,&OCR0B,0},
#else
{0,&OCR0B,0},
#endif
#if defined(COM1C1) && defined (TIMER1C)
{ (const uint8_t*) &OCR1A, (const uint8_t*) &OCR1B, (const uint8_t*) &OCR1C},
#else
{ (const uint8_t*) &OCR1A, (const uint8_t*) &OCR1B,0},
#endif
#if defined(TCCR2A) && defined(COM2A1)
{&OCR2A,&OCR2B,0},
#endif
#if defined(TCCR3A) && defined(COM3A1)
#if defined(COM3C1)
{ (const uint8_t*) &OCR3A, (const uint8_t*) &OCR3B, (const uint8_t*) &OCR3C},
#else
{ (const uint8_t*) &OCR3A, (const uint8_t*) &OCR3B,0},
#endif
#endif
#ifdef TCCR4A
{ (const uint8_t*) &OCR4A, (const uint8_t*) &OCR4B, (const uint8_t*) &OCR4C},
#endif
#if defined(TCCR5A) && defined(COM5A1)
{ (const uint8_t*) &OCR5A, (const uint8_t*) &OCR5B, (const uint8_t*) &OCR5C},
#endif
};
#define TCCR_A(T) pgm_read_word(&PWM_other[T][0])
#define TCCR_B(T) pgm_read_word(&PWM_other[T][1])
#define TIMSK(T) pgm_read_word(&PWM_other[T][2])
#define CS_0 0
#define CS_1 1
#define CS_2 2
#define WGM_0 0
#define WGM_1 1
#define WGM_2 3
#define WGM_3 4
#define TOIE 0
#define OCR_VAL(T, L) pgm_read_word(&PWM_OCR[T][L])
static void err_is_counter() {
SERIAL_PROTOCOLPGM(" non-standard PWM mode");
}
static void err_is_interrupt() {
SERIAL_PROTOCOLPGM(" compare interrupt enabled ");
}
static void err_prob_interrupt() {
SERIAL_PROTOCOLPGM(" overflow interrupt enabled");
}
static void can_be_used() { SERIAL_PROTOCOLPGM(" can be used as PWM "); }
void com_print(uint8_t N, uint8_t Z) {
uint8_t *TCCRA = (uint8_t*) TCCR_A(N);
SERIAL_PROTOCOLPGM(" COM");
SERIAL_PROTOCOLCHAR(N + '0');
switch(Z) {
case 'A' :
SERIAL_PROTOCOLPAIR("A: ", ((*TCCRA & (_BV(7) | _BV(6))) >> 6));
break;
case 'B' :
SERIAL_PROTOCOLPAIR("B: ", ((*TCCRA & (_BV(5) | _BV(4))) >> 4));
break;
case 'C' :
SERIAL_PROTOCOLPAIR("C: ", ((*TCCRA & (_BV(3) | _BV(2))) >> 2));
break;
}
}
void timer_prefix(uint8_t T, char L, uint8_t N){ // T - timer L - pwm n - WGM bit layout
char buffer[20]; // for the sprintf statements
uint8_t *TCCRB = (uint8_t*) TCCR_B(T);
uint8_t *TCCRA = (uint8_t*) TCCR_A(T);
uint8_t WGM = (((*TCCRB & _BV(WGM_2)) >> 1) | (*TCCRA & (_BV(WGM_0) | _BV(WGM_1))));
if (N == 4) WGM |= ((*TCCRB & _BV(WGM_3)) >> 1);
SERIAL_PROTOCOLPGM(" TIMER");
SERIAL_PROTOCOLCHAR(T + '0');
SERIAL_PROTOCOLCHAR(L);
SERIAL_PROTOCOLPGM(" ");
if (N == 3) {
uint8_t *OCRVAL8 = (uint8_t*) OCR_VAL(T, L - 'A');
PWM_PRINT(*OCRVAL8);
}
else {
uint16_t *OCRVAL16 = (uint16_t*) OCR_VAL(T, L - 'A');
PWM_PRINT(*OCRVAL16);
}
SERIAL_PROTOCOLPAIR(" WGM: ", WGM);
com_print(T,L);
SERIAL_PROTOCOLPAIR(" CS: ", (*TCCRB & (_BV(CS_0) | _BV(CS_1) | _BV(CS_2)) ));
SERIAL_PROTOCOLPGM(" TCCR");
SERIAL_PROTOCOLCHAR(T + '0');
SERIAL_PROTOCOLPAIR("A: ", *TCCRA);
SERIAL_PROTOCOLPGM(" TCCR");
SERIAL_PROTOCOLCHAR(T + '0');
SERIAL_PROTOCOLPAIR("B: ", *TCCRB);
uint8_t *TMSK = (uint8_t*) TIMSK(T);
SERIAL_PROTOCOLPGM(" TIMSK");
SERIAL_PROTOCOLCHAR(T + '0');
SERIAL_PROTOCOLPAIR(": ", *TMSK);
uint8_t OCIE = L - 'A' + 1;
if (N == 3) {if (WGM == 0 || WGM == 2 || WGM == 4 || WGM == 6) err_is_counter();}
else {if (WGM == 0 || WGM == 4 || WGM == 12 || WGM == 13) err_is_counter();}
if (TEST(*TMSK, OCIE)) err_is_interrupt();
if (TEST(*TMSK, TOIE)) err_prob_interrupt();
}
static void pwm_details(uint8_t pin) {
char buffer[20]; // for the sprintf statements
uint8_t WGM;
switch(digitalPinToTimer(pin)) {
#if defined(TCCR0A) && defined(COM0A1)
#if defined (TIMER0A)
case TIMER0A:
timer_prefix(0,'A',3);
break;
#endif
case TIMER0B:
timer_prefix(0,'B',3);
break;
#endif
#if defined(TCCR1A) && defined(COM1A1)
case TIMER1A:
timer_prefix(1,'A',4);
break;
case TIMER1B:
timer_prefix(1,'B',4);
break;
#if defined(COM1C1) && defined (TIMER1C)
case TIMER1C:
timer_prefix(1,'C',4);
break;
#endif
#endif
#if defined(TCCR2A) && defined(COM2A1)
case TIMER2A:
timer_prefix(2,'A',3);
break;
case TIMER2B:
timer_prefix(2,'B',3);
break;
#endif
#if defined(TCCR3A) && defined(COM3A1)
case TIMER3A:
timer_prefix(3,'A',4);
break;
case TIMER3B:
timer_prefix(3,'B',4);
break;
#if defined(COM3C1)
case TIMER3C:
timer_prefix(3,'C',4);
break;
#endif
#endif
#ifdef TCCR4A
case TIMER4A:
timer_prefix(4,'A',4);
break;
case TIMER4B:
timer_prefix(4,'B',4);
break;
case TIMER4C:
timer_prefix(4,'C',4);
break;
#endif
#if defined(TCCR5A) && defined(COM5A1)
case TIMER5A:
timer_prefix(5,'A',4);
break;
case TIMER5B:
timer_prefix(5,'B',4);
break;
case TIMER5C:
timer_prefix(5,'C',4);
break;
#endif
case NOT_ON_TIMER: break;
}
SERIAL_PROTOCOLPGM(" ");
// on pins that have two PWMs, print info on second PWM
#if AVR_ATmega2560_FAMILY || AVR_AT90USB1286_FAMILY
// looking for port B7 - PWMs 0A and 1C
if ( ('B' == digitalPinToPort(pin) + 64) && (0x80 == digitalPinToBitMask(pin))) {
#if !defined(TEENSYDUINO_IDE)
SERIAL_EOL;
SERIAL_PROTOCOLPGM (" . TIMER1C is also tied to this pin ");
timer_prefix(1,'C',4);
#else
SERIAL_EOL;
SERIAL_PROTOCOLPGM (" . TIMER0A is also tied to this pin ");
timer_prefix(0,'A',3);
#endif
}
#endif
} // pwm_details
bool get_pinMode(int8_t pin) { return *portModeRegister(digitalPinToPort(pin)) & digitalPinToBitMask(pin); }
#if !defined(digitalRead_mod) // use Teensyduino's version of digitalRead - it doesn't disable the PWMs
int digitalRead_mod(int8_t pin) { // same as digitalRead except the PWM stop section has been removed
uint8_t port = digitalPinToPort(pin);
return (port != NOT_A_PIN) && (*portInputRegister(port) & digitalPinToBitMask(pin)) ? HIGH : LOW;
}
#endif
void print_port(int8_t pin) { // print port number
#if defined(digitalPinToPort)
SERIAL_PROTOCOLPGM(" Port: ");
uint8_t x = digitalPinToPort(pin) + 64;
SERIAL_CHAR(x);
uint8_t temp = digitalPinToBitMask(pin);
for (x = '0'; (x < '9' && !(temp == 1)); x++){
temp = temp >> 1;
}
SERIAL_CHAR(x);
#else
SERIAL_PROTOCOLPGM(" ")
#endif
}
// pretty report with PWM info
inline void report_pin_state_extended(int8_t pin, bool ignore, bool extended = true) {
uint8_t temp_char;
char *name_mem_pointer;
char buffer[30]; // for the sprintf statements
bool found = false;
bool multi_name_pin = false;
for (uint8_t x = 0; x < n_array; x++) { // scan entire array and report all instances of this pin
if (pgm_read_byte(&pin_array[x][1]) == pin) {
if (found == true) multi_name_pin = true;
found = true;
if (multi_name_pin == false) { // report digitial and analog pin number only on the first time through
sprintf(buffer, "PIN:% 3d ", pin); // digital pin number
SERIAL_ECHO(buffer);
print_port(pin);
if (IS_ANALOG(pin)) {
sprintf(buffer, " (A%2d) ", int(pin - analogInputToDigitalPin(0))); // analog pin number
SERIAL_ECHO(buffer);
}
else SERIAL_ECHOPGM(" "); // add padding if not an analog pin
}
else SERIAL_ECHOPGM(". "); // add padding if not the first instance found
name_mem_pointer = (char*) pgm_read_word(&pin_array[x][0]);
for (uint8_t y = 0; y < 28; y++) { // always print pin name
temp_char = pgm_read_byte(name_mem_pointer + y);
if (temp_char != 0) MYSERIAL.write(temp_char);
else {
for (uint8_t i = 0; i < 28 - y; i++) MYSERIAL.write(" ");
break;
}
}
if (pin_is_protected(pin) && !ignore)
SERIAL_ECHOPGM("protected ");
else {
if (!(pgm_read_byte(&pin_array[x][2]))) {
sprintf(buffer, "Analog in =% 5d", analogRead(pin - analogInputToDigitalPin(0)));
SERIAL_ECHO(buffer);
}
else {
if (!get_pinMode(pin)) {
// pinMode(pin, INPUT_PULLUP); // make sure input isn't floating - stopped doing this
// because this could interfere with inductive/capacitive
// sensors (high impedance voltage divider) and with PT100 amplifier
SERIAL_PROTOCOLPAIR("Input = ", digitalRead_mod(pin));
}
else if (pwm_status(pin)) {
// do nothing
}
else SERIAL_PROTOCOLPAIR("Output = ", digitalRead_mod(pin));
}
if (multi_name_pin == false && extended) pwm_details(pin); // report PWM capabilities only on the first pass & only if doing an extended report
}
SERIAL_EOL;
} // end of IF
} // end of for loop
if (found == false) {
sprintf(buffer, "PIN:% 3d ", pin);
SERIAL_ECHO(buffer);
print_port(pin);
if (IS_ANALOG(pin)) {
sprintf(buffer, " (A%2d) ", int(pin - analogInputToDigitalPin(0))); // analog pin number
SERIAL_ECHO(buffer);
}
else SERIAL_ECHOPGM(" "); // add padding if not an analog pin
SERIAL_ECHOPGM("<unused/unknown>");
if (get_pinMode(pin)) {
SERIAL_PROTOCOLPAIR(" Output = ", digitalRead_mod(pin));
}
else {
if (IS_ANALOG(pin)) {
sprintf(buffer, " Analog in =% 5d", analogRead(pin - analogInputToDigitalPin(0)));
SERIAL_ECHO(buffer);
}
else {
SERIAL_ECHOPGM(" "); // add padding if not an analog pin
}
SERIAL_PROTOCOLPAIR(" Input = ", digitalRead_mod(pin));
}
// if (!pwm_status(pin)) SERIAL_ECHOPGM(" "); // add padding if it's not a PWM pin
if (extended) pwm_details(pin); // report PWM capabilities only if doing an extended report
SERIAL_EOL;
}
}
inline void report_pin_state(int8_t pin) {
report_pin_state_extended(pin, false, false);
}