Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
I am trying to read ADC(ADS8320) values using SPI communication. I am using IAR embedded workbench for ARM and SM470R1B1 controller. In the data sheet it says first 6 bits are dummy read and next 16 bits is the actval data. I am trying to read 24 bits and ignore first 6 bits and last 2 bits. I am getting wrong values when i try with the below code.
unsigned int readADC8320(void)
{
value = AD8320_16(0xFFFFFF); // read registe
return value;
}
unsigned int AD8320_16(unsigned int value)
{
unsigned int data;
AD8320_CS_Status(0);
SW_Delay(DELAY_10US);
while(GIODOUTA&X2);
data = spi2(value >> 16); //read high 8 bits
data = (data << 6)| spi2(value >> 8); //read next 8 bits but 6+8 =14
data = (data << 8)| spi2(value >> 2); //add last two bits only
SW_Delay(DELAY_10US);
AD8320_CS_Status(1);
return data;
}
unsigned char spi2(unsigned char data)
{
// Write byte to SPI2DAT1 register
SPI2DAT1 = data;
while(!(SPI2CTRL3 & 0x01)){} // Wait for RxFlag to get set
return (SPI2BUF & 0x000000FF); // Read SPIBUF
}
Can anyone suggest me where i am doing wrong. I am quite poor with shift operations.
Your code looks wonky. For example, what purpose does value have?
The datasheet you linked to indicates that the 22 clock cycles suffice (and that 24 clock cycles are okay), no extra delays are needed, assuming the SPI clock is within limits (at most 2.4 MHz, I believe). So, I'd expect your code to look more like
void AD8320_setup(void)
{
/* TODO:
* - Set SPI2 clock frequency, if programmable (max. 2.4 MHz)
* - Set SPI2 transfer size, if programmable (8 bits per byte)
* - Set SPI2 to latch data on the falling edge of clock pulse
* - Set SPI2 to pull clock high when inactive
* - Set AD8320 chip select pin as an output
* - Set AD8320 chip select pin high (it is active low)
* - Ensure AD8320 is powered
*/
}
uint16_t AD8320_16(void)
{
uint16_t result;
/* TODO: Set AD8320 chip select pin 0/LOW (to select it)
*/
/* TODO: Clear SPI2 FIFO if it has one,
* so that we get actual wire data,
* not old cached data.
*/
result = ((uint16_t)(spi2_read() & 3U)) << 14;
result |= ((uint16_t)spi2_read()) << 6;
result |= ((uint16_t)spi2_read()) >> 2;
/* TODO: Set AD8320 chip select pin 1/HIGH (to deselect it)
*/
return result;
}
The prototype for spi2_read is typically unsigned char spi2_read(void);, but it might be of any integer type that can represent all eight bit values correctly (0 to 255, inclusive). Above, I cast the result -- after masking out any irrelevant bits, if any, with a binary AND operation -- to uint16_t before shifting it into the correct position in the result, so that regardless of the spi2_read() return type, we can use it to construct our 16-bit value.
The first spi2_read() reads the first 8 bits. I am assuming your device SPI bus sends and receives the most significant bit first; this means that the 6 dummy bits are most significant bits, and the 2 least significant bits are the most significant bits of the result. That's why we only keep the two least significant bits of the first byte, and transfer it 14 places left -- that way we get them in the most significant position.
The second spi2_read() reads the next eight bits. These are bits 13..6 of the data; that's why we shift this byte left by 6 places, and OR with the existing data.
The last spi2_read() reads the next eight bits. The last two bits are to be discarded, because only the first (most significant) 6 bits contain the rest of the data we're interested in. So, we shift this down by 2 places, and OR with the existing data.
Preparing spi2 involves stuff like word size (8 bits), data rate (if programmable), and so on.
The chip select for the AD8320 is just a generic output pin, it's just active low. That's why it's described as ^CS/SHDN in the datasheet: chip selected when low, shutdown when high.
The chip is a micropower one, so it requires less than 2mA, so you could conceivably power it from an output pin on many microcontrollers. If you were to do so, you'd also have to remember to make that pin an output and 1/high in _setup() function.
Related
I'm a begginer in programming AVR microcontroler and I get a lot of headacke sometimes from reading the datasheets.
I'm trying to make a communication between my AVR and PC just to send some caracters and receive it on my computer.
There are two lines I don't understand from the whole program and that is:
void USART_init(void)
{
UBRRH = (uint8_t)(BAUD_PRESCALLER>>8); <---- this one!
UBRRL = (uint8_t)(BAUD_PRESCALLER); <--- and this one
UCSRB = (1<<RXEN)|(1<<TXEN);
UCSRC = (1<<UCSZ0)|(1<<UCSZ1)|(1<<URSEL);
}
Datasheet
Why do I have to shift BAUD_PRESCALLER with 8? If BAUD_PRESCALLER is a number and shifting that number with 8 doesn't mean the result will be zero?(Because we are shifting it too many times)
From the datasheet I understand that UBRRH contains the four most significant bits and the UBRRL contains the eight least signicant bits of the USART baut rate.(Note:UBBR is a 12-bit register)
So how actually we put all the required numbers in the UBBR register?
You have to shift it right 8 bits because the result of BAUD_PRESCALLER is larger than 8 bits. Shifting it right 8 bits gives you the most significant byte of a 16-bit value.
For example, if the value of BAUD_PRESCALAR is 0x123 - then 0x1 would be assigned to UBRRH and 0x23 would be assigned to UBRRL.
If the library was smart it could also perform sanity checking on the BAUD_PRESCALAR to make sure it fits in 16bits. If it can't, that means you cannot achieve the baud rate you want given the clock you are using. If you're UBRRx is truly 12bits, the sanity check would look something like this:
#if BAUD_PRESCALAR > 0xFFF
#error Invalid prescalar
#endif
Recently I am implementing the internal peripherals of STM32F0 family mcu in a c library.
While I was looking at how ST managed to do the same with their HAL library I stepped into this piece of code...
1 /*--------------------- EXTI Mode Configuration ------------------------*/
2 /* Configure the External Interrupt or event for the current IO */
3 if((GPIO_Init->Mode & EXTI_MODE) == EXTI_MODE)
4 {
5 /* Enable SYSCFG Clock */
6 __HAL_RCC_SYSCFG_CLK_ENABLE();
7 temp = SYSCFG->EXTICR[position >> 2];
8 CLEAR_BIT(temp, (0x0FU) << (4U * (position & 0x03U)));
9 SET_BIT(temp, (GPIO_GET_INDEX(GPIOx)) << (4U * (position & 0x03U)));
10 SYSCFG->EXTICR[position >> 2] = temp;
11 /* Clear EXTI line configuration */
12 temp = EXTI->IMR;
13 CLEAR_BIT(temp, (uint32_t)iocurrent);
14 if((GPIO_Init->Mode & GPIO_MODE_IT) == GPIO_MODE_IT)
15 {
16 SET_BIT(temp, iocurrent);
17 }
18 EXTI->IMR = temp;
What I am trying to understand is why the pair commands in lines 7-10 and 12-18 are used. Why are there? Why store something then write it and then retrieve it from memory discarted wroten content?
Is it about multitasking and race conditions? I do not fully understand!
This is used for speed optimization and to avoid unwanted states in memory-mapped registers.
Imagine some example memory-mapped variable EXT->ABC. Imagine such exaggerated example situation:
We have some hardware memory address that is accessible via memory-mapped EXT->ABC variable
We know that bit 1 is set and bit 2 is cleared in EXT->ABC
We want to clear bit 1 and set bit 2 and leave all other bits unchanged in EXT->ABC
Hardware doesn't allow bit 1 and bit 2 in EXT->ABC register to be both cleared or both set. Such states are forbidden and may result in undefined behavior (that means in ANY behavior, for example software reset).
Reading and writing to EXT->ABC register is very very slow.
If we operate on EXT->ABC variable directly:
CLEAR_BIT(EXT->ABC, 1); // expands to: EXT->ABC = EXT->ABC & ~1;
SET_BIT(EXT->ABC, 2); // expands to: EXT->ABC = EXT->ABC | 2;
this will result in a forbidden state in between the two lines, as bit 1 and bit 2 will be set. Also, in these 2 lines, EXTI->IMR is accessed 4 times.
To resolve this, we need to store the value in EXTI->IMR register somewhere, modify needed bits, and then write the modified value to EXTI->IMR register. So we will:
uint32_t temp;
temp = EXT->ABC; // store
CLEAR_BIT(temp, 1);
SET_BIT(temp, 2);
EXT->ABC = temp; // write
We read once from a memory-mapped variable, modify&play with the value, do whatever we want. After that, we write once to memory-mapped variable. That way EXT->ABC register is touched only twice and code is written safely, so that no undefined/forbidden/unwanted states occur.
For example: as for lines 7-10: to access SYSCFG->EXTICR[position >> 2U] you need multiple cpu operations (calculate position >> 2U, multiple by sizeof(SYSCFG->EXTICR), add to SYSCFG->EXTICR address, dereference). Maybe it was developer intention to execute these lines faster. Or you need to clear bits 0x0F<<(4*(position&0x03) when writting to (GPIO_GET_INDEX(GPIOx))<<(4*position&0x03).
It is to make the operations "atomic". All changes to the registers in one write operation.
I can't see anything strange:
Lines 7-10 and 12-18 copy what seems to be the contents of a register to a local variable, do some operation on it and then store the result back to the original register.
The reason why this may happen are multiple, but my guess is that the author of the code did not want the MCU to be in an intermediate state while tinkering with the registers.
For example, what happens to the core after line 8 but before line 9?
I am working on a library for controlling the M95128-W EEPROM from an STM32 device. I have the library writing and reading back data however the first byte of each page it not as expected and seems to be fixed at 0x04.
For example I write 128 bytes across two pages starting at 0x00 address with value 0x80. When read back I get:
byte[0] = 0x04;
byte[1] = 0x80;
byte[2] = 0x80;
byte[3] = 0x80;
.......
byte[64] = 0x04;
byte[65] = 0x80;
byte[66] = 0x80;
byte[67] = 0x80;
I have debugged the SPI with a logic analyzer and confirmed the correct bytes are being sent. When using the logic analyzer on the read command the mysterios 0x04 is transmitted from the EEPROM.
Here is my code:
void FLA::write(const void* data, unsigned int dataLength, uint16_t address)
{
int pagePos = 0;
int pageCount = (dataLength + 64 - 1) / 64;
int bytePos = 0;
int startAddress = address;
while (pagePos < pageCount)
{
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_2, GPIO_PIN_SET); // WP High
chipSelect();
_spi->transfer(INSTRUCTION_WREN);
chipUnselect();
uint8_t status = readRegister(INSTRUCTION_RDSR);
chipSelect();
_spi->transfer(INSTRUCTION_WRITE);
uint8_t xlow = address & 0xff;
uint8_t xhigh = (address >> 8);
_spi->transfer(xhigh); // part 1 address MSB
_spi->transfer(xlow); // part 2 address LSB
for (unsigned int i = 0; i < 64 && bytePos < dataLength; i++ )
{
uint8_t byte = ((uint8_t*)data)[bytePos];
_spi->transfer(byte);
printConsole("Wrote byte to ");
printConsoleInt(startAddress + bytePos);
printConsole("with value ");
printConsoleInt(byte);
printConsole("\n");
bytePos ++;
}
_spi->transfer(INSTRUCTION_WRDI);
chipUnselect();
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_2, GPIO_PIN_RESET); //WP LOW
bool writeComplete = false;
while (writeComplete == false)
{
uint8_t status = readRegister(INSTRUCTION_RDSR);
if(status&1<<0)
{
printConsole("Waiting for write to complete....\n");
}
else
{
writeComplete = true;
printConsole("Write complete to page ");
printConsoleInt(pagePos);
printConsole("# address ");
printConsoleInt(bytePos);
printConsole("\n");
}
}
pagePos++;
address = address + 64;
}
printConsole("Finished writing all pages total bytes ");
printConsoleInt(bytePos);
printConsole("\n");
}
void FLA::read(char* returndata, unsigned int dataLength, uint16_t address)
{
chipSelect();
_spi->transfer(INSTRUCTION_READ);
uint8_t xlow = address & 0xff;
uint8_t xhigh = (address >> 8);
_spi->transfer(xhigh); // part 1 address
_spi->transfer(xlow); // part 2 address
for (unsigned int i = 0; i < dataLength; i++)
returndata[i] = _spi->transfer(0x00);
chipUnselect();
}
Any suggestion or help appreciated.
UPDATES:
I have tried writing sequentially 255 bytes increasing data to check for rollover. The results are as follows:
byte[0] = 4; // Incorrect Mystery Byte
byte[1] = 1;
byte[2] = 2;
byte[3] = 3;
.......
byte[63] = 63;
byte[64] = 4; // Incorrect Mystery Byte
byte[65] = 65;
byte[66] = 66;
.......
byte[127] = 127;
byte[128] = 4; // Incorrect Mystery Byte
byte[129} = 129;
Pattern continues. I have also tried writing just 8 bytes from address 0x00 and the same problem persists so I think we can rule out rollover.
I have tried removing the debug printConsole and it has had no effect.
Here is a SPI logic trace of the write command:
And a close up of the first byte that is not working correctly:
Code can be viewed on gitlab here:
https://gitlab.com/DanielBeyzade/stm32f107vc-home-control-master/blob/master/Src/flash.cpp
Init code of SPI can be seen here in MX_SPI_Init()
https://gitlab.com/DanielBeyzade/stm32f107vc-home-control-master/blob/master/Src/main.cpp
I have another device on the SPI bus (RFM69HW RF Module) which works as expected sending and receiving data.
The explanation was actually already given by Craig Estey in his answer. You do have a rollover. You write full page and then - without cycling the CS pin - you send INSTRUCTION_WRDI command. Guess what's the binary code of this command? If you guessed that it's 4, then you're absolutely right.
Check your code here:
chipSelect();
_spi->transfer(INSTRUCTION_WRITE);
uint8_t xlow = address & 0xff;
uint8_t xhigh = (address >> 8);
_spi->transfer(xhigh); // part 1 address MSB
_spi->transfer(xlow); // part 2 address LSB
for (unsigned int i = 0; i < 64 && bytePos < dataLength; i++ )
{
uint8_t byte = ((uint8_t*)data)[bytePos];
_spi->transfer(byte);
// ...
bytePos ++;
}
_spi->transfer(INSTRUCTION_WRDI); // <-------------- ROLLOEVER!
chipUnselect();
With these devices, each command MUST start with cycling CS. After CS goes low, the first byte is interpreted as command. All remaining bytes - until CS is cycled again - are interpreted as data. So you cannot send multiple commands in a single "block" with CS being constantly pulled low.
Another thing is that you don't need WRDI command at all - after the write instruction is terminated (by CS going high), the WEL bit is automatically reset. See page 18 of the datasheet:
The Write Enable Latch (WEL) bit, in fact, becomes reset by any of the
following events:
• Power-up
• WRDI instruction execution
• WRSR instruction completion
• WRITE instruction completion.
Caveat: I don't have a definitive solution, just some observations and suggestions [that would be too large for a comment].
From 6.6: Each time a new data byte is shifted in, the least significant bits of the internal address counter are incremented. If more bytes are sent than will fit up to the end of the page, a condition known as “roll-over” occurs. In case of roll-over, the bytes exceeding the page size are overwritten from location 0 of the same page.
So, in your write loop code, you do: for (i = 0; i < 64; i++). This is incorrect in the general case if the LSB of address (xlow) is non-zero. You'd need to do something like: for (i = xlow % 64; i < 64; i++)
In other words, you might be getting the page boundary rollover. But, you mentioned that you're using address 0x0000, so it should work, even with the code as it exists.
I might remove the print statements from the loop as they could have an effect on the serialization timing.
I might try this with an incrementing data pattern: (e.g.) 0x01,0x02,0x03,... That way, you could see which byte is rolling over [if any].
Also, try writing a single page from address zero, and write less than the full page size (i.e. less that 64 bytes) to guarantee that you're not getting rollover.
Also, from figure 13 [the timing diagram for WRITE], it looks like once you assert chip select, the ROM wants a continuous bit stream clocked precisely, so you may have a race condition where you're not providing the data at precisely the clock edge(s) needed. You may want to use the logic analyzer to verify that the data appears exactly in sync with clock edge as required (i.e. at clock rising edge)
As you've probably already noticed, offset 0 and offset 64 are getting the 0x04. So, this adds to the notion of rollover.
Or, it could be that the first data byte of each page is being written "late" and the 0x04 is a result of that.
I don't know if your output port has a SILO so you can send data as in a traditional serial I/O port or do you have to maintain precise bit-for-bit timing (which I presume the _spi->transfer would do)
Another thing to try is to write a shorter pattern (e.g. 10 bytes) starting at a non-zero address (e.g. xhigh = 0; xlow = 4) and the incrementing pattern and see how things change.
UPDATE:
From your update, it appears to be the first byte of each page [obviously].
From the exploded view of the timing, I notice SCLK is not strictly uniform. The pulse width is slightly erratic. Since the write data is sampled on the clock rising edge, this shouldn't matter. But, I wonder where this comes from. That is, is SCLK asserted/deasserted by the software (i.e. transfer) and SCLK is connected to another GPIO pin? I'd be interested in seeing the source for the transfer function [or a disassembly].
I've just looked up SPI here: https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus and it answers my own question.
From that, here is a sample transfer function:
/*
* Simultaneously transmit and receive a byte on the SPI.
*
* Polarity and phase are assumed to be both 0, i.e.:
* - input data is captured on rising edge of SCLK.
* - output data is propagated on falling edge of SCLK.
*
* Returns the received byte.
*/
uint8_t SPI_transfer_byte(uint8_t byte_out)
{
uint8_t byte_in = 0;
uint8_t bit;
for (bit = 0x80; bit; bit >>= 1) {
/* Shift-out a bit to the MOSI line */
write_MOSI((byte_out & bit) ? HIGH : LOW);
/* Delay for at least the peer's setup time */
delay(SPI_SCLK_LOW_TIME);
/* Pull the clock line high */
write_SCLK(HIGH);
/* Shift-in a bit from the MISO line */
if (read_MISO() == HIGH)
byte_in |= bit;
/* Delay for at least the peer's hold time */
delay(SPI_SCLK_HIGH_TIME);
/* Pull the clock line low */
write_SCLK(LOW);
}
return byte_in;
}
So, the delay times need be at least the ones the ROM needs. Hopefully, you can verify that is the case.
But, I also notice that on the problem byte, the first bit of the data appears to lag its rising clock edge. That is, I would want the data line to be stabilized before clock rising edge.
But, that assumes CPOL=0,CPHA=1. Your ROM can be programmed for that mode or CPOL=0,CPHA=0, which is the mode used by the sample code above.
This is what I see from the timing diagram. It implies that the transfer function does CPOL=0,CPHA=0:
SCLK
__
| |
___| |___
DATA
___
/ \
/ \
This is what I originally expected (CPOL=0,CPHA=1) based on something earlier in the ROM document:
SCLK
__
| |
___| |___
DATA
___
/ \
/ \
The ROM can be configured to use either CPOL=0,CPHA=0 or CPOL=1,CPHA=1. So, you may need to configure these values to match the transfer function (or vice-versa) And, verify that the transfer function's delay times are adequate for your ROM. The SDK may do all this for you, but, since you're having trouble, double checking this may be worthwhile (e.g. See Table 18 et. al. in the ROM document).
However, since the ROM seems to respond well for most byte locations, the timing may already be adequate.
One thing you might also try. Since it's the first byte that is the problem, and here I mean first byte after the LSB address byte, the memory might need some additional [and undocumented] setup time.
So, after the transfer(xlow), add a small spin loop after that before entering the data transfer loop, to give the ROM time to set up for the write burst [or read burst].
This could be confirmed by starting xlow at a non-zero value (e.g. 3) and shortening the transfer. If the problem byte tracks xlow, that's one way to verify that the setup time may be required. You'd need to use a different data value for each test to be sure you're not just reading back a stale value from a prior test.
I am trying to read the data from FXLS8471Q 3-Axis, Linear Accelerometer using SPI. I am using bit banging method to read the data from Accelerometer. I am using LPC 2184 ARM processor. I used the following code.
unsigned char spiReadReg (const unsigned char regAddr)
{
unsigned char SPICount;
unsigned char SPIData;
SPI_CS = 1;
SPI_CK = 0;
SPIData = regAddr;
SPI_CS = 0;
for (SPICount = 0; SPICount < 8; SPICount++)
{
if (SPIData & 0x80)
SPI_MOSI = 1;
else
SPI_MOSI = 0;
SPI_CK = 1;
SPI_CK = 0;
SPIData <<= 1;
}
SPI_MOSI = 0;
SPIData = 0;
for (SPICount = 0; SPICount < 8; SPICount++)
{
SPIData <<=1;
SPI_CK = 1;
SPIData += SPI_MISO;
SPI_CK = 0;
SPIData &=(0xFE);
}
SPI_CS = 1;
return ((unsigned char)SPIData);
}
But instead of getting valid value 0x6A , I am getting garbage value.
Please help me out to solve this problem;
SPIData &=(0xFE); as pointed out in another answer, is definitely wrong as it erases the bit you just received.
However, there are other major issues with your code.
An SPI slave device sends you data by setting the value of MISO on a rising or falling clock, depending on the type of device. However, you didn't wait in your code for the value to appear on MISO.
You control the communication by setting the clock to 1 and 0. The datasheet of the accelerometer says on page 19, that
Data is sampled during the rising edge of SCLK and set up during the falling edge of SCLK.
This means that in order to read from it, your processor needs to change the clock from one to zero, thereby signaling the accelerometer to send the next bit to the MISO. This means you did the reverse, you in your code read on a rising edge while you should be reading on the falling edge. After setting the clock to zero, you have to wait a little while until the value appears on MISO, and only then should you read it and add it to your SPIData variable. Table 9, SPI timing indicates how much you have to wait: at least 500 nanoseconds. That's not much, but if your CPU runs faster than 2 MHz then if you don't use a delay, you will try to read the MISO before the accelerometer had time to properly set it.
You also forgot the slave Select, which is actually required to indicate the beginning and the end of a datagram.
Check the Figure 7. SPI Timing Diagram in the datasheet, it indicates what you are required to do and in what order, to communicate with the device using SPI.
I also suggest reading about how the rotating registers of the SPI work, because it seems from its datasheet, that the accelerometer needs to receive a well specified number of bits before useful data appears on its output. Don't forget, as you send a single bit to the device, it also has to send a bit back to you, so if it didn't decode a command yet, it can only send gibberish. Your code, as the master, can only "push" bits in, and collect the bits which "pop out" on the other side. This means you have to send a command, and then send further bits until all the answer is pushed out to you bit by bit.
If you get stuck, I think you will have much more luck getting help on https://electronics.stackexchange.com/, but instead of just putting this same code there (which seems to be blindly copied from www.maximintegrated.com and has absolutely nothing to do with the problem you try to solve), I strongly recommend trying to understand the "Figure 7. SPI Timing Diagram" I was suggesting before, and alter your code accordingly.
Without understanding how the device you try to communicate with works, you will never succeed if you just blindly copy the code from a completely different project just because it says "spi" in the title.
SPIData &=(0xFE);
The above line is causing the problem. Here the LSB is reset to 0 (which contained the data bit just taken from MISO) -- basically you are destroying the bit you just read. Omitting the line should correct the problem.
be sure to compile your function with NO optimization
as that will corrupt the bitbang operation.
this is the code from the maxum site for the spiReadReg function.
(which looks like were you got your code.
However, this is just a guide for the 'general' sequence of operations for communicating with the maxim 1481 part.
the accel. part needs several setup commands and reads completely differently
Suggest reading the app notes and white papers available at freescale.com for the specific part number.
Those app notes/white papers will indicate the sequence of commands needed for setting up a specific mode of operation and how to request/interpret the resulting data.
There are a number of device specifics that your code has not taken into account.
Per the spec sheet the first bit transmitted is a read/write indicator, followed by 8 bits of register address, followed by 7 trash bits (suggest sending all 0's for the trash bits.) followed by the data bits.
Depending on the setup commands, those data bits could be 8 bits or 14 bits or multiple registers of 8 or 14 bits per register.
I'm writing a program for an ATMega328P that will take readings from several ADC channels, combine them into a single signal and output this signal through PWM.
I've successfully backed off my ADC polling to 50Hz per channel using Single Conversion mode. I'm using Timer/Counter2 for PWM generation, and Timer/Counter1 for doing the calculations I need to do to set compare values for Timer/Counter2. This is the ISR for Timer/Counter1:
// Interrupt service routine called to generate PWM compare values
ISR(TIMER1_COMPA_vect)
{
// Grab most recent ADC reading for ADC0
uint32_t sensor_value_0 = adc_readings[0];
// Get current value for base waveform from wavetable stored in sinewave_data
uint32_t sample_value_0 = pgm_read_byte(&sinewave_data[sample_0]);
// Multiply these two values together
// In other words, use the ADC reading to modulate the amplitude of base wave
uint32_t sine_0 = (sample_value_0 * sensor_value_0) >> 10;
// Do the same thing for ADC2
uint32_t sensor_value_1 = adc_readings[1];
uint32_t sample_value_1 = pgm_read_byte(&sinewave_data[sample_1]);
uint32_t sine_1 = (sample_value_1 * sensor_value_1) >> 10;
// Add channels together, divide by two, set compare register for PWM
OCR2A = (sine_0 + sine_1) >> 1;
// Move successive ADC base waves through wavetable at integral increments
// i.e., ADC0 is carried by a 200Hz sine wave, ADC1 at 300Hz, etc.
sample_0 += 2;
sample_1 += 3;
// Wrap back to front of wavetable, if necessary
if (sample_0 >= sinewave_length) {
sample_0 = 0;
}
if (sample_1 >= sinewave_length) {
sample_1 = 0;
}
} // END - Interrupt service routine called to generate PWM compare values
My problem is that that I get no PWM output. If I set either sensor_value_0 or sensor_value_1 to 1024 and leave the other sensor_value_ set to read from the ADC, I do get one full-amplitude component wave, and an amplitude-modulated component wave. If however, I choose a different value for the hardcoded, mock amplitude, I am not so lucky (1023, for instance). Any other values give me no PWM output. If I set both sensor_value_s to look at the same ADC channel, I would expect two component waves whose amplitudes are modulated identically. Instead, I get no PWM output. What is most confusing of all to me is that if I choose a value for the hardcoded amplitude that is an exact power of two, all is well.
The whole power-of-two part makes this seem to me to be a bit-twiddling issue that I'm not seeing. Can you see what I must have clearly missed? I'd appreciate any tips at all!
(I've posted my entire source here to keep things as neat as possible on SO.)
Your issue may be caused by the architecture of the AVR that you're developing on. The ATMega328p has 8 bit registers, similar to most other AVR chips. This means that the 32b values that you're working with must be stored in memory by the compiler and broken up into four separate registers every time you perform arithmetic on them. In fact, there are no arithmetic instructions that perform on more than one register at once, so I'm really not sure what the compiler is doing!
I'd be interested to know what the disassembly of your code is, but my guess is that gcc is using the MUL instruction to execute the sample_value_0 * sensor_value_0 code. This instruction operates on two 8b values and produces a 16b value, so I wouldn't be surprised if the reason you're seeing an odd dependence on multiples of two produce results.
I'd say try reworking this block of code by changing the data types of the variables. Use uint8_t for sensor_value_* and sample_value_*, and uint16_t for sine_*. Then, to make sure everything fits in the 8b OCR2A register, change the assignment to something like:
OCR2A = (sine_0 + sine_1) & 0xFF;
#Devrin, I appreciate the response, but just manipulating types didn't do it for me. Here's what I ended up doing:
uint8_t sine_0 = (pgm_read_byte(&sinewave_data[sample_0]) >> 5) * (adc_readings[1] >> 5);
uint8_t sine_1 = (pgm_read_byte(&sinewave_data[sample_1]) >> 5) * (adc_readings[2] >> 5);
OCR2A = (sine_0 >> 1) + (sine_1 >> 1);
Essentially, I've done all my shifting immediately, instead of waiting until the last minute. Unfortunately, I lose a lot of precision, but at least the code works as expected. Now, I wil begin cranking things back up to find the initial cause of my issues.