<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                ## 基于RT-Thread之HAPH數據采集儀設計 ## [toc] HAPH數據采集儀初始版本于2017年完成,MCU為STM32F103RCT6,顯示為SPI接口的OLED屏,程序是在原數據采集器(藍盒)的基礎上修改的,裸奔,沒有跑RTOS。重新設計的目標是采用操作系統RT-Thread,實現的基本功能如下: * Modbus RTU從站 * OLED顯示、按鍵輸入 * 無線通訊功能,用于主機訪問HAPH數據采集儀modbus寄存器 * 繼電器控制 * ADC數據采集 * SPI Flash存儲及文件管理 ### 1. RT-Thread下載和開發環境的配置 ### #### 1.1 準備工作 #### * 從[RT-Thread官方代碼倉庫](https://github.com/RT-Thread/rt-thread)下載RT-Thread源代碼,我現在下載的版本是3.0.4,可以根據需要切換至相應的版本: ``` git clone https://github.com/RT-Thread/rt-thread ``` * 下載安裝[RT-Thread Env工具](https://pan.baidu.com/s/1cg28rk#list/path=%2F),配置右鍵打開。 * 代碼編程規范參考rt_thread項目的*代碼風格文檔*(```documentation/coding_style_cn.txt```) #### 1.2 選擇bsp #### 數據采集儀的MCU為STM32F103RCT6(*LQFP64,256Kflash,48K ram*)。RT-Thread源代碼對應BSP包為*stm32f10x*和*stm32f10x-HAL*, |驅動模塊|stm32f10x|stm32f10x-HAL| |:-|:-|:- |庫支持|支持標準庫|支持新的HAL庫| CAN總線|√|×| 以太網(dma9000a)|√|× 24C64|√|× GPIO|√|√ ili LCD|√|× LED|√|× SD卡|√|√ SSD1289|√|× I2C|√|× RTC|√|× 看門狗|√|× 觸摸屏|√|× 串口|√|√ SPI|×|√ USB|×|√ 暫時想不到什么理由,就先選擇HAL庫再說,喜新厭舊?選擇挑戰! 選擇**stm32f10x-HAL**!!! 詳見[STM32F10x-HAL 板級支持包](https://www.rt-thread.org/document/site/rt-thread/bsp/stm32f10x-HAL/README/) #### 1.3 構建MDK5工程 #### 1. 打開**RT-Thread Configuration**(輸入```menuconfig```),選擇相關組件和驅動; 2. 生成MDK5工程(輸入```scons --help```查看命令,```scons --target=mdk5 -s```); 3. 用MDK5打開工程,選擇MCU和JTAG仿真器型號,編譯下載調試; #### 1.4 工程里的main()函數 #### RT-Thread里的main()函數其實是用戶線程的入口,本身也是一個線程,不是傳統意義里的main函數,的由來: ``` //main()相關的選項開關 #define RT_USING_COMPONENTS_INIT #define RT_USING_USER_MAIN #define RT_MAIN_THREAD_STACK_SIZE 4096//2048 //main()任務的創建 #ifdef RT_USING_HEAP tid = rt_thread_create("main", main_thread_entry, RT_NULL, RT_MAIN_THREAD_STACK_SIZE, RT_THREAD_PRIORITY_MAX / 3, 20); RT_ASSERT(tid != RT_NULL); #else ``` #### 1.5 組件和驅動啟動順序 #### 在*component.c*看到一段有關組件和驅動啟動順序的說明,如下 ``` #ifdef RT_USING_COMPONENTS_INIT /* * Components Initialization will initialize some driver and components as following * order: * rti_start --> 0 * BOARD_EXPORT --> 1 * rti_board_end --> 1.end * * DEVICE_EXPORT --> 2 * COMPONENT_EXPORT --> 3 * FS_EXPORT --> 4 * ENV_EXPORT --> 5 * APP_EXPORT --> 6 * * rti_end --> 6.end * * These automatically initializaiton, the driver or component initial function must * be defined with: * INIT_BOARD_EXPORT(fn); * INIT_DEVICE_EXPORT(fn); * ... * INIT_APP_EXPORT(fn); * etc. */ ``` ### 2. 功能實現 ### 根據設計要求應用*RT-Thread Configuration*選擇相關組件。 #### 2.1 GPIO的操作(*繼電器、按鍵、LED和蜂鳴器*) #### 打開*RT-Thread Configuration*,使能GPIO驅動,```RT-Thread Components -> Device Drivers -> [*]Using generic GPIO device drivers```,具體應用參考[通用GPIO設備應用筆記](https://www.rt-thread.org/document/site/rtthread-application-note/driver/gpio/an0002-rtthread-driver-gpio/)。 | |pin_name|pin_number(LQFP64)|備注| |:-|:-|:-|:- |繼電器1|PC0|8| |繼電器2|PC1|9| |繼電器3|PC2|10| |繼電器4|PC3|11| |LED(綠)|PC7|38| |按鍵+|PC9|40| |按鍵-|PC8|39| |按鍵MODE|PA8|41| |蜂鳴器|PC10|51| GPIO初始化 ``` /* LED indicator initialize */ rt_pin_mode(PIN_MCU_LED, PIN_MODE_OUTPUT); //設置pin為輸出模式 /* 4 relay control pin initialize */ rt_pin_mode(PIN_RELAYOUT_SW_1, PIN_MODE_OUTPUT); //設置pin為輸出模式 rt_pin_mode(PIN_RELAYOUT_SW_2, PIN_MODE_OUTPUT); //設置pin為輸出模式 rt_pin_mode(PIN_RELAYOUT_SW_3, PIN_MODE_OUTPUT); //設置pin為輸出模式 rt_pin_mode(PIN_RELAYOUT_SW_4, PIN_MODE_OUTPUT); //設置pin為輸出模式 /* button pin initialize */ rt_pin_mode(PIN_BUTTON_PLUS, PIN_MODE_INPUT); rt_pin_mode(PIN_BUTTON_MINUS, PIN_MODE_INPUT); rt_pin_mode(PIN_BUTTON_MODE, PIN_MODE_INPUT); /* BUZZER control pin initialize */ rt_pin_mode(PIN_BUZZER_SW, PIN_MODE_OUTPUT); ``` ##### 2.1.1 按鍵處理 ##### 新建一個掃描按鍵的任務,讀取按鍵,若有按鍵按下,發送相應鍵值到鍵值消息隊列。新建一個響應按鍵的任務,讀取鍵值消息隊列,并處理相關消息。 采用RT-Thread的消息隊列(rt_messagequeue)緩存鍵值,這里配置消息的內存池key_value_msg_pool[16]為16字節,每條消息為1個字節,按理應該緩存16個消息,經測試只能緩存2條(16÷8)消息,比較奇怪!最開始我定義的內存池為4個字節,結果發送失敗,不能正常工作。還需深入了解RT-Thread的消息隊列的相關機制。 1. 初始化鍵值消息隊列 ``` /* 消息隊列控制塊 */ static struct rt_messagequeue mq_key_value; /* 消息隊列中用到的放置消息的內存池 */ static rt_uint8_t key_value_msg_pool[16]; /* initialize message queue for key scan */ rt_err_t result; result = rt_mq_init(&mq_key_value, "mq_key", &key_value_msg_pool[0], /* 內存池指向msg_pool */ 1, /* 每個消息的大小是 128 - void* */ sizeof(key_value_msg_pool), /* 內存池的大小是msg_pool的大小 */ RT_IPC_FLAG_FIFO); /* 如果有多個線程等待,按照FIFO的方法分配消息 */ if (RT_EOK != result) { rt_kprintf("init message queue failed.\n"); return -1; } ``` 2. 發送消息 ``` result = rt_mq_send(&mq_key_value, &key_value, sizeof(key_value)); if (RT_EOK != result) { rt_kprintf("rt_mq_send ERR\n"); } ``` 3. 接收消息 ``` if(RT_EOK == rt_mq_recv(&mq_key_value, &key_value, sizeof(key_value), /*RT_WAITING_FOREVER*/10)) { rt_kprintf("recv mq for key 0x%02x\n", key_value); switch(key_value) { case KEY_VALUE_MINUS: // break; case KEY_VALUE_PLUS: break; case KEY_VALUE_MODE: break; default: break; } } ``` ##### 2.1.2 繼電器和LED控制 ##### * 繼電器控制 ``` if(PIN_HIGH == rt_pin_read(PIN_RELAYOUT_SW_1)) { rt_pin_write(PIN_RELAYOUT_SW_1, PIN_LOW); } else { rt_pin_write(PIN_RELAYOUT_SW_1, PIN_HIGH); } ``` * LED控制 ``` rt_pin_write(PIN_MCU_LED, PIN_LOW); //PIN_HIGH ``` ##### 2.1.3 有源蜂鳴器的控制 ##### 有源蜂鳴器通過gpio控制 #### 2.2 串口的操作(470MHz 無線通訊模塊) #### 470MHz無線通訊模塊,通過MCU的串口3通訊控制,相關引腳定義: ||pin_name|pin_number|備注 |:-|:-|:-|:- |TXD|PB10|29|MCU發送,無線模塊接收 |RXD|PB11|30|MCU接收,無線模塊發送 |RST|PB5|57|無線模塊復位 |STATUS|PC5|25|無線模塊狀態 1. 串口初始化 ``` rt_err_t uart_open(const char *name) { rt_err_t res; /* 查找系統中的串口設備 */ uart_device_wireless_module = rt_device_find(name); /* 查找到設備后將其打開 */ if (uart_device_wireless_module != RT_NULL) { res = rt_device_set_rx_indicate(uart_device_wireless_module, uart_intput); if (res != RT_EOK) /* 檢查返回值 */ { rt_kprintf("set %s rx indicate error.%d\n",name,res); return -RT_ERROR; } /* 打開設備,以可讀寫、中斷方式 */ res = rt_device_open(uart_device_wireless_module, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_INT_RX ); /* 檢查返回值 */ if (res != RT_EOK) { rt_kprintf("open %s device error.%d\n", name, res); return -RT_ERROR; } } else { rt_kprintf("can't find %s device.\n",name); return -RT_ERROR; } /* 初始化事件對象 */ rt_event_init(&uart_470m_event, "uart_470m_event", RT_IPC_FLAG_FIFO); return RT_EOK; } /* 回調函數 */ static rt_err_t uart_intput(rt_device_t dev, rt_size_t size) { /* 發送事件 */ rt_event_send(&uart_470m_event, UART_RX_EVENT); return RT_EOK; } ``` 2. 串口發送 ``` void uart_putchar(const rt_uint8_t c) { rt_size_t len = 0; rt_uint32_t timeout = 0; do { len = rt_device_write(uart_device_wireless_module, 0, &c, 1); timeout++; } while (len != 1 && timeout < 500); } void uart_putstring(rt_uint8_t *pt) { while(*pt) { uart_putchar(*pt++); } } ``` 3. 串口接收和數據幀的處理 ``` /* 串口接收事件標志 */ #define UART_RX_EVENT (1 << 0) #define UART_470M_RECEIVE_BUFFER_SIZE 128 #define UART_470M_MSG_FRAME_TIMEOUT_MS 100 // 100mS /* 事件控制塊 */ static struct rt_event uart_470m_event; /* 設備句柄 */ static rt_device_t uart_device_wireless_module = RT_NULL; rt_uint8_t uart_470m_receive_buffer[UART_470M_RECEIVE_BUFFER_SIZE]; rt_uint8_t uart_470m_receive_buffer_pos; rt_uint8_t uart_get_msg_frame(void) { rt_uint32_t e; rt_uint8_t ch; rt_err_t result; uart_470m_receive_buffer_pos = 0; /* 接收事件 */ result = rt_event_recv(&uart_470m_event, UART_RX_EVENT, RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, RT_WAITING_FOREVER, &e); /* 讀取1字節數據 */ while (1 == rt_device_read(uart_device_wireless_module, uart_470m_receive_buffer_pos, uart_470m_receive_buffer + uart_470m_receive_buffer_pos, 1)) { uart_470m_receive_buffer_pos++; if(uart_470m_receive_buffer_pos >= UART_470M_RECEIVE_BUFFER_SIZE) { uart_470m_receive_buffer_pos = 0; } } while(1){ result = rt_event_recv(&uart_470m_event, UART_RX_EVENT, RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, rt_tick_from_millisecond(UART_470M_MSG_FRAME_TIMEOUT_MS)/*RT_WAITING_FOREVER*/, &e); if(-RT_ETIMEOUT == result) // received a frame of message { uart_470m_receive_buffer[uart_470m_receive_buffer_pos] = 0x00; rt_kprintf("receive message(num_%d):%s\n", uart_470m_receive_buffer_pos, (char*)uart_470m_receive_buffer); uart_putchar('e'); break; } else { /* 讀取1字節數據 */ while (1 == rt_device_read(uart_device_wireless_module, uart_470m_receive_buffer_pos, uart_470m_receive_buffer + uart_470m_receive_buffer_pos, 1)) { uart_470m_receive_buffer_pos++; if(uart_470m_receive_buffer_pos >= UART_470M_RECEIVE_BUFFER_SIZE) { uart_470m_receive_buffer_pos = 0; } } } } return ch; } ``` 4. 串口的應用 ``` void uart_470m_thread_entry(void* parameter) { rt_uint8_t uart_rx_data; /* 打開串口 */ if (uart_open("uart3") != RT_EOK) { rt_kprintf("uart open error.\n"); while (1) { rt_thread_delay(10); } } /* 單個字符寫 */ uart_putchar('2'); uart_putchar('0'); uart_putchar('1'); uart_putchar('8'); uart_putchar('\n'); /* 寫字符串 */ //uart_putstring("Hello RT-Thread!\r\n"); while (1) { /* 讀數據 */ //uart_rx_data = uart_getchar(); /* 錯位 */ //uart_rx_data = uart_rx_data + 1; /* 輸出 */ //uart_putchar(uart_rx_data); uart_get_msg_frame(); } } ``` #### 2.3 SPI flash存儲(W25Q128FVSIG) #### 在配置工具中: * RT-Thread Configuration -> RT-Thread Components -> Device Drivers -> 選擇Using SPI Bus/Device device drivers和Using W25QXX SPI Norflash; * RT-Thread Configuration -> RT-Thread Components -> Device virtual file system -> 選擇 Using device virtual file system和Enable elm-chan fatfs; * RT-Thread Configuration -> Using spi2; SPI flash存儲連接的相關引腳定義: ||pin_name|pin_number|備注 |:-|:-|:-|:- |nCS|PB1|27|片選,低電平有效 |SPI2_SCK|PB13|34|SPI2的時鐘 |SPI2_MOSI|PB15|36|主出 |SPI2_MISO|PB14|35|主入 由于原bsp對spi flash(W25Qxxx)在工程配置工具menuconfig沒有設置好,需修改menuconfig的相關配置文件Kconfig和scons的相關配置文件SConsript。 ① 復制flash存儲器的驅動`bsp/stm32fxx-HAL/drv_spiflash.c`至工程,修改`xxx\bsp\stm32f10x-HAL\drivers\SConscript`相關的語句,以便scons工具生成工程。 ``` if GetDepend(['RT_USING_W25QXX']): src += ['drv_spiflash.c'] ``` ② 修改menuconfig的配置文件,添加spi_flash的片選引腳選項和spi的設備名稱,xxx\components\drivers\Kconfig ``` config RT_USING_SPI bool "Using SPI Bus/Device device drivers" default n if RT_USING_SPI config RT_USING_W25QXX bool "Using W25QXX SPI NorFlash" default n if RT_USING_W25QXX config RT_W25QXX_CS_PIN int "W25Qxx cs pin" default 27 config RT_W25QXX_SPI_BUS_NAME string "W25Qxx SPI bus name" default "spi2" endif endif ``` **特別注意:由于W25Q128的扇區大小為4096,須設置RT_DFS_ELM_MAX_SECTOR_SIZE為4096,否則不能正常格式化和掛載!!!** ``` #define RT_DFS_ELM_MAX_SECTOR_SIZE 4096 ``` * 格式化flash,掛載 在中斷格式化flash的命令為 >mkfs -t elm flash0 掛載的代碼如下,驗證掛載是否成功可用命令(*df,ls,echo,cat*): ``` rt_device_t device = RT_NULL; char* device_name = "flash0"; // step 1:find device device = rt_device_find(device_name); if( device == RT_NULL) { rt_kprintf(device_name); rt_kprintf("device [%s]: not found!\r\n", device_name); } // step 2:init device if (!(device->flag & RT_DEVICE_FLAG_ACTIVATED)) { rt_err_t result; result = rt_device_init(device); if (result != RT_EOK) { rt_kprintf("To initialize device:%s failed. The error code is %d\r\n", device->parent.name, result); } else { device->flag |= RT_DEVICE_FLAG_ACTIVATED; } } if (dfs_mount(device_name, "/", "elm", 0, 0) == 0) { rt_kprintf("%s mount to / \n", device->parent.name); } else { rt_kprintf("%s mount to / failed!\n", device->parent.name); } ``` #### 2.4 通過無線模塊控制繼電器 #### 通過470M無線模塊控制4路繼電器,通訊協議采用Modbus RTU之強制單線圈(功能5指令)。該指令用于強制單個線圈(DO,0X類型)為開或關的狀態。 05狀態碼指令包含線圈地址和設置數據,線圈的起始地址為0000H~0015H(16個線圈的尋址地址);設置數據強制線圈的ON/OFF狀態,值FF00H表示線圈處于ON狀態,值0000H表示線圈處于OFF狀態,其它值對線圈無效。 *** 示例:請求從機17號DO1為開 |從站地址|功能碼|DO地址(高位)|DO地址(低位)|值(高位)|值(低位)|CRC16(高位)|CRC16(低位) |-|-|-|-|-|-|-|-|-|-|-|-| |11h|05h|00h|00h|00h|FFh|00h|xxH|xxH| 響應 |從站地址|功能碼|DO地址(高位)|DO地址(低位)|值(高位)|值(低位)|CRC16(高位)|CRC16(低位) |-|-|-|-|-|-|-|-|-|-|-|-| |11h|05h|00h|00h|00h|FFh|00h|xxH|xxH| #### 2.5 Modbus RTU從站 #### Modbus RTU協議參考[這里](https://en.wikipedia.org/wiki/Modbus),該Modbus RTU從站占用UART1,如下: |名稱|pin_name|pin_num|備注 |-|-|-|- 485_TXD|PA9|42| 485_RXD|PA10|43 RT_EN|PA11|44|發送接收使能開關 1. PORTING 在文件(*portserial.c*)定義使能開關引腳 >#define MODBUS_SLAVE_RT_CONTROL_PIN_INDEX 44 編譯提示freemodbus相關文件的assert_param未定義,解決方法是注釋掉相關語句即可,這種方法比較粗魯,以后有時間再整吧。 2. 建立ModbusRTU從站任務 在該項目中的modbusRTU從站相關配置,從站地址0x01,串口號為COM1,波特率為9600,無檢驗。如下: ``` static void modbus_rtu_slave_thread_entry(void* parameter) { eMBErrorCode err; err = eMBInit(MB_RTU, 0x01, 1, BAUD_RATE_9600, MB_PAR_NONE); if(MB_ENOERR != err){ rt_kprintf("eMBInit fail..."); goto suspend; } eMBEnable(); while (1){ err = eMBPoll(); if(MB_ENOERR != err){ rt_kprintf("eMBPoll states error..."); goto suspend; } else{ rt_kprintf("response..."); } } suspend: rt_thread_suspend(rt_thread_self()); } ``` 3. 測試ModbusRTU從站功能,問題1 用串口助手發送訪問從站(0x01)查詢指令(0x03功能碼), >01 03 00 00 00 01 84 0A 結果有時候響應有時不響應,解決的辦法是,延長幀間隔時間,避免接收指令異常,修改文件*mbrtu.c*如下: ``` eMBErrorCode eMBRTUInit( UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity ) { ... usTimerT35_50us = 5 * ( 7UL * 220000UL ) / ( 2UL * ulBaudRate ); ... } ``` 4. 問題2 至此,每次接收到查詢指令都能響應,但是回應信息異常,信息的最后兩個字節丟失,如應該回應的信息為(*01 03 02 00 00 B8 44*),結果最后的兩個字節(*B8 44*)接收不到。 * 問題分析:通過串口發送完回應數據(其實是送至串口發送緩沖區)后,發送接收控制端切換為接收狀態,這時串口的數據最后的一兩個字節可能還在發送緩沖區,造成回應數據的丟數據。 * 解決方法1:在發送數據以后加延時,文件portserial.c,函數vMBPortSerialEnable(),添加一個延時: ``` void vMBPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable) { rt_uint32_t recved_event; rt_thread_delay(rt_tick_from_millisecond(50)); //delay 50mS to avoid lose the last sending bytes if (xRxEnable) ... } ``` * 解決方法2:在串口發送中斷函數中,發送完緩沖區內數據后,加一段延時,以便把數據發送完成,再把發送接收使能端切換為接收模式。文件mbrtu.c,函數xMBRTUTransmitFSM(): ``` BOOL xMBRTUTransmitFSM( void ) { BOOL xNeedPoll = FALSE; // assert_param( eRcvState == STATE_RX_IDLE ); switch ( eSndState ) { case STATE_TX_XMIT: /* check if we are finished. */ if( usSndBufferCount != 0 ) { ``` } else { for(int i = 60000; i > 0; i--); // delay to avoid lose the last sending bytes xNeedPoll = xMBPortEventPost( EV_FRAME_SENT ); } break; } return xNeedPoll; } ``` 通過兩種方法可以解決發送的最后兩個字節被切掉的問題,個人感覺還是方法1稍微靠譜點,方法2添加的延時for語句可能會被有些編譯器優化掉。 #### 2.6 TIM之PWM功能驗證 #### TIM之PWM功能驗證,用于驗證設置頻率的方法和正確性,相關的宏定義為HAPH_MULTI_CHANNEL_ADC_SAMPLE_CLOCK_FREQ_TEST,相關的文件為*timer_test.c*和*time_test.h*。 >[warning]由于實驗的時鐘和下節ADC的觸發時鐘為同一個TIM,故TIM之PWM功能驗證和ADC數據采集功能做了互斥處理,即只能同時實現一個功能。 #### 2.7 ADC數據采集 #### ##### 2.7.1 ADC驅動 ##### 采集12個通道的直流信號或50Hz的交流信號的電流值,應用scons工具添加文件**multi_channel_adc.c**和**multi_channel_adc.h**,關聯的宏定義: > #define MULTI_CHANNEL_ADC 修改menuconfig工具的配置文件(xxx\bsp\stm32f10x-HAL\Kconfig),添加ADC數據采集項**multi channel adc**: ``` config MULTI_CHANNEL_ADC bool "Using multi channel adc" default n ``` 在目錄\bsp\stm32f10x-HAL\applications,添加文件**multi_channel_adc.c**和**multi_channel_adc.h**。 在env工具輸入*scons --target=mdk5 -s*,重新生成工程,打開工程,在文件rtconfig.h即可查到新定義的宏*MULTI_CHANNEL_ADC*,并在工程GROUP(Applications)下看到新建的文件*multi_channel_adc.c* 參考的例程為從STM32下載的STM32 HAL官方庫的例程,以后有時間可以參考*drv_gpio.c編寫ADC相關驅動 > 位于 xxx\STM32Cube_FW_F1_V1.6.0\Projects\STM32F103RB-Nucleo\Examples\ADC\ADC_AnalogWatchdog\MDK-ARM STM32Fxxx ADC通道轉換模式的問題: STM32的ADC有單次轉換和連續轉換2種模式,這兩種模式又可以選擇是否結合掃描模式。 |CONT=0,SCAN=0| 單次轉換模式|單次掃描1通道 |-|-|-| |CONT=1,SCAN=0| 連續轉換模式|連續掃描1通道 |CONT=0,SCAN=1| 掃描轉換模式| 所有ADC_SQR序列通道轉換一次后停止。(單次掃描組) |CONT=1,SCAN=1| 掃描轉換模式| 所有ADC_SQR序列通道轉換一次后,再從第一個通道循環。連續掃描一組 需要注意的是,如果你的轉換序列當中有超過一個通道需要轉換的話,那么必須要開啟掃描模式,否則的話,始終只轉換第一通道。 用ADC1,Regular通道的順序為Ch0,Ch1,Ch2,Ch3,啟動Scan模式 - 在單次轉換模式下,啟動ADC1,則 1. 開始轉換Ch0 1. 轉換完成后自動開始轉換Ch1 1. 轉換完成后自動開始轉換Ch2 1. 轉換完成后自動開始轉換Ch3 1. 轉換完成后停止,等待ADC的下一次啟動。下一次ADC啟動從第一步開始 - 在連續轉換模式下,啟動ADC1,則 1. 開始轉換Ch0 1. 轉換完成后自動開始轉換Ch1 1. 轉換完成后自動開始轉換Ch2 1. 轉換完成后自動開始轉換Ch3 1. 轉換完成后回到第一步 如果沒啟動Sacn模式則上述過程中沒有2、3、4這三個步驟 上述前提是Discontinuous模式沒有啟用。 - 工程配置 1. 配置定時器 這里定時器用作ADC的采樣觸發時鐘,為保證一個周期50Hz交流信號采樣20個點,定時器的頻率為50×20=1000Hz,周期為1mS。 1. 配置ADC |ADCMODE_SEL1|PA4|20|ADC_CH0模式選擇 |-|-|-|-| |ADCMODE_SEL2|PA5|21|ADC_CH1模式選擇 |CHSEL0|PB8|61|通道選擇 |CHSEL1|PB9|62|通道選擇 在文件*stm32f1xx_hal_conf.h*使能HAL庫的ADC頭文件包含: ``` #define HAL_ADC_MODULE_ENABLED /* 這樣才會把ADC相關頭文件包含進來如下 */ #ifdef HAL_RCC_MODULE_ENABLED #include "stm32f1xx_hal_rcc.h" #endif /* HAL_RCC_MODULE_ENABLED */ ``` ##### 2.7.2 ADC轉換值處理 ##### ADC值在定時器時鐘的驅動下采集各通道電壓值,通過DMA存入DMA緩沖區,緩沖區滿后產生DMA中斷,在中斷里計算處理各通道電壓值。由于采樣數據比較多,運算量比較大,中斷處理時間過長,導致中斷處理還沒完,下次中斷又來了。 #### 2.8 OLED顯示 #### 采用的顯示器為OLED_MODULE:QG-2864KSWNG01(1.54inch),控制器為SSD1309,這里使用SPI接口,相關的連線如下: |OLED_RST|PB0|26|復位端| |-|-|-|-| |OLED_CS|PB12|33|片選端 |OLED_SPI_SCK|PB13|34 |OLED_SPI_MOSI|PB15|36 |OLED_D/C|PA12|45|數據/命令選擇端 |V_12V_SW|PC6|37|OLED之12V電源開關 *** OLED顯示驅動的編寫,參考[RT-Thread在Github上托管的示例代碼](https://github.com/RT-Thread-packages/samples),SPI接口的OLED顯示的示例代碼位于目錄[samples/driver/spi](https://github.com/RT-Thread-packages/samples/tree/master/driver/spi),在此基礎上編寫OLED驅動程序,文件名為*drv_ssd1309.c和drv_ssd1309.h* 至此,SPI2總線上掛在了2個設備,一個是flash存儲器(w25q128),另一個是OLED顯示器,這兩個設備均由片選端,同時只能使能一個設備,RT-Thread的SPI驅動保證互斥的使用SPI總線(這是我個人理解,因為我沒有刻意的去建一個互斥量來分時使用spi總線,應該是RTT的SPI驅動框架在保證),目前來看這兩個設備能正常使用。 #### 2.9 cJSON #### cJSON is C語言編寫的超輕量級JSON解析器,可以在menuconfig配置工具中選擇使用: >*.config - RT-Thread Configuration -> RT-Thread online packages -> IoT - internet of things -> >[ * ] cJSON: Ultralightweight JSON parser in ANSI C 選擇cJSON組件后,下載該組件,重新重新生成工程。 ``` pkgs --update 下載組件 scons --target=mdk5 -s 生成mdk5工程 ``` cJSON組件是開源項目,[托管于github](https://github.com/DaveGamble/cJSON),具體使用參看**README.md** 配置文件的樣式: ``` { "slave_address": "CALL", "hw": 4660, "fw": 22136, "ac_range": 20, "dc_range": 100, "wireless": { "pan": 8214, "local": 257, "dst": 1 }, "com1": { "baudrate": 9600, "parity": "odd" } } ``` 1. 創建json - in = cJSON_CreateObject();創建json對象 - cJSON_AddStringToObject();向json對象中添加字符串 - cJSON_AddNumberToObject();向json對象中添加數字 (2)arr = cJSON_CreateArray();創建json數組 cJSON_AddItemToArray(jar,cJSON_CreateNumber(10501));向json數組添加元素 (3)cJSON_AddItemToObject();將一個元組添加到對象中,如把arr添加到in中,可以理解為json中再封裝json數據 2. 解析json (1)cJSON_Parse();解析json數據 cJSON_GetObjectItem(json,"data")->valuestring;獲取json中data元組的值,字符串 cJSON_GetObjectItem(json,"data")->valueint;獲取json中data元組的值,數值 3. 其它 cJSON_ReplaceItemInObject(json,"data",cJSON_CreateString("hello"));用于代替json對象中data元組的值 cJSON_PrintUnformatted(in);對創建的json不帶格式輸出,即對每一個元組不用換行分隔 cJSON_Delete(in);刪除創建的json對象 #### 2.10 W5500驅動 #### STM32F103通過SPI接口連接W5500擴展以太網口,實現了TCP/IP協議,[代碼托管于github](https://github.com/thrillerqin/W5500_DEMO)。現在參考[《STM32+W5500+MQTT+Android實現遠程數據采集及控制》](https://blog.csdn.net/WIZnet2012/article/details/47401241)實現遠程數據采集和控制。 MQTT協議是基于TCP的協議,所以我們只需要在單片機端實現TCP客戶端代碼之后就很容易移植MQTT了,我在STM32F103RC裸機平臺上實現的W5500官方驅動庫ioLibrary_Driver,[托管于github](https://github.com/Wiznet/ioLibrary_Driver)。 1. 配置Kconfig文件() 在文件stm32f10x-HAL/Kconfig添加W5500的相關設置項: ``` config RT_USING_W5500 bool "Using W5500 ethernet module" default n if RT_USING_W5500 config RT_W5500_CS_PIN int "W5500 cs pin" default 52 config RT_W5500_SPI_BUS_NAME string "W5500 SPI bus name" default "spi2" endif ``` 生成工程后,在rtconfig.h中自動添加相關宏 ``` #define RT_USING_W5500 #define RT_W5500_CS_PIN 52 #define RT_W5500_SPI_BUS_NAME "spi2" ``` 2. 實現W5500的驅動(drv_w5500.c和drv_w5500.h) W5500以太網模塊用的SPI接口,用RT-Thread的SPI驅動框架驅動, |DAI2017|W5500 ethernet module| |-|-| |PB13|SCLK | PB15 |MOSI |PB14 |MISO |PC11 |nCS |PB0 |RST |--|INTn ``` static struct rt_spi_device spi_w5500_dev; /* SPI設備W5500對象 */ static struct stm32_hw_spi_cs spi_w5500_cs; /* SPI設備CS片選引腳 */ /** *@brief W5500復位設置函數 *@param 無 *@return 無 */ void w5500_reset(void) { //與OLED現實模塊共用一個復位端,注意只能復位一次,避免兩個器件重復復位 // rt_pin_write(W5500_RST_PIN, PIN_LOW); // //wait at least 100ms for reset // rt_thread_delay(100); // rt_pin_write(W5500_RST_PIN, PIN_HIGH); } static int rt_hw_w5500_config(void) { rt_err_t res; /* w5500 CS pin */ spi_w5500_cs.pin = RT_W5500_CS_PIN; rt_pin_mode(spi_w5500_cs.pin, PIN_MODE_OUTPUT); /* 設置片選管腳模式為輸出 */ res = rt_spi_bus_attach_device(&spi_w5500_dev, SPI_W5500_DEVICE_NAME, RT_W5500_SPI_BUS_NAME, (void *)&spi_w5500_cs); if (res != RT_EOK) { W5500_TRACE("rt_spi_bus_attach_device(W5500)!\r\n"); return res; } /* config spi */ { struct rt_spi_configuration cfg; cfg.data_width = 8; /* RT_SPI_MODE_0(OK), RT_SPI_MODE_1(NG), RT_SPI_MODE_2(NG), RT_SPI_MODE_3(OK) */ cfg.mode = RT_SPI_MASTER | RT_SPI_MODE_0 | RT_SPI_MSB; cfg.max_hz = 20 * 1000 * 1000; /* 20M,SPI max 42MHz,w5500 4-wire spi */ rt_spi_configure(&spi_w5500_dev, &cfg); } return RT_EOK; } int rt_hw_w5500_init(void) { rt_hw_w5500_config(); // rt_pin_mode(W5500_RST_PIN, PIN_MODE_OUTPUT); // w5500_reset(); //與OLED現實模塊共用一個復位端,注意只能復位一次,避免兩個器件重復復位 return 0; } //INIT_PREV_EXPORT(rt_hw_w5500_init); /* 使用RT-Thread的組件自動初始化機制 */ /* device initialization */ //INIT_DEVICE_EXPORT(rt_hw_w5500_init);// INIT_EXPORT(fn, "3") /* components initialization (dfs, lwip, ...) */ //INIT_COMPONENT_EXPORT(rt_hw_w5500_init);// INIT_EXPORT(fn, "4") //INIT_APP_EXPORT(rt_hw_w5500_init); /** *@brief 寫入一個8位數據到W5500 *@param addrbsb: 寫入數據的地址 *@param data:寫入的8位數據 *@return 無 */ void IINCHIP_WRITE( uint32_t addrbsb, uint8_t data) { rt_err_t result; uint8_t send_buf1[4]; send_buf1[0] = (addrbsb & 0x00FF0000)>>16; send_buf1[1] = (addrbsb & 0x0000FF00)>> 8; send_buf1[2] = (addrbsb & 0x000000F8) + 4; result = rt_spi_send_then_send(&spi_w5500_dev, send_buf1, 3, &data, 1); if(result != RT_EOK) { W5500_TRACE("w5500 write data error\r\n"); } return; } void WIZCHIP_WRITE(uint32_t AddrSel, uint8_t wb) { IINCHIP_WRITE(AddrSel, wb); } /** *@brief 從W5500讀出一個8位數據 *@param addrbsb: 寫入數據的地址 *@param data:從寫入的地址處讀取到的8位數據 *@return 無 */ uint8_t IINCHIP_READ(uint32_t addrbsb) { uint8_t send_buf1[4]; rt_err_t result; uint8_t data; send_buf1[0] = (addrbsb & 0x00FF0000)>>16; send_buf1[1] = (addrbsb & 0x0000FF00)>> 8; send_buf1[2] = (addrbsb & 0x000000F8); result = rt_spi_send_then_recv(&spi_w5500_dev, send_buf1, 3, &data, 1); if(result != RT_EOK) { W5500_TRACE("w5500 read data error\r\n"); } return data; } uint8_t WIZCHIP_READ(uint32_t AddrSel) { IINCHIP_READ(AddrSel); } /** *@brief 向W5500寫入len字節數據 *@param addrbsb: 寫入數據的地址 *@param buf:寫入字符串 *@param len:字符串長度 *@return len:返回字符串長度 */ uint16_t wiz_write_buf(uint32_t addrbsb, uint8_t* buf, uint16_t len) { rt_err_t result; uint8_t send_buf1[4]; send_buf1[0] = (addrbsb & 0x00FF0000)>>16; send_buf1[1] = (addrbsb & 0x0000FF00)>> 8; send_buf1[2] = (addrbsb & 0x000000F8) + 4; result = rt_spi_send_then_send(&spi_w5500_dev, send_buf1, 3, buf, len); if(result == -RT_EIO) { W5500_TRACE("w5500 write buf error\r\n"); return(0); } else { return(len); } } void WIZCHIP_WRITE_BUF(uint32_t AddrSel, uint8_t* pBuf, uint16_t len) { wiz_write_buf(AddrSel, pBuf, len); } /** *@brief 從W5500讀出len字節數據 *@param addrbsb: 讀取數據的地址 *@param buf:存放讀取數據 *@param len:字符串長度 *@return len:返回字符串長度 */ uint16_t wiz_read_buf(uint32_t addrbsb, uint8_t* buf,uint16_t len) { uint8_t send_buf1[4]; rt_err_t result; send_buf1[0] = (addrbsb & 0x00FF0000)>>16; send_buf1[1] = (addrbsb & 0x0000FF00)>> 8; send_buf1[2] = (addrbsb & 0x000000F8); result = rt_spi_send_then_recv(&spi_w5500_dev, send_buf1, 3, buf, len); if(result == -RT_EIO) { W5500_TRACE("w5500 read buf error\r\n"); return(0); } else { return(len); } } void WIZCHIP_READ_BUF (uint32_t AddrSel, uint8_t* pBuf, uint16_t len) { wiz_read_buf(AddrSel, pBuf, len); } ``` 3. W5500初始化 設置W5500的IP、子網掩碼、網關和DNS服務器,即緩沖區大小后,即可ping通,之中即可測試應用協議,如MQTT。 #### 2.10 MQTT #### 沒有用RT-Thread官方的MQTT組件,他是基于LWIP的,而W5500是硬件實現TCP/IP協議,不能直接用,由于我是剛接觸mqtt不會做適應性的改動,所以用[wiznet的官方庫里的mqtt例程實現](https://github.com/Wiznet/ioLibrary_Driver)。 目前基本能實現發送和接收mqtt消息。 - 參考[基于STM32F4移植W5500官方驅動庫ioLibrary_Driver](https://blog.csdn.net/xiayufeng520/article/details/79602635); - **eclipse paho**](http://www.eclipse.org/paho/)下載[Embedded MQTT](https://github.com/eclipse/paho.mqtt.embedded-c)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看