pomin

  • 2024-05-17
  • 发表了主题帖: 【2023 DigiKey大赛参与奖】开箱帖 Raspberry Pi 5 4G

    本帖最后由 pomin 于 2024-5-17 02:12 编辑 > 这次参加了2023得捷大赛,虽然没有获得大奖,最后也有了一个参与奖,很开心。下单了一个4GB内存的树莓派5开发板,快递今天到了,板子很不错。 快递袋 树莓派5的盒子,还是这一贯的玫红色包装盒 打开盒子就是树莓派5了,这代虽然没有之前的好看些,不过性能确实提高了不少 感谢EEWorld和得捷举办的本次活动。 希望EEWorld和得捷越办越好!!!

  • 2024-04-24
  • 加入了学习《【DigiKey创意大赛】多通道微型气相色谱采集单元》,观看 多通道微型气相色谱采集单元

  • 2024-04-15
  • 回复了主题帖: 【STM32F411Nucleo测评】驱动 1.3 寸 LCD 屏幕

    deng0713 发表于 2024-4-14 22:26 请问这个扩展版是嘉立创元件库里的吗还是大佬自己画的(☆▽☆) 自己画的

  • 2024-03-11
  • 回复了主题帖: 【STM32F411Nucleo测评】移植 Freemodbus 库

    qiao--- 发表于 2024-3-10 00:07 请问一下,你是用的这个modbus调试工具是什么啊 mthings

  • 2024-03-08
  • 发表了主题帖: 【STM32F411Nucleo测评】modbus&LVGL 屏显从站

    > 在电机应用中,尝尝需要获知电机的运行参数,例如转速、温度等,许多驱动板也会带有屏显,或者通过modbus连接到上位机或者组态屏来监控电机状态。本文使用 LVGL 和 Freemodbus 库制作了一个带屏线从站的 demo ## 往期链接 [【STM32F411Nucleo测评】开箱,搭建开发环境,串口输出](https://bbs.eeworld.com.cn/thread-1272849-1-1.html) [【STM32F411Nucleo测评】驱动 1.3 寸 LCD 屏幕](https://bbs.eeworld.com.cn/thread-1272852-1-1.html) [【STM32F411Nucleo测评】移植多功能按键驱动库](https://bbs.eeworld.com.cn/thread-1273453-1-1.html) [【STM32F411Nucleo测评】移植 Freemodbus 库](https://bbs.eeworld.com.cn/thread-1273454-1-1.html) [【STM32F411Nucleo测评】移植 LVGL 界面库](https://bbs.eeworld.com.cn/thread-1273455-1-1.html) 在前文的介绍中,已经完成了对 LVGL 和 Freemodbus 的移植,下面介绍做一个上层应用——modbus LVGL 屏显从站的 demo 制作。 ## GUI Guider 设计界面 GUI Guider 是 NXP 给 LVGL 开发的可视化界面编辑、模拟器,所见即所得,同时也可以在此之上完成对于时间、定时器、界面切换任务的添加,这里绘制了一个 demo 界面,有两个按键、一个滑条和一个标签文本。 设计的操作逻辑如下: - 按下 Down 按键减小速度、Up 按键增大速度 - 滑条可以百分比放缩显示当前速度 - 标签文本显示当前速度 对于上述的功能写代码当然是比较容易实现的,但是这里是在 GUI Guider 中添加这些事件和任务的响应代码。 按下 Down 按键减小速度、Up 按键增大速度时对应的是 Click 事件,这里添加 Click 的事件代码,当 Down 按下后会设置标签文本并且修改滑条的位置 ```c unsigned int speed = 1000; ``` ```c speed-=10; lv_label_set_text_fmt(guider_ui.screen_label_1, "Speed: %dRPM", speed); lv_slider_set_value(guider_ui.screen_slider_1, speed / 16, LV_ANIM_ON); ``` 类似的, Up 按键的 Click 事件添加代码 ```c speed+=10; lv_label_set_text_fmt(guider_ui.screen_label_1, "Speed: %dRPM", speed); lv_slider_set_value(guider_ui.screen_slider_1, speed / 16, LV_ANIM_ON); ``` 然后在 GUI Guider 中仿真一下,与预期的想法一致,然后生成代码加入到 keil 工程中 添加的文件如下,当然用别的字体的话字体文件跟这个肯定名字不一样 然后就可以把生成的界面代码加载起来了,先在 main.c 中定义一个全局变量 ```c lv_ui guider_ui; ``` 然后在 while(1) 之前加载 GUI Guider 绘制的界面 ```c setup_ui(&guider_ui); ``` 编译烧录到开发板 此时,这个按键是没法操作的,因为在仿真器中加入的是 Windows 的输入设备,在 MCU 端还需要再把按键注册到 LVGL 的输入设备中。 编写 lv_port_indev.c 的代码如下,主要就是在 button_is_pressed 函数中获取按键的电平状态,在初始化时定义按键到 btn_points,按键按下时相当于对应的坐标点被点击。 ```c /*Copy this file as "lv_port_indev.c" and set this value to "1" to enable content*/ #if 1 /********************* *      INCLUDES *********************/ #include "lv_port_indev.h" #include "lvgl/lvgl.h" /********************* *      DEFINES *********************/ /********************** *      TYPEDEFS **********************/ /********************** *  STATIC PROTOTYPES **********************/ static void button_init(void); static void button_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data); static int8_t button_get_pressed_id(void); static bool button_is_pressed(uint8_t id); /********************** *  STATIC VARIABLES **********************/ lv_indev_t * indev_button; static int32_t encoder_diff; static lv_indev_state_t encoder_state; /********************** *      MACROS **********************/ /********************** *   GLOBAL FUNCTIONS **********************/ void lv_port_indev_init(void) {     /**      * Here you will find example implementation of input devices supported by LittelvGL:      *  - Touchpad      *  - Mouse (with cursor support)      *  - Keypad (supports GUI usage only with key)      *  - Encoder (supports GUI usage only with: left, right, push)      *  - Button (external buttons to press points on the screen)      *      *  The `..._read()` function are only examples.      *  You should shape them according to your hardware      */     static lv_indev_drv_t indev_drv;     /*------------------      * Button      * -----------------*/     /*Initialize your button if you have*/     button_init();     /*Register a button input device*/     lv_indev_drv_init(&indev_drv);     indev_drv.type = LV_INDEV_TYPE_BUTTON;     indev_drv.read_cb = button_read;     indev_button = lv_indev_drv_register(&indev_drv);     /*Assign buttons to points on the screen*/     static const lv_point_t btn_points[2] = {         {60, 60},   /*Button 0 -> x:10; y:10*/         {180, 60},  /*Button 1 -> x:40; y:100*/     };     lv_indev_set_button_points(indev_button, btn_points); } /*------------------ * Button * -----------------*/ /*Initialize your buttons*/ static void button_init(void) {     /*Your code comes here*/ } /*Will be called by the library to read the button*/ static void button_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data) {     static uint8_t last_btn = 0;     /*Get the pressed button's ID*/     int8_t btn_act = button_get_pressed_id();     if(btn_act >= 0) {         data->state = LV_INDEV_STATE_PR;         last_btn = btn_act;     }     else {         data->state = LV_INDEV_STATE_REL;     }     /*Save the last pressed button's ID*/     data->btn_id = last_btn; } /*Get ID  (0, 1, 2 ..) of the pressed button*/ static int8_t button_get_pressed_id(void) {     uint8_t i;     /*Check to buttons see which is being pressed (assume there are 2 buttons)*/     for(i = 0; i < 2; i++) {         /*Return the pressed button's ID*/         if(button_is_pressed(i)) {             return i;         }     }     /*No button pressed*/     return -1; } #include "main.h" /*Test if `id` button is pressed or not*/ static bool button_is_pressed(uint8_t id) {     /*Your code comes here*/     if (id) return !HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin);     else return !HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin);     return false; } #else /*Enable this file at the top*/ /*This dummy typedef exists purely to silence -Wpedantic.*/ typedef int keep_pedantic_happy; #endif ``` 然后在 main.c 的初始化代码中加入 indev 的初始化 ```c   lv_init();   lv_port_disp_init();   lv_port_indev_init();   setup_ui(&guider_ui); ``` 编译烧录到开发板,可以看到操作按键修改速度值,modbus主机端也可以实时的监控到这个值 [localvideo]1dbe188ba2d29ca0d4708a1f75745f19[/localvideo]

  • 2024-03-05
  • 回复了主题帖: 【STM32F411Nucleo测评】移植 LVGL 界面库

    LitchiCheng 发表于 2024-3-5 09:04 刷新率怎么样,spi通信加上DMA大概可以到多少fps 有动画的时候FPS也可以稳定60,很丝滑

  • 加入了学习《follow me 四期项目提交视频》,观看 follow me 四期项目提交视频

  • 2024-03-04
  • 发表了主题帖: 【STM32F411Nucleo测评】移植 LVGL 界面库

    > STM32F411RE 有 512KB 的 Flash 和 128KB 的 RAM,资源丰富,本文介绍如何移植 LVGL 界面库到 Nucleo-F411 开发板上面 ## 资料获取 LVGL 是比较知名的嵌入式 GUI 库,代码在 GitHub 可以获得,地址 https://github.com/lvgl/lvgl ,这里移植 V8.2 版本,在拉取时使用如下命令,不直接拉取最新版,因为最新版是9.0版本的 ```bash git clone -b release/v8.2 https://github.com/lvgl/lvgl.git ``` 然后将 LVGL 除了别的平台的代码外全部加入 keil 工程中 ## 显示接口适配 LVGL的显示接口主要就是一个刷屏函数disp_flush,这里在之前文章的 LCD 完成刷屏的基础上,修改思路如下: 删除全屏缓存区,因为占用过多 RAM,改为选择显示空间后使用DMA传输LVGL的color参数 LVGL 的 disp_flush 接口代码如下 ```c static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) {     /*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/     LCD_FillWindow(area->x1,area->y1,area->x2,area->y2, (uint16_t*)color_p);     /*IMPORTANT!!!      *Inform the graphics library that you are ready with the flushing*/     lv_disp_flush_ready(disp_drv); } void LCD_FillWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t *pixels) {     u16 i,j,width,height;     width = x1 - x0 + 1;     height = y1 - y0 + 1;     uint32_t size = width * height;     LCD_SetFrame(x0, y0, x1, y1);     hspi1.Init.DataSize = SPI_DATASIZE_16BIT;     hspi1.Instance->CR1|=SPI_CR1_DFF;     HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, 1);     HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, 0);     HAL_SPI_Transmit_DMA(&hspi1,(uint8_t*)pixels, size);     while(__HAL_DMA_GET_COUNTER(&hdma_spi1_tx)!=0);     HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, 1);     hspi1.Init.DataSize = SPI_DATASIZE_8BIT;     hspi1.Instance->CR1&=~SPI_CR1_DFF; } ``` 至此 LVGL 的刷屏接口就适配好了,其中为了加快执行的速度,部分代码直接操作寄存器来完成 ## 时钟接口适配 在 CubeMX 中初始化 TIM10 配置为1ms周期中断 在中断函数中调用 lv_tick_inc 函数,为LVGL提供时钟 ```c void TIM1_UP_TIM10_IRQHandler(void) {   /* USER CODE BEGIN TIM1_UP_TIM10_IRQn 0 */   /* USER CODE END TIM1_UP_TIM10_IRQn 0 */   HAL_TIM_IRQHandler(&htim10);   /* USER CODE BEGIN TIM1_UP_TIM10_IRQn 1 */   lv_tick_inc(1);   /* USER CODE END TIM1_UP_TIM10_IRQn 1 */ } s ``` ## 主函数代码 最后主函数中调用 lv_init、lv_port_disp_init 和 lv_task_handler,即可完成 LVGL 的移植 ```c   lv_init();   lv_port_disp_init();   /* USER CODE END 2 */   /* Infinite loop */   /* USER CODE BEGIN WHILE */   while (1)   {     /* USER CODE END WHILE */     /* USER CODE BEGIN 3 */     lv_task_handler();   }   /* USER CODE END 3 */ ``` 单单有 LVGL 初始化不够的,还需要有一个界面来交给它来绘制,下面写段验证代码 ## 验证 代码如下,创建一个自定义风格的滑动条控件,百分比为 70%,长度为200,居中显示 ```c void foo(void) {     static lv_style_t style_indic;     lv_style_init(&style_indic);     lv_style_set_bg_color(&style_indic, lv_palette_lighten(LV_PALETTE_RED, 3));     lv_style_set_bg_grad_color(&style_indic, lv_palette_main(LV_PALETTE_RED));     lv_style_set_bg_grad_dir(&style_indic, LV_GRAD_DIR_HOR);     static lv_style_t style_indic_pr;     lv_style_init(&style_indic_pr);     lv_style_set_shadow_color(&style_indic_pr, lv_palette_main(LV_PALETTE_RED));     lv_style_set_shadow_width(&style_indic_pr, 10);     lv_style_set_shadow_spread(&style_indic_pr, 3);     /*Create an object with the new style_pr*/     lv_obj_t * obj = lv_slider_create(lv_scr_act());     lv_obj_add_style(obj, &style_indic, LV_PART_INDICATOR);     lv_obj_add_style(obj, &style_indic_pr, LV_PART_INDICATOR | LV_STATE_PRESSED);     lv_slider_set_value(obj, 70, LV_ANIM_OFF);     lv_obj_center(obj); } ``` 添加到主函数中。 ```c   lv_init();   lv_port_disp_init();   foo();   /* USER CODE END 2 */   /* Infinite loop */   /* USER CODE BEGIN WHILE */   while (1)   {     /* USER CODE END WHILE */     /* USER CODE BEGIN 3 */     lv_task_handler();   }   /* USER CODE END 3 */ ``` 编译烧录到开发板中,可以看到显示出了使用LVGL绘制的滑动条

  • 发表了主题帖: 【STM32F411Nucleo测评】移植 Freemodbus 库

    > 本文介绍如何移植 FreeModbus 库,在 Nucleo-F411 板上实现 Modbus 通信 ## 资料准备 FreeModbus 的代码是存放在 GitHub 的,地址 https://github.com/cwalter-at/freemodbus ,拉取到本地,其中关键的就是demo和modbus两个文件夹,demo存放一些移植好的工程,modbus中存放库代码。 ## 硬件连接 查看原理图的串口是接在STLink 的虚拟串口上面的,管脚为PA2和PA3 ## 接口配置 将拉取到的 FreeModbus 代码添加到 keil 工程中,其中需要添加库代码中的 functions、rtu 文件夹中的代码和 mb.c 此外还需要把demo中BARE文件夹的代码复制到工程中,这里同时将demo.c修改为了data.c添加到工程 添加的代码文件如下 需要的代码文件都添加好了,下面开始对 portxxx.c进行适配,其中 portserial.c 是串口的接口层, porttimer.c 是定时器的接口层。 配置 USART2,参数如下,设置波特率为115200bps 定时器TIM11周期设置为50us 同时将 USART2 和 TIM11 的中断使能 ### 串口接口适配 利用 HAL_UART_TxCpltCallback,HAL_UART_RxCpltCallback 两个HAL库的两个回调函数编写如下代码 ```c void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {     if(huart->Instance == uart->Instance)     {         pxMBFrameCBByteReceived();         HAL_UART_Receive_IT(uart, &singlechar, 1);     } } void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {     if(huart->Instance == uart->Instance)     {         pxMBFrameCBTransmitterEmpty();     } } ``` 串口收发的接口代码如下 ```c /* ----------------------- Static variables ---------------------------------*/ UART_HandleTypeDef *uart = &huart2; static uint8_t singlechar; /* ----------------------- Start implementation -----------------------------*/ BOOL            xMBPortSerialInit( UCHAR ucPort, ULONG ulBaudRate,                                    UCHAR ucDataBits, eMBParity eParity ) {     return TRUE; } void vMBPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable) {     if(xRxEnable)     {         HAL_UART_Receive_IT(uart, &singlechar, 1);     }        else     {         HAL_UART_AbortReceive_IT(uart);     }     if(xTxEnable)     {         pxMBFrameCBTransmitterEmpty();     }     else     {         HAL_UART_AbortTransmit_IT(uart);     } } void vMBPortClose(void) {     HAL_UART_AbortReceive_IT(uart);     HAL_UART_AbortTransmit_IT(uart); } BOOL xMBPortSerialPutByte(CHAR ucByte) {     HAL_UART_Transmit_IT(uart, (uint8_t*)&ucByte, 1);     return TRUE; } BOOL xMBPortSerialPutBytes(volatile UCHAR *ucByte, USHORT usSize) {     HAL_UART_Transmit_IT(uart, (uint8_t *)ucByte, usSize);     return TRUE; } BOOL xMBPortSerialGetByte(CHAR * pucByte) {     *pucByte = (uint8_t)(singlechar);     return TRUE; } ``` ### 定时器接口适配 定时器使用 HAL_TIM_PeriodElapsedCallback 这个 HAL 提供的定时器周期中断时的回调函数 ```c void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {     if(htim->Instance == tim->Instance)     {         if((++counter) >= timeout)             pxMBPortCBTimerExpired();     } } ``` 其他的代码就是使能、失能 TIM 的一些接口代码 ```c /* ----------------------- User defenitions ---------------------------------*/ TIM_HandleTypeDef *tim = &htim11; static uint16_t timeout = 0; volatile uint16_t counter = 0; /* ----------------------- Start implementation -----------------------------*/ BOOL xMBPortTimersInit( USHORT usTim1Timerout50us ) {     timeout = usTim1Timerout50us;     return TRUE; } inline void vMBPortTimersEnable(  ) {     counter=0;     HAL_TIM_Base_Start_IT(tim); } inline void vMBPortTimersDisable(  ) {     HAL_TIM_Base_Stop_IT(tim); } inline void vMBPortTimersDelay( USHORT usTimeOutMS ) {     HAL_Delay(usTimeOutMS); } ``` 至此接口层的代码就适配好了,代码量不大,还算比较容易,下面开始主函数编写应用代码来验证 ## 验证 将main函数中while前后的代码改为如下代码,先初始化modbus库然后使能,在while循环中轮询。 ```c   eMBInit(MB_RTU, 0x01, 0, 115200, MB_PAR_NONE); // 初始化modbus为RTU方式,波特率9600,奇校验   eMBEnable();                                   // 使能modbus协议栈   /* USER CODE END 2 */   /* Infinite loop */   /* USER CODE BEGIN WHILE */   while (1)   {     /* USER CODE END WHILE */     /* USER CODE BEGIN 3 */     eMBPoll();  // 轮训查询   }   /* USER CODE END 3 */ ``` 光有这些还是不够的,现在已经完成了通信过程的适配,还需要提供保持寄存器、线圈等数据源代码,在data.c中编写代码如下 ```c #include "mb.h" #include "mbport.h" // 十路输入寄存器 #define REG_INPUT_SIZE  10 uint16_t REG_INPUT_BUF[REG_INPUT_SIZE]; // 十路保持寄存器 #define REG_HOLD_SIZE   10 uint16_t REG_HOLD_BUF[REG_HOLD_SIZE]; // 十路线圈 #define REG_COILS_SIZE 10 uint8_t REG_COILS_BUF[REG_COILS_SIZE] = {1, 1, 1, 1, 0, 0, 0, 0, 1, 1}; // 十路离散量 #define REG_DISC_SIZE  10 uint8_t REG_DISC_BUF[REG_DISC_SIZE] = {1,1,1,1,0,0,0,0,1,1}; /// CMD4命令处理回调函数 eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) {     USHORT usRegIndex = usAddress - 1;     // 非法检测     if((usRegIndex + usNRegs) > REG_INPUT_SIZE)     {         return MB_ENOREG;     }     // 循环读取     while( usNRegs > 0 )     {         *pucRegBuffer++ = ( unsigned char )( REG_INPUT_BUF[usRegIndex] >> 8 );         *pucRegBuffer++ = ( unsigned char )( REG_INPUT_BUF[usRegIndex] & 0xFF );         usRegIndex++;         usNRegs--;     }     // 模拟输入寄存器被改变     for(usRegIndex = 0; usRegIndex < REG_INPUT_SIZE; usRegIndex++)     {         REG_INPUT_BUF[usRegIndex]++;     }     return MB_ENOERR; } /// CMD6、3、16命令处理回调函数 eMBErrorCode eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode ) {     USHORT usRegIndex = usAddress - 1;     // 非法检测     if((usRegIndex + usNRegs) > REG_HOLD_SIZE)     {         return MB_ENOREG;     }     // 写寄存器     if(eMode == MB_REG_WRITE)     {         while( usNRegs > 0 )         {             REG_HOLD_BUF[usRegIndex] = (pucRegBuffer[0] 0 )         {             *pucRegBuffer++ = ( unsigned char )( REG_HOLD_BUF[usRegIndex] >> 8 );             *pucRegBuffer++ = ( unsigned char )( REG_HOLD_BUF[usRegIndex] & 0xFF );             usRegIndex++;             usNRegs--;         }     }     return MB_ENOERR; } /// CMD1、5、15命令处理回调函数 eMBErrorCode eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode ) {     USHORT usRegIndex   = usAddress - 1;     UCHAR  ucBits       = 0;     UCHAR  ucState      = 0;     UCHAR  ucLoops      = 0;     // 非法检测     if((usRegIndex + usNCoils) > REG_COILS_SIZE)     {         return MB_ENOREG;     }     if(eMode == MB_REG_WRITE)     {         ucLoops = (usNCoils - 1) / 8 + 1;         while(ucLoops != 0)         {             ucState = *pucRegBuffer++;             ucBits  = 0;             while(usNCoils != 0 && ucBits < 8)             {                 REG_COILS_BUF[usRegIndex++] = (ucState >> ucBits) & 0X01;                 usNCoils--;                 ucBits++;             }             ucLoops--;         }     }     else     {         ucLoops = (usNCoils - 1) / 8 + 1;         while(ucLoops != 0)         {             ucState = 0;             ucBits  = 0;             while(usNCoils != 0 && ucBits < 8)             {                 if(REG_COILS_BUF[usRegIndex])                 {                     ucState |= (1 REG_DISC_SIZE)     {         return MB_ENOREG;     }     ucLoops = (usNDiscrete - 1) / 8 + 1;     while(ucLoops != 0)     {         ucState = 0;         ucBits  = 0;         while(usNDiscrete != 0 && ucBits < 8)         {             if(REG_DISC_BUF[usRegIndex])             {                 ucState |= (1

  • 发表了主题帖: 【STM32F411Nucleo测评】移植多功能按键驱动库

    > MultiButton 是一个小巧简单易用的事件驱动型按键驱动模块,可无限量扩展按键,按键事件的回调异步处理方式可以简化你的程序结构,去除冗余的按键处理硬编码,让你的按键业务逻辑更清晰 > 本文将介绍该按键库在STM32F411上面移植的过程 ## 硬件连接 原理图中的两个按键管脚对应如下表所示 | 按键   | 管脚  | | ---- | --- | | KEY0 | B3  | | KEY1 | A10 | ## 初始化配置 然后在 CubeMX 中初始化这两个管脚和一个定时器,定时器用于检测按键的任务提供时钟,设置为 5ms 一次 tick ## 移植按键库 初始化按键对象和回调函数,当按键按下时会printf打印信息 ```c void btn1_cb(struct Button* btn) {     switch (btn->event) {         case SINGLE_CLICK:             printf("btn 1 single click\r\n");             break;         case DOUBLE_CLICK:             printf("btn 1 double click\r\n");             break;         default:             break;     } } void btn2_cb(struct Button* btn) {     switch (btn->event) {         case SINGLE_CLICK:             printf("btn 2 single click\r\n");             break;         case DOUBLE_CLICK:             printf("btn 2 double click\r\n");             break;         case LONG_PRESS_HOLD:             break;         default:             break;     } } uint8_t read_btn(uint8_t button_id) {     uint8_t ret;     // you can share the GPIO read function with multiple Buttons     switch (button_id) {         case 0:             ret = HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin);             return ret;             break;         case 1:             ret = HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin);             return ret;             break;         default:             return 0;             break;     } } struct Button btn1; struct Button btn2; void button_setup(void) {     /* init */     button_init(&btn1, read_btn, 0, 1);     button_init(&btn2, read_btn, 0, 0);     /* attach */     button_attach(&btn1, SINGLE_CLICK, (BtnCallback) btn1_cb);     button_attach(&btn1, DOUBLE_CLICK, (BtnCallback) btn1_cb);     button_attach(&btn2, SINGLE_CLICK, (BtnCallback) btn2_cb);     button_attach(&btn2, DOUBLE_CLICK, (BtnCallback) btn2_cb);     button_attach(&btn2, LONG_PRESS_HOLD, (BtnCallback) btn2_cb);     /* start */     button_start(&btn1);     button_start(&btn2); } ``` 添加printf的支持 ```c int fputc(int ch, FILE *f) {   HAL_UART_Transmit(&huart2,(uint8_t *)&ch,1,0xFFFF);   return ch; } ``` 在中断函数内添加按键任务的时钟 ```c void TIM1_BRK_TIM9_IRQHandler(void) {   /* USER CODE BEGIN TIM1_BRK_TIM9_IRQn 0 */   /* USER CODE END TIM1_BRK_TIM9_IRQn 0 */   HAL_TIM_IRQHandler(&htim9);   /* USER CODE BEGIN TIM1_BRK_TIM9_IRQn 1 */   button_ticks();   /* USER CODE END TIM1_BRK_TIM9_IRQn 1 */ } ``` multi_button.h 中修改tick为5ms ```c #define TICKS_INTERVAL    5        //ms #define DEBOUNCE_TICKS    3        //MAX 7 (0 ~ 7) #define SHORT_TICKS       (300 /TICKS_INTERVAL) #define LONG_TICKS        (1000 /TICKS_INTERVAL) ``` ## 验证 编译烧录到开发板,当按键按下会打印信息,后面也可以将这个单击、双击添加上别的事件。

  • 2024-02-28
  • 发表了主题帖: 【STM32F411Nucleo测评】驱动 1.3 寸 LCD 屏幕

    本帖最后由 pomin 于 2024-2-28 09:09 编辑 > 这块 Nucleo-F411 板子支持 Arduino 的接口,得益于F411的丰富资源,当然可以跑屏幕应用了,正好手上有块空闲屏幕,F411 板没收到时就画了一个接口拓展板子,外接屏幕、按键、RS485 接口 ## 硬件原理图 ## 3D 仿真图 ## 实物图 ## 点屏 ### 确定驱动芯片 这块屏幕是中景园的,驱动是 ST7789,下载下来他们提供的资料,其中只需要他们提供的屏幕初始化序列即可,因为例程较多的是标准库,我是使用的 CubeMX 生成的 HAL 来开发,提供的初始化序列如下 ```c //************* Start Initial Sequence **********// LCD_WR_REG(0x36); if(USE_HORIZONTAL==0)LCD_WR_DATA8(0x00); else if(USE_HORIZONTAL==1)LCD_WR_DATA8(0xC0); else if(USE_HORIZONTAL==2)LCD_WR_DATA8(0x70); else LCD_WR_DATA8(0xA0); LCD_WR_REG(0x3A); LCD_WR_DATA8(0x05); LCD_WR_REG(0xB2); LCD_WR_DATA8(0x0C); LCD_WR_DATA8(0x0C); LCD_WR_DATA8(0x00); LCD_WR_DATA8(0x33); LCD_WR_DATA8(0x33); LCD_WR_REG(0xB7); LCD_WR_DATA8(0x35);   LCD_WR_REG(0xBB); LCD_WR_DATA8(0x37); LCD_WR_REG(0xC0); LCD_WR_DATA8(0x2C); LCD_WR_REG(0xC2); LCD_WR_DATA8(0x01); LCD_WR_REG(0xC3); LCD_WR_DATA8(0x12);    LCD_WR_REG(0xC4); LCD_WR_DATA8(0x20);   LCD_WR_REG(0xC6); LCD_WR_DATA8(0x0F);    LCD_WR_REG(0xD0); LCD_WR_DATA8(0xA4); LCD_WR_DATA8(0xA1); LCD_WR_REG(0xE0); LCD_WR_DATA8(0xD0); LCD_WR_DATA8(0x04); LCD_WR_DATA8(0x0D); LCD_WR_DATA8(0x11); LCD_WR_DATA8(0x13); LCD_WR_DATA8(0x2B); LCD_WR_DATA8(0x3F); LCD_WR_DATA8(0x54); LCD_WR_DATA8(0x4C); LCD_WR_DATA8(0x18); LCD_WR_DATA8(0x0D); LCD_WR_DATA8(0x0B); LCD_WR_DATA8(0x1F); LCD_WR_DATA8(0x23); LCD_WR_REG(0xE1); LCD_WR_DATA8(0xD0); LCD_WR_DATA8(0x04); LCD_WR_DATA8(0x0C); LCD_WR_DATA8(0x11); LCD_WR_DATA8(0x13); LCD_WR_DATA8(0x2C); LCD_WR_DATA8(0x3F); LCD_WR_DATA8(0x44); LCD_WR_DATA8(0x51); LCD_WR_DATA8(0x2F); LCD_WR_DATA8(0x1F); LCD_WR_DATA8(0x1F); LCD_WR_DATA8(0x20); LCD_WR_DATA8(0x23); LCD_WR_REG(0x21); LCD_WR_REG(0x11); //Delay (120); LCD_WR_REG(0x29); ``` ### CubeMX 生成初始化代码 驱动一个 SPI 屏幕,当然要确定 MCU 与屏幕的接线定义,查看F411 板的原理图 可以看到线序如下表 | LCD 信号脚 | MCU 信号脚 | | ------- | ------- | | SCLK    | PA5     | | MOSI    | PA7     | | DC      | PC7     | | CS      | PB6     | | BL      | PA6     | | RES     | PA9     | 然后按照线序用 CubeMX 配置好管脚,并且设置 SPI 为八位,频率为F411的最高速度50MHz 然后配置 DMA,设置为半字,也就是两字节,至于为什么后面再介绍 ### 添加 LCD 驱动 ST7789 的数据包分为两种,通过 DC 管脚来控制是 data 还是 command。 先写三个函数如下,发送命令和八位&十六位数据 ```c void LCD_WriteByte(uint8_t dat) {     HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, 0);     HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, 1);     HAL_SPI_Transmit(&hspi1, &dat, 1, 0xffff);     HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, 1); } void LCD_WriteCmd(uint8_t cmd) {     HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, 0);     HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, 0);     HAL_SPI_Transmit(&hspi1, &cmd, 1, 0xffff);     HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, 1); } void LCD_WriteWord(uint16_t dat) {     LCD_WriteByte(dat >> 8);     LCD_WriteByte(dat); } ``` LCD 驱动万变不离其宗,我这里是分为四步: 1. 复位屏幕 2. 发送初始化序列(不使用 DMA),设置刷新域为全屏,SPI 修改为 16 位模式 3. 设置全屏缓存全局变量,显示内容写入变量 4. DMA 刷新全屏内容 复位屏幕的话就不多说了,直接 io 翻转即可,初始化序列厂家也有提供,直接发送即可,代码如下 ```c static void LCD_Reset(void) {     HAL_GPIO_WritePin(LCD_RES_GPIO_Port, LCD_RES_Pin, 0);     HAL_Delay(200);     HAL_GPIO_WritePin(LCD_RES_GPIO_Port, LCD_RES_Pin, 1);     HAL_Delay(200); } void LCD_Init(void) {     LCD_Reset();     //************* Start Initial Sequence **********//     LCD_WriteCmd(0x36);     if (BOARD_LCD_ROTATION == 0)LCD_WriteByte(0x00);     else if (BOARD_LCD_ROTATION == 1)LCD_WriteByte(0xC0);     else if (BOARD_LCD_ROTATION == 2)LCD_WriteByte(0x70);     else LCD_WriteByte(0xA0);     LCD_WriteCmd(0x3A);     LCD_WriteByte(0x05);     LCD_WriteCmd(0xB2);     LCD_WriteByte(0x0C);     LCD_WriteByte(0x0C);     LCD_WriteByte(0x00);     LCD_WriteByte(0x33);     LCD_WriteByte(0x33);     LCD_WriteCmd(0xB7);     LCD_WriteByte(0x35);     LCD_WriteCmd(0xBB);     LCD_WriteByte(0x37);     LCD_WriteCmd(0xC0);     LCD_WriteByte(0x2C);     LCD_WriteCmd(0xC2);     LCD_WriteByte(0x01);     LCD_WriteCmd(0xC3);     LCD_WriteByte(0x12);     LCD_WriteCmd(0xC4);     LCD_WriteByte(0x20);     LCD_WriteCmd(0xC6);     LCD_WriteByte(0x0F);     LCD_WriteCmd(0xD0);     LCD_WriteByte(0xA4);     LCD_WriteByte(0xA1);     LCD_WriteCmd(0xE0);     LCD_WriteByte(0xD0);     LCD_WriteByte(0x04);     LCD_WriteByte(0x0D);     LCD_WriteByte(0x11);     LCD_WriteByte(0x13);     LCD_WriteByte(0x2B);     LCD_WriteByte(0x3F);     LCD_WriteByte(0x54);     LCD_WriteByte(0x4C);     LCD_WriteByte(0x18);     LCD_WriteByte(0x0D);     LCD_WriteByte(0x0B);     LCD_WriteByte(0x1F);     LCD_WriteByte(0x23);     LCD_WriteCmd(0xE1);     LCD_WriteByte(0xD0);     LCD_WriteByte(0x04);     LCD_WriteByte(0x0C);     LCD_WriteByte(0x11);     LCD_WriteByte(0x13);     LCD_WriteByte(0x2C);     LCD_WriteByte(0x3F);     LCD_WriteByte(0x44);     LCD_WriteByte(0x51);     LCD_WriteByte(0x2F);     LCD_WriteByte(0x1F);     LCD_WriteByte(0x1F);     LCD_WriteByte(0x20);     LCD_WriteByte(0x23);     LCD_WriteCmd(0x21);     LCD_WriteCmd(0x11);     LCD_WriteCmd(0x29);     LCD_EnterBrushMode(); } ``` 其中,LCD_EnterBrushMode 就是框选整个屏幕,然后后面向屏幕发送数据时都使用 DMA 来发送,且 SPI 更改为 16 位,代码如下 ```c static void LCD_SetFrame(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { #if (BOARD_LCD_ROTATION == 0)     LCD_WriteCmd(0x2a);     LCD_WriteWord(x0);     LCD_WriteWord(x1);     LCD_WriteCmd(0x2b);     LCD_WriteWord(y0);     LCD_WriteWord(y1);     LCD_WriteCmd(0x2c); #elif (BOARD_LCD_ROTATION == 1)     LCD_WriteCmd(0x2a);     LCD_WriteWord(x0 + 2);     LCD_WriteWord(x1 + 2);     LCD_WriteCmd(0x2b);     LCD_WriteWord(y0 + 1);     LCD_WriteWord(y1 + 1);     LCD_WriteCmd(0x2c); #elif (BOARD_LCD_ROTATION == 2)     LCD_WriteCmd(0x2a);     LCD_WriteWord(x0 + 1);     LCD_WriteWord(x1 + 1);     LCD_WriteCmd(0x2b);     LCD_WriteWord(y0 + 2);     LCD_WriteWord(y1 + 2);     LCD_WriteCmd(0x2c); #elif (BOARD_LCD_ROTATION == 3)     LCD_WriteCmd(0x2a);     LCD_WriteWord(x0 + 1);     LCD_WriteWord(x1 + 1);     LCD_WriteCmd(0x2b);     LCD_WriteWord(y0 + 2);     LCD_WriteWord(y1 + 2);     LCD_WriteCmd(0x2c); #else #error "The BOARD_LCD_ROTATION set error!!" #endif } void LCD_EnterBrushMode(void) { /* Select full LCD screen */     LCD_SetFrame(0, 0, BOARD_LCD_WIDTH - 1, BOARD_LCD_HEIGHT - 1);     /* Set DC and CS state to DMA continuously transmits data to LCD */     HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, 0);     HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, 1);     hspi1.Init.DataSize = SPI_DATASIZE_16BIT;     HAL_SPI_Init(&hspi1); } ``` 为什么改为 16 位呢?是因为这个屏幕是 240\*240 分辨率的,所以像素点有 115200 字节,DMA 发送的函数的参数 Size 是 uint16_t 类型的,最大 65535,虽然可以修改 HAL 库代码,但是为了尽量不动库代码的原则,采取修改 SPI 和 DMA 为 16 位模式来解决,也就是 57600 个两字节,这样就可以正常使用这个库函数了。 再写一个 DMA 刷屏的函数 ```c volatile uint16_t lcd_tx_buf[BOARD_LCD_HEIGHT][BOARD_LCD_WIDTH]; /* LCD tx buffer. */ void LCD_ReflashFrame(void) { /* Enable DMA transmission */     HAL_SPI_Transmit_DMA(&hspi1, (uint8_t *) lcd_tx_buf, BOARD_LCD_HEIGHT * BOARD_LCD_WIDTH); } void LCD_ClearWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1,     uint16_t color) {     for (uint16_t j = y0; j < y1; j++)     {         for (uint16_t i = x0; i < x1; i++)         {             lcd_tx_buf[j] = color;         }     }     LCD_ReflashFrame(); } ``` ### 点亮屏幕 在 main 函数中添加如下代码,延时是因为没有引出屏幕的 TE 管脚,所以为了避免 DMA 没有发送完的时候缓存区的内容就被修改而导致显示异常的问题就加了延时 ```c   LCD_Init();   LCD_ClearWindow(0, 0, 240, 240, LCD_COLOR_RED);   HAL_Delay(50);   LCD_ClearWindow(0, 0, 160, 160, LCD_COLOR_GREEN);   HAL_Delay(50);   LCD_ClearWindow(0, 0, 80, 80, LCD_COLOR_BLUE); ``` 最终效果如下

  • 发表了主题帖: 【STM32F411Nucleo测评】开箱,搭建开发环境,串口输出

    > 当时申请了年终回炉的Nucleo-F411板子,申请的是年后发货,春节物流速度慢到龟速,现在终于收到了板子,开始玩耍。 ### 开箱 这次申请的是Nucleo-F411的板子,最高100MHz主频,512KB Flash和128KB RAM,外设资源也比较丰富 拿出板卡 Nucleo的白色板子看着还是挺顺眼的 ### 资料获取 STM32 的资料就不用多说了,直接找到官方的原理图,用 CubeMX 配置就可以,找到原理图看下调试串口的管脚是PA2和PA3,用cubemx初始化一下SWD调试口、外部晶振和这两个串口管脚。 生成Keil工程,然后加段循环打印输出hello world的代码 ``` HAL_Delay(500); HAL_UART_Transmit(&huart2, "hello world\r\n", 13, 0xffff); ``` 编译程序,通过板载的STlink下载到板子上,打开设备管理看到STLink2.1的虚拟串口是COM4 可以看到正常输出了hello world

  • 2024-02-27
  • 加入了学习《 得捷电子Follow me第4期》,观看 得捷电子Follow me第4期

  • 2024-02-26
  • 回复了主题帖: 【得捷电子Follow me第4期】项目总结

    FTP最新的代码放在了[GitHub](https://github.com/POMIN-163/W5500-EVB-Pico-FTPServer),具体的修改查看commits不太方便,因为许多对submodule的修改我做成了补丁的形式,可以查看[https://github.com/POMIN-163/W5500-EVB-Pico-FTPServer/tree/main/patches](https://github.com/POMIN-163/W5500-EVB-Pico-FTPServer/tree/main/patches)中的两个补丁文件

  • 2024-02-24
  • 加入了学习《 【得捷电子Follow me第4期】》,观看 【得捷Follow me第4期】简易FTP文件服务器

  • 2024-02-23
  • 发表了主题帖: 【得捷电子Follow me第4期】项目总结

    > 很高兴能参加得捷电子Follow me第4期活动,在官方的 C SDK 上进行开发,借此机会深入学习一下lwip和网络应用层的TCP、UDP、NTP这些的应用,加深对以太网开发的认识。 ## 视频展示 [>>  演示视频](https://training.eeworld.com.cn/video/39550 ">>  演示视频") 因为 wireshark 的抓包分析内容比较多,所以视频中没有体现,在之前的分享中有写 ## 传送门 - [【得捷电子Follow me第4期】入门任务:开箱,开发环境、BLINK、helloworld](https://bbs.eeworld.com.cn/thread-1269906-1-1.html) - [【得捷电子Follow me第4期】基础任务一:静态IP配置、Ping、抓包分析、iperf测速](https://bbs.eeworld.com.cn/thread-1270131-1-1.html) - [【得捷电子Follow me第4期】基础任务二:TCP echo 服务器](https://bbs.eeworld.com.cn/thread-1270399-1-1.html) - [【得捷电子Follow me第4期】进阶任务:从NTP服务器同步时间](https://bbs.eeworld.com.cn/thread-1270403-1-1.html) - [【得捷电子Follow me第4期】终极任务2:SD 卡搭建 FTP 服务器](https://bbs.eeworld.com.cn/thread-1270406-1-1.html) ## DIY 的拓展板 因为来回的插拔杜邦线不太方便,所以画了一个底板,原理图如下,引出了 485 串口、LCD、按键\*3、CH340、两路 ADC ## 基础 + 进阶任务 ### 简介 基础任务和进阶任务都写在了一份代码里面,完成了如下内容: - 点灯 - W5500-EVB-Pico 驱动 2.8 寸屏幕显示内容 - W5500-EVB-Pico、Nucleo-H743 和 PC 互相 ping 通且延迟都在 10ms 以下(之前分享中有写) - W5500-EVB-Pico、Nucleo-H743 ping 通外网(之前分享中有写) - W5500-EVB-Pico 作 TCP Server,Nucleo-H743 作 TCP Client 的数据收发 - W5500-EVB-Pico、Nucleo-H743 通过 DNS 服务器解析得到 NTP 授时服务器 IP,隔 15s 获取一次当前时间 - W5500-EVB-Pico 驱动屏幕显示 TCP Server 接收到的数据、NTP 获取的时间 基础任务和进阶任务都是在 LWIP 下开发的,其中 Nucleo-H743 使用 LWIP 的 NETCONN API,运行 FreeRTOS,W5500-EVB-Pico 为裸机环境,使用 LWIP 的 RAW API,对于两套 api 同步学习。 ### 代码 因为 C-SDK 的代码量较大,所以只展示了核心代码,至于完整的代码可以查看附件 #### Pico 端 ```c int main() {     /* Initialize */     int8_t retval = 0;     uint8_t *pack = malloc(ETHERNET_MTU);     uint16_t pack_len = 0;     struct pbuf *p = NULL;     // Initialize network configuration     IP4_ADDR(&g_picoip, 192, 168, 0, 188);     IP4_ADDR(&g_mask, 255, 255, 255, 0);     IP4_ADDR(&g_gateway, 192, 168, 0, 1);     IP4_ADDR(&g_pcip, 192, 168, 0, 105);     set_clock_khz();     // Initialize stdio after the clock change     stdio_init_all();     wizchip_spi_initialize();     wizchip_cris_initialize();     wizchip_reset();     wizchip_initialize();     wizchip_check();     // Set ethernet chip MAC address     setSHAR(mac);     ctlwizchip(CW_RESET_PHY, 0);     // Initialize LWIP in NO_SYS mode     lwip_init();     netif_add(&g_netif, &g_picoip, &g_mask, &g_gateway, NULL, netif_initialize, netif_input);     g_netif.name[0] = 'e';     g_netif.name[1] = '0';     // Assign callbacks for link and status     netif_set_link_callback(&g_netif, netif_link_callback);     netif_set_status_callback(&g_netif, netif_status_callback);     // MACRAW socket open     retval = socket(SOCKET_MACRAW, Sn_MR_MACRAW, PORT_LWIPERF, 0x00);     if (retval < 0)     {         printf(" MACRAW socket open failed\n");     }     // Set the default interface and bring it up     netif_set_default(&g_netif);     netif_set_link_up(&g_netif);     netif_set_up(&g_netif);     // dns_init();     dns_setserver(0, &g_gateway);     // Start lwiperf server     lwiperf_start_tcp_server_default(fn, NULL);     printf(">>>> ---------------- W5500 Pico >>> ---------------- STM32H743

  • 回复了主题帖: 【得捷Follow me第4期】综合实践之智能家居控制器

    秦天qintian0303 发表于 2024-2-23 08:36 这个手机APP是一个在线网页?   看起来是 HomeAssistant的界面

  • 2024-02-22
  • 加入了学习《【得捷Follow me第4期】项目任务提交》,观看 视频

  • 加入了学习《Follow me 第4期任务视频》,观看 Follow me 第4期任务视频

  • 加入了学习《Follow me 第4期任务视频展示及介绍》,观看 Follow me 第4期任务完成效果视频

统计信息

已有101人来访过

  • 芯积分:211
  • 好友:--
  • 主题:34
  • 回复:23

留言

你需要登录后才可以留言 登录 | 注册


现在还没有留言