PICAXE 14M2 controls a homebrew bluetooth speaker

Years ago, I bought a small bluetooth soundbar from Logitech. One thing I didn't like was that whenever it ran on battery, the bass was gone.

So I decided to build my own bluetooth speaker.

I've actually gone through several iterations, but all except for the prototype have the same PICAXE circuit that takes care of power management.

What I wanted the PICAXE to do is:
1) monitor battery levels for the built-in battery (a small AGM lead-acid battery, for simplicity of use)
2) turn power to the amplifiers and the bluetooth receiver on and off
3) provide a sleep timer that shuts the whole box down after one hour
4) display battery state on a four LED meter (two green, yellow, red)
5) Shut everything down if the battery voltage goes too far down, and display a warning on the battery meter

For what I needed, I went to the PICAXE 14M2, because it had the necessary number of inputs and outputs.

The program I wrote up basically works like this:
1) after power on, turn all battery indicator lights on for a second as a sort of test
2) measure battery state. If battery voltage is too low, display "battery fail state" on the red LED
3) if battery state is sufficient, light up the appropriate LEDs for battery state. The program will then stay there, since the battery will not recover on its own.
4) Energize the relay that sends power to the amps and the volume/bass/treble control board.
5) Switch power to the bluetooth module on.
6) keep monitoring the battery state. The display will only change once ten measurements in row are different from the previous state to prevent flicker from bass pulses dipping the voltage.
7) if sleep button is pushed, start sleep timer. If sleep timer is running, red power LED will flash slowly to show that the timer is running. After one hour, turn all power outputs off. Red LED keeps flashing so I don't forget to throw the main switch to off at some point
8) if sleep button is pushed while the timer runs, timer is cancelled.
9) if the box has fallen asleep, pressing the sleep button will wake it again.
Dear Ibenson,

I can provide code and pictures of the circuit board soldered on stripboard. I need to get around to it, though. I'm afraid it might take me at least till Friday.

I've posted a series of videos on how I built the speaker on Youtube. You can find the first one here: https://youtu.be/ovLgpn_cyYA

The picaxe circuit is soldered up and set up in part ten (around 8 minute mark): https://youtu.be/ASqu7qMcc2M

I had to go back to the board later on, because I made a few mistakes (misplaced parts by one row) that cause some strange behaviour.
The code I used

Nice project; thanks for sharing.

Can you provide a photograph? Schematic? Code?

The 14M2 is a powerful little chip.
This is the code. It contains a lot of comments. Please don't mind that I put "Test version" in the beginning, this is the version that is actually running in the box right now.

#picaxe 14m2
;Boombox battery protector
;Test version - work in progress
;Pin layout
;				V+		V0	
;	PC Serial In     	C.5		B.0 	PC Serial out	
;	ADC in		C.4		B.1	MOSFET out - amplifier
;				C.3		B.2	
;	LED red		C.2		B.3	Sleep button
;	LED yellow		C.1		B.4	LED green high
;	LED green low	C.0		B.5	MOSFET out - Bluetooth
;Table of variables
;w0		b0	flags
;w0		b1	
;w1		b2	all purpose byte	
;w1		b3	
;w2		b4	low_thresh
;w2		b5	low_thresh
;w3		b6	strikes
;w3		b7	hours_suspend
;w4		b8	voltvalue
;w4		b9	voltvalue
;w5		b10	count_red
;w5		b11	count_yellow
;w6		b12	count_green
;w6		b13	count_green_high
;w7		b14	count_button
;w7		b15	count_flasher
;w8		b16	
;w8		b17	
;w9		b18	mid_thresh
;w9		b19	mid_thresh
;w10		b20	high_thresh
;w10		b21	high_thresh
;w11		b22	
;w11		b23	
;w12		b24	
;w12		b25	
;w13		b26	
;w13		b27	
;Table of flags
;bit0		sleepmode, status; 1= suspend
;Memory locations:
;	not used
	symbol sleepmode=bit0	;1=suspend; 0=normal
	symbol voltage=c.4		;adc in
	symbol ext_in=pinc.3
	symbol LED_red=c.2
	symbol LED_yellow=c.1
	symbol LED_green=c.0
	symbol ampout=b.1
	symbol sleepbutton=pinb.3
	symbol LED_green_high=b.4
	symbol btout=b.5
	symbol apb=b2
	symbol low_thresh=w2		;suspend voltage
	symbol strikes=b6			;number of strikes
	symbol voltvalue=w4
	symbol hours_suspend=b7
	symbol count_red=b10
	symbol count_yellow=b11
	symbol count_green=b12
	symbol count_green_high=b13
	symbol count_button=b14
	symbol count_flasher=b15
	symbol mid_thresh=w9
	symbol high_thresh=w10 		;restart voltage
	;potential divider 1/3
	;15 volts will read as 5 volts
	;0 volts will read as 0 volts
	;>12 Volts (two green) => 	4,13 volts		>816
	;12,0 Volts (one green) => 	4,06 volts		816
	;11,5 Volts (warning)=>		3,83 volts		782
	;11,1 Volts (shutdown) =>	3,70 volts 		755
	;full voltage at PICAXE: 5 Volts
	let low_thresh=755		;about 11.0 volts
	let mid_thresh=782		;about 11.3 volts
	let high_thresh=816		;about 11.8 volts
	enabletime  ;make sure timer is running
	high led_green_high, led_green, led_yellow, led_red		;check lights
	pause 1500	;1.5 second pause
	low led_green_high, led_green, led_yellow, led_red
	low ampout, btout		;shut down power
					;will be enabled if voltage is sufficient
	gosub Measure
	if count_green_high>0 or count_green>0 or count_yellow>0 then
		high btout	;switch on bt receiver first
		pause 250
		high ampout	;switch on amp

	gosub control_lights

	gosub Measure		;check voltage
	if count_green_high>20 or count_green>20 or count_yellow>20 or count_red>20 then
		gosub control_lights	;only change lights if 20 consequtive measures show consistent value
						;this will avoid flickering if the load is fluctuating
		if count_red>20 then goto Power_fail	;LED is red then - system powers down				
		;reset all voltage counters to avoid counter overflow if the same state persists
		let count_green_high=0
		let count_green=0
		let count_red=0
		let count_yellow=0
	if sleepbutton=1 and count_button=0 then
		let count_button=3	;for debounce
		if sleepmode=1 then
			let sleepmode=0		;clear sleepmode flag
			high led_red		;tur on red led right now to show that button press has registered
			let sleepmode=1		;set flag for sleepmode
			let count_flasher=0	;reset the flasher count
			let time=0			;reset timer if sleepmode is entered

	if sleepmode=1 and time>=3600 then		;one hour has passed in sleep mode
		low ampout		;turn off amp first
		pause 500		;wait half a sec
		low btout		;turn off bt receiver and fans then	
		low led_green_high, led_green, led_yellow	;turn these LED off in sleep mode
		goto Awaken_from_sleep

	if sleepmode=1 then
					;this will flash in sleep mode to show that program
					;will fall asleep at some point
		inc count_flasher
			select case count_flasher
				case 1
					low led_red
				case 10
					high led_red
				case >15
					let count_flasher=0
			end select		

	pause 250	;wait quarter second before cycling
	if count_button>0 then	;debouncer that doesn't interfere with timing
		dec count_button

	goto Main_Circle

	;measure input from voltage divider

	readadc10 voltage, voltvalue
	;for development: send values to PC	
	;sertxd ("ADC: ",#voltvalue,13,10)	
	select case voltvalue
		case < low_thresh				;power failing
			inc count_red
			let count_yellow=0
			let count_green=0	
			let count_green_high=0
		case low_thresh to mid_thresh		;power low, but still working
			let count_red=0
			inc count_yellow
			let count_green=0
			let count_green_high=0
		case mid_thresh to high_thresh	;power pretty good
			let count_red=0
			let count_yellow=0
			inc count_green
			let count_green_high=0
		case >high_thresh				;power excellent
			let count_red=0
			let count_yellow=0
			let count_green=0
			inc count_green_high
	end select	



	if count_red>0 then
		if sleepmode=0 then	;leave red light to sleepmode if triggered
			high led_red
		low led_green_high, led_green, led_yellow

	if count_yellow>0 then
		if sleepmode=0 then	;leave red light to sleepmode if triggered
			high led_red
		high led_yellow
		low led_green, led_green_high

	if count_green>0 then
		if sleepmode=0 then	;leave red light to sleepmode if triggered
			high led_red
		high led_green, led_yellow
		low led_green_high

	if count_green_high>0 then
		if sleepmode=0 then	;leave red light to sleepmode if triggered
			high led_red
		high led_green_high, LED_green, led_yellow


	low led_green_high, led_green, led_yellow
	high LED_red
	low ampout		;turn off amp first
	pause 500		;wait half a sec
	low btout		;turn off bt receiver then

	;no dead end here - once the amp relay drops off, the battery can be recharged. 
	;If the charge reaches "green" again, restart by sleep button is permitted.
	;otherwise, program will restart "naturally" on shutdown (power cut by switch) and power-on

	gosub Measure	;take measurement in "low power mode"
	if count_green>=5 then 
		gosub control_lights	;will switch on "green"
		goto awaken_from_sleep	;only allow restart once 5 readings show "green"
	pause 1000		;wait 1 secs - power has failed anyway, so there is no hurry
	goto power_recheck
	;this will run once program has detected power failure and sufficient recharge
	;or has fallen asleep after sleep button has been pressed
	;This part may only be reached from Main_Circle and by "goto", never by "gosub"
	;to avoid stack overflow
	low led_red		;shut this down - will be powered up shortly
	let sleepmode=0	;just in case program went here after falling asleep
	let count_flasher=0
	if sleepbutton=1 then
		let count_button=10	;disable button for 10 cycles
		let count_green_high=0
		let count_green=0
		let count_red=0
		let count_yellow=0
		goto Restart_here
	inc count_flasher
		select case count_flasher
			case 1
				high led_red
			case 6
				low led_red	
			case >30	
				let count_flasher=0
		end select		
	pause 100
	goto Awaken_Cycle		;if no wakeup is triggered - go back
Last edited:
...and I actually just read that the code could recover from a power failure, which is really silly. Since it will only recover if the lower "green" threshold is reached, it will in my experience not bounce back from power fail. The way I set up the relay, as soon as it drops off, the charging jacks in the back of the box are connected and can be used to charge. In theory, the system could bounce back on after a sufficient recharge. Since I never run it down to power failure, I never figured to remove this from the code.

The reason it's there is because I reused a code I wrote for a solar power station to cut and reconnect power after the solar cells have recharged the batteries. Ah well... I hope you don't mind, I guess nobody is perfect.


Senior Member
Good for you for all the comments. I like your "table of variables". If your code had block indentation (for instance, within IF and SELECT blocks), it will show in the posting if you go back and edit it to have the tags [ code] and [ /code] at the beginning and end (without any spaces).
Thank you, Ibenson. I followed your hint and now the code looks a lot better - of course I had used indentation, it just hasn't shown up. I've edited the original post, so the "bad" code is no longer there.


Senior Member
Much easier for forum followers to read, now, with your block indentation showing as you intended.

Good project--especially for an initial posting.