I had posted quite some time ago about getting all negative values when I setup DMA. However, I also posted that the problem went away and I was able to read correctly when started using the macro with ADC as the input.
I dont think the DMA is being setup correctly as the macro XDATA_SFR_ADDRESS returns dfba and dfbb for ADCL and ADCH. When I setup the src and dest addresses in DMA, I am actually using df as ADCH and ba for ADCL in DMA SRCH and SRCL. I suspect that I am not really reading from ba and bb and all the values that are dma’ed into XDATA are not coming from the right source.
Please help. How can I setup SRCH and SRCL of DMA?
In your last post, it sounded like you were able to get the ADC working with the DMA by using these lines:
dmaConfig._2.SRCADDRH = XDATA_SFR_ADDRESS(ADC) >> 8;
dmaConfig._2.SRCADDRL = XDATA_SFR_ADDRESS(ADC);
I would expect that those lines of code set SRCADDRH to 0xDF and set SRCADDRL to 0xBA, meaning that the DMA will access the XDATA address 0xDFBA. If you look at Figure 14 in Section 10.2.1 of the CC2511 datasheet, you can verify that the XDATA address 0xDFBA would map to the SFR address 0xBA. Then if you look at Table 30 in section 10.2.3.3, you can see that SFR address 0xBA corresponds to the register ADCL. So the lines of code above should result in your DMA channel pointing to the ADCL register. Note that the 16-bit ADC register is defined differently from ADCL, but they should both have the same address, so they should both give the same result when you pass them to XDATA_SFR_ADDRESS().
After setting up the DMA to point to ADCL, you will need to somehow tell the DMA to read two bytes so that it can get the low byte of the ADC result from ADCL and then get the high byte of the ADC result from ADCH, which is positioned directly after ADCL in memory (SFR address 0xBB, XDATA address 0xDFBB). In your previous post, it looks like you achieved that by putting the DMA into word-size transfer mode.
No. The DMA source address register (SRCADDR) can only hold one address at a time. It cannot point to two different SFRs at the same time. The address is a 16-bit address, and the high bits are loaded into an 8-bit register called SRCADDRH, while the low bits are loaded into an 8-bit register called SRCADDRL. However, it is still just a single address that can point to a single location in the XDATA memory space. So you should point it to ADCL, and then tell the DMA to transfer two bytes.
Thanks David. I thought that it worked. But then I could not explain the variation in ADC values and I have spent quite a bit of time trying to figure out why there is a huge variation in ADC values. I thought the source may be incorrect since I was seeing such variations. When I set the source to a known voltage level (for example, there is no movement or grounding the input pin), I still get ADC data which in no way reflect input data. The ADC data values have to be between 0 and 2047 when VDD is 3.3 volt. Here is a sample. Each column represents data for x,y and z axis:
3971 1283 1795
3907 1155 1987
3651 1027 1795
3907 1027 1859
3843 1091 1795
3843 1027 1731
3907 1027 2051
3843 1155 1731
3779 963 1731
3907 1091 1667
3971 1027 1731
3843 1091 1731
3843 1027 1795
3715 1027 1731
3971 1027 1731
3907 1155 1603
3907 899 1859
3715 1155 1667
3907 963 1923
3843 1155 2051
3907 1027 1795
4035 1155 1539
2895 2051 387
2895 3331 1475
Once I configure DMA registers, I call dmaInit() provided by wixel library. I have added more delay (nop). DMA configuration has not changed. Is there an order that need to follow?
Following is the DC6 and DC7 configuration.
dmaConfig._2.DC6 = 0b10010101;
dmaConfig._2.DC7 = 0b00010010;
If what I am doing is ok, then maybe I need to focus on DSM function about the timing? I have tried different sampling rates there as well.
Is there any other trick to ensure what I am doing is right?
I do not see anything wrong with the two lines of code you posted.
It is possible that the ADC is giving you some negative results. In single-ended mode, the ADC is not supposed to give negative results, but I found that it does sometimes anyway. I recommend checking the most-significant bit of ADCH to see if the result is negative, as we do in the Wixel’s adcRead function. However, I am not sure if that would explain everything you are seeing. If you simplify your code to the simplest possible thing that should work, but doesn’t, and post it here along with descriptions of how you tested it, then I might be able to see what the problem is.
You might also consider accessing the ADC using the Wixel’s ADC library instead of using DMA.
I am posting the code as an attachment. Hopefully it is simple enough to follow. I will explain my logic, which I think is simple. Also, as per your suggestion, I am not printing the negative values. But the main problem of rapidly changing values still exists. If it takes 396 microseconds for A-D conversion for 3 channels, I have tried sampling once in 800 microseconds and once in 1200 microseconds. But the problem still persists.
Here is my logic.
I have setup the sequence of 0, 1 and 2. Analog to digital conversion is started by setting a bit in ADC registers. I dont use ADCCON3 as is done by adcRead().
Since I am using the sequence, once ADC is started, the data from port 0 is sampled for ADC first. When ADC values are available, (as specified by ADC DMA trigger)
DMA transfer takes place as per the configuration to a destination buffer.
I have setup DMA channels 2, 3 and 4 to read from ports 0, 1 and 2. The inputs are single ended inputs. ADC DMA triggers are used (21 to 23).
As per the manual, once ADC is started, port 0 is sampled first and after the conversion, data is DMAed to the destination. then the port 1 is sampled and so on.
I use repeat single, which refers to rearming the DMA channel after the transfer count is reached. I did use the other option, single, which refers to rearming
the DMA channel in DMA ISR. But that did not really work well. After about 4 or 5 transfers it stops working. But I am ok with repeat single as it is working fine.
Timer is used to start A to D conversion. It takes 132 micro seconds for each conversion. So the min time interval between two ADC start conversion is 396 micro seconds.
In the code, I have used much higher values and tried different values just to see when I get consistent readings. I was not able to get any consistent readings.
I disable DSM for a certain time period just so that I give some time to print the values.
In summary, I want to use the hardware features as much as possible in the hope that it conserves power. So here is the code…
hal.h (4.94 KB)
t1adcdma.c (11.5 KB)
I tried running your code here and it doesn’t seem to work. I fed various voltages to the P0_0 pin and the corresponding column in your program’s output didn’t seem to be correlated to the voltage.
Here is a sample of the output that I got:
121 1 1 1 1600 1472 1536
121 1 1 1 1536 1408 1408
121 1 1 1 1536 1408 1472
121 1 1 1 0 1472 1344
121 1 1 1 0 1600 1472
I noticed some bugs in lines of code where you are attempting to clear bits in an SFR. Here are two examples:
P0DIR = ~0x07;
DMAARM = (~(DMA_CHANNEL_2 | DMA_CHANNEL_3 | DMA_CHANNEL_4));
In both lines of code above, you are inadvertently setting several bits in the register to 1, and I think you wanted to leave those bits alone. You can fix bugs like this by changing
&=. We have discussed this before in this thread: Wixelcmd read provides address too high message.
However, I don’t think that those bugs would explain the problems we are seeing. If you want to fix your program, I suggest simplifying it further, since it is still over 400 lines long.
Is there are reason that you are using DMA instead of using the ADC library that comes with the Wixel SDK?
Thanks David. I will take care of those. Somwhere on TI website relying on &= operation was not suggested so I had resorted to reading the registers and then doing the desired operation. I will fix those.
I want to utilize the hardware features for few reasons:
It is my understanding that it will save power and much faster than reading ADC value and storing it. I could also use adcRead() in the timer ISR and sample data at a certain frequency. But that has to be either in a loop (for the 3 ports) or keep track of the port being sampled and move the data.
I can save code space by not including some of the libraries. I can then use the space for creating bigger buffers. I still have to add code to transmit and receive data.
Further, I possibly can dedicate some space for pseudo boot-code which could be used for different purposes. I call it pseudo boot-code because even though it is an application as far as wixel is concerned, for me it is boot-code which can take commands and do few things as I have to embed the device into an enclosure which cant be removed.
As you rightly pointed out, ADC and DMA does not seem to work at least the way I want it to. I can reduce the number of lines by removing the sampling for port 1 and port 2. Other than DMA part of the code, for the three ports, where there is repetition of code, I may not be able to simplify it further. I will do that as well and attach the code.
As an alternative to the current solution, some time ago I had tried the option of using a single DMA channel and sampling data from all three ports. I don’t think that worked either. Since I want to gather X, Y and Z data in separate buffers as it is easy to manipulate them or omit the axis that I don’t need. My thought is following the current design and if it works, will be very easy from different perspectives.
So this is what I will do.
- I will simplify the code further by eliminating ports 1 and 2.
- I will also try and use wixel adc library functions to read ADC values for all three ports in timer ISR.
Will post the code when I am done.
Are you planning on putting the Wixel into a sleep mode to save power?
The Wixel’s ADC library is pretty simple and takes very little space. Unused functions are removed by the compiler.
Writing code that runs in an interrupt and correctly transfers data to the main loop can be tricky. Instead of calling the ADC library routines inside a timer ISR, I would just call adcRead in your main loop. If your main loop runs often enough, the delay should be negligible. Depending on how often you need to read the inputs, you can schedule those readings using getMs() or you could set up a timer and just poll an interrupt flag or an overflow flag.
If you post more code, I recommend making the code simpler by not using the third-party header (hal.h) and also removing all the configurable parameters. Also, if you continue to use large buffers to hold ADC readings, you should make sure that you are printing the right part of the buffer. In your earlier code, it wasn’t clear to me if the portion of the buffer printed to USB was related to the portion of the buffer recently populated with DMA transfers, so you might have been sending old or uninitialized data.