ADM00308/MTS2916A Speed Control - c

I am using an ADM00308 development board from MicroChip. The board has a PIC16F883 processor. The code example can be downloaded from their website. I am using a nanotec steppermotor ST4118m0706 with a step 1.8 degree. I've calculated the max speed of the stepper motor:
from this website
RPMmax = 24V/(2⋅0,032mh⋅0,5A) = 3,75n/s (3,75⋅60 = 225rpm)
Minimum time per step = (2⋅0,032mh⋅0,5)/24V = 0,00133 seconds.
So in theory, the stepper motor should be able to handle 225rpm without oscillating. Now the software.
The code example provides a variable speed up to around 45rpm. This is too slow, since my target is 130rpm. Here is the original code:
Pulse width, Max speed and scan
// Prescale: Must change both values together - PRESCALE Divisor and BIT MASK */
// 1 = 0b00000000, 2 = 0b00010000, 4 = 0b00100000, 8 = 0b00110000
//#define REF_PWM_PRESCALE 8
//#define REF_PWM_PRESCALE_MASK 0b00110000
// The Rollover Count is the period of the timer.
// The timer period = 0 - Rollover Count.
//#define REF_PWM_PERIOD = ((float) ((1.0 / (((float)_XTAL_FREQ) / 4.0)) * (REF_PWM_PRESCALE * REF_RWM_ROLLOVER_COUNT)))
//#define REF_FREQ ((float) (1.0 / REF_PERIOD) )
// Set minimum speed by (65535 - MAX_SPEED_COUNT) * usec per bit
// 12 msec =
#define MIN_MOTOR_PULSEWIDTH (0.015)
#define MAX_SPEED_COUNT ((unsigned int) (65535.0 - ((float) ((MIN_MOTOR_PULSEWIDTH / (1.0 / (((float)_XTAL_FREQ) / 4.0))) / (ROTATION_PRESCALE * 2)))))
#define SPEED_INPUT_SPAN ((unsigned int) 900)
#define SPEED_INPUT_COUNTS_PER_BIT ((unsigned int) (MAX_SPEED_COUNT / SPEED_INPUT_SPAN))
#define ROTATION_ROLLOVER_COUNT (MAX_SPEED_COUNT + 100)
#define ROTATION_PRESCALE 8
#define ROTATION_PRESCALE_MASK 0b00110000
#define ROTATION_PERIOD = ((float) ((1.0 / (((float)_XTAL_FREQ) / 4.0)) * (ROTATION_PRESCALE * ROTATION_ROLLOVER_COUNT)))
#define ROTATION_FREQ ((float) (1.0 / ROTATION_PERIOD) )
SpeedUpdate
FaultTypeEnum SpeedUpdate(void)
{
FaultTypeEnum Fault;
unsigned int Speed;
Fault = NO_FAULT;
if (SpeedInput < 65)
{
/* open/shorted(GND) speed input */
Speed = 0;
Fault = SPEED_INPUT_LOW;
}
else if (SpeedInput < 100)
{
Speed = 0;
Fault = NO_FAULT;
}
else if (SpeedInput > 950)
{
Speed = 0;
Fault = SPEED_INPUT_HIGH;
}
else if (SpeedInput > 900)
{
/* open/shorted(VDD) speed input */
Speed = MAX_SPEED_COUNT;
Fault = NO_FAULT;
}
else
{
Speed = (SpeedInput - 100) * SPEED_INPUT_COUNTS_PER_BIT;
Fault = NO_FAULT;
}
RotationTimerRolloverCount = MAX_SPEED_COUNT - Speed;
/* setup the next timer reload value */
T1CON = 0b00000000; /* Temporarily pause the PWM timer */
/* use variables to reload timer faster in interrupt routine */
RotationTimerReloadHi = (unsigned )(RotationTimerRolloverCount >> 8);
RotationTimerReloadLo = (unsigned short) RotationTimerRolloverCount;
T1CON = 0b00000001 | ROTATION_PRESCALE_MASK; // Re-enable PWM timer, set prescale
return Fault;
}
Timer
/* Rotation Timer. Must be fast. */
if (PIR1bits.TMR1IF)
{
PIR1bits.TMR1IF = 0;
TMR1H = RotationTimerReloadHi;
TMR1L = RotationTimerReloadLo;
/* Calculate next stepper rotation state */
/* HOLD switch sets min_rotation_state = max_rotation_state */
/* SINGLE STEP switch sets min_rotation_state = max_rotation_state */
if (System.Bits.Stop)
{
/* no current output */
RotationData.All = ROTATION_STOP;
}
else
{
/* update stepper driver with last calculated data */
PORTB = ((PORTB & 0b11000000) | RotationData.All);
I managed to change the speed to 146rpm by changing:
#define MIN_MOTOR_PULSEWIDTH (0.015)
to for testing
#define MIN_MOTOR_PULSEWIDTH (0.006)
The stepper rotates at a speed of 98rpm with the potentio completely rotated to the left. Lowering the value to 0.004 will get a speed of 146rpm with good torque on a 24v supply(torque will suffer on a 12v supply). Lowering the value even more, will oscillate the motor (you can hear the motor but it doesn't rotate anymore). Which is strange since the max rpm is supposed to be 225rpm. However, the main problem is that I can't seem to reach a rpm of 130. Changing the value to 0.005, 0.0045, etc doesn't increase the speed more than 98 until 0.004. It also seems that the potentiometer has some kind of presets. By turning the potentiometer, it changes from 146rpm to 98rpm, to 73rpm, etc. It doesn't change the speeds fluently, if you get my point. Hence I am getting the idea it's programmed in presets, which I also tried to change.
Firmware and information can be downloaded on the original site

Related

how to calculate the value to be assigned to TCNT0 (timer0) in atmega32 when used to make a delay without using float variables?

so my problem is related to Timers in atmega32 in general, my problem is that I am using timer0 in my atmega32 as a delay timer with interrupts every unit time specified by the callee function, so for example, if the application user specified that I want an interrupt every 1 second for example, then I initialize the timer0 and based on some equations I can delay for one second then I call the application user ISR.
my problem in the equations itself requires me to use floating variables while the microprocessor on atmega32 doesn't have a floating point unit so the compiler increases the code size.
by the way, I am using my timer in Normal mode and this is the datasheet of timer0 (page 69)
here are the equations I use:
T(tick) = prescalar / freq(CPU) -> where T(tick) is the time needed by one tick for the timer, freq(CPU) is the frequency of the MCU.
T(max_delay) = (2^8) * T(tick) -> where T(max_delay) represents the max delay the timer can provide until the 1st overflow, (2^8) is the maximum number of ticks that timer0 can make before the overflow
Timer(init_value) = (T(max_delay) - T(delay)) / T(tick) -> where Timer(init_value) is the intial value to be inserted into TCNT0 register at the first and every time there is an overflow, T(delay) is the user required delay.
N(of_overflows) = [ceil](T(delay) / T(max_delay)) -> where N(of_overflows) is the number of overflows needed to achieve application user delay if it's greater than T(max_delay)
and this is my code that I wrote for just as a reference:
/*
* #fn -: -calculatInitValueForTimer0
*
* #params[0] -: -a number in milliseconds to delay for
*
* #brief -: -calculate the initial value needed for timer0 to be inserted into the timer
*
* #return -: -the initial value to be in the timer0
*/
static uint8_t calculatInitValueForTimer0(uint32_t args_u32TimeInMilliSeconds, uint16_t args_u8Prescalar)
{
/*local variable for time in seconds*/
double volatile local_f64TimerInSeconds = args_u32TimeInMilliSeconds / 1000.0;
/*local variable that will contain the value for init timer*/
uint8_t volatile local_u8TimerInit = 0;
/*local variable that will contain the time for one tick*/
double volatile local_f64Ttick;
/*local variable that will contain the time for max delay*/
double volatile local_f64Tmaxdelay;
/*get the tick timer*/
local_f64Ttick = args_u8Prescalar / 1000000.0;
/*get the max delay*/
local_f64Tmaxdelay = 256 * local_f64Ttick;
/*see which init time to be used*/
if (local_f64TimerInSeconds == (uint32_t) local_f64Tmaxdelay)
{
/*only one overflow needed*/
global_ValueToReachCount = 1;
/*begin counting from the start*/
local_u8TimerInit = 0;
}
else if (local_f64TimerInSeconds < (uint32_t) local_f64Tmaxdelay)
{
/*only one overflow needed*/
global_ValueToReachCount = 1;
/*begin counting from the start*/
local_u8TimerInit = (uint8_t)((local_f64Tmaxdelay - local_f64TimerInSeconds) / local_f64Ttick);
}
else if (local_f64TimerInSeconds > (uint32_t) local_f64Tmaxdelay)
{
/*many overflow needed*/
global_ValueToReachCount = ((local_f64TimerInSeconds / local_f64Tmaxdelay) == ((uint32_t)local_f64TimerInSeconds / (uint32_t)local_f64Tmaxdelay)) ? (uint32_t)(local_f64TimerInSeconds / local_f64Tmaxdelay) : (uint32_t)(local_f64TimerInSeconds / local_f64Tmaxdelay) + 1;
/*begin counting from the start*/
local_u8TimerInit = 256 - (uint8_t)( (local_f64TimerInSeconds / local_f64Ttick) / global_ValueToReachCount);
}
/*return the calculated value*/
return local_u8TimerInit;
}
currently I am not handling the case when there is only one overflow required, but this isn't the case.
my problem is that in order to calculate the timer initial value and number of overflows needed to achieve a big delay is all calculated using double or float variables and the problem is that the microprocessor in atmega32 doesn't have FPU so it makes the Compiler increase code size to achieve these equations, so is there any other way to calculate the timer initial value and number of overflows needed without double or float variables ?

Control DC Motor Speed with an AVR 8-bit Microcontroller

I'm trying to control DC motor speed. As of right now, to my limited understanding, I suppose I'm able to control the motor with just high and low by setting and clearing bits. The goal is to have the motor run at its 50% or 70% of the max speed, preferrably while gradually increasing and decreasing the speed. Please see the following piece of code and the circuit diagram for the current system configuration.
Here's the motor's datasheet:
https://www.ti.com/lit/ds/symlink/drv8801.pdf
The board I'm using is:
ATmega64A
Atmel AVR 8-bit Microcontroller
#define MOTOR_CON PORTA
#define DC_ENABLE_ON sbi(PORTA, 3)
#define DC_ENABLE_OFF cbi(PORTA, 3)
#define DC_PHASE_ON sbi(PORTA, 2)
#define DC_PHASE_OFF cbi(PORTA, 2)
#define DC_MODE_ON sbi(PORTA, 1)
#define DC_MODE_OFF cbi(PORTA, 1)
void MotorSpeedSwingUp(void)
{
DC_ENABLE_ON;
}
void MotorSpeedSwingDn(void)
{
DC_ENABLE_OFF;
}
void MotorForwardPhase(void)
{
DC_PHASE_ON;
DC_MODE_OFF;
}
void MotorReversePhase(void)
{
DC_PHASE_OFF;
DC_MODE_OFF;
}
void MotorRun(void)
{
if(motor_swing_flag == TRUE)
{
motor_swing_flag = FALSE;
MotorSpeedSwingUp();
}
else
{
motor_swing_flag = TRUE;
MotorSpeedSwingDn();
}
}
void MotorBrake(void)
{
DC_PHASE_OFF;//Forward
DC_ENABLE_OFF;
DC_MODE_ON;
}
Many thanks for helping!
The concept of PWM control is to repeat ON and OFF in a very
short cycle as shown in Figure 9-3 and so on. The ratio of ON time
in the cycle is called "duty cycle" and is proportional to the motor
rotation speed as enhzflep comments.
Let's suppose the cycle is 200usec and we want to have the motor
rotate at 70% speed. Then we should repeat the following sequence:
Enable the motor.
Wait 140usec.
Disable the motor.
Wait 60usec.
Assuming your C compiler supports usleep() function, would you please
try something like (not tested):
#define CYCLE 200 // 200usec for 1 cycle
#include <unistd.h>
/*
* have the motor run at "duty" speed for "duration" seconds
* 0.0 <= duty <= 1.0
*/
void PwmRun(double duty, int duration)
{
if (duty < 0.) duty = 0.; // lower limit of "duty"
if (duty > 1.) duty = 1.; // upper limit of "duty"
int ontime = (int)(CYCLE * duty); // PWM on time
int offtime = (int)(CYCLE * (1. - duty)); // PWM off time
for (int t = 0; t < duration * 1000000; t += CYCLE) {
if (ontime > 0) DC_ENABLE_ON;
usleep(ontime);
if (offtime > 0) DC_ENABLE_OFF;
usleep(offtime);
}
}
// example to run the motor at 70% speed for 3 seconds
PwmRun(0.7, 3);
It will be easy to modify the code to gradually increase the speed
from 50% to 70%.

Is it possible to get an hour timer interrupt from Atmega328p?

Coming from this question and I just wonder How to calculate maximum time that an Atmega328 timer can give us before trigger an interrupt? I want it to trigger every hour or so in my project but due to the fact that C integer and OCR1A register has some limitation in size it seems far fetch to get an hour from it.
Is it possible to modify my last question code to get some hour delay?
It should be, depending on the frequency of your microcontroller and the prescaler you're going to use.
Atmega 328P is 20 MHz. So if you take 20,000,000/1024 = 19531. Thats the number of cycles in 1 second.
You can find this in the data sheet for a 16 Bit Timer:
volatile uint8_t count = 0;
void set_up_timer() {
TCCR1B = (1 << WGM12); // from the datasheet
OCR1A = 19531; // number of ticks in a second
TIMSK1 = (1 << OCIE1A); // from the data sheet
TCCR1B |= (1 << CS12) | (1 << CS10);
You can set a global variable, and increment it in the ISR Routine until the desired value is achieved. Something along the lines of:
ISR(TIMER1_COMP1_VECT) {
counter++;
if(counter >= 3600) {
// do whatever needs to be done
}
The comment by Jabberwocky translates to this code (based on the other question to which your have posted the link)
... includes
/* In milliseconds */
const unsigned int ISR_QUANTUM = 1000; // once in a second
/* How much time should pass (1 hours = 3600 seconds = 3600 * 1000 ms */
const unsigned long LONG_TIME_INTERVAL = 1000 * 3600;
volatile unsigned long time_counter;
void once_an_hour() {
... do what needs to be done
}
int main(void) {
... setup your timer interrupt (high-precision, "short-range")
// reset your 'seconds' time counter
time_counter = 0;
while (1)
{
// busy-wait for time counter passing
if (time_counter > LONG_TIME_INTERVAL) {
// reset global timer
time_counter = 0;
once_an_hour();
}
// do other things
...
}
}
ISR (TIMER1_COMPA_vect)
{
// action to be done every Xms - just increment the time_counter
time_counter += ISR_QUANTUM;
}
This way you just increment a "global" time counter in a "local" interrupt handler.

Mega2560 Timer and uSec

I know there are a bunch of questions on here about timers and how to configure and use them, I have looked through all I could find but can't figure out what I am doing wrong.
I need a class that contains basically the same functionality as the Arduino micros() function. I want to stay with straight AVR. Here is what I have so far, I am using Timer4 so I don't step on any toes, this is a 16bit timer and I am using a prescale of 8 which should give me .5us every clock cycle using a Mega2560, wouldn't this equate to TCNT4 = 2 = 1us?
To verify that my timing functions are correct I created a simple program that only contains the Timer and a couple of delays from "util/delay.h". The resulting output is not what I expected. So here is my issue, I am not sure if the _delay_us is actually delaying the right time or if my timer/math is off.
I realize that there are no checks for overflows or anything, I am focusing on simply getting the timer to output the correct values first.
SystemTime:
class SystemTime{
unsigned long ovfCount = 1;
public:
SystemTime();
void Overflow();
uint32_t Micro();
void Reset();
};
/**
* Constructor
*/
SystemTime::SystemTime() {
TCCR4B |= (1 << CS41); //Set Prescale to 8
TIMSK4 |= (1 << TOIE4); //Enable the Overflow Interrupt
}
/**
* Increase the Overflow count
*/
void SystemTime::Overflow(){
this->ovfCount++;
}
/**
* Returns the number of Microseconds since start
*/
uint32_t SystemTime::Micro(){
uint32_t t;
t = (TCNT4 * 2) * this->ovfCount;
return t;
}
/**
* Resets the SystemTimer
*/
void SystemTime::Reset(){
this->ovfCount = 0;
TCNT4 = 0;
}
SystemTime sysTime;
ISR(TIMER4_OVF_vect){
sysTime.Overflow();
}
Main:
#include "inttypes.h"
#include "USARTSerial.h"
#include "SystemTime.h"
#include "util/delay.h"
#define debugSize 50
void setup(){
char debug1[debugSize];
char debug2[debugSize];
char debug3[debugSize];
char debug4[debugSize];
uSerial.Baudrate(57600);
uSerial.Write("Ready ...");
uint32_t test;
sysTime.Reset();
test = sysTime.Micro();
sprintf(debug1, "Time 1: %lu", test);
_delay_us(200);
test = sysTime.Micro();
sprintf(debug2, "Time 2: %lu", test);
_delay_us(200);
test = sysTime.Micro();
sprintf(debug3, "Time 3: %lu", test);
_delay_us(200);
test = sysTime.Micro();
sprintf(debug4, "Time 4: %lu", test);
uSerial.Write(debug1);
uSerial.Write(debug2);
uSerial.Write(debug3);
uSerial.Write(debug4);
}
void loop(){
}
Output:
Ready ...
Time 1: 0
Time 2: 144
Time 3: 306
Time 4: 464
Update:
Thanks for helping me out, I wanted to post the working code just in case someone else is having problems or needs to know how this can be done. One thing to keep in mind is the time it takes to do the Micros calculation. It looks like (at least on my Mega2560) that it takes around 36us to perform the calculation so either the timer prescale needs to be adjusted or the math to eliminate the double multiplications. None the less this class works as is, but is by no means optimized.
#define F_CPU 16000000L
#include <stdio.h>
#include <avr/interrupt.h>
class SystemTime {
private:
unsigned long ovfCount = 0;
public:
SystemTime();
void Overflow();
uint32_t Micro();
void Reset();
};
/*
* Constructor, Initializes the System Timer for keeping track
* of the time since start.
*/
SystemTime::SystemTime() {
TCCR4B |= (1 << CS41); //Set Prescale to 8
TIMSK4 |= (1 << TOIE4); //Enable the Overflow Interrupt
//Enable Interrupts
sei();
}
/**
* Increase the Overflow count
*/
void SystemTime::Overflow() {
this->ovfCount++;
}
/**
* Resets the SystemTimer
*/
void SystemTime::Reset() {
this->ovfCount = 0;
TCNT4 = 0;
}
/**
* Returns the number of Microseconds since start
*/
uint32_t SystemTime::Micro() {
uint32_t t;
t = (TCNT4 * 0.5) + ((this->ovfCount * sizeof(this->ovfCount)) * 0.5);
return t;
}
SystemTime sysTime;
ISR(TIMER4_OVF_vect) {
sysTime.Overflow();
}
Assuming that your MCU really runs on 16 MHz, I would change the following things in your code.
If one timer increment is 0.5 μs, then you should divide TCNT4's value by 2, not multiply. Because it is TCNT4 times 0.5 μs.
Also the this->ovfCount usage is wrong as well. The elapsed microseconds from startup is equal to: TCNT4 * 0.5 + this->ovfCount * 65535 * 0.5. So the current increment number (TCNT4) multiplied by 0.5 μs plus the overflow count (this->ovfCount) multiplied by the max increment count (216-1 = 65535) multiplied by 0.5 μs.
uint32_t SystemTime::Micro(){
uint32_t t;
t = (TCNT4 * 0.5) + this->ovfCount * 65535 * 0.5;
return t;
}
Finally, I cannot see you enabling global interrupts anywhere with sei().

Modify code from an ATmega32 to an Arduino Mega - error in function `main' even if there is none

I'm trying to modify this code in an attempt to make it work on an Arduino Mega. I'm pretty much new to C so, I may have made some major mistakes. By the way, this is for a self balancing skateboard.
This code is taken from an ATmega32 (from here and I'm trying to make it work on a Arduino Mega).
This code was writen for an ATmega32 development board.
But I encounter this error:
o: In function main':</br>
C:\Users\*******\AppData\Local\Temp\build27006.tmp/Test2.cpp:406:</br>
undefined reference tosetup'
How come? I don't even have a reference to setup in here!
Here is my code:
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <math.h>
#define CLOCK_SPEED 16000000
#define OCR1_MAX 1023
typedef unsigned char u8;
void set_motor_idle(void);
void InitPorts(void);
float level = 0;
float Throttle_pedal;
float aa;
float accelraw;
float x_acc;
float accsum;
float x_accdeg;
float gyrosum;
float gangleratedeg;
float gangleraterads;
float ti = 2.2;
float overallgain;
float gaincontrol;
float batteryvolts = 24;
float gyroangledt;
float angle;
float anglerads;
float balance_torque;
float softstart;
float cur_speed;
float cycle_time = 0.0064;
float Balance_point;
float a0, a1, a2, a3, a4, a5, a6; //Savitzky-Golay variables for accelerometer.
float TCCR0;
int i;
int j;
int tipstart;
void InitPorts(void)
{
PORTC = 0x00; //Port C pullups set to low (no output voltage) to begin with.
DDRC = 0xFF; //Port C pins all set as output via the port C direction register.
//PORTC |= (1<<PC1); //Make C1 +ve so disables OSMC during startup.
DDRA = 0x00; //All port A pins set as input.
PORTA = 0x00; //Port A input pullups set to low pullups.
DDRD = 0xFF; //Configure all port D pins as output as prerequisite
//for OCR1A (PinD5) and OCR1B (Pin D4) working properly.
PORTB = 0x00; //Port B pullups set to low (no output voltage) to begin with.
DDRB = 0xFF; //All port B pins set to output.
}
/*
IO:
I am using a ATMega32 16 MHz with an external crystal clock. New planned pin
arrangement to OSMC motor controller.
PC4 Onboard LED
PD5/OC1A ALI -> OSMC pin 6
PD4/OC1B BLI -> OSMC pin 8
PC1 Disable -> OSMC pin 4
PC2 BHI -> OSMC pin 7
PC3 AHI -> OSMC pin 5
PA6/ADC6 Vbatt/10 -> OSMC pin 3
PA1/ADC1 pitch rate gyro
PA0/ADC0 accelerometer
*/
void adc_init(void) {
/* Turn off analogue comparator as we don't use it */
ACSR = (1 << ACD);
/* Select PA0 */
ADMUX = 0;
ADMUX |=(1<<REFS0); //This tells it to use VCC (approx. 5 V) as the reference
//voltage NOT the default which is the internal 2.5V reference
/* Set ADC prescaler to 128, enable ADC, and start conversion. */
ADCSRA = 0 | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0)
| (1<<ADEN) //Enable ADC
| (1<<ADSC); //Start first conversion
/* Wait until bogus first conversion is finished */
while (ADCSRA & (1 << ADSC)) {
}
}
uint16_t adc_read(uint8_t channel) {
/* Select channel */
ADMUX = channel;
ADMUX |= (1<<REFS0); //Here it is again
/* Start conversion */
ADCSRA |= (1 << ADSC);
/* Wait until conversion finished */
while (ADCSRA & (1 << ADSC)) {
}
/* Return the result */
return ADCW;
}
/* 156 cycles per sec, 6.4 ms per cycle MEASURED ON OSCILLOSCOPE. */
/* Read all the ADC inputs and do some conversion. */
void sample_inputs(void) {
uint16_t adc0, adc1, adc2, adc3, adc4, adc5;
gyrosum = 0;
adc0 = adc_read(0); /* Accelerometer pin PA0 */
accelraw = (float) adc0;
for (j=0; j<7; j++) {
adc1 = adc_read(1); //Gyro pin PA1
gyrosum = (float) gyrosum + adc1; //Using a mean of 7 samples per loop for the gyro so
//it gets a complete update with each loop of the program.
}
adc2 = adc_read(2); /* Grey wire overallgain (via cutout switch), position PA2. */
adc3 = adc_read(3); /* Position lever pulled back position PA3. */
adc4 = adc_read(4); /* Throttle_pedal, position PA4. */
adc5 = adc_read(5); /* Position lever pushed forwards, position PA5. */
//adc6 = adc_read(6); /* Vbatt input from OSMC (not used at present), position PA6. */
//Sav Golay filter for accelerometer only.
a0 = a1;
a1 = a2;
a2 = a3;
a3 = a4;
a4 = a5;
a5 = a6;
a6 = (float) accelraw;
accsum = (float) ((-2*a0) + (3*a1) + (6*a2) + (7*a3) +
(6*a4) + (3*a5) + (-2*a6))/21; //Sav Golay calculation
gaincontrol = (float) gaincontrol*0.9 + 0.1*adc2/341; //Smooths any voltage spikes and gives range 0-3.
Throttle_pedal=(float) Throttle_pedal*0.9 + 0.1*adc4/341; //Smooths any voltage spikes and gives range 0-3
//Cuts the motor if the dead mans button is let go
//(gaincontrol variable also wired in through this button to adc2
if (adc2<100) {
Throttle_pedal = 0.001;
gaincontrol = 0.001;
}
overallgain = gaincontrol*softstart;
//What to do if the lever is pulled back or pushed forwards or not doing anything:
Balance_point = 514;
if (adc3 > 100)
Balance_point = 534;
if (adc5>100)
Balance_point = 494;
PORTB |= (1<<PB2); //Port B2 turned on/off once per loop so I can measure
//loop time with an oscilloscope
/*ACCELEROMETER signal processing*/
/*Subtract offsets*/
x_acc = (float) accsum - Balance_point; //accsum is SG value for accelerometer, not
//a true "sum" so no need to divide by 7
if (x_acc < -250)
x_acc = -250; //Cap acceleration values to a range of -250 to +250 (80
//degree tilt each way).
if (x_acc > 250)
x_acc = 250;
/* Accelerometer angle change is about 3.45 units per degree tilt in
range 0-30 degrees(sin theta). Convert tilt to degrees of tilt from
accelerometer sensor. Sin angle roughly = angle for small angles so
no need to do trigonometry. x_acc below is now in DEGREES */
x_accdeg= (float) x_acc/-3.45; //The minus sign corrects for a back
//to front accelerometer mounting!
/* GYRO signal processing*/
/* Subtract offsets: Sensor reading is 0-1024 so "balance point" i.e. my required
zero point will be that reading minus 512. */
/* Gyro angle change of 20mV per deg per sec from datasheet gives change
of 4.096 units (on the scale of 0 - 1023) per degree per sec angle change.
This limits the rate of change of gyro angle to just less than the
maximum rate it is actually capable of measuring (100 deg/sec). Note
all these fractions are rounded up to an integer later just before
it is sent to the PWM generator which in turn is connected to the
motor controller. */
gangleratedeg = (float)((gyrosum/7) - 508)/4.096; //gyrosum is a sum of a group
//of 7 samples so divide by 7 for gyro value
if (gangleratedeg < -92)
gangleratedeg = -92;
if (gangleratedeg > 92)
gangleratedeg = 92;
/* I turn port B2 on and off once per main program cycle so I can attach an
oscilloscope to it and work out the program cycle time.
I use the cycle time to work out gyro angle change per cycle where you
have to know the length of this time interval. */
PORTB &= (0<<PB2);
/* ti represents scaling for the "i" or integral factor (currently 2.2 here)
gyroangledt is anglechange since last CYCLE in degrees from gyro sensor,
where ti is scaling factor (should in theory be about 1 but 2.2 makes
board feel tighter)
ganglerate is now in units of degrees per second.
aa varies the time constant, that is, a smaller aa value makes
accelerometer time constant longer as it slowly corrects for
the gyro drift. */
aa=0.005;
gyroangledt = (float)ti*cycle_time*gangleratedeg;
gangleraterads = (float)gangleratedeg*0.017453;
/* New angle in DEGREES is old angle plus change in angle from gyro
since last cycle with little bit of new accel reading factored in. */
angle = (float)((1-aa) * (angle+gyroangledt)) + (aa * x_accdeg); //The main angle calculating function*/
//Convert angle from degrees to radians
anglerads=(float)angle*0.017453;
balance_torque=(float)(4.5*anglerads) + (0.5*gangleraterads);
cur_speed = (float)(cur_speed + (Throttle_pedal * balance_torque * cycle_time)) * 0.999;
/* The level value is from -1 to +1 and represents the duty cycle to be sent to
the motor. Converting to radians helps us stay within these limits. */
level = (balance_torque + cur_speed) * overallgain;
}
/* Configure timer and set up the output pins OC1A(Pin PD5 on my micro) and
OC1B (Pin PD4 on my micro) as phase-correct PWM channels.
Note: Some strongly feel that locked-antiphase is the way to go as get
regenerative braking and good control around mid-balance point. The downside
is that you can get a lot more noise and voltage spikes in system but
these can be smoothed out with filters.
Others are far more expert on this than I am so need to look into this
for yourself but this is my understanding.
My aim is to start with phase-correct as I just about understand it and
others have used it OK, then develop from there. */
void timer_init()
{
TCCR0 = 0 |
(1<<CS02) | (1<<CS01) | (1<<CS00); // External clock to Pin T0 Clock on rising edge/1024
// PWM mode is "PWM, Phase Correct, 10-bit"
TCCR1A = 0 |
(1<<COM1A1) | (1<<COM1A0) | // set on match up, clear on match down
(1<<COM1B1) | (1<<COM1B0) | // set on match up, clear on match down
(1<<WGM11) | (1<<WGM10); //OCR1_Max is 1023 so these are set like this
TCCR1B = 0 |
(1<<CS10); // Prescaler divide by 1 see P131 datasheet about prescaling
values to change here.
/* 16 MHz / 1 / 1024 / 2 gives 8 kHz, probably about right. */
}
void set_motor()
/* The leveli terms is the level term rescaled from -1023 to +1023 as an
integer ready to send to the PWM motor control ports that are in
turn connected to the OSMC. */
{
//if (level<-0.9) level= -0.9; //Checks we are within sensible limits
//if (level>0.9) level=0.9;
int16_t leveli = (int16_t)(level*1023); //NOTE: here we take the floating
//point value we have ended up with
//for "level", we multiply it by 1023
//and then make it into an integer
//before feeding the value into
//the PWM generator as "leveli"
if (leveli<-1020)
leveli=-1020; //Double-checks that we are within sensible PWM limits as do
//not want to suddenly be thrown off the board
if (leveli>1020)
leveli=1020;
/* Set up LED or buzzer on Port B1 to warn me to slow down if torque to be
delivered is more than 50% of max possible. The reason for this is that
you always need some reserve motor power in case you start tipping
forward at speed. If the motor is already running flat-out you would
be about to fall over at high speed! Some use an auto-tip back routine
to automatically limit top speed. For now I will do it this way as easier. */
if (level<-0.7 || level>0.7) {
PORTB |= (1<<PB1);
}
else {
PORTB &= (0<<PB1);
}
softstart = (float) softstart+0.001;
if (softstart>1.0)
softstart=1.0;
//PORTC |= (0<<PC1); // AHI=1 PinC3, BHI=1 PinC2 set both to ON for OSMC to
//work and both to OFF to shut motor down.
/*NOTE: I am not sure why, but to stop the motor cutting out on direction
changes I had in the end to hard wire AHI and BHI to +12 V. */
/* Un-disabled OSMC by setting PinC1 output to zero, a 1 would disable the OSMC. */
PORTC |= 0x0c; //Make C1 pulled down so un-disables the OSMC i.e. enables it.
PORTC &= ~0x02; //Disable is off
if (leveli<0) {
OCR1A = -leveli; // ALI is PWM. Going backwards as leveli variable is a
//negative signed value, keep the minus sign in here!
OCR1B = 0; // BLI = 0
}
else {
OCR1A = 0; // ALI = 0 going forwards as leveli variable is a positive signed value
OCR1B = leveli; // BLI is PWM
}
}
void loop()
{
InitPorts();
adc_init();
timer_init();
/* Initial tilt-start code
Turn on micro while board tipped to one side, rider about to step
onto it, if tilt angle crosses zero (mid) point balance algorithm
becomes operational otherwise locked in this loop forever until
it is tipped to level position as rider gets onto the board. */
tipstart=0;
accelraw = 0;
while (tipstart<1){
// You need this to allow the SG filter to wind up to the proper
//stable value when you first turn machine on, before looking
//at the value of accsum (below).
for (i=0; i<20; i++) {
sample_inputs();
}
if (accsum<504 || accsum>524) {
// if (x_accdeg>0) {
tipstart=0;
}
else {
tipstart=1;
softstart=0.4;
}
}
angle=0;
cur_speed=0;
/* End of tilt start code. If go beyond this point then machine
has become level and is active. */
sei();
while (1) {
sample_inputs();
set_motor();
}
}
BTW, I'm using the Arduino compiler (you can find it at Download the Arduino Software).
How come? I dont even have a reference to setup in here!
It's because you don't have a reference to setup that it's complaining.
Arduino code generates a main that (for the discussion here) basically looks like this:
int main(int argc, char **argv) {
setup();
while(1) {
loop();
}
}
So, you should rename your loop function to setup and take this part:
while (1) {
sample_inputs();
set_motor();
}
drop the while loop, and put those two function calls into a new function called loop.
The Arduino language requires you to define two functions, setup() and loop().
If you don't have anything to setup(), you should define an empty function to satisfy the language requirements:
void setup()
{
// do nothing
}
The included code doesn't even have a main function. It looks to me like a setup issue in your toolchain, or else you've been investigating the wrong cpp file.
Find Test2.cpp and line 406 (in a function called main)

Resources