Category Archives: SiRiMa – the Bell-y Project

A tower bell ‘simulator’

Wireless access to the Raspi OS

 Setting up the Raspi as a wireless access point

I have previously shown how to instal and use Remote Desktop (rdp) on the Raspi.  This relies on the Pi connecting to my home wi-fi network, and then connecting to my PC via my home network. This was great, but when I was not in reach of my home network, the system would hang.

If I disabled the wireless LAN, the only way of connecting to the Pi was to open the box, and directly plug in the keyboard, mouse and monitor, not something I wanted to do when on site.

Someone suggested that I should configure it as a wireless access point.  I was able to do this,  using the very comprehensive and clear advice here:

https://thepi.io/how-to-use-your-raspberry-pi-as-a-wireless-access-point/

The Pi was broadcasting the network wi-fi ID and I could connect my wifi to it, but I couldn’t use VNC or Remote Desktop (rdp), meaning I couldn’t access the Pi’s OS and therefore make adjustments to the Pi’s setting or to my program.

This was back last June and although I tried a few things, other events overtook me and I set the whole project aside.

Re-visiting the problem

Anyway, I now have a realistic prospect of using the system, so I decided to revisit the problem, and it was amazing when I realised I had missed an obvious problem.  The advice given above and the software installed does correctly instal a wireless access point, but it makes it a wireless access point to the internet.  It connects the Raspi wireless access point  to your internet router via the Pi’s ethernet connection.  This is called ‘bridging’.  So it bypasses the Raspi OS entirely.  Of course, my SiRiMa software was starting correctly – which was a great improvement – so I could run it, but I couldn’t tweak it because any commands sent via rdp or vnc would just be redirected to the router, where they would be lost.

Once the penny dropped, all I needed to do was to remove the commands from the configuration settings that set up and configure the ‘bridge’ .

These are the steps given  in the above article that I had to revert:

Step 6: Set up traffic forwarding

Step 8: Enable internet connection

Problem solved

Possibly a couple of the other steps are overkill for my needs, but once I’d reverted the work on done on the two steps above
it works perfectly.  

VNC is built into the latest release of Raspian, so all you need to do is to activate it in the Pi’s configuration dialogue, and install vnc on your own PC.  Note that you don’t have to set up an account with realVNC to do this, but they do their best to avoid making this obvious.

If you don’t want to use vnc, then you need to install the remote desktop utilities onto the Pi and use rdp on your PC under Windows.  But bear in mind that rdp is only provided in the professional version of Windows.

Clean sound at last using Kemo M032N 12W amplifier

Sirima ringing demonstration

I believe that Sirima is now able to ‘ring’ bells correctly as demonstrated in this YouTube video, so that you can judge for yourself.  It uses recordings of the bells at my local tower and it is great to hear them being rung perfectly!

Eliminating timing problems on the sound output

Previous blogs about my bell-ringing machine describe the problems I’d had getting sound output with correct timing and how I’d resolved the problems. Without going over all of this again, it turned out to be necessary to read the wav files into the Pygame mixer’s buffers ahead of using them.

Running headless

To get Pygame to start correctly when running headless, it was necessary to ensure that the Python script was not initiated until  default user (Pi)  had been (automatically) logged in.

The Kemo M032N power amplifier

I am playing the sound through the Raspi’s headphone socket, into a Kemo M032N amplifier and through a speaker mounted in the box, so you just plug into a power point, wait 30 seconds for it to boot and away you go.

The Kemo M032N is a small modular single channel unit that can output about 10 watts into a 4 ohm speaker using a 12 v power supply.

Getting clean sound

But I had a huge amount of trouble getting clean sound.  I am using two small modular power supplies located within the box, one supplying 5 v for the Raspi, the other supplying 12 v for the Kemo.  The first part of the problem turned out to be that it was inadequate to supply the Raspi via the GPIO because the voltage loss via the connections was too high when the Pi and other outputs were taking a high demand. This caused a great deal of buzzing, crackling and popping on the audio jack output.

I found that I had to power it via the micro USB connector (which enables voltage regulation and overload protection built into the Pi’s circuit board) using a good quality lead and micro  USB plug.  The wiring of a phone charger type lead is too thin, giving a significant voltage drop at high load.

The better power supply solved the noise from the Pi’s audio chip.  But now I was getting a high-pitched whistle from the loudspeaker which is wired as recommended in the manufacturer’s diagram:

Kemo M032N connection diagram

Except it wasn’t! I had been worried about ensuring that everything was earthed, so I’d connected the negative of the modular power supply to the ground line of the unit.  I was interested that as I adjusted the input potentiometer (shown as Poti 10 k in the diagram) the pitch of the whistle changed.  It suddenly dawned on me that this must be oscillation (positive feedback) within the amplifier circuit, and that it might be due to a loop between the negative of the power supply and the grounded side of the signal input.  So I disconnected the earth of the power supply, so the power supply is now floating (i.e. not connected to anything else in the box).

Problem cured! Not only has the whistle gone, but the quality of the output is vastly improved too.  In fact it sounds truly excellent.

The Pi can give good quality sound

So it is not true that the output jack of the Pi can’t give good sound – it can and does give excellent sound, provided care is taken with the connections and wiring.

I will give more details of the connections in due course, but I must echo the advice of other bloggers: if your Pi is not giving decent sound output, you probably don’t need to buy a better jack plug, cable or amplifier, or put in a sound card, but instead sort out the power supplies and eliminate any loop that might give rise to feedback, earth currents and the like.

Obviously the jack output doesn’t claim to be high fidelity but it is certainly good enough for applications where this is not required.  To be honest, and speaking as a professional acoustician, how many expensive Hi-Fi installations actually do give high fidelity?

 

Starting Pygame sound when running headless on Raspi

I’m using Pygame to produce the sound output of my bell-ringing machine.  I had considerable difficulties in making this work satisfactorily and think others may find my experience helpful.

My machine does ‘change-ringing’, which is an abstruse activity that I don’t need to describe in this post.  Basically, the machine (dubbed SiRiMa) outputs bell sounds that must be made in a prescribed but constantly changing order, in a perfect rhythm or beat. The bell sounds are stored in wav files and this caused some issues in creating a perfect beat, which I have covered in a separate post.

SiRiMa does not have any screen or keyboard: it some has some switches to make the initial settings, and push buttons to start and stop the ringing.

Starting ‘headless’

The issue covered in this post is that of getting the sound out of the Raspberry Pi (Raspi) using Pygame when running headless. There are at least seven ways of getting a Python script to run headless on a Raspi.   Essentially, these start the script at different stages in the boot process, and of course the basic services of the Pi operating system also start at different stages in the boot process, so depending on when you boot the script, the services may or may not have started, especially as some take a while to initialise.

Starting too soon

I discovered that some of the methods of starting the script did so too early in the boot process.  During the earlier part of the boot process, the user is ‘root’, and although root has comprehensive privileges, it might not have full configuration profiles.  It turned out that Pygame could not find a configuration profile for root, and I could not find a way of making one for root.  The result was that Pygame corrupted the low level audio settings, which I could not fix except by completely re-installing the operating system.  This did have the slight advantage that I now have the latest version of the OS!

pi is the user

So, I needed to find the stage in the boot process where ‘pi’ became the user.  Pygame did have a ‘profile’ for user ‘pi’, so this meant that it could start with the desired audio settings.  I have covered in another blog some of the considerations when initialising Pygame sound.

Following some research, I found that it was straightforward to put it into the desktop boot-up after the regular user has been loaded.  All you need to do is to load the desktop start-up file , as follows:

sudo nano /etc/xdg/lxsession/LXDE-pi/autostart

Then add a line to the end of this file as follows:

@python /home/pi/sirima/sirimarun.py

This is your normal command to start your Python script, so obviously you need to point it to whichever directory holds your Python script.

I have found that this solved the start-up problem and Sirima now runs as intended. I should mention that I had logged onto the machine via the desktop during the process of installing the libraries and software: I can’t guarantee that this would work if the whole installation had been ‘blown’ straight onto the Raspi without any user login.

More Trouble with Raspi Sound

SiRiMa (my bell-ringing machine) was working well on the bench.  Time to try it in the tower.  Firstly, it didn’t seem to want to boot up, but on a second try, it did. So I selected a method, pressed ‘Go’ and the lights flashed in sequence – but not a ding came out of the speaker.  ‘Oh, I expect you’ve got a dry joint’ said a friend.  But whilst my soldering may not always be neat, I thought that was improbable.  After all, I was taught soldering by my mother, who was an expert, this being her job as a member of the WAAF.

Then of course, I realised that the Pi expected to connect to my home Wi-Fi network, which it couldn’t do in the tower.  I’d sort of prepared for this by taking by Microsoft Surface tablet with me.  Using ‘mobile hotspot’ on the tablet, it wanted to connect with a different IP address using the remote desktop protocol, which I managed to do.  I decided to check out the loudspeaker setting in the toolbar.  The speaker (i.e. the on-board jack socket) was turned off!!! What’s more it couldn’t be turned on.  It just gave a message that the AV socket was not available ‘in this profile’.  That was it.

I have at last come to realise that Sirima is being executed by the root user and although there should be a ‘system’ configuration, this may be faulty.  So far I have not been able to identify where.

So more news later, I hope

 

Accurate timing in Python/Pygame on Raspi

My bell-ringing machine needs very accurate timing in order to ring at a precise beat. In a previous post, I mentioned problems with irregularity  in the bell sound which I fixed by keeping the sound data in RAM.  But the sounds need to be triggered accurately, too.

Originally, I just relied on using the Python time.sleep(s) function, where s is a real variable containing the (floating-point) number of seconds and fractional seconds to wait.  Whilst this worked well, it has a disadvantage: whilst the program is sleeping, it can’t do anything else (although the sounds keep playing, because they are running in a background thread). However, a process that is sleeping could be interrupted by by an ‘event’.

Since I have pygame loaded, there is an alternative:
pygame.time.wait(s) – this sleeps the process for s milliseconds.  Here s is an integer in milliseconds, but this doesn’t make it more accurate than the time.sleep() function.

Another alternative is pygame.time.delay()
This uses the processor rather than sleeping, and is said to be more accurate than the wait() function, since the sleep and wait functions can get interrupted by events (which I’m not dealing with here).

Using signals

But there is a further option which I think is superior to any of these for my purposes, and that is the signal function.  I’ve previously written about how it can be used to pause execution of the program until an event occurs (it has many other uses as well).

I’m interested in the timer method, which is called as follows: signal.setitimer(signal.ITIMER_REAL,mydelay)
This sets up the interval timer such that it will issue the SIGALRM signal  after an interval of mydelay which is a floating point number of seconds and fractions of a second.  (There is in fact a similar signal.alarm() function but this only accepts an integer number of seconds, which is no good for me.) When the specified time interval has passed, the function sends the signal SIGALRM to (i.e. it runs) a handler function which you write. This can be very simple, for example:

def myhandler(signum, frame):
  global gap
  gap=False
  return

Set up the signal handler

You have to set up Python with the signal you want it to respond to and the name of the function to handle the signal as follows:

signal.signal(signal.SIGALRM, myhandler)
This needs to be put in the main routine of your program.

You can now use this in either or both of two ways.

The simplest is to set the timer and then make the program wait in a while loop, for example

signal.setitimer(signal.ITIMER_REAL,mydelay)
while gap : continue

This is fine for very short delays.  If the delay may be long, you can pause the program until the alarm is received:

signal.pause()
This sleeps the process until a signal is received and the handler is called.  If you are using something like the very simple handler I have shown above, the program will continue execution with the next statement after the pause command, but the handler has set the flag called gap, which your program could use.

This is better than using a while loop because the software will be sleeping during the wait time, which reduces processor load and hence heat generation.

Note that setitimer can also issue alarms at regular intervals, which could be very useful for producing the regular beat of the bells, although I have chosen not to use it like that.

Pygame events

Pygame has functions to create ‘events’ which are very similar in concept to the signals of Python, and timing functions which are very similar to those in Python.  However, I don’t want or need to get involved in handling Pygame’s events queue, so I haven’t gone that route.

Resolving sound timing problems in Pygame

Pygame

My previous post explained that it’s essential to get even striking of the bells in Sirima, my bell-ringing machine.  I am using Pygame to play the sounds, but I was getting dreadful unevenness in timing that was excruciating to listen to.  I tried changing the timing algorithm, but this made little difference and after much investigation, I concluded that it was originating in the low-level sound playing system. I had great trouble in resolving this, so I am hoping this blog will be helpful to others.

The pygame mixer function provides a huge amount of control over the sound player, but the documentation is terse, with little in the way of examples, and seems to assume a high level of programming knowledge and insight into how the sound player works.  I suspected that the unevenness was to be due to delays in importing the sound from the wav files stored on the Pi’s SD drive, although the documentation warns that having a large buffer can create ‘laggy sound’, whilst having a small buffer can cause scratchy sound and dropouts, whilst not really explaining how the buffer works.

Initialising the mixer

So the first thing I did was to explicitly set the sound mixer as follows:

pygame.mixer.pre_init(44100, -16, 2, 1024)
pygame.init()
pygame.mixer.quit()
pygame.mixer.init(44100, -16, 2, 1024)

What does this do?  Standard Microsoft WAV files are strings of bytes recorded at a rate of 44100 samples per second.  The second parameter (-16) means that the samples are stored as signed numbers 16 bits long (as standard) and the  second shows that there are 2 channels (stereo) and the final parameter is the buffer size.  I originally tried a buffer size of 512, which is probably the smallest practicable size.  This worked well from the desktop, but was scratchy when run from autoboot.  I found a length of 1024 was fine.

I am not sure whether the initialise/de-initialise and re-initialise rigmarole is needed, but some writers did find it was necessary, so I just went with it and haven’t tested that again.

The sound of each of the 12 bells is in a separate WAV file (each between 125 and 250 k long depending on whether it is mono or stereo (which doesn’t seem to make any difference to replay), and lasts about 1.4 seconds. The bells strike at (adjustable) intervals of about 1/3rd of a second, so it takes about 2 seconds for 6 bells to strike. In other words, the repetition rate is about once every 2 seconds.

In order that the sound of one bell doesn’t cut off the sound of the previous bell, I originally put each bell onto a separate channel.  I discovered that this was unnecessary because Pygame handles the channel assignment automatically: all I needed to do was to ensure that Pygame had enough channels so that it did not need to cut off one bell sound to play the next (which it does if there are no spare channels).  The reverberant part of each sound is essential for a proper sound and in fact can last for several seconds, although the long hum is masked by the following strike.  In any case, I could not get explicit channel assignment to work with the sound buffer, although it worked fine when I loaded the wav files each time.

Setting the number of channels

I can have up to 12 bells, so I have set 12 channels for the bells and I use channel 0 for machine announcements. This means that there are 13 channels (it is so easy to forget that numbering starts from 0 in Python!).  The command is:

pygame.mixer.set_num_channels(13)

Playing a sound

The simple way to play a sound from a file is with the command:

pygame.mixer.Sound(“/home/pi/Bells/Sound_1.wav”).play()

This is case-sensitive, and don’t forget the () after each function call, otherwise you’ll get a confusing error message.

As you can see, this loads the file each time from the SD card, interprets it and sends it to the low-level sound system which plays it.

I felt sure that this system sometimes took longer than other times, causing unevenness, and it was clearly unnecessary to keep re-loading the same lot of bytes every time a bell sounded.

I noted that sound could be loaded from a ‘buffer object’, rather than directly from the wav file. The terminology is again confusing, because this buffer object is entirely different from the buffer in the init file.  Presumably this means the sound data is stored in RAM, but I had a lot of trouble finding how to code this correctly.

Differences in timing running from the desktop vs headless start

One big problem was that sound reproduction when running from the desk top via remote desktop (rdp) differed from running via autoboot. I have already mentioned that the headless start needed a larger buffer, but the sound was louder and clearer than running via rdp. However, rdp running did not have the same problems with timing.  It was almost perfect, though perhaps not completely rhythmical when running from the desktop, but sounded dreadfully lumpy when setting it to auto-run on boot-up.  This made testing rather difficult, because once I’d got some coding to work promisingly from the desktop, I then had to put it into autostart and reboot, when it might or might not do what I wanted.

This meant that I worked in quite a number of small steps, taking quite a round-about route.   Of course, it can now be shortened, but here it is in full, which hopefully makes the procedure clear.  Firstly I set up a list called bwav to store the ‘sound objects’ that reference the external wav files, as follows:

bwav = [[‘ ‘] for i in range (13)]

I then loaded the sound objects into the list:
bwav[1] = pygame.mixer.Sound(“/home/pi/Bells/Sound_1.wav”)
bwav[2] = pygame.mixer.Sound(“/home/pi/Bells/Sound_2.wav”)
bwav[3] = pygame.mixer.Sound(“/home/pi/Bells/Sound_3.wav”)
bwav[4] = pygame.mixer.Sound(“/home/pi/Bells/Sound_4.wav”)
bwav[5] = pygame.mixer.Sound(“/home/pi/Bells/Sound_5.wav”)
bwav[6] = pygame.mixer.Sound(“/home/pi/Bells/Sound_6.wav”)
bwav[7] = pygame.mixer.Sound(“/home/pi/Bells/Sound_7.wav”)
bwav[8] = pygame.mixer.Sound(“/home/pi/Bells/Sound_8.wav”)
bwav[9] = pygame.mixer.Sound(“/home/pi/Bells/Sound_9.wav”)
bwav[10]= pygame.mixer.Sound(“/home/pi/Bells/Sound_10.wav”)

bwav now contains a list of ‘sound objects’ rather than mere file references.  These contain more than the bytes of the wav file, and are essential for the next step to work.

We now need to extract the raw numerical sound data from the sound objects, so that we can play it.  So I set up a new list to contain the raw data:

rawdata = [[‘ ‘] for i in range (13)]

Then for the first 10 bells (I don’t have bells 11 and 12 available at present), I extract the raw data from the Sound object using the get_raw function from the mixer module:

for i in range (10):
rawdata[i+1] = pygame.mixer.Sound.get_raw(bwav[i+1])

Note: it is essential that you only use get_raw on valid sound objects.  If you try to extract raw data from one of the empty list elements (by not having the correct range in the for loop) you get another misleading error message!

rawdata now contains the actual sound bytes of each wav file in a form that mixer can play.  The raw sound for each bell is stored as an element in the rawdata list.  This makes it easy to reference each sound.  This list will be stored in RAM so won’t need to be re-loaded from the SD card, and it can be re-used any number of times.

It is now easy to play the sound in the buffer:

pygame.mixer.Sound(buffer=rawdata[bell]).play()

where bell contains the index number of the bell sound to be played. The keyword buffer=rawdata  ensures that Sound knows that rawdata contains raw sound rather than a file reference or some such.

This works beautifully well.

Note that I could NOT get the buffer to work with the Channel () command, as I could not get the play() command to accept the buffer information. Maybe there is a fix, but since mixer handles channel assignment, it is not necessary to deal with it.

I have also done some work on the timing algorithm which I will cover in my next blog.

PyGame Sound Timings

Uneven sound timings

I thought SiRiMa was working very well so set to to boot up headless, rather than starting it via Remote Desktop and using the IDLE interface, which I had been doing.  DISASTER!

It started fine and all the controls did what I wanted, but the sound was uneven.  A fundamental expectation of bell ringing is that the striking must be even.  That is, each bell must sound with a perfect rhythm, evenly spaced, like clockwork.  This is extremely difficult to achieve in a bell-tower, since you have to control the motion of a bell that could weigh over a ton, to a precision of one-twentieth of a second or better.  This is a big topic in itself which isn’t relevant here.

The point is that when running under IDLE, I was getting the precision I wanted, but when starting headless, I would get a couple of rounds correct and then slight gaps would occur.  The gap is very short (possibly 50 ms)  but destroys the rhythm. The place of the gap gradually drifted through the sequence, occurring at first between say the last pair of bells, then drifting down through the sequence until it reach the first pair, then starting at the end again.

After investigation, I came to the conclusion that it is some sort of buffering problem occurring in the low-level sound-playing system.

Pygame

I am using PyGame to play the sounds – I don’t want to grope with the innards of sound generation on the Raspi.  The sound of each bell is in a WAV file and lasts about 1.4 seconds. The bells strike at intervals of about 1/3rd of a second, so it takes about 2 seconds for 6 bells to strike. In other words, the repetition rate is about once every 2 seconds, although this is adjustable. In order that the sound of one bell doesn’t cut off the sound of the previous bell, I have put each bell onto a separate channel.  In PyGame you can set the number of channels you need, and since I can have up to 12 bells, I have set 12 channels for the bells and I use channel 0 for machine announcements.  The gapping problem doesn’t seem to depend on how many bells I am using.

Pygame mixer

Pygame has a lot of functionality for controlling sound: obviously it is necessary, but the significance of differences between some functions is not entirely clear. It says it is based on SDL mixer but I don’t know, and don’t really want to know, anything about SDL.

It tells me that the sound is ‘mixed’ in background threads, which is great because when you have started a sound, control returns to the main program.  So the delay is likely to be partly down to the time taken to load the sound file: these are short files, about 125 k long, but they’re read from the SD card.  It does say that using a smaller buffer size will make the sound less laggy but could result in more scratchy sound and possibly dropouts. Unfortunately there is no guidance on how to assess this.  It seems that the default buffer size has changed a number of times, the most recent being a change from 4096 to 512, which seems rather drastic, and perhaps the smallest that can be managed. I’m not clear whether each channel has its own buffer, but at this stage I won’t change the buffer, as the sound itself is fine.

Buffers

I note that sound can be loaded from a ‘buffer object’.  Presumably this means it is stored in RAM, but I don’t know how it would be created. I will investigate.  I’m aware that a single sound object can be played multiple times, but each instance must be started at a precise time.  I will have to see if this is possible.

I’ll be back when I’ve found something that works!

Using the MCP3008 ADC chip as an input switch

My previous post described my idea of using the MCP3008 Analogue to Digital converter (ADC) chip to detect the input switch settings of my ringing machine.  This chip has eight analogue input channels, and although it does need four GPIO pins on the Raspberry Pi, its resolution is far more than needed to obtain the settings of the 12-position switches I want to use.  In fact I am only using 3 input switches so most of its capability is unused.

You will see that I have connected 470 Ω resistors between each of the switch contacts to form a potential divider.  The circuit diagram is shown below.

potential divider
Potential divider circuit

The ends of the potential divider are connected to the Pi’s 3.3 v supply, to ensure that the voltage on the Pi’s GPIO pins doesn’t exceed this amount. The potential divider has a total resistance of 11 x 570 = 5200 Ω, taking a current of 3.3 /5200 = 635 μA.  I have three of these input switches, thus taking 2­ mA in total (using round numbers as there is tolerance in the components).  This should be well within the Pi’s current capacity. The potential increases by 0.3 v across each successive resistor, well within the resolution of the MCP chip.

The circuit board is very simple as shown in the photo

MCP3008 circuit board
MCP3008 circuit board

I haven’t shown the solder-side of the strip-board, but the strips are mostly straight connections.  The 1 μF capacitor is simply a decoupling capacitor as recommended by the chip manufacturer to prevent noise on the power supply (caused by variable current draw) from affecting the operation of the chip especially on the reference voltage pin.

I am using the Adafruit ADC library to read the MCP chip. To read the 8 channels of the ADC and to convert the readings (which range from 0 to 1023) to values 1 to 12, I use the following:

for i in range(8):
values[i] = int(mcp.read_adc(i)/93+1.5)

The reason for the divisor being 93 is that there are 11 steps between the values 1 to 12 and 1023/11 = 93.  The +1 changes the values to 1 to 12 instead of 0 to 11, and the  +0.5 rounds intermediate values to take account of jitter and tolerance errors in the readings (in fact there is very little).  It works extremely well.

 

 

Designing input switches using the MCP3008 ADC

My plan is to use the MCP3008 ADC (analogue to digital converter) to sense the position of a 12-position switch, by connecting series resistors to act as a voltage divider.  I presumed that the chip would have a high-impedance input, but having found its datasheet, I see  the opposite – it uses a 1 kΩ resistor in series with a 20 pF capacitor to act as the sampling circuit, which holds the charge for a number of clock cycles whilst sampling takes place.  The source impedance needs to be low enough to allow the capacitor to fully charge during one clock cycle.  This can be done by using a slower clock rate, but then the sampling capacitor can discharge before the conversion process is complete!  The datasheet advises that if the source has high impedance, a buffer circuit should be used.

I was planning to use series resistors totalling 10 kΩ.  This would give a time constant of 10,000 x 20×10^-9 = 200 microseconds for the capacitor to reach 63 % of the input voltage, and five times this, i.e. 1mS, to reach 99 % of the input voltage.  But the clock frequency needs to be at least 10 kHz, a clock period of 100 microseconds,  to avoid charge bleeding off the sampling capacitor.

The datasheet shows how to use an op-amp to buffer the input, but this adds a lot of complexity to the circuit.  The datasheet also describes how inaccuracies can be introduced by ground currents and cross-talk and describes ways to design the circuit board to minimise the effects. Similar issues to noise on the sound output!

If I did use a potential divider totalling 10 k Ω, on a 5 v* supply, the current is 500 μA .  I can risk a current of 1 mA, so 5kΩ total resistance for the potential divider would be OK, giving a dissipation of 5mW and a time constant of 100 μs. With 12 positions, the voltage per step is 5/11 = 0.45 v.  The ADC has 10-bit resolution, ie 2^10 = 1024 divisions, but the 10th bit may be inaccurate.  9 bits equates to 512 divisions, so on a  5 v supply, gives a resolution of 10 mV, whereas we only need about 0.5 v resolution, ie less than 4 bit accuracy.  On this basis, I think we can take a risk with, say, 470 Ω resistors between each terminal of the switch, giving a total of  5.17kΩ for the potential divider.

I am not sure how the software controls the clock rate and that is the next thing for me to settle, apart from the rather tricky problem of how to mount everything in the rather small project enclosure!

  • I have realised that since the ADC digital high output is 70 % of the supply voltage, at 5 v supply this is 3.5 v.  The Pi has a maximum voltage of 3.3 v on the GPIO input pins, so maybe I should only use a 3.3 v supply.
Why not use a binary encoder chip?

The MCP3008 needs to use 4 lines  (i.e. 4 GPIO pins) to drive it, which are enough to encode 16 values into binary.  Since I’m encoding a 12-position switch a straightforward binary encoder chip could do this.  But the ADC can encode up to 8 inputs, so it makes better use of the available pins. Indeed one of the ‘switches’ will be a potentiometer used to give a continuous speed scale, which is easier to do with an ADC.

I hope to describe the circuitry in my next post.

 

Trouble with GPIO events and callback

I’ve been using the RPI.GPIO library to detect events on the GPIO pins, but I’ve had a real problem because the callback functions seemed to be unreliable at detecting input events on the pins. [For those unfamiliar with the subject, the GPIO library can be used to create an ‘event’ when the voltage on a pin changes from high to low or vice-versa.  When the event occurs, it causes the program to execute a user function called a callback. But my code was failing to respond to some of the events.]

After much investigation, including searching for explanations on the web (which was not very revealing),  I have realised that although further events are registered, the associated callback is not triggered until the previous callback has terminated. 

There is almost no mention of this in the documentation.  Perhaps it is considered to be too obvious?  I suppose that if this weren’t the case, you could end up with a huge stack of nested callbacks that could fill up the Pi’s stack or memory.

So you have to use the callback to register the event (perhaps by setting a flag) and then return to the main loop as soon as possible.

This just wasn’t obvious to me!  But although I have had to restructure some of the code, I have resolved the issue and can make progress.

Other GPIO Libraries

I had spent some time trying to understand the limitations of RPI.GPIO, checking for updates and investigating other GPIO libraries like pigpio.  I was concerned because the documentation says the RPI.GPIO library is not suitable for real-time programming ‘as the Linux kernel is not suitable’.  But Sirima is not desperately time-critical, i.e. I’m concerned about milliseconds  rather than microseconds.   I also found that some of the documentation is inexact, presumably due to changes in the functions.

Some research shows there are several GPIO libraries, so the question is, should I try a different library?  It would be quite a lot of faffage to change, especially if it still didn’t work.