Pages

Sunday 28 January 2024

Serial Data Transmission with RF - Receiving Notes Part 2

So we're transmitting some bytes and looking at the receiver output. Note on the following that the interference persists past the first start symbol and into the actual byte symbols. 



Changing the first start symbol to simply be five high units fixed this.




And we can see it takes a comparatively long time when the TX is close for the gain to rise again so that noise and interference is toggling the receiver output.

 

 
 As a result the first start symbol was changed from LLHHH to a preamble of  HHHHH. 
 
It would have been better if I had solved the noise problem but so far it has eluded me.
 
 


Sunday 21 January 2024

Serial Data Transmission with RF - Receiving Notes Part 1

 

What follows is some preliminary detail on how I receive the data stream.

The STM8 chips have multiple timers. The simplest timer is 8 bits which is sufficient and was unused by STM8 eForth. I ended up using a lot of assembly code in STM8 eForth to measure the pulse timing. 

Update - with the benefit of hindsight now that it is working I didn't need to use as much assembly code as I used.

I clocked the 8 bit TIMER4 at 250kHz to avoid overflowing when a start pulse was received.

 In pseudo code then a word like: 

: PULSE@ ( -- ) \ measure the time from now till falling edge of pulse
         Turn on the timer      
         Wait for falling edge
         Turn off timer
;

 actually becomes when you can only test for logic levels:

: PULSE@ ( -- ) \ measure the time from now till falling edge of pulse

   TimOn \ turn on the timer
   [ $7200 _RX 2* + 1+ , PD_IDR , $FB C, ] \ wait till _rx is high
   [ $7200 _RX 2* +   , PD_IDR , $FB C, ] \ wait till _rx is LOW
   TimOff \ turn off the timer

This worked really well and could easily handle higher data rates.

However, I later worked out how to  copy the assembly code from a compiled word into a new word avoiding the need for CALLS and RETURNS. This gave me a smoother coding experience and avoided  repeating the assembly code whenever I wanted to incorporate it in a high level word. A bit like a macro in AVR assembler but the destination for the macro is a high level word.

: Pulse@ \ ( -- )  time, now to falling edge
   [ ' TimOn ]M! \ Turn on TIM4
   [ ' _RX=1 ]M! \ wait till _rx is high
   [ ' _RX=0 ]M! \ wait till _rx is LOW
   [ ' TimOff ]M! \ Turn off TIM4
;

While it is not obvious Pulse@ is now just pure assembly with no call's or return instructions. If I decide to use a different timer I only have to change the definition of TimOn and TimOff in one place. Later when I post the full code it will hopefully be clearer.

At this point the code now easily meets any time constraints.  But a better receiver, still to be tested, might allow much faster data transmission so I'm leaving the assembly code in place because I might want to push the data rate still faster one day.

And while all of this only takes a few clock cycles, the longest pulse length we want to measure is a start pulse of around 640uS. If all we were doing was measuring one pulse it would be simpler. But I planned to measure up to 10 pules in succession. My plan is to do some error checking takes after the pulses following a start pulse have been received. 

 




Sunday 14 January 2024

Serial Data Transmission with RF - Transmtting Notes Part 1

I was so easily deceived. All those byes flying around and not being decoded reliably. I spent a long time on the receiver and in frustration checked the transmitter data stream.

You could have heard a pin drop. 

When it was transmitting two good start pulses the data was being ruined by timing errors:


The shaded areas above show where the delay in changing state was recorded. There were also examples of start pulses being mistimed. 

There was no reason for the missing clock cycles. Op codes which were supposed to take 1 cycle were sometimes times taking longer. No interrupts were firing. After days of testing Thomas, author of STM eForth, and I think this is caused by the pipe-lining of execution instructions. The programming manuals makes an off hand reference to this by pointing out "In some cases, depending on the instruction sequence, the cycle taken could be more than that number." Add to that the fact that the pipeline fills differently for RAM compared to Flash.

Which could explain why, when sending a string of high/low instructions which should have produced a nice 50:50 duty cycle waveform I got the following when executing from RAM



And this when executing from flash.




Same code. I didn't zoom in as close on the flash version but you should see that when the code was executed in RAM every second pulse lost a time unit during the low period, but when executed from flash the lost time unit occurred during every second high period.  

This was eventually traced to the way the pipeline fetches code. Fixed by changing the code so that the pipeline was always reloading. Consistent outcome but slower. Required an 8fold increase in the CPU clock to 125kHz to compensate for this.


Sunday 7 January 2024

Serial Data Transmission with RF - Overview

What follows is some background information to sending data via RF. I will fully expand on this in coming weeks.  This is working across the workbench but I have yet to explore the range that can be achieved.

I stumbled across Roman Black's article on using cheap RF modules for transmitting data. While the world has come a long way in the 10 years since Roman wrote about his findings those modules, and more modern ones at similar modest cost, are widely available. 

The concept had my hooked immediately.

  • It uses RF
  • It was really well written and comprehensive
  • Seemed like an ideal project that will enable other projects.
  • I get a thrill out of using STM8 eForth. You should try it too.

My broad goal was a burst transmission of one to three bytes of data with long off periods. This required some maths to understand the tradeoff between battery life, data speed and preamble length. At present I am using a "pedestrian" data rate, (uS shown is measured):

  • Start pulse is 3 units low, 2 units high. (560uS)
  • Zero is one unit low, one unit high,and (298uS)
  • One is two units low, one unit high       (410uS)

Each byte takes about 4ms to send and for testing I spaced them 500ms apart. In practice I expect I will send a packet of 1-3 bytes, repeated several times before going into a long sleep.

With some modules on the workbench and my digital scope set up it was no time before I had a transmitter sending  $AA or $55 for testing purposes.

Then the heartache started. One of my two receiver's didn't work. That was traced down to a through hole via not being connected. 

But with all the hardware working it was time to finesse the software. While it took me ages to work this out it was all down to hardware and software timing. And some weird seemingly random timing errors in the transmitter which took a really long time to track down. 

I will try to explain in coming articles, perhaps once per week, how I moved from concept to finished RF link, or indeed if I couldn't get it to work. This will definitely not be some superficial clap trap telling you to wire this pin to this, load this library and your done series of posts. I'll be sharing what I learned and hopefully inspire you to improve my code and hardware choices.

Plenty to talk about next time.

Conclusion

I wish more projects were written up to the standard of Roman Black's article. I gave up looking at other articles on these modules because it was clear they were factually wrong. From the solid foundation Roman's article gave me I expect I can send bursts of data with long battery life for the transmitter.

This now enables me to pursue some projects I had never progressed beyond being a possibility. Monitoring my workshop door will be the first real application. I can't see it from anywhere in the house meaning I have to walk up there to confirm I have locked it.

The github for this project will be open soon.