ADC dsPIC33 issue - c

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

Related

Microchip PIC24FJ SPI writes correctly but only reads zeros

I have a PIC24FJ256GA702 communicating with a AD5724RBREZ quad DAC with a SPI link.
The DAC works fine, writing is no problem, but I am stuck on reading back the control register from it.
I get the correct waveform on the PIC pin that I am expecting, and the read routine runs, but it always returns zeros. The waveform is clearly not zero- the waveform on the scope is correct for the 4 channels and the internal reference being enabled.
Scope view of waveforms - blue = clock, yellow = data input from DAC at PIC pin
(The excessive ringing on the scope image is probably caused by a long distance ground connection- in practice these chips are about 25mm apart.)
I thought that the input pin was configured as an analogue, but it was correctly a digital input.
I connected it to a counter based on Timer1, and that counter does count if I try to read the DAC. This suggests that the PPS is working, the pin is not bust, and the the input signal is clean enough to use.
I think it may be a problem with the code or the decode timing of the SPI module, but as shown in the image the data is stable during the clock cycle so I cannot see what is wrong.
I have searched the forums, and it seems most with this problem trace it to analogue functions being enabled but that is not the case here.
Would anyone like to suggest something else to try, or post some working SPI read code if my code is not looking correct?
The code follows-
void AOUT_init(void)
{
//assume PPS is unlocked (it is on reset) - see note below
//setup SPI1 itself
SPI1CON1L = 0x0160; //TX work read 0
//SPI1CON1L = 0x0060; //TX not work
//SPI1CON1L = 0x0120; //TX work, read 0
//SPI1CON1L = 0x0020; //not tried
SPI1CON1H = 0x3000;
SPI1CON2L = 0x0017; //word length (17 hex = 24 bits)
//BRG
SPI1BRGL = 0x0000; //default = no divisor
//PPS - assume unlocked at this point
ANSBbits.ANSB13 = 0;
TRISBbits.TRISB13 = TRIS_INPUT;
//##########################################################################
RPINR20bits.SDI1R = 13; //set SDI1 data input to PIC to RB13
//##########################################################################
TRISBbits.TRISB15 = TRIS_OUTPUT;
RPOR7bits.RP15R = 8; //RB15 to SDI1 clock out from PIC
TRISBbits.TRISB14 = TRIS_OUTPUT;
RPOR7bits.RP14R = 7; //RB14 to SDI1 data out from PIC
//AD5724R has additional lines - not all used in practice
//setup and set initial level here
//AOUT-/LDAC
TRISBbits.TRISB6 = TRIS_OUTPUT;
LATBbits.LATB6 = 1;
//AOUT-/SYNC
TRISBbits.TRISB7 = TRIS_OUTPUT;
LATBbits.LATB7 = 1;
//AOUT-/CLR
TRISBbits.TRISB12 = TRIS_OUTPUT;
LATBbits.LATB12 = 1;
//turn SPI on
SPI1CON1Lbits.SPIEN = 1;
SPI1CON1Lbits.MSTEN = 1; //included in definition above
//now setup the AD chip
//output range set
AOUT_TX(0x0C00,0x0100); //all channels to 10V
//control
AOUT_TX(0x1900,0x0500);
//power control - enable DACs
AOUT_TX(0x1000,0x1F00);
}
The comms routine below is included for completeness- it just controls the other DAC lines. The /SYNC line is doing a chip select function.
static void AOUT_Comms(bool bSync, bool bLDAC, bool bClr)
{
//AOUT-/LDAC
LATBbits.LATB6 = bLDAC;
//AOUT-/SYNC
LATBbits.LATB7 = bSync;
//AOUT-/CLR
LATBbits.LATB12 = bClr;
}
This is write routine which works fine.
void AOUT_TX(uint16_t dataH, uint16_t dataL)
{
//AOUT uses 24 bit data
//this routine handles /SYNC line
//relies on AD chip having much faster response than chip cycle time
//AD chip limits at about 38MHz so much quicker than PIC.
AOUT_Comms(0,1,1);
SPI1BUFL = dataL;
SPI1BUFH = dataH;
while(SPI1STATLbits.SPIBUSY) //wait until sent
{
;
}
AOUT_Comms(1,1,1);
//
}
This is the read routine, which uses the routines above.
void AOUT_read_control(uint16_t ReadH, uint16_t ReadL)
{
uint16_t temp;
//to read, transmit the register to be read, then transmit a dummy command "NOP" to clock the data out.
//send register
AOUT_TX(0x9000,0x0000);
//read out- this is similar to write but reads the received buffer at the end.
//clear buffer
while (SPI1STATLbits.SPIRBF)
{
temp = SPI1BUFL;
temp = SPI1BUFH;
}
AOUT_Comms(0,1,1);
SPI1BUFL = 0;
SPI1BUFH = 0x1800; //nop operation via control register
while(SPI1STATLbits.SPIBUSY) //wait until sent
{
;
}
while (!SPI1STATLbits.SPIRBF)
{
; //hold until something received
}
ReadH = SPI1BUFH;
ReadL = SPI1BUFL; //these read zero
AOUT_Comms(1,1,1);
//
//dummy so can check counter also connected to same pin
temp = TMR1;
temp = TMR1;
}
Update-
I checked the SPI read decode by sending the received words directly to a display as suggested by the comments. I get 0000 for both words.
Also as suggested I connected a logic analyser at the PIC pins, and it decodes both read and write correctly.Logic Analyser view- Ch1/Blue/MOSI writing 0x180000, Ch2/Green/MISP reading 0x00001F, both correct

AnalogValue PIC16F18875 serial print problem

I am having troubles with my curiousity HPC board. I am new to microchip and i want to read the analogValue of the onboard potentiometer. I am using a serial converter to display the value in a terminal on my PC.
It works fine, EXCEPT when i fully turn the potentiometer from beginning to end, the analogValue goes from 0 to 255, drops to 0, goes up to 255, drops to 0, goes up to 255, drops to 0 and goes up to 255. I expected it to go from 0 to 1023. All my variables are 16 bits. The uC is the PIC16F18875 and the serial converter is UM230XB.
How can this be? All the variables and constant have the right size (unless i am missing one)
This is the main.c:
void main(void){
SYSTEM_Initialize();
static uint16_t adcResult; // Used to store the result of the ADC
adcResult = ADCC_GetSingleConversion(POT_CHANNEL);
IO_RC7_SetLow();
console_print("Value = ");
console_print_dec_char(adcResult);
console_print("\t\n\r");
__delay_ms(100);
This is the ADCC_GetSingleConversion function:
adc_result_t ADCC_GetSingleConversion(adcc_channel_t channel){
// select the A/D channel
ADPCH = channel;
// Turn on the ADC module
ADCON0bits.ADON = 1;
//Disable the continuous mode.
ADCON0bits.ADCONT = 0;
// Start the conversion
ADCON0bits.ADGO = 1;
// Extra NOP() instruction required; See rev. A2 errata: http://ww1.microchip.com/downloads/en/DeviceDoc/80000669C.pdf
NOP();
// Wait for the conversion to finish
while (ADCON0bits.ADGO)
{
}
// Conversion finished, return the result
return ((adc_result_t)((ADRESH << 8) + ADRESL));
ADC_result_t is also 16 bits.
You should cast the 8 Bit value of ADRESH to a 16 Bit value before the left shift, otherwise the result of the shift is always 0.
return ((adc_result_t)(((uint16_t)ADRESH << 8) + ADRESL));

DRDY PIC18F45K80 to QT1481

I also have a problem with DRDY. I need to include DRDY. The pins for DRDY are RD2 and RD5. They are both inputs.
Here is the information for DRDY.
DRDY Pin
DRDY is an open-drain output (in SPI mode) or bidirectional pin (in UART mode) with an internal 20 k – 50 k pullup
resistor.
Most communications failures are the result of failure to properly observe the DRDY timing.
Serial communications pacing is controlled by this pin. Use of DRDY is critical to successful communications with the
QT1481. In either UART or SPI mode, the host is permitted to perform a data transfer only when DRDY has returned
high. Additionally, in UART mode, the QT1481 delays responses to the host if DRDY is being held low by the host.
After each byte transfer, DRDY goes low after a short delay and remains low until the QT1481 is ready for another
transfer. A short delay occurs before DRDY is driven low because the QT1481 may otherwise be busy and requires
a finite time to respond.
DRDY may go low for a microsecond only. During the period from the end of one transfer until DRDY goes low and
back high again, the host should not perform another transfer. Therefore, before each byte transmission the host
should first check that DRDY is high again.
If the host wants to perform a byte transfer with the QT1481 it should behave as follows:
1. Wait at least 100 µs after the previous transfer (time S5 in Figure 3-2 on page 23: DRDY is guaranteed to go
low before this 100 µs expires).
2. Wait until DRDY is high (it may already be high).
3. Perform the next transfer with the QT1481.
In most cases it takes up to 3 ms for DRDY to return high again. However, this time is longer with some commands
or if the STS_DEBUG setup is enabled, as follows:
0x01 (Setups load): <20 ms
0x02 (Low Level Cal and Offset): <20 ms
Add 15 ms to the above times if the STS_DEBUG setup is enabled.
Other DRDY specifications:
Min time DRDY is low: 1 µs
Max time DRDY is low after reset: 100 ms
The timing diagram is this:
How can implement that?
The code I have written with my friend is written here:
#include <xc.h>
#include "PIC.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
//#include <pic18f45k80.h>
#define MSB 1
#define LSB 0
// SPI PIN CONFIGURATION
#define SCK_TRIS TRISCbits.TRISC3 = 0 ;
#define SDO_TRIS TRISCbits.TRISC5 = 0 ;
#define SDI_TRIS TRISCbits.TRISC4 = 1 ;
#define QTA_SS_TRIS TRISDbits.TRISD4 = 0 ;
#define QTB_SS_TRIS TRISEbits.TRISE2 = 0 ;
#define QTA_SS_LAT_LOW LATDbits.LATD4 = 0 ;
#define QTA_SS_LAT_HIGH LATDbits.LATD4 = 1 ;
#define QTB_SS_LAT_LOW LATEbits.LATE2 = 0 ;
#define QTB_SS_LAT_HIGH LATEbits.LATE2 = 1 ;
#define QTA_DRDY_TRIS TRISDbits.TRISD5 = 1 ;
#define QTB_DRDY_TRIS TRISDbits.TRISD2 = 1 ;
#define QTA_DRDY_LAT_LOW LATDbits.LATD5 = 0 ;
#define QTA_DRDY_LAT_HIGH LATDbits.LAT52 = 1 ;
#define QTB_DRDY_LAT_LOW LATDbits.LAT25 = 0 ;
#define QTB_DRDY_LAT_HIGH LATDbits.LATD2 = 1 ;
#define QTB_DRDY PORTDbits.RD2 ;
#define QTA_DRDY PORTDbits.RD5 ;
// FREQUENCY SELECT
#define _XTAL_FREQ 16000000
// PIN SETUP
void PIN_MANAGER_Initialize(void)
{
/**
LATx registers
*/
LATE = 0x00;
LATD = 0x00;
LATA = 0x00;
LATB = 0b00010000;
LATC = 0x00;
/**
TRISx registers
*/
TRISE = 0x00;
TRISA = 0x08;
TRISB = 0x01;
TRISC = 0b00010000;
TRISD = 0xEF;
PORTC = 0b00010010 ;
/**
ANSELx registers
*/
ANCON0 = 0x00;
ANCON1 = 0x00;
/**
WPUx registers
*/
WPUB = 0x00;
INTCON2bits.nRBPU = 1;
}
// SPI
void SPI_Initialize(void)
{
// SMP Middle; CKE Idle to Active;
SSPSTAT = 0b00000000;
// SSPEN enabled; WCOL no_collision; CKP Idle:High, Active:Low; SSPM FOSC/4; SSPOV no_overflow;
SSPCON1 = 0b00111010;
// SSPADD 0;
SSPADD = 0x00;
ADCON0 = 0 ;
ADCON1 = 0x0F ; //Makes all I/O digital
SCK_TRIS ;
SDO_TRIS ;
SDI_TRIS ;
QTA_SS_TRIS ;
QTB_SS_TRIS ;
QTA_DRDY_TRIS ;
QTB_DRDY_TRIS ;
}
signed char WriteSPI( unsigned char data_out )
{
unsigned char TempVar;
TempVar = SSPBUF; // Clears BF
PIR1bits.SSPIF = 0; // Clear interrupt flag
SSPCON1bits.WCOL = 0; //Clear any previous write collision
SSPBUF = data_out; // write byte to SSPBUF register
if ( SSPCON1 & 0x80 ) // test if write collision occurred
return ( -1 ); // if WCOL bit is set return negative #
else
while( !PIR1bits.SSPIF ); // wait until bus cycle complete
return ( 0 ); // if WCOL bit is not set return non-negative#
}
unsigned char ReadSPI( void )
{
unsigned char TempVar;
TempVar = SSPBUF; // Clear BF
PIR1bits.SSPIF = 0; // Clear interrupt flag
SSPBUF = 0x00; // initiate bus cycle
while(!PIR1bits.SSPIF); // wait until cycle complete
return ( SSPBUF ); // return with byte read
}
unsigned char DataRdySPI( void )
{
if ( SSPSTATbits.BF )
return ( +1 ); // data in SSPBUF register
else
return ( 0 ); // no data in SSPBUF register
}
// SOFTWARE EUART
void out_char(char character, char bit_order){
uint8_t i = 0;
RSOUT = 1 ; // MSB
__delay_ms(1);
RSOUT = 0 ; // START
__delay_us(100);
for (i = 8; i>0; --i){
if (bit_order){ // Bit order determines how you will put the bits, from left to right (MSB) or right to left (LSB)
RSOUT = (character & 0x80) ? 1:0; // in MSB you compare the left-most bit doing an AND with 0x80, and put 1 if true, 0 elsewhere.
character <<= 1; // Shift the character to the left, discrading the bit just sent
} else {
RSOUT = (character & 0x01); // in LSB you compare the right-most bit doing an AND with 0x01, and put 1 if true, 0 else.
character >>= 1; // Shift the character to the right, discrading the bit just sent
}
__delay_us(100);
}
RSOUT = 1 ; // STOP
}
void out_str(char * string, uint8_t len, char bit_order){
uint8_t i = 0;
for (i = 0; i< len; i++){
out_char(string[i], bit_order);
}
}
void SYSTEM_Initialize(void)
{
PIN_MANAGER_Initialize() ;
SPI_Initialize() ;
}
void main(void)
{
SYSTEM_Initialize() ;
while (1)
{
QTB_SS_LAT_LOW ; // Transmit data
char temp ;
WriteSPI(0x0F) ; // Send a byte
while(!DataRdySPI()) ; // wait for a data to arrive
temp = ReadSPI(); // Read a byte from the
QTB_SS_LAT_HIGH ; // Stop transmitting data
__delay_us(100) ;
}
}
No. Do not just write a bunch of code, then see what it does. That kind of shotgun (or, if you prefer, spaghetti-to-the-wall) approach is a waste of effort.
First, drop all those macros. Instead, write comments that describe the purpose of each chunk of code, like the first three assignments in your SPI_Initialize() function.
Next, convert your specification to pseudocode. The format does not matter much, just use something that lets you keep your mind focused on what the purpose is, rather than on the details on how to do it.
The datasheet says that with SPI, there are three outputs from the PIC (^SS, SCK, MOSI on the QT1481), and two inputs (^DRDY and MISO on the QT1481). I'll use those names for the data lines, and for their respective I/O pin names on the PIC.
The setup phase on the PIC should be simple:
Make ^DRDY an input
Make ^SS an output, set it HIGH
Make SCK an output, set it LOW
Make MOSI an output, set it LOW
Make MISO an input
Set up SPI using SCK, MOSI, MISO
Each transfer is a bidirectional one. Whenever you send data, you also receive data. The zero command is reserved for receiving multiple data, says the datasheet. So, you only need a function that sends a byte, and at the same time receives a byte:
Function SPITransfer(command):
Make sure at least 0.1ms has passed since the previous transfer.
Do:
Nothing
While (^DRDY is LOW)
Set ^SS LOW
response = Transfer(command)
Set ^SS HIGH
Return response
End Function
As I understand it, for PICs and properly initialized hardware SPI the response = Transfer(command) line is in C
SSPBUF = command;
while (!DataRdySPI())
;
response = SSPBUF;
You can also bit-bang it, in which case it is (in pseudocode):
response = 0
For bit = 7 down to 0, inclusive:
If (command & 128):
Set MOSI high
Else:
Set MOSI low
End If
Set SCK low
Sleep for a half period
command = command / 2
response = response * 2
If MISO high:
response = response + 1
End If
Set SCK high
Sleep for a half period
End For
but obviously the hardware SPI approach is better.
(When you get this working, you can use the hardware SPI without a wait loop from a timer interrupt, making the communications essentially transparent to the "main operation" of the PIC microcontroller. That requires a slightly different approach, with a command and response queues (of a few bytes), but will make it much easier for the PIC to do actual work, other than just scan the QT1481.)
After a reset, you essentially send 0x0F until you get 0xF0 back:
while (SPITransfer(0x0F) != 0xF0)
;
At this point, you have the steps you need to implement in C. OP also has the hardware (an oscilloscope) to verify their code works.

Can't initialize PWM on dsPIC33F

I'm probably just having a can't-see-the-forest-for-the-trees moment with one of these registers, but I can't get the PWM working on the dsPIC33FJ32MC102 microcontroller (warning: big PDF) I'm playing around with. I've followed both the datasheet and further application note (warning: another PDF) and even code samples and I can't see what I'm doing wrong, though on my testbench I'm getting Vcc on the high output and Ground on the low output. I've tied the fault pins both to Vcc and disabled the register keycode so my changes should see some effect. What am I doing wrong?
#define FOSC (3686400ULL)
#define FCY (FOSC/2)
#include <xc.h>
#include <libpic30.h>
...
#pragma config PWMPIN = ON // Motor Control PWM Module Pin Mode bit (PWM module pins controlled by PORT register at device Reset)
#pragma config PWMLOCK = OFF
...
void main(void){
...
//setup PWM
//Clear faults
IFS3bits.PWM1IF = 0;
IFS3bits.FLTA1IF = 0;
IFS4bits.FLTB1IF = 0;
//Setup dead times
P1DTCON1bits.DTAPS = 0b00; //Dead time tick is 1 TCY
P1DTCON1bits.DTBPS = 0b00;
P1DTCON1bits.DTA = 10; //Dead time is 10TCY ~= 3uS
P1DTCON1bits.DTB = 10;
P1DTCON2bits.DTS1A = 0; //Active and Inactive transition dead times
P1DTCON2bits.DTS2A = 0; //0 takes A dead time
P1DTCON2bits.DTS3A = 0; //1 takes B dead time
P1DTCON2bits.DTS1I = 1;
P1DTCON2bits.DTS2I = 1;
P1DTCON2bits.DTS3I = 1;
P1TCONbits.PTOPS = 0b0000; //1 CPU tick = 1 PWM tick
P1TCONbits.PTCKPS = 0b00;
P1TCONbits.PTMOD = 0b00;
P1TCONbits.PTSIDL = 0; //Run when CPU idles
// no longer necessary since I disabled register write lock:
// __builtin_write_PWMSFR(&P1FLTACON,0x0000,&PWM1KEY);
// __builtin_write_PWMSFR(&P1FLTBCON,0x0000,&PWM1KEY);
// __builtin_write_PWMSFR(&PWM1CON1 ,0x0077,&PWM1KEY);
PWM1CON1 = 0x0077; //Enable all 3 channels
P1FLTACON = 0x0000; //Disable faults
P1FLTBCON = 0x0000;
//Setup Wave freq/duty
//Fosc = 7.3728 MHz -> Fcy = 3.6864MHz
//Desire a PWM of 20250Hz (smaller scalar error than 20kHz)
//P1TPER = [Fcy/(Fpwm*Scalar)] - 1
//Therefore P1TPER = [3.6864M/(20250*1)] - 1 = 181;
P1TPER = 181;
P1DC1 = 0x7FFF; // 0x7FFF for 50%
P1DC2 = 0x7FFF;
P1DC3 = 0x7FFF;
P1OVDCON = 0x3F00; //Disable override; override disables PWM
PWM1CON2 = 0x0000;
P1TCONbits.PTEN = 1; //Turn on
...
while(1);
}
I believe you have set up the timer to count from 0 to 181 (P1TPER) and then reset and repeat. But you have set the duty cycle registers to 0x7FFF, which is greater than 181. So I believe the duty cycle value will never be less than the timer value and therefore the output will never change. Try setting the duty cycle registers to 181/2 = 90 to get a duty cycle of 50%.

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