Showing posts with label DMA. Show all posts
Showing posts with label DMA. Show all posts

Thursday, October 25, 2012

REM detector + display updates (or, scrolling displays are almost as cool as fezzes)

As I remarked in a recent post, I've nailed down the cause of my pesky REM detector noise issue.  Additionally, I started sprucing up the display subsystem, using a DMA channel to significantly reduce the processor load needed.  I've taken another couple of steps forward, in that I've implemented a fix to the REM noise issue, and I've refined the display subsystem so that it can easily produce text and primitive graphics; these refinements are shown immediately below.

The bottom 3/4 of the display is the REM differential signal level, scrolling from left to right.  The numbers on top are the 32-bit integer holding the device's seconds counter (left) and the IR illuimnator level (right).

Not very interested waveform-wise, but a clearer picture.


The video shows the scrolling display in action; since the sensor is pointed at the couch arm, there's not much going on, except when I rotate it.

Implementation details, vaguely

On the display side, this is really only a slight complication of what I outlined previously; there's a local buffer that I draw on, and periodically I set the DMA channel to transmit the contents of that buffer to the display via the serial transmitter peripheral.  There's a little bit of trickery translating the signed ADC inputs into the bytes that get painted into the display buffer, but it's overall quite straightforward.  A zipped archive of the project files is here.

On the REM detector side, I'd determined that the reason I was getting 'bias noise' every four samples was that the PWM switching transients were making it into the ADC signal, and because the PWM and REM counters were not integer multiples of each other, they would only 'line up' every four samples.  To rectify this scenario, I modified the code to make sure that the sample timer is always set up to be an integer multiple of the PWM timer period.  I also set up the clocking of the timers to go through the XMEGA's event system, so that I could keep the timers in sync.  A plot of the signal below shows no abnormal patterns in the noise, so I think this has taken care of the problem.



Moving Forward again

So, my next step has to be nailing down the REM detector.  Specifically, I'm going to slightly modify the hardware so that I can quarter the PWM duty cycle.  This way, I can maximize the settling time between the PWM switching time and the ADC sample time.  After this is done, I'm going to assess whether this has had the effect of decreasing the noise; either way, I'm going to collect some real-life sleep data, and get cracking on whether I can assemble a filter/classifier that can detect REM.

Of course, this completely avoids the issue of the busted charger circuit; unfortunately, I still haven't secured access to a hot-air rework station...

Wednesday, October 24, 2012

Using DMA to automatically transfer display data (or, it's RTFM, not 'Haphazardly Cast Your Eyes Left to Right Over The Manual While Thinking of Something Else')

As an aside from the process of perfecting the REM detector, I decided to start to clean up the display-painting parts of the firmware.

It takes half a kilobyte of data to completely fill the display (128-by-32 off-or-on pixels) and the data is sent through one of the chip's serial peripherals.  Since there is so much data to transmit per display update, it takes a LOT of processor cycles to do so; it takes even more cycles to do so by waiting and polling the serial peripheral instead of setting up an interrupt (as I did to debug the display).

Since the process of getting that data out to the display could be fairly straightforward, it seemed like the perfect excuse to take advantage of the AVR XMEGA microcontroller's onboard DMA feature.  And since I made a couple of errors along the way (and couldn't find a straightforward explanation of my problem online), it seemed appropriate to describe the situation here.

What is DMA?

Briefly, DMA (direct memory access) is a way of taking the load off of the processor by 'automating' data transfers.  Specifically, the DMA controller transfers a block of data from one location in memory to another while the processor executes other tasks.  Without a DMA controller, any large data transfer would take up processor time as the processor accessed and copied each byte of data individually.

There are many situations where you would want to transfer a lot of data from one location to another, or a small amount of data from (or to) one location a great many times.  In my scenario, I have a buffer in local memory that represents what the display will look like; the buffer is local so that manipulating it (compositing the image, blanking regions, adding text) is easier.  However, that buffer data needs to be periodically sent (serially) to the display hardware.  Ideally, the DMA will automate the process of taking each byte of the buffer in turn and sending it to the serial peripheral when it's ready to accept the bytes.

Implementation on the XMEGA

The XMEGA A microcontroller that I'm using has four independent DMA channels.  Each of them has a lot of configuration, including the ability to specify the source and destination data addresses and the ability to set what triggers the data transfers.

My problem was that I didn't read the manual closely enough; I thought that setting the channel trigger to the serial peripheral meant that every time the send register was empty (the trigger source), that a single byte would be sent.  In the default mode, however, the trigger causes the DMA to transfer an entire block of data as quickly as possible; since the serial peripheral sends out bytes a LOT slower than the rate that the DMA controller pushes bytes, this meant that each transfer only resulted in a few randomly-selected bytes of the buffer actually being sent to the display.  This caused me some consternation until I re-read the manual and realized my error; this fast operation is useful when copying data into SRAM or other fast destinations, but completely inappropriate for slow, single-byte destinations like the USART peripheral.

For slower destinations which will need to signal the transmission of each byte (or each burst of 2, 4, or 8 bytes) one at a time, the 'Single-Shot Data transfer' mode is used.  This mode completes a single burst, instead of a whole block, with each DMA channel trigger activation.

Since I want to transfer the complete contents of the display buffer to a single address on the serial peripheral, I need the destination address to be fixed and the source address to increment during the transmission, and reset at the end for the next update.

The actual C code I used to initialize the DMA controller and channel on the ATXMEGA128A4U is shown below; USARTC1 is the serial peripheral I'm using as my transmitter (it's been set up in master SPI mode for my display module, and then used to initialize the display module) and debugBuffer is the 512-byte-long stretch of internal memory that I'm using as my display buffer.

//set up a DMA channel
//enable the DMA controller
DMA_CTRL = DMA_ENABLE_bm;
//set the burst length to 1 byte
DMA_CH0_CTRLA = ( DMA_CH_SINGLE_bm | DMA_CH_BURSTLEN_1BYTE_gc );
//set the following: source address incremented, reload after each block; destination address fixed (reload after each block)
DMA_CH0_ADDRCTRL = ( DMA_CH_SRCRELOAD_TRANSACTION_gc | DMA_CH_SRCDIR_INC_gc | DMA_CH_DESTRELOAD_TRANSACTION_gc | DMA_CH_DESTDIR_FIXED_gc );
//now set the DMA trigger source to the USART data register being empty
DMA_CH0_TRIGSRC = DMA_CH_TRIGSRC_USARTC1_DRE_gc;
//load the block transfer count register with the number of bytes in our blocks (that is, 128*4 = 512)
DMA_CH0_TRFCNT = 512;
//now put in the initial source address; should be the memory address of the first byte of the display buffer
DMA_CH0_SRCADDR0 = ( (uint16_t) debugBuffer >> 0 ) & 0xFF;
DMA_CH0_SRCADDR1 = ( (uint16_t) debugBuffer >> 8 ) & 0xFF;
DMA_CH0_SRCADDR2 = 0x00;
//now specify the destination address; the transmit register of the USARTC1
DMA_CH0_DESTADDR0 = (( (uint16_t) &USARTC1_DATA ) >> 0) & 0xFF;
DMA_CH0_DESTADDR1 = (( (uint16_t) &USARTC1_DATA ) >> 8) & 0xFF;
DMA_CH0_DESTADDR2 = 0x00;

Once the DMA channel is set up (and assuming the USART and display have been initialized), I can update the display with the current contents of the buffer by simply enabling the DMA channel:

DMA_CH0_CTRLA |= 0b10000000;