Remember to like it, please leave a message if you have any questions
Preface
In embedded development, it is often used to print debugging information through the serial port. Sometimes in order to save costs, there is no extra serial port available. Therefore, the debugging information can be printed:
Method 1: Add SEGGER_RTT debugging print in the project
Method 2: Simulate serial port
UART working principle
UART stands for Universal Asynchronous Receiver and Transmitter, which is a serial communication method. In the process of data transmission, communication is realized by transmitting one by one. The serial communication method has the advantages of fewer transmission lines and low cost. The disadvantage is that the speed is slow. Serial communication is divided into two types: synchronous communication and asynchronous communication. However, asynchronous communication methods are generally used, mainly because the receiving and sending clocks can be independent, which is beneficial to increase the flexibility of sending and receiving. Asynchronous communication is a character-by-character transmission. The information of a character consists of a start bit, a data bit, a parity bit, and a stop bit.
The transmission of each character is synchronized by the start bit. The first bit of the character is the start bit, and the falling edge is used to notify the receiver to start the transmission. The data bit is immediately after the start bit, and the low bit is before the high bit during transmission. , The character itself is composed of 5 to 8 data bits. The data bit is followed by the parity bit, and finally the stop bit. The stop bit uses a high level to mark the end of a character and prepares for the transmission of the next character. The stop bit is followed by idle bits of different lengths. Both the stop bit and the idle bit are defined as high level, so that a falling edge of the start bit can be guaranteed. The frame format of
UART is shown in the figure. The frame format of UART includes line idle state (idle, high level), start bit (start bit, low level), 5 to 8 data bits, parity bit, optional) and stop bit (stop bit, the number of bits can be 1, 1.5, 2).
UART simulation principle
The analog mode of UART is basically realized by timer + IO port.
Option 1: Only print but not receive
If in actual use only to print log without receiving data, you can use DWT plus ordinary IO port;
#define VCOM_BOUND 115200
#define VCOM_PIN GPIO_Pin_11
#define VCOM_PORT GPIOA
#define VCOM_PIN_HIGH VCOM_PORT->BSRR = VCOM_PIN
#define VCOM_PIN_LOW VCOM_PORT->BRR = VCOM_PIN
#define BSP_REG_DEM_CR (*(volatile unsigned int *)0xE000EDFC) //DEMCR寄存器
#define BSP_REG_DWT_CR (*(volatile unsigned int *)0xE0001000) //DWT控制寄存器
#define BSP_REG_DWT_CYCCNT (*(volatile unsigned int *)0xE0001004) //DWT时钟计数寄存器
#define BSP_REG_DBGMCU_CR (*(volatile unsigned int *)0xE0042004)
#define DEF_BIT_00 0x01u
#define DEF_BIT_24 0x01000000u
#define BSP_BIT_DEM_CR_TRCENA DEF_BIT_24
#define BSP_BIT_DWT_CR_CYCCNTENA DEF_BIT_00
static unsigned int sys_clock = 48000000;
inline void dwt_start(void)
{
BSP_REG_DEM_CR |= (unsigned int)BSP_BIT_DEM_CR_TRCENA;
BSP_REG_DWT_CYCCNT = (unsigned int)0u; //初始化CYCCNT寄存器
BSP_REG_DWT_CR |= (unsigned int)BSP_BIT_DWT_CR_CYCCNTENA; //开启CYCCNT
}
inline void dwt_stop(void)
{
BSP_REG_DWT_CR = 0;
}
void vcom_pin_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Pin = VCOM_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(VCOM_PORT, &GPIO_InitStructure);
GPIO_SetBits(VCOM_PORT,VCOM_PIN);
VCOM_PIN_HIGH;
}
void vcom_put_char(char ch)
{
int i;
int dat[8];
uint32_t sys_clk, bit_width;
volatile uint32_t time_stamp;
sys_clk = sys_clock/1000000;
bit_width = 1000000*sys_clk/VCOM_BOUND;
for(i=0; i<8; i++)
{
if(ch & 0x01)
dat[i] = 1;
else
dat[i] = 0;
ch >>= 1;
}
OS_CPU_SR cpu_sr;
enter_critical();//以下代码进行临界保护,防止被中断打断造成发送误码
dwt_start();
VCOM_PIN_LOW; //发送起始位
time_stamp = BSP_REG_DWT_CYCCNT;
while(BSP_REG_DWT_CYCCNT < (time_stamp+bit_width));
for(i=0; i<8; i++)
{
if(dat[i])
VCOM_PIN_HIGH;
else
VCOM_PIN_LOW;
time_stamp = BSP_REG_DWT_CYCCNT;
while(BSP_REG_DWT_CYCCNT < (time_stamp+bit_width)); //发8bit 数据位
}
VCOM_PIN_HIGH;
time_stamp = BSP_REG_DWT_CYCCNT;
while(BSP_REG_DWT_CYCCNT < (time_stamp+bit_width)); //发停止位
dwt_stop();
exit_critical();
}
void vcom_printf(const char *fmt, ...)
{
char buf[0x80];
int i;
va_list ap;
memset(buf, 0x00, sizeof(buf));
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
i = 0;
while(buf[i])
{
vcom_put_char(buf[i]);
i++;
}
}
Option 2: Half-duplex UART
Implementation method: ordinary timer + ordinary IO port interrupt + fifo
/**
*软件串口的实现(IO模拟串口)
* 波特率:9600 1-8-N
* TXD : PC13
* RXD : PB14
* 使用外部中断对RXD的下降沿进行触发,使用定时器4按照9600波特率进行定时数据接收。
* Demo功能: 接收11个数据,然后把接收到的数据发送出去
*/
#define OI_TXD PCout(13)
#define OI_RXD PBin(14)
#define BuadRate_9600 100
u8 len = 0; //接收计数
u8 USART_buf[11]; //接收缓冲区
enum{
COM_START_BIT,
COM_D0_BIT,
COM_D1_BIT,
COM_D2_BIT,
COM_D3_BIT,
COM_D4_BIT,
COM_D5_BIT,
COM_D6_BIT,
COM_D7_BIT,
COM_STOP_BIT,
};
u8 recvStat = COM_STOP_BIT;
u8 recvData = 0;
void IO_TXD(u8 Data)
{
u8 i = 0;
OI_TXD = 0;
delay_us(BuadRate_9600);
for(i = 0; i < 8; i++)
{
if(Data&0x01)
OI_TXD = 1;
else
OI_TXD = 0;
delay_us(BuadRate_9600);
Data = Data>>1;
}
OI_TXD = 1;
delay_us(BuadRate_9600);
}
void USART_Send(u8 *buf, u8 len)
{
u8 t;
for(t = 0; t < len; t++)
{
IO_TXD(buf[t]);
}
}
void IOConfig(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
EXTI_InitTypeDef EXTI_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC, ENABLE); //使能PB,PC端口时钟
//SoftWare Serial TXD
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_SetBits(GPIOC,GPIO_Pin_13);
//SoftWare Serial RXD
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);
EXTI_InitStruct.EXTI_Line = EXTI_Line14;
EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Falling; //下降沿触发中断
EXTI_InitStruct.EXTI_LineCmd=ENABLE;
EXTI_Init(&EXTI_InitStruct);
NVIC_InitStructure.NVIC_IRQChannel= EXTI15_10_IRQn ;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority =2;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void TIM4_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //时钟使能
//定时器TIM4初始化
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
TIM_ClearITPendingBit(TIM4, TIM_FLAG_Update);
TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断
//中断优先级NVIC设置
NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn; //TIM4中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //先占优先级1级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //从优先级1级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器
}
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
delay_init();
IOConfig();
TIM4_Int_Init(107, 71); //1M计数频率
while(1)
{
if(len > 10)
{
len = 0;
USART_Send(USART_buf,11);
}
}
}
void EXTI15_10_IRQHandler(void)
{
if(EXTI_GetFlagStatus(EXTI_Line14) != RESET)
{
if(OI_RXD == 0)
{
if(recvStat == COM_STOP_BIT)
{
recvStat = COM_START_BIT;
TIM_Cmd(TIM4, ENABLE);
}
}
EXTI_ClearITPendingBit(EXTI_Line14);
}
}
void TIM4_IRQHandler(void)
{
if(TIM_GetFlagStatus(TIM4, TIM_FLAG_Update) != RESET)
{
TIM_ClearITPendingBit(TIM4, TIM_FLAG_Update);
recvStat++;
if(recvStat == COM_STOP_BIT)
{
TIM_Cmd(TIM4, DISABLE);
USART_buf[len++] = recvData;
return;
}
if(OI_RXD)
{
recvData |= (1 << (recvStat - 1));
}else{
recvData &= ~(1 << (recvStat - 1));
}
}
}
Reference code: https://github.com/sckuck-bit/SoftWareSerial
Option 2: Full-duplex UART
Realization idea: adopt timer capture and compare, and require timer to correspond to IO
Code: Slightly
in conclusion
串口打印会占用CPU,调试完成记得关闭调试信息。