
1. 方案介绍% c+ `) P4 g) y# d 系统功能就是一个可以通过手机蓝牙实时查看远端采集器实时温度的系统。系统由采集器和Android手机端组成。采集器由STM32F072-Nucleo、蓝牙转串口模块(信弛达BM-S02模块)、温度传感器(MCP9808)组成。Android手机端负责和蓝牙模块通信,获取温度数据,并显示。& y0 l3 C5 G- a8 t- C5 l 2. 主要功能- e4 f" N4 z: R, J2 S4 [, g! S0 r 系统重点除了完成一个特定的功能外,另一个目的在于研究CubeMX开发平台和RTOS,完成一系列通用的软件模块。已经完成了一些比较有用的模块,可供网友参考、指正。开发环境基于STM32 CubeMX+FreeRTOS(实际上是CMSIS OS的接口),所有的API调用都是基于CubeMX生成的Hal层API。完成的软件模块包括: 基于uart2和FreeROTS-cli的命令终端,支持上下翻页和命令自动补全); 基于DMA的串口驱动,适合和需要大吞吐的wifi或蓝牙模块通信的场合;% n! Q% y1 Z) K, W/ b 以及一个简单的MCP9808的驱动(CubeMX hal的i2c接口先赞一个)。 3. 实施方案现场实施4 K! p2 P' X& B7 a) v ![]() 蓝牙部分原理(出自官方文档) ![]() BRTS、BCTS、GND接地,UART_TX、UART_RX接Nucleo的UART1_TX(PA9)和UART1_RX(PA10),VCC接3.3V。MCP9808原理图7 w1 q8 U& A8 v A& ?! s E ![]() MCP9808地址可编程,上下拉都接了,实际贴片时会根据需要决定是否贴上电阻,这样可以在一个i2c总线上挂多个MCP9808。 4. 软件实现4 @4 y0 W3 v1 \$ V printf到串口 重载fputc函数0 G7 L& L! {5 L& s* [6 E: _; X' ]! @ #define PRINT_BUF_SIZE 2. c* E# n v$ T2 g static int read_bc = 0; static int __serial_send(char *buf, uint16_t size)# M6 x: m& e) m9 r8 a4 q { HAL_UART_Transmit(&huart2, (uint8_t *)buf, size, 100);; n4 z& T# b# ^) }" i7 P5 v& B return size;8 c( Q8 K! K# O }) W j& T) q/ K2 e1 o4 S int fputc(int ch, FILE *f)0 K( `& c3 Q/ n+ F6 O { /* Place your implementation of fputc here */( p2 `9 k9 m* K0 h6 Q /* e.g. write a character to the USART */ char buf[PRINT_BUF_SIZE];; ~. t( ?5 i/ t buf[0] = ch;; U0 }6 S: b* k n0 u5 x. ^ if (ch == '\r') {- m- X% \7 V w- l) X read_bc = 1; } else if (ch == '\n') {- m3 u9 U- _" g5 q" ^ if (!read_bc) {( A! U: g. W. V, S4 q+ s1 |/ [ buf[1] = '\r';( m) y& O: E& r8 O. n+ C$ c0 u __serial_send(buf, 2); return ch;; p j% ?9 ^: c8 j* h1 ]& a/ y$ r } read_bc = 0; } else if (read_bc) { read_bc = 0; }" i: _3 ~9 N( h4 H7 @; ^( m% v ( x/ q- ~" o" _2 T. z E$ d0 F __serial_send(buf, 1);9 u% G0 ^( m; t4 E$ H. }# B& b8 S return ch; }) X/ ^5 u% R' K/ d0 B - ]8 X; q9 }1 t7 z9 D; K7 u- h3 I 6 a, M& @/ z7 G* i' o 命令终端 8 X& P) r' [! Q! m9 l 基于uart2和FreeRTOS-Cli实现,由于Nucleo的虚拟串口直接和uart2是相通的,因此不需要再另接串口线。9 m/ H2 Z9 P2 E5 }2 s+ b! }: j4 r 总体流程" L8 f# e! Q9 g0 s ![]() 代码逻辑并不复杂,要讲有点麻烦,会把所有代码发出来,参考shell.c代码。9 s$ C2 ^6 X$ h( `5 X 为了命令补全,在FreeRTOS-Cli中加了一个函数,其余FreeRTOS-Cli代码未做修改。8 m3 E; C. l8 `: Z$ Y# L 实际效果如图所示: ![]() 显示不对齐是由于FreeRTOS的vTaskList函数实现导致,不影响调试。% _8 |9 y2 z$ X4 ` UART DMA传输7 Z3 P# k$ P. W, D 主要用于大流量的数据通信,比如:wifi、蓝牙模块和MCU之间的通信。% ~# k' Z2 O- r9 |( N8 u* B9 k 网上有很多关于串口DMA的讨论,主要集中在UART DMA收上面,因为DMA接受需要固定长度的字节数,而这个限制对大部分应用是不可能做到的。所以,有人讨论了用有通信格式的数据包的方式,这个其实也不能完全满足所有需要,比如:这里用的蓝牙模块有的时候会打印一些系统信息,有的时候是远端传过来的数据,不太好区分。 这里的DMA传输无需格式,无需固定长度,实现方式参考了这个帖子http://blog.csdn.net/jdh99/article/details/8444474,现在STM32F103上调通,后来移到Nucleo平台,主要困难在于CubeMX的API比较生疏,研究完具体实现才能明白怎么做。核心在于先启动DMA RX传输,然后在IT_IDLE中断中处理接受的数据包(不完整的DMA RX接收,但一次通信已结束)。 初始化, C$ m* \ j" q# M+ s typedef struct {# a( I. b# Q' t7 P/ P uint8_t *rxbuf; uint8_t *txbuf; uint16_t rxbuf_size;' b. O4 q5 D/ b8 {$ e1 x uint16_t txbuf_size; uint32_t flag;% P0 C' |2 x, s( R6 n$ u4 j osSemaphoreId rx_semp_id, tx_semp_id;" E- g' S2 z# [8 s8 L0 O UART_HandleTypeDef *UART_hd; //DMA_HandleTypeDef *DMARx_hd, *DMATx_hd; serial_rx_callback rx;( P# s6 s( s7 t* B0 I* Z1 e( t) s int rxlen;( B! s. D8 b! l# \- K } uart_dma_info_t; : }4 ^# @9 M, Z( y4 \) j# m int uart_dma_init(UART_HandleTypeDef *UART_hd, serial_rx_callback rx, uint16_t rxbuf_size, " w5 a! d- p9 P$ n1 _- E4 k uint16_t txbuf_size)2 V- q2 I( g& `% P {! x4 S% \" }' i8 W3 W- f: |& k0 h% W( Y int id; uart_dma_info_t *info;7 Z. u6 Q" E; ^7 [+ G osSemaphoreDef(uart_rx_sep); osSemaphoreDef(uart_tx_sep);! Y" o, i2 [2 n osThreadDef(uart_rx_dma, uartRxTask, osPriorityHigh, 0, configMINIMAL_STACK_SIZE+100);' n1 U; g3 a1 q: c1 H: @9 v4 E5 o/ b . K6 d) [) M8 I assert_param(UART_hd != NULL); id = __get_uart_id(UART_hd->Instance);+ A) y! @. i' F4 [ assert_param(id >= 0 && id < MAX_UART_NUM); . o6 A, X3 D) L" {, j info = &uart_info[id]; info->rxbuf_size = rxbuf_size; info->txbuf_size = txbuf_size; info->UART_hd = UART_hd;% ~* j4 N4 f/ H9 D info->rx = rx; if (rxbuf_size > 0) {1 u7 K4 F& d# `1 n assert_param(info->UART_hd->hdmarx);! P! f( ~& y7 P" ~ info->rxbuf = pvPortMalloc(rxbuf_size); assert_param(info->rxbuf);1 n* F% t F% t& { osThreadCreate (osThread(uart_rx_dma), info); info->rx_semp_id = osSemaphoreCreate(osSemaphore(uart_rx_sep), 1);! b- C) S3 B8 k2 @2 x6 R/ g assert_param(info->rx_semp_id);- k8 d' @3 l+ z6 _ osSemaphoreWait(info->rx_semp_id, osWaitForever);//skip the first time+ W8 e) ]* R, k% B . u: M$ |4 @4 N, A* t __HAL_UART_DISABLE(info->UART_hd);) M" z3 K" |& N! V' m info->UART_hd->Instance->CR3 |= UART_DMA_RX_ENABLE; __HAL_UART_ENABLE(info->UART_hd); HAL_DMA_Start(info->UART_hd->hdmarx, (uint32_t)&info->UART_hd->Instance->RDR, (uint32_t)info->rxbuf, info->rxbuf_size); }4 R. }4 a) E) r/ X* x d9 ? if (txbuf_size > 0) {4 L4 ^" i. ]: N6 E. x assert_param(info->UART_hd->hdmatx); info->txbuf = pvPortMalloc(txbuf_size); assert_param(info->txbuf); info->tx_semp_id = osSemaphoreCreate(osSemaphore(uart_tx_sep), 1); assert_param(info->tx_semp_id); osSemaphoreWait(info->tx_semp_id, osWaitForever);// skip the first wait0 G1 U" G' c3 z5 V- a info->UART_hd->hdmatx->XferCpltCallback = uart_dma_send_finsh_cb;) P+ d2 s* B' S1 \% |; j2 a1 l5 W info->UART_hd->hdmatx->XferErrorCallback = uart_dma_send_err_cb;( b: ^( Y2 d" \. ^9 e info->UART_hd->Instance->CR3 |= UART_DMA_TX_ENABLE; } return id;7 ~: z- U7 d3 ~+ d- Q) m } 中断处理 void USART1_IRQHandler(void) { /* USER CODE BEGIN USART1_IRQn 0 */# H' G) ~# i- X( a0 z /* USER CODE END USART1_IRQn 0 */; r: {' D+ w# b, H, o( s6 }% Y- K HAL_UART_IRQHandler(&huart1); /* USER CODE BEGIN USART1_IRQn 1 */9 s0 U) G! ?5 s( w' c2 B6 v2 E if (__HAL_UART_GET_IT(&huart1, UART_IT_IDLE) != RESET) { uart_dma_recv_isr(uart1_fd); }- w/ R+ h* H$ `* ?- U. } $ C4 x/ }; P7 U/ ~ /* USER CODE END USART1_IRQn 1 */$ ^( T/ c. X/ _: p2 t }4 `; K8 d4 `; ] e. y6 x void uart_dma_recv_isr(int fd): D- o; m6 u3 J1 ] { uart_dma_info_t *info;) C8 q7 P, H8 A0 ~ E% q0 o uint32_t temp = 0; ( ~) _& q. D+ s$ K* w d/ K6 f assert_param(fd >= 0 && fd < MAX_UART_NUM); info = &uart_info[fd];4 d5 m. O, n3 V# `4 C0 ?! O1 q" x assert_param(info->UART_hd->hdmarx);: H4 }2 l6 c/ w/ v0 ]" N/ ? , a3 E/ i. N; W' J8 x7 J- [5 D __HAL_UART_CLEAR_IT(info->UART_hd, UART_CLEAR_IDLEF); SET_BIT(info->UART_hd->hdmarx->ErrorCode, HAL_DMA_ERROR_NONE); ) L$ T- o% y+ x, f3 I& N: `: j( J- p /* Change the DMA state */ info->UART_hd->hdmarx->State = HAL_DMA_STATE_READY; 5 }+ c2 n7 t, z( C9 N$ l4 E9 b- r /* Process Unlocked */ __HAL_UNLOCK(info->UART_hd->hdmarx);! L% f6 Z% I! D4 v* l __HAL_DMA_DISABLE(info->UART_hd->hdmarx); temp = info->rxbuf_size - info->UART_hd->hdmarx->Instance->CNDTR; if (temp > 0) {4 [$ l$ H6 N* O" T info->rxlen = temp;8 E0 T* b( I$ k$ {5 G } osSemaphoreRelease(info->rx_semp_id); }' {- I# p" w9 W2 b6 K ' r. o; a& m* d( W* L; W$ F HAL_DMA_Start(info->UART_hd->hdmarx, (uint32_t)&info->UART_hd->Instance->RDR, 1 `+ M" U9 G8 f8 U! P (uint32_t)info->rxbuf, info->rxbuf_size);0 [: ~/ w2 C# r/ I% R3 p } " u- V1 y% q8 p 接收处理线程+ ?. ]3 X9 P( u static void uartRxTask(void const *args) {# ]3 T* D- u' F/ P uart_dma_info_t *info = (uart_dma_info_t *)args; 8 ]& p$ v+ y' i/ i. T while (1) {$ n! d! }4 ^# T0 p% E7 v osSemaphoreWait(info->rx_semp_id, osWaitForever); if (info->rx && info->rxlen > 0) { O; o4 L# a3 A info->rx(info->rxbuf, info->rxlen); } } } - W3 z7 E5 ^( J' v4 G! w( c2 L # x& W, l/ W. U* {: g 具体实现看附件把,贴代码也不太容易说清楚。! D6 @: R+ Z+ |6 o; [ MCP9808驱动; b1 m$ d! I; [& R* U) F; {# D) X 做了一些封装,但基本上只要调用HAL_I2C_Mem_Read就可以搞定了。 9 ^, B: I7 b4 F0 g MCU全部的代码见附件1 p. u% L4 C6 X1 [ ![]() |
嗯,其实这个东西主要是前端和后端难搞,前端指探头部分,如果就是用i2c芯片读读,确实没什么难度,但要是精度高,并且探头要非常小,这个难度一下子就大起来了,像这类的要求在实验室设备上要求比较多。如果是民用,重点还是app要搞好,单片机这里比较简单。/ \& q- {3 G% B$ P7 P Q# m
这次的东西其实硬件和功能都比较简单,重点还是研究基于STM32CubeMX的开发,现在毕竟文档和案例都比较少,弄个这个给大家参考参考。