Bluetooth Controlled Robot - Part 12 Microcontroller Basics (PIC10F200)

Published


Hi! The time has come to have some fun! After the “meaty” tutorial devoted to the UART, we need to relax and make something interesting. Thus, I decided to show you how you can make a remotely controlled robot via Bluetooth.

As you probably already assumed, we can’t use Bluetooth directly with the PIC10F200 microcontroller. So we need some sort of converter. Fortunately there are plenty of Bluetooth-to-UART converters which we can use. They operate like USB-to-UART converters in the sense that the microcontroller sends and receives the data via the typical UART, and this module makes all conversions and transforms the data into the correct Bluetooth packet which can be recognized by electronics like your smartphone, for instance. These converters hide all the Bluetooth stuff and make both devices communicate like they are simply connected with a UART cable.

Among all converters I’ve used, I’d recommend using the HC-06 module. You can see what it looks like in figure 1. It’s relatively inexpensive (price is normally $2-3 at Aliexpress) and quite reliable.

Figure 1. HC-06 Module.
Figure 1. HC-06 Module.

On the microcontroller side this module acts like the usual UART device. So you connect the RXD of the module to the Tx of the microcontroller, and TXD of the module to the Rx of the microcontroller. But please note - this module has the UART level of 3.3V, not 5V. In our application, this fact doesn’t make any difference because we will only use the TXD pin of the module. This pin will produce a signal with the amplitude of 3.3V but it’s OK because, according to the datasheet, the logical ‘1’ for the PIC10F200 starts at 2V. But if you need to send data from the microcontroller to the HC-06 module, you will need to use a voltage divider so as not to damage the RXD input.

The HC-06 board has a power voltage converter so the supply power can be applied in the range of 3.6 to 6.0 V which makes life a bit easier for us.

To make the robot move we will need two motors. As the PIC10F200 has very few pins, we can’t use the normal DC motors, as each of them needs two pins to control the direction of the rotation. So, we will use the servo motors. I hope you remember them from Tutorial 10 (creating a code lock). I told you that they have a limited range when spinning - just 180 degrees. But fortunately there are versions of servos that have a full 360 degrees rotation range. They are a bit more expensive than usual servos and less widespread but you still can find them. If you don’t, then there is a way to hack the usual servo and break the limiter which prevents its full rotation. You also trade the ability to control exactly where your motor ends up for the ability to go in a complete circle.

The last thing you will need is the chassis where you will assemble all the parts. If you are lucky enough that you have a 3D printer, I’d recommend you make this cool robot called SMARS. It was designed to be used with DC motors but you can find the chassis and wheels version for the SG90 servos here and print it instead of the original one. All other parts can be taken from the original design.

The fully assembled robot is shown in figure 2.

Figure 2. SMARS Robot.
Figure 2. SMARS Robot.

The “head” and the “tail” are not needed for this but we will use them in following tutorials, so I’d recommend printing them too.

If you don’t have a 3D printer you can buy a ready-made chassis on eBay or AliExpress (search “Arduino car chassis”), or even cut it by yourself of the plywood, acrylic, or textolite.

Now let’s present the task for today. The above mentioned robot should receive and execute 5 commands - “Move forward”, “Move backward”, “Rotate left”, “Rotate right”, and “Stop moving”. Each command is represented by one character sent via UART to the microcontroller from the HC-06 module.

The schematic diagram of the radio-controlled robot is shown in figure 3.

Figure 3. Schematic diagram of the Bluetooth-controlled robot.
Figure 3. Schematic diagram of the Bluetooth-controlled robot.

As you may notice, we connect more and more to the microcontroller. So DD1 is the PIC10F200 microcontroller itself. X1 is the programmer/debugger as usual.

X2 represents the HC-06 module. You can see that we power it from the programmer during programming, and only attach the TXD pin to the GP1 input of the microcontroller.

X3 and X4 are known from Tutorial 10 as servo motors - left and right ones correspondingly.

The other new thing is the battery GB1. I used three AA batteries connected in series to get the 4.5V total. This is a normal voltage for all the devices used in the device. Apparently, the battery is used only on the working device, and the programmer should be disconnected in this case (which is obvious because - why would we need to pull it behind the robot?)

Let’s now consider the code for this robot.

#include "p10f200.inc"

__CONFIG _WDT_OFF & _CP_OFF & _MCLRE_OFF

i   EQU 10 ;Delay register

rx_data EQU 12 ;Received byte

count   EQU 13   ;Bit counter for UART communication

servo1   EQU 14   ;Servo1 pulse width

servo2   EQU 15   ;Servo2 pulse width

ORG 0x0000    

INIT

MOVLW ~((1<<T0CS)|(1<<PSA)|(1<<PS0))

OPTION      ;Enable GP2, assign prescaler to Timer, set prescaler 128

    MOVLW ~((1 << GP0) | (1 << GP2))    

    TRIS GPIO   ;Set GP0 and GP2 as outputs

    CLRF servo1   ;Clear 'servo1' to stop servo 1

    CLRF servo2   ;Clear 'servo2' to stop servo 2

LOOP

    BTFSC servo1, 3                 ;If servo 1 is not stopped

    CALL CONTROL_SERVO   ;then set the pulse width for the servos

WAIT_RX

    BTFSS GPIO, GP1               ;Check the GP1 level, if 1 then skip next line

    CALL RX_BYTE                 ;Otherwise receive the byte

    GOTO LOOP   ;Return to the 'LOOP' label

;-----------------------------------------------------------------

CONTROL_SERVO   ;CONTROL_SERVO subroutine

    MOVF TMR0, W   ;Copy the TMR0 register into W

    BTFSS STATUS, Z   ;Check if it is 0 using Z bit of STATUS register

    GOTO SET_PULSE   ;If TMR0 is not 0 then move to SET_PULSE label

    BSF GPIO, GP0   ;Otherwise set GP0 high

    NOP

    BSF GPIO, GP2   ;and set GP2 high

SET_PULSE    

    MOVF servo1, W   ;Copy 'servo1' into W

    XORWF TMR0, W   ;Compare it with TMR0 value

    BTFSC STATUS, Z   ;If 'servo1' = TMR0

    BCF GPIO, GP0   ;Then set GP0 low

    MOVF servo2, W   ;Copy 'servo2' into W

    XORWF TMR0, W   ;Compare it with TMR0 value

    BTFSC STATUS, Z   ;If 'servo2' = TMR0

    BCF GPIO, GP2   ;Then set GP2 low

    RETLW 0

;-----------------------------------------------------------------

RX_BYTE   ;Beginning of the RX_BYTE subroutine

    MOVLW ~((1<<GP0)|(1<<GP2));Set servo outputs low

    MOVWF GPIO   ;to prevent the prolonged pulse

    CALL HALF_UART_DELAY;Delay to the middle of the bit interval

    BTFSC GPIO, GP1   ;If the GP1 bit is not 0

    RETLW 0   ;then error of the Start bit, and return

    CLRF count   ;Else clear 'count' register

    CLRF rx_data   ;and clear 'rx_data' register

SHIFT_RX_DATA   ;Start receiving the data byte

    CALL UART_DELAY     ;Call one bit delay

    RRF rx_data, F   ;Shift the 'rx_data' one bit to the right

    BTFSC GPIO,GP1   ;If GP1 bit is 1

    BSF rx_data, 7   ;then set the MSB of the 'rx_data' to 1

    INCF count, F   ;Increment the counter

    BTFSS count, 3   ;and check if it is 8

    GOTO SHIFT_RX_DATA    ;If it is not then return to the 'SHIFT_RX_DATA'

    CALL UART_DELAY     ;Otherwise call one bit delay

    BTFSS GPIO, GP1   ;And check the stop bit

    RETLW 0   ;if the GP1 is not 1 then return

CHECK_LEFT   ;Check Left button

    MOVLW '1'   ;Load the '1' into the W register

    XORWF rx_data, W    ;And perform the XOR between W and 'rx_data'

    BTFSS STATUS, Z   ;If result is not 0 (rx_data != W)

    GOTO CHECK_FWD   ;then check the next button

    MOVLW D'10'   ;Otherwise load value 10

    MOVWF servo1   ;into both 'servo1'

    MOVWF servo2   ;and 'servo2'

    RETLW 0

CHECK_FWD   ;Check Forward button

    MOVLW '2'   ;Load the '2' into the W register

    XORWF rx_data, W    ;And perform the XOR between W and 'rx_data'

    BTFSS STATUS, Z   ;If result is not 0 (rx_data != W)

    GOTO CHECK_RIGHT    ;then check the next button

    MOVLW D'10'   ;Otherwise load vakue 10

    MOVWF servo1   ;into 'servo1'

    MOVLW D'12'   ;and load value 12

    MOVWF servo2   ;into 'servo2'

    RETLW 0

CHECK_RIGHT   ;Check Right button

    MOVLW '3'   ;Load the '3' into the W register

    XORWF rx_data, W    ;And perform the XOR between W and 'rx_data'

    BTFSS STATUS, Z   ;If result is not 0 (rx_data != W)

    GOTO CHECK_BKWD   ;then check the next button

    MOVLW D'12'   ;Otherwise load value 12

    MOVWF servo1   ;into both 'servo1'

    MOVWF servo2   ;and 'servo2'

    RETLW 0

CHECK_BKWD   ;Check Backward button

    MOVLW '4'   ;Load the '4' into the W register

    XORWF rx_data, W    ;And perform the XOR between W and 'rx_data'

    BTFSS STATUS, Z   ;If result is not 0 (rx_data != W)

    GOTO CHECK_STOP   ;then check the next button

    MOVLW D'12'   ;Otherwise load value 12

    MOVWF servo1   ;into 'servo1'

    MOVLW D'10'   ;and load value 10

    MOVWF servo2   ;into 'servo2'

    RETLW 0

CHECK_STOP   ;Check Stop button

    MOVLW '9'   ;Load the '9' into the W register

    XORWF rx_data, W    ;And perform the XOR between W and 'rx_data'

    BTFSS STATUS, Z   ;If result is not 0 (rx_data != W)

    RETLW 0   ;then return from the subroutine

    CLRF servo1   ;Otherwise clear 'servo1'

    CLRF servo2   ;and clear 'servo2'

    RETLW 0

;--------------------------------------------------------------

UART_DELAY      ;Start UART_DELAY subroutine here

MOVLW D'29'  ;Load initial value for the delay    

MOVWF i  ;Copy the value to the register i

DELAY_LOOP_UART         ;Start delay loop

DECFSZ i, F  ;Decrement i and check if it is not zero

GOTO DELAY_LOOP_UART;If not, go to the DELAY_LOOP_UART label

RETLW 0  ;Else return from the subroutine

HALF_UART_DELAY ;Start HALF_UART_DELAY subroutine here

MOVLW D'16'  ;Load initial value for the delay    

MOVWF i  ;Copy the value to the register i

DELAY_LOOP_HALF         ;Start delay loop

DECFSZ i, F  ;Decrement i and check if it is not zero

GOTO DELAY_LOOP_HALF;If not, go to the DELAY_LOOP_HALF label

RETLW 0  ;Else return from the subroutine

END

Let’s see what we’ve done here.

As usual, we make some definitions in the beginning. ‘i’, ‘rx_data’, and ‘count’ play the same role as in the previous tutorial. The ‘servo1’ and ‘servo2’ registers are used to set the pulse width of the right and left servos correspondingly.

In the initialization part there are some differences caused by using Timer0 in the current program. You you remember, when I described the PIC10F200 microcontroller many tutorials ago, I mentioned that it has the timer, and that we would use it later. Well, the time has come - let’s use it.

Why do we need it? Because there are two time-dependent concurring parts - servos and UART. And it would be good to separate them a bit. So we will control the servos using the timer, and control UART reception using delays.

In line 12 we have MOVLW ~((1<<T0CS)|(1<<PSA)|(1<<PS0)). Let’s see what that means.

I’ve already said what ‘T0CS’ means, and we usually used it to “enable the GP2 pin”. Let’s now consider it from the perspective of Timer0. So if the ‘T0CS’ (Timer0 Clock Source) bit is set to ‘1’ (default value) then the timer counts external pulses that come to the T0CKI pin which is multiplexed with the GP2 pin. And if ‘T0CS’ bit is ‘0’ then the timer increments its value on transition of the internal clock source Fosc/4 (which is 4MHz/4 = 1MHz). So when we reset the ‘T0CS’ to zero we both enable the GP2 pin and make the Timer0 run from the internal clock. Yep, this entire time, Timer0 has been counting in almost all our programs, and we neither knew about it nor used it. But as I said, today we will fix that.

The ‘PSA’ (Prescaler Assignment Bit) bit selects to what timer the prescaler will be assigned - to Timer0 (when PSA = 0) or to the watchdog timer (when PSA = 1, default value). Prescaler is the clock divider which allows the timer to run slower. This is very useful to us because if the timer’s frequency is 1MHz, and the timer is 8 bit, then the whole timer period is just 256us. But to control the servo, we need a period of at least 20 ms. So we need to slow down the timer’s frequency, and we will do this via the prescaler.

There are three bits to control the prescaler called PS2, PS1, and PS0. The prescaler rates applied to Timer0 are shown in the table below:

I’ve already calculated for you that the prescaler rate of 128 suits us because in this case we can get a period of 1us * 128 * 256 = 32768 microseconds which is about 33ms. This is the best for us, as the previous value gives us 16ms (which is too few), and the next value gives 66ms (which is too much). So we need to set PS2 and PS1 to ‘1’ and clear PS0 to ‘0’. By default all PSx pins are set to ‘1’ so we need to only reset the ‘PS0’ bit which we do in line 12.

All these bits belong to the OPTION register, so we copy them in with line 13. After implementing these two lines Timer0 will start to work with the frequency of 1000000/128 = 7.8 kHz (each timer tick is 128us, this value will be important for us!).

In lines 14-15 we configure the GP0 and GP2 pins as outputs as servos are connected to them.

In lines 16-17 we clear registers ‘servo1’ and ‘servo2’. The logic of the program is that these registers are 0, so no pulses applied to the servos, and they are stopped and disabled so you can rotate the wheels manually.

Actually we implement this logic in line 19 which is the first line of the main loop. We check the third bit of ‘servo1’ (I’ll explain later why so), and if it’s 0 then we skip ‘CONTROL_SERVO’ subroutine, and don’t apply pulses to the servos. Otherwise line 20 is not skipped, and we call the ‘CONTROL_SERVO’.

Then we check if there is any activity on the Rx pin of the microcontroller and receive the byte if the start bit is detected (lines 21-23, which are the same as in the previous Tutorial).

Let’s move to line 26 and consider the ‘CONTROL_SERVO’ subroutine.

First, we move the content of the ‘TMR0’ register into the Working register using the MOVF instruction (line 27). The ‘TMR0’ register is the Timer0 counting register, and its value is incremented by Timer0 automatically.

As you remember the MOVF instruction affects the Zero bit of the status register, so we can check this bit and find out if the value of ‘TMR0’ is 0 (line 28). If the value of the ‘TMR0’ is not 0 then line 29 is skipped and we go to line 30. There we set the GP0 bit high, and after one-cycle delay caused by the NOP instruction (line 31) we set the GP2 bit as well (line 32). I mentioned in one of the previous tutorials that if you apply read-modify-write instructions (like BSF or BCF) to the GPIO register, you need to separate them with a small delay to let the pin voltage physically change.

So the point of lines 27-32 is to set the GP0 and GP2 pins high when TMR0 = 0. After that, we will keep checking the timer’s value, and when it reaches the required values, we will set GP0 and GP2 low and end the pulse.

In line 34 we copy the value of ‘servo1’ into the W register. Then we perform the XOR operation between the TMR0 and W (line 35) to check if TMR0 = servo1 (line 36). If they are equal we set the GP0 pin low (line 37).

Lines 38-41 are the same as lines 34-37 but there we check if TMR0 = servo2 and set the GP2 low if it’s true.

So by means of the timer we get stable pulses with a stable period which are applied to the servos. The timer frequency is 128 times lower than the microprocessor frequency, and this means that we 100% will not lose the required timer value. And even the UART reception, which takes about 1ms, the timer will not interfere with us.

Now let’s consider what’s going on when we receive the byte via the UART. The beginning of the ‘RX_BYTE’ subroutine (lines 44-62) is absolutely the same as in the previous tutorial, so I’ll skip it. Yay reusable modular code! We will move to line 63 where we find the label “CHECK_LEFT”.

As expected from its name, this part checks if the “left'' button was pressed, and if yes, the robot starts to rotate counterclockwise.

First, we check if we received the value ‘1’, as this value represents the command “Left” (lines 64-67). These lines are the same as in the previous tutorial so we will not stop on them either. If we received ‘1’ then we finally move to line 68, where we load the value 10 into the W register and then copy it to the registers ‘servo1’ (line 69) and ‘servo2’ (line 70).

Let’s consider this part in more detail. As I mentioned earlier the timer’s tick is 128us. Also, you should remember that the servo accepts pulses from 1 to 2 us.

When we load the value 10 into ‘servo1’ register we will compare the TMR0 value with it and finish the pulse when we reach this value. As we start the pulse when TMR0 = 0 we have 11 ticks till it reaches value 10 which gives us the pulse duration of 11 x 128us = 1408us. Which is 1.4ms. It’s also easy to calculate that for the value 11 the pulse width is (11 + 1) x 128 = 1536us, and for the value 12 it is (12 + 1) x 128 = 1664us.

What do these calculations give us? The most important thing is that if we load the value 11 into the ‘servo’ register this will give us the pulse width of about 1.5ms. This pulse duration corresponds to the center position of the shaft in the typical servo. But in a rotating servo this is a stall state, where it simply holds its position. So if we give pulses with exactly 1.5ms, the servo shaft will not move. If we set the value of 1-1.5ms then it will rotate counterclockwise, and the more of a difference between the pulse duration and 1.5ms, the higher the rotation speed. Similarly, if we set the value of 1.5-2ms then it will rotate clockwise.

So when we load the value 10 into the ‘servo1’ and ‘servo2’ registers we make the both servos rotate counterclockwise, and as they are installed on opposite sides of the chassis this will lead to the whole robot rotating counterclockwise in one place. Unfortunately, you can only notice the difference in the rotation speed in the pulse range of 1.4 to 1.6 ms. Any bigger difference between the pulse width and 1.5 ms will not give any noticeable speed change. So using the timer we can consider this speed as the only applicable, of 1.4ms or 1.6ms.

The other buttons are similar, so we will consider them very quickly. The “Forward” button has the value ‘2’ and is proceeded in the lines 72-81. To move forward, we load the value 10 into ‘servo1’ and the value 12 into ‘servo2’. So the right servo will rotate counterclockwise and the left servo will rotate clockwise, and this will lead to moving the robot forward.

The “Right” button (lines 82-90) has the value ‘3’ and is opposite to the “Left” button.

The “Backward” button (lines 91-100) has the value ‘4’ and is opposite of the “Forward” button (obviously).

The “Stop” button (lines 101-108) has value ‘9’ (surprise!). We clear both ‘servo1’ and ‘servo2’ registers to stop the robot and disable the servos.

And that’s all. Quite simple, isn’t it? We previously learned how to work with servos and the UART, and now we just joined our knowledge to make a robot.

Now let’s do some practice. First assemble and download the code into the microcontroller. Then assemble the mechanical and the electronic parts of the robot. And finally power up the robot with the battery. You should see that the LED on the HC-06 module blinks, which means it’s not connected.

Now take your smartphone (I have an Android one, so I’ll show how to setup it). Enable the Bluetooth and open the Bluetooth settings. Then search for the new devices. You should see the HC-06 device among the Available devices.

Select it and wait while it asks for the PIN code. HC-06 usually has the PIN 1234. Enter it and press OK.

Then your HC-06 device will appear in the Paired devices.

Now you need to install the virtual gamepad application to control the robot. I used the ‘Arduino Bluetooth Controller’ by Giumig Apps.

When you download and open the app, it will ask which device you want to connect to. You should choose the HC-06 from the list.

When the connection is established successfully, the LED on the HC-06 module should stop blinking and keep ON. Also the app will ask you what mode you want to select. You need to choose Controller mode.

Then you will see a Playstation-like controller.

Press the Settings button in the right top of the screen and set the codes of each button, I set them just consequently from 1 to 9, and 0 last.

Now you understand why we used these values for the corresponding buttons. You can return to the controller screen and try to control your robot using the joystick in the left and X button to stop the car. It will be hard initially as the robot doesn’t finish the last maneuver until you give it the new command. But in five minutes you will get used to it and even have fun, which, as you remember, was our goal for today.


And that’s all for today. Now let’s have some traditional statistics. The program just uses 94 words of flash memory. We haven’t learned any new commands again but we are now familiar with the Timer0 which can be helpful in those applications with concurring time-dependent parts.

As homework, I’d suggest you modify your car on your own. There are still 5 unused buttons so you can do whatever you want. Add new maneuvers, for instance. Also you can move the Rx pin from GP1 to GP3 pin and connect the LED or buzzer to the GP1 and control it as well. But be careful in this case and always disconnect the HC-06 module before connecting the programmer, because 12V on GP3 pin will burn the HC-06 module!

Next time I’ll show you how to use the “tail'' of the robot to make it follow a line. Line following robots are all the rage nowadays.

Have fun and share your masterpieces in the comments!

Related Tutorials


Terms Used

Make Bread with our CircuitBread Toaster!

Get the latest tools and tutorials, fresh from the toaster.

What are you looking for?