バックグラウンド
なぜここでシリアル通信を書くかというと、実際のプロジェクトではシリアルポートを使用するため、STM8S003F3P6 のシリアルポートは単純であり、特に言及する必要はありません。この記事で書かれているシリアル ポートは非常に単純です。ここでは簡単なものから始めて、徐々に難しいものを取り上げていきたいと思います。ここで概要を説明すると、この記事で扱うシリアル ポートは STM8S003F3P6 チップの IO アナログ シリアル ポートを使用します。STM8S003F3P6 のリソースは限られているため、2 台のマシンの通信リソースでは不足することがよくありますが、次の記事では IO を使用してデータ送受信用のシリアル ポートをシミュレートすることを提案します。
IOシリアルポートのシミュレーションはやはり難しく、デバッグには非常に時間がかかり、1週間以上ここでデバッグした記憶があります。デバッグを行う前にシリアルポートのタイミングを深く理解する必要があり、シリアルポートのタイミングが明確でないとコードを読むのが混乱してしまいます。
回路図
上に示すように、ここに STM8S003F3P6 のシリアル ポートがあります。
もちろん、ここでタイトルに挙げたシリアルポートの2台通信とは、他のシングルチップマイコンを指したり、コンピュータのシリアルポートに直接対応したりすることも可能です。
シリアル TTL レベルを RS232 または他のレベルに変換したのかと言う人もいるでしょう。
はい、確かにここには誰もいません。ここでは、上の写真の CN2 を介して小型 TTL-to-USB モジュールを接続し、デバッグ用にコンピュータまたはノートブックに接続できます。
ソフトウェア設計
システムのメイン周波数は、内部クロック 16M、つまり HSI 1 分周を使用して設定されます。
シリアルポートのボーレートには遅延があり、遅延時間はメイン周波数と密接に関係しているため、ここでクロックについて言及する必要があります。
/************************************************
函数名称 : CLK_Configuration
功 能 : 时钟配置
参 数 : 无
返 回 值 : 无
作 者 :
*************************************************/
void CLK_Configuration(void)
{
/*
ErrorStatus clk_return_status;
CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV8); //HSI = 16M (8分频)=2MHZ
//切换内部低速时钟128khz
clk_return_status = CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_LSI, DISABLE, CLK_CURRENTCLOCKSTATE_DISABLE);
if (clk_return_status == SUCCESS) //SUCCESS or ERROR
{
CLK_ClockSwitchCmd(ENABLE);
CLK_LSICmd(ENABLE);
CLK_ClockSwitchCmd(DISABLE);
}*/
// CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1); //HSI = 16M (1分频)
//ErrorStatus clk_return_status;
CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1); //HSI = 16M (8分频)=2MHZ
/*
//切换内部低速时钟8M
clk_return_status = CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSE, DISABLE, CLK_CURRENTCLOCKSTATE_DISABLE);
if (clk_return_status == SUCCESS) //SUCCESS or ERROR
{
CLK_ClockSwitchCmd(ENABLE);
CLK_HSECmd(ENABLE);
CLK_ClockSwitchCmd(DISABLE);
}*/
CLK_DeInit();//设置为默认值
CLK_HSICmd(ENABLE);//启用HSI
CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1);//HSI分频
CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1);//CPU分频
}
シリアルポートの初期化設定
概略図に示すように、ここで設定します。
PC4 はシリアル ポートの送信ピンで、出力として構成されています。
PC5 はシリアル ポートの受信ピンであり、入力割り込みとして構成されます。
void IO_UART_init(void)
{
vm_UART_RX_O = 0;
vm_UART_RX_P = 0;
memset(vm_UART_RX_BUF, 0 ,sizeof(vm_UART_RX_BUF));
//VM_UART_TXD_PORT_IN;//发送初始化
//外部中断
///EXTI_CR1 &=~(3<<4);//清零///这里源程序针对PC管脚配置外部中断,需要修改PB管脚即可
///EXTI_CR1 |=2<<4;//下降沿触发
VM_UART_TXD_PORT_OUT;
VM_UART_RXD_PORT_INT_IN;//接收初始化
EXTI_CR1 &=~(3<<4);//清零//这里需要修改为PC管脚
//EXTI_CR1 |=3<<2;//上升沿和下降沿触发
EXTI_CR1 |=2<<4;//下降沿触发
}
関連するマクロは次のように定義されます。
#define PC_ODR (GPIOC->ODR)
#define PC_IDR (GPIOC->IDR)
#define PC_DDR (GPIOC->DDR)
#define PC_CR1 (GPIOC->CR1)
#define PC_CR2 (GPIOC->CR2)
#define PB_ODR (GPIOC->ODR)
#define PB_IDR (GPIOC->IDR)
#define PB_DDR (GPIOC->DDR)
#define PB_CR1 (GPIOC->CR1)
#define PB_CR2 (GPIOC->CR2)
#define EXTI_CR1 (EXTI->CR1)
#define TIM2_CR1 (TIM2->CR1)
#define TIM2_IER (TIM2->IER)
#define TIM2_SR1 (TIM2->SR1)
#define TIM2_SR2 (TIM2->SR2)
#define TIM2_EGR (TIM2->EGR)
#define TIM2_CCMR1 (TIM2->CCMR1)
#define TIM2_CCMR2 (TIM2->CCMR2)
#define TIM2_CCMR3 (TIM2->CCMR3)
#define TIM2_CCER1 (TIM2->CCER1)
#define TIM2_CCER2 (TIM2->CCER2)
#define TIM2_CNTRH (TIM2->CNTRH)
#define TIM2_CNTRL (TIM2->CNTRL)
#define TIM2_PSCR (TIM2->PSCR)
#define TIM2_ARRH (TIM2->ARRH)
#define TIM2_ARRL (TIM2->ARRL)
#define TIM2_CCR1H (TIM2->CCR1H)
#define TIM2_CCR1L (TIM2->CCR1L)
#define TIM2_CCR2H (TIM2->CCR2H)
#define TIM2_CCR2L (TIM2->CCR2L)
#define TIM2_CCR3H (TIM2->CCR3H)
#define TIM2_CCR3L (TIM2->CCR3L)
#define TIM1_CR1 (TIM1->CR1)
#define TIM1_CR2 (TIM1->CR2)
#define TIM1_SMCR (TIM1->SMCR)
#define TIM1_ETR (TIM1->ETR)
#define TIM1_IER (TIM1->IER)
#define TIM1_SR1 (TIM1->SR1)
#define TIM1_SR2 (TIM1->SR2)
#define TIM1_EGR (TIM1->EGR)
#define TIM1_CCMR1 (TIM1->CCMR1)
#define TIM1_CCMR2 (TIM1->CCMR2)
#define TIM1_CCMR3 (TIM1->CCMR3)
#define TIM1_CCMR4 (TIM1->CCMR4)
#define TIM1_CCER1 (TIM1->CCER1)
#define TIM1_CCER2 (TIM1->CCER2)
#define TIM1_CNTRH (TIM1->CNTRH)
#define TIM1_CNTRL (TIM1->CNTRL)
#define TIM1_PSCRH (TIM1->PSCRH)
#define TIM1_PSCRL (TIM1->PSCRL)
#define TIM1_ARRH (TIM1->ARRH)
#define TIM1_ARRL (TIM1->ARRL)
#define TIM1_RCR (TIM1->RCR)
#define TIM1_CCR1H (TIM1->CCR1H)
#define TIM1_CCR1L (TIM1->CCR1L)
#define TIM1_CCR2H (TIM1->CCR2H)
#define TIM1_CCR2L (TIM1->CCR2L)
#define TIM1_CCR3H (TIM1->CCR3H)
#define TIM1_CCR3L (TIM1->CCR3L)
#define TIM1_CCR4H (TIM1->CCR4H)
#define TIM1_CCR4L (TIM1->CCR4L)
#define TIM1_BKR (TIM1->BKR)
#define TIM1_DTR (TIM1->DTR)
#define TIM1_OISR (TIM1->OISR)
#define TIM4_CR1 (TIM4->CR1)
#define TIM4_IER (TIM4->IER)
#define TIM4_SR1 (TIM4->SR1)
#define TIM4_EGR (TIM4->EGR)
#define TIM4_CNTR (TIM4->CNTR)
#define TIM4_PSCR (TIM4->PSCR)
#define TIM4_ARR (TIM4->ARR)
#define TIM1_timer 1667 //接收定时器
#define TIM2_timer 1667 //发送定时器
//#define TIM1_timer 139 //接收定时器
//#define TIM2_timer 139 //发送定时器
#define vm_UART_RX_BUF_L 32//接收缓存长度
#define TIM2_START TIM2_CNTRL = 0;TIM2_CNTRH = 0;TIM2_CR1|= 1<<0 //计数器置零,启动定时器2
#define TIM2_STOP TIM2_CR1 &= (~1<<0) //停止定时器
#define TIM1_START TIM1_CNTRL = 0;TIM1_CNTRH = 0;TIM1_CR1 |= 1<<0 //计数器置零,启动定时器1
#define TIM1_STOP TIM1_CR1 &= ~(1<<0) //停止定时器
#define VM_UART_TXD_PORT_WriteHigh PC_ODR|= 1<<4 //高电平
#define VM_UART_TXD_PORT_WriteLow PC_ODR &=~(1<<4) //低电平
#define VM_UART_TXD_PORT_OUT PC_DDR|=1<<4 ;PC_CR1|=(1<<4 );PC_CR2 &=~(1<<4)//设定为输出
#define VM_UART_TXD_PORT_IN PC_DDR&=~(1<<4 );PC_CR1|=(1<<4 );PC_CR2&=~(1<<4 )//设定为输入
#define VM_UART_RXD_PORT_IN PB_DDR&=~(1<<5);PB_CR1|=(1<<5);PB_CR2&=~(1<<5) //设置只上拉输入 不中断
#define VM_UART_RXD_PORT_INT_IN PB_DDR&=~(1<<5);PB_CR1|=(1<<5);PB_CR2|=(1<<5) //设置只上拉输入 中断
シリアルポートの受信スキャン機能としてタイマー4を使用する
一般に、シリアル ポートの送信は比較的単純で、シリアル ポートのタイミングに従って PC4 ピンを High および Low にプルするだけで済みます。
ただし、シリアルポートの受信はさらに面倒です
ここでまず受け取りの考え方を説明します
タイマーを開始してシリアルポート受信ピン PC5 を継続的にスキャンし、スキャンしたデータを抽出して最終受信データを取得します。
/* TIM4 configuration:
- TIM4CLK is set to 16 MHz, the TIM4 Prescaler is equal to 128 so the TIM1 counter
clock used is 16 MHz / 128 = 125 000 Hz
- With 125 000 Hz we can generate time base:
max time base is 2.048 ms if TIM4_PERIOD = 255 --> (255 + 1) / 125000 = 2.048 ms
min time base is 0.016 ms if TIM4_PERIOD = 1 --> ( 1 + 1) / 125000 = 0.016 ms
- In this example we need to generate a time base equal to 1 ms
so TIM4_PERIOD = (0.001 * 125000 - 1) = 124 */
void Tim4_Init(void)
{
TIM4_DeInit();
//TIM4_TimeBaseInit(TIM4_PRESCALER_32, 52);
///TIM4_TimeBaseInit(TIM4_PRESCALER_8, 208);
TIM4_TimeBaseInit(TIM4_PRESCALER_8, 208);
///TIM4_TimeBaseInit(TIM4_PRESCALER_16, 21);//提升5倍采样率,没有用
//TIM4_TimeBaseInit(TIM4_PRESCALER_32, 208);
TIM4_ClearFlag(TIM4_FLAG_UPDATE); //清除标志位
TIM4_ARRPreloadConfig(ENABLE);
TIM4_ITConfig(TIM4_IT_UPDATE, ENABLE); //使能更新UPDATE中断
/* Enable TIM4 */
TIM4_Cmd(DISABLE);
}
シリアルポート受信データの実現
まず、シリアル ポート受信ピンが割り込みを捕捉し、タイマー 4 を開始します。
下図に示すように、すべてのレセプションストーリーはここから始まります
ここでは、まず PC5 ピンを入力モードとして設定し、いくつかの変数をクリアして、タイマー 4 を開始します。
/**
* @brief External Interrupt PORTC Interrupt routine.
* @param None
* @retval None
*/
INTERRUPT_HANDLER(EXTI_PORTC_IRQHandler, 5)
{
/* In order to detect unexpected events during development,
it is recommended to set a breakpoint on the following instruction.
*/
//外中断一次收一个字节,只识别起始位
if((PB_IDR &0x20) == 0)
{
VM_UART_RXD_PORT_IN; //只上拉输入 不中断
vm_uart_rx_flag = 1;//开启发送
vm_UART_RX_byte = 0;
vm_UART_RX_bit = 0;
//TIM1_START;//启动定时器
TIM4_CNTR = 0;//清零计数器
TIM4_CR1|= 1<<0;//启动定时器
}
}
タイマ4の割り込み機能は以下の通りです。
タイマー 4 の割り込み機能は、PC5 ピンを継続的にスキャンしてシリアル ポート データを取得することです。ここでは、シリアル ポートのタイミングをある程度理解する必要があります。
/**
* @brief Timer4 Update/Overflow Interrupt routine.
* @param None
* @retval None
*/
INTERRUPT_HANDLER(TIM4_UPD_OVF_IRQHandler, 23)
{
/* In order to detect unexpected events during development,
it is recommended to set a breakpoint on the following instruction.
*/
TIM4_SR1&=~(1<<0);//清空标志位
if((PB_IDR &0x20) == 0)
{
vm_UART_RX_byte /= 2;
}
else
{
vm_UART_RX_byte /= 2;
vm_UART_RX_byte |= 0X80;
}
vm_UART_RX_bit++;
if(vm_UART_RX_bit >= 8)
{
//GPIO_WriteReverse(GPIOB, GPIO_PIN_4);//高取反,低取高,必须配置为输出
///(uint8_t)PC_ODR_ODR4>0?0:1;//高取反,低取高,必须配置为输出
//TIM4_STOP;//停止定时器
TIM4_CR1 &= ~(1<<0);
VM_UART_RXD_PORT_INT_IN;
vm_uart_rx_flag = 0;
vm_UART_RX_bit = 0;//
//if(((vm_UART_RX_P + 1) & (vm_UART_RX_BUF_L - 1)) != vm_UART_RX_O)
{
vm_UART_RX_BUF[vm_UART_RX_P] = vm_UART_RX_byte; //一个字节时接收完毕
vm_UART_RX_P++;
vm_UART_RX_P &= (vm_UART_RX_BUF_L - 1);
}
}
}
シリアルポート送信機能
波形に応じて対応する遅延を行い、対応するタイミングを引き出します。
//发送一个字节
//以stm8中9600bit/s的波特率计算的过程为例(1秒钟传输9600位)。
//可以计算出传输1位所需要的时间 T1 = 1/9600 约为104us。
void WriteByte( unsigned char sdata ) //波特率9600
{
//如果数据误码率比较高,可以修改delay_us延时时间
unsigned char i;
unsigned char value = 0;
VM_UART_TXD_PORT_OUT;
//发送起始位
Send_0();
delay_us( DELAY_CNT );
//发送数据位
for( i = 0; i < 8; i++ )
{
value = ( sdata & 0x01 ); //先传低位
if( value )
{
Send_1();
}
else
{
Send_0();
}
delay_us( DELAY_CNT ); //经测试延时100us 数据没有误差 示波器上观察波形时间为104us左右
sdata = sdata >> 1;
}
//停止位
Send_1();
delay_us( DELAY_CNT );
VM_UART_TXD_PORT_WriteHigh;
///VM_UART_TXD_PORT_IN;
}
//发送字符串
void WriteString( unsigned char *s )
{
while( *s != 0 )
{
WriteByte( *s );
//vm_UARTsend_byte(*s);
s++;
}
}