Monday, September 25, 2017

Remote controlled USB mouse using PIC18F4550


Building a USB mouse using PIC18F4550 microcontroller and CCS C compiler is easy as shown in the link below:
USB Mouse using PIC18F4550 microcontroller
Also, it is not hard to add an infrared remote control to the previous USB project. This post shows how did I build a simple IR remote controlled USB mouse using PIC18F4550 microcontroller and Car MP3 IR remote control (NEC protocol).
The link below shows how to decode NEC IR remote control (remote controls may not have the same code messages, so to know the code of each button we've to decode it first):
NEC Protocol decoder with PIC16F887 microcontroller
Hardware Required:
  • PIC18F4550 microcontroller
  • IR remote control (NEC protocol)
  • IR receiver
  • 8MHz Crystal Oscillator
  • 2 x 22pF capacitors
  • 47uF polarized capacitor
  • 470nF Capacitor
  • 10K ohm resistor
  • USB Connector
  • +5V Power Supply (USB VCC can be used)
  • Breadboard
  • Jumper Wires
Remote controlled USB mouse using PIC18F4550 circuit:
Remote controlled USB mouse using PIC18F4550 circuit 
The IR receiver has 3 pins: GND, VCC and OUT. The OUT pin is connected to RB0 (INT0 pin).
The circuit shown above is powered from an external 5V source which means there is no need to connect the VCC pin of the USB connector, but this pin can be used to supply the circuit and therefore the external 5V source has to be removed.
In this project the microcontroller runs with 8 MHz crystal oscillator and MCLR pin function is disabled.
Remote controlled USB mouse using PIC18F4550 C code:
The C code below was tested with CCS C compiler version 5.051.
In this project the PIC18F4550 MCU runs with 8 MHz crystal oscillator and it is configured so that the MCU runs at 8 MHz. I enabled PLL2 for the USB module in order to get 48 MHz which allows the USB module to run at full speed (12 Mbps).
The output of the IR receiver is connected to the external interrupt 0 pin (RB0/INT0) and with that I used it to decode the IR remote control. The NEC code massage is 32-bit long (2 words).
I used Timer1 to measure pulse and space widths and I used Timer1 interrupt to reset the decoding process in case of very long pulse or very long space (time out). Timer1 is configured to increment every 1 us. So for example for the 9 ms pulse I used the interval between 8500 and 9500.
I used Timer3 with the repeated remote control codes. If a button is pressed with hold the remote control will send the button massage code and then it sends 9 ms pulse --> 2.5 ms space --> 562 us pulse and so on every 110 ms. (more details at: http://www.sbprojects.com/knowledge/ir/nec.php)
From previous if a button is pressed with hold it will continuously sets Timer3 to the value 25000 ( set_timer3(25000); ). When the button is released Timer3 will overflow and its interrupt will reset the code message ( nec_code = 0; ).


The function usb_put_packet(1, out_data, 4, USB_DTS_TOGGLE); sends a packet of 4 bytes from the array out_data over USB.The variable array out_data is a 4-byte array where:
Byte 1 corresponds to button status. The left button of the mouse is represented by 1 and the right button is represented by 3.
Byte 2 and byte 3 correspond to X axis and Y axis directions respectively.
Byte 4 for the mouse wheel which is not used in this project.
The X axis and Y axis bytes are signed int8 which can vary from -127 to 127. The ( - ) sign indicates direction ( for X axis left/right and for Y axis up/down) and the number means number of steps at a time.
The remote control which I used is shown below:
Car MP3 IR remote control
I used the buttons numbered from 1 to 6 where:

Button Number Function Code
1 Left button 0x40BF30CF
2 Move cursor up 0x40BFB04F
3 Right button 0x40BF708F
4 Move cursor left 0x40BF08F7
5 Move cursor down 0x40BF8877
6 Move cursor right 0x40BF48B7

The complete C code is below.
// IR Remote controlled USB mouse using PIC18F4550 microcontroller CCS C code.
// Car MP3 IR remote control is used (NEC protocol)
// http://ccspicc.blogspot.com/
// electronnote@gmail.com

#include <18F4550.h>
#fuses  HS, CPUDIV1, PLL2, USBDIV, VREGEN, NOMCLR, NOWDT, NOLVP
#use delay(clock = 8MHz)
#include <usb_desc_mouse.h>
#include<pic18_usb.h>
#include<usb.c>

signed int8 out_data[4] = {0, 0, 0, 0};
short nec_ok = 0, repeated = 0;
int8 i, nec_state = 0;
int32 nec_code;
#INT_EXT                                         // External interrupt
void ext_isr(void){
  int16 time;
  if(nec_state != 0){
    time = get_timer1();                         // Store Timer1 value
    set_timer1(0);                               // Reset Timer1
  }
  switch(nec_state){
    case 0 :                                     // Start receiving IR data (we're at the beginning of 9ms pulse)
      setup_timer_1( T1_INTERNAL | T1_DIV_BY_2 );   // Enable Timer1 module with internal clock source and prescaler = 2
      set_timer1(0);                             // Reset Timer1 value
      nec_state = 1;                             // Next state: end of 9ms pulse (start of 4.5ms space)
      i = 0;
      ext_int_edge( L_TO_H );                    // Toggle external interrupt edge
      return;
    case 1 :                                     // End of 9ms pulse
      if((time > 9500) || (time < 8500)){        // Invalid interval ==> stop decoding and reset
        nec_state = 0;                           // Reset decoding process
        setup_timer_1(T1_DISABLED);              // Stop Timer1 module
      }
      else
        nec_state = 2;                           // Next state: end of 4.5ms space (start of 562µs pulse)
      ext_int_edge( H_TO_L );                    // Toggle external interrupt edge
      return;
    case 2 :                                     // End of 4.5ms space
      if((time > 5000) || (time < 1500)){        // Invalid interval ==> stop decoding and reset
        nec_state = 0;                           // Reset decoding process
        setup_timer_1(T1_DISABLED);              // Stop Timer1 module
        return;
      }
      nec_state = 3;                             // Next state: end of 562µs pulse (start of 562µs or 1687µs space)
      if(time < 3000)                            // Check if previous code is repeated
        repeated = 1;
      ext_int_edge( L_TO_H );                    // Toggle external interrupt edge
      return;
    case 3 :                                     // End of 562µs pulse
      if((time > 700) || (time < 400)){          // Invalid interval ==> stop decoding and reset
        nec_state = 0;                           // Reset decoding process
        setup_timer_1(T1_DISABLED);              // Disable Timer1 module
      }
      else{
        // Check if the code is repeated
        if(repeated){
          set_timer3(25000);
          repeated = 0;
          nec_ok = 1;                            // Decoding process is finished with success
          return;
        }
        nec_state = 4;                           // Next state: end of 562µs or 1687µs space
      }
      ext_int_edge( H_TO_L );                    // Toggle external interrupt edge
      return;
    case 4 :                                     // End of 562µs or 1687µs space
      if((time > 1800) || (time < 400)){         // Invalid interval ==> stop decoding and reset
        nec_state = 0;                           // Reset decoding process
        setup_timer_1(T1_DISABLED);              // Disable Timer1 module
        return;
      }
      if( time > 1000)                           // If space width > 1ms (short space)
        bit_set(nec_code, (31 - i));             // Write 1 to bit (31 - i)
      else                                       // If space width < 1ms (long space)
        bit_clear(nec_code, (31 - i));           // Write 0 to bit (31 - i)
      i++;
      if(i > 31){                                // If all bits are received
        nec_ok = 1;                              // Decoding process OK
      }
      nec_state = 3;                             // Next state: end of 562µs pulse (start of 562µs or 1687µs space)
      ext_int_edge( L_TO_H );                    // Toggle external interrupt edge
  }
}
#INT_TIMER1                                      // Timer1 interrupt (used for time out)
void timer1_isr(void){
  nec_ok = 0;
  nec_state = 0;                                 // Reset decoding process
  ext_int_edge( H_TO_L );                        // External interrupt edge from high to low
  setup_timer_1(T1_DISABLED);                    // Disable Timer1 module
  clear_interrupt(INT_TIMER1);                   // Clear Timer1 interrupt flag bit
}
#INT_TIMER3                                      // Timer1 interrupt (used for time out)
void timer3_isr(void){
  nec_code = 0;
  setup_timer_3(T3_DISABLED);                    // Disable Timer3 module
  clear_interrupt(INT_TIMER3);                   // Clear Timer3 interrupt flag bit
}
void main(){
  delay_ms(500);
  usb_init();                                    // Initialize USB hardware
  enable_interrupts(GLOBAL);                     // Enable global interrupts
  enable_interrupts(PERIPH);
  enable_interrupts(INT_EXT_H2L);                // Enable external interrupt
  clear_interrupt(INT_TIMER1);                   // Clear Timer1 interrupt flag bit
  enable_interrupts(INT_TIMER1);                 // Enable Timer1 interrupt
  clear_interrupt(INT_TIMER3);                   // Clear Timer3 interrupt flag bit
  enable_interrupts(INT_TIMER3);                 // Enable Timer3 interrupt
  while(TRUE){
    if(nec_ok){                                  // If the mcu successfully receives NEC protocol message
      nec_ok = 0;                                // Reset decoding process
      nec_state = 0;
      setup_timer_1(T1_DISABLED);                // Disable Timer1 module
      set_timer3(25000);
      setup_timer_3( T3_INTERNAL | T1_DIV_BY_8 );    // Enable Timer3 module with internal clock source and prescaler = 8 
      if(nec_code == 0x40BF30CF)                 // Button 1 code (mouse left button)
        out_data[0] = 1;
      if(nec_code == 0x40BFB04F)                 // Button 2 code (move cursor up)
        out_data[2] = -1;
      if(nec_code == 0x40BF708F)                 // Button 3 code (mouse right button)
        out_data[0] = 3;
      if(nec_code == 0x40BF08F7)                 // Button 4 code (move cursor left)
        out_data[1] = -1;
      if(nec_code == 0x40BF8877)                 // Button 5 code (move cursor down)
        out_data[2] = 1;
      if(nec_code == 0x40BF48B7)                 // Button 6 code (move cursor right)
        out_data[1] = 1;
      while(nec_code != 0)
        usb_put_packet(1, out_data, 4, USB_DTS_TOGGLE);    // Send USB packet
      delay_ms(10);
      out_data[0] = 0;
      out_data[1] = 0;
      out_data[2] = 0;
      usb_put_packet(1, out_data, 4, USB_DTS_TOGGLE);      // Send USB packet
    }
  }
}
Remote controlled USB mouse using PIC18F4550 video:
The following video is divided into 2 parts, part shows PC screen which is the main part and the small part shows my simple hardware circuit (sorry for video quality).


Thursday, September 15, 2016

USB Mouse using PIC18F4550 microcontroller


PIC18F4550 microcontroller has USB module which can work as a HID (Human Interface Device). The USB HID device doesn't need any additional driver because it's already installed in most of modern operating systems.
Using PIC18F4550 as a HID device we can easily transfer data between PC and the microcontroller as shown at the following URL:
PIC18F4550 USB HID Example using CCS PIC C
This topic shows how to build a simple USB HID mouse using PIC18F4550 microcontroller (PIC18F2550 can also be used).
Required Components:
  • PIC18F4550 Microcontroller ( or PIC18F2550) 
  • 8MHz Crystal Oscillator
  • 2 x 22pF capacitors
  • 470nF Capacitor
  • 6 Push-Buttons
  • USB Connector
  • +5V Power Supply (USB VCC can be used)
  • Protoboard
  • Jumper Wires
Project circuit schematic is shown below:
PIC18F4550 PIC18F2550 USB HID Mouse circuit
USB Mouse using PIC18F4550 CCS C code:
The CCS PIC C compiler has USB Mouse driver which allows us to build a USB mouse more easily. The following line is used to add this driver to this project:
#include <usb_desc_mouse.h>
In this project there is no need to receive data from the HID device, we have only to send data to this device using the following line:
usb_put_packet(1, out_data, 4, USB_DTS_TOGGLE);
Where out_data is an array of 4 elements (4 bytes):
Byte 1 corresponds to button status. The left button of the mouse is represented by 0x01 and the right button is represented by 0x03.
Byte 2 and byte 3 correspond to X axis and Y axis directions.
Byte 4 for the mouse wheel which is not used in this project.
The X axis and Y axis bytes are signed int8 which can vary from -127 to 127. The ( - ) sign indicates direction ( for X axis left/right and for Y axis up/down) and the number means number of steps at a time.
The complete C code is as the one below:
// PIC18F4550 USB Mouse CCS C code
// http://ccspicc.blogspot.com/
// electronnote@gmail.com
// Use at your own risk

#include <18F4550.h>
#fuses  HSPLL PLL2 CPUDIV1 USBDIV VREGEN NOMCLR
#use delay(clock = 48000000)
#include <usb_desc_mouse.h>
#include<pic18_usb.h>
#include<usb.c>
#use fast_io(B)

char out_data[4];
void main(){
  setup_adc_ports(NO_ANALOGS);                          // Configure all AN pins as digital
  output_b(0);                                          // PORTB initial state
  set_tris_b(0x3F);                                     // Configure RB0 to RB5 as inputs
  port_b_pullups(TRUE);                                 // Enable PORTB pull-ups
  usb_init_cs();                                        // Initialize USB hardware
  delay_ms(1000);
  while(TRUE){
    usb_task();
    if(usb_enumerated()){                               // If the device has been enumerated by the PC
      while(!input(PIN_B0)){
        out_data[0] = 0;
        out_data[1] = 1;
        out_data[2] = 0;
        out_data[3] = 0;
        usb_put_packet(1,out_data,4,USB_DTS_TOGGLE);
      }
      while(!input(PIN_B1)){
        out_data[0] = 0;
        out_data[1] = -1;
        out_data[2] = 0;
        out_data[3] = 0;
        usb_put_packet(1,out_data,4,USB_DTS_TOGGLE);
      }
      while(!input(PIN_B2)){
        out_data[0] = 0;
        out_data[1] = 0;
        out_data[2] = 1;
        out_data[3] = 0;
        usb_put_packet(1,out_data,4,USB_DTS_TOGGLE);
      }
      while(!input(PIN_B3)){
        out_data[0] = 0;
        out_data[1] = 0;
        out_data[2] = -1;
        out_data[3] = 0;
        usb_put_packet(1, out_data, 4, USB_DTS_TOGGLE);
      }
      while(!input(PIN_B4)){
        out_data[0] = 1;
        out_data[1] = 0;
        out_data[2] = 0;
        out_data[3] = 0;
        usb_put_packet(1,out_data,4,USB_DTS_TOGGLE);
      }
      while(!input(PIN_B5)){
        while(!input(PIN_B5));
        out_data[0] = 3;
        out_data[1] = 0;
        out_data[2] = 0;
        out_data[3] = 0;
        usb_put_packet(1,out_data,4,USB_DTS_TOGGLE);
      }
        out_data[0] = 0;
        out_data[1] = 0;
        out_data[2] = 0;
        out_data[3] = 0;
        usb_put_packet(1,out_data,4,USB_DTS_TOGGLE);
    }
  }
}
USB Mouse using PIC18F4550 video:
This video shows a simple hardware circuit connected to PC where the screen is divided into two parts, the left part is a recorded video of the PC screen and the right side shows the hardware circuit.

Tuesday, September 13, 2016

PIC18F4550 USB HID Example using CCS PIC C


PIC18F4550 microcontroller has 1 USB (Universal Serial Bus) communication module. This topic shows how to use PIC18F4550 as a USB HID (Human Interface Device) to send and receive data from the PC.
The USB HID device doesn't need any additional driver because it's already installed in most of modern operating systems.
PIC18F4550 USB HID example hardware circuit:
The following figure shows our example basic circuit schematic.
PIC18F4550 USB HID example circuit CCS PIC C
The USB power supply pin (5V) can be used in this project.
To send/receive data to/from the microcontroller using USB communication a software named HID Terminal from MikroElektronika is used. The main interface of this software is shown below:
USB HID Terminal data receive and transmit
HID Terminal can be downloaded from the following link:
HID Terminal
About Example:
After plugging the USB cable which comes from the microcontroller, the device will appear in the HID devices list which named USB HID Example. If we want to send data to the microcontroller just tape the text and click on Send.
The LCD is used to display the data received by the microcontroller and after 1 second the microcontroller sends the same data back to the PC which will appear in the HID terminal software.
PIC18F4550 USB HID Example CCS C code:
In this project the an external oscillator (8MHz) is used to run the microcontroller as well as the USB module. PIC18F4550 microcontroller always needs an external oscillator to run its USB module.
The fuses used in this project are:
#fuses  HSPLL PLL2 CPUDIV1 USBDIV VREGEN NOMCLR
HSPLL : high speed oscillator is used with PLL enabled
PLL2 : Divide by 2  (8MHz oscillator input)
CPUDIV1 : No system clock postscaler
USBDIV : USB clock source comes from PLL divide by 2
VREGEN : USB voltage regulator enabled
NOMCLR : Master Clear pin used for I/O (MCLR pin function disabled)
The program can send and receive a maximum of 16 bytes. The sizes can be changed with the following two lines:
#define USB_CONFIG_HID_TX_SIZE 16                // Transmit packet size (bytes)
#define USB_CONFIG_HID_RX_SIZE 16                // Receive packet size (bytes)

The following two lines for product ID and vendor ID numbers. This numbers can be changed:
#define USB_CONFIG_PID 1                         //Chnage Product Id
#define USB_CONFIG_VID 1234                      //Chnage Vendor Id

Other lines are described in the code.
// PIC18F4550 USB HID Examlpe CCS C code
// http://ccspicc.blogspot.com/
// electronnote@gmail.com
// Use at your own risk

//LCD module connections
#define LCD_RS_PIN PIN_D0
#define LCD_RW_PIN PIN_D1
#define LCD_ENABLE_PIN PIN_D2
#define LCD_DATA4 PIN_D3
#define LCD_DATA5 PIN_D4
#define LCD_DATA6 PIN_D5
#define LCD_DATA7 PIN_D6
//End LCD module connections

#define USB_CONFIG_HID_TX_SIZE 16                // Transmit packet size (bytes)
#define USB_CONFIG_HID_RX_SIZE 16                // Receive packet size (bytes)
#define USB_CONFIG_PID 1                         //Chnage Product Id
#define USB_CONFIG_VID 1234                      //Chnage Vendor Id
#define USB_STRINGS_OVERWRITTEN

#include <18F4550.h>
#fuses  HSPLL PLL2 CPUDIV1 USBDIV VREGEN NOMCLR
#use delay(clock = 48000000)
#include <lcd.c>
#define USB_STRING(x)  (sizeof(_unicode(x))+2), 3, _unicode(x)
rom char USB_STRING_DESC[]={
    //string 0 (must always provide this string)
    //4(length of string index),3(desciptor type is string),9&4(Microsoft Defined for US-English)
    4, 3, 9, 4,  
    //string 1 - vendor (this is optional, but we specified it's use in the device descriptor)
    USB_STRING("CCS"),
    //string 2 - product (this is optional, but we specified it's use in the device descriptor)
    USB_STRING("USB HID Example"),
    //string 3 - serial number (this is optional, but we specified it's use in the device descriptor)
    USB_STRING("123456789")};
#include <usb_desc_hid.h>
#include<pic18_usb.h>
#include<usb.c>

char data[16];
void main(){
  lcd_init();                                    // Initialize LCD module
  lcd_putc('\f');                                // LCD clear
  lcd_gotoxy(1, 1);
  lcd_putc("USB HID Example");
  usb_init_cs();                                 // Initialize USB hardware
  delay_ms(1000);
  while(TRUE){
    usb_task();
    if(usb_enumerated()){                // If the device has been enumerated by the PC
      if(usb_kbhit(1)){                  // If endpoint1 has data in it's receive buffer
        lcd_gotoxy(1, 2);
        lcd_putc("                ");            // Clear 2nd line
        // Read up to 16 bytes from endpoint1 buffer and save it to variable data
        usb_get_packet(1, data, 16);
        lcd_gotoxy(1, 2);
        printf(lcd_putc, data);                  // Display the received bytes on LCD
        delay_ms(1000);                          // Wait 1 second
        // Return the received bytes back
        usb_put_packet(1, data, 16, USB_DTS_TOGGLE);
      }
    }
  }
}


PIC18F4550 USB HID Example Videos:
The following video shows our example in protoboard circuit.


And the following video shows example Proteus simulation.