I'm using an ATmega328. I am currently doing several measurements using the 10-bit ADC. I would like to store the values it converts in variables in order to be able to operate with them. For example:
int a;
(...)
ADMUX = 0b01000011; //Vref = 5V, ADC3
ADCSRA |= (1<<ADSC); //Starts conversion
while(!(ADCSRA & (1<<ADIF))); //Wait until it finishes
ADCSRA |= (1<<ADIF); //Clear flag
Suppose that the ADC stored the value 576 in ADCH:ADCL. Is it possible to achieve, somehow, the variable a to take that same value? (i.e. a=576;).
The full 16-bit result register should be accessible as such:
a = ADC;
But if you want to read both parts manually, then
a = ADCL;
a |= ADCH << 8;
That has to be done in two separate statements to force ADCH to be read last. The I/O modules have a temporary register to hold the high byte, preventing the module itself from corrupting the read value if it changes the value of the register. (i.e. if the ADC finishes another conversion and stores the new value.)
If you have interrupts that access the ADC (or need to use the value at a), you'll need to disable them for the duration of the access (that also goes for a = ADC, since it also compiles into multiple 8-bit reads).
The answer above is close, still comes up a little short based on the recommendations from the chip manufacturer. To be safe you should follow the instructions in the authoritative reference linked below.
Simply turn off interrupts just before and restore them after:
unsigned int a; // 16-bit word
// other code
cli();
a = ADCL;
a |= ADCH << 8;
sei();
Every example in this reference from the chip manufacturer follows this pattern for atomic 16-bit reads and writes. Reference: AVR Application Note 072.
Related
I am wondering if it is possible to speed up the ISR without changing the prescaler.
I have a timer with 2 compare registers A and B.
COMPA is used for a PWM output from around 22% up to 100%. This has a fixed frequency and I am not allowed to change it at least not much.
Now I would like to use the COMPB with around 4 times the speed but with a fixed duty cycle of 50%.
If I set the OCIE0B bit in TIMSK0 for the attiny13 can I do the following to speed things up?
Or am I misunderstanding something here?
ISR(TIM0_COMPB_vect){
switch (timing){
case 0:
OCR0B = 63;
PORTB ^= (1 << PB3);
timing = 1;
break;
case 1:
OCR0B = 127;
PORTB ^= (1 << PB3);
timing = 2;
break;
case 2:
OCR0B = 191;
PORTB ^= (1 << PB3);
timing = 3;
break;
case 3:
OCR0B = 255;
PORTB ^= (1 << PB3);
timing = 0;
break;
}
}
Any help appreciated.
Thanx :D
You can do this very efficiently by creatively using the Normal Mode.
The trick is to set the prescaller to get a clock period that is double what you want the variable-duty PWM signal to run at. So if, for example, you want that to PWM at 1Mhz, set the prescaller to 2Mhz.
Assume the variable duty cycle PWM is on pin A and the fixed 50% 4x clock signal is on pin B. (You can also swap these and and also update the code everything will still work)
Enable interrupts for "On compare match B" and "Overflow".
Force pin A high with a force compare match. (Alternately you can skip this step and instead use the inverse of the desired duty cycle in step 7)
Set the COM bits for 'A' to Toggle on match.
Leave the COM bits for B to off. Assumes you have DDR set for this pin to be normal GPIO.
Set the OCR for B to 128.
Set the WGM timer mode to 0 - "Normal Mode".
Set the OCR for A whatever you want the variable duty cycle to be. Note that you might need to special case here for extreme values of 0 and/or 255 depending on what you want to have happen (just turn the pin ON of OFF with GPIO).
You can repeat step 6 anytime you want to change the duty cycle of A and it will update on the next TOP.
Once you do these steps, the A pin will output the desired duty cycle at 1/2 the prescaller clock and the B will output 50% duty at 2x the prescaller clock (which is the desired 4x of the A period).
Here is the ISR code (note that I am not sure what the TOV vector is called in the attiny13 headers [it is sometimes different across AVRs] so you might have to edit the TIM0_OVF_vect name)...
ISR(TIM0_COMPB_vect,TIM0_OVF_vect){
PINB |= (1 << PB3); // Compiles to a single cycle SBI
}
See how this works?
Note that setting a bit in the PIN register actually toggles the PORT bit. This is a quirk of the AVR GPIOs that is documented in the datasheets.
Hopefully this is fast enough. If you really want to squeeze every last cycle out, you can even potentially save the 2 cycles of the RJMP from the interrupt vector by putting the single SBI instruction that the ISR compiles down to directly into the interrupt vector table with a trailing RETI, but this is more complicated!
Focusing solely on the C code aspects, then this can be trivially optimized as:
ISR(TIM0_COMPB_vect)
{
static const uint8_t OCR[4] = {63,127,191,255};
OCR0B = OCR[timing];
PORTB ^= 1u << PB3;
timing++;
if(timing==4)
timing=0;
}
Disassembled on gcc AVR -O3 (with all variables/registers volatile) this brings down the amount of instructions from ~50 to ~20, so it's about twice as fast and takes less memory.
If you just want the fastest equivalent version of the supplied code, then here it is...
ISR(TIM0_COMPB_vect){
OCR0B += 64;
PINB |= (1 << PB3);
}
OCR0B will overflow every 4 passes, which is defined behavior. Probably wise to initialize OCR0B to some non-zero number like 1 to avoid edge cases.
This avoids all variables and memory access - only register access.
Avoids all compares and braches.
The PINB method of toggling the pin compiles down to a single SBI instruction rather than a PUSH, load, XOR, store, POP sequence.
...but again, none of this matters if it does not work and unless you are using one of the two "immediate OCR update" modes, then updating OCR in the middle of a timer cycle will have no effect.
I'm not sure if this is happening when assigning a variable specifically but when debugging the assembly code, the compiler executes RJMP $+0000 where it hangs the program.
EDIT: I added included libraries if that's relevant
#define __DELAY_BACKWARD_COMPATIBLE__
#define F_CPU 8000000UL
#include <avr/io.h>
#include <avr/delay.h>
#include <stdint.h>
void ReadTemp(uint8_t address){
ADCSRA = ADCSRA | 0b10000111; //enable ADC, CLK/128 conversion speed
ADMUX = ADMUX | 0b01000000; //Use internal 2.56V Vref and PA0 as input, right-hand justified
ADCSRA |= (1 << ADSC); //start conversion
while(!(ADCSRA & (1 << ADIF))) {} // wait until process is finished;
uint8_t low_value = ADC & 0x00FF;
// or low_value = ADCL;
uint8_t high_value = ADC & 0xFF00; //problem here
...
}
I don't know what any of this is doing, but I do see an error in the bitwise math.
uint8_t low_value = ADC & 0x00FF;
uint8_t high_value = ADC & 0xFF00; //problem here
low_value and high_value are both 8 bits (uint8_t). I am going to go out on a limb here and say ADC is 16 bit. For high_value, you are anding ADC with 0xFF00 then truncating the value to 8 bit. high_value will always be zero.
What should be done is:
uint8_t high_value = (ADC & 0xFF00) >> 8;
This will grab the left byte of ADC and shift it right by 8 bits then assign it to the high_value byte storage giving you the correct value.
How you are doing low_value is correct. As a matter of fact, you could simply do:
uint8_t low_value = ADC;
There is something somewhat suboptimal here:
uint8_t low_value = ADC & 0x00FF;
You are reading ADC, which is a 16-bit register, implemented as a pair
of 8-bit registers. As shown in the disassembly, this requires two in
instructions, one per byte. And then you are just throwing away one of
those bytes. You may think that the compiler is smart enough to avoid
reading the byte it is going to discard right away. Unfortunately, it
cannot do that, as I/O registers are declared as volatile. The
compiler is forced to access the register as many times as the source
code does.
If you just want the low byte, you should read only that byte:
uint8_t low_value = ADCL;
You then wrote:
uint8_t high_value = ADC & 0xFF00;
As explained in the previous answer, high_value will be zero. Yet the
compiler will have to read the two bytes again, because the I/O register
is volatile. If you want to read the high byte, read ADCH.
But why would you want to read those two bytes one by one? Is it to put
them back together into a 16-bit variable? If this is the case, then
there is no need to read them separately. Instead, just read the 16-bit
register in the most straight-forward fashion:
uint16_t value = ADC;
A long time ago, gcc didn't know how to handle 16-bit registers, and
people had to resort to reading the bytes one by one, and then gluing
them together. You may still find very old example code on the Internet
that does that. Today, there is absolutely no reason to continue
programming this way.
Then you wrote:
//problem here
Nope, the problem is not here. That is not what generated the rjmp
instruction. The problem is probably right after that line, in the code
you have chosen not to post. You have some bug that manifests itself
only when optimizations are turned on. This is typical of code that
produces undefined behavior: works as expected with optimizations off,
then does weird “unexplainable” things when you enable optimizations.
There are quite a few similar questions, but none seem to have quite the same issue. I am connecting an STML4 MCU to a 6-axis sensor (LSM6DS3). I have successfully implemented everything in I2C, but would like the extra speed of SPI (and DMA, if I can get these first steps working...). So for a first step, I am trying to read the WHO_AM_I register (0x0F) of the device, which should reply with 0x69. Here is the code:
uint8_t who = 0;
// Sanity check/debugging aid should get 0x5D
who = 0x43 + 0x1A;
// Set SS low
GPIO_WritePin (GPIOB, LL_GPIO_PIN_7, GPIO_PIN_RESET);
// while tx buffer is in use, wait
while (!LL_SPI_IsActiveFlag_TXE(SPI1));
// Send READ command to the WHO_AM_I register
(SPI1->DR) = 0x8F;
// while rx buffer is in use, wait
while (!LL_SPI_IsActiveFlag_RXNE(SPI1));
// Get data off the register
who = (SPI1->DR);
// Wait for everything to wrap up before setting SS high again
while (LL_SPI_IsActiveFlag_BSY(SPI1));
// OK, now we can set SS high
GPIO_WritePin (GPIOB, LL_GPIO_PIN_7, GPIO_PIN_SET);
On the scope/analyzer, I see everything run as expected, including the sensor sending back 0x69. However, when I set a break on the other side of this code block, I see that who goes from 0 to 0x5D to 0xFF. It never reads the 0x69. I looked through other code examples and some people have a second transmit with the data set to some dummy value (usually 0xFF or 0x0), so I also tried that, but the sensor seems to get confused during the second attempt and who ends up being 0x48. I have tried every permutation of waiting for the RXNE/TXE/BSY flags that I could have, as well as many many other things to get the variable to correctly read the SPI1 data register, including reading other registers off the sensor, but all to no avail.
So then the question is, how does one correctly read values off this register?
I should also mention that I can successfully write to the device's registers. I can send the command I want, then read it back and see that it worked on the scope, even though I can never get the values assigned to a variable in code. I always get 0xFF.
I include a screen of my analyzer showing the sensor sending back 0x69 from a single read request, as well as the garbled mess it sends if I try the "dummy transmit" method.
SPI always (if the receiver is enabled) receives the data when you transfer.
This is the problem with the libraries that you do not know what is there. SPI is a lot easier to program using registers.
I assume that your data is 8bits.
You need to set the 1/4 (one byte) FIFO threshold during the SPI initialization by:
SPI1 -> CR2 |= SPI_CR2_FRXTH;
next you need to read the data from the FIFO after every write (you need also to force the compiler to use the correct size (in this case 8bits) load and store instructions):
*(volatile uint8_t *)&SPI1->DR = 0x8F; // write exactly 8 bits to the FIFO
while (!LL_SPI_IsActiveFlag_RXNE(SPI1));
dummy = *(volatile uint8_t *)&SPI-> DR;
*(volatile uint8_t *)&SPI1->DR = 0; // dummy write
while (!LL_SPI_IsActiveFlag_RXNE(SPI1));
who = *(volatile uint8_t *)&(SPI1->DR);
I do not know what is the point of using the LL libraries.
instead of
while (!LL_SPI_IsActiveFlag_RXNE(SPI1));
use registers
while (!(SPI1 -> SR & SPI_SR_RNE));
You can also wrap it into the function:
uint8_t SPI_ReadWrite8(SPI_TypeDef *spi, uint8_t data)
{
while(!(spi -> SR & SPI_SR_TXE));
*(volatile uint8_t *)&spi->DR = data;
while (!(spi -> SR & SPI_SR_RNE));
return *(volatile uint8_t *)&spi-> DR;
}
And
SPI_ReadWrite8(SPI1, 0x8f);
who = SPI_ReadWrite8(SPI1, 0);
I'm a beginner in this field. My goal is to change the output of 8 LEDs (which are connected to PORTA) according to the potentiometer. I have connected the middle line of the potentiometer to PF0, which is ADC0. I also connected the other two lines to the 5V and ground.
I know there's no problem with the chip or connection because the LEDs are working just fine.
But no matter how I change the code below (what I mean by changing is by slightly changing the ADMUX and ADCSRA registers) no output is shown!
I am using atmega128 with 16MHZ clock. Below is the code that I'm trying to solve.
#include <asf.h>
#include <avr/io.h>
#define F_CPU 16000000L
int init_board(void)
{
DDRA=0xff;
PORTA=0x01;
}
int ADC_init(void)
{
//ADCSRA
ADCSRA = 0b10000111;
//ADMUX
ADMUX = 0b01100000; // middle line connected to ADC0
}
int main (void)
{
init_board();
ADC_init();
ADCSRA |= (ADSC >> 1);
while(1)
{
if(ADSC == 0)
{
uint8_t disp_value = ADCL;
PORTA = disp_value;
delay_ms(200);
ADCSRA |= (ADSC >> 1);
}
}
}
I have no idea why the code doesn't work.
I suppose it's because it didn't set my register correctly, but I've followed all the instructions on the atmega128 datasheet.
First issue is your bit shifting, it should be ADCSRA |= (1 << ADSC).
Next issue is results reading. You set fifth bit of ADMUX to 1, so ADLAR=1 and in that mode result is left adjusted so you should read ADCH.
Moreover when you switch to 10-bit resolution, i.e. you start working with multi-byte results, be aware that reading only ADCL is not enough, see datasheet 23.3 for explanation: "Once ADCL is read, ADC access to data registers is blocked. This means that if ADCL has been read, and a conversion completes before ADCH is read, neither register is updated and the result from the conversion is lost. When ADCH is read, ADC access to the ADCH and ADCL Registers is re-enabled."
Lastly, using hardcoded delays for reading is not good practice especially when you change code later to read ADC as fast as possible. In such case after conversion start you should check if ADIF flag is set or react with interrup when ADEN is set. Refer to datasheet for details.
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.