NOTE当时的我会的还是太少了,很多都是在蓝桥杯过程中学到的
开始书写我的第二份博客
这次开始继续从stm32开始实地学习MCU,虽然说句实话感觉学的还是很不实在,属于是我和AI共同奋斗的结果
基本信息
- 使用的设备:stm32f103c8t6
aht20
bmp280
中景园ssd1680三色屏幕 - 使用keil5(使用armv5因为armv6我没搞懂会在hal库初始化时死循环),cubemx和vscode
- 本次使用了copilot(因为白嫖了github学生会员~)
- 因为没做完所以也没有上传github
cubemx创建工程
我个人觉得这个软件和keil一样像一个上时代的东西,这个创建看着很复杂实际上不算很难,创建完工程后期也可以再改很方便。注意把代码写在它注释要求的里面就好了,而我第一次像个弱智一样把它的注释全删掉了,不写在对应注释里面的话修改工程自己的代码就没有了。
关于keil的使用
之前也提到了,虽然我很想紧跟时代,使用armv6,毕竟现在新的keil5都不自带了,但是我的HAL_Init死循环最后就是换编译器解决的。
我基本也就用几个功能,编译烧录,当然由于神秘的STM32没有自己的日志函数(至少AI说要自己写),我还需要用到keil的watch窗口来查看变量的值,终于还是用回了低级的调试,写esp32时都没有这一步。
设备连接
HAL库下的i2c还是很方便的,基本上只要按照要求写写就好了。和spi一样,基本上跟着流程走就好,如果出了问题,且确定代码没问题的话首先看看接线和cubemx配置之类的有没有问题吧?我aht20接线接错了。
最后一定要提一下神秘的ssd1680,我原来按照官方提供的文档里的初始化方式出现了持续了busy线繁忙问题,最后我也不知道是怎么解决的,大概的话是告诉了gemini(copilot)我用的是中景园的ssd1680以后AI说这个产品有特殊的要求,要增加初始化的内容数量,但我觉得这不是问题根本,因为原来即使把繁忙检测放在初始化一开始的复位后也依然会死循环。但最后进行了代码替换后真的就解决了这个问题,我也就没再进一步追究了。也可能是将while循环中的不等号换成了等于号?我觉得这个可能性很小。总之现在busy正常工作了。
ps后面我想了一下还可能是spi配置的时候,我更改了配置,首先降低了通讯速度,其次,改为了仅发送模式
这里记录一下最后使用的初始化函数,可以看出来AI修改程度很大,AI还把命令和参数分开进行了发送,这个我不太清楚对不对,因为当初我初始化ssd1306的时候好像是都当作命令发送的。
HAL_StatusTypeDef SSD1680_Init(SPI_HandleTypeDef *hspi) {#ifdef SSD1680_RES_Pin // 硬件复位(重要!) HAL_GPIO_WritePin(SSD1680_RES_GPIO_Port, SSD1680_RES_Pin, GPIO_PIN_RESET); HAL_Delay(20); // 复位时间加长 HAL_GPIO_WritePin(SSD1680_RES_GPIO_Port, SSD1680_RES_Pin, GPIO_PIN_SET); HAL_Delay(200); // 等待复位完成#endif // 1) 软件复位 SSD1680_SendCmd(hspi, (uint8_t[]){0x12}, 1); HAL_Delay(10); // 等待 Busy uint32_t wait = 100; while (SSD1680_Busy_Check() == GPIO_PIN_SET && wait-- > 0) HAL_Delay(10); // 2) Driver output control SSD1680_SendCmd(hspi, (uint8_t[]){0x01}, 1); SSD1680_SendData(hspi, (uint8_t[]){0x97, 0x00, 0x00}, 3); // 3) Gate driving voltage control SSD1680_SendCmd(hspi, (uint8_t[]){0x03}, 1); SSD1680_SendData(hspi, (uint8_t[]){0x00}, 1); // 4) Source driving voltage control SSD1680_SendCmd(hspi, (uint8_t[]){0x04}, 1); SSD1680_SendData(hspi, (uint8_t[]){0x0A, 0x00, 0x00}, 3); // 5) Booster soft start SSD1680_SendCmd(hspi, (uint8_t[]){0x0C}, 1); SSD1680_SendData(hspi, (uint8_t[]){0xD7, 0xD6, 0x9D}, 3); // 6) VCOM register (重要!影响显示对比度) SSD1680_SendCmd(hspi, (uint8_t[]){0x2C}, 1); SSD1680_SendData(hspi, (uint8_t[]){0xA8}, 1); // -1.5V // 7) Dummy line period SSD1680_SendCmd(hspi, (uint8_t[]){0x3A}, 1); SSD1680_SendData(hspi, (uint8_t[]){0x1A}, 1); // 8) Gate line width SSD1680_SendCmd(hspi, (uint8_t[]){0x3B}, 1); SSD1680_SendData(hspi, (uint8_t[]){0x08}, 1); // 9) Data entry mode - 修改为 Y 方向自动递增 // 0x03: Y和X都递增(会产生斜线) // 0x01: X方向递增,地址自动更新 SSD1680_SendCmd(hspi, (uint8_t[]){0x11}, 1); SSD1680_SendData(hspi, (uint8_t[]){0x01}, 1); // 10) Set RAM X address start/end (0x00 到 0x12, 即 0-18,共19字节 = 152位) SSD1680_SendCmd(hspi, (uint8_t[]){0x44}, 1); SSD1680_SendData(hspi, (uint8_t[]){0x00, 0x12}, 2); // 0x12 = 18 // 11) Set RAM Y address start/end (0 到 151) SSD1680_SendCmd(hspi, (uint8_t[]){0x45}, 1); SSD1680_SendData(hspi, (uint8_t[]){0x00, 0x00, 0x97, 0x00}, 4); // 12) Border waveform control SSD1680_SendCmd(hspi, (uint8_t[]){0x3C}, 1); SSD1680_SendData(hspi, (uint8_t[]){0x01}, 1); // 13) LUT (可选,使用内部 LUT) SSD1680_SendCmd(hspi, (uint8_t[]){0x22}, 1); SSD1680_SendData(hspi, (uint8_t[]){0xF7}, 1); // 不要在初始化时刷新,只清空 RAM if (SSD1680_Clear(hspi) != HAL_OK) return HAL_ERROR; return HAL_OK; }字体写入直接用了AI修改后的,我觉得我原来的应该也没大问题,只是定位可能还需要微调之类的,但是目前AI写的确实解决了所有问题。
#ifdef __cplusplusextern "C"{#endif
#include <stdio.h>
#include "word.h"#include "main.h"#include "spi.h"#include "SSD1680.h"
#define SSD1680_SCREEN_WIDTH_BYTES 19U#define SSD1680_FONT_HEIGHT 16U#define SSD1680_FONT_WIDTH_BITS 8U
static const uint8_t glyph0[] = {0x00, 0xC0, 0x30, 0x10, 0x10, 0x30, 0xE0, 0x00, 0x00, 0x07, 0x0C, 0x08, 0x08, 0x0C, 0x03, 0x00}; static const uint8_t glyph1[] = {0x00, 0x20, 0x20, 0x10, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00}; static const uint8_t glyph2[] = {0x00, 0x20, 0x10, 0x10, 0x10, 0x90, 0xE0, 0x00, 0x00, 0x0C, 0x0A, 0x0A, 0x09, 0x08, 0x08, 0x00}; static const uint8_t glyph3[] = {0x00, 0x20, 0x90, 0x90, 0x90, 0x60, 0x00, 0x00, 0x00, 0x04, 0x08, 0x08, 0x08, 0x07, 0x00, 0x00}; static const uint8_t glyph4[] = {0x00, 0x00, 0x80, 0x40, 0x20, 0xF0, 0x00, 0x00, 0x02, 0x03, 0x02, 0x02, 0x02, 0x0F, 0x02, 0x02}; static const uint8_t glyph5[] = {0x00, 0xF0, 0x90, 0x90, 0x90, 0x90, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x0D, 0x07, 0x00}; static const uint8_t glyph6[] = {0x00, 0xC0, 0x20, 0x90, 0x90, 0x90, 0x00, 0x00, 0x00, 0x07, 0x0D, 0x08, 0x08, 0x08, 0x07, 0x00}; static const uint8_t glyph7[] = {0x10, 0x10, 0x10, 0x10, 0x90, 0x70, 0x10, 0x00, 0x00, 0x00, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x00}; static const uint8_t glyph8[] = {0x00, 0x60, 0x90, 0x90, 0x90, 0x90, 0x60, 0x00, 0x00, 0x07, 0x08, 0x08, 0x08, 0x08, 0x07, 0x00}; static const uint8_t glyph9[] = {0x00, 0xE0, 0x10, 0x10, 0x10, 0x30, 0xE0, 0x00, 0x00, 0x00, 0x09, 0x09, 0x09, 0x05, 0x03, 0x00}; static const uint8_t glyphDot[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00}; static const uint8_t glyphMinus[] = {0x00, 0x00, 0x00, 0xF0, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
static uint8_t *glyph_from_char(char c) { switch (c) { case '0': return (uint8_t *)glyph0; case '1': return (uint8_t *)glyph1; case '2': return (uint8_t *)glyph2; case '3': return (uint8_t *)glyph3; case '4': return (uint8_t *)glyph4; case '5': return (uint8_t *)glyph5; case '6': return (uint8_t *)glyph6; case '7': return (uint8_t *)glyph7; case '8': return (uint8_t *)glyph8; case '9': return (uint8_t *)glyph9; case '.': return (uint8_t *)glyphDot; case '-': return (uint8_t *)glyphMinus; case ' ': return NULL; // 让 SSD1680_num 写入全白 default: return NULL; } } // 将纵向字模转换为横向字模(8x16 -> 16x8位) // 原始字模:每字节代表纵向8个点,共16个字节(上8字节=前8行,下8字节=后8行) // 转换后:每字节代表横向8个点,共16个字节(16行数据) static void transpose_glyph(const uint8_t *src, uint8_t *dst) { for (uint8_t row = 0; row < 16; row++) { uint8_t byte = 0; for (uint8_t col = 0; col < 8; col++) { // 从原始字模中提取对应的位 uint8_t src_byte_idx = col; // 原始字模的列索引 uint8_t bit_idx = (row < 8) ? row : (row - 8); // 前8行在上半部分,后8行在下半部分 uint8_t src_byte_base = (row < 8) ? 0 : 8; // 上半部分或下半部分
uint8_t src_val = src[src_byte_base + col]; uint8_t bit = (src_val >> bit_idx) & 0x01;
// 写入到目标字节(MSB first) byte |= (bit << (7 - col)); } dst[row] = ~byte; // 反转位(0=白,1=黑 -> 0=黑,1=白) } } static void draw_char(uint16_t row, uint16_t column, char ch) { if (column >= SSD1680_SCREEN_WIDTH_BYTES) return;
uint8_t *glyph = glyph_from_char(ch);
if (glyph != NULL) { // 转换字模格式 uint8_t transposed[16]; transpose_glyph(glyph, transposed); SSD1680_num(&hspi1, transposed, row, column, SSD1680_FONT_HEIGHT, SSD1680_FONT_WIDTH_BITS); } else { // 空字符,写入全白 SSD1680_num(&hspi1, NULL, row, column, SSD1680_FONT_HEIGHT, SSD1680_FONT_WIDTH_BITS); } } static void draw_string(const char *text, uint16_t row, uint16_t column) { uint16_t cursor = column; while (*text && cursor < SSD1680_SCREEN_WIDTH_BYTES) { draw_char(row, cursor, *text); text++; cursor++; } } void print_T(float value, uint16_t row, uint16_t column) { if (value > 999.99f) value = 999.99f; if (value < -99.99f) value = -99.99f;
char buffer[12]; snprintf(buffer, sizeof(buffer), "%05.2f", value); draw_string(buffer, row, column); } void print_P(float value, uint16_t row, uint16_t column) { if (value < 0) value = 0; if (value > 999999) value = 999999;
char buffer[10]; snprintf(buffer, sizeof(buffer), "%06.0f", value); draw_string(buffer, row, column); }#ifdef __cplusplus}#endifbmp280初始化也很难的其实,但至少我跟着写下来没有出任何问题,所以不多说了。
后记
如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时





