2023寒假一起练 - 基于 MSP-EXP430F5529LP 开发板和扩展板上的外设实现菜单功能
一、项目描述
MSP-EXP430F5529LP 开发板是一款针对 MSP430F5529 USB 微控制器的廉价而简单的开发套件,它为 MSP430 MCU 提供了一种简单的使用方法,具有用于编程和调试的板载仿真器,以及用于简单用户界面的按钮。本次活动,硬禾提供了 MSP-EXP430F5529LP 开发板和 IO 扩展板,其中 IO 扩展板上包含了下列外设:
- 按键和旋转编码器 (模拟信号)
- 双电位计控制 (数字信号)
- RGB 三色 LED
- 1.44 寸 128*128 LCD (SPI 接口,st7735 芯片)
- MMA7660 三轴姿态传感器
- 电阻加热
- NST112 温度传感器
在本项目中,我使用了下列硬件:
- MSP-EXP430F5529LP 开发板
- 按键和旋转编码器 (模拟信号)
- RGB 三色 LED
- 1.44 寸 128*128 LCD (SPI 接口,st7735 芯片)
备注:
因为我另外一个参与的项目 ESP32-S2 也是做同样的菜单展示功能,所以文章中部分图片和描述是一样的。
二、设计思路
首先使用 MSP-EXP430F5529LP 实现控制使用 st7735 芯片的 LCD (我使用软件控制 GPIO 实现 SPI 功能),在屏幕的主界面展示主菜单,主菜单包含五个条目,模块上电后主菜单默认选中第一个条目,此时条目的背景色为黑色,条目的字体为白色。通过旋转编码器可以实现条目切换,向左旋转则条目向下切换,向右旋转则条目向上切换。此时单击旋转按钮,进入当前条目并展示该条目下的内容,五个条目的内容如下:
- 条目一 - Blue LED,进入该条目,在 LCD 上会周期性地展示蓝色圆圈,同时蓝色 LED 也同步闪烁;
- 条目二 - Green LED,进入该条目,在 LCD 上会周期性地展示绿色正方形,同时绿色 LED 也同步闪烁;
- 条目三 - RED LED,进入该条目,在 LCD 上会周期性地展示红色圆圈,同时红色 LED 也同步闪烁;
- 条目四 - Color Test,进入该条目,在 LCD 上会周期性地全屏展示红绿蓝三条竖行色块;
- 条目五 - Triangle Test,进入该条目,在 LCD 上会显示一个白色的三角形,此时旋转编码器,三角形可以同步地按照顺时针和逆时针方向转动,如果单击旋转编码器,则三角形会连续转动360度之后静止。
在子菜单中单击 S1 按键,会退出当前子界面。
三、硬件连接
- 旋转编码器和按键共用一个模拟输出 Aout,连接到 MSP-EXP430F5529LP 的 P6.0 (Analog In - A0);
-
LCD 与 MSP-EXP430F5529LP 的管脚连接如下:
128*128 LCD (ST7735) | MSP-EXP430F5529LP |
LCD_SDA | P3.0 |
LCD_SCL | P3.2 |
LCD_CSn | P2.6 |
LCD_DCx | P2.7 |
LCD_RESn | P3.7 |
- RGB LED 与 MSP-EXP430F5529LP 的管脚连接如下:
RGB LED | MSP-EXP430F5529LP |
BLUE_LED (LED1) | P2.5 |
GREEN_LED (LED2) | P2.4 |
RED_LED (LED3) | P1.5 |
- 为了避免 IO 板上的电阻烫手,需要把下列管脚输出低电平:
SI2302 | MSP-EXP430F5529LP |
V_HEAT | P1.4 |
四、软件流程
五、开发环境的搭建
TI 为 MSP430 主推的是 Code Composer Studio 集成式开发环境 (IDE),IDE 分别提供了Windows、Linux和macOS平台的安装程序,在本次开发中我使用的是 Windows 开发环境,从https://www.ti.com.cn/tool/cn/CCSTUDIO 下载安装了 CCS12.2.0.00009_win64.zip 安装包。
具体的安装过程和使用方法见:https://software-dl.ti.com/ccs/esd/documents/users_guide_zh/index.html
工程配置方法如下,本次项目我在官方示例程序 adc12_a_ex5_repeatedSingle 基础上进行修改:
- 选择官方提供的示例项目:
# 在 CCS 中,选择 View->Getting Started,然后选择 Browser and Import Examples # 在 Select board or device 栏位,输入 MSP-EXP430F5529LP,下方会列出 Examples
- 选择 adc12_a_ex5_repeatedSingle 并导入到工作区:
- 编译工程:
在 CCS 界面,选择 Project->Build Project
- 工程调试:
在 CCS 界面,选择 Run->Debug
六、代码和功能展示
- LCD st7735 驱动代码
- 代码如下(这个屏幕有蓝边的问题,设置offset可以消除):
#define CONFIG_WIDTH 128 // Width - 128px #define CONFIG_HEIGHT 128 // Height - 128px // 设置offset可以消除屏幕的蓝边 #define CONFIG_OFFSETX 2 // to fix display issue #define CONFIG_OFFSETY 3 // to fix display issue #define LCD_SDA P3DIR |= BIT0 #define LCD_RS P2DIR |= BIT7 #define LCD_CS P2DIR |= BIT6 #define LCD_RST P3DIR |= BIT7 #define LCD_SCL P3DIR |= BIT2 #define LCD_LED P6DIR |= BIT5 #define LCD_CS_SET P2OUT |= BIT6 #define LCD_RS_SET P2OUT |= BIT7 #define LCD_SDA_SET P3OUT |= BIT0 #define LCD_SCL_SET P3OUT |= BIT2 #define LCD_RST_SET P3OUT |= BIT7 #define LCD_LED_SET P6OUT |= BIT5 #define LCD_CS_CLR P2OUT &= ~BIT6 #define LCD_RS_CLR P2OUT &= ~BIT7 #define LCD_SDA_CLR P3OUT &= ~BIT0 #define LCD_SCL_CLR P3OUT &= ~BIT2 #define LCD_RST_CLR P3OUT &= ~BIT7 #define LCD_LED_CLR P6OUT &= ~BIT5 void SPI_WriteData(uint8_t Data) { uint8_t i = 0; for (i = 8;i > 0; i--) { if (Data & 0x80) LCD_SDA_SET; else LCD_SDA_CLR; LCD_SCL_CLR; LCD_SCL_SET; Data <<= 1; } } void Lcd_WriteIndex(uint8_t Index) { LCD_CS_CLR; LCD_RS_CLR; SPI_WriteData(Index); LCD_CS_SET; } void Lcd_WriteData(uint8_t Data) { LCD_CS_CLR; LCD_RS_SET; SPI_WriteData(Data); LCD_CS_SET; } void LCD_WriteData_16Bit(uint16_t Data) { LCD_CS_CLR; LCD_RS_SET; SPI_WriteData(Data >> 8); SPI_WriteData(Data); LCD_CS_SET; } void Lcd_WriteReg(uint8_t Index,uint8_t Data) { Lcd_WriteIndex(Index); Lcd_WriteData(Data); } void LCD_GPIO_Init(void) { LCD_SDA; LCD_RS; LCD_CS; LCD_RST; LCD_SCL; } // LCD Init For 1.44Inch LCD Panel with ST7735R. void Lcd_Init(void) { LCD_GPIO_Init(); Lcd_Reset(); //Reset before LCD Init. } void spi_master_write_byte(const uint8_t *Data, uint16_t DataLength) { if (DataLength > 0) { LCD_CS_CLR; SPI_WriteData(*Data); LCD_CS_SET; } } void spi_master_write_comm_byte(TFT_t *dev, uint8_t cmd) { static uint8_t Byte = 0; Byte = cmd; LCD_RS_CLR; spi_master_write_byte(&Byte, 1); } void spi_master_write_comm_word(TFT_t *dev, uint16_t cmd) { static uint8_t Byte[2]; Byte[0] = (cmd >> 8) & 0xFF; Byte[1] = cmd & 0xFF; LCD_RS_CLR; spi_master_write_byte(&Byte[0], 1); spi_master_write_byte(&Byte[1], 1); } void spi_master_write_data_byte(TFT_t *dev, uint8_t data) { static uint8_t Byte = 0; Byte = data; LCD_RS_SET; spi_master_write_byte(&Byte, 1); } void spi_master_write_data_word(TFT_t *dev, uint16_t data) { static uint8_t Byte[2]; Byte[0] = (data >> 8) & 0xFF; Byte[1] = data & 0xFF; LCD_RS_SET; spi_master_write_byte(&Byte[0], 1); spi_master_write_byte(&Byte[1], 1); } void spi_master_write_addr(TFT_t *dev, uint16_t addr1, uint16_t addr2) { static uint8_t Byte[4]; Byte[0] = (addr1 >> 8) & 0xFF; Byte[1] = addr1 & 0xFF; Byte[2] = (addr2 >> 8) & 0xFF; Byte[3] = addr2 & 0xFF; LCD_RS_SET; spi_master_write_byte(&Byte[0], 1); spi_master_write_byte(&Byte[1], 1); spi_master_write_byte(&Byte[2], 1); spi_master_write_byte(&Byte[3], 1); } oid lcdInit(TFT_t *dev, uint16_t model, int width, int height, int offsetx, int offsety) { dev->_model = model; dev->_width = width; dev->_height = height; dev->_offsetx = offsetx; dev->_offsety = offsety; dev->_font_direction = DIRECTION0; dev->_font_fill = false; dev->_font_underline = false; // printf("Your TFT is ST7735"); // printf("Screen width:%d", width); // printf("Screen height:%d", height); spi_master_write_comm_byte(dev, 0xC0); // Power Control 1 spi_master_write_data_byte(dev, 0x23); spi_master_write_comm_byte(dev, 0xC1); // Power Control 2 spi_master_write_data_byte(dev, 0x10); spi_master_write_comm_byte(dev, 0xC5); // VCOM Control 1 spi_master_write_data_byte(dev, 0x3E); spi_master_write_data_byte(dev, 0x28); spi_master_write_comm_byte(dev, 0xC7); // VCOM Control 2 spi_master_write_data_byte(dev, 0x86); spi_master_write_comm_byte(dev, 0x36); // Memory Access Control spi_master_write_data_byte(dev, 0xC8); // Right top start, BGR color filter panel C8/68/A8/08 spi_master_write_comm_byte(dev, 0x3A); // Pixel Format Set spi_master_write_data_byte(dev, 0x55); // 65K color: 16-bit/pixel spi_master_write_comm_byte(dev, 0x20); // Display Inversion OFF spi_master_write_comm_byte(dev, 0xB1); // Frame Rate Control spi_master_write_data_byte(dev, 0x00); spi_master_write_data_byte(dev, 0x18); spi_master_write_comm_byte(dev, 0xB6); // Display Function Control spi_master_write_data_byte(dev, 0x08); spi_master_write_data_byte(dev, 0xA2); // REV:1 GS:0 SS:0 SM:0 spi_master_write_data_byte(dev, 0x27); spi_master_write_data_byte(dev, 0x00); spi_master_write_comm_byte(dev, 0x26); // Gamma Set spi_master_write_data_byte(dev, 0x01); spi_master_write_comm_byte(dev, 0xE0); // Positive Gamma Correction spi_master_write_data_byte(dev, 0x0F); spi_master_write_data_byte(dev, 0x31); spi_master_write_data_byte(dev, 0x2B); spi_master_write_data_byte(dev, 0x0C); spi_master_write_data_byte(dev, 0x0E); spi_master_write_data_byte(dev, 0x08); spi_master_write_data_byte(dev, 0x4E); spi_master_write_data_byte(dev, 0xF1); spi_master_write_data_byte(dev, 0x37); spi_master_write_data_byte(dev, 0x07); spi_master_write_data_byte(dev, 0x10); spi_master_write_data_byte(dev, 0x03); spi_master_write_data_byte(dev, 0x0E); spi_master_write_data_byte(dev, 0x09); spi_master_write_data_byte(dev, 0x00); spi_master_write_comm_byte(dev, 0xE1); // Negative Gamma Correction spi_master_write_data_byte(dev, 0x00); spi_master_write_data_byte(dev, 0x0E); spi_master_write_data_byte(dev, 0x14); spi_master_write_data_byte(dev, 0x03); spi_master_write_data_byte(dev, 0x11); spi_master_write_data_byte(dev, 0x07); spi_master_write_data_byte(dev, 0x31); spi_master_write_data_byte(dev, 0xC1); spi_master_write_data_byte(dev, 0x48); spi_master_write_data_byte(dev, 0x08); spi_master_write_data_byte(dev, 0x0F); spi_master_write_data_byte(dev, 0x0C); spi_master_write_data_byte(dev, 0x31); spi_master_write_data_byte(dev, 0x36); spi_master_write_data_byte(dev, 0x0F); spi_master_write_comm_byte(dev, 0x11); // Sleep Out delayMS(120); spi_master_write_comm_byte(dev, 0x29); // Display ON if (dev->_bl >= 0) { // back light is always on } }
- 代码如下(这个屏幕有蓝边的问题,设置offset可以消除):
- ADC 采样和按键消抖代码
- 代码如下:
#define ADC_DATA_LEN 20 volatile uint16_t results[ADC_DATA_LEN]; void adc_init(void) { //Enable A/D channel A0 GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P6, GPIO_PIN0); //Initialize the ADC12_A Module /* * Base address of ADC12_A Module * Use internal ADC12_A bit as sample/hold signal to start conversion * USE MODOSC 5MHZ Digital Oscillator as clock source * Use default clock divider of 1 */ ADC12_A_init(ADC12_A_BASE, ADC12_A_SAMPLEHOLDSOURCE_SC, ADC12_A_CLOCKSOURCE_ADC12OSC, ADC12_A_CLOCKDIVIDER_1); ADC12_A_enable(ADC12_A_BASE); /* * Base address of ADC12_A Module * For memory buffers 0-7 sample/hold for 256 clock cycles * For memory buffers 8-15 sample/hold for 4 clock cycles (default) * Enable Multiple Sampling */ ADC12_A_setupSamplingTimer(ADC12_A_BASE, ADC12_A_CYCLEHOLD_256_CYCLES, ADC12_A_CYCLEHOLD_4_CYCLES, ADC12_A_MULTIPLESAMPLESENABLE); //Configure Memory Buffer /* * Base address of the ADC12_A Module * Configure memory buffer 0 * Map input A0 to memory buffer 0 * Vref+ = AVcc * Vref- = AVss * Memory buffer 0 is not the end of a sequence */ ADC12_A_configureMemoryParam param = {0}; param.memoryBufferControlIndex = ADC12_A_MEMORY_0; param.inputSourceSelect = ADC12_A_INPUT_A0; param.positiveRefVoltageSourceSelect = ADC12_A_VREFPOS_AVCC; param.negativeRefVoltageSourceSelect = ADC12_A_VREFNEG_AVSS; param.endOfSequence = ADC12_A_NOTENDOFSEQUENCE; ADC12_A_configureMemory(ADC12_A_BASE ,¶m); //Enable memory buffer 0 interrupt ADC12_A_clearInterrupt(ADC12_A_BASE, ADC12IFG0); ADC12_A_enableInterrupt(ADC12_A_BASE, ADC12IE0); //Enable/Start first sampling and conversion cycle /* * Base address of ADC12_A Module * Start the conversion into memory buffer 0 * Use the repeated single-channel */ ADC12_A_startConversion(ADC12_A_BASE, ADC12_A_MEMORY_0, ADC12_A_REPEATED_SINGLECHANNEL); //Enable interrupts __bis_SR_register(GIE); } #if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__) #pragma vector=ADC12_VECTOR __interrupt #elif defined(__GNUC__) __attribute__((interrupt(ADC12_VECTOR))) #endif void ADC12ISR(void) { static uint8_t index = 0; switch (__even_in_range(ADC12IV,34)){ case 0: break; //Vector 0: No interrupt case 2: break; //Vector 2: ADC overflow case 4: break; //Vector 4: ADC timing overflow case 6: //Vector 6: ADC12IFG0 //Move results results[index] = ADC12_A_getResults(ADC12_A_BASE, ADC12_A_MEMORY_0); //Increment results index, modulo; //Set Breakpoint1 here and watch results[] index++; if (index == ADC_DATA_LEN){ cur_raw_adc = get_raw_adc(); index = 0; } case 8: break; //Vector 8: ADC12IFG1 case 10: break; //Vector 10: ADC12IFG2 case 12: break; //Vector 12: ADC12IFG3 case 14: break; //Vector 14: ADC12IFG4 case 16: break; //Vector 16: ADC12IFG5 case 18: break; //Vector 18: ADC12IFG6 case 20: break; //Vector 20: ADC12IFG7 case 22: break; //Vector 22: ADC12IFG8 case 24: break; //Vector 24: ADC12IFG9 case 26: break; //Vector 26: ADC12IFG10 case 28: break; //Vector 28: ADC12IFG11 case 30: break; //Vector 30: ADC12IFG12 case 32: break; //Vector 32: ADC12IFG13 case 34: break; //Vector 34: ADC12IFG14 default: break; } } volatile int cur_raw_adc = 0; int get_raw_adc(void) { int32_t i, j; uint16_t tmp; for (i = 0; i <= ADC_DATA_LEN / 2; i++) { for (j = 0; j < ADC_DATA_LEN - i - 1; j++) { if (results[j + 1] < results[j]) { tmp = results[j + 1]; results[j + 1] = results[j]; results[j] = tmp; } } } if (ADC_DATA_LEN % 2 == 0) { return (((results[ADC_DATA_LEN / 2 - 1] + results[ADC_DATA_LEN / 2]) / 2)); } else { return (results[ADC_DATA_LEN / 2]); } } KEY_STATUS get_key_status(void) { KEY_STATUS key_status; if (cur_raw_adc > 1900 && cur_raw_adc < 1930) { polling_adc_flag = 1; key_status = KEY_1_PRESSED; } else if (cur_raw_adc > 2915 && cur_raw_adc < 2945) { polling_adc_flag = 0; key_status = KEY_2_PRESSED; } else if (cur_raw_adc > 3425 && cur_raw_adc < 3455) { key_status = KEY_C_PRESSED; } else if (cur_raw_adc > 3690 && cur_raw_adc < 3720) { key_status = KEY_C_LEFT; } else if (cur_raw_adc > 3815 && cur_raw_adc < 3845) { key_status = KEY_C_RIGHT; } else { key_status = KEY_UNKNOWN; } if (menu_type == 0 && polling_adc_flag == 1) { char adc_value[10]; memset(adc_value, 0, sizeof(adc_value)); snprintf(adc_value, 10, "%d", cur_raw_adc); lcdDrawFillRect(&dev, 64, 96, CONFIG_WIDTH, 96 + 16, RED); LCD_ShowString(&dev, 64, 96, (const uint8_t *)adc_value, WHITE, BLACK, 16, 1); //lcdDrawFillRect(&dev, 0, 96, CONFIG_WIDTH, 96 + 16, RED); //LCD_ShowString(&dev, 10, 96, (const uint8_t *)adc_value, WHITE, BLACK, 16, 1); } #if 0 if (key_status != KEY_UNKNOWN) { if ((xTaskGetTickCount() - startTick) > pdMS_TO_TICKS(300)) { startTick = xTaskGetTickCount(); return key_status; } } #endif //return KEY_UNKNOWN; return key_status; }
- 代码如下:
七、功能展示
- 主菜单
- 三角形测试子界面
- 三色块测试子界面
- 红色LED测试子界面
八、未来计划
未来计划能够把IO扩展板上的测温芯片和电位计给利用起来。