Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- //PIC 12F1572 Shift LCD Example (MPLABX)
- //By Gadorach
- //PIC 12F1572 Pins
- //Pin 1 = +5V
- //Pin 8 = 0V (GND)
- //Pin 4 = LCD Enable
- //Pin 6 = 74HC595 Clock OUT
- //Pin 7 = 74HC595 Data OUT
- //74HC595 Pins
- //Pin 11 + Pin 12 = 74HC595 Clock IN (tie Pins 11&12 as a clock IN source)
- //Pin 14 = 74HC595 Data IN
- //Pin 15 = LCD D4 OUT
- //Pin 1 = LCD D5 OUT
- //Pin 2 = LCD D6 OUT
- //Pin 3 = LCD D7 OUT
- //Pin 4 = LCD RS OUT
- // CONFIG1
- #pragma config FOSC = INTOSC // (INTOSC oscillator; I/O function on CLKIN pin)
- #pragma config WDTE = OFF // Watchdog Timer Enable (WDT disabled)
- #pragma config PWRTE = OFF // Power-up Timer Enable (PWRT disabled)
- #pragma config MCLRE = ON // MCLR Pin Function Select (MCLR/VPP pin function is MCLR)
- #pragma config CP = OFF // Flash Program Memory Code Protection (Program memory code protection is disabled)
- #pragma config BOREN = OFF // Brown-out Reset Enable (Brown-out Reset disabled)
- #pragma config CLKOUTEN = OFF // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
- // CONFIG2
- #pragma config WRT = OFF // Flash Memory Self-Write Protection (Write protection off)
- #pragma config PLLEN = ON // PLL Enable (4x PLL enabled)
- #pragma config STVREN = ON // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
- #pragma config BORV = LO // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
- #pragma config LPBOREN = OFF // Low Power Brown-out Reset enable bit (LPBOR is disabled)
- #pragma config LVP = ON // Low-Voltage Programming Enable (Low-voltage programming enabled)
- // INCLUDES
- #include <xc.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <pic12f1572.h> //This is the PIC's own setting file, change for your PIC if it's not a 12F1572
- // SETUP CLOCK
- #define _XTAL_FREQ = 16000000; //define 16MHz internal frequency for software use (dosen't set it, just defines it in Hz)
- // DEFINITIONS
- #define LCD_ENABLE LATAbits.LATA2 //LCD enable line for HD44780 parallel LCD
- #define LCD_DATA LATAbits.LATA5 //LCD data line for 74HC595
- #define LCD_CLK LATAbits.LATA4
- #define ON 1 //define bool values for readability
- #define OFF 0
- // GLOBAL VARIABLES
- // FUNCTIONS
- void setup_clock()
- {
- //configure Timer0
- OSCCON = 0x7A; //Setup internal osc to 16MHz
- OSCSTAT = 0x00; //disable unnecessary modules
- OSCTUNE = 0x00; //set INTOSC to factory calibrated settings
- OPTION_REGbits.T0CS = 0; //enable timer mode on timer 0
- OPTION_REGbits.PSA = 0; //assign prescaler to timer0
- OPTION_REGbits.PS = 0b111; //set prescaler to 256 (64us per increment)
- }
- void setup_ports()
- {
- TRISAbits.TRISA2 = 0; // Set Pins (4-RA2, 6-RA4, 7-RA5) to Output
- TRISAbits.TRISA4 = 0;
- TRISAbits.TRISA5 = 0;
- ANSELAbits.ANSA2 = 0; // Set Pins (4-RA2, 6-RA4) to digital I/O
- ANSELAbits.ANSA4 = 0; // 7-RA5 has no ADC, so it's already digital
- }
- void delay_ms(unsigned long time) //delay function based on OSCCON settings and T0, creates delays in ms
- {
- if (time <= 16) //only likes it if it's set to less than 16ms
- {
- TMR0 = 0; //reset timer0
- unsigned long timeHold = (((time * 1000) / 64) + 0.5); //turn ms into us and round to nearest int after dividing by the base of 64us
- while (TMR0 < timeHold); //input time divided by 64us = delay
- }
- else //when it's greater than 16ms
- {
- for(int i=0; i<time; i++) //loop to reach larger values of ms
- {
- TMR0 = 0; //reset timer0
- while (TMR0 < (1024/64)); //only likes clean multiples for some reason
- }
- }
- }
- void delay_50ms() //generalize delays, for tuning
- {
- delay_ms(50);
- }
- int string_length(char *instr) //find the size of a character array
- {
- char length = 0; //setup the length variable so we can calculate the array size
- while(*(instr + length)){ //using a pointer to check the contents of the array, increment the array position with the length variable
- //an alternate way to do this would be "while (instr[length] > 0)", or even just "while (instr[length])". both have the same result,
- //but pointers are preferable due to convention and operation speed. Basically, it's more professional code.
- length++; //as long as the value of the position is greater than null(0x0, \0, 0b0, whatever representation you like), the loop continues and increments the length counter
- } //amusingly, '0', the character for zero, is not equal to the null byte, as the character is actually stored in ASCII, and the true value is 0x30, or 0b00011110, where as null is 0x0, or 0b00000000
- return length; //return the final length of the array, which is found thanks to all arrays ending with a null byte
- }
- void write_nibble(char RS, unsigned short nibble) //RS is the command state. 1 = data, 0 = command. nibble is the character to be sent to the display, converted to it's binary representation
- { // begin by writing the RS bit
- LCD_ENABLE = OFF; // disable the LCD's input command, so we can setup the input data
- LCD_CLK = 0; // setup the clock's first state for the shift register
- LCD_DATA = RS; // make the first bit equal our RS state, so the LCD knows the input is either a command or regular character data
- LCD_CLK = 1; // start the clock pulse for the shift register
- LCD_CLK = 0; // complete the high to low trigger pulse for the shift register's clock
- // shift in our input nibble's 4 bits
- unsigned short mask = 0b1000; // start by setting up the bit position to be checked. in this case, we're starting with position 4 from the LSB (0b1000, equal to a decimal 8)
- for (unsigned short i=0; i<4; i++){ // we're going to write our data from MSB to LSB (0b1000 to 0b0001, for visual example) to match the shift register's outputs to the LCD's inputs
- unsigned short flag = nibble & mask;// now we do a bitwise AND (if both the mask bit and the input bit are equal, we get a 1 for that position and a 0 for all others. else, we get a completely 0 nibble returned. So, if our nibble was 0b1011, and our mask was 0b1000, we would get a 0b1000 as the output. However, if our mask was instead 0b0100, we would get a 0b0000)
- if(flag == 0){ // if the flag returned as a 0b0000 (mask and nibble position not equal)
- LCD_DATA = 0; // data at that position was a 0, so write a 0 to the shift register
- }
- else{ // if it was anything else (0b1000, 0b0100, 0b0010, or 0b0001)
- LCD_DATA = 1; // we know it was a 1 at that position, so we write a 1 to the shift register instead
- } // we do it this way because it's super fast and uses almost no memory, as well as being supported by every traditional microprocessor in existence
- LCD_CLK = 1; // start the shift register's clock
- LCD_CLK = 0; // and finish it with the high to low trigger of the shift register's clock inputs
- mask = mask >> 1; // shift the mask right by one bit position (0b1000 to 0b0100, for example)
- } // repeat until all four bit positions of the nibble are handled
- // one more clock to catch up because our shift register clocks are tied with one behind the other, so our data won't appear at the output until one clock after we set it.
- // a good example would be that the memory of the register holds 0b11010000, but it's outputs show 0b10100000. this is because they are one clock cycle behind each other normally.
- // by clocking it one extra time, we can make them equal, yet not. the data we want ends up at the output, but the memory of the register shows an additional bit of data.
- LCD_CLK = 1; // so, we'd have 0b11101000 in the memory now, but 0b11010000 at the output, which is what we wanted all along.
- LCD_CLK = 0; // clock the shift register (we don't care about the next bit's value, as it won't appear at the output until it doesn't matter)
- LCD_DATA = 0; // reset the data line, so we aren't using more current than necessary until we need to pass data again
- LCD_ENABLE = ON; // enable the LCD controller's input so it can read the inputted bits from the shift register's output pins
- LCD_ENABLE = OFF; // disable it again so we can change the data on the shift register without affecting the LCD's state
- }
- void write_CMD(unsigned short command_bits) // used to more easily pass our command data to the LCD writer function. we need to chop our command in half though, as we need to pass 8 bits, but we're operating in 4-bit mode.
- {
- unsigned short upper_nibble = (command_bits / 0b00010000); // remove the lower half of the byte, placing the upper half in the lower part of the nibble. this is achieved by dividing the input by 0b10000, as the result is equivalent to the upper 4 bits, which are now in the lower 4 bits thanks to math, as any remainder is chopped off in the conversion.
- unsigned short lower_nibble = (command_bits & 0b00001111); // remove the upper 4 bits of the byte. put simply, we just do a bitwise AND again, with 0b00001111 as the mask. the resulting flag is whatever the lower nibble amounts to. honestly, this is un-necessary, but whatever, we'll do it for consistency's sake
- write_nibble(0, upper_nibble); // pass the upper nibble
- write_nibble(0, lower_nibble); // pass the lower nibble
- }
- void write_DATA(unsigned short data_bits) // used to more easily pass our character data to the LCD writer function
- {
- unsigned short upper_nibble = (data_bits / 0b00010000); // same as the command sequence
- unsigned short lower_nibble = (data_bits & 0b00001111);
- write_nibble(1, upper_nibble); // same as before, the only difference is we're sending character data, so we send a 1 instead of a 0
- write_nibble(1, lower_nibble);
- }
- void write_TEXT(char line, char position, char *StrData) //this is a joined function that writes the starting position, and the character data, all in one go
- {
- unsigned char length = string_length(StrData); // get the length of the input string, for multiple handling sections of this code
- if (position == 0){ // this is a special case, where if the position is entered as '0', the characters are automatically centered
- unsigned char center = (((16 - length)/2)+1); // by declaring a new variable, the code will automatically chop off any decimals, which prevents errors.
- // we take the LCD line length, subtract the length of the character array, divide that result by 2, and add 1 to correct for the first position
- position = center; // now we just make the start position equal to the calculated centered starting position
- }
- unsigned short temp = 127 + position; // LCD's have the ability to scroll data across their screen, so they have a really big buffer for characters, which just means that there are more characters slots than there are visible characters on the display.
- // so, the actual starting position for visible characters is position 128, and it ends at position 133.
- if (line == 2){ // in order to display text on line 2 of the LCD, we need to move to a further position on the LCD's buffer
- temp += 64; // specifically, position 1 of line two is at the buffer position of 128+64, which is position 192
- }
- write_CMD(temp); // when we write commands now, as the LCD has already been initialized and set to our mode, commands are recognized as a position request. by writing the new position as a command, we can jump to it immediately instead of cycling through the buffer until we get back to where we want to write our characters
- delay_50ms(); // give the LCD some time to jump to the set position
- for (char i = 0; i < length; i++){ // we have to write the character array one character at a time, so we loop through it
- temp = StrData[i]; // this is pretty basic, just write whatever character that's found in the character array at the loop's incremented position to the LCD, until you reach the end of the loop
- write_DATA(temp);
- }
- }
- void initialize_LCD() //here, we setup the LCD using 4bit mode
- {
- delay_50ms(); // wait until the LCD finishes it's power-up sequence, in case our microcontroller beats it to the punch
- write_CMD(0x20); // wake up the LCD
- delay_50ms(); // give the LCD time to finish the last instruction
- write_CMD(0x20);
- delay_50ms();
- write_CMD(0x20); // it takes three requests to wake it up. this is to prevent random noise from waking it up
- delay_50ms();
- write_CMD(0x28); // set the LCD to 4-bit mode, 2 LCD lines, 5x7 font size
- delay_50ms();
- write_CMD(0x0C); // turn the display on, and disable the cursor
- delay_50ms();
- write_CMD(0x06); // set the LCD to character entry mode, and make it automatically move to the next position after the last entry. Also disable display scrolling.
- delay_50ms();
- write_CMD(0x01); // clear the LCD
- delay_50ms();
- }
- // MAIN LOOP
- void main(void)
- {
- char Message1[] = "PIC12F1572 LCD"; //text to display
- char Message2[] = "with a 74HC595";
- setup_clock(); // setup the internal clock module
- setup_ports(); // setup the I/O ports
- initialize_LCD(); // startup the LCD
- while(1){ // do forever
- write_TEXT(1, 0, Message1); // write a character array to the LCD
- write_TEXT(2, 0, Message2); // write a character array to the LCD
- delay_ms(1500); // put the microcontroller to sleep for a bit
- //write_CMD(0x01); // clear the LCD (uncomment to implement)
- //delay_ms(1000);
- }
- return; //End program (never reached)
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement