Introduction
This blog post discusses how to take segment LCD glass and attach it to ARM Cortex-M series microcontrollers known as the Renesas RA family.
Often projects might connect via I2C or SPI to displays that have on-board LCD controllers, however they are usually a little more expensive than controller-less LCD glass, and consume more power. The specific Renesas RA4M1 microcontroller which I used in this blog post (the information applies many other RA family microcontrollers too) has an internal controller so that cheaper controller-less LCD glass like in the photo above can be directly attached, and this is great for very low power battery-operated projects. The blog post then uses the LCD in an example project to build a simple voltmeter using an analog-to-digital converter (ADC) that is also present inside the RA microcontroller.
If you’re new to Renesas RA microcontrollers, please read the first blog post in this short series:
Working with the Renesas RA4M1 Microcontroller
This blog post assumes that you have already read that earlier one.
Choosing a Segment LCD
Broadly speaking there are two main types of segment LCDs; statically-driven, and multiplexed. The former require a lot of pins, so it’s not very feasible to use displays with more than about three or four seven-segment characters. The benefit though is that very high contrast characters can be displayed. On the other hand, multiplexed displays can have more digits with fewer (relatively, there’s still a lot!) connections. The display contrast is lower however, but still very usable!
For this blog post, I went for a multiplexed display with four 7-segment characters. It requires 12 connections to the microcontroller, so it’s certainly more of a pain to wire up compared to I2C or SPI displays.
The LCD datasheet can be inspected to see the pin numbering.
Although I chose to use the display in the photo above, you could choose to use almost any segment LCD display, provided that there are enough free GPIO pins on the microcontroller to support it.
Multiplexed displays have a number of COM connections, and 4 is a popular value. To drive such a display with 4 COM connections, the microcontroller will need to output a waveform with a complex pattern that includes voltages in-between the normal logic high and logic low levels. The Renesas RA microcontrollers require a few resistors or capacitors to be added to the circuit to enable this capability. It is described further below.
Adding and Configuring the LCD Peripheral
The Renesas RA microcontrollers are programmed using the Renesas e2studio development environment which was discussed in the earlier blog post. As described in that blog post, in the Project Explorer, double-click on configuration.xml, to see the FSP Configuration pane appear. Click on the Stacks tab, and go to New Stack, then select Graphics->Segment LCD (r_slcdc). A graphical building-block will appear in your stacks display.
You should also bring up the help for r_slcdc, (go to the Renesas FSP Documentation and search for r_slcdc there) and you’ll see this helpful table:
Since the LCD screen I’m using has four COM connections, that means that a bias of 1/3 is needed (i.e. 1 divided by one less than the number of COM connections). The number of Slices is equal to the number of COM connections. Looking at the table above, this means that I can drive the LCD using any of three methods that the RA microcontroller supports: External Resistance, Internal Boost, or Capacitor Split.
Click on the Properties tab as shown in the screenshot below, and configure the bias, slices, and drive method as shown. There are two different waveform methods, and it may be LCD-dependent which one provides better results. I left that setting at Waveform A.
Next, click on the Pins tab.
The help documentation directs the user to section 45 of the RA4M1 user manual, which explains the three methods. Both the Internal Boost and Capacitor Split methods require 0.47uF capacitors to be connected to certain pins.
The user manual also lists what pins can be used for the COM and SEG connections; I’ve labelled them in blue on the diagram below.
Select the number of slices, and enable the pins as shown below. If you see any red error circles appear, that means that the pins may be already used elsewhere. For instance, the development board has pin P106 used for the on-board LED. You’ll have to resolve the conflict by setting the LED GPIO pin to disabled, so that the LCD peripheral can claim it. After everything has green check-marks as shown below, save the configuration using Ctrl-S.
Click on Generate Project Content for the LCD code to be auto-generated (it will appear in the Project Explorer, in the ra/fsp/src/r_slcdc sub-folder, and the API will be in the ra/fsp/inc/api/r_slcdc_api.h file).
Creating LCD code for your Application
The help documentation page (i.e. https://renesas.github.io/fsp/ and search for r_slcdc) contains a complete example.
I used that example to add LCD functionality into the hal_entry.cpp file, which you’ll recall from the earlier blog post, contains the hal_entry function which gets executed from the auto-generated main() function.
I added the following line to the hal_entry.cpp file to get access to the LCD functions:
#include "r_slcdc_api.h"
I created an LCD initialization function based on the code above:
void lcd_init(void) { fsp_err_t err; /* Open SLCDC driver */ err = R_SLCDC_Open(&g_slcdc0_ctrl, &g_slcdc0_cfg); /* Handle any errors. This function should be defined by the user. */ assert(FSP_SUCCESS == err); /* When using internal boost mode this delay is required to allow the boost circuit to charge. See RA4M1 User's * Manual (R01UH0887EJ0100) 8.2.18 "Segment LCD Source Clock Control Register (SLCDSCKCR)" for details. */ R_BSP_SoftwareDelay(5, BSP_DELAY_UNITS_MILLISECONDS); /* Start SLCDC output */ err = R_SLCDC_Start(&g_slcdc0_ctrl); assert(FSP_SUCCESS == err); }
Turning on any LCD segments is easy, using the following line of code:
R_SLCDC_Write(&g_slcdc0_ctrl, 0, seg_data, NUM_QUAD_SEGMENTS);
The LCD screen has 32 segments, so NUM_QUAD_SEGMENTS is set to 8. The array called seg_data contains the segment data in the lower 4 bits (the upper 4 bits are unused; they are needed for blinking scenarios), so the array is of length 8 bytes (number of individual segments divided by 4). It is up to the software developer to write code that can map text numbers (and possibly some ASCII characters) into the segment data.
I created this bitmap array, which covers decimal digits as well as any ASCII characters which can fit in 7 segments (not all can of course).
const uint8_t char_bitmap[] = { 0xd7, 0x50, 0xb5, 0xf1, 0x72, 0xe3, 0xe7, 0x51, 0xf7, 0xf3, /* 0-9 */ 0x77, 0xe6, 0x87, 0xf4, 0xa7, 0x27, /*A-F*/ 0xc7, 0x66, 0x40, 0xd0, 0x00, 0x86, 0x65, 0x64, 0xe4, /*G-O*/ 0x37, 0x73, 0x24, 0xe3, 0xa6, 0xc4, 0x00, 0xc5, /*P-W*/ 0x00, 0xf2, 0x00, /*X-Z*/ 0x20, /*- (hyphen)*/ 0x01, 0x10, 0x40, 0x80, 0x04, 0x02 /*single segments*/ };
The following function will accept a 4-character ASCII string, and output it to the LCD:
// lcd_display accepts a 4-byte array char_data, and a decimal point indicator dp (0-3) // example to display the number 123.4: // uint8_t char_data[] = "1234"; // lcd_display(char_data, 1); void lcd_display(const char* char_data, uint8_t dp) { fsp_err_t err; uint8_t i; uint8_t seg_data[8]; uint8_t seg_idx; uint8_t seg_mask; for (i=0; i<4; i++) { seg_idx = i << 1; if ((char_data[i] >= '0') && (char_data[i] <= '9')) { seg_mask = char_bitmap[(char_data[i] - '0') + IDX_NUM]; } else if ((char_data[i] >= 'A') && (char_data[i] <= 'Z')) { seg_mask = char_bitmap[(char_data[i] - 'A') + IDX_ALPHA]; } else if (char_data[i] == '-') { seg_mask = char_bitmap[IDX_HYPHEN]; } else if ((char_data[i] >= 'a') && (char_data[i] <= 'f')) { seg_mask = char_bitmap[(char_data[i] - 'a') + IDX_ROT]; } else if (char_data[i] == ' ') { seg_mask = 0x00; } else { // unrecognized character seg_mask = char_bitmap[IDX_HYPHEN]; } seg_data[seg_idx] = seg_mask & 0x0f; seg_data[seg_idx+1] = seg_mask >> 4; } switch(dp) { case 0: break; case 1: seg_data[6] |= 0x08; break; case 2: seg_data[4] |= 0x08; break; case 3: seg_data[2] |= 0x08; break; default: break; } err = R_SLCDC_Write(&g_slcdc0_ctrl, 0, seg_data, NUM_QUAD_SEGMENTS); assert(FSP_SUCCESS == err); }
The function accepts a value which will turn on one of the decimal places if required. For instance, if it is desired to display the text ABCD then the function is called like this:
lcd_display(“ABCD”, 0);
If the desired text is ABC.D then use the following code:
lcd_display(“ABCD”, 1);
Connecting the LCD to the Microcontroller
The RA4M1 evaluation board doesn’t have any connections convenient for an LCD. However, there are plenty of unallocated header pins, and so it’s easy to patch wires to create your own LCD header pin area. It takes a while though. It’s probably better to just build a custom PCB at this stage! Anyway, for now I persevered with the evaluation board. In the photo below, I patched enough wires to have up to 16 SEG pins, even though the LCD I’m using only has 8 SEG pins. I might swap out the LCD at a later date for a different one, so I wanted the board to be ready for that.
In the photo above, for the LCD bias voltage generation, I used pairs of 1uF capacitors in series, because I didn’t have any 0.47uF capacitors handy.
Here’s a diagram showing all the pins used for displays with up to 16 SEG pins:
There are a few points to note here. The purple pads can be shorted, to allow a potentiometer to be connected to GPIO P0_4 (P004). This is unrelated to the LCD, but is useful for the next section (Adding the ADC Peripheral).
Secondly, a few pads need to be cut, they are indicated in the diagram. Finally, a very important point is that SEG11 is unusable, because that GPIO pin is required for USB. Since I wanted a total of 16 LCD segments, I decided to use a span on 17 pins (SEG0 to SEG17), and not use the SEG11 GPIO.
Adding the ADC Peripheral
The RA4M1 microcontroller contains a high-resolution (14-bit) analog-to-digital converter (ADC). I decided to enable it, so that the LCD could be used to display the voltage present at an ADC input.
As before, use the FSP Configuration window to add a new stack; this time it is an ADC stack:
Select the Pins tab, and navigate to ADC0 and enable the pin that is to be used to accept ADC input:
Now go back to the Stacks tab, click to highlight the ADC block that was added before, and then click on the Properties tab. Enable Channel 0 in the Channel Scan Mask:
Hit Ctrl-S to save the FSP Configuration and then click on Generate Project Content as before.
Go to the ADC documentation (at the documentation link https://renesas.github.io/fsp/ and search for ADC, and select r_adc).
Scroll down, and there is an entire example there!
I split up that code to create these functions:
adc_init()
adc_start_scan();
adc_wait_conversion();
int is_adc_conversion_done();
uint16_t adc_read_result(chan_number);
Here is the actual code for all of these functions:
void adc_init (void) { fsp_err_t err = FSP_SUCCESS; err = R_ADC_Open(&g_adc0_ctrl, &g_adc0_cfg); assert(FSP_SUCCESS == err); err = R_ADC_ScanCfg(&g_adc0_ctrl, &g_adc0_channel_cfg); assert(FSP_SUCCESS == err); } void adc_start_scan(void) { R_ADC_ScanStart(&g_adc0_ctrl); } void adc_wait_conversion(void) { adc_status_t status; status.state = ADC_STATE_SCAN_IN_PROGRESS; while (ADC_STATE_SCAN_IN_PROGRESS == status.state) { (void) R_ADC_StatusGet(&g_adc0_ctrl, &status); } } int is_adc_conversion_done(void) { adc_status_t status; R_ADC_StatusGet(&g_adc0_ctrl, &status); if (status.state == ADC_STATE_SCAN_IN_PROGRESS) { return(0); } return(1); } uint16_t adc_read_result(adc_channel_t achan) { fsp_err_t err = FSP_SUCCESS; uint16_t channel0_conversion_result; err = R_ADC_Read(&g_adc0_ctrl, achan, &channel0_conversion_result); assert(FSP_SUCCESS == err); return(channel0_conversion_result); }
Now the code is easy to use in the hal_entry function (which if you’ll recall, is where the application code can reside). I also needed to include the ADC API in that file:
#include "r_adc_api.h"
Putting it together: Voltmeter App
For a simple example, I decided to combine the LCD and ADC code, to report voltages applied to the ADC input pin.
The evaluation board has a space to solder a trimmer potentiometer, and it is connected to pin P004. I modified the FSP configuration (see the earlier screenshots; it is done in the same way that P000 was added previously) to add P004 to the ADC channels.
The photo above shows where to solder an optional 100nF capacitor for reducing noise on the variable voltage output from the potentiometer.
The displayed voltage should be in the range of 0.000 to 3.300 Volts, for which the ADC will report a value between 0 and 16384 (i.e. 2^14) respectively. The following code will do the conversion; it shows how to read the ADC value, and converts to a number between 0 and 3.3:
uint16_t adc_result;
float dispfloat;
adc_start_scan();
adc_wait_conversion();
adc_result = adc_read_result(ADC_CHANNEL_4);
dispfloat = ((float)adc_result)/4964.8485f; // 16384/3.3
Next, the number needs to be converted to a string, so that the earlier lcd_display function can be used to display it.
I created the following code to do that:
// convert a number between 0.000 and 9.999 into text "0000" to "9999" char* float2text(float num) { char text[14]; static char ret_text[5]; int num_int; int num_frac; int i; num_int = (int)num; num_frac = (int)((num - (float)num_int) * 1000); sprintf(text, "%d%03d", num_int, num_frac); for (i=0; i<4; i++) { ret_text[i]=text[i]; } ret_text[4]='\0'; return ret_text; }
Finally, the following three lines of code can be used to call that function and then send the text to the display, with the decimal point inserted at the correct position:
char* disptxtptr;
disptxtptr = float2text(dispfloat);
lcd_display(disptxtptr, 3);
The project is complete! Rotating the potentiometer causes the displayed value to change between 0.000 and 3.300 as it should.
Summary
Adding controller-less segment LCD screens to a project can be useful for low-power projects. It takes more wiring than I2C or SPI operated displays with a built-in controller however.
This blog post discussed how to connect arbitrary multiplexed LCD screens to Renesas RA series microcontrollers that contain an integrated Segment LCD peripheral. The 14-bit ADC was used to measure the voltage from a potentiometer and send it to the display.
Together with the previous blog post, it should now be clear how to begin using GPIO, DAC, Timers, USB Serial, Segment LCD (SLCD) and ADC peripherals within Renesas RA series microcontrollers in projects.
Thanks for reading!