Generating a bit-stream?

Go To Last Post
41 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Most (all?) serial protocols require the uC to generate a bitstream of data.  the AVR chips obviously have that capacity built into the hardware as part of i2c, spi, usart, etc.

 

I'm wondering if there's a good way to use such a hardware-generated bitstream in software.  It's easy enough to do so purely in software - pull the next byte from a buffer, loop on a counter, pulling out one bit at a time.  But that's a lot of looping, maintaining counters, etc.  Fair amount of clock cycles.  Is there a faster way? 

 

An application for this would be any sort of "other" serial based communication protocol - bitbanging, etc.

Last Edited: Mon. Feb 19, 2018 - 02:28 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Most of the devices you mentioned create a BYTE stream not a BIT stream. They are very 8-bit centric (or multiples thereof). So even if possible it wouldn't help much to create a true Bitstream such as you might want for things like Huffman.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I suppose with something like an SPI you could do something like connecting MOSI to MISO if bit length really were configurable between 1 and 8 bits (not tiny/mega but some other designs) but what does that really buy you? You send out 5 bits (or whatever) to then have the same 5 come back in. Wouldn't an AND with a 0x1F bit mask have been easier? Forget the SPI.

Last Edited: Mon. Feb 19, 2018 - 02:37 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

jgalak wrote:

I'm wondering if there's a good way to use such a hardware-generated bitstream in software. 

I've used the USART on an m168 in SPI mode to send a Manchester-encoded bitstream to a 433MHz RF transmitter module. Each "byte" of the bitstream is loaded into the Tx register in the Tx-FIFO-Empty ISR, the bytes having been prepared earlier. MISO and SCK were not used.

 

This is handy if the CPU needs to do other stuff in the foreground while maintaining jitter-free background data transmission. The USART was used, rather than the native SPI interface, because it has a Tx holding register so can tolerate some delay in interrupt response time.

 

Steve

Maverick Embedded Technologies Ltd. Home of Maven and wAVR.

Maven: WiFi ARM Cortex-M Debugger/Programmer

wAVR: WiFi AVR ISP/PDI/uPDI Programmer

https://www.maverick-embedded.co...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

But that's still byte-wise, not bit-wise isn't it? OP mentioned using the hardware to create a "bitstream".

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The main question is how does the receiver know when bit's are coming?

like a UART with same time between (and start bit to sync on), or do you want a clk (so same time don't need to be perfect SPI, (I2C have to rules), or Manchester code where variation matter or .......   ).

In SW you can do what you want.

Speed : you have to get the byte :)  the bit handling don't need to be a loop can be 8 (if you use a byte format) check each bit and then set a IO after the need. 

I't much harder to make the receiver than the transmitter (unless you use a clk. )

 

but why reinvent the wheel?

   

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The CPU still deals in bytes, yes. But the bytes are just convenient containers for the bitstream visible on the MOSI pin.

 

Steve

 

Maverick Embedded Technologies Ltd. Home of Maven and wAVR.

Maven: WiFi ARM Cortex-M Debugger/Programmer

wAVR: WiFi AVR ISP/PDI/uPDI Programmer

https://www.maverick-embedded.co...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
Most of the devices you mentioned create a BYTE stream not a BIT stream. They are very 8-bit centric (or multiples thereof). So even if possible it wouldn't help much to create a true Bitstream such as you might want for things like Huffman.

 

No, it generates a bitstream - when the signal actually hits the SPI, I2C, or USART physical wire, it comes out one bit at a time.  In software, you load a byte at a time into the register, but the uC send it out as a bitstream.  It could only send out a bytestream if you had a parallel bus.

 

But i don't want to send out a bitstream.  I just want to generate it, internally, to do something with.  I want a function call, like "nextbit()" that returns a single bit from a buffer (to which bytes were added).  Exactly what the hardware is doing for serial protocols, but accessed internally.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

No, these interfaces just do the shifting in hardware straight to the output pin - the bit stream is not available to software.

 

ADDENDUM

 

In assembler, you can just shift & pick the next bit from the Carry flag

 

https://www.microchip.com/webdoc/avrassembler/avrassembler.wb_LSL.html

 

https://www.microchip.com/webdoc/avrassembler/avrassembler.wb_LSr.html

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
Last Edited: Mon. Feb 19, 2018 - 03:11 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

sparrow2 wrote:
but why reinvent the wheel?

 

Because it was an interesting hobby project.

 

Steve

 

Maverick Embedded Technologies Ltd. Home of Maven and wAVR.

Maven: WiFi ARM Cortex-M Debugger/Programmer

wAVR: WiFi AVR ISP/PDI/uPDI Programmer

https://www.maverick-embedded.co...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

jgalak wrote:
Exactly what the hardware is doing for serial protocols, but accessed internally.
yes but a UART does not have the ability (on receive) to say "give me the next 3 bits". It's "give me the next byte" or nothing. So it is not dividing the bits.

 

Even if you did something like wiring MOSI and SCK from an SPI back into two input lines so you could "see the bits" you still have no way to say "next bit" or "next 3 bits" or whatever. It's always going to be a case of "next 8". So in that case how does:

uint8_ buff[N];

...
  SPDR = buff[n];
  // grab the bits on IO
  bits = receive8();

actually help in any way? You might as well have just done:

bits = buff[n];

Anyway the nextbit() you propose is hardly rocket science:

uint8_t buff[N];

uint8_t nextbit() {
    static int idx = 0;
    static int bitpos = 0;
    uint8_t retval;
    
    retval = buff[idx] & (1 << bitpos);
    bitpos++;
    if (bitpos > 7) {
        bitpos = 0;
        idx++;
        if (idx > N) {
            // signal end of data somehow?
        }
    }
    return retval;
}

In that I increment bitpos so it will deliver buff[0].0, buff[0].1, buff[0].2 .... buff[0].7, buff[1].0 and so on. If it were preferred to have the bits "from the top down" as buff[0].7, buff[0].6, buff[0].5 .. buff[0].0, buff[1].7 then:

uint8_t nextbit() {
    static int idx = 0;
    static int bitpos = 7;
    uint8_t retval;
    
    retval = !!(buff[idx] & (1 << bitpos));
    bitpos--;
    if (bitpos < 0) {
        bitpos = 7;
        idx++;
        if (idx > N) {
            // signal end of data somehow?
        }
    }
    return retval;
}

Obviously this could all be optimised! Was it the performance of this code you were concerned about? I agree the 1 << bitpos in:

    retval = buff[idx] & (1 << bitpos);

is expensive. In which case:

uint8_t nextbit() {
    const __flash uint8_t masks[] = { 1, 2, 4, 8, 16, 32, 64, 128 };
    static int idx = 0;
    static int bitpos = 0;
    uint8_t retval;
    
    retval = buff[idx] & masks[bitpos];
    bitpos++;
    if (bitpos > 7) {
        bitpos = 0;
        idx++;
        if (idx > N) {
            // signal end of data somehow?
        }
    }
    return retval;
}

As I say I just scribbled this - no thought to optimisation.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

jgalak wrote:

No, it generates a bitstream - when the signal actually hits the SPI, I2C, or USART physical wire, it comes out one bit at a time.  In software, you load a byte at a time into the register, but the uC send it out as a bitstream.

 

Pedantically, it comes out as a delimited bitstream, being delimited into bytes. For example, the USART has start and stop bits at each end of the bitstream block.

#1 Hardware Problem? https://www.avrfreaks.net/forum/...

#2 Hardware Problem? Read AVR042.

#3 All grounds are not created equal

#4 Have you proved your chip is running at xxMHz?

#5 "If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand."

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Perhaps take a look at the USI that are on some small tiny's (like tiny85) it's basically a shift-register with some counters. and that can be programmed to work in many "odd" ways.   

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This is the kind of thing where a chip with a built-in FPGA or CPLD would be good ... 

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I knew I'd seen some chips from Atmel with SPI that had variable bit length. Finally found it lurking in a UC3 datasheet...

But this still just has 4..8 bits going out to come back in again (assuming loopback wiring) so achieve nothing. At the time of Tx you know whether you just set it to send 5 or whatever (and it can't be shorter than 4 anyway).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If you feel the need for speed you can do horrible things in assembler and abuse the hardware.

 

A few years ago I needed to generate a video waveform. The chip (an original 90S2313) had a maximum speed of 10MHz, which meant I had exactly 4 clock cycles to update the output pin. Using the SPI was not an option as it didn't have one.

 

So I precomputed the output buffer of 12 bytes/96 bits and had a routine that wrote a register to a whole 8-bit port, on which only the bottom bit was used as the output pin. My output code was then 8 copies of this...

 

	out	video_port,reg_1	;[1] pixel 2
	lsr	reg_1			;[1]
	nop				;[1]
	nop				;[1]

 

...one for each bit. I still needed to manage the loop overhead and updating of the buffer pointer so all that code went into the two NOPs on each bit output giving me 16 cycles spread about the place to handle the overhead. Something like this...

 

do_it:
	ldi	counter,12		;[1]
	ld	reg_1,Y+		;[2]

_101:
	out	video_port,reg_1	;[1] pixel 0
	st	Z+,reg_1		;[2] save pixels into repeat buffer
	lsr	reg_1			;[1]
	out	video_port,reg_1	;[1] pixel 1
	lsr	reg_1			;[1]
	nop				;[1]
	nop				;[1]
	out	video_port,reg_1	;[1] pixel 2
	lsr	reg_1			;[1]
	nop				;[1]
	nop				;[1]
	out	video_port,reg_1	;[1] pixel 3
	lsr	reg_1			;[1]
	dec	counter			;[1] dec counter
	nop				;[1]
	out	video_port,reg_1	;[1] pixel 4
	lsr	reg_1			;[1]
	tst	counter			;[1] is counter zero?
	in	temp,SREG		;[1] save Z flag
	out	video_port,reg_1	;[1] pixel 5
	lsr	reg_1			;[1]
	bst	temp,1			;[1] move Z into T
	nop				;[1]
	out	video_port,reg_1	;[1] pixel 6
	lsr	reg_1			;[1]
	LD	temp,Y+			;[2] get next 8 pixels
	out	video_port,reg_1	;[1] pixel 7
	mov	reg_1,temp		;[1] put next 8 pixels ready
	brtc	_101			;[2/1] loop if not 12 times round
	nop
	video_low

 

 

The headache was that LSR affects all flags so doing any testing of counter values became 'interesting'.

 

So this technique could be used to make a rotate routine if you wanted to do something 'odd'.

#1 Hardware Problem? https://www.avrfreaks.net/forum/...

#2 Hardware Problem? Read AVR042.

#3 All grounds are not created equal

#4 Have you proved your chip is running at xxMHz?

#5 "If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand."

Last Edited: Mon. Feb 19, 2018 - 03:46 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

jgalak wrote:
I'm wondering if there's a good way to use such a hardware-generated bitstream in software

Even if there were, what would be the purpose?

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

awneil wrote:
Even if there were,
Well there is actually - it's called the AVR CPU. It's not bad at shifts/rotates is it? (OK not a barrel shifter but they are fairly efficient opcodes).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

that was #9.

 

but that is, as you say, getting the CPU to do it - not somehow "tapping" the UART/SPI/I2C peripheral output stream 

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

The only reason I could see that a separate peripheral might help is if (a) the CPU were terrible at doing bit shifts (but it's not) and (b) something so CPU intensive is occurring (like video generation) that the CPU doesn't have even a handful of cycles for some shifting task.

 

Otherwise the CPU itself seems like the best designed "peripheral" in the entire chip for doing the job.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes, I think so:  by the time you'd written your byte out to the UART/SPI/I2C peripheral, managed the peripheral, and read back in the "bitstream", it'd probably be just as quick & easy to do the software shifts.

 

And you'd still be able to use the UART/SPI/I2C peripheral for its proper intended purpose!

 

EDIT

 

managed the peripheral

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

awneil wrote:

jgalak wrote:
I'm wondering if there's a good way to use such a hardware-generated bitstream in software

Even if there were, what would be the purpose?

 

The purpose is to generate a bitstream :)

 

The project I'm working on takes in a stream of bytes, and has to take specific actions based on each bit, one at a time.  The actions are entirely internal to the uC - it actually changes, or doesn't change, a timer control register based on the next bit.  The processing of the bit happens in an interrupt handler (fired off a different timer) so getting it efficiently seems useful. 

 

It's currently working fine with the "loop through each byte on a counter" approach, but the processor (Atmega328P right now, but I might try to move to a smaller one later in the development) isn't doing much else.  Since it's all happening in interrupts which need to be fairly precisely timed, I'm concerned with what will happen when more things are added to the workload of the  processor.  But it's entirely possible that this is a groundless worry.

 

(For those that like to know more details, I've asked about other aspects of this before.  The project is an APRS tracker for a high altitude balloon.  The APRS protocol is, essentially, a non-return to zero serial protocol alternating between two tones.  If the next bit is 0, it changes to the other tone.  If the next bit is 1 it doesn't.  The implementation uses a 4 bit R2R ladder as a DAC, driven by 4 GPIO pins on the uC.  One timer-based interrupt outputs sine wave values based on a look up table.  This interrupt fires 16 times for each period of the tone, outputting the next sine wave value.  A different timer-based interrupt, which fires 1200 times per second (1200 baud) looks at the next bit and, on a 0, changes the CTC register of the first interrupt.)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

But, as noted, the CPU is perfectly able to do this itself

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Write code that writes bytes into a buffer and can read back bit by bit. Example:

class BitStream
{
public:

	void writeByte(uint8_t b)
	{
		buffer[writeIndex++] = b;
	}

	uint8_t readBit()
	{
		uint16_t idx = readIndex++;
		uint8_t bufferIndex = (uint8_t)(idx / 8);
		return buffer[bufferIndex] & (1 << (7 - (idx % 8))) ? 1 : 0;
	}

private:

	uint16_t readIndex;
	uint16_t writeIndex;

	uint8_t buffer[16];
};

This is:

  • Wildly inefficient
  • Needs to be changed if you're going to use C
  • Does not do any kind of error or bounds checking
  • Does not care about overrun
  • Should be re-done per your requirements
  • Probably sucks, it was written in a few minutes

 

I'll leave those as a job for you to do.

My digital portfolio: www.jamisonjerving.com

My game company: www.polygonbyte.com

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

for #23

 

I would just use one timebase.

And not change timer value for the tones , but make a DDS for the two tones. 

All in all I would think that would be more efficient, and easier to find worst case.

(And all that know me :)  I would write the ISR in ASM). 

 

So if we have about 40KHz ISR and run @16MHz it would be about 400 CPU clk pr ISR, and my guess is that worst case ISR would be about 30 clk (or 25 if this is the main thing the CPU does.)  

that give about 6-8% of the cpu time for this, and perhaps 15-30 % of the time if written in C. So that should no be a time problem.

 

Then you talk about go for a smaller AVR, but that can run at same speed, and none of this will be slower on a tiny (perhaps some  * and / calc. at init, but not for the ISR(s))

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I gave it a shot to type a bit more streamlined algorithm for you.

Here is what I came up with (untested):

class BitStream {
uint8_t buffer[ 16];
uint8_t bitIndex, byteIndex;

public:

void readInit( void) {
    bitIndex = 1;
    byteIndex = 0;
}

bool readBit( void) {
    bool bit;

    if( bitIndex & buffer[ byteIndex]) {        // Extract the bit.
        bit = true;
    }else {
        bit = false;
    }

    bitIndex <<= 1;                          // Recalculate indexes.
    if( bitIndex ){
        // Do nothing, still good.
    }else {
        // Overflow, Reinit parameters.
        bitIndex = 1;
        ++byteIndex;
        if( byteIndex > sizeof (buffer)) {
            byteIndex = 0;  // Oops, error, end of data, whatever. Prevent overflow.
        }

    }
    return bit;
}

};  // class BitStream

The idea to making it "fast" is not by using  less lines of code, but by only using constructs which can be easily translated by the C++ compiler to asm opcodes.

Using a few more lines to write your code on makes it a lot more easyer for "normal" humans to read & maintain the code.

 

Internally a "bool" is stored in a "byte".

You could benefit from using negative numbers.

For example not return a bool, but an int8_t you can code more information in your return type.

return 1;       // "true".
return 0;       // "false".
return -1;      // All bytes have been read, or error ...
return -3;      // We're gonne crash.

For real performance quantification you will want to look at the LSS output to verify the quality of the ASM code the compiler spitts out.

Doing magic with a USD 7 Logic Analyser: https://www.avrfreaks.net/comment/2421756#comment-2421756

Bunch of old projects with AVR's: http://www.hoevendesign.com

Last Edited: Tue. Feb 20, 2018 - 04:03 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

sparrow2 wrote:

for #23

 

I would just use one timebase.

And not change timer value for the tones , but make a DDS for the two tones. 

All in all I would think that would be more efficient, and easier to find worst case.

(And all that know me :)  I would write the ISR in ASM). 

 

So if we have about 40KHz ISR and run @16MHz it would be about 400 CPU clk pr ISR, and my guess is that worst case ISR would be about 30 clk (or 25 if this is the main thing the CPU does.)  

that give about 6-8% of the cpu time for this, and perhaps 15-30 % of the time if written in C. So that should no be a time problem.

 

Then you talk about go for a smaller AVR, but that can run at same speed, and none of this will be slower on a tiny (perhaps some  * and / calc. at init, but not for the ISR(s))

 

I'm not sure how that'd help - there's two different things going on here.  The tone is either 1200 Hz or 2200 Hz, which means that with a 4 bit DAC I am pushing a value at either 19200 ops/sec or 35200 ops per second.  But the signal is 1200 baud, so the decision of which tone to send always happens 1200 times per second.  Two timers make this work easily.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In case anyone is interested, here's my current code (which works).

 

First the sine wave generation and control code:

 

#define COUNTER_1200 51
#define COUNTER_2200 27

void start_wave()
{
	DDRD = DDRD | 0b00111100;  //set port D5:2 to output - for 4 bit DAC

	//set up the frequency timer (1200/2200Hz)
	initFreqTimer();

	initBaudTimer();

	//activate interrupts
	sei();
}

void initFreqTimer()
{

	//Put timer0 into CTC mode
	TCCR0A |= (1 << WGM01);

	//Set no prescaler for 6 bit
	//TCCR0B |= (1 << CS00);

	//Set 1/8 prescaler for 4 bit
	TCCR0B |= (1 << CS01);

	//Set Count: 1200Hz = 207, 2200Hz = 111(calc) or 113(experiment)
	OCR0A = COUNTER_1200;

	//Enable CTC interrupt
	TIMSK0 |= (1 << OCIE0A);

	//initialize phasecount
	phasecount=0;
}

void initBaudTimer()
{
	//Put timer2 into CTC mode
	TCCR2A |= (1 << WGM21);

	//Set 1/256 prescaler
	TCCR2B |= (1 << CS22) | (1 << CS21);

	//Set Count: 1200 baud @ 256 prescale = 25
	OCR2A = (uint8_t) ((1.0/BAUDRATE) / (1.0/(F_CPU/PRESCALER)) - 1);

	//Enable CTC interrupt
	TIMSK2 |= (1 << OCIE2A);
}

//Interrupt for changing freq
ISR (TIMER2_COMPA_vect)
{
	uint8_t nextbit;
	nextbit = queue_nextbit_stuffed();
	if (nextbit == 0)
	{
		OCR0A ^= (COUNTER_1200 ^ COUNTER_2200);
	}

}

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

And the bitstream generation code:

 

static volatile uint8_t queue[MAXQLEN];

//items go in through tail, come out through head
static volatile uint8_t *qhead;  //points at oldest bit
static volatile uint8_t *qtail;  //points at newest bit
static uint8_t *qstart; //start of queue
static uint8_t *qstop;  //end of queue

static volatile uint8_t bitpos;  //points at next bit
static volatile uint8_t stuffcount;

uint8_t queue_nextbit_stuffed()
{
	uint8_t curbyte;
	uint8_t ret;
	//if we've used up the byte, then call deq, otherwise get non-destructively
	if (bitpos == 7)
	{
		curbyte = queue_deq();
		} else {
		curbyte = *qhead;
	}
	
	ret = (curbyte & (1 << bitpos)) >> bitpos;  //selected bit will be in the LSB position
	
	if ((ret & 0x1) == 1)  //check if it's a 1
	{
		if (stuffcount >= 5)			//need to bitstuff.  send a zero, don't advance bit counter, reset stuffcounter
		{
			ret = 0;				
			stuffcount = 0;
			
		} else {					//no need to bitstuff bit advance stuff counter
			
			bitpos = ((unsigned) bitpos + 1) % 8;  //since APRS requires bits to be LSB first, need to count down
			stuffcount++;
			
		}
	
	} else {					//it's a zero, carry on and reset bitstuff
	
		bitpos = ((unsigned) bitpos + 1) % 8;  //since APRS requires bits to be LSB first, need to count down
		stuffcount = 0;
	}
	return ret;
}

void queue_setup()
{
	qhead = queue;
	qtail = queue;
	qstart = (uint8_t *) queue;
	qstop = (uint8_t *) queue + MAXQLEN -1;
	stuffcount=0;
	bitpos=0;
}

uint8_t queue_deq()
{
	uint8_t ret;

	ret = *qhead;
	
	if(!(qhead == qstop))
	{
		qhead++;
	}
	else
	{
		qhead=qstart;  //circular array
	}
	return ret;
}

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

you call :

queue_nextbit_stuffed()

From the ISR that is a bad habit! 

and that call

queue_deq()

Do you know max time for a ISR? 

 

Add:

And in that time the tone can't change!

 

Last Edited: Tue. Feb 20, 2018 - 12:20 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
	ret = (curbyte & (1 << bitpos)) >> bitpos;  //selected bit will be in the LSB position

The second shift in that is expensive and unnecesasry. Try:

	ret = !!(curbyte & (1 << bitpos));  //selected bit will be in the LSB position

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The "normal" way in C is to have bitpos as a mask. (then never more than 1 shift, I'm afraid that some compilers actually will do up to 7 shifts )

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:

	ret = (curbyte & (1 << bitpos)) >> bitpos;  //selected bit will be in the LSB position

The second shift in that is expensive and unnecesasry. Try:

	ret = !!(curbyte & (1 << bitpos));  //selected bit will be in the LSB position

 

This makes sense, for the most part, but why the double negation?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

The first ! turns 0/n into 1/0 (effectively "binary" but also "upside down") then the second ! turns that back to 0/1. At the end 0/n has become 0/1. It's a very common  technique (esp in the Linux kernel).

 

Actually looking at the way you actually use it:

	nextbit = queue_nextbit_stuffed();
	if (nextbit == 0)

You don't need any of this. This only considers ==0 so in the non-0 case it doesn't care if it's 1 or n anyway. You might as well just use:

ret = curbyte & (1 << bitpos);  //selected bit will be in the LSB position

which just returns 0 or n

Last Edited: Tue. Feb 20, 2018 - 08:44 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:

The first ! turns 0/n into 1/0 (effectively "binary" but also "upside down") then the second ! turns that back to 0/1. At the end 0/n has become 0/1. It's a very common  technique (esp in the Linux kernel).

 

Actually looking at the way you actually use it:

	nextbit = queue_nextbit_stuffed();
	if (nextbit == 0)

You don't need any of this. This only considers ==0 so in the non-0 case it doesn't care if it's 1 or n anyway. You might as well just use:

ret = curbyte & (1 << bitpos);  //selected bit will be in the LSB position

which just returns 0 or n

 

Yeah, I was just thinking that based on your prior post.  And then the double negation confused me :)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

And I just realized my confusion - I was thinking of bitwise complement, not logical negation, and couldn't figure out why doing it twice would accomplish anything.  Got my ~ and ! confused.  Makes perfect sense now.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

sparrow2 wrote:

you call :

queue_nextbit_stuffed()

From the ISR that is a bad habit! 

and that call

queue_deq()

Do you know max time for a ISR? 

 

Add:

And in that time the tone can't change!

 

 

I agree it's not great.  But is there a better way to access the next bit from inside the ISR? 

 

I suppose I could use a flag and a "nextbit" volatile variable, with the main loop checking the flag and then putting the next bit into the variable, and the ISR can set the flag and just access the variable.  But given the multiple interrupts, there's no guaranty that the main loop will have had a chance to do so before the interrupt happens.  I mean, it'll probably happen, 1200Hz isn't that fast for a processor running at 8MHz, but then again, it might not... 

I can do a two way flag signal, where if the main loop hasn't updated then, and only then, does the ISR go and get its own bit, but that seems really messy....

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Again the main question is what is the max time the ISR can take.

 

If you run 8 MHz (comments sounds like 7.68MHz), you problem is your generation of the 2200Hz tone.

You have 16 step, so that is "only" 227 clk between each ISR, so you will get jitter, or even missing ISR's if the

other ISR is slow.

You should look at some SW UART's to see how they find bits to be send etc. (your way aren't optimal) the only "odd" thing you have

is the stuffing bit.

But again you suffer if you write in "clean" C, I guess you should try to place some of the variables in registers.

 

Again I would only have one ISR that run at consistent time, so tone update is every n ISR. (and then a simple DDS to make the tones (it's just a 8 bit add )).    

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yeah, I was just thinking I need to go look at one of the soft serial libraries for Arduino (or equivalent) to see how they do it. 

 

I'm running at 8 MHz, in theory.  The current unit is an Arduino Pro Mini 3.3v, which is what it's set to.  But I've not measured it's actual clock speed (not sure how I'd even do that, easily).  Could be out of spec?  The values are a little bit trial and error.  I calculated the values, tried them, and then adjusted based on a measurement of the frequency.

 

"Real" DDS was my backup plan.  Might rewrite it that way.  So far, this is working, the frequencies look fairly clean but the uC isn't really doing anything other than this at the moment, either.