📜 ⬆️ ⬇️

Simple clock on MSP430

Having read a huge number of articles about Arduino / LaunchPad wanted to buy a similar toy. The choice fell on the MSP430, since its price is much more attractive to start in the world of microcontrollers.
After a tedious 5 days of waiting, the magic box was in my hands. After playing about 10 minutes with LEDs, I wanted to do something more interesting ... For example, watch!



At hand was an old Siemens A65, which became a donor for my small project. We take the screen out of it and think how to connect it. After a short googling, I successfully got to the branch of the RadioKot forum, where pinouts and initialization of screens were discussed. If someone faced the task of connecting a small screen to a microcontroller, then he knows that it is not enough to know the connection scheme, since there is a controller on the screen, to communicate with which you need to know the commands. For example, to turn on the screen and display garbage from memory, some controllers need to send several dozen commands, and some have less than 10. So, often datasheets on the controllers are not found, and in that case only reading the screen initialization helps during its operation on the phone . But I was lucky, the initialization and commands for my small screen (in my case LPH8731-3C with the EPSON S1D15G14 controller) were not only disassembled, but even datashit was found on it.

And so, we look pinout, solder the wiring and connect to the microcontroller.
')
Pinout for LPH8731-3C

Pinout for LPH8731-3C. (Taken from the RadioKot forum)
Where:
  • CS - Chip Select. When it is in the Low state, the chip is ready to receive information.
  • RESET - foot for resetting the controller. A reset signal is a transition from High -> Low -> High (according to the controller's specification, a minimum time of 5 ms).
  • RS - Used to determine the type of data transmitted (in the datasheet and in my designation as a CD). To send a command it must be able to Low, for data transmission - High.
  • CLK - serves as a clock signal for data transmission.
  • DAT - for data transfer.
  • VDD - according to the specification from + 1.6V to + 3.6V.
  • GND - I hope you can guess yourself ?;)
  • LED_A - both connectors for powering the backlight. It is better to give voltage through a resistor (it is possible without it, but in my case one of the LEDs started to overheat, which made the light on the screen).
  • LED_K is for GND.

By the way, some may have already noticed that SPI is used here for data transmission, so that CLK and DAT can be connected to the SPI MSP430 pin.


We get a "barrel organ"



Now we need to figure out how to communicate with the controller. For a screen controller, there are 2 types of received data - a command or data. To select the type of data used a separate pin. As for the rest, the data transfer procedure is the same.

The procedure for transferring data to the controller, taken from the datasheet. Here, for some reason, the status of the RS / CD pin is not indicated. By the way, if during the data transmission the state of CS changes to Low -> High, the data reception will stop. But at the end of the data transfer, it is not necessary to pull the CS up (but it is recommended).

A bit of evil code
The code is for CSS (TI's Code Composer Studio).
Here it is not completely, but only in pieces for example. Comments in English, since I like it better :)

LPH87313C.h
/**********************************************************/ /* Pins and outputs */ /**********************************************************/ // Chip Select line pin1.0 #define LCD_CS BIT7 #define LCD_CS_DIR P1DIR #define LCD_CS_OUT P1OUT // Hardware Reset pin1.1 #define LCD_RESET BIT6 #define LCD_RESET_DIR P1DIR #define LCD_RESET_OUT P1OUT // Command/Data mode line pin1.4 #define LCD_CD BIT3 #define LCD_CD_DIR P1DIR #define LCD_CD_OUT P1OUT // SPI #define SPI UCA0TXBUF 


LPH87313C.c
 void LCD_SendCmd(unsigned char Cmd) { LCD_CS_OUT |= LCD_CS; // set CS pin to High LCD_CD_OUT &= ~LCD_CD; // set CD pin to Low LCD_CS_OUT &= ~LCD_CS; SPI = Cmd; } void LCD_SendDat(unsigned char Data) { LCD_CD_OUT |= LCD_CD; // set CD pin to High SPI = Data; } 



Now we know how to send data to the controller (or at least we have an idea). Fortunately, the datasheet not only describes all the commands, but even has an example of the initial screen initialization. In general, it can be divided into 3 stages: we do a controller reset (hardware & software reset), set the initial parameter settings, turn on the display.

And there are a lot of commands sent
In principle, most of the parameters are set as is, but some things can be changed. For example, the order in which the values ​​will be written into memory (from top to bottom - from right to left / from bottom to top - from right to left / etc), contrast, as well as the color depth (256 colors or 4096).

LPH87313C.c
 void LCD_Init() { // Set pins to output direction LCD_CS_DIR |= LCD_CS; LCD_RESET_DIR |= LCD_RESET; LCD_CD_DIR |= LCD_CD; LCD_CS_OUT &= ~LCD_CS; LCD_RESET_OUT &= ~LCD_RESET; LCD_CD_OUT &= ~LCD_CD; __delay_cycles(160000); //wait 100ms (F_CPU 16MHz) LCD_RESET_OUT |= LCD_RESET; __delay_cycles(160000); LCD_SendCmd(0x01); //reset sw __delay_cycles(80000); LCD_SendCmd(0xc6); //initial escape LCD_SendCmd(0xb9); //Refresh set LCD_SendDat(0x00); __delay_cycles(160000); LCD_SendCmd(0xb6); //Display control LCD_SendDat(0x80); // LCD_SendDat(0x04); // LCD_SendDat(0x0a); // LCD_SendDat(0x54); // LCD_SendDat(0x45); // LCD_SendDat(0x52); // LCD_SendDat(0x43); // LCD_SendCmd(0xb3); //Gray scale position set 0 LCD_SendDat(0x02); // LCD_SendDat(0x0a); // LCD_SendDat(0x15); // LCD_SendDat(0x1f); // LCD_SendDat(0x28); // LCD_SendDat(0x30); // LCD_SendDat(0x37); // LCD_SendDat(0x3f); // LCD_SendDat(0x47); // LCD_SendDat(0x4c); // LCD_SendDat(0x54); // LCD_SendDat(0x65); // LCD_SendDat(0x75); // LCD_SendDat(0x80); // LCD_SendDat(0x85); // LCD_SendCmd(0xb5); //Gamma curve LCD_SendDat(0x01); // LCD_SendCmd(0xbd); //Common driver output select LCD_SendDat(0x00); // LCD_SendCmd(0xbe); //Power control LCD_SendDat(0x54); //0x58 before LCD_SendCmd(0x11); //sleep out __delay_cycles(800000); LCD_SendCmd(0xba); //Voltage control LCD_SendDat(0x2f); // LCD_SendDat(0x03); // LCD_SendCmd(0x25); //Write contrast LCD_SendDat(0x60); // LCD_SendCmd(0xb7); //Temperature gradient LCD_SendDat(0x00); // LCD_SendDat(0x00); // LCD_SendDat(0x00); // LCD_SendDat(0x00); // LCD_SendDat(0x00); // LCD_SendDat(0x00); // LCD_SendDat(0x00); // LCD_SendDat(0x00); // LCD_SendDat(0x00); // LCD_SendDat(0x00); // LCD_SendDat(0x00); // LCD_SendDat(0x00); // LCD_SendDat(0x00); // LCD_SendDat(0x00); // LCD_SendCmd(0x03); //Booster voltage ON __delay_cycles(800000); LCD_SendCmd(0x36); //Memory access control LCD_SendDat(0x48); // LCD_SendCmd(0x2d); //Color set LCD_SendDat(0x00); // LCD_SendDat(0x03); // LCD_SendDat(0x05); // LCD_SendDat(0x07); // LCD_SendDat(0x09); // LCD_SendDat(0x0b); // LCD_SendDat(0x0d); // LCD_SendDat(0x0f); // LCD_SendDat(0x00); // LCD_SendDat(0x03); // LCD_SendDat(0x05); // LCD_SendDat(0x07); // LCD_SendDat(0x09); // LCD_SendDat(0x0b); // LCD_SendDat(0x0d); // LCD_SendDat(0x0f); // LCD_SendDat(0x00); // LCD_SendDat(0x05); // LCD_SendDat(0x0b); // LCD_SendDat(0x0f); // LCD_SendCmd(0x3a); //interface pixel format LCD_SendDat(0x03); // 0x02 for 8-bit 0x03 for 12bit __delay_cycles(1600000); LCD_SendCmd(0x29); //Display ON } 



Our address is not a house or a street, our address is ...



Turning on the display, we will see either garbage or a white / black screen. This is because the controller changes the state of the matrix relative to the internal memory, and by turning it on, it will display everything that it “remembers”. To display any information (or its changes), it is enough to change the memory and the controller updates the display (it updates the display constantly with a certain frequency specified in the initial setting, by default the refresh rate is 85Hz). For example, to change the color of a pixel, you just need to write a new value in memory. But first you need to specify the address where to write the new value. If the computer simply sets the memory address and writes a new value, then you must specify the range of memory to which you can send data sequentially.

For example, to fill the entire screen you need to select the beginning of the recording area (x0, y0) and the end (x101, y80). And if you need to change the color of only one pixel, then respectively set the area [x, y] [x + 1, y + 1].

Having chosen a region, we can now simply send data and they will be sequentially stored in memory (and how exactly (from left to right, from top to bottom or vice versa) will depend on the initial setting). For example, choosing the area 40x40px, we will need to send 1600 consecutive values ​​(although this is not quite so, but in order), which will be stored in the memory and this area will be completely updated. And if you continue to send values, the update will continue from the next pixel (in this case, from the first).

Commands to set the area
 LCD_SendCmd(0x2A); //   X (x0 - , x1 - ) LCD_SendDat(x0); LCD_SendDat(x1); LCD_SendCmd(0x2B); //   Y (y0 - , y1 - ) LCD_SendDat(y0+1); //   Y   1,   0 LCD_SendDat(y1+1); LCD_SendCmd(0x2C); //            



Letter for you! True to Chinese ...



We have already figured out how to turn on the display and even how to choose an area for drawing, but how to send the color? The display can work with 2 color palettes:

In the case of 8-bit color, everything is simple - just send 8 bits for each color (namely R2R1R0G2G1G0B1B0, where R2R1R0 is 3 bits red and so on. Red and green are 3 bits, and blue is 2 bits).
But in the case of 12-bit color everything is dumb harder. There are already 4 bits for each shade. I will result a picture from.

As you can see, one and a half bytes is used to send one color. If you only need to change 1 pixel, then 2 bytes of information are sent, where in the second byte D3-D0 will not be used. And if you need to change 2 pixels, then it is enough to send 3 bytes (where D3-D0 of the second byte will be the beginning, and D7-D0 of the third byte will be the continuation of the color for the second pixel).

Again a piece of code
An example of the function to fill the entire screen.
 void LCD_Flush(unsigned char R, unsigned char G, unsigned char B) { volatile int i = 4040; volatile char B0, B1, B2; B0 = ((R << 4) & 0xF0) + (G & 0x0F); B1 = ((B << 4) & 0xF0) + (R & 0x0F); B2 = ((G << 4) & 0xF0) + (B & 0x0F); LCD_SendCmd(0x2A); LCD_SendDat(0); LCD_SendDat(100); LCD_SendCmd(0x2B); LCD_SendDat(1); LCD_SendDat(80); LCD_SendCmd(0x2C); while (i--) { LCD_CD_OUT |= LCD_CD; SPI = B0; SPI = B1; SPI = B2; } } 



And where is the promised watch?



And now the most difficult thing is to draw a watch. As you can see, they are stylized as a segment indicator, so to display the clock, it is enough to draw 2 types of segments (vertical and horizontal) in different places.
First you need to decide on the design. Thanks to the great program from MS - Paint, it really helped me with this;).

Here's what I got. Each segment is 12x4px in size (and vertical, respectively, on the contrary - 4x12px).

And now let's remember about the choice of the drawing area. You can also set the 12x4 area in the right place and draw a segment without redrawing the entire screen. If you take a closer look at the segment, you can see that it is almost completely filled with one color, except for the corners. So the segment drawing algorithm is quite simple: we start filling the memory with an empty color (unfortunately there is no transparency here, so we fill it with a background color), add checks for the upper right and lower left corner, and fill the last pixel with the background color too. Similarly, for vertical. And how to draw dots, I will not even tell :).

A set of swear words in an incomprehensible language
An example of a function for displaying a horizontal segment. greenBright is the “bright” color constant (for the active segment), greenDim is the color constant for the inactive segment. BG0, BG1, BG2 are bit constants for drawing the background.
And if you notice __delay_cycles - this is inexplicable magic, without which it does not work (although it’s likely that the hardware SPI doesn’t have time to send data, since it’s not sent in one clock cycle (but much faster, unlike if you implement sending it yourself)).
 void drawHorizontal(char type, unsigned char x, unsigned char y) { volatile unsigned char i = 22, B2, B1, B0; if (type) { B0 = greenBright; B1 = 0; B2 = (greenBright << 4) & 0xF0; } else { B0 = greenDim; B1 = 0; B2 = (greenDim << 4) & 0xF0; } LCD_SendCmd(0x2A); LCD_SendDat(x); LCD_SendDat(x+11); LCD_SendCmd(0x2B); LCD_SendDat(y+1); LCD_SendDat(y+4); LCD_SendCmd(0x2C); __delay_cycles(4); LCD_CD_OUT |= LCD_CD; SPI = BG0; SPI = (BG1 << 4) & 0xF0; __delay_cycles(2); SPI = B2; while(i--) { if (i == 17) { SPI = B0; __delay_cycles(2); SPI = (BG0 >> 4) & 0x0F; __delay_cycles(2); SPI = (BG0 << 4) & 0xF0 + (BG1 << 4) & 0x0F; continue; } if (i == 4) { SPI = BG0; SPI = (BG1 << 4) & 0xF0; __delay_cycles(2); SPI = B2; continue; } SPI = B0; SPI = B1; SPI = B2; } SPI = B0; __delay_cycles(2); SPI = (BG0 >> 4) & 0x0F; __delay_cycles(2); SPI = (BG0 << 4) & 0xF0 + (BG1 << 4) & 0x0F; } 



Now you need to turn the number into a set of segments (for example, to display 1, you need to draw only the right vertical segments). I decided this quite simply - I created an array of segment values ​​for different digits (from 0 to 9). Substituting a digit into it, I get an array with values ​​of 1/0 that controlled the drawing of the segments. For example, 1 means that the segment needs to be drawn, and 0 means that it is not necessary (or render it “inactive”). And knowing what and where to draw, make the function is not difficult.
Simple massif
 /******************************************************************************************** * Array for Clock * ____ * _|__1_|_ * |6| |2| * |_|____|_| * _|__7_|_ * |5| |3| * |_|____|_| * |__4_| * ********************************************************************************************/ static const char HH[10][7] = { {1,1,1,1,1,1,0}, // 0 {0,1,1,0,0,0,0}, // 1 {1,1,0,1,1,0,1}, // 2 {1,1,1,1,0,0,1}, // 3 {0,1,1,0,0,1,1}, // 4 {1,0,1,1,0,1,1}, // 5 {1,0,1,1,1,1,1}, // 6 {1,1,1,0,0,0,0}, // 7 {1,1,1,1,1,1,1}, // 8 {1,1,1,1,0,1,1} // 9 }; 

Clock drawing code
 void drawClock(char hh, char mm, char dots) { volatile char h0, h1, m0, m1; h0 = hh / 10; h1 = hh - (h0 * 10); m0 = mm / 10; m1 = mm - (m0 * 10); drawHorizontal(HH[h0][0], 9, 25); drawHorizontal(HH[h1][0], 31, 25); drawHorizontal(HH[m0][0], 58, 25); drawHorizontal(HH[m1][0], 80, 25); drawVertical(HH[h0][5], 6, 29); drawVertical(HH[h0][1], 20, 29); drawVertical(HH[h1][5], 28, 29); drawVertical(HH[h1][1], 42, 29); drawVertical(HH[m0][5], 55, 29); drawVertical(HH[m0][1], 69, 29); drawVertical(HH[m1][5], 77, 29); drawVertical(HH[m1][1], 91, 29); drawHorizontal(HH[h0][6], 9, 38); drawHorizontal(HH[h1][6], 31, 38); drawHorizontal(HH[m0][6], 58, 38); drawHorizontal(HH[m1][6], 80, 38); drawVertical(HH[h0][4], 6, 42); drawVertical(HH[h0][2], 20, 42); drawVertical(HH[h1][4], 28, 42); drawVertical(HH[h1][2], 42, 42); drawVertical(HH[m0][4], 55, 42); drawVertical(HH[m0][2], 69, 42); drawVertical(HH[m1][4], 77, 42); drawVertical(HH[m1][2], 91, 42); drawHorizontal(HH[h0][3], 9, 51); drawHorizontal(HH[h1][3], 31, 51); drawHorizontal(HH[m0][3], 58, 51); drawHorizontal(HH[m1][3], 80, 51); drawDots(dots); } 



And here we come to the end of the article. Hopefully, I was able to explain the principle of their work as thoroughly as possible :) And for a snack, a small video of how they work and blink “dots”.




PS
Perhaps this article contains spelling, grammatical and punctuation errors. If you find them, I urge you to send me a message in a personal, and not to write a comment.
The article is not written about the implementation of watches as such because they are not completed :) It was originally planned to make a small gadget, where the display, external flash memory and external calendar clock would be used, but since I accidentally bought the wrong watch, everything got up :) One more reason - I wanted to use a larger display, but I could not buy a suitable one, for example from the Chinese Nokla n95 8gb. Can someone tell me where to buy one?
If someone needs the source code - contact me, I can share it :) If anyone has any questions about the implementation of displaying characters on the screen (printing text), I can also share the source code (I didn’t write about it, here’s an article about watches: ), and on a separate post also does not pull). I can also share the library for working with the screen from Siemens CX75 (on the controller SSD-1286, there is even a datasheet), I wrote for myself, but accidentally burned it.

Source: https://habr.com/ru/post/159011/


All Articles