I have a simple C program:
#include <stdint.h>
#define WHEELS_PWM_CYCLES_PER_MS (5)
#define WHEELS_TIME_TO_GO_1_CM_MS (10)
int main(void)
{
for (;;) {
uint32_t x = (uint32_t)WHEELS_TIME_TO_GO_1_CM_MS * 100 * (uint32_t)WHEELS_PWM_CYCLES_PER_MS * 100 / 50;
}
return 0;
}
Originally this is a bigger file, but I reduced it in order to find the error, but without success. I had a program that controlled a dc motor with PWM pulses, but I noticed a strange behavior, so I debugged it with simavr and avr-gdb.
I compiled it with
avr-gcc -c -Wall -Wextra -g -O0 -DF_CPU=8000000UL -mmcu=atmega328p testwheel.c -o testwheel.o
Then I debugged it and got this
Breakpoint 1, main () at testwheel.c:10
10 uint32_t x = (uint32_t)WHEELS_TIME_TO_GO_1_CM_MS * 100 * (uint32_t)WHEELS_PWM_CYCLES_PER_MS * 100 / 50;
(gdb) n
11 }
(gdb) p x
$1 = 16
But x should be 10000, not 16! After some fiddling and experimentation with the compiler flags, I got the right result if I use -ggdb3 instead of -g:
avr-gcc -c -Wall -Wextra -ggdb3 -O0 -DF_CPU=8000000UL -mmcu=atmega328p testwheel.c -o testwheel.o
Breakpoint 1, main () at testwheel.c:10
10 uint32_t x = (uint32_t)WHEELS_TIME_TO_GO_1_CM_MS * 100 * (uint32_t)WHEELS_PWM_CYCLES_PER_MS * 100 / 50;
(gdb) n
11 }
(gdb) p x
$1 = 10000
I have no idea what's wrong with my code and why -ggdb3 fixes it. Does anyone know? Also I cannot use any -g flag in the real atmega, so I need to know what is wrong.
UPDATE:
So I will post the same bug in the real code here, where x is actually used in a meaningful way. The same error is evident there. So please, if someone that really understands what this is about can answer, I would be very happy.
wheels.c
#include "wheels.h"
#include "globals.h"
#include <stdint.h>
#define __DELAY_BACKWARD_COMPATIBLE__
#include <util/delay.h>
#include <avr/io.h>
/*
Q1 Q3
Q2 Q4
If Q1, Q4 is on time, there are 3 types of off times:
- Dynamic braking (Q1 on, Q2, Q3, Q4 off)
- Regenerative braking (Q2, Q3 on, Q1, Q4 off)
- Coasting (all off)
Maximum raise time is 250ns, which is 2 clock cycles at 8MHz.
*/
uint8_t volatile stop_wheels = 0;
static uint8_t speed = 50; // 0-100
void wheels_init() {
DDRD = 0xFF;
PORTD = 0;
}
void wheels_pwd(uint8_t drivepins, uint8_t brakepins, uint32_t cycles) {
PORTD = 0;
uint8_t time_on_us = WHEELS_PWM_CYCLE_US * speed / 100;
uint8_t time_off_us = WHEELS_PWM_CYCLE_US - time_on_us;
//if (time_on_us != 100 ) return;
_delay_us(1); // > maximum raise time
for (uint32_t i = 0; i < cycles; ++i) {
// Using coasting, there is no risk of shoot throughs.
// Even if reversing to opposite direction, this function will always start at PORTD = 0
// and the time it will take to reach this line again is more than 2 clock cycles (maximum raise time).
PORTD = drivepins;
_delay_us(time_on_us);
PORTD = 0;
_delay_us(time_off_us);
if ( stop_wheels ) {
PORTD = brakepins;
stop_wheels = 0;
_delay_ms(800); // Based on dc0.py model 0.6s
break;
}
}
}
void wheels_set_speed(uint8_t spd) {
if ( spd >= WHEELS_MIN_SPEED || spd <= 100 - WHEELS_MIN_SPEED ) {
speed = spd;
}
}
void wheels_go_forward_cm(uint16_t dist) {
wheels_pwd(WHEELS_LEFT_FORWARD | WHEELS_RIGHT_FORWARD,
WHEELS_LEFT_FORWARD_BRAKE | WHEELS_RIGHT_FORWARD_BRAKE,
(uint32_t)WHEELS_TIME_TO_GO_1_CM_MS * dist * (uint32_t)WHEELS_PWM_CYCLES_PER_MS * 100 / speed);
}
void wheels_go_backward_cm(uint16_t dist) {
wheels_pwd(WHEELS_LEFT_BACKWARD | WHEELS_RIGHT_BACKWARD,
WHEELS_LEFT_BACKWARD_BRAKE | WHEELS_RIGHT_BACKWARD_BRAKE,
(uint32_t)WHEELS_TIME_TO_GO_1_CM_MS * dist * (uint32_t)WHEELS_PWM_CYCLES_PER_MS * 100 / speed);
}
void wheels_turn_left_degrees(uint16_t degrees) {
wheels_pwd(WHEELS_LEFT_BACKWARD | WHEELS_RIGHT_FORWARD,
WHEELS_LEFT_BACKWARD_BRAKE | WHEELS_RIGHT_FORWARD_BRAKE,
(uint32_t)WHEELS_TIME_TO_TURN_1_DEGREE_MS * degrees * (uint32_t)WHEELS_PWM_CYCLES_PER_MS * 100 / speed);
}
void wheels_turn_right_degrees(uint16_t degrees) {
wheels_pwd(WHEELS_LEFT_FORWARD | WHEELS_RIGHT_BACKWARD,
WHEELS_LEFT_FORWARD_BRAKE | WHEELS_RIGHT_BACKWARD_BRAKE,
(uint32_t)WHEELS_TIME_TO_TURN_1_DEGREE_MS * degrees * (uint32_t)WHEELS_PWM_CYCLES_PER_MS * 100 / speed);
}
wheels.h
#ifndef WHEELS_H
#define WHEELS_H
#include <stdint.h>
#define WHEELS_MIN_SPEED 1 // Speed = [WHEELS_MIN_SPEED, 1 - WHEELS_MIN_SPEED]
#define WHEELS_PWM_CYCLE_US 200 // Time per cycle, us. If > 255, need to change to uint16_t in wheels_set_speed.
#define WHEELS_PWM_CYCLES_PER_MS (5) // Cycles per ms,typically ranges from 1 kHz to 20 kHz for DC motors.
#define WHEELS_RIGHT_FORWARD 0x03 // 00000011
#define WHEELS_RIGHT_BACKWARD 0x0c // 00001100
#define WHEELS_LEFT_FORWARD 0x30 // 00110000
#define WHEELS_LEFT_BACKWARD 0xc0 // 11000000
#define WHEELS_RIGHT_FORWARD_BRAKE 0x2 // 00000010
#define WHEELS_RIGHT_BACKWARD_BRAKE 0x8 // 00001000
#define WHEELS_LEFT_FORWARD_BRAKE 0x20 // 00100000
#define WHEELS_LEFT_BACKWARD_BRAKE 0x80 // 10000000
#define WHEELS_TIME_TO_TURN_1_DEGREE_MS 0.6 // 1 / ( 360 * 280 RPM / 60000 ms )
#define WHEELS_TIME_TO_GO_1_CM_MS (10) // 1 / ( 280 RPM * 6.7 cm * pi / 60000 ms )
void wheels_init();
void wheels_set_speed(uint8_t spd);
void wheels_go_forward_cm(uint16_t dist);
void wheels_go_backward_cm(uint16_t dist);
void wheels_turn_left_degrees(uint16_t degrees);
void wheels_turn_right_degrees(uint16_t degrees);
#endif
main.c
#include "wheels.h"
int main() {
wheels_init();
for (;;) {
wheels_go_forward_cm(100);
wheels_go_backward_cm(100);
}
}
See the 3rd argument to wheels_go_forward_cm:
(uint32_t)WHEELS_TIME_TO_GO_1_CM_MS * dist * (uint32_t)WHEELS_PWM_CYCLES_PER_MS * 100 / speed
This gives exactly the same error!
I have now simplified the code in another way, just to please the skeptics out there:
#include <stdint.h>
#include <avr/io.h>
const uint32_t WHEELS_TIME_TO_GO_1_CM_MS = 10;
int main(void)
{
for (;;) {
uint32_t x = (uint32_t)WHEELS_TIME_TO_GO_1_CM_MS * 100;
uint32_t y = x+3;
for (uint32_t i = 0; i < y; ++i) {
PORTD = i & 0xff;
}
}
return 0;
}
Guess what x is! It is not 1000, but 232, which is actually 1000%256 or just the lower byte of 1000. I realized the same is true for my previous code where I got x as 16, which is just 10000%256 or the lower byte of 10000.
So I continued the hell of trying to spot the error i gdb and the assembly code which I post here below.
Dump of assembler code for function main:
0x00000096 <+0>: push r28
0x00000098 <+2>: push r29
0x0000009a <+4>: in r28, 0x3d ; 61
0x0000009c <+6>: in r29, 0x3e ; 62
0x0000009e <+8>: sbiw r28, 0x08 ; 8
0x000000a0 <+10>: in r0, 0x3f ; 63
0x000000a2 <+12>: cli
0x000000a4 <+14>: out 0x3e, r29 ; 62
0x000000a6 <+16>: out 0x3f, r0 ; 63
0x000000a8 <+18>: out 0x3d, r28 ; 61
0x000000aa <+20>: ldi r18, 0x0A ; 10
0x000000ac <+22>: ldi r19, 0x00 ; 0
0x000000ae <+24>: ldi r20, 0x00 ; 0
0x000000b0 <+26>: ldi r21, 0x00 ; 0
0x000000b2 <+28>: ldi r24, 0x64 ; 100
0x000000b4 <+30>: ldi r25, 0x00 ; 0
0x000000b6 <+32>: movw r26, r24
0x000000b8 <+34>: call 0x10e ; 0x10e <__muluhisi3>
0x000000bc <+38>: movw r26, r24
0x000000be <+40>: movw r24, r22
0x000000c0 <+42>: std Y+5, r24 ; 0x05
0x000000c2 <+44>: std Y+6, r25 ; 0x06
0x000000c4 <+46>: std Y+7, r26 ; 0x07
0x000000c6 <+48>: std Y+8, r27 ; 0x08
0x000000c8 <+50>: std Y+1, r1 ; 0x01
0x000000ca <+52>: std Y+2, r1 ; 0x02
0x000000cc <+54>: std Y+3, r1 ; 0x03
0x000000ce <+56>: std Y+4, r1 ; 0x04
0x000000d0 <+58>: rjmp .+32 ; 0xf2 <main+92>
0x000000d2 <+60>: ldi r24, 0x2B ; 43
0x000000d4 <+62>: ldi r25, 0x00 ; 0
0x000000d6 <+64>: ldd r18, Y+5 ; 0x05
0x000000d8 <+66>: movw r30, r24
0x000000da <+68>: st Z, r18
0x000000dc <+70>: ldd r24, Y+1 ; 0x01
0x000000de <+72>: ldd r25, Y+2 ; 0x02
0x000000e0 <+74>: ldd r26, Y+3 ; 0x03
0x000000e2 <+76>: ldd r27, Y+4 ; 0x04
0x000000e4 <+78>: adiw r24, 0x01 ; 1
0x000000e6 <+80>: adc r26, r1
0x000000e8 <+82>: adc r27, r1
0x000000ea <+84>: std Y+1, r24 ; 0x01
0x000000ec <+86>: std Y+2, r25 ; 0x02
0x000000ee <+88>: std Y+3, r26 ; 0x03
0x000000f0 <+90>: std Y+4, r27 ; 0x04
0x000000f2 <+92>: ldd r18, Y+1 ; 0x01
0x000000f4 <+94>: ldd r19, Y+2 ; 0x02
0x000000f6 <+96>: ldd r20, Y+3 ; 0x03
0x000000f8 <+98>: ldd r21, Y+4 ; 0x04
0x000000fa <+100>: ldd r24, Y+5 ; 0x05
0x000000fc <+102>: ldd r25, Y+6 ; 0x06
0x000000fe <+104>: ldd r26, Y+7 ; 0x07
0x00000100 <+106>: ldd r27, Y+8 ; 0x08
0x00000102 <+108>: cp r18, r24
0x00000104 <+110>: cpc r19, r25
0x00000106 <+112>: cpc r20, r26
0x00000108 <+114>: cpc r21, r27
0x0000010a <+116>: brcs .-58 ; 0xd2 <main+60>
0x0000010c <+118>: rjmp .-100 ; 0xaa <main+20>
(gdb) x/20x &y
0x8008f8: 0x000003eb 0x4700ff08 Cannot access memory at address 0x800900
Looking at it, it correctly computes x as 232 low and 3 high stored in r24-r25, but x is not used in the right way. Somehow it is truncated and uint32_t is ignored. The memory around y is also suspicious. Atmega328p's SRAM does not reach 0x8008f8! Further it never enters the loop, but jumps back and forth like this:
9 uint32_t x = (uint32_t)WHEELS_TIME_TO_GO_1_CM_MS * 100;
(gdb) n
10 uint32_t y = x+3;
(gdb) n
11 for (uint32_t i = 0; i < y; ++i) {
(gdb) s
10 uint32_t y = x+3;
(gdb)
11 for (uint32_t i = 0; i < y; ++i) {
(gdb)
10 uint32_t y = x+3;
(gdb) s
11 for (uint32_t i = 0; i < y; ++i) {
(gdb)
10 uint32_t y = x+3;
SOLUTION:
After installing a recent tool chain with a new avr-gdb and avr-gcc, the strange behavior disappeared, at least in the debugger. I tried to use the new avr-gdb with the file compiled with the older avr-gcc and much of the strange errors was still there. Using the new compiler worked fine though. The assembly looks very different in many places, although about the same in the function main. If both the gdb and gcc where contributing to the behavior is still hard to say, but I did find a bug in my original code, which was related to the _delay_us function. From the documentation it says:
In order for these functions to work as intended, compiler optimizations must be enabled, and the delay time must be an expression that is a known constant at compile-time. If these requirements are not met, the resulting delay will be much longer (and basically unpredictable), and applications that otherwise do not use floating-point calculations will experience severe code bloat by the floating-point library routines linked into the application.
I had the optimizations on when I discovered the errors, but I was calculating the delays at run time. This was the main cause of the strange behaviors. So I fixed all my problems after this. Still, I'm happy I moved away from a 10 year old tool chain that obviously produced buggy code that even the new state of the art debugger couldn't understand.