这个板子是好久之前画好的,焊接之后一直放着吃灰了。趁着参加活动拿出来用用。板子如下:

使用的是STM32G070KBT6型号MCU。该MCU是高性能Arm® Cortex®-M0+ 32位内核,工作频率可达64 MHz。有128 KB Flash程序存储器以及36 KB SRAM,DMA、丰富的系统功能、增强型I/O和外设。提供了多个标准通信接口,包含2个I2C、2个SPI/1个I2S和4个USART、1个具有多达19通道的12位ADC (2.5 MSps)、一个低功耗RTC、一个高级控制PWM定时器、5个通用16位定时器、2个基本定时器、2个看门狗定时器和SysTick定时器。
板载有:4个用户按键,复位按键,LED指示灯,红外IR接收头,0.96寸OLED。USB转串口CH340, SPI FLASH W25Q32和TF卡座。另外扩展有1路SPI,I2C,UART通信接口,以及4个IO接口。
板子电路图如下:

这个板子是准备用来做一个简单的工具的,可以测试IR红外接收头,用于分析红外遥控编码解析。可以用于串口协议通信,串口shell控制,YModem协议测试传输文件,flash和TF卡的fatfs文件系统数据存储,I2C接口传感器测试,SPI接口控制,IO各种可控频率脉冲输出等等。
下面我先基于这个板子分享UART串口shell功能。
首先打开STM32CubeMX软件,新建初始化工程,通过界面初始化各个外设。这里2个串口都选择串口初始化参数115200波特率,使用中断方式发送接收数据。





系统时钟配置最高64MHz。

最后生成KEIL初始化代码工程。

接下来就是串口框架代码编写了。就是串口的printf重定向,以及shell移植功能。
printf串口输出重定向有2个方法。第一个是勾选target的MicroLib,然后实现一个int fputc(int ch,FILE *f)串口输出函数。

第二个方法就是不勾选MicroLib,然后在pack里面勾选compiler的STDOUT User,再实现一个int stdout_putchar (int ch)串口输出函数。

我的串口部分代码如下,使用中断发送接收,通过FIFO缓存数据,这样通信不容易掉数据。
#include<RTE_Components.h>
#undef __USE_EVENT_RECORDER__
#if defined(RTE_Compiler_EventRecorder) || defined(RTE_CMSIS_View_EventRecorder)
# define __USE_EVENT_RECORDER__ 1
#endif
#if __USE_EVENT_RECORDER__
# include <EventRecorder.h>
# include "EventRecorderConf.h"
#endif
typedef struct fifo_buffer_t
{
volatile uint32_t read_i;
volatile uint32_t write_i;
uint8_t buff[UART_FIFO_BUFF_LEN];
}fifo_buffer_t;
typedef struct shell_uart_buffer_t
{
fifo_buffer_t tx;
fifo_buffer_t rx;
volatile uint32_t tx_cpl : 1;
void *userdata;
}shell_uart_buffer_t;
extern shell_uart_buffer_t g_shell_uart[];
typedef struct stm32_uart_para_t
{
UART_HandleTypeDef * _uart;
uint8_t uart_recv_buff[4];
uint8_t uart_send_buff[128];
}stm32_uart_para_t;
stm32_uart_para_t stm32_uart_p[UART_DEV_NUM];
shell_uart_buffer_t g_shell_uart[UART_DEV_NUM];
static void uart_get_data_send(uint8_t index)
{
uint32_t i;
//
for(i=0;i<128;i++)
{
if(g_shell_uart[index].tx.read_i != g_shell_uart[index].tx.write_i)
{
((stm32_uart_para_t *)g_shell_uart[index].userdata)->uart_send_buff[i] = g_shell_uart[index].tx.buff[g_shell_uart[index].tx.read_i++];
g_shell_uart[index].tx.read_i &= (UART_FIFO_BUFF_LEN-1); //256Byte
}else break;
}
if(i)
{
g_shell_uart[index].tx_cpl = 1;
HAL_UART_Transmit_IT(((stm32_uart_para_t *)g_shell_uart[index].userdata)->_uart,
((stm32_uart_para_t *)g_shell_uart[index].userdata)->uart_send_buff,i);
}
}
void uart_flush_data(uint8_t index)
{
if(g_shell_uart[index].tx_cpl == 0)
{
uart_get_data_send(index);
}
}
void uart_send_char(uint8_t index, uint8_t ch)
{
while(((g_shell_uart[index].tx.write_i+1) & (UART_FIFO_BUFF_LEN-1)) == g_shell_uart[index].tx.read_i)
{
//fifo buffer full
if((g_shell_uart[index].tx_cpl == 0))
{
uart_get_data_send(index);
}
}
g_shell_uart[index].tx.buff[g_shell_uart[index].tx.write_i++] = ch;
g_shell_uart[index].tx.write_i &= (UART_FIFO_BUFF_LEN-1);//256Byte
uart_flush_data(index);
}
uint8_t uart_recv_char(uint8_t index, uint8_t *ch)
{
if(g_shell_uart[index].rx.read_i != g_shell_uart[index].rx.write_i)
{
*ch = g_shell_uart[index].rx.buff[g_shell_uart[index].rx.read_i++];
g_shell_uart[index].rx.read_i &= (UART_FIFO_BUFF_LEN-1); //256Byte
return 0;
}
return 1;
}
串口中断回调函数处理:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
for(int i=0;i<UART_DEV_NUM;i++)
{
if(huart->Instance == ((stm32_uart_para_t *)g_shell_uart[i].userdata)->_uart->Instance) //shell
{
if(((g_shell_uart[i].rx.write_i+1) & (UART_FIFO_BUFF_LEN-1)) != g_shell_uart[i].rx.read_i)
{
g_shell_uart[i].rx.buff[g_shell_uart[i].rx.write_i++] = ((stm32_uart_para_t *)g_shell_uart[i].userdata)->uart_recv_buff[0];
g_shell_uart[i].rx.write_i &= (UART_FIFO_BUFF_LEN-1);//256Byte
}
HAL_UART_Receive_IT(huart,((stm32_uart_para_t *)g_shell_uart[i].userdata)->uart_recv_buff,1);//
}
}
}
//
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
for(int i=0;i<UART_DEV_NUM;i++)
{
if(huart->Instance == ((stm32_uart_para_t *)g_shell_uart[i].userdata)->_uart->Instance) //shell
{
g_shell_uart[i].tx_cpl = 0;
uart_get_data_send(i);
}
}
}
//
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
for(int i=0;i<UART_DEV_NUM;i++)
{
if(huart->Instance == ((stm32_uart_para_t *)g_shell_uart[i].userdata)->_uart->Instance) //shell
{
HAL_UART_Receive_IT(huart,((stm32_uart_para_t *)g_shell_uart[i].userdata)->uart_recv_buff,1);//
}
}
}
再就是串口shell移植处理,这里我已经实现了nr_micro_shell和letter shell移植。还有一个我自己在AI帮助下写的shell。
这里以我自己写的shell为例,只要实现一个串口输出字符串函数和读取串口接收的字符函数即可。然后主循环调用void shell_usart_init(void)初始化和void shell_usart_loop(void)shell解析循环。
uint8_t USART_PutChar(uint8_t ch)
{
uart_send_char(UART_SHELL_DEV_INDEX, ch);
return 0;
}
uint8_t USART_GetChar(uint8_t *ch)
{
return uart_recv_char(UART_SHELL_DEV_INDEX, ch);
}
int USART_PutStr(const char* str, uint32_t len)
{
char *s= (char*)str;
int sl = len;
while(len --) USART_PutChar(*s++);
return sl;
}
int USART_GetStr(const char* str, uint32_t len)
{
uint8_t *s= (uint8_t *)str;
uint32_t rlen = 0;
for(uint32_t i=0;i<len;i++)
{
if(0 == uart_recv_char(UART_SHELL_DEV_INDEX, &s[rlen]))
{
rlen++;
}
}
return rlen;
}
void shell_usart_init(void)
{
stm32_uart_p[0]._uart = &huart1;
g_shell_uart[0].tx.read_i = 0;
g_shell_uart[0].tx.write_i = 0;
g_shell_uart[0].rx.read_i = 0;
g_shell_uart[0].rx.write_i = 0;
g_shell_uart[0].tx_cpl = 0;
g_shell_uart[0].userdata = &stm32_uart_p[0];
stm32_uart_p[1]._uart = &huart3;
g_shell_uart[1].tx.read_i = 0;
g_shell_uart[1].tx.write_i = 0;
g_shell_uart[1].rx.read_i = 0;
g_shell_uart[1].rx.write_i = 0;
g_shell_uart[1].tx_cpl = 0;
g_shell_uart[1].userdata = &stm32_uart_p[1];
#if __USE_EVENT_RECORDER__
EventRecorderInitialize(0, 1);
#endif
#ifdef UART_SHELL
#if UART_SHELL == 1
userShellInit(); //LETTER_SHELL
#elif UART_SHELL == 2
shell_init(); //NR_MICRO_SHELL
#elif UART_SHELL == 3
// 配置IO操作
shell_io_ops_t io_ops = {
.send = USART_PutStr,
};
lsy_shell_init(&io_ops); //LSY_SHELL
#else
#endif
#endif
/* 启动非阻塞式读取接收数据 */
for(int i=0;i<UART_DEV_NUM;i++)
{
HAL_UART_Receive_IT(((stm32_uart_para_t *)g_shell_uart[i].userdata)->_uart,
((stm32_uart_para_t *)g_shell_uart[i].userdata)->uart_recv_buff,1);//
}
}
void shell_usart_loop(void)
{
uint8_t ch;
if(0 == uart_recv_char(UART_SHELL_DEV_INDEX, &ch))
{
#ifdef UART_SHELL
#if UART_SHELL == 1
shellHandler(&shell, ch); //letter shell
#elif UART_SHELL == 2
shell(ch);
#elif UART_SHELL == 3
lsy_shell_input(ch);
#else
USART_PutChar(ch);
#endif
#endif
}
//将串口FIFO内数据继续发送
for(int i=0;i<UART_DEV_NUM;i++)
{
uart_flush_data(i);
}
}

我写的这个shell支持特殊控制按键功能,历史命令记录功能和用户命令权限控制功能。
命令编写例子:
int cmd_led_control(int argc, char **argv)
{
uint32_t pinstate = 1;
if (argc != 2) {
printf("Usage: led <on|off>\r\n");
return SHELL_ERR_PARAM;
}
if(!strcmp(argv[1], "on"))
{
pinstate = 0;
}else if(!strcmp(argv[1],"off"))
{
pinstate = 1;
}else
{
printf("cmd para ERROR!\n");
return SHELL_ERR_PARAM;
}
printf("led %s\r\n",pinstate ? "off":"on");
return SHELL_OK;
}
LSY_SHELL_CMD_EXPORT(led, cmd_led_control, "Control LED: led <on|off>");
最后shell命令执行效果如下:

shell核心代码和串口发送接收