小編兜兜轉轉,又做回了FAE(現場應用工程師)好幾年。雖然有時也會煩躁,尤其是當各種客戶問題集中爆發的時候,但總體上還算喜歡這份工作。原因有兩點:一是不用像銷售那樣直接承擔業績壓力,尤其是在當前整體行情不佳的時期;二是不用像研發工程師那樣,整天悶在辦公室里,甚至有時還需要強制性加班。FAE的工作相對更自由和豐富一點,能不斷接觸新的人和事,當然也會遇到源源不斷的新問題。
從這篇文章開始,我計劃將實際客戶支持過程中遇到的案例、問題,以及接觸客戶時的部分有趣見聞和大家分享,希望能對一些人有所幫助。
本次案例分享
工程師C是一名嵌入式軟件工程師,在開發一個新項目。新項目相比老項目,硬件上段碼屏的引腳順序發生了部分變化,導致顯示錯亂。該工程師向我咨詢軟件上的解決方案。

這其實是一個非常典型的問題,之前也有人詢問過類似的問題,比如:

要解決這個問題,首先需要了解段碼式LCD的控制機制
控制原理詳解
1.基礎控制方式
比如下面這個4COM的 LCD

假設MCU的COM0-COM3對應連接LCD的COM0-COM3,SEG也是按照順序對應連接。
如果我們想控制1號數字顯示“1”,只需要將其對應的段1C和1B設置為1即可。
1C段對應MCU的COM1和SEG1,1B段對應MCU的COM3和SEG1,只需要設置MCU對應的LCD_RAM1和LCD_RAM3寄存器相應位即可。
LCD->RAM[1] = 0x00000002;
LCD->RAM[3] = 0x00000002;

上面這種寫法簡單粗暴,但實際項目中一般不這么用,因為直接賦值會覆蓋整個寄存器,影響其它段的顯示。更穩妥的做法是使用位操作,在寫入新數據時不破壞原有顯示。
2.改進的位操作函數
例如要控制2號數字顯示“2” 
可以調用以下函數來實現:
std_lcd_write(LCD_RAM_REGISTER0,~(LCD_SEG2 | LCD_SEG3), 0x02<<2);
std_lcd_write(LCD_RAM_REGISTER1,~(LCD_SEG2 | LCD_SEG3), 0x01<<2);
std_lcd_write(LCD_RAM_REGISTER2,~(LCD_SEG2 | LCD_SEG3), 0x02<<2);
std_lcd_write(LCD_RAM_REGISTER3,~(LCD_SEG2 | LCD_SEG3), 0x03<<2);
函數的第一個參數對應COMx,第二個參數是掩碼(指定要操作的SEG段,例如這里的SEG2和SEG3),最后一個參數是寫入的數值,比如第一行的0x02對應段2D為1,第二行的0x01對應段2E為1。其實0x02<<2 寫成0x00<<2|0x01<<3 更容易理解。

std_lcd_write()函數實現如下:
__STATIC_INLINE void std_lcd_write(uint32_t lcd_ram_index, uint32_t lcd_ram_mask, uint32_t pixel)
{
MODIFY_REG(LCD->RAM[lcd_ram_index], ~(lcd_ram_mask), pixel);
}
/* bit operations */
#define MODIFY_REG(REG, CLEARMASK, SETMASK) ((REG) = (((REG) & (~(CLEARMASK))) | (SETMASK)))
這個宏定義是嵌入式開發中一個非常經典和實用的位操作,用于安全地修改寄存器或變量的特定位。
如果硬件連接發生變化,例如LCD的SEG2實際連接到了MCU的SEG9引腳上,我們只需要在調用函數時修改掩碼和數值的移位操作即可適應,無需改動底層驅動:
std_lcd_write(LCD_RAM_REGISTER0,~(LCD_SEG9 | LCD_SEG3), 0x00<<9|0x01<<3);
std_lcd_write(LCD_RAM_REGISTER1,~(LCD_SEG9 | LCD_SEG3), 0x01<<9|0x00<<3);
std_lcd_write(LCD_RAM_REGISTER2,~(LCD_SEG9 | LCD_SEG3), 0x00<<9|0x01<<3);
std_lcd_write(LCD_RAM_REGISTER3,~(LCD_SEG9 | LCD_SEG3), 0x01<<9|0x01<<3);
3. 應用層封裝與硬件適配優化
我們已經了解如何顯示一個數字,但這在應用層仍不方便。我們需要一個函數能直接顯示一串數字,例如:
bsp_lcd_display_string((uint8_t*)"1234");
實現4個數字的任意顯示

該函數具體實現如下:
/**
* @brief LCD顯示字符串
* @param ptr 字符串指針
* @retval 無
*/
void bsp_lcd_display_string(uint8_t* ptr)
{
digit_position_t position = LCD_DIGIT_POSITION_1;
while ((*ptr != 0) & (position <= LCD_DIGIT_POSITION_MAX))
{
bsp_lcd_display_char(ptr, position);
ptr++;
position++;
}
}
/**
* @brief LCD數字顯示的位置
*/
typedef enum
{
LCD_DIGIT_POSITION_1 = 1,
LCD_DIGIT_POSITION_2 = 2,
LCD_DIGIT_POSITION_3 = 3,
LCD_DIGIT_POSITION_4 = 4,
LCD_DIGIT_POSITION_MAX = 4,
}digit_position_t;
bsp_lcd_display_char()函數實現如下:
/**
* @brief LCD顯示字符
* @param pixel 字符指針
* @param position 字符顯示位置
* @arg LCD_DIGIT_POSITION_1
* @arg LCD_DIGIT_POSITION_2
* @arg LCD_DIGIT_POSITION_3
* @arg LCD_DIGIT_POSITION_4
* @retval 無
*/
void bsp_lcd_display_char(uint8_t* pixel, digit_position_t position)
{
uint32_t number[4];
uint32_t position_mask_list[4] = {LCD_DIGIT_POSITION1_MASK,
LCD_DIGIT_POSITION2_MASK,
LCD_DIGIT_POSITION3_MASK,
LCD_DIGIT_POSITION4_MASK};
convert(pixel);
number[0] = ((pixel_digit_table[0] & 0x1) << ((position-1)*2)) | (((pixel_digit_table[0] & 0x2) >> 1) << ((position-1)*2 + 1));
number[1] = ((pixel_digit_table[1] & 0x1) << ((position-1)*2)) | (((pixel_digit_table[1] & 0x2) >> 1) << ((position-1)*2 + 1));
number[2] = ((pixel_digit_table[2] & 0x1) << ((position-1)*2)) | (((pixel_digit_table[2] & 0x2) >> 1) << ((position-1)*2 + 1));
number[3] = ((pixel_digit_table[3] & 0x1) << ((position-1)*2)) | (((pixel_digit_table[3] & 0x2) >> 1) << ((position-1)*2 + 1));
std_lcd_write(LCD_DIGIT_RAM0, position_mask_list[position-1], number[0]);
std_lcd_write(LCD_DIGIT_RAM1, position_mask_list[position-1], number[1]);
std_lcd_write(LCD_DIGIT_RAM2, position_mask_list[position-1], number[2]);
std_lcd_write(LCD_DIGIT_RAM3, position_mask_list[position-1], number[3]);
}
#define LCD_DIGIT_POSITION1_MASK ~(LCD_SEG0 | LCD_SEG1)
#define LCD_DIGIT_POSITION2_MASK ~(LCD_SEG2 | LCD_SEG3)
#define LCD_DIGIT_POSITION3_MASK ~(LCD_SEG4 | LCD_SEG5)
#define LCD_DIGIT_POSITION4_MASK ~(LCD_SEG6 | LCD_SEG7)
convert()函數實現如下:
/*-------------------------------------------functions------------------------------------------*/
/**
* @brief LCD屏數字字符轉換
* @param pixel 字符串指針
* @note 根據LCD的真值表轉換成對應的數字字符表,再轉換成字符
* @retval 無
*/
static void convert(uint8_t* pixel)
{
uint16_t ch = 0;
uint8_t loop = 0, index = 0;
if((*pixel <= '9') && ( *pixel >= '0'))
{
ch = digit_map[*pixel - '0'];
}
/* pixel_digit_table[0] 表示LCD_RAM0
pixel_digit_table[1] 表示LCD_RAM1
pixel_digit_table[2] 表示LCD_RAM2
pixel_digit_table[3] 表示LCD_RAM3
*/
for (loop = 12, index=0; index < 4; loop -= 4, index++)
{
pixel_digit_table[index] = (ch >> loop) & 0x0f;
}
}
以下是digit_map定義
/**
================================================================================
SEGMENT LCD MAPPING
================================================================================
LCD segment mapping:
-------------------------------------------
----A----
| |
F B
| |
----G----
| |
E C
| |
----D----
An LCD character coding is based on the following matrix:
COM 0 1 2 3
SEG(2 * n) { , E, F, A }
SEG(2 * n + 1) { D , C , G , B }
n is the position of the character on the LCD screen, n = 0, 1, 2, 3.
The character '2'for example is:
---------------------------------
SEG(2 * n) { 0, 1, 0, 1 }
SEG(2 * n + 1) { 1, 0, 1, 1 }
---------------------------------
'2' = 2 1 2 3
*/
/* 數字字符表 '0' --> '9' */
const uint16_t digit_map[10] =
{
/* 0 1 2 3 4 5 6 7 8 9 */
0x2313, 0x0202, 0x2123, 0x2223, 0x0232, 0x2231, 0x2331, 0x0203, 0x2333, 0x2233
};
上述代碼來源于MCU廠商的SDK例程,需要花點時間去理解一下,不然換了一個LCD屏幕或者引腳循序發生了變化,將無從下手去修改。
1)首先要理解digit_map數組的含義,注釋里其實已經解釋的很清楚了,就是將數字0-9基于LCD的真值表進行編碼。不同的LCD ABCDEF的位置可能會有不同,需要確認和修改。
2)convert()函數實現的是將digit_map數組中的數轉換到pixel_digit_table[4]數組中,比如數字0對應0x2123,轉換后pixel_digit_table[4]的4個值分別為0x02,0x01,0x02,0x03。
3)position_mask_list[4]對應的要顯示的4個數字分別對應的SEG組合。
4)bsp_lcd_display_char(uint8_t* pixel, digit_position_t position)函數中有以下4行代碼
number[0] = ((pixel_digit_table[0] & 0x1) << ((position-1)*2)) | (((pixel_digit_table[0] & 0x2) >> 1) << ((position-1)*2 + 1));
number[1] = ((pixel_digit_table[1] & 0x1) << ((position-1)*2)) | (((pixel_digit_table[1] & 0x2) >> 1) << ((position-1)*2 + 1));
number[2] = ((pixel_digit_table[2] & 0x1) << ((position-1)*2)) | (((pixel_digit_table[2] & 0x2) >> 1) << ((position-1)*2 + 1));
number[3] = ((pixel_digit_table[3] & 0x1) << ((position-1)*2)) | (((pixel_digit_table[3] & 0x2) >> 1) << ((position-1)*2 + 1));
實現的是將pixel_digit_table[4]的4個值轉換到std_lcd_write()函數需要的第三個參數值。但是這種寫法的前提是:MCU的SEG必須完全按照SEG0、SEG1、SEG2… 順序使用,并且和LCD的SEG一一對應連接。如果SEG的連接順序發生了變化,這么用就有問題了。
所以應當對其進行優化,優化的方案是引入一個映射表number_seg_list,將邏輯段與實際物理SEG引腳號解耦。
void bsp_lcd_display_char(uint8_t* pixel, digit_position_t position)
{
uint32_t number[4];
uint32_t position_mask_list[4] = {LCD_DIGIT_POSITION1_MASK,
LCD_DIGIT_POSITION2_MASK,
LCD_DIGIT_POSITION3_MASK,
LCD_DIGIT_POSITION4_MASK};
uint32_t number_seg_list[4][2] ={{0,1},
{2,3},
{4,5},
{6,7},
};
convert(pixel);
//number[0] = ((pixel_digit_table[0] & 0x1) << ((position-1)*2)) | (((pixel_digit_table[0] & 0x2) >> 1) << ((position-1)*2 + 1));
//number[1] = ((pixel_digit_table[1] & 0x1) << ((position-1)*2)) | (((pixel_digit_table[1] & 0x2) >> 1) << ((position-1)*2 + 1));
//number[2] = ((pixel_digit_table[2] & 0x1) << ((position-1)*2)) | (((pixel_digit_table[2] & 0x2) >> 1) << ((position-1)*2 + 1));
//number[3] = ((pixel_digit_table[3] & 0x1) << ((position-1)*2)) | (((pixel_digit_table[3] & 0x2) >> 1) << ((position-1)*2 + 1));
number[0] = ((pixel_digit_table[0] & 0x1) << (number_seg_list[position-1][0])) | (((pixel_digit_table[0] & 0x2) >> 1) << (number_seg_list[position-1][1]));
number[1] = ((pixel_digit_table[1] & 0x1) << (number_seg_list[position-1][0])) | (((pixel_digit_table[1] & 0x2) >> 1) << (number_seg_list[position-1][1]));
number[2] = ((pixel_digit_table[2] & 0x1) << (number_seg_list[position-1][0])) | (((pixel_digit_table[2] & 0x2) >> 1) << (number_seg_list[position-1][1]));
number[3] = ((pixel_digit_table[3] & 0x1) << (number_seg_list[position-1][0])) | (((pixel_digit_table[3] & 0x2) >> 1) << (number_seg_list[position-1][1]));
std_lcd_write(LCD_DIGIT_RAM0, position_mask_list[position-1], number[0]);
std_lcd_write(LCD_DIGIT_RAM1, position_mask_list[position-1], number[1]);
std_lcd_write(LCD_DIGIT_RAM2, position_mask_list[position-1], number[2]);
std_lcd_write(LCD_DIGIT_RAM3, position_mask_list[position-1], number[3]);
}
這樣不管SEG怎么連接,只需要修改position_mask_list和number_seg_lis中的定義即可。
如果COM的順序發生了變化有沒有問題呢?當然是可以的,COM變了只需要把上述函數的第一個參數調整一下即可。我之前還聽到一位工程師非常確信的告訴另外一位工程師,MCU的COM必須按照順序一一對應接LCD屏的COM口,這顯然是錯誤的。并不要求MCU的COM0一定要接LCD的COM0,只要COM接到COM,SEG接到SEG,不是COM接到SEG上就可以。
4. 實際適配案例
以上如果都搞清楚的話,以后再換個屏換個連接方式,就能輕松去控制了。
比如我們要控制下面這個屏:

MCU的SEG0-13 依次連接LCD的SEG1-14,SEG15-19連接LCD的SEG15-19,MCU的SEG14沒有用。
此外這個屏的排列方式有點非主流,數字1/2和數字3/4/5 ABCDEFG的排序方式竟然是反著的。
解決方法如下:
1)為兩種不同的段碼排列順序定義不同的digit_map(digit_map和digit_map2)。
2)根據顯示位置(position)決定使用哪個映射表進行轉換(convert或convert2)。
3)在number_seg_list中正確配置每個數字位置實際使用的物理SEG引腳號。
數字1/2對應的digit_map如下
/**
================================================================================
SEGMENT LCD MAPPING
================================================================================
LCD segment mapping:
-------------------------------------------
----A----
| |
F B
| |
----G----
| |
E C
| |
----D----
An LCD character coding is based on the following matrix:
COM 0 1 2 3
SEG(2 * n) { A , F, E, D }
SEG(2 * n + 1) { B , G , C , }
n is the position of the character on the LCD screen, n = 0, 1, 2, 3.
The character '2'for example is:
---------------------------------
SEG(2 * n) { 1, 0, 1, 1 }
SEG(2 * n + 1) { 1, 1, 0, 0 }
---------------------------------
'2' = 3 2 1 1
*/
/* 數字字符表 '0' --> '9' */
const uint16_t digit_map[10] =
{
/* 0 1 2 3 4 5 6 7 8 9 */
0x3131, 0x2020, 0x3211, 0x3221, 0x2320, 0x1321, 0x1331, 0x3020, 0x3331, 0x3321
};
數字3/4/5對應的digit_map如下
/**
An LCD character coding is based on the following matrix:
COM 0 1 2 3
SEG(2 * n) { B , G, C, }
SEG(2 * n + 1) { A , F , E , D }
*/
/* 數字字符表 '0' --> '9' */
const uint16_t digit_map2[10] =
{
/* 0 1 2 3 4 5 6 7 8 9 */
0x3232, 0x1010, 0x3122, 0x3112, 0x1310, 0x2312, 0x2332, 0x3010, 0x3332, 0x3312
};
void bsp_lcd_display_char(uint8_t* pixel, digit_position_t position)
{
uint32_t number[4];
uint32_t position_mask_list[5] = {LCD_DIGIT_POSITION1_MASK,
LCD_DIGIT_POSITION2_MASK,
LCD_DIGIT_POSITION3_MASK,
LCD_DIGIT_POSITION4_MASK,
LCD_DIGIT_POSITION5_MASK};
uint32_t number_seg_list[5][2] ={
{2,3},
{4,5},
{18,19},
{16,17},
{13,15}
};
if(position<LCD_DIGIT_POSITION_3)
convert(pixel);
else
convert2(pixel);
number[0] = ((pixel_digit_table[0] & 0x1) << (number_seg_list[position-1][0])) | (((pixel_digit_table[0] & 0x2) >> 1) << (number_seg_list[position-1][1]));
number[1] = ((pixel_digit_table[1] & 0x1) << (number_seg_list[position-1][0])) | (((pixel_digit_table[1] & 0x2) >> 1) << (number_seg_list[position-1][1]));
number[2] = ((pixel_digit_table[2] & 0x1) << (number_seg_list[position-1][0])) | (((pixel_digit_table[2] & 0x2) >> 1) << (number_seg_list[position-1][1]));
number[3] = ((pixel_digit_table[3] & 0x1) << (number_seg_list[position-1][0])) | (((pixel_digit_table[3] & 0x2) >> 1) << (number_seg_list[position-1][1]));
std_lcd_write(LCD_DIGIT_RAM0, position_mask_list[position-1], number[0]);
std_lcd_write(LCD_DIGIT_RAM1, position_mask_list[position-1], number[1]);
std_lcd_write(LCD_DIGIT_RAM2, position_mask_list[position-1], number[2]);
std_lcd_write(LCD_DIGIT_RAM3, position_mask_list[position-1], number[3]);
}
通過以上調整,即可調用bsp_lcd_display_string((uint8_t*)"12345")正常顯示。

關注我們:
掃碼加入嵌入式交流群:
