EihiS

July 9, 2013

microNitel : A homebrew video card for your PIC projects (and others)

Filed under: PIC progs and schematics — Tags: , , , , , , — admin @ 7:46 am

This article in in writing progress.


I’m going to explain the basic idea and principles of a home-made simple video card.
Many old VGA cards coming from old PCs are available, but it’s always painfull to get something of that devices, because it’s originally designed to be used on a PC bus , which has complicated protocols for read/write accesses, and specific clock frequencies for the timing side.
I came across the idea of designing a simple video adapter for my electronic projects because there is almost no simple hardware anywhere, to generate rather ‘good’ quality graphics on a tv set like screen, or even on a PC monitor.

Because i’m living in France, i choosed for this project, to try re-using a Minitel-1 video screen.
The minitel-1 is not anymore used here, in france, and one can find many of that device for a very low price here.
But this project’s hardware and explanations can be used to get video image for many other screens, once adapted to the correct timings, specific to the screen you are going to use.

So, let’s talk of the Minitel-1 video :

Inside the Minitel, one finds 2 boards :

The minitel-1 CPU board

The minitel-1 CPU board : Thats the board wich is removed and replaced by the Nitel Hardware

The minitel-1 video board

The minitel-1 video board ( top ) with the CRT

The first one is dedicated to the power supplies, and all ‘hi voltage’ things : THT generation, vertical and horizontal deviation for the CRT , synchronization , electron-beam focus and brightness modulation and so on.

The second board,the CPU board, onto wich is connected the keyboard, is a micro-computer based on a 8052AH microcontroller, in conjonction with a 4168C static ram and an EF9345 video controller.This board is removed from the minitel and replaced by this project’s , pic18F4520 based board.

A carefull reading of the EF9345 datasheet, combinated with some real-life measurements on the minitel-1 powered-on, reveals that the EF9345 is sending the video signal to the video board in a non-interlaced mode.

This non-interlaced mode video signal is composed of :

  1. The video signal : inverted logic, 0 to 5V amplitude. ( 5V=dark level, 0V=full brightness )
  2. The separate sync. signal ( 0 or 5V amplitude ) : A mix of the vertical and horizontal syncs, with :
    - horizontal line : 64us length , inverted logic ( the pulse is active low, 4.5us )
    - vertical sync : each 310 lines, the vertical sync pulse takes 2 lines more , inverted logic
You better take a look at the datasheet’s chronograms : ef9345_video_generator_datasheet (.PDF file 559Ko )

So :

To use the minitel-1 video screen display board, we need to generate a video picture wich has 312 lines of height, and a 64us horizontal line timing.
I’ll keep the non-interlaced mode for my project.
maybe later i’ll take a closer look at the minitel-1’s video board to check if it’s able to support interlaced frames, or not, but the 9″ size of this CRT is small enough to generate non-interlaced video without annoying flickering of the picture.

Because this article is in writing progress, let’s take a look at a ‘teaser’ video :
This is a small footage of this project’s homemade video card @ work.
The image displayed on the minitel-1 CRT is one generated by my video-card.
My project’s video RAM has a double buffer. in this demonstration and test program running on the video card, each time the little ’sprite’ is hitting the right or left of the screen, the video buffer displayed is swapped, alternating between a blank(dark) background (video frame buffer 2 ) and a background with some test characters and picture on it (the frame buffer 1 ) .
All the video signal is generated by a pic18F4520 running at 40MHz with some additional hardware ( external static video RAM ).
Notice that the  PIC18F is generating the video signal + hsync&vsync signals and in the available time is also able to move the sprite and update it’s display position.
In the actual design’s step, the PIC18F has about 4ms ‘free-time’ to do “additional jobs” in addition to the time needed to generate the picture’s videosignal datas.
The video signal generated is monochrome, 1bit signal. resolution is 480×312 , with about 400×256 ‘useable’ (inside the viewable frame) user display.
A character generator is embedded into the PIC18 to generate the texts, based on the ASCII standard.

july 9, continued :
Here is the schematic of the hardware used for the demo video (NITEL , V1.0) :

The power supply pins are not visible to simplify the view.
Notice how many external components the hardware needs : only the static RAM, and some resistors.
This version of the hardware, the simpliest ever, has the drawback to let only a few pins available for I/O or communication with the outer world.( i’ll talk about another , with more free pins, later ).
The oscillator is a 40MHz clock generator (Golledge - like , pin format is like a DIL14 ).

Now, another video movie , demonstrating that this hardware can display up to 10 numbers composed of 5 chars each, in the 4ms time available between each picture frame generation :

Now, some theory of operation, and explanations about the hardware used :
Each of the 312 lines are generated by software.
Because the active lines are 256 , the pic18 has free time to do additional job , like updating the video ram datas for example, to display changing numbers, moving graphics and so on.
The hard part of the programming job here, is to get all the things running in exact time. for a video monitor (CRT) and, moreover, for a LCD monitor, timing must be EXACTLY into the specs, else you get garbage on the displayed picture, or even no display at all.

Each line is 64us long. Each line has a horizontal sync pulse of 4.5us, and each line has a front-pulse, back-pulse blanking time that must be in the specs. the front and back pulse blankings are used by the video board to hide the CRT’s electron beam’s return path (to the next line), and frequency synchronization time, from one line to the next other.

during the display time of the line, the program sets up the corresponding Y adress on PORTD and X adress on PORT C bits 0..4 (+bit5 for the page),  for the data to be displayed.
Each byte of the RAM represents 8 pixels, in monochrome format.
So, a complete line could be, at maximum : 2^6 x 8 = 64 x8 bits) = 512 pixels .
Another bit (PORTC,5), called ‘PAGE’ is also used : it enables to select the line from 2 possible buffer. this allows that hardware to have a mechanism like double buffering : One can write into the hidden video ram while the other page, the visible one, is displayed.
using that mechanism in software, its possible to update large parts of pictures, taking more than 20ms to be done, without bad flickering of the displayed picture : until the next picture is completly updated, the displayed picture remains on the previous ‘complete and clean’ frame buffer.
Googling for that will help you understand that basics, if needed.

Once the correct adress has been set up,
the video ram byte is then read on PORTB, and immediatly shifted out ( video_shift_output Pin ), by using the internal shift register of the PIC18F4520 that’s usually devoted to slave serial TX/RX .
The shift rate of this internal register is depending on the clock speed that’s present at the PORTC,Bit6 pin (wich is the serial slave clock in pin ).

I’ve used the main OSC freq divided by 4, to clock it up, just connecting the PORTA,6 (fosc/4 out) to PORTC,6. ( 40Mhz / 4 = 10MHz Pixel clock = 1.25MHz Byte clock. )
This computations allows a theoretical 640 pixels line , if the line time is 64us.

In C code speaking, a line can be displayed using that portion of code :
supposing that LATC initial value was Zero ( first column) : LATC++;TXREG=PORTB;asm nop;asm nop;asm nop;asm nop; LATC++;TXREG=PORTB;asm nop;asm nop;asm nop;asm nop; .. and so on until the 60th col has been reached

Practicaly speaking, the line can’t be composed of 640 pixels, because in the 64us time is included the sync pulse time (4.5us) and the front/back pulse blanking times. thats the reason why the RAM line len is 512 pixels maximum.

Practice of this hardware on the Minitel-1 CRT has shown that only the bytes 0 to 60 can be used ( even if 64 accessible bytes are available in ram )

When coding C for that kind of hardware job, its often necessary to switch to ASM code, because timing is critical.
the asm NOP is usefull to keep the timings as precise as 100ns exactly when needed (and it IS needed ).
Looking at the asm code generated by the C compiler is a must do, if you dont want to have unexpected results once compiled.
For example, doing

LATC=0; // c code
LATC=1;
will be compiled like ASM ( supposing there is no 'optimize' option as directive for the compiler ) :
MOVW 0;   // asm code
MOVF LATC;
MOVW 1;
MOVF LATC;
this set of instruction, compiled like this, will take 4 cycles to execute. It could have been written directly in ASM like this :
CLRF LATC; // asm code for 'clear file'
INCF LATC; // asm code for 'increment file'
Wich will take only 2 cycles to execute, and do the same. ASM rules :D ...

Now, lets take a look at the front and back porch blanking.
This time lapses are very important for CRT electron beam. if that 2 blankings are not correct, you can have your picture damaged when displayed.

Here is a real-life exemple of a bad blank timing :
This is the picture with the problem : the right and left part are the same : a grayscale , from total black to full white brightness (with some additional graphics, but we dont care for that )
As you can see, on the left part of the picture, the dark side of the grayscale is not dark enough. in fact, while the electron beam is getting back to the start of the line, it’s not blanked for sufficient time. this lack of blanking generates the ”whitey’ cloud on the left of the screen :

Now, here is the scope capture of the signals :

Top : SYNC signal, Bottom trace : VIDEO SIGNAL

Top : SYNC signal, Bottom trace : VIDEO SIGNAL

The video signal of the minitel’s video board is inverted. thats why, when ‘blanked’, it has to get ‘high’ (5V).
Notice what happends during the sync pulse : the video signal gets right after the end of the pulse to a low level, wich is ‘bright white’ level, for the minitel. But thats the reason of the problem : after the sync pulse, the ‘back porch’ blanking is not executed, creating the ‘whitey’ artifact, on the screen.

Now, you can check the difference with this 2 new pictures :

Now the grayscales are fine for each sides of the screen

Now the grayscales are fine for each sides of the screen

and here , one can see the blanking signal after the SYNC pulse

and here , one can see the blanking signal after the SYNC pulse

If you have looked carefully at the scope’s traces, you have noticed that the video signal looks like analogic.
And you’re right.

That scope traces come from the hardware version ‘2.0′.
If you understood the theory of operation of the v1.0, then you’ll understand most of the hardware V2.0 schematic.

For now, its a prototype, i’m working on these days…
Here is the schematic of that new circuit :

Take a look at that movie and check by yourself ..

after some adjustements , the full capabilities of this hardware version is now visible...

after some adjustements , the full capabilities of this hardware version 2.0 is now visible...

to be continued..

Stay tuned for more theory coming soon :D !

314159265358979323846264338327950288
419716939937510582097494459230781640
628620899862803482534211706798214808

July 9, 2010

A pic18f452 based 8 servo controller

Filed under: PIC progs and schematics — Tags: , , , , , , , — admin @ 11:44 am

This electronic project’s goal is to drive 8 servos directly , using a pic18f452 microcontroller.
Each of the 8 servo’s channels are driven by a potentiometer, wich position is directly converted to a pulse length, thus allowing each servo to reach the desired position.
Additionaly, a 16×2 chars display can be connected (it’s fully operationnal, but optional) to allow for reading of the 8-Bit data for each of the 8 channels ( this can be usefull when searching for specific ‘key’ positions of a robot for example)

It’s not a big project, but i think it’s a good start point for people who wants to learn more about the microchip pic’s programming techniques.
In this project, we use 3 main elements :

  • A pic 18f452 at 40Mhz clocking
  • A cmos 4067 used as an analogic multiplexer
  • A hd44780 compatible 2*16 characters display

So we are going to deal with analogic/digital conversion, addressing from the pic to the 4067 , and manage the commands between the pic and the display.

The pic’s program is written in C , using CC8E (free edition).

Here is the schematic (click to see full sized picture)

RV1,RV2 are here for test purposes and show how the potentiometers have to be connected to POT-CONN.
SW1(reset switch) is also there for test purposes.
VCC is 5V, and we use a separate line for SERVO power (i.e a 6V power line)
The pic’s outputs are limited thru the 8 resistors of 10k.If you dont do this, the pic will suffer of overload and will no longer output the whole pulses on each of it’s ports.
RB5 is not used , to enable an In circuit Low voltage programming.That pin must be set to ground if you dont want to enter ‘program mode’ ( the pulldown resistor is not shown on this schematic).

The logic behavior is pretty simple :

Each 18ms (an interrupt routine has been setup into the pic using Timer0 ) , we scan the 8 input lines of the cd4067 and convert the select analogique signal present on RA0 to a digital one.

These oscillograms show the voltage on the analogue-out pin of the 4067 :
( 1V/Div-vert & 2ms/Div-hor)

A closer look : the 8 analogue values follow each others during the conversion process

The A/D conversion is done in 10-bits, but we keep only the 8 leftmost bits so we have a 8-bit value as a result of the conversion for our program.
At this time, we put the 8 pwm lines to a logical ‘1′ state.
Right after this conversion time, we start increasing internal counters starting from zero and compare it to match the converted 8bit value.
When the internal counter equals the converted value, we bring down the corresponding pwm line to a zero logic level. So, for each channels, the pulse width will be directly related to the potentiometer’s value.
After that process, we program the timer0 value to match a 18ms time between each new pulses :

Aspect of the ouput pulses on the SERVO-0 channel ouput  :
1V/Div-vert & 5ms/Div-hor

You’ll notice that i’ve used a 16 channels mux ,but a 8 channel mux already exists in cmos technology : that’s for test conveniences because the actual program can be modified and more analogic inputs could be usefull for specific projetcs.
The output connectors for servos a JR-like.

The display part is out of the 18ms interrupt routine and runs in the forever-main loop of the pic.
I’ve put some fonctions to simplify writing to the display , wich is a hd44780 (industry standard) compatible display.
For this project’s building i’ve used a noritake-itron display i had in my hands. it’s complete ref is CU16025ECPB-W3J.

It’s a VFD (pretty neat green fluo light) build to replace the classic LCD 16×2 displays.
The only difference is that it does not use the pin3 (VEE) of its connector :
For LCD displays, that pin is used to tune the brightness with a potentiometer connected to it.
On that VFD , the brightness is setup by a software command.

Once running, the display shows the 8 servo’s 8bit A/D converted value, as shown of on the above picture.
The full system can run without the display connected , so if you dont have one .. dont worry about it :
I arranged the display routines to wait for the display to be updated without reading the busy flag thus enabling the display to be disconnected ( not present )

Now here is the code part (cc8e ,  C language. the code is compact as a one only file (no linker needed) )

 

#pragma chip PIC18F452
#pragma sharedAllocation
#include “int18XXX.h”
/*
####################################################################################
PIC 18F452 @ 40MHz , 8-SERVO A/D DRIVER
####################################################################################
*/
//
//
#define OUTPUT 0b00000000;
#define INPUT  0b11111111;
#define INBIT  1;
#define OUTBIT  0;
//
//
void InitialiseADC (void);    // A/D conversion Init routine
void ReadADC(void);     // A/D conversion READ routine
void Delay(unsigned long howmuch); // delay routine
void DelayLong(unsigned long howmuch);
void Delay2(unsigned long howmuch); //
void _highPriorityInt(void);  // interrupt handling
void LCD_PUTCMD(unsigned int what); // LCD put command routine
void LCD_WRITESTR(unsigned int y,unsigned int pos,unsigned int indice);
void LCD_WRITECHAR(unsigned int y,unsigned int pos,unsigned int what);
//
//
unsigned char _SERVO_VAL[8];  // SERVO channel counter
unsigned char _READ_VAL[8];   // storing current servo’s values from ADC
unsigned char _ADC_MUX_CHANNEL;  // active mux channel for a/d conv
unsigned char _DISPLAY_TOGGLE;  // for display routines : 4 up channels or 4 down channels display toggle
//
const struct  {      // display strings structure
  const char *str;
} txtp[] = {
  “PIC18 SERVODRIVE\0″,
  “0123456789ABCDEF\0″,
  “READY\0″,
  “WAITING\0″,
  “RECEIVED:\0″,   
  “TRANSMIT:\0″,   
};
//
#pragma origin 0×8
interrupt highPriorityIntServer(void)
{
    // W, STATUS and BSR are saved to shadow registers
    // handle the interrupt
    // 8 code words available including call and RETFIE
    _highPriorityInt();
 // restore W, STATUS and BSR from shadow registers
    #pragma fastMode
}
#pragma origin 0×18
interrupt lowPriorityIntServer(void)
{
    // W, STATUS and BSR are saved by the next macro.
    int_save_registers
    int_restore_registers // W, STATUS and BSR
}
void _highPriorityInt(void)
{
    uns16 sv_FSR0 = FSR0; // saves used registers
  
 
 unsigned char c;
 unsigned int loop_timer;
 unsigned int limit;
 
 //
 
 limit=0×0;
 if (TMR0IF)
 {
  // set all pwm channels (0 to 7) to high logic level (pulse high)
  loop_timer=0;
  PORTB=0b11011111;
  PORTC.3=1;
  //
  TMR0IE=0;
  PORTC.4=0;         // enable MUX decode
  //
  
  for (c=0;c<8;c++)
  {
    PORTC.0=c.0;
    nop();
    PORTC.1=c.1;
    nop();
    PORTC.2=c.2;
    nop();
    // 
    Delay(50);      // this delay and the conversion time (..)
    _ADC_MUX_CHANNEL=c;    // will generate a 600us pulse (minimum length)
    ReadADC();      // — sequential read of each channels –
    _SERVO_VAL[c]=0;    // — init channel upcounters –
  }
  PORTC.4=1;       // disable MUX decode
  
  // updates pulse high or low
  while (loop_timer<(unsigned int)0xff)
  {
   //
   
   if (_SERVO_VAL[0]==_READ_VAL[0]) { PORTB.0=0;} else { _SERVO_VAL[0]++;}
   if (_SERVO_VAL[1]==_READ_VAL[1]) { PORTB.1=0;} else { _SERVO_VAL[1]++;}
   if (_SERVO_VAL[2]==_READ_VAL[2]) { PORTB.2=0;} else { _SERVO_VAL[2]++;}
   if (_SERVO_VAL[3]==_READ_VAL[3]) { PORTB.3=0;} else { _SERVO_VAL[3]++;} 
   if (_SERVO_VAL[4]==_READ_VAL[4]) { PORTB.4=0;} else { _SERVO_VAL[4]++;}
   if (_SERVO_VAL[5]==_READ_VAL[5]) { PORTC.3=0;} else { _SERVO_VAL[5]++;}
   if (_SERVO_VAL[6]==_READ_VAL[6]) { PORTB.6=0;} else { _SERVO_VAL[6]++;}
   if (_SERVO_VAL[7]==_READ_VAL[7]) { PORTB.7=0;} else { _SERVO_VAL[7]++;}
   loop_timer++;
   // ajusting timing to reach a 2.4 max pulse width, from 0.6ms pulse min width
   nop();nop();nop();nop();nop();
   nop();nop();nop();nop();nop();
   nop();nop();nop();nop();nop();
   nop();nop();nop();nop();nop();
   nop();nop();nop();nop();nop();
  }
  //
  TMR0H=0xed;       // setup timer0 value : will match 18ms , @40MHz clock between pulses time
  TMR0L=0×40; //e5
  
 
  TMR0IF=0;       // reset timer0 interrupt flag
 }
 
 TMR0IE=1;        // enables back TIMER0 overflow interrupts
 //
  FSR0 = sv_FSR0;       // restore used registers
 
}
//
void main(void)
{ // some vars
  unsigned char c;
 // lcd potentiometer display related vars
 static const char chaine[]=”0123456789abcdef”; // hexadecimal chars
 unsigned char disp_var;
 unsigned char ypos;
 unsigned char trs;
 //
 _DISPLAY_TOGGLE=0;  // toggles 0=(0…3) and 1=(4…7) potentiometer values display
 ypos=0;
 trs=0;
//
  //
 TXEN=0;
 SPEN=0;
 // for TIMER0 interrupt
 TMR0IP=1;    // timer 0 interupt is high priority
 TMR0IF=0;    // clears timer 0 interrupt bit flag
  RCON.7=1;    // enables priority levels on interrupts
 //
 SSPEN=0;   // disable synchronous serial port
 T1CON.0=0;    // no timer 1
 T2CON.2=0;    // disable timer 2
 T3CON.0=0;    // disable timer 3
 CCP1CON=0;    // no CCP1
 CCP2CON=0;    // no CCP2
 RBIE=0;     // disable port B change interrupt
 INT2IE=0;
 INT1IE=0;    // not external interrupts enabled
 PIE1=0;     // no various interrupts (check databook)
 //
 TRISA.5=OUTBIT;   // RS line of display
 TRISA.4=OUTBIT;   // R/w line of display
 TRISA.3=OUTBIT;   // E line of display
 TRISB = OUTPUT;   // PWM output servo 0.1.2.3.4.5.6.7
 TRISD = OUTPUT;   // output datas for display
 TRISC = OUTPUT;   // output channel select (4bits) - only 3 are needed ( 8 servos)
 //
 LATA=0;
 LATB=0;     // clears B port latches ( pgm pin as input is a problem..)
 LATC=0;
 //
 _ADC_MUX_CHANNEL=0;
 InitialiseADC();  // init ADC and PIC ports as needed by the hardware
 PORTA=PORTA&0b1100011;    // Rs,r/w and E are low
 PORTD=0;       // clears display data bus
 // —- init display first :
 Delay2(2000);      // waits for power-up display module RESET done.
 LCD_PUTCMD(0b00110000) ;    // 8 bit interface, 2 lines (old val=0×38)
 LCD_PUTCMD(0b00001100);    // display on, cursor off, blinking off
 Delay2(200);
 LCD_PUTCMD(0b00000001);    // LCD CLEAR
 Delay2(200);
 LCD_PUTCMD(0b00000010);    //cursor home, init address counter to 0 ..
 // —- displays welcome message
 LCD_WRITESTR(1,0,0);    // (row 1..2,xpos 0…F,string number).
 DelayLong(0×300);     // pause
 LCD_PUTCMD(0b00000001);    // LCD CLEAR
 Delay2(2000);
 // —- starting up timer interruption and counting
 TMR0H=0×00;
 TMR0L=0×01;
 T0CON=0b10000100;  // timer0 = ON, internal clock, prescaler 1:256 ,16bit count
       // enabling interrupts, both high and/or low priority
 //GIEL = 1;      // enable low priority interrupt
    GIEH = 1;     // enable high priority interrupt
 // fin interruptions program
 TMR0IE=1;    // starts timer0 interupts
 while(1)
 {
  if (_DISPLAY_TOGGLE==0)
   {
    ypos=0;trs=0;
   }
  else
   {
    ypos=1;trs=4;
   }
  
  for (c=0;c<4;c++)
   {
    // updating display with the 8 potentiometer values
    disp_var=_READ_VAL[c+trs]>>4;
    PORTA.5=0; // register mode
    nop();
    PORTA.4=0; // write mode
    if (ypos==0) { PORTD=0×80+c*4;} else { PORTD=0xc0+c*4;}
    PORTA.3=1;
    Delay(200);
    PORTA.3=0;
    nop();
    //
    PORTA.5=1; // data mode
    nop();
    PORTA.4=0; // write mode
    PORTD=chaine[disp_var];
    PORTA.3=1;
    Delay(200);
    PORTA.3=0;
    nop();
    //
    disp_var=_READ_VAL[c+trs]&0b00001111;
    PORTA.5=1; // data mode
    nop();
    PORTA.4=0; // write mode
    PORTD=chaine[disp_var];
    PORTA.3=1;
    Delay(200);
    PORTA.3=0;
    nop();
    
   }
   _DISPLAY_TOGGLE=!_DISPLAY_TOGGLE; 
  
  }
 
}
// end MAIN
// ###########################################################################
//
// SUB ROUTINES following :
//
//
// ### DELAY ROUTINE ( For interrupt use )
void Delay(unsigned long howmuch)
{
 unsigned long ma;
 char count=0;
 for( ma=0;ma<=howmuch;ma++) {
  // PORTC.0=count;
  count++;
 } 
}
void DelayLong(unsigned long howmuch)
{
 unsigned long ma;
 unsigned long ko;
 char count=0;
 for( ma=0;ma<=howmuch;ma++) {
  for (ko=0;ko<1000;ko++) { nop();}
  count++;
 } 
}
//
// ### DELAY ROUTINE (For general purpose use)
void Delay2(unsigned long howmuch)
{
 unsigned long ma;
 char count=0;
 for( ma=0;ma<=howmuch;ma++) {
  // PORTC.0=count;
  count++;
 } 
}
// ### init conversion A/D
void InitialiseADC (void) {
 TRISA.0 = INBIT;   // RA0 as analogue input for A/D conversion
 ADCON1 = 0b01000100; // left justified result, an0 analog input, vcc/gnd, b6=1 (pour fosc/64)
 RCIE=0;     // no interrupt on AD / godone
 ADIE   = 0;        /* Masking the interrupt */
   ADIF    = 0;         /* Resetting the ADC interupt bit */ 
 PORTC.3=0;
 ADCON0=0b10000001; // A/D on , B7,6=10 pour fosc/64 , b2=1 pour conversion START
 Delay(100); 
}
// ###  A/D conversion request
void ReadADC(void)
{
 
 ADRESL = 0;        /* Resetting the ADRES value register */
 ADRESH = 0;       
 
  ADCON0=ADCON0|0b100; // GO !
 while((ADCON0&0b00000100)) continue; // tant que bit 2 est a 1, attente de fin de conversion…      
   _READ_VAL[_ADC_MUX_CHANNEL]=ADRESH;
 if (_READ_VAL[_ADC_MUX_CHANNEL]==0xff) { _READ_VAL[_ADC_MUX_CHANNEL]–;}
}
// ______________________________________________________________
//
// ########## LCD RELATED ################################
//
// ______________________________________________________________
void LCD_PUTCMD(unsigned int what)
{
 PORTA.5=0; // register mode
 nop();
 PORTA.4=0; // write mode
 PORTD=what;
 PORTA.3=1;
 Delay2(300);
 PORTA.3=0;
}
//
// ________________________________________________________________
void LCD_WRITESTR(unsigned int y,unsigned int pos,unsigned int indice)
{
 unsigned char the_char;
 unsigned char char_pos;
 if (pos!=0xFF)
 {
 PORTA.5=0; // register mode
 nop();
 PORTA.4=0; // write mode
 if (y==1) { PORTD=0×80+pos;} else { PORTD=0xc0+pos;}
 PORTA.3=1;
 Delay2(300);
 PORTA.3=0;
 nop();
 }
 //
 PORTA.5=1; // data mode
 nop();
 PORTA.4=0; // write mode
 char_pos=0;
 while (txtp[indice].str[char_pos]!=0)  // loop to display full string, ended by a zero ascii char
 {
  the_char=txtp[indice].str[char_pos];
  PORTD=the_char;
  char_pos++;
  PORTA.3=1;
  Delay2(300);
  PORTA.3=0;
 }
}
//
// ______________________________________________________________
//
// where : pos=0xff -> writes char after the previous one OR pos =0 … 0xF = line position
// y=1 : first row, y=2 second row of the display ( if  pos=0xff is not used )
void LCD_WRITECHAR(unsigned int y,unsigned int pos,unsigned int what)
{
 if (pos!=0xFF)
 {
 PORTA.5=0; // register mode
 nop();
 PORTA.4=0; // write mode
 if (y==1) { PORTD=0×80+pos;} else { PORTD=0xc0+pos;}
 PORTA.3=1;
 Delay2(300);
 PORTA.3=0;
 nop();
 }
 //
 PORTA.5=1; // data mode
 nop();
 PORTA.4=0; // write mode
 PORTD=what;
 PORTA.3=1;
 Delay2(300);
 PORTA.3=0;
 nop();
}

 

 

The full source is here for your convenience.  ( the PWM pulse length timing may need to be adapted depending on the servo’s youre using but looks to work fine with my basic servos )

If you dont have the compiler, you can directly use scd_displayed2.HEX for you favorite pic programmer.

ENJOY ! (be smart, put some comments about this post if it helped you ! thanks)

314159265358979323846264338327950288
419716939937510582097494459230781640
628620899862803482534211706798214808

May 23, 2010

A remote 4 SERVOs Driving module diagram

Filed under: PIC progs and schematics — Tags: , , , , , — admin @ 6:48 pm

I’ve moved and been away for a little time…

Now i’m back with a project about the remote control of servomotors with a PIC as controller , and discrete ICs to generate the correct timings pulses necessary to drive the servos.

In the way i look at the things, i’m having trouble with the fact that the PIC microcontroller could be used only as a PWM generator, that’s the reason why i decided to develop a remote board , that could take in charge the generation of correct sized pulses in the 20ms period , providing that the PIC has pre-loaded the duration time into each timers.

I came out with a concrete solution designed to be stack-able and easily customizable for all of you , robots addicts ! It uses 4516 CMOS counters and 4011 classic CMOS gates

Take a look at this ( click to enlarge )

So what the hell is that ?

The clock for each counters is the same one , a 8KHz tunable clock, wich input is ‘clock in’ on my diagram.

A ‘load pulse’ is generated on the counter’s ‘Sx_LOAD’ input : it enables the loading of the data PD0,PD1,PD3,PD4 (binary) into the selected counter ( each of them have a separate ‘load data’ line ).
Each counter is counting down so it will be set at the binary value PD when the ‘load pulse’ is applied, then imediatly after the pulse has come to the low level, they will start counting down.In this mean time, the ‘carry out’ pin of the counter gets High, hence the ‘SERVO_x’ pin also.
When then counter reachs the ‘zero’ value, it’s ‘Carry Out’ gets low. so the generated ‘SERVO_x’ pulse will also get down, and the NAND gate then disables the clock to reach the counter’s clock input : the counter stops counting.

Then a new cycle can start again, by loading a new value into PD by a ‘load_pulse’ on the related pin, after , in the case of servomotors driving, around 20ms (minus the servo pulse time) of waiting time.

I could have added a 2->4 decoder onto the 4 servo board, but i have first decided to keep the ‘Sx_LOAD’ as-is to keep that diagram simple and easily customizable.

One of the important thing is that the counters are 4 bit counters, wich means that the servo will have a 4 bit resolution for positioning, hence a 11.25° resolution. ( 16 * 11.25° = 180° ) .

The timing are also important to understand :

A standard servo is operating from around 0.5ms to 2.5ms pulse length ( thats what i’ve experienced for my side ).
If you get deeper into my diagram logic, you’ll catch that the generated ’servo pulse’s’ duration is the sum of the ‘load_pulse’ time with the counter’s preset datas down count.
If we use a 0.5ms ‘load pulse’ duration, the preset datas combined with the 8KHz clock pulses will provide a covering of the needed time range , from 0.5ms to 2.375ms.

With that kind of remote timers, the robot’s microcontroller just has to manage the ‘load pulses’ and PD datas each 20ms.

Some of you could ask : so and..whats the gain there ?

If the microcontroller takes 0.5ms for each ‘load pulse’, and each load pulse must come every 20ms, then we can achieve a 40 servos driving with one microcontroller (theorical calculation)
Practically lets take a look at a 32 servos system :

  1. 4 Bits port for PD ( duration data )
  2. 6 Bits port for Servo Selection ( a 6 to 32 decoder , wich routes the ‘load_pulse’ to the needed servo) + 1 extra bit to enable decoding.

We have around 11 output Port-bits to achieve a 32 servo driving system - lets say it’s 12bits.
The only routine we have to code is a timer routing into the microcontroller.This routine has to happend every 20ms.
Each time the routine is executed, it programs PD0,PD1,PD2,PD3 of servo number ‘N’ then it programs the ’servo select’ port (6bits) , with the number ‘N’ , then sends a pulse of 0.5ms duration enabling the servo selection decoding (a ’decode_and_load_enable’ bit).
then the routine skips to the next servo in the list and repeat the cycle from the start : programming PD0,PD1,PD2,PD3, for the next servo ,  and so on for the 32 servos.

As we’ve already seen before,  if we use 32 servos, the complete routine will take about 0.5ms x 32 = 16ms.
So we are free with an extra processor time of about 4ms before the 20ms deadline.
If for example, we use a PIC18F @ 32MHz , those 4ms mean a lot of instructions and time to do other stuff than just driving the servos : our microcontroller has now time to do interesting stuff like converting probe datas , doing neural computations and so on , as a 4ms time @32 MHz means 32000 instructions.

Stay tuned !

 

314159265358979323846264338327950288
419716939937510582097494459230781640
628620899862803482534211706798214808

cat{ 16 } { post_69 } { } 2009-2015 EIhIS Powered by WordPress