使用STM32CubeMX
代码生成工具,不用关注底层配置的细节,真舒服。
使用教程:
https://sxf1024.lanzoui.com/b09rf2dwj 密码:bgvi
虽然Cube+HAL
很舒服,但新手不建议用。最好还是先去学一下标准库怎么用,有个大致概念后,再来学这一套。
RCC(HSE:Crystal/Ceramic Resonator)
、SYS(Debug:Serial Wiire)
GPIO
相关,可以直接在Pinout view选择;若是其他功能,可以在左边Categories打开,会自动配置引脚)、设置Parameter Settings/NVIC
等HAL_
开头stm32f4xx_hal_XXX.c
或其.h
文件中找函数定义,一般在靠后位置HAL
库并没有把所有的操作都封装成凼数。HAL
库会使用宏定义来实现。而且会用__HAL_
作为这类宏定义的前缀。_GET
;而设置某个参数的,宏定义中就会有_SET
。HAL
库函数第一个参数一般都是句柄(一个包含了当前对象绝大部分状态的结构体),虽然增加了开销,但是用起来便捷了非常多。HAL_XXX_XXXCallback()
。
stm32f4xx_it.c
- > 中断函数XXX_IRQHandler(void)
-> HAL库中断函数HAL_XXX_IRQHandler(GPIO_PIN_13)
-> 回调函数HAL_XXX_XXXCallback()
MX_GPIO_Init()
__HAL_RCC_GPIOC_CLK_ENABLE()
RCC_AHB1ENR_GPIOCEN
stm32f429xx.h
文件RCC_AHB1ENR_DMA1EN
源码链接:
https://sxf1024.lanzoui.com/b09rf535a 密码:bf5q
详细教程网上挺多,配置也简单,只要勾选一下USARTx,再开一下中断就行。
在Keil就比较要注意了。
由于每次接收完,程序内部自动把接收中断关了,所以每次要手动打开。
总的来说,加这几部分:
main
函数中,while
之前:// 使能串口中断接收
HAL_UART_Receive_IT(&huart1, (uint8_t*)&DataTemp_UART1, 1);
#include "stdio.h"
int fputc(int ch, FILE *f){
HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, 0XFF);
return ch;
}
#define UART1BuffLen 200
extern uint8_t DataBuff_UART1[UART1BuffLen];
extern uint32_t DataTemp_UART1;
extern uint16_t DataSTA_UART1;
uint32_t DataTemp_UART1;
uint8_t DataBuff_UART1[UART1BuffLen];
uint16_t DataSTA_UART1;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1){
if(DataSTA_UART1 < UART1BuffLen){
if(DataTemp_UART1 == 0x0A && DataSTA_UART1>0 && DataBuff_UART1[DataSTA_UART1-1]==0X0D){
printf("USART: %s\r\n", DataBuff_UART1);
DataSTA_UART1 = 0;
}
else{
if(DataSTA_UART1 == 0){
memset(DataBuff_UART1, 0, sizeof(DataBuff_UART1));
}
DataBuff_UART1[DataSTA_UART1++] = DataTemp_UART1;
}
}
// 使能串口中断接收
HAL_UART_Receive_IT(&huart1, (uint8_t*)&DataTemp_UART1, 1);
}
}
RTC_DateTypeDef sDate;
RTC_TimeTypeDef sTime;
uint8_t second_tmp = 0;
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN); // 读取时间
HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN); // 读取日期
if(second_tmp != sTime.Seconds) { // 读取秒
second_tmp = sTime.Seconds;
printf("20%d%d-%d%d-%d%d\r\n",
sDate.Year/10%10, sDate.Year%10,
sDate.Month/10%10, sDate.Month%10,
sDate.Date/10%10, sDate.Date%10);
printf("%d%d:%d%d:%d%d\r\n",
sTime.Hours/10%10, sTime.Hours%10,
sTime.Minutes/10%10, sTime.Minutes%10,
sTime.Seconds/10%10, sTime.Seconds%10);
}
Connectivity -> SDIO -> Mode: SD 4bit Wide bus -> 勾选NVIC
Yes
。SDIO时钟分频系数CLKDIV
,计算公式为SDIO_CK=48MHz/(CLKDIV+2)
也可手动修改时钟配置。Middleware -> FATFS
,模式选择SD卡,配置文件系统:
如果要支持中文文件名,则配置CODE_PAGE
为Simplified Chinese
如果要支持长文件名,则要使能USE_LEN
Use dma template
Connectivity -> SDIO-> DMA Settings
System Core -> NVIC
。注意,SDIO中断优先级必须高于DMA2 stream3和DMA2 stream6的中断优先级
static void BL8782_PDN_INIT(void)
{
/*定义一个GPIO_InitTypeDef类型的结构体*/
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd ( RCC_AHB1Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_ResetBits(GPIOB,GPIO_Pin_13); //禁用WiFi模块
}
UINT bw;
retSD = f_mount(&SDFatFS, SDPath, 0);
if(retSD != FR_OK) {
printf("Mount Error :%d\r\n", retSD);
}
retSD = f_open(&SDFile, "0:/test.txt", FA_CREATE_ALWAYS | FA_WRITE);
if(retSD != FR_OK){
printf("Open Error :%d\r\n", retSD);
}
retSD = f_write(&SDFile, "abcde", 5, &bw);
if(retSD != FR_OK){
printf("Write Error :%d\r\n", retSD);
}
f_close(&SDFile);
retSD = f_open(&SDFile, "0:/test.txt", FA_READ);
char buff[10] = {0};
retSD = f_read(&SDFile, buff, 5, &bw);
if(retSD == FR_OK){
printf("%s\r\n", buff);
}
f_close(&SDFile);
Connectivity -> FMC -> SDRAM2
SDRAM1的起始地址为0XC0000000,SDRAM2的起始地址为0XD0000000。
一般SDRAM都含有4个bank。
Configuration中的参数可从SDRAM的数据手册上找到。
各个选项的配置(只做解释,不对应上图):
Clock and chip enable
:FMC_SDCKE0 和FMC_SDCLK0对应的存储区域1 的地址范围是0xC000 0000-0xCFFF FFFF;而FMC_SDCKE1 和FMC_SDCLK1 对应的存储区域2 的地址范围是0xD000 0000- 0xDFFF FFFF
Bank
由硬件连接决定需要选择SDRAM bank 2
Column bit number
表示列数,8位
Row bit number
表示行数,12位
CAS latency
表示CAS潜伏期,即上面说的CL,该配置需要与之后的SDRAM模式寄存器的配置相同,这里先配置为2 memory clock cycles(对于SDRAM时钟超过133MHz的,则需要配置为3 memory clock cycles)
Write protection
表示写保护,一般配置为Disabled
SDRAM common clock
为SDRAM 时钟配置,可选HCLK的2分频\3分频\不使能SDCLK时钟。前面主频配置为216MHz,SDRAM common clock设置为2分频,那SDCLK时钟为108MHz,每个时钟周期为9.25ns
SDRAM common burst read
表示突发读,这里选择使能
SDRAM common read pipe delay
表示CAS潜伏期后延迟多少个时钟在进行读数据,这里选择0 HCLK clock cycle
Load mode register to active delay
加载模式寄存器命令和激活或刷新命令之间的延迟,按存储器时钟周期计
Exit self-refresh delay
从发出自刷新命令到发出激活命令之间的延迟,按存储器时钟周期数计查数据手册知道其最小值为70ns,由于我们每个时钟周期为9.25ns,所以设为8 (70÷9.25,向上取整)SDRAM common row cycle delay
刷新命令和激活命令之间的延迟,以及两个相邻刷新命令之间的延迟, 以存储器时钟周期数表示
查数据手册知道其最小值为63ns,由于我们每个时钟周期为9.25ns,所以设为7 (63÷9.25,向上取整)
Write recovery time
写命令和预充电命令之间的延迟,按存储器时钟周期数计SDRAM common row precharge delay
预充电命令与其它命令之间的延迟,按存储器时钟周期数计
查数据手册知道其最小值为15ns,由于我们每个时钟周期为9.25ns,所以设为2 (15÷9.25,向上取整)
Row to column delay
激活命令与读/写命令之间的延迟,按存储器时钟周期数计
查数据手册知道其最小值为15ns,由于我们每个时钟周期为9.25ns,所以这里本应该设为2 (15÷9.25,向上取整)
但要注意,时序必须满足以下式子:
TWR ≥ TRAS - TRCD
TWR ≥ TRC - TRCD - TRP
其中:TWR = Write recovery time = 2
TRAS = Self refresh time = 5
TRC = SDRAM common row cycle delay = 7
TRP = SDRAM common row precharge delay = 2
TRCD = Row to column delay
所以这里Row to column delay应该取3uint8_t temp[100]__attribute__((at(0xD0000000)));
for(int i=0;i<100;i++){
temp[i] = i;
}
for(int i=0;i<100;i++){
printf("%d ", temp[i]);
}
/*****************************SDRAM使能函数******************************/
/**
* @brief 对SDRAM芯片进行初始化配置
* @param None.
* @retval None.
*/
static void USER_SDRAM_ENABLE(void)
{
FMC_SDRAM_CommandTypeDef Command;
__IO uint32_t tmpmrd =0;
/* Step 1: Configure a clock configuration enable command */
Command.CommandMode = FMC_SDRAM_CMD_CLK_ENABLE;
Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2;
Command.AutoRefreshNumber = 1;
Command.ModeRegisterDefinition = 0;
/* Send the command */
HAL_SDRAM_SendCommand(&hsdram1, &Command, SDRAM_TIMEOUT);
/* Step 2: Insert 100 us minimum delay */
/* Inserted delay is equal to 1 ms due to systick time base unit (ms) */
HAL_Delay(1);
/* Step 3: Configure a PALL (precharge all) command */
Command.CommandMode = FMC_SDRAM_CMD_PALL;
Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2;
Command.AutoRefreshNumber = 1;
Command.ModeRegisterDefinition = 0;
/* Send the command */
HAL_SDRAM_SendCommand(&hsdram1, &Command, SDRAM_TIMEOUT);
/* Step 4: Configure an Auto Refresh command */
Command.CommandMode = FMC_SDRAM_CMD_AUTOREFRESH_MODE;
Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2;
Command.AutoRefreshNumber = 4;
Command.ModeRegisterDefinition = 0;
/* Send the command */
HAL_SDRAM_SendCommand(&hsdram1, &Command, SDRAM_TIMEOUT);
/* Step 5: Program the external memory mode register */
tmpmrd = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_2 |
SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL |
SDRAM_MODEREG_CAS_LATENCY_3 |
SDRAM_MODEREG_OPERATING_MODE_STANDARD |
SDRAM_MODEREG_WRITEBURST_MODE_SINGLE;
Command.CommandMode = FMC_SDRAM_CMD_LOAD_MODE;
Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2;
Command.AutoRefreshNumber = 1;
Command.ModeRegisterDefinition = tmpmrd;
/* Send the command */
HAL_SDRAM_SendCommand(&hsdram1, &Command, SDRAM_TIMEOUT);
/* Step 6: Set the refresh rate counter */
/* Set the device refresh rate */
HAL_SDRAM_ProgramRefreshRate(&hsdram1, REFRESH_COUNT);
}
/*****************************使能函数结束******************************/
或者以我的野火STM32F429IGT6的版本SDRAM为8M,源码链接:
https://sxf1024.lanzoui.com/b09rf535a 密码:bf5q
添加到工程Core
路径下,然后在KEIL中初始化操作:
(注意这个SDRAM_InitSequence();
不能加在HAL_SDRAM_MspInit()
后面!!! 因为它这里还没FMC初始化完成!!! 加在这里是没有用的!!!)
#include "bsp_sdram.h"
MX_FMC_Init();
SDRAM_InitSequence();
SDRAM_Test();
务必在上面SDRAM配置成功后,再来搞这个!!!
详细教程看这个:https://zzttzz.gitee.io/blog/posts/7109b92c
但他给的源码还有点问题,运行处理没效果。
我提供的源码链接:
https://sxf1024.lanzoui.com/b09rf535a 密码:bf5q
注意:
后面要上TouchGFX,这里先加操作系统。
当FreeRTOS遇到FATFS+SDIO时,这里有挺多注意细节的!!!
针对初学者,使用STM32CubeMX配置FreeRTOS时,大部分参数默认即可
改完之后,注意:中断处理程序调用RTOS函数,请确保它们的优先级比最高的系统调用中断优先级低(数字上高),例如FreeRTOS中的LIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
5
的,而FreeRTOS要求优先级从5
开始Project Manager->Project->Linker Setting
中的最小堆栈大小,太小就会无法挂载SD卡或者读写时失败,基本上默认值都是无法正常运行的MINIMAL_STACK_SIZE
,默认值是128,使用默认值会造成f_mount直接卡死在内部,这里使用256
defaultTask()
,在freertos.c
文件中
defaultTask()
中
osThreadNew
,对xTaskCreate
又进行了封装,省去了繁琐
虽然方便,但它是用C++开发的,所以不是特别友好…说白了就是看不懂,不知道怎么去改
Cube
里安装TouchGFX
包FreeRTOS
要选CMSIS V1
版本!!!TouchGFX
包,并配置如下C:\Users\10617\STM32Cube\Repository\Packs\STMicroelectronics\X-CUBE-TOUCHGFX\4.15.0\Utilities\PC_Software\TouchGFXDesigner\TouchGFX-4.14.0.msi
TouchGFX
文件夹,打开其中的.touchgfx
文件,绘制界面#include "app_touchgfx.h"
// 开启LCD
LCD_DisplayOn();
LCD_SetLayerVisible(1,DISABLE);
LCD_SetLayerVisible(0,ENABLE);
LCD_SetTransparency(1,0);
LCD_SetTransparency(0,255);
LCD_SelectLayer(0);
// 显示TouchGFX内容
MX_TouchGFX_Process();
MicroLib
不支持C++
,所以需要在keil里取消勾选!!!printf
函数又需要MicroLib
支持,所以需要添加以下函数,否则会开机无法运行!!!#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{
x = x;
}
void _ttywrch(int ch)
{
ch = ch;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, 0XFF);
return ch;
}
#endif
奈何不会C++,只能另谋出路,LittltVGL设计的界面似乎还挺好看的,而且用C编写,兼容C++,更新很活跃。EMWIN风格类似window XP ,littlevGL风格类似android 。移植很简单(并没有多简单)。
官网github:https://github.com/lvgl/lvgl
官方文档:https://docs.lvgl.io/latest/en/html
官方推荐方法学习路线:
1、 教程可交叉参考以下这几篇,取长补短吧:
2、在Cube里开一个MTM的DMA(或者不开它,直接用DMA2D)
3、生成工程,修改disp_flush
函数
/**********************
* STATIC VARIABLES
**********************/
static __IO uint16_t * my_fb = (__IO uint16_t*) (0xD0000000);
static DMA_HandleTypeDef DmaHandle;
static int32_t x1_flush;
//static int32_t y1_flush;
static int32_t x2_flush;
static int32_t y2_fill;
static int32_t y_fill_act;
static const lv_color_t * buf_to_flush;
static lv_disp_t *our_disp = NULL;
/*********************
* INCLUDES
*********************/
#include "lv_port_disp.h"
#include "bsp_lcd.h"
#include "dma2d.h"
#include "stm32f4xx_hal_dma.h"
#include "dma.h"
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
// int32_t x;
// int32_t y;
// for(y = area->y1; y <= area->y2; y++) {
// for(x = area->x1; x <= area->x2; x++) {
// /* Put a pixel to the display. For example: */
// /* put_px(x, y, *color_p)*/
LCD_FillRect_C(area->x1, area->y1, area->x2-area->x1, area->y2-area->y1, (uint32_t)color_p);
LCD_DrawPixel(x, y, (uint32_t)color_p->full);
// LCD_FillRect_C(x, y, 1, 1, (uint32_t)color_p->full);
// color_p++;
// }
// }
int32_t x1 = area->x1;
int32_t x2 = area->x2;
int32_t y1 = area->y1;
int32_t y2 = area->y2;
/*Return if the area is out the screen*/
if(x2 < 0) return;
if(y2 < 0) return;
if(x1 > LV_HOR_RES_MAX - 1) return;
if(y1 > LV_VER_RES_MAX - 1) return;
/*Truncate the area to the screen*/
int32_t act_x1 = x1 < 0 ? 0 : x1;
int32_t act_y1 = y1 < 0 ? 0 : y1;
int32_t act_x2 = x2 > LV_HOR_RES_MAX - 1 ? LV_HOR_RES_MAX - 1 : x2;
int32_t act_y2 = y2 > LV_VER_RES_MAX - 1 ? LV_VER_RES_MAX - 1 : y2;
x1_flush = act_x1;
// y1_flush = act_y1;
x2_flush = act_x2;
y2_fill = act_y2;
y_fill_act = act_y1;
buf_to_flush = color;
HAL_StatusTypeDef err;
uint32_t length = (x2_flush - x1_flush + 1);
#if LV_COLOR_DEPTH == 24 || LV_COLOR_DEPTH == 32
length *= 2; /* STM32 DMA uses 16-bit chunks so multiply by 2 for 32-bit color */
#endif
err = HAL_DMA_Start_IT(&hdma_memtomem_dma2_stream0,(uint32_t)buf_to_flush, (uint32_t)&my_fb[y_fill_act * LV_HOR_RES_MAX + x1_flush], length);
if(err != HAL_OK) {
printf("disp_flush %d\r\n",err);
while(1); /*Halt on error*/
}
lv_disp_flush_ready(disp_drv);
}
4、按着上面教程,把littlvgl的显存地址改为SDRAM的
5、由于用作时基的TIM6的中断时间是100ms
,lvgl的动画会有明显卡顿,所以我们可以新开一个定时器如TIM7,设置它的中断时间为1ms
6、keil测试:
lv_init();
lv_port_disp_init();
// 开启LCD
LCD_DisplayOn();
LCD_SetLayerVisible(1,DISABLE);
LCD_SetLayerVisible(0,ENABLE);
LCD_SetTransparency(1,0);
LCD_SetTransparency(0,255);
LCD_SelectLayer(0);
LCD_Clear(LCD_COLOR_BLUE);
/*Create a Label on the currently active screen*/
lv_obj_t * label1 = lv_label_create(lv_scr_act(), NULL);
/*Modify the Label's text*/
lv_label_set_text(label1, "Hello world!");
/* Align the Label to the center
* NULL means align on parent (which is the screen now)
* 0, 0 at the end means an x, y offset after alignment*/
lv_obj_align(label1, NULL, LV_ALIGN_CENTER, 0, 0);
#include "bsp_lcd.h"
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
int32_t x;
int32_t y;
for(y = area->y1; y <= area->y2; y++) {
for(x = area->x1; x <= area->x2; x++) {
LCD_DrawPixel(x, y, (uint32_t)color_p->full);
color_p++;
}
}
lv_disp_flush_ready(disp_drv);
}
当然,我的源码链接(只有显示部分,触摸目前没用到):