I have a simple question about how to read rotary encoder input.
If I understand this image correctly, then every turn triggers a rise on pin A. Then, you have to check pin B, which is high if the encoder is turning clockwise and low if the encoder is turning counter clockwise.
I've tried to write my own code and not using any libraries, because I thought this would be really simple, but it turned out it was not.
This is the code I've written:
#define rotary_A 2
#define rotary_B 3
void setup()
{
pinMode(rotary_A, INPUT);
pinMode(rotary_B, INPUT);
attachInterrupt(digitalPinToInterrupt(rotary_A), rotary_spin, RISING);
Serial.begin(9600);
}
void rotary_spin()
{
if (digitalRead(rotary_B) == HIGH)
Serial.println("+");
else
Serial.println("-");
}
I was expecting to get + when I turn it clockwise and - when I turn it counter clockwise. However, I'm getting several outputs for each turn, like there were several interrupts triggered in rapid succession. For example, when I turn the encoder clockwise:
-
-
+
+
and counter clockwise:
+
+
-
-
-
-
The outputs are different every time, but the last character is always the right one.
What am I getting wrong? Is it not that simple or are there different types of encoders?
The question implies that there should only be a single interrupt per revolution. But encoders typically generate more than a single cycle per revolution--some in the thousands. That is probably why the interrupts seem to occur more rapidly than expected.
In a zero-latency interrupt environment, the code should work. But if the phase B pin is sampled too long after the phase A pin goes high, it will fail. This might occur if the next phase A rising edge occurs while Serial.println is still executing in the previous interrupt.
A simple test to see if this is the case is to turn the encoder very, very slowly. If this results in correct data, the problem is probably interrupt latency. Serial.println can then be replaced with something much quicker, like illuminating LEDs, to see if that resolves latency issues.
For actual use, you would need to make sure that the worse-case latency between phase A going high and phase B being sampled is adequate for the maximum rate of encoder rotation.
Final note: The code should be able to adequately detect direction, but cannot be used to increment/decrement a counter to track position. That requires more than one interrupt per cycle.
check this repository of mine on Github.
https://github.com/KingZuluKing/Rotary-Encoder-Peripheral-System
It has to be something like this, more details inside the repo.
void encode_Rotor_func()
{
lcd.clear();
Vol_Knob_stat=digitalRead(Vol_Knob);
if(Vol_Knob_stat==0)
{
Vol_Dir_stat=digitalRead(Vol_Dir);
if(Vol_Dir_stat==0 && deciBells<=9)
{
deciBells++;
lcd.setCursor(0, 0);
lcd.print("VOLUME= .");
lcd.setCursor(7, 0);
lcd.print(deciBells);
lcd.setCursor(10, 0);
lcd.print("dB's");
}
else if(Vol_Dir_stat==1 && deciBells >=-79)
{
deciBells--;
lcd.setCursor(0, 0);
lcd.print("VOLUME= .");
lcd.setCursor(7, 0);
lcd.print(deciBells);
lcd.setCursor(10, 0);
lcd.print("dB's");
}
else{
do something else etc.. etc....
}
}
}
I made a small sketch that makes clear how to interpret the encoder accordingly to the image sequence in your post:
Try this code with the equivalent pins CLK and DT connected to A2 and A1 receptively.
// Rotary Encoder Inputs
#define CLK A2
#define DT A1
int counter = 0;
String currentDir ="";
unsigned char currentPairBit = 0b0; // 8 bits
unsigned char lastPairBit = 0b0; // 8 bits
void setup() {
Serial.begin(9600); // opens serial port, sets data rate to 9600 bps
DDRC = 0b00000000; // Set Analog(C) encoder pins as inputs
}
void loop() {
while(true) { // while cycle is faster than loop!
// reads the analog states!
currentPairBit = PINC >> 1 & 0b11; // Gets A2(PC2) and A1(PC1) bits on C (analog input pins) (>> 1 = Jumps pin 0)
if ((lastPairBit & 0b11) != currentPairBit) {
lastPairBit = lastPairBit << 2 | currentPairBit;
// Bit Pairs Cyclic Sequence:
// 1. 2. 3. 4. 5.
// 11 | 01 | 00 | 10 | 11 for CCW
// 11 | 10 | 00 | 01 | 11 for CW
if (lastPairBit == 0b01001011 || lastPairBit == 0b10000111) {
if (lastPairBit == 0b01001011) {
currentDir = "CCW";
counter--;
} else {
currentDir = "CW";
counter++;
}
Serial.print("Direction: ");
Serial.print(currentDir);
Serial.print(" | Counter: ");
Serial.println(counter);
}
}
}
}
Then check the Serial Monitor to see how fast it is. Note that you should avoid the delay() function in your code or any other that interrupts the cycle by too much time. Consider using a second auxiliary Arduino for anything else than counting.
Resulting Serial Output:
Related
I'm creating part of a program right now for a personal project. This is my first year ACTUALLY coding and have been studying for hours to understand many concepts so please be nice and try not to be rude as others have...
The project of mine is an AVRDUDE using a chip called ATMEGA328P in a program called Microchip studios.
[This project is having 3 LED count every time a switch is pressed, I should have them count continuously and change to the next number every second using a TIMER 1.
**counting in binary from 0-8 then rolling over **
I need some help on one aspect of it which is using interrupts after I have already created a blinking LED to use a TIMER0 instead of delays.
I have made my fourth LED flash at 5Hz which is the blinking part of my code include below at the end of this question.
Now the problem I am running into is trying to create interrupts for the 3 LEDS count every time a switch is pressed, I should instead have them continually count, changing to the next number approximately every second, using TIMER1.]
This is my code for the project
Again please be nice and at least lead me in some type of direction...
#include <avr/io.h>
#include <avr/interrupt.h>
#define F_CPU 1000000UL
int global = 0 ;
volatile uint8_t overflow0; // Variable for counting overflows for first timer
volatile uint8_t overflow1; // Variable for counting overflows for second timer
void FirstTimer() // Initialize Timer 1
{
TCCR0B |= (1<<CS02) | (1<<CS00); // Prescaler of 1024
TCNT0 = 0;
}
void SecondTimer() //Initialize Timer 2
{
TCCR1B |= (1<<CS11) | (1<<CS10);
TCNT1 = 0;
}
int main(void)
{
DDRB |= (1 << DDB0);
FirstTimer(); // Calling timer 1 and 2 initialization
SecondTimer();
while (1)
{
{
if (TCNT0 >= 195) // Amount of ticks needed
{
PORTB ^= (1 << PORTB0); //LED on
TCNT0 = 0; //Reset counter
}
}
{
if (TCNT1 >= 15625) // Ticks needed
{
TCNT1 = 0; // Timer reset
}
}
}
}
An approach you could use is keeping a count variable (unsigned char - assuming 8 bit register width) and in your Timer ISR simply increment the count and write that value to the output register. Once count > 8 set to zero.
Configure the Timer mode to trigger on overflow w/ reset to zero.
Then you do some basic math using the clock speed and timer ceiling (overflow value) to calculate the frequency you want the overflow ISR to occur (increment the LED count values).
Note that for this to work the LEDs need to be on the first 3 pins.
i.e.
P1.0 = LED1
P1.1 = LED2
P1.2 = LED3
...
If not connected like this then you can still make it work with additional bit manipulation (shifts and masks).
I am really stuck on part a and b for problem 1 below. I am really confused on how to change the multiply and divide functions to change pins/LED’s, using the << and >> functions instead.
Any help would be much appreciated. Thanks!
Multiple LEDs and using and input button
Modify the C program:
a. Instead of using the multiply and divide functions to change pins/LED’s, use the << and >> functions. References: Deitel and Deitel “C, How to Program and https://en.wikipedia.org/wiki/Operators_in_C_and_C
b. Change the clock frequency in the program to 1 MHz and make the on/off time of each LED .1 seconds. This should make the rotation visibly faster. (Remember to change the _XTAL_FREQ value since this is used for the __delay_ms() function built into XC8)
Devices:
Low Pin Count board (16F1829 on board) and 44-Pin Demo Board are both on same backboard. (You only use the 16F1829 for this lab.)
PICKIT 3 programmer with USB cable
MPLAB X (I used v3.00 but a different version may be on lab computers))
Microchip XC8 C Compiler User Manual
PIC16F1829 Data Sheet
PICkit 3 User’s Guide
Low Pin Count Board User Guide
“C How to Program” Deitel, Pearson/Prentice-Hall (Any edition)
Internet Browser Search Engine for research (Google, Bing, etc)
upload_2018-9-5_23-27-22.png
The code is below.
/*
LEDs on for approximately 0.5 sec.
PIC: 16F1829 Enhanced Mid-Level
Compiler: XC8 v1.34
IDE: MPLABX v3.00 */
#include <pic16f1829.h> //Not required but this is the reference used by "C" for names and location on uC
#include <htc.h> //refers on HiTech C, Microchip purchased HiTech
#define _XTAL_FREQ 4000000 //Used by the XC8 delay_ms(x) macro
#define switch PORTAbits.RA2 // Can use RA2 instead of PORTAbit.RA2 to define pin attached to switch
//instead of saying PORTAbits.RA2 each time
//config bits for the PIC16F1829
#pragma config FOSC=INTOSC, WDTE=OFF, PWRTE=OFF, MCLRE=OFF, CP=OFF, CPD=OFF, BOREN=ON, CLKOUTEN=OFF, IESO=OFF, FCMEN=OFF
#pragma config WRT=OFF, PLLEN=OFF, STVREN=OFF, LVP=OFF
//Initialization subroutine
void initialize(void) {
ANSELC=0; //All pins of Port C are digital I/O
ANSA2=0; //switch pin, RA2, is digital IO
TRISA2 = 1; //switch is an input
TRISC = 0; //all pins of Port C are outputs
OSCCON = 0b01101000; // 4 MHz
}
unsigned char i1; //only need 4 bits to count to 16. unsigned character variable is 8 bits long
// Here is main(). There are many ways to do this 4-pin (LED) sequence
void main(void)
{
initialize();
i1=1; //Start the main program with the variable =1. Could have done this during its definition
while (1) //runs continuously until MCU is shut off
{
if (switch==1) //Button not pressed pin at 5V
{ i1=1; }
while (switch==1) //Button not pressed
{
PORTC=i1; //Note that writing to PORTC writes to LATC
__delay_ms(500);
i1=i1*2;
if (i1==16)
{ i1=1; }
}
if (switch==0) //Button pressed pin at ground
{ i1=8; }
while (switch==0) //Button pressed
{
PORTC=i1;
__delay_ms(500);
i1=i1/2;
if (i1==0)
{ i1=8; }
}
}
}
a. Instead of using the multiply and divide functions to change
pins/LED’s, use the << and >> functions. References: Deitel and Deitel
“C, How to Program and
https://en.wikipedia.org/wiki/Operators_in_C_and_C
Left shift value << n is integer multiplication of value by 2^n or value*(2^n)
Right shift value >> n is integer division of value by 2^n or value/(2^n))
When you have some var and you use one of the shift operators, you are taking the value of whatever var is and shifting the binary digits (bits) that represent it's value to the left or the right.
A basic example of this:
uint8_t var = 1; //0b00000001 in binary
var <<= 1; //var is now 0b00000010, that is 1*(2^1) or 2
var >>= 1; //var is now 0b00000001, that is 2/(2^1) or 1
There is a huge caveat for using the shift operator and that is that whenever you shift bits you are filling 0s in from the opposite direction that you are shifting so you have to pay attention to the integer size.
uint8_t var = 1;
var <<= 4; //var is now 0b00010000, 4 zeros filled in on the right
var = 1;
var <<= 8; //var is now 0b00000000, because 8 zeros were filled in on the right!
Now with regard to how you use this to manipulate the pins on a microcontroller, you would take some variable that increments or decrements and shift left or right by that variable and assign the resulting value to the register in the module that controls that pin, which in this case is the PORTx module. In your code that would look like this:
if (switch == 1) //Button not pressed pin at 5V
{
i1 = 0; //initialize to 0
}
while (switch == 1) //Button not pressed
{
PORTC = (1 << i1++); //set will set just one pin at a time, the first will be pin 0, the next pin 1, and so on
__delay_ms(500);
if (i1 == 8){
i1 = 0; //reset variable
}
}
if (switch == 0) //Button pressed pin at ground
{
i1 = 0; //initialize to 0
}
while (switch == 0) //Button pressed
{
PORTC = (0x80 >> i1++); //this will set 1 pin at a time, the first will be pin 7, the next will be pin 6, and so on
__delay_ms(500);
if (i1 == 8)
{
i1 = 0; //reset variable
}
}
b. Change the clock frequency in the program to 1 MHz and make the
on/off time of each LED .1 seconds. This should make the rotation
visibly faster. (Remember to change the _XTAL_FREQ value since this is
used for the __delay_ms() function built into XC8)
This portion of your code:
OSCCON = 0b01101000; // 4 MHz
Actually configures the frequency of the oscillator used by the microcontroller for its clock signal. However, it is important for you to know the source of that clock signal, which according to the datasheet is controlled by Configuration Word 1. This is set in the #pragma config FOSC=INTOSC portion of your code.
To obtain 1 MHz you will want to change that line to this:
OSCCON = 0b01011000; // 1 MHz
This is found in the OSCCON register description in the datasheet.
The __delay_ms function uses the _XTAL_FREQ to calculate a delay which is why you are being told to change this line of your code:
#define _XTAL_FREQ 4000000
To this
#define _XTAL_FREQ 1000000
Currently I'm working on a project where I have to read out pulses from a Arduino and check if the result is High or Low.
I had to write my own code to generate the high/low output from the Arduino:
//Pulse Generator Arduino Code
int potPin = 2; // select the input pin for the knob
int outputPin = 13; // select the pin for the output
float val = 0; // variable to store the value coming from the sensor
void setup() {
pinMode(outputPin, OUTPUT); // declare the outputPin as an OUTPUT
Serial.begin(9600);
}
void loop() {
val = analogRead(potPin); // read the value from the k
val = val/1024;
digitalWrite(outputPin, HIGH); // sets the output HIGH
delay(val*1000);
digitalWrite(outputPin, LOW); // sets the output LOW
delay(val*1000);
}
It uses a knob to change the delay between the pulses.
Im currently trying to read the high/low data with another Arduino (Lets call this one the "count Arduino") by simply connecting the 2 with the a cable from the "outputPin" to a port on the count Arduino.
I'm using digitalRead to read the port without any delay.
//Count Arduino Code
int sensorPin = 22;
int sensorState = 0;
void setup() {
pinMode(sensorPin, INPUT);
Serial.begin(9600);
}
void loop(){
sensorState = digitalRead(sensorPin);
Serial.println(sensorState);
}
First it tried with a pulse every 1 second but the result was a spam of a ton of lows and highs. Always 3 Lows and 3 highs and repeating. It wasn’t even close to one every 1 second but more like 1 every 1 millisecond.
I cant figure out what i'm doing wrong. Is it timing issue or is there a better way to detect these changes?
a spam of a ton of lows and highs
... happens if the GND of the two Arduinos are not connected.
Also, your reading arduino prints at every loop cycle, which were a few microseconds only, if the Serial buffer would not overflow.
Better printout changes only, or use a led to show what's happening.
void loop(){
static bool oldState;
bool sensorState = digitalRead(sensorPin);
if (sensorState != oldState) {
Serial.println(sensorState);
oldState = sensorState;
}
}
I am trying to read analogic signal for a sort of mouse with a pic18f14k50 controller. Here the simple circuit: http://dl.dropbox.com/u/14663091/schematiconew.pdf . I have to read analogic signal from AN9 circuit port. Main function reads from the port, and blinks 30 time if threshold is reached:
void main(void) {
InitializeSystem();
#if defined(USB_INTERRUPT)
USBDeviceAttach();
#endif
while(1) {
if((USBDeviceState < CONFIGURED_STATE)||(USBSuspendControl==1)) continue;
if(!HIDTxHandleBusy(lastTransmission))
{
int readed = myReadADC2(); //Here i tried both myReadADC2() or myReadADC1()
if(readed>40) { //If read threshold > 40, blink led 30 times
int i;
for(i=0; i<30; i++) {
Delay1KTCYx(0);
mLED_1_On();
Delay1KTCYx(0);
mLED_1_Off();
}
}
lastTransmission = HIDTxPacket(HID_EP, (BYTE*)hid_report_in, 0x03);
}//end while
}//end main
I used two method to read from the AN9 port, myReadADC() that uses OpenADC() API method:
int myReadADC(void) {
#define ADC_REF_VDD_VDD_X 0b11110011
OpenADC(ADC_FOSC_RC & ADC_RIGHT_JUST & ADC_12_TAD, ADC_CH9 & ADC_INT_OFF, ADC_REF_VDD_VDD_X & ADC_REF_VDD_VSS, 0b00000010); // channel 9
SetChanADC(ADC_CH9);
ConvertADC(); // Start conversion
while(BusyADC()); // Wait for completion
return ReadADC(); // Read result
}
and myReadADC2(), that implements manual read from the port.
int myReadADC2() {
int iRet;
OSCCON=0x70; // Select 16 MHz internal clock
ANSEL = 0b00000010; // Set PORT AN9 to analog input
ANSELH = 0; // Set other PORTS as Digital I/O
/* Init ADC */
ADCON0=0b00100101; // ADC port channel 9 (AN9), Enable ADC
ADCON1=0b00000000; // Use Internal Voltage Reference (Vdd and Vss)
ADCON2=0b10101011; // Right justify result, 12 TAD, Select the FRC for 16 MHz
iRet=100;
ADCON0bits.GO=1;
while (ADCON0bits.GO); // Wait conversion done
iRet=ADRESL; // Get the 8 bit LSB result
iRet += (ADRESH << 8); // Get the 2 bit MSB result
return iDelay;
}
Both cases doesn't works, i touch (sending analogic signal) port AN9 but when I set high threshold (~50) led don't blinks, with low threshold (~0) it blinks immidiatly when i provide power to the PIC. Maybe i'm using wrong port? I'm actually passing AN9 as reading port? Or maybe threshold is wrong? How can i found the right value? Thank you
Here the MPLAB C18 Apis http://dl.dropbox.com/u/14663091/API%20microchip%20C18.pdf .
Regarding function myReadADC2(): you need to switch ANSEL and ANSELH configs as RC7/AN9 is configured in bit 1 of ANSELH. Also call me paranoid but for the line
iRet += (ADRESH << 8);
I always like to either save it a temporary variable first or cast explicitly the value ADRESH before shifting it up:
iRet += (((UINT) ADRESH) << 8);
That way I know for sure the bits won't get lost when shifting up which has bitten me before.
Regarding function myReadADC():
OpenADC() only takes two parameters. I presume that bitfield in the third parameter field is for the analog enable (ADRESH/ADRES). I'm assuming that's handled by SetChanADC() but you may have to set ADRESH/ADRES manually. It may help to set a breakpoint in the debugger and stop after configuration is complete to make sure your registers are set appropriatley.
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)