Why is my PWM timer behaving weirdly

I have an arduino code running on arduino mega which is supposed to output a pulse train and its duty cyles are stored in an array, but for somereason every time I reset my Mega and expect something it has breaks in the middle Frequency of pulse train is 400hz

Here's the file
sketch_jan31a.ino (237.2 KB)

When I scope this:
For 4 secs I get 900 us pulses which is correct, than for 5 seconds I should get 1200 us on time pulses but it only does that till 2 seconds and than somehow goes to 0 and than starts doing random stuff

#include <avr/pgmspace.h>

#define PWM_PIN 9  // On Mega, Timer1 controls Pin 9 & 10
#define PWM_FREQ 400  // 400 Hz PWM frequency
#define F_CPU 16000000UL  // Arduino Mega runs at 16 MHz
#define PRESCALER 8
#define TOP ((F_CPU / (PRESCALER * PWM_FREQ)) - 1)  // Compute TOP value

// Large duty cycle array stored in PROGMEM (values are ON-time in microseconds)
const PROGMEM uint16_t dutyCycleArray1[] = {900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900
, 900, 900, 900,
...
};
...
...
const PROGMEM uint16_t dutyCycleArray5[] = {900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 9
00, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900,
...
900,
};

// Array to store chunk sizes
const uint16_t chunkSizes[] = {
    10000,
    10000,
    10000,
    10000,
    501,    
};


volatile uint8_t currentChunk = 0;  // Track current chunk

volatile uint16_t currentIndex = 0;  // Track array index
const uint16_t arraySize = sizeof(dutyCycleArray1) / sizeof(dutyCycleArray1[0]);  // Compute array size

volatile uint16_t onTimeMicroseconds;  // Preloaded ON-time to avoid repeated variable creation

#define PWM_PIN 5  // Pin 5 is controlled by Timer3 on Mega

void setup() {
    pinMode(PWM_PIN, OUTPUT);
    // Configure Timer3 (instead of Timer1)
    TCCR3A = (1 << WGM31) | (1 << COM3A1);  
    TCCR3B = (1 << WGM33) | (1 << WGM32) | (1 << CS31);  

    ICR3 = TOP;  // Set PWM period (400 Hz)
    OCR3A = (TOP * pgm_read_word(&dutyCycleArray1[0])) / (1000000 / PWM_FREQ); 

    TIMSK3 |= (1 << TOIE3);  // Enable Timer3 Overflow Interrupt
}

ISR(TIMER3_OVF_vect) {
  // Determine which chunk to read from
    switch (currentChunk) {
        case 0:
            onTimeMicroseconds = pgm_read_word(&dutyCycleArray1[currentIndex]);
            break;
        case 1:
            onTimeMicroseconds = pgm_read_word(&dutyCycleArray2[currentIndex]);
            break;
        case 2:
            onTimeMicroseconds = pgm_read_word(&dutyCycleArray3[currentIndex]);
            break;
        case 3:
            onTimeMicroseconds = pgm_read_word(&dutyCycleArray4[currentIndex]);
            break;
        case 4:
            onTimeMicroseconds = pgm_read_word(&dutyCycleArray5[currentIndex]);
            break;
    }

    // Update PWM duty cycle
    OCR3A = (TOP * onTimeMicroseconds) / (1000000 / PWM_FREQ);  

    // Increment index within the chunk
    currentIndex++;

    // If we reach the end of the current chunk, switch to the next one
    if (currentIndex >= chunkSizes[currentChunk]) {
        currentIndex = 0;
        currentChunk = (currentChunk + 1) % 5;  // Cycle through 4 chunks
    }  
    
}

Maybe the intermediate integer math in this expression:

    // Update PWM duty cycle
    OCR3A = (TOP * onTimeMicroseconds) / (1000000 / PWM_FREQ);  

might exceed a uint16_t?

You might be correct any, solution to that? I changed the ontime to uint32_t it still has the same behavior, what do I do? Do I increase the prescaler to reduce TOP size?

Well, promoting this to a uint32_t should have promoted the arithmetic to handle my suspicion.

... and looking deeper at the parentheses in this definition, my suspicion was unwarranted.

I'd add some Serial.print()ing periodically and see if the currentChunk and currentIndex are as expected.

I did do that the very first time the chunk is at 0 (expected) and the count is increasing from 0-10000 the only thing I don't understand are the blips which I see on scope, this is supposed to run a motor so this blips are evident when the motor throws a fault

Well, another thing I might test is whether the array sizes are as expected. I'd first try putting the array sizes explicitly in the declarations to see if the compile throws a fault. If not, then the declarations aren't bigger than expected, but you could still be reading past the initializations. Then I'd try setting the array sizes 1 entry too small, and expect a fault when you initialize too many elements in a too small space. If you don't get a fault, then you don't have enough initializers and it might be filling in/accessing zeros or random data where you don't expect them.

Or maybe the data is wrong somehow....

Maybe try this in your setup and see if you get unexpected values:

  Serial.begin(115200);
  for (int ii = 0; ii < 10000; ++ii) {
    uint16_t val = pgm_read_word(&dutyCycleArray1[ii]);
    if (val > 2000) {
      Serial.print(ii);
      Serial.print(' ');
      Serial.print(val);
      Serial.println();
    }
  }
  delay(10000);

I fiddled with in this Wokwi sim and found unexpected data:

... in these elements of array 1:

2041 37900
2042 40728
2043 37900
2044 40777
2045 37900
2046 40777
2047 37900
2048 40777
2049 37900
2050 40777
2051 37900
2052 40777
2053 37900
2054 40777
2055 37900
2056 40777
2057 37900
2058 40777
2059 37900
2060 40777
2061 37900
2062 40777
2063 37900
2064 40777
2065 37900
2066 40777
2067 37900
2068 40777
2069 37900
2070 40777
2071 37900
2072 40777
2073 37900
2074 40777
2075 37900
2076 40777
2077 37900
2078 40777
2079 37900
2080 40777
2081 37900
2082 40777
2083 37900
2084 40777
2085 37900
2086 40777
2087 37900
2088 41207
2089 37900
2090 40777
2091 37900
2092 41323
2093 37900
2094 41281
2095 37900
2096 40777
2097 37900
2098 40777
2099 37900
2100 40777
2101 37900
2102 40777
2103 37900
2104 40777
2105 37900
2106 40777
2107 37900
2108 40777
2109 37900
2110 40777
2111 37900
2112 41377
2113 37900
2114 40777
2115 37900
2116 40777
2117 37900
2118 40777
2119 37900
2120 40777
2121 37900
2122 40777
2123 37900
2124 40777
2125 37900
2126 40777
2127 37900
2128 40777
2129 37900
2130 40777
2131 37900
2132 40777
2133 37900
2134 40777
2135 37900
2136 40777
2137 37900
2138 40777
2139 37900
2140 40777
2141 37900
2142 40777
2143 37900
2144 40777
2145 37900
2146 40777
2147 37900
2148 40777
2149 37900
2150 40777
2151 37900
2152 40777
2153 37900
2154 40777
2155 37900
2156 40876
2157 37900
2158 40861
2159 37900
2160 40966
2161 37900
2162 41048
2163 37900
2164 40896
2165 37900
2166 40816
2167 37900
2168 41912
2169 37900
2170 40910

It might be some problem with the size of the PROGMEM pointers per hints found here, etc...

Hi, @123drone_code
Looking back you have a previous thread concerning 400Hz.

What is your project, why do you need this signal?

Tom.... :smiley: :+1: :coffee: :australia:

I need it to test a motor drive, if I use bit banging I have observed that pwm pulses go to 0 volts for some time due to a random interrupt which is detrimental to testing

Per:

https://www.nongnu.org/avr-libc/user-manual/group__avr__pgmspace.html

 #define 	pgm_read_word(address_short)   pgm_read_word_near(address_short)

pgm_read_word() works in a 16 bit address space and can't deal with large address offsets like 40501X worth of uint16_t tables. You'll have to do some fiddling with pgm_read_word_far()

#define 	pgm_read_byte_far(address_long)   __ELPM((uint32_t)(address_long))

I dont think I agree with your result here, i did the same on my mega it doesn't throw out garbage values, only change was I didnt initialize any timers or interrupts its just a simple array traversal

OK. But it happened for me in the sim in the same ~5s place noted in the first post.

If it is the pgm_read_word()'s #define pgm_read_word_near(address_short) __LPM_word((uint16_t)(address_short)) 16 bit address type, it could depend on where the linker puts the the 4 simple 20000 byte arrays relative to the code and the code modifications. My toss-out code only looked for out-of-range values in the first array, since each of the arrays has a separate hard-coded name. If coding changes or linker choices somehow put a different array farther away, it wouldn't detect it.

When I tried:

    if (UseWorkAround) val = pgm_read_word_far(pgm_get_far_address(dutyCycleArray1) + 2 * ii);

... the out-of-range values seemed to resolve and I didn't get the stall at about 5sec.

Enable this with 'true' in the (updated) wokwi sim:

// A fix: 
const bool UseWorkAround = false; // use pgm_get_word_far(pgm_get_address_far(...)...)

See:

I got the same results as DaveX.

Using a Mega2560, with the code suggested by DaveX in post #6, (except I only printed val, and not ii) here are the values of array1 displayed on the serial plotter:

The values on the Y-axis suddenly jump up from 1200 to 37900 / 40777, and then back down to 900.

2000 1200
2001 1200
2002 1200
2003 1200
2004 1200
2005 1200
2006 1200
2007 1200
2008 1200
2009 1200
2010 1200
2011 1200
2012 1200
2013 1200
2014 1200
2015 1200
2016 1200
2017 1200
2018 1200
2019 1200
2020 1200
2021 1200
2022 1200
2023 1200
2024 1200
2025 1200
2026 1200
2027 1200
2028 1200
2029 1200
2030 1200
2031 1200
2032 1200
2033 1200
2034 1200
2035 1200
2036 1200
2037 1200
2038 1200
2039 1200
2040 1200
2041 37900
2042 40728
2043 37900
2044 40777
2045 37900
2046 40777
2047 37900
2048 40777
2049 37900
2050 40777
2051 37900
2052 40777
2053 37900
2054 40777
2055 37900
2056 40777
2057 37900
2058 40777
2059 37900
2060 40777
2061 37900
2062 40777
2063 37900
2064 40777
2065 37900
2066 40777
2067 37900
2068 40777
2069 37900
2070 40777
2071 37900
2072 40777
2073 37900
2074 40777
2075 37900
2076 40777
2077 37900
2078 40777
2079 37900
2080 40777
2081 37900
2082 40777
2083 37900
2084 40777
2085 37900
2086 40777
2087 37900
2088 41348
2089 37900
2090 40777
2091 37900
2092 41294
2093 37900
2094 41252
2095 37900
2096 40777
2097 37900
2098 40777
2099 37900
2100 40777
2101 37900
2102 40777
2103 37900
2104 40777
2105 37900
2106 40777
2107 37900
2108 40777
2109 37900
2110 40777
2111 37900
2112 41422
2113 37900
2114 40777
2115 37900
2116 40777
2117 37900
2118 40777
2119 37900
2120 40777
2121 37900
2122 40777
2123 37900
2124 40777
2125 37900
2126 40777
2127 37900
2128 40777
2129 37900
2130 40777
2131 37900
2132 40777
2133 37900
2134 40777
2135 37900
2136 40777
2137 37900
2138 40777
2139 37900
2140 40777
2141 37900
2142 40777
2143 37900
2144 40777
2145 37900
2146 40777
2147 37900
2148 40777
2149 37900
2150 40777
2151 37900
2152 40777
2153 37900
2154 40777
2155 37900
2156 41873
2157 37900
2158 40873
2159 37900
2160 40859
2161 37900
2162 40929
2163 37900
2164 40779
2165 37900
2166 40839
2167 37900
2168 41006
2169 37900
2170 40824
2171 900
2172 900
2173 900
2174 900
2175 900
2176 900
2177 900
2178 900
2179 900
2180 900
2181 900
2182 900
2183 900
2184 900
2185 900
2186 900
2187 900
2188 900
2189 900
2190 900
2191 900
2192 900
2193 900
2194 900
2195 900
2196 900
2197 900
2198 900
2199 900
2200 900

I added a couple lines to also print the address into my Wokwi sim, and got these high (HEX) values:

2041 940C 0x10000
2042 9F18 0x10002
2043 940C 0x10004
2044 9F49 0x10006
2045 940C 0x10008
2046 9F49 0x1000A
2047 940C 0x1000C
2048 9F49 0x1000E
2049 940C 0x10010
2050 9F49 0x10012
2051 940C 0x10014
2052 9F49 0x10016
2053 940C 0x10018
2054 9F49 0x1001A
2055 940C 0x1001C
2056 9F49 0x1001E
2057 940C 0x10020
2058 9F49 0x10022
2059 940C 0x10024
2060 9F49 0x10026
2061 940C 0x10028
2062 9F49 0x1002A
2063 940C 0x1002C
2064 9F49 0x1002E
2065 940C 0x10030
2066 9F49 0x10032
2067 940C 0x10034
2068 9F49 0x10036
2069 940C 0x10038
2070 9F49 0x1003A
2071 940C 0x1003C
2072 9F49 0x1003E
2073 940C 0x10040
2074 9F49 0x10042
2075 940C 0x10044
2076 9F49 0x10046
2077 940C 0x10048
2078 9F49 0x1004A
2079 940C 0x1004C
2080 9F49 0x1004E
2081 940C 0x10050
2082 9F49 0x10052
2083 940C 0x10054
2084 9F49 0x10056
2085 940C 0x10058
2086 9F49 0x1005A
2087 940C 0x1005C
2088 A0F5 0x1005E
2089 940C 0x10060
2090 9F49 0x10062
2091 940C 0x10064
2092 A169 0x10066
2093 940C 0x10068
2094 A13F 0x1006A
2095 940C 0x1006C
2096 9F49 0x1006E
2097 940C 0x10070
2098 9F49 0x10072
2099 940C 0x10074
2100 9F49 0x10076
2101 940C 0x10078
2102 9F49 0x1007A
2103 940C 0x1007C
2104 9F49 0x1007E
2105 940C 0x10080
2106 9F49 0x10082
2107 940C 0x10084
2108 9F49 0x10086
2109 940C 0x10088
2110 9F49 0x1008A
2111 940C 0x1008C
2112 A19F 0x1008E
2113 940C 0x10090
2114 9F49 0x10092
2115 940C 0x10094
2116 9F49 0x10096
2117 940C 0x10098
2118 9F49 0x1009A
2119 940C 0x1009C
2120 9F49 0x1009E
2121 940C 0x100A0
2122 9F49 0x100A2
2123 940C 0x100A4
2124 9F49 0x100A6
2125 940C 0x100A8
2126 9F49 0x100AA
2127 940C 0x100AC
2128 9F49 0x100AE
2129 940C 0x100B0
2130 9F49 0x100B2
2131 940C 0x100B4
2132 9F49 0x100B6
2133 940C 0x100B8
2134 9F49 0x100BA
2135 940C 0x100BC
2136 9F49 0x100BE
2137 940C 0x100C0
2138 9F49 0x100C2
2139 940C 0x100C4
2140 9F49 0x100C6
2141 940C 0x100C8
2142 9F49 0x100CA
2143 940C 0x100CC
2144 9F49 0x100CE
2145 940C 0x100D0
2146 9F49 0x100D2
2147 940C 0x100D4
2148 9F49 0x100D6
2149 940C 0x100D8
2150 9F49 0x100DA
2151 940C 0x100DC
2152 9F49 0x100DE
2153 940C 0x100E0
2154 9F49 0x100E2
2155 940C 0x100E4
2156 9FC0 0x100E6
2157 940C 0x100E8
2158 9FCE 0x100EA
2159 940C 0x100EC
2160 9F9D 0x100EE
2161 940C 0x100F0
2162 9FAC 0x100F2
2163 940C 0x100F4
2164 A3B2 0x100F6
2165 940C 0x100F8
2166 A053 0x100FA
2167 940C 0x100FC
2168 A006 0x100FE
2169 940C 0x10100
2170 9F70 0x10102

Where the values start out like a byte-swapped copy of the interrupt vector table:

Disassembly of section .text:

00000000 <__vectors>:
       0:	0c 94 18 9f 	jmp	0x13e30	; 0x13e30 <__ctors_end>
       4:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
       8:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
       c:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      10:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      14:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      18:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      1c:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      20:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      24:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      28:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      2c:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      30:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      34:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      38:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      3c:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      40:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      44:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      48:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      4c:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      50:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      54:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      58:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      5c:	0c 94 f5 a0 	jmp	0x141ea	; 0x141ea <__vector_23>
      60:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      64:	0c 94 69 a1 	jmp	0x142d2	; 0x142d2 <__vector_25>
      68:	0c 94 3f a1 	jmp	0x1427e	; 0x1427e <__vector_26>
      6c:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      70:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      74:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      78:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      7c:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      80:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      84:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      88:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      8c:	0c 94 9f a1 	jmp	0x1433e	; 0x1433e <__vector_35>
      90:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      94:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      98:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      9c:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      a0:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      a4:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      a8:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      ac:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      b0:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      b4:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      b8:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      bc:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      c0:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      c4:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      c8:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      cc:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      d0:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      d4:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      d8:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      dc:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>
      e0:	0c 94 49 9f 	jmp	0x13e92	; 0x13e92 <__bad_interrupt>

The lines:

  for (int ii = 0; ii < 10000; ++ii) {
    uint16_t val = pgm_read_word(&dutyCycleArray1[ii]);
    if (UseWorkAround) val = pgm_read_word_far(pgm_get_far_address(dutyCycleArray1) + 2 * ii);
    if (val > 2000) {
      Serial.print(ii);
      Serial.print(' ');
      Serial.print(val,HEX);
      Serial.print(" 0x");
      Serial.print(pgm_get_far_address(dutyCycleArray1) + 2 * ii,HEX);
      Serial.println();
    }
  }

Here is my modified verison of @DaveX code from reply #11
wokwi PWMtimer

Whenever there is 64KB or more of data stored in PROGMEM, the compiler/linker has to be told to store your data at the upper part of memory, after the code and any other data that must reside in the lower 64KB segment of flash memory (text literals used with the F() macro, pre-defined arrays used for relating Arduino pin numbers to hardware pin numbers, etc).

I've also gotten into the habit of casting pgm_read_word() to the specific data type being used, there have been times when the compiler will assume int instead of unsigned int or vice-versa and really cause confusion.

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.