PIC24 and DAC SPI MCP4921

For questions and comments on programming in general. And for any items that don't fit into the forums below.

Moderators: Benj, Mods

Post Reply
viki2000
Posts: 190
Joined: Mon Jul 07, 2014 9:38 am
Has thanked: 30 times
Been thanked: 77 times
Contact:

PIC24 and DAC SPI MCP4921

Post by viki2000 »

This is a sharing example with some questions about optimization.

I have implemented successful the code using CCS C compiler:
CCS code Sin3x.txt
(2.57 KiB) Downloaded 363 times
Then I decided to translate it for Flowcode 7:
PIC24HJ64GP202_sine_ASM_S3x_v1.fcfx
(12.76 KiB) Downloaded 319 times
PIC24HJ64GP202_sine_ASM_S3x_v2.fcfx
(11.1 KiB) Downloaded 286 times
The code uses SPI Master and not SPI Master EB013, not legacy as in the links below and not SPI CAL.
These are working examples tested on real device.

Then later on, after the work was done, I have seen next:
viewtopic.php?f=26&t=12592&p=50280&hili ... its#p50280
viewtopic.php?f=29&t=8705&p=26272&hilit ... its#p26272
http://www.matrixtsl.com/resources/file ... hnique.pdf

My example is about producing a sine wave analog signal using MCP4921 DAC SPI.
The idea is to write the code to produce a sine in the range 0…PI/2 (90°) and then considering the symmetry of the sine wave to use the same range to get the proper output.
My particular case I needed a rectified sine wave and not a full sine wave, so the situation is lot simpler: just produce 0…90° and then apply symmetry for 90°…180°.
I have implemented this using CCS C compiler in various ways in order to see the differences between them. The known and most simple way is using the lookup table (LUT) or DSP component that uses LUT.
I have a problem with that, because I want a high resolution analog signal with many points per signal period. We speak about 2000 points up to 10K points. Then takes a lot of memory and is hard to handle.
It works, I have tested it, but is not so nice and is slower than the solution presented below.
Next approach to make a sine wave is CORDIC. I have tested it, works with a good resolution, but slower than LUT, so does not allow so many points as LUT due to lower speed.
The sin(x) function with real/float numbers is the slowest method, just forget about it.
The last method that I am aware is polynomial approximation of the sine wave, Taylor series.
There are different polynomial orders and higher order means better approximation.
Just have a look here: https://www.desmos.com/calculator/xxkkb0gmvw
I have implemented 3rd order and 5th order polynomial approximation.
If this implementation is done with integers, then is fast. In fact is faster than LUT.
We can write the polynomial approximation in C or Flowcode Calculation block, but to be faster we must write it in ASM. It takes only few arithmetical instructions.
The lookup table version was implemented in C and to retrieve one element from that 2K array it takes 1.425us in C.
Polynomial implementation in C is slow, but better than float sin(x) default function calculation.
The ASM version of the 3rd order polynomial, calculation of one value, takes 250ns.
The ASM version of the 5th order polynomial, calculation of one value, takes 425ns.
The Microchip fixed point library _Q15sinPI takes 1.9us, but probably better accuracy using a higher order polynomial. That’s why I asked the problem here: viewtopic.php?f=7&t=18949
The CORDIC approach in C is better than polynomial in C as accuracy and speed (depending by the number of loops), but slower than polynomial in ASM.
When comes to accuracy and lower number of points, then LUT is the best. When the accuracy provided by polynomial approximation is acceptable by the application and the speed of processing is important, then polynomial approximation in ASM is the best. If we add DMA to polynomial in ASM, then I think is a rocket and we can go with many more points (samples per second). That’s why I asked the question here: viewtopic.php?f=7&t=19055
I will present here the ASM polynomial approximation of the 3rd order.
How to arrive to that ASM code and certain polynomial format is a long story and we can discuss later if someone is interested, but basically I was inspired by next:
http://www.coranac.com/2009/07/sines/

At the end of the above link you may see the ASM code also for 5th order polynomial and also the stripped out ASM code of the _Q15sinPI from Microchip fixed point library.
In my case the argument of the function is Q14, meaning 16384 values=2^14, in the range 0…16383 and the function result is Q12, approx. 4096=2^12 values in the range 0…4093.

Due to the approximations done and because of the limited speed of the SPI interface and SPI subroutine, I limited the number of samples to around 3400 per period in my case.
I generate a rectified sine wave with frequency around 100Hz (can be easy tweaked), 10ms period and each 10ms has around 3400 samples.
I sweep the range 0..15489 with step 9, meaning 1721 samples, for 0…PI/2 of the sine wave.
The MCP4921 works at 20MHz SPI frequency.
The PIC24 that I used is PIC24HJ64GP202 at 80MHz internal clock and SPI at 20MHz.
The setup of the PIC24HJ64GP202 clock is here:
viewtopic.php?f=7&t=18929

The only problem that I encountered during setup and receiving analog signal was related with SPI parameters as follows:
- If Clock Polarity was Idle Low (Default) and Clock Phase was Leading Edge, then MCP4921 did not work, did not provide any analog output signal.
- Everything was fine if I change the Clock Phase to Trailing Edge and maintained Clock Polarity to Idle Low.
- Everything was fine as well if Clock Polarity was set Idle High. In such case the Clock Phase was not important, it worked either way.

I provided 2 examples of FC7 code. The v1 is a bit faster due to while loop, but takes more memory. The v2 is a bit slower due to counting loop 1720, but is half of size.
If you have any other ideas how to optimize even better in favor of size or speed would be nice to hear about it.

I would like to increase the number of points per period (samples per second) even more.
There are 2 options: a PIC with higher frequency and DAC SPI with higher frequency or the existing PIC24HJ64GP202 + MCP4921 and using DMA SPI.
Any idea how to setup that? (viewtopic.php?f=7&t=19055 )

Here are some questions related with the present Flowcode 7 project and SPI.
I look at oscilloscope SPI signals and I see 2 frames of 8 clock signals.

Why is not possible to have continuous 16 clocks and one SPI Transmit function?

PIC24 works on 16bit, so has 16bit wide registers and here is the user manual of PIC24 SPI:
http://ww1.microchip.com/downloads/en/D ... 05185a.pdf
If the 16 continuous clock is a problem, then why is not possible to write a single instruction block in Flowcode 7 similar as send string, but to be integers, 2 bytes long? I mean, instead of sending Char as 1 byte, why is not possible to send 2 or 3 Char in one instruction? That will be helpful for DACs with 12bit, 16bit or 24bit. As it is now we send 1 byte with Char and then we repeat.
I could not use Send String, because it is not a PC or another uC on the other side of the SPI. It is a DAC that requires specific bits order and not integers converted to strings.
My question here is: how could be, at least for future, simplified the SPI transmission for more bytes continuously in one instruction, but not strings? I have no idea now how could I use the strings for DAC SPI to take advantage of a single SPI Transmit instruction/block.
Do you have any ideas?

Post Reply