STM32 ADC Reading Instability - c

I am reading 5 ADC channels in polling mode. For each channel I get 10 samples and calculate the average.
Looking for the samples array, it is visible some instability. First readings have higher values and they decrease until the last sample. This behavior is very repetitive.
See the output array (ui32_RawTemp):
First read 1855
Last read 1850
Original output values
ui32_RawTemp[0]: 1855
ui32_RawTemp[1]: 1855
ui32_RawTemp[2]: 1854
ui32_RawTemp[3]: 1852
ui32_RawTemp[4]: 1852
ui32_RawTemp[5]: 1852
ui32_RawTemp[6]: 1851
ui32_RawTemp[7]: 1851
ui32_RawTemp[8]: 1850
ui32_RawTemp[9]: 1850
For debug, I replaced the sensors connected to my ADC channels by fixed resistors, in order to get fixed reading values.
ADC Self calibration is performed before the first channel read. Every 30s the Calibration and 5 reads are repeated.
Here is the original code snippet (for one channel). To make it easier to understand I removed the ADC HAL Error handling.
AVG_NUMBER 10
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_247CYCLES_5;
sConfig.SingleDiff = ADC_SINGLE_ENDED;
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;
// reading TEMP1_ADC on channel 5
sConfig.Channel = ADC_CHANNEL_5;
ADC_HAL_ErrCode = HAL_ADC_ConfigChannel(&hadc1, &sConfig);
if(ADC_HAL_ErrCode != HAL_OK) {
......
}
ui32_Temp = 0;
for(ui8_AVGCounter=0; ui8_AVGCounter < AVG_NUMBER; ui8_AVGCounter++) {
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 10);
ui16_RawTemp[ui8_AVGCounter] = HAL_ADC_GetValue(&hadc1); // Read ADC register
ui32_Temp = ui32_Temp + (uint32_t)ui16_RawTemp[ui8_AVGCounter]; // Sum all read values
ui16_avgADC[0] = (uint16_t)(ui32_Temp / AVG_NUMBER); // Calculate the average
HAL_ADC_Stop(&hadc1);
}
After some investigation, I added a delay in my loop and the result is much better. Readings are very stable now.
But, 1ms delay is a lot!
Here is the adapted code snippet, with better results:
// reading TEMP1_ADC on channel 5
sConfig.Channel = ADC_CHANNEL_5;
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_247CYCLES_5;
sConfig.SingleDiff = ADC_SINGLE_ENDED;
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;
ADC_HAL_ErrCode = HAL_ADC_ConfigChannel(&hadc1, &sConfig);
if(ADC_HAL_ErrCode != HAL_OK) {
.....
}
ui32_Temp = 0;
for(ui8_AVGCounter=0; ui8_AVGCounter < AVG_NUMBER; ui8_AVGCounter++) {
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 10);
ui32_RawTemp[ui8_AVGCounter] = HAL_ADC_GetValue(&hadc1); // Read ADC register
HAL_ADC_Stop(&hadc1);
ui32_Temp = ui32_Temp + ui32_RawTemp[ui8_AVGCounter]; // Sum all read values
vTaskDelay(1);
}
ui16_avgADC[0] = (uint16_t)((ui32_Temp + (0.5 * AVG_NUMBER)) / AVG_NUMBER); //Round number and Calculate the average
See the new output array:
New output values:
ui32_RawTemp[0]: 1855
ui32_RawTemp[1]: 1855
ui32_RawTemp[2]: 1855
ui32_RawTemp[3]: 1855
ui32_RawTemp[4]: 1855
ui32_RawTemp[5]: 1855
ui32_RawTemp[6]: 1855
ui32_RawTemp[7]: 1855
ui32_RawTemp[8]: 1856
ui32_RawTemp[9]: 1855
Questions are:
Any explanation for this behavior?
Is something wrong in my code?
I noticed the first read is the most instable. Should I do something before the first ADC conversion?

It is an obvious sign thet your ADC input circuit has too large impedance and the internal ADC capacitance is not loaded fast enough.
If you want to use shorter sampling times you need to lower it by changing the input circuit - for example by adding an operating amplifier (as a voltage repeater)
.

Related

DMA data loss when changing PWM frequency during the DMA reading

First time working with DMA here and I'm getting data loss when the frequency of a PWM signal is varied during the DMA reading. The DMA request is triggered by a 16MHz clock. I'm using DMA2 on STM32f429zi.
The DMA direction is peripheral to memory, where I try to read the whole GPIO port E to memory. The data loss is clearly visible in the beginning of the DMA reading when the PWM frequency is changed. If I comment out the code with the varying of PWM frequency (see part of the code below), there is no visible data loss in the DMA reading.
You can see in the attached image that the upper plot loses data points of the sine wave (4 points per period) and in the lower plot there is no data loss.
As I understand, DMA should work independently and not be affected by CPU, but for me it seems to not be the case. Could this be due to working with a too high clock frequency? Or am I missing something else? I'm really stuck on this problem, any help is appreciated.
Regards,
Linda
/*------------------- Comment out when not changing PWM frequency --------------------------------------------------------------------*/
// /* Start 40 MHz */
// TIM3->CCR1 = 1; // 40 MHz
/*------------------------------------------------------------------------------------------------------------------------------------*/
/* Start DMA for ping signal with length BUFFER_SIZE (PA9 (channel 2) is clockout+ of ADC, sampling on falling edge of PA9) */
if (HAL_DMA_Start_IT(htim1.hdma[TIM_DMA_ID_CC2], (uint32_t)&(GPIOF->IDR), (uint32_t)&aDST_Buffer, BUFFER_SIZE) != HAL_OK)
{
/* Transfer Error */
Error_Handler();
}
/*------------------- Comment out when not changing PWM frequency --------------------------------------------------------------------*/
// /* Continue while timer is lower than 1,3 us */
// while (__HAL_TIM_GET_COUNTER(&htim5) - timer_val1 < 13)
// {
// }
// /* Start ping, 4 MHz */
// TIM3->ARR = 20; // period
// TIM3->CCR1 = 9; // pulse
// timer_val1 = __HAL_TIM_GET_COUNTER(&htim5);
// while (__HAL_TIM_GET_COUNTER(&htim5) - timer_val1 < 9) // 5 pulses
// {
// }
//
// TIM3->ARR = 1; // 40Mhz
// TIM3->CCR1 = 1;
// while (__HAL_TIM_GET_COUNTER(&htim5) - timer_val1 < 25)
// {
// }
/*------------------------------------------------------------------------------------------------------------------------------------*/

How to read rotary encoder input

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:

Getting continuous stream from Lepton FLIR Camera with board Nucleo-f401re

I connected my Flir Lepton Camera to my board, and I'm trying to have a continuous stream of the image, thanks to the program ThermalView (source code here: https://github.com/groupgets/LeptonModule/tree/master/software/ThermalView)
I compiled and downloaded the following code on my board:
int main(void)
{
//HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_I2C1_Init();
MX_SPI1_Init();
MX_USART1_UART_Init();
MX_USART2_UART_Init();
SystemClock_Config();
leptonEnd();
// LEPTON VDD OFF
HAL_Delay(1000);
// LEPTON VDD ON
HAL_Delay(185);
LeptonConfigure_I2C();
while(1)
{
LeptonReadFrame();
frameToPc();
}
}
// Output image buffer to USART2
void frameToPc()
{
static uint8_t frameSkipper = 0;
uint8_t timeStamp = (HAL_GetTick() - last_frame_millis); // calculate time passed since last been here
last_frame_millis = HAL_GetTick();
if(frameSkipper==4)
{
//PSEDO//
//IMGtoRGB();
////////
uint8_t thermalView_header[] = {0xDE,0xAD,0xBE,0xEF}; // 4-byte header for ThermalView application
HAL_Delay(1000);
HAL_UART_Transmit_DMA(&huart2, &thermalView_header[0], 4); // print header
while(huart2.gState != HAL_UART_STATE_READY); // wait for transmission to complete
HAL_UART_Transmit_DMA(&huart2, &IMG[0], LEPTON_IMG_SIZE_BYTES);
frameSkipper = 0;
}
frameSkipper++;
}uint8_t LeptonReadFrame()
{
uint8_t i, frame_number, frame_complete=0;
uint16_t bad_frame_counter=0;
while(!frame_complete)
{
leptonBegin();
HAL_SPI_Receive(&hspi1, &FramePacket[0], LEPTON_PACKET_LENGTH, 1000); // READ LINE
leptonEnd();
//HAL_UART_Transmit(&huart2, &FramePacket[0], LEPTON_PACKET_LENGTH, 1000); // PRINT LINE
if( (FramePacket[0] & 0x0f) != 0x0f ) // not discard frame
{
frame_number = FramePacket[1];
if(frame_number < 60) // valid frame
{
bad_frame_counter = 0;
for(i= 0; i<LEPTON_PACKET_CONTENT_LENGTH; i++)
{
IMG[frame_number*LEPTON_PACKET_CONTENT_LENGTH+i] = FramePacket[i+4]; // copy line into IMG buffer, ignoring ID and CRC
}
}
else
{
bad_frame_counter++;
}
if(frame_number == 59)
{
frame_complete = 1;
}
if(bad_frame_counter>1000) // 800 lines = 5 bad frames = resync needed
{
bad_frame_counter = 0;
HAL_Delay(185); // CS is already disabled so the delay is enougth
}
}
}
return 1;
}
I'm succesfully getting a stream, but I have to put a delay of 1 sec between each frame, and have to skip frames between 2 frames I'm sending to the computer. If you pay attention to something wrong in the code which prevents increasing the frame rate, let me know.
Are you using a Lepton 2 or Lepton 3? The Lepton 3 will require acquisition of not only the "Frames" but also 4 "segments" There are also 2 blank screens output by the Lepton Modules. More details in this document:
http://www.flir.com/uploadedFiles/OEM/Products/LWIR-Cameras/Lepton/Lepton-vs-Lepton-3-App-Note.pdf
comparing the Lepton 2X series (80x60) resolution and the Lepton 3 (160x120) resolution.
The four most significant differences between the Lepton and Lepton 3 VoSPI interfaces are:
1) On Lepton, reconstructing a video frame from the individual packets requires the host to decode the packet number from each packet header. On Lepton 3, the host must decode both the packet number and the segment number.
2) The total number of bits per frame is 4X greater for Lepton 3 than for Lepton. Consequently, the minimum SPI clock rate is 4X faster. The maximum SPI clock rate for both modules is 20 MHz.
3) Both Lepton and Lepton 3 provide the option to output a sync pulse on GPIO3. The frequency of the pulse is 4X higher on Lepton 3 than on Lepton. For Lepton 3, the sync pulse represents when the next available segment is available whereas for Lepton it indicates when the next available frame is available.
4) When telemetry is enabled in Lepton, it results in three extra video lines (63 total packets per frame). When telemetry is enabled in Lepton 3, it results in 1 additional packet per segment for a total of 2 extra video lines.
Im trying to get the lepton 3 to work on my stm32f746g-discovery board.

ADC dsPIC33 issue

I'm struggling to get the ADC to work with my device. I'm using the dsPIC33FJ128GP802 and have attempted to start off slow with manual sampling and conversion.
My code is posted below, I've set every register for ADC and have then attempted to sample just once to get the voltage from a sensor I've got attached. The value I should be seeing is around 0.7V, but what I'm getting is in the region of -17408 (10111100 00000000). This can go up to somewhere around -2000, but the value shouldn't be negative in the first place.
#include <p33Fxxxx.h>
_FOSCSEL(FNOSC_FRCPLL) // select internal 7.37MHz osc with PPL
_FOSC(OSCIOFNC_OFF & POSCMD_XT) // no clock output, external OSC disabled
_FWDT(FWDTEN_OFF) // disable the watchdog timer
_FPOR(FPWRT_PWR1) // Turn off the power-up timers.
int ADCValue;
void DELAY(unsigned ms) {
unsigned j;
unsigned i;
for (j = 0; j < ms; j++) {
for (i = 0; i < 0x1F40; i++);
}
}
int main(void) {
// set up clock to 80MHz
PLLFBD = 41; // sets M = 41+2 = 43
CLKDIVbits.PLLPRE = 0; // sets N1 = 2
CLKDIVbits.PLLPOST = 0; // sets N2 = 2
while (!OSCCONbits.LOCK); // wait for PLL ready
AD1CON1 = 0; // set everything to zero to start with.
AD1CON1bits.ADON = 0; // turn ADC off.
AD1CON1bits.ADSIDL = 0; // continue module operation in idle mode.
AD1CON1bits.ADDMABM = 1; // DMA buffers are written in the order of conversion.
AD1CON1bits.AD12B = 0; // set to 10bit mode.
AD1CON1bits.FORM = 3; // set data output to signed fractional.
AD1CON1bits.SSRC = 0; // manual conversion. clearing sample bit manually.
AD1CON1bits.SIMSAM = 1; // collect samples from channels 0, 1, 2, 3 simultaneously.
AD1CON1bits.ASAM = 0; // manual sample. samples when SAMP bit is set.
AD1CON1bits.SAMP = 0; // sample enable bit.
AD1CON1bits.DONE = 0; // ADC conversion status bit.
AD1CON2 = 0; // set everything to zero to start with.
AD1CON2bits.VCFG = 0; // converter voltage ref. set to AVdd and AVss.
AD1CON2bits.CSCNA = 0; // input scan select bit. set to do not scan.
AD1CON2bits.CHPS = 0; // channel select bits. set to just channel 0;
AD1CON2bits.BUFS = 0; // buffer fill status (invalid as BUFM is 0);
AD1CON2bits.SMPI = 0; // ADC interrupt is generated after every sample/conversion.
AD1CON2bits.BUFM = 0; // buffer fill mode. set to always start filling from start address.
AD1CON2bits.ALTS = 0; // Alternate input sample mode. set to always uses channel input from sample A.
AD1CON3 = 0; // set everything to zero to start with.
AD1CON3bits.ADRC = 0; // ADC conversion clock derived from system clock.
AD1CON3bits.SAMC = 0; // auto sample time bits, TAD, set to 0.
AD1CON3bits.ADCS = 0; // ADC conversion clock set to 0. 1 * TCY = TAD.
AD1CON4 = 0; // set everything to zero to start with.
AD1CON4bits.DMABL = 0; // allocates 1 word of buffer to each analogue input.
AD1CHS123 = 0; // everything set to zero as not using channels 1, 2, or 3.
AD1CHS0 = 0; // set everything to zero to start with.
AD1CHS0bits.CH0NB = 0; // channel 0 negative input, set by CH0NA. sample B.
AD1CHS0bits.CH0SB = 0; // channel 0 positive input, set by CH0SA. sample B.
AD1CHS0bits.CH0NA = 0; // channel 0 negative input, for sample A. set to VREFL.
AD1CHS0bits.CH0SA = 0; // channel 0 positive input is AN0.
AD1CSSL = 0; // input scan register set to zero as not using it.
AD1PCFGL = 0; // port configuration, set to analogue mode, ADC samples voltage.
AD1CON1bits.ADON = 1; // turn on ADC
AD1CON1bits.SAMP = 1; // Start sampling
DELAY(1); // Wait for sampling time (1ms)
AD1CON1bits.SAMP = 0; // Start the conversion
while (!AD1CON1bits.DONE); // Wait for the conversion to complete
ADCValue = ADC1BUF0; // Read the conversion result
while (1);
}
I have the sensor powered using the same rails the PIC is using, and I have the output of the sensor to AN0 (pin 2), as I set it up in the code. The PIC is powered to the standard Vss and Vdd (pins 8 and 13), the analogue power pins AVdd and AVss (pins 28 and 27), and a 33uF capacitor across Vcap and Vss (pins 20 and 19). Is there anything else I need to do hardware-wise? I'm a little confused with the AD1CHS0bits.CH0NA register, as I don't know if I have to connect ground to VREFL or what to do in that instance.
Any help with what I should be doing to correct this issue will be most appreciated! Also, any help on how to convert the value once it's received correctly will be very helpful.
If the value shouldn't be negative to start with, then you shouldn't be using this setting:
AD1CON1bits.FORM = 3; // set data output to signed fractional.
Were I would expect your value to be (evaluated using Python):
int((2**10) * # 10-bit operation
(0.7/3.3) # 0.7 volts on a 3.3 volt system
- (2**9) # Center at VDD / 2 because of signed operation
) << 6 # Fractional output left-shifted the 10-bit up by 6 to fill 16-bits
= -18816
Which sounds about what your code is outputting.
Instead use:
AD1CON1bits.FORM = 0; // set data output to integer.
Using this settings, along with 10-bit mode, I would expect your value to be
int((2**10) * # 10-bit operation
(0.7/3.3)) # 0.7 volts on a 3.3 volt system
= 217

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