• Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

Learning C

Pommie

Well-Known Member
Most Helpful Member
To demonstrate how to pass back a value we can write the readEEPROM function,
You should be able to look at the datasheet now and see how it's the same as the example on page 98.
Code:
unsigned char readEEPROM(unsigned char address){
    EEADRL=address;
    CFGS=0;
    EEPGD=0;
    RD=1;
    return(EEDATL);
}
To test this we need to add a prototype and we'll use a global variable to store the result.
So, at the top of the code is,
Code:
void sendDDSword(int);
void writeEEPROM(unsigned char,unsigned char);
unsigned char readEEPROM(unsigned char);

unsigned char test;

void main(void){
test is our new variable and we can test the readEEPROM routine straight after the writeEEPROM,
Code:
    WPUA = 0x00;
    writeEEPROM(0,0x23);
    test=readEEPROM(0);
    while(1){
Now, when we compile, run and then pause the code, we can hover over the variable and see it contains 0x23,
test.png
And we know that works too.

Mike.
 
Last edited:

Pommie

Well-Known Member
Most Helpful Member
No worries, take your time.

Looked at the datasheet for the DDS and your code again and see that it actually requires 40 bits to be sent to it. First bit sent is the LSB of the frequency and last bit sent is the MSB of the phase, is this correct?

Maybe the function could be turned into void sendDDS(long freq,char control,char power,char phase)? Just a suggestion.

I also note that you have a timed interrupt used to do timeouts etc. With the newer chips I tend to setup a 1mS interrupt and use that to time events. Does it make sense to do this to you?

Mike.
 
Forgot to add for Mike - OK on the GIE stuff, I was going to suggest we leave that to near the end anyway and just get the basics of making the frequency output go up & down working first, then add all the other bits.
 
No worries, take your time.

Looked at the datasheet for the DDS and your code again and see that it actually requires 40 bits to be sent to it. First bit sent is the LSB of the frequency and last bit sent is the MSB of the phase, is this correct?

Mike.
Sounds right, but let me double check - yes - 40 bits - the last byte sent is always 0x0 for the AD9850 or 0x1 for the AD9851 in all of my projects.
 
As I've placed this code at the end of the code I need to add a prototype to the top.
The top of the code now looks like,
Code:
#define cal_freq 0x00501BD0L

#define DDSdata AN0
#define DDSclock AN2

void sendDDSword(int);
void writeEEPROM(unsigned char,unsigned char);

void main(void){
To test the function I added a call to it just before the while(1),
Code:
    WPUA = 0x00;
    writeEEPROM(0,0x23);
    while(1){
    }
I can compile this and it will run.......

You can try this for yourself.

Mike.
Edit, I missed out the GIE stuff as I don't have an IRQ yet and didn't want to tempt fate.
Got that to work - I'm using a standalone program for my PicKit3 - I don't trust MPlabX not to bugger it up, but I can read the PIC back and see the same as your example.
 

Pommie

Well-Known Member
Most Helpful Member
Good to hear that you can get it working. Are you able to explain what calc_dds_word does? It states where the result is stored but not what it is.

I'm slowly getting to understand this chip so sorry for the newby questions.

Mike.
 
calc_dds_word transfers the osc_0 - 4 values to temporary variables (osc_temp_0 - 4) and multiplies those values by the value in freq_0 - 3 to get what they call the tuning word value - this is stored in AD9851_0 - 4 which is sent to the DDS module.

AD9851_0 - 4 is not saved as it is not relevant after the tuning word is sent to the DDS chip (unless you are doing thing's like dual VFO's or some other fancy stuff).

Some info below about the calculation of ref_osc (the defines at the head of the program) which is osc_0 - 4 in the program.

; ref_osc represents the change in the frequency control word which results
; in a 1 Hz change in output frequency. It is interpreted as a fixed point
; integer in the format <ref_osc_3>.<ref_osc_2><ref_osc_1><ref_osc_0>
;
; The values for common oscillator frequencies are as follows:
;
; Frequency ref_osc_3 ref_osc_2 ref_osc_1 ref_osc_0
;
; 180.00 MHz 0x17 0xDC 0x65 0xDF
; 125.00 MHz 0x22 0x5C 0x17 0xCA
; 120.00 MHz 0x23 0xCA 0x98 0xCE
; 100.00 MHz 0x2A 0xF3 0x1D 0xC4
; 90.70 MHz 0x2F 0x5A 0x82 0x7A
; 66.66 MHz 0x40 0x6E 0x52 0xE7
; 66.00 MHz 0x41 0x13 0x44 0x5F
; 50.00 MHz 0x55 0xE6 0x3B 0x88
; 30.00 MHz 0x8F 0x2A 0x63 0x39
;
; To calculate other values:
; ref_osc_3 = (2^32 / oscillator_freq_in_Hertz).
; ref_osc_2, ref_osc_1, and ref_osc_0 are the fractional part of
; (2^32 / oscillator_freq_in_Hertz) times 2^24.
; Note: 2^32 = 4294967296 and 2^24 = 16777216
;
; For example, for a 120 MHz clock:
; ref_osc_3 is (2^32 / 120 x 10^6) = 35.791394133 truncated to 35 (0x23)
; ref_osc_2 is the high byte of (.791394133 x 2^24) = 13277390.32
; 13277390.32 = 0xCA98CE, so high byte is CA.
; ref_osc_1 is the next byte of 0xCA98CE, or 98
; ref_osc_0 is the last byte of 0xCA98CE, or CE
;
; For example, for a 180 MHz clock:
; ref_osc_3 is (2^32 / 180 x 10^6) = 23.860929422 truncated to 23 (0x17)
; ref_osc_2 is the high byte of (.860929422 x 2^24) = 14443998
; 14443998 = 0xDC65DE, so high byte is 14.
; ref_osc_1 is the next byte of 0xDC65DE, or 65
; ref_osc_0 is the last byte of 0xDC65DE, or DE
 
OK, back, although now there is a bit of cooler weather around, outside chores will take up a bit of time.

So, next dumb question - I'm assuming that the while(1){ area is what will hold the the equivalent of 'main' from the asm program?
 
Slight off-track - is there any way to stop MPlabX burying compiled hex files 4 folders deep and adding this rubbish to the end - Simple_VFO.X.production.hex, took me forever to find it the other day.

If I wanted weird **** added to a file name, I'd do it myself.
 
In between doing some reading, household chores etc, I've been hunting some bits of code and have got these so far (writing them in C from scratch is way beyond me at the moment)

Firstly for the encoder:
Code:
/*--- Encoder lookup table ---*/

const signed char table[] = {0,-1,+1,0,+1,0,0,-1,-1,0,0,+1,0,+1,-1,0};

/*--- Initialise Encoder ---*/

void init_encoder(void)
  {
  ANSELH &= 0xfc;   /* RB2 and RB3 as digital I/O */
  IOCB = 0x0c;      /* Interrupt on change enabled for RB2, RB3 */

  encoder_count = 0;

  RBIF = 0;
  RBIE = 1;         /* Interrupt on change enable */
  }
 
/*--- Encoder function ---*/

void encoder_click(void)
  {
  static unsigned char previous = 0;
  unsigned char temp;

  temp = 5;

  while(temp--){ /* debounce */
    ;
    }

  temp = PORTB;     /* Read port */
  temp >>= 2;       /* Shift input to bit positions 1, 0 */
  temp &= 0x03;     /* Mask out bits */
  previous <<= 2;   /* shift the previous data left two places */
  previous |= temp; /* OR in the two new bits */

  encoder_count += table[(previous & 0x0f)];  /* Index into table */
  }

/*--- End of File ---*/
And associated header file

Code:
/*--------------------------------------------------------------------
  
  Title       : Header file for encoder.c
  Filename    : encoder.h
  
----------------------------------------------------------------------*/

#ifndef ENCODER_H
#define ENCODER_H

/*--- Global encoder count ---*/

extern volatile int encoder_count;

/*--- Function prototype ---*/

void init_encoder(void);
void encoder_click(void);

#endif

/*--- End of File ---*/
I 'think' I understand most of that one as it is almost identical to the asm encoder routine Mike posted in my other old (LCD) thread.

For the DDS tuning word calculation and write, I have found this that seems simple enough, there are bits I don't understand, but will ask about that later.
Although written for an STM32, I beleive the idea of C is that this should run on the PIC with suitable modifications.

Code:
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include "ad9850.h"

#define DDS_PORT GPIOA
#define DDS_RCC RCC_GPIOA
#define DDS_CLOCK GPIO2
#define DDS_RESET GPIO3
#define DDS_LOAD GPIO4 // Also called FQ_UD (Frequency Update)
#define DDS_DATA GPIO5

static const float DDS_REF = 125e6;

void dds_setup(void)
{
    rcc_periph_clock_enable(DDS_RCC);
    gpio_set_mode(DDS_PORT, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL,
            DDS_CLOCK | DDS_RESET | DDS_LOAD | DDS_DATA);
}

void dds_reset(void)
{
    // Set everything low first
    gpio_clear(DDS_PORT, DDS_CLOCK | DDS_RESET | DDS_LOAD | DDS_DATA);

    // Pulse reset
    gpio_set(DDS_PORT, DDS_RESET);
    gpio_clear(DDS_PORT, DDS_RESET);

    // Pulse clock
    gpio_set(DDS_PORT, DDS_CLOCK);
    gpio_clear(DDS_PORT, DDS_CLOCK);

    // Pulse load
    gpio_set(DDS_PORT, DDS_LOAD);
    gpio_clear(DDS_PORT, DDS_LOAD);
}

void dds_write(uint8_t byte)
{
    uint8_t i;
    uint8_t bit;
    for(i = 0; i < 8; i++) {
        bit = ((byte >> i) & 1);
        if(bit == 1)
            gpio_set(DDS_PORT, DDS_DATA);
        else
            gpio_clear(DDS_PORT, DDS_DATA);

        gpio_set(DDS_PORT, DDS_CLOCK);
        gpio_clear(DDS_PORT, DDS_CLOCK);
    }
}

void dds_update_freq(float freq)
{
    // Updates DDS output frequency. Supply frequency in Hz.

    uint32_t tuning_word = (freq * 4294967295UL) / DDS_REF;
    dds_write(tuning_word & 0xFF);
    dds_write((tuning_word >> 8) & 0xFF);
    dds_write((tuning_word >> 16) & 0xFF);
    dds_write((tuning_word >> 24) & 0xFF);
    dds_write(0);

    gpio_set(DDS_PORT, DDS_LOAD);
    gpio_clear(DDS_PORT, DDS_LOAD);
}
And again, associated header file:

Code:
#ifndef AD9850_H
#define AD9850_H

#include <stdint.h>

void dds_setup(void);
void dds_reset(void);
void dds_write(uint8_t byte);
void dds_update_freq(float freq);

#endif  // AD9850_H
So the question is, is the above in the ball park of what I need to get my head around to carry on converting the my ASM file?

I realise that the frequency limit (upper and lower) and other bits need to be added, but I just want to get to the first part to being able to get a changeable frequency out of the DDS as a start.
 

rjenkinsgb

Well-Known Member
Most Helpful Member
Just a thought -

The 12F1820 has hardware SPI, which would allow the data transfer to the DDS IC to be faster and avoid the "bit bashing" serial data.
It depends which pins you are using? That can only use pin 3 or 7 for data & 6 for clock.


Also, you could pre-define the final constant for the DDS; eg. the example code you found above code has

C:
static const float DDS_REF = 125e6;

// and separately:
uint32_t tuning_word = (freq * 4294967295UL) / DDS_REF;
That forces the multiplication first, then the division, each update.

If you do the division beforehand to create a new floating point constant, the result should be the same but it eliminates the floating point division at every update, which is a relatively slow operation.
If there are no other floating point divisions, the final program should be smaller too as the floating point division code will probably be left out of the binary, depending how modular the libraries are.
eg.

C:
#define DDS_CONST 4294967295.0
#define DDS_REF 125000000.0

static const float DDS_FACTOR = DDS_CONST / DDS_REF;
// That should be worked out by the compiler and stored as a single value.

// And change the calc slightly
    uint32_t tuning_word = ((float)freq * DDS_FACTOR);

or even
Code:
#define DDS_CONST 4294967295.0
#define DDS_REF 125000000.0

#define DDS_FACTOR = DDS_CONST / DDS_REF;
// That should be worked out by the compiler and stored as a single value.

// And change the calc slightly
    uint32_t tuning_word = ((float)freq * DDS_FACTOR);
Which makes it a literal numeric constant, which may be fractionally faster again than a static const.
(It depends on the compiler; you could try both).

Or calculate it by hand and use the result in the multiplication - that just makes it less readable and less convenient to change the ref value, if you ever do, without any speed advantage.
 
I can't use the hardware SPI as I am converting the code to work on existing hardware, so bit banging it has to be.

Understand your point re the conversion to get the tuning word, but being totally hopeless at maths, I'm going to have to sit down with pencil & paper and see if the alternatives actually work, but I'm not sure they will.

Edit: As I said, totally useless at maths but it gives the same answer according to my calculator, so something to try.
 
Last edited:

Nigel Goodwin

Super Moderator
Most Helpful Member
I can't use the hardware SPI as I am converting the code to work on existing hardware, so bit banging it has to be.
It doesn't really matter anyway - you've got no need for speed (good title for a film there somewhere?), you're only setting a chip up so bit banging is fine.

If you're trying to do colour graphics on an SPI LCD module, that's a different matter - you need more speed than you can get :D But even then, it will still work with bit banging, and it's an easy way to check everything is connected the right way.
 

Latest threads

EE World Online Articles

Loading
Top